@sovereignfs/ui 0.45.0 → 0.54.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.
Files changed (93) hide show
  1. package/dist/LegalLinks.module.css +24 -0
  2. package/dist/Markdown.module.css +49 -0
  3. package/dist/MobileAppsDrawer.module.css +81 -0
  4. package/dist/MobileFooter.module.css +73 -0
  5. package/dist/MobileHeader.module.css +63 -0
  6. package/dist/PageContainer.module.css +20 -0
  7. package/dist/SwipableMobileCarousel.module.css +55 -0
  8. package/dist/SwipableMobileCarouselDots.module.css +49 -0
  9. package/dist/SwipableMobileCarouselSlideParts.module.css +29 -0
  10. package/dist/index.d.ts +654 -6
  11. package/dist/index.js +1856 -1133
  12. package/dist/tokens/primitives.css +25 -0
  13. package/package.json +13 -9
  14. package/src/components/Badge/Badge.tsx +1 -7
  15. package/src/components/DeviceOnlyGate/DeviceOnlyGate.tsx +58 -0
  16. package/src/components/DeviceOnlyGate/__tests__/DeviceOnlyGate.test.tsx +45 -0
  17. package/src/components/DeviceStorageKeyGate/DeviceStorageKeyGate.tsx +100 -0
  18. package/src/components/DeviceStorageKeyGate/__tests__/DeviceStorageKeyGate.test.tsx +119 -0
  19. package/src/components/Dialog/Dialog.tsx +8 -59
  20. package/src/components/Drawer/Drawer.stories.tsx +44 -0
  21. package/src/components/Drawer/Drawer.tsx +18 -56
  22. package/src/components/Drawer/__tests__/Drawer.test.tsx +22 -0
  23. package/src/components/Icon/icons/index.ts +8 -0
  24. package/src/components/Icon/icons/menu.tsx +22 -0
  25. package/src/components/Icon/icons/smartphone.tsx +21 -0
  26. package/src/components/LegalLinks/LegalLinks.module.css +24 -0
  27. package/src/components/LegalLinks/LegalLinks.stories.tsx +22 -0
  28. package/src/components/LegalLinks/LegalLinks.tsx +40 -0
  29. package/src/components/Markdown/Markdown.module.css +49 -0
  30. package/src/components/Markdown/Markdown.stories.tsx +30 -0
  31. package/src/components/Markdown/Markdown.tsx +168 -0
  32. package/src/components/MobileAppsDrawer/MobileAppsDrawer.module.css +81 -0
  33. package/src/components/MobileAppsDrawer/MobileAppsDrawer.tsx +78 -0
  34. package/src/components/MobileAppsDrawer/__tests__/MobileAppsDrawer.test.tsx +80 -0
  35. package/src/components/MobileFooter/MobileFooter.module.css +73 -0
  36. package/src/components/MobileFooter/MobileFooter.stories.tsx +47 -0
  37. package/src/components/MobileFooter/MobileFooter.tsx +109 -0
  38. package/src/components/MobileFooter/__tests__/MobileFooter.test.tsx +71 -0
  39. package/src/components/MobileHeader/MobileHeader.module.css +63 -0
  40. package/src/components/MobileHeader/MobileHeader.stories.tsx +57 -0
  41. package/src/components/MobileHeader/MobileHeader.tsx +77 -0
  42. package/src/components/MobileHeader/__tests__/MobileHeader.test.tsx +58 -0
  43. package/src/components/NavTabs/NavTabs.tsx +39 -11
  44. package/src/components/OfflineGate/OfflineGate.tsx +44 -0
  45. package/src/components/OfflineGate/__tests__/OfflineGate.test.tsx +66 -0
  46. package/src/components/PageContainer/PageContainer.module.css +20 -0
  47. package/src/components/PageContainer/PageContainer.tsx +31 -0
  48. package/src/components/PageContainer/__tests__/PageContainer.test.tsx +37 -0
  49. package/src/components/PageHeader/PageHeader.tsx +16 -2
  50. package/src/components/ResponsiveSurface/ResponsiveSurface.tsx +24 -0
  51. package/src/components/ResponsiveSurface/__tests__/ResponsiveSurface.test.tsx +73 -0
  52. package/src/components/Sheet/Sheet.tsx +9 -51
  53. package/src/components/SwipableMobileCarousel/SwipableMobileCarousel.module.css +55 -0
  54. package/src/components/SwipableMobileCarousel/SwipableMobileCarousel.tsx +227 -0
  55. package/src/components/SwipableMobileCarousel/SwipableMobileCarouselSlide.tsx +35 -0
  56. package/src/components/SwipableMobileCarousel/SwipableMobileCarouselSlideBody.tsx +42 -0
  57. package/src/components/SwipableMobileCarousel/SwipableMobileCarouselSlideFooter.tsx +18 -0
  58. package/src/components/SwipableMobileCarousel/SwipableMobileCarouselSlideHeader.tsx +21 -0
  59. package/src/components/SwipableMobileCarousel/SwipableMobileCarouselSlideParts.module.css +29 -0
  60. package/src/components/SwipableMobileCarousel/__tests__/SwipableMobileCarousel.test.tsx +133 -0
  61. package/src/components/SwipableMobileCarousel/__tests__/SwipableMobileCarouselDevGuard.test.tsx +61 -0
  62. package/src/components/SwipableMobileCarousel/context.ts +13 -0
  63. package/src/components/SwipableMobileCarouselDots/SwipableMobileCarouselDots.module.css +49 -0
  64. package/src/components/SwipableMobileCarouselDots/SwipableMobileCarouselDots.tsx +62 -0
  65. package/src/components/SwipableMobileCarouselDots/__tests__/SwipableMobileCarouselDots.test.tsx +63 -0
  66. package/src/hooks/__tests__/useCarouselRouteSync.test.tsx +126 -0
  67. package/src/hooks/__tests__/useIsOffline.test.tsx +53 -0
  68. package/src/hooks/__tests__/useOfflineTileState.test.tsx +69 -0
  69. package/src/hooks/__tests__/useResponsiveLayout.test.tsx +87 -0
  70. package/src/hooks/__tests__/useSnapCarousel.test.tsx +93 -0
  71. package/src/hooks/__tests__/useSwipeReveal.test.tsx +110 -0
  72. package/src/hooks/index.ts +18 -0
  73. package/src/hooks/useCarouselRouteSync.ts +92 -0
  74. package/src/hooks/useIsOffline.ts +41 -0
  75. package/src/hooks/useOfflineTileState.ts +45 -0
  76. package/src/hooks/useResponsiveLayout.ts +36 -0
  77. package/src/hooks/useSnapCarousel.ts +79 -0
  78. package/src/hooks/useSwipeReveal.ts +107 -0
  79. package/src/index.ts +63 -2
  80. package/src/overlay-shell.ts +94 -0
  81. package/src/stories/DesignSystemOverview.stories.tsx +322 -1
  82. package/src/stories/DeviceOnlyGate.stories.tsx +35 -0
  83. package/src/stories/DeviceStorageKeyGate.stories.tsx +57 -0
  84. package/src/stories/InteractionHooks.stories.tsx +483 -0
  85. package/src/stories/MobilePatterns.stories.tsx +247 -2
  86. package/src/stories/NavTabs.stories.tsx +18 -0
  87. package/src/stories/OfflineGate.stories.tsx +42 -0
  88. package/src/stories/PageContainer.stories.tsx +37 -0
  89. package/src/stories/PageHeader.stories.tsx +11 -0
  90. package/src/stories/ResponsiveSurface.stories.tsx +47 -0
  91. package/src/stories/SwipableMobileCarousel.stories.tsx +304 -0
  92. package/src/stories/TokenGallery.stories.tsx +65 -0
  93. package/src/tokens/primitives.css +25 -0
package/dist/index.d.ts CHANGED
@@ -1,5 +1,35 @@
1
1
  import * as react from 'react';
2
- import { ReactNode, ButtonHTMLAttributes, SelectHTMLAttributes, ReactElement, CSSProperties, InputHTMLAttributes, TextareaHTMLAttributes, HTMLAttributes, TdHTMLAttributes, ThHTMLAttributes, LabelHTMLAttributes, ElementType, PointerEvent, KeyboardEvent, MouseEvent } from 'react';
2
+ import { ReactNode, ButtonHTMLAttributes, SelectHTMLAttributes, ReactElement, CSSProperties, InputHTMLAttributes, TextareaHTMLAttributes, HTMLAttributes, TdHTMLAttributes, ThHTMLAttributes, LabelHTMLAttributes, ElementType, PointerEvent, KeyboardEvent, MouseEvent, RefObject } from 'react';
3
+
4
+ interface LegalLinksProps {
5
+ privacyHref?: string;
6
+ tosHref?: string;
7
+ /** Renders a linked item. Defaults to a plain `<a href>`, which causes a
8
+ * full page reload — pass Next's `<Link>` (or equivalent) to keep
9
+ * navigation client-side. Not needed for a full-reload navigation away
10
+ * from a standalone auth page, but matters if this is ever mounted inside
11
+ * an SPA-like shell. */
12
+ renderLink?: (href: string, label: string) => ReactNode;
13
+ className?: string;
14
+ }
15
+ declare function LegalLinks({ privacyHref, tosHref, renderLink, className, }: LegalLinksProps): react.JSX.Element;
16
+
17
+ interface MarkdownProps {
18
+ /** Raw markdown source. Supports the subset used by Sovereign's own
19
+ * long-form content pages: headings (#/##/###), paragraphs, **bold**,
20
+ * *italic*, `code`, [links](url), unordered lists (-), and blockquotes
21
+ * (>), including soft-wrapped continuation lines within a paragraph or
22
+ * list item. Not a general-purpose CommonMark parser — no tables,
23
+ * images, ordered lists, or nested blocks. */
24
+ content: string;
25
+ className?: string;
26
+ }
27
+ /** Renders a constrained markdown subset (see MarkdownProps) as styled React
28
+ * elements built from Typography — no HTML string injection. Meant for
29
+ * long-form, first-party content pages (privacy policy, terms of service)
30
+ * sourced from a single markdown file, not for rendering arbitrary or
31
+ * user-supplied markdown. */
32
+ declare function Markdown({ content, className }: MarkdownProps): react.JSX.Element;
3
33
 
4
34
  type BadgeVariant = 'role' | 'status' | 'mono';
5
35
  type BadgeSize = 'sm' | 'md' | 'lg';
@@ -30,7 +60,7 @@ interface BadgeProps {
30
60
  */
31
61
  declare function Badge({ variant, size, status, uppercase, children, }: BadgeProps): react.JSX.Element;
32
62
 
33
- type IconName = 'house' | 'settings' | 'log-out' | 'chevron-right' | 'chevron-left' | 'chevron-down' | 'chevron-up' | 'x' | 'check' | 'plus' | 'trash-2' | 'pencil' | 'rotate-ccw' | 'search' | 'user' | 'shield' | 'lock' | 'eye' | 'eye-off' | 'mail' | 'bell' | 'activity' | 'package' | 'grid-2x2' | 'info' | 'alert-triangle' | 'circle-check' | 'circle-x' | 'calendar' | 'sliders-horizontal' | 'ellipsis-vertical' | 'file' | 'upload' | 'external-link' | 'banana' | 'apple' | 'carrot' | 'egg' | 'beef' | 'fish' | 'coffee' | 'wine' | 'beer' | 'cookie' | 'pizza' | 'candy' | 'salad' | 'milk' | 'drumstick' | 'croissant' | 'cup-soda' | 'spray-can' | 'snowflake' | 'shopping-basket';
63
+ type IconName = 'house' | 'settings' | 'log-out' | 'smartphone' | 'chevron-right' | 'chevron-left' | 'chevron-down' | 'chevron-up' | 'menu' | 'x' | 'check' | 'plus' | 'trash-2' | 'pencil' | 'rotate-ccw' | 'search' | 'user' | 'shield' | 'lock' | 'eye' | 'eye-off' | 'mail' | 'bell' | 'activity' | 'package' | 'grid-2x2' | 'info' | 'alert-triangle' | 'circle-check' | 'circle-x' | 'calendar' | 'sliders-horizontal' | 'ellipsis-vertical' | 'file' | 'upload' | 'external-link' | 'banana' | 'apple' | 'carrot' | 'egg' | 'beef' | 'fish' | 'coffee' | 'wine' | 'beer' | 'cookie' | 'pizza' | 'candy' | 'salad' | 'milk' | 'drumstick' | 'croissant' | 'cup-soda' | 'spray-can' | 'snowflake' | 'shopping-basket';
34
64
 
35
65
  /** Decorative icon — visually meaningful but described by surrounding text. */
36
66
  type DecorativeProps = {
@@ -374,6 +404,12 @@ interface DrawerProps {
374
404
  * "half screen or less" convention for Drawer, this is the only other
375
405
  * supported size; taller content should use `Sheet` instead. */
376
406
  snapHeight?: 'content' | 'half';
407
+ /** Renders a built-in `OverlayHeader` (title + close) below the grab
408
+ * handle. Omit when the content supplies its own header, or none is
409
+ * needed at all — e.g. the mobile shell's Apps drawer, whose grab handle
410
+ * and swipe-down/scrim-tap dismissal are enough on their own. Mirrors
411
+ * `Sheet`'s identical `title` prop. */
412
+ title?: string;
377
413
  children: ReactNode;
378
414
  }
379
415
  /**
@@ -398,7 +434,7 @@ interface DrawerProps {
398
434
  * scroll, the same reasoning behind the tasks plugin's edge-zone-only
399
435
  * swipe-to-reveal technique.
400
436
  */
401
- declare function Drawer({ open, onClose, 'aria-label': ariaLabel, snapHeight, children, }: DrawerProps): react.JSX.Element | null;
437
+ declare function Drawer({ open, onClose, 'aria-label': ariaLabel, snapHeight, title, children, }: DrawerProps): react.JSX.Element | null;
402
438
 
403
439
  interface OverlayHeaderProps {
404
440
  /** Overlay title, shown between the optional back button and the trailing
@@ -656,8 +692,132 @@ interface PageHeaderProps {
656
692
  description?: string;
657
693
  action?: ReactNode;
658
694
  className?: string;
695
+ /** Heading level for the title. Defaults to `1` for standalone use; pass
696
+ * `2` or `3` when the page already sits under a shell/plugin `<h1>`, to
697
+ * avoid a duplicate top-level heading. Visual style is unchanged at every
698
+ * level — only the rendered tag changes. */
699
+ headingLevel?: 1 | 2 | 3;
700
+ }
701
+ declare function PageHeader({ title, description, action, className, headingLevel, }: PageHeaderProps): react.JSX.Element;
702
+
703
+ interface PageContainerProps extends HTMLAttributes<HTMLDivElement> {
704
+ /** Constrains and centers content width. Adds no padding of its own — the
705
+ * runtime shell already pads plugin content. Defaults to 'md'. */
706
+ maxWidth?: 'sm' | 'md' | 'lg' | 'full';
707
+ children: ReactNode;
708
+ }
709
+ declare function PageContainer({ maxWidth, className, children, ...rest }: PageContainerProps): react.JSX.Element;
710
+
711
+ interface MobileHeaderProps extends HTMLAttributes<HTMLDivElement> {
712
+ /** The brand/logo element — already a complete, clickable link home (e.g. an
713
+ * instance logo wrapped in the consumer's own router `Link`). Always
714
+ * rendered. `MobileHeader` stays router-agnostic, so this is provided
715
+ * fully built rather than as a bare icon + href. Optional — omit it to get
716
+ * a default "S" badge (matching the runtime shell's own fallback brand
717
+ * icon when no instance logo is configured), sized and styled to the same
718
+ * 36px/radius-md convention a custom logo is expected to follow. */
719
+ logo?: ReactNode;
720
+ /** Optional contextual title rendered beside the logo (e.g. the active
721
+ * plugin's name). Absent by default — this is the one overridable part of
722
+ * the header. See RFC 0088. */
723
+ title?: string;
724
+ /** Notification bell — always rendered; not overridable.
725
+ * TODO: this is just an icon slot today (the consumer wires up its own
726
+ * click handler/content) — it doesn't yet accommodate a full notification
727
+ * feature (unread badge, dropdown panel, etc. — see the runtime shell's
728
+ * own NotificationBell). Deferred; revisit once a plugin actually needs
729
+ * more than a bare bell icon here. */
730
+ bell: ReactNode;
731
+ /** Avatar / account menu trigger — always rendered; not overridable. */
732
+ avatarMenu: ReactNode;
733
+ }
734
+ /**
735
+ * The mobile header bar: brand/logo (always shown), an optional contextual
736
+ * title, and a fixed bell + avatar-menu cluster. Owns its own chrome —
737
+ * background, border-bottom, sticky positioning, safe-area-aware padding,
738
+ * and a 768px max-width (so it never stretches full-bleed if mounted outside
739
+ * an actual mobile viewport) — matching the runtime shell's own mobile
740
+ * header exactly, since this component is meant to replace that hand-rolled
741
+ * markup. Presentational only — no data fetching; the consumer supplies
742
+ * `bell`/`avatarMenu` (and, optionally, `logo`) as already-wired components
743
+ * (their own sizing — 36px logo/avatar, 44px bell tap target — is the
744
+ * consumer's responsibility, matching the reference implementation in the
745
+ * runtime shell). See RFC 0088 for the immutable/overridable boundary this
746
+ * enforces.
747
+ */
748
+ declare function MobileHeader({ logo, title, bell, avatarMenu, className, ...rest }: MobileHeaderProps): react.JSX.Element;
749
+
750
+ interface FooterIcon {
751
+ icon: ReactNode;
752
+ label: string;
753
+ href?: string;
754
+ onClick?: () => void;
755
+ active?: boolean;
756
+ }
757
+ interface MobileFooterProps extends HTMLAttributes<HTMLElement> {
758
+ /** Opens the app drawer/launcher. Always rendered as the centered slot;
759
+ * not overridable. */
760
+ onOpenApps: () => void;
761
+ /** Custom launcher icon (e.g. a plugin-supplied image). Defaults to a
762
+ * generic grid icon. */
763
+ launcherIcon?: ReactNode;
764
+ /** Whether the launcher's own drawer is currently open (applies the
765
+ * pressed/active visual state). */
766
+ launcherOpen?: boolean;
767
+ /** 1 or 2 icons rendered to the left of the centered launcher. */
768
+ leftIcons: FooterIcon[];
769
+ /** 1 or 2 icons rendered to the right of the centered launcher. Must match
770
+ * `leftIcons.length` — the launcher stays visually centered, so a mismatch
771
+ * is flagged (dev-mode only, never thrown) rather than silently allowed. */
772
+ rightIcons: FooterIcon[];
773
+ }
774
+ /**
775
+ * The mobile footer bar: a centered, fixed "Apps" launcher flanked by 1 or 2
776
+ * overridable icons on each side (3 or 5 total, matching standard
777
+ * iOS/Android bottom-nav conventions). Owns its own chrome — background,
778
+ * border-top, safe-area-aware bottom padding, and a 768px max-width (so it
779
+ * never stretches full-bleed if mounted outside an actual mobile viewport)
780
+ * — matching the runtime shell's own mobile footer (MobileNav) exactly,
781
+ * since this component is meant to replace that hand-rolled markup.
782
+ * Presentational only — no data fetching; the consumer owns the
783
+ * drawer/overlay `onOpenApps` opens. See RFC 0088 for the
784
+ * immutable/overridable boundary this enforces.
785
+ */
786
+ declare function MobileFooter({ onOpenApps, launcherIcon, launcherOpen, leftIcons, rightIcons, className, ...rest }: MobileFooterProps): react.JSX.Element;
787
+
788
+ interface MobileAppsDrawerItem {
789
+ /** Stable identity for this tile — used as the React key. */
790
+ key: string;
791
+ icon: ReactNode;
792
+ label: string;
793
+ /** Renders an `<a>` when set; otherwise a `<button>` calling `onClick`. */
794
+ href?: string;
795
+ onClick?: () => void;
796
+ }
797
+ interface MobileAppsDrawerProps {
798
+ open: boolean;
799
+ onClose: () => void;
800
+ /** Accessible name for the drawer panel — required since there's no
801
+ * visible title to fall back to (Drawer's grab handle plus swipe-down /
802
+ * scrim-tap already cover dismissal, so no header row is rendered). */
803
+ 'aria-label': string;
804
+ /** The grid tiles — supplied by the consumer (e.g. installed plugins,
805
+ * a plugin's own sections). MobileAppsDrawer owns only the drawer
806
+ * chrome/grid layout, never the item list itself. */
807
+ items: MobileAppsDrawerItem[];
659
808
  }
660
- declare function PageHeader({ title, description, action, className }: PageHeaderProps): react.JSX.Element;
809
+ /**
810
+ * The mobile "Apps" drawer: a bottom sheet with a 3-column grid of icon
811
+ * tiles — same tile size (56px, radius 14px) and grid layout as the runtime
812
+ * shell's own Apps drawer (MobileNav's Drawer content), generalized so the
813
+ * tile list comes from the consumer (the shell passes installed plugins; a
814
+ * plugin composing its own MobileFooter launcher passes whatever its own
815
+ * "apps" are, e.g. a section index) rather than being hardcoded to plugins
816
+ * specifically. No header row (title/close button) — Drawer's own grab
817
+ * handle plus swipe-down/scrim-tap dismissal are enough, and a header ate
818
+ * into the limited vertical space a launcher grid needs most.
819
+ */
820
+ declare function MobileAppsDrawer({ open, onClose, items, ...rest }: MobileAppsDrawerProps): react.JSX.Element;
661
821
 
662
822
  interface EmptyStateProps {
663
823
  icon?: IconName;
@@ -668,6 +828,121 @@ interface EmptyStateProps {
668
828
  }
669
829
  declare function EmptyState({ icon, heading, description, action, className }: EmptyStateProps): react.JSX.Element;
670
830
 
831
+ interface OfflineGateProps {
832
+ children: ReactNode;
833
+ /** Defaults to a generic "this section" phrasing — pass the surface's own name. */
834
+ surfaceName?: string;
835
+ className?: string;
836
+ }
837
+ /**
838
+ * OfflineGate — blocks administrative/settings surfaces from rendering
839
+ * (possibly stale) cached content while the device has no network, showing an
840
+ * explanatory empty state instead (research 0012, epic task 2.32).
841
+ *
842
+ * Distinct from `OfflineBanner`: the banner is an informational overlay that
843
+ * coexists with normal content everywhere in the shell. This is a hard block
844
+ * for surfaces where operating against stale data is actively wrong — e.g. a
845
+ * Console user list or an Account billing page reflects a point-in-time
846
+ * snapshot the moment it's server-rendered, and any cached copy replayed
847
+ * later has no way to signal that it may already be stale. `OfflineGate`
848
+ * doesn't try to tell staleness apart from freshness; it removes the
849
+ * question by not rendering the content at all while offline.
850
+ *
851
+ * `children` is rendered unconditionally while online, so this adds no cost
852
+ * to the common case — it only decides what to show, never how the wrapped
853
+ * content itself is fetched or cached.
854
+ */
855
+ declare function OfflineGate({ children, surfaceName, className }: OfflineGateProps): react.JSX.Element;
856
+
857
+ interface DeviceOnlyGateProps {
858
+ children: ReactNode;
859
+ /** Defaults to a generic "this app" phrasing — pass the plugin's own name. */
860
+ surfaceName?: string;
861
+ /**
862
+ * The caller's own `isDeviceOnlyTierAvailable()` result (from
863
+ * `@sovereignfs/sdk/device-client`) — taken as a plain boolean rather than
864
+ * computed inside this component, so `@sovereignfs/ui` never depends on
865
+ * `@sovereignfs/sdk` (this package must stay usable standalone, outside the
866
+ * plugin runtime — RFC 0073).
867
+ */
868
+ available: boolean;
869
+ className?: string;
870
+ }
871
+ /**
872
+ * DeviceOnlyGate — blocks a `device-only`-tier plugin's content from
873
+ * rendering on a surface that cannot provide the durable, encrypted,
874
+ * device-auth-gated store that tier requires, showing an explanatory empty
875
+ * state instead of a broken screen (research 0012, epic tasks 2.33 + 3.36).
876
+ *
877
+ * A `device-only` plugin wraps its own root content in this the same way
878
+ * Console and Account wrap theirs in `OfflineGate` — an opt-in pattern each
879
+ * surface applies to itself, not a platform-level route gate. The launcher
880
+ * tile's own "Phone only" badge (`useOfflineTileState`) is advisory UI on top
881
+ * of this; this component is the actual gate, since a user can always reach a
882
+ * route directly (a bookmark, a deep link) without passing through the
883
+ * launcher tile at all.
884
+ *
885
+ * **Not a security boundary.** Availability here is a capability signal, not
886
+ * an authorization check — the real protection for `device-only` data is
887
+ * that it is encrypted and the key requires device auth to release (epic task
888
+ * 1.22), so it stays inaccessible regardless of whether this gate is present,
889
+ * bypassed, or simply never reached (e.g. a hand-crafted request against the
890
+ * plugin's own API routes).
891
+ */
892
+ declare function DeviceOnlyGate({ children, surfaceName, available, className, }: DeviceOnlyGateProps): react.JSX.Element;
893
+
894
+ /**
895
+ * `'checking'` covers the moment before the caller's own async status check
896
+ * resolves — same transitional-state pattern `EncryptionSection`'s local
897
+ * `LocalState` uses, so this gate never flashes an empty state before the
898
+ * real answer is known. `'no-device-auth'` is the hard-block case (RFC 0093
899
+ * §5, epic task 1.22): the environment supports the tier, but this device
900
+ * has no passcode, fingerprint, or face unlock configured, so a key cannot
901
+ * be created here at all — distinct from `'not-set-up'`, where it can.
902
+ */
903
+ type DeviceStorageKeyGateStatus = 'checking' | 'unsupported' | 'no-device-auth' | 'not-set-up' | 'set-up';
904
+ interface DeviceStorageKeyGateProps {
905
+ children: ReactNode;
906
+ /** Defaults to a generic "This app" phrasing — pass the plugin's own name. */
907
+ surfaceName?: string;
908
+ /**
909
+ * The caller's own `getDeviceStorageKeyStatus()` result (from
910
+ * `@sovereignfs/sdk/device-only-storage`) — taken as a plain status rather
911
+ * than computed inside this component, so `@sovereignfs/ui` never depends
912
+ * on `@sovereignfs/sdk` (same boundary `DeviceOnlyGate`'s `available` prop
913
+ * draws — this package must stay usable standalone, outside the plugin
914
+ * runtime — RFC 0073).
915
+ */
916
+ status: DeviceStorageKeyGateStatus;
917
+ /**
918
+ * Rendered under the "not set up" message — typically a link to Account →
919
+ * Security. Not built in here: this package depends on no router, so the
920
+ * caller supplies its own navigable element (a plain `<a>`, a framework
921
+ * `<Link>`, whatever the host app uses).
922
+ */
923
+ setupAction?: ReactNode;
924
+ className?: string;
925
+ }
926
+ /**
927
+ * DeviceStorageKeyGate — blocks a `device-only`-tier plugin's content until
928
+ * the user has set up their Device Storage Key (RFC 0093 §2, epic task
929
+ * 1.22). Distinct from `DeviceOnlyGate`: that one checks whether the *tier
930
+ * itself* is available on this surface (native shell, secure-storage bridge
931
+ * present); this one checks whether the user has actually completed the
932
+ * one-time, per-device enrollment that tier depends on. A `device-only`
933
+ * plugin typically wants both — `DeviceOnlyGate` outermost (no point
934
+ * checking enrollment on a surface that cannot offer the tier at all), this
935
+ * gate inside it.
936
+ *
937
+ * Enrollment is centralized and decoupled from any single plugin (RFC 0093
938
+ * §2): a plugin that finds no key set up does not run its own enrollment
939
+ * ceremony inline. It stops here and points the user at Account → Security —
940
+ * the one place enrollment happens for every `device-only` plugin on this
941
+ * device — via `setupAction`, the same opt-in, self-applied pattern
942
+ * `OfflineGate`/`DeviceOnlyGate` already use for their own surfaces.
943
+ */
944
+ declare function DeviceStorageKeyGate({ children, surfaceName, status, setupAction, className, }: DeviceStorageKeyGateProps): react.JSX.Element | null;
945
+
671
946
  interface SpinnerProps {
672
947
  size?: 'sm' | 'md' | 'lg';
673
948
  label?: string;
@@ -688,12 +963,25 @@ interface NavTabItem {
688
963
  href: string;
689
964
  active?: boolean;
690
965
  }
966
+ /** Props to spread onto a consumer-supplied link element — keeps NavTabs' own
967
+ * class names and a11y attributes in sync with whatever the consumer renders. */
968
+ interface NavTabLinkProps {
969
+ href: string;
970
+ className: string;
971
+ 'aria-current': 'page' | undefined;
972
+ children: ReactNode;
973
+ }
691
974
  interface NavTabsProps {
692
975
  items: NavTabItem[];
693
976
  className?: string;
694
977
  'aria-label'?: string;
978
+ /** Render a tab as something other than a plain `<a href>` — e.g. Next's
979
+ * `<Link replace>` inside an overlay-shell plugin, where a full page
980
+ * navigation would break the dialog's history contract. Receives the item
981
+ * plus the class name/aria attributes NavTabs would otherwise apply. */
982
+ renderLink?: (item: NavTabItem, linkProps: NavTabLinkProps) => ReactNode;
695
983
  }
696
- declare function NavTabs({ items, className, 'aria-label': ariaLabel }: NavTabsProps): react.JSX.Element;
984
+ declare function NavTabs({ items, className, 'aria-label': ariaLabel, renderLink }: NavTabsProps): react.JSX.Element;
697
985
 
698
986
  interface TooltipProps {
699
987
  content: string;
@@ -1711,6 +1999,57 @@ declare const MOBILE_BREAKPOINT_PX = 768;
1711
1999
  */
1712
2000
  declare function useIsMobile(breakpointPx?: number): boolean;
1713
2001
 
2002
+ /**
2003
+ * useIsOffline — whether the browser currently has no network connection.
2004
+ *
2005
+ * SSR-safe: always initialises to `false` (online) so the server-rendered HTML
2006
+ * matches the first client render, then reads the real `navigator.onLine`
2007
+ * value in `useEffect`. This picks up the case where the page loaded already
2008
+ * offline (e.g. served from the service-worker cache) at the cost of an
2009
+ * imperceptible one-frame delay before the offline state appears — the same
2010
+ * trade-off `OfflineBanner` makes, which this hook was extracted from so every
2011
+ * consumer shares one connectivity source instead of re-deriving it slightly
2012
+ * differently (research 0012, epic task 2.32).
2013
+ *
2014
+ * Reflects only `navigator.onLine` — true whenever the OS reports a network
2015
+ * interface is up, even without real upstream connectivity (e.g. Wi-Fi with no
2016
+ * internet). That is a known limitation of the browser API itself, not of this
2017
+ * hook; there is currently no better cross-browser signal available.
2018
+ */
2019
+ declare function useIsOffline(): boolean;
2020
+
2021
+ /**
2022
+ * The two reasons an app tile can be non-interactive, kept structurally
2023
+ * distinct because they have different causes and must never read as the same
2024
+ * thing to a user (research 0012, epic task 2.33):
2025
+ *
2026
+ * - `'connectivity-dimmed'` — a no-offline-tier app, dimmed only while the
2027
+ * device is **actually offline right now**. Reactive and temporary; clears
2028
+ * the instant connectivity returns.
2029
+ * - `'capability-restricted'` — a `device-only` app on a surface without the
2030
+ * durable, encrypted, device-auth-gated store it needs. Static and
2031
+ * unrelated to connectivity — it stays restricted even when fully online,
2032
+ * and must never say "offline" to a user who plainly isn't.
2033
+ */
2034
+ type OfflineTileState = 'connectivity-dimmed' | 'capability-restricted' | null;
2035
+ /**
2036
+ * Which of the two states, if any, applies to an app tile given its declared
2037
+ * offline tier.
2038
+ *
2039
+ * `deviceOnlyAvailable` is the caller's own
2040
+ * `isDeviceOnlyTierAvailable()` result (from
2041
+ * `@sovereignfs/sdk/device-client`) — deliberately taken as a plain boolean
2042
+ * parameter rather than computed inside this hook, so `@sovereignfs/ui` never
2043
+ * depends on `@sovereignfs/sdk` (this package must stay usable standalone,
2044
+ * outside the plugin runtime — RFC 0073).
2045
+ *
2046
+ * An `offline-first` app is never restricted by either state: that tier's
2047
+ * whole point is working with no network, so it stays fully interactive both
2048
+ * online and offline. A `device-only` app that *is* available behaves the
2049
+ * same way — the restriction is about availability, not connectivity.
2050
+ */
2051
+ declare function useOfflineTileState(offline: 'offline-first' | 'device-only' | undefined, deviceOnlyAvailable: boolean): OfflineTileState;
2052
+
1714
2053
  interface CommitOnEnterOrBlurHandlers {
1715
2054
  onKeyDown: (e: KeyboardEvent<HTMLInputElement | HTMLTextAreaElement>) => void;
1716
2055
  onBlur: () => void;
@@ -1739,4 +2078,313 @@ interface CommitOnEnterOrBlurHandlers {
1739
2078
  */
1740
2079
  declare function useCommitOnEnterOrBlur(onCommit: () => void): CommitOnEnterOrBlurHandlers;
1741
2080
 
1742
- export { Accordion, type AccordionItem, type AccordionProps, Alert, type AlertProps, type AlertVariant, AspectRatio, type AspectRatioProps, Avatar, type AvatarProps, Badge, type BadgeProps, type BadgeStatus, type BadgeVariant, BalanceChip, type BalanceChipProps, type BannerVariant, Breadcrumb, type BreadcrumbItem, type BreadcrumbProps, Button, ButtonGroup, type ButtonGroupProps, type ButtonProps, type ButtonSize, type ButtonVariant, Calendar, type CalendarProps, Card, type CardProps, CheckableListRow, type CheckableListRowProps, Checkbox, type CheckboxProps, CodeTextarea, type CodeTextareaProps, Collapsible, type CollapsibleProps, Combobox, type ComboboxOption, type ComboboxProps, Command, type CommandItem, type CommandProps, type CommitOnEnterOrBlurHandlers, ConfirmDialog, type ConfirmDialogProps, ContextMenu, type ContextMenuProps, CurrencyInput, type CurrencyInputProps, DataTable, type DataTableColumn, type DataTableProps, DatePicker, type DatePickerProps, Dialog, type DialogProps, type DialogSize, DragHandleRow, type DragHandleRowProps, Drawer, type DrawerProps, EmptyState, type EmptyStateProps, FileDropzone, type FileDropzoneProps, FormField, type FormFieldProps, type FormFieldRenderProps, HoverCard, type HoverCardProps, Icon, type IconName, IconPicker, type IconPickerProps, type IconProps, Input, type InputProps, Item, type ItemProps, Kbd, type KbdProps, Label, type LabelProps, type LongPressHandlers, MOBILE_BREAKPOINT_PX, Marker, type MarkerProps, MemberMultiSelect, type MemberMultiSelectOption, type MemberMultiSelectProps, Menu, MenuEntries, type MenuEntry, type MenuItem, type MenuLabel, type MenuProps, type MenuSeparator, Menubar, type MenubarMenu, type MenubarProps, Message, type MessageProps, MessageScroller, type MessageScrollerProps, type MessageSender, type NavTabItem, NavTabs, type NavTabsProps, NavigationMenu, type NavigationMenuItem, type NavigationMenuProps, OverlayHeader, type OverlayHeaderProps, PageHeader, type PageHeaderProps, Pagination, type PaginationProps, Popover, type PopoverProps, Progress, type ProgressProps, QuantityStepper, type QuantityStepperProps, RadioGroup, type RadioGroupItem, type RadioGroupProps, type ResizableDirection, ResizableHandle, type ResizableHandleProps, ResizablePanel, ResizablePanelGroup, type ResizablePanelGroupProps, type ResizablePanelProps, ScrollArea, type ScrollAreaProps, SegmentedControl, type SegmentedControlProps, type SegmentedOption, Select, type SelectProps, Sheet, type SheetProps, Slider, type SliderProps, type SortDirection, Spinner, type SpinnerProps, type SplitMethod, SplitMethodSelector, type SplitMethodSelectorProps, SplitPane, type SplitPaneProps, StatusBadge, type StatusBadgeProps, type StatusBadgeStatus, SuggestionInput, type SuggestionInputProps, type SuggestionOption, SystemBanner, type SystemBannerProps, type TabItem, Table, TableBody, TableCell, TableHead, TableHeaderCell, type TableProps, TableRow, Tabs, type TabsProps, TagInput, type TagInputProps, Textarea, type TextareaProps, type ToastContextValue, type ToastItem, ToastProvider, Toggle, type ToggleProps, Tooltip, type TooltipProps, Typography, type TypographyProps, type TypographyVariant, type UseLongPressOptions, useCommitOnEnterOrBlur, useDoubleTapHandler, useIsMobile, useLongPress, useOverlaySecondRow, useSingleOrDoubleTap, useToast };
2081
+ interface UseSwipeRevealOptions {
2082
+ /** Max reveal distance in px — the row translates by `-revealWidth` when open. */
2083
+ revealWidth: number;
2084
+ /** Whether the reveal is currently open. Controlled by the caller so a
2085
+ * list of rows can coordinate "only one open at a time" — this hook has
2086
+ * no open state of its own. */
2087
+ open: boolean;
2088
+ /** Called when a drag releases past the halfway point — the caller should
2089
+ * set `open` to true. */
2090
+ onOpen: () => void;
2091
+ /** Called when a drag releases before the halfway point — the caller
2092
+ * should set `open` to false. */
2093
+ onClose: () => void;
2094
+ /** Skip entirely (hooks can't be called conditionally). */
2095
+ disabled?: boolean;
2096
+ }
2097
+ interface UseSwipeRevealHandlers {
2098
+ onPointerDown: (e: PointerEvent) => void;
2099
+ onPointerMove: (e: PointerEvent) => void;
2100
+ onPointerUp: (e: PointerEvent) => void;
2101
+ onPointerCancel: (e: PointerEvent) => void;
2102
+ }
2103
+ interface UseSwipeRevealResult {
2104
+ /** Attach to the swipeable row element — the hook writes a live transform
2105
+ * to it directly during the drag (not via React state) so the row tracks
2106
+ * the finger at 60fps with no re-render per pointermove. */
2107
+ rowRef: RefObject<HTMLDivElement | null>;
2108
+ /** Spread onto the row's pointer event handlers. */
2109
+ handlers: UseSwipeRevealHandlers;
2110
+ }
2111
+ /**
2112
+ * useSwipeReveal — horizontal swipe-to-reveal (e.g. Done/Delete actions
2113
+ * behind a list row), extracted from `sovereign-tasks`'s `TaskItem`/
2114
+ * `ListSidebar`, which independently hand-rolled the identical gesture twice
2115
+ * with only `revealWidth` differing (RFC 0079, epic task 9.20).
2116
+ *
2117
+ * Axis-locks on the first 8px of movement (so a vertical scroll or the
2118
+ * carousel's own horizontal swipe-between-slides gesture isn't hijacked by a
2119
+ * diagonal drag) and only resolves to open/closed on release, past the
2120
+ * halfway point of `revealWidth`.
2121
+ */
2122
+ declare function useSwipeReveal({ revealWidth, open, onOpen, onClose, disabled, }: UseSwipeRevealOptions): UseSwipeRevealResult;
2123
+
2124
+ interface UseSnapCarouselOptions {
2125
+ /** Total number of slides — settle detection clamps to this range. */
2126
+ itemCount: number;
2127
+ /** Called when the user's scroll gesture settles on a different slide
2128
+ * index than the last one reported. */
2129
+ onSettle?: (index: number) => void;
2130
+ /** Debounce delay (ms) after the last `scroll` event before checking where
2131
+ * the container settled. Default 120 — matches the value this hook was
2132
+ * extracted from. Debounced rather than keyed to the `scrollend` event,
2133
+ * which pre-17.4 iOS Safari/WKWebView doesn't support. */
2134
+ debounceMs?: number;
2135
+ }
2136
+ interface UseSnapCarouselResult {
2137
+ /** Attach to the `scroll-snap-type: x` scroll container. */
2138
+ scrollRef: RefObject<HTMLDivElement | null>;
2139
+ /** Imperatively scrolls the container to a slide index. */
2140
+ scrollToIndex: (index: number, behavior?: ScrollBehavior) => void;
2141
+ }
2142
+ /**
2143
+ * useSnapCarousel — debounced "settled slide" detection over a native
2144
+ * `scroll-snap-type: x` container, extracted from `sovereign-tasks`'s
2145
+ * `MobileTasksCarousel` and independently reimplemented in
2146
+ * `sovereign-shopper`'s `MobileShopperCarousel` (RFC 0079, epic task 9.20).
2147
+ * Gives swipe-between-slides physics for free via native scroll-snap, with
2148
+ * this hook only handling "which slide did we land on."
2149
+ */
2150
+ declare function useSnapCarousel({ itemCount, onSettle, debounceMs, }: UseSnapCarouselOptions): UseSnapCarouselResult;
2151
+
2152
+ interface UseResponsiveLayoutOptions<TWeb, TMobile> {
2153
+ web: TWeb;
2154
+ mobile: TMobile;
2155
+ breakpointPx?: number;
2156
+ }
2157
+ interface UseResponsiveLayoutResult<TWeb, TMobile> {
2158
+ isMobile: boolean;
2159
+ value: TWeb | TMobile;
2160
+ }
2161
+ /**
2162
+ * useResponsiveLayout — picks between a `web` and `mobile` value for the
2163
+ * current viewport, built on `useIsMobile` (same breakpoint/SSR-safe-default
2164
+ * behavior, not reimplemented here). Generic over the value type so it works
2165
+ * for JSX (the common case — forking an entire component tree, not just a
2166
+ * class name) or any other per-breakpoint value a plugin wants to pick
2167
+ * without repeating the isMobile-ternary boilerplate every call site.
2168
+ *
2169
+ * This formalizes the `if (isMobile) return <mobile/>; return <web/>;` fork
2170
+ * plugins have hand-rolled per-plugin (e.g. a `MobileAwareShell` component
2171
+ * that swaps an entire routed page tree for a client-only mobile one) —
2172
+ * see `ResponsiveSurface` for the equivalent as a plain JSX component.
2173
+ */
2174
+ declare function useResponsiveLayout<TWeb, TMobile>({ web, mobile, breakpointPx, }: UseResponsiveLayoutOptions<TWeb, TMobile>): UseResponsiveLayoutResult<TWeb, TMobile>;
2175
+
2176
+ interface UseCarouselRouteSyncOptions {
2177
+ /** Maps the current pathname to a slide index — the caller's own routing
2178
+ * logic (e.g. special-casing a virtual "starred" slide), unchanged from
2179
+ * whatever a plugin already has today. This hook has no idea what a
2180
+ * plugin's routes look like, by design — it never imports a router. */
2181
+ indexForPathname: (pathname: string) => number;
2182
+ /** Inverse mapping — given a settled slide index, the path to navigate to. */
2183
+ pathForIndex: (index: number) => string;
2184
+ /** The current pathname. The caller reads this via their own router (e.g.
2185
+ * `usePathname()` from `next/navigation`) and passes it in every render —
2186
+ * this package stays framework-generic and has zero runtime dependencies. */
2187
+ pathname: string;
2188
+ /** Called when this hook wants to navigate — typically
2189
+ * `(path) => router.replace(path, { scroll: false })`. */
2190
+ onNavigate: (path: string) => void;
2191
+ }
2192
+ interface UseCarouselRouteSyncResult {
2193
+ /** Feed directly into SwipableMobileCarousel's `activeIndex` prop. */
2194
+ activeIndex: number;
2195
+ /** Feed directly into SwipableMobileCarousel's `onSettle` prop. */
2196
+ onSettle: (index: number) => void;
2197
+ }
2198
+ /**
2199
+ * useCarouselRouteSync — centralizes the pathname↔slide-index mapping and the
2200
+ * "was this pathname change our own settle, or an external navigation"
2201
+ * distinction that carousel-driving plugins otherwise hand-roll per plugin
2202
+ * (a tapped `<Link>`, browser back/forward, vs. this hook's own
2203
+ * `router.replace` after a swipe settles). Router-agnostic on purpose: the
2204
+ * caller owns the actual `usePathname()`/`router.replace()` calls and only
2205
+ * hands this hook plain values/callbacks.
2206
+ *
2207
+ * Deliberately does not own neighbor-prefetch (SwipableMobileCarousel's
2208
+ * `prefetchDistance`/mount-window covers that) or scroll mechanics (the
2209
+ * carousel component reacts to `activeIndex` changes itself) — this hook is
2210
+ * pathname↔index mapping only, keeping routing glue decoupled from
2211
+ * data-fetching and DOM scrolling.
2212
+ */
2213
+ declare function useCarouselRouteSync({ indexForPathname, pathForIndex, pathname, onNavigate, }: UseCarouselRouteSyncOptions): UseCarouselRouteSyncResult;
2214
+
2215
+ interface ResponsiveSurfaceProps {
2216
+ web: ReactNode;
2217
+ mobile: ReactNode;
2218
+ breakpointPx?: number;
2219
+ }
2220
+ /**
2221
+ * ResponsiveSurface — thin JSX convenience wrapper around
2222
+ * `useResponsiveLayout` for the common two-tree case: render an entirely
2223
+ * different component tree below the breakpoint, not a CSS squeeze of the
2224
+ * same one. Renders only whichever side is active — the other tree is never
2225
+ * mounted.
2226
+ *
2227
+ * This does not replace a plugin's own shell component (e.g. a
2228
+ * `MobileAwareShell` that also threads plugin-specific props like a refresh
2229
+ * signal) — those stay a thin per-plugin wrapper around this.
2230
+ */
2231
+ declare function ResponsiveSurface({ web, mobile, breakpointPx }: ResponsiveSurfaceProps): react.JSX.Element;
2232
+
2233
+ interface SwipableMobileCarouselDotsProps {
2234
+ count: number;
2235
+ activeIndex: number;
2236
+ onJump: (index: number) => void;
2237
+ /** Per-dot accessible names, e.g. ["Lists", "Starred", "Groceries"]. Falls
2238
+ * back to "Slide N of count" for any index without one. */
2239
+ labels?: (string | undefined)[];
2240
+ /** Labels the whole group — required, no generic fallback (mirrors
2241
+ * SwipableMobileCarousel's own required aria-label). */
2242
+ 'aria-label': string;
2243
+ className?: string;
2244
+ }
2245
+ /**
2246
+ * SwipableMobileCarouselDots — a real, tappable, labeled slide indicator.
2247
+ * Standalone (not carousel-only): reusable anywhere a set of positions needs
2248
+ * a dot indicator (e.g. an image gallery, an onboarding stepper), and is also
2249
+ * SwipableMobileCarousel's default `renderIndicator`.
2250
+ *
2251
+ * Replaces the `aria-hidden`, non-interactive dots pattern both
2252
+ * sovereign-tasks and sovereign-shopper currently hand-roll — every dot here
2253
+ * is a real `role="tab"` button with its own accessible name, reachable by
2254
+ * keyboard. No roving tabIndex/arrow-key handling: `Tabs` (this library's
2255
+ * other `role="tablist"` component) has none either, so this stays
2256
+ * consistent with it rather than gold-plating past its sibling.
2257
+ */
2258
+ declare function SwipableMobileCarouselDots({ count, activeIndex, onJump, labels, 'aria-label': ariaLabel, className, }: SwipableMobileCarouselDotsProps): react.JSX.Element;
2259
+
2260
+ interface SwipableMobileCarouselIndicatorProps {
2261
+ count: number;
2262
+ activeIndex: number;
2263
+ labels: (string | undefined)[];
2264
+ onJump: (index: number) => void;
2265
+ }
2266
+ interface SwipableMobileCarouselProps {
2267
+ /** Controlled active slide index — the caller owns this (typically via
2268
+ * useCarouselRouteSync, or plain useState). */
2269
+ activeIndex: number;
2270
+ /** Called when a swipe gesture settles on a different index, or when the
2271
+ * reorder-jump fix re-snaps after the active slide's position changes.
2272
+ * The caller is responsible for updating `activeIndex` in response. */
2273
+ onSettle: (index: number) => void;
2274
+ /** How many slides on each side of the active one stay mounted. Default 1
2275
+ * matches both sovereign-tasks' and sovereign-shopper's existing "swipe
2276
+ * never shows a spinner" behavior. Slides outside this window keep their
2277
+ * DOM slot (so scroll-snap's position-based indexing stays correct) but
2278
+ * their children are not mounted at all. */
2279
+ prefetchDistance?: number;
2280
+ /** Passed straight through to the internal useSnapCarousel. */
2281
+ settleDebounceMs?: number;
2282
+ /** Required — labels the scroller region for assistive tech. No generic
2283
+ * fallback, since one would be wrong for every actual consumer. */
2284
+ 'aria-label': string;
2285
+ /** Renders the indicator. Defaults to SwipableMobileCarouselDots. Pass
2286
+ * `null` to render no indicator at all. */
2287
+ renderIndicator?: null | ((props: SwipableMobileCarouselIndicatorProps) => ReactNode);
2288
+ className?: string;
2289
+ /** Must be SwipableMobileCarouselSlide elements (nullish/boolean children
2290
+ * are safely skipped for conditional slides). A dev-mode warning is
2291
+ * logged for any other child type — see resolveSlides below. */
2292
+ children: ReactNode;
2293
+ }
2294
+ /**
2295
+ * SwipableMobileCarousel — a compound component for swiping between full-
2296
+ * width slides (each an independent route/view on mobile), wrapping
2297
+ * useSnapCarousel. This library's first compound component: every other
2298
+ * component here is flat/prop-based, but slide content is large, arbitrary,
2299
+ * per-plugin subtrees that need a header-known/body-loading split an
2300
+ * array-of-objects prop would only express with worse JSX ergonomics.
2301
+ *
2302
+ * Owns rendering and mount-window mechanics only — it has no opinion on
2303
+ * where slide data lives. Do NOT: (a) aggregate cross-slide data inside a
2304
+ * SwipableMobileCarouselSlideBody (that recomputes on every slide render
2305
+ * regardless of which slide is active — belongs in the parent, which
2306
+ * already knows every slide exists); (b) mount a detail overlay (Sheet,
2307
+ * Dialog) inside a Slide's children — mount it as a sibling of
2308
+ * SwipableMobileCarousel instead, controlled by the same state that would
2309
+ * otherwise drive a routed page's overlay. sovereign-tasks' current
2310
+ * hand-rolled carousel does both and is measurably laggier than
2311
+ * sovereign-shopper's equivalent, which keeps its overlay in the routed pane.
2312
+ */
2313
+ declare function SwipableMobileCarousel({ activeIndex, onSettle, prefetchDistance, settleDebounceMs, 'aria-label': ariaLabel, renderIndicator, className, children, }: SwipableMobileCarouselProps): react.JSX.Element;
2314
+
2315
+ interface SwipableMobileCarouselSlideProps {
2316
+ /** Stable identity for this slide, independent of its position among
2317
+ * siblings — required. This is what makes SwipableMobileCarousel's
2318
+ * reorder-jump fix possible: a plain array index can't distinguish "this
2319
+ * slide moved to position 2" from "this slide was removed and a
2320
+ * different one is now at position 2." Reuse whatever id the caller
2321
+ * already keys their `.map()` on (e.g. a list's own id). */
2322
+ slideKey: string;
2323
+ /** Accessible label surfaced to the default dots indicator (e.g.
2324
+ * "Groceries"). Falls back to "Slide N of count" if omitted — omitting it
2325
+ * logs a dev-mode warning, since every real slide usually has a title
2326
+ * available synchronously already. */
2327
+ label?: string;
2328
+ /** SwipableMobileCarouselSlideHeader/Body/Footer, in any order/subset. */
2329
+ children: ReactNode;
2330
+ }
2331
+ /**
2332
+ * SwipableMobileCarouselSlide — a single slide of a SwipableMobileCarousel.
2333
+ * Renders its children only while within the carousel's mount window (the
2334
+ * active slide ± its configured prefetchDistance); outside that window it
2335
+ * renders nothing, matching both sovereign-tasks and sovereign-shopper's
2336
+ * existing "never fetch/render a non-neighbor slide" behavior, now
2337
+ * centralized instead of hand-rolled per plugin.
2338
+ */
2339
+ declare function SwipableMobileCarouselSlide({ children }: SwipableMobileCarouselSlideProps): react.JSX.Element | null;
2340
+
2341
+ interface SwipableMobileCarouselSlideHeaderProps {
2342
+ children: ReactNode;
2343
+ className?: string;
2344
+ }
2345
+ /**
2346
+ * SwipableMobileCarouselSlideHeader — always renders its children, no
2347
+ * loading concept. Meant for content already known synchronously (a list's
2348
+ * title, color, item count from data the caller already has) so it can
2349
+ * render immediately even while SwipableMobileCarouselSlideBody's own
2350
+ * content is still loading — see that component's doc comment.
2351
+ */
2352
+ declare function SwipableMobileCarouselSlideHeader({ children, className, }: SwipableMobileCarouselSlideHeaderProps): react.JSX.Element;
2353
+
2354
+ interface SwipableMobileCarouselSlideBodyProps {
2355
+ children: ReactNode;
2356
+ /** While true, renders `loadingFallback` INSTEAD OF children — scoped to
2357
+ * just this region, not the whole slide. This is what fixes the
2358
+ * "whole slide blanks out, title included, until its own fetch resolves"
2359
+ * bug: a caller renders `<SwipableMobileCarouselSlideHeader>` from
2360
+ * already-known metadata unconditionally, and gates only this component's
2361
+ * `loading` on the slower fetch — the natural way to compose
2362
+ * Header/Body/Footer as siblings already produces that behavior, instead
2363
+ * of gating the entire slide behind one boolean. */
2364
+ loading?: boolean;
2365
+ loadingFallback?: ReactNode;
2366
+ className?: string;
2367
+ }
2368
+ /**
2369
+ * SwipableMobileCarouselSlideBody — the scrollable region of a slide.
2370
+ *
2371
+ * Deliberately does not set `touch-action: pan-y` on its scroll container
2372
+ * (see SwipableMobileCarouselSlideParts.module.css's `.body` rule) — that
2373
+ * would intersect-to-empty against the carousel's own horizontal
2374
+ * touch-action handling (docs/architecture-rules.md's touch-action
2375
+ * intersection rule), breaking both the vertical scroll here and the
2376
+ * horizontal swipe between slides.
2377
+ */
2378
+ declare function SwipableMobileCarouselSlideBody({ children, loading, loadingFallback, className, }: SwipableMobileCarouselSlideBodyProps): react.JSX.Element;
2379
+
2380
+ interface SwipableMobileCarouselSlideFooterProps {
2381
+ children: ReactNode;
2382
+ className?: string;
2383
+ }
2384
+ /**
2385
+ * SwipableMobileCarouselSlideFooter — always renders its children, no
2386
+ * loading concept. See SwipableMobileCarouselSlideHeader's doc comment.
2387
+ */
2388
+ declare function SwipableMobileCarouselSlideFooter({ children, className, }: SwipableMobileCarouselSlideFooterProps): react.JSX.Element;
2389
+
2390
+ export { Accordion, type AccordionItem, type AccordionProps, Alert, type AlertProps, type AlertVariant, AspectRatio, type AspectRatioProps, Avatar, type AvatarProps, Badge, type BadgeProps, type BadgeStatus, type BadgeVariant, BalanceChip, type BalanceChipProps, type BannerVariant, Breadcrumb, type BreadcrumbItem, type BreadcrumbProps, Button, ButtonGroup, type ButtonGroupProps, type ButtonProps, type ButtonSize, type ButtonVariant, Calendar, type CalendarProps, Card, type CardProps, CheckableListRow, type CheckableListRowProps, Checkbox, type CheckboxProps, CodeTextarea, type CodeTextareaProps, Collapsible, type CollapsibleProps, Combobox, type ComboboxOption, type ComboboxProps, Command, type CommandItem, type CommandProps, type CommitOnEnterOrBlurHandlers, ConfirmDialog, type ConfirmDialogProps, ContextMenu, type ContextMenuProps, CurrencyInput, type CurrencyInputProps, DataTable, type DataTableColumn, type DataTableProps, DatePicker, type DatePickerProps, DeviceOnlyGate, type DeviceOnlyGateProps, DeviceStorageKeyGate, type DeviceStorageKeyGateProps, type DeviceStorageKeyGateStatus, Dialog, type DialogProps, type DialogSize, DragHandleRow, type DragHandleRowProps, Drawer, type DrawerProps, EmptyState, type EmptyStateProps, FileDropzone, type FileDropzoneProps, type FooterIcon, FormField, type FormFieldProps, type FormFieldRenderProps, HoverCard, type HoverCardProps, Icon, type IconName, IconPicker, type IconPickerProps, type IconProps, Input, type InputProps, Item, type ItemProps, Kbd, type KbdProps, Label, type LabelProps, LegalLinks, type LegalLinksProps, type LongPressHandlers, MOBILE_BREAKPOINT_PX, Markdown, type MarkdownProps, Marker, type MarkerProps, MemberMultiSelect, type MemberMultiSelectOption, type MemberMultiSelectProps, Menu, MenuEntries, type MenuEntry, type MenuItem, type MenuLabel, type MenuProps, type MenuSeparator, Menubar, type MenubarMenu, type MenubarProps, Message, type MessageProps, MessageScroller, type MessageScrollerProps, type MessageSender, MobileAppsDrawer, type MobileAppsDrawerItem, type MobileAppsDrawerProps, MobileFooter, type MobileFooterProps, MobileHeader, type MobileHeaderProps, type NavTabItem, type NavTabLinkProps, NavTabs, type NavTabsProps, NavigationMenu, type NavigationMenuItem, type NavigationMenuProps, OfflineGate, type OfflineGateProps, type OfflineTileState, OverlayHeader, type OverlayHeaderProps, PageContainer, type PageContainerProps, PageHeader, type PageHeaderProps, Pagination, type PaginationProps, Popover, type PopoverProps, Progress, type ProgressProps, QuantityStepper, type QuantityStepperProps, RadioGroup, type RadioGroupItem, type RadioGroupProps, type ResizableDirection, ResizableHandle, type ResizableHandleProps, ResizablePanel, ResizablePanelGroup, type ResizablePanelGroupProps, type ResizablePanelProps, ResponsiveSurface, type ResponsiveSurfaceProps, ScrollArea, type ScrollAreaProps, SegmentedControl, type SegmentedControlProps, type SegmentedOption, Select, type SelectProps, Sheet, type SheetProps, Slider, type SliderProps, type SortDirection, Spinner, type SpinnerProps, type SplitMethod, SplitMethodSelector, type SplitMethodSelectorProps, SplitPane, type SplitPaneProps, StatusBadge, type StatusBadgeProps, type StatusBadgeStatus, SuggestionInput, type SuggestionInputProps, type SuggestionOption, SwipableMobileCarousel, SwipableMobileCarouselDots, type SwipableMobileCarouselDotsProps, type SwipableMobileCarouselIndicatorProps, type SwipableMobileCarouselProps, SwipableMobileCarouselSlide, SwipableMobileCarouselSlideBody, type SwipableMobileCarouselSlideBodyProps, SwipableMobileCarouselSlideFooter, type SwipableMobileCarouselSlideFooterProps, SwipableMobileCarouselSlideHeader, type SwipableMobileCarouselSlideHeaderProps, type SwipableMobileCarouselSlideProps, SystemBanner, type SystemBannerProps, type TabItem, Table, TableBody, TableCell, TableHead, TableHeaderCell, type TableProps, TableRow, Tabs, type TabsProps, TagInput, type TagInputProps, Textarea, type TextareaProps, type ToastContextValue, type ToastItem, ToastProvider, Toggle, type ToggleProps, Tooltip, type TooltipProps, Typography, type TypographyProps, type TypographyVariant, type UseCarouselRouteSyncOptions, type UseCarouselRouteSyncResult, type UseLongPressOptions, type UseResponsiveLayoutOptions, type UseResponsiveLayoutResult, type UseSnapCarouselOptions, type UseSnapCarouselResult, type UseSwipeRevealHandlers, type UseSwipeRevealOptions, type UseSwipeRevealResult, useCarouselRouteSync, useCommitOnEnterOrBlur, useDoubleTapHandler, useIsMobile, useIsOffline, useLongPress, useOfflineTileState, useOverlaySecondRow, useResponsiveLayout, useSingleOrDoubleTap, useSnapCarousel, useSwipeReveal, useToast };