@gnome-ui/react 1.33.0 → 1.35.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.
@@ -1 +1 @@
1
- {"version":3,"file":"index.cjs","names":[],"sources":["../src/components/Blockquote/Blockquote.module.css","../src/components/Blockquote/Blockquote.tsx","../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/CountDownTimer/CountDownTimer.module.css","../src/components/CountDownTimer/CountDownTimer.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/dialogUtils.ts","../src/components/Dialog/Dialog.module.css","../src/components/Dialog/Dialog.tsx","../src/components/AboutDialog/AboutDialog.module.css","../src/components/AboutDialog/AboutDialog.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/Box/Box.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","../src/components/Timeline/Timeline.module.css","../src/components/Timeline/Timeline.tsx","../src/components/PathBar/PathBar.module.css","../src/components/PathBar/PathBar.tsx","../src/components/ContributionGraph/ContributionGraph.module.css","../src/components/ContributionGraph/ContributionGraph.tsx","../src/components/ColumnView/ColumnView.module.css","../src/components/ColumnView/ColumnView.tsx","../src/components/TerminalView/TerminalView.module.css","../src/components/TerminalView/TerminalView.tsx"],"sourcesContent":["/* ─── Base ────────────────────────────────────────────────────────────────── */\n\n.blockquote {\n margin: 0;\n padding: var(--gnome-space-2, 12px) var(--gnome-space-3, 18px);\n border-left: var(--gnome-blockquote-border-width, 3px) solid;\n border-radius: 0 var(--gnome-radius-sm, 4px) var(--gnome-radius-sm, 4px) 0;\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/* ─── Body (icon + text) ──────────────────────────────────────────────────── */\n\n.body {\n display: flex;\n align-items: flex-start;\n gap: var(--gnome-space-2, 12px);\n}\n\n.icon {\n display: flex;\n align-items: center;\n flex-shrink: 0;\n margin-top: 2px; /* optical alignment with first text baseline */\n}\n\n.content {\n margin: 0;\n flex: 1;\n min-width: 0;\n}\n\n/* ─── Attribution ─────────────────────────────────────────────────────────── */\n\n.footer {\n margin-top: var(--gnome-space-1, 6px);\n font-size: var(--gnome-font-size-caption, 0.75rem);\n opacity: var(--gnome-opacity-dim, 0.55);\n}\n\n.cite {\n font-style: normal;\n}\n\n/* ─── Variants ────────────────────────────────────────────────────────────── */\n\n.default {\n border-color: var(--gnome-border-subtle, rgba(0, 0, 0, 0.15));\n}\n\n.info {\n border-color: var(--gnome-accent-bg-color, #3584e4);\n background-color: color-mix(\n in srgb,\n var(--gnome-accent-bg-color, #3584e4) 8%,\n transparent\n );\n}\n\n.warning {\n border-color: var(--gnome-warning-bg-color, #f6d32d);\n background-color: color-mix(\n in srgb,\n var(--gnome-warning-bg-color, #f6d32d) 14%,\n transparent\n );\n}\n\n.error {\n border-color: var(--gnome-error-bg-color, #e01b24);\n background-color: color-mix(\n in srgb,\n var(--gnome-error-bg-color, #e01b24) 8%,\n transparent\n );\n}\n\n.success {\n border-color: var(--gnome-success-bg-color, #2ec27e);\n background-color: color-mix(\n in srgb,\n var(--gnome-success-bg-color, #2ec27e) 8%,\n transparent\n );\n}\n","import type { HTMLAttributes, ReactNode } from \"react\";\nimport styles from \"./Blockquote.module.css\";\n\n// ─── Types ────────────────────────────────────────────────────────────────────\n\nexport type BlockquoteVariant = \"default\" | \"info\" | \"warning\" | \"error\" | \"success\";\n\nexport interface BlockquoteProps extends HTMLAttributes<HTMLQuoteElement> {\n /**\n * Visual emphasis style.\n * - `\"default\"` — neutral left border; use for general quotations.\n * - `\"info\"` — accent blue; use for tips and noteworthy passages.\n * - `\"warning\"` — yellow; use for cautionary content.\n * - `\"error\"` — red; use for critical or deprecated content.\n * - `\"success\"` — green; use for positive or approved content.\n */\n variant?: BlockquoteVariant;\n /**\n * Optional icon displayed to the left of the quoted text.\n * Typically a 16 px `<Icon>` component.\n */\n icon?: ReactNode;\n /**\n * Attribution rendered below the quote — author name, source title, etc.\n * Rendered inside a `<footer><cite>` for semantic correctness.\n */\n cite?: ReactNode;\n /** The quoted content. */\n children: ReactNode;\n}\n\n// ─── Component ────────────────────────────────────────────────────────────────\n\n/**\n * Styled pull-quote with semantic `<blockquote>` markup.\n *\n * Supports five visual variants and optional icon + attribution.\n *\n * ```tsx\n * <Blockquote variant=\"info\" cite=\"Ada Lovelace, 1842\">\n * The Analytical Engine has no pretensions to originate anything.\n * </Blockquote>\n *\n * <Blockquote variant=\"warning\" icon={<Icon icon={Warning} size={16} />}>\n * This API is deprecated and will be removed in v3.\n * </Blockquote>\n * ```\n */\nexport function Blockquote({\n variant = \"default\",\n icon,\n cite,\n children,\n className,\n ...props\n}: BlockquoteProps) {\n return (\n <blockquote\n className={[styles.blockquote, styles[variant], className]\n .filter(Boolean)\n .join(\" \")}\n {...props}\n >\n <div className={styles.body}>\n {icon && (\n <span className={styles.icon} aria-hidden=\"true\">\n {icon}\n </span>\n )}\n <p className={styles.content}>{children}</p>\n </div>\n\n {cite !== undefined && (\n <footer className={styles.footer}>\n <cite className={styles.cite}>{cite}</cite>\n </footer>\n )}\n </blockquote>\n );\n}\n",".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 transition: width var(--gnome-duration-normal) var(--gnome-easing-default);\n will-change: width;\n}\n\n/* ─── Variants ─────────────────────────────────────────────────────────────── */\n\n.accent { background-color: var(--gnome-accent-bg-color, #3584e4); }\n.success { background-color: var(--gnome-success-bg-color, #2ec27e); }\n.warning { background-color: var(--gnome-warning-bg-color, #f6d32d); }\n.error { background-color: var(--gnome-error-bg-color, #e01b24); }\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 type ProgressBarVariant = \"accent\" | \"success\" | \"warning\" | \"error\";\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 /**\n * Fill color of the progress indicator.\n * - `\"accent\"` (default) — blue, general-purpose progress.\n * - `\"success\"` — green, completed or healthy state.\n * - `\"warning\"` — yellow, approaching a limit.\n * - `\"error\"` — red, failed or critical state.\n */\n variant?: ProgressBarVariant;\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 variant = \"accent\",\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 styles[variant],\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","/* ─── Base container ──────────────────────────────────────────────────────── */\n\n.countdownTimer {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n gap: 0.5rem;\n\n padding: 0.5rem 1rem;\n border-radius: var(--gnome-radius-default, 6px);\n background-color: var(--gnome-view-bg-color, #f6f5f4);\n\n font-family: var(--gnome-font-family);\n font-size: 1rem;\n font-weight: var(--gnome-font-weight-medium, 500);\n line-height: 1.5;\n letter-spacing: 0.02em;\n\n user-select: none;\n transition: all 200ms ease-out;\n}\n\n/* ─── Value display ────────────────────────────────────────────────────────── */\n\n.value {\n font-family: \"Fira Mono\", monospace;\n font-size: 1.125rem;\n font-weight: 600;\n letter-spacing: 0.025em;\n}\n\n/* ─── Variant: accent (default) ────────────────────────────────────────────── */\n\n.accent {\n background-color: rgba(53, 132, 228, 0.1);\n color: #3584e4;\n border: 1px solid rgba(53, 132, 228, 0.2);\n}\n\n.accent .value {\n color: #3584e4;\n}\n\n/* ─── Variant: success ─────────────────────────────────────────────────────── */\n\n.success {\n background-color: rgba(51, 209, 122, 0.1);\n color: #33d17a;\n border: 1px solid rgba(51, 209, 122, 0.2);\n}\n\n.success .value {\n color: #33d17a;\n}\n\n/* ─── Variant: warning ─────────────────────────────────────────────────────── */\n\n.warning {\n background-color: rgba(255, 190, 0, 0.1);\n color: #f8e45c;\n border: 1px solid rgba(255, 190, 0, 0.2);\n}\n\n.warning .value {\n color: #f8e45c;\n}\n\n/* ─── Variant: destructive ────────────────────────────────────────────────── */\n\n.destructive {\n background-color: rgba(225, 59, 59, 0.1);\n color: #e13b3b;\n border: 1px solid rgba(225, 59, 59, 0.2);\n}\n\n.destructive .value {\n color: #e13b3b;\n}\n\n/* ─── Finished state ───────────────────────────────────────────────────────── */\n\n.finished {\n opacity: 0.7;\n}\n\n@media (prefers-color-scheme: dark) {\n .countdownTimer {\n background-color: var(--gnome-view-bg-color, #3d3846);\n }\n\n .accent {\n background-color: rgba(53, 132, 228, 0.15);\n border-color: rgba(53, 132, 228, 0.3);\n }\n\n .success {\n background-color: rgba(51, 209, 122, 0.15);\n border-color: rgba(51, 209, 122, 0.3);\n }\n\n .warning {\n background-color: rgba(255, 190, 0, 0.15);\n border-color: rgba(255, 190, 0, 0.3);\n }\n\n .destructive {\n background-color: rgba(225, 59, 59, 0.15);\n border-color: rgba(225, 59, 59, 0.3);\n }\n}\n","import { useEffect, useState } from \"react\";\nimport type { HTMLAttributes } from \"react\";\nimport styles from \"./CountDownTimer.module.css\";\n\nexport type CountDownVariant = \"accent\" | \"destructive\" | \"success\" | \"warning\";\n\nexport interface CountDownTimerProps extends HTMLAttributes<HTMLDivElement> {\n /** Start date for the countdown */\n start: Date;\n /** End date for the countdown */\n end: Date;\n /** Format to display: \"date\", \"time\", or \"datetime\" */\n format?: \"date\" | \"time\" | \"datetime\";\n /** Visual style variant. Defaults to `\"accent\"`. */\n variant?: CountDownVariant;\n /** Callback action to execute when countdown reaches zero */\n action?: () => void;\n}\n\ninterface TimeRemaining {\n days: number;\n hours: number;\n minutes: number;\n seconds: number;\n isFinished: boolean;\n}\n\n/**\n * CountDown timer component that displays remaining time and executes an action when time runs out.\n *\n * @see https://developer.gnome.org/hig/patterns/\n */\nexport function CountDownTimer({\n start,\n end,\n format = \"time\",\n variant = \"accent\",\n action,\n className,\n ...props\n}: CountDownTimerProps) {\n const [timeRemaining, setTimeRemaining] = useState<TimeRemaining | null>(null);\n const [hasExecutedAction, setHasExecutedAction] = useState(false);\n\n useEffect(() => {\n const calculateTimeRemaining = (): TimeRemaining => {\n const now = new Date();\n const endTime = new Date(end).getTime();\n const nowTime = now.getTime();\n const difference = endTime - nowTime;\n\n if (difference <= 0) {\n return {\n days: 0,\n hours: 0,\n minutes: 0,\n seconds: 0,\n isFinished: true,\n };\n }\n\n const days = Math.floor(difference / (1000 * 60 * 60 * 24));\n const hours = Math.floor((difference / (1000 * 60 * 60)) % 24);\n const minutes = Math.floor((difference / 1000 / 60) % 60);\n const seconds = Math.floor((difference / 1000) % 60);\n\n return {\n days,\n hours,\n minutes,\n seconds,\n isFinished: false,\n };\n };\n\n // Initial calculation\n setTimeRemaining(calculateTimeRemaining());\n\n // Update every second\n const interval = setInterval(() => {\n const remaining = calculateTimeRemaining();\n setTimeRemaining(remaining);\n\n // Execute action when countdown finishes\n if (remaining.isFinished && !hasExecutedAction) {\n action?.();\n setHasExecutedAction(true);\n }\n }, 1000);\n\n return () => clearInterval(interval);\n }, [end, action, hasExecutedAction]);\n\n if (!timeRemaining) {\n return null;\n }\n\n const formatTime = (): string => {\n if (format === \"date\") {\n const endDate = new Date(end);\n return endDate.toLocaleDateString();\n }\n\n const { days, hours, minutes, seconds, isFinished } = timeRemaining;\n\n if (isFinished) {\n return \"00:00:00\";\n }\n\n if (format === \"datetime\") {\n const endDate = new Date(end);\n const dateString = endDate.toLocaleDateString();\n const timeString = `${String(hours).padStart(2, \"0\")}:${String(minutes).padStart(2, \"0\")}:${String(seconds).padStart(2, \"0\")}`;\n return `${dateString} ${timeString}`;\n }\n\n if (days > 0) {\n return `${days}d ${String(hours).padStart(2, \"0\")}h ${String(minutes).padStart(2, \"0\")}m`;\n }\n\n return `${String(hours).padStart(2, \"0\")}:${String(minutes).padStart(2, \"0\")}:${String(seconds).padStart(2, \"0\")}`;\n };\n\n const displayVariant = timeRemaining.isFinished ? \"destructive\" : variant;\n\n return (\n <div\n className={[\n styles.countdownTimer,\n styles[displayVariant],\n timeRemaining.isFinished ? styles.finished : null,\n className,\n ]\n .filter(Boolean)\n .join(\" \")}\n {...props}\n >\n <span className={styles.value}>{formatTime()}</span>\n </div>\n );\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 2a3 3 0 1 0 0 6 3 3 0 0 0 0-6zM2 14c0-3.314 2.686-5 6-5s6 1.686 6 5z\",\n\t\tfillRule: \"evenodd\"\n\t}]\n}, F = {\n\tviewBox: \"0 0 16 16\",\n\tpaths: [{ d: \"M8 1a1.5 1.5 0 1 1 0 3 1.5 1.5 0 0 1 0-3zM3.5 5.5C5 6.2 6.5 6.5 8 6.5s3-.3 4.5-1l-.5 1.5-4 .5-4-.5z\" }, { d: \"M5.5 7.5 4 13l2.5-1.5L8 14l1.5-2.5L12 13l-1.5-5.5\" }]\n}, I = {\n\tviewBox: \"0 0 16 16\",\n\tpaths: [{ d: \"M1 1h6v6H1zm8 0h6v6H9zM1 9h6v6H1zm8 0h6v6H9z\" }]\n}, L = {\n\tviewBox: \"0 0 16 16\",\n\tpaths: [{ d: \"M8 1a5 5 0 0 0-5 5v3L1.5 11.5h13L13 9V6a5 5 0 0 0-5-5zm-1.5 12a1.5 1.5 0 0 0 3 0z\" }]\n}, R = {\n\tviewBox: \"0 0 16 16\",\n\tpaths: [{ d: \"M8 1a5 5 0 0 0-5 5v4a5 5 0 0 0 10 0V6a5 5 0 0 0-5-5zm-1 1.1V7H4.05A4 4 0 0 1 7 2.1zM9 2.1A4 4 0 0 1 11.95 7H9V2.1z\" }]\n}, z = {\n\tviewBox: \"0 0 16 16\",\n\tpaths: [{ d: \"M1 4.5A1.5 1.5 0 0 1 2.5 3h11A1.5 1.5 0 0 1 15 4.5v7A1.5 1.5 0 0 1 13.5 13h-11A1.5 1.5 0 0 1 1 11.5v-7zM3 6h2v2H3zm3.5 0h2v2h-2zm3.5 0h2v2h-2zM5 9h6v2H5z\" }]\n}, B = {\n\tviewBox: \"0 0 16 16\",\n\tpaths: [{ d: \"M3 1.5A1.5 1.5 0 0 1 4.5 0h7A1.5 1.5 0 0 1 13 1.5v13a1.5 1.5 0 0 1-1.5 1.5h-7A1.5 1.5 0 0 1 3 14.5zm5 11.5a1 1 0 1 0 0 2 1 1 0 0 0 0-2z\" }]\n}, V = {\n\tviewBox: \"0 0 16 16\",\n\tpaths: [{ d: \"M8 2a6 6 0 1 0 0 12A6 6 0 0 0 8 2zM4 7a1.5 1.5 0 1 1 3 0 1.5 1.5 0 0 1-3 0zm5.5-1.5a1.5 1.5 0 1 1 0 3 1.5 1.5 0 0 1 0-3zM6.5 10a1.5 1.5 0 1 1 3 0 1.5 1.5 0 0 1-3 0z\" }]\n}, H = {\n\tviewBox: \"0 0 16 16\",\n\tpaths: [{ d: \"M4 1h8v4H4zM2 6.5A1.5 1.5 0 0 1 3.5 5h9A1.5 1.5 0 0 1 14 6.5v4a1.5 1.5 0 0 1-1.5 1.5H12v-2H4v2H2.5A1.5 1.5 0 0 1 2 10.5zM4 11h8v4H4z\" }]\n}, U = {\n\tviewBox: \"0 0 16 16\",\n\tpaths: [{ d: \"M8 1a3 3 0 0 0-3 3v2H3.5A1.5 1.5 0 0 0 2 7.5v7A1.5 1.5 0 0 0 3.5 16h9a1.5 1.5 0 0 0 1.5-1.5v-7A1.5 1.5 0 0 0 12.5 6H11V4a3 3 0 0 0-3-3zm0 9a1.5 1.5 0 1 1 0 3 1.5 1.5 0 0 1 0-3z\" }]\n}, W = {\n\tviewBox: \"0 0 16 16\",\n\tpaths: [{\n\t\td: \"M1 3a1 1 0 0 0-1 1v8a1 1 0 0 0 1 1h14a1 1 0 0 0 1-1V5a1 1 0 0 0-1-1H7.5L6.2 3H1z\",\n\t\tfillRule: \"evenodd\"\n\t}]\n}, G = {\n\tviewBox: \"0 0 16 16\",\n\tpaths: [{\n\t\td: \"M3 1a1 1 0 0 0-1 1v12a1 1 0 0 0 1 1h10a1 1 0 0 0 1-1V5l-4-4H3zm0 1h6v3a1 1 0 0 0 1 1h3v8H3V2zm7 .5L12.5 5H10V2.5z\",\n\t\tfillRule: \"evenodd\"\n\t}]\n}, K = {\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 1h14v10H1V3zm9.5 1a1.5 1.5 0 1 0 0 3 1.5 1.5 0 0 0 0-3zM3 11l3-4 2.5 3L11 7l3 4H3z\",\n\t\tfillRule: \"evenodd\"\n\t}]\n}, q = {\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}, J = {\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}, Y = {\n\tviewBox: \"0 0 16 16\",\n\tpaths: [{ d: \"M8 13.5S2 9.5 2 5.5a3.5 3.5 0 0 1 6-2.45A3.5 3.5 0 0 1 14 5.5c0 4-6 8-6 8z\" }]\n}, X = {\n\tviewBox: \"0 0 16 16\",\n\tpaths: [{\n\t\td: \"M4 2v12l10-6z\",\n\t\tfillRule: \"evenodd\"\n\t}]\n}, Z = {\n\tviewBox: \"0 0 16 16\",\n\tpaths: [{ d: \"M3 2h3v12H3zm7 0h3v12h-3z\" }]\n}, Q = {\n\tviewBox: \"0 0 16 16\",\n\tpaths: [{ d: \"M3 2v12l8-6-8-6zm8 0h2v12h-2z\" }]\n}, $ = {\n\tviewBox: \"0 0 16 16\",\n\tpaths: [{ d: \"M13 2v12l-8-6 8-6zm-8 0H3v12h2z\" }]\n};\n//#endregion\nexport { F as Accessibility, c as Add, I as Applications, C as Attachment, N as Check, y as Close, V as ColorManagement, f as Copy, m as Cut, u as Delete, G as Document, v as DocumentOpen, d as Edit, M as Error, W as Folder, n as GoHome, t as GoNext, e as GoPrevious, r as GoUp, Y as Heart, K as Image, A as Information, z as InputKeyboard, R as InputMouse, B as InputTablet, U as Lock, Z as MediaPause, X as MediaPlay, $ as MediaSkipBackward, Q as MediaSkipForward, L as Notifications, w as OpenMenu, i as PanDown, s as PanEnd, o as PanStart, a as PanUp, p as Paste, P as Person, H as Printer, g as Redo, x as Refresh, l as Remove, _ as Save, b as Search, k as Settings, S as Share, q as Star, J 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/* ─── Suggestions popover ───────────────────────────────────────────────────── */\n\n.suggestionsPanel {\n position: fixed;\n z-index: 10200;\n margin: 0;\n padding: var(--gnome-space-1, 6px);\n list-style: none;\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-top: none;\n border-radius: 0 0 var(--gnome-radius-lg, 12px) 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 max-height: 280px;\n overflow-y: auto;\n\n font-family: var(--gnome-font-family);\n font-size: var(--gnome-font-size-body, 1rem);\n}\n\n.suggestionsLoading {\n display: flex;\n justify-content: center;\n padding: var(--gnome-space-2, 12px);\n}\n\n.suggestionItem {\n padding: var(--gnome-space-1, 6px) var(--gnome-space-2, 12px);\n border-radius: var(--gnome-radius-sm, 6px);\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.suggestionItem:hover,\n.suggestionItemActive {\n background-color: color-mix(in srgb, var(--gnome-accent-color, #3584e4) 12%, transparent);\n}\n\n.suggestionItemActive {\n color: var(--gnome-accent-color, #3584e4);\n}\n\n@media (prefers-color-scheme: dark) {\n .suggestionsPanel {\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\n@media (prefers-reduced-motion: reduce) {\n .suggestionItem {\n transition: none;\n }\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 useId,\n useState,\n type KeyboardEvent,\n type InputHTMLAttributes,\n type ReactNode,\n} from \"react\";\nimport { createPortal } from \"react-dom\";\nimport { Search, Close } from \"@gnome-ui/icons\";\nimport { Icon } from \"../Icon\";\nimport { Spinner } from \"../Spinner\";\nimport styles from \"./SearchBar.module.css\";\n\nexport interface Suggestion {\n id: string;\n label: string;\n}\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 * Mirrors the `.inline` style class.\n */\n inline?: boolean;\n /** Suggestion items to show in the autocomplete popover. */\n suggestions?: Suggestion[];\n /** Called when the user selects a suggestion. */\n onSuggestionSelect?: (item: Suggestion) => void;\n /** Shows a spinner inside the suggestions popover while fetching async results. */\n loadingSuggestions?: boolean;\n /** Custom row renderer for each suggestion. */\n renderSuggestion?: (item: Suggestion) => ReactNode;\n /** `aria-label` for the suggestions listbox. Defaults to `\"Suggestions\"`. */\n suggestionsLabel?: string;\n}\n\ninterface DropdownPos {\n top: number;\n left: number;\n width: number;\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 * - Optional `suggestions` / `loadingSuggestions` props show an autocomplete\n * popover anchored below the bar — follows the GTK4 pattern of\n * `AdwSearchBar` + `GtkPopover` + `GtkListView` (replaces `GtkEntryCompletion`).\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 suggestions,\n onSuggestionSelect,\n loadingSuggestions = false,\n renderSuggestion,\n suggestionsLabel = \"Suggestions\",\n ...props\n}: SearchBarProps) {\n const inputRef = useRef<HTMLInputElement>(null);\n const wrapperRef = useRef<HTMLDivElement>(null);\n const listboxRef = useRef<HTMLUListElement>(null);\n\n const listboxId = useId();\n const optionIdPrefix = useId();\n\n const [activeIndex, setActiveIndex] = useState(-1);\n const [dropdownPos, setDropdownPos] = useState<DropdownPos | null>(null);\n\n const hasSuggestions = (suggestions?.length ?? 0) > 0;\n const showDropdown = open && (loadingSuggestions || hasSuggestions);\n\n // Reset active index whenever the suggestion list changes\n useEffect(() => { setActiveIndex(-1); }, [suggestions]);\n\n // Auto-focus when opening\n useEffect(() => {\n if (open) {\n const id = requestAnimationFrame(() => inputRef.current?.focus());\n return () => cancelAnimationFrame(id);\n }\n }, [open]);\n\n // Position the suggestions dropdown below the wrapper\n useEffect(() => {\n if (!showDropdown || !wrapperRef.current) {\n setDropdownPos(null);\n return;\n }\n function updatePos() {\n if (!wrapperRef.current) return;\n const r = wrapperRef.current.getBoundingClientRect();\n setDropdownPos({ top: r.bottom, left: r.left, width: r.width });\n }\n updatePos();\n window.addEventListener(\"scroll\", updatePos, { passive: true, capture: true });\n window.addEventListener(\"resize\", updatePos, { passive: true });\n return () => {\n window.removeEventListener(\"scroll\", updatePos, { capture: true });\n window.removeEventListener(\"resize\", updatePos);\n };\n }, [showDropdown]);\n\n // Scroll the active option into view\n useEffect(() => {\n if (activeIndex < 0 || !listboxRef.current) return;\n (listboxRef.current.children[activeIndex] as HTMLElement | undefined)\n ?.scrollIntoView({ block: \"nearest\" });\n }, [activeIndex]);\n\n const handleKeyDown = useCallback(\n (e: KeyboardEvent<HTMLInputElement>) => {\n if (showDropdown && hasSuggestions && suggestions) {\n if (e.key === \"ArrowDown\") {\n e.preventDefault();\n setActiveIndex((i) => Math.min(i + 1, suggestions.length - 1));\n return;\n }\n if (e.key === \"ArrowUp\") {\n e.preventDefault();\n setActiveIndex((i) => Math.max(i - 1, -1));\n return;\n }\n if (e.key === \"Enter\" && activeIndex >= 0) {\n e.preventDefault();\n onSuggestionSelect?.(suggestions[activeIndex]);\n return;\n }\n }\n if (e.key === \"Escape\") {\n e.preventDefault();\n onClose?.();\n }\n props.onKeyDown?.(e);\n },\n [onClose, props, showDropdown, hasSuggestions, suggestions, activeIndex, onSuggestionSelect]\n );\n\n const hasValue = value !== undefined && value !== \"\";\n const activeOptionId = activeIndex >= 0 && suggestions?.[activeIndex]\n ? `${optionIdPrefix}-${suggestions[activeIndex].id}`\n : undefined;\n\n const dropdown = showDropdown && dropdownPos\n ? createPortal(\n <ul\n ref={listboxRef}\n role=\"listbox\"\n aria-label={suggestionsLabel}\n id={listboxId}\n className={styles.suggestionsPanel}\n style={{ top: dropdownPos.top, left: dropdownPos.left, width: dropdownPos.width }}\n onMouseDown={(e) => e.preventDefault()} // keep focus on input\n >\n {loadingSuggestions && (\n <li role=\"presentation\" className={styles.suggestionsLoading}>\n <Spinner size=\"sm\" label=\"Loading suggestions…\" />\n </li>\n )}\n {!loadingSuggestions &&\n suggestions?.map((item, index) => (\n <li\n key={item.id}\n id={`${optionIdPrefix}-${item.id}`}\n role=\"option\"\n aria-selected={index === activeIndex}\n className={[\n styles.suggestionItem,\n index === activeIndex ? styles.suggestionItemActive : null,\n ]\n .filter(Boolean)\n .join(\" \")}\n onMouseEnter={() => setActiveIndex(index)}\n onMouseLeave={() => setActiveIndex(-1)}\n onClick={() => onSuggestionSelect?.(item)}\n >\n {renderSuggestion ? renderSuggestion(item) : item.label}\n </li>\n ))}\n </ul>,\n document.body\n )\n : null;\n\n return (\n <>\n <div\n ref={wrapperRef}\n className={[styles.wrapper, open ? styles.open : null, className]\n .filter(Boolean)\n .join(\" \")}\n aria-hidden={!open}\n >\n <div\n className={[\n styles.bar,\n inline ? styles.inline : null,\n disabled ? styles.disabled : null,\n ]\n .filter(Boolean)\n .join(\" \")}\n >\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=\"combobox\"\n aria-autocomplete=\"list\"\n aria-expanded={showDropdown}\n aria-controls={showDropdown ? listboxId : undefined}\n aria-activedescendant={activeOptionId}\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 {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 && <div className={styles.filterRow}>{children}</div>}\n </div>\n\n {typeof document !== \"undefined\" && dropdown}\n </>\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/* ─── Section header (title row, optionally a button when collapsible) ──────── */\n\n.sectionHeader {\n display: flex;\n align-items: center;\n gap: var(--gnome-space-1, 6px);\n width: 100%;\n margin: 4px 0 2px;\n padding: 2px var(--gnome-space-1, 6px);\n border-radius: var(--gnome-radius-sm, 4px);\n background: transparent;\n border: none;\n font: inherit;\n text-align: start;\n cursor: default;\n}\n\nbutton.sectionHeader {\n cursor: pointer;\n transition: background-color var(--gnome-duration-fast) var(--gnome-easing-default);\n}\n\nbutton.sectionHeader:hover {\n background-color: var(--gnome-hover-overlay);\n}\n\nbutton.sectionHeader:active {\n background-color: var(--gnome-active-overlay);\n}\n\nbutton.sectionHeader:focus-visible {\n outline: none;\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.sectionHeaderIcon {\n display: flex;\n align-items: center;\n flex-shrink: 0;\n opacity: 0.7;\n}\n\n.sectionTitle {\n flex: 1;\n min-width: 0;\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 white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n}\n\n.sectionChevron {\n display: flex;\n align-items: center;\n flex-shrink: 0;\n margin-left: auto;\n opacity: 0.5;\n}\n\n/* ─── Collapsible section body ──────────────────────────────────────────────── */\n\n.sectionBody {\n display: grid;\n grid-template-rows: 1fr;\n transition: grid-template-rows 200ms var(--gnome-easing-default, ease);\n}\n\n.sectionBodyCollapsed {\n grid-template-rows: 0fr;\n}\n\n.sectionBodyInner {\n overflow: hidden;\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/* ─── Variants ──────────────────────────────────────────────────────────────── */\n\n.variantBlue {\n --gnome-sidebar-bg-color: #1a4a7a;\n --gnome-sidebar-fg-color: rgba(255, 255, 255, 0.95);\n --gnome-hover-overlay: rgba(255, 255, 255, 0.1);\n --gnome-active-overlay: rgba(255, 255, 255, 0.18);\n --gnome-divider-color: rgba(255, 255, 255, 0.15);\n --gnome-headerbar-border-color: rgba(255, 255, 255, 0.15);\n}\n\n.variantGreen {\n --gnome-sidebar-bg-color: #1a4a2e;\n --gnome-sidebar-fg-color: rgba(255, 255, 255, 0.95);\n --gnome-hover-overlay: rgba(255, 255, 255, 0.1);\n --gnome-active-overlay: rgba(255, 255, 255, 0.18);\n --gnome-divider-color: rgba(255, 255, 255, 0.15);\n --gnome-headerbar-border-color: rgba(255, 255, 255, 0.15);\n}\n\n.variantRed {\n --gnome-sidebar-bg-color: #6e1414;\n --gnome-sidebar-fg-color: rgba(255, 255, 255, 0.95);\n --gnome-hover-overlay: rgba(255, 255, 255, 0.1);\n --gnome-active-overlay: rgba(255, 255, 255, 0.18);\n --gnome-divider-color: rgba(255, 255, 255, 0.15);\n --gnome-headerbar-border-color: rgba(255, 255, 255, 0.15);\n}\n\n.variantYellow {\n --gnome-sidebar-bg-color: #5a4500;\n --gnome-sidebar-fg-color: rgba(255, 255, 255, 0.95);\n --gnome-hover-overlay: rgba(255, 255, 255, 0.1);\n --gnome-active-overlay: rgba(255, 255, 255, 0.18);\n --gnome-divider-color: rgba(255, 255, 255, 0.15);\n --gnome-headerbar-border-color: rgba(255, 255, 255, 0.15);\n}\n\n.variantBlack {\n --gnome-sidebar-bg-color: #0a0a0a;\n --gnome-sidebar-fg-color: rgba(255, 255, 255, 0.9);\n --gnome-hover-overlay: rgba(255, 255, 255, 0.08);\n --gnome-active-overlay: rgba(255, 255, 255, 0.14);\n --gnome-divider-color: rgba(255, 255, 255, 0.1);\n --gnome-headerbar-border-color: rgba(255, 255, 255, 0.1);\n}\n\n.variantTransparent {\n --gnome-sidebar-bg-color: transparent;\n border-right-color: var(--gnome-divider-color);\n}\n\n.variantBlurred {\n --gnome-sidebar-bg-color: rgba(255, 255, 255, 0.55);\n border-right-color: rgba(255, 255, 255, 0.25);\n backdrop-filter: blur(20px) saturate(1.8);\n -webkit-backdrop-filter: blur(20px) saturate(1.8);\n}\n\n@media (prefers-color-scheme: dark) {\n .variantBlurred {\n --gnome-sidebar-bg-color: rgba(30, 30, 30, 0.6);\n border-right-color: rgba(255, 255, 255, 0.1);\n }\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 button.sectionHeader,\n .sectionBody {\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// ─── Variant map ──────────────────────────────────────────────────────────────\n\nconst variantClass: Record<string, string> = {\n blue: \"variantBlue\",\n green: \"variantGreen\",\n red: \"variantRed\",\n yellow: \"variantYellow\",\n black: \"variantBlack\",\n transparent: \"variantTransparent\",\n blurred: \"variantBlurred\",\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 * Visual style variant for the sidebar background.\n * - `\"classic\"` — default GNOME gray\n * - `\"blue\"` | `\"green\"` | `\"red\"` | `\"yellow\"` — tinted accent backgrounds\n * - `\"black\"` — dark/OLED background\n * - `\"transparent\"` — no background, inherits parent\n * - `\"blurred\"` — frosted glass (backdrop-filter blur), like macOS\n */\n variant?: \"classic\" | \"blue\" | \"green\" | \"red\" | \"yellow\" | \"black\" | \"transparent\" | \"blurred\";\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 variant = \"classic\",\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 variant !== \"classic\" ? styles[variantClass[variant]] : 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 {\n forwardRef,\n useImperativeHandle,\n useState,\n useContext,\n useId,\n isValidElement,\n Children,\n type HTMLAttributes,\n type ReactNode,\n} from \"react\";\nimport type { IconDefinition } from \"@gnome-ui/icons\";\nimport { PanDown, PanUp } from \"@gnome-ui/icons\";\nimport { Icon } from \"../Icon\";\nimport { useSidebarCollapsed, SidebarFilterContext } from \"./Sidebar\";\nimport styles from \"./Sidebar.module.css\";\n\n// ─── Filter helper ────────────────────────────────────────────────────────────\n\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// ─── Handle ───────────────────────────────────────────────────────────────────\n\nexport interface SidebarSectionHandle {\n expand: () => void;\n collapse: () => void;\n toggle: () => void;\n}\n\n// ─── Props ────────────────────────────────────────────────────────────────────\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 /** Icon rendered left of the title. Same type as `SidebarItem.icon`. */\n icon?: IconDefinition;\n /** Whether the section body can be toggled open/closed. Defaults to `false`. */\n collapsible?: boolean;\n /** Initial open state when `collapsible` is true. Defaults to `true`. */\n defaultOpen?: boolean;\n /** Controlled open state. */\n open?: boolean;\n /** Called when open state changes. */\n onOpenChange?: (open: boolean) => void;\n children?: ReactNode;\n}\n\n// ─── Component ────────────────────────────────────────────────────────────────\n\n/**\n * Named group of `SidebarItem` entries inside a `Sidebar`.\n *\n * Sections are separated by a thin divider. The title and icon are optional.\n * When `collapsible` is true the body can be toggled via the header button or\n * imperatively via a `ref` (`expand`, `collapse`, `toggle`).\n *\n * In rail (icon-only) mode the body is always visible regardless of open state.\n * When a sidebar filter is active and no children match, the section is hidden.\n *\n * @see https://gnome.pages.gitlab.gnome.org/libadwaita/doc/main/class.Sidebar.html\n */\nexport const SidebarSection = forwardRef<SidebarSectionHandle, SidebarSectionProps>(\n function SidebarSection(\n {\n title,\n icon,\n collapsible = false,\n defaultOpen = true,\n open: controlledOpen,\n onOpenChange,\n children,\n className,\n ...props\n },\n ref,\n ) {\n const [internalOpen, setInternalOpen] = useState(defaultOpen);\n const isControlled = controlledOpen !== undefined;\n const open = isControlled ? controlledOpen : internalOpen;\n\n const sidebarCollapsed = useSidebarCollapsed();\n const filterValue = useContext(SidebarFilterContext);\n const bodyId = useId();\n\n const setAndNotify = (next: boolean) => {\n if (!isControlled) setInternalOpen(next);\n onOpenChange?.(next);\n };\n\n useImperativeHandle(ref, () => ({\n expand: () => setAndNotify(true),\n collapse: () => setAndNotify(false),\n toggle: () => setAndNotify(!open),\n }));\n\n // Hide section entirely when filter is active and no children match\n const hasVisibleChildren =\n filterValue.length === 0 || countMatchingItems(children, filterValue) > 0;\n\n if (!hasVisibleChildren) return null;\n\n // In rail mode the body is always visible\n const isOpen = sidebarCollapsed ? true : open;\n\n const showHeader = !!(title || icon || (collapsible && !sidebarCollapsed));\n\n return (\n <section\n className={[styles.section, className].filter(Boolean).join(\" \")}\n {...props}\n >\n {showHeader && (\n collapsible && !sidebarCollapsed ? (\n <button\n type=\"button\"\n className={styles.sectionHeader}\n onClick={() => setAndNotify(!open)}\n aria-expanded={open}\n aria-controls={bodyId}\n >\n {icon && (\n <span className={styles.sectionHeaderIcon}>\n <Icon icon={icon} size=\"sm\" aria-hidden />\n </span>\n )}\n {title && <span className={styles.sectionTitle}>{title}</span>}\n <span className={styles.sectionChevron}>\n <Icon icon={open ? PanUp : PanDown} size=\"sm\" aria-hidden />\n </span>\n </button>\n ) : (\n <div className={styles.sectionHeader}>\n {icon && (\n <span className={styles.sectionHeaderIcon}>\n <Icon icon={icon} size=\"sm\" aria-hidden />\n </span>\n )}\n {title && <span className={styles.sectionTitle}>{title}</span>}\n </div>\n )\n )}\n\n <div\n id={bodyId}\n className={[\n styles.sectionBody,\n !isOpen ? styles.sectionBodyCollapsed : null,\n ]\n .filter(Boolean)\n .join(\" \")}\n aria-hidden={!isOpen || undefined}\n >\n <div className={styles.sectionBodyInner}>\n <ul role=\"list\" className={styles.list}>\n {children}\n </ul>\n </div>\n </div>\n </section>\n );\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}\n\n/* ─── Row wrapper ──────────────────────────────────────────────────────────── */\n\n.item {\n position: relative;\n display: flex;\n flex-direction: column;\n}\n\n.item:first-child > * {\n border-radius: var(--gnome-radius-lg) var(--gnome-radius-lg) 0 0;\n}\n\n.item:last-child > * {\n border-radius: 0 0 var(--gnome-radius-lg) var(--gnome-radius-lg);\n}\n\n.item:only-child > * {\n border-radius: var(--gnome-radius-lg);\n}\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 { Separator } from \"../Separator\";\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 && <Separator 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","import { useState, useEffect, type CSSProperties, type KeyboardEvent, RefObject } from \"react\";\n\nexport const FOCUSABLE =\n 'button:not([disabled]), [href], input:not([disabled]), select:not([disabled]), textarea:not([disabled]), [tabindex]:not([tabindex=\"-1\"])';\n\n/**\n * Returns inline styles that anchor a `position:fixed` backdrop to the visual\n * viewport so the dialog stays centered when the virtual keyboard is open on\n * mobile (iOS Safari / Android Chrome shrink `visualViewport` but not the\n * layout viewport that `position:fixed` is relative to).\n */\nexport function useVisualViewport(): CSSProperties {\n const [style, setStyle] = useState<CSSProperties>({});\n\n useEffect(() => {\n const { visualViewport } = window;\n\n if (!visualViewport) {\n return;\n }\n\n const update = (): void => {\n const { offsetLeft: left, offsetTop: top, width, height } = visualViewport;\n setStyle({\n top,\n left,\n width,\n height,\n });\n };\n\n update();\n visualViewport.addEventListener(\"resize\", update);\n visualViewport.addEventListener(\"scroll\", update);\n\n return () => {\n visualViewport.removeEventListener(\"resize\", update);\n visualViewport.removeEventListener(\"scroll\", update);\n };\n }, []);\n\n return style;\n}\n\nexport function trapFocus(e: KeyboardEvent<HTMLDivElement>, ref: RefObject<HTMLDivElement | null>) {\n if (e.key !== \"Tab\") {\n return;\n }\n\n const focusable = Array.from(ref.current?.querySelectorAll<HTMLElement>(FOCUSABLE) ?? []);\n\n if (!focusable.length) {\n return;\n }\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","/* ─── 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: var(--gnome-dialog-backdrop-color, rgba(0, 0, 0, 0.5));\n backdrop-filter: blur(var(--gnome-dialog-backdrop-blur, 4px));\n -webkit-backdrop-filter: blur(var(--gnome-dialog-backdrop-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);\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.dialog:has(.body) .title {\n padding: var(--gnome-space-4, 24px) var(--gnome-space-4, 24px) 0 !important;\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/* ─── Dark mode ─────────────────────────────────────────────────────────────── */\n\n@media (prefers-color-scheme: dark) {\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 type KeyboardEvent,\n type HTMLAttributes,\n type ReactNode,\n} from \"react\";\nimport { createPortal } from \"react-dom\";\nimport { trapFocus, FOCUSABLE, useVisualViewport } from \"./dialogUtils\";\nimport styles from \"./Dialog.module.css\";\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// ─── 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\n/**\n * Blocking modal dialog following the Adwaita pattern.\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 * For the app-info dialog, use `<AboutDialog />` instead.\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 */\nexport function Dialog({\n open,\n title,\n children,\n buttons = [],\n onClose,\n closeOnBackdrop = true,\n role = \"dialog\",\n responses,\n onResponse,\n className,\n ...props\n}: DialogProps) {\n const dialogRef = useRef<HTMLDivElement>(null);\n const titleId = useId();\n const previouslyFocused = useRef<Element | null>(null);\n\n const isAlert = !!responses;\n const viewportStyle = useVisualViewport();\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 }\n }, [open]);\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 // ── Render buttons (standard or alert) ────────────────────────────────────\n const renderButtons = () => {\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 const node = (\n <div\n className={styles.backdrop}\n style={viewportStyle}\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={[styles.dialog, className].filter(Boolean).join(\" \")}\n onKeyDown={handleKeyDown}\n onClick={(e) => e.stopPropagation()}\n {...props}\n >\n {title && <div id={titleId} className={styles.title}>{title}</div>}\n {children && <div className={styles.body}>{children}</div>}\n {renderButtons()}\n </div>\n </div>\n );\n\n if (typeof document === \"undefined\") return node;\n return createPortal(node, 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: var(--gnome-dialog-backdrop-color, rgba(0, 0, 0, 0.5));\n backdrop-filter: blur(var(--gnome-dialog-backdrop-blur, 4px));\n -webkit-backdrop-filter: blur(var(--gnome-dialog-backdrop-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 max-height: min(640px, 90dvh);\n display: flex;\n flex-direction: column;\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/* ─── Header ────────────────────────────────────────────────────────────────── */\n\n.header {\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.icon {\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.appName {\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.version {\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\n.tabBar {\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.tabBtn {\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.tabBtn:hover { opacity: 1; }\n\n.tabBtnActive {\n opacity: 1;\n font-weight: var(--gnome-font-weight-semibold, 600);\n border-bottom-color: var(--gnome-accent-color, #3584e4);\n}\n\n.tabBtn: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\n.tabContent {\n flex: 1;\n overflow-y: auto;\n}\n\n.section {\n padding: var(--gnome-space-3, 16px) var(--gnome-space-4, 24px);\n}\n\n/* Details */\n.comments {\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.infoList {\n display: grid;\n grid-template-columns: auto 1fr;\n gap: 6px var(--gnome-space-3, 16px);\n margin: 0;\n}\n\n.infoLabel {\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.infoValue {\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.link {\n color: var(--gnome-accent-color, #3584e4);\n text-decoration: none;\n}\n\n.link:hover { text-decoration: underline; }\n\n/* Credits */\n.creditGroup + .creditGroup {\n margin-top: var(--gnome-space-3, 16px);\n}\n\n.creditTitle {\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.creditList {\n list-style: none;\n margin: 0;\n padding: 0;\n display: flex;\n flex-direction: column;\n gap: 2px;\n}\n\n.creditItem {\n font-family: var(--gnome-font-family);\n font-size: var(--gnome-font-size-body, 1rem);\n}\n\n/* Legal */\n.copyright {\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.licenseType {\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.licenseText {\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/* ─── Footer (close button) ─────────────────────────────────────────────────── */\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.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\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 { border-bottom: none; }\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 { outline: none; }\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.btnDefault {\n color: var(--gnome-accent-color, #3584e4);\n}\n\n/* ─── Dark mode ─────────────────────────────────────────────────────────────── */\n\n@media (prefers-color-scheme: dark) {\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 ReactNode,\n} from \"react\";\nimport { createPortal } from \"react-dom\";\nimport { FOCUSABLE, trapFocus, useVisualViewport } from \"../Dialog/dialogUtils\";\nimport styles from \"./AboutDialog.module.css\";\n\nexport interface AboutDialogLink {\n label: string;\n url: string;\n}\n\nexport interface AboutDialogProps {\n /** Whether the dialog is visible. */\n open: boolean;\n /** Called on Escape or close button click. */\n onClose?: () => void;\n /** Application name. */\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\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 * Standard app info dialog with details, credits, and legal tabs.\n * Mirrors `AdwAboutDialog`.\n *\n * @see https://gnome.pages.gitlab.gnome.org/libadwaita/doc/main/class.AboutDialog.html\n */\nexport function AboutDialog({\n open,\n onClose,\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}: AboutDialogProps) {\n const dialogRef = useRef<HTMLDivElement>(null);\n const titleId = useId();\n const previouslyFocused = useRef<Element | null>(null);\n const viewportStyle = useVisualViewport();\n const [activeTab, setActiveTab] = useState<AboutTab>(\"details\");\n\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 setActiveTab(\"details\");\n }\n }, [open]);\n\n const handleKeyDown = useCallback(\n (e: KeyboardEvent<HTMLDivElement>) => {\n if (e.key === \"Escape\") {\n e.preventDefault();\n onClose?.();\n return;\n }\n trapFocus(e, dialogRef);\n },\n [onClose]\n );\n\n if (!open) return null;\n\n const hasCredits = !!(developers?.length || designers?.length || artists?.length);\n const hasLegal = !!(copyright || licenseType || licenseText);\n const tabs = (\n [\"details\", hasCredits && \"credits\", hasLegal && \"legal\"] as (AboutTab | false)[]\n ).filter(Boolean) as AboutTab[];\n\n const node = (\n <div className={styles.backdrop} style={viewportStyle} onClick={onClose} aria-hidden=\"true\">\n <div\n ref={dialogRef}\n role=\"dialog\"\n aria-modal=\"true\"\n aria-labelledby={titleId}\n className={styles.dialog}\n onKeyDown={handleKeyDown}\n onClick={(e) => e.stopPropagation()}\n >\n <div className={styles.header}>\n {applicationIcon && <div className={styles.icon}>{applicationIcon}</div>}\n <div id={titleId} className={styles.appName}>{applicationName}</div>\n {version && <div className={styles.version}>{version}</div>}\n </div>\n\n {tabs.length > 1 && (\n <div className={styles.tabBar} role=\"tablist\">\n {tabs.map((t) => (\n <button\n key={t}\n type=\"button\"\n role=\"tab\"\n aria-selected={activeTab === t}\n className={[styles.tabBtn, activeTab === t ? styles.tabBtnActive : null]\n .filter(Boolean).join(\" \")}\n onClick={() => setActiveTab(t)}\n >\n {ABOUT_TAB_LABEL[t]}\n </button>\n ))}\n </div>\n )}\n\n <div className={styles.tabContent}>\n {activeTab === \"details\" && (\n <div className={styles.section}>\n {comments && <p className={styles.comments}>{comments}</p>}\n {(developerName || website || links?.length) && (\n <dl className={styles.infoList}>\n {developerName && (\n <><dt className={styles.infoLabel}>Developer</dt><dd className={styles.infoValue}>{developerName}</dd></>\n )}\n {website && (\n <><dt className={styles.infoLabel}>Website</dt><dd className={styles.infoValue}>\n <a href={website} target=\"_blank\" rel=\"noopener noreferrer\" className={styles.link}>\n {websiteLabel ?? website}\n </a>\n </dd></>\n )}\n {links?.map((l) => (\n <><dt key={`${l.label}-dt`} className={styles.infoLabel}>{l.label}</dt>\n <dd key={`${l.label}-dd`} className={styles.infoValue}>\n <a href={l.url} target=\"_blank\" rel=\"noopener noreferrer\" className={styles.link}>{l.url}</a>\n </dd></>\n ))}\n </dl>\n )}\n </div>\n )}\n\n {activeTab === \"credits\" && (\n <div className={styles.section}>\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.creditGroup}>\n <h4 className={styles.creditTitle}>{heading}</h4>\n <ul className={styles.creditList}>\n {list!.map((name) => <li key={name} className={styles.creditItem}>{name}</li>)}\n </ul>\n </div>\n ))}\n </div>\n )}\n\n {activeTab === \"legal\" && (\n <div className={styles.section}>\n {copyright && <p className={styles.copyright}>{copyright}</p>}\n {licenseType && <p className={styles.licenseType}>{licenseType}</p>}\n {licenseText && <pre className={styles.licenseText}>{licenseText}</pre>}\n </div>\n )}\n </div>\n\n <div className={styles.footer}>\n <button type=\"button\" className={[styles.btn, styles.btnDefault].join(\" \")} onClick={onClose}>\n Close\n </button>\n </div>\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 /** Arrow center offset in px from the near edge of the panel.\n * Horizontal (left) for top/bottom; vertical (top) for left/right.\n * Undefined means use the default 50% CSS. */\n arrowOffset?: number;\n}\n\nconst GAP = 8;\nconst MARGIN = 10;\nconst ARROW_HALF = 6; // half of the 12 px arrow\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 function calcRaw(p: PopoverPlacement): { top: number; left: number } {\n if (p === \"bottom\") return {\n top: trigger.bottom + GAP,\n left: trigger.left + trigger.width / 2 - popover.width / 2,\n };\n if (p === \"top\") return {\n top: trigger.top - popover.height - GAP,\n left: trigger.left + trigger.width / 2 - popover.width / 2,\n };\n if (p === \"left\") return {\n top: trigger.top + trigger.height / 2 - popover.height / 2,\n left: trigger.left - popover.width - GAP,\n };\n return {\n top: trigger.top + trigger.height / 2 - popover.height / 2,\n left: trigger.right + GAP,\n };\n }\n\n // First pass: find a placement that fits both axes perfectly.\n for (const p of candidates) {\n const { top, left } = calcRaw(p);\n const fitsH = left >= MARGIN && left + popover.width <= vw - MARGIN;\n const fitsV = top >= MARGIN && top + popover.height <= vh - MARGIN;\n if (fitsH && fitsV) {\n // Always use pixel arrowOffset (never rely on CSS left:50%) so the arrow\n // points exactly at the trigger center regardless of sub-pixel rounding.\n const arrowOffset =\n p === \"top\" || p === \"bottom\"\n ? trigger.left + trigger.width / 2 - left\n : trigger.top + trigger.height / 2 - top;\n return { top, left, placement: p, arrowOffset };\n }\n }\n\n // Second pass: fits the primary axis; clamp the secondary axis and\n // shift the arrow so it still points at the trigger center.\n for (const p of candidates) {\n const { top, left } = calcRaw(p);\n const fitsV = top >= MARGIN && top + popover.height <= vh - MARGIN;\n const fitsH = left >= MARGIN && left + popover.width <= vw - MARGIN;\n\n if ((p === \"top\" || p === \"bottom\") && fitsV) {\n const clampedLeft = Math.max(MARGIN, Math.min(left, vw - popover.width - MARGIN));\n const rawOffset = trigger.left + trigger.width / 2 - clampedLeft;\n return {\n top, left: clampedLeft, placement: p,\n arrowOffset: Math.max(ARROW_HALF + 4, Math.min(rawOffset, popover.width - ARROW_HALF - 4)),\n };\n }\n\n if ((p === \"left\" || p === \"right\") && fitsH) {\n const clampedTop = Math.max(MARGIN, Math.min(top, vh - popover.height - MARGIN));\n const rawOffset = trigger.top + trigger.height / 2 - clampedTop;\n return {\n top: clampedTop, left, placement: p,\n arrowOffset: Math.max(ARROW_HALF + 4, Math.min(rawOffset, popover.height - ARROW_HALF - 4)),\n };\n }\n }\n\n // Fallback: clamp bottom placement and shift arrow to match trigger.\n const fbTop = trigger.bottom + GAP;\n const fbLeft = trigger.left + trigger.width / 2 - popover.width / 2;\n const clampedTop = Math.max(MARGIN, Math.min(fbTop, vh - popover.height - MARGIN));\n const clampedLeft = Math.max(MARGIN, Math.min(fbLeft, vw - popover.width - MARGIN));\n const rawArrowOffset = trigger.left + trigger.width / 2 - clampedLeft;\n return {\n top: clampedTop,\n left: clampedLeft,\n placement: \"bottom\",\n arrowOffset: Math.max(ARROW_HALF + 4, Math.min(rawArrowOffset, popover.width - ARROW_HALF - 4)),\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 — offset shifts it to point at the trigger when the panel is clamped */}\n <div\n className={styles.arrow}\n aria-hidden=\"true\"\n style={\n pos?.arrowOffset !== undefined\n ? pos.placement === \"top\" || pos.placement === \"bottom\"\n ? { left: pos.arrowOffset - ARROW_HALF, marginLeft: 0 }\n : { top: pos.arrowOffset - ARROW_HALF, marginTop: 0 }\n : undefined\n }\n />\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 transition: width 200ms var(--gnome-easing-default, ease);\n}\n\n.wide.collapsed .sidebar {\n width: 56px;\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 * When `true`, collapses the sidebar to icon-only width on wide screens.\n * Has no effect in narrow (overlay) mode.\n * Pair with `collapsed` on the inner `<Sidebar>` component to hide labels.\n */\n collapsed?: boolean;\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 collapsed = false,\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 // Swipe-to-dismiss: drag the sidebar in the close direction to trigger onClose\n useEffect(() => {\n if (!isNarrow || !showSidebar) return;\n const el = sidebarRef.current;\n if (!el) return;\n\n let startX = 0;\n const THRESHOLD = 80;\n\n const onTouchStart = (e: TouchEvent) => { startX = e.touches[0].clientX; };\n const onTouchEnd = (e: TouchEvent) => {\n const dx = e.changedTouches[0].clientX - startX;\n const closeSwipe = isEnd ? dx > THRESHOLD : dx < -THRESHOLD;\n if (closeSwipe) onClose?.();\n };\n\n el.addEventListener(\"touchstart\", onTouchStart, { passive: true });\n el.addEventListener(\"touchend\", onTouchEnd, { passive: true });\n return () => {\n el.removeEventListener(\"touchstart\", onTouchStart);\n el.removeEventListener(\"touchend\", onTouchEnd);\n };\n }, [isNarrow, showSidebar, isEnd, onClose]);\n\n return (\n <div\n className={[\n styles.root,\n isNarrow ? styles.narrow : styles.wide,\n !isNarrow && collapsed ? styles.collapsed : null,\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","import type { CSSProperties, HTMLAttributes, ReactNode } from \"react\";\n\n/** GNOME HIG standard spacing values (matches GtkBox spacing tokens). */\nexport type BoxSpacing = 3 | 6 | 12 | 18 | 24 | 32 | 48;\n\nexport type BoxOrientation = \"horizontal\" | \"vertical\";\nexport type BoxAlign = \"start\" | \"center\" | \"end\" | \"stretch\" | \"baseline\";\nexport type BoxJustify =\n | \"start\"\n | \"center\"\n | \"end\"\n | \"space-between\"\n | \"space-around\"\n | \"space-evenly\";\n\nexport interface BoxProps extends HTMLAttributes<HTMLDivElement> {\n /**\n * Direction children are arranged.\n * `\"vertical\"` → `flex-direction: column` (default).\n * `\"horizontal\"` → `flex-direction: row`.\n */\n orientation?: BoxOrientation;\n /**\n * Gap between children in pixels.\n * Accepts any of the GNOME HIG standard spacing values or any CSS string.\n * Defaults to `6` (the HIG \"standard\" inner spacing).\n */\n spacing?: BoxSpacing | number | string;\n /**\n * Cross-axis alignment (`align-items`).\n * Defaults to `\"stretch\"` for vertical, `\"center\"` for horizontal.\n */\n align?: BoxAlign;\n /**\n * Main-axis distribution (`justify-content`).\n * Defaults to `\"start\"`.\n */\n justify?: BoxJustify;\n children?: ReactNode;\n}\n\n/**\n * Fundamental flex layout primitive — the web equivalent of `GtkBox`.\n *\n * Arranges children in a single row or column with consistent spacing\n * following the GNOME Human Interface Guidelines spacing scale:\n *\n * | Token | px | Use |\n * |-------|----|-----|\n * | tight | 3 | Dense UI, icon + label pairs |\n * | standard | 6 | Default inner spacing |\n * | medium | 12 | Between related groups |\n * | large | 18 | Between loosely related sections |\n * | section | 24 | Page-level section gaps |\n * | loose | 32 | Large content separation |\n * | jumbo | 48 | Hero / splash spacing |\n *\n * **Usage:**\n * ```tsx\n * // Vertical section (heading + content)\n * <Box spacing={8}>\n * <Text variant=\"caption-heading\" color=\"dim\">Devices</Text>\n * <BoxedList>…</BoxedList>\n * </Box>\n *\n * // Horizontal icon + label\n * <Box orientation=\"horizontal\" spacing={6} align=\"center\">\n * <Icon name=\"folder\" />\n * <Text>Documents</Text>\n * </Box>\n * ```\n *\n * @see https://developer.gnome.org/hig/guidelines/spacing.html\n */\nexport function Box({\n orientation = \"vertical\",\n spacing = 6,\n align,\n justify = \"start\",\n className,\n style,\n children,\n ...props\n}: BoxProps) {\n const defaultAlign: BoxAlign =\n orientation === \"horizontal\" ? \"center\" : \"stretch\";\n\n const resolvedStyle: CSSProperties = {\n display: \"flex\",\n flexDirection: orientation === \"horizontal\" ? \"row\" : \"column\",\n gap: typeof spacing === \"number\" ? `${spacing}px` : spacing,\n alignItems: align ?? defaultAlign,\n justifyContent: justify,\n ...style,\n };\n\n return (\n <div className={className} style={resolvedStyle} {...props}>\n {children}\n </div>\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: 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: hidden;\n transition: width var(--gnome-duration-normal, 200ms) var(--gnome-easing-default, ease);\n}\n\n.sidebarCollapsed {\n width: 56px;\n}\n\n.sidebarCollapsed .itemBtn {\n justify-content: center;\n padding: 9px;\n}\n\n/* ─── Header / Footer slots ─────────────────────────────────────────────────── */\n\n.header {\n flex-shrink: 0;\n border-bottom: 1px solid var(--gnome-headerbar-border-color, rgba(0, 0, 0, 0.12));\n}\n\n.footer {\n flex-shrink: 0;\n border-top: 1px solid var(--gnome-headerbar-border-color, rgba(0, 0, 0, 0.12));\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 flex: 1;\n overflow-y: auto;\n overflow-x: hidden;\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 .header,\n .footer {\n border-color: rgba(255, 255, 255, 0.08);\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 collapsed: boolean;\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 /**\n * When `true`, items render icon-only (labels hidden) and the sidebar\n * shrinks to its compact width. Used by `AdaptiveLayout` on tablet viewports.\n */\n collapsed?: boolean;\n /** Rendered above the scrollable item list (e.g. user card). */\n header?: ReactNode;\n /** Rendered below the scrollable item list (e.g. collapse toggle). */\n footer?: ReactNode;\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 collapsed = false,\n header,\n footer,\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, collapsed }}>\n <nav\n className={[styles.sidebar, collapsed ? styles.sidebarCollapsed : null, className].filter(Boolean).join(\" \")}\n onKeyDown={handleKeyDown}\n {...props}\n >\n {header && <div className={styles.header}>{header}</div>}\n <ul\n ref={groupRef}\n role=\"radiogroup\"\n aria-label={ariaLabel}\n className={styles.list}\n >\n {children}\n </ul>\n {footer && <div className={styles.footer}>{footer}</div>}\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, collapsed } = 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 {!collapsed && <span className={styles.itemLabel}>{label}</span>}\n {!collapsed && 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/* ─── 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\n/* ─── Reduced motion ────────────────────────────────────────────────────────── */\n\n@media (prefers-reduced-motion: reduce) {\n .panel {\n transition: none;\n }\n\n .chevronWrap {\n transition: none;\n }\n}\n","import { useState, Children, useId, type HTMLAttributes, type ReactNode } from \"react\";\nimport { Separator } from \"../Separator\";\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 <Separator 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/* ─── Variant: pill ─────────────────────────────────────────────────────────── */\n\n.pill {\n background-color: var(--gnome-hover-overlay, rgba(0, 0, 0, 0.06));\n border: none;\n border-radius: var(--gnome-radius-pill, 9999px);\n box-shadow: none;\n padding: 3px;\n gap: 2px;\n}\n\n.pill .item {\n border-radius: var(--gnome-radius-pill, 9999px);\n padding: 5px var(--gnome-space-2, 16px);\n color: var(--gnome-window-fg-color);\n}\n\n.pill .item:hover:not(:disabled):not(.active) {\n background-color: rgba(0, 0, 0, 0.04);\n}\n\n.pill .item:active:not(:disabled):not(.active) {\n background-color: rgba(0, 0, 0, 0.08);\n}\n\n.pill .item:focus-visible {\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, 2px))\n var(--gnome-focus-ring-color, #3584e4);\n}\n\n.pill .active {\n background-color: var(--gnome-card-bg-color, #ffffff);\n box-shadow: var(--gnome-shadow-sm, 0 1px 2px rgba(0, 0, 0, 0.12));\n font-weight: var(--gnome-font-weight-semibold, 600);\n color: var(--gnome-window-fg-color);\n}\n\n@media (prefers-color-scheme: dark) {\n .pill {\n background-color: var(--gnome-hover-overlay, rgba(255, 255, 255, 0.08));\n }\n\n .pill .item:hover:not(:disabled):not(.active) {\n background-color: rgba(255, 255, 255, 0.05);\n }\n\n .pill .item:active:not(:disabled):not(.active) {\n background-color: rgba(255, 255, 255, 0.10);\n }\n\n .pill .active {\n background-color: var(--gnome-card-bg-color, #383838);\n box-shadow: none;\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\" | \"pill\";\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 * - `pill` — segmented-control style; active item appears lifted, no accent color.\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","/* ─── Vertical orientation ───────────────────────────────────────────────── */\n\n/*\n * Grid: 3 columns shared by every item via subgrid.\n * col 1 (auto) → leading: timestamps, labels — sized by widest content\n * col 2 (24px) → nodeTrack: node circle + connector line\n * col 3 (1fr) → content: event title / description\n *\n * align-self on items defaults to stretch so the nodeTrack cell grows to fill\n * the full row height; the flex line inside it then fills gap between nodes.\n */\n.vertical {\n display: grid;\n grid-template-columns: auto 24px 1fr;\n column-gap: 12px;\n}\n\n.vertical .item {\n display: grid;\n grid-column: 1 / -1;\n grid-template-columns: subgrid;\n}\n\n/* Leading: timestamp / label — left-aligned column, text right-aligned */\n.vertical .leading {\n padding-top: 4px; /* align text baseline with node center */\n text-align: end;\n align-self: start; /* don't stretch; only nodeTrack stretches */\n}\n\n/* Node track: node on top, connector line fills remaining height */\n.vertical .nodeTrack {\n display: flex;\n flex-direction: column;\n align-items: center;\n /* align-self defaults to stretch — fills full row height so the line\n reaches the next item's node without a gap */\n}\n\n/* Content: fills the third column */\n.vertical .content {\n min-width: 0;\n padding-top: 2px;\n align-self: start; /* don't stretch content, only nodeTrack */\n}\n\n/* Space between items — applied only to content so nodeTrack (which draws the\n connector line) fills the padding area, keeping the line continuous. */\n.vertical .item:not(:last-child) .content {\n padding-bottom: 20px;\n}\n\n/* ─── Horizontal orientation ──────────────────────────────────────────────── */\n\n/*\n * Grid: 3 rows shared by every item via subgrid.\n * row 1 (auto) → leading: label / timestamp above the node\n * row 2 (auto) → nodeTrack: the horizontal line + node\n * row 3 (auto) → content: label / description below the node\n *\n * grid-auto-flow: column places each item into a new column automatically.\n * All items share the same row heights → leading and content stay vertically\n * aligned regardless of text length.\n */\n.horizontal {\n display: grid;\n grid-template-rows: auto auto auto;\n grid-auto-flow: column;\n grid-auto-columns: minmax(72px, 1fr);\n overflow-x: auto;\n}\n\n.horizontal .item {\n display: grid;\n grid-row: 1 / -1; /* span all 3 rows */\n grid-template-rows: subgrid;\n}\n\n/* Leading: label / timestamp — bottom-aligned so it sits close to the node */\n.horizontal .leading {\n text-align: center;\n align-self: end;\n padding-bottom: 8px;\n}\n\n/* Node track: horizontal row — left line · node · right line */\n.horizontal .nodeTrack {\n display: flex;\n flex-direction: row;\n align-items: center;\n width: 100%;\n}\n\n/* Content: label / description — top-aligned below the node */\n.horizontal .content {\n text-align: center;\n padding: 8px 8px 0;\n align-self: start;\n}\n\n/* ─── Node ────────────────────────────────────────────────────────────────── */\n\n.node {\n display: flex;\n align-items: center;\n justify-content: center;\n flex-shrink: 0;\n border-radius: 50%;\n position: relative;\n z-index: 1;\n}\n\n/* Small dot when no icon */\n.nodeDot {\n width: 12px;\n height: 12px;\n background: var(--gnome-accent-bg-color, #3584e4);\n}\n\n/* Larger circle when an icon is provided */\n.nodeWithIcon {\n width: 28px;\n height: 28px;\n background: var(--gnome-accent-bg-color, #3584e4);\n color: var(--gnome-accent-fg-color, #fff);\n}\n\n.iconWrap {\n display: flex;\n align-items: center;\n justify-content: center;\n line-height: 1;\n}\n\n/* ─── Invisible line (edge positions in horizontal) ──────────────────────── */\n\n/* Takes the same space as a real line but is invisible — keeps the node\n centered in horizontal items regardless of whether it's first or last. */\n.lineInvisible {\n visibility: hidden;\n}\n\n/* ─── Connector line ──────────────────────────────────────────────────────── */\n\n/* Vertical line (inside .vertical .nodeTrack) */\n.vertical .line {\n width: 2px;\n flex: 1;\n min-height: 16px;\n background: var(--gnome-border-subtle, rgba(0, 0, 0, 0.15));\n}\n\n.vertical .lineDotted {\n background: none;\n border-left: 2px dotted var(--gnome-border-subtle, rgba(0, 0, 0, 0.15));\n width: 0;\n}\n\n/* Horizontal line (inside .horizontal .nodeTrack) */\n.horizontal .line {\n flex: 1;\n height: 2px;\n background: var(--gnome-border-subtle, rgba(0, 0, 0, 0.15));\n}\n\n.horizontal .lineDotted {\n background: none;\n border-top: 2px dotted var(--gnome-border-subtle, rgba(0, 0, 0, 0.15));\n height: 0;\n flex: 1;\n}\n\n/* ─── Dark mode ───────────────────────────────────────────────────────────── */\n\n@media (prefers-color-scheme: dark) {\n .vertical .line,\n .horizontal .line {\n background: var(--gnome-border-subtle, rgba(255, 255, 255, 0.15));\n }\n\n .vertical .lineDotted {\n border-left-color: var(--gnome-border-subtle, rgba(255, 255, 255, 0.15));\n }\n\n .horizontal .lineDotted {\n border-top-color: var(--gnome-border-subtle, rgba(255, 255, 255, 0.15));\n }\n}\n","import type { HTMLAttributes, ReactNode } from \"react\";\nimport styles from \"./Timeline.module.css\";\n\n// ─── Types ────────────────────────────────────────────────────────────────────\n\nexport type TimelineOrientation = \"vertical\" | \"horizontal\";\nexport type TimelineVariant = \"default\" | \"dotted\" | \"none\";\n\nexport interface TimelineItem {\n /** Icon rendered inside the timeline node. */\n icon?: ReactNode;\n /**\n * Content at the leading edge of the node.\n * - **Vertical:** rendered to the left of the connector track.\n * - **Horizontal:** rendered above the node.\n *\n * Typical use: timestamp, badge, short label.\n */\n leading?: ReactNode;\n /** Main event content rendered adjacent to the node (title, description…). */\n content: ReactNode;\n}\n\nexport interface TimelineProps extends HTMLAttributes<HTMLDivElement> {\n /** Ordered list of timeline events. */\n items: TimelineItem[];\n /**\n * Axis direction of the connector line.\n * - `\"vertical\"` — events stack top-to-bottom (default).\n * - `\"horizontal\"` — events flow left-to-right (stepper / progress pattern).\n */\n orientation?: TimelineOrientation;\n /**\n * Visual style of the connector between nodes.\n * - `\"default\"` — solid thin line.\n * - `\"dotted\"` — dotted line; useful for future or pending events.\n * - `\"none\"` — no connector; each node stands alone.\n */\n variant?: TimelineVariant;\n}\n\n// ─── Component ────────────────────────────────────────────────────────────────\n\n/**\n * Ordered sequence of events connected by a visual timeline.\n *\n * Supports two orientations — vertical (activity feed, history log) and\n * horizontal (stepper, progress indicator) — and three connector styles.\n *\n * ```tsx\n * <Timeline\n * items={[\n * {\n * leading: <Text variant=\"caption\" color=\"dim\">10:32</Text>,\n * icon: <Icon icon={Check} />,\n * content: <Text variant=\"body\">Approved</Text>,\n * },\n * ]}\n * />\n *\n * <Timeline orientation=\"horizontal\" variant=\"dotted\" items={steps} />\n * ```\n */\nexport function Timeline({\n items,\n orientation = \"vertical\",\n variant = \"default\",\n className,\n ...props\n}: TimelineProps) {\n const horizontal = orientation === \"horizontal\";\n const showLine = variant !== \"none\";\n\n return (\n <div\n role=\"list\"\n className={[\n styles.root,\n horizontal ? styles.horizontal : styles.vertical,\n className,\n ]\n .filter(Boolean)\n .join(\" \")}\n {...props}\n >\n {items.map((item, i) => {\n const isFirst = i === 0;\n const isLast = i === items.length - 1;\n\n const lineClass = [\n styles.line,\n variant === \"dotted\" ? styles.lineDotted : null,\n ]\n .filter(Boolean)\n .join(\" \");\n\n const node = (\n <div\n className={[\n styles.node,\n item.icon ? styles.nodeWithIcon : styles.nodeDot,\n ]\n .filter(Boolean)\n .join(\" \")}\n >\n {item.icon && (\n <span className={styles.iconWrap} aria-hidden=\"true\">\n {item.icon}\n </span>\n )}\n </div>\n );\n\n return (\n <div key={i} role=\"listitem\" className={styles.item}>\n {/* ── Leading (timestamp / label) ── */}\n {/* Always rendered — even when empty — so every item has the same\n 3 children (leading · nodeTrack · content) that map to the\n subgrid columns (vertical) or rows (horizontal). */}\n <div className={styles.leading}>{item.leading}</div>\n\n {/* ── Node track ── */}\n {horizontal ? (\n /*\n * Always render both half-lines so the node stays centered in\n * the item regardless of its position. Edge lines are hidden\n * with visibility:hidden (take space, invisible).\n */\n <div className={styles.nodeTrack}>\n <div\n className={[\n styles.line,\n variant === \"dotted\" ? styles.lineDotted : null,\n (isFirst || !showLine) ? styles.lineInvisible : null,\n ].filter(Boolean).join(\" \")}\n aria-hidden=\"true\"\n />\n {node}\n <div\n className={[\n styles.line,\n variant === \"dotted\" ? styles.lineDotted : null,\n (isLast || !showLine) ? styles.lineInvisible : null,\n ].filter(Boolean).join(\" \")}\n aria-hidden=\"true\"\n />\n </div>\n ) : (\n <div className={styles.nodeTrack}>\n {node}\n {!isLast && showLine && (\n <div className={lineClass} aria-hidden=\"true\" />\n )}\n </div>\n )}\n\n {/* ── Content ── */}\n <div className={styles.content}>{item.content}</div>\n </div>\n );\n })}\n </div>\n );\n}\n","/* ─── Root ────────────────────────────────────────────────────────────────── */\n\n.pathBar {\n display: flex;\n align-items: center;\n min-width: 0;\n font-family: var(--gnome-font-family, sans-serif);\n}\n\n/* ─── List ────────────────────────────────────────────────────────────────── */\n\n.list {\n display: flex;\n align-items: center;\n flex-wrap: wrap;\n gap: 0;\n margin: 0;\n padding: 0;\n list-style: none;\n min-width: 0;\n}\n\n/* ─── Item ────────────────────────────────────────────────────────────────── */\n\n.item {\n display: flex;\n align-items: center;\n min-width: 0;\n}\n\n/* ─── Separator ───────────────────────────────────────────────────────────── */\n\n.separator {\n display: flex;\n align-items: center;\n flex-shrink: 0;\n color: var(--gnome-window-fg-color, rgba(0, 0, 0, 0.35));\n opacity: 0.5;\n margin: 0 1px;\n}\n\n/* ─── Ancestor segment (button) ───────────────────────────────────────────── */\n\n.segment {\n display: inline-flex;\n align-items: center;\n gap: 4px;\n padding: 2px 6px;\n border-radius: var(--gnome-radius-sm, 6px);\n border: none;\n background: transparent;\n font-family: inherit;\n font-size: var(--gnome-font-size-body, 1rem);\n color: var(--gnome-window-fg-color, rgba(0, 0, 0, 0.8));\n cursor: pointer;\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n max-width: 160px;\n transition: background-color var(--gnome-duration-fast, 100ms) var(--gnome-easing-default, ease);\n}\n\n.segment:hover {\n background-color: var(--gnome-hover-overlay, rgba(0, 0, 0, 0.06));\n}\n\n.segment:active {\n background-color: var(--gnome-active-overlay, rgba(0, 0, 0, 0.12));\n}\n\n.segment: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, 2px))\n var(--gnome-focus-ring-color, #3584e4);\n}\n\n/* ─── Current segment (non-interactive) ──────────────────────────────────── */\n\n.current {\n display: inline-flex;\n align-items: center;\n gap: 4px;\n padding: 2px 6px;\n font-family: inherit;\n font-size: var(--gnome-font-size-body, 1rem);\n font-weight: var(--gnome-font-weight-semibold, 600);\n color: var(--gnome-window-fg-color, rgba(0, 0, 0, 0.8));\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n max-width: 200px;\n}\n\n/* ─── Icon ────────────────────────────────────────────────────────────────── */\n\n.icon {\n display: flex;\n align-items: center;\n flex-shrink: 0;\n}\n\n/* ─── Reduced motion ──────────────────────────────────────────────────────── */\n\n@media (prefers-reduced-motion: reduce) {\n .segment {\n transition: none;\n }\n}\n\n/* ─── Dark mode ───────────────────────────────────────────────────────────── */\n\n@media (prefers-color-scheme: dark) {\n .segment:hover {\n background-color: rgba(255, 255, 255, 0.08);\n }\n\n .segment:active {\n background-color: rgba(255, 255, 255, 0.14);\n }\n}\n","import { type HTMLAttributes, type ReactNode } from \"react\";\nimport styles from \"./PathBar.module.css\";\n\nexport interface PathBarSegment {\n /** Display label for this path segment. */\n label: string;\n /** Opaque path value passed to `onNavigate` when the segment is clicked. */\n path: string;\n /** Optional icon placed before the label (e.g. a folder icon). */\n icon?: ReactNode;\n}\n\nexport interface PathBarProps extends HTMLAttributes<HTMLElement> {\n /**\n * Ordered path segments from root to current location.\n * The last segment is the current folder and is rendered non-interactive.\n */\n segments: PathBarSegment[];\n /**\n * Called when the user clicks on a non-current segment.\n * Receives the `path` and zero-based `index` of the clicked segment.\n */\n onNavigate?: (path: string, index: number) => void;\n}\n\n/**\n * Breadcrumb path bar for navigating a hierarchical location.\n *\n * Mirrors the location bar in GNOME Files (Nautilus). Segments are separated\n * by chevron dividers. All segments except the last are interactive — clicking\n * them calls `onNavigate`. The last segment represents the current location and\n * is rendered as a static label.\n *\n * @see https://developer.gnome.org/hig/patterns/nav/search.html\n */\nexport function PathBar({ segments, onNavigate, className, ...props }: PathBarProps) {\n return (\n <nav\n aria-label=\"Breadcrumb\"\n className={[styles.pathBar, className].filter(Boolean).join(\" \")}\n {...props}\n >\n <ol className={styles.list} aria-label=\"Path segments\">\n {segments.map((segment, index) => {\n const isCurrent = index === segments.length - 1;\n return (\n <li key={segment.path} className={styles.item}>\n {/* Separator chevron (not before the first segment) */}\n {index > 0 && (\n <span className={styles.separator} aria-hidden=\"true\">\n <svg width=\"12\" height=\"12\" viewBox=\"0 0 12 12\" focusable=\"false\">\n <path\n d=\"M4 2l4 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 )}\n\n {isCurrent ? (\n /* Current folder — non-interactive */\n <span\n className={styles.current}\n aria-current=\"page\"\n >\n {segment.icon && (\n <span className={styles.icon} aria-hidden=\"true\">{segment.icon}</span>\n )}\n {segment.label}\n </span>\n ) : (\n /* Ancestor folder — clickable */\n <button\n type=\"button\"\n className={styles.segment}\n onClick={() => onNavigate?.(segment.path, index)}\n >\n {segment.icon && (\n <span className={styles.icon} aria-hidden=\"true\">{segment.icon}</span>\n )}\n {segment.label}\n </button>\n )}\n </li>\n );\n })}\n </ol>\n </nav>\n );\n}\n",".wrapper {\n position: relative;\n display: inline-flex;\n flex-direction: column;\n gap: var(--gnome-space-1, 6px);\n overflow-x: auto;\n max-width: 100%;\n}\n\n.svg {\n display: block;\n flex-shrink: 0;\n}\n\n.label {\n font-family: var(--gnome-font-family, system-ui);\n font-size: 11px;\n fill: var(--gnome-window-fg-color, rgba(0, 0, 0, 0.8));\n opacity: 0.6;\n user-select: none;\n}\n\n.cell {\n cursor: default;\n transition: transform var(--gnome-transition-fast, 100ms) ease;\n transform-box: fill-box;\n transform-origin: center;\n outline: none;\n}\n\n.cell:not([aria-disabled=\"true\"]):hover {\n transform: scale(1.2);\n}\n\n.cell:not([aria-disabled=\"true\"]) {\n cursor: pointer;\n}\n\n.cell:focus-visible {\n outline: 2px solid var(--gnome-accent-color, #3584e4);\n outline-offset: 1px;\n}\n\n@media (prefers-reduced-motion: reduce) {\n .cell {\n transition: none;\n }\n}\n\n@media (prefers-contrast: more) {\n .cell {\n stroke: var(--gnome-window-fg-color, rgba(0, 0, 0, 0.8));\n stroke-width: 0.5;\n stroke-opacity: 0.3;\n }\n}\n\n.tooltip {\n position: absolute;\n transform: translate(-50%, calc(-100% - 6px));\n background: var(--gnome-popover-bg-color, #fff);\n border: 1px solid var(--gnome-light-3, #deddda);\n border-radius: var(--gnome-radius-md, 8px);\n padding: 4px 8px;\n font-family: var(--gnome-font-family, system-ui);\n font-size: 12px;\n color: var(--gnome-window-fg-color, rgba(0, 0, 0, 0.8));\n box-shadow: var(--gnome-shadow-sm, 0 1px 3px rgba(0, 0, 0, 0.12));\n pointer-events: none;\n white-space: nowrap;\n z-index: 10;\n}\n\n.legend {\n display: flex;\n align-items: center;\n justify-content: flex-end;\n gap: var(--gnome-space-1, 6px);\n}\n\n.legendLabel {\n font-family: var(--gnome-font-family, system-ui);\n font-size: 11px;\n color: var(--gnome-window-fg-color, rgba(0, 0, 0, 0.8));\n opacity: 0.6;\n user-select: none;\n}\n\n.legendCell {\n display: block;\n flex-shrink: 0;\n}\n","import { useMemo, useRef, useState } from \"react\";\nimport type { KeyboardEvent } from \"react\";\nimport styles from \"./ContributionGraph.module.css\";\n\nexport interface ContributionDay {\n /** ISO 8601 date — \"YYYY-MM-DD\". */\n date: string;\n /** Non-negative activity count. */\n count: number;\n}\n\nexport interface ContributionGraphProps {\n /** Activity data. Days absent from the array are treated as count = 0. */\n data: ContributionDay[];\n /**\n * Number of intensity levels (excluding 0).\n * @default 4\n */\n maxLevel?: number;\n /**\n * Colour scale — length must be maxLevel + 1 (index 0 = empty).\n * Defaults to the Adwaita green palette.\n */\n colorScale?: string[];\n /** Cell side length in pixels. @default 12 */\n cellSize?: number;\n /** Gap between cells in pixels. @default 3 */\n cellGap?: number;\n /** 0 = Sunday · 1 = Monday (GNOME locale default). @default 1 */\n weekStartDay?: 0 | 1;\n /** @default true */\n showMonthLabels?: boolean;\n /** @default true */\n showDayLabels?: boolean;\n /** @default true */\n showLegend?: boolean;\n /** Number of week columns to display. @default 52 */\n weeks?: number;\n /** @default \"Contribution graph\" */\n ariaLabel?: string;\n onDayClick?: (day: ContributionDay) => void;\n /** Returns a plain-text tooltip string for a day. */\n tooltipContent?: (day: ContributionDay) => string;\n className?: string;\n}\n\nconst SHORT_MONTHS = [\n \"Jan\", \"Feb\", \"Mar\", \"Apr\", \"May\", \"Jun\",\n \"Jul\", \"Aug\", \"Sep\", \"Oct\", \"Nov\", \"Dec\",\n];\n// 0=Sun … 6=Sat\nconst SHORT_DAYS = [\"Sun\", \"Mon\", \"Tue\", \"Wed\", \"Thu\", \"Fri\", \"Sat\"];\n\nconst DAY_LABEL_WIDTH = 28;\nconst MONTH_LABEL_HEIGHT = 20;\n\nconst DEFAULT_COLORS = [\n \"var(--gnome-card-shade-color, rgba(0,0,0,0.07))\",\n \"var(--gnome-green-1, #8ff0a4)\",\n \"var(--gnome-green-2, #57e389)\",\n \"var(--gnome-green-4, #2ec27e)\",\n \"var(--gnome-green-5, #26a269)\",\n];\n\nfunction dateToIso(date: Date): string {\n const y = date.getFullYear();\n const m = String(date.getMonth() + 1).padStart(2, \"0\");\n const d = String(date.getDate()).padStart(2, \"0\");\n return `${y}-${m}-${d}`;\n}\n\nfunction isoToLocal(iso: string): Date {\n const [y, m, d] = iso.split(\"-\").map(Number);\n return new Date(y, m - 1, d);\n}\n\nfunction fullDateLabel(iso: string): string {\n return isoToLocal(iso).toLocaleDateString(\"en-US\", {\n weekday: \"long\",\n year: \"numeric\",\n month: \"long\",\n day: \"numeric\",\n });\n}\n\ninterface GridCell {\n iso: string;\n count: number;\n level: number;\n future: boolean;\n}\n\nexport function ContributionGraph({\n data,\n maxLevel = 4,\n colorScale,\n cellSize = 12,\n cellGap = 3,\n weekStartDay = 1,\n showMonthLabels = true,\n showDayLabels = true,\n showLegend = true,\n weeks = 52,\n ariaLabel = \"Contribution graph\",\n onDayClick,\n tooltipContent,\n className,\n}: ContributionGraphProps) {\n const stride = cellSize + cellGap;\n const svgRef = useRef<SVGSVGElement>(null);\n const wrapperRef = useRef<HTMLDivElement>(null);\n const colors = colorScale ?? DEFAULT_COLORS;\n\n const [focusedCell, setFocusedCell] = useState({ col: 0, row: 0 });\n const [tooltip, setTooltip] = useState<{\n x: number;\n y: number;\n text: string;\n } | null>(null);\n\n const countMap = useMemo(() => {\n const map = new Map<string, number>();\n for (const d of data) map.set(d.date, d.count);\n return map;\n }, [data]);\n\n const maxCount = useMemo(\n () => Math.max(1, ...data.map((d) => d.count)),\n [data],\n );\n\n const { grid, monthLabels } = useMemo(() => {\n const today = new Date();\n today.setHours(0, 0, 0, 0);\n\n // Align to start of current week\n const daysFromWeekStart = (today.getDay() - weekStartDay + 7) % 7;\n const lastWeekStart = new Date(today);\n lastWeekStart.setDate(today.getDate() - daysFromWeekStart);\n\n const firstDay = new Date(lastWeekStart);\n firstDay.setDate(lastWeekStart.getDate() - (weeks - 1) * 7);\n\n const gridResult: GridCell[][] = [];\n const labels: { col: number; month: string }[] = [];\n let lastMonth = -1;\n\n for (let col = 0; col < weeks; col++) {\n const colStart = new Date(firstDay);\n colStart.setDate(firstDay.getDate() + col * 7);\n\n // Month label when the column starts a new month\n if (colStart.getMonth() !== lastMonth) {\n labels.push({ col, month: SHORT_MONTHS[colStart.getMonth()] });\n lastMonth = colStart.getMonth();\n }\n\n const column: GridCell[] = [];\n for (let row = 0; row < 7; row++) {\n const date = new Date(firstDay);\n date.setDate(firstDay.getDate() + col * 7 + row);\n const future = date > today;\n const iso = dateToIso(date);\n const count = future ? 0 : (countMap.get(iso) ?? 0);\n const level =\n count === 0\n ? 0\n : Math.min(maxLevel, Math.ceil((count / maxCount) * maxLevel));\n column.push({ iso, count, level, future });\n }\n gridResult.push(column);\n }\n\n return { grid: gridResult, monthLabels: labels };\n }, [countMap, maxCount, maxLevel, weeks, weekStartDay]);\n\n // Always label Mon(1), Wed(3), Fri(5) — rows shift with weekStartDay\n const labelRows = useMemo(\n () =>\n [1, 3, 5].map((dayIndex) => ({\n row: (dayIndex - weekStartDay + 7) % 7,\n label: SHORT_DAYS[dayIndex],\n })),\n [weekStartDay],\n );\n\n const dayLabelW = showDayLabels ? DAY_LABEL_WIDTH : 0;\n const monthLabelH = showMonthLabels ? MONTH_LABEL_HEIGHT : 0;\n const svgWidth = dayLabelW + weeks * stride - cellGap;\n const svgHeight = monthLabelH + 7 * stride - cellGap;\n const cellRx = Math.min(4, Math.floor(cellSize / 3));\n\n function focusCell(col: number, row: number) {\n const c = Math.max(0, Math.min(weeks - 1, col));\n const r = Math.max(0, Math.min(6, row));\n setFocusedCell({ col: c, row: r });\n svgRef.current\n ?.querySelector<SVGRectElement>(`[data-col=\"${c}\"][data-row=\"${r}\"]`)\n ?.focus();\n }\n\n function handleCellKey(e: KeyboardEvent, col: number, row: number) {\n const moves: Record<string, [number, number]> = {\n ArrowRight: [col + 1, row],\n ArrowLeft: [col - 1, row],\n ArrowDown: [col, row + 1],\n ArrowUp: [col, row - 1],\n };\n if (moves[e.key]) {\n e.preventDefault();\n focusCell(...moves[e.key]);\n } else if (e.key === \"Enter\" || e.key === \" \") {\n e.preventDefault();\n const cell = grid[col]?.[row];\n if (cell && !cell.future) onDayClick?.({ date: cell.iso, count: cell.count });\n }\n }\n\n function handleMouseEnter(\n e: React.MouseEvent<SVGRectElement>,\n text: string,\n ) {\n const wrapper = wrapperRef.current;\n if (!wrapper) return;\n const wRect = wrapper.getBoundingClientRect();\n const rRect = (e.currentTarget as Element).getBoundingClientRect();\n setTooltip({\n x: rRect.left - wRect.left + cellSize / 2,\n y: rRect.top - wRect.top,\n text,\n });\n }\n\n function cellColor(cell: GridCell): string {\n if (cell.future) return colors[0];\n return colors[Math.min(cell.level, colors.length - 1)] ?? colors[colors.length - 1];\n }\n\n function cellTooltip(cell: GridCell): string {\n if (cell.future) return fullDateLabel(cell.iso);\n return (\n tooltipContent?.({ date: cell.iso, count: cell.count }) ??\n `${cell.count} contribution${cell.count !== 1 ? \"s\" : \"\"} on ${fullDateLabel(cell.iso)}`\n );\n }\n\n return (\n <div\n ref={wrapperRef}\n className={[styles.wrapper, className].filter(Boolean).join(\" \")}\n >\n <svg\n ref={svgRef}\n width={svgWidth}\n height={svgHeight}\n className={styles.svg}\n aria-label={ariaLabel}\n role=\"img\"\n >\n {/* Month labels */}\n {showMonthLabels &&\n monthLabels.map(({ col, month }) => (\n <text\n key={`m-${col}`}\n x={dayLabelW + col * stride}\n y={12}\n className={styles.label}\n >\n {month}\n </text>\n ))}\n\n {/* Day-of-week labels: Mon, Wed, Fri */}\n {showDayLabels &&\n labelRows.map(({ row, label }) => (\n <text\n key={`d-${row}`}\n x={0}\n y={monthLabelH + row * stride + cellSize - 1}\n className={styles.label}\n >\n {label}\n </text>\n ))}\n\n {/* Activity grid */}\n <g role=\"grid\" aria-label={ariaLabel}>\n {grid.map((column, col) => (\n <g key={col} role=\"row\">\n {column.map((cell, row) => {\n const tipText = cellTooltip(cell);\n const isFocused =\n focusedCell.col === col && focusedCell.row === row;\n\n return (\n <rect\n key={`${col}-${row}`}\n data-col={col}\n data-row={row}\n x={dayLabelW + col * stride}\n y={monthLabelH + row * stride}\n width={cellSize}\n height={cellSize}\n rx={cellRx}\n fill={cellColor(cell)}\n opacity={cell.future ? 0.35 : 1}\n className={styles.cell}\n role=\"gridcell\"\n aria-label={tipText}\n aria-disabled={cell.future || undefined}\n tabIndex={isFocused ? 0 : -1}\n onClick={() => !cell.future && onDayClick?.({ date: cell.iso, count: cell.count })}\n onKeyDown={(e) => handleCellKey(e, col, row)}\n onFocus={() => setFocusedCell({ col, row })}\n onMouseEnter={(e) => handleMouseEnter(e, tipText)}\n onMouseLeave={() => setTooltip(null)}\n />\n );\n })}\n </g>\n ))}\n </g>\n </svg>\n\n {tooltip && (\n <div\n className={styles.tooltip}\n style={{ left: tooltip.x, top: tooltip.y }}\n role=\"tooltip\"\n >\n {tooltip.text}\n </div>\n )}\n\n {showLegend && (\n <div className={styles.legend}>\n <span className={styles.legendLabel}>Less</span>\n {Array.from({ length: maxLevel + 1 }, (_, i) => (\n <svg\n key={i}\n width={cellSize}\n height={cellSize}\n aria-hidden=\"true\"\n className={styles.legendCell}\n >\n <rect\n width={cellSize}\n height={cellSize}\n rx={cellRx}\n fill={colors[Math.min(i, colors.length - 1)]}\n />\n </svg>\n ))}\n <span className={styles.legendLabel}>More</span>\n </div>\n )}\n </div>\n );\n}\n","/* ─── Outer container (border + radius, no overflow to preserve sticky) ────── */\n\n.outer {\n border: 1px solid var(--gnome-divider-color, rgba(0, 0, 0, 0.15));\n border-radius: var(--gnome-radius-lg, 12px);\n background: var(--gnome-card-bg-color, #ffffff);\n /* overflow:clip clips to border-radius without creating a scroll ctx */\n overflow: clip;\n}\n\n/* ─── Scroll container ──────────────────────────────────────────────────────── */\n\n.scroll {\n overflow-y: auto;\n width: 100%;\n}\n\n/* ─── Table ─────────────────────────────────────────────────────────────────── */\n\n.table {\n width: 100%;\n border-collapse: collapse;\n table-layout: auto;\n}\n\n/* ─── Header ────────────────────────────────────────────────────────────────── */\n\n.thead {\n position: sticky;\n top: 0;\n z-index: 2;\n background: var(--gnome-card-bg-color, #ffffff);\n /* Separator below sticky header */\n box-shadow: 0 1px 0 var(--gnome-divider-color, rgba(0, 0, 0, 0.15));\n}\n\n.th {\n padding: 0 var(--gnome-space-4, 24px);\n height: 40px;\n text-align: start;\n font-family: var(--gnome-font-family, system-ui);\n font-size: var(--gnome-font-size-caption, 0.75rem);\n font-weight: 700;\n white-space: nowrap;\n vertical-align: middle;\n}\n\n.headerLabel {\n color: var(--gnome-window-fg-color, rgba(0, 0, 0, 0.8));\n opacity: 0.55;\n}\n\n/* ─── Sort button ───────────────────────────────────────────────────────────── */\n\n.sortBtn {\n display: inline-flex;\n align-items: center;\n gap: 4px;\n appearance: none;\n background: none;\n border: none;\n padding: 0;\n cursor: pointer;\n font: inherit;\n font-size: var(--gnome-font-size-caption, 0.75rem);\n font-weight: 700;\n color: var(--gnome-window-fg-color, rgba(0, 0, 0, 0.8));\n opacity: 0.55;\n outline: none;\n border-radius: var(--gnome-radius-sm, 4px);\n transition: opacity var(--gnome-transition-fast, 100ms) ease;\n}\n\n.sortBtn:hover {\n opacity: 0.8;\n}\n\n.sortBtn:focus-visible {\n outline: 2px solid var(--gnome-accent-color, #3584e4);\n outline-offset: 2px;\n opacity: 0.8;\n}\n\n.sortBtnActive {\n opacity: 1;\n color: var(--gnome-accent-color, #3584e4);\n}\n\n.sortActive {\n color: var(--gnome-accent-color, #3584e4);\n}\n\n.sortNeutral {\n color: var(--gnome-window-fg-color, rgba(0, 0, 0, 0.8));\n}\n\n/* ─── Body ──────────────────────────────────────────────────────────────────── */\n\n.tbody {\n color: var(--gnome-card-fg-color, rgba(0, 0, 0, 0.8));\n}\n\n/* ─── Row ───────────────────────────────────────────────────────────────────── */\n\n.row {\n outline: none;\n transition: background-color var(--gnome-transition-fast, 100ms) ease;\n}\n\n.row:focus-visible {\n box-shadow: inset 0 0 0 2px var(--gnome-accent-color, #3584e4);\n}\n\n.rowSelectable {\n cursor: pointer;\n}\n\n.rowSelectable:hover {\n background-color: var(--gnome-hover-overlay, rgba(0, 0, 0, 0.07));\n}\n\n.rowSelectable:active {\n background-color: var(--gnome-active-overlay, rgba(0, 0, 0, 0.12));\n}\n\n.rowSelected {\n background-color: color-mix(\n in srgb,\n var(--gnome-accent-color, #3584e4) 15%,\n transparent\n );\n}\n\n.rowSelected:hover {\n background-color: color-mix(\n in srgb,\n var(--gnome-accent-color, #3584e4) 22%,\n transparent\n );\n}\n\n/* ─── Cell ──────────────────────────────────────────────────────────────────── */\n\n.td {\n padding: var(--gnome-space-2, 12px) var(--gnome-space-4, 24px);\n font-family: var(--gnome-font-family, system-ui);\n font-size: var(--gnome-font-size-body, 1rem);\n vertical-align: middle;\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n}\n\n/* ─── Alignment ─────────────────────────────────────────────────────────────── */\n\n.alignStart {\n text-align: start;\n}\n\n.alignCenter {\n text-align: center;\n}\n\n.alignEnd {\n text-align: end;\n}\n\n/* ─── Checkbox column ───────────────────────────────────────────────────────── */\n\n.checkboxCol {\n width: 48px;\n padding-inline: var(--gnome-space-3, 18px);\n text-align: center;\n}\n\n.checkbox {\n width: 16px;\n height: 16px;\n cursor: pointer;\n accent-color: var(--gnome-accent-color, #3584e4);\n vertical-align: middle;\n}\n\n/* ─── Empty state ───────────────────────────────────────────────────────────── */\n\n.emptyCell {\n padding: var(--gnome-space-6, 36px) var(--gnome-space-4, 24px);\n text-align: center;\n}\n\n.emptyLabel {\n font-family: var(--gnome-font-family, system-ui);\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.55;\n}\n\n/* ─── High contrast ─────────────────────────────────────────────────────────── */\n\n@media (prefers-contrast: more) {\n .outer {\n border-width: 2px;\n }\n\n .row + .row .td {\n border-top: 1px solid var(--gnome-divider-color, rgba(0, 0, 0, 0.15));\n }\n\n .sortBtn {\n text-decoration: underline;\n }\n}\n","import { type ReactNode, useRef, useState } from \"react\";\nimport styles from \"./ColumnView.module.css\";\n\nexport interface ColumnDef<TRow> {\n id: string;\n header: ReactNode;\n cell: (row: TRow, index: number) => ReactNode;\n sortable?: boolean;\n /** CSS width value — e.g. \"200px\", \"30%\". */\n width?: string;\n align?: \"start\" | \"center\" | \"end\";\n}\n\nexport type SortDirection = \"asc\" | \"desc\";\n\nexport interface ColumnViewSortState {\n columnId: string;\n direction: SortDirection;\n}\n\nexport type ColumnViewSelectionMode = \"none\" | \"single\" | \"multiple\";\n\nexport interface ColumnViewProps<TRow extends object = Record<string, unknown>> {\n columns: ColumnDef<TRow>[];\n rows: TRow[];\n /** Extract a stable unique key per row. */\n rowKey: (row: TRow) => string | number;\n /** @default \"none\" */\n selectionMode?: ColumnViewSelectionMode;\n /** Controlled set of selected row keys. */\n selectedRows?: (string | number)[];\n onSelectionChange?: (keys: (string | number)[]) => void;\n /** Controlled sort state. */\n sortState?: ColumnViewSortState;\n onSort?: (columnId: string, direction: SortDirection) => void;\n /** Constrains height and enables vertical scroll. */\n height?: string | number;\n /** Rendered when `rows` is empty. */\n emptyState?: ReactNode;\n className?: string;\n ariaLabel?: string;\n}\n\nconst ALIGN: Record<string, string> = {\n start: styles.alignStart,\n center: styles.alignCenter,\n end: styles.alignEnd,\n};\n\nfunction SortChevron({ direction }: { direction: SortDirection | null }) {\n return (\n <svg\n width=\"10\"\n height=\"10\"\n viewBox=\"0 0 10 10\"\n aria-hidden=\"true\"\n className={direction ? styles.sortActive : styles.sortNeutral}\n >\n {direction === \"asc\" ? (\n <path d=\"M5 2 L9 8 L1 8 Z\" fill=\"currentColor\" />\n ) : direction === \"desc\" ? (\n <path d=\"M5 8 L1 2 L9 2 Z\" fill=\"currentColor\" />\n ) : (\n <>\n <path d=\"M5 1 L8 5 L2 5 Z\" fill=\"currentColor\" opacity=\"0.4\" />\n <path d=\"M5 9 L2 5 L8 5 Z\" fill=\"currentColor\" opacity=\"0.4\" />\n </>\n )}\n </svg>\n );\n}\n\nexport function ColumnView<TRow extends object = Record<string, unknown>>({\n columns,\n rows,\n rowKey,\n selectionMode = \"none\",\n selectedRows = [],\n onSelectionChange,\n sortState,\n onSort,\n height,\n emptyState,\n className,\n ariaLabel,\n}: ColumnViewProps<TRow>) {\n const tbodyRef = useRef<HTMLTableSectionElement>(null);\n const [focusedIndex, setFocusedIndex] = useState(0);\n const selectedSet = new Set(selectedRows);\n const hasCheckbox = selectionMode === \"multiple\";\n const colSpan = columns.length + (hasCheckbox ? 1 : 0);\n\n function handleSort(colId: string) {\n if (!onSort) return;\n const next: SortDirection =\n sortState?.columnId === colId && sortState.direction === \"asc\"\n ? \"desc\"\n : \"asc\";\n onSort(colId, next);\n }\n\n function toggleRow(key: string | number) {\n if (selectionMode === \"none\" || !onSelectionChange) return;\n if (selectionMode === \"single\") {\n onSelectionChange(selectedSet.has(key) ? [] : [key]);\n } else {\n const next = new Set(selectedSet);\n next.has(key) ? next.delete(key) : next.add(key);\n onSelectionChange([...next]);\n }\n }\n\n function handleRowKey(\n e: React.KeyboardEvent,\n index: number,\n key: string | number,\n ) {\n const rows_els =\n tbodyRef.current?.querySelectorAll<HTMLTableRowElement>(\"tr\") ?? [];\n if (e.key === \"ArrowDown\") {\n e.preventDefault();\n const next = Math.min(rows.length - 1, index + 1);\n setFocusedIndex(next);\n rows_els[next]?.focus();\n } else if (e.key === \"ArrowUp\") {\n e.preventDefault();\n const prev = Math.max(0, index - 1);\n setFocusedIndex(prev);\n rows_els[prev]?.focus();\n } else if (e.key === \" \" || e.key === \"Enter\") {\n e.preventDefault();\n toggleRow(key);\n }\n }\n\n function handleSelectAll(checked: boolean) {\n if (!onSelectionChange) return;\n onSelectionChange(checked ? rows.map(rowKey) : []);\n }\n\n const containerStyle: React.CSSProperties = height\n ? { height: typeof height === \"number\" ? `${height}px` : height }\n : {};\n\n return (\n <div className={[styles.outer, className].filter(Boolean).join(\" \")}>\n <div className={styles.scroll} style={containerStyle}>\n <table\n className={styles.table}\n role=\"grid\"\n aria-label={ariaLabel}\n aria-multiselectable={selectionMode === \"multiple\" || undefined}\n >\n <thead className={styles.thead}>\n <tr>\n {hasCheckbox && (\n <th className={[styles.th, styles.checkboxCol].join(\" \")}>\n <input\n type=\"checkbox\"\n className={styles.checkbox}\n checked={\n rows.length > 0 && selectedRows.length === rows.length\n }\n ref={(el) => {\n if (el)\n el.indeterminate =\n selectedRows.length > 0 &&\n selectedRows.length < rows.length;\n }}\n onChange={(e) => handleSelectAll(e.target.checked)}\n aria-label=\"Select all rows\"\n />\n </th>\n )}\n {columns.map((col) => {\n const isSorted = sortState?.columnId === col.id;\n return (\n <th\n key={col.id}\n className={[styles.th, col.align ? ALIGN[col.align] : null]\n .filter(Boolean)\n .join(\" \")}\n style={col.width ? { width: col.width } : undefined}\n aria-sort={\n isSorted\n ? sortState!.direction === \"asc\"\n ? \"ascending\"\n : \"descending\"\n : col.sortable\n ? \"none\"\n : undefined\n }\n >\n {col.sortable && onSort ? (\n <button\n type=\"button\"\n className={[\n styles.sortBtn,\n isSorted ? styles.sortBtnActive : null,\n ]\n .filter(Boolean)\n .join(\" \")}\n onClick={() => handleSort(col.id)}\n >\n <span>{col.header}</span>\n <SortChevron\n direction={isSorted ? sortState!.direction : null}\n />\n </button>\n ) : (\n <span className={styles.headerLabel}>{col.header}</span>\n )}\n </th>\n );\n })}\n </tr>\n </thead>\n\n <tbody ref={tbodyRef} className={styles.tbody}>\n {rows.length === 0 ? (\n <tr>\n <td colSpan={colSpan} className={styles.emptyCell}>\n {emptyState ?? (\n <span className={styles.emptyLabel}>No items</span>\n )}\n </td>\n </tr>\n ) : (\n rows.map((row, index) => {\n const key = rowKey(row);\n const isSelected = selectedSet.has(key);\n return (\n <tr\n key={key}\n className={[\n styles.row,\n isSelected ? styles.rowSelected : null,\n selectionMode !== \"none\" ? styles.rowSelectable : null,\n ]\n .filter(Boolean)\n .join(\" \")}\n role=\"row\"\n aria-selected={\n selectionMode !== \"none\" ? isSelected : undefined\n }\n tabIndex={focusedIndex === index ? 0 : -1}\n onClick={() => toggleRow(key)}\n onKeyDown={(e) => handleRowKey(e, index, key)}\n onFocus={() => setFocusedIndex(index)}\n >\n {hasCheckbox && (\n <td className={[styles.td, styles.checkboxCol].join(\" \")}>\n <input\n type=\"checkbox\"\n className={styles.checkbox}\n checked={isSelected}\n onChange={() => toggleRow(key)}\n onClick={(e) => e.stopPropagation()}\n tabIndex={-1}\n aria-label={`Select row ${index + 1}`}\n />\n </td>\n )}\n {columns.map((col) => (\n <td\n key={col.id}\n className={[\n styles.td,\n col.align ? ALIGN[col.align] : null,\n ]\n .filter(Boolean)\n .join(\" \")}\n role=\"gridcell\"\n >\n {col.cell(row, index)}\n </td>\n ))}\n </tr>\n );\n })\n )}\n </tbody>\n </table>\n </div>\n </div>\n );\n}\n","/* ─── Container ──────────────────────────────────────────────────────────────── */\n\n.terminal {\n background-color: var(--gnome-dark-4, #241f31);\n color: var(--gnome-view-fg-color, rgba(255, 255, 255, 0.87));\n border-radius: var(--gnome-radius-default, 6px);\n overflow-y: auto;\n max-height: 400px;\n padding: 12px 16px;\n font-size: 0.875rem;\n line-height: 1.6;\n\n /* Adwaita-style thin scrollbar */\n scrollbar-width: thin;\n scrollbar-color: rgba(255, 255, 255, 0.2) transparent;\n}\n\n.pre {\n margin: 0;\n font-family: var(--gnome-font-family-mono, \"Adwaita Mono\", \"Source Code Pro\", monospace);\n white-space: pre-wrap;\n word-break: break-all;\n}\n\n.line {\n min-height: 1em;\n}\n\n/* ─── Light theme ────────────────────────────────────────────────────────────── */\n\n@media (prefers-color-scheme: light) {\n .terminal {\n background-color: var(--gnome-light-2, #f6f5f4);\n color: var(--gnome-window-fg-color, rgba(0, 0, 0, 0.8));\n scrollbar-color: rgba(0, 0, 0, 0.2) transparent;\n }\n}\n\n/* ─── Variants ───────────────────────────────────────────────────────────────── */\n\n.success .line {\n color: var(--gnome-success-color, #33d17a);\n}\n\n.warning .line {\n color: var(--gnome-warning-color, #f6d32d);\n}\n\n.destructive .line {\n color: var(--gnome-destructive-color, #f66151);\n}\n","import {\n useEffect,\n useRef,\n type HTMLAttributes,\n} from \"react\";\nimport styles from \"./TerminalView.module.css\";\n\nexport type TerminalVariant = \"default\" | \"success\" | \"warning\" | \"destructive\";\n\nexport interface TerminalViewProps extends HTMLAttributes<HTMLDivElement> {\n /** Lines of text to display. */\n lines: string[];\n /** Colorize text based on semantic meaning. Defaults to `\"default\"`. */\n variant?: TerminalVariant;\n /** Maximum number of lines to keep in the buffer. Defaults to `500`. */\n maxLines?: number;\n /** Automatically scroll to the latest line when content changes. Defaults to `true`. */\n autoScroll?: boolean;\n}\n\nexport function TerminalView({\n lines,\n variant = \"default\",\n maxLines = 500,\n autoScroll = true,\n className,\n ...props\n}: TerminalViewProps) {\n const bottomRef = useRef<HTMLDivElement>(null);\n const visibleLines = lines.slice(-maxLines);\n\n useEffect(() => {\n if (autoScroll) {\n bottomRef.current?.scrollIntoView({ block: \"nearest\" });\n }\n }, [visibleLines.length, autoScroll]);\n\n return (\n <div\n className={[styles.terminal, styles[variant], className]\n .filter(Boolean)\n .join(\" \")}\n {...props}\n >\n <pre className={styles.pre}>\n {visibleLines.map((line, i) => (\n <div key={i} className={styles.line}>\n {line}\n </div>\n ))}\n <div ref={bottomRef} />\n </pre>\n </div>\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.cjs","names":[],"sources":["../src/components/Blockquote/Blockquote.module.css","../src/components/Blockquote/Blockquote.tsx","../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/CountDownTimer/CountDownTimer.module.css","../src/components/CountDownTimer/CountDownTimer.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/dialogUtils.ts","../src/components/Dialog/Dialog.module.css","../src/components/Dialog/Dialog.tsx","../src/components/AboutDialog/AboutDialog.module.css","../src/components/AboutDialog/AboutDialog.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/Box/Box.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","../src/components/Timeline/Timeline.module.css","../src/components/Timeline/Timeline.tsx","../src/components/PathBar/PathBar.module.css","../src/components/PathBar/PathBar.tsx","../src/components/ContributionGraph/ContributionGraph.module.css","../src/components/ContributionGraph/ContributionGraph.tsx","../src/components/ColumnView/ColumnView.module.css","../src/components/ColumnView/ColumnView.tsx","../src/components/TerminalView/TerminalView.module.css","../src/components/TerminalView/TerminalView.tsx","../src/components/StatusBadge/StatusBadge.module.css","../src/components/StatusBadge/StatusBadge.tsx"],"sourcesContent":["/* ─── Base ────────────────────────────────────────────────────────────────── */\n\n.blockquote {\n margin: 0;\n padding: var(--gnome-space-2, 12px) var(--gnome-space-3, 18px);\n border-left: var(--gnome-blockquote-border-width, 3px) solid;\n border-radius: 0 var(--gnome-radius-sm, 4px) var(--gnome-radius-sm, 4px) 0;\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/* ─── Body (icon + text) ──────────────────────────────────────────────────── */\n\n.body {\n display: flex;\n align-items: flex-start;\n gap: var(--gnome-space-2, 12px);\n}\n\n.icon {\n display: flex;\n align-items: center;\n flex-shrink: 0;\n margin-top: 2px; /* optical alignment with first text baseline */\n}\n\n.content {\n margin: 0;\n flex: 1;\n min-width: 0;\n}\n\n/* ─── Attribution ─────────────────────────────────────────────────────────── */\n\n.footer {\n margin-top: var(--gnome-space-1, 6px);\n font-size: var(--gnome-font-size-caption, 0.75rem);\n opacity: var(--gnome-opacity-dim, 0.55);\n}\n\n.cite {\n font-style: normal;\n}\n\n/* ─── Variants ────────────────────────────────────────────────────────────── */\n\n.default {\n border-color: var(--gnome-border-subtle, rgba(0, 0, 0, 0.15));\n}\n\n.info {\n border-color: var(--gnome-accent-bg-color, #3584e4);\n background-color: color-mix(\n in srgb,\n var(--gnome-accent-bg-color, #3584e4) 8%,\n transparent\n );\n}\n\n.warning {\n border-color: var(--gnome-warning-bg-color, #f6d32d);\n background-color: color-mix(\n in srgb,\n var(--gnome-warning-bg-color, #f6d32d) 14%,\n transparent\n );\n}\n\n.error {\n border-color: var(--gnome-error-bg-color, #e01b24);\n background-color: color-mix(\n in srgb,\n var(--gnome-error-bg-color, #e01b24) 8%,\n transparent\n );\n}\n\n.success {\n border-color: var(--gnome-success-bg-color, #2ec27e);\n background-color: color-mix(\n in srgb,\n var(--gnome-success-bg-color, #2ec27e) 8%,\n transparent\n );\n}\n","import type { HTMLAttributes, ReactNode } from \"react\";\nimport styles from \"./Blockquote.module.css\";\n\n// ─── Types ────────────────────────────────────────────────────────────────────\n\nexport type BlockquoteVariant = \"default\" | \"info\" | \"warning\" | \"error\" | \"success\";\n\nexport interface BlockquoteProps extends HTMLAttributes<HTMLQuoteElement> {\n /**\n * Visual emphasis style.\n * - `\"default\"` — neutral left border; use for general quotations.\n * - `\"info\"` — accent blue; use for tips and noteworthy passages.\n * - `\"warning\"` — yellow; use for cautionary content.\n * - `\"error\"` — red; use for critical or deprecated content.\n * - `\"success\"` — green; use for positive or approved content.\n */\n variant?: BlockquoteVariant;\n /**\n * Optional icon displayed to the left of the quoted text.\n * Typically a 16 px `<Icon>` component.\n */\n icon?: ReactNode;\n /**\n * Attribution rendered below the quote — author name, source title, etc.\n * Rendered inside a `<footer><cite>` for semantic correctness.\n */\n cite?: ReactNode;\n /** The quoted content. */\n children: ReactNode;\n}\n\n// ─── Component ────────────────────────────────────────────────────────────────\n\n/**\n * Styled pull-quote with semantic `<blockquote>` markup.\n *\n * Supports five visual variants and optional icon + attribution.\n *\n * ```tsx\n * <Blockquote variant=\"info\" cite=\"Ada Lovelace, 1842\">\n * The Analytical Engine has no pretensions to originate anything.\n * </Blockquote>\n *\n * <Blockquote variant=\"warning\" icon={<Icon icon={Warning} size={16} />}>\n * This API is deprecated and will be removed in v3.\n * </Blockquote>\n * ```\n */\nexport function Blockquote({\n variant = \"default\",\n icon,\n cite,\n children,\n className,\n ...props\n}: BlockquoteProps) {\n return (\n <blockquote\n className={[styles.blockquote, styles[variant], className]\n .filter(Boolean)\n .join(\" \")}\n {...props}\n >\n <div className={styles.body}>\n {icon && (\n <span className={styles.icon} aria-hidden=\"true\">\n {icon}\n </span>\n )}\n <p className={styles.content}>{children}</p>\n </div>\n\n {cite !== undefined && (\n <footer className={styles.footer}>\n <cite className={styles.cite}>{cite}</cite>\n </footer>\n )}\n </blockquote>\n );\n}\n",".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 transition: width var(--gnome-duration-normal) var(--gnome-easing-default);\n will-change: width;\n}\n\n/* ─── Variants ─────────────────────────────────────────────────────────────── */\n\n.accent { background-color: var(--gnome-accent-bg-color, #3584e4); }\n.success { background-color: var(--gnome-success-bg-color, #2ec27e); }\n.warning { background-color: var(--gnome-warning-bg-color, #f6d32d); }\n.error { background-color: var(--gnome-error-bg-color, #e01b24); }\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 type ProgressBarVariant = \"accent\" | \"success\" | \"warning\" | \"error\";\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 /**\n * Fill color of the progress indicator.\n * - `\"accent\"` (default) — blue, general-purpose progress.\n * - `\"success\"` — green, completed or healthy state.\n * - `\"warning\"` — yellow, approaching a limit.\n * - `\"error\"` — red, failed or critical state.\n */\n variant?: ProgressBarVariant;\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 variant = \"accent\",\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 styles[variant],\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","/* ─── Base container ──────────────────────────────────────────────────────── */\n\n.countdownTimer {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n gap: 0.5rem;\n\n padding: 0.5rem 1rem;\n border-radius: var(--gnome-radius-default, 6px);\n background-color: var(--gnome-view-bg-color, #f6f5f4);\n\n font-family: var(--gnome-font-family);\n font-size: 1rem;\n font-weight: var(--gnome-font-weight-medium, 500);\n line-height: 1.5;\n letter-spacing: 0.02em;\n\n user-select: none;\n transition: all 200ms ease-out;\n}\n\n/* ─── Value display ────────────────────────────────────────────────────────── */\n\n.value {\n font-family: \"Fira Mono\", monospace;\n font-size: 1.125rem;\n font-weight: 600;\n letter-spacing: 0.025em;\n}\n\n/* ─── Variant: accent (default) ────────────────────────────────────────────── */\n\n.accent {\n background-color: rgba(53, 132, 228, 0.1);\n color: #3584e4;\n border: 1px solid rgba(53, 132, 228, 0.2);\n}\n\n.accent .value {\n color: #3584e4;\n}\n\n/* ─── Variant: success ─────────────────────────────────────────────────────── */\n\n.success {\n background-color: rgba(51, 209, 122, 0.1);\n color: #33d17a;\n border: 1px solid rgba(51, 209, 122, 0.2);\n}\n\n.success .value {\n color: #33d17a;\n}\n\n/* ─── Variant: warning ─────────────────────────────────────────────────────── */\n\n.warning {\n background-color: rgba(255, 190, 0, 0.1);\n color: #f8e45c;\n border: 1px solid rgba(255, 190, 0, 0.2);\n}\n\n.warning .value {\n color: #f8e45c;\n}\n\n/* ─── Variant: destructive ────────────────────────────────────────────────── */\n\n.destructive {\n background-color: rgba(225, 59, 59, 0.1);\n color: #e13b3b;\n border: 1px solid rgba(225, 59, 59, 0.2);\n}\n\n.destructive .value {\n color: #e13b3b;\n}\n\n/* ─── Finished state ───────────────────────────────────────────────────────── */\n\n.finished {\n opacity: 0.7;\n}\n\n@media (prefers-color-scheme: dark) {\n .countdownTimer {\n background-color: var(--gnome-view-bg-color, #3d3846);\n }\n\n .accent {\n background-color: rgba(53, 132, 228, 0.15);\n border-color: rgba(53, 132, 228, 0.3);\n }\n\n .success {\n background-color: rgba(51, 209, 122, 0.15);\n border-color: rgba(51, 209, 122, 0.3);\n }\n\n .warning {\n background-color: rgba(255, 190, 0, 0.15);\n border-color: rgba(255, 190, 0, 0.3);\n }\n\n .destructive {\n background-color: rgba(225, 59, 59, 0.15);\n border-color: rgba(225, 59, 59, 0.3);\n }\n}\n","import { useEffect, useState } from \"react\";\nimport type { HTMLAttributes } from \"react\";\nimport styles from \"./CountDownTimer.module.css\";\n\nexport type CountDownVariant = \"accent\" | \"destructive\" | \"success\" | \"warning\";\n\nexport interface CountDownTimerProps extends HTMLAttributes<HTMLDivElement> {\n /** Start date for the countdown */\n start: Date;\n /** End date for the countdown */\n end: Date;\n /** Format to display: \"date\", \"time\", or \"datetime\" */\n format?: \"date\" | \"time\" | \"datetime\";\n /** Visual style variant. Defaults to `\"accent\"`. */\n variant?: CountDownVariant;\n /** Callback action to execute when countdown reaches zero */\n action?: () => void;\n}\n\ninterface TimeRemaining {\n days: number;\n hours: number;\n minutes: number;\n seconds: number;\n isFinished: boolean;\n}\n\n/**\n * CountDown timer component that displays remaining time and executes an action when time runs out.\n *\n * @see https://developer.gnome.org/hig/patterns/\n */\nexport function CountDownTimer({\n start,\n end,\n format = \"time\",\n variant = \"accent\",\n action,\n className,\n ...props\n}: CountDownTimerProps) {\n const [timeRemaining, setTimeRemaining] = useState<TimeRemaining | null>(null);\n const [hasExecutedAction, setHasExecutedAction] = useState(false);\n\n useEffect(() => {\n const calculateTimeRemaining = (): TimeRemaining => {\n const now = new Date();\n const endTime = new Date(end).getTime();\n const nowTime = now.getTime();\n const difference = endTime - nowTime;\n\n if (difference <= 0) {\n return {\n days: 0,\n hours: 0,\n minutes: 0,\n seconds: 0,\n isFinished: true,\n };\n }\n\n const days = Math.floor(difference / (1000 * 60 * 60 * 24));\n const hours = Math.floor((difference / (1000 * 60 * 60)) % 24);\n const minutes = Math.floor((difference / 1000 / 60) % 60);\n const seconds = Math.floor((difference / 1000) % 60);\n\n return {\n days,\n hours,\n minutes,\n seconds,\n isFinished: false,\n };\n };\n\n // Initial calculation\n setTimeRemaining(calculateTimeRemaining());\n\n // Update every second\n const interval = setInterval(() => {\n const remaining = calculateTimeRemaining();\n setTimeRemaining(remaining);\n\n // Execute action when countdown finishes\n if (remaining.isFinished && !hasExecutedAction) {\n action?.();\n setHasExecutedAction(true);\n }\n }, 1000);\n\n return () => clearInterval(interval);\n }, [end, action, hasExecutedAction]);\n\n if (!timeRemaining) {\n return null;\n }\n\n const formatTime = (): string => {\n if (format === \"date\") {\n const endDate = new Date(end);\n return endDate.toLocaleDateString();\n }\n\n const { days, hours, minutes, seconds, isFinished } = timeRemaining;\n\n if (isFinished) {\n return \"00:00:00\";\n }\n\n if (format === \"datetime\") {\n const endDate = new Date(end);\n const dateString = endDate.toLocaleDateString();\n const timeString = `${String(hours).padStart(2, \"0\")}:${String(minutes).padStart(2, \"0\")}:${String(seconds).padStart(2, \"0\")}`;\n return `${dateString} ${timeString}`;\n }\n\n if (days > 0) {\n return `${days}d ${String(hours).padStart(2, \"0\")}h ${String(minutes).padStart(2, \"0\")}m`;\n }\n\n return `${String(hours).padStart(2, \"0\")}:${String(minutes).padStart(2, \"0\")}:${String(seconds).padStart(2, \"0\")}`;\n };\n\n const displayVariant = timeRemaining.isFinished ? \"destructive\" : variant;\n\n return (\n <div\n className={[\n styles.countdownTimer,\n styles[displayVariant],\n timeRemaining.isFinished ? styles.finished : null,\n className,\n ]\n .filter(Boolean)\n .join(\" \")}\n {...props}\n >\n <span className={styles.value}>{formatTime()}</span>\n </div>\n );\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 2a3 3 0 1 0 0 6 3 3 0 0 0 0-6zM2 14c0-3.314 2.686-5 6-5s6 1.686 6 5z\",\n\t\tfillRule: \"evenodd\"\n\t}]\n}, F = {\n\tviewBox: \"0 0 16 16\",\n\tpaths: [{ d: \"M8 1a1.5 1.5 0 1 1 0 3 1.5 1.5 0 0 1 0-3zM3.5 5.5C5 6.2 6.5 6.5 8 6.5s3-.3 4.5-1l-.5 1.5-4 .5-4-.5z\" }, { d: \"M5.5 7.5 4 13l2.5-1.5L8 14l1.5-2.5L12 13l-1.5-5.5\" }]\n}, I = {\n\tviewBox: \"0 0 16 16\",\n\tpaths: [{ d: \"M1 1h6v6H1zm8 0h6v6H9zM1 9h6v6H1zm8 0h6v6H9z\" }]\n}, L = {\n\tviewBox: \"0 0 16 16\",\n\tpaths: [{ d: \"M8 1a5 5 0 0 0-5 5v3L1.5 11.5h13L13 9V6a5 5 0 0 0-5-5zm-1.5 12a1.5 1.5 0 0 0 3 0z\" }]\n}, R = {\n\tviewBox: \"0 0 16 16\",\n\tpaths: [{ d: \"M8 1a5 5 0 0 0-5 5v4a5 5 0 0 0 10 0V6a5 5 0 0 0-5-5zm-1 1.1V7H4.05A4 4 0 0 1 7 2.1zM9 2.1A4 4 0 0 1 11.95 7H9V2.1z\" }]\n}, z = {\n\tviewBox: \"0 0 16 16\",\n\tpaths: [{ d: \"M1 4.5A1.5 1.5 0 0 1 2.5 3h11A1.5 1.5 0 0 1 15 4.5v7A1.5 1.5 0 0 1 13.5 13h-11A1.5 1.5 0 0 1 1 11.5v-7zM3 6h2v2H3zm3.5 0h2v2h-2zm3.5 0h2v2h-2zM5 9h6v2H5z\" }]\n}, B = {\n\tviewBox: \"0 0 16 16\",\n\tpaths: [{ d: \"M3 1.5A1.5 1.5 0 0 1 4.5 0h7A1.5 1.5 0 0 1 13 1.5v13a1.5 1.5 0 0 1-1.5 1.5h-7A1.5 1.5 0 0 1 3 14.5zm5 11.5a1 1 0 1 0 0 2 1 1 0 0 0 0-2z\" }]\n}, V = {\n\tviewBox: \"0 0 16 16\",\n\tpaths: [{ d: \"M8 2a6 6 0 1 0 0 12A6 6 0 0 0 8 2zM4 7a1.5 1.5 0 1 1 3 0 1.5 1.5 0 0 1-3 0zm5.5-1.5a1.5 1.5 0 1 1 0 3 1.5 1.5 0 0 1 0-3zM6.5 10a1.5 1.5 0 1 1 3 0 1.5 1.5 0 0 1-3 0z\" }]\n}, H = {\n\tviewBox: \"0 0 16 16\",\n\tpaths: [{ d: \"M4 1h8v4H4zM2 6.5A1.5 1.5 0 0 1 3.5 5h9A1.5 1.5 0 0 1 14 6.5v4a1.5 1.5 0 0 1-1.5 1.5H12v-2H4v2H2.5A1.5 1.5 0 0 1 2 10.5zM4 11h8v4H4z\" }]\n}, U = {\n\tviewBox: \"0 0 16 16\",\n\tpaths: [{ d: \"M8 1a3 3 0 0 0-3 3v2H3.5A1.5 1.5 0 0 0 2 7.5v7A1.5 1.5 0 0 0 3.5 16h9a1.5 1.5 0 0 0 1.5-1.5v-7A1.5 1.5 0 0 0 12.5 6H11V4a3 3 0 0 0-3-3zm0 9a1.5 1.5 0 1 1 0 3 1.5 1.5 0 0 1 0-3z\" }]\n}, W = {\n\tviewBox: \"0 0 16 16\",\n\tpaths: [{\n\t\td: \"M1 3a1 1 0 0 0-1 1v8a1 1 0 0 0 1 1h14a1 1 0 0 0 1-1V5a1 1 0 0 0-1-1H7.5L6.2 3H1z\",\n\t\tfillRule: \"evenodd\"\n\t}]\n}, G = {\n\tviewBox: \"0 0 16 16\",\n\tpaths: [{\n\t\td: \"M3 1a1 1 0 0 0-1 1v12a1 1 0 0 0 1 1h10a1 1 0 0 0 1-1V5l-4-4H3zm0 1h6v3a1 1 0 0 0 1 1h3v8H3V2zm7 .5L12.5 5H10V2.5z\",\n\t\tfillRule: \"evenodd\"\n\t}]\n}, K = {\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 1h14v10H1V3zm9.5 1a1.5 1.5 0 1 0 0 3 1.5 1.5 0 0 0 0-3zM3 11l3-4 2.5 3L11 7l3 4H3z\",\n\t\tfillRule: \"evenodd\"\n\t}]\n}, q = {\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}, J = {\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}, Y = {\n\tviewBox: \"0 0 16 16\",\n\tpaths: [{ d: \"M8 13.5S2 9.5 2 5.5a3.5 3.5 0 0 1 6-2.45A3.5 3.5 0 0 1 14 5.5c0 4-6 8-6 8z\" }]\n}, X = {\n\tviewBox: \"0 0 16 16\",\n\tpaths: [{\n\t\td: \"M4 2v12l10-6z\",\n\t\tfillRule: \"evenodd\"\n\t}]\n}, Z = {\n\tviewBox: \"0 0 16 16\",\n\tpaths: [{ d: \"M3 2h3v12H3zm7 0h3v12h-3z\" }]\n}, Q = {\n\tviewBox: \"0 0 16 16\",\n\tpaths: [{ d: \"M3 2v12l8-6-8-6zm8 0h2v12h-2z\" }]\n}, $ = {\n\tviewBox: \"0 0 16 16\",\n\tpaths: [{ d: \"M13 2v12l-8-6 8-6zm-8 0H3v12h2z\" }]\n}, ee = {\n\tviewBox: \"0 0 16 16\",\n\tpaths: [{\n\t\td: \"M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59.4.07.55-.17.55-.38 0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82A7.65 7.65 0 0 1 8 3.8c.68 0 1.36.09 2 .27 1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 2.2 0 .21.15.46.55.38A8.01 8.01 0 0 0 16 8c0-4.42-3.58-8-8-8z\",\n\t\tfillRule: \"evenodd\"\n\t}]\n}, te = {\n\tviewBox: \"0 0 16 16\",\n\tpaths: [{\n\t\td: \"M15.97 9.058 14.87 5.716l-1.07-3.295a.293.293 0 0 0-.557 0L12.174 5.72H3.826L2.757 2.421a.293.293 0 0 0-.557 0L1.13 5.717.03 9.058a.59.59 0 0 0 .214.66L8 15.07l7.756-5.352a.59.59 0 0 0 .214-.66z\",\n\t\tfillRule: \"evenodd\"\n\t}]\n}, ne = {\n\tviewBox: \"0 0 16 16\",\n\tpaths: [{\n\t\td: \"M.778 0A.778.778 0 0 0 0 .802l2.189 13.54a.777.777 0 0 0 .766.658h10.09a.583.583 0 0 0 .58-.496L15.824.806A.778.778 0 0 0 15.048 0zm9.12 9.81H6.116L5.37 6.19h5.27z\",\n\t\tfillRule: \"evenodd\"\n\t}]\n}, re = {\n\tviewBox: \"0 0 16 16\",\n\tpaths: [{ d: \"M9.294 6.928 14.357 1h-1.2L8.762 6.147 5.25 1H1l5.31 7.728L1 15h1.2l4.642-5.396L10.75 15H15zM7.41 8.79l-.538-.77L2.64 1.91h1.843l3.454 4.94.538.77 4.49 6.42h-1.843z\" }]\n};\n//#endregion\nexport { F as Accessibility, c as Add, I as Applications, C as Attachment, ne as Bitbucket, N as Check, y as Close, V as ColorManagement, f as Copy, m as Cut, u as Delete, G as Document, v as DocumentOpen, d as Edit, M as Error, W as Folder, ee as GitHub, te as GitLab, n as GoHome, t as GoNext, e as GoPrevious, r as GoUp, Y as Heart, K as Image, A as Information, z as InputKeyboard, R as InputMouse, B as InputTablet, U as Lock, Z as MediaPause, X as MediaPlay, $ as MediaSkipBackward, Q as MediaSkipForward, L as Notifications, w as OpenMenu, i as PanDown, s as PanEnd, o as PanStart, a as PanUp, p as Paste, P as Person, H as Printer, g as Redo, x as Refresh, l as Remove, _ as Save, b as Search, k as Settings, S as Share, q as Star, J as StarOutline, h as Undo, O as ViewConceal, T as ViewMore, D as ViewReveal, E as ViewSidebar, j as Warning, re as X };\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/* ─── Suggestions popover ───────────────────────────────────────────────────── */\n\n.suggestionsPanel {\n position: fixed;\n z-index: 10200;\n margin: 0;\n padding: var(--gnome-space-1, 6px);\n list-style: none;\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-top: none;\n border-radius: 0 0 var(--gnome-radius-lg, 12px) 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 max-height: 280px;\n overflow-y: auto;\n\n font-family: var(--gnome-font-family);\n font-size: var(--gnome-font-size-body, 1rem);\n}\n\n.suggestionsLoading {\n display: flex;\n justify-content: center;\n padding: var(--gnome-space-2, 12px);\n}\n\n.suggestionItem {\n padding: var(--gnome-space-1, 6px) var(--gnome-space-2, 12px);\n border-radius: var(--gnome-radius-sm, 6px);\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.suggestionItem:hover,\n.suggestionItemActive {\n background-color: color-mix(in srgb, var(--gnome-accent-color, #3584e4) 12%, transparent);\n}\n\n.suggestionItemActive {\n color: var(--gnome-accent-color, #3584e4);\n}\n\n@media (prefers-color-scheme: dark) {\n .suggestionsPanel {\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\n@media (prefers-reduced-motion: reduce) {\n .suggestionItem {\n transition: none;\n }\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 useId,\n useState,\n type KeyboardEvent,\n type InputHTMLAttributes,\n type ReactNode,\n} from \"react\";\nimport { createPortal } from \"react-dom\";\nimport { Search, Close } from \"@gnome-ui/icons\";\nimport { Icon } from \"../Icon\";\nimport { Spinner } from \"../Spinner\";\nimport styles from \"./SearchBar.module.css\";\n\nexport interface Suggestion {\n id: string;\n label: string;\n}\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 * Mirrors the `.inline` style class.\n */\n inline?: boolean;\n /** Suggestion items to show in the autocomplete popover. */\n suggestions?: Suggestion[];\n /** Called when the user selects a suggestion. */\n onSuggestionSelect?: (item: Suggestion) => void;\n /** Shows a spinner inside the suggestions popover while fetching async results. */\n loadingSuggestions?: boolean;\n /** Custom row renderer for each suggestion. */\n renderSuggestion?: (item: Suggestion) => ReactNode;\n /** `aria-label` for the suggestions listbox. Defaults to `\"Suggestions\"`. */\n suggestionsLabel?: string;\n}\n\ninterface DropdownPos {\n top: number;\n left: number;\n width: number;\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 * - Optional `suggestions` / `loadingSuggestions` props show an autocomplete\n * popover anchored below the bar — follows the GTK4 pattern of\n * `AdwSearchBar` + `GtkPopover` + `GtkListView` (replaces `GtkEntryCompletion`).\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 suggestions,\n onSuggestionSelect,\n loadingSuggestions = false,\n renderSuggestion,\n suggestionsLabel = \"Suggestions\",\n ...props\n}: SearchBarProps) {\n const inputRef = useRef<HTMLInputElement>(null);\n const wrapperRef = useRef<HTMLDivElement>(null);\n const listboxRef = useRef<HTMLUListElement>(null);\n\n const listboxId = useId();\n const optionIdPrefix = useId();\n\n const [activeIndex, setActiveIndex] = useState(-1);\n const [dropdownPos, setDropdownPos] = useState<DropdownPos | null>(null);\n\n const hasSuggestions = (suggestions?.length ?? 0) > 0;\n const showDropdown = open && (loadingSuggestions || hasSuggestions);\n\n // Reset active index whenever the suggestion list changes\n useEffect(() => { setActiveIndex(-1); }, [suggestions]);\n\n // Auto-focus when opening\n useEffect(() => {\n if (open) {\n const id = requestAnimationFrame(() => inputRef.current?.focus());\n return () => cancelAnimationFrame(id);\n }\n }, [open]);\n\n // Position the suggestions dropdown below the wrapper\n useEffect(() => {\n if (!showDropdown || !wrapperRef.current) {\n setDropdownPos(null);\n return;\n }\n function updatePos() {\n if (!wrapperRef.current) return;\n const r = wrapperRef.current.getBoundingClientRect();\n setDropdownPos({ top: r.bottom, left: r.left, width: r.width });\n }\n updatePos();\n window.addEventListener(\"scroll\", updatePos, { passive: true, capture: true });\n window.addEventListener(\"resize\", updatePos, { passive: true });\n return () => {\n window.removeEventListener(\"scroll\", updatePos, { capture: true });\n window.removeEventListener(\"resize\", updatePos);\n };\n }, [showDropdown]);\n\n // Scroll the active option into view\n useEffect(() => {\n if (activeIndex < 0 || !listboxRef.current) return;\n (listboxRef.current.children[activeIndex] as HTMLElement | undefined)\n ?.scrollIntoView({ block: \"nearest\" });\n }, [activeIndex]);\n\n const handleKeyDown = useCallback(\n (e: KeyboardEvent<HTMLInputElement>) => {\n if (showDropdown && hasSuggestions && suggestions) {\n if (e.key === \"ArrowDown\") {\n e.preventDefault();\n setActiveIndex((i) => Math.min(i + 1, suggestions.length - 1));\n return;\n }\n if (e.key === \"ArrowUp\") {\n e.preventDefault();\n setActiveIndex((i) => Math.max(i - 1, -1));\n return;\n }\n if (e.key === \"Enter\" && activeIndex >= 0) {\n e.preventDefault();\n onSuggestionSelect?.(suggestions[activeIndex]);\n return;\n }\n }\n if (e.key === \"Escape\") {\n e.preventDefault();\n onClose?.();\n }\n props.onKeyDown?.(e);\n },\n [onClose, props, showDropdown, hasSuggestions, suggestions, activeIndex, onSuggestionSelect]\n );\n\n const hasValue = value !== undefined && value !== \"\";\n const activeOptionId = activeIndex >= 0 && suggestions?.[activeIndex]\n ? `${optionIdPrefix}-${suggestions[activeIndex].id}`\n : undefined;\n\n const dropdown = showDropdown && dropdownPos\n ? createPortal(\n <ul\n ref={listboxRef}\n role=\"listbox\"\n aria-label={suggestionsLabel}\n id={listboxId}\n className={styles.suggestionsPanel}\n style={{ top: dropdownPos.top, left: dropdownPos.left, width: dropdownPos.width }}\n onMouseDown={(e) => e.preventDefault()} // keep focus on input\n >\n {loadingSuggestions && (\n <li role=\"presentation\" className={styles.suggestionsLoading}>\n <Spinner size=\"sm\" label=\"Loading suggestions…\" />\n </li>\n )}\n {!loadingSuggestions &&\n suggestions?.map((item, index) => (\n <li\n key={item.id}\n id={`${optionIdPrefix}-${item.id}`}\n role=\"option\"\n aria-selected={index === activeIndex}\n className={[\n styles.suggestionItem,\n index === activeIndex ? styles.suggestionItemActive : null,\n ]\n .filter(Boolean)\n .join(\" \")}\n onMouseEnter={() => setActiveIndex(index)}\n onMouseLeave={() => setActiveIndex(-1)}\n onClick={() => onSuggestionSelect?.(item)}\n >\n {renderSuggestion ? renderSuggestion(item) : item.label}\n </li>\n ))}\n </ul>,\n document.body\n )\n : null;\n\n return (\n <>\n <div\n ref={wrapperRef}\n className={[styles.wrapper, open ? styles.open : null, className]\n .filter(Boolean)\n .join(\" \")}\n aria-hidden={!open}\n >\n <div\n className={[\n styles.bar,\n inline ? styles.inline : null,\n disabled ? styles.disabled : null,\n ]\n .filter(Boolean)\n .join(\" \")}\n >\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=\"combobox\"\n aria-autocomplete=\"list\"\n aria-expanded={showDropdown}\n aria-controls={showDropdown ? listboxId : undefined}\n aria-activedescendant={activeOptionId}\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 {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 && <div className={styles.filterRow}>{children}</div>}\n </div>\n\n {typeof document !== \"undefined\" && dropdown}\n </>\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/* ─── Section header (title row, optionally a button when collapsible) ──────── */\n\n.sectionHeader {\n display: flex;\n align-items: center;\n gap: var(--gnome-space-1, 6px);\n width: 100%;\n margin: 4px 0 2px;\n padding: 2px var(--gnome-space-1, 6px);\n border-radius: var(--gnome-radius-sm, 4px);\n background: transparent;\n border: none;\n font: inherit;\n text-align: start;\n cursor: default;\n}\n\nbutton.sectionHeader {\n cursor: pointer;\n transition: background-color var(--gnome-duration-fast) var(--gnome-easing-default);\n}\n\nbutton.sectionHeader:hover {\n background-color: var(--gnome-hover-overlay);\n}\n\nbutton.sectionHeader:active {\n background-color: var(--gnome-active-overlay);\n}\n\nbutton.sectionHeader:focus-visible {\n outline: none;\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.sectionHeaderIcon {\n display: flex;\n align-items: center;\n flex-shrink: 0;\n opacity: 0.7;\n}\n\n.sectionTitle {\n flex: 1;\n min-width: 0;\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 white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n}\n\n.sectionChevron {\n display: flex;\n align-items: center;\n flex-shrink: 0;\n margin-left: auto;\n opacity: 0.5;\n}\n\n/* ─── Collapsible section body ──────────────────────────────────────────────── */\n\n.sectionBody {\n display: grid;\n grid-template-rows: 1fr;\n transition: grid-template-rows 200ms var(--gnome-easing-default, ease);\n}\n\n.sectionBodyCollapsed {\n grid-template-rows: 0fr;\n}\n\n.sectionBodyInner {\n overflow: hidden;\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/* ─── Variants ──────────────────────────────────────────────────────────────── */\n\n.variantBlue {\n --gnome-sidebar-bg-color: #1a4a7a;\n --gnome-sidebar-fg-color: rgba(255, 255, 255, 0.95);\n --gnome-hover-overlay: rgba(255, 255, 255, 0.1);\n --gnome-active-overlay: rgba(255, 255, 255, 0.18);\n --gnome-divider-color: rgba(255, 255, 255, 0.15);\n --gnome-headerbar-border-color: rgba(255, 255, 255, 0.15);\n}\n\n.variantGreen {\n --gnome-sidebar-bg-color: #1a4a2e;\n --gnome-sidebar-fg-color: rgba(255, 255, 255, 0.95);\n --gnome-hover-overlay: rgba(255, 255, 255, 0.1);\n --gnome-active-overlay: rgba(255, 255, 255, 0.18);\n --gnome-divider-color: rgba(255, 255, 255, 0.15);\n --gnome-headerbar-border-color: rgba(255, 255, 255, 0.15);\n}\n\n.variantRed {\n --gnome-sidebar-bg-color: #6e1414;\n --gnome-sidebar-fg-color: rgba(255, 255, 255, 0.95);\n --gnome-hover-overlay: rgba(255, 255, 255, 0.1);\n --gnome-active-overlay: rgba(255, 255, 255, 0.18);\n --gnome-divider-color: rgba(255, 255, 255, 0.15);\n --gnome-headerbar-border-color: rgba(255, 255, 255, 0.15);\n}\n\n.variantYellow {\n --gnome-sidebar-bg-color: #5a4500;\n --gnome-sidebar-fg-color: rgba(255, 255, 255, 0.95);\n --gnome-hover-overlay: rgba(255, 255, 255, 0.1);\n --gnome-active-overlay: rgba(255, 255, 255, 0.18);\n --gnome-divider-color: rgba(255, 255, 255, 0.15);\n --gnome-headerbar-border-color: rgba(255, 255, 255, 0.15);\n}\n\n.variantBlack {\n --gnome-sidebar-bg-color: #0a0a0a;\n --gnome-sidebar-fg-color: rgba(255, 255, 255, 0.9);\n --gnome-hover-overlay: rgba(255, 255, 255, 0.08);\n --gnome-active-overlay: rgba(255, 255, 255, 0.14);\n --gnome-divider-color: rgba(255, 255, 255, 0.1);\n --gnome-headerbar-border-color: rgba(255, 255, 255, 0.1);\n}\n\n.variantTransparent {\n --gnome-sidebar-bg-color: transparent;\n border-right-color: var(--gnome-divider-color);\n}\n\n.variantBlurred {\n --gnome-sidebar-bg-color: rgba(255, 255, 255, 0.55);\n border-right-color: rgba(255, 255, 255, 0.25);\n backdrop-filter: blur(20px) saturate(1.8);\n -webkit-backdrop-filter: blur(20px) saturate(1.8);\n}\n\n@media (prefers-color-scheme: dark) {\n .variantBlurred {\n --gnome-sidebar-bg-color: rgba(30, 30, 30, 0.6);\n border-right-color: rgba(255, 255, 255, 0.1);\n }\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 button.sectionHeader,\n .sectionBody {\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// ─── Variant map ──────────────────────────────────────────────────────────────\n\nconst variantClass: Record<string, string> = {\n blue: \"variantBlue\",\n green: \"variantGreen\",\n red: \"variantRed\",\n yellow: \"variantYellow\",\n black: \"variantBlack\",\n transparent: \"variantTransparent\",\n blurred: \"variantBlurred\",\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 * Visual style variant for the sidebar background.\n * - `\"classic\"` — default GNOME gray\n * - `\"blue\"` | `\"green\"` | `\"red\"` | `\"yellow\"` — tinted accent backgrounds\n * - `\"black\"` — dark/OLED background\n * - `\"transparent\"` — no background, inherits parent\n * - `\"blurred\"` — frosted glass (backdrop-filter blur), like macOS\n */\n variant?: \"classic\" | \"blue\" | \"green\" | \"red\" | \"yellow\" | \"black\" | \"transparent\" | \"blurred\";\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 variant = \"classic\",\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 variant !== \"classic\" ? styles[variantClass[variant]] : 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 {\n forwardRef,\n useImperativeHandle,\n useState,\n useContext,\n useId,\n isValidElement,\n Children,\n type HTMLAttributes,\n type ReactNode,\n} from \"react\";\nimport type { IconDefinition } from \"@gnome-ui/icons\";\nimport { PanDown, PanUp } from \"@gnome-ui/icons\";\nimport { Icon } from \"../Icon\";\nimport { useSidebarCollapsed, SidebarFilterContext } from \"./Sidebar\";\nimport styles from \"./Sidebar.module.css\";\n\n// ─── Filter helper ────────────────────────────────────────────────────────────\n\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// ─── Handle ───────────────────────────────────────────────────────────────────\n\nexport interface SidebarSectionHandle {\n expand: () => void;\n collapse: () => void;\n toggle: () => void;\n}\n\n// ─── Props ────────────────────────────────────────────────────────────────────\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 /** Icon rendered left of the title. Same type as `SidebarItem.icon`. */\n icon?: IconDefinition;\n /** Whether the section body can be toggled open/closed. Defaults to `false`. */\n collapsible?: boolean;\n /** Initial open state when `collapsible` is true. Defaults to `true`. */\n defaultOpen?: boolean;\n /** Controlled open state. */\n open?: boolean;\n /** Called when open state changes. */\n onOpenChange?: (open: boolean) => void;\n children?: ReactNode;\n}\n\n// ─── Component ────────────────────────────────────────────────────────────────\n\n/**\n * Named group of `SidebarItem` entries inside a `Sidebar`.\n *\n * Sections are separated by a thin divider. The title and icon are optional.\n * When `collapsible` is true the body can be toggled via the header button or\n * imperatively via a `ref` (`expand`, `collapse`, `toggle`).\n *\n * In rail (icon-only) mode the body is always visible regardless of open state.\n * When a sidebar filter is active and no children match, the section is hidden.\n *\n * @see https://gnome.pages.gitlab.gnome.org/libadwaita/doc/main/class.Sidebar.html\n */\nexport const SidebarSection = forwardRef<SidebarSectionHandle, SidebarSectionProps>(\n function SidebarSection(\n {\n title,\n icon,\n collapsible = false,\n defaultOpen = true,\n open: controlledOpen,\n onOpenChange,\n children,\n className,\n ...props\n },\n ref,\n ) {\n const [internalOpen, setInternalOpen] = useState(defaultOpen);\n const isControlled = controlledOpen !== undefined;\n const open = isControlled ? controlledOpen : internalOpen;\n\n const sidebarCollapsed = useSidebarCollapsed();\n const filterValue = useContext(SidebarFilterContext);\n const bodyId = useId();\n\n const setAndNotify = (next: boolean) => {\n if (!isControlled) setInternalOpen(next);\n onOpenChange?.(next);\n };\n\n useImperativeHandle(ref, () => ({\n expand: () => setAndNotify(true),\n collapse: () => setAndNotify(false),\n toggle: () => setAndNotify(!open),\n }));\n\n // Hide section entirely when filter is active and no children match\n const hasVisibleChildren =\n filterValue.length === 0 || countMatchingItems(children, filterValue) > 0;\n\n if (!hasVisibleChildren) return null;\n\n // In rail mode the body is always visible\n const isOpen = sidebarCollapsed ? true : open;\n\n const showHeader = !!(title || icon || (collapsible && !sidebarCollapsed));\n\n return (\n <section\n className={[styles.section, className].filter(Boolean).join(\" \")}\n {...props}\n >\n {showHeader && (\n collapsible && !sidebarCollapsed ? (\n <button\n type=\"button\"\n className={styles.sectionHeader}\n onClick={() => setAndNotify(!open)}\n aria-expanded={open}\n aria-controls={bodyId}\n >\n {icon && (\n <span className={styles.sectionHeaderIcon}>\n <Icon icon={icon} size=\"sm\" aria-hidden />\n </span>\n )}\n {title && <span className={styles.sectionTitle}>{title}</span>}\n <span className={styles.sectionChevron}>\n <Icon icon={open ? PanUp : PanDown} size=\"sm\" aria-hidden />\n </span>\n </button>\n ) : (\n <div className={styles.sectionHeader}>\n {icon && (\n <span className={styles.sectionHeaderIcon}>\n <Icon icon={icon} size=\"sm\" aria-hidden />\n </span>\n )}\n {title && <span className={styles.sectionTitle}>{title}</span>}\n </div>\n )\n )}\n\n <div\n id={bodyId}\n className={[\n styles.sectionBody,\n !isOpen ? styles.sectionBodyCollapsed : null,\n ]\n .filter(Boolean)\n .join(\" \")}\n aria-hidden={!isOpen || undefined}\n >\n <div className={styles.sectionBodyInner}>\n <ul role=\"list\" className={styles.list}>\n {children}\n </ul>\n </div>\n </div>\n </section>\n );\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}\n\n/* ─── Row wrapper ──────────────────────────────────────────────────────────── */\n\n.item {\n position: relative;\n display: flex;\n flex-direction: column;\n}\n\n.item:first-child > * {\n border-radius: var(--gnome-radius-lg) var(--gnome-radius-lg) 0 0;\n}\n\n.item:last-child > * {\n border-radius: 0 0 var(--gnome-radius-lg) var(--gnome-radius-lg);\n}\n\n.item:only-child > * {\n border-radius: var(--gnome-radius-lg);\n}\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 { Separator } from \"../Separator\";\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 && <Separator 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","import { useState, useEffect, type CSSProperties, type KeyboardEvent, RefObject } from \"react\";\n\nexport const FOCUSABLE =\n 'button:not([disabled]), [href], input:not([disabled]), select:not([disabled]), textarea:not([disabled]), [tabindex]:not([tabindex=\"-1\"])';\n\n/**\n * Returns inline styles that anchor a `position:fixed` backdrop to the visual\n * viewport so the dialog stays centered when the virtual keyboard is open on\n * mobile (iOS Safari / Android Chrome shrink `visualViewport` but not the\n * layout viewport that `position:fixed` is relative to).\n */\nexport function useVisualViewport(): CSSProperties {\n const [style, setStyle] = useState<CSSProperties>({});\n\n useEffect(() => {\n const { visualViewport } = window;\n\n if (!visualViewport) {\n return;\n }\n\n const update = (): void => {\n const { offsetLeft: left, offsetTop: top, width, height } = visualViewport;\n setStyle({\n top,\n left,\n width,\n height,\n });\n };\n\n update();\n visualViewport.addEventListener(\"resize\", update);\n visualViewport.addEventListener(\"scroll\", update);\n\n return () => {\n visualViewport.removeEventListener(\"resize\", update);\n visualViewport.removeEventListener(\"scroll\", update);\n };\n }, []);\n\n return style;\n}\n\nexport function trapFocus(e: KeyboardEvent<HTMLDivElement>, ref: RefObject<HTMLDivElement | null>) {\n if (e.key !== \"Tab\") {\n return;\n }\n\n const focusable = Array.from(ref.current?.querySelectorAll<HTMLElement>(FOCUSABLE) ?? []);\n\n if (!focusable.length) {\n return;\n }\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","/* ─── 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: var(--gnome-dialog-backdrop-color, rgba(0, 0, 0, 0.5));\n backdrop-filter: blur(var(--gnome-dialog-backdrop-blur, 4px));\n -webkit-backdrop-filter: blur(var(--gnome-dialog-backdrop-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);\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.dialog:has(.body) .title {\n padding: var(--gnome-space-4, 24px) var(--gnome-space-4, 24px) 0 !important;\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/* ─── Dark mode ─────────────────────────────────────────────────────────────── */\n\n@media (prefers-color-scheme: dark) {\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 type KeyboardEvent,\n type HTMLAttributes,\n type ReactNode,\n} from \"react\";\nimport { createPortal } from \"react-dom\";\nimport { trapFocus, FOCUSABLE, useVisualViewport } from \"./dialogUtils\";\nimport styles from \"./Dialog.module.css\";\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// ─── 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\n/**\n * Blocking modal dialog following the Adwaita pattern.\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 * For the app-info dialog, use `<AboutDialog />` instead.\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 */\nexport function Dialog({\n open,\n title,\n children,\n buttons = [],\n onClose,\n closeOnBackdrop = true,\n role = \"dialog\",\n responses,\n onResponse,\n className,\n ...props\n}: DialogProps) {\n const dialogRef = useRef<HTMLDivElement>(null);\n const titleId = useId();\n const previouslyFocused = useRef<Element | null>(null);\n\n const isAlert = !!responses;\n const viewportStyle = useVisualViewport();\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 }\n }, [open]);\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 // ── Render buttons (standard or alert) ────────────────────────────────────\n const renderButtons = () => {\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 const node = (\n <div\n className={styles.backdrop}\n style={viewportStyle}\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={[styles.dialog, className].filter(Boolean).join(\" \")}\n onKeyDown={handleKeyDown}\n onClick={(e) => e.stopPropagation()}\n {...props}\n >\n {title && <div id={titleId} className={styles.title}>{title}</div>}\n {children && <div className={styles.body}>{children}</div>}\n {renderButtons()}\n </div>\n </div>\n );\n\n if (typeof document === \"undefined\") return node;\n return createPortal(node, 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: var(--gnome-dialog-backdrop-color, rgba(0, 0, 0, 0.5));\n backdrop-filter: blur(var(--gnome-dialog-backdrop-blur, 4px));\n -webkit-backdrop-filter: blur(var(--gnome-dialog-backdrop-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 max-height: min(640px, 90dvh);\n display: flex;\n flex-direction: column;\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/* ─── Header ────────────────────────────────────────────────────────────────── */\n\n.header {\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.icon {\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.appName {\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.version {\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\n.tabBar {\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.tabBtn {\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.tabBtn:hover { opacity: 1; }\n\n.tabBtnActive {\n opacity: 1;\n font-weight: var(--gnome-font-weight-semibold, 600);\n border-bottom-color: var(--gnome-accent-color, #3584e4);\n}\n\n.tabBtn: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\n.tabContent {\n flex: 1;\n overflow-y: auto;\n}\n\n.section {\n padding: var(--gnome-space-3, 16px) var(--gnome-space-4, 24px);\n}\n\n/* Details */\n.comments {\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.infoList {\n display: grid;\n grid-template-columns: auto 1fr;\n gap: 6px var(--gnome-space-3, 16px);\n margin: 0;\n}\n\n.infoLabel {\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.infoValue {\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.link {\n color: var(--gnome-accent-color, #3584e4);\n text-decoration: none;\n}\n\n.link:hover { text-decoration: underline; }\n\n/* Credits */\n.creditGroup + .creditGroup {\n margin-top: var(--gnome-space-3, 16px);\n}\n\n.creditTitle {\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.creditList {\n list-style: none;\n margin: 0;\n padding: 0;\n display: flex;\n flex-direction: column;\n gap: 2px;\n}\n\n.creditItem {\n font-family: var(--gnome-font-family);\n font-size: var(--gnome-font-size-body, 1rem);\n}\n\n/* Legal */\n.copyright {\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.licenseType {\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.licenseText {\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/* ─── Footer (close button) ─────────────────────────────────────────────────── */\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.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\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 { border-bottom: none; }\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 { outline: none; }\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.btnDefault {\n color: var(--gnome-accent-color, #3584e4);\n}\n\n/* ─── Dark mode ─────────────────────────────────────────────────────────────── */\n\n@media (prefers-color-scheme: dark) {\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 ReactNode,\n} from \"react\";\nimport { createPortal } from \"react-dom\";\nimport { FOCUSABLE, trapFocus, useVisualViewport } from \"../Dialog/dialogUtils\";\nimport styles from \"./AboutDialog.module.css\";\n\nexport interface AboutDialogLink {\n label: string;\n url: string;\n}\n\nexport interface AboutDialogProps {\n /** Whether the dialog is visible. */\n open: boolean;\n /** Called on Escape or close button click. */\n onClose?: () => void;\n /** Application name. */\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\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 * Standard app info dialog with details, credits, and legal tabs.\n * Mirrors `AdwAboutDialog`.\n *\n * @see https://gnome.pages.gitlab.gnome.org/libadwaita/doc/main/class.AboutDialog.html\n */\nexport function AboutDialog({\n open,\n onClose,\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}: AboutDialogProps) {\n const dialogRef = useRef<HTMLDivElement>(null);\n const titleId = useId();\n const previouslyFocused = useRef<Element | null>(null);\n const viewportStyle = useVisualViewport();\n const [activeTab, setActiveTab] = useState<AboutTab>(\"details\");\n\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 setActiveTab(\"details\");\n }\n }, [open]);\n\n const handleKeyDown = useCallback(\n (e: KeyboardEvent<HTMLDivElement>) => {\n if (e.key === \"Escape\") {\n e.preventDefault();\n onClose?.();\n return;\n }\n trapFocus(e, dialogRef);\n },\n [onClose]\n );\n\n if (!open) return null;\n\n const hasCredits = !!(developers?.length || designers?.length || artists?.length);\n const hasLegal = !!(copyright || licenseType || licenseText);\n const tabs = (\n [\"details\", hasCredits && \"credits\", hasLegal && \"legal\"] as (AboutTab | false)[]\n ).filter(Boolean) as AboutTab[];\n\n const node = (\n <div className={styles.backdrop} style={viewportStyle} onClick={onClose} aria-hidden=\"true\">\n <div\n ref={dialogRef}\n role=\"dialog\"\n aria-modal=\"true\"\n aria-labelledby={titleId}\n className={styles.dialog}\n onKeyDown={handleKeyDown}\n onClick={(e) => e.stopPropagation()}\n >\n <div className={styles.header}>\n {applicationIcon && <div className={styles.icon}>{applicationIcon}</div>}\n <div id={titleId} className={styles.appName}>{applicationName}</div>\n {version && <div className={styles.version}>{version}</div>}\n </div>\n\n {tabs.length > 1 && (\n <div className={styles.tabBar} role=\"tablist\">\n {tabs.map((t) => (\n <button\n key={t}\n type=\"button\"\n role=\"tab\"\n aria-selected={activeTab === t}\n className={[styles.tabBtn, activeTab === t ? styles.tabBtnActive : null]\n .filter(Boolean).join(\" \")}\n onClick={() => setActiveTab(t)}\n >\n {ABOUT_TAB_LABEL[t]}\n </button>\n ))}\n </div>\n )}\n\n <div className={styles.tabContent}>\n {activeTab === \"details\" && (\n <div className={styles.section}>\n {comments && <p className={styles.comments}>{comments}</p>}\n {(developerName || website || links?.length) && (\n <dl className={styles.infoList}>\n {developerName && (\n <><dt className={styles.infoLabel}>Developer</dt><dd className={styles.infoValue}>{developerName}</dd></>\n )}\n {website && (\n <><dt className={styles.infoLabel}>Website</dt><dd className={styles.infoValue}>\n <a href={website} target=\"_blank\" rel=\"noopener noreferrer\" className={styles.link}>\n {websiteLabel ?? website}\n </a>\n </dd></>\n )}\n {links?.map((l) => (\n <><dt key={`${l.label}-dt`} className={styles.infoLabel}>{l.label}</dt>\n <dd key={`${l.label}-dd`} className={styles.infoValue}>\n <a href={l.url} target=\"_blank\" rel=\"noopener noreferrer\" className={styles.link}>{l.url}</a>\n </dd></>\n ))}\n </dl>\n )}\n </div>\n )}\n\n {activeTab === \"credits\" && (\n <div className={styles.section}>\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.creditGroup}>\n <h4 className={styles.creditTitle}>{heading}</h4>\n <ul className={styles.creditList}>\n {list!.map((name) => <li key={name} className={styles.creditItem}>{name}</li>)}\n </ul>\n </div>\n ))}\n </div>\n )}\n\n {activeTab === \"legal\" && (\n <div className={styles.section}>\n {copyright && <p className={styles.copyright}>{copyright}</p>}\n {licenseType && <p className={styles.licenseType}>{licenseType}</p>}\n {licenseText && <pre className={styles.licenseText}>{licenseText}</pre>}\n </div>\n )}\n </div>\n\n <div className={styles.footer}>\n <button type=\"button\" className={[styles.btn, styles.btnDefault].join(\" \")} onClick={onClose}>\n Close\n </button>\n </div>\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 /** Arrow center offset in px from the near edge of the panel.\n * Horizontal (left) for top/bottom; vertical (top) for left/right.\n * Undefined means use the default 50% CSS. */\n arrowOffset?: number;\n}\n\nconst GAP = 8;\nconst MARGIN = 10;\nconst ARROW_HALF = 6; // half of the 12 px arrow\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 function calcRaw(p: PopoverPlacement): { top: number; left: number } {\n if (p === \"bottom\") return {\n top: trigger.bottom + GAP,\n left: trigger.left + trigger.width / 2 - popover.width / 2,\n };\n if (p === \"top\") return {\n top: trigger.top - popover.height - GAP,\n left: trigger.left + trigger.width / 2 - popover.width / 2,\n };\n if (p === \"left\") return {\n top: trigger.top + trigger.height / 2 - popover.height / 2,\n left: trigger.left - popover.width - GAP,\n };\n return {\n top: trigger.top + trigger.height / 2 - popover.height / 2,\n left: trigger.right + GAP,\n };\n }\n\n // First pass: find a placement that fits both axes perfectly.\n for (const p of candidates) {\n const { top, left } = calcRaw(p);\n const fitsH = left >= MARGIN && left + popover.width <= vw - MARGIN;\n const fitsV = top >= MARGIN && top + popover.height <= vh - MARGIN;\n if (fitsH && fitsV) {\n // Always use pixel arrowOffset (never rely on CSS left:50%) so the arrow\n // points exactly at the trigger center regardless of sub-pixel rounding.\n const arrowOffset =\n p === \"top\" || p === \"bottom\"\n ? trigger.left + trigger.width / 2 - left\n : trigger.top + trigger.height / 2 - top;\n return { top, left, placement: p, arrowOffset };\n }\n }\n\n // Second pass: fits the primary axis; clamp the secondary axis and\n // shift the arrow so it still points at the trigger center.\n for (const p of candidates) {\n const { top, left } = calcRaw(p);\n const fitsV = top >= MARGIN && top + popover.height <= vh - MARGIN;\n const fitsH = left >= MARGIN && left + popover.width <= vw - MARGIN;\n\n if ((p === \"top\" || p === \"bottom\") && fitsV) {\n const clampedLeft = Math.max(MARGIN, Math.min(left, vw - popover.width - MARGIN));\n const rawOffset = trigger.left + trigger.width / 2 - clampedLeft;\n return {\n top, left: clampedLeft, placement: p,\n arrowOffset: Math.max(ARROW_HALF + 4, Math.min(rawOffset, popover.width - ARROW_HALF - 4)),\n };\n }\n\n if ((p === \"left\" || p === \"right\") && fitsH) {\n const clampedTop = Math.max(MARGIN, Math.min(top, vh - popover.height - MARGIN));\n const rawOffset = trigger.top + trigger.height / 2 - clampedTop;\n return {\n top: clampedTop, left, placement: p,\n arrowOffset: Math.max(ARROW_HALF + 4, Math.min(rawOffset, popover.height - ARROW_HALF - 4)),\n };\n }\n }\n\n // Fallback: clamp bottom placement and shift arrow to match trigger.\n const fbTop = trigger.bottom + GAP;\n const fbLeft = trigger.left + trigger.width / 2 - popover.width / 2;\n const clampedTop = Math.max(MARGIN, Math.min(fbTop, vh - popover.height - MARGIN));\n const clampedLeft = Math.max(MARGIN, Math.min(fbLeft, vw - popover.width - MARGIN));\n const rawArrowOffset = trigger.left + trigger.width / 2 - clampedLeft;\n return {\n top: clampedTop,\n left: clampedLeft,\n placement: \"bottom\",\n arrowOffset: Math.max(ARROW_HALF + 4, Math.min(rawArrowOffset, popover.width - ARROW_HALF - 4)),\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 — offset shifts it to point at the trigger when the panel is clamped */}\n <div\n className={styles.arrow}\n aria-hidden=\"true\"\n style={\n pos?.arrowOffset !== undefined\n ? pos.placement === \"top\" || pos.placement === \"bottom\"\n ? { left: pos.arrowOffset - ARROW_HALF, marginLeft: 0 }\n : { top: pos.arrowOffset - ARROW_HALF, marginTop: 0 }\n : undefined\n }\n />\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 transition: width 200ms var(--gnome-easing-default, ease);\n}\n\n.wide.collapsed .sidebar {\n width: 56px;\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 * When `true`, collapses the sidebar to icon-only width on wide screens.\n * Has no effect in narrow (overlay) mode.\n * Pair with `collapsed` on the inner `<Sidebar>` component to hide labels.\n */\n collapsed?: boolean;\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 collapsed = false,\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 // Swipe-to-dismiss: drag the sidebar in the close direction to trigger onClose\n useEffect(() => {\n if (!isNarrow || !showSidebar) return;\n const el = sidebarRef.current;\n if (!el) return;\n\n let startX = 0;\n const THRESHOLD = 80;\n\n const onTouchStart = (e: TouchEvent) => { startX = e.touches[0].clientX; };\n const onTouchEnd = (e: TouchEvent) => {\n const dx = e.changedTouches[0].clientX - startX;\n const closeSwipe = isEnd ? dx > THRESHOLD : dx < -THRESHOLD;\n if (closeSwipe) onClose?.();\n };\n\n el.addEventListener(\"touchstart\", onTouchStart, { passive: true });\n el.addEventListener(\"touchend\", onTouchEnd, { passive: true });\n return () => {\n el.removeEventListener(\"touchstart\", onTouchStart);\n el.removeEventListener(\"touchend\", onTouchEnd);\n };\n }, [isNarrow, showSidebar, isEnd, onClose]);\n\n return (\n <div\n className={[\n styles.root,\n isNarrow ? styles.narrow : styles.wide,\n !isNarrow && collapsed ? styles.collapsed : null,\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","import type { CSSProperties, HTMLAttributes, ReactNode } from \"react\";\n\n/** GNOME HIG standard spacing values (matches GtkBox spacing tokens). */\nexport type BoxSpacing = 3 | 6 | 12 | 18 | 24 | 32 | 48;\n\nexport type BoxOrientation = \"horizontal\" | \"vertical\";\nexport type BoxAlign = \"start\" | \"center\" | \"end\" | \"stretch\" | \"baseline\";\nexport type BoxJustify =\n | \"start\"\n | \"center\"\n | \"end\"\n | \"space-between\"\n | \"space-around\"\n | \"space-evenly\";\n\nexport interface BoxProps extends HTMLAttributes<HTMLDivElement> {\n /**\n * Direction children are arranged.\n * `\"vertical\"` → `flex-direction: column` (default).\n * `\"horizontal\"` → `flex-direction: row`.\n */\n orientation?: BoxOrientation;\n /**\n * Gap between children in pixels.\n * Accepts any of the GNOME HIG standard spacing values or any CSS string.\n * Defaults to `6` (the HIG \"standard\" inner spacing).\n */\n spacing?: BoxSpacing | number | string;\n /**\n * Cross-axis alignment (`align-items`).\n * Defaults to `\"stretch\"` for vertical, `\"center\"` for horizontal.\n */\n align?: BoxAlign;\n /**\n * Main-axis distribution (`justify-content`).\n * Defaults to `\"start\"`.\n */\n justify?: BoxJustify;\n children?: ReactNode;\n}\n\n/**\n * Fundamental flex layout primitive — the web equivalent of `GtkBox`.\n *\n * Arranges children in a single row or column with consistent spacing\n * following the GNOME Human Interface Guidelines spacing scale:\n *\n * | Token | px | Use |\n * |-------|----|-----|\n * | tight | 3 | Dense UI, icon + label pairs |\n * | standard | 6 | Default inner spacing |\n * | medium | 12 | Between related groups |\n * | large | 18 | Between loosely related sections |\n * | section | 24 | Page-level section gaps |\n * | loose | 32 | Large content separation |\n * | jumbo | 48 | Hero / splash spacing |\n *\n * **Usage:**\n * ```tsx\n * // Vertical section (heading + content)\n * <Box spacing={8}>\n * <Text variant=\"caption-heading\" color=\"dim\">Devices</Text>\n * <BoxedList>…</BoxedList>\n * </Box>\n *\n * // Horizontal icon + label\n * <Box orientation=\"horizontal\" spacing={6} align=\"center\">\n * <Icon name=\"folder\" />\n * <Text>Documents</Text>\n * </Box>\n * ```\n *\n * @see https://developer.gnome.org/hig/guidelines/spacing.html\n */\nexport function Box({\n orientation = \"vertical\",\n spacing = 6,\n align,\n justify = \"start\",\n className,\n style,\n children,\n ...props\n}: BoxProps) {\n const defaultAlign: BoxAlign =\n orientation === \"horizontal\" ? \"center\" : \"stretch\";\n\n const resolvedStyle: CSSProperties = {\n display: \"flex\",\n flexDirection: orientation === \"horizontal\" ? \"row\" : \"column\",\n gap: typeof spacing === \"number\" ? `${spacing}px` : spacing,\n alignItems: align ?? defaultAlign,\n justifyContent: justify,\n ...style,\n };\n\n return (\n <div className={className} style={resolvedStyle} {...props}>\n {children}\n </div>\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: 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: hidden;\n transition: width var(--gnome-duration-normal, 200ms) var(--gnome-easing-default, ease);\n}\n\n.sidebarCollapsed {\n width: 56px;\n}\n\n.sidebarCollapsed .itemBtn {\n justify-content: center;\n padding: 9px;\n}\n\n/* ─── Header / Footer slots ─────────────────────────────────────────────────── */\n\n.header {\n flex-shrink: 0;\n border-bottom: 1px solid var(--gnome-headerbar-border-color, rgba(0, 0, 0, 0.12));\n}\n\n.footer {\n flex-shrink: 0;\n border-top: 1px solid var(--gnome-headerbar-border-color, rgba(0, 0, 0, 0.12));\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 flex: 1;\n overflow-y: auto;\n overflow-x: hidden;\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 .header,\n .footer {\n border-color: rgba(255, 255, 255, 0.08);\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 collapsed: boolean;\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 /**\n * When `true`, items render icon-only (labels hidden) and the sidebar\n * shrinks to its compact width. Used by `AdaptiveLayout` on tablet viewports.\n */\n collapsed?: boolean;\n /** Rendered above the scrollable item list (e.g. user card). */\n header?: ReactNode;\n /** Rendered below the scrollable item list (e.g. collapse toggle). */\n footer?: ReactNode;\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 collapsed = false,\n header,\n footer,\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, collapsed }}>\n <nav\n className={[styles.sidebar, collapsed ? styles.sidebarCollapsed : null, className].filter(Boolean).join(\" \")}\n onKeyDown={handleKeyDown}\n {...props}\n >\n {header && <div className={styles.header}>{header}</div>}\n <ul\n ref={groupRef}\n role=\"radiogroup\"\n aria-label={ariaLabel}\n className={styles.list}\n >\n {children}\n </ul>\n {footer && <div className={styles.footer}>{footer}</div>}\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, collapsed } = 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 {!collapsed && <span className={styles.itemLabel}>{label}</span>}\n {!collapsed && 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/* ─── 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\n/* ─── Reduced motion ────────────────────────────────────────────────────────── */\n\n@media (prefers-reduced-motion: reduce) {\n .panel {\n transition: none;\n }\n\n .chevronWrap {\n transition: none;\n }\n}\n","import { useState, Children, useId, type HTMLAttributes, type ReactNode } from \"react\";\nimport { Separator } from \"../Separator\";\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 <Separator 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/* ─── Variant: pill ─────────────────────────────────────────────────────────── */\n\n.pill {\n background-color: var(--gnome-hover-overlay, rgba(0, 0, 0, 0.06));\n border: none;\n border-radius: var(--gnome-radius-pill, 9999px);\n box-shadow: none;\n padding: 3px;\n gap: 2px;\n}\n\n.pill .item {\n border-radius: var(--gnome-radius-pill, 9999px);\n padding: 5px var(--gnome-space-2, 16px);\n color: var(--gnome-window-fg-color);\n}\n\n.pill .item:hover:not(:disabled):not(.active) {\n background-color: rgba(0, 0, 0, 0.04);\n}\n\n.pill .item:active:not(:disabled):not(.active) {\n background-color: rgba(0, 0, 0, 0.08);\n}\n\n.pill .item:focus-visible {\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, 2px))\n var(--gnome-focus-ring-color, #3584e4);\n}\n\n.pill .active {\n background-color: var(--gnome-card-bg-color, #ffffff);\n box-shadow: var(--gnome-shadow-sm, 0 1px 2px rgba(0, 0, 0, 0.12));\n font-weight: var(--gnome-font-weight-semibold, 600);\n color: var(--gnome-window-fg-color);\n}\n\n@media (prefers-color-scheme: dark) {\n .pill {\n background-color: var(--gnome-hover-overlay, rgba(255, 255, 255, 0.08));\n }\n\n .pill .item:hover:not(:disabled):not(.active) {\n background-color: rgba(255, 255, 255, 0.05);\n }\n\n .pill .item:active:not(:disabled):not(.active) {\n background-color: rgba(255, 255, 255, 0.10);\n }\n\n .pill .active {\n background-color: var(--gnome-card-bg-color, #383838);\n box-shadow: none;\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\" | \"pill\";\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 * - `pill` — segmented-control style; active item appears lifted, no accent color.\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","/* ─── Vertical orientation ───────────────────────────────────────────────── */\n\n/*\n * Grid: 3 columns shared by every item via subgrid.\n * col 1 (auto) → leading: timestamps, labels — sized by widest content\n * col 2 (24px) → nodeTrack: node circle + connector line\n * col 3 (1fr) → content: event title / description\n *\n * align-self on items defaults to stretch so the nodeTrack cell grows to fill\n * the full row height; the flex line inside it then fills gap between nodes.\n */\n.vertical {\n display: grid;\n grid-template-columns: auto 24px 1fr;\n column-gap: 12px;\n}\n\n.vertical .item {\n display: grid;\n grid-column: 1 / -1;\n grid-template-columns: subgrid;\n}\n\n/* Leading: timestamp / label — left-aligned column, text right-aligned */\n.vertical .leading {\n padding-top: 4px; /* align text baseline with node center */\n text-align: end;\n align-self: start; /* don't stretch; only nodeTrack stretches */\n}\n\n/* Node track: node on top, connector line fills remaining height */\n.vertical .nodeTrack {\n display: flex;\n flex-direction: column;\n align-items: center;\n /* align-self defaults to stretch — fills full row height so the line\n reaches the next item's node without a gap */\n}\n\n/* Content: fills the third column */\n.vertical .content {\n min-width: 0;\n padding-top: 2px;\n align-self: start; /* don't stretch content, only nodeTrack */\n}\n\n/* Space between items — applied only to content so nodeTrack (which draws the\n connector line) fills the padding area, keeping the line continuous. */\n.vertical .item:not(:last-child) .content {\n padding-bottom: 20px;\n}\n\n/* ─── Horizontal orientation ──────────────────────────────────────────────── */\n\n/*\n * Grid: 3 rows shared by every item via subgrid.\n * row 1 (auto) → leading: label / timestamp above the node\n * row 2 (auto) → nodeTrack: the horizontal line + node\n * row 3 (auto) → content: label / description below the node\n *\n * grid-auto-flow: column places each item into a new column automatically.\n * All items share the same row heights → leading and content stay vertically\n * aligned regardless of text length.\n */\n.horizontal {\n display: grid;\n grid-template-rows: auto auto auto;\n grid-auto-flow: column;\n grid-auto-columns: minmax(72px, 1fr);\n overflow-x: auto;\n}\n\n.horizontal .item {\n display: grid;\n grid-row: 1 / -1; /* span all 3 rows */\n grid-template-rows: subgrid;\n}\n\n/* Leading: label / timestamp — bottom-aligned so it sits close to the node */\n.horizontal .leading {\n text-align: center;\n align-self: end;\n padding-bottom: 8px;\n}\n\n/* Node track: horizontal row — left line · node · right line */\n.horizontal .nodeTrack {\n display: flex;\n flex-direction: row;\n align-items: center;\n width: 100%;\n}\n\n/* Content: label / description — top-aligned below the node */\n.horizontal .content {\n text-align: center;\n padding: 8px 8px 0;\n align-self: start;\n}\n\n/* ─── Node ────────────────────────────────────────────────────────────────── */\n\n.node {\n display: flex;\n align-items: center;\n justify-content: center;\n flex-shrink: 0;\n border-radius: 50%;\n position: relative;\n z-index: 1;\n}\n\n/* Small dot when no icon */\n.nodeDot {\n width: 12px;\n height: 12px;\n background: var(--gnome-accent-bg-color, #3584e4);\n}\n\n/* Larger circle when an icon is provided */\n.nodeWithIcon {\n width: 28px;\n height: 28px;\n background: var(--gnome-accent-bg-color, #3584e4);\n color: var(--gnome-accent-fg-color, #fff);\n}\n\n.iconWrap {\n display: flex;\n align-items: center;\n justify-content: center;\n line-height: 1;\n}\n\n/* ─── Invisible line (edge positions in horizontal) ──────────────────────── */\n\n/* Takes the same space as a real line but is invisible — keeps the node\n centered in horizontal items regardless of whether it's first or last. */\n.lineInvisible {\n visibility: hidden;\n}\n\n/* ─── Connector line ──────────────────────────────────────────────────────── */\n\n/* Vertical line (inside .vertical .nodeTrack) */\n.vertical .line {\n width: 2px;\n flex: 1;\n min-height: 16px;\n background: var(--gnome-border-subtle, rgba(0, 0, 0, 0.15));\n}\n\n.vertical .lineDotted {\n background: none;\n border-left: 2px dotted var(--gnome-border-subtle, rgba(0, 0, 0, 0.15));\n width: 0;\n}\n\n/* Horizontal line (inside .horizontal .nodeTrack) */\n.horizontal .line {\n flex: 1;\n height: 2px;\n background: var(--gnome-border-subtle, rgba(0, 0, 0, 0.15));\n}\n\n.horizontal .lineDotted {\n background: none;\n border-top: 2px dotted var(--gnome-border-subtle, rgba(0, 0, 0, 0.15));\n height: 0;\n flex: 1;\n}\n\n/* ─── Dark mode ───────────────────────────────────────────────────────────── */\n\n@media (prefers-color-scheme: dark) {\n .vertical .line,\n .horizontal .line {\n background: var(--gnome-border-subtle, rgba(255, 255, 255, 0.15));\n }\n\n .vertical .lineDotted {\n border-left-color: var(--gnome-border-subtle, rgba(255, 255, 255, 0.15));\n }\n\n .horizontal .lineDotted {\n border-top-color: var(--gnome-border-subtle, rgba(255, 255, 255, 0.15));\n }\n}\n","import type { HTMLAttributes, ReactNode } from \"react\";\nimport styles from \"./Timeline.module.css\";\n\n// ─── Types ────────────────────────────────────────────────────────────────────\n\nexport type TimelineOrientation = \"vertical\" | \"horizontal\";\nexport type TimelineVariant = \"default\" | \"dotted\" | \"none\";\n\nexport interface TimelineItem {\n /** Icon rendered inside the timeline node. */\n icon?: ReactNode;\n /**\n * Content at the leading edge of the node.\n * - **Vertical:** rendered to the left of the connector track.\n * - **Horizontal:** rendered above the node.\n *\n * Typical use: timestamp, badge, short label.\n */\n leading?: ReactNode;\n /** Main event content rendered adjacent to the node (title, description…). */\n content: ReactNode;\n}\n\nexport interface TimelineProps extends HTMLAttributes<HTMLDivElement> {\n /** Ordered list of timeline events. */\n items: TimelineItem[];\n /**\n * Axis direction of the connector line.\n * - `\"vertical\"` — events stack top-to-bottom (default).\n * - `\"horizontal\"` — events flow left-to-right (stepper / progress pattern).\n */\n orientation?: TimelineOrientation;\n /**\n * Visual style of the connector between nodes.\n * - `\"default\"` — solid thin line.\n * - `\"dotted\"` — dotted line; useful for future or pending events.\n * - `\"none\"` — no connector; each node stands alone.\n */\n variant?: TimelineVariant;\n}\n\n// ─── Component ────────────────────────────────────────────────────────────────\n\n/**\n * Ordered sequence of events connected by a visual timeline.\n *\n * Supports two orientations — vertical (activity feed, history log) and\n * horizontal (stepper, progress indicator) — and three connector styles.\n *\n * ```tsx\n * <Timeline\n * items={[\n * {\n * leading: <Text variant=\"caption\" color=\"dim\">10:32</Text>,\n * icon: <Icon icon={Check} />,\n * content: <Text variant=\"body\">Approved</Text>,\n * },\n * ]}\n * />\n *\n * <Timeline orientation=\"horizontal\" variant=\"dotted\" items={steps} />\n * ```\n */\nexport function Timeline({\n items,\n orientation = \"vertical\",\n variant = \"default\",\n className,\n ...props\n}: TimelineProps) {\n const horizontal = orientation === \"horizontal\";\n const showLine = variant !== \"none\";\n\n return (\n <div\n role=\"list\"\n className={[\n styles.root,\n horizontal ? styles.horizontal : styles.vertical,\n className,\n ]\n .filter(Boolean)\n .join(\" \")}\n {...props}\n >\n {items.map((item, i) => {\n const isFirst = i === 0;\n const isLast = i === items.length - 1;\n\n const lineClass = [\n styles.line,\n variant === \"dotted\" ? styles.lineDotted : null,\n ]\n .filter(Boolean)\n .join(\" \");\n\n const node = (\n <div\n className={[\n styles.node,\n item.icon ? styles.nodeWithIcon : styles.nodeDot,\n ]\n .filter(Boolean)\n .join(\" \")}\n >\n {item.icon && (\n <span className={styles.iconWrap} aria-hidden=\"true\">\n {item.icon}\n </span>\n )}\n </div>\n );\n\n return (\n <div key={i} role=\"listitem\" className={styles.item}>\n {/* ── Leading (timestamp / label) ── */}\n {/* Always rendered — even when empty — so every item has the same\n 3 children (leading · nodeTrack · content) that map to the\n subgrid columns (vertical) or rows (horizontal). */}\n <div className={styles.leading}>{item.leading}</div>\n\n {/* ── Node track ── */}\n {horizontal ? (\n /*\n * Always render both half-lines so the node stays centered in\n * the item regardless of its position. Edge lines are hidden\n * with visibility:hidden (take space, invisible).\n */\n <div className={styles.nodeTrack}>\n <div\n className={[\n styles.line,\n variant === \"dotted\" ? styles.lineDotted : null,\n (isFirst || !showLine) ? styles.lineInvisible : null,\n ].filter(Boolean).join(\" \")}\n aria-hidden=\"true\"\n />\n {node}\n <div\n className={[\n styles.line,\n variant === \"dotted\" ? styles.lineDotted : null,\n (isLast || !showLine) ? styles.lineInvisible : null,\n ].filter(Boolean).join(\" \")}\n aria-hidden=\"true\"\n />\n </div>\n ) : (\n <div className={styles.nodeTrack}>\n {node}\n {!isLast && showLine && (\n <div className={lineClass} aria-hidden=\"true\" />\n )}\n </div>\n )}\n\n {/* ── Content ── */}\n <div className={styles.content}>{item.content}</div>\n </div>\n );\n })}\n </div>\n );\n}\n","/* ─── Root ────────────────────────────────────────────────────────────────── */\n\n.pathBar {\n display: flex;\n align-items: center;\n min-width: 0;\n font-family: var(--gnome-font-family, sans-serif);\n}\n\n/* ─── List ────────────────────────────────────────────────────────────────── */\n\n.list {\n display: flex;\n align-items: center;\n flex-wrap: wrap;\n gap: 0;\n margin: 0;\n padding: 0;\n list-style: none;\n min-width: 0;\n}\n\n/* ─── Item ────────────────────────────────────────────────────────────────── */\n\n.item {\n display: flex;\n align-items: center;\n min-width: 0;\n}\n\n/* ─── Separator ───────────────────────────────────────────────────────────── */\n\n.separator {\n display: flex;\n align-items: center;\n flex-shrink: 0;\n color: var(--gnome-window-fg-color, rgba(0, 0, 0, 0.35));\n opacity: 0.5;\n margin: 0 1px;\n}\n\n/* ─── Ancestor segment (button) ───────────────────────────────────────────── */\n\n.segment {\n display: inline-flex;\n align-items: center;\n gap: 4px;\n padding: 2px 6px;\n border-radius: var(--gnome-radius-sm, 6px);\n border: none;\n background: transparent;\n font-family: inherit;\n font-size: var(--gnome-font-size-body, 1rem);\n color: var(--gnome-window-fg-color, rgba(0, 0, 0, 0.8));\n cursor: pointer;\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n max-width: 160px;\n transition: background-color var(--gnome-duration-fast, 100ms) var(--gnome-easing-default, ease);\n}\n\n.segment:hover {\n background-color: var(--gnome-hover-overlay, rgba(0, 0, 0, 0.06));\n}\n\n.segment:active {\n background-color: var(--gnome-active-overlay, rgba(0, 0, 0, 0.12));\n}\n\n.segment: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, 2px))\n var(--gnome-focus-ring-color, #3584e4);\n}\n\n/* ─── Current segment (non-interactive) ──────────────────────────────────── */\n\n.current {\n display: inline-flex;\n align-items: center;\n gap: 4px;\n padding: 2px 6px;\n font-family: inherit;\n font-size: var(--gnome-font-size-body, 1rem);\n font-weight: var(--gnome-font-weight-semibold, 600);\n color: var(--gnome-window-fg-color, rgba(0, 0, 0, 0.8));\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n max-width: 200px;\n}\n\n/* ─── Icon ────────────────────────────────────────────────────────────────── */\n\n.icon {\n display: flex;\n align-items: center;\n flex-shrink: 0;\n}\n\n/* ─── Reduced motion ──────────────────────────────────────────────────────── */\n\n@media (prefers-reduced-motion: reduce) {\n .segment {\n transition: none;\n }\n}\n\n/* ─── Dark mode ───────────────────────────────────────────────────────────── */\n\n@media (prefers-color-scheme: dark) {\n .segment:hover {\n background-color: rgba(255, 255, 255, 0.08);\n }\n\n .segment:active {\n background-color: rgba(255, 255, 255, 0.14);\n }\n}\n","import { type HTMLAttributes, type ReactNode } from \"react\";\nimport styles from \"./PathBar.module.css\";\n\nexport interface PathBarSegment {\n /** Display label for this path segment. */\n label: string;\n /** Opaque path value passed to `onNavigate` when the segment is clicked. */\n path: string;\n /** Optional icon placed before the label (e.g. a folder icon). */\n icon?: ReactNode;\n}\n\nexport interface PathBarProps extends HTMLAttributes<HTMLElement> {\n /**\n * Ordered path segments from root to current location.\n * The last segment is the current folder and is rendered non-interactive.\n */\n segments: PathBarSegment[];\n /**\n * Called when the user clicks on a non-current segment.\n * Receives the `path` and zero-based `index` of the clicked segment.\n */\n onNavigate?: (path: string, index: number) => void;\n}\n\n/**\n * Breadcrumb path bar for navigating a hierarchical location.\n *\n * Mirrors the location bar in GNOME Files (Nautilus). Segments are separated\n * by chevron dividers. All segments except the last are interactive — clicking\n * them calls `onNavigate`. The last segment represents the current location and\n * is rendered as a static label.\n *\n * @see https://developer.gnome.org/hig/patterns/nav/search.html\n */\nexport function PathBar({ segments, onNavigate, className, ...props }: PathBarProps) {\n return (\n <nav\n aria-label=\"Breadcrumb\"\n className={[styles.pathBar, className].filter(Boolean).join(\" \")}\n {...props}\n >\n <ol className={styles.list} aria-label=\"Path segments\">\n {segments.map((segment, index) => {\n const isCurrent = index === segments.length - 1;\n return (\n <li key={segment.path} className={styles.item}>\n {/* Separator chevron (not before the first segment) */}\n {index > 0 && (\n <span className={styles.separator} aria-hidden=\"true\">\n <svg width=\"12\" height=\"12\" viewBox=\"0 0 12 12\" focusable=\"false\">\n <path\n d=\"M4 2l4 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 )}\n\n {isCurrent ? (\n /* Current folder — non-interactive */\n <span\n className={styles.current}\n aria-current=\"page\"\n >\n {segment.icon && (\n <span className={styles.icon} aria-hidden=\"true\">{segment.icon}</span>\n )}\n {segment.label}\n </span>\n ) : (\n /* Ancestor folder — clickable */\n <button\n type=\"button\"\n className={styles.segment}\n onClick={() => onNavigate?.(segment.path, index)}\n >\n {segment.icon && (\n <span className={styles.icon} aria-hidden=\"true\">{segment.icon}</span>\n )}\n {segment.label}\n </button>\n )}\n </li>\n );\n })}\n </ol>\n </nav>\n );\n}\n",".wrapper {\n position: relative;\n display: inline-flex;\n flex-direction: column;\n gap: var(--gnome-space-1, 6px);\n overflow-x: auto;\n max-width: 100%;\n}\n\n.svg {\n display: block;\n flex-shrink: 0;\n}\n\n.label {\n font-family: var(--gnome-font-family, system-ui);\n font-size: 11px;\n fill: var(--gnome-window-fg-color, rgba(0, 0, 0, 0.8));\n opacity: 0.6;\n user-select: none;\n}\n\n.cell {\n cursor: default;\n transition: transform var(--gnome-transition-fast, 100ms) ease;\n transform-box: fill-box;\n transform-origin: center;\n outline: none;\n}\n\n.cell:not([aria-disabled=\"true\"]):hover {\n transform: scale(1.2);\n}\n\n.cell:not([aria-disabled=\"true\"]) {\n cursor: pointer;\n}\n\n.cell:focus-visible {\n outline: 2px solid var(--gnome-accent-color, #3584e4);\n outline-offset: 1px;\n}\n\n@media (prefers-reduced-motion: reduce) {\n .cell {\n transition: none;\n }\n}\n\n@media (prefers-contrast: more) {\n .cell {\n stroke: var(--gnome-window-fg-color, rgba(0, 0, 0, 0.8));\n stroke-width: 0.5;\n stroke-opacity: 0.3;\n }\n}\n\n.tooltip {\n position: absolute;\n transform: translate(-50%, calc(-100% - 6px));\n background: var(--gnome-popover-bg-color, #fff);\n border: 1px solid var(--gnome-light-3, #deddda);\n border-radius: var(--gnome-radius-md, 8px);\n padding: 4px 8px;\n font-family: var(--gnome-font-family, system-ui);\n font-size: 12px;\n color: var(--gnome-window-fg-color, rgba(0, 0, 0, 0.8));\n box-shadow: var(--gnome-shadow-sm, 0 1px 3px rgba(0, 0, 0, 0.12));\n pointer-events: none;\n white-space: nowrap;\n z-index: 10;\n}\n\n.legend {\n display: flex;\n align-items: center;\n justify-content: flex-end;\n gap: var(--gnome-space-1, 6px);\n}\n\n.legendLabel {\n font-family: var(--gnome-font-family, system-ui);\n font-size: 11px;\n color: var(--gnome-window-fg-color, rgba(0, 0, 0, 0.8));\n opacity: 0.6;\n user-select: none;\n}\n\n.legendCell {\n display: block;\n flex-shrink: 0;\n}\n","import { useMemo, useRef, useState } from \"react\";\nimport type { KeyboardEvent } from \"react\";\nimport styles from \"./ContributionGraph.module.css\";\n\nexport interface ContributionDay {\n /** ISO 8601 date — \"YYYY-MM-DD\". */\n date: string;\n /** Non-negative activity count. */\n count: number;\n}\n\nexport interface ContributionGraphProps {\n /** Activity data. Days absent from the array are treated as count = 0. */\n data: ContributionDay[];\n /**\n * Number of intensity levels (excluding 0).\n * @default 4\n */\n maxLevel?: number;\n /**\n * Colour scale — length must be maxLevel + 1 (index 0 = empty).\n * Defaults to the Adwaita green palette.\n */\n colorScale?: string[];\n /** Cell side length in pixels. @default 12 */\n cellSize?: number;\n /** Gap between cells in pixels. @default 3 */\n cellGap?: number;\n /** 0 = Sunday · 1 = Monday (GNOME locale default). @default 1 */\n weekStartDay?: 0 | 1;\n /** @default true */\n showMonthLabels?: boolean;\n /** @default true */\n showDayLabels?: boolean;\n /** @default true */\n showLegend?: boolean;\n /** Number of week columns to display. @default 52 */\n weeks?: number;\n /** @default \"Contribution graph\" */\n ariaLabel?: string;\n onDayClick?: (day: ContributionDay) => void;\n /** Returns a plain-text tooltip string for a day. */\n tooltipContent?: (day: ContributionDay) => string;\n className?: string;\n}\n\nconst SHORT_MONTHS = [\n \"Jan\", \"Feb\", \"Mar\", \"Apr\", \"May\", \"Jun\",\n \"Jul\", \"Aug\", \"Sep\", \"Oct\", \"Nov\", \"Dec\",\n];\n// 0=Sun … 6=Sat\nconst SHORT_DAYS = [\"Sun\", \"Mon\", \"Tue\", \"Wed\", \"Thu\", \"Fri\", \"Sat\"];\n\nconst DAY_LABEL_WIDTH = 28;\nconst MONTH_LABEL_HEIGHT = 20;\n\nconst DEFAULT_COLORS = [\n \"var(--gnome-card-shade-color, rgba(0,0,0,0.07))\",\n \"var(--gnome-green-1, #8ff0a4)\",\n \"var(--gnome-green-2, #57e389)\",\n \"var(--gnome-green-4, #2ec27e)\",\n \"var(--gnome-green-5, #26a269)\",\n];\n\nfunction dateToIso(date: Date): string {\n const y = date.getFullYear();\n const m = String(date.getMonth() + 1).padStart(2, \"0\");\n const d = String(date.getDate()).padStart(2, \"0\");\n return `${y}-${m}-${d}`;\n}\n\nfunction isoToLocal(iso: string): Date {\n const [y, m, d] = iso.split(\"-\").map(Number);\n return new Date(y, m - 1, d);\n}\n\nfunction fullDateLabel(iso: string): string {\n return isoToLocal(iso).toLocaleDateString(\"en-US\", {\n weekday: \"long\",\n year: \"numeric\",\n month: \"long\",\n day: \"numeric\",\n });\n}\n\ninterface GridCell {\n iso: string;\n count: number;\n level: number;\n future: boolean;\n}\n\nexport function ContributionGraph({\n data,\n maxLevel = 4,\n colorScale,\n cellSize = 12,\n cellGap = 3,\n weekStartDay = 1,\n showMonthLabels = true,\n showDayLabels = true,\n showLegend = true,\n weeks = 52,\n ariaLabel = \"Contribution graph\",\n onDayClick,\n tooltipContent,\n className,\n}: ContributionGraphProps) {\n const stride = cellSize + cellGap;\n const svgRef = useRef<SVGSVGElement>(null);\n const wrapperRef = useRef<HTMLDivElement>(null);\n const colors = colorScale ?? DEFAULT_COLORS;\n\n const [focusedCell, setFocusedCell] = useState({ col: 0, row: 0 });\n const [tooltip, setTooltip] = useState<{\n x: number;\n y: number;\n text: string;\n } | null>(null);\n\n const countMap = useMemo(() => {\n const map = new Map<string, number>();\n for (const d of data) map.set(d.date, d.count);\n return map;\n }, [data]);\n\n const maxCount = useMemo(\n () => Math.max(1, ...data.map((d) => d.count)),\n [data],\n );\n\n const { grid, monthLabels } = useMemo(() => {\n const today = new Date();\n today.setHours(0, 0, 0, 0);\n\n // Align to start of current week\n const daysFromWeekStart = (today.getDay() - weekStartDay + 7) % 7;\n const lastWeekStart = new Date(today);\n lastWeekStart.setDate(today.getDate() - daysFromWeekStart);\n\n const firstDay = new Date(lastWeekStart);\n firstDay.setDate(lastWeekStart.getDate() - (weeks - 1) * 7);\n\n const gridResult: GridCell[][] = [];\n const labels: { col: number; month: string }[] = [];\n let lastMonth = -1;\n\n for (let col = 0; col < weeks; col++) {\n const colStart = new Date(firstDay);\n colStart.setDate(firstDay.getDate() + col * 7);\n\n // Month label when the column starts a new month\n if (colStart.getMonth() !== lastMonth) {\n labels.push({ col, month: SHORT_MONTHS[colStart.getMonth()] });\n lastMonth = colStart.getMonth();\n }\n\n const column: GridCell[] = [];\n for (let row = 0; row < 7; row++) {\n const date = new Date(firstDay);\n date.setDate(firstDay.getDate() + col * 7 + row);\n const future = date > today;\n const iso = dateToIso(date);\n const count = future ? 0 : (countMap.get(iso) ?? 0);\n const level =\n count === 0\n ? 0\n : Math.min(maxLevel, Math.ceil((count / maxCount) * maxLevel));\n column.push({ iso, count, level, future });\n }\n gridResult.push(column);\n }\n\n return { grid: gridResult, monthLabels: labels };\n }, [countMap, maxCount, maxLevel, weeks, weekStartDay]);\n\n // Always label Mon(1), Wed(3), Fri(5) — rows shift with weekStartDay\n const labelRows = useMemo(\n () =>\n [1, 3, 5].map((dayIndex) => ({\n row: (dayIndex - weekStartDay + 7) % 7,\n label: SHORT_DAYS[dayIndex],\n })),\n [weekStartDay],\n );\n\n const dayLabelW = showDayLabels ? DAY_LABEL_WIDTH : 0;\n const monthLabelH = showMonthLabels ? MONTH_LABEL_HEIGHT : 0;\n const svgWidth = dayLabelW + weeks * stride - cellGap;\n const svgHeight = monthLabelH + 7 * stride - cellGap;\n const cellRx = Math.min(4, Math.floor(cellSize / 3));\n\n function focusCell(col: number, row: number) {\n const c = Math.max(0, Math.min(weeks - 1, col));\n const r = Math.max(0, Math.min(6, row));\n setFocusedCell({ col: c, row: r });\n svgRef.current\n ?.querySelector<SVGRectElement>(`[data-col=\"${c}\"][data-row=\"${r}\"]`)\n ?.focus();\n }\n\n function handleCellKey(e: KeyboardEvent, col: number, row: number) {\n const moves: Record<string, [number, number]> = {\n ArrowRight: [col + 1, row],\n ArrowLeft: [col - 1, row],\n ArrowDown: [col, row + 1],\n ArrowUp: [col, row - 1],\n };\n if (moves[e.key]) {\n e.preventDefault();\n focusCell(...moves[e.key]);\n } else if (e.key === \"Enter\" || e.key === \" \") {\n e.preventDefault();\n const cell = grid[col]?.[row];\n if (cell && !cell.future) onDayClick?.({ date: cell.iso, count: cell.count });\n }\n }\n\n function handleMouseEnter(\n e: React.MouseEvent<SVGRectElement>,\n text: string,\n ) {\n const wrapper = wrapperRef.current;\n if (!wrapper) return;\n const wRect = wrapper.getBoundingClientRect();\n const rRect = (e.currentTarget as Element).getBoundingClientRect();\n setTooltip({\n x: rRect.left - wRect.left + cellSize / 2,\n y: rRect.top - wRect.top,\n text,\n });\n }\n\n function cellColor(cell: GridCell): string {\n if (cell.future) return colors[0];\n return colors[Math.min(cell.level, colors.length - 1)] ?? colors[colors.length - 1];\n }\n\n function cellTooltip(cell: GridCell): string {\n if (cell.future) return fullDateLabel(cell.iso);\n return (\n tooltipContent?.({ date: cell.iso, count: cell.count }) ??\n `${cell.count} contribution${cell.count !== 1 ? \"s\" : \"\"} on ${fullDateLabel(cell.iso)}`\n );\n }\n\n return (\n <div\n ref={wrapperRef}\n className={[styles.wrapper, className].filter(Boolean).join(\" \")}\n >\n <svg\n ref={svgRef}\n width={svgWidth}\n height={svgHeight}\n className={styles.svg}\n aria-label={ariaLabel}\n role=\"img\"\n >\n {/* Month labels */}\n {showMonthLabels &&\n monthLabels.map(({ col, month }) => (\n <text\n key={`m-${col}`}\n x={dayLabelW + col * stride}\n y={12}\n className={styles.label}\n >\n {month}\n </text>\n ))}\n\n {/* Day-of-week labels: Mon, Wed, Fri */}\n {showDayLabels &&\n labelRows.map(({ row, label }) => (\n <text\n key={`d-${row}`}\n x={0}\n y={monthLabelH + row * stride + cellSize - 1}\n className={styles.label}\n >\n {label}\n </text>\n ))}\n\n {/* Activity grid */}\n <g role=\"grid\" aria-label={ariaLabel}>\n {grid.map((column, col) => (\n <g key={col} role=\"row\">\n {column.map((cell, row) => {\n const tipText = cellTooltip(cell);\n const isFocused =\n focusedCell.col === col && focusedCell.row === row;\n\n return (\n <rect\n key={`${col}-${row}`}\n data-col={col}\n data-row={row}\n x={dayLabelW + col * stride}\n y={monthLabelH + row * stride}\n width={cellSize}\n height={cellSize}\n rx={cellRx}\n fill={cellColor(cell)}\n opacity={cell.future ? 0.35 : 1}\n className={styles.cell}\n role=\"gridcell\"\n aria-label={tipText}\n aria-disabled={cell.future || undefined}\n tabIndex={isFocused ? 0 : -1}\n onClick={() => !cell.future && onDayClick?.({ date: cell.iso, count: cell.count })}\n onKeyDown={(e) => handleCellKey(e, col, row)}\n onFocus={() => setFocusedCell({ col, row })}\n onMouseEnter={(e) => handleMouseEnter(e, tipText)}\n onMouseLeave={() => setTooltip(null)}\n />\n );\n })}\n </g>\n ))}\n </g>\n </svg>\n\n {tooltip && (\n <div\n className={styles.tooltip}\n style={{ left: tooltip.x, top: tooltip.y }}\n role=\"tooltip\"\n >\n {tooltip.text}\n </div>\n )}\n\n {showLegend && (\n <div className={styles.legend}>\n <span className={styles.legendLabel}>Less</span>\n {Array.from({ length: maxLevel + 1 }, (_, i) => (\n <svg\n key={i}\n width={cellSize}\n height={cellSize}\n aria-hidden=\"true\"\n className={styles.legendCell}\n >\n <rect\n width={cellSize}\n height={cellSize}\n rx={cellRx}\n fill={colors[Math.min(i, colors.length - 1)]}\n />\n </svg>\n ))}\n <span className={styles.legendLabel}>More</span>\n </div>\n )}\n </div>\n );\n}\n","/* ─── Outer container (border + radius, no overflow to preserve sticky) ────── */\n\n.outer {\n border: 1px solid var(--gnome-divider-color, rgba(0, 0, 0, 0.15));\n border-radius: var(--gnome-radius-lg, 12px);\n background: var(--gnome-card-bg-color, #ffffff);\n /* overflow:clip clips to border-radius without creating a scroll ctx */\n overflow: clip;\n}\n\n/* ─── Scroll container ──────────────────────────────────────────────────────── */\n\n.scroll {\n overflow-y: auto;\n width: 100%;\n}\n\n/* ─── Table ─────────────────────────────────────────────────────────────────── */\n\n.table {\n width: 100%;\n border-collapse: collapse;\n table-layout: auto;\n}\n\n/* ─── Header ────────────────────────────────────────────────────────────────── */\n\n.thead {\n position: sticky;\n top: 0;\n z-index: 2;\n background: var(--gnome-card-bg-color, #ffffff);\n /* Separator below sticky header */\n box-shadow: 0 1px 0 var(--gnome-divider-color, rgba(0, 0, 0, 0.15));\n}\n\n.th {\n padding: 0 var(--gnome-space-4, 24px);\n height: 40px;\n text-align: start;\n font-family: var(--gnome-font-family, system-ui);\n font-size: var(--gnome-font-size-caption, 0.75rem);\n font-weight: 700;\n white-space: nowrap;\n vertical-align: middle;\n}\n\n.headerLabel {\n color: var(--gnome-window-fg-color, rgba(0, 0, 0, 0.8));\n opacity: 0.55;\n}\n\n/* ─── Sort button ───────────────────────────────────────────────────────────── */\n\n.sortBtn {\n display: inline-flex;\n align-items: center;\n gap: 4px;\n appearance: none;\n background: none;\n border: none;\n padding: 0;\n cursor: pointer;\n font: inherit;\n font-size: var(--gnome-font-size-caption, 0.75rem);\n font-weight: 700;\n color: var(--gnome-window-fg-color, rgba(0, 0, 0, 0.8));\n opacity: 0.55;\n outline: none;\n border-radius: var(--gnome-radius-sm, 4px);\n transition: opacity var(--gnome-transition-fast, 100ms) ease;\n}\n\n.sortBtn:hover {\n opacity: 0.8;\n}\n\n.sortBtn:focus-visible {\n outline: 2px solid var(--gnome-accent-color, #3584e4);\n outline-offset: 2px;\n opacity: 0.8;\n}\n\n.sortBtnActive {\n opacity: 1;\n color: var(--gnome-accent-color, #3584e4);\n}\n\n.sortActive {\n color: var(--gnome-accent-color, #3584e4);\n}\n\n.sortNeutral {\n color: var(--gnome-window-fg-color, rgba(0, 0, 0, 0.8));\n}\n\n/* ─── Body ──────────────────────────────────────────────────────────────────── */\n\n.tbody {\n color: var(--gnome-card-fg-color, rgba(0, 0, 0, 0.8));\n}\n\n/* ─── Row ───────────────────────────────────────────────────────────────────── */\n\n.row {\n outline: none;\n transition: background-color var(--gnome-transition-fast, 100ms) ease;\n}\n\n.row:focus-visible {\n box-shadow: inset 0 0 0 2px var(--gnome-accent-color, #3584e4);\n}\n\n.rowSelectable {\n cursor: pointer;\n}\n\n.rowSelectable:hover {\n background-color: var(--gnome-hover-overlay, rgba(0, 0, 0, 0.07));\n}\n\n.rowSelectable:active {\n background-color: var(--gnome-active-overlay, rgba(0, 0, 0, 0.12));\n}\n\n.rowSelected {\n background-color: color-mix(\n in srgb,\n var(--gnome-accent-color, #3584e4) 15%,\n transparent\n );\n}\n\n.rowSelected:hover {\n background-color: color-mix(\n in srgb,\n var(--gnome-accent-color, #3584e4) 22%,\n transparent\n );\n}\n\n/* ─── Cell ──────────────────────────────────────────────────────────────────── */\n\n.td {\n padding: var(--gnome-space-2, 12px) var(--gnome-space-4, 24px);\n font-family: var(--gnome-font-family, system-ui);\n font-size: var(--gnome-font-size-body, 1rem);\n vertical-align: middle;\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n}\n\n/* ─── Alignment ─────────────────────────────────────────────────────────────── */\n\n.alignStart {\n text-align: start;\n}\n\n.alignCenter {\n text-align: center;\n}\n\n.alignEnd {\n text-align: end;\n}\n\n/* ─── Checkbox column ───────────────────────────────────────────────────────── */\n\n.checkboxCol {\n width: 48px;\n padding-inline: var(--gnome-space-3, 18px);\n text-align: center;\n}\n\n.checkbox {\n width: 16px;\n height: 16px;\n cursor: pointer;\n accent-color: var(--gnome-accent-color, #3584e4);\n vertical-align: middle;\n}\n\n/* ─── Empty state ───────────────────────────────────────────────────────────── */\n\n.emptyCell {\n padding: var(--gnome-space-6, 36px) var(--gnome-space-4, 24px);\n text-align: center;\n}\n\n.emptyLabel {\n font-family: var(--gnome-font-family, system-ui);\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.55;\n}\n\n/* ─── High contrast ─────────────────────────────────────────────────────────── */\n\n@media (prefers-contrast: more) {\n .outer {\n border-width: 2px;\n }\n\n .row + .row .td {\n border-top: 1px solid var(--gnome-divider-color, rgba(0, 0, 0, 0.15));\n }\n\n .sortBtn {\n text-decoration: underline;\n }\n}\n","import { type ReactNode, useRef, useState } from \"react\";\nimport styles from \"./ColumnView.module.css\";\n\nexport interface ColumnDef<TRow> {\n id: string;\n header: ReactNode;\n cell: (row: TRow, index: number) => ReactNode;\n sortable?: boolean;\n /** CSS width value — e.g. \"200px\", \"30%\". */\n width?: string;\n align?: \"start\" | \"center\" | \"end\";\n}\n\nexport type SortDirection = \"asc\" | \"desc\";\n\nexport interface ColumnViewSortState {\n columnId: string;\n direction: SortDirection;\n}\n\nexport type ColumnViewSelectionMode = \"none\" | \"single\" | \"multiple\";\n\nexport interface ColumnViewProps<TRow extends object = Record<string, unknown>> {\n columns: ColumnDef<TRow>[];\n rows: TRow[];\n /** Extract a stable unique key per row. */\n rowKey: (row: TRow) => string | number;\n /** @default \"none\" */\n selectionMode?: ColumnViewSelectionMode;\n /** Controlled set of selected row keys. */\n selectedRows?: (string | number)[];\n onSelectionChange?: (keys: (string | number)[]) => void;\n /** Controlled sort state. */\n sortState?: ColumnViewSortState;\n onSort?: (columnId: string, direction: SortDirection) => void;\n /** Constrains height and enables vertical scroll. */\n height?: string | number;\n /** Rendered when `rows` is empty. */\n emptyState?: ReactNode;\n className?: string;\n ariaLabel?: string;\n}\n\nconst ALIGN: Record<string, string> = {\n start: styles.alignStart,\n center: styles.alignCenter,\n end: styles.alignEnd,\n};\n\nfunction SortChevron({ direction }: { direction: SortDirection | null }) {\n return (\n <svg\n width=\"10\"\n height=\"10\"\n viewBox=\"0 0 10 10\"\n aria-hidden=\"true\"\n className={direction ? styles.sortActive : styles.sortNeutral}\n >\n {direction === \"asc\" ? (\n <path d=\"M5 2 L9 8 L1 8 Z\" fill=\"currentColor\" />\n ) : direction === \"desc\" ? (\n <path d=\"M5 8 L1 2 L9 2 Z\" fill=\"currentColor\" />\n ) : (\n <>\n <path d=\"M5 1 L8 5 L2 5 Z\" fill=\"currentColor\" opacity=\"0.4\" />\n <path d=\"M5 9 L2 5 L8 5 Z\" fill=\"currentColor\" opacity=\"0.4\" />\n </>\n )}\n </svg>\n );\n}\n\nexport function ColumnView<TRow extends object = Record<string, unknown>>({\n columns,\n rows,\n rowKey,\n selectionMode = \"none\",\n selectedRows = [],\n onSelectionChange,\n sortState,\n onSort,\n height,\n emptyState,\n className,\n ariaLabel,\n}: ColumnViewProps<TRow>) {\n const tbodyRef = useRef<HTMLTableSectionElement>(null);\n const [focusedIndex, setFocusedIndex] = useState(0);\n const selectedSet = new Set(selectedRows);\n const hasCheckbox = selectionMode === \"multiple\";\n const colSpan = columns.length + (hasCheckbox ? 1 : 0);\n\n function handleSort(colId: string) {\n if (!onSort) return;\n const next: SortDirection =\n sortState?.columnId === colId && sortState.direction === \"asc\"\n ? \"desc\"\n : \"asc\";\n onSort(colId, next);\n }\n\n function toggleRow(key: string | number) {\n if (selectionMode === \"none\" || !onSelectionChange) return;\n if (selectionMode === \"single\") {\n onSelectionChange(selectedSet.has(key) ? [] : [key]);\n } else {\n const next = new Set(selectedSet);\n next.has(key) ? next.delete(key) : next.add(key);\n onSelectionChange([...next]);\n }\n }\n\n function handleRowKey(\n e: React.KeyboardEvent,\n index: number,\n key: string | number,\n ) {\n const rows_els =\n tbodyRef.current?.querySelectorAll<HTMLTableRowElement>(\"tr\") ?? [];\n if (e.key === \"ArrowDown\") {\n e.preventDefault();\n const next = Math.min(rows.length - 1, index + 1);\n setFocusedIndex(next);\n rows_els[next]?.focus();\n } else if (e.key === \"ArrowUp\") {\n e.preventDefault();\n const prev = Math.max(0, index - 1);\n setFocusedIndex(prev);\n rows_els[prev]?.focus();\n } else if (e.key === \" \" || e.key === \"Enter\") {\n e.preventDefault();\n toggleRow(key);\n }\n }\n\n function handleSelectAll(checked: boolean) {\n if (!onSelectionChange) return;\n onSelectionChange(checked ? rows.map(rowKey) : []);\n }\n\n const containerStyle: React.CSSProperties = height\n ? { height: typeof height === \"number\" ? `${height}px` : height }\n : {};\n\n return (\n <div className={[styles.outer, className].filter(Boolean).join(\" \")}>\n <div className={styles.scroll} style={containerStyle}>\n <table\n className={styles.table}\n role=\"grid\"\n aria-label={ariaLabel}\n aria-multiselectable={selectionMode === \"multiple\" || undefined}\n >\n <thead className={styles.thead}>\n <tr>\n {hasCheckbox && (\n <th className={[styles.th, styles.checkboxCol].join(\" \")}>\n <input\n type=\"checkbox\"\n className={styles.checkbox}\n checked={\n rows.length > 0 && selectedRows.length === rows.length\n }\n ref={(el) => {\n if (el)\n el.indeterminate =\n selectedRows.length > 0 &&\n selectedRows.length < rows.length;\n }}\n onChange={(e) => handleSelectAll(e.target.checked)}\n aria-label=\"Select all rows\"\n />\n </th>\n )}\n {columns.map((col) => {\n const isSorted = sortState?.columnId === col.id;\n return (\n <th\n key={col.id}\n className={[styles.th, col.align ? ALIGN[col.align] : null]\n .filter(Boolean)\n .join(\" \")}\n style={col.width ? { width: col.width } : undefined}\n aria-sort={\n isSorted\n ? sortState!.direction === \"asc\"\n ? \"ascending\"\n : \"descending\"\n : col.sortable\n ? \"none\"\n : undefined\n }\n >\n {col.sortable && onSort ? (\n <button\n type=\"button\"\n className={[\n styles.sortBtn,\n isSorted ? styles.sortBtnActive : null,\n ]\n .filter(Boolean)\n .join(\" \")}\n onClick={() => handleSort(col.id)}\n >\n <span>{col.header}</span>\n <SortChevron\n direction={isSorted ? sortState!.direction : null}\n />\n </button>\n ) : (\n <span className={styles.headerLabel}>{col.header}</span>\n )}\n </th>\n );\n })}\n </tr>\n </thead>\n\n <tbody ref={tbodyRef} className={styles.tbody}>\n {rows.length === 0 ? (\n <tr>\n <td colSpan={colSpan} className={styles.emptyCell}>\n {emptyState ?? (\n <span className={styles.emptyLabel}>No items</span>\n )}\n </td>\n </tr>\n ) : (\n rows.map((row, index) => {\n const key = rowKey(row);\n const isSelected = selectedSet.has(key);\n return (\n <tr\n key={key}\n className={[\n styles.row,\n isSelected ? styles.rowSelected : null,\n selectionMode !== \"none\" ? styles.rowSelectable : null,\n ]\n .filter(Boolean)\n .join(\" \")}\n role=\"row\"\n aria-selected={\n selectionMode !== \"none\" ? isSelected : undefined\n }\n tabIndex={focusedIndex === index ? 0 : -1}\n onClick={() => toggleRow(key)}\n onKeyDown={(e) => handleRowKey(e, index, key)}\n onFocus={() => setFocusedIndex(index)}\n >\n {hasCheckbox && (\n <td className={[styles.td, styles.checkboxCol].join(\" \")}>\n <input\n type=\"checkbox\"\n className={styles.checkbox}\n checked={isSelected}\n onChange={() => toggleRow(key)}\n onClick={(e) => e.stopPropagation()}\n tabIndex={-1}\n aria-label={`Select row ${index + 1}`}\n />\n </td>\n )}\n {columns.map((col) => (\n <td\n key={col.id}\n className={[\n styles.td,\n col.align ? ALIGN[col.align] : null,\n ]\n .filter(Boolean)\n .join(\" \")}\n role=\"gridcell\"\n >\n {col.cell(row, index)}\n </td>\n ))}\n </tr>\n );\n })\n )}\n </tbody>\n </table>\n </div>\n </div>\n );\n}\n","/* ─── Container ──────────────────────────────────────────────────────────────── */\n\n.terminal {\n background-color: var(--gnome-dark-4, #241f31);\n color: var(--gnome-view-fg-color, rgba(255, 255, 255, 0.87));\n border-radius: var(--gnome-radius-default, 6px);\n overflow-y: auto;\n max-height: 400px;\n padding: 12px 16px;\n font-size: 0.875rem;\n line-height: 1.6;\n\n /* Adwaita-style thin scrollbar */\n scrollbar-width: thin;\n scrollbar-color: rgba(255, 255, 255, 0.2) transparent;\n}\n\n.pre {\n margin: 0;\n font-family: var(--gnome-font-family-mono, \"Adwaita Mono\", \"Source Code Pro\", monospace);\n white-space: pre-wrap;\n word-break: break-all;\n}\n\n.line {\n min-height: 1em;\n}\n\n/* ─── Light theme ────────────────────────────────────────────────────────────── */\n\n@media (prefers-color-scheme: light) {\n .terminal {\n background-color: var(--gnome-light-2, #f6f5f4);\n color: var(--gnome-window-fg-color, rgba(0, 0, 0, 0.8));\n scrollbar-color: rgba(0, 0, 0, 0.2) transparent;\n }\n}\n\n/* ─── Variants ───────────────────────────────────────────────────────────────── */\n\n.success .line {\n color: var(--gnome-success-color, #33d17a);\n}\n\n.warning .line {\n color: var(--gnome-warning-color, #f6d32d);\n}\n\n.destructive .line {\n color: var(--gnome-destructive-color, #f66151);\n}\n","import {\n useEffect,\n useRef,\n type HTMLAttributes,\n} from \"react\";\nimport styles from \"./TerminalView.module.css\";\n\nexport type TerminalVariant = \"default\" | \"success\" | \"warning\" | \"destructive\";\n\nexport interface TerminalViewProps extends HTMLAttributes<HTMLDivElement> {\n /** Lines of text to display. */\n lines: string[];\n /** Colorize text based on semantic meaning. Defaults to `\"default\"`. */\n variant?: TerminalVariant;\n /** Maximum number of lines to keep in the buffer. Defaults to `500`. */\n maxLines?: number;\n /** Automatically scroll to the latest line when content changes. Defaults to `true`. */\n autoScroll?: boolean;\n}\n\nexport function TerminalView({\n lines,\n variant = \"default\",\n maxLines = 500,\n autoScroll = true,\n className,\n ...props\n}: TerminalViewProps) {\n const bottomRef = useRef<HTMLDivElement>(null);\n const visibleLines = lines.slice(-maxLines);\n\n useEffect(() => {\n if (autoScroll) {\n bottomRef.current?.scrollIntoView({ block: \"nearest\" });\n }\n }, [visibleLines.length, autoScroll]);\n\n return (\n <div\n className={[styles.terminal, styles[variant], className]\n .filter(Boolean)\n .join(\" \")}\n {...props}\n >\n <pre className={styles.pre}>\n {visibleLines.map((line, i) => (\n <div key={i} className={styles.line}>\n {line}\n </div>\n ))}\n <div ref={bottomRef} />\n </pre>\n </div>\n );\n}\n","/* ─── Base ────────────────────────────────────────────────────────────────── */\n\n.badge {\n display: inline-flex;\n align-items: center;\n padding: 2px 8px;\n border-radius: var(--gnome-radius-pill, 9999px);\n font-family: var(--gnome-font-family);\n font-size: 0.6875rem;\n font-weight: 600;\n line-height: 1.4;\n white-space: nowrap;\n letter-spacing: 0.02em;\n}\n\n/* ─── Variants ────────────────────────────────────────────────────────────── */\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.new {\n background-color: var(--gnome-new-bg-color, #9141ac);\n color: var(--gnome-new-fg-color, #ffffff);\n}\n\n.accent {\n background-color: var(--gnome-accent-bg-color, #3584e4);\n color: var(--gnome-accent-fg-color, #ffffff);\n}\n\n.neutral {\n background-color: var(--gnome-hover-overlay, rgba(0, 0, 0, 0.08));\n color: var(--gnome-window-fg-color, rgba(0, 0, 0, 0.8));\n}\n","import type { HTMLAttributes, ReactNode } from \"react\";\nimport styles from \"./StatusBadge.module.css\";\n\nexport type StatusBadgeVariant =\n | \"success\"\n | \"warning\"\n | \"error\"\n | \"new\"\n | \"accent\"\n | \"neutral\";\n\nexport interface StatusBadgeProps extends HTMLAttributes<HTMLSpanElement> {\n /**\n * Visual style. Defaults to `\"neutral\"`.\n * - `success` — green (`--gnome-success-*`), for published / active states.\n * - `warning` — yellow (`--gnome-warning-*`), for beta / pending states.\n * - `error` — red (`--gnome-error-*`), for failed / rejected states.\n * - `new` — purple (`--gnome-new-*`), for newly released / featured items.\n * - `accent` — blue (`--gnome-accent-*`), for highlighted / primary states.\n * - `neutral` — muted overlay, for draft / archived / inactive states.\n */\n variant?: StatusBadgeVariant;\n children: ReactNode;\n}\n\n/**\n * Pill-shaped text label for entity status — published, beta, new, etc.\n *\n * Unlike `Badge` (which is for numeric counts), `StatusBadge` is designed\n * for short human-readable state labels.\n *\n * ```tsx\n * <StatusBadge variant=\"success\">published</StatusBadge>\n * <StatusBadge variant=\"warning\">beta</StatusBadge>\n * <StatusBadge variant=\"new\">new</StatusBadge>\n * ```\n */\nexport function StatusBadge({\n variant = \"neutral\",\n className,\n children,\n ...props\n}: StatusBadgeProps) {\n return (\n <span\n className={[styles.badge, styles[variant], className]\n .filter(Boolean)\n .join(\" \")}\n {...props}\n >\n {children}\n </span>\n );\n}\n"],"mappings":"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"}