@geomak/ui 1.2.0 → 1.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.cts CHANGED
@@ -465,22 +465,25 @@ interface NotificationPayload {
465
465
  * toast notifications. Then call `useNotification()` anywhere inside to
466
466
  * trigger them.
467
467
  *
468
+ * Toasts slide in from the right and exit to the right.
469
+ * `prefers-reduced-motion` is respected via `useReducedMotion()`.
470
+ *
468
471
  * @example
469
- * // _app.tsx / main.tsx
472
+ * // main.tsx / _app.tsx
470
473
  * <NotificationProvider>
471
474
  * <App />
472
475
  * </NotificationProvider>
473
476
  *
474
477
  * // Inside a component
475
478
  * const notify = useNotification()
476
- * notify.success({ title: 'Saved!', description: 'Your changes were saved.', duration: 3000 })
479
+ * notify.success({ title: 'Saved!', description: 'Your changes were saved.' })
477
480
  */
478
481
  declare function NotificationProvider({ children }: {
479
482
  children: React$1.ReactNode;
480
483
  }): react_jsx_runtime.JSX.Element;
481
484
  /** ─────────────────── hook ─────────────────── */
482
485
  /**
483
- * Imperative notification API.
486
+ * Imperative notification API. Must be called inside `NotificationProvider`.
484
487
  *
485
488
  * @example
486
489
  * const notify = useNotification()
@@ -827,38 +830,101 @@ interface ThemeSwitchProps {
827
830
  checked: boolean;
828
831
  };
829
832
  }) => void;
833
+ /** Optional accessible label (defaults to "Toggle dark mode") */
834
+ label?: string;
830
835
  }
831
836
  /**
832
837
  * Theme (dark-mode) toggle switch powered by Radix Switch.
833
838
  *
834
- * Displays a red/green dot to indicate the checked state.
835
- * Used as the dark-mode toggle in the TopBar.
839
+ * The thumb color indicates mode: green = light, slate = dark.
840
+ * Layout (position, margin) is the parent's responsibility this component
841
+ * renders inline with no external margins.
836
842
  *
837
843
  * @example
838
- * <Switch checked={isDarkMode} onChange={({ target }) => setDarkMode(target.checked)} />
844
+ * <ThemeSwitch checked={isDark} onChange={({ target }) => setDark(target.checked)} />
839
845
  */
840
- declare function Switch$1({ checked, onChange }: ThemeSwitchProps): react_jsx_runtime.JSX.Element;
846
+ declare function ThemeSwitch({ checked, onChange, label }: ThemeSwitchProps): react_jsx_runtime.JSX.Element;
847
+
848
+ interface TopBarProps {
849
+ /** Brand area — logo, wordmark, or app name. Rendered on the leading edge. */
850
+ brand?: React$1.ReactNode;
851
+ /**
852
+ * Centre content — primary navigation links, breadcrumb, or page title.
853
+ * On mobile (< md breakpoint) this moves below the brand row.
854
+ */
855
+ center?: React$1.ReactNode;
856
+ /**
857
+ * Trailing actions — theme toggle, user avatar, notification bell, etc.
858
+ * Rendered on the trailing edge.
859
+ */
860
+ actions?: React$1.ReactNode;
861
+ /**
862
+ * Height in pixels (default 56). Controls the `h-*` style directly so
863
+ * child scroll-offset calculations can consume it as a CSS var.
864
+ */
865
+ height?: number;
866
+ /** Additional className on the root element */
867
+ className?: string;
868
+ }
869
+ /**
870
+ * App-shell top navigation bar.
871
+ *
872
+ * Three named slots: brand (leading), center, actions (trailing).
873
+ * All slots are optional — omit what you don't need.
874
+ *
875
+ * The component is sticky by default (`sticky top-0`) with `z-[100]`.
876
+ * Height is exposed as `--topbar-height` CSS variable on the element so
877
+ * layout children can reference it for scroll-offset or sticky positioning.
878
+ *
879
+ * Light/dark aware via Phase B semantic tokens.
880
+ *
881
+ * @example
882
+ * <TopBar
883
+ * brand={<Logo />}
884
+ * center={<NavLinks />}
885
+ * actions={
886
+ * <>
887
+ * <ThemeSwitch checked={isDark} onChange={toggleDark} />
888
+ * <UserAvatar />
889
+ * </>
890
+ * }
891
+ * />
892
+ */
893
+ declare function TopBar({ brand, center, actions, height, className, }: TopBarProps): react_jsx_runtime.JSX.Element;
841
894
 
842
895
  interface ButtonProps {
843
896
  content?: React$1.ReactNode;
844
- /** HTML button type attribute */
897
+ /** Visual style variant */
898
+ variant?: 'primary' | 'secondary' | 'ghost' | 'danger';
899
+ /** Size — controls height, padding, and font size */
900
+ size?: 'sm' | 'md' | 'lg';
901
+ /** HTML button type */
845
902
  buttonType?: 'button' | 'submit' | 'reset';
846
- /** Visual variant */
847
- type?: string;
848
903
  loading?: boolean;
849
904
  disabled?: boolean;
905
+ /** Inline style overrides (width, etc.). Margins/layout belong in the parent. */
850
906
  style?: React$1.CSSProperties;
907
+ /** Leading icon — rendered before content */
851
908
  icon?: React$1.ReactNode;
852
909
  onClick?: React$1.MouseEventHandler<HTMLButtonElement>;
910
+ /**
911
+ * @deprecated Pass `variant` instead. Kept for API compat — currently no-op.
912
+ * Will be removed in the next major version.
913
+ */
914
+ type?: string;
853
915
  }
854
916
  /**
855
- * Primary action button.
917
+ * Primary action button with variant + size system.
918
+ *
919
+ * Width is never hardcoded — set `style={{ width }}` or let the parent grid/flex control it.
856
920
  *
857
921
  * @example
858
922
  * <Button content="Save" onClick={handleSave} />
859
- * <Button content="Submit" buttonType="submit" loading={isPending} />
923
+ * <Button content="Delete" variant="danger" size="sm" />
924
+ * <Button content="Cancel" variant="secondary" />
925
+ * <Button content="Loading…" loading buttonType="submit" />
860
926
  */
