@sovereignfs/ui 0.41.0 → 0.54.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (256) hide show
  1. package/dist/Accordion.module.css +3 -0
  2. package/dist/Alert.module.css +62 -0
  3. package/dist/AspectRatio.module.css +15 -0
  4. package/dist/Badge.module.css +75 -9
  5. package/dist/Breadcrumb.module.css +49 -0
  6. package/dist/Button.module.css +12 -3
  7. package/dist/ButtonGroup.module.css +29 -0
  8. package/dist/Card.module.css +6 -0
  9. package/dist/Checkbox.module.css +3 -1
  10. package/dist/Collapsible.module.css +70 -0
  11. package/dist/Combobox.module.css +122 -0
  12. package/dist/Command.module.css +80 -0
  13. package/dist/ConfirmDialog.module.css +5 -1
  14. package/dist/DataTable.module.css +28 -0
  15. package/dist/DatePicker.module.css +6 -2
  16. package/dist/Dialog.module.css +3 -0
  17. package/dist/HoverCard.module.css +3 -0
  18. package/dist/Input.module.css +6 -1
  19. package/dist/Item.module.css +70 -0
  20. package/dist/Kbd.module.css +14 -0
  21. package/dist/Label.module.css +11 -0
  22. package/dist/LegalLinks.module.css +24 -0
  23. package/dist/Markdown.module.css +49 -0
  24. package/dist/Marker.module.css +33 -0
  25. package/dist/Menu.module.css +8 -1
  26. package/dist/Message.module.css +99 -0
  27. package/dist/MessageScroller.module.css +54 -0
  28. package/dist/MobileAppsDrawer.module.css +81 -0
  29. package/dist/MobileFooter.module.css +73 -0
  30. package/dist/MobileHeader.module.css +63 -0
  31. package/dist/NavigationMenu.module.css +53 -0
  32. package/dist/PageContainer.module.css +20 -0
  33. package/dist/Pagination.module.css +98 -0
  34. package/dist/Progress.module.css +20 -0
  35. package/dist/RadioGroup.module.css +88 -0
  36. package/dist/Resizable.module.css +70 -0
  37. package/dist/ScrollArea.module.css +20 -0
  38. package/dist/Select.module.css +7 -3
  39. package/dist/Slider.module.css +95 -0
  40. package/dist/SwipableMobileCarousel.module.css +55 -0
  41. package/dist/SwipableMobileCarouselDots.module.css +49 -0
  42. package/dist/SwipableMobileCarouselSlideParts.module.css +29 -0
  43. package/dist/Table.module.css +45 -0
  44. package/dist/Tabs.module.css +6 -1
  45. package/dist/Toggle.module.css +6 -1
  46. package/dist/Typography.module.css +47 -0
  47. package/dist/index.d.ts +1268 -8
  48. package/dist/index.js +3046 -975
  49. package/dist/tokens/primitives.css +47 -8
  50. package/package.json +13 -9
  51. package/src/components/Accordion/Accordion.module.css +3 -0
  52. package/src/components/Accordion/Accordion.stories.tsx +110 -0
  53. package/src/components/Accordion/Accordion.tsx +53 -0
  54. package/src/components/Accordion/__tests__/Accordion.test.tsx +61 -0
  55. package/src/components/Alert/Alert.module.css +62 -0
  56. package/src/components/Alert/Alert.stories.tsx +96 -0
  57. package/src/components/Alert/Alert.tsx +58 -0
  58. package/src/components/Alert/__tests__/Alert.test.tsx +75 -0
  59. package/src/components/AspectRatio/AspectRatio.module.css +15 -0
  60. package/src/components/AspectRatio/AspectRatio.stories.tsx +78 -0
  61. package/src/components/AspectRatio/AspectRatio.tsx +21 -0
  62. package/src/components/AspectRatio/__tests__/AspectRatio.test.tsx +27 -0
  63. package/src/components/Badge/Badge.module.css +75 -9
  64. package/src/components/Badge/Badge.stories.tsx +109 -1
  65. package/src/components/Badge/Badge.tsx +31 -9
  66. package/src/components/Badge/__tests__/Badge.test.tsx +26 -2
  67. package/src/components/Breadcrumb/Breadcrumb.module.css +49 -0
  68. package/src/components/Breadcrumb/Breadcrumb.stories.tsx +47 -0
  69. package/src/components/Breadcrumb/Breadcrumb.tsx +57 -0
  70. package/src/components/Breadcrumb/__tests__/Breadcrumb.test.tsx +52 -0
  71. package/src/components/Button/Button.module.css +12 -3
  72. package/src/components/Button/Button.stories.tsx +3 -2
  73. package/src/components/Button/Button.tsx +1 -1
  74. package/src/components/Button/__tests__/Button.test.tsx +10 -0
  75. package/src/components/ButtonGroup/ButtonGroup.module.css +29 -0
  76. package/src/components/ButtonGroup/ButtonGroup.stories.tsx +57 -0
  77. package/src/components/ButtonGroup/ButtonGroup.tsx +27 -0
  78. package/src/components/ButtonGroup/__tests__/ButtonGroup.test.tsx +29 -0
  79. package/src/components/Card/Card.module.css +6 -0
  80. package/src/components/Checkbox/Checkbox.module.css +3 -1
  81. package/src/components/Collapsible/Collapsible.module.css +70 -0
  82. package/src/components/Collapsible/Collapsible.stories.tsx +62 -0
  83. package/src/components/Collapsible/Collapsible.tsx +66 -0
  84. package/src/components/Collapsible/__tests__/Collapsible.test.tsx +71 -0
  85. package/src/components/Combobox/Combobox.module.css +122 -0
  86. package/src/components/Combobox/Combobox.stories.tsx +79 -0
  87. package/src/components/Combobox/Combobox.tsx +186 -0
  88. package/src/components/Combobox/__tests__/Combobox.test.tsx +158 -0
  89. package/src/components/Command/Command.module.css +80 -0
  90. package/src/components/Command/Command.stories.tsx +104 -0
  91. package/src/components/Command/Command.tsx +168 -0
  92. package/src/components/Command/__tests__/Command.test.tsx +162 -0
  93. package/src/components/ConfirmDialog/ConfirmDialog.module.css +5 -1
  94. package/src/components/ContextMenu/ContextMenu.stories.tsx +81 -0
  95. package/src/components/ContextMenu/ContextMenu.tsx +100 -0
  96. package/src/components/ContextMenu/__tests__/ContextMenu.test.tsx +87 -0
  97. package/src/components/DataTable/DataTable.module.css +28 -0
  98. package/src/components/DataTable/DataTable.stories.tsx +74 -0
  99. package/src/components/DataTable/DataTable.tsx +154 -0
  100. package/src/components/DataTable/__tests__/DataTable.test.tsx +123 -0
  101. package/src/components/DatePicker/DatePicker.module.css +6 -2
  102. package/src/components/DeviceOnlyGate/DeviceOnlyGate.tsx +58 -0
  103. package/src/components/DeviceOnlyGate/__tests__/DeviceOnlyGate.test.tsx +45 -0
  104. package/src/components/DeviceStorageKeyGate/DeviceStorageKeyGate.tsx +100 -0
  105. package/src/components/DeviceStorageKeyGate/__tests__/DeviceStorageKeyGate.test.tsx +119 -0
  106. package/src/components/Dialog/Dialog.module.css +3 -0
  107. package/src/components/Dialog/Dialog.tsx +8 -59
  108. package/src/components/Drawer/Drawer.stories.tsx +44 -0
  109. package/src/components/Drawer/Drawer.tsx +18 -56
  110. package/src/components/Drawer/__tests__/Drawer.test.tsx +22 -0
  111. package/src/components/HoverCard/HoverCard.module.css +3 -0
  112. package/src/components/HoverCard/HoverCard.stories.tsx +66 -0
  113. package/src/components/HoverCard/HoverCard.tsx +111 -0
  114. package/src/components/HoverCard/__tests__/HoverCard.test.tsx +99 -0
  115. package/src/components/Icon/icons/circle-check.tsx +21 -0
  116. package/src/components/Icon/icons/circle-x.tsx +22 -0
  117. package/src/components/Icon/icons/index.ts +16 -0
  118. package/src/components/Icon/icons/menu.tsx +22 -0
  119. package/src/components/Icon/icons/smartphone.tsx +21 -0
  120. package/src/components/Input/Input.module.css +6 -1
  121. package/src/components/Item/Item.module.css +70 -0
  122. package/src/components/Item/Item.stories.tsx +62 -0
  123. package/src/components/Item/Item.tsx +54 -0
  124. package/src/components/Item/__tests__/Item.test.tsx +39 -0
  125. package/src/components/Kbd/Kbd.module.css +14 -0
  126. package/src/components/Kbd/Kbd.stories.tsx +32 -0
  127. package/src/components/Kbd/Kbd.tsx +12 -0
  128. package/src/components/Kbd/__tests__/Kbd.test.tsx +14 -0
  129. package/src/components/Label/Label.module.css +11 -0
  130. package/src/components/Label/Label.stories.tsx +40 -0
  131. package/src/components/Label/Label.tsx +24 -0
  132. package/src/components/Label/__tests__/Label.test.tsx +32 -0
  133. package/src/components/LegalLinks/LegalLinks.module.css +24 -0
  134. package/src/components/LegalLinks/LegalLinks.stories.tsx +22 -0
  135. package/src/components/LegalLinks/LegalLinks.tsx +40 -0
  136. package/src/components/Markdown/Markdown.module.css +49 -0
  137. package/src/components/Markdown/Markdown.stories.tsx +30 -0
  138. package/src/components/Markdown/Markdown.tsx +168 -0
  139. package/src/components/Marker/Marker.module.css +33 -0
  140. package/src/components/Marker/Marker.stories.tsx +39 -0
  141. package/src/components/Marker/Marker.tsx +34 -0
  142. package/src/components/Marker/__tests__/Marker.test.tsx +23 -0
  143. package/src/components/Menu/Menu.module.css +8 -1
  144. package/src/components/Menu/Menu.tsx +30 -8
  145. package/src/components/Menubar/Menubar.stories.tsx +52 -0
  146. package/src/components/Menubar/Menubar.tsx +42 -0
  147. package/src/components/Menubar/__tests__/Menubar.test.tsx +45 -0
  148. package/src/components/Message/Message.module.css +99 -0
  149. package/src/components/Message/Message.stories.tsx +79 -0
  150. package/src/components/Message/Message.tsx +41 -0
  151. package/src/components/Message/__tests__/Message.test.tsx +46 -0
  152. package/src/components/MessageScroller/MessageScroller.module.css +54 -0
  153. package/src/components/MessageScroller/MessageScroller.stories.tsx +84 -0
  154. package/src/components/MessageScroller/MessageScroller.tsx +72 -0
  155. package/src/components/MessageScroller/__tests__/MessageScroller.test.tsx +105 -0
  156. package/src/components/MobileAppsDrawer/MobileAppsDrawer.module.css +81 -0
  157. package/src/components/MobileAppsDrawer/MobileAppsDrawer.tsx +78 -0
  158. package/src/components/MobileAppsDrawer/__tests__/MobileAppsDrawer.test.tsx +80 -0
  159. package/src/components/MobileFooter/MobileFooter.module.css +73 -0
  160. package/src/components/MobileFooter/MobileFooter.stories.tsx +47 -0
  161. package/src/components/MobileFooter/MobileFooter.tsx +109 -0
  162. package/src/components/MobileFooter/__tests__/MobileFooter.test.tsx +71 -0
  163. package/src/components/MobileHeader/MobileHeader.module.css +63 -0
  164. package/src/components/MobileHeader/MobileHeader.stories.tsx +57 -0
  165. package/src/components/MobileHeader/MobileHeader.tsx +77 -0
  166. package/src/components/MobileHeader/__tests__/MobileHeader.test.tsx +58 -0
  167. package/src/components/NavTabs/NavTabs.tsx +39 -11
  168. package/src/components/NavigationMenu/NavigationMenu.module.css +53 -0
  169. package/src/components/NavigationMenu/NavigationMenu.stories.tsx +58 -0
  170. package/src/components/NavigationMenu/NavigationMenu.tsx +125 -0
  171. package/src/components/NavigationMenu/__tests__/NavigationMenu.test.tsx +93 -0
  172. package/src/components/OfflineGate/OfflineGate.tsx +44 -0
  173. package/src/components/OfflineGate/__tests__/OfflineGate.test.tsx +66 -0
  174. package/src/components/PageContainer/PageContainer.module.css +20 -0
  175. package/src/components/PageContainer/PageContainer.tsx +31 -0
  176. package/src/components/PageContainer/__tests__/PageContainer.test.tsx +37 -0
  177. package/src/components/PageHeader/PageHeader.tsx +16 -2
  178. package/src/components/Pagination/Pagination.module.css +98 -0
  179. package/src/components/Pagination/Pagination.stories.tsx +49 -0
  180. package/src/components/Pagination/Pagination.tsx +87 -0
  181. package/src/components/Pagination/__tests__/Pagination.test.tsx +56 -0
  182. package/src/components/Progress/Progress.module.css +20 -0
  183. package/src/components/Progress/Progress.stories.tsx +40 -0
  184. package/src/components/Progress/Progress.tsx +35 -0
  185. package/src/components/Progress/__tests__/Progress.test.tsx +31 -0
  186. package/src/components/RadioGroup/RadioGroup.module.css +88 -0
  187. package/src/components/RadioGroup/RadioGroup.stories.tsx +69 -0
  188. package/src/components/RadioGroup/RadioGroup.tsx +99 -0
  189. package/src/components/RadioGroup/__tests__/RadioGroup.test.tsx +59 -0
  190. package/src/components/Resizable/Resizable.module.css +70 -0
  191. package/src/components/Resizable/Resizable.stories.tsx +82 -0
  192. package/src/components/Resizable/Resizable.tsx +313 -0
  193. package/src/components/Resizable/__tests__/Resizable.test.tsx +128 -0
  194. package/src/components/ResponsiveSurface/ResponsiveSurface.tsx +24 -0
  195. package/src/components/ResponsiveSurface/__tests__/ResponsiveSurface.test.tsx +73 -0
  196. package/src/components/ScrollArea/ScrollArea.module.css +20 -0
  197. package/src/components/ScrollArea/ScrollArea.stories.tsx +57 -0
  198. package/src/components/ScrollArea/ScrollArea.tsx +27 -0
  199. package/src/components/ScrollArea/__tests__/ScrollArea.test.tsx +39 -0
  200. package/src/components/Select/Select.module.css +7 -3
  201. package/src/components/Sheet/Sheet.tsx +9 -51
  202. package/src/components/Slider/Slider.module.css +95 -0
  203. package/src/components/Slider/Slider.stories.tsx +64 -0
  204. package/src/components/Slider/Slider.tsx +77 -0
  205. package/src/components/Slider/__tests__/Slider.test.tsx +45 -0
  206. package/src/components/SwipableMobileCarousel/SwipableMobileCarousel.module.css +55 -0
  207. package/src/components/SwipableMobileCarousel/SwipableMobileCarousel.tsx +227 -0
  208. package/src/components/SwipableMobileCarousel/SwipableMobileCarouselSlide.tsx +35 -0
  209. package/src/components/SwipableMobileCarousel/SwipableMobileCarouselSlideBody.tsx +42 -0
  210. package/src/components/SwipableMobileCarousel/SwipableMobileCarouselSlideFooter.tsx +18 -0
  211. package/src/components/SwipableMobileCarousel/SwipableMobileCarouselSlideHeader.tsx +21 -0
  212. package/src/components/SwipableMobileCarousel/SwipableMobileCarouselSlideParts.module.css +29 -0
  213. package/src/components/SwipableMobileCarousel/__tests__/SwipableMobileCarousel.test.tsx +133 -0
  214. package/src/components/SwipableMobileCarousel/__tests__/SwipableMobileCarouselDevGuard.test.tsx +61 -0
  215. package/src/components/SwipableMobileCarousel/context.ts +13 -0
  216. package/src/components/SwipableMobileCarouselDots/SwipableMobileCarouselDots.module.css +49 -0
  217. package/src/components/SwipableMobileCarouselDots/SwipableMobileCarouselDots.tsx +62 -0
  218. package/src/components/SwipableMobileCarouselDots/__tests__/SwipableMobileCarouselDots.test.tsx +63 -0
  219. package/src/components/Table/Table.module.css +45 -0
  220. package/src/components/Table/Table.stories.tsx +125 -0
  221. package/src/components/Table/Table.tsx +51 -0
  222. package/src/components/Table/__tests__/Table.test.tsx +50 -0
  223. package/src/components/Tabs/Tabs.module.css +6 -1
  224. package/src/components/Toggle/Toggle.module.css +6 -1
  225. package/src/components/Typography/Typography.module.css +47 -0
  226. package/src/components/Typography/Typography.stories.tsx +62 -0
  227. package/src/components/Typography/Typography.tsx +30 -0
  228. package/src/components/Typography/__tests__/Typography.test.tsx +34 -0
  229. package/src/hooks/__tests__/useCarouselRouteSync.test.tsx +126 -0
  230. package/src/hooks/__tests__/useIsOffline.test.tsx +53 -0
  231. package/src/hooks/__tests__/useOfflineTileState.test.tsx +69 -0
  232. package/src/hooks/__tests__/useResponsiveLayout.test.tsx +87 -0
  233. package/src/hooks/__tests__/useSnapCarousel.test.tsx +93 -0
  234. package/src/hooks/__tests__/useSwipeReveal.test.tsx +110 -0
  235. package/src/hooks/index.ts +18 -0
  236. package/src/hooks/useCarouselRouteSync.ts +92 -0
  237. package/src/hooks/useIsOffline.ts +41 -0
  238. package/src/hooks/useOfflineTileState.ts +45 -0
  239. package/src/hooks/useResponsiveLayout.ts +36 -0
  240. package/src/hooks/useSnapCarousel.ts +79 -0
  241. package/src/hooks/useSwipeReveal.ts +107 -0
  242. package/src/index.ts +141 -3
  243. package/src/overlay-shell.ts +94 -0
  244. package/src/stories/DesignSystemOverview.stories.tsx +936 -3
  245. package/src/stories/DeviceOnlyGate.stories.tsx +35 -0
  246. package/src/stories/DeviceStorageKeyGate.stories.tsx +57 -0
  247. package/src/stories/InteractionHooks.stories.tsx +483 -0
  248. package/src/stories/MobilePatterns.stories.tsx +255 -4
  249. package/src/stories/NavTabs.stories.tsx +18 -0
  250. package/src/stories/OfflineGate.stories.tsx +42 -0
  251. package/src/stories/PageContainer.stories.tsx +37 -0
  252. package/src/stories/PageHeader.stories.tsx +11 -0
  253. package/src/stories/ResponsiveSurface.stories.tsx +47 -0
  254. package/src/stories/SwipableMobileCarousel.stories.tsx +304 -0
  255. package/src/stories/TokenGallery.stories.tsx +177 -16
  256. package/src/tokens/primitives.css +47 -8
