@gnome-ui/react 1.9.1 → 1.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/components/Footer/Footer.d.ts +23 -0
- package/dist/components/Footer/index.d.ts +2 -0
- package/dist/index.cjs +1 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.js +669 -636
- package/dist/index.js.map +1 -1
- package/dist/style.css +1 -1
- package/package.json +1 -1
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":[],"sources":["../src/components/Button/Button.module.css","../src/components/Button/Button.tsx","../src/components/Text/Text.module.css","../src/components/Text/Text.tsx","../src/components/Card/Card.module.css","../src/components/Card/Card.tsx","../src/components/Spinner/Spinner.module.css","../src/components/Spinner/Spinner.tsx","../src/components/Avatar/Avatar.module.css","../src/components/Avatar/Avatar.tsx","../src/components/Separator/Separator.module.css","../src/components/Separator/Separator.tsx","../src/components/Switch/Switch.module.css","../src/components/Switch/Switch.tsx","../src/components/Checkbox/Checkbox.module.css","../src/components/Checkbox/Checkbox.tsx","../src/components/TextField/TextField.module.css","../src/components/TextField/TextField.tsx","../src/components/RadioButton/RadioButton.module.css","../src/components/RadioButton/RadioButton.tsx","../src/components/ProgressBar/ProgressBar.module.css","../src/components/ProgressBar/ProgressBar.tsx","../src/components/Banner/Banner.module.css","../src/components/Banner/Banner.tsx","../src/components/HeaderBar/HeaderBar.module.css","../src/components/HeaderBar/HeaderBar.tsx","../src/components/Badge/Badge.module.css","../src/components/Badge/Badge.tsx","../src/components/Icon/Icon.tsx","../../icons/dist/index.js","../src/components/SearchBar/SearchBar.module.css","../src/components/SearchBar/SearchBar.tsx","../src/components/StatusPage/StatusPage.module.css","../src/components/StatusPage/StatusPage.tsx","../src/hooks/useBreakpoint.ts","../src/components/Sidebar/Sidebar.module.css","../src/components/Sidebar/Sidebar.tsx","../src/components/Sidebar/SidebarSection.tsx","../src/components/Tooltip/Tooltip.module.css","../src/components/Tooltip/Tooltip.tsx","../src/components/Sidebar/SidebarItem.tsx","../src/components/SpinButton/SpinButton.module.css","../src/components/SpinButton/SpinButton.tsx","../src/components/Tabs/Tabs.module.css","../src/components/Tabs/TabBar.tsx","../src/components/Tabs/TabItem.tsx","../src/components/Tabs/TabPanel.tsx","../src/components/ActionRow/ActionRow.module.css","../src/components/ActionRow/ActionRow.tsx","../src/components/BoxedList/BoxedList.module.css","../src/components/BoxedList/BoxedList.tsx","../src/components/ViewSwitcher/ViewSwitcher.module.css","../src/components/ViewSwitcher/ViewSwitcher.tsx","../src/components/ViewSwitcher/ViewSwitcherItem.tsx","../src/components/Toast/Toast.module.css","../src/components/Toast/Toast.tsx","../src/components/Toast/Toaster.tsx","../src/components/Dialog/Dialog.module.css","../src/components/Dialog/Dialog.tsx","../src/components/Dropdown/Dropdown.module.css","../src/components/Dropdown/Dropdown.tsx","../src/components/Slider/Slider.module.css","../src/components/Slider/Slider.tsx","../src/components/Popover/Popover.module.css","../src/components/Popover/Popover.tsx","../src/components/Clamp/Clamp.module.css","../src/components/Clamp/Clamp.tsx","../src/components/NavigationSplitView/NavigationSplitView.module.css","../src/components/NavigationSplitView/NavigationSplitView.tsx","../src/components/OverlaySplitView/OverlaySplitView.module.css","../src/components/OverlaySplitView/OverlaySplitView.tsx","../src/components/ViewSwitcherBar/ViewSwitcherBar.module.css","../src/components/ViewSwitcherBar/ViewSwitcherBar.tsx","../src/components/Link/Link.module.css","../src/components/Link/Link.tsx","../src/components/ToggleGroup/ToggleGroup.module.css","../src/components/ToggleGroup/ToggleGroup.tsx","../src/components/ToggleGroup/ToggleGroupItem.tsx","../src/components/WrapBox/WrapBox.module.css","../src/components/WrapBox/WrapBox.tsx","../src/components/Chip/Chip.module.css","../src/components/Chip/Chip.tsx","../src/components/ShortcutsDialog/ShortcutsDialog.module.css","../src/components/ShortcutsDialog/ShortcutsDialog.tsx","../src/components/ViewSwitcherSidebar/ViewSwitcherSidebar.module.css","../src/components/ViewSwitcherSidebar/ViewSwitcherSidebar.tsx","../src/components/ViewSwitcherSidebar/ViewSwitcherSidebarItem.tsx","../src/components/BreakpointBin/BreakpointBin.tsx","../src/components/ButtonRow/ButtonRow.module.css","../src/components/ButtonRow/ButtonRow.tsx","../src/components/SplitButton/SplitButton.module.css","../src/components/SplitButton/SplitButton.tsx","../src/components/Toolbar/Toolbar.module.css","../src/components/Toolbar/Toolbar.tsx","../src/components/Toolbar/Spacer.tsx","../src/components/LinkedGroup/LinkedGroup.module.css","../src/components/LinkedGroup/LinkedGroup.tsx","../src/components/Frame/Frame.module.css","../src/components/Frame/Frame.tsx","../src/components/ExpanderRow/ExpanderRow.module.css","../src/components/ExpanderRow/ExpanderRow.tsx","../src/components/InlineViewSwitcher/InlineViewSwitcher.module.css","../src/components/InlineViewSwitcher/InlineViewSwitcher.tsx","../src/components/InlineViewSwitcher/InlineViewSwitcherItem.tsx","../src/components/SwitchRow/SwitchRow.module.css","../src/components/SwitchRow/SwitchRow.tsx","../src/components/CheckRow/CheckRow.module.css","../src/components/CheckRow/CheckRow.tsx","../src/components/ComboRow/ComboRow.module.css","../src/components/ComboRow/ComboRow.tsx","../src/components/EntryRow/EntryRow.module.css","../src/components/EntryRow/EntryRow.tsx","../src/components/PasswordEntryRow/PasswordEntryRow.module.css","../src/components/PasswordEntryRow/PasswordEntryRow.tsx","../src/components/SpinRow/SpinRow.module.css","../src/components/SpinRow/SpinRow.tsx","../src/components/NavigationView/NavigationView.module.css","../src/components/NavigationView/NavigationView.tsx","../src/components/BottomSheet/BottomSheet.module.css","../src/components/BottomSheet/BottomSheet.tsx","../src/components/Carousel/Carousel.module.css","../src/components/Carousel/Carousel.tsx","../src/components/Bin/Bin.tsx","../src/components/ButtonContent/ButtonContent.module.css","../src/components/ButtonContent/ButtonContent.tsx","../src/components/ShortcutLabel/ShortcutLabel.module.css","../src/components/ShortcutLabel/ShortcutLabel.tsx","../src/components/ToolbarView/ToolbarView.module.css","../src/components/ToolbarView/ToolbarView.tsx","../src/components/WindowTitle/WindowTitle.module.css","../src/components/WindowTitle/WindowTitle.tsx","../src/components/PreferencesGroup/PreferencesGroup.module.css","../src/components/PreferencesGroup/PreferencesGroup.tsx","../src/components/PreferencesPage/PreferencesPage.module.css","../src/components/PreferencesPage/PreferencesPage.tsx","../src/components/PreferencesDialog/PreferencesDialog.module.css","../src/components/PreferencesDialog/PreferencesDialog.tsx"],"sourcesContent":[".button {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n gap: var(--gnome-space-1);\n\n /* Typography */\n font-family: var(--gnome-font-family);\n font-size: var(--gnome-font-size-body);\n font-weight: var(--gnome-font-weight-semibold);\n line-height: 1;\n white-space: nowrap;\n\n /* Shape */\n border-radius: var(--gnome-radius-md);\n border: 1px solid transparent;\n padding: var(--gnome-space-1) var(--gnome-space-2);\n min-height: 34px;\n min-width: 5.5rem;\n\n /* Behavior */\n cursor: pointer;\n user-select: none;\n text-decoration: none;\n vertical-align: middle;\n outline: none;\n transition:\n background-color var(--gnome-duration-fast) var(--gnome-easing-default),\n box-shadow var(--gnome-duration-fast) var(--gnome-easing-default),\n opacity var(--gnome-duration-fast) var(--gnome-easing-default);\n}\n\n/* ─── Focus ───────────────────────────────────────────────────── */\n\n.button:focus-visible {\n box-shadow:\n 0 0 0 var(--gnome-focus-ring-offset) var(--gnome-window-bg-color),\n 0 0 0 calc(var(--gnome-focus-ring-offset) + var(--gnome-focus-ring-width)) var(--gnome-focus-ring-color);\n}\n\n/* ─── Disabled ────────────────────────────────────────────────── */\n\n.button:disabled,\n.button[aria-disabled=\"true\"] {\n opacity: var(--gnome-opacity-disabled);\n cursor: not-allowed;\n pointer-events: none;\n}\n\n/* ─── Pill shape ──────────────────────────────────────────────── */\n\n.pill {\n border-radius: var(--gnome-radius-pill);\n padding-inline: var(--gnome-space-3);\n}\n\n/* ─── Circular shape ──────────────────────────────────────────── */\n\n.circular {\n border-radius: var(--gnome-radius-pill);\n padding: var(--gnome-space-1);\n min-width: 34px;\n min-height: 34px;\n width: 34px;\n height: 34px;\n}\n\n/* ─── Variants ────────────────────────────────────────────────── */\n\n/* Default — slight raised appearance with visible border */\n.default {\n background-color: var(--gnome-card-bg-color, #ffffff);\n color: var(--gnome-window-fg-color, rgba(0, 0, 0, 0.8));\n border-color: var(--gnome-light-3, #deddda);\n box-shadow: var(--gnome-shadow-sm);\n}\n\n.default:hover:not(:disabled) {\n background-color: var(--gnome-light-2, #f6f5f4);\n}\n\n.default:active:not(:disabled) {\n background-color: var(--gnome-light-3, #deddda);\n box-shadow: none;\n}\n\n@media (prefers-color-scheme: dark) {\n .default {\n background-color: var(--gnome-card-bg-color, #383838);\n border-color: rgba(255, 255, 255, 0.12);\n box-shadow: none;\n }\n\n .default:hover:not(:disabled) {\n background-color: rgba(255, 255, 255, 0.08);\n }\n\n .default:active:not(:disabled) {\n background-color: rgba(255, 255, 255, 0.14);\n box-shadow: none;\n }\n}\n\n/* Suggested — accent color for primary / affirmative actions */\n.suggested {\n background-color: var(--gnome-accent-bg-color);\n color: var(--gnome-accent-fg-color);\n border-color: transparent;\n}\n\n.suggested:hover:not(:disabled) {\n filter: brightness(1.08);\n}\n\n.suggested:active:not(:disabled) {\n filter: brightness(0.92);\n}\n\n/* Destructive — red for dangerous / irreversible actions */\n.destructive {\n background-color: var(--gnome-destructive-bg-color);\n color: var(--gnome-destructive-fg-color);\n border-color: transparent;\n}\n\n.destructive:hover:not(:disabled) {\n filter: brightness(1.08);\n}\n\n.destructive:active:not(:disabled) {\n filter: brightness(0.92);\n}\n\n/* Flat — no border, no background. Used inside header bars */\n.flat {\n background-color: transparent;\n border-color: transparent;\n color: var(--gnome-window-fg-color, rgba(0, 0, 0, 0.8));\n}\n\n.flat:hover:not(:disabled) {\n background-color: var(--gnome-hover-overlay);\n}\n\n.flat:active:not(:disabled) {\n background-color: var(--gnome-active-overlay);\n}\n\n/* Raised — explicit raised look inside flat/toolbar contexts */\n.raised {\n background-color: var(--gnome-card-bg-color, #ffffff);\n color: var(--gnome-window-fg-color, rgba(0, 0, 0, 0.8));\n border-color: var(--gnome-light-3, #deddda);\n box-shadow: var(--gnome-shadow-sm);\n}\n\n.raised:hover:not(:disabled) {\n background-color: var(--gnome-light-2, #f6f5f4);\n}\n\n.raised:active:not(:disabled) {\n background-color: var(--gnome-light-3, #deddda);\n box-shadow: none;\n}\n\n@media (prefers-color-scheme: dark) {\n .raised {\n background-color: var(--gnome-card-bg-color, #383838);\n border-color: rgba(255, 255, 255, 0.12);\n box-shadow: var(--gnome-shadow-sm);\n }\n\n .raised:hover:not(:disabled) {\n background-color: rgba(255, 255, 255, 0.08);\n }\n\n .raised:active:not(:disabled) {\n background-color: rgba(255, 255, 255, 0.14);\n box-shadow: none;\n }\n}\n\n/* OSD — dark semi-transparent overlay style for media/image contexts */\n.osd {\n background-color: rgba(0, 0, 0, 0.65) !important;\n color: rgba(255, 255, 255, 0.95) !important;\n border-color: rgba(255, 255, 255, 0.12) !important;\n box-shadow: none !important;\n backdrop-filter: blur(4px);\n -webkit-backdrop-filter: blur(4px);\n}\n\n.osd:hover:not(:disabled) {\n background-color: rgba(0, 0, 0, 0.75) !important;\n filter: none;\n}\n\n.osd:active:not(:disabled) {\n background-color: rgba(0, 0, 0, 0.85) !important;\n filter: none;\n}\n\n@media (prefers-color-scheme: dark) {\n .osd {\n background-color: rgba(0, 0, 0, 0.65) !important;\n border-color: rgba(255, 255, 255, 0.1) !important;\n }\n}\n\n/* ─── High contrast ───────────────────────────────────────────── */\n\n@media (prefers-contrast: more) {\n .default {\n border-width: 2px;\n box-shadow: none;\n }\n\n .button:focus-visible {\n box-shadow:\n 0 0 0 var(--gnome-focus-ring-offset) var(--gnome-window-bg-color),\n 0 0 0 calc(var(--gnome-focus-ring-offset) + var(--gnome-focus-ring-width)) var(--gnome-focus-ring-color),\n 0 0 0 calc(var(--gnome-focus-ring-offset) + var(--gnome-focus-ring-width) + 1px) var(--gnome-window-bg-color);\n }\n}\n\n/* ─── Sizes ───────────────────────────────────────────────────── */\n\n.sm {\n font-size: var(--gnome-font-size-caption);\n min-height: 28px;\n padding: 4px var(--gnome-space-1);\n min-width: 4rem;\n}\n\n.lg {\n font-size: var(--gnome-font-size-title-4);\n min-height: 42px;\n padding: var(--gnome-space-1) var(--gnome-space-3);\n}\n","import type { ButtonHTMLAttributes, ReactNode } from \"react\";\nimport styles from \"./Button.module.css\";\n\nexport type ButtonVariant = \"default\" | \"suggested\" | \"destructive\" | \"flat\" | \"raised\";\nexport type ButtonSize = \"sm\" | \"md\" | \"lg\";\nexport type ButtonShape = \"default\" | \"pill\" | \"circular\";\n\nexport interface ButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> {\n /** Visual style of the button. Follows GNOME HIG button types. */\n variant?: ButtonVariant;\n /** Size of the button. */\n size?: ButtonSize;\n /** Shape of the button. \"pill\" for primary actions in open space, \"circular\" for icon-only buttons. */\n shape?: ButtonShape;\n /**\n * Dark semi-transparent overlay style for buttons placed on top of\n * media or images — mirrors the `.osd` style class.\n */\n osd?: boolean;\n /** Icon placed before the label. */\n leadingIcon?: ReactNode;\n /** Icon placed after the label. */\n trailingIcon?: ReactNode;\n children?: ReactNode;\n}\n\n/**\n * Button component following GNOME Human Interface Guidelines.\n *\n * Variants:\n * - `default` — Standard action, flat appearance with subtle border.\n * - `suggested` — Affirmative/primary action (accent color). Use at most once per view.\n * - `destructive` — Dangerous or irreversible action (red). Use sparingly.\n * - `flat` — No border or background; ideal for header bars and toolbars.\n *\n * @see https://developer.gnome.org/hig/patterns/controls/buttons.html\n */\nexport function Button({\n variant = \"default\",\n size = \"md\",\n shape = \"default\",\n osd = false,\n leadingIcon,\n trailingIcon,\n children,\n className,\n ...props\n}: ButtonProps) {\n const classes = [\n styles.button,\n styles[variant],\n size !== \"md\" ? styles[size] : null,\n shape !== \"default\" ? styles[shape] : null,\n osd ? styles.osd : null,\n className,\n ]\n .filter(Boolean)\n .join(\" \");\n\n return (\n <button className={classes} {...props}>\n {leadingIcon}\n {children}\n {trailingIcon}\n </button>\n );\n}\n",".text {\n font-family: var(--gnome-font-family);\n color: var(--gnome-window-fg-color, rgba(0, 0, 0, 0.8));\n margin: 0;\n}\n\n/* ─── Variants ────────────────────────────────────────────────── */\n\n.large-title {\n font-size: var(--gnome-font-size-large-title); /* 2.25rem */\n font-weight: 300;\n line-height: var(--gnome-line-height-heading);\n letter-spacing: -0.02em;\n}\n\n.title-1 {\n font-size: var(--gnome-font-size-title-1); /* 1.875rem */\n font-weight: var(--gnome-font-weight-bold);\n line-height: var(--gnome-line-height-heading);\n}\n\n.title-2 {\n font-size: var(--gnome-font-size-title-2); /* 1.5rem */\n font-weight: var(--gnome-font-weight-bold);\n line-height: var(--gnome-line-height-heading);\n}\n\n.title-3 {\n font-size: var(--gnome-font-size-title-3); /* 1.25rem */\n font-weight: var(--gnome-font-weight-bold);\n line-height: var(--gnome-line-height-heading);\n}\n\n.title-4 {\n font-size: var(--gnome-font-size-title-4); /* 1.125rem */\n font-weight: var(--gnome-font-weight-semibold);\n line-height: var(--gnome-line-height-heading);\n}\n\n/* Heading — same size as body but bold. Used for section labels, boxed list headers */\n.heading {\n font-size: var(--gnome-font-size-body);\n font-weight: var(--gnome-font-weight-bold);\n line-height: var(--gnome-line-height-heading);\n}\n\n/* Body — default UI text for labels and descriptions */\n.body {\n font-size: var(--gnome-font-size-body);\n font-weight: var(--gnome-font-weight-normal);\n line-height: var(--gnome-line-height-body);\n}\n\n/* Document — main reading content (chat messages, articles) */\n.document {\n font-size: var(--gnome-font-size-body);\n font-weight: var(--gnome-font-weight-normal);\n line-height: 1.65;\n}\n\n/* Caption — sub-text accompanying body text */\n.caption {\n font-size: var(--gnome-font-size-caption); /* 0.75rem */\n font-weight: var(--gnome-font-weight-normal);\n line-height: var(--gnome-line-height-body);\n}\n\n/* Caption Heading — small label, often used above groups */\n.caption-heading {\n font-size: var(--gnome-font-size-caption);\n font-weight: var(--gnome-font-weight-semibold);\n line-height: var(--gnome-line-height-heading);\n text-transform: uppercase;\n letter-spacing: 0.06em;\n}\n\n/* Monospace — code, logs, shell commands */\n.monospace {\n font-family: var(--gnome-font-family-mono);\n font-size: 0.875rem;\n line-height: var(--gnome-line-height-body);\n}\n\n/* Numeric — tabular figures for aligned numbers */\n.numeric {\n font-size: var(--gnome-font-size-body);\n font-weight: var(--gnome-font-weight-normal);\n font-variant-numeric: tabular-nums;\n font-feature-settings: \"tnum\";\n line-height: var(--gnome-line-height-body);\n}\n\n/* ─── Colors ──────────────────────────────────────────────────── */\n\n.color-default {\n color: var(--gnome-window-fg-color, rgba(0, 0, 0, 0.8));\n}\n\n.color-dim {\n opacity: var(--gnome-opacity-dim, 0.55);\n}\n\n.color-accent {\n color: var(--gnome-accent-color, #3584e4);\n}\n\n.color-destructive {\n color: var(--gnome-destructive-color, #e01b24);\n}\n\n.color-success {\n color: var(--gnome-success-color, #2ec27e);\n}\n\n.color-warning {\n color: var(--gnome-warning-color, #e5a50a);\n}\n\n.color-error {\n color: var(--gnome-error-color, #e01b24);\n}\n","import type { ElementType, HTMLAttributes, ReactNode } from \"react\";\nimport styles from \"./Text.module.css\";\n\nexport type TextVariant =\n | \"large-title\"\n | \"title-1\"\n | \"title-2\"\n | \"title-3\"\n | \"title-4\"\n | \"heading\"\n | \"body\"\n | \"document\"\n | \"caption\"\n | \"caption-heading\"\n | \"monospace\"\n | \"numeric\";\n\nexport type TextColor =\n | \"default\"\n | \"dim\"\n | \"accent\"\n | \"destructive\"\n | \"success\"\n | \"warning\"\n | \"error\";\n\n/** Default HTML element rendered for each variant */\nconst defaultElement: Record<TextVariant, ElementType> = {\n \"large-title\": \"h1\",\n \"title-1\": \"h1\",\n \"title-2\": \"h2\",\n \"title-3\": \"h3\",\n \"title-4\": \"h4\",\n heading: \"h3\",\n body: \"p\",\n document: \"p\",\n caption: \"span\",\n \"caption-heading\": \"span\",\n monospace: \"code\",\n numeric: \"span\",\n};\n\nexport interface TextProps extends HTMLAttributes<HTMLElement> {\n /** Typography style. Mirrors Adwaita / GNOME HIG text styles. */\n variant?: TextVariant;\n /** Semantic color. */\n color?: TextColor;\n /** Override the rendered HTML element. */\n as?: ElementType;\n children?: ReactNode;\n}\n\n/**\n * Text component following GNOME Human Interface Guidelines typography styles.\n *\n * Variants map directly to Adwaita style classes:\n * `large-title`, `title-1`–`title-4`, `heading`, `body`, `document`,\n * `caption`, `caption-heading`, `monospace`, `numeric`.\n *\n * @see https://developer.gnome.org/hig/guidelines/typography.html\n * @see https://gnome.pages.gitlab.gnome.org/libadwaita/doc/main/style-classes.html\n */\nexport function Text({\n variant = \"body\",\n color = \"default\",\n as,\n className,\n children,\n ...props\n}: TextProps) {\n const Tag = as ?? defaultElement[variant];\n\n const classes = [\n styles.text,\n styles[variant],\n styles[`color-${color}`],\n className,\n ]\n .filter(Boolean)\n .join(\" \");\n\n return (\n <Tag className={classes} {...props}>\n {children}\n </Tag>\n );\n}\n",".card {\n background-color: var(--gnome-card-bg-color, #ffffff);\n color: var(--gnome-card-fg-color, rgba(0, 0, 0, 0.8));\n border: 1px solid var(--gnome-card-shade-color, rgba(0, 0, 0, 0.07));\n border-radius: var(--gnome-radius-lg);\n overflow: hidden;\n position: relative;\n}\n\n/* ─── Padding ─────────────────────────────────────────────────── */\n\n.padding-none { padding: 0; }\n.padding-sm { padding: var(--gnome-space-2); }\n.padding-md { padding: var(--gnome-space-4); }\n.padding-lg { padding: var(--gnome-space-5); }\n\n/* ─── Interactive (activatable) ───────────────────────────────── */\n\n.interactive {\n cursor: pointer;\n user-select: none;\n outline: none;\n\n /* Reset button styles when rendered as <button> */\n appearance: none;\n -webkit-appearance: none;\n border: 1px solid var(--gnome-card-shade-color, rgba(0, 0, 0, 0.07));\n font: inherit;\n text-align: inherit;\n width: 100%;\n\n transition:\n background-color var(--gnome-duration-fast) var(--gnome-easing-default),\n box-shadow var(--gnome-duration-fast) var(--gnome-easing-default);\n}\n\n.interactive:hover {\n background-color: color-mix(\n in srgb,\n var(--gnome-card-bg-color, #ffffff) 94%,\n black 6%\n );\n}\n\n.interactive:active {\n background-color: color-mix(\n in srgb,\n var(--gnome-card-bg-color, #ffffff) 88%,\n black 12%\n );\n}\n\n.interactive:focus-visible {\n box-shadow:\n 0 0 0 var(--gnome-focus-ring-offset) var(--gnome-window-bg-color, #fafafa),\n 0 0 0 calc(var(--gnome-focus-ring-offset) + var(--gnome-focus-ring-width))\n var(--gnome-focus-ring-color, #3584e4);\n}\n\n.interactive:disabled {\n opacity: var(--gnome-opacity-disabled);\n cursor: not-allowed;\n pointer-events: none;\n}\n\n@media (prefers-color-scheme: dark) {\n .interactive:hover {\n background-color: color-mix(\n in srgb,\n var(--gnome-card-bg-color, rgba(255, 255, 255, 0.08)) 85%,\n white 15%\n );\n }\n\n .interactive:active {\n background-color: color-mix(\n in srgb,\n var(--gnome-card-bg-color, rgba(255, 255, 255, 0.08)) 75%,\n white 25%\n );\n }\n}\n","import type { ElementType, HTMLAttributes, ReactNode } from \"react\";\nimport styles from \"./Card.module.css\";\n\nexport type CardPadding = \"none\" | \"sm\" | \"md\" | \"lg\";\n\nexport interface CardProps extends HTMLAttributes<HTMLElement> {\n /**\n * When true the card becomes clickable (Adwaita `.activatable`).\n * Renders as `<button>` by default; override with `as`.\n */\n interactive?: boolean;\n /** Internal spacing. Defaults to `\"md\"`. */\n padding?: CardPadding;\n /** Override the rendered HTML element. */\n as?: ElementType;\n children?: ReactNode;\n}\n\n/**\n * Card component following the GNOME HIG and Adwaita `.card` style class.\n *\n * Use for grouping related content on an elevated surface.\n * For clickable cards (e.g. grid item, settings shortcut) set `interactive`.\n *\n * @see https://gnome.pages.gitlab.gnome.org/libadwaita/doc/main/style-classes.html\n * @see https://developer.gnome.org/hig/patterns/containers.html\n */\nexport function Card({\n interactive = false,\n padding = \"md\",\n as,\n className,\n children,\n ...props\n}: CardProps) {\n const Tag: ElementType = as ?? (interactive ? \"button\" : \"div\");\n\n const classes = [\n styles.card,\n styles[`padding-${padding}`],\n interactive ? styles.interactive : null,\n className,\n ]\n .filter(Boolean)\n .join(\" \");\n\n return (\n <Tag className={classes} {...props}>\n {children}\n </Tag>\n );\n}\n","/* ─── Base ─────────────────────────────────────────────────────────────────── */\n\n.spinner {\n display: inline-block;\n border-radius: 50%;\n border-style: solid;\n border-color: var(--gnome-spinner-track-color, rgba(0, 0, 0, 0.15));\n border-top-color: var(--gnome-accent-color, #3584e4);\n animation: spin 0.75s linear infinite;\n flex-shrink: 0;\n}\n\n/* ─── Sizes ────────────────────────────────────────────────────────────────── */\n\n.sm {\n width: 16px;\n height: 16px;\n border-width: 2px;\n}\n\n.md {\n width: 24px;\n height: 24px;\n border-width: 2.5px;\n}\n\n.lg {\n width: 36px;\n height: 36px;\n border-width: 3px;\n}\n\n/* ─── Keyframes ────────────────────────────────────────────────────────────── */\n\n@keyframes spin {\n to { transform: rotate(360deg); }\n}\n\n/* ─── Dark mode ────────────────────────────────────────────────────────────── */\n\n@media (prefers-color-scheme: dark) {\n .spinner {\n border-color: var(--gnome-spinner-track-color, rgba(255, 255, 255, 0.15));\n border-top-color: var(--gnome-accent-color, #3584e4);\n }\n}\n\n/* ─── Reduced motion ───────────────────────────────────────────────────────── */\n\n@media (prefers-reduced-motion: reduce) {\n .spinner {\n animation-duration: 2s;\n }\n}\n","import type { HTMLAttributes } from \"react\";\nimport styles from \"./Spinner.module.css\";\n\nexport type SpinnerSize = \"sm\" | \"md\" | \"lg\";\n\nexport interface SpinnerProps extends HTMLAttributes<HTMLSpanElement> {\n /** Size of the spinner. Defaults to `\"md\"`. */\n size?: SpinnerSize;\n /**\n * Accessible label announced by screen readers.\n * Defaults to `\"Loading…\"`. Set to `\"\"` to silence if a sibling label is present.\n */\n label?: string;\n}\n\n/**\n * Indeterminate loading indicator following the Adwaita spinner style.\n *\n * Renders a spinning ring using a pure CSS animation that respects\n * `prefers-reduced-motion`.\n *\n * @see https://gnome.pages.gitlab.gnome.org/libadwaita/doc/main/style-classes.html\n */\nexport function Spinner({\n size = \"md\",\n label = \"Loading\\u2026\",\n className,\n ...props\n}: SpinnerProps) {\n const classes = [styles.spinner, styles[size], className]\n .filter(Boolean)\n .join(\" \");\n\n return (\n <span\n role=\"status\"\n aria-label={label || undefined}\n aria-hidden={label === \"\" ? true : undefined}\n className={classes}\n {...props}\n />\n );\n}\n","/* ─── Base ─────────────────────────────────────────────────────────────────── */\n\n.avatar {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n border-radius: var(--gnome-radius-pill);\n overflow: hidden;\n flex-shrink: 0;\n user-select: none;\n /* Subtle ring so the avatar reads on any background */\n box-shadow: inset 0 0 0 1px rgba(0, 0, 0, 0.08);\n}\n\n/* ─── Sizes ────────────────────────────────────────────────────────────────── */\n\n.sm {\n width: 24px;\n height: 24px;\n}\n\n.md {\n width: 32px;\n height: 32px;\n}\n\n.lg {\n width: 48px;\n height: 48px;\n}\n\n.xl {\n width: 64px;\n height: 64px;\n}\n\n/* ─── Image ────────────────────────────────────────────────────────────────── */\n\n.image {\n width: 100%;\n height: 100%;\n object-fit: cover;\n display: block;\n}\n\n/* ─── Initials ─────────────────────────────────────────────────────────────── */\n\n.initials {\n font-family: var(--gnome-font-family);\n font-weight: var(--gnome-font-weight-semibold);\n line-height: 1;\n color: #ffffff;\n /* Scale initials font relative to the avatar size */\n font-size: 0.42em;\n}\n\n.sm .initials { font-size: 0.5em; }\n.md .initials { font-size: 0.60em; }\n.lg .initials { font-size: 1.0em; }\n.xl .initials { font-size: 1.40em; }\n\n/* ─── Color variants (initials fallback) ───────────────────────────────────── */\n\n.color-blue { background-color: var(--gnome-blue-3, #3584e4); }\n.color-green { background-color: var(--gnome-green-4, #2ec27e); }\n.color-yellow {\n background-color: var(--gnome-yellow-4, #f5c211);\n /* yellow needs dark initials for contrast */\n}\n.color-yellow .initials { color: rgba(0, 0, 0, 0.8); }\n\n.color-orange { background-color: var(--gnome-orange-4, #e66100); }\n.color-red { background-color: var(--gnome-red-3, #e01b24); }\n.color-purple { background-color: var(--gnome-purple-3, #9141ac); }\n.color-brown { background-color: var(--gnome-brown-3, #986a44); }\n.color-teal { background-color: #2190a4; }\n.color-slate { background-color: var(--gnome-dark-2, #5e5c64); }\n\n/* ─── Dark mode ring ───────────────────────────────────────────────────────── */\n\n@media (prefers-color-scheme: dark) {\n .avatar {\n box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.08);\n }\n}\n","import type { HTMLAttributes } from \"react\";\nimport styles from \"./Avatar.module.css\";\n\nexport type AvatarSize = \"sm\" | \"md\" | \"lg\" | \"xl\";\n\n/**\n * Named color palette for the initials fallback.\n * Mirrors libadwaita's avatar color set.\n */\nexport type AvatarColor =\n | \"blue\"\n | \"green\"\n | \"yellow\"\n | \"orange\"\n | \"red\"\n | \"purple\"\n | \"brown\"\n | \"teal\"\n | \"slate\";\n\nexport interface AvatarProps extends HTMLAttributes<HTMLSpanElement> {\n /**\n * Full name used to generate initials and — when `color` is omitted —\n * to deterministically pick a background color.\n */\n name?: string;\n /** Image URL. When provided the initials fallback is hidden. */\n src?: string;\n /** Alt text for the image. Defaults to `name`. */\n alt?: string;\n /** Size of the avatar. Defaults to `\"md\"`. */\n size?: AvatarSize;\n /**\n * Override the auto-derived background color for the initials fallback.\n * When omitted a color is derived from `name` via a stable hash.\n */\n color?: AvatarColor;\n}\n\nconst COLOR_LIST: AvatarColor[] = [\n \"blue\",\n \"green\",\n \"yellow\",\n \"orange\",\n \"red\",\n \"purple\",\n \"brown\",\n \"teal\",\n \"slate\",\n];\n\n/** Stable, non-cryptographic hash → index into COLOR_LIST. */\nfunction nameToColor(name: string): AvatarColor {\n let hash = 0;\n for (let i = 0; i < name.length; i++) {\n hash = (hash * 31 + name.charCodeAt(i)) >>> 0;\n }\n return COLOR_LIST[hash % COLOR_LIST.length];\n}\n\n/** Extract up to 2 initials from a name string. */\nfunction getInitials(name: string): string {\n const parts = name.trim().split(/\\s+/).filter(Boolean);\n if (parts.length === 0) return \"\";\n if (parts.length === 1) return parts[0].charAt(0).toUpperCase();\n return (\n parts[0].charAt(0).toUpperCase() +\n parts[parts.length - 1].charAt(0).toUpperCase()\n );\n}\n\n/**\n * Circular avatar with image or initials fallback.\n *\n * Follows the Adwaita `AdwAvatar` pattern — deterministic color from name,\n * up to two initials when no image is supplied.\n *\n * @see https://gnome.pages.gitlab.gnome.org/libadwaita/doc/main/class.Avatar.html\n */\nexport function Avatar({\n name = \"\",\n src,\n alt,\n size = \"md\",\n color,\n className,\n ...props\n}: AvatarProps) {\n const resolvedColor = color ?? (name ? nameToColor(name) : \"blue\");\n const initials = getInitials(name);\n\n const classes = [\n styles.avatar,\n styles[size],\n !src ? styles[`color-${resolvedColor}`] : null,\n className,\n ]\n .filter(Boolean)\n .join(\" \");\n\n return (\n <span\n role=\"img\"\n aria-label={alt ?? name ?? \"Avatar\"}\n className={classes}\n {...props}\n >\n {src ? (\n <img src={src} alt={alt ?? name} className={styles.image} />\n ) : (\n <span aria-hidden=\"true\" className={styles.initials}>\n {initials}\n </span>\n )}\n </span>\n );\n}\n","/* ─── Base ─────────────────────────────────────────────────────────────────── */\n\n.separator {\n border: none;\n margin: 0;\n background-color: var(--gnome-card-shade-color, rgba(0, 0, 0, 0.07));\n flex-shrink: 0;\n}\n\n/* ─── Horizontal ───────────────────────────────────────────────────────────── */\n\n.horizontal {\n display: block;\n width: 100%;\n height: 1px;\n}\n\n/* ─── Vertical ─────────────────────────────────────────────────────────────── */\n\n.vertical {\n display: inline-block;\n width: 1px;\n height: 100%;\n align-self: stretch;\n}\n","import type { HTMLAttributes } from \"react\";\nimport styles from \"./Separator.module.css\";\n\nexport type SeparatorOrientation = \"horizontal\" | \"vertical\";\n\nexport interface SeparatorProps extends HTMLAttributes<HTMLHRElement> {\n /** Direction of the dividing line. Defaults to `\"horizontal\"`. */\n orientation?: SeparatorOrientation;\n}\n\n/**\n * Thin dividing line that separates groups of content.\n *\n * Renders as `<hr>` (horizontal) or a `<div role=\"separator\">` (vertical).\n * Color is driven entirely by design tokens and adapts to dark mode automatically.\n *\n * @see https://developer.gnome.org/hig/patterns/containers.html\n */\nexport function Separator({\n orientation = \"horizontal\",\n className,\n ...props\n}: SeparatorProps) {\n const classes = [\n styles.separator,\n orientation === \"vertical\" ? styles.vertical : styles.horizontal,\n className,\n ]\n .filter(Boolean)\n .join(\" \");\n\n if (orientation === \"vertical\") {\n return (\n <div\n role=\"separator\"\n aria-orientation=\"vertical\"\n className={classes}\n {...(props as HTMLAttributes<HTMLDivElement>)}\n />\n );\n }\n\n return <hr className={classes} {...props} />;\n}\n","/* ─── Reset & layout ───────────────────────────────────────────────────────── */\n\n.switch {\n appearance: none;\n -webkit-appearance: none;\n position: relative;\n display: inline-block;\n cursor: pointer;\n flex-shrink: 0;\n\n width: 56px;\n height: 28px;\n border-radius: var(--gnome-radius-pill);\n\n background-color: var(--gnome-light-4, #c0bfbc);\n border: 1px solid var(--gnome-border-subtle);\n\n transition:\n background-color var(--gnome-duration-fast) var(--gnome-easing-default),\n border-color var(--gnome-duration-fast) var(--gnome-easing-default);\n}\n\n/* ─── Thumb ────────────────────────────────────────────────────────────────── */\n\n.switch::before {\n content: \"\";\n position: absolute;\n top: 3px;\n left: 3px;\n width: 20px;\n height: 20px;\n border-radius: var(--gnome-radius-pill);\n background-color: #ffffff;\n box-shadow: 0 1px 3px rgba(0, 0, 0, 0.25);\n transition: transform var(--gnome-duration-fast) var(--gnome-easing-default);\n}\n\n/* ─── Checked ──────────────────────────────────────────────────────────────── */\n\n.switch:checked {\n background-color: var(--gnome-accent-bg-color, #3584e4);\n border-color: var(--gnome-accent-bg-color, #3584e4);\n}\n\n.switch:checked::before {\n transform: translateX(28px);\n}\n\n/* ─── Focus ────────────────────────────────────────────────────────────────── */\n\n.switch:focus-visible {\n outline: none;\n box-shadow:\n 0 0 0 var(--gnome-focus-ring-offset, 2px) var(--gnome-window-bg-color, #fafafa),\n 0 0 0 calc(var(--gnome-focus-ring-offset, 2px) + var(--gnome-focus-ring-width, 3px))\n var(--gnome-focus-ring-color, #3584e4);\n}\n\n/* ─── Disabled ─────────────────────────────────────────────────────────────── */\n\n.switch:disabled {\n opacity: var(--gnome-opacity-disabled, 0.5);\n cursor: not-allowed;\n}\n\n/* ─── Dark mode ────────────────────────────────────────────────────────────── */\n\n@media (prefers-color-scheme: dark) {\n .switch {\n background-color: var(--gnome-dark-2, #5e5c64);\n border-color: var(--gnome-border-subtle);\n }\n\n .switch::before {\n background-color: var(--gnome-light-3, #deddda);\n }\n\n .switch:checked {\n background-color: var(--gnome-accent-bg-color, #3584e4);\n border-color: var(--gnome-accent-bg-color, #3584e4);\n }\n\n .switch:checked::before {\n background-color: #ffffff;\n }\n}\n\n/* ─── High contrast ────────────────────────────────────────────────────────── */\n\n@media (prefers-contrast: more) {\n .switch {\n border-width: 2px;\n }\n\n .switch::before {\n box-shadow: none;\n }\n}\n\n/* ─── Reduced motion ───────────────────────────────────────────────────────── */\n\n@media (prefers-reduced-motion: reduce) {\n .switch,\n .switch::before {\n transition: none;\n }\n}\n","import type { InputHTMLAttributes } from \"react\";\nimport styles from \"./Switch.module.css\";\n\nexport interface SwitchProps\n extends Omit<InputHTMLAttributes<HTMLInputElement>, \"type\" | \"size\"> {\n /** Accessible label. Required when no visible `<label>` is associated. */\n \"aria-label\"?: string;\n}\n\n/**\n * On/off toggle following the Adwaita switch style.\n *\n * Renders as `<input type=\"checkbox\" role=\"switch\">` so it is natively\n * keyboard-accessible and works with standard form libraries.\n *\n * @see https://developer.gnome.org/hig/patterns/controls/switches.html\n */\nexport function Switch({ className, ...props }: SwitchProps) {\n return (\n <input\n type=\"checkbox\"\n role=\"switch\"\n className={[styles.switch, className].filter(Boolean).join(\" \")}\n {...props}\n />\n );\n}\n","/* ─── Reset & base ─────────────────────────────────────────────────────────── */\n\n.checkbox {\n appearance: none;\n -webkit-appearance: none;\n position: relative;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n flex-shrink: 0;\n cursor: pointer;\n\n width: 18px;\n height: 18px;\n border-radius: var(--gnome-radius-sm, 4px);\n\n background-color: var(--gnome-card-bg-color, #ffffff);\n border: 1.5px solid var(--gnome-light-4, #c0bfbc);\n\n transition:\n background-color var(--gnome-duration-fast) var(--gnome-easing-default),\n border-color var(--gnome-duration-fast) var(--gnome-easing-default);\n}\n\n/* ─── Checkmark (checked) ──────────────────────────────────────────────────── */\n\n.checkbox::before {\n content: \"\";\n display: block;\n opacity: 0;\n transition: opacity var(--gnome-duration-fast) var(--gnome-easing-default);\n}\n\n.checkbox:checked {\n background-color: var(--gnome-accent-bg-color, #3584e4);\n border-color: var(--gnome-accent-bg-color, #3584e4);\n}\n\n.checkbox:checked::before {\n opacity: 1;\n /* SVG checkmark rendered via clip-path */\n width: 10px;\n height: 7px;\n background-color: #ffffff;\n clip-path: polygon(\n 14% 44%,\n 0 58%,\n 36% 100%,\n 100% 12%,\n 84% 0%,\n 36% 68%\n );\n}\n\n/* ─── Indeterminate (mixed) ────────────────────────────────────────────────── */\n\n.checkbox:indeterminate {\n background-color: var(--gnome-accent-bg-color, #3584e4);\n border-color: var(--gnome-accent-bg-color, #3584e4);\n}\n\n.checkbox:indeterminate::before {\n opacity: 1;\n width: 10px;\n height: 2px;\n background-color: #ffffff;\n clip-path: none;\n border-radius: 1px;\n}\n\n/* ─── Focus ────────────────────────────────────────────────────────────────── */\n\n.checkbox:focus-visible {\n outline: none;\n box-shadow:\n 0 0 0 var(--gnome-focus-ring-offset, 2px) var(--gnome-window-bg-color, #fafafa),\n 0 0 0 calc(var(--gnome-focus-ring-offset, 2px) + var(--gnome-focus-ring-width, 3px))\n var(--gnome-focus-ring-color, #3584e4);\n}\n\n/* ─── Hover ────────────────────────────────────────────────────────────────── */\n\n.checkbox:not(:checked):not(:indeterminate):hover {\n border-color: var(--gnome-light-5, #9a9996);\n background-color: color-mix(\n in srgb,\n var(--gnome-card-bg-color, #ffffff) 94%,\n black 6%\n );\n}\n\n/* ─── Disabled ─────────────────────────────────────────────────────────────── */\n\n.checkbox:disabled {\n opacity: var(--gnome-opacity-disabled, 0.5);\n cursor: not-allowed;\n}\n\n/* ─── Dark mode ────────────────────────────────────────────────────────────── */\n\n@media (prefers-color-scheme: dark) {\n .checkbox {\n background-color: var(--gnome-card-bg-color, #383838);\n border-color: var(--gnome-dark-1, #77767b);\n }\n\n .checkbox:not(:checked):not(:indeterminate):hover {\n border-color: var(--gnome-light-4, #c0bfbc);\n background-color: color-mix(\n in srgb,\n var(--gnome-card-bg-color, #383838) 85%,\n white 15%\n );\n }\n}\n\n/* ─── High contrast ────────────────────────────────────────────────────────── */\n\n@media (prefers-contrast: more) {\n .checkbox {\n border-width: 2px;\n border-color: var(--gnome-window-fg-color, rgba(0, 0, 0, 0.8));\n }\n\n .checkbox:not(:checked):not(:indeterminate):hover {\n border-color: var(--gnome-window-fg-color, rgba(0, 0, 0, 0.8));\n background-color: var(--gnome-hover-overlay);\n }\n}\n\n/* ─── Reduced motion ───────────────────────────────────────────────────────── */\n\n@media (prefers-reduced-motion: reduce) {\n .checkbox,\n .checkbox::before {\n transition: none;\n }\n}\n","import { useEffect, useRef, type InputHTMLAttributes } from \"react\";\nimport styles from \"./Checkbox.module.css\";\n\nexport interface CheckboxProps\n extends Omit<InputHTMLAttributes<HTMLInputElement>, \"type\"> {\n /**\n * Indeterminate state — shown when only some children in a group are checked.\n * Takes visual precedence over `checked`.\n */\n indeterminate?: boolean;\n /** Accessible label. Required when no visible `<label>` is associated. */\n \"aria-label\"?: string;\n}\n\n/**\n * Checkbox for multi-selection, following the GNOME HIG and Adwaita style.\n *\n * Supports three states: unchecked, checked, and indeterminate (mixed).\n * Use `indeterminate` for \"select all\" controls when only some items are selected.\n *\n * Renders as `<input type=\"checkbox\">` — natively keyboard-accessible and\n * compatible with standard form libraries.\n *\n * @see https://developer.gnome.org/hig/patterns/controls/checkboxes.html\n */\nexport function Checkbox({ indeterminate = false, className, ...props }: CheckboxProps) {\n const ref = useRef<HTMLInputElement>(null);\n\n useEffect(() => {\n if (ref.current) {\n ref.current.indeterminate = indeterminate;\n }\n }, [indeterminate]);\n\n return (\n <input\n ref={ref}\n type=\"checkbox\"\n className={[styles.checkbox, className].filter(Boolean).join(\" \")}\n {...props}\n />\n );\n}\n","/* ─── Wrapper ──────────────────────────────────────────────────────────────── */\n\n.wrapper {\n display: flex;\n flex-direction: column;\n gap: var(--gnome-space-1, 6px);\n}\n\n.disabled {\n opacity: var(--gnome-opacity-disabled, 0.5);\n}\n\n/* ─── Label ────────────────────────────────────────────────────────────────── */\n\n.label {\n font-family: var(--gnome-font-family);\n font-size: var(--gnome-font-size-body, 1rem);\n font-weight: var(--gnome-font-weight-semibold, 600);\n line-height: var(--gnome-line-height-body, 1.5);\n color: var(--gnome-window-fg-color, rgba(0, 0, 0, 0.8));\n}\n\n/* ─── Input ────────────────────────────────────────────────────────────────── */\n\n.input {\n appearance: none;\n -webkit-appearance: none;\n\n width: 100%;\n padding: 8px var(--gnome-space-2, 12px);\n\n font-family: var(--gnome-font-family);\n font-size: var(--gnome-font-size-body, 1rem);\n font-weight: var(--gnome-font-weight-normal, 400);\n line-height: var(--gnome-line-height-body, 1.5);\n color: var(--gnome-view-fg-color, rgba(0, 0, 0, 0.8));\n\n background-color: var(--gnome-view-bg-color, #ffffff);\n border: 1px solid var(--gnome-border-subtle);\n border-radius: var(--gnome-radius-md, 8px);\n\n box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.06);\n\n transition:\n border-color var(--gnome-duration-fast) var(--gnome-easing-default),\n box-shadow var(--gnome-duration-fast) var(--gnome-easing-default);\n}\n\n.input::placeholder {\n color: var(--gnome-window-fg-color, rgba(0, 0, 0, 0.8));\n opacity: var(--gnome-opacity-dim, 0.55);\n}\n\n.input:focus {\n outline: none;\n}\n\n.input:focus-visible {\n border-color: var(--gnome-accent-color, #3584e4);\n box-shadow:\n inset 0 1px 2px rgba(0, 0, 0, 0.06),\n 0 0 0 var(--gnome-focus-ring-offset, 2px) var(--gnome-window-bg-color, #fafafa),\n 0 0 0 calc(var(--gnome-focus-ring-offset, 2px) + var(--gnome-focus-ring-width, 3px))\n var(--gnome-focus-ring-color, #3584e4);\n}\n\n.input:disabled {\n cursor: not-allowed;\n /* opacity handled by .disabled wrapper */\n}\n\n/* ─── Error state ──────────────────────────────────────────────────────────── */\n\n.errorInput {\n border-color: var(--gnome-error-color, #e01b24);\n}\n\n.errorInput:focus-visible {\n border-color: var(--gnome-error-color, #e01b24);\n box-shadow:\n inset 0 1px 2px rgba(0, 0, 0, 0.06),\n 0 0 0 var(--gnome-focus-ring-offset, 2px) var(--gnome-window-bg-color, #fafafa),\n 0 0 0 calc(var(--gnome-focus-ring-offset, 2px) + var(--gnome-focus-ring-width, 3px))\n var(--gnome-error-color, #e01b24);\n}\n\n/* ─── Hint / error text ────────────────────────────────────────────────────── */\n\n.hint {\n font-family: var(--gnome-font-family);\n font-size: var(--gnome-font-size-caption, 0.75rem);\n line-height: var(--gnome-line-height-body, 1.5);\n color: var(--gnome-window-fg-color, rgba(0, 0, 0, 0.8));\n opacity: var(--gnome-opacity-dim, 0.55);\n}\n\n.errorHint {\n opacity: 1;\n color: var(--gnome-error-color, #e01b24);\n}\n\n/* ─── Dark mode ────────────────────────────────────────────────────────────── */\n\n@media (prefers-color-scheme: dark) {\n .input {\n background-color: var(--gnome-view-bg-color, #1e1e1e);\n border-color: var(--gnome-border-subtle);\n box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.2);\n }\n}\n\n/* ─── High contrast ────────────────────────────────────────────────────────── */\n\n@media (prefers-contrast: more) {\n .input {\n border-width: 2px;\n box-shadow: none;\n }\n}\n\n/* ─── Reduced motion ───────────────────────────────────────────────────────── */\n\n@media (prefers-reduced-motion: reduce) {\n .input {\n transition: none;\n }\n}\n","import { useId, type InputHTMLAttributes } from \"react\";\nimport styles from \"./TextField.module.css\";\n\nexport interface TextFieldProps\n extends Omit<InputHTMLAttributes<HTMLInputElement>, \"id\"> {\n /** Visible label rendered above the input. */\n label?: string;\n /** Helper text rendered below the input. Hidden when `error` is set. */\n helperText?: string;\n /**\n * Error message rendered below the input in place of `helperText`.\n * Also applies the error visual state to the input border.\n */\n error?: string;\n /** Explicit id. Auto-generated when omitted. */\n id?: string;\n}\n\n/**\n * Single-line text input with label, helper text, and error state.\n *\n * Follows the Adwaita `GtkEntry` / `.entry` style class.\n *\n * @see https://developer.gnome.org/hig/patterns/controls/text-fields.html\n */\nexport function TextField({\n label,\n helperText,\n error,\n id: idProp,\n className,\n disabled,\n ...props\n}: TextFieldProps) {\n const autoId = useId();\n const id = idProp ?? autoId;\n const helpId = `${id}-help`;\n\n return (\n <div className={[styles.wrapper, disabled ? styles.disabled : null].filter(Boolean).join(\" \")}>\n {label && (\n <label htmlFor={id} className={styles.label}>\n {label}\n </label>\n )}\n\n <input\n id={id}\n disabled={disabled}\n aria-describedby={error || helperText ? helpId : undefined}\n aria-invalid={error ? true : undefined}\n className={[\n styles.input,\n error ? styles.errorInput : null,\n className,\n ]\n .filter(Boolean)\n .join(\" \")}\n {...props}\n />\n\n {(error || helperText) && (\n <span\n id={helpId}\n className={[styles.hint, error ? styles.errorHint : null]\n .filter(Boolean)\n .join(\" \")}\n >\n {error ?? helperText}\n </span>\n )}\n </div>\n );\n}\n","/* ─── Reset & base ─────────────────────────────────────────────────────────── */\n\n.radio {\n appearance: none;\n -webkit-appearance: none;\n position: relative;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n flex-shrink: 0;\n cursor: pointer;\n\n width: 18px;\n height: 18px;\n border-radius: var(--gnome-radius-pill);\n\n background-color: var(--gnome-card-bg-color, #ffffff);\n border: 1.5px solid var(--gnome-light-4, #c0bfbc);\n\n transition:\n background-color var(--gnome-duration-fast) var(--gnome-easing-default),\n border-color var(--gnome-duration-fast) var(--gnome-easing-default);\n}\n\n/* ─── Inner dot (checked) ──────────────────────────────────────────────────── */\n\n.radio::before {\n content: \"\";\n display: block;\n width: 6px;\n height: 6px;\n border-radius: var(--gnome-radius-pill);\n background-color: #ffffff;\n opacity: 0;\n transition: opacity var(--gnome-duration-fast) var(--gnome-easing-default);\n}\n\n.radio:checked {\n background-color: var(--gnome-accent-bg-color, #3584e4);\n border-color: var(--gnome-accent-bg-color, #3584e4);\n}\n\n.radio:checked::before {\n opacity: 1;\n}\n\n/* ─── Hover ────────────────────────────────────────────────────────────────── */\n\n.radio:not(:checked):hover {\n border-color: var(--gnome-light-5, #9a9996);\n background-color: color-mix(\n in srgb,\n var(--gnome-card-bg-color, #ffffff) 94%,\n black 6%\n );\n}\n\n/* ─── Focus ────────────────────────────────────────────────────────────────── */\n\n.radio:focus-visible {\n outline: none;\n box-shadow:\n 0 0 0 var(--gnome-focus-ring-offset, 2px) var(--gnome-window-bg-color, #fafafa),\n 0 0 0 calc(var(--gnome-focus-ring-offset, 2px) + var(--gnome-focus-ring-width, 3px))\n var(--gnome-focus-ring-color, #3584e4);\n}\n\n/* ─── Disabled ─────────────────────────────────────────────────────────────── */\n\n.radio:disabled {\n opacity: var(--gnome-opacity-disabled, 0.5);\n cursor: not-allowed;\n}\n\n/* ─── Dark mode ────────────────────────────────────────────────────────────── */\n\n@media (prefers-color-scheme: dark) {\n .radio {\n background-color: var(--gnome-card-bg-color, #383838);\n border-color: var(--gnome-dark-1, #77767b);\n }\n\n .radio:not(:checked):hover {\n border-color: var(--gnome-light-4, #c0bfbc);\n background-color: color-mix(\n in srgb,\n var(--gnome-card-bg-color, #383838) 85%,\n white 15%\n );\n }\n}\n\n/* ─── High contrast ────────────────────────────────────────────────────────── */\n\n@media (prefers-contrast: more) {\n .radio {\n border-width: 2px;\n border-color: var(--gnome-window-fg-color, rgba(0, 0, 0, 0.8));\n }\n\n .radio:not(:checked):hover {\n border-color: var(--gnome-window-fg-color, rgba(0, 0, 0, 0.8));\n background-color: var(--gnome-hover-overlay);\n }\n}\n\n/* ─── Reduced motion ───────────────────────────────────────────────────────── */\n\n@media (prefers-reduced-motion: reduce) {\n .radio,\n .radio::before {\n transition: none;\n }\n}\n","import type { InputHTMLAttributes } from \"react\";\nimport styles from \"./RadioButton.module.css\";\n\nexport interface RadioButtonProps\n extends Omit<InputHTMLAttributes<HTMLInputElement>, \"type\"> {\n /** Accessible label. Required when no visible `<label>` is associated. */\n \"aria-label\"?: string;\n}\n\n/**\n * Single-selection radio button following the GNOME HIG and Adwaita style.\n *\n * Group radio buttons by giving them the same `name` attribute.\n * Renders as `<input type=\"radio\">` — natively keyboard-accessible\n * (arrow keys cycle within the group) and compatible with standard form libraries.\n *\n * @see https://developer.gnome.org/hig/patterns/controls/radio-buttons.html\n */\nexport function RadioButton({ className, ...props }: RadioButtonProps) {\n return (\n <input\n type=\"radio\"\n className={[styles.radio, className].filter(Boolean).join(\" \")}\n {...props}\n />\n );\n}\n","/* ─── Track ────────────────────────────────────────────────────────────────── */\n\n.track {\n position: relative;\n width: 100%;\n height: 6px;\n border-radius: var(--gnome-radius-pill);\n background-color: var(--gnome-light-3, #deddda);\n overflow: hidden;\n}\n\n/* ─── Fill (determinate) ───────────────────────────────────────────────────── */\n\n.fill {\n height: 100%;\n border-radius: var(--gnome-radius-pill);\n background-color: var(--gnome-accent-bg-color, #3584e4);\n transition: width var(--gnome-duration-normal) var(--gnome-easing-default);\n will-change: width;\n}\n\n/* ─── Indeterminate pulse ──────────────────────────────────────────────────── */\n\n.indeterminate {\n position: absolute;\n top: 0;\n width: 40%;\n animation: pulse 1.4s var(--gnome-easing-default) infinite;\n}\n\n@keyframes pulse {\n 0% { left: -40%; }\n 100% { left: 100%; }\n}\n\n/* ─── Dark mode ────────────────────────────────────────────────────────────── */\n\n@media (prefers-color-scheme: dark) {\n .track {\n background-color: var(--gnome-dark-2, #5e5c64);\n }\n}\n\n/* ─── Reduced motion ───────────────────────────────────────────────────────── */\n\n@media (prefers-reduced-motion: reduce) {\n .fill {\n transition: none;\n }\n\n .indeterminate {\n animation: none;\n left: 0;\n width: 100%;\n opacity: 0.5;\n }\n}\n","import type { HTMLAttributes } from \"react\";\nimport styles from \"./ProgressBar.module.css\";\n\nexport interface ProgressBarProps extends HTMLAttributes<HTMLDivElement> {\n /**\n * Current progress value between `0` and `1` (e.g. `0.6` = 60 %).\n * Omit or set to `undefined` for the indeterminate (pulsing) state.\n */\n value?: number;\n /** Accessible label describing what is loading. */\n \"aria-label\"?: string;\n /** Associates the bar with a visible element that labels it. */\n \"aria-labelledby\"?: string;\n}\n\n/**\n * Determinate and indeterminate progress bar following the Adwaita style.\n *\n * - **Determinate** — pass `value` (0–1) to show exact progress.\n * - **Indeterminate** — omit `value` for an animated pulse when duration is unknown.\n *\n * Renders as `role=\"progressbar\"` with `aria-valuenow` / `aria-valuemin` /\n * `aria-valuemax` for screen readers.\n *\n * @see https://developer.gnome.org/hig/patterns/feedback/progress.html\n */\nexport function ProgressBar({\n value,\n className,\n \"aria-label\": ariaLabel,\n \"aria-labelledby\": ariaLabelledBy,\n ...props\n}: ProgressBarProps) {\n const isIndeterminate = value === undefined || value === null;\n const clamped = isIndeterminate ? undefined : Math.min(1, Math.max(0, value));\n const percent = clamped !== undefined ? clamped * 100 : undefined;\n\n return (\n <div\n role=\"progressbar\"\n aria-label={ariaLabel}\n aria-labelledby={ariaLabelledBy}\n aria-valuenow={clamped !== undefined ? Math.round(percent!) : undefined}\n aria-valuemin={isIndeterminate ? undefined : 0}\n aria-valuemax={isIndeterminate ? undefined : 100}\n className={[styles.track, className].filter(Boolean).join(\" \")}\n {...props}\n >\n <div\n className={[\n styles.fill,\n isIndeterminate ? styles.indeterminate : null,\n ]\n .filter(Boolean)\n .join(\" \")}\n style={!isIndeterminate ? { width: `${percent}%` } : undefined}\n />\n </div>\n );\n}\n","/* ─── Base ─────────────────────────────────────────────────────────────────── */\n\n.banner {\n display: flex;\n align-items: center;\n gap: var(--gnome-space-2, 12px);\n padding: 10px var(--gnome-space-4, 24px);\n min-height: 44px;\n width: 100%;\n font-family: var(--gnome-font-family);\n font-size: var(--gnome-font-size-body, 1rem);\n line-height: var(--gnome-line-height-body, 1.5);\n}\n\n/* ─── Message ──────────────────────────────────────────────────────────────── */\n\n.message {\n flex: 1;\n min-width: 0;\n}\n\n/* ─── Actions (action button + dismiss) ────────────────────────────────────── */\n\n.actions {\n display: flex;\n align-items: center;\n gap: var(--gnome-space-1, 6px);\n flex-shrink: 0;\n}\n\n/* ─── Action button ────────────────────────────────────────────────────────── */\n\n.actionBtn {\n appearance: none;\n border: none;\n background: transparent;\n font: inherit;\n font-weight: var(--gnome-font-weight-semibold, 600);\n cursor: pointer;\n padding: 4px var(--gnome-space-2, 12px);\n border-radius: var(--gnome-radius-md, 8px);\n transition: background-color var(--gnome-duration-fast) var(--gnome-easing-default);\n}\n\n.actionBtn:focus-visible {\n outline: none;\n box-shadow:\n 0 0 0 var(--gnome-focus-ring-offset, 2px) transparent,\n 0 0 0 calc(var(--gnome-focus-ring-offset, 2px) + var(--gnome-focus-ring-width, 3px))\n currentColor;\n}\n\n/* ─── Dismiss button ───────────────────────────────────────────────────────── */\n\n.dismissBtn {\n appearance: none;\n border: none;\n background: transparent;\n cursor: pointer;\n display: flex;\n align-items: center;\n justify-content: center;\n padding: 4px;\n border-radius: var(--gnome-radius-md, 8px);\n opacity: 0.6;\n transition:\n opacity var(--gnome-duration-fast) var(--gnome-easing-default),\n background-color var(--gnome-duration-fast) var(--gnome-easing-default);\n}\n\n.dismissBtn:hover { opacity: 1; }\n.dismissBtn:active { opacity: 0.8; }\n\n.dismissBtn:focus-visible {\n outline: none;\n opacity: 1;\n box-shadow:\n 0 0 0 var(--gnome-focus-ring-offset, 2px) transparent,\n 0 0 0 calc(var(--gnome-focus-ring-offset, 2px) + var(--gnome-focus-ring-width, 3px))\n currentColor;\n}\n\n/* ─── Variant: info ────────────────────────────────────────────────────────── */\n\n.info {\n background-color: var(--gnome-accent-bg-color, #3584e4);\n color: var(--gnome-accent-fg-color, #ffffff);\n}\n\n.actionBtn-info:hover {\n background-color: rgba(255, 255, 255, 0.15);\n}\n.actionBtn-info:active {\n background-color: rgba(255, 255, 255, 0.25);\n}\n\n/* ─── Variant: warning ─────────────────────────────────────────────────────── */\n\n.warning {\n background-color: var(--gnome-warning-bg-color, #f6d32d);\n color: var(--gnome-warning-fg-color, rgba(0, 0, 0, 0.8));\n}\n\n.actionBtn-warning:hover {\n background-color: rgba(0, 0, 0, 0.08);\n}\n.actionBtn-warning:active {\n background-color: rgba(0, 0, 0, 0.14);\n}\n\n/* ─── Variant: error ───────────────────────────────────────────────────────── */\n\n.error {\n background-color: var(--gnome-error-bg-color, #e01b24);\n color: var(--gnome-error-fg-color, #ffffff);\n}\n\n.actionBtn-error:hover {\n background-color: rgba(255, 255, 255, 0.15);\n}\n.actionBtn-error:active {\n background-color: rgba(255, 255, 255, 0.25);\n}\n\n/* ─── Variant: success ─────────────────────────────────────────────────────── */\n\n.success {\n background-color: var(--gnome-success-bg-color, #2ec27e);\n color: var(--gnome-success-fg-color, #ffffff);\n}\n\n.actionBtn-success:hover {\n background-color: rgba(255, 255, 255, 0.15);\n}\n.actionBtn-success:active {\n background-color: rgba(255, 255, 255, 0.25);\n}\n","import type { HTMLAttributes, ReactNode } from \"react\";\nimport styles from \"./Banner.module.css\";\n\nexport type BannerVariant = \"info\" | \"warning\" | \"error\" | \"success\";\n\nexport interface BannerProps extends HTMLAttributes<HTMLDivElement> {\n /**\n * Visual emphasis level.\n * - `info` (default) — neutral, accent-colored. Use for tips and notices.\n * - `warning` — yellow. Use for recoverable problems.\n * - `error` — red. Use for failures that need attention.\n * - `success` — green. Use for confirmations.\n */\n variant?: BannerVariant;\n /** The message text. Keep it short — one or two sentences. */\n children: ReactNode;\n /**\n * Label for the optional action button placed at the trailing end.\n * When provided, `onAction` should also be supplied.\n */\n actionLabel?: string;\n /** Called when the user clicks the action button. */\n onAction?: () => void;\n /**\n * When true a dismiss (×) button is shown at the trailing edge.\n * Provide `onDismiss` to handle removal from the DOM.\n */\n dismissible?: boolean;\n /** Called when the user clicks the dismiss button. */\n onDismiss?: () => void;\n}\n\n/**\n * Persistent message strip displayed at the top of a view.\n *\n * Mirrors the Adwaita `AdwBanner` pattern. Use for important information\n * that persists until the user acts or explicitly dismisses it.\n *\n * @see https://gnome.pages.gitlab.gnome.org/libadwaita/doc/main/class.Banner.html\n * @see https://developer.gnome.org/hig/patterns/feedback/banners.html\n */\nexport function Banner({\n variant = \"info\",\n children,\n actionLabel,\n onAction,\n dismissible = false,\n onDismiss,\n className,\n ...props\n}: BannerProps) {\n return (\n <div\n role=\"status\"\n aria-live=\"polite\"\n className={[styles.banner, styles[variant], className]\n .filter(Boolean)\n .join(\" \")}\n {...props}\n >\n <span className={styles.message}>{children}</span>\n\n {(actionLabel || dismissible) && (\n <span className={styles.actions}>\n {actionLabel && (\n <button\n type=\"button\"\n className={[styles.actionBtn, styles[`actionBtn-${variant}`]]\n .filter(Boolean)\n .join(\" \")}\n onClick={onAction}\n >\n {actionLabel}\n </button>\n )}\n {dismissible && (\n <button\n type=\"button\"\n className={styles.dismissBtn}\n onClick={onDismiss}\n aria-label=\"Dismiss\"\n >\n {/* × close icon */}\n <svg\n width=\"16\"\n height=\"16\"\n viewBox=\"0 0 16 16\"\n aria-hidden=\"true\"\n fill=\"currentColor\"\n >\n <path d=\"M3.293 3.293a1 1 0 011.414 0L8 6.586l3.293-3.293a1 1 0 111.414 1.414L9.414 8l3.293 3.293a1 1 0 01-1.414 1.414L8 9.414l-3.293 3.293a1 1 0 01-1.414-1.414L6.586 8 3.293 4.707a1 1 0 010-1.414z\" />\n </svg>\n </button>\n )}\n </span>\n )}\n </div>\n );\n}\n","/* ─── Base ─────────────────────────────────────────────────────────────────── */\n\n.headerBar {\n display: grid;\n grid-template-columns: 1fr auto 1fr;\n align-items: center;\n gap: var(--gnome-space-1, 6px);\n\n padding: 0 var(--gnome-space-2, 12px);\n min-height: 47px;\n\n background-color: var(--gnome-headerbar-bg-color, #ebebeb);\n color: var(--gnome-headerbar-fg-color, rgba(0, 0, 0, 0.8));\n\n border-bottom: 1px solid var(--gnome-headerbar-border-color, rgba(0, 0, 0, 0.12));\n\n font-family: var(--gnome-font-family);\n}\n\n/* ─── Flat variant ─────────────────────────────────────────────────────────── */\n\n.flat {\n border-bottom-color: transparent;\n box-shadow: none;\n}\n\n/* ─── Leading / trailing slots ─────────────────────────────────────────────── */\n\n.slot {\n display: flex;\n align-items: center;\n gap: var(--gnome-space-1, 6px);\n min-width: 0;\n}\n\n.slotEnd {\n justify-content: flex-end;\n}\n\n/* ─── Title slot (center) ──────────────────────────────────────────────────── */\n\n.titleSlot {\n display: flex;\n align-items: center;\n justify-content: center;\n min-width: 0;\n padding: 0 var(--gnome-space-1, 6px);\n}\n\n/* ─── Title text ───────────────────────────────────────────────────────────── */\n\n.title {\n font-size: var(--gnome-font-size-body, 1rem);\n font-weight: var(--gnome-font-weight-semibold, 600);\n line-height: var(--gnome-line-height-heading, 1.2);\n color: var(--gnome-headerbar-fg-color, rgba(0, 0, 0, 0.8));\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n}\n\n/* ─── Dark mode ────────────────────────────────────────────────────────────── */\n\n@media (prefers-color-scheme: dark) {\n .headerBar {\n border-bottom-color: var(--gnome-headerbar-border-color, rgba(255, 255, 255, 0.1));\n }\n}\n","import type { HTMLAttributes, ReactNode } from \"react\";\nimport styles from \"./HeaderBar.module.css\";\n\nexport interface HeaderBarProps extends Omit<HTMLAttributes<HTMLElement>, \"title\"> {\n /** Centered title. Pass a string or a custom element. */\n title?: ReactNode;\n /** Controls placed at the leading (left) edge — back button, menu, etc. */\n start?: ReactNode;\n /** Controls placed at the trailing (right) edge — actions, overflow menu, etc. */\n end?: ReactNode;\n /**\n * When true the header bar blends into the window chrome\n * (no bottom border, slightly transparent background).\n * Use for the topmost bar of a full-window layout.\n */\n flat?: boolean;\n}\n\n/**\n * Title bar with centered title and leading/trailing action slots.\n *\n * Mirrors the Adwaita `AdwHeaderBar` pattern. Use `flat` buttons\n * (`<Button variant=\"flat\">`) inside the header bar per GNOME HIG.\n *\n * @see https://gnome.pages.gitlab.gnome.org/libadwaita/doc/main/class.HeaderBar.html\n * @see https://developer.gnome.org/hig/patterns/containers/header-bars.html\n */\nexport function HeaderBar({\n title,\n start,\n end,\n flat = false,\n className,\n ...props\n}: HeaderBarProps) {\n return (\n <header\n className={[styles.headerBar, flat ? styles.flat : null, className]\n .filter(Boolean)\n .join(\" \")}\n {...props}\n >\n {/* Leading slot */}\n <div className={styles.slot}>\n {start}\n </div>\n\n {/* Centered title */}\n <div className={styles.titleSlot} aria-live=\"polite\">\n {typeof title === \"string\" ? (\n <span className={styles.title}>{title}</span>\n ) : (\n title\n )}\n </div>\n\n {/* Trailing slot */}\n <div className={[styles.slot, styles.slotEnd].filter(Boolean).join(\" \")}>\n {end}\n </div>\n </header>\n );\n}\n","/* ─── Wrapper (when used with anchor) ──────────────────────────────────────── */\n\n.wrapper {\n position: relative;\n display: inline-flex;\n flex-shrink: 0;\n}\n\n/* ─── Base badge ───────────────────────────────────────────────────────────── */\n\n.badge {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n\n font-family: var(--gnome-font-family);\n font-size: 0.6875rem; /* 11px — keeps it compact */\n font-weight: var(--gnome-font-weight-semibold, 600);\n line-height: 1;\n\n min-width: 18px;\n height: 18px;\n padding: 0 5px;\n border-radius: var(--gnome-radius-pill);\n\n /* Thin white ring to separate from any background */\n box-shadow: 0 0 0 2px var(--gnome-window-bg-color, #fafafa);\n}\n\n/* ─── Anchored position (top-right of parent) ──────────────────────────────── */\n\n.anchored {\n position: absolute;\n top: -4px;\n right: -4px;\n z-index: 1;\n}\n\n/* ─── Dot variant ──────────────────────────────────────────────────────────── */\n\n.dot {\n min-width: 10px;\n width: 10px;\n height: 10px;\n padding: 0;\n border-radius: var(--gnome-radius-pill);\n}\n\n.dot.anchored {\n top: -2px;\n right: -2px;\n}\n\n/* ─── Color variants ───────────────────────────────────────────────────────── */\n\n.accent {\n background-color: var(--gnome-accent-bg-color, #3584e4);\n color: var(--gnome-accent-fg-color, #ffffff);\n}\n\n.success {\n background-color: var(--gnome-success-bg-color, #2ec27e);\n color: var(--gnome-success-fg-color, #ffffff);\n}\n\n.warning {\n background-color: var(--gnome-warning-bg-color, #f6d32d);\n color: var(--gnome-warning-fg-color, rgba(0, 0, 0, 0.8));\n}\n\n.error {\n background-color: var(--gnome-error-bg-color, #e01b24);\n color: var(--gnome-error-fg-color, #ffffff);\n}\n\n.neutral {\n background-color: var(--gnome-light-4, #c0bfbc);\n color: rgba(0, 0, 0, 0.8);\n}\n\n/* ─── Dark mode ────────────────────────────────────────────────────────────── */\n\n@media (prefers-color-scheme: dark) {\n .badge {\n box-shadow: 0 0 0 2px var(--gnome-window-bg-color, #242424);\n }\n\n .neutral {\n background-color: var(--gnome-dark-2, #5e5c64);\n color: rgba(255, 255, 255, 0.87);\n }\n}\n","import type { HTMLAttributes, ReactNode } from \"react\";\nimport styles from \"./Badge.module.css\";\n\nexport type BadgeVariant = \"accent\" | \"success\" | \"warning\" | \"error\" | \"neutral\";\n\nexport interface BadgeProps extends HTMLAttributes<HTMLSpanElement> {\n /**\n * Visual style. Defaults to `\"accent\"`.\n * - `accent` — blue, for counts and highlights.\n * - `success` — green, for positive status.\n * - `warning` — yellow, for cautionary status.\n * - `error` — red, for failures or urgent counts.\n * - `neutral` — gray, for inactive or secondary counts.\n */\n variant?: BadgeVariant;\n /**\n * When true, renders a small dot with no label — used for unread/online indicators.\n * The `children` are ignored in dot mode.\n */\n dot?: boolean;\n /** Number or short text to display. Keep to 1–3 characters. */\n children?: ReactNode;\n /**\n * When provided, the badge is absolutely positioned over this child element\n * at the top-right corner. The wrapper becomes `position: relative`.\n */\n anchor?: ReactNode;\n}\n\n/**\n * Counter or status indicator, optionally overlaid on another element.\n *\n * @see https://developer.gnome.org/hig/patterns/feedback/badges.html\n */\nexport function Badge({\n variant = \"accent\",\n dot = false,\n children,\n anchor,\n className,\n ...props\n}: BadgeProps) {\n const badgeEl = (\n <span\n className={[\n styles.badge,\n styles[variant],\n dot ? styles.dot : null,\n anchor ? styles.anchored : null,\n className,\n ]\n .filter(Boolean)\n .join(\" \")}\n {...props}\n >\n {!dot && children}\n </span>\n );\n\n if (anchor) {\n return (\n <span className={styles.wrapper}>\n {anchor}\n {badgeEl}\n </span>\n );\n }\n\n return badgeEl;\n}\n","import type { SVGAttributes } from \"react\";\nimport type { IconDefinition } from \"@gnome-ui/icons\";\n\nexport type IconSize = \"sm\" | \"md\" | \"lg\";\n\nexport interface IconProps extends SVGAttributes<SVGSVGElement> {\n /** Icon definition imported from `@gnome-ui/icons`. */\n icon: IconDefinition;\n /**\n * Rendered size.\n * - `sm` — 12 px\n * - `md` — 16 px (default)\n * - `lg` — 20 px\n *\n * Override with `width`/`height` for non-standard sizes.\n */\n size?: IconSize;\n /** Accessible label. Omit for decorative icons — they are hidden from screen readers. */\n label?: string;\n}\n\nconst SIZE_MAP: Record<IconSize, number> = { sm: 12, md: 16, lg: 20 };\n\n/**\n * Renders an `@gnome-ui/icons` definition as an inline SVG.\n *\n * Uses `currentColor` so the icon inherits the text color of its parent.\n * Pass `label` only when the icon conveys meaning on its own;\n * omit it when a sibling text label already describes the action.\n *\n * @example\n * import { Search } from \"@gnome-ui/icons\";\n * <Icon icon={Search} label=\"Search\" />\n */\nexport function Icon({\n icon,\n size = \"md\",\n label,\n width,\n height,\n ...props\n}: IconProps) {\n const px = SIZE_MAP[size];\n\n return (\n <svg\n xmlns=\"http://www.w3.org/2000/svg\"\n viewBox={icon.viewBox}\n width={width ?? px}\n height={height ?? px}\n fill=\"currentColor\"\n aria-label={label}\n aria-hidden={label ? undefined : true}\n role={label ? \"img\" : undefined}\n focusable=\"false\"\n {...props}\n >\n {icon.paths.map((p, i) => (\n <path\n key={i}\n d={p.d}\n fillRule={p.fillRule}\n clipRule={p.clipRule}\n />\n ))}\n </svg>\n );\n}\n","//#region src/icons/GoPrevious.ts\nvar e = {\n\tviewBox: \"0 0 16 16\",\n\tpaths: [{\n\t\td: \"M10.5 2.5 5 8l5.5 5.5 1.5-1.5L8 8l4-4z\",\n\t\tfillRule: \"evenodd\"\n\t}]\n}, t = {\n\tviewBox: \"0 0 16 16\",\n\tpaths: [{\n\t\td: \"M5.5 2.5 11 8l-5.5 5.5L4 12l4-4-4-4z\",\n\t\tfillRule: \"evenodd\"\n\t}]\n}, n = {\n\tviewBox: \"0 0 16 16\",\n\tpaths: [{\n\t\td: \"M8 1 1 7h2v7h4v-4h2v4h4V7h2L8 1zm0 2.2L13 8v5h-2.5v-4H5.5v4H3V8z\",\n\t\tfillRule: \"evenodd\"\n\t}]\n}, r = {\n\tviewBox: \"0 0 16 16\",\n\tpaths: [{\n\t\td: \"M8 2 2.5 8H6v6h4V8h3.5z\",\n\t\tfillRule: \"evenodd\"\n\t}]\n}, i = {\n\tviewBox: \"0 0 16 16\",\n\tpaths: [{\n\t\td: \"m2.5 5 5.5 6 5.5-6z\",\n\t\tfillRule: \"evenodd\"\n\t}]\n}, a = {\n\tviewBox: \"0 0 16 16\",\n\tpaths: [{\n\t\td: \"M2.5 11 8 5l5.5 6z\",\n\t\tfillRule: \"evenodd\"\n\t}]\n}, o = {\n\tviewBox: \"0 0 16 16\",\n\tpaths: [{\n\t\td: \"M11 2.5 5 8l6 5.5z\",\n\t\tfillRule: \"evenodd\"\n\t}]\n}, s = {\n\tviewBox: \"0 0 16 16\",\n\tpaths: [{\n\t\td: \"M5 2.5 11 8 5 13.5z\",\n\t\tfillRule: \"evenodd\"\n\t}]\n}, c = {\n\tviewBox: \"0 0 16 16\",\n\tpaths: [{ d: \"M7 2v5H2v2h5v5h2V9h5V7H9V2z\" }]\n}, l = {\n\tviewBox: \"0 0 16 16\",\n\tpaths: [{ d: \"M2 7h12v2H2z\" }]\n}, u = {\n\tviewBox: \"0 0 16 16\",\n\tpaths: [{\n\t\td: \"M6 2v1H2v2h1v8a1 1 0 0 0 1 1h8a1 1 0 0 0 1-1V5h1V3h-4V2H6zm0 4h1v5H6V6zm3 0h1v5H9V6z\",\n\t\tfillRule: \"evenodd\"\n\t}]\n}, d = {\n\tviewBox: \"0 0 16 16\",\n\tpaths: [{\n\t\td: \"M12.5 1a1.5 1.5 0 0 1 1.06 2.56l-8.5 8.5-.06.06-.08.02-3 .75a.5.5 0 0 1-.61-.61l.75-3 .02-.08.06-.06 8.5-8.5A1.49 1.49 0 0 1 12.5 1zm0 1a.5.5 0 0 0-.35.15L4 10.29l-.5 2 2-.5 8.15-8.15A.5.5 0 0 0 12.5 2z\",\n\t\tfillRule: \"evenodd\"\n\t}]\n}, f = {\n\tviewBox: \"0 0 16 16\",\n\tpaths: [{\n\t\td: \"M4 2a1 1 0 0 0-1 1v9a1 1 0 0 0 1 1h7a1 1 0 0 0 1-1V3a1 1 0 0 0-1-1H4zm0 1h7v9H4V3zM2 4v9a1 1 0 0 0 1 1h7v-1H3V4H2z\",\n\t\tfillRule: \"evenodd\"\n\t}]\n}, p = {\n\tviewBox: \"0 0 16 16\",\n\tpaths: [{\n\t\td: \"M6 1a1 1 0 0 0-1 1H3a1 1 0 0 0-1 1v10a1 1 0 0 0 1 1h10a1 1 0 0 0 1-1V3a1 1 0 0 0-1-1h-2a1 1 0 0 0-1-1H6zm0 1h4v1H6V2zm-3 1h2v1a1 1 0 0 0 1 1h4a1 1 0 0 0 1-1V3h2v10H3V3z\",\n\t\tfillRule: \"evenodd\"\n\t}]\n}, m = {\n\tviewBox: \"0 0 16 16\",\n\tpaths: [{\n\t\td: \"M5.5 1 3 5.5A2.5 2.5 0 1 0 6 8l2-3.5 2 3.5a2.5 2.5 0 1 0 2.94-2.46L10 2H8L6.5 4.5zm-1 8.5a1.5 1.5 0 1 1 0 3 1.5 1.5 0 0 1 0-3zm7 0a1.5 1.5 0 1 1 0 3 1.5 1.5 0 0 1 0-3z\",\n\t\tfillRule: \"evenodd\"\n\t}]\n}, h = {\n\tviewBox: \"0 0 16 16\",\n\tpaths: [{\n\t\td: \"M6.5 2 2 6l4.5 4V7.5C9.5 7.5 12 8.5 13 11c0-4-2.5-6-6.5-6V2z\",\n\t\tfillRule: \"evenodd\"\n\t}]\n}, g = {\n\tviewBox: \"0 0 16 16\",\n\tpaths: [{\n\t\td: \"M9.5 2v3C5.5 5 3 7 3 11c1-2.5 3.5-3.5 6.5-3.5V10l4.5-4z\",\n\t\tfillRule: \"evenodd\"\n\t}]\n}, _ = {\n\tviewBox: \"0 0 16 16\",\n\tpaths: [{\n\t\td: \"M2 2v12h12V5l-3-3H2zm5 1h2v3H7V3zm-3 0h2v4a1 1 0 0 0 1 1h4a1 1 0 0 0 1-1V3.41L13 4.83V13H3V3h1zm3 7a2 2 0 1 1 0 4 2 2 0 0 1 0-4z\",\n\t\tfillRule: \"evenodd\"\n\t}]\n}, v = {\n\tviewBox: \"0 0 16 16\",\n\tpaths: [{\n\t\td: \"M2 3a1 1 0 0 0-1 1v8a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1V5a1 1 0 0 0-1-1H7.41L6 3H2zm0 1h4l1.41 1.41.59.59H14v6H2V4z\",\n\t\tfillRule: \"evenodd\"\n\t}]\n}, y = {\n\tviewBox: \"0 0 16 16\",\n\tpaths: [{\n\t\td: \"M3.293 3.293a1 1 0 0 1 1.414 0L8 6.586l3.293-3.293a1 1 0 1 1 1.414 1.414L9.414 8l3.293 3.293a1 1 0 0 1-1.414 1.414L8 9.414l-3.293 3.293a1 1 0 0 1-1.414-1.414L6.586 8 3.293 4.707a1 1 0 0 1 0-1.414z\",\n\t\tfillRule: \"evenodd\"\n\t}]\n}, b = {\n\tviewBox: \"0 0 16 16\",\n\tpaths: [{\n\t\td: \"M6.5 1a5.5 5.5 0 0 1 4.33 8.92l3.13 3.13-1.41 1.41-3.13-3.13A5.5 5.5 0 1 1 6.5 1zm0 2a3.5 3.5 0 1 0 0 7 3.5 3.5 0 0 0 0-7z\",\n\t\tfillRule: \"evenodd\"\n\t}]\n}, x = {\n\tviewBox: \"0 0 16 16\",\n\tpaths: [{\n\t\td: \"M8 2a6 6 0 0 0-6 6h2a4 4 0 0 1 4-4 4 4 0 0 1 4 4 4 4 0 0 1-4 4v-2L5 13l3 3v-2a6 6 0 0 0 6-6 6 6 0 0 0-6-6z\",\n\t\tfillRule: \"evenodd\"\n\t}]\n}, S = {\n\tviewBox: \"0 0 16 16\",\n\tpaths: [{\n\t\td: \"M11 1a3 3 0 1 1-1.18 5.76L6.6 8.9A3.02 3.02 0 0 1 7 10a3 3 0 1 1-1.18-2.38l3.36-2.24A3 3 0 0 1 9 5a3 3 0 0 1 .18-1.02L5.82 1.76A3 3 0 1 1 7 0a2.99 2.99 0 0 1-.18 1.02l3.36 2.22A3 3 0 0 1 11 1z\",\n\t\tfillRule: \"evenodd\"\n\t}]\n}, C = {\n\tviewBox: \"0 0 16 16\",\n\tpaths: [{\n\t\td: \"M11.5 1A3.5 3.5 0 0 0 8 4.5V12a2 2 0 1 1-4 0V5H2v7a4 4 0 0 0 8 0V4.5a2 2 0 1 1 4 0V12a6 6 0 0 1-12 0V5h2V3H2a2 2 0 0 0 0 4h2v5a2 2 0 1 0 4 0V4.5A3.5 3.5 0 0 0 11.5 1z\",\n\t\tfillRule: \"evenodd\"\n\t}]\n}, w = {\n\tviewBox: \"0 0 16 16\",\n\tpaths: [{ d: \"M2 4h12v2H2zm0 3h12v2H2zm0 3h12v2H2z\" }]\n}, T = {\n\tviewBox: \"0 0 16 16\",\n\tpaths: [{\n\t\td: \"M8 3a1.5 1.5 0 1 1 0-3 1.5 1.5 0 0 1 0 3zm0 5a1.5 1.5 0 1 1 0-3 1.5 1.5 0 0 1 0 3zm0 5a1.5 1.5 0 1 1 0-3 1.5 1.5 0 0 1 0 3z\",\n\t\tfillRule: \"evenodd\"\n\t}]\n}, E = {\n\tviewBox: \"0 0 16 16\",\n\tpaths: [{\n\t\td: \"M1 2a1 1 0 0 0-1 1v10a1 1 0 0 0 1 1h14a1 1 0 0 0 1-1V3a1 1 0 0 0-1-1H1zm0 1h5v10H1V3zm6 0h8v10H7V3z\",\n\t\tfillRule: \"evenodd\"\n\t}]\n}, D = {\n\tviewBox: \"0 0 16 16\",\n\tpaths: [{\n\t\td: \"M8 3C4.5 3 1.5 5.667 0 8c1.5 2.333 4.5 5 8 5s6.5-2.667 8-5c-1.5-2.333-4.5-5-8-5zm0 8a3 3 0 1 1 0-6 3 3 0 0 1 0 6zm0-4.5a1.5 1.5 0 1 0 0 3 1.5 1.5 0 0 0 0-3z\",\n\t\tfillRule: \"evenodd\"\n\t}]\n}, O = {\n\tviewBox: \"0 0 16 16\",\n\tpaths: [{\n\t\td: \"M1.293 1.293a1 1 0 0 1 1.414 0l12 12a1 1 0 0 1-1.414 1.414l-1.88-1.88A8.8 8.8 0 0 1 8 13C4.5 13 1.5 10.333 0 8c.637-1.023 1.52-2.049 2.6-2.893L1.293 2.707a1 1 0 0 1 0-1.414zM5.03 6.444 7.09 8.505A1.5 1.5 0 0 0 9.494 6.91L7.432 4.848A3 3 0 0 0 5.03 6.444zM8 3c.645 0 1.27.083 1.867.237l-1.59 1.59A3 3 0 0 0 5.173 7.92L3.584 6.333C4.51 4.95 6.152 3 8 3zm5.4 2.107C14.48 5.951 15.363 6.977 16 8c-1.5 2.333-4.5 5-8 5a8.8 8.8 0 0 1-2.072-.249l1.538-1.538a3 3 0 0 0 3.703-3.703L12.81 5.87c.202.076.4.156.59.237z\",\n\t\tfillRule: \"evenodd\"\n\t}]\n}, k = {\n\tviewBox: \"0 0 16 16\",\n\tpaths: [{\n\t\td: \"M7 1v2.06a5 5 0 0 0-1.73.72L3.64 2.22 2.22 3.64l1.56 1.56A5.02 5.02 0 0 0 3.06 7H1v2h2.06c.16.63.42 1.22.78 1.73L2.22 12.36l1.42 1.42 1.63-1.63A5 5 0 0 0 7 12.94V15h2v-2.06a5 5 0 0 0 1.73-.72l1.63 1.63 1.42-1.42-1.63-1.63c.36-.51.62-1.1.78-1.73H15V7h-2.06a5.02 5.02 0 0 0-.72-1.8l1.56-1.56-1.42-1.42-1.63 1.56A5 5 0 0 0 9 3.06V1H7zm1 4a3 3 0 1 1 0 6 3 3 0 0 1 0-6z\",\n\t\tfillRule: \"evenodd\"\n\t}]\n}, A = {\n\tviewBox: \"0 0 16 16\",\n\tpaths: [{\n\t\td: \"M8 1a7 7 0 1 0 0 14A7 7 0 0 0 8 1zm0 2a5 5 0 1 1 0 10A5 5 0 0 1 8 3zM7 7v5h2V7H7zm0-3v2h2V4H7z\",\n\t\tfillRule: \"evenodd\"\n\t}]\n}, j = {\n\tviewBox: \"0 0 16 16\",\n\tpaths: [{\n\t\td: \"M8 1 0 15h16L8 1zm0 3 6 10H2L8 4zM7 8v3h2V8H7zm0 4v2h2v-2H7z\",\n\t\tfillRule: \"evenodd\"\n\t}]\n}, M = {\n\tviewBox: \"0 0 16 16\",\n\tpaths: [{\n\t\td: \"M8 1a7 7 0 1 0 0 14A7 7 0 0 0 8 1zm0 2a5 5 0 1 1 0 10A5 5 0 0 1 8 3zM7 5v5h2V5H7zm0 6v2h2v-2H7z\",\n\t\tfillRule: \"evenodd\"\n\t}]\n}, N = {\n\tviewBox: \"0 0 16 16\",\n\tpaths: [{\n\t\td: \"M13.5 3 6 11.5 2.5 8 1 9.5l5 5 9-10z\",\n\t\tfillRule: \"evenodd\"\n\t}]\n}, P = {\n\tviewBox: \"0 0 16 16\",\n\tpaths: [{\n\t\td: \"M8 1.5 9.8 6H15l-4.2 3 1.6 5L8 11l-4.4 3 1.6-5L1 6h5.2z\",\n\t\tfillRule: \"evenodd\"\n\t}]\n}, F = {\n\tviewBox: \"0 0 16 16\",\n\tpaths: [{\n\t\td: \"M8 1.5 9.8 6H15l-4.2 3 1.6 5L8 11l-4.4 3 1.6-5L1 6h5.2L8 1.5zm0 2.55L6.7 7H3.5l2.7 1.95-1 3.15L8 10.2l2.8 1.9-1-3.15L12.5 7H9.3z\",\n\t\tfillRule: \"evenodd\"\n\t}]\n}, I = {\n\tviewBox: \"0 0 16 16\",\n\tpaths: [{\n\t\td: \"M4 2v12l10-6z\",\n\t\tfillRule: \"evenodd\"\n\t}]\n}, L = {\n\tviewBox: \"0 0 16 16\",\n\tpaths: [{ d: \"M3 2h3v12H3zm7 0h3v12h-3z\" }]\n}, R = {\n\tviewBox: \"0 0 16 16\",\n\tpaths: [{ d: \"M3 2v12l8-6-8-6zm8 0h2v12h-2z\" }]\n}, z = {\n\tviewBox: \"0 0 16 16\",\n\tpaths: [{ d: \"M13 2v12l-8-6 8-6zm-8 0H3v12h2z\" }]\n};\n//#endregion\nexport { c as Add, C as Attachment, N as Check, y as Close, f as Copy, m as Cut, u as Delete, v as DocumentOpen, d as Edit, M as Error, n as GoHome, t as GoNext, e as GoPrevious, r as GoUp, A as Information, L as MediaPause, I as MediaPlay, z as MediaSkipBackward, R as MediaSkipForward, w as OpenMenu, i as PanDown, s as PanEnd, o as PanStart, a as PanUp, p as Paste, g as Redo, x as Refresh, l as Remove, _ as Save, b as Search, k as Settings, S as Share, P as Star, F as StarOutline, h as Undo, O as ViewConceal, T as ViewMore, D as ViewReveal, E as ViewSidebar, j as Warning };\n\n//# sourceMappingURL=index.js.map","/* ─── Collapsible wrapper ───────────────────────────────────────────────────── */\n\n.wrapper {\n overflow: hidden;\n max-height: 0;\n opacity: 0;\n\n transition:\n max-height var(--gnome-duration-normal, 200ms) var(--gnome-easing-default, ease),\n opacity var(--gnome-duration-fast, 100ms) var(--gnome-easing-default, ease);\n}\n\n.open {\n max-height: 120px; /* enough for bar + filter row */\n opacity: 1;\n}\n\n/* ─── Inner bar ─────────────────────────────────────────────────────────────── */\n\n.bar {\n display: flex;\n align-items: center;\n gap: var(--gnome-space-1, 6px);\n\n padding: var(--gnome-space-1, 6px) var(--gnome-space-2, 12px);\n\n background-color: var(--gnome-headerbar-bg-color, #ebebeb);\n border-bottom: 1px solid var(--gnome-headerbar-shade-color, rgba(0, 0, 0, 0.12));\n}\n\n.disabled {\n opacity: var(--gnome-opacity-disabled, 0.5);\n}\n\n/* ─── Search icon ───────────────────────────────────────────────────────────── */\n\n.searchIcon {\n flex-shrink: 0;\n display: flex;\n align-items: center;\n color: var(--gnome-window-fg-color, rgba(0, 0, 0, 0.8));\n opacity: var(--gnome-opacity-dim, 0.55);\n}\n\n/* ─── Input ─────────────────────────────────────────────────────────────────── */\n\n.input {\n flex: 1;\n min-width: 0;\n\n /* Reset browser search input */\n appearance: none;\n -webkit-appearance: none;\n\n border: none;\n background: transparent;\n outline: none;\n\n font-family: var(--gnome-font-family);\n font-size: var(--gnome-font-size-body, 1rem);\n font-weight: var(--gnome-font-weight-normal, 400);\n line-height: var(--gnome-line-height-body, 1.5);\n color: var(--gnome-window-fg-color, rgba(0, 0, 0, 0.8));\n}\n\n.input::placeholder {\n color: var(--gnome-window-fg-color, rgba(0, 0, 0, 0.8));\n opacity: var(--gnome-opacity-dim, 0.55);\n}\n\n/* Hide webkit search cancel button — we render our own */\n.input::-webkit-search-cancel-button {\n -webkit-appearance: none;\n}\n\n.input:focus {\n outline: none;\n}\n\n/* ─── Clear button ──────────────────────────────────────────────────────────── */\n\n.clearButton {\n flex-shrink: 0;\n display: flex;\n align-items: center;\n justify-content: center;\n\n width: 24px;\n height: 24px;\n\n appearance: none;\n border: none;\n background: transparent;\n border-radius: var(--gnome-radius-sm, 6px);\n padding: 0;\n cursor: pointer;\n color: var(--gnome-window-fg-color, rgba(0, 0, 0, 0.8));\n opacity: var(--gnome-opacity-dim, 0.55);\n\n transition: opacity var(--gnome-duration-fast, 100ms) var(--gnome-easing-default, ease);\n}\n\n.clearButton:hover:not(:disabled) {\n opacity: 1;\n background-color: color-mix(in srgb, currentColor 10%, transparent);\n}\n\n.clearButton:active:not(:disabled) {\n background-color: color-mix(in srgb, currentColor 18%, transparent);\n}\n\n.clearButton:focus {\n outline: none;\n}\n\n.clearButton:focus-visible {\n opacity: 1;\n outline: var(--gnome-focus-ring-width, 3px) solid var(--gnome-focus-ring-color, #3584e4);\n outline-offset: var(--gnome-focus-ring-offset, 2px);\n}\n\n/* ─── Filter row (optional children) ───────────────────────────────────────── */\n\n.filterRow {\n display: flex;\n flex-wrap: wrap;\n gap: var(--gnome-space-1, 6px);\n padding: var(--gnome-space-1, 6px) var(--gnome-space-2, 12px);\n\n background-color: var(--gnome-headerbar-bg-color, #ebebeb);\n border-bottom: 1px solid var(--gnome-headerbar-shade-color, rgba(0, 0, 0, 0.12));\n}\n\n/* ─── Inline variant ────────────────────────────────────────────────────────── */\n\n.inline {\n background-color: transparent;\n border-bottom-color: transparent;\n}\n\n/* ─── Dark mode ─────────────────────────────────────────────────────────────── */\n\n@media (prefers-color-scheme: dark) {\n .bar,\n .filterRow {\n background-color: var(--gnome-headerbar-bg-color, #303030);\n border-bottom-color: rgba(0, 0, 0, 0.35);\n }\n}\n\n/* ─── Reduced motion ────────────────────────────────────────────────────────── */\n\n@media (prefers-reduced-motion: reduce) {\n .wrapper {\n transition: none;\n }\n\n .clearButton {\n transition: none;\n }\n}\n","import {\n useRef,\n useEffect,\n useCallback,\n type KeyboardEvent,\n type InputHTMLAttributes,\n type ReactNode,\n} from \"react\";\nimport { Search, Close } from \"@gnome-ui/icons\";\nimport { Icon } from \"../Icon\";\nimport styles from \"./SearchBar.module.css\";\n\nexport interface SearchBarProps\n extends Omit<InputHTMLAttributes<HTMLInputElement>, \"type\"> {\n /**\n * Whether the search bar is visible/expanded.\n * When `false` the bar collapses to zero height and is hidden from AT.\n */\n open: boolean;\n /** Called when the user presses Escape or clicks the clear button with empty value. */\n onClose?: () => void;\n /** Called when the clear (×) button is clicked. Also clears the input value. */\n onClear?: () => void;\n /**\n * Content to render below the search bar (e.g. filter chips).\n * Only visible while `open` is true.\n */\n children?: ReactNode;\n /**\n * Removes the header-bar background so the bar blends into any surface.\n * Use inside cards, content areas, or custom containers.\n * Mirrors the `.inline` style class.\n */\n inline?: boolean;\n}\n\n/**\n * Collapsible search bar following the Adwaita `AdwSearchBar` pattern.\n *\n * - Slides in/out with a CSS height transition.\n * - Auto-focuses the input when `open` becomes `true`.\n * - Escape key calls `onClose`.\n * - Shows a clear (×) button whenever the input has a value.\n *\n * @see https://gnome.pages.gitlab.gnome.org/libadwaita/doc/main/class.SearchBar.html\n */\nexport function SearchBar({\n open,\n onClose,\n onClear,\n children,\n value,\n onChange,\n placeholder = \"Search…\",\n disabled,\n inline = false,\n className,\n ...props\n}: SearchBarProps) {\n const inputRef = useRef<HTMLInputElement>(null);\n\n // Auto-focus when opening\n useEffect(() => {\n if (open) {\n // Defer so the CSS transition has begun and the element is interactable\n const id = requestAnimationFrame(() => inputRef.current?.focus());\n return () => cancelAnimationFrame(id);\n }\n }, [open]);\n\n const handleKeyDown = useCallback(\n (e: KeyboardEvent<HTMLInputElement>) => {\n if (e.key === \"Escape\") {\n e.preventDefault();\n onClose?.();\n }\n props.onKeyDown?.(e);\n },\n [onClose, props]\n );\n\n const hasValue = value !== undefined && value !== \"\";\n\n return (\n <div\n className={[styles.wrapper, open ? styles.open : null, className]\n .filter(Boolean)\n .join(\" \")}\n aria-hidden={!open}\n >\n <div className={[styles.bar, inline ? styles.inline : null, disabled ? styles.disabled : null].filter(Boolean).join(\" \")}>\n {/* Search icon */}\n <span className={styles.searchIcon} aria-hidden>\n <Icon icon={Search} size=\"md\" />\n </span>\n\n <input\n ref={inputRef}\n type=\"search\"\n role=\"searchbox\"\n value={value}\n onChange={onChange}\n placeholder={placeholder}\n disabled={disabled}\n tabIndex={open ? undefined : -1}\n className={styles.input}\n onKeyDown={handleKeyDown}\n {...props}\n />\n\n {/* Clear button — shown only when there is a value */}\n {hasValue && (\n <button\n type=\"button\"\n className={styles.clearButton}\n aria-label=\"Clear search\"\n tabIndex={open ? undefined : -1}\n disabled={disabled}\n onClick={() => {\n onClear?.();\n inputRef.current?.focus();\n }}\n >\n <Icon icon={Close} size=\"md\" />\n </button>\n )}\n </div>\n\n {children && (\n <div className={styles.filterRow}>{children}</div>\n )}\n </div>\n );\n}\n","/* ─── Page container ─────────────────────────────────────────────────────────── */\n\n.page {\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n text-align: center;\n\n width: 100%;\n padding: var(--gnome-space-6, 48px) var(--gnome-space-4, 24px);\n gap: 0;\n\n font-family: var(--gnome-font-family);\n color: var(--gnome-window-fg-color, rgba(0, 0, 0, 0.8));\n}\n\n/* ─── Icon ───────────────────────────────────────────────────────────────────── */\n\n.iconWrap {\n display: flex;\n align-items: center;\n justify-content: center;\n margin-bottom: var(--gnome-space-4, 24px);\n\n /* Adwaita dims the icon slightly to keep the title as the focal point */\n opacity: 0.55;\n color: var(--gnome-window-fg-color, rgba(0, 0, 0, 0.8));\n}\n\n/* ─── Title ──────────────────────────────────────────────────────────────────── */\n\n.title {\n margin: 0;\n\n font-size: var(--gnome-font-size-title-1, 2rem);\n font-weight: var(--gnome-font-weight-bold, 700);\n line-height: var(--gnome-line-height-title, 1.2);\n color: var(--gnome-window-fg-color, rgba(0, 0, 0, 0.8));\n}\n\n/* ─── Description ────────────────────────────────────────────────────────────── */\n\n.description {\n margin: var(--gnome-space-2, 12px) 0 0;\n\n max-width: 36ch;\n\n font-size: var(--gnome-font-size-body, 1rem);\n font-weight: var(--gnome-font-weight-normal, 400);\n line-height: var(--gnome-line-height-body, 1.5);\n color: var(--gnome-window-fg-color, rgba(0, 0, 0, 0.8));\n opacity: var(--gnome-opacity-dim, 0.55);\n}\n\n/* ─── Actions ────────────────────────────────────────────────────────────────── */\n\n.actions {\n display: flex;\n flex-wrap: wrap;\n gap: var(--gnome-space-2, 12px);\n justify-content: center;\n margin-top: var(--gnome-space-4, 24px);\n}\n\n/* ─── Compact mode ───────────────────────────────────────────────────────────── */\n\n.compact {\n padding: var(--gnome-space-3, 18px) var(--gnome-space-3, 18px);\n}\n\n.compact .iconWrap {\n margin-bottom: var(--gnome-space-2, 12px);\n}\n\n.compact .title {\n font-size: var(--gnome-font-size-title-4, 1.125rem);\n font-weight: var(--gnome-font-weight-semibold, 600);\n}\n\n.compact .description {\n font-size: var(--gnome-font-size-caption, 0.75rem);\n margin-top: var(--gnome-space-1, 6px);\n}\n\n.compact .actions {\n margin-top: var(--gnome-space-2, 12px);\n gap: var(--gnome-space-1, 6px);\n}\n\n/* ─── Dark mode ──────────────────────────────────────────────────────────────── */\n\n@media (prefers-color-scheme: dark) {\n .iconWrap {\n opacity: 0.45;\n }\n}\n","import type { HTMLAttributes, ReactNode } from \"react\";\nimport type { IconDefinition } from \"@gnome-ui/icons\";\nimport { Icon } from \"../Icon\";\nimport styles from \"./StatusPage.module.css\";\n\nexport interface StatusPageProps extends HTMLAttributes<HTMLDivElement> {\n /**\n * Large icon displayed above the title.\n * Use an icon from `@gnome-ui/icons` or omit for a text-only page.\n */\n icon?: IconDefinition;\n /**\n * Custom icon node. Use when you need an image, emoji, or\n * a rendered SVG that is not part of `@gnome-ui/icons`.\n * Ignored when `icon` is also provided.\n */\n iconNode?: ReactNode;\n /** Main heading. Keep it short — one noun phrase. */\n title: string;\n /** Supporting description rendered below the title. */\n description?: string;\n /**\n * Optional action area — typically one or two `<Button>` elements.\n * Rendered below the description.\n */\n children?: ReactNode;\n /**\n * Reduces padding, icon size, and title scale for use in compact\n * contexts such as sidebars, popovers, and small panels.\n * Mirrors the `.compact` style class.\n */\n compact?: boolean;\n}\n\n/**\n * Empty-state / status page following the Adwaita `AdwStatusPage` pattern.\n *\n * Use to fill a view with no content yet, an error state, or a completion\n * confirmation. Centred both horizontally and vertically.\n *\n * @see https://gnome.pages.gitlab.gnome.org/libadwaita/doc/main/class.StatusPage.html\n * @see https://developer.gnome.org/hig/patterns/feedback/empty-states.html\n */\nexport function StatusPage({\n icon,\n iconNode,\n title,\n description,\n children,\n compact = false,\n className,\n ...props\n}: StatusPageProps) {\n const iconSize = compact ? 64 : 128;\n const iconContent = icon\n ? <Icon icon={icon} width={iconSize} height={iconSize} aria-hidden />\n : iconNode ?? null;\n\n return (\n <div\n className={[styles.page, compact ? styles.compact : null, className]\n .filter(Boolean)\n .join(\" \")}\n {...props}\n >\n {iconContent && (\n <div className={styles.iconWrap} aria-hidden=\"true\">\n {iconContent}\n </div>\n )}\n\n <p className={styles.title}>{title}</p>\n\n {description && (\n <p className={styles.description}>{description}</p>\n )}\n\n {children && (\n <div className={styles.actions}>{children}</div>\n )}\n </div>\n );\n}\n","import { useState, useEffect } from \"react\";\n\n/**\n * GNOME / libadwaita canonical breakpoints (in CSS px, assuming 1 sp = 1 px at 1× density).\n *\n * | Name | Max width | Pattern triggered |\n * |------------|-----------|-------------------|\n * | `narrow` | ≤ 400 px | Collapse split views; sidebar becomes overlay |\n * | `medium` | ≤ 550 px | Move ViewSwitcher to a bottom bar |\n * | `wide` | ≤ 860 px | Collapse outer pane in nested split views |\n *\n * @see https://gnome.pages.gitlab.gnome.org/libadwaita/doc/main/class.Breakpoint.html\n */\nexport const GNOME_BREAKPOINTS = {\n /** ≤ 400 px — split views collapse to single pane */\n narrow: 400,\n /** ≤ 550 px — ViewSwitcher moves to bottom bar */\n medium: 550,\n /** ≤ 860 px — outer pane of nested split views collapses */\n wide: 860,\n} as const;\n\nexport type GnomeBreakpointName = keyof typeof GNOME_BREAKPOINTS;\n\nexport interface BreakpointState {\n /** Width ≤ 400 px — split views are collapsed. */\n isNarrow: boolean;\n /** Width ≤ 550 px — medium or narrower. */\n isMedium: boolean;\n /** Width ≤ 860 px — wide or narrower. */\n isWide: boolean;\n /** Current viewport width in px. */\n width: number;\n}\n\n/**\n * Tracks the viewport width against GNOME / libadwaita breakpoints.\n *\n * Returns a reactive state object that updates on every window resize.\n * Uses a passive `resize` event listener and cleans up automatically.\n *\n * @example\n * const { isNarrow, isMedium } = useBreakpoint();\n * // isNarrow → true when viewport ≤ 400 px (split views should collapse)\n * // isMedium → true when viewport ≤ 550 px (use ViewSwitcherBar instead)\n */\nexport function useBreakpoint(): BreakpointState {\n const getState = (): BreakpointState => {\n const width = typeof window !== \"undefined\" ? window.innerWidth : 1280;\n return {\n isNarrow: width <= GNOME_BREAKPOINTS.narrow,\n isMedium: width <= GNOME_BREAKPOINTS.medium,\n isWide: width <= GNOME_BREAKPOINTS.wide,\n width,\n };\n };\n\n const [state, setState] = useState<BreakpointState>(getState);\n\n useEffect(() => {\n const handler = () => setState(getState());\n window.addEventListener(\"resize\", handler, { passive: true });\n return () => window.removeEventListener(\"resize\", handler);\n }, []);\n\n return state;\n}\n","/* ─── Container ────────────────────────────────────────────────────────────── */\n\n.sidebar {\n display: flex;\n flex-direction: column;\n width: 240px;\n height: 100%;\n background-color: var(--gnome-sidebar-bg-color, #ebebeb);\n color: var(--gnome-sidebar-fg-color, rgba(0, 0, 0, 0.8));\n border-right: 1px solid var(--gnome-headerbar-border-color, rgba(0, 0, 0, 0.12));\n overflow-y: auto;\n overflow-x: hidden;\n padding: var(--gnome-space-1, 6px);\n gap: 0;\n transition: width 200ms var(--gnome-easing-default, ease);\n flex-shrink: 0;\n}\n\n/* ─── Section ───────────────────────────────────────────────────────────────── */\n\n.section {\n display: flex;\n flex-direction: column;\n}\n\n/* Divider between consecutive sections */\n.section + .section {\n margin-top: var(--gnome-space-1, 6px);\n padding-top: var(--gnome-space-1, 6px);\n border-top: 1px solid var(--gnome-divider-color);\n}\n\n.sectionTitle {\n margin: 4px var(--gnome-space-1, 6px) 2px;\n padding: 0;\n font-family: var(--gnome-font-family);\n font-size: var(--gnome-font-size-caption, 0.875rem);\n font-weight: var(--gnome-font-weight-semibold, 600);\n text-transform: uppercase;\n letter-spacing: 0.06em;\n opacity: 0.5;\n user-select: none;\n}\n\n/* ─── List ─────────────────────────────────────────────────────────────────── */\n\n.list {\n list-style: none;\n margin: 0;\n padding: 0;\n display: flex;\n flex-direction: column;\n gap: 2px;\n}\n\n/* ─── Item wrapper ─────────────────────────────────────────────────────────── */\n\n.item {\n display: block;\n}\n\n/* ─── Item button ──────────────────────────────────────────────────────────── */\n\n.itemBtn {\n appearance: none;\n -webkit-appearance: none;\n border: none;\n background: transparent;\n font: inherit;\n text-align: start;\n cursor: pointer;\n outline: none;\n\n display: flex;\n align-items: center;\n gap: var(--gnome-space-2, 12px);\n width: 100%;\n padding: 8px var(--gnome-space-2, 12px);\n border-radius: var(--gnome-radius-md, 8px);\n\n color: var(--gnome-sidebar-fg-color, rgba(0, 0, 0, 0.8));\n font-family: var(--gnome-font-family);\n font-size: var(--gnome-font-size-body, 1rem);\n font-weight: var(--gnome-font-weight-normal, 400);\n\n transition: background-color var(--gnome-duration-fast) var(--gnome-easing-default);\n}\n\n.itemBtn:hover {\n background-color: var(--gnome-hover-overlay);\n}\n\n.itemBtn:active {\n background-color: var(--gnome-active-overlay);\n}\n\n.itemBtn:focus-visible {\n box-shadow:\n 0 0 0 var(--gnome-focus-ring-offset, 2px) var(--gnome-sidebar-bg-color, #ebebeb),\n 0 0 0 calc(var(--gnome-focus-ring-offset, 2px) + var(--gnome-focus-ring-width, 3px))\n var(--gnome-focus-ring-color, #3584e4);\n}\n\n.itemBtn:disabled {\n opacity: var(--gnome-opacity-disabled, 0.5);\n cursor: not-allowed;\n}\n\n/* ─── Active state ─────────────────────────────────────────────────────────── */\n\n.active {\n background-color: var(--gnome-accent-bg-color, #3584e4);\n color: var(--gnome-accent-fg-color, #ffffff);\n font-weight: var(--gnome-font-weight-semibold, 600);\n}\n\n.active:hover {\n filter: brightness(1.08);\n}\n\n.active:active {\n filter: brightness(0.92);\n}\n\n/* ─── Icon ─────────────────────────────────────────────────────────────────── */\n\n.itemIcon {\n display: flex;\n align-items: center;\n flex-shrink: 0;\n opacity: 0.85;\n}\n\n.active .itemIcon {\n opacity: 1;\n}\n\n/* ─── Label ────────────────────────────────────────────────────────────────── */\n\n.itemLabel {\n flex: 1;\n min-width: 0;\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n}\n\n/* ─── Suffix (trailing widget) ──────────────────────────────────────────────── */\n\n.itemSuffix {\n flex-shrink: 0;\n margin-left: auto;\n display: flex;\n align-items: center;\n}\n\n/* ─── Context menu ──────────────────────────────────────────────────────────── */\n\n.contextMenu {\n position: fixed;\n z-index: 10500;\n min-width: 160px;\n\n background-color: var(--gnome-card-bg-color, #ffffff);\n border: 1px solid var(--gnome-border-subtle);\n border-radius: var(--gnome-radius-md, 8px);\n box-shadow: var(--gnome-shadow-md, 0 4px 16px rgba(0, 0, 0, 0.15));\n padding: 4px;\n overflow: hidden;\n\n display: flex;\n flex-direction: column;\n gap: 1px;\n\n animation: menu-in var(--gnome-duration-fast, 100ms) var(--gnome-easing-default, ease) both;\n}\n\n@keyframes menu-in {\n from { opacity: 0; transform: scale(0.96); }\n to { opacity: 1; transform: scale(1); }\n}\n\n.contextMenuItem {\n appearance: none;\n border: none;\n background: transparent;\n width: 100%;\n text-align: start;\n cursor: pointer;\n outline: none;\n\n padding: 6px var(--gnome-space-2, 12px);\n border-radius: calc(var(--gnome-radius-md, 8px) - 4px);\n\n font-family: var(--gnome-font-family);\n font-size: var(--gnome-font-size-body, 1rem);\n color: var(--gnome-window-fg-color, rgba(0, 0, 0, 0.8));\n\n transition: background-color var(--gnome-duration-fast) var(--gnome-easing-default);\n}\n\n.contextMenuItem:hover:not(:disabled) {\n background-color: var(--gnome-hover-overlay);\n}\n\n.contextMenuItem:active:not(:disabled) {\n background-color: var(--gnome-active-overlay);\n}\n\n.contextMenuItem:focus-visible {\n box-shadow: inset 0 0 0 2px var(--gnome-focus-ring-color, #3584e4);\n}\n\n.contextMenuItem:disabled {\n opacity: var(--gnome-opacity-disabled, 0.5);\n cursor: not-allowed;\n}\n\n.contextMenuDestructive {\n color: var(--gnome-destructive-color, #e01b24);\n}\n\n/* ─── Search wrap ───────────────────────────────────────────────────────────── */\n\n.searchWrap {\n padding-bottom: var(--gnome-space-1, 6px);\n}\n\n/* Override SearchBar min-height to keep it compact inside the sidebar */\n.searchWrap > * {\n min-height: unset;\n}\n\n/* ─── Page mode (full-width boxed-list layout) ──────────────────────────────── */\n\n.pageMode {\n width: 100%;\n border-right: none;\n}\n\n/* ─── Drag-over highlight ───────────────────────────────────────────────────── */\n\n.dragOver {\n background-color: var(--gnome-accent-bg-color, #3584e4);\n color: var(--gnome-accent-fg-color, #ffffff);\n opacity: 0.7;\n}\n\n/* ─── Collapsed (icon-only / rail) mode ────────────────────────────────────── */\n\n.collapsed {\n width: 56px;\n transition: width 200ms var(--gnome-easing-default, ease);\n}\n\n/* Hide section headings when collapsed */\n.collapsed .sectionTitle {\n display: none;\n}\n\n/* Center icon when collapsed */\n.itemCollapsed {\n justify-content: center;\n padding-left: 0;\n padding-right: 0;\n}\n\n/* ─── High contrast ────────────────────────────────────────────────────────── */\n\n@media (prefers-contrast: more) {\n .sidebar {\n border-right-width: 2px;\n }\n\n .section + .section {\n border-top-width: 2px;\n }\n\n .contextMenu {\n border-width: 2px;\n }\n\n .active {\n outline: 2px solid var(--gnome-accent-fg-color, #ffffff);\n outline-offset: -3px;\n }\n}\n\n/* ─── Reduced motion ───────────────────────────────────────────────────────── */\n\n@media (prefers-reduced-motion: reduce) {\n .itemBtn {\n transition: none;\n }\n}\n","import {\n createContext,\n useContext,\n useState,\n isValidElement,\n Children,\n type HTMLAttributes,\n type ReactNode,\n} from \"react\";\nimport { SearchBar } from \"../SearchBar\";\nimport { StatusPage } from \"../StatusPage\";\nimport { useBreakpoint } from \"../../hooks/useBreakpoint\";\nimport styles from \"./Sidebar.module.css\";\n\n// ─── Contexts ─────────────────────────────────────────────────────────────────\n\n/** Provides the collapsed state to all descendant `SidebarItem` components. */\nexport const SidebarCollapsedContext = createContext(false);\n\n/** Returns `true` when the nearest `Sidebar` is in collapsed (icon-only) mode. */\nexport function useSidebarCollapsed() {\n return useContext(SidebarCollapsedContext);\n}\n\n/** Provides the active filter string to all descendant `SidebarItem` components. */\nexport const SidebarFilterContext = createContext(\"\");\n\n// ─── Helpers ──────────────────────────────────────────────────────────────────\n\n/** Recursively count items whose `label` matches `filter` (case-insensitive). */\nfunction countMatchingItems(children: ReactNode, filter: string): number {\n let count = 0;\n Children.forEach(children, (child) => {\n if (!isValidElement(child)) return;\n const props = child.props as Record<string, unknown>;\n if (typeof props.label === \"string\") {\n if (props.label.toLowerCase().includes(filter.toLowerCase())) count++;\n } else if (props.children) {\n count += countMatchingItems(props.children as ReactNode, filter);\n }\n });\n return count;\n}\n\n// ─── Component ────────────────────────────────────────────────────────────────\n\nexport interface SidebarProps extends HTMLAttributes<HTMLElement> {\n children?: ReactNode;\n /**\n * When `true`, collapses the sidebar to icon-only mode (rail/mini).\n * Labels, suffixes, and section titles are hidden; tooltips appear on hover.\n */\n collapsed?: boolean;\n /**\n * Renders a built-in `SearchBar` inside the sidebar and manages the filter\n * state internally (uncontrolled). Combine with `filter` + `onFilterChange`\n * for controlled mode.\n */\n searchable?: boolean;\n /**\n * Controlled filter string. Items whose `label` does not match\n * (case-insensitive substring) are hidden. Does not render a `SearchBar`\n * on its own — use `searchable` for the built-in search input.\n */\n filter?: string;\n /** Called when the built-in search value changes. */\n onFilterChange?: (value: string) => void;\n /**\n * Layout mode:\n * - `\"sidebar\"` — fixed-width panel with a border (default).\n * - `\"page\"` — full-width boxed-list layout for narrow viewports.\n *\n * When omitted the sidebar auto-switches to `\"page\"` at ≤ 400 sp,\n * mirroring `AdwSidebar` mobile behaviour.\n */\n mode?: \"sidebar\" | \"page\";\n}\n\n/**\n * Lateral navigation panel following the Adwaita `.navigation-sidebar` style.\n *\n * **Tier 11 — libadwaita 1.9 completeness:**\n * - `searchable` — built-in `SearchBar` with automatic item filtering.\n * - `filter` / `onFilterChange` — controlled external filtering.\n * - `mode` — `\"sidebar\"` (default) or `\"page\"` (boxed-list layout).\n * Auto-switches to `\"page\"` at ≤ 400 sp when `mode` is unset.\n *\n * @see https://gnome.pages.gitlab.gnome.org/libadwaita/doc/main/class.Sidebar.html\n */\nexport function Sidebar({\n children,\n collapsed = false,\n searchable,\n filter: controlledFilter,\n onFilterChange,\n mode,\n className,\n ...props\n}: SidebarProps) {\n const [internalFilter, setInternalFilter] = useState(\"\");\n const { isNarrow } = useBreakpoint();\n\n const isControlled = controlledFilter !== undefined;\n const filterValue = isControlled ? controlledFilter : internalFilter;\n\n const handleFilterChange = (v: string) => {\n if (!isControlled) setInternalFilter(v);\n onFilterChange?.(v);\n };\n\n const isFilterActive = filterValue.length > 0;\n const effectiveMode = mode ?? (isNarrow ? \"page\" : \"sidebar\");\n const hasMatches = !isFilterActive || countMatchingItems(children, filterValue) > 0;\n\n return (\n <SidebarCollapsedContext.Provider value={collapsed}>\n <SidebarFilterContext.Provider value={filterValue}>\n <nav\n className={[\n styles.sidebar,\n collapsed ? styles.collapsed : null,\n effectiveMode === \"page\" ? styles.pageMode : null,\n className,\n ]\n .filter(Boolean)\n .join(\" \")}\n {...props}\n >\n {searchable && (\n <div className={styles.searchWrap}>\n <SearchBar\n open\n value={filterValue}\n onChange={(e) => handleFilterChange(e.target.value)}\n onClose={() => handleFilterChange(\"\")}\n onClear={() => handleFilterChange(\"\")}\n inline\n />\n </div>\n )}\n\n {isFilterActive && !hasMatches ? (\n <StatusPage\n title=\"No Results\"\n description=\"No items match your search.\"\n compact\n />\n ) : (\n children\n )}\n </nav>\n </SidebarFilterContext.Provider>\n </SidebarCollapsedContext.Provider>\n );\n}\n","import type { HTMLAttributes, ReactNode } from \"react\";\nimport styles from \"./Sidebar.module.css\";\n\nexport interface SidebarSectionProps extends HTMLAttributes<HTMLElement> {\n /**\n * Section heading. Rendered in small caps above the items.\n * Omit for an untitled section (e.g. the first group in a sidebar).\n */\n title?: string;\n children?: ReactNode;\n}\n\n/**\n * Named group of `SidebarItem` entries inside a `Sidebar`.\n *\n * Sections are separated by a thin divider. The title is optional —\n * omit it for the primary section when the grouping is self-evident.\n *\n * Mirrors the `AdwSidebar` section model (libadwaita 1.9 / GNOME 50).\n *\n * @example\n * ```tsx\n * <Sidebar>\n * <SidebarSection title=\"Mailboxes\">\n * <SidebarItem label=\"Inbox\" icon={GoHome} active />\n * <SidebarItem label=\"Sent\" icon={Share} />\n * </SidebarSection>\n * <SidebarSection title=\"Tags\">\n * <SidebarItem label=\"Work\" icon={Star} />\n * <SidebarItem label=\"Personal\" icon={Star} />\n * </SidebarSection>\n * </Sidebar>\n * ```\n */\nexport function SidebarSection({\n title,\n children,\n className,\n ...props\n}: SidebarSectionProps) {\n return (\n <section\n className={[styles.section, className].filter(Boolean).join(\" \")}\n {...props}\n >\n {title && (\n <h3 className={styles.sectionTitle}>{title}</h3>\n )}\n <ul role=\"list\" className={styles.list}>\n {children}\n </ul>\n </section>\n );\n}\n","/* ─── Tooltip bubble ─────────────────────────────────────────────────────────── */\n\n.tooltip {\n position: fixed;\n z-index: 10500; /* above Dialog (10000) and Toaster (9999) */\n\n max-width: 240px;\n padding: 4px var(--gnome-space-2, 12px);\n\n background-color: var(--gnome-tooltip-bg-color, #3d3d3d);\n color: var(--gnome-tooltip-fg-color, #ffffff);\n\n font-family: var(--gnome-font-family);\n font-size: var(--gnome-font-size-caption, 0.8125rem);\n font-weight: var(--gnome-font-weight-normal, 400);\n line-height: var(--gnome-line-height-body, 1.5);\n text-align: center;\n white-space: pre-wrap;\n word-break: break-word;\n\n border-radius: var(--gnome-radius-sm, 6px);\n box-shadow:\n 0 2px 8px rgba(0, 0, 0, 0.2),\n 0 0 0 1px rgba(255, 255, 255, 0.06);\n\n pointer-events: none;\n user-select: none;\n\n /* Hidden by default — fades in when .visible is applied */\n opacity: 0;\n transform: scale(0.94);\n transition:\n opacity var(--gnome-duration-fast, 100ms) var(--gnome-easing-default, ease),\n transform var(--gnome-duration-fast, 100ms) var(--gnome-easing-default, ease);\n}\n\n/* ─── Visible state ──────────────────────────────────────────────────────────── */\n\n.visible {\n opacity: 1;\n transform: scale(1);\n}\n\n/* ─── Arrow ──────────────────────────────────────────────────────────────────── */\n\n.tooltip::after {\n content: \"\";\n position: absolute;\n width: 0;\n height: 0;\n border: 5px solid transparent;\n pointer-events: none;\n}\n\n.top::after {\n top: 100%;\n left: 50%;\n transform: translateX(-50%);\n border-top-color: var(--gnome-tooltip-bg-color, #3d3d3d);\n}\n\n.bottom::after {\n bottom: 100%;\n left: 50%;\n transform: translateX(-50%);\n border-bottom-color: var(--gnome-tooltip-bg-color, #3d3d3d);\n}\n\n.left::after {\n left: 100%;\n top: 50%;\n transform: translateY(-50%);\n border-left-color: var(--gnome-tooltip-bg-color, #3d3d3d);\n}\n\n.right::after {\n right: 100%;\n top: 50%;\n transform: translateY(-50%);\n border-right-color: var(--gnome-tooltip-bg-color, #3d3d3d);\n}\n\n/* ─── Dark mode ──────────────────────────────────────────────────────────────── */\n\n@media (prefers-color-scheme: dark) {\n .tooltip {\n background-color: var(--gnome-tooltip-bg-color, #c0bfbc);\n color: var(--gnome-tooltip-fg-color, rgba(0, 0, 0, 0.8));\n box-shadow:\n 0 2px 8px rgba(0, 0, 0, 0.4),\n 0 0 0 1px rgba(0, 0, 0, 0.12);\n }\n\n .top::after { border-top-color: var(--gnome-tooltip-bg-color, #c0bfbc); }\n .bottom::after { border-bottom-color: var(--gnome-tooltip-bg-color, #c0bfbc); }\n .left::after { border-left-color: var(--gnome-tooltip-bg-color, #c0bfbc); }\n .right::after { border-right-color: var(--gnome-tooltip-bg-color, #c0bfbc); }\n}\n\n/* ─── Reduced motion ─────────────────────────────────────────────────────────── */\n\n@media (prefers-reduced-motion: reduce) {\n .tooltip {\n transition: opacity var(--gnome-duration-fast, 100ms) linear;\n transform: none;\n }\n\n .visible {\n transform: none;\n }\n}\n","import {\n useState,\n useRef,\n useId,\n useCallback,\n useEffect,\n cloneElement,\n type ReactElement,\n type HTMLAttributes,\n} from \"react\";\nimport { createPortal } from \"react-dom\";\nimport styles from \"./Tooltip.module.css\";\n\nexport type TooltipPlacement = \"top\" | \"bottom\" | \"left\" | \"right\";\n\nexport interface TooltipProps {\n /**\n * The tooltip label. Keep it short — a noun phrase or brief description.\n * Do not duplicate information already visible on screen.\n */\n label: string;\n /**\n * Preferred placement relative to the trigger.\n * The tooltip flips automatically if there is not enough space.\n * Defaults to `\"top\"`.\n */\n placement?: TooltipPlacement;\n /**\n * Delay in milliseconds before the tooltip appears on hover.\n * Defaults to `500`. Set to `0` for instant.\n */\n delay?: number;\n /**\n * The element that triggers the tooltip.\n * Must be a single React element that can receive `ref` and event props.\n */\n children: ReactElement<HTMLAttributes<HTMLElement>>;\n}\n\ninterface Position {\n top: number;\n left: number;\n placement: TooltipPlacement;\n}\n\nconst GAP = 6; // px between trigger and tooltip\n\nfunction computePosition(\n trigger: DOMRect,\n tooltip: DOMRect,\n preferred: TooltipPlacement\n): Position {\n const vw = window.innerWidth;\n const vh = window.innerHeight;\n\n const placements: TooltipPlacement[] = [\n preferred,\n preferred === \"top\" || preferred === \"bottom\"\n ? preferred === \"top\" ? \"bottom\" : \"top\"\n : preferred === \"left\" ? \"right\" : \"left\",\n \"top\",\n \"bottom\",\n \"left\",\n \"right\",\n ];\n\n for (const p of placements) {\n let top = 0;\n let left = 0;\n\n if (p === \"top\") {\n top = trigger.top - tooltip.height - GAP;\n left = trigger.left + trigger.width / 2 - tooltip.width / 2;\n } else if (p === \"bottom\") {\n top = trigger.bottom + GAP;\n left = trigger.left + trigger.width / 2 - tooltip.width / 2;\n } else if (p === \"left\") {\n top = trigger.top + trigger.height / 2 - tooltip.height / 2;\n left = trigger.left - tooltip.width - GAP;\n } else {\n top = trigger.top + trigger.height / 2 - tooltip.height / 2;\n left = trigger.right + GAP;\n }\n\n // Clamp to viewport with 8 px margin\n const margin = 8;\n const fitsH = left >= margin && left + tooltip.width <= vw - margin;\n const fitsV = top >= margin && top + tooltip.height <= vh - margin;\n\n if (fitsH && fitsV) {\n return {\n top: Math.max(margin, Math.min(top, vh - tooltip.height - margin)),\n left: Math.max(margin, Math.min(left, vw - tooltip.width - margin)),\n placement: p,\n };\n }\n }\n\n // Fallback: clamp preferred\n let top = 0, left = 0;\n if (preferred === \"top\") { top = trigger.top - tooltip.height - GAP; left = trigger.left + trigger.width / 2 - tooltip.width / 2; }\n if (preferred === \"bottom\") { top = trigger.bottom + GAP; left = trigger.left + trigger.width / 2 - tooltip.width / 2; }\n if (preferred === \"left\") { top = trigger.top + trigger.height / 2 - tooltip.height / 2; left = trigger.left - tooltip.width - GAP; }\n if (preferred === \"right\") { top = trigger.top + trigger.height / 2 - tooltip.height / 2; left = trigger.right + GAP; }\n\n const margin = 8;\n return {\n top: Math.max(margin, Math.min(top, vh - tooltip.height - margin)),\n left: Math.max(margin, Math.min(left, window.innerWidth - tooltip.width - margin)),\n placement: preferred,\n };\n}\n\n/**\n * Informational tooltip following the Adwaita / GNOME HIG pattern.\n *\n * Wraps a single trigger element and shows a floating label on hover or\n * keyboard focus. The tooltip is positioned automatically and flips if\n * there is not enough space.\n *\n * - `aria-describedby` is wired to the trigger automatically.\n * - The tooltip itself is `role=\"tooltip\"` and rendered in a portal.\n * - Does not show on touch — touch devices have no hover state.\n *\n * @example\n * <Tooltip label=\"Save file (Ctrl+S)\">\n * <Button aria-label=\"Save\"><Icon icon={Save} /></Button>\n * </Tooltip>\n *\n * @see https://developer.gnome.org/hig/patterns/feedback/tooltips.html\n */\nexport function Tooltip({\n label,\n placement: preferredPlacement = \"top\",\n delay = 500,\n children,\n}: TooltipProps) {\n const id = useId();\n const [visible, setVisible] = useState(false);\n const [pos, setPos] = useState<Position | null>(null);\n\n const triggerRef = useRef<HTMLElement>(null);\n const tooltipRef = useRef<HTMLDivElement>(null);\n const timerRef = useRef<ReturnType<typeof setTimeout> | null>(null);\n\n const clearTimer = () => {\n if (timerRef.current) { clearTimeout(timerRef.current); timerRef.current = null; }\n };\n\n const place = useCallback(() => {\n if (!triggerRef.current || !tooltipRef.current) return;\n const triggerRect = triggerRef.current.getBoundingClientRect();\n const tooltipRect = tooltipRef.current.getBoundingClientRect();\n setPos(computePosition(triggerRect, tooltipRect, preferredPlacement));\n }, [preferredPlacement]);\n\n // Reposition on scroll/resize while visible\n useEffect(() => {\n if (!visible) return;\n place();\n window.addEventListener(\"scroll\", place, { passive: true, capture: true });\n window.addEventListener(\"resize\", place, { passive: true });\n return () => {\n window.removeEventListener(\"scroll\", place, { capture: true });\n window.removeEventListener(\"resize\", place);\n };\n }, [visible, place]);\n\n const show = useCallback(() => {\n clearTimer();\n timerRef.current = setTimeout(() => setVisible(true), delay);\n }, [delay]);\n\n const hide = useCallback(() => {\n clearTimer();\n setVisible(false);\n setPos(null);\n }, []);\n\n // Position as soon as the tooltip mounts\n useEffect(() => {\n if (visible) place();\n }, [visible, place]);\n\n // Dismiss on Escape\n useEffect(() => {\n if (!visible) return;\n const handler = (e: KeyboardEvent) => { if (e.key === \"Escape\") hide(); };\n document.addEventListener(\"keydown\", handler);\n return () => document.removeEventListener(\"keydown\", handler);\n }, [visible, hide]);\n\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const child = cloneElement(children as any, {\n ref: triggerRef,\n \"aria-describedby\": id,\n onMouseEnter: (e: React.MouseEvent<HTMLElement>) => {\n show();\n children.props.onMouseEnter?.(e);\n },\n onMouseLeave: (e: React.MouseEvent<HTMLElement>) => {\n hide();\n children.props.onMouseLeave?.(e);\n },\n onFocus: (e: React.FocusEvent<HTMLElement>) => {\n show();\n children.props.onFocus?.(e);\n },\n onBlur: (e: React.FocusEvent<HTMLElement>) => {\n hide();\n children.props.onBlur?.(e);\n },\n });\n\n const tooltip = (\n <div\n ref={tooltipRef}\n id={id}\n role=\"tooltip\"\n className={[\n styles.tooltip,\n pos ? styles[pos.placement] : null,\n visible && pos ? styles.visible : null,\n ]\n .filter(Boolean)\n .join(\" \")}\n style={\n pos\n ? { top: pos.top, left: pos.left }\n : { visibility: \"hidden\", top: -9999, left: -9999 }\n }\n >\n {label}\n </div>\n );\n\n return (\n <>\n {child}\n {typeof document !== \"undefined\"\n ? createPortal(tooltip, document.body)\n : tooltip}\n </>\n );\n}\n","import {\n useState,\n useRef,\n useEffect,\n useCallback,\n useContext,\n type ButtonHTMLAttributes,\n type DragEvent,\n type ReactNode,\n type MouseEvent,\n type KeyboardEvent,\n} from \"react\";\nimport { createPortal } from \"react-dom\";\nimport type { IconDefinition } from \"@gnome-ui/icons\";\nimport { Icon } from \"../Icon\";\nimport { Tooltip } from \"../Tooltip\";\nimport { useSidebarCollapsed, SidebarFilterContext } from \"./Sidebar\";\nimport styles from \"./Sidebar.module.css\";\n\nexport interface SidebarMenuEntry {\n /** Label shown in the context menu. */\n label: string;\n /** Called when the entry is clicked. */\n onClick: () => void;\n /** Renders the entry in red. Use for destructive actions. */\n destructive?: boolean;\n /** Disables the entry. */\n disabled?: boolean;\n}\n\nexport interface SidebarItemProps extends ButtonHTMLAttributes<HTMLButtonElement> {\n /** Primary label. */\n label: string;\n /** Icon from `@gnome-ui/icons`. */\n icon?: IconDefinition;\n /** Marks this item as the currently active view. */\n active?: boolean;\n /**\n * Trailing widget — any ReactNode (badge, button, icon…).\n * Supersedes `badge` when both are provided.\n */\n suffix?: ReactNode;\n /**\n * @deprecated Use `suffix` instead.\n */\n badge?: ReactNode;\n /**\n * Short tooltip shown when hovering the item.\n * Automatically shown when the sidebar is `collapsed`.\n */\n tooltip?: string;\n /**\n * Context-menu entries shown on right-click or the Menu key.\n */\n menuItems?: SidebarMenuEntry[];\n /**\n * Called when a dragged item is dropped onto this row.\n * Mirrors `AdwSidebar` per-row drop target support (libadwaita 1.9).\n */\n onDrop?: (e: DragEvent<HTMLButtonElement>) => void;\n /**\n * MIME types this row accepts as drop targets (e.g. `[\"text/plain\"]`).\n * When provided, drops of other types are rejected via `e.preventDefault()`.\n * When omitted, all types are accepted.\n */\n acceptTypes?: string[];\n}\n\n/**\n * Individual navigation item inside a `Sidebar` or `SidebarSection`.\n *\n * **Tier 11 additions:**\n * - Automatically hidden when `Sidebar`'s `filter` / `searchable` is active\n * and the `label` does not match (case-insensitive substring).\n * - `onDrop` / `acceptTypes` — HTML5 drag-and-drop target support.\n */\nexport function SidebarItem({\n label,\n icon,\n active = false,\n suffix,\n badge,\n tooltip,\n menuItems,\n onDrop,\n acceptTypes,\n className,\n ...props\n}: SidebarItemProps) {\n const collapsed = useSidebarCollapsed();\n const filterValue = useContext(SidebarFilterContext);\n const trailing = suffix ?? badge;\n const effectiveTooltip = collapsed ? (tooltip ?? label) : tooltip;\n\n // ── Filter visibility ────────────────────────────────────────────────────\n const isFiltered =\n filterValue.length > 0 &&\n !label.toLowerCase().includes(filterValue.toLowerCase());\n\n // ── Drag-and-drop ────────────────────────────────────────────────────────\n const [isDragOver, setIsDragOver] = useState(false);\n\n const handleDragOver = (e: DragEvent<HTMLButtonElement>) => {\n if (!onDrop) return;\n if (acceptTypes?.length) {\n const accepted = acceptTypes.some((t) => e.dataTransfer.types.includes(t));\n if (!accepted) return;\n }\n e.preventDefault();\n setIsDragOver(true);\n };\n\n const handleDragLeave = () => setIsDragOver(false);\n\n const handleDrop = (e: DragEvent<HTMLButtonElement>) => {\n if (!onDrop) return;\n e.preventDefault();\n setIsDragOver(false);\n onDrop(e);\n };\n\n // ── Context menu ─────────────────────────────────────────────────────────\n const [menu, setMenu] = useState<{ x: number; y: number } | null>(null);\n const menuRef = useRef<HTMLDivElement>(null);\n\n const openMenu = useCallback((x: number, y: number) => setMenu({ x, y }), []);\n const closeMenu = useCallback(() => setMenu(null), []);\n\n useEffect(() => {\n if (!menu) return;\n const handleClick = (e: globalThis.MouseEvent) => {\n if (menuRef.current && !menuRef.current.contains(e.target as Node)) closeMenu();\n };\n const handleKey = (e: globalThis.KeyboardEvent) => {\n if (e.key === \"Escape\") closeMenu();\n };\n document.addEventListener(\"mousedown\", handleClick);\n document.addEventListener(\"keydown\", handleKey);\n return () => {\n document.removeEventListener(\"mousedown\", handleClick);\n document.removeEventListener(\"keydown\", handleKey);\n };\n }, [menu, closeMenu]);\n\n const handleContextMenu = (e: MouseEvent<HTMLButtonElement>) => {\n if (!menuItems?.length) return;\n e.preventDefault();\n openMenu(e.clientX, e.clientY);\n };\n\n const handleMenuKey = (e: KeyboardEvent<HTMLButtonElement>) => {\n if (e.key === \"ContextMenu\" || (e.key === \"F10\" && e.shiftKey)) {\n if (!menuItems?.length) return;\n e.preventDefault();\n const rect = (e.currentTarget as HTMLElement).getBoundingClientRect();\n openMenu(rect.right, rect.top);\n }\n };\n\n // ── Render ───────────────────────────────────────────────────────────────\n const button = (\n <button\n type=\"button\"\n aria-current={active ? \"page\" : undefined}\n aria-label={collapsed ? label : undefined}\n className={[\n styles.itemBtn,\n active ? styles.active : null,\n collapsed ? styles.itemCollapsed : null,\n isDragOver ? styles.dragOver : null,\n className,\n ]\n .filter(Boolean)\n .join(\" \")}\n onContextMenu={handleContextMenu}\n onKeyDown={handleMenuKey}\n onDragOver={handleDragOver}\n onDragLeave={handleDragLeave}\n onDrop={handleDrop}\n {...props}\n >\n {icon && (\n <span className={styles.itemIcon}>\n <Icon icon={icon} size=\"md\" aria-hidden />\n </span>\n )}\n {!collapsed && <span className={styles.itemLabel}>{label}</span>}\n {!collapsed && trailing && <span className={styles.itemSuffix}>{trailing}</span>}\n </button>\n );\n\n return (\n <li className={styles.item} hidden={isFiltered || undefined}>\n {effectiveTooltip\n ? <Tooltip label={effectiveTooltip} placement=\"right\">{button}</Tooltip>\n : button}\n\n {menu && menuItems?.length && typeof document !== \"undefined\" &&\n createPortal(\n <div\n ref={menuRef}\n className={styles.contextMenu}\n style={{ top: menu.y, left: menu.x }}\n role=\"menu\"\n >\n {menuItems.map((entry) => (\n <button\n key={entry.label}\n type=\"button\"\n role=\"menuitem\"\n disabled={entry.disabled}\n className={[\n styles.contextMenuItem,\n entry.destructive ? styles.contextMenuDestructive : null,\n ]\n .filter(Boolean)\n .join(\" \")}\n onClick={() => {\n entry.onClick();\n closeMenu();\n }}\n >\n {entry.label}\n </button>\n ))}\n </div>,\n document.body,\n )}\n </li>\n );\n}\n","/* ─── Container ────────────────────────────────────────────────────────────── */\n\n.spin {\n display: inline-flex;\n align-items: center;\n\n background-color: var(--gnome-view-bg-color, #ffffff);\n border: 1px solid var(--gnome-card-shade-color, rgba(0, 0, 0, 0.15));\n border-radius: var(--gnome-radius-md, 8px);\n box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.06);\n overflow: hidden;\n\n font-family: var(--gnome-font-family);\n font-size: var(--gnome-font-size-body, 1rem);\n color: var(--gnome-view-fg-color, rgba(0, 0, 0, 0.8));\n\n transition: box-shadow var(--gnome-duration-fast) var(--gnome-easing-default),\n border-color var(--gnome-duration-fast) var(--gnome-easing-default);\n}\n\n.spin:focus {\n outline: none;\n}\n\n.spin:focus-visible {\n border-color: var(--gnome-accent-color, #3584e4);\n box-shadow:\n inset 0 1px 2px rgba(0, 0, 0, 0.06),\n 0 0 0 var(--gnome-focus-ring-offset, 2px) var(--gnome-window-bg-color, #fafafa),\n 0 0 0 calc(var(--gnome-focus-ring-offset, 2px) + var(--gnome-focus-ring-width, 3px))\n var(--gnome-focus-ring-color, #3584e4);\n}\n\n/* ─── Disabled ─────────────────────────────────────────────────────────────── */\n\n.disabled {\n opacity: var(--gnome-opacity-disabled, 0.5);\n pointer-events: none;\n}\n\n/* ─── +/− buttons ──────────────────────────────────────────────────────────── */\n\n.btn {\n appearance: none;\n -webkit-appearance: none;\n border: none;\n background: transparent;\n font: inherit;\n font-size: 1.1rem;\n line-height: 1;\n cursor: pointer;\n color: inherit;\n\n display: flex;\n align-items: center;\n justify-content: center;\n width: 36px;\n height: 36px;\n flex-shrink: 0;\n\n transition: background-color var(--gnome-duration-fast) var(--gnome-easing-default);\n}\n\n.btn:hover:not(:disabled) {\n background-color: color-mix(\n in srgb,\n var(--gnome-view-bg-color, #ffffff) 90%,\n black 10%\n );\n}\n\n.btn:active:not(:disabled) {\n background-color: color-mix(\n in srgb,\n var(--gnome-view-bg-color, #ffffff) 80%,\n black 20%\n );\n}\n\n.btn:disabled {\n opacity: var(--gnome-opacity-disabled, 0.5);\n cursor: not-allowed;\n}\n\n/* Dividers between button and value */\n.btn:first-child {\n border-right: 1px solid var(--gnome-card-shade-color, rgba(0, 0, 0, 0.12));\n}\n\n.btn:last-child {\n border-left: 1px solid var(--gnome-card-shade-color, rgba(0, 0, 0, 0.12));\n}\n\n/* ─── Value display ────────────────────────────────────────────────────────── */\n\n.value {\n flex: 1;\n text-align: center;\n padding: 0 var(--gnome-space-2, 12px);\n min-width: 56px;\n font-variant-numeric: tabular-nums;\n user-select: none;\n white-space: nowrap;\n}\n\n/* ─── Dark mode ────────────────────────────────────────────────────────────── */\n\n@media (prefers-color-scheme: dark) {\n .spin {\n background-color: var(--gnome-view-bg-color, #1e1e1e);\n border-color: rgba(255, 255, 255, 0.12);\n box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.2);\n }\n\n .btn:hover:not(:disabled) {\n background-color: color-mix(\n in srgb,\n var(--gnome-view-bg-color, #1e1e1e) 85%,\n white 15%\n );\n }\n\n .btn:active:not(:disabled) {\n background-color: color-mix(\n in srgb,\n var(--gnome-view-bg-color, #1e1e1e) 75%,\n white 25%\n );\n }\n}\n\n/* ─── Reduced motion ───────────────────────────────────────────────────────── */\n\n@media (prefers-reduced-motion: reduce) {\n .spin,\n .btn {\n transition: none;\n }\n}\n","import { useCallback, type KeyboardEvent, type HTMLAttributes } from \"react\";\nimport styles from \"./SpinButton.module.css\";\n\nexport interface SpinButtonProps\n extends Omit<HTMLAttributes<HTMLDivElement>, \"onChange\"> {\n /** Current value. */\n value: number;\n /** Called when the value changes. */\n onChange: (value: number) => void;\n /** Minimum allowed value. Defaults to `0`. */\n min?: number;\n /** Maximum allowed value. Defaults to `100`. */\n max?: number;\n /** Amount to increment/decrement per step. Defaults to `1`. */\n step?: number;\n /** Number of decimal places shown. Derived from `step` when omitted. */\n decimals?: number;\n /** Disables the control. */\n disabled?: boolean;\n /** Accessible label. Required when no visible `<label>` is associated. */\n \"aria-label\"?: string;\n /** Associates the control with a visible label element. */\n \"aria-labelledby\"?: string;\n}\n\nfunction countDecimals(n: number): number {\n const s = n.toString();\n const dot = s.indexOf(\".\");\n return dot === -1 ? 0 : s.length - dot - 1;\n}\n\nfunction clamp(value: number, min: number, max: number): number {\n return Math.min(max, Math.max(min, value));\n}\n\n/**\n * Numeric input with − and + buttons following the Adwaita `GtkSpinButton` style.\n *\n * Supports keyboard interaction: ↑/↓ increment by one step,\n * Page Up/Down increment by 10×, Home/End jump to min/max.\n *\n * @see https://developer.gnome.org/hig/patterns/controls/spin-buttons.html\n */\nexport function SpinButton({\n value,\n onChange,\n min = 0,\n max = 100,\n step = 1,\n decimals,\n disabled = false,\n className,\n \"aria-label\": ariaLabel,\n \"aria-labelledby\": ariaLabelledBy,\n ...props\n}: SpinButtonProps) {\n const dp = decimals ?? countDecimals(step);\n\n const set = useCallback(\n (next: number) => onChange(parseFloat(clamp(next, min, max).toFixed(dp))),\n [onChange, min, max, dp],\n );\n\n const handleKeyDown = useCallback(\n (e: KeyboardEvent<HTMLDivElement>) => {\n switch (e.key) {\n case \"ArrowUp\": e.preventDefault(); set(value + step); break;\n case \"ArrowDown\": e.preventDefault(); set(value - step); break;\n case \"PageUp\": e.preventDefault(); set(value + step * 10); break;\n case \"PageDown\": e.preventDefault(); set(value - step * 10); break;\n case \"Home\": e.preventDefault(); set(min); break;\n case \"End\": e.preventDefault(); set(max); break;\n }\n },\n [value, step, min, max, set],\n );\n\n return (\n <div\n role=\"spinbutton\"\n aria-valuenow={value}\n aria-valuemin={min}\n aria-valuemax={max}\n aria-label={ariaLabel}\n aria-labelledby={ariaLabelledBy}\n aria-disabled={disabled || undefined}\n tabIndex={disabled ? -1 : 0}\n onKeyDown={disabled ? undefined : handleKeyDown}\n className={[styles.spin, disabled ? styles.disabled : null, className]\n .filter(Boolean)\n .join(\" \")}\n {...props}\n >\n <button\n type=\"button\"\n tabIndex={-1}\n aria-hidden=\"true\"\n disabled={disabled || value <= min}\n className={styles.btn}\n onClick={() => set(value - step)}\n >\n −\n </button>\n\n <span className={styles.value} aria-hidden=\"true\">\n {value.toFixed(dp)}\n </span>\n\n <button\n type=\"button\"\n tabIndex={-1}\n aria-hidden=\"true\"\n disabled={disabled || value >= max}\n className={styles.btn}\n onClick={() => set(value + step)}\n >\n +\n </button>\n </div>\n );\n}\n","/* ─── Bar (outer wrapper) ──────────────────────────────────────────────────── */\n\n.bar {\n background-color: var(--gnome-headerbar-bg-color, #ebebeb);\n border-bottom: 1px solid var(--gnome-headerbar-border-color, rgba(0, 0, 0, 0.12));\n}\n\n/* ─── Tab list ─────────────────────────────────────────────────────────────── */\n\n.list {\n display: flex;\n align-items: stretch;\n overflow-x: auto;\n scrollbar-width: none;\n padding: 0 var(--gnome-space-1, 6px);\n gap: 2px;\n min-height: 40px;\n}\n\n.list::-webkit-scrollbar {\n display: none;\n}\n\n/* ─── Tab button ───────────────────────────────────────────────────────────── */\n\n.tab {\n appearance: none;\n -webkit-appearance: none;\n border: none;\n background: transparent;\n font: inherit;\n cursor: pointer;\n outline: none;\n\n position: relative;\n display: inline-flex;\n align-items: center;\n gap: var(--gnome-space-1, 6px);\n padding: 0 var(--gnome-space-2, 12px);\n min-width: 80px;\n max-width: 200px;\n white-space: nowrap;\n\n font-family: var(--gnome-font-family);\n font-size: var(--gnome-font-size-body, 1rem);\n font-weight: var(--gnome-font-weight-normal, 400);\n color: var(--gnome-headerbar-fg-color, rgba(0, 0, 0, 0.8));\n opacity: 0.7;\n\n border-radius: var(--gnome-radius-md, 8px) var(--gnome-radius-md, 8px) 0 0;\n transition:\n background-color var(--gnome-duration-fast) var(--gnome-easing-default),\n opacity var(--gnome-duration-fast) var(--gnome-easing-default);\n}\n\n.tab::after {\n content: \"\";\n position: absolute;\n bottom: 0;\n left: var(--gnome-space-2, 12px);\n right: var(--gnome-space-2, 12px);\n height: 2px;\n border-radius: 1px 1px 0 0;\n background-color: var(--gnome-accent-bg-color, #3584e4);\n opacity: 0;\n transition: opacity var(--gnome-duration-fast) var(--gnome-easing-default);\n}\n\n.tab:hover {\n opacity: 1;\n background-color: var(--gnome-hover-overlay);\n}\n\n.tab:disabled {\n opacity: var(--gnome-opacity-disabled, 0.5);\n cursor: not-allowed;\n}\n\n/* ─── Active tab ───────────────────────────────────────────────────────────── */\n\n.active {\n opacity: 1;\n font-weight: var(--gnome-font-weight-semibold, 600);\n background-color: var(--gnome-active-overlay);\n}\n\n.active::after {\n opacity: 1;\n}\n\n/* ─── Focus ────────────────────────────────────────────────────────────────── */\n\n.tab:focus-visible {\n box-shadow:\n inset 0 0 0 var(--gnome-focus-ring-offset, 2px) var(--gnome-headerbar-bg-color, #ebebeb),\n inset 0 0 0 calc(var(--gnome-focus-ring-offset, 2px) + var(--gnome-focus-ring-width, 3px))\n var(--gnome-focus-ring-color, #3584e4);\n}\n\n/* ─── Icon inside tab ──────────────────────────────────────────────────────── */\n\n.tabIcon {\n display: flex;\n align-items: center;\n flex-shrink: 0;\n}\n\n/* ─── Label inside tab ─────────────────────────────────────────────────────── */\n\n.tabLabel {\n flex: 1;\n min-width: 0;\n overflow: hidden;\n text-overflow: ellipsis;\n}\n\n/* ─── Close button ─────────────────────────────────────────────────────────── */\n\n.closeBtn {\n flex-shrink: 0;\n display: flex;\n align-items: center;\n justify-content: center;\n width: 18px;\n height: 18px;\n border-radius: var(--gnome-radius-sm, 4px);\n font-size: 1rem;\n line-height: 1;\n opacity: 0.5;\n margin-left: 2px;\n transition: opacity var(--gnome-duration-fast) var(--gnome-easing-default),\n background-color var(--gnome-duration-fast) var(--gnome-easing-default);\n}\n\n.closeBtn:hover {\n opacity: 1;\n background-color: var(--gnome-active-overlay);\n}\n\n.closeBtn:focus-visible {\n outline: 2px solid var(--gnome-focus-ring-color, #3584e4);\n outline-offset: 1px;\n opacity: 1;\n}\n\n/* ─── Panel ────────────────────────────────────────────────────────────────── */\n\n.panel {\n outline: none;\n}\n\n.panel:focus-visible {\n box-shadow: inset 0 0 0 2px var(--gnome-focus-ring-color, #3584e4);\n}\n\n/* ─── Inline variant ───────────────────────────────────────────────────────── */\n\n.inline {\n background-color: transparent;\n border-bottom-color: transparent;\n}\n\n.inline .tab:focus-visible {\n box-shadow:\n inset 0 0 0 var(--gnome-focus-ring-offset, 2px) var(--gnome-window-bg-color, #fafafa),\n inset 0 0 0 calc(var(--gnome-focus-ring-offset, 2px) + var(--gnome-focus-ring-width, 3px))\n var(--gnome-focus-ring-color, #3584e4);\n}\n\n/* ─── High contrast ────────────────────────────────────────────────────────── */\n\n@media (prefers-contrast: more) {\n .bar {\n border-bottom-width: 2px;\n }\n\n .tab::after {\n height: 3px;\n }\n}\n\n/* ─── Reduced motion ───────────────────────────────────────────────────────── */\n\n@media (prefers-reduced-motion: reduce) {\n .tab,\n .tab::after,\n .closeBtn {\n transition: none;\n }\n}\n","import {\n useRef,\n type HTMLAttributes,\n type KeyboardEvent,\n type ReactNode,\n} from \"react\";\nimport styles from \"./Tabs.module.css\";\n\nexport interface TabBarProps extends HTMLAttributes<HTMLDivElement> {\n children?: ReactNode;\n /** Accessible label for the tab list. */\n \"aria-label\"?: string;\n /**\n * Removes the header-bar background so the tab bar blends into any surface.\n * Use when placing the bar inside a card, content area, or custom container.\n * Mirrors the `.inline` style class.\n */\n inline?: boolean;\n}\n\n/**\n * Horizontal tab bar that holds `TabItem` elements.\n *\n * Manages roving-tabindex keyboard navigation (← → Home End).\n *\n * @see https://developer.gnome.org/hig/patterns/nav/tabs.html\n */\nexport function TabBar({\n children,\n className,\n inline = false,\n \"aria-label\": ariaLabel = \"Tabs\",\n ...props\n}: TabBarProps) {\n const listRef = useRef<HTMLDivElement>(null);\n\n function handleKeyDown(e: KeyboardEvent<HTMLDivElement>) {\n const tabs = Array.from(\n listRef.current?.querySelectorAll<HTMLButtonElement>(\n \"[role=tab]:not(:disabled)\",\n ) ?? [],\n );\n const idx = tabs.findIndex((t) => t === document.activeElement);\n if (idx === -1) return;\n\n let next = idx;\n if (e.key === \"ArrowRight\") next = (idx + 1) % tabs.length;\n else if (e.key === \"ArrowLeft\") next = (idx - 1 + tabs.length) % tabs.length;\n else if (e.key === \"Home\") next = 0;\n else if (e.key === \"End\") next = tabs.length - 1;\n else return;\n\n e.preventDefault();\n tabs[next].focus();\n }\n\n return (\n <div\n className={[styles.bar, inline ? styles.inline : null, className]\n .filter(Boolean)\n .join(\" \")}\n {...props}\n >\n <div\n ref={listRef}\n role=\"tablist\"\n aria-label={ariaLabel}\n className={styles.list}\n onKeyDown={handleKeyDown}\n >\n {children}\n </div>\n </div>\n );\n}\n","import type { ButtonHTMLAttributes, ReactNode } from \"react\";\nimport type { IconDefinition } from \"@gnome-ui/icons\";\nimport { Icon } from \"../Icon\";\nimport styles from \"./Tabs.module.css\";\n\nexport interface TabItemProps extends ButtonHTMLAttributes<HTMLButtonElement> {\n /** Tab label. */\n label: string;\n /** Optional icon from `@gnome-ui/icons`. */\n icon?: IconDefinition;\n /** Marks this tab as the currently selected one. */\n active?: boolean;\n /**\n * id of the `TabPanel` this tab controls.\n * Sets `aria-controls` automatically.\n */\n panelId?: string;\n /**\n * When provided, a close (×) button is rendered at the trailing edge.\n * Called when the user clicks the close button.\n */\n onClose?: () => void;\n /** Accessible label for the close button. Defaults to `\"Close tab\"`. */\n closeLabel?: string;\n children?: ReactNode;\n}\n\n/**\n * Individual tab button inside a `TabBar`.\n */\nexport function TabItem({\n label,\n icon,\n active = false,\n panelId,\n onClose,\n closeLabel = \"Close tab\",\n className,\n disabled,\n ...props\n}: TabItemProps) {\n return (\n <button\n role=\"tab\"\n type=\"button\"\n aria-selected={active}\n aria-controls={panelId}\n tabIndex={active ? 0 : -1}\n disabled={disabled}\n className={[\n styles.tab,\n active ? styles.active : null,\n onClose ? styles.closeable : null,\n className,\n ]\n .filter(Boolean)\n .join(\" \")}\n {...props}\n >\n {icon && (\n <span className={styles.tabIcon}>\n <Icon icon={icon} size=\"sm\" aria-hidden />\n </span>\n )}\n <span className={styles.tabLabel}>{label}</span>\n\n {onClose && (\n <span\n role=\"button\"\n tabIndex={0}\n aria-label={closeLabel}\n className={styles.closeBtn}\n onClick={(e) => { e.stopPropagation(); onClose(); }}\n onKeyDown={(e) => {\n if (e.key === \"Enter\" || e.key === \" \") {\n e.preventDefault();\n e.stopPropagation();\n onClose();\n }\n }}\n >\n ×\n </span>\n )}\n </button>\n );\n}\n","import type { HTMLAttributes, ReactNode } from \"react\";\nimport styles from \"./Tabs.module.css\";\n\nexport interface TabPanelProps extends HTMLAttributes<HTMLDivElement> {\n /** Must match the `panelId` passed to the corresponding `TabItem`. */\n id: string;\n /** Controls whether this panel is rendered. */\n active?: boolean;\n children?: ReactNode;\n}\n\n/**\n * Content panel associated with a `TabItem`.\n * Hidden panels are kept in the DOM but visually hidden.\n */\nexport function TabPanel({\n id,\n active = false,\n className,\n children,\n ...props\n}: TabPanelProps) {\n return (\n <div\n id={id}\n role=\"tabpanel\"\n hidden={!active}\n tabIndex={0}\n className={[styles.panel, className].filter(Boolean).join(\" \")}\n {...props}\n >\n {children}\n </div>\n );\n}\n","/* ─── Base row ─────────────────────────────────────────────────────────────── */\n\n.row {\n display: flex;\n align-items: center;\n gap: var(--gnome-space-2, 12px);\n padding: var(--gnome-space-2, 12px) var(--gnome-space-4, 24px);\n min-height: 52px;\n width: 100%;\n color: var(--gnome-card-fg-color, rgba(0, 0, 0, 0.8));\n text-align: start;\n}\n\n/* ─── Leading icon/avatar ──────────────────────────────────────────────────── */\n\n.leading {\n display: flex;\n align-items: center;\n flex-shrink: 0;\n color: var(--gnome-window-fg-color, rgba(0, 0, 0, 0.8));\n}\n\n/* ─── Text content ─────────────────────────────────────────────────────────── */\n\n.content {\n display: flex;\n flex-direction: column;\n flex: 1;\n min-width: 0;\n gap: 2px;\n}\n\n.title {\n font-family: var(--gnome-font-family);\n font-size: var(--gnome-font-size-body, 1rem);\n font-weight: var(--gnome-font-weight-normal, 400);\n line-height: var(--gnome-line-height-body, 1.5);\n color: var(--gnome-card-fg-color, rgba(0, 0, 0, 0.8));\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n}\n\n.subtitle {\n font-family: var(--gnome-font-family);\n font-size: var(--gnome-font-size-caption, 0.75rem);\n font-weight: var(--gnome-font-weight-normal, 400);\n line-height: var(--gnome-line-height-body, 1.5);\n color: var(--gnome-window-fg-color, rgba(0, 0, 0, 0.55));\n opacity: var(--gnome-opacity-dim, 0.55);\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n}\n\n/* ─── Trailing widget ──────────────────────────────────────────────────────── */\n\n.trailing {\n display: flex;\n align-items: center;\n flex-shrink: 0;\n gap: var(--gnome-space-1, 6px);\n}\n\n/* ─── Property variant ─────────────────────────────────────────────────────── */\n\n/* The label (title) is rendered small and dim above the value (subtitle) */\n.propertyLabel {\n font-family: var(--gnome-font-family);\n font-size: var(--gnome-font-size-caption, 0.75rem);\n font-weight: var(--gnome-font-weight-normal, 400);\n line-height: var(--gnome-line-height-body, 1.5);\n color: var(--gnome-window-fg-color, rgba(0, 0, 0, 0.55));\n opacity: var(--gnome-opacity-dim, 0.55);\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n}\n\n/* The value (subtitle) is rendered at body size and full opacity */\n.propertyValue {\n font-family: var(--gnome-font-family);\n font-size: var(--gnome-font-size-body, 1rem);\n font-weight: var(--gnome-font-weight-normal, 400);\n line-height: var(--gnome-line-height-body, 1.5);\n color: var(--gnome-card-fg-color, rgba(0, 0, 0, 0.8));\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n}\n\n/* ─── Interactive (activatable row) ────────────────────────────────────────── */\n\n.interactive {\n cursor: pointer;\n appearance: none;\n -webkit-appearance: none;\n background: transparent;\n border: none;\n font: inherit;\n outline: none;\n\n transition: background-color var(--gnome-duration-fast) var(--gnome-easing-default);\n}\n\n.interactive:hover {\n background-color: var(--gnome-hover-overlay);\n}\n\n.interactive:active {\n background-color: var(--gnome-active-overlay);\n}\n\n.interactive:focus-visible {\n box-shadow:\n inset 0 0 0 var(--gnome-focus-ring-offset, 2px) var(--gnome-window-bg-color, #fafafa),\n inset 0 0 0 calc(var(--gnome-focus-ring-offset, 2px) + var(--gnome-focus-ring-width, 3px))\n var(--gnome-focus-ring-color, #3584e4);\n}\n\n.interactive:disabled {\n opacity: var(--gnome-opacity-disabled, 0.5);\n cursor: not-allowed;\n pointer-events: none;\n}\n","import type { HTMLAttributes, ReactNode } from \"react\";\nimport styles from \"./ActionRow.module.css\";\n\nexport interface ActionRowProps extends HTMLAttributes<HTMLDivElement> {\n /** Primary label. */\n title: string;\n /** Secondary line below the title. */\n subtitle?: string;\n /** Icon or image placed at the leading edge. */\n leading?: ReactNode;\n /**\n * Widget placed at the trailing edge (Switch, Button, Text…).\n * For interactive end widgets prefer a controlled component and stop\n * event propagation inside it so the row's own onClick isn't triggered.\n */\n trailing?: ReactNode;\n /**\n * When true the entire row becomes clickable (renders as `<button>`).\n * Use for rows that navigate or trigger an action.\n */\n interactive?: boolean;\n /**\n * `\"property\"` flips the visual hierarchy: the subtitle becomes the\n * prominent value and the title shrinks to a dim label above it.\n * Use for read-only property display (e.g. \"OS Version / Ubuntu 24.04\").\n * Mirrors the `.property` style class.\n */\n variant?: \"default\" | \"property\";\n}\n\n/**\n * Standard settings row with title, optional subtitle, and end widget.\n *\n * Mirrors the Adwaita `AdwActionRow` pattern — the fundamental building block\n * inside a `BoxedList`.\n *\n * @see https://gnome.pages.gitlab.gnome.org/libadwaita/doc/main/class.ActionRow.html\n * @see https://developer.gnome.org/hig/patterns/containers.html\n */\nexport function ActionRow({\n title,\n subtitle,\n leading,\n trailing,\n interactive = false,\n variant = \"default\",\n className,\n ...props\n}: ActionRowProps) {\n const Tag = interactive ? \"button\" : \"div\";\n const property = variant === \"property\";\n\n const classes = [\n styles.row,\n interactive ? styles.interactive : null,\n property ? styles.property : null,\n className,\n ]\n .filter(Boolean)\n .join(\" \");\n\n return (\n <Tag className={classes} {...(props as HTMLAttributes<HTMLElement>)}>\n {leading && <span className={styles.leading}>{leading}</span>}\n\n <span className={styles.content}>\n <span className={property ? styles.propertyLabel : styles.title}>{title}</span>\n {subtitle && <span className={property ? styles.propertyValue : styles.subtitle}>{subtitle}</span>}\n </span>\n\n {trailing && <span className={styles.trailing}>{trailing}</span>}\n </Tag>\n );\n}\n","/* ─── Container ────────────────────────────────────────────────────────────── */\n\n.list {\n list-style: none;\n margin: 0;\n padding: 0;\n\n background-color: var(--gnome-card-bg-color, #ffffff);\n color: var(--gnome-card-fg-color, rgba(0, 0, 0, 0.8));\n border: 1px solid var(--gnome-divider-color);\n border-radius: var(--gnome-radius-lg);\n overflow: hidden;\n}\n\n/* ─── Row wrapper ──────────────────────────────────────────────────────────── */\n\n.item {\n position: relative;\n display: flex;\n flex-direction: column;\n}\n\n/* ─── Divider between rows ─────────────────────────────────────────────────── */\n\n.divider {\n height: 1px;\n background-color: var(--gnome-divider-color);\n /* Indent to align with row content — matches ActionRow's horizontal padding */\n margin-left: var(--gnome-space-4, 24px);\n}\n\n/* ─── Separate variant ─────────────────────────────────────────────────────── */\n\n.separate {\n background: none;\n border: none;\n border-radius: 0;\n display: flex;\n flex-direction: column;\n gap: var(--gnome-space-2, 12px);\n overflow: visible;\n}\n\n.separateItem {\n background-color: var(--gnome-card-bg-color, #ffffff);\n border: 1px solid var(--gnome-divider-color);\n border-radius: var(--gnome-radius-lg);\n overflow: hidden;\n display: flex;\n flex-direction: column;\n}\n\n@media (prefers-color-scheme: dark) {\n .separateItem {\n background-color: var(--gnome-card-bg-color, #2d2d2d);\n }\n}\n\n/* ─── High contrast ────────────────────────────────────────────────────────── */\n\n@media (prefers-contrast: more) {\n .list {\n border-width: 2px;\n }\n\n .divider {\n height: 2px;\n }\n}\n","import { Children, type HTMLAttributes, type ReactNode } from \"react\";\nimport styles from \"./BoxedList.module.css\";\n\nexport type BoxedListVariant = \"default\" | \"separate\";\n\nexport interface BoxedListProps extends HTMLAttributes<HTMLUListElement> {\n children?: ReactNode;\n /**\n * `\"separate\"` renders each child as its own standalone rounded card\n * instead of a single joined list — mirrors `.boxed-list-separate`.\n * Use when rows are independent items rather than a continuous group.\n */\n variant?: BoxedListVariant;\n}\n\n/**\n * Rounded bordered list — the most common container pattern in GNOME apps.\n *\n * Mirrors the Adwaita `.boxed-list` style applied to a `GtkListBox`.\n * Separators between rows are inserted automatically.\n * Each child is wrapped in a `<li>` — use with `ActionRow` or any row-shaped element.\n *\n * Use `variant=\"separate\"` to render each child as its own card, mirroring\n * the `.boxed-list-separate` style class.\n *\n * @see https://gnome.pages.gitlab.gnome.org/libadwaita/doc/main/style-classes.html#boxed-lists-cards\n * @see https://developer.gnome.org/hig/patterns/containers.html\n */\nexport function BoxedList({ children, variant = \"default\", className, ...props }: BoxedListProps) {\n const items = Children.toArray(children).filter(Boolean);\n const separate = variant === \"separate\";\n\n return (\n <ul\n role=\"list\"\n className={[styles.list, separate ? styles.separate : null, className]\n .filter(Boolean)\n .join(\" \")}\n {...props}\n >\n {items.map((child, i) => (\n <li key={i} className={separate ? styles.separateItem : styles.item}>\n {!separate && i > 0 && <div className={styles.divider} aria-hidden=\"true\" />}\n {child}\n </li>\n ))}\n </ul>\n );\n}\n","/* ─── Container ────────────────────────────────────────────────────────────── */\n\n.switcher {\n display: inline-flex;\n align-items: center;\n gap: 2px;\n padding: 3px;\n border-radius: var(--gnome-radius-pill);\n background-color: color-mix(\n in srgb,\n var(--gnome-headerbar-bg-color, #ebebeb) 75%,\n black 25%\n );\n}\n\n/* ─── Item ─────────────────────────────────────────────────────────────────── */\n\n.item {\n appearance: none;\n -webkit-appearance: none;\n border: none;\n background: transparent;\n font: inherit;\n cursor: pointer;\n outline: none;\n\n display: inline-flex;\n align-items: center;\n justify-content: center;\n gap: var(--gnome-space-1, 6px);\n padding: 5px var(--gnome-space-2, 12px);\n border-radius: calc(var(--gnome-radius-pill) - 3px);\n\n font-family: var(--gnome-font-family);\n font-size: var(--gnome-font-size-body, 1rem);\n font-weight: var(--gnome-font-weight-normal, 400);\n color: var(--gnome-headerbar-fg-color, rgba(0, 0, 0, 0.8));\n white-space: nowrap;\n\n transition:\n background-color var(--gnome-duration-fast) var(--gnome-easing-default),\n color var(--gnome-duration-fast) var(--gnome-easing-default);\n}\n\n.item:hover:not(:disabled):not(.active) {\n background-color: rgba(0, 0, 0, 0.06);\n}\n\n.item:active:not(:disabled):not(.active) {\n background-color: rgba(0, 0, 0, 0.12);\n}\n\n.item:disabled {\n opacity: var(--gnome-opacity-disabled, 0.5);\n cursor: not-allowed;\n}\n\n.item:focus-visible {\n box-shadow:\n 0 0 0 var(--gnome-focus-ring-offset, 2px)\n color-mix(in srgb, var(--gnome-headerbar-bg-color, #ebebeb) 75%, black 25%),\n 0 0 0 calc(var(--gnome-focus-ring-offset, 2px) + var(--gnome-focus-ring-width, 3px))\n var(--gnome-focus-ring-color, #3584e4);\n}\n\n/* ─── Active item ──────────────────────────────────────────────────────────── */\n\n.active {\n background-color: var(--gnome-headerbar-bg-color, #ebebeb);\n font-weight: var(--gnome-font-weight-semibold, 600);\n box-shadow: var(--gnome-shadow-sm, 0 1px 2px rgba(0, 0, 0, 0.12));\n}\n\n/* ─── Icon ─────────────────────────────────────────────────────────────────── */\n\n.itemIcon {\n display: flex;\n align-items: center;\n flex-shrink: 0;\n}\n\n/* ─── Label ────────────────────────────────────────────────────────────────── */\n\n.itemLabel {\n line-height: var(--gnome-line-height-body, 1.5);\n}\n\n/* ─── Dark mode ────────────────────────────────────────────────────────────── */\n\n@media (prefers-color-scheme: dark) {\n .switcher {\n background-color: color-mix(\n in srgb,\n var(--gnome-headerbar-bg-color, #303030) 70%,\n black 30%\n );\n }\n\n .item:hover:not(:disabled):not(.active) {\n background-color: rgba(255, 255, 255, 0.08);\n }\n\n .item:active:not(:disabled):not(.active) {\n background-color: rgba(255, 255, 255, 0.14);\n }\n\n .active {\n background-color: var(--gnome-headerbar-bg-color, #303030);\n box-shadow: 0 1px 2px rgba(0, 0, 0, 0.4);\n }\n}\n\n/* ─── Reduced motion ───────────────────────────────────────────────────────── */\n\n@media (prefers-reduced-motion: reduce) {\n .item {\n transition: none;\n }\n}\n","import {\n useRef,\n type HTMLAttributes,\n type KeyboardEvent,\n type ReactNode,\n} from \"react\";\nimport styles from \"./ViewSwitcher.module.css\";\n\nexport interface ViewSwitcherProps extends HTMLAttributes<HTMLDivElement> {\n children?: ReactNode;\n /** Accessible label for the group. */\n \"aria-label\"?: string;\n}\n\n/**\n * Segmented control for switching between major views.\n *\n * Mirrors the Adwaita `AdwViewSwitcher` pattern. Place in a `HeaderBar`\n * as the `title` for the canonical GNOME layout.\n *\n * Compose with `ViewSwitcherItem` for each option.\n * Keyboard: ← / → cycle through items, Home / End jump to first / last.\n *\n * @see https://gnome.pages.gitlab.gnome.org/libadwaita/doc/main/class.ViewSwitcher.html\n * @see https://developer.gnome.org/hig/patterns/nav/view-switchers.html\n */\nexport function ViewSwitcher({\n children,\n className,\n \"aria-label\": ariaLabel = \"View switcher\",\n ...props\n}: ViewSwitcherProps) {\n const groupRef = useRef<HTMLDivElement>(null);\n\n function handleKeyDown(e: KeyboardEvent<HTMLDivElement>) {\n const items = Array.from(\n groupRef.current?.querySelectorAll<HTMLButtonElement>(\n \"[role=radio]:not(:disabled)\",\n ) ?? [],\n );\n const idx = items.findIndex((el) => el === document.activeElement);\n if (idx === -1) return;\n\n let next = idx;\n if (e.key === \"ArrowRight\" || e.key === \"ArrowDown\")\n next = (idx + 1) % items.length;\n else if (e.key === \"ArrowLeft\" || e.key === \"ArrowUp\")\n next = (idx - 1 + items.length) % items.length;\n else if (e.key === \"Home\") next = 0;\n else if (e.key === \"End\") next = items.length - 1;\n else return;\n\n e.preventDefault();\n items[next].focus();\n items[next].click();\n }\n\n return (\n <div\n ref={groupRef}\n role=\"radiogroup\"\n aria-label={ariaLabel}\n onKeyDown={handleKeyDown}\n className={[styles.switcher, className].filter(Boolean).join(\" \")}\n {...props}\n >\n {children}\n </div>\n );\n}\n","import type { ButtonHTMLAttributes } from \"react\";\nimport type { IconDefinition } from \"@gnome-ui/icons\";\nimport { Icon } from \"../Icon\";\nimport styles from \"./ViewSwitcher.module.css\";\n\nexport interface ViewSwitcherItemProps\n extends ButtonHTMLAttributes<HTMLButtonElement> {\n /** Display label. */\n label: string;\n /** Optional icon from `@gnome-ui/icons`. */\n icon?: IconDefinition;\n /** Marks this item as the currently active view. */\n active?: boolean;\n}\n\n/**\n * Individual option inside a `ViewSwitcher`.\n *\n * Renders as `role=\"radio\"` so the group has proper radiogroup semantics.\n */\nexport function ViewSwitcherItem({\n label,\n icon,\n active = false,\n className,\n disabled,\n ...props\n}: ViewSwitcherItemProps) {\n return (\n <button\n type=\"button\"\n role=\"radio\"\n aria-checked={active}\n tabIndex={active ? 0 : -1}\n disabled={disabled}\n className={[\n styles.item,\n active ? styles.active : null,\n className,\n ]\n .filter(Boolean)\n .join(\" \")}\n {...props}\n >\n {icon && (\n <span className={styles.itemIcon}>\n <Icon icon={icon} size=\"md\" aria-hidden />\n </span>\n )}\n <span className={styles.itemLabel}>{label}</span>\n </button>\n );\n}\n","/* ─── Toaster (portal container) ────────────────────────────────────────────── */\n\n.toaster {\n position: fixed;\n left: 50%;\n transform: translateX(-50%);\n z-index: 9999;\n\n display: flex;\n flex-direction: column;\n align-items: center;\n gap: var(--gnome-space-1, 6px);\n\n /* Don't intercept pointer events in empty space */\n pointer-events: none;\n}\n\n.toasterBottom {\n bottom: var(--gnome-space-4, 24px);\n}\n\n.toasterTop {\n top: var(--gnome-space-4, 24px);\n}\n\n/* ─── Toast card ─────────────────────────────────────────────────────────────── */\n\n.toast {\n pointer-events: auto;\n\n display: flex;\n align-items: center;\n gap: var(--gnome-space-2, 12px);\n\n padding: 10px var(--gnome-space-2, 12px) 10px var(--gnome-space-3, 16px);\n min-height: 44px;\n max-width: 480px;\n min-width: 240px;\n\n background-color: var(--gnome-card-bg-color, #ffffff);\n color: var(--gnome-window-fg-color, rgba(0, 0, 0, 0.8));\n\n border-radius: var(--gnome-radius-lg, 12px);\n box-shadow:\n 0 2px 8px rgba(0, 0, 0, 0.15),\n 0 0 0 1px rgba(0, 0, 0, 0.08);\n\n font-family: var(--gnome-font-family);\n font-size: var(--gnome-font-size-body, 1rem);\n line-height: var(--gnome-line-height-body, 1.5);\n\n /* Slide in from bottom */\n animation: toast-in var(--gnome-duration-normal, 200ms) var(--gnome-easing-default, ease) both;\n}\n\n@keyframes toast-in {\n from {\n opacity: 0;\n transform: translateY(12px) scale(0.96);\n }\n to {\n opacity: 1;\n transform: translateY(0) scale(1);\n }\n}\n\n/* ─── Title ──────────────────────────────────────────────────────────────────── */\n\n.title {\n flex: 1;\n min-width: 0;\n}\n\n/* ─── Actions ────────────────────────────────────────────────────────────────── */\n\n.actions {\n display: flex;\n align-items: center;\n gap: var(--gnome-space-1, 6px);\n flex-shrink: 0;\n}\n\n/* ─── Action button ──────────────────────────────────────────────────────────── */\n\n.actionBtn {\n appearance: none;\n border: none;\n background: transparent;\n font: inherit;\n font-weight: var(--gnome-font-weight-semibold, 600);\n color: var(--gnome-accent-color, #3584e4);\n cursor: pointer;\n padding: 4px var(--gnome-space-1, 6px);\n border-radius: var(--gnome-radius-sm, 6px);\n\n transition: background-color var(--gnome-duration-fast, 100ms) var(--gnome-easing-default, ease);\n}\n\n.actionBtn:hover {\n background-color: color-mix(in srgb, var(--gnome-accent-color, #3584e4) 10%, transparent);\n}\n\n.actionBtn:active {\n background-color: color-mix(in srgb, var(--gnome-accent-color, #3584e4) 18%, transparent);\n}\n\n.actionBtn:focus {\n outline: none;\n}\n\n.actionBtn:focus-visible {\n outline: var(--gnome-focus-ring-width, 3px) solid var(--gnome-focus-ring-color, #3584e4);\n outline-offset: var(--gnome-focus-ring-offset, 2px);\n}\n\n/* ─── Dismiss button ─────────────────────────────────────────────────────────── */\n\n.dismissBtn {\n appearance: none;\n border: none;\n background: transparent;\n cursor: pointer;\n display: flex;\n align-items: center;\n justify-content: center;\n width: 28px;\n height: 28px;\n padding: 0;\n border-radius: var(--gnome-radius-sm, 6px);\n color: var(--gnome-window-fg-color, rgba(0, 0, 0, 0.8));\n opacity: 0.55;\n\n transition:\n opacity var(--gnome-duration-fast, 100ms) var(--gnome-easing-default, ease),\n background-color var(--gnome-duration-fast, 100ms) var(--gnome-easing-default, ease);\n}\n\n.dismissBtn:hover {\n opacity: 1;\n background-color: color-mix(in srgb, currentColor 10%, transparent);\n}\n\n.dismissBtn:active {\n background-color: color-mix(in srgb, currentColor 18%, transparent);\n}\n\n.dismissBtn:focus {\n outline: none;\n}\n\n.dismissBtn:focus-visible {\n opacity: 1;\n outline: var(--gnome-focus-ring-width, 3px) solid var(--gnome-focus-ring-color, #3584e4);\n outline-offset: var(--gnome-focus-ring-offset, 2px);\n}\n\n/* ─── Dark mode ──────────────────────────────────────────────────────────────── */\n\n@media (prefers-color-scheme: dark) {\n .toast {\n background-color: var(--gnome-card-bg-color, #383838);\n box-shadow:\n 0 2px 12px rgba(0, 0, 0, 0.4),\n 0 0 0 1px rgba(255, 255, 255, 0.08);\n }\n}\n\n/* ─── Reduced motion ─────────────────────────────────────────────────────────── */\n\n@media (prefers-reduced-motion: reduce) {\n .toast {\n animation: none;\n }\n\n .actionBtn,\n .dismissBtn {\n transition: none;\n }\n}\n","import {\n useEffect,\n useRef,\n type HTMLAttributes,\n type ReactNode,\n} from \"react\";\nimport { Close } from \"@gnome-ui/icons\";\nimport { Icon } from \"../Icon\";\nimport styles from \"./Toast.module.css\";\n\nexport interface ToastProps extends Omit<HTMLAttributes<HTMLDivElement>, \"title\"> {\n /** The notification message. Keep it short — one sentence. */\n title: ReactNode;\n /**\n * Auto-dismiss timeout in milliseconds.\n * - Set to `0` to disable auto-dismiss (user must click action or dismiss).\n * - Defaults to `3000`.\n */\n duration?: number;\n /** Called when the toast should be removed — after timeout or user action. */\n onDismiss?: () => void;\n /** Label for the optional action button. */\n actionLabel?: string;\n /** Called when the user clicks the action button. */\n onAction?: () => void;\n /** Whether to show a manual dismiss (×) button. */\n dismissible?: boolean;\n}\n\n/**\n * Non-blocking temporary notification following the Adwaita `AdwToast` pattern.\n *\n * - Auto-dismisses after `duration` ms (default 3 s). Set `duration={0}` to disable.\n * - Timer pauses while the toast is hovered or focused.\n * - Use inside `<Toaster>` for correct positioning and stacking.\n *\n * @see https://gnome.pages.gitlab.gnome.org/libadwaita/doc/main/class.Toast.html\n */\nexport function Toast({\n title,\n duration = 3000,\n onDismiss,\n actionLabel,\n onAction,\n dismissible = false,\n className,\n ...props\n}: ToastProps) {\n const timerRef = useRef<ReturnType<typeof setTimeout> | null>(null);\n const remainingRef = useRef(duration);\n const startedAtRef = useRef<number>(0);\n\n const clearTimer = () => {\n if (timerRef.current) {\n clearTimeout(timerRef.current);\n timerRef.current = null;\n }\n };\n\n const startTimer = (ms: number) => {\n if (ms <= 0 || !onDismiss) return;\n clearTimer();\n startedAtRef.current = Date.now();\n timerRef.current = setTimeout(() => onDismiss(), ms);\n };\n\n // Start auto-dismiss on mount\n useEffect(() => {\n if (duration > 0) {\n remainingRef.current = duration;\n startTimer(duration);\n }\n return clearTimer;\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [duration]);\n\n // Pause timer on hover/focus\n const handleMouseEnter = () => {\n if (timerRef.current) {\n const elapsed = Date.now() - startedAtRef.current;\n remainingRef.current = Math.max(0, remainingRef.current - elapsed);\n clearTimer();\n }\n };\n\n const handleMouseLeave = () => {\n startTimer(remainingRef.current);\n };\n\n const handleAction = () => {\n clearTimer();\n onAction?.();\n onDismiss?.();\n };\n\n return (\n <div\n role=\"status\"\n aria-live=\"polite\"\n aria-atomic=\"true\"\n className={[styles.toast, className].filter(Boolean).join(\" \")}\n onMouseEnter={handleMouseEnter}\n onMouseLeave={handleMouseLeave}\n onFocus={handleMouseEnter}\n onBlur={handleMouseLeave}\n {...props}\n >\n <span className={styles.title}>{title}</span>\n\n {(actionLabel || dismissible) && (\n <span className={styles.actions}>\n {actionLabel && (\n <button\n type=\"button\"\n className={styles.actionBtn}\n onClick={handleAction}\n >\n {actionLabel}\n </button>\n )}\n {dismissible && (\n <button\n type=\"button\"\n className={styles.dismissBtn}\n aria-label=\"Dismiss\"\n onClick={() => { clearTimer(); onDismiss?.(); }}\n >\n <Icon icon={Close} size=\"md\" aria-hidden />\n </button>\n )}\n </span>\n )}\n </div>\n );\n}\n","import { createPortal } from \"react-dom\";\nimport type { HTMLAttributes, ReactNode } from \"react\";\nimport styles from \"./Toast.module.css\";\n\nexport interface ToasterProps extends HTMLAttributes<HTMLDivElement> {\n /**\n * Where to render the toast stack.\n * - `bottom` (default) — bottom-center, following the GNOME HIG.\n * - `top` — top-center.\n */\n position?: \"bottom\" | \"top\";\n /** `<Toast>` elements to display. */\n children?: ReactNode;\n /**\n * DOM node to render the portal into.\n * Defaults to `document.body`.\n */\n container?: Element | DocumentFragment;\n}\n\n/**\n * Fixed-position overlay that stacks `<Toast>` notifications.\n *\n * Renders into a portal (default: `document.body`) so it sits above all\n * other content regardless of where you place it in the React tree.\n *\n * @example\n * <Toaster>\n * {toasts.map(t => (\n * <Toast key={t.id} title={t.message} onDismiss={() => remove(t.id)} />\n * ))}\n * </Toaster>\n */\nexport function Toaster({\n position = \"bottom\",\n children,\n container,\n className,\n ...props\n}: ToasterProps) {\n const node = (\n <div\n aria-label=\"Notifications\"\n className={[\n styles.toaster,\n position === \"top\" ? styles.toasterTop : styles.toasterBottom,\n className,\n ]\n .filter(Boolean)\n .join(\" \")}\n {...props}\n >\n {children}\n </div>\n );\n\n // During SSR document is not available — render inline\n if (typeof document === \"undefined\") return node;\n\n return createPortal(node, container ?? document.body);\n}\n","/* ─── Backdrop ──────────────────────────────────────────────────────────────── */\n\n.backdrop {\n position: fixed;\n inset: 0;\n z-index: 10000;\n\n display: flex;\n align-items: center;\n justify-content: center;\n\n background-color: rgba(0, 0, 0, 0.5);\n backdrop-filter: blur(4px);\n -webkit-backdrop-filter: blur(4px);\n\n animation: backdrop-in var(--gnome-duration-normal, 200ms) var(--gnome-easing-default, ease) both;\n}\n\n@keyframes backdrop-in {\n from { opacity: 0; }\n to { opacity: 1; }\n}\n\n/* ─── Dialog card ───────────────────────────────────────────────────────────── */\n\n.dialog {\n position: relative;\n width: min(480px, calc(100vw - 48px));\n\n background-color: var(--gnome-dialog-bg-color, var(--gnome-window-bg-color, #fafafa));\n color: var(--gnome-window-fg-color, rgba(0, 0, 0, 0.8));\n\n border-radius: var(--gnome-radius-xl, 16px);\n box-shadow:\n 0 8px 32px rgba(0, 0, 0, 0.25),\n 0 0 0 1px rgba(0, 0, 0, 0.08);\n\n overflow: hidden;\n\n animation: dialog-in var(--gnome-duration-normal, 200ms) var(--gnome-easing-default, ease) both;\n}\n\n@keyframes dialog-in {\n from {\n opacity: 0;\n transform: scale(0.92) translateY(-8px);\n }\n to {\n opacity: 1;\n transform: scale(1) translateY(0);\n }\n}\n\n/* ─── Title ─────────────────────────────────────────────────────────────────── */\n\n.title {\n padding: var(--gnome-space-4, 24px) var(--gnome-space-4, 24px) 0;\n\n font-family: var(--gnome-font-family);\n font-size: var(--gnome-font-size-title-4, 1.125rem);\n font-weight: var(--gnome-font-weight-bold, 700);\n line-height: var(--gnome-line-height-title, 1.3);\n color: var(--gnome-window-fg-color, rgba(0, 0, 0, 0.8));\n\n text-align: center;\n}\n\n/* ─── Body ──────────────────────────────────────────────────────────────────── */\n\n.body {\n padding: var(--gnome-space-2, 12px) var(--gnome-space-4, 24px) var(--gnome-space-3, 16px);\n\n font-family: var(--gnome-font-family);\n font-size: var(--gnome-font-size-body, 1rem);\n font-weight: var(--gnome-font-weight-normal, 400);\n line-height: var(--gnome-line-height-body, 1.5);\n color: var(--gnome-window-fg-color, rgba(0, 0, 0, 0.8));\n opacity: var(--gnome-opacity-dim, 0.7);\n\n text-align: center;\n}\n\n/* ─── Footer (buttons) ──────────────────────────────────────────────────────── */\n\n.footer {\n display: flex;\n flex-direction: column;\n border-top: 1px solid var(--gnome-card-shade-color, rgba(0, 0, 0, 0.1));\n}\n\n/* Each button gets a full-width row separated by hairlines */\n.btn {\n appearance: none;\n border: none;\n border-bottom: 1px solid var(--gnome-card-shade-color, rgba(0, 0, 0, 0.08));\n background: transparent;\n width: 100%;\n padding: 14px var(--gnome-space-4, 24px);\n\n font-family: var(--gnome-font-family);\n font-size: var(--gnome-font-size-body, 1rem);\n font-weight: var(--gnome-font-weight-normal, 400);\n line-height: var(--gnome-line-height-body, 1.5);\n color: var(--gnome-accent-color, #3584e4);\n\n cursor: pointer;\n text-align: center;\n\n transition: background-color var(--gnome-duration-fast, 100ms) var(--gnome-easing-default, ease);\n}\n\n.btn:last-child {\n border-bottom: none;\n}\n\n.btn:hover:not(:disabled) {\n background-color: color-mix(in srgb, currentColor 6%, transparent);\n}\n\n.btn:active:not(:disabled) {\n background-color: color-mix(in srgb, currentColor 12%, transparent);\n}\n\n.btn:focus {\n outline: none;\n}\n\n.btn:focus-visible {\n outline: var(--gnome-focus-ring-width, 3px) solid var(--gnome-focus-ring-color, #3584e4);\n outline-offset: -3px;\n}\n\n.btn:disabled {\n opacity: var(--gnome-opacity-disabled, 0.5);\n cursor: not-allowed;\n}\n\n/* ─── Button variants ───────────────────────────────────────────────────────── */\n\n.btn-default {\n color: var(--gnome-accent-color, #3584e4);\n}\n\n.btn-suggested {\n color: var(--gnome-accent-color, #3584e4);\n font-weight: var(--gnome-font-weight-semibold, 600);\n}\n\n.btn-destructive {\n color: var(--gnome-destructive-color, #e01b24);\n}\n\n/* ─── About variant ─────────────────────────────────────────────────────────── */\n\n.dialogAbout {\n max-height: min(640px, 90dvh);\n display: flex;\n flex-direction: column;\n}\n\n/* Header */\n.aboutHeader {\n display: flex;\n flex-direction: column;\n align-items: center;\n gap: 4px;\n padding: var(--gnome-space-4, 24px) var(--gnome-space-4, 24px) var(--gnome-space-3, 16px);\n text-align: center;\n}\n\n.aboutIcon {\n width: 64px;\n height: 64px;\n margin-bottom: var(--gnome-space-2, 12px);\n display: flex;\n align-items: center;\n justify-content: center;\n}\n\n.aboutAppName {\n font-family: var(--gnome-font-family);\n font-size: var(--gnome-font-size-title-3, 1.25rem);\n font-weight: var(--gnome-font-weight-bold, 700);\n line-height: 1.3;\n}\n\n.aboutVersion {\n font-family: var(--gnome-font-family);\n font-size: var(--gnome-font-size-body, 1rem);\n opacity: var(--gnome-opacity-dim, 0.55);\n}\n\n/* Tab bar */\n.aboutTabBar {\n display: flex;\n border-bottom: 1px solid var(--gnome-card-shade-color, rgba(0, 0, 0, 0.1));\n padding: 0 var(--gnome-space-3, 16px);\n gap: 4px;\n}\n\n.aboutTabBtn {\n appearance: none;\n border: none;\n background: transparent;\n cursor: pointer;\n padding: 8px var(--gnome-space-2, 12px);\n border-bottom: 2px solid transparent;\n margin-bottom: -1px;\n\n font-family: var(--gnome-font-family);\n font-size: var(--gnome-font-size-body, 1rem);\n color: var(--gnome-window-fg-color, rgba(0, 0, 0, 0.8));\n opacity: 0.6;\n transition: opacity var(--gnome-duration-fast) var(--gnome-easing-default),\n border-color var(--gnome-duration-fast) var(--gnome-easing-default);\n}\n\n.aboutTabBtn:hover { opacity: 1; }\n\n.aboutTabBtnActive {\n opacity: 1;\n font-weight: var(--gnome-font-weight-semibold, 600);\n border-bottom-color: var(--gnome-accent-color, #3584e4);\n}\n\n.aboutTabBtn:focus-visible {\n outline: 2px solid var(--gnome-focus-ring-color, #3584e4);\n outline-offset: 2px;\n border-radius: 4px;\n}\n\n/* Tab content */\n.aboutTabContent {\n flex: 1;\n overflow-y: auto;\n}\n\n.aboutSection {\n padding: var(--gnome-space-3, 16px) var(--gnome-space-4, 24px);\n}\n\n/* Details */\n.aboutComments {\n margin: 0 0 var(--gnome-space-3, 16px);\n font-family: var(--gnome-font-family);\n font-size: var(--gnome-font-size-body, 1rem);\n line-height: 1.5;\n text-align: center;\n opacity: var(--gnome-opacity-dim, 0.7);\n}\n\n.aboutInfoList {\n display: grid;\n grid-template-columns: auto 1fr;\n gap: 6px var(--gnome-space-3, 16px);\n margin: 0;\n}\n\n.aboutInfoLabel {\n font-family: var(--gnome-font-family);\n font-size: var(--gnome-font-size-body, 1rem);\n font-weight: var(--gnome-font-weight-semibold, 600);\n opacity: 0.55;\n white-space: nowrap;\n}\n\n.aboutInfoValue {\n font-family: var(--gnome-font-family);\n font-size: var(--gnome-font-size-body, 1rem);\n margin: 0;\n min-width: 0;\n overflow-wrap: break-word;\n}\n\n.aboutLink {\n color: var(--gnome-accent-color, #3584e4);\n text-decoration: none;\n}\n\n.aboutLink:hover { text-decoration: underline; }\n\n/* Credits */\n.aboutCreditGroup + .aboutCreditGroup {\n margin-top: var(--gnome-space-3, 16px);\n}\n\n.aboutCreditTitle {\n margin: 0 0 6px;\n font-family: var(--gnome-font-family);\n font-size: var(--gnome-font-size-caption, 0.75rem);\n font-weight: var(--gnome-font-weight-semibold, 600);\n text-transform: uppercase;\n letter-spacing: 0.06em;\n opacity: 0.5;\n}\n\n.aboutCreditList {\n list-style: none;\n margin: 0;\n padding: 0;\n display: flex;\n flex-direction: column;\n gap: 2px;\n}\n\n.aboutCreditItem {\n font-family: var(--gnome-font-family);\n font-size: var(--gnome-font-size-body, 1rem);\n}\n\n/* Legal */\n.aboutCopyright {\n margin: 0 0 var(--gnome-space-2, 12px);\n font-family: var(--gnome-font-family);\n font-size: var(--gnome-font-size-body, 1rem);\n opacity: var(--gnome-opacity-dim, 0.7);\n}\n\n.aboutLicenseType {\n margin: 0 0 var(--gnome-space-2, 12px);\n font-family: var(--gnome-font-family);\n font-size: var(--gnome-font-size-body, 1rem);\n font-weight: var(--gnome-font-weight-semibold, 600);\n}\n\n.aboutLicenseText {\n margin: 0;\n padding: var(--gnome-space-2, 12px);\n border-radius: var(--gnome-radius-md, 8px);\n background: var(--gnome-card-bg-color, rgba(0, 0, 0, 0.04));\n font-family: monospace;\n font-size: 0.8rem;\n line-height: 1.5;\n white-space: pre-wrap;\n overflow-wrap: break-word;\n}\n\n/* ─── Dark mode ─────────────────────────────────────────────────────────────── */\n\n@media (prefers-color-scheme: dark) {\n .backdrop {\n background-color: rgba(0, 0, 0, 0.65);\n }\n\n .dialog {\n background-color: var(--gnome-dialog-bg-color, var(--gnome-window-bg-color, #242424));\n box-shadow:\n 0 8px 32px rgba(0, 0, 0, 0.5),\n 0 0 0 1px rgba(255, 255, 255, 0.08);\n }\n\n .footer {\n border-top-color: rgba(255, 255, 255, 0.08);\n }\n\n .btn {\n border-bottom-color: rgba(255, 255, 255, 0.06);\n }\n}\n\n/* ─── Reduced motion ────────────────────────────────────────────────────────── */\n\n@media (prefers-reduced-motion: reduce) {\n .backdrop,\n .dialog {\n animation: none;\n }\n\n .btn {\n transition: none;\n }\n}\n","import {\n useEffect,\n useRef,\n useCallback,\n useId,\n useState,\n type KeyboardEvent,\n type HTMLAttributes,\n type ReactNode,\n} from \"react\";\nimport { createPortal } from \"react-dom\";\nimport styles from \"./Dialog.module.css\";\n\n// ─── Shared focus trap ────────────────────────────────────────────────────────\n\nconst FOCUSABLE =\n 'button:not([disabled]), [href], input:not([disabled]), select:not([disabled]), textarea:not([disabled]), [tabindex]:not([tabindex=\"-1\"])';\n\nfunction trapFocus(e: KeyboardEvent<HTMLDivElement>, ref: React.RefObject<HTMLDivElement | null>) {\n if (e.key !== \"Tab\") return;\n const focusable = Array.from(ref.current?.querySelectorAll<HTMLElement>(FOCUSABLE) ?? []);\n if (!focusable.length) return;\n const first = focusable[0];\n const last = focusable[focusable.length - 1];\n if (e.shiftKey) {\n if (document.activeElement === first) { e.preventDefault(); last.focus(); }\n } else {\n if (document.activeElement === last) { e.preventDefault(); first.focus(); }\n }\n}\n\n// ─── AlertDialog types ────────────────────────────────────────────────────────\n\nexport type AlertDialogResponseVariant = \"default\" | \"suggested\" | \"destructive\";\n\nexport interface AlertDialogResponse {\n /**\n * Unique identifier returned via `onResponse`.\n * Use `\"cancel\"` by convention for the dismissive action.\n */\n id: string;\n /** Button label. */\n label: string;\n /** Visual emphasis. Defaults to `\"default\"`. */\n variant?: AlertDialogResponseVariant;\n /** Disables the button. */\n disabled?: boolean;\n}\n\n// ─── AboutDialog types ────────────────────────────────────────────────────────\n\nexport interface AboutDialogLink {\n label: string;\n url: string;\n}\n\n// ─── Dialog ───────────────────────────────────────────────────────────────────\n\nexport interface DialogButton {\n /** Button label. */\n label: string;\n /** Visual variant. Defaults to `\"default\"`. */\n variant?: \"default\" | \"suggested\" | \"destructive\";\n /** Called when the button is clicked. */\n onClick: () => void;\n /** Whether the button is disabled. */\n disabled?: boolean;\n}\n\nexport interface DialogProps extends Omit<HTMLAttributes<HTMLDivElement>, \"title\"> {\n /** Whether the dialog is visible. */\n open: boolean;\n\n // ── Standard dialog ──────────────────────────────────────────────────────\n\n /** Dialog heading. */\n title?: ReactNode;\n /** Body content. */\n children?: ReactNode;\n /** Action buttons (standard dialog API). */\n buttons?: DialogButton[];\n /** Called on Escape or backdrop click. */\n onClose?: () => void;\n /** Whether clicking the backdrop closes the dialog. Defaults to `true`. */\n closeOnBackdrop?: boolean;\n\n // ── AlertDialog extension ─────────────────────────────────────────────────\n\n /**\n * ARIA role. Use `\"alertdialog\"` for confirmations and destructive warnings —\n * screen readers announce it immediately. Defaults to `\"dialog\"`.\n *\n * When using `role=\"alertdialog\"`, prefer the `responses`/`onResponse` API\n * over `buttons`/`onClick` for semantic clarity.\n */\n role?: \"dialog\" | \"alertdialog\";\n\n /**\n * Response buttons (AlertDialog API). Alternative to `buttons` — each\n * response has a semantic `id` returned via `onResponse`.\n * Escape and backdrop click fire the first non-destructive response.\n */\n responses?: AlertDialogResponse[];\n\n /**\n * Called with the `id` of the response button clicked.\n * Required when `responses` is provided.\n */\n onResponse?: (id: string) => void;\n\n // ── AboutDialog extension ─────────────────────────────────────────────────\n\n /**\n * Set to `\"about\"` to render as an application info dialog.\n * When `variant=\"about\"`, `title`/`buttons`/`children` are ignored in favour\n * of the about-specific props below.\n */\n variant?: \"about\";\n\n /** Application name (required when `variant=\"about\"`). */\n applicationName?: string;\n /** Icon rendered above the app name. */\n applicationIcon?: ReactNode;\n /** Version string (e.g. `\"1.0.0\"`). */\n version?: string;\n /** One-sentence description. */\n comments?: string;\n /** Developer or organisation name. */\n developerName?: string;\n /** Application website URL. */\n website?: string;\n /** Label for the website link. Defaults to the URL. */\n websiteLabel?: string;\n /** Developer names shown in the Credits tab. */\n developers?: string[];\n /** Designer names shown in the Credits tab. */\n designers?: string[];\n /** Artist names shown in the Credits tab. */\n artists?: string[];\n /** Copyright notice (e.g. `\"© 2024 Jane Smith\"`). */\n copyright?: string;\n /** SPDX identifier or short name (e.g. `\"GPL-3.0\"`). */\n licenseType?: string;\n /** Full license text shown in the Legal tab. */\n licenseText?: string;\n /** Extra links shown in the Details section. */\n links?: AboutDialogLink[];\n}\n\n// ─── About tab internals ──────────────────────────────────────────────────────\n\ntype AboutTab = \"details\" | \"credits\" | \"legal\";\nconst ABOUT_TAB_LABEL: Record<AboutTab, string> = {\n details: \"Details\",\n credits: \"Credits\",\n legal: \"Legal\",\n};\n\n/**\n * Blocking modal dialog following the Adwaita pattern.\n *\n * Three modes in one component:\n *\n * **Standard** — `title` + `children` + `buttons[]`.\n *\n * **Alert** — add `role=\"alertdialog\"` + `responses[]` + `onResponse`.\n * Uses a semantic response id instead of per-button `onClick`.\n * Escape / backdrop fire the first non-destructive response.\n * Mirrors `AdwAlertDialog`.\n *\n * **About** — add `variant=\"about\"` + `applicationName` + info props.\n * Renders app icon, version, details/credits/legal tabs.\n * Mirrors `AdwAboutDialog`.\n *\n * All modes share the same portal, focus-trap, and card/backdrop styles.\n *\n * @see https://gnome.pages.gitlab.gnome.org/libadwaita/doc/main/class.Dialog.html\n * @see https://gnome.pages.gitlab.gnome.org/libadwaita/doc/main/class.AlertDialog.html\n * @see https://gnome.pages.gitlab.gnome.org/libadwaita/doc/main/class.AboutDialog.html\n */\nexport function Dialog({\n open,\n title,\n children,\n buttons = [],\n onClose,\n closeOnBackdrop = true,\n role = \"dialog\",\n responses,\n onResponse,\n variant,\n applicationName,\n applicationIcon,\n version,\n comments,\n developerName,\n website,\n websiteLabel,\n developers,\n designers,\n artists,\n copyright,\n licenseType,\n licenseText,\n links,\n className,\n ...props\n}: DialogProps) {\n const dialogRef = useRef<HTMLDivElement>(null);\n const titleId = useId();\n const previouslyFocused = useRef<Element | null>(null);\n const [aboutTab, setAboutTab] = useState<AboutTab>(\"details\");\n\n const isAlert = !!responses;\n const isAbout = variant === \"about\";\n\n // ── Focus management ───────────────────────────────────────────────────────\n useEffect(() => {\n if (open) {\n previouslyFocused.current = document.activeElement;\n const el = dialogRef.current?.querySelector<HTMLElement>(FOCUSABLE);\n el?.focus();\n } else {\n (previouslyFocused.current as HTMLElement | null)?.focus();\n if (isAbout) setAboutTab(\"details\");\n }\n }, [open, isAbout]);\n\n // ── Dismiss helpers ────────────────────────────────────────────────────────\n const dismissAlert = useCallback(() => {\n const cancel = responses?.find((r) => r.variant !== \"destructive\" && !r.disabled);\n if (cancel) onResponse?.(cancel.id);\n }, [responses, onResponse]);\n\n const handleBackdrop = isAlert ? dismissAlert : closeOnBackdrop ? onClose : undefined;\n\n // ── Keyboard ───────────────────────────────────────────────────────────────\n const handleKeyDown = useCallback(\n (e: KeyboardEvent<HTMLDivElement>) => {\n if (e.key === \"Escape\") {\n e.preventDefault();\n if (isAlert) dismissAlert();\n else onClose?.();\n return;\n }\n trapFocus(e, dialogRef);\n },\n [isAlert, dismissAlert, onClose]\n );\n\n if (!open) return null;\n\n // ── About tabs ─────────────────────────────────────────────────────────────\n const hasCredits = !!(developers?.length || designers?.length || artists?.length);\n const hasLegal = !!(copyright || licenseType || licenseText);\n const aboutTabs = (\n [\"details\", hasCredits && \"credits\", hasLegal && \"legal\"] as (AboutTab | false)[]\n ).filter(Boolean) as AboutTab[];\n\n // ── Render buttons (standard or alert) ────────────────────────────────────\n const renderButtons = () => {\n if (isAbout) {\n return (\n <div className={styles.footer}>\n <button type=\"button\" className={[styles.btn, styles[\"btn-default\"]].join(\" \")} onClick={onClose}>\n Close\n </button>\n </div>\n );\n }\n if (isAlert && responses) {\n return (\n <div className={styles.footer}>\n {responses.map((r) => (\n <button\n key={r.id}\n type=\"button\"\n disabled={r.disabled}\n className={[styles.btn, styles[`btn-${r.variant ?? \"default\"}`]].filter(Boolean).join(\" \")}\n onClick={() => onResponse?.(r.id)}\n >\n {r.label}\n </button>\n ))}\n </div>\n );\n }\n if (buttons.length > 0) {\n return (\n <div className={styles.footer}>\n {buttons.map((btn) => (\n <button\n key={btn.label}\n type=\"button\"\n disabled={btn.disabled}\n className={[styles.btn, styles[`btn-${btn.variant ?? \"default\"}`]].filter(Boolean).join(\" \")}\n onClick={btn.onClick}\n >\n {btn.label}\n </button>\n ))}\n </div>\n );\n }\n return null;\n };\n\n // ── About content ──────────────────────────────────────────────────────────\n const renderAboutContent = () => (\n <>\n <div className={styles.aboutHeader}>\n {applicationIcon && <div className={styles.aboutIcon}>{applicationIcon}</div>}\n <div id={titleId} className={styles.aboutAppName}>{applicationName}</div>\n {version && <div className={styles.aboutVersion}>{version}</div>}\n </div>\n\n {aboutTabs.length > 1 && (\n <div className={styles.aboutTabBar} role=\"tablist\">\n {aboutTabs.map((t) => (\n <button\n key={t}\n type=\"button\"\n role=\"tab\"\n aria-selected={aboutTab === t}\n className={[styles.aboutTabBtn, aboutTab === t ? styles.aboutTabBtnActive : null]\n .filter(Boolean).join(\" \")}\n onClick={() => setAboutTab(t)}\n >\n {ABOUT_TAB_LABEL[t]}\n </button>\n ))}\n </div>\n )}\n\n <div className={styles.aboutTabContent}>\n {aboutTab === \"details\" && (\n <div className={styles.aboutSection}>\n {comments && <p className={styles.aboutComments}>{comments}</p>}\n {(developerName || website || links?.length) && (\n <dl className={styles.aboutInfoList}>\n {developerName && (\n <><dt className={styles.aboutInfoLabel}>Developer</dt><dd className={styles.aboutInfoValue}>{developerName}</dd></>\n )}\n {website && (\n <><dt className={styles.aboutInfoLabel}>Website</dt><dd className={styles.aboutInfoValue}>\n <a href={website} target=\"_blank\" rel=\"noopener noreferrer\" className={styles.aboutLink}>\n {websiteLabel ?? website}\n </a>\n </dd></>\n )}\n {links?.map((l) => (\n <><dt key={`${l.label}-dt`} className={styles.aboutInfoLabel}>{l.label}</dt>\n <dd key={`${l.label}-dd`} className={styles.aboutInfoValue}>\n <a href={l.url} target=\"_blank\" rel=\"noopener noreferrer\" className={styles.aboutLink}>{l.url}</a>\n </dd></>\n ))}\n </dl>\n )}\n </div>\n )}\n\n {aboutTab === \"credits\" && (\n <div className={styles.aboutSection}>\n {[\n { heading: \"Developers\", list: developers },\n { heading: \"Designers\", list: designers },\n { heading: \"Artists\", list: artists },\n ].filter((g) => g.list?.length).map(({ heading, list }) => (\n <div key={heading} className={styles.aboutCreditGroup}>\n <h4 className={styles.aboutCreditTitle}>{heading}</h4>\n <ul className={styles.aboutCreditList}>\n {list!.map((name) => <li key={name} className={styles.aboutCreditItem}>{name}</li>)}\n </ul>\n </div>\n ))}\n </div>\n )}\n\n {aboutTab === \"legal\" && (\n <div className={styles.aboutSection}>\n {copyright && <p className={styles.aboutCopyright}>{copyright}</p>}\n {licenseType && <p className={styles.aboutLicenseType}>{licenseType}</p>}\n {licenseText && <pre className={styles.aboutLicenseText}>{licenseText}</pre>}\n </div>\n )}\n </div>\n </>\n );\n\n const node = (\n <div\n className={styles.backdrop}\n onClick={handleBackdrop}\n aria-hidden=\"true\"\n >\n <div\n ref={dialogRef}\n role={role}\n aria-modal=\"true\"\n aria-labelledby={titleId}\n className={[\n styles.dialog,\n isAbout ? styles.dialogAbout : null,\n className,\n ].filter(Boolean).join(\" \")}\n onKeyDown={handleKeyDown}\n onClick={(e) => e.stopPropagation()}\n {...props}\n >\n {isAbout ? (\n renderAboutContent()\n ) : (\n <>\n {title && <div id={titleId} className={styles.title}>{title}</div>}\n {children && <div className={styles.body}>{children}</div>}\n </>\n )}\n\n {renderButtons()}\n </div>\n </div>\n );\n\n if (typeof document === \"undefined\") return node;\n return createPortal(node, document.body);\n}\n","/* ─── Wrapper ────────────────────────────────────────────────────────────────── */\n\n.wrapper {\n position: relative;\n display: inline-block;\n min-width: 160px;\n}\n\n/* ─── Trigger button ─────────────────────────────────────────────────────────── */\n\n.trigger {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: var(--gnome-space-1, 6px);\n width: 100%;\n\n padding: 7px var(--gnome-space-2, 12px);\n min-height: 36px;\n\n appearance: none;\n background-color: var(--gnome-card-bg-color, #ffffff);\n border: 1px solid var(--gnome-card-shade-color, rgba(0, 0, 0, 0.15));\n border-radius: var(--gnome-radius-md, 8px);\n box-shadow: var(--gnome-shadow-sm, 0 1px 2px rgba(0,0,0,0.08));\n\n font-family: var(--gnome-font-family);\n font-size: var(--gnome-font-size-body, 1rem);\n font-weight: var(--gnome-font-weight-normal, 400);\n color: var(--gnome-window-fg-color, rgba(0, 0, 0, 0.8));\n\n cursor: pointer;\n user-select: none;\n text-align: left;\n\n transition:\n background-color var(--gnome-duration-fast, 100ms) var(--gnome-easing-default, ease),\n border-color var(--gnome-duration-fast, 100ms) var(--gnome-easing-default, ease),\n box-shadow var(--gnome-duration-fast, 100ms) var(--gnome-easing-default, ease);\n}\n\n.trigger:hover:not(:disabled) {\n background-color: color-mix(in srgb, var(--gnome-card-bg-color, #ffffff) 90%, black 10%);\n}\n\n.trigger:focus {\n outline: none;\n}\n\n.trigger:focus-visible {\n border-color: var(--gnome-accent-color, #3584e4);\n box-shadow:\n 0 0 0 var(--gnome-focus-ring-offset, 2px) var(--gnome-window-bg-color, #fafafa),\n 0 0 0 calc(var(--gnome-focus-ring-offset, 2px) + var(--gnome-focus-ring-width, 3px))\n var(--gnome-focus-ring-color, #3584e4);\n}\n\n.trigger:disabled {\n opacity: var(--gnome-opacity-disabled, 0.5);\n cursor: not-allowed;\n}\n\n.triggerOpen {\n border-color: var(--gnome-accent-color, #3584e4);\n box-shadow:\n 0 0 0 var(--gnome-focus-ring-offset, 2px) var(--gnome-window-bg-color, #fafafa),\n 0 0 0 calc(var(--gnome-focus-ring-offset, 2px) + var(--gnome-focus-ring-width, 3px))\n var(--gnome-focus-ring-color, #3584e4);\n}\n\n/* ─── Trigger label ──────────────────────────────────────────────────────────── */\n\n.triggerLabel {\n flex: 1;\n min-width: 0;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n.placeholder {\n opacity: var(--gnome-opacity-dim, 0.55);\n}\n\n/* ─── Chevron icon ───────────────────────────────────────────────────────────── */\n\n.chevron {\n flex-shrink: 0;\n color: var(--gnome-window-fg-color, rgba(0, 0, 0, 0.8));\n opacity: var(--gnome-opacity-dim, 0.55);\n transition: transform var(--gnome-duration-fast, 100ms) var(--gnome-easing-default, ease);\n}\n\n.chevronOpen {\n transform: rotate(180deg);\n}\n\n/* ─── Listbox ────────────────────────────────────────────────────────────────── */\n\n.list {\n position: absolute;\n left: 0;\n right: 0;\n z-index: 1000;\n\n margin: 0;\n padding: 4px 0;\n list-style: none;\n\n background-color: var(--gnome-popover-bg-color, var(--gnome-card-bg-color, #ffffff));\n border: 1px solid var(--gnome-card-shade-color, rgba(0, 0, 0, 0.12));\n border-radius: var(--gnome-radius-md, 8px);\n box-shadow:\n 0 4px 16px rgba(0, 0, 0, 0.12),\n 0 1px 3px rgba(0, 0, 0, 0.08);\n\n max-height: 280px;\n overflow-y: auto;\n overscroll-behavior: contain;\n\n animation: list-in var(--gnome-duration-fast, 100ms) var(--gnome-easing-default, ease) both;\n}\n\n@keyframes list-in {\n from { opacity: 0; transform: scaleY(0.95); transform-origin: top center; }\n to { opacity: 1; transform: scaleY(1); transform-origin: top center; }\n}\n\n.listDown {\n top: calc(100% + 4px);\n}\n\n.listUp {\n bottom: calc(100% + 4px);\n animation-name: list-in-up;\n}\n\n@keyframes list-in-up {\n from { opacity: 0; transform: scaleY(0.95); transform-origin: bottom center; }\n to { opacity: 1; transform: scaleY(1); transform-origin: bottom center; }\n}\n\n/* ─── Option ─────────────────────────────────────────────────────────────────── */\n\n.option {\n display: flex;\n align-items: center;\n gap: var(--gnome-space-2, 12px);\n padding: 8px var(--gnome-space-2, 12px);\n min-height: 40px;\n\n font-family: var(--gnome-font-family);\n font-size: var(--gnome-font-size-body, 1rem);\n color: var(--gnome-window-fg-color, rgba(0, 0, 0, 0.8));\n\n cursor: pointer;\n user-select: none;\n\n transition: background-color var(--gnome-duration-fast, 100ms) var(--gnome-easing-default, ease);\n}\n\n.optionActive {\n background-color: color-mix(in srgb, var(--gnome-accent-bg-color, #3584e4) 12%, transparent);\n}\n\n.optionSelected {\n font-weight: var(--gnome-font-weight-semibold, 600);\n}\n\n.optionDisabled {\n opacity: var(--gnome-opacity-disabled, 0.5);\n cursor: not-allowed;\n pointer-events: none;\n}\n\n/* ─── Option text block ──────────────────────────────────────────────────────── */\n\n.optionText {\n flex: 1;\n min-width: 0;\n display: flex;\n flex-direction: column;\n gap: 1px;\n}\n\n.optionLabel {\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n}\n\n.optionDesc {\n font-size: var(--gnome-font-size-caption, 0.8125rem);\n opacity: var(--gnome-opacity-dim, 0.55);\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n}\n\n/* ─── Check icon (selected) ──────────────────────────────────────────────────── */\n\n.checkIcon {\n flex-shrink: 0;\n color: var(--gnome-accent-color, #3584e4);\n}\n\n/* ─── Dark mode ──────────────────────────────────────────────────────────────── */\n\n@media (prefers-color-scheme: dark) {\n .trigger {\n background-color: var(--gnome-card-bg-color, #383838);\n border-color: rgba(255, 255, 255, 0.12);\n box-shadow: none;\n }\n\n .trigger:hover:not(:disabled) {\n background-color: rgba(255, 255, 255, 0.08);\n }\n\n .list {\n background-color: var(--gnome-card-bg-color, #383838);\n border-color: rgba(255, 255, 255, 0.1);\n box-shadow:\n 0 4px 16px rgba(0, 0, 0, 0.4),\n 0 1px 3px rgba(0, 0, 0, 0.3);\n }\n}\n\n/* ─── Reduced motion ─────────────────────────────────────────────────────────── */\n\n@media (prefers-reduced-motion: reduce) {\n .trigger,\n .chevron {\n transition: none;\n }\n\n .list {\n animation: none;\n }\n}\n","import {\n useState,\n useRef,\n useId,\n useEffect,\n useCallback,\n type KeyboardEvent,\n type HTMLAttributes,\n} from \"react\";\nimport { PanDown, Check } from \"@gnome-ui/icons\";\nimport { Icon } from \"../Icon\";\nimport styles from \"./Dropdown.module.css\";\n\nexport interface DropdownOption<V extends string = string> {\n /** The value submitted / returned on selection. */\n value: V;\n /** Display label shown in the list and trigger. */\n label: string;\n /** Optional descriptive text shown below the label. */\n description?: string;\n /** Whether the option is selectable. */\n disabled?: boolean;\n}\n\nexport interface DropdownProps<V extends string = string>\n extends Omit<HTMLAttributes<HTMLDivElement>, \"onChange\"> {\n /** The list of selectable options. */\n options: DropdownOption<V>[];\n /** The currently selected value. */\n value?: V;\n /** Called when the user selects an option. */\n onChange?: (value: V) => void;\n /** Placeholder shown when no option is selected. */\n placeholder?: string;\n /** Accessible label for the control (used as `aria-label`). */\n \"aria-label\"?: string;\n /** Disables the entire control. */\n disabled?: boolean;\n}\n\n/**\n * Expandable option list following the Adwaita combo-row / drop-down pattern.\n *\n * - Fully keyboard-navigable: Space/Enter opens; ↑/↓ navigate; Enter selects; Escape closes.\n * - Uses `role=\"combobox\"` + `role=\"listbox\"` + `role=\"option\"` ARIA pattern.\n * - Closes when clicking outside or pressing Escape.\n * - The list flips above the trigger when there is not enough space below.\n *\n * @see https://developer.gnome.org/hig/patterns/controls/drop-down-lists.html\n */\nexport function Dropdown<V extends string = string>({\n options,\n value,\n onChange,\n placeholder = \"Select an option\",\n disabled,\n className,\n ...props\n}: DropdownProps<V>) {\n const [open, setOpen] = useState(false);\n const [activeIndex, setActiveIndex] = useState<number>(-1);\n const [flipUp, setFlipUp] = useState(false);\n\n const triggerId = useId();\n const listboxId = useId();\n const triggerRef = useRef<HTMLButtonElement>(null);\n const listRef = useRef<HTMLUListElement>(null);\n\n const selected = options.find((o) => o.value === value);\n\n // Compute flip direction when opening\n const computeFlip = useCallback(() => {\n if (!triggerRef.current) return;\n const rect = triggerRef.current.getBoundingClientRect();\n const spaceBelow = window.innerHeight - rect.bottom;\n const listH = Math.min(options.length * 48 + 8, 280);\n setFlipUp(spaceBelow < listH && rect.top > listH);\n }, [options.length]);\n\n const openList = useCallback(() => {\n if (disabled) return;\n computeFlip();\n setOpen(true);\n const idx = options.findIndex((o) => o.value === value && !o.disabled);\n setActiveIndex(idx >= 0 ? idx : options.findIndex((o) => !o.disabled));\n }, [disabled, computeFlip, options, value]);\n\n const closeList = useCallback(() => {\n setOpen(false);\n setActiveIndex(-1);\n triggerRef.current?.focus();\n }, []);\n\n const selectOption = useCallback(\n (opt: DropdownOption<V>) => {\n if (opt.disabled) return;\n onChange?.(opt.value);\n closeList();\n },\n [onChange, closeList]\n );\n\n // Scroll active item into view\n useEffect(() => {\n if (!open || activeIndex < 0 || !listRef.current) return;\n const item = listRef.current.children[activeIndex] as HTMLElement | undefined;\n item?.scrollIntoView({ block: \"nearest\" });\n }, [activeIndex, open]);\n\n // Close on outside click\n useEffect(() => {\n if (!open) return;\n const handler = (e: MouseEvent) => {\n if (\n !triggerRef.current?.contains(e.target as Node) &&\n !listRef.current?.contains(e.target as Node)\n ) {\n closeList();\n }\n };\n document.addEventListener(\"mousedown\", handler);\n return () => document.removeEventListener(\"mousedown\", handler);\n }, [open, closeList]);\n\n // Trigger keyboard handler\n const handleTriggerKeyDown = useCallback(\n (e: KeyboardEvent<HTMLButtonElement>) => {\n switch (e.key) {\n case \"Enter\":\n case \" \":\n case \"ArrowDown\":\n e.preventDefault();\n openList();\n break;\n case \"ArrowUp\":\n e.preventDefault();\n computeFlip();\n setOpen(true);\n setActiveIndex(\n options.reduce(\n (last, o, i) => (!o.disabled ? i : last),\n -1\n )\n );\n break;\n }\n },\n [openList, computeFlip, options]\n );\n\n // Listbox keyboard handler\n const handleListKeyDown = useCallback(\n (e: KeyboardEvent<HTMLUListElement>) => {\n const enabledIndexes = options\n .map((o, i) => ({ o, i }))\n .filter(({ o }) => !o.disabled)\n .map(({ i }) => i);\n\n const currentPos = enabledIndexes.indexOf(activeIndex);\n\n switch (e.key) {\n case \"ArrowDown\": {\n e.preventDefault();\n const next = enabledIndexes[Math.min(currentPos + 1, enabledIndexes.length - 1)];\n setActiveIndex(next ?? activeIndex);\n break;\n }\n case \"ArrowUp\": {\n e.preventDefault();\n const prev = enabledIndexes[Math.max(currentPos - 1, 0)];\n setActiveIndex(prev ?? activeIndex);\n break;\n }\n case \"Home\":\n e.preventDefault();\n setActiveIndex(enabledIndexes[0] ?? -1);\n break;\n case \"End\":\n e.preventDefault();\n setActiveIndex(enabledIndexes[enabledIndexes.length - 1] ?? -1);\n break;\n case \"Enter\":\n case \" \": {\n e.preventDefault();\n if (activeIndex >= 0) selectOption(options[activeIndex]);\n break;\n }\n case \"Escape\":\n case \"Tab\":\n closeList();\n break;\n }\n },\n [options, activeIndex, selectOption, closeList]\n );\n\n return (\n <div\n className={[styles.wrapper, className].filter(Boolean).join(\" \")}\n {...props}\n >\n {/* Trigger button */}\n <button\n ref={triggerRef}\n id={triggerId}\n type=\"button\"\n role=\"combobox\"\n aria-haspopup=\"listbox\"\n aria-expanded={open}\n aria-controls={listboxId}\n aria-activedescendant={\n open && activeIndex >= 0 ? `${listboxId}-opt-${activeIndex}` : undefined\n }\n disabled={disabled}\n className={[styles.trigger, open ? styles.triggerOpen : null]\n .filter(Boolean)\n .join(\" \")}\n onClick={() => (open ? closeList() : openList())}\n onKeyDown={handleTriggerKeyDown}\n >\n <span className={[styles.triggerLabel, !selected ? styles.placeholder : null]\n .filter(Boolean).join(\" \")}>\n {selected?.label ?? placeholder}\n </span>\n <Icon\n icon={PanDown}\n size=\"md\"\n aria-hidden\n className={[styles.chevron, open ? styles.chevronOpen : null]\n .filter(Boolean).join(\" \")}\n />\n </button>\n\n {/* Listbox */}\n {open && (\n <ul\n ref={listRef}\n id={listboxId}\n role=\"listbox\"\n aria-labelledby={triggerId}\n tabIndex={-1}\n className={[styles.list, flipUp ? styles.listUp : styles.listDown]\n .filter(Boolean)\n .join(\" \")}\n onKeyDown={handleListKeyDown}\n >\n {options.map((opt, i) => (\n <li\n key={opt.value}\n id={`${listboxId}-opt-${i}`}\n role=\"option\"\n aria-selected={opt.value === value}\n aria-disabled={opt.disabled}\n className={[\n styles.option,\n opt.value === value ? styles.optionSelected : null,\n i === activeIndex ? styles.optionActive : null,\n opt.disabled ? styles.optionDisabled : null,\n ]\n .filter(Boolean)\n .join(\" \")}\n onMouseEnter={() => !opt.disabled && setActiveIndex(i)}\n onClick={() => selectOption(opt)}\n >\n <span className={styles.optionText}>\n <span className={styles.optionLabel}>{opt.label}</span>\n {opt.description && (\n <span className={styles.optionDesc}>{opt.description}</span>\n )}\n </span>\n {opt.value === value && (\n <Icon icon={Check} size=\"md\" aria-hidden className={styles.checkIcon} />\n )}\n </li>\n ))}\n </ul>\n )}\n </div>\n );\n}\n","/* ─── Outer wrapper ──────────────────────────────────────────────────────────── */\n\n.wrapper {\n display: flex;\n flex-direction: column;\n width: 100%;\n /* Vertical space for thumb overflow */\n padding: 10px 0;\n}\n\n.hasLabels {\n padding-bottom: 0;\n}\n\n/* ─── Track ──────────────────────────────────────────────────────────────────── */\n\n.track {\n position: relative;\n width: 100%;\n height: 6px;\n\n background-color: var(--gnome-card-shade-color, rgba(0, 0, 0, 0.15));\n border-radius: var(--gnome-radius-pill, 9999px);\n\n cursor: pointer;\n touch-action: none; /* prevent scroll interference during drag */\n user-select: none;\n\n outline: none;\n}\n\n.track:focus-visible {\n /* Focus ring on the track itself as a fallback */\n box-shadow:\n 0 0 0 var(--gnome-focus-ring-offset, 2px) var(--gnome-window-bg-color, #fafafa),\n 0 0 0 calc(var(--gnome-focus-ring-offset, 2px) + var(--gnome-focus-ring-width, 3px))\n var(--gnome-focus-ring-color, #3584e4);\n}\n\n/* ─── Disabled ───────────────────────────────────────────────────────────────── */\n\n.disabled {\n opacity: var(--gnome-opacity-disabled, 0.5);\n cursor: not-allowed;\n pointer-events: none;\n}\n\n/* ─── Filled section (left of thumb) ────────────────────────────────────────── */\n\n.fill {\n position: absolute;\n top: 0;\n left: 0;\n height: 100%;\n border-radius: var(--gnome-radius-pill, 9999px);\n\n background-color: var(--gnome-accent-bg-color, #3584e4);\n\n pointer-events: none;\n}\n\n/* ─── Thumb ──────────────────────────────────────────────────────────────────── */\n\n.thumb {\n position: absolute;\n top: 50%;\n /* Translate left by half thumb width so it centres on the left edge of .fill */\n transform: translate(-50%, -50%);\n\n width: 22px;\n height: 22px;\n border-radius: var(--gnome-radius-pill, 9999px);\n\n background-color: var(--gnome-accent-bg-color, #3584e4);\n box-shadow:\n 0 1px 3px rgba(0, 0, 0, 0.25),\n 0 0 0 2px var(--gnome-window-bg-color, #fafafa);\n\n pointer-events: none;\n\n transition:\n transform var(--gnome-duration-fast, 100ms) var(--gnome-easing-default, ease),\n box-shadow var(--gnome-duration-fast, 100ms) var(--gnome-easing-default, ease);\n}\n\n/* Slightly larger thumb on focus / active drag */\n.track:focus-visible .thumb,\n.track:active .thumb {\n transform: translate(-50%, -50%) scale(1.18);\n box-shadow:\n 0 2px 6px rgba(0, 0, 0, 0.3),\n 0 0 0 2px var(--gnome-window-bg-color, #fafafa);\n}\n\n/* ─── Tick marks ─────────────────────────────────────────────────────────────── */\n\n.tick {\n position: absolute;\n top: 50%;\n transform: translate(-50%, -50%);\n\n width: 4px;\n height: 4px;\n border-radius: 50%;\n\n background-color: var(--gnome-window-bg-color, #fafafa);\n pointer-events: none;\n}\n\n/* ─── Labels row ─────────────────────────────────────────────────────────────── */\n\n.labels {\n position: relative;\n width: 100%;\n height: 20px;\n margin-top: var(--gnome-space-1, 6px);\n}\n\n.markLabel {\n position: absolute;\n transform: translateX(-50%);\n\n font-family: var(--gnome-font-family);\n font-size: var(--gnome-font-size-caption, 0.8125rem);\n line-height: 1;\n color: var(--gnome-window-fg-color, rgba(0, 0, 0, 0.8));\n opacity: var(--gnome-opacity-dim, 0.55);\n\n white-space: nowrap;\n pointer-events: none;\n user-select: none;\n}\n\n/* ─── Dark mode ──────────────────────────────────────────────────────────────── */\n\n@media (prefers-color-scheme: dark) {\n .track {\n background-color: rgba(255, 255, 255, 0.15);\n }\n\n .thumb {\n box-shadow:\n 0 1px 4px rgba(0, 0, 0, 0.5),\n 0 0 0 2px var(--gnome-window-bg-color, #242424);\n }\n\n .track:focus-visible .thumb,\n .track:active .thumb {\n box-shadow:\n 0 2px 8px rgba(0, 0, 0, 0.6),\n 0 0 0 2px var(--gnome-window-bg-color, #242424);\n }\n\n .tick {\n background-color: var(--gnome-window-bg-color, #242424);\n }\n}\n\n/* ─── Reduced motion ─────────────────────────────────────────────────────────── */\n\n@media (prefers-reduced-motion: reduce) {\n .thumb {\n transition: none;\n }\n}\n","import {\n useRef,\n useCallback,\n type PointerEvent,\n type KeyboardEvent,\n type HTMLAttributes,\n} from \"react\";\nimport styles from \"./Slider.module.css\";\n\nexport interface SliderProps\n extends Omit<HTMLAttributes<HTMLDivElement>, \"onChange\"> {\n /** Current value. Must be between `min` and `max`. */\n value: number;\n /** Called when the value changes. */\n onChange: (value: number) => void;\n /** Minimum value. Defaults to `0`. */\n min?: number;\n /** Maximum value. Defaults to `100`. */\n max?: number;\n /**\n * Granularity of each step.\n * - Arrow keys move by one step.\n * - Page Up/Down move by 10 steps.\n * Defaults to `1`.\n */\n step?: number;\n /** Disables the control. */\n disabled?: boolean;\n /**\n * Marks to display along the track.\n * Each mark can have an optional label rendered below the track.\n */\n marks?: Array<{ value: number; label?: string }>;\n /** Accessible label. Required when no visible `<label>` is present. */\n \"aria-label\"?: string;\n /** Associates the control with a visible label element. */\n \"aria-labelledby\"?: string;\n /** `id` of an element that describes the current value (e.g. a live region). */\n \"aria-describedby\"?: string;\n}\n\nfunction clamp(v: number, min: number, max: number) {\n return Math.min(max, Math.max(min, v));\n}\n\nfunction snapToStep(v: number, min: number, step: number, decimals: number) {\n return parseFloat((Math.round((v - min) / step) * step + min).toFixed(decimals));\n}\n\nfunction countDecimals(n: number) {\n const s = n.toString();\n const dot = s.indexOf(\".\");\n return dot === -1 ? 0 : s.length - dot - 1;\n}\n\n/**\n * Draggable range slider following the Adwaita `GtkScale` pattern.\n *\n * - Pointer (mouse / touch / pen) drag to set value continuously.\n * - Keyboard: ← / → move by one step; Page Up/Down by 10 steps; Home/End jump to bounds.\n * - Optional tick marks with labels.\n * - Fills the track from the left up to the thumb (accent colour).\n *\n * @see https://developer.gnome.org/hig/patterns/controls/sliders.html\n */\nexport function Slider({\n value,\n onChange,\n min = 0,\n max = 100,\n step = 1,\n disabled = false,\n marks,\n className,\n \"aria-label\": ariaLabel,\n \"aria-labelledby\": ariaLabelledBy,\n \"aria-describedby\": ariaDescribedBy,\n ...props\n}: SliderProps) {\n const trackRef = useRef<HTMLDivElement>(null);\n const dp = countDecimals(step);\n\n const pct = ((clamp(value, min, max) - min) / (max - min)) * 100;\n\n const setFromX = useCallback(\n (clientX: number) => {\n if (!trackRef.current) return;\n const { left, width } = trackRef.current.getBoundingClientRect();\n const raw = ((clientX - left) / width) * (max - min) + min;\n onChange(clamp(snapToStep(raw, min, step, dp), min, max));\n },\n [min, max, step, dp, onChange]\n );\n\n // Pointer drag — capture on the thumb, move anywhere\n const handlePointerDown = useCallback(\n (e: PointerEvent<HTMLDivElement>) => {\n if (disabled) return;\n e.currentTarget.setPointerCapture(e.pointerId);\n setFromX(e.clientX);\n },\n [disabled, setFromX]\n );\n\n const handlePointerMove = useCallback(\n (e: PointerEvent<HTMLDivElement>) => {\n if (!e.currentTarget.hasPointerCapture(e.pointerId)) return;\n setFromX(e.clientX);\n },\n [setFromX]\n );\n\n // Keyboard\n const handleKeyDown = useCallback(\n (e: KeyboardEvent<HTMLDivElement>) => {\n const set = (next: number) => {\n e.preventDefault();\n onChange(clamp(snapToStep(next, min, step, dp), min, max));\n };\n switch (e.key) {\n case \"ArrowRight\":\n case \"ArrowUp\":\n set(value + step);\n break;\n case \"ArrowLeft\":\n case \"ArrowDown\":\n set(value - step);\n break;\n case \"PageUp\":\n set(value + step * 10);\n break;\n case \"PageDown\":\n set(value - step * 10);\n break;\n case \"Home\":\n set(min);\n break;\n case \"End\":\n set(max);\n break;\n }\n },\n [value, step, min, max, dp, onChange]\n );\n\n const hasMarks = marks && marks.length > 0;\n const hasLabels = hasMarks && marks.some((m) => m.label);\n\n return (\n <div\n className={[\n styles.wrapper,\n hasLabels ? styles.hasLabels : null,\n className,\n ]\n .filter(Boolean)\n .join(\" \")}\n {...props}\n >\n {/* Accessible slider */}\n <div\n ref={trackRef}\n role=\"slider\"\n tabIndex={disabled ? -1 : 0}\n aria-valuemin={min}\n aria-valuemax={max}\n aria-valuenow={value}\n aria-label={ariaLabel}\n aria-labelledby={ariaLabelledBy}\n aria-describedby={ariaDescribedBy}\n aria-disabled={disabled || undefined}\n className={[styles.track, disabled ? styles.disabled : null]\n .filter(Boolean)\n .join(\" \")}\n onPointerDown={handlePointerDown}\n onPointerMove={handlePointerMove}\n onKeyDown={disabled ? undefined : handleKeyDown}\n >\n {/* Filled portion */}\n <div className={styles.fill} style={{ width: `${pct}%` }} />\n\n {/* Thumb */}\n <div\n className={styles.thumb}\n style={{ left: `${pct}%` }}\n aria-hidden=\"true\"\n />\n\n {/* Tick marks */}\n {hasMarks &&\n marks.map((m) => {\n const mPct = ((clamp(m.value, min, max) - min) / (max - min)) * 100;\n return (\n <div\n key={m.value}\n className={styles.tick}\n style={{ left: `${mPct}%` }}\n aria-hidden=\"true\"\n />\n );\n })}\n </div>\n\n {/* Mark labels */}\n {hasLabels && (\n <div className={styles.labels} aria-hidden=\"true\">\n {marks.map((m) => {\n const mPct = ((clamp(m.value, min, max) - min) / (max - min)) * 100;\n return (\n <span\n key={m.value}\n className={styles.markLabel}\n style={{ left: `${mPct}%` }}\n >\n {m.label}\n </span>\n );\n })}\n </div>\n )}\n </div>\n );\n}\n","/* ─── Panel ──────────────────────────────────────────────────────────────────── */\n\n.panel {\n position: fixed;\n z-index: 10200; /* above Toaster (9999), below Dialog (10000) */\n\n min-width: 180px;\n max-width: 320px;\n\n background-color: var(--gnome-popover-bg-color, var(--gnome-card-bg-color, #ffffff));\n color: var(--gnome-window-fg-color, rgba(0, 0, 0, 0.8));\n\n border: 1px solid var(--gnome-card-shade-color, rgba(0, 0, 0, 0.1));\n border-radius: var(--gnome-radius-lg, 12px);\n box-shadow:\n 0 4px 20px rgba(0, 0, 0, 0.14),\n 0 1px 4px rgba(0, 0, 0, 0.08);\n\n padding: var(--gnome-space-2, 12px);\n\n font-family: var(--gnome-font-family);\n\n /* Hidden before position is known */\n opacity: 0;\n transform: scale(0.95);\n transition:\n opacity var(--gnome-duration-fast, 100ms) var(--gnome-easing-default, ease),\n transform var(--gnome-duration-fast, 100ms) var(--gnome-easing-default, ease);\n\n /* Don't intercept events while invisible */\n pointer-events: none;\n\n outline: none;\n}\n\n.visible {\n opacity: 1;\n transform: scale(1);\n pointer-events: auto;\n}\n\n/* ─── Arrow ──────────────────────────────────────────────────────────────────── */\n\n.arrow {\n position: absolute;\n width: 12px;\n height: 12px;\n\n background-color: var(--gnome-popover-bg-color, var(--gnome-card-bg-color, #ffffff));\n border: 1px solid var(--gnome-card-shade-color, rgba(0, 0, 0, 0.1));\n\n /* Arrow points toward the trigger */\n transform: rotate(45deg);\n pointer-events: none;\n z-index: -1;\n}\n\n/* Position arrow on each side */\n.bottom .arrow {\n top: -6px;\n left: 50%;\n margin-left: -6px;\n border-right-color: transparent;\n border-bottom-color: transparent;\n}\n\n.top .arrow {\n bottom: -6px;\n left: 50%;\n margin-left: -6px;\n border-left-color: transparent;\n border-top-color: transparent;\n}\n\n.left .arrow {\n right: -6px;\n top: 50%;\n margin-top: -6px;\n border-left-color: transparent;\n border-bottom-color: transparent;\n}\n\n.right .arrow {\n left: -6px;\n top: 50%;\n margin-top: -6px;\n border-right-color: transparent;\n border-top-color: transparent;\n}\n\n/* ─── Dark mode ──────────────────────────────────────────────────────────────── */\n\n@media (prefers-color-scheme: dark) {\n .panel {\n background-color: var(--gnome-popover-bg-color, var(--gnome-card-bg-color, #383838));\n border-color: rgba(255, 255, 255, 0.1);\n box-shadow:\n 0 4px 24px rgba(0, 0, 0, 0.4),\n 0 1px 4px rgba(0, 0, 0, 0.3);\n }\n\n .arrow {\n background-color: var(--gnome-popover-bg-color, var(--gnome-card-bg-color, #383838));\n border-color: rgba(255, 255, 255, 0.1);\n }\n\n .bottom .arrow { border-right-color: transparent; border-bottom-color: transparent; }\n .top .arrow { border-left-color: transparent; border-top-color: transparent; }\n .left .arrow { border-left-color: transparent; border-bottom-color: transparent; }\n .right .arrow { border-right-color: transparent; border-top-color: transparent; }\n}\n\n/* ─── Reduced motion ─────────────────────────────────────────────────────────── */\n\n@media (prefers-reduced-motion: reduce) {\n .panel {\n transition: opacity var(--gnome-duration-fast, 100ms) linear;\n transform: none;\n }\n\n .visible {\n transform: none;\n }\n}\n","import {\n useState,\n useRef,\n useId,\n useEffect,\n useCallback,\n cloneElement,\n type ReactElement,\n type HTMLAttributes,\n type ReactNode,\n type KeyboardEvent,\n} from \"react\";\nimport { createPortal } from \"react-dom\";\nimport styles from \"./Popover.module.css\";\n\nexport type PopoverPlacement = \"top\" | \"bottom\" | \"left\" | \"right\";\n\nexport interface PopoverProps {\n /**\n * The rich content rendered inside the popover panel.\n * Can include interactive elements (buttons, links, forms).\n */\n content: ReactNode;\n /**\n * Preferred placement relative to the trigger.\n * Flips automatically when there is not enough viewport space.\n * Defaults to `\"bottom\"`.\n */\n placement?: PopoverPlacement;\n /**\n * Whether the popover is open (controlled mode).\n * Omit to use uncontrolled mode where the trigger toggles it.\n */\n open?: boolean;\n /**\n * Called when the popover requests to be closed\n * (Escape key, outside click, or trigger click while open).\n */\n onClose?: () => void;\n /**\n * Called when open state should change (controlled mode).\n */\n onOpenChange?: (open: boolean) => void;\n /**\n * The trigger element. Must be a single React element that can\n * receive `ref` and click/keyboard event props.\n */\n children: ReactElement<HTMLAttributes<HTMLElement>>;\n}\n\ninterface Position {\n top: number;\n left: number;\n placement: PopoverPlacement;\n}\n\nconst GAP = 8;\nconst MARGIN = 8;\n\nfunction computePosition(\n trigger: DOMRect,\n popover: DOMRect,\n preferred: PopoverPlacement\n): Position {\n const vw = window.innerWidth;\n const vh = window.innerHeight;\n\n const order: PopoverPlacement[] = [\n preferred,\n preferred === \"top\" ? \"bottom\"\n : preferred === \"bottom\" ? \"top\"\n : preferred === \"left\" ? \"right\"\n : \"left\",\n \"bottom\", \"top\", \"left\", \"right\",\n ];\n\n // De-duplicate while preserving order\n const candidates = [...new Set(order)];\n\n for (const p of candidates) {\n let top = 0, left = 0;\n\n if (p === \"bottom\") {\n top = trigger.bottom + GAP;\n left = trigger.left + trigger.width / 2 - popover.width / 2;\n } else if (p === \"top\") {\n top = trigger.top - popover.height - GAP;\n left = trigger.left + trigger.width / 2 - popover.width / 2;\n } else if (p === \"left\") {\n top = trigger.top + trigger.height / 2 - popover.height / 2;\n left = trigger.left - popover.width - GAP;\n } else {\n top = trigger.top + trigger.height / 2 - popover.height / 2;\n left = trigger.right + GAP;\n }\n\n const fitsH = left >= MARGIN && left + popover.width <= vw - MARGIN;\n const fitsV = top >= MARGIN && top + popover.height <= vh - MARGIN;\n\n if (fitsH && fitsV) {\n return { top, left, placement: p };\n }\n }\n\n // Fallback: clamp bottom placement\n const top = trigger.bottom + GAP;\n const left = trigger.left + trigger.width / 2 - popover.width / 2;\n return {\n top: Math.max(MARGIN, Math.min(top, vh - popover.height - MARGIN)),\n left: Math.max(MARGIN, Math.min(left, vw - popover.width - MARGIN)),\n placement: \"bottom\",\n };\n}\n\n/**\n * Floating panel anchored to a trigger element.\n *\n * Unlike `Tooltip`, a Popover can contain rich interactive content\n * (menus, forms, details). It follows the Adwaita `GtkPopover` pattern.\n *\n * - Supports both controlled (`open` + `onClose`) and uncontrolled modes.\n * - Auto-positions with viewport-aware flip.\n * - Closes on Escape, outside click, or trigger re-click.\n * - Focus is moved into the panel on open; returns to the trigger on close.\n * - `aria-haspopup=\"dialog\"` + `aria-expanded` wired to the trigger.\n * - `role=\"dialog\"` on the panel with `aria-labelledby` when a heading is present.\n *\n * @see https://gnome.pages.gitlab.gnome.org/libadwaita/doc/main/class.Popover.html\n */\nexport function Popover({\n content,\n placement: preferredPlacement = \"bottom\",\n open: controlledOpen,\n onClose,\n onOpenChange,\n children,\n}: PopoverProps) {\n const isControlled = controlledOpen !== undefined;\n const [uncontrolledOpen, setUncontrolledOpen] = useState(false);\n const open = isControlled ? controlledOpen : uncontrolledOpen;\n\n const [pos, setPos] = useState<Position | null>(null);\n\n const triggerId = useId();\n const popoverId = useId();\n const triggerRef = useRef<HTMLElement>(null);\n const panelRef = useRef<HTMLDivElement>(null);\n const previouslyFocused = useRef<Element | null>(null);\n\n const close = useCallback(() => {\n if (isControlled) {\n onClose?.();\n } else {\n setUncontrolledOpen(false);\n }\n onOpenChange?.(false);\n }, [isControlled, onClose, onOpenChange]);\n\n const toggle = useCallback(() => {\n if (open) {\n close();\n } else {\n if (!isControlled) setUncontrolledOpen(true);\n onOpenChange?.(true);\n }\n }, [open, close, isControlled, onOpenChange]);\n\n // Position panel\n const place = useCallback(() => {\n if (!triggerRef.current || !panelRef.current) return;\n setPos(\n computePosition(\n triggerRef.current.getBoundingClientRect(),\n panelRef.current.getBoundingClientRect(),\n preferredPlacement\n )\n );\n }, [preferredPlacement]);\n\n // Reposition on open, scroll, resize\n useEffect(() => {\n if (!open) return;\n // Two RAF: first lets panel mount with hidden styles so we can measure it;\n // second applies the measured position.\n const id = requestAnimationFrame(() => {\n place();\n window.addEventListener(\"scroll\", place, { passive: true, capture: true });\n window.addEventListener(\"resize\", place, { passive: true });\n });\n return () => {\n cancelAnimationFrame(id);\n window.removeEventListener(\"scroll\", place, { capture: true });\n window.removeEventListener(\"resize\", place);\n };\n }, [open, place]);\n\n // Save / restore focus\n useEffect(() => {\n if (open) {\n previouslyFocused.current = document.activeElement;\n requestAnimationFrame(() => {\n const first = panelRef.current?.querySelector<HTMLElement>(\n 'button, [href], input, select, textarea, [tabindex]:not([tabindex=\"-1\"])'\n );\n (first ?? panelRef.current)?.focus();\n });\n } else {\n (previouslyFocused.current as HTMLElement | null)?.focus();\n setPos(null);\n }\n }, [open]);\n\n // Close on outside pointer\n useEffect(() => {\n if (!open) return;\n const handler = (e: MouseEvent) => {\n if (\n !triggerRef.current?.contains(e.target as Node) &&\n !panelRef.current?.contains(e.target as Node)\n ) {\n close();\n }\n };\n document.addEventListener(\"mousedown\", handler);\n return () => document.removeEventListener(\"mousedown\", handler);\n }, [open, close]);\n\n // Escape key\n const handlePanelKeyDown = useCallback(\n (e: KeyboardEvent<HTMLDivElement>) => {\n if (e.key === \"Escape\") { e.stopPropagation(); close(); }\n },\n [close]\n );\n\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const trigger = cloneElement(children as any, {\n ref: triggerRef,\n id: triggerId,\n \"aria-haspopup\": \"dialog\",\n \"aria-expanded\": open,\n \"aria-controls\": open ? popoverId : undefined,\n onClick: (e: React.MouseEvent<HTMLElement>) => {\n toggle();\n children.props.onClick?.(e);\n },\n });\n\n const panel = (\n <div\n ref={panelRef}\n id={popoverId}\n role=\"dialog\"\n aria-labelledby={triggerId}\n tabIndex={-1}\n className={[\n styles.panel,\n pos ? styles[pos.placement] : null,\n open && pos ? styles.visible : null,\n ]\n .filter(Boolean)\n .join(\" \")}\n style={\n pos\n ? { top: pos.top, left: pos.left }\n : { visibility: \"hidden\", pointerEvents: \"none\", top: -9999, left: -9999 }\n }\n onKeyDown={handlePanelKeyDown}\n >\n {/* Arrow */}\n <div className={styles.arrow} aria-hidden=\"true\" />\n {content}\n </div>\n );\n\n return (\n <>\n {trigger}\n {open &&\n (typeof document !== \"undefined\"\n ? createPortal(panel, document.body)\n : panel)}\n </>\n );\n}\n","/* ─── Clamp ───────────────────────────────────────────────────────────────────\n Constrains content to maximumSize px while allowing it to shrink.\n Mirrors AdwClamp: width is 100% up to the max, then stays fixed.\n ──────────────────────────────────────────────────────────────────────────── */\n\n.clamp {\n width: 100%;\n margin-inline: auto; /* center horizontally */\n box-sizing: border-box;\n}\n","import type { HTMLAttributes, ReactNode } from \"react\";\nimport styles from \"./Clamp.module.css\";\n\nexport interface ClampProps extends HTMLAttributes<HTMLDivElement> {\n /**\n * Maximum content width in pixels.\n * The element shrinks freely below this value.\n * Defaults to `600` — the Adwaita recommended narrow-content width.\n */\n maximumSize?: number;\n /**\n * Fractional width (0–1) of the viewport to use when the viewport is\n * narrower than `maximumSize`. Useful for keeping a comfortable margin\n * on medium-width screens. Set to `1` (default) to always fill the width.\n */\n tighteningThreshold?: number;\n children?: ReactNode;\n}\n\n/**\n * Constrains its child to a maximum width while allowing it to shrink freely,\n * mirroring the Adwaita `AdwClamp` widget.\n *\n * Use this to keep readable line lengths and comfortable reading widths on\n * large screens while still filling the available space on narrow screens.\n *\n * @example\n * // Settings page — content never wider than 600 px\n * <Clamp maximumSize={600}>\n * <BoxedList>…</BoxedList>\n * </Clamp>\n *\n * @see https://gnome.pages.gitlab.gnome.org/libadwaita/doc/main/class.Clamp.html\n */\nexport function Clamp({\n maximumSize = 600,\n children,\n className,\n style,\n ...props\n}: ClampProps) {\n return (\n <div\n className={[styles.clamp, className].filter(Boolean).join(\" \")}\n style={{ maxWidth: maximumSize, ...style }}\n {...props}\n >\n {children}\n </div>\n );\n}\n","/* ─── Root ───────────────────────────────────────────────────────────────────── */\n\n.root {\n display: flex;\n width: 100%;\n height: 100%;\n overflow: hidden;\n}\n\n/* ─── Wide (both panes visible) ──────────────────────────────────────────────── */\n\n.expanded {\n flex-direction: row;\n}\n\n.expanded .sidebar {\n width: var(--sidebar-width, clamp(180px, 25%, 280px));\n flex-shrink: 0;\n overflow-y: auto;\n}\n\n.expanded .contentPane {\n flex: 1;\n min-width: 0;\n overflow-y: auto;\n}\n\n/* ─── Narrow (one pane at a time) ────────────────────────────────────────────── */\n\n.collapsed {\n flex-direction: column;\n position: relative;\n}\n\n.collapsed .sidebar,\n.collapsed .contentPane {\n position: absolute;\n inset: 0;\n width: 100%;\n height: 100%;\n overflow-y: auto;\n transition: transform var(--gnome-duration-normal, 200ms) var(--gnome-easing-default, ease);\n}\n\n/* Sidebar slides out to the left when content is shown */\n.collapsed .sidebar.paneVisible {\n transform: translateX(0);\n}\n\n.collapsed .sidebar.paneHidden {\n transform: translateX(-100%);\n pointer-events: none;\n}\n\n/* Content slides in from the right when shown */\n.collapsed .contentPane.paneVisible {\n transform: translateX(0);\n}\n\n.collapsed .contentPane.paneHidden {\n transform: translateX(100%);\n pointer-events: none;\n}\n\n/* ─── Divider ────────────────────────────────────────────────────────────────── */\n\n.divider {\n width: 1px;\n background-color: var(--gnome-card-shade-color, rgba(0, 0, 0, 0.1));\n flex-shrink: 0;\n}\n\n@media (prefers-color-scheme: dark) {\n .divider {\n background-color: rgba(255, 255, 255, 0.08);\n }\n}\n\n/* ─── Reduced motion ─────────────────────────────────────────────────────────── */\n\n@media (prefers-reduced-motion: reduce) {\n .collapsed .sidebar,\n .collapsed .contentPane {\n transition: none;\n }\n}\n","import { type HTMLAttributes, type ReactNode } from \"react\";\nimport { useBreakpoint } from \"../../hooks/useBreakpoint\";\nimport styles from \"./NavigationSplitView.module.css\";\n\nexport interface NavigationSplitViewProps\n extends Omit<HTMLAttributes<HTMLDivElement>, \"content\"> {\n /**\n * The sidebar / list pane (left side on wide screens).\n * On narrow screens this is the \"list\" view shown when `showContent` is false.\n */\n sidebar: ReactNode;\n /**\n * The detail / content pane (right side on wide screens).\n * On narrow screens this is the \"detail\" view shown when `showContent` is true.\n */\n content: ReactNode;\n /**\n * Controls which pane is visible on narrow screens (≤ 400 px).\n * - `false` (default) — show the sidebar list.\n * - `true` — show the content detail.\n *\n * Has no effect on wide screens where both panes are visible simultaneously.\n */\n showContent?: boolean;\n /**\n * Minimum sidebar width in px. Defaults to `180`.\n */\n minSidebarWidth?: number;\n /**\n * Maximum sidebar width in px. Defaults to `280`.\n */\n maxSidebarWidth?: number;\n /**\n * Fraction of total width given to the sidebar (0–1). Defaults to `0.25`.\n */\n sidebarWidthFraction?: number;\n}\n\n/**\n * Two-pane sidebar + content layout following the Adwaita\n * `AdwNavigationSplitView` pattern.\n *\n * On **wide** screens (> 400 px) both panes are visible side-by-side.\n * On **narrow** screens (≤ 400 px) only one pane is shown at a time;\n * use `showContent` to switch between the sidebar list and the detail view.\n *\n * The sidebar width is constrained between `minSidebarWidth` and\n * `maxSidebarWidth`, gravitating toward `sidebarWidthFraction` of the total.\n *\n * @example\n * const [showContent, setShowContent] = useState(false);\n * <NavigationSplitView\n * showContent={showContent}\n * sidebar={<MailList onSelect={() => setShowContent(true)} />}\n * content={<MailDetail onBack={() => setShowContent(false)} />}\n * />\n *\n * @see https://gnome.pages.gitlab.gnome.org/libadwaita/doc/main/class.NavigationSplitView.html\n */\nexport function NavigationSplitView({\n sidebar,\n content,\n showContent = false,\n minSidebarWidth = 180,\n maxSidebarWidth = 280,\n sidebarWidthFraction = 0.25,\n className,\n style,\n ...props\n}: NavigationSplitViewProps) {\n const { isNarrow } = useBreakpoint();\n\n // CSS custom property drives the sidebar width clamp\n const sidebarWidth =\n `clamp(${minSidebarWidth}px, ${sidebarWidthFraction * 100}%, ${maxSidebarWidth}px)`;\n\n return (\n <div\n className={[\n styles.root,\n isNarrow ? styles.collapsed : styles.expanded,\n className,\n ]\n .filter(Boolean)\n .join(\" \")}\n style={{ \"--sidebar-width\": sidebarWidth, ...style } as React.CSSProperties}\n {...props}\n >\n {/* Sidebar pane */}\n <div\n className={[\n styles.sidebar,\n isNarrow && showContent ? styles.paneHidden : styles.paneVisible,\n ]\n .filter(Boolean)\n .join(\" \")}\n aria-hidden={isNarrow && showContent}\n >\n {sidebar}\n </div>\n\n {/* Separator — only on wide screens */}\n {!isNarrow && <div className={styles.divider} aria-hidden=\"true\" />}\n\n {/* Content pane */}\n <div\n className={[\n styles.contentPane,\n isNarrow && !showContent ? styles.paneHidden : styles.paneVisible,\n ]\n .filter(Boolean)\n .join(\" \")}\n aria-hidden={isNarrow && !showContent}\n >\n {content}\n </div>\n </div>\n );\n}\n","/* ─── Root ───────────────────────────────────────────────────────────────────── */\n\n.root {\n display: flex;\n width: 100%;\n height: 100%;\n overflow: hidden;\n position: relative;\n}\n\n/* ─── Wide — side by side ────────────────────────────────────────────────────── */\n\n.wide {\n flex-direction: row;\n}\n\n.wide.end {\n flex-direction: row-reverse;\n}\n\n.wide .sidebar {\n width: var(--sidebar-width, clamp(180px, 25%, 280px));\n flex-shrink: 0;\n overflow-y: auto;\n}\n\n.wide .content {\n flex: 1;\n min-width: 0;\n overflow-y: auto;\n}\n\n/* ─── Narrow — overlay mode ──────────────────────────────────────────────────── */\n\n.narrow .content {\n width: 100%;\n overflow-y: auto;\n}\n\n.narrow .sidebar {\n position: absolute;\n top: 0;\n bottom: 0;\n width: var(--sidebar-width, clamp(180px, 75%, 280px));\n overflow-y: auto;\n z-index: 100;\n\n background-color: var(--gnome-window-bg-color, #fafafa);\n box-shadow: 2px 0 16px rgba(0, 0, 0, 0.2);\n\n transition: transform var(--gnome-duration-normal, 200ms) var(--gnome-easing-default, ease);\n}\n\n/* Start (left) sidebar */\n.narrow.start .sidebar {\n left: 0;\n}\n\n.narrow.start .sidebarOpen { transform: translateX(0); }\n.narrow.start .sidebarClosed { transform: translateX(-100%); }\n\n/* End (right) sidebar */\n.narrow.end .sidebar {\n right: 0;\n}\n\n.narrow.end .sidebarOpen { transform: translateX(0); }\n.narrow.end .sidebarClosed { transform: translateX(100%); }\n\n/* ─── Backdrop ───────────────────────────────────────────────────────────────── */\n\n.backdrop {\n position: absolute;\n inset: 0;\n z-index: 99;\n background-color: rgba(0, 0, 0, 0.35);\n\n opacity: 0;\n pointer-events: none;\n transition: opacity var(--gnome-duration-normal, 200ms) var(--gnome-easing-default, ease);\n}\n\n.backdropVisible {\n opacity: 1;\n pointer-events: auto;\n}\n\n/* ─── Dark mode ──────────────────────────────────────────────────────────────── */\n\n@media (prefers-color-scheme: dark) {\n .narrow .sidebar {\n background-color: var(--gnome-window-bg-color, #242424);\n box-shadow: 2px 0 16px rgba(0, 0, 0, 0.5);\n }\n\n .backdrop {\n background-color: rgba(0, 0, 0, 0.55);\n }\n}\n\n/* ─── Reduced motion ─────────────────────────────────────────────────────────── */\n\n@media (prefers-reduced-motion: reduce) {\n .narrow .sidebar,\n .backdrop {\n transition: none;\n }\n}\n","import { useEffect, useRef, type HTMLAttributes, type ReactNode } from \"react\";\nimport { useBreakpoint } from \"../../hooks/useBreakpoint\";\nimport styles from \"./OverlaySplitView.module.css\";\n\nexport interface OverlaySplitViewProps\n extends Omit<HTMLAttributes<HTMLDivElement>, \"content\"> {\n /**\n * The sidebar pane.\n * On wide screens it sits beside the content.\n * On narrow screens it slides over the content as an overlay.\n */\n sidebar: ReactNode;\n /** The main content pane — always visible. */\n content: ReactNode;\n /**\n * Whether the sidebar is shown.\n * - Wide screens: always both visible; this prop is ignored.\n * - Narrow screens (≤ 400 px): controls whether the overlay is open.\n */\n showSidebar?: boolean;\n /**\n * Called when the user clicks the backdrop (narrow screens only).\n * Use this to set `showSidebar` to false.\n */\n onClose?: () => void;\n /**\n * Which side the sidebar appears on. Defaults to `\"start\"` (left in LTR).\n */\n sidebarPosition?: \"start\" | \"end\";\n /**\n * Minimum sidebar width in px. Defaults to `180`.\n */\n minSidebarWidth?: number;\n /**\n * Maximum sidebar width in px. Defaults to `280`.\n */\n maxSidebarWidth?: number;\n /**\n * Fraction of total width given to the sidebar. Defaults to `0.25`.\n */\n sidebarWidthFraction?: number;\n}\n\n/**\n * Sidebar + content layout where the sidebar becomes a slide-over overlay\n * on narrow screens, mirroring the Adwaita `AdwOverlaySplitView` pattern.\n *\n * On **wide** screens (> 400 px) sidebar and content are side-by-side.\n * On **narrow** screens (≤ 400 px) the content fills the full width and\n * the sidebar slides in as an overlay when `showSidebar` is true,\n * with a translucent backdrop behind it.\n *\n * @example\n * const [open, setOpen] = useState(false);\n * <OverlaySplitView\n * showSidebar={open}\n * onClose={() => setOpen(false)}\n * sidebar={<Nav />}\n * content={<MainContent onMenuClick={() => setOpen(true)} />}\n * />\n *\n * @see https://gnome.pages.gitlab.gnome.org/libadwaita/doc/main/class.OverlaySplitView.html\n */\nexport function OverlaySplitView({\n sidebar,\n content,\n showSidebar = false,\n onClose,\n sidebarPosition = \"start\",\n minSidebarWidth = 180,\n maxSidebarWidth = 280,\n sidebarWidthFraction = 0.25,\n className,\n style,\n ...props\n}: OverlaySplitViewProps) {\n const { isNarrow } = useBreakpoint();\n const sidebarRef = useRef<HTMLDivElement>(null);\n\n const sidebarWidth =\n `clamp(${minSidebarWidth}px, ${sidebarWidthFraction * 100}%, ${maxSidebarWidth}px)`;\n\n // Close on Escape in overlay mode\n useEffect(() => {\n if (!isNarrow || !showSidebar) return;\n const handler = (e: KeyboardEvent) => {\n if (e.key === \"Escape\") onClose?.();\n };\n document.addEventListener(\"keydown\", handler);\n return () => document.removeEventListener(\"keydown\", handler);\n }, [isNarrow, showSidebar, onClose]);\n\n // Trap focus inside sidebar when open in overlay mode\n useEffect(() => {\n if (!isNarrow || !showSidebar) return;\n const first = sidebarRef.current?.querySelector<HTMLElement>(\n 'button, [href], input, select, textarea, [tabindex]:not([tabindex=\"-1\"])'\n );\n first?.focus();\n }, [isNarrow, showSidebar]);\n\n const isEnd = sidebarPosition === \"end\";\n\n return (\n <div\n className={[\n styles.root,\n isNarrow ? styles.narrow : styles.wide,\n isEnd ? styles.end : styles.start,\n className,\n ]\n .filter(Boolean)\n .join(\" \")}\n style={{ \"--sidebar-width\": sidebarWidth, ...style } as React.CSSProperties}\n {...props}\n >\n {/* Backdrop — narrow mode only */}\n {isNarrow && (\n <div\n className={[styles.backdrop, showSidebar ? styles.backdropVisible : null]\n .filter(Boolean)\n .join(\" \")}\n aria-hidden=\"true\"\n onClick={onClose}\n />\n )}\n\n {/* Sidebar */}\n <div\n ref={sidebarRef}\n className={[\n styles.sidebar,\n isNarrow\n ? showSidebar ? styles.sidebarOpen : styles.sidebarClosed\n : null,\n ]\n .filter(Boolean)\n .join(\" \")}\n aria-hidden={isNarrow && !showSidebar}\n >\n {sidebar}\n </div>\n\n {/* Content */}\n <div className={styles.content}>\n {content}\n </div>\n </div>\n );\n}\n","/* ─── Bottom bar ─────────────────────────────────────────────────────────────── */\n\n.bar {\n display: flex;\n align-items: stretch;\n\n width: 100%;\n min-height: 52px;\n\n background-color: var(--gnome-headerbar-bg-color, #ebebeb);\n border-top: 1px solid var(--gnome-headerbar-shade-color, rgba(0, 0, 0, 0.12));\n\n /* Safe-area inset for mobile browsers */\n padding-bottom: env(safe-area-inset-bottom, 0px);\n}\n\n/* Each child (ViewSwitcherItem) fills equal width */\n.bar > * {\n flex: 1;\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n gap: 2px;\n\n /* Override the horizontal pill style from ViewSwitcherItem */\n border-radius: 0 !important;\n padding: 6px 4px !important;\n font-size: var(--gnome-font-size-caption, 0.8125rem) !important;\n min-height: 52px;\n}\n\n/* ─── Dark mode ──────────────────────────────────────────────────────────────── */\n\n@media (prefers-color-scheme: dark) {\n .bar {\n background-color: var(--gnome-headerbar-bg-color, #303030);\n border-top-color: rgba(0, 0, 0, 0.3);\n }\n}\n","import type { HTMLAttributes, ReactNode } from \"react\";\nimport styles from \"./ViewSwitcherBar.module.css\";\n\nexport interface ViewSwitcherBarProps extends HTMLAttributes<HTMLDivElement> {\n /**\n * `ViewSwitcherItem` elements to render in the bottom bar.\n * Use the same items you pass to the header-bar `ViewSwitcher`.\n */\n children: ReactNode;\n /**\n * Whether the bar is visible.\n * Typically tied to `useBreakpoint().isMedium` so it appears\n * automatically when the window is ≤ 550 px wide.\n */\n reveal?: boolean;\n}\n\n/**\n * Bottom navigation bar for `ViewSwitcher` items on narrow screens (≤ 550 px),\n * mirroring the Adwaita `AdwViewSwitcherBar` pattern.\n *\n * Use in tandem with a `ViewSwitcher` in the `HeaderBar`:\n * - **Wide** (> 550 px): show the `ViewSwitcher` in the header, hide the bar.\n * - **Narrow** (≤ 550 px): hide the header switcher, reveal the bar.\n *\n * @example\n * const { isMedium } = useBreakpoint();\n * <>\n * <HeaderBar\n * title={\n * !isMedium && (\n * <ViewSwitcher aria-label=\"View\">…items…</ViewSwitcher>\n * )\n * }\n * />\n * <ViewSwitcherBar reveal={isMedium}>…items…</ViewSwitcherBar>\n * </>\n *\n * @see https://gnome.pages.gitlab.gnome.org/libadwaita/doc/main/class.ViewSwitcherBar.html\n */\nexport function ViewSwitcherBar({\n children,\n reveal = true,\n className,\n ...props\n}: ViewSwitcherBarProps) {\n if (!reveal) return null;\n\n return (\n <div\n role=\"navigation\"\n aria-label=\"Bottom navigation\"\n className={[styles.bar, className].filter(Boolean).join(\" \")}\n {...props}\n >\n {children}\n </div>\n );\n}\n",".link {\n color: var(--gnome-accent-color);\n text-decoration: none;\n cursor: pointer;\n outline: none;\n\n /* Smooth underline on hover */\n background-image: linear-gradient(currentColor, currentColor);\n background-position: 0 100%;\n background-repeat: no-repeat;\n background-size: 0% 1px;\n transition:\n background-size var(--gnome-duration-fast) var(--gnome-easing-default),\n opacity var(--gnome-duration-fast) var(--gnome-easing-default);\n}\n\n.link:hover {\n background-size: 100% 1px;\n}\n\n.link:active {\n opacity: 0.7;\n}\n\n/* ─── Focus ────────────────────────────────────────────────────── */\n\n.link:focus-visible {\n border-radius: 2px;\n box-shadow:\n 0 0 0 var(--gnome-focus-ring-offset) var(--gnome-window-bg-color),\n 0 0 0 calc(var(--gnome-focus-ring-offset) + var(--gnome-focus-ring-width)) var(--gnome-focus-ring-color);\n}\n\n/* ─── External indicator ──────────────────────────────────────── */\n\n.externalIcon {\n display: inline-block;\n margin-inline-start: 0.15em;\n font-size: 0.8em;\n vertical-align: super;\n line-height: 1;\n /* don't underline the icon itself */\n background-image: none;\n}\n\n/* ─── Reduced motion ──────────────────────────────────────────── */\n\n@media (prefers-reduced-motion: reduce) {\n .link {\n transition: none;\n }\n\n .link:hover {\n text-decoration: underline;\n background-size: 0%;\n }\n}\n","import type { AnchorHTMLAttributes, ReactNode } from \"react\";\nimport styles from \"./Link.module.css\";\n\nexport interface LinkProps extends AnchorHTMLAttributes<HTMLAnchorElement> {\n /**\n * When true the link is treated as external: opens in a new tab,\n * adds `rel=\"noopener noreferrer\"`, and shows a trailing ↗ icon.\n */\n external?: boolean;\n children?: ReactNode;\n}\n\n/**\n * Inline hyperlink following GNOME HIG.\n *\n * - Uses the accent colour and shows an underline on hover.\n * - `external` opens a new tab safely and appends a visual ↗ indicator.\n *\n * @see https://developer.gnome.org/hig/patterns/controls/links.html\n */\nexport function Link({\n external = false,\n children,\n className,\n target,\n rel,\n ...props\n}: LinkProps) {\n const isExternal = external || target === \"_blank\";\n\n return (\n <a\n className={[styles.link, className].filter(Boolean).join(\" \")}\n target={isExternal ? \"_blank\" : target}\n rel={isExternal ? \"noopener noreferrer\" : rel}\n {...props}\n >\n {children}\n {isExternal && (\n <span className={styles.externalIcon} aria-label=\"(opens in new tab)\">\n ↗\n </span>\n )}\n </a>\n );\n}\n","/* ─── Container ─────────────────────────────────────────────────────────────── */\n\n.group {\n display: inline-flex;\n align-items: stretch;\n gap: 0;\n\n background-color: var(--gnome-card-bg-color, #ffffff);\n border: 1px solid var(--gnome-light-3, #deddda);\n border-radius: var(--gnome-radius-md);\n box-shadow: var(--gnome-shadow-sm);\n padding: 2px;\n gap: 2px;\n}\n\n/* ─── Item ──────────────────────────────────────────────────────────────────── */\n\n.item {\n appearance: none;\n -webkit-appearance: none;\n border: none;\n background: transparent;\n font: inherit;\n cursor: pointer;\n outline: none;\n\n display: inline-flex;\n align-items: center;\n justify-content: center;\n gap: var(--gnome-space-1, 6px);\n\n padding: 4px var(--gnome-space-2, 12px);\n border-radius: calc(var(--gnome-radius-md) - 2px);\n min-height: 28px;\n\n font-family: var(--gnome-font-family);\n font-size: var(--gnome-font-size-body, 1rem);\n font-weight: var(--gnome-font-weight-normal, 400);\n color: var(--gnome-window-fg-color, rgba(0, 0, 0, 0.8));\n white-space: nowrap;\n\n transition:\n background-color var(--gnome-duration-fast) var(--gnome-easing-default),\n box-shadow var(--gnome-duration-fast) var(--gnome-easing-default);\n}\n\n/* Icon-only: square aspect ratio */\n.iconOnly {\n padding: 4px;\n min-width: 28px;\n}\n\n.item:hover:not(:disabled):not(.active) {\n background-color: rgba(0, 0, 0, 0.06);\n}\n\n.item:active:not(:disabled):not(.active) {\n background-color: rgba(0, 0, 0, 0.12);\n}\n\n.item:disabled {\n opacity: var(--gnome-opacity-disabled, 0.5);\n cursor: not-allowed;\n}\n\n/* ─── Focus ─────────────────────────────────────────────────────────────────── */\n\n.item:focus-visible {\n box-shadow:\n 0 0 0 var(--gnome-focus-ring-offset, 2px) var(--gnome-card-bg-color, #ffffff),\n 0 0 0 calc(var(--gnome-focus-ring-offset, 2px) + var(--gnome-focus-ring-width, 2px))\n var(--gnome-focus-ring-color, #3584e4);\n}\n\n/* ─── Active ────────────────────────────────────────────────────────────────── */\n\n.active {\n background-color: color-mix(in srgb, var(--gnome-accent-bg-color, #3584e4) 15%, transparent);\n color: var(--gnome-accent-color, #3584e4);\n font-weight: var(--gnome-font-weight-semibold, 600);\n box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--gnome-accent-bg-color, #3584e4) 40%, transparent);\n}\n\n/* ─── Icon ──────────────────────────────────────────────────────────────────── */\n\n.itemIcon {\n display: flex;\n align-items: center;\n flex-shrink: 0;\n}\n\n.itemLabel {\n line-height: 1;\n}\n\n/* ─── Dark mode ─────────────────────────────────────────────────────────────── */\n\n@media (prefers-color-scheme: dark) {\n .group {\n background-color: var(--gnome-card-bg-color, #383838);\n border-color: rgba(255, 255, 255, 0.12);\n box-shadow: none;\n }\n\n .item:hover:not(:disabled):not(.active) {\n background-color: rgba(255, 255, 255, 0.08);\n }\n\n .item:active:not(:disabled):not(.active) {\n background-color: rgba(255, 255, 255, 0.14);\n }\n\n .item:focus-visible {\n box-shadow:\n 0 0 0 var(--gnome-focus-ring-offset, 2px) var(--gnome-card-bg-color, #383838),\n 0 0 0 calc(var(--gnome-focus-ring-offset, 2px) + var(--gnome-focus-ring-width, 2px))\n var(--gnome-focus-ring-color, #78aeed);\n }\n\n .active {\n background-color: color-mix(in srgb, var(--gnome-accent-bg-color, #3584e4) 25%, transparent);\n color: var(--gnome-accent-color, #78aeed);\n box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--gnome-accent-bg-color, #3584e4) 50%, transparent);\n }\n}\n\n/* ─── Reduced motion ────────────────────────────────────────────────────────── */\n\n@media (prefers-reduced-motion: reduce) {\n .item {\n transition: none;\n }\n}\n","import {\n createContext,\n useContext,\n useRef,\n type HTMLAttributes,\n type KeyboardEvent,\n type ReactNode,\n} from \"react\";\nimport styles from \"./ToggleGroup.module.css\";\n\n// ─── Context ──────────────────────────────────────────────────────────────────\n\ninterface ToggleGroupContextValue {\n value: string;\n onValueChange: (value: string) => void;\n}\n\nconst ToggleGroupContext = createContext<ToggleGroupContextValue | null>(null);\n\nexport function useToggleGroup() {\n const ctx = useContext(ToggleGroupContext);\n if (!ctx) throw new Error(\"ToggleGroupItem must be used inside ToggleGroup\");\n return ctx;\n}\n\n// ─── ToggleGroup ──────────────────────────────────────────────────────────────\n\nexport interface ToggleGroupProps extends HTMLAttributes<HTMLDivElement> {\n /** Name of the currently active toggle. */\n value: string;\n /** Called with the new value when a toggle is selected. */\n onValueChange: (value: string) => void;\n /** Accessible label for the group. */\n \"aria-label\"?: string;\n children?: ReactNode;\n}\n\n/**\n * Mutually-exclusive group of toggle buttons for in-place option selection.\n *\n * Mirrors `AdwToggleGroup` (libadwaita 1.7 / GNOME 48).\n *\n * Use for formatting controls, view-mode selectors, and toolbar options —\n * wherever a `ViewSwitcher` would be too heavy or is not placed in a HeaderBar.\n *\n * Compose with `ToggleGroupItem`. Keyboard: ← / → cycle, Home / End jump.\n *\n * @see https://gnome.pages.gitlab.gnome.org/libadwaita/doc/main/class.ToggleGroup.html\n */\nexport function ToggleGroup({\n value,\n onValueChange,\n \"aria-label\": ariaLabel = \"Options\",\n children,\n className,\n ...props\n}: ToggleGroupProps) {\n const groupRef = useRef<HTMLDivElement>(null);\n\n function handleKeyDown(e: KeyboardEvent<HTMLDivElement>) {\n const items = Array.from(\n groupRef.current?.querySelectorAll<HTMLButtonElement>(\n \"[role=radio]:not(:disabled)\",\n ) ?? [],\n );\n const idx = items.findIndex((el) => el === document.activeElement);\n if (idx === -1) return;\n\n let next = idx;\n if (e.key === \"ArrowRight\" || e.key === \"ArrowDown\")\n next = (idx + 1) % items.length;\n else if (e.key === \"ArrowLeft\" || e.key === \"ArrowUp\")\n next = (idx - 1 + items.length) % items.length;\n else if (e.key === \"Home\") next = 0;\n else if (e.key === \"End\") next = items.length - 1;\n else return;\n\n e.preventDefault();\n items[next].focus();\n items[next].click();\n }\n\n return (\n <ToggleGroupContext.Provider value={{ value, onValueChange }}>\n <div\n ref={groupRef}\n role=\"radiogroup\"\n aria-label={ariaLabel}\n onKeyDown={handleKeyDown}\n className={[styles.group, className].filter(Boolean).join(\" \")}\n {...props}\n >\n {children}\n </div>\n </ToggleGroupContext.Provider>\n );\n}\n","import type { ButtonHTMLAttributes } from \"react\";\nimport type { IconDefinition } from \"@gnome-ui/icons\";\nimport { Icon } from \"../Icon\";\nimport { useToggleGroup } from \"./ToggleGroup\";\nimport styles from \"./ToggleGroup.module.css\";\n\nexport interface ToggleGroupItemProps\n extends ButtonHTMLAttributes<HTMLButtonElement> {\n /** String identifier — used as the group's `value` when this item is selected. */\n name: string;\n /** Visible label. Omit for icon-only items (provide `aria-label` instead). */\n label?: string;\n /** Icon from `@gnome-ui/icons`. */\n icon?: IconDefinition;\n}\n\n/**\n * Individual toggle inside a `ToggleGroup`.\n *\n * Can be icon-only, label-only, or icon + label. For icon-only items always\n * provide an `aria-label` so screen readers can identify the option.\n */\nexport function ToggleGroupItem({\n name,\n label,\n icon,\n disabled,\n className,\n ...props\n}: ToggleGroupItemProps) {\n const { value, onValueChange } = useToggleGroup();\n const active = value === name;\n const iconOnly = icon && !label;\n\n return (\n <button\n type=\"button\"\n role=\"radio\"\n aria-checked={active}\n tabIndex={active ? 0 : -1}\n disabled={disabled}\n onClick={() => onValueChange(name)}\n className={[\n styles.item,\n active ? styles.active : null,\n iconOnly ? styles.iconOnly : null,\n className,\n ]\n .filter(Boolean)\n .join(\" \")}\n {...props}\n >\n {icon && (\n <span className={styles.itemIcon}>\n <Icon icon={icon} size=\"md\" aria-hidden />\n </span>\n )}\n {label && <span className={styles.itemLabel}>{label}</span>}\n </button>\n );\n}\n",".wrapBox {\n display: flex;\n flex-wrap: wrap;\n flex-direction: row;\n gap: var(--wrapbox-row-gap) var(--wrapbox-gap);\n justify-content: var(--wrapbox-justify);\n align-items: var(--wrapbox-align);\n}\n\n.reverse {\n flex-wrap: wrap-reverse;\n}\n","import type { CSSProperties, HTMLAttributes, ReactNode } from \"react\";\nimport styles from \"./WrapBox.module.css\";\n\nexport type WrapBoxJustify = \"start\" | \"center\" | \"end\" | \"space-between\" | \"space-around\" | \"space-evenly\";\nexport type WrapBoxAlign = \"start\" | \"center\" | \"end\" | \"stretch\";\n\nexport interface WrapBoxProps extends HTMLAttributes<HTMLDivElement> {\n /** Gap between children on the same line. Default: `6`. */\n childSpacing?: number | string;\n /** Gap between lines. Defaults to `childSpacing`. */\n lineSpacing?: number | string;\n /** Horizontal distribution of children within each line. Default: `\"start\"`. */\n justify?: WrapBoxJustify;\n /** Cross-axis alignment of children within each line. Default: `\"center\"`. */\n align?: WrapBoxAlign;\n /** When true children wrap in the reverse direction (bottom to top). */\n wrapReverse?: boolean;\n children?: ReactNode;\n}\n\n/**\n * Flexible wrapping layout container.\n *\n * Children flow horizontally and wrap to new lines when they don't fit,\n * like words in a paragraph — without locking them into a grid.\n *\n * Mirrors `AdwWrapBox` (libadwaita 1.7 / GNOME 48).\n *\n * Pair with `Chip` for tag lists and filter rows.\n *\n * @see https://gnome.pages.gitlab.gnome.org/libadwaita/doc/main/class.WrapBox.html\n */\nexport function WrapBox({\n childSpacing = 6,\n lineSpacing,\n justify = \"start\",\n align = \"center\",\n wrapReverse = false,\n children,\n className,\n style,\n ...props\n}: WrapBoxProps) {\n const gap = typeof childSpacing === \"number\" ? `${childSpacing}px` : childSpacing;\n const rowGap = lineSpacing != null\n ? (typeof lineSpacing === \"number\" ? `${lineSpacing}px` : lineSpacing)\n : gap;\n\n const cssVars: CSSProperties = {\n \"--wrapbox-gap\": gap,\n \"--wrapbox-row-gap\": rowGap,\n \"--wrapbox-justify\": justify,\n \"--wrapbox-align\": align,\n ...style,\n } as CSSProperties;\n\n return (\n <div\n className={[\n styles.wrapBox,\n wrapReverse ? styles.reverse : null,\n className,\n ]\n .filter(Boolean)\n .join(\" \")}\n style={cssVars}\n {...props}\n >\n {children}\n </div>\n );\n}\n","/* ─── Base ──────────────────────────────────────────────────────────────────── */\n\n.chip {\n display: inline-flex;\n align-items: center;\n gap: 4px;\n\n padding: 3px 10px;\n border-radius: var(--gnome-radius-pill);\n\n font-family: var(--gnome-font-family);\n font-size: var(--gnome-font-size-caption, 0.875rem);\n font-weight: var(--gnome-font-weight-normal, 400);\n line-height: 1.4;\n color: var(--gnome-window-fg-color, rgba(0, 0, 0, 0.8));\n white-space: nowrap;\n\n background-color: var(--gnome-card-bg-color, #ffffff);\n border: 1px solid var(--gnome-light-3, #deddda);\n\n /* Not a button by default */\n appearance: none;\n -webkit-appearance: none;\n cursor: default;\n user-select: none;\n outline: none;\n}\n\n/* ─── Selectable (toggle button) ────────────────────────────────────────────── */\n\n.selectable {\n cursor: pointer;\n transition:\n background-color var(--gnome-duration-fast) var(--gnome-easing-default),\n border-color var(--gnome-duration-fast) var(--gnome-easing-default);\n}\n\n.selectable:hover:not(:disabled) {\n background-color: color-mix(in srgb, var(--gnome-card-bg-color, #ffffff) 85%, black);\n}\n\n.selectable:active:not(:disabled) {\n background-color: color-mix(in srgb, var(--gnome-card-bg-color, #ffffff) 70%, black);\n}\n\n.selectable:focus-visible {\n box-shadow:\n 0 0 0 var(--gnome-focus-ring-offset, 2px) var(--gnome-window-bg-color, #fafafa),\n 0 0 0 calc(var(--gnome-focus-ring-offset, 2px) + var(--gnome-focus-ring-width, 2px))\n var(--gnome-focus-ring-color, #3584e4);\n}\n\n/* ─── Selected state ────────────────────────────────────────────────────────── */\n\n.selected {\n background-color: color-mix(in srgb, var(--gnome-accent-bg-color, #3584e4) 15%, transparent);\n border-color: color-mix(in srgb, var(--gnome-accent-bg-color, #3584e4) 50%, transparent);\n color: var(--gnome-accent-color, #3584e4);\n font-weight: var(--gnome-font-weight-semibold, 600);\n}\n\n.selected.selectable:hover:not(:disabled) {\n background-color: color-mix(in srgb, var(--gnome-accent-bg-color, #3584e4) 22%, transparent);\n}\n\n/* ─── Disabled ──────────────────────────────────────────────────────────────── */\n\n.disabled {\n opacity: var(--gnome-opacity-disabled, 0.5);\n cursor: not-allowed;\n pointer-events: none;\n}\n\n/* ─── Leading icon ──────────────────────────────────────────────────────────── */\n\n.icon {\n display: flex;\n align-items: center;\n flex-shrink: 0;\n margin-inline-start: -2px;\n}\n\n/* ─── Label ─────────────────────────────────────────────────────────────────── */\n\n.label {\n /* nothing extra needed */\n}\n\n/* ─── Remove button ─────────────────────────────────────────────────────────── */\n\n.remove {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n\n appearance: none;\n -webkit-appearance: none;\n border: none;\n background: transparent;\n padding: 0;\n margin-inline-end: -4px;\n cursor: pointer;\n outline: none;\n\n color: inherit;\n opacity: 0.6;\n border-radius: var(--gnome-radius-pill);\n transition: opacity var(--gnome-duration-fast) var(--gnome-easing-default);\n}\n\n.remove:hover:not(:disabled) {\n opacity: 1;\n}\n\n.remove:focus-visible {\n opacity: 1;\n box-shadow:\n 0 0 0 2px var(--gnome-window-bg-color, #fafafa),\n 0 0 0 4px var(--gnome-focus-ring-color, #3584e4);\n}\n\n/* ─── Dark mode ─────────────────────────────────────────────────────────────── */\n\n@media (prefers-color-scheme: dark) {\n .chip {\n background-color: var(--gnome-card-bg-color, #383838);\n border-color: rgba(255, 255, 255, 0.12);\n }\n\n .selectable:hover:not(:disabled) {\n background-color: color-mix(in srgb, var(--gnome-card-bg-color, #383838) 85%, white);\n }\n\n .selectable:active:not(:disabled) {\n background-color: color-mix(in srgb, var(--gnome-card-bg-color, #383838) 70%, white);\n }\n\n .selected {\n background-color: color-mix(in srgb, var(--gnome-accent-bg-color, #3584e4) 25%, transparent);\n border-color: color-mix(in srgb, var(--gnome-accent-bg-color, #3584e4) 60%, transparent);\n color: var(--gnome-accent-color, #78aeed);\n }\n\n .selected.selectable:hover:not(:disabled) {\n background-color: color-mix(in srgb, var(--gnome-accent-bg-color, #3584e4) 35%, transparent);\n }\n\n .selectable:focus-visible {\n box-shadow:\n 0 0 0 var(--gnome-focus-ring-offset, 2px) var(--gnome-window-bg-color, #242424),\n 0 0 0 calc(var(--gnome-focus-ring-offset, 2px) + var(--gnome-focus-ring-width, 2px))\n var(--gnome-focus-ring-color, #78aeed);\n }\n}\n\n/* ─── Reduced motion ────────────────────────────────────────────────────────── */\n\n@media (prefers-reduced-motion: reduce) {\n .selectable,\n .remove {\n transition: none;\n }\n}\n","import type { HTMLAttributes } from \"react\";\nimport type { IconDefinition } from \"@gnome-ui/icons\";\nimport { Icon } from \"../Icon\";\nimport { Close } from \"@gnome-ui/icons\";\nimport styles from \"./Chip.module.css\";\n\nexport interface ChipProps extends HTMLAttributes<HTMLElement> {\n /** Text label displayed inside the chip. */\n label: string;\n /** Leading icon from `@gnome-ui/icons`. */\n icon?: IconDefinition;\n /**\n * When provided, renders a remove (×) button and calls this handler.\n * The chip root becomes a `<span>`; only the remove button is interactive.\n */\n onRemove?: () => void;\n /**\n * When true the chip renders as a toggle button.\n * Use `selected` + `onToggle` to control its state.\n */\n selectable?: boolean;\n /** Active/selected state. Only relevant when `selectable` is true. */\n selected?: boolean;\n /**\n * Called when a selectable chip is clicked.\n * Only relevant when `selectable` is true.\n */\n onToggle?: () => void;\n /** Disabled state — applies to both selectable chips and the remove button. */\n disabled?: boolean;\n}\n\n/**\n * Compact pill-shaped label for tags, filters, and selection states.\n *\n * Three usage modes:\n * - **Static** — just a visual label (no `onRemove`, no `selectable`).\n * - **Removable** — add `onRemove` to show a × button.\n * - **Selectable** — add `selectable` + `selected` + `onToggle` for toggle behaviour.\n *\n * Pair with `WrapBox` for multi-chip layouts.\n */\nexport function Chip({\n label,\n icon,\n onRemove,\n selectable = false,\n selected = false,\n onToggle,\n disabled = false,\n className,\n ...props\n}: ChipProps) {\n const isInteractive = selectable && !onRemove;\n\n const chipClasses = [\n styles.chip,\n selected ? styles.selected : null,\n disabled ? styles.disabled : null,\n isInteractive ? styles.selectable : null,\n className,\n ]\n .filter(Boolean)\n .join(\" \");\n\n const content = (\n <>\n {icon && (\n <span className={styles.icon}>\n <Icon icon={icon} size=\"sm\" aria-hidden />\n </span>\n )}\n <span className={styles.label}>{label}</span>\n {onRemove && (\n <button\n type=\"button\"\n className={styles.remove}\n aria-label={`Remove ${label}`}\n disabled={disabled}\n onClick={(e) => {\n e.stopPropagation();\n onRemove();\n }}\n tabIndex={disabled ? -1 : 0}\n >\n <Icon icon={Close} size=\"sm\" aria-hidden />\n </button>\n )}\n </>\n );\n\n if (isInteractive) {\n return (\n <button\n type=\"button\"\n role=\"checkbox\"\n aria-checked={selected}\n aria-label={label}\n disabled={disabled}\n onClick={onToggle}\n className={chipClasses}\n {...(props as HTMLAttributes<HTMLButtonElement>)}\n >\n {content}\n </button>\n );\n }\n\n return (\n <span\n className={chipClasses}\n {...props}\n >\n {content}\n </span>\n );\n}\n","/* ─── Backdrop ──────────────────────────────────────────────────────────────── */\n\n.backdrop {\n position: fixed;\n inset: 0;\n z-index: 10000;\n\n display: flex;\n align-items: center;\n justify-content: center;\n\n background-color: rgba(0, 0, 0, 0.5);\n backdrop-filter: blur(4px);\n -webkit-backdrop-filter: blur(4px);\n\n animation: backdrop-in var(--gnome-duration-normal, 200ms) var(--gnome-easing-default, ease) both;\n}\n\n@keyframes backdrop-in {\n from { opacity: 0; }\n to { opacity: 1; }\n}\n\n/* ─── Dialog card ───────────────────────────────────────────────────────────── */\n\n.dialog {\n display: flex;\n flex-direction: column;\n\n width: min(580px, calc(100vw - 48px));\n max-height: min(640px, calc(100vh - 80px));\n\n background-color: var(--gnome-window-bg-color, #fafafa);\n color: var(--gnome-window-fg-color, rgba(0, 0, 0, 0.8));\n\n border-radius: var(--gnome-radius-xl, 16px);\n box-shadow:\n 0 8px 32px rgba(0, 0, 0, 0.25),\n 0 0 0 1px rgba(0, 0, 0, 0.08);\n\n overflow: hidden;\n\n animation: dialog-in var(--gnome-duration-normal, 200ms) var(--gnome-easing-default, ease) both;\n}\n\n@keyframes dialog-in {\n from {\n opacity: 0;\n transform: scale(0.94) translateY(-8px);\n }\n to {\n opacity: 1;\n transform: scale(1) translateY(0);\n }\n}\n\n/* ─── Header ────────────────────────────────────────────────────────────────── */\n\n.header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: var(--gnome-space-3, 18px) var(--gnome-space-3, 18px) var(--gnome-space-2, 12px);\n flex-shrink: 0;\n}\n\n.title {\n font-family: var(--gnome-font-family);\n font-size: var(--gnome-font-size-title-4, 1.125rem);\n font-weight: var(--gnome-font-weight-bold, 700);\n line-height: 1.3;\n}\n\n.closeBtn {\n appearance: none;\n border: none;\n background: transparent;\n cursor: pointer;\n outline: none;\n\n display: flex;\n align-items: center;\n justify-content: center;\n\n width: 32px;\n height: 32px;\n border-radius: var(--gnome-radius-pill);\n\n font-size: 1.25rem;\n line-height: 1;\n color: var(--gnome-window-fg-color, rgba(0, 0, 0, 0.8));\n opacity: 0.6;\n\n transition: background-color var(--gnome-duration-fast) var(--gnome-easing-default),\n opacity var(--gnome-duration-fast) var(--gnome-easing-default);\n}\n\n.closeBtn:hover {\n background-color: rgba(0, 0, 0, 0.07);\n opacity: 1;\n}\n\n.closeBtn:focus-visible {\n opacity: 1;\n box-shadow:\n 0 0 0 2px var(--gnome-window-bg-color, #fafafa),\n 0 0 0 4px var(--gnome-focus-ring-color, #3584e4);\n}\n\n/* ─── Search ────────────────────────────────────────────────────────────────── */\n\n.searchRow {\n position: relative;\n display: flex;\n align-items: center;\n margin: 0 var(--gnome-space-3, 18px) var(--gnome-space-2, 12px);\n flex-shrink: 0;\n\n background-color: var(--gnome-card-bg-color, #ffffff);\n border: 1px solid var(--gnome-light-3, #deddda);\n border-radius: var(--gnome-radius-md, 8px);\n}\n\n.searchIcon {\n padding: 0 var(--gnome-space-1, 6px) 0 var(--gnome-space-2, 12px);\n font-size: 1.1rem;\n opacity: 0.5;\n pointer-events: none;\n user-select: none;\n}\n\n.searchInput {\n flex: 1;\n border: none;\n background: transparent;\n outline: none;\n padding: 7px 0;\n\n font-family: var(--gnome-font-family);\n font-size: var(--gnome-font-size-body, 1rem);\n color: var(--gnome-window-fg-color, rgba(0, 0, 0, 0.8));\n}\n\n.searchInput::placeholder {\n opacity: 0.5;\n}\n\n/* hide browser-native × */\n.searchInput::-webkit-search-cancel-button { display: none; }\n\n.searchClear {\n appearance: none;\n border: none;\n background: transparent;\n cursor: pointer;\n outline: none;\n padding: 0 var(--gnome-space-2, 12px);\n\n font-size: 1rem;\n color: var(--gnome-window-fg-color, rgba(0, 0, 0, 0.8));\n opacity: 0.5;\n transition: opacity var(--gnome-duration-fast) var(--gnome-easing-default);\n}\n\n.searchClear:hover { opacity: 1; }\n\n.searchRow:focus-within {\n border-color: var(--gnome-accent-color, #3584e4);\n box-shadow: 0 0 0 2px color-mix(in srgb, var(--gnome-accent-color, #3584e4) 25%, transparent);\n}\n\n/* ─── Scrollable body ───────────────────────────────────────────────────────── */\n\n.body {\n overflow-y: auto;\n padding: 0 var(--gnome-space-3, 18px) var(--gnome-space-3, 18px);\n display: flex;\n flex-direction: column;\n gap: var(--gnome-space-3, 18px);\n}\n\n.empty {\n text-align: center;\n padding: var(--gnome-space-4, 24px) 0;\n font-family: var(--gnome-font-family);\n font-size: var(--gnome-font-size-body, 1rem);\n opacity: 0.5;\n margin: 0;\n}\n\n/* ─── Section ───────────────────────────────────────────────────────────────── */\n\n.section {\n display: flex;\n flex-direction: column;\n gap: 2px;\n}\n\n.sectionTitle {\n margin: 0 0 var(--gnome-space-1, 6px);\n font-family: var(--gnome-font-family);\n font-size: var(--gnome-font-size-caption, 0.875rem);\n font-weight: var(--gnome-font-weight-semibold, 600);\n text-transform: uppercase;\n letter-spacing: 0.06em;\n opacity: 0.5;\n}\n\n.list {\n list-style: none;\n margin: 0;\n padding: 0;\n display: flex;\n flex-direction: column;\n gap: 1px;\n\n background-color: var(--gnome-card-bg-color, #ffffff);\n border: 1px solid rgba(0, 0, 0, 0.08);\n border-radius: var(--gnome-radius-md, 8px);\n overflow: hidden;\n}\n\n/* ─── Shortcut row ──────────────────────────────────────────────────────────── */\n\n.row {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: var(--gnome-space-3, 18px);\n padding: 8px var(--gnome-space-2, 12px);\n border-bottom: 1px solid rgba(0, 0, 0, 0.05);\n}\n\n.row:last-child {\n border-bottom: none;\n}\n\n/* ─── Key caps ──────────────────────────────────────────────────────────────── */\n\n.keys {\n display: inline-flex;\n align-items: center;\n gap: 3px;\n flex-shrink: 0;\n}\n\n.keyCap {\n display: inline-flex;\n align-items: center;\n gap: 3px;\n}\n\n.keyCap kbd {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n\n padding: 2px 7px;\n min-width: 24px;\n\n font-family: var(--gnome-font-family);\n font-size: var(--gnome-font-size-caption, 0.875rem);\n font-weight: var(--gnome-font-weight-semibold, 600);\n font-style: normal;\n line-height: 1.6;\n\n background-color: var(--gnome-window-bg-color, #fafafa);\n border: 1px solid rgba(0, 0, 0, 0.18);\n border-bottom-width: 2px;\n border-radius: var(--gnome-radius-sm, 4px);\n box-shadow: 0 1px 0 rgba(0, 0, 0, 0.08);\n\n color: var(--gnome-window-fg-color, rgba(0, 0, 0, 0.8));\n white-space: nowrap;\n}\n\n.plus {\n font-size: var(--gnome-font-size-caption, 0.875rem);\n opacity: 0.4;\n}\n\n/* ─── Description ───────────────────────────────────────────────────────────── */\n\n.description {\n font-family: var(--gnome-font-family);\n font-size: var(--gnome-font-size-body, 1rem);\n color: var(--gnome-window-fg-color, rgba(0, 0, 0, 0.8));\n text-align: end;\n}\n\n/* ─── Dark mode ─────────────────────────────────────────────────────────────── */\n\n@media (prefers-color-scheme: dark) {\n .backdrop {\n background-color: rgba(0, 0, 0, 0.65);\n }\n\n .dialog {\n background-color: var(--gnome-window-bg-color, #242424);\n box-shadow:\n 0 8px 32px rgba(0, 0, 0, 0.5),\n 0 0 0 1px rgba(255, 255, 255, 0.08);\n }\n\n .closeBtn:hover {\n background-color: rgba(255, 255, 255, 0.08);\n }\n\n .searchRow {\n background-color: var(--gnome-card-bg-color, #383838);\n border-color: rgba(255, 255, 255, 0.12);\n }\n\n .list {\n background-color: var(--gnome-card-bg-color, #383838);\n border-color: rgba(255, 255, 255, 0.08);\n }\n\n .row {\n border-bottom-color: rgba(255, 255, 255, 0.05);\n }\n\n .keyCap kbd {\n background-color: var(--gnome-window-bg-color, #242424);\n border-color: rgba(255, 255, 255, 0.2);\n box-shadow: 0 1px 0 rgba(0, 0, 0, 0.3);\n }\n}\n\n/* ─── Reduced motion ────────────────────────────────────────────────────────── */\n\n@media (prefers-reduced-motion: reduce) {\n .backdrop,\n .dialog {\n animation: none;\n }\n\n .closeBtn,\n .searchClear {\n transition: none;\n }\n}\n","import {\n useState,\n useEffect,\n useRef,\n useCallback,\n type KeyboardEvent,\n} from \"react\";\nimport { createPortal } from \"react-dom\";\nimport styles from \"./ShortcutsDialog.module.css\";\n\n// ─── Public types ─────────────────────────────────────────────────────────────\n\nexport interface ShortcutEntry {\n /**\n * Key combination tokens, e.g. `[\"Ctrl\", \"S\"]` or `[\"F5\"]`.\n * Each token is rendered as a styled `<kbd>` key cap.\n */\n keys: string[];\n /** Human-readable description of what the shortcut does. */\n description: string;\n}\n\nexport interface ShortcutsSection {\n /** Section heading, e.g. \"File\", \"Edit\". */\n title: string;\n shortcuts: ShortcutEntry[];\n}\n\nexport interface ShortcutsDialogProps {\n /** Whether the dialog is visible. */\n open: boolean;\n /** Called when the dialog is dismissed (Escape, close button). */\n onClose: () => void;\n /** Dialog heading. Defaults to `\"Keyboard Shortcuts\"`. */\n title?: string;\n /** Shortcut sections to display. */\n sections: ShortcutsSection[];\n}\n\n// ─── Focusable selector (reused from Dialog) ─────────────────────────────────\n\nconst FOCUSABLE =\n 'button:not([disabled]), [href], input:not([disabled]), [tabindex]:not([tabindex=\"-1\"])';\n\n// ─── Component ────────────────────────────────────────────────────────────────\n\n/**\n * Modal dialog listing keyboard shortcuts, grouped in sections, with\n * integrated search to filter by description or key name.\n *\n * Mirrors `AdwShortcutsDialog` (libadwaita 1.8 / GNOME 49).\n *\n * - Renders into a portal (`document.body`).\n * - Traps focus; Escape closes.\n * - Search filters across all sections in real time.\n *\n * @see https://gnome.pages.gitlab.gnome.org/libadwaita/doc/main/class.ShortcutsDialog.html\n */\nexport function ShortcutsDialog({\n open,\n onClose,\n title = \"Keyboard Shortcuts\",\n sections,\n}: ShortcutsDialogProps) {\n const [query, setQuery] = useState(\"\");\n const dialogRef = useRef<HTMLDivElement>(null);\n const searchRef = useRef<HTMLInputElement>(null);\n const previouslyFocused = useRef<Element | null>(null);\n const titleId = useRef(`shortcuts-title-${Math.random().toString(36).slice(2, 9)}`);\n\n // Save / restore focus\n useEffect(() => {\n if (open) {\n previouslyFocused.current = document.activeElement;\n // Focus the search field after mount\n requestAnimationFrame(() => searchRef.current?.focus());\n } else {\n setQuery(\"\");\n (previouslyFocused.current as HTMLElement | null)?.focus();\n }\n }, [open]);\n\n // Focus trap + Escape\n const handleKeyDown = useCallback(\n (e: KeyboardEvent<HTMLDivElement>) => {\n if (e.key === \"Escape\") {\n e.preventDefault();\n onClose();\n return;\n }\n if (e.key !== \"Tab\") return;\n\n const focusable = Array.from(\n dialogRef.current?.querySelectorAll<HTMLElement>(FOCUSABLE) ?? [],\n );\n if (focusable.length === 0) return;\n\n const first = focusable[0];\n const last = focusable[focusable.length - 1];\n\n if (e.shiftKey) {\n if (document.activeElement === first) {\n e.preventDefault();\n last.focus();\n }\n } else {\n if (document.activeElement === last) {\n e.preventDefault();\n first.focus();\n }\n }\n },\n [onClose],\n );\n\n if (!open) return null;\n\n // Filter sections by query\n const q = query.trim().toLowerCase();\n const filtered = sections\n .map((section) => ({\n ...section,\n shortcuts: section.shortcuts.filter(\n ({ description, keys }) =>\n !q ||\n description.toLowerCase().includes(q) ||\n keys.some((k) => k.toLowerCase().includes(q)),\n ),\n }))\n .filter((s) => s.shortcuts.length > 0);\n\n const node = (\n <div\n className={styles.backdrop}\n onClick={onClose}\n aria-hidden=\"true\"\n >\n <div\n ref={dialogRef}\n role=\"dialog\"\n aria-modal=\"true\"\n aria-labelledby={titleId.current}\n className={styles.dialog}\n onKeyDown={handleKeyDown}\n onClick={(e) => e.stopPropagation()}\n >\n {/* ── Header ── */}\n <div className={styles.header}>\n <span id={titleId.current} className={styles.title}>\n {title}\n </span>\n <button\n type=\"button\"\n className={styles.closeBtn}\n aria-label=\"Close\"\n onClick={onClose}\n >\n ×\n </button>\n </div>\n\n {/* ── Search ── */}\n <div className={styles.searchRow}>\n <span className={styles.searchIcon} aria-hidden=\"true\">⌕</span>\n <input\n ref={searchRef}\n type=\"search\"\n className={styles.searchInput}\n placeholder=\"Search shortcuts…\"\n value={query}\n onChange={(e) => setQuery(e.target.value)}\n aria-label=\"Search shortcuts\"\n />\n {query && (\n <button\n type=\"button\"\n className={styles.searchClear}\n aria-label=\"Clear search\"\n onClick={() => { setQuery(\"\"); searchRef.current?.focus(); }}\n >\n ×\n </button>\n )}\n </div>\n\n {/* ── Shortcut list ── */}\n <div className={styles.body} role=\"list\">\n {filtered.length === 0 ? (\n <p className={styles.empty}>No shortcuts match \"{query}\"</p>\n ) : (\n filtered.map((section) => (\n <section key={section.title} className={styles.section}>\n <h3 className={styles.sectionTitle}>{section.title}</h3>\n <ul className={styles.list}>\n {section.shortcuts.map((shortcut) => (\n <li key={shortcut.description} className={styles.row} role=\"listitem\">\n <span className={styles.keys} aria-label={shortcut.keys.join(\" + \")}>\n {shortcut.keys.map((key, i) => (\n <span key={i} className={styles.keyCap}>\n <kbd>{key}</kbd>\n {i < shortcut.keys.length - 1 && (\n <span className={styles.plus} aria-hidden=\"true\">+</span>\n )}\n </span>\n ))}\n </span>\n <span className={styles.description}>{shortcut.description}</span>\n </li>\n ))}\n </ul>\n </section>\n ))\n )}\n </div>\n </div>\n </div>\n );\n\n if (typeof document === \"undefined\") return node;\n return createPortal(node, document.body);\n}\n","/* ─── Container ─────────────────────────────────────────────────────────────── */\n\n.sidebar {\n display: flex;\n flex-direction: column;\n width: 220px;\n height: 100%;\n background-color: var(--gnome-sidebar-bg-color, #ebebeb);\n color: var(--gnome-sidebar-fg-color, rgba(0, 0, 0, 0.8));\n border-right: 1px solid var(--gnome-headerbar-border-color, rgba(0, 0, 0, 0.12));\n overflow-y: auto;\n overflow-x: hidden;\n}\n\n/* ─── List ──────────────────────────────────────────────────────────────────── */\n\n.list {\n list-style: none;\n margin: 0;\n padding: var(--gnome-space-1, 6px);\n display: flex;\n flex-direction: column;\n gap: 2px;\n}\n\n/* ─── Item wrapper ──────────────────────────────────────────────────────────── */\n\n.item {\n display: block;\n}\n\n/* ─── Item button ───────────────────────────────────────────────────────────── */\n\n.itemBtn {\n appearance: none;\n -webkit-appearance: none;\n border: none;\n background: transparent;\n font: inherit;\n text-align: start;\n cursor: pointer;\n outline: none;\n\n display: flex;\n align-items: center;\n gap: var(--gnome-space-2, 12px);\n width: 100%;\n padding: 9px var(--gnome-space-2, 12px);\n border-radius: var(--gnome-radius-md, 8px);\n min-height: 42px;\n\n color: var(--gnome-sidebar-fg-color, rgba(0, 0, 0, 0.8));\n font-family: var(--gnome-font-family);\n font-size: var(--gnome-font-size-body, 1rem);\n font-weight: var(--gnome-font-weight-normal, 400);\n\n transition: background-color var(--gnome-duration-fast) var(--gnome-easing-default);\n}\n\n.itemBtn:hover:not(:disabled):not(.active) {\n background-color: color-mix(\n in srgb,\n var(--gnome-sidebar-bg-color, #ebebeb) 88%,\n black 12%\n );\n}\n\n.itemBtn:active:not(:disabled):not(.active) {\n background-color: color-mix(\n in srgb,\n var(--gnome-sidebar-bg-color, #ebebeb) 78%,\n black 22%\n );\n}\n\n.itemBtn:focus-visible {\n box-shadow:\n 0 0 0 var(--gnome-focus-ring-offset, 2px) var(--gnome-sidebar-bg-color, #ebebeb),\n 0 0 0 calc(var(--gnome-focus-ring-offset, 2px) + var(--gnome-focus-ring-width, 2px))\n var(--gnome-focus-ring-color, #3584e4);\n}\n\n.itemBtn:disabled {\n opacity: var(--gnome-opacity-disabled, 0.5);\n cursor: not-allowed;\n}\n\n/* ─── Active ────────────────────────────────────────────────────────────────── */\n\n.active {\n background-color: var(--gnome-accent-bg-color, #3584e4);\n color: var(--gnome-accent-fg-color, #ffffff);\n font-weight: var(--gnome-font-weight-semibold, 600);\n}\n\n.active:hover:not(:disabled) {\n background-color: color-mix(in srgb, var(--gnome-accent-bg-color, #3584e4) 92%, black 8%);\n}\n\n.active:active:not(:disabled) {\n background-color: color-mix(in srgb, var(--gnome-accent-bg-color, #3584e4) 84%, black 16%);\n}\n\n/* ─── Icon ──────────────────────────────────────────────────────────────────── */\n\n.itemIcon {\n display: flex;\n align-items: center;\n flex-shrink: 0;\n opacity: 0.85;\n}\n\n.active .itemIcon {\n opacity: 1;\n}\n\n/* ─── Label ─────────────────────────────────────────────────────────────────── */\n\n.itemLabel {\n flex: 1;\n min-width: 0;\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n}\n\n/* ─── Suffix / count ────────────────────────────────────────────────────────── */\n\n.itemSuffix {\n flex-shrink: 0;\n margin-left: auto;\n display: flex;\n align-items: center;\n}\n\n.count {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n min-width: 20px;\n height: 20px;\n padding: 0 6px;\n border-radius: var(--gnome-radius-pill);\n\n font-size: var(--gnome-font-size-caption, 0.875rem);\n font-weight: var(--gnome-font-weight-semibold, 600);\n line-height: 1;\n\n /* Inactive: dim pill */\n background-color: rgba(0, 0, 0, 0.12);\n color: var(--gnome-sidebar-fg-color, rgba(0, 0, 0, 0.8));\n}\n\n.active .count {\n /* Active: white pill on accent */\n background-color: rgba(255, 255, 255, 0.25);\n color: var(--gnome-accent-fg-color, #ffffff);\n}\n\n/* ─── Dark mode ─────────────────────────────────────────────────────────────── */\n\n@media (prefers-color-scheme: dark) {\n .sidebar {\n border-right-color: rgba(255, 255, 255, 0.1);\n }\n\n .itemBtn:hover:not(:disabled):not(.active) {\n background-color: color-mix(\n in srgb,\n var(--gnome-sidebar-bg-color, #303030) 85%,\n white 15%\n );\n }\n\n .itemBtn:active:not(:disabled):not(.active) {\n background-color: color-mix(\n in srgb,\n var(--gnome-sidebar-bg-color, #303030) 75%,\n white 25%\n );\n }\n\n .count {\n background-color: rgba(255, 255, 255, 0.12);\n color: var(--gnome-sidebar-fg-color, rgba(255, 255, 255, 0.87));\n }\n}\n\n/* ─── Reduced motion ────────────────────────────────────────────────────────── */\n\n@media (prefers-reduced-motion: reduce) {\n .itemBtn {\n transition: none;\n }\n}\n","import {\n createContext,\n useContext,\n useRef,\n type HTMLAttributes,\n type KeyboardEvent,\n type ReactNode,\n} from \"react\";\nimport styles from \"./ViewSwitcherSidebar.module.css\";\n\n// ─── Context ──────────────────────────────────────────────────────────────────\n\ninterface ViewSwitcherSidebarContextValue {\n value: string;\n onValueChange: (value: string) => void;\n}\n\nconst ViewSwitcherSidebarContext =\n createContext<ViewSwitcherSidebarContextValue | null>(null);\n\nexport function useViewSwitcherSidebar() {\n const ctx = useContext(ViewSwitcherSidebarContext);\n if (!ctx)\n throw new Error(\n \"ViewSwitcherSidebarItem must be used inside ViewSwitcherSidebar\",\n );\n return ctx;\n}\n\n// ─── Component ────────────────────────────────────────────────────────────────\n\nexport interface ViewSwitcherSidebarProps extends HTMLAttributes<HTMLElement> {\n /** Name of the currently active view. */\n value: string;\n /** Called with the new view name when the user selects an item. */\n onValueChange: (value: string) => void;\n /** Accessible label for the group. Defaults to `\"Views\"`. */\n \"aria-label\"?: string;\n children?: ReactNode;\n}\n\n/**\n * Sidebar-style view switcher for apps with more than 4 top-level views,\n * or when the sidebar layout fits better than a header-bar `ViewSwitcher`.\n *\n * Mirrors `AdwViewSwitcherSidebar` (libadwaita 1.9 / GNOME 50), the modern\n * replacement for `GtkStackSidebar`.\n *\n * Compose with `ViewSwitcherSidebarItem` for each view. The active item is\n * highlighted with the accent colour; keyboard ↑ / ↓ cycles through items.\n *\n * @see https://gnome.pages.gitlab.gnome.org/libadwaita/doc/main/class.ViewSwitcherSidebar.html\n *\n * @example\n * ```tsx\n * <ViewSwitcherSidebar value={view} onValueChange={setView}>\n * <ViewSwitcherSidebarItem name=\"photos\" label=\"Photos\" icon={MediaPlay} />\n * <ViewSwitcherSidebarItem name=\"albums\" label=\"Albums\" icon={Star} count={12} />\n * </ViewSwitcherSidebar>\n * ```\n */\nexport function ViewSwitcherSidebar({\n value,\n onValueChange,\n \"aria-label\": ariaLabel = \"Views\",\n children,\n className,\n ...props\n}: ViewSwitcherSidebarProps) {\n const groupRef = useRef<HTMLUListElement>(null);\n\n function handleKeyDown(e: KeyboardEvent<HTMLElement>) {\n const items = Array.from(\n groupRef.current?.querySelectorAll<HTMLButtonElement>(\n \"[role=radio]:not(:disabled)\",\n ) ?? [],\n );\n const idx = items.findIndex((el) => el === document.activeElement);\n if (idx === -1) return;\n\n let next = idx;\n if (e.key === \"ArrowDown\" || e.key === \"ArrowRight\")\n next = (idx + 1) % items.length;\n else if (e.key === \"ArrowUp\" || e.key === \"ArrowLeft\")\n next = (idx - 1 + items.length) % items.length;\n else if (e.key === \"Home\") next = 0;\n else if (e.key === \"End\") next = items.length - 1;\n else return;\n\n e.preventDefault();\n items[next].focus();\n items[next].click();\n }\n\n return (\n <ViewSwitcherSidebarContext.Provider value={{ value, onValueChange }}>\n <nav\n className={[styles.sidebar, className].filter(Boolean).join(\" \")}\n onKeyDown={handleKeyDown}\n {...props}\n >\n <ul\n ref={groupRef}\n role=\"radiogroup\"\n aria-label={ariaLabel}\n className={styles.list}\n >\n {children}\n </ul>\n </nav>\n </ViewSwitcherSidebarContext.Provider>\n );\n}\n","import type { ButtonHTMLAttributes, ReactNode } from \"react\";\nimport type { IconDefinition } from \"@gnome-ui/icons\";\nimport { Icon } from \"../Icon\";\nimport { useViewSwitcherSidebar } from \"./ViewSwitcherSidebar\";\nimport styles from \"./ViewSwitcherSidebar.module.css\";\n\nexport interface ViewSwitcherSidebarItemProps\n extends ButtonHTMLAttributes<HTMLButtonElement> {\n /** String identifier — matched against the group's `value`. */\n name: string;\n /** View label. */\n label: string;\n /** Icon from `@gnome-ui/icons`. */\n icon?: IconDefinition;\n /**\n * Optional trailing count (unread messages, items…).\n * Rendered as a small badge at the trailing edge.\n */\n count?: number;\n /**\n * Optional trailing widget. Takes precedence over `count`.\n * Use for custom badges, buttons, or status indicators.\n */\n suffix?: ReactNode;\n}\n\n/**\n * Individual view item inside a `ViewSwitcherSidebar`.\n *\n * Renders as `role=\"radio\"`. Only the active item is in the natural Tab order;\n * all others use `tabIndex={-1}` and are reached via arrow-key navigation.\n */\nexport function ViewSwitcherSidebarItem({\n name,\n label,\n icon,\n count,\n suffix,\n disabled,\n className,\n ...props\n}: ViewSwitcherSidebarItemProps) {\n const { value, onValueChange } = useViewSwitcherSidebar();\n const active = value === name;\n const trailing = suffix ?? (count != null ? count : null);\n\n return (\n <li className={styles.item}>\n <button\n type=\"button\"\n role=\"radio\"\n aria-checked={active}\n tabIndex={active ? 0 : -1}\n disabled={disabled}\n onClick={() => onValueChange(name)}\n className={[\n styles.itemBtn,\n active ? styles.active : null,\n className,\n ]\n .filter(Boolean)\n .join(\" \")}\n {...props}\n >\n {icon && (\n <span className={styles.itemIcon}>\n <Icon icon={icon} size=\"md\" aria-hidden />\n </span>\n )}\n <span className={styles.itemLabel}>{label}</span>\n {trailing != null && (\n <span className={styles.itemSuffix}>\n {typeof trailing === \"number\" ? (\n <span className={styles.count}>{trailing > 99 ? \"99+\" : trailing}</span>\n ) : (\n trailing\n )}\n </span>\n )}\n </button>\n </li>\n );\n}\n","import {\n useState,\n useEffect,\n useRef,\n type HTMLAttributes,\n type ReactNode,\n type CSSProperties,\n} from \"react\";\n\n// ─── Public types ─────────────────────────────────────────────────────────────\n\nexport interface BreakpointDefinition {\n /**\n * Identifier for this breakpoint, e.g. `\"compact\"` or `\"narrow\"`.\n * Exposed as `data-breakpoint` on the container element for CSS targeting.\n */\n name: string;\n /**\n * Container width threshold in pixels.\n * This breakpoint becomes active when the container width is ≤ this value.\n */\n maxWidth: number;\n}\n\nexport interface BreakpointBinState {\n /**\n * Name of the currently active breakpoint, or `null` when the container\n * is wider than all defined breakpoints.\n */\n activeBreakpoint: string | null;\n /** Current container width in px (from `ResizeObserver`). */\n width: number;\n}\n\nexport interface BreakpointBinProps\n extends Omit<HTMLAttributes<HTMLDivElement>, \"children\"> {\n /**\n * Breakpoint definitions sorted by `maxWidth` ascending.\n * The active breakpoint is the smallest `maxWidth` ≥ the container's current width.\n */\n breakpoints: BreakpointDefinition[];\n /**\n * Render prop that receives the current breakpoint state.\n *\n * @example\n * ```tsx\n * <BreakpointBin breakpoints={[{ name: \"compact\", maxWidth: 400 }]}>\n * {({ activeBreakpoint }) =>\n * activeBreakpoint === \"compact\" ? <CompactLayout /> : <WideLayout />\n * }\n * </BreakpointBin>\n * ```\n */\n children: (state: BreakpointBinState) => ReactNode;\n}\n\n// ─── Component ────────────────────────────────────────────────────────────────\n\n/**\n * Container that fires layout changes when **its own width** crosses defined\n * thresholds — the CSS container-query equivalent of `AdwBreakpointBin`\n * (libadwaita 1.9 / GNOME 50).\n *\n * Unlike `useBreakpoint` (which watches the viewport), `BreakpointBin` uses\n * `ResizeObserver` to watch the element itself. This makes it composable:\n * the same component can behave differently depending on how much space it\n * is given by its parent, regardless of the viewport size.\n *\n * ### How it works\n * - Breakpoints are evaluated in ascending `maxWidth` order.\n * - The active breakpoint is the first one whose `maxWidth` ≥ the container width.\n * - When no breakpoint matches (container is wider than all), `activeBreakpoint` is `null`.\n * - The active name is set as `data-breakpoint` on the wrapper `<div>` for CSS targeting.\n *\n * ### CSS targeting\n * ```css\n * .myWidget[data-breakpoint=\"compact\"] .layout { flex-direction: column; }\n * ```\n *\n * @see https://gnome.pages.gitlab.gnome.org/libadwaita/doc/main/class.BreakpointBin.html\n */\nexport function BreakpointBin({\n breakpoints,\n children,\n className,\n style,\n ...props\n}: BreakpointBinProps) {\n const containerRef = useRef<HTMLDivElement>(null);\n const [state, setState] = useState<BreakpointBinState>({\n activeBreakpoint: null,\n width: 0,\n });\n\n useEffect(() => {\n const el = containerRef.current;\n if (!el) return;\n\n // Sort breakpoints ascending so we find the smallest matching threshold\n const sorted = [...breakpoints].sort((a, b) => a.maxWidth - b.maxWidth);\n\n const evaluate = (width: number): BreakpointBinState => {\n const match = sorted.find((bp) => width <= bp.maxWidth) ?? null;\n return { activeBreakpoint: match?.name ?? null, width };\n };\n\n // Initial measurement\n setState(evaluate(el.offsetWidth));\n\n const observer = new ResizeObserver((entries) => {\n const entry = entries[0];\n if (!entry) return;\n // contentBoxSize is more reliable than contentRect for inline-size\n const width =\n entry.contentBoxSize?.[0]?.inlineSize ?? entry.contentRect.width;\n setState(evaluate(width));\n });\n\n observer.observe(el);\n return () => observer.disconnect();\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [JSON.stringify(breakpoints)]);\n\n const containerStyle: CSSProperties = {\n // Ensure the container establishes a containing block\n // so ResizeObserver reflects actual layout width\n minWidth: 0,\n ...style,\n };\n\n return (\n <div\n ref={containerRef}\n className={className}\n style={containerStyle}\n data-breakpoint={state.activeBreakpoint ?? undefined}\n {...props}\n >\n {children(state)}\n </div>\n );\n}\n","/* ─── Base row ─────────────────────────────────────────────────────────────── */\n\n.row {\n display: flex;\n align-items: center;\n justify-content: center;\n gap: var(--gnome-space-2, 12px);\n padding: var(--gnome-space-2, 12px) var(--gnome-space-4, 24px);\n min-height: 52px;\n width: 100%;\n\n /* Reset button */\n appearance: none;\n -webkit-appearance: none;\n background: transparent;\n border: none;\n font: inherit;\n outline: none;\n cursor: pointer;\n text-align: center;\n\n transition: background-color var(--gnome-duration-fast) var(--gnome-easing-default);\n}\n\n.row:hover {\n background-color: var(--gnome-hover-overlay);\n}\n\n.row:active {\n background-color: var(--gnome-active-overlay);\n}\n\n.row:focus-visible {\n box-shadow:\n inset 0 0 0 var(--gnome-focus-ring-offset, 2px) var(--gnome-window-bg-color, #fafafa),\n inset 0 0 0 calc(var(--gnome-focus-ring-offset, 2px) + var(--gnome-focus-ring-width, 3px))\n var(--gnome-focus-ring-color, #3584e4);\n}\n\n.row:disabled {\n opacity: var(--gnome-opacity-disabled, 0.5);\n cursor: not-allowed;\n pointer-events: none;\n}\n\n/* ─── Title ─────────────────────────────────────────────────────────────────── */\n\n.title {\n font-family: var(--gnome-font-family);\n font-size: var(--gnome-font-size-body, 1rem);\n font-weight: var(--gnome-font-weight-semibold, 600);\n line-height: var(--gnome-line-height-body, 1.5);\n flex: 1;\n text-align: center;\n}\n\n/* ─── Leading / trailing icons ─────────────────────────────────────────────── */\n\n.leading,\n.trailing {\n display: flex;\n align-items: center;\n flex-shrink: 0;\n}\n\n/* ─── Variants ─────────────────────────────────────────────────────────────── */\n\n.default .title {\n color: var(--gnome-window-fg-color, rgba(0, 0, 0, 0.8));\n}\n\n.default .leading,\n.default .trailing {\n color: var(--gnome-window-fg-color, rgba(0, 0, 0, 0.8));\n}\n\n.suggested .title {\n color: var(--gnome-accent-color, #3584e4);\n}\n\n.suggested .leading,\n.suggested .trailing {\n color: var(--gnome-accent-color, #3584e4);\n}\n\n.destructive .title {\n color: var(--gnome-destructive-color, #e01b24);\n}\n\n.destructive .leading,\n.destructive .trailing {\n color: var(--gnome-destructive-color, #e01b24);\n}\n\n/* ─── High contrast ─────────────────────────────────────────────────────────── */\n\n@media (prefers-contrast: more) {\n .row:focus-visible {\n box-shadow:\n inset 0 0 0 var(--gnome-focus-ring-offset, 2px) var(--gnome-window-bg-color, #fafafa),\n inset 0 0 0 calc(var(--gnome-focus-ring-offset, 2px) + var(--gnome-focus-ring-width, 3px))\n var(--gnome-focus-ring-color, #3584e4),\n inset 0 0 0 calc(var(--gnome-focus-ring-offset, 2px) + var(--gnome-focus-ring-width, 3px) + 1px)\n var(--gnome-window-bg-color, #fafafa);\n }\n}\n","import type { ButtonHTMLAttributes, ReactNode } from \"react\";\nimport styles from \"./ButtonRow.module.css\";\n\nexport type ButtonRowVariant = \"default\" | \"suggested\" | \"destructive\";\n\nexport interface ButtonRowProps extends ButtonHTMLAttributes<HTMLButtonElement> {\n /** Label displayed centered in the row. */\n title: string;\n /** Visual style that colours the title text. */\n variant?: ButtonRowVariant;\n /** Icon placed at the leading edge. */\n leading?: ReactNode;\n /** Icon placed at the trailing edge. */\n trailing?: ReactNode;\n}\n\n/**\n * Full-width activatable row styled as a button inside a `BoxedList`.\n *\n * Mirrors `AdwButtonRow` — use when an entire list row should trigger a single\n * action. Prefer `ActionRow` with `interactive` when the row also needs a\n * title/subtitle layout; prefer `ButtonRow` for single-label CTA rows.\n *\n * @see https://gnome.pages.gitlab.gnome.org/libadwaita/doc/main/class.ButtonRow.html\n */\nexport function ButtonRow({\n title,\n variant = \"default\",\n leading,\n trailing,\n className,\n ...props\n}: ButtonRowProps) {\n const classes = [\n styles.row,\n styles[variant],\n className,\n ]\n .filter(Boolean)\n .join(\" \");\n\n return (\n <button className={classes} {...props}>\n {leading && <span className={styles.leading}>{leading}</span>}\n <span className={styles.title}>{title}</span>\n {trailing && <span className={styles.trailing}>{trailing}</span>}\n </button>\n );\n}\n","/* ─── Container ─────────────────────────────────────────────────────────────── */\n\n.container {\n display: inline-flex;\n align-items: stretch;\n border-radius: var(--gnome-radius-md);\n border: 1px solid transparent;\n overflow: visible;\n position: relative;\n}\n\n/* ─── Shared button base ────────────────────────────────────────────────────── */\n\n.primary,\n.toggle {\n appearance: none;\n -webkit-appearance: none;\n border: none;\n outline: none;\n font: inherit;\n cursor: pointer;\n user-select: none;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n\n font-family: var(--gnome-font-family);\n font-size: var(--gnome-font-size-body);\n font-weight: var(--gnome-font-weight-semibold);\n line-height: 1;\n white-space: nowrap;\n\n min-height: 34px;\n\n transition:\n background-color var(--gnome-duration-fast) var(--gnome-easing-default),\n box-shadow var(--gnome-duration-fast) var(--gnome-easing-default);\n}\n\n.primary:focus-visible,\n.toggle:focus-visible {\n box-shadow:\n 0 0 0 var(--gnome-focus-ring-offset) var(--gnome-window-bg-color),\n 0 0 0 calc(var(--gnome-focus-ring-offset) + var(--gnome-focus-ring-width)) var(--gnome-focus-ring-color);\n position: relative;\n z-index: 1;\n}\n\n.primary:disabled,\n.toggle:disabled {\n opacity: var(--gnome-opacity-disabled);\n cursor: not-allowed;\n pointer-events: none;\n}\n\n/* ─── Primary half ──────────────────────────────────────────────────────────── */\n\n.primary {\n border-radius: var(--gnome-radius-md) 0 0 var(--gnome-radius-md);\n padding: var(--gnome-space-1) var(--gnome-space-2);\n background: transparent;\n}\n\n/* ─── Separator ─────────────────────────────────────────────────────────────── */\n\n.separator {\n display: block;\n width: 1px;\n align-self: stretch;\n flex-shrink: 0;\n}\n\n/* ─── Toggle (arrow) half ───────────────────────────────────────────────────── */\n\n.toggle {\n border-radius: 0 var(--gnome-radius-md) var(--gnome-radius-md) 0;\n padding: var(--gnome-space-1) 8px;\n background: transparent;\n}\n\n.chevron {\n display: block;\n flex-shrink: 0;\n}\n\n/* ─── Variant: default ──────────────────────────────────────────────────────── */\n\n.default {\n background-color: var(--gnome-card-bg-color, #ffffff);\n border-color: var(--gnome-light-3, #deddda);\n box-shadow: var(--gnome-shadow-sm);\n color: var(--gnome-window-fg-color, rgba(0, 0, 0, 0.8));\n}\n\n.default .primary:hover:not(:disabled),\n.default .toggle:hover:not(:disabled) {\n background-color: var(--gnome-hover-overlay);\n}\n\n.default .primary:active:not(:disabled),\n.default .toggle:active:not(:disabled) {\n background-color: var(--gnome-active-overlay);\n}\n\n.default .separator {\n background-color: var(--gnome-light-3, #deddda);\n}\n\n@media (prefers-color-scheme: dark) {\n .default {\n background-color: var(--gnome-card-bg-color, #383838);\n border-color: rgba(255, 255, 255, 0.12);\n box-shadow: none;\n }\n\n .default .separator {\n background-color: rgba(255, 255, 255, 0.12);\n }\n}\n\n/* ─── Variant: suggested ────────────────────────────────────────────────────── */\n\n.suggested {\n background-color: var(--gnome-accent-bg-color);\n border-color: transparent;\n color: var(--gnome-accent-fg-color);\n}\n\n.suggested .primary:hover:not(:disabled),\n.suggested .toggle:hover:not(:disabled) {\n filter: brightness(1.08);\n}\n\n.suggested .primary:active:not(:disabled),\n.suggested .toggle:active:not(:disabled) {\n filter: brightness(0.92);\n}\n\n.suggested .separator {\n background-color: rgba(255, 255, 255, 0.25);\n}\n\n/* ─── Variant: destructive ──────────────────────────────────────────────────── */\n\n.destructive {\n background-color: var(--gnome-destructive-bg-color);\n border-color: transparent;\n color: var(--gnome-destructive-fg-color);\n}\n\n.destructive .primary:hover:not(:disabled),\n.destructive .toggle:hover:not(:disabled) {\n filter: brightness(1.08);\n}\n\n.destructive .primary:active:not(:disabled),\n.destructive .toggle:active:not(:disabled) {\n filter: brightness(0.92);\n}\n\n.destructive .separator {\n background-color: rgba(255, 255, 255, 0.25);\n}\n\n/* ─── Dropdown panel ────────────────────────────────────────────────────────── */\n\n.dropdown {\n position: fixed;\n z-index: var(--gnome-z-popover, 1000);\n min-width: 160px;\n\n background-color: var(--gnome-popover-bg-color, #ffffff);\n border: 1px solid var(--gnome-popover-border-color, rgba(0, 0, 0, 0.12));\n border-radius: var(--gnome-radius-md);\n box-shadow: var(--gnome-shadow-md, 0 4px 16px rgba(0, 0, 0, 0.18));\n padding: var(--gnome-space-1, 6px);\n\n opacity: 0;\n transform: translateY(-4px);\n transition:\n opacity var(--gnome-duration-fast) var(--gnome-easing-default),\n transform var(--gnome-duration-fast) var(--gnome-easing-default);\n}\n\n.dropdownVisible {\n opacity: 1;\n transform: translateY(0);\n}\n\n@media (prefers-color-scheme: dark) {\n .dropdown {\n background-color: var(--gnome-popover-bg-color, #2d2d2d);\n border-color: rgba(255, 255, 255, 0.1);\n }\n}\n\n/* ─── High contrast ─────────────────────────────────────────────────────────── */\n\n@media (prefers-contrast: more) {\n .default {\n border-width: 2px;\n box-shadow: none;\n }\n\n .primary:focus-visible,\n .toggle:focus-visible {\n box-shadow:\n 0 0 0 var(--gnome-focus-ring-offset) var(--gnome-window-bg-color),\n 0 0 0 calc(var(--gnome-focus-ring-offset) + var(--gnome-focus-ring-width)) var(--gnome-focus-ring-color),\n 0 0 0 calc(var(--gnome-focus-ring-offset) + var(--gnome-focus-ring-width) + 1px) var(--gnome-window-bg-color);\n }\n}\n","import {\n useState,\n useRef,\n useEffect,\n useCallback,\n type ButtonHTMLAttributes,\n type ReactNode,\n type KeyboardEvent,\n} from \"react\";\nimport { createPortal } from \"react-dom\";\nimport styles from \"./SplitButton.module.css\";\n\nexport type SplitButtonVariant = \"default\" | \"suggested\" | \"destructive\";\n\nexport interface SplitButtonProps\n extends Omit<ButtonHTMLAttributes<HTMLButtonElement>, \"children\"> {\n /** Label shown in the primary button. */\n label: string;\n /** Visual style. Applies to both the primary and toggle halves. */\n variant?: SplitButtonVariant;\n /** Content rendered inside the dropdown panel when the arrow is clicked. */\n dropdownContent: ReactNode;\n /** Accessible label for the dropdown toggle button. Defaults to \"More options\". */\n dropdownLabel?: string;\n /** Whether both halves are disabled. */\n disabled?: boolean;\n}\n\n/**\n * Primary action button with an attached dropdown arrow.\n *\n * Clicking the label half fires `onClick`; clicking the arrow half opens a\n * floating panel with `dropdownContent` (menus, options, etc.).\n *\n * Mirrors `AdwSplitButton` — supports `suggested` and `destructive` variants.\n *\n * @see https://gnome.pages.gitlab.gnome.org/libadwaita/doc/main/class.SplitButton.html\n */\nexport function SplitButton({\n label,\n variant = \"default\",\n dropdownContent,\n dropdownLabel = \"More options\",\n disabled = false,\n onClick,\n className,\n ...props\n}: SplitButtonProps) {\n const [open, setOpen] = useState(false);\n const [dropdownStyle, setDropdownStyle] = useState<React.CSSProperties>({});\n\n const containerRef = useRef<HTMLDivElement>(null);\n const toggleRef = useRef<HTMLButtonElement>(null);\n const panelRef = useRef<HTMLDivElement>(null);\n\n const close = useCallback(() => setOpen(false), []);\n\n // Position dropdown below the toggle button\n const place = useCallback(() => {\n if (!toggleRef.current || !panelRef.current) return;\n const triggerRect = toggleRef.current.getBoundingClientRect();\n const panelRect = panelRef.current.getBoundingClientRect();\n const vw = window.innerWidth;\n\n let left = triggerRect.right - panelRect.width;\n if (left < 8) left = 8;\n if (left + panelRect.width > vw - 8) left = vw - panelRect.width - 8;\n\n setDropdownStyle({\n top: triggerRect.bottom + 6,\n left,\n });\n }, []);\n\n useEffect(() => {\n if (!open) return;\n const id = requestAnimationFrame(() => {\n place();\n window.addEventListener(\"scroll\", place, { passive: true, capture: true });\n window.addEventListener(\"resize\", place, { passive: true });\n });\n return () => {\n cancelAnimationFrame(id);\n window.removeEventListener(\"scroll\", place, { capture: true });\n window.removeEventListener(\"resize\", place);\n };\n }, [open, place]);\n\n // Close on outside click\n useEffect(() => {\n if (!open) return;\n const handler = (e: MouseEvent) => {\n if (\n !containerRef.current?.contains(e.target as Node) &&\n !panelRef.current?.contains(e.target as Node)\n ) {\n close();\n }\n };\n document.addEventListener(\"mousedown\", handler);\n return () => document.removeEventListener(\"mousedown\", handler);\n }, [open, close]);\n\n const handlePanelKeyDown = (e: KeyboardEvent<HTMLDivElement>) => {\n if (e.key === \"Escape\") {\n e.stopPropagation();\n close();\n toggleRef.current?.focus();\n }\n };\n\n const containerClass = [\n styles.container,\n styles[variant],\n className,\n ]\n .filter(Boolean)\n .join(\" \");\n\n const panel = (\n <div\n ref={panelRef}\n role=\"dialog\"\n aria-label={dropdownLabel}\n tabIndex={-1}\n className={[styles.dropdown, open ? styles.dropdownVisible : null]\n .filter(Boolean)\n .join(\" \")}\n style={\n Object.keys(dropdownStyle).length\n ? { ...dropdownStyle, position: \"fixed\" }\n : { visibility: \"hidden\", pointerEvents: \"none\", top: -9999, left: -9999, position: \"fixed\" }\n }\n onKeyDown={handlePanelKeyDown}\n >\n {dropdownContent}\n </div>\n );\n\n return (\n <>\n <div ref={containerRef} className={containerClass}>\n <button\n className={styles.primary}\n disabled={disabled}\n onClick={onClick}\n {...props}\n >\n {label}\n </button>\n\n <span className={styles.separator} aria-hidden=\"true\" />\n\n <button\n ref={toggleRef}\n className={styles.toggle}\n disabled={disabled}\n aria-label={dropdownLabel}\n aria-haspopup=\"dialog\"\n aria-expanded={open}\n onClick={() => setOpen((v) => !v)}\n >\n <svg\n className={styles.chevron}\n width=\"16\"\n height=\"16\"\n viewBox=\"0 0 16 16\"\n aria-hidden=\"true\"\n focusable=\"false\"\n >\n <path\n d=\"M4 6l4 4 4-4\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth=\"1.5\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n />\n </svg>\n </button>\n </div>\n\n {open &&\n (typeof document !== \"undefined\"\n ? createPortal(panel, document.body)\n : panel)}\n </>\n );\n}\n","/* ─── Toolbar ─────────────────────────────────────────────────────────────────\n Mirrors the libadwaita `.toolbar` style class:\n horizontal flex row, 6 px padding on all sides, 6 px gap between children.\n ─────────────────────────────────────────────────────────────────────────── */\n\n.toolbar {\n display: flex;\n flex-direction: row;\n align-items: center;\n flex-wrap: nowrap;\n gap: var(--gnome-space-1, 6px);\n padding: var(--gnome-space-1, 6px);\n min-height: 46px;\n\n color: var(--gnome-window-fg-color, rgba(0, 0, 0, 0.8));\n font-family: var(--gnome-font-family);\n}\n\n/* ─── Spacer ──────────────────────────────────────────────────────────────────\n Invisible flex filler — mirrors `GtkSeparator.spacer`.\n ─────────────────────────────────────────────────────────────────────────── */\n\n.spacer {\n flex: 1;\n min-width: 0;\n}\n","import type { HTMLAttributes, ReactNode } from \"react\";\nimport styles from \"./Toolbar.module.css\";\n\nexport interface ToolbarProps extends HTMLAttributes<HTMLDivElement> {\n children: ReactNode;\n}\n\n/**\n * Horizontal action bar following the libadwaita `.toolbar` pattern.\n *\n * Provides 6 px padding and gap — the standard spacing for rows of flat\n * buttons in header bars, action bars, and tool rows. Place `<Spacer />`\n * between leading and trailing groups to push trailing items to the end.\n *\n * Use `<Button variant=\"flat\">` or `<Button variant=\"raised\">` inside:\n * - `flat` for buttons that blend into the bar background.\n * - `raised` for buttons that need explicit elevation within a flat context.\n *\n * @see https://gnome.pages.gitlab.gnome.org/libadwaita/doc/1-latest/style-classes.html#toolbar-style-class\n */\nexport function Toolbar({ children, className, ...props }: ToolbarProps) {\n return (\n <div\n className={[styles.toolbar, className].filter(Boolean).join(\" \")}\n {...props}\n >\n {children}\n </div>\n );\n}\n","import type { HTMLAttributes } from \"react\";\nimport styles from \"./Toolbar.module.css\";\n\nexport interface SpacerProps extends HTMLAttributes<HTMLDivElement> {}\n\n/**\n * Invisible `flex: 1` filler for `Toolbar` and `HeaderBar`.\n *\n * Place between leading and trailing groups to push trailing items to the end.\n * Mirrors `GtkSeparator` with the `.spacer` style class.\n *\n * @see https://gnome.pages.gitlab.gnome.org/libadwaita/doc/1-latest/style-classes.html#spacer\n *\n * @example\n * <Toolbar>\n * <Button variant=\"flat\">Back</Button>\n * <Spacer />\n * <Button variant=\"flat\">Done</Button>\n * </Toolbar>\n */\nexport function Spacer({ className, ...props }: SpacerProps) {\n return (\n <div\n aria-hidden=\"true\"\n className={[styles.spacer, className].filter(Boolean).join(\" \")}\n {...props}\n />\n );\n}\n","/* ─── Container ─────────────────────────────────────────────────────────────── */\n\n.linked {\n display: inline-flex;\n flex-direction: row;\n align-items: stretch;\n}\n\n.vertical {\n flex-direction: column;\n}\n\n/* ─── Strip inner border-radius from all children ───────────────────────────── */\n\n.linked > :global(*) {\n border-radius: 0 !important;\n flex-shrink: 0;\n}\n\n/* ─── Collapse shared borders (horizontal) ──────────────────────────────────── */\n\n.linked:not(.vertical) > :global(*:not(:first-child)) {\n margin-left: -1px;\n}\n\n/* ─── Collapse shared borders (vertical) ───────────────────────────────────── */\n\n.linked.vertical > :global(*:not(:first-child)) {\n margin-top: -1px;\n}\n\n/* ─── Restore radius — first child ─────────────────────────────────────────── */\n\n.linked:not(.vertical) > :global(*:first-child) {\n border-top-left-radius: var(--gnome-radius-md) !important;\n border-bottom-left-radius: var(--gnome-radius-md) !important;\n}\n\n.linked.vertical > :global(*:first-child) {\n border-top-left-radius: var(--gnome-radius-md) !important;\n border-top-right-radius: var(--gnome-radius-md) !important;\n}\n\n/* ─── Restore radius — last child ──────────────────────────────────────────── */\n\n.linked:not(.vertical) > :global(*:last-child) {\n border-top-right-radius: var(--gnome-radius-md) !important;\n border-bottom-right-radius: var(--gnome-radius-md) !important;\n}\n\n.linked.vertical > :global(*:last-child) {\n border-bottom-left-radius: var(--gnome-radius-md) !important;\n border-bottom-right-radius: var(--gnome-radius-md) !important;\n}\n\n/* ─── Bring hovered / focused child to the front so its border shows ────────── */\n\n.linked > :global(*:hover),\n.linked > :global(*:focus-visible) {\n position: relative;\n z-index: 1;\n}\n","import type { HTMLAttributes, ReactNode } from \"react\";\nimport styles from \"./LinkedGroup.module.css\";\n\nexport interface LinkedGroupProps extends HTMLAttributes<HTMLDivElement> {\n children: ReactNode;\n /**\n * Stack children vertically instead of horizontally.\n * Useful for stacked input or button groups.\n */\n vertical?: boolean;\n}\n\n/**\n * Renders children as a single visually-connected unit with no gap and\n * merged borders — the canonical GNOME pattern for button groups and\n * segmented inputs.\n *\n * Mirrors the libadwaita `.linked` style class.\n *\n * - First child gets left (or top) border radius.\n * - Last child gets right (or bottom) border radius.\n * - Adjacent borders are collapsed to a single pixel.\n * - Hover and focus bring the active child to the front via `z-index`.\n *\n * @see https://gnome.pages.gitlab.gnome.org/libadwaita/doc/1-latest/style-classes.html#linked-style-class\n *\n * @example\n * <LinkedGroup>\n * <Button>Cut</Button>\n * <Button>Copy</Button>\n * <Button>Paste</Button>\n * </LinkedGroup>\n */\nexport function LinkedGroup({\n children,\n vertical = false,\n className,\n ...props\n}: LinkedGroupProps) {\n const classes = [\n styles.linked,\n vertical ? styles.vertical : null,\n className,\n ]\n .filter(Boolean)\n .join(\" \");\n\n return (\n <div className={classes} {...props}>\n {children}\n </div>\n );\n}\n","/* ─── Frame ───────────────────────────────────────────────────────────────────\n Bordered surface without background fill — mirrors GtkFrame / .frame.\n ─────────────────────────────────────────────────────────────────────────── */\n\n.frame {\n border: 1px solid var(--gnome-borders, rgba(0, 0, 0, 0.12));\n border-radius: var(--gnome-radius-md);\n overflow: hidden;\n}\n\n@media (prefers-color-scheme: dark) {\n .frame {\n border-color: var(--gnome-borders, rgba(255, 255, 255, 0.12));\n }\n}\n\n@media (prefers-contrast: more) {\n .frame {\n border-width: 2px;\n }\n}\n","import type { HTMLAttributes, ReactNode } from \"react\";\nimport styles from \"./Frame.module.css\";\n\nexport interface FrameProps extends HTMLAttributes<HTMLDivElement> {\n children?: ReactNode;\n}\n\n/**\n * Simple bordered surface with border-radius but no background fill.\n *\n * Use to visually delimit a region without adding the elevated appearance\n * of a `Card`. Mirrors `GtkFrame` default styling and the libadwaita\n * `.frame` style class.\n *\n * @see https://gnome.pages.gitlab.gnome.org/libadwaita/doc/1-latest/style-classes.html#frame-style-class\n */\nexport function Frame({ children, className, ...props }: FrameProps) {\n return (\n <div\n className={[styles.frame, className].filter(Boolean).join(\" \")}\n {...props}\n >\n {children}\n </div>\n );\n}\n","/* ─── Wrapper ───────────────────────────────────────────────────────────────── */\n\n.expanderRow {\n display: flex;\n flex-direction: column;\n width: 100%;\n}\n\n/* ─── Header button ─────────────────────────────────────────────────────────── */\n\n.header {\n display: flex;\n align-items: center;\n gap: var(--gnome-space-2, 12px);\n padding: var(--gnome-space-2, 12px) var(--gnome-space-4, 24px);\n min-height: 52px;\n width: 100%;\n\n appearance: none;\n -webkit-appearance: none;\n background: transparent;\n border: none;\n font: inherit;\n outline: none;\n cursor: pointer;\n text-align: start;\n color: var(--gnome-card-fg-color, rgba(0, 0, 0, 0.8));\n\n transition: background-color var(--gnome-duration-fast) var(--gnome-easing-default);\n}\n\n.header:hover {\n background-color: var(--gnome-hover-overlay);\n}\n\n.header:active {\n background-color: var(--gnome-active-overlay);\n}\n\n.header:focus-visible {\n box-shadow:\n inset 0 0 0 var(--gnome-focus-ring-offset, 2px) var(--gnome-window-bg-color, #fafafa),\n inset 0 0 0 calc(var(--gnome-focus-ring-offset, 2px) + var(--gnome-focus-ring-width, 3px))\n var(--gnome-focus-ring-color, #3584e4);\n}\n\n.header:disabled {\n opacity: var(--gnome-opacity-disabled, 0.5);\n cursor: not-allowed;\n pointer-events: none;\n}\n\n/* ─── Leading ───────────────────────────────────────────────────────────────── */\n\n.leading {\n display: flex;\n align-items: center;\n flex-shrink: 0;\n color: var(--gnome-window-fg-color, rgba(0, 0, 0, 0.8));\n}\n\n/* ─── Text content ──────────────────────────────────────────────────────────── */\n\n.content {\n display: flex;\n flex-direction: column;\n flex: 1;\n min-width: 0;\n gap: 2px;\n}\n\n.title {\n font-family: var(--gnome-font-family);\n font-size: var(--gnome-font-size-body, 1rem);\n font-weight: var(--gnome-font-weight-normal, 400);\n line-height: var(--gnome-line-height-body, 1.5);\n color: var(--gnome-card-fg-color, rgba(0, 0, 0, 0.8));\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n}\n\n.subtitle {\n font-family: var(--gnome-font-family);\n font-size: var(--gnome-font-size-caption, 0.75rem);\n font-weight: var(--gnome-font-weight-normal, 400);\n line-height: var(--gnome-line-height-body, 1.5);\n color: var(--gnome-window-fg-color, rgba(0, 0, 0, 0.55));\n opacity: var(--gnome-opacity-dim, 0.55);\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n}\n\n/* ─── Trailing ──────────────────────────────────────────────────────────────── */\n\n.trailing {\n display: flex;\n align-items: center;\n flex-shrink: 0;\n gap: var(--gnome-space-1, 6px);\n}\n\n/* ─── Chevron ───────────────────────────────────────────────────────────────── */\n\n.chevronWrap {\n display: flex;\n align-items: center;\n flex-shrink: 0;\n color: var(--gnome-window-fg-color, rgba(0, 0, 0, 0.55));\n opacity: 0.55;\n transition: transform var(--gnome-duration-fast) var(--gnome-easing-default);\n}\n\n.chevronOpen {\n transform: rotate(180deg);\n}\n\n.chevron {\n display: block;\n}\n\n/* ─── Reveal panel (CSS grid height animation) ──────────────────────────────── */\n\n.panel {\n display: grid;\n grid-template-rows: 0fr;\n transition: grid-template-rows var(--gnome-duration-normal, 200ms) var(--gnome-easing-default);\n}\n\n.expanded .panel {\n grid-template-rows: 1fr;\n}\n\n.panelInner {\n overflow: hidden;\n min-height: 0;\n}\n\n/* ─── Child row wrapper + divider ───────────────────────────────────────────── */\n\n.childItem {\n display: flex;\n flex-direction: column;\n}\n\n.divider {\n height: 1px;\n background-color: var(--gnome-divider-color, rgba(0, 0, 0, 0.08));\n margin-left: var(--gnome-space-4, 24px);\n}\n\n/* ─── High contrast ─────────────────────────────────────────────────────────── */\n\n@media (prefers-contrast: more) {\n .header:focus-visible {\n box-shadow:\n inset 0 0 0 var(--gnome-focus-ring-offset, 2px) var(--gnome-window-bg-color, #fafafa),\n inset 0 0 0 calc(var(--gnome-focus-ring-offset, 2px) + var(--gnome-focus-ring-width, 3px))\n var(--gnome-focus-ring-color, #3584e4),\n inset 0 0 0 calc(var(--gnome-focus-ring-offset, 2px) + var(--gnome-focus-ring-width, 3px) + 1px)\n var(--gnome-window-bg-color, #fafafa);\n }\n\n .divider {\n height: 2px;\n }\n}\n","import { useState, Children, useId, type HTMLAttributes, type ReactNode } from \"react\";\nimport styles from \"./ExpanderRow.module.css\";\n\nexport interface ExpanderRowProps extends HTMLAttributes<HTMLDivElement> {\n /** Primary label. */\n title: string;\n /** Secondary line below the title. */\n subtitle?: string;\n /** Icon or image placed at the leading edge of the header row. */\n leading?: ReactNode;\n /**\n * Widget placed at the trailing edge of the header row, before the chevron\n * (e.g. a value label or a Switch). Stop event propagation inside it so the\n * row's toggle isn't triggered.\n */\n trailing?: ReactNode;\n /**\n * Nested rows revealed when expanded. Use `ActionRow`, `ButtonRow`, or any\n * row-shaped element — separators are inserted automatically.\n */\n children?: ReactNode;\n /** Controlled expanded state. */\n expanded?: boolean;\n /** Initial expanded state when uncontrolled. Defaults to `false`. */\n defaultExpanded?: boolean;\n /** Called when the expanded state changes. */\n onExpandedChange?: (expanded: boolean) => void;\n}\n\n/**\n * Collapsible `ActionRow` that reveals nested rows on activation.\n *\n * Mirrors `AdwExpanderRow` — the header row toggles a reveal animation exposing\n * child rows. Supports both controlled and uncontrolled expand state.\n *\n * @see https://gnome.pages.gitlab.gnome.org/libadwaita/doc/main/class.ExpanderRow.html\n */\nexport function ExpanderRow({\n title,\n subtitle,\n leading,\n trailing,\n children,\n expanded: controlledExpanded,\n defaultExpanded = false,\n onExpandedChange,\n className,\n ...props\n}: ExpanderRowProps) {\n const isControlled = controlledExpanded !== undefined;\n const [uncontrolledExpanded, setUncontrolledExpanded] = useState(defaultExpanded);\n const expanded = isControlled ? controlledExpanded : uncontrolledExpanded;\n\n const panelId = useId();\n const headerId = useId();\n\n const toggle = () => {\n const next = !expanded;\n if (!isControlled) setUncontrolledExpanded(next);\n onExpandedChange?.(next);\n };\n\n const childItems = Children.toArray(children).filter(Boolean);\n\n return (\n <div\n className={[styles.expanderRow, expanded ? styles.expanded : null, className]\n .filter(Boolean)\n .join(\" \")}\n {...props}\n >\n {/* ─── Header (toggle button) ─────────────────────────────────────── */}\n <button\n id={headerId}\n className={styles.header}\n aria-expanded={expanded}\n aria-controls={panelId}\n onClick={toggle}\n >\n {leading && <span className={styles.leading}>{leading}</span>}\n\n <span className={styles.content}>\n <span className={styles.title}>{title}</span>\n {subtitle && <span className={styles.subtitle}>{subtitle}</span>}\n </span>\n\n {trailing && <span className={styles.trailing}>{trailing}</span>}\n\n <span className={[styles.chevronWrap, expanded ? styles.chevronOpen : null].filter(Boolean).join(\" \")} aria-hidden=\"true\">\n <svg className={styles.chevron} width=\"16\" height=\"16\" viewBox=\"0 0 16 16\" focusable=\"false\">\n <path\n d=\"M4 6l4 4 4-4\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth=\"1.5\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n />\n </svg>\n </span>\n </button>\n\n {/* ─── Reveal panel ───────────────────────────────────────────────── */}\n <div\n id={panelId}\n role=\"region\"\n aria-labelledby={headerId}\n className={styles.panel}\n >\n <div className={styles.panelInner}>\n {childItems.map((child, i) => (\n <div key={i} className={styles.childItem}>\n <div className={styles.divider} aria-hidden=\"true\" />\n {child}\n </div>\n ))}\n </div>\n </div>\n </div>\n );\n}\n","/* ─── Switcher container ────────────────────────────────────────────────────── */\n\n.switcher {\n display: inline-flex;\n align-items: stretch;\n}\n\n/* ─── Item base ─────────────────────────────────────────────────────────────── */\n\n.item {\n appearance: none;\n -webkit-appearance: none;\n border: none;\n background: transparent;\n font: inherit;\n cursor: pointer;\n outline: none;\n\n display: inline-flex;\n align-items: center;\n justify-content: center;\n gap: var(--gnome-space-1, 6px);\n\n padding: 4px var(--gnome-space-2, 12px);\n min-height: 28px;\n\n font-family: var(--gnome-font-family);\n font-size: var(--gnome-font-size-body, 1rem);\n font-weight: var(--gnome-font-weight-normal, 400);\n color: var(--gnome-window-fg-color, rgba(0, 0, 0, 0.8));\n white-space: nowrap;\n\n transition:\n background-color var(--gnome-duration-fast) var(--gnome-easing-default),\n box-shadow var(--gnome-duration-fast) var(--gnome-easing-default);\n}\n\n.iconOnly {\n padding: 4px;\n min-width: 28px;\n}\n\n.itemIcon {\n display: flex;\n align-items: center;\n flex-shrink: 0;\n}\n\n.itemLabel {\n line-height: 1;\n}\n\n/* ─── Variant: default ──────────────────────────────────────────────────────── */\n\n.default {\n background-color: var(--gnome-card-bg-color, #ffffff);\n border: 1px solid var(--gnome-light-3, #deddda);\n border-radius: var(--gnome-radius-md);\n box-shadow: var(--gnome-shadow-sm);\n padding: 2px;\n gap: 2px;\n}\n\n.default .item {\n border-radius: calc(var(--gnome-radius-md) - 2px);\n}\n\n.default .item:hover:not(:disabled):not(.active) {\n background-color: rgba(0, 0, 0, 0.06);\n}\n\n.default .item:active:not(:disabled):not(.active) {\n background-color: rgba(0, 0, 0, 0.12);\n}\n\n.default .item:focus-visible {\n box-shadow:\n 0 0 0 var(--gnome-focus-ring-offset, 2px) var(--gnome-card-bg-color, #ffffff),\n 0 0 0 calc(var(--gnome-focus-ring-offset, 2px) + var(--gnome-focus-ring-width, 2px))\n var(--gnome-focus-ring-color, #3584e4);\n}\n\n.default .active {\n background-color: color-mix(in srgb, var(--gnome-accent-bg-color, #3584e4) 15%, transparent);\n color: var(--gnome-accent-color, #3584e4);\n font-weight: var(--gnome-font-weight-semibold, 600);\n box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--gnome-accent-bg-color, #3584e4) 40%, transparent);\n}\n\n@media (prefers-color-scheme: dark) {\n .default {\n background-color: var(--gnome-card-bg-color, #383838);\n border-color: rgba(255, 255, 255, 0.12);\n box-shadow: none;\n }\n\n .default .item:hover:not(:disabled):not(.active) {\n background-color: rgba(255, 255, 255, 0.08);\n }\n\n .default .item:active:not(:disabled):not(.active) {\n background-color: rgba(255, 255, 255, 0.14);\n }\n\n .default .item:focus-visible {\n box-shadow:\n 0 0 0 var(--gnome-focus-ring-offset, 2px) var(--gnome-card-bg-color, #383838),\n 0 0 0 calc(var(--gnome-focus-ring-offset, 2px) + var(--gnome-focus-ring-width, 2px))\n var(--gnome-focus-ring-color, #78aeed);\n }\n\n .default .active {\n background-color: color-mix(in srgb, var(--gnome-accent-bg-color, #3584e4) 25%, transparent);\n color: var(--gnome-accent-color, #78aeed);\n box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--gnome-accent-bg-color, #3584e4) 50%, transparent);\n }\n}\n\n/* ─── Variant: flat ─────────────────────────────────────────────────────────── */\n\n.flat {\n background: transparent;\n border: none;\n box-shadow: none;\n border-radius: var(--gnome-radius-md);\n padding: 2px;\n gap: 2px;\n}\n\n.flat .item {\n border-radius: calc(var(--gnome-radius-md) - 2px);\n color: var(--gnome-window-fg-color, rgba(0, 0, 0, 0.6));\n}\n\n.flat .item:hover:not(:disabled):not(.active) {\n background-color: var(--gnome-hover-overlay);\n}\n\n.flat .item:active:not(:disabled):not(.active) {\n background-color: var(--gnome-active-overlay);\n}\n\n.flat .item:focus-visible {\n box-shadow:\n 0 0 0 var(--gnome-focus-ring-offset, 2px) var(--gnome-window-bg-color, #fafafa),\n 0 0 0 calc(var(--gnome-focus-ring-offset, 2px) + var(--gnome-focus-ring-width, 2px))\n var(--gnome-focus-ring-color, #3584e4);\n}\n\n.flat .active {\n background-color: color-mix(in srgb, var(--gnome-accent-bg-color, #3584e4) 15%, transparent);\n color: var(--gnome-accent-color, #3584e4);\n font-weight: var(--gnome-font-weight-semibold, 600);\n}\n\n@media (prefers-color-scheme: dark) {\n .flat .item {\n color: var(--gnome-window-fg-color, rgba(255, 255, 255, 0.6));\n }\n\n .flat .active {\n background-color: color-mix(in srgb, var(--gnome-accent-bg-color, #3584e4) 25%, transparent);\n color: var(--gnome-accent-color, #78aeed);\n }\n}\n\n/* ─── Variant: round ────────────────────────────────────────────────────────── */\n\n.round {\n background-color: var(--gnome-card-bg-color, #ffffff);\n border: 1px solid var(--gnome-light-3, #deddda);\n border-radius: var(--gnome-radius-pill, 9999px);\n box-shadow: var(--gnome-shadow-sm);\n padding: 2px;\n gap: 2px;\n}\n\n.round .item {\n border-radius: var(--gnome-radius-pill, 9999px);\n padding: 4px var(--gnome-space-2, 12px);\n}\n\n.round .iconOnly {\n padding: 4px;\n min-width: 28px;\n}\n\n.round .item:hover:not(:disabled):not(.active) {\n background-color: rgba(0, 0, 0, 0.06);\n}\n\n.round .item:active:not(:disabled):not(.active) {\n background-color: rgba(0, 0, 0, 0.12);\n}\n\n.round .item:focus-visible {\n box-shadow:\n 0 0 0 var(--gnome-focus-ring-offset, 2px) var(--gnome-card-bg-color, #ffffff),\n 0 0 0 calc(var(--gnome-focus-ring-offset, 2px) + var(--gnome-focus-ring-width, 2px))\n var(--gnome-focus-ring-color, #3584e4);\n}\n\n.round .active {\n background-color: var(--gnome-accent-bg-color, #3584e4);\n color: var(--gnome-accent-fg-color, #ffffff);\n font-weight: var(--gnome-font-weight-semibold, 600);\n box-shadow: none;\n}\n\n@media (prefers-color-scheme: dark) {\n .round {\n background-color: var(--gnome-card-bg-color, #383838);\n border-color: rgba(255, 255, 255, 0.12);\n box-shadow: none;\n }\n\n .round .item:hover:not(:disabled):not(.active) {\n background-color: rgba(255, 255, 255, 0.08);\n }\n\n .round .item:active:not(:disabled):not(.active) {\n background-color: rgba(255, 255, 255, 0.14);\n }\n\n .round .item:focus-visible {\n box-shadow:\n 0 0 0 var(--gnome-focus-ring-offset, 2px) var(--gnome-card-bg-color, #383838),\n 0 0 0 calc(var(--gnome-focus-ring-offset, 2px) + var(--gnome-focus-ring-width, 2px))\n var(--gnome-focus-ring-color, #78aeed);\n }\n}\n\n/* ─── Disabled ──────────────────────────────────────────────────────────────── */\n\n.item:disabled {\n opacity: var(--gnome-opacity-disabled, 0.5);\n cursor: not-allowed;\n}\n\n/* ─── Reduced motion ────────────────────────────────────────────────────────── */\n\n@media (prefers-reduced-motion: reduce) {\n .item {\n transition: none;\n }\n}\n","import {\n createContext,\n useContext,\n useRef,\n type HTMLAttributes,\n type KeyboardEvent,\n type ReactNode,\n} from \"react\";\nimport styles from \"./InlineViewSwitcher.module.css\";\n\nexport type InlineViewSwitcherVariant = \"default\" | \"flat\" | \"round\";\n\n// ─── Internal context ──────────────────────────────────────────────────────────\n\ninterface InlineViewSwitcherContextValue {\n value: string;\n onValueChange: (value: string) => void;\n}\n\nconst InlineViewSwitcherContext =\n createContext<InlineViewSwitcherContextValue | null>(null);\n\nexport function useInlineViewSwitcher() {\n const ctx = useContext(InlineViewSwitcherContext);\n if (!ctx)\n throw new Error(\n \"InlineViewSwitcherItem must be used inside InlineViewSwitcher\",\n );\n return ctx;\n}\n\n// ─── InlineViewSwitcher ────────────────────────────────────────────────────────\n\nexport interface InlineViewSwitcherProps extends HTMLAttributes<HTMLDivElement> {\n /** Currently active view name. */\n value: string;\n /** Called with the new value when a view is selected. */\n onValueChange: (value: string) => void;\n /**\n * Visual style of the switcher.\n * - `default` — card background with border and shadow (same as `ToggleGroup`).\n * - `flat` — no background or border; active indicator only.\n * - `round` — pill-shaped container and items.\n */\n variant?: InlineViewSwitcherVariant;\n /** Accessible label for the group. */\n \"aria-label\"?: string;\n children?: ReactNode;\n}\n\n/**\n * Compact inline view switcher for placing inside content areas, cards, or\n * toolbars — wherever `ViewSwitcher` (header-bar sized) would be too heavy.\n *\n * Built on `ToggleGroup` internals with `flat` and `round` style variants.\n * Mirrors `AdwInlineViewSwitcher` (libadwaita 1.7 / GNOME 48).\n *\n * Compose with `InlineViewSwitcherItem`. Keyboard: ← / → cycle, Home / End jump.\n *\n * @see https://gnome.pages.gitlab.gnome.org/libadwaita/doc/main/class.InlineViewSwitcher.html\n */\nexport function InlineViewSwitcher({\n value,\n onValueChange,\n variant = \"default\",\n \"aria-label\": ariaLabel = \"View switcher\",\n children,\n className,\n ...props\n}: InlineViewSwitcherProps) {\n const groupRef = useRef<HTMLDivElement>(null);\n\n function handleKeyDown(e: KeyboardEvent<HTMLDivElement>) {\n const items = Array.from(\n groupRef.current?.querySelectorAll<HTMLButtonElement>(\n \"[role=radio]:not(:disabled)\",\n ) ?? [],\n );\n const idx = items.findIndex((el) => el === document.activeElement);\n if (idx === -1) return;\n\n let next = idx;\n if (e.key === \"ArrowRight\" || e.key === \"ArrowDown\")\n next = (idx + 1) % items.length;\n else if (e.key === \"ArrowLeft\" || e.key === \"ArrowUp\")\n next = (idx - 1 + items.length) % items.length;\n else if (e.key === \"Home\") next = 0;\n else if (e.key === \"End\") next = items.length - 1;\n else return;\n\n e.preventDefault();\n items[next].focus();\n items[next].click();\n }\n\n return (\n <InlineViewSwitcherContext.Provider value={{ value, onValueChange }}>\n <div\n ref={groupRef}\n role=\"radiogroup\"\n aria-label={ariaLabel}\n onKeyDown={handleKeyDown}\n className={[styles.switcher, styles[variant], className]\n .filter(Boolean)\n .join(\" \")}\n {...props}\n >\n {children}\n </div>\n </InlineViewSwitcherContext.Provider>\n );\n}\n","import type { ButtonHTMLAttributes } from \"react\";\nimport type { IconDefinition } from \"@gnome-ui/icons\";\nimport { Icon } from \"../Icon\";\nimport { useInlineViewSwitcher } from \"./InlineViewSwitcher\";\nimport styles from \"./InlineViewSwitcher.module.css\";\n\nexport interface InlineViewSwitcherItemProps\n extends ButtonHTMLAttributes<HTMLButtonElement> {\n /** String identifier — used as the switcher's `value` when this item is active. */\n name: string;\n /** Visible label. */\n label?: string;\n /** Icon from `@gnome-ui/icons`. */\n icon?: IconDefinition;\n}\n\n/**\n * Individual view option inside an `InlineViewSwitcher`.\n *\n * Can be icon-only, label-only, or icon + label. For icon-only items always\n * provide an `aria-label` so screen readers can identify the view.\n */\nexport function InlineViewSwitcherItem({\n name,\n label,\n icon,\n disabled,\n className,\n ...props\n}: InlineViewSwitcherItemProps) {\n const { value, onValueChange } = useInlineViewSwitcher();\n const active = value === name;\n const iconOnly = icon && !label;\n\n return (\n <button\n type=\"button\"\n role=\"radio\"\n aria-checked={active}\n tabIndex={active ? 0 : -1}\n disabled={disabled}\n onClick={() => onValueChange(name)}\n className={[\n styles.item,\n active ? styles.active : null,\n iconOnly ? styles.iconOnly : null,\n className,\n ]\n .filter(Boolean)\n .join(\" \")}\n {...props}\n >\n {icon && (\n <span className={styles.itemIcon}>\n <Icon icon={icon} size=\"md\" aria-hidden />\n </span>\n )}\n {label && <span className={styles.itemLabel}>{label}</span>}\n </button>\n );\n}\n","/* ─── Row button ────────────────────────────────────────────────────────────── */\n\n.row {\n display: flex;\n align-items: center;\n gap: var(--gnome-space-2, 12px);\n padding: var(--gnome-space-2, 12px) var(--gnome-space-4, 24px);\n min-height: 52px;\n width: 100%;\n\n appearance: none;\n -webkit-appearance: none;\n background: transparent;\n border: none;\n font: inherit;\n outline: none;\n cursor: pointer;\n text-align: start;\n color: var(--gnome-card-fg-color, rgba(0, 0, 0, 0.8));\n\n transition: background-color var(--gnome-duration-fast) var(--gnome-easing-default);\n}\n\n.row:hover {\n background-color: var(--gnome-hover-overlay);\n}\n\n.row:active {\n background-color: var(--gnome-active-overlay);\n}\n\n.row:focus-visible {\n box-shadow:\n inset 0 0 0 var(--gnome-focus-ring-offset, 2px) var(--gnome-window-bg-color, #fafafa),\n inset 0 0 0 calc(var(--gnome-focus-ring-offset, 2px) + var(--gnome-focus-ring-width, 3px))\n var(--gnome-focus-ring-color, #3584e4);\n}\n\n/* ─── Disabled ──────────────────────────────────────────────────────────────── */\n\n.disabled {\n opacity: var(--gnome-opacity-disabled, 0.5);\n cursor: not-allowed;\n pointer-events: none;\n}\n\n/* ─── Leading icon/avatar ───────────────────────────────────────────────────── */\n\n.leading {\n display: flex;\n align-items: center;\n flex-shrink: 0;\n color: var(--gnome-window-fg-color, rgba(0, 0, 0, 0.8));\n}\n\n/* ─── Text content ──────────────────────────────────────────────────────────── */\n\n.content {\n display: flex;\n flex-direction: column;\n flex: 1;\n min-width: 0;\n gap: 2px;\n}\n\n.title {\n font-family: var(--gnome-font-family);\n font-size: var(--gnome-font-size-body, 1rem);\n font-weight: var(--gnome-font-weight-normal, 400);\n line-height: var(--gnome-line-height-body, 1.5);\n color: var(--gnome-card-fg-color, rgba(0, 0, 0, 0.8));\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n}\n\n.subtitle {\n font-family: var(--gnome-font-family);\n font-size: var(--gnome-font-size-caption, 0.75rem);\n font-weight: var(--gnome-font-weight-normal, 400);\n line-height: var(--gnome-line-height-body, 1.5);\n color: var(--gnome-window-fg-color, rgba(0, 0, 0, 0.55));\n opacity: var(--gnome-opacity-dim, 0.55);\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n}\n\n/* ─── Switch track ──────────────────────────────────────────────────────────── */\n\n.switchTrack {\n position: relative;\n display: inline-flex;\n align-items: center;\n flex-shrink: 0;\n width: 56px;\n height: 28px;\n border-radius: var(--gnome-radius-pill);\n background-color: var(--gnome-light-4, #c0bfbc);\n border: 1px solid var(--gnome-border-subtle, rgba(0, 0, 0, 0.12));\n transition:\n background-color var(--gnome-duration-fast) var(--gnome-easing-default),\n border-color var(--gnome-duration-fast) var(--gnome-easing-default);\n}\n\n/* When checked, colour the track */\n.row[aria-checked=\"true\"] .switchTrack {\n background-color: var(--gnome-accent-bg-color, #3584e4);\n border-color: var(--gnome-accent-bg-color, #3584e4);\n}\n\n/* ─── Switch thumb ──────────────────────────────────────────────────────────── */\n\n.switchThumb {\n position: absolute;\n top: 3px;\n left: 3px;\n width: 20px;\n height: 20px;\n border-radius: var(--gnome-radius-pill);\n background-color: #ffffff;\n box-shadow: 0 1px 3px rgba(0, 0, 0, 0.25);\n transition: transform var(--gnome-duration-fast) var(--gnome-easing-default);\n}\n\n.switchThumbOn {\n transform: translateX(28px);\n}\n\n/* ─── Dark mode ─────────────────────────────────────────────────────────────── */\n\n@media (prefers-color-scheme: dark) {\n .switchTrack {\n background-color: var(--gnome-dark-2, #5e5c64);\n }\n\n .switchThumb {\n background-color: var(--gnome-light-3, #deddda);\n }\n\n .row[aria-checked=\"true\"] .switchThumb {\n background-color: #ffffff;\n }\n}\n\n/* ─── High contrast ─────────────────────────────────────────────────────────── */\n\n@media (prefers-contrast: more) {\n .row:focus-visible {\n box-shadow:\n inset 0 0 0 var(--gnome-focus-ring-offset, 2px) var(--gnome-window-bg-color, #fafafa),\n inset 0 0 0 calc(var(--gnome-focus-ring-offset, 2px) + var(--gnome-focus-ring-width, 3px))\n var(--gnome-focus-ring-color, #3584e4),\n inset 0 0 0 calc(var(--gnome-focus-ring-offset, 2px) + var(--gnome-focus-ring-width, 3px) + 1px)\n var(--gnome-window-bg-color, #fafafa);\n }\n\n .switchTrack {\n border-width: 2px;\n }\n\n .switchThumb {\n box-shadow: none;\n }\n}\n\n/* ─── Reduced motion ────────────────────────────────────────────────────────── */\n\n@media (prefers-reduced-motion: reduce) {\n .switchTrack,\n .switchThumb {\n transition: none;\n }\n}\n","import { useState, useId, type HTMLAttributes, type ReactNode } from \"react\";\nimport styles from \"./SwitchRow.module.css\";\n\nexport interface SwitchRowProps extends Omit<HTMLAttributes<HTMLButtonElement>, \"onChange\"> {\n /** Primary label. */\n title: string;\n /** Secondary line below the title. */\n subtitle?: string;\n /** Icon or image placed at the leading edge. */\n leading?: ReactNode;\n /** Controlled checked state. */\n checked?: boolean;\n /** Initial checked state when uncontrolled. Defaults to `false`. */\n defaultChecked?: boolean;\n /** Called when the checked state changes. */\n onCheckedChange?: (checked: boolean) => void;\n /** Disables the row. */\n disabled?: boolean;\n}\n\n/**\n * Activatable row with an integrated switch.\n *\n * The entire row is a button — clicking anywhere toggles the switch.\n * Mirrors `AdwSwitchRow`, which differs from `ActionRow + trailing Switch`\n * because the row itself is the interactive element.\n *\n * Supports both controlled (`checked`) and uncontrolled (`defaultChecked`) modes.\n *\n * @see https://gnome.pages.gitlab.gnome.org/libadwaita/doc/main/class.SwitchRow.html\n */\nexport function SwitchRow({\n title,\n subtitle,\n leading,\n checked: controlledChecked,\n defaultChecked = false,\n onCheckedChange,\n disabled = false,\n className,\n onClick,\n ...props\n}: SwitchRowProps) {\n const isControlled = controlledChecked !== undefined;\n const [uncontrolledChecked, setUncontrolledChecked] = useState(defaultChecked);\n const checked = isControlled ? controlledChecked : uncontrolledChecked;\n\n const switchId = useId();\n\n const handleClick = (e: React.MouseEvent<HTMLButtonElement>) => {\n const next = !checked;\n if (!isControlled) setUncontrolledChecked(next);\n onCheckedChange?.(next);\n onClick?.(e);\n };\n\n return (\n <button\n role=\"switch\"\n aria-checked={checked}\n aria-labelledby={switchId}\n disabled={disabled}\n className={[styles.row, disabled ? styles.disabled : null, className]\n .filter(Boolean)\n .join(\" \")}\n onClick={handleClick}\n {...props}\n >\n {leading && <span className={styles.leading}>{leading}</span>}\n\n <span className={styles.content} id={switchId}>\n <span className={styles.title}>{title}</span>\n {subtitle && <span className={styles.subtitle}>{subtitle}</span>}\n </span>\n\n {/* Visual switch indicator — aria-hidden since the button carries the role */}\n <span className={styles.switchTrack} aria-hidden=\"true\">\n <span className={[styles.switchThumb, checked ? styles.switchThumbOn : null].filter(Boolean).join(\" \")} />\n </span>\n </button>\n );\n}\n","/* ─── Row button ────────────────────────────────────────────────────────────── */\n\n.row {\n display: flex;\n align-items: center;\n gap: var(--gnome-space-2, 12px);\n padding: var(--gnome-space-2, 12px) var(--gnome-space-4, 24px);\n min-height: 52px;\n width: 100%;\n\n appearance: none;\n -webkit-appearance: none;\n background: transparent;\n border: none;\n font: inherit;\n outline: none;\n cursor: pointer;\n text-align: start;\n color: var(--gnome-card-fg-color, rgba(0, 0, 0, 0.8));\n\n transition: background-color var(--gnome-duration-fast) var(--gnome-easing-default);\n}\n\n.row:hover {\n background-color: var(--gnome-hover-overlay);\n}\n\n.row:active {\n background-color: var(--gnome-active-overlay);\n}\n\n.row:focus-visible {\n box-shadow:\n inset 0 0 0 var(--gnome-focus-ring-offset, 2px) var(--gnome-window-bg-color, #fafafa),\n inset 0 0 0 calc(var(--gnome-focus-ring-offset, 2px) + var(--gnome-focus-ring-width, 3px))\n var(--gnome-focus-ring-color, #3584e4);\n}\n\n/* ─── Disabled ──────────────────────────────────────────────────────────────── */\n\n.disabled {\n opacity: var(--gnome-opacity-disabled, 0.5);\n cursor: not-allowed;\n pointer-events: none;\n}\n\n/* ─── Checkbox visual ───────────────────────────────────────────────────────── */\n\n.checkboxWrap {\n display: flex;\n align-items: center;\n flex-shrink: 0;\n}\n\n.checkbox {\n display: flex;\n align-items: center;\n justify-content: center;\n width: 20px;\n height: 20px;\n border-radius: var(--gnome-radius-sm, 4px);\n border: 1.5px solid var(--gnome-border-subtle, rgba(0, 0, 0, 0.25));\n background-color: transparent;\n color: transparent;\n transition:\n background-color var(--gnome-duration-fast) var(--gnome-easing-default),\n border-color var(--gnome-duration-fast) var(--gnome-easing-default),\n color var(--gnome-duration-fast) var(--gnome-easing-default);\n}\n\n.checkboxChecked {\n background-color: var(--gnome-accent-bg-color, #3584e4);\n border-color: var(--gnome-accent-bg-color, #3584e4);\n color: #ffffff;\n}\n\n.checkmark {\n display: block;\n flex-shrink: 0;\n}\n\n/* ─── Leading icon/avatar ───────────────────────────────────────────────────── */\n\n.leading {\n display: flex;\n align-items: center;\n flex-shrink: 0;\n color: var(--gnome-window-fg-color, rgba(0, 0, 0, 0.8));\n}\n\n/* ─── Text content ──────────────────────────────────────────────────────────── */\n\n.content {\n display: flex;\n flex-direction: column;\n flex: 1;\n min-width: 0;\n gap: 2px;\n}\n\n.title {\n font-family: var(--gnome-font-family);\n font-size: var(--gnome-font-size-body, 1rem);\n font-weight: var(--gnome-font-weight-normal, 400);\n line-height: var(--gnome-line-height-body, 1.5);\n color: var(--gnome-card-fg-color, rgba(0, 0, 0, 0.8));\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n}\n\n.subtitle {\n font-family: var(--gnome-font-family);\n font-size: var(--gnome-font-size-caption, 0.75rem);\n font-weight: var(--gnome-font-weight-normal, 400);\n line-height: var(--gnome-line-height-body, 1.5);\n color: var(--gnome-window-fg-color, rgba(0, 0, 0, 0.55));\n opacity: var(--gnome-opacity-dim, 0.55);\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n}\n\n/* ─── High contrast ─────────────────────────────────────────────────────────── */\n\n@media (prefers-contrast: more) {\n .row:focus-visible {\n box-shadow:\n inset 0 0 0 var(--gnome-focus-ring-offset, 2px) var(--gnome-window-bg-color, #fafafa),\n inset 0 0 0 calc(var(--gnome-focus-ring-offset, 2px) + var(--gnome-focus-ring-width, 3px))\n var(--gnome-focus-ring-color, #3584e4),\n inset 0 0 0 calc(var(--gnome-focus-ring-offset, 2px) + var(--gnome-focus-ring-width, 3px) + 1px)\n var(--gnome-window-bg-color, #fafafa);\n }\n\n .checkbox {\n border-width: 2px;\n }\n}\n\n/* ─── Reduced motion ────────────────────────────────────────────────────────── */\n\n@media (prefers-reduced-motion: reduce) {\n .checkbox {\n transition: none;\n }\n}\n","import { useState, useId, type HTMLAttributes, type ReactNode } from \"react\";\nimport styles from \"./CheckRow.module.css\";\n\nexport interface CheckRowProps extends Omit<HTMLAttributes<HTMLButtonElement>, \"onChange\"> {\n /** Primary label. */\n title: string;\n /** Secondary line below the title. */\n subtitle?: string;\n /** Icon or image placed at the leading edge. */\n leading?: ReactNode;\n /** Controlled checked state. */\n checked?: boolean;\n /** Initial checked state when uncontrolled. Defaults to `false`. */\n defaultChecked?: boolean;\n /** Called when the checked state changes. */\n onCheckedChange?: (checked: boolean) => void;\n /** Disables the row. */\n disabled?: boolean;\n}\n\n/**\n * Activatable row with an integrated checkbox.\n *\n * The entire row is a button — clicking anywhere toggles the checkbox.\n * Mirrors the `AdwCheckButton`-in-a-row pattern used in list selection contexts.\n *\n * Supports both controlled (`checked`) and uncontrolled (`defaultChecked`) modes.\n *\n * @see https://gnome.pages.gitlab.gnome.org/libadwaita/doc/main/class.CheckButton.html\n */\nexport function CheckRow({\n title,\n subtitle,\n leading,\n checked: controlledChecked,\n defaultChecked = false,\n onCheckedChange,\n disabled = false,\n className,\n onClick,\n ...props\n}: CheckRowProps) {\n const isControlled = controlledChecked !== undefined;\n const [uncontrolledChecked, setUncontrolledChecked] = useState(defaultChecked);\n const checked = isControlled ? controlledChecked : uncontrolledChecked;\n\n const labelId = useId();\n\n const handleClick = (e: React.MouseEvent<HTMLButtonElement>) => {\n const next = !checked;\n if (!isControlled) setUncontrolledChecked(next);\n onCheckedChange?.(next);\n onClick?.(e);\n };\n\n return (\n <button\n role=\"checkbox\"\n aria-checked={checked}\n aria-labelledby={labelId}\n disabled={disabled}\n className={[styles.row, disabled ? styles.disabled : null, className]\n .filter(Boolean)\n .join(\" \")}\n onClick={handleClick}\n {...props}\n >\n {/* Visual checkbox — aria-hidden since the button carries the role */}\n <span className={styles.checkboxWrap} aria-hidden=\"true\">\n <span className={[styles.checkbox, checked ? styles.checkboxChecked : null].filter(Boolean).join(\" \")}>\n {checked && (\n <svg className={styles.checkmark} width=\"12\" height=\"12\" viewBox=\"0 0 12 12\" focusable=\"false\">\n <path\n d=\"M2 6l3 3 5-5\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth=\"1.75\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n />\n </svg>\n )}\n </span>\n </span>\n\n {leading && <span className={styles.leading}>{leading}</span>}\n\n <span className={styles.content} id={labelId}>\n <span className={styles.title}>{title}</span>\n {subtitle && <span className={styles.subtitle}>{subtitle}</span>}\n </span>\n </button>\n );\n}\n","/* ─── Row container ─────────────────────────────────────────────────────────── */\n\n.row {\n display: flex;\n align-items: center;\n gap: var(--gnome-space-2, 12px);\n padding: var(--gnome-space-2, 12px) var(--gnome-space-4, 24px);\n min-height: 52px;\n width: 100%;\n color: var(--gnome-card-fg-color, rgba(0, 0, 0, 0.8));\n text-align: start;\n}\n\n/* ─── Disabled ──────────────────────────────────────────────────────────────── */\n\n.disabled {\n opacity: var(--gnome-opacity-disabled, 0.5);\n pointer-events: none;\n}\n\n/* ─── Leading icon/avatar ───────────────────────────────────────────────────── */\n\n.leading {\n display: flex;\n align-items: center;\n flex-shrink: 0;\n color: var(--gnome-window-fg-color, rgba(0, 0, 0, 0.8));\n}\n\n/* ─── Text content ──────────────────────────────────────────────────────────── */\n\n.content {\n display: flex;\n flex-direction: column;\n flex: 1;\n min-width: 0;\n gap: 2px;\n}\n\n.title {\n font-family: var(--gnome-font-family);\n font-size: var(--gnome-font-size-body, 1rem);\n font-weight: var(--gnome-font-weight-normal, 400);\n line-height: var(--gnome-line-height-body, 1.5);\n color: var(--gnome-card-fg-color, rgba(0, 0, 0, 0.8));\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n}\n\n.subtitle {\n font-family: var(--gnome-font-family);\n font-size: var(--gnome-font-size-caption, 0.75rem);\n font-weight: var(--gnome-font-weight-normal, 400);\n line-height: var(--gnome-line-height-body, 1.5);\n color: var(--gnome-window-fg-color, rgba(0, 0, 0, 0.55));\n opacity: var(--gnome-opacity-dim, 0.55);\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n}\n\n/* ─── Combo wrapper ─────────────────────────────────────────────────────────── */\n\n.comboWrap {\n position: relative;\n flex-shrink: 0;\n min-width: 120px;\n}\n\n/* ─── Trigger button ────────────────────────────────────────────────────────── */\n\n.trigger {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: var(--gnome-space-1, 6px);\n width: 100%;\n\n padding: 5px var(--gnome-space-2, 12px);\n min-height: 32px;\n\n appearance: none;\n background-color: var(--gnome-card-bg-color, #ffffff);\n border: 1px solid var(--gnome-card-shade-color, rgba(0, 0, 0, 0.15));\n border-radius: var(--gnome-radius-md, 8px);\n box-shadow: var(--gnome-shadow-sm, 0 1px 2px rgba(0, 0, 0, 0.08));\n\n font-family: var(--gnome-font-family);\n font-size: var(--gnome-font-size-body, 1rem);\n font-weight: var(--gnome-font-weight-normal, 400);\n color: var(--gnome-window-fg-color, rgba(0, 0, 0, 0.8));\n\n cursor: pointer;\n user-select: none;\n text-align: left;\n white-space: nowrap;\n\n transition:\n background-color var(--gnome-duration-fast) var(--gnome-easing-default),\n border-color var(--gnome-duration-fast) var(--gnome-easing-default),\n box-shadow var(--gnome-duration-fast) var(--gnome-easing-default);\n}\n\n.trigger:hover:not(:disabled) {\n background-color: color-mix(in srgb, var(--gnome-card-bg-color, #ffffff) 90%, black 10%);\n}\n\n.trigger:focus {\n outline: none;\n}\n\n.trigger:focus-visible {\n border-color: var(--gnome-accent-color, #3584e4);\n box-shadow:\n 0 0 0 var(--gnome-focus-ring-offset, 2px) var(--gnome-window-bg-color, #fafafa),\n 0 0 0 calc(var(--gnome-focus-ring-offset, 2px) + var(--gnome-focus-ring-width, 3px))\n var(--gnome-focus-ring-color, #3584e4);\n}\n\n.trigger:disabled {\n opacity: var(--gnome-opacity-disabled, 0.5);\n cursor: not-allowed;\n}\n\n.triggerOpen {\n border-color: var(--gnome-accent-color, #3584e4);\n box-shadow:\n 0 0 0 var(--gnome-focus-ring-offset, 2px) var(--gnome-window-bg-color, #fafafa),\n 0 0 0 calc(var(--gnome-focus-ring-offset, 2px) + var(--gnome-focus-ring-width, 3px))\n var(--gnome-focus-ring-color, #3584e4);\n}\n\n/* ─── Trigger label ─────────────────────────────────────────────────────────── */\n\n.triggerLabel {\n flex: 1;\n min-width: 0;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n.placeholder {\n opacity: var(--gnome-opacity-dim, 0.55);\n}\n\n/* ─── Chevron ───────────────────────────────────────────────────────────────── */\n\n.chevron {\n flex-shrink: 0;\n color: var(--gnome-window-fg-color, rgba(0, 0, 0, 0.8));\n opacity: var(--gnome-opacity-dim, 0.55);\n transition: transform var(--gnome-duration-fast) var(--gnome-easing-default);\n}\n\n.chevronOpen {\n transform: rotate(180deg);\n}\n\n/* ─── Listbox ───────────────────────────────────────────────────────────────── */\n\n.list {\n position: absolute;\n left: 0;\n right: 0;\n z-index: 1000;\n\n margin: 0;\n padding: 4px 0;\n list-style: none;\n\n background-color: var(--gnome-popover-bg-color, var(--gnome-card-bg-color, #ffffff));\n border: 1px solid var(--gnome-card-shade-color, rgba(0, 0, 0, 0.12));\n border-radius: var(--gnome-radius-md, 8px);\n box-shadow:\n 0 4px 16px rgba(0, 0, 0, 0.12),\n 0 1px 3px rgba(0, 0, 0, 0.08);\n\n max-height: 260px;\n overflow-y: auto;\n overscroll-behavior: contain;\n\n animation: list-in var(--gnome-duration-fast) var(--gnome-easing-default) both;\n}\n\n@keyframes list-in {\n from { opacity: 0; transform: scaleY(0.95); transform-origin: top center; }\n to { opacity: 1; transform: scaleY(1); transform-origin: top center; }\n}\n\n.listDown {\n top: calc(100% + 4px);\n}\n\n.listUp {\n bottom: calc(100% + 4px);\n animation-name: list-in-up;\n}\n\n@keyframes list-in-up {\n from { opacity: 0; transform: scaleY(0.95); transform-origin: bottom center; }\n to { opacity: 1; transform: scaleY(1); transform-origin: bottom center; }\n}\n\n/* ─── Option ────────────────────────────────────────────────────────────────── */\n\n.option {\n display: flex;\n align-items: center;\n gap: var(--gnome-space-2, 12px);\n padding: 6px var(--gnome-space-2, 12px);\n min-height: 36px;\n\n font-family: var(--gnome-font-family);\n font-size: var(--gnome-font-size-body, 1rem);\n color: var(--gnome-window-fg-color, rgba(0, 0, 0, 0.8));\n\n cursor: pointer;\n user-select: none;\n\n transition: background-color var(--gnome-duration-fast) var(--gnome-easing-default);\n}\n\n.optionActive {\n background-color: color-mix(in srgb, var(--gnome-accent-bg-color, #3584e4) 12%, transparent);\n}\n\n.optionSelected {\n font-weight: var(--gnome-font-weight-semibold, 600);\n}\n\n.optionDisabled {\n opacity: var(--gnome-opacity-disabled, 0.5);\n cursor: not-allowed;\n pointer-events: none;\n}\n\n.optionLabel {\n flex: 1;\n min-width: 0;\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n}\n\n.checkIcon {\n flex-shrink: 0;\n color: var(--gnome-accent-color, #3584e4);\n}\n\n/* ─── Dark mode ─────────────────────────────────────────────────────────────── */\n\n@media (prefers-color-scheme: dark) {\n .trigger {\n background-color: var(--gnome-card-bg-color, #383838);\n border-color: rgba(255, 255, 255, 0.12);\n box-shadow: none;\n }\n\n .trigger:hover:not(:disabled) {\n background-color: rgba(255, 255, 255, 0.08);\n }\n\n .list {\n background-color: var(--gnome-card-bg-color, #383838);\n border-color: rgba(255, 255, 255, 0.1);\n box-shadow:\n 0 4px 16px rgba(0, 0, 0, 0.4),\n 0 1px 3px rgba(0, 0, 0, 0.3);\n }\n}\n\n/* ─── Reduced motion ────────────────────────────────────────────────────────── */\n\n@media (prefers-reduced-motion: reduce) {\n .trigger,\n .chevron {\n transition: none;\n }\n\n .list {\n animation: none;\n }\n}\n","import { useState, useRef, useId, useEffect, useCallback, type HTMLAttributes, type KeyboardEvent, type ReactNode } from \"react\";\nimport styles from \"./ComboRow.module.css\";\n\nexport interface ComboRowOption<V extends string = string> {\n value: V;\n label: string;\n disabled?: boolean;\n}\n\nexport interface ComboRowProps<V extends string = string>\n extends Omit<HTMLAttributes<HTMLDivElement>, \"onChange\"> {\n /** Primary label. */\n title: string;\n /** Secondary line below the title. */\n subtitle?: string;\n /** Icon or image placed at the leading edge. */\n leading?: ReactNode;\n /** The list of selectable options. */\n options: ComboRowOption<V>[];\n /** The currently selected value (controlled). */\n value?: V;\n /** Initial value when uncontrolled. */\n defaultValue?: V;\n /** Called when the user selects an option. */\n onValueChange?: (value: V) => void;\n /** Disables the row. */\n disabled?: boolean;\n}\n\n/**\n * Settings row with an inline combo selector at the trailing edge.\n *\n * Mirrors `AdwComboRow` — a standard `ActionRow` layout with a dropdown\n * for choosing among a set of options. The dropdown opens inline in the row\n * and follows the same keyboard patterns as the standalone `Dropdown`.\n *\n * @see https://gnome.pages.gitlab.gnome.org/libadwaita/doc/main/class.ComboRow.html\n */\nexport function ComboRow<V extends string = string>({\n title,\n subtitle,\n leading,\n options,\n value: controlledValue,\n defaultValue,\n onValueChange,\n disabled = false,\n className,\n ...props\n}: ComboRowProps<V>) {\n const isControlled = controlledValue !== undefined;\n const [uncontrolledValue, setUncontrolledValue] = useState<V | undefined>(defaultValue);\n const value = isControlled ? controlledValue : uncontrolledValue;\n\n const [open, setOpen] = useState(false);\n const [activeIndex, setActiveIndex] = useState(-1);\n const [flipUp, setFlipUp] = useState(false);\n\n const triggerId = useId();\n const listboxId = useId();\n const triggerRef = useRef<HTMLButtonElement>(null);\n const listRef = useRef<HTMLUListElement>(null);\n\n const selected = options.find((o) => o.value === value);\n\n const computeFlip = useCallback(() => {\n if (!triggerRef.current) return;\n const rect = triggerRef.current.getBoundingClientRect();\n const spaceBelow = window.innerHeight - rect.bottom;\n const listH = Math.min(options.length * 44 + 8, 260);\n setFlipUp(spaceBelow < listH && rect.top > listH);\n }, [options.length]);\n\n const openList = useCallback(() => {\n if (disabled) return;\n computeFlip();\n setOpen(true);\n const idx = options.findIndex((o) => o.value === value && !o.disabled);\n setActiveIndex(idx >= 0 ? idx : options.findIndex((o) => !o.disabled));\n }, [disabled, computeFlip, options, value]);\n\n const closeList = useCallback(() => {\n setOpen(false);\n setActiveIndex(-1);\n triggerRef.current?.focus();\n }, []);\n\n const selectOption = useCallback(\n (opt: ComboRowOption<V>) => {\n if (opt.disabled) return;\n if (!isControlled) setUncontrolledValue(opt.value);\n onValueChange?.(opt.value);\n closeList();\n },\n [isControlled, onValueChange, closeList],\n );\n\n useEffect(() => {\n if (!open || activeIndex < 0 || !listRef.current) return;\n const item = listRef.current.children[activeIndex] as HTMLElement | undefined;\n item?.scrollIntoView({ block: \"nearest\" });\n }, [activeIndex, open]);\n\n useEffect(() => {\n if (!open) return;\n const handler = (e: MouseEvent) => {\n if (\n !triggerRef.current?.contains(e.target as Node) &&\n !listRef.current?.contains(e.target as Node)\n ) {\n closeList();\n }\n };\n document.addEventListener(\"mousedown\", handler);\n return () => document.removeEventListener(\"mousedown\", handler);\n }, [open, closeList]);\n\n const handleTriggerKeyDown = useCallback(\n (e: KeyboardEvent<HTMLButtonElement>) => {\n switch (e.key) {\n case \"Enter\":\n case \" \":\n case \"ArrowDown\":\n e.preventDefault();\n openList();\n break;\n case \"ArrowUp\":\n e.preventDefault();\n computeFlip();\n setOpen(true);\n setActiveIndex(\n options.reduce((last, o, i) => (!o.disabled ? i : last), -1),\n );\n break;\n }\n },\n [openList, computeFlip, options],\n );\n\n const handleListKeyDown = useCallback(\n (e: KeyboardEvent<HTMLUListElement>) => {\n const enabledIndexes = options\n .map((o, i) => ({ o, i }))\n .filter(({ o }) => !o.disabled)\n .map(({ i }) => i);\n const currentPos = enabledIndexes.indexOf(activeIndex);\n\n switch (e.key) {\n case \"ArrowDown\": {\n e.preventDefault();\n const next = enabledIndexes[Math.min(currentPos + 1, enabledIndexes.length - 1)];\n setActiveIndex(next ?? activeIndex);\n break;\n }\n case \"ArrowUp\": {\n e.preventDefault();\n const prev = enabledIndexes[Math.max(currentPos - 1, 0)];\n setActiveIndex(prev ?? activeIndex);\n break;\n }\n case \"Home\":\n e.preventDefault();\n setActiveIndex(enabledIndexes[0] ?? -1);\n break;\n case \"End\":\n e.preventDefault();\n setActiveIndex(enabledIndexes[enabledIndexes.length - 1] ?? -1);\n break;\n case \"Enter\":\n case \" \": {\n e.preventDefault();\n if (activeIndex >= 0) selectOption(options[activeIndex]);\n break;\n }\n case \"Escape\":\n case \"Tab\":\n closeList();\n break;\n }\n },\n [options, activeIndex, selectOption, closeList],\n );\n\n return (\n <div\n className={[styles.row, disabled ? styles.disabled : null, className]\n .filter(Boolean)\n .join(\" \")}\n {...props}\n >\n {leading && <span className={styles.leading}>{leading}</span>}\n\n <span className={styles.content}>\n <span className={styles.title}>{title}</span>\n {subtitle && <span className={styles.subtitle}>{subtitle}</span>}\n </span>\n\n {/* Combo trigger button */}\n <div className={styles.comboWrap}>\n <button\n ref={triggerRef}\n id={triggerId}\n type=\"button\"\n role=\"combobox\"\n aria-haspopup=\"listbox\"\n aria-expanded={open}\n aria-controls={listboxId}\n aria-activedescendant={\n open && activeIndex >= 0 ? `${listboxId}-opt-${activeIndex}` : undefined\n }\n disabled={disabled}\n className={[styles.trigger, open ? styles.triggerOpen : null].filter(Boolean).join(\" \")}\n onClick={() => (open ? closeList() : openList())}\n onKeyDown={handleTriggerKeyDown}\n >\n <span className={[styles.triggerLabel, !selected ? styles.placeholder : null]\n .filter(Boolean).join(\" \")}>\n {selected?.label ?? \"—\"}\n </span>\n <svg\n className={[styles.chevron, open ? styles.chevronOpen : null].filter(Boolean).join(\" \")}\n width=\"16\"\n height=\"16\"\n viewBox=\"0 0 16 16\"\n aria-hidden=\"true\"\n focusable=\"false\"\n >\n <path\n d=\"M4 6l4 4 4-4\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth=\"1.5\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n />\n </svg>\n </button>\n\n {open && (\n <ul\n ref={listRef}\n id={listboxId}\n role=\"listbox\"\n aria-labelledby={triggerId}\n tabIndex={-1}\n className={[styles.list, flipUp ? styles.listUp : styles.listDown]\n .filter(Boolean)\n .join(\" \")}\n onKeyDown={handleListKeyDown}\n >\n {options.map((opt, i) => (\n <li\n key={opt.value}\n id={`${listboxId}-opt-${i}`}\n role=\"option\"\n aria-selected={opt.value === value}\n aria-disabled={opt.disabled}\n className={[\n styles.option,\n opt.value === value ? styles.optionSelected : null,\n i === activeIndex ? styles.optionActive : null,\n opt.disabled ? styles.optionDisabled : null,\n ]\n .filter(Boolean)\n .join(\" \")}\n onMouseEnter={() => !opt.disabled && setActiveIndex(i)}\n onClick={() => selectOption(opt)}\n >\n <span className={styles.optionLabel}>{opt.label}</span>\n {opt.value === value && (\n <svg width=\"16\" height=\"16\" viewBox=\"0 0 16 16\" aria-hidden=\"true\" focusable=\"false\" className={styles.checkIcon}>\n <path\n d=\"M3 8l4 4 6-6\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth=\"1.75\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n />\n </svg>\n )}\n </li>\n ))}\n </ul>\n )}\n </div>\n </div>\n );\n}\n","/* ─── Row container ─────────────────────────────────────────────────────────── */\n\n.row {\n display: flex;\n align-items: center;\n gap: var(--gnome-space-2, 12px);\n padding: 0 var(--gnome-space-4, 24px);\n min-height: 52px;\n width: 100%;\n cursor: text;\n color: var(--gnome-card-fg-color, rgba(0, 0, 0, 0.8));\n\n transition: box-shadow var(--gnome-duration-fast) var(--gnome-easing-default);\n}\n\n.focused {\n box-shadow:\n inset 0 0 0 var(--gnome-focus-ring-offset, 2px) var(--gnome-window-bg-color, #fafafa),\n inset 0 0 0 calc(var(--gnome-focus-ring-offset, 2px) + var(--gnome-focus-ring-width, 3px))\n var(--gnome-focus-ring-color, #3584e4);\n}\n\n/* ─── Disabled ──────────────────────────────────────────────────────────────── */\n\n.disabled {\n opacity: var(--gnome-opacity-disabled, 0.5);\n cursor: not-allowed;\n pointer-events: none;\n}\n\n/* ─── Leading / trailing ────────────────────────────────────────────────────── */\n\n.leading {\n display: flex;\n align-items: center;\n flex-shrink: 0;\n color: var(--gnome-window-fg-color, rgba(0, 0, 0, 0.8));\n}\n\n.trailing {\n display: flex;\n align-items: center;\n flex-shrink: 0;\n gap: var(--gnome-space-1, 6px);\n}\n\n/* ─── Field wrap ────────────────────────────────────────────────────────────── */\n\n.fieldWrap {\n position: relative;\n display: flex;\n flex-direction: column;\n justify-content: center;\n flex: 1;\n min-width: 0;\n min-height: 52px;\n}\n\n/* ─── Floating label ────────────────────────────────────────────────────────── */\n\n.label {\n position: absolute;\n left: 0;\n top: 50%;\n transform: translateY(-50%);\n pointer-events: none;\n\n font-family: var(--gnome-font-family);\n font-size: var(--gnome-font-size-body, 1rem);\n line-height: var(--gnome-line-height-body, 1.5);\n color: var(--gnome-window-fg-color, rgba(0, 0, 0, 0.55));\n opacity: var(--gnome-opacity-dim, 0.55);\n\n transition:\n top var(--gnome-duration-fast) var(--gnome-easing-default),\n transform var(--gnome-duration-fast) var(--gnome-easing-default),\n font-size var(--gnome-duration-fast) var(--gnome-easing-default),\n opacity var(--gnome-duration-fast) var(--gnome-easing-default);\n}\n\n/* When floated (focused or has value) the label moves up */\n.labelFloated {\n top: 10px;\n transform: translateY(0);\n font-size: var(--gnome-font-size-caption, 0.75rem);\n opacity: 0.7;\n}\n\n/* ─── Input field ───────────────────────────────────────────────────────────── */\n\n.input {\n appearance: none;\n background: transparent;\n border: none;\n outline: none;\n padding: 0;\n margin: 0;\n width: 100%;\n\n font-family: var(--gnome-font-family);\n font-size: var(--gnome-font-size-body, 1rem);\n font-weight: var(--gnome-font-weight-normal, 400);\n line-height: var(--gnome-line-height-body, 1.5);\n color: var(--gnome-card-fg-color, rgba(0, 0, 0, 0.8));\n\n /* Hide until floated — the label is the placeholder */\n opacity: 0;\n padding-top: 18px;\n padding-bottom: 4px;\n\n transition: opacity var(--gnome-duration-fast) var(--gnome-easing-default);\n}\n\n.inputFloated {\n opacity: 1;\n}\n\n.input:disabled {\n cursor: not-allowed;\n}\n\n/* ─── High contrast ─────────────────────────────────────────────────────────── */\n\n@media (prefers-contrast: more) {\n .focused {\n box-shadow:\n inset 0 0 0 var(--gnome-focus-ring-offset, 2px) var(--gnome-window-bg-color, #fafafa),\n inset 0 0 0 calc(var(--gnome-focus-ring-offset, 2px) + var(--gnome-focus-ring-width, 3px))\n var(--gnome-focus-ring-color, #3584e4),\n inset 0 0 0 calc(var(--gnome-focus-ring-offset, 2px) + var(--gnome-focus-ring-width, 3px) + 1px)\n var(--gnome-window-bg-color, #fafafa);\n }\n}\n\n/* ─── Reduced motion ────────────────────────────────────────────────────────── */\n\n@media (prefers-reduced-motion: reduce) {\n .row,\n .label,\n .input {\n transition: none;\n }\n}\n","import { useState, useId, useRef, type InputHTMLAttributes, type ReactNode } from \"react\";\nimport styles from \"./EntryRow.module.css\";\n\nexport interface EntryRowProps extends InputHTMLAttributes<HTMLInputElement> {\n /**\n * Acts as a floating label: shown small above the input when the field has\n * content or focus, shown as a placeholder when empty and unfocused.\n */\n title: string;\n /** Called when the input value changes. */\n onValueChange?: (value: string) => void;\n /** Icon or widget placed at the leading edge. */\n leading?: ReactNode;\n /** Icon or widget placed at the trailing edge (e.g. a clear button, copy button). */\n trailing?: ReactNode;\n}\n\n/**\n * Row with an inline text entry field.\n *\n * Mirrors `AdwEntryRow` — the `title` floats up as a small label when the\n * field is focused or has content, acting as a placeholder when empty and\n * unfocused. Use inside a `BoxedList` for inline settings that require text input.\n *\n * @see https://gnome.pages.gitlab.gnome.org/libadwaita/doc/main/class.EntryRow.html\n */\nexport function EntryRow({\n title,\n value: controlledValue,\n defaultValue = \"\",\n onValueChange,\n leading,\n trailing,\n disabled,\n className,\n id: externalId,\n onChange,\n ...props\n}: EntryRowProps) {\n const generatedId = useId();\n const inputId = externalId ?? generatedId;\n\n const isControlled = controlledValue !== undefined;\n const [uncontrolledValue, setUncontrolledValue] = useState(String(defaultValue));\n const inputValue = isControlled ? String(controlledValue) : uncontrolledValue;\n\n const [focused, setFocused] = useState(false);\n const inputRef = useRef<HTMLInputElement>(null);\n\n const floated = focused || inputValue.length > 0;\n\n const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {\n if (!isControlled) setUncontrolledValue(e.target.value);\n onValueChange?.(e.target.value);\n onChange?.(e);\n };\n\n return (\n <div\n className={[\n styles.row,\n focused ? styles.focused : null,\n disabled ? styles.disabled : null,\n className,\n ]\n .filter(Boolean)\n .join(\" \")}\n onClick={() => inputRef.current?.focus()}\n >\n {leading && <span className={styles.leading}>{leading}</span>}\n\n <span className={styles.fieldWrap}>\n <label\n htmlFor={inputId}\n className={[styles.label, floated ? styles.labelFloated : null].filter(Boolean).join(\" \")}\n >\n {title}\n </label>\n <input\n ref={inputRef}\n id={inputId}\n className={[styles.input, floated ? styles.inputFloated : null].filter(Boolean).join(\" \")}\n value={inputValue}\n disabled={disabled}\n onFocus={() => setFocused(true)}\n onBlur={() => setFocused(false)}\n onChange={handleChange}\n {...props}\n />\n </span>\n\n {trailing && <span className={styles.trailing}>{trailing}</span>}\n </div>\n );\n}\n","/* ─── Reveal button ─────────────────────────────────────────────────────────── */\n\n.revealButton {\n display: flex;\n align-items: center;\n justify-content: center;\n width: 32px;\n height: 32px;\n padding: 0;\n border: none;\n border-radius: var(--gnome-radius-small, 6px);\n background: transparent;\n cursor: pointer;\n color: var(--gnome-window-fg-color, rgba(0, 0, 0, 0.8));\n opacity: var(--gnome-opacity-dim, 0.55);\n flex-shrink: 0;\n\n transition:\n opacity var(--gnome-duration-fast) var(--gnome-easing-default),\n background var(--gnome-duration-fast) var(--gnome-easing-default);\n}\n\n.revealButton:hover {\n opacity: 1;\n background: var(--gnome-button-bg-color, rgba(0, 0, 0, 0.07));\n}\n\n.revealButton:active {\n background: var(--gnome-button-active-bg-color, rgba(0, 0, 0, 0.14));\n}\n\n.revealButton:focus-visible {\n outline: none;\n box-shadow:\n 0 0 0 var(--gnome-focus-ring-offset, 2px) var(--gnome-window-bg-color, #fafafa),\n 0 0 0 calc(var(--gnome-focus-ring-offset, 2px) + var(--gnome-focus-ring-width, 3px))\n var(--gnome-focus-ring-color, #3584e4);\n opacity: 1;\n}\n\n.revealButton:disabled {\n pointer-events: none;\n opacity: var(--gnome-opacity-disabled, 0.5);\n}\n\n/* ─── Reduced motion ────────────────────────────────────────────────────────── */\n\n@media (prefers-reduced-motion: reduce) {\n .revealButton {\n transition: none;\n }\n}\n","import { useState, type ReactNode } from \"react\";\nimport { Icon } from \"../Icon\";\nimport { ViewReveal, ViewConceal } from \"@gnome-ui/icons\";\nimport { EntryRow, type EntryRowProps } from \"../EntryRow\";\nimport styles from \"./PasswordEntryRow.module.css\";\n\nexport interface PasswordEntryRowProps extends Omit<EntryRowProps, \"type\" | \"trailing\"> {\n /** Additional trailing widgets placed before the reveal button. */\n trailing?: ReactNode;\n}\n\n/**\n * Password entry row with a built-in reveal/conceal toggle.\n *\n * Mirrors `AdwPasswordEntryRow` — an `EntryRow` variant that defaults to\n * `type=\"password\"` and provides a trailing icon button to reveal or conceal\n * the entered text. Use inside a `BoxedList` for password settings fields.\n *\n * @see https://gnome.pages.gitlab.gnome.org/libadwaita/doc/main/class.PasswordEntryRow.html\n */\nexport function PasswordEntryRow({ trailing, disabled, ...props }: PasswordEntryRowProps) {\n const [revealed, setRevealed] = useState(false);\n\n const revealButton = (\n <button\n type=\"button\"\n className={styles.revealButton}\n onClick={(e) => {\n e.stopPropagation();\n setRevealed((v) => !v);\n }}\n aria-label={revealed ? \"Conceal password\" : \"Reveal password\"}\n aria-pressed={revealed}\n disabled={disabled}\n >\n <Icon\n icon={revealed ? ViewConceal : ViewReveal}\n size=\"md\"\n aria-hidden\n />\n </button>\n );\n\n return (\n <EntryRow\n {...props}\n type={revealed ? \"text\" : \"password\"}\n disabled={disabled}\n trailing={\n <>\n {trailing}\n {revealButton}\n </>\n }\n />\n );\n}\n","/* ─── Row container ─────────────────────────────────────────────────────────── */\n\n.row {\n display: flex;\n align-items: center;\n gap: var(--gnome-space-2, 12px);\n padding: var(--gnome-space-2, 12px) var(--gnome-space-4, 24px);\n min-height: 52px;\n width: 100%;\n color: var(--gnome-card-fg-color, rgba(0, 0, 0, 0.8));\n text-align: start;\n}\n\n/* ─── Disabled ──────────────────────────────────────────────────────────────── */\n\n.disabled {\n opacity: var(--gnome-opacity-disabled, 0.5);\n pointer-events: none;\n}\n\n/* ─── Leading ───────────────────────────────────────────────────────────────── */\n\n.leading {\n display: flex;\n align-items: center;\n flex-shrink: 0;\n color: var(--gnome-window-fg-color, rgba(0, 0, 0, 0.8));\n}\n\n/* ─── Text content ──────────────────────────────────────────────────────────── */\n\n.content {\n display: flex;\n flex-direction: column;\n flex: 1;\n min-width: 0;\n gap: 2px;\n}\n\n.title {\n font-family: var(--gnome-font-family);\n font-size: var(--gnome-font-size-body, 1rem);\n font-weight: var(--gnome-font-weight-normal, 400);\n line-height: var(--gnome-line-height-body, 1.5);\n color: var(--gnome-card-fg-color, rgba(0, 0, 0, 0.8));\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n}\n\n.subtitle {\n font-family: var(--gnome-font-family);\n font-size: var(--gnome-font-size-caption, 0.75rem);\n font-weight: var(--gnome-font-weight-normal, 400);\n line-height: var(--gnome-line-height-body, 1.5);\n color: var(--gnome-window-fg-color, rgba(0, 0, 0, 0.55));\n opacity: var(--gnome-opacity-dim, 0.55);\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n}\n\n/* ─── Spin widget ───────────────────────────────────────────────────────────── */\n\n.spin {\n display: inline-flex;\n align-items: center;\n flex-shrink: 0;\n\n background-color: var(--gnome-view-bg-color, #ffffff);\n border: 1px solid var(--gnome-card-shade-color, rgba(0, 0, 0, 0.15));\n border-radius: var(--gnome-radius-md, 8px);\n box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.06);\n overflow: hidden;\n\n font-family: var(--gnome-font-family);\n font-size: var(--gnome-font-size-body, 1rem);\n color: var(--gnome-window-fg-color, rgba(0, 0, 0, 0.8));\n\n transition:\n box-shadow var(--gnome-duration-fast) var(--gnome-easing-default),\n border-color var(--gnome-duration-fast) var(--gnome-easing-default);\n}\n\n.spin:focus {\n outline: none;\n}\n\n.spin:focus-visible {\n border-color: var(--gnome-accent-color, #3584e4);\n box-shadow:\n inset 0 1px 2px rgba(0, 0, 0, 0.06),\n 0 0 0 var(--gnome-focus-ring-offset, 2px) var(--gnome-window-bg-color, #fafafa),\n 0 0 0 calc(var(--gnome-focus-ring-offset, 2px) + var(--gnome-focus-ring-width, 3px))\n var(--gnome-focus-ring-color, #3584e4);\n}\n\n/* ─── −/+ buttons ───────────────────────────────────────────────────────────── */\n\n.spinBtn {\n appearance: none;\n -webkit-appearance: none;\n border: none;\n background: transparent;\n font: inherit;\n font-size: 1.1rem;\n line-height: 1;\n cursor: pointer;\n color: inherit;\n\n display: flex;\n align-items: center;\n justify-content: center;\n width: 32px;\n height: 34px;\n flex-shrink: 0;\n\n transition: background-color var(--gnome-duration-fast) var(--gnome-easing-default);\n}\n\n.spinBtn:hover:not(:disabled) {\n background-color: color-mix(in srgb, var(--gnome-view-bg-color, #ffffff) 90%, black 10%);\n}\n\n.spinBtn:active:not(:disabled) {\n background-color: color-mix(in srgb, var(--gnome-view-bg-color, #ffffff) 80%, black 20%);\n}\n\n.spinBtn:disabled {\n opacity: var(--gnome-opacity-disabled, 0.5);\n cursor: not-allowed;\n}\n\n.spinBtn:first-child {\n border-right: 1px solid var(--gnome-card-shade-color, rgba(0, 0, 0, 0.12));\n}\n\n.spinBtn:last-child {\n border-left: 1px solid var(--gnome-card-shade-color, rgba(0, 0, 0, 0.12));\n}\n\n/* ─── Value display ─────────────────────────────────────────────────────────── */\n\n.spinValue {\n text-align: center;\n padding: 0 var(--gnome-space-2, 12px);\n min-width: 48px;\n font-variant-numeric: tabular-nums;\n user-select: none;\n white-space: nowrap;\n}\n\n/* ─── Dark mode ─────────────────────────────────────────────────────────────── */\n\n@media (prefers-color-scheme: dark) {\n .spin {\n background-color: var(--gnome-view-bg-color, #1e1e1e);\n border-color: rgba(255, 255, 255, 0.12);\n box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.2);\n }\n\n .spinBtn:hover:not(:disabled) {\n background-color: color-mix(in srgb, var(--gnome-view-bg-color, #1e1e1e) 85%, white 15%);\n }\n\n .spinBtn:active:not(:disabled) {\n background-color: color-mix(in srgb, var(--gnome-view-bg-color, #1e1e1e) 75%, white 25%);\n }\n}\n\n/* ─── Reduced motion ────────────────────────────────────────────────────────── */\n\n@media (prefers-reduced-motion: reduce) {\n .spin,\n .spinBtn {\n transition: none;\n }\n}\n","import { useState, useId, useCallback, type HTMLAttributes, type ReactNode, type KeyboardEvent } from \"react\";\nimport styles from \"./SpinRow.module.css\";\n\nexport interface SpinRowProps extends Omit<HTMLAttributes<HTMLDivElement>, \"onChange\"> {\n /** Primary label. */\n title: string;\n /** Secondary line below the title. */\n subtitle?: string;\n /** Icon or image placed at the leading edge. */\n leading?: ReactNode;\n /** Current value (controlled). */\n value?: number;\n /** Initial value when uncontrolled. Defaults to `0`. */\n defaultValue?: number;\n /** Called when the value changes. */\n onValueChange?: (value: number) => void;\n /** Minimum allowed value. Defaults to `0`. */\n min?: number;\n /** Maximum allowed value. Defaults to `100`. */\n max?: number;\n /** Amount to increment/decrement per step. Defaults to `1`. */\n step?: number;\n /** Number of decimal places shown. Derived from `step` when omitted. */\n decimals?: number;\n /** Disables the row. */\n disabled?: boolean;\n}\n\nfunction countDecimals(n: number): number {\n const s = n.toString();\n const dot = s.indexOf(\".\");\n return dot === -1 ? 0 : s.length - dot - 1;\n}\n\nfunction clamp(v: number, min: number, max: number): number {\n return Math.min(max, Math.max(min, v));\n}\n\n/**\n * Settings row with an integrated spin button for numeric values.\n *\n * Mirrors `AdwSpinRow` — a standard row layout with − / + buttons at the\n * trailing edge. Use inside a `BoxedList` for settings with numeric ranges\n * (volume, timeout duration, count limits, etc.).\n *\n * Supports both controlled (`value`) and uncontrolled (`defaultValue`) modes.\n *\n * @see https://gnome.pages.gitlab.gnome.org/libadwaita/doc/main/class.SpinRow.html\n */\nexport function SpinRow({\n title,\n subtitle,\n leading,\n value: controlledValue,\n defaultValue = 0,\n onValueChange,\n min = 0,\n max = 100,\n step = 1,\n decimals,\n disabled = false,\n className,\n ...props\n}: SpinRowProps) {\n const isControlled = controlledValue !== undefined;\n const [uncontrolledValue, setUncontrolledValue] = useState(defaultValue);\n const value = isControlled ? controlledValue : uncontrolledValue;\n\n const dp = decimals ?? countDecimals(step);\n const spinId = useId();\n\n const set = useCallback(\n (next: number) => {\n const clamped = parseFloat(clamp(next, min, max).toFixed(dp));\n if (!isControlled) setUncontrolledValue(clamped);\n onValueChange?.(clamped);\n },\n [isControlled, min, max, dp, onValueChange],\n );\n\n const handleKeyDown = useCallback(\n (e: KeyboardEvent<HTMLDivElement>) => {\n switch (e.key) {\n case \"ArrowUp\": e.preventDefault(); set(value + step); break;\n case \"ArrowDown\": e.preventDefault(); set(value - step); break;\n case \"PageUp\": e.preventDefault(); set(value + step * 10); break;\n case \"PageDown\": e.preventDefault(); set(value - step * 10); break;\n case \"Home\": e.preventDefault(); set(min); break;\n case \"End\": e.preventDefault(); set(max); break;\n }\n },\n [value, step, min, max, set],\n );\n\n return (\n <div\n className={[styles.row, disabled ? styles.disabled : null, className]\n .filter(Boolean)\n .join(\" \")}\n {...props}\n >\n {leading && <span className={styles.leading}>{leading}</span>}\n\n <span className={styles.content}>\n <span className={styles.title}>{title}</span>\n {subtitle && <span className={styles.subtitle}>{subtitle}</span>}\n </span>\n\n {/* Spin button widget */}\n <div\n role=\"spinbutton\"\n aria-valuenow={value}\n aria-valuemin={min}\n aria-valuemax={max}\n aria-labelledby={spinId}\n aria-disabled={disabled || undefined}\n tabIndex={disabled ? -1 : 0}\n onKeyDown={disabled ? undefined : handleKeyDown}\n className={styles.spin}\n >\n <button\n type=\"button\"\n tabIndex={-1}\n aria-hidden=\"true\"\n disabled={disabled || value <= min}\n className={styles.spinBtn}\n onClick={(e) => { e.stopPropagation(); set(value - step); }}\n >\n −\n </button>\n\n <span id={spinId} className={styles.spinValue} aria-hidden=\"true\">\n {value.toFixed(dp)}\n </span>\n\n <button\n type=\"button\"\n tabIndex={-1}\n aria-hidden=\"true\"\n disabled={disabled || value >= max}\n className={styles.spinBtn}\n onClick={(e) => { e.stopPropagation(); set(value + step); }}\n >\n +\n </button>\n </div>\n </div>\n );\n}\n","/* ─── View container ────────────────────────────────────────────────────────── */\n\n.view {\n position: relative;\n overflow: hidden;\n width: 100%;\n height: 100%;\n}\n\n/* ─── Page ──────────────────────────────────────────────────────────────────── */\n\n.page {\n display: flex;\n flex-direction: column;\n width: 100%;\n height: 100%;\n background-color: var(--gnome-window-bg-color, #fafafa);\n color: var(--gnome-window-fg-color, rgba(0, 0, 0, 0.8));\n}\n\n/* ─── Page header (title bar) ────────────────────────────────────────────────── */\n\n.pageHeader {\n display: flex;\n align-items: center;\n justify-content: center;\n min-height: 47px;\n padding: 0 var(--gnome-space-4, 24px);\n border-bottom: 1px solid var(--gnome-headerbar-border-color, rgba(0, 0, 0, 0.12));\n background-color: var(--gnome-headerbar-bg-color, #ebebeb);\n flex-shrink: 0;\n}\n\n.pageTitle {\n font-family: var(--gnome-font-family);\n font-size: var(--gnome-font-size-body, 1rem);\n font-weight: var(--gnome-font-weight-semibold, 600);\n color: var(--gnome-headerbar-fg-color, rgba(0, 0, 0, 0.8));\n}\n\n/* ─── Page content ──────────────────────────────────────────────────────────── */\n\n.pageContent {\n flex: 1;\n overflow-y: auto;\n overflow-x: hidden;\n padding: var(--gnome-space-3, 16px);\n}\n\n/* ─── Enter animations ──────────────────────────────────────────────────────── */\n\n.enterForward {\n animation: slide-in-right var(--gnome-duration-normal, 200ms) var(--gnome-easing-default, ease) both;\n}\n\n.enterBack {\n animation: slide-in-left var(--gnome-duration-normal, 200ms) var(--gnome-easing-default, ease) both;\n}\n\n@keyframes slide-in-right {\n from { transform: translateX(40px); opacity: 0; }\n to { transform: translateX(0); opacity: 1; }\n}\n\n@keyframes slide-in-left {\n from { transform: translateX(-40px); opacity: 0; }\n to { transform: translateX(0); opacity: 1; }\n}\n\n/* ─── Reduced motion ────────────────────────────────────────────────────────── */\n\n@media (prefers-reduced-motion: reduce) {\n .enterForward,\n .enterBack {\n animation: none;\n }\n}\n","import {\n createContext,\n useContext,\n useState,\n type ReactNode,\n type HTMLAttributes,\n} from \"react\";\nimport styles from \"./NavigationView.module.css\";\n\n// ─── Context ─────────────────────────────────────────────────────────────────\n\ninterface NavigationContextValue {\n /** Navigate to a page by its `tag`. Pushes onto the stack. */\n navigate: (tag: string) => void;\n /** Go back one page. No-op if already at the root. */\n pop: () => void;\n /** `true` when there is at least one page to go back to. */\n canGoBack: boolean;\n /** Tag of the currently visible page. */\n currentTag: string;\n /** Direction of the last transition (`\"forward\"` or `\"back\"`). */\n direction: \"forward\" | \"back\";\n}\n\nconst NavigationContext = createContext<NavigationContextValue>({\n navigate: () => { },\n pop: () => { },\n canGoBack: false,\n currentTag: \"\",\n direction: \"forward\",\n});\n\n/** Access the nearest `NavigationView`'s navigation functions. */\nexport function useNavigation() {\n return useContext(NavigationContext);\n}\n\n// ─── NavigationPage ───────────────────────────────────────────────────────────\n\nexport interface NavigationPageProps extends HTMLAttributes<HTMLDivElement> {\n /** Unique identifier used with `navigate()`. */\n tag: string;\n /** Page heading shown in a `HeaderBar`-style title bar. */\n title: string;\n children?: ReactNode;\n}\n\n/**\n * A single page inside a `NavigationView`.\n * Only visible when its `tag` matches the current page in the stack.\n */\nexport function NavigationPage({\n tag,\n title,\n children,\n className,\n ...props\n}: NavigationPageProps) {\n const { currentTag, direction } = useContext(NavigationContext);\n if (currentTag !== tag) return null;\n\n return (\n <div\n className={[\n styles.page,\n direction === \"forward\" ? styles.enterForward : styles.enterBack,\n className,\n ]\n .filter(Boolean)\n .join(\" \")}\n {...props}\n >\n <div className={styles.pageHeader}>\n <span className={styles.pageTitle}>{title}</span>\n </div>\n <div className={styles.pageContent}>{children}</div>\n </div>\n );\n}\n\n// ─── NavigationView ───────────────────────────────────────────────────────────\n\nexport interface NavigationViewProps extends HTMLAttributes<HTMLDivElement> {\n /** Tag of the first page to show. */\n initialPage: string;\n children?: ReactNode;\n}\n\n/**\n * Single-pane push/pop navigation stack.\n *\n * Mirrors `AdwNavigationView` — the mobile-first counterpart to\n * `NavigationSplitView`. Each \"screen\" is a `NavigationPage` with a unique\n * `tag`. Use `useNavigation()` inside pages to call `navigate(tag)` and `pop()`.\n *\n * @example\n * ```tsx\n * <NavigationView initialPage=\"home\">\n * <NavigationPage tag=\"home\" title=\"Home\">\n * <Button onClick={() => navigate(\"details\")}>Open</Button>\n * </NavigationPage>\n * <NavigationPage tag=\"details\" title=\"Details\">\n * <Button onClick={pop}>Back</Button>\n * </NavigationPage>\n * </NavigationView>\n * ```\n *\n * @see https://gnome.pages.gitlab.gnome.org/libadwaita/doc/main/class.NavigationView.html\n */\nexport function NavigationView({\n initialPage,\n children,\n className,\n ...props\n}: NavigationViewProps) {\n const [stack, setStack] = useState<string[]>([initialPage]);\n\n const currentTag = stack[stack.length - 1];\n const canGoBack = stack.length > 1;\n const [direction, setDirection] = useState<\"forward\" | \"back\">(\"forward\");\n\n const navigate = (tag: string) => {\n setDirection(\"forward\");\n setStack((s) => [...s, tag]);\n };\n\n const pop = () => {\n if (stack.length <= 1) return;\n setDirection(\"back\");\n setStack((s) => s.slice(0, -1));\n };\n\n return (\n <NavigationContext.Provider value={{ navigate, pop, canGoBack, currentTag, direction }}>\n <div\n className={[styles.view, className].filter(Boolean).join(\" \")}\n {...props}\n >\n {children}\n </div>\n </NavigationContext.Provider>\n );\n}\n","/* ─── Backdrop ──────────────────────────────────────────────────────────────── */\n\n.backdrop {\n position: fixed;\n inset: 0;\n z-index: 10000;\n\n display: flex;\n align-items: flex-end;\n justify-content: center;\n\n background-color: rgba(0, 0, 0, 0.5);\n backdrop-filter: blur(4px);\n -webkit-backdrop-filter: blur(4px);\n\n animation: backdrop-in var(--gnome-duration-normal, 200ms) var(--gnome-easing-default, ease) both;\n}\n\n@keyframes backdrop-in {\n from { opacity: 0; }\n to { opacity: 1; }\n}\n\n/* ─── Sheet ─────────────────────────────────────────────────────────────────── */\n\n.sheet {\n width: min(560px, 100vw);\n max-height: 90dvh;\n overflow-y: auto;\n\n background-color: var(--gnome-dialog-bg-color, var(--gnome-window-bg-color, #fafafa));\n color: var(--gnome-window-fg-color, rgba(0, 0, 0, 0.8));\n\n border-radius: var(--gnome-radius-xl, 16px) var(--gnome-radius-xl, 16px) 0 0;\n box-shadow:\n 0 -4px 24px rgba(0, 0, 0, 0.18),\n 0 0 0 1px rgba(0, 0, 0, 0.06);\n\n animation: sheet-in var(--gnome-duration-normal, 200ms) var(--gnome-easing-default, ease) both;\n}\n\n@keyframes sheet-in {\n from { transform: translateY(100%); }\n to { transform: translateY(0); }\n}\n\n/* ─── Handle bar ────────────────────────────────────────────────────────────── */\n\n.handle {\n display: flex;\n align-items: center;\n justify-content: center;\n padding: var(--gnome-space-2, 12px) 0 var(--gnome-space-1, 6px);\n}\n\n.handleBar {\n width: 36px;\n height: 4px;\n border-radius: 2px;\n background-color: var(--gnome-window-fg-color, rgba(0, 0, 0, 0.8));\n opacity: 0.2;\n}\n\n/* ─── Title ─────────────────────────────────────────────────────────────────── */\n\n.title {\n padding: 0 var(--gnome-space-4, 24px) var(--gnome-space-2, 12px);\n\n font-family: var(--gnome-font-family);\n font-size: var(--gnome-font-size-title-4, 1.125rem);\n font-weight: var(--gnome-font-weight-bold, 700);\n line-height: var(--gnome-line-height-title, 1.3);\n color: var(--gnome-window-fg-color, rgba(0, 0, 0, 0.8));\n text-align: center;\n}\n\n/* ─── Content ───────────────────────────────────────────────────────────────── */\n\n.content {\n padding: 0 var(--gnome-space-4, 24px) var(--gnome-space-4, 24px);\n}\n\n/* ─── Dark mode ─────────────────────────────────────────────────────────────── */\n\n@media (prefers-color-scheme: dark) {\n .backdrop {\n background-color: rgba(0, 0, 0, 0.65);\n }\n\n .sheet {\n background-color: var(--gnome-dialog-bg-color, var(--gnome-window-bg-color, #242424));\n box-shadow:\n 0 -4px 24px rgba(0, 0, 0, 0.45),\n 0 0 0 1px rgba(255, 255, 255, 0.06);\n }\n}\n\n/* ─── Reduced motion ────────────────────────────────────────────────────────── */\n\n@media (prefers-reduced-motion: reduce) {\n .backdrop,\n .sheet {\n animation: none;\n }\n}\n","import {\n useEffect,\n useRef,\n useCallback,\n useId,\n type KeyboardEvent,\n type HTMLAttributes,\n type ReactNode,\n} from \"react\";\nimport { createPortal } from \"react-dom\";\nimport styles from \"./BottomSheet.module.css\";\n\nconst FOCUSABLE =\n 'button:not([disabled]), [href], input:not([disabled]), select:not([disabled]), textarea:not([disabled]), [tabindex]:not([tabindex=\"-1\"])';\n\nexport interface BottomSheetProps extends Omit<HTMLAttributes<HTMLDivElement>, \"title\"> {\n /** Whether the sheet is visible. */\n open: boolean;\n /**\n * Optional heading shown in the drag handle area.\n */\n title?: ReactNode;\n children?: ReactNode;\n /** Called when the user dismisses the sheet (Escape or backdrop click). */\n onClose?: () => void;\n /**\n * Whether clicking the backdrop closes the sheet.\n * Defaults to `true`.\n */\n closeOnBackdrop?: boolean;\n}\n\n/**\n * Slide-up panel that overlays content from the bottom edge.\n *\n * Mirrors `AdwBottomSheet` (libadwaita 1.6+). Renders into a portal on\n * `document.body`. Traps focus, closes on Escape or backdrop click.\n * The handle bar is decorative — drag-to-close is not implemented.\n *\n * @see https://gnome.pages.gitlab.gnome.org/libadwaita/doc/main/class.BottomSheet.html\n */\nexport function BottomSheet({\n open,\n title,\n children,\n onClose,\n closeOnBackdrop = true,\n className,\n ...props\n}: BottomSheetProps) {\n const sheetRef = useRef<HTMLDivElement>(null);\n const titleId = useId();\n const previouslyFocused = useRef<Element | null>(null);\n\n // Save / restore focus\n useEffect(() => {\n if (open) {\n previouslyFocused.current = document.activeElement;\n const el = sheetRef.current?.querySelector<HTMLElement>(FOCUSABLE);\n el?.focus();\n } else {\n (previouslyFocused.current as HTMLElement | null)?.focus();\n }\n }, [open]);\n\n // Focus trap + Escape\n const handleKeyDown = useCallback(\n (e: KeyboardEvent<HTMLDivElement>) => {\n if (e.key === \"Escape\") {\n e.preventDefault();\n onClose?.();\n return;\n }\n if (e.key !== \"Tab\") return;\n const focusable = Array.from(\n sheetRef.current?.querySelectorAll<HTMLElement>(FOCUSABLE) ?? []\n );\n if (focusable.length === 0) return;\n const first = focusable[0];\n const last = focusable[focusable.length - 1];\n if (e.shiftKey) {\n if (document.activeElement === first) { e.preventDefault(); last.focus(); }\n } else {\n if (document.activeElement === last) { e.preventDefault(); first.focus(); }\n }\n },\n [onClose]\n );\n\n if (!open) return null;\n\n const node = (\n <div\n className={styles.backdrop}\n onClick={closeOnBackdrop ? onClose : undefined}\n aria-hidden=\"true\"\n >\n <div\n ref={sheetRef}\n role=\"dialog\"\n aria-modal=\"true\"\n aria-labelledby={title ? titleId : undefined}\n className={[styles.sheet, className].filter(Boolean).join(\" \")}\n onKeyDown={handleKeyDown}\n onClick={(e) => e.stopPropagation()}\n {...props}\n >\n {/* Handle bar */}\n <div className={styles.handle} aria-hidden=\"true\">\n <div className={styles.handleBar} />\n </div>\n\n {/* Optional title */}\n {title && (\n <div id={titleId} className={styles.title}>{title}</div>\n )}\n\n {/* Content */}\n {children && (\n <div className={styles.content}>{children}</div>\n )}\n </div>\n </div>\n );\n\n if (typeof document === \"undefined\") return node;\n return createPortal(node, document.body);\n}\n","/* ─── Carousel container ────────────────────────────────────────────────────── */\n\n.carousel {\n display: flex;\n overflow: hidden;\n scroll-snap-type: both mandatory;\n -webkit-overflow-scrolling: touch;\n outline: none;\n width: 100%;\n}\n\n.carousel:focus-visible {\n box-shadow:\n 0 0 0 var(--gnome-focus-ring-offset, 2px) var(--gnome-window-bg-color, #fafafa),\n 0 0 0 calc(var(--gnome-focus-ring-offset, 2px) + var(--gnome-focus-ring-width, 3px))\n var(--gnome-focus-ring-color, #3584e4);\n border-radius: var(--gnome-radius-md, 8px);\n}\n\n.horizontal {\n flex-direction: row;\n overflow-x: scroll;\n overflow-y: hidden;\n scroll-snap-type: x mandatory;\n}\n\n.vertical {\n flex-direction: column;\n overflow-y: scroll;\n overflow-x: hidden;\n scroll-snap-type: y mandatory;\n}\n\n/* ─── Slide ─────────────────────────────────────────────────────────────────── */\n\n.slide {\n flex: 0 0 100%;\n scroll-snap-align: start;\n scroll-snap-stop: always;\n min-width: 0;\n min-height: 0;\n}\n\n/* ─── Dot indicators ────────────────────────────────────────────────────────── */\n\n.indicatorDots {\n display: flex;\n align-items: center;\n justify-content: center;\n gap: 6px;\n padding: var(--gnome-space-2, 12px) 0;\n}\n\n.dot {\n appearance: none;\n border: none;\n padding: 0;\n cursor: pointer;\n width: 8px;\n height: 8px;\n border-radius: 50%;\n background-color: var(--gnome-window-fg-color, rgba(0, 0, 0, 0.8));\n opacity: 0.2;\n transition:\n opacity var(--gnome-duration-fast) var(--gnome-easing-default),\n transform var(--gnome-duration-fast) var(--gnome-easing-default);\n}\n\n.dot:hover {\n opacity: 0.5;\n}\n\n.dotActive {\n opacity: 1;\n transform: scale(1.15);\n}\n\n.dot:focus-visible {\n outline: 2px solid var(--gnome-focus-ring-color, #3584e4);\n outline-offset: 2px;\n}\n\n/* ─── Line indicators ───────────────────────────────────────────────────────── */\n\n.indicatorLines {\n display: flex;\n align-items: center;\n justify-content: center;\n gap: 4px;\n padding: var(--gnome-space-2, 12px) 0;\n}\n\n.line {\n appearance: none;\n border: none;\n padding: 0;\n cursor: pointer;\n width: 24px;\n height: 3px;\n border-radius: 2px;\n background-color: var(--gnome-window-fg-color, rgba(0, 0, 0, 0.8));\n opacity: 0.2;\n transition: opacity var(--gnome-duration-fast) var(--gnome-easing-default);\n}\n\n.line:hover {\n opacity: 0.5;\n}\n\n.lineActive {\n opacity: 1;\n}\n\n.line:focus-visible {\n outline: 2px solid var(--gnome-focus-ring-color, #3584e4);\n outline-offset: 2px;\n}\n\n/* ─── Reduced motion ────────────────────────────────────────────────────────── */\n\n@media (prefers-reduced-motion: reduce) {\n .carousel {\n scroll-behavior: auto;\n }\n\n .dot,\n .line {\n transition: none;\n }\n}\n","import {\n useRef,\n useState,\n useEffect,\n useCallback,\n Children,\n type HTMLAttributes,\n type ReactNode,\n} from \"react\";\nimport styles from \"./Carousel.module.css\";\n\n// ─── CarouselIndicatorDots ────────────────────────────────────────────────────\n\nexport interface CarouselIndicatorDotsProps extends HTMLAttributes<HTMLDivElement> {\n /** Total number of pages. */\n pages: number;\n /** Zero-based index of the current page. */\n currentPage: number;\n /** Called when the user clicks a dot. */\n onPageSelected?: (index: number) => void;\n}\n\n/**\n * Dot-style page indicator for `Carousel`.\n * Mirrors `AdwCarouselIndicatorDots`.\n */\nexport function CarouselIndicatorDots({\n pages,\n currentPage,\n onPageSelected,\n className,\n ...props\n}: CarouselIndicatorDotsProps) {\n return (\n <div\n className={[styles.indicatorDots, className].filter(Boolean).join(\" \")}\n role=\"tablist\"\n aria-label=\"Carousel pages\"\n {...props}\n >\n {Array.from({ length: pages }, (_, i) => (\n <button\n key={i}\n type=\"button\"\n role=\"tab\"\n aria-selected={i === currentPage}\n aria-label={`Page ${i + 1}`}\n className={[styles.dot, i === currentPage ? styles.dotActive : null]\n .filter(Boolean)\n .join(\" \")}\n onClick={() => onPageSelected?.(i)}\n />\n ))}\n </div>\n );\n}\n\n// ─── CarouselIndicatorLines ───────────────────────────────────────────────────\n\nexport interface CarouselIndicatorLinesProps extends HTMLAttributes<HTMLDivElement> {\n /** Total number of pages. */\n pages: number;\n /** Zero-based index of the current page. */\n currentPage: number;\n /** Called when the user clicks a line. */\n onPageSelected?: (index: number) => void;\n}\n\n/**\n * Line-style page indicator for `Carousel`.\n * Mirrors `AdwCarouselIndicatorLines`.\n */\nexport function CarouselIndicatorLines({\n pages,\n currentPage,\n onPageSelected,\n className,\n ...props\n}: CarouselIndicatorLinesProps) {\n return (\n <div\n className={[styles.indicatorLines, className].filter(Boolean).join(\" \")}\n role=\"tablist\"\n aria-label=\"Carousel pages\"\n {...props}\n >\n {Array.from({ length: pages }, (_, i) => (\n <button\n key={i}\n type=\"button\"\n role=\"tab\"\n aria-selected={i === currentPage}\n aria-label={`Page ${i + 1}`}\n className={[styles.line, i === currentPage ? styles.lineActive : null]\n .filter(Boolean)\n .join(\" \")}\n onClick={() => onPageSelected?.(i)}\n />\n ))}\n </div>\n );\n}\n\n// ─── Carousel ─────────────────────────────────────────────────────────────────\n\nexport interface CarouselProps extends HTMLAttributes<HTMLDivElement> {\n children?: ReactNode;\n /**\n * Scroll orientation.\n * @default \"horizontal\"\n */\n orientation?: \"horizontal\" | \"vertical\";\n /**\n * Gap between pages in px.\n * @default 0\n */\n spacing?: number;\n /**\n * Allow infinite looping (wraps around on the last/first page).\n * @default false\n */\n loop?: boolean;\n /** Called whenever the visible page changes. */\n onPageChanged?: (index: number) => void;\n /**\n * Controlled current page index.\n * When omitted the carousel manages page state internally.\n */\n page?: number;\n}\n\n/**\n * Swipeable content carousel.\n *\n * Mirrors `AdwCarousel`. Uses CSS scroll-snapping for smooth, native-feeling\n * page transitions. Supports keyboard navigation (arrow keys) and touch/mouse\n * drag via the browser's native scroll handling.\n *\n * Pair with `CarouselIndicatorDots` or `CarouselIndicatorLines` for pagination UI.\n *\n * @see https://gnome.pages.gitlab.gnome.org/libadwaita/doc/main/class.Carousel.html\n */\nexport function Carousel({\n children,\n orientation = \"horizontal\",\n spacing = 0,\n loop = false,\n onPageChanged,\n page: controlledPage,\n className,\n ...props\n}: CarouselProps) {\n const scrollRef = useRef<HTMLDivElement>(null);\n const pageCount = Children.count(children);\n const [internalPage, setInternalPage] = useState(0);\n const isControlled = controlledPage !== undefined;\n const currentPage = isControlled ? controlledPage : internalPage;\n\n // Scroll to the controlled page when it changes externally\n useEffect(() => {\n if (!isControlled) return;\n scrollToPage(controlledPage, \"smooth\");\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [controlledPage]);\n\n const scrollToPage = useCallback(\n (index: number, behavior: ScrollBehavior = \"smooth\") => {\n const el = scrollRef.current;\n if (!el) return;\n if (orientation === \"horizontal\") {\n el.scrollTo({ left: el.clientWidth * index, behavior });\n } else {\n el.scrollTo({ top: el.clientHeight * index, behavior });\n }\n },\n [orientation]\n );\n\n // Detect page changes via IntersectionObserver on each child\n useEffect(() => {\n const el = scrollRef.current;\n if (!el) return;\n\n const handleScroll = () => {\n const pages = Array.from(el.children) as HTMLElement[];\n const size = orientation === \"horizontal\" ? el.clientWidth : el.clientHeight;\n const scroll = orientation === \"horizontal\" ? el.scrollLeft : el.scrollTop;\n const idx = Math.round(scroll / (size || 1));\n const clamped = Math.max(0, Math.min(idx, pageCount - 1));\n if (!isControlled) setInternalPage(clamped);\n onPageChanged?.(clamped);\n void pages; // suppress lint\n };\n\n el.addEventListener(\"scroll\", handleScroll, { passive: true });\n return () => el.removeEventListener(\"scroll\", handleScroll);\n }, [orientation, pageCount, isControlled, onPageChanged]);\n\n // Keyboard navigation\n const handleKeyDown = useCallback(\n (e: React.KeyboardEvent) => {\n const isForward =\n orientation === \"horizontal\" ? e.key === \"ArrowRight\" : e.key === \"ArrowDown\";\n const isBack =\n orientation === \"horizontal\" ? e.key === \"ArrowLeft\" : e.key === \"ArrowUp\";\n\n if (isForward) {\n e.preventDefault();\n const next = loop\n ? (currentPage + 1) % pageCount\n : Math.min(currentPage + 1, pageCount - 1);\n scrollToPage(next);\n if (!isControlled) setInternalPage(next);\n onPageChanged?.(next);\n } else if (isBack) {\n e.preventDefault();\n const prev = loop\n ? (currentPage - 1 + pageCount) % pageCount\n : Math.max(currentPage - 1, 0);\n scrollToPage(prev);\n if (!isControlled) setInternalPage(prev);\n onPageChanged?.(prev);\n }\n },\n [currentPage, pageCount, loop, orientation, scrollToPage, isControlled, onPageChanged]\n );\n\n const isHorizontal = orientation === \"horizontal\";\n\n return (\n <div\n ref={scrollRef}\n role=\"region\"\n aria-roledescription=\"carousel\"\n tabIndex={0}\n className={[\n styles.carousel,\n isHorizontal ? styles.horizontal : styles.vertical,\n className,\n ]\n .filter(Boolean)\n .join(\" \")}\n style={\n isHorizontal\n ? { columnGap: spacing || undefined }\n : { rowGap: spacing || undefined }\n }\n onKeyDown={handleKeyDown}\n {...props}\n >\n {Children.map(children, (child, i) => (\n <div\n key={i}\n className={styles.slide}\n role=\"group\"\n aria-roledescription=\"slide\"\n aria-label={`${i + 1} of ${pageCount}`}\n >\n {child}\n </div>\n ))}\n </div>\n );\n}\n","import type { HTMLAttributes, ReactNode } from \"react\";\n\nexport interface BinProps extends HTMLAttributes<HTMLDivElement> {\n /** Single child. */\n children?: ReactNode;\n}\n\n/**\n * Single-child container with no visual styling.\n *\n * A transparent wrapper that constrains its content to one child while\n * forwarding all HTML div attributes. Useful as a base for custom components\n * that need a neutral container — e.g. to apply layout or size constraints\n * without introducing visual chrome.\n *\n * Mirrors `AdwBin`.\n *\n * @see https://gnome.pages.gitlab.gnome.org/libadwaita/doc/main/class.Bin.html\n */\nexport function Bin({ children, ...props }: BinProps) {\n return <div {...props}>{children}</div>;\n}\n","/* ─── ButtonContent ──────────────────────────────────────────────────────────── */\n\n.content {\n display: inline-flex;\n align-items: center;\n gap: 6px;\n}\n\n.icon {\n display: inline-flex;\n align-items: center;\n flex-shrink: 0;\n}\n\n.label {\n /* Inherit all button text styles */\n}\n","import type { HTMLAttributes, ReactNode } from \"react\";\nimport styles from \"./ButtonContent.module.css\";\n\nexport interface ButtonContentProps extends HTMLAttributes<HTMLSpanElement> {\n /** Icon element placed before the label. */\n icon?: ReactNode;\n /** Text label. */\n label: string;\n /**\n * Position of the icon relative to the label.\n * Defaults to `\"start\"`.\n */\n iconPosition?: \"start\" | \"end\";\n}\n\n/**\n * Icon + label layout helper for buttons that contain both an icon and text.\n *\n * Pass as the `children` of a `Button` to get the correct Adwaita spacing\n * between the icon and label (4 px gap, icon centred vertically with the text).\n *\n * Mirrors `AdwButtonContent`.\n *\n * @example\n * ```tsx\n * <Button>\n * <ButtonContent icon={<Icon name=\"document-save-symbolic\" />} label=\"Save\" />\n * </Button>\n * ```\n *\n * @see https://gnome.pages.gitlab.gnome.org/libadwaita/doc/main/class.ButtonContent.html\n */\nexport function ButtonContent({\n icon,\n label,\n iconPosition = \"start\",\n className,\n ...props\n}: ButtonContentProps) {\n return (\n <span\n className={[\n styles.content,\n iconPosition === \"end\" ? styles.iconEnd : null,\n className,\n ].filter(Boolean).join(\" \")}\n {...props}\n >\n {icon && iconPosition === \"start\" && (\n <span className={styles.icon} aria-hidden=\"true\">{icon}</span>\n )}\n <span className={styles.label}>{label}</span>\n {icon && iconPosition === \"end\" && (\n <span className={styles.icon} aria-hidden=\"true\">{icon}</span>\n )}\n </span>\n );\n}\n","/* ─── ShortcutLabel ──────────────────────────────────────────────────────────── */\n\n.label {\n display: inline-flex;\n align-items: center;\n gap: 2px;\n}\n\n.key {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n\n min-width: 22px;\n height: 22px;\n padding: 0 5px;\n\n font-family: var(--gnome-font-family);\n font-size: var(--gnome-font-size-caption, 0.75rem);\n font-weight: var(--gnome-font-weight-semibold, 600);\n font-style: normal;\n line-height: 1;\n color: var(--gnome-window-fg-color, rgba(0, 0, 0, 0.8));\n\n background-color: var(--gnome-card-bg-color, rgba(0, 0, 0, 0.06));\n border: 1px solid var(--gnome-card-shade-color, rgba(0, 0, 0, 0.15));\n border-radius: var(--gnome-radius-sm, 4px);\n box-shadow: 0 1px 0 var(--gnome-card-shade-color, rgba(0, 0, 0, 0.12));\n}\n\n/* ─── Dark mode ──────────────────────────────────────────────────────────────── */\n\n@media (prefers-color-scheme: dark) {\n .key {\n background-color: rgba(255, 255, 255, 0.08);\n border-color: rgba(255, 255, 255, 0.15);\n box-shadow: 0 1px 0 rgba(255, 255, 255, 0.06);\n }\n}\n\n/* ─── High contrast ──────────────────────────────────────────────────────────── */\n\n@media (prefers-contrast: more) {\n .key {\n border-width: 2px;\n box-shadow: none;\n }\n}\n","import type { HTMLAttributes } from \"react\";\nimport styles from \"./ShortcutLabel.module.css\";\n\nexport interface ShortcutLabelProps extends HTMLAttributes<HTMLSpanElement> {\n /**\n * Keyboard shortcut string.\n *\n * Tokens are separated by `+`. Each token is rendered as its own key cap.\n * Common modifiers are normalised to their symbol:\n * - `Ctrl` / `Control` → `⌃`\n * - `Shift` → `⇧`\n * - `Alt` / `Option` → `⌥`\n * - `Super` / `Win` / `Cmd` / `Command` → `⊞`\n * - `Up` → `↑` `Down` → `↓` `Left` → `←` `Right` → `→`\n * - `Enter` / `Return` → `↵`\n * - `Backspace` → `⌫`\n * - `Delete` → `⌦`\n * - `Escape` / `Esc` → `⎋`\n * - `Tab` → `⇥`\n * - `Space` → `␣`\n *\n * @example \"Ctrl+S\" → ⌃ S\n * @example \"Ctrl+Shift+Z\" → ⌃ ⇧ Z\n */\n shortcut: string;\n /** When true, modifier tokens are shown as symbols. Defaults to `true`. */\n symbols?: boolean;\n}\n\nconst SYMBOL_MAP: Record<string, string> = {\n ctrl: \"⌃\",\n control: \"⌃\",\n shift: \"⇧\",\n alt: \"⌥\",\n option: \"⌥\",\n super: \"⊞\",\n win: \"⊞\",\n cmd: \"⌘\",\n command: \"⌘\",\n meta: \"⌘\",\n up: \"↑\",\n down: \"↓\",\n left: \"←\",\n right: \"→\",\n enter: \"↵\",\n return: \"↵\",\n backspace: \"⌫\",\n delete: \"⌦\",\n escape: \"⎋\",\n esc: \"⎋\",\n tab: \"⇥\",\n space: \"␣\",\n pageup: \"⇞\",\n pagedown: \"⇟\",\n home: \"⇱\",\n end: \"⇲\",\n};\n\n/**\n * Read-only display of a keyboard shortcut with per-key key-cap styling.\n *\n * Each token in the `shortcut` string (split by `+`) is rendered as a\n * separate `<kbd>` element. Modifier keys are normalised to their Unicode\n * symbols by default.\n *\n * Mirrors `GtkShortcutLabel`.\n *\n * @see https://docs.gtk.org/gtk4/class.ShortcutLabel.html\n */\nexport function ShortcutLabel({ shortcut, symbols = true, className, ...props }: ShortcutLabelProps) {\n const tokens = shortcut.split(\"+\").map((t) => t.trim()).filter(Boolean);\n\n return (\n <span\n className={[styles.label, className].filter(Boolean).join(\" \")}\n aria-label={shortcut}\n {...props}\n >\n {tokens.map((token, i) => {\n const display = symbols\n ? (SYMBOL_MAP[token.toLowerCase()] ?? token)\n : token;\n\n return (\n <kbd key={i} className={styles.key}>\n {display}\n </kbd>\n );\n })}\n </span>\n );\n}\n","/* ─── ToolbarView ────────────────────────────────────────────────────────────── */\n\n.toolbarView {\n display: flex;\n flex-direction: column;\n height: 100%;\n overflow: hidden;\n}\n\n/* Top bar — fixed, no scroll */\n.top {\n flex-shrink: 0;\n}\n\n/* Scrollable content */\n.content {\n flex: 1;\n overflow-y: auto;\n min-height: 0;\n}\n\n/* Bottom bar — fixed, no scroll */\n.bottom {\n flex-shrink: 0;\n}\n","import type { HTMLAttributes, ReactNode } from \"react\";\nimport styles from \"./ToolbarView.module.css\";\n\nexport interface ToolbarViewProps extends HTMLAttributes<HTMLDivElement> {\n /**\n * Bar(s) pinned to the top — typically a `HeaderBar` or `Toolbar`.\n * Multiple bars stack vertically.\n */\n topBar?: ReactNode;\n /**\n * Bar(s) pinned to the bottom — typically a `Toolbar` or `ViewSwitcherBar`.\n * Multiple bars stack vertically.\n */\n bottomBar?: ReactNode;\n /** Scrollable content area between the bars. */\n children?: ReactNode;\n}\n\n/**\n * Layout container that attaches bars at the top and/or bottom while\n * scrolling only the middle content.\n *\n * The top/bottom bars remain fixed; the inner content area gets\n * `overflow-y: auto` so it scrolls independently.\n *\n * Mirrors `AdwToolbarView`.\n *\n * @see https://gnome.pages.gitlab.gnome.org/libadwaita/doc/main/class.ToolbarView.html\n */\nexport function ToolbarView({\n topBar,\n bottomBar,\n children,\n className,\n ...props\n}: ToolbarViewProps) {\n return (\n <div\n className={[styles.toolbarView, className].filter(Boolean).join(\" \")}\n {...props}\n >\n {topBar && <div className={styles.top}>{topBar}</div>}\n <div className={styles.content}>{children}</div>\n {bottomBar && <div className={styles.bottom}>{bottomBar}</div>}\n </div>\n );\n}\n","/* ─── WindowTitle ────────────────────────────────────────────────────────────── */\n\n.windowTitle {\n display: flex;\n flex-direction: column;\n align-items: center;\n gap: 1px;\n min-width: 0;\n}\n\n.title {\n font-family: var(--gnome-font-family);\n font-size: var(--gnome-font-size-body, 1rem);\n font-weight: var(--gnome-font-weight-semibold, 600);\n line-height: 1.2;\n color: var(--gnome-headerbar-fg-color, var(--gnome-window-fg-color, rgba(0, 0, 0, 0.8)));\n\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n max-width: 100%;\n}\n\n.subtitle {\n font-family: var(--gnome-font-family);\n font-size: var(--gnome-font-size-caption, 0.75rem);\n font-weight: var(--gnome-font-weight-normal, 400);\n line-height: 1.2;\n color: var(--gnome-headerbar-fg-color, var(--gnome-window-fg-color, rgba(0, 0, 0, 0.8)));\n opacity: var(--gnome-opacity-dim, 0.55);\n\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n max-width: 100%;\n}\n","import type { HTMLAttributes } from \"react\";\nimport styles from \"./WindowTitle.module.css\";\n\nexport interface WindowTitleProps extends HTMLAttributes<HTMLDivElement> {\n /** Primary title — rendered in bold. */\n title: string;\n /** Secondary subtitle — rendered smaller and dimmed below the title. */\n subtitle?: string;\n}\n\n/**\n * Two-line title + subtitle widget for use inside a `HeaderBar`.\n *\n * Pass as the `title` prop of `HeaderBar` to get a centred two-line header\n * that shows an app name and current document/view name.\n *\n * Mirrors `AdwWindowTitle`.\n *\n * @example\n * ```tsx\n * <HeaderBar title={<WindowTitle title=\"Files\" subtitle=\"/home/user/Documents\" />} />\n * ```\n *\n * @see https://gnome.pages.gitlab.gnome.org/libadwaita/doc/main/class.WindowTitle.html\n */\nexport function WindowTitle({ title, subtitle, className, ...props }: WindowTitleProps) {\n return (\n <div\n className={[styles.windowTitle, className].filter(Boolean).join(\" \")}\n {...props}\n >\n <span className={styles.title}>{title}</span>\n {subtitle && <span className={styles.subtitle}>{subtitle}</span>}\n </div>\n );\n}\n","/* ─── PreferencesGroup ───────────────────────────────────────────────────────── */\n\n.group {\n display: flex;\n flex-direction: column;\n gap: var(--gnome-space-2, 12px);\n}\n\n/* ─── Header ────────────────────────────────────────────────────────────────── */\n\n.header {\n display: flex;\n align-items: baseline;\n gap: var(--gnome-space-2, 12px);\n}\n\n.headerText {\n flex: 1;\n min-width: 0;\n display: flex;\n flex-direction: column;\n gap: 2px;\n}\n\n.title {\n font-family: var(--gnome-font-family);\n font-size: var(--gnome-font-size-body, 1rem);\n font-weight: var(--gnome-font-weight-semibold, 600);\n line-height: var(--gnome-line-height-body, 1.5);\n color: var(--gnome-window-fg-color, rgba(0, 0, 0, 0.8));\n}\n\n.description {\n font-family: var(--gnome-font-family);\n font-size: var(--gnome-font-size-caption, 0.75rem);\n font-weight: var(--gnome-font-weight-normal, 400);\n line-height: var(--gnome-line-height-body, 1.5);\n color: var(--gnome-window-fg-color, rgba(0, 0, 0, 0.8));\n opacity: var(--gnome-opacity-dim, 0.55);\n}\n\n.suffix {\n flex-shrink: 0;\n}\n\n/* ─── Content ───────────────────────────────────────────────────────────────── */\n\n.content {\n /* Rows/BoxedList rendered here */\n}\n","import { type HTMLAttributes, type ReactNode } from \"react\";\nimport styles from \"./PreferencesGroup.module.css\";\n\nexport interface PreferencesGroupProps extends HTMLAttributes<HTMLDivElement> {\n /** Group heading. */\n title?: string;\n /** Optional description rendered below the title. */\n description?: string;\n /** Widget placed at the trailing edge of the title row (e.g. a reset Button). */\n headerSuffix?: ReactNode;\n /** `BoxedList` rows or any row-shaped content. */\n children?: ReactNode;\n}\n\n/**\n * Titled section that wraps a `BoxedList` with an optional description.\n *\n * Use inside a `PreferencesPage` to group related settings under a named\n * heading. The group is purely a layout and labelling wrapper — it does not\n * render the `BoxedList` itself; pass it as `children`.\n *\n * Mirrors `AdwPreferencesGroup`.\n *\n * @see https://gnome.pages.gitlab.gnome.org/libadwaita/doc/main/class.PreferencesGroup.html\n */\nexport function PreferencesGroup({\n title,\n description,\n headerSuffix,\n children,\n className,\n ...props\n}: PreferencesGroupProps) {\n const hasHeader = title || description || headerSuffix;\n\n return (\n <div\n className={[styles.group, className].filter(Boolean).join(\" \")}\n {...props}\n >\n {hasHeader && (\n <div className={styles.header}>\n <div className={styles.headerText}>\n {title && <span className={styles.title}>{title}</span>}\n {description && <span className={styles.description}>{description}</span>}\n </div>\n {headerSuffix && <div className={styles.suffix}>{headerSuffix}</div>}\n </div>\n )}\n <div className={styles.content}>{children}</div>\n </div>\n );\n}\n","/* ─── PreferencesPage ────────────────────────────────────────────────────────── */\n\n.page {\n flex: 1;\n overflow-y: auto;\n min-height: 0;\n}\n\n/* Centred column, mirrors AdwClamp at ~468 px */\n.inner {\n display: flex;\n flex-direction: column;\n gap: var(--gnome-space-4, 24px);\n max-width: 468px;\n margin: 0 auto;\n padding: var(--gnome-space-4, 24px) var(--gnome-space-3, 16px);\n}\n","import { type HTMLAttributes, type ReactNode } from \"react\";\nimport styles from \"./PreferencesPage.module.css\";\n\nexport interface PreferencesPageProps extends HTMLAttributes<HTMLDivElement> {\n /**\n * Page title shown in the sidebar / tab of a `PreferencesDialog`.\n * Required — used as the navigation label.\n */\n title: string;\n /**\n * Name of a symbolic icon displayed next to the page title in the sidebar.\n * Pass a valid icon name from the system icon theme (e.g. `\"preferences-system-symbolic\"`).\n */\n iconName?: string;\n /** `PreferencesGroup` sections. */\n children?: ReactNode;\n}\n\n/**\n * Scrollable page composed of `PreferencesGroup` sections.\n *\n * Use as a child of `PreferencesDialog`. Each page appears as a labelled\n * navigation entry in the dialog sidebar.\n *\n * Mirrors `AdwPreferencesPage`.\n *\n * @see https://gnome.pages.gitlab.gnome.org/libadwaita/doc/main/class.PreferencesPage.html\n */\nexport function PreferencesPage({\n title: _title,\n iconName: _iconName,\n children,\n className,\n ...props\n}: PreferencesPageProps) {\n return (\n <div\n className={[styles.page, className].filter(Boolean).join(\" \")}\n role=\"tabpanel\"\n {...props}\n >\n <div className={styles.inner}>{children}</div>\n </div>\n );\n}\n","/* ─── Backdrop ──────────────────────────────────────────────────────────────── */\n\n.backdrop {\n position: fixed;\n inset: 0;\n z-index: 10000;\n\n display: flex;\n align-items: center;\n justify-content: center;\n\n background-color: rgba(0, 0, 0, 0.5);\n backdrop-filter: blur(4px);\n -webkit-backdrop-filter: blur(4px);\n\n animation: backdrop-in var(--gnome-duration-normal, 200ms) var(--gnome-easing-default, ease) both;\n}\n\n@keyframes backdrop-in {\n from { opacity: 0; }\n to { opacity: 1; }\n}\n\n/* ─── Dialog card ───────────────────────────────────────────────────────────── */\n\n.dialog {\n display: flex;\n flex-direction: column;\n width: min(540px, calc(100vw - 48px));\n max-height: min(640px, 90dvh);\n\n background-color: var(--gnome-dialog-bg-color, var(--gnome-window-bg-color, #fafafa));\n color: var(--gnome-window-fg-color, rgba(0, 0, 0, 0.8));\n\n border-radius: var(--gnome-radius-xl, 16px);\n box-shadow:\n 0 8px 32px rgba(0, 0, 0, 0.25),\n 0 0 0 1px rgba(0, 0, 0, 0.08);\n\n overflow: hidden;\n\n animation: dialog-in var(--gnome-duration-normal, 200ms) var(--gnome-easing-default, ease) both;\n}\n\n/* Wider layout when sidebar is present */\n.dialogMulti {\n width: min(720px, calc(100vw - 48px));\n}\n\n@keyframes dialog-in {\n from {\n opacity: 0;\n transform: scale(0.94) translateY(-8px);\n }\n to {\n opacity: 1;\n transform: scale(1) translateY(0);\n }\n}\n\n/* ─── Header ────────────────────────────────────────────────────────────────── */\n\n.header {\n display: grid;\n grid-template-columns: auto 1fr auto;\n align-items: center;\n gap: var(--gnome-space-2, 12px);\n padding: var(--gnome-space-2, 12px) var(--gnome-space-2, 12px) var(--gnome-space-2, 12px) var(--gnome-space-3, 16px);\n border-bottom: 1px solid var(--gnome-card-shade-color, rgba(0, 0, 0, 0.1));\n flex-shrink: 0;\n}\n\n.closeBtn {\n appearance: none;\n border: none;\n background: transparent;\n cursor: pointer;\n width: 32px;\n height: 32px;\n border-radius: 50%;\n display: flex;\n align-items: center;\n justify-content: center;\n font-size: 0.875rem;\n color: var(--gnome-window-fg-color, rgba(0, 0, 0, 0.8));\n opacity: 0.6;\n transition: background-color var(--gnome-duration-fast, 100ms) var(--gnome-easing-default, ease),\n opacity var(--gnome-duration-fast, 100ms) var(--gnome-easing-default, ease);\n order: -1;\n}\n\n.closeBtn:hover {\n background-color: color-mix(in srgb, currentColor 10%, transparent);\n opacity: 1;\n}\n\n.closeBtn:focus-visible {\n outline: var(--gnome-focus-ring-width, 3px) solid var(--gnome-focus-ring-color, #3584e4);\n outline-offset: 2px;\n}\n\n.title {\n font-family: var(--gnome-font-family);\n font-size: var(--gnome-font-size-body, 1rem);\n font-weight: var(--gnome-font-weight-semibold, 600);\n line-height: 1;\n text-align: center;\n}\n\n/* ─── Search ────────────────────────────────────────────────────────────────── */\n\n.searchWrap {\n grid-column: 3;\n}\n\n.searchInput {\n appearance: none;\n border: 1px solid var(--gnome-card-shade-color, rgba(0, 0, 0, 0.15));\n border-radius: var(--gnome-radius-md, 8px);\n background-color: var(--gnome-card-bg-color, rgba(0, 0, 0, 0.04));\n color: var(--gnome-window-fg-color, rgba(0, 0, 0, 0.8));\n padding: 5px var(--gnome-space-2, 12px);\n font-family: var(--gnome-font-family);\n font-size: var(--gnome-font-size-body, 1rem);\n line-height: 1.5;\n width: 180px;\n transition: border-color var(--gnome-duration-fast, 100ms) var(--gnome-easing-default, ease),\n box-shadow var(--gnome-duration-fast, 100ms) var(--gnome-easing-default, ease);\n}\n\n.searchInput::placeholder {\n opacity: 0.5;\n}\n\n.searchInput:focus {\n outline: none;\n border-color: var(--gnome-accent-color, #3584e4);\n box-shadow: 0 0 0 var(--gnome-focus-ring-width, 3px) color-mix(in srgb, var(--gnome-accent-color, #3584e4) 30%, transparent);\n}\n\n/* ─── Body ──────────────────────────────────────────────────────────────────── */\n\n.body {\n display: flex;\n flex: 1;\n min-height: 0;\n overflow: hidden;\n}\n\n/* ─── Sidebar ───────────────────────────────────────────────────────────────── */\n\n.sidebar {\n width: 200px;\n flex-shrink: 0;\n border-right: 1px solid var(--gnome-card-shade-color, rgba(0, 0, 0, 0.1));\n overflow-y: auto;\n padding: var(--gnome-space-2, 12px) var(--gnome-space-2, 12px);\n}\n\n.navList {\n list-style: none;\n margin: 0;\n padding: 0;\n display: flex;\n flex-direction: column;\n gap: 2px;\n}\n\n.navItem {\n appearance: none;\n border: none;\n background: transparent;\n cursor: pointer;\n width: 100%;\n padding: 8px var(--gnome-space-2, 12px);\n border-radius: var(--gnome-radius-md, 8px);\n display: flex;\n align-items: center;\n gap: var(--gnome-space-2, 12px);\n text-align: left;\n\n font-family: var(--gnome-font-family);\n font-size: var(--gnome-font-size-body, 1rem);\n font-weight: var(--gnome-font-weight-normal, 400);\n color: var(--gnome-window-fg-color, rgba(0, 0, 0, 0.8));\n\n transition: background-color var(--gnome-duration-fast, 100ms) var(--gnome-easing-default, ease);\n}\n\n.navItem:hover:not(.navItemActive) {\n background-color: color-mix(in srgb, currentColor 6%, transparent);\n}\n\n.navItemActive {\n background-color: color-mix(in srgb, var(--gnome-accent-color, #3584e4) 12%, transparent);\n color: var(--gnome-accent-color, #3584e4);\n font-weight: var(--gnome-font-weight-semibold, 600);\n}\n\n.navItem:focus-visible {\n outline: var(--gnome-focus-ring-width, 3px) solid var(--gnome-focus-ring-color, #3584e4);\n outline-offset: 2px;\n}\n\n.navIcon {\n width: 16px;\n height: 16px;\n flex-shrink: 0;\n}\n\n.navLabel {\n flex: 1;\n min-width: 0;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n/* ─── Page area ─────────────────────────────────────────────────────────────── */\n\n.pageWrap {\n flex: 1;\n min-width: 0;\n min-height: 0;\n display: flex;\n flex-direction: column;\n}\n\n/* ─── Dark mode ─────────────────────────────────────────────────────────────── */\n\n@media (prefers-color-scheme: dark) {\n .backdrop {\n background-color: rgba(0, 0, 0, 0.65);\n }\n\n .dialog {\n background-color: var(--gnome-dialog-bg-color, var(--gnome-window-bg-color, #242424));\n box-shadow:\n 0 8px 32px rgba(0, 0, 0, 0.5),\n 0 0 0 1px rgba(255, 255, 255, 0.08);\n }\n\n .header {\n border-bottom-color: rgba(255, 255, 255, 0.08);\n }\n\n .sidebar {\n border-right-color: rgba(255, 255, 255, 0.08);\n }\n\n .searchInput {\n background-color: rgba(255, 255, 255, 0.06);\n border-color: rgba(255, 255, 255, 0.15);\n }\n}\n\n/* ─── Reduced motion ────────────────────────────────────────────────────────── */\n\n@media (prefers-reduced-motion: reduce) {\n .backdrop,\n .dialog {\n animation: none;\n }\n\n .closeBtn,\n .navItem,\n .searchInput {\n transition: none;\n }\n}\n\n/* ─── High contrast ─────────────────────────────────────────────────────────── */\n\n@media (prefers-contrast: more) {\n .dialog {\n box-shadow: none;\n border: 2px solid var(--gnome-window-fg-color, rgba(0, 0, 0, 0.8));\n }\n\n .header {\n border-bottom-width: 2px;\n }\n\n .sidebar {\n border-right-width: 2px;\n }\n\n .navItemActive {\n outline: 2px solid var(--gnome-accent-color, #3584e4);\n }\n}\n","import {\n Children,\n cloneElement,\n isValidElement,\n useCallback,\n useEffect,\n useId,\n useRef,\n useState,\n type HTMLAttributes,\n type KeyboardEvent,\n type ReactElement,\n type ReactNode,\n} from \"react\";\nimport { createPortal } from \"react-dom\";\nimport styles from \"./PreferencesDialog.module.css\";\nimport type { PreferencesPageProps } from \"../PreferencesPage/PreferencesPage\";\n\n// ─── Focus trap ───────────────────────────────────────────────────────────────\n\nconst FOCUSABLE =\n 'button:not([disabled]), [href], input:not([disabled]), select:not([disabled]), textarea:not([disabled]), [tabindex]:not([tabindex=\"-1\"])';\n\nfunction trapFocus(\n e: KeyboardEvent<HTMLDivElement>,\n ref: React.RefObject<HTMLDivElement | null>\n) {\n if (e.key !== \"Tab\") return;\n const focusable = Array.from(\n ref.current?.querySelectorAll<HTMLElement>(FOCUSABLE) ?? []\n );\n if (!focusable.length) return;\n const first = focusable[0];\n const last = focusable[focusable.length - 1];\n if (e.shiftKey) {\n if (document.activeElement === first) {\n e.preventDefault();\n last.focus();\n }\n } else {\n if (document.activeElement === last) {\n e.preventDefault();\n first.focus();\n }\n }\n}\n\n// ─── Types ────────────────────────────────────────────────────────────────────\n\nexport interface PreferencesDialogProps\n extends Omit<HTMLAttributes<HTMLDivElement>, \"title\"> {\n /** Whether the dialog is open. */\n open: boolean;\n /** Called when the user requests to close the dialog. */\n onClose: () => void;\n /**\n * `PreferencesPage` children. Each page appears as a sidebar entry.\n * When only one page is provided the sidebar is hidden.\n */\n children?: ReactNode;\n /**\n * Whether to show a search bar in the header.\n * When `true` the user can filter group/row content by typing.\n * Defaults to `true`.\n */\n searchable?: boolean;\n}\n\n// ─── Component ────────────────────────────────────────────────────────────────\n\n/**\n * Multi-page settings dialog using `PreferencesPage` tabs.\n *\n * Renders a wide modal with an optional sidebar listing pages. Each\n * `PreferencesPage` child becomes a navigation entry. Supports built-in\n * search when `searchable` is `true` (the default).\n *\n * Mirrors `AdwPreferencesDialog`.\n *\n * @example\n * ```tsx\n * <PreferencesDialog open={open} onClose={() => setOpen(false)}>\n * <PreferencesPage title=\"General\" iconName=\"preferences-system-symbolic\">\n * <PreferencesGroup title=\"Appearance\">\n * <BoxedList>\n * <SwitchRow title=\"Dark mode\" />\n * </BoxedList>\n * </PreferencesGroup>\n * </PreferencesPage>\n * </PreferencesDialog>\n * ```\n *\n * @see https://gnome.pages.gitlab.gnome.org/libadwaita/doc/main/class.PreferencesDialog.html\n */\nexport function PreferencesDialog({\n open,\n onClose,\n children,\n searchable = true,\n className,\n ...props\n}: PreferencesDialogProps) {\n const dialogRef = useRef<HTMLDivElement>(null);\n const searchRef = useRef<HTMLInputElement>(null);\n const titleId = useId();\n\n const pages = Children.toArray(children).filter(\n (c): c is ReactElement<PreferencesPageProps> =>\n isValidElement(c)\n ) as ReactElement<PreferencesPageProps>[];\n\n const [activeIndex, setActiveIndex] = useState(0);\n const [searchQuery, setSearchQuery] = useState(\"\");\n\n // Reset state when dialog opens\n useEffect(() => {\n if (open) {\n setActiveIndex(0);\n setSearchQuery(\"\");\n // Focus search or first focusable after open animation\n const id = requestAnimationFrame(() => {\n if (searchable && searchRef.current) {\n searchRef.current.focus();\n } else {\n const first = dialogRef.current?.querySelector<HTMLElement>(FOCUSABLE);\n first?.focus();\n }\n });\n return () => cancelAnimationFrame(id);\n }\n }, [open, searchable]);\n\n // Close on Escape\n const handleKeyDown = useCallback(\n (e: KeyboardEvent<HTMLDivElement>) => {\n if (e.key === \"Escape\") {\n e.stopPropagation();\n onClose();\n return;\n }\n trapFocus(e, dialogRef);\n },\n [onClose]\n );\n\n const multiPage = pages.length > 1;\n const activePage = pages[activeIndex] ?? pages[0];\n\n // Filter content by search query\n const query = searchQuery.trim().toLowerCase();\n\n if (!open) return null;\n\n return createPortal(\n <div\n className={styles.backdrop}\n onClick={(e) => {\n if (e.target === e.currentTarget) onClose();\n }}\n role=\"presentation\"\n >\n <div\n ref={dialogRef}\n role=\"dialog\"\n aria-modal=\"true\"\n aria-labelledby={titleId}\n className={[\n styles.dialog,\n multiPage ? styles.dialogMulti : null,\n className,\n ]\n .filter(Boolean)\n .join(\" \")}\n onKeyDown={handleKeyDown}\n {...props}\n >\n {/* ── Header ─────────────────────────────────────────────────────── */}\n <div className={styles.header}>\n <button\n type=\"button\"\n className={styles.closeBtn}\n onClick={onClose}\n aria-label=\"Close preferences\"\n >\n ✕\n </button>\n <span id={titleId} className={styles.title}>\n Preferences\n </span>\n {searchable && (\n <div className={styles.searchWrap}>\n <input\n ref={searchRef}\n type=\"search\"\n placeholder=\"Search…\"\n value={searchQuery}\n onChange={(e) => setSearchQuery(e.target.value)}\n className={styles.searchInput}\n aria-label=\"Search preferences\"\n />\n </div>\n )}\n </div>\n\n {/* ── Body ───────────────────────────────────────────────────────── */}\n <div className={styles.body}>\n {/* Sidebar — only shown when there are multiple pages */}\n {multiPage && (\n <nav className={styles.sidebar} aria-label=\"Preferences pages\">\n <ul role=\"tablist\" className={styles.navList}>\n {pages.map((page, i) => (\n <li key={i} role=\"presentation\">\n <button\n role=\"tab\"\n type=\"button\"\n aria-selected={i === activeIndex}\n className={[\n styles.navItem,\n i === activeIndex ? styles.navItemActive : null,\n ]\n .filter(Boolean)\n .join(\" \")}\n onClick={() => setActiveIndex(i)}\n >\n {page.props.iconName && (\n <span\n className={[\n styles.navIcon,\n \"icon\",\n page.props.iconName,\n ].join(\" \")}\n aria-hidden=\"true\"\n />\n )}\n <span className={styles.navLabel}>\n {page.props.title}\n </span>\n </button>\n </li>\n ))}\n </ul>\n </nav>\n )}\n\n {/* Page content */}\n <div className={styles.pageWrap}>\n {activePage &&\n cloneElement(activePage, {\n \"data-search-query\": query || undefined,\n } as Partial<PreferencesPageProps>)}\n </div>\n </div>\n </div>\n </div>,\n document.body\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.js","names":[],"sources":["../src/components/Button/Button.module.css","../src/components/Button/Button.tsx","../src/components/Text/Text.module.css","../src/components/Text/Text.tsx","../src/components/Card/Card.module.css","../src/components/Card/Card.tsx","../src/components/Spinner/Spinner.module.css","../src/components/Spinner/Spinner.tsx","../src/components/Avatar/Avatar.module.css","../src/components/Avatar/Avatar.tsx","../src/components/Separator/Separator.module.css","../src/components/Separator/Separator.tsx","../src/components/Switch/Switch.module.css","../src/components/Switch/Switch.tsx","../src/components/Checkbox/Checkbox.module.css","../src/components/Checkbox/Checkbox.tsx","../src/components/TextField/TextField.module.css","../src/components/TextField/TextField.tsx","../src/components/RadioButton/RadioButton.module.css","../src/components/RadioButton/RadioButton.tsx","../src/components/ProgressBar/ProgressBar.module.css","../src/components/ProgressBar/ProgressBar.tsx","../src/components/Banner/Banner.module.css","../src/components/Banner/Banner.tsx","../src/components/HeaderBar/HeaderBar.module.css","../src/components/HeaderBar/HeaderBar.tsx","../src/components/Footer/Footer.module.css","../src/components/Footer/Footer.tsx","../src/components/Badge/Badge.module.css","../src/components/Badge/Badge.tsx","../src/components/Icon/Icon.tsx","../../icons/dist/index.js","../src/components/SearchBar/SearchBar.module.css","../src/components/SearchBar/SearchBar.tsx","../src/components/StatusPage/StatusPage.module.css","../src/components/StatusPage/StatusPage.tsx","../src/hooks/useBreakpoint.ts","../src/components/Sidebar/Sidebar.module.css","../src/components/Sidebar/Sidebar.tsx","../src/components/Sidebar/SidebarSection.tsx","../src/components/Tooltip/Tooltip.module.css","../src/components/Tooltip/Tooltip.tsx","../src/components/Sidebar/SidebarItem.tsx","../src/components/SpinButton/SpinButton.module.css","../src/components/SpinButton/SpinButton.tsx","../src/components/Tabs/Tabs.module.css","../src/components/Tabs/TabBar.tsx","../src/components/Tabs/TabItem.tsx","../src/components/Tabs/TabPanel.tsx","../src/components/ActionRow/ActionRow.module.css","../src/components/ActionRow/ActionRow.tsx","../src/components/BoxedList/BoxedList.module.css","../src/components/BoxedList/BoxedList.tsx","../src/components/ViewSwitcher/ViewSwitcher.module.css","../src/components/ViewSwitcher/ViewSwitcher.tsx","../src/components/ViewSwitcher/ViewSwitcherItem.tsx","../src/components/Toast/Toast.module.css","../src/components/Toast/Toast.tsx","../src/components/Toast/Toaster.tsx","../src/components/Dialog/Dialog.module.css","../src/components/Dialog/Dialog.tsx","../src/components/Dropdown/Dropdown.module.css","../src/components/Dropdown/Dropdown.tsx","../src/components/Slider/Slider.module.css","../src/components/Slider/Slider.tsx","../src/components/Popover/Popover.module.css","../src/components/Popover/Popover.tsx","../src/components/Clamp/Clamp.module.css","../src/components/Clamp/Clamp.tsx","../src/components/NavigationSplitView/NavigationSplitView.module.css","../src/components/NavigationSplitView/NavigationSplitView.tsx","../src/components/OverlaySplitView/OverlaySplitView.module.css","../src/components/OverlaySplitView/OverlaySplitView.tsx","../src/components/ViewSwitcherBar/ViewSwitcherBar.module.css","../src/components/ViewSwitcherBar/ViewSwitcherBar.tsx","../src/components/Link/Link.module.css","../src/components/Link/Link.tsx","../src/components/ToggleGroup/ToggleGroup.module.css","../src/components/ToggleGroup/ToggleGroup.tsx","../src/components/ToggleGroup/ToggleGroupItem.tsx","../src/components/WrapBox/WrapBox.module.css","../src/components/WrapBox/WrapBox.tsx","../src/components/Chip/Chip.module.css","../src/components/Chip/Chip.tsx","../src/components/ShortcutsDialog/ShortcutsDialog.module.css","../src/components/ShortcutsDialog/ShortcutsDialog.tsx","../src/components/ViewSwitcherSidebar/ViewSwitcherSidebar.module.css","../src/components/ViewSwitcherSidebar/ViewSwitcherSidebar.tsx","../src/components/ViewSwitcherSidebar/ViewSwitcherSidebarItem.tsx","../src/components/BreakpointBin/BreakpointBin.tsx","../src/components/ButtonRow/ButtonRow.module.css","../src/components/ButtonRow/ButtonRow.tsx","../src/components/SplitButton/SplitButton.module.css","../src/components/SplitButton/SplitButton.tsx","../src/components/Toolbar/Toolbar.module.css","../src/components/Toolbar/Toolbar.tsx","../src/components/Toolbar/Spacer.tsx","../src/components/LinkedGroup/LinkedGroup.module.css","../src/components/LinkedGroup/LinkedGroup.tsx","../src/components/Frame/Frame.module.css","../src/components/Frame/Frame.tsx","../src/components/ExpanderRow/ExpanderRow.module.css","../src/components/ExpanderRow/ExpanderRow.tsx","../src/components/InlineViewSwitcher/InlineViewSwitcher.module.css","../src/components/InlineViewSwitcher/InlineViewSwitcher.tsx","../src/components/InlineViewSwitcher/InlineViewSwitcherItem.tsx","../src/components/SwitchRow/SwitchRow.module.css","../src/components/SwitchRow/SwitchRow.tsx","../src/components/CheckRow/CheckRow.module.css","../src/components/CheckRow/CheckRow.tsx","../src/components/ComboRow/ComboRow.module.css","../src/components/ComboRow/ComboRow.tsx","../src/components/EntryRow/EntryRow.module.css","../src/components/EntryRow/EntryRow.tsx","../src/components/PasswordEntryRow/PasswordEntryRow.module.css","../src/components/PasswordEntryRow/PasswordEntryRow.tsx","../src/components/SpinRow/SpinRow.module.css","../src/components/SpinRow/SpinRow.tsx","../src/components/NavigationView/NavigationView.module.css","../src/components/NavigationView/NavigationView.tsx","../src/components/BottomSheet/BottomSheet.module.css","../src/components/BottomSheet/BottomSheet.tsx","../src/components/Carousel/Carousel.module.css","../src/components/Carousel/Carousel.tsx","../src/components/Bin/Bin.tsx","../src/components/ButtonContent/ButtonContent.module.css","../src/components/ButtonContent/ButtonContent.tsx","../src/components/ShortcutLabel/ShortcutLabel.module.css","../src/components/ShortcutLabel/ShortcutLabel.tsx","../src/components/ToolbarView/ToolbarView.module.css","../src/components/ToolbarView/ToolbarView.tsx","../src/components/WindowTitle/WindowTitle.module.css","../src/components/WindowTitle/WindowTitle.tsx","../src/components/PreferencesGroup/PreferencesGroup.module.css","../src/components/PreferencesGroup/PreferencesGroup.tsx","../src/components/PreferencesPage/PreferencesPage.module.css","../src/components/PreferencesPage/PreferencesPage.tsx","../src/components/PreferencesDialog/PreferencesDialog.module.css","../src/components/PreferencesDialog/PreferencesDialog.tsx"],"sourcesContent":[".button {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n gap: var(--gnome-space-1);\n\n /* Typography */\n font-family: var(--gnome-font-family);\n font-size: var(--gnome-font-size-body);\n font-weight: var(--gnome-font-weight-semibold);\n line-height: 1;\n white-space: nowrap;\n\n /* Shape */\n border-radius: var(--gnome-radius-md);\n border: 1px solid transparent;\n padding: var(--gnome-space-1) var(--gnome-space-2);\n min-height: 34px;\n min-width: 5.5rem;\n\n /* Behavior */\n cursor: pointer;\n user-select: none;\n text-decoration: none;\n vertical-align: middle;\n outline: none;\n transition:\n background-color var(--gnome-duration-fast) var(--gnome-easing-default),\n box-shadow var(--gnome-duration-fast) var(--gnome-easing-default),\n opacity var(--gnome-duration-fast) var(--gnome-easing-default);\n}\n\n/* ─── Focus ───────────────────────────────────────────────────── */\n\n.button:focus-visible {\n box-shadow:\n 0 0 0 var(--gnome-focus-ring-offset) var(--gnome-window-bg-color),\n 0 0 0 calc(var(--gnome-focus-ring-offset) + var(--gnome-focus-ring-width)) var(--gnome-focus-ring-color);\n}\n\n/* ─── Disabled ────────────────────────────────────────────────── */\n\n.button:disabled,\n.button[aria-disabled=\"true\"] {\n opacity: var(--gnome-opacity-disabled);\n cursor: not-allowed;\n pointer-events: none;\n}\n\n/* ─── Pill shape ──────────────────────────────────────────────── */\n\n.pill {\n border-radius: var(--gnome-radius-pill);\n padding-inline: var(--gnome-space-3);\n}\n\n/* ─── Circular shape ──────────────────────────────────────────── */\n\n.circular {\n border-radius: var(--gnome-radius-pill);\n padding: var(--gnome-space-1);\n min-width: 34px;\n min-height: 34px;\n width: 34px;\n height: 34px;\n}\n\n/* ─── Variants ────────────────────────────────────────────────── */\n\n/* Default — slight raised appearance with visible border */\n.default {\n background-color: var(--gnome-card-bg-color, #ffffff);\n color: var(--gnome-window-fg-color, rgba(0, 0, 0, 0.8));\n border-color: var(--gnome-light-3, #deddda);\n box-shadow: var(--gnome-shadow-sm);\n}\n\n.default:hover:not(:disabled) {\n background-color: var(--gnome-light-2, #f6f5f4);\n}\n\n.default:active:not(:disabled) {\n background-color: var(--gnome-light-3, #deddda);\n box-shadow: none;\n}\n\n@media (prefers-color-scheme: dark) {\n .default {\n background-color: var(--gnome-card-bg-color, #383838);\n border-color: rgba(255, 255, 255, 0.12);\n box-shadow: none;\n }\n\n .default:hover:not(:disabled) {\n background-color: rgba(255, 255, 255, 0.08);\n }\n\n .default:active:not(:disabled) {\n background-color: rgba(255, 255, 255, 0.14);\n box-shadow: none;\n }\n}\n\n/* Suggested — accent color for primary / affirmative actions */\n.suggested {\n background-color: var(--gnome-accent-bg-color);\n color: var(--gnome-accent-fg-color);\n border-color: transparent;\n}\n\n.suggested:hover:not(:disabled) {\n filter: brightness(1.08);\n}\n\n.suggested:active:not(:disabled) {\n filter: brightness(0.92);\n}\n\n/* Destructive — red for dangerous / irreversible actions */\n.destructive {\n background-color: var(--gnome-destructive-bg-color);\n color: var(--gnome-destructive-fg-color);\n border-color: transparent;\n}\n\n.destructive:hover:not(:disabled) {\n filter: brightness(1.08);\n}\n\n.destructive:active:not(:disabled) {\n filter: brightness(0.92);\n}\n\n/* Flat — no border, no background. Used inside header bars */\n.flat {\n background-color: transparent;\n border-color: transparent;\n color: var(--gnome-window-fg-color, rgba(0, 0, 0, 0.8));\n}\n\n.flat:hover:not(:disabled) {\n background-color: var(--gnome-hover-overlay);\n}\n\n.flat:active:not(:disabled) {\n background-color: var(--gnome-active-overlay);\n}\n\n/* Raised — explicit raised look inside flat/toolbar contexts */\n.raised {\n background-color: var(--gnome-card-bg-color, #ffffff);\n color: var(--gnome-window-fg-color, rgba(0, 0, 0, 0.8));\n border-color: var(--gnome-light-3, #deddda);\n box-shadow: var(--gnome-shadow-sm);\n}\n\n.raised:hover:not(:disabled) {\n background-color: var(--gnome-light-2, #f6f5f4);\n}\n\n.raised:active:not(:disabled) {\n background-color: var(--gnome-light-3, #deddda);\n box-shadow: none;\n}\n\n@media (prefers-color-scheme: dark) {\n .raised {\n background-color: var(--gnome-card-bg-color, #383838);\n border-color: rgba(255, 255, 255, 0.12);\n box-shadow: var(--gnome-shadow-sm);\n }\n\n .raised:hover:not(:disabled) {\n background-color: rgba(255, 255, 255, 0.08);\n }\n\n .raised:active:not(:disabled) {\n background-color: rgba(255, 255, 255, 0.14);\n box-shadow: none;\n }\n}\n\n/* OSD — dark semi-transparent overlay style for media/image contexts */\n.osd {\n background-color: rgba(0, 0, 0, 0.65) !important;\n color: rgba(255, 255, 255, 0.95) !important;\n border-color: rgba(255, 255, 255, 0.12) !important;\n box-shadow: none !important;\n backdrop-filter: blur(4px);\n -webkit-backdrop-filter: blur(4px);\n}\n\n.osd:hover:not(:disabled) {\n background-color: rgba(0, 0, 0, 0.75) !important;\n filter: none;\n}\n\n.osd:active:not(:disabled) {\n background-color: rgba(0, 0, 0, 0.85) !important;\n filter: none;\n}\n\n@media (prefers-color-scheme: dark) {\n .osd {\n background-color: rgba(0, 0, 0, 0.65) !important;\n border-color: rgba(255, 255, 255, 0.1) !important;\n }\n}\n\n/* ─── High contrast ───────────────────────────────────────────── */\n\n@media (prefers-contrast: more) {\n .default {\n border-width: 2px;\n box-shadow: none;\n }\n\n .button:focus-visible {\n box-shadow:\n 0 0 0 var(--gnome-focus-ring-offset) var(--gnome-window-bg-color),\n 0 0 0 calc(var(--gnome-focus-ring-offset) + var(--gnome-focus-ring-width)) var(--gnome-focus-ring-color),\n 0 0 0 calc(var(--gnome-focus-ring-offset) + var(--gnome-focus-ring-width) + 1px) var(--gnome-window-bg-color);\n }\n}\n\n/* ─── Sizes ───────────────────────────────────────────────────── */\n\n.sm {\n font-size: var(--gnome-font-size-caption);\n min-height: 28px;\n padding: 4px var(--gnome-space-1);\n min-width: 4rem;\n}\n\n.lg {\n font-size: var(--gnome-font-size-title-4);\n min-height: 42px;\n padding: var(--gnome-space-1) var(--gnome-space-3);\n}\n","import type { ButtonHTMLAttributes, ReactNode } from \"react\";\nimport styles from \"./Button.module.css\";\n\nexport type ButtonVariant = \"default\" | \"suggested\" | \"destructive\" | \"flat\" | \"raised\";\nexport type ButtonSize = \"sm\" | \"md\" | \"lg\";\nexport type ButtonShape = \"default\" | \"pill\" | \"circular\";\n\nexport interface ButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> {\n /** Visual style of the button. Follows GNOME HIG button types. */\n variant?: ButtonVariant;\n /** Size of the button. */\n size?: ButtonSize;\n /** Shape of the button. \"pill\" for primary actions in open space, \"circular\" for icon-only buttons. */\n shape?: ButtonShape;\n /**\n * Dark semi-transparent overlay style for buttons placed on top of\n * media or images — mirrors the `.osd` style class.\n */\n osd?: boolean;\n /** Icon placed before the label. */\n leadingIcon?: ReactNode;\n /** Icon placed after the label. */\n trailingIcon?: ReactNode;\n children?: ReactNode;\n}\n\n/**\n * Button component following GNOME Human Interface Guidelines.\n *\n * Variants:\n * - `default` — Standard action, flat appearance with subtle border.\n * - `suggested` — Affirmative/primary action (accent color). Use at most once per view.\n * - `destructive` — Dangerous or irreversible action (red). Use sparingly.\n * - `flat` — No border or background; ideal for header bars and toolbars.\n *\n * @see https://developer.gnome.org/hig/patterns/controls/buttons.html\n */\nexport function Button({\n variant = \"default\",\n size = \"md\",\n shape = \"default\",\n osd = false,\n leadingIcon,\n trailingIcon,\n children,\n className,\n ...props\n}: ButtonProps) {\n const classes = [\n styles.button,\n styles[variant],\n size !== \"md\" ? styles[size] : null,\n shape !== \"default\" ? styles[shape] : null,\n osd ? styles.osd : null,\n className,\n ]\n .filter(Boolean)\n .join(\" \");\n\n return (\n <button className={classes} {...props}>\n {leadingIcon}\n {children}\n {trailingIcon}\n </button>\n );\n}\n",".text {\n font-family: var(--gnome-font-family);\n color: var(--gnome-window-fg-color, rgba(0, 0, 0, 0.8));\n margin: 0;\n}\n\n/* ─── Variants ────────────────────────────────────────────────── */\n\n.large-title {\n font-size: var(--gnome-font-size-large-title); /* 2.25rem */\n font-weight: 300;\n line-height: var(--gnome-line-height-heading);\n letter-spacing: -0.02em;\n}\n\n.title-1 {\n font-size: var(--gnome-font-size-title-1); /* 1.875rem */\n font-weight: var(--gnome-font-weight-bold);\n line-height: var(--gnome-line-height-heading);\n}\n\n.title-2 {\n font-size: var(--gnome-font-size-title-2); /* 1.5rem */\n font-weight: var(--gnome-font-weight-bold);\n line-height: var(--gnome-line-height-heading);\n}\n\n.title-3 {\n font-size: var(--gnome-font-size-title-3); /* 1.25rem */\n font-weight: var(--gnome-font-weight-bold);\n line-height: var(--gnome-line-height-heading);\n}\n\n.title-4 {\n font-size: var(--gnome-font-size-title-4); /* 1.125rem */\n font-weight: var(--gnome-font-weight-semibold);\n line-height: var(--gnome-line-height-heading);\n}\n\n/* Heading — same size as body but bold. Used for section labels, boxed list headers */\n.heading {\n font-size: var(--gnome-font-size-body);\n font-weight: var(--gnome-font-weight-bold);\n line-height: var(--gnome-line-height-heading);\n}\n\n/* Body — default UI text for labels and descriptions */\n.body {\n font-size: var(--gnome-font-size-body);\n font-weight: var(--gnome-font-weight-normal);\n line-height: var(--gnome-line-height-body);\n}\n\n/* Document — main reading content (chat messages, articles) */\n.document {\n font-size: var(--gnome-font-size-body);\n font-weight: var(--gnome-font-weight-normal);\n line-height: 1.65;\n}\n\n/* Caption — sub-text accompanying body text */\n.caption {\n font-size: var(--gnome-font-size-caption); /* 0.75rem */\n font-weight: var(--gnome-font-weight-normal);\n line-height: var(--gnome-line-height-body);\n}\n\n/* Caption Heading — small label, often used above groups */\n.caption-heading {\n font-size: var(--gnome-font-size-caption);\n font-weight: var(--gnome-font-weight-semibold);\n line-height: var(--gnome-line-height-heading);\n text-transform: uppercase;\n letter-spacing: 0.06em;\n}\n\n/* Monospace — code, logs, shell commands */\n.monospace {\n font-family: var(--gnome-font-family-mono);\n font-size: 0.875rem;\n line-height: var(--gnome-line-height-body);\n}\n\n/* Numeric — tabular figures for aligned numbers */\n.numeric {\n font-size: var(--gnome-font-size-body);\n font-weight: var(--gnome-font-weight-normal);\n font-variant-numeric: tabular-nums;\n font-feature-settings: \"tnum\";\n line-height: var(--gnome-line-height-body);\n}\n\n/* ─── Colors ──────────────────────────────────────────────────── */\n\n.color-default {\n color: var(--gnome-window-fg-color, rgba(0, 0, 0, 0.8));\n}\n\n.color-dim {\n opacity: var(--gnome-opacity-dim, 0.55);\n}\n\n.color-accent {\n color: var(--gnome-accent-color, #3584e4);\n}\n\n.color-destructive {\n color: var(--gnome-destructive-color, #e01b24);\n}\n\n.color-success {\n color: var(--gnome-success-color, #2ec27e);\n}\n\n.color-warning {\n color: var(--gnome-warning-color, #e5a50a);\n}\n\n.color-error {\n color: var(--gnome-error-color, #e01b24);\n}\n","import type { ElementType, HTMLAttributes, ReactNode } from \"react\";\nimport styles from \"./Text.module.css\";\n\nexport type TextVariant =\n | \"large-title\"\n | \"title-1\"\n | \"title-2\"\n | \"title-3\"\n | \"title-4\"\n | \"heading\"\n | \"body\"\n | \"document\"\n | \"caption\"\n | \"caption-heading\"\n | \"monospace\"\n | \"numeric\";\n\nexport type TextColor =\n | \"default\"\n | \"dim\"\n | \"accent\"\n | \"destructive\"\n | \"success\"\n | \"warning\"\n | \"error\";\n\n/** Default HTML element rendered for each variant */\nconst defaultElement: Record<TextVariant, ElementType> = {\n \"large-title\": \"h1\",\n \"title-1\": \"h1\",\n \"title-2\": \"h2\",\n \"title-3\": \"h3\",\n \"title-4\": \"h4\",\n heading: \"h3\",\n body: \"p\",\n document: \"p\",\n caption: \"span\",\n \"caption-heading\": \"span\",\n monospace: \"code\",\n numeric: \"span\",\n};\n\nexport interface TextProps extends HTMLAttributes<HTMLElement> {\n /** Typography style. Mirrors Adwaita / GNOME HIG text styles. */\n variant?: TextVariant;\n /** Semantic color. */\n color?: TextColor;\n /** Override the rendered HTML element. */\n as?: ElementType;\n children?: ReactNode;\n}\n\n/**\n * Text component following GNOME Human Interface Guidelines typography styles.\n *\n * Variants map directly to Adwaita style classes:\n * `large-title`, `title-1`–`title-4`, `heading`, `body`, `document`,\n * `caption`, `caption-heading`, `monospace`, `numeric`.\n *\n * @see https://developer.gnome.org/hig/guidelines/typography.html\n * @see https://gnome.pages.gitlab.gnome.org/libadwaita/doc/main/style-classes.html\n */\nexport function Text({\n variant = \"body\",\n color = \"default\",\n as,\n className,\n children,\n ...props\n}: TextProps) {\n const Tag = as ?? defaultElement[variant];\n\n const classes = [\n styles.text,\n styles[variant],\n styles[`color-${color}`],\n className,\n ]\n .filter(Boolean)\n .join(\" \");\n\n return (\n <Tag className={classes} {...props}>\n {children}\n </Tag>\n );\n}\n",".card {\n background-color: var(--gnome-card-bg-color, #ffffff);\n color: var(--gnome-card-fg-color, rgba(0, 0, 0, 0.8));\n border: 1px solid var(--gnome-card-shade-color, rgba(0, 0, 0, 0.07));\n border-radius: var(--gnome-radius-lg);\n overflow: hidden;\n position: relative;\n}\n\n/* ─── Padding ─────────────────────────────────────────────────── */\n\n.padding-none { padding: 0; }\n.padding-sm { padding: var(--gnome-space-2); }\n.padding-md { padding: var(--gnome-space-4); }\n.padding-lg { padding: var(--gnome-space-5); }\n\n/* ─── Interactive (activatable) ───────────────────────────────── */\n\n.interactive {\n cursor: pointer;\n user-select: none;\n outline: none;\n\n /* Reset button styles when rendered as <button> */\n appearance: none;\n -webkit-appearance: none;\n border: 1px solid var(--gnome-card-shade-color, rgba(0, 0, 0, 0.07));\n font: inherit;\n text-align: inherit;\n width: 100%;\n\n transition:\n background-color var(--gnome-duration-fast) var(--gnome-easing-default),\n box-shadow var(--gnome-duration-fast) var(--gnome-easing-default);\n}\n\n.interactive:hover {\n background-color: color-mix(\n in srgb,\n var(--gnome-card-bg-color, #ffffff) 94%,\n black 6%\n );\n}\n\n.interactive:active {\n background-color: color-mix(\n in srgb,\n var(--gnome-card-bg-color, #ffffff) 88%,\n black 12%\n );\n}\n\n.interactive:focus-visible {\n box-shadow:\n 0 0 0 var(--gnome-focus-ring-offset) var(--gnome-window-bg-color, #fafafa),\n 0 0 0 calc(var(--gnome-focus-ring-offset) + var(--gnome-focus-ring-width))\n var(--gnome-focus-ring-color, #3584e4);\n}\n\n.interactive:disabled {\n opacity: var(--gnome-opacity-disabled);\n cursor: not-allowed;\n pointer-events: none;\n}\n\n@media (prefers-color-scheme: dark) {\n .interactive:hover {\n background-color: color-mix(\n in srgb,\n var(--gnome-card-bg-color, rgba(255, 255, 255, 0.08)) 85%,\n white 15%\n );\n }\n\n .interactive:active {\n background-color: color-mix(\n in srgb,\n var(--gnome-card-bg-color, rgba(255, 255, 255, 0.08)) 75%,\n white 25%\n );\n }\n}\n","import type { ElementType, HTMLAttributes, ReactNode } from \"react\";\nimport styles from \"./Card.module.css\";\n\nexport type CardPadding = \"none\" | \"sm\" | \"md\" | \"lg\";\n\nexport interface CardProps extends HTMLAttributes<HTMLElement> {\n /**\n * When true the card becomes clickable (Adwaita `.activatable`).\n * Renders as `<button>` by default; override with `as`.\n */\n interactive?: boolean;\n /** Internal spacing. Defaults to `\"md\"`. */\n padding?: CardPadding;\n /** Override the rendered HTML element. */\n as?: ElementType;\n children?: ReactNode;\n}\n\n/**\n * Card component following the GNOME HIG and Adwaita `.card` style class.\n *\n * Use for grouping related content on an elevated surface.\n * For clickable cards (e.g. grid item, settings shortcut) set `interactive`.\n *\n * @see https://gnome.pages.gitlab.gnome.org/libadwaita/doc/main/style-classes.html\n * @see https://developer.gnome.org/hig/patterns/containers.html\n */\nexport function Card({\n interactive = false,\n padding = \"md\",\n as,\n className,\n children,\n ...props\n}: CardProps) {\n const Tag: ElementType = as ?? (interactive ? \"button\" : \"div\");\n\n const classes = [\n styles.card,\n styles[`padding-${padding}`],\n interactive ? styles.interactive : null,\n className,\n ]\n .filter(Boolean)\n .join(\" \");\n\n return (\n <Tag className={classes} {...props}>\n {children}\n </Tag>\n );\n}\n","/* ─── Base ─────────────────────────────────────────────────────────────────── */\n\n.spinner {\n display: inline-block;\n border-radius: 50%;\n border-style: solid;\n border-color: var(--gnome-spinner-track-color, rgba(0, 0, 0, 0.15));\n border-top-color: var(--gnome-accent-color, #3584e4);\n animation: spin 0.75s linear infinite;\n flex-shrink: 0;\n}\n\n/* ─── Sizes ────────────────────────────────────────────────────────────────── */\n\n.sm {\n width: 16px;\n height: 16px;\n border-width: 2px;\n}\n\n.md {\n width: 24px;\n height: 24px;\n border-width: 2.5px;\n}\n\n.lg {\n width: 36px;\n height: 36px;\n border-width: 3px;\n}\n\n/* ─── Keyframes ────────────────────────────────────────────────────────────── */\n\n@keyframes spin {\n to { transform: rotate(360deg); }\n}\n\n/* ─── Dark mode ────────────────────────────────────────────────────────────── */\n\n@media (prefers-color-scheme: dark) {\n .spinner {\n border-color: var(--gnome-spinner-track-color, rgba(255, 255, 255, 0.15));\n border-top-color: var(--gnome-accent-color, #3584e4);\n }\n}\n\n/* ─── Reduced motion ───────────────────────────────────────────────────────── */\n\n@media (prefers-reduced-motion: reduce) {\n .spinner {\n animation-duration: 2s;\n }\n}\n","import type { HTMLAttributes } from \"react\";\nimport styles from \"./Spinner.module.css\";\n\nexport type SpinnerSize = \"sm\" | \"md\" | \"lg\";\n\nexport interface SpinnerProps extends HTMLAttributes<HTMLSpanElement> {\n /** Size of the spinner. Defaults to `\"md\"`. */\n size?: SpinnerSize;\n /**\n * Accessible label announced by screen readers.\n * Defaults to `\"Loading…\"`. Set to `\"\"` to silence if a sibling label is present.\n */\n label?: string;\n}\n\n/**\n * Indeterminate loading indicator following the Adwaita spinner style.\n *\n * Renders a spinning ring using a pure CSS animation that respects\n * `prefers-reduced-motion`.\n *\n * @see https://gnome.pages.gitlab.gnome.org/libadwaita/doc/main/style-classes.html\n */\nexport function Spinner({\n size = \"md\",\n label = \"Loading\\u2026\",\n className,\n ...props\n}: SpinnerProps) {\n const classes = [styles.spinner, styles[size], className]\n .filter(Boolean)\n .join(\" \");\n\n return (\n <span\n role=\"status\"\n aria-label={label || undefined}\n aria-hidden={label === \"\" ? true : undefined}\n className={classes}\n {...props}\n />\n );\n}\n","/* ─── Base ─────────────────────────────────────────────────────────────────── */\n\n.avatar {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n border-radius: var(--gnome-radius-pill);\n overflow: hidden;\n flex-shrink: 0;\n user-select: none;\n /* Subtle ring so the avatar reads on any background */\n box-shadow: inset 0 0 0 1px rgba(0, 0, 0, 0.08);\n}\n\n/* ─── Sizes ────────────────────────────────────────────────────────────────── */\n\n.sm {\n width: 24px;\n height: 24px;\n}\n\n.md {\n width: 32px;\n height: 32px;\n}\n\n.lg {\n width: 48px;\n height: 48px;\n}\n\n.xl {\n width: 64px;\n height: 64px;\n}\n\n/* ─── Image ────────────────────────────────────────────────────────────────── */\n\n.image {\n width: 100%;\n height: 100%;\n object-fit: cover;\n display: block;\n}\n\n/* ─── Initials ─────────────────────────────────────────────────────────────── */\n\n.initials {\n font-family: var(--gnome-font-family);\n font-weight: var(--gnome-font-weight-semibold);\n line-height: 1;\n color: #ffffff;\n /* Scale initials font relative to the avatar size */\n font-size: 0.42em;\n}\n\n.sm .initials { font-size: 0.5em; }\n.md .initials { font-size: 0.60em; }\n.lg .initials { font-size: 1.0em; }\n.xl .initials { font-size: 1.40em; }\n\n/* ─── Color variants (initials fallback) ───────────────────────────────────── */\n\n.color-blue { background-color: var(--gnome-blue-3, #3584e4); }\n.color-green { background-color: var(--gnome-green-4, #2ec27e); }\n.color-yellow {\n background-color: var(--gnome-yellow-4, #f5c211);\n /* yellow needs dark initials for contrast */\n}\n.color-yellow .initials { color: rgba(0, 0, 0, 0.8); }\n\n.color-orange { background-color: var(--gnome-orange-4, #e66100); }\n.color-red { background-color: var(--gnome-red-3, #e01b24); }\n.color-purple { background-color: var(--gnome-purple-3, #9141ac); }\n.color-brown { background-color: var(--gnome-brown-3, #986a44); }\n.color-teal { background-color: #2190a4; }\n.color-slate { background-color: var(--gnome-dark-2, #5e5c64); }\n\n/* ─── Dark mode ring ───────────────────────────────────────────────────────── */\n\n@media (prefers-color-scheme: dark) {\n .avatar {\n box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.08);\n }\n}\n","import type { HTMLAttributes } from \"react\";\nimport styles from \"./Avatar.module.css\";\n\nexport type AvatarSize = \"sm\" | \"md\" | \"lg\" | \"xl\";\n\n/**\n * Named color palette for the initials fallback.\n * Mirrors libadwaita's avatar color set.\n */\nexport type AvatarColor =\n | \"blue\"\n | \"green\"\n | \"yellow\"\n | \"orange\"\n | \"red\"\n | \"purple\"\n | \"brown\"\n | \"teal\"\n | \"slate\";\n\nexport interface AvatarProps extends HTMLAttributes<HTMLSpanElement> {\n /**\n * Full name used to generate initials and — when `color` is omitted —\n * to deterministically pick a background color.\n */\n name?: string;\n /** Image URL. When provided the initials fallback is hidden. */\n src?: string;\n /** Alt text for the image. Defaults to `name`. */\n alt?: string;\n /** Size of the avatar. Defaults to `\"md\"`. */\n size?: AvatarSize;\n /**\n * Override the auto-derived background color for the initials fallback.\n * When omitted a color is derived from `name` via a stable hash.\n */\n color?: AvatarColor;\n}\n\nconst COLOR_LIST: AvatarColor[] = [\n \"blue\",\n \"green\",\n \"yellow\",\n \"orange\",\n \"red\",\n \"purple\",\n \"brown\",\n \"teal\",\n \"slate\",\n];\n\n/** Stable, non-cryptographic hash → index into COLOR_LIST. */\nfunction nameToColor(name: string): AvatarColor {\n let hash = 0;\n for (let i = 0; i < name.length; i++) {\n hash = (hash * 31 + name.charCodeAt(i)) >>> 0;\n }\n return COLOR_LIST[hash % COLOR_LIST.length];\n}\n\n/** Extract up to 2 initials from a name string. */\nfunction getInitials(name: string): string {\n const parts = name.trim().split(/\\s+/).filter(Boolean);\n if (parts.length === 0) return \"\";\n if (parts.length === 1) return parts[0].charAt(0).toUpperCase();\n return (\n parts[0].charAt(0).toUpperCase() +\n parts[parts.length - 1].charAt(0).toUpperCase()\n );\n}\n\n/**\n * Circular avatar with image or initials fallback.\n *\n * Follows the Adwaita `AdwAvatar` pattern — deterministic color from name,\n * up to two initials when no image is supplied.\n *\n * @see https://gnome.pages.gitlab.gnome.org/libadwaita/doc/main/class.Avatar.html\n */\nexport function Avatar({\n name = \"\",\n src,\n alt,\n size = \"md\",\n color,\n className,\n ...props\n}: AvatarProps) {\n const resolvedColor = color ?? (name ? nameToColor(name) : \"blue\");\n const initials = getInitials(name);\n\n const classes = [\n styles.avatar,\n styles[size],\n !src ? styles[`color-${resolvedColor}`] : null,\n className,\n ]\n .filter(Boolean)\n .join(\" \");\n\n return (\n <span\n role=\"img\"\n aria-label={alt ?? name ?? \"Avatar\"}\n className={classes}\n {...props}\n >\n {src ? (\n <img src={src} alt={alt ?? name} className={styles.image} />\n ) : (\n <span aria-hidden=\"true\" className={styles.initials}>\n {initials}\n </span>\n )}\n </span>\n );\n}\n","/* ─── Base ─────────────────────────────────────────────────────────────────── */\n\n.separator {\n border: none;\n margin: 0;\n background-color: var(--gnome-card-shade-color, rgba(0, 0, 0, 0.07));\n flex-shrink: 0;\n}\n\n/* ─── Horizontal ───────────────────────────────────────────────────────────── */\n\n.horizontal {\n display: block;\n width: 100%;\n height: 1px;\n}\n\n/* ─── Vertical ─────────────────────────────────────────────────────────────── */\n\n.vertical {\n display: inline-block;\n width: 1px;\n height: 100%;\n align-self: stretch;\n}\n","import type { HTMLAttributes } from \"react\";\nimport styles from \"./Separator.module.css\";\n\nexport type SeparatorOrientation = \"horizontal\" | \"vertical\";\n\nexport interface SeparatorProps extends HTMLAttributes<HTMLHRElement> {\n /** Direction of the dividing line. Defaults to `\"horizontal\"`. */\n orientation?: SeparatorOrientation;\n}\n\n/**\n * Thin dividing line that separates groups of content.\n *\n * Renders as `<hr>` (horizontal) or a `<div role=\"separator\">` (vertical).\n * Color is driven entirely by design tokens and adapts to dark mode automatically.\n *\n * @see https://developer.gnome.org/hig/patterns/containers.html\n */\nexport function Separator({\n orientation = \"horizontal\",\n className,\n ...props\n}: SeparatorProps) {\n const classes = [\n styles.separator,\n orientation === \"vertical\" ? styles.vertical : styles.horizontal,\n className,\n ]\n .filter(Boolean)\n .join(\" \");\n\n if (orientation === \"vertical\") {\n return (\n <div\n role=\"separator\"\n aria-orientation=\"vertical\"\n className={classes}\n {...(props as HTMLAttributes<HTMLDivElement>)}\n />\n );\n }\n\n return <hr className={classes} {...props} />;\n}\n","/* ─── Reset & layout ───────────────────────────────────────────────────────── */\n\n.switch {\n appearance: none;\n -webkit-appearance: none;\n position: relative;\n display: inline-block;\n cursor: pointer;\n flex-shrink: 0;\n\n width: 56px;\n height: 28px;\n border-radius: var(--gnome-radius-pill);\n\n background-color: var(--gnome-light-4, #c0bfbc);\n border: 1px solid var(--gnome-border-subtle);\n\n transition:\n background-color var(--gnome-duration-fast) var(--gnome-easing-default),\n border-color var(--gnome-duration-fast) var(--gnome-easing-default);\n}\n\n/* ─── Thumb ────────────────────────────────────────────────────────────────── */\n\n.switch::before {\n content: \"\";\n position: absolute;\n top: 3px;\n left: 3px;\n width: 20px;\n height: 20px;\n border-radius: var(--gnome-radius-pill);\n background-color: #ffffff;\n box-shadow: 0 1px 3px rgba(0, 0, 0, 0.25);\n transition: transform var(--gnome-duration-fast) var(--gnome-easing-default);\n}\n\n/* ─── Checked ──────────────────────────────────────────────────────────────── */\n\n.switch:checked {\n background-color: var(--gnome-accent-bg-color, #3584e4);\n border-color: var(--gnome-accent-bg-color, #3584e4);\n}\n\n.switch:checked::before {\n transform: translateX(28px);\n}\n\n/* ─── Focus ────────────────────────────────────────────────────────────────── */\n\n.switch:focus-visible {\n outline: none;\n box-shadow:\n 0 0 0 var(--gnome-focus-ring-offset, 2px) var(--gnome-window-bg-color, #fafafa),\n 0 0 0 calc(var(--gnome-focus-ring-offset, 2px) + var(--gnome-focus-ring-width, 3px))\n var(--gnome-focus-ring-color, #3584e4);\n}\n\n/* ─── Disabled ─────────────────────────────────────────────────────────────── */\n\n.switch:disabled {\n opacity: var(--gnome-opacity-disabled, 0.5);\n cursor: not-allowed;\n}\n\n/* ─── Dark mode ────────────────────────────────────────────────────────────── */\n\n@media (prefers-color-scheme: dark) {\n .switch {\n background-color: var(--gnome-dark-2, #5e5c64);\n border-color: var(--gnome-border-subtle);\n }\n\n .switch::before {\n background-color: var(--gnome-light-3, #deddda);\n }\n\n .switch:checked {\n background-color: var(--gnome-accent-bg-color, #3584e4);\n border-color: var(--gnome-accent-bg-color, #3584e4);\n }\n\n .switch:checked::before {\n background-color: #ffffff;\n }\n}\n\n/* ─── High contrast ────────────────────────────────────────────────────────── */\n\n@media (prefers-contrast: more) {\n .switch {\n border-width: 2px;\n }\n\n .switch::before {\n box-shadow: none;\n }\n}\n\n/* ─── Reduced motion ───────────────────────────────────────────────────────── */\n\n@media (prefers-reduced-motion: reduce) {\n .switch,\n .switch::before {\n transition: none;\n }\n}\n","import type { InputHTMLAttributes } from \"react\";\nimport styles from \"./Switch.module.css\";\n\nexport interface SwitchProps\n extends Omit<InputHTMLAttributes<HTMLInputElement>, \"type\" | \"size\"> {\n /** Accessible label. Required when no visible `<label>` is associated. */\n \"aria-label\"?: string;\n}\n\n/**\n * On/off toggle following the Adwaita switch style.\n *\n * Renders as `<input type=\"checkbox\" role=\"switch\">` so it is natively\n * keyboard-accessible and works with standard form libraries.\n *\n * @see https://developer.gnome.org/hig/patterns/controls/switches.html\n */\nexport function Switch({ className, ...props }: SwitchProps) {\n return (\n <input\n type=\"checkbox\"\n role=\"switch\"\n className={[styles.switch, className].filter(Boolean).join(\" \")}\n {...props}\n />\n );\n}\n","/* ─── Reset & base ─────────────────────────────────────────────────────────── */\n\n.checkbox {\n appearance: none;\n -webkit-appearance: none;\n position: relative;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n flex-shrink: 0;\n cursor: pointer;\n\n width: 18px;\n height: 18px;\n border-radius: var(--gnome-radius-sm, 4px);\n\n background-color: var(--gnome-card-bg-color, #ffffff);\n border: 1.5px solid var(--gnome-light-4, #c0bfbc);\n\n transition:\n background-color var(--gnome-duration-fast) var(--gnome-easing-default),\n border-color var(--gnome-duration-fast) var(--gnome-easing-default);\n}\n\n/* ─── Checkmark (checked) ──────────────────────────────────────────────────── */\n\n.checkbox::before {\n content: \"\";\n display: block;\n opacity: 0;\n transition: opacity var(--gnome-duration-fast) var(--gnome-easing-default);\n}\n\n.checkbox:checked {\n background-color: var(--gnome-accent-bg-color, #3584e4);\n border-color: var(--gnome-accent-bg-color, #3584e4);\n}\n\n.checkbox:checked::before {\n opacity: 1;\n /* SVG checkmark rendered via clip-path */\n width: 10px;\n height: 7px;\n background-color: #ffffff;\n clip-path: polygon(\n 14% 44%,\n 0 58%,\n 36% 100%,\n 100% 12%,\n 84% 0%,\n 36% 68%\n );\n}\n\n/* ─── Indeterminate (mixed) ────────────────────────────────────────────────── */\n\n.checkbox:indeterminate {\n background-color: var(--gnome-accent-bg-color, #3584e4);\n border-color: var(--gnome-accent-bg-color, #3584e4);\n}\n\n.checkbox:indeterminate::before {\n opacity: 1;\n width: 10px;\n height: 2px;\n background-color: #ffffff;\n clip-path: none;\n border-radius: 1px;\n}\n\n/* ─── Focus ────────────────────────────────────────────────────────────────── */\n\n.checkbox:focus-visible {\n outline: none;\n box-shadow:\n 0 0 0 var(--gnome-focus-ring-offset, 2px) var(--gnome-window-bg-color, #fafafa),\n 0 0 0 calc(var(--gnome-focus-ring-offset, 2px) + var(--gnome-focus-ring-width, 3px))\n var(--gnome-focus-ring-color, #3584e4);\n}\n\n/* ─── Hover ────────────────────────────────────────────────────────────────── */\n\n.checkbox:not(:checked):not(:indeterminate):hover {\n border-color: var(--gnome-light-5, #9a9996);\n background-color: color-mix(\n in srgb,\n var(--gnome-card-bg-color, #ffffff) 94%,\n black 6%\n );\n}\n\n/* ─── Disabled ─────────────────────────────────────────────────────────────── */\n\n.checkbox:disabled {\n opacity: var(--gnome-opacity-disabled, 0.5);\n cursor: not-allowed;\n}\n\n/* ─── Dark mode ────────────────────────────────────────────────────────────── */\n\n@media (prefers-color-scheme: dark) {\n .checkbox {\n background-color: var(--gnome-card-bg-color, #383838);\n border-color: var(--gnome-dark-1, #77767b);\n }\n\n .checkbox:not(:checked):not(:indeterminate):hover {\n border-color: var(--gnome-light-4, #c0bfbc);\n background-color: color-mix(\n in srgb,\n var(--gnome-card-bg-color, #383838) 85%,\n white 15%\n );\n }\n}\n\n/* ─── High contrast ────────────────────────────────────────────────────────── */\n\n@media (prefers-contrast: more) {\n .checkbox {\n border-width: 2px;\n border-color: var(--gnome-window-fg-color, rgba(0, 0, 0, 0.8));\n }\n\n .checkbox:not(:checked):not(:indeterminate):hover {\n border-color: var(--gnome-window-fg-color, rgba(0, 0, 0, 0.8));\n background-color: var(--gnome-hover-overlay);\n }\n}\n\n/* ─── Reduced motion ───────────────────────────────────────────────────────── */\n\n@media (prefers-reduced-motion: reduce) {\n .checkbox,\n .checkbox::before {\n transition: none;\n }\n}\n","import { useEffect, useRef, type InputHTMLAttributes } from \"react\";\nimport styles from \"./Checkbox.module.css\";\n\nexport interface CheckboxProps\n extends Omit<InputHTMLAttributes<HTMLInputElement>, \"type\"> {\n /**\n * Indeterminate state — shown when only some children in a group are checked.\n * Takes visual precedence over `checked`.\n */\n indeterminate?: boolean;\n /** Accessible label. Required when no visible `<label>` is associated. */\n \"aria-label\"?: string;\n}\n\n/**\n * Checkbox for multi-selection, following the GNOME HIG and Adwaita style.\n *\n * Supports three states: unchecked, checked, and indeterminate (mixed).\n * Use `indeterminate` for \"select all\" controls when only some items are selected.\n *\n * Renders as `<input type=\"checkbox\">` — natively keyboard-accessible and\n * compatible with standard form libraries.\n *\n * @see https://developer.gnome.org/hig/patterns/controls/checkboxes.html\n */\nexport function Checkbox({ indeterminate = false, className, ...props }: CheckboxProps) {\n const ref = useRef<HTMLInputElement>(null);\n\n useEffect(() => {\n if (ref.current) {\n ref.current.indeterminate = indeterminate;\n }\n }, [indeterminate]);\n\n return (\n <input\n ref={ref}\n type=\"checkbox\"\n className={[styles.checkbox, className].filter(Boolean).join(\" \")}\n {...props}\n />\n );\n}\n","/* ─── Wrapper ──────────────────────────────────────────────────────────────── */\n\n.wrapper {\n display: flex;\n flex-direction: column;\n gap: var(--gnome-space-1, 6px);\n}\n\n.disabled {\n opacity: var(--gnome-opacity-disabled, 0.5);\n}\n\n/* ─── Label ────────────────────────────────────────────────────────────────── */\n\n.label {\n font-family: var(--gnome-font-family);\n font-size: var(--gnome-font-size-body, 1rem);\n font-weight: var(--gnome-font-weight-semibold, 600);\n line-height: var(--gnome-line-height-body, 1.5);\n color: var(--gnome-window-fg-color, rgba(0, 0, 0, 0.8));\n}\n\n/* ─── Input ────────────────────────────────────────────────────────────────── */\n\n.input {\n appearance: none;\n -webkit-appearance: none;\n\n width: 100%;\n padding: 8px var(--gnome-space-2, 12px);\n\n font-family: var(--gnome-font-family);\n font-size: var(--gnome-font-size-body, 1rem);\n font-weight: var(--gnome-font-weight-normal, 400);\n line-height: var(--gnome-line-height-body, 1.5);\n color: var(--gnome-view-fg-color, rgba(0, 0, 0, 0.8));\n\n background-color: var(--gnome-view-bg-color, #ffffff);\n border: 1px solid var(--gnome-border-subtle);\n border-radius: var(--gnome-radius-md, 8px);\n\n box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.06);\n\n transition:\n border-color var(--gnome-duration-fast) var(--gnome-easing-default),\n box-shadow var(--gnome-duration-fast) var(--gnome-easing-default);\n}\n\n.input::placeholder {\n color: var(--gnome-window-fg-color, rgba(0, 0, 0, 0.8));\n opacity: var(--gnome-opacity-dim, 0.55);\n}\n\n.input:focus {\n outline: none;\n}\n\n.input:focus-visible {\n border-color: var(--gnome-accent-color, #3584e4);\n box-shadow:\n inset 0 1px 2px rgba(0, 0, 0, 0.06),\n 0 0 0 var(--gnome-focus-ring-offset, 2px) var(--gnome-window-bg-color, #fafafa),\n 0 0 0 calc(var(--gnome-focus-ring-offset, 2px) + var(--gnome-focus-ring-width, 3px))\n var(--gnome-focus-ring-color, #3584e4);\n}\n\n.input:disabled {\n cursor: not-allowed;\n /* opacity handled by .disabled wrapper */\n}\n\n/* ─── Error state ──────────────────────────────────────────────────────────── */\n\n.errorInput {\n border-color: var(--gnome-error-color, #e01b24);\n}\n\n.errorInput:focus-visible {\n border-color: var(--gnome-error-color, #e01b24);\n box-shadow:\n inset 0 1px 2px rgba(0, 0, 0, 0.06),\n 0 0 0 var(--gnome-focus-ring-offset, 2px) var(--gnome-window-bg-color, #fafafa),\n 0 0 0 calc(var(--gnome-focus-ring-offset, 2px) + var(--gnome-focus-ring-width, 3px))\n var(--gnome-error-color, #e01b24);\n}\n\n/* ─── Hint / error text ────────────────────────────────────────────────────── */\n\n.hint {\n font-family: var(--gnome-font-family);\n font-size: var(--gnome-font-size-caption, 0.75rem);\n line-height: var(--gnome-line-height-body, 1.5);\n color: var(--gnome-window-fg-color, rgba(0, 0, 0, 0.8));\n opacity: var(--gnome-opacity-dim, 0.55);\n}\n\n.errorHint {\n opacity: 1;\n color: var(--gnome-error-color, #e01b24);\n}\n\n/* ─── Dark mode ────────────────────────────────────────────────────────────── */\n\n@media (prefers-color-scheme: dark) {\n .input {\n background-color: var(--gnome-view-bg-color, #1e1e1e);\n border-color: var(--gnome-border-subtle);\n box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.2);\n }\n}\n\n/* ─── High contrast ────────────────────────────────────────────────────────── */\n\n@media (prefers-contrast: more) {\n .input {\n border-width: 2px;\n box-shadow: none;\n }\n}\n\n/* ─── Reduced motion ───────────────────────────────────────────────────────── */\n\n@media (prefers-reduced-motion: reduce) {\n .input {\n transition: none;\n }\n}\n","import { useId, type InputHTMLAttributes } from \"react\";\nimport styles from \"./TextField.module.css\";\n\nexport interface TextFieldProps\n extends Omit<InputHTMLAttributes<HTMLInputElement>, \"id\"> {\n /** Visible label rendered above the input. */\n label?: string;\n /** Helper text rendered below the input. Hidden when `error` is set. */\n helperText?: string;\n /**\n * Error message rendered below the input in place of `helperText`.\n * Also applies the error visual state to the input border.\n */\n error?: string;\n /** Explicit id. Auto-generated when omitted. */\n id?: string;\n}\n\n/**\n * Single-line text input with label, helper text, and error state.\n *\n * Follows the Adwaita `GtkEntry` / `.entry` style class.\n *\n * @see https://developer.gnome.org/hig/patterns/controls/text-fields.html\n */\nexport function TextField({\n label,\n helperText,\n error,\n id: idProp,\n className,\n disabled,\n ...props\n}: TextFieldProps) {\n const autoId = useId();\n const id = idProp ?? autoId;\n const helpId = `${id}-help`;\n\n return (\n <div className={[styles.wrapper, disabled ? styles.disabled : null].filter(Boolean).join(\" \")}>\n {label && (\n <label htmlFor={id} className={styles.label}>\n {label}\n </label>\n )}\n\n <input\n id={id}\n disabled={disabled}\n aria-describedby={error || helperText ? helpId : undefined}\n aria-invalid={error ? true : undefined}\n className={[\n styles.input,\n error ? styles.errorInput : null,\n className,\n ]\n .filter(Boolean)\n .join(\" \")}\n {...props}\n />\n\n {(error || helperText) && (\n <span\n id={helpId}\n className={[styles.hint, error ? styles.errorHint : null]\n .filter(Boolean)\n .join(\" \")}\n >\n {error ?? helperText}\n </span>\n )}\n </div>\n );\n}\n","/* ─── Reset & base ─────────────────────────────────────────────────────────── */\n\n.radio {\n appearance: none;\n -webkit-appearance: none;\n position: relative;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n flex-shrink: 0;\n cursor: pointer;\n\n width: 18px;\n height: 18px;\n border-radius: var(--gnome-radius-pill);\n\n background-color: var(--gnome-card-bg-color, #ffffff);\n border: 1.5px solid var(--gnome-light-4, #c0bfbc);\n\n transition:\n background-color var(--gnome-duration-fast) var(--gnome-easing-default),\n border-color var(--gnome-duration-fast) var(--gnome-easing-default);\n}\n\n/* ─── Inner dot (checked) ──────────────────────────────────────────────────── */\n\n.radio::before {\n content: \"\";\n display: block;\n width: 6px;\n height: 6px;\n border-radius: var(--gnome-radius-pill);\n background-color: #ffffff;\n opacity: 0;\n transition: opacity var(--gnome-duration-fast) var(--gnome-easing-default);\n}\n\n.radio:checked {\n background-color: var(--gnome-accent-bg-color, #3584e4);\n border-color: var(--gnome-accent-bg-color, #3584e4);\n}\n\n.radio:checked::before {\n opacity: 1;\n}\n\n/* ─── Hover ────────────────────────────────────────────────────────────────── */\n\n.radio:not(:checked):hover {\n border-color: var(--gnome-light-5, #9a9996);\n background-color: color-mix(\n in srgb,\n var(--gnome-card-bg-color, #ffffff) 94%,\n black 6%\n );\n}\n\n/* ─── Focus ────────────────────────────────────────────────────────────────── */\n\n.radio:focus-visible {\n outline: none;\n box-shadow:\n 0 0 0 var(--gnome-focus-ring-offset, 2px) var(--gnome-window-bg-color, #fafafa),\n 0 0 0 calc(var(--gnome-focus-ring-offset, 2px) + var(--gnome-focus-ring-width, 3px))\n var(--gnome-focus-ring-color, #3584e4);\n}\n\n/* ─── Disabled ─────────────────────────────────────────────────────────────── */\n\n.radio:disabled {\n opacity: var(--gnome-opacity-disabled, 0.5);\n cursor: not-allowed;\n}\n\n/* ─── Dark mode ────────────────────────────────────────────────────────────── */\n\n@media (prefers-color-scheme: dark) {\n .radio {\n background-color: var(--gnome-card-bg-color, #383838);\n border-color: var(--gnome-dark-1, #77767b);\n }\n\n .radio:not(:checked):hover {\n border-color: var(--gnome-light-4, #c0bfbc);\n background-color: color-mix(\n in srgb,\n var(--gnome-card-bg-color, #383838) 85%,\n white 15%\n );\n }\n}\n\n/* ─── High contrast ────────────────────────────────────────────────────────── */\n\n@media (prefers-contrast: more) {\n .radio {\n border-width: 2px;\n border-color: var(--gnome-window-fg-color, rgba(0, 0, 0, 0.8));\n }\n\n .radio:not(:checked):hover {\n border-color: var(--gnome-window-fg-color, rgba(0, 0, 0, 0.8));\n background-color: var(--gnome-hover-overlay);\n }\n}\n\n/* ─── Reduced motion ───────────────────────────────────────────────────────── */\n\n@media (prefers-reduced-motion: reduce) {\n .radio,\n .radio::before {\n transition: none;\n }\n}\n","import type { InputHTMLAttributes } from \"react\";\nimport styles from \"./RadioButton.module.css\";\n\nexport interface RadioButtonProps\n extends Omit<InputHTMLAttributes<HTMLInputElement>, \"type\"> {\n /** Accessible label. Required when no visible `<label>` is associated. */\n \"aria-label\"?: string;\n}\n\n/**\n * Single-selection radio button following the GNOME HIG and Adwaita style.\n *\n * Group radio buttons by giving them the same `name` attribute.\n * Renders as `<input type=\"radio\">` — natively keyboard-accessible\n * (arrow keys cycle within the group) and compatible with standard form libraries.\n *\n * @see https://developer.gnome.org/hig/patterns/controls/radio-buttons.html\n */\nexport function RadioButton({ className, ...props }: RadioButtonProps) {\n return (\n <input\n type=\"radio\"\n className={[styles.radio, className].filter(Boolean).join(\" \")}\n {...props}\n />\n );\n}\n","/* ─── Track ────────────────────────────────────────────────────────────────── */\n\n.track {\n position: relative;\n width: 100%;\n height: 6px;\n border-radius: var(--gnome-radius-pill);\n background-color: var(--gnome-light-3, #deddda);\n overflow: hidden;\n}\n\n/* ─── Fill (determinate) ───────────────────────────────────────────────────── */\n\n.fill {\n height: 100%;\n border-radius: var(--gnome-radius-pill);\n background-color: var(--gnome-accent-bg-color, #3584e4);\n transition: width var(--gnome-duration-normal) var(--gnome-easing-default);\n will-change: width;\n}\n\n/* ─── Indeterminate pulse ──────────────────────────────────────────────────── */\n\n.indeterminate {\n position: absolute;\n top: 0;\n width: 40%;\n animation: pulse 1.4s var(--gnome-easing-default) infinite;\n}\n\n@keyframes pulse {\n 0% { left: -40%; }\n 100% { left: 100%; }\n}\n\n/* ─── Dark mode ────────────────────────────────────────────────────────────── */\n\n@media (prefers-color-scheme: dark) {\n .track {\n background-color: var(--gnome-dark-2, #5e5c64);\n }\n}\n\n/* ─── Reduced motion ───────────────────────────────────────────────────────── */\n\n@media (prefers-reduced-motion: reduce) {\n .fill {\n transition: none;\n }\n\n .indeterminate {\n animation: none;\n left: 0;\n width: 100%;\n opacity: 0.5;\n }\n}\n","import type { HTMLAttributes } from \"react\";\nimport styles from \"./ProgressBar.module.css\";\n\nexport interface ProgressBarProps extends HTMLAttributes<HTMLDivElement> {\n /**\n * Current progress value between `0` and `1` (e.g. `0.6` = 60 %).\n * Omit or set to `undefined` for the indeterminate (pulsing) state.\n */\n value?: number;\n /** Accessible label describing what is loading. */\n \"aria-label\"?: string;\n /** Associates the bar with a visible element that labels it. */\n \"aria-labelledby\"?: string;\n}\n\n/**\n * Determinate and indeterminate progress bar following the Adwaita style.\n *\n * - **Determinate** — pass `value` (0–1) to show exact progress.\n * - **Indeterminate** — omit `value` for an animated pulse when duration is unknown.\n *\n * Renders as `role=\"progressbar\"` with `aria-valuenow` / `aria-valuemin` /\n * `aria-valuemax` for screen readers.\n *\n * @see https://developer.gnome.org/hig/patterns/feedback/progress.html\n */\nexport function ProgressBar({\n value,\n className,\n \"aria-label\": ariaLabel,\n \"aria-labelledby\": ariaLabelledBy,\n ...props\n}: ProgressBarProps) {\n const isIndeterminate = value === undefined || value === null;\n const clamped = isIndeterminate ? undefined : Math.min(1, Math.max(0, value));\n const percent = clamped !== undefined ? clamped * 100 : undefined;\n\n return (\n <div\n role=\"progressbar\"\n aria-label={ariaLabel}\n aria-labelledby={ariaLabelledBy}\n aria-valuenow={clamped !== undefined ? Math.round(percent!) : undefined}\n aria-valuemin={isIndeterminate ? undefined : 0}\n aria-valuemax={isIndeterminate ? undefined : 100}\n className={[styles.track, className].filter(Boolean).join(\" \")}\n {...props}\n >\n <div\n className={[\n styles.fill,\n isIndeterminate ? styles.indeterminate : null,\n ]\n .filter(Boolean)\n .join(\" \")}\n style={!isIndeterminate ? { width: `${percent}%` } : undefined}\n />\n </div>\n );\n}\n","/* ─── Base ─────────────────────────────────────────────────────────────────── */\n\n.banner {\n display: flex;\n align-items: center;\n gap: var(--gnome-space-2, 12px);\n padding: 10px var(--gnome-space-4, 24px);\n min-height: 44px;\n width: 100%;\n font-family: var(--gnome-font-family);\n font-size: var(--gnome-font-size-body, 1rem);\n line-height: var(--gnome-line-height-body, 1.5);\n}\n\n/* ─── Message ──────────────────────────────────────────────────────────────── */\n\n.message {\n flex: 1;\n min-width: 0;\n}\n\n/* ─── Actions (action button + dismiss) ────────────────────────────────────── */\n\n.actions {\n display: flex;\n align-items: center;\n gap: var(--gnome-space-1, 6px);\n flex-shrink: 0;\n}\n\n/* ─── Action button ────────────────────────────────────────────────────────── */\n\n.actionBtn {\n appearance: none;\n border: none;\n background: transparent;\n font: inherit;\n font-weight: var(--gnome-font-weight-semibold, 600);\n cursor: pointer;\n padding: 4px var(--gnome-space-2, 12px);\n border-radius: var(--gnome-radius-md, 8px);\n transition: background-color var(--gnome-duration-fast) var(--gnome-easing-default);\n}\n\n.actionBtn:focus-visible {\n outline: none;\n box-shadow:\n 0 0 0 var(--gnome-focus-ring-offset, 2px) transparent,\n 0 0 0 calc(var(--gnome-focus-ring-offset, 2px) + var(--gnome-focus-ring-width, 3px))\n currentColor;\n}\n\n/* ─── Dismiss button ───────────────────────────────────────────────────────── */\n\n.dismissBtn {\n appearance: none;\n border: none;\n background: transparent;\n cursor: pointer;\n display: flex;\n align-items: center;\n justify-content: center;\n padding: 4px;\n border-radius: var(--gnome-radius-md, 8px);\n opacity: 0.6;\n transition:\n opacity var(--gnome-duration-fast) var(--gnome-easing-default),\n background-color var(--gnome-duration-fast) var(--gnome-easing-default);\n}\n\n.dismissBtn:hover { opacity: 1; }\n.dismissBtn:active { opacity: 0.8; }\n\n.dismissBtn:focus-visible {\n outline: none;\n opacity: 1;\n box-shadow:\n 0 0 0 var(--gnome-focus-ring-offset, 2px) transparent,\n 0 0 0 calc(var(--gnome-focus-ring-offset, 2px) + var(--gnome-focus-ring-width, 3px))\n currentColor;\n}\n\n/* ─── Variant: info ────────────────────────────────────────────────────────── */\n\n.info {\n background-color: var(--gnome-accent-bg-color, #3584e4);\n color: var(--gnome-accent-fg-color, #ffffff);\n}\n\n.actionBtn-info:hover {\n background-color: rgba(255, 255, 255, 0.15);\n}\n.actionBtn-info:active {\n background-color: rgba(255, 255, 255, 0.25);\n}\n\n/* ─── Variant: warning ─────────────────────────────────────────────────────── */\n\n.warning {\n background-color: var(--gnome-warning-bg-color, #f6d32d);\n color: var(--gnome-warning-fg-color, rgba(0, 0, 0, 0.8));\n}\n\n.actionBtn-warning:hover {\n background-color: rgba(0, 0, 0, 0.08);\n}\n.actionBtn-warning:active {\n background-color: rgba(0, 0, 0, 0.14);\n}\n\n/* ─── Variant: error ───────────────────────────────────────────────────────── */\n\n.error {\n background-color: var(--gnome-error-bg-color, #e01b24);\n color: var(--gnome-error-fg-color, #ffffff);\n}\n\n.actionBtn-error:hover {\n background-color: rgba(255, 255, 255, 0.15);\n}\n.actionBtn-error:active {\n background-color: rgba(255, 255, 255, 0.25);\n}\n\n/* ─── Variant: success ─────────────────────────────────────────────────────── */\n\n.success {\n background-color: var(--gnome-success-bg-color, #2ec27e);\n color: var(--gnome-success-fg-color, #ffffff);\n}\n\n.actionBtn-success:hover {\n background-color: rgba(255, 255, 255, 0.15);\n}\n.actionBtn-success:active {\n background-color: rgba(255, 255, 255, 0.25);\n}\n","import type { HTMLAttributes, ReactNode } from \"react\";\nimport styles from \"./Banner.module.css\";\n\nexport type BannerVariant = \"info\" | \"warning\" | \"error\" | \"success\";\n\nexport interface BannerProps extends HTMLAttributes<HTMLDivElement> {\n /**\n * Visual emphasis level.\n * - `info` (default) — neutral, accent-colored. Use for tips and notices.\n * - `warning` — yellow. Use for recoverable problems.\n * - `error` — red. Use for failures that need attention.\n * - `success` — green. Use for confirmations.\n */\n variant?: BannerVariant;\n /** The message text. Keep it short — one or two sentences. */\n children: ReactNode;\n /**\n * Label for the optional action button placed at the trailing end.\n * When provided, `onAction` should also be supplied.\n */\n actionLabel?: string;\n /** Called when the user clicks the action button. */\n onAction?: () => void;\n /**\n * When true a dismiss (×) button is shown at the trailing edge.\n * Provide `onDismiss` to handle removal from the DOM.\n */\n dismissible?: boolean;\n /** Called when the user clicks the dismiss button. */\n onDismiss?: () => void;\n}\n\n/**\n * Persistent message strip displayed at the top of a view.\n *\n * Mirrors the Adwaita `AdwBanner` pattern. Use for important information\n * that persists until the user acts or explicitly dismisses it.\n *\n * @see https://gnome.pages.gitlab.gnome.org/libadwaita/doc/main/class.Banner.html\n * @see https://developer.gnome.org/hig/patterns/feedback/banners.html\n */\nexport function Banner({\n variant = \"info\",\n children,\n actionLabel,\n onAction,\n dismissible = false,\n onDismiss,\n className,\n ...props\n}: BannerProps) {\n return (\n <div\n role=\"status\"\n aria-live=\"polite\"\n className={[styles.banner, styles[variant], className]\n .filter(Boolean)\n .join(\" \")}\n {...props}\n >\n <span className={styles.message}>{children}</span>\n\n {(actionLabel || dismissible) && (\n <span className={styles.actions}>\n {actionLabel && (\n <button\n type=\"button\"\n className={[styles.actionBtn, styles[`actionBtn-${variant}`]]\n .filter(Boolean)\n .join(\" \")}\n onClick={onAction}\n >\n {actionLabel}\n </button>\n )}\n {dismissible && (\n <button\n type=\"button\"\n className={styles.dismissBtn}\n onClick={onDismiss}\n aria-label=\"Dismiss\"\n >\n {/* × close icon */}\n <svg\n width=\"16\"\n height=\"16\"\n viewBox=\"0 0 16 16\"\n aria-hidden=\"true\"\n fill=\"currentColor\"\n >\n <path d=\"M3.293 3.293a1 1 0 011.414 0L8 6.586l3.293-3.293a1 1 0 111.414 1.414L9.414 8l3.293 3.293a1 1 0 01-1.414 1.414L8 9.414l-3.293 3.293a1 1 0 01-1.414-1.414L6.586 8 3.293 4.707a1 1 0 010-1.414z\" />\n </svg>\n </button>\n )}\n </span>\n )}\n </div>\n );\n}\n","/* ─── Base ─────────────────────────────────────────────────────────────────── */\n\n.headerBar {\n display: grid;\n grid-template-columns: 1fr auto 1fr;\n align-items: center;\n gap: var(--gnome-space-1, 6px);\n\n padding: 0 var(--gnome-space-2, 12px);\n min-height: 47px;\n\n background-color: var(--gnome-headerbar-bg-color, #ebebeb);\n color: var(--gnome-headerbar-fg-color, rgba(0, 0, 0, 0.8));\n\n border-bottom: 1px solid var(--gnome-headerbar-border-color, rgba(0, 0, 0, 0.12));\n\n font-family: var(--gnome-font-family);\n}\n\n/* ─── Flat variant ─────────────────────────────────────────────────────────── */\n\n.flat {\n border-bottom-color: transparent;\n box-shadow: none;\n}\n\n/* ─── Leading / trailing slots ─────────────────────────────────────────────── */\n\n.slot {\n display: flex;\n align-items: center;\n gap: var(--gnome-space-1, 6px);\n min-width: 0;\n}\n\n.slotEnd {\n justify-content: flex-end;\n}\n\n/* ─── Title slot (center) ──────────────────────────────────────────────────── */\n\n.titleSlot {\n display: flex;\n align-items: center;\n justify-content: center;\n min-width: 0;\n padding: 0 var(--gnome-space-1, 6px);\n}\n\n/* ─── Title text ───────────────────────────────────────────────────────────── */\n\n.title {\n font-size: var(--gnome-font-size-body, 1rem);\n font-weight: var(--gnome-font-weight-semibold, 600);\n line-height: var(--gnome-line-height-heading, 1.2);\n color: var(--gnome-headerbar-fg-color, rgba(0, 0, 0, 0.8));\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n}\n\n/* ─── Dark mode ────────────────────────────────────────────────────────────── */\n\n@media (prefers-color-scheme: dark) {\n .headerBar {\n border-bottom-color: var(--gnome-headerbar-border-color, rgba(255, 255, 255, 0.1));\n }\n}\n","import type { HTMLAttributes, ReactNode } from \"react\";\nimport styles from \"./HeaderBar.module.css\";\n\nexport interface HeaderBarProps extends Omit<HTMLAttributes<HTMLElement>, \"title\"> {\n /** Centered title. Pass a string or a custom element. */\n title?: ReactNode;\n /** Controls placed at the leading (left) edge — back button, menu, etc. */\n start?: ReactNode;\n /** Controls placed at the trailing (right) edge — actions, overflow menu, etc. */\n end?: ReactNode;\n /**\n * When true the header bar blends into the window chrome\n * (no bottom border, slightly transparent background).\n * Use for the topmost bar of a full-window layout.\n */\n flat?: boolean;\n}\n\n/**\n * Title bar with centered title and leading/trailing action slots.\n *\n * Mirrors the Adwaita `AdwHeaderBar` pattern. Use `flat` buttons\n * (`<Button variant=\"flat\">`) inside the header bar per GNOME HIG.\n *\n * @see https://gnome.pages.gitlab.gnome.org/libadwaita/doc/main/class.HeaderBar.html\n * @see https://developer.gnome.org/hig/patterns/containers/header-bars.html\n */\nexport function HeaderBar({\n title,\n start,\n end,\n flat = false,\n className,\n ...props\n}: HeaderBarProps) {\n return (\n <header\n className={[styles.headerBar, flat ? styles.flat : null, className]\n .filter(Boolean)\n .join(\" \")}\n {...props}\n >\n {/* Leading slot */}\n <div className={styles.slot}>\n {start}\n </div>\n\n {/* Centered title */}\n <div className={styles.titleSlot} aria-live=\"polite\">\n {typeof title === \"string\" ? (\n <span className={styles.title}>{title}</span>\n ) : (\n title\n )}\n </div>\n\n {/* Trailing slot */}\n <div className={[styles.slot, styles.slotEnd].filter(Boolean).join(\" \")}>\n {end}\n </div>\n </header>\n );\n}\n","/* ─── Base ─────────────────────────────────────────────────────────────────── */\n\n.footer {\n display: grid;\n grid-template-columns: 1fr auto 1fr;\n align-items: center;\n gap: var(--gnome-space-1, 6px);\n\n padding: 0 var(--gnome-space-2, 12px);\n min-height: 40px;\n\n background-color: var(--gnome-headerbar-bg-color, #ebebeb);\n color: var(--gnome-headerbar-fg-color, rgba(0, 0, 0, 0.8));\n\n border-top: 1px solid var(--gnome-headerbar-border-color, rgba(0, 0, 0, 0.12));\n\n font-family: var(--gnome-font-family);\n font-size: var(--gnome-font-size-caption, 0.8125rem);\n}\n\n/* ─── Flat variant ─────────────────────────────────────────────────────────── */\n\n.flat {\n border-top-color: transparent;\n}\n\n/* ─── Leading / trailing slots ─────────────────────────────────────────────── */\n\n.slot {\n display: flex;\n align-items: center;\n gap: var(--gnome-space-1, 6px);\n min-width: 0;\n}\n\n.slotEnd {\n justify-content: flex-end;\n}\n\n/* ─── Center content ───────────────────────────────────────────────────────── */\n\n.center {\n display: flex;\n align-items: center;\n justify-content: center;\n gap: var(--gnome-space-1, 6px);\n min-width: 0;\n padding: 0 var(--gnome-space-1, 6px);\n}\n\n/* ─── Links inside footer ──────────────────────────────────────────────────── */\n\n.footer a {\n color: inherit;\n text-decoration: none;\n opacity: 0.7;\n transition: opacity var(--gnome-duration-fast, 100ms) var(--gnome-easing-default, ease);\n}\n\n.footer a:hover {\n opacity: 1;\n}\n\n.footer a:focus-visible {\n outline: var(--gnome-focus-ring-width, 2px) solid var(--gnome-focus-ring-color, #1c71d8);\n outline-offset: var(--gnome-focus-ring-offset, 2px);\n border-radius: var(--gnome-radius-sm, 4px);\n}\n\n/* ─── Dark mode ────────────────────────────────────────────────────────────── */\n\n@media (prefers-color-scheme: dark) {\n .footer {\n border-top-color: var(--gnome-headerbar-border-color, rgba(255, 255, 255, 0.1));\n }\n}\n\n/* ─── Reduced motion ───────────────────────────────────────────────────────── */\n\n@media (prefers-reduced-motion: reduce) {\n .footer a {\n transition: none;\n }\n}\n","import type { HTMLAttributes, ReactNode } from \"react\";\nimport styles from \"./Footer.module.css\";\n\nexport interface FooterProps extends HTMLAttributes<HTMLElement> {\n /** Content placed at the leading (left) edge — links, copyright, etc. */\n start?: ReactNode;\n /** Content placed at the trailing (right) edge — actions, links, etc. */\n end?: ReactNode;\n /** Center content or arbitrary children. */\n children?: ReactNode;\n /**\n * When true the footer has no top border.\n * Use when the footer is separated from content by another visual boundary.\n */\n flat?: boolean;\n}\n\n/**\n * Bottom bar with leading/trailing slots and optional center content.\n *\n * Mirrors the `HeaderBar` pattern at the bottom of a view or window.\n * Accepts plain text, links, and arbitrary ReactNode in each slot.\n *\n * @see https://developer.gnome.org/hig/patterns/containers/header-bars.html\n */\nexport function Footer({\n start,\n end,\n children,\n flat = false,\n className,\n ...props\n}: FooterProps) {\n return (\n <footer\n className={[styles.footer, flat ? styles.flat : null, className]\n .filter(Boolean)\n .join(\" \")}\n {...props}\n >\n <div className={styles.slot}>{start}</div>\n\n {children != null && (\n <div className={styles.center}>{children}</div>\n )}\n\n <div className={[styles.slot, styles.slotEnd].filter(Boolean).join(\" \")}>\n {end}\n </div>\n </footer>\n );\n}\n","/* ─── Wrapper (when used with anchor) ──────────────────────────────────────── */\n\n.wrapper {\n position: relative;\n display: inline-flex;\n flex-shrink: 0;\n}\n\n/* ─── Base badge ───────────────────────────────────────────────────────────── */\n\n.badge {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n\n font-family: var(--gnome-font-family);\n font-size: 0.6875rem; /* 11px — keeps it compact */\n font-weight: var(--gnome-font-weight-semibold, 600);\n line-height: 1;\n\n min-width: 18px;\n height: 18px;\n padding: 0 5px;\n border-radius: var(--gnome-radius-pill);\n\n /* Thin white ring to separate from any background */\n box-shadow: 0 0 0 2px var(--gnome-window-bg-color, #fafafa);\n}\n\n/* ─── Anchored position (top-right of parent) ──────────────────────────────── */\n\n.anchored {\n position: absolute;\n top: -4px;\n right: -4px;\n z-index: 1;\n}\n\n/* ─── Dot variant ──────────────────────────────────────────────────────────── */\n\n.dot {\n min-width: 10px;\n width: 10px;\n height: 10px;\n padding: 0;\n border-radius: var(--gnome-radius-pill);\n}\n\n.dot.anchored {\n top: -2px;\n right: -2px;\n}\n\n/* ─── Color variants ───────────────────────────────────────────────────────── */\n\n.accent {\n background-color: var(--gnome-accent-bg-color, #3584e4);\n color: var(--gnome-accent-fg-color, #ffffff);\n}\n\n.success {\n background-color: var(--gnome-success-bg-color, #2ec27e);\n color: var(--gnome-success-fg-color, #ffffff);\n}\n\n.warning {\n background-color: var(--gnome-warning-bg-color, #f6d32d);\n color: var(--gnome-warning-fg-color, rgba(0, 0, 0, 0.8));\n}\n\n.error {\n background-color: var(--gnome-error-bg-color, #e01b24);\n color: var(--gnome-error-fg-color, #ffffff);\n}\n\n.neutral {\n background-color: var(--gnome-light-4, #c0bfbc);\n color: rgba(0, 0, 0, 0.8);\n}\n\n/* ─── Dark mode ────────────────────────────────────────────────────────────── */\n\n@media (prefers-color-scheme: dark) {\n .badge {\n box-shadow: 0 0 0 2px var(--gnome-window-bg-color, #242424);\n }\n\n .neutral {\n background-color: var(--gnome-dark-2, #5e5c64);\n color: rgba(255, 255, 255, 0.87);\n }\n}\n","import type { HTMLAttributes, ReactNode } from \"react\";\nimport styles from \"./Badge.module.css\";\n\nexport type BadgeVariant = \"accent\" | \"success\" | \"warning\" | \"error\" | \"neutral\";\n\nexport interface BadgeProps extends HTMLAttributes<HTMLSpanElement> {\n /**\n * Visual style. Defaults to `\"accent\"`.\n * - `accent` — blue, for counts and highlights.\n * - `success` — green, for positive status.\n * - `warning` — yellow, for cautionary status.\n * - `error` — red, for failures or urgent counts.\n * - `neutral` — gray, for inactive or secondary counts.\n */\n variant?: BadgeVariant;\n /**\n * When true, renders a small dot with no label — used for unread/online indicators.\n * The `children` are ignored in dot mode.\n */\n dot?: boolean;\n /** Number or short text to display. Keep to 1–3 characters. */\n children?: ReactNode;\n /**\n * When provided, the badge is absolutely positioned over this child element\n * at the top-right corner. The wrapper becomes `position: relative`.\n */\n anchor?: ReactNode;\n}\n\n/**\n * Counter or status indicator, optionally overlaid on another element.\n *\n * @see https://developer.gnome.org/hig/patterns/feedback/badges.html\n */\nexport function Badge({\n variant = \"accent\",\n dot = false,\n children,\n anchor,\n className,\n ...props\n}: BadgeProps) {\n const badgeEl = (\n <span\n className={[\n styles.badge,\n styles[variant],\n dot ? styles.dot : null,\n anchor ? styles.anchored : null,\n className,\n ]\n .filter(Boolean)\n .join(\" \")}\n {...props}\n >\n {!dot && children}\n </span>\n );\n\n if (anchor) {\n return (\n <span className={styles.wrapper}>\n {anchor}\n {badgeEl}\n </span>\n );\n }\n\n return badgeEl;\n}\n","import type { SVGAttributes } from \"react\";\nimport type { IconDefinition } from \"@gnome-ui/icons\";\n\nexport type IconSize = \"sm\" | \"md\" | \"lg\";\n\nexport interface IconProps extends SVGAttributes<SVGSVGElement> {\n /** Icon definition imported from `@gnome-ui/icons`. */\n icon: IconDefinition;\n /**\n * Rendered size.\n * - `sm` — 12 px\n * - `md` — 16 px (default)\n * - `lg` — 20 px\n *\n * Override with `width`/`height` for non-standard sizes.\n */\n size?: IconSize;\n /** Accessible label. Omit for decorative icons — they are hidden from screen readers. */\n label?: string;\n}\n\nconst SIZE_MAP: Record<IconSize, number> = { sm: 12, md: 16, lg: 20 };\n\n/**\n * Renders an `@gnome-ui/icons` definition as an inline SVG.\n *\n * Uses `currentColor` so the icon inherits the text color of its parent.\n * Pass `label` only when the icon conveys meaning on its own;\n * omit it when a sibling text label already describes the action.\n *\n * @example\n * import { Search } from \"@gnome-ui/icons\";\n * <Icon icon={Search} label=\"Search\" />\n */\nexport function Icon({\n icon,\n size = \"md\",\n label,\n width,\n height,\n ...props\n}: IconProps) {\n const px = SIZE_MAP[size];\n\n return (\n <svg\n xmlns=\"http://www.w3.org/2000/svg\"\n viewBox={icon.viewBox}\n width={width ?? px}\n height={height ?? px}\n fill=\"currentColor\"\n aria-label={label}\n aria-hidden={label ? undefined : true}\n role={label ? \"img\" : undefined}\n focusable=\"false\"\n {...props}\n >\n {icon.paths.map((p, i) => (\n <path\n key={i}\n d={p.d}\n fillRule={p.fillRule}\n clipRule={p.clipRule}\n />\n ))}\n </svg>\n );\n}\n","//#region src/icons/GoPrevious.ts\nvar e = {\n\tviewBox: \"0 0 16 16\",\n\tpaths: [{\n\t\td: \"M10.5 2.5 5 8l5.5 5.5 1.5-1.5L8 8l4-4z\",\n\t\tfillRule: \"evenodd\"\n\t}]\n}, t = {\n\tviewBox: \"0 0 16 16\",\n\tpaths: [{\n\t\td: \"M5.5 2.5 11 8l-5.5 5.5L4 12l4-4-4-4z\",\n\t\tfillRule: \"evenodd\"\n\t}]\n}, n = {\n\tviewBox: \"0 0 16 16\",\n\tpaths: [{\n\t\td: \"M8 1 1 7h2v7h4v-4h2v4h4V7h2L8 1zm0 2.2L13 8v5h-2.5v-4H5.5v4H3V8z\",\n\t\tfillRule: \"evenodd\"\n\t}]\n}, r = {\n\tviewBox: \"0 0 16 16\",\n\tpaths: [{\n\t\td: \"M8 2 2.5 8H6v6h4V8h3.5z\",\n\t\tfillRule: \"evenodd\"\n\t}]\n}, i = {\n\tviewBox: \"0 0 16 16\",\n\tpaths: [{\n\t\td: \"m2.5 5 5.5 6 5.5-6z\",\n\t\tfillRule: \"evenodd\"\n\t}]\n}, a = {\n\tviewBox: \"0 0 16 16\",\n\tpaths: [{\n\t\td: \"M2.5 11 8 5l5.5 6z\",\n\t\tfillRule: \"evenodd\"\n\t}]\n}, o = {\n\tviewBox: \"0 0 16 16\",\n\tpaths: [{\n\t\td: \"M11 2.5 5 8l6 5.5z\",\n\t\tfillRule: \"evenodd\"\n\t}]\n}, s = {\n\tviewBox: \"0 0 16 16\",\n\tpaths: [{\n\t\td: \"M5 2.5 11 8 5 13.5z\",\n\t\tfillRule: \"evenodd\"\n\t}]\n}, c = {\n\tviewBox: \"0 0 16 16\",\n\tpaths: [{ d: \"M7 2v5H2v2h5v5h2V9h5V7H9V2z\" }]\n}, l = {\n\tviewBox: \"0 0 16 16\",\n\tpaths: [{ d: \"M2 7h12v2H2z\" }]\n}, u = {\n\tviewBox: \"0 0 16 16\",\n\tpaths: [{\n\t\td: \"M6 2v1H2v2h1v8a1 1 0 0 0 1 1h8a1 1 0 0 0 1-1V5h1V3h-4V2H6zm0 4h1v5H6V6zm3 0h1v5H9V6z\",\n\t\tfillRule: \"evenodd\"\n\t}]\n}, d = {\n\tviewBox: \"0 0 16 16\",\n\tpaths: [{\n\t\td: \"M12.5 1a1.5 1.5 0 0 1 1.06 2.56l-8.5 8.5-.06.06-.08.02-3 .75a.5.5 0 0 1-.61-.61l.75-3 .02-.08.06-.06 8.5-8.5A1.49 1.49 0 0 1 12.5 1zm0 1a.5.5 0 0 0-.35.15L4 10.29l-.5 2 2-.5 8.15-8.15A.5.5 0 0 0 12.5 2z\",\n\t\tfillRule: \"evenodd\"\n\t}]\n}, f = {\n\tviewBox: \"0 0 16 16\",\n\tpaths: [{\n\t\td: \"M4 2a1 1 0 0 0-1 1v9a1 1 0 0 0 1 1h7a1 1 0 0 0 1-1V3a1 1 0 0 0-1-1H4zm0 1h7v9H4V3zM2 4v9a1 1 0 0 0 1 1h7v-1H3V4H2z\",\n\t\tfillRule: \"evenodd\"\n\t}]\n}, p = {\n\tviewBox: \"0 0 16 16\",\n\tpaths: [{\n\t\td: \"M6 1a1 1 0 0 0-1 1H3a1 1 0 0 0-1 1v10a1 1 0 0 0 1 1h10a1 1 0 0 0 1-1V3a1 1 0 0 0-1-1h-2a1 1 0 0 0-1-1H6zm0 1h4v1H6V2zm-3 1h2v1a1 1 0 0 0 1 1h4a1 1 0 0 0 1-1V3h2v10H3V3z\",\n\t\tfillRule: \"evenodd\"\n\t}]\n}, m = {\n\tviewBox: \"0 0 16 16\",\n\tpaths: [{\n\t\td: \"M5.5 1 3 5.5A2.5 2.5 0 1 0 6 8l2-3.5 2 3.5a2.5 2.5 0 1 0 2.94-2.46L10 2H8L6.5 4.5zm-1 8.5a1.5 1.5 0 1 1 0 3 1.5 1.5 0 0 1 0-3zm7 0a1.5 1.5 0 1 1 0 3 1.5 1.5 0 0 1 0-3z\",\n\t\tfillRule: \"evenodd\"\n\t}]\n}, h = {\n\tviewBox: \"0 0 16 16\",\n\tpaths: [{\n\t\td: \"M6.5 2 2 6l4.5 4V7.5C9.5 7.5 12 8.5 13 11c0-4-2.5-6-6.5-6V2z\",\n\t\tfillRule: \"evenodd\"\n\t}]\n}, g = {\n\tviewBox: \"0 0 16 16\",\n\tpaths: [{\n\t\td: \"M9.5 2v3C5.5 5 3 7 3 11c1-2.5 3.5-3.5 6.5-3.5V10l4.5-4z\",\n\t\tfillRule: \"evenodd\"\n\t}]\n}, _ = {\n\tviewBox: \"0 0 16 16\",\n\tpaths: [{\n\t\td: \"M2 2v12h12V5l-3-3H2zm5 1h2v3H7V3zm-3 0h2v4a1 1 0 0 0 1 1h4a1 1 0 0 0 1-1V3.41L13 4.83V13H3V3h1zm3 7a2 2 0 1 1 0 4 2 2 0 0 1 0-4z\",\n\t\tfillRule: \"evenodd\"\n\t}]\n}, v = {\n\tviewBox: \"0 0 16 16\",\n\tpaths: [{\n\t\td: \"M2 3a1 1 0 0 0-1 1v8a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1V5a1 1 0 0 0-1-1H7.41L6 3H2zm0 1h4l1.41 1.41.59.59H14v6H2V4z\",\n\t\tfillRule: \"evenodd\"\n\t}]\n}, y = {\n\tviewBox: \"0 0 16 16\",\n\tpaths: [{\n\t\td: \"M3.293 3.293a1 1 0 0 1 1.414 0L8 6.586l3.293-3.293a1 1 0 1 1 1.414 1.414L9.414 8l3.293 3.293a1 1 0 0 1-1.414 1.414L8 9.414l-3.293 3.293a1 1 0 0 1-1.414-1.414L6.586 8 3.293 4.707a1 1 0 0 1 0-1.414z\",\n\t\tfillRule: \"evenodd\"\n\t}]\n}, b = {\n\tviewBox: \"0 0 16 16\",\n\tpaths: [{\n\t\td: \"M6.5 1a5.5 5.5 0 0 1 4.33 8.92l3.13 3.13-1.41 1.41-3.13-3.13A5.5 5.5 0 1 1 6.5 1zm0 2a3.5 3.5 0 1 0 0 7 3.5 3.5 0 0 0 0-7z\",\n\t\tfillRule: \"evenodd\"\n\t}]\n}, x = {\n\tviewBox: \"0 0 16 16\",\n\tpaths: [{\n\t\td: \"M8 2a6 6 0 0 0-6 6h2a4 4 0 0 1 4-4 4 4 0 0 1 4 4 4 4 0 0 1-4 4v-2L5 13l3 3v-2a6 6 0 0 0 6-6 6 6 0 0 0-6-6z\",\n\t\tfillRule: \"evenodd\"\n\t}]\n}, S = {\n\tviewBox: \"0 0 16 16\",\n\tpaths: [{\n\t\td: \"M11 1a3 3 0 1 1-1.18 5.76L6.6 8.9A3.02 3.02 0 0 1 7 10a3 3 0 1 1-1.18-2.38l3.36-2.24A3 3 0 0 1 9 5a3 3 0 0 1 .18-1.02L5.82 1.76A3 3 0 1 1 7 0a2.99 2.99 0 0 1-.18 1.02l3.36 2.22A3 3 0 0 1 11 1z\",\n\t\tfillRule: \"evenodd\"\n\t}]\n}, C = {\n\tviewBox: \"0 0 16 16\",\n\tpaths: [{\n\t\td: \"M11.5 1A3.5 3.5 0 0 0 8 4.5V12a2 2 0 1 1-4 0V5H2v7a4 4 0 0 0 8 0V4.5a2 2 0 1 1 4 0V12a6 6 0 0 1-12 0V5h2V3H2a2 2 0 0 0 0 4h2v5a2 2 0 1 0 4 0V4.5A3.5 3.5 0 0 0 11.5 1z\",\n\t\tfillRule: \"evenodd\"\n\t}]\n}, w = {\n\tviewBox: \"0 0 16 16\",\n\tpaths: [{ d: \"M2 4h12v2H2zm0 3h12v2H2zm0 3h12v2H2z\" }]\n}, T = {\n\tviewBox: \"0 0 16 16\",\n\tpaths: [{\n\t\td: \"M8 3a1.5 1.5 0 1 1 0-3 1.5 1.5 0 0 1 0 3zm0 5a1.5 1.5 0 1 1 0-3 1.5 1.5 0 0 1 0 3zm0 5a1.5 1.5 0 1 1 0-3 1.5 1.5 0 0 1 0 3z\",\n\t\tfillRule: \"evenodd\"\n\t}]\n}, E = {\n\tviewBox: \"0 0 16 16\",\n\tpaths: [{\n\t\td: \"M1 2a1 1 0 0 0-1 1v10a1 1 0 0 0 1 1h14a1 1 0 0 0 1-1V3a1 1 0 0 0-1-1H1zm0 1h5v10H1V3zm6 0h8v10H7V3z\",\n\t\tfillRule: \"evenodd\"\n\t}]\n}, D = {\n\tviewBox: \"0 0 16 16\",\n\tpaths: [{\n\t\td: \"M8 3C4.5 3 1.5 5.667 0 8c1.5 2.333 4.5 5 8 5s6.5-2.667 8-5c-1.5-2.333-4.5-5-8-5zm0 8a3 3 0 1 1 0-6 3 3 0 0 1 0 6zm0-4.5a1.5 1.5 0 1 0 0 3 1.5 1.5 0 0 0 0-3z\",\n\t\tfillRule: \"evenodd\"\n\t}]\n}, O = {\n\tviewBox: \"0 0 16 16\",\n\tpaths: [{\n\t\td: \"M1.293 1.293a1 1 0 0 1 1.414 0l12 12a1 1 0 0 1-1.414 1.414l-1.88-1.88A8.8 8.8 0 0 1 8 13C4.5 13 1.5 10.333 0 8c.637-1.023 1.52-2.049 2.6-2.893L1.293 2.707a1 1 0 0 1 0-1.414zM5.03 6.444 7.09 8.505A1.5 1.5 0 0 0 9.494 6.91L7.432 4.848A3 3 0 0 0 5.03 6.444zM8 3c.645 0 1.27.083 1.867.237l-1.59 1.59A3 3 0 0 0 5.173 7.92L3.584 6.333C4.51 4.95 6.152 3 8 3zm5.4 2.107C14.48 5.951 15.363 6.977 16 8c-1.5 2.333-4.5 5-8 5a8.8 8.8 0 0 1-2.072-.249l1.538-1.538a3 3 0 0 0 3.703-3.703L12.81 5.87c.202.076.4.156.59.237z\",\n\t\tfillRule: \"evenodd\"\n\t}]\n}, k = {\n\tviewBox: \"0 0 16 16\",\n\tpaths: [{\n\t\td: \"M7 1v2.06a5 5 0 0 0-1.73.72L3.64 2.22 2.22 3.64l1.56 1.56A5.02 5.02 0 0 0 3.06 7H1v2h2.06c.16.63.42 1.22.78 1.73L2.22 12.36l1.42 1.42 1.63-1.63A5 5 0 0 0 7 12.94V15h2v-2.06a5 5 0 0 0 1.73-.72l1.63 1.63 1.42-1.42-1.63-1.63c.36-.51.62-1.1.78-1.73H15V7h-2.06a5.02 5.02 0 0 0-.72-1.8l1.56-1.56-1.42-1.42-1.63 1.56A5 5 0 0 0 9 3.06V1H7zm1 4a3 3 0 1 1 0 6 3 3 0 0 1 0-6z\",\n\t\tfillRule: \"evenodd\"\n\t}]\n}, A = {\n\tviewBox: \"0 0 16 16\",\n\tpaths: [{\n\t\td: \"M8 1a7 7 0 1 0 0 14A7 7 0 0 0 8 1zm0 2a5 5 0 1 1 0 10A5 5 0 0 1 8 3zM7 7v5h2V7H7zm0-3v2h2V4H7z\",\n\t\tfillRule: \"evenodd\"\n\t}]\n}, j = {\n\tviewBox: \"0 0 16 16\",\n\tpaths: [{\n\t\td: \"M8 1 0 15h16L8 1zm0 3 6 10H2L8 4zM7 8v3h2V8H7zm0 4v2h2v-2H7z\",\n\t\tfillRule: \"evenodd\"\n\t}]\n}, M = {\n\tviewBox: \"0 0 16 16\",\n\tpaths: [{\n\t\td: \"M8 1a7 7 0 1 0 0 14A7 7 0 0 0 8 1zm0 2a5 5 0 1 1 0 10A5 5 0 0 1 8 3zM7 5v5h2V5H7zm0 6v2h2v-2H7z\",\n\t\tfillRule: \"evenodd\"\n\t}]\n}, N = {\n\tviewBox: \"0 0 16 16\",\n\tpaths: [{\n\t\td: \"M13.5 3 6 11.5 2.5 8 1 9.5l5 5 9-10z\",\n\t\tfillRule: \"evenodd\"\n\t}]\n}, P = {\n\tviewBox: \"0 0 16 16\",\n\tpaths: [{\n\t\td: \"M8 1.5 9.8 6H15l-4.2 3 1.6 5L8 11l-4.4 3 1.6-5L1 6h5.2z\",\n\t\tfillRule: \"evenodd\"\n\t}]\n}, F = {\n\tviewBox: \"0 0 16 16\",\n\tpaths: [{\n\t\td: \"M8 1.5 9.8 6H15l-4.2 3 1.6 5L8 11l-4.4 3 1.6-5L1 6h5.2L8 1.5zm0 2.55L6.7 7H3.5l2.7 1.95-1 3.15L8 10.2l2.8 1.9-1-3.15L12.5 7H9.3z\",\n\t\tfillRule: \"evenodd\"\n\t}]\n}, I = {\n\tviewBox: \"0 0 16 16\",\n\tpaths: [{\n\t\td: \"M4 2v12l10-6z\",\n\t\tfillRule: \"evenodd\"\n\t}]\n}, L = {\n\tviewBox: \"0 0 16 16\",\n\tpaths: [{ d: \"M3 2h3v12H3zm7 0h3v12h-3z\" }]\n}, R = {\n\tviewBox: \"0 0 16 16\",\n\tpaths: [{ d: \"M3 2v12l8-6-8-6zm8 0h2v12h-2z\" }]\n}, z = {\n\tviewBox: \"0 0 16 16\",\n\tpaths: [{ d: \"M13 2v12l-8-6 8-6zm-8 0H3v12h2z\" }]\n};\n//#endregion\nexport { c as Add, C as Attachment, N as Check, y as Close, f as Copy, m as Cut, u as Delete, v as DocumentOpen, d as Edit, M as Error, n as GoHome, t as GoNext, e as GoPrevious, r as GoUp, A as Information, L as MediaPause, I as MediaPlay, z as MediaSkipBackward, R as MediaSkipForward, w as OpenMenu, i as PanDown, s as PanEnd, o as PanStart, a as PanUp, p as Paste, g as Redo, x as Refresh, l as Remove, _ as Save, b as Search, k as Settings, S as Share, P as Star, F as StarOutline, h as Undo, O as ViewConceal, T as ViewMore, D as ViewReveal, E as ViewSidebar, j as Warning };\n\n//# sourceMappingURL=index.js.map","/* ─── Collapsible wrapper ───────────────────────────────────────────────────── */\n\n.wrapper {\n overflow: hidden;\n max-height: 0;\n opacity: 0;\n\n transition:\n max-height var(--gnome-duration-normal, 200ms) var(--gnome-easing-default, ease),\n opacity var(--gnome-duration-fast, 100ms) var(--gnome-easing-default, ease);\n}\n\n.open {\n max-height: 120px; /* enough for bar + filter row */\n opacity: 1;\n}\n\n/* ─── Inner bar ─────────────────────────────────────────────────────────────── */\n\n.bar {\n display: flex;\n align-items: center;\n gap: var(--gnome-space-1, 6px);\n\n padding: var(--gnome-space-1, 6px) var(--gnome-space-2, 12px);\n\n background-color: var(--gnome-headerbar-bg-color, #ebebeb);\n border-bottom: 1px solid var(--gnome-headerbar-shade-color, rgba(0, 0, 0, 0.12));\n}\n\n.disabled {\n opacity: var(--gnome-opacity-disabled, 0.5);\n}\n\n/* ─── Search icon ───────────────────────────────────────────────────────────── */\n\n.searchIcon {\n flex-shrink: 0;\n display: flex;\n align-items: center;\n color: var(--gnome-window-fg-color, rgba(0, 0, 0, 0.8));\n opacity: var(--gnome-opacity-dim, 0.55);\n}\n\n/* ─── Input ─────────────────────────────────────────────────────────────────── */\n\n.input {\n flex: 1;\n min-width: 0;\n\n /* Reset browser search input */\n appearance: none;\n -webkit-appearance: none;\n\n border: none;\n background: transparent;\n outline: none;\n\n font-family: var(--gnome-font-family);\n font-size: var(--gnome-font-size-body, 1rem);\n font-weight: var(--gnome-font-weight-normal, 400);\n line-height: var(--gnome-line-height-body, 1.5);\n color: var(--gnome-window-fg-color, rgba(0, 0, 0, 0.8));\n}\n\n.input::placeholder {\n color: var(--gnome-window-fg-color, rgba(0, 0, 0, 0.8));\n opacity: var(--gnome-opacity-dim, 0.55);\n}\n\n/* Hide webkit search cancel button — we render our own */\n.input::-webkit-search-cancel-button {\n -webkit-appearance: none;\n}\n\n.input:focus {\n outline: none;\n}\n\n/* ─── Clear button ──────────────────────────────────────────────────────────── */\n\n.clearButton {\n flex-shrink: 0;\n display: flex;\n align-items: center;\n justify-content: center;\n\n width: 24px;\n height: 24px;\n\n appearance: none;\n border: none;\n background: transparent;\n border-radius: var(--gnome-radius-sm, 6px);\n padding: 0;\n cursor: pointer;\n color: var(--gnome-window-fg-color, rgba(0, 0, 0, 0.8));\n opacity: var(--gnome-opacity-dim, 0.55);\n\n transition: opacity var(--gnome-duration-fast, 100ms) var(--gnome-easing-default, ease);\n}\n\n.clearButton:hover:not(:disabled) {\n opacity: 1;\n background-color: color-mix(in srgb, currentColor 10%, transparent);\n}\n\n.clearButton:active:not(:disabled) {\n background-color: color-mix(in srgb, currentColor 18%, transparent);\n}\n\n.clearButton:focus {\n outline: none;\n}\n\n.clearButton:focus-visible {\n opacity: 1;\n outline: var(--gnome-focus-ring-width, 3px) solid var(--gnome-focus-ring-color, #3584e4);\n outline-offset: var(--gnome-focus-ring-offset, 2px);\n}\n\n/* ─── Filter row (optional children) ───────────────────────────────────────── */\n\n.filterRow {\n display: flex;\n flex-wrap: wrap;\n gap: var(--gnome-space-1, 6px);\n padding: var(--gnome-space-1, 6px) var(--gnome-space-2, 12px);\n\n background-color: var(--gnome-headerbar-bg-color, #ebebeb);\n border-bottom: 1px solid var(--gnome-headerbar-shade-color, rgba(0, 0, 0, 0.12));\n}\n\n/* ─── Inline variant ────────────────────────────────────────────────────────── */\n\n.inline {\n background-color: transparent;\n border-bottom-color: transparent;\n}\n\n/* ─── Dark mode ─────────────────────────────────────────────────────────────── */\n\n@media (prefers-color-scheme: dark) {\n .bar,\n .filterRow {\n background-color: var(--gnome-headerbar-bg-color, #303030);\n border-bottom-color: rgba(0, 0, 0, 0.35);\n }\n}\n\n/* ─── Reduced motion ────────────────────────────────────────────────────────── */\n\n@media (prefers-reduced-motion: reduce) {\n .wrapper {\n transition: none;\n }\n\n .clearButton {\n transition: none;\n }\n}\n","import {\n useRef,\n useEffect,\n useCallback,\n type KeyboardEvent,\n type InputHTMLAttributes,\n type ReactNode,\n} from \"react\";\nimport { Search, Close } from \"@gnome-ui/icons\";\nimport { Icon } from \"../Icon\";\nimport styles from \"./SearchBar.module.css\";\n\nexport interface SearchBarProps\n extends Omit<InputHTMLAttributes<HTMLInputElement>, \"type\"> {\n /**\n * Whether the search bar is visible/expanded.\n * When `false` the bar collapses to zero height and is hidden from AT.\n */\n open: boolean;\n /** Called when the user presses Escape or clicks the clear button with empty value. */\n onClose?: () => void;\n /** Called when the clear (×) button is clicked. Also clears the input value. */\n onClear?: () => void;\n /**\n * Content to render below the search bar (e.g. filter chips).\n * Only visible while `open` is true.\n */\n children?: ReactNode;\n /**\n * Removes the header-bar background so the bar blends into any surface.\n * Use inside cards, content areas, or custom containers.\n * Mirrors the `.inline` style class.\n */\n inline?: boolean;\n}\n\n/**\n * Collapsible search bar following the Adwaita `AdwSearchBar` pattern.\n *\n * - Slides in/out with a CSS height transition.\n * - Auto-focuses the input when `open` becomes `true`.\n * - Escape key calls `onClose`.\n * - Shows a clear (×) button whenever the input has a value.\n *\n * @see https://gnome.pages.gitlab.gnome.org/libadwaita/doc/main/class.SearchBar.html\n */\nexport function SearchBar({\n open,\n onClose,\n onClear,\n children,\n value,\n onChange,\n placeholder = \"Search…\",\n disabled,\n inline = false,\n className,\n ...props\n}: SearchBarProps) {\n const inputRef = useRef<HTMLInputElement>(null);\n\n // Auto-focus when opening\n useEffect(() => {\n if (open) {\n // Defer so the CSS transition has begun and the element is interactable\n const id = requestAnimationFrame(() => inputRef.current?.focus());\n return () => cancelAnimationFrame(id);\n }\n }, [open]);\n\n const handleKeyDown = useCallback(\n (e: KeyboardEvent<HTMLInputElement>) => {\n if (e.key === \"Escape\") {\n e.preventDefault();\n onClose?.();\n }\n props.onKeyDown?.(e);\n },\n [onClose, props]\n );\n\n const hasValue = value !== undefined && value !== \"\";\n\n return (\n <div\n className={[styles.wrapper, open ? styles.open : null, className]\n .filter(Boolean)\n .join(\" \")}\n aria-hidden={!open}\n >\n <div className={[styles.bar, inline ? styles.inline : null, disabled ? styles.disabled : null].filter(Boolean).join(\" \")}>\n {/* Search icon */}\n <span className={styles.searchIcon} aria-hidden>\n <Icon icon={Search} size=\"md\" />\n </span>\n\n <input\n ref={inputRef}\n type=\"search\"\n role=\"searchbox\"\n value={value}\n onChange={onChange}\n placeholder={placeholder}\n disabled={disabled}\n tabIndex={open ? undefined : -1}\n className={styles.input}\n onKeyDown={handleKeyDown}\n {...props}\n />\n\n {/* Clear button — shown only when there is a value */}\n {hasValue && (\n <button\n type=\"button\"\n className={styles.clearButton}\n aria-label=\"Clear search\"\n tabIndex={open ? undefined : -1}\n disabled={disabled}\n onClick={() => {\n onClear?.();\n inputRef.current?.focus();\n }}\n >\n <Icon icon={Close} size=\"md\" />\n </button>\n )}\n </div>\n\n {children && (\n <div className={styles.filterRow}>{children}</div>\n )}\n </div>\n );\n}\n","/* ─── Page container ─────────────────────────────────────────────────────────── */\n\n.page {\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n text-align: center;\n\n width: 100%;\n padding: var(--gnome-space-6, 48px) var(--gnome-space-4, 24px);\n gap: 0;\n\n font-family: var(--gnome-font-family);\n color: var(--gnome-window-fg-color, rgba(0, 0, 0, 0.8));\n}\n\n/* ─── Icon ───────────────────────────────────────────────────────────────────── */\n\n.iconWrap {\n display: flex;\n align-items: center;\n justify-content: center;\n margin-bottom: var(--gnome-space-4, 24px);\n\n /* Adwaita dims the icon slightly to keep the title as the focal point */\n opacity: 0.55;\n color: var(--gnome-window-fg-color, rgba(0, 0, 0, 0.8));\n}\n\n/* ─── Title ──────────────────────────────────────────────────────────────────── */\n\n.title {\n margin: 0;\n\n font-size: var(--gnome-font-size-title-1, 2rem);\n font-weight: var(--gnome-font-weight-bold, 700);\n line-height: var(--gnome-line-height-title, 1.2);\n color: var(--gnome-window-fg-color, rgba(0, 0, 0, 0.8));\n}\n\n/* ─── Description ────────────────────────────────────────────────────────────── */\n\n.description {\n margin: var(--gnome-space-2, 12px) 0 0;\n\n max-width: 36ch;\n\n font-size: var(--gnome-font-size-body, 1rem);\n font-weight: var(--gnome-font-weight-normal, 400);\n line-height: var(--gnome-line-height-body, 1.5);\n color: var(--gnome-window-fg-color, rgba(0, 0, 0, 0.8));\n opacity: var(--gnome-opacity-dim, 0.55);\n}\n\n/* ─── Actions ────────────────────────────────────────────────────────────────── */\n\n.actions {\n display: flex;\n flex-wrap: wrap;\n gap: var(--gnome-space-2, 12px);\n justify-content: center;\n margin-top: var(--gnome-space-4, 24px);\n}\n\n/* ─── Compact mode ───────────────────────────────────────────────────────────── */\n\n.compact {\n padding: var(--gnome-space-3, 18px) var(--gnome-space-3, 18px);\n}\n\n.compact .iconWrap {\n margin-bottom: var(--gnome-space-2, 12px);\n}\n\n.compact .title {\n font-size: var(--gnome-font-size-title-4, 1.125rem);\n font-weight: var(--gnome-font-weight-semibold, 600);\n}\n\n.compact .description {\n font-size: var(--gnome-font-size-caption, 0.75rem);\n margin-top: var(--gnome-space-1, 6px);\n}\n\n.compact .actions {\n margin-top: var(--gnome-space-2, 12px);\n gap: var(--gnome-space-1, 6px);\n}\n\n/* ─── Dark mode ──────────────────────────────────────────────────────────────── */\n\n@media (prefers-color-scheme: dark) {\n .iconWrap {\n opacity: 0.45;\n }\n}\n","import type { HTMLAttributes, ReactNode } from \"react\";\nimport type { IconDefinition } from \"@gnome-ui/icons\";\nimport { Icon } from \"../Icon\";\nimport styles from \"./StatusPage.module.css\";\n\nexport interface StatusPageProps extends HTMLAttributes<HTMLDivElement> {\n /**\n * Large icon displayed above the title.\n * Use an icon from `@gnome-ui/icons` or omit for a text-only page.\n */\n icon?: IconDefinition;\n /**\n * Custom icon node. Use when you need an image, emoji, or\n * a rendered SVG that is not part of `@gnome-ui/icons`.\n * Ignored when `icon` is also provided.\n */\n iconNode?: ReactNode;\n /** Main heading. Keep it short — one noun phrase. */\n title: string;\n /** Supporting description rendered below the title. */\n description?: string;\n /**\n * Optional action area — typically one or two `<Button>` elements.\n * Rendered below the description.\n */\n children?: ReactNode;\n /**\n * Reduces padding, icon size, and title scale for use in compact\n * contexts such as sidebars, popovers, and small panels.\n * Mirrors the `.compact` style class.\n */\n compact?: boolean;\n}\n\n/**\n * Empty-state / status page following the Adwaita `AdwStatusPage` pattern.\n *\n * Use to fill a view with no content yet, an error state, or a completion\n * confirmation. Centred both horizontally and vertically.\n *\n * @see https://gnome.pages.gitlab.gnome.org/libadwaita/doc/main/class.StatusPage.html\n * @see https://developer.gnome.org/hig/patterns/feedback/empty-states.html\n */\nexport function StatusPage({\n icon,\n iconNode,\n title,\n description,\n children,\n compact = false,\n className,\n ...props\n}: StatusPageProps) {\n const iconSize = compact ? 64 : 128;\n const iconContent = icon\n ? <Icon icon={icon} width={iconSize} height={iconSize} aria-hidden />\n : iconNode ?? null;\n\n return (\n <div\n className={[styles.page, compact ? styles.compact : null, className]\n .filter(Boolean)\n .join(\" \")}\n {...props}\n >\n {iconContent && (\n <div className={styles.iconWrap} aria-hidden=\"true\">\n {iconContent}\n </div>\n )}\n\n <p className={styles.title}>{title}</p>\n\n {description && (\n <p className={styles.description}>{description}</p>\n )}\n\n {children && (\n <div className={styles.actions}>{children}</div>\n )}\n </div>\n );\n}\n","import { useState, useEffect } from \"react\";\n\n/**\n * GNOME / libadwaita canonical breakpoints (in CSS px, assuming 1 sp = 1 px at 1× density).\n *\n * | Name | Max width | Pattern triggered |\n * |------------|-----------|-------------------|\n * | `narrow` | ≤ 400 px | Collapse split views; sidebar becomes overlay |\n * | `medium` | ≤ 550 px | Move ViewSwitcher to a bottom bar |\n * | `wide` | ≤ 860 px | Collapse outer pane in nested split views |\n *\n * @see https://gnome.pages.gitlab.gnome.org/libadwaita/doc/main/class.Breakpoint.html\n */\nexport const GNOME_BREAKPOINTS = {\n /** ≤ 400 px — split views collapse to single pane */\n narrow: 400,\n /** ≤ 550 px — ViewSwitcher moves to bottom bar */\n medium: 550,\n /** ≤ 860 px — outer pane of nested split views collapses */\n wide: 860,\n} as const;\n\nexport type GnomeBreakpointName = keyof typeof GNOME_BREAKPOINTS;\n\nexport interface BreakpointState {\n /** Width ≤ 400 px — split views are collapsed. */\n isNarrow: boolean;\n /** Width ≤ 550 px — medium or narrower. */\n isMedium: boolean;\n /** Width ≤ 860 px — wide or narrower. */\n isWide: boolean;\n /** Current viewport width in px. */\n width: number;\n}\n\n/**\n * Tracks the viewport width against GNOME / libadwaita breakpoints.\n *\n * Returns a reactive state object that updates on every window resize.\n * Uses a passive `resize` event listener and cleans up automatically.\n *\n * @example\n * const { isNarrow, isMedium } = useBreakpoint();\n * // isNarrow → true when viewport ≤ 400 px (split views should collapse)\n * // isMedium → true when viewport ≤ 550 px (use ViewSwitcherBar instead)\n */\nexport function useBreakpoint(): BreakpointState {\n const getState = (): BreakpointState => {\n const width = typeof window !== \"undefined\" ? window.innerWidth : 1280;\n return {\n isNarrow: width <= GNOME_BREAKPOINTS.narrow,\n isMedium: width <= GNOME_BREAKPOINTS.medium,\n isWide: width <= GNOME_BREAKPOINTS.wide,\n width,\n };\n };\n\n const [state, setState] = useState<BreakpointState>(getState);\n\n useEffect(() => {\n const handler = () => setState(getState());\n window.addEventListener(\"resize\", handler, { passive: true });\n return () => window.removeEventListener(\"resize\", handler);\n }, []);\n\n return state;\n}\n","/* ─── Container ────────────────────────────────────────────────────────────── */\n\n.sidebar {\n display: flex;\n flex-direction: column;\n width: 240px;\n height: 100%;\n background-color: var(--gnome-sidebar-bg-color, #ebebeb);\n color: var(--gnome-sidebar-fg-color, rgba(0, 0, 0, 0.8));\n border-right: 1px solid var(--gnome-headerbar-border-color, rgba(0, 0, 0, 0.12));\n overflow-y: auto;\n overflow-x: hidden;\n padding: var(--gnome-space-1, 6px);\n gap: 0;\n transition: width 200ms var(--gnome-easing-default, ease);\n flex-shrink: 0;\n}\n\n/* ─── Section ───────────────────────────────────────────────────────────────── */\n\n.section {\n display: flex;\n flex-direction: column;\n}\n\n/* Divider between consecutive sections */\n.section + .section {\n margin-top: var(--gnome-space-1, 6px);\n padding-top: var(--gnome-space-1, 6px);\n border-top: 1px solid var(--gnome-divider-color);\n}\n\n.sectionTitle {\n margin: 4px var(--gnome-space-1, 6px) 2px;\n padding: 0;\n font-family: var(--gnome-font-family);\n font-size: var(--gnome-font-size-caption, 0.875rem);\n font-weight: var(--gnome-font-weight-semibold, 600);\n text-transform: uppercase;\n letter-spacing: 0.06em;\n opacity: 0.5;\n user-select: none;\n}\n\n/* ─── List ─────────────────────────────────────────────────────────────────── */\n\n.list {\n list-style: none;\n margin: 0;\n padding: 0;\n display: flex;\n flex-direction: column;\n gap: 2px;\n}\n\n/* ─── Item wrapper ─────────────────────────────────────────────────────────── */\n\n.item {\n display: block;\n}\n\n/* ─── Item button ──────────────────────────────────────────────────────────── */\n\n.itemBtn {\n appearance: none;\n -webkit-appearance: none;\n border: none;\n background: transparent;\n font: inherit;\n text-align: start;\n cursor: pointer;\n outline: none;\n\n display: flex;\n align-items: center;\n gap: var(--gnome-space-2, 12px);\n width: 100%;\n padding: 8px var(--gnome-space-2, 12px);\n border-radius: var(--gnome-radius-md, 8px);\n\n color: var(--gnome-sidebar-fg-color, rgba(0, 0, 0, 0.8));\n font-family: var(--gnome-font-family);\n font-size: var(--gnome-font-size-body, 1rem);\n font-weight: var(--gnome-font-weight-normal, 400);\n\n transition: background-color var(--gnome-duration-fast) var(--gnome-easing-default);\n}\n\n.itemBtn:hover {\n background-color: var(--gnome-hover-overlay);\n}\n\n.itemBtn:active {\n background-color: var(--gnome-active-overlay);\n}\n\n.itemBtn:focus-visible {\n box-shadow:\n 0 0 0 var(--gnome-focus-ring-offset, 2px) var(--gnome-sidebar-bg-color, #ebebeb),\n 0 0 0 calc(var(--gnome-focus-ring-offset, 2px) + var(--gnome-focus-ring-width, 3px))\n var(--gnome-focus-ring-color, #3584e4);\n}\n\n.itemBtn:disabled {\n opacity: var(--gnome-opacity-disabled, 0.5);\n cursor: not-allowed;\n}\n\n/* ─── Active state ─────────────────────────────────────────────────────────── */\n\n.active {\n background-color: var(--gnome-accent-bg-color, #3584e4);\n color: var(--gnome-accent-fg-color, #ffffff);\n font-weight: var(--gnome-font-weight-semibold, 600);\n}\n\n.active:hover {\n filter: brightness(1.08);\n}\n\n.active:active {\n filter: brightness(0.92);\n}\n\n/* ─── Icon ─────────────────────────────────────────────────────────────────── */\n\n.itemIcon {\n display: flex;\n align-items: center;\n flex-shrink: 0;\n opacity: 0.85;\n}\n\n.active .itemIcon {\n opacity: 1;\n}\n\n/* ─── Label ────────────────────────────────────────────────────────────────── */\n\n.itemLabel {\n flex: 1;\n min-width: 0;\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n}\n\n/* ─── Suffix (trailing widget) ──────────────────────────────────────────────── */\n\n.itemSuffix {\n flex-shrink: 0;\n margin-left: auto;\n display: flex;\n align-items: center;\n}\n\n/* ─── Context menu ──────────────────────────────────────────────────────────── */\n\n.contextMenu {\n position: fixed;\n z-index: 10500;\n min-width: 160px;\n\n background-color: var(--gnome-card-bg-color, #ffffff);\n border: 1px solid var(--gnome-border-subtle);\n border-radius: var(--gnome-radius-md, 8px);\n box-shadow: var(--gnome-shadow-md, 0 4px 16px rgba(0, 0, 0, 0.15));\n padding: 4px;\n overflow: hidden;\n\n display: flex;\n flex-direction: column;\n gap: 1px;\n\n animation: menu-in var(--gnome-duration-fast, 100ms) var(--gnome-easing-default, ease) both;\n}\n\n@keyframes menu-in {\n from { opacity: 0; transform: scale(0.96); }\n to { opacity: 1; transform: scale(1); }\n}\n\n.contextMenuItem {\n appearance: none;\n border: none;\n background: transparent;\n width: 100%;\n text-align: start;\n cursor: pointer;\n outline: none;\n\n padding: 6px var(--gnome-space-2, 12px);\n border-radius: calc(var(--gnome-radius-md, 8px) - 4px);\n\n font-family: var(--gnome-font-family);\n font-size: var(--gnome-font-size-body, 1rem);\n color: var(--gnome-window-fg-color, rgba(0, 0, 0, 0.8));\n\n transition: background-color var(--gnome-duration-fast) var(--gnome-easing-default);\n}\n\n.contextMenuItem:hover:not(:disabled) {\n background-color: var(--gnome-hover-overlay);\n}\n\n.contextMenuItem:active:not(:disabled) {\n background-color: var(--gnome-active-overlay);\n}\n\n.contextMenuItem:focus-visible {\n box-shadow: inset 0 0 0 2px var(--gnome-focus-ring-color, #3584e4);\n}\n\n.contextMenuItem:disabled {\n opacity: var(--gnome-opacity-disabled, 0.5);\n cursor: not-allowed;\n}\n\n.contextMenuDestructive {\n color: var(--gnome-destructive-color, #e01b24);\n}\n\n/* ─── Search wrap ───────────────────────────────────────────────────────────── */\n\n.searchWrap {\n padding-bottom: var(--gnome-space-1, 6px);\n}\n\n/* Override SearchBar min-height to keep it compact inside the sidebar */\n.searchWrap > * {\n min-height: unset;\n}\n\n/* ─── Page mode (full-width boxed-list layout) ──────────────────────────────── */\n\n.pageMode {\n width: 100%;\n border-right: none;\n}\n\n/* ─── Drag-over highlight ───────────────────────────────────────────────────── */\n\n.dragOver {\n background-color: var(--gnome-accent-bg-color, #3584e4);\n color: var(--gnome-accent-fg-color, #ffffff);\n opacity: 0.7;\n}\n\n/* ─── Collapsed (icon-only / rail) mode ────────────────────────────────────── */\n\n.collapsed {\n width: 56px;\n transition: width 200ms var(--gnome-easing-default, ease);\n}\n\n/* Hide section headings when collapsed */\n.collapsed .sectionTitle {\n display: none;\n}\n\n/* Center icon when collapsed */\n.itemCollapsed {\n justify-content: center;\n padding-left: 0;\n padding-right: 0;\n}\n\n/* ─── High contrast ────────────────────────────────────────────────────────── */\n\n@media (prefers-contrast: more) {\n .sidebar {\n border-right-width: 2px;\n }\n\n .section + .section {\n border-top-width: 2px;\n }\n\n .contextMenu {\n border-width: 2px;\n }\n\n .active {\n outline: 2px solid var(--gnome-accent-fg-color, #ffffff);\n outline-offset: -3px;\n }\n}\n\n/* ─── Reduced motion ───────────────────────────────────────────────────────── */\n\n@media (prefers-reduced-motion: reduce) {\n .itemBtn {\n transition: none;\n }\n}\n","import {\n createContext,\n useContext,\n useState,\n isValidElement,\n Children,\n type HTMLAttributes,\n type ReactNode,\n} from \"react\";\nimport { SearchBar } from \"../SearchBar\";\nimport { StatusPage } from \"../StatusPage\";\nimport { useBreakpoint } from \"../../hooks/useBreakpoint\";\nimport styles from \"./Sidebar.module.css\";\n\n// ─── Contexts ─────────────────────────────────────────────────────────────────\n\n/** Provides the collapsed state to all descendant `SidebarItem` components. */\nexport const SidebarCollapsedContext = createContext(false);\n\n/** Returns `true` when the nearest `Sidebar` is in collapsed (icon-only) mode. */\nexport function useSidebarCollapsed() {\n return useContext(SidebarCollapsedContext);\n}\n\n/** Provides the active filter string to all descendant `SidebarItem` components. */\nexport const SidebarFilterContext = createContext(\"\");\n\n// ─── Helpers ──────────────────────────────────────────────────────────────────\n\n/** Recursively count items whose `label` matches `filter` (case-insensitive). */\nfunction countMatchingItems(children: ReactNode, filter: string): number {\n let count = 0;\n Children.forEach(children, (child) => {\n if (!isValidElement(child)) return;\n const props = child.props as Record<string, unknown>;\n if (typeof props.label === \"string\") {\n if (props.label.toLowerCase().includes(filter.toLowerCase())) count++;\n } else if (props.children) {\n count += countMatchingItems(props.children as ReactNode, filter);\n }\n });\n return count;\n}\n\n// ─── Component ────────────────────────────────────────────────────────────────\n\nexport interface SidebarProps extends HTMLAttributes<HTMLElement> {\n children?: ReactNode;\n /**\n * When `true`, collapses the sidebar to icon-only mode (rail/mini).\n * Labels, suffixes, and section titles are hidden; tooltips appear on hover.\n */\n collapsed?: boolean;\n /**\n * Renders a built-in `SearchBar` inside the sidebar and manages the filter\n * state internally (uncontrolled). Combine with `filter` + `onFilterChange`\n * for controlled mode.\n */\n searchable?: boolean;\n /**\n * Controlled filter string. Items whose `label` does not match\n * (case-insensitive substring) are hidden. Does not render a `SearchBar`\n * on its own — use `searchable` for the built-in search input.\n */\n filter?: string;\n /** Called when the built-in search value changes. */\n onFilterChange?: (value: string) => void;\n /**\n * Layout mode:\n * - `\"sidebar\"` — fixed-width panel with a border (default).\n * - `\"page\"` — full-width boxed-list layout for narrow viewports.\n *\n * When omitted the sidebar auto-switches to `\"page\"` at ≤ 400 sp,\n * mirroring `AdwSidebar` mobile behaviour.\n */\n mode?: \"sidebar\" | \"page\";\n}\n\n/**\n * Lateral navigation panel following the Adwaita `.navigation-sidebar` style.\n *\n * **Tier 11 — libadwaita 1.9 completeness:**\n * - `searchable` — built-in `SearchBar` with automatic item filtering.\n * - `filter` / `onFilterChange` — controlled external filtering.\n * - `mode` — `\"sidebar\"` (default) or `\"page\"` (boxed-list layout).\n * Auto-switches to `\"page\"` at ≤ 400 sp when `mode` is unset.\n *\n * @see https://gnome.pages.gitlab.gnome.org/libadwaita/doc/main/class.Sidebar.html\n */\nexport function Sidebar({\n children,\n collapsed = false,\n searchable,\n filter: controlledFilter,\n onFilterChange,\n mode,\n className,\n ...props\n}: SidebarProps) {\n const [internalFilter, setInternalFilter] = useState(\"\");\n const { isNarrow } = useBreakpoint();\n\n const isControlled = controlledFilter !== undefined;\n const filterValue = isControlled ? controlledFilter : internalFilter;\n\n const handleFilterChange = (v: string) => {\n if (!isControlled) setInternalFilter(v);\n onFilterChange?.(v);\n };\n\n const isFilterActive = filterValue.length > 0;\n const effectiveMode = mode ?? (isNarrow ? \"page\" : \"sidebar\");\n const hasMatches = !isFilterActive || countMatchingItems(children, filterValue) > 0;\n\n return (\n <SidebarCollapsedContext.Provider value={collapsed}>\n <SidebarFilterContext.Provider value={filterValue}>\n <nav\n className={[\n styles.sidebar,\n collapsed ? styles.collapsed : null,\n effectiveMode === \"page\" ? styles.pageMode : null,\n className,\n ]\n .filter(Boolean)\n .join(\" \")}\n {...props}\n >\n {searchable && (\n <div className={styles.searchWrap}>\n <SearchBar\n open\n value={filterValue}\n onChange={(e) => handleFilterChange(e.target.value)}\n onClose={() => handleFilterChange(\"\")}\n onClear={() => handleFilterChange(\"\")}\n inline\n />\n </div>\n )}\n\n {isFilterActive && !hasMatches ? (\n <StatusPage\n title=\"No Results\"\n description=\"No items match your search.\"\n compact\n />\n ) : (\n children\n )}\n </nav>\n </SidebarFilterContext.Provider>\n </SidebarCollapsedContext.Provider>\n );\n}\n","import type { HTMLAttributes, ReactNode } from \"react\";\nimport styles from \"./Sidebar.module.css\";\n\nexport interface SidebarSectionProps extends HTMLAttributes<HTMLElement> {\n /**\n * Section heading. Rendered in small caps above the items.\n * Omit for an untitled section (e.g. the first group in a sidebar).\n */\n title?: string;\n children?: ReactNode;\n}\n\n/**\n * Named group of `SidebarItem` entries inside a `Sidebar`.\n *\n * Sections are separated by a thin divider. The title is optional —\n * omit it for the primary section when the grouping is self-evident.\n *\n * Mirrors the `AdwSidebar` section model (libadwaita 1.9 / GNOME 50).\n *\n * @example\n * ```tsx\n * <Sidebar>\n * <SidebarSection title=\"Mailboxes\">\n * <SidebarItem label=\"Inbox\" icon={GoHome} active />\n * <SidebarItem label=\"Sent\" icon={Share} />\n * </SidebarSection>\n * <SidebarSection title=\"Tags\">\n * <SidebarItem label=\"Work\" icon={Star} />\n * <SidebarItem label=\"Personal\" icon={Star} />\n * </SidebarSection>\n * </Sidebar>\n * ```\n */\nexport function SidebarSection({\n title,\n children,\n className,\n ...props\n}: SidebarSectionProps) {\n return (\n <section\n className={[styles.section, className].filter(Boolean).join(\" \")}\n {...props}\n >\n {title && (\n <h3 className={styles.sectionTitle}>{title}</h3>\n )}\n <ul role=\"list\" className={styles.list}>\n {children}\n </ul>\n </section>\n );\n}\n","/* ─── Tooltip bubble ─────────────────────────────────────────────────────────── */\n\n.tooltip {\n position: fixed;\n z-index: 10500; /* above Dialog (10000) and Toaster (9999) */\n\n max-width: 240px;\n padding: 4px var(--gnome-space-2, 12px);\n\n background-color: var(--gnome-tooltip-bg-color, #3d3d3d);\n color: var(--gnome-tooltip-fg-color, #ffffff);\n\n font-family: var(--gnome-font-family);\n font-size: var(--gnome-font-size-caption, 0.8125rem);\n font-weight: var(--gnome-font-weight-normal, 400);\n line-height: var(--gnome-line-height-body, 1.5);\n text-align: center;\n white-space: pre-wrap;\n word-break: break-word;\n\n border-radius: var(--gnome-radius-sm, 6px);\n box-shadow:\n 0 2px 8px rgba(0, 0, 0, 0.2),\n 0 0 0 1px rgba(255, 255, 255, 0.06);\n\n pointer-events: none;\n user-select: none;\n\n /* Hidden by default — fades in when .visible is applied */\n opacity: 0;\n transform: scale(0.94);\n transition:\n opacity var(--gnome-duration-fast, 100ms) var(--gnome-easing-default, ease),\n transform var(--gnome-duration-fast, 100ms) var(--gnome-easing-default, ease);\n}\n\n/* ─── Visible state ──────────────────────────────────────────────────────────── */\n\n.visible {\n opacity: 1;\n transform: scale(1);\n}\n\n/* ─── Arrow ──────────────────────────────────────────────────────────────────── */\n\n.tooltip::after {\n content: \"\";\n position: absolute;\n width: 0;\n height: 0;\n border: 5px solid transparent;\n pointer-events: none;\n}\n\n.top::after {\n top: 100%;\n left: 50%;\n transform: translateX(-50%);\n border-top-color: var(--gnome-tooltip-bg-color, #3d3d3d);\n}\n\n.bottom::after {\n bottom: 100%;\n left: 50%;\n transform: translateX(-50%);\n border-bottom-color: var(--gnome-tooltip-bg-color, #3d3d3d);\n}\n\n.left::after {\n left: 100%;\n top: 50%;\n transform: translateY(-50%);\n border-left-color: var(--gnome-tooltip-bg-color, #3d3d3d);\n}\n\n.right::after {\n right: 100%;\n top: 50%;\n transform: translateY(-50%);\n border-right-color: var(--gnome-tooltip-bg-color, #3d3d3d);\n}\n\n/* ─── Dark mode ──────────────────────────────────────────────────────────────── */\n\n@media (prefers-color-scheme: dark) {\n .tooltip {\n background-color: var(--gnome-tooltip-bg-color, #c0bfbc);\n color: var(--gnome-tooltip-fg-color, rgba(0, 0, 0, 0.8));\n box-shadow:\n 0 2px 8px rgba(0, 0, 0, 0.4),\n 0 0 0 1px rgba(0, 0, 0, 0.12);\n }\n\n .top::after { border-top-color: var(--gnome-tooltip-bg-color, #c0bfbc); }\n .bottom::after { border-bottom-color: var(--gnome-tooltip-bg-color, #c0bfbc); }\n .left::after { border-left-color: var(--gnome-tooltip-bg-color, #c0bfbc); }\n .right::after { border-right-color: var(--gnome-tooltip-bg-color, #c0bfbc); }\n}\n\n/* ─── Reduced motion ─────────────────────────────────────────────────────────── */\n\n@media (prefers-reduced-motion: reduce) {\n .tooltip {\n transition: opacity var(--gnome-duration-fast, 100ms) linear;\n transform: none;\n }\n\n .visible {\n transform: none;\n }\n}\n","import {\n useState,\n useRef,\n useId,\n useCallback,\n useEffect,\n cloneElement,\n type ReactElement,\n type HTMLAttributes,\n} from \"react\";\nimport { createPortal } from \"react-dom\";\nimport styles from \"./Tooltip.module.css\";\n\nexport type TooltipPlacement = \"top\" | \"bottom\" | \"left\" | \"right\";\n\nexport interface TooltipProps {\n /**\n * The tooltip label. Keep it short — a noun phrase or brief description.\n * Do not duplicate information already visible on screen.\n */\n label: string;\n /**\n * Preferred placement relative to the trigger.\n * The tooltip flips automatically if there is not enough space.\n * Defaults to `\"top\"`.\n */\n placement?: TooltipPlacement;\n /**\n * Delay in milliseconds before the tooltip appears on hover.\n * Defaults to `500`. Set to `0` for instant.\n */\n delay?: number;\n /**\n * The element that triggers the tooltip.\n * Must be a single React element that can receive `ref` and event props.\n */\n children: ReactElement<HTMLAttributes<HTMLElement>>;\n}\n\ninterface Position {\n top: number;\n left: number;\n placement: TooltipPlacement;\n}\n\nconst GAP = 6; // px between trigger and tooltip\n\nfunction computePosition(\n trigger: DOMRect,\n tooltip: DOMRect,\n preferred: TooltipPlacement\n): Position {\n const vw = window.innerWidth;\n const vh = window.innerHeight;\n\n const placements: TooltipPlacement[] = [\n preferred,\n preferred === \"top\" || preferred === \"bottom\"\n ? preferred === \"top\" ? \"bottom\" : \"top\"\n : preferred === \"left\" ? \"right\" : \"left\",\n \"top\",\n \"bottom\",\n \"left\",\n \"right\",\n ];\n\n for (const p of placements) {\n let top = 0;\n let left = 0;\n\n if (p === \"top\") {\n top = trigger.top - tooltip.height - GAP;\n left = trigger.left + trigger.width / 2 - tooltip.width / 2;\n } else if (p === \"bottom\") {\n top = trigger.bottom + GAP;\n left = trigger.left + trigger.width / 2 - tooltip.width / 2;\n } else if (p === \"left\") {\n top = trigger.top + trigger.height / 2 - tooltip.height / 2;\n left = trigger.left - tooltip.width - GAP;\n } else {\n top = trigger.top + trigger.height / 2 - tooltip.height / 2;\n left = trigger.right + GAP;\n }\n\n // Clamp to viewport with 8 px margin\n const margin = 8;\n const fitsH = left >= margin && left + tooltip.width <= vw - margin;\n const fitsV = top >= margin && top + tooltip.height <= vh - margin;\n\n if (fitsH && fitsV) {\n return {\n top: Math.max(margin, Math.min(top, vh - tooltip.height - margin)),\n left: Math.max(margin, Math.min(left, vw - tooltip.width - margin)),\n placement: p,\n };\n }\n }\n\n // Fallback: clamp preferred\n let top = 0, left = 0;\n if (preferred === \"top\") { top = trigger.top - tooltip.height - GAP; left = trigger.left + trigger.width / 2 - tooltip.width / 2; }\n if (preferred === \"bottom\") { top = trigger.bottom + GAP; left = trigger.left + trigger.width / 2 - tooltip.width / 2; }\n if (preferred === \"left\") { top = trigger.top + trigger.height / 2 - tooltip.height / 2; left = trigger.left - tooltip.width - GAP; }\n if (preferred === \"right\") { top = trigger.top + trigger.height / 2 - tooltip.height / 2; left = trigger.right + GAP; }\n\n const margin = 8;\n return {\n top: Math.max(margin, Math.min(top, vh - tooltip.height - margin)),\n left: Math.max(margin, Math.min(left, window.innerWidth - tooltip.width - margin)),\n placement: preferred,\n };\n}\n\n/**\n * Informational tooltip following the Adwaita / GNOME HIG pattern.\n *\n * Wraps a single trigger element and shows a floating label on hover or\n * keyboard focus. The tooltip is positioned automatically and flips if\n * there is not enough space.\n *\n * - `aria-describedby` is wired to the trigger automatically.\n * - The tooltip itself is `role=\"tooltip\"` and rendered in a portal.\n * - Does not show on touch — touch devices have no hover state.\n *\n * @example\n * <Tooltip label=\"Save file (Ctrl+S)\">\n * <Button aria-label=\"Save\"><Icon icon={Save} /></Button>\n * </Tooltip>\n *\n * @see https://developer.gnome.org/hig/patterns/feedback/tooltips.html\n */\nexport function Tooltip({\n label,\n placement: preferredPlacement = \"top\",\n delay = 500,\n children,\n}: TooltipProps) {\n const id = useId();\n const [visible, setVisible] = useState(false);\n const [pos, setPos] = useState<Position | null>(null);\n\n const triggerRef = useRef<HTMLElement>(null);\n const tooltipRef = useRef<HTMLDivElement>(null);\n const timerRef = useRef<ReturnType<typeof setTimeout> | null>(null);\n\n const clearTimer = () => {\n if (timerRef.current) { clearTimeout(timerRef.current); timerRef.current = null; }\n };\n\n const place = useCallback(() => {\n if (!triggerRef.current || !tooltipRef.current) return;\n const triggerRect = triggerRef.current.getBoundingClientRect();\n const tooltipRect = tooltipRef.current.getBoundingClientRect();\n setPos(computePosition(triggerRect, tooltipRect, preferredPlacement));\n }, [preferredPlacement]);\n\n // Reposition on scroll/resize while visible\n useEffect(() => {\n if (!visible) return;\n place();\n window.addEventListener(\"scroll\", place, { passive: true, capture: true });\n window.addEventListener(\"resize\", place, { passive: true });\n return () => {\n window.removeEventListener(\"scroll\", place, { capture: true });\n window.removeEventListener(\"resize\", place);\n };\n }, [visible, place]);\n\n const show = useCallback(() => {\n clearTimer();\n timerRef.current = setTimeout(() => setVisible(true), delay);\n }, [delay]);\n\n const hide = useCallback(() => {\n clearTimer();\n setVisible(false);\n setPos(null);\n }, []);\n\n // Position as soon as the tooltip mounts\n useEffect(() => {\n if (visible) place();\n }, [visible, place]);\n\n // Dismiss on Escape\n useEffect(() => {\n if (!visible) return;\n const handler = (e: KeyboardEvent) => { if (e.key === \"Escape\") hide(); };\n document.addEventListener(\"keydown\", handler);\n return () => document.removeEventListener(\"keydown\", handler);\n }, [visible, hide]);\n\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const child = cloneElement(children as any, {\n ref: triggerRef,\n \"aria-describedby\": id,\n onMouseEnter: (e: React.MouseEvent<HTMLElement>) => {\n show();\n children.props.onMouseEnter?.(e);\n },\n onMouseLeave: (e: React.MouseEvent<HTMLElement>) => {\n hide();\n children.props.onMouseLeave?.(e);\n },\n onFocus: (e: React.FocusEvent<HTMLElement>) => {\n show();\n children.props.onFocus?.(e);\n },\n onBlur: (e: React.FocusEvent<HTMLElement>) => {\n hide();\n children.props.onBlur?.(e);\n },\n });\n\n const tooltip = (\n <div\n ref={tooltipRef}\n id={id}\n role=\"tooltip\"\n className={[\n styles.tooltip,\n pos ? styles[pos.placement] : null,\n visible && pos ? styles.visible : null,\n ]\n .filter(Boolean)\n .join(\" \")}\n style={\n pos\n ? { top: pos.top, left: pos.left }\n : { visibility: \"hidden\", top: -9999, left: -9999 }\n }\n >\n {label}\n </div>\n );\n\n return (\n <>\n {child}\n {typeof document !== \"undefined\"\n ? createPortal(tooltip, document.body)\n : tooltip}\n </>\n );\n}\n","import {\n useState,\n useRef,\n useEffect,\n useCallback,\n useContext,\n type ButtonHTMLAttributes,\n type DragEvent,\n type ReactNode,\n type MouseEvent,\n type KeyboardEvent,\n} from \"react\";\nimport { createPortal } from \"react-dom\";\nimport type { IconDefinition } from \"@gnome-ui/icons\";\nimport { Icon } from \"../Icon\";\nimport { Tooltip } from \"../Tooltip\";\nimport { useSidebarCollapsed, SidebarFilterContext } from \"./Sidebar\";\nimport styles from \"./Sidebar.module.css\";\n\nexport interface SidebarMenuEntry {\n /** Label shown in the context menu. */\n label: string;\n /** Called when the entry is clicked. */\n onClick: () => void;\n /** Renders the entry in red. Use for destructive actions. */\n destructive?: boolean;\n /** Disables the entry. */\n disabled?: boolean;\n}\n\nexport interface SidebarItemProps extends ButtonHTMLAttributes<HTMLButtonElement> {\n /** Primary label. */\n label: string;\n /** Icon from `@gnome-ui/icons`. */\n icon?: IconDefinition;\n /** Marks this item as the currently active view. */\n active?: boolean;\n /**\n * Trailing widget — any ReactNode (badge, button, icon…).\n * Supersedes `badge` when both are provided.\n */\n suffix?: ReactNode;\n /**\n * @deprecated Use `suffix` instead.\n */\n badge?: ReactNode;\n /**\n * Short tooltip shown when hovering the item.\n * Automatically shown when the sidebar is `collapsed`.\n */\n tooltip?: string;\n /**\n * Context-menu entries shown on right-click or the Menu key.\n */\n menuItems?: SidebarMenuEntry[];\n /**\n * Called when a dragged item is dropped onto this row.\n * Mirrors `AdwSidebar` per-row drop target support (libadwaita 1.9).\n */\n onDrop?: (e: DragEvent<HTMLButtonElement>) => void;\n /**\n * MIME types this row accepts as drop targets (e.g. `[\"text/plain\"]`).\n * When provided, drops of other types are rejected via `e.preventDefault()`.\n * When omitted, all types are accepted.\n */\n acceptTypes?: string[];\n}\n\n/**\n * Individual navigation item inside a `Sidebar` or `SidebarSection`.\n *\n * **Tier 11 additions:**\n * - Automatically hidden when `Sidebar`'s `filter` / `searchable` is active\n * and the `label` does not match (case-insensitive substring).\n * - `onDrop` / `acceptTypes` — HTML5 drag-and-drop target support.\n */\nexport function SidebarItem({\n label,\n icon,\n active = false,\n suffix,\n badge,\n tooltip,\n menuItems,\n onDrop,\n acceptTypes,\n className,\n ...props\n}: SidebarItemProps) {\n const collapsed = useSidebarCollapsed();\n const filterValue = useContext(SidebarFilterContext);\n const trailing = suffix ?? badge;\n const effectiveTooltip = collapsed ? (tooltip ?? label) : tooltip;\n\n // ── Filter visibility ────────────────────────────────────────────────────\n const isFiltered =\n filterValue.length > 0 &&\n !label.toLowerCase().includes(filterValue.toLowerCase());\n\n // ── Drag-and-drop ────────────────────────────────────────────────────────\n const [isDragOver, setIsDragOver] = useState(false);\n\n const handleDragOver = (e: DragEvent<HTMLButtonElement>) => {\n if (!onDrop) return;\n if (acceptTypes?.length) {\n const accepted = acceptTypes.some((t) => e.dataTransfer.types.includes(t));\n if (!accepted) return;\n }\n e.preventDefault();\n setIsDragOver(true);\n };\n\n const handleDragLeave = () => setIsDragOver(false);\n\n const handleDrop = (e: DragEvent<HTMLButtonElement>) => {\n if (!onDrop) return;\n e.preventDefault();\n setIsDragOver(false);\n onDrop(e);\n };\n\n // ── Context menu ─────────────────────────────────────────────────────────\n const [menu, setMenu] = useState<{ x: number; y: number } | null>(null);\n const menuRef = useRef<HTMLDivElement>(null);\n\n const openMenu = useCallback((x: number, y: number) => setMenu({ x, y }), []);\n const closeMenu = useCallback(() => setMenu(null), []);\n\n useEffect(() => {\n if (!menu) return;\n const handleClick = (e: globalThis.MouseEvent) => {\n if (menuRef.current && !menuRef.current.contains(e.target as Node)) closeMenu();\n };\n const handleKey = (e: globalThis.KeyboardEvent) => {\n if (e.key === \"Escape\") closeMenu();\n };\n document.addEventListener(\"mousedown\", handleClick);\n document.addEventListener(\"keydown\", handleKey);\n return () => {\n document.removeEventListener(\"mousedown\", handleClick);\n document.removeEventListener(\"keydown\", handleKey);\n };\n }, [menu, closeMenu]);\n\n const handleContextMenu = (e: MouseEvent<HTMLButtonElement>) => {\n if (!menuItems?.length) return;\n e.preventDefault();\n openMenu(e.clientX, e.clientY);\n };\n\n const handleMenuKey = (e: KeyboardEvent<HTMLButtonElement>) => {\n if (e.key === \"ContextMenu\" || (e.key === \"F10\" && e.shiftKey)) {\n if (!menuItems?.length) return;\n e.preventDefault();\n const rect = (e.currentTarget as HTMLElement).getBoundingClientRect();\n openMenu(rect.right, rect.top);\n }\n };\n\n // ── Render ───────────────────────────────────────────────────────────────\n const button = (\n <button\n type=\"button\"\n aria-current={active ? \"page\" : undefined}\n aria-label={collapsed ? label : undefined}\n className={[\n styles.itemBtn,\n active ? styles.active : null,\n collapsed ? styles.itemCollapsed : null,\n isDragOver ? styles.dragOver : null,\n className,\n ]\n .filter(Boolean)\n .join(\" \")}\n onContextMenu={handleContextMenu}\n onKeyDown={handleMenuKey}\n onDragOver={handleDragOver}\n onDragLeave={handleDragLeave}\n onDrop={handleDrop}\n {...props}\n >\n {icon && (\n <span className={styles.itemIcon}>\n <Icon icon={icon} size=\"md\" aria-hidden />\n </span>\n )}\n {!collapsed && <span className={styles.itemLabel}>{label}</span>}\n {!collapsed && trailing && <span className={styles.itemSuffix}>{trailing}</span>}\n </button>\n );\n\n return (\n <li className={styles.item} hidden={isFiltered || undefined}>\n {effectiveTooltip\n ? <Tooltip label={effectiveTooltip} placement=\"right\">{button}</Tooltip>\n : button}\n\n {menu && menuItems?.length && typeof document !== \"undefined\" &&\n createPortal(\n <div\n ref={menuRef}\n className={styles.contextMenu}\n style={{ top: menu.y, left: menu.x }}\n role=\"menu\"\n >\n {menuItems.map((entry) => (\n <button\n key={entry.label}\n type=\"button\"\n role=\"menuitem\"\n disabled={entry.disabled}\n className={[\n styles.contextMenuItem,\n entry.destructive ? styles.contextMenuDestructive : null,\n ]\n .filter(Boolean)\n .join(\" \")}\n onClick={() => {\n entry.onClick();\n closeMenu();\n }}\n >\n {entry.label}\n </button>\n ))}\n </div>,\n document.body,\n )}\n </li>\n );\n}\n","/* ─── Container ────────────────────────────────────────────────────────────── */\n\n.spin {\n display: inline-flex;\n align-items: center;\n\n background-color: var(--gnome-view-bg-color, #ffffff);\n border: 1px solid var(--gnome-card-shade-color, rgba(0, 0, 0, 0.15));\n border-radius: var(--gnome-radius-md, 8px);\n box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.06);\n overflow: hidden;\n\n font-family: var(--gnome-font-family);\n font-size: var(--gnome-font-size-body, 1rem);\n color: var(--gnome-view-fg-color, rgba(0, 0, 0, 0.8));\n\n transition: box-shadow var(--gnome-duration-fast) var(--gnome-easing-default),\n border-color var(--gnome-duration-fast) var(--gnome-easing-default);\n}\n\n.spin:focus {\n outline: none;\n}\n\n.spin:focus-visible {\n border-color: var(--gnome-accent-color, #3584e4);\n box-shadow:\n inset 0 1px 2px rgba(0, 0, 0, 0.06),\n 0 0 0 var(--gnome-focus-ring-offset, 2px) var(--gnome-window-bg-color, #fafafa),\n 0 0 0 calc(var(--gnome-focus-ring-offset, 2px) + var(--gnome-focus-ring-width, 3px))\n var(--gnome-focus-ring-color, #3584e4);\n}\n\n/* ─── Disabled ─────────────────────────────────────────────────────────────── */\n\n.disabled {\n opacity: var(--gnome-opacity-disabled, 0.5);\n pointer-events: none;\n}\n\n/* ─── +/− buttons ──────────────────────────────────────────────────────────── */\n\n.btn {\n appearance: none;\n -webkit-appearance: none;\n border: none;\n background: transparent;\n font: inherit;\n font-size: 1.1rem;\n line-height: 1;\n cursor: pointer;\n color: inherit;\n\n display: flex;\n align-items: center;\n justify-content: center;\n width: 36px;\n height: 36px;\n flex-shrink: 0;\n\n transition: background-color var(--gnome-duration-fast) var(--gnome-easing-default);\n}\n\n.btn:hover:not(:disabled) {\n background-color: color-mix(\n in srgb,\n var(--gnome-view-bg-color, #ffffff) 90%,\n black 10%\n );\n}\n\n.btn:active:not(:disabled) {\n background-color: color-mix(\n in srgb,\n var(--gnome-view-bg-color, #ffffff) 80%,\n black 20%\n );\n}\n\n.btn:disabled {\n opacity: var(--gnome-opacity-disabled, 0.5);\n cursor: not-allowed;\n}\n\n/* Dividers between button and value */\n.btn:first-child {\n border-right: 1px solid var(--gnome-card-shade-color, rgba(0, 0, 0, 0.12));\n}\n\n.btn:last-child {\n border-left: 1px solid var(--gnome-card-shade-color, rgba(0, 0, 0, 0.12));\n}\n\n/* ─── Value display ────────────────────────────────────────────────────────── */\n\n.value {\n flex: 1;\n text-align: center;\n padding: 0 var(--gnome-space-2, 12px);\n min-width: 56px;\n font-variant-numeric: tabular-nums;\n user-select: none;\n white-space: nowrap;\n}\n\n/* ─── Dark mode ────────────────────────────────────────────────────────────── */\n\n@media (prefers-color-scheme: dark) {\n .spin {\n background-color: var(--gnome-view-bg-color, #1e1e1e);\n border-color: rgba(255, 255, 255, 0.12);\n box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.2);\n }\n\n .btn:hover:not(:disabled) {\n background-color: color-mix(\n in srgb,\n var(--gnome-view-bg-color, #1e1e1e) 85%,\n white 15%\n );\n }\n\n .btn:active:not(:disabled) {\n background-color: color-mix(\n in srgb,\n var(--gnome-view-bg-color, #1e1e1e) 75%,\n white 25%\n );\n }\n}\n\n/* ─── Reduced motion ───────────────────────────────────────────────────────── */\n\n@media (prefers-reduced-motion: reduce) {\n .spin,\n .btn {\n transition: none;\n }\n}\n","import { useCallback, type KeyboardEvent, type HTMLAttributes } from \"react\";\nimport styles from \"./SpinButton.module.css\";\n\nexport interface SpinButtonProps\n extends Omit<HTMLAttributes<HTMLDivElement>, \"onChange\"> {\n /** Current value. */\n value: number;\n /** Called when the value changes. */\n onChange: (value: number) => void;\n /** Minimum allowed value. Defaults to `0`. */\n min?: number;\n /** Maximum allowed value. Defaults to `100`. */\n max?: number;\n /** Amount to increment/decrement per step. Defaults to `1`. */\n step?: number;\n /** Number of decimal places shown. Derived from `step` when omitted. */\n decimals?: number;\n /** Disables the control. */\n disabled?: boolean;\n /** Accessible label. Required when no visible `<label>` is associated. */\n \"aria-label\"?: string;\n /** Associates the control with a visible label element. */\n \"aria-labelledby\"?: string;\n}\n\nfunction countDecimals(n: number): number {\n const s = n.toString();\n const dot = s.indexOf(\".\");\n return dot === -1 ? 0 : s.length - dot - 1;\n}\n\nfunction clamp(value: number, min: number, max: number): number {\n return Math.min(max, Math.max(min, value));\n}\n\n/**\n * Numeric input with − and + buttons following the Adwaita `GtkSpinButton` style.\n *\n * Supports keyboard interaction: ↑/↓ increment by one step,\n * Page Up/Down increment by 10×, Home/End jump to min/max.\n *\n * @see https://developer.gnome.org/hig/patterns/controls/spin-buttons.html\n */\nexport function SpinButton({\n value,\n onChange,\n min = 0,\n max = 100,\n step = 1,\n decimals,\n disabled = false,\n className,\n \"aria-label\": ariaLabel,\n \"aria-labelledby\": ariaLabelledBy,\n ...props\n}: SpinButtonProps) {\n const dp = decimals ?? countDecimals(step);\n\n const set = useCallback(\n (next: number) => onChange(parseFloat(clamp(next, min, max).toFixed(dp))),\n [onChange, min, max, dp],\n );\n\n const handleKeyDown = useCallback(\n (e: KeyboardEvent<HTMLDivElement>) => {\n switch (e.key) {\n case \"ArrowUp\": e.preventDefault(); set(value + step); break;\n case \"ArrowDown\": e.preventDefault(); set(value - step); break;\n case \"PageUp\": e.preventDefault(); set(value + step * 10); break;\n case \"PageDown\": e.preventDefault(); set(value - step * 10); break;\n case \"Home\": e.preventDefault(); set(min); break;\n case \"End\": e.preventDefault(); set(max); break;\n }\n },\n [value, step, min, max, set],\n );\n\n return (\n <div\n role=\"spinbutton\"\n aria-valuenow={value}\n aria-valuemin={min}\n aria-valuemax={max}\n aria-label={ariaLabel}\n aria-labelledby={ariaLabelledBy}\n aria-disabled={disabled || undefined}\n tabIndex={disabled ? -1 : 0}\n onKeyDown={disabled ? undefined : handleKeyDown}\n className={[styles.spin, disabled ? styles.disabled : null, className]\n .filter(Boolean)\n .join(\" \")}\n {...props}\n >\n <button\n type=\"button\"\n tabIndex={-1}\n aria-hidden=\"true\"\n disabled={disabled || value <= min}\n className={styles.btn}\n onClick={() => set(value - step)}\n >\n −\n </button>\n\n <span className={styles.value} aria-hidden=\"true\">\n {value.toFixed(dp)}\n </span>\n\n <button\n type=\"button\"\n tabIndex={-1}\n aria-hidden=\"true\"\n disabled={disabled || value >= max}\n className={styles.btn}\n onClick={() => set(value + step)}\n >\n +\n </button>\n </div>\n );\n}\n","/* ─── Bar (outer wrapper) ──────────────────────────────────────────────────── */\n\n.bar {\n background-color: var(--gnome-headerbar-bg-color, #ebebeb);\n border-bottom: 1px solid var(--gnome-headerbar-border-color, rgba(0, 0, 0, 0.12));\n}\n\n/* ─── Tab list ─────────────────────────────────────────────────────────────── */\n\n.list {\n display: flex;\n align-items: stretch;\n overflow-x: auto;\n scrollbar-width: none;\n padding: 0 var(--gnome-space-1, 6px);\n gap: 2px;\n min-height: 40px;\n}\n\n.list::-webkit-scrollbar {\n display: none;\n}\n\n/* ─── Tab button ───────────────────────────────────────────────────────────── */\n\n.tab {\n appearance: none;\n -webkit-appearance: none;\n border: none;\n background: transparent;\n font: inherit;\n cursor: pointer;\n outline: none;\n\n position: relative;\n display: inline-flex;\n align-items: center;\n gap: var(--gnome-space-1, 6px);\n padding: 0 var(--gnome-space-2, 12px);\n min-width: 80px;\n max-width: 200px;\n white-space: nowrap;\n\n font-family: var(--gnome-font-family);\n font-size: var(--gnome-font-size-body, 1rem);\n font-weight: var(--gnome-font-weight-normal, 400);\n color: var(--gnome-headerbar-fg-color, rgba(0, 0, 0, 0.8));\n opacity: 0.7;\n\n border-radius: var(--gnome-radius-md, 8px) var(--gnome-radius-md, 8px) 0 0;\n transition:\n background-color var(--gnome-duration-fast) var(--gnome-easing-default),\n opacity var(--gnome-duration-fast) var(--gnome-easing-default);\n}\n\n.tab::after {\n content: \"\";\n position: absolute;\n bottom: 0;\n left: var(--gnome-space-2, 12px);\n right: var(--gnome-space-2, 12px);\n height: 2px;\n border-radius: 1px 1px 0 0;\n background-color: var(--gnome-accent-bg-color, #3584e4);\n opacity: 0;\n transition: opacity var(--gnome-duration-fast) var(--gnome-easing-default);\n}\n\n.tab:hover {\n opacity: 1;\n background-color: var(--gnome-hover-overlay);\n}\n\n.tab:disabled {\n opacity: var(--gnome-opacity-disabled, 0.5);\n cursor: not-allowed;\n}\n\n/* ─── Active tab ───────────────────────────────────────────────────────────── */\n\n.active {\n opacity: 1;\n font-weight: var(--gnome-font-weight-semibold, 600);\n background-color: var(--gnome-active-overlay);\n}\n\n.active::after {\n opacity: 1;\n}\n\n/* ─── Focus ────────────────────────────────────────────────────────────────── */\n\n.tab:focus-visible {\n box-shadow:\n inset 0 0 0 var(--gnome-focus-ring-offset, 2px) var(--gnome-headerbar-bg-color, #ebebeb),\n inset 0 0 0 calc(var(--gnome-focus-ring-offset, 2px) + var(--gnome-focus-ring-width, 3px))\n var(--gnome-focus-ring-color, #3584e4);\n}\n\n/* ─── Icon inside tab ──────────────────────────────────────────────────────── */\n\n.tabIcon {\n display: flex;\n align-items: center;\n flex-shrink: 0;\n}\n\n/* ─── Label inside tab ─────────────────────────────────────────────────────── */\n\n.tabLabel {\n flex: 1;\n min-width: 0;\n overflow: hidden;\n text-overflow: ellipsis;\n}\n\n/* ─── Close button ─────────────────────────────────────────────────────────── */\n\n.closeBtn {\n flex-shrink: 0;\n display: flex;\n align-items: center;\n justify-content: center;\n width: 18px;\n height: 18px;\n border-radius: var(--gnome-radius-sm, 4px);\n font-size: 1rem;\n line-height: 1;\n opacity: 0.5;\n margin-left: 2px;\n transition: opacity var(--gnome-duration-fast) var(--gnome-easing-default),\n background-color var(--gnome-duration-fast) var(--gnome-easing-default);\n}\n\n.closeBtn:hover {\n opacity: 1;\n background-color: var(--gnome-active-overlay);\n}\n\n.closeBtn:focus-visible {\n outline: 2px solid var(--gnome-focus-ring-color, #3584e4);\n outline-offset: 1px;\n opacity: 1;\n}\n\n/* ─── Panel ────────────────────────────────────────────────────────────────── */\n\n.panel {\n outline: none;\n}\n\n.panel:focus-visible {\n box-shadow: inset 0 0 0 2px var(--gnome-focus-ring-color, #3584e4);\n}\n\n/* ─── Inline variant ───────────────────────────────────────────────────────── */\n\n.inline {\n background-color: transparent;\n border-bottom-color: transparent;\n}\n\n.inline .tab:focus-visible {\n box-shadow:\n inset 0 0 0 var(--gnome-focus-ring-offset, 2px) var(--gnome-window-bg-color, #fafafa),\n inset 0 0 0 calc(var(--gnome-focus-ring-offset, 2px) + var(--gnome-focus-ring-width, 3px))\n var(--gnome-focus-ring-color, #3584e4);\n}\n\n/* ─── High contrast ────────────────────────────────────────────────────────── */\n\n@media (prefers-contrast: more) {\n .bar {\n border-bottom-width: 2px;\n }\n\n .tab::after {\n height: 3px;\n }\n}\n\n/* ─── Reduced motion ───────────────────────────────────────────────────────── */\n\n@media (prefers-reduced-motion: reduce) {\n .tab,\n .tab::after,\n .closeBtn {\n transition: none;\n }\n}\n","import {\n useRef,\n type HTMLAttributes,\n type KeyboardEvent,\n type ReactNode,\n} from \"react\";\nimport styles from \"./Tabs.module.css\";\n\nexport interface TabBarProps extends HTMLAttributes<HTMLDivElement> {\n children?: ReactNode;\n /** Accessible label for the tab list. */\n \"aria-label\"?: string;\n /**\n * Removes the header-bar background so the tab bar blends into any surface.\n * Use when placing the bar inside a card, content area, or custom container.\n * Mirrors the `.inline` style class.\n */\n inline?: boolean;\n}\n\n/**\n * Horizontal tab bar that holds `TabItem` elements.\n *\n * Manages roving-tabindex keyboard navigation (← → Home End).\n *\n * @see https://developer.gnome.org/hig/patterns/nav/tabs.html\n */\nexport function TabBar({\n children,\n className,\n inline = false,\n \"aria-label\": ariaLabel = \"Tabs\",\n ...props\n}: TabBarProps) {\n const listRef = useRef<HTMLDivElement>(null);\n\n function handleKeyDown(e: KeyboardEvent<HTMLDivElement>) {\n const tabs = Array.from(\n listRef.current?.querySelectorAll<HTMLButtonElement>(\n \"[role=tab]:not(:disabled)\",\n ) ?? [],\n );\n const idx = tabs.findIndex((t) => t === document.activeElement);\n if (idx === -1) return;\n\n let next = idx;\n if (e.key === \"ArrowRight\") next = (idx + 1) % tabs.length;\n else if (e.key === \"ArrowLeft\") next = (idx - 1 + tabs.length) % tabs.length;\n else if (e.key === \"Home\") next = 0;\n else if (e.key === \"End\") next = tabs.length - 1;\n else return;\n\n e.preventDefault();\n tabs[next].focus();\n }\n\n return (\n <div\n className={[styles.bar, inline ? styles.inline : null, className]\n .filter(Boolean)\n .join(\" \")}\n {...props}\n >\n <div\n ref={listRef}\n role=\"tablist\"\n aria-label={ariaLabel}\n className={styles.list}\n onKeyDown={handleKeyDown}\n >\n {children}\n </div>\n </div>\n );\n}\n","import type { ButtonHTMLAttributes, ReactNode } from \"react\";\nimport type { IconDefinition } from \"@gnome-ui/icons\";\nimport { Icon } from \"../Icon\";\nimport styles from \"./Tabs.module.css\";\n\nexport interface TabItemProps extends ButtonHTMLAttributes<HTMLButtonElement> {\n /** Tab label. */\n label: string;\n /** Optional icon from `@gnome-ui/icons`. */\n icon?: IconDefinition;\n /** Marks this tab as the currently selected one. */\n active?: boolean;\n /**\n * id of the `TabPanel` this tab controls.\n * Sets `aria-controls` automatically.\n */\n panelId?: string;\n /**\n * When provided, a close (×) button is rendered at the trailing edge.\n * Called when the user clicks the close button.\n */\n onClose?: () => void;\n /** Accessible label for the close button. Defaults to `\"Close tab\"`. */\n closeLabel?: string;\n children?: ReactNode;\n}\n\n/**\n * Individual tab button inside a `TabBar`.\n */\nexport function TabItem({\n label,\n icon,\n active = false,\n panelId,\n onClose,\n closeLabel = \"Close tab\",\n className,\n disabled,\n ...props\n}: TabItemProps) {\n return (\n <button\n role=\"tab\"\n type=\"button\"\n aria-selected={active}\n aria-controls={panelId}\n tabIndex={active ? 0 : -1}\n disabled={disabled}\n className={[\n styles.tab,\n active ? styles.active : null,\n onClose ? styles.closeable : null,\n className,\n ]\n .filter(Boolean)\n .join(\" \")}\n {...props}\n >\n {icon && (\n <span className={styles.tabIcon}>\n <Icon icon={icon} size=\"sm\" aria-hidden />\n </span>\n )}\n <span className={styles.tabLabel}>{label}</span>\n\n {onClose && (\n <span\n role=\"button\"\n tabIndex={0}\n aria-label={closeLabel}\n className={styles.closeBtn}\n onClick={(e) => { e.stopPropagation(); onClose(); }}\n onKeyDown={(e) => {\n if (e.key === \"Enter\" || e.key === \" \") {\n e.preventDefault();\n e.stopPropagation();\n onClose();\n }\n }}\n >\n ×\n </span>\n )}\n </button>\n );\n}\n","import type { HTMLAttributes, ReactNode } from \"react\";\nimport styles from \"./Tabs.module.css\";\n\nexport interface TabPanelProps extends HTMLAttributes<HTMLDivElement> {\n /** Must match the `panelId` passed to the corresponding `TabItem`. */\n id: string;\n /** Controls whether this panel is rendered. */\n active?: boolean;\n children?: ReactNode;\n}\n\n/**\n * Content panel associated with a `TabItem`.\n * Hidden panels are kept in the DOM but visually hidden.\n */\nexport function TabPanel({\n id,\n active = false,\n className,\n children,\n ...props\n}: TabPanelProps) {\n return (\n <div\n id={id}\n role=\"tabpanel\"\n hidden={!active}\n tabIndex={0}\n className={[styles.panel, className].filter(Boolean).join(\" \")}\n {...props}\n >\n {children}\n </div>\n );\n}\n","/* ─── Base row ─────────────────────────────────────────────────────────────── */\n\n.row {\n display: flex;\n align-items: center;\n gap: var(--gnome-space-2, 12px);\n padding: var(--gnome-space-2, 12px) var(--gnome-space-4, 24px);\n min-height: 52px;\n width: 100%;\n color: var(--gnome-card-fg-color, rgba(0, 0, 0, 0.8));\n text-align: start;\n}\n\n/* ─── Leading icon/avatar ──────────────────────────────────────────────────── */\n\n.leading {\n display: flex;\n align-items: center;\n flex-shrink: 0;\n color: var(--gnome-window-fg-color, rgba(0, 0, 0, 0.8));\n}\n\n/* ─── Text content ─────────────────────────────────────────────────────────── */\n\n.content {\n display: flex;\n flex-direction: column;\n flex: 1;\n min-width: 0;\n gap: 2px;\n}\n\n.title {\n font-family: var(--gnome-font-family);\n font-size: var(--gnome-font-size-body, 1rem);\n font-weight: var(--gnome-font-weight-normal, 400);\n line-height: var(--gnome-line-height-body, 1.5);\n color: var(--gnome-card-fg-color, rgba(0, 0, 0, 0.8));\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n}\n\n.subtitle {\n font-family: var(--gnome-font-family);\n font-size: var(--gnome-font-size-caption, 0.75rem);\n font-weight: var(--gnome-font-weight-normal, 400);\n line-height: var(--gnome-line-height-body, 1.5);\n color: var(--gnome-window-fg-color, rgba(0, 0, 0, 0.55));\n opacity: var(--gnome-opacity-dim, 0.55);\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n}\n\n/* ─── Trailing widget ──────────────────────────────────────────────────────── */\n\n.trailing {\n display: flex;\n align-items: center;\n flex-shrink: 0;\n gap: var(--gnome-space-1, 6px);\n}\n\n/* ─── Property variant ─────────────────────────────────────────────────────── */\n\n/* The label (title) is rendered small and dim above the value (subtitle) */\n.propertyLabel {\n font-family: var(--gnome-font-family);\n font-size: var(--gnome-font-size-caption, 0.75rem);\n font-weight: var(--gnome-font-weight-normal, 400);\n line-height: var(--gnome-line-height-body, 1.5);\n color: var(--gnome-window-fg-color, rgba(0, 0, 0, 0.55));\n opacity: var(--gnome-opacity-dim, 0.55);\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n}\n\n/* The value (subtitle) is rendered at body size and full opacity */\n.propertyValue {\n font-family: var(--gnome-font-family);\n font-size: var(--gnome-font-size-body, 1rem);\n font-weight: var(--gnome-font-weight-normal, 400);\n line-height: var(--gnome-line-height-body, 1.5);\n color: var(--gnome-card-fg-color, rgba(0, 0, 0, 0.8));\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n}\n\n/* ─── Interactive (activatable row) ────────────────────────────────────────── */\n\n.interactive {\n cursor: pointer;\n appearance: none;\n -webkit-appearance: none;\n background: transparent;\n border: none;\n font: inherit;\n outline: none;\n\n transition: background-color var(--gnome-duration-fast) var(--gnome-easing-default);\n}\n\n.interactive:hover {\n background-color: var(--gnome-hover-overlay);\n}\n\n.interactive:active {\n background-color: var(--gnome-active-overlay);\n}\n\n.interactive:focus-visible {\n box-shadow:\n inset 0 0 0 var(--gnome-focus-ring-offset, 2px) var(--gnome-window-bg-color, #fafafa),\n inset 0 0 0 calc(var(--gnome-focus-ring-offset, 2px) + var(--gnome-focus-ring-width, 3px))\n var(--gnome-focus-ring-color, #3584e4);\n}\n\n.interactive:disabled {\n opacity: var(--gnome-opacity-disabled, 0.5);\n cursor: not-allowed;\n pointer-events: none;\n}\n","import type { HTMLAttributes, ReactNode } from \"react\";\nimport styles from \"./ActionRow.module.css\";\n\nexport interface ActionRowProps extends HTMLAttributes<HTMLDivElement> {\n /** Primary label. */\n title: string;\n /** Secondary line below the title. */\n subtitle?: string;\n /** Icon or image placed at the leading edge. */\n leading?: ReactNode;\n /**\n * Widget placed at the trailing edge (Switch, Button, Text…).\n * For interactive end widgets prefer a controlled component and stop\n * event propagation inside it so the row's own onClick isn't triggered.\n */\n trailing?: ReactNode;\n /**\n * When true the entire row becomes clickable (renders as `<button>`).\n * Use for rows that navigate or trigger an action.\n */\n interactive?: boolean;\n /**\n * `\"property\"` flips the visual hierarchy: the subtitle becomes the\n * prominent value and the title shrinks to a dim label above it.\n * Use for read-only property display (e.g. \"OS Version / Ubuntu 24.04\").\n * Mirrors the `.property` style class.\n */\n variant?: \"default\" | \"property\";\n}\n\n/**\n * Standard settings row with title, optional subtitle, and end widget.\n *\n * Mirrors the Adwaita `AdwActionRow` pattern — the fundamental building block\n * inside a `BoxedList`.\n *\n * @see https://gnome.pages.gitlab.gnome.org/libadwaita/doc/main/class.ActionRow.html\n * @see https://developer.gnome.org/hig/patterns/containers.html\n */\nexport function ActionRow({\n title,\n subtitle,\n leading,\n trailing,\n interactive = false,\n variant = \"default\",\n className,\n ...props\n}: ActionRowProps) {\n const Tag = interactive ? \"button\" : \"div\";\n const property = variant === \"property\";\n\n const classes = [\n styles.row,\n interactive ? styles.interactive : null,\n property ? styles.property : null,\n className,\n ]\n .filter(Boolean)\n .join(\" \");\n\n return (\n <Tag className={classes} {...(props as HTMLAttributes<HTMLElement>)}>\n {leading && <span className={styles.leading}>{leading}</span>}\n\n <span className={styles.content}>\n <span className={property ? styles.propertyLabel : styles.title}>{title}</span>\n {subtitle && <span className={property ? styles.propertyValue : styles.subtitle}>{subtitle}</span>}\n </span>\n\n {trailing && <span className={styles.trailing}>{trailing}</span>}\n </Tag>\n );\n}\n","/* ─── Container ────────────────────────────────────────────────────────────── */\n\n.list {\n list-style: none;\n margin: 0;\n padding: 0;\n\n background-color: var(--gnome-card-bg-color, #ffffff);\n color: var(--gnome-card-fg-color, rgba(0, 0, 0, 0.8));\n border: 1px solid var(--gnome-divider-color);\n border-radius: var(--gnome-radius-lg);\n overflow: hidden;\n}\n\n/* ─── Row wrapper ──────────────────────────────────────────────────────────── */\n\n.item {\n position: relative;\n display: flex;\n flex-direction: column;\n}\n\n/* ─── Divider between rows ─────────────────────────────────────────────────── */\n\n.divider {\n height: 1px;\n background-color: var(--gnome-divider-color);\n /* Indent to align with row content — matches ActionRow's horizontal padding */\n margin-left: var(--gnome-space-4, 24px);\n}\n\n/* ─── Separate variant ─────────────────────────────────────────────────────── */\n\n.separate {\n background: none;\n border: none;\n border-radius: 0;\n display: flex;\n flex-direction: column;\n gap: var(--gnome-space-2, 12px);\n overflow: visible;\n}\n\n.separateItem {\n background-color: var(--gnome-card-bg-color, #ffffff);\n border: 1px solid var(--gnome-divider-color);\n border-radius: var(--gnome-radius-lg);\n overflow: hidden;\n display: flex;\n flex-direction: column;\n}\n\n@media (prefers-color-scheme: dark) {\n .separateItem {\n background-color: var(--gnome-card-bg-color, #2d2d2d);\n }\n}\n\n/* ─── High contrast ────────────────────────────────────────────────────────── */\n\n@media (prefers-contrast: more) {\n .list {\n border-width: 2px;\n }\n\n .divider {\n height: 2px;\n }\n}\n","import { Children, type HTMLAttributes, type ReactNode } from \"react\";\nimport styles from \"./BoxedList.module.css\";\n\nexport type BoxedListVariant = \"default\" | \"separate\";\n\nexport interface BoxedListProps extends HTMLAttributes<HTMLUListElement> {\n children?: ReactNode;\n /**\n * `\"separate\"` renders each child as its own standalone rounded card\n * instead of a single joined list — mirrors `.boxed-list-separate`.\n * Use when rows are independent items rather than a continuous group.\n */\n variant?: BoxedListVariant;\n}\n\n/**\n * Rounded bordered list — the most common container pattern in GNOME apps.\n *\n * Mirrors the Adwaita `.boxed-list` style applied to a `GtkListBox`.\n * Separators between rows are inserted automatically.\n * Each child is wrapped in a `<li>` — use with `ActionRow` or any row-shaped element.\n *\n * Use `variant=\"separate\"` to render each child as its own card, mirroring\n * the `.boxed-list-separate` style class.\n *\n * @see https://gnome.pages.gitlab.gnome.org/libadwaita/doc/main/style-classes.html#boxed-lists-cards\n * @see https://developer.gnome.org/hig/patterns/containers.html\n */\nexport function BoxedList({ children, variant = \"default\", className, ...props }: BoxedListProps) {\n const items = Children.toArray(children).filter(Boolean);\n const separate = variant === \"separate\";\n\n return (\n <ul\n role=\"list\"\n className={[styles.list, separate ? styles.separate : null, className]\n .filter(Boolean)\n .join(\" \")}\n {...props}\n >\n {items.map((child, i) => (\n <li key={i} className={separate ? styles.separateItem : styles.item}>\n {!separate && i > 0 && <div className={styles.divider} aria-hidden=\"true\" />}\n {child}\n </li>\n ))}\n </ul>\n );\n}\n","/* ─── Container ────────────────────────────────────────────────────────────── */\n\n.switcher {\n display: inline-flex;\n align-items: center;\n gap: 2px;\n padding: 3px;\n border-radius: var(--gnome-radius-pill);\n background-color: color-mix(\n in srgb,\n var(--gnome-headerbar-bg-color, #ebebeb) 75%,\n black 25%\n );\n}\n\n/* ─── Item ─────────────────────────────────────────────────────────────────── */\n\n.item {\n appearance: none;\n -webkit-appearance: none;\n border: none;\n background: transparent;\n font: inherit;\n cursor: pointer;\n outline: none;\n\n display: inline-flex;\n align-items: center;\n justify-content: center;\n gap: var(--gnome-space-1, 6px);\n padding: 5px var(--gnome-space-2, 12px);\n border-radius: calc(var(--gnome-radius-pill) - 3px);\n\n font-family: var(--gnome-font-family);\n font-size: var(--gnome-font-size-body, 1rem);\n font-weight: var(--gnome-font-weight-normal, 400);\n color: var(--gnome-headerbar-fg-color, rgba(0, 0, 0, 0.8));\n white-space: nowrap;\n\n transition:\n background-color var(--gnome-duration-fast) var(--gnome-easing-default),\n color var(--gnome-duration-fast) var(--gnome-easing-default);\n}\n\n.item:hover:not(:disabled):not(.active) {\n background-color: rgba(0, 0, 0, 0.06);\n}\n\n.item:active:not(:disabled):not(.active) {\n background-color: rgba(0, 0, 0, 0.12);\n}\n\n.item:disabled {\n opacity: var(--gnome-opacity-disabled, 0.5);\n cursor: not-allowed;\n}\n\n.item:focus-visible {\n box-shadow:\n 0 0 0 var(--gnome-focus-ring-offset, 2px)\n color-mix(in srgb, var(--gnome-headerbar-bg-color, #ebebeb) 75%, black 25%),\n 0 0 0 calc(var(--gnome-focus-ring-offset, 2px) + var(--gnome-focus-ring-width, 3px))\n var(--gnome-focus-ring-color, #3584e4);\n}\n\n/* ─── Active item ──────────────────────────────────────────────────────────── */\n\n.active {\n background-color: var(--gnome-headerbar-bg-color, #ebebeb);\n font-weight: var(--gnome-font-weight-semibold, 600);\n box-shadow: var(--gnome-shadow-sm, 0 1px 2px rgba(0, 0, 0, 0.12));\n}\n\n/* ─── Icon ─────────────────────────────────────────────────────────────────── */\n\n.itemIcon {\n display: flex;\n align-items: center;\n flex-shrink: 0;\n}\n\n/* ─── Label ────────────────────────────────────────────────────────────────── */\n\n.itemLabel {\n line-height: var(--gnome-line-height-body, 1.5);\n}\n\n/* ─── Dark mode ────────────────────────────────────────────────────────────── */\n\n@media (prefers-color-scheme: dark) {\n .switcher {\n background-color: color-mix(\n in srgb,\n var(--gnome-headerbar-bg-color, #303030) 70%,\n black 30%\n );\n }\n\n .item:hover:not(:disabled):not(.active) {\n background-color: rgba(255, 255, 255, 0.08);\n }\n\n .item:active:not(:disabled):not(.active) {\n background-color: rgba(255, 255, 255, 0.14);\n }\n\n .active {\n background-color: var(--gnome-headerbar-bg-color, #303030);\n box-shadow: 0 1px 2px rgba(0, 0, 0, 0.4);\n }\n}\n\n/* ─── Reduced motion ───────────────────────────────────────────────────────── */\n\n@media (prefers-reduced-motion: reduce) {\n .item {\n transition: none;\n }\n}\n","import {\n useRef,\n type HTMLAttributes,\n type KeyboardEvent,\n type ReactNode,\n} from \"react\";\nimport styles from \"./ViewSwitcher.module.css\";\n\nexport interface ViewSwitcherProps extends HTMLAttributes<HTMLDivElement> {\n children?: ReactNode;\n /** Accessible label for the group. */\n \"aria-label\"?: string;\n}\n\n/**\n * Segmented control for switching between major views.\n *\n * Mirrors the Adwaita `AdwViewSwitcher` pattern. Place in a `HeaderBar`\n * as the `title` for the canonical GNOME layout.\n *\n * Compose with `ViewSwitcherItem` for each option.\n * Keyboard: ← / → cycle through items, Home / End jump to first / last.\n *\n * @see https://gnome.pages.gitlab.gnome.org/libadwaita/doc/main/class.ViewSwitcher.html\n * @see https://developer.gnome.org/hig/patterns/nav/view-switchers.html\n */\nexport function ViewSwitcher({\n children,\n className,\n \"aria-label\": ariaLabel = \"View switcher\",\n ...props\n}: ViewSwitcherProps) {\n const groupRef = useRef<HTMLDivElement>(null);\n\n function handleKeyDown(e: KeyboardEvent<HTMLDivElement>) {\n const items = Array.from(\n groupRef.current?.querySelectorAll<HTMLButtonElement>(\n \"[role=radio]:not(:disabled)\",\n ) ?? [],\n );\n const idx = items.findIndex((el) => el === document.activeElement);\n if (idx === -1) return;\n\n let next = idx;\n if (e.key === \"ArrowRight\" || e.key === \"ArrowDown\")\n next = (idx + 1) % items.length;\n else if (e.key === \"ArrowLeft\" || e.key === \"ArrowUp\")\n next = (idx - 1 + items.length) % items.length;\n else if (e.key === \"Home\") next = 0;\n else if (e.key === \"End\") next = items.length - 1;\n else return;\n\n e.preventDefault();\n items[next].focus();\n items[next].click();\n }\n\n return (\n <div\n ref={groupRef}\n role=\"radiogroup\"\n aria-label={ariaLabel}\n onKeyDown={handleKeyDown}\n className={[styles.switcher, className].filter(Boolean).join(\" \")}\n {...props}\n >\n {children}\n </div>\n );\n}\n","import type { ButtonHTMLAttributes } from \"react\";\nimport type { IconDefinition } from \"@gnome-ui/icons\";\nimport { Icon } from \"../Icon\";\nimport styles from \"./ViewSwitcher.module.css\";\n\nexport interface ViewSwitcherItemProps\n extends ButtonHTMLAttributes<HTMLButtonElement> {\n /** Display label. */\n label: string;\n /** Optional icon from `@gnome-ui/icons`. */\n icon?: IconDefinition;\n /** Marks this item as the currently active view. */\n active?: boolean;\n}\n\n/**\n * Individual option inside a `ViewSwitcher`.\n *\n * Renders as `role=\"radio\"` so the group has proper radiogroup semantics.\n */\nexport function ViewSwitcherItem({\n label,\n icon,\n active = false,\n className,\n disabled,\n ...props\n}: ViewSwitcherItemProps) {\n return (\n <button\n type=\"button\"\n role=\"radio\"\n aria-checked={active}\n tabIndex={active ? 0 : -1}\n disabled={disabled}\n className={[\n styles.item,\n active ? styles.active : null,\n className,\n ]\n .filter(Boolean)\n .join(\" \")}\n {...props}\n >\n {icon && (\n <span className={styles.itemIcon}>\n <Icon icon={icon} size=\"md\" aria-hidden />\n </span>\n )}\n <span className={styles.itemLabel}>{label}</span>\n </button>\n );\n}\n","/* ─── Toaster (portal container) ────────────────────────────────────────────── */\n\n.toaster {\n position: fixed;\n left: 50%;\n transform: translateX(-50%);\n z-index: 9999;\n\n display: flex;\n flex-direction: column;\n align-items: center;\n gap: var(--gnome-space-1, 6px);\n\n /* Don't intercept pointer events in empty space */\n pointer-events: none;\n}\n\n.toasterBottom {\n bottom: var(--gnome-space-4, 24px);\n}\n\n.toasterTop {\n top: var(--gnome-space-4, 24px);\n}\n\n/* ─── Toast card ─────────────────────────────────────────────────────────────── */\n\n.toast {\n pointer-events: auto;\n\n display: flex;\n align-items: center;\n gap: var(--gnome-space-2, 12px);\n\n padding: 10px var(--gnome-space-2, 12px) 10px var(--gnome-space-3, 16px);\n min-height: 44px;\n max-width: 480px;\n min-width: 240px;\n\n background-color: var(--gnome-card-bg-color, #ffffff);\n color: var(--gnome-window-fg-color, rgba(0, 0, 0, 0.8));\n\n border-radius: var(--gnome-radius-lg, 12px);\n box-shadow:\n 0 2px 8px rgba(0, 0, 0, 0.15),\n 0 0 0 1px rgba(0, 0, 0, 0.08);\n\n font-family: var(--gnome-font-family);\n font-size: var(--gnome-font-size-body, 1rem);\n line-height: var(--gnome-line-height-body, 1.5);\n\n /* Slide in from bottom */\n animation: toast-in var(--gnome-duration-normal, 200ms) var(--gnome-easing-default, ease) both;\n}\n\n@keyframes toast-in {\n from {\n opacity: 0;\n transform: translateY(12px) scale(0.96);\n }\n to {\n opacity: 1;\n transform: translateY(0) scale(1);\n }\n}\n\n/* ─── Title ──────────────────────────────────────────────────────────────────── */\n\n.title {\n flex: 1;\n min-width: 0;\n}\n\n/* ─── Actions ────────────────────────────────────────────────────────────────── */\n\n.actions {\n display: flex;\n align-items: center;\n gap: var(--gnome-space-1, 6px);\n flex-shrink: 0;\n}\n\n/* ─── Action button ──────────────────────────────────────────────────────────── */\n\n.actionBtn {\n appearance: none;\n border: none;\n background: transparent;\n font: inherit;\n font-weight: var(--gnome-font-weight-semibold, 600);\n color: var(--gnome-accent-color, #3584e4);\n cursor: pointer;\n padding: 4px var(--gnome-space-1, 6px);\n border-radius: var(--gnome-radius-sm, 6px);\n\n transition: background-color var(--gnome-duration-fast, 100ms) var(--gnome-easing-default, ease);\n}\n\n.actionBtn:hover {\n background-color: color-mix(in srgb, var(--gnome-accent-color, #3584e4) 10%, transparent);\n}\n\n.actionBtn:active {\n background-color: color-mix(in srgb, var(--gnome-accent-color, #3584e4) 18%, transparent);\n}\n\n.actionBtn:focus {\n outline: none;\n}\n\n.actionBtn:focus-visible {\n outline: var(--gnome-focus-ring-width, 3px) solid var(--gnome-focus-ring-color, #3584e4);\n outline-offset: var(--gnome-focus-ring-offset, 2px);\n}\n\n/* ─── Dismiss button ─────────────────────────────────────────────────────────── */\n\n.dismissBtn {\n appearance: none;\n border: none;\n background: transparent;\n cursor: pointer;\n display: flex;\n align-items: center;\n justify-content: center;\n width: 28px;\n height: 28px;\n padding: 0;\n border-radius: var(--gnome-radius-sm, 6px);\n color: var(--gnome-window-fg-color, rgba(0, 0, 0, 0.8));\n opacity: 0.55;\n\n transition:\n opacity var(--gnome-duration-fast, 100ms) var(--gnome-easing-default, ease),\n background-color var(--gnome-duration-fast, 100ms) var(--gnome-easing-default, ease);\n}\n\n.dismissBtn:hover {\n opacity: 1;\n background-color: color-mix(in srgb, currentColor 10%, transparent);\n}\n\n.dismissBtn:active {\n background-color: color-mix(in srgb, currentColor 18%, transparent);\n}\n\n.dismissBtn:focus {\n outline: none;\n}\n\n.dismissBtn:focus-visible {\n opacity: 1;\n outline: var(--gnome-focus-ring-width, 3px) solid var(--gnome-focus-ring-color, #3584e4);\n outline-offset: var(--gnome-focus-ring-offset, 2px);\n}\n\n/* ─── Dark mode ──────────────────────────────────────────────────────────────── */\n\n@media (prefers-color-scheme: dark) {\n .toast {\n background-color: var(--gnome-card-bg-color, #383838);\n box-shadow:\n 0 2px 12px rgba(0, 0, 0, 0.4),\n 0 0 0 1px rgba(255, 255, 255, 0.08);\n }\n}\n\n/* ─── Reduced motion ─────────────────────────────────────────────────────────── */\n\n@media (prefers-reduced-motion: reduce) {\n .toast {\n animation: none;\n }\n\n .actionBtn,\n .dismissBtn {\n transition: none;\n }\n}\n","import {\n useEffect,\n useRef,\n type HTMLAttributes,\n type ReactNode,\n} from \"react\";\nimport { Close } from \"@gnome-ui/icons\";\nimport { Icon } from \"../Icon\";\nimport styles from \"./Toast.module.css\";\n\nexport interface ToastProps extends Omit<HTMLAttributes<HTMLDivElement>, \"title\"> {\n /** The notification message. Keep it short — one sentence. */\n title: ReactNode;\n /**\n * Auto-dismiss timeout in milliseconds.\n * - Set to `0` to disable auto-dismiss (user must click action or dismiss).\n * - Defaults to `3000`.\n */\n duration?: number;\n /** Called when the toast should be removed — after timeout or user action. */\n onDismiss?: () => void;\n /** Label for the optional action button. */\n actionLabel?: string;\n /** Called when the user clicks the action button. */\n onAction?: () => void;\n /** Whether to show a manual dismiss (×) button. */\n dismissible?: boolean;\n}\n\n/**\n * Non-blocking temporary notification following the Adwaita `AdwToast` pattern.\n *\n * - Auto-dismisses after `duration` ms (default 3 s). Set `duration={0}` to disable.\n * - Timer pauses while the toast is hovered or focused.\n * - Use inside `<Toaster>` for correct positioning and stacking.\n *\n * @see https://gnome.pages.gitlab.gnome.org/libadwaita/doc/main/class.Toast.html\n */\nexport function Toast({\n title,\n duration = 3000,\n onDismiss,\n actionLabel,\n onAction,\n dismissible = false,\n className,\n ...props\n}: ToastProps) {\n const timerRef = useRef<ReturnType<typeof setTimeout> | null>(null);\n const remainingRef = useRef(duration);\n const startedAtRef = useRef<number>(0);\n\n const clearTimer = () => {\n if (timerRef.current) {\n clearTimeout(timerRef.current);\n timerRef.current = null;\n }\n };\n\n const startTimer = (ms: number) => {\n if (ms <= 0 || !onDismiss) return;\n clearTimer();\n startedAtRef.current = Date.now();\n timerRef.current = setTimeout(() => onDismiss(), ms);\n };\n\n // Start auto-dismiss on mount\n useEffect(() => {\n if (duration > 0) {\n remainingRef.current = duration;\n startTimer(duration);\n }\n return clearTimer;\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [duration]);\n\n // Pause timer on hover/focus\n const handleMouseEnter = () => {\n if (timerRef.current) {\n const elapsed = Date.now() - startedAtRef.current;\n remainingRef.current = Math.max(0, remainingRef.current - elapsed);\n clearTimer();\n }\n };\n\n const handleMouseLeave = () => {\n startTimer(remainingRef.current);\n };\n\n const handleAction = () => {\n clearTimer();\n onAction?.();\n onDismiss?.();\n };\n\n return (\n <div\n role=\"status\"\n aria-live=\"polite\"\n aria-atomic=\"true\"\n className={[styles.toast, className].filter(Boolean).join(\" \")}\n onMouseEnter={handleMouseEnter}\n onMouseLeave={handleMouseLeave}\n onFocus={handleMouseEnter}\n onBlur={handleMouseLeave}\n {...props}\n >\n <span className={styles.title}>{title}</span>\n\n {(actionLabel || dismissible) && (\n <span className={styles.actions}>\n {actionLabel && (\n <button\n type=\"button\"\n className={styles.actionBtn}\n onClick={handleAction}\n >\n {actionLabel}\n </button>\n )}\n {dismissible && (\n <button\n type=\"button\"\n className={styles.dismissBtn}\n aria-label=\"Dismiss\"\n onClick={() => { clearTimer(); onDismiss?.(); }}\n >\n <Icon icon={Close} size=\"md\" aria-hidden />\n </button>\n )}\n </span>\n )}\n </div>\n );\n}\n","import { createPortal } from \"react-dom\";\nimport type { HTMLAttributes, ReactNode } from \"react\";\nimport styles from \"./Toast.module.css\";\n\nexport interface ToasterProps extends HTMLAttributes<HTMLDivElement> {\n /**\n * Where to render the toast stack.\n * - `bottom` (default) — bottom-center, following the GNOME HIG.\n * - `top` — top-center.\n */\n position?: \"bottom\" | \"top\";\n /** `<Toast>` elements to display. */\n children?: ReactNode;\n /**\n * DOM node to render the portal into.\n * Defaults to `document.body`.\n */\n container?: Element | DocumentFragment;\n}\n\n/**\n * Fixed-position overlay that stacks `<Toast>` notifications.\n *\n * Renders into a portal (default: `document.body`) so it sits above all\n * other content regardless of where you place it in the React tree.\n *\n * @example\n * <Toaster>\n * {toasts.map(t => (\n * <Toast key={t.id} title={t.message} onDismiss={() => remove(t.id)} />\n * ))}\n * </Toaster>\n */\nexport function Toaster({\n position = \"bottom\",\n children,\n container,\n className,\n ...props\n}: ToasterProps) {\n const node = (\n <div\n aria-label=\"Notifications\"\n className={[\n styles.toaster,\n position === \"top\" ? styles.toasterTop : styles.toasterBottom,\n className,\n ]\n .filter(Boolean)\n .join(\" \")}\n {...props}\n >\n {children}\n </div>\n );\n\n // During SSR document is not available — render inline\n if (typeof document === \"undefined\") return node;\n\n return createPortal(node, container ?? document.body);\n}\n","/* ─── Backdrop ──────────────────────────────────────────────────────────────── */\n\n.backdrop {\n position: fixed;\n inset: 0;\n z-index: 10000;\n\n display: flex;\n align-items: center;\n justify-content: center;\n\n background-color: rgba(0, 0, 0, 0.5);\n backdrop-filter: blur(4px);\n -webkit-backdrop-filter: blur(4px);\n\n animation: backdrop-in var(--gnome-duration-normal, 200ms) var(--gnome-easing-default, ease) both;\n}\n\n@keyframes backdrop-in {\n from { opacity: 0; }\n to { opacity: 1; }\n}\n\n/* ─── Dialog card ───────────────────────────────────────────────────────────── */\n\n.dialog {\n position: relative;\n width: min(480px, calc(100vw - 48px));\n\n background-color: var(--gnome-dialog-bg-color, var(--gnome-window-bg-color, #fafafa));\n color: var(--gnome-window-fg-color, rgba(0, 0, 0, 0.8));\n\n border-radius: var(--gnome-radius-xl, 16px);\n box-shadow:\n 0 8px 32px rgba(0, 0, 0, 0.25),\n 0 0 0 1px rgba(0, 0, 0, 0.08);\n\n overflow: hidden;\n\n animation: dialog-in var(--gnome-duration-normal, 200ms) var(--gnome-easing-default, ease) both;\n}\n\n@keyframes dialog-in {\n from {\n opacity: 0;\n transform: scale(0.92) translateY(-8px);\n }\n to {\n opacity: 1;\n transform: scale(1) translateY(0);\n }\n}\n\n/* ─── Title ─────────────────────────────────────────────────────────────────── */\n\n.title {\n padding: var(--gnome-space-4, 24px) var(--gnome-space-4, 24px) 0;\n\n font-family: var(--gnome-font-family);\n font-size: var(--gnome-font-size-title-4, 1.125rem);\n font-weight: var(--gnome-font-weight-bold, 700);\n line-height: var(--gnome-line-height-title, 1.3);\n color: var(--gnome-window-fg-color, rgba(0, 0, 0, 0.8));\n\n text-align: center;\n}\n\n/* ─── Body ──────────────────────────────────────────────────────────────────── */\n\n.body {\n padding: var(--gnome-space-2, 12px) var(--gnome-space-4, 24px) var(--gnome-space-3, 16px);\n\n font-family: var(--gnome-font-family);\n font-size: var(--gnome-font-size-body, 1rem);\n font-weight: var(--gnome-font-weight-normal, 400);\n line-height: var(--gnome-line-height-body, 1.5);\n color: var(--gnome-window-fg-color, rgba(0, 0, 0, 0.8));\n opacity: var(--gnome-opacity-dim, 0.7);\n\n text-align: center;\n}\n\n/* ─── Footer (buttons) ──────────────────────────────────────────────────────── */\n\n.footer {\n display: flex;\n flex-direction: column;\n border-top: 1px solid var(--gnome-card-shade-color, rgba(0, 0, 0, 0.1));\n}\n\n/* Each button gets a full-width row separated by hairlines */\n.btn {\n appearance: none;\n border: none;\n border-bottom: 1px solid var(--gnome-card-shade-color, rgba(0, 0, 0, 0.08));\n background: transparent;\n width: 100%;\n padding: 14px var(--gnome-space-4, 24px);\n\n font-family: var(--gnome-font-family);\n font-size: var(--gnome-font-size-body, 1rem);\n font-weight: var(--gnome-font-weight-normal, 400);\n line-height: var(--gnome-line-height-body, 1.5);\n color: var(--gnome-accent-color, #3584e4);\n\n cursor: pointer;\n text-align: center;\n\n transition: background-color var(--gnome-duration-fast, 100ms) var(--gnome-easing-default, ease);\n}\n\n.btn:last-child {\n border-bottom: none;\n}\n\n.btn:hover:not(:disabled) {\n background-color: color-mix(in srgb, currentColor 6%, transparent);\n}\n\n.btn:active:not(:disabled) {\n background-color: color-mix(in srgb, currentColor 12%, transparent);\n}\n\n.btn:focus {\n outline: none;\n}\n\n.btn:focus-visible {\n outline: var(--gnome-focus-ring-width, 3px) solid var(--gnome-focus-ring-color, #3584e4);\n outline-offset: -3px;\n}\n\n.btn:disabled {\n opacity: var(--gnome-opacity-disabled, 0.5);\n cursor: not-allowed;\n}\n\n/* ─── Button variants ───────────────────────────────────────────────────────── */\n\n.btn-default {\n color: var(--gnome-accent-color, #3584e4);\n}\n\n.btn-suggested {\n color: var(--gnome-accent-color, #3584e4);\n font-weight: var(--gnome-font-weight-semibold, 600);\n}\n\n.btn-destructive {\n color: var(--gnome-destructive-color, #e01b24);\n}\n\n/* ─── About variant ─────────────────────────────────────────────────────────── */\n\n.dialogAbout {\n max-height: min(640px, 90dvh);\n display: flex;\n flex-direction: column;\n}\n\n/* Header */\n.aboutHeader {\n display: flex;\n flex-direction: column;\n align-items: center;\n gap: 4px;\n padding: var(--gnome-space-4, 24px) var(--gnome-space-4, 24px) var(--gnome-space-3, 16px);\n text-align: center;\n}\n\n.aboutIcon {\n width: 64px;\n height: 64px;\n margin-bottom: var(--gnome-space-2, 12px);\n display: flex;\n align-items: center;\n justify-content: center;\n}\n\n.aboutAppName {\n font-family: var(--gnome-font-family);\n font-size: var(--gnome-font-size-title-3, 1.25rem);\n font-weight: var(--gnome-font-weight-bold, 700);\n line-height: 1.3;\n}\n\n.aboutVersion {\n font-family: var(--gnome-font-family);\n font-size: var(--gnome-font-size-body, 1rem);\n opacity: var(--gnome-opacity-dim, 0.55);\n}\n\n/* Tab bar */\n.aboutTabBar {\n display: flex;\n border-bottom: 1px solid var(--gnome-card-shade-color, rgba(0, 0, 0, 0.1));\n padding: 0 var(--gnome-space-3, 16px);\n gap: 4px;\n}\n\n.aboutTabBtn {\n appearance: none;\n border: none;\n background: transparent;\n cursor: pointer;\n padding: 8px var(--gnome-space-2, 12px);\n border-bottom: 2px solid transparent;\n margin-bottom: -1px;\n\n font-family: var(--gnome-font-family);\n font-size: var(--gnome-font-size-body, 1rem);\n color: var(--gnome-window-fg-color, rgba(0, 0, 0, 0.8));\n opacity: 0.6;\n transition: opacity var(--gnome-duration-fast) var(--gnome-easing-default),\n border-color var(--gnome-duration-fast) var(--gnome-easing-default);\n}\n\n.aboutTabBtn:hover { opacity: 1; }\n\n.aboutTabBtnActive {\n opacity: 1;\n font-weight: var(--gnome-font-weight-semibold, 600);\n border-bottom-color: var(--gnome-accent-color, #3584e4);\n}\n\n.aboutTabBtn:focus-visible {\n outline: 2px solid var(--gnome-focus-ring-color, #3584e4);\n outline-offset: 2px;\n border-radius: 4px;\n}\n\n/* Tab content */\n.aboutTabContent {\n flex: 1;\n overflow-y: auto;\n}\n\n.aboutSection {\n padding: var(--gnome-space-3, 16px) var(--gnome-space-4, 24px);\n}\n\n/* Details */\n.aboutComments {\n margin: 0 0 var(--gnome-space-3, 16px);\n font-family: var(--gnome-font-family);\n font-size: var(--gnome-font-size-body, 1rem);\n line-height: 1.5;\n text-align: center;\n opacity: var(--gnome-opacity-dim, 0.7);\n}\n\n.aboutInfoList {\n display: grid;\n grid-template-columns: auto 1fr;\n gap: 6px var(--gnome-space-3, 16px);\n margin: 0;\n}\n\n.aboutInfoLabel {\n font-family: var(--gnome-font-family);\n font-size: var(--gnome-font-size-body, 1rem);\n font-weight: var(--gnome-font-weight-semibold, 600);\n opacity: 0.55;\n white-space: nowrap;\n}\n\n.aboutInfoValue {\n font-family: var(--gnome-font-family);\n font-size: var(--gnome-font-size-body, 1rem);\n margin: 0;\n min-width: 0;\n overflow-wrap: break-word;\n}\n\n.aboutLink {\n color: var(--gnome-accent-color, #3584e4);\n text-decoration: none;\n}\n\n.aboutLink:hover { text-decoration: underline; }\n\n/* Credits */\n.aboutCreditGroup + .aboutCreditGroup {\n margin-top: var(--gnome-space-3, 16px);\n}\n\n.aboutCreditTitle {\n margin: 0 0 6px;\n font-family: var(--gnome-font-family);\n font-size: var(--gnome-font-size-caption, 0.75rem);\n font-weight: var(--gnome-font-weight-semibold, 600);\n text-transform: uppercase;\n letter-spacing: 0.06em;\n opacity: 0.5;\n}\n\n.aboutCreditList {\n list-style: none;\n margin: 0;\n padding: 0;\n display: flex;\n flex-direction: column;\n gap: 2px;\n}\n\n.aboutCreditItem {\n font-family: var(--gnome-font-family);\n font-size: var(--gnome-font-size-body, 1rem);\n}\n\n/* Legal */\n.aboutCopyright {\n margin: 0 0 var(--gnome-space-2, 12px);\n font-family: var(--gnome-font-family);\n font-size: var(--gnome-font-size-body, 1rem);\n opacity: var(--gnome-opacity-dim, 0.7);\n}\n\n.aboutLicenseType {\n margin: 0 0 var(--gnome-space-2, 12px);\n font-family: var(--gnome-font-family);\n font-size: var(--gnome-font-size-body, 1rem);\n font-weight: var(--gnome-font-weight-semibold, 600);\n}\n\n.aboutLicenseText {\n margin: 0;\n padding: var(--gnome-space-2, 12px);\n border-radius: var(--gnome-radius-md, 8px);\n background: var(--gnome-card-bg-color, rgba(0, 0, 0, 0.04));\n font-family: monospace;\n font-size: 0.8rem;\n line-height: 1.5;\n white-space: pre-wrap;\n overflow-wrap: break-word;\n}\n\n/* ─── Dark mode ─────────────────────────────────────────────────────────────── */\n\n@media (prefers-color-scheme: dark) {\n .backdrop {\n background-color: rgba(0, 0, 0, 0.65);\n }\n\n .dialog {\n background-color: var(--gnome-dialog-bg-color, var(--gnome-window-bg-color, #242424));\n box-shadow:\n 0 8px 32px rgba(0, 0, 0, 0.5),\n 0 0 0 1px rgba(255, 255, 255, 0.08);\n }\n\n .footer {\n border-top-color: rgba(255, 255, 255, 0.08);\n }\n\n .btn {\n border-bottom-color: rgba(255, 255, 255, 0.06);\n }\n}\n\n/* ─── Reduced motion ────────────────────────────────────────────────────────── */\n\n@media (prefers-reduced-motion: reduce) {\n .backdrop,\n .dialog {\n animation: none;\n }\n\n .btn {\n transition: none;\n }\n}\n","import {\n useEffect,\n useRef,\n useCallback,\n useId,\n useState,\n type KeyboardEvent,\n type HTMLAttributes,\n type ReactNode,\n} from \"react\";\nimport { createPortal } from \"react-dom\";\nimport styles from \"./Dialog.module.css\";\n\n// ─── Shared focus trap ────────────────────────────────────────────────────────\n\nconst FOCUSABLE =\n 'button:not([disabled]), [href], input:not([disabled]), select:not([disabled]), textarea:not([disabled]), [tabindex]:not([tabindex=\"-1\"])';\n\nfunction trapFocus(e: KeyboardEvent<HTMLDivElement>, ref: React.RefObject<HTMLDivElement | null>) {\n if (e.key !== \"Tab\") return;\n const focusable = Array.from(ref.current?.querySelectorAll<HTMLElement>(FOCUSABLE) ?? []);\n if (!focusable.length) return;\n const first = focusable[0];\n const last = focusable[focusable.length - 1];\n if (e.shiftKey) {\n if (document.activeElement === first) { e.preventDefault(); last.focus(); }\n } else {\n if (document.activeElement === last) { e.preventDefault(); first.focus(); }\n }\n}\n\n// ─── AlertDialog types ────────────────────────────────────────────────────────\n\nexport type AlertDialogResponseVariant = \"default\" | \"suggested\" | \"destructive\";\n\nexport interface AlertDialogResponse {\n /**\n * Unique identifier returned via `onResponse`.\n * Use `\"cancel\"` by convention for the dismissive action.\n */\n id: string;\n /** Button label. */\n label: string;\n /** Visual emphasis. Defaults to `\"default\"`. */\n variant?: AlertDialogResponseVariant;\n /** Disables the button. */\n disabled?: boolean;\n}\n\n// ─── AboutDialog types ────────────────────────────────────────────────────────\n\nexport interface AboutDialogLink {\n label: string;\n url: string;\n}\n\n// ─── Dialog ───────────────────────────────────────────────────────────────────\n\nexport interface DialogButton {\n /** Button label. */\n label: string;\n /** Visual variant. Defaults to `\"default\"`. */\n variant?: \"default\" | \"suggested\" | \"destructive\";\n /** Called when the button is clicked. */\n onClick: () => void;\n /** Whether the button is disabled. */\n disabled?: boolean;\n}\n\nexport interface DialogProps extends Omit<HTMLAttributes<HTMLDivElement>, \"title\"> {\n /** Whether the dialog is visible. */\n open: boolean;\n\n // ── Standard dialog ──────────────────────────────────────────────────────\n\n /** Dialog heading. */\n title?: ReactNode;\n /** Body content. */\n children?: ReactNode;\n /** Action buttons (standard dialog API). */\n buttons?: DialogButton[];\n /** Called on Escape or backdrop click. */\n onClose?: () => void;\n /** Whether clicking the backdrop closes the dialog. Defaults to `true`. */\n closeOnBackdrop?: boolean;\n\n // ── AlertDialog extension ─────────────────────────────────────────────────\n\n /**\n * ARIA role. Use `\"alertdialog\"` for confirmations and destructive warnings —\n * screen readers announce it immediately. Defaults to `\"dialog\"`.\n *\n * When using `role=\"alertdialog\"`, prefer the `responses`/`onResponse` API\n * over `buttons`/`onClick` for semantic clarity.\n */\n role?: \"dialog\" | \"alertdialog\";\n\n /**\n * Response buttons (AlertDialog API). Alternative to `buttons` — each\n * response has a semantic `id` returned via `onResponse`.\n * Escape and backdrop click fire the first non-destructive response.\n */\n responses?: AlertDialogResponse[];\n\n /**\n * Called with the `id` of the response button clicked.\n * Required when `responses` is provided.\n */\n onResponse?: (id: string) => void;\n\n // ── AboutDialog extension ─────────────────────────────────────────────────\n\n /**\n * Set to `\"about\"` to render as an application info dialog.\n * When `variant=\"about\"`, `title`/`buttons`/`children` are ignored in favour\n * of the about-specific props below.\n */\n variant?: \"about\";\n\n /** Application name (required when `variant=\"about\"`). */\n applicationName?: string;\n /** Icon rendered above the app name. */\n applicationIcon?: ReactNode;\n /** Version string (e.g. `\"1.0.0\"`). */\n version?: string;\n /** One-sentence description. */\n comments?: string;\n /** Developer or organisation name. */\n developerName?: string;\n /** Application website URL. */\n website?: string;\n /** Label for the website link. Defaults to the URL. */\n websiteLabel?: string;\n /** Developer names shown in the Credits tab. */\n developers?: string[];\n /** Designer names shown in the Credits tab. */\n designers?: string[];\n /** Artist names shown in the Credits tab. */\n artists?: string[];\n /** Copyright notice (e.g. `\"© 2024 Jane Smith\"`). */\n copyright?: string;\n /** SPDX identifier or short name (e.g. `\"GPL-3.0\"`). */\n licenseType?: string;\n /** Full license text shown in the Legal tab. */\n licenseText?: string;\n /** Extra links shown in the Details section. */\n links?: AboutDialogLink[];\n}\n\n// ─── About tab internals ──────────────────────────────────────────────────────\n\ntype AboutTab = \"details\" | \"credits\" | \"legal\";\nconst ABOUT_TAB_LABEL: Record<AboutTab, string> = {\n details: \"Details\",\n credits: \"Credits\",\n legal: \"Legal\",\n};\n\n/**\n * Blocking modal dialog following the Adwaita pattern.\n *\n * Three modes in one component:\n *\n * **Standard** — `title` + `children` + `buttons[]`.\n *\n * **Alert** — add `role=\"alertdialog\"` + `responses[]` + `onResponse`.\n * Uses a semantic response id instead of per-button `onClick`.\n * Escape / backdrop fire the first non-destructive response.\n * Mirrors `AdwAlertDialog`.\n *\n * **About** — add `variant=\"about\"` + `applicationName` + info props.\n * Renders app icon, version, details/credits/legal tabs.\n * Mirrors `AdwAboutDialog`.\n *\n * All modes share the same portal, focus-trap, and card/backdrop styles.\n *\n * @see https://gnome.pages.gitlab.gnome.org/libadwaita/doc/main/class.Dialog.html\n * @see https://gnome.pages.gitlab.gnome.org/libadwaita/doc/main/class.AlertDialog.html\n * @see https://gnome.pages.gitlab.gnome.org/libadwaita/doc/main/class.AboutDialog.html\n */\nexport function Dialog({\n open,\n title,\n children,\n buttons = [],\n onClose,\n closeOnBackdrop = true,\n role = \"dialog\",\n responses,\n onResponse,\n variant,\n applicationName,\n applicationIcon,\n version,\n comments,\n developerName,\n website,\n websiteLabel,\n developers,\n designers,\n artists,\n copyright,\n licenseType,\n licenseText,\n links,\n className,\n ...props\n}: DialogProps) {\n const dialogRef = useRef<HTMLDivElement>(null);\n const titleId = useId();\n const previouslyFocused = useRef<Element | null>(null);\n const [aboutTab, setAboutTab] = useState<AboutTab>(\"details\");\n\n const isAlert = !!responses;\n const isAbout = variant === \"about\";\n\n // ── Focus management ───────────────────────────────────────────────────────\n useEffect(() => {\n if (open) {\n previouslyFocused.current = document.activeElement;\n const el = dialogRef.current?.querySelector<HTMLElement>(FOCUSABLE);\n el?.focus();\n } else {\n (previouslyFocused.current as HTMLElement | null)?.focus();\n if (isAbout) setAboutTab(\"details\");\n }\n }, [open, isAbout]);\n\n // ── Dismiss helpers ────────────────────────────────────────────────────────\n const dismissAlert = useCallback(() => {\n const cancel = responses?.find((r) => r.variant !== \"destructive\" && !r.disabled);\n if (cancel) onResponse?.(cancel.id);\n }, [responses, onResponse]);\n\n const handleBackdrop = isAlert ? dismissAlert : closeOnBackdrop ? onClose : undefined;\n\n // ── Keyboard ───────────────────────────────────────────────────────────────\n const handleKeyDown = useCallback(\n (e: KeyboardEvent<HTMLDivElement>) => {\n if (e.key === \"Escape\") {\n e.preventDefault();\n if (isAlert) dismissAlert();\n else onClose?.();\n return;\n }\n trapFocus(e, dialogRef);\n },\n [isAlert, dismissAlert, onClose]\n );\n\n if (!open) return null;\n\n // ── About tabs ─────────────────────────────────────────────────────────────\n const hasCredits = !!(developers?.length || designers?.length || artists?.length);\n const hasLegal = !!(copyright || licenseType || licenseText);\n const aboutTabs = (\n [\"details\", hasCredits && \"credits\", hasLegal && \"legal\"] as (AboutTab | false)[]\n ).filter(Boolean) as AboutTab[];\n\n // ── Render buttons (standard or alert) ────────────────────────────────────\n const renderButtons = () => {\n if (isAbout) {\n return (\n <div className={styles.footer}>\n <button type=\"button\" className={[styles.btn, styles[\"btn-default\"]].join(\" \")} onClick={onClose}>\n Close\n </button>\n </div>\n );\n }\n if (isAlert && responses) {\n return (\n <div className={styles.footer}>\n {responses.map((r) => (\n <button\n key={r.id}\n type=\"button\"\n disabled={r.disabled}\n className={[styles.btn, styles[`btn-${r.variant ?? \"default\"}`]].filter(Boolean).join(\" \")}\n onClick={() => onResponse?.(r.id)}\n >\n {r.label}\n </button>\n ))}\n </div>\n );\n }\n if (buttons.length > 0) {\n return (\n <div className={styles.footer}>\n {buttons.map((btn) => (\n <button\n key={btn.label}\n type=\"button\"\n disabled={btn.disabled}\n className={[styles.btn, styles[`btn-${btn.variant ?? \"default\"}`]].filter(Boolean).join(\" \")}\n onClick={btn.onClick}\n >\n {btn.label}\n </button>\n ))}\n </div>\n );\n }\n return null;\n };\n\n // ── About content ──────────────────────────────────────────────────────────\n const renderAboutContent = () => (\n <>\n <div className={styles.aboutHeader}>\n {applicationIcon && <div className={styles.aboutIcon}>{applicationIcon}</div>}\n <div id={titleId} className={styles.aboutAppName}>{applicationName}</div>\n {version && <div className={styles.aboutVersion}>{version}</div>}\n </div>\n\n {aboutTabs.length > 1 && (\n <div className={styles.aboutTabBar} role=\"tablist\">\n {aboutTabs.map((t) => (\n <button\n key={t}\n type=\"button\"\n role=\"tab\"\n aria-selected={aboutTab === t}\n className={[styles.aboutTabBtn, aboutTab === t ? styles.aboutTabBtnActive : null]\n .filter(Boolean).join(\" \")}\n onClick={() => setAboutTab(t)}\n >\n {ABOUT_TAB_LABEL[t]}\n </button>\n ))}\n </div>\n )}\n\n <div className={styles.aboutTabContent}>\n {aboutTab === \"details\" && (\n <div className={styles.aboutSection}>\n {comments && <p className={styles.aboutComments}>{comments}</p>}\n {(developerName || website || links?.length) && (\n <dl className={styles.aboutInfoList}>\n {developerName && (\n <><dt className={styles.aboutInfoLabel}>Developer</dt><dd className={styles.aboutInfoValue}>{developerName}</dd></>\n )}\n {website && (\n <><dt className={styles.aboutInfoLabel}>Website</dt><dd className={styles.aboutInfoValue}>\n <a href={website} target=\"_blank\" rel=\"noopener noreferrer\" className={styles.aboutLink}>\n {websiteLabel ?? website}\n </a>\n </dd></>\n )}\n {links?.map((l) => (\n <><dt key={`${l.label}-dt`} className={styles.aboutInfoLabel}>{l.label}</dt>\n <dd key={`${l.label}-dd`} className={styles.aboutInfoValue}>\n <a href={l.url} target=\"_blank\" rel=\"noopener noreferrer\" className={styles.aboutLink}>{l.url}</a>\n </dd></>\n ))}\n </dl>\n )}\n </div>\n )}\n\n {aboutTab === \"credits\" && (\n <div className={styles.aboutSection}>\n {[\n { heading: \"Developers\", list: developers },\n { heading: \"Designers\", list: designers },\n { heading: \"Artists\", list: artists },\n ].filter((g) => g.list?.length).map(({ heading, list }) => (\n <div key={heading} className={styles.aboutCreditGroup}>\n <h4 className={styles.aboutCreditTitle}>{heading}</h4>\n <ul className={styles.aboutCreditList}>\n {list!.map((name) => <li key={name} className={styles.aboutCreditItem}>{name}</li>)}\n </ul>\n </div>\n ))}\n </div>\n )}\n\n {aboutTab === \"legal\" && (\n <div className={styles.aboutSection}>\n {copyright && <p className={styles.aboutCopyright}>{copyright}</p>}\n {licenseType && <p className={styles.aboutLicenseType}>{licenseType}</p>}\n {licenseText && <pre className={styles.aboutLicenseText}>{licenseText}</pre>}\n </div>\n )}\n </div>\n </>\n );\n\n const node = (\n <div\n className={styles.backdrop}\n onClick={handleBackdrop}\n aria-hidden=\"true\"\n >\n <div\n ref={dialogRef}\n role={role}\n aria-modal=\"true\"\n aria-labelledby={titleId}\n className={[\n styles.dialog,\n isAbout ? styles.dialogAbout : null,\n className,\n ].filter(Boolean).join(\" \")}\n onKeyDown={handleKeyDown}\n onClick={(e) => e.stopPropagation()}\n {...props}\n >\n {isAbout ? (\n renderAboutContent()\n ) : (\n <>\n {title && <div id={titleId} className={styles.title}>{title}</div>}\n {children && <div className={styles.body}>{children}</div>}\n </>\n )}\n\n {renderButtons()}\n </div>\n </div>\n );\n\n if (typeof document === \"undefined\") return node;\n return createPortal(node, document.body);\n}\n","/* ─── Wrapper ────────────────────────────────────────────────────────────────── */\n\n.wrapper {\n position: relative;\n display: inline-block;\n min-width: 160px;\n}\n\n/* ─── Trigger button ─────────────────────────────────────────────────────────── */\n\n.trigger {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: var(--gnome-space-1, 6px);\n width: 100%;\n\n padding: 7px var(--gnome-space-2, 12px);\n min-height: 36px;\n\n appearance: none;\n background-color: var(--gnome-card-bg-color, #ffffff);\n border: 1px solid var(--gnome-card-shade-color, rgba(0, 0, 0, 0.15));\n border-radius: var(--gnome-radius-md, 8px);\n box-shadow: var(--gnome-shadow-sm, 0 1px 2px rgba(0,0,0,0.08));\n\n font-family: var(--gnome-font-family);\n font-size: var(--gnome-font-size-body, 1rem);\n font-weight: var(--gnome-font-weight-normal, 400);\n color: var(--gnome-window-fg-color, rgba(0, 0, 0, 0.8));\n\n cursor: pointer;\n user-select: none;\n text-align: left;\n\n transition:\n background-color var(--gnome-duration-fast, 100ms) var(--gnome-easing-default, ease),\n border-color var(--gnome-duration-fast, 100ms) var(--gnome-easing-default, ease),\n box-shadow var(--gnome-duration-fast, 100ms) var(--gnome-easing-default, ease);\n}\n\n.trigger:hover:not(:disabled) {\n background-color: color-mix(in srgb, var(--gnome-card-bg-color, #ffffff) 90%, black 10%);\n}\n\n.trigger:focus {\n outline: none;\n}\n\n.trigger:focus-visible {\n border-color: var(--gnome-accent-color, #3584e4);\n box-shadow:\n 0 0 0 var(--gnome-focus-ring-offset, 2px) var(--gnome-window-bg-color, #fafafa),\n 0 0 0 calc(var(--gnome-focus-ring-offset, 2px) + var(--gnome-focus-ring-width, 3px))\n var(--gnome-focus-ring-color, #3584e4);\n}\n\n.trigger:disabled {\n opacity: var(--gnome-opacity-disabled, 0.5);\n cursor: not-allowed;\n}\n\n.triggerOpen {\n border-color: var(--gnome-accent-color, #3584e4);\n box-shadow:\n 0 0 0 var(--gnome-focus-ring-offset, 2px) var(--gnome-window-bg-color, #fafafa),\n 0 0 0 calc(var(--gnome-focus-ring-offset, 2px) + var(--gnome-focus-ring-width, 3px))\n var(--gnome-focus-ring-color, #3584e4);\n}\n\n/* ─── Trigger label ──────────────────────────────────────────────────────────── */\n\n.triggerLabel {\n flex: 1;\n min-width: 0;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n.placeholder {\n opacity: var(--gnome-opacity-dim, 0.55);\n}\n\n/* ─── Chevron icon ───────────────────────────────────────────────────────────── */\n\n.chevron {\n flex-shrink: 0;\n color: var(--gnome-window-fg-color, rgba(0, 0, 0, 0.8));\n opacity: var(--gnome-opacity-dim, 0.55);\n transition: transform var(--gnome-duration-fast, 100ms) var(--gnome-easing-default, ease);\n}\n\n.chevronOpen {\n transform: rotate(180deg);\n}\n\n/* ─── Listbox ────────────────────────────────────────────────────────────────── */\n\n.list {\n position: absolute;\n left: 0;\n right: 0;\n z-index: 1000;\n\n margin: 0;\n padding: 4px 0;\n list-style: none;\n\n background-color: var(--gnome-popover-bg-color, var(--gnome-card-bg-color, #ffffff));\n border: 1px solid var(--gnome-card-shade-color, rgba(0, 0, 0, 0.12));\n border-radius: var(--gnome-radius-md, 8px);\n box-shadow:\n 0 4px 16px rgba(0, 0, 0, 0.12),\n 0 1px 3px rgba(0, 0, 0, 0.08);\n\n max-height: 280px;\n overflow-y: auto;\n overscroll-behavior: contain;\n\n animation: list-in var(--gnome-duration-fast, 100ms) var(--gnome-easing-default, ease) both;\n}\n\n@keyframes list-in {\n from { opacity: 0; transform: scaleY(0.95); transform-origin: top center; }\n to { opacity: 1; transform: scaleY(1); transform-origin: top center; }\n}\n\n.listDown {\n top: calc(100% + 4px);\n}\n\n.listUp {\n bottom: calc(100% + 4px);\n animation-name: list-in-up;\n}\n\n@keyframes list-in-up {\n from { opacity: 0; transform: scaleY(0.95); transform-origin: bottom center; }\n to { opacity: 1; transform: scaleY(1); transform-origin: bottom center; }\n}\n\n/* ─── Option ─────────────────────────────────────────────────────────────────── */\n\n.option {\n display: flex;\n align-items: center;\n gap: var(--gnome-space-2, 12px);\n padding: 8px var(--gnome-space-2, 12px);\n min-height: 40px;\n\n font-family: var(--gnome-font-family);\n font-size: var(--gnome-font-size-body, 1rem);\n color: var(--gnome-window-fg-color, rgba(0, 0, 0, 0.8));\n\n cursor: pointer;\n user-select: none;\n\n transition: background-color var(--gnome-duration-fast, 100ms) var(--gnome-easing-default, ease);\n}\n\n.optionActive {\n background-color: color-mix(in srgb, var(--gnome-accent-bg-color, #3584e4) 12%, transparent);\n}\n\n.optionSelected {\n font-weight: var(--gnome-font-weight-semibold, 600);\n}\n\n.optionDisabled {\n opacity: var(--gnome-opacity-disabled, 0.5);\n cursor: not-allowed;\n pointer-events: none;\n}\n\n/* ─── Option text block ──────────────────────────────────────────────────────── */\n\n.optionText {\n flex: 1;\n min-width: 0;\n display: flex;\n flex-direction: column;\n gap: 1px;\n}\n\n.optionLabel {\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n}\n\n.optionDesc {\n font-size: var(--gnome-font-size-caption, 0.8125rem);\n opacity: var(--gnome-opacity-dim, 0.55);\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n}\n\n/* ─── Check icon (selected) ──────────────────────────────────────────────────── */\n\n.checkIcon {\n flex-shrink: 0;\n color: var(--gnome-accent-color, #3584e4);\n}\n\n/* ─── Dark mode ──────────────────────────────────────────────────────────────── */\n\n@media (prefers-color-scheme: dark) {\n .trigger {\n background-color: var(--gnome-card-bg-color, #383838);\n border-color: rgba(255, 255, 255, 0.12);\n box-shadow: none;\n }\n\n .trigger:hover:not(:disabled) {\n background-color: rgba(255, 255, 255, 0.08);\n }\n\n .list {\n background-color: var(--gnome-card-bg-color, #383838);\n border-color: rgba(255, 255, 255, 0.1);\n box-shadow:\n 0 4px 16px rgba(0, 0, 0, 0.4),\n 0 1px 3px rgba(0, 0, 0, 0.3);\n }\n}\n\n/* ─── Reduced motion ─────────────────────────────────────────────────────────── */\n\n@media (prefers-reduced-motion: reduce) {\n .trigger,\n .chevron {\n transition: none;\n }\n\n .list {\n animation: none;\n }\n}\n","import {\n useState,\n useRef,\n useId,\n useEffect,\n useCallback,\n type KeyboardEvent,\n type HTMLAttributes,\n} from \"react\";\nimport { PanDown, Check } from \"@gnome-ui/icons\";\nimport { Icon } from \"../Icon\";\nimport styles from \"./Dropdown.module.css\";\n\nexport interface DropdownOption<V extends string = string> {\n /** The value submitted / returned on selection. */\n value: V;\n /** Display label shown in the list and trigger. */\n label: string;\n /** Optional descriptive text shown below the label. */\n description?: string;\n /** Whether the option is selectable. */\n disabled?: boolean;\n}\n\nexport interface DropdownProps<V extends string = string>\n extends Omit<HTMLAttributes<HTMLDivElement>, \"onChange\"> {\n /** The list of selectable options. */\n options: DropdownOption<V>[];\n /** The currently selected value. */\n value?: V;\n /** Called when the user selects an option. */\n onChange?: (value: V) => void;\n /** Placeholder shown when no option is selected. */\n placeholder?: string;\n /** Accessible label for the control (used as `aria-label`). */\n \"aria-label\"?: string;\n /** Disables the entire control. */\n disabled?: boolean;\n}\n\n/**\n * Expandable option list following the Adwaita combo-row / drop-down pattern.\n *\n * - Fully keyboard-navigable: Space/Enter opens; ↑/↓ navigate; Enter selects; Escape closes.\n * - Uses `role=\"combobox\"` + `role=\"listbox\"` + `role=\"option\"` ARIA pattern.\n * - Closes when clicking outside or pressing Escape.\n * - The list flips above the trigger when there is not enough space below.\n *\n * @see https://developer.gnome.org/hig/patterns/controls/drop-down-lists.html\n */\nexport function Dropdown<V extends string = string>({\n options,\n value,\n onChange,\n placeholder = \"Select an option\",\n disabled,\n className,\n ...props\n}: DropdownProps<V>) {\n const [open, setOpen] = useState(false);\n const [activeIndex, setActiveIndex] = useState<number>(-1);\n const [flipUp, setFlipUp] = useState(false);\n\n const triggerId = useId();\n const listboxId = useId();\n const triggerRef = useRef<HTMLButtonElement>(null);\n const listRef = useRef<HTMLUListElement>(null);\n\n const selected = options.find((o) => o.value === value);\n\n // Compute flip direction when opening\n const computeFlip = useCallback(() => {\n if (!triggerRef.current) return;\n const rect = triggerRef.current.getBoundingClientRect();\n const spaceBelow = window.innerHeight - rect.bottom;\n const listH = Math.min(options.length * 48 + 8, 280);\n setFlipUp(spaceBelow < listH && rect.top > listH);\n }, [options.length]);\n\n const openList = useCallback(() => {\n if (disabled) return;\n computeFlip();\n setOpen(true);\n const idx = options.findIndex((o) => o.value === value && !o.disabled);\n setActiveIndex(idx >= 0 ? idx : options.findIndex((o) => !o.disabled));\n }, [disabled, computeFlip, options, value]);\n\n const closeList = useCallback(() => {\n setOpen(false);\n setActiveIndex(-1);\n triggerRef.current?.focus();\n }, []);\n\n const selectOption = useCallback(\n (opt: DropdownOption<V>) => {\n if (opt.disabled) return;\n onChange?.(opt.value);\n closeList();\n },\n [onChange, closeList]\n );\n\n // Scroll active item into view\n useEffect(() => {\n if (!open || activeIndex < 0 || !listRef.current) return;\n const item = listRef.current.children[activeIndex] as HTMLElement | undefined;\n item?.scrollIntoView({ block: \"nearest\" });\n }, [activeIndex, open]);\n\n // Close on outside click\n useEffect(() => {\n if (!open) return;\n const handler = (e: MouseEvent) => {\n if (\n !triggerRef.current?.contains(e.target as Node) &&\n !listRef.current?.contains(e.target as Node)\n ) {\n closeList();\n }\n };\n document.addEventListener(\"mousedown\", handler);\n return () => document.removeEventListener(\"mousedown\", handler);\n }, [open, closeList]);\n\n // Trigger keyboard handler\n const handleTriggerKeyDown = useCallback(\n (e: KeyboardEvent<HTMLButtonElement>) => {\n switch (e.key) {\n case \"Enter\":\n case \" \":\n case \"ArrowDown\":\n e.preventDefault();\n openList();\n break;\n case \"ArrowUp\":\n e.preventDefault();\n computeFlip();\n setOpen(true);\n setActiveIndex(\n options.reduce(\n (last, o, i) => (!o.disabled ? i : last),\n -1\n )\n );\n break;\n }\n },\n [openList, computeFlip, options]\n );\n\n // Listbox keyboard handler\n const handleListKeyDown = useCallback(\n (e: KeyboardEvent<HTMLUListElement>) => {\n const enabledIndexes = options\n .map((o, i) => ({ o, i }))\n .filter(({ o }) => !o.disabled)\n .map(({ i }) => i);\n\n const currentPos = enabledIndexes.indexOf(activeIndex);\n\n switch (e.key) {\n case \"ArrowDown\": {\n e.preventDefault();\n const next = enabledIndexes[Math.min(currentPos + 1, enabledIndexes.length - 1)];\n setActiveIndex(next ?? activeIndex);\n break;\n }\n case \"ArrowUp\": {\n e.preventDefault();\n const prev = enabledIndexes[Math.max(currentPos - 1, 0)];\n setActiveIndex(prev ?? activeIndex);\n break;\n }\n case \"Home\":\n e.preventDefault();\n setActiveIndex(enabledIndexes[0] ?? -1);\n break;\n case \"End\":\n e.preventDefault();\n setActiveIndex(enabledIndexes[enabledIndexes.length - 1] ?? -1);\n break;\n case \"Enter\":\n case \" \": {\n e.preventDefault();\n if (activeIndex >= 0) selectOption(options[activeIndex]);\n break;\n }\n case \"Escape\":\n case \"Tab\":\n closeList();\n break;\n }\n },\n [options, activeIndex, selectOption, closeList]\n );\n\n return (\n <div\n className={[styles.wrapper, className].filter(Boolean).join(\" \")}\n {...props}\n >\n {/* Trigger button */}\n <button\n ref={triggerRef}\n id={triggerId}\n type=\"button\"\n role=\"combobox\"\n aria-haspopup=\"listbox\"\n aria-expanded={open}\n aria-controls={listboxId}\n aria-activedescendant={\n open && activeIndex >= 0 ? `${listboxId}-opt-${activeIndex}` : undefined\n }\n disabled={disabled}\n className={[styles.trigger, open ? styles.triggerOpen : null]\n .filter(Boolean)\n .join(\" \")}\n onClick={() => (open ? closeList() : openList())}\n onKeyDown={handleTriggerKeyDown}\n >\n <span className={[styles.triggerLabel, !selected ? styles.placeholder : null]\n .filter(Boolean).join(\" \")}>\n {selected?.label ?? placeholder}\n </span>\n <Icon\n icon={PanDown}\n size=\"md\"\n aria-hidden\n className={[styles.chevron, open ? styles.chevronOpen : null]\n .filter(Boolean).join(\" \")}\n />\n </button>\n\n {/* Listbox */}\n {open && (\n <ul\n ref={listRef}\n id={listboxId}\n role=\"listbox\"\n aria-labelledby={triggerId}\n tabIndex={-1}\n className={[styles.list, flipUp ? styles.listUp : styles.listDown]\n .filter(Boolean)\n .join(\" \")}\n onKeyDown={handleListKeyDown}\n >\n {options.map((opt, i) => (\n <li\n key={opt.value}\n id={`${listboxId}-opt-${i}`}\n role=\"option\"\n aria-selected={opt.value === value}\n aria-disabled={opt.disabled}\n className={[\n styles.option,\n opt.value === value ? styles.optionSelected : null,\n i === activeIndex ? styles.optionActive : null,\n opt.disabled ? styles.optionDisabled : null,\n ]\n .filter(Boolean)\n .join(\" \")}\n onMouseEnter={() => !opt.disabled && setActiveIndex(i)}\n onClick={() => selectOption(opt)}\n >\n <span className={styles.optionText}>\n <span className={styles.optionLabel}>{opt.label}</span>\n {opt.description && (\n <span className={styles.optionDesc}>{opt.description}</span>\n )}\n </span>\n {opt.value === value && (\n <Icon icon={Check} size=\"md\" aria-hidden className={styles.checkIcon} />\n )}\n </li>\n ))}\n </ul>\n )}\n </div>\n );\n}\n","/* ─── Outer wrapper ──────────────────────────────────────────────────────────── */\n\n.wrapper {\n display: flex;\n flex-direction: column;\n width: 100%;\n /* Vertical space for thumb overflow */\n padding: 10px 0;\n}\n\n.hasLabels {\n padding-bottom: 0;\n}\n\n/* ─── Track ──────────────────────────────────────────────────────────────────── */\n\n.track {\n position: relative;\n width: 100%;\n height: 6px;\n\n background-color: var(--gnome-card-shade-color, rgba(0, 0, 0, 0.15));\n border-radius: var(--gnome-radius-pill, 9999px);\n\n cursor: pointer;\n touch-action: none; /* prevent scroll interference during drag */\n user-select: none;\n\n outline: none;\n}\n\n.track:focus-visible {\n /* Focus ring on the track itself as a fallback */\n box-shadow:\n 0 0 0 var(--gnome-focus-ring-offset, 2px) var(--gnome-window-bg-color, #fafafa),\n 0 0 0 calc(var(--gnome-focus-ring-offset, 2px) + var(--gnome-focus-ring-width, 3px))\n var(--gnome-focus-ring-color, #3584e4);\n}\n\n/* ─── Disabled ───────────────────────────────────────────────────────────────── */\n\n.disabled {\n opacity: var(--gnome-opacity-disabled, 0.5);\n cursor: not-allowed;\n pointer-events: none;\n}\n\n/* ─── Filled section (left of thumb) ────────────────────────────────────────── */\n\n.fill {\n position: absolute;\n top: 0;\n left: 0;\n height: 100%;\n border-radius: var(--gnome-radius-pill, 9999px);\n\n background-color: var(--gnome-accent-bg-color, #3584e4);\n\n pointer-events: none;\n}\n\n/* ─── Thumb ──────────────────────────────────────────────────────────────────── */\n\n.thumb {\n position: absolute;\n top: 50%;\n /* Translate left by half thumb width so it centres on the left edge of .fill */\n transform: translate(-50%, -50%);\n\n width: 22px;\n height: 22px;\n border-radius: var(--gnome-radius-pill, 9999px);\n\n background-color: var(--gnome-accent-bg-color, #3584e4);\n box-shadow:\n 0 1px 3px rgba(0, 0, 0, 0.25),\n 0 0 0 2px var(--gnome-window-bg-color, #fafafa);\n\n pointer-events: none;\n\n transition:\n transform var(--gnome-duration-fast, 100ms) var(--gnome-easing-default, ease),\n box-shadow var(--gnome-duration-fast, 100ms) var(--gnome-easing-default, ease);\n}\n\n/* Slightly larger thumb on focus / active drag */\n.track:focus-visible .thumb,\n.track:active .thumb {\n transform: translate(-50%, -50%) scale(1.18);\n box-shadow:\n 0 2px 6px rgba(0, 0, 0, 0.3),\n 0 0 0 2px var(--gnome-window-bg-color, #fafafa);\n}\n\n/* ─── Tick marks ─────────────────────────────────────────────────────────────── */\n\n.tick {\n position: absolute;\n top: 50%;\n transform: translate(-50%, -50%);\n\n width: 4px;\n height: 4px;\n border-radius: 50%;\n\n background-color: var(--gnome-window-bg-color, #fafafa);\n pointer-events: none;\n}\n\n/* ─── Labels row ─────────────────────────────────────────────────────────────── */\n\n.labels {\n position: relative;\n width: 100%;\n height: 20px;\n margin-top: var(--gnome-space-1, 6px);\n}\n\n.markLabel {\n position: absolute;\n transform: translateX(-50%);\n\n font-family: var(--gnome-font-family);\n font-size: var(--gnome-font-size-caption, 0.8125rem);\n line-height: 1;\n color: var(--gnome-window-fg-color, rgba(0, 0, 0, 0.8));\n opacity: var(--gnome-opacity-dim, 0.55);\n\n white-space: nowrap;\n pointer-events: none;\n user-select: none;\n}\n\n/* ─── Dark mode ──────────────────────────────────────────────────────────────── */\n\n@media (prefers-color-scheme: dark) {\n .track {\n background-color: rgba(255, 255, 255, 0.15);\n }\n\n .thumb {\n box-shadow:\n 0 1px 4px rgba(0, 0, 0, 0.5),\n 0 0 0 2px var(--gnome-window-bg-color, #242424);\n }\n\n .track:focus-visible .thumb,\n .track:active .thumb {\n box-shadow:\n 0 2px 8px rgba(0, 0, 0, 0.6),\n 0 0 0 2px var(--gnome-window-bg-color, #242424);\n }\n\n .tick {\n background-color: var(--gnome-window-bg-color, #242424);\n }\n}\n\n/* ─── Reduced motion ─────────────────────────────────────────────────────────── */\n\n@media (prefers-reduced-motion: reduce) {\n .thumb {\n transition: none;\n }\n}\n","import {\n useRef,\n useCallback,\n type PointerEvent,\n type KeyboardEvent,\n type HTMLAttributes,\n} from \"react\";\nimport styles from \"./Slider.module.css\";\n\nexport interface SliderProps\n extends Omit<HTMLAttributes<HTMLDivElement>, \"onChange\"> {\n /** Current value. Must be between `min` and `max`. */\n value: number;\n /** Called when the value changes. */\n onChange: (value: number) => void;\n /** Minimum value. Defaults to `0`. */\n min?: number;\n /** Maximum value. Defaults to `100`. */\n max?: number;\n /**\n * Granularity of each step.\n * - Arrow keys move by one step.\n * - Page Up/Down move by 10 steps.\n * Defaults to `1`.\n */\n step?: number;\n /** Disables the control. */\n disabled?: boolean;\n /**\n * Marks to display along the track.\n * Each mark can have an optional label rendered below the track.\n */\n marks?: Array<{ value: number; label?: string }>;\n /** Accessible label. Required when no visible `<label>` is present. */\n \"aria-label\"?: string;\n /** Associates the control with a visible label element. */\n \"aria-labelledby\"?: string;\n /** `id` of an element that describes the current value (e.g. a live region). */\n \"aria-describedby\"?: string;\n}\n\nfunction clamp(v: number, min: number, max: number) {\n return Math.min(max, Math.max(min, v));\n}\n\nfunction snapToStep(v: number, min: number, step: number, decimals: number) {\n return parseFloat((Math.round((v - min) / step) * step + min).toFixed(decimals));\n}\n\nfunction countDecimals(n: number) {\n const s = n.toString();\n const dot = s.indexOf(\".\");\n return dot === -1 ? 0 : s.length - dot - 1;\n}\n\n/**\n * Draggable range slider following the Adwaita `GtkScale` pattern.\n *\n * - Pointer (mouse / touch / pen) drag to set value continuously.\n * - Keyboard: ← / → move by one step; Page Up/Down by 10 steps; Home/End jump to bounds.\n * - Optional tick marks with labels.\n * - Fills the track from the left up to the thumb (accent colour).\n *\n * @see https://developer.gnome.org/hig/patterns/controls/sliders.html\n */\nexport function Slider({\n value,\n onChange,\n min = 0,\n max = 100,\n step = 1,\n disabled = false,\n marks,\n className,\n \"aria-label\": ariaLabel,\n \"aria-labelledby\": ariaLabelledBy,\n \"aria-describedby\": ariaDescribedBy,\n ...props\n}: SliderProps) {\n const trackRef = useRef<HTMLDivElement>(null);\n const dp = countDecimals(step);\n\n const pct = ((clamp(value, min, max) - min) / (max - min)) * 100;\n\n const setFromX = useCallback(\n (clientX: number) => {\n if (!trackRef.current) return;\n const { left, width } = trackRef.current.getBoundingClientRect();\n const raw = ((clientX - left) / width) * (max - min) + min;\n onChange(clamp(snapToStep(raw, min, step, dp), min, max));\n },\n [min, max, step, dp, onChange]\n );\n\n // Pointer drag — capture on the thumb, move anywhere\n const handlePointerDown = useCallback(\n (e: PointerEvent<HTMLDivElement>) => {\n if (disabled) return;\n e.currentTarget.setPointerCapture(e.pointerId);\n setFromX(e.clientX);\n },\n [disabled, setFromX]\n );\n\n const handlePointerMove = useCallback(\n (e: PointerEvent<HTMLDivElement>) => {\n if (!e.currentTarget.hasPointerCapture(e.pointerId)) return;\n setFromX(e.clientX);\n },\n [setFromX]\n );\n\n // Keyboard\n const handleKeyDown = useCallback(\n (e: KeyboardEvent<HTMLDivElement>) => {\n const set = (next: number) => {\n e.preventDefault();\n onChange(clamp(snapToStep(next, min, step, dp), min, max));\n };\n switch (e.key) {\n case \"ArrowRight\":\n case \"ArrowUp\":\n set(value + step);\n break;\n case \"ArrowLeft\":\n case \"ArrowDown\":\n set(value - step);\n break;\n case \"PageUp\":\n set(value + step * 10);\n break;\n case \"PageDown\":\n set(value - step * 10);\n break;\n case \"Home\":\n set(min);\n break;\n case \"End\":\n set(max);\n break;\n }\n },\n [value, step, min, max, dp, onChange]\n );\n\n const hasMarks = marks && marks.length > 0;\n const hasLabels = hasMarks && marks.some((m) => m.label);\n\n return (\n <div\n className={[\n styles.wrapper,\n hasLabels ? styles.hasLabels : null,\n className,\n ]\n .filter(Boolean)\n .join(\" \")}\n {...props}\n >\n {/* Accessible slider */}\n <div\n ref={trackRef}\n role=\"slider\"\n tabIndex={disabled ? -1 : 0}\n aria-valuemin={min}\n aria-valuemax={max}\n aria-valuenow={value}\n aria-label={ariaLabel}\n aria-labelledby={ariaLabelledBy}\n aria-describedby={ariaDescribedBy}\n aria-disabled={disabled || undefined}\n className={[styles.track, disabled ? styles.disabled : null]\n .filter(Boolean)\n .join(\" \")}\n onPointerDown={handlePointerDown}\n onPointerMove={handlePointerMove}\n onKeyDown={disabled ? undefined : handleKeyDown}\n >\n {/* Filled portion */}\n <div className={styles.fill} style={{ width: `${pct}%` }} />\n\n {/* Thumb */}\n <div\n className={styles.thumb}\n style={{ left: `${pct}%` }}\n aria-hidden=\"true\"\n />\n\n {/* Tick marks */}\n {hasMarks &&\n marks.map((m) => {\n const mPct = ((clamp(m.value, min, max) - min) / (max - min)) * 100;\n return (\n <div\n key={m.value}\n className={styles.tick}\n style={{ left: `${mPct}%` }}\n aria-hidden=\"true\"\n />\n );\n })}\n </div>\n\n {/* Mark labels */}\n {hasLabels && (\n <div className={styles.labels} aria-hidden=\"true\">\n {marks.map((m) => {\n const mPct = ((clamp(m.value, min, max) - min) / (max - min)) * 100;\n return (\n <span\n key={m.value}\n className={styles.markLabel}\n style={{ left: `${mPct}%` }}\n >\n {m.label}\n </span>\n );\n })}\n </div>\n )}\n </div>\n );\n}\n","/* ─── Panel ──────────────────────────────────────────────────────────────────── */\n\n.panel {\n position: fixed;\n z-index: 10200; /* above Toaster (9999), below Dialog (10000) */\n\n min-width: 180px;\n max-width: 320px;\n\n background-color: var(--gnome-popover-bg-color, var(--gnome-card-bg-color, #ffffff));\n color: var(--gnome-window-fg-color, rgba(0, 0, 0, 0.8));\n\n border: 1px solid var(--gnome-card-shade-color, rgba(0, 0, 0, 0.1));\n border-radius: var(--gnome-radius-lg, 12px);\n box-shadow:\n 0 4px 20px rgba(0, 0, 0, 0.14),\n 0 1px 4px rgba(0, 0, 0, 0.08);\n\n padding: var(--gnome-space-2, 12px);\n\n font-family: var(--gnome-font-family);\n\n /* Hidden before position is known */\n opacity: 0;\n transform: scale(0.95);\n transition:\n opacity var(--gnome-duration-fast, 100ms) var(--gnome-easing-default, ease),\n transform var(--gnome-duration-fast, 100ms) var(--gnome-easing-default, ease);\n\n /* Don't intercept events while invisible */\n pointer-events: none;\n\n outline: none;\n}\n\n.visible {\n opacity: 1;\n transform: scale(1);\n pointer-events: auto;\n}\n\n/* ─── Arrow ──────────────────────────────────────────────────────────────────── */\n\n.arrow {\n position: absolute;\n width: 12px;\n height: 12px;\n\n background-color: var(--gnome-popover-bg-color, var(--gnome-card-bg-color, #ffffff));\n border: 1px solid var(--gnome-card-shade-color, rgba(0, 0, 0, 0.1));\n\n /* Arrow points toward the trigger */\n transform: rotate(45deg);\n pointer-events: none;\n z-index: -1;\n}\n\n/* Position arrow on each side */\n.bottom .arrow {\n top: -6px;\n left: 50%;\n margin-left: -6px;\n border-right-color: transparent;\n border-bottom-color: transparent;\n}\n\n.top .arrow {\n bottom: -6px;\n left: 50%;\n margin-left: -6px;\n border-left-color: transparent;\n border-top-color: transparent;\n}\n\n.left .arrow {\n right: -6px;\n top: 50%;\n margin-top: -6px;\n border-left-color: transparent;\n border-bottom-color: transparent;\n}\n\n.right .arrow {\n left: -6px;\n top: 50%;\n margin-top: -6px;\n border-right-color: transparent;\n border-top-color: transparent;\n}\n\n/* ─── Dark mode ──────────────────────────────────────────────────────────────── */\n\n@media (prefers-color-scheme: dark) {\n .panel {\n background-color: var(--gnome-popover-bg-color, var(--gnome-card-bg-color, #383838));\n border-color: rgba(255, 255, 255, 0.1);\n box-shadow:\n 0 4px 24px rgba(0, 0, 0, 0.4),\n 0 1px 4px rgba(0, 0, 0, 0.3);\n }\n\n .arrow {\n background-color: var(--gnome-popover-bg-color, var(--gnome-card-bg-color, #383838));\n border-color: rgba(255, 255, 255, 0.1);\n }\n\n .bottom .arrow { border-right-color: transparent; border-bottom-color: transparent; }\n .top .arrow { border-left-color: transparent; border-top-color: transparent; }\n .left .arrow { border-left-color: transparent; border-bottom-color: transparent; }\n .right .arrow { border-right-color: transparent; border-top-color: transparent; }\n}\n\n/* ─── Reduced motion ─────────────────────────────────────────────────────────── */\n\n@media (prefers-reduced-motion: reduce) {\n .panel {\n transition: opacity var(--gnome-duration-fast, 100ms) linear;\n transform: none;\n }\n\n .visible {\n transform: none;\n }\n}\n","import {\n useState,\n useRef,\n useId,\n useEffect,\n useCallback,\n cloneElement,\n type ReactElement,\n type HTMLAttributes,\n type ReactNode,\n type KeyboardEvent,\n} from \"react\";\nimport { createPortal } from \"react-dom\";\nimport styles from \"./Popover.module.css\";\n\nexport type PopoverPlacement = \"top\" | \"bottom\" | \"left\" | \"right\";\n\nexport interface PopoverProps {\n /**\n * The rich content rendered inside the popover panel.\n * Can include interactive elements (buttons, links, forms).\n */\n content: ReactNode;\n /**\n * Preferred placement relative to the trigger.\n * Flips automatically when there is not enough viewport space.\n * Defaults to `\"bottom\"`.\n */\n placement?: PopoverPlacement;\n /**\n * Whether the popover is open (controlled mode).\n * Omit to use uncontrolled mode where the trigger toggles it.\n */\n open?: boolean;\n /**\n * Called when the popover requests to be closed\n * (Escape key, outside click, or trigger click while open).\n */\n onClose?: () => void;\n /**\n * Called when open state should change (controlled mode).\n */\n onOpenChange?: (open: boolean) => void;\n /**\n * The trigger element. Must be a single React element that can\n * receive `ref` and click/keyboard event props.\n */\n children: ReactElement<HTMLAttributes<HTMLElement>>;\n}\n\ninterface Position {\n top: number;\n left: number;\n placement: PopoverPlacement;\n}\n\nconst GAP = 8;\nconst MARGIN = 8;\n\nfunction computePosition(\n trigger: DOMRect,\n popover: DOMRect,\n preferred: PopoverPlacement\n): Position {\n const vw = window.innerWidth;\n const vh = window.innerHeight;\n\n const order: PopoverPlacement[] = [\n preferred,\n preferred === \"top\" ? \"bottom\"\n : preferred === \"bottom\" ? \"top\"\n : preferred === \"left\" ? \"right\"\n : \"left\",\n \"bottom\", \"top\", \"left\", \"right\",\n ];\n\n // De-duplicate while preserving order\n const candidates = [...new Set(order)];\n\n for (const p of candidates) {\n let top = 0, left = 0;\n\n if (p === \"bottom\") {\n top = trigger.bottom + GAP;\n left = trigger.left + trigger.width / 2 - popover.width / 2;\n } else if (p === \"top\") {\n top = trigger.top - popover.height - GAP;\n left = trigger.left + trigger.width / 2 - popover.width / 2;\n } else if (p === \"left\") {\n top = trigger.top + trigger.height / 2 - popover.height / 2;\n left = trigger.left - popover.width - GAP;\n } else {\n top = trigger.top + trigger.height / 2 - popover.height / 2;\n left = trigger.right + GAP;\n }\n\n const fitsH = left >= MARGIN && left + popover.width <= vw - MARGIN;\n const fitsV = top >= MARGIN && top + popover.height <= vh - MARGIN;\n\n if (fitsH && fitsV) {\n return { top, left, placement: p };\n }\n }\n\n // Fallback: clamp bottom placement\n const top = trigger.bottom + GAP;\n const left = trigger.left + trigger.width / 2 - popover.width / 2;\n return {\n top: Math.max(MARGIN, Math.min(top, vh - popover.height - MARGIN)),\n left: Math.max(MARGIN, Math.min(left, vw - popover.width - MARGIN)),\n placement: \"bottom\",\n };\n}\n\n/**\n * Floating panel anchored to a trigger element.\n *\n * Unlike `Tooltip`, a Popover can contain rich interactive content\n * (menus, forms, details). It follows the Adwaita `GtkPopover` pattern.\n *\n * - Supports both controlled (`open` + `onClose`) and uncontrolled modes.\n * - Auto-positions with viewport-aware flip.\n * - Closes on Escape, outside click, or trigger re-click.\n * - Focus is moved into the panel on open; returns to the trigger on close.\n * - `aria-haspopup=\"dialog\"` + `aria-expanded` wired to the trigger.\n * - `role=\"dialog\"` on the panel with `aria-labelledby` when a heading is present.\n *\n * @see https://gnome.pages.gitlab.gnome.org/libadwaita/doc/main/class.Popover.html\n */\nexport function Popover({\n content,\n placement: preferredPlacement = \"bottom\",\n open: controlledOpen,\n onClose,\n onOpenChange,\n children,\n}: PopoverProps) {\n const isControlled = controlledOpen !== undefined;\n const [uncontrolledOpen, setUncontrolledOpen] = useState(false);\n const open = isControlled ? controlledOpen : uncontrolledOpen;\n\n const [pos, setPos] = useState<Position | null>(null);\n\n const triggerId = useId();\n const popoverId = useId();\n const triggerRef = useRef<HTMLElement>(null);\n const panelRef = useRef<HTMLDivElement>(null);\n const previouslyFocused = useRef<Element | null>(null);\n\n const close = useCallback(() => {\n if (isControlled) {\n onClose?.();\n } else {\n setUncontrolledOpen(false);\n }\n onOpenChange?.(false);\n }, [isControlled, onClose, onOpenChange]);\n\n const toggle = useCallback(() => {\n if (open) {\n close();\n } else {\n if (!isControlled) setUncontrolledOpen(true);\n onOpenChange?.(true);\n }\n }, [open, close, isControlled, onOpenChange]);\n\n // Position panel\n const place = useCallback(() => {\n if (!triggerRef.current || !panelRef.current) return;\n setPos(\n computePosition(\n triggerRef.current.getBoundingClientRect(),\n panelRef.current.getBoundingClientRect(),\n preferredPlacement\n )\n );\n }, [preferredPlacement]);\n\n // Reposition on open, scroll, resize\n useEffect(() => {\n if (!open) return;\n // Two RAF: first lets panel mount with hidden styles so we can measure it;\n // second applies the measured position.\n const id = requestAnimationFrame(() => {\n place();\n window.addEventListener(\"scroll\", place, { passive: true, capture: true });\n window.addEventListener(\"resize\", place, { passive: true });\n });\n return () => {\n cancelAnimationFrame(id);\n window.removeEventListener(\"scroll\", place, { capture: true });\n window.removeEventListener(\"resize\", place);\n };\n }, [open, place]);\n\n // Save / restore focus\n useEffect(() => {\n if (open) {\n previouslyFocused.current = document.activeElement;\n requestAnimationFrame(() => {\n const first = panelRef.current?.querySelector<HTMLElement>(\n 'button, [href], input, select, textarea, [tabindex]:not([tabindex=\"-1\"])'\n );\n (first ?? panelRef.current)?.focus();\n });\n } else {\n (previouslyFocused.current as HTMLElement | null)?.focus();\n setPos(null);\n }\n }, [open]);\n\n // Close on outside pointer\n useEffect(() => {\n if (!open) return;\n const handler = (e: MouseEvent) => {\n if (\n !triggerRef.current?.contains(e.target as Node) &&\n !panelRef.current?.contains(e.target as Node)\n ) {\n close();\n }\n };\n document.addEventListener(\"mousedown\", handler);\n return () => document.removeEventListener(\"mousedown\", handler);\n }, [open, close]);\n\n // Escape key\n const handlePanelKeyDown = useCallback(\n (e: KeyboardEvent<HTMLDivElement>) => {\n if (e.key === \"Escape\") { e.stopPropagation(); close(); }\n },\n [close]\n );\n\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const trigger = cloneElement(children as any, {\n ref: triggerRef,\n id: triggerId,\n \"aria-haspopup\": \"dialog\",\n \"aria-expanded\": open,\n \"aria-controls\": open ? popoverId : undefined,\n onClick: (e: React.MouseEvent<HTMLElement>) => {\n toggle();\n children.props.onClick?.(e);\n },\n });\n\n const panel = (\n <div\n ref={panelRef}\n id={popoverId}\n role=\"dialog\"\n aria-labelledby={triggerId}\n tabIndex={-1}\n className={[\n styles.panel,\n pos ? styles[pos.placement] : null,\n open && pos ? styles.visible : null,\n ]\n .filter(Boolean)\n .join(\" \")}\n style={\n pos\n ? { top: pos.top, left: pos.left }\n : { visibility: \"hidden\", pointerEvents: \"none\", top: -9999, left: -9999 }\n }\n onKeyDown={handlePanelKeyDown}\n >\n {/* Arrow */}\n <div className={styles.arrow} aria-hidden=\"true\" />\n {content}\n </div>\n );\n\n return (\n <>\n {trigger}\n {open &&\n (typeof document !== \"undefined\"\n ? createPortal(panel, document.body)\n : panel)}\n </>\n );\n}\n","/* ─── Clamp ───────────────────────────────────────────────────────────────────\n Constrains content to maximumSize px while allowing it to shrink.\n Mirrors AdwClamp: width is 100% up to the max, then stays fixed.\n ──────────────────────────────────────────────────────────────────────────── */\n\n.clamp {\n width: 100%;\n margin-inline: auto; /* center horizontally */\n box-sizing: border-box;\n}\n","import type { HTMLAttributes, ReactNode } from \"react\";\nimport styles from \"./Clamp.module.css\";\n\nexport interface ClampProps extends HTMLAttributes<HTMLDivElement> {\n /**\n * Maximum content width in pixels.\n * The element shrinks freely below this value.\n * Defaults to `600` — the Adwaita recommended narrow-content width.\n */\n maximumSize?: number;\n /**\n * Fractional width (0–1) of the viewport to use when the viewport is\n * narrower than `maximumSize`. Useful for keeping a comfortable margin\n * on medium-width screens. Set to `1` (default) to always fill the width.\n */\n tighteningThreshold?: number;\n children?: ReactNode;\n}\n\n/**\n * Constrains its child to a maximum width while allowing it to shrink freely,\n * mirroring the Adwaita `AdwClamp` widget.\n *\n * Use this to keep readable line lengths and comfortable reading widths on\n * large screens while still filling the available space on narrow screens.\n *\n * @example\n * // Settings page — content never wider than 600 px\n * <Clamp maximumSize={600}>\n * <BoxedList>…</BoxedList>\n * </Clamp>\n *\n * @see https://gnome.pages.gitlab.gnome.org/libadwaita/doc/main/class.Clamp.html\n */\nexport function Clamp({\n maximumSize = 600,\n children,\n className,\n style,\n ...props\n}: ClampProps) {\n return (\n <div\n className={[styles.clamp, className].filter(Boolean).join(\" \")}\n style={{ maxWidth: maximumSize, ...style }}\n {...props}\n >\n {children}\n </div>\n );\n}\n","/* ─── Root ───────────────────────────────────────────────────────────────────── */\n\n.root {\n display: flex;\n width: 100%;\n height: 100%;\n overflow: hidden;\n}\n\n/* ─── Wide (both panes visible) ──────────────────────────────────────────────── */\n\n.expanded {\n flex-direction: row;\n}\n\n.expanded .sidebar {\n width: var(--sidebar-width, clamp(180px, 25%, 280px));\n flex-shrink: 0;\n overflow-y: auto;\n}\n\n.expanded .contentPane {\n flex: 1;\n min-width: 0;\n overflow-y: auto;\n}\n\n/* ─── Narrow (one pane at a time) ────────────────────────────────────────────── */\n\n.collapsed {\n flex-direction: column;\n position: relative;\n}\n\n.collapsed .sidebar,\n.collapsed .contentPane {\n position: absolute;\n inset: 0;\n width: 100%;\n height: 100%;\n overflow-y: auto;\n transition: transform var(--gnome-duration-normal, 200ms) var(--gnome-easing-default, ease);\n}\n\n/* Sidebar slides out to the left when content is shown */\n.collapsed .sidebar.paneVisible {\n transform: translateX(0);\n}\n\n.collapsed .sidebar.paneHidden {\n transform: translateX(-100%);\n pointer-events: none;\n}\n\n/* Content slides in from the right when shown */\n.collapsed .contentPane.paneVisible {\n transform: translateX(0);\n}\n\n.collapsed .contentPane.paneHidden {\n transform: translateX(100%);\n pointer-events: none;\n}\n\n/* ─── Divider ────────────────────────────────────────────────────────────────── */\n\n.divider {\n width: 1px;\n background-color: var(--gnome-card-shade-color, rgba(0, 0, 0, 0.1));\n flex-shrink: 0;\n}\n\n@media (prefers-color-scheme: dark) {\n .divider {\n background-color: rgba(255, 255, 255, 0.08);\n }\n}\n\n/* ─── Reduced motion ─────────────────────────────────────────────────────────── */\n\n@media (prefers-reduced-motion: reduce) {\n .collapsed .sidebar,\n .collapsed .contentPane {\n transition: none;\n }\n}\n","import { type HTMLAttributes, type ReactNode } from \"react\";\nimport { useBreakpoint } from \"../../hooks/useBreakpoint\";\nimport styles from \"./NavigationSplitView.module.css\";\n\nexport interface NavigationSplitViewProps\n extends Omit<HTMLAttributes<HTMLDivElement>, \"content\"> {\n /**\n * The sidebar / list pane (left side on wide screens).\n * On narrow screens this is the \"list\" view shown when `showContent` is false.\n */\n sidebar: ReactNode;\n /**\n * The detail / content pane (right side on wide screens).\n * On narrow screens this is the \"detail\" view shown when `showContent` is true.\n */\n content: ReactNode;\n /**\n * Controls which pane is visible on narrow screens (≤ 400 px).\n * - `false` (default) — show the sidebar list.\n * - `true` — show the content detail.\n *\n * Has no effect on wide screens where both panes are visible simultaneously.\n */\n showContent?: boolean;\n /**\n * Minimum sidebar width in px. Defaults to `180`.\n */\n minSidebarWidth?: number;\n /**\n * Maximum sidebar width in px. Defaults to `280`.\n */\n maxSidebarWidth?: number;\n /**\n * Fraction of total width given to the sidebar (0–1). Defaults to `0.25`.\n */\n sidebarWidthFraction?: number;\n}\n\n/**\n * Two-pane sidebar + content layout following the Adwaita\n * `AdwNavigationSplitView` pattern.\n *\n * On **wide** screens (> 400 px) both panes are visible side-by-side.\n * On **narrow** screens (≤ 400 px) only one pane is shown at a time;\n * use `showContent` to switch between the sidebar list and the detail view.\n *\n * The sidebar width is constrained between `minSidebarWidth` and\n * `maxSidebarWidth`, gravitating toward `sidebarWidthFraction` of the total.\n *\n * @example\n * const [showContent, setShowContent] = useState(false);\n * <NavigationSplitView\n * showContent={showContent}\n * sidebar={<MailList onSelect={() => setShowContent(true)} />}\n * content={<MailDetail onBack={() => setShowContent(false)} />}\n * />\n *\n * @see https://gnome.pages.gitlab.gnome.org/libadwaita/doc/main/class.NavigationSplitView.html\n */\nexport function NavigationSplitView({\n sidebar,\n content,\n showContent = false,\n minSidebarWidth = 180,\n maxSidebarWidth = 280,\n sidebarWidthFraction = 0.25,\n className,\n style,\n ...props\n}: NavigationSplitViewProps) {\n const { isNarrow } = useBreakpoint();\n\n // CSS custom property drives the sidebar width clamp\n const sidebarWidth =\n `clamp(${minSidebarWidth}px, ${sidebarWidthFraction * 100}%, ${maxSidebarWidth}px)`;\n\n return (\n <div\n className={[\n styles.root,\n isNarrow ? styles.collapsed : styles.expanded,\n className,\n ]\n .filter(Boolean)\n .join(\" \")}\n style={{ \"--sidebar-width\": sidebarWidth, ...style } as React.CSSProperties}\n {...props}\n >\n {/* Sidebar pane */}\n <div\n className={[\n styles.sidebar,\n isNarrow && showContent ? styles.paneHidden : styles.paneVisible,\n ]\n .filter(Boolean)\n .join(\" \")}\n aria-hidden={isNarrow && showContent}\n >\n {sidebar}\n </div>\n\n {/* Separator — only on wide screens */}\n {!isNarrow && <div className={styles.divider} aria-hidden=\"true\" />}\n\n {/* Content pane */}\n <div\n className={[\n styles.contentPane,\n isNarrow && !showContent ? styles.paneHidden : styles.paneVisible,\n ]\n .filter(Boolean)\n .join(\" \")}\n aria-hidden={isNarrow && !showContent}\n >\n {content}\n </div>\n </div>\n );\n}\n","/* ─── Root ───────────────────────────────────────────────────────────────────── */\n\n.root {\n display: flex;\n width: 100%;\n height: 100%;\n overflow: hidden;\n position: relative;\n}\n\n/* ─── Wide — side by side ────────────────────────────────────────────────────── */\n\n.wide {\n flex-direction: row;\n}\n\n.wide.end {\n flex-direction: row-reverse;\n}\n\n.wide .sidebar {\n width: var(--sidebar-width, clamp(180px, 25%, 280px));\n flex-shrink: 0;\n overflow-y: auto;\n}\n\n.wide .content {\n flex: 1;\n min-width: 0;\n overflow-y: auto;\n}\n\n/* ─── Narrow — overlay mode ──────────────────────────────────────────────────── */\n\n.narrow .content {\n width: 100%;\n overflow-y: auto;\n}\n\n.narrow .sidebar {\n position: absolute;\n top: 0;\n bottom: 0;\n width: var(--sidebar-width, clamp(180px, 75%, 280px));\n overflow-y: auto;\n z-index: 100;\n\n background-color: var(--gnome-window-bg-color, #fafafa);\n box-shadow: 2px 0 16px rgba(0, 0, 0, 0.2);\n\n transition: transform var(--gnome-duration-normal, 200ms) var(--gnome-easing-default, ease);\n}\n\n/* Start (left) sidebar */\n.narrow.start .sidebar {\n left: 0;\n}\n\n.narrow.start .sidebarOpen { transform: translateX(0); }\n.narrow.start .sidebarClosed { transform: translateX(-100%); }\n\n/* End (right) sidebar */\n.narrow.end .sidebar {\n right: 0;\n}\n\n.narrow.end .sidebarOpen { transform: translateX(0); }\n.narrow.end .sidebarClosed { transform: translateX(100%); }\n\n/* ─── Backdrop ───────────────────────────────────────────────────────────────── */\n\n.backdrop {\n position: absolute;\n inset: 0;\n z-index: 99;\n background-color: rgba(0, 0, 0, 0.35);\n\n opacity: 0;\n pointer-events: none;\n transition: opacity var(--gnome-duration-normal, 200ms) var(--gnome-easing-default, ease);\n}\n\n.backdropVisible {\n opacity: 1;\n pointer-events: auto;\n}\n\n/* ─── Dark mode ──────────────────────────────────────────────────────────────── */\n\n@media (prefers-color-scheme: dark) {\n .narrow .sidebar {\n background-color: var(--gnome-window-bg-color, #242424);\n box-shadow: 2px 0 16px rgba(0, 0, 0, 0.5);\n }\n\n .backdrop {\n background-color: rgba(0, 0, 0, 0.55);\n }\n}\n\n/* ─── Reduced motion ─────────────────────────────────────────────────────────── */\n\n@media (prefers-reduced-motion: reduce) {\n .narrow .sidebar,\n .backdrop {\n transition: none;\n }\n}\n","import { useEffect, useRef, type HTMLAttributes, type ReactNode } from \"react\";\nimport { useBreakpoint } from \"../../hooks/useBreakpoint\";\nimport styles from \"./OverlaySplitView.module.css\";\n\nexport interface OverlaySplitViewProps\n extends Omit<HTMLAttributes<HTMLDivElement>, \"content\"> {\n /**\n * The sidebar pane.\n * On wide screens it sits beside the content.\n * On narrow screens it slides over the content as an overlay.\n */\n sidebar: ReactNode;\n /** The main content pane — always visible. */\n content: ReactNode;\n /**\n * Whether the sidebar is shown.\n * - Wide screens: always both visible; this prop is ignored.\n * - Narrow screens (≤ 400 px): controls whether the overlay is open.\n */\n showSidebar?: boolean;\n /**\n * Called when the user clicks the backdrop (narrow screens only).\n * Use this to set `showSidebar` to false.\n */\n onClose?: () => void;\n /**\n * Which side the sidebar appears on. Defaults to `\"start\"` (left in LTR).\n */\n sidebarPosition?: \"start\" | \"end\";\n /**\n * Minimum sidebar width in px. Defaults to `180`.\n */\n minSidebarWidth?: number;\n /**\n * Maximum sidebar width in px. Defaults to `280`.\n */\n maxSidebarWidth?: number;\n /**\n * Fraction of total width given to the sidebar. Defaults to `0.25`.\n */\n sidebarWidthFraction?: number;\n}\n\n/**\n * Sidebar + content layout where the sidebar becomes a slide-over overlay\n * on narrow screens, mirroring the Adwaita `AdwOverlaySplitView` pattern.\n *\n * On **wide** screens (> 400 px) sidebar and content are side-by-side.\n * On **narrow** screens (≤ 400 px) the content fills the full width and\n * the sidebar slides in as an overlay when `showSidebar` is true,\n * with a translucent backdrop behind it.\n *\n * @example\n * const [open, setOpen] = useState(false);\n * <OverlaySplitView\n * showSidebar={open}\n * onClose={() => setOpen(false)}\n * sidebar={<Nav />}\n * content={<MainContent onMenuClick={() => setOpen(true)} />}\n * />\n *\n * @see https://gnome.pages.gitlab.gnome.org/libadwaita/doc/main/class.OverlaySplitView.html\n */\nexport function OverlaySplitView({\n sidebar,\n content,\n showSidebar = false,\n onClose,\n sidebarPosition = \"start\",\n minSidebarWidth = 180,\n maxSidebarWidth = 280,\n sidebarWidthFraction = 0.25,\n className,\n style,\n ...props\n}: OverlaySplitViewProps) {\n const { isNarrow } = useBreakpoint();\n const sidebarRef = useRef<HTMLDivElement>(null);\n\n const sidebarWidth =\n `clamp(${minSidebarWidth}px, ${sidebarWidthFraction * 100}%, ${maxSidebarWidth}px)`;\n\n // Close on Escape in overlay mode\n useEffect(() => {\n if (!isNarrow || !showSidebar) return;\n const handler = (e: KeyboardEvent) => {\n if (e.key === \"Escape\") onClose?.();\n };\n document.addEventListener(\"keydown\", handler);\n return () => document.removeEventListener(\"keydown\", handler);\n }, [isNarrow, showSidebar, onClose]);\n\n // Trap focus inside sidebar when open in overlay mode\n useEffect(() => {\n if (!isNarrow || !showSidebar) return;\n const first = sidebarRef.current?.querySelector<HTMLElement>(\n 'button, [href], input, select, textarea, [tabindex]:not([tabindex=\"-1\"])'\n );\n first?.focus();\n }, [isNarrow, showSidebar]);\n\n const isEnd = sidebarPosition === \"end\";\n\n return (\n <div\n className={[\n styles.root,\n isNarrow ? styles.narrow : styles.wide,\n isEnd ? styles.end : styles.start,\n className,\n ]\n .filter(Boolean)\n .join(\" \")}\n style={{ \"--sidebar-width\": sidebarWidth, ...style } as React.CSSProperties}\n {...props}\n >\n {/* Backdrop — narrow mode only */}\n {isNarrow && (\n <div\n className={[styles.backdrop, showSidebar ? styles.backdropVisible : null]\n .filter(Boolean)\n .join(\" \")}\n aria-hidden=\"true\"\n onClick={onClose}\n />\n )}\n\n {/* Sidebar */}\n <div\n ref={sidebarRef}\n className={[\n styles.sidebar,\n isNarrow\n ? showSidebar ? styles.sidebarOpen : styles.sidebarClosed\n : null,\n ]\n .filter(Boolean)\n .join(\" \")}\n aria-hidden={isNarrow && !showSidebar}\n >\n {sidebar}\n </div>\n\n {/* Content */}\n <div className={styles.content}>\n {content}\n </div>\n </div>\n );\n}\n","/* ─── Bottom bar ─────────────────────────────────────────────────────────────── */\n\n.bar {\n display: flex;\n align-items: stretch;\n\n width: 100%;\n min-height: 52px;\n\n background-color: var(--gnome-headerbar-bg-color, #ebebeb);\n border-top: 1px solid var(--gnome-headerbar-shade-color, rgba(0, 0, 0, 0.12));\n\n /* Safe-area inset for mobile browsers */\n padding-bottom: env(safe-area-inset-bottom, 0px);\n}\n\n/* Each child (ViewSwitcherItem) fills equal width */\n.bar > * {\n flex: 1;\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n gap: 2px;\n\n /* Override the horizontal pill style from ViewSwitcherItem */\n border-radius: 0 !important;\n padding: 6px 4px !important;\n font-size: var(--gnome-font-size-caption, 0.8125rem) !important;\n min-height: 52px;\n}\n\n/* ─── Dark mode ──────────────────────────────────────────────────────────────── */\n\n@media (prefers-color-scheme: dark) {\n .bar {\n background-color: var(--gnome-headerbar-bg-color, #303030);\n border-top-color: rgba(0, 0, 0, 0.3);\n }\n}\n","import type { HTMLAttributes, ReactNode } from \"react\";\nimport styles from \"./ViewSwitcherBar.module.css\";\n\nexport interface ViewSwitcherBarProps extends HTMLAttributes<HTMLDivElement> {\n /**\n * `ViewSwitcherItem` elements to render in the bottom bar.\n * Use the same items you pass to the header-bar `ViewSwitcher`.\n */\n children: ReactNode;\n /**\n * Whether the bar is visible.\n * Typically tied to `useBreakpoint().isMedium` so it appears\n * automatically when the window is ≤ 550 px wide.\n */\n reveal?: boolean;\n}\n\n/**\n * Bottom navigation bar for `ViewSwitcher` items on narrow screens (≤ 550 px),\n * mirroring the Adwaita `AdwViewSwitcherBar` pattern.\n *\n * Use in tandem with a `ViewSwitcher` in the `HeaderBar`:\n * - **Wide** (> 550 px): show the `ViewSwitcher` in the header, hide the bar.\n * - **Narrow** (≤ 550 px): hide the header switcher, reveal the bar.\n *\n * @example\n * const { isMedium } = useBreakpoint();\n * <>\n * <HeaderBar\n * title={\n * !isMedium && (\n * <ViewSwitcher aria-label=\"View\">…items…</ViewSwitcher>\n * )\n * }\n * />\n * <ViewSwitcherBar reveal={isMedium}>…items…</ViewSwitcherBar>\n * </>\n *\n * @see https://gnome.pages.gitlab.gnome.org/libadwaita/doc/main/class.ViewSwitcherBar.html\n */\nexport function ViewSwitcherBar({\n children,\n reveal = true,\n className,\n ...props\n}: ViewSwitcherBarProps) {\n if (!reveal) return null;\n\n return (\n <div\n role=\"navigation\"\n aria-label=\"Bottom navigation\"\n className={[styles.bar, className].filter(Boolean).join(\" \")}\n {...props}\n >\n {children}\n </div>\n );\n}\n",".link {\n color: var(--gnome-accent-color);\n text-decoration: none;\n cursor: pointer;\n outline: none;\n\n /* Smooth underline on hover */\n background-image: linear-gradient(currentColor, currentColor);\n background-position: 0 100%;\n background-repeat: no-repeat;\n background-size: 0% 1px;\n transition:\n background-size var(--gnome-duration-fast) var(--gnome-easing-default),\n opacity var(--gnome-duration-fast) var(--gnome-easing-default);\n}\n\n.link:hover {\n background-size: 100% 1px;\n}\n\n.link:active {\n opacity: 0.7;\n}\n\n/* ─── Focus ────────────────────────────────────────────────────── */\n\n.link:focus-visible {\n border-radius: 2px;\n box-shadow:\n 0 0 0 var(--gnome-focus-ring-offset) var(--gnome-window-bg-color),\n 0 0 0 calc(var(--gnome-focus-ring-offset) + var(--gnome-focus-ring-width)) var(--gnome-focus-ring-color);\n}\n\n/* ─── External indicator ──────────────────────────────────────── */\n\n.externalIcon {\n display: inline-block;\n margin-inline-start: 0.15em;\n font-size: 0.8em;\n vertical-align: super;\n line-height: 1;\n /* don't underline the icon itself */\n background-image: none;\n}\n\n/* ─── Reduced motion ──────────────────────────────────────────── */\n\n@media (prefers-reduced-motion: reduce) {\n .link {\n transition: none;\n }\n\n .link:hover {\n text-decoration: underline;\n background-size: 0%;\n }\n}\n","import type { AnchorHTMLAttributes, ReactNode } from \"react\";\nimport styles from \"./Link.module.css\";\n\nexport interface LinkProps extends AnchorHTMLAttributes<HTMLAnchorElement> {\n /**\n * When true the link is treated as external: opens in a new tab,\n * adds `rel=\"noopener noreferrer\"`, and shows a trailing ↗ icon.\n */\n external?: boolean;\n children?: ReactNode;\n}\n\n/**\n * Inline hyperlink following GNOME HIG.\n *\n * - Uses the accent colour and shows an underline on hover.\n * - `external` opens a new tab safely and appends a visual ↗ indicator.\n *\n * @see https://developer.gnome.org/hig/patterns/controls/links.html\n */\nexport function Link({\n external = false,\n children,\n className,\n target,\n rel,\n ...props\n}: LinkProps) {\n const isExternal = external || target === \"_blank\";\n\n return (\n <a\n className={[styles.link, className].filter(Boolean).join(\" \")}\n target={isExternal ? \"_blank\" : target}\n rel={isExternal ? \"noopener noreferrer\" : rel}\n {...props}\n >\n {children}\n {isExternal && (\n <span className={styles.externalIcon} aria-label=\"(opens in new tab)\">\n ↗\n </span>\n )}\n </a>\n );\n}\n","/* ─── Container ─────────────────────────────────────────────────────────────── */\n\n.group {\n display: inline-flex;\n align-items: stretch;\n gap: 0;\n\n background-color: var(--gnome-card-bg-color, #ffffff);\n border: 1px solid var(--gnome-light-3, #deddda);\n border-radius: var(--gnome-radius-md);\n box-shadow: var(--gnome-shadow-sm);\n padding: 2px;\n gap: 2px;\n}\n\n/* ─── Item ──────────────────────────────────────────────────────────────────── */\n\n.item {\n appearance: none;\n -webkit-appearance: none;\n border: none;\n background: transparent;\n font: inherit;\n cursor: pointer;\n outline: none;\n\n display: inline-flex;\n align-items: center;\n justify-content: center;\n gap: var(--gnome-space-1, 6px);\n\n padding: 4px var(--gnome-space-2, 12px);\n border-radius: calc(var(--gnome-radius-md) - 2px);\n min-height: 28px;\n\n font-family: var(--gnome-font-family);\n font-size: var(--gnome-font-size-body, 1rem);\n font-weight: var(--gnome-font-weight-normal, 400);\n color: var(--gnome-window-fg-color, rgba(0, 0, 0, 0.8));\n white-space: nowrap;\n\n transition:\n background-color var(--gnome-duration-fast) var(--gnome-easing-default),\n box-shadow var(--gnome-duration-fast) var(--gnome-easing-default);\n}\n\n/* Icon-only: square aspect ratio */\n.iconOnly {\n padding: 4px;\n min-width: 28px;\n}\n\n.item:hover:not(:disabled):not(.active) {\n background-color: rgba(0, 0, 0, 0.06);\n}\n\n.item:active:not(:disabled):not(.active) {\n background-color: rgba(0, 0, 0, 0.12);\n}\n\n.item:disabled {\n opacity: var(--gnome-opacity-disabled, 0.5);\n cursor: not-allowed;\n}\n\n/* ─── Focus ─────────────────────────────────────────────────────────────────── */\n\n.item:focus-visible {\n box-shadow:\n 0 0 0 var(--gnome-focus-ring-offset, 2px) var(--gnome-card-bg-color, #ffffff),\n 0 0 0 calc(var(--gnome-focus-ring-offset, 2px) + var(--gnome-focus-ring-width, 2px))\n var(--gnome-focus-ring-color, #3584e4);\n}\n\n/* ─── Active ────────────────────────────────────────────────────────────────── */\n\n.active {\n background-color: color-mix(in srgb, var(--gnome-accent-bg-color, #3584e4) 15%, transparent);\n color: var(--gnome-accent-color, #3584e4);\n font-weight: var(--gnome-font-weight-semibold, 600);\n box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--gnome-accent-bg-color, #3584e4) 40%, transparent);\n}\n\n/* ─── Icon ──────────────────────────────────────────────────────────────────── */\n\n.itemIcon {\n display: flex;\n align-items: center;\n flex-shrink: 0;\n}\n\n.itemLabel {\n line-height: 1;\n}\n\n/* ─── Dark mode ─────────────────────────────────────────────────────────────── */\n\n@media (prefers-color-scheme: dark) {\n .group {\n background-color: var(--gnome-card-bg-color, #383838);\n border-color: rgba(255, 255, 255, 0.12);\n box-shadow: none;\n }\n\n .item:hover:not(:disabled):not(.active) {\n background-color: rgba(255, 255, 255, 0.08);\n }\n\n .item:active:not(:disabled):not(.active) {\n background-color: rgba(255, 255, 255, 0.14);\n }\n\n .item:focus-visible {\n box-shadow:\n 0 0 0 var(--gnome-focus-ring-offset, 2px) var(--gnome-card-bg-color, #383838),\n 0 0 0 calc(var(--gnome-focus-ring-offset, 2px) + var(--gnome-focus-ring-width, 2px))\n var(--gnome-focus-ring-color, #78aeed);\n }\n\n .active {\n background-color: color-mix(in srgb, var(--gnome-accent-bg-color, #3584e4) 25%, transparent);\n color: var(--gnome-accent-color, #78aeed);\n box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--gnome-accent-bg-color, #3584e4) 50%, transparent);\n }\n}\n\n/* ─── Reduced motion ────────────────────────────────────────────────────────── */\n\n@media (prefers-reduced-motion: reduce) {\n .item {\n transition: none;\n }\n}\n","import {\n createContext,\n useContext,\n useRef,\n type HTMLAttributes,\n type KeyboardEvent,\n type ReactNode,\n} from \"react\";\nimport styles from \"./ToggleGroup.module.css\";\n\n// ─── Context ──────────────────────────────────────────────────────────────────\n\ninterface ToggleGroupContextValue {\n value: string;\n onValueChange: (value: string) => void;\n}\n\nconst ToggleGroupContext = createContext<ToggleGroupContextValue | null>(null);\n\nexport function useToggleGroup() {\n const ctx = useContext(ToggleGroupContext);\n if (!ctx) throw new Error(\"ToggleGroupItem must be used inside ToggleGroup\");\n return ctx;\n}\n\n// ─── ToggleGroup ──────────────────────────────────────────────────────────────\n\nexport interface ToggleGroupProps extends HTMLAttributes<HTMLDivElement> {\n /** Name of the currently active toggle. */\n value: string;\n /** Called with the new value when a toggle is selected. */\n onValueChange: (value: string) => void;\n /** Accessible label for the group. */\n \"aria-label\"?: string;\n children?: ReactNode;\n}\n\n/**\n * Mutually-exclusive group of toggle buttons for in-place option selection.\n *\n * Mirrors `AdwToggleGroup` (libadwaita 1.7 / GNOME 48).\n *\n * Use for formatting controls, view-mode selectors, and toolbar options —\n * wherever a `ViewSwitcher` would be too heavy or is not placed in a HeaderBar.\n *\n * Compose with `ToggleGroupItem`. Keyboard: ← / → cycle, Home / End jump.\n *\n * @see https://gnome.pages.gitlab.gnome.org/libadwaita/doc/main/class.ToggleGroup.html\n */\nexport function ToggleGroup({\n value,\n onValueChange,\n \"aria-label\": ariaLabel = \"Options\",\n children,\n className,\n ...props\n}: ToggleGroupProps) {\n const groupRef = useRef<HTMLDivElement>(null);\n\n function handleKeyDown(e: KeyboardEvent<HTMLDivElement>) {\n const items = Array.from(\n groupRef.current?.querySelectorAll<HTMLButtonElement>(\n \"[role=radio]:not(:disabled)\",\n ) ?? [],\n );\n const idx = items.findIndex((el) => el === document.activeElement);\n if (idx === -1) return;\n\n let next = idx;\n if (e.key === \"ArrowRight\" || e.key === \"ArrowDown\")\n next = (idx + 1) % items.length;\n else if (e.key === \"ArrowLeft\" || e.key === \"ArrowUp\")\n next = (idx - 1 + items.length) % items.length;\n else if (e.key === \"Home\") next = 0;\n else if (e.key === \"End\") next = items.length - 1;\n else return;\n\n e.preventDefault();\n items[next].focus();\n items[next].click();\n }\n\n return (\n <ToggleGroupContext.Provider value={{ value, onValueChange }}>\n <div\n ref={groupRef}\n role=\"radiogroup\"\n aria-label={ariaLabel}\n onKeyDown={handleKeyDown}\n className={[styles.group, className].filter(Boolean).join(\" \")}\n {...props}\n >\n {children}\n </div>\n </ToggleGroupContext.Provider>\n );\n}\n","import type { ButtonHTMLAttributes } from \"react\";\nimport type { IconDefinition } from \"@gnome-ui/icons\";\nimport { Icon } from \"../Icon\";\nimport { useToggleGroup } from \"./ToggleGroup\";\nimport styles from \"./ToggleGroup.module.css\";\n\nexport interface ToggleGroupItemProps\n extends ButtonHTMLAttributes<HTMLButtonElement> {\n /** String identifier — used as the group's `value` when this item is selected. */\n name: string;\n /** Visible label. Omit for icon-only items (provide `aria-label` instead). */\n label?: string;\n /** Icon from `@gnome-ui/icons`. */\n icon?: IconDefinition;\n}\n\n/**\n * Individual toggle inside a `ToggleGroup`.\n *\n * Can be icon-only, label-only, or icon + label. For icon-only items always\n * provide an `aria-label` so screen readers can identify the option.\n */\nexport function ToggleGroupItem({\n name,\n label,\n icon,\n disabled,\n className,\n ...props\n}: ToggleGroupItemProps) {\n const { value, onValueChange } = useToggleGroup();\n const active = value === name;\n const iconOnly = icon && !label;\n\n return (\n <button\n type=\"button\"\n role=\"radio\"\n aria-checked={active}\n tabIndex={active ? 0 : -1}\n disabled={disabled}\n onClick={() => onValueChange(name)}\n className={[\n styles.item,\n active ? styles.active : null,\n iconOnly ? styles.iconOnly : null,\n className,\n ]\n .filter(Boolean)\n .join(\" \")}\n {...props}\n >\n {icon && (\n <span className={styles.itemIcon}>\n <Icon icon={icon} size=\"md\" aria-hidden />\n </span>\n )}\n {label && <span className={styles.itemLabel}>{label}</span>}\n </button>\n );\n}\n",".wrapBox {\n display: flex;\n flex-wrap: wrap;\n flex-direction: row;\n gap: var(--wrapbox-row-gap) var(--wrapbox-gap);\n justify-content: var(--wrapbox-justify);\n align-items: var(--wrapbox-align);\n}\n\n.reverse {\n flex-wrap: wrap-reverse;\n}\n","import type { CSSProperties, HTMLAttributes, ReactNode } from \"react\";\nimport styles from \"./WrapBox.module.css\";\n\nexport type WrapBoxJustify = \"start\" | \"center\" | \"end\" | \"space-between\" | \"space-around\" | \"space-evenly\";\nexport type WrapBoxAlign = \"start\" | \"center\" | \"end\" | \"stretch\";\n\nexport interface WrapBoxProps extends HTMLAttributes<HTMLDivElement> {\n /** Gap between children on the same line. Default: `6`. */\n childSpacing?: number | string;\n /** Gap between lines. Defaults to `childSpacing`. */\n lineSpacing?: number | string;\n /** Horizontal distribution of children within each line. Default: `\"start\"`. */\n justify?: WrapBoxJustify;\n /** Cross-axis alignment of children within each line. Default: `\"center\"`. */\n align?: WrapBoxAlign;\n /** When true children wrap in the reverse direction (bottom to top). */\n wrapReverse?: boolean;\n children?: ReactNode;\n}\n\n/**\n * Flexible wrapping layout container.\n *\n * Children flow horizontally and wrap to new lines when they don't fit,\n * like words in a paragraph — without locking them into a grid.\n *\n * Mirrors `AdwWrapBox` (libadwaita 1.7 / GNOME 48).\n *\n * Pair with `Chip` for tag lists and filter rows.\n *\n * @see https://gnome.pages.gitlab.gnome.org/libadwaita/doc/main/class.WrapBox.html\n */\nexport function WrapBox({\n childSpacing = 6,\n lineSpacing,\n justify = \"start\",\n align = \"center\",\n wrapReverse = false,\n children,\n className,\n style,\n ...props\n}: WrapBoxProps) {\n const gap = typeof childSpacing === \"number\" ? `${childSpacing}px` : childSpacing;\n const rowGap = lineSpacing != null\n ? (typeof lineSpacing === \"number\" ? `${lineSpacing}px` : lineSpacing)\n : gap;\n\n const cssVars: CSSProperties = {\n \"--wrapbox-gap\": gap,\n \"--wrapbox-row-gap\": rowGap,\n \"--wrapbox-justify\": justify,\n \"--wrapbox-align\": align,\n ...style,\n } as CSSProperties;\n\n return (\n <div\n className={[\n styles.wrapBox,\n wrapReverse ? styles.reverse : null,\n className,\n ]\n .filter(Boolean)\n .join(\" \")}\n style={cssVars}\n {...props}\n >\n {children}\n </div>\n );\n}\n","/* ─── Base ──────────────────────────────────────────────────────────────────── */\n\n.chip {\n display: inline-flex;\n align-items: center;\n gap: 4px;\n\n padding: 3px 10px;\n border-radius: var(--gnome-radius-pill);\n\n font-family: var(--gnome-font-family);\n font-size: var(--gnome-font-size-caption, 0.875rem);\n font-weight: var(--gnome-font-weight-normal, 400);\n line-height: 1.4;\n color: var(--gnome-window-fg-color, rgba(0, 0, 0, 0.8));\n white-space: nowrap;\n\n background-color: var(--gnome-card-bg-color, #ffffff);\n border: 1px solid var(--gnome-light-3, #deddda);\n\n /* Not a button by default */\n appearance: none;\n -webkit-appearance: none;\n cursor: default;\n user-select: none;\n outline: none;\n}\n\n/* ─── Selectable (toggle button) ────────────────────────────────────────────── */\n\n.selectable {\n cursor: pointer;\n transition:\n background-color var(--gnome-duration-fast) var(--gnome-easing-default),\n border-color var(--gnome-duration-fast) var(--gnome-easing-default);\n}\n\n.selectable:hover:not(:disabled) {\n background-color: color-mix(in srgb, var(--gnome-card-bg-color, #ffffff) 85%, black);\n}\n\n.selectable:active:not(:disabled) {\n background-color: color-mix(in srgb, var(--gnome-card-bg-color, #ffffff) 70%, black);\n}\n\n.selectable:focus-visible {\n box-shadow:\n 0 0 0 var(--gnome-focus-ring-offset, 2px) var(--gnome-window-bg-color, #fafafa),\n 0 0 0 calc(var(--gnome-focus-ring-offset, 2px) + var(--gnome-focus-ring-width, 2px))\n var(--gnome-focus-ring-color, #3584e4);\n}\n\n/* ─── Selected state ────────────────────────────────────────────────────────── */\n\n.selected {\n background-color: color-mix(in srgb, var(--gnome-accent-bg-color, #3584e4) 15%, transparent);\n border-color: color-mix(in srgb, var(--gnome-accent-bg-color, #3584e4) 50%, transparent);\n color: var(--gnome-accent-color, #3584e4);\n font-weight: var(--gnome-font-weight-semibold, 600);\n}\n\n.selected.selectable:hover:not(:disabled) {\n background-color: color-mix(in srgb, var(--gnome-accent-bg-color, #3584e4) 22%, transparent);\n}\n\n/* ─── Disabled ──────────────────────────────────────────────────────────────── */\n\n.disabled {\n opacity: var(--gnome-opacity-disabled, 0.5);\n cursor: not-allowed;\n pointer-events: none;\n}\n\n/* ─── Leading icon ──────────────────────────────────────────────────────────── */\n\n.icon {\n display: flex;\n align-items: center;\n flex-shrink: 0;\n margin-inline-start: -2px;\n}\n\n/* ─── Label ─────────────────────────────────────────────────────────────────── */\n\n.label {\n /* nothing extra needed */\n}\n\n/* ─── Remove button ─────────────────────────────────────────────────────────── */\n\n.remove {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n\n appearance: none;\n -webkit-appearance: none;\n border: none;\n background: transparent;\n padding: 0;\n margin-inline-end: -4px;\n cursor: pointer;\n outline: none;\n\n color: inherit;\n opacity: 0.6;\n border-radius: var(--gnome-radius-pill);\n transition: opacity var(--gnome-duration-fast) var(--gnome-easing-default);\n}\n\n.remove:hover:not(:disabled) {\n opacity: 1;\n}\n\n.remove:focus-visible {\n opacity: 1;\n box-shadow:\n 0 0 0 2px var(--gnome-window-bg-color, #fafafa),\n 0 0 0 4px var(--gnome-focus-ring-color, #3584e4);\n}\n\n/* ─── Dark mode ─────────────────────────────────────────────────────────────── */\n\n@media (prefers-color-scheme: dark) {\n .chip {\n background-color: var(--gnome-card-bg-color, #383838);\n border-color: rgba(255, 255, 255, 0.12);\n }\n\n .selectable:hover:not(:disabled) {\n background-color: color-mix(in srgb, var(--gnome-card-bg-color, #383838) 85%, white);\n }\n\n .selectable:active:not(:disabled) {\n background-color: color-mix(in srgb, var(--gnome-card-bg-color, #383838) 70%, white);\n }\n\n .selected {\n background-color: color-mix(in srgb, var(--gnome-accent-bg-color, #3584e4) 25%, transparent);\n border-color: color-mix(in srgb, var(--gnome-accent-bg-color, #3584e4) 60%, transparent);\n color: var(--gnome-accent-color, #78aeed);\n }\n\n .selected.selectable:hover:not(:disabled) {\n background-color: color-mix(in srgb, var(--gnome-accent-bg-color, #3584e4) 35%, transparent);\n }\n\n .selectable:focus-visible {\n box-shadow:\n 0 0 0 var(--gnome-focus-ring-offset, 2px) var(--gnome-window-bg-color, #242424),\n 0 0 0 calc(var(--gnome-focus-ring-offset, 2px) + var(--gnome-focus-ring-width, 2px))\n var(--gnome-focus-ring-color, #78aeed);\n }\n}\n\n/* ─── Reduced motion ────────────────────────────────────────────────────────── */\n\n@media (prefers-reduced-motion: reduce) {\n .selectable,\n .remove {\n transition: none;\n }\n}\n","import type { HTMLAttributes } from \"react\";\nimport type { IconDefinition } from \"@gnome-ui/icons\";\nimport { Icon } from \"../Icon\";\nimport { Close } from \"@gnome-ui/icons\";\nimport styles from \"./Chip.module.css\";\n\nexport interface ChipProps extends HTMLAttributes<HTMLElement> {\n /** Text label displayed inside the chip. */\n label: string;\n /** Leading icon from `@gnome-ui/icons`. */\n icon?: IconDefinition;\n /**\n * When provided, renders a remove (×) button and calls this handler.\n * The chip root becomes a `<span>`; only the remove button is interactive.\n */\n onRemove?: () => void;\n /**\n * When true the chip renders as a toggle button.\n * Use `selected` + `onToggle` to control its state.\n */\n selectable?: boolean;\n /** Active/selected state. Only relevant when `selectable` is true. */\n selected?: boolean;\n /**\n * Called when a selectable chip is clicked.\n * Only relevant when `selectable` is true.\n */\n onToggle?: () => void;\n /** Disabled state — applies to both selectable chips and the remove button. */\n disabled?: boolean;\n}\n\n/**\n * Compact pill-shaped label for tags, filters, and selection states.\n *\n * Three usage modes:\n * - **Static** — just a visual label (no `onRemove`, no `selectable`).\n * - **Removable** — add `onRemove` to show a × button.\n * - **Selectable** — add `selectable` + `selected` + `onToggle` for toggle behaviour.\n *\n * Pair with `WrapBox` for multi-chip layouts.\n */\nexport function Chip({\n label,\n icon,\n onRemove,\n selectable = false,\n selected = false,\n onToggle,\n disabled = false,\n className,\n ...props\n}: ChipProps) {\n const isInteractive = selectable && !onRemove;\n\n const chipClasses = [\n styles.chip,\n selected ? styles.selected : null,\n disabled ? styles.disabled : null,\n isInteractive ? styles.selectable : null,\n className,\n ]\n .filter(Boolean)\n .join(\" \");\n\n const content = (\n <>\n {icon && (\n <span className={styles.icon}>\n <Icon icon={icon} size=\"sm\" aria-hidden />\n </span>\n )}\n <span className={styles.label}>{label}</span>\n {onRemove && (\n <button\n type=\"button\"\n className={styles.remove}\n aria-label={`Remove ${label}`}\n disabled={disabled}\n onClick={(e) => {\n e.stopPropagation();\n onRemove();\n }}\n tabIndex={disabled ? -1 : 0}\n >\n <Icon icon={Close} size=\"sm\" aria-hidden />\n </button>\n )}\n </>\n );\n\n if (isInteractive) {\n return (\n <button\n type=\"button\"\n role=\"checkbox\"\n aria-checked={selected}\n aria-label={label}\n disabled={disabled}\n onClick={onToggle}\n className={chipClasses}\n {...(props as HTMLAttributes<HTMLButtonElement>)}\n >\n {content}\n </button>\n );\n }\n\n return (\n <span\n className={chipClasses}\n {...props}\n >\n {content}\n </span>\n );\n}\n","/* ─── Backdrop ──────────────────────────────────────────────────────────────── */\n\n.backdrop {\n position: fixed;\n inset: 0;\n z-index: 10000;\n\n display: flex;\n align-items: center;\n justify-content: center;\n\n background-color: rgba(0, 0, 0, 0.5);\n backdrop-filter: blur(4px);\n -webkit-backdrop-filter: blur(4px);\n\n animation: backdrop-in var(--gnome-duration-normal, 200ms) var(--gnome-easing-default, ease) both;\n}\n\n@keyframes backdrop-in {\n from { opacity: 0; }\n to { opacity: 1; }\n}\n\n/* ─── Dialog card ───────────────────────────────────────────────────────────── */\n\n.dialog {\n display: flex;\n flex-direction: column;\n\n width: min(580px, calc(100vw - 48px));\n max-height: min(640px, calc(100vh - 80px));\n\n background-color: var(--gnome-window-bg-color, #fafafa);\n color: var(--gnome-window-fg-color, rgba(0, 0, 0, 0.8));\n\n border-radius: var(--gnome-radius-xl, 16px);\n box-shadow:\n 0 8px 32px rgba(0, 0, 0, 0.25),\n 0 0 0 1px rgba(0, 0, 0, 0.08);\n\n overflow: hidden;\n\n animation: dialog-in var(--gnome-duration-normal, 200ms) var(--gnome-easing-default, ease) both;\n}\n\n@keyframes dialog-in {\n from {\n opacity: 0;\n transform: scale(0.94) translateY(-8px);\n }\n to {\n opacity: 1;\n transform: scale(1) translateY(0);\n }\n}\n\n/* ─── Header ────────────────────────────────────────────────────────────────── */\n\n.header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: var(--gnome-space-3, 18px) var(--gnome-space-3, 18px) var(--gnome-space-2, 12px);\n flex-shrink: 0;\n}\n\n.title {\n font-family: var(--gnome-font-family);\n font-size: var(--gnome-font-size-title-4, 1.125rem);\n font-weight: var(--gnome-font-weight-bold, 700);\n line-height: 1.3;\n}\n\n.closeBtn {\n appearance: none;\n border: none;\n background: transparent;\n cursor: pointer;\n outline: none;\n\n display: flex;\n align-items: center;\n justify-content: center;\n\n width: 32px;\n height: 32px;\n border-radius: var(--gnome-radius-pill);\n\n font-size: 1.25rem;\n line-height: 1;\n color: var(--gnome-window-fg-color, rgba(0, 0, 0, 0.8));\n opacity: 0.6;\n\n transition: background-color var(--gnome-duration-fast) var(--gnome-easing-default),\n opacity var(--gnome-duration-fast) var(--gnome-easing-default);\n}\n\n.closeBtn:hover {\n background-color: rgba(0, 0, 0, 0.07);\n opacity: 1;\n}\n\n.closeBtn:focus-visible {\n opacity: 1;\n box-shadow:\n 0 0 0 2px var(--gnome-window-bg-color, #fafafa),\n 0 0 0 4px var(--gnome-focus-ring-color, #3584e4);\n}\n\n/* ─── Search ────────────────────────────────────────────────────────────────── */\n\n.searchRow {\n position: relative;\n display: flex;\n align-items: center;\n margin: 0 var(--gnome-space-3, 18px) var(--gnome-space-2, 12px);\n flex-shrink: 0;\n\n background-color: var(--gnome-card-bg-color, #ffffff);\n border: 1px solid var(--gnome-light-3, #deddda);\n border-radius: var(--gnome-radius-md, 8px);\n}\n\n.searchIcon {\n padding: 0 var(--gnome-space-1, 6px) 0 var(--gnome-space-2, 12px);\n font-size: 1.1rem;\n opacity: 0.5;\n pointer-events: none;\n user-select: none;\n}\n\n.searchInput {\n flex: 1;\n border: none;\n background: transparent;\n outline: none;\n padding: 7px 0;\n\n font-family: var(--gnome-font-family);\n font-size: var(--gnome-font-size-body, 1rem);\n color: var(--gnome-window-fg-color, rgba(0, 0, 0, 0.8));\n}\n\n.searchInput::placeholder {\n opacity: 0.5;\n}\n\n/* hide browser-native × */\n.searchInput::-webkit-search-cancel-button { display: none; }\n\n.searchClear {\n appearance: none;\n border: none;\n background: transparent;\n cursor: pointer;\n outline: none;\n padding: 0 var(--gnome-space-2, 12px);\n\n font-size: 1rem;\n color: var(--gnome-window-fg-color, rgba(0, 0, 0, 0.8));\n opacity: 0.5;\n transition: opacity var(--gnome-duration-fast) var(--gnome-easing-default);\n}\n\n.searchClear:hover { opacity: 1; }\n\n.searchRow:focus-within {\n border-color: var(--gnome-accent-color, #3584e4);\n box-shadow: 0 0 0 2px color-mix(in srgb, var(--gnome-accent-color, #3584e4) 25%, transparent);\n}\n\n/* ─── Scrollable body ───────────────────────────────────────────────────────── */\n\n.body {\n overflow-y: auto;\n padding: 0 var(--gnome-space-3, 18px) var(--gnome-space-3, 18px);\n display: flex;\n flex-direction: column;\n gap: var(--gnome-space-3, 18px);\n}\n\n.empty {\n text-align: center;\n padding: var(--gnome-space-4, 24px) 0;\n font-family: var(--gnome-font-family);\n font-size: var(--gnome-font-size-body, 1rem);\n opacity: 0.5;\n margin: 0;\n}\n\n/* ─── Section ───────────────────────────────────────────────────────────────── */\n\n.section {\n display: flex;\n flex-direction: column;\n gap: 2px;\n}\n\n.sectionTitle {\n margin: 0 0 var(--gnome-space-1, 6px);\n font-family: var(--gnome-font-family);\n font-size: var(--gnome-font-size-caption, 0.875rem);\n font-weight: var(--gnome-font-weight-semibold, 600);\n text-transform: uppercase;\n letter-spacing: 0.06em;\n opacity: 0.5;\n}\n\n.list {\n list-style: none;\n margin: 0;\n padding: 0;\n display: flex;\n flex-direction: column;\n gap: 1px;\n\n background-color: var(--gnome-card-bg-color, #ffffff);\n border: 1px solid rgba(0, 0, 0, 0.08);\n border-radius: var(--gnome-radius-md, 8px);\n overflow: hidden;\n}\n\n/* ─── Shortcut row ──────────────────────────────────────────────────────────── */\n\n.row {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: var(--gnome-space-3, 18px);\n padding: 8px var(--gnome-space-2, 12px);\n border-bottom: 1px solid rgba(0, 0, 0, 0.05);\n}\n\n.row:last-child {\n border-bottom: none;\n}\n\n/* ─── Key caps ──────────────────────────────────────────────────────────────── */\n\n.keys {\n display: inline-flex;\n align-items: center;\n gap: 3px;\n flex-shrink: 0;\n}\n\n.keyCap {\n display: inline-flex;\n align-items: center;\n gap: 3px;\n}\n\n.keyCap kbd {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n\n padding: 2px 7px;\n min-width: 24px;\n\n font-family: var(--gnome-font-family);\n font-size: var(--gnome-font-size-caption, 0.875rem);\n font-weight: var(--gnome-font-weight-semibold, 600);\n font-style: normal;\n line-height: 1.6;\n\n background-color: var(--gnome-window-bg-color, #fafafa);\n border: 1px solid rgba(0, 0, 0, 0.18);\n border-bottom-width: 2px;\n border-radius: var(--gnome-radius-sm, 4px);\n box-shadow: 0 1px 0 rgba(0, 0, 0, 0.08);\n\n color: var(--gnome-window-fg-color, rgba(0, 0, 0, 0.8));\n white-space: nowrap;\n}\n\n.plus {\n font-size: var(--gnome-font-size-caption, 0.875rem);\n opacity: 0.4;\n}\n\n/* ─── Description ───────────────────────────────────────────────────────────── */\n\n.description {\n font-family: var(--gnome-font-family);\n font-size: var(--gnome-font-size-body, 1rem);\n color: var(--gnome-window-fg-color, rgba(0, 0, 0, 0.8));\n text-align: end;\n}\n\n/* ─── Dark mode ─────────────────────────────────────────────────────────────── */\n\n@media (prefers-color-scheme: dark) {\n .backdrop {\n background-color: rgba(0, 0, 0, 0.65);\n }\n\n .dialog {\n background-color: var(--gnome-window-bg-color, #242424);\n box-shadow:\n 0 8px 32px rgba(0, 0, 0, 0.5),\n 0 0 0 1px rgba(255, 255, 255, 0.08);\n }\n\n .closeBtn:hover {\n background-color: rgba(255, 255, 255, 0.08);\n }\n\n .searchRow {\n background-color: var(--gnome-card-bg-color, #383838);\n border-color: rgba(255, 255, 255, 0.12);\n }\n\n .list {\n background-color: var(--gnome-card-bg-color, #383838);\n border-color: rgba(255, 255, 255, 0.08);\n }\n\n .row {\n border-bottom-color: rgba(255, 255, 255, 0.05);\n }\n\n .keyCap kbd {\n background-color: var(--gnome-window-bg-color, #242424);\n border-color: rgba(255, 255, 255, 0.2);\n box-shadow: 0 1px 0 rgba(0, 0, 0, 0.3);\n }\n}\n\n/* ─── Reduced motion ────────────────────────────────────────────────────────── */\n\n@media (prefers-reduced-motion: reduce) {\n .backdrop,\n .dialog {\n animation: none;\n }\n\n .closeBtn,\n .searchClear {\n transition: none;\n }\n}\n","import {\n useState,\n useEffect,\n useRef,\n useCallback,\n type KeyboardEvent,\n} from \"react\";\nimport { createPortal } from \"react-dom\";\nimport styles from \"./ShortcutsDialog.module.css\";\n\n// ─── Public types ─────────────────────────────────────────────────────────────\n\nexport interface ShortcutEntry {\n /**\n * Key combination tokens, e.g. `[\"Ctrl\", \"S\"]` or `[\"F5\"]`.\n * Each token is rendered as a styled `<kbd>` key cap.\n */\n keys: string[];\n /** Human-readable description of what the shortcut does. */\n description: string;\n}\n\nexport interface ShortcutsSection {\n /** Section heading, e.g. \"File\", \"Edit\". */\n title: string;\n shortcuts: ShortcutEntry[];\n}\n\nexport interface ShortcutsDialogProps {\n /** Whether the dialog is visible. */\n open: boolean;\n /** Called when the dialog is dismissed (Escape, close button). */\n onClose: () => void;\n /** Dialog heading. Defaults to `\"Keyboard Shortcuts\"`. */\n title?: string;\n /** Shortcut sections to display. */\n sections: ShortcutsSection[];\n}\n\n// ─── Focusable selector (reused from Dialog) ─────────────────────────────────\n\nconst FOCUSABLE =\n 'button:not([disabled]), [href], input:not([disabled]), [tabindex]:not([tabindex=\"-1\"])';\n\n// ─── Component ────────────────────────────────────────────────────────────────\n\n/**\n * Modal dialog listing keyboard shortcuts, grouped in sections, with\n * integrated search to filter by description or key name.\n *\n * Mirrors `AdwShortcutsDialog` (libadwaita 1.8 / GNOME 49).\n *\n * - Renders into a portal (`document.body`).\n * - Traps focus; Escape closes.\n * - Search filters across all sections in real time.\n *\n * @see https://gnome.pages.gitlab.gnome.org/libadwaita/doc/main/class.ShortcutsDialog.html\n */\nexport function ShortcutsDialog({\n open,\n onClose,\n title = \"Keyboard Shortcuts\",\n sections,\n}: ShortcutsDialogProps) {\n const [query, setQuery] = useState(\"\");\n const dialogRef = useRef<HTMLDivElement>(null);\n const searchRef = useRef<HTMLInputElement>(null);\n const previouslyFocused = useRef<Element | null>(null);\n const titleId = useRef(`shortcuts-title-${Math.random().toString(36).slice(2, 9)}`);\n\n // Save / restore focus\n useEffect(() => {\n if (open) {\n previouslyFocused.current = document.activeElement;\n // Focus the search field after mount\n requestAnimationFrame(() => searchRef.current?.focus());\n } else {\n setQuery(\"\");\n (previouslyFocused.current as HTMLElement | null)?.focus();\n }\n }, [open]);\n\n // Focus trap + Escape\n const handleKeyDown = useCallback(\n (e: KeyboardEvent<HTMLDivElement>) => {\n if (e.key === \"Escape\") {\n e.preventDefault();\n onClose();\n return;\n }\n if (e.key !== \"Tab\") return;\n\n const focusable = Array.from(\n dialogRef.current?.querySelectorAll<HTMLElement>(FOCUSABLE) ?? [],\n );\n if (focusable.length === 0) return;\n\n const first = focusable[0];\n const last = focusable[focusable.length - 1];\n\n if (e.shiftKey) {\n if (document.activeElement === first) {\n e.preventDefault();\n last.focus();\n }\n } else {\n if (document.activeElement === last) {\n e.preventDefault();\n first.focus();\n }\n }\n },\n [onClose],\n );\n\n if (!open) return null;\n\n // Filter sections by query\n const q = query.trim().toLowerCase();\n const filtered = sections\n .map((section) => ({\n ...section,\n shortcuts: section.shortcuts.filter(\n ({ description, keys }) =>\n !q ||\n description.toLowerCase().includes(q) ||\n keys.some((k) => k.toLowerCase().includes(q)),\n ),\n }))\n .filter((s) => s.shortcuts.length > 0);\n\n const node = (\n <div\n className={styles.backdrop}\n onClick={onClose}\n aria-hidden=\"true\"\n >\n <div\n ref={dialogRef}\n role=\"dialog\"\n aria-modal=\"true\"\n aria-labelledby={titleId.current}\n className={styles.dialog}\n onKeyDown={handleKeyDown}\n onClick={(e) => e.stopPropagation()}\n >\n {/* ── Header ── */}\n <div className={styles.header}>\n <span id={titleId.current} className={styles.title}>\n {title}\n </span>\n <button\n type=\"button\"\n className={styles.closeBtn}\n aria-label=\"Close\"\n onClick={onClose}\n >\n ×\n </button>\n </div>\n\n {/* ── Search ── */}\n <div className={styles.searchRow}>\n <span className={styles.searchIcon} aria-hidden=\"true\">⌕</span>\n <input\n ref={searchRef}\n type=\"search\"\n className={styles.searchInput}\n placeholder=\"Search shortcuts…\"\n value={query}\n onChange={(e) => setQuery(e.target.value)}\n aria-label=\"Search shortcuts\"\n />\n {query && (\n <button\n type=\"button\"\n className={styles.searchClear}\n aria-label=\"Clear search\"\n onClick={() => { setQuery(\"\"); searchRef.current?.focus(); }}\n >\n ×\n </button>\n )}\n </div>\n\n {/* ── Shortcut list ── */}\n <div className={styles.body} role=\"list\">\n {filtered.length === 0 ? (\n <p className={styles.empty}>No shortcuts match \"{query}\"</p>\n ) : (\n filtered.map((section) => (\n <section key={section.title} className={styles.section}>\n <h3 className={styles.sectionTitle}>{section.title}</h3>\n <ul className={styles.list}>\n {section.shortcuts.map((shortcut) => (\n <li key={shortcut.description} className={styles.row} role=\"listitem\">\n <span className={styles.keys} aria-label={shortcut.keys.join(\" + \")}>\n {shortcut.keys.map((key, i) => (\n <span key={i} className={styles.keyCap}>\n <kbd>{key}</kbd>\n {i < shortcut.keys.length - 1 && (\n <span className={styles.plus} aria-hidden=\"true\">+</span>\n )}\n </span>\n ))}\n </span>\n <span className={styles.description}>{shortcut.description}</span>\n </li>\n ))}\n </ul>\n </section>\n ))\n )}\n </div>\n </div>\n </div>\n );\n\n if (typeof document === \"undefined\") return node;\n return createPortal(node, document.body);\n}\n","/* ─── Container ─────────────────────────────────────────────────────────────── */\n\n.sidebar {\n display: flex;\n flex-direction: column;\n width: 220px;\n height: 100%;\n background-color: var(--gnome-sidebar-bg-color, #ebebeb);\n color: var(--gnome-sidebar-fg-color, rgba(0, 0, 0, 0.8));\n border-right: 1px solid var(--gnome-headerbar-border-color, rgba(0, 0, 0, 0.12));\n overflow-y: auto;\n overflow-x: hidden;\n}\n\n/* ─── List ──────────────────────────────────────────────────────────────────── */\n\n.list {\n list-style: none;\n margin: 0;\n padding: var(--gnome-space-1, 6px);\n display: flex;\n flex-direction: column;\n gap: 2px;\n}\n\n/* ─── Item wrapper ──────────────────────────────────────────────────────────── */\n\n.item {\n display: block;\n}\n\n/* ─── Item button ───────────────────────────────────────────────────────────── */\n\n.itemBtn {\n appearance: none;\n -webkit-appearance: none;\n border: none;\n background: transparent;\n font: inherit;\n text-align: start;\n cursor: pointer;\n outline: none;\n\n display: flex;\n align-items: center;\n gap: var(--gnome-space-2, 12px);\n width: 100%;\n padding: 9px var(--gnome-space-2, 12px);\n border-radius: var(--gnome-radius-md, 8px);\n min-height: 42px;\n\n color: var(--gnome-sidebar-fg-color, rgba(0, 0, 0, 0.8));\n font-family: var(--gnome-font-family);\n font-size: var(--gnome-font-size-body, 1rem);\n font-weight: var(--gnome-font-weight-normal, 400);\n\n transition: background-color var(--gnome-duration-fast) var(--gnome-easing-default);\n}\n\n.itemBtn:hover:not(:disabled):not(.active) {\n background-color: color-mix(\n in srgb,\n var(--gnome-sidebar-bg-color, #ebebeb) 88%,\n black 12%\n );\n}\n\n.itemBtn:active:not(:disabled):not(.active) {\n background-color: color-mix(\n in srgb,\n var(--gnome-sidebar-bg-color, #ebebeb) 78%,\n black 22%\n );\n}\n\n.itemBtn:focus-visible {\n box-shadow:\n 0 0 0 var(--gnome-focus-ring-offset, 2px) var(--gnome-sidebar-bg-color, #ebebeb),\n 0 0 0 calc(var(--gnome-focus-ring-offset, 2px) + var(--gnome-focus-ring-width, 2px))\n var(--gnome-focus-ring-color, #3584e4);\n}\n\n.itemBtn:disabled {\n opacity: var(--gnome-opacity-disabled, 0.5);\n cursor: not-allowed;\n}\n\n/* ─── Active ────────────────────────────────────────────────────────────────── */\n\n.active {\n background-color: var(--gnome-accent-bg-color, #3584e4);\n color: var(--gnome-accent-fg-color, #ffffff);\n font-weight: var(--gnome-font-weight-semibold, 600);\n}\n\n.active:hover:not(:disabled) {\n background-color: color-mix(in srgb, var(--gnome-accent-bg-color, #3584e4) 92%, black 8%);\n}\n\n.active:active:not(:disabled) {\n background-color: color-mix(in srgb, var(--gnome-accent-bg-color, #3584e4) 84%, black 16%);\n}\n\n/* ─── Icon ──────────────────────────────────────────────────────────────────── */\n\n.itemIcon {\n display: flex;\n align-items: center;\n flex-shrink: 0;\n opacity: 0.85;\n}\n\n.active .itemIcon {\n opacity: 1;\n}\n\n/* ─── Label ─────────────────────────────────────────────────────────────────── */\n\n.itemLabel {\n flex: 1;\n min-width: 0;\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n}\n\n/* ─── Suffix / count ────────────────────────────────────────────────────────── */\n\n.itemSuffix {\n flex-shrink: 0;\n margin-left: auto;\n display: flex;\n align-items: center;\n}\n\n.count {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n min-width: 20px;\n height: 20px;\n padding: 0 6px;\n border-radius: var(--gnome-radius-pill);\n\n font-size: var(--gnome-font-size-caption, 0.875rem);\n font-weight: var(--gnome-font-weight-semibold, 600);\n line-height: 1;\n\n /* Inactive: dim pill */\n background-color: rgba(0, 0, 0, 0.12);\n color: var(--gnome-sidebar-fg-color, rgba(0, 0, 0, 0.8));\n}\n\n.active .count {\n /* Active: white pill on accent */\n background-color: rgba(255, 255, 255, 0.25);\n color: var(--gnome-accent-fg-color, #ffffff);\n}\n\n/* ─── Dark mode ─────────────────────────────────────────────────────────────── */\n\n@media (prefers-color-scheme: dark) {\n .sidebar {\n border-right-color: rgba(255, 255, 255, 0.1);\n }\n\n .itemBtn:hover:not(:disabled):not(.active) {\n background-color: color-mix(\n in srgb,\n var(--gnome-sidebar-bg-color, #303030) 85%,\n white 15%\n );\n }\n\n .itemBtn:active:not(:disabled):not(.active) {\n background-color: color-mix(\n in srgb,\n var(--gnome-sidebar-bg-color, #303030) 75%,\n white 25%\n );\n }\n\n .count {\n background-color: rgba(255, 255, 255, 0.12);\n color: var(--gnome-sidebar-fg-color, rgba(255, 255, 255, 0.87));\n }\n}\n\n/* ─── Reduced motion ────────────────────────────────────────────────────────── */\n\n@media (prefers-reduced-motion: reduce) {\n .itemBtn {\n transition: none;\n }\n}\n","import {\n createContext,\n useContext,\n useRef,\n type HTMLAttributes,\n type KeyboardEvent,\n type ReactNode,\n} from \"react\";\nimport styles from \"./ViewSwitcherSidebar.module.css\";\n\n// ─── Context ──────────────────────────────────────────────────────────────────\n\ninterface ViewSwitcherSidebarContextValue {\n value: string;\n onValueChange: (value: string) => void;\n}\n\nconst ViewSwitcherSidebarContext =\n createContext<ViewSwitcherSidebarContextValue | null>(null);\n\nexport function useViewSwitcherSidebar() {\n const ctx = useContext(ViewSwitcherSidebarContext);\n if (!ctx)\n throw new Error(\n \"ViewSwitcherSidebarItem must be used inside ViewSwitcherSidebar\",\n );\n return ctx;\n}\n\n// ─── Component ────────────────────────────────────────────────────────────────\n\nexport interface ViewSwitcherSidebarProps extends HTMLAttributes<HTMLElement> {\n /** Name of the currently active view. */\n value: string;\n /** Called with the new view name when the user selects an item. */\n onValueChange: (value: string) => void;\n /** Accessible label for the group. Defaults to `\"Views\"`. */\n \"aria-label\"?: string;\n children?: ReactNode;\n}\n\n/**\n * Sidebar-style view switcher for apps with more than 4 top-level views,\n * or when the sidebar layout fits better than a header-bar `ViewSwitcher`.\n *\n * Mirrors `AdwViewSwitcherSidebar` (libadwaita 1.9 / GNOME 50), the modern\n * replacement for `GtkStackSidebar`.\n *\n * Compose with `ViewSwitcherSidebarItem` for each view. The active item is\n * highlighted with the accent colour; keyboard ↑ / ↓ cycles through items.\n *\n * @see https://gnome.pages.gitlab.gnome.org/libadwaita/doc/main/class.ViewSwitcherSidebar.html\n *\n * @example\n * ```tsx\n * <ViewSwitcherSidebar value={view} onValueChange={setView}>\n * <ViewSwitcherSidebarItem name=\"photos\" label=\"Photos\" icon={MediaPlay} />\n * <ViewSwitcherSidebarItem name=\"albums\" label=\"Albums\" icon={Star} count={12} />\n * </ViewSwitcherSidebar>\n * ```\n */\nexport function ViewSwitcherSidebar({\n value,\n onValueChange,\n \"aria-label\": ariaLabel = \"Views\",\n children,\n className,\n ...props\n}: ViewSwitcherSidebarProps) {\n const groupRef = useRef<HTMLUListElement>(null);\n\n function handleKeyDown(e: KeyboardEvent<HTMLElement>) {\n const items = Array.from(\n groupRef.current?.querySelectorAll<HTMLButtonElement>(\n \"[role=radio]:not(:disabled)\",\n ) ?? [],\n );\n const idx = items.findIndex((el) => el === document.activeElement);\n if (idx === -1) return;\n\n let next = idx;\n if (e.key === \"ArrowDown\" || e.key === \"ArrowRight\")\n next = (idx + 1) % items.length;\n else if (e.key === \"ArrowUp\" || e.key === \"ArrowLeft\")\n next = (idx - 1 + items.length) % items.length;\n else if (e.key === \"Home\") next = 0;\n else if (e.key === \"End\") next = items.length - 1;\n else return;\n\n e.preventDefault();\n items[next].focus();\n items[next].click();\n }\n\n return (\n <ViewSwitcherSidebarContext.Provider value={{ value, onValueChange }}>\n <nav\n className={[styles.sidebar, className].filter(Boolean).join(\" \")}\n onKeyDown={handleKeyDown}\n {...props}\n >\n <ul\n ref={groupRef}\n role=\"radiogroup\"\n aria-label={ariaLabel}\n className={styles.list}\n >\n {children}\n </ul>\n </nav>\n </ViewSwitcherSidebarContext.Provider>\n );\n}\n","import type { ButtonHTMLAttributes, ReactNode } from \"react\";\nimport type { IconDefinition } from \"@gnome-ui/icons\";\nimport { Icon } from \"../Icon\";\nimport { useViewSwitcherSidebar } from \"./ViewSwitcherSidebar\";\nimport styles from \"./ViewSwitcherSidebar.module.css\";\n\nexport interface ViewSwitcherSidebarItemProps\n extends ButtonHTMLAttributes<HTMLButtonElement> {\n /** String identifier — matched against the group's `value`. */\n name: string;\n /** View label. */\n label: string;\n /** Icon from `@gnome-ui/icons`. */\n icon?: IconDefinition;\n /**\n * Optional trailing count (unread messages, items…).\n * Rendered as a small badge at the trailing edge.\n */\n count?: number;\n /**\n * Optional trailing widget. Takes precedence over `count`.\n * Use for custom badges, buttons, or status indicators.\n */\n suffix?: ReactNode;\n}\n\n/**\n * Individual view item inside a `ViewSwitcherSidebar`.\n *\n * Renders as `role=\"radio\"`. Only the active item is in the natural Tab order;\n * all others use `tabIndex={-1}` and are reached via arrow-key navigation.\n */\nexport function ViewSwitcherSidebarItem({\n name,\n label,\n icon,\n count,\n suffix,\n disabled,\n className,\n ...props\n}: ViewSwitcherSidebarItemProps) {\n const { value, onValueChange } = useViewSwitcherSidebar();\n const active = value === name;\n const trailing = suffix ?? (count != null ? count : null);\n\n return (\n <li className={styles.item}>\n <button\n type=\"button\"\n role=\"radio\"\n aria-checked={active}\n tabIndex={active ? 0 : -1}\n disabled={disabled}\n onClick={() => onValueChange(name)}\n className={[\n styles.itemBtn,\n active ? styles.active : null,\n className,\n ]\n .filter(Boolean)\n .join(\" \")}\n {...props}\n >\n {icon && (\n <span className={styles.itemIcon}>\n <Icon icon={icon} size=\"md\" aria-hidden />\n </span>\n )}\n <span className={styles.itemLabel}>{label}</span>\n {trailing != null && (\n <span className={styles.itemSuffix}>\n {typeof trailing === \"number\" ? (\n <span className={styles.count}>{trailing > 99 ? \"99+\" : trailing}</span>\n ) : (\n trailing\n )}\n </span>\n )}\n </button>\n </li>\n );\n}\n","import {\n useState,\n useEffect,\n useRef,\n type HTMLAttributes,\n type ReactNode,\n type CSSProperties,\n} from \"react\";\n\n// ─── Public types ─────────────────────────────────────────────────────────────\n\nexport interface BreakpointDefinition {\n /**\n * Identifier for this breakpoint, e.g. `\"compact\"` or `\"narrow\"`.\n * Exposed as `data-breakpoint` on the container element for CSS targeting.\n */\n name: string;\n /**\n * Container width threshold in pixels.\n * This breakpoint becomes active when the container width is ≤ this value.\n */\n maxWidth: number;\n}\n\nexport interface BreakpointBinState {\n /**\n * Name of the currently active breakpoint, or `null` when the container\n * is wider than all defined breakpoints.\n */\n activeBreakpoint: string | null;\n /** Current container width in px (from `ResizeObserver`). */\n width: number;\n}\n\nexport interface BreakpointBinProps\n extends Omit<HTMLAttributes<HTMLDivElement>, \"children\"> {\n /**\n * Breakpoint definitions sorted by `maxWidth` ascending.\n * The active breakpoint is the smallest `maxWidth` ≥ the container's current width.\n */\n breakpoints: BreakpointDefinition[];\n /**\n * Render prop that receives the current breakpoint state.\n *\n * @example\n * ```tsx\n * <BreakpointBin breakpoints={[{ name: \"compact\", maxWidth: 400 }]}>\n * {({ activeBreakpoint }) =>\n * activeBreakpoint === \"compact\" ? <CompactLayout /> : <WideLayout />\n * }\n * </BreakpointBin>\n * ```\n */\n children: (state: BreakpointBinState) => ReactNode;\n}\n\n// ─── Component ────────────────────────────────────────────────────────────────\n\n/**\n * Container that fires layout changes when **its own width** crosses defined\n * thresholds — the CSS container-query equivalent of `AdwBreakpointBin`\n * (libadwaita 1.9 / GNOME 50).\n *\n * Unlike `useBreakpoint` (which watches the viewport), `BreakpointBin` uses\n * `ResizeObserver` to watch the element itself. This makes it composable:\n * the same component can behave differently depending on how much space it\n * is given by its parent, regardless of the viewport size.\n *\n * ### How it works\n * - Breakpoints are evaluated in ascending `maxWidth` order.\n * - The active breakpoint is the first one whose `maxWidth` ≥ the container width.\n * - When no breakpoint matches (container is wider than all), `activeBreakpoint` is `null`.\n * - The active name is set as `data-breakpoint` on the wrapper `<div>` for CSS targeting.\n *\n * ### CSS targeting\n * ```css\n * .myWidget[data-breakpoint=\"compact\"] .layout { flex-direction: column; }\n * ```\n *\n * @see https://gnome.pages.gitlab.gnome.org/libadwaita/doc/main/class.BreakpointBin.html\n */\nexport function BreakpointBin({\n breakpoints,\n children,\n className,\n style,\n ...props\n}: BreakpointBinProps) {\n const containerRef = useRef<HTMLDivElement>(null);\n const [state, setState] = useState<BreakpointBinState>({\n activeBreakpoint: null,\n width: 0,\n });\n\n useEffect(() => {\n const el = containerRef.current;\n if (!el) return;\n\n // Sort breakpoints ascending so we find the smallest matching threshold\n const sorted = [...breakpoints].sort((a, b) => a.maxWidth - b.maxWidth);\n\n const evaluate = (width: number): BreakpointBinState => {\n const match = sorted.find((bp) => width <= bp.maxWidth) ?? null;\n return { activeBreakpoint: match?.name ?? null, width };\n };\n\n // Initial measurement\n setState(evaluate(el.offsetWidth));\n\n const observer = new ResizeObserver((entries) => {\n const entry = entries[0];\n if (!entry) return;\n // contentBoxSize is more reliable than contentRect for inline-size\n const width =\n entry.contentBoxSize?.[0]?.inlineSize ?? entry.contentRect.width;\n setState(evaluate(width));\n });\n\n observer.observe(el);\n return () => observer.disconnect();\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [JSON.stringify(breakpoints)]);\n\n const containerStyle: CSSProperties = {\n // Ensure the container establishes a containing block\n // so ResizeObserver reflects actual layout width\n minWidth: 0,\n ...style,\n };\n\n return (\n <div\n ref={containerRef}\n className={className}\n style={containerStyle}\n data-breakpoint={state.activeBreakpoint ?? undefined}\n {...props}\n >\n {children(state)}\n </div>\n );\n}\n","/* ─── Base row ─────────────────────────────────────────────────────────────── */\n\n.row {\n display: flex;\n align-items: center;\n justify-content: center;\n gap: var(--gnome-space-2, 12px);\n padding: var(--gnome-space-2, 12px) var(--gnome-space-4, 24px);\n min-height: 52px;\n width: 100%;\n\n /* Reset button */\n appearance: none;\n -webkit-appearance: none;\n background: transparent;\n border: none;\n font: inherit;\n outline: none;\n cursor: pointer;\n text-align: center;\n\n transition: background-color var(--gnome-duration-fast) var(--gnome-easing-default);\n}\n\n.row:hover {\n background-color: var(--gnome-hover-overlay);\n}\n\n.row:active {\n background-color: var(--gnome-active-overlay);\n}\n\n.row:focus-visible {\n box-shadow:\n inset 0 0 0 var(--gnome-focus-ring-offset, 2px) var(--gnome-window-bg-color, #fafafa),\n inset 0 0 0 calc(var(--gnome-focus-ring-offset, 2px) + var(--gnome-focus-ring-width, 3px))\n var(--gnome-focus-ring-color, #3584e4);\n}\n\n.row:disabled {\n opacity: var(--gnome-opacity-disabled, 0.5);\n cursor: not-allowed;\n pointer-events: none;\n}\n\n/* ─── Title ─────────────────────────────────────────────────────────────────── */\n\n.title {\n font-family: var(--gnome-font-family);\n font-size: var(--gnome-font-size-body, 1rem);\n font-weight: var(--gnome-font-weight-semibold, 600);\n line-height: var(--gnome-line-height-body, 1.5);\n flex: 1;\n text-align: center;\n}\n\n/* ─── Leading / trailing icons ─────────────────────────────────────────────── */\n\n.leading,\n.trailing {\n display: flex;\n align-items: center;\n flex-shrink: 0;\n}\n\n/* ─── Variants ─────────────────────────────────────────────────────────────── */\n\n.default .title {\n color: var(--gnome-window-fg-color, rgba(0, 0, 0, 0.8));\n}\n\n.default .leading,\n.default .trailing {\n color: var(--gnome-window-fg-color, rgba(0, 0, 0, 0.8));\n}\n\n.suggested .title {\n color: var(--gnome-accent-color, #3584e4);\n}\n\n.suggested .leading,\n.suggested .trailing {\n color: var(--gnome-accent-color, #3584e4);\n}\n\n.destructive .title {\n color: var(--gnome-destructive-color, #e01b24);\n}\n\n.destructive .leading,\n.destructive .trailing {\n color: var(--gnome-destructive-color, #e01b24);\n}\n\n/* ─── High contrast ─────────────────────────────────────────────────────────── */\n\n@media (prefers-contrast: more) {\n .row:focus-visible {\n box-shadow:\n inset 0 0 0 var(--gnome-focus-ring-offset, 2px) var(--gnome-window-bg-color, #fafafa),\n inset 0 0 0 calc(var(--gnome-focus-ring-offset, 2px) + var(--gnome-focus-ring-width, 3px))\n var(--gnome-focus-ring-color, #3584e4),\n inset 0 0 0 calc(var(--gnome-focus-ring-offset, 2px) + var(--gnome-focus-ring-width, 3px) + 1px)\n var(--gnome-window-bg-color, #fafafa);\n }\n}\n","import type { ButtonHTMLAttributes, ReactNode } from \"react\";\nimport styles from \"./ButtonRow.module.css\";\n\nexport type ButtonRowVariant = \"default\" | \"suggested\" | \"destructive\";\n\nexport interface ButtonRowProps extends ButtonHTMLAttributes<HTMLButtonElement> {\n /** Label displayed centered in the row. */\n title: string;\n /** Visual style that colours the title text. */\n variant?: ButtonRowVariant;\n /** Icon placed at the leading edge. */\n leading?: ReactNode;\n /** Icon placed at the trailing edge. */\n trailing?: ReactNode;\n}\n\n/**\n * Full-width activatable row styled as a button inside a `BoxedList`.\n *\n * Mirrors `AdwButtonRow` — use when an entire list row should trigger a single\n * action. Prefer `ActionRow` with `interactive` when the row also needs a\n * title/subtitle layout; prefer `ButtonRow` for single-label CTA rows.\n *\n * @see https://gnome.pages.gitlab.gnome.org/libadwaita/doc/main/class.ButtonRow.html\n */\nexport function ButtonRow({\n title,\n variant = \"default\",\n leading,\n trailing,\n className,\n ...props\n}: ButtonRowProps) {\n const classes = [\n styles.row,\n styles[variant],\n className,\n ]\n .filter(Boolean)\n .join(\" \");\n\n return (\n <button className={classes} {...props}>\n {leading && <span className={styles.leading}>{leading}</span>}\n <span className={styles.title}>{title}</span>\n {trailing && <span className={styles.trailing}>{trailing}</span>}\n </button>\n );\n}\n","/* ─── Container ─────────────────────────────────────────────────────────────── */\n\n.container {\n display: inline-flex;\n align-items: stretch;\n border-radius: var(--gnome-radius-md);\n border: 1px solid transparent;\n overflow: visible;\n position: relative;\n}\n\n/* ─── Shared button base ────────────────────────────────────────────────────── */\n\n.primary,\n.toggle {\n appearance: none;\n -webkit-appearance: none;\n border: none;\n outline: none;\n font: inherit;\n cursor: pointer;\n user-select: none;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n\n font-family: var(--gnome-font-family);\n font-size: var(--gnome-font-size-body);\n font-weight: var(--gnome-font-weight-semibold);\n line-height: 1;\n white-space: nowrap;\n\n min-height: 34px;\n\n transition:\n background-color var(--gnome-duration-fast) var(--gnome-easing-default),\n box-shadow var(--gnome-duration-fast) var(--gnome-easing-default);\n}\n\n.primary:focus-visible,\n.toggle:focus-visible {\n box-shadow:\n 0 0 0 var(--gnome-focus-ring-offset) var(--gnome-window-bg-color),\n 0 0 0 calc(var(--gnome-focus-ring-offset) + var(--gnome-focus-ring-width)) var(--gnome-focus-ring-color);\n position: relative;\n z-index: 1;\n}\n\n.primary:disabled,\n.toggle:disabled {\n opacity: var(--gnome-opacity-disabled);\n cursor: not-allowed;\n pointer-events: none;\n}\n\n/* ─── Primary half ──────────────────────────────────────────────────────────── */\n\n.primary {\n border-radius: var(--gnome-radius-md) 0 0 var(--gnome-radius-md);\n padding: var(--gnome-space-1) var(--gnome-space-2);\n background: transparent;\n}\n\n/* ─── Separator ─────────────────────────────────────────────────────────────── */\n\n.separator {\n display: block;\n width: 1px;\n align-self: stretch;\n flex-shrink: 0;\n}\n\n/* ─── Toggle (arrow) half ───────────────────────────────────────────────────── */\n\n.toggle {\n border-radius: 0 var(--gnome-radius-md) var(--gnome-radius-md) 0;\n padding: var(--gnome-space-1) 8px;\n background: transparent;\n}\n\n.chevron {\n display: block;\n flex-shrink: 0;\n}\n\n/* ─── Variant: default ──────────────────────────────────────────────────────── */\n\n.default {\n background-color: var(--gnome-card-bg-color, #ffffff);\n border-color: var(--gnome-light-3, #deddda);\n box-shadow: var(--gnome-shadow-sm);\n color: var(--gnome-window-fg-color, rgba(0, 0, 0, 0.8));\n}\n\n.default .primary:hover:not(:disabled),\n.default .toggle:hover:not(:disabled) {\n background-color: var(--gnome-hover-overlay);\n}\n\n.default .primary:active:not(:disabled),\n.default .toggle:active:not(:disabled) {\n background-color: var(--gnome-active-overlay);\n}\n\n.default .separator {\n background-color: var(--gnome-light-3, #deddda);\n}\n\n@media (prefers-color-scheme: dark) {\n .default {\n background-color: var(--gnome-card-bg-color, #383838);\n border-color: rgba(255, 255, 255, 0.12);\n box-shadow: none;\n }\n\n .default .separator {\n background-color: rgba(255, 255, 255, 0.12);\n }\n}\n\n/* ─── Variant: suggested ────────────────────────────────────────────────────── */\n\n.suggested {\n background-color: var(--gnome-accent-bg-color);\n border-color: transparent;\n color: var(--gnome-accent-fg-color);\n}\n\n.suggested .primary:hover:not(:disabled),\n.suggested .toggle:hover:not(:disabled) {\n filter: brightness(1.08);\n}\n\n.suggested .primary:active:not(:disabled),\n.suggested .toggle:active:not(:disabled) {\n filter: brightness(0.92);\n}\n\n.suggested .separator {\n background-color: rgba(255, 255, 255, 0.25);\n}\n\n/* ─── Variant: destructive ──────────────────────────────────────────────────── */\n\n.destructive {\n background-color: var(--gnome-destructive-bg-color);\n border-color: transparent;\n color: var(--gnome-destructive-fg-color);\n}\n\n.destructive .primary:hover:not(:disabled),\n.destructive .toggle:hover:not(:disabled) {\n filter: brightness(1.08);\n}\n\n.destructive .primary:active:not(:disabled),\n.destructive .toggle:active:not(:disabled) {\n filter: brightness(0.92);\n}\n\n.destructive .separator {\n background-color: rgba(255, 255, 255, 0.25);\n}\n\n/* ─── Dropdown panel ────────────────────────────────────────────────────────── */\n\n.dropdown {\n position: fixed;\n z-index: var(--gnome-z-popover, 1000);\n min-width: 160px;\n\n background-color: var(--gnome-popover-bg-color, #ffffff);\n border: 1px solid var(--gnome-popover-border-color, rgba(0, 0, 0, 0.12));\n border-radius: var(--gnome-radius-md);\n box-shadow: var(--gnome-shadow-md, 0 4px 16px rgba(0, 0, 0, 0.18));\n padding: var(--gnome-space-1, 6px);\n\n opacity: 0;\n transform: translateY(-4px);\n transition:\n opacity var(--gnome-duration-fast) var(--gnome-easing-default),\n transform var(--gnome-duration-fast) var(--gnome-easing-default);\n}\n\n.dropdownVisible {\n opacity: 1;\n transform: translateY(0);\n}\n\n@media (prefers-color-scheme: dark) {\n .dropdown {\n background-color: var(--gnome-popover-bg-color, #2d2d2d);\n border-color: rgba(255, 255, 255, 0.1);\n }\n}\n\n/* ─── High contrast ─────────────────────────────────────────────────────────── */\n\n@media (prefers-contrast: more) {\n .default {\n border-width: 2px;\n box-shadow: none;\n }\n\n .primary:focus-visible,\n .toggle:focus-visible {\n box-shadow:\n 0 0 0 var(--gnome-focus-ring-offset) var(--gnome-window-bg-color),\n 0 0 0 calc(var(--gnome-focus-ring-offset) + var(--gnome-focus-ring-width)) var(--gnome-focus-ring-color),\n 0 0 0 calc(var(--gnome-focus-ring-offset) + var(--gnome-focus-ring-width) + 1px) var(--gnome-window-bg-color);\n }\n}\n","import {\n useState,\n useRef,\n useEffect,\n useCallback,\n type ButtonHTMLAttributes,\n type ReactNode,\n type KeyboardEvent,\n} from \"react\";\nimport { createPortal } from \"react-dom\";\nimport styles from \"./SplitButton.module.css\";\n\nexport type SplitButtonVariant = \"default\" | \"suggested\" | \"destructive\";\n\nexport interface SplitButtonProps\n extends Omit<ButtonHTMLAttributes<HTMLButtonElement>, \"children\"> {\n /** Label shown in the primary button. */\n label: string;\n /** Visual style. Applies to both the primary and toggle halves. */\n variant?: SplitButtonVariant;\n /** Content rendered inside the dropdown panel when the arrow is clicked. */\n dropdownContent: ReactNode;\n /** Accessible label for the dropdown toggle button. Defaults to \"More options\". */\n dropdownLabel?: string;\n /** Whether both halves are disabled. */\n disabled?: boolean;\n}\n\n/**\n * Primary action button with an attached dropdown arrow.\n *\n * Clicking the label half fires `onClick`; clicking the arrow half opens a\n * floating panel with `dropdownContent` (menus, options, etc.).\n *\n * Mirrors `AdwSplitButton` — supports `suggested` and `destructive` variants.\n *\n * @see https://gnome.pages.gitlab.gnome.org/libadwaita/doc/main/class.SplitButton.html\n */\nexport function SplitButton({\n label,\n variant = \"default\",\n dropdownContent,\n dropdownLabel = \"More options\",\n disabled = false,\n onClick,\n className,\n ...props\n}: SplitButtonProps) {\n const [open, setOpen] = useState(false);\n const [dropdownStyle, setDropdownStyle] = useState<React.CSSProperties>({});\n\n const containerRef = useRef<HTMLDivElement>(null);\n const toggleRef = useRef<HTMLButtonElement>(null);\n const panelRef = useRef<HTMLDivElement>(null);\n\n const close = useCallback(() => setOpen(false), []);\n\n // Position dropdown below the toggle button\n const place = useCallback(() => {\n if (!toggleRef.current || !panelRef.current) return;\n const triggerRect = toggleRef.current.getBoundingClientRect();\n const panelRect = panelRef.current.getBoundingClientRect();\n const vw = window.innerWidth;\n\n let left = triggerRect.right - panelRect.width;\n if (left < 8) left = 8;\n if (left + panelRect.width > vw - 8) left = vw - panelRect.width - 8;\n\n setDropdownStyle({\n top: triggerRect.bottom + 6,\n left,\n });\n }, []);\n\n useEffect(() => {\n if (!open) return;\n const id = requestAnimationFrame(() => {\n place();\n window.addEventListener(\"scroll\", place, { passive: true, capture: true });\n window.addEventListener(\"resize\", place, { passive: true });\n });\n return () => {\n cancelAnimationFrame(id);\n window.removeEventListener(\"scroll\", place, { capture: true });\n window.removeEventListener(\"resize\", place);\n };\n }, [open, place]);\n\n // Close on outside click\n useEffect(() => {\n if (!open) return;\n const handler = (e: MouseEvent) => {\n if (\n !containerRef.current?.contains(e.target as Node) &&\n !panelRef.current?.contains(e.target as Node)\n ) {\n close();\n }\n };\n document.addEventListener(\"mousedown\", handler);\n return () => document.removeEventListener(\"mousedown\", handler);\n }, [open, close]);\n\n const handlePanelKeyDown = (e: KeyboardEvent<HTMLDivElement>) => {\n if (e.key === \"Escape\") {\n e.stopPropagation();\n close();\n toggleRef.current?.focus();\n }\n };\n\n const containerClass = [\n styles.container,\n styles[variant],\n className,\n ]\n .filter(Boolean)\n .join(\" \");\n\n const panel = (\n <div\n ref={panelRef}\n role=\"dialog\"\n aria-label={dropdownLabel}\n tabIndex={-1}\n className={[styles.dropdown, open ? styles.dropdownVisible : null]\n .filter(Boolean)\n .join(\" \")}\n style={\n Object.keys(dropdownStyle).length\n ? { ...dropdownStyle, position: \"fixed\" }\n : { visibility: \"hidden\", pointerEvents: \"none\", top: -9999, left: -9999, position: \"fixed\" }\n }\n onKeyDown={handlePanelKeyDown}\n >\n {dropdownContent}\n </div>\n );\n\n return (\n <>\n <div ref={containerRef} className={containerClass}>\n <button\n className={styles.primary}\n disabled={disabled}\n onClick={onClick}\n {...props}\n >\n {label}\n </button>\n\n <span className={styles.separator} aria-hidden=\"true\" />\n\n <button\n ref={toggleRef}\n className={styles.toggle}\n disabled={disabled}\n aria-label={dropdownLabel}\n aria-haspopup=\"dialog\"\n aria-expanded={open}\n onClick={() => setOpen((v) => !v)}\n >\n <svg\n className={styles.chevron}\n width=\"16\"\n height=\"16\"\n viewBox=\"0 0 16 16\"\n aria-hidden=\"true\"\n focusable=\"false\"\n >\n <path\n d=\"M4 6l4 4 4-4\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth=\"1.5\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n />\n </svg>\n </button>\n </div>\n\n {open &&\n (typeof document !== \"undefined\"\n ? createPortal(panel, document.body)\n : panel)}\n </>\n );\n}\n","/* ─── Toolbar ─────────────────────────────────────────────────────────────────\n Mirrors the libadwaita `.toolbar` style class:\n horizontal flex row, 6 px padding on all sides, 6 px gap between children.\n ─────────────────────────────────────────────────────────────────────────── */\n\n.toolbar {\n display: flex;\n flex-direction: row;\n align-items: center;\n flex-wrap: nowrap;\n gap: var(--gnome-space-1, 6px);\n padding: var(--gnome-space-1, 6px);\n min-height: 46px;\n\n color: var(--gnome-window-fg-color, rgba(0, 0, 0, 0.8));\n font-family: var(--gnome-font-family);\n}\n\n/* ─── Spacer ──────────────────────────────────────────────────────────────────\n Invisible flex filler — mirrors `GtkSeparator.spacer`.\n ─────────────────────────────────────────────────────────────────────────── */\n\n.spacer {\n flex: 1;\n min-width: 0;\n}\n","import type { HTMLAttributes, ReactNode } from \"react\";\nimport styles from \"./Toolbar.module.css\";\n\nexport interface ToolbarProps extends HTMLAttributes<HTMLDivElement> {\n children: ReactNode;\n}\n\n/**\n * Horizontal action bar following the libadwaita `.toolbar` pattern.\n *\n * Provides 6 px padding and gap — the standard spacing for rows of flat\n * buttons in header bars, action bars, and tool rows. Place `<Spacer />`\n * between leading and trailing groups to push trailing items to the end.\n *\n * Use `<Button variant=\"flat\">` or `<Button variant=\"raised\">` inside:\n * - `flat` for buttons that blend into the bar background.\n * - `raised` for buttons that need explicit elevation within a flat context.\n *\n * @see https://gnome.pages.gitlab.gnome.org/libadwaita/doc/1-latest/style-classes.html#toolbar-style-class\n */\nexport function Toolbar({ children, className, ...props }: ToolbarProps) {\n return (\n <div\n className={[styles.toolbar, className].filter(Boolean).join(\" \")}\n {...props}\n >\n {children}\n </div>\n );\n}\n","import type { HTMLAttributes } from \"react\";\nimport styles from \"./Toolbar.module.css\";\n\nexport interface SpacerProps extends HTMLAttributes<HTMLDivElement> {}\n\n/**\n * Invisible `flex: 1` filler for `Toolbar` and `HeaderBar`.\n *\n * Place between leading and trailing groups to push trailing items to the end.\n * Mirrors `GtkSeparator` with the `.spacer` style class.\n *\n * @see https://gnome.pages.gitlab.gnome.org/libadwaita/doc/1-latest/style-classes.html#spacer\n *\n * @example\n * <Toolbar>\n * <Button variant=\"flat\">Back</Button>\n * <Spacer />\n * <Button variant=\"flat\">Done</Button>\n * </Toolbar>\n */\nexport function Spacer({ className, ...props }: SpacerProps) {\n return (\n <div\n aria-hidden=\"true\"\n className={[styles.spacer, className].filter(Boolean).join(\" \")}\n {...props}\n />\n );\n}\n","/* ─── Container ─────────────────────────────────────────────────────────────── */\n\n.linked {\n display: inline-flex;\n flex-direction: row;\n align-items: stretch;\n}\n\n.vertical {\n flex-direction: column;\n}\n\n/* ─── Strip inner border-radius from all children ───────────────────────────── */\n\n.linked > :global(*) {\n border-radius: 0 !important;\n flex-shrink: 0;\n}\n\n/* ─── Collapse shared borders (horizontal) ──────────────────────────────────── */\n\n.linked:not(.vertical) > :global(*:not(:first-child)) {\n margin-left: -1px;\n}\n\n/* ─── Collapse shared borders (vertical) ───────────────────────────────────── */\n\n.linked.vertical > :global(*:not(:first-child)) {\n margin-top: -1px;\n}\n\n/* ─── Restore radius — first child ─────────────────────────────────────────── */\n\n.linked:not(.vertical) > :global(*:first-child) {\n border-top-left-radius: var(--gnome-radius-md) !important;\n border-bottom-left-radius: var(--gnome-radius-md) !important;\n}\n\n.linked.vertical > :global(*:first-child) {\n border-top-left-radius: var(--gnome-radius-md) !important;\n border-top-right-radius: var(--gnome-radius-md) !important;\n}\n\n/* ─── Restore radius — last child ──────────────────────────────────────────── */\n\n.linked:not(.vertical) > :global(*:last-child) {\n border-top-right-radius: var(--gnome-radius-md) !important;\n border-bottom-right-radius: var(--gnome-radius-md) !important;\n}\n\n.linked.vertical > :global(*:last-child) {\n border-bottom-left-radius: var(--gnome-radius-md) !important;\n border-bottom-right-radius: var(--gnome-radius-md) !important;\n}\n\n/* ─── Bring hovered / focused child to the front so its border shows ────────── */\n\n.linked > :global(*:hover),\n.linked > :global(*:focus-visible) {\n position: relative;\n z-index: 1;\n}\n","import type { HTMLAttributes, ReactNode } from \"react\";\nimport styles from \"./LinkedGroup.module.css\";\n\nexport interface LinkedGroupProps extends HTMLAttributes<HTMLDivElement> {\n children: ReactNode;\n /**\n * Stack children vertically instead of horizontally.\n * Useful for stacked input or button groups.\n */\n vertical?: boolean;\n}\n\n/**\n * Renders children as a single visually-connected unit with no gap and\n * merged borders — the canonical GNOME pattern for button groups and\n * segmented inputs.\n *\n * Mirrors the libadwaita `.linked` style class.\n *\n * - First child gets left (or top) border radius.\n * - Last child gets right (or bottom) border radius.\n * - Adjacent borders are collapsed to a single pixel.\n * - Hover and focus bring the active child to the front via `z-index`.\n *\n * @see https://gnome.pages.gitlab.gnome.org/libadwaita/doc/1-latest/style-classes.html#linked-style-class\n *\n * @example\n * <LinkedGroup>\n * <Button>Cut</Button>\n * <Button>Copy</Button>\n * <Button>Paste</Button>\n * </LinkedGroup>\n */\nexport function LinkedGroup({\n children,\n vertical = false,\n className,\n ...props\n}: LinkedGroupProps) {\n const classes = [\n styles.linked,\n vertical ? styles.vertical : null,\n className,\n ]\n .filter(Boolean)\n .join(\" \");\n\n return (\n <div className={classes} {...props}>\n {children}\n </div>\n );\n}\n","/* ─── Frame ───────────────────────────────────────────────────────────────────\n Bordered surface without background fill — mirrors GtkFrame / .frame.\n ─────────────────────────────────────────────────────────────────────────── */\n\n.frame {\n border: 1px solid var(--gnome-borders, rgba(0, 0, 0, 0.12));\n border-radius: var(--gnome-radius-md);\n overflow: hidden;\n}\n\n@media (prefers-color-scheme: dark) {\n .frame {\n border-color: var(--gnome-borders, rgba(255, 255, 255, 0.12));\n }\n}\n\n@media (prefers-contrast: more) {\n .frame {\n border-width: 2px;\n }\n}\n","import type { HTMLAttributes, ReactNode } from \"react\";\nimport styles from \"./Frame.module.css\";\n\nexport interface FrameProps extends HTMLAttributes<HTMLDivElement> {\n children?: ReactNode;\n}\n\n/**\n * Simple bordered surface with border-radius but no background fill.\n *\n * Use to visually delimit a region without adding the elevated appearance\n * of a `Card`. Mirrors `GtkFrame` default styling and the libadwaita\n * `.frame` style class.\n *\n * @see https://gnome.pages.gitlab.gnome.org/libadwaita/doc/1-latest/style-classes.html#frame-style-class\n */\nexport function Frame({ children, className, ...props }: FrameProps) {\n return (\n <div\n className={[styles.frame, className].filter(Boolean).join(\" \")}\n {...props}\n >\n {children}\n </div>\n );\n}\n","/* ─── Wrapper ───────────────────────────────────────────────────────────────── */\n\n.expanderRow {\n display: flex;\n flex-direction: column;\n width: 100%;\n}\n\n/* ─── Header button ─────────────────────────────────────────────────────────── */\n\n.header {\n display: flex;\n align-items: center;\n gap: var(--gnome-space-2, 12px);\n padding: var(--gnome-space-2, 12px) var(--gnome-space-4, 24px);\n min-height: 52px;\n width: 100%;\n\n appearance: none;\n -webkit-appearance: none;\n background: transparent;\n border: none;\n font: inherit;\n outline: none;\n cursor: pointer;\n text-align: start;\n color: var(--gnome-card-fg-color, rgba(0, 0, 0, 0.8));\n\n transition: background-color var(--gnome-duration-fast) var(--gnome-easing-default);\n}\n\n.header:hover {\n background-color: var(--gnome-hover-overlay);\n}\n\n.header:active {\n background-color: var(--gnome-active-overlay);\n}\n\n.header:focus-visible {\n box-shadow:\n inset 0 0 0 var(--gnome-focus-ring-offset, 2px) var(--gnome-window-bg-color, #fafafa),\n inset 0 0 0 calc(var(--gnome-focus-ring-offset, 2px) + var(--gnome-focus-ring-width, 3px))\n var(--gnome-focus-ring-color, #3584e4);\n}\n\n.header:disabled {\n opacity: var(--gnome-opacity-disabled, 0.5);\n cursor: not-allowed;\n pointer-events: none;\n}\n\n/* ─── Leading ───────────────────────────────────────────────────────────────── */\n\n.leading {\n display: flex;\n align-items: center;\n flex-shrink: 0;\n color: var(--gnome-window-fg-color, rgba(0, 0, 0, 0.8));\n}\n\n/* ─── Text content ──────────────────────────────────────────────────────────── */\n\n.content {\n display: flex;\n flex-direction: column;\n flex: 1;\n min-width: 0;\n gap: 2px;\n}\n\n.title {\n font-family: var(--gnome-font-family);\n font-size: var(--gnome-font-size-body, 1rem);\n font-weight: var(--gnome-font-weight-normal, 400);\n line-height: var(--gnome-line-height-body, 1.5);\n color: var(--gnome-card-fg-color, rgba(0, 0, 0, 0.8));\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n}\n\n.subtitle {\n font-family: var(--gnome-font-family);\n font-size: var(--gnome-font-size-caption, 0.75rem);\n font-weight: var(--gnome-font-weight-normal, 400);\n line-height: var(--gnome-line-height-body, 1.5);\n color: var(--gnome-window-fg-color, rgba(0, 0, 0, 0.55));\n opacity: var(--gnome-opacity-dim, 0.55);\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n}\n\n/* ─── Trailing ──────────────────────────────────────────────────────────────── */\n\n.trailing {\n display: flex;\n align-items: center;\n flex-shrink: 0;\n gap: var(--gnome-space-1, 6px);\n}\n\n/* ─── Chevron ───────────────────────────────────────────────────────────────── */\n\n.chevronWrap {\n display: flex;\n align-items: center;\n flex-shrink: 0;\n color: var(--gnome-window-fg-color, rgba(0, 0, 0, 0.55));\n opacity: 0.55;\n transition: transform var(--gnome-duration-fast) var(--gnome-easing-default);\n}\n\n.chevronOpen {\n transform: rotate(180deg);\n}\n\n.chevron {\n display: block;\n}\n\n/* ─── Reveal panel (CSS grid height animation) ──────────────────────────────── */\n\n.panel {\n display: grid;\n grid-template-rows: 0fr;\n transition: grid-template-rows var(--gnome-duration-normal, 200ms) var(--gnome-easing-default);\n}\n\n.expanded .panel {\n grid-template-rows: 1fr;\n}\n\n.panelInner {\n overflow: hidden;\n min-height: 0;\n}\n\n/* ─── Child row wrapper + divider ───────────────────────────────────────────── */\n\n.childItem {\n display: flex;\n flex-direction: column;\n}\n\n.divider {\n height: 1px;\n background-color: var(--gnome-divider-color, rgba(0, 0, 0, 0.08));\n margin-left: var(--gnome-space-4, 24px);\n}\n\n/* ─── High contrast ─────────────────────────────────────────────────────────── */\n\n@media (prefers-contrast: more) {\n .header:focus-visible {\n box-shadow:\n inset 0 0 0 var(--gnome-focus-ring-offset, 2px) var(--gnome-window-bg-color, #fafafa),\n inset 0 0 0 calc(var(--gnome-focus-ring-offset, 2px) + var(--gnome-focus-ring-width, 3px))\n var(--gnome-focus-ring-color, #3584e4),\n inset 0 0 0 calc(var(--gnome-focus-ring-offset, 2px) + var(--gnome-focus-ring-width, 3px) + 1px)\n var(--gnome-window-bg-color, #fafafa);\n }\n\n .divider {\n height: 2px;\n }\n}\n","import { useState, Children, useId, type HTMLAttributes, type ReactNode } from \"react\";\nimport styles from \"./ExpanderRow.module.css\";\n\nexport interface ExpanderRowProps extends HTMLAttributes<HTMLDivElement> {\n /** Primary label. */\n title: string;\n /** Secondary line below the title. */\n subtitle?: string;\n /** Icon or image placed at the leading edge of the header row. */\n leading?: ReactNode;\n /**\n * Widget placed at the trailing edge of the header row, before the chevron\n * (e.g. a value label or a Switch). Stop event propagation inside it so the\n * row's toggle isn't triggered.\n */\n trailing?: ReactNode;\n /**\n * Nested rows revealed when expanded. Use `ActionRow`, `ButtonRow`, or any\n * row-shaped element — separators are inserted automatically.\n */\n children?: ReactNode;\n /** Controlled expanded state. */\n expanded?: boolean;\n /** Initial expanded state when uncontrolled. Defaults to `false`. */\n defaultExpanded?: boolean;\n /** Called when the expanded state changes. */\n onExpandedChange?: (expanded: boolean) => void;\n}\n\n/**\n * Collapsible `ActionRow` that reveals nested rows on activation.\n *\n * Mirrors `AdwExpanderRow` — the header row toggles a reveal animation exposing\n * child rows. Supports both controlled and uncontrolled expand state.\n *\n * @see https://gnome.pages.gitlab.gnome.org/libadwaita/doc/main/class.ExpanderRow.html\n */\nexport function ExpanderRow({\n title,\n subtitle,\n leading,\n trailing,\n children,\n expanded: controlledExpanded,\n defaultExpanded = false,\n onExpandedChange,\n className,\n ...props\n}: ExpanderRowProps) {\n const isControlled = controlledExpanded !== undefined;\n const [uncontrolledExpanded, setUncontrolledExpanded] = useState(defaultExpanded);\n const expanded = isControlled ? controlledExpanded : uncontrolledExpanded;\n\n const panelId = useId();\n const headerId = useId();\n\n const toggle = () => {\n const next = !expanded;\n if (!isControlled) setUncontrolledExpanded(next);\n onExpandedChange?.(next);\n };\n\n const childItems = Children.toArray(children).filter(Boolean);\n\n return (\n <div\n className={[styles.expanderRow, expanded ? styles.expanded : null, className]\n .filter(Boolean)\n .join(\" \")}\n {...props}\n >\n {/* ─── Header (toggle button) ─────────────────────────────────────── */}\n <button\n id={headerId}\n className={styles.header}\n aria-expanded={expanded}\n aria-controls={panelId}\n onClick={toggle}\n >\n {leading && <span className={styles.leading}>{leading}</span>}\n\n <span className={styles.content}>\n <span className={styles.title}>{title}</span>\n {subtitle && <span className={styles.subtitle}>{subtitle}</span>}\n </span>\n\n {trailing && <span className={styles.trailing}>{trailing}</span>}\n\n <span className={[styles.chevronWrap, expanded ? styles.chevronOpen : null].filter(Boolean).join(\" \")} aria-hidden=\"true\">\n <svg className={styles.chevron} width=\"16\" height=\"16\" viewBox=\"0 0 16 16\" focusable=\"false\">\n <path\n d=\"M4 6l4 4 4-4\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth=\"1.5\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n />\n </svg>\n </span>\n </button>\n\n {/* ─── Reveal panel ───────────────────────────────────────────────── */}\n <div\n id={panelId}\n role=\"region\"\n aria-labelledby={headerId}\n className={styles.panel}\n >\n <div className={styles.panelInner}>\n {childItems.map((child, i) => (\n <div key={i} className={styles.childItem}>\n <div className={styles.divider} aria-hidden=\"true\" />\n {child}\n </div>\n ))}\n </div>\n </div>\n </div>\n );\n}\n","/* ─── Switcher container ────────────────────────────────────────────────────── */\n\n.switcher {\n display: inline-flex;\n align-items: stretch;\n}\n\n/* ─── Item base ─────────────────────────────────────────────────────────────── */\n\n.item {\n appearance: none;\n -webkit-appearance: none;\n border: none;\n background: transparent;\n font: inherit;\n cursor: pointer;\n outline: none;\n\n display: inline-flex;\n align-items: center;\n justify-content: center;\n gap: var(--gnome-space-1, 6px);\n\n padding: 4px var(--gnome-space-2, 12px);\n min-height: 28px;\n\n font-family: var(--gnome-font-family);\n font-size: var(--gnome-font-size-body, 1rem);\n font-weight: var(--gnome-font-weight-normal, 400);\n color: var(--gnome-window-fg-color, rgba(0, 0, 0, 0.8));\n white-space: nowrap;\n\n transition:\n background-color var(--gnome-duration-fast) var(--gnome-easing-default),\n box-shadow var(--gnome-duration-fast) var(--gnome-easing-default);\n}\n\n.iconOnly {\n padding: 4px;\n min-width: 28px;\n}\n\n.itemIcon {\n display: flex;\n align-items: center;\n flex-shrink: 0;\n}\n\n.itemLabel {\n line-height: 1;\n}\n\n/* ─── Variant: default ──────────────────────────────────────────────────────── */\n\n.default {\n background-color: var(--gnome-card-bg-color, #ffffff);\n border: 1px solid var(--gnome-light-3, #deddda);\n border-radius: var(--gnome-radius-md);\n box-shadow: var(--gnome-shadow-sm);\n padding: 2px;\n gap: 2px;\n}\n\n.default .item {\n border-radius: calc(var(--gnome-radius-md) - 2px);\n}\n\n.default .item:hover:not(:disabled):not(.active) {\n background-color: rgba(0, 0, 0, 0.06);\n}\n\n.default .item:active:not(:disabled):not(.active) {\n background-color: rgba(0, 0, 0, 0.12);\n}\n\n.default .item:focus-visible {\n box-shadow:\n 0 0 0 var(--gnome-focus-ring-offset, 2px) var(--gnome-card-bg-color, #ffffff),\n 0 0 0 calc(var(--gnome-focus-ring-offset, 2px) + var(--gnome-focus-ring-width, 2px))\n var(--gnome-focus-ring-color, #3584e4);\n}\n\n.default .active {\n background-color: color-mix(in srgb, var(--gnome-accent-bg-color, #3584e4) 15%, transparent);\n color: var(--gnome-accent-color, #3584e4);\n font-weight: var(--gnome-font-weight-semibold, 600);\n box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--gnome-accent-bg-color, #3584e4) 40%, transparent);\n}\n\n@media (prefers-color-scheme: dark) {\n .default {\n background-color: var(--gnome-card-bg-color, #383838);\n border-color: rgba(255, 255, 255, 0.12);\n box-shadow: none;\n }\n\n .default .item:hover:not(:disabled):not(.active) {\n background-color: rgba(255, 255, 255, 0.08);\n }\n\n .default .item:active:not(:disabled):not(.active) {\n background-color: rgba(255, 255, 255, 0.14);\n }\n\n .default .item:focus-visible {\n box-shadow:\n 0 0 0 var(--gnome-focus-ring-offset, 2px) var(--gnome-card-bg-color, #383838),\n 0 0 0 calc(var(--gnome-focus-ring-offset, 2px) + var(--gnome-focus-ring-width, 2px))\n var(--gnome-focus-ring-color, #78aeed);\n }\n\n .default .active {\n background-color: color-mix(in srgb, var(--gnome-accent-bg-color, #3584e4) 25%, transparent);\n color: var(--gnome-accent-color, #78aeed);\n box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--gnome-accent-bg-color, #3584e4) 50%, transparent);\n }\n}\n\n/* ─── Variant: flat ─────────────────────────────────────────────────────────── */\n\n.flat {\n background: transparent;\n border: none;\n box-shadow: none;\n border-radius: var(--gnome-radius-md);\n padding: 2px;\n gap: 2px;\n}\n\n.flat .item {\n border-radius: calc(var(--gnome-radius-md) - 2px);\n color: var(--gnome-window-fg-color, rgba(0, 0, 0, 0.6));\n}\n\n.flat .item:hover:not(:disabled):not(.active) {\n background-color: var(--gnome-hover-overlay);\n}\n\n.flat .item:active:not(:disabled):not(.active) {\n background-color: var(--gnome-active-overlay);\n}\n\n.flat .item:focus-visible {\n box-shadow:\n 0 0 0 var(--gnome-focus-ring-offset, 2px) var(--gnome-window-bg-color, #fafafa),\n 0 0 0 calc(var(--gnome-focus-ring-offset, 2px) + var(--gnome-focus-ring-width, 2px))\n var(--gnome-focus-ring-color, #3584e4);\n}\n\n.flat .active {\n background-color: color-mix(in srgb, var(--gnome-accent-bg-color, #3584e4) 15%, transparent);\n color: var(--gnome-accent-color, #3584e4);\n font-weight: var(--gnome-font-weight-semibold, 600);\n}\n\n@media (prefers-color-scheme: dark) {\n .flat .item {\n color: var(--gnome-window-fg-color, rgba(255, 255, 255, 0.6));\n }\n\n .flat .active {\n background-color: color-mix(in srgb, var(--gnome-accent-bg-color, #3584e4) 25%, transparent);\n color: var(--gnome-accent-color, #78aeed);\n }\n}\n\n/* ─── Variant: round ────────────────────────────────────────────────────────── */\n\n.round {\n background-color: var(--gnome-card-bg-color, #ffffff);\n border: 1px solid var(--gnome-light-3, #deddda);\n border-radius: var(--gnome-radius-pill, 9999px);\n box-shadow: var(--gnome-shadow-sm);\n padding: 2px;\n gap: 2px;\n}\n\n.round .item {\n border-radius: var(--gnome-radius-pill, 9999px);\n padding: 4px var(--gnome-space-2, 12px);\n}\n\n.round .iconOnly {\n padding: 4px;\n min-width: 28px;\n}\n\n.round .item:hover:not(:disabled):not(.active) {\n background-color: rgba(0, 0, 0, 0.06);\n}\n\n.round .item:active:not(:disabled):not(.active) {\n background-color: rgba(0, 0, 0, 0.12);\n}\n\n.round .item:focus-visible {\n box-shadow:\n 0 0 0 var(--gnome-focus-ring-offset, 2px) var(--gnome-card-bg-color, #ffffff),\n 0 0 0 calc(var(--gnome-focus-ring-offset, 2px) + var(--gnome-focus-ring-width, 2px))\n var(--gnome-focus-ring-color, #3584e4);\n}\n\n.round .active {\n background-color: var(--gnome-accent-bg-color, #3584e4);\n color: var(--gnome-accent-fg-color, #ffffff);\n font-weight: var(--gnome-font-weight-semibold, 600);\n box-shadow: none;\n}\n\n@media (prefers-color-scheme: dark) {\n .round {\n background-color: var(--gnome-card-bg-color, #383838);\n border-color: rgba(255, 255, 255, 0.12);\n box-shadow: none;\n }\n\n .round .item:hover:not(:disabled):not(.active) {\n background-color: rgba(255, 255, 255, 0.08);\n }\n\n .round .item:active:not(:disabled):not(.active) {\n background-color: rgba(255, 255, 255, 0.14);\n }\n\n .round .item:focus-visible {\n box-shadow:\n 0 0 0 var(--gnome-focus-ring-offset, 2px) var(--gnome-card-bg-color, #383838),\n 0 0 0 calc(var(--gnome-focus-ring-offset, 2px) + var(--gnome-focus-ring-width, 2px))\n var(--gnome-focus-ring-color, #78aeed);\n }\n}\n\n/* ─── Disabled ──────────────────────────────────────────────────────────────── */\n\n.item:disabled {\n opacity: var(--gnome-opacity-disabled, 0.5);\n cursor: not-allowed;\n}\n\n/* ─── Reduced motion ────────────────────────────────────────────────────────── */\n\n@media (prefers-reduced-motion: reduce) {\n .item {\n transition: none;\n }\n}\n","import {\n createContext,\n useContext,\n useRef,\n type HTMLAttributes,\n type KeyboardEvent,\n type ReactNode,\n} from \"react\";\nimport styles from \"./InlineViewSwitcher.module.css\";\n\nexport type InlineViewSwitcherVariant = \"default\" | \"flat\" | \"round\";\n\n// ─── Internal context ──────────────────────────────────────────────────────────\n\ninterface InlineViewSwitcherContextValue {\n value: string;\n onValueChange: (value: string) => void;\n}\n\nconst InlineViewSwitcherContext =\n createContext<InlineViewSwitcherContextValue | null>(null);\n\nexport function useInlineViewSwitcher() {\n const ctx = useContext(InlineViewSwitcherContext);\n if (!ctx)\n throw new Error(\n \"InlineViewSwitcherItem must be used inside InlineViewSwitcher\",\n );\n return ctx;\n}\n\n// ─── InlineViewSwitcher ────────────────────────────────────────────────────────\n\nexport interface InlineViewSwitcherProps extends HTMLAttributes<HTMLDivElement> {\n /** Currently active view name. */\n value: string;\n /** Called with the new value when a view is selected. */\n onValueChange: (value: string) => void;\n /**\n * Visual style of the switcher.\n * - `default` — card background with border and shadow (same as `ToggleGroup`).\n * - `flat` — no background or border; active indicator only.\n * - `round` — pill-shaped container and items.\n */\n variant?: InlineViewSwitcherVariant;\n /** Accessible label for the group. */\n \"aria-label\"?: string;\n children?: ReactNode;\n}\n\n/**\n * Compact inline view switcher for placing inside content areas, cards, or\n * toolbars — wherever `ViewSwitcher` (header-bar sized) would be too heavy.\n *\n * Built on `ToggleGroup` internals with `flat` and `round` style variants.\n * Mirrors `AdwInlineViewSwitcher` (libadwaita 1.7 / GNOME 48).\n *\n * Compose with `InlineViewSwitcherItem`. Keyboard: ← / → cycle, Home / End jump.\n *\n * @see https://gnome.pages.gitlab.gnome.org/libadwaita/doc/main/class.InlineViewSwitcher.html\n */\nexport function InlineViewSwitcher({\n value,\n onValueChange,\n variant = \"default\",\n \"aria-label\": ariaLabel = \"View switcher\",\n children,\n className,\n ...props\n}: InlineViewSwitcherProps) {\n const groupRef = useRef<HTMLDivElement>(null);\n\n function handleKeyDown(e: KeyboardEvent<HTMLDivElement>) {\n const items = Array.from(\n groupRef.current?.querySelectorAll<HTMLButtonElement>(\n \"[role=radio]:not(:disabled)\",\n ) ?? [],\n );\n const idx = items.findIndex((el) => el === document.activeElement);\n if (idx === -1) return;\n\n let next = idx;\n if (e.key === \"ArrowRight\" || e.key === \"ArrowDown\")\n next = (idx + 1) % items.length;\n else if (e.key === \"ArrowLeft\" || e.key === \"ArrowUp\")\n next = (idx - 1 + items.length) % items.length;\n else if (e.key === \"Home\") next = 0;\n else if (e.key === \"End\") next = items.length - 1;\n else return;\n\n e.preventDefault();\n items[next].focus();\n items[next].click();\n }\n\n return (\n <InlineViewSwitcherContext.Provider value={{ value, onValueChange }}>\n <div\n ref={groupRef}\n role=\"radiogroup\"\n aria-label={ariaLabel}\n onKeyDown={handleKeyDown}\n className={[styles.switcher, styles[variant], className]\n .filter(Boolean)\n .join(\" \")}\n {...props}\n >\n {children}\n </div>\n </InlineViewSwitcherContext.Provider>\n );\n}\n","import type { ButtonHTMLAttributes } from \"react\";\nimport type { IconDefinition } from \"@gnome-ui/icons\";\nimport { Icon } from \"../Icon\";\nimport { useInlineViewSwitcher } from \"./InlineViewSwitcher\";\nimport styles from \"./InlineViewSwitcher.module.css\";\n\nexport interface InlineViewSwitcherItemProps\n extends ButtonHTMLAttributes<HTMLButtonElement> {\n /** String identifier — used as the switcher's `value` when this item is active. */\n name: string;\n /** Visible label. */\n label?: string;\n /** Icon from `@gnome-ui/icons`. */\n icon?: IconDefinition;\n}\n\n/**\n * Individual view option inside an `InlineViewSwitcher`.\n *\n * Can be icon-only, label-only, or icon + label. For icon-only items always\n * provide an `aria-label` so screen readers can identify the view.\n */\nexport function InlineViewSwitcherItem({\n name,\n label,\n icon,\n disabled,\n className,\n ...props\n}: InlineViewSwitcherItemProps) {\n const { value, onValueChange } = useInlineViewSwitcher();\n const active = value === name;\n const iconOnly = icon && !label;\n\n return (\n <button\n type=\"button\"\n role=\"radio\"\n aria-checked={active}\n tabIndex={active ? 0 : -1}\n disabled={disabled}\n onClick={() => onValueChange(name)}\n className={[\n styles.item,\n active ? styles.active : null,\n iconOnly ? styles.iconOnly : null,\n className,\n ]\n .filter(Boolean)\n .join(\" \")}\n {...props}\n >\n {icon && (\n <span className={styles.itemIcon}>\n <Icon icon={icon} size=\"md\" aria-hidden />\n </span>\n )}\n {label && <span className={styles.itemLabel}>{label}</span>}\n </button>\n );\n}\n","/* ─── Row button ────────────────────────────────────────────────────────────── */\n\n.row {\n display: flex;\n align-items: center;\n gap: var(--gnome-space-2, 12px);\n padding: var(--gnome-space-2, 12px) var(--gnome-space-4, 24px);\n min-height: 52px;\n width: 100%;\n\n appearance: none;\n -webkit-appearance: none;\n background: transparent;\n border: none;\n font: inherit;\n outline: none;\n cursor: pointer;\n text-align: start;\n color: var(--gnome-card-fg-color, rgba(0, 0, 0, 0.8));\n\n transition: background-color var(--gnome-duration-fast) var(--gnome-easing-default);\n}\n\n.row:hover {\n background-color: var(--gnome-hover-overlay);\n}\n\n.row:active {\n background-color: var(--gnome-active-overlay);\n}\n\n.row:focus-visible {\n box-shadow:\n inset 0 0 0 var(--gnome-focus-ring-offset, 2px) var(--gnome-window-bg-color, #fafafa),\n inset 0 0 0 calc(var(--gnome-focus-ring-offset, 2px) + var(--gnome-focus-ring-width, 3px))\n var(--gnome-focus-ring-color, #3584e4);\n}\n\n/* ─── Disabled ──────────────────────────────────────────────────────────────── */\n\n.disabled {\n opacity: var(--gnome-opacity-disabled, 0.5);\n cursor: not-allowed;\n pointer-events: none;\n}\n\n/* ─── Leading icon/avatar ───────────────────────────────────────────────────── */\n\n.leading {\n display: flex;\n align-items: center;\n flex-shrink: 0;\n color: var(--gnome-window-fg-color, rgba(0, 0, 0, 0.8));\n}\n\n/* ─── Text content ──────────────────────────────────────────────────────────── */\n\n.content {\n display: flex;\n flex-direction: column;\n flex: 1;\n min-width: 0;\n gap: 2px;\n}\n\n.title {\n font-family: var(--gnome-font-family);\n font-size: var(--gnome-font-size-body, 1rem);\n font-weight: var(--gnome-font-weight-normal, 400);\n line-height: var(--gnome-line-height-body, 1.5);\n color: var(--gnome-card-fg-color, rgba(0, 0, 0, 0.8));\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n}\n\n.subtitle {\n font-family: var(--gnome-font-family);\n font-size: var(--gnome-font-size-caption, 0.75rem);\n font-weight: var(--gnome-font-weight-normal, 400);\n line-height: var(--gnome-line-height-body, 1.5);\n color: var(--gnome-window-fg-color, rgba(0, 0, 0, 0.55));\n opacity: var(--gnome-opacity-dim, 0.55);\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n}\n\n/* ─── Switch track ──────────────────────────────────────────────────────────── */\n\n.switchTrack {\n position: relative;\n display: inline-flex;\n align-items: center;\n flex-shrink: 0;\n width: 56px;\n height: 28px;\n border-radius: var(--gnome-radius-pill);\n background-color: var(--gnome-light-4, #c0bfbc);\n border: 1px solid var(--gnome-border-subtle, rgba(0, 0, 0, 0.12));\n transition:\n background-color var(--gnome-duration-fast) var(--gnome-easing-default),\n border-color var(--gnome-duration-fast) var(--gnome-easing-default);\n}\n\n/* When checked, colour the track */\n.row[aria-checked=\"true\"] .switchTrack {\n background-color: var(--gnome-accent-bg-color, #3584e4);\n border-color: var(--gnome-accent-bg-color, #3584e4);\n}\n\n/* ─── Switch thumb ──────────────────────────────────────────────────────────── */\n\n.switchThumb {\n position: absolute;\n top: 3px;\n left: 3px;\n width: 20px;\n height: 20px;\n border-radius: var(--gnome-radius-pill);\n background-color: #ffffff;\n box-shadow: 0 1px 3px rgba(0, 0, 0, 0.25);\n transition: transform var(--gnome-duration-fast) var(--gnome-easing-default);\n}\n\n.switchThumbOn {\n transform: translateX(28px);\n}\n\n/* ─── Dark mode ─────────────────────────────────────────────────────────────── */\n\n@media (prefers-color-scheme: dark) {\n .switchTrack {\n background-color: var(--gnome-dark-2, #5e5c64);\n }\n\n .switchThumb {\n background-color: var(--gnome-light-3, #deddda);\n }\n\n .row[aria-checked=\"true\"] .switchThumb {\n background-color: #ffffff;\n }\n}\n\n/* ─── High contrast ─────────────────────────────────────────────────────────── */\n\n@media (prefers-contrast: more) {\n .row:focus-visible {\n box-shadow:\n inset 0 0 0 var(--gnome-focus-ring-offset, 2px) var(--gnome-window-bg-color, #fafafa),\n inset 0 0 0 calc(var(--gnome-focus-ring-offset, 2px) + var(--gnome-focus-ring-width, 3px))\n var(--gnome-focus-ring-color, #3584e4),\n inset 0 0 0 calc(var(--gnome-focus-ring-offset, 2px) + var(--gnome-focus-ring-width, 3px) + 1px)\n var(--gnome-window-bg-color, #fafafa);\n }\n\n .switchTrack {\n border-width: 2px;\n }\n\n .switchThumb {\n box-shadow: none;\n }\n}\n\n/* ─── Reduced motion ────────────────────────────────────────────────────────── */\n\n@media (prefers-reduced-motion: reduce) {\n .switchTrack,\n .switchThumb {\n transition: none;\n }\n}\n","import { useState, useId, type HTMLAttributes, type ReactNode } from \"react\";\nimport styles from \"./SwitchRow.module.css\";\n\nexport interface SwitchRowProps extends Omit<HTMLAttributes<HTMLButtonElement>, \"onChange\"> {\n /** Primary label. */\n title: string;\n /** Secondary line below the title. */\n subtitle?: string;\n /** Icon or image placed at the leading edge. */\n leading?: ReactNode;\n /** Controlled checked state. */\n checked?: boolean;\n /** Initial checked state when uncontrolled. Defaults to `false`. */\n defaultChecked?: boolean;\n /** Called when the checked state changes. */\n onCheckedChange?: (checked: boolean) => void;\n /** Disables the row. */\n disabled?: boolean;\n}\n\n/**\n * Activatable row with an integrated switch.\n *\n * The entire row is a button — clicking anywhere toggles the switch.\n * Mirrors `AdwSwitchRow`, which differs from `ActionRow + trailing Switch`\n * because the row itself is the interactive element.\n *\n * Supports both controlled (`checked`) and uncontrolled (`defaultChecked`) modes.\n *\n * @see https://gnome.pages.gitlab.gnome.org/libadwaita/doc/main/class.SwitchRow.html\n */\nexport function SwitchRow({\n title,\n subtitle,\n leading,\n checked: controlledChecked,\n defaultChecked = false,\n onCheckedChange,\n disabled = false,\n className,\n onClick,\n ...props\n}: SwitchRowProps) {\n const isControlled = controlledChecked !== undefined;\n const [uncontrolledChecked, setUncontrolledChecked] = useState(defaultChecked);\n const checked = isControlled ? controlledChecked : uncontrolledChecked;\n\n const switchId = useId();\n\n const handleClick = (e: React.MouseEvent<HTMLButtonElement>) => {\n const next = !checked;\n if (!isControlled) setUncontrolledChecked(next);\n onCheckedChange?.(next);\n onClick?.(e);\n };\n\n return (\n <button\n role=\"switch\"\n aria-checked={checked}\n aria-labelledby={switchId}\n disabled={disabled}\n className={[styles.row, disabled ? styles.disabled : null, className]\n .filter(Boolean)\n .join(\" \")}\n onClick={handleClick}\n {...props}\n >\n {leading && <span className={styles.leading}>{leading}</span>}\n\n <span className={styles.content} id={switchId}>\n <span className={styles.title}>{title}</span>\n {subtitle && <span className={styles.subtitle}>{subtitle}</span>}\n </span>\n\n {/* Visual switch indicator — aria-hidden since the button carries the role */}\n <span className={styles.switchTrack} aria-hidden=\"true\">\n <span className={[styles.switchThumb, checked ? styles.switchThumbOn : null].filter(Boolean).join(\" \")} />\n </span>\n </button>\n );\n}\n","/* ─── Row button ────────────────────────────────────────────────────────────── */\n\n.row {\n display: flex;\n align-items: center;\n gap: var(--gnome-space-2, 12px);\n padding: var(--gnome-space-2, 12px) var(--gnome-space-4, 24px);\n min-height: 52px;\n width: 100%;\n\n appearance: none;\n -webkit-appearance: none;\n background: transparent;\n border: none;\n font: inherit;\n outline: none;\n cursor: pointer;\n text-align: start;\n color: var(--gnome-card-fg-color, rgba(0, 0, 0, 0.8));\n\n transition: background-color var(--gnome-duration-fast) var(--gnome-easing-default);\n}\n\n.row:hover {\n background-color: var(--gnome-hover-overlay);\n}\n\n.row:active {\n background-color: var(--gnome-active-overlay);\n}\n\n.row:focus-visible {\n box-shadow:\n inset 0 0 0 var(--gnome-focus-ring-offset, 2px) var(--gnome-window-bg-color, #fafafa),\n inset 0 0 0 calc(var(--gnome-focus-ring-offset, 2px) + var(--gnome-focus-ring-width, 3px))\n var(--gnome-focus-ring-color, #3584e4);\n}\n\n/* ─── Disabled ──────────────────────────────────────────────────────────────── */\n\n.disabled {\n opacity: var(--gnome-opacity-disabled, 0.5);\n cursor: not-allowed;\n pointer-events: none;\n}\n\n/* ─── Checkbox visual ───────────────────────────────────────────────────────── */\n\n.checkboxWrap {\n display: flex;\n align-items: center;\n flex-shrink: 0;\n}\n\n.checkbox {\n display: flex;\n align-items: center;\n justify-content: center;\n width: 20px;\n height: 20px;\n border-radius: var(--gnome-radius-sm, 4px);\n border: 1.5px solid var(--gnome-border-subtle, rgba(0, 0, 0, 0.25));\n background-color: transparent;\n color: transparent;\n transition:\n background-color var(--gnome-duration-fast) var(--gnome-easing-default),\n border-color var(--gnome-duration-fast) var(--gnome-easing-default),\n color var(--gnome-duration-fast) var(--gnome-easing-default);\n}\n\n.checkboxChecked {\n background-color: var(--gnome-accent-bg-color, #3584e4);\n border-color: var(--gnome-accent-bg-color, #3584e4);\n color: #ffffff;\n}\n\n.checkmark {\n display: block;\n flex-shrink: 0;\n}\n\n/* ─── Leading icon/avatar ───────────────────────────────────────────────────── */\n\n.leading {\n display: flex;\n align-items: center;\n flex-shrink: 0;\n color: var(--gnome-window-fg-color, rgba(0, 0, 0, 0.8));\n}\n\n/* ─── Text content ──────────────────────────────────────────────────────────── */\n\n.content {\n display: flex;\n flex-direction: column;\n flex: 1;\n min-width: 0;\n gap: 2px;\n}\n\n.title {\n font-family: var(--gnome-font-family);\n font-size: var(--gnome-font-size-body, 1rem);\n font-weight: var(--gnome-font-weight-normal, 400);\n line-height: var(--gnome-line-height-body, 1.5);\n color: var(--gnome-card-fg-color, rgba(0, 0, 0, 0.8));\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n}\n\n.subtitle {\n font-family: var(--gnome-font-family);\n font-size: var(--gnome-font-size-caption, 0.75rem);\n font-weight: var(--gnome-font-weight-normal, 400);\n line-height: var(--gnome-line-height-body, 1.5);\n color: var(--gnome-window-fg-color, rgba(0, 0, 0, 0.55));\n opacity: var(--gnome-opacity-dim, 0.55);\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n}\n\n/* ─── High contrast ─────────────────────────────────────────────────────────── */\n\n@media (prefers-contrast: more) {\n .row:focus-visible {\n box-shadow:\n inset 0 0 0 var(--gnome-focus-ring-offset, 2px) var(--gnome-window-bg-color, #fafafa),\n inset 0 0 0 calc(var(--gnome-focus-ring-offset, 2px) + var(--gnome-focus-ring-width, 3px))\n var(--gnome-focus-ring-color, #3584e4),\n inset 0 0 0 calc(var(--gnome-focus-ring-offset, 2px) + var(--gnome-focus-ring-width, 3px) + 1px)\n var(--gnome-window-bg-color, #fafafa);\n }\n\n .checkbox {\n border-width: 2px;\n }\n}\n\n/* ─── Reduced motion ────────────────────────────────────────────────────────── */\n\n@media (prefers-reduced-motion: reduce) {\n .checkbox {\n transition: none;\n }\n}\n","import { useState, useId, type HTMLAttributes, type ReactNode } from \"react\";\nimport styles from \"./CheckRow.module.css\";\n\nexport interface CheckRowProps extends Omit<HTMLAttributes<HTMLButtonElement>, \"onChange\"> {\n /** Primary label. */\n title: string;\n /** Secondary line below the title. */\n subtitle?: string;\n /** Icon or image placed at the leading edge. */\n leading?: ReactNode;\n /** Controlled checked state. */\n checked?: boolean;\n /** Initial checked state when uncontrolled. Defaults to `false`. */\n defaultChecked?: boolean;\n /** Called when the checked state changes. */\n onCheckedChange?: (checked: boolean) => void;\n /** Disables the row. */\n disabled?: boolean;\n}\n\n/**\n * Activatable row with an integrated checkbox.\n *\n * The entire row is a button — clicking anywhere toggles the checkbox.\n * Mirrors the `AdwCheckButton`-in-a-row pattern used in list selection contexts.\n *\n * Supports both controlled (`checked`) and uncontrolled (`defaultChecked`) modes.\n *\n * @see https://gnome.pages.gitlab.gnome.org/libadwaita/doc/main/class.CheckButton.html\n */\nexport function CheckRow({\n title,\n subtitle,\n leading,\n checked: controlledChecked,\n defaultChecked = false,\n onCheckedChange,\n disabled = false,\n className,\n onClick,\n ...props\n}: CheckRowProps) {\n const isControlled = controlledChecked !== undefined;\n const [uncontrolledChecked, setUncontrolledChecked] = useState(defaultChecked);\n const checked = isControlled ? controlledChecked : uncontrolledChecked;\n\n const labelId = useId();\n\n const handleClick = (e: React.MouseEvent<HTMLButtonElement>) => {\n const next = !checked;\n if (!isControlled) setUncontrolledChecked(next);\n onCheckedChange?.(next);\n onClick?.(e);\n };\n\n return (\n <button\n role=\"checkbox\"\n aria-checked={checked}\n aria-labelledby={labelId}\n disabled={disabled}\n className={[styles.row, disabled ? styles.disabled : null, className]\n .filter(Boolean)\n .join(\" \")}\n onClick={handleClick}\n {...props}\n >\n {/* Visual checkbox — aria-hidden since the button carries the role */}\n <span className={styles.checkboxWrap} aria-hidden=\"true\">\n <span className={[styles.checkbox, checked ? styles.checkboxChecked : null].filter(Boolean).join(\" \")}>\n {checked && (\n <svg className={styles.checkmark} width=\"12\" height=\"12\" viewBox=\"0 0 12 12\" focusable=\"false\">\n <path\n d=\"M2 6l3 3 5-5\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth=\"1.75\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n />\n </svg>\n )}\n </span>\n </span>\n\n {leading && <span className={styles.leading}>{leading}</span>}\n\n <span className={styles.content} id={labelId}>\n <span className={styles.title}>{title}</span>\n {subtitle && <span className={styles.subtitle}>{subtitle}</span>}\n </span>\n </button>\n );\n}\n","/* ─── Row container ─────────────────────────────────────────────────────────── */\n\n.row {\n display: flex;\n align-items: center;\n gap: var(--gnome-space-2, 12px);\n padding: var(--gnome-space-2, 12px) var(--gnome-space-4, 24px);\n min-height: 52px;\n width: 100%;\n color: var(--gnome-card-fg-color, rgba(0, 0, 0, 0.8));\n text-align: start;\n}\n\n/* ─── Disabled ──────────────────────────────────────────────────────────────── */\n\n.disabled {\n opacity: var(--gnome-opacity-disabled, 0.5);\n pointer-events: none;\n}\n\n/* ─── Leading icon/avatar ───────────────────────────────────────────────────── */\n\n.leading {\n display: flex;\n align-items: center;\n flex-shrink: 0;\n color: var(--gnome-window-fg-color, rgba(0, 0, 0, 0.8));\n}\n\n/* ─── Text content ──────────────────────────────────────────────────────────── */\n\n.content {\n display: flex;\n flex-direction: column;\n flex: 1;\n min-width: 0;\n gap: 2px;\n}\n\n.title {\n font-family: var(--gnome-font-family);\n font-size: var(--gnome-font-size-body, 1rem);\n font-weight: var(--gnome-font-weight-normal, 400);\n line-height: var(--gnome-line-height-body, 1.5);\n color: var(--gnome-card-fg-color, rgba(0, 0, 0, 0.8));\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n}\n\n.subtitle {\n font-family: var(--gnome-font-family);\n font-size: var(--gnome-font-size-caption, 0.75rem);\n font-weight: var(--gnome-font-weight-normal, 400);\n line-height: var(--gnome-line-height-body, 1.5);\n color: var(--gnome-window-fg-color, rgba(0, 0, 0, 0.55));\n opacity: var(--gnome-opacity-dim, 0.55);\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n}\n\n/* ─── Combo wrapper ─────────────────────────────────────────────────────────── */\n\n.comboWrap {\n position: relative;\n flex-shrink: 0;\n min-width: 120px;\n}\n\n/* ─── Trigger button ────────────────────────────────────────────────────────── */\n\n.trigger {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: var(--gnome-space-1, 6px);\n width: 100%;\n\n padding: 5px var(--gnome-space-2, 12px);\n min-height: 32px;\n\n appearance: none;\n background-color: var(--gnome-card-bg-color, #ffffff);\n border: 1px solid var(--gnome-card-shade-color, rgba(0, 0, 0, 0.15));\n border-radius: var(--gnome-radius-md, 8px);\n box-shadow: var(--gnome-shadow-sm, 0 1px 2px rgba(0, 0, 0, 0.08));\n\n font-family: var(--gnome-font-family);\n font-size: var(--gnome-font-size-body, 1rem);\n font-weight: var(--gnome-font-weight-normal, 400);\n color: var(--gnome-window-fg-color, rgba(0, 0, 0, 0.8));\n\n cursor: pointer;\n user-select: none;\n text-align: left;\n white-space: nowrap;\n\n transition:\n background-color var(--gnome-duration-fast) var(--gnome-easing-default),\n border-color var(--gnome-duration-fast) var(--gnome-easing-default),\n box-shadow var(--gnome-duration-fast) var(--gnome-easing-default);\n}\n\n.trigger:hover:not(:disabled) {\n background-color: color-mix(in srgb, var(--gnome-card-bg-color, #ffffff) 90%, black 10%);\n}\n\n.trigger:focus {\n outline: none;\n}\n\n.trigger:focus-visible {\n border-color: var(--gnome-accent-color, #3584e4);\n box-shadow:\n 0 0 0 var(--gnome-focus-ring-offset, 2px) var(--gnome-window-bg-color, #fafafa),\n 0 0 0 calc(var(--gnome-focus-ring-offset, 2px) + var(--gnome-focus-ring-width, 3px))\n var(--gnome-focus-ring-color, #3584e4);\n}\n\n.trigger:disabled {\n opacity: var(--gnome-opacity-disabled, 0.5);\n cursor: not-allowed;\n}\n\n.triggerOpen {\n border-color: var(--gnome-accent-color, #3584e4);\n box-shadow:\n 0 0 0 var(--gnome-focus-ring-offset, 2px) var(--gnome-window-bg-color, #fafafa),\n 0 0 0 calc(var(--gnome-focus-ring-offset, 2px) + var(--gnome-focus-ring-width, 3px))\n var(--gnome-focus-ring-color, #3584e4);\n}\n\n/* ─── Trigger label ─────────────────────────────────────────────────────────── */\n\n.triggerLabel {\n flex: 1;\n min-width: 0;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n.placeholder {\n opacity: var(--gnome-opacity-dim, 0.55);\n}\n\n/* ─── Chevron ───────────────────────────────────────────────────────────────── */\n\n.chevron {\n flex-shrink: 0;\n color: var(--gnome-window-fg-color, rgba(0, 0, 0, 0.8));\n opacity: var(--gnome-opacity-dim, 0.55);\n transition: transform var(--gnome-duration-fast) var(--gnome-easing-default);\n}\n\n.chevronOpen {\n transform: rotate(180deg);\n}\n\n/* ─── Listbox ───────────────────────────────────────────────────────────────── */\n\n.list {\n position: absolute;\n left: 0;\n right: 0;\n z-index: 1000;\n\n margin: 0;\n padding: 4px 0;\n list-style: none;\n\n background-color: var(--gnome-popover-bg-color, var(--gnome-card-bg-color, #ffffff));\n border: 1px solid var(--gnome-card-shade-color, rgba(0, 0, 0, 0.12));\n border-radius: var(--gnome-radius-md, 8px);\n box-shadow:\n 0 4px 16px rgba(0, 0, 0, 0.12),\n 0 1px 3px rgba(0, 0, 0, 0.08);\n\n max-height: 260px;\n overflow-y: auto;\n overscroll-behavior: contain;\n\n animation: list-in var(--gnome-duration-fast) var(--gnome-easing-default) both;\n}\n\n@keyframes list-in {\n from { opacity: 0; transform: scaleY(0.95); transform-origin: top center; }\n to { opacity: 1; transform: scaleY(1); transform-origin: top center; }\n}\n\n.listDown {\n top: calc(100% + 4px);\n}\n\n.listUp {\n bottom: calc(100% + 4px);\n animation-name: list-in-up;\n}\n\n@keyframes list-in-up {\n from { opacity: 0; transform: scaleY(0.95); transform-origin: bottom center; }\n to { opacity: 1; transform: scaleY(1); transform-origin: bottom center; }\n}\n\n/* ─── Option ────────────────────────────────────────────────────────────────── */\n\n.option {\n display: flex;\n align-items: center;\n gap: var(--gnome-space-2, 12px);\n padding: 6px var(--gnome-space-2, 12px);\n min-height: 36px;\n\n font-family: var(--gnome-font-family);\n font-size: var(--gnome-font-size-body, 1rem);\n color: var(--gnome-window-fg-color, rgba(0, 0, 0, 0.8));\n\n cursor: pointer;\n user-select: none;\n\n transition: background-color var(--gnome-duration-fast) var(--gnome-easing-default);\n}\n\n.optionActive {\n background-color: color-mix(in srgb, var(--gnome-accent-bg-color, #3584e4) 12%, transparent);\n}\n\n.optionSelected {\n font-weight: var(--gnome-font-weight-semibold, 600);\n}\n\n.optionDisabled {\n opacity: var(--gnome-opacity-disabled, 0.5);\n cursor: not-allowed;\n pointer-events: none;\n}\n\n.optionLabel {\n flex: 1;\n min-width: 0;\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n}\n\n.checkIcon {\n flex-shrink: 0;\n color: var(--gnome-accent-color, #3584e4);\n}\n\n/* ─── Dark mode ─────────────────────────────────────────────────────────────── */\n\n@media (prefers-color-scheme: dark) {\n .trigger {\n background-color: var(--gnome-card-bg-color, #383838);\n border-color: rgba(255, 255, 255, 0.12);\n box-shadow: none;\n }\n\n .trigger:hover:not(:disabled) {\n background-color: rgba(255, 255, 255, 0.08);\n }\n\n .list {\n background-color: var(--gnome-card-bg-color, #383838);\n border-color: rgba(255, 255, 255, 0.1);\n box-shadow:\n 0 4px 16px rgba(0, 0, 0, 0.4),\n 0 1px 3px rgba(0, 0, 0, 0.3);\n }\n}\n\n/* ─── Reduced motion ────────────────────────────────────────────────────────── */\n\n@media (prefers-reduced-motion: reduce) {\n .trigger,\n .chevron {\n transition: none;\n }\n\n .list {\n animation: none;\n }\n}\n","import { useState, useRef, useId, useEffect, useCallback, type HTMLAttributes, type KeyboardEvent, type ReactNode } from \"react\";\nimport styles from \"./ComboRow.module.css\";\n\nexport interface ComboRowOption<V extends string = string> {\n value: V;\n label: string;\n disabled?: boolean;\n}\n\nexport interface ComboRowProps<V extends string = string>\n extends Omit<HTMLAttributes<HTMLDivElement>, \"onChange\"> {\n /** Primary label. */\n title: string;\n /** Secondary line below the title. */\n subtitle?: string;\n /** Icon or image placed at the leading edge. */\n leading?: ReactNode;\n /** The list of selectable options. */\n options: ComboRowOption<V>[];\n /** The currently selected value (controlled). */\n value?: V;\n /** Initial value when uncontrolled. */\n defaultValue?: V;\n /** Called when the user selects an option. */\n onValueChange?: (value: V) => void;\n /** Disables the row. */\n disabled?: boolean;\n}\n\n/**\n * Settings row with an inline combo selector at the trailing edge.\n *\n * Mirrors `AdwComboRow` — a standard `ActionRow` layout with a dropdown\n * for choosing among a set of options. The dropdown opens inline in the row\n * and follows the same keyboard patterns as the standalone `Dropdown`.\n *\n * @see https://gnome.pages.gitlab.gnome.org/libadwaita/doc/main/class.ComboRow.html\n */\nexport function ComboRow<V extends string = string>({\n title,\n subtitle,\n leading,\n options,\n value: controlledValue,\n defaultValue,\n onValueChange,\n disabled = false,\n className,\n ...props\n}: ComboRowProps<V>) {\n const isControlled = controlledValue !== undefined;\n const [uncontrolledValue, setUncontrolledValue] = useState<V | undefined>(defaultValue);\n const value = isControlled ? controlledValue : uncontrolledValue;\n\n const [open, setOpen] = useState(false);\n const [activeIndex, setActiveIndex] = useState(-1);\n const [flipUp, setFlipUp] = useState(false);\n\n const triggerId = useId();\n const listboxId = useId();\n const triggerRef = useRef<HTMLButtonElement>(null);\n const listRef = useRef<HTMLUListElement>(null);\n\n const selected = options.find((o) => o.value === value);\n\n const computeFlip = useCallback(() => {\n if (!triggerRef.current) return;\n const rect = triggerRef.current.getBoundingClientRect();\n const spaceBelow = window.innerHeight - rect.bottom;\n const listH = Math.min(options.length * 44 + 8, 260);\n setFlipUp(spaceBelow < listH && rect.top > listH);\n }, [options.length]);\n\n const openList = useCallback(() => {\n if (disabled) return;\n computeFlip();\n setOpen(true);\n const idx = options.findIndex((o) => o.value === value && !o.disabled);\n setActiveIndex(idx >= 0 ? idx : options.findIndex((o) => !o.disabled));\n }, [disabled, computeFlip, options, value]);\n\n const closeList = useCallback(() => {\n setOpen(false);\n setActiveIndex(-1);\n triggerRef.current?.focus();\n }, []);\n\n const selectOption = useCallback(\n (opt: ComboRowOption<V>) => {\n if (opt.disabled) return;\n if (!isControlled) setUncontrolledValue(opt.value);\n onValueChange?.(opt.value);\n closeList();\n },\n [isControlled, onValueChange, closeList],\n );\n\n useEffect(() => {\n if (!open || activeIndex < 0 || !listRef.current) return;\n const item = listRef.current.children[activeIndex] as HTMLElement | undefined;\n item?.scrollIntoView({ block: \"nearest\" });\n }, [activeIndex, open]);\n\n useEffect(() => {\n if (!open) return;\n const handler = (e: MouseEvent) => {\n if (\n !triggerRef.current?.contains(e.target as Node) &&\n !listRef.current?.contains(e.target as Node)\n ) {\n closeList();\n }\n };\n document.addEventListener(\"mousedown\", handler);\n return () => document.removeEventListener(\"mousedown\", handler);\n }, [open, closeList]);\n\n const handleTriggerKeyDown = useCallback(\n (e: KeyboardEvent<HTMLButtonElement>) => {\n switch (e.key) {\n case \"Enter\":\n case \" \":\n case \"ArrowDown\":\n e.preventDefault();\n openList();\n break;\n case \"ArrowUp\":\n e.preventDefault();\n computeFlip();\n setOpen(true);\n setActiveIndex(\n options.reduce((last, o, i) => (!o.disabled ? i : last), -1),\n );\n break;\n }\n },\n [openList, computeFlip, options],\n );\n\n const handleListKeyDown = useCallback(\n (e: KeyboardEvent<HTMLUListElement>) => {\n const enabledIndexes = options\n .map((o, i) => ({ o, i }))\n .filter(({ o }) => !o.disabled)\n .map(({ i }) => i);\n const currentPos = enabledIndexes.indexOf(activeIndex);\n\n switch (e.key) {\n case \"ArrowDown\": {\n e.preventDefault();\n const next = enabledIndexes[Math.min(currentPos + 1, enabledIndexes.length - 1)];\n setActiveIndex(next ?? activeIndex);\n break;\n }\n case \"ArrowUp\": {\n e.preventDefault();\n const prev = enabledIndexes[Math.max(currentPos - 1, 0)];\n setActiveIndex(prev ?? activeIndex);\n break;\n }\n case \"Home\":\n e.preventDefault();\n setActiveIndex(enabledIndexes[0] ?? -1);\n break;\n case \"End\":\n e.preventDefault();\n setActiveIndex(enabledIndexes[enabledIndexes.length - 1] ?? -1);\n break;\n case \"Enter\":\n case \" \": {\n e.preventDefault();\n if (activeIndex >= 0) selectOption(options[activeIndex]);\n break;\n }\n case \"Escape\":\n case \"Tab\":\n closeList();\n break;\n }\n },\n [options, activeIndex, selectOption, closeList],\n );\n\n return (\n <div\n className={[styles.row, disabled ? styles.disabled : null, className]\n .filter(Boolean)\n .join(\" \")}\n {...props}\n >\n {leading && <span className={styles.leading}>{leading}</span>}\n\n <span className={styles.content}>\n <span className={styles.title}>{title}</span>\n {subtitle && <span className={styles.subtitle}>{subtitle}</span>}\n </span>\n\n {/* Combo trigger button */}\n <div className={styles.comboWrap}>\n <button\n ref={triggerRef}\n id={triggerId}\n type=\"button\"\n role=\"combobox\"\n aria-haspopup=\"listbox\"\n aria-expanded={open}\n aria-controls={listboxId}\n aria-activedescendant={\n open && activeIndex >= 0 ? `${listboxId}-opt-${activeIndex}` : undefined\n }\n disabled={disabled}\n className={[styles.trigger, open ? styles.triggerOpen : null].filter(Boolean).join(\" \")}\n onClick={() => (open ? closeList() : openList())}\n onKeyDown={handleTriggerKeyDown}\n >\n <span className={[styles.triggerLabel, !selected ? styles.placeholder : null]\n .filter(Boolean).join(\" \")}>\n {selected?.label ?? \"—\"}\n </span>\n <svg\n className={[styles.chevron, open ? styles.chevronOpen : null].filter(Boolean).join(\" \")}\n width=\"16\"\n height=\"16\"\n viewBox=\"0 0 16 16\"\n aria-hidden=\"true\"\n focusable=\"false\"\n >\n <path\n d=\"M4 6l4 4 4-4\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth=\"1.5\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n />\n </svg>\n </button>\n\n {open && (\n <ul\n ref={listRef}\n id={listboxId}\n role=\"listbox\"\n aria-labelledby={triggerId}\n tabIndex={-1}\n className={[styles.list, flipUp ? styles.listUp : styles.listDown]\n .filter(Boolean)\n .join(\" \")}\n onKeyDown={handleListKeyDown}\n >\n {options.map((opt, i) => (\n <li\n key={opt.value}\n id={`${listboxId}-opt-${i}`}\n role=\"option\"\n aria-selected={opt.value === value}\n aria-disabled={opt.disabled}\n className={[\n styles.option,\n opt.value === value ? styles.optionSelected : null,\n i === activeIndex ? styles.optionActive : null,\n opt.disabled ? styles.optionDisabled : null,\n ]\n .filter(Boolean)\n .join(\" \")}\n onMouseEnter={() => !opt.disabled && setActiveIndex(i)}\n onClick={() => selectOption(opt)}\n >\n <span className={styles.optionLabel}>{opt.label}</span>\n {opt.value === value && (\n <svg width=\"16\" height=\"16\" viewBox=\"0 0 16 16\" aria-hidden=\"true\" focusable=\"false\" className={styles.checkIcon}>\n <path\n d=\"M3 8l4 4 6-6\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth=\"1.75\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n />\n </svg>\n )}\n </li>\n ))}\n </ul>\n )}\n </div>\n </div>\n );\n}\n","/* ─── Row container ─────────────────────────────────────────────────────────── */\n\n.row {\n display: flex;\n align-items: center;\n gap: var(--gnome-space-2, 12px);\n padding: 0 var(--gnome-space-4, 24px);\n min-height: 52px;\n width: 100%;\n cursor: text;\n color: var(--gnome-card-fg-color, rgba(0, 0, 0, 0.8));\n\n transition: box-shadow var(--gnome-duration-fast) var(--gnome-easing-default);\n}\n\n.focused {\n box-shadow:\n inset 0 0 0 var(--gnome-focus-ring-offset, 2px) var(--gnome-window-bg-color, #fafafa),\n inset 0 0 0 calc(var(--gnome-focus-ring-offset, 2px) + var(--gnome-focus-ring-width, 3px))\n var(--gnome-focus-ring-color, #3584e4);\n}\n\n/* ─── Disabled ──────────────────────────────────────────────────────────────── */\n\n.disabled {\n opacity: var(--gnome-opacity-disabled, 0.5);\n cursor: not-allowed;\n pointer-events: none;\n}\n\n/* ─── Leading / trailing ────────────────────────────────────────────────────── */\n\n.leading {\n display: flex;\n align-items: center;\n flex-shrink: 0;\n color: var(--gnome-window-fg-color, rgba(0, 0, 0, 0.8));\n}\n\n.trailing {\n display: flex;\n align-items: center;\n flex-shrink: 0;\n gap: var(--gnome-space-1, 6px);\n}\n\n/* ─── Field wrap ────────────────────────────────────────────────────────────── */\n\n.fieldWrap {\n position: relative;\n display: flex;\n flex-direction: column;\n justify-content: center;\n flex: 1;\n min-width: 0;\n min-height: 52px;\n}\n\n/* ─── Floating label ────────────────────────────────────────────────────────── */\n\n.label {\n position: absolute;\n left: 0;\n top: 50%;\n transform: translateY(-50%);\n pointer-events: none;\n\n font-family: var(--gnome-font-family);\n font-size: var(--gnome-font-size-body, 1rem);\n line-height: var(--gnome-line-height-body, 1.5);\n color: var(--gnome-window-fg-color, rgba(0, 0, 0, 0.55));\n opacity: var(--gnome-opacity-dim, 0.55);\n\n transition:\n top var(--gnome-duration-fast) var(--gnome-easing-default),\n transform var(--gnome-duration-fast) var(--gnome-easing-default),\n font-size var(--gnome-duration-fast) var(--gnome-easing-default),\n opacity var(--gnome-duration-fast) var(--gnome-easing-default);\n}\n\n/* When floated (focused or has value) the label moves up */\n.labelFloated {\n top: 10px;\n transform: translateY(0);\n font-size: var(--gnome-font-size-caption, 0.75rem);\n opacity: 0.7;\n}\n\n/* ─── Input field ───────────────────────────────────────────────────────────── */\n\n.input {\n appearance: none;\n background: transparent;\n border: none;\n outline: none;\n padding: 0;\n margin: 0;\n width: 100%;\n\n font-family: var(--gnome-font-family);\n font-size: var(--gnome-font-size-body, 1rem);\n font-weight: var(--gnome-font-weight-normal, 400);\n line-height: var(--gnome-line-height-body, 1.5);\n color: var(--gnome-card-fg-color, rgba(0, 0, 0, 0.8));\n\n /* Hide until floated — the label is the placeholder */\n opacity: 0;\n padding-top: 18px;\n padding-bottom: 4px;\n\n transition: opacity var(--gnome-duration-fast) var(--gnome-easing-default);\n}\n\n.inputFloated {\n opacity: 1;\n}\n\n.input:disabled {\n cursor: not-allowed;\n}\n\n/* ─── High contrast ─────────────────────────────────────────────────────────── */\n\n@media (prefers-contrast: more) {\n .focused {\n box-shadow:\n inset 0 0 0 var(--gnome-focus-ring-offset, 2px) var(--gnome-window-bg-color, #fafafa),\n inset 0 0 0 calc(var(--gnome-focus-ring-offset, 2px) + var(--gnome-focus-ring-width, 3px))\n var(--gnome-focus-ring-color, #3584e4),\n inset 0 0 0 calc(var(--gnome-focus-ring-offset, 2px) + var(--gnome-focus-ring-width, 3px) + 1px)\n var(--gnome-window-bg-color, #fafafa);\n }\n}\n\n/* ─── Reduced motion ────────────────────────────────────────────────────────── */\n\n@media (prefers-reduced-motion: reduce) {\n .row,\n .label,\n .input {\n transition: none;\n }\n}\n","import { useState, useId, useRef, type InputHTMLAttributes, type ReactNode } from \"react\";\nimport styles from \"./EntryRow.module.css\";\n\nexport interface EntryRowProps extends InputHTMLAttributes<HTMLInputElement> {\n /**\n * Acts as a floating label: shown small above the input when the field has\n * content or focus, shown as a placeholder when empty and unfocused.\n */\n title: string;\n /** Called when the input value changes. */\n onValueChange?: (value: string) => void;\n /** Icon or widget placed at the leading edge. */\n leading?: ReactNode;\n /** Icon or widget placed at the trailing edge (e.g. a clear button, copy button). */\n trailing?: ReactNode;\n}\n\n/**\n * Row with an inline text entry field.\n *\n * Mirrors `AdwEntryRow` — the `title` floats up as a small label when the\n * field is focused or has content, acting as a placeholder when empty and\n * unfocused. Use inside a `BoxedList` for inline settings that require text input.\n *\n * @see https://gnome.pages.gitlab.gnome.org/libadwaita/doc/main/class.EntryRow.html\n */\nexport function EntryRow({\n title,\n value: controlledValue,\n defaultValue = \"\",\n onValueChange,\n leading,\n trailing,\n disabled,\n className,\n id: externalId,\n onChange,\n ...props\n}: EntryRowProps) {\n const generatedId = useId();\n const inputId = externalId ?? generatedId;\n\n const isControlled = controlledValue !== undefined;\n const [uncontrolledValue, setUncontrolledValue] = useState(String(defaultValue));\n const inputValue = isControlled ? String(controlledValue) : uncontrolledValue;\n\n const [focused, setFocused] = useState(false);\n const inputRef = useRef<HTMLInputElement>(null);\n\n const floated = focused || inputValue.length > 0;\n\n const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {\n if (!isControlled) setUncontrolledValue(e.target.value);\n onValueChange?.(e.target.value);\n onChange?.(e);\n };\n\n return (\n <div\n className={[\n styles.row,\n focused ? styles.focused : null,\n disabled ? styles.disabled : null,\n className,\n ]\n .filter(Boolean)\n .join(\" \")}\n onClick={() => inputRef.current?.focus()}\n >\n {leading && <span className={styles.leading}>{leading}</span>}\n\n <span className={styles.fieldWrap}>\n <label\n htmlFor={inputId}\n className={[styles.label, floated ? styles.labelFloated : null].filter(Boolean).join(\" \")}\n >\n {title}\n </label>\n <input\n ref={inputRef}\n id={inputId}\n className={[styles.input, floated ? styles.inputFloated : null].filter(Boolean).join(\" \")}\n value={inputValue}\n disabled={disabled}\n onFocus={() => setFocused(true)}\n onBlur={() => setFocused(false)}\n onChange={handleChange}\n {...props}\n />\n </span>\n\n {trailing && <span className={styles.trailing}>{trailing}</span>}\n </div>\n );\n}\n","/* ─── Reveal button ─────────────────────────────────────────────────────────── */\n\n.revealButton {\n display: flex;\n align-items: center;\n justify-content: center;\n width: 32px;\n height: 32px;\n padding: 0;\n border: none;\n border-radius: var(--gnome-radius-small, 6px);\n background: transparent;\n cursor: pointer;\n color: var(--gnome-window-fg-color, rgba(0, 0, 0, 0.8));\n opacity: var(--gnome-opacity-dim, 0.55);\n flex-shrink: 0;\n\n transition:\n opacity var(--gnome-duration-fast) var(--gnome-easing-default),\n background var(--gnome-duration-fast) var(--gnome-easing-default);\n}\n\n.revealButton:hover {\n opacity: 1;\n background: var(--gnome-button-bg-color, rgba(0, 0, 0, 0.07));\n}\n\n.revealButton:active {\n background: var(--gnome-button-active-bg-color, rgba(0, 0, 0, 0.14));\n}\n\n.revealButton:focus-visible {\n outline: none;\n box-shadow:\n 0 0 0 var(--gnome-focus-ring-offset, 2px) var(--gnome-window-bg-color, #fafafa),\n 0 0 0 calc(var(--gnome-focus-ring-offset, 2px) + var(--gnome-focus-ring-width, 3px))\n var(--gnome-focus-ring-color, #3584e4);\n opacity: 1;\n}\n\n.revealButton:disabled {\n pointer-events: none;\n opacity: var(--gnome-opacity-disabled, 0.5);\n}\n\n/* ─── Reduced motion ────────────────────────────────────────────────────────── */\n\n@media (prefers-reduced-motion: reduce) {\n .revealButton {\n transition: none;\n }\n}\n","import { useState, type ReactNode } from \"react\";\nimport { Icon } from \"../Icon\";\nimport { ViewReveal, ViewConceal } from \"@gnome-ui/icons\";\nimport { EntryRow, type EntryRowProps } from \"../EntryRow\";\nimport styles from \"./PasswordEntryRow.module.css\";\n\nexport interface PasswordEntryRowProps extends Omit<EntryRowProps, \"type\" | \"trailing\"> {\n /** Additional trailing widgets placed before the reveal button. */\n trailing?: ReactNode;\n}\n\n/**\n * Password entry row with a built-in reveal/conceal toggle.\n *\n * Mirrors `AdwPasswordEntryRow` — an `EntryRow` variant that defaults to\n * `type=\"password\"` and provides a trailing icon button to reveal or conceal\n * the entered text. Use inside a `BoxedList` for password settings fields.\n *\n * @see https://gnome.pages.gitlab.gnome.org/libadwaita/doc/main/class.PasswordEntryRow.html\n */\nexport function PasswordEntryRow({ trailing, disabled, ...props }: PasswordEntryRowProps) {\n const [revealed, setRevealed] = useState(false);\n\n const revealButton = (\n <button\n type=\"button\"\n className={styles.revealButton}\n onClick={(e) => {\n e.stopPropagation();\n setRevealed((v) => !v);\n }}\n aria-label={revealed ? \"Conceal password\" : \"Reveal password\"}\n aria-pressed={revealed}\n disabled={disabled}\n >\n <Icon\n icon={revealed ? ViewConceal : ViewReveal}\n size=\"md\"\n aria-hidden\n />\n </button>\n );\n\n return (\n <EntryRow\n {...props}\n type={revealed ? \"text\" : \"password\"}\n disabled={disabled}\n trailing={\n <>\n {trailing}\n {revealButton}\n </>\n }\n />\n );\n}\n","/* ─── Row container ─────────────────────────────────────────────────────────── */\n\n.row {\n display: flex;\n align-items: center;\n gap: var(--gnome-space-2, 12px);\n padding: var(--gnome-space-2, 12px) var(--gnome-space-4, 24px);\n min-height: 52px;\n width: 100%;\n color: var(--gnome-card-fg-color, rgba(0, 0, 0, 0.8));\n text-align: start;\n}\n\n/* ─── Disabled ──────────────────────────────────────────────────────────────── */\n\n.disabled {\n opacity: var(--gnome-opacity-disabled, 0.5);\n pointer-events: none;\n}\n\n/* ─── Leading ───────────────────────────────────────────────────────────────── */\n\n.leading {\n display: flex;\n align-items: center;\n flex-shrink: 0;\n color: var(--gnome-window-fg-color, rgba(0, 0, 0, 0.8));\n}\n\n/* ─── Text content ──────────────────────────────────────────────────────────── */\n\n.content {\n display: flex;\n flex-direction: column;\n flex: 1;\n min-width: 0;\n gap: 2px;\n}\n\n.title {\n font-family: var(--gnome-font-family);\n font-size: var(--gnome-font-size-body, 1rem);\n font-weight: var(--gnome-font-weight-normal, 400);\n line-height: var(--gnome-line-height-body, 1.5);\n color: var(--gnome-card-fg-color, rgba(0, 0, 0, 0.8));\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n}\n\n.subtitle {\n font-family: var(--gnome-font-family);\n font-size: var(--gnome-font-size-caption, 0.75rem);\n font-weight: var(--gnome-font-weight-normal, 400);\n line-height: var(--gnome-line-height-body, 1.5);\n color: var(--gnome-window-fg-color, rgba(0, 0, 0, 0.55));\n opacity: var(--gnome-opacity-dim, 0.55);\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n}\n\n/* ─── Spin widget ───────────────────────────────────────────────────────────── */\n\n.spin {\n display: inline-flex;\n align-items: center;\n flex-shrink: 0;\n\n background-color: var(--gnome-view-bg-color, #ffffff);\n border: 1px solid var(--gnome-card-shade-color, rgba(0, 0, 0, 0.15));\n border-radius: var(--gnome-radius-md, 8px);\n box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.06);\n overflow: hidden;\n\n font-family: var(--gnome-font-family);\n font-size: var(--gnome-font-size-body, 1rem);\n color: var(--gnome-window-fg-color, rgba(0, 0, 0, 0.8));\n\n transition:\n box-shadow var(--gnome-duration-fast) var(--gnome-easing-default),\n border-color var(--gnome-duration-fast) var(--gnome-easing-default);\n}\n\n.spin:focus {\n outline: none;\n}\n\n.spin:focus-visible {\n border-color: var(--gnome-accent-color, #3584e4);\n box-shadow:\n inset 0 1px 2px rgba(0, 0, 0, 0.06),\n 0 0 0 var(--gnome-focus-ring-offset, 2px) var(--gnome-window-bg-color, #fafafa),\n 0 0 0 calc(var(--gnome-focus-ring-offset, 2px) + var(--gnome-focus-ring-width, 3px))\n var(--gnome-focus-ring-color, #3584e4);\n}\n\n/* ─── −/+ buttons ───────────────────────────────────────────────────────────── */\n\n.spinBtn {\n appearance: none;\n -webkit-appearance: none;\n border: none;\n background: transparent;\n font: inherit;\n font-size: 1.1rem;\n line-height: 1;\n cursor: pointer;\n color: inherit;\n\n display: flex;\n align-items: center;\n justify-content: center;\n width: 32px;\n height: 34px;\n flex-shrink: 0;\n\n transition: background-color var(--gnome-duration-fast) var(--gnome-easing-default);\n}\n\n.spinBtn:hover:not(:disabled) {\n background-color: color-mix(in srgb, var(--gnome-view-bg-color, #ffffff) 90%, black 10%);\n}\n\n.spinBtn:active:not(:disabled) {\n background-color: color-mix(in srgb, var(--gnome-view-bg-color, #ffffff) 80%, black 20%);\n}\n\n.spinBtn:disabled {\n opacity: var(--gnome-opacity-disabled, 0.5);\n cursor: not-allowed;\n}\n\n.spinBtn:first-child {\n border-right: 1px solid var(--gnome-card-shade-color, rgba(0, 0, 0, 0.12));\n}\n\n.spinBtn:last-child {\n border-left: 1px solid var(--gnome-card-shade-color, rgba(0, 0, 0, 0.12));\n}\n\n/* ─── Value display ─────────────────────────────────────────────────────────── */\n\n.spinValue {\n text-align: center;\n padding: 0 var(--gnome-space-2, 12px);\n min-width: 48px;\n font-variant-numeric: tabular-nums;\n user-select: none;\n white-space: nowrap;\n}\n\n/* ─── Dark mode ─────────────────────────────────────────────────────────────── */\n\n@media (prefers-color-scheme: dark) {\n .spin {\n background-color: var(--gnome-view-bg-color, #1e1e1e);\n border-color: rgba(255, 255, 255, 0.12);\n box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.2);\n }\n\n .spinBtn:hover:not(:disabled) {\n background-color: color-mix(in srgb, var(--gnome-view-bg-color, #1e1e1e) 85%, white 15%);\n }\n\n .spinBtn:active:not(:disabled) {\n background-color: color-mix(in srgb, var(--gnome-view-bg-color, #1e1e1e) 75%, white 25%);\n }\n}\n\n/* ─── Reduced motion ────────────────────────────────────────────────────────── */\n\n@media (prefers-reduced-motion: reduce) {\n .spin,\n .spinBtn {\n transition: none;\n }\n}\n","import { useState, useId, useCallback, type HTMLAttributes, type ReactNode, type KeyboardEvent } from \"react\";\nimport styles from \"./SpinRow.module.css\";\n\nexport interface SpinRowProps extends Omit<HTMLAttributes<HTMLDivElement>, \"onChange\"> {\n /** Primary label. */\n title: string;\n /** Secondary line below the title. */\n subtitle?: string;\n /** Icon or image placed at the leading edge. */\n leading?: ReactNode;\n /** Current value (controlled). */\n value?: number;\n /** Initial value when uncontrolled. Defaults to `0`. */\n defaultValue?: number;\n /** Called when the value changes. */\n onValueChange?: (value: number) => void;\n /** Minimum allowed value. Defaults to `0`. */\n min?: number;\n /** Maximum allowed value. Defaults to `100`. */\n max?: number;\n /** Amount to increment/decrement per step. Defaults to `1`. */\n step?: number;\n /** Number of decimal places shown. Derived from `step` when omitted. */\n decimals?: number;\n /** Disables the row. */\n disabled?: boolean;\n}\n\nfunction countDecimals(n: number): number {\n const s = n.toString();\n const dot = s.indexOf(\".\");\n return dot === -1 ? 0 : s.length - dot - 1;\n}\n\nfunction clamp(v: number, min: number, max: number): number {\n return Math.min(max, Math.max(min, v));\n}\n\n/**\n * Settings row with an integrated spin button for numeric values.\n *\n * Mirrors `AdwSpinRow` — a standard row layout with − / + buttons at the\n * trailing edge. Use inside a `BoxedList` for settings with numeric ranges\n * (volume, timeout duration, count limits, etc.).\n *\n * Supports both controlled (`value`) and uncontrolled (`defaultValue`) modes.\n *\n * @see https://gnome.pages.gitlab.gnome.org/libadwaita/doc/main/class.SpinRow.html\n */\nexport function SpinRow({\n title,\n subtitle,\n leading,\n value: controlledValue,\n defaultValue = 0,\n onValueChange,\n min = 0,\n max = 100,\n step = 1,\n decimals,\n disabled = false,\n className,\n ...props\n}: SpinRowProps) {\n const isControlled = controlledValue !== undefined;\n const [uncontrolledValue, setUncontrolledValue] = useState(defaultValue);\n const value = isControlled ? controlledValue : uncontrolledValue;\n\n const dp = decimals ?? countDecimals(step);\n const spinId = useId();\n\n const set = useCallback(\n (next: number) => {\n const clamped = parseFloat(clamp(next, min, max).toFixed(dp));\n if (!isControlled) setUncontrolledValue(clamped);\n onValueChange?.(clamped);\n },\n [isControlled, min, max, dp, onValueChange],\n );\n\n const handleKeyDown = useCallback(\n (e: KeyboardEvent<HTMLDivElement>) => {\n switch (e.key) {\n case \"ArrowUp\": e.preventDefault(); set(value + step); break;\n case \"ArrowDown\": e.preventDefault(); set(value - step); break;\n case \"PageUp\": e.preventDefault(); set(value + step * 10); break;\n case \"PageDown\": e.preventDefault(); set(value - step * 10); break;\n case \"Home\": e.preventDefault(); set(min); break;\n case \"End\": e.preventDefault(); set(max); break;\n }\n },\n [value, step, min, max, set],\n );\n\n return (\n <div\n className={[styles.row, disabled ? styles.disabled : null, className]\n .filter(Boolean)\n .join(\" \")}\n {...props}\n >\n {leading && <span className={styles.leading}>{leading}</span>}\n\n <span className={styles.content}>\n <span className={styles.title}>{title}</span>\n {subtitle && <span className={styles.subtitle}>{subtitle}</span>}\n </span>\n\n {/* Spin button widget */}\n <div\n role=\"spinbutton\"\n aria-valuenow={value}\n aria-valuemin={min}\n aria-valuemax={max}\n aria-labelledby={spinId}\n aria-disabled={disabled || undefined}\n tabIndex={disabled ? -1 : 0}\n onKeyDown={disabled ? undefined : handleKeyDown}\n className={styles.spin}\n >\n <button\n type=\"button\"\n tabIndex={-1}\n aria-hidden=\"true\"\n disabled={disabled || value <= min}\n className={styles.spinBtn}\n onClick={(e) => { e.stopPropagation(); set(value - step); }}\n >\n −\n </button>\n\n <span id={spinId} className={styles.spinValue} aria-hidden=\"true\">\n {value.toFixed(dp)}\n </span>\n\n <button\n type=\"button\"\n tabIndex={-1}\n aria-hidden=\"true\"\n disabled={disabled || value >= max}\n className={styles.spinBtn}\n onClick={(e) => { e.stopPropagation(); set(value + step); }}\n >\n +\n </button>\n </div>\n </div>\n );\n}\n","/* ─── View container ────────────────────────────────────────────────────────── */\n\n.view {\n position: relative;\n overflow: hidden;\n width: 100%;\n height: 100%;\n}\n\n/* ─── Page ──────────────────────────────────────────────────────────────────── */\n\n.page {\n display: flex;\n flex-direction: column;\n width: 100%;\n height: 100%;\n background-color: var(--gnome-window-bg-color, #fafafa);\n color: var(--gnome-window-fg-color, rgba(0, 0, 0, 0.8));\n}\n\n/* ─── Page header (title bar) ────────────────────────────────────────────────── */\n\n.pageHeader {\n display: flex;\n align-items: center;\n justify-content: center;\n min-height: 47px;\n padding: 0 var(--gnome-space-4, 24px);\n border-bottom: 1px solid var(--gnome-headerbar-border-color, rgba(0, 0, 0, 0.12));\n background-color: var(--gnome-headerbar-bg-color, #ebebeb);\n flex-shrink: 0;\n}\n\n.pageTitle {\n font-family: var(--gnome-font-family);\n font-size: var(--gnome-font-size-body, 1rem);\n font-weight: var(--gnome-font-weight-semibold, 600);\n color: var(--gnome-headerbar-fg-color, rgba(0, 0, 0, 0.8));\n}\n\n/* ─── Page content ──────────────────────────────────────────────────────────── */\n\n.pageContent {\n flex: 1;\n overflow-y: auto;\n overflow-x: hidden;\n padding: var(--gnome-space-3, 16px);\n}\n\n/* ─── Enter animations ──────────────────────────────────────────────────────── */\n\n.enterForward {\n animation: slide-in-right var(--gnome-duration-normal, 200ms) var(--gnome-easing-default, ease) both;\n}\n\n.enterBack {\n animation: slide-in-left var(--gnome-duration-normal, 200ms) var(--gnome-easing-default, ease) both;\n}\n\n@keyframes slide-in-right {\n from { transform: translateX(40px); opacity: 0; }\n to { transform: translateX(0); opacity: 1; }\n}\n\n@keyframes slide-in-left {\n from { transform: translateX(-40px); opacity: 0; }\n to { transform: translateX(0); opacity: 1; }\n}\n\n/* ─── Reduced motion ────────────────────────────────────────────────────────── */\n\n@media (prefers-reduced-motion: reduce) {\n .enterForward,\n .enterBack {\n animation: none;\n }\n}\n","import {\n createContext,\n useContext,\n useState,\n type ReactNode,\n type HTMLAttributes,\n} from \"react\";\nimport styles from \"./NavigationView.module.css\";\n\n// ─── Context ─────────────────────────────────────────────────────────────────\n\ninterface NavigationContextValue {\n /** Navigate to a page by its `tag`. Pushes onto the stack. */\n navigate: (tag: string) => void;\n /** Go back one page. No-op if already at the root. */\n pop: () => void;\n /** `true` when there is at least one page to go back to. */\n canGoBack: boolean;\n /** Tag of the currently visible page. */\n currentTag: string;\n /** Direction of the last transition (`\"forward\"` or `\"back\"`). */\n direction: \"forward\" | \"back\";\n}\n\nconst NavigationContext = createContext<NavigationContextValue>({\n navigate: () => { },\n pop: () => { },\n canGoBack: false,\n currentTag: \"\",\n direction: \"forward\",\n});\n\n/** Access the nearest `NavigationView`'s navigation functions. */\nexport function useNavigation() {\n return useContext(NavigationContext);\n}\n\n// ─── NavigationPage ───────────────────────────────────────────────────────────\n\nexport interface NavigationPageProps extends HTMLAttributes<HTMLDivElement> {\n /** Unique identifier used with `navigate()`. */\n tag: string;\n /** Page heading shown in a `HeaderBar`-style title bar. */\n title: string;\n children?: ReactNode;\n}\n\n/**\n * A single page inside a `NavigationView`.\n * Only visible when its `tag` matches the current page in the stack.\n */\nexport function NavigationPage({\n tag,\n title,\n children,\n className,\n ...props\n}: NavigationPageProps) {\n const { currentTag, direction } = useContext(NavigationContext);\n if (currentTag !== tag) return null;\n\n return (\n <div\n className={[\n styles.page,\n direction === \"forward\" ? styles.enterForward : styles.enterBack,\n className,\n ]\n .filter(Boolean)\n .join(\" \")}\n {...props}\n >\n <div className={styles.pageHeader}>\n <span className={styles.pageTitle}>{title}</span>\n </div>\n <div className={styles.pageContent}>{children}</div>\n </div>\n );\n}\n\n// ─── NavigationView ───────────────────────────────────────────────────────────\n\nexport interface NavigationViewProps extends HTMLAttributes<HTMLDivElement> {\n /** Tag of the first page to show. */\n initialPage: string;\n children?: ReactNode;\n}\n\n/**\n * Single-pane push/pop navigation stack.\n *\n * Mirrors `AdwNavigationView` — the mobile-first counterpart to\n * `NavigationSplitView`. Each \"screen\" is a `NavigationPage` with a unique\n * `tag`. Use `useNavigation()` inside pages to call `navigate(tag)` and `pop()`.\n *\n * @example\n * ```tsx\n * <NavigationView initialPage=\"home\">\n * <NavigationPage tag=\"home\" title=\"Home\">\n * <Button onClick={() => navigate(\"details\")}>Open</Button>\n * </NavigationPage>\n * <NavigationPage tag=\"details\" title=\"Details\">\n * <Button onClick={pop}>Back</Button>\n * </NavigationPage>\n * </NavigationView>\n * ```\n *\n * @see https://gnome.pages.gitlab.gnome.org/libadwaita/doc/main/class.NavigationView.html\n */\nexport function NavigationView({\n initialPage,\n children,\n className,\n ...props\n}: NavigationViewProps) {\n const [stack, setStack] = useState<string[]>([initialPage]);\n\n const currentTag = stack[stack.length - 1];\n const canGoBack = stack.length > 1;\n const [direction, setDirection] = useState<\"forward\" | \"back\">(\"forward\");\n\n const navigate = (tag: string) => {\n setDirection(\"forward\");\n setStack((s) => [...s, tag]);\n };\n\n const pop = () => {\n if (stack.length <= 1) return;\n setDirection(\"back\");\n setStack((s) => s.slice(0, -1));\n };\n\n return (\n <NavigationContext.Provider value={{ navigate, pop, canGoBack, currentTag, direction }}>\n <div\n className={[styles.view, className].filter(Boolean).join(\" \")}\n {...props}\n >\n {children}\n </div>\n </NavigationContext.Provider>\n );\n}\n","/* ─── Backdrop ──────────────────────────────────────────────────────────────── */\n\n.backdrop {\n position: fixed;\n inset: 0;\n z-index: 10000;\n\n display: flex;\n align-items: flex-end;\n justify-content: center;\n\n background-color: rgba(0, 0, 0, 0.5);\n backdrop-filter: blur(4px);\n -webkit-backdrop-filter: blur(4px);\n\n animation: backdrop-in var(--gnome-duration-normal, 200ms) var(--gnome-easing-default, ease) both;\n}\n\n@keyframes backdrop-in {\n from { opacity: 0; }\n to { opacity: 1; }\n}\n\n/* ─── Sheet ─────────────────────────────────────────────────────────────────── */\n\n.sheet {\n width: min(560px, 100vw);\n max-height: 90dvh;\n overflow-y: auto;\n\n background-color: var(--gnome-dialog-bg-color, var(--gnome-window-bg-color, #fafafa));\n color: var(--gnome-window-fg-color, rgba(0, 0, 0, 0.8));\n\n border-radius: var(--gnome-radius-xl, 16px) var(--gnome-radius-xl, 16px) 0 0;\n box-shadow:\n 0 -4px 24px rgba(0, 0, 0, 0.18),\n 0 0 0 1px rgba(0, 0, 0, 0.06);\n\n animation: sheet-in var(--gnome-duration-normal, 200ms) var(--gnome-easing-default, ease) both;\n}\n\n@keyframes sheet-in {\n from { transform: translateY(100%); }\n to { transform: translateY(0); }\n}\n\n/* ─── Handle bar ────────────────────────────────────────────────────────────── */\n\n.handle {\n display: flex;\n align-items: center;\n justify-content: center;\n padding: var(--gnome-space-2, 12px) 0 var(--gnome-space-1, 6px);\n}\n\n.handleBar {\n width: 36px;\n height: 4px;\n border-radius: 2px;\n background-color: var(--gnome-window-fg-color, rgba(0, 0, 0, 0.8));\n opacity: 0.2;\n}\n\n/* ─── Title ─────────────────────────────────────────────────────────────────── */\n\n.title {\n padding: 0 var(--gnome-space-4, 24px) var(--gnome-space-2, 12px);\n\n font-family: var(--gnome-font-family);\n font-size: var(--gnome-font-size-title-4, 1.125rem);\n font-weight: var(--gnome-font-weight-bold, 700);\n line-height: var(--gnome-line-height-title, 1.3);\n color: var(--gnome-window-fg-color, rgba(0, 0, 0, 0.8));\n text-align: center;\n}\n\n/* ─── Content ───────────────────────────────────────────────────────────────── */\n\n.content {\n padding: 0 var(--gnome-space-4, 24px) var(--gnome-space-4, 24px);\n}\n\n/* ─── Dark mode ─────────────────────────────────────────────────────────────── */\n\n@media (prefers-color-scheme: dark) {\n .backdrop {\n background-color: rgba(0, 0, 0, 0.65);\n }\n\n .sheet {\n background-color: var(--gnome-dialog-bg-color, var(--gnome-window-bg-color, #242424));\n box-shadow:\n 0 -4px 24px rgba(0, 0, 0, 0.45),\n 0 0 0 1px rgba(255, 255, 255, 0.06);\n }\n}\n\n/* ─── Reduced motion ────────────────────────────────────────────────────────── */\n\n@media (prefers-reduced-motion: reduce) {\n .backdrop,\n .sheet {\n animation: none;\n }\n}\n","import {\n useEffect,\n useRef,\n useCallback,\n useId,\n type KeyboardEvent,\n type HTMLAttributes,\n type ReactNode,\n} from \"react\";\nimport { createPortal } from \"react-dom\";\nimport styles from \"./BottomSheet.module.css\";\n\nconst FOCUSABLE =\n 'button:not([disabled]), [href], input:not([disabled]), select:not([disabled]), textarea:not([disabled]), [tabindex]:not([tabindex=\"-1\"])';\n\nexport interface BottomSheetProps extends Omit<HTMLAttributes<HTMLDivElement>, \"title\"> {\n /** Whether the sheet is visible. */\n open: boolean;\n /**\n * Optional heading shown in the drag handle area.\n */\n title?: ReactNode;\n children?: ReactNode;\n /** Called when the user dismisses the sheet (Escape or backdrop click). */\n onClose?: () => void;\n /**\n * Whether clicking the backdrop closes the sheet.\n * Defaults to `true`.\n */\n closeOnBackdrop?: boolean;\n}\n\n/**\n * Slide-up panel that overlays content from the bottom edge.\n *\n * Mirrors `AdwBottomSheet` (libadwaita 1.6+). Renders into a portal on\n * `document.body`. Traps focus, closes on Escape or backdrop click.\n * The handle bar is decorative — drag-to-close is not implemented.\n *\n * @see https://gnome.pages.gitlab.gnome.org/libadwaita/doc/main/class.BottomSheet.html\n */\nexport function BottomSheet({\n open,\n title,\n children,\n onClose,\n closeOnBackdrop = true,\n className,\n ...props\n}: BottomSheetProps) {\n const sheetRef = useRef<HTMLDivElement>(null);\n const titleId = useId();\n const previouslyFocused = useRef<Element | null>(null);\n\n // Save / restore focus\n useEffect(() => {\n if (open) {\n previouslyFocused.current = document.activeElement;\n const el = sheetRef.current?.querySelector<HTMLElement>(FOCUSABLE);\n el?.focus();\n } else {\n (previouslyFocused.current as HTMLElement | null)?.focus();\n }\n }, [open]);\n\n // Focus trap + Escape\n const handleKeyDown = useCallback(\n (e: KeyboardEvent<HTMLDivElement>) => {\n if (e.key === \"Escape\") {\n e.preventDefault();\n onClose?.();\n return;\n }\n if (e.key !== \"Tab\") return;\n const focusable = Array.from(\n sheetRef.current?.querySelectorAll<HTMLElement>(FOCUSABLE) ?? []\n );\n if (focusable.length === 0) return;\n const first = focusable[0];\n const last = focusable[focusable.length - 1];\n if (e.shiftKey) {\n if (document.activeElement === first) { e.preventDefault(); last.focus(); }\n } else {\n if (document.activeElement === last) { e.preventDefault(); first.focus(); }\n }\n },\n [onClose]\n );\n\n if (!open) return null;\n\n const node = (\n <div\n className={styles.backdrop}\n onClick={closeOnBackdrop ? onClose : undefined}\n aria-hidden=\"true\"\n >\n <div\n ref={sheetRef}\n role=\"dialog\"\n aria-modal=\"true\"\n aria-labelledby={title ? titleId : undefined}\n className={[styles.sheet, className].filter(Boolean).join(\" \")}\n onKeyDown={handleKeyDown}\n onClick={(e) => e.stopPropagation()}\n {...props}\n >\n {/* Handle bar */}\n <div className={styles.handle} aria-hidden=\"true\">\n <div className={styles.handleBar} />\n </div>\n\n {/* Optional title */}\n {title && (\n <div id={titleId} className={styles.title}>{title}</div>\n )}\n\n {/* Content */}\n {children && (\n <div className={styles.content}>{children}</div>\n )}\n </div>\n </div>\n );\n\n if (typeof document === \"undefined\") return node;\n return createPortal(node, document.body);\n}\n","/* ─── Carousel container ────────────────────────────────────────────────────── */\n\n.carousel {\n display: flex;\n overflow: hidden;\n scroll-snap-type: both mandatory;\n -webkit-overflow-scrolling: touch;\n outline: none;\n width: 100%;\n}\n\n.carousel:focus-visible {\n box-shadow:\n 0 0 0 var(--gnome-focus-ring-offset, 2px) var(--gnome-window-bg-color, #fafafa),\n 0 0 0 calc(var(--gnome-focus-ring-offset, 2px) + var(--gnome-focus-ring-width, 3px))\n var(--gnome-focus-ring-color, #3584e4);\n border-radius: var(--gnome-radius-md, 8px);\n}\n\n.horizontal {\n flex-direction: row;\n overflow-x: scroll;\n overflow-y: hidden;\n scroll-snap-type: x mandatory;\n}\n\n.vertical {\n flex-direction: column;\n overflow-y: scroll;\n overflow-x: hidden;\n scroll-snap-type: y mandatory;\n}\n\n/* ─── Slide ─────────────────────────────────────────────────────────────────── */\n\n.slide {\n flex: 0 0 100%;\n scroll-snap-align: start;\n scroll-snap-stop: always;\n min-width: 0;\n min-height: 0;\n}\n\n/* ─── Dot indicators ────────────────────────────────────────────────────────── */\n\n.indicatorDots {\n display: flex;\n align-items: center;\n justify-content: center;\n gap: 6px;\n padding: var(--gnome-space-2, 12px) 0;\n}\n\n.dot {\n appearance: none;\n border: none;\n padding: 0;\n cursor: pointer;\n width: 8px;\n height: 8px;\n border-radius: 50%;\n background-color: var(--gnome-window-fg-color, rgba(0, 0, 0, 0.8));\n opacity: 0.2;\n transition:\n opacity var(--gnome-duration-fast) var(--gnome-easing-default),\n transform var(--gnome-duration-fast) var(--gnome-easing-default);\n}\n\n.dot:hover {\n opacity: 0.5;\n}\n\n.dotActive {\n opacity: 1;\n transform: scale(1.15);\n}\n\n.dot:focus-visible {\n outline: 2px solid var(--gnome-focus-ring-color, #3584e4);\n outline-offset: 2px;\n}\n\n/* ─── Line indicators ───────────────────────────────────────────────────────── */\n\n.indicatorLines {\n display: flex;\n align-items: center;\n justify-content: center;\n gap: 4px;\n padding: var(--gnome-space-2, 12px) 0;\n}\n\n.line {\n appearance: none;\n border: none;\n padding: 0;\n cursor: pointer;\n width: 24px;\n height: 3px;\n border-radius: 2px;\n background-color: var(--gnome-window-fg-color, rgba(0, 0, 0, 0.8));\n opacity: 0.2;\n transition: opacity var(--gnome-duration-fast) var(--gnome-easing-default);\n}\n\n.line:hover {\n opacity: 0.5;\n}\n\n.lineActive {\n opacity: 1;\n}\n\n.line:focus-visible {\n outline: 2px solid var(--gnome-focus-ring-color, #3584e4);\n outline-offset: 2px;\n}\n\n/* ─── Reduced motion ────────────────────────────────────────────────────────── */\n\n@media (prefers-reduced-motion: reduce) {\n .carousel {\n scroll-behavior: auto;\n }\n\n .dot,\n .line {\n transition: none;\n }\n}\n","import {\n useRef,\n useState,\n useEffect,\n useCallback,\n Children,\n type HTMLAttributes,\n type ReactNode,\n} from \"react\";\nimport styles from \"./Carousel.module.css\";\n\n// ─── CarouselIndicatorDots ────────────────────────────────────────────────────\n\nexport interface CarouselIndicatorDotsProps extends HTMLAttributes<HTMLDivElement> {\n /** Total number of pages. */\n pages: number;\n /** Zero-based index of the current page. */\n currentPage: number;\n /** Called when the user clicks a dot. */\n onPageSelected?: (index: number) => void;\n}\n\n/**\n * Dot-style page indicator for `Carousel`.\n * Mirrors `AdwCarouselIndicatorDots`.\n */\nexport function CarouselIndicatorDots({\n pages,\n currentPage,\n onPageSelected,\n className,\n ...props\n}: CarouselIndicatorDotsProps) {\n return (\n <div\n className={[styles.indicatorDots, className].filter(Boolean).join(\" \")}\n role=\"tablist\"\n aria-label=\"Carousel pages\"\n {...props}\n >\n {Array.from({ length: pages }, (_, i) => (\n <button\n key={i}\n type=\"button\"\n role=\"tab\"\n aria-selected={i === currentPage}\n aria-label={`Page ${i + 1}`}\n className={[styles.dot, i === currentPage ? styles.dotActive : null]\n .filter(Boolean)\n .join(\" \")}\n onClick={() => onPageSelected?.(i)}\n />\n ))}\n </div>\n );\n}\n\n// ─── CarouselIndicatorLines ───────────────────────────────────────────────────\n\nexport interface CarouselIndicatorLinesProps extends HTMLAttributes<HTMLDivElement> {\n /** Total number of pages. */\n pages: number;\n /** Zero-based index of the current page. */\n currentPage: number;\n /** Called when the user clicks a line. */\n onPageSelected?: (index: number) => void;\n}\n\n/**\n * Line-style page indicator for `Carousel`.\n * Mirrors `AdwCarouselIndicatorLines`.\n */\nexport function CarouselIndicatorLines({\n pages,\n currentPage,\n onPageSelected,\n className,\n ...props\n}: CarouselIndicatorLinesProps) {\n return (\n <div\n className={[styles.indicatorLines, className].filter(Boolean).join(\" \")}\n role=\"tablist\"\n aria-label=\"Carousel pages\"\n {...props}\n >\n {Array.from({ length: pages }, (_, i) => (\n <button\n key={i}\n type=\"button\"\n role=\"tab\"\n aria-selected={i === currentPage}\n aria-label={`Page ${i + 1}`}\n className={[styles.line, i === currentPage ? styles.lineActive : null]\n .filter(Boolean)\n .join(\" \")}\n onClick={() => onPageSelected?.(i)}\n />\n ))}\n </div>\n );\n}\n\n// ─── Carousel ─────────────────────────────────────────────────────────────────\n\nexport interface CarouselProps extends HTMLAttributes<HTMLDivElement> {\n children?: ReactNode;\n /**\n * Scroll orientation.\n * @default \"horizontal\"\n */\n orientation?: \"horizontal\" | \"vertical\";\n /**\n * Gap between pages in px.\n * @default 0\n */\n spacing?: number;\n /**\n * Allow infinite looping (wraps around on the last/first page).\n * @default false\n */\n loop?: boolean;\n /** Called whenever the visible page changes. */\n onPageChanged?: (index: number) => void;\n /**\n * Controlled current page index.\n * When omitted the carousel manages page state internally.\n */\n page?: number;\n}\n\n/**\n * Swipeable content carousel.\n *\n * Mirrors `AdwCarousel`. Uses CSS scroll-snapping for smooth, native-feeling\n * page transitions. Supports keyboard navigation (arrow keys) and touch/mouse\n * drag via the browser's native scroll handling.\n *\n * Pair with `CarouselIndicatorDots` or `CarouselIndicatorLines` for pagination UI.\n *\n * @see https://gnome.pages.gitlab.gnome.org/libadwaita/doc/main/class.Carousel.html\n */\nexport function Carousel({\n children,\n orientation = \"horizontal\",\n spacing = 0,\n loop = false,\n onPageChanged,\n page: controlledPage,\n className,\n ...props\n}: CarouselProps) {\n const scrollRef = useRef<HTMLDivElement>(null);\n const pageCount = Children.count(children);\n const [internalPage, setInternalPage] = useState(0);\n const isControlled = controlledPage !== undefined;\n const currentPage = isControlled ? controlledPage : internalPage;\n\n // Scroll to the controlled page when it changes externally\n useEffect(() => {\n if (!isControlled) return;\n scrollToPage(controlledPage, \"smooth\");\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [controlledPage]);\n\n const scrollToPage = useCallback(\n (index: number, behavior: ScrollBehavior = \"smooth\") => {\n const el = scrollRef.current;\n if (!el) return;\n if (orientation === \"horizontal\") {\n el.scrollTo({ left: el.clientWidth * index, behavior });\n } else {\n el.scrollTo({ top: el.clientHeight * index, behavior });\n }\n },\n [orientation]\n );\n\n // Detect page changes via IntersectionObserver on each child\n useEffect(() => {\n const el = scrollRef.current;\n if (!el) return;\n\n const handleScroll = () => {\n const pages = Array.from(el.children) as HTMLElement[];\n const size = orientation === \"horizontal\" ? el.clientWidth : el.clientHeight;\n const scroll = orientation === \"horizontal\" ? el.scrollLeft : el.scrollTop;\n const idx = Math.round(scroll / (size || 1));\n const clamped = Math.max(0, Math.min(idx, pageCount - 1));\n if (!isControlled) setInternalPage(clamped);\n onPageChanged?.(clamped);\n void pages; // suppress lint\n };\n\n el.addEventListener(\"scroll\", handleScroll, { passive: true });\n return () => el.removeEventListener(\"scroll\", handleScroll);\n }, [orientation, pageCount, isControlled, onPageChanged]);\n\n // Keyboard navigation\n const handleKeyDown = useCallback(\n (e: React.KeyboardEvent) => {\n const isForward =\n orientation === \"horizontal\" ? e.key === \"ArrowRight\" : e.key === \"ArrowDown\";\n const isBack =\n orientation === \"horizontal\" ? e.key === \"ArrowLeft\" : e.key === \"ArrowUp\";\n\n if (isForward) {\n e.preventDefault();\n const next = loop\n ? (currentPage + 1) % pageCount\n : Math.min(currentPage + 1, pageCount - 1);\n scrollToPage(next);\n if (!isControlled) setInternalPage(next);\n onPageChanged?.(next);\n } else if (isBack) {\n e.preventDefault();\n const prev = loop\n ? (currentPage - 1 + pageCount) % pageCount\n : Math.max(currentPage - 1, 0);\n scrollToPage(prev);\n if (!isControlled) setInternalPage(prev);\n onPageChanged?.(prev);\n }\n },\n [currentPage, pageCount, loop, orientation, scrollToPage, isControlled, onPageChanged]\n );\n\n const isHorizontal = orientation === \"horizontal\";\n\n return (\n <div\n ref={scrollRef}\n role=\"region\"\n aria-roledescription=\"carousel\"\n tabIndex={0}\n className={[\n styles.carousel,\n isHorizontal ? styles.horizontal : styles.vertical,\n className,\n ]\n .filter(Boolean)\n .join(\" \")}\n style={\n isHorizontal\n ? { columnGap: spacing || undefined }\n : { rowGap: spacing || undefined }\n }\n onKeyDown={handleKeyDown}\n {...props}\n >\n {Children.map(children, (child, i) => (\n <div\n key={i}\n className={styles.slide}\n role=\"group\"\n aria-roledescription=\"slide\"\n aria-label={`${i + 1} of ${pageCount}`}\n >\n {child}\n </div>\n ))}\n </div>\n );\n}\n","import type { HTMLAttributes, ReactNode } from \"react\";\n\nexport interface BinProps extends HTMLAttributes<HTMLDivElement> {\n /** Single child. */\n children?: ReactNode;\n}\n\n/**\n * Single-child container with no visual styling.\n *\n * A transparent wrapper that constrains its content to one child while\n * forwarding all HTML div attributes. Useful as a base for custom components\n * that need a neutral container — e.g. to apply layout or size constraints\n * without introducing visual chrome.\n *\n * Mirrors `AdwBin`.\n *\n * @see https://gnome.pages.gitlab.gnome.org/libadwaita/doc/main/class.Bin.html\n */\nexport function Bin({ children, ...props }: BinProps) {\n return <div {...props}>{children}</div>;\n}\n","/* ─── ButtonContent ──────────────────────────────────────────────────────────── */\n\n.content {\n display: inline-flex;\n align-items: center;\n gap: 6px;\n}\n\n.icon {\n display: inline-flex;\n align-items: center;\n flex-shrink: 0;\n}\n\n.label {\n /* Inherit all button text styles */\n}\n","import type { HTMLAttributes, ReactNode } from \"react\";\nimport styles from \"./ButtonContent.module.css\";\n\nexport interface ButtonContentProps extends HTMLAttributes<HTMLSpanElement> {\n /** Icon element placed before the label. */\n icon?: ReactNode;\n /** Text label. */\n label: string;\n /**\n * Position of the icon relative to the label.\n * Defaults to `\"start\"`.\n */\n iconPosition?: \"start\" | \"end\";\n}\n\n/**\n * Icon + label layout helper for buttons that contain both an icon and text.\n *\n * Pass as the `children` of a `Button` to get the correct Adwaita spacing\n * between the icon and label (4 px gap, icon centred vertically with the text).\n *\n * Mirrors `AdwButtonContent`.\n *\n * @example\n * ```tsx\n * <Button>\n * <ButtonContent icon={<Icon name=\"document-save-symbolic\" />} label=\"Save\" />\n * </Button>\n * ```\n *\n * @see https://gnome.pages.gitlab.gnome.org/libadwaita/doc/main/class.ButtonContent.html\n */\nexport function ButtonContent({\n icon,\n label,\n iconPosition = \"start\",\n className,\n ...props\n}: ButtonContentProps) {\n return (\n <span\n className={[\n styles.content,\n iconPosition === \"end\" ? styles.iconEnd : null,\n className,\n ].filter(Boolean).join(\" \")}\n {...props}\n >\n {icon && iconPosition === \"start\" && (\n <span className={styles.icon} aria-hidden=\"true\">{icon}</span>\n )}\n <span className={styles.label}>{label}</span>\n {icon && iconPosition === \"end\" && (\n <span className={styles.icon} aria-hidden=\"true\">{icon}</span>\n )}\n </span>\n );\n}\n","/* ─── ShortcutLabel ──────────────────────────────────────────────────────────── */\n\n.label {\n display: inline-flex;\n align-items: center;\n gap: 2px;\n}\n\n.key {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n\n min-width: 22px;\n height: 22px;\n padding: 0 5px;\n\n font-family: var(--gnome-font-family);\n font-size: var(--gnome-font-size-caption, 0.75rem);\n font-weight: var(--gnome-font-weight-semibold, 600);\n font-style: normal;\n line-height: 1;\n color: var(--gnome-window-fg-color, rgba(0, 0, 0, 0.8));\n\n background-color: var(--gnome-card-bg-color, rgba(0, 0, 0, 0.06));\n border: 1px solid var(--gnome-card-shade-color, rgba(0, 0, 0, 0.15));\n border-radius: var(--gnome-radius-sm, 4px);\n box-shadow: 0 1px 0 var(--gnome-card-shade-color, rgba(0, 0, 0, 0.12));\n}\n\n/* ─── Dark mode ──────────────────────────────────────────────────────────────── */\n\n@media (prefers-color-scheme: dark) {\n .key {\n background-color: rgba(255, 255, 255, 0.08);\n border-color: rgba(255, 255, 255, 0.15);\n box-shadow: 0 1px 0 rgba(255, 255, 255, 0.06);\n }\n}\n\n/* ─── High contrast ──────────────────────────────────────────────────────────── */\n\n@media (prefers-contrast: more) {\n .key {\n border-width: 2px;\n box-shadow: none;\n }\n}\n","import type { HTMLAttributes } from \"react\";\nimport styles from \"./ShortcutLabel.module.css\";\n\nexport interface ShortcutLabelProps extends HTMLAttributes<HTMLSpanElement> {\n /**\n * Keyboard shortcut string.\n *\n * Tokens are separated by `+`. Each token is rendered as its own key cap.\n * Common modifiers are normalised to their symbol:\n * - `Ctrl` / `Control` → `⌃`\n * - `Shift` → `⇧`\n * - `Alt` / `Option` → `⌥`\n * - `Super` / `Win` / `Cmd` / `Command` → `⊞`\n * - `Up` → `↑` `Down` → `↓` `Left` → `←` `Right` → `→`\n * - `Enter` / `Return` → `↵`\n * - `Backspace` → `⌫`\n * - `Delete` → `⌦`\n * - `Escape` / `Esc` → `⎋`\n * - `Tab` → `⇥`\n * - `Space` → `␣`\n *\n * @example \"Ctrl+S\" → ⌃ S\n * @example \"Ctrl+Shift+Z\" → ⌃ ⇧ Z\n */\n shortcut: string;\n /** When true, modifier tokens are shown as symbols. Defaults to `true`. */\n symbols?: boolean;\n}\n\nconst SYMBOL_MAP: Record<string, string> = {\n ctrl: \"⌃\",\n control: \"⌃\",\n shift: \"⇧\",\n alt: \"⌥\",\n option: \"⌥\",\n super: \"⊞\",\n win: \"⊞\",\n cmd: \"⌘\",\n command: \"⌘\",\n meta: \"⌘\",\n up: \"↑\",\n down: \"↓\",\n left: \"←\",\n right: \"→\",\n enter: \"↵\",\n return: \"↵\",\n backspace: \"⌫\",\n delete: \"⌦\",\n escape: \"⎋\",\n esc: \"⎋\",\n tab: \"⇥\",\n space: \"␣\",\n pageup: \"⇞\",\n pagedown: \"⇟\",\n home: \"⇱\",\n end: \"⇲\",\n};\n\n/**\n * Read-only display of a keyboard shortcut with per-key key-cap styling.\n *\n * Each token in the `shortcut` string (split by `+`) is rendered as a\n * separate `<kbd>` element. Modifier keys are normalised to their Unicode\n * symbols by default.\n *\n * Mirrors `GtkShortcutLabel`.\n *\n * @see https://docs.gtk.org/gtk4/class.ShortcutLabel.html\n */\nexport function ShortcutLabel({ shortcut, symbols = true, className, ...props }: ShortcutLabelProps) {\n const tokens = shortcut.split(\"+\").map((t) => t.trim()).filter(Boolean);\n\n return (\n <span\n className={[styles.label, className].filter(Boolean).join(\" \")}\n aria-label={shortcut}\n {...props}\n >\n {tokens.map((token, i) => {\n const display = symbols\n ? (SYMBOL_MAP[token.toLowerCase()] ?? token)\n : token;\n\n return (\n <kbd key={i} className={styles.key}>\n {display}\n </kbd>\n );\n })}\n </span>\n );\n}\n","/* ─── ToolbarView ────────────────────────────────────────────────────────────── */\n\n.toolbarView {\n display: flex;\n flex-direction: column;\n height: 100%;\n overflow: hidden;\n}\n\n/* Top bar — fixed, no scroll */\n.top {\n flex-shrink: 0;\n}\n\n/* Scrollable content */\n.content {\n flex: 1;\n overflow-y: auto;\n min-height: 0;\n}\n\n/* Bottom bar — fixed, no scroll */\n.bottom {\n flex-shrink: 0;\n}\n","import type { HTMLAttributes, ReactNode } from \"react\";\nimport styles from \"./ToolbarView.module.css\";\n\nexport interface ToolbarViewProps extends HTMLAttributes<HTMLDivElement> {\n /**\n * Bar(s) pinned to the top — typically a `HeaderBar` or `Toolbar`.\n * Multiple bars stack vertically.\n */\n topBar?: ReactNode;\n /**\n * Bar(s) pinned to the bottom — typically a `Toolbar` or `ViewSwitcherBar`.\n * Multiple bars stack vertically.\n */\n bottomBar?: ReactNode;\n /** Scrollable content area between the bars. */\n children?: ReactNode;\n}\n\n/**\n * Layout container that attaches bars at the top and/or bottom while\n * scrolling only the middle content.\n *\n * The top/bottom bars remain fixed; the inner content area gets\n * `overflow-y: auto` so it scrolls independently.\n *\n * Mirrors `AdwToolbarView`.\n *\n * @see https://gnome.pages.gitlab.gnome.org/libadwaita/doc/main/class.ToolbarView.html\n */\nexport function ToolbarView({\n topBar,\n bottomBar,\n children,\n className,\n ...props\n}: ToolbarViewProps) {\n return (\n <div\n className={[styles.toolbarView, className].filter(Boolean).join(\" \")}\n {...props}\n >\n {topBar && <div className={styles.top}>{topBar}</div>}\n <div className={styles.content}>{children}</div>\n {bottomBar && <div className={styles.bottom}>{bottomBar}</div>}\n </div>\n );\n}\n","/* ─── WindowTitle ────────────────────────────────────────────────────────────── */\n\n.windowTitle {\n display: flex;\n flex-direction: column;\n align-items: center;\n gap: 1px;\n min-width: 0;\n}\n\n.title {\n font-family: var(--gnome-font-family);\n font-size: var(--gnome-font-size-body, 1rem);\n font-weight: var(--gnome-font-weight-semibold, 600);\n line-height: 1.2;\n color: var(--gnome-headerbar-fg-color, var(--gnome-window-fg-color, rgba(0, 0, 0, 0.8)));\n\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n max-width: 100%;\n}\n\n.subtitle {\n font-family: var(--gnome-font-family);\n font-size: var(--gnome-font-size-caption, 0.75rem);\n font-weight: var(--gnome-font-weight-normal, 400);\n line-height: 1.2;\n color: var(--gnome-headerbar-fg-color, var(--gnome-window-fg-color, rgba(0, 0, 0, 0.8)));\n opacity: var(--gnome-opacity-dim, 0.55);\n\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n max-width: 100%;\n}\n","import type { HTMLAttributes } from \"react\";\nimport styles from \"./WindowTitle.module.css\";\n\nexport interface WindowTitleProps extends HTMLAttributes<HTMLDivElement> {\n /** Primary title — rendered in bold. */\n title: string;\n /** Secondary subtitle — rendered smaller and dimmed below the title. */\n subtitle?: string;\n}\n\n/**\n * Two-line title + subtitle widget for use inside a `HeaderBar`.\n *\n * Pass as the `title` prop of `HeaderBar` to get a centred two-line header\n * that shows an app name and current document/view name.\n *\n * Mirrors `AdwWindowTitle`.\n *\n * @example\n * ```tsx\n * <HeaderBar title={<WindowTitle title=\"Files\" subtitle=\"/home/user/Documents\" />} />\n * ```\n *\n * @see https://gnome.pages.gitlab.gnome.org/libadwaita/doc/main/class.WindowTitle.html\n */\nexport function WindowTitle({ title, subtitle, className, ...props }: WindowTitleProps) {\n return (\n <div\n className={[styles.windowTitle, className].filter(Boolean).join(\" \")}\n {...props}\n >\n <span className={styles.title}>{title}</span>\n {subtitle && <span className={styles.subtitle}>{subtitle}</span>}\n </div>\n );\n}\n","/* ─── PreferencesGroup ───────────────────────────────────────────────────────── */\n\n.group {\n display: flex;\n flex-direction: column;\n gap: var(--gnome-space-2, 12px);\n}\n\n/* ─── Header ────────────────────────────────────────────────────────────────── */\n\n.header {\n display: flex;\n align-items: baseline;\n gap: var(--gnome-space-2, 12px);\n}\n\n.headerText {\n flex: 1;\n min-width: 0;\n display: flex;\n flex-direction: column;\n gap: 2px;\n}\n\n.title {\n font-family: var(--gnome-font-family);\n font-size: var(--gnome-font-size-body, 1rem);\n font-weight: var(--gnome-font-weight-semibold, 600);\n line-height: var(--gnome-line-height-body, 1.5);\n color: var(--gnome-window-fg-color, rgba(0, 0, 0, 0.8));\n}\n\n.description {\n font-family: var(--gnome-font-family);\n font-size: var(--gnome-font-size-caption, 0.75rem);\n font-weight: var(--gnome-font-weight-normal, 400);\n line-height: var(--gnome-line-height-body, 1.5);\n color: var(--gnome-window-fg-color, rgba(0, 0, 0, 0.8));\n opacity: var(--gnome-opacity-dim, 0.55);\n}\n\n.suffix {\n flex-shrink: 0;\n}\n\n/* ─── Content ───────────────────────────────────────────────────────────────── */\n\n.content {\n /* Rows/BoxedList rendered here */\n}\n","import { type HTMLAttributes, type ReactNode } from \"react\";\nimport styles from \"./PreferencesGroup.module.css\";\n\nexport interface PreferencesGroupProps extends HTMLAttributes<HTMLDivElement> {\n /** Group heading. */\n title?: string;\n /** Optional description rendered below the title. */\n description?: string;\n /** Widget placed at the trailing edge of the title row (e.g. a reset Button). */\n headerSuffix?: ReactNode;\n /** `BoxedList` rows or any row-shaped content. */\n children?: ReactNode;\n}\n\n/**\n * Titled section that wraps a `BoxedList` with an optional description.\n *\n * Use inside a `PreferencesPage` to group related settings under a named\n * heading. The group is purely a layout and labelling wrapper — it does not\n * render the `BoxedList` itself; pass it as `children`.\n *\n * Mirrors `AdwPreferencesGroup`.\n *\n * @see https://gnome.pages.gitlab.gnome.org/libadwaita/doc/main/class.PreferencesGroup.html\n */\nexport function PreferencesGroup({\n title,\n description,\n headerSuffix,\n children,\n className,\n ...props\n}: PreferencesGroupProps) {\n const hasHeader = title || description || headerSuffix;\n\n return (\n <div\n className={[styles.group, className].filter(Boolean).join(\" \")}\n {...props}\n >\n {hasHeader && (\n <div className={styles.header}>\n <div className={styles.headerText}>\n {title && <span className={styles.title}>{title}</span>}\n {description && <span className={styles.description}>{description}</span>}\n </div>\n {headerSuffix && <div className={styles.suffix}>{headerSuffix}</div>}\n </div>\n )}\n <div className={styles.content}>{children}</div>\n </div>\n );\n}\n","/* ─── PreferencesPage ────────────────────────────────────────────────────────── */\n\n.page {\n flex: 1;\n overflow-y: auto;\n min-height: 0;\n}\n\n/* Centred column, mirrors AdwClamp at ~468 px */\n.inner {\n display: flex;\n flex-direction: column;\n gap: var(--gnome-space-4, 24px);\n max-width: 468px;\n margin: 0 auto;\n padding: var(--gnome-space-4, 24px) var(--gnome-space-3, 16px);\n}\n","import { type HTMLAttributes, type ReactNode } from \"react\";\nimport styles from \"./PreferencesPage.module.css\";\n\nexport interface PreferencesPageProps extends HTMLAttributes<HTMLDivElement> {\n /**\n * Page title shown in the sidebar / tab of a `PreferencesDialog`.\n * Required — used as the navigation label.\n */\n title: string;\n /**\n * Name of a symbolic icon displayed next to the page title in the sidebar.\n * Pass a valid icon name from the system icon theme (e.g. `\"preferences-system-symbolic\"`).\n */\n iconName?: string;\n /** `PreferencesGroup` sections. */\n children?: ReactNode;\n}\n\n/**\n * Scrollable page composed of `PreferencesGroup` sections.\n *\n * Use as a child of `PreferencesDialog`. Each page appears as a labelled\n * navigation entry in the dialog sidebar.\n *\n * Mirrors `AdwPreferencesPage`.\n *\n * @see https://gnome.pages.gitlab.gnome.org/libadwaita/doc/main/class.PreferencesPage.html\n */\nexport function PreferencesPage({\n title: _title,\n iconName: _iconName,\n children,\n className,\n ...props\n}: PreferencesPageProps) {\n return (\n <div\n className={[styles.page, className].filter(Boolean).join(\" \")}\n role=\"tabpanel\"\n {...props}\n >\n <div className={styles.inner}>{children}</div>\n </div>\n );\n}\n","/* ─── Backdrop ──────────────────────────────────────────────────────────────── */\n\n.backdrop {\n position: fixed;\n inset: 0;\n z-index: 10000;\n\n display: flex;\n align-items: center;\n justify-content: center;\n\n background-color: rgba(0, 0, 0, 0.5);\n backdrop-filter: blur(4px);\n -webkit-backdrop-filter: blur(4px);\n\n animation: backdrop-in var(--gnome-duration-normal, 200ms) var(--gnome-easing-default, ease) both;\n}\n\n@keyframes backdrop-in {\n from { opacity: 0; }\n to { opacity: 1; }\n}\n\n/* ─── Dialog card ───────────────────────────────────────────────────────────── */\n\n.dialog {\n display: flex;\n flex-direction: column;\n width: min(540px, calc(100vw - 48px));\n max-height: min(640px, 90dvh);\n\n background-color: var(--gnome-dialog-bg-color, var(--gnome-window-bg-color, #fafafa));\n color: var(--gnome-window-fg-color, rgba(0, 0, 0, 0.8));\n\n border-radius: var(--gnome-radius-xl, 16px);\n box-shadow:\n 0 8px 32px rgba(0, 0, 0, 0.25),\n 0 0 0 1px rgba(0, 0, 0, 0.08);\n\n overflow: hidden;\n\n animation: dialog-in var(--gnome-duration-normal, 200ms) var(--gnome-easing-default, ease) both;\n}\n\n/* Wider layout when sidebar is present */\n.dialogMulti {\n width: min(720px, calc(100vw - 48px));\n}\n\n@keyframes dialog-in {\n from {\n opacity: 0;\n transform: scale(0.94) translateY(-8px);\n }\n to {\n opacity: 1;\n transform: scale(1) translateY(0);\n }\n}\n\n/* ─── Header ────────────────────────────────────────────────────────────────── */\n\n.header {\n display: grid;\n grid-template-columns: auto 1fr auto;\n align-items: center;\n gap: var(--gnome-space-2, 12px);\n padding: var(--gnome-space-2, 12px) var(--gnome-space-2, 12px) var(--gnome-space-2, 12px) var(--gnome-space-3, 16px);\n border-bottom: 1px solid var(--gnome-card-shade-color, rgba(0, 0, 0, 0.1));\n flex-shrink: 0;\n}\n\n.closeBtn {\n appearance: none;\n border: none;\n background: transparent;\n cursor: pointer;\n width: 32px;\n height: 32px;\n border-radius: 50%;\n display: flex;\n align-items: center;\n justify-content: center;\n font-size: 0.875rem;\n color: var(--gnome-window-fg-color, rgba(0, 0, 0, 0.8));\n opacity: 0.6;\n transition: background-color var(--gnome-duration-fast, 100ms) var(--gnome-easing-default, ease),\n opacity var(--gnome-duration-fast, 100ms) var(--gnome-easing-default, ease);\n order: -1;\n}\n\n.closeBtn:hover {\n background-color: color-mix(in srgb, currentColor 10%, transparent);\n opacity: 1;\n}\n\n.closeBtn:focus-visible {\n outline: var(--gnome-focus-ring-width, 3px) solid var(--gnome-focus-ring-color, #3584e4);\n outline-offset: 2px;\n}\n\n.title {\n font-family: var(--gnome-font-family);\n font-size: var(--gnome-font-size-body, 1rem);\n font-weight: var(--gnome-font-weight-semibold, 600);\n line-height: 1;\n text-align: center;\n}\n\n/* ─── Search ────────────────────────────────────────────────────────────────── */\n\n.searchWrap {\n grid-column: 3;\n}\n\n.searchInput {\n appearance: none;\n border: 1px solid var(--gnome-card-shade-color, rgba(0, 0, 0, 0.15));\n border-radius: var(--gnome-radius-md, 8px);\n background-color: var(--gnome-card-bg-color, rgba(0, 0, 0, 0.04));\n color: var(--gnome-window-fg-color, rgba(0, 0, 0, 0.8));\n padding: 5px var(--gnome-space-2, 12px);\n font-family: var(--gnome-font-family);\n font-size: var(--gnome-font-size-body, 1rem);\n line-height: 1.5;\n width: 180px;\n transition: border-color var(--gnome-duration-fast, 100ms) var(--gnome-easing-default, ease),\n box-shadow var(--gnome-duration-fast, 100ms) var(--gnome-easing-default, ease);\n}\n\n.searchInput::placeholder {\n opacity: 0.5;\n}\n\n.searchInput:focus {\n outline: none;\n border-color: var(--gnome-accent-color, #3584e4);\n box-shadow: 0 0 0 var(--gnome-focus-ring-width, 3px) color-mix(in srgb, var(--gnome-accent-color, #3584e4) 30%, transparent);\n}\n\n/* ─── Body ──────────────────────────────────────────────────────────────────── */\n\n.body {\n display: flex;\n flex: 1;\n min-height: 0;\n overflow: hidden;\n}\n\n/* ─── Sidebar ───────────────────────────────────────────────────────────────── */\n\n.sidebar {\n width: 200px;\n flex-shrink: 0;\n border-right: 1px solid var(--gnome-card-shade-color, rgba(0, 0, 0, 0.1));\n overflow-y: auto;\n padding: var(--gnome-space-2, 12px) var(--gnome-space-2, 12px);\n}\n\n.navList {\n list-style: none;\n margin: 0;\n padding: 0;\n display: flex;\n flex-direction: column;\n gap: 2px;\n}\n\n.navItem {\n appearance: none;\n border: none;\n background: transparent;\n cursor: pointer;\n width: 100%;\n padding: 8px var(--gnome-space-2, 12px);\n border-radius: var(--gnome-radius-md, 8px);\n display: flex;\n align-items: center;\n gap: var(--gnome-space-2, 12px);\n text-align: left;\n\n font-family: var(--gnome-font-family);\n font-size: var(--gnome-font-size-body, 1rem);\n font-weight: var(--gnome-font-weight-normal, 400);\n color: var(--gnome-window-fg-color, rgba(0, 0, 0, 0.8));\n\n transition: background-color var(--gnome-duration-fast, 100ms) var(--gnome-easing-default, ease);\n}\n\n.navItem:hover:not(.navItemActive) {\n background-color: color-mix(in srgb, currentColor 6%, transparent);\n}\n\n.navItemActive {\n background-color: color-mix(in srgb, var(--gnome-accent-color, #3584e4) 12%, transparent);\n color: var(--gnome-accent-color, #3584e4);\n font-weight: var(--gnome-font-weight-semibold, 600);\n}\n\n.navItem:focus-visible {\n outline: var(--gnome-focus-ring-width, 3px) solid var(--gnome-focus-ring-color, #3584e4);\n outline-offset: 2px;\n}\n\n.navIcon {\n width: 16px;\n height: 16px;\n flex-shrink: 0;\n}\n\n.navLabel {\n flex: 1;\n min-width: 0;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n/* ─── Page area ─────────────────────────────────────────────────────────────── */\n\n.pageWrap {\n flex: 1;\n min-width: 0;\n min-height: 0;\n display: flex;\n flex-direction: column;\n}\n\n/* ─── Dark mode ─────────────────────────────────────────────────────────────── */\n\n@media (prefers-color-scheme: dark) {\n .backdrop {\n background-color: rgba(0, 0, 0, 0.65);\n }\n\n .dialog {\n background-color: var(--gnome-dialog-bg-color, var(--gnome-window-bg-color, #242424));\n box-shadow:\n 0 8px 32px rgba(0, 0, 0, 0.5),\n 0 0 0 1px rgba(255, 255, 255, 0.08);\n }\n\n .header {\n border-bottom-color: rgba(255, 255, 255, 0.08);\n }\n\n .sidebar {\n border-right-color: rgba(255, 255, 255, 0.08);\n }\n\n .searchInput {\n background-color: rgba(255, 255, 255, 0.06);\n border-color: rgba(255, 255, 255, 0.15);\n }\n}\n\n/* ─── Reduced motion ────────────────────────────────────────────────────────── */\n\n@media (prefers-reduced-motion: reduce) {\n .backdrop,\n .dialog {\n animation: none;\n }\n\n .closeBtn,\n .navItem,\n .searchInput {\n transition: none;\n }\n}\n\n/* ─── High contrast ─────────────────────────────────────────────────────────── */\n\n@media (prefers-contrast: more) {\n .dialog {\n box-shadow: none;\n border: 2px solid var(--gnome-window-fg-color, rgba(0, 0, 0, 0.8));\n }\n\n .header {\n border-bottom-width: 2px;\n }\n\n .sidebar {\n border-right-width: 2px;\n }\n\n .navItemActive {\n outline: 2px solid var(--gnome-accent-color, #3584e4);\n }\n}\n","import {\n Children,\n cloneElement,\n isValidElement,\n useCallback,\n useEffect,\n useId,\n useRef,\n useState,\n type HTMLAttributes,\n type KeyboardEvent,\n type ReactElement,\n type ReactNode,\n} from \"react\";\nimport { createPortal } from \"react-dom\";\nimport styles from \"./PreferencesDialog.module.css\";\nimport type { PreferencesPageProps } from \"../PreferencesPage/PreferencesPage\";\n\n// ─── Focus trap ───────────────────────────────────────────────────────────────\n\nconst FOCUSABLE =\n 'button:not([disabled]), [href], input:not([disabled]), select:not([disabled]), textarea:not([disabled]), [tabindex]:not([tabindex=\"-1\"])';\n\nfunction trapFocus(\n e: KeyboardEvent<HTMLDivElement>,\n ref: React.RefObject<HTMLDivElement | null>\n) {\n if (e.key !== \"Tab\") return;\n const focusable = Array.from(\n ref.current?.querySelectorAll<HTMLElement>(FOCUSABLE) ?? []\n );\n if (!focusable.length) return;\n const first = focusable[0];\n const last = focusable[focusable.length - 1];\n if (e.shiftKey) {\n if (document.activeElement === first) {\n e.preventDefault();\n last.focus();\n }\n } else {\n if (document.activeElement === last) {\n e.preventDefault();\n first.focus();\n }\n }\n}\n\n// ─── Types ────────────────────────────────────────────────────────────────────\n\nexport interface PreferencesDialogProps\n extends Omit<HTMLAttributes<HTMLDivElement>, \"title\"> {\n /** Whether the dialog is open. */\n open: boolean;\n /** Called when the user requests to close the dialog. */\n onClose: () => void;\n /**\n * `PreferencesPage` children. Each page appears as a sidebar entry.\n * When only one page is provided the sidebar is hidden.\n */\n children?: ReactNode;\n /**\n * Whether to show a search bar in the header.\n * When `true` the user can filter group/row content by typing.\n * Defaults to `true`.\n */\n searchable?: boolean;\n}\n\n// ─── Component ────────────────────────────────────────────────────────────────\n\n/**\n * Multi-page settings dialog using `PreferencesPage` tabs.\n *\n * Renders a wide modal with an optional sidebar listing pages. Each\n * `PreferencesPage` child becomes a navigation entry. Supports built-in\n * search when `searchable` is `true` (the default).\n *\n * Mirrors `AdwPreferencesDialog`.\n *\n * @example\n * ```tsx\n * <PreferencesDialog open={open} onClose={() => setOpen(false)}>\n * <PreferencesPage title=\"General\" iconName=\"preferences-system-symbolic\">\n * <PreferencesGroup title=\"Appearance\">\n * <BoxedList>\n * <SwitchRow title=\"Dark mode\" />\n * </BoxedList>\n * </PreferencesGroup>\n * </PreferencesPage>\n * </PreferencesDialog>\n * ```\n *\n * @see https://gnome.pages.gitlab.gnome.org/libadwaita/doc/main/class.PreferencesDialog.html\n */\nexport function PreferencesDialog({\n open,\n onClose,\n children,\n searchable = true,\n className,\n ...props\n}: PreferencesDialogProps) {\n const dialogRef = useRef<HTMLDivElement>(null);\n const searchRef = useRef<HTMLInputElement>(null);\n const titleId = useId();\n\n const pages = Children.toArray(children).filter(\n (c): c is ReactElement<PreferencesPageProps> =>\n isValidElement(c)\n ) as ReactElement<PreferencesPageProps>[];\n\n const [activeIndex, setActiveIndex] = useState(0);\n const [searchQuery, setSearchQuery] = useState(\"\");\n\n // Reset state when dialog opens\n useEffect(() => {\n if (open) {\n setActiveIndex(0);\n setSearchQuery(\"\");\n // Focus search or first focusable after open animation\n const id = requestAnimationFrame(() => {\n if (searchable && searchRef.current) {\n searchRef.current.focus();\n } else {\n const first = dialogRef.current?.querySelector<HTMLElement>(FOCUSABLE);\n first?.focus();\n }\n });\n return () => cancelAnimationFrame(id);\n }\n }, [open, searchable]);\n\n // Close on Escape\n const handleKeyDown = useCallback(\n (e: KeyboardEvent<HTMLDivElement>) => {\n if (e.key === \"Escape\") {\n e.stopPropagation();\n onClose();\n return;\n }\n trapFocus(e, dialogRef);\n },\n [onClose]\n );\n\n const multiPage = pages.length > 1;\n const activePage = pages[activeIndex] ?? pages[0];\n\n // Filter content by search query\n const query = searchQuery.trim().toLowerCase();\n\n if (!open) return null;\n\n return createPortal(\n <div\n className={styles.backdrop}\n onClick={(e) => {\n if (e.target === e.currentTarget) onClose();\n }}\n role=\"presentation\"\n >\n <div\n ref={dialogRef}\n role=\"dialog\"\n aria-modal=\"true\"\n aria-labelledby={titleId}\n className={[\n styles.dialog,\n multiPage ? styles.dialogMulti : null,\n className,\n ]\n .filter(Boolean)\n .join(\" \")}\n onKeyDown={handleKeyDown}\n {...props}\n >\n {/* ── Header ─────────────────────────────────────────────────────── */}\n <div className={styles.header}>\n <button\n type=\"button\"\n className={styles.closeBtn}\n onClick={onClose}\n aria-label=\"Close preferences\"\n >\n ✕\n </button>\n <span id={titleId} className={styles.title}>\n Preferences\n </span>\n {searchable && (\n <div className={styles.searchWrap}>\n <input\n ref={searchRef}\n type=\"search\"\n placeholder=\"Search…\"\n value={searchQuery}\n onChange={(e) => setSearchQuery(e.target.value)}\n className={styles.searchInput}\n aria-label=\"Search preferences\"\n />\n </div>\n )}\n </div>\n\n {/* ── Body ───────────────────────────────────────────────────────── */}\n <div className={styles.body}>\n {/* Sidebar — only shown when there are multiple pages */}\n {multiPage && (\n <nav className={styles.sidebar} aria-label=\"Preferences pages\">\n <ul role=\"tablist\" className={styles.navList}>\n {pages.map((page, i) => (\n <li key={i} role=\"presentation\">\n <button\n role=\"tab\"\n type=\"button\"\n aria-selected={i === activeIndex}\n className={[\n styles.navItem,\n i === activeIndex ? styles.navItemActive : null,\n ]\n .filter(Boolean)\n .join(\" \")}\n onClick={() => setActiveIndex(i)}\n >\n {page.props.iconName && (\n <span\n className={[\n styles.navIcon,\n \"icon\",\n page.props.iconName,\n ].join(\" \")}\n aria-hidden=\"true\"\n />\n )}\n <span className={styles.navLabel}>\n {page.props.title}\n </span>\n </button>\n </li>\n ))}\n </ul>\n </nav>\n )}\n\n {/* Page content */}\n <div className={styles.pageWrap}>\n {activePage &&\n cloneElement(activePage, {\n \"data-search-query\": query || undefined,\n } as Partial<PreferencesPageProps>)}\n </div>\n </div>\n </div>\n </div>,\n document.body\n );\n}\n"],"mappings":"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"}
|