@sovereignfs/ui 0.54.0 → 0.89.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.
Files changed (473) hide show
  1. package/LICENSE +373 -661
  2. package/NOTICE +8 -0
  3. package/README.md +4 -6
  4. package/dist/Accordion.module.css +1 -1
  5. package/dist/Alert.module.css +1 -1
  6. package/dist/AppsLauncher.module.css +102 -0
  7. package/dist/Avatar.module.css +1 -1
  8. package/dist/Badge.module.css +30 -7
  9. package/dist/BalanceChip.module.css +1 -1
  10. package/dist/Button.module.css +23 -2
  11. package/dist/Card.module.css +7 -1
  12. package/dist/CardTile.module.css +187 -0
  13. package/dist/CheckableListRow.module.css +8 -0
  14. package/dist/Checkbox.module.css +8 -0
  15. package/dist/CodeTextarea.module.css +7 -1
  16. package/dist/Collapsible.module.css +1 -1
  17. package/dist/ColorPicker.module.css +138 -0
  18. package/dist/Combobox.module.css +8 -2
  19. package/dist/Command.module.css +7 -1
  20. package/dist/ConfirmDialog.module.css +8 -2
  21. package/dist/ConsentPrompt.module.css +21 -0
  22. package/dist/DatePicker.module.css +7 -1
  23. package/dist/Dialog.module.css +130 -21
  24. package/dist/DragHandleRow.module.css +6 -0
  25. package/dist/Drawer.module.css +36 -7
  26. package/dist/FileDropzone.module.css +25 -1
  27. package/dist/Header.module.css +68 -0
  28. package/dist/HeaderFooterLayout.module.css +21 -0
  29. package/dist/IconPicker.module.css +15 -5
  30. package/dist/Input.module.css +7 -1
  31. package/dist/Kbd.module.css +1 -1
  32. package/dist/LegalLinks.module.css +9 -2
  33. package/dist/Markdown.module.css +29 -0
  34. package/dist/Menu.module.css +1 -0
  35. package/dist/Message.module.css +10 -1
  36. package/dist/MessageScroller.module.css +12 -1
  37. package/dist/MobileAppsDrawer.module.css +19 -14
  38. package/dist/MobileFooter.module.css +7 -1
  39. package/dist/MobileHeader.module.css +14 -1
  40. package/dist/NavList.module.css +139 -0
  41. package/dist/NavTabs.module.css +8 -2
  42. package/dist/NotificationsPanel.module.css +268 -0
  43. package/dist/OverlayHeader.module.css +1 -1
  44. package/dist/PageContainer.module.css +62 -0
  45. package/dist/PageHeader.module.css +54 -0
  46. package/dist/PageLayout.module.css +51 -0
  47. package/dist/Popover.module.css +1 -1
  48. package/dist/QrCode.module.css +17 -0
  49. package/dist/QuantityStepper.module.css +5 -3
  50. package/dist/RadioGroup.module.css +6 -0
  51. package/dist/Resizable.module.css +3 -3
  52. package/dist/RootLayout.module.css +12 -0
  53. package/dist/SegmentedControl.module.css +7 -1
  54. package/dist/Select.module.css +7 -1
  55. package/dist/Sheet.module.css +20 -2
  56. package/dist/SplitPane.module.css +5 -5
  57. package/dist/StatusBadge.module.css +1 -1
  58. package/dist/StepStrip.module.css +55 -0
  59. package/dist/SuggestionInput.module.css +14 -3
  60. package/dist/SwipableMobileCarousel.module.css +12 -3
  61. package/dist/SwipableMobileCarouselDots.module.css +21 -0
  62. package/dist/SwipeStack.module.css +101 -0
  63. package/dist/SystemBanner.module.css +16 -0
  64. package/dist/Table.module.css +2 -2
  65. package/dist/Tabs.module.css +7 -1
  66. package/dist/TagInput.module.css +8 -2
  67. package/dist/Textarea.module.css +15 -1
  68. package/dist/ThreeColumnLayout.module.css +35 -0
  69. package/dist/Toast.module.css +67 -3
  70. package/dist/Toggle.module.css +7 -0
  71. package/dist/Tooltip.module.css +7 -1
  72. package/dist/UserMenu.module.css +95 -0
  73. package/dist/fonts/OFL.txt +93 -0
  74. package/dist/fonts/hanken-grotesk-cyrillic-ext-wght-normal.woff2 +0 -0
  75. package/dist/fonts/hanken-grotesk-latin-ext-wght-normal.woff2 +0 -0
  76. package/dist/fonts/hanken-grotesk-latin-wght-normal.woff2 +0 -0
  77. package/dist/fonts/hanken-grotesk-vietnamese-wght-normal.woff2 +0 -0
  78. package/dist/index.d.ts +1156 -76
  79. package/dist/index.js +4223 -1513
  80. package/dist/tokens/fonts.css +53 -0
  81. package/dist/tokens/primitives.css +23 -12
  82. package/dist/tokens/semantic.css +32 -0
  83. package/dist/tokens/theme-presets.css +33 -0
  84. package/dist/tokens.css +2 -1
  85. package/package.json +46 -19
  86. package/src/__tests__/motion.test.tsx +0 -105
  87. package/src/components/Accordion/Accordion.module.css +0 -3
  88. package/src/components/Accordion/Accordion.stories.tsx +0 -110
  89. package/src/components/Accordion/Accordion.tsx +0 -53
  90. package/src/components/Accordion/__tests__/Accordion.test.tsx +0 -61
  91. package/src/components/Alert/Alert.module.css +0 -62
  92. package/src/components/Alert/Alert.stories.tsx +0 -96
  93. package/src/components/Alert/Alert.tsx +0 -58
  94. package/src/components/Alert/__tests__/Alert.test.tsx +0 -75
  95. package/src/components/AspectRatio/AspectRatio.module.css +0 -15
  96. package/src/components/AspectRatio/AspectRatio.stories.tsx +0 -78
  97. package/src/components/AspectRatio/AspectRatio.tsx +0 -21
  98. package/src/components/AspectRatio/__tests__/AspectRatio.test.tsx +0 -27
  99. package/src/components/Avatar/Avatar.module.css +0 -38
  100. package/src/components/Avatar/Avatar.tsx +0 -42
  101. package/src/components/Badge/Badge.module.css +0 -154
  102. package/src/components/Badge/Badge.stories.tsx +0 -226
  103. package/src/components/Badge/Badge.tsx +0 -81
  104. package/src/components/Badge/__tests__/Badge.test.tsx +0 -88
  105. package/src/components/BalanceChip/BalanceChip.module.css +0 -32
  106. package/src/components/BalanceChip/BalanceChip.stories.tsx +0 -45
  107. package/src/components/BalanceChip/BalanceChip.tsx +0 -33
  108. package/src/components/Breadcrumb/Breadcrumb.module.css +0 -49
  109. package/src/components/Breadcrumb/Breadcrumb.stories.tsx +0 -47
  110. package/src/components/Breadcrumb/Breadcrumb.tsx +0 -57
  111. package/src/components/Breadcrumb/__tests__/Breadcrumb.test.tsx +0 -52
  112. package/src/components/Button/Button.module.css +0 -153
  113. package/src/components/Button/Button.stories.tsx +0 -83
  114. package/src/components/Button/Button.tsx +0 -52
  115. package/src/components/Button/__tests__/Button.test.tsx +0 -66
  116. package/src/components/ButtonGroup/ButtonGroup.module.css +0 -29
  117. package/src/components/ButtonGroup/ButtonGroup.stories.tsx +0 -57
  118. package/src/components/ButtonGroup/ButtonGroup.tsx +0 -27
  119. package/src/components/ButtonGroup/__tests__/ButtonGroup.test.tsx +0 -29
  120. package/src/components/Calendar/Calendar.module.css +0 -126
  121. package/src/components/Calendar/Calendar.tsx +0 -213
  122. package/src/components/Calendar/__tests__/Calendar.test.tsx +0 -98
  123. package/src/components/Calendar/dateUtils.ts +0 -97
  124. package/src/components/Card/Card.module.css +0 -42
  125. package/src/components/Card/Card.tsx +0 -34
  126. package/src/components/CheckableListRow/CheckableListRow.module.css +0 -95
  127. package/src/components/CheckableListRow/CheckableListRow.stories.tsx +0 -99
  128. package/src/components/CheckableListRow/CheckableListRow.tsx +0 -89
  129. package/src/components/Checkbox/Checkbox.module.css +0 -111
  130. package/src/components/Checkbox/Checkbox.tsx +0 -78
  131. package/src/components/CodeTextarea/CodeTextarea.module.css +0 -53
  132. package/src/components/CodeTextarea/CodeTextarea.stories.tsx +0 -77
  133. package/src/components/CodeTextarea/CodeTextarea.tsx +0 -36
  134. package/src/components/Collapsible/Collapsible.module.css +0 -70
  135. package/src/components/Collapsible/Collapsible.stories.tsx +0 -62
  136. package/src/components/Collapsible/Collapsible.tsx +0 -66
  137. package/src/components/Collapsible/__tests__/Collapsible.test.tsx +0 -71
  138. package/src/components/Combobox/Combobox.module.css +0 -122
  139. package/src/components/Combobox/Combobox.stories.tsx +0 -79
  140. package/src/components/Combobox/Combobox.tsx +0 -186
  141. package/src/components/Combobox/__tests__/Combobox.test.tsx +0 -158
  142. package/src/components/Command/Command.module.css +0 -80
  143. package/src/components/Command/Command.stories.tsx +0 -104
  144. package/src/components/Command/Command.tsx +0 -168
  145. package/src/components/Command/__tests__/Command.test.tsx +0 -162
  146. package/src/components/ConfirmDialog/ConfirmDialog.module.css +0 -113
  147. package/src/components/ConfirmDialog/ConfirmDialog.tsx +0 -124
  148. package/src/components/ConfirmDialog/__tests__/ConfirmDialog.test.tsx +0 -146
  149. package/src/components/ContextMenu/ContextMenu.stories.tsx +0 -81
  150. package/src/components/ContextMenu/ContextMenu.tsx +0 -100
  151. package/src/components/ContextMenu/__tests__/ContextMenu.test.tsx +0 -87
  152. package/src/components/CurrencyInput/CurrencyInput.stories.tsx +0 -50
  153. package/src/components/CurrencyInput/CurrencyInput.tsx +0 -60
  154. package/src/components/DataTable/DataTable.module.css +0 -28
  155. package/src/components/DataTable/DataTable.stories.tsx +0 -74
  156. package/src/components/DataTable/DataTable.tsx +0 -154
  157. package/src/components/DataTable/__tests__/DataTable.test.tsx +0 -123
  158. package/src/components/DatePicker/DatePicker.module.css +0 -61
  159. package/src/components/DatePicker/DatePicker.tsx +0 -103
  160. package/src/components/DatePicker/__tests__/DatePicker.test.tsx +0 -89
  161. package/src/components/DeviceOnlyGate/DeviceOnlyGate.tsx +0 -58
  162. package/src/components/DeviceOnlyGate/__tests__/DeviceOnlyGate.test.tsx +0 -45
  163. package/src/components/DeviceStorageKeyGate/DeviceStorageKeyGate.tsx +0 -100
  164. package/src/components/DeviceStorageKeyGate/__tests__/DeviceStorageKeyGate.test.tsx +0 -119
  165. package/src/components/Dialog/Dialog.module.css +0 -226
  166. package/src/components/Dialog/Dialog.stories.tsx +0 -147
  167. package/src/components/Dialog/Dialog.tsx +0 -160
  168. package/src/components/Dialog/__tests__/Dialog.test.tsx +0 -137
  169. package/src/components/DragHandleRow/DragHandleRow.module.css +0 -57
  170. package/src/components/DragHandleRow/DragHandleRow.tsx +0 -61
  171. package/src/components/Drawer/Drawer.module.css +0 -92
  172. package/src/components/Drawer/Drawer.stories.tsx +0 -132
  173. package/src/components/Drawer/Drawer.tsx +0 -176
  174. package/src/components/Drawer/__tests__/Drawer.test.tsx +0 -199
  175. package/src/components/EmptyState/EmptyState.module.css +0 -27
  176. package/src/components/EmptyState/EmptyState.tsx +0 -27
  177. package/src/components/FileDropzone/FileDropzone.module.css +0 -63
  178. package/src/components/FileDropzone/FileDropzone.tsx +0 -109
  179. package/src/components/FileDropzone/__tests__/FileDropzone.test.tsx +0 -67
  180. package/src/components/FormField/FormField.module.css +0 -33
  181. package/src/components/FormField/FormField.tsx +0 -78
  182. package/src/components/FormField/__tests__/FormField.test.tsx +0 -83
  183. package/src/components/HoverCard/HoverCard.module.css +0 -3
  184. package/src/components/HoverCard/HoverCard.stories.tsx +0 -66
  185. package/src/components/HoverCard/HoverCard.tsx +0 -111
  186. package/src/components/HoverCard/__tests__/HoverCard.test.tsx +0 -99
  187. package/src/components/Icon/Icon.module.css +0 -27
  188. package/src/components/Icon/Icon.stories.tsx +0 -95
  189. package/src/components/Icon/Icon.tsx +0 -43
  190. package/src/components/Icon/__tests__/Icon.test.tsx +0 -40
  191. package/src/components/Icon/icons/activity.tsx +0 -20
  192. package/src/components/Icon/icons/alert-triangle.tsx +0 -22
  193. package/src/components/Icon/icons/apple.tsx +0 -21
  194. package/src/components/Icon/icons/banana.tsx +0 -21
  195. package/src/components/Icon/icons/beef.tsx +0 -22
  196. package/src/components/Icon/icons/beer.tsx +0 -24
  197. package/src/components/Icon/icons/bell.tsx +0 -21
  198. package/src/components/Icon/icons/calendar.tsx +0 -23
  199. package/src/components/Icon/icons/candy.tsx +0 -24
  200. package/src/components/Icon/icons/carrot.tsx +0 -22
  201. package/src/components/Icon/icons/check.tsx +0 -20
  202. package/src/components/Icon/icons/chevron-down.tsx +0 -20
  203. package/src/components/Icon/icons/chevron-left.tsx +0 -20
  204. package/src/components/Icon/icons/chevron-right.tsx +0 -20
  205. package/src/components/Icon/icons/chevron-up.tsx +0 -20
  206. package/src/components/Icon/icons/circle-check.tsx +0 -21
  207. package/src/components/Icon/icons/circle-x.tsx +0 -22
  208. package/src/components/Icon/icons/coffee.tsx +0 -23
  209. package/src/components/Icon/icons/cookie.tsx +0 -25
  210. package/src/components/Icon/icons/croissant.tsx +0 -24
  211. package/src/components/Icon/icons/cup-soda.tsx +0 -23
  212. package/src/components/Icon/icons/drumstick.tsx +0 -21
  213. package/src/components/Icon/icons/egg.tsx +0 -20
  214. package/src/components/Icon/icons/ellipsis-vertical.tsx +0 -22
  215. package/src/components/Icon/icons/external-link.tsx +0 -22
  216. package/src/components/Icon/icons/eye-off.tsx +0 -23
  217. package/src/components/Icon/icons/eye.tsx +0 -21
  218. package/src/components/Icon/icons/file.tsx +0 -21
  219. package/src/components/Icon/icons/fish.tsx +0 -25
  220. package/src/components/Icon/icons/grid-2x2.tsx +0 -22
  221. package/src/components/Icon/icons/house.tsx +0 -21
  222. package/src/components/Icon/icons/index.ts +0 -234
  223. package/src/components/Icon/icons/info.tsx +0 -22
  224. package/src/components/Icon/icons/lock.tsx +0 -21
  225. package/src/components/Icon/icons/log-out.tsx +0 -22
  226. package/src/components/Icon/icons/mail.tsx +0 -21
  227. package/src/components/Icon/icons/menu.tsx +0 -22
  228. package/src/components/Icon/icons/milk.tsx +0 -22
  229. package/src/components/Icon/icons/package.tsx +0 -23
  230. package/src/components/Icon/icons/pencil.tsx +0 -21
  231. package/src/components/Icon/icons/pizza.tsx +0 -24
  232. package/src/components/Icon/icons/plus.tsx +0 -21
  233. package/src/components/Icon/icons/rotate-ccw.tsx +0 -21
  234. package/src/components/Icon/icons/salad.tsx +0 -24
  235. package/src/components/Icon/icons/search.tsx +0 -21
  236. package/src/components/Icon/icons/settings.tsx +0 -21
  237. package/src/components/Icon/icons/shield.tsx +0 -20
  238. package/src/components/Icon/icons/shopping-basket.tsx +0 -26
  239. package/src/components/Icon/icons/sliders-horizontal.tsx +0 -28
  240. package/src/components/Icon/icons/smartphone.tsx +0 -21
  241. package/src/components/Icon/icons/snowflake.tsx +0 -31
  242. package/src/components/Icon/icons/spray-can.tsx +0 -29
  243. package/src/components/Icon/icons/trash-2.tsx +0 -24
  244. package/src/components/Icon/icons/upload.tsx +0 -22
  245. package/src/components/Icon/icons/user.tsx +0 -21
  246. package/src/components/Icon/icons/wine.tsx +0 -23
  247. package/src/components/Icon/icons/x.tsx +0 -21
  248. package/src/components/IconPicker/IconPicker.module.css +0 -63
  249. package/src/components/IconPicker/IconPicker.stories.tsx +0 -84
  250. package/src/components/IconPicker/IconPicker.tsx +0 -90
  251. package/src/components/Input/Input.module.css +0 -52
  252. package/src/components/Input/Input.stories.tsx +0 -86
  253. package/src/components/Input/Input.tsx +0 -14
  254. package/src/components/Input/__tests__/Input.test.tsx +0 -22
  255. package/src/components/Item/Item.module.css +0 -70
  256. package/src/components/Item/Item.stories.tsx +0 -62
  257. package/src/components/Item/Item.tsx +0 -54
  258. package/src/components/Item/__tests__/Item.test.tsx +0 -39
  259. package/src/components/Kbd/Kbd.module.css +0 -14
  260. package/src/components/Kbd/Kbd.stories.tsx +0 -32
  261. package/src/components/Kbd/Kbd.tsx +0 -12
  262. package/src/components/Kbd/__tests__/Kbd.test.tsx +0 -14
  263. package/src/components/Label/Label.module.css +0 -11
  264. package/src/components/Label/Label.stories.tsx +0 -40
  265. package/src/components/Label/Label.tsx +0 -24
  266. package/src/components/Label/__tests__/Label.test.tsx +0 -32
  267. package/src/components/LegalLinks/LegalLinks.module.css +0 -24
  268. package/src/components/LegalLinks/LegalLinks.stories.tsx +0 -22
  269. package/src/components/LegalLinks/LegalLinks.tsx +0 -40
  270. package/src/components/Markdown/Markdown.module.css +0 -49
  271. package/src/components/Markdown/Markdown.stories.tsx +0 -30
  272. package/src/components/Markdown/Markdown.tsx +0 -168
  273. package/src/components/Marker/Marker.module.css +0 -33
  274. package/src/components/Marker/Marker.stories.tsx +0 -39
  275. package/src/components/Marker/Marker.tsx +0 -34
  276. package/src/components/Marker/__tests__/Marker.test.tsx +0 -23
  277. package/src/components/MemberMultiSelect/MemberMultiSelect.module.css +0 -27
  278. package/src/components/MemberMultiSelect/MemberMultiSelect.stories.tsx +0 -74
  279. package/src/components/MemberMultiSelect/MemberMultiSelect.tsx +0 -67
  280. package/src/components/Menu/Menu.module.css +0 -89
  281. package/src/components/Menu/Menu.tsx +0 -159
  282. package/src/components/Menu/__tests__/Menu.test.tsx +0 -197
  283. package/src/components/Menubar/Menubar.stories.tsx +0 -52
  284. package/src/components/Menubar/Menubar.tsx +0 -42
  285. package/src/components/Menubar/__tests__/Menubar.test.tsx +0 -45
  286. package/src/components/Message/Message.module.css +0 -99
  287. package/src/components/Message/Message.stories.tsx +0 -79
  288. package/src/components/Message/Message.tsx +0 -41
  289. package/src/components/Message/__tests__/Message.test.tsx +0 -46
  290. package/src/components/MessageScroller/MessageScroller.module.css +0 -54
  291. package/src/components/MessageScroller/MessageScroller.stories.tsx +0 -84
  292. package/src/components/MessageScroller/MessageScroller.tsx +0 -72
  293. package/src/components/MessageScroller/__tests__/MessageScroller.test.tsx +0 -105
  294. package/src/components/MobileAppsDrawer/MobileAppsDrawer.module.css +0 -81
  295. package/src/components/MobileAppsDrawer/MobileAppsDrawer.tsx +0 -78
  296. package/src/components/MobileAppsDrawer/__tests__/MobileAppsDrawer.test.tsx +0 -80
  297. package/src/components/MobileFooter/MobileFooter.module.css +0 -73
  298. package/src/components/MobileFooter/MobileFooter.stories.tsx +0 -47
  299. package/src/components/MobileFooter/MobileFooter.tsx +0 -109
  300. package/src/components/MobileFooter/__tests__/MobileFooter.test.tsx +0 -71
  301. package/src/components/MobileHeader/MobileHeader.module.css +0 -63
  302. package/src/components/MobileHeader/MobileHeader.stories.tsx +0 -57
  303. package/src/components/MobileHeader/MobileHeader.tsx +0 -77
  304. package/src/components/MobileHeader/__tests__/MobileHeader.test.tsx +0 -58
  305. package/src/components/NavTabs/NavTabs.module.css +0 -50
  306. package/src/components/NavTabs/NavTabs.tsx +0 -61
  307. package/src/components/NavigationMenu/NavigationMenu.module.css +0 -53
  308. package/src/components/NavigationMenu/NavigationMenu.stories.tsx +0 -58
  309. package/src/components/NavigationMenu/NavigationMenu.tsx +0 -125
  310. package/src/components/NavigationMenu/__tests__/NavigationMenu.test.tsx +0 -93
  311. package/src/components/OfflineGate/OfflineGate.tsx +0 -44
  312. package/src/components/OfflineGate/__tests__/OfflineGate.test.tsx +0 -66
  313. package/src/components/OverlayHeader/OverlayHeader.module.css +0 -98
  314. package/src/components/OverlayHeader/OverlayHeader.tsx +0 -62
  315. package/src/components/OverlayHeader/__tests__/OverlayHeader.test.tsx +0 -51
  316. package/src/components/PageContainer/PageContainer.module.css +0 -20
  317. package/src/components/PageContainer/PageContainer.tsx +0 -31
  318. package/src/components/PageContainer/__tests__/PageContainer.test.tsx +0 -37
  319. package/src/components/PageHeader/PageHeader.module.css +0 -32
  320. package/src/components/PageHeader/PageHeader.tsx +0 -35
  321. package/src/components/Pagination/Pagination.module.css +0 -98
  322. package/src/components/Pagination/Pagination.stories.tsx +0 -49
  323. package/src/components/Pagination/Pagination.tsx +0 -87
  324. package/src/components/Pagination/__tests__/Pagination.test.tsx +0 -56
  325. package/src/components/Popover/Popover.module.css +0 -74
  326. package/src/components/Popover/Popover.stories.tsx +0 -357
  327. package/src/components/Popover/Popover.tsx +0 -208
  328. package/src/components/Popover/__tests__/Popover.test.tsx +0 -98
  329. package/src/components/Progress/Progress.module.css +0 -20
  330. package/src/components/Progress/Progress.stories.tsx +0 -40
  331. package/src/components/Progress/Progress.tsx +0 -35
  332. package/src/components/Progress/__tests__/Progress.test.tsx +0 -31
  333. package/src/components/QuantityStepper/QuantityStepper.module.css +0 -67
  334. package/src/components/QuantityStepper/QuantityStepper.stories.tsx +0 -79
  335. package/src/components/QuantityStepper/QuantityStepper.tsx +0 -107
  336. package/src/components/RadioGroup/RadioGroup.module.css +0 -88
  337. package/src/components/RadioGroup/RadioGroup.stories.tsx +0 -69
  338. package/src/components/RadioGroup/RadioGroup.tsx +0 -99
  339. package/src/components/RadioGroup/__tests__/RadioGroup.test.tsx +0 -59
  340. package/src/components/Resizable/Resizable.module.css +0 -70
  341. package/src/components/Resizable/Resizable.stories.tsx +0 -82
  342. package/src/components/Resizable/Resizable.tsx +0 -313
  343. package/src/components/Resizable/__tests__/Resizable.test.tsx +0 -128
  344. package/src/components/ResponsiveSurface/ResponsiveSurface.tsx +0 -24
  345. package/src/components/ResponsiveSurface/__tests__/ResponsiveSurface.test.tsx +0 -73
  346. package/src/components/ScrollArea/ScrollArea.module.css +0 -20
  347. package/src/components/ScrollArea/ScrollArea.stories.tsx +0 -57
  348. package/src/components/ScrollArea/ScrollArea.tsx +0 -27
  349. package/src/components/ScrollArea/__tests__/ScrollArea.test.tsx +0 -39
  350. package/src/components/SegmentedControl/SegmentedControl.module.css +0 -55
  351. package/src/components/SegmentedControl/SegmentedControl.stories.tsx +0 -118
  352. package/src/components/SegmentedControl/SegmentedControl.tsx +0 -56
  353. package/src/components/SegmentedControl/__tests__/SegmentedControl.test.tsx +0 -63
  354. package/src/components/Select/Select.module.css +0 -82
  355. package/src/components/Select/Select.stories.tsx +0 -115
  356. package/src/components/Select/Select.tsx +0 -48
  357. package/src/components/Select/__tests__/Select.test.tsx +0 -59
  358. package/src/components/Sheet/Sheet.module.css +0 -55
  359. package/src/components/Sheet/Sheet.tsx +0 -109
  360. package/src/components/Sheet/__tests__/Sheet.test.tsx +0 -99
  361. package/src/components/Slider/Slider.module.css +0 -95
  362. package/src/components/Slider/Slider.stories.tsx +0 -64
  363. package/src/components/Slider/Slider.tsx +0 -77
  364. package/src/components/Slider/__tests__/Slider.test.tsx +0 -45
  365. package/src/components/Spinner/Spinner.module.css +0 -39
  366. package/src/components/Spinner/Spinner.tsx +0 -23
  367. package/src/components/SplitMethodSelector/SplitMethodSelector.stories.tsx +0 -33
  368. package/src/components/SplitMethodSelector/SplitMethodSelector.tsx +0 -43
  369. package/src/components/SplitPane/SplitPane.module.css +0 -85
  370. package/src/components/SplitPane/SplitPane.stories.tsx +0 -160
  371. package/src/components/SplitPane/SplitPane.tsx +0 -149
  372. package/src/components/SplitPane/__tests__/SplitPane.test.tsx +0 -51
  373. package/src/components/StatusBadge/StatusBadge.module.css +0 -59
  374. package/src/components/StatusBadge/StatusBadge.stories.tsx +0 -74
  375. package/src/components/StatusBadge/StatusBadge.tsx +0 -65
  376. package/src/components/SuggestionInput/SuggestionInput.module.css +0 -103
  377. package/src/components/SuggestionInput/SuggestionInput.stories.tsx +0 -144
  378. package/src/components/SuggestionInput/SuggestionInput.tsx +0 -207
  379. package/src/components/SwipableMobileCarousel/SwipableMobileCarousel.module.css +0 -55
  380. package/src/components/SwipableMobileCarousel/SwipableMobileCarousel.tsx +0 -227
  381. package/src/components/SwipableMobileCarousel/SwipableMobileCarouselSlide.tsx +0 -35
  382. package/src/components/SwipableMobileCarousel/SwipableMobileCarouselSlideBody.tsx +0 -42
  383. package/src/components/SwipableMobileCarousel/SwipableMobileCarouselSlideFooter.tsx +0 -18
  384. package/src/components/SwipableMobileCarousel/SwipableMobileCarouselSlideHeader.tsx +0 -21
  385. package/src/components/SwipableMobileCarousel/SwipableMobileCarouselSlideParts.module.css +0 -29
  386. package/src/components/SwipableMobileCarousel/__tests__/SwipableMobileCarousel.test.tsx +0 -133
  387. package/src/components/SwipableMobileCarousel/__tests__/SwipableMobileCarouselDevGuard.test.tsx +0 -61
  388. package/src/components/SwipableMobileCarousel/context.ts +0 -13
  389. package/src/components/SwipableMobileCarouselDots/SwipableMobileCarouselDots.module.css +0 -49
  390. package/src/components/SwipableMobileCarouselDots/SwipableMobileCarouselDots.tsx +0 -62
  391. package/src/components/SwipableMobileCarouselDots/__tests__/SwipableMobileCarouselDots.test.tsx +0 -63
  392. package/src/components/SystemBanner/SystemBanner.module.css +0 -69
  393. package/src/components/SystemBanner/SystemBanner.stories.tsx +0 -86
  394. package/src/components/SystemBanner/SystemBanner.tsx +0 -50
  395. package/src/components/SystemBanner/__tests__/SystemBanner.test.tsx +0 -50
  396. package/src/components/Table/Table.module.css +0 -45
  397. package/src/components/Table/Table.stories.tsx +0 -125
  398. package/src/components/Table/Table.tsx +0 -51
  399. package/src/components/Table/__tests__/Table.test.tsx +0 -50
  400. package/src/components/Tabs/Tabs.module.css +0 -56
  401. package/src/components/Tabs/Tabs.stories.tsx +0 -100
  402. package/src/components/Tabs/Tabs.tsx +0 -46
  403. package/src/components/Tabs/__tests__/Tabs.test.tsx +0 -45
  404. package/src/components/TagInput/TagInput.module.css +0 -141
  405. package/src/components/TagInput/TagInput.stories.tsx +0 -109
  406. package/src/components/TagInput/TagInput.tsx +0 -174
  407. package/src/components/TagInput/__tests__/TagInput.test.tsx +0 -69
  408. package/src/components/Textarea/Textarea.module.css +0 -39
  409. package/src/components/Textarea/Textarea.tsx +0 -15
  410. package/src/components/Textarea/__tests__/Textarea.test.tsx +0 -29
  411. package/src/components/Toast/Toast.module.css +0 -132
  412. package/src/components/Toast/Toast.stories.tsx +0 -191
  413. package/src/components/Toast/Toast.tsx +0 -139
  414. package/src/components/Toggle/Toggle.module.css +0 -57
  415. package/src/components/Toggle/Toggle.stories.tsx +0 -95
  416. package/src/components/Toggle/Toggle.tsx +0 -39
  417. package/src/components/Toggle/__tests__/Toggle.test.tsx +0 -47
  418. package/src/components/Tooltip/Tooltip.module.css +0 -56
  419. package/src/components/Tooltip/Tooltip.tsx +0 -30
  420. package/src/components/Typography/Typography.module.css +0 -47
  421. package/src/components/Typography/Typography.stories.tsx +0 -62
  422. package/src/components/Typography/Typography.tsx +0 -30
  423. package/src/components/Typography/__tests__/Typography.test.tsx +0 -34
  424. package/src/css-modules.d.ts +0 -4
  425. package/src/hooks/__tests__/useCarouselRouteSync.test.tsx +0 -126
  426. package/src/hooks/__tests__/useDoubleTap.test.tsx +0 -117
  427. package/src/hooks/__tests__/useIsMobile.test.tsx +0 -80
  428. package/src/hooks/__tests__/useIsOffline.test.tsx +0 -53
  429. package/src/hooks/__tests__/useLongPress.test.tsx +0 -158
  430. package/src/hooks/__tests__/useOfflineTileState.test.tsx +0 -69
  431. package/src/hooks/__tests__/useResponsiveLayout.test.tsx +0 -87
  432. package/src/hooks/__tests__/useSnapCarousel.test.tsx +0 -93
  433. package/src/hooks/__tests__/useSwipeReveal.test.tsx +0 -110
  434. package/src/hooks/index.ts +0 -24
  435. package/src/hooks/useCarouselRouteSync.ts +0 -92
  436. package/src/hooks/useCommitOnEnterOrBlur.ts +0 -39
  437. package/src/hooks/useDoubleTap.ts +0 -91
  438. package/src/hooks/useIsMobile.ts +0 -33
  439. package/src/hooks/useIsOffline.ts +0 -41
  440. package/src/hooks/useLongPress.ts +0 -185
  441. package/src/hooks/useOfflineTileState.ts +0 -45
  442. package/src/hooks/useResponsiveLayout.ts +0 -36
  443. package/src/hooks/useSnapCarousel.ts +0 -79
  444. package/src/hooks/useSwipeReveal.ts +0 -107
  445. package/src/index.ts +0 -251
  446. package/src/motion.ts +0 -78
  447. package/src/overlay-shell.ts +0 -94
  448. package/src/scroll-lock.ts +0 -22
  449. package/src/stories/Avatar.stories.tsx +0 -44
  450. package/src/stories/Card.stories.tsx +0 -35
  451. package/src/stories/Checkbox.stories.tsx +0 -45
  452. package/src/stories/DesignSystemOverview.stories.tsx +0 -3187
  453. package/src/stories/DeviceOnlyGate.stories.tsx +0 -35
  454. package/src/stories/DeviceStorageKeyGate.stories.tsx +0 -57
  455. package/src/stories/DragHandleRow.stories.tsx +0 -41
  456. package/src/stories/EmptyState.stories.tsx +0 -33
  457. package/src/stories/FileDropzone.stories.tsx +0 -69
  458. package/src/stories/FormField.stories.tsx +0 -47
  459. package/src/stories/InteractionHooks.stories.tsx +0 -925
  460. package/src/stories/MobilePatterns.stories.tsx +0 -1536
  461. package/src/stories/NavTabs.stories.tsx +0 -53
  462. package/src/stories/OfflineGate.stories.tsx +0 -42
  463. package/src/stories/PageContainer.stories.tsx +0 -37
  464. package/src/stories/PageHeader.stories.tsx +0 -42
  465. package/src/stories/ResponsiveSurface.stories.tsx +0 -47
  466. package/src/stories/Spinner.stories.tsx +0 -25
  467. package/src/stories/SwipableMobileCarousel.stories.tsx +0 -304
  468. package/src/stories/Textarea.stories.tsx +0 -51
  469. package/src/stories/TokenGallery.stories.tsx +0 -602
  470. package/src/stories/Tooltip.stories.tsx +0 -59
  471. package/src/tokens/primitives.css +0 -157
  472. package/src/tokens/semantic.css +0 -130
  473. package/src/tokens.css +0 -5