@@ -8,6 +8,9 @@ import { Card } from '../components/Card/Card';
8
8
  import { Dialog } from '../components/Dialog/Dialog';
9
9
  import { Drawer } from '../components/Drawer/Drawer';
10
10
  import { EmptyState } from '../components/EmptyState/EmptyState';
11
+ import { OfflineGate } from '../components/OfflineGate/OfflineGate';
12
+ import { DeviceOnlyGate } from '../components/DeviceOnlyGate/DeviceOnlyGate';
13
+ import { DeviceStorageKeyGate } from '../components/DeviceStorageKeyGate/DeviceStorageKeyGate';
11
14
  import { FormField } from '../components/FormField/FormField';
12
15
  import { Icon } from '../components/Icon/Icon';
13
16
  import type { IconName } from '../components/Icon/Icon';
@@ -21,9 +24,19 @@ import { StatusBadge } from '../components/StatusBadge/StatusBadge';
21
24
  import { SplitPane } from '../components/SplitPane/SplitPane';
22
25
  import { TagInput } from '../components/TagInput/TagInput';
23
26
  import { SuggestionInput } from '../components/SuggestionInput/SuggestionInput';
27
+ import { MobileAppsDrawer } from '../components/MobileAppsDrawer/MobileAppsDrawer';
28
+ import { MobileFooter } from '../components/MobileFooter/MobileFooter';
29
+ import { MobileHeader } from '../components/MobileHeader/MobileHeader';
24
30
  import { NavTabs } from '../components/NavTabs/NavTabs';
