Files
readest/apps/readest-app/src/components/settings/primitives/BoxedList.tsx
T
Huang Xin 787bbf2103 feat(reader): custom hardware-button page turning (#4177)
* feat(reader): add custom hardware-button page turning (#4139)

Lets users bind hardware remote keys (media keys, D-pad/arrow keys) to
previous/next page via a learn-mode capture UI in reader settings — an
accessibility feature for page-turner remotes.

- New global hardwarePageTurner system setting (enabled + key bindings).
- hardwareKeys.ts: key normalization, matching, and page-turn resolution.
- deviceStore: reference-counted media-key interception + learn mode.
- usePagination: flips pages from bound media keys (native bridge) and
  D-pad/keyboard keys (DOM keydown), scoped to the active book and
  suppressed while the toolbar is visible.
- Page Turner settings section on all platforms; web/desktop bind keys
  via DOM keydown only, native media-key interception stays mobile-only.
- Android: intercept media + learn-mode keys in dispatchKeyEvent.
- iOS: forward media keys via MPRemoteCommandCenter.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* chore(i18n): add and translate hardware page turner strings (#4139)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* feat(reader): refine hardware page turner (#4139)

- Handle book-iframe key events (iframe-keydown messages) so custom
  bindings work as soon as a book is open, not only after the settings
  panel has been shown.
- Add Previous/Next Section bindings alongside the page bindings.
- Rename the hardwareKeys util to keybinding.
- Wire the Page Turner section into the settings Reset action.
- Drop the focus ring on the capture buttons; BoxedList gains an
  optional description.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* chore(i18n): translate page turner section and key strings (#4139)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-15 19:57:33 +02:00

58 lines
1.9 KiB
TypeScript

import clsx from 'clsx';
import React from 'react';
import SectionTitle from './SectionTitle';
interface BoxedListProps {
/**
* Optional small-uppercase label above the boxed list (Adwaita
* AdwPreferencesGroup style). Style is fixed: caller passes the string.
*/
title?: string;
/**
* Optional one-line description rendered between the title and the list.
* Use sparingly — most groups need just the label.
*/
description?: React.ReactNode;
/** Child rows — typically `<SettingsRow>` / `<SettingsSwitchRow>` / `<NavigationRow>`. */
children: React.ReactNode;
/** Outer wrapper className (spacing, data-setting-id ancestor, etc.). */
className?: string;
/** Inner card className (borders, bg, etc.). */
cardClassName?: string;
/** Inner wrapper className (padding, etc.). */
innerClassName?: string;
/** Forwarded to the outer wrapper for command-palette deep-linking. */
'data-setting-id'?: string;
}
/**
* Adwaita-style `AdwPreferencesGroup` container. Renders an optional small
* uppercase title + description, then the boxed-list card with `divide-y`
* rows inside. See DESIGN.md §5.
*/
const BoxedList: React.FC<BoxedListProps> = ({
title,
description,
children,
className,
cardClassName,
innerClassName,
'data-setting-id': dataSettingId,
}) => {
return (
<div className={clsx('w-full', className)} data-setting-id={dataSettingId}>
{title && <SectionTitle className='mb-2'>{title}</SectionTitle>}
<div className={clsx('card eink-bordered border-base-200 bg-base-100 border', cardClassName)}>
<div className={clsx('divide-base-200 divide-y ps-4', innerClassName)}>{children}</div>
</div>
{description && (
<p className='text-base-content/65 mb-2 mt-1 ps-4 text-[0.8em] leading-relaxed'>
{description}
</p>
)}
</div>
);
};
export default BoxedList;