@sovereignfs/ui 0.45.0 → 0.83.1
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/Accordion.module.css +1 -1
- package/dist/Alert.module.css +1 -1
- package/dist/AppsLauncher.module.css +96 -0
- package/dist/Avatar.module.css +1 -1
- package/dist/Badge.module.css +30 -7
- package/dist/BalanceChip.module.css +1 -1
- package/dist/Button.module.css +18 -2
- package/dist/Card.module.css +1 -1
- package/dist/CardTile.module.css +181 -0
- package/dist/CodeTextarea.module.css +1 -1
- package/dist/Collapsible.module.css +1 -1
- package/dist/ColorPicker.module.css +132 -0
- package/dist/Combobox.module.css +2 -2
- package/dist/Command.module.css +1 -1
- package/dist/ConfirmDialog.module.css +2 -2
- package/dist/ConsentPrompt.module.css +21 -0
- package/dist/DatePicker.module.css +1 -1
- package/dist/Dialog.module.css +130 -21
- package/dist/Drawer.module.css +36 -7
- package/dist/Header.module.css +62 -0
- package/dist/HeaderFooterLayout.module.css +21 -0
- package/dist/IconPicker.module.css +1 -1
- package/dist/Input.module.css +1 -1
- package/dist/Kbd.module.css +1 -1
- package/dist/LegalLinks.module.css +24 -0
- package/dist/Markdown.module.css +73 -0
- package/dist/Menu.module.css +1 -0
- package/dist/Message.module.css +1 -1
- package/dist/MessageScroller.module.css +12 -1
- package/dist/MobileAppsDrawer.module.css +82 -0
- package/dist/MobileFooter.module.css +73 -0
- package/dist/MobileHeader.module.css +76 -0
- package/dist/NavList.module.css +130 -0
- package/dist/NavTabs.module.css +1 -1
- package/dist/NotificationsPanel.module.css +258 -0
- package/dist/OverlayHeader.module.css +1 -1
- package/dist/PageContainer.module.css +82 -0
- package/dist/PageHeader.module.css +54 -0
- package/dist/PageLayout.module.css +51 -0
- package/dist/Popover.module.css +1 -1
- package/dist/QuantityStepper.module.css +1 -1
- package/dist/Resizable.module.css +3 -3
- package/dist/RootLayout.module.css +12 -0
- package/dist/SegmentedControl.module.css +1 -1
- package/dist/Select.module.css +1 -1
- package/dist/Sheet.module.css +4 -1
- package/dist/SplitPane.module.css +4 -4
- package/dist/StatusBadge.module.css +1 -1
- package/dist/StepStrip.module.css +53 -0
- package/dist/SuggestionInput.module.css +2 -2
- package/dist/SwipableMobileCarousel.module.css +64 -0
- package/dist/SwipableMobileCarouselDots.module.css +64 -0
- package/dist/SwipableMobileCarouselSlideParts.module.css +29 -0
- package/dist/SwipeStack.module.css +101 -0
- package/dist/SystemBanner.module.css +10 -0
- package/dist/Table.module.css +2 -2
- package/dist/Tabs.module.css +1 -1
- package/dist/TagInput.module.css +2 -2
- package/dist/Textarea.module.css +9 -1
- package/dist/ThreeColumnLayout.module.css +35 -0
- package/dist/Toast.module.css +62 -3
- package/dist/Tooltip.module.css +1 -1
- package/dist/UserMenu.module.css +90 -0
- package/dist/index.d.ts +1680 -30
- package/dist/index.js +4618 -1443
- package/dist/tokens/primitives.css +36 -0
- package/dist/tokens/semantic.css +32 -0
- package/dist/tokens/theme-presets.css +33 -0
- package/package.json +32 -12
- package/src/__tests__/package-json.test.ts +8 -0
- package/src/components/Accordion/Accordion.module.css +1 -1
- package/src/components/Alert/Alert.module.css +1 -1
- package/src/components/AppsLauncher/AppsLauncher.module.css +96 -0
- package/src/components/AppsLauncher/AppsLauncher.stories.tsx +58 -0
- package/src/components/AppsLauncher/AppsLauncher.tsx +117 -0
- package/src/components/AppsLauncher/__tests__/AppsLauncher.test.tsx +52 -0
- package/src/components/Avatar/Avatar.module.css +1 -1
- package/src/components/Badge/Badge.module.css +30 -7
- package/src/components/Badge/Badge.stories.tsx +16 -4
- package/src/components/Badge/Badge.tsx +8 -11
- package/src/components/Badge/__tests__/Badge.test.tsx +4 -2
- package/src/components/BalanceChip/BalanceChip.module.css +1 -1
- package/src/components/Button/Button.module.css +18 -2
- package/src/components/Card/Card.module.css +1 -1
- package/src/components/CardTile/CardTile.module.css +181 -0
- package/src/components/CardTile/CardTile.stories.tsx +86 -0
- package/src/components/CardTile/CardTile.tsx +128 -0
- package/src/components/CardTile/__tests__/CardTile.test.tsx +56 -0
- package/src/components/CodeTextarea/CodeTextarea.module.css +1 -1
- package/src/components/Collapsible/Collapsible.module.css +1 -1
- package/src/components/ColorPicker/ColorPicker.module.css +132 -0
- package/src/components/ColorPicker/ColorPicker.stories.tsx +80 -0
- package/src/components/ColorPicker/ColorPicker.tsx +157 -0
- package/src/components/ColorPicker/__tests__/ColorPicker.test.tsx +117 -0
- package/src/components/Combobox/Combobox.module.css +2 -2
- package/src/components/Command/Command.module.css +1 -1
- package/src/components/ConfirmDialog/ConfirmDialog.module.css +2 -2
- package/src/components/ConfirmDialog/ConfirmDialog.tsx +28 -2
- package/src/components/ConfirmDialog/__tests__/ConfirmDialog.test.tsx +71 -3
- package/src/components/ConsentPrompt/ConsentPrompt.module.css +21 -0
- package/src/components/ConsentPrompt/ConsentPrompt.stories.tsx +31 -0
- package/src/components/ConsentPrompt/ConsentPrompt.tsx +66 -0
- package/src/components/ContextMenu/ContextMenu.tsx +1 -1
- package/src/components/DatePicker/DatePicker.module.css +1 -1
- 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.module.css +130 -21
- package/src/components/Dialog/Dialog.stories.tsx +179 -2
- package/src/components/Dialog/Dialog.tsx +137 -94
- package/src/components/Dialog/__tests__/Dialog.test.tsx +190 -9
- package/src/components/DragHandleRow/DragHandleRow.tsx +2 -21
- package/src/components/Drawer/Drawer.module.css +36 -7
- package/src/components/Drawer/Drawer.stories.tsx +44 -0
- package/src/components/Drawer/Drawer.tsx +27 -66
- package/src/components/Drawer/__tests__/Drawer.test.tsx +55 -6
- package/src/components/GripIcon/GripIcon.tsx +27 -0
- package/src/components/Header/Header.module.css +62 -0
- package/src/components/Header/Header.stories.tsx +452 -0
- package/src/components/Header/Header.tsx +102 -0
- package/src/components/Header/__tests__/Header.test.tsx +104 -0
- package/src/components/HeaderFooterLayout/HeaderFooterLayout.module.css +21 -0
- package/src/components/HeaderFooterLayout/HeaderFooterLayout.stories.tsx +111 -0
- package/src/components/HeaderFooterLayout/HeaderFooterLayout.tsx +54 -0
- package/src/components/HeaderFooterLayout/__tests__/HeaderFooterLayout.test.tsx +58 -0
- package/src/components/Icon/icons/arrow-left-right.tsx +23 -0
- package/src/components/Icon/icons/bold.tsx +20 -0
- package/src/components/Icon/icons/calendar.tsx +4 -4
- package/src/components/Icon/icons/carrot.tsx +3 -3
- package/src/components/Icon/icons/circle-chevron-left.tsx +21 -0
- package/src/components/Icon/icons/code.tsx +21 -0
- package/src/components/Icon/icons/copy.tsx +21 -0
- package/src/components/Icon/icons/credit-card.tsx +21 -0
- package/src/components/Icon/icons/database.tsx +22 -0
- package/src/components/Icon/icons/download.tsx +22 -0
- package/src/components/Icon/icons/file-text.tsx +24 -0
- package/src/components/Icon/icons/folder-closed.tsx +21 -0
- package/src/components/Icon/icons/folder-open.tsx +20 -0
- package/src/components/Icon/icons/folder.tsx +20 -0
- package/src/components/Icon/icons/folders.tsx +21 -0
- package/src/components/Icon/icons/hat-glasses.tsx +24 -0
- package/src/components/Icon/icons/history.tsx +22 -0
- package/src/components/Icon/icons/inbox.tsx +21 -0
- package/src/components/Icon/icons/index.ts +169 -1
- package/src/components/Icon/icons/italic.tsx +22 -0
- package/src/components/Icon/icons/layers.tsx +22 -0
- package/src/components/Icon/icons/layout-dashboard.tsx +23 -0
- package/src/components/Icon/icons/layout-grid.tsx +23 -0
- package/src/components/Icon/icons/link.tsx +21 -0
- package/src/components/Icon/icons/list-ordered.tsx +25 -0
- package/src/components/Icon/icons/list.tsx +25 -0
- package/src/components/Icon/icons/luggage.tsx +24 -0
- package/src/components/Icon/icons/map-pin.tsx +21 -0
- package/src/components/Icon/icons/menu.tsx +22 -0
- package/src/components/Icon/icons/minus.tsx +20 -0
- package/src/components/Icon/icons/paint-bucket.tsx +23 -0
- package/src/components/Icon/icons/panel-left.tsx +21 -0
- package/src/components/Icon/icons/pin.tsx +21 -0
- package/src/components/Icon/icons/rectangle-ellipsis.tsx +23 -0
- package/src/components/Icon/icons/refresh-cw.tsx +23 -0
- package/src/components/Icon/icons/rotate-cw.tsx +21 -0
- package/src/components/Icon/icons/route.tsx +22 -0
- package/src/components/Icon/icons/send.tsx +21 -0
- package/src/components/Icon/icons/share-2.tsx +24 -0
- package/src/components/Icon/icons/sheet.tsx +24 -0
- package/src/components/Icon/icons/smartphone.tsx +21 -0
- package/src/components/Icon/icons/square-kanban.tsx +23 -0
- package/src/components/Icon/icons/table.tsx +23 -0
- package/src/components/Icon/icons/user-round-plus.tsx +23 -0
- package/src/components/Icon/icons/users.tsx +23 -0
- package/src/components/IconPicker/IconPicker.module.css +1 -1
- package/src/components/Input/Input.module.css +1 -1
- package/src/components/Input/Input.tsx +8 -2
- package/src/components/Kbd/Kbd.module.css +1 -1
- 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 +73 -0
- package/src/components/Markdown/Markdown.stories.tsx +40 -0
- package/src/components/Markdown/Markdown.tsx +305 -0
- package/src/components/Markdown/__tests__/Markdown.test.tsx +144 -0
- package/src/components/Menu/Menu.module.css +1 -0
- package/src/components/Menu/Menu.tsx +72 -22
- package/src/components/Menu/__tests__/Menu.test.tsx +30 -0
- package/src/components/Menubar/Menubar.tsx +1 -1
- package/src/components/Message/Message.module.css +1 -1
- package/src/components/MessageScroller/MessageScroller.module.css +12 -1
- package/src/components/MessageScroller/MessageScroller.tsx +12 -0
- package/src/components/MobileAppsDrawer/MobileAppsDrawer.module.css +82 -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 +122 -0
- package/src/components/MobileFooter/__tests__/MobileFooter.test.tsx +129 -0
- package/src/components/MobileHeader/MobileHeader.module.css +76 -0
- package/src/components/MobileHeader/MobileHeader.stories.tsx +57 -0
- package/src/components/MobileHeader/MobileHeader.tsx +90 -0
- package/src/components/MobileHeader/__tests__/MobileHeader.test.tsx +113 -0
- package/src/components/NavList/NavList.module.css +130 -0
- package/src/components/NavList/NavList.tsx +136 -0
- package/src/components/NavList/__tests__/NavList.test.tsx +109 -0
- package/src/components/NavTabs/NavTabs.module.css +1 -1
- package/src/components/NavTabs/NavTabs.tsx +39 -11
- package/src/components/NotificationsPanel/NotificationsPanel.module.css +258 -0
- package/src/components/NotificationsPanel/NotificationsPanel.stories.tsx +61 -0
- package/src/components/NotificationsPanel/NotificationsPanel.tsx +187 -0
- package/src/components/NotificationsPanel/__tests__/NotificationsPanel.test.tsx +78 -0
- package/src/components/OfflineGate/OfflineGate.tsx +44 -0
- package/src/components/OfflineGate/__tests__/OfflineGate.test.tsx +66 -0
- package/src/components/OverlayHeader/OverlayHeader.module.css +1 -1
- package/src/components/OverlayHeader/OverlayHeader.tsx +19 -4
- package/src/components/OverlayHeader/__tests__/OverlayHeader.test.tsx +18 -0
- package/src/components/PageContainer/PageContainer.module.css +82 -0
- package/src/components/PageContainer/PageContainer.tsx +64 -0
- package/src/components/PageContainer/__tests__/PageContainer.test.tsx +70 -0
- package/src/components/PageHeader/PageHeader.module.css +54 -0
- package/src/components/PageHeader/PageHeader.tsx +157 -2
- package/src/components/PageHeader/__tests__/PageHeader.test.tsx +103 -0
- package/src/components/PageLayout/PageLayout.module.css +51 -0
- package/src/components/PageLayout/PageLayout.stories.tsx +73 -0
- package/src/components/PageLayout/PageLayout.tsx +51 -0
- package/src/components/PageLayout/__tests__/PageLayout.test.tsx +34 -0
- package/src/components/Popover/Popover.module.css +1 -1
- package/src/components/Popover/Popover.tsx +86 -4
- package/src/components/Popover/__tests__/Popover.test.tsx +165 -0
- package/src/components/QuantityStepper/QuantityStepper.module.css +1 -1
- package/src/components/Resizable/Resizable.module.css +3 -3
- package/src/components/ResponsiveSurface/ResponsiveSurface.tsx +24 -0
- package/src/components/ResponsiveSurface/__tests__/ResponsiveSurface.test.tsx +73 -0
- package/src/components/RootLayout/RootLayout.module.css +12 -0
- package/src/components/RootLayout/RootLayout.stories.tsx +125 -0
- package/src/components/RootLayout/RootLayout.tsx +185 -0
- package/src/components/RootLayout/__tests__/RootLayout.test.tsx +161 -0
- package/src/components/SegmentedControl/SegmentedControl.module.css +1 -1
- package/src/components/Select/Select.module.css +1 -1
- package/src/components/Sheet/Sheet.module.css +4 -1
- package/src/components/Sheet/Sheet.tsx +14 -56
- package/src/components/SplitPane/SplitPane.module.css +4 -4
- package/src/components/StatusBadge/StatusBadge.module.css +1 -1
- package/src/components/StepStrip/StepStrip.module.css +53 -0
- package/src/components/StepStrip/StepStrip.tsx +74 -0
- package/src/components/SuggestionInput/SuggestionInput.module.css +2 -2
- package/src/components/SwipableMobileCarousel/SwipableMobileCarousel.module.css +64 -0
- package/src/components/SwipableMobileCarousel/SwipableMobileCarousel.tsx +243 -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 +214 -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 +64 -0
- package/src/components/SwipableMobileCarouselDots/SwipableMobileCarouselDots.tsx +73 -0
- package/src/components/SwipableMobileCarouselDots/__tests__/SwipableMobileCarouselDots.test.tsx +63 -0
- package/src/components/SwipeStack/SwipeStack.module.css +101 -0
- package/src/components/SwipeStack/SwipeStack.tsx +199 -0
- package/src/components/SwipeStack/SwipeStackCard.tsx +22 -0
- package/src/components/SwipeStack/__tests__/SwipeStack.test.tsx +169 -0
- package/src/components/SystemBanner/SystemBanner.module.css +10 -0
- package/src/components/Table/Table.module.css +2 -2
- package/src/components/Tabs/Tabs.module.css +1 -1
- package/src/components/TagInput/TagInput.module.css +2 -2
- package/src/components/Textarea/Textarea.module.css +9 -1
- package/src/components/Textarea/Textarea.tsx +28 -5
- package/src/components/Textarea/__tests__/Textarea.test.tsx +23 -1
- package/src/components/ThreeColumnLayout/ThreeColumnLayout.module.css +35 -0
- package/src/components/ThreeColumnLayout/ThreeColumnLayout.stories.tsx +152 -0
- package/src/components/ThreeColumnLayout/ThreeColumnLayout.tsx +87 -0
- package/src/components/ThreeColumnLayout/__tests__/ThreeColumnLayout.test.tsx +78 -0
- package/src/components/Toast/Toast.module.css +62 -3
- package/src/components/Toast/Toast.stories.tsx +1 -1
- package/src/components/Tooltip/Tooltip.module.css +1 -1
- package/src/components/UserMenu/UserMenu.module.css +90 -0
- package/src/components/UserMenu/UserMenu.stories.tsx +49 -0
- package/src/components/UserMenu/UserMenu.tsx +132 -0
- package/src/components/UserMenu/__tests__/UserMenu.test.tsx +77 -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__/usePublishShellChromeHeight.test.tsx +118 -0
- package/src/hooks/__tests__/useReorderSensors.test.ts +26 -0
- package/src/hooks/__tests__/useResponsiveLayout.test.tsx +87 -0
- package/src/hooks/__tests__/useSnapCarousel.test.tsx +175 -0
- package/src/hooks/__tests__/useSwipeReveal.test.tsx +110 -0
- package/src/hooks/__tests__/useSwipeStack.test.tsx +155 -0
- package/src/hooks/index.ts +27 -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/usePublishShellChromeHeight.ts +96 -0
- package/src/hooks/useReorderSensors.ts +88 -0
- package/src/hooks/useResponsiveLayout.ts +36 -0
- package/src/hooks/useSnapCarousel.ts +284 -0
- package/src/hooks/useSwipeReveal.ts +107 -0
- package/src/hooks/useSwipeStack.ts +235 -0
- package/src/index.ts +124 -3
- package/src/overlay-shell.ts +159 -0
- package/src/stories/DesignSystemOverview.stories.tsx +933 -6
- package/src/stories/DeviceOnlyGate.stories.tsx +35 -0
- package/src/stories/DeviceStorageKeyGate.stories.tsx +57 -0
- package/src/stories/GripIcon.stories.tsx +29 -0
- package/src/stories/InteractionHooks.stories.tsx +483 -0
- package/src/stories/LayoutSystem.stories.tsx +639 -0
- package/src/stories/MenuEntries.stories.tsx +86 -0
- package/src/stories/MobilePatterns.stories.tsx +247 -2
- package/src/stories/NavList.stories.tsx +147 -0
- package/src/stories/NavTabs.stories.tsx +18 -0
- package/src/stories/OfflineGate.stories.tsx +42 -0
- package/src/stories/PageContainer.stories.tsx +82 -0
- package/src/stories/PageHeader.stories.tsx +65 -1
- package/src/stories/ResponsiveSurface.stories.tsx +47 -0
- package/src/stories/StepStrip.stories.tsx +76 -0
- package/src/stories/SwipableMobileCarousel.stories.tsx +362 -0
- package/src/stories/SwipeStack.stories.tsx +200 -0
- package/src/stories/Textarea.stories.tsx +12 -0
- package/src/stories/TokenGallery.stories.tsx +115 -0
- package/src/tokens/primitives.css +36 -0
- package/src/tokens/semantic.css +32 -0
- package/src/tokens/theme-presets.css +33 -0
- package/src/tokens/theme-presets.ts +72 -0
package/dist/index.d.ts
CHANGED
|
@@ -1,13 +1,96 @@
|
|
|
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, Ref, TextareaHTMLAttributes, HTMLAttributes, TdHTMLAttributes, ThHTMLAttributes, LabelHTMLAttributes, ElementType, PointerEvent, KeyboardEvent, MouseEvent, RefObject } from 'react';
|
|
3
|
+
import * as _dnd_kit_core from '@dnd-kit/core';
|
|
4
|
+
|
|
5
|
+
interface LegalLinksProps {
|
|
6
|
+
privacyHref?: string;
|
|
7
|
+
tosHref?: string;
|
|
8
|
+
/** Renders a linked item. Defaults to a plain `<a href>`, which causes a
|
|
9
|
+
* full page reload — pass Next's `<Link>` (or equivalent) to keep
|
|
10
|
+
* navigation client-side. Not needed for a full-reload navigation away
|
|
11
|
+
* from a standalone auth page, but matters if this is ever mounted inside
|
|
12
|
+
* an SPA-like shell. */
|
|
13
|
+
renderLink?: (href: string, label: string) => ReactNode;
|
|
14
|
+
className?: string;
|
|
15
|
+
}
|
|
16
|
+
declare function LegalLinks({ privacyHref, tosHref, renderLink, className, }: LegalLinksProps): react.JSX.Element;
|
|
17
|
+
|
|
18
|
+
interface ConsentPromptProps {
|
|
19
|
+
/** The plugin requesting access. */
|
|
20
|
+
consumerName: string;
|
|
21
|
+
/** The plugin whose data would be read. */
|
|
22
|
+
providerName: string;
|
|
23
|
+
/** The named contract being requested (e.g. `"expenses"`). */
|
|
24
|
+
contract: string;
|
|
25
|
+
/**
|
|
26
|
+
* The provider's own manifest-declared explanation of what this contract
|
|
27
|
+
* exposes (`data.provides[].description`) — never caller-supplied at
|
|
28
|
+
* request time, so a consuming plugin can't write its own persuasive copy
|
|
29
|
+
* for what it's asking to read.
|
|
30
|
+
*/
|
|
31
|
+
description?: string | null;
|
|
32
|
+
onAllow: () => void;
|
|
33
|
+
onDeny?: () => void;
|
|
34
|
+
allowLabel?: string;
|
|
35
|
+
denyLabel?: string;
|
|
36
|
+
/** Disables both buttons and shows a busy state on Allow while a request is in flight. */
|
|
37
|
+
pending?: boolean;
|
|
38
|
+
className?: string;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* The consent screen shown before one plugin is allowed to read another
|
|
42
|
+
* plugin's data (RFC 0002 §4) — the step that was previously missing between
|
|
43
|
+
* a `ConsentRequiredError` and a grant actually being created. Presentational
|
|
44
|
+
* only: holds no state of its own, so it renders identically wherever it's
|
|
45
|
+
* used (currently Account → Data's pending-requests list).
|
|
46
|
+
*/
|
|
47
|
+
declare function ConsentPrompt({ consumerName, providerName, contract, description, onAllow, onDeny, allowLabel, denyLabel, pending, className, }: ConsentPromptProps): react.JSX.Element;
|
|
48
|
+
|
|
49
|
+
interface MarkdownProps {
|
|
50
|
+
/** Raw markdown source. Supports: headings (#/##/###), paragraphs,
|
|
51
|
+
* **bold**, *italic*, `code`, [links](url), unordered lists (-), ordered
|
|
52
|
+
* lists (1. / 1)), fenced code blocks (```), and blockquotes (>),
|
|
53
|
+
* including soft-wrapped continuation lines within a paragraph or list
|
|
54
|
+
* item. Not a general-purpose CommonMark parser — no tables, images, or
|
|
55
|
+
* nested blocks. */
|
|
56
|
+
content: string;
|
|
57
|
+
/** Renders every line within a paragraph on its own visual line (a
|
|
58
|
+
* `<br>` between them) instead of the default CommonMark-style soft-wrap
|
|
59
|
+
* (consecutive lines joined with a single space, requiring a blank line
|
|
60
|
+
* for a new paragraph). Off by default — the soft-wrap behavior is
|
|
61
|
+
* correct for this component's primary use (long-form, first-party
|
|
62
|
+
* content authored *as* markdown, where a hard line break is a
|
|
63
|
+
* deliberate `\` ` or blank-line choice). Turn this on when rendering
|
|
64
|
+
* plain, user-typed multi-line text that happens to be passed through
|
|
65
|
+
* this component (e.g. a card description or comment body edited in an
|
|
66
|
+
* ordinary `<textarea>`) — there, every Enter press is a real,
|
|
67
|
+
* intentional line break the user typed, and silently collapsing it
|
|
68
|
+
* into one run-on paragraph reads as broken, not as "markdown
|
|
69
|
+
* formatting applied." */
|
|
70
|
+
preserveLineBreaks?: boolean;
|
|
71
|
+
className?: string;
|
|
72
|
+
}
|
|
73
|
+
/** Renders a constrained markdown subset (see MarkdownProps) as styled React
|
|
74
|
+
* elements built from Typography — no HTML string injection. Primarily
|
|
75
|
+
* meant for long-form, first-party content pages (privacy policy, terms of
|
|
76
|
+
* service) sourced from a single markdown file — not a general-purpose
|
|
77
|
+
* CommonMark renderer for arbitrary markdown. Also fine for plain,
|
|
78
|
+
* user-typed multi-line text (a card description, a comment body) edited
|
|
79
|
+
* in an ordinary `<textarea>` and passed through unchanged, as long as
|
|
80
|
+
* `preserveLineBreaks` is set — see that prop's own doc comment for why it
|
|
81
|
+
* isn't the default. */
|
|
82
|
+
declare function Markdown({ content, preserveLineBreaks, className }: MarkdownProps): react.JSX.Element;
|
|
3
83
|
|
|
4
84
|
type BadgeVariant = 'role' | 'status' | 'mono';
|
|
5
|
-
type BadgeSize = 'sm' | 'md' | 'lg';
|
|
85
|
+
type BadgeSize = 'xs' | 'sm' | 'md' | 'lg';
|
|
6
86
|
type BadgeStatus = 'active' | 'enabled' | 'deactivated' | 'failed' | 'invited' | 'pending' | 'neutral';
|
|
7
87
|
interface BadgeProps {
|
|
8
88
|
variant?: BadgeVariant;
|
|
9
|
-
/** 'sm' | 'md' (default) | 'lg'. 'md' is the badge's original — and
|
|
10
|
-
* now, only — size, so the default renders unchanged.
|
|
89
|
+
/** 'xs' | 'sm' | 'md' (default) | 'lg'. 'md' is the badge's original — and
|
|
90
|
+
* until now, only — size, so the default renders unchanged. 'xs' is the
|
|
91
|
+
* smallest tier (`--sv-font-size-2xs`, 10px) — for a badge sitting inline
|
|
92
|
+
* next to larger text (e.g. a status chip beside a card's headline value)
|
|
93
|
+
* where even 'sm' still reads oversized next to it. */
|
|
11
94
|
size?: BadgeSize;
|
|
12
95
|
/** Only relevant when variant="status" — determines the dot colour. */
|
|
13
96
|
status?: BadgeStatus;
|
|
@@ -25,12 +108,12 @@ interface BadgeProps {
|
|
|
25
108
|
* - `status` tinted chip with leading colour dot — for Active / Deactivated / etc.
|
|
26
109
|
* - `mono` monospace font, neutral surface — for platform / community / v0.1.0
|
|
27
110
|
*
|
|
28
|
-
*
|
|
111
|
+
* Four sizes (`xs`/`sm`/`md`/`lg`, default `md`). ALL CAPS by default
|
|
29
112
|
* (`uppercase`) — set `uppercase={false}` for a title-case badge instead.
|
|
30
113
|
*/
|
|
31
114
|
declare function Badge({ variant, size, status, uppercase, children, }: BadgeProps): react.JSX.Element;
|
|
32
115
|
|
|
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';
|
|
116
|
+
type IconName = 'house' | 'settings' | 'log-out' | 'smartphone' | 'chevron-right' | 'chevron-left' | 'chevron-down' | 'chevron-up' | 'circle-chevron-left' | 'menu' | 'x' | 'check' | 'plus' | 'trash-2' | 'pencil' | 'rotate-ccw' | 'search' | 'copy' | 'user' | 'users' | 'user-round-plus' | 'shield' | 'lock' | 'eye' | 'eye-off' | 'hat-glasses' | 'mail' | 'bell' | 'inbox' | 'activity' | 'package' | 'database' | 'layers' | 'grid-2x2' | 'layout-dashboard' | 'layout-grid' | 'square-kanban' | 'info' | 'alert-triangle' | 'circle-check' | 'circle-x' | 'calendar' | 'sliders-horizontal' | 'ellipsis-vertical' | 'rectangle-ellipsis' | 'file' | 'folder' | 'upload' | 'download' | 'external-link' | 'arrow-left-right' | 'send' | 'list' | 'list-ordered' | 'link' | 'code' | 'rotate-cw' | 'refresh-cw' | 'history' | 'minus' | 'table' | 'sheet' | 'folders' | 'folder-closed' | 'file-text' | 'folder-open' | 'share-2' | 'luggage' | 'map-pin' | 'route' | 'bold' | 'italic' | 'paint-bucket' | 'banana' | 'apple' | 'carrot' | 'egg' | 'beef' | 'fish' | 'coffee' | 'wine' | 'beer' | 'cookie' | 'pizza' | 'candy' | 'salad' | 'milk' | 'drumstick' | 'croissant' | 'cup-soda' | 'spray-can' | 'snowflake' | 'shopping-basket' | 'panel-left' | 'pin' | 'credit-card';
|
|
34
117
|
|
|
35
118
|
/** Decorative icon — visually meaningful but described by surrounding text. */
|
|
36
119
|
type DecorativeProps = {
|
|
@@ -177,6 +260,17 @@ interface PopoverProps {
|
|
|
177
260
|
* regardless of CSS module load order, unlike passing an extra className.
|
|
178
261
|
*/
|
|
179
262
|
panelStyle?: CSSProperties;
|
|
263
|
+
/**
|
|
264
|
+
* Class applied to the popover's own wrapper around `trigger` — the
|
|
265
|
+
* element that participates in the caller's layout.
|
|
266
|
+
*
|
|
267
|
+
* Mostly needed for flex sizing: the wrapper is an `inline-flex` box with
|
|
268
|
+
* the default `min-width: auto`, which pins it to the trigger's content
|
|
269
|
+
* width. Inside a flex row that has to shrink, that stops a truncatable
|
|
270
|
+
* trigger from ever giving up space, and pushes its siblings out of the
|
|
271
|
+
* container instead. Pass a class setting `min-width: 0` there.
|
|
272
|
+
*/
|
|
273
|
+
className?: string;
|
|
180
274
|
'aria-label': string;
|
|
181
275
|
children: ReactNode;
|
|
182
276
|
}
|
|
@@ -203,7 +297,7 @@ interface PopoverProps {
|
|
|
203
297
|
*
|
|
204
298
|
* Closes on outside click or Escape. Does not trap focus (it is non-modal).
|
|
205
299
|
*/
|
|
206
|
-
declare function Popover({ trigger, open, onClose, align, width, panelStyle, 'aria-label': ariaLabel, children, }: PopoverProps): react.JSX.Element;
|
|
300
|
+
declare function Popover({ trigger, open, onClose, align, width, panelStyle, className, 'aria-label': ariaLabel, children, }: PopoverProps): react.JSX.Element;
|
|
207
301
|
|
|
208
302
|
type ButtonVariant = 'primary' | 'secondary' | 'ghost' | 'destructive';
|
|
209
303
|
type ButtonSize = 'sm' | 'md' | 'lg';
|
|
@@ -222,7 +316,13 @@ interface ButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> {
|
|
|
222
316
|
*/
|
|
223
317
|
declare function Button({ variant, size, type, loading, disabled, className, children, ...rest }: ButtonProps): react.JSX.Element;
|
|
224
318
|
|
|
225
|
-
type InputProps = InputHTMLAttributes<HTMLInputElement
|
|
319
|
+
type InputProps = InputHTMLAttributes<HTMLInputElement> & {
|
|
320
|
+
/** React 19 ref-as-prop — no `forwardRef` needed. Not part of
|
|
321
|
+
* `InputHTMLAttributes` (refs aren't a DOM attribute), so it's added
|
|
322
|
+
* explicitly here; left out of the destructure below so it flows through
|
|
323
|
+
* `...rest` straight onto the underlying `<input>`. */
|
|
324
|
+
ref?: Ref<HTMLInputElement>;
|
|
325
|
+
};
|
|
226
326
|
/**
|
|
227
327
|
* Input — the primitive text field. Presentational and RSC-safe: it forwards
|
|
228
328
|
* all native input props to the underlying `<input>`. Styling references
|
|
@@ -230,14 +330,23 @@ type InputProps = InputHTMLAttributes<HTMLInputElement>;
|
|
|
230
330
|
*/
|
|
231
331
|
declare function Input({ type, className, ...rest }: InputProps): react.JSX.Element;
|
|
232
332
|
|
|
233
|
-
|
|
333
|
+
interface TextareaProps extends TextareaHTMLAttributes<HTMLTextAreaElement> {
|
|
334
|
+
/** Grows the field's own height to fit its content as it's typed, instead
|
|
335
|
+
* of scrolling internally or leaving the user to drag-resize — e.g. an
|
|
336
|
+
* editable title that should read as a heading, not a fixed-size form
|
|
337
|
+
* field. Disables manual resize (`resize: none`) since a height driven by
|
|
338
|
+
* content and a user-draggable height would fight each other. `rows` is
|
|
339
|
+
* still honoured as the starting/minimum height. */
|
|
340
|
+
autoGrow?: boolean;
|
|
341
|
+
}
|
|
234
342
|
/**
|
|
235
343
|
* Textarea — the primitive multi-line text field. Presentational and
|
|
236
|
-
* RSC-safe
|
|
344
|
+
* RSC-safe unless `autoGrow` is used (client-only, needs a layout
|
|
345
|
+
* measurement). Forwards all native textarea props to the underlying
|
|
237
346
|
* `<textarea>`. Styling references `--sv-*` tokens via CSS Modules; there
|
|
238
347
|
* are no hardcoded values.
|
|
239
348
|
*/
|
|
240
|
-
declare function Textarea({ className, rows, ...rest }: TextareaProps): react.JSX.Element;
|
|
349
|
+
declare function Textarea({ className, rows, autoGrow, ...rest }: TextareaProps): react.JSX.Element;
|
|
241
350
|
|
|
242
351
|
interface CodeTextareaProps extends TextareaHTMLAttributes<HTMLTextAreaElement> {
|
|
243
352
|
invalid?: boolean;
|
|
@@ -308,11 +417,13 @@ declare function TagInput({ value, onChange, id, name, placeholder, disabled, re
|
|
|
308
417
|
/**
|
|
309
418
|
* Lets content deep inside a `Dialog` (e.g. a plugin's own route layout,
|
|
310
419
|
* rendered several levels below wherever the `Dialog` itself is
|
|
311
|
-
* instantiated) supply the second-row content of the Dialog's
|
|
312
|
-
*
|
|
313
|
-
*
|
|
314
|
-
*
|
|
315
|
-
*
|
|
420
|
+
* instantiated) supply the second-row content of the Dialog's `OverlayHeader`
|
|
421
|
+
* — typically a tab strip. Solves the "double header" problem: without this,
|
|
422
|
+
* a plugin's own tab strip has no way to reach the Dialog's header and ends
|
|
423
|
+
* up rendered a second time, as its own sticky bar, inside the scrolling
|
|
424
|
+
* content area. Reaches the header regardless of whether it's showing via
|
|
425
|
+
* the `header` prop (both breakpoints) or the default `title`-driven mobile
|
|
426
|
+
* bar (mobile only) — see `DialogProps.header`'s own doc comment.
|
|
316
427
|
*
|
|
317
428
|
* A no-op when there is no enclosing `Dialog` (e.g. the same plugin layout
|
|
318
429
|
* also rendered on a plain, non-overlay route) — safe to call unconditionally.
|
|
@@ -329,7 +440,7 @@ declare function TagInput({ value, onChange, id, name, placeholder, disabled, re
|
|
|
329
440
|
* ```
|
|
330
441
|
*/
|
|
331
442
|
declare function useOverlaySecondRow(node: ReactNode | null): boolean;
|
|
332
|
-
type DialogSize = 'sm' | 'md' | 'lg' | '
|
|
443
|
+
type DialogSize = 'sm' | 'md' | 'lg' | 'auto' | 'fixed';
|
|
333
444
|
interface DialogProps {
|
|
334
445
|
/** Whether the dialog is shown. When false, nothing renders. */
|
|
335
446
|
open: boolean;
|
|
@@ -337,11 +448,42 @@ interface DialogProps {
|
|
|
337
448
|
onClose: () => void;
|
|
338
449
|
/** Panel size on desktop. Mobile always renders as a full-screen sheet. */
|
|
339
450
|
size?: DialogSize;
|
|
340
|
-
/** Accessible name for the dialog (sets `aria-label` on the panel).
|
|
451
|
+
/** Accessible name for the dialog (sets `aria-label` on the panel). Falls
|
|
452
|
+
* back to `title` when omitted — this still applies even when `header` is
|
|
453
|
+
* used instead of `title` for the visible header (see `header`'s own doc
|
|
454
|
+
* comment), so pass `aria-label` explicitly in that case if `title` is
|
|
455
|
+
* also omitted. */
|
|
341
456
|
'aria-label'?: string;
|
|
342
|
-
/**
|
|
343
|
-
*
|
|
457
|
+
/** Shown in the top bar alongside the close button, on mobile only —
|
|
458
|
+
* desktop has no header row by default (see `header` for that). Ignored
|
|
459
|
+
* as visible content when `header` is provided; still used as the
|
|
460
|
+
* `aria-label` fallback either way. */
|
|
344
461
|
title?: string;
|
|
462
|
+
/** Renders a persistent header row (via the same `OverlayHeader` component
|
|
463
|
+
* `title` alone only shows on mobile) on **both** desktop and mobile —
|
|
464
|
+
* the "Header + Body" / "Header + Body + Footer" shapes. Supersedes
|
|
465
|
+
* `title` for visible content when provided; `title` remains the
|
|
466
|
+
* `aria-label` fallback if `aria-label` is also omitted. Omitting both
|
|
467
|
+
* `header` and `footer` is "Body only" — today's default, unchanged. */
|
|
468
|
+
header?: ReactNode;
|
|
469
|
+
/** Renders a persistent footer row below the scrollable body — pinned on
|
|
470
|
+
* both desktop and mobile the same way `header`/the mobile title bar are
|
|
471
|
+
* (a non-scrolling flex sibling, not `position: sticky`): only the region
|
|
472
|
+
* between header and footer scrolls. Typically action buttons (Save/
|
|
473
|
+
* Cancel). Omit for no footer — today's default, where any actions live
|
|
474
|
+
* inside `children` and scroll with the rest of the body. Layout-agnostic
|
|
475
|
+
* (no built-in button alignment) — arrange the footer's own content as
|
|
476
|
+
* needed, same as `children`. */
|
|
477
|
+
footer?: ReactNode;
|
|
478
|
+
/** Forwarded to the active `OverlayHeader` instance's own `rowClassName`
|
|
479
|
+
* (whichever is showing — `header` or the mobile `title` bar) — restyle
|
|
480
|
+
* the title row (e.g. tighter padding to match a consumer's own compact
|
|
481
|
+
* header elsewhere) without changing its title/close layout. */
|
|
482
|
+
rowClassName?: string;
|
|
483
|
+
/** Forwarded to the active `OverlayHeader` instance's own `titleClassName`
|
|
484
|
+
* — restyle the title text (e.g. a smaller font size to match a
|
|
485
|
+
* consumer's own compact header elsewhere). */
|
|
486
|
+
titleClassName?: string;
|
|
345
487
|
children: ReactNode;
|
|
346
488
|
}
|
|
347
489
|
/**
|
|
@@ -359,8 +501,14 @@ interface DialogProps {
|
|
|
359
501
|
* `onClose` API is unchanged — closing still stays mounted internally for the
|
|
360
502
|
* exit transition before actually unmounting; `prefers-reduced-motion: reduce`
|
|
361
503
|
* collapses both to near-instant.
|
|
504
|
+
*
|
|
505
|
+
* Three composition shapes, chosen by which of `header`/`footer` are passed:
|
|
506
|
+
* Body only (both omitted — today's default), Header + Body (`header` only),
|
|
507
|
+
* Header + Body + Footer (`header` and `footer`). Whichever of header/footer
|
|
508
|
+
* are present render as non-scrolling flex siblings around `.content`, which
|
|
509
|
+
* stays the only scrollable region in every shape.
|
|
362
510
|
*/
|
|
363
|
-
declare function Dialog({ open, onClose, size, 'aria-label': ariaLabel, title, children, }: DialogProps): react.JSX.Element | null;
|
|
511
|
+
declare function Dialog({ open, onClose, size, 'aria-label': ariaLabel, title, header, footer, rowClassName, titleClassName, children, }: DialogProps): react.JSX.Element | null;
|
|
364
512
|
|
|
365
513
|
interface DrawerProps {
|
|
366
514
|
/** Whether the drawer is shown. When false, nothing renders. */
|
|
@@ -374,6 +522,12 @@ interface DrawerProps {
|
|
|
374
522
|
* "half screen or less" convention for Drawer, this is the only other
|
|
375
523
|
* supported size; taller content should use `Sheet` instead. */
|
|
376
524
|
snapHeight?: 'content' | 'half';
|
|
525
|
+
/** Renders a built-in `OverlayHeader` (title + close) below the grab
|
|
526
|
+
* handle. Omit when the content supplies its own header, or none is
|
|
527
|
+
* needed at all — e.g. the mobile shell's Apps drawer, whose grab handle
|
|
528
|
+
* and swipe-down/scrim-tap dismissal are enough on their own. Mirrors
|
|
529
|
+
* `Sheet`'s identical `title` prop. */
|
|
530
|
+
title?: string;
|
|
377
531
|
children: ReactNode;
|
|
378
532
|
}
|
|
379
533
|
/**
|
|
@@ -398,13 +552,17 @@ interface DrawerProps {
|
|
|
398
552
|
* scroll, the same reasoning behind the tasks plugin's edge-zone-only
|
|
399
553
|
* swipe-to-reveal technique.
|
|
400
554
|
*/
|
|
401
|
-
declare function Drawer({ open, onClose, 'aria-label': ariaLabel, snapHeight, children, }: DrawerProps): react.JSX.Element | null;
|
|
555
|
+
declare function Drawer({ open, onClose, 'aria-label': ariaLabel, snapHeight, title, children, }: DrawerProps): react.JSX.Element | null;
|
|
402
556
|
|
|
403
557
|
interface OverlayHeaderProps {
|
|
404
558
|
/** Overlay title, shown between the optional back button and the trailing
|
|
405
559
|
* slots. Optional — some consumers (e.g. `Dialog`'s mobile bar) need the
|
|
406
|
-
* row's close button present even when no title applies.
|
|
407
|
-
|
|
560
|
+
* row's close button present even when no title applies. Accepts any
|
|
561
|
+
* `ReactNode`, not just a string — e.g. `Dialog`'s `header` prop passes
|
|
562
|
+
* arbitrary content through here (an icon next to text, etc.); the
|
|
563
|
+
* row's title slot still assumes single-line content sized like text
|
|
564
|
+
* (see the component's own doc comment for the line this doesn't cross). */
|
|
565
|
+
title?: ReactNode;
|
|
408
566
|
/** Always renders a close button; called on click. */
|
|
409
567
|
onClose: () => void;
|
|
410
568
|
/** When provided, renders a back-chevron button before the title (for
|
|
@@ -416,6 +574,15 @@ interface OverlayHeaderProps {
|
|
|
416
574
|
/** A second row below the title row — e.g. a tab strip. */
|
|
417
575
|
secondRow?: ReactNode;
|
|
418
576
|
className?: string;
|
|
577
|
+
/** Override the title row's own className (e.g. tighter padding to match
|
|
578
|
+
* a consumer's own compact header elsewhere in the same surface) — the
|
|
579
|
+
* row keeps its standard title/back/action/close layout, just restyled.
|
|
580
|
+
* For a genuinely different shape, don't use `OverlayHeader` at all (see
|
|
581
|
+
* this component's own doc comment). */
|
|
582
|
+
rowClassName?: string;
|
|
583
|
+
/** Override the title text's own className (e.g. a smaller font size to
|
|
584
|
+
* match a consumer's own compact header elsewhere in the same surface). */
|
|
585
|
+
titleClassName?: string;
|
|
419
586
|
}
|
|
420
587
|
/**
|
|
421
588
|
* OverlayHeader — the shared fixed secondary header for `Dialog`'s mobile
|
|
@@ -431,7 +598,7 @@ interface OverlayHeaderProps {
|
|
|
431
598
|
* component — render its own header row instead; `Sheet`'s `title` prop is
|
|
432
599
|
* optional specifically to leave that escape hatch open.
|
|
433
600
|
*/
|
|
434
|
-
declare function OverlayHeader({ title, onClose, onBack, action, secondRow, className, }: OverlayHeaderProps): react.JSX.Element;
|
|
601
|
+
declare function OverlayHeader({ title, onClose, onBack, action, secondRow, className, rowClassName, titleClassName, }: OverlayHeaderProps): react.JSX.Element;
|
|
435
602
|
|
|
436
603
|
interface SheetProps {
|
|
437
604
|
/** Whether the sheet is shown. When false, nothing renders. */
|
|
@@ -526,7 +693,16 @@ declare function ConfirmDialog({ open, onClose, title, message, onConfirm, confi
|
|
|
526
693
|
interface MenuItem {
|
|
527
694
|
type?: 'item';
|
|
528
695
|
label: string;
|
|
529
|
-
|
|
696
|
+
/** Optional when `href` is provided (a pure navigation entry needs no
|
|
697
|
+
* extra callback) — required otherwise. Called before the surface closes
|
|
698
|
+
* is the caller's job to avoid; `Menu`'s own wiring always closes first,
|
|
699
|
+
* then calls this. */
|
|
700
|
+
onSelect?: () => void;
|
|
701
|
+
/** Renders the entry as a link (`<a href>`, `role="menuitem"`) instead of
|
|
702
|
+
* a `<button>` — same href-vs-onClick convention as `MobileAppsDrawer`'s
|
|
703
|
+
* items. Framework-agnostic (a plain anchor, not `next/link`), matching
|
|
704
|
+
* every other `packages/ui` href slot. */
|
|
705
|
+
href?: string;
|
|
530
706
|
icon?: IconName;
|
|
531
707
|
/** Styles the item as a destructive action (e.g. "Delete list"). */
|
|
532
708
|
destructive?: boolean;
|
|
@@ -562,6 +738,15 @@ interface MenuProps {
|
|
|
562
738
|
/** Forwarded to `Popover` on desktop. Has no effect on mobile, where the
|
|
563
739
|
* menu is always a full-width `Drawer`. */
|
|
564
740
|
align?: 'left' | 'right';
|
|
741
|
+
/** Forwarded to `Popover` on desktop (same default: 288). Has no effect
|
|
742
|
+
* on mobile. Narrow this when the trigger lives inside a column
|
|
743
|
+
* narrower than the default width — e.g. a per-row "⋯" menu in a
|
|
744
|
+
* sidebar list — so the panel fits within that column's own bounds
|
|
745
|
+
* instead of overflowing past its edge, where a scrollable ancestor's
|
|
746
|
+
* `overflow-y: auto` (which the CSS Overflow spec computes the paired
|
|
747
|
+
* `overflow-x` to `auto` as well, not `visible`, once either axis is
|
|
748
|
+
* set) would clip it. */
|
|
749
|
+
width?: number | 'trigger';
|
|
565
750
|
}
|
|
566
751
|
/**
|
|
567
752
|
* Menu — an adaptive action menu: `Popover` on desktop, a bottom-sheet
|
|
@@ -592,7 +777,148 @@ declare function MenuEntries({ items, onSelect, }: {
|
|
|
592
777
|
/** Called after an item's own onSelect — typically the surface's onClose. */
|
|
593
778
|
onSelect: (entry: MenuItem) => void;
|
|
594
779
|
}): react.JSX.Element;
|
|
595
|
-
declare function Menu({ trigger, open, onClose, items, 'aria-label': ariaLabel, align }: MenuProps): react.JSX.Element;
|
|
780
|
+
declare function Menu({ trigger, open, onClose, items, 'aria-label': ariaLabel, align, width, }: MenuProps): react.JSX.Element;
|
|
781
|
+
|
|
782
|
+
interface UserMenuItem {
|
|
783
|
+
label: string;
|
|
784
|
+
icon: IconName;
|
|
785
|
+
/** Optional when `href` is provided (a pure navigation entry needs no
|
|
786
|
+
* extra callback). Required otherwise (e.g. "Sign out"). */
|
|
787
|
+
onSelect?: () => void;
|
|
788
|
+
/** Renders the entry as a link (`<a href>`) instead of a button — same
|
|
789
|
+
* href-vs-onClick convention as `MobileAppsDrawer`'s items. */
|
|
790
|
+
href?: string;
|
|
791
|
+
destructive?: boolean;
|
|
792
|
+
}
|
|
793
|
+
interface UserMenuProps {
|
|
794
|
+
name?: string;
|
|
795
|
+
email?: string;
|
|
796
|
+
avatarUrl?: string;
|
|
797
|
+
items: UserMenuItem[];
|
|
798
|
+
/** Trigger button's accessible label. Defaults to "Account". */
|
|
799
|
+
'aria-label'?: string;
|
|
800
|
+
align?: 'left' | 'right';
|
|
801
|
+
/** Trigger avatar size: `'md'` (36px) matches the platform sidebar's own
|
|
802
|
+
* `.avatar` exactly — the default, for that context. `'sm'` (32px) suits
|
|
803
|
+
* the more compact `Header` top-bar context instead. The panel's own
|
|
804
|
+
* user-info avatar (38px) is unaffected either way — only the trigger
|
|
805
|
+
* scales. */
|
|
806
|
+
size?: 'sm' | 'md';
|
|
807
|
+
}
|
|
808
|
+
/**
|
|
809
|
+
* UserMenu — the account avatar trigger + dropdown, generalized from the
|
|
810
|
+
* runtime shell's own `AccountMenu.tsx` (the real, currently-shipped
|
|
811
|
+
* component that every `shell: minimal` plugin would otherwise have to
|
|
812
|
+
* hand-rebuild locally instead of importing). Same visual structure and
|
|
813
|
+
* copy: an accent-filled avatar trigger (36px, matching the sidebar's own
|
|
814
|
+
* `.avatar`), a name+email header block (38px avatar) above a divider, then
|
|
815
|
+
* a plain item list.
|
|
816
|
+
*
|
|
817
|
+
* Presentational only — no auth/session/routing logic of its own. The
|
|
818
|
+
* consumer supplies `items`, each with its own `onSelect` and/or `href`,
|
|
819
|
+
* matching `MobileHeader`'s "consumer wires it up" boundary; a real
|
|
820
|
+
* "Sign out" is just an item whose `onSelect` does whatever the consuming
|
|
821
|
+
* app's sign-out actually requires. Built on the shared `Popover` primitive
|
|
822
|
+
* (self-contained open state, matching the original's own architecture)
|
|
823
|
+
* rather than a hand-rolled outside-click/Escape listener — the original
|
|
824
|
+
* `AccountMenu` and every plugin-local rebuild of it independently re-derive
|
|
825
|
+
* that same logic; one primitive here means one implementation to get
|
|
826
|
+
* right. Always a small anchored dropdown, never a full-width sheet —
|
|
827
|
+
* this is deliberately not built on the adaptive `Menu` component (which
|
|
828
|
+
* becomes a bottom `Drawer` on mobile): the real account menu is a compact
|
|
829
|
+
* floating panel on every surface, mobile included.
|
|
830
|
+
*/
|
|
831
|
+
declare function UserMenu({ name, email, avatarUrl, items, 'aria-label': ariaLabel, align, size, }: UserMenuProps): react.JSX.Element;
|
|
832
|
+
|
|
833
|
+
interface AppsLauncherItem {
|
|
834
|
+
/** Stable identity for this tile — used as the React key. */
|
|
835
|
+
key: string;
|
|
836
|
+
icon: ReactNode;
|
|
837
|
+
label: string;
|
|
838
|
+
/** Renders an `<a>` when set; otherwise a `<button>` calling `onClick` —
|
|
839
|
+
* same dual-mode convention as `MobileAppsDrawer`'s items. */
|
|
840
|
+
href?: string;
|
|
841
|
+
onClick?: () => void;
|
|
842
|
+
}
|
|
843
|
+
interface AppsLauncherProps {
|
|
844
|
+
items: AppsLauncherItem[];
|
|
845
|
+
/** Shows a spinner in place of the grid — e.g. while the tile list is
|
|
846
|
+
* still being fetched. */
|
|
847
|
+
loading?: boolean;
|
|
848
|
+
/** Shows an error message in place of the grid. */
|
|
849
|
+
error?: boolean;
|
|
850
|
+
errorMessage?: string;
|
|
851
|
+
/** Trigger button's icon. Defaults to a generic grid glyph — pass the
|
|
852
|
+
* real Launcher plugin's own icon for pixel parity with the platform. */
|
|
853
|
+
triggerIcon?: ReactNode;
|
|
854
|
+
/** Trigger button's accessible label, and the popover's own. Defaults to "Apps". */
|
|
855
|
+
'aria-label'?: string;
|
|
856
|
+
align?: 'left' | 'right';
|
|
857
|
+
}
|
|
858
|
+
/**
|
|
859
|
+
* AppsLauncher — the desktop "Apps" switcher: a 28px grid-icon trigger that
|
|
860
|
+
* opens a `Popover` with a 3-column tile grid. The desktop counterpart to
|
|
861
|
+
* `MobileAppsDrawer` — same `items` shape (`key`/`icon`/`label`/`href`/
|
|
862
|
+
* `onClick`), different chrome (floating panel vs. bottom sheet), matching
|
|
863
|
+
* how `Header`/`MobileHeader` already split the same way.
|
|
864
|
+
*
|
|
865
|
+
* Presentational only — no plugin-list fetching of its own. The consumer
|
|
866
|
+
* supplies `items` already resolved (and `loading`/`error` while it isn't
|
|
867
|
+
* yet), matching `MobileAppsDrawer`'s own boundary.
|
|
868
|
+
*/
|
|
869
|
+
declare function AppsLauncher({ items, loading, error, errorMessage, triggerIcon, 'aria-label': ariaLabel, align, }: AppsLauncherProps): react.JSX.Element;
|
|
870
|
+
|
|
871
|
+
interface NotificationsPanelItem {
|
|
872
|
+
id: string;
|
|
873
|
+
/** Category icon, already resolved (sized + colored) by the consumer —
|
|
874
|
+
* this component doesn't own category → color mapping. */
|
|
875
|
+
icon: ReactNode;
|
|
876
|
+
title: string;
|
|
877
|
+
/** Pre-formatted relative time (e.g. "2d ago") — formatting stays
|
|
878
|
+
* consumer-side so this component needs no Date/clock dependency. */
|
|
879
|
+
timeLabel: string;
|
|
880
|
+
read: boolean;
|
|
881
|
+
/** Renders the title as a link when set. */
|
|
882
|
+
href?: string;
|
|
883
|
+
/** Called when the title is clicked/tapped — typically "mark as read". */
|
|
884
|
+
onOpen?: () => void;
|
|
885
|
+
onDismiss: () => void;
|
|
886
|
+
}
|
|
887
|
+
interface NotificationsPanelProps {
|
|
888
|
+
items: NotificationsPanelItem[];
|
|
889
|
+
unreadCount: number;
|
|
890
|
+
onMarkAllRead?: () => void;
|
|
891
|
+
onClearAll?: () => void;
|
|
892
|
+
emptyMessage?: string;
|
|
893
|
+
/** Trigger button's accessible label base — the unread count is appended
|
|
894
|
+
* automatically, matching the real NotificationBell. Defaults to
|
|
895
|
+
* "Notifications". */
|
|
896
|
+
'aria-label'?: string;
|
|
897
|
+
align?: 'left' | 'right';
|
|
898
|
+
/** Optional footer link (e.g. to the consumer's own full notification
|
|
899
|
+
* list/detail view — RFC 0048) — mirrors the real `NotificationBell`'s
|
|
900
|
+
* "View all" link. Omit to render no footer, matching this component's
|
|
901
|
+
* behavior before this prop existed. */
|
|
902
|
+
viewAllHref?: string;
|
|
903
|
+
}
|
|
904
|
+
/**
|
|
905
|
+
* NotificationsPanel — the bell trigger + dropdown, generalized from the
|
|
906
|
+
* runtime shell's own `NotificationBell.tsx` visual structure and copy
|
|
907
|
+
* (title / Mark all read / Clear all / close, then a list of category-icon +
|
|
908
|
+
* title + time + unread-dot + dismiss rows, or an empty state).
|
|
909
|
+
*
|
|
910
|
+
* Deliberately **presentational only** — no fetch/SSE/polling, no
|
|
911
|
+
* category → color mapping, no mark-read/dismiss networking. The real
|
|
912
|
+
* `NotificationBell` owns real platform data (`/api/account/notifications`,
|
|
913
|
+
* a shared cross-instance store); this component can't and shouldn't know
|
|
914
|
+
* about that (SDK boundary). The consumer supplies `items` already resolved
|
|
915
|
+
* and wires `onOpen`/`onDismiss`/`onMarkAllRead`/`onClearAll` to whatever
|
|
916
|
+
* its own data layer needs, against a shared panel instead of a
|
|
917
|
+
* `shell: minimal` plugin hand-rebuilding one locally. Built on the shared
|
|
918
|
+
* `Popover` primitive (self-contained open state) rather than the
|
|
919
|
+
* original's hand-rolled outside-click/Escape listener.
|
|
920
|
+
*/
|
|
921
|
+
declare function NotificationsPanel({ items, unreadCount, onMarkAllRead, onClearAll, emptyMessage, 'aria-label': ariaLabel, align, viewAllHref, }: NotificationsPanelProps): react.JSX.Element;
|
|
596
922
|
|
|
597
923
|
interface ToastItem {
|
|
598
924
|
id: string;
|
|
@@ -624,6 +950,64 @@ interface CardProps extends HTMLAttributes<HTMLElement> {
|
|
|
624
950
|
}
|
|
625
951
|
declare function Card({ as: Tag, interactive, padding, className, children, ...rest }: CardProps): react.JSX.Element;
|
|
626
952
|
|
|
953
|
+
interface CardTileGridProps {
|
|
954
|
+
children: ReactNode;
|
|
955
|
+
/** Minimum tile width (px) before the grid wraps to fewer columns. */
|
|
956
|
+
minTileWidth?: number;
|
|
957
|
+
className?: string;
|
|
958
|
+
/**
|
|
959
|
+
* Tight spacing with tiles that keep their natural width instead of
|
|
960
|
+
* stretching to fill the row — for small `variant="icon"` tiles (e.g. a
|
|
961
|
+
* folder grid), where the default grid's spacious gap and `1fr`-stretched
|
|
962
|
+
* columns leave a lot of empty space around a tile that's really just an
|
|
963
|
+
* icon + short label.
|
|
964
|
+
*/
|
|
965
|
+
dense?: boolean;
|
|
966
|
+
}
|
|
967
|
+
/** Responsive grid of `CardTile`/`NewCardTile` children — `repeat(auto-fill, minmax(minTileWidth, 1fr))`, or `minmax(minTileWidth, max-content)` with a tighter gap when `dense`. */
|
|
968
|
+
declare function CardTileGrid({ children, minTileWidth, className, dense, }: CardTileGridProps): react.JSX.Element;
|
|
969
|
+
interface CardTileProps extends HTMLAttributes<HTMLDivElement> {
|
|
970
|
+
/** Content centered in the tile's full-bleed top banner — typically an `<Icon>`. Omit for a plain strip. */
|
|
971
|
+
banner?: ReactNode;
|
|
972
|
+
/** Banner background color (applied inline). Falls back to a neutral sunken-surface strip when omitted. Ignored by `variant="icon"`, which has no banner box. */
|
|
973
|
+
bannerColor?: string;
|
|
974
|
+
/** Tile footer content — typically the item's name. */
|
|
975
|
+
children: ReactNode;
|
|
976
|
+
/**
|
|
977
|
+
* `"card"` (default): a bordered card with a colored/iconed banner strip
|
|
978
|
+
* over a footer label. `"icon"`: a plain "Finder icon" tile — no card
|
|
979
|
+
* chrome at all, just a large centered icon with a label beneath it. Use
|
|
980
|
+
* `"icon"` for entities that read as a single glyph + name (e.g. a
|
|
981
|
+
* folder), where a bordered card reads as heavier than the content needs.
|
|
982
|
+
*/
|
|
983
|
+
variant?: 'card' | 'icon';
|
|
984
|
+
}
|
|
985
|
+
/**
|
|
986
|
+
* A colored/iconed banner over a footer label — a card shape `Card` can't
|
|
987
|
+
* express, since `Card`'s `padding` prop always pads its single box with no
|
|
988
|
+
* way for a top region to bleed to the edges. Not link-aware: wrap it in the
|
|
989
|
+
* host framework's own link component (e.g. Next's `<Link>`) to make it
|
|
990
|
+
* navigable, same as every existing consumer of this pattern does.
|
|
991
|
+
*/
|
|
992
|
+
declare function CardTile({ banner, bannerColor, children, className, variant, ...rest }: CardTileProps): react.JSX.Element;
|
|
993
|
+
interface NewCardTileProps extends ButtonHTMLAttributes<HTMLButtonElement> {
|
|
994
|
+
/** Icon shown beside the label (`"card"`) or alone (`"icon"`). Defaults to `"plus"`. */
|
|
995
|
+
icon?: IconName;
|
|
996
|
+
/** Visible next to the icon for `"card"`. For `"icon"`, not rendered as text — used as the button's `aria-label` instead, since the tile is icon-only. */
|
|
997
|
+
label: string;
|
|
998
|
+
/**
|
|
999
|
+
* `"card"` (default): a dashed box matching a `variant="card"` `CardTile`'s
|
|
1000
|
+
* footprint — icon beside a visible label. `"icon"`: matches a
|
|
1001
|
+
* `variant="icon"` `CardTile`'s footprint instead, but icon-only, no
|
|
1002
|
+
* visible label — a plain "+" reads as self-explanatory once every
|
|
1003
|
+
* sibling tile in the same `dense` grid is already an unlabeled icon
|
|
1004
|
+
* (the dashed border alone marks it as the "add new" affordance).
|
|
1005
|
+
*/
|
|
1006
|
+
variant?: 'card' | 'icon';
|
|
1007
|
+
}
|
|
1008
|
+
/** Dashed ghost tile matching a real `CardTile`'s footprint — the grid's own "add new" affordance. */
|
|
1009
|
+
declare function NewCardTile({ icon, label, type, variant, className, ...rest }: NewCardTileProps): react.JSX.Element;
|
|
1010
|
+
|
|
627
1011
|
/** Props to spread onto the field's actual form control. */
|
|
628
1012
|
interface FormFieldRenderProps {
|
|
629
1013
|
id: string;
|
|
@@ -654,10 +1038,252 @@ declare function FormField({ label, hint, error, id, required, className, childr
|
|
|
654
1038
|
interface PageHeaderProps {
|
|
655
1039
|
title: string;
|
|
656
1040
|
description?: string;
|
|
1041
|
+
/** Optional icon rendered inline before the title text, on both breakpoints — e.g. `folder-open` next to a folder's name. */
|
|
1042
|
+
icon?: IconName;
|
|
1043
|
+
/**
|
|
1044
|
+
* Page-level controls — anywhere from one button to a whole toolbar.
|
|
1045
|
+
* Renders on both breakpoints unchanged; if it needs to look different on
|
|
1046
|
+
* mobile (e.g. collapsing into its own "⋮" trigger), that's the action
|
|
1047
|
+
* content's own responsibility — same pattern Shopper's `ListHeaderActions`
|
|
1048
|
+
* already follows (it calls `useIsMobile()` internally). `PageHeader`
|
|
1049
|
+
* itself has no opinion on what's inside this slot.
|
|
1050
|
+
*/
|
|
657
1051
|
action?: ReactNode;
|
|
658
1052
|
className?: string;
|
|
1053
|
+
/** Heading level for the title. Defaults to `1` for standalone use; pass
|
|
1054
|
+
* `2` or `3` when the page already sits under a shell/plugin `<h1>`, to
|
|
1055
|
+
* avoid a duplicate top-level heading. Visual style is unchanged at every
|
|
1056
|
+
* level — only the rendered tag changes. Applies on both breakpoints. */
|
|
1057
|
+
headingLevel?: 1 | 2 | 3;
|
|
1058
|
+
/** Mobile only — shows a back button (calling this) to the left of the
|
|
1059
|
+
* title when provided. Omit it to render without one. */
|
|
1060
|
+
onBack?: () => void;
|
|
1061
|
+
/** Mobile only — shows a vertical-ellipsis icon button (calling this) at
|
|
1062
|
+
* the far right when provided, typically to open a drawer/menu. PageHeader
|
|
1063
|
+
* has no opinion on what that menu contains — it only renders the trigger
|
|
1064
|
+
* and calls back. Separate from `action`, which renders identically on
|
|
1065
|
+
* both breakpoints; this is a mobile-only affordance with no web
|
|
1066
|
+
* equivalent. */
|
|
1067
|
+
onMenuClick?: () => void;
|
|
659
1068
|
}
|
|
660
|
-
|
|
1069
|
+
/**
|
|
1070
|
+
* PageHeader — a page's title section. Web: title + description on the
|
|
1071
|
+
* left, action on the right. Mobile: an optional back button, a single-line
|
|
1072
|
+
* title, and the same action slot — a compact single row, matching Kanban's
|
|
1073
|
+
* own hand-rolled MobileBoardHeader this was generalized from. The
|
|
1074
|
+
* web/mobile fork is `ResponsiveSurface` — only one tree is ever mounted.
|
|
1075
|
+
*/
|
|
1076
|
+
declare function PageHeader({ title, description, icon, action, className, headingLevel, onBack, onMenuClick, }: PageHeaderProps): react.JSX.Element;
|
|
1077
|
+
|
|
1078
|
+
interface PageContainerProps extends HTMLAttributes<HTMLDivElement> {
|
|
1079
|
+
/**
|
|
1080
|
+
* Constrains and centers content width. The bound includes `padding`
|
|
1081
|
+
* (the container is `border-box`). Defaults to `'full'` — narrowing is
|
|
1082
|
+
* opt-in, so wrapping a page in `PageContainer` never silently clamps it.
|
|
1083
|
+
*/
|
|
1084
|
+
maxWidth?: 'sm' | 'md' | 'lg' | 'full';
|
|
1085
|
+
/**
|
|
1086
|
+
* The page gutter. Responsive — each step is tighter at ≤768px. Defaults
|
|
1087
|
+
* to `'md'`, which is exactly what the runtime shell used to apply
|
|
1088
|
+
* (`--sv-space-8` desktop / `--sv-space-4` mobile), so a bare
|
|
1089
|
+
* `<PageContainer>` is the correct wrapper for a plugin that previously
|
|
1090
|
+
* relied on the shell for its padding.
|
|
1091
|
+
*
|
|
1092
|
+
* Use `'none'` when an ancestor already supplies the gutter — inside a
|
|
1093
|
+
* `Dialog`, whose own content region is padded.
|
|
1094
|
+
*/
|
|
1095
|
+
padding?: 'none' | 'sm' | 'md' | 'lg';
|
|
1096
|
+
children: ReactNode;
|
|
1097
|
+
}
|
|
1098
|
+
/**
|
|
1099
|
+
* The shared page wrapper every plugin's own layout or page renders through.
|
|
1100
|
+
*
|
|
1101
|
+
* A plugin owns its page gutter; the runtime shell does not impose one
|
|
1102
|
+
* (task 9.25). The shell still reserves space for chrome a plugin cannot
|
|
1103
|
+
* know about — the offline banner's height and the mobile footer's — but it
|
|
1104
|
+
* contributes no inset of its own, so this component is the single place a
|
|
1105
|
+
* plugin's four-sided padding comes from.
|
|
1106
|
+
*/
|
|
1107
|
+
declare function PageContainer({ maxWidth, padding, className, children, ...rest }: PageContainerProps): react.JSX.Element;
|
|
1108
|
+
|
|
1109
|
+
interface HeaderPlugin {
|
|
1110
|
+
/** Plugin display name, rendered next to its icon. */
|
|
1111
|
+
name: string;
|
|
1112
|
+
/** Plugin icon — already sized (28px) and built by the consumer (typically
|
|
1113
|
+
* an `<img src="/plugin-icons/<id>.svg">`), matching `MobileHeader`'s own
|
|
1114
|
+
* router-agnostic convention for icon-shaped slots. */
|
|
1115
|
+
icon?: ReactNode;
|
|
1116
|
+
/** Where the plugin name/icon links to — typically the plugin's own
|
|
1117
|
+
* `routePrefix`. Omit to render plain (non-interactive) text instead of a
|
|
1118
|
+
* link — e.g. a plugin's own home page linking to itself would be a no-op. */
|
|
1119
|
+
href?: string;
|
|
1120
|
+
}
|
|
1121
|
+
interface HeaderProps extends HTMLAttributes<HTMLElement> {
|
|
1122
|
+
/** Instance name — derives the brand badge's initial (its first character,
|
|
1123
|
+
* uppercased) and the badge's accessible label. Does not render as text
|
|
1124
|
+
* anywhere in this component (unlike `MobileHeader`'s optional `title`). */
|
|
1125
|
+
instanceName: string;
|
|
1126
|
+
/** Optional instance logo image; falls back to `instanceName`'s initial
|
|
1127
|
+
* (matching the runtime shell's own sidebar/mobile-header fallback). */
|
|
1128
|
+
instanceLogoUrl?: string;
|
|
1129
|
+
/** Where the brand badge links — typically the Launcher plugin's route. */
|
|
1130
|
+
homeHref: string;
|
|
1131
|
+
/** The active plugin, rendered next to the brand badge. Omit for a page
|
|
1132
|
+
* with no single active plugin (e.g. the Launcher itself). */
|
|
1133
|
+
plugin?: HeaderPlugin;
|
|
1134
|
+
/** Apps launcher trigger — already wired by the consumer (icon button plus
|
|
1135
|
+
* whatever popover/drawer it opens). Always rendered, matching
|
|
1136
|
+
* `MobileHeader`'s bell/avatarMenu boundary: this component owns layout
|
|
1137
|
+
* only, never launcher/notification/account interaction logic. */
|
|
1138
|
+
launcher: ReactNode;
|
|
1139
|
+
/** Notification bell trigger — always rendered; not overridable. Same
|
|
1140
|
+
* "just an icon slot" boundary as `MobileHeader`'s own `bell` prop. */
|
|
1141
|
+
notifications: ReactNode;
|
|
1142
|
+
/** Avatar / account menu trigger — always rendered; not overridable. */
|
|
1143
|
+
avatarMenu: ReactNode;
|
|
1144
|
+
}
|
|
1145
|
+
/**
|
|
1146
|
+
* The web top-bar header: brand badge (+ optional active plugin) on the
|
|
1147
|
+
* left, launcher · notifications · avatar menu on the right. This is the
|
|
1148
|
+
* `shell: minimal` counterpart to the runtime shell's own sidebar — a plugin
|
|
1149
|
+
* that owns its whole viewport (no platform sidebar) renders this instead,
|
|
1150
|
+
* the same way it renders `MobileHeader` in place of the platform's mobile
|
|
1151
|
+
* chrome. Extracted into a shared component so a `shell: minimal` plugin
|
|
1152
|
+
* doesn't have to hand-roll its own top bar from scratch.
|
|
1153
|
+
*
|
|
1154
|
+
* Presentational only — no data fetching, no account/notification/launcher
|
|
1155
|
+
* logic of its own. `launcher`/`notifications`/`avatarMenu` are supplied
|
|
1156
|
+
* fully built, exactly like `MobileHeader`'s `bell`/`avatarMenu`: this
|
|
1157
|
+
* component stays router-agnostic and interaction-agnostic, and never
|
|
1158
|
+
* duplicates a real `AccountMenu`/notification-panel/apps-switcher
|
|
1159
|
+
* implementation.
|
|
1160
|
+
*/
|
|
1161
|
+
declare function Header({ instanceName, instanceLogoUrl, homeHref, plugin, launcher, notifications, avatarMenu, className, ...rest }: HeaderProps): react.JSX.Element;
|
|
1162
|
+
|
|
1163
|
+
interface MobileHeaderProps extends HTMLAttributes<HTMLDivElement> {
|
|
1164
|
+
/** The brand/logo element — already a complete, clickable link home (e.g. an
|
|
1165
|
+
* instance logo wrapped in the consumer's own router `Link`). Always
|
|
1166
|
+
* rendered. `MobileHeader` stays router-agnostic, so this is provided
|
|
1167
|
+
* fully built rather than as a bare icon + href. Optional — omit it to get
|
|
1168
|
+
* a default "S" badge (matching the runtime shell's own fallback brand
|
|
1169
|
+
* icon when no instance logo is configured), sized and styled to the same
|
|
1170
|
+
* 36px/radius-md convention a custom logo is expected to follow. */
|
|
1171
|
+
logo?: ReactNode;
|
|
1172
|
+
/** Optional contextual title rendered beside the logo (e.g. the active
|
|
1173
|
+
* plugin's name). Absent by default — this is the one overridable part of
|
|
1174
|
+
* the header. See RFC 0088. */
|
|
1175
|
+
title?: string;
|
|
1176
|
+
/** Notification bell — always rendered; not overridable.
|
|
1177
|
+
* TODO: this is just an icon slot today (the consumer wires up its own
|
|
1178
|
+
* click handler/content) — it doesn't yet accommodate a full notification
|
|
1179
|
+
* feature (unread badge, dropdown panel, etc. — see the runtime shell's
|
|
1180
|
+
* own NotificationBell). Deferred; revisit once a plugin actually needs
|
|
1181
|
+
* more than a bare bell icon here. */
|
|
1182
|
+
bell: ReactNode;
|
|
1183
|
+
/** Avatar / account menu trigger — always rendered; not overridable. */
|
|
1184
|
+
avatarMenu: ReactNode;
|
|
1185
|
+
}
|
|
1186
|
+
/**
|
|
1187
|
+
* The mobile header bar: brand/logo (always shown), an optional contextual
|
|
1188
|
+
* title, and a fixed bell + avatar-menu cluster. Owns its own chrome —
|
|
1189
|
+
* background, border-bottom, sticky positioning, safe-area-aware padding,
|
|
1190
|
+
* and a 768px max-width (so it never stretches full-bleed if mounted outside
|
|
1191
|
+
* an actual mobile viewport) — matching the runtime shell's own mobile
|
|
1192
|
+
* header exactly, since this component is meant to replace that hand-rolled
|
|
1193
|
+
* markup. Presentational only — no data fetching; the consumer supplies
|
|
1194
|
+
* `bell`/`avatarMenu` (and, optionally, `logo`) as already-wired components
|
|
1195
|
+
* (their own sizing — 36px logo/avatar, 44px bell tap target — is the
|
|
1196
|
+
* consumer's responsibility, matching the reference implementation in the
|
|
1197
|
+
* runtime shell). See RFC 0088 for the immutable/overridable boundary this
|
|
1198
|
+
* enforces.
|
|
1199
|
+
*
|
|
1200
|
+
* Self-measures its own rendered height and publishes it as
|
|
1201
|
+
* `--sv-shell-header-height` on the shell root — see
|
|
1202
|
+
* `usePublishShellChromeHeight`'s own doc comment for why this exists (it's
|
|
1203
|
+
* what makes a self-rendered header, the `shellConfig.mobileHeader: false`
|
|
1204
|
+
* pattern, correctly position `Dialog`'s inset and any other consumer of
|
|
1205
|
+
* that variable instead of being overlapped by it).
|
|
1206
|
+
*/
|
|
1207
|
+
declare function MobileHeader({ logo, title, bell, avatarMenu, className, ...rest }: MobileHeaderProps): react.JSX.Element;
|
|
1208
|
+
|
|
1209
|
+
interface FooterIcon {
|
|
1210
|
+
icon: ReactNode;
|
|
1211
|
+
label: string;
|
|
1212
|
+
href?: string;
|
|
1213
|
+
onClick?: () => void;
|
|
1214
|
+
active?: boolean;
|
|
1215
|
+
}
|
|
1216
|
+
interface MobileFooterProps extends HTMLAttributes<HTMLElement> {
|
|
1217
|
+
/** Opens the app drawer/launcher. Always rendered as the centered slot;
|
|
1218
|
+
* not overridable. */
|
|
1219
|
+
onOpenApps: () => void;
|
|
1220
|
+
/** Custom launcher icon (e.g. a plugin-supplied image). Defaults to a
|
|
1221
|
+
* generic grid icon. */
|
|
1222
|
+
launcherIcon?: ReactNode;
|
|
1223
|
+
/** Whether the launcher's own drawer is currently open (applies the
|
|
1224
|
+
* pressed/active visual state). */
|
|
1225
|
+
launcherOpen?: boolean;
|
|
1226
|
+
/** 1 or 2 icons rendered to the left of the centered launcher. */
|
|
1227
|
+
leftIcons: FooterIcon[];
|
|
1228
|
+
/** 1 or 2 icons rendered to the right of the centered launcher. Must match
|
|
1229
|
+
* `leftIcons.length` — the launcher stays visually centered, so a mismatch
|
|
1230
|
+
* is flagged (dev-mode only, never thrown) rather than silently allowed. */
|
|
1231
|
+
rightIcons: FooterIcon[];
|
|
1232
|
+
}
|
|
1233
|
+
/**
|
|
1234
|
+
* The mobile footer bar: a centered, fixed "Apps" launcher flanked by 1 or 2
|
|
1235
|
+
* overridable icons on each side (3 or 5 total, matching standard
|
|
1236
|
+
* iOS/Android bottom-nav conventions). Owns its own chrome — background,
|
|
1237
|
+
* border-top, safe-area-aware bottom padding, and a 768px max-width (so it
|
|
1238
|
+
* never stretches full-bleed if mounted outside an actual mobile viewport)
|
|
1239
|
+
* — matching the runtime shell's own mobile footer (MobileNav) exactly,
|
|
1240
|
+
* since this component is meant to replace that hand-rolled markup.
|
|
1241
|
+
* Presentational only — no data fetching; the consumer owns the
|
|
1242
|
+
* drawer/overlay `onOpenApps` opens. See RFC 0088 for the
|
|
1243
|
+
* immutable/overridable boundary this enforces.
|
|
1244
|
+
*
|
|
1245
|
+
* Self-measures its own rendered height and publishes it as
|
|
1246
|
+
* `--sv-shell-footer-height` on the shell root — see
|
|
1247
|
+
* `usePublishShellChromeHeight`'s own doc comment for why this exists (it's
|
|
1248
|
+
* what makes a self-rendered footer, the `shellConfig.mobileFooter: false`
|
|
1249
|
+
* pattern, correctly clear `Sheet`/`Drawer`/`Dialog` instead of being
|
|
1250
|
+
* overlapped by them).
|
|
1251
|
+
*/
|
|
1252
|
+
declare function MobileFooter({ onOpenApps, launcherIcon, launcherOpen, leftIcons, rightIcons, className, ...rest }: MobileFooterProps): react.JSX.Element;
|
|
1253
|
+
|
|
1254
|
+
interface MobileAppsDrawerItem {
|
|
1255
|
+
/** Stable identity for this tile — used as the React key. */
|
|
1256
|
+
key: string;
|
|
1257
|
+
icon: ReactNode;
|
|
1258
|
+
label: string;
|
|
1259
|
+
/** Renders an `<a>` when set; otherwise a `<button>` calling `onClick`. */
|
|
1260
|
+
href?: string;
|
|
1261
|
+
onClick?: () => void;
|
|
1262
|
+
}
|
|
1263
|
+
interface MobileAppsDrawerProps {
|
|
1264
|
+
open: boolean;
|
|
1265
|
+
onClose: () => void;
|
|
1266
|
+
/** Accessible name for the drawer panel — required since there's no
|
|
1267
|
+
* visible title to fall back to (Drawer's grab handle plus swipe-down /
|
|
1268
|
+
* scrim-tap already cover dismissal, so no header row is rendered). */
|
|
1269
|
+
'aria-label': string;
|
|
1270
|
+
/** The grid tiles — supplied by the consumer (e.g. installed plugins,
|
|
1271
|
+
* a plugin's own sections). MobileAppsDrawer owns only the drawer
|
|
1272
|
+
* chrome/grid layout, never the item list itself. */
|
|
1273
|
+
items: MobileAppsDrawerItem[];
|
|
1274
|
+
}
|
|
1275
|
+
/**
|
|
1276
|
+
* The mobile "Apps" drawer: a bottom sheet with a 3-column grid of icon
|
|
1277
|
+
* tiles — same tile size (56px, radius 14px) and grid layout as the runtime
|
|
1278
|
+
* shell's own Apps drawer (MobileNav's Drawer content), generalized so the
|
|
1279
|
+
* tile list comes from the consumer (the shell passes installed plugins; a
|
|
1280
|
+
* plugin composing its own MobileFooter launcher passes whatever its own
|
|
1281
|
+
* "apps" are, e.g. a section index) rather than being hardcoded to plugins
|
|
1282
|
+
* specifically. No header row (title/close button) — Drawer's own grab
|
|
1283
|
+
* handle plus swipe-down/scrim-tap dismissal are enough, and a header ate
|
|
1284
|
+
* into the limited vertical space a launcher grid needs most.
|
|
1285
|
+
*/
|
|
1286
|
+
declare function MobileAppsDrawer({ open, onClose, items, ...rest }: MobileAppsDrawerProps): react.JSX.Element;
|
|
661
1287
|
|
|
662
1288
|
interface EmptyStateProps {
|
|
663
1289
|
icon?: IconName;
|
|
@@ -668,6 +1294,121 @@ interface EmptyStateProps {
|
|
|
668
1294
|
}
|
|
669
1295
|
declare function EmptyState({ icon, heading, description, action, className }: EmptyStateProps): react.JSX.Element;
|
|
670
1296
|
|
|
1297
|
+
interface OfflineGateProps {
|
|
1298
|
+
children: ReactNode;
|
|
1299
|
+
/** Defaults to a generic "this section" phrasing — pass the surface's own name. */
|
|
1300
|
+
surfaceName?: string;
|
|
1301
|
+
className?: string;
|
|
1302
|
+
}
|
|
1303
|
+
/**
|
|
1304
|
+
* OfflineGate — blocks administrative/settings surfaces from rendering
|
|
1305
|
+
* (possibly stale) cached content while the device has no network, showing an
|
|
1306
|
+
* explanatory empty state instead (research 0012, epic task 2.32).
|
|
1307
|
+
*
|
|
1308
|
+
* Distinct from `OfflineBanner`: the banner is an informational overlay that
|
|
1309
|
+
* coexists with normal content everywhere in the shell. This is a hard block
|
|
1310
|
+
* for surfaces where operating against stale data is actively wrong — e.g. a
|
|
1311
|
+
* Console user list or an Account billing page reflects a point-in-time
|
|
1312
|
+
* snapshot the moment it's server-rendered, and any cached copy replayed
|
|
1313
|
+
* later has no way to signal that it may already be stale. `OfflineGate`
|
|
1314
|
+
* doesn't try to tell staleness apart from freshness; it removes the
|
|
1315
|
+
* question by not rendering the content at all while offline.
|
|
1316
|
+
*
|
|
1317
|
+
* `children` is rendered unconditionally while online, so this adds no cost
|
|
1318
|
+
* to the common case — it only decides what to show, never how the wrapped
|
|
1319
|
+
* content itself is fetched or cached.
|
|
1320
|
+
*/
|
|
1321
|
+
declare function OfflineGate({ children, surfaceName, className }: OfflineGateProps): react.JSX.Element;
|
|
1322
|
+
|
|
1323
|
+
interface DeviceOnlyGateProps {
|
|
1324
|
+
children: ReactNode;
|
|
1325
|
+
/** Defaults to a generic "this app" phrasing — pass the plugin's own name. */
|
|
1326
|
+
surfaceName?: string;
|
|
1327
|
+
/**
|
|
1328
|
+
* The caller's own `isDeviceOnlyTierAvailable()` result (from
|
|
1329
|
+
* `@sovereignfs/sdk/device-client`) — taken as a plain boolean rather than
|
|
1330
|
+
* computed inside this component, so `@sovereignfs/ui` never depends on
|
|
1331
|
+
* `@sovereignfs/sdk` (this package must stay usable standalone, outside the
|
|
1332
|
+
* plugin runtime — RFC 0073).
|
|
1333
|
+
*/
|
|
1334
|
+
available: boolean;
|
|
1335
|
+
className?: string;
|
|
1336
|
+
}
|
|
1337
|
+
/**
|
|
1338
|
+
* DeviceOnlyGate — blocks a `device-only`-tier plugin's content from
|
|
1339
|
+
* rendering on a surface that cannot provide the durable, encrypted,
|
|
1340
|
+
* device-auth-gated store that tier requires, showing an explanatory empty
|
|
1341
|
+
* state instead of a broken screen (research 0012, epic tasks 2.33 + 3.36).
|
|
1342
|
+
*
|
|
1343
|
+
* A `device-only` plugin wraps its own root content in this the same way
|
|
1344
|
+
* Console and Account wrap theirs in `OfflineGate` — an opt-in pattern each
|
|
1345
|
+
* surface applies to itself, not a platform-level route gate. The launcher
|
|
1346
|
+
* tile's own "Phone only" badge (`useOfflineTileState`) is advisory UI on top
|
|
1347
|
+
* of this; this component is the actual gate, since a user can always reach a
|
|
1348
|
+
* route directly (a bookmark, a deep link) without passing through the
|
|
1349
|
+
* launcher tile at all.
|
|
1350
|
+
*
|
|
1351
|
+
* **Not a security boundary.** Availability here is a capability signal, not
|
|
1352
|
+
* an authorization check — the real protection for `device-only` data is
|
|
1353
|
+
* that it is encrypted and the key requires device auth to release (epic task
|
|
1354
|
+
* 1.22), so it stays inaccessible regardless of whether this gate is present,
|
|
1355
|
+
* bypassed, or simply never reached (e.g. a hand-crafted request against the
|
|
1356
|
+
* plugin's own API routes).
|
|
1357
|
+
*/
|
|
1358
|
+
declare function DeviceOnlyGate({ children, surfaceName, available, className, }: DeviceOnlyGateProps): react.JSX.Element;
|
|
1359
|
+
|
|
1360
|
+
/**
|
|
1361
|
+
* `'checking'` covers the moment before the caller's own async status check
|
|
1362
|
+
* resolves — same transitional-state pattern `EncryptionSection`'s local
|
|
1363
|
+
* `LocalState` uses, so this gate never flashes an empty state before the
|
|
1364
|
+
* real answer is known. `'no-device-auth'` is the hard-block case (RFC 0093
|
|
1365
|
+
* §5, epic task 1.22): the environment supports the tier, but this device
|
|
1366
|
+
* has no passcode, fingerprint, or face unlock configured, so a key cannot
|
|
1367
|
+
* be created here at all — distinct from `'not-set-up'`, where it can.
|
|
1368
|
+
*/
|
|
1369
|
+
type DeviceStorageKeyGateStatus = 'checking' | 'unsupported' | 'no-device-auth' | 'not-set-up' | 'set-up';
|
|
1370
|
+
interface DeviceStorageKeyGateProps {
|
|
1371
|
+
children: ReactNode;
|
|
1372
|
+
/** Defaults to a generic "This app" phrasing — pass the plugin's own name. */
|
|
1373
|
+
surfaceName?: string;
|
|
1374
|
+
/**
|
|
1375
|
+
* The caller's own `getDeviceStorageKeyStatus()` result (from
|
|
1376
|
+
* `@sovereignfs/sdk/device-only-storage`) — taken as a plain status rather
|
|
1377
|
+
* than computed inside this component, so `@sovereignfs/ui` never depends
|
|
1378
|
+
* on `@sovereignfs/sdk` (same boundary `DeviceOnlyGate`'s `available` prop
|
|
1379
|
+
* draws — this package must stay usable standalone, outside the plugin
|
|
1380
|
+
* runtime — RFC 0073).
|
|
1381
|
+
*/
|
|
1382
|
+
status: DeviceStorageKeyGateStatus;
|
|
1383
|
+
/**
|
|
1384
|
+
* Rendered under the "not set up" message — typically a link to Account →
|
|
1385
|
+
* Security. Not built in here: this package depends on no router, so the
|
|
1386
|
+
* caller supplies its own navigable element (a plain `<a>`, a framework
|
|
1387
|
+
* `<Link>`, whatever the host app uses).
|
|
1388
|
+
*/
|
|
1389
|
+
setupAction?: ReactNode;
|
|
1390
|
+
className?: string;
|
|
1391
|
+
}
|
|
1392
|
+
/**
|
|
1393
|
+
* DeviceStorageKeyGate — blocks a `device-only`-tier plugin's content until
|
|
1394
|
+
* the user has set up their Device Storage Key (RFC 0093 §2, epic task
|
|
1395
|
+
* 1.22). Distinct from `DeviceOnlyGate`: that one checks whether the *tier
|
|
1396
|
+
* itself* is available on this surface (native shell, secure-storage bridge
|
|
1397
|
+
* present); this one checks whether the user has actually completed the
|
|
1398
|
+
* one-time, per-device enrollment that tier depends on. A `device-only`
|
|
1399
|
+
* plugin typically wants both — `DeviceOnlyGate` outermost (no point
|
|
1400
|
+
* checking enrollment on a surface that cannot offer the tier at all), this
|
|
1401
|
+
* gate inside it.
|
|
1402
|
+
*
|
|
1403
|
+
* Enrollment is centralized and decoupled from any single plugin (RFC 0093
|
|
1404
|
+
* §2): a plugin that finds no key set up does not run its own enrollment
|
|
1405
|
+
* ceremony inline. It stops here and points the user at Account → Security —
|
|
1406
|
+
* the one place enrollment happens for every `device-only` plugin on this
|
|
1407
|
+
* device — via `setupAction`, the same opt-in, self-applied pattern
|
|
1408
|
+
* `OfflineGate`/`DeviceOnlyGate` already use for their own surfaces.
|
|
1409
|
+
*/
|
|
1410
|
+
declare function DeviceStorageKeyGate({ children, surfaceName, status, setupAction, className, }: DeviceStorageKeyGateProps): react.JSX.Element | null;
|
|
1411
|
+
|
|
671
1412
|
interface SpinnerProps {
|
|
672
1413
|
size?: 'sm' | 'md' | 'lg';
|
|
673
1414
|
label?: string;
|
|
@@ -688,12 +1429,25 @@ interface NavTabItem {
|
|
|
688
1429
|
href: string;
|
|
689
1430
|
active?: boolean;
|
|
690
1431
|
}
|
|
1432
|
+
/** Props to spread onto a consumer-supplied link element — keeps NavTabs' own
|
|
1433
|
+
* class names and a11y attributes in sync with whatever the consumer renders. */
|
|
1434
|
+
interface NavTabLinkProps {
|
|
1435
|
+
href: string;
|
|
1436
|
+
className: string;
|
|
1437
|
+
'aria-current': 'page' | undefined;
|
|
1438
|
+
children: ReactNode;
|
|
1439
|
+
}
|
|
691
1440
|
interface NavTabsProps {
|
|
692
1441
|
items: NavTabItem[];
|
|
693
1442
|
className?: string;
|
|
694
1443
|
'aria-label'?: string;
|
|
1444
|
+
/** Render a tab as something other than a plain `<a href>` — e.g. Next's
|
|
1445
|
+
* `<Link replace>` inside an overlay-shell plugin, where a full page
|
|
1446
|
+
* navigation would break the dialog's history contract. Receives the item
|
|
1447
|
+
* plus the class name/aria attributes NavTabs would otherwise apply. */
|
|
1448
|
+
renderLink?: (item: NavTabItem, linkProps: NavTabLinkProps) => ReactNode;
|
|
695
1449
|
}
|
|
696
|
-
declare function NavTabs({ items, className, 'aria-label': ariaLabel }: NavTabsProps): react.JSX.Element;
|
|
1450
|
+
declare function NavTabs({ items, className, 'aria-label': ariaLabel, renderLink }: NavTabsProps): react.JSX.Element;
|
|
697
1451
|
|
|
698
1452
|
interface TooltipProps {
|
|
699
1453
|
content: string;
|
|
@@ -1043,6 +1797,17 @@ interface MessageScrollerProps {
|
|
|
1043
1797
|
* content does not yank them back down — a "New messages" button appears
|
|
1044
1798
|
* instead, matching the pattern every chat product (Slack, Discord,
|
|
1045
1799
|
* ChatGPT) already uses.
|
|
1800
|
+
*
|
|
1801
|
+
* A short conversation is bottom-anchored, not left floating at the top of
|
|
1802
|
+
* the container with empty space below it — a leading flex-grow spacer
|
|
1803
|
+
* absorbs the leftover space and collapses to nothing once messages
|
|
1804
|
+
* overflow. Deliberately not `justify-content: flex-end` on the scroll
|
|
1805
|
+
* container itself: Chromium miscomputes `scrollHeight` as equal to
|
|
1806
|
+
* `clientHeight` for an overflowing flex-end-packed column, which makes the
|
|
1807
|
+
* container look fully scrolled to the bottom and permanently hides the
|
|
1808
|
+
* start of a long conversation (confirmed live before choosing this
|
|
1809
|
+
* approach). The spacer keeps `justify-content` at its default, so overflow
|
|
1810
|
+
* scrolling is unaffected.
|
|
1046
1811
|
*/
|
|
1047
1812
|
declare function MessageScroller({ children, className }: MessageScrollerProps): react.JSX.Element;
|
|
1048
1813
|
|
|
@@ -1313,6 +2078,48 @@ interface DragHandleRowProps extends HTMLAttributes<HTMLDivElement> {
|
|
|
1313
2078
|
*/
|
|
1314
2079
|
declare function DragHandleRow({ children, handleProps, isDragging, className, ...rest }: DragHandleRowProps): react.JSX.Element;
|
|
1315
2080
|
|
|
2081
|
+
interface GripIconProps {
|
|
2082
|
+
size?: number;
|
|
2083
|
+
className?: string;
|
|
2084
|
+
}
|
|
2085
|
+
/** Six-dot drag-handle affordance. No baked-in pointer-events or color —
|
|
2086
|
+
* a consumer that needs `pointer-events: none` (e.g. a button wrapping it)
|
|
2087
|
+
* or a specific color passes those via `className`/`currentColor`. */
|
|
2088
|
+
declare function GripIcon({ size, className }: GripIconProps): react.JSX.Element;
|
|
2089
|
+
|
|
2090
|
+
interface StepStripItem {
|
|
2091
|
+
id: string;
|
|
2092
|
+
}
|
|
2093
|
+
interface StepStripProps<T extends StepStripItem> {
|
|
2094
|
+
items: T[];
|
|
2095
|
+
activeId: string | null;
|
|
2096
|
+
/** Full control over each chip's content, click handling, and (if needed) drag-reorder wiring. */
|
|
2097
|
+
renderItem: (item: T, state: {
|
|
2098
|
+
isActive: boolean;
|
|
2099
|
+
index: number;
|
|
2100
|
+
}) => ReactNode;
|
|
2101
|
+
/** Trailing dashed "add" chip — omit to render the strip with no add affordance. */
|
|
2102
|
+
onAdd?: () => void;
|
|
2103
|
+
addLabel?: string;
|
|
2104
|
+
className?: string;
|
|
2105
|
+
'aria-label'?: string;
|
|
2106
|
+
}
|
|
2107
|
+
/**
|
|
2108
|
+
* StepStrip — a horizontal, connected sequence of steps (a route or
|
|
2109
|
+
* itinerary strip): ordered chips joined by a thin connector line, with an
|
|
2110
|
+
* optional trailing dashed "add" chip. Purely presentational — it owns
|
|
2111
|
+
* layout and the connector lines, nothing else.
|
|
2112
|
+
*
|
|
2113
|
+
* Each chip's content, active styling, and click handling are entirely up
|
|
2114
|
+
* to `renderItem`; if a consumer needs drag-reorder, `renderItem` attaches
|
|
2115
|
+
* its own dnd-kit `useSortable` ref/listeners to whatever it returns. This
|
|
2116
|
+
* mirrors `DragHandleRow`'s existing split (DS owns chrome, consumer wires
|
|
2117
|
+
* dnd-kit via passed-through props) generalized for a horizontal,
|
|
2118
|
+
* handle-less strip where the whole rendered item — not a separate handle
|
|
2119
|
+
* icon — is both the click target and the drag surface.
|
|
2120
|
+
*/
|
|
2121
|
+
declare function StepStrip<T extends StepStripItem>({ items, activeId, renderItem, onAdd, addLabel, className, 'aria-label': ariaLabel, }: StepStripProps<T>): react.JSX.Element;
|
|
2122
|
+
|
|
1316
2123
|
interface FileDropzoneProps {
|
|
1317
2124
|
/** Native `<input type="file">` `name` attribute — set this to submit the
|
|
1318
2125
|
* file as part of a native `<form action={...}>` (Server Action) request. */
|
|
@@ -1461,6 +2268,63 @@ interface IconPickerProps {
|
|
|
1461
2268
|
*/
|
|
1462
2269
|
declare function IconPicker({ value, onChange, options, 'aria-label': ariaLabel, triggerLabel, disabled, className, }: IconPickerProps): react.JSX.Element;
|
|
1463
2270
|
|
|
2271
|
+
interface ColorPickerSwatch {
|
|
2272
|
+
/** Accessible name and tooltip, e.g. "Sky". */
|
|
2273
|
+
label: string;
|
|
2274
|
+
/** 6-digit hex, e.g. "#b5c9e8". */
|
|
2275
|
+
value: string;
|
|
2276
|
+
}
|
|
2277
|
+
interface ColorPickerProps {
|
|
2278
|
+
/** Curated quick-pick suggestions, in display order. */
|
|
2279
|
+
swatches: readonly ColorPickerSwatch[];
|
|
2280
|
+
/** Current hex value, or `null` for no color. Compared case-insensitively
|
|
2281
|
+
* against `swatches` — a value that doesn't match any swatch renders as
|
|
2282
|
+
* the active "custom" selection instead. */
|
|
2283
|
+
value: string | null;
|
|
2284
|
+
onChange: (value: string | null) => void;
|
|
2285
|
+
/**
|
|
2286
|
+
* Fires once after a discrete, complete pick — a swatch click or the "no
|
|
2287
|
+
* color" click. Optional; most callers don't need it. Deliberately NOT
|
|
2288
|
+
* fired from the native custom-color `<input type="color">`'s `onChange`
|
|
2289
|
+
* — that can fire repeatedly while the user is still dragging inside the
|
|
2290
|
+
* browser's own color dialog, so a caller using this to auto-close a
|
|
2291
|
+
* wrapping `Popover` would otherwise slam it shut mid-adjustment.
|
|
2292
|
+
*/
|
|
2293
|
+
onSelectionComplete?: () => void;
|
|
2294
|
+
/** Shows a leading "no color" option. Off by default — most consumers
|
|
2295
|
+
* (anything painting a fixed-size chip) always need a real color; only
|
|
2296
|
+
* a board canvas-style consumer typically wants "none" as a real choice. */
|
|
2297
|
+
allowNone?: boolean;
|
|
2298
|
+
noneLabel?: string;
|
|
2299
|
+
/** Accessible name for the native color-input trigger that lets a user
|
|
2300
|
+
* pick any color, not just a curated swatch. */
|
|
2301
|
+
customLabel?: string;
|
|
2302
|
+
disabled?: boolean;
|
|
2303
|
+
'aria-label': string;
|
|
2304
|
+
className?: string;
|
|
2305
|
+
}
|
|
2306
|
+
/**
|
|
2307
|
+
* ColorPicker — a row of curated swatch suggestions plus a native
|
|
2308
|
+
* `<input type="color">` trigger for picking any color, not just the
|
|
2309
|
+
* curated set. The native color input is deliberately not a custom-built
|
|
2310
|
+
* hue/saturation picker: it already gives every browser's own full-spectrum
|
|
2311
|
+
* picker (with an eyedropper tool in most), guarantees valid hex output, and
|
|
2312
|
+
* needs no bespoke a11y work of its own.
|
|
2313
|
+
*
|
|
2314
|
+
* `value` drives which affordance reads as selected: a hex matching a
|
|
2315
|
+
* swatch highlights that swatch; a hex matching none of them highlights the
|
|
2316
|
+
* custom trigger instead (its own dial shows that color); `null` (only
|
|
2317
|
+
* meaningful with `allowNone`) highlights the "no color" option.
|
|
2318
|
+
*
|
|
2319
|
+
* The custom trigger shows a plain "+" on a neutral dashed box until a
|
|
2320
|
+
* genuinely custom color is active — it never previews an arbitrary dial
|
|
2321
|
+
* color the user hasn't actually chosen. The underlying native color input
|
|
2322
|
+
* still needs *some* real hex to hold internally at all times (it has no
|
|
2323
|
+
* true empty state), so that starting value stays invisible (`opacity: 0`
|
|
2324
|
+
* on the input itself) rather than painted as this trigger's own swatch.
|
|
2325
|
+
*/
|
|
2326
|
+
declare function ColorPicker({ swatches, value, onChange, onSelectionComplete, allowNone, noneLabel, customLabel, disabled, 'aria-label': ariaLabel, className, }: ColorPickerProps): react.JSX.Element;
|
|
2327
|
+
|
|
1464
2328
|
interface QuantityStepperProps {
|
|
1465
2329
|
value: number;
|
|
1466
2330
|
onChange: (value: number) => void;
|
|
@@ -1711,6 +2575,57 @@ declare const MOBILE_BREAKPOINT_PX = 768;
|
|
|
1711
2575
|
*/
|
|
1712
2576
|
declare function useIsMobile(breakpointPx?: number): boolean;
|
|
1713
2577
|
|
|
2578
|
+
/**
|
|
2579
|
+
* useIsOffline — whether the browser currently has no network connection.
|
|
2580
|
+
*
|
|
2581
|
+
* SSR-safe: always initialises to `false` (online) so the server-rendered HTML
|
|
2582
|
+
* matches the first client render, then reads the real `navigator.onLine`
|
|
2583
|
+
* value in `useEffect`. This picks up the case where the page loaded already
|
|
2584
|
+
* offline (e.g. served from the service-worker cache) at the cost of an
|
|
2585
|
+
* imperceptible one-frame delay before the offline state appears — the same
|
|
2586
|
+
* trade-off `OfflineBanner` makes, which this hook was extracted from so every
|
|
2587
|
+
* consumer shares one connectivity source instead of re-deriving it slightly
|
|
2588
|
+
* differently (research 0012, epic task 2.32).
|
|
2589
|
+
*
|
|
2590
|
+
* Reflects only `navigator.onLine` — true whenever the OS reports a network
|
|
2591
|
+
* interface is up, even without real upstream connectivity (e.g. Wi-Fi with no
|
|
2592
|
+
* internet). That is a known limitation of the browser API itself, not of this
|
|
2593
|
+
* hook; there is currently no better cross-browser signal available.
|
|
2594
|
+
*/
|
|
2595
|
+
declare function useIsOffline(): boolean;
|
|
2596
|
+
|
|
2597
|
+
/**
|
|
2598
|
+
* The two reasons an app tile can be non-interactive, kept structurally
|
|
2599
|
+
* distinct because they have different causes and must never read as the same
|
|
2600
|
+
* thing to a user (research 0012, epic task 2.33):
|
|
2601
|
+
*
|
|
2602
|
+
* - `'connectivity-dimmed'` — a no-offline-tier app, dimmed only while the
|
|
2603
|
+
* device is **actually offline right now**. Reactive and temporary; clears
|
|
2604
|
+
* the instant connectivity returns.
|
|
2605
|
+
* - `'capability-restricted'` — a `device-only` app on a surface without the
|
|
2606
|
+
* durable, encrypted, device-auth-gated store it needs. Static and
|
|
2607
|
+
* unrelated to connectivity — it stays restricted even when fully online,
|
|
2608
|
+
* and must never say "offline" to a user who plainly isn't.
|
|
2609
|
+
*/
|
|
2610
|
+
type OfflineTileState = 'connectivity-dimmed' | 'capability-restricted' | null;
|
|
2611
|
+
/**
|
|
2612
|
+
* Which of the two states, if any, applies to an app tile given its declared
|
|
2613
|
+
* offline tier.
|
|
2614
|
+
*
|
|
2615
|
+
* `deviceOnlyAvailable` is the caller's own
|
|
2616
|
+
* `isDeviceOnlyTierAvailable()` result (from
|
|
2617
|
+
* `@sovereignfs/sdk/device-client`) — deliberately taken as a plain boolean
|
|
2618
|
+
* parameter rather than computed inside this hook, so `@sovereignfs/ui` never
|
|
2619
|
+
* depends on `@sovereignfs/sdk` (this package must stay usable standalone,
|
|
2620
|
+
* outside the plugin runtime — RFC 0073).
|
|
2621
|
+
*
|
|
2622
|
+
* An `offline-first` app is never restricted by either state: that tier's
|
|
2623
|
+
* whole point is working with no network, so it stays fully interactive both
|
|
2624
|
+
* online and offline. A `device-only` app that *is* available behaves the
|
|
2625
|
+
* same way — the restriction is about availability, not connectivity.
|
|
2626
|
+
*/
|
|
2627
|
+
declare function useOfflineTileState(offline: 'offline-first' | 'device-only' | undefined, deviceOnlyAvailable: boolean): OfflineTileState;
|
|
2628
|
+
|
|
1714
2629
|
interface CommitOnEnterOrBlurHandlers {
|
|
1715
2630
|
onKeyDown: (e: KeyboardEvent<HTMLInputElement | HTMLTextAreaElement>) => void;
|
|
1716
2631
|
onBlur: () => void;
|
|
@@ -1739,4 +2654,739 @@ interface CommitOnEnterOrBlurHandlers {
|
|
|
1739
2654
|
*/
|
|
1740
2655
|
declare function useCommitOnEnterOrBlur(onCommit: () => void): CommitOnEnterOrBlurHandlers;
|
|
1741
2656
|
|
|
1742
|
-
|
|
2657
|
+
interface UseSwipeRevealOptions {
|
|
2658
|
+
/** Max reveal distance in px — the row translates by `-revealWidth` when open. */
|
|
2659
|
+
revealWidth: number;
|
|
2660
|
+
/** Whether the reveal is currently open. Controlled by the caller so a
|
|
2661
|
+
* list of rows can coordinate "only one open at a time" — this hook has
|
|
2662
|
+
* no open state of its own. */
|
|
2663
|
+
open: boolean;
|
|
2664
|
+
/** Called when a drag releases past the halfway point — the caller should
|
|
2665
|
+
* set `open` to true. */
|
|
2666
|
+
onOpen: () => void;
|
|
2667
|
+
/** Called when a drag releases before the halfway point — the caller
|
|
2668
|
+
* should set `open` to false. */
|
|
2669
|
+
onClose: () => void;
|
|
2670
|
+
/** Skip entirely (hooks can't be called conditionally). */
|
|
2671
|
+
disabled?: boolean;
|
|
2672
|
+
}
|
|
2673
|
+
interface UseSwipeRevealHandlers {
|
|
2674
|
+
onPointerDown: (e: PointerEvent) => void;
|
|
2675
|
+
onPointerMove: (e: PointerEvent) => void;
|
|
2676
|
+
onPointerUp: (e: PointerEvent) => void;
|
|
2677
|
+
onPointerCancel: (e: PointerEvent) => void;
|
|
2678
|
+
}
|
|
2679
|
+
interface UseSwipeRevealResult {
|
|
2680
|
+
/** Attach to the swipeable row element — the hook writes a live transform
|
|
2681
|
+
* to it directly during the drag (not via React state) so the row tracks
|
|
2682
|
+
* the finger at 60fps with no re-render per pointermove. */
|
|
2683
|
+
rowRef: RefObject<HTMLDivElement | null>;
|
|
2684
|
+
/** Spread onto the row's pointer event handlers. */
|
|
2685
|
+
handlers: UseSwipeRevealHandlers;
|
|
2686
|
+
}
|
|
2687
|
+
/**
|
|
2688
|
+
* useSwipeReveal — horizontal swipe-to-reveal (e.g. Done/Delete actions
|
|
2689
|
+
* behind a list row), extracted from `sovereign-tasks`'s `TaskItem`/
|
|
2690
|
+
* `ListSidebar`, which independently hand-rolled the identical gesture twice
|
|
2691
|
+
* with only `revealWidth` differing (RFC 0079, epic task 9.20).
|
|
2692
|
+
*
|
|
2693
|
+
* Axis-locks on the first 8px of movement (so a vertical scroll or the
|
|
2694
|
+
* carousel's own horizontal swipe-between-slides gesture isn't hijacked by a
|
|
2695
|
+
* diagonal drag) and only resolves to open/closed on release, past the
|
|
2696
|
+
* halfway point of `revealWidth`.
|
|
2697
|
+
*/
|
|
2698
|
+
declare function useSwipeReveal({ revealWidth, open, onOpen, onClose, disabled, }: UseSwipeRevealOptions): UseSwipeRevealResult;
|
|
2699
|
+
|
|
2700
|
+
interface UseSnapCarouselOptions {
|
|
2701
|
+
/** Total number of slides — settle detection clamps to this range. */
|
|
2702
|
+
itemCount: number;
|
|
2703
|
+
/** Called when the user's scroll gesture settles on a different slide
|
|
2704
|
+
* index than the last one reported. */
|
|
2705
|
+
onSettle?: (index: number) => void;
|
|
2706
|
+
/** Debounce delay (ms) after the last `scroll` event before checking where
|
|
2707
|
+
* the container settled. Default 120 — matches the value this hook was
|
|
2708
|
+
* extracted from. Debounced rather than keyed to the `scrollend` event,
|
|
2709
|
+
* which pre-17.4 iOS Safari/WKWebView doesn't support. */
|
|
2710
|
+
debounceMs?: number;
|
|
2711
|
+
/** The index to seed `liveIndex` with before any scroll event has fired —
|
|
2712
|
+
* must match whatever the caller initially scrolls/renders to (typically
|
|
2713
|
+
* the same value used for `SwipableMobileCarousel`'s `activeIndex`).
|
|
2714
|
+
* Without this, `liveIndex` would start at 0 regardless of where the
|
|
2715
|
+
* carousel actually opens (e.g. a deep link straight to slide 8),
|
|
2716
|
+
* briefly unmounting every slide except the first few until the first
|
|
2717
|
+
* scroll event arrives to correct it. */
|
|
2718
|
+
initialIndex?: number;
|
|
2719
|
+
}
|
|
2720
|
+
interface UseSnapCarouselResult {
|
|
2721
|
+
/** Attach to the `scroll-snap-type: x` scroll container. */
|
|
2722
|
+
scrollRef: RefObject<HTMLDivElement | null>;
|
|
2723
|
+
/** Imperatively scrolls the container to a slide index. */
|
|
2724
|
+
scrollToIndex: (index: number, behavior?: ScrollBehavior) => void;
|
|
2725
|
+
/** The slide index nearest the container's *current* scroll position,
|
|
2726
|
+
* updated synchronously on every `scroll` event — not debounced, and not
|
|
2727
|
+
* the same thing as the last-reported `onSettle` index. See its own
|
|
2728
|
+
* assignment below for why a consumer needs both. */
|
|
2729
|
+
liveIndex: number;
|
|
2730
|
+
}
|
|
2731
|
+
/**
|
|
2732
|
+
* useSnapCarousel — debounced "settled slide" detection over a native
|
|
2733
|
+
* `scroll-snap-type: x` container, extracted from `sovereign-tasks`'s
|
|
2734
|
+
* `MobileTasksCarousel` and independently reimplemented in
|
|
2735
|
+
* `sovereign-shopper`'s `MobileShopperCarousel` (RFC 0079, epic task 9.20).
|
|
2736
|
+
* Gives swipe-between-slides physics for free via native scroll-snap, with
|
|
2737
|
+
* this hook only handling "which slide did we land on."
|
|
2738
|
+
*/
|
|
2739
|
+
declare function useSnapCarousel({ itemCount, onSettle, debounceMs, initialIndex, }: UseSnapCarouselOptions): UseSnapCarouselResult;
|
|
2740
|
+
|
|
2741
|
+
interface UseResponsiveLayoutOptions<TWeb, TMobile> {
|
|
2742
|
+
web: TWeb;
|
|
2743
|
+
mobile: TMobile;
|
|
2744
|
+
breakpointPx?: number;
|
|
2745
|
+
}
|
|
2746
|
+
interface UseResponsiveLayoutResult<TWeb, TMobile> {
|
|
2747
|
+
isMobile: boolean;
|
|
2748
|
+
value: TWeb | TMobile;
|
|
2749
|
+
}
|
|
2750
|
+
/**
|
|
2751
|
+
* useResponsiveLayout — picks between a `web` and `mobile` value for the
|
|
2752
|
+
* current viewport, built on `useIsMobile` (same breakpoint/SSR-safe-default
|
|
2753
|
+
* behavior, not reimplemented here). Generic over the value type so it works
|
|
2754
|
+
* for JSX (the common case — forking an entire component tree, not just a
|
|
2755
|
+
* class name) or any other per-breakpoint value a plugin wants to pick
|
|
2756
|
+
* without repeating the isMobile-ternary boilerplate every call site.
|
|
2757
|
+
*
|
|
2758
|
+
* This formalizes the `if (isMobile) return <mobile/>; return <web/>;` fork
|
|
2759
|
+
* plugins have hand-rolled per-plugin (e.g. a `MobileAwareShell` component
|
|
2760
|
+
* that swaps an entire routed page tree for a client-only mobile one) —
|
|
2761
|
+
* see `ResponsiveSurface` for the equivalent as a plain JSX component.
|
|
2762
|
+
*/
|
|
2763
|
+
declare function useResponsiveLayout<TWeb, TMobile>({ web, mobile, breakpointPx, }: UseResponsiveLayoutOptions<TWeb, TMobile>): UseResponsiveLayoutResult<TWeb, TMobile>;
|
|
2764
|
+
|
|
2765
|
+
interface UseCarouselRouteSyncOptions {
|
|
2766
|
+
/** Maps the current pathname to a slide index — the caller's own routing
|
|
2767
|
+
* logic (e.g. special-casing a virtual "starred" slide), unchanged from
|
|
2768
|
+
* whatever a plugin already has today. This hook has no idea what a
|
|
2769
|
+
* plugin's routes look like, by design — it never imports a router. */
|
|
2770
|
+
indexForPathname: (pathname: string) => number;
|
|
2771
|
+
/** Inverse mapping — given a settled slide index, the path to navigate to. */
|
|
2772
|
+
pathForIndex: (index: number) => string;
|
|
2773
|
+
/** The current pathname. The caller reads this via their own router (e.g.
|
|
2774
|
+
* `usePathname()` from `next/navigation`) and passes it in every render —
|
|
2775
|
+
* this package stays framework-generic and has zero runtime dependencies. */
|
|
2776
|
+
pathname: string;
|
|
2777
|
+
/** Called when this hook wants to navigate — typically
|
|
2778
|
+
* `(path) => router.replace(path, { scroll: false })`. */
|
|
2779
|
+
onNavigate: (path: string) => void;
|
|
2780
|
+
}
|
|
2781
|
+
interface UseCarouselRouteSyncResult {
|
|
2782
|
+
/** Feed directly into SwipableMobileCarousel's `activeIndex` prop. */
|
|
2783
|
+
activeIndex: number;
|
|
2784
|
+
/** Feed directly into SwipableMobileCarousel's `onSettle` prop. */
|
|
2785
|
+
onSettle: (index: number) => void;
|
|
2786
|
+
}
|
|
2787
|
+
/**
|
|
2788
|
+
* useCarouselRouteSync — centralizes the pathname↔slide-index mapping and the
|
|
2789
|
+
* "was this pathname change our own settle, or an external navigation"
|
|
2790
|
+
* distinction that carousel-driving plugins otherwise hand-roll per plugin
|
|
2791
|
+
* (a tapped `<Link>`, browser back/forward, vs. this hook's own
|
|
2792
|
+
* `router.replace` after a swipe settles). Router-agnostic on purpose: the
|
|
2793
|
+
* caller owns the actual `usePathname()`/`router.replace()` calls and only
|
|
2794
|
+
* hands this hook plain values/callbacks.
|
|
2795
|
+
*
|
|
2796
|
+
* Deliberately does not own neighbor-prefetch (SwipableMobileCarousel's
|
|
2797
|
+
* `prefetchDistance`/mount-window covers that) or scroll mechanics (the
|
|
2798
|
+
* carousel component reacts to `activeIndex` changes itself) — this hook is
|
|
2799
|
+
* pathname↔index mapping only, keeping routing glue decoupled from
|
|
2800
|
+
* data-fetching and DOM scrolling.
|
|
2801
|
+
*/
|
|
2802
|
+
declare function useCarouselRouteSync({ indexForPathname, pathForIndex, pathname, onNavigate, }: UseCarouselRouteSyncOptions): UseCarouselRouteSyncResult;
|
|
2803
|
+
|
|
2804
|
+
/**
|
|
2805
|
+
* True when a drag should be allowed to start from `target`. Refused when
|
|
2806
|
+
* `target` sits inside an element marked `data-no-dnd` — a nested control
|
|
2807
|
+
* (e.g. a visibility toggle) opts out so tapping it fires its own handler
|
|
2808
|
+
* instead of lifting the row. Exported standalone so it's unit-testable
|
|
2809
|
+
* without spinning up dnd-kit.
|
|
2810
|
+
*/
|
|
2811
|
+
declare function shouldHandleDndEvent(target: EventTarget | null): boolean;
|
|
2812
|
+
interface UseReorderSensorsOptions {
|
|
2813
|
+
mouseActivationDistancePx?: number;
|
|
2814
|
+
touchActivationDelayMs?: number;
|
|
2815
|
+
touchActivationTolerancePx?: number;
|
|
2816
|
+
}
|
|
2817
|
+
/**
|
|
2818
|
+
* Sensor set for a `data-no-dnd`-aware drag-reorder list — MouseSensor for
|
|
2819
|
+
* desktop's handle-initiated drag, TouchSensor for mobile's long-press lift
|
|
2820
|
+
* (fixes reordering being unusable on iOS PWA/Safari, which never implements
|
|
2821
|
+
* the native HTML5 Drag-and-Drop API for touch input), KeyboardSensor
|
|
2822
|
+
* unchanged. A bare call keeps the historical 8px/300ms/8px defaults.
|
|
2823
|
+
*/
|
|
2824
|
+
declare function useReorderSensors(options?: UseReorderSensorsOptions): _dnd_kit_core.SensorDescriptor<_dnd_kit_core.SensorOptions>[];
|
|
2825
|
+
|
|
2826
|
+
type SwipeDirection = 'left' | 'right' | 'up' | 'down';
|
|
2827
|
+
/** Duration (ms) of both the commit fling-out and the spring-back-under-
|
|
2828
|
+
* threshold transition. Exported so SwipeStack can hold a just-committed
|
|
2829
|
+
* card in place for exactly this long before advancing to the next one. */
|
|
2830
|
+
declare const SWIPE_STACK_TRANSITION_MS = 220;
|
|
2831
|
+
interface UseSwipeStackOptions {
|
|
2832
|
+
/** Directions this drag can move toward and resolve to. A direction left
|
|
2833
|
+
* out of this list is a wall, not just an unreachable outcome — the card
|
|
2834
|
+
* will not visually move that way at all (see clampToLiveDirections). */
|
|
2835
|
+
directions: readonly SwipeDirection[];
|
|
2836
|
+
/** Effective distance (px) a drag must cross before release commits to
|
|
2837
|
+
* that direction instead of springing back. No velocity component — a
|
|
2838
|
+
* fast short flick under this distance still springs back. */
|
|
2839
|
+
threshold?: number;
|
|
2840
|
+
/** Fires once, synchronously, when a drag (or triggerCommit) resolves to
|
|
2841
|
+
* a live direction past threshold. This hook has already started the
|
|
2842
|
+
* fling-out animation on cardRef's element by the time this is called —
|
|
2843
|
+
* the caller only needs to react to the outcome, not drive the motion. */
|
|
2844
|
+
onCommit: (direction: SwipeDirection) => void;
|
|
2845
|
+
/** Disables both the drag and triggerCommit — e.g. while there is no
|
|
2846
|
+
* current card to act on. */
|
|
2847
|
+
disabled?: boolean;
|
|
2848
|
+
}
|
|
2849
|
+
interface UseSwipeStackHandlers {
|
|
2850
|
+
onPointerDown: (e: PointerEvent) => void;
|
|
2851
|
+
onPointerMove: (e: PointerEvent) => void;
|
|
2852
|
+
onPointerUp: (e: PointerEvent) => void;
|
|
2853
|
+
onPointerCancel: (e: PointerEvent) => void;
|
|
2854
|
+
}
|
|
2855
|
+
interface UseSwipeStackResult {
|
|
2856
|
+
/** Attach to the card element that should translate/rotate with the drag. */
|
|
2857
|
+
cardRef: RefObject<HTMLDivElement | null>;
|
|
2858
|
+
/** Attach to the ancestor of both the card and any direction-stamp
|
|
2859
|
+
* overlays. The hook writes live drag progress here as CSS custom
|
|
2860
|
+
* properties (--sv-stack-progress-left/right/up/down, each 0–1) so
|
|
2861
|
+
* stamps — siblings of the card, not the card itself — can react without
|
|
2862
|
+
* a re-render per pointermove. */
|
|
2863
|
+
wrapRef: RefObject<HTMLDivElement | null>;
|
|
2864
|
+
handlers: UseSwipeStackHandlers;
|
|
2865
|
+
/** CSS touch-action for the card, derived from which directions are live
|
|
2866
|
+
* — an axis with nothing configured on it stays native (pan-x/pan-y) so
|
|
2867
|
+
* a page can still scroll through a stack that only uses the other axis;
|
|
2868
|
+
* `none` only once both axes are actually needed. */
|
|
2869
|
+
touchAction: string;
|
|
2870
|
+
/** Programmatically commits a direction — the same fling animation and
|
|
2871
|
+
* onCommit call a real drag past threshold would produce. Backs the
|
|
2872
|
+
* non-gesture fallback buttons: they are not a separate code path, just
|
|
2873
|
+
* a different way to call this. No-ops if the direction isn't live or
|
|
2874
|
+
* the hook is disabled. */
|
|
2875
|
+
triggerCommit: (direction: SwipeDirection) => void;
|
|
2876
|
+
}
|
|
2877
|
+
/**
|
|
2878
|
+
* useSwipeStack — hand-rolled 4-directional free drag, extending
|
|
2879
|
+
* useSwipeReveal's pattern (raw Pointer Events, a deadzone, transform
|
|
2880
|
+
* written straight to a ref, resolved only on release) from one axis with
|
|
2881
|
+
* two outcomes to four directions with a tilt. Threshold-only on purpose,
|
|
2882
|
+
* matching useSwipeReveal — no velocity tracking, so a fast short flick
|
|
2883
|
+
* under `threshold` still springs back.
|
|
2884
|
+
*/
|
|
2885
|
+
declare function useSwipeStack({ directions, threshold, onCommit, disabled, }: UseSwipeStackOptions): UseSwipeStackResult;
|
|
2886
|
+
|
|
2887
|
+
interface ResponsiveSurfaceProps {
|
|
2888
|
+
web: ReactNode;
|
|
2889
|
+
mobile: ReactNode;
|
|
2890
|
+
breakpointPx?: number;
|
|
2891
|
+
}
|
|
2892
|
+
/**
|
|
2893
|
+
* ResponsiveSurface — thin JSX convenience wrapper around
|
|
2894
|
+
* `useResponsiveLayout` for the common two-tree case: render an entirely
|
|
2895
|
+
* different component tree below the breakpoint, not a CSS squeeze of the
|
|
2896
|
+
* same one. Renders only whichever side is active — the other tree is never
|
|
2897
|
+
* mounted.
|
|
2898
|
+
*
|
|
2899
|
+
* This does not replace a plugin's own shell component (e.g. a
|
|
2900
|
+
* `MobileAwareShell` that also threads plugin-specific props like a refresh
|
|
2901
|
+
* signal) — those stay a thin per-plugin wrapper around this.
|
|
2902
|
+
*/
|
|
2903
|
+
declare function ResponsiveSurface({ web, mobile, breakpointPx }: ResponsiveSurfaceProps): react.JSX.Element;
|
|
2904
|
+
|
|
2905
|
+
interface SwipableMobileCarouselDotsProps {
|
|
2906
|
+
count: number;
|
|
2907
|
+
activeIndex: number;
|
|
2908
|
+
onJump: (index: number) => void;
|
|
2909
|
+
/** Per-dot accessible names, e.g. ["Lists", "Starred", "Groceries"]. Falls
|
|
2910
|
+
* back to "Slide N of count" for any index without one. */
|
|
2911
|
+
labels?: (string | undefined)[];
|
|
2912
|
+
/** Labels the whole group — required, no generic fallback (mirrors
|
|
2913
|
+
* SwipableMobileCarousel's own required aria-label). */
|
|
2914
|
+
'aria-label': string;
|
|
2915
|
+
/** `'compact'` halves the gap between dots (`--sv-space-2` → `--sv-space-1`)
|
|
2916
|
+
* and shrinks each dot's own hit target (20px → 16px) — for a carousel
|
|
2917
|
+
* with enough slides that even the reduced gap alone still reads as too
|
|
2918
|
+
* wide on a narrow viewport (e.g. a many-list mobile app). The visible
|
|
2919
|
+
* 6px dot itself is unchanged; only its surrounding tap-target box
|
|
2920
|
+
* shrinks. Defaults to `'default'` — every existing consumer's spacing is
|
|
2921
|
+
* unaffected unless it opts in. */
|
|
2922
|
+
density?: 'default' | 'compact';
|
|
2923
|
+
className?: string;
|
|
2924
|
+
}
|
|
2925
|
+
/**
|
|
2926
|
+
* SwipableMobileCarouselDots — a real, tappable, labeled slide indicator.
|
|
2927
|
+
* Standalone (not carousel-only): reusable anywhere a set of positions needs
|
|
2928
|
+
* a dot indicator (e.g. an image gallery, an onboarding stepper), and is also
|
|
2929
|
+
* SwipableMobileCarousel's default `renderIndicator`.
|
|
2930
|
+
*
|
|
2931
|
+
* Replaces the `aria-hidden`, non-interactive dots pattern both
|
|
2932
|
+
* sovereign-tasks and sovereign-shopper currently hand-roll — every dot here
|
|
2933
|
+
* is a real `role="tab"` button with its own accessible name, reachable by
|
|
2934
|
+
* keyboard. No roving tabIndex/arrow-key handling: `Tabs` (this library's
|
|
2935
|
+
* other `role="tablist"` component) has none either, so this stays
|
|
2936
|
+
* consistent with it rather than gold-plating past its sibling.
|
|
2937
|
+
*/
|
|
2938
|
+
declare function SwipableMobileCarouselDots({ count, activeIndex, onJump, labels, 'aria-label': ariaLabel, density, className, }: SwipableMobileCarouselDotsProps): react.JSX.Element;
|
|
2939
|
+
|
|
2940
|
+
interface SwipableMobileCarouselIndicatorProps {
|
|
2941
|
+
count: number;
|
|
2942
|
+
activeIndex: number;
|
|
2943
|
+
labels: (string | undefined)[];
|
|
2944
|
+
onJump: (index: number) => void;
|
|
2945
|
+
}
|
|
2946
|
+
interface SwipableMobileCarouselProps {
|
|
2947
|
+
/** Controlled active slide index — the caller owns this (typically via
|
|
2948
|
+
* useCarouselRouteSync, or plain useState). */
|
|
2949
|
+
activeIndex: number;
|
|
2950
|
+
/** Called when a swipe gesture settles on a different index, or when the
|
|
2951
|
+
* reorder-jump fix re-snaps after the active slide's position changes.
|
|
2952
|
+
* The caller is responsible for updating `activeIndex` in response. */
|
|
2953
|
+
onSettle: (index: number) => void;
|
|
2954
|
+
/** How many slides on each side of the active one stay mounted. Default 1
|
|
2955
|
+
* matches both sovereign-tasks' and sovereign-shopper's existing "swipe
|
|
2956
|
+
* never shows a spinner" behavior. Slides outside this window keep their
|
|
2957
|
+
* DOM slot (so scroll-snap's position-based indexing stays correct) but
|
|
2958
|
+
* their children are not mounted at all. */
|
|
2959
|
+
prefetchDistance?: number;
|
|
2960
|
+
/** Passed straight through to the internal useSnapCarousel. */
|
|
2961
|
+
settleDebounceMs?: number;
|
|
2962
|
+
/** Required — labels the scroller region for assistive tech. No generic
|
|
2963
|
+
* fallback, since one would be wrong for every actual consumer. */
|
|
2964
|
+
'aria-label': string;
|
|
2965
|
+
/** Renders the indicator. Defaults to SwipableMobileCarouselDots. Pass
|
|
2966
|
+
* `null` to render no indicator at all. */
|
|
2967
|
+
renderIndicator?: null | ((props: SwipableMobileCarouselIndicatorProps) => ReactNode);
|
|
2968
|
+
className?: string;
|
|
2969
|
+
/** Must be SwipableMobileCarouselSlide elements (nullish/boolean children
|
|
2970
|
+
* are safely skipped for conditional slides). A dev-mode warning is
|
|
2971
|
+
* logged for any other child type — see resolveSlides below. */
|
|
2972
|
+
children: ReactNode;
|
|
2973
|
+
}
|
|
2974
|
+
/**
|
|
2975
|
+
* SwipableMobileCarousel — a compound component for swiping between full-
|
|
2976
|
+
* width slides (each an independent route/view on mobile), wrapping
|
|
2977
|
+
* useSnapCarousel. This library's first compound component: every other
|
|
2978
|
+
* component here is flat/prop-based, but slide content is large, arbitrary,
|
|
2979
|
+
* per-plugin subtrees that need a header-known/body-loading split an
|
|
2980
|
+
* array-of-objects prop would only express with worse JSX ergonomics.
|
|
2981
|
+
*
|
|
2982
|
+
* Owns rendering and mount-window mechanics only — it has no opinion on
|
|
2983
|
+
* where slide data lives. Do NOT: (a) aggregate cross-slide data inside a
|
|
2984
|
+
* SwipableMobileCarouselSlideBody (that recomputes on every slide render
|
|
2985
|
+
* regardless of which slide is active — belongs in the parent, which
|
|
2986
|
+
* already knows every slide exists); (b) mount a detail overlay (Sheet,
|
|
2987
|
+
* Dialog) inside a Slide's children — mount it as a sibling of
|
|
2988
|
+
* SwipableMobileCarousel instead, controlled by the same state that would
|
|
2989
|
+
* otherwise drive a routed page's overlay. sovereign-tasks' current
|
|
2990
|
+
* hand-rolled carousel does both and is measurably laggier than
|
|
2991
|
+
* sovereign-shopper's equivalent, which keeps its overlay in the routed pane.
|
|
2992
|
+
*/
|
|
2993
|
+
declare function SwipableMobileCarousel({ activeIndex, onSettle, prefetchDistance, settleDebounceMs, 'aria-label': ariaLabel, renderIndicator, className, children, }: SwipableMobileCarouselProps): react.JSX.Element;
|
|
2994
|
+
|
|
2995
|
+
interface SwipableMobileCarouselSlideProps {
|
|
2996
|
+
/** Stable identity for this slide, independent of its position among
|
|
2997
|
+
* siblings — required. This is what makes SwipableMobileCarousel's
|
|
2998
|
+
* reorder-jump fix possible: a plain array index can't distinguish "this
|
|
2999
|
+
* slide moved to position 2" from "this slide was removed and a
|
|
3000
|
+
* different one is now at position 2." Reuse whatever id the caller
|
|
3001
|
+
* already keys their `.map()` on (e.g. a list's own id). */
|
|
3002
|
+
slideKey: string;
|
|
3003
|
+
/** Accessible label surfaced to the default dots indicator (e.g.
|
|
3004
|
+
* "Groceries"). Falls back to "Slide N of count" if omitted — omitting it
|
|
3005
|
+
* logs a dev-mode warning, since every real slide usually has a title
|
|
3006
|
+
* available synchronously already. */
|
|
3007
|
+
label?: string;
|
|
3008
|
+
/** SwipableMobileCarouselSlideHeader/Body/Footer, in any order/subset. */
|
|
3009
|
+
children: ReactNode;
|
|
3010
|
+
}
|
|
3011
|
+
/**
|
|
3012
|
+
* SwipableMobileCarouselSlide — a single slide of a SwipableMobileCarousel.
|
|
3013
|
+
* Renders its children only while within the carousel's mount window (the
|
|
3014
|
+
* active slide ± its configured prefetchDistance); outside that window it
|
|
3015
|
+
* renders nothing, matching both sovereign-tasks and sovereign-shopper's
|
|
3016
|
+
* existing "never fetch/render a non-neighbor slide" behavior, now
|
|
3017
|
+
* centralized instead of hand-rolled per plugin.
|
|
3018
|
+
*/
|
|
3019
|
+
declare function SwipableMobileCarouselSlide({ children }: SwipableMobileCarouselSlideProps): react.JSX.Element | null;
|
|
3020
|
+
|
|
3021
|
+
interface SwipableMobileCarouselSlideHeaderProps {
|
|
3022
|
+
children: ReactNode;
|
|
3023
|
+
className?: string;
|
|
3024
|
+
}
|
|
3025
|
+
/**
|
|
3026
|
+
* SwipableMobileCarouselSlideHeader — always renders its children, no
|
|
3027
|
+
* loading concept. Meant for content already known synchronously (a list's
|
|
3028
|
+
* title, color, item count from data the caller already has) so it can
|
|
3029
|
+
* render immediately even while SwipableMobileCarouselSlideBody's own
|
|
3030
|
+
* content is still loading — see that component's doc comment.
|
|
3031
|
+
*/
|
|
3032
|
+
declare function SwipableMobileCarouselSlideHeader({ children, className, }: SwipableMobileCarouselSlideHeaderProps): react.JSX.Element;
|
|
3033
|
+
|
|
3034
|
+
interface SwipableMobileCarouselSlideBodyProps {
|
|
3035
|
+
children: ReactNode;
|
|
3036
|
+
/** While true, renders `loadingFallback` INSTEAD OF children — scoped to
|
|
3037
|
+
* just this region, not the whole slide. This is what fixes the
|
|
3038
|
+
* "whole slide blanks out, title included, until its own fetch resolves"
|
|
3039
|
+
* bug: a caller renders `<SwipableMobileCarouselSlideHeader>` from
|
|
3040
|
+
* already-known metadata unconditionally, and gates only this component's
|
|
3041
|
+
* `loading` on the slower fetch — the natural way to compose
|
|
3042
|
+
* Header/Body/Footer as siblings already produces that behavior, instead
|
|
3043
|
+
* of gating the entire slide behind one boolean. */
|
|
3044
|
+
loading?: boolean;
|
|
3045
|
+
loadingFallback?: ReactNode;
|
|
3046
|
+
className?: string;
|
|
3047
|
+
}
|
|
3048
|
+
/**
|
|
3049
|
+
* SwipableMobileCarouselSlideBody — the scrollable region of a slide.
|
|
3050
|
+
*
|
|
3051
|
+
* Deliberately does not set `touch-action: pan-y` on its scroll container
|
|
3052
|
+
* (see SwipableMobileCarouselSlideParts.module.css's `.body` rule) — that
|
|
3053
|
+
* would intersect-to-empty against the carousel's own horizontal
|
|
3054
|
+
* touch-action handling (docs/architecture-rules.md's touch-action
|
|
3055
|
+
* intersection rule), breaking both the vertical scroll here and the
|
|
3056
|
+
* horizontal swipe between slides.
|
|
3057
|
+
*/
|
|
3058
|
+
declare function SwipableMobileCarouselSlideBody({ children, loading, loadingFallback, className, }: SwipableMobileCarouselSlideBodyProps): react.JSX.Element;
|
|
3059
|
+
|
|
3060
|
+
interface SwipableMobileCarouselSlideFooterProps {
|
|
3061
|
+
children: ReactNode;
|
|
3062
|
+
className?: string;
|
|
3063
|
+
}
|
|
3064
|
+
/**
|
|
3065
|
+
* SwipableMobileCarouselSlideFooter — always renders its children, no
|
|
3066
|
+
* loading concept. See SwipableMobileCarouselSlideHeader's doc comment.
|
|
3067
|
+
*/
|
|
3068
|
+
declare function SwipableMobileCarouselSlideFooter({ children, className, }: SwipableMobileCarouselSlideFooterProps): react.JSX.Element;
|
|
3069
|
+
|
|
3070
|
+
interface SwipeDirectionMeta {
|
|
3071
|
+
/** Visible text on the fallback button and the in-drag stamp. */
|
|
3072
|
+
label: string;
|
|
3073
|
+
icon?: IconName;
|
|
3074
|
+
}
|
|
3075
|
+
interface SwipeStackProps {
|
|
3076
|
+
/** Required — labels the swipeable region for assistive tech. No generic
|
|
3077
|
+
* fallback, since one would be wrong for every actual consumer. */
|
|
3078
|
+
'aria-label': string;
|
|
3079
|
+
/** Which of the 4 directions are live, and what each is called. A
|
|
3080
|
+
* direction absent here cannot be dragged toward at all (useSwipeStack
|
|
3081
|
+
* treats it as a wall) and gets no fallback button or stamp. SwipeStack
|
|
3082
|
+
* itself has no opinion on what a direction *means* — that vocabulary
|
|
3083
|
+
* belongs entirely to the caller. */
|
|
3084
|
+
directions: Partial<Record<SwipeDirection, SwipeDirectionMeta>>;
|
|
3085
|
+
/** Fires once a drag (or a fallback button) commits to a direction. Every
|
|
3086
|
+
* configured direction behaves the same way: the card leaves for good —
|
|
3087
|
+
* there is no reversible "go back" state. SwipeStack has no opinion on
|
|
3088
|
+
* where card data lives; the caller is responsible for eventually no
|
|
3089
|
+
* longer including this cardId in children (e.g. once a mutation lands). */
|
|
3090
|
+
onSwipe: (direction: SwipeDirection, cardId: string) => void;
|
|
3091
|
+
/** Effective drag distance (px) required to commit. Passed straight
|
|
3092
|
+
* through to useSwipeStack. */
|
|
3093
|
+
threshold?: number;
|
|
3094
|
+
className?: string;
|
|
3095
|
+
/** Must be SwipeStackCard elements (nullish/boolean children are safely
|
|
3096
|
+
* skipped for conditional cards). A dev-mode warning is logged for any
|
|
3097
|
+
* other child type — see resolveCards below. */
|
|
3098
|
+
children: ReactNode;
|
|
3099
|
+
}
|
|
3100
|
+
/**
|
|
3101
|
+
* SwipeStack — a compound component for triaging one card at a time by
|
|
3102
|
+
* dragging it left/right/up/down (wrapping useSwipeStack), plus an
|
|
3103
|
+
* always-visible non-gesture fallback: a button per configured direction,
|
|
3104
|
+
* wired to the identical commit path and exit animation as a real drag.
|
|
3105
|
+
*
|
|
3106
|
+
* Every direction is independently optional and behaves the same way once
|
|
3107
|
+
* triggered — the card leaves for good, there is no reversible "go back".
|
|
3108
|
+
* SwipeStack owns rendering, drag physics, and the stamp/fallback UI only;
|
|
3109
|
+
* it has no opinion on where card data lives or what a direction means —
|
|
3110
|
+
* see SwipeStackProps.onSwipe.
|
|
3111
|
+
*/
|
|
3112
|
+
declare function SwipeStack({ 'aria-label': ariaLabel, directions, onSwipe, threshold, className, children, }: SwipeStackProps): react.JSX.Element;
|
|
3113
|
+
|
|
3114
|
+
interface SwipeStackCardProps {
|
|
3115
|
+
/** Stable identity for this card, independent of its position among
|
|
3116
|
+
* siblings — required. This is what SwipeStack reads (via child.props,
|
|
3117
|
+
* not React's own `key`, which a component can't see on itself) to know
|
|
3118
|
+
* which card is on top and what id to report through onSwipe. Reuse
|
|
3119
|
+
* whatever id the caller already keys their `.map()` on. */
|
|
3120
|
+
cardId: string;
|
|
3121
|
+
children: ReactNode;
|
|
3122
|
+
}
|
|
3123
|
+
/**
|
|
3124
|
+
* SwipeStackCard — a single card of a SwipeStack. Unlike
|
|
3125
|
+
* SwipableMobileCarouselSlide, this has no mount-window logic of its own:
|
|
3126
|
+
* SwipeStack only ever renders the top two cards' elements at all (there is
|
|
3127
|
+
* no scroll-position reason to keep every sibling in the DOM the way the
|
|
3128
|
+
* carousel's scroll-snap track does), so there is nothing here to gate.
|
|
3129
|
+
*/
|
|
3130
|
+
declare function SwipeStackCard({ children }: SwipeStackCardProps): react.JSX.Element;
|
|
3131
|
+
|
|
3132
|
+
interface ThreeColumnLayoutProps {
|
|
3133
|
+
/**
|
|
3134
|
+
* Exactly 2 (sidebar, main) or 3 (sidebar, main, detail) elements. Omit
|
|
3135
|
+
* the third child entirely to render two columns — e.g.
|
|
3136
|
+
* `{selected && <Detail />}` — React.Children.toArray drops falsy
|
|
3137
|
+
* children, so no separate `detail` prop is needed to make that
|
|
3138
|
+
* conditional.
|
|
3139
|
+
*/
|
|
3140
|
+
children: ReactNode;
|
|
3141
|
+
/** px. Fixed width of the first (leftmost) column. Default 280. */
|
|
3142
|
+
sidebarWidth?: number;
|
|
3143
|
+
/** px. Fixed width of the third column, when present. Default 360. */
|
|
3144
|
+
detailWidth?: number;
|
|
3145
|
+
/**
|
|
3146
|
+
* Hides the sidebar column entirely — no width, no border, no layout box
|
|
3147
|
+
* — while keeping it mounted and every sibling in its own stable position.
|
|
3148
|
+
*
|
|
3149
|
+
* Prefer this over conditionally omitting the sidebar child, or swapping
|
|
3150
|
+
* to a different wrapper when collapsed. Omitting it shifts `main` into
|
|
3151
|
+
* the sidebar slot, and swapping the surrounding element type unmounts
|
|
3152
|
+
* `main`'s entire subtree — silently discarding its React state (a live
|
|
3153
|
+
* stream, unsent composer text, an in-memory-only conversation) on every
|
|
3154
|
+
* collapse toggle. Found live in Warden, whose chat column lost all of
|
|
3155
|
+
* that each time the sidebar was shown or hidden.
|
|
3156
|
+
*/
|
|
3157
|
+
sidebarHidden?: boolean;
|
|
3158
|
+
className?: string;
|
|
3159
|
+
}
|
|
3160
|
+
/**
|
|
3161
|
+
* ThreeColumnLayout — sidebar + main + optional detail column, for the
|
|
3162
|
+
* common "list app" shape (a list sidebar, a primary content list, and a
|
|
3163
|
+
* detail pane that only takes up space once something is selected).
|
|
3164
|
+
*
|
|
3165
|
+
* Purely positional and structural — it has no awareness of what's inside
|
|
3166
|
+
* each slot (no injected landmarks/labels; those belong to the children
|
|
3167
|
+
* themselves) and no responsive behavior of its own. Collapsing further at
|
|
3168
|
+
* narrow desktop/tablet widths (e.g. swapping the detail column for an
|
|
3169
|
+
* overlay) is the consuming plugin's decision; below the mobile breakpoint,
|
|
3170
|
+
* compose with ResponsiveSurface to fork to a different tree entirely.
|
|
3171
|
+
*/
|
|
3172
|
+
declare function ThreeColumnLayout({ children, sidebarWidth, detailWidth, sidebarHidden, className, }: ThreeColumnLayoutProps): react.JSX.Element;
|
|
3173
|
+
|
|
3174
|
+
interface HeaderFooterLayoutProps {
|
|
3175
|
+
/** Main content — required, always fills whatever height header/footer don't claim. */
|
|
3176
|
+
children: ReactNode;
|
|
3177
|
+
header?: ReactNode;
|
|
3178
|
+
footer?: ReactNode;
|
|
3179
|
+
/** px. Fixed height of the header, when present. Default 60 (matches
|
|
3180
|
+
* MobileHeader/--sv-shell-header-height's own default). */
|
|
3181
|
+
headerHeight?: number;
|
|
3182
|
+
/** px. Fixed height of the footer, when present. Default 60 (matches
|
|
3183
|
+
* MobileFooter/--sv-shell-footer-height's own default). */
|
|
3184
|
+
footerHeight?: number;
|
|
3185
|
+
className?: string;
|
|
3186
|
+
}
|
|
3187
|
+
/**
|
|
3188
|
+
* HeaderFooterLayout — header + main + optional footer, both fixed-height
|
|
3189
|
+
* and independently optional, with main always claiming the remaining
|
|
3190
|
+
* height. The vertical counterpart to ThreeColumnLayout: same purely
|
|
3191
|
+
* structural approach (no color/background opinions, no awareness of what's
|
|
3192
|
+
* inside each slot, no responsive behavior of its own — a plain flex
|
|
3193
|
+
* column), just rotated. "Footer always at the bottom" falls out of flex
|
|
3194
|
+
* layout alone; there is no position: fixed or safe-area handling here, so
|
|
3195
|
+
* a caller composing this with the platform shell's own chrome still owns
|
|
3196
|
+
* that separately.
|
|
3197
|
+
*/
|
|
3198
|
+
declare function HeaderFooterLayout({ children, header, footer, headerHeight, footerHeight, className, }: HeaderFooterLayoutProps): react.JSX.Element;
|
|
3199
|
+
|
|
3200
|
+
type RootLayoutVariant = 'plain' | 'sidebar' | 'header' | 'shell';
|
|
3201
|
+
interface RootLayoutProps {
|
|
3202
|
+
/**
|
|
3203
|
+
* Which chrome shape this page uses. Children are positional and their
|
|
3204
|
+
* count is fixed per variant:
|
|
3205
|
+
* - `'plain'` — 1 child: `[main]`. No chrome on either breakpoint.
|
|
3206
|
+
* - `'sidebar'` — 2 children: `[sidebar, main]`. Sidebar + main on web
|
|
3207
|
+
* only — on mobile, sidebar is dropped and only main renders.
|
|
3208
|
+
* - `'header'` — 2 children: `[header, main]`. Header + main on both
|
|
3209
|
+
* breakpoints — same structure, but the header's own height differs
|
|
3210
|
+
* per breakpoint (`headerHeight` on web, `mobileHeaderHeight` on
|
|
3211
|
+
* mobile), matching how real `shell: minimal` plugins actually do
|
|
3212
|
+
* this (a compact web top bar vs. the platform's mobile chrome
|
|
3213
|
+
* height).
|
|
3214
|
+
* - `'shell'` — 3 children: `[header, main, footer]`. Header + main +
|
|
3215
|
+
* footer on mobile only — on web, header/footer are dropped and only
|
|
3216
|
+
* main renders.
|
|
3217
|
+
* Default `'plain'`.
|
|
3218
|
+
*/
|
|
3219
|
+
variant?: RootLayoutVariant;
|
|
3220
|
+
children: ReactNode;
|
|
3221
|
+
/** px. Default 64 — matches the platform's own persistent nav rail
|
|
3222
|
+
* (`--sv-shell-sidebar-width`, runtime/app/(platform)/shell.module.css).
|
|
3223
|
+
* Only used by `'sidebar'`. */
|
|
3224
|
+
sidebarWidth?: number;
|
|
3225
|
+
/** px. Default 48 — matches a real `shell: minimal` plugin's own compact
|
|
3226
|
+
* web header (e.g. Kanban's `KanbanHeader`, deliberately not the
|
|
3227
|
+
* platform's 60px shell header since a `shell: minimal` plugin owns its
|
|
3228
|
+
* whole viewport). Web only — used by `'header'` on web. */
|
|
3229
|
+
headerHeight?: number;
|
|
3230
|
+
/** px. Default 60 — matches the platform's own mobile chrome
|
|
3231
|
+
* (`--sv-shell-header-height`, `MobileHeader`'s default). Mobile only —
|
|
3232
|
+
* used by `'header'` and `'shell'` on mobile. */
|
|
3233
|
+
mobileHeaderHeight?: number;
|
|
3234
|
+
/** px. Default 60 — matches the platform's own mobile chrome
|
|
3235
|
+
* (`--sv-shell-footer-height`, `MobileFooter`'s default). Mobile only —
|
|
3236
|
+
* used by `'shell'`. */
|
|
3237
|
+
footerHeight?: number;
|
|
3238
|
+
className?: string;
|
|
3239
|
+
}
|
|
3240
|
+
/**
|
|
3241
|
+
* RootLayout — the root-level layout a plugin's page tree sits in. Enforces
|
|
3242
|
+
* only structure and dimensions (`width: 100%; height: 100%`, filling
|
|
3243
|
+
* whatever real height the platform shell's own ancestor chain already
|
|
3244
|
+
* establishes — not viewport units, since a plugin isn't alone on the
|
|
3245
|
+
* page). A definite height, not min-height: ThreeColumnLayout/
|
|
3246
|
+
* HeaderFooterLayout's own .shell need one to resolve their own height:
|
|
3247
|
+
* 100% against (see RootLayout.module.css's comment). All content comes
|
|
3248
|
+
* from children; RootLayout has no opinion on what's inside any region.
|
|
3249
|
+
*
|
|
3250
|
+
* The web/mobile fork is `ResponsiveSurface` — only one of
|
|
3251
|
+
* `RootLayoutWeb`/`RootLayoutMobile` is ever mounted, never a CSS squeeze of
|
|
3252
|
+
* the same tree. Each variant's actual shape is delegated entirely to
|
|
3253
|
+
* `ThreeColumnLayout`/`HeaderFooterLayout`; RootLayout itself contains no
|
|
3254
|
+
* media queries.
|
|
3255
|
+
*/
|
|
3256
|
+
declare function RootLayout({ variant, children, sidebarWidth, headerHeight, mobileHeaderHeight, footerHeight, className, }: RootLayoutProps): react.JSX.Element;
|
|
3257
|
+
|
|
3258
|
+
type PageLayoutPadding = 'none' | 'sm' | 'md' | 'lg';
|
|
3259
|
+
interface PageLayoutProps {
|
|
3260
|
+
/**
|
|
3261
|
+
* A page-specific header — e.g. a board title + search/share toolbar.
|
|
3262
|
+
* Not the app-level header (that's `RootLayout`'s `header`/`shell`
|
|
3263
|
+
* variants) — this is a second, contextual row scoped to this one page.
|
|
3264
|
+
* Renders edge-to-edge above the padded content, outside `padding`.
|
|
3265
|
+
*/
|
|
3266
|
+
header?: ReactNode;
|
|
3267
|
+
/** The page gutter. Default `'none'` — unlike `PageContainer`, a bare
|
|
3268
|
+
* `PageLayout` applies no padding at all; opt in per page. Same four-step
|
|
3269
|
+
* scale and `--sv-page-gutter` stand-down hook as `PageContainer` (steps
|
|
3270
|
+
* down at the same 768px breakpoint `Dialog`/`MobileHeader` use). */
|
|
3271
|
+
padding?: PageLayoutPadding;
|
|
3272
|
+
children: ReactNode;
|
|
3273
|
+
className?: string;
|
|
3274
|
+
}
|
|
3275
|
+
/**
|
|
3276
|
+
* PageLayout — a single page's content area within a plugin (nested inside
|
|
3277
|
+
* `RootLayout`'s `main` slot, not a replacement for it). Enforces only
|
|
3278
|
+
* structure and dimensions; content always comes from props/children.
|
|
3279
|
+
*
|
|
3280
|
+
* `min-width: 100%; min-height: 100%` — safe here (unlike `RootLayout`'s own
|
|
3281
|
+
* root) because `PageLayout` is always nested inside an already
|
|
3282
|
+
* flex-stretched region (`ThreeColumnLayout`/`HeaderFooterLayout`'s
|
|
3283
|
+
* `.main`), and a stretched flex item's cross-size is definite for its
|
|
3284
|
+
* descendants' percentage resolution, so no separate height-cascading fix
|
|
3285
|
+
* is needed the way `RootLayout`'s root required one.
|
|
3286
|
+
*/
|
|
3287
|
+
declare function PageLayout({ header, padding, children, className }: PageLayoutProps): react.JSX.Element;
|
|
3288
|
+
|
|
3289
|
+
interface NavListItem {
|
|
3290
|
+
id: string;
|
|
3291
|
+
label: string;
|
|
3292
|
+
href: string;
|
|
3293
|
+
icon: IconName;
|
|
3294
|
+
/** Trailing content, e.g. a count — reserved for a future consumer, not used by `variant="drilldown"`'s own chevron slot. */
|
|
3295
|
+
badge?: ReactNode;
|
|
3296
|
+
/**
|
|
3297
|
+
* Only meaningful for `variant="static"` — whether this row represents the
|
|
3298
|
+
* current page. Computed by the consumer (e.g. via `usePathname()` plus a
|
|
3299
|
+
* longest-prefix match against every item's `href`), since this package
|
|
3300
|
+
* doesn't depend on `next/navigation` — same reasoning as `NavTabs`.
|
|
3301
|
+
*/
|
|
3302
|
+
active?: boolean;
|
|
3303
|
+
}
|
|
3304
|
+
interface NavListGroup {
|
|
3305
|
+
id: string;
|
|
3306
|
+
/** Omit for an ungrouped leading item/group — e.g. a pinned "Overview" row above the grouped sections. */
|
|
3307
|
+
label?: string;
|
|
3308
|
+
items: NavListItem[];
|
|
3309
|
+
}
|
|
3310
|
+
/** Props to spread onto a consumer-supplied link element — mirrors `NavTabs`' `NavTabLinkProps`. */
|
|
3311
|
+
interface NavListLinkProps {
|
|
3312
|
+
href: string;
|
|
3313
|
+
className: string;
|
|
3314
|
+
'aria-current': 'page' | undefined;
|
|
3315
|
+
children: ReactNode;
|
|
3316
|
+
}
|
|
3317
|
+
interface NavListProps {
|
|
3318
|
+
groups: NavListGroup[];
|
|
3319
|
+
/**
|
|
3320
|
+
* `"static"`: active row highlighted via `item.active`, no chevron — a
|
|
3321
|
+
* persistent sidebar nav. `"drilldown"`: every row gets a trailing chevron
|
|
3322
|
+
* and no active-state, tapping navigates to a full-screen section — a
|
|
3323
|
+
* native Settings-app style index.
|
|
3324
|
+
*/
|
|
3325
|
+
variant: 'static' | 'drilldown';
|
|
3326
|
+
/**
|
|
3327
|
+
* `"default"`: each row is a full `--sv-touch-target-min` tall tap target
|
|
3328
|
+
* — the right choice for `variant="drilldown"`'s mobile index, and for any
|
|
3329
|
+
* `static` sidebar list long enough that scanability matters more than
|
|
3330
|
+
* density. `"compact"` shrinks row height/padding for a short, always-
|
|
3331
|
+
* visible `static` list (e.g. 2-4 items above a longer scrollable list of
|
|
3332
|
+
* something else) where the default's touch-target height reads as
|
|
3333
|
+
* needlessly spaced out. Only meaningful for `variant="static"` —
|
|
3334
|
+
* `drilldown` rows stay full-height regardless, since that variant is
|
|
3335
|
+
* mobile-only and touch-target size there isn't optional. Defaults to
|
|
3336
|
+
* `"default"` so every existing consumer (e.g. Console's own sidebar) is
|
|
3337
|
+
* visually unchanged.
|
|
3338
|
+
*/
|
|
3339
|
+
density?: 'default' | 'compact';
|
|
3340
|
+
'aria-label': string;
|
|
3341
|
+
className?: string;
|
|
3342
|
+
/**
|
|
3343
|
+
* Render a row as something other than a plain `<a href>` — e.g. Next's
|
|
3344
|
+
* `<Link>`. Receives the item plus the class name/aria attributes NavList
|
|
3345
|
+
* would otherwise apply, matching `NavTabs`' `renderLink` contract.
|
|
3346
|
+
*/
|
|
3347
|
+
renderLink?: (item: NavListItem, linkProps: NavListLinkProps) => ReactNode;
|
|
3348
|
+
}
|
|
3349
|
+
/**
|
|
3350
|
+
* NavList — a vertical list of icon+label rows, optionally grouped under
|
|
3351
|
+
* section headers. Two presentations of the same `groups` data: `static` (a
|
|
3352
|
+
* persistent sidebar, active item highlighted) and `drilldown` (a
|
|
3353
|
+
* native-Settings-app style index, every row navigates to a full section).
|
|
3354
|
+
*
|
|
3355
|
+
* Framework-agnostic like `NavTabs` — no `next/navigation`/`next/link`
|
|
3356
|
+
* import, no internal pathname reading. Active state and navigation behavior
|
|
3357
|
+
* are supplied by the consumer via `item.active` and `renderLink`.
|
|
3358
|
+
*/
|
|
3359
|
+
declare function NavList({ groups, variant, density, 'aria-label': ariaLabel, className, renderLink, }: NavListProps): react.JSX.Element;
|
|
3360
|
+
|
|
3361
|
+
/**
|
|
3362
|
+
* Sovereign Design System — theme presets (RFC 0094).
|
|
3363
|
+
*
|
|
3364
|
+
* The single source of truth for every built-in theme preset's token
|
|
3365
|
+
* overrides. Deliberately plain data (no CSS-in-JS, no build dependency) so
|
|
3366
|
+
* it can be consumed two ways with zero duplication:
|
|
3367
|
+
* - `scripts/generate-theme-presets-css.ts` (`pnpm generate:theme-presets`)
|
|
3368
|
+
* reads this file and emits `theme-presets.css` for Storybook/standalone
|
|
3369
|
+
* consumers.
|
|
3370
|
+
* - `runtime/src/instance-style.ts` (RFC 0095) imports `THEME_PRESETS`
|
|
3371
|
+
* directly and flattens the selected preset into the existing
|
|
3372
|
+
* `:root { ... }` instance-style injection — no CSS parsing, no second
|
|
3373
|
+
* copy of these values to drift out of sync.
|
|
3374
|
+
*
|
|
3375
|
+
* Presets are closed and built-in (RFC 0094's "Alternatives considered") —
|
|
3376
|
+
* add a new one here and re-run the generator, never accept arbitrary
|
|
3377
|
+
* operator-supplied values into this shape.
|
|
3378
|
+
*
|
|
3379
|
+
* `'default'` MUST stay `{ light: {}, dark: {} }` — an empty override set is
|
|
3380
|
+
* what guarantees today's look stays byte-identical for every instance that
|
|
3381
|
+
* never opts into a different preset.
|
|
3382
|
+
*/
|
|
3383
|
+
type ThemePresetName = 'default' | 'neobrutalism';
|
|
3384
|
+
declare const THEME_PRESET_NAMES: readonly ThemePresetName[];
|
|
3385
|
+
interface ThemePresetTokens {
|
|
3386
|
+
/** Sparse — only tokens this preset overrides. Everything else falls through to the default. */
|
|
3387
|
+
light: Record<string, string>;
|
|
3388
|
+
dark: Record<string, string>;
|
|
3389
|
+
}
|
|
3390
|
+
declare const THEME_PRESETS: Record<ThemePresetName, ThemePresetTokens>;
|
|
3391
|
+
|
|
3392
|
+
export { Accordion, type AccordionItem, type AccordionProps, Alert, type AlertProps, type AlertVariant, AppsLauncher, type AppsLauncherItem, type AppsLauncherProps, 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, CardTile, CardTileGrid, type CardTileGridProps, type CardTileProps, CheckableListRow, type CheckableListRowProps, Checkbox, type CheckboxProps, CodeTextarea, type CodeTextareaProps, Collapsible, type CollapsibleProps, ColorPicker, type ColorPickerProps, type ColorPickerSwatch, Combobox, type ComboboxOption, type ComboboxProps, Command, type CommandItem, type CommandProps, type CommitOnEnterOrBlurHandlers, ConfirmDialog, type ConfirmDialogProps, ConsentPrompt, type ConsentPromptProps, 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, GripIcon, type GripIconProps, Header, HeaderFooterLayout, type HeaderFooterLayoutProps, type HeaderPlugin, type HeaderProps, 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, NavList, type NavListGroup, type NavListItem, type NavListLinkProps, type NavListProps, type NavTabItem, type NavTabLinkProps, NavTabs, type NavTabsProps, NavigationMenu, type NavigationMenuItem, type NavigationMenuProps, NewCardTile, type NewCardTileProps, NotificationsPanel, type NotificationsPanelItem, type NotificationsPanelProps, OfflineGate, type OfflineGateProps, type OfflineTileState, OverlayHeader, type OverlayHeaderProps, PageContainer, type PageContainerProps, PageHeader, type PageHeaderProps, PageLayout, type PageLayoutPadding, type PageLayoutProps, 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, RootLayout, type RootLayoutProps, type RootLayoutVariant, SWIPE_STACK_TRANSITION_MS, 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, StepStrip, type StepStripItem, type StepStripProps, 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, type SwipeDirection, type SwipeDirectionMeta, SwipeStack, SwipeStackCard, type SwipeStackCardProps, type SwipeStackProps, SystemBanner, type SystemBannerProps, THEME_PRESETS, THEME_PRESET_NAMES, type TabItem, Table, TableBody, TableCell, TableHead, TableHeaderCell, type TableProps, TableRow, Tabs, type TabsProps, TagInput, type TagInputProps, Textarea, type TextareaProps, type ThemePresetName, type ThemePresetTokens, ThreeColumnLayout, type ThreeColumnLayoutProps, type ToastContextValue, type ToastItem, ToastProvider, Toggle, type ToggleProps, Tooltip, type TooltipProps, Typography, type TypographyProps, type TypographyVariant, type UseCarouselRouteSyncOptions, type UseCarouselRouteSyncResult, type UseLongPressOptions, type UseReorderSensorsOptions, type UseResponsiveLayoutOptions, type UseResponsiveLayoutResult, type UseSnapCarouselOptions, type UseSnapCarouselResult, type UseSwipeRevealHandlers, type UseSwipeRevealOptions, type UseSwipeRevealResult, type UseSwipeStackHandlers, type UseSwipeStackOptions, type UseSwipeStackResult, UserMenu, type UserMenuItem, type UserMenuProps, shouldHandleDndEvent, useCarouselRouteSync, useCommitOnEnterOrBlur, useDoubleTapHandler, useIsMobile, useIsOffline, useLongPress, useOfflineTileState, useOverlaySecondRow, useReorderSensors, useResponsiveLayout, useSingleOrDoubleTap, useSnapCarousel, useSwipeReveal, useSwipeStack, useToast };
|