25
31
  import { PageHeader } from '../components/PageHeader/PageHeader';
32
+ import { PageContainer } from '../components/PageContainer/PageContainer';
26
33
  import { Popover } from '../components/Popover/Popover';
34
+ import { ResponsiveSurface } from '../components/ResponsiveSurface/ResponsiveSurface';
35
+ import { SwipableMobileCarousel } from '../components/SwipableMobileCarousel/SwipableMobileCarousel';
36
+ import { SwipableMobileCarouselSlide } from '../components/SwipableMobileCarousel/SwipableMobileCarouselSlide';
37
+ import { SwipableMobileCarouselSlideHeader } from '../components/SwipableMobileCarousel/SwipableMobileCarouselSlideHeader';
38
+ import { SwipableMobileCarouselSlideBody } from '../components/SwipableMobileCarousel/SwipableMobileCarouselSlideBody';
39
+ import { SwipableMobileCarouselDots } from '../components/SwipableMobileCarouselDots/SwipableMobileCarouselDots';
27
40
  import { SegmentedControl } from '../components/SegmentedControl/SegmentedControl';
28
41
  import { Select } from '../components/Select/Select';
29
42
  import { Spinner } from '../components/Spinner/Spinner';
@@ -33,6 +46,46 @@ import { ToastProvider, useToast } from '../components/Toast/Toast';
33
46
  import { Toggle } from '../components/Toggle/Toggle';
34
47
  import { Tooltip } from '../components/Tooltip/Tooltip';
35
48
  import { Checkbox } from '../components/Checkbox/Checkbox';
49
+ import { RadioGroup } from '../components/RadioGroup/RadioGroup';
50
+ import { Slider } from '../components/Slider/Slider';
51
+ import { Progress } from '../components/Progress/Progress';
52
+ import {
53
+ Table,
54
+ TableHead,
55
+ TableBody,
56
+ TableRow,
57
+ TableHeaderCell,
58
+ TableCell,
59
+ } from '../components/Table/Table';
60
+ import { Alert } from '../components/Alert/Alert';
61
+ import { Breadcrumb } from '../components/Breadcrumb/Breadcrumb';
62
+ import { LegalLinks } from '../components/LegalLinks/LegalLinks';
63
+ import { Markdown } from '../components/Markdown/Markdown';
64
+ import { Pagination } from '../components/Pagination/Pagination';
65
+ import { Kbd } from '../components/Kbd/Kbd';
66
+ import { Accordion } from '../components/Accordion/Accordion';
67
+ import { AspectRatio } from '../components/AspectRatio/AspectRatio';
68
+ import { ButtonGroup } from '../components/ButtonGroup/ButtonGroup';
69
+ import { Item } from '../components/Item/Item';
70
+ import { Label } from '../components/Label/Label';
71
+ import { ScrollArea } from '../components/ScrollArea/ScrollArea';
72
+ import { Typography } from '../components/Typography/Typography';
73
+ import { Marker } from '../components/Marker/Marker';
74
+ import { Message } from '../components/Message/Message';
75
+ import { MessageScroller } from '../components/MessageScroller/MessageScroller';
76
+ import { HoverCard } from '../components/HoverCard/HoverCard';
77
+ import { ContextMenu } from '../components/ContextMenu/ContextMenu';
78
+ import { Command } from '../components/Command/Command';
79
+ import {
80
+ ResizableHandle,
81
+ ResizablePanel,
82
+ ResizablePanelGroup,
83
+ } from '../components/Resizable/Resizable';
84
+ import { DataTable } from '../components/DataTable/DataTable';
85
+ import { Combobox } from '../components/Combobox/Combobox';
86
+ import { NavigationMenu } from '../components/NavigationMenu/NavigationMenu';
87
+ import { Menubar } from '../components/Menubar/Menubar';
88
+ import { Collapsible } from '../components/Collapsible/Collapsible';
36
89
  import { DragHandleRow } from '../components/DragHandleRow/DragHandleRow';
37
90
  import { FileDropzone } from '../components/FileDropzone/FileDropzone';
38
91
  import { OverlayHeader } from '../components/OverlayHeader/OverlayHeader';
@@ -466,6 +519,64 @@ function TabsDemo() {
466
519
  );
467
520
  }
468
521
 