@@ -1,3187 +0,0 @@
1
- import { useState } from 'react';
2
- import type { Meta, StoryObj } from '@storybook/react-vite';
3
-
4
- import { Avatar } from '../components/Avatar/Avatar';
5
- import { Badge } from '../components/Badge/Badge';
6
- import { Button } from '../components/Button/Button';
7
- import { Card } from '../components/Card/Card';
8
- import { Dialog } from '../components/Dialog/Dialog';
9
- import { Drawer } from '../components/Drawer/Drawer';
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';
14
- import { FormField } from '../components/FormField/FormField';
15
- import { Icon } from '../components/Icon/Icon';
16
- import type { IconName } from '../components/Icon/Icon';
17
- import { IconPicker } from '../components/IconPicker/IconPicker';
18
- import { QuantityStepper } from '../components/QuantityStepper/QuantityStepper';
19
- import { CheckableListRow } from '../components/CheckableListRow/CheckableListRow';
20
- import { Input } from '../components/Input/Input';
21
- import { CodeTextarea } from '../components/CodeTextarea/CodeTextarea';
22
- import { Textarea } from '../components/Textarea/Textarea';
23
- import { StatusBadge } from '../components/StatusBadge/StatusBadge';
24
- import { SplitPane } from '../components/SplitPane/SplitPane';
25
- import { TagInput } from '../components/TagInput/TagInput';
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';
30
- import { NavTabs } from '../components/NavTabs/NavTabs';
31
- import { PageHeader } from '../components/PageHeader/PageHeader';
32
- import { PageContainer } from '../components/PageContainer/PageContainer';
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';
40
- import { SegmentedControl } from '../components/SegmentedControl/SegmentedControl';
41
- import { Select } from '../components/Select/Select';
42
- import { Spinner } from '../components/Spinner/Spinner';
43
- import { SystemBanner } from '../components/SystemBanner/SystemBanner';
44
- import { Tabs } from '../components/Tabs/Tabs';
45
- import { ToastProvider, useToast } from '../components/Toast/Toast';
46
- import { Toggle } from '../components/Toggle/Toggle';
47
- import { Tooltip } from '../components/Tooltip/Tooltip';
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';
89
- import { DragHandleRow } from '../components/DragHandleRow/DragHandleRow';
90
- import { FileDropzone } from '../components/FileDropzone/FileDropzone';
91
- import { OverlayHeader } from '../components/OverlayHeader/OverlayHeader';
92
- import { Sheet } from '../components/Sheet/Sheet';
93
- import { ConfirmDialog } from '../components/ConfirmDialog/ConfirmDialog';
94
- import { Menu } from '../components/Menu/Menu';
95
- import { Calendar } from '../components/Calendar/Calendar';
96
- import { DatePicker } from '../components/DatePicker/DatePicker';
97
- import { CurrencyInput } from '../components/CurrencyInput/CurrencyInput';
98
- import { BalanceChip } from '../components/BalanceChip/BalanceChip';
99
- import {
100
- SplitMethodSelector,
101
- type SplitMethod,
102
- } from '../components/SplitMethodSelector/SplitMethodSelector';
103
- import {
104
- MemberMultiSelect,
105
- type MemberMultiSelectOption,
106
- } from '../components/MemberMultiSelect/MemberMultiSelect';
107
-
108
- // ---------------------------------------------------------------------------
109
- // Shared primitives
110
- // ---------------------------------------------------------------------------
111
-
112
- const ff = 'var(--sv-font-family)';
113
- const ffm = 'var(--sv-font-family-mono)';
114
-
115
- function Heading({ level = 2, children }: { level?: 1 | 2 | 3; children: React.ReactNode }) {
116
- const sizes: Record<number, string> = { 1: '2rem', 2: '1.25rem', 3: '1rem' };
117
- const weights: Record<number, number> = { 1: 700, 2: 600, 3: 600 };
118
- const Tag = `h${level}` as 'h1' | 'h2' | 'h3';
119
- return (
120
- <Tag
121
- style={{
122
- fontFamily: ff,
123
- fontSize: sizes[level],
124
- fontWeight: weights[level],
125
- color: 'var(--sv-color-text-primary)',
126
- margin: 0,
127
- lineHeight: 1.2,
128
- }}
129
- >
130
- {children}
131
- </Tag>
132
- );
133
- }
134
-
135
- function SectionHeader({ title, subtitle }: { title: string; subtitle?: string }) {
136
- return (
137
- <div
138
- style={{
139
- paddingBottom: 'var(--sv-space-3)',
140
- borderBottom: '2px solid var(--sv-color-accent)',
141
- marginBottom: 'var(--sv-space-5)',
142
- }}
143
- >
144
- <Heading level={2}>{title}</Heading>
145
- {subtitle && (
146
- <p
147
- style={{
148
- fontFamily: ff,
149
- fontSize: 'var(--sv-font-size-sm)',
150
- color: 'var(--sv-color-text-muted)',
151
- marginTop: 'var(--sv-space-1)',
152
- }}
153
- >
154
- {subtitle}
155
- </p>
156
- )}
157
- </div>
158
- );
159
- }
160
-
161
- function Code({ children }: { children: string }) {
162
- return (
163
- <pre
164
- style={{
165
- fontFamily: ffm,
166
- fontSize: 'var(--sv-font-size-xs)',
167
- background: 'var(--sv-color-surface-sunken)',
168
- border: '1px solid var(--sv-color-border)',
169
- borderRadius: 'var(--sv-radius-md)',
170
- padding: 'var(--sv-space-3) var(--sv-space-4)',
171
- color: 'var(--sv-color-text-primary)',
172
- overflowX: 'auto',
173
- margin: 0,
174
- lineHeight: 1.6,
175
- whiteSpace: 'pre',
176
- }}
177
- >
178
- {children}
179
- </pre>
180
- );
181
- }
182
-
183
- function DemoBox({ children, style }: { children: React.ReactNode; style?: React.CSSProperties }) {
184
- return (
185
- <div
186
- style={{
187
- background: 'var(--sv-color-surface-sunken)',
188
- border: '1px solid var(--sv-color-border)',
189
- borderRadius: 'var(--sv-radius-lg)',
190
- padding: 'var(--sv-space-4)',
191
- display: 'flex',
192
- alignItems: 'center',
193
- justifyContent: 'center',
194
- gap: 'var(--sv-space-3)',
195
- flexWrap: 'wrap',
196
- minHeight: 80,
197
- ...style,
198
- }}
199
- >
200
- {children}
201
- </div>
202
- );
203
- }
204
-
205
- function Pill({ children }: { children: React.ReactNode }) {
206
- return (
207
- <span
208
- style={{
209
- display: 'inline-block',
210
- fontFamily: ffm,
211
- fontSize: '0.6875rem',
212
- fontWeight: 600,
213
- letterSpacing: '0.03em',
214
- background: 'var(--sv-color-surface-sunken)',
215
- border: '1px solid var(--sv-color-border)',
216
- color: 'var(--sv-color-text-muted)',
217
- borderRadius: 'var(--sv-radius-full)',
218
- padding: '2px 10px',
219
- }}
220
- >
221
- {children}
222
- </span>
223
- );
224
- }
225
-
226
- // ---------------------------------------------------------------------------
227
- // Color groups
228
- // ---------------------------------------------------------------------------
229
-
230
- const COLOR_GROUPS: Array<{
231
- label: string;
232
- usage: string;
233
- tokens: Array<{ token: string; name: string }>;
234
- }> = [
235
- {
236
- label: 'Surface',
237
- usage: 'Page backgrounds and card elevations.',
238
- tokens: [
239
- { token: '--sv-color-surface', name: 'surface' },
240
- { token: '--sv-color-surface-sunken', name: 'surface-sunken' },
241
- { token: '--sv-color-surface-raised', name: 'surface-raised' },
242
- ],
243
- },
244
- {
245
- label: 'Text',
246
- usage: 'Copy hierarchy from primary body to disabled hints.',
247
- tokens: [
248
- { token: '--sv-color-text-primary', name: 'text-primary' },
249
- { token: '--sv-color-text-muted', name: 'text-muted' },
250
- { token: '--sv-color-text-subtle', name: 'text-subtle' },
251
- { token: '--sv-color-text-on-accent', name: 'text-on-accent' },
252
- { token: '--sv-color-text-on-error', name: 'text-on-error' },
253
- { token: '--sv-color-text-on-success', name: 'text-on-success' },
254
- ],
255
- },
256
- {
257
- label: 'Border',
258
- usage: 'Dividers, input outlines, and card edges.',
259
- tokens: [
260
- { token: '--sv-color-border', name: 'border' },
261
- { token: '--sv-color-border-strong', name: 'border-strong' },
262
- ],
263
- },
264
- {
265
- label: 'Accent',
266
- usage:
267
- 'Primary interactive color. Monochrome by default; instance admins override with their brand color.',
268
- tokens: [
269
- { token: '--sv-color-accent', name: 'accent' },
270
- { token: '--sv-color-accent-hover', name: 'accent-hover' },
271
- { token: '--sv-color-focus-ring', name: 'focus-ring' },
272
- ],
273
- },
274
- {
275
- label: 'Error',
276
- usage: 'Destructive states, form validation errors, critical banners.',
277
- tokens: [
278
- { token: '--sv-color-error-surface', name: 'error-surface' },
279
- { token: '--sv-color-error-text', name: 'error-text' },
280
- { token: '--sv-color-error-border', name: 'error-border' },
281
- { token: '--sv-color-error-solid', name: 'error-solid' },
282
- ],
283
- },
284
- {
285
- label: 'Warning',
286
- usage: 'Caution states, expiring licenses, near-limit notices.',
287
- tokens: [
288
- { token: '--sv-color-warning-surface', name: 'warning-surface' },
289
- { token: '--sv-color-warning-text', name: 'warning-text' },
290
- { token: '--sv-color-warning-border', name: 'warning-border' },
291
- ],
292
- },
293
- {
294
- label: 'Success',
295
- usage: 'Positive confirmations, completed actions, healthy status.',
296
- tokens: [
297
- { token: '--sv-color-success-surface', name: 'success-surface' },
298
- { token: '--sv-color-success-text', name: 'success-text' },
299
- { token: '--sv-color-success-border', name: 'success-border' },
300
- { token: '--sv-color-success-solid', name: 'success-solid' },
301
- ],
302
- },
303
- {
304
- label: 'Info',
305
- usage: 'Informational notices and neutral callouts.',
306
- tokens: [
307
- { token: '--sv-color-info-surface', name: 'info-surface' },
308
- { token: '--sv-color-info-text', name: 'info-text' },
309
- { token: '--sv-color-info-border', name: 'info-border' },
310
- ],
311
- },
312
- ];
313
-
314
- function ColorSwatch({ token, name }: { token: string; name: string }) {
315
- const value =
316
- typeof window !== 'undefined'
317
- ? getComputedStyle(document.documentElement).getPropertyValue(token).trim()
318
- : '';
319
- return (
320
- <div style={{ display: 'flex', flexDirection: 'column', gap: 6, flex: '1 1 160px' }}>
321
- <div
322
- style={{
323
- height: 44,
324
- borderRadius: 'var(--sv-radius-md)',
325
- background: `var(${token})`,
326
- border: '1px solid var(--sv-color-border)',
327
- }}
328
- />
329
- <div>
330
- <p
331
- style={{
332
- fontFamily: ffm,
333
- fontSize: '0.6875rem',
334
- color: 'var(--sv-color-text-primary)',
335
- margin: 0,
336
- }}
337
- >
338
- --sv-color-{name}
339
- </p>
340
- <p
341
- style={{
342
- fontFamily: ffm,
343
- fontSize: '0.6875rem',
344
- color: 'var(--sv-color-text-muted)',
345
- margin: 0,
346
- }}
347
- >
348
- {value}
349
- </p>
350
- </div>
351
- </div>
352
- );
353
- }
354
-
355
- function ColorGroup({
356
- label,
357
- usage,
358
- tokens,
359
- }: {
360
- label: string;
361
- usage: string;
362
- tokens: Array<{ token: string; name: string }>;
363
- }) {
364
- return (
365
- <div style={{ marginBottom: 'var(--sv-space-8)' }}>
366
- <div style={{ marginBottom: 'var(--sv-space-3)' }}>
367
- <Heading level={3}>{label}</Heading>
368
- <p
369
- style={{
370
- fontFamily: ff,
371
- fontSize: 'var(--sv-font-size-caption)',
372
- color: 'var(--sv-color-text-muted)',
373
- margin: '4px 0 0',
374
- }}
375
- >
376
- {usage}
377
- </p>
378
- </div>
379
- <div style={{ display: 'flex', flexWrap: 'wrap', gap: 'var(--sv-space-4)' }}>
380
- {tokens.map((t) => (
381
- <ColorSwatch key={t.token} token={t.token} name={t.name} />
382
- ))}
383
- </div>
384
- </div>
385
- );
386
- }
387
-
388
- // ---------------------------------------------------------------------------
389
- // Typography
390
- // ---------------------------------------------------------------------------
391
-
392
- const TYPE_SCALE: Array<{ token: string; label: string; note: string }> = [
393
- { token: '--sv-font-size-2xl', label: '2xl — 24px', note: 'Page titles' },
394
- { token: '--sv-font-size-xl', label: 'xl — 20px', note: 'Section headings' },
395
- { token: '--sv-font-size-lg', label: 'lg — 18px', note: 'Sub-headings' },
396
- { token: '--sv-font-size-md', label: 'md — 16px', note: 'Body (base)' },
397
- { token: '--sv-font-size-sm', label: 'sm — 14px', note: 'Body copy, labels' },
398
- { token: '--sv-font-size-caption', label: 'caption — 13px', note: 'Secondary / supporting copy' },
399
- { token: '--sv-font-size-xs', label: 'xs — 12px', note: 'Mono identifiers, badges' },
400
- { token: '--sv-font-size-label', label: 'label — 11px', note: 'All-caps section labels' },
401
- ];
402
-
403
- // ---------------------------------------------------------------------------
404
- // Component cards
405
- // ---------------------------------------------------------------------------
406
-
407
- function ComponentCard({
408
- name,
409
- importLine,
410
- usage,
411
- children,
412
- }: {
413
- name: string;
414
- importLine: string;
415
- usage: string;
416
- children: React.ReactNode;
417
- }) {
418
- return (
419
- <div
420
- style={{
421
- border: '1px solid var(--sv-color-border)',
422
- borderRadius: 'var(--sv-radius-xl)',
423
- overflow: 'hidden',
424
- background: 'var(--sv-color-surface)',
425
- boxShadow: 'var(--sv-shadow-card)',
426
- display: 'flex',
427
- flexDirection: 'column',
428
- }}
429
- >
430
- {/* Header */}
431
- <div
432
- style={{
433
- padding: 'var(--sv-space-4)',
434
- borderBottom: '1px solid var(--sv-color-border)',
435
- background: 'var(--sv-color-surface-sunken)',
436
- }}
437
- >
438
- <Heading level={3}>{name}</Heading>
439
- <p
440
- style={{
441
- fontFamily: ff,
442
- fontSize: 'var(--sv-font-size-caption)',
443
- color: 'var(--sv-color-text-muted)',
444
- margin: '4px 0 0',
445
- }}
446
- >
447
- {usage}
448
- </p>
449
- </div>
450
- {/* Demo */}
451
- <div
452
- style={{
453
- padding: 'var(--sv-space-5)',
454
- display: 'flex',
455
- alignItems: 'center',
456
- justifyContent: 'center',
457
- minHeight: 96,
458
- gap: 'var(--sv-space-3)',
459
- flexWrap: 'wrap',
460
- flexGrow: 1,
461
- }}
462
- >
463
- {children}
464
- </div>
465
- {/* Import */}
466
- <div style={{ padding: 'var(--sv-space-3) var(--sv-space-4)' }}>
467
- <code
468
- style={{
469
- fontFamily: ffm,
470
- fontSize: '0.6875rem',
471
- color: 'var(--sv-color-text-muted)',
472
- display: 'block',
473
- }}
474
- >
475
- {importLine}
476
- </code>
477
- </div>
478
- </div>
479
- );
480
- }
481
-
482
- // Interactive wrappers
483
-
484
- function ToggleDemo() {
485
- const [on, setOn] = useState(false);
486
- return <Toggle checked={on} onChange={setOn} aria-label="Enable feature" />;
487
- }
488
-
489
- function SegmentedDemo() {
490
- const [v, setV] = useState<'user' | 'admin'>('user');
491
- return (
492
- <SegmentedControl
493
- value={v}
494
- onChange={setV}
495
- options={[
496
- { label: 'User', value: 'user' },
497
- { label: 'Admin', value: 'admin' },
498
- ]}
499
- aria-label="Role"
500
- />
501
- );
502
- }
503
-
504
- function TabsDemo() {
505
- const [tab, setTab] = useState('profile');
506
- return (
507
- <div style={{ width: '100%', fontFamily: ff }}>
508
- <Tabs
509
- items={[
510
- { label: 'Profile', value: 'profile' },
511
- { label: 'Security', value: 'security' },
512
- { label: 'Data', value: 'data' },
513
- ]}
514
- value={tab}
515
- onChange={setTab}
516
- aria-label="Account"
517
- />
518
- </div>
519
- );
520
- }
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
-
580
- function DialogDemo() {
581
- const [open, setOpen] = useState(false);
582
- return (
583
- <>
584
- <Button size="sm" onClick={() => setOpen(true)}>
585
- Open dialog
586
- </Button>
587
- <Dialog open={open} onClose={() => setOpen(false)} size="sm" aria-label="Example dialog">
588
- <div style={{ padding: 24, fontFamily: ff }}>
589
- <Heading level={3}>Confirm action</Heading>
590
- <p
591
- style={{
592
- color: 'var(--sv-color-text-muted)',
593
- fontSize: 'var(--sv-font-size-sm)',
594
- margin: '12px 0 20px',
595
- }}
596
- >
597
- This will permanently delete the item. Are you sure?
598
- </p>
599
- <div style={{ display: 'flex', gap: 8 }}>
600
- <Button variant="destructive" size="sm" onClick={() => setOpen(false)}>
601
- Delete
602
- </Button>
603
- <Button variant="secondary" size="sm" onClick={() => setOpen(false)}>
604
- Cancel
605
- </Button>
606
- </div>
607
- </div>
608
- </Dialog>
609
- </>
610
- );
611
- }
612
-
613
- function DrawerDemo() {
614
- const [open, setOpen] = useState(false);
615
- return (
616
- <>
617
- <Button size="sm" variant="secondary" onClick={() => setOpen(true)}>
618
- Open drawer
619
- </Button>
620
- <Drawer open={open} onClose={() => setOpen(false)} aria-label="Navigation">
621
- <ul style={{ listStyle: 'none', margin: 0, padding: '8px 0' }}>
622
- {(['Home', 'Settings', 'Account'] as const).map((item) => (
623
- <li key={item}>
624
- <button
625
- onClick={() => setOpen(false)}
626
- style={{
627
- display: 'block',
628
- width: '100%',
629
- padding: '12px 20px',
630
- background: 'none',
631
- border: 'none',
632
- cursor: 'pointer',
633
- fontSize: 15,
634
- textAlign: 'left',
635
- color: 'var(--sv-color-text-primary)',
636
- fontFamily: ff,
637
- }}
638
- >
639
- {item}
640
- </button>
641
- </li>
642
- ))}
643
- </ul>
644
- </Drawer>
645
- </>
646
- );
647
- }
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
-
671
- function PopoverDemo() {
672
- const [open, setOpen] = useState(false);
673
- return (
674
- <Popover
675
- open={open}
676
- onClose={() => setOpen(false)}
677
- align="left"
678
- aria-label="Options menu"
679
- trigger={
680
- <Button size="sm" variant="ghost" onClick={() => setOpen((o) => !o)}>
681
- <Icon name="settings" size="sm" aria-hidden /> Options
682
- </Button>
683
- }
684
- >
685
- <div style={{ padding: 'var(--sv-space-3)', fontFamily: ff }}>
686
- {['Edit', 'Duplicate', 'Delete'].map((item) => (
687
- <button
688
- key={item}
689
- type="button"
690
- onClick={() => setOpen(false)}
691
- style={{
692
- display: 'block',
693
- width: '100%',
694
- padding: '8px 12px',
695
- background: 'none',
696
- border: 'none',
697
- cursor: 'pointer',
698
- fontSize: 'var(--sv-font-size-sm)',
699
- textAlign: 'left',
700
- color:
701
- item === 'Delete' ? 'var(--sv-color-error-text)' : 'var(--sv-color-text-primary)',
702
- fontFamily: ff,
703
- borderRadius: 'var(--sv-radius-sm)',
704
- }}
705
- >
706
- {item}
707
- </button>
708
- ))}
709
- </div>
710
- </Popover>
711
- );
712
- }
713
-
714
- function OverlayHeaderDemo() {
715
- return (
716
- <div
717
- style={{
718
- width: '100%',
719
- maxWidth: 360,
720
- border: '1px solid var(--sv-color-border)',
721
- borderRadius: 'var(--sv-radius-lg)',
722
- overflow: 'hidden',
723
- }}
724
- >
725
- <OverlayHeader
726
- title="Edit list"
727
- onClose={() => {}}
728
- onBack={() => {}}
729
- action={
730
- <Button size="sm" variant="ghost" onClick={() => {}}>
731
- Save
732
- </Button>
733
- }
734
- />
735
- </div>
736
- );
737
- }
738
-
739
- function SheetDemo() {
740
- const [open, setOpen] = useState(false);
741
- return (
742
- <>
743
- <Button size="sm" onClick={() => setOpen(true)}>
744
- Open sheet
745
- </Button>
746
- {/* Storybook has no --sv-shell-header-height/--sv-shell-footer-height, so
747
- the panel fills this fixed-height preview box instead of the real
748
- app's header-to-footer region — same component, just no shell
749
- around it here. */}
750
- <div
751
- style={{
752
- position: 'relative',
753
- width: '100%',
754
- maxWidth: 360,
755
- height: 280,
756
- marginTop: 12,
757
- border: '1px solid var(--sv-color-border)',
758
- borderRadius: 'var(--sv-radius-lg)',
759
- overflow: 'hidden',
760
- }}
761
- >
762
- <div style={{ position: 'absolute', inset: 0 }}>
763
- <Sheet
764
- open={open}
765
- onClose={() => setOpen(false)}
766
- title="Task detail"
767
- aria-label="Task detail"
768
- >
769
- <div
770
- style={{
771
- padding: 16,
772
- fontFamily: ff,
773
- fontSize: 14,
774
- color: 'var(--sv-color-text-muted)',
775
- }}
776
- >
777
- Full-page overlay content — same pattern the tasks plugin used for its task-detail and
778
- list-edit panels.
779
- </div>
780
- </Sheet>
781
- </div>
782
- </div>
783
- </>
784
- );
785
- }
786
-
787
- function ConfirmDialogDemo() {
788
- const [open, setOpen] = useState(false);
789
- return (
790
- <>
791
- <Button size="sm" variant="destructive" onClick={() => setOpen(true)}>
792
- Remove passkey
793
- </Button>
794
- <ConfirmDialog
795
- open={open}
796
- onClose={() => setOpen(false)}
797
- onConfirm={() => setOpen(false)}
798
- title="Remove passkey"
799
- message="You will no longer be able to sign in with this passkey."
800
- confirmLabel="Remove"
801
- destructive
802
- />
803
- </>
804
- );
805
- }
806
-
807
- function MenuDemo() {
808
- const [open, setOpen] = useState(false);
809
- const [sortBy, setSortBy] = useState<'manual' | 'title' | 'due'>('manual');
810
- return (
811
- <Menu
812
- open={open}
813
- onClose={() => setOpen(false)}
814
- aria-label="List actions"
815
- align="left"
816
- trigger={
817
- <Button size="sm" variant="ghost" onClick={() => setOpen((o) => !o)}>
818
- <Icon name="settings" size="sm" aria-hidden /> Actions
819
- </Button>
820
- }
821
- items={[
822
- { type: 'label', label: 'Sort by' },
823
- { label: 'Manual', checked: sortBy === 'manual', onSelect: () => setSortBy('manual') },
824
- { label: 'Title', checked: sortBy === 'title', onSelect: () => setSortBy('title') },
825
- { label: 'Due date', checked: sortBy === 'due', onSelect: () => setSortBy('due') },
826
- { type: 'separator' },
827
- { label: 'Rename', icon: 'pencil', onSelect: () => {} },
828
- { label: 'Duplicate', icon: 'plus', onSelect: () => {} },
829
- { label: 'Delete', icon: 'trash-2', destructive: true, onSelect: () => {} },
830
- ]}
831
- />
832
- );
833
- }
834
-
835
- function CalendarDemo() {
836
- const [value, setValue] = useState<Date | null>(new Date());
837
- return (
838
- <div style={{ width: '100%', maxWidth: 320 }}>
839
- <Calendar value={value} onChange={setValue} aria-label="Example calendar" />
840
- </div>
841
- );
842
- }
843
-
844
- function DatePickerDemo() {
845
- const [value, setValue] = useState<Date | null>(null);
846
- return (
847
- <div style={{ width: '100%', maxWidth: 240 }}>
848
- <DatePicker
849
- value={value}
850
- onChange={setValue}
851
- aria-label="Due date"
852
- placeholder="Select date"
853
- />
854
- </div>
855
- );
856
- }
857
-
858
- function ToastDemo() {
859
- const { show } = useToast();
860
- return (
861
- <Button
862
- size="sm"
863
- variant="secondary"
864
- onClick={() =>
865
- show({
866
- title: 'Plugin installed',
867
- message: 'Tasks v1.2.0 is now active.',
868
- category: 'success',
869
- })
870
- }
871
- >
872
- Fire toast
873
- </Button>
874
- );
875
- }
876
-
877
- function TagInputDemo() {
878
- const [tags, setTags] = useState(['draft', 'frontmatter']);
879
- return <TagInput value={tags} onChange={setTags} aria-label="Tags" />;
880
- }
881
-
882
- function QuantityStepperDemo() {
883
- const [value, setValue] = useState(6);
884
- return <QuantityStepper value={value} onChange={setValue} unit="pcs" aria-label="Quantity" />;
885
- }
886
-
887
- function CheckableListRowDemo() {
888
- const [checked, setChecked] = useState(false);
889
- return (
890
- <div style={{ width: '100%' }}>
891
- <CheckableListRow
892
- checked={checked}
893
- onCheckedChange={setChecked}
894
- label="Bananas"
895
- icon={<Icon name="banana" size="md" aria-hidden />}
896
- trailing={<span style={{ fontSize: 13, color: 'var(--sv-color-text-muted)' }}>6 pcs</span>}
897
- />
898
- </div>
899
- );
900
- }
901
-
902
- function CurrencyInputDemo() {
903
- const [cents, setCents] = useState<number | null>(4250);
904
- return (
905
- <CurrencyInput
906
- valueCents={cents}
907
- onValueChange={setCents}
908
- placeholder="0.00"
909
- aria-label="Amount"
910
- />
911
- );
912
- }
913
-
914
- function SplitMethodSelectorDemo() {
915
- const [value, setValue] = useState<SplitMethod>('equal');
916
- return <SplitMethodSelector value={value} onChange={setValue} />;
917
- }
918
-
919
- const MEMBER_MULTI_SELECT_OPTIONS: MemberMultiSelectOption[] = [
920
- { id: '1', label: 'Priya' },
921
- { id: '2', label: 'Jamie (guest)' },
922
- { id: '3', label: 'Sam' },
923
- ];
924
-
925
- function MemberMultiSelectDemo() {
926
- const [selected, setSelected] = useState<Set<string>>(new Set(['1', '3']));
927
- return (
928
- <div style={{ width: '100%' }}>
929
- <MemberMultiSelect
930
- options={MEMBER_MULTI_SELECT_OPTIONS}
931
- selectedIds={selected}
932
- onToggle={(id, checked) => {
933
- setSelected((prev) => {
934
- const next = new Set(prev);
935
- if (checked) next.add(id);
936
- else next.delete(id);
937
- return next;
938
- });
939
- }}
940
- label="Split between"
941
- />
942
- </div>
943
- );
944
- }
945
-
946
- function FileDropzoneDemo() {
947
- const [file, setFile] = useState<File | null>(null);
948
- return (
949
- <div style={{ width: '100%' }}>
950
- <FileDropzone
951
- ariaLabel="Upload ZIP file"
952
- accept=".zip,application/zip"
953
- label={file ? file.name : 'Choose a ZIP file'}
954
- hint={file ? `${(file.size / 1024).toFixed(0)} KB` : 'or drag and drop here'}
955
- onFileSelect={setFile}
956
- />
957
- </div>
958
- );
959
- }
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
-
1055
- function IconPickerDemo() {
1056
- const [value, setValue] = useState<IconName | null>('banana');
1057
- const options: IconName[] = [
1058
- 'banana',
1059
- 'apple',
1060
- 'carrot',
1061
- 'egg',
1062
- 'milk',
1063
- 'beef',
1064
- 'drumstick',
1065
- 'fish',
1066
- 'croissant',
1067
- 'cookie',
1068
- ];
1069
- return (
1070
- <IconPicker
1071
- value={value}
1072
- onChange={setValue}
1073
- options={options}
1074
- aria-label="Item icon"
1075
- triggerLabel={value ?? 'Choose icon'}
1076
- />
1077
- );
1078
- }
1079
-
1080
- function SuggestionInputDemo() {
1081
- const [value, setValue] = useState('ban');
1082
- const options = ['Bananas', 'Banana bread mix']
1083
- .filter((label) => label.toLowerCase().includes(value.trim().toLowerCase()))
1084
- .map((label, i) => ({ id: String(i), label }));
1085
- return (
1086
- <SuggestionInput
1087
- value={value}
1088
- onChange={setValue}
1089
- options={value.trim() ? options : []}
1090
- onSelect={(o) => setValue(o.label)}
1091
- placeholder="Add an item…"
1092
- aria-label="Add an item"
1093
- createLabel={(v) => `Add "${v}" as a new item`}
1094
- onCreate={setValue}
1095
- />
1096
- );
1097
- }
1098
-
1099
- // ---------------------------------------------------------------------------
1100
- // Main component
1101
- // ---------------------------------------------------------------------------
1102
-
1103
- function OverviewPage() {
1104
- return (
1105
- <div
1106
- style={{
1107
- fontFamily: ff,
1108
- background: 'var(--sv-color-surface)',
1109
- minHeight: '100vh',
1110
- color: 'var(--sv-color-text-primary)',
1111
- }}
1112
- >
1113
- {/* ── Hero ─────────────────────────────────────────────────────────── */}
1114
- <div
1115
- style={{
1116
- padding: 'var(--sv-space-10) var(--sv-space-8)',
1117
- borderBottom: '1px solid var(--sv-color-border)',
1118
- background: 'var(--sv-color-surface-sunken)',
1119
- }}
1120
- >
1121
- <div style={{ maxWidth: 800, margin: '0 auto' }}>
1122
- <div
1123
- style={{
1124
- display: 'flex',
1125
- alignItems: 'center',
1126
- gap: 'var(--sv-space-3)',
1127
- marginBottom: 'var(--sv-space-4)',
1128
- }}
1129
- >
1130
- <Pill>@sovereignfs/ui</Pill>
1131
- <Pill>v1.x</Pill>
1132
- </div>
1133
- <Heading level={1}>Sovereign Design System</Heading>
1134
- <p
1135
- style={{
1136
- fontSize: 'var(--sv-font-size-lg)',
1137
- color: 'var(--sv-color-text-muted)',
1138
- marginTop: 'var(--sv-space-3)',
1139
- maxWidth: 600,
1140
- lineHeight: 1.5,
1141
- }}
1142
- >
1143
- The component library and token system for building Sovereign plugins. Everything here
1144
- is the public contract — available to every plugin developer, stable across minor
1145
- versions.
1146
- </p>
1147
- <div
1148
- style={{
1149
- marginTop: 'var(--sv-space-5)',
1150
- padding: 'var(--sv-space-4)',
1151
- background: 'var(--sv-color-surface)',
1152
- border: '1px solid var(--sv-color-border)',
1153
- borderRadius: 'var(--sv-radius-lg)',
1154
- display: 'flex',
1155
- flexDirection: 'column',
1156
- gap: 'var(--sv-space-2)',
1157
- }}
1158
- >
1159
- <p
1160
- style={{
1161
- fontFamily: ffm,
1162
- fontSize: 'var(--sv-font-size-xs)',
1163
- color: 'var(--sv-color-text-muted)',
1164
- margin: 0,
1165
- }}
1166
- >
1167
- Three things to remember:
1168
- </p>
1169
- <ul
1170
- style={{
1171
- margin: 0,
1172
- paddingLeft: 'var(--sv-space-5)',
1173
- color: 'var(--sv-color-text-primary)',
1174
- fontSize: 'var(--sv-font-size-sm)',
1175
- lineHeight: 1.8,
1176
- }}
1177
- >
1178
- <li>
1179
- Import components from <code style={{ fontFamily: ffm }}>@sovereignfs/ui</code>
1180
- </li>
1181
- <li>
1182
- Use <code style={{ fontFamily: ffm }}>--sv-*</code> semantic tokens in your CSS —
1183
- they are injected globally by the runtime shell, no import needed
1184
- </li>
1185
- <li>Never hardcode hex values or reference primitive tokens directly</li>
1186
- </ul>
1187
- </div>
1188
- </div>
1189
- </div>
1190
-
1191
- <div style={{ maxWidth: 800, margin: '0 auto', padding: 'var(--sv-space-8)' }}>
1192
- {/* ── Quick Start ───────────────────────────────────────────────── */}
1193
- <section style={{ marginBottom: 'var(--sv-space-12)' }}>
1194
- <SectionHeader
1195
- title="Quick start"
1196
- subtitle="Everything you need to build a Sovereign plugin UI."
1197
- />
1198
- <div style={{ display: 'flex', flexDirection: 'column', gap: 'var(--sv-space-4)' }}>
1199
- <div>
1200
- <p
1201
- style={{
1202
- fontSize: 'var(--sv-font-size-caption)',
1203
- color: 'var(--sv-color-text-muted)',
1204
- marginBottom: 'var(--sv-space-2)',
1205
- }}
1206
- >
1207
- Import typed React components:
1208
- </p>
1209
- <Code>{`import { Button, Badge, Input, Icon, Toggle, Tabs } from '@sovereignfs/ui';`}</Code>
1210
- </div>
1211
- <div>
1212
- <p
1213
- style={{
1214
- fontSize: 'var(--sv-font-size-caption)',
1215
- color: 'var(--sv-color-text-muted)',
1216
- marginBottom: 'var(--sv-space-2)',
1217
- }}
1218
- >
1219
- Reference semantic tokens directly in plugin CSS — no import required:
1220
- </p>
1221
- <Code>{`.my-card {
1222
- background: var(--sv-color-surface);
1223
- border: 1px solid var(--sv-color-border);
1224
- border-radius: var(--sv-radius-lg);
1225
- padding: var(--sv-space-4);
1226
- box-shadow: var(--sv-shadow-card);
1227
- font-family: var(--sv-font-family);
1228
- color: var(--sv-color-text-primary);
1229
- }`}</Code>
1230
- </div>
1231
- </div>
1232
- </section>
1233
-
1234
- {/* ── Token Architecture ────────────────────────────────────────── */}
1235
- <section style={{ marginBottom: 'var(--sv-space-12)' }}>
1236
- <SectionHeader
1237
- title="Token architecture"
1238
- subtitle="Two layers — only the semantic layer is a public API."
1239
- />
1240
- <div
1241
- style={{
1242
- display: 'grid',
1243
- gridTemplateColumns: '1fr auto 1fr auto 1fr',
1244
- gap: 'var(--sv-space-3)',
1245
- alignItems: 'center',
1246
- marginBottom: 'var(--sv-space-6)',
1247
- }}
1248
- >
1249
- {[
1250
- {
1251
- label: 'Primitives',
1252
- sub: '--sv-grey-900\n--sv-red-100\n--sv-space-4',
1253
- note: 'Internal only. Raw scale values.',
1254
- muted: true,
1255
- },
1256
- ].map((b) => (
1257
- <div
1258
- key={b.label}
1259
- style={{
1260
- border: `1px solid ${b.muted ? 'var(--sv-color-border)' : 'var(--sv-color-accent)'}`,
1261
- borderRadius: 'var(--sv-radius-lg)',
1262
- padding: 'var(--sv-space-4)',
1263
- opacity: b.muted ? 0.6 : 1,
1264
- }}
1265
- >
1266
- <p
1267
- style={{
1268
- fontSize: 'var(--sv-font-size-sm)',
1269
- fontWeight: 600,
1270
- margin: '0 0 6px',
1271
- color: 'var(--sv-color-text-primary)',
1272
- }}
1273
- >
1274
- {b.label}
1275
- </p>
1276
- <pre
1277
- style={{
1278
- fontFamily: ffm,
1279
- fontSize: '0.6875rem',
1280
- color: 'var(--sv-color-text-muted)',
1281
- margin: 0,
1282
- whiteSpace: 'pre',
1283
- }}
1284
- >
1285
- {b.sub}
1286
- </pre>
1287
- <p
1288
- style={{
1289
- fontSize: 'var(--sv-font-size-caption)',
1290
- color: 'var(--sv-color-text-muted)',
1291
- margin: '8px 0 0',
1292
- }}
1293
- >
1294
- {b.note}
1295
- </p>
1296
- </div>
1297
- ))}
1298
- <div style={{ textAlign: 'center', color: 'var(--sv-color-text-muted)', fontSize: 24 }}>
1299
- →
1300
- </div>
1301
- <div
1302
- style={{
1303
- border: '2px solid var(--sv-color-accent)',
1304
- borderRadius: 'var(--sv-radius-lg)',
1305
- padding: 'var(--sv-space-4)',
1306
- }}
1307
- >
1308
- <p
1309
- style={{
1310
- fontSize: 'var(--sv-font-size-sm)',
1311
- fontWeight: 600,
1312
- margin: '0 0 6px',
1313
- color: 'var(--sv-color-text-primary)',
1314
- }}
1315
- >
1316
- Semantic tokens ✓
1317
- </p>
1318
- <pre
1319
- style={{
1320
- fontFamily: ffm,
1321
- fontSize: '0.6875rem',
1322
- color: 'var(--sv-color-text-muted)',
1323
- margin: 0,
1324
- whiteSpace: 'pre',
1325
- }}
1326
- >
1327
- {'--sv-color-surface\n--sv-color-error-text\n--sv-radius-lg'}
1328
- </pre>
1329
- <p
1330
- style={{
1331
- fontSize: 'var(--sv-font-size-caption)',
1332
- color: 'var(--sv-color-text-muted)',
1333
- margin: '8px 0 0',
1334
- }}
1335
- >
1336
- Plugin public API. Theme-aware.
1337
- </p>
1338
- </div>
1339
- <div style={{ textAlign: 'center', color: 'var(--sv-color-text-muted)', fontSize: 24 }}>
1340
- →
1341
- </div>
1342
- <div
1343
- style={{
1344
- border: '2px solid var(--sv-color-accent)',
1345
- borderRadius: 'var(--sv-radius-lg)',
1346
- padding: 'var(--sv-space-4)',
1347
- }}
1348
- >
1349
- <p
1350
- style={{
1351
- fontSize: 'var(--sv-font-size-sm)',
1352
- fontWeight: 600,
1353
- margin: '0 0 6px',
1354
- color: 'var(--sv-color-text-primary)',
1355
- }}
1356
- >
1357
- Components ✓
1358
- </p>
1359
- <pre
1360
- style={{
1361
- fontFamily: ffm,
1362
- fontSize: '0.6875rem',
1363
- color: 'var(--sv-color-text-muted)',
1364
- margin: 0,
1365
- whiteSpace: 'pre',
1366
- }}
1367
- >
1368
- {'<Button />\n<Badge />\n<Input />'}
1369
- </pre>
1370
- <p
1371
- style={{
1372
- fontSize: 'var(--sv-font-size-caption)',
1373
- color: 'var(--sv-color-text-muted)',
1374
- margin: '8px 0 0',
1375
- }}
1376
- >
1377
- React, typed, RSC-safe.
1378
- </p>
1379
- </div>
1380
- </div>
1381
- <div
1382
- style={{
1383
- background: 'var(--sv-color-warning-surface)',
1384
- border: '1px solid var(--sv-color-warning-border)',
1385
- borderRadius: 'var(--sv-radius-md)',
1386
- padding: 'var(--sv-space-3) var(--sv-space-4)',
1387
- fontSize: 'var(--sv-font-size-sm)',
1388
- color: 'var(--sv-color-warning-text)',
1389
- }}
1390
- >
1391
- <strong>Never use primitive tokens in plugin code.</strong> Primitives like{' '}
1392
- <code style={{ fontFamily: ffm }}>--sv-grey-900</code> or{' '}
1393
- <code style={{ fontFamily: ffm }}>--sv-red-100</code> are fixed values — they do not
1394
- swap with dark mode or instance theming. Only semantic tokens do.
1395
- </div>
1396
- </section>
1397
-
1398
- {/* ── Color System ─────────────────────────────────────────────── */}
1399
- <section style={{ marginBottom: 'var(--sv-space-12)' }}>
1400
- <SectionHeader
1401
- title="Color system"
1402
- subtitle="Semantic groups — use the Themes toolbar above to compare light and dark."
1403
- />
1404
- {COLOR_GROUPS.map((g) => (
1405
- <ColorGroup key={g.label} label={g.label} usage={g.usage} tokens={g.tokens} />
1406
- ))}
1407
- </section>
1408
-
1409
- {/* ── Typography ───────────────────────────────────────────────── */}
1410
- <section style={{ marginBottom: 'var(--sv-space-12)' }}>
1411
- <SectionHeader
1412
- title="Typography"
1413
- subtitle="Hanken Grotesk (body) · JetBrains Mono (code) — fallback stacks apply when web fonts are not loaded."
1414
- />
1415
-
1416
- <div
1417
- style={{
1418
- display: 'grid',
1419
- gridTemplateColumns: '1fr 1fr',
1420
- gap: 'var(--sv-space-6)',
1421
- marginBottom: 'var(--sv-space-6)',
1422
- }}
1423
- >
1424
- <div>
1425
- <p
1426
- style={{
1427
- fontSize: 'var(--sv-font-size-caption)',
1428
- color: 'var(--sv-color-text-muted)',
1429
- marginBottom: 'var(--sv-space-2)',
1430
- }}
1431
- >
1432
- Body — var(--sv-font-family)
1433
- </p>
1434
- <p
1435
- style={{
1436
- fontFamily: ff,
1437
- fontSize: 'var(--sv-font-size-lg)',
1438
- color: 'var(--sv-color-text-primary)',
1439
- margin: 0,
1440
- }}
1441
- >
1442
- The quick brown fox
1443
- </p>
1444
- <p
1445
- style={{
1446
- fontFamily: ff,
1447
- fontSize: 'var(--sv-font-size-sm)',
1448
- color: 'var(--sv-color-text-muted)',
1449
- margin: '4px 0 0',
1450
- }}
1451
- >
1452
- ABCDEFGHIJKLMNOPQRSTUVWXYZ
1453
- </p>
1454
- </div>
1455
- <div>
1456
- <p
1457
- style={{
1458
- fontSize: 'var(--sv-font-size-caption)',
1459
- color: 'var(--sv-color-text-muted)',
1460
- marginBottom: 'var(--sv-space-2)',
1461
- }}
1462
- >
1463
- Mono — var(--sv-font-family-mono)
1464
- </p>
1465
- <p
1466
- style={{
1467
- fontFamily: ffm,
1468
- fontSize: 'var(--sv-font-size-lg)',
1469
- color: 'var(--sv-color-text-primary)',
1470
- margin: 0,
1471
- }}
1472
- >
1473
- const x = 42;
1474
- </p>
1475
- <p
1476
- style={{
1477
- fontFamily: ffm,
1478
- fontSize: 'var(--sv-font-size-sm)',
1479
- color: 'var(--sv-color-text-muted)',
1480
- margin: '4px 0 0',
1481
- }}
1482
- >
1483
- --sv-font-family-mono
1484
- </p>
1485
- </div>
1486
- </div>
1487
-
1488
- <div
1489
- style={{
1490
- border: '1px solid var(--sv-color-border)',
1491
- borderRadius: 'var(--sv-radius-lg)',
1492
- overflow: 'hidden',
1493
- }}
1494
- >
1495
- {TYPE_SCALE.map((t, i) => (
1496
- <div
1497
- key={t.token}
1498
- style={{
1499
- display: 'flex',
1500
- alignItems: 'baseline',
1501
- gap: 'var(--sv-space-4)',
1502
- padding: 'var(--sv-space-3) var(--sv-space-4)',
1503
- borderBottom:
1504
- i < TYPE_SCALE.length - 1 ? '1px solid var(--sv-color-border)' : 'none',
1505
- background:
1506
- i % 2 === 0 ? 'var(--sv-color-surface)' : 'var(--sv-color-surface-sunken)',
1507
- }}
1508
- >
1509
- <span
1510
- style={{
1511
- fontSize: `var(${t.token})`,
1512
- color: 'var(--sv-color-text-primary)',
1513
- lineHeight: 1.2,
1514
- minWidth: 160,
1515
- }}
1516
- >
1517
- {t.label}
1518
- </span>
1519
- <code
1520
- style={{
1521
- fontFamily: ffm,
1522
- fontSize: '0.6875rem',
1523
- color: 'var(--sv-color-text-muted)',
1524
- flexShrink: 0,
1525
- }}
1526
- >
1527
- {t.token}
1528
- </code>
1529
- <span
1530
- style={{
1531
- fontSize: 'var(--sv-font-size-caption)',
1532
- color: 'var(--sv-color-text-muted)',
1533
- marginLeft: 'auto',
1534
- }}
1535
- >
1536
- {t.note}
1537
- </span>
1538
- </div>
1539
- ))}
1540
- </div>
1541
-
1542
- <div style={{ marginTop: 'var(--sv-space-4)' }}>
1543
- <p
1544
- style={{
1545
- fontSize: 'var(--sv-font-size-caption)',
1546
- color: 'var(--sv-color-text-muted)',
1547
- marginBottom: 'var(--sv-space-2)',
1548
- }}
1549
- >
1550
- Font weight tokens:
1551
- </p>
1552
- <Code>{`font-weight: var(--sv-font-weight-regular); /* 400 */
1553
- font-weight: var(--sv-font-weight-medium); /* 500 */
1554
- font-weight: var(--sv-font-weight-semibold); /* 600 */
1555
- font-weight: var(--sv-font-weight-bold); /* 700 */`}</Code>
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>
1572
- </section>
1573
-
1574
- {/* ── Spacing & Radius ─────────────────────────────────────────── */}
1575
- <section style={{ marginBottom: 'var(--sv-space-12)' }}>
1576
- <SectionHeader title="Spacing & radius" subtitle="4px base grid." />
1577
- <div
1578
- style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 'var(--sv-space-8)' }}
1579
- >
1580
- {/* Spacing */}
1581
- <div>
1582
- <p
1583
- style={{
1584
- fontSize: 'var(--sv-font-size-caption)',
1585
- color: 'var(--sv-color-text-muted)',
1586
- marginBottom: 'var(--sv-space-3)',
1587
- }}
1588
- >
1589
- Spacing (--sv-space-*)
1590
- </p>
1591
- {(
1592
- [
1593
- '--sv-space-1',
1594
- '--sv-space-2',
1595
- '--sv-space-3',
1596
- '--sv-space-4',
1597
- '--sv-space-5',
1598
- '--sv-space-6',
1599
- '--sv-space-8',
1600
- '--sv-space-10',
1601
- '--sv-space-12',
1602
- '--sv-space-16',
1603
- ] as const
1604
- ).map((t) => (
1605
- <div
1606
- key={t}
1607
- style={{ display: 'flex', alignItems: 'center', gap: 10, marginBottom: 8 }}
1608
- >
1609
- <div
1610
- style={{
1611
- width: `var(${t})`,
1612
- minWidth: 4,
1613
- height: 10,
1614
- background: 'var(--sv-color-accent)',
1615
- borderRadius: 2,
1616
- flexShrink: 0,
1617
- }}
1618
- />
1619
- <code
1620
- style={{
1621
- fontFamily: ffm,
1622
- fontSize: '0.6875rem',
1623
- color: 'var(--sv-color-text-muted)',
1624
- }}
1625
- >
1626
- {t}
1627
- </code>
1628
- </div>
1629
- ))}
1630
- </div>
1631
- {/* Radius */}
1632
- <div>
1633
- <p
1634
- style={{
1635
- fontSize: 'var(--sv-font-size-caption)',
1636
- color: 'var(--sv-color-text-muted)',
1637
- marginBottom: 'var(--sv-space-3)',
1638
- }}
1639
- >
1640
- Border radius (--sv-radius-*)
1641
- </p>
1642
- {(
1643
- [
1644
- '--sv-radius-sm',
1645
- '--sv-radius-md',
1646
- '--sv-radius-lg',
1647
- '--sv-radius-xl',
1648
- '--sv-radius-2xl',
1649
- '--sv-radius-3xl',
1650
- '--sv-radius-full',
1651
- ] as const
1652
- ).map((t) => (
1653
- <div
1654
- key={t}
1655
- style={{ display: 'flex', alignItems: 'center', gap: 10, marginBottom: 12 }}
1656
- >
1657
- <div
1658
- style={{
1659
- width: 48,
1660
- height: 24,
1661
- background: 'var(--sv-color-accent)',
1662
- borderRadius: `var(${t})`,
1663
- flexShrink: 0,
1664
- }}
1665
- />
1666
- <code
1667
- style={{
1668
- fontFamily: ffm,
1669
- fontSize: '0.6875rem',
1670
- color: 'var(--sv-color-text-muted)',
1671
- }}
1672
- >
1673
- {t}
1674
- </code>
1675
- </div>
1676
- ))}
1677
- </div>
1678
- </div>
1679
- </section>
1680
-
1681
- {/* ── Shadows ──────────────────────────────────────────────────── */}
1682
- <section style={{ marginBottom: 'var(--sv-space-12)' }}>
1683
- <SectionHeader title="Elevation & shadows" subtitle="Four levels, dark-mode adjusted." />
1684
- <div
1685
- style={{
1686
- display: 'grid',
1687
- gridTemplateColumns: 'repeat(4, 1fr)',
1688
- gap: 'var(--sv-space-5)',
1689
- }}
1690
- >
1691
- {(
1692
- [
1693
- '--sv-shadow-card',
1694
- '--sv-shadow-hover',
1695
- '--sv-shadow-popover',
1696
- '--sv-shadow-overlay',
1697
- ] as const
1698
- ).map((t) => (
1699
- <div
1700
- key={t}
1701
- style={{ display: 'flex', flexDirection: 'column', alignItems: 'center', gap: 12 }}
1702
- >
1703
- <div
1704
- style={{
1705
- width: 64,
1706
- height: 40,
1707
- background: 'var(--sv-color-surface-raised)',
1708
- boxShadow: `var(${t})`,
1709
- borderRadius: 'var(--sv-radius-md)',
1710
- }}
1711
- />
1712
- <code
1713
- style={{
1714
- fontFamily: ffm,
1715
- fontSize: '0.6875rem',
1716
- color: 'var(--sv-color-text-muted)',
1717
- textAlign: 'center',
1718
- }}
1719
- >
1720
- {t.replace('--sv-shadow-', '')}
1721
- </code>
1722
- </div>
1723
- ))}
1724
- </div>
1725
- </section>
1726
-
1727
- {/* ── Component Gallery ─────────────────────────────────────────── */}
1728
- <section style={{ marginBottom: 'var(--sv-space-12)' }}>
1729
- <SectionHeader
1730
- title="Component gallery"
1731
- subtitle="All 74 components — click each story in the sidebar for the full API, variants, and controls."
1732
- />
1733
-
1734
- <div style={{ marginBottom: 'var(--sv-space-6)' }}>
1735
- <SystemBanner variant="info">
1736
- All components reference <code style={{ fontFamily: ffm }}>--sv-*</code> tokens
1737
- internally — they automatically adapt to dark mode and instance theming without any
1738
- extra configuration.
1739
- </SystemBanner>
1740
- </div>
1741
-
1742
- <div
1743
- style={{
1744
- display: 'grid',
1745
- gridTemplateColumns: 'repeat(auto-fill, minmax(340px, 1fr))',
1746
- gap: 'var(--sv-space-5)',
1747
- }}
1748
- >
1749
- {/* Button */}
1750
- <ComponentCard
1751
- name="Button"
1752
- importLine="import { Button } from '@sovereignfs/ui';"
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)."
1754
- >
1755
- <Button>Primary</Button>
1756
- <Button variant="secondary">Secondary</Button>
1757
- <Button variant="ghost">Ghost</Button>
1758
- <Button variant="destructive" size="sm">
1759
- Delete
1760
- </Button>
1761
- <Button size="lg">Large</Button>
1762
- <Button loading>Saving…</Button>
1763
- </ComponentCard>
1764
-
1765
- {/* Badge */}
1766
- <ComponentCard
1767
- name="Badge"
1768
- importLine="import { Badge } from '@sovereignfs/ui';"
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."
1770
- >
1771
- <Badge variant="role">Admin</Badge>
1772
- <Badge variant="status" status="active">
1773
- Active
1774
- </Badge>
1775
- <Badge variant="status" status="invited">
1776
- Invited
1777
- </Badge>
1778
- <Badge variant="mono">v1.2.0</Badge>
1779
- <Badge variant="role" size="lg" uppercase={false}>
1780
- Owner
1781
- </Badge>
1782
- </ComponentCard>
1783
-
1784
- {/* StatusBadge */}
1785
- <ComponentCard
1786
- name="StatusBadge"
1787
- importLine="import { StatusBadge } from '@sovereignfs/ui';"
1788
- usage="Inline status indicator for editor sync, draft, conflict, and delete-pending states. RSC-safe."
1789
- >
1790
- <StatusBadge status="draft" />
1791
- <StatusBadge status="synced" />
1792
- <StatusBadge status="conflict" />
1793
- <StatusBadge status="pending-delete" />
1794
- </ComponentCard>
1795
-
1796
- {/* BalanceChip */}
1797
- <ComponentCard
1798
- name="BalanceChip"
1799
- importLine="import { BalanceChip } from '@sovereignfs/ui';"
1800
- usage="Inline net-balance indicator — green when owed to them, red when they owe, neutral when settled. Not tied to expense-splitting specifically."
1801
- >
1802
- <BalanceChip amountCents={2500} currency="USD" />
1803
- <BalanceChip amountCents={-1350} currency="USD" />
1804
- <BalanceChip amountCents={0} currency="USD" />
1805
- </ComponentCard>
1806
-
1807
- {/* Input */}
1808
- <ComponentCard
1809
- name="Input"
1810
- importLine="import { Input } from '@sovereignfs/ui';"
1811
- usage="Primitive text field. No label built-in — always pair with <label> for accessibility. Forwards all native input props."
1812
- >
1813
- <div style={{ width: '100%', display: 'flex', flexDirection: 'column', gap: 6 }}>
1814
- <Input placeholder="Email address" type="email" style={{ width: '100%' }} />
1815
- <Input placeholder="Disabled" disabled style={{ width: '100%' }} />
1816
- </div>
1817
- </ComponentCard>
1818
-
1819
- {/* CurrencyInput */}
1820
- <ComponentCard
1821
- name="CurrencyInput"
1822
- importLine="import { CurrencyInput } from '@sovereignfs/ui';"
1823
- usage="Decimal amount entry that reports its value as integer cents. Preserves in-progress typing instead of reformatting on every keystroke."
1824
- >
1825
- <CurrencyInputDemo />
1826
- </ComponentCard>
1827
-
1828
- {/* TagInput */}
1829
- <ComponentCard
1830
- name="TagInput"
1831
- importLine="import { TagInput } from '@sovereignfs/ui';"
1832
- usage="Controlled multi-value input for tags and frontmatter arrays. Enter/comma adds, Backspace removes, paste can split multiple tags."
1833
- >
1834
- <TagInputDemo />
1835
- </ComponentCard>
1836
-
1837
- {/* SuggestionInput */}
1838
- <ComponentCard
1839
- name="SuggestionInput"
1840
- importLine="import { SuggestionInput } from '@sovereignfs/ui';"
1841
- usage="Text field with an anchored, keyboard-navigable async suggestion list, plus an optional trailing 'create new' row. Built on Popover."
1842
- >
1843
- <div style={{ width: '100%' }}>
1844
- <SuggestionInputDemo />
1845
- </div>
1846
- </ComponentCard>
1847
-
1848
- {/* IconPicker */}
1849
- <ComponentCard
1850
- name="IconPicker"
1851
- importLine="import { IconPicker } from '@sovereignfs/ui';"
1852
- usage="Trigger button + Popover grid for a curated, bounded icon set (e.g. a plugin's category icons) — not the full icon library."
1853
- >
1854
- <IconPickerDemo />
1855
- </ComponentCard>
1856
-
1857
- {/* QuantityStepper */}
1858
- <ComponentCard
1859
- name="QuantityStepper"
1860
- importLine="import { QuantityStepper } from '@sovereignfs/ui';"
1861
- usage="Numeric input with +/- buttons and an optional read-only unit suffix. Supports fractional step values (e.g. 0.5 kg)."
1862
- >
1863
- <QuantityStepperDemo />
1864
- </ComponentCard>
1865
-
1866
- {/* Select */}
1867
- <ComponentCard
1868
- name="Select"
1869
- importLine="import { Select } from '@sovereignfs/ui';"
1870
- usage="Styled native <select> — same visual language as Input. Preserves native picker on mobile. RSC-safe."
1871
- >
1872
- <div style={{ width: '100%' }}>
1873
- <Select defaultValue="admin" style={{ width: '100%' }}>
1874
- <option value="user">User</option>
1875
- <option value="admin">Admin</option>
1876
- <option value="owner">Owner</option>
1877
- </Select>
1878
- </div>
1879
- </ComponentCard>
1880
-
1881
- {/* Toggle */}
1882
- <ComponentCard
1883
- name="Toggle"
1884
- importLine="import { Toggle } from '@sovereignfs/ui';"
1885
- usage="38×22px binary switch. Renders as role='switch' for screen reader support. aria-label is required."
1886
- >
1887
- <ToggleDemo />
1888
- <span
1889
- style={{
1890
- fontFamily: ff,
1891
- fontSize: 'var(--sv-font-size-sm)',
1892
- color: 'var(--sv-color-text-muted)',
1893
- }}
1894
- >
1895
- Click to toggle
1896
- </span>
1897
- </ComponentCard>
1898
-
1899
- {/* SegmentedControl */}
1900
- <ComponentCard
1901
- name="SegmentedControl"
1902
- importLine="import { SegmentedControl } from '@sovereignfs/ui';"
1903
- usage="Pill-based 2–3 option picker for inline use (role selector, theme switcher). Renders as role='radiogroup'."
1904
- >
1905
- <SegmentedDemo />
1906
- </ComponentCard>
1907
-
1908
- {/* SplitMethodSelector */}
1909
- <ComponentCard
1910
- name="SplitMethodSelector"
1911
- importLine="import { SplitMethodSelector } from '@sovereignfs/ui';"
1912
- usage="The four-way Equal/Amount/Percentage/Shares picker shared by any cost-splitting plugin. A thin SegmentedControl preset."
1913
- >
1914
- <SplitMethodSelectorDemo />
1915
- </ComponentCard>
1916
-
1917
- {/* Tabs */}
1918
- <ComponentCard
1919
- name="Tabs"
1920
- importLine="import { Tabs } from '@sovereignfs/ui';"
1921
- usage="Underline tab nav. Stateless — caller owns value + onChange. Scrolls horizontally on mobile."
1922
- >
1923
- <TabsDemo />
1924
- </ComponentCard>
1925
-
1926
- {/* Icon */}
1927
- <ComponentCard
1928
- name="Icon"
1929
- importLine="import { Icon } from '@sovereignfs/ui';"
1930
- usage="SVG icon primitive. 52 bundled icons. Decorative: aria-hidden. Meaningful: aria-label. Three sizes: sm, md, lg."
1931
- >
1932
- {(
1933
- [
1934
- 'house',
1935
- 'settings',
1936
- 'bell',
1937
- 'user',
1938
- 'shield',
1939
- 'mail',
1940
- 'search',
1941
- 'plus',
1942
- 'trash-2',
1943
- 'external-link',
1944
- ] as const
1945
- ).map((n) => (
1946
- <Icon key={n} name={n} size="md" aria-hidden />
1947
- ))}
1948
- </ComponentCard>
1949
-
1950
- {/* Dialog */}
1951
- <ComponentCard
1952
- name="Dialog"
1953
- importLine="import { Dialog } from '@sovereignfs/ui';"
1954
- usage="Modal surface (scrim + panel). Esc, scrim-click, focus trap. Sizes: sm, md, lg, full. Mobile renders fullscreen."
1955
- >
1956
- <DialogDemo />
1957
- </ComponentCard>
1958
-
1959
- {/* Drawer */}
1960
- <ComponentCard
1961
- name="Drawer"
1962
- importLine="import { Drawer } from '@sovereignfs/ui';"
1963
- usage="Bottom-sheet panel. Esc, scrim-click, or swipe-down on the built-in grab handle to dismiss. snapHeight: 'content' (default, capped 80dvh) or 'half' (fixed 50dvh). Respects safe-area-inset-bottom."
1964
- >
1965
- <DrawerDemo />
1966
- </ComponentCard>
1967
-
1968
- {/* OverlayHeader */}
1969
- <ComponentCard
1970
- name="OverlayHeader"
1971
- importLine="import { OverlayHeader } from '@sovereignfs/ui';"
1972
- usage="Shared fixed secondary header for Dialog's mobile mode, Sheet, and Drawer: title + close, optional back button, trailing action, and a second row for tab strips."
1973
- >
1974
- <OverlayHeaderDemo />
1975
- </ComponentCard>
1976
-
1977
- {/* Sheet */}
1978
- <ComponentCard
1979
- name="Sheet"
1980
- importLine="import { Sheet } from '@sovereignfs/ui';"
1981
- usage="Full-page overlay filling a plugin's content area between the shell header and footer — for detail views (task detail, list edit). No desktop equivalent; a desktop layout shows the same content inline instead."
1982
- >
1983
- <SheetDemo />
1984
- </ComponentCard>
1985
-
1986
- {/* ConfirmDialog */}
1987
- <ComponentCard
1988
- name="ConfirmDialog"
1989
- importLine="import { ConfirmDialog } from '@sovereignfs/ui';"
1990
- usage="Small, content-sized confirm/cancel prompt. Same presentation on desktop and mobile — not a full-screen sheet. destructive for a solid red confirm action; pending + error for an async onConfirm."
1991
- >
1992
- <ConfirmDialogDemo />
1993
- </ComponentCard>
1994
-
1995
- {/* Popover */}
1996
- <ComponentCard
1997
- name="Popover"
1998
- importLine="import { Popover } from '@sovereignfs/ui';"
1999
- usage="Floating panel anchored below a trigger. Non-modal. Closes on outside click or Escape. Left or right aligned."
2000
- >
2001
- <PopoverDemo />
2002
- </ComponentCard>
2003
-
2004
- {/* Menu */}
2005
- <ComponentCard
2006
- name="Menu"
2007
- importLine="import { Menu } from '@sovereignfs/ui';"
2008
- usage="Adaptive action menu: Popover on desktop, Drawer on mobile. Same items list renders in both — for '⋯' row/list actions. Entries can be a plain item, a { type: 'label' } section heading, a { type: 'separator' } divider, or a checkable item (pass checked on every item in the group) for a mutually-exclusive set like sort order."
2009
- >
2010
- <MenuDemo />
2011
- </ComponentCard>
2012
-
2013
- {/* Calendar */}
2014
- <ComponentCard
2015
- name="Calendar"
2016
- importLine="import { Calendar } from '@sovereignfs/ui';"
2017
- usage="Keyboard-navigable month grid. Date-only (no time/range yet). Arrow keys, Home/End, PageUp/PageDown, Enter/Space to select."
2018
- >
2019
- <CalendarDemo />
2020
- </ComponentCard>
2021
-
2022
- {/* DatePicker */}
2023
- <ComponentCard
2024
- name="DatePicker"
2025
- importLine="import { DatePicker } from '@sovereignfs/ui';"
2026
- usage="Form field pairing a trigger with Calendar: Popover on desktop, Drawer on mobile. Built-in trigger, unlike Menu's caller-supplied one."
2027
- >
2028
- <DatePickerDemo />
2029
- </ComponentCard>
2030
-
2031
- {/* SystemBanner */}
2032
- <ComponentCard
2033
- name="SystemBanner"
2034
- importLine="import { SystemBanner } from '@sovereignfs/ui';"
2035
- usage="Full-width sticky strip for platform-level notices. Three variants: info, warning, error. Dismissible optional."
2036
- >
2037
- <div style={{ width: '100%', display: 'flex', flexDirection: 'column', gap: 4 }}>
2038
- <SystemBanner variant="info">Read-only mode during migration.</SystemBanner>
2039
- <SystemBanner variant="warning">License expires in 7 days.</SystemBanner>
2040
- <SystemBanner variant="error">Maintenance mode active.</SystemBanner>
2041
- </div>
2042
- </ComponentCard>
2043
-
2044
- {/* Toast */}
2045
- <ComponentCard
2046
- name="Toast"
2047
- importLine="import { ToastProvider, useToast } from '@sovereignfs/ui';"
2048
- usage="Fixed top-right notification stack. Wrap app in ToastProvider; call useToast().show() imperatively. Six categories."
2049
- >
2050
- <ToastProvider>
2051
- <ToastDemo />
2052
- </ToastProvider>
2053
- </ComponentCard>
2054
-
2055
- {/* Card */}
2056
- <ComponentCard
2057
- name="Card"
2058
- importLine="import { Card } from '@sovereignfs/ui';"
2059
- usage="Surface container with border, shadow, and padding. Use as='article' or 'li' for semantics. Add interactive for hover/focus styles on clickable cards."
2060
- >
2061
- <div style={{ display: 'flex', flexDirection: 'column', gap: 8, width: '100%' }}>
2062
- <Card padding="sm">Small padding card</Card>
2063
- <Card padding="md" interactive>
2064
- Interactive card — hover me
2065
- </Card>
2066
- </div>
2067
- </ComponentCard>
2068
-
2069
- {/* FormField */}
2070
- <ComponentCard
2071
- name="FormField"
2072
- importLine="import { FormField } from '@sovereignfs/ui';"
2073
- usage="Accessible label + input wrapper. The render-prop `children` receives field props (id, aria-describedby, aria-invalid) to spread onto the control, so hint/error text stays wired to it."
2074
- >
2075
- <div style={{ display: 'flex', flexDirection: 'column', gap: 12, width: '100%' }}>
2076
- <FormField label="Email" hint="We'll never share this." id="ov-email">
2077
- {(field) => <Input {...field} type="email" placeholder="you@example.com" />}
2078
- </FormField>
2079
- <FormField label="Password" error="Must be 8+ characters." id="ov-pw">
2080
- {(field) => <Input {...field} type="password" />}
2081
- </FormField>
2082
- </div>
2083
- </ComponentCard>
2084
-
2085
- {/* Textarea */}
2086
- <ComponentCard
2087
- name="Textarea"
2088
- importLine="import { Textarea } from '@sovereignfs/ui';"
2089
- usage="The primitive multi-line text field. Forwards all native textarea props. Pair with FormField or a <label> for accessibility."
2090
- >
2091
- <Textarea
2092
- aria-label="Description"
2093
- placeholder="Add a description…"
2094
- style={{ width: '100%' }}
2095
- />
2096
- </ComponentCard>
2097
-
2098
- {/* CodeTextarea */}
2099
- <ComponentCard
2100
- name="CodeTextarea"
2101
- importLine="import { CodeTextarea } from '@sovereignfs/ui';"
2102
- usage="Monospace textarea for Markdown, YAML, JSON, and raw frontmatter editing. Pair with FormField for labels and validation."
2103
- >
2104
- <CodeTextarea
2105
- aria-label="YAML source"
2106
- defaultValue={'title: Release notes\ntags:\n - launch'}
2107
- rows={5}
2108
- style={{ width: '100%' }}
2109
- />
2110
- </ComponentCard>
2111
-
2112
- {/* SplitPane */}
2113
- <ComponentCard
2114
- name="SplitPane"
2115
- importLine="import { SplitPane } from '@sovereignfs/ui';"
2116
- usage="Responsive editor/preview or list/detail layout. Desktop panes are resizable with pointer and keyboard controls; mobile stacks to one column."
2117
- >
2118
- <SplitPane
2119
- defaultPrimarySize={44}
2120
- primary={
2121
- <div
2122
- style={{
2123
- padding: 'var(--sv-space-3)',
2124
- fontFamily: ffm,
2125
- fontSize: 'var(--sv-font-size-xs)',
2126
- color: 'var(--sv-color-text-primary)',
2127
- }}
2128
- >
2129
- # Draft
2130
- </div>
2131
- }
2132
- secondary={
2133
- <div
2134
- style={{
2135
- padding: 'var(--sv-space-3)',
2136
- fontFamily: ff,
2137
- fontSize: 'var(--sv-font-size-sm)',
2138
- color: 'var(--sv-color-text-primary)',
2139
- }}
2140
- >
2141
- Draft preview
2142
- </div>
2143
- }
2144
- />
2145
- </ComponentCard>
2146
-
2147
- {/* PageHeader */}
2148
- <ComponentCard
2149
- name="PageHeader"
2150
- importLine="import { PageHeader } from '@sovereignfs/ui';"
2151
- usage="Plugin page top section. Title + optional description + right-side action slot. Replaces the hand-rolled .pageHeader pattern in every plugin."
2152
- >
2153
- <div style={{ width: '100%' }}>
2154
- <PageHeader
2155
- title="Users"
2156
- description="Manage who has access to this instance."
2157
- action={<Button size="sm">Invite user</Button>}
2158
- />
2159
- </div>
2160
- </ComponentCard>
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
-
2241
- {/* EmptyState */}
2242
- <ComponentCard
2243
- name="EmptyState"
2244
- importLine="import { EmptyState } from '@sovereignfs/ui';"
2245
- usage="Zero-data placeholder. Icon slot, heading, description, and optional CTA. Use whenever a list or table has no rows."
2246
- >
2247
- <EmptyState
2248
- icon="search"
2249
- heading="No results found"
2250
- description="Try adjusting your search."
2251
- action={
2252
- <Button variant="secondary" size="sm">
2253
- Clear filters
2254
- </Button>
2255
- }
2256
- />
2257
- </ComponentCard>
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
-
2306
- {/* Spinner */}
2307
- <ComponentCard
2308
- name="Spinner"
2309
- importLine="import { Spinner } from '@sovereignfs/ui';"
2310
- usage="CSS-animated loading ring in sm/md/lg sizes matching icon-size tokens. Pauses under prefers-reduced-motion. Sets role='status' with aria-label."
2311
- >
2312
- <div style={{ display: 'flex', alignItems: 'center', gap: 16 }}>
2313
- <Spinner size="sm" />
2314
- <Spinner size="md" />
2315
- <Spinner size="lg" />
2316
- </div>
2317
- </ComponentCard>
2318
-
2319
- {/* Avatar */}
2320
- <ComponentCard
2321
- name="Avatar"
2322
- importLine="import { Avatar } from '@sovereignfs/ui';"
2323
- usage="User representation. Shows image when src loads; falls back to initials derived from name. Three sizes. Always sets alt text for accessibility."
2324
- >
2325
- <div style={{ display: 'flex', alignItems: 'center', gap: 12 }}>
2326
- <Avatar name="Jane Smith" size="sm" />
2327
- <Avatar name="Jane Smith" size="md" />
2328
- <Avatar name="Jane Smith" size="lg" />
2329
- <Avatar name="Admin" src="https://i.pravatar.cc/150?u=sb" size="lg" />
2330
- </div>
2331
- </ComponentCard>
2332
-
2333
- {/* NavTabs */}
2334
- <ComponentCard
2335
- name="NavTabs"
2336
- importLine="import { NavTabs } from '@sovereignfs/ui';"
2337
- usage="Underline-style navigation tabs for plugin-level page routing. Distinct from the contained Tabs component. Scrolls horizontally on mobile with no visible scrollbar."
2338
- >
2339
- <div style={{ width: '100%' }}>
2340
- <NavTabs
2341
- items={[
2342
- { label: 'Profile', href: '#', active: true },
2343
- { label: 'Security', href: '#' },
2344
- { label: 'Preferences', href: '#' },
2345
- { label: 'Data', href: '#' },
2346
- ]}
2347
- />
2348
- </div>
2349
- </ComponentCard>
2350
-
2351
- {/* Tooltip */}
2352
- <ComponentCard
2353
- name="Tooltip"
2354
- importLine="import { Tooltip } from '@sovereignfs/ui';"
2355
- usage="CSS-only hover/focus hint. Four placements. Wired to its trigger via aria-describedby. No JS positioning — RSC-safe."
2356
- >
2357
- <div style={{ display: 'flex', gap: 16, alignItems: 'center' }}>
2358
- <Tooltip content="Saved to your account" side="top">
2359
- <Button variant="secondary" size="sm">
2360
- Save
2361
- </Button>
2362
- </Tooltip>
2363
- <Tooltip content="Cannot be undone" side="right">
2364
- <Button variant="destructive" size="sm">
2365
- Delete
2366
- </Button>
2367
- </Tooltip>
2368
- </div>
2369
- </ComponentCard>
2370
-
2371
- {/* CheckableListRow */}
2372
- <ComponentCard
2373
- name="CheckableListRow"
2374
- importLine="import { CheckableListRow } from '@sovereignfs/ui';"
2375
- usage="Whole-row tap target that toggles a checked state, with strike-through on the label — for 'tap the row to mark it done' lists (not a form checkbox)."
2376
- >
2377
- <CheckableListRowDemo />
2378
- </ComponentCard>
2379
-
2380
- {/* MemberMultiSelect */}
2381
- <ComponentCard
2382
- name="MemberMultiSelect"
2383
- importLine="import { MemberMultiSelect } from '@sovereignfs/ui';"
2384
- usage="Checkbox list for picking any number of people from an already-resolved option set. Domain-agnostic about guest vs. instance-user options; pair with SuggestionInput for search + add."
2385
- >
2386
- <MemberMultiSelectDemo />
2387
- </ComponentCard>
2388
-
2389
- {/* Checkbox */}
2390
- <ComponentCard
2391
- name="Checkbox"
2392
- importLine="import { Checkbox } from '@sovereignfs/ui';"
2393
- usage="Accessible checkbox with optional animated strike-through on the label when checked. Used in task lists. Tap target expands to 44px under (pointer: coarse) without growing the visible 18px box."
2394
- >
2395
- {(() => {
2396
- const [a, setA] = useState(false);
2397
- const [b, setB] = useState(true);
2398
- return (
2399
- <div style={{ display: 'flex', flexDirection: 'column', gap: 8 }}>
2400
- <Checkbox checked={a} onChange={setA} label="Unchecked task" strikeThrough />
2401
- <Checkbox checked={b} onChange={setB} label="Completed task" strikeThrough />
2402
- </div>
2403
- );
2404
- })()}
2405
- </ComponentCard>
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
-
2941
- {/* DragHandleRow */}
2942
- <ComponentCard
2943
- name="DragHandleRow"
2944
- importLine="import { DragHandleRow } from '@sovereignfs/ui';"
2945
- usage="Row wrapper with a drag handle that appears on hover. Attach dnd-kit sortable props via handleProps."
2946
- >
2947
- <div style={{ width: '100%' }}>
2948
- {['First item', 'Second item', 'Third item'].map((label) => (
2949
- <DragHandleRow key={label}>
2950
- <div
2951
- style={{
2952
- padding: 'var(--sv-space-2) var(--sv-space-1)',
2953
- fontSize: 'var(--sv-font-size-sm)',
2954
- color: 'var(--sv-color-text-primary)',
2955
- borderBottom: '1px solid var(--sv-color-border)',
2956
- }}
2957
- >
2958
- {label}
2959
- </div>
2960
- </DragHandleRow>
2961
- ))}
2962
- </div>
2963
- </ComponentCard>
2964
-
2965
- {/* FileDropzone */}
2966
- <ComponentCard
2967
- name="FileDropzone"
2968
- importLine="import { FileDropzone } from '@sovereignfs/ui';"
2969
- usage="Styled drag-and-drop file picker — a dashed-border dropzone wrapping a visually-hidden native file input. Caller owns selected-file state and passes label/hint accordingly."
2970
- >
2971
- <FileDropzoneDemo />
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>
3000
- </div>
3001
- </section>
3002
-
3003
- {/* ── Theming ──────────────────────────────────────────────────── */}
3004
- <section style={{ marginBottom: 'var(--sv-space-12)' }}>
3005
- <SectionHeader
3006
- title="Tenant theming"
3007
- subtitle="The identity is monochrome by default. Tenants add brand color by overriding --sv-color-accent."
3008
- />
3009
- <p
3010
- style={{
3011
- fontSize: 'var(--sv-font-size-sm)',
3012
- color: 'var(--sv-color-text-muted)',
3013
- marginBottom: 'var(--sv-space-4)',
3014
- }}
3015
- >
3016
- Set via the Console → Instance identity section (or the{' '}
3017
- <code style={{ fontFamily: ffm }}>/api/instance/</code> API). Plugin CSS does not need
3018
- to change — all components read{' '}
3019
- <code style={{ fontFamily: ffm }}>--sv-color-accent</code> from the cascade.
3020
- </p>
3021
- <Code>{`/* Operator CSS override — injected by InstanceProvider */
3022
- :root {
3023
- --sv-color-accent: #5c6bc0; /* brand blue */
3024
- --sv-color-accent-hover: #3949ab;
3025
- --sv-color-focus-ring: #5c6bc0;
3026
- --sv-color-text-on-accent: #ffffff; /* must contrast with accent */
3027
- }`}</Code>
3028
-
3029
- <DemoBox style={{ marginTop: 'var(--sv-space-4)' }}>
3030
- <Button style={{ background: '#5c6bc0', borderColor: '#5c6bc0', color: '#fff' }}>
3031
- Themed button
3032
- </Button>
3033
- <span
3034
- style={{
3035
- display: 'inline-block',
3036
- fontFamily: ff,
3037
- fontSize: 'var(--sv-font-size-caption)',
3038
- fontWeight: 600,
3039
- padding: '2px 8px',
3040
- borderRadius: 'var(--sv-radius-sm)',
3041
- background: '#e8eaf6',
3042
- color: '#3949ab',
3043
- border: '1px solid #c5cae9',
3044
- }}
3045
- >
3046
- Owner
3047
- </span>
3048
- <span
3049
- style={{
3050
- fontFamily: ff,
3051
- fontSize: 'var(--sv-font-size-caption)',
3052
- color: 'var(--sv-color-text-muted)',
3053
- }}
3054
- >
3055
- Components read the accent token — brand applied everywhere.
3056
- </span>
3057
- </DemoBox>
3058
- </section>
3059
-
3060
- {/* ── Rules ────────────────────────────────────────────────────── */}
3061
- <section style={{ marginBottom: 'var(--sv-space-8)' }}>
3062
- <SectionHeader
3063
- title="Design rules"
3064
- subtitle="The short list of things that break dark mode or instance theming if ignored."
3065
- />
3066
- <div
3067
- style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 'var(--sv-space-5)' }}
3068
- >
3069
- <div
3070
- style={{
3071
- background: 'var(--sv-color-success-surface)',
3072
- border: '1px solid var(--sv-color-success-border)',
3073
- borderRadius: 'var(--sv-radius-lg)',
3074
- padding: 'var(--sv-space-4)',
3075
- }}
3076
- >
3077
- <p
3078
- style={{
3079
- fontWeight: 600,
3080
- color: 'var(--sv-color-success-text)',
3081
- marginBottom: 'var(--sv-space-3)',
3082
- fontSize: 'var(--sv-font-size-sm)',
3083
- }}
3084
- >
3085
- ✓ Do
3086
- </p>
3087
- <ul
3088
- style={{
3089
- margin: 0,
3090
- padding: '0 0 0 var(--sv-space-4)',
3091
- fontSize: 'var(--sv-font-size-sm)',
3092
- color: 'var(--sv-color-text-primary)',
3093
- lineHeight: 1.8,
3094
- }}
3095
- >
3096
- <li>
3097
- Use <code style={{ fontFamily: ffm }}>--sv-color-*</code> semantic tokens
3098
- </li>
3099
- <li>
3100
- Use <code style={{ fontFamily: ffm }}>--sv-space-*</code>,{' '}
3101
- <code style={{ fontFamily: ffm }}>--sv-radius-*</code> scale tokens
3102
- </li>
3103
- <li>
3104
- Use <code style={{ fontFamily: ffm }}>--sv-font-family</code> and{' '}
3105
- <code style={{ fontFamily: ffm }}>--sv-font-family-mono</code>
3106
- </li>
3107
- <li>
3108
- Import components from <code style={{ fontFamily: ffm }}>@sovereignfs/ui</code>
3109
- </li>
3110
- <li>
3111
- Use <code style={{ fontFamily: ffm }}>--sv-shadow-*</code> for elevation
3112
- </li>
3113
- </ul>
3114
- </div>
3115
- <div
3116
- style={{
3117
- background: 'var(--sv-color-error-surface)',
3118
- border: '1px solid var(--sv-color-error-border)',
3119
- borderRadius: 'var(--sv-radius-lg)',
3120
- padding: 'var(--sv-space-4)',
3121
- }}
3122
- >
3123
- <p
3124
- style={{
3125
- fontWeight: 600,
3126
- color: 'var(--sv-color-error-text)',
3127
- marginBottom: 'var(--sv-space-3)',
3128
- fontSize: 'var(--sv-font-size-sm)',
3129
- }}
3130
- >
3131
- ✗ Don't
3132
- </p>
3133
- <ul
3134
- style={{
3135
- margin: 0,
3136
- padding: '0 0 0 var(--sv-space-4)',
3137
- fontSize: 'var(--sv-font-size-sm)',
3138
- color: 'var(--sv-color-text-primary)',
3139
- lineHeight: 1.8,
3140
- }}
3141
- >
3142
- <li>
3143
- Hardcode hex values like <code style={{ fontFamily: ffm }}>#333</code>
3144
- </li>
3145
- <li>
3146
- Use primitive tokens like <code style={{ fontFamily: ffm }}>--sv-grey-900</code>{' '}
3147
- directly
3148
- </li>
3149
- <li>Use Tailwind classes or runtime CSS-in-JS</li>
3150
- <li>
3151
- Import from <code style={{ fontFamily: ffm }}>runtime/src</code> (SDK boundary)
3152
- </li>
3153
- <li>
3154
- Override <code style={{ fontFamily: ffm }}>--sv-color-accent</code> from plugin
3155
- CSS
3156
- </li>
3157
- </ul>
3158
- </div>
3159
- </div>
3160
- </section>
3161
- </div>
3162
- </div>
3163
- );
3164
- }
3165
-
3166
- // ---------------------------------------------------------------------------
3167
- // Story
3168
- // ---------------------------------------------------------------------------
3169
-
3170
- const meta = {
3171
- title: 'Overview',
3172
- component: OverviewPage,
3173
- parameters: {
3174
- layout: 'fullscreen',
3175
- docs: {
3176
- description: {
3177
- component:
3178
- 'Complete reference for plugin developers — components, tokens, theming, and design rules in one page.',
3179
- },
3180
- },
3181
- },
3182
- } satisfies Meta<typeof OverviewPage>;
3183
-
3184
- export default meta;
3185
- type Story = StoryObj<typeof meta>;
3186
-
3187
- export const DesignSystem: Story = {};