@sovereignfs/ui 0.83.1 → 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 (556) hide show
  1. package/LICENSE +373 -661
  2. package/NOTICE +8 -0
  3. package/README.md +4 -6
  4. package/dist/AppsLauncher.module.css +6 -0
  5. package/dist/Button.module.css +5 -0
  6. package/dist/Card.module.css +6 -0
  7. package/dist/CardTile.module.css +6 -0
  8. package/dist/CheckableListRow.module.css +8 -0
  9. package/dist/Checkbox.module.css +8 -0
  10. package/dist/CodeTextarea.module.css +6 -0
  11. package/dist/ColorPicker.module.css +6 -0
  12. package/dist/Combobox.module.css +6 -0
  13. package/dist/Command.module.css +6 -0
  14. package/dist/ConfirmDialog.module.css +6 -0
  15. package/dist/DatePicker.module.css +6 -0
  16. package/dist/Dialog.module.css +1 -1
  17. package/dist/DragHandleRow.module.css +6 -0
  18. package/dist/FileDropzone.module.css +25 -1
  19. package/dist/Header.module.css +6 -0
  20. package/dist/IconPicker.module.css +14 -4
  21. package/dist/Input.module.css +6 -0
  22. package/dist/LegalLinks.module.css +9 -2
  23. package/dist/Markdown.module.css +5 -0
  24. package/dist/Message.module.css +9 -0
  25. package/dist/MobileAppsDrawer.module.css +9 -5
  26. package/dist/MobileFooter.module.css +6 -0
  27. package/dist/NavList.module.css +11 -2
  28. package/dist/NavTabs.module.css +7 -1
  29. package/dist/NotificationsPanel.module.css +10 -0
  30. package/dist/QrCode.module.css +17 -0
  31. package/dist/QuantityStepper.module.css +4 -2
  32. package/dist/RadioGroup.module.css +6 -0
  33. package/dist/SegmentedControl.module.css +6 -0
  34. package/dist/Select.module.css +6 -0
  35. package/dist/Sheet.module.css +16 -1
  36. package/dist/SplitPane.module.css +1 -1
  37. package/dist/StepStrip.module.css +5 -3
  38. package/dist/SuggestionInput.module.css +12 -1
  39. package/dist/SwipableMobileCarouselDots.module.css +6 -0
  40. package/dist/SystemBanner.module.css +6 -0
  41. package/dist/Tabs.module.css +6 -0
  42. package/dist/TagInput.module.css +6 -0
  43. package/dist/Textarea.module.css +6 -0
  44. package/dist/Toast.module.css +5 -0
  45. package/dist/Toggle.module.css +7 -0
  46. package/dist/Tooltip.module.css +6 -0
  47. package/dist/UserMenu.module.css +5 -0
  48. package/dist/fonts/OFL.txt +93 -0
  49. package/dist/fonts/hanken-grotesk-cyrillic-ext-wght-normal.woff2 +0 -0
  50. package/dist/fonts/hanken-grotesk-latin-ext-wght-normal.woff2 +0 -0
  51. package/dist/fonts/hanken-grotesk-latin-wght-normal.woff2 +0 -0
  52. package/dist/fonts/hanken-grotesk-vietnamese-wght-normal.woff2 +0 -0
  53. package/dist/index.d.ts +121 -43
  54. package/dist/index.js +1410 -1152
  55. package/dist/tokens/fonts.css +53 -0
  56. package/dist/tokens/primitives.css +12 -12
  57. package/dist/tokens.css +2 -1
  58. package/package.json +24 -13
  59. package/src/__tests__/motion.test.tsx +0 -105
  60. package/src/__tests__/package-json.test.ts +0 -8
  61. package/src/components/Accordion/Accordion.module.css +0 -3
  62. package/src/components/Accordion/Accordion.stories.tsx +0 -110
  63. package/src/components/Accordion/Accordion.tsx +0 -53
  64. package/src/components/Accordion/__tests__/Accordion.test.tsx +0 -61
  65. package/src/components/Alert/Alert.module.css +0 -62
  66. package/src/components/Alert/Alert.stories.tsx +0 -96
  67. package/src/components/Alert/Alert.tsx +0 -58
  68. package/src/components/Alert/__tests__/Alert.test.tsx +0 -75
  69. package/src/components/AppsLauncher/AppsLauncher.module.css +0 -96
  70. package/src/components/AppsLauncher/AppsLauncher.stories.tsx +0 -58
  71. package/src/components/AppsLauncher/AppsLauncher.tsx +0 -117
  72. package/src/components/AppsLauncher/__tests__/AppsLauncher.test.tsx +0 -52
  73. package/src/components/AspectRatio/AspectRatio.module.css +0 -15
  74. package/src/components/AspectRatio/AspectRatio.stories.tsx +0 -78
  75. package/src/components/AspectRatio/AspectRatio.tsx +0 -21
  76. package/src/components/AspectRatio/__tests__/AspectRatio.test.tsx +0 -27
  77. package/src/components/Avatar/Avatar.module.css +0 -38
  78. package/src/components/Avatar/Avatar.tsx +0 -42
  79. package/src/components/Badge/Badge.module.css +0 -177
  80. package/src/components/Badge/Badge.stories.tsx +0 -238
  81. package/src/components/Badge/Badge.tsx +0 -84
  82. package/src/components/Badge/__tests__/Badge.test.tsx +0 -90
  83. package/src/components/BalanceChip/BalanceChip.module.css +0 -32
  84. package/src/components/BalanceChip/BalanceChip.stories.tsx +0 -45
  85. package/src/components/BalanceChip/BalanceChip.tsx +0 -33
  86. package/src/components/Breadcrumb/Breadcrumb.module.css +0 -49
  87. package/src/components/Breadcrumb/Breadcrumb.stories.tsx +0 -47
  88. package/src/components/Breadcrumb/Breadcrumb.tsx +0 -57
  89. package/src/components/Breadcrumb/__tests__/Breadcrumb.test.tsx +0 -52
  90. package/src/components/Button/Button.module.css +0 -169
  91. package/src/components/Button/Button.stories.tsx +0 -83
  92. package/src/components/Button/Button.tsx +0 -52
  93. package/src/components/Button/__tests__/Button.test.tsx +0 -66
  94. package/src/components/ButtonGroup/ButtonGroup.module.css +0 -29
  95. package/src/components/ButtonGroup/ButtonGroup.stories.tsx +0 -57
  96. package/src/components/ButtonGroup/ButtonGroup.tsx +0 -27
  97. package/src/components/ButtonGroup/__tests__/ButtonGroup.test.tsx +0 -29
  98. package/src/components/Calendar/Calendar.module.css +0 -126
  99. package/src/components/Calendar/Calendar.tsx +0 -213
  100. package/src/components/Calendar/__tests__/Calendar.test.tsx +0 -98
  101. package/src/components/Calendar/dateUtils.ts +0 -97
  102. package/src/components/Card/Card.module.css +0 -42
  103. package/src/components/Card/Card.tsx +0 -34
  104. package/src/components/CardTile/CardTile.module.css +0 -181
  105. package/src/components/CardTile/CardTile.stories.tsx +0 -86
  106. package/src/components/CardTile/CardTile.tsx +0 -128
  107. package/src/components/CardTile/__tests__/CardTile.test.tsx +0 -56
  108. package/src/components/CheckableListRow/CheckableListRow.module.css +0 -95
  109. package/src/components/CheckableListRow/CheckableListRow.stories.tsx +0 -99
  110. package/src/components/CheckableListRow/CheckableListRow.tsx +0 -89
  111. package/src/components/Checkbox/Checkbox.module.css +0 -111
  112. package/src/components/Checkbox/Checkbox.tsx +0 -78
  113. package/src/components/CodeTextarea/CodeTextarea.module.css +0 -53
  114. package/src/components/CodeTextarea/CodeTextarea.stories.tsx +0 -77
  115. package/src/components/CodeTextarea/CodeTextarea.tsx +0 -36
  116. package/src/components/Collapsible/Collapsible.module.css +0 -70
  117. package/src/components/Collapsible/Collapsible.stories.tsx +0 -62
  118. package/src/components/Collapsible/Collapsible.tsx +0 -66
  119. package/src/components/Collapsible/__tests__/Collapsible.test.tsx +0 -71
  120. package/src/components/ColorPicker/ColorPicker.module.css +0 -132
  121. package/src/components/ColorPicker/ColorPicker.stories.tsx +0 -80
  122. package/src/components/ColorPicker/ColorPicker.tsx +0 -157
  123. package/src/components/ColorPicker/__tests__/ColorPicker.test.tsx +0 -117
  124. package/src/components/Combobox/Combobox.module.css +0 -122
  125. package/src/components/Combobox/Combobox.stories.tsx +0 -79
  126. package/src/components/Combobox/Combobox.tsx +0 -186
  127. package/src/components/Combobox/__tests__/Combobox.test.tsx +0 -158
  128. package/src/components/Command/Command.module.css +0 -80
  129. package/src/components/Command/Command.stories.tsx +0 -104
  130. package/src/components/Command/Command.tsx +0 -168
  131. package/src/components/Command/__tests__/Command.test.tsx +0 -162
  132. package/src/components/ConfirmDialog/ConfirmDialog.module.css +0 -113
  133. package/src/components/ConfirmDialog/ConfirmDialog.tsx +0 -150
  134. package/src/components/ConfirmDialog/__tests__/ConfirmDialog.test.tsx +0 -214
  135. package/src/components/ConsentPrompt/ConsentPrompt.module.css +0 -21
  136. package/src/components/ConsentPrompt/ConsentPrompt.stories.tsx +0 -31
  137. package/src/components/ConsentPrompt/ConsentPrompt.tsx +0 -66
  138. package/src/components/ContextMenu/ContextMenu.stories.tsx +0 -81
  139. package/src/components/ContextMenu/ContextMenu.tsx +0 -100
  140. package/src/components/ContextMenu/__tests__/ContextMenu.test.tsx +0 -87
  141. package/src/components/CurrencyInput/CurrencyInput.stories.tsx +0 -50
  142. package/src/components/CurrencyInput/CurrencyInput.tsx +0 -60
  143. package/src/components/DataTable/DataTable.module.css +0 -28
  144. package/src/components/DataTable/DataTable.stories.tsx +0 -74
  145. package/src/components/DataTable/DataTable.tsx +0 -154
  146. package/src/components/DataTable/__tests__/DataTable.test.tsx +0 -123
  147. package/src/components/DatePicker/DatePicker.module.css +0 -61
  148. package/src/components/DatePicker/DatePicker.tsx +0 -103
  149. package/src/components/DatePicker/__tests__/DatePicker.test.tsx +0 -89
  150. package/src/components/DeviceOnlyGate/DeviceOnlyGate.tsx +0 -58
  151. package/src/components/DeviceOnlyGate/__tests__/DeviceOnlyGate.test.tsx +0 -45
  152. package/src/components/DeviceStorageKeyGate/DeviceStorageKeyGate.tsx +0 -100
  153. package/src/components/DeviceStorageKeyGate/__tests__/DeviceStorageKeyGate.test.tsx +0 -119
  154. package/src/components/Dialog/Dialog.module.css +0 -335
  155. package/src/components/Dialog/Dialog.stories.tsx +0 -324
  156. package/src/components/Dialog/Dialog.tsx +0 -254
  157. package/src/components/Dialog/__tests__/Dialog.test.tsx +0 -318
  158. package/src/components/DragHandleRow/DragHandleRow.module.css +0 -57
  159. package/src/components/DragHandleRow/DragHandleRow.tsx +0 -42
  160. package/src/components/Drawer/Drawer.module.css +0 -121
  161. package/src/components/Drawer/Drawer.stories.tsx +0 -132
  162. package/src/components/Drawer/Drawer.tsx +0 -175
  163. package/src/components/Drawer/__tests__/Drawer.test.tsx +0 -226
  164. package/src/components/EmptyState/EmptyState.module.css +0 -27
  165. package/src/components/EmptyState/EmptyState.tsx +0 -27
  166. package/src/components/FileDropzone/FileDropzone.module.css +0 -63
  167. package/src/components/FileDropzone/FileDropzone.tsx +0 -109
  168. package/src/components/FileDropzone/__tests__/FileDropzone.test.tsx +0 -67
  169. package/src/components/FormField/FormField.module.css +0 -33
  170. package/src/components/FormField/FormField.tsx +0 -78
  171. package/src/components/FormField/__tests__/FormField.test.tsx +0 -83
  172. package/src/components/GripIcon/GripIcon.tsx +0 -27
  173. package/src/components/Header/Header.module.css +0 -62
  174. package/src/components/Header/Header.stories.tsx +0 -452
  175. package/src/components/Header/Header.tsx +0 -102
  176. package/src/components/Header/__tests__/Header.test.tsx +0 -104
  177. package/src/components/HeaderFooterLayout/HeaderFooterLayout.module.css +0 -21
  178. package/src/components/HeaderFooterLayout/HeaderFooterLayout.stories.tsx +0 -111
  179. package/src/components/HeaderFooterLayout/HeaderFooterLayout.tsx +0 -54
  180. package/src/components/HeaderFooterLayout/__tests__/HeaderFooterLayout.test.tsx +0 -58
  181. package/src/components/HoverCard/HoverCard.module.css +0 -3
  182. package/src/components/HoverCard/HoverCard.stories.tsx +0 -66
  183. package/src/components/HoverCard/HoverCard.tsx +0 -111
  184. package/src/components/HoverCard/__tests__/HoverCard.test.tsx +0 -99
  185. package/src/components/Icon/Icon.module.css +0 -27
  186. package/src/components/Icon/Icon.stories.tsx +0 -95
  187. package/src/components/Icon/Icon.tsx +0 -43
  188. package/src/components/Icon/__tests__/Icon.test.tsx +0 -40
  189. package/src/components/Icon/icons/activity.tsx +0 -20
  190. package/src/components/Icon/icons/alert-triangle.tsx +0 -22
  191. package/src/components/Icon/icons/apple.tsx +0 -21
  192. package/src/components/Icon/icons/arrow-left-right.tsx +0 -23
  193. package/src/components/Icon/icons/banana.tsx +0 -21
  194. package/src/components/Icon/icons/beef.tsx +0 -22
  195. package/src/components/Icon/icons/beer.tsx +0 -24
  196. package/src/components/Icon/icons/bell.tsx +0 -21
  197. package/src/components/Icon/icons/bold.tsx +0 -20
  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-chevron-left.tsx +0 -21
  208. package/src/components/Icon/icons/circle-x.tsx +0 -22
  209. package/src/components/Icon/icons/code.tsx +0 -21
  210. package/src/components/Icon/icons/coffee.tsx +0 -23
  211. package/src/components/Icon/icons/cookie.tsx +0 -25
  212. package/src/components/Icon/icons/copy.tsx +0 -21
  213. package/src/components/Icon/icons/credit-card.tsx +0 -21
  214. package/src/components/Icon/icons/croissant.tsx +0 -24
  215. package/src/components/Icon/icons/cup-soda.tsx +0 -23
  216. package/src/components/Icon/icons/database.tsx +0 -22
  217. package/src/components/Icon/icons/download.tsx +0 -22
  218. package/src/components/Icon/icons/drumstick.tsx +0 -21
  219. package/src/components/Icon/icons/egg.tsx +0 -20
  220. package/src/components/Icon/icons/ellipsis-vertical.tsx +0 -22
  221. package/src/components/Icon/icons/external-link.tsx +0 -22
  222. package/src/components/Icon/icons/eye-off.tsx +0 -23
  223. package/src/components/Icon/icons/eye.tsx +0 -21
  224. package/src/components/Icon/icons/file-text.tsx +0 -24
  225. package/src/components/Icon/icons/file.tsx +0 -21
  226. package/src/components/Icon/icons/fish.tsx +0 -25
  227. package/src/components/Icon/icons/folder-closed.tsx +0 -21
  228. package/src/components/Icon/icons/folder-open.tsx +0 -20
  229. package/src/components/Icon/icons/folder.tsx +0 -20
  230. package/src/components/Icon/icons/folders.tsx +0 -21
  231. package/src/components/Icon/icons/grid-2x2.tsx +0 -22
  232. package/src/components/Icon/icons/hat-glasses.tsx +0 -24
  233. package/src/components/Icon/icons/history.tsx +0 -22
  234. package/src/components/Icon/icons/house.tsx +0 -21
  235. package/src/components/Icon/icons/inbox.tsx +0 -21
  236. package/src/components/Icon/icons/index.ts +0 -394
  237. package/src/components/Icon/icons/info.tsx +0 -22
  238. package/src/components/Icon/icons/italic.tsx +0 -22
  239. package/src/components/Icon/icons/layers.tsx +0 -22
  240. package/src/components/Icon/icons/layout-dashboard.tsx +0 -23
  241. package/src/components/Icon/icons/layout-grid.tsx +0 -23
  242. package/src/components/Icon/icons/link.tsx +0 -21
  243. package/src/components/Icon/icons/list-ordered.tsx +0 -25
  244. package/src/components/Icon/icons/list.tsx +0 -25
  245. package/src/components/Icon/icons/lock.tsx +0 -21
  246. package/src/components/Icon/icons/log-out.tsx +0 -22
  247. package/src/components/Icon/icons/luggage.tsx +0 -24
  248. package/src/components/Icon/icons/mail.tsx +0 -21
  249. package/src/components/Icon/icons/map-pin.tsx +0 -21
  250. package/src/components/Icon/icons/menu.tsx +0 -22
  251. package/src/components/Icon/icons/milk.tsx +0 -22
  252. package/src/components/Icon/icons/minus.tsx +0 -20
  253. package/src/components/Icon/icons/package.tsx +0 -23
  254. package/src/components/Icon/icons/paint-bucket.tsx +0 -23
  255. package/src/components/Icon/icons/panel-left.tsx +0 -21
  256. package/src/components/Icon/icons/pencil.tsx +0 -21
  257. package/src/components/Icon/icons/pin.tsx +0 -21
  258. package/src/components/Icon/icons/pizza.tsx +0 -24
  259. package/src/components/Icon/icons/plus.tsx +0 -21
  260. package/src/components/Icon/icons/rectangle-ellipsis.tsx +0 -23
  261. package/src/components/Icon/icons/refresh-cw.tsx +0 -23
  262. package/src/components/Icon/icons/rotate-ccw.tsx +0 -21
  263. package/src/components/Icon/icons/rotate-cw.tsx +0 -21
  264. package/src/components/Icon/icons/route.tsx +0 -22
  265. package/src/components/Icon/icons/salad.tsx +0 -24
  266. package/src/components/Icon/icons/search.tsx +0 -21
  267. package/src/components/Icon/icons/send.tsx +0 -21
  268. package/src/components/Icon/icons/settings.tsx +0 -21
  269. package/src/components/Icon/icons/share-2.tsx +0 -24
  270. package/src/components/Icon/icons/sheet.tsx +0 -24
  271. package/src/components/Icon/icons/shield.tsx +0 -20
  272. package/src/components/Icon/icons/shopping-basket.tsx +0 -26
  273. package/src/components/Icon/icons/sliders-horizontal.tsx +0 -28
  274. package/src/components/Icon/icons/smartphone.tsx +0 -21
  275. package/src/components/Icon/icons/snowflake.tsx +0 -31
  276. package/src/components/Icon/icons/spray-can.tsx +0 -29
  277. package/src/components/Icon/icons/square-kanban.tsx +0 -23
  278. package/src/components/Icon/icons/table.tsx +0 -23
  279. package/src/components/Icon/icons/trash-2.tsx +0 -24
  280. package/src/components/Icon/icons/upload.tsx +0 -22
  281. package/src/components/Icon/icons/user-round-plus.tsx +0 -23
  282. package/src/components/Icon/icons/user.tsx +0 -21
  283. package/src/components/Icon/icons/users.tsx +0 -23
  284. package/src/components/Icon/icons/wine.tsx +0 -23
  285. package/src/components/Icon/icons/x.tsx +0 -21
  286. package/src/components/IconPicker/IconPicker.module.css +0 -63
  287. package/src/components/IconPicker/IconPicker.stories.tsx +0 -84
  288. package/src/components/IconPicker/IconPicker.tsx +0 -90
  289. package/src/components/Input/Input.module.css +0 -52
  290. package/src/components/Input/Input.stories.tsx +0 -86
  291. package/src/components/Input/Input.tsx +0 -20
  292. package/src/components/Input/__tests__/Input.test.tsx +0 -22
  293. package/src/components/Item/Item.module.css +0 -70
  294. package/src/components/Item/Item.stories.tsx +0 -62
  295. package/src/components/Item/Item.tsx +0 -54
  296. package/src/components/Item/__tests__/Item.test.tsx +0 -39
  297. package/src/components/Kbd/Kbd.module.css +0 -14
  298. package/src/components/Kbd/Kbd.stories.tsx +0 -32
  299. package/src/components/Kbd/Kbd.tsx +0 -12
  300. package/src/components/Kbd/__tests__/Kbd.test.tsx +0 -14
  301. package/src/components/Label/Label.module.css +0 -11
  302. package/src/components/Label/Label.stories.tsx +0 -40
  303. package/src/components/Label/Label.tsx +0 -24
  304. package/src/components/Label/__tests__/Label.test.tsx +0 -32
  305. package/src/components/LegalLinks/LegalLinks.module.css +0 -24
  306. package/src/components/LegalLinks/LegalLinks.stories.tsx +0 -22
  307. package/src/components/LegalLinks/LegalLinks.tsx +0 -40
  308. package/src/components/Markdown/Markdown.module.css +0 -73
  309. package/src/components/Markdown/Markdown.stories.tsx +0 -40
  310. package/src/components/Markdown/Markdown.tsx +0 -305
  311. package/src/components/Markdown/__tests__/Markdown.test.tsx +0 -144
  312. package/src/components/Marker/Marker.module.css +0 -33
  313. package/src/components/Marker/Marker.stories.tsx +0 -39
  314. package/src/components/Marker/Marker.tsx +0 -34
  315. package/src/components/Marker/__tests__/Marker.test.tsx +0 -23
  316. package/src/components/MemberMultiSelect/MemberMultiSelect.module.css +0 -27
  317. package/src/components/MemberMultiSelect/MemberMultiSelect.stories.tsx +0 -74
  318. package/src/components/MemberMultiSelect/MemberMultiSelect.tsx +0 -67
  319. package/src/components/Menu/Menu.module.css +0 -90
  320. package/src/components/Menu/Menu.tsx +0 -209
  321. package/src/components/Menu/__tests__/Menu.test.tsx +0 -227
  322. package/src/components/Menubar/Menubar.stories.tsx +0 -52
  323. package/src/components/Menubar/Menubar.tsx +0 -42
  324. package/src/components/Menubar/__tests__/Menubar.test.tsx +0 -45
  325. package/src/components/Message/Message.module.css +0 -99
  326. package/src/components/Message/Message.stories.tsx +0 -79
  327. package/src/components/Message/Message.tsx +0 -41
  328. package/src/components/Message/__tests__/Message.test.tsx +0 -46
  329. package/src/components/MessageScroller/MessageScroller.module.css +0 -65
  330. package/src/components/MessageScroller/MessageScroller.stories.tsx +0 -84
  331. package/src/components/MessageScroller/MessageScroller.tsx +0 -84
  332. package/src/components/MessageScroller/__tests__/MessageScroller.test.tsx +0 -105
  333. package/src/components/MobileAppsDrawer/MobileAppsDrawer.module.css +0 -82
  334. package/src/components/MobileAppsDrawer/MobileAppsDrawer.tsx +0 -78
  335. package/src/components/MobileAppsDrawer/__tests__/MobileAppsDrawer.test.tsx +0 -80
  336. package/src/components/MobileFooter/MobileFooter.module.css +0 -73
  337. package/src/components/MobileFooter/MobileFooter.stories.tsx +0 -47
  338. package/src/components/MobileFooter/MobileFooter.tsx +0 -122
  339. package/src/components/MobileFooter/__tests__/MobileFooter.test.tsx +0 -129
  340. package/src/components/MobileHeader/MobileHeader.module.css +0 -76
  341. package/src/components/MobileHeader/MobileHeader.stories.tsx +0 -57
  342. package/src/components/MobileHeader/MobileHeader.tsx +0 -90
  343. package/src/components/MobileHeader/__tests__/MobileHeader.test.tsx +0 -113
  344. package/src/components/NavList/NavList.module.css +0 -130
  345. package/src/components/NavList/NavList.tsx +0 -136
  346. package/src/components/NavList/__tests__/NavList.test.tsx +0 -109
  347. package/src/components/NavTabs/NavTabs.module.css +0 -50
  348. package/src/components/NavTabs/NavTabs.tsx +0 -61
  349. package/src/components/NavigationMenu/NavigationMenu.module.css +0 -53
  350. package/src/components/NavigationMenu/NavigationMenu.stories.tsx +0 -58
  351. package/src/components/NavigationMenu/NavigationMenu.tsx +0 -125
  352. package/src/components/NavigationMenu/__tests__/NavigationMenu.test.tsx +0 -93
  353. package/src/components/NotificationsPanel/NotificationsPanel.module.css +0 -258
  354. package/src/components/NotificationsPanel/NotificationsPanel.stories.tsx +0 -61
  355. package/src/components/NotificationsPanel/NotificationsPanel.tsx +0 -187
  356. package/src/components/NotificationsPanel/__tests__/NotificationsPanel.test.tsx +0 -78
  357. package/src/components/OfflineGate/OfflineGate.tsx +0 -44
  358. package/src/components/OfflineGate/__tests__/OfflineGate.test.tsx +0 -66
  359. package/src/components/OverlayHeader/OverlayHeader.module.css +0 -98
  360. package/src/components/OverlayHeader/OverlayHeader.tsx +0 -77
  361. package/src/components/OverlayHeader/__tests__/OverlayHeader.test.tsx +0 -69
  362. package/src/components/PageContainer/PageContainer.module.css +0 -82
  363. package/src/components/PageContainer/PageContainer.tsx +0 -64
  364. package/src/components/PageContainer/__tests__/PageContainer.test.tsx +0 -70
  365. package/src/components/PageHeader/PageHeader.module.css +0 -86
  366. package/src/components/PageHeader/PageHeader.tsx +0 -176
  367. package/src/components/PageHeader/__tests__/PageHeader.test.tsx +0 -103
  368. package/src/components/PageLayout/PageLayout.module.css +0 -51
  369. package/src/components/PageLayout/PageLayout.stories.tsx +0 -73
  370. package/src/components/PageLayout/PageLayout.tsx +0 -51
  371. package/src/components/PageLayout/__tests__/PageLayout.test.tsx +0 -34
  372. package/src/components/Pagination/Pagination.module.css +0 -98
  373. package/src/components/Pagination/Pagination.stories.tsx +0 -49
  374. package/src/components/Pagination/Pagination.tsx +0 -87
  375. package/src/components/Pagination/__tests__/Pagination.test.tsx +0 -56
  376. package/src/components/Popover/Popover.module.css +0 -74
  377. package/src/components/Popover/Popover.stories.tsx +0 -357
  378. package/src/components/Popover/Popover.tsx +0 -290
  379. package/src/components/Popover/__tests__/Popover.test.tsx +0 -263
  380. package/src/components/Progress/Progress.module.css +0 -20
  381. package/src/components/Progress/Progress.stories.tsx +0 -40
  382. package/src/components/Progress/Progress.tsx +0 -35
  383. package/src/components/Progress/__tests__/Progress.test.tsx +0 -31
  384. package/src/components/QuantityStepper/QuantityStepper.module.css +0 -67
  385. package/src/components/QuantityStepper/QuantityStepper.stories.tsx +0 -79
  386. package/src/components/QuantityStepper/QuantityStepper.tsx +0 -107
  387. package/src/components/RadioGroup/RadioGroup.module.css +0 -88
  388. package/src/components/RadioGroup/RadioGroup.stories.tsx +0 -69
  389. package/src/components/RadioGroup/RadioGroup.tsx +0 -99
  390. package/src/components/RadioGroup/__tests__/RadioGroup.test.tsx +0 -59
  391. package/src/components/Resizable/Resizable.module.css +0 -70
  392. package/src/components/Resizable/Resizable.stories.tsx +0 -82
  393. package/src/components/Resizable/Resizable.tsx +0 -313
  394. package/src/components/Resizable/__tests__/Resizable.test.tsx +0 -128
  395. package/src/components/ResponsiveSurface/ResponsiveSurface.tsx +0 -24
  396. package/src/components/ResponsiveSurface/__tests__/ResponsiveSurface.test.tsx +0 -73
  397. package/src/components/RootLayout/RootLayout.module.css +0 -12
  398. package/src/components/RootLayout/RootLayout.stories.tsx +0 -125
  399. package/src/components/RootLayout/RootLayout.tsx +0 -185
  400. package/src/components/RootLayout/__tests__/RootLayout.test.tsx +0 -161
  401. package/src/components/ScrollArea/ScrollArea.module.css +0 -20
  402. package/src/components/ScrollArea/ScrollArea.stories.tsx +0 -57
  403. package/src/components/ScrollArea/ScrollArea.tsx +0 -27
  404. package/src/components/ScrollArea/__tests__/ScrollArea.test.tsx +0 -39
  405. package/src/components/SegmentedControl/SegmentedControl.module.css +0 -55
  406. package/src/components/SegmentedControl/SegmentedControl.stories.tsx +0 -118
  407. package/src/components/SegmentedControl/SegmentedControl.tsx +0 -56
  408. package/src/components/SegmentedControl/__tests__/SegmentedControl.test.tsx +0 -63
  409. package/src/components/Select/Select.module.css +0 -82
  410. package/src/components/Select/Select.stories.tsx +0 -115
  411. package/src/components/Select/Select.tsx +0 -48
  412. package/src/components/Select/__tests__/Select.test.tsx +0 -59
  413. package/src/components/Sheet/Sheet.module.css +0 -58
  414. package/src/components/Sheet/Sheet.tsx +0 -109
  415. package/src/components/Sheet/__tests__/Sheet.test.tsx +0 -99
  416. package/src/components/Slider/Slider.module.css +0 -95
  417. package/src/components/Slider/Slider.stories.tsx +0 -64
  418. package/src/components/Slider/Slider.tsx +0 -77
  419. package/src/components/Slider/__tests__/Slider.test.tsx +0 -45
  420. package/src/components/Spinner/Spinner.module.css +0 -39
  421. package/src/components/Spinner/Spinner.tsx +0 -23
  422. package/src/components/SplitMethodSelector/SplitMethodSelector.stories.tsx +0 -33
  423. package/src/components/SplitMethodSelector/SplitMethodSelector.tsx +0 -43
  424. package/src/components/SplitPane/SplitPane.module.css +0 -85
  425. package/src/components/SplitPane/SplitPane.stories.tsx +0 -160
  426. package/src/components/SplitPane/SplitPane.tsx +0 -149
  427. package/src/components/SplitPane/__tests__/SplitPane.test.tsx +0 -51
  428. package/src/components/StatusBadge/StatusBadge.module.css +0 -59
  429. package/src/components/StatusBadge/StatusBadge.stories.tsx +0 -74
  430. package/src/components/StatusBadge/StatusBadge.tsx +0 -65
  431. package/src/components/StepStrip/StepStrip.module.css +0 -53
  432. package/src/components/StepStrip/StepStrip.tsx +0 -74
  433. package/src/components/SuggestionInput/SuggestionInput.module.css +0 -103
  434. package/src/components/SuggestionInput/SuggestionInput.stories.tsx +0 -144
  435. package/src/components/SuggestionInput/SuggestionInput.tsx +0 -207
  436. package/src/components/SwipableMobileCarousel/SwipableMobileCarousel.module.css +0 -64
  437. package/src/components/SwipableMobileCarousel/SwipableMobileCarousel.tsx +0 -243
  438. package/src/components/SwipableMobileCarousel/SwipableMobileCarouselSlide.tsx +0 -35
  439. package/src/components/SwipableMobileCarousel/SwipableMobileCarouselSlideBody.tsx +0 -42
  440. package/src/components/SwipableMobileCarousel/SwipableMobileCarouselSlideFooter.tsx +0 -18
  441. package/src/components/SwipableMobileCarousel/SwipableMobileCarouselSlideHeader.tsx +0 -21
  442. package/src/components/SwipableMobileCarousel/SwipableMobileCarouselSlideParts.module.css +0 -29
  443. package/src/components/SwipableMobileCarousel/__tests__/SwipableMobileCarousel.test.tsx +0 -214
  444. package/src/components/SwipableMobileCarousel/__tests__/SwipableMobileCarouselDevGuard.test.tsx +0 -61
  445. package/src/components/SwipableMobileCarousel/context.ts +0 -13
  446. package/src/components/SwipableMobileCarouselDots/SwipableMobileCarouselDots.module.css +0 -64
  447. package/src/components/SwipableMobileCarouselDots/SwipableMobileCarouselDots.tsx +0 -73
  448. package/src/components/SwipableMobileCarouselDots/__tests__/SwipableMobileCarouselDots.test.tsx +0 -63
  449. package/src/components/SwipeStack/SwipeStack.module.css +0 -101
  450. package/src/components/SwipeStack/SwipeStack.tsx +0 -199
  451. package/src/components/SwipeStack/SwipeStackCard.tsx +0 -22
  452. package/src/components/SwipeStack/__tests__/SwipeStack.test.tsx +0 -169
  453. package/src/components/SystemBanner/SystemBanner.module.css +0 -79
  454. package/src/components/SystemBanner/SystemBanner.stories.tsx +0 -86
  455. package/src/components/SystemBanner/SystemBanner.tsx +0 -50
  456. package/src/components/SystemBanner/__tests__/SystemBanner.test.tsx +0 -50
  457. package/src/components/Table/Table.module.css +0 -45
  458. package/src/components/Table/Table.stories.tsx +0 -125
  459. package/src/components/Table/Table.tsx +0 -51
  460. package/src/components/Table/__tests__/Table.test.tsx +0 -50
  461. package/src/components/Tabs/Tabs.module.css +0 -56
  462. package/src/components/Tabs/Tabs.stories.tsx +0 -100
  463. package/src/components/Tabs/Tabs.tsx +0 -46
  464. package/src/components/Tabs/__tests__/Tabs.test.tsx +0 -45
  465. package/src/components/TagInput/TagInput.module.css +0 -141
  466. package/src/components/TagInput/TagInput.stories.tsx +0 -109
  467. package/src/components/TagInput/TagInput.tsx +0 -174
  468. package/src/components/TagInput/__tests__/TagInput.test.tsx +0 -69
  469. package/src/components/Textarea/Textarea.module.css +0 -47
  470. package/src/components/Textarea/Textarea.tsx +0 -38
  471. package/src/components/Textarea/__tests__/Textarea.test.tsx +0 -51
  472. package/src/components/ThreeColumnLayout/ThreeColumnLayout.module.css +0 -35
  473. package/src/components/ThreeColumnLayout/ThreeColumnLayout.stories.tsx +0 -152
  474. package/src/components/ThreeColumnLayout/ThreeColumnLayout.tsx +0 -87
  475. package/src/components/ThreeColumnLayout/__tests__/ThreeColumnLayout.test.tsx +0 -78
  476. package/src/components/Toast/Toast.module.css +0 -191
  477. package/src/components/Toast/Toast.stories.tsx +0 -191
  478. package/src/components/Toast/Toast.tsx +0 -139
  479. package/src/components/Toggle/Toggle.module.css +0 -57
  480. package/src/components/Toggle/Toggle.stories.tsx +0 -95
  481. package/src/components/Toggle/Toggle.tsx +0 -39
  482. package/src/components/Toggle/__tests__/Toggle.test.tsx +0 -47
  483. package/src/components/Tooltip/Tooltip.module.css +0 -56
  484. package/src/components/Tooltip/Tooltip.tsx +0 -30
  485. package/src/components/Typography/Typography.module.css +0 -47
  486. package/src/components/Typography/Typography.stories.tsx +0 -62
  487. package/src/components/Typography/Typography.tsx +0 -30
  488. package/src/components/Typography/__tests__/Typography.test.tsx +0 -34
  489. package/src/components/UserMenu/UserMenu.module.css +0 -90
  490. package/src/components/UserMenu/UserMenu.stories.tsx +0 -49
  491. package/src/components/UserMenu/UserMenu.tsx +0 -132
  492. package/src/components/UserMenu/__tests__/UserMenu.test.tsx +0 -77
  493. package/src/css-modules.d.ts +0 -4
  494. package/src/hooks/__tests__/useCarouselRouteSync.test.tsx +0 -126
  495. package/src/hooks/__tests__/useDoubleTap.test.tsx +0 -117
  496. package/src/hooks/__tests__/useIsMobile.test.tsx +0 -80
  497. package/src/hooks/__tests__/useIsOffline.test.tsx +0 -53
  498. package/src/hooks/__tests__/useLongPress.test.tsx +0 -158
  499. package/src/hooks/__tests__/useOfflineTileState.test.tsx +0 -69
  500. package/src/hooks/__tests__/usePublishShellChromeHeight.test.tsx +0 -118
  501. package/src/hooks/__tests__/useReorderSensors.test.ts +0 -26
  502. package/src/hooks/__tests__/useResponsiveLayout.test.tsx +0 -87
  503. package/src/hooks/__tests__/useSnapCarousel.test.tsx +0 -175
  504. package/src/hooks/__tests__/useSwipeReveal.test.tsx +0 -110
  505. package/src/hooks/__tests__/useSwipeStack.test.tsx +0 -155
  506. package/src/hooks/index.ts +0 -33
  507. package/src/hooks/useCarouselRouteSync.ts +0 -92
  508. package/src/hooks/useCommitOnEnterOrBlur.ts +0 -39
  509. package/src/hooks/useDoubleTap.ts +0 -91
  510. package/src/hooks/useIsMobile.ts +0 -33
  511. package/src/hooks/useIsOffline.ts +0 -41
  512. package/src/hooks/useLongPress.ts +0 -185
  513. package/src/hooks/useOfflineTileState.ts +0 -45
  514. package/src/hooks/usePublishShellChromeHeight.ts +0 -96
  515. package/src/hooks/useReorderSensors.ts +0 -88
  516. package/src/hooks/useResponsiveLayout.ts +0 -36
  517. package/src/hooks/useSnapCarousel.ts +0 -284
  518. package/src/hooks/useSwipeReveal.ts +0 -107
  519. package/src/hooks/useSwipeStack.ts +0 -235
  520. package/src/index.ts +0 -311
  521. package/src/motion.ts +0 -78
  522. package/src/overlay-shell.ts +0 -159
  523. package/src/scroll-lock.ts +0 -22
  524. package/src/stories/Avatar.stories.tsx +0 -44
  525. package/src/stories/Card.stories.tsx +0 -35
  526. package/src/stories/Checkbox.stories.tsx +0 -45
  527. package/src/stories/DesignSystemOverview.stories.tsx +0 -3793
  528. package/src/stories/DeviceOnlyGate.stories.tsx +0 -35
  529. package/src/stories/DeviceStorageKeyGate.stories.tsx +0 -57
  530. package/src/stories/DragHandleRow.stories.tsx +0 -41
  531. package/src/stories/EmptyState.stories.tsx +0 -33
  532. package/src/stories/FileDropzone.stories.tsx +0 -69
  533. package/src/stories/FormField.stories.tsx +0 -47
  534. package/src/stories/GripIcon.stories.tsx +0 -29
  535. package/src/stories/InteractionHooks.stories.tsx +0 -925
  536. package/src/stories/LayoutSystem.stories.tsx +0 -639
  537. package/src/stories/MenuEntries.stories.tsx +0 -86
  538. package/src/stories/MobilePatterns.stories.tsx +0 -1536
  539. package/src/stories/NavList.stories.tsx +0 -147
  540. package/src/stories/NavTabs.stories.tsx +0 -53
  541. package/src/stories/OfflineGate.stories.tsx +0 -42
  542. package/src/stories/PageContainer.stories.tsx +0 -82
  543. package/src/stories/PageHeader.stories.tsx +0 -95
  544. package/src/stories/ResponsiveSurface.stories.tsx +0 -47
  545. package/src/stories/Spinner.stories.tsx +0 -25
  546. package/src/stories/StepStrip.stories.tsx +0 -76
  547. package/src/stories/SwipableMobileCarousel.stories.tsx +0 -362
  548. package/src/stories/SwipeStack.stories.tsx +0 -200
  549. package/src/stories/Textarea.stories.tsx +0 -63
  550. package/src/stories/TokenGallery.stories.tsx +0 -652
  551. package/src/stories/Tooltip.stories.tsx +0 -59
  552. package/src/tokens/primitives.css +0 -168
  553. package/src/tokens/semantic.css +0 -162
  554. package/src/tokens/theme-presets.css +0 -33
  555. package/src/tokens/theme-presets.ts +0 -72
  556. package/src/tokens.css +0 -5