522
+ function CommandDemo() {
523
+ const [open, setOpen] = useState(false);
524
+ return (
525
+ <>
526
+ <Button size="sm" onClick={() => setOpen(true)}>
527
+ Open command palette
528
+ </Button>
529
+ <Command
530
+ open={open}
531
+ onClose={() => setOpen(false)}
532
+ aria-label="Command palette"
533
+ items={[
534
+ {
535
+ id: 'new',
536
+ label: 'New conversation',
537
+ group: 'Actions',
538
+ icon: 'plus',
539
+ onSelect: () => {},
540
+ },
541
+ {
542
+ id: 'export',
543
+ label: 'Export chat',
544
+ group: 'Actions',
545
+ icon: 'upload',
546
+ onSelect: () => {},
547
+ },
548
+ {
549
+ id: 'profile',
550
+ label: 'Go to profile',
551
+ group: 'Navigation',
552
+ icon: 'user',
553
+ onSelect: () => {},
554
+ },
555
+ ]}
556
+ />
557
+ </>
558
+ );
559
+ }
560
+
561
+ function ComboboxDemo() {
562
+ const [value, setValue] = useState<string | null>(null);
563
+ return (
564
+ <div style={{ width: 240 }}>
565
+ <Combobox
566
+ options={[
567
+ { value: 'tasks', label: 'sovereign-tasks' },
568
+ { value: 'ledger', label: 'sovereign-ledger' },
569
+ { value: 'shopper', label: 'sovereign-shopper' },
570
+ ]}
571
+ value={value}
572
+ onChange={setValue}
573
+ placeholder="Select a plugin"
574
+ aria-label="Plugin"
575
+ />
576
+ </div>
577
+ );
578
+ }
579
+
469
580
  function DialogDemo() {
470
581
  const [open, setOpen] = useState(false);
471
582
  return (
@@ -535,6 +646,28 @@ function DrawerDemo() {
535
646
  );
536
647
  }
537
648
 
649
+ function MobileAppsDrawerDemo() {
650
+ const [open, setOpen] = useState(false);
651
+ const items = [
652
+ { key: 'home', icon: <Icon name="house" size="lg" aria-hidden />, label: 'Home' },
653
+ { key: 'notes', icon: <Icon name="file" size="lg" aria-hidden />, label: 'Notes' },
654
+ { key: 'gallery', icon: <Icon name="grid-2x2" size="lg" aria-hidden />, label: 'Gallery' },
655
+ ];
656
+ return (
657
+ <>
658
+ <Button size="sm" variant="secondary" onClick={() => setOpen(true)}>
659
+ Open drawer
660
+ </Button>
661
+ <MobileAppsDrawer
662
+ open={open}
663
+ onClose={() => setOpen(false)}
664
+ aria-label="Sections"
665
+ items={items.map((item) => ({ ...item, onClick: () => setOpen(false) }))}
666
+ />
667
+ </>
668
+ );
669
+ }
670
+
538
671
  function PopoverDemo() {
539
672
  const [open, setOpen] = useState(false);
540
673
  return (
@@ -825,6 +958,100 @@ function FileDropzoneDemo() {
825
958
  );
826
959
  }
827
960
 
961
+ function ResponsiveSurfaceDemo() {
962
+ return (
963
+ <div
964
+ style={{
965
+ border: '1px dashed var(--sv-color-border)',
966
+ borderRadius: 'var(--sv-radius-md)',
967
+ padding: 'var(--sv-space-4)',
968
+ fontFamily: ff,
969
+ fontSize: 'var(--sv-font-size-sm)',
970
+ color: 'var(--sv-color-text-muted)',
971
+ }}
972
+ >
973
+ <ResponsiveSurface
974
+ web={
975
+ <span style={{ color: 'var(--sv-color-text-primary)' }}>
976
+ Web tree (resize below 768px)
977
+ </span>
978
+ }
979
+ mobile={<span style={{ color: 'var(--sv-color-text-primary)' }}>Mobile tree</span>}
980
+ />
981
+ </div>
982
+ );
983
+ }
984
+
985
+ const SWIPABLE_CAROUSEL_DEMO_SLIDES = [
986
+ { key: 'lists', label: 'Lists' },
987
+ { key: 'groceries', label: 'Groceries' },
988
+ { key: 'errands', label: 'Errands' },
989
+ ];
990
+
991
+ function SwipableMobileCarouselDemo() {
992
+ const [activeIndex, setActiveIndex] = useState(0);
993
+ return (
994
+ <div
995
+ style={{
996
+ height: 220,
997
+ border: '1px solid var(--sv-color-border)',
998
+ borderRadius: 'var(--sv-radius-md)',
999
+ overflow: 'hidden',
1000
+ }}
1001
+ >
1002
+ <SwipableMobileCarousel
1003
+ activeIndex={activeIndex}
1004
+ onSettle={setActiveIndex}
1005
+ aria-label="Task lists"
1006
+ >
1007
+ {SWIPABLE_CAROUSEL_DEMO_SLIDES.map((slide) => (
1008
+ <SwipableMobileCarouselSlide key={slide.key} slideKey={slide.key} label={slide.label}>
1009
+ <SwipableMobileCarouselSlideHeader>
1010
+ <div
1011
+ style={{
1012
+ padding: 'var(--sv-space-3)',
1013
+ fontFamily: ff,
1014
+ fontWeight: 'var(--sv-font-weight-semibold)',
1015
+ color: 'var(--sv-color-text-primary)',
1016
+ }}
1017
+ >
1018
+ {slide.label}
1019
+ </div>
1020
+ </SwipableMobileCarouselSlideHeader>
1021
+ <SwipableMobileCarouselSlideBody>
1022
+ <div
1023
+ style={{
1024
+ display: 'flex',
1025
+ alignItems: 'center',
1026
+ justifyContent: 'center',
1027
+ height: '100%',
1028
+ fontFamily: ff,
1029
+ color: 'var(--sv-color-text-muted)',
1030
+ }}
1031
+ >
1032
+ {slide.label} content
1033
+ </div>
1034
+ </SwipableMobileCarouselSlideBody>
1035
+ </SwipableMobileCarouselSlide>
1036
+ ))}
1037
+ </SwipableMobileCarousel>
1038
+ </div>
1039
+ );
1040
+ }
1041
+
1042
+ function SwipableMobileCarouselDotsDemo() {
1043
+ const [activeIndex, setActiveIndex] = useState(0);
1044
+ return (
1045
+ <SwipableMobileCarouselDots
1046
+ count={3}
1047
+ activeIndex={activeIndex}
1048
+ onJump={setActiveIndex}
1049
+ labels={['Lists', 'Groceries', 'Errands']}
1050
+ aria-label="Task lists"
1051
+ />
1052
+ );
1053
+ }
1054
+
828
1055
  function IconPickerDemo() {
829
1056
  const [value, setValue] = useState<IconName | null>('banana');
830
1057
  const options: IconName[] = [
@@ -1327,6 +1554,21 @@ font-weight: var(--sv-font-weight-medium); /* 500 */
1327
1554
  font-weight: var(--sv-font-weight-semibold); /* 600 */
1328
1555
  font-weight: var(--sv-font-weight-bold); /* 700 */`}</Code>
1329
1556
  </div>
1557
+
1558
+ <div style={{ marginTop: 'var(--sv-space-4)' }}>
1559
+ <p
1560
+ style={{
1561
+ fontSize: 'var(--sv-font-size-caption)',
1562
+ color: 'var(--sv-color-text-muted)',
1563
+ }}
1564
+ >
1565
+ Every size above is rem-based, so the whole scale responds to the in-app text-size
1566
+ control (Account → Preferences → Appearance, task 10.2), which sets{' '}
1567
+ <code>[data-text-size]</code> on <code>&lt;html&gt;</code> to override{' '}
1568
+ <code>--sv-text-size-scale</code> — see the &quot;Text-size presets&quot; section of
1569
+ the Token Gallery.
1570
+ </p>
1571
+ </div>
1330
1572
  </section>
1331
1573
 
1332
1574
  {/* ── Spacing & Radius ─────────────────────────────────────────── */}
@@ -1486,7 +1728,7 @@ font-weight: var(--sv-font-weight-bold); /* 700 */`}</Code>
1486
1728
  <section style={{ marginBottom: 'var(--sv-space-12)' }}>
1487
1729
  <SectionHeader
1488
1730
  title="Component gallery"
1489
- subtitle="All 22 components — click each story in the sidebar for the full API, variants, and controls."
1731
+ subtitle="All 74 components — click each story in the sidebar for the full API, variants, and controls."
1490
1732
  />
1491
1733
 
1492
1734
  <div style={{ marginBottom: 'var(--sv-space-6)' }}>
@@ -1508,7 +1750,7 @@ font-weight: var(--sv-font-weight-bold); /* 700 */`}</Code>
1508
1750
  <ComponentCard
1509
1751
  name="Button"
1510
1752
  importLine="import { Button } from '@sovereignfs/ui';"
1511
- usage="Primary interactive control. Four variants: primary, secondary, ghost, destructive. Two sizes: md (default), sm. loading disables the button, sets aria-busy, and shows a spinner. 44px min-height under (pointer: coarse); hover behind (hover: hover)."
1753
+ usage="Primary interactive control. Four variants: primary, secondary, ghost, destructive. Three sizes: lg, md (default), sm. loading disables the button, sets aria-busy, and shows a spinner. 44px min-height under (pointer: coarse); hover behind (hover: hover)."
1512
1754
  >
1513
1755
  <Button>Primary</Button>
1514
1756
  <Button variant="secondary">Secondary</Button>
@@ -1516,6 +1758,7 @@ font-weight: var(--sv-font-weight-bold); /* 700 */`}</Code>
1516
1758
  <Button variant="destructive" size="sm">
1517
1759
  Delete
1518
1760
  </Button>
1761
+ <Button size="lg">Large</Button>
1519
1762
  <Button loading>Saving…</Button>
1520
1763
  </ComponentCard>
1521
1764
 
@@ -1523,7 +1766,7 @@ font-weight: var(--sv-font-weight-bold); /* 700 */`}</Code>
1523
1766
  <ComponentCard
1524
1767
  name="Badge"
1525
1768
  importLine="import { Badge } from '@sovereignfs/ui';"
1526
- usage="Compact label for roles (role), lifecycle states (status), and type/version tags (mono). RSC-safe."
1769
+ usage="Compact label for roles (role), lifecycle states (status), and type/version tags (mono). sm/md/lg sizes; ALL CAPS by default, or uppercase={false} for title case. RSC-safe."
1527
1770
  >
1528
1771
  <Badge variant="role">Admin</Badge>
1529
1772
  <Badge variant="status" status="active">
@@ -1533,6 +1776,9 @@ font-weight: var(--sv-font-weight-bold); /* 700 */`}</Code>
1533
1776
  Invited
1534
1777
  </Badge>
1535
1778
  <Badge variant="mono">v1.2.0</Badge>
1779
+ <Badge variant="role" size="lg" uppercase={false}>
1780
+ Owner
1781
+ </Badge>
1536
1782
  </ComponentCard>
1537
1783
 
1538
1784
  {/* StatusBadge */}
@@ -1913,6 +2159,85 @@ font-weight: var(--sv-font-weight-bold); /* 700 */`}</Code>
1913
2159
  </div>
1914
2160
  </ComponentCard>
1915
2161
 
2162
+ {/* PageContainer */}
2163
+ <ComponentCard
2164
+ name="PageContainer"
2165
+ importLine="import { PageContainer } from '@sovereignfs/ui';"
2166
+ usage="Constrains and centers a plugin's main content width (sm/md/lg/full). Adds no padding of its own — the runtime shell already pads plugin content. Use instead of local container CSS in a plugin's layout."
2167
+ >
2168
+ <div style={{ width: '100%', background: 'var(--sv-color-surface-sunken)' }}>
2169
+ <PageContainer maxWidth="sm">
2170
+ <div
2171
+ style={{
2172
+ background: 'var(--sv-color-surface-raised)',
2173
+ border: '1px dashed var(--sv-color-border)',
2174
+ borderRadius: 'var(--sv-radius-md)',
2175
+ padding: 'var(--sv-space-4)',
2176
+ }}
2177
+ >
2178
+ maxWidth=&quot;sm&quot;
2179
+ </div>
2180
+ </PageContainer>
2181
+ </div>
2182
+ </ComponentCard>
2183
+
2184
+ {/* MobileHeader */}
2185
+ <ComponentCard
2186
+ name="MobileHeader"
2187
+ importLine="import { MobileHeader } from '@sovereignfs/ui';"
2188
+ usage="Mobile shell header content row (RFC 0088). Bell and avatarMenu are always rendered by the consumer; logo is optional — omit it for a default 'S' badge (matching the shell's own fallback brand icon), or supply your own. Title is the only content-level override. Presentational only — see Overview/Mobile Patterns for the full immutable/overridable boundary."
2189
+ >
2190
+ <div
2191
+ style={{
2192
+ width: '100%',
2193
+ background: 'var(--sv-color-surface)',
2194
+ border: '1px solid var(--sv-color-border)',
2195
+ borderRadius: 'var(--sv-radius-md)',
2196
+ padding: '0 var(--sv-space-3)',
2197
+ }}
2198
+ >
2199
+ <MobileHeader
2200
+ title="Tasks"
2201
+ bell={<Icon name="bell" size="lg" aria-label="Notifications" />}
2202
+ avatarMenu={<Avatar name="Jamie Doe" size="sm" />}
2203
+ />
2204
+ </div>
2205
+ </ComponentCard>
2206
+
2207
+ {/* MobileFooter */}
2208
+ <ComponentCard
2209
+ name="MobileFooter"
2210
+ importLine="import { MobileFooter } from '@sovereignfs/ui';"
2211
+ usage="Mobile shell footer nav row (RFC 0088). The centered launcher (onOpenApps) is always rendered; leftIcons/rightIcons take 1 or 2 items each, symmetric so the launcher stays centered."
2212
+ >
2213
+ <div
2214
+ style={{
2215
+ width: '100%',
2216
+ background: 'var(--sv-color-surface)',
2217
+ border: '1px solid var(--sv-color-border)',
2218
+ borderRadius: 'var(--sv-radius-md)',
2219
+ padding: '0 var(--sv-space-3)',
2220
+ }}
2221
+ >
2222
+ <MobileFooter
2223
+ onOpenApps={() => {}}
2224
+ leftIcons={[{ icon: <Icon name="house" size="md" aria-hidden />, label: 'Home' }]}
2225
+ rightIcons={[
2226
+ { icon: <Icon name="search" size="md" aria-hidden />, label: 'Search' },
2227
+ ]}
2228
+ />
2229
+ </div>
2230
+ </ComponentCard>
2231
+
2232
+ {/* MobileAppsDrawer */}
2233
+ <ComponentCard
2234
+ name="MobileAppsDrawer"
2235
+ importLine="import { MobileAppsDrawer } from '@sovereignfs/ui';"
2236
+ usage="The mobile 'Apps' bottom sheet: a 3-column tile grid matching the runtime shell's own Apps drawer exactly (56px icon tiles, radius 14px). No header row — Drawer's own grab handle plus swipe-down/scrim-tap dismissal are enough. The tile list is supplied by the consumer via `items` — e.g. installed plugins for the shell, or a plugin's own sections for its own launcher button."
2237
+ >
2238
+ <MobileAppsDrawerDemo />
2239
+ </ComponentCard>
2240
+
1916
2241
  {/* EmptyState */}
1917
2242
  <ComponentCard
1918
2243
  name="EmptyState"
@@ -1931,6 +2256,53 @@ font-weight: var(--sv-font-weight-bold); /* 700 */`}</Code>
1931
2256
  />
