Files
fusion/packages/dashboard/app/components/settings/sections/KeyboardShortcutsSection.tsx
gsxdsm 4b7f0d2d0e fix(dashboard): theme every Settings checkbox and move all inline help behind ? icons
- One settings-scoped checkbox rule (accent, size, focus ring) so the Advanced-settings
  toggle, SettingsToggleRow, ntfy/webhook card headers, and MCP toggle stop falling
  back to the browser-default accent.
- Fix the empty/off-screen help bubble on mobile: .notification-provider-header and
  .settings-field-label-row are now positioned ancestors for SettingsHelpTip.
- Migrate every remaining inline <small> description across settings sections to the
  shared SettingsHelpTip "?" affordance (validation errors and live status stay inline).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-16 01:06:43 -07:00

84 lines
5.2 KiB
TypeScript

import { useTranslation } from "react-i18next";
import type { SectionBaseProps } from "./context";
import { SettingsHelpTip } from "../SettingsHelpTip";
import { ShortcutCaptureInput } from "./ShortcutCaptureInput";
import {
DEFAULT_DASHBOARD_KEYBOARD_SHORTCUTS,
SHORTCUT_CATEGORIES,
describeShortcutValidation,
getShortcutActionLabel,
normalizeKeyboardShortcut,
resolveDashboardKeyboardShortcuts,
type DashboardShortcutAction,
} from "../../../utils/keyboardShortcuts";
export type KeyboardShortcutsSectionProps = SectionBaseProps;
/*
FNXC:DashboardShortcuts 2026-07-04-00:00:
FN-7553 promotes keyboard shortcuts from two bare inputs buried in Global General to their own dedicated settings section, grouped by category (Communication/Workspace/Navigation/Tasks from SHORTCUT_CATEGORIES) with a press-to-record capture control per row. `dashboardKeyboardShortcuts` ownership moved here from `global-general` (save-split.ts GLOBAL_SECTION_KEYS + section-keys.ts) so exactly one section owns the key for save/reset.
FNXC:SettingsStyling 2026-07-15-17:35:
This section stays off the shared settings row primitives, unlike its neighbors. It renders no plain setting: every row is a ShortcutCaptureInput bound to one action inside the single `dashboardKeyboardShortcuts` map. A descriptor row keys on a settings field name and there is no field per shortcut — so there is nothing here for a toggle/text/select row to own, and no `.search.ts` sibling. Shortcut discovery is served by the nav entry's `searchableText` in SettingsModal instead.
FNXC:SettingsHelp 2026-07-16-12:45:
Inline help moved behind the shared "?" affordance — operator requirement: no inline description paragraphs in Settings. The section description sits beside the heading; each row's static "Default: … Leave blank to disable." hint moved into a tip beside its label. The per-row `small` (still `aria-describedby` target via `hintId`) now carries ONLY live capture validation (`normalizeKeyboardShortcut` errors), and the conflict banner stays inline — validation feedback never hides behind a "?".
*/
export function KeyboardShortcutsSection({ form, setForm }: KeyboardShortcutsSectionProps) {
const { t } = useTranslation("app");
const shortcutValues = resolveDashboardKeyboardShortcuts(form.dashboardKeyboardShortcuts);
const shortcutValidationMessage = describeShortcutValidation(shortcutValues);
const updateShortcut = (action: DashboardShortcutAction, value: string) => setForm((f) => ({
...f,
dashboardKeyboardShortcuts: {
...resolveDashboardKeyboardShortcuts(f.dashboardKeyboardShortcuts),
[action]: value,
},
}));
return (
<>
<div className="settings-field-label-row">
<h4 className="settings-section-heading">{t("settings.keyboardShortcuts.title", "Keyboard Shortcuts")}</h4>
<SettingsHelpTip settingKey="dashboardKeyboardShortcuts">{t("settings.keyboardShortcuts.hint", "Configure global dashboard shortcuts. Click Record and press a combination, or type one manually. Shortcuts are ignored while typing in inputs, editors, chat composers, and terminal fields. Leave blank to disable an action.")}</SettingsHelpTip>
</div>
<div className="form-group settings-keyboard-shortcuts" data-testid="keyboard-shortcuts-settings">
{SHORTCUT_CATEGORIES.map((category) => (
<div className="shortcut-category" key={category.id}>
<h5 className="settings-section-heading">{t(`settings.keyboardShortcuts.category.${category.id}`, category.label)}</h5>
{category.actions.map((action) => {
const parsed = normalizeKeyboardShortcut(shortcutValues[action]);
const inputId = `dashboardShortcut-${action}`;
const hintId = `${inputId}Hint`;
return (
<div className="shortcut-row" key={action}>
<div className="settings-field-label-row">
<label htmlFor={inputId}>{t(`settings.keyboardShortcuts.action.${action}`, getShortcutActionLabel(action))}</label>
<SettingsHelpTip settingKey={inputId}>{t("settings.keyboardShortcuts.rowHint", "Default: {{default}}. Leave blank to disable.", { default: DEFAULT_DASHBOARD_KEYBOARD_SHORTCUTS[action] })}</SettingsHelpTip>
</div>
<ShortcutCaptureInput
id={inputId}
value={shortcutValues[action]}
defaultValue={DEFAULT_DASHBOARD_KEYBOARD_SHORTCUTS[action]}
invalid={!parsed.valid}
describedById={hintId}
onChange={(value) => updateShortcut(action, value)}
/>
{/* FNXC:SettingsHelp 2026-07-16-12:45: The small keeps its hintId (aria-describedby target) but now carries only live validation errors; the static default hint moved behind the "?" above. */}
<small id={hintId}>{parsed.valid ? null : parsed.error}</small>
</div>
);
})}
</div>
))}
{shortcutValidationMessage && (
<small className="settings-description shortcut-conflict-banner" role="alert">{shortcutValidationMessage}</small>
)}
</div>
</>
);
}
export default KeyboardShortcutsSection;