@@ -1,3793 +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 { CardTile, CardTileGrid, NewCardTile } from '../components/CardTile/CardTile';
9
- import { Dialog } from '../components/Dialog/Dialog';
10
- import { Drawer } from '../components/Drawer/Drawer';
11
- import { EmptyState } from '../components/EmptyState/EmptyState';
12
- import { OfflineGate } from '../components/OfflineGate/OfflineGate';
13
- import { DeviceOnlyGate } from '../components/DeviceOnlyGate/DeviceOnlyGate';
14
- import { DeviceStorageKeyGate } from '../components/DeviceStorageKeyGate/DeviceStorageKeyGate';
15
- import { FormField } from '../components/FormField/FormField';
16
- import { Icon } from '../components/Icon/Icon';
17
- import type { IconName } from '../components/Icon/Icon';
18
- import { IconPicker } from '../components/IconPicker/IconPicker';
19
- import { ColorPicker } from '../components/ColorPicker/ColorPicker';
20
- import { QuantityStepper } from '../components/QuantityStepper/QuantityStepper';
21
- import { CheckableListRow } from '../components/CheckableListRow/CheckableListRow';
22
- import { Input } from '../components/Input/Input';
23
- import { CodeTextarea } from '../components/CodeTextarea/CodeTextarea';
24
- import { Textarea } from '../components/Textarea/Textarea';
25
- import { StatusBadge } from '../components/StatusBadge/StatusBadge';
26
- import { SplitPane } from '../components/SplitPane/SplitPane';
27
- import { ThreeColumnLayout } from '../components/ThreeColumnLayout/ThreeColumnLayout';
28
- import { HeaderFooterLayout } from '../components/HeaderFooterLayout/HeaderFooterLayout';
29
- import { RootLayout } from '../components/RootLayout/RootLayout';
30
- import { PageLayout } from '../components/PageLayout/PageLayout';
31
- import { TagInput } from '../components/TagInput/TagInput';
32
- import { SuggestionInput } from '../components/SuggestionInput/SuggestionInput';
33
- import { AppsLauncher } from '../components/AppsLauncher/AppsLauncher';
34
- import { Header } from '../components/Header/Header';
35
- import { MobileAppsDrawer } from '../components/MobileAppsDrawer/MobileAppsDrawer';
36
- import { MobileFooter } from '../components/MobileFooter/MobileFooter';
37
- import { MobileHeader } from '../components/MobileHeader/MobileHeader';
38
- import { NavList } from '../components/NavList/NavList';
39
- import { NavTabs } from '../components/NavTabs/NavTabs';
40
- import { NotificationsPanel } from '../components/NotificationsPanel/NotificationsPanel';
41
- import { PageHeader } from '../components/PageHeader/PageHeader';
42
- import { PageContainer } from '../components/PageContainer/PageContainer';
43
- import { Popover } from '../components/Popover/Popover';
44
- import { UserMenu } from '../components/UserMenu/UserMenu';
45
- import { ResponsiveSurface } from '../components/ResponsiveSurface/ResponsiveSurface';
46
- import { SwipableMobileCarousel } from '../components/SwipableMobileCarousel/SwipableMobileCarousel';
47
- import { SwipableMobileCarouselSlide } from '../components/SwipableMobileCarousel/SwipableMobileCarouselSlide';
48
- import { SwipableMobileCarouselSlideHeader } from '../components/SwipableMobileCarousel/SwipableMobileCarouselSlideHeader';
49
- import { SwipableMobileCarouselSlideBody } from '../components/SwipableMobileCarousel/SwipableMobileCarouselSlideBody';
50
- import { SwipableMobileCarouselDots } from '../components/SwipableMobileCarouselDots/SwipableMobileCarouselDots';
51
- import { SwipeStack } from '../components/SwipeStack/SwipeStack';
52
- import { SwipeStackCard } from '../components/SwipeStack/SwipeStackCard';
53
- import { SegmentedControl } from '../components/SegmentedControl/SegmentedControl';
54
- import { Select } from '../components/Select/Select';
55
- import { Spinner } from '../components/Spinner/Spinner';
56
- import { SystemBanner } from '../components/SystemBanner/SystemBanner';
57
- import { Tabs } from '../components/Tabs/Tabs';
58
- import { ToastProvider, useToast } from '../components/Toast/Toast';
59
- import { Toggle } from '../components/Toggle/Toggle';
60
- import { Tooltip } from '../components/Tooltip/Tooltip';
61
- import { Checkbox } from '../components/Checkbox/Checkbox';
62
- import { RadioGroup } from '../components/RadioGroup/RadioGroup';
63
- import { Slider } from '../components/Slider/Slider';
64
- import { Progress } from '../components/Progress/Progress';
65
- import {
66
- Table,
67
- TableHead,
68
- TableBody,
69
- TableRow,
70
- TableHeaderCell,
71
- TableCell,
72
- } from '../components/Table/Table';
73
- import { Alert } from '../components/Alert/Alert';
74
- import { Breadcrumb } from '../components/Breadcrumb/Breadcrumb';
75
- import { LegalLinks } from '../components/LegalLinks/LegalLinks';
76
- import { ConsentPrompt } from '../components/ConsentPrompt/ConsentPrompt';
77
- import { Markdown } from '../components/Markdown/Markdown';
78
- import { Pagination } from '../components/Pagination/Pagination';
79
- import { Kbd } from '../components/Kbd/Kbd';
80
- import { Accordion } from '../components/Accordion/Accordion';
81
- import { AspectRatio } from '../components/AspectRatio/AspectRatio';
82
- import { ButtonGroup } from '../components/ButtonGroup/ButtonGroup';
83
- import { Item } from '../components/Item/Item';
84
- import { Label } from '../components/Label/Label';
85
- import { ScrollArea } from '../components/ScrollArea/ScrollArea';
86
- import { Typography } from '../components/Typography/Typography';
87
- import { Marker } from '../components/Marker/Marker';
88
- import { Message } from '../components/Message/Message';
89
- import { MessageScroller } from '../components/MessageScroller/MessageScroller';
90
- import { HoverCard } from '../components/HoverCard/HoverCard';
91
- import { ContextMenu } from '../components/ContextMenu/ContextMenu';
92
- import { Command } from '../components/Command/Command';
93
- import {
94
- ResizableHandle,
95
- ResizablePanel,
96
- ResizablePanelGroup,
97
- } from '../components/Resizable/Resizable';
98
- import { DataTable } from '../components/DataTable/DataTable';
99
- import { Combobox } from '../components/Combobox/Combobox';
100
- import { NavigationMenu } from '../components/NavigationMenu/NavigationMenu';
101
- import { Menubar } from '../components/Menubar/Menubar';
102
- import { Collapsible } from '../components/Collapsible/Collapsible';
103
- import { DragHandleRow } from '../components/DragHandleRow/DragHandleRow';
104
- import { GripIcon } from '../components/GripIcon/GripIcon';
105
- import { StepStrip, type StepStripItem } from '../components/StepStrip/StepStrip';
106
- import { FileDropzone } from '../components/FileDropzone/FileDropzone';
107
- import { OverlayHeader } from '../components/OverlayHeader/OverlayHeader';
108
- import { Sheet } from '../components/Sheet/Sheet';
109
- import { ConfirmDialog } from '../components/ConfirmDialog/ConfirmDialog';
110
- import { Menu, MenuEntries } from '../components/Menu/Menu';
111
- import { Calendar } from '../components/Calendar/Calendar';
112
- import { DatePicker } from '../components/DatePicker/DatePicker';
113
- import { CurrencyInput } from '../components/CurrencyInput/CurrencyInput';
114
- import { BalanceChip } from '../components/BalanceChip/BalanceChip';
115
- import {
116
- SplitMethodSelector,
117
- type SplitMethod,
118
- } from '../components/SplitMethodSelector/SplitMethodSelector';
119
- import {
120
- MemberMultiSelect,
121
- type MemberMultiSelectOption,
122
- } from '../components/MemberMultiSelect/MemberMultiSelect';
123
-
124
- // ---------------------------------------------------------------------------
125
- // Shared primitives
126
- // ---------------------------------------------------------------------------
127
-
128
- const ff = 'var(--sv-font-family)';
129
- const ffm = 'var(--sv-font-family-mono)';
130
-
131
- function Heading({ level = 2, children }: { level?: 1 | 2 | 3; children: React.ReactNode }) {
132
- const sizes: Record<number, string> = { 1: '2rem', 2: '1.25rem', 3: '1rem' };
133
- const weights: Record<number, number> = { 1: 700, 2: 600, 3: 600 };
134
- const Tag = `h${level}` as 'h1' | 'h2' | 'h3';
135
- return (
136
- <Tag
137
- style={{
138
- fontFamily: ff,
139
- fontSize: sizes[level],
140
- fontWeight: weights[level],
141
- color: 'var(--sv-color-text-primary)',
142
- margin: 0,
143
- lineHeight: 1.2,
144
- }}
145
- >
146
- {children}
147
- </Tag>
148
- );
149
- }
150
-
151
- function SectionHeader({ title, subtitle }: { title: string; subtitle?: string }) {
152
- return (
153
- <div
154
- style={{
155
- paddingBottom: 'var(--sv-space-3)',
156
- borderBottom: '2px solid var(--sv-color-accent)',
157
- marginBottom: 'var(--sv-space-5)',
158
- }}
159
- >
160
- <Heading level={2}>{title}</Heading>
161
- {subtitle && (
162
- <p
163
- style={{
164
- fontFamily: ff,
165
- fontSize: 'var(--sv-font-size-sm)',
166
- color: 'var(--sv-color-text-muted)',
167
- marginTop: 'var(--sv-space-1)',
168
- }}
169
- >
170
- {subtitle}
171
- </p>
172
- )}
173
- </div>
174
- );
175
- }
176
-
177
- function Code({ children }: { children: string }) {
178
- return (
179
- <pre
180
- style={{
181
- fontFamily: ffm,
182
- fontSize: 'var(--sv-font-size-xs)',
183
- background: 'var(--sv-color-surface-sunken)',
184
- border: '1px solid var(--sv-color-border)',
185
- borderRadius: 'var(--sv-radius-md)',
186
- padding: 'var(--sv-space-3) var(--sv-space-4)',
187
- color: 'var(--sv-color-text-primary)',
188
- overflowX: 'auto',
189
- margin: 0,
190
- lineHeight: 1.6,
191
- whiteSpace: 'pre',
192
- }}
193
- >
194
- {children}
195
- </pre>
196
- );
197
- }
198
-
199
- function DemoBox({ children, style }: { children: React.ReactNode; style?: React.CSSProperties }) {
200
- return (
201
- <div
202
- style={{
203
- background: 'var(--sv-color-surface-sunken)',
204
- border: '1px solid var(--sv-color-border)',
205
- borderRadius: 'var(--sv-radius-lg)',
206
- padding: 'var(--sv-space-4)',
207
- display: 'flex',
208
- alignItems: 'center',
209
- justifyContent: 'center',
210
- gap: 'var(--sv-space-3)',
211
- flexWrap: 'wrap',
212
- minHeight: 80,
213
- ...style,
214
- }}
215
- >
216
- {children}
217
- </div>
218
- );
219
- }
220
-
221
- function Pill({ children }: { children: React.ReactNode }) {
222
- return (
223
- <span
224
- style={{
225
- display: 'inline-block',
226
- fontFamily: ffm,
227
- fontSize: '0.6875rem',
228
- fontWeight: 600,
229
- letterSpacing: '0.03em',
230
- background: 'var(--sv-color-surface-sunken)',
231
- border: '1px solid var(--sv-color-border)',
232
- color: 'var(--sv-color-text-muted)',
233
- borderRadius: 'var(--sv-radius-full)',
234
- padding: '2px 10px',
235
- }}
236
- >
237
- {children}
238
- </span>
239
- );
240
- }
241
-
242
- // ---------------------------------------------------------------------------
243
- // Color groups
244
- // ---------------------------------------------------------------------------
245
-
246
- const COLOR_GROUPS: Array<{
247
- label: string;
248
- usage: string;
249
- tokens: Array<{ token: string; name: string }>;
250
- }> = [
251
- {
252
- label: 'Surface',
253
- usage: 'Page backgrounds and card elevations.',
254
- tokens: [
255
- { token: '--sv-color-surface', name: 'surface' },
256
- { token: '--sv-color-surface-sunken', name: 'surface-sunken' },
257
- { token: '--sv-color-surface-raised', name: 'surface-raised' },
258
- ],
259
- },
260
- {
261
- label: 'Text',
262
- usage: 'Copy hierarchy from primary body to disabled hints.',
263
- tokens: [
264
- { token: '--sv-color-text-primary', name: 'text-primary' },
265
- { token: '--sv-color-text-muted', name: 'text-muted' },
266
- { token: '--sv-color-text-subtle', name: 'text-subtle' },
267
- { token: '--sv-color-text-on-accent', name: 'text-on-accent' },
268
- { token: '--sv-color-text-on-error', name: 'text-on-error' },
269
- { token: '--sv-color-text-on-success', name: 'text-on-success' },
270
- { token: '--sv-color-text-on-light-fill', name: 'text-on-light-fill' },
271
- { token: '--sv-color-text-on-dark-fill', name: 'text-on-dark-fill' },
272
- ],
273
- },
274
- {
275
- label: 'Border',
276
- usage: 'Dividers, input outlines, and card edges.',
277
- tokens: [
278
- { token: '--sv-color-border', name: 'border' },
279
- { token: '--sv-color-border-strong', name: 'border-strong' },
280
- ],
281
- },
282
- {
283
- label: 'Shadow',
284
- usage:
285
- 'High-contrast shadow color for theme presets with hard offset shadows (RFC 0094). Unused by the default theme.',
286
- tokens: [{ token: '--sv-color-shadow-strong', name: 'shadow-strong' }],
287
- },
288
- {
289
- label: 'Accent',
290
- usage:
291
- 'Primary interactive color. Monochrome by default; instance admins override with their brand color.',
292
- tokens: [
293
- { token: '--sv-color-accent', name: 'accent' },
294
- { token: '--sv-color-accent-hover', name: 'accent-hover' },
295
- { token: '--sv-color-focus-ring', name: 'focus-ring' },
296
- ],
297
- },
298
- {
299
- label: 'Error',
300
- usage: 'Destructive states, form validation errors, critical banners.',
301
- tokens: [
302
- { token: '--sv-color-error-surface', name: 'error-surface' },
303
- { token: '--sv-color-error-text', name: 'error-text' },
304
- { token: '--sv-color-error-border', name: 'error-border' },
305
- { token: '--sv-color-error-solid', name: 'error-solid' },
306
- ],
307
- },
308
- {
309
- label: 'Warning',
310
- usage: 'Caution states, expiring licenses, near-limit notices.',
311
- tokens: [
312
- { token: '--sv-color-warning-surface', name: 'warning-surface' },
313
- { token: '--sv-color-warning-text', name: 'warning-text' },
314
- { token: '--sv-color-warning-border', name: 'warning-border' },
315
- ],
316
- },
317
- {
318
- label: 'Success',
319
- usage: 'Positive confirmations, completed actions, healthy status.',
320
- tokens: [
321
- { token: '--sv-color-success-surface', name: 'success-surface' },
322
- { token: '--sv-color-success-text', name: 'success-text' },
323
- { token: '--sv-color-success-border', name: 'success-border' },
324
- { token: '--sv-color-success-solid', name: 'success-solid' },
325
- ],
326
- },
327
- {
328
- label: 'Info',
329
- usage: 'Informational notices and neutral callouts.',
330
- tokens: [
331
- { token: '--sv-color-info-surface', name: 'info-surface' },
332
- { token: '--sv-color-info-text', name: 'info-text' },
333
- { token: '--sv-color-info-border', name: 'info-border' },
334
- ],
335
- },
336
- ];
337
-
338
- function ColorSwatch({ token, name }: { token: string; name: string }) {
339
- const value =
340
- typeof window !== 'undefined'
341
- ? getComputedStyle(document.documentElement).getPropertyValue(token).trim()
342
- : '';
343
- return (
344
- <div style={{ display: 'flex', flexDirection: 'column', gap: 6, flex: '1 1 160px' }}>
345
- <div
346
- style={{
347
- height: 44,
348
- borderRadius: 'var(--sv-radius-md)',
349
- background: `var(${token})`,
350
- border: '1px solid var(--sv-color-border)',
351
- }}
352
- />
353
- <div>
354
- <p
355
- style={{
356
- fontFamily: ffm,
357
- fontSize: '0.6875rem',
358
- color: 'var(--sv-color-text-primary)',
359
- margin: 0,
360
- }}
361
- >
362
- --sv-color-{name}
363
- </p>
364
- <p
365
- style={{
366
- fontFamily: ffm,
367
- fontSize: '0.6875rem',
368
- color: 'var(--sv-color-text-muted)',
369
- margin: 0,
370
- }}
371
- >
372
- {value}
373
- </p>
374
- </div>
375
- </div>
376
- );
377
- }
378
-
379
- function ColorGroup({
380
- label,
381
- usage,
382
- tokens,
383
- }: {
384
- label: string;
385
- usage: string;
386
- tokens: Array<{ token: string; name: string }>;
387
- }) {
388
- return (
389
- <div style={{ marginBottom: 'var(--sv-space-8)' }}>
390
- <div style={{ marginBottom: 'var(--sv-space-3)' }}>
391
- <Heading level={3}>{label}</Heading>
392
- <p
393
- style={{
394
- fontFamily: ff,
395
- fontSize: 'var(--sv-font-size-caption)',
396
- color: 'var(--sv-color-text-muted)',
397
- margin: '4px 0 0',
398
- }}
399
- >
400
- {usage}
401
- </p>
402
- </div>
403
- <div style={{ display: 'flex', flexWrap: 'wrap', gap: 'var(--sv-space-4)' }}>
404
- {tokens.map((t) => (
405
- <ColorSwatch key={t.token} token={t.token} name={t.name} />
406
- ))}
407
- </div>
408
- </div>
409
- );
410
- }
411
-
412
- // ---------------------------------------------------------------------------
413
- // Typography
414
- // ---------------------------------------------------------------------------
415
-
416
- const TYPE_SCALE: Array<{ token: string; label: string; note: string }> = [
417
- { token: '--sv-font-size-3xl', label: '3xl — 32px', note: 'Document title style' },
418
- { token: '--sv-font-size-2xl', label: '2xl — 24px', note: 'Page titles' },
419
- { token: '--sv-font-size-xl', label: 'xl — 20px', note: 'Section headings' },
420
- { token: '--sv-font-size-lg', label: 'lg — 18px', note: 'Sub-headings' },
421
- { token: '--sv-font-size-md', label: 'md — 16px', note: 'Body (base)' },
422
- { token: '--sv-font-size-sm', label: 'sm — 14px', note: 'Body copy, labels' },
423
- { token: '--sv-font-size-caption', label: 'caption — 13px', note: 'Secondary / supporting copy' },
424
- { token: '--sv-font-size-xs', label: 'xs — 12px', note: 'Mono identifiers, badges' },
425
- { token: '--sv-font-size-label', label: 'label — 11px', note: 'All-caps section labels' },
426
- {
427
- token: '--sv-font-size-2xs',
428
- label: '2xs — 10px',
429
- note: 'Badge size="xs" — smallest badge tier',
430
- },
431
- ];
432
-
433
- // ---------------------------------------------------------------------------
434
- // Component cards
435
- // ---------------------------------------------------------------------------
436
-
437
- function ComponentCard({
438
- name,
439
- importLine,
440
- usage,
441
- children,
442
- }: {
443
- name: string;
444
- importLine: string;
445
- usage: string;
446
- children: React.ReactNode;
447
- }) {
448
- return (
449
- <div
450
- style={{
451
- border: '1px solid var(--sv-color-border)',
452
- borderRadius: 'var(--sv-radius-xl)',
453
- overflow: 'hidden',
454
- background: 'var(--sv-color-surface)',
455
- boxShadow: 'var(--sv-shadow-card)',
456
- display: 'flex',
457
- flexDirection: 'column',
458
- }}
459
- >
460
- {/* Header */}
461
- <div
462
- style={{
463
- padding: 'var(--sv-space-4)',
464
- borderBottom: '1px solid var(--sv-color-border)',
465
- background: 'var(--sv-color-surface-sunken)',
466
- }}
467
- >
468
- <Heading level={3}>{name}</Heading>
469
- <p
470
- style={{
471
- fontFamily: ff,
472
- fontSize: 'var(--sv-font-size-caption)',
473
- color: 'var(--sv-color-text-muted)',
474
- margin: '4px 0 0',
475
- }}
476
- >
477
- {usage}
478
- </p>
479
- </div>
480
- {/* Demo */}
481
- <div
482
- style={{
483
- padding: 'var(--sv-space-5)',
484
- display: 'flex',
485
- alignItems: 'center',
486
- justifyContent: 'center',
487
- minHeight: 96,
488
- gap: 'var(--sv-space-3)',
489
- flexWrap: 'wrap',
490
- flexGrow: 1,
491
- }}
492
- >
493
- {children}
494
- </div>
495
- {/* Import */}
496
- <div style={{ padding: 'var(--sv-space-3) var(--sv-space-4)' }}>
497
- <code
498
- style={{
499
- fontFamily: ffm,
500
- fontSize: '0.6875rem',
501
- color: 'var(--sv-color-text-muted)',
502
- display: 'block',
503
- }}
504
- >
505
- {importLine}
506
- </code>
507
- </div>
508
- </div>
509
- );
510
- }
511
-
512
- // Interactive wrappers
513
-
514
- function ToggleDemo() {
515
- const [on, setOn] = useState(false);
516
- return <Toggle checked={on} onChange={setOn} aria-label="Enable feature" />;
517
- }
518
-
519
- function SegmentedDemo() {
520
- const [v, setV] = useState<'user' | 'admin'>('user');
521
- return (
522
- <SegmentedControl
523
- value={v}
524
- onChange={setV}
525
- options={[
526
- { label: 'User', value: 'user' },
527
- { label: 'Admin', value: 'admin' },
528
- ]}
529
- aria-label="Role"
530
- />
531
- );
532
- }
533
-
534
- function TabsDemo() {
535
- const [tab, setTab] = useState('profile');
536
- return (
537
- <div style={{ width: '100%', fontFamily: ff }}>
538
- <Tabs
539
- items={[
540
- { label: 'Profile', value: 'profile' },
541
- { label: 'Security', value: 'security' },
542
- { label: 'Data', value: 'data' },
543
- ]}
544
- value={tab}
545
- onChange={setTab}
546
- aria-label="Account"
547
- />
548
- </div>
549
- );
550
- }
551
-
552
- function CommandDemo() {
553
- const [open, setOpen] = useState(false);
554
- return (
555
- <>
556
- <Button size="sm" onClick={() => setOpen(true)}>
557
- Open command palette
558
- </Button>
559
- <Command
560
- open={open}
561
- onClose={() => setOpen(false)}
562
- aria-label="Command palette"
563
- items={[
564
- {
565
- id: 'new',
566
- label: 'New conversation',
567
- group: 'Actions',
568
- icon: 'plus',
569
- onSelect: () => {},
570
- },
571
- {
572
- id: 'export',
573
- label: 'Export chat',
574
- group: 'Actions',
575
- icon: 'upload',
576
- onSelect: () => {},
577
- },
578
- {
579
- id: 'profile',
580
- label: 'Go to profile',
581
- group: 'Navigation',
582
- icon: 'user',
583
- onSelect: () => {},
584
- },
585
- ]}
586
- />
587
- </>
588
- );
589
- }
590
-
591
- function ComboboxDemo() {
592
- const [value, setValue] = useState<string | null>(null);
593
- return (
594
- <div style={{ width: 240 }}>
595
- <Combobox
596
- options={[
597
- { value: 'tasks', label: 'sovereign-tasks' },
598
- { value: 'ledger', label: 'sovereign-ledger' },
599
- { value: 'shopper', label: 'sovereign-shopper' },
600
- ]}
601
- value={value}
602
- onChange={setValue}
603
- placeholder="Select a plugin"
604
- aria-label="Plugin"
605
- />
606
- </div>
607
- );
608
- }
609
-
610
- function DialogDemo() {
611
- const [open, setOpen] = useState(false);
612
- return (
613
- <>
614
- <Button size="sm" onClick={() => setOpen(true)}>
615
- Open dialog
616
- </Button>
617
- <Dialog open={open} onClose={() => setOpen(false)} size="sm" aria-label="Example dialog">
618
- <div style={{ padding: 24, fontFamily: ff }}>
619
- <Heading level={3}>Confirm action</Heading>
620
- <p
621
- style={{
622
- color: 'var(--sv-color-text-muted)',
623
- fontSize: 'var(--sv-font-size-sm)',
624
- margin: '12px 0 20px',
625
- }}
626
- >
627
- This will permanently delete the item. Are you sure?
628
- </p>
629
- <div style={{ display: 'flex', gap: 8 }}>
630
- <Button variant="destructive" size="sm" onClick={() => setOpen(false)}>
631
- Delete
632
- </Button>
633
- <Button variant="secondary" size="sm" onClick={() => setOpen(false)}>
634
- Cancel
635
- </Button>
636
- </div>
637
- </div>
638
- </Dialog>
639
- </>
640
- );
641
- }
642
-
643
- function DrawerDemo() {
644
- const [open, setOpen] = useState(false);
645
- return (
646
- <>
647
- <Button size="sm" variant="secondary" onClick={() => setOpen(true)}>
648
- Open drawer
649
- </Button>
650
- <Drawer open={open} onClose={() => setOpen(false)} aria-label="Navigation">
651
- <ul style={{ listStyle: 'none', margin: 0, padding: '8px 0' }}>
652
- {(['Home', 'Settings', 'Account'] as const).map((item) => (
653
- <li key={item}>
654
- <button
655
- onClick={() => setOpen(false)}
656
- style={{
657
- display: 'block',
658
- width: '100%',
659
- padding: '12px 20px',
660
- background: 'none',
661
- border: 'none',
662
- cursor: 'pointer',
663
- fontSize: 15,
664
- textAlign: 'left',
665
- color: 'var(--sv-color-text-primary)',
666
- fontFamily: ff,
667
- }}
668
- >
669
- {item}
670
- </button>
671
- </li>
672
- ))}
673
- </ul>
674
- </Drawer>
675
- </>
676
- );
677
- }
678
-
679
- function MobileAppsDrawerDemo() {
680
- const [open, setOpen] = useState(false);
681
- const items = [
682
- { key: 'home', icon: <Icon name="house" size="lg" aria-hidden />, label: 'Home' },
683
- { key: 'notes', icon: <Icon name="file" size="lg" aria-hidden />, label: 'Notes' },
684
- { key: 'gallery', icon: <Icon name="grid-2x2" size="lg" aria-hidden />, label: 'Gallery' },
685
- ];
686
- return (
687
- <>
688
- <Button size="sm" variant="secondary" onClick={() => setOpen(true)}>
689
- Open drawer
690
- </Button>
691
- <MobileAppsDrawer
692
- open={open}
693
- onClose={() => setOpen(false)}
694
- aria-label="Sections"
695
- items={items.map((item) => ({ ...item, onClick: () => setOpen(false) }))}
696
- />
697
- </>
698
- );
699
- }
700
-
701
- function PopoverDemo() {
702
- const [open, setOpen] = useState(false);
703
- return (
704
- <Popover
705
- open={open}
706
- onClose={() => setOpen(false)}
707
- align="left"
708
- aria-label="Options menu"
709
- trigger={
710
- <Button size="sm" variant="ghost" onClick={() => setOpen((o) => !o)}>
711
- <Icon name="settings" size="sm" aria-hidden /> Options
712
- </Button>
713
- }
714
- >
715
- <div style={{ padding: 'var(--sv-space-3)', fontFamily: ff }}>
716
- {['Edit', 'Duplicate', 'Delete'].map((item) => (
717
- <button
718
- key={item}
719
- type="button"
720
- onClick={() => setOpen(false)}
721
- style={{
722
- display: 'block',
723
- width: '100%',
724
- padding: '8px 12px',
725
- background: 'none',
726
- border: 'none',
727
- cursor: 'pointer',
728
- fontSize: 'var(--sv-font-size-sm)',
729
- textAlign: 'left',
730
- color:
731
- item === 'Delete' ? 'var(--sv-color-error-text)' : 'var(--sv-color-text-primary)',
732
- fontFamily: ff,
733
- borderRadius: 'var(--sv-radius-sm)',
734
- }}
735
- >
736
- {item}
737
- </button>
738
- ))}
739
- </div>
740
- </Popover>
741
- );
742
- }
743
-
744
- function OverlayHeaderDemo() {
745
- return (
746
- <div
747
- style={{
748
- width: '100%',
749
- maxWidth: 360,
750
- border: '1px solid var(--sv-color-border)',
751
- borderRadius: 'var(--sv-radius-lg)',
752
- overflow: 'hidden',
753
- }}
754
- >
755
- <OverlayHeader
756
- title="Edit list"
757
- onClose={() => {}}
758
- onBack={() => {}}
759
- action={
760
- <Button size="sm" variant="ghost" onClick={() => {}}>
761
- Save
762
- </Button>
763
- }
764
- />
765
- </div>
766
- );
767
- }
768
-
769
- function SheetDemo() {
770
- const [open, setOpen] = useState(false);
771
- return (
772
- <>
773
- <Button size="sm" onClick={() => setOpen(true)}>
774
- Open sheet
775
- </Button>
776
- {/* Storybook has no --sv-shell-header-height/--sv-shell-footer-height, so
777
- the panel fills this fixed-height preview box instead of the real
778
- app's header-to-footer region — same component, just no shell
779
- around it here. */}
780
- <div
781
- style={{
782
- position: 'relative',
783
- width: '100%',
784
- maxWidth: 360,
785
- height: 280,
786
- marginTop: 12,
787
- border: '1px solid var(--sv-color-border)',
788
- borderRadius: 'var(--sv-radius-lg)',
789
- overflow: 'hidden',
790
- }}
791
- >
792
- <div style={{ position: 'absolute', inset: 0 }}>
793
- <Sheet
794
- open={open}
795
- onClose={() => setOpen(false)}
796
- title="Task detail"
797
- aria-label="Task detail"
798
- >
799
- <div
800
- style={{
801
- padding: 16,
802
- fontFamily: ff,
803
- fontSize: 14,
804
- color: 'var(--sv-color-text-muted)',
805
- }}
806
- >
807
- Full-page overlay content — same pattern the tasks plugin used for its task-detail and
808
- list-edit panels.
809
- </div>
810
- </Sheet>
811
- </div>
812
- </div>
813
- </>
814
- );
815
- }
816
-
817
- function ConfirmDialogDemo() {
818
- const [open, setOpen] = useState(false);
819
- return (
820
- <>
821
- <Button size="sm" variant="destructive" onClick={() => setOpen(true)}>
822
- Remove passkey
823
- </Button>
824
- <ConfirmDialog
825
- open={open}
826
- onClose={() => setOpen(false)}
827
- onConfirm={() => setOpen(false)}
828
- title="Remove passkey"
829
- message="You will no longer be able to sign in with this passkey."
830
- confirmLabel="Remove"
831
- destructive
832
- />
833
- </>
834
- );
835
- }
836
-
837
- function MenuDemo() {
838
- const [open, setOpen] = useState(false);
839
- const [sortBy, setSortBy] = useState<'manual' | 'title' | 'due'>('manual');
840
- return (
841
- <Menu
842
- open={open}
843
- onClose={() => setOpen(false)}
844
- aria-label="List actions"
845
- align="left"
846
- trigger={
847
- <Button size="sm" variant="ghost" onClick={() => setOpen((o) => !o)}>
848
- <Icon name="settings" size="sm" aria-hidden /> Actions
849
- </Button>
850
- }
851
- items={[
852
- { type: 'label', label: 'Sort by' },
853
- { label: 'Manual', checked: sortBy === 'manual', onSelect: () => setSortBy('manual') },
854
- { label: 'Title', checked: sortBy === 'title', onSelect: () => setSortBy('title') },
855
- { label: 'Due date', checked: sortBy === 'due', onSelect: () => setSortBy('due') },
856
- { type: 'separator' },
857
- { label: 'Rename', icon: 'pencil', onSelect: () => {} },
858
- { label: 'Duplicate', icon: 'plus', onSelect: () => {} },
859
- { label: 'Delete', icon: 'trash-2', destructive: true, onSelect: () => {} },
860
- ]}
861
- />
862
- );
863
- }
864
-
865
- function MenuEntriesDemo() {
866
- return (
867
- <div style={{ width: '100%', maxWidth: 240 }}>
868
- <MenuEntries
869
- items={[
870
- { type: 'label', label: 'Sort by' },
871
- { label: 'Manual', checked: true },
872
- { label: 'Title', checked: false },
873
- { type: 'separator' },
874
- { label: 'Rename', icon: 'pencil' },
875
- { label: 'Delete', icon: 'trash-2', destructive: true },
876
- ]}
877
- onSelect={() => {}}
878
- />
879
- </div>
880
- );
881
- }
882
-
883
- function CalendarDemo() {
884
- const [value, setValue] = useState<Date | null>(new Date());
885
- return (
886
- <div style={{ width: '100%', maxWidth: 320 }}>
887
- <Calendar value={value} onChange={setValue} aria-label="Example calendar" />
888
- </div>
889
- );
890
- }
891
-
892
- function DatePickerDemo() {
893
- const [value, setValue] = useState<Date | null>(null);
894
- return (
895
- <div style={{ width: '100%', maxWidth: 240 }}>
896
- <DatePicker
897
- value={value}
898
- onChange={setValue}
899
- aria-label="Due date"
900
- placeholder="Select date"
901
- />
902
- </div>
903
- );
904
- }
905
-
906
- function ToastDemo() {
907
- const { show } = useToast();
908
- return (
909
- <Button
910
- size="sm"
911
- variant="secondary"
912
- onClick={() =>
913
- show({
914
- title: 'Plugin installed',
915
- message: 'Tasks v1.2.0 is now active.',
916
- category: 'success',
917
- })
918
- }
919
- >
920
- Fire toast
921
- </Button>
922
- );
923
- }
924
-
925
- function TagInputDemo() {
926
- const [tags, setTags] = useState(['draft', 'frontmatter']);
927
- return <TagInput value={tags} onChange={setTags} aria-label="Tags" />;
928
- }
929
-
930
- function QuantityStepperDemo() {
931
- const [value, setValue] = useState(6);
932
- return <QuantityStepper value={value} onChange={setValue} unit="pcs" aria-label="Quantity" />;
933
- }
934
-
935
- function CheckableListRowDemo() {
936
- const [checked, setChecked] = useState(false);
937
- return (
938
- <div style={{ width: '100%' }}>
939
- <CheckableListRow
940
- checked={checked}
941
- onCheckedChange={setChecked}
942
- label="Bananas"
943
- icon={<Icon name="banana" size="md" aria-hidden />}
944
- trailing={<span style={{ fontSize: 13, color: 'var(--sv-color-text-muted)' }}>6 pcs</span>}
945
- />
946
- </div>
947
- );
948
- }
949
-
950
- function CurrencyInputDemo() {
951
- const [cents, setCents] = useState<number | null>(4250);
952
- return (
953
- <CurrencyInput
954
- valueCents={cents}
955
- onValueChange={setCents}
956
- placeholder="0.00"
957
- aria-label="Amount"
958
- />
959
- );
960
- }
961
-
962
- function SplitMethodSelectorDemo() {
963
- const [value, setValue] = useState<SplitMethod>('equal');
964
- return <SplitMethodSelector value={value} onChange={setValue} />;
965
- }
966
-
967
- const MEMBER_MULTI_SELECT_OPTIONS: MemberMultiSelectOption[] = [
968
- { id: '1', label: 'Priya' },
969
- { id: '2', label: 'Jamie (guest)' },
970
- { id: '3', label: 'Sam' },
971
- ];
972
-
973
- function MemberMultiSelectDemo() {
974
- const [selected, setSelected] = useState<Set<string>>(new Set(['1', '3']));
975
- return (
976
- <div style={{ width: '100%' }}>
977
- <MemberMultiSelect
978
- options={MEMBER_MULTI_SELECT_OPTIONS}
979
- selectedIds={selected}
980
- onToggle={(id, checked) => {
981
- setSelected((prev) => {
982
- const next = new Set(prev);
983
- if (checked) next.add(id);
984
- else next.delete(id);
985
- return next;
986
- });
987
- }}
988
- label="Split between"
989
- />
990
- </div>
991
- );
992
- }
993
-
994
- function FileDropzoneDemo() {
995
- const [file, setFile] = useState<File | null>(null);
996
- return (
997
- <div style={{ width: '100%' }}>
998
- <FileDropzone
999
- ariaLabel="Upload ZIP file"
1000
- accept=".zip,application/zip"
1001
- label={file ? file.name : 'Choose a ZIP file'}
1002
- hint={file ? `${(file.size / 1024).toFixed(0)} KB` : 'or drag and drop here'}
1003
- onFileSelect={setFile}
1004
- />
1005
- </div>
1006
- );
1007
- }
1008
-
1009
- interface StepStripDemoStop extends StepStripItem {
1010
- label: string;
1011
- sublabel: string;
1012
- }
1013
-
1014
- const STEP_STRIP_DEMO_STOPS: StepStripDemoStop[] = [
1015
- { id: 'lisbon', label: 'Lisbon', sublabel: 'Aug 26 – 28' },
1016
- { id: 'porto', label: 'Porto', sublabel: 'Aug 28 – 30' },
1017
- ];
1018
-
1019
- function StepStripDemo() {
1020
- const [activeId, setActiveId] = useState('lisbon');
1021
- return (
1022
- <StepStrip
1023
- items={STEP_STRIP_DEMO_STOPS}
1024
- activeId={activeId}
1025
- aria-label="Trip stops"
1026
- onAdd={() => undefined}
1027
- addLabel="Add a stop"
1028
- renderItem={(stop, { isActive }) => (
1029
- <button
1030
- type="button"
1031
- onClick={() => setActiveId(stop.id)}
1032
- style={{
1033
- boxSizing: 'border-box',
1034
- minWidth: '150px',
1035
- padding: 'var(--sv-space-3) var(--sv-space-4)',
1036
- borderRadius: 'var(--sv-radius-lg)',
1037
- border: `1px solid ${isActive ? 'var(--sv-color-text-primary)' : 'var(--sv-color-border)'}`,
1038
- background: isActive
1039
- ? 'var(--sv-color-text-primary)'
1040
- : 'var(--sv-color-surface-raised)',
1041
- color: isActive ? 'var(--sv-color-surface)' : 'var(--sv-color-text-primary)',
1042
- cursor: 'pointer',
1043
- textAlign: 'left',
1044
- }}
1045
- >
1046
- <div style={{ fontWeight: 600, fontSize: 'var(--sv-font-size-sm)' }}>{stop.label}</div>
1047
- <div style={{ fontSize: 'var(--sv-font-size-xs)', opacity: 0.8 }}>{stop.sublabel}</div>
1048
- </button>
1049
- )}
1050
- />
1051
- );
1052
- }
1053
-
1054
- function ResponsiveSurfaceDemo() {
1055
- return (
1056
- <div
1057
- style={{
1058
- border: '1px dashed var(--sv-color-border)',
1059
- borderRadius: 'var(--sv-radius-md)',
1060
- padding: 'var(--sv-space-4)',
1061
- fontFamily: ff,
1062
- fontSize: 'var(--sv-font-size-sm)',
1063
- color: 'var(--sv-color-text-muted)',
1064
- }}
1065
- >
1066
- <ResponsiveSurface
1067
- web={
1068
- <span style={{ color: 'var(--sv-color-text-primary)' }}>
1069
- Web tree (resize below 768px)
1070
- </span>
1071
- }
1072
- mobile={<span style={{ color: 'var(--sv-color-text-primary)' }}>Mobile tree</span>}
1073
- />
1074
- </div>
1075
- );
1076
- }
1077
-
1078
- const SWIPABLE_CAROUSEL_DEMO_SLIDES = [
1079
- { key: 'lists', label: 'Lists' },
1080
- { key: 'groceries', label: 'Groceries' },
1081
- { key: 'errands', label: 'Errands' },
1082
- ];
1083
-
1084
- function SwipableMobileCarouselDemo() {
1085
- const [activeIndex, setActiveIndex] = useState(0);
1086
- return (
1087
- <div
1088
- style={{
1089
- height: 220,
1090
- border: '1px solid var(--sv-color-border)',
1091
- borderRadius: 'var(--sv-radius-md)',
1092
- overflow: 'hidden',
1093
- }}
1094
- >
1095
- <SwipableMobileCarousel
1096
- activeIndex={activeIndex}
1097
- onSettle={setActiveIndex}
1098
- aria-label="Task lists"
1099
- >
1100
- {SWIPABLE_CAROUSEL_DEMO_SLIDES.map((slide) => (
1101
- <SwipableMobileCarouselSlide key={slide.key} slideKey={slide.key} label={slide.label}>
1102
- <SwipableMobileCarouselSlideHeader>
1103
- <div
1104
- style={{
1105
- padding: 'var(--sv-space-3)',
1106
- fontFamily: ff,
1107
- fontWeight: 'var(--sv-font-weight-semibold)',
1108
- color: 'var(--sv-color-text-primary)',
1109
- }}
1110
- >
1111
- {slide.label}
1112
- </div>
1113
- </SwipableMobileCarouselSlideHeader>
1114
- <SwipableMobileCarouselSlideBody>
1115
- <div
1116
- style={{
1117
- display: 'flex',
1118
- alignItems: 'center',
1119
- justifyContent: 'center',
1120
- height: '100%',
1121
- fontFamily: ff,
1122
- color: 'var(--sv-color-text-muted)',
1123
- }}
1124
- >
1125
- {slide.label} content
1126
- </div>
1127
- </SwipableMobileCarouselSlideBody>
1128
- </SwipableMobileCarouselSlide>
1129
- ))}
1130
- </SwipableMobileCarousel>
1131
- </div>
1132
- );
1133
- }
1134
-
1135
- function SwipableMobileCarouselDotsDemo() {
1136
- const [activeIndex, setActiveIndex] = useState(0);
1137
- return (
1138
- <SwipableMobileCarouselDots
1139
- count={3}
1140
- activeIndex={activeIndex}
1141
- onJump={setActiveIndex}
1142
- labels={['Lists', 'Groceries', 'Errands']}
1143
- aria-label="Task lists"
1144
- />
1145
- );
1146
- }
1147
-
1148
- const SWIPE_STACK_DEMO_CARDS = [
1149
- { id: 'water-plants', title: 'Water the plants' },
1150
- { id: 'standup', title: 'Team standup · 9:30am' },
1151
- { id: 'groceries', title: 'Buy groceries' },
1152
- ];
1153
-
1154
- function SwipeStackDemo() {
1155
- return (
1156
- <div style={{ height: 260, maxWidth: 320 }}>
1157
- <SwipeStack
1158
- aria-label="Today's agenda"
1159
- directions={{
1160
- up: { label: 'Done', icon: 'check' },
1161
- down: { label: 'Defer', icon: 'history' },
1162
- }}
1163
- onSwipe={() => {}}
1164
- >
1165
- {SWIPE_STACK_DEMO_CARDS.map((card) => (
1166
- <SwipeStackCard key={card.id} cardId={card.id}>
1167
- <div
1168
- style={{
1169
- height: '100%',
1170
- boxSizing: 'border-box',
1171
- display: 'flex',
1172
- alignItems: 'center',
1173
- justifyContent: 'center',
1174
- padding: 'var(--sv-space-4)',
1175
- fontFamily: ff,
1176
- fontWeight: 'var(--sv-font-weight-semibold)',
1177
- color: 'var(--sv-color-text-primary)',
1178
- textAlign: 'center',
1179
- }}
1180
- >
1181
- {card.title}
1182
- </div>
1183
- </SwipeStackCard>
1184
- ))}
1185
- </SwipeStack>
1186
- </div>
1187
- );
1188
- }
1189
-
1190
- function IconPickerDemo() {
1191
- const [value, setValue] = useState<IconName | null>('banana');
1192
- const options: IconName[] = [
1193
- 'banana',
1194
- 'apple',
1195
- 'carrot',
1196
- 'egg',
1197
- 'milk',
1198
- 'beef',
1199
- 'drumstick',
1200
- 'fish',
1201
- 'croissant',
1202
- 'cookie',
1203
- ];
1204
- return (
1205
- <IconPicker
1206
- value={value}
1207
- onChange={setValue}
1208
- options={options}
1209
- aria-label="Item icon"
1210
- triggerLabel={value ?? 'Choose icon'}
1211
- />
1212
- );
1213
- }
1214
-
1215
- function ColorPickerDemo() {
1216
- const [value, setValue] = useState<string | null>('#b5c9e8');
1217
- const swatches = [
1218
- { label: 'Sky', value: '#b5c9e8' },
1219
- { label: 'Sage', value: '#c9e0c4' },
1220
- { label: 'Sand', value: '#e8d3b5' },
1221
- { label: 'Clay', value: '#e0c4c4' },
1222
- { label: 'Lilac', value: '#d8c4e0' },
1223
- ];
1224
- return (
1225
- <ColorPicker
1226
- swatches={swatches}
1227
- value={value}
1228
- onChange={setValue}
1229
- allowNone
1230
- aria-label="Board color"
1231
- />
1232
- );
1233
- }
1234
-
1235
- function SuggestionInputDemo() {
1236
- const [value, setValue] = useState('ban');
1237
- const options = ['Bananas', 'Banana bread mix']
1238
- .filter((label) => label.toLowerCase().includes(value.trim().toLowerCase()))
1239
- .map((label, i) => ({ id: String(i), label }));
1240
- return (
1241
- <SuggestionInput
1242
- value={value}
1243
- onChange={setValue}
1244
- options={value.trim() ? options : []}
1245
- onSelect={(o) => setValue(o.label)}
1246
- placeholder="Add an item…"
1247
- aria-label="Add an item"
1248
- createLabel={(v) => `Add "${v}" as a new item`}
1249
- onCreate={setValue}
1250
- />
1251
- );
1252
- }
1253
-
1254
- // ---------------------------------------------------------------------------
1255
- // Main component
1256
- // ---------------------------------------------------------------------------
1257
-
1258
- function OverviewPage() {
1259
- return (
1260
- <div
1261
- style={{
1262
- fontFamily: ff,
1263
- background: 'var(--sv-color-surface)',
1264
- minHeight: '100vh',
1265
- color: 'var(--sv-color-text-primary)',
1266
- }}
1267
- >
1268
- {/* ── Hero ─────────────────────────────────────────────────────────── */}
1269
- <div
1270
- style={{
1271
- padding: 'var(--sv-space-10) var(--sv-space-8)',
1272
- borderBottom: '1px solid var(--sv-color-border)',
1273
- background: 'var(--sv-color-surface-sunken)',
1274
- }}
1275
- >
1276
- <div style={{ maxWidth: 800, margin: '0 auto' }}>
1277
- <div
1278
- style={{
1279
- display: 'flex',
1280
- alignItems: 'center',
1281
- gap: 'var(--sv-space-3)',
1282
- marginBottom: 'var(--sv-space-4)',
1283
- }}
1284
- >
1285
- <Pill>@sovereignfs/ui</Pill>
1286
- <Pill>v1.x</Pill>
1287
- </div>
1288
- <Heading level={1}>Sovereign Design System</Heading>
1289
- <p
1290
- style={{
1291
- fontSize: 'var(--sv-font-size-lg)',
1292
- color: 'var(--sv-color-text-muted)',
1293
- marginTop: 'var(--sv-space-3)',
1294
- maxWidth: 600,
1295
- lineHeight: 1.5,
1296
- }}
1297
- >
1298
- The component library and token system for building Sovereign plugins. Everything here
1299
- is the public contract — available to every plugin developer, stable across minor
1300
- versions.
1301
- </p>
1302
- <div
1303
- style={{
1304
- marginTop: 'var(--sv-space-5)',
1305
- padding: 'var(--sv-space-4)',
1306
- background: 'var(--sv-color-surface)',
1307
- border: '1px solid var(--sv-color-border)',
1308
- borderRadius: 'var(--sv-radius-lg)',
1309
- display: 'flex',
1310
- flexDirection: 'column',
1311
- gap: 'var(--sv-space-2)',
1312
- }}
1313
- >
1314
- <p
1315
- style={{
1316
- fontFamily: ffm,
1317
- fontSize: 'var(--sv-font-size-xs)',
1318
- color: 'var(--sv-color-text-muted)',
1319
- margin: 0,
1320
- }}
1321
- >
1322
- Three things to remember:
1323
- </p>
1324
- <ul
1325
- style={{
1326
- margin: 0,
1327
- paddingLeft: 'var(--sv-space-5)',
1328
- color: 'var(--sv-color-text-primary)',
1329
- fontSize: 'var(--sv-font-size-sm)',
1330
- lineHeight: 1.8,
1331
- }}
1332
- >
1333
- <li>
1334
- Import components from <code style={{ fontFamily: ffm }}>@sovereignfs/ui</code>
1335
- </li>
1336
- <li>
1337
- Use <code style={{ fontFamily: ffm }}>--sv-*</code> semantic tokens in your CSS —
1338
- they are injected globally by the runtime shell, no import needed
1339
- </li>
1340
- <li>Never hardcode hex values or reference primitive tokens directly</li>
1341
- </ul>
1342
- </div>
1343
- </div>
1344
- </div>
1345
-
1346
- <div style={{ maxWidth: 800, margin: '0 auto', padding: 'var(--sv-space-8)' }}>
1347
- {/* ── Quick Start ───────────────────────────────────────────────── */}
1348
- <section style={{ marginBottom: 'var(--sv-space-12)' }}>
1349
- <SectionHeader
1350
- title="Quick start"
1351
- subtitle="Everything you need to build a Sovereign plugin UI."
1352
- />
1353
- <div style={{ display: 'flex', flexDirection: 'column', gap: 'var(--sv-space-4)' }}>
1354
- <div>
1355
- <p
1356
- style={{
1357
- fontSize: 'var(--sv-font-size-caption)',
1358
- color: 'var(--sv-color-text-muted)',
1359
- marginBottom: 'var(--sv-space-2)',
1360
- }}
1361
- >
1362
- Import typed React components:
1363
- </p>
1364
- <Code>{`import { Button, Badge, Input, Icon, Toggle, Tabs } from '@sovereignfs/ui';`}</Code>
1365
- </div>
1366
- <div>
1367
- <p
1368
- style={{
1369
- fontSize: 'var(--sv-font-size-caption)',
1370
- color: 'var(--sv-color-text-muted)',
1371
- marginBottom: 'var(--sv-space-2)',
1372
- }}
1373
- >
1374
- Reference semantic tokens directly in plugin CSS — no import required:
1375
- </p>
1376
- <Code>{`.my-card {
1377
- background: var(--sv-color-surface);
1378
- border: 1px solid var(--sv-color-border);
1379
- border-radius: var(--sv-radius-lg);
1380
- padding: var(--sv-space-4);
1381
- box-shadow: var(--sv-shadow-card);
1382
- font-family: var(--sv-font-family);
1383
- color: var(--sv-color-text-primary);
1384
- }`}</Code>
1385
- </div>
1386
- </div>
1387
- </section>
1388
-
1389
- {/* ── Token Architecture ────────────────────────────────────────── */}
1390
- <section style={{ marginBottom: 'var(--sv-space-12)' }}>
1391
- <SectionHeader
1392
- title="Token architecture"
1393
- subtitle="Two layers — only the semantic layer is a public API."
1394
- />
1395
- <div
1396
- style={{
1397
- display: 'grid',
1398
- gridTemplateColumns: '1fr auto 1fr auto 1fr',
1399
- gap: 'var(--sv-space-3)',
1400
- alignItems: 'center',
1401
- marginBottom: 'var(--sv-space-6)',
1402
- }}
1403
- >
1404
- {[
1405
- {
1406
- label: 'Primitives',
1407
- sub: '--sv-grey-900\n--sv-red-100\n--sv-space-4',
1408
- note: 'Internal only. Raw scale values.',
1409
- muted: true,
1410
- },
1411
- ].map((b) => (
1412
- <div
1413
- key={b.label}
1414
- style={{
1415
- border: `1px solid ${b.muted ? 'var(--sv-color-border)' : 'var(--sv-color-accent)'}`,
1416
- borderRadius: 'var(--sv-radius-lg)',
1417
- padding: 'var(--sv-space-4)',
1418
- opacity: b.muted ? 0.6 : 1,
1419
- }}
1420
- >
1421
- <p
1422
- style={{
1423
- fontSize: 'var(--sv-font-size-sm)',
1424
- fontWeight: 600,
1425
- margin: '0 0 6px',
1426
- color: 'var(--sv-color-text-primary)',
1427
- }}
1428
- >
1429
- {b.label}
1430
- </p>
1431
- <pre
1432
- style={{
1433
- fontFamily: ffm,
1434
- fontSize: '0.6875rem',
1435
- color: 'var(--sv-color-text-muted)',
1436
- margin: 0,
1437
- whiteSpace: 'pre',
1438
- }}
1439
- >
1440
- {b.sub}
1441
- </pre>
1442
- <p
1443
- style={{
1444
- fontSize: 'var(--sv-font-size-caption)',
1445
- color: 'var(--sv-color-text-muted)',
1446
- margin: '8px 0 0',
1447
- }}
1448
- >
1449
- {b.note}
1450
- </p>
1451
- </div>
1452
- ))}
1453
- <div style={{ textAlign: 'center', color: 'var(--sv-color-text-muted)', fontSize: 24 }}>
1454
- →
1455
- </div>
1456
- <div
1457
- style={{
1458
- border: '2px solid var(--sv-color-accent)',
1459
- borderRadius: 'var(--sv-radius-lg)',
1460
- padding: 'var(--sv-space-4)',
1461
- }}
1462
- >
1463
- <p
1464
- style={{
1465
- fontSize: 'var(--sv-font-size-sm)',
1466
- fontWeight: 600,
1467
- margin: '0 0 6px',
1468
- color: 'var(--sv-color-text-primary)',
1469
- }}
1470
- >
1471
- Semantic tokens ✓
1472
- </p>
1473
- <pre
1474
- style={{
1475
- fontFamily: ffm,
1476
- fontSize: '0.6875rem',
1477
- color: 'var(--sv-color-text-muted)',
1478
- margin: 0,
1479
- whiteSpace: 'pre',
1480
- }}
1481
- >
1482
- {'--sv-color-surface\n--sv-color-error-text\n--sv-radius-lg'}
1483
- </pre>
1484
- <p
1485
- style={{
1486
- fontSize: 'var(--sv-font-size-caption)',
1487
- color: 'var(--sv-color-text-muted)',
1488
- margin: '8px 0 0',
1489
- }}
1490
- >
1491
- Plugin public API. Theme-aware.
1492
- </p>
1493
- </div>
1494
- <div style={{ textAlign: 'center', color: 'var(--sv-color-text-muted)', fontSize: 24 }}>
1495
- →
1496
- </div>
1497
- <div
1498
- style={{
1499
- border: '2px solid var(--sv-color-accent)',
1500
- borderRadius: 'var(--sv-radius-lg)',
1501
- padding: 'var(--sv-space-4)',
1502
- }}
1503
- >
1504
- <p
1505
- style={{
1506
- fontSize: 'var(--sv-font-size-sm)',
1507
- fontWeight: 600,
1508
- margin: '0 0 6px',
1509
- color: 'var(--sv-color-text-primary)',
1510
- }}
1511
- >
1512
- Components ✓
1513
- </p>
1514
- <pre
1515
- style={{
1516
- fontFamily: ffm,
1517
- fontSize: '0.6875rem',
1518
- color: 'var(--sv-color-text-muted)',
1519
- margin: 0,
1520
- whiteSpace: 'pre',
1521
- }}
1522
- >
1523
- {'<Button />\n<Badge />\n<Input />'}
1524
- </pre>
1525
- <p
1526
- style={{
1527
- fontSize: 'var(--sv-font-size-caption)',
1528
- color: 'var(--sv-color-text-muted)',
1529
- margin: '8px 0 0',
1530
- }}
1531
- >
1532
- React, typed, RSC-safe.
1533
- </p>
1534
- </div>
1535
- </div>
1536
- <div
1537
- style={{
1538
- background: 'var(--sv-color-warning-surface)',
1539
- border: '1px solid var(--sv-color-warning-border)',
1540
- borderRadius: 'var(--sv-radius-md)',
1541
- padding: 'var(--sv-space-3) var(--sv-space-4)',
1542
- fontSize: 'var(--sv-font-size-sm)',
1543
- color: 'var(--sv-color-warning-text)',
1544
- }}
1545
- >
1546
- <strong>Never use primitive tokens in plugin code.</strong> Primitives like{' '}
1547
- <code style={{ fontFamily: ffm }}>--sv-grey-900</code> or{' '}
1548
- <code style={{ fontFamily: ffm }}>--sv-red-100</code> are fixed values — they do not
1549
- swap with dark mode or instance theming. Only semantic tokens do.
1550
- </div>
1551
- </section>
1552
-
1553
- {/* ── Color System ─────────────────────────────────────────────── */}
1554
- <section style={{ marginBottom: 'var(--sv-space-12)' }}>
1555
- <SectionHeader
1556
- title="Color system"
1557
- subtitle="Semantic groups — use the Themes toolbar above to compare light and dark."
1558
- />
1559
- {COLOR_GROUPS.map((g) => (
1560
- <ColorGroup key={g.label} label={g.label} usage={g.usage} tokens={g.tokens} />
1561
- ))}
1562
- </section>
1563
-
1564
- {/* ── Typography ───────────────────────────────────────────────── */}
1565
- <section style={{ marginBottom: 'var(--sv-space-12)' }}>
1566
- <SectionHeader
1567
- title="Typography"
1568
- subtitle="Hanken Grotesk (body) · JetBrains Mono (code) — fallback stacks apply when web fonts are not loaded."
1569
- />
1570
-
1571
- <div
1572
- style={{
1573
- display: 'grid',
1574
- gridTemplateColumns: '1fr 1fr',
1575
- gap: 'var(--sv-space-6)',
1576
- marginBottom: 'var(--sv-space-6)',
1577
- }}
1578
- >
1579
- <div>
1580
- <p
1581
- style={{
1582
- fontSize: 'var(--sv-font-size-caption)',
1583
- color: 'var(--sv-color-text-muted)',
1584
- marginBottom: 'var(--sv-space-2)',
1585
- }}
1586
- >
1587
- Body — var(--sv-font-family)
1588
- </p>
1589
- <p
1590
- style={{
1591
- fontFamily: ff,
1592
- fontSize: 'var(--sv-font-size-lg)',
1593
- color: 'var(--sv-color-text-primary)',
1594
- margin: 0,
1595
- }}
1596
- >
1597
- The quick brown fox
1598
- </p>
1599
- <p
1600
- style={{
1601
- fontFamily: ff,
1602
- fontSize: 'var(--sv-font-size-sm)',
1603
- color: 'var(--sv-color-text-muted)',
1604
- margin: '4px 0 0',
1605
- }}
1606
- >
1607
- ABCDEFGHIJKLMNOPQRSTUVWXYZ
1608
- </p>
1609
- </div>
1610
- <div>
1611
- <p
1612
- style={{
1613
- fontSize: 'var(--sv-font-size-caption)',
1614
- color: 'var(--sv-color-text-muted)',
1615
- marginBottom: 'var(--sv-space-2)',
1616
- }}
1617
- >
1618
- Mono — var(--sv-font-family-mono)
1619
- </p>
1620
- <p
1621
- style={{
1622
- fontFamily: ffm,
1623
- fontSize: 'var(--sv-font-size-lg)',
1624
- color: 'var(--sv-color-text-primary)',
1625
- margin: 0,
1626
- }}
1627
- >
1628
- const x = 42;
1629
- </p>
1630
- <p
1631
- style={{
1632
- fontFamily: ffm,
1633
- fontSize: 'var(--sv-font-size-sm)',
1634
- color: 'var(--sv-color-text-muted)',
1635
- margin: '4px 0 0',
1636
- }}
1637
- >
1638
- --sv-font-family-mono
1639
- </p>
1640
- </div>
1641
- </div>
1642
-
1643
- <div
1644
- style={{
1645
- border: '1px solid var(--sv-color-border)',
1646
- borderRadius: 'var(--sv-radius-lg)',
1647
- overflow: 'hidden',
1648
- }}
1649
- >
1650
- {TYPE_SCALE.map((t, i) => (
1651
- <div
1652
- key={t.token}
1653
- style={{
1654
- display: 'flex',
1655
- alignItems: 'baseline',
1656
- gap: 'var(--sv-space-4)',
1657
- padding: 'var(--sv-space-3) var(--sv-space-4)',
1658
- borderBottom:
1659
- i < TYPE_SCALE.length - 1 ? '1px solid var(--sv-color-border)' : 'none',
1660
- background:
1661
- i % 2 === 0 ? 'var(--sv-color-surface)' : 'var(--sv-color-surface-sunken)',
1662
- }}
1663
- >
1664
- <span
1665
- style={{
1666
- fontSize: `var(${t.token})`,
1667
- color: 'var(--sv-color-text-primary)',
1668
- lineHeight: 1.2,
1669
- minWidth: 160,
1670
- }}
1671
- >
1672
- {t.label}
1673
- </span>
1674
- <code
1675
- style={{
1676
- fontFamily: ffm,
1677
- fontSize: '0.6875rem',
1678
- color: 'var(--sv-color-text-muted)',
1679
- flexShrink: 0,
1680
- }}
1681
- >
1682
- {t.token}
1683
- </code>
1684
- <span
1685
- style={{
1686
- fontSize: 'var(--sv-font-size-caption)',
1687
- color: 'var(--sv-color-text-muted)',
1688
- marginLeft: 'auto',
1689
- }}
1690
- >
1691
- {t.note}
1692
- </span>
1693
- </div>
1694
- ))}
1695
- </div>
1696
-
1697
- <div style={{ marginTop: 'var(--sv-space-4)' }}>
1698
- <p
1699
- style={{
1700
- fontSize: 'var(--sv-font-size-caption)',
1701
- color: 'var(--sv-color-text-muted)',
1702
- marginBottom: 'var(--sv-space-2)',
1703
- }}
1704
- >
1705
- Font weight tokens:
1706
- </p>
1707
- <Code>{`font-weight: var(--sv-font-weight-regular); /* 400 */
1708
- font-weight: var(--sv-font-weight-medium); /* 500 */
1709
- font-weight: var(--sv-font-weight-semibold); /* 600 */
1710
- font-weight: var(--sv-font-weight-bold); /* 700 */`}</Code>
1711
- </div>
1712
-
1713
- <div style={{ marginTop: 'var(--sv-space-4)' }}>
1714
- <p
1715
- style={{
1716
- fontSize: 'var(--sv-font-size-caption)',
1717
- color: 'var(--sv-color-text-muted)',
1718
- }}
1719
- >
1720
- Every size above is rem-based, so the whole scale responds to the in-app text-size
1721
- control (Account → Preferences → Appearance, task 10.2), which sets{' '}
1722
- <code>[data-text-size]</code> on <code>&lt;html&gt;</code> to override{' '}
1723
- <code>--sv-text-size-scale</code> — see the &quot;Text-size presets&quot; section of
1724
- the Token Gallery.
1725
- </p>
1726
- </div>
1727
- </section>
1728
-
1729
- {/* ── Spacing & Radius ─────────────────────────────────────────── */}
1730
- <section style={{ marginBottom: 'var(--sv-space-12)' }}>
1731
- <SectionHeader title="Spacing & radius" subtitle="4px base grid." />
1732
- <div
1733
- style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 'var(--sv-space-8)' }}
1734
- >
1735
- {/* Spacing */}
1736
- <div>
1737
- <p
1738
- style={{
1739
- fontSize: 'var(--sv-font-size-caption)',
1740
- color: 'var(--sv-color-text-muted)',
1741
- marginBottom: 'var(--sv-space-3)',
1742
- }}
1743
- >
1744
- Spacing (--sv-space-*)
1745
- </p>
1746
- {(
1747
- [
1748
- '--sv-space-1',
1749
- '--sv-space-2',
1750
- '--sv-space-3',
1751
- '--sv-space-4',
1752
- '--sv-space-5',
1753
- '--sv-space-6',
1754
- '--sv-space-8',
1755
- '--sv-space-10',
1756
- '--sv-space-12',
1757
- '--sv-space-16',
1758
- ] as const
1759
- ).map((t) => (
1760
- <div
1761
- key={t}
1762
- style={{ display: 'flex', alignItems: 'center', gap: 10, marginBottom: 8 }}
1763
- >
1764
- <div
1765
- style={{
1766
- width: `var(${t})`,
1767
- minWidth: 4,
1768
- height: 10,
1769
- background: 'var(--sv-color-accent)',
1770
- borderRadius: 2,
1771
- flexShrink: 0,
1772
- }}
1773
- />
1774
- <code
1775
- style={{
1776
- fontFamily: ffm,
1777
- fontSize: '0.6875rem',
1778
- color: 'var(--sv-color-text-muted)',
1779
- }}
1780
- >
1781
- {t}
1782
- </code>
1783
- </div>
1784
- ))}
1785
- </div>
1786
- {/* Radius */}
1787
- <div>
1788
- <p
1789
- style={{
1790
- fontSize: 'var(--sv-font-size-caption)',
1791
- color: 'var(--sv-color-text-muted)',
1792
- marginBottom: 'var(--sv-space-3)',
1793
- }}
1794
- >
1795
- Border radius (--sv-radius-*)
1796
- </p>
1797
- {(
1798
- [
1799
- '--sv-radius-sm',
1800
- '--sv-radius-md',
1801
- '--sv-radius-lg',
1802
- '--sv-radius-xl',
1803
- '--sv-radius-2xl',
1804
- '--sv-radius-3xl',
1805
- '--sv-radius-full',
1806
- ] as const
1807
- ).map((t) => (
1808
- <div
1809
- key={t}
1810
- style={{ display: 'flex', alignItems: 'center', gap: 10, marginBottom: 12 }}
1811
- >
1812
- <div
1813
- style={{
1814
- width: 48,
1815
- height: 24,
1816
- background: 'var(--sv-color-accent)',
1817
- borderRadius: `var(${t})`,
1818
- flexShrink: 0,
1819
- }}
1820
- />
1821
- <code
1822
- style={{
1823
- fontFamily: ffm,
1824
- fontSize: '0.6875rem',
1825
- color: 'var(--sv-color-text-muted)',
1826
- }}
1827
- >
1828
- {t}
1829
- </code>
1830
- </div>
1831
- ))}
1832
- </div>
1833
- </div>
1834
- </section>
1835
-
1836
- {/* ── Shadows ──────────────────────────────────────────────────── */}
1837
- <section style={{ marginBottom: 'var(--sv-space-12)' }}>
1838
- <SectionHeader title="Elevation & shadows" subtitle="Four levels, dark-mode adjusted." />
1839
- <div
1840
- style={{
1841
- display: 'grid',
1842
- gridTemplateColumns: 'repeat(4, 1fr)',
1843
- gap: 'var(--sv-space-5)',
1844
- }}
1845
- >
1846
- {(
1847
- [
1848
- '--sv-shadow-card',
1849
- '--sv-shadow-hover',
1850
- '--sv-shadow-popover',
1851
- '--sv-shadow-overlay',
1852
- ] as const
1853
- ).map((t) => (
1854
- <div
1855
- key={t}
1856
- style={{ display: 'flex', flexDirection: 'column', alignItems: 'center', gap: 12 }}
1857
- >
1858
- <div
1859
- style={{
1860
- width: 64,
1861
- height: 40,
1862
- background: 'var(--sv-color-surface-raised)',
1863
- boxShadow: `var(${t})`,
1864
- borderRadius: 'var(--sv-radius-md)',
1865
- }}
1866
- />
1867
- <code
1868
- style={{
1869
- fontFamily: ffm,
1870
- fontSize: '0.6875rem',
1871
- color: 'var(--sv-color-text-muted)',
1872
- textAlign: 'center',
1873
- }}
1874
- >
1875
- {t.replace('--sv-shadow-', '')}
1876
- </code>
1877
- </div>
1878
- ))}
1879
- </div>
1880
- </section>
1881
-
1882
- {/* ── Component Gallery ─────────────────────────────────────────── */}
1883
- <section style={{ marginBottom: 'var(--sv-space-12)' }}>
1884
- <SectionHeader
1885
- title="Component gallery"
1886
- subtitle="All 77 components — click each story in the sidebar for the full API, variants, and controls."
1887
- />
1888
-
1889
- <div style={{ marginBottom: 'var(--sv-space-6)' }}>
1890
- <SystemBanner variant="info">
1891
- All components reference <code style={{ fontFamily: ffm }}>--sv-*</code> tokens
1892
- internally — they automatically adapt to dark mode and instance theming without any
1893
- extra configuration.
1894
- </SystemBanner>
1895
- </div>
1896
-
1897
- <div
1898
- style={{
1899
- display: 'grid',
1900
- gridTemplateColumns: 'repeat(auto-fill, minmax(340px, 1fr))',
1901
- gap: 'var(--sv-space-5)',
1902
- }}
1903
- >
1904
- {/* Button */}
1905
- <ComponentCard
1906
- name="Button"
1907
- importLine="import { Button } from '@sovereignfs/ui';"
1908
- 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)."
1909
- >
1910
- <Button>Primary</Button>
1911
- <Button variant="secondary">Secondary</Button>
1912
- <Button variant="ghost">Ghost</Button>
1913
- <Button variant="destructive" size="sm">
1914
- Delete
1915
- </Button>
1916
- <Button size="lg">Large</Button>
1917
- <Button loading>Saving…</Button>
1918
- </ComponentCard>
1919
-
1920
- {/* Badge */}
1921
- <ComponentCard
1922
- name="Badge"
1923
- importLine="import { Badge } from '@sovereignfs/ui';"
1924
- usage="Compact label for roles (role), lifecycle states (status), and type/version tags (mono). xs/sm/md/lg sizes; ALL CAPS by default, or uppercase={false} for title case. RSC-safe."
1925
- >
1926
- <Badge variant="role">Admin</Badge>
1927
- <Badge variant="status" status="active">
1928
- Active
1929
- </Badge>
1930
- <Badge variant="status" status="invited">
1931
- Invited
1932
- </Badge>
1933
- <Badge variant="mono">v1.2.0</Badge>
1934
- <Badge variant="role" size="lg" uppercase={false}>
1935
- Owner
1936
- </Badge>
1937
- <Badge variant="status" status="active" size="xs">
1938
- Connected
1939
- </Badge>
1940
- </ComponentCard>
1941
-
1942
- {/* StatusBadge */}
1943
- <ComponentCard
1944
- name="StatusBadge"
1945
- importLine="import { StatusBadge } from '@sovereignfs/ui';"
1946
- usage="Inline status indicator for editor sync, draft, conflict, and delete-pending states. RSC-safe."
1947
- >
1948
- <StatusBadge status="draft" />
1949
- <StatusBadge status="synced" />
1950
- <StatusBadge status="conflict" />
1951
- <StatusBadge status="pending-delete" />
1952
- </ComponentCard>
1953
-
1954
- {/* BalanceChip */}
1955
- <ComponentCard
1956
- name="BalanceChip"
1957
- importLine="import { BalanceChip } from '@sovereignfs/ui';"
1958
- usage="Inline net-balance indicator — green when owed to them, red when they owe, neutral when settled. Not tied to expense-splitting specifically."
1959
- >
1960
- <BalanceChip amountCents={2500} currency="USD" />
1961
- <BalanceChip amountCents={-1350} currency="USD" />
1962
- <BalanceChip amountCents={0} currency="USD" />
1963
- </ComponentCard>
1964
-
1965
- {/* Input */}
1966
- <ComponentCard
1967
- name="Input"
1968
- importLine="import { Input } from '@sovereignfs/ui';"
1969
- usage="Primitive text field. No label built-in — always pair with <label> for accessibility. Forwards all native input props."
1970
- >
1971
- <div style={{ width: '100%', display: 'flex', flexDirection: 'column', gap: 6 }}>
1972
- <Input placeholder="Email address" type="email" style={{ width: '100%' }} />
1973
- <Input placeholder="Disabled" disabled style={{ width: '100%' }} />
1974
- </div>
1975
- </ComponentCard>
1976
-
1977
- {/* CurrencyInput */}
1978
- <ComponentCard
1979
- name="CurrencyInput"
1980
- importLine="import { CurrencyInput } from '@sovereignfs/ui';"
1981
- usage="Decimal amount entry that reports its value as integer cents. Preserves in-progress typing instead of reformatting on every keystroke."
1982
- >
1983
- <CurrencyInputDemo />
1984
- </ComponentCard>
1985
-
1986
- {/* TagInput */}
1987
- <ComponentCard
1988
- name="TagInput"
1989
- importLine="import { TagInput } from '@sovereignfs/ui';"
1990
- usage="Controlled multi-value input for tags and frontmatter arrays. Enter/comma adds, Backspace removes, paste can split multiple tags."
1991
- >
1992
- <TagInputDemo />
1993
- </ComponentCard>
1994
-
1995
- {/* SuggestionInput */}
1996
- <ComponentCard
1997
- name="SuggestionInput"
1998
- importLine="import { SuggestionInput } from '@sovereignfs/ui';"
1999
- usage="Text field with an anchored, keyboard-navigable async suggestion list, plus an optional trailing 'create new' row. Built on Popover."
2000
- >
2001
- <div style={{ width: '100%' }}>
2002
- <SuggestionInputDemo />
2003
- </div>
2004
- </ComponentCard>
2005
-
2006
- {/* IconPicker */}
2007
- <ComponentCard
2008
- name="IconPicker"
2009
- importLine="import { IconPicker } from '@sovereignfs/ui';"
2010
- usage="Trigger button + Popover grid for a curated, bounded icon set (e.g. a plugin's category icons) — not the full icon library."
2011
- >
2012
- <IconPickerDemo />
2013
- </ComponentCard>
2014
-
2015
- {/* ColorPicker */}
2016
- <ComponentCard
2017
- name="ColorPicker"
2018
- importLine="import { ColorPicker } from '@sovereignfs/ui';"
2019
- usage="Curated swatch suggestions plus a native color-input trigger for picking any color — not just the curated set."
2020
- >
2021
- <ColorPickerDemo />
2022
- </ComponentCard>
2023
-
2024
- {/* QuantityStepper */}
2025
- <ComponentCard
2026
- name="QuantityStepper"
2027
- importLine="import { QuantityStepper } from '@sovereignfs/ui';"
2028
- usage="Numeric input with +/- buttons and an optional read-only unit suffix. Supports fractional step values (e.g. 0.5 kg)."
2029
- >
2030
- <QuantityStepperDemo />
2031
- </ComponentCard>
2032
-
2033
- {/* Select */}
2034
- <ComponentCard
2035
- name="Select"
2036
- importLine="import { Select } from '@sovereignfs/ui';"
2037
- usage="Styled native <select> — same visual language as Input. Preserves native picker on mobile. RSC-safe."
2038
- >
2039
- <div style={{ width: '100%' }}>
2040
- <Select defaultValue="admin" style={{ width: '100%' }}>
2041
- <option value="user">User</option>
2042
- <option value="admin">Admin</option>
2043
- <option value="owner">Owner</option>
2044
- </Select>
2045
- </div>
2046
- </ComponentCard>
2047
-
2048
- {/* Toggle */}
2049
- <ComponentCard
2050
- name="Toggle"
2051
- importLine="import { Toggle } from '@sovereignfs/ui';"
2052
- usage="38×22px binary switch. Renders as role='switch' for screen reader support. aria-label is required."
2053
- >
2054
- <ToggleDemo />
2055
- <span
2056
- style={{
2057
- fontFamily: ff,
2058
- fontSize: 'var(--sv-font-size-sm)',
2059
- color: 'var(--sv-color-text-muted)',
2060
- }}
2061
- >
2062
- Click to toggle
2063
- </span>
2064
- </ComponentCard>
2065
-
2066
- {/* SegmentedControl */}
2067
- <ComponentCard
2068
- name="SegmentedControl"
2069
- importLine="import { SegmentedControl } from '@sovereignfs/ui';"
2070
- usage="Pill-based 2–3 option picker for inline use (role selector, theme switcher). Renders as role='radiogroup'."
2071
- >
2072
- <SegmentedDemo />
2073
- </ComponentCard>
2074
-
2075
- {/* SplitMethodSelector */}
2076
- <ComponentCard
2077
- name="SplitMethodSelector"
2078
- importLine="import { SplitMethodSelector } from '@sovereignfs/ui';"
2079
- usage="The four-way Equal/Amount/Percentage/Shares picker shared by any cost-splitting plugin. A thin SegmentedControl preset."
2080
- >
2081
- <SplitMethodSelectorDemo />
2082
- </ComponentCard>
2083
-
2084
- {/* Tabs */}
2085
- <ComponentCard
2086
- name="Tabs"
2087
- importLine="import { Tabs } from '@sovereignfs/ui';"
2088
- usage="Underline tab nav. Stateless — caller owns value + onChange. Scrolls horizontally on mobile."
2089
- >
2090
- <TabsDemo />
2091
- </ComponentCard>
2092
-
2093
- {/* Icon */}
2094
- <ComponentCard
2095
- name="Icon"
2096
- importLine="import { Icon } from '@sovereignfs/ui';"
2097
- usage="SVG icon primitive. 96 bundled icons. Decorative: aria-hidden. Meaningful: aria-label. Three sizes: sm, md, lg."
2098
- >
2099
- {(
2100
- [
2101
- 'house',
2102
- 'settings',
2103
- 'bell',
2104
- 'user',
2105
- 'shield',
2106
- 'mail',
2107
- 'search',
2108
- 'plus',
2109
- 'trash-2',
2110
- 'external-link',
2111
- ] as const
2112
- ).map((n) => (
2113
- <Icon key={n} name={n} size="md" aria-hidden />
2114
- ))}
2115
- </ComponentCard>
2116
-
2117
- {/* Dialog */}
2118
- <ComponentCard
2119
- name="Dialog"
2120
- importLine="import { Dialog } from '@sovereignfs/ui';"
2121
- usage="Modal surface (scrim + panel). Esc, scrim-click, focus trap. Sizes: sm, md, lg, full. Mobile renders fullscreen."
2122
- >
2123
- <DialogDemo />
2124
- </ComponentCard>
2125
-
2126
- {/* Drawer */}
2127
- <ComponentCard
2128
- name="Drawer"
2129
- importLine="import { Drawer } from '@sovereignfs/ui';"
2130
- 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."
2131
- >
2132
- <DrawerDemo />
2133
- </ComponentCard>
2134
-
2135
- {/* OverlayHeader */}
2136
- <ComponentCard
2137
- name="OverlayHeader"
2138
- importLine="import { OverlayHeader } from '@sovereignfs/ui';"
2139
- 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."
2140
- >
2141
- <OverlayHeaderDemo />
2142
- </ComponentCard>
2143
-
2144
- {/* Sheet */}
2145
- <ComponentCard
2146
- name="Sheet"
2147
- importLine="import { Sheet } from '@sovereignfs/ui';"
2148
- 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."
2149
- >
2150
- <SheetDemo />
2151
- </ComponentCard>
2152
-
2153
- {/* ConfirmDialog */}
2154
- <ComponentCard
2155
- name="ConfirmDialog"
2156
- importLine="import { ConfirmDialog } from '@sovereignfs/ui';"
2157
- 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."
2158
- >
2159
- <ConfirmDialogDemo />
2160
- </ComponentCard>
2161
-
2162
- {/* Popover */}
2163
- <ComponentCard
2164
- name="Popover"
2165
- importLine="import { Popover } from '@sovereignfs/ui';"
2166
- usage="Floating panel anchored below a trigger. Non-modal. Closes on outside click or Escape. Left or right aligned."
2167
- >
2168
- <PopoverDemo />
2169
- </ComponentCard>
2170
-
2171
- {/* Menu */}
2172
- <ComponentCard
2173
- name="Menu"
2174
- importLine="import { Menu } from '@sovereignfs/ui';"
2175
- 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."
2176
- >
2177
- <MenuDemo />
2178
- </ComponentCard>
2179
-
2180
- {/* MenuEntries */}
2181
- <ComponentCard
2182
- name="MenuEntries"
2183
- importLine="import { MenuEntries } from '@sovereignfs/ui';"
2184
- usage='The <ul role="menu"> item list itself, factored out of Menu so other adaptive-surface components (ContextMenu, Menubar) can render the exact same items/destructive/checked/icon markup inside their own positioning shell instead of re-deriving it. No panel chrome of its own — the background/border/shadow around it belongs to whichever surface hosts it.'
2185
- >
2186
- <MenuEntriesDemo />
2187
- </ComponentCard>
2188
-
2189
- {/* Calendar */}
2190
- <ComponentCard
2191
- name="Calendar"
2192
- importLine="import { Calendar } from '@sovereignfs/ui';"
2193
- usage="Keyboard-navigable month grid. Date-only (no time/range yet). Arrow keys, Home/End, PageUp/PageDown, Enter/Space to select."
2194
- >
2195
- <CalendarDemo />
2196
- </ComponentCard>
2197
-
2198
- {/* DatePicker */}
2199
- <ComponentCard
2200
- name="DatePicker"
2201
- importLine="import { DatePicker } from '@sovereignfs/ui';"
2202
- usage="Form field pairing a trigger with Calendar: Popover on desktop, Drawer on mobile. Built-in trigger, unlike Menu's caller-supplied one."
2203
- >
2204
- <DatePickerDemo />
2205
- </ComponentCard>
2206
-
2207
- {/* SystemBanner */}
2208
- <ComponentCard
2209
- name="SystemBanner"
2210
- importLine="import { SystemBanner } from '@sovereignfs/ui';"
2211
- usage="Full-width sticky strip for platform-level notices. Three variants: info, warning, error. Dismissible optional."
2212
- >
2213
- <div style={{ width: '100%', display: 'flex', flexDirection: 'column', gap: 4 }}>
2214
- <SystemBanner variant="info">Read-only mode during migration.</SystemBanner>
2215
- <SystemBanner variant="warning">License expires in 7 days.</SystemBanner>
2216
- <SystemBanner variant="error">Maintenance mode active.</SystemBanner>
2217
- </div>
2218
- </ComponentCard>
2219
-
2220
- {/* Toast */}
2221
- <ComponentCard
2222
- name="Toast"
2223
- importLine="import { ToastProvider, useToast } from '@sovereignfs/ui';"
2224
- usage="Fixed top-right on desktop, bottom-center on mobile. Wrap app in ToastProvider; call useToast().show() imperatively. Six categories."
2225
- >
2226
- <ToastProvider>
2227
- <ToastDemo />
2228
- </ToastProvider>
2229
- </ComponentCard>
2230
-
2231
- {/* Card */}
2232
- <ComponentCard
2233
- name="Card"
2234
- importLine="import { Card } from '@sovereignfs/ui';"
2235
- usage="Surface container with border, shadow, and padding. Use as='article' or 'li' for semantics. Add interactive for hover/focus styles on clickable cards."
2236
- >
2237
- <div style={{ display: 'flex', flexDirection: 'column', gap: 8, width: '100%' }}>
2238
- <Card padding="sm">Small padding card</Card>
2239
- <Card padding="md" interactive>
2240
- Interactive card — hover me
2241
- </Card>
2242
- </div>
2243
- </ComponentCard>
2244
-
2245
- {/* CardTile */}
2246
- <ComponentCard
2247
- name="CardTile"
2248
- importLine="import { CardTile, CardTileGrid, NewCardTile } from '@sovereignfs/ui';"
2249
- usage="A colored/iconed banner over a footer label, for a grid of navigable top-level entities (projects, boards). Card can't express this — its padding always wraps the whole box, with no bleed slot for a full-width banner. Not link-aware; wrap each tile in the host router's own link component. Pair with NewCardTile for the grid's 'add new' affordance. The icon variant below swaps the card chrome for a plain Finder-style icon + label, for entities that read as a single glyph + name (e.g. a folder)."
2250
- >
2251
- <CardTileGrid minTileWidth={140}>
2252
- <CardTile banner={<Icon name="folder" size="lg" aria-hidden={true} />}>
2253
- Q4 Planning
2254
- </CardTile>
2255
- <CardTile bannerColor="#c7d8ff">Marketing site</CardTile>
2256
- <NewCardTile label="New project" />
2257
- </CardTileGrid>
2258
- <div style={{ marginTop: 'var(--sv-space-4)' }}>
2259
- <CardTileGrid dense minTileWidth={100}>
2260
- <CardTile
2261
- variant="icon"
2262
- banner={<Icon name="folder-closed" size="lg" aria-hidden={true} />}
2263
- >
2264
- Handbook
2265
- </CardTile>
2266
- <CardTile
2267
- variant="icon"
2268
- banner={<Icon name="folder-closed" size="lg" aria-hidden={true} />}
2269
- >
2270
- Q4 Planning
2271
- </CardTile>
2272
- <NewCardTile variant="icon" label="New" />
2273
- </CardTileGrid>
2274
- </div>
2275
- </ComponentCard>
2276
-
2277
- {/* FormField */}
2278
- <ComponentCard
2279
- name="FormField"
2280
- importLine="import { FormField } from '@sovereignfs/ui';"
2281
- 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."
2282
- >
2283
- <div style={{ display: 'flex', flexDirection: 'column', gap: 12, width: '100%' }}>
2284
- <FormField label="Email" hint="We'll never share this." id="ov-email">
2285
- {(field) => <Input {...field} type="email" placeholder="you@example.com" />}
2286
- </FormField>
2287
- <FormField label="Password" error="Must be 8+ characters." id="ov-pw">
2288
- {(field) => <Input {...field} type="password" />}
2289
- </FormField>
2290
- </div>
2291
- </ComponentCard>
2292
-
2293
- {/* Textarea */}
2294
- <ComponentCard
2295
- name="Textarea"
2296
- importLine="import { Textarea } from '@sovereignfs/ui';"
2297
- usage="The primitive multi-line text field. Forwards all native textarea props. Pair with FormField or a <label> for accessibility."
2298
- >
2299
- <Textarea
2300
- aria-label="Description"
2301
- placeholder="Add a description…"
2302
- style={{ width: '100%' }}
2303
- />
2304
- </ComponentCard>
2305
-
2306
- {/* CodeTextarea */}
2307
- <ComponentCard
2308
- name="CodeTextarea"
2309
- importLine="import { CodeTextarea } from '@sovereignfs/ui';"
2310
- usage="Monospace textarea for Markdown, YAML, JSON, and raw frontmatter editing. Pair with FormField for labels and validation."
2311
- >
2312
- <CodeTextarea
2313
- aria-label="YAML source"
2314
- defaultValue={'title: Release notes\ntags:\n - launch'}
2315
- rows={5}
2316
- style={{ width: '100%' }}
2317
- />
2318
- </ComponentCard>
2319
-
2320
- {/* SplitPane */}
2321
- <ComponentCard
2322
- name="SplitPane"
2323
- importLine="import { SplitPane } from '@sovereignfs/ui';"
2324
- usage="Responsive editor/preview or list/detail layout. Desktop panes are resizable with pointer and keyboard controls; mobile stacks to one column."
2325
- >
2326
- <SplitPane
2327
- defaultPrimarySize={44}
2328
- primary={
2329
- <div
2330
- style={{
2331
- padding: 'var(--sv-space-3)',
2332
- fontFamily: ffm,
2333
- fontSize: 'var(--sv-font-size-xs)',
2334
- color: 'var(--sv-color-text-primary)',
2335
- }}
2336
- >
2337
- # Draft
2338
- </div>
2339
- }
2340
- secondary={
2341
- <div
2342
- style={{
2343
- padding: 'var(--sv-space-3)',
2344
- fontFamily: ff,
2345
- fontSize: 'var(--sv-font-size-sm)',
2346
- color: 'var(--sv-color-text-primary)',
2347
- }}
2348
- >
2349
- Draft preview
2350
- </div>
2351
- }
2352
- />
2353
- </ComponentCard>
2354
-
2355
- {/* ThreeColumnLayout */}
2356
- <ComponentCard
2357
- name="ThreeColumnLayout"
2358
- importLine="import { ThreeColumnLayout } from '@sovereignfs/ui';"
2359
- usage="Sidebar + main + optional detail column, for list-app shells (Tasks, Shopper). Purely positional — pass 2 children for sidebar+main, or 3 for sidebar+main+detail; the third is simply omitted when nothing is selected."
2360
- >
2361
- <div style={{ width: '100%', height: '220px' }}>
2362
- <ThreeColumnLayout sidebarWidth={120} detailWidth={140}>
2363
- <div
2364
- style={{
2365
- padding: 'var(--sv-space-3)',
2366
- fontFamily: ffm,
2367
- fontSize: 'var(--sv-font-size-xs)',
2368
- color: 'var(--sv-color-text-primary)',
2369
- }}
2370
- >
2371
- Sidebar
2372
- </div>
2373
- <div
2374
- style={{
2375
- padding: 'var(--sv-space-3)',
2376
- fontFamily: ffm,
2377
- fontSize: 'var(--sv-font-size-xs)',
2378
- color: 'var(--sv-color-text-primary)',
2379
- }}
2380
- >
2381
- Main
2382
- </div>
2383
- <div
2384
- style={{
2385
- padding: 'var(--sv-space-3)',
2386
- fontFamily: ffm,
2387
- fontSize: 'var(--sv-font-size-xs)',
2388
- color: 'var(--sv-color-text-primary)',
2389
- }}
2390
- >
2391
- Detail
2392
- </div>
2393
- </ThreeColumnLayout>
2394
- </div>
2395
- </ComponentCard>
2396
-
2397
- {/* HeaderFooterLayout */}
2398
- <ComponentCard
2399
- name="HeaderFooterLayout"
2400
- importLine="import { HeaderFooterLayout } from '@sovereignfs/ui';"
2401
- usage="Header + main + optional footer, both fixed-height and independently optional; main always claims the remaining height. The vertical counterpart to ThreeColumnLayout."
2402
- >
2403
- <div style={{ width: '100%', height: '220px' }}>
2404
- <HeaderFooterLayout
2405
- headerHeight={36}
2406
- footerHeight={36}
2407
- header={
2408
- <div
2409
- style={{
2410
- display: 'flex',
2411
- alignItems: 'center',
2412
- justifyContent: 'center',
2413
- height: '100%',
2414
- fontFamily: ffm,
2415
- fontSize: 'var(--sv-font-size-xs)',
2416
- color: 'var(--sv-color-text-primary)',
2417
- }}
2418
- >
2419
- Header
2420
- </div>
2421
- }
2422
- footer={
2423
- <div
2424
- style={{
2425
- display: 'flex',
2426
- alignItems: 'center',
2427
- justifyContent: 'center',
2428
- height: '100%',
2429
- fontFamily: ffm,
2430
- fontSize: 'var(--sv-font-size-xs)',
2431
- color: 'var(--sv-color-text-primary)',
2432
- }}
2433
- >
2434
- Footer
2435
- </div>
2436
- }
2437
- >
2438
- <div
2439
- style={{
2440
- padding: 'var(--sv-space-3)',
2441
- fontFamily: ffm,
2442
- fontSize: 'var(--sv-font-size-xs)',
2443
- color: 'var(--sv-color-text-primary)',
2444
- }}
2445
- >
2446
- Main (scrolls)
2447
- </div>
2448
- </HeaderFooterLayout>
2449
- </div>
2450
- </ComponentCard>
2451
-
2452
- {/* RootLayout */}
2453
- <ComponentCard
2454
- name="RootLayout"
2455
- importLine="import { RootLayout } from '@sovereignfs/ui';"
2456
- usage="The root-level layout a plugin's page tree sits in. variant='sidebar' is web-only (sidebar + main); variant='shell' is mobile-only (header + main + footer) — same children, different fork per breakpoint via ResponsiveSurface. Shown here: variant='header', identical on both breakpoints."
2457
- >
2458
- <div style={{ width: '100%', height: '160px' }}>
2459
- <RootLayout variant="header" headerHeight={32}>
2460
- <div
2461
- style={{
2462
- display: 'flex',
2463
- alignItems: 'center',
2464
- justifyContent: 'center',
2465
- height: '100%',
2466
- fontFamily: ffm,
2467
- fontSize: 'var(--sv-font-size-xs)',
2468
- color: 'var(--sv-color-text-primary)',
2469
- }}
2470
- >
2471
- Header
2472
- </div>
2473
- <div
2474
- style={{
2475
- padding: 'var(--sv-space-3)',
2476
- fontFamily: ffm,
2477
- fontSize: 'var(--sv-font-size-xs)',
2478
- color: 'var(--sv-color-text-primary)',
2479
- }}
2480
- >
2481
- Main
2482
- </div>
2483
- </RootLayout>
2484
- </div>
2485
- </ComponentCard>
2486
-
2487
- {/* PageLayout */}
2488
- <ComponentCard
2489
- name="PageLayout"
2490
- importLine="import { PageLayout } from '@sovereignfs/ui';"
2491
- usage="A single page's content area, nested inside RootLayout's main slot. No padding by default (opt in via padding, same scale as PageContainer). The optional header is page-specific — a board title/toolbar, not the app-level header."
2492
- >
2493
- <div
2494
- style={{
2495
- width: '100%',
2496
- height: '160px',
2497
- border: '1px solid var(--sv-color-border)',
2498
- }}
2499
- >
2500
- <PageLayout
2501
- padding="sm"
2502
- header={
2503
- <div
2504
- style={{
2505
- padding: 'var(--sv-space-2) var(--sv-space-3)',
2506
- borderBottom: '1px solid var(--sv-color-border)',
2507
- fontFamily: ffm,
2508
- fontSize: 'var(--sv-font-size-xs)',
2509
- fontWeight: 600,
2510
- color: 'var(--sv-color-text-primary)',
2511
- }}
2512
- >
2513
- Page header
2514
- </div>
2515
- }
2516
- >
2517
- <div
2518
- style={{
2519
- fontFamily: ffm,
2520
- fontSize: 'var(--sv-font-size-xs)',
2521
- color: 'var(--sv-color-text-primary)',
2522
- }}
2523
- >
2524
- Page content
2525
- </div>
2526
- </PageLayout>
2527
- </div>
2528
- </ComponentCard>
2529
-
2530
- {/* PageHeader */}
2531
- <ComponentCard
2532
- name="PageHeader"
2533
- importLine="import { PageHeader } from '@sovereignfs/ui';"
2534
- usage="Plugin page top section. Title + optional description + right-side action slot. Replaces the hand-rolled .pageHeader pattern in every plugin. An optional `icon` renders before the title text on both breakpoints. On mobile, description drops out and onBack/onMenuClick add an optional back button and vertical-ellipsis menu trigger."
2535
- >
2536
- <div style={{ width: '100%' }}>
2537
- <PageHeader
2538
- title="Users"
2539
- description="Manage who has access to this instance."
2540
- action={<Button size="sm">Invite user</Button>}
2541
- />
2542
- </div>
2543
- </ComponentCard>
2544
-
2545
- {/* PageContainer */}
2546
- <ComponentCard
2547
- name="PageContainer"
2548
- importLine="import { PageContainer } from '@sovereignfs/ui';"
2549
- usage="The wrapper every plugin page renders through. Supplies the page gutter (padding: none/sm/md/lg, default md — the runtime shell no longer pads plugin content) and constrains width (maxWidth: sm/md/lg/full, default full). Use instead of declaring padding or max-width in a plugin's own CSS."
2550
- >
2551
- <div style={{ width: '100%', background: 'var(--sv-color-surface-sunken)' }}>
2552
- <PageContainer maxWidth="sm" padding="md">
2553
- <div
2554
- style={{
2555
- background: 'var(--sv-color-surface-raised)',
2556
- border: '1px dashed var(--sv-color-border)',
2557
- borderRadius: 'var(--sv-radius-md)',
2558
- padding: 'var(--sv-space-4)',
2559
- }}
2560
- >
2561
- maxWidth=&quot;sm&quot; padding=&quot;md&quot;
2562
- </div>
2563
- </PageContainer>
2564
- </div>
2565
- </ComponentCard>
2566
-
2567
- {/* Header */}
2568
- <ComponentCard
2569
- name="Header"
2570
- importLine="import { Header } from '@sovereignfs/ui';"
2571
- usage="The web top-bar header for shell: minimal plugins that own their whole viewport (no platform sidebar). Brand badge + optional active plugin on the left; launcher (AppsLauncher), notifications (NotificationsPanel), and avatarMenu (UserMenu) on the right — all real, shared, independently clickable components. Presentational only — see Components/Header for the mobile counterpart (MobileHeader) shown side by side."
2572
- >
2573
- <Header
2574
- instanceName="Sovereign"
2575
- homeHref="/launcher"
2576
- plugin={{
2577
- // The plugin icon is 28px — the same size as the brand
2578
- // badge next to it, not a smaller inline glyph.
2579
- name: 'Notes',
2580
- icon: (
2581
- <span
2582
- aria-hidden="true"
2583
- style={{
2584
- display: 'inline-flex',
2585
- alignItems: 'center',
2586
- justifyContent: 'center',
2587
- width: 28,
2588
- height: 28,
2589
- }}
2590
- >
2591
- <span style={{ display: 'inline-flex', transform: 'scale(1.15)' }}>
2592
- <Icon name="file" size="lg" aria-hidden />
2593
- </span>
2594
- </span>
2595
- ),
2596
- href: '/notes',
2597
- }}
2598
- launcher={
2599
- <AppsLauncher
2600
- items={[
2601
- {
2602
- key: 'home',
2603
- icon: <Icon name="house" size="md" aria-hidden />,
2604
- label: 'Home',
2605
- href: '/launcher',
2606
- },
2607
- {
2608
- key: 'notes',
2609
- icon: <Icon name="file" size="md" aria-hidden />,
2610
- label: 'Notes',
2611
- href: '/notes',
2612
- },
2613
- ]}
2614
- />
2615
- }
2616
- notifications={<NotificationsPanel items={[]} unreadCount={0} />}
2617
- avatarMenu={
2618
- <UserMenu
2619
- name="Kasun Benthara"
2620
- email="kasun@openfs.io"
2621
- items={[
2622
- { label: 'Account', icon: 'user', href: '/account' },
2623
- { label: 'Sign out', icon: 'log-out', destructive: true, onSelect: () => {} },
2624
- ]}
2625
- size="sm"
2626
- />
2627
- }
2628
- />
2629
- </ComponentCard>
2630
-
2631
- {/* AppsLauncher */}
2632
- <ComponentCard
2633
- name="AppsLauncher"
2634
- importLine="import { AppsLauncher } from '@sovereignfs/ui';"
2635
- usage="The desktop 'Apps' switcher — a 28px grid trigger opening a Popover tile grid. The desktop counterpart to MobileAppsDrawer: same items shape (key/icon/label/href/onClick), different chrome. Presentational only — items are supplied already resolved; loading/error are separate props."
2636
- >
2637
- <AppsLauncher
2638
- items={[
2639
- {
2640
- key: 'home',
2641
- icon: <Icon name="house" size="md" aria-hidden />,
2642
- label: 'Home',
2643
- href: '/launcher',
2644
- },
2645
- {
2646
- key: 'console',
2647
- icon: <Icon name="layout-dashboard" size="md" aria-hidden />,
2648
- label: 'Console',
2649
- href: '/console',
2650
- },
2651
- {
2652
- key: 'notes',
2653
- icon: <Icon name="file" size="md" aria-hidden />,
2654
- label: 'Notes',
2655
- href: '/notes',
2656
- },
2657
- ]}
2658
- />
2659
- </ComponentCard>
2660
-
2661
- {/* NotificationsPanel */}
2662
- <ComponentCard
2663
- name="NotificationsPanel"
2664
- importLine="import { NotificationsPanel } from '@sovereignfs/ui';"
2665
- usage="The bell trigger + dropdown, generalized from the runtime shell's own NotificationBell — title/Mark all read/Clear all/close, then category-icon + title + time + unread-dot + dismiss rows. Pure presentation: no fetch/SSE/mark-read logic of its own; the consumer supplies already-resolved items and wires onOpen/onDismiss/onMarkAllRead/onClearAll."
2666
- >
2667
- <NotificationsPanel
2668
- items={[
2669
- {
2670
- id: '1',
2671
- icon: <Icon name="layers" size="sm" aria-hidden />,
2672
- title: 'Added to a project',
2673
- timeLabel: '2d ago',
2674
- read: false,
2675
- onDismiss: () => {},
2676
- },
2677
- ]}
2678
- unreadCount={1}
2679
- onMarkAllRead={() => {}}
2680
- onClearAll={() => {}}
2681
- />
2682
- </ComponentCard>
2683
-
2684
- {/* UserMenu */}
2685
- <ComponentCard
2686
- name="UserMenu"
2687
- importLine="import { UserMenu } from '@sovereignfs/ui';"
2688
- usage="The account avatar trigger + dropdown, generalized from the runtime shell's own AccountMenu — accent-filled avatar, a name/email header block, then a plain item list. Presentational only: no auth/session logic — items carry their own onSelect and/or href (e.g. a real 'Sign out' item just does whatever the consuming app's sign-out requires)."
2689
- >
2690
- <UserMenu
2691
- name="Kasun Benthara"
2692
- email="kasun@openfs.io"
2693
- items={[
2694
- { label: 'Account', icon: 'user', href: '/account' },
2695
- {
2696
- label: 'Preferences',
2697
- icon: 'sliders-horizontal',
2698
- href: '/account/preferences',
2699
- },
2700
- { label: 'Sign out', icon: 'log-out', destructive: true, onSelect: () => {} },
2701
- ]}
2702
- />
2703
- </ComponentCard>
2704
-
2705
- {/* MobileHeader */}
2706
- <ComponentCard
2707
- name="MobileHeader"
2708
- importLine="import { MobileHeader } from '@sovereignfs/ui';"
2709
- 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."
2710
- >
2711
- <div
2712
- style={{
2713
- width: '100%',
2714
- background: 'var(--sv-color-surface)',
2715
- border: '1px solid var(--sv-color-border)',
2716
- borderRadius: 'var(--sv-radius-md)',
2717
- padding: '0 var(--sv-space-3)',
2718
- }}
2719
- >
2720
- <MobileHeader
2721
- title="Tasks"
2722
- bell={<Icon name="bell" size="lg" aria-label="Notifications" />}
2723
- avatarMenu={<Avatar name="Jamie Doe" size="sm" />}
2724
- />
2725
- </div>
2726
- </ComponentCard>
2727
-
2728
- {/* MobileFooter */}
2729
- <ComponentCard
2730
- name="MobileFooter"
2731
- importLine="import { MobileFooter } from '@sovereignfs/ui';"
2732
- 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."
2733
- >
2734
- <div
2735
- style={{
2736
- width: '100%',
2737
- background: 'var(--sv-color-surface)',
2738
- border: '1px solid var(--sv-color-border)',
2739
- borderRadius: 'var(--sv-radius-md)',
2740
- padding: '0 var(--sv-space-3)',
2741
- }}
2742
- >
2743
- <MobileFooter
2744
- onOpenApps={() => {}}
2745
- leftIcons={[{ icon: <Icon name="house" size="md" aria-hidden />, label: 'Home' }]}
2746
- rightIcons={[
2747
- { icon: <Icon name="search" size="md" aria-hidden />, label: 'Search' },
2748
- ]}
2749
- />
2750
- </div>
2751
- </ComponentCard>
2752
-
2753
- {/* MobileAppsDrawer */}
2754
- <ComponentCard
2755
- name="MobileAppsDrawer"
2756
- importLine="import { MobileAppsDrawer } from '@sovereignfs/ui';"
2757
- 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."
2758
- >
2759
- <MobileAppsDrawerDemo />
2760
- </ComponentCard>
2761
-
2762
- {/* EmptyState */}
2763
- <ComponentCard
2764
- name="EmptyState"
2765
- importLine="import { EmptyState } from '@sovereignfs/ui';"
2766
- usage="Zero-data placeholder. Icon slot, heading, description, and optional CTA. Use whenever a list or table has no rows."
2767
- >
2768
- <EmptyState
2769
- icon="search"
2770
- heading="No results found"
2771
- description="Try adjusting your search."
2772
- action={
2773
- <Button variant="secondary" size="sm">
2774
- Clear filters
2775
- </Button>
2776
- }
2777
- />
2778
- </ComponentCard>
2779
-
2780
- {/* OfflineGate */}
2781
- <ComponentCard
2782
- name="OfflineGate"
2783
- importLine="import { OfflineGate } from '@sovereignfs/ui';"
2784
- 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."
2785
- >
2786
- <OfflineGate surfaceName="Console">
2787
- <p style={{ margin: 0, fontFamily: 'var(--sv-font-family)', fontSize: 14 }}>
2788
- Renders normally while online.
2789
- </p>
2790
- </OfflineGate>
2791
- </ComponentCard>
2792
-
2793
- {/* DeviceOnlyGate */}
2794
- <ComponentCard
2795
- name="DeviceOnlyGate"
2796
- importLine="import { DeviceOnlyGate } from '@sovereignfs/ui';"
2797
- 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."
2798
- >
2799
- <DeviceOnlyGate available={false} surfaceName="Wallet">
2800
- <p style={{ margin: 0, fontFamily: 'var(--sv-font-family)', fontSize: 14 }}>
2801
- Renders normally when available.
2802
- </p>
2803
- </DeviceOnlyGate>
2804
- </ComponentCard>
2805
-
2806
- {/* DeviceStorageKeyGate */}
2807
- <ComponentCard
2808
- name="DeviceStorageKeyGate"
2809
- importLine="import { DeviceStorageKeyGate } from '@sovereignfs/ui';"
2810
- 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."
2811
- >
2812
- <DeviceStorageKeyGate
2813
- status="not-set-up"
2814
- surfaceName="Notes"
2815
- setupAction={
2816
- <a href="/account/security" style={{ color: 'var(--sv-color-accent)' }}>
2817
- Go to Account → Security
2818
- </a>
2819
- }
2820
- >
2821
- <p style={{ margin: 0, fontFamily: 'var(--sv-font-family)', fontSize: 14 }}>
2822
- Renders normally once set up.
2823
- </p>
2824
- </DeviceStorageKeyGate>
2825
- </ComponentCard>
2826
-
2827
- {/* Spinner */}
2828
- <ComponentCard
2829
- name="Spinner"
2830
- importLine="import { Spinner } from '@sovereignfs/ui';"
2831
- 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."
2832
- >
2833
- <div style={{ display: 'flex', alignItems: 'center', gap: 16 }}>
2834
- <Spinner size="sm" />
2835
- <Spinner size="md" />
2836
- <Spinner size="lg" />
2837
- </div>
2838
- </ComponentCard>
2839
-
2840
- {/* Avatar */}
2841
- <ComponentCard
2842
- name="Avatar"
2843
- importLine="import { Avatar } from '@sovereignfs/ui';"
2844
- usage="User representation. Shows image when src loads; falls back to initials derived from name. Three sizes. Always sets alt text for accessibility."
2845
- >
2846
- <div style={{ display: 'flex', alignItems: 'center', gap: 12 }}>
2847
- <Avatar name="Jane Smith" size="sm" />
2848
- <Avatar name="Jane Smith" size="md" />
2849
- <Avatar name="Jane Smith" size="lg" />
2850
- <Avatar name="Admin" src="https://i.pravatar.cc/150?u=sb" size="lg" />
2851
- </div>
2852
- </ComponentCard>
2853
-
2854
- {/* NavTabs */}
2855
- <ComponentCard
2856
- name="NavTabs"
2857
- importLine="import { NavTabs } from '@sovereignfs/ui';"
2858
- usage="Underline-style navigation tabs for plugin-level page routing. Distinct from the contained Tabs component. Scrolls horizontally on mobile with no visible scrollbar."
2859
- >
2860
- <div style={{ width: '100%' }}>
2861
- <NavTabs
2862
- items={[
2863
- { label: 'Profile', href: '#', active: true },
2864
- { label: 'Security', href: '#' },
2865
- { label: 'Preferences', href: '#' },
2866
- { label: 'Data', href: '#' },
2867
- ]}
2868
- />
2869
- </div>
2870
- </ComponentCard>
2871
-
2872
- {/* NavList */}
2873
- <ComponentCard
2874
- name="NavList"
2875
- importLine="import { NavList } from '@sovereignfs/ui';"
2876
- usage='Vertical list of icon+label rows, optionally grouped under section headers. static: active row highlighted, no chevron — a persistent sidebar. drilldown: trailing chevron on every row, no active state — a native-Settings-app style index. density="compact" (static only) shrinks row height for a short, always-visible list. Framework-agnostic like NavTabs; supply active/renderLink from the consumer.'
2877
- >
2878
- <div style={{ width: '100%', maxWidth: 220 }}>
2879
- <NavList
2880
- variant="static"
2881
- aria-label="Example sections"
2882
- groups={[
2883
- {
2884
- id: 'overview',
2885
- items: [
2886
- {
2887
- id: 'overview',
2888
- label: 'Overview',
2889
- href: '#',
2890
- icon: 'layout-dashboard',
2891
- active: true,
2892
- },
2893
- ],
2894
- },
2895
- {
2896
- id: 'people',
2897
- label: 'People',
2898
- items: [
2899
- { id: 'users', label: 'Users', href: '#', icon: 'users' },
2900
- { id: 'groups', label: 'Groups', href: '#', icon: 'layers' },
2901
- ],
2902
- },
2903
- ]}
2904
- />
2905
- </div>
2906
- </ComponentCard>
2907
-
2908
- {/* Tooltip */}
2909
- <ComponentCard
2910
- name="Tooltip"
2911
- importLine="import { Tooltip } from '@sovereignfs/ui';"
2912
- usage="CSS-only hover/focus hint. Four placements. Wired to its trigger via aria-describedby. No JS positioning — RSC-safe."
2913
- >
2914
- <div style={{ display: 'flex', gap: 16, alignItems: 'center' }}>
2915
- <Tooltip content="Saved to your account" side="top">
2916
- <Button variant="secondary" size="sm">
2917
- Save
2918
- </Button>
2919
- </Tooltip>
2920
- <Tooltip content="Cannot be undone" side="right">
2921
- <Button variant="destructive" size="sm">
2922
- Delete
2923
- </Button>
2924
- </Tooltip>
2925
- </div>
2926
- </ComponentCard>
2927
-
2928
- {/* CheckableListRow */}
2929
- <ComponentCard
2930
- name="CheckableListRow"
2931
- importLine="import { CheckableListRow } from '@sovereignfs/ui';"
2932
- 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)."
2933
- >
2934
- <CheckableListRowDemo />
2935
- </ComponentCard>
2936
-
2937
- {/* MemberMultiSelect */}
2938
- <ComponentCard
2939
- name="MemberMultiSelect"
2940
- importLine="import { MemberMultiSelect } from '@sovereignfs/ui';"
2941
- 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."
2942
- >
2943
- <MemberMultiSelectDemo />
2944
- </ComponentCard>
2945
-
2946
- {/* Checkbox */}
2947
- <ComponentCard
2948
- name="Checkbox"
2949
- importLine="import { Checkbox } from '@sovereignfs/ui';"
2950
- 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."
2951
- >
2952
- {(() => {
2953
- const [a, setA] = useState(false);
2954
- const [b, setB] = useState(true);
2955
- return (
2956
- <div style={{ display: 'flex', flexDirection: 'column', gap: 8 }}>
2957
- <Checkbox checked={a} onChange={setA} label="Unchecked task" strikeThrough />
2958
- <Checkbox checked={b} onChange={setB} label="Completed task" strikeThrough />
2959
- </div>
2960
- );
2961
- })()}
2962
- </ComponentCard>
2963
-
2964
- {/* RadioGroup */}
2965
- <ComponentCard
2966
- name="RadioGroup"
2967
- importLine="import { RadioGroup } from '@sovereignfs/ui';"
2968
- 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."
2969
- >
2970
- {(() => {
2971
- const [size, setSize] = useState('md');
2972
- return (
2973
- <RadioGroup
2974
- items={[
2975
- { label: 'Small', value: 'sm' },
2976
- { label: 'Medium', value: 'md' },
2977
- { label: 'Large', value: 'lg' },
2978
- ]}
2979
- value={size}
2980
- onChange={setSize}
2981
- aria-label="Size"
2982
- />
2983
- );
2984
- })()}
2985
- </ComponentCard>
2986
-
2987
- {/* Slider */}
2988
- <ComponentCard
2989
- name="Slider"
2990
- importLine="import { Slider } from '@sovereignfs/ui';"
2991
- 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."
2992
- >
2993
- {(() => {
2994
- const [value, setValue] = useState(50);
2995
- return (
2996
- <div style={{ width: 240 }}>
2997
- <Slider value={value} onChange={setValue} min={0} max={100} label="Volume" />
2998
- </div>
2999
- );
3000
- })()}
3001
- </ComponentCard>
3002
-
3003
- {/* Progress */}
3004
- <ComponentCard
3005
- name="Progress"
3006
- importLine="import { Progress } from '@sovereignfs/ui';"
3007
- usage="Determinate progress bar. role=progressbar with aria-valuenow/min/max so screen readers announce the current value."
3008
- >
3009
- <div style={{ width: 240 }}>
3010
- <Progress value={65} label="Upload progress" />
3011
- </div>
3012
- </ComponentCard>
3013
-
3014
- {/* Table */}
3015
- <ComponentCard
3016
- name="Table"
3017
- importLine="import { Table, TableHead, TableBody, TableRow, TableHeaderCell, TableCell } from '@sovereignfs/ui';"
3018
- 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."
3019
- >
3020
- <Table>
3021
- <TableHead>
3022
- <TableRow>
3023
- <TableHeaderCell>Plugin</TableHeaderCell>
3024
- <TableHeaderCell>Status</TableHeaderCell>
3025
- </TableRow>
3026
- </TableHead>
3027
- <TableBody>
3028
- <TableRow>
3029
- <TableCell>sovereign-tasks</TableCell>
3030
- <TableCell>Active</TableCell>
3031
- </TableRow>
3032
- <TableRow>
3033
- <TableCell>sovereign-shopper</TableCell>
3034
- <TableCell>Disabled</TableCell>
3035
- </TableRow>
3036
- </TableBody>
3037
- </Table>
3038
- </ComponentCard>
3039
-
3040
- {/* Alert */}
3041
- <ComponentCard
3042
- name="Alert"
3043
- importLine="import { Alert } from '@sovereignfs/ui';"
3044
- 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."
3045
- >
3046
- <div style={{ display: 'flex', flexDirection: 'column', gap: 8 }}>
3047
- <Alert variant="error" heading="Something went wrong">
3048
- We couldn’t save your changes. Check your connection and try again.
3049
- </Alert>
3050
- <Alert variant="success">Invite sent.</Alert>
3051
- <Alert variant="neutral">This project has no members yet.</Alert>
3052
- </div>
3053
- </ComponentCard>
3054
-
3055
- {/* Breadcrumb */}
3056
- <ComponentCard
3057
- name="Breadcrumb"
3058
- importLine="import { Breadcrumb } from '@sovereignfs/ui';"
3059
- 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."
3060
- >
3061
- <Breadcrumb
3062
- items={[
3063
- { label: 'Console', href: '/console' },
3064
- { label: 'Plugins', href: '/console/plugins' },
3065
- { label: 'sovereign-tasks' },
3066
- ]}
3067
- />
3068
- </ComponentCard>
3069
-
3070
- {/* LegalLinks */}
3071
- <ComponentCard
3072
- name="LegalLinks"
3073
- importLine="import { LegalLinks } from '@sovereignfs/ui';"
3074
- 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."
3075
- >
3076
- <LegalLinks />
3077
- </ComponentCard>
3078
-
3079
- {/* ConsentPrompt */}
3080
- <ComponentCard
3081
- name="ConsentPrompt"
3082
- importLine="import { ConsentPrompt } from '@sovereignfs/ui';"
3083
- usage="Consent screen shown before one plugin is allowed to read another plugin's data (RFC 0002 data grants) or connect to an external service on the user's behalf. description should come from the provider's own manifest declaration, never caller-supplied copy."
3084
- >
3085
- <ConsentPrompt
3086
- consumerName="Ledger"
3087
- providerName="Finance Tracker"
3088
- contract="expenses"
3089
- description="Monthly expense totals by category, no transaction-level detail."
3090
- onAllow={() => {}}
3091
- onDeny={() => {}}
3092
- />
3093
- </ComponentCard>
3094
-
3095
- {/* Markdown */}
3096
- <ComponentCard
3097
- name="Markdown"
3098
- importLine="import { Markdown } from '@sovereignfs/ui';"
3099
- usage="Renders a constrained markdown subset (headings, paragraphs, bold/italic/code, links, ordered and unordered lists, fenced code blocks, blockquotes) as Typography-based React elements — no HTML string injection. Built for first-party long-form content and model-generated replies; still not a full CommonMark parser (no tables, images, or nested blocks)."
3100
- >
3101
- <Markdown
3102
- content={
3103
- '## Heading\n\nA paragraph with **bold** and a [link](/privacy).\n\n- One\n- Two\n\n1. First\n2. Second\n\n```ts\nconst a = 1;\n```'
3104
- }
3105
- />
3106
- </ComponentCard>
3107
-
3108
- {/* Pagination */}
3109
- <ComponentCard
3110
- name="Pagination"
3111
- importLine="import { Pagination } from '@sovereignfs/ui';"
3112
- 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."
3113
- >
3114
- {(() => {
3115
- const [page, setPage] = useState(4);
3116
- return <Pagination page={page} totalPages={12} onChange={setPage} />;
3117
- })()}
3118
- </ComponentCard>
3119
-
3120
- {/* Kbd */}
3121
- <ComponentCard
3122
- name="Kbd"
3123
- importLine="import { Kbd } from '@sovereignfs/ui';"
3124
- usage="Inline keyboard-key styling. Renders a real <kbd> element."
3125
- >
3126
- <span style={{ display: 'inline-flex', alignItems: 'center', gap: 4 }}>
3127
- <Kbd>Ctrl</Kbd>
3128
- <span>+</span>
3129
- <Kbd>K</Kbd>
3130
- </span>
3131
- </ComponentCard>
3132
-
3133
- {/* Collapsible */}
3134
- <ComponentCard
3135
- name="Collapsible"
3136
- importLine="import { Collapsible } from '@sovereignfs/ui';"
3137
- usage="Single expand/collapse primitive. Independently useful (e.g. a 'show more' toggle), and composed internally by Accordion."
3138
- >
3139
- {(() => {
3140
- const [open, setOpen] = useState(false);
3141
- return (
3142
- <Collapsible open={open} onOpenChange={setOpen} trigger="Advanced settings">
3143
- These settings are rarely needed — change them only if you know what you're
3144
- doing.
3145
- </Collapsible>
3146
- );
3147
- })()}
3148
- </ComponentCard>
3149
-
3150
- {/* Accordion */}
3151
- <ComponentCard
3152
- name="Accordion"
3153
- importLine="import { Accordion } from '@sovereignfs/ui';"
3154
- usage="One or more Collapsible sections. type=single closes other sections when one opens; type=multiple allows any number open at once."
3155
- >
3156
- {(() => {
3157
- const [openIds, setOpenIds] = useState<string[]>(['plugins']);
3158
- return (
3159
- <Accordion
3160
- items={[
3161
- {
3162
- id: 'plugins',
3163
- trigger: 'What plugins ship by default?',
3164
- content:
3165
- 'Only the platform plugins (Console, Launcher, Account) plus Sovereign Tasks.',
3166
- },
3167
- {
3168
- id: 'hosting',
3169
- trigger: 'Can I self-host this?',
3170
- content:
3171
- 'Yes — Sovereign is designed to be self-hosted via Docker Compose.',
3172
- },
3173
- ]}
3174
- type="single"
3175
- openIds={openIds}
3176
- onOpenIdsChange={setOpenIds}
3177
- />
3178
- );
3179
- })()}
3180
- </ComponentCard>
3181
-
3182
- {/* AspectRatio */}
3183
- <ComponentCard
3184
- name="AspectRatio"
3185
- importLine="import { AspectRatio } from '@sovereignfs/ui';"
3186
- usage="Constrains content (image, video, embed) to a fixed ratio."
3187
- >
3188
- <div style={{ width: 200 }}>
3189
- <AspectRatio ratio={16 / 9}>
3190
- <div
3191
- style={{
3192
- width: '100%',
3193
- height: '100%',
3194
- background: 'var(--sv-color-surface-sunken)',
3195
- display: 'flex',
3196
- alignItems: 'center',
3197
- justifyContent: 'center',
3198
- color: 'var(--sv-color-text-muted)',
3199
- fontSize: 'var(--sv-font-size-xs)',
3200
- }}
3201
- >
3202
- 16:9
3203
- </div>
3204
- </AspectRatio>
3205
- </div>
3206
- </ComponentCard>
3207
-
3208
- {/* ButtonGroup */}
3209
- <ComponentCard
3210
- name="ButtonGroup"
3211
- importLine="import { ButtonGroup } from '@sovereignfs/ui';"
3212
- usage="Visually joins adjacent Buttons into one control (shared border, connected corners)."
3213
- >
3214
- <ButtonGroup aria-label="View">
3215
- <Button variant="secondary" size="sm">
3216
- Day
3217
- </Button>
3218
- <Button variant="secondary" size="sm">
3219
- Week
3220
- </Button>
3221
- <Button variant="secondary" size="sm">
3222
- Month
3223
- </Button>
3224
- </ButtonGroup>
3225
- </ComponentCard>
3226
-
3227
- {/* Item */}
3228
- <ComponentCard
3229
- name="Item"
3230
- importLine="import { Item } from '@sovereignfs/ui';"
3231
- 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."
3232
- >
3233
- <Item
3234
- title="Notifications"
3235
- description="Manage email and push alerts"
3236
- leading={<Icon name="bell" aria-hidden />}
3237
- trailing={<Icon name="chevron-right" size="sm" aria-hidden />}
3238
- onClick={() => {}}
3239
- />
3240
- </ComponentCard>
3241
-
3242
- {/* Label */}
3243
- <ComponentCard
3244
- name="Label"
3245
- importLine="import { Label } from '@sovereignfs/ui';"
3246
- usage="Standalone accessible form label, independent of FormField. Use FormField when a control needs hint/error text too."
3247
- >
3248
- <div style={{ display: 'flex', flexDirection: 'column', gap: 4 }}>
3249
- <Label htmlFor="label-demo-input">Email address</Label>
3250
- <input
3251
- id="label-demo-input"
3252
- type="email"
3253
- placeholder="you@example.com"
3254
- style={{
3255
- padding: 'var(--sv-space-2) var(--sv-space-3)',
3256
- border: '1px solid var(--sv-color-border-strong)',
3257
- borderRadius: 'var(--sv-radius-md)',
3258
- fontFamily: 'var(--sv-font-family)',
3259
- fontSize: 'var(--sv-font-size-sm)',
3260
- }}
3261
- />
3262
- </div>
3263
- </ComponentCard>
3264
-
3265
- {/* ScrollArea */}
3266
- <ComponentCard
3267
- name="ScrollArea"
3268
- importLine="import { ScrollArea } from '@sovereignfs/ui';"
3269
- usage="Styled scrollable container — thin, token-colored scrollbar instead of the OS default. Native overflow scrolling underneath."
3270
- >
3271
- <ScrollArea maxHeight={120}>
3272
- <div style={{ display: 'flex', flexDirection: 'column', gap: 8, padding: 4 }}>
3273
- {Array.from({ length: 10 }, (_, i) => (
3274
- <div key={i} style={{ fontSize: 13 }}>
3275
- Row {i + 1}
3276
- </div>
3277
- ))}
3278
- </div>
3279
- </ScrollArea>
3280
- </ComponentCard>
3281
-
3282
- {/* Typography */}
3283
- <ComponentCard
3284
- name="Typography"
3285
- importLine="import { Typography } from '@sovereignfs/ui';"
3286
- 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."
3287
- >
3288
- <div style={{ display: 'flex', flexDirection: 'column', gap: 8 }}>
3289
- <Typography variant="h3">Heading 3</Typography>
3290
- <Typography variant="body">Body copy for descriptions.</Typography>
3291
- <Typography variant="caption">Caption — secondary text.</Typography>
3292
- </div>
3293
- </ComponentCard>
3294
-
3295
- {/* Marker */}
3296
- <ComponentCard
3297
- name="Marker"
3298
- importLine="import { Marker } from '@sovereignfs/ui';"
3299
- usage="Inline citation/reference marker — attributes part of an assistant answer to a source (Sovereign Harness 'source trace', RFC 0040)."
3300
- >
3301
- <p style={{ fontSize: 14, margin: 0 }}>
3302
- Your next task is due Thursday
3303
- <Marker index={1} label="Source: Tasks" onClick={() => {}} />.
3304
- </p>
3305
- </ComponentCard>
3306
-
3307
- {/* Message */}
3308
- <ComponentCard
3309
- name="Message"
3310
- importLine="import { Message } from '@sovereignfs/ui';"
3311
- 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."
3312
- >
3313
- <div style={{ display: 'flex', flexDirection: 'column', gap: 8 }}>
3314
- <Message sender="user">What's on my plate this week?</Message>
3315
- <Message sender="assistant">You have 3 tasks due this week.</Message>
3316
- </div>
3317
- </ComponentCard>
3318
-
3319
- {/* MessageScroller */}
3320
- <ComponentCard
3321
- name="MessageScroller"
3322
- importLine="import { MessageScroller } from '@sovereignfs/ui';"
3323
- 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."
3324
- >
3325
- <div style={{ height: 160, border: '1px solid var(--sv-color-border)' }}>
3326
- <MessageScroller>
3327
- <Message sender="user">Hi there</Message>
3328
- <Message sender="assistant">How can I help?</Message>
3329
- </MessageScroller>
3330
- </div>
3331
- </ComponentCard>
3332
-
3333
- {/* HoverCard */}
3334
- <ComponentCard
3335
- name="HoverCard"
3336
- importLine="import { HoverCard } from '@sovereignfs/ui';"
3337
- 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."
3338
- >
3339
- <HoverCard
3340
- aria-label="User preview"
3341
- trigger={
3342
- <button
3343
- type="button"
3344
- style={{ background: 'none', border: 'none', padding: 0, cursor: 'pointer' }}
3345
- >
3346
- <Avatar name="Jane Smith" size="sm" />
3347
- </button>
3348
- }
3349
- >
3350
- <div style={{ padding: 'var(--sv-space-3)', fontSize: 14 }}>
3351
- <div style={{ fontWeight: 600 }}>Jane Smith</div>
3352
- <div style={{ color: 'var(--sv-color-text-muted)' }}>jane@example.com</div>
3353
- </div>
3354
- </HoverCard>
3355
- </ComponentCard>
3356
-
3357
- {/* ContextMenu */}
3358
- <ComponentCard
3359
- name="ContextMenu"
3360
- importLine="import { ContextMenu } from '@sovereignfs/ui';"
3361
- 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."
3362
- >
3363
- <ContextMenu
3364
- aria-label="Row actions"
3365
- items={[
3366
- { label: 'Rename', onSelect: () => {} },
3367
- { label: 'Duplicate', onSelect: () => {} },
3368
- { type: 'separator' },
3369
- { label: 'Delete', destructive: true, onSelect: () => {} },
3370
- ]}
3371
- >
3372
- <div
3373
- style={{
3374
- padding: 'var(--sv-space-4)',
3375
- border: '1px dashed var(--sv-color-border-strong)',
3376
- borderRadius: 'var(--sv-radius-md)',
3377
- textAlign: 'center',
3378
- fontSize: 14,
3379
- color: 'var(--sv-color-text-muted)',
3380
- }}
3381
- >
3382
- Right-click here
3383
- </div>
3384
- </ContextMenu>
3385
- </ComponentCard>
3386
-
3387
- {/* NavigationMenu */}
3388
- <ComponentCard
3389
- name="NavigationMenu"
3390
- importLine="import { NavigationMenu } from '@sovereignfs/ui';"
3391
- usage="Top-level nav bar where some items open a flyout panel. Desktop-oriented — a hover-triggered flyout bar has no mobile equivalent."
3392
- >
3393
- <NavigationMenu
3394
- aria-label="Main"
3395
- items={[
3396
- { label: 'Home', href: '/home' },
3397
- {
3398
- label: 'Products',
3399
- content: (
3400
- <div style={{ padding: 'var(--sv-space-3)', fontSize: 14 }}>
3401
- Tasks · Ledger · Shopper
3402
- </div>
3403
- ),
3404
- },
3405
- ]}
3406
- />
3407
- </ComponentCard>
3408
-
3409
- {/* Menubar */}
3410
- <ComponentCard
3411
- name="Menubar"
3412
- importLine="import { Menubar } from '@sovereignfs/ui';"
3413
- usage="Desktop app-style menu bar (File/Edit/View...). A thin composition of NavigationMenu with MenuEntries as each item's flyout content."
3414
- >
3415
- <Menubar
3416
- menus={[
3417
- {
3418
- label: 'File',
3419
- items: [
3420
- { label: 'New conversation', onSelect: () => {} },
3421
- { label: 'Export', onSelect: () => {} },
3422
- ],
3423
- },
3424
- {
3425
- label: 'Edit',
3426
- items: [
3427
- { label: 'Undo', onSelect: () => {} },
3428
- { label: 'Redo', onSelect: () => {} },
3429
- ],
3430
- },
3431
- ]}
3432
- />
3433
- </ComponentCard>
3434
-
3435
- {/* Command */}
3436
- <ComponentCard
3437
- name="Command"
3438
- importLine="import { Command } from '@sovereignfs/ui';"
3439
- 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."
3440
- >
3441
- <CommandDemo />
3442
- </ComponentCard>
3443
-
3444
- {/* Resizable */}
3445
- <ComponentCard
3446
- name="Resizable"
3447
- importLine="import { ResizablePanelGroup, ResizablePanel, ResizableHandle } from '@sovereignfs/ui';"
3448
- 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."
3449
- >
3450
- <div style={{ width: '100%', height: 160 }}>
3451
- <ResizablePanelGroup direction="horizontal">
3452
- <ResizablePanel defaultSize={30} minSize={15} maxSize={50}>
3453
- <div
3454
- style={{
3455
- display: 'flex',
3456
- alignItems: 'center',
3457
- justifyContent: 'center',
3458
- height: '100%',
3459
- fontSize: 14,
3460
- color: 'var(--sv-color-text-muted)',
3461
- }}
3462
- >
3463
- Sidebar
3464
- </div>
3465
- </ResizablePanel>
3466
- <ResizableHandle aria-label="Resize sidebar" />
3467
- <ResizablePanel defaultSize={70}>
3468
- <div
3469
- style={{
3470
- display: 'flex',
3471
- alignItems: 'center',
3472
- justifyContent: 'center',
3473
- height: '100%',
3474
- fontSize: 14,
3475
- color: 'var(--sv-color-text-muted)',
3476
- }}
3477
- >
3478
- Content
3479
- </div>
3480
- </ResizablePanel>
3481
- </ResizablePanelGroup>
3482
- </div>
3483
- </ComponentCard>
3484
-
3485
- {/* DataTable */}
3486
- <ComponentCard
3487
- name="DataTable"
3488
- importLine="import { DataTable } from '@sovereignfs/ui';"
3489
- 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."
3490
- >
3491
- <DataTable
3492
- columns={[
3493
- { key: 'name', header: 'Plugin', sortable: true },
3494
- { key: 'installs', header: 'Installs', sortable: true, align: 'end' },
3495
- ]}
3496
- data={[
3497
- { id: 'tasks', name: 'sovereign-tasks', installs: 482 },
3498
- { id: 'ledger', name: 'sovereign-ledger', installs: 219 },
3499
- { id: 'shopper', name: 'sovereign-shopper', installs: 37 },
3500
- ]}
3501
- getRowKey={(row) => row.id}
3502
- />
3503
- </ComponentCard>
3504
-
3505
- {/* Combobox */}
3506
- <ComponentCard
3507
- name="Combobox"
3508
- importLine="import { Combobox } from '@sovereignfs/ui';"
3509
- 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."
3510
- >
3511
- <ComboboxDemo />
3512
- </ComponentCard>
3513
-
3514
- {/* DragHandleRow */}
3515
- <ComponentCard
3516
- name="DragHandleRow"
3517
- importLine="import { DragHandleRow } from '@sovereignfs/ui';"
3518
- usage="Row wrapper with a drag handle that appears on hover. Attach dnd-kit sortable props via handleProps."
3519
- >
3520
- <div style={{ width: '100%' }}>
3521
- {['First item', 'Second item', 'Third item'].map((label) => (
3522
- <DragHandleRow key={label}>
3523
- <div
3524
- style={{
3525
- padding: 'var(--sv-space-2) var(--sv-space-1)',
3526
- fontSize: 'var(--sv-font-size-sm)',
3527
- color: 'var(--sv-color-text-primary)',
3528
- borderBottom: '1px solid var(--sv-color-border)',
3529
- }}
3530
- >
3531
- {label}
3532
- </div>
3533
- </DragHandleRow>
3534
- ))}
3535
- </div>
3536
- </ComponentCard>
3537
-
3538
- {/* GripIcon */}
3539
- <ComponentCard
3540
- name="GripIcon"
3541
- importLine="import { GripIcon } from '@sovereignfs/ui';"
3542
- usage="Six-dot drag-handle icon, sized via `size` (default 14). No baked-in pointer-events or color — DragHandleRow uses it internally; a floating, absolutely-positioned handle can use it directly."
3543
- >
3544
- <div style={{ color: 'var(--sv-color-text-muted)' }}>
3545
- <GripIcon />
3546
- </div>
3547
- </ComponentCard>
3548
-
3549
- {/* StepStrip */}
3550
- <ComponentCard
3551
- name="StepStrip"
3552
- importLine="import { StepStrip } from '@sovereignfs/ui';"
3553
- usage="Horizontal connected-chip strip (a route/itinerary sequence) with an optional trailing add chip. Purely presentational — renderItem owns each chip's content, click handling, and any drag-reorder wiring (attach dnd-kit's useSortable the same way DragHandleRow's handleProps expects it)."
3554
- >
3555
- <StepStripDemo />
3556
- </ComponentCard>
3557
-
3558
- {/* FileDropzone */}
3559
- <ComponentCard
3560
- name="FileDropzone"
3561
- importLine="import { FileDropzone } from '@sovereignfs/ui';"
3562
- 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."
3563
- >
3564
- <FileDropzoneDemo />
3565
- </ComponentCard>
3566
-
3567
- {/* ResponsiveSurface */}
3568
- <ComponentCard
3569
- name="ResponsiveSurface"
3570
- importLine="import { ResponsiveSurface } from '@sovereignfs/ui';"
3571
- 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."
3572
- >
3573
- <ResponsiveSurfaceDemo />
3574
- </ComponentCard>
3575
-
3576
- {/* SwipableMobileCarousel */}
3577
- <ComponentCard
3578
- name="SwipableMobileCarousel"
3579
- importLine="import { SwipableMobileCarousel, SwipableMobileCarouselSlide, SwipableMobileCarouselSlideHeader, SwipableMobileCarouselSlideBody } from '@sovereignfs/ui';"
3580
- 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."
3581
- >
3582
- <SwipableMobileCarouselDemo />
3583
- </ComponentCard>
3584
-
3585
- {/* SwipableMobileCarouselDots */}
3586
- <ComponentCard
3587
- name="SwipableMobileCarouselDots"
3588
- importLine="import { SwipableMobileCarouselDots } from '@sovereignfs/ui';"
3589
- usage="A real, tappable, labeled slide indicator (role=tablist/tab) — standalone and reusable outside SwipableMobileCarousel too, and its default renderIndicator."
3590
- >
3591
- <SwipableMobileCarouselDotsDemo />
3592
- </ComponentCard>
3593
-
3594
- {/* SwipeStack */}
3595
- <ComponentCard
3596
- name="SwipeStack"
3597
- importLine="import { SwipeStack, SwipeStackCard } from '@sovereignfs/ui';"
3598
- usage="Triage one card at a time by dragging left/right/up/down, plus an always-visible fallback button per configured direction wired to the identical commit path. Every direction is independently optional and has no baked-in meaning — the caller supplies label/icon and reacts to onSwipe; once triggered a direction removes the card for good, there is no reversible 'go back'."
3599
- >
3600
- <SwipeStackDemo />
3601
- </ComponentCard>
3602
- </div>
3603
- </section>
3604
-
3605
- {/* ── Theming ──────────────────────────────────────────────────── */}
3606
- <section style={{ marginBottom: 'var(--sv-space-12)' }}>
3607
- <SectionHeader
3608
- title="Tenant theming"
3609
- subtitle="The identity is monochrome by default. Tenants add brand color by overriding --sv-color-accent."
3610
- />
3611
- <p
3612
- style={{
3613
- fontSize: 'var(--sv-font-size-sm)',
3614
- color: 'var(--sv-color-text-muted)',
3615
- marginBottom: 'var(--sv-space-4)',
3616
- }}
3617
- >
3618
- Set via the Console → Instance identity section (or the{' '}
3619
- <code style={{ fontFamily: ffm }}>/api/instance/</code> API). Plugin CSS does not need
3620
- to change — all components read{' '}
3621
- <code style={{ fontFamily: ffm }}>--sv-color-accent</code> from the cascade.
3622
- </p>
3623
- <Code>{`/* Operator CSS override — injected by InstanceProvider */
3624
- :root {
3625
- --sv-color-accent: #5c6bc0; /* brand blue */
3626
- --sv-color-accent-hover: #3949ab;
3627
- --sv-color-focus-ring: #5c6bc0;
3628
- --sv-color-text-on-accent: #ffffff; /* must contrast with accent */
3629
- }`}</Code>
3630
-
3631
- <DemoBox style={{ marginTop: 'var(--sv-space-4)' }}>
3632
- <Button style={{ background: '#5c6bc0', borderColor: '#5c6bc0', color: '#fff' }}>
3633
- Themed button
3634
- </Button>
3635
- <span
3636
- style={{
3637
- display: 'inline-block',
3638
- fontFamily: ff,
3639
- fontSize: 'var(--sv-font-size-caption)',
3640
- fontWeight: 600,
3641
- padding: '2px 8px',
3642
- borderRadius: 'var(--sv-radius-sm)',
3643
- background: '#e8eaf6',
3644
- color: '#3949ab',
3645
- border: '1px solid #c5cae9',
3646
- }}
3647
- >
3648
- Owner
3649
- </span>
3650
- <span
3651
- style={{
3652
- fontFamily: ff,
3653
- fontSize: 'var(--sv-font-size-caption)',
3654
- color: 'var(--sv-color-text-muted)',
3655
- }}
3656
- >
3657
- Components read the accent token — brand applied everywhere.
3658
- </span>
3659
- </DemoBox>
3660
- </section>
3661
-
3662
- {/* ── Rules ────────────────────────────────────────────────────── */}
3663
- <section style={{ marginBottom: 'var(--sv-space-8)' }}>
3664
- <SectionHeader
3665
- title="Design rules"
3666
- subtitle="The short list of things that break dark mode or instance theming if ignored."
3667
- />
3668
- <div
3669
- style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 'var(--sv-space-5)' }}
3670
- >
3671
- <div
3672
- style={{
3673
- background: 'var(--sv-color-success-surface)',
3674
- border: '1px solid var(--sv-color-success-border)',
3675
- borderRadius: 'var(--sv-radius-lg)',
3676
- padding: 'var(--sv-space-4)',
3677
- }}
3678
- >
3679
- <p
3680
- style={{
3681
- fontWeight: 600,
3682
- color: 'var(--sv-color-success-text)',
3683
- marginBottom: 'var(--sv-space-3)',
3684
- fontSize: 'var(--sv-font-size-sm)',
3685
- }}
3686
- >
3687
- ✓ Do
3688
- </p>
3689
- <ul
3690
- style={{
3691
- margin: 0,
3692
- padding: '0 0 0 var(--sv-space-4)',
3693
- fontSize: 'var(--sv-font-size-sm)',
3694
- color: 'var(--sv-color-text-primary)',
3695
- lineHeight: 1.8,
3696
- }}
3697
- >
3698
- <li>
3699
- Use <code style={{ fontFamily: ffm }}>--sv-color-*</code> semantic tokens
3700
- </li>
3701
- <li>
3702
- Use <code style={{ fontFamily: ffm }}>--sv-space-*</code>,{' '}
3703
- <code style={{ fontFamily: ffm }}>--sv-radius-*</code> scale tokens
3704
- </li>
3705
- <li>
3706
- Use <code style={{ fontFamily: ffm }}>--sv-font-family</code> and{' '}
3707
- <code style={{ fontFamily: ffm }}>--sv-font-family-mono</code>
3708
- </li>
3709
- <li>
3710
- Import components from <code style={{ fontFamily: ffm }}>@sovereignfs/ui</code>
3711
- </li>
3712
- <li>
3713
- Use <code style={{ fontFamily: ffm }}>--sv-shadow-*</code> for elevation
3714
- </li>
3715
- <li>
3716
- Use <code style={{ fontFamily: ffm }}>--sv-border-width-hairline</code> instead of
3717
- a literal border width (RFC 0094)
3718
- </li>
3719
- </ul>
3720
- </div>
3721
- <div
3722
- style={{
3723
- background: 'var(--sv-color-error-surface)',
3724
- border: '1px solid var(--sv-color-error-border)',
3725
- borderRadius: 'var(--sv-radius-lg)',
3726
- padding: 'var(--sv-space-4)',
3727
- }}
3728
- >
3729
- <p
3730
- style={{
3731
- fontWeight: 600,
3732
- color: 'var(--sv-color-error-text)',
3733
- marginBottom: 'var(--sv-space-3)',
3734
- fontSize: 'var(--sv-font-size-sm)',
3735
- }}
3736
- >
3737
- ✗ Don't
3738
- </p>
3739
- <ul
3740
- style={{
3741
- margin: 0,
3742
- padding: '0 0 0 var(--sv-space-4)',
3743
- fontSize: 'var(--sv-font-size-sm)',
3744
- color: 'var(--sv-color-text-primary)',
3745
- lineHeight: 1.8,
3746
- }}
3747
- >
3748
- <li>
3749
- Hardcode hex values like <code style={{ fontFamily: ffm }}>#333</code>
3750
- </li>
3751
- <li>
3752
- Use primitive tokens like <code style={{ fontFamily: ffm }}>--sv-grey-900</code>{' '}
3753
- directly
3754
- </li>
3755
- <li>Use Tailwind classes or runtime CSS-in-JS</li>
3756
- <li>
3757
- Import from <code style={{ fontFamily: ffm }}>runtime/src</code> (SDK boundary)
3758
- </li>
3759
- <li>
3760
- Override <code style={{ fontFamily: ffm }}>--sv-color-accent</code> from plugin
3761
- CSS
3762
- </li>
3763
- </ul>
3764
- </div>
3765
- </div>
3766
- </section>
3767
- </div>
3768
- </div>
3769
- );
3770
- }
3771
-
3772
- // ---------------------------------------------------------------------------
3773
- // Story
3774
- // ---------------------------------------------------------------------------
3775
-
3776
- const meta = {
3777
- title: 'Overview',
3778
- component: OverviewPage,
3779
- parameters: {
3780
- layout: 'fullscreen',
3781
- docs: {
3782
- description: {
3783
- component:
3784
- 'Complete reference for plugin developers — components, tokens, theming, and design rules in one page.',
3785
- },
3786
- },
3787
- },
3788
- } satisfies Meta<typeof OverviewPage>;
3789
-
3790
- export default meta;
3791
- type Story = StoryObj<typeof meta>;
3792
-
3793
- export const DesignSystem: Story = {};