1932
2257
  </ComponentCard>
1933
2258
 
2259
+ {/* OfflineGate */}
2260
+ <ComponentCard
2261
+ name="OfflineGate"
2262
+ importLine="import { OfflineGate } from '@sovereignfs/ui';"
2263
+ usage="Blocks an administrative/settings surface from rendering possibly-stale cached content while offline, showing an EmptyState instead. Reads the live useIsOffline() hook — toggle DevTools > Network > Offline to see it swap. Wrap the body of a surface like Console or Account where operating against stale data is actively wrong, not every page."
2264
+ >
2265
+ <OfflineGate surfaceName="Console">
2266
+ <p style={{ margin: 0, fontFamily: 'var(--sv-font-family)', fontSize: 14 }}>
2267
+ Renders normally while online.
2268
+ </p>
2269
+ </OfflineGate>
2270
+ </ComponentCard>
2271
+
2272
+ {/* DeviceOnlyGate */}
2273
+ <ComponentCard
2274
+ name="DeviceOnlyGate"
2275
+ importLine="import { DeviceOnlyGate } from '@sovereignfs/ui';"
2276
+ usage="Blocks a device-only-tier plugin's content on a surface without the durable, encrypted, device-auth-gated store that tier needs. `available` is the caller's own isDeviceOnlyTierAvailable() result (from @sovereignfs/sdk/device-client) — a capability signal, never a security boundary; the real protection is that the data is encrypted regardless."
2277
+ >
2278
+ <DeviceOnlyGate available={false} surfaceName="Wallet">
2279
+ <p style={{ margin: 0, fontFamily: 'var(--sv-font-family)', fontSize: 14 }}>
2280
+ Renders normally when available.
2281
+ </p>
2282
+ </DeviceOnlyGate>
2283
+ </ComponentCard>
2284
+
2285
+ {/* DeviceStorageKeyGate */}
2286
+ <ComponentCard
2287
+ name="DeviceStorageKeyGate"
2288
+ importLine="import { DeviceStorageKeyGate } from '@sovereignfs/ui';"
2289
+ usage="Blocks a device-only-tier plugin's content until the user has set up their Device Storage Key. `status` is the caller's own getDeviceStorageKeyStatus() result (from @sovereignfs/sdk/device-only-storage). Enrollment is centralized in Account > Security, not per-plugin — `setupAction` is where the caller supplies a link there."
2290
+ >
2291
+ <DeviceStorageKeyGate
2292
+ status="not-set-up"
2293
+ surfaceName="Notes"
2294
+ setupAction={
2295
+ <a href="/account/security" style={{ color: 'var(--sv-color-accent)' }}>
2296
+ Go to Account → Security
2297
+ </a>
2298
+ }
2299
+ >
2300
+ <p style={{ margin: 0, fontFamily: 'var(--sv-font-family)', fontSize: 14 }}>
2301
+ Renders normally once set up.
2302
+ </p>
2303
+ </DeviceStorageKeyGate>
2304
+ </ComponentCard>
2305
+
1934
2306
  {/* Spinner */}
1935
2307
  <ComponentCard
1936
2308
  name="Spinner"
