@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
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from '@storybook/react-vite';
|
|
2
|
+
import { MenuEntries } from '../components/Menu/Menu';
|
|
3
|
+
import type { MenuEntry } from '../components/Menu/Menu';
|
|
4
|
+
|
|
5
|
+
/** Plain action entries — no separator, label, or checkable state. */
|
|
6
|
+
const actionItems: MenuEntry[] = [
|
|
7
|
+
{ label: 'Rename', icon: 'pencil' },
|
|
8
|
+
{ label: 'Duplicate', icon: 'plus' },
|
|
9
|
+
{ label: 'Delete', icon: 'trash-2', destructive: true },
|
|
10
|
+
];
|
|
11
|
+
|
|
12
|
+
const meta = {
|
|
13
|
+
title: 'Components/MenuEntries',
|
|
14
|
+
component: MenuEntries,
|
|
15
|
+
parameters: { layout: 'padded' },
|
|
16
|
+
args: {
|
|
17
|
+
items: actionItems,
|
|
18
|
+
onSelect: (entry) => console.log(`MenuEntries selected: ${entry.label}`),
|
|
19
|
+
},
|
|
20
|
+
} satisfies Meta<typeof MenuEntries>;
|
|
21
|
+
|
|
22
|
+
export default meta;
|
|
23
|
+
type Story = StoryObj<typeof meta>;
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* The `<ul role="menu">` item list itself, factored out of `Menu` so other
|
|
27
|
+
* adaptive-surface components (`ContextMenu`, `Menubar`) can render the exact
|
|
28
|
+
* same items/destructive/checked/icon markup inside their own positioning
|
|
29
|
+
* shell (a floating `Popover` panel, a `Drawer` bottom sheet, or a `Menubar`
|
|
30
|
+
* flyout) instead of re-deriving it and risking drift. Renders with no panel
|
|
31
|
+
* chrome of its own here — the surrounding background, border, and shadow
|
|
32
|
+
* belong to whichever surface hosts it; see `Menu` in this gallery for the
|
|
33
|
+
* composed, positioned result.
|
|
34
|
+
*/
|
|
35
|
+
export const Default: Story = {};
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* The full entry vocabulary in one list: a `{ type: 'label' }` section
|
|
39
|
+
* heading, a checkable `menuitemradio` group (pass `checked` on every item in
|
|
40
|
+
* the group, including the unchecked ones, so their labels stay aligned with
|
|
41
|
+
* the checked one's reserved checkmark gutter), a `{ type: 'separator' }`
|
|
42
|
+
* divider, and a run of plain/destructive actions — the same composition
|
|
43
|
+
* `Menu`'s own "List actions" demo (Components/Menu) renders.
|
|
44
|
+
*/
|
|
45
|
+
export const GroupedWithLabelAndSeparator: Story = {
|
|
46
|
+
args: {
|
|
47
|
+
items: [
|
|
48
|
+
{ type: 'label', label: 'Sort by' },
|
|
49
|
+
{ label: 'Manual', checked: true },
|
|
50
|
+
{ label: 'Title', checked: false },
|
|
51
|
+
{ label: 'Due date', checked: false },
|
|
52
|
+
{ type: 'separator' },
|
|
53
|
+
...actionItems,
|
|
54
|
+
],
|
|
55
|
+
},
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* A `disabled` item stays in the list but renders `disabled` on its
|
|
60
|
+
* `<button>` — it can't be activated and `onSelect` never fires for it.
|
|
61
|
+
*/
|
|
62
|
+
export const WithDisabledItem: Story = {
|
|
63
|
+
args: {
|
|
64
|
+
items: [
|
|
65
|
+
{ label: 'Rename', icon: 'pencil' },
|
|
66
|
+
{ label: 'Duplicate', icon: 'plus', disabled: true },
|
|
67
|
+
{ label: 'Delete', icon: 'trash-2', destructive: true },
|
|
68
|
+
],
|
|
69
|
+
},
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* An `href` entry renders as an `<a role="menuitem">` instead of a
|
|
74
|
+
* `<button>` — same href-vs-onClick convention as `MobileAppsDrawer`'s
|
|
75
|
+
* items — for a pure navigation entry that needs no extra `onSelect`.
|
|
76
|
+
*/
|
|
77
|
+
export const WithLinkItems: Story = {
|
|
78
|
+
args: {
|
|
79
|
+
items: [
|
|
80
|
+
{ label: 'View profile', icon: 'user', href: '#profile' },
|
|
81
|
+
{ label: 'Settings', icon: 'settings', href: '#settings' },
|
|
82
|
+
{ type: 'separator' },
|
|
83
|
+
{ label: 'Sign out', icon: 'log-out', destructive: true },
|
|
84
|
+
],
|
|
85
|
+
},
|
|
86
|
+
};
|
|
@@ -8,6 +8,8 @@ import { EmptyState } from '../components/EmptyState/EmptyState';
|
|
|
8
8
|
import { FormField } from '../components/FormField/FormField';
|
|
9
9
|
import { Icon } from '../components/Icon/Icon';
|
|
10
10
|
import { Input } from '../components/Input/Input';
|
|
11
|
+
import { MobileFooter } from '../components/MobileFooter/MobileFooter';
|
|
12
|
+
import { MobileHeader } from '../components/MobileHeader/MobileHeader';
|
|
11
13
|
import { NavTabs } from '../components/NavTabs/NavTabs';
|
|
12
14
|
import { PageHeader } from '../components/PageHeader/PageHeader';
|
|
13
15
|
|
|
@@ -263,6 +265,175 @@ function DrawerDemo() {
|
|
|
263
265
|
);
|
|
264
266
|
}
|
|
265
267
|
|
|
268
|
+
function MobileHeaderFooterDemo() {
|
|
269
|
+
const [showTitle, setShowTitle] = useState(false);
|
|
270
|
+
const [customLogo, setCustomLogo] = useState(true);
|
|
271
|
+
const [wideFooter, setWideFooter] = useState(false);
|
|
272
|
+
const [appsOpen, setAppsOpen] = useState(false);
|
|
273
|
+
|
|
274
|
+
const logo = (
|
|
275
|
+
<span
|
|
276
|
+
aria-label="Home"
|
|
277
|
+
style={{
|
|
278
|
+
display: 'flex',
|
|
279
|
+
alignItems: 'center',
|
|
280
|
+
justifyContent: 'center',
|
|
281
|
+
width: 28,
|
|
282
|
+
height: 28,
|
|
283
|
+
borderRadius: 'var(--sv-radius-sm)',
|
|
284
|
+
background: 'var(--sv-color-accent)',
|
|
285
|
+
color: 'var(--sv-color-text-on-accent)',
|
|
286
|
+
fontWeight: 700,
|
|
287
|
+
fontSize: 13,
|
|
288
|
+
flexShrink: 0,
|
|
289
|
+
}}
|
|
290
|
+
>
|
|
291
|
+
S
|
|
292
|
+
</span>
|
|
293
|
+
);
|
|
294
|
+
|
|
295
|
+
const leftOne = [
|
|
296
|
+
{ icon: <Icon name="house" size="md" aria-hidden />, label: 'Home', active: true },
|
|
297
|
+
];
|
|
298
|
+
const rightOne = [{ icon: <Icon name="search" size="md" aria-hidden />, label: 'Search' }];
|
|
299
|
+
const leftTwo = [
|
|
300
|
+
...leftOne,
|
|
301
|
+
{ icon: <Icon name="calendar" size="md" aria-hidden />, label: 'Calendar' },
|
|
302
|
+
];
|
|
303
|
+
const rightTwo = [
|
|
304
|
+
...rightOne,
|
|
305
|
+
{ icon: <Icon name="activity" size="md" aria-hidden />, label: 'Activity' },
|
|
306
|
+
];
|
|
307
|
+
|
|
308
|
+
return (
|
|
309
|
+
<>
|
|
310
|
+
<div style={{ display: 'flex', gap: 'var(--sv-space-4)', marginBottom: 'var(--sv-space-4)' }}>
|
|
311
|
+
<label
|
|
312
|
+
style={{
|
|
313
|
+
display: 'flex',
|
|
314
|
+
alignItems: 'center',
|
|
315
|
+
gap: 'var(--sv-space-2)',
|
|
316
|
+
fontSize: 'var(--sv-font-size-sm)',
|
|
317
|
+
color: 'var(--sv-color-text-primary)',
|
|
318
|
+
cursor: 'pointer',
|
|
319
|
+
}}
|
|
320
|
+
>
|
|
321
|
+
<input
|
|
322
|
+
type="checkbox"
|
|
323
|
+
checked={showTitle}
|
|
324
|
+
onChange={(e) => setShowTitle(e.target.checked)}
|
|
325
|
+
/>
|
|
326
|
+
Header <code>title</code> prop
|
|
327
|
+
</label>
|
|
328
|
+
<label
|
|
329
|
+
style={{
|
|
330
|
+
display: 'flex',
|
|
331
|
+
alignItems: 'center',
|
|
332
|
+
gap: 'var(--sv-space-2)',
|
|
333
|
+
fontSize: 'var(--sv-font-size-sm)',
|
|
334
|
+
color: 'var(--sv-color-text-primary)',
|
|
335
|
+
cursor: 'pointer',
|
|
336
|
+
}}
|
|
337
|
+
>
|
|
338
|
+
<input
|
|
339
|
+
type="checkbox"
|
|
340
|
+
checked={customLogo}
|
|
341
|
+
onChange={(e) => setCustomLogo(e.target.checked)}
|
|
342
|
+
/>
|
|
343
|
+
Custom <code>logo</code> prop (unchecked = default badge)
|
|
344
|
+
</label>
|
|
345
|
+
<label
|
|
346
|
+
style={{
|
|
347
|
+
display: 'flex',
|
|
348
|
+
alignItems: 'center',
|
|
349
|
+
gap: 'var(--sv-space-2)',
|
|
350
|
+
fontSize: 'var(--sv-font-size-sm)',
|
|
351
|
+
color: 'var(--sv-color-text-primary)',
|
|
352
|
+
cursor: 'pointer',
|
|
353
|
+
}}
|
|
354
|
+
>
|
|
355
|
+
<input
|
|
356
|
+
type="checkbox"
|
|
357
|
+
checked={wideFooter}
|
|
358
|
+
onChange={(e) => setWideFooter(e.target.checked)}
|
|
359
|
+
/>
|
|
360
|
+
Footer: 2 + 2 icons
|
|
361
|
+
</label>
|
|
362
|
+
</div>
|
|
363
|
+
|
|
364
|
+
<div
|
|
365
|
+
style={{
|
|
366
|
+
border: '1px solid var(--sv-color-border)',
|
|
367
|
+
borderRadius: 'var(--sv-radius-lg)',
|
|
368
|
+
overflow: 'hidden',
|
|
369
|
+
}}
|
|
370
|
+
>
|
|
371
|
+
<MobileHeader
|
|
372
|
+
logo={customLogo ? logo : undefined}
|
|
373
|
+
title={showTitle ? 'Tasks' : undefined}
|
|
374
|
+
bell={
|
|
375
|
+
<button
|
|
376
|
+
type="button"
|
|
377
|
+
aria-label="Notifications"
|
|
378
|
+
style={{
|
|
379
|
+
display: 'inline-flex',
|
|
380
|
+
alignItems: 'center',
|
|
381
|
+
justifyContent: 'center',
|
|
382
|
+
width: 44,
|
|
383
|
+
height: 44,
|
|
384
|
+
background: 'none',
|
|
385
|
+
border: 'none',
|
|
386
|
+
borderRadius: 'var(--sv-radius-md)',
|
|
387
|
+
color: 'var(--sv-color-text-muted)',
|
|
388
|
+
cursor: 'pointer',
|
|
389
|
+
}}
|
|
390
|
+
>
|
|
391
|
+
<Icon name="bell" size="lg" aria-hidden />
|
|
392
|
+
</button>
|
|
393
|
+
}
|
|
394
|
+
avatarMenu={
|
|
395
|
+
<span
|
|
396
|
+
aria-label="Jamie Doe"
|
|
397
|
+
style={{
|
|
398
|
+
display: 'flex',
|
|
399
|
+
alignItems: 'center',
|
|
400
|
+
justifyContent: 'center',
|
|
401
|
+
width: 36,
|
|
402
|
+
height: 36,
|
|
403
|
+
borderRadius: 'var(--sv-radius-full)',
|
|
404
|
+
background: 'var(--sv-color-accent)',
|
|
405
|
+
border: '1px solid var(--sv-color-accent)',
|
|
406
|
+
color: 'var(--sv-color-text-on-accent)',
|
|
407
|
+
fontSize: 'var(--sv-font-size-xs)',
|
|
408
|
+
fontWeight: 'var(--sv-font-weight-semibold)',
|
|
409
|
+
}}
|
|
410
|
+
>
|
|
411
|
+
JD
|
|
412
|
+
</span>
|
|
413
|
+
}
|
|
414
|
+
/>
|
|
415
|
+
<div
|
|
416
|
+
style={{
|
|
417
|
+
background: 'var(--sv-color-surface-sunken)',
|
|
418
|
+
padding: 'var(--sv-space-6) var(--sv-space-4)',
|
|
419
|
+
textAlign: 'center',
|
|
420
|
+
color: 'var(--sv-color-text-muted)',
|
|
421
|
+
fontSize: 'var(--sv-font-size-xs)',
|
|
422
|
+
}}
|
|
423
|
+
>
|
|
424
|
+
Plugin content area
|
|
425
|
+
</div>
|
|
426
|
+
<MobileFooter
|
|
427
|
+
onOpenApps={() => setAppsOpen((v) => !v)}
|
|
428
|
+
launcherOpen={appsOpen}
|
|
429
|
+
leftIcons={wideFooter ? leftTwo : leftOne}
|
|
430
|
+
rightIcons={wideFooter ? rightTwo : rightOne}
|
|
431
|
+
/>
|
|
432
|
+
</div>
|
|
433
|
+
</>
|
|
434
|
+
);
|
|
435
|
+
}
|
|
436
|
+
|
|
266
437
|
// ---------------------------------------------------------------------------
|
|
267
438
|
// Main story component
|
|
268
439
|
// ---------------------------------------------------------------------------
|
|
@@ -916,11 +1087,19 @@ function MobilePatternsDoc() {
|
|
|
916
1087
|
</div>
|
|
917
1088
|
</div>
|
|
918
1089
|
<Callout type="info">
|
|
919
|
-
Plugin developers cannot customise the Launcher Drawer. Use
|
|
1090
|
+
Plugin developers cannot customise the Launcher Drawer. Use{' '}
|
|
1091
|
+
<code style={{ fontFamily: 'var(--sv-font-family-mono)', fontSize: '0.8em' }}>
|
|
1092
|
+
MobileAppsDrawer
|
|
1093
|
+
</code>{' '}
|
|
1094
|
+
from{' '}
|
|
920
1095
|
<code style={{ fontFamily: 'var(--sv-font-family-mono)', fontSize: '0.8em' }}>
|
|
921
1096
|
@sovereignfs/ui
|
|
922
1097
|
</code>{' '}
|
|
923
|
-
if you need
|
|
1098
|
+
if you need this exact grid pattern inside your own plugin (e.g. a mobile-only launcher
|
|
1099
|
+
for the plugin's own sections) — same tile styling as the shell's own drawer (no header
|
|
1100
|
+
row; Drawer's own grab handle/swipe-down/scrim-tap already dismiss it), with the tile
|
|
1101
|
+
list supplied by you via its <code>items</code> prop. See the Component Gallery for a
|
|
1102
|
+
live demo.
|
|
924
1103
|
</Callout>
|
|
925
1104
|
</Card>
|
|
926
1105
|
|
|
@@ -1066,6 +1245,38 @@ function MobilePatternsDoc() {
|
|
|
1066
1245
|
</Card>
|
|
1067
1246
|
</section>
|
|
1068
1247
|
|
|
1248
|
+
{/* ── MobileHeader / MobileFooter components (RFC 0088) ── */}
|
|
1249
|
+
<section style={{ marginBottom: 'var(--sv-space-10)' }}>
|
|
1250
|
+
<SectionHeader
|
|
1251
|
+
title="MobileHeader / MobileFooter components"
|
|
1252
|
+
subtitle="The presentational components behind the shell chrome above (RFC 0088). Bell, avatar menu, and the centered launcher are always rendered by the consumer — the header's logo/title (logo defaults to an 'S' badge when omitted) and the footer's leftIcons/rightIcons are the only overridable parts."
|
|
1253
|
+
/>
|
|
1254
|
+
<Card padding="md">
|
|
1255
|
+
<MobileHeaderFooterDemo />
|
|
1256
|
+
<div style={{ marginTop: 'var(--sv-space-4)' }}>
|
|
1257
|
+
<Callout type="info">
|
|
1258
|
+
<code style={{ fontFamily: 'var(--sv-font-family-mono)', fontSize: '0.8em' }}>
|
|
1259
|
+
leftIcons
|
|
1260
|
+
</code>{' '}
|
|
1261
|
+
and{' '}
|
|
1262
|
+
<code style={{ fontFamily: 'var(--sv-font-family-mono)', fontSize: '0.8em' }}>
|
|
1263
|
+
rightIcons
|
|
1264
|
+
</code>{' '}
|
|
1265
|
+
must have the same length so the launcher stays centered — a mismatch logs a
|
|
1266
|
+
dev-mode-only{' '}
|
|
1267
|
+
<code style={{ fontFamily: 'var(--sv-font-family-mono)', fontSize: '0.8em' }}>
|
|
1268
|
+
console.error
|
|
1269
|
+
</code>{' '}
|
|
1270
|
+
(never thrown), the same non-fatal guard pattern used by{' '}
|
|
1271
|
+
<code style={{ fontFamily: 'var(--sv-font-family-mono)', fontSize: '0.8em' }}>
|
|
1272
|
+
SwipableMobileCarousel
|
|
1273
|
+
</code>
|
|
1274
|
+
.
|
|
1275
|
+
</Callout>
|
|
1276
|
+
</div>
|
|
1277
|
+
</Card>
|
|
1278
|
+
</section>
|
|
1279
|
+
|
|
1069
1280
|
{/* ── Touch targets ── */}
|
|
1070
1281
|
<section style={{ marginBottom: 'var(--sv-space-10)' }}>
|
|
1071
1282
|
<SectionHeader
|
|
@@ -1205,6 +1416,40 @@ function MobilePatternsDoc() {
|
|
|
1205
1416
|
</Callout>
|
|
1206
1417
|
</section>
|
|
1207
1418
|
|
|
1419
|
+
{/* ── Layout: swipeable route views ── */}
|
|
1420
|
+
<section style={{ marginBottom: 'var(--sv-space-10)' }}>
|
|
1421
|
+
<SectionHeader
|
|
1422
|
+
title="Layout: swipeable route views"
|
|
1423
|
+
subtitle="Some views are route-based on web but want a different mobile UX — a swipeable filmstrip of the same sibling routes, not a squeeze of a multi-column layout. This is a recurring pattern (sovereign-tasks, sovereign-shopper) — build it with the shared primitives, not a hand-rolled scroller."
|
|
1424
|
+
/>
|
|
1425
|
+
<p
|
|
1426
|
+
style={{
|
|
1427
|
+
fontFamily: ff,
|
|
1428
|
+
fontSize: 'var(--sv-font-size-sm)',
|
|
1429
|
+
color: 'var(--sv-color-text-muted)',
|
|
1430
|
+
lineHeight: 1.6,
|
|
1431
|
+
}}
|
|
1432
|
+
>
|
|
1433
|
+
<code style={{ fontFamily: 'var(--sv-font-family-mono)', fontSize: '0.8em' }}>
|
|
1434
|
+
SwipableMobileCarousel
|
|
1435
|
+
</code>{' '}
|
|
1436
|
+
renders full-width slides over a native <code>scroll-snap-type: x</code> container — no
|
|
1437
|
+
hand-rolled pointer/touch dragging. Pair it with <code>useCarouselRouteSync</code> to keep
|
|
1438
|
+
the active slide synced to the URL, and <code>ResponsiveSurface</code>/
|
|
1439
|
+
<code>useResponsiveLayout</code> for the breakpoint fork itself. See the{' '}
|
|
1440
|
+
<em>Interaction Hooks</em> story for the full API and live demos of both hooks.
|
|
1441
|
+
</p>
|
|
1442
|
+
<Callout type="warn">
|
|
1443
|
+
<code style={{ fontFamily: 'var(--sv-font-family-mono)', fontSize: '0.8em' }}>
|
|
1444
|
+
SwipableMobileCarousel
|
|
1445
|
+
</code>{' '}
|
|
1446
|
+
owns rendering and mount-window mechanics only — it has no opinion on where slide data
|
|
1447
|
+
lives. Don't aggregate cross-slide data or mount a detail overlay (Sheet, Dialog)
|
|
1448
|
+
inside a Slide's children; mount overlays as a sibling of the carousel instead. See
|
|
1449
|
+
docs/design-system.md's carousel section for the full rationale.
|
|
1450
|
+
</Callout>
|
|
1451
|
+
</section>
|
|
1452
|
+
|
|
1208
1453
|
{/* ── Checklist ── */}
|
|
1209
1454
|
<section style={{ marginBottom: 'var(--sv-space-10)' }}>
|
|
1210
1455
|
<SectionHeader title="Mobile readiness checklist" />
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from '@storybook/react-vite';
|
|
2
|
+
import { NavList } from '../components/NavList/NavList';
|
|
3
|
+
|
|
4
|
+
const overviewGroup = {
|
|
5
|
+
id: 'overview',
|
|
6
|
+
items: [
|
|
7
|
+
{ id: 'overview', label: 'Overview', href: '#overview', icon: 'layout-dashboard' as const },
|
|
8
|
+
],
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
const peopleGroup = {
|
|
12
|
+
id: 'people',
|
|
13
|
+
label: 'People',
|
|
14
|
+
items: [
|
|
15
|
+
{ id: 'users', label: 'Users', href: '#users', icon: 'users' as const },
|
|
16
|
+
{ id: 'groups', label: 'Groups', href: '#groups', icon: 'layers' as const },
|
|
17
|
+
],
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
const appsGroup = {
|
|
21
|
+
id: 'apps',
|
|
22
|
+
label: 'Apps',
|
|
23
|
+
items: [
|
|
24
|
+
{ id: 'plugins', label: 'Apps', href: '#plugins', icon: 'layout-grid' as const },
|
|
25
|
+
{ id: 'entitlements', label: 'Entitlements', href: '#entitlements', icon: 'shield' as const },
|
|
26
|
+
],
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
const meta = {
|
|
30
|
+
title: 'Components/NavList',
|
|
31
|
+
component: NavList,
|
|
32
|
+
parameters: { layout: 'padded' },
|
|
33
|
+
args: {
|
|
34
|
+
groups: [overviewGroup, peopleGroup, appsGroup],
|
|
35
|
+
variant: 'static',
|
|
36
|
+
'aria-label': 'Example sections',
|
|
37
|
+
},
|
|
38
|
+
} satisfies Meta<typeof NavList>;
|
|
39
|
+
|
|
40
|
+
export default meta;
|
|
41
|
+
type Story = StoryObj<typeof meta>;
|
|
42
|
+
|
|
43
|
+
/** Desktop sidebar shape — active row highlighted, no chevron. */
|
|
44
|
+
export const StaticActiveItem: Story = {
|
|
45
|
+
args: {
|
|
46
|
+
groups: [
|
|
47
|
+
{
|
|
48
|
+
id: 'overview',
|
|
49
|
+
items: [
|
|
50
|
+
{
|
|
51
|
+
id: 'overview',
|
|
52
|
+
label: 'Overview',
|
|
53
|
+
href: '#overview',
|
|
54
|
+
icon: 'layout-dashboard',
|
|
55
|
+
active: true,
|
|
56
|
+
},
|
|
57
|
+
],
|
|
58
|
+
},
|
|
59
|
+
peopleGroup,
|
|
60
|
+
appsGroup,
|
|
61
|
+
],
|
|
62
|
+
},
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* `density="compact"` — shrinks row height/padding for a short, always-
|
|
67
|
+
* visible `static` list (e.g. Warden's sidebar "New chat"/"Providers"/
|
|
68
|
+
* "Models" rows above its longer session list), where the default's full
|
|
69
|
+
* touch-target height reads as needlessly spaced out. Only meaningful with
|
|
70
|
+
* `variant="static"`; every other story on this page stays at the default
|
|
71
|
+
* density, unaffected.
|
|
72
|
+
*/
|
|
73
|
+
export const CompactDensity: Story = {
|
|
74
|
+
args: {
|
|
75
|
+
density: 'compact',
|
|
76
|
+
groups: [
|
|
77
|
+
{
|
|
78
|
+
id: 'primary',
|
|
79
|
+
items: [
|
|
80
|
+
{ id: 'new', label: 'New chat', href: '#new', icon: 'plus' },
|
|
81
|
+
{ id: 'providers', label: 'Providers', href: '#providers', icon: 'link' },
|
|
82
|
+
{ id: 'models', label: 'Models', href: '#models', icon: 'layers' },
|
|
83
|
+
],
|
|
84
|
+
},
|
|
85
|
+
],
|
|
86
|
+
},
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
/** Mobile index shape — every row gets a trailing chevron, no active state; tapping navigates to a full-screen section. */
|
|
90
|
+
export const Drilldown: Story = {
|
|
91
|
+
args: { variant: 'drilldown' },
|
|
92
|
+
parameters: { viewport: { defaultViewport: 'mobile' } },
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
/** `group.label` omitted entirely renders a flat list with no section headers. */
|
|
96
|
+
export const Ungrouped: Story = {
|
|
97
|
+
args: {
|
|
98
|
+
groups: [
|
|
99
|
+
{
|
|
100
|
+
id: 'flat',
|
|
101
|
+
items: [
|
|
102
|
+
{ id: 'a', label: 'First', href: '#a', icon: 'house' },
|
|
103
|
+
{ id: 'b', label: 'Second', href: '#b', icon: 'settings' },
|
|
104
|
+
],
|
|
105
|
+
},
|
|
106
|
+
],
|
|
107
|
+
},
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
/** Simulates a consumer swapping in a client-side router link (e.g. Next's `<Link>`) instead of a plain `<a>`. */
|
|
111
|
+
export const WithCustomLinkRenderer: Story = {
|
|
112
|
+
args: {
|
|
113
|
+
groups: [
|
|
114
|
+
{
|
|
115
|
+
id: 'overview',
|
|
116
|
+
items: [
|
|
117
|
+
{
|
|
118
|
+
id: 'overview',
|
|
119
|
+
label: 'Overview',
|
|
120
|
+
href: '#overview',
|
|
121
|
+
icon: 'layout-dashboard',
|
|
122
|
+
active: true,
|
|
123
|
+
},
|
|
124
|
+
],
|
|
125
|
+
},
|
|
126
|
+
peopleGroup,
|
|
127
|
+
],
|
|
128
|
+
renderLink: (item, linkProps) => (
|
|
129
|
+
<button
|
|
130
|
+
type="button"
|
|
131
|
+
className={linkProps.className}
|
|
132
|
+
aria-current={linkProps['aria-current']}
|
|
133
|
+
onClick={() => console.log(`client-side navigate to ${item.href}`)}
|
|
134
|
+
style={{
|
|
135
|
+
background: 'none',
|
|
136
|
+
border: 'none',
|
|
137
|
+
cursor: 'pointer',
|
|
138
|
+
font: 'inherit',
|
|
139
|
+
textAlign: 'left',
|
|
140
|
+
width: '100%',
|
|
141
|
+
}}
|
|
142
|
+
>
|
|
143
|
+
{linkProps.children}
|
|
144
|
+
</button>
|
|
145
|
+
),
|
|
146
|
+
},
|
|
147
|
+
};
|
|
@@ -33,3 +33,21 @@ export const ManyTabs: Story = {
|
|
|
33
33
|
},
|
|
34
34
|
parameters: { viewport: { defaultViewport: 'mobile' } },
|
|
35
35
|
};
|
|
36
|
+
|
|
37
|
+
/** Simulates a consumer swapping in a client-side router link (e.g. Next's
|
|
38
|
+
* `<Link replace>` inside an overlay-shell plugin) instead of a plain `<a>`. */
|
|
39
|
+
export const WithCustomLinkRenderer: Story = {
|
|
40
|
+
args: {
|
|
41
|
+
renderLink: (item, linkProps) => (
|
|
42
|
+
<button
|
|
43
|
+
type="button"
|
|
44
|
+
className={linkProps.className}
|
|
45
|
+
aria-current={linkProps['aria-current']}
|
|
46
|
+
onClick={() => console.log(`client-side navigate to ${item.href}`)}
|
|
47
|
+
style={{ background: 'none', border: 'none', cursor: 'pointer', font: 'inherit' }}
|
|
48
|
+
>
|
|
49
|
+
{linkProps.children}
|
|
50
|
+
</button>
|
|
51
|
+
),
|
|
52
|
+
},
|
|
53
|
+
};
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from '@storybook/react-vite';
|
|
2
|
+
import { OfflineGate } from '../components/OfflineGate/OfflineGate';
|
|
3
|
+
import { EmptyState } from '../components/EmptyState/EmptyState';
|
|
4
|
+
|
|
5
|
+
const sampleContent = (
|
|
6
|
+
<div style={{ padding: 'var(--sv-space-4)' }}>
|
|
7
|
+
<p style={{ margin: 0, fontFamily: 'var(--sv-font-family)' }}>
|
|
8
|
+
The wrapped content — a user list, a settings form, anything that shouldn’t operate
|
|
9
|
+
against a stale cached snapshot.
|
|
10
|
+
</p>
|
|
11
|
+
</div>
|
|
12
|
+
);
|
|
13
|
+
|
|
14
|
+
const meta = {
|
|
15
|
+
title: 'Components/OfflineGate',
|
|
16
|
+
component: OfflineGate,
|
|
17
|
+
parameters: { layout: 'padded' },
|
|
18
|
+
} satisfies Meta<typeof OfflineGate>;
|
|
19
|
+
|
|
20
|
+
export default meta;
|
|
21
|
+
type Story = StoryObj<typeof meta>;
|
|
22
|
+
|
|
23
|
+
// `OfflineGate` reads the live `useIsOffline()` hook, which Storybook can't
|
|
24
|
+
// drive via args — toggle DevTools > Network > Offline (or throttling) in this
|
|
25
|
+
// preview to see it swap `children` for the blocked state below.
|
|
26
|
+
export const Default: Story = {
|
|
27
|
+
args: { children: sampleContent, surfaceName: 'Console' },
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
// The offline state is not independently controllable here (same reason as
|
|
31
|
+
// Default), so this renders the exact EmptyState output OfflineGate produces
|
|
32
|
+
// when useIsOffline() is true, for visual review without a live toggle.
|
|
33
|
+
export const OfflineState: Story = {
|
|
34
|
+
args: { children: sampleContent, surfaceName: 'Console' },
|
|
35
|
+
render: () => (
|
|
36
|
+
<EmptyState
|
|
37
|
+
icon="alert-triangle"
|
|
38
|
+
heading="You're offline"
|
|
39
|
+
description="Console needs a connection — reconnect to continue."
|
|
40
|
+
/>
|
|
41
|
+
),
|
|
42
|
+
};
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from '@storybook/react-vite';
|
|
2
|
+
import { PageContainer } from '../components/PageContainer/PageContainer';
|
|
3
|
+
|
|
4
|
+
const demoBlock = (
|
|
5
|
+
<div
|
|
6
|
+
style={{
|
|
7
|
+
background: 'var(--sv-color-surface-raised)',
|
|
8
|
+
border: '1px dashed var(--sv-color-border)',
|
|
9
|
+
borderRadius: 'var(--sv-radius-md)',
|
|
10
|
+
padding: 'var(--sv-space-4)',
|
|
11
|
+
}}
|
|
12
|
+
>
|
|
13
|
+
Page content goes here. This component supplies the page's gutter and constrains its width
|
|
14
|
+
— the runtime shell contributes neither.
|
|
15
|
+
</div>
|
|
16
|
+
);
|
|
17
|
+
|
|
18
|
+
const meta = {
|
|
19
|
+
title: 'Components/PageContainer',
|
|
20
|
+
component: PageContainer,
|
|
21
|
+
parameters: {
|
|
22
|
+
layout: 'fullscreen',
|
|
23
|
+
docs: {
|
|
24
|
+
description: {
|
|
25
|
+
component:
|
|
26
|
+
'The wrapper every plugin page renders through. `padding` is the page gutter (the ' +
|
|
27
|
+
'runtime shell no longer applies one) and `maxWidth` constrains content, padding ' +
|
|
28
|
+
'included. Both default to the common case: a full-width page with the standard ' +
|
|
29
|
+
'32px/16px gutter.',
|
|
30
|
+
},
|
|
31
|
+
},
|
|
32
|
+
},
|
|
33
|
+
args: { children: demoBlock },
|
|
34
|
+
} satisfies Meta<typeof PageContainer>;
|
|
35
|
+
|
|
36
|
+
export default meta;
|
|
37
|
+
type Story = StoryObj<typeof meta>;
|
|
38
|
+
|
|
39
|
+
/** Defaults: full width, `md` gutter. */
|
|
40
|
+
export const Default: Story = {};
|
|
41
|
+
|
|
42
|
+
// ── maxWidth ──────────────────────────────────────────────────────────────
|
|
43
|
+
|
|
44
|
+
export const WidthSmall: Story = { args: { maxWidth: 'sm' } };
|
|
45
|
+
export const WidthMedium: Story = { args: { maxWidth: 'md' } };
|
|
46
|
+
export const WidthLarge: Story = { args: { maxWidth: 'lg' } };
|
|
47
|
+
|
|
48
|
+
// ── padding ───────────────────────────────────────────────────────────────
|
|
49
|
+
|
|
50
|
+
/** For a page whose ancestor already supplies the gutter, or a full-bleed layout. */
|
|
51
|
+
export const PaddingNone: Story = { args: { padding: 'none' } };
|
|
52
|
+
export const PaddingSmall: Story = { args: { padding: 'sm' } };
|
|
53
|
+
export const PaddingLarge: Story = { args: { padding: 'lg' } };
|
|
54
|
+
|
|
55
|
+
/** Every gutter step tightens below 768px. */
|
|
56
|
+
export const MobileViewport: Story = {
|
|
57
|
+
args: { maxWidth: 'md' },
|
|
58
|
+
parameters: { viewport: { defaultViewport: 'mobile1' } },
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* An ancestor that already pads its content sets `--sv-page-gutter: 0` and
|
|
63
|
+
* PageContainer stands its own gutter down — how `Dialog` keeps an overlay
|
|
64
|
+
* plugin from being padded twice when the same page renders inside it.
|
|
65
|
+
*/
|
|
66
|
+
export const GutterSuppressedByAncestor: Story = {
|
|
67
|
+
args: { maxWidth: 'md' },
|
|
68
|
+
decorators: [
|
|
69
|
+
(Story) => (
|
|
70
|
+
<div
|
|
71
|
+
style={{
|
|
72
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- CSS custom property
|
|
73
|
+
['--sv-page-gutter' as any]: '0px',
|
|
74
|
+
border: '1px solid var(--sv-color-border)',
|
|
75
|
+
padding: 'var(--sv-space-6)',
|
|
76
|
+
}}
|
|
77
|
+
>
|
|
78
|
+
<Story />
|
|
79
|
+
</div>
|
|
80
|
+
),
|
|
81
|
+
],
|
|
82
|
+
};
|