861
- declare function Button({ content, buttonType, loading, disabled, style, icon, onClick, }: ButtonProps): react_jsx_runtime.JSX.Element;
927
+ declare function Button({ content, variant, size, buttonType, loading, disabled, style, icon, onClick, }: ButtonProps): react_jsx_runtime.JSX.Element;
862
928
 
863
929
  interface TextInputProps {
864
930
  value?: any;
@@ -1237,4 +1303,4 @@ declare const Temporal: {
1237
1303
  TemporalPicker: typeof TemporalPickerBase;
1238
1304
  };
1239
1305
 
1240
- export { AutoComplete, type AutoCompleteProps, Button, type ButtonProps, Catalog, CatalogCarousel, type CatalogCarouselProps, CatalogGrid, type CatalogGridProps, type CatalogProps, Checkbox, type CheckboxProps, ContextMenu, type ContextMenuActionItem, type ContextMenuPosition, type ContextMenuProps, type DatePickerProps, Drawer, type DrawerProps, Dropdown, type DropdownItem, DropdownPill, type DropdownPillProps, type DropdownProps, type ExpandRowOptions, FadingBase, type FadingBaseProps, FileInput, type FileInputProps, GridCard, type GridCardItem, type GridCardProps, Icon, IconButton, type IconButtonProps, List, type ListItem, type ListProps, LoadingSpinner, type LoadingSpinnerProps, MenuBar, MenuBarItem, type MenuBarItemConfig, type MenuBarItemProps, type MenuBarProps, Modal, type ModalProps, type NotificationPayload, NotificationProvider, NumberInput, type NumberInputProps, OpaqueGridCard, type OpaqueGridCardProps, type PaginationOptions, Password, type PasswordProps, ScalableContainer, type ScalableContainerProps, SearchInput, type SearchInputProps, Switch, type SwitchInputProps, type TabItem, Table, type TableColumn, type TableProps, Tabs, type TabsProps, Temporal, type TemporalPickerProps, TextInput, type TextInputProps, Switch$1 as ThemeSwitch, type ThemeSwitchProps, ToggleButton, type ToggleButtonProps, type ToggleItem, Tooltip, type TooltipProps, TooltipProvider, Tree, type TreeItemClickPayload, type TreeNode, type TreeProps, TreeSelect, type TreeSelectProps, Wizard, type WizardProps, type WizardStep, useNotification };
1306
+ export { AutoComplete, type AutoCompleteProps, Button, type ButtonProps, Catalog, CatalogCarousel, type CatalogCarouselProps, CatalogGrid, type CatalogGridProps, type CatalogProps, Checkbox, type CheckboxProps, ContextMenu, type ContextMenuActionItem, type ContextMenuPosition, type ContextMenuProps, type DatePickerProps, Drawer, type DrawerProps, Dropdown, type DropdownItem, DropdownPill, type DropdownPillProps, type DropdownProps, type ExpandRowOptions, FadingBase, type FadingBaseProps, FileInput, type FileInputProps, GridCard, type GridCardItem, type GridCardProps, Icon, IconButton, type IconButtonProps, List, type ListItem, type ListProps, LoadingSpinner, type LoadingSpinnerProps, MenuBar, MenuBarItem, type MenuBarItemConfig, type MenuBarItemProps, type MenuBarProps, Modal, type ModalProps, type NotificationPayload, NotificationProvider, NumberInput, type NumberInputProps, OpaqueGridCard, type OpaqueGridCardProps, type PaginationOptions, Password, type PasswordProps, ScalableContainer, type ScalableContainerProps, SearchInput, type SearchInputProps, Switch, type SwitchInputProps, type TabItem, Table, type TableColumn, type TableProps, Tabs, type TabsProps, Temporal, type TemporalPickerProps, TextInput, type TextInputProps, ThemeSwitch, type ThemeSwitchProps, ToggleButton, type ToggleButtonProps, type ToggleItem, Tooltip, type TooltipProps, TooltipProvider, TopBar, type TopBarProps, Tree, type TreeItemClickPayload, type TreeNode, type TreeProps, TreeSelect, type TreeSelectProps, Wizard, type WizardProps, type WizardStep, useNotification };
package/dist/index.d.ts CHANGED
@@ -465,22 +465,25 @@ interface NotificationPayload {
465
465
  * toast notifications. Then call `useNotification()` anywhere inside to
466
466
  * trigger them.
467
467
  *
468
+ * Toasts slide in from the right and exit to the right.
469
+ * `prefers-reduced-motion` is respected via `useReducedMotion()`.
470
+ *
468
471
  * @example
469
- * // _app.tsx / main.tsx
472
+ * // main.tsx / _app.tsx
470
473
  * <NotificationProvider>
471
474
  * <App />
472
475
  * </NotificationProvider>
473
476
  *
474
477
  * // Inside a component
475
478
  * const notify = useNotification()
476
- * notify.success({ title: 'Saved!', description: 'Your changes were saved.', duration: 3000 })
479
+ * notify.success({ title: 'Saved!', description: 'Your changes were saved.' })
477
480
  */
478
481
  declare function NotificationProvider({ children }: {
479
482
  children: React$1.ReactNode;
480
483
  }): react_jsx_runtime.JSX.Element;
481
484
  /** ─────────────────── hook ─────────────────── */
482
485
  /**
483
- * Imperative notification API.
486
+ * Imperative notification API. Must be called inside `NotificationProvider`.
484
487
  *
485
488
  * @example
486
489
  * const notify = useNotification()
@@ -827,38 +830,101 @@ interface ThemeSwitchProps {
827
830
  checked: boolean;
828
831
  };
829
832
  }) => void;
833
+ /** Optional accessible label (defaults to "Toggle dark mode") */
834
+ label?: string;
830
835
  }
831
836
  /**
832
837
  * Theme (dark-mode) toggle switch powered by Radix Switch.
833
838
  *
834
- * Displays a red/green dot to indicate the checked state.
835
- * Used as the dark-mode toggle in the TopBar.
839
+ * The thumb color indicates mode: green = light, slate = dark.
840
+ * Layout (position, margin) is the parent's responsibility this component
841
+ * renders inline with no external margins.
836
842
  *
837
843
  * @example
838
- * <Switch checked={isDarkMode} onChange={({ target }) => setDarkMode(target.checked)} />
844
+ * <ThemeSwitch checked={isDark} onChange={({ target }) => setDark(target.checked)} />
839
845
  */
840
- declare function Switch$1({ checked, onChange }: ThemeSwitchProps): react_jsx_runtime.JSX.Element;
846
+ declare function ThemeSwitch({ checked, onChange, label }: ThemeSwitchProps): react_jsx_runtime.JSX.Element;
847
+
848
+ interface TopBarProps {
849
+ /** Brand area — logo, wordmark, or app name. Rendered on the leading edge. */
850
+ brand?: React$1.ReactNode;
851
+ /**
852
+ * Centre content — primary navigation links, breadcrumb, or page title.
853
+ * On mobile (< md breakpoint) this moves below the brand row.
854
+ */
855
+ center?: React$1.ReactNode;
856
+ /**
857
+ * Trailing actions — theme toggle, user avatar, notification bell, etc.
858
+ * Rendered on the trailing edge.
859
+ */
860
+ actions?: React$1.ReactNode;
861
+ /**
862
+ * Height in pixels (default 56). Controls the `h-*` style directly so
863
+ * child scroll-offset calculations can consume it as a CSS var.
864
+ */
865
+ height?: number;
866
+ /** Additional className on the root element */
867
+ className?: string;
868
+ }
869
+ /**
870
+ * App-shell top navigation bar.
871
+ *
872
+ * Three named slots: brand (leading), center, actions (trailing).
873
+ * All slots are optional — omit what you don't need.
874
+ *
875
+ * The component is sticky by default (`sticky top-0`) with `z-[100]`.
876
+ * Height is exposed as `--topbar-height` CSS variable on the element so
877
+ * layout children can reference it for scroll-offset or sticky positioning.
878
+ *
879
+ * Light/dark aware via Phase B semantic tokens.
880
+ *
881
+ * @example
882
+ * <TopBar
883
+ * brand={<Logo />}
884
+ * center={<NavLinks />}
885
+ * actions={
886
+ * <>
887
+ * <ThemeSwitch checked={isDark} onChange={toggleDark} />
888
+ * <UserAvatar />
889
+ * </>
890
+ * }
891
+ * />
892
+ */
893
+ declare function TopBar({ brand, center, actions, height, className, }: TopBarProps): react_jsx_runtime.JSX.Element;
841
894
 
842
895
  interface ButtonProps {
843
896
  content?: React$1.ReactNode;
844
- /** HTML button type attribute */
897
+ /** Visual style variant */
898
+ variant?: 'primary' | 'secondary' | 'ghost' | 'danger';
899
+ /** Size — controls height, padding, and font size */
900
+ size?: 'sm' | 'md' | 'lg';
901
+ /** HTML button type */
845
902
  buttonType?: 'button' | 'submit' | 'reset';
846
- /** Visual variant */
847
- type?: string;
848
903
  loading?: boolean;
849
904
  disabled?: boolean;
905
+ /** Inline style overrides (width, etc.). Margins/layout belong in the parent. */
850
906
  style?: React$1.CSSProperties;
907
+ /** Leading icon — rendered before content */
851
908
  icon?: React$1.ReactNode;
852
909
  onClick?: React$1.MouseEventHandler<HTMLButtonElement>;
910
+ /**
911
+ * @deprecated Pass `variant` instead. Kept for API compat — currently no-op.
912
+ * Will be removed in the next major version.
913
+ */
914
+ type?: string;
853
915
  }
854
916
  /**
855
- * Primary action button.
917
+ * Primary action button with variant + size system.
918
+ *
919
+ * Width is never hardcoded — set `style={{ width }}` or let the parent grid/flex control it.
856
920
  *
857
921
  * @example
858
922
  * <Button content="Save" onClick={handleSave} />
859
- * <Button content="Submit" buttonType="submit" loading={isPending} />
923
+ * <Button content="Delete" variant="danger" size="sm" />
924
+ * <Button content="Cancel" variant="secondary" />
925
+ * <Button content="Loading…" loading buttonType="submit" />
860
926
  */
861
- declare function Button({ content, buttonType, loading, disabled, style, icon, onClick, }: ButtonProps): react_jsx_runtime.JSX.Element;
927
+ declare function Button({ content, variant, size, buttonType, loading, disabled, style, icon, onClick, }: ButtonProps): react_jsx_runtime.JSX.Element;
862
928
 
863
929
  interface TextInputProps {
864
930
  value?: any;
@@ -1237,4 +1303,4 @@ declare const Temporal: {
1237
1303
  TemporalPicker: typeof TemporalPickerBase;
1238
1304
  };
1239
1305
 
1240
- export { AutoComplete, type AutoCompleteProps, Button, type ButtonProps, Catalog, CatalogCarousel, type CatalogCarouselProps, CatalogGrid, type CatalogGridProps, type CatalogProps, Checkbox, type CheckboxProps, ContextMenu, type ContextMenuActionItem, type ContextMenuPosition, type ContextMenuProps, type DatePickerProps, Drawer, type DrawerProps, Dropdown, type DropdownItem, DropdownPill, type DropdownPillProps, type DropdownProps, type ExpandRowOptions, FadingBase, type FadingBaseProps, FileInput, type FileInputProps, GridCard, type GridCardItem, type GridCardProps, Icon, IconButton, type IconButtonProps, List, type ListItem, type ListProps, LoadingSpinner, type LoadingSpinnerProps, MenuBar, MenuBarItem, type MenuBarItemConfig, type MenuBarItemProps, type MenuBarProps, Modal, type ModalProps, type NotificationPayload, NotificationProvider, NumberInput, type NumberInputProps, OpaqueGridCard, type OpaqueGridCardProps, type PaginationOptions, Password, type PasswordProps, ScalableContainer, type ScalableContainerProps, SearchInput, type SearchInputProps, Switch, type SwitchInputProps, type TabItem, Table, type TableColumn, type TableProps, Tabs, type TabsProps, Temporal, type TemporalPickerProps, TextInput, type TextInputProps, Switch$1 as ThemeSwitch, type ThemeSwitchProps, ToggleButton, type ToggleButtonProps, type ToggleItem, Tooltip, type TooltipProps, TooltipProvider, Tree, type TreeItemClickPayload, type TreeNode, type TreeProps, TreeSelect, type TreeSelectProps, Wizard, type WizardProps, type WizardStep, useNotification };
1306
+ export { AutoComplete, type AutoCompleteProps, Button, type ButtonProps, Catalog, CatalogCarousel, type CatalogCarouselProps, CatalogGrid, type CatalogGridProps, type CatalogProps, Checkbox, type CheckboxProps, ContextMenu, type ContextMenuActionItem, type ContextMenuPosition, type ContextMenuProps, type DatePickerProps, Drawer, type DrawerProps, Dropdown, type DropdownItem, DropdownPill, type DropdownPillProps, type DropdownProps, type ExpandRowOptions, FadingBase, type FadingBaseProps, FileInput, type FileInputProps, GridCard, type GridCardItem, type GridCardProps, Icon, IconButton, type IconButtonProps, List, type ListItem, type ListProps, LoadingSpinner, type LoadingSpinnerProps, MenuBar, MenuBarItem, type MenuBarItemConfig, type MenuBarItemProps, type MenuBarProps, Modal, type ModalProps, type NotificationPayload, NotificationProvider, NumberInput, type NumberInputProps, OpaqueGridCard, type OpaqueGridCardProps, type PaginationOptions, Password, type PasswordProps, ScalableContainer, type ScalableContainerProps, SearchInput, type SearchInputProps, Switch, type SwitchInputProps, type TabItem, Table, type TableColumn, type TableProps, Tabs, type TabsProps, Temporal, type TemporalPickerProps, TextInput, type TextInputProps, ThemeSwitch, type ThemeSwitchProps, ToggleButton, type ToggleButtonProps, type ToggleItem, Tooltip, type TooltipProps, TooltipProvider, TopBar, type TopBarProps, Tree, type TreeItemClickPayload, type TreeNode, type TreeProps, TreeSelect, type TreeSelectProps, Wizard, type WizardProps, type WizardStep, useNotification };
package/dist/index.js CHANGED
@@ -202,8 +202,45 @@ function IconButton({
202
202
  }
203
203
  );
204
204
  }
205
+ var VARIANT_CLASSES = {
206
+ primary: [
207
+ "bg-accent text-white",
208
+ "hover:bg-accent-hover",
209
+ "active:bg-accent",
210
+ "disabled:bg-roman-silver disabled:text-white/70 disabled:cursor-not-allowed",
211
+ "focus-visible:ring-2 focus-visible:ring-accent focus-visible:ring-offset-2"
212
+ ].join(" "),
213
+ secondary: [
214
+ "bg-transparent border border-accent text-accent",
215
+ "hover:bg-accent hover:text-white",
216
+ "active:bg-accent-hover active:text-white",
217
+ "disabled:border-roman-silver disabled:text-roman-silver disabled:cursor-not-allowed",
218
+ "focus-visible:ring-2 focus-visible:ring-accent focus-visible:ring-offset-2"
219
+ ].join(" "),
220
+ ghost: [
221
+ "bg-transparent text-foreground-secondary",
222
+ "hover:bg-ice dark:hover:bg-oxford-blue-700 hover:text-foreground",
223
+ "active:bg-ice-dark dark:active:bg-independence",
224
+ "disabled:text-roman-silver disabled:cursor-not-allowed",
225
+ "focus-visible:ring-2 focus-visible:ring-accent focus-visible:ring-offset-2"
226
+ ].join(" "),
227
+ danger: [
228
+ "bg-status-error text-white",
229
+ "hover:opacity-90",
230
+ "active:opacity-100",
231
+ "disabled:opacity-50 disabled:cursor-not-allowed",
232
+ "focus-visible:ring-2 focus-visible:ring-status-error focus-visible:ring-offset-2"
233
+ ].join(" ")
234
+ };
235
+ var SIZE_CLASSES = {
236
+ sm: "h-7 px-3 text-xs gap-1 rounded-md",
237
+ md: "h-9 px-4 text-sm gap-1.5 rounded-lg",
238
+ lg: "h-11 px-5 text-sm gap-2 rounded-xl"
239
+ };
205
240
  function Button({
206
241
  content,
242
+ variant = "primary",
243
+ size = "md",
207
244
  buttonType = "button",
208
245
  loading,
209
246
  disabled,
@@ -217,26 +254,33 @@ function Button({
217
254
  onClick,
218
255
  disabled: disabled || loading,
219
256
  type: buttonType,
220
- className: "bg-usafa-blue w-60 h-9 outline-offset-2 mt-5 rounded-lg disabled:bg-roman-silver disabled:cursor-not-allowed transition-all duration-300 hover:bg-true-blue active:bg-usafa-blue flex justify-center gap-1 items-center text-white",
221
- style: style ?? {},
257
+ style,
258
+ className: [
259
+ // Base — layout, transitions, focus reset
260
+ "inline-flex items-center justify-center font-medium",
261
+ "outline-none transition-colors duration-150 select-none",
262
+ "whitespace-nowrap",
263
+ SIZE_CLASSES[size],
264
+ VARIANT_CLASSES[variant]
265
+ ].join(" "),
222
266
  children: [
223
267
  loading ? /* @__PURE__ */ jsx(
224
268
  "svg",
225
269
  {
226
- xmlns: "http://www.w3.org/2000/svg",
227
270
  viewBox: "0 0 24 24",
228
- fill: "#fff",
229
- className: "w-6 h-6 animate-spin",
271
+ fill: "currentColor",
272
+ className: "w-4 h-4 animate-spin flex-shrink-0",
273
+ "aria-hidden": "true",
230
274
  children: /* @__PURE__ */ jsx(
231
275
  "path",
232
276
  {
233
277
  fillRule: "evenodd",
234
- d: "M4.755 10.059a7.5 7.5 0 0112.548-3.364l1.903 1.903h-3.183a.75.75 0 100 1.5h4.992a.75.75 0 00.75-.75V4.356a.75.75 0 00-1.5 0v3.18l-1.9-1.9A9 9 0 003.306 9.67a.75.75 0 101.45.388zm15.408 3.352a.75.75 0 00-.919.53 7.5 7.5 0 01-12.548 3.364l-1.902-1.903h3.183a.75.75 0 000-1.5H2.984a.75.75 0 00-.75.75v4.992a.75.75 0 001.5 0v-3.18l1.9 1.9a9 9 0 0015.059-4.035.75.75 0 00-.53-.918z",
235
- clipRule: "evenodd"
278
+ clipRule: "evenodd",
279
+ d: "M4.755 10.059a7.5 7.5 0 0112.548-3.364l1.903 1.903h-3.183a.75.75 0 100 1.5h4.992a.75.75 0 00.75-.75V4.356a.75.75 0 00-1.5 0v3.18l-1.9-1.9A9 9 0 003.306 9.67a.75.75 0 101.45.388zm15.408 3.352a.75.75 0 00-.919.53 7.5 7.5 0 01-12.548 3.364l-1.902-1.903h3.183a.75.75 0 000-1.5H2.984a.75.75 0 00-.75.75v4.992a.75.75 0 001.5 0v-3.18l1.9 1.9a9 9 0 0015.059-4.035.75.75 0 00-.53-.918z"
236
280
  }
237
281
  )
238
282
  }
239
- ) : icon ? icon : null,
283
+ ) : icon ? /* @__PURE__ */ jsx("span", { className: "flex-shrink-0", "aria-hidden": "true", children: icon }) : null,
240
284
  content
241
285
  ]
242
286
  }
@@ -659,15 +703,15 @@ var NotificationContext = createContext({
659
703
  open: () => void 0,
660
704
  close: () => void 0
661
705
  });
662
- var typeClass = {
663
- info: "bg-info",
664
- success: "bg-success",
665
- warning: "bg-warning",
666
- danger: "bg-error"
706
+ var TYPE_BG = {
707
+ info: "bg-status-info",
708
+ success: "bg-status-success",
709
+ warning: "bg-status-warning",
710
+ danger: "bg-status-error"
667
711
  };
668
712
  function TypeIcon({ type }) {
669
713
  if (type === "success") {
670
- return /* @__PURE__ */ jsx("svg", { width: "28", height: "28", viewBox: "0 0 28 28", fill: "none", children: /* @__PURE__ */ jsx(
714
+ return /* @__PURE__ */ jsx("svg", { width: "20", height: "20", viewBox: "0 0 28 28", fill: "none", "aria-hidden": "true", children: /* @__PURE__ */ jsx(
671
715
  "path",
672
716
  {
673
717
  fillRule: "evenodd",
@@ -678,52 +722,94 @@ function TypeIcon({ type }) {
678
722
  ) });
679
723
  }
680
724
  if (type === "info") {
681
- return /* @__PURE__ */ jsx("svg", { xmlns: "http://www.w3.org/2000/svg", viewBox: "0 0 24 24", fill: "#fff", className: "w-7 h-7", children: /* @__PURE__ */ jsx("path", { fillRule: "evenodd", d: "M2.25 12c0-5.385 4.365-9.75 9.75-9.75s9.75 4.365 9.75 9.75-4.365 9.75-9.75 9.75S2.25 17.385 2.25 12zm8.706-1.442c1.146-.573 2.437.463 2.126 1.706l-.709 2.836.042-.02a.75.75 0 01.67 1.34l-.04.022c-1.147.573-2.438-.463-2.127-1.706l.71-2.836-.042.02a.75.75 0 11-.671-1.34l.041-.022zM12 9a.75.75 0 100-1.5.75.75 0 000 1.5z", clipRule: "evenodd" }) });
725
+ return /* @__PURE__ */ jsx("svg", { xmlns: "http://www.w3.org/2000/svg", viewBox: "0 0 24 24", fill: "#fff", className: "w-5 h-5", "aria-hidden": "true", children: /* @__PURE__ */ jsx("path", { fillRule: "evenodd", d: "M2.25 12c0-5.385 4.365-9.75 9.75-9.75s9.75 4.365 9.75 9.75-4.365 9.75-9.75 9.75S2.25 17.385 2.25 12zm8.706-1.442c1.146-.573 2.437.463 2.126 1.706l-.709 2.836.042-.02a.75.75 0 01.67 1.34l-.04.022c-1.147.573-2.438-.463-2.127-1.706l.71-2.836-.042.02a.75.75 0 11-.671-1.34l.041-.022zM12 9a.75.75 0 100-1.5.75.75 0 000 1.5z", clipRule: "evenodd" }) });
682
726
  }
683
727
  if (type === "warning") {
684
- return /* @__PURE__ */ jsx("svg", { xmlns: "http://www.w3.org/2000/svg", viewBox: "0 0 24 24", fill: "#fff", className: "w-7 h-7", children: /* @__PURE__ */ jsx("path", { fillRule: "evenodd", d: "M9.401 3.003c1.155-2 4.043-2 5.197 0l7.355 12.748c1.154 2-.29 4.5-2.599 4.5H4.645c-2.309 0-3.752-2.5-2.598-4.5L9.4 3.003zM12 8.25a.75.75 0 01.75.75v3.75a.75.75 0 01-1.5 0V9a.75.75 0 01.75-.75zm0 8.25a.75.75 0 100-1.5.75.75 0 000 1.5z", clipRule: "evenodd" }) });
728
+ return /* @__PURE__ */ jsx("svg", { xmlns: "http://www.w3.org/2000/svg", viewBox: "0 0 24 24", fill: "#fff", className: "w-5 h-5", "aria-hidden": "true", children: /* @__PURE__ */ jsx("path", { fillRule: "evenodd", d: "M9.401 3.003c1.155-2 4.043-2 5.197 0l7.355 12.748c1.154 2-.29 4.5-2.599 4.5H4.645c-2.309 0-3.752-2.5-2.598-4.5L9.4 3.003zM12 8.25a.75.75 0 01.75.75v3.75a.75.75 0 01-1.5 0V9a.75.75 0 01.75-.75zm0 8.25a.75.75 0 100-1.5.75.75 0 000 1.5z", clipRule: "evenodd" }) });
685
729
  }
686
- return /* @__PURE__ */ jsx("svg", { xmlns: "http://www.w3.org/2000/svg", viewBox: "0 0 24 24", fill: "#fff", className: "w-7 h-7", children: /* @__PURE__ */ jsx("path", { fillRule: "evenodd", d: "M2.25 12c0-5.385 4.365-9.75 9.75-9.75s9.75 4.365 9.75 9.75-4.365 9.75-9.75 9.75S2.25 17.385 2.25 12zm11.378-3.917c-.89-.777-2.366-.777-3.255 0a.75.75 0 01-.988-1.129c1.454-1.272 3.776-1.272 5.23 0 1.513 1.324 1.513 3.518 0 4.842a3.75 3.75 0 01-.837.552c-.676.328-1.028.774-1.028 1.152v.75a.75.75 0 01-1.5 0v-.75c0-1.279 1.06-2.107 1.875-2.502.182-.088.351-.199.503-.331.83-.727.83-1.857 0-2.584zM12 18a.75.75 0 100-1.5.75.75 0 000 1.5z", clipRule: "evenodd" }) });
730
+ return /* @__PURE__ */ jsx("svg", { xmlns: "http://www.w3.org/2000/svg", viewBox: "0 0 24 24", fill: "#fff", className: "w-5 h-5", "aria-hidden": "true", children: /* @__PURE__ */ jsx("path", { fillRule: "evenodd", d: "M2.25 12c0-5.385 4.365-9.75 9.75-9.75s9.75 4.365 9.75 9.75-4.365 9.75-9.75 9.75S2.25 17.385 2.25 12zm11.378-3.917c-.89-.777-2.366-.777-3.255 0a.75.75 0 01-.988-1.129c1.454-1.272 3.776-1.272 5.23 0 1.513 1.324 1.513 3.518 0 4.842a3.75 3.75 0 01-.837.552c-.676.328-1.028.774-1.028 1.152v.75a.75.75 0 01-1.5 0v-.75c0-1.279 1.06-2.107 1.875-2.502.182-.088.351-.199.503-.331.83-.727.83-1.857 0-2.584zM12 18a.75.75 0 100-1.5.75.75 0 000 1.5z", clipRule: "evenodd" }) });
731
+ }
732
+ function NotificationItem({
733
+ n,
734
+ onClose,
735
+ reduced
736
+ }) {
737
+ return /* @__PURE__ */ jsx(
738
+ motion.div,
739
+ {
740
+ layout: true,
741
+ initial: { opacity: 0, x: reduced ? 0 : 56, scale: reduced ? 0.97 : 1 },
742
+ animate: { opacity: 1, x: 0, scale: 1 },
743
+ exit: { opacity: 0, x: reduced ? 0 : 40, scale: reduced ? 0.97 : 1 },
744
+ transition: reduced ? { duration: 0 } : {
745
+ opacity: { duration: 0.15 },
746
+ x: { type: "tween", duration: 0.22, ease: [0.16, 1, 0.3, 1] },
747
+ layout: { duration: 0.2 }
748
+ },
749
+ children: /* @__PURE__ */ jsxs(
750
+ Toast.Root,
751
+ {
752
+ open: true,
753
+ duration: n.duration,
754
+ onOpenChange: (o) => {
755
+ if (!o) onClose(n.id);
756
+ },
757
+ className: [
758
+ "rounded-xl shadow-md p-3 w-[300px] text-white",
759
+ "focus:outline-none focus-visible:ring-2 focus-visible:ring-white/60",
760
+ TYPE_BG[n.type ?? "info"]
761
+ ].join(" "),
762
+ children: [
763
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2.5", children: [
764
+ /* @__PURE__ */ jsx("span", { className: "flex-shrink-0", children: /* @__PURE__ */ jsx(TypeIcon, { type: n.type ?? "info" }) }),
765
+ /* @__PURE__ */ jsx(Toast.Title, { className: "font-semibold text-sm leading-snug flex-1", children: n.title }),
766
+ /* @__PURE__ */ jsx(Toast.Action, { asChild: true, altText: "Close", children: /* @__PURE__ */ jsx(
767
+ "button",
768
+ {
769
+ "aria-label": "Close notification",
770
+ onClick: () => onClose(n.id),
771
+ className: "flex-shrink-0 rounded-md p-1 hover:bg-white/20 transition-colors duration-150 focus:outline-none focus-visible:ring-1 focus-visible:ring-white/60",
772
+ children: /* @__PURE__ */ jsx("svg", { width: "14", height: "14", viewBox: "0 0 20 20", fill: "none", "aria-hidden": "true", children: /* @__PURE__ */ jsx("path", { d: "M15 5L5 15M5 5l10 10", stroke: "#fff", strokeWidth: "1.5", strokeLinecap: "round", strokeLinejoin: "round" }) })
773
+ }
774
+ ) })
775
+ ] }),
776
+ n.description && /* @__PURE__ */ jsx(Toast.Description, { className: "mt-1.5 text-xs text-white/80 leading-relaxed pl-7", children: n.description })
777
+ ]
778
+ }
779
+ )
780
+ }
781
+ );
687
782
  }
688
783
  function NotificationProvider({ children }) {
689
784
  const [notifications, setNotifications] = useState([]);
785
+ const reduced = useReducedMotion();
690
786
  const open = (payload) => {
691
- setNotifications((prev) => [...prev, { duration: 4e3, ...payload, id: Date.now() + Math.random() }]);
787
+ setNotifications((prev) => [
788
+ ...prev,
789
+ { duration: 4e3, ...payload, id: Date.now() + Math.random() }
790
+ ]);
692
791
  };
693
792
  const close = (id) => {
694
793
  setNotifications((prev) => prev.filter((n) => n.id !== id));
695
794
  };
696
795
  return /* @__PURE__ */ jsx(NotificationContext.Provider, { value: { open, close }, children: /* @__PURE__ */ jsxs(Toast.Provider, { swipeDirection: "right", children: [
697
796
  children,
698
- notifications.map((n) => /* @__PURE__ */ jsxs(
699
- Toast.Root,
797
+ /* @__PURE__ */ jsx(
798
+ Toast.Viewport,
700
799
  {
701
- open: true,
702
- duration: n.duration,
703
- onOpenChange: (o) => {
704
- if (!o) close(n.id);
705
- },
706
- className: `rounded-lg shadow-md p-2 w-[300px] text-white ${typeClass[n.type ?? "info"]} data-[state=open]:animate-in data-[state=closed]:animate-out data-[swipe=end]:animate-out data-[state=closed]:fade-out-80 data-[state=open]:slide-in-from-right-full data-[state=closed]:slide-out-to-right-full`,
707
- children: [
708
- /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between gap-3 border-b border-white pb-1", children: [
709
- /* @__PURE__ */ jsx(TypeIcon, { type: n.type ?? "info" }),
710
- /* @__PURE__ */ jsx(Toast.Title, { className: "text-center font-bold text-lg flex-1", children: n.title }),
711
- /* @__PURE__ */ jsx(Toast.Action, { asChild: true, altText: "Close", children: /* @__PURE__ */ jsx(
712
- "button",
713
- {
714
- "aria-label": "Close notification",
715
- onClick: () => close(n.id),
716
- className: "cursor-pointer rounded p-0.5 hover:bg-white/20 transition-colors",
717
- children: /* @__PURE__ */ jsx("svg", { width: "16", height: "16", viewBox: "0 0 20 20", fill: "none", children: /* @__PURE__ */ jsx("path", { d: "M15 5L5 15M5 5l10 10", stroke: "#fff", strokeWidth: "1.5", strokeLinecap: "round", strokeLinejoin: "round" }) })
718
- }
719
- ) })
720
- ] }),
721
- n.description && /* @__PURE__ */ jsx(Toast.Description, { className: "text-center mt-1 text-sm", children: n.description })
722
- ]
723
- },
724
- n.id
725
- )),
726
- /* @__PURE__ */ jsx(Toast.Viewport, { className: "fixed top-[50px] right-0 flex flex-col gap-2 w-[350px] z-[500000] p-4 outline-none" })
800
+ asChild: true,
801
+ className: "fixed top-[56px] right-0 z-[500000] flex flex-col gap-2 w-[350px] p-4 outline-none",
802
+ children: /* @__PURE__ */ jsx("ul", { children: /* @__PURE__ */ jsx(AnimatePresence, { initial: false, children: notifications.map((n) => /* @__PURE__ */ jsx(
803
+ NotificationItem,
804
+ {
805
+ n,
806
+ onClose: close,
807
+ reduced
808
+ },
809
+ n.id
810
+ )) }) })
811
+ }
812
+ )
727
813
  ] }) });
728
814
  }
729
815
  function useNotification() {
@@ -1700,29 +1786,77 @@ function Table({
1700
1786
  /* @__PURE__ */ jsx("div", { children: footer })
1701
1787
  ] });
1702
1788
  }
1703
- function Switch({ checked, onChange }) {
1789
+ function ThemeSwitch({ checked, onChange, label = "Toggle dark mode" }) {
1704
1790
  const id = useId();
1705
- return /* @__PURE__ */ jsx("div", { className: "relative bottom-5", children: /* @__PURE__ */ jsx("label", { htmlFor: id, className: "flex items-center cursor-pointer mr-12 select-none", children: /* @__PURE__ */ jsx(
1791
+ return /* @__PURE__ */ jsx("label", { htmlFor: id, className: "flex items-center gap-2 cursor-pointer select-none", children: /* @__PURE__ */ jsx(
1706
1792
  SwitchPrimitive.Root,
1707
1793
  {
1708
1794
  id,
1709
1795
  checked,
1710
1796
  onCheckedChange: (c) => onChange({ target: { checked: c } }),
1711
- className: "relative inline-flex h-6 w-14 items-center rounded-full bg-prussian-blue dark:bg-white transition-colors focus:outline-none focus-visible:ring-2 focus-visible:ring-usafa-blue",
1797
+ "aria-label": label,
1798
+ className: [
1799
+ "relative inline-flex h-6 w-11 items-center rounded-full",
1800
+ "transition-colors duration-200",
1801
+ "bg-foreground-secondary data-[state=checked]:bg-accent",
1802
+ "focus:outline-none focus-visible:ring-2 focus-visible:ring-accent focus-visible:ring-offset-2"
1803
+ ].join(" "),
1712
1804
  children: /* @__PURE__ */ jsx(
1713
1805
  SwitchPrimitive.Thumb,
1714
1806
  {
1715
- className: "pointer-events-none inline-flex h-8 w-8 rounded-full shadow transition-transform duration-200 data-[state=checked]:translate-x-[22px] data-[state=unchecked]:translate-x-[-4px]",
1716
- children: /* @__PURE__ */ jsx(
1717
- "div",
1718
- {
1719
- className: `rounded-full w-8 h-8 ${checked ? "bg-success" : "bg-error"} transition-colors duration-200`
1720
- }
1807
+ className: [
1808
+ "pointer-events-none block h-5 w-5 rounded-full shadow-sm",
1809
+ "transition-transform duration-200",
1810
+ "data-[state=checked]:translate-x-[22px]",
1811
+ "data-[state=unchecked]:translate-x-[2px]",
1812
+ // Moon icon (dark mode indicator) when checked, sun when unchecked
1813
+ checked ? "bg-oxford-blue-900" : "bg-white"
1814
+ ].join(" "),
1815
+ children: checked ? (
1816
+ // Moon
1817
+ /* @__PURE__ */ jsx("svg", { viewBox: "0 0 16 16", fill: "currentColor", className: "w-3 h-3 m-1 text-manatee", "aria-hidden": "true", children: /* @__PURE__ */ jsx("path", { d: "M6 .278a.768.768 0 0 1 .08.858 7.208 7.208 0 0 0-.878 3.46c0 4.021 3.278 7.277 7.318 7.277.527 0 1.04-.055 1.533-.16a.787.787 0 0 1 .81.316.733.733 0 0 1-.031.893A8.349 8.349 0 0 1 8.344 16C3.734 16 0 12.286 0 7.71 0 4.266 2.114 1.312 5.124.06A.752.752 0 0 1 6 .278z" }) })
1818
+ ) : (
1819
+ // Sun
1820
+ /* @__PURE__ */ jsx("svg", { viewBox: "0 0 16 16", fill: "currentColor", className: "w-3 h-3 m-1 text-usafa-blue", "aria-hidden": "true", children: /* @__PURE__ */ jsx("path", { d: "M8 11a3 3 0 1 1 0-6 3 3 0 0 1 0 6zm0 1a4 4 0 1 0 0-8 4 4 0 0 0 0 8zM8 0a.5.5 0 0 1 .5.5v2a.5.5 0 0 1-1 0v-2A.5.5 0 0 1 8 0zm0 13a.5.5 0 0 1 .5.5v2a.5.5 0 0 1-1 0v-2A.5.5 0 0 1 8 13zm8-5a.5.5 0 0 1-.5.5h-2a.5.5 0 0 1 0-1h2a.5.5 0 0 1 .5.5zM3 8a.5.5 0 0 1-.5.5h-2a.5.5 0 0 1 0-1h2A.5.5 0 0 1 3 8zm10.657-5.657a.5.5 0 0 1 0 .707l-1.414 1.415a.5.5 0 1 1-.707-.708l1.414-1.414a.5.5 0 0 1 .707 0zm-9.193 9.193a.5.5 0 0 1 0 .707L3.05 13.657a.5.5 0 0 1-.707-.707l1.414-1.414a.5.5 0 0 1 .707 0zm9.193 2.121a.5.5 0 0 1-.707 0l-1.414-1.414a.5.5 0 0 1 .707-.707l1.414 1.414a.5.5 0 0 1 0 .707zM4.464 4.465a.5.5 0 0 1-.707 0L2.343 3.05a.5.5 0 1 1 .707-.707l1.414 1.414a.5.5 0 0 1 0 .707z" }) })
1721
1821
  )
1722
1822
  }
1723
1823
  )
1724
1824
  }
1725
- ) }) });
1825
+ ) });
1826
+ }
1827
+ function TopBar({
1828
+ brand,
1829
+ center,
1830
+ actions,
1831
+ height = 56,
1832
+ className = ""
1833
+ }) {
1834
+ return /* @__PURE__ */ jsxs(
1835
+ "header",
1836
+ {
1837
+ className: [
1838
+ "sticky top-0 z-[100]",
1839
+ "flex items-center justify-between gap-4",
1840
+ "border-b border-border bg-surface",
1841
+ "px-4 md:px-6",
1842
+ className
1843
+ ].join(" "),
1844
+ style: {
1845
+ height,
1846
+ // Expose as CSS var so consumers can write:
1847
+ // padding-top: calc(var(--topbar-height) + 1rem)
1848
+ ["--topbar-height"]: `${height}px`
1849
+ },
1850
+ children: [
1851
+ brand !== void 0 ? /* @__PURE__ */ jsx("div", { className: "flex items-center gap-2 flex-shrink-0", children: brand }) : (
1852
+ // Reserve leading space even when brand is null so center stays centred
1853
+ /* @__PURE__ */ jsx("div", { "aria-hidden": "true", className: "flex-shrink-0 w-0" })
1854
+ ),
1855
+ center !== void 0 && /* @__PURE__ */ jsx("div", { className: "flex flex-1 items-center justify-center min-w-0", children: center }),
1856
+ actions !== void 0 && /* @__PURE__ */ jsx("div", { className: "flex items-center gap-2 flex-shrink-0", children: actions })
1857
+ ]
1858
+ }
1859
+ );
1726
1860
  }
1727
1861
  function TextInput({
1728
1862
  value,
@@ -1999,7 +2133,7 @@ function Checkbox({
1999
2133
  /* @__PURE__ */ jsx("div", { className: "text-error text-center", children: errorMessage })
2000
2134
  ] });
2001
2135
  }
2002
- function Switch2({
2136
+ function Switch({
2003
2137
  checked = false,
2004
2138
  onChange,
2005
2139
  checkedIcon,
@@ -2599,6 +2733,6 @@ Temporal.DatePicker = DatePickerBase;
2599
2733
  Temporal.TemporalPicker = TemporalPickerBase;
2600
2734
  var DatePicker_default = Temporal;
2601
2735
 
2602
- export { AutoComplete, Button, Catalog, CatalogCarousel, CatalogGrid, Checkbox, ContextMenu, Drawer, Dropdown, DropdownPill, FadingBase, FileInput, GridCard, icons_default as Icon, IconButton, List2 as List, LoadingSpinner, MenuBar, MenuBarItem, Modal, NotificationProvider, NumberInput, OpaqueGridCard, Password, ScalableContainer, SearchInput_default as SearchInput, Switch2 as Switch, Table, Tabs, DatePicker_default as Temporal, TextInput, Switch as ThemeSwitch, ToggleButton, Tooltip, TooltipProvider, Tree, TreeSelect, Wizard, useNotification };
2736
+ export { AutoComplete, Button, Catalog, CatalogCarousel, CatalogGrid, Checkbox, ContextMenu, Drawer, Dropdown, DropdownPill, FadingBase, FileInput, GridCard, icons_default as Icon, IconButton, List2 as List, LoadingSpinner, MenuBar, MenuBarItem, Modal, NotificationProvider, NumberInput, OpaqueGridCard, Password, ScalableContainer, SearchInput_default as SearchInput, Switch, Table, Tabs, DatePicker_default as Temporal, TextInput, ThemeSwitch, ToggleButton, Tooltip, TooltipProvider, TopBar, Tree, TreeSelect, Wizard, useNotification };
2603
2737
  //# sourceMappingURL=index.js.map
2604
2738
  //# sourceMappingURL=index.js.map