@@ -2032,6 +2404,540 @@ font-weight: var(--sv-font-weight-bold); /* 700 */`}</Code>
2032
2404
  })()}
2033
2405
  </ComponentCard>
2034
2406
 
2407
+ {/* RadioGroup */}
2408
+ <ComponentCard
2409
+ name="RadioGroup"
2410
+ importLine="import { RadioGroup } from '@sovereignfs/ui';"
2411
+ usage="Single-select list of options. Renders real <input type=radio> elements sharing one name, so arrow-key navigation between options is native browser behavior, not hand-rolled."
2412
+ >
2413
+ {(() => {
2414
+ const [size, setSize] = useState('md');
2415
+ return (
2416
+ <RadioGroup
2417
+ items={[
2418
+ { label: 'Small', value: 'sm' },
2419
+ { label: 'Medium', value: 'md' },
2420
+ { label: 'Large', value: 'lg' },
2421
+ ]}
2422
+ value={size}
2423
+ onChange={setSize}
2424
+ aria-label="Size"
2425
+ />
2426
+ );
2427
+ })()}
2428
+ </ComponentCard>
2429
+
2430
+ {/* Slider */}
2431
+ <ComponentCard
2432
+ name="Slider"
2433
+ importLine="import { Slider } from '@sovereignfs/ui';"
2434
+ usage="Single-thumb range input. A native <input type=range> under custom styling — arrow keys, Home/End, and touch-drag all come from the browser."
2435
+ >
2436
+ {(() => {
2437
+ const [value, setValue] = useState(50);
2438
+ return (
2439
+ <div style={{ width: 240 }}>
2440
+ <Slider value={value} onChange={setValue} min={0} max={100} label="Volume" />
2441
+ </div>
2442
+ );
2443
+ })()}
2444
+ </ComponentCard>
2445
+
2446
+ {/* Progress */}
2447
+ <ComponentCard
2448
+ name="Progress"
2449
+ importLine="import { Progress } from '@sovereignfs/ui';"
2450
+ usage="Determinate progress bar. role=progressbar with aria-valuenow/min/max so screen readers announce the current value."
2451
+ >
2452
+ <div style={{ width: 240 }}>
2453
+ <Progress value={65} label="Upload progress" />
2454
+ </div>
2455
+ </ComponentCard>
2456
+
2457
+ {/* Table */}
2458
+ <ComponentCard
2459
+ name="Table"
2460
+ importLine="import { Table, TableHead, TableBody, TableRow, TableHeaderCell, TableCell } from '@sovereignfs/ui';"
2461
+ usage="Thin, styled wrappers around the native table elements. Not a data grid — no sort/filter/virtualization. Scrolls horizontally (masked-edge fade) at any viewport size."
2462
+ >
2463
+ <Table>
2464
+ <TableHead>
2465
+ <TableRow>
2466
+ <TableHeaderCell>Plugin</TableHeaderCell>
2467
+ <TableHeaderCell>Status</TableHeaderCell>
2468
+ </TableRow>
2469
+ </TableHead>
2470
+ <TableBody>
2471
+ <TableRow>
2472
+ <TableCell>sovereign-tasks</TableCell>
2473
+ <TableCell>Active</TableCell>
2474
+ </TableRow>
2475
+ <TableRow>
2476
+ <TableCell>sovereign-shopper</TableCell>
2477
+ <TableCell>Disabled</TableCell>
2478
+ </TableRow>
2479
+ </TableBody>
2480
+ </Table>
2481
+ </ComponentCard>
2482
+
2483
+ {/* Alert */}
2484
+ <ComponentCard
2485
+ name="Alert"
2486
+ importLine="import { Alert } from '@sovereignfs/ui';"
2487
+ usage="Inline, non-dismissible banner. Distinct from Toast (transient) and SystemBanner (instance-wide) — for form-level errors or explaining an empty/blocked state. Leading icon defaults per variant; pass icon to override or icon={false} to suppress."
2488
+ >
2489
+ <div style={{ display: 'flex', flexDirection: 'column', gap: 8 }}>
2490
+ <Alert variant="error" heading="Something went wrong">
2491
+ We couldn’t save your changes. Check your connection and try again.
2492
+ </Alert>
2493
+ <Alert variant="success">Invite sent.</Alert>
2494
+ <Alert variant="neutral">This project has no members yet.</Alert>
2495
+ </div>
2496
+ </ComponentCard>
2497
+
2498
+ {/* Breadcrumb */}
2499
+ <ComponentCard
2500
+ name="Breadcrumb"
2501
+ importLine="import { Breadcrumb } from '@sovereignfs/ui';"
2502
+ usage="Link trail. The last item (no href) renders as plain text with aria-current=page. Pass renderLink to keep navigation client-side inside overlay-shell plugins."
2503
+ >
2504
+ <Breadcrumb
2505
+ items={[
2506
+ { label: 'Console', href: '/console' },
2507
+ { label: 'Plugins', href: '/console/plugins' },
2508
+ { label: 'sovereign-tasks' },
2509
+ ]}
2510
+ />
2511
+ </ComponentCard>
2512
+
2513
+ {/* LegalLinks */}
2514
+ <ComponentCard
2515
+ name="LegalLinks"
2516
+ importLine="import { LegalLinks } from '@sovereignfs/ui';"
2517
+ usage="Privacy Policy / Terms of Service link row for standalone auth pages (login, register, forgot/reset password). Pass renderLink to keep navigation client-side if ever mounted inside an SPA-like shell."
2518
+ >
2519
+ <LegalLinks />
2520
+ </ComponentCard>
2521
+
2522
+ {/* Markdown */}
2523
+ <ComponentCard
2524
+ name="Markdown"
2525
+ importLine="import { Markdown } from '@sovereignfs/ui';"
2526
+ usage="Renders a constrained markdown subset (headings, paragraphs, bold/italic/code, links, lists, blockquotes) as Typography-based React elements — no HTML string injection. Built for first-party long-form content pages (privacy policy, terms of service), not arbitrary or user-supplied markdown."
2527
+ >
2528
+ <Markdown
2529
+ content={
2530
+ '## Heading\n\nA paragraph with **bold** and a [link](/privacy).\n\n- One\n- Two'
2531
+ }
2532
+ />
2533
+ </ComponentCard>
2534
+
2535
+ {/* Pagination */}
2536
+ <ComponentCard
2537
+ name="Pagination"
2538
+ importLine="import { Pagination } from '@sovereignfs/ui';"
2539
+ usage="Page-number / prev-next control. Shows first, last, and pages near the current one, with an ellipsis for gaps once the page count grows."
2540
+ >
2541
+ {(() => {
2542
+ const [page, setPage] = useState(4);
2543
+ return <Pagination page={page} totalPages={12} onChange={setPage} />;
2544
+ })()}
2545
+ </ComponentCard>
2546
+
2547
+ {/* Kbd */}
2548
+ <ComponentCard
2549
+ name="Kbd"
2550
+ importLine="import { Kbd } from '@sovereignfs/ui';"
2551
+ usage="Inline keyboard-key styling. Renders a real <kbd> element."
2552
+ >
2553
+ <span style={{ display: 'inline-flex', alignItems: 'center', gap: 4 }}>
2554
+ <Kbd>Ctrl</Kbd>
2555
+ <span>+</span>
2556
+ <Kbd>K</Kbd>
2557
+ </span>
2558
+ </ComponentCard>
2559
+
2560
+ {/* Collapsible */}
2561
+ <ComponentCard
2562
+ name="Collapsible"
2563
+ importLine="import { Collapsible } from '@sovereignfs/ui';"
2564
+ usage="Single expand/collapse primitive. Independently useful (e.g. a 'show more' toggle), and composed internally by Accordion."
2565
+ >
2566
+ {(() => {
2567
+ const [open, setOpen] = useState(false);
2568
+ return (
2569
+ <Collapsible open={open} onOpenChange={setOpen} trigger="Advanced settings">
2570
+ These settings are rarely needed — change them only if you know what you're
2571
+ doing.
2572
+ </Collapsible>
2573
+ );
2574
+ })()}
2575
+ </ComponentCard>
2576
+
2577
+ {/* Accordion */}
2578
+ <ComponentCard
2579
+ name="Accordion"
2580
+ importLine="import { Accordion } from '@sovereignfs/ui';"
2581
+ usage="One or more Collapsible sections. type=single closes other sections when one opens; type=multiple allows any number open at once."
2582
+ >
2583
+ {(() => {
2584
+ const [openIds, setOpenIds] = useState<string[]>(['plugins']);
2585
+ return (
2586
+ <Accordion
2587
+ items={[
2588
+ {
2589
+ id: 'plugins',
2590
+ trigger: 'What plugins ship by default?',
2591
+ content:
2592
+ 'Only the platform plugins (Console, Launcher, Account) plus Sovereign Tasks.',
2593
+ },
2594
+ {
2595
+ id: 'hosting',
2596
+ trigger: 'Can I self-host this?',
2597
+ content:
2598
+ 'Yes — Sovereign is designed to be self-hosted via Docker Compose.',
2599
+ },
2600
+ ]}
2601
+ type="single"
2602
+ openIds={openIds}
2603
+ onOpenIdsChange={setOpenIds}
2604
+ />
2605
+ );
2606
+ })()}
2607
+ </ComponentCard>
2608
+
2609
+ {/* AspectRatio */}
2610
+ <ComponentCard
2611
+ name="AspectRatio"
2612
+ importLine="import { AspectRatio } from '@sovereignfs/ui';"
2613
+ usage="Constrains content (image, video, embed) to a fixed ratio."
2614
+ >
2615
+ <div style={{ width: 200 }}>
2616
+ <AspectRatio ratio={16 / 9}>
2617
+ <div
2618
+ style={{
2619
+ width: '100%',
2620
+ height: '100%',
2621
+ background: 'var(--sv-color-surface-sunken)',
2622
+ display: 'flex',
2623
+ alignItems: 'center',
2624
+ justifyContent: 'center',
2625
+ color: 'var(--sv-color-text-muted)',
2626
+ fontSize: 'var(--sv-font-size-xs)',
2627
+ }}
2628
+ >
2629
+ 16:9
2630
+ </div>
2631
+ </AspectRatio>
2632
+ </div>
2633
+ </ComponentCard>
2634
+
2635
+ {/* ButtonGroup */}
2636
+ <ComponentCard
2637
+ name="ButtonGroup"
2638
+ importLine="import { ButtonGroup } from '@sovereignfs/ui';"
2639
+ usage="Visually joins adjacent Buttons into one control (shared border, connected corners)."
2640
+ >
2641
+ <ButtonGroup aria-label="View">
2642
+ <Button variant="secondary" size="sm">
2643
+ Day
2644
+ </Button>
2645
+ <Button variant="secondary" size="sm">
2646
+ Week
2647
+ </Button>
2648
+ <Button variant="secondary" size="sm">
2649
+ Month
2650
+ </Button>
2651
+ </ButtonGroup>
2652
+ </ComponentCard>
2653
+
2654
+ {/* Item */}
2655
+ <ComponentCard
2656
+ name="Item"
2657
+ importLine="import { Item } from '@sovereignfs/ui';"
2658
+ usage="Generic row primitive: leading slot, title + optional description, trailing slot. For settings rows and list rows that don't fit Menu's own item shape."
2659
+ >
2660
+ <Item
2661
+ title="Notifications"
2662
+ description="Manage email and push alerts"
2663
+ leading={<Icon name="bell" aria-hidden />}
2664
+ trailing={<Icon name="chevron-right" size="sm" aria-hidden />}
2665
+ onClick={() => {}}
2666
+ />
2667
+ </ComponentCard>
2668
+
2669
+ {/* Label */}
2670
+ <ComponentCard
2671
+ name="Label"
2672
+ importLine="import { Label } from '@sovereignfs/ui';"
2673
+ usage="Standalone accessible form label, independent of FormField. Use FormField when a control needs hint/error text too."
2674
+ >
2675
+ <div style={{ display: 'flex', flexDirection: 'column', gap: 4 }}>
2676
+ <Label htmlFor="label-demo-input">Email address</Label>
2677
+ <input
2678
+ id="label-demo-input"
2679
+ type="email"
2680
+ placeholder="you@example.com"
2681
+ style={{
2682
+ padding: 'var(--sv-space-2) var(--sv-space-3)',
2683
+ border: '1px solid var(--sv-color-border-strong)',
2684
+ borderRadius: 'var(--sv-radius-md)',
2685
+ fontFamily: 'var(--sv-font-family)',
2686
+ fontSize: 'var(--sv-font-size-sm)',
2687
+ }}
2688
+ />
2689
+ </div>
2690
+ </ComponentCard>
2691
+
2692
+ {/* ScrollArea */}
2693
+ <ComponentCard
2694
+ name="ScrollArea"
2695
+ importLine="import { ScrollArea } from '@sovereignfs/ui';"
2696
+ usage="Styled scrollable container — thin, token-colored scrollbar instead of the OS default. Native overflow scrolling underneath."
2697
+ >
2698
+ <ScrollArea maxHeight={120}>
2699
+ <div style={{ display: 'flex', flexDirection: 'column', gap: 8, padding: 4 }}>
2700
+ {Array.from({ length: 10 }, (_, i) => (
2701
+ <div key={i} style={{ fontSize: 13 }}>
2702
+ Row {i + 1}
2703
+ </div>
2704
+ ))}
2705
+ </div>
2706
+ </ScrollArea>
2707
+ </ComponentCard>
2708
+
2709
+ {/* Typography */}
2710
+ <ComponentCard
2711
+ name="Typography"
2712
+ importLine="import { Typography } from '@sovereignfs/ui';"
2713
+ usage="Text bound to the design system's font-size/weight scale. Pass `as` to override the rendered tag while keeping the variant's visual style."
2714
+ >
2715
+ <div style={{ display: 'flex', flexDirection: 'column', gap: 8 }}>
2716
+ <Typography variant="h3">Heading 3</Typography>
2717
+ <Typography variant="body">Body copy for descriptions.</Typography>
2718
+ <Typography variant="caption">Caption — secondary text.</Typography>
2719
+ </div>
2720
+ </ComponentCard>
2721
+
2722
+ {/* Marker */}
2723
+ <ComponentCard
2724
+ name="Marker"
2725
+ importLine="import { Marker } from '@sovereignfs/ui';"
2726
+ usage="Inline citation/reference marker — attributes part of an assistant answer to a source (Sovereign Harness 'source trace', RFC 0040)."
2727
+ >
2728
+ <p style={{ fontSize: 14, margin: 0 }}>
2729
+ Your next task is due Thursday
2730
+ <Marker index={1} label="Source: Tasks" onClick={() => {}} />.
2731
+ </p>
2732
+ </ComponentCard>
2733
+
2734
+ {/* Message */}
2735
+ <ComponentCard
2736
+ name="Message"
2737
+ importLine="import { Message } from '@sovereignfs/ui';"
2738
+ usage="A single chat turn for the Sovereign Harness assistant. role is user/assistant/tool, matching harness_messages (RFC 0040). Content is caller-controlled ReactNode."
2739
+ >
2740
+ <div style={{ display: 'flex', flexDirection: 'column', gap: 8 }}>
2741
+ <Message sender="user">What's on my plate this week?</Message>
2742
+ <Message sender="assistant">You have 3 tasks due this week.</Message>
2743
+ </div>
2744
+ </ComponentCard>
2745
+
2746
+ {/* MessageScroller */}
2747
+ <ComponentCard
2748
+ name="MessageScroller"
2749
+ importLine="import { MessageScroller } from '@sovereignfs/ui';"
2750
+ usage="Auto-scrolling chat container. Scrolls to the newest message while the user is near the bottom; shows a 'New messages' button instead of yanking them down if they've scrolled up."
2751
+ >
2752
+ <div style={{ height: 160, border: '1px solid var(--sv-color-border)' }}>
2753
+ <MessageScroller>
2754
+ <Message sender="user">Hi there</Message>
2755
+ <Message sender="assistant">How can I help?</Message>
2756
+ </MessageScroller>
2757
+ </div>
2758
+ </ComponentCard>
2759
+
2760
+ {/* HoverCard */}
2761
+ <ComponentCard
2762
+ name="HoverCard"
2763
+ importLine="import { HoverCard } from '@sovereignfs/ui';"
2764
+ usage="Hover-triggered popover on desktop, tap-to-toggle on touch. Built on Popover for positioning — adds hover-intent timing and the touch fallback. Opens on keyboard focus too."
2765
+ >
2766
+ <HoverCard
2767
+ aria-label="User preview"
2768
+ trigger={
2769
+ <button
2770
+ type="button"
2771
+ style={{ background: 'none', border: 'none', padding: 0, cursor: 'pointer' }}
2772
+ >
2773
+ <Avatar name="Jane Smith" size="sm" />
2774
+ </button>
2775
+ }
2776
+ >
2777
+ <div style={{ padding: 'var(--sv-space-3)', fontSize: 14 }}>
2778
+ <div style={{ fontWeight: 600 }}>Jane Smith</div>
2779
+ <div style={{ color: 'var(--sv-color-text-muted)' }}>jane@example.com</div>
2780
+ </div>
2781
+ </HoverCard>
2782
+ </ComponentCard>
2783
+
2784
+ {/* ContextMenu */}
2785
+ <ComponentCard
2786
+ name="ContextMenu"
2787
+ importLine="import { ContextMenu } from '@sovereignfs/ui';"
2788
+ usage="Right-click menu on desktop, long-press on touch. Desktop positioning reuses Popover's collision detection anchored to the click point; touch opens the same items in a Drawer."
2789
+ >
2790
+ <ContextMenu
2791
+ aria-label="Row actions"
2792
+ items={[
2793
+ { label: 'Rename', onSelect: () => {} },
2794
+ { label: 'Duplicate', onSelect: () => {} },
2795
+ { type: 'separator' },
2796
+ { label: 'Delete', destructive: true, onSelect: () => {} },
2797
+ ]}
2798
+ >
2799
+ <div
2800
+ style={{
2801
+ padding: 'var(--sv-space-4)',
2802
+ border: '1px dashed var(--sv-color-border-strong)',
2803
+ borderRadius: 'var(--sv-radius-md)',
2804
+ textAlign: 'center',
2805
+ fontSize: 14,
2806
+ color: 'var(--sv-color-text-muted)',
2807
+ }}
2808
+ >
2809
+ Right-click here
2810
+ </div>
2811
+ </ContextMenu>
2812
+ </ComponentCard>
2813
+
2814
+ {/* NavigationMenu */}
2815
+ <ComponentCard
2816
+ name="NavigationMenu"
2817
+ importLine="import { NavigationMenu } from '@sovereignfs/ui';"
2818
+ usage="Top-level nav bar where some items open a flyout panel. Desktop-oriented — a hover-triggered flyout bar has no mobile equivalent."
2819
+ >
2820
+ <NavigationMenu
2821
+ aria-label="Main"
2822
+ items={[
2823
+ { label: 'Home', href: '/home' },
2824
+ {
2825
+ label: 'Products',
2826
+ content: (
2827
+ <div style={{ padding: 'var(--sv-space-3)', fontSize: 14 }}>
2828
+ Tasks · Ledger · Shopper
2829
+ </div>
2830
+ ),
2831
+ },
2832
+ ]}
2833
+ />
2834
+ </ComponentCard>
2835
+
2836
+ {/* Menubar */}
2837
+ <ComponentCard
2838
+ name="Menubar"
2839
+ importLine="import { Menubar } from '@sovereignfs/ui';"
2840
+ usage="Desktop app-style menu bar (File/Edit/View...). A thin composition of NavigationMenu with MenuEntries as each item's flyout content."
2841
+ >
2842
+ <Menubar
2843
+ menus={[
2844
+ {
2845
+ label: 'File',
2846
+ items: [
2847
+ { label: 'New conversation', onSelect: () => {} },
2848
+ { label: 'Export', onSelect: () => {} },
2849
+ ],
2850
+ },
2851
+ {
2852
+ label: 'Edit',
2853
+ items: [
2854
+ { label: 'Undo', onSelect: () => {} },
2855
+ { label: 'Redo', onSelect: () => {} },
2856
+ ],
2857
+ },
2858
+ ]}
2859
+ />
2860
+ </ComponentCard>
2861
+
2862
+ {/* Command */}
2863
+ <ComponentCard
2864
+ name="Command"
2865
+ importLine="import { Command } from '@sovereignfs/ui';"
2866
+ usage="⌘K-style command palette. Opened via Dialog for the modal shell — adds the search input, substring filtering, and arrow-key/Enter selection. Controlled: consumer owns the open state."
2867
+ >
2868
+ <CommandDemo />
2869
+ </ComponentCard>
2870
+
2871
+ {/* Resizable */}
2872
+ <ComponentCard
2873
+ name="Resizable"
2874
+ importLine="import { ResizablePanelGroup, ResizablePanel, ResizableHandle } from '@sovereignfs/ui';"
2875
+ usage="A row or column of resizable panes. Each handle resizes only its two immediate neighbor panels. Desktop-oriented — panels render at their default sizes on touch."
2876
+ >
2877
+ <div style={{ width: '100%', height: 160 }}>
2878
+ <ResizablePanelGroup direction="horizontal">
2879
+ <ResizablePanel defaultSize={30} minSize={15} maxSize={50}>
2880
+ <div
2881
+ style={{
2882
+ display: 'flex',
2883
+ alignItems: 'center',
2884
+ justifyContent: 'center',
2885
+ height: '100%',
2886
+ fontSize: 14,
2887
+ color: 'var(--sv-color-text-muted)',
2888
+ }}
2889
+ >
2890
+ Sidebar
2891
+ </div>
2892
+ </ResizablePanel>
2893
+ <ResizableHandle aria-label="Resize sidebar" />
2894
+ <ResizablePanel defaultSize={70}>
2895
+ <div
2896
+ style={{
2897
+ display: 'flex',
2898
+ alignItems: 'center',
2899
+ justifyContent: 'center',
2900
+ height: '100%',
2901
+ fontSize: 14,
2902
+ color: 'var(--sv-color-text-muted)',
2903
+ }}
2904
+ >
2905
+ Content
2906
+ </div>
2907
+ </ResizablePanel>
2908
+ </ResizablePanelGroup>
2909
+ </div>
2910
+ </ComponentCard>
2911
+
2912
+ {/* DataTable */}
2913
+ <ComponentCard
2914
+ name="DataTable"
2915
+ importLine="import { DataTable } from '@sovereignfs/ui';"
2916
+ usage="Sortable table built on Table. Column-driven: pass columns + data instead of composing TableRow/TableCell by hand. Clicking a sortable header cycles ascending → descending → unsorted."
2917
+ >
2918
+ <DataTable
2919
+ columns={[
2920
+ { key: 'name', header: 'Plugin', sortable: true },
2921
+ { key: 'installs', header: 'Installs', sortable: true, align: 'end' },
2922
+ ]}
2923
+ data={[
2924
+ { id: 'tasks', name: 'sovereign-tasks', installs: 482 },
2925
+ { id: 'ledger', name: 'sovereign-ledger', installs: 219 },
2926
+ { id: 'shopper', name: 'sovereign-shopper', installs: 37 },
2927
+ ]}
2928
+ getRowKey={(row) => row.id}
2929
+ />
2930
+ </ComponentCard>
2931
+
2932
+ {/* Combobox */}
2933
+ <ComponentCard
2934
+ name="Combobox"
2935
+ importLine="import { Combobox } from '@sovereignfs/ui';"
2936
+ usage="A searchable single-select: Popover on desktop, a bottom-sheet Drawer on mobile. For a short fixed list where search adds no value, use Select's native <select> instead."
2937
+ >
2938
+ <ComboboxDemo />
2939
+ </ComponentCard>
2940
+
2035
2941
  {/* DragHandleRow */}
2036
2942
  <ComponentCard
2037
2943
  name="DragHandleRow"
@@ -2064,6 +2970,33 @@ font-weight: var(--sv-font-weight-bold); /* 700 */`}</Code>
2064
2970
  >
2065
2971
  <FileDropzoneDemo />
2066
2972
  </ComponentCard>
2973
+
2974
+ {/* ResponsiveSurface */}
2975
+ <ComponentCard
2976
+ name="ResponsiveSurface"
2977
+ importLine="import { ResponsiveSurface } from '@sovereignfs/ui';"
2978
+ usage="Renders an entirely different component tree below a breakpoint, not a CSS squeeze of the same one — only the active side is ever mounted. See also the useResponsiveLayout hook for non-JSX values."
2979
+ >
2980
+ <ResponsiveSurfaceDemo />
2981
+ </ComponentCard>
2982
+
2983
+ {/* SwipableMobileCarousel */}
2984
+ <ComponentCard
2985
+ name="SwipableMobileCarousel"
2986
+ importLine="import { SwipableMobileCarousel, SwipableMobileCarouselSlide, SwipableMobileCarouselSlideHeader, SwipableMobileCarouselSlideBody } from '@sovereignfs/ui';"
2987
+ usage="Compound component for swiping between full-width slides. A Slide's Header/Footer always render; only Body's own loading prop gates its content, so a title can show immediately while the body's own fetch is still in flight. Owns mount-window/reorder mechanics only — never put cross-slide aggregation or a detail overlay inside a Slide."
2988
+ >
2989
+ <SwipableMobileCarouselDemo />
2990
+ </ComponentCard>
2991
+
2992
+ {/* SwipableMobileCarouselDots */}
2993
+ <ComponentCard
2994
+ name="SwipableMobileCarouselDots"
2995
+ importLine="import { SwipableMobileCarouselDots } from '@sovereignfs/ui';"
2996
+ usage="A real, tappable, labeled slide indicator (role=tablist/tab) — standalone and reusable outside SwipableMobileCarousel too, and its default renderIndicator."
2997
+ >
2998
+ <SwipableMobileCarouselDotsDemo />
2999
+ </ComponentCard>
2067
3000
  </div>
2068
3001
  </section>
2069
3002