@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.
- package/dist/LegalLinks.module.css +24 -0
- package/dist/Markdown.module.css +49 -0
- package/dist/MobileAppsDrawer.module.css +81 -0
- package/dist/MobileFooter.module.css +73 -0
- package/dist/MobileHeader.module.css +63 -0
- package/dist/PageContainer.module.css +20 -0
- package/dist/SwipableMobileCarousel.module.css +55 -0
- package/dist/SwipableMobileCarouselDots.module.css +49 -0
- package/dist/SwipableMobileCarouselSlideParts.module.css +29 -0
- package/dist/index.d.ts +654 -6
- package/dist/index.js +1856 -1133
- package/dist/tokens/primitives.css +25 -0
- package/package.json +13 -9
- package/src/components/Badge/Badge.tsx +1 -7
- package/src/components/DeviceOnlyGate/DeviceOnlyGate.tsx +58 -0
- package/src/components/DeviceOnlyGate/__tests__/DeviceOnlyGate.test.tsx +45 -0
- package/src/components/DeviceStorageKeyGate/DeviceStorageKeyGate.tsx +100 -0
- package/src/components/DeviceStorageKeyGate/__tests__/DeviceStorageKeyGate.test.tsx +119 -0
- package/src/components/Dialog/Dialog.tsx +8 -59
- package/src/components/Drawer/Drawer.stories.tsx +44 -0
- package/src/components/Drawer/Drawer.tsx +18 -56
- package/src/components/Drawer/__tests__/Drawer.test.tsx +22 -0
- package/src/components/Icon/icons/index.ts +8 -0
- package/src/components/Icon/icons/menu.tsx +22 -0
- package/src/components/Icon/icons/smartphone.tsx +21 -0
- package/src/components/LegalLinks/LegalLinks.module.css +24 -0
- package/src/components/LegalLinks/LegalLinks.stories.tsx +22 -0
- package/src/components/LegalLinks/LegalLinks.tsx +40 -0
- package/src/components/Markdown/Markdown.module.css +49 -0
- package/src/components/Markdown/Markdown.stories.tsx +30 -0
- package/src/components/Markdown/Markdown.tsx +168 -0
- package/src/components/MobileAppsDrawer/MobileAppsDrawer.module.css +81 -0
- package/src/components/MobileAppsDrawer/MobileAppsDrawer.tsx +78 -0
- package/src/components/MobileAppsDrawer/__tests__/MobileAppsDrawer.test.tsx +80 -0
- package/src/components/MobileFooter/MobileFooter.module.css +73 -0
- package/src/components/MobileFooter/MobileFooter.stories.tsx +47 -0
- package/src/components/MobileFooter/MobileFooter.tsx +109 -0
- package/src/components/MobileFooter/__tests__/MobileFooter.test.tsx +71 -0
- package/src/components/MobileHeader/MobileHeader.module.css +63 -0
- package/src/components/MobileHeader/MobileHeader.stories.tsx +57 -0
- package/src/components/MobileHeader/MobileHeader.tsx +77 -0
- package/src/components/MobileHeader/__tests__/MobileHeader.test.tsx +58 -0
- package/src/components/NavTabs/NavTabs.tsx +39 -11
- package/src/components/OfflineGate/OfflineGate.tsx +44 -0
- package/src/components/OfflineGate/__tests__/OfflineGate.test.tsx +66 -0
- package/src/components/PageContainer/PageContainer.module.css +20 -0
- package/src/components/PageContainer/PageContainer.tsx +31 -0
- package/src/components/PageContainer/__tests__/PageContainer.test.tsx +37 -0
- package/src/components/PageHeader/PageHeader.tsx +16 -2
- package/src/components/ResponsiveSurface/ResponsiveSurface.tsx +24 -0
- package/src/components/ResponsiveSurface/__tests__/ResponsiveSurface.test.tsx +73 -0
- package/src/components/Sheet/Sheet.tsx +9 -51
- package/src/components/SwipableMobileCarousel/SwipableMobileCarousel.module.css +55 -0
- package/src/components/SwipableMobileCarousel/SwipableMobileCarousel.tsx +227 -0
- package/src/components/SwipableMobileCarousel/SwipableMobileCarouselSlide.tsx +35 -0
- package/src/components/SwipableMobileCarousel/SwipableMobileCarouselSlideBody.tsx +42 -0
- package/src/components/SwipableMobileCarousel/SwipableMobileCarouselSlideFooter.tsx +18 -0
- package/src/components/SwipableMobileCarousel/SwipableMobileCarouselSlideHeader.tsx +21 -0
- package/src/components/SwipableMobileCarousel/SwipableMobileCarouselSlideParts.module.css +29 -0
- package/src/components/SwipableMobileCarousel/__tests__/SwipableMobileCarousel.test.tsx +133 -0
- package/src/components/SwipableMobileCarousel/__tests__/SwipableMobileCarouselDevGuard.test.tsx +61 -0
- package/src/components/SwipableMobileCarousel/context.ts +13 -0
- package/src/components/SwipableMobileCarouselDots/SwipableMobileCarouselDots.module.css +49 -0
- package/src/components/SwipableMobileCarouselDots/SwipableMobileCarouselDots.tsx +62 -0
- package/src/components/SwipableMobileCarouselDots/__tests__/SwipableMobileCarouselDots.test.tsx +63 -0
- package/src/hooks/__tests__/useCarouselRouteSync.test.tsx +126 -0
- package/src/hooks/__tests__/useIsOffline.test.tsx +53 -0
- package/src/hooks/__tests__/useOfflineTileState.test.tsx +69 -0
- package/src/hooks/__tests__/useResponsiveLayout.test.tsx +87 -0
- package/src/hooks/__tests__/useSnapCarousel.test.tsx +93 -0
- package/src/hooks/__tests__/useSwipeReveal.test.tsx +110 -0
- package/src/hooks/index.ts +18 -0
- package/src/hooks/useCarouselRouteSync.ts +92 -0
- package/src/hooks/useIsOffline.ts +41 -0
- package/src/hooks/useOfflineTileState.ts +45 -0
- package/src/hooks/useResponsiveLayout.ts +36 -0
- package/src/hooks/useSnapCarousel.ts +79 -0
- package/src/hooks/useSwipeReveal.ts +107 -0
- package/src/index.ts +63 -2
- package/src/overlay-shell.ts +94 -0
- package/src/stories/DesignSystemOverview.stories.tsx +322 -1
- package/src/stories/DeviceOnlyGate.stories.tsx +35 -0
- package/src/stories/DeviceStorageKeyGate.stories.tsx +57 -0
- package/src/stories/InteractionHooks.stories.tsx +483 -0
- package/src/stories/MobilePatterns.stories.tsx +247 -2
- package/src/stories/NavTabs.stories.tsx +18 -0
- package/src/stories/OfflineGate.stories.tsx +42 -0
- package/src/stories/PageContainer.stories.tsx +37 -0
- package/src/stories/PageHeader.stories.tsx +11 -0
- package/src/stories/ResponsiveSurface.stories.tsx +47 -0
- package/src/stories/SwipableMobileCarousel.stories.tsx +304 -0
- package/src/stories/TokenGallery.stories.tsx +65 -0
- 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
|
-
|
|
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
|
-
|
|
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 };
|