@sovereignfs/ui 0.54.0 → 0.83.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (277) hide show
  1. package/dist/Accordion.module.css +1 -1
  2. package/dist/Alert.module.css +1 -1
  3. package/dist/AppsLauncher.module.css +96 -0
  4. package/dist/Avatar.module.css +1 -1
  5. package/dist/Badge.module.css +30 -7
  6. package/dist/BalanceChip.module.css +1 -1
  7. package/dist/Button.module.css +18 -2
  8. package/dist/Card.module.css +1 -1
  9. package/dist/CardTile.module.css +181 -0
  10. package/dist/CodeTextarea.module.css +1 -1
  11. package/dist/Collapsible.module.css +1 -1
  12. package/dist/ColorPicker.module.css +132 -0
  13. package/dist/Combobox.module.css +2 -2
  14. package/dist/Command.module.css +1 -1
  15. package/dist/ConfirmDialog.module.css +2 -2
  16. package/dist/ConsentPrompt.module.css +21 -0
  17. package/dist/DatePicker.module.css +1 -1
  18. package/dist/Dialog.module.css +130 -21
  19. package/dist/Drawer.module.css +36 -7
  20. package/dist/Header.module.css +62 -0
  21. package/dist/HeaderFooterLayout.module.css +21 -0
  22. package/dist/IconPicker.module.css +1 -1
  23. package/dist/Input.module.css +1 -1
  24. package/dist/Kbd.module.css +1 -1
  25. package/dist/Markdown.module.css +24 -0
  26. package/dist/Menu.module.css +1 -0
  27. package/dist/Message.module.css +1 -1
  28. package/dist/MessageScroller.module.css +12 -1
  29. package/dist/MobileAppsDrawer.module.css +10 -9
  30. package/dist/MobileFooter.module.css +1 -1
  31. package/dist/MobileHeader.module.css +14 -1
  32. package/dist/NavList.module.css +130 -0
  33. package/dist/NavTabs.module.css +1 -1
  34. package/dist/NotificationsPanel.module.css +258 -0
  35. package/dist/OverlayHeader.module.css +1 -1
  36. package/dist/PageContainer.module.css +62 -0
  37. package/dist/PageHeader.module.css +54 -0
  38. package/dist/PageLayout.module.css +51 -0
  39. package/dist/Popover.module.css +1 -1
  40. package/dist/QuantityStepper.module.css +1 -1
  41. package/dist/Resizable.module.css +3 -3
  42. package/dist/RootLayout.module.css +12 -0
  43. package/dist/SegmentedControl.module.css +1 -1
  44. package/dist/Select.module.css +1 -1
  45. package/dist/Sheet.module.css +4 -1
  46. package/dist/SplitPane.module.css +4 -4
  47. package/dist/StatusBadge.module.css +1 -1
  48. package/dist/StepStrip.module.css +53 -0
  49. package/dist/SuggestionInput.module.css +2 -2
  50. package/dist/SwipableMobileCarousel.module.css +12 -3
  51. package/dist/SwipableMobileCarouselDots.module.css +15 -0
  52. package/dist/SwipeStack.module.css +101 -0
  53. package/dist/SystemBanner.module.css +10 -0
  54. package/dist/Table.module.css +2 -2
  55. package/dist/Tabs.module.css +1 -1
  56. package/dist/TagInput.module.css +2 -2
  57. package/dist/Textarea.module.css +9 -1
  58. package/dist/ThreeColumnLayout.module.css +35 -0
  59. package/dist/Toast.module.css +62 -3
  60. package/dist/Tooltip.module.css +1 -1
  61. package/dist/UserMenu.module.css +90 -0
  62. package/dist/index.d.ts +1047 -45
  63. package/dist/index.js +3908 -1456
  64. package/dist/tokens/primitives.css +11 -0
  65. package/dist/tokens/semantic.css +32 -0
  66. package/dist/tokens/theme-presets.css +33 -0
  67. package/package.json +25 -9
  68. package/src/__tests__/package-json.test.ts +8 -0
  69. package/src/components/Accordion/Accordion.module.css +1 -1
  70. package/src/components/Alert/Alert.module.css +1 -1
  71. package/src/components/AppsLauncher/AppsLauncher.module.css +96 -0
  72. package/src/components/AppsLauncher/AppsLauncher.stories.tsx +58 -0
  73. package/src/components/AppsLauncher/AppsLauncher.tsx +117 -0
  74. package/src/components/AppsLauncher/__tests__/AppsLauncher.test.tsx +52 -0
  75. package/src/components/Avatar/Avatar.module.css +1 -1
  76. package/src/components/Badge/Badge.module.css +30 -7
  77. package/src/components/Badge/Badge.stories.tsx +16 -4
  78. package/src/components/Badge/Badge.tsx +7 -4
  79. package/src/components/Badge/__tests__/Badge.test.tsx +4 -2
  80. package/src/components/BalanceChip/BalanceChip.module.css +1 -1
  81. package/src/components/Button/Button.module.css +18 -2
  82. package/src/components/Card/Card.module.css +1 -1
  83. package/src/components/CardTile/CardTile.module.css +181 -0
  84. package/src/components/CardTile/CardTile.stories.tsx +86 -0
  85. package/src/components/CardTile/CardTile.tsx +128 -0
  86. package/src/components/CardTile/__tests__/CardTile.test.tsx +56 -0
  87. package/src/components/CodeTextarea/CodeTextarea.module.css +1 -1
  88. package/src/components/Collapsible/Collapsible.module.css +1 -1
  89. package/src/components/ColorPicker/ColorPicker.module.css +132 -0
  90. package/src/components/ColorPicker/ColorPicker.stories.tsx +80 -0
  91. package/src/components/ColorPicker/ColorPicker.tsx +157 -0
  92. package/src/components/ColorPicker/__tests__/ColorPicker.test.tsx +117 -0
  93. package/src/components/Combobox/Combobox.module.css +2 -2
  94. package/src/components/Command/Command.module.css +1 -1
  95. package/src/components/ConfirmDialog/ConfirmDialog.module.css +2 -2
  96. package/src/components/ConfirmDialog/ConfirmDialog.tsx +28 -2
  97. package/src/components/ConfirmDialog/__tests__/ConfirmDialog.test.tsx +71 -3
  98. package/src/components/ConsentPrompt/ConsentPrompt.module.css +21 -0
  99. package/src/components/ConsentPrompt/ConsentPrompt.stories.tsx +31 -0
  100. package/src/components/ConsentPrompt/ConsentPrompt.tsx +66 -0
  101. package/src/components/ContextMenu/ContextMenu.tsx +1 -1
  102. package/src/components/DatePicker/DatePicker.module.css +1 -1
  103. package/src/components/Dialog/Dialog.module.css +130 -21
  104. package/src/components/Dialog/Dialog.stories.tsx +179 -2
  105. package/src/components/Dialog/Dialog.tsx +129 -35
  106. package/src/components/Dialog/__tests__/Dialog.test.tsx +190 -9
  107. package/src/components/DragHandleRow/DragHandleRow.tsx +2 -21
  108. package/src/components/Drawer/Drawer.module.css +36 -7
  109. package/src/components/Drawer/Drawer.tsx +9 -10
  110. package/src/components/Drawer/__tests__/Drawer.test.tsx +33 -6
  111. package/src/components/GripIcon/GripIcon.tsx +27 -0
  112. package/src/components/Header/Header.module.css +62 -0
  113. package/src/components/Header/Header.stories.tsx +452 -0
  114. package/src/components/Header/Header.tsx +102 -0
  115. package/src/components/Header/__tests__/Header.test.tsx +104 -0
  116. package/src/components/HeaderFooterLayout/HeaderFooterLayout.module.css +21 -0
  117. package/src/components/HeaderFooterLayout/HeaderFooterLayout.stories.tsx +111 -0
  118. package/src/components/HeaderFooterLayout/HeaderFooterLayout.tsx +54 -0
  119. package/src/components/HeaderFooterLayout/__tests__/HeaderFooterLayout.test.tsx +58 -0
  120. package/src/components/Icon/icons/arrow-left-right.tsx +23 -0
  121. package/src/components/Icon/icons/bold.tsx +20 -0
  122. package/src/components/Icon/icons/calendar.tsx +4 -4
  123. package/src/components/Icon/icons/carrot.tsx +3 -3
  124. package/src/components/Icon/icons/circle-chevron-left.tsx +21 -0
  125. package/src/components/Icon/icons/code.tsx +21 -0
  126. package/src/components/Icon/icons/copy.tsx +21 -0
  127. package/src/components/Icon/icons/credit-card.tsx +21 -0
  128. package/src/components/Icon/icons/database.tsx +22 -0
  129. package/src/components/Icon/icons/download.tsx +22 -0
  130. package/src/components/Icon/icons/file-text.tsx +24 -0
  131. package/src/components/Icon/icons/folder-closed.tsx +21 -0
  132. package/src/components/Icon/icons/folder-open.tsx +20 -0
  133. package/src/components/Icon/icons/folder.tsx +20 -0
  134. package/src/components/Icon/icons/folders.tsx +21 -0
  135. package/src/components/Icon/icons/hat-glasses.tsx +24 -0
  136. package/src/components/Icon/icons/history.tsx +22 -0
  137. package/src/components/Icon/icons/inbox.tsx +21 -0
  138. package/src/components/Icon/icons/index.ts +161 -1
  139. package/src/components/Icon/icons/italic.tsx +22 -0
  140. package/src/components/Icon/icons/layers.tsx +22 -0
  141. package/src/components/Icon/icons/layout-dashboard.tsx +23 -0
  142. package/src/components/Icon/icons/layout-grid.tsx +23 -0
  143. package/src/components/Icon/icons/link.tsx +21 -0
  144. package/src/components/Icon/icons/list-ordered.tsx +25 -0
  145. package/src/components/Icon/icons/list.tsx +25 -0
  146. package/src/components/Icon/icons/luggage.tsx +24 -0
  147. package/src/components/Icon/icons/map-pin.tsx +21 -0
  148. package/src/components/Icon/icons/minus.tsx +20 -0
  149. package/src/components/Icon/icons/paint-bucket.tsx +23 -0
  150. package/src/components/Icon/icons/panel-left.tsx +21 -0
  151. package/src/components/Icon/icons/pin.tsx +21 -0
  152. package/src/components/Icon/icons/rectangle-ellipsis.tsx +23 -0
  153. package/src/components/Icon/icons/refresh-cw.tsx +23 -0
  154. package/src/components/Icon/icons/rotate-cw.tsx +21 -0
  155. package/src/components/Icon/icons/route.tsx +22 -0
  156. package/src/components/Icon/icons/send.tsx +21 -0
  157. package/src/components/Icon/icons/share-2.tsx +24 -0
  158. package/src/components/Icon/icons/sheet.tsx +24 -0
  159. package/src/components/Icon/icons/square-kanban.tsx +23 -0
  160. package/src/components/Icon/icons/table.tsx +23 -0
  161. package/src/components/Icon/icons/user-round-plus.tsx +23 -0
  162. package/src/components/Icon/icons/users.tsx +23 -0
  163. package/src/components/IconPicker/IconPicker.module.css +1 -1
  164. package/src/components/Input/Input.module.css +1 -1
  165. package/src/components/Input/Input.tsx +8 -2
  166. package/src/components/Kbd/Kbd.module.css +1 -1
  167. package/src/components/Markdown/Markdown.module.css +24 -0
  168. package/src/components/Markdown/Markdown.stories.tsx +10 -0
  169. package/src/components/Markdown/Markdown.tsx +166 -29
  170. package/src/components/Markdown/__tests__/Markdown.test.tsx +144 -0
  171. package/src/components/Menu/Menu.module.css +1 -0
  172. package/src/components/Menu/Menu.tsx +72 -22
  173. package/src/components/Menu/__tests__/Menu.test.tsx +30 -0
  174. package/src/components/Menubar/Menubar.tsx +1 -1
  175. package/src/components/Message/Message.module.css +1 -1
  176. package/src/components/MessageScroller/MessageScroller.module.css +12 -1
  177. package/src/components/MessageScroller/MessageScroller.tsx +12 -0
  178. package/src/components/MobileAppsDrawer/MobileAppsDrawer.module.css +10 -9
  179. package/src/components/MobileFooter/MobileFooter.module.css +1 -1
  180. package/src/components/MobileFooter/MobileFooter.tsx +15 -2
  181. package/src/components/MobileFooter/__tests__/MobileFooter.test.tsx +58 -0
  182. package/src/components/MobileHeader/MobileHeader.module.css +14 -1
  183. package/src/components/MobileHeader/MobileHeader.tsx +15 -2
  184. package/src/components/MobileHeader/__tests__/MobileHeader.test.tsx +56 -1
  185. package/src/components/NavList/NavList.module.css +130 -0
  186. package/src/components/NavList/NavList.tsx +136 -0
  187. package/src/components/NavList/__tests__/NavList.test.tsx +109 -0
  188. package/src/components/NavTabs/NavTabs.module.css +1 -1
  189. package/src/components/NotificationsPanel/NotificationsPanel.module.css +258 -0
  190. package/src/components/NotificationsPanel/NotificationsPanel.stories.tsx +61 -0
  191. package/src/components/NotificationsPanel/NotificationsPanel.tsx +187 -0
  192. package/src/components/NotificationsPanel/__tests__/NotificationsPanel.test.tsx +78 -0
  193. package/src/components/OverlayHeader/OverlayHeader.module.css +1 -1
  194. package/src/components/OverlayHeader/OverlayHeader.tsx +19 -4
  195. package/src/components/OverlayHeader/__tests__/OverlayHeader.test.tsx +18 -0
  196. package/src/components/PageContainer/PageContainer.module.css +62 -0
  197. package/src/components/PageContainer/PageContainer.tsx +37 -4
  198. package/src/components/PageContainer/__tests__/PageContainer.test.tsx +41 -8
  199. package/src/components/PageHeader/PageHeader.module.css +54 -0
  200. package/src/components/PageHeader/PageHeader.tsx +146 -5
  201. package/src/components/PageHeader/__tests__/PageHeader.test.tsx +103 -0
  202. package/src/components/PageLayout/PageLayout.module.css +51 -0
  203. package/src/components/PageLayout/PageLayout.stories.tsx +73 -0
  204. package/src/components/PageLayout/PageLayout.tsx +51 -0
  205. package/src/components/PageLayout/__tests__/PageLayout.test.tsx +34 -0
  206. package/src/components/Popover/Popover.module.css +1 -1
  207. package/src/components/Popover/Popover.tsx +86 -4
  208. package/src/components/Popover/__tests__/Popover.test.tsx +165 -0
  209. package/src/components/QuantityStepper/QuantityStepper.module.css +1 -1
  210. package/src/components/Resizable/Resizable.module.css +3 -3
  211. package/src/components/RootLayout/RootLayout.module.css +12 -0
  212. package/src/components/RootLayout/RootLayout.stories.tsx +125 -0
  213. package/src/components/RootLayout/RootLayout.tsx +185 -0
  214. package/src/components/RootLayout/__tests__/RootLayout.test.tsx +161 -0
  215. package/src/components/SegmentedControl/SegmentedControl.module.css +1 -1
  216. package/src/components/Select/Select.module.css +1 -1
  217. package/src/components/Sheet/Sheet.module.css +4 -1
  218. package/src/components/Sheet/Sheet.tsx +5 -5
  219. package/src/components/SplitPane/SplitPane.module.css +4 -4
  220. package/src/components/StatusBadge/StatusBadge.module.css +1 -1
  221. package/src/components/StepStrip/StepStrip.module.css +53 -0
  222. package/src/components/StepStrip/StepStrip.tsx +74 -0
  223. package/src/components/SuggestionInput/SuggestionInput.module.css +2 -2
  224. package/src/components/SwipableMobileCarousel/SwipableMobileCarousel.module.css +12 -3
  225. package/src/components/SwipableMobileCarousel/SwipableMobileCarousel.tsx +25 -9
  226. package/src/components/SwipableMobileCarousel/__tests__/SwipableMobileCarousel.test.tsx +81 -0
  227. package/src/components/SwipableMobileCarouselDots/SwipableMobileCarouselDots.module.css +15 -0
  228. package/src/components/SwipableMobileCarouselDots/SwipableMobileCarouselDots.tsx +12 -1
  229. package/src/components/SwipeStack/SwipeStack.module.css +101 -0
  230. package/src/components/SwipeStack/SwipeStack.tsx +199 -0
  231. package/src/components/SwipeStack/SwipeStackCard.tsx +22 -0
  232. package/src/components/SwipeStack/__tests__/SwipeStack.test.tsx +169 -0
  233. package/src/components/SystemBanner/SystemBanner.module.css +10 -0
  234. package/src/components/Table/Table.module.css +2 -2
  235. package/src/components/Tabs/Tabs.module.css +1 -1
  236. package/src/components/TagInput/TagInput.module.css +2 -2
  237. package/src/components/Textarea/Textarea.module.css +9 -1
  238. package/src/components/Textarea/Textarea.tsx +28 -5
  239. package/src/components/Textarea/__tests__/Textarea.test.tsx +23 -1
  240. package/src/components/ThreeColumnLayout/ThreeColumnLayout.module.css +35 -0
  241. package/src/components/ThreeColumnLayout/ThreeColumnLayout.stories.tsx +152 -0
  242. package/src/components/ThreeColumnLayout/ThreeColumnLayout.tsx +87 -0
  243. package/src/components/ThreeColumnLayout/__tests__/ThreeColumnLayout.test.tsx +78 -0
  244. package/src/components/Toast/Toast.module.css +62 -3
  245. package/src/components/Toast/Toast.stories.tsx +1 -1
  246. package/src/components/Tooltip/Tooltip.module.css +1 -1
  247. package/src/components/UserMenu/UserMenu.module.css +90 -0
  248. package/src/components/UserMenu/UserMenu.stories.tsx +49 -0
  249. package/src/components/UserMenu/UserMenu.tsx +132 -0
  250. package/src/components/UserMenu/__tests__/UserMenu.test.tsx +77 -0
  251. package/src/hooks/__tests__/usePublishShellChromeHeight.test.tsx +118 -0
  252. package/src/hooks/__tests__/useReorderSensors.test.ts +26 -0
  253. package/src/hooks/__tests__/useSnapCarousel.test.tsx +82 -0
  254. package/src/hooks/__tests__/useSwipeStack.test.tsx +155 -0
  255. package/src/hooks/index.ts +9 -0
  256. package/src/hooks/usePublishShellChromeHeight.ts +96 -0
  257. package/src/hooks/useReorderSensors.ts +88 -0
  258. package/src/hooks/useSnapCarousel.ts +211 -6
  259. package/src/hooks/useSwipeStack.ts +235 -0
  260. package/src/index.ts +61 -1
  261. package/src/overlay-shell.ts +65 -0
  262. package/src/stories/DesignSystemOverview.stories.tsx +617 -11
  263. package/src/stories/GripIcon.stories.tsx +29 -0
  264. package/src/stories/LayoutSystem.stories.tsx +639 -0
  265. package/src/stories/MenuEntries.stories.tsx +86 -0
  266. package/src/stories/NavList.stories.tsx +147 -0
  267. package/src/stories/PageContainer.stories.tsx +52 -7
  268. package/src/stories/PageHeader.stories.tsx +54 -1
  269. package/src/stories/StepStrip.stories.tsx +76 -0
  270. package/src/stories/SwipableMobileCarousel.stories.tsx +58 -0
  271. package/src/stories/SwipeStack.stories.tsx +200 -0
  272. package/src/stories/Textarea.stories.tsx +12 -0
  273. package/src/stories/TokenGallery.stories.tsx +50 -0
  274. package/src/tokens/primitives.css +11 -0
  275. package/src/tokens/semantic.css +32 -0
  276. package/src/tokens/theme-presets.css +33 -0
  277. package/src/tokens/theme-presets.ts +72 -0
@@ -64,6 +64,7 @@
64
64
  sans-serif;
65
65
  --sv-font-family-mono:
66
66
  'JetBrains Mono', ui-monospace, 'SF Mono', Menlo, Consolas, 'Liberation Mono', monospace;
67
+ --sv-font-size-2xs: 0.625rem; /* 10px — smallest badge size (Badge size="xs") */
67
68
  --sv-font-size-label: 0.6875rem; /* 11px — all-caps section labels */
68
69
  --sv-font-size-xs: 0.75rem; /* 12px — mono identifiers */
69
70
  --sv-font-size-caption: 0.8125rem; /* 13px — secondary / supporting copy */
@@ -72,6 +73,10 @@
72
73
  --sv-font-size-lg: 1.125rem; /* 18px */
73
74
  --sv-font-size-xl: 1.25rem; /* 20px */
74
75
  --sv-font-size-2xl: 1.5rem; /* 24px */
76
+ --sv-font-size-3xl: 2rem; /* 32px — document title style (RichTextEditor "Title" paragraph style). A
77
+ modest step above 2xl reads too close to Heading 1 at a glance (both
78
+ bold, only 4px apart) — this keeps roughly the same ~1.3x jump Google
79
+ Docs itself uses between its own Title and Heading 1 sizes. */
75
80
  --sv-font-weight-regular: 400;
76
81
  --sv-font-weight-medium: 500;
77
82
  --sv-font-weight-semibold: 600;
@@ -116,6 +121,12 @@
116
121
  ); /* 20px @ scale 1 — bottom sheet (Drawer top corners) */
117
122
  --sv-radius-full: 9999px; /* pill / avatar — unaffected by --sv-radius-scale */
118
123
 
124
+ /* Border width (RFC 0094). Theme-stable like the other scale tokens above —
125
+ a theme preset can override it (e.g. neobrutalism's thicker outline), but
126
+ it has no separate semantic tier. Every component references this by
127
+ name instead of a literal `1px`, the way --sv-radius-* etc. do. */
128
+ --sv-border-width-hairline: 1px;
129
+
119
130
  /* Touch targets — minimum hit-area dimension for interactive controls.
120
131
  44px follows the WCAG 2.5.5 / Apple HIG / Material guideline for reliable
121
132
  touch without a pointing device. Applied to icon-only controls on mobile
@@ -19,6 +19,17 @@
19
19
  --sv-color-text-muted: var(--sv-grey-500);
20
20
  --sv-color-text-subtle: var(--sv-grey-400);
21
21
  --sv-color-text-on-accent: var(--sv-white);
22
+ /* Guaranteed-contrast text pair for content painted directly on a
23
+ plugin-supplied literal background colour (data, not a design-system
24
+ token — e.g. a kanban board's own canvas colour). A consumer picks
25
+ whichever member actually contrasts with its own literal background;
26
+ neither can be theme-reactive the way --sv-color-text-primary is,
27
+ since the background itself doesn't react to the app's theme either.
28
+ Intentionally NOT overridden under [data-theme='dark'] below — same
29
+ "reads the same regardless of overall page theme" reasoning as
30
+ --sv-color-text-on-error/-success. */
31
+ --sv-color-text-on-light-fill: var(--sv-grey-950);
32
+ --sv-color-text-on-dark-fill: var(--sv-grey-50);
22
33
 
23
34
  --sv-color-border: var(--sv-grey-200);
24
35
  --sv-color-border-strong: var(--sv-grey-300);
@@ -68,6 +79,14 @@
68
79
  elevation shadow. Composed rgba layers, like --sv-shadow-card. */
69
80
  --sv-color-scrim: rgba(0, 0, 0, 0.5);
70
81
 
82
+ /* High-contrast shadow colour (RFC 0094). Distinct from --sv-color-border-strong
83
+ even though they resolve to the same monochrome scale — a theme preset's
84
+ hard offset shadows (e.g. neobrutalism) need a bolder, more saturated value
85
+ than a border does to read as "authentic," so this is its own token rather
86
+ than reusing border-strong. Unused by the default theme's soft rgba()
87
+ shadows above; only a preset that opts in (theme-presets.ts) references it. */
88
+ --sv-color-shadow-strong: var(--sv-grey-950);
89
+
71
90
  --sv-shadow-card: 0 1px 2px rgba(0, 0, 0, 0.06), 0 1px 3px rgba(0, 0, 0, 0.1);
72
91
  --sv-shadow-hover: 0 2px 6px rgba(0, 0, 0, 0.08); /* e1 — card hover lift */
73
92
  --sv-shadow-popover:
@@ -75,6 +94,17 @@
75
94
  --sv-shadow-overlay: 0 10px 38px rgba(0, 0, 0, 0.18), 0 6px 12px rgba(0, 0, 0, 0.12);
76
95
  --sv-shadow-control: 0 1px 3px rgba(0, 0, 0, 0.2); /* small interactive-control shadows, e.g. Toggle thumb */
77
96
 
97
+ /* Button press-interaction tokens (RFC 0094). Consumed unconditionally by
98
+ Button.module.css's :active rule — these defaults (none/0/0) make that
99
+ rule a no-op for today's default look. A theme preset (e.g.
100
+ neobrutalism) sets non-zero values to get a tactile "press into shadow"
101
+ effect. Not overridden under [data-theme='dark'] below — like the
102
+ shadow tokens above, a theme preset's own light/dark variants set these
103
+ directly rather than this file deriving a dark-mode default for them. */
104
+ --sv-button-shadow: none;
105
+ --sv-button-press-x: 0;
106
+ --sv-button-press-y: 0;
107
+
78
108
  /*
79
109
  * Instance identity tokens (RFC 0027 / RFC 0032 rename). Distinct namespace from --sv-color-*:
80
110
  * these hold URLs (not colours) and are set once by the operator, not swapped
@@ -122,6 +152,8 @@
122
152
 
123
153
  --sv-color-scrim: rgba(0, 0, 0, 0.65);
124
154
 
155
+ --sv-color-shadow-strong: var(--sv-grey-50);
156
+
125
157
  --sv-shadow-card: 0 1px 2px rgba(0, 0, 0, 0.4), 0 1px 3px rgba(0, 0, 0, 0.5);
126
158
  --sv-shadow-hover: 0 2px 6px rgba(0, 0, 0, 0.3);
127
159
  --sv-shadow-popover: 0 4px 16px rgba(0, 0, 0, 0.5), 0 2px 6px rgba(0, 0, 0, 0.3);
@@ -0,0 +1,33 @@
1
+ /*
2
+ * Generated by scripts/generate-theme-presets-css.ts — do not edit.
3
+ * Source of truth: packages/ui/src/tokens/theme-presets.ts
4
+ *
5
+ * For Storybook / standalone (non-runtime) use only — set data-theme-preset
6
+ * on the document root element (<html>), never a nested wrapper. See the
7
+ * generator script's doc comment for why.
8
+ */
9
+ [data-theme-preset='neobrutalism'] {
10
+ --sv-radius-scale: 0;
11
+ --sv-border-width-hairline: 2px;
12
+ --sv-shadow-card: 4px 4px 0 0 var(--sv-color-shadow-strong);
13
+ --sv-shadow-hover: 6px 6px 0 0 var(--sv-color-shadow-strong);
14
+ --sv-shadow-popover: 4px 4px 0 0 var(--sv-color-shadow-strong);
15
+ --sv-shadow-overlay: 8px 8px 0 0 var(--sv-color-shadow-strong);
16
+ --sv-shadow-control: 2px 2px 0 0 var(--sv-color-shadow-strong);
17
+ --sv-button-shadow: 4px 4px 0 0 var(--sv-color-shadow-strong);
18
+ --sv-button-press-x: 4px;
19
+ --sv-button-press-y: 4px;
20
+ }
21
+
22
+ [data-theme-preset='neobrutalism'][data-theme='dark'] {
23
+ --sv-radius-scale: 0;
24
+ --sv-border-width-hairline: 2px;
25
+ --sv-shadow-card: 4px 4px 0 0 var(--sv-color-shadow-strong);
26
+ --sv-shadow-hover: 6px 6px 0 0 var(--sv-color-shadow-strong);
27
+ --sv-shadow-popover: 4px 4px 0 0 var(--sv-color-shadow-strong);
28
+ --sv-shadow-overlay: 8px 8px 0 0 var(--sv-color-shadow-strong);
29
+ --sv-shadow-control: 2px 2px 0 0 var(--sv-color-shadow-strong);
30
+ --sv-button-shadow: 4px 4px 0 0 var(--sv-color-shadow-strong);
31
+ --sv-button-press-x: 4px;
32
+ --sv-button-press-y: 4px;
33
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sovereignfs/ui",
3
- "version": "0.54.0",
3
+ "version": "0.83.1",
4
4
  "type": "module",
5
5
  "description": "Sovereign Design System — design tokens and React components.",
6
6
  "keywords": [
@@ -14,6 +14,9 @@
14
14
  "self-hosted"
15
15
  ],
16
16
  "license": "AGPL-3.0-or-later",
17
+ "sideEffects": [
18
+ "**/*.css"
19
+ ],
17
20
  "publishConfig": {
18
21
  "access": "public"
19
22
  },
@@ -24,7 +27,8 @@
24
27
  },
25
28
  "./tokens.css": "./dist/tokens.css",
26
29
  "./tokens/primitives.css": "./dist/tokens/primitives.css",
27
- "./tokens/semantic.css": "./dist/tokens/semantic.css"
30
+ "./tokens/semantic.css": "./dist/tokens/semantic.css",
31
+ "./tokens/theme-presets.css": "./dist/tokens/theme-presets.css"
28
32
  },
29
33
  "files": [
30
34
  "dist",
@@ -32,23 +36,35 @@
32
36
  "NOTICE"
33
37
  ],
34
38
  "peerDependencies": {
39
+ "@dnd-kit/core": "^6.3.1",
40
+ "@dnd-kit/sortable": "^10.0.0",
35
41
  "react": "^19.0.0",
36
42
  "react-dom": "^19.0.0"
37
43
  },
44
+ "peerDependenciesMeta": {
45
+ "@dnd-kit/core": {
46
+ "optional": true
47
+ },
48
+ "@dnd-kit/sortable": {
49
+ "optional": true
50
+ }
51
+ },
38
52
  "devDependencies": {
53
+ "@dnd-kit/core": "^6.3.1",
54
+ "@dnd-kit/sortable": "^10.0.0",
39
55
  "@playwright/test": "^1.62.1",
40
- "@storybook/addon-a11y": "^10.5.5",
41
- "@storybook/addon-themes": "^10.5.5",
42
- "@storybook/react-vite": "^10.5.5",
56
+ "@storybook/addon-a11y": "^10.6.0",
57
+ "@storybook/addon-themes": "^10.6.0",
58
+ "@storybook/react-vite": "^10.6.0",
43
59
  "@testing-library/dom": "^10.4.1",
44
- "@testing-library/react": "^16.3.2",
60
+ "@testing-library/react": "^16.3.3",
45
61
  "@types/react": "^19.2.18",
46
- "@types/react-dom": "^19.2.4",
62
+ "@types/react-dom": "^19.2.5",
47
63
  "react": "^19.2.8",
48
64
  "react-dom": "^19.2.8",
49
- "storybook": "^10.5.5",
65
+ "storybook": "^10.6.0",
50
66
  "tsup": "^8.5.1",
51
- "typescript": "^5.9.3",
67
+ "typescript": "^6.0.3",
52
68
  "@sovereignfs/tsconfig": "0.0.0"
53
69
  },
54
70
  "scripts": {
@@ -0,0 +1,8 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import packageJson from '../../package.json';
3
+
4
+ describe('packages/ui/package.json — sideEffects', () => {
5
+ it('declares sideEffects as exactly ["**/*.css"]', () => {
6
+ expect(packageJson.sideEffects).toEqual(['**/*.css']);
7
+ });
8
+ });
@@ -1,3 +1,3 @@
1
1
  .root {
2
- border-top: 1px solid var(--sv-color-border);
2
+ border-top: var(--sv-border-width-hairline) solid var(--sv-color-border);
3
3
  }
@@ -3,7 +3,7 @@
3
3
  align-items: flex-start;
4
4
  gap: var(--sv-space-2);
5
5
  padding: var(--sv-space-3) var(--sv-space-4);
6
- border: 1px solid transparent;
6
+ border: var(--sv-border-width-hairline) solid transparent;
7
7
  border-radius: var(--sv-radius-md);
8
8
  font-size: var(--sv-font-size-sm);
9
9
  }
@@ -0,0 +1,96 @@
1
+ .trigger {
2
+ display: inline-flex;
3
+ align-items: center;
4
+ justify-content: center;
5
+ flex-shrink: 0;
6
+ width: 28px;
7
+ height: 28px;
8
+ padding: 0;
9
+ border: none;
10
+ border-radius: var(--sv-radius-md);
11
+ background: transparent;
12
+ color: var(--sv-color-text-primary);
13
+ cursor: pointer;
14
+ }
15
+
16
+ @media (hover: hover) {
17
+ .trigger:hover {
18
+ background: var(--sv-color-surface-sunken);
19
+ }
20
+ }
21
+
22
+ .header {
23
+ padding: var(--sv-space-4) var(--sv-space-3) var(--sv-space-1);
24
+ font-size: var(--sv-font-size-sm);
25
+ font-weight: var(--sv-font-weight-semibold);
26
+ color: var(--sv-color-text-primary);
27
+ }
28
+
29
+ .loading {
30
+ display: flex;
31
+ justify-content: center;
32
+ padding: var(--sv-space-6);
33
+ }
34
+
35
+ .error {
36
+ margin: 0;
37
+ padding: 0 var(--sv-space-3) var(--sv-space-3);
38
+ color: var(--sv-color-error-text);
39
+ font-size: var(--sv-font-size-sm);
40
+ }
41
+
42
+ .grid {
43
+ display: grid;
44
+ /* minmax(0, 1fr), not a plain 1fr — a plain 1fr track can grow past the
45
+ grid's own available width to fit a tile name's min-content size
46
+ (nowrap), blowing the grid out past the popover's own width instead of
47
+ letting the name truncate with ellipsis as intended. */
48
+ grid-template-columns: repeat(3, minmax(0, 1fr));
49
+ gap: var(--sv-space-1);
50
+ padding: var(--sv-space-2);
51
+ max-height: 60vh;
52
+ overflow-y: auto;
53
+ }
54
+
55
+ .tile {
56
+ display: flex;
57
+ flex-direction: column;
58
+ align-items: center;
59
+ gap: var(--sv-space-2);
60
+ padding: var(--sv-space-2);
61
+ border: none;
62
+ border-radius: var(--sv-radius-md);
63
+ background: transparent;
64
+ text-decoration: none;
65
+ color: var(--sv-color-text-primary);
66
+ text-align: center;
67
+ font: inherit;
68
+ cursor: pointer;
69
+ }
70
+
71
+ @media (hover: hover) {
72
+ .tile:hover {
73
+ background: var(--sv-color-surface-sunken);
74
+ }
75
+ }
76
+
77
+ .tileIcon {
78
+ display: flex;
79
+ align-items: center;
80
+ justify-content: center;
81
+ width: 32px;
82
+ height: 32px;
83
+ border-radius: var(--sv-radius-md);
84
+ background: var(--sv-color-surface-sunken);
85
+ font-size: var(--sv-font-size-xs);
86
+ font-weight: var(--sv-font-weight-semibold);
87
+ overflow: hidden;
88
+ }
89
+
90
+ .tileName {
91
+ font-size: var(--sv-font-size-caption);
92
+ overflow: hidden;
93
+ text-overflow: ellipsis;
94
+ white-space: nowrap;
95
+ max-width: 100%;
96
+ }
@@ -0,0 +1,58 @@
1
+ import type { Meta, StoryObj } from '@storybook/react-vite';
2
+ import { AppsLauncher } from './AppsLauncher';
3
+ import { Icon } from '../Icon/Icon';
4
+
5
+ const items = [
6
+ {
7
+ key: 'home',
8
+ icon: <Icon name="house" size="md" aria-hidden />,
9
+ label: 'Home',
10
+ href: '/launcher',
11
+ },
12
+ {
13
+ key: 'console',
14
+ icon: <Icon name="layout-dashboard" size="md" aria-hidden />,
15
+ label: 'Console',
16
+ href: '/console',
17
+ },
18
+ {
19
+ key: 'notes',
20
+ icon: <Icon name="file" size="md" aria-hidden />,
21
+ label: 'Notes',
22
+ href: '/notes',
23
+ },
24
+ ];
25
+
26
+ const meta = {
27
+ title: 'Components/AppsLauncher',
28
+ component: AppsLauncher,
29
+ parameters: {
30
+ layout: 'centered',
31
+ docs: {
32
+ description: {
33
+ component:
34
+ "The desktop 'Apps' switcher — a 28px grid trigger opening a Popover tile grid. The desktop counterpart to MobileAppsDrawer: same items shape, different chrome. Presentational only — items are supplied already resolved.",
35
+ },
36
+ },
37
+ },
38
+ args: {
39
+ items,
40
+ },
41
+ } satisfies Meta<typeof AppsLauncher>;
42
+
43
+ export default meta;
44
+ type Story = StoryObj<typeof meta>;
45
+
46
+ export const Default: Story = {};
47
+
48
+ export const Loading: Story = {
49
+ args: {
50
+ loading: true,
51
+ },
52
+ };
53
+
54
+ export const ErrorState: Story = {
55
+ args: {
56
+ error: true,
57
+ },
58
+ };
@@ -0,0 +1,117 @@
1
+ 'use client';
2
+
3
+ import { useState, type ReactNode } from 'react';
4
+ import { Icon } from '../Icon/Icon';
5
+ import { Popover } from '../Popover/Popover';
6
+ import { Spinner } from '../Spinner/Spinner';
7
+ import styles from './AppsLauncher.module.css';
8
+
9
+ export interface AppsLauncherItem {
10
+ /** Stable identity for this tile — used as the React key. */
11
+ key: string;
12
+ icon: ReactNode;
13
+ label: string;
14
+ /** Renders an `<a>` when set; otherwise a `<button>` calling `onClick` —
15
+ * same dual-mode convention as `MobileAppsDrawer`'s items. */
16
+ href?: string;
17
+ onClick?: () => void;
18
+ }
19
+
20
+ export interface AppsLauncherProps {
21
+ items: AppsLauncherItem[];
22
+ /** Shows a spinner in place of the grid — e.g. while the tile list is
23
+ * still being fetched. */
24
+ loading?: boolean;
25
+ /** Shows an error message in place of the grid. */
26
+ error?: boolean;
27
+ errorMessage?: string;
28
+ /** Trigger button's icon. Defaults to a generic grid glyph — pass the
29
+ * real Launcher plugin's own icon for pixel parity with the platform. */
30
+ triggerIcon?: ReactNode;
31
+ /** Trigger button's accessible label, and the popover's own. Defaults to "Apps". */
32
+ 'aria-label'?: string;
33
+ align?: 'left' | 'right';
34
+ }
35
+
36
+ function AppsLauncherTile({ icon, label, href, onClick }: Omit<AppsLauncherItem, 'key'>) {
37
+ const content = (
38
+ <>
39
+ <span className={styles.tileIcon} aria-hidden="true">
40
+ {icon}
41
+ </span>
42
+ <span className={styles.tileName}>{label}</span>
43
+ </>
44
+ );
45
+ if (href) {
46
+ return (
47
+ <a href={href} className={styles.tile} onClick={onClick}>
48
+ {content}
49
+ </a>
50
+ );
51
+ }
52
+ return (
53
+ <button type="button" className={styles.tile} onClick={onClick}>
54
+ {content}
55
+ </button>
56
+ );
57
+ }
58
+
59
+ /**
60
+ * AppsLauncher — the desktop "Apps" switcher: a 28px grid-icon trigger that
61
+ * opens a `Popover` with a 3-column tile grid. The desktop counterpart to
62
+ * `MobileAppsDrawer` — same `items` shape (`key`/`icon`/`label`/`href`/
63
+ * `onClick`), different chrome (floating panel vs. bottom sheet), matching
64
+ * how `Header`/`MobileHeader` already split the same way.
65
+ *
66
+ * Presentational only — no plugin-list fetching of its own. The consumer
67
+ * supplies `items` already resolved (and `loading`/`error` while it isn't
68
+ * yet), matching `MobileAppsDrawer`'s own boundary.
69
+ */
70
+ export function AppsLauncher({
71
+ items,
72
+ loading,
73
+ error,
74
+ errorMessage = "Couldn't load apps. Try again.",
75
+ triggerIcon,
76
+ 'aria-label': ariaLabel = 'Apps',
77
+ align = 'right',
78
+ }: AppsLauncherProps) {
79
+ const [open, setOpen] = useState(false);
80
+
81
+ return (
82
+ <Popover
83
+ align={align}
84
+ width={320}
85
+ aria-label={ariaLabel}
86
+ open={open}
87
+ onClose={() => setOpen(false)}
88
+ trigger={
89
+ <button
90
+ type="button"
91
+ aria-label={ariaLabel}
92
+ aria-haspopup="true"
93
+ aria-expanded={open}
94
+ onClick={() => setOpen((v) => !v)}
95
+ className={styles.trigger}
96
+ >
97
+ {triggerIcon ?? <Icon name="layout-grid" size="md" aria-hidden />}
98
+ </button>
99
+ }
100
+ >
101
+ <div className={styles.header}>{ariaLabel}</div>
102
+ {loading ? (
103
+ <div className={styles.loading}>
104
+ <Spinner size="md" label="Loading apps…" />
105
+ </div>
106
+ ) : error ? (
107
+ <p className={styles.error}>{errorMessage}</p>
108
+ ) : (
109
+ <div className={styles.grid}>
110
+ {items.map(({ key, ...item }) => (
111
+ <AppsLauncherTile key={key} {...item} />
112
+ ))}
113
+ </div>
114
+ )}
115
+ </Popover>
116
+ );
117
+ }
@@ -0,0 +1,52 @@
1
+ // @vitest-environment jsdom
2
+ import { afterEach, describe, expect, it, vi } from 'vitest';
3
+ import { cleanup, fireEvent, render, screen } from '@testing-library/react';
4
+ import { AppsLauncher } from '../AppsLauncher';
5
+
6
+ afterEach(() => {
7
+ cleanup();
8
+ });
9
+
10
+ const items = [
11
+ { key: 'home', icon: <span>home-icon</span>, label: 'Home', href: '/launcher' },
12
+ { key: 'notes', icon: <span>notes-icon</span>, label: 'Notes', onClick: vi.fn() },
13
+ ];
14
+
15
+ describe('AppsLauncher', () => {
16
+ it('renders a closed trigger by default', () => {
17
+ render(<AppsLauncher items={items} />);
18
+ expect(screen.getByRole('button', { name: 'Apps' })).toBeTruthy();
19
+ expect(screen.queryByText('Home')).toBeNull();
20
+ });
21
+
22
+ it('opens the tile grid on trigger click', () => {
23
+ render(<AppsLauncher items={items} />);
24
+ fireEvent.click(screen.getByRole('button', { name: 'Apps' }));
25
+ expect(screen.getByText('Home')).toBeTruthy();
26
+ expect(screen.getByText('Notes')).toBeTruthy();
27
+ });
28
+
29
+ it('renders an href item as a link and an onClick item as a button', () => {
30
+ render(<AppsLauncher items={items} />);
31
+ fireEvent.click(screen.getByRole('button', { name: 'Apps' }));
32
+ expect(screen.getByText('Home').closest('a')).toHaveProperty(
33
+ 'href',
34
+ expect.stringContaining('/launcher'),
35
+ );
36
+ expect(screen.getByText('Notes').closest('button')).toBeTruthy();
37
+ });
38
+
39
+ it('shows a loading state instead of the grid', () => {
40
+ render(<AppsLauncher items={items} loading />);
41
+ fireEvent.click(screen.getByRole('button', { name: 'Apps' }));
42
+ expect(screen.queryByText('Home')).toBeNull();
43
+ expect(screen.getByRole('status', { name: /Loading apps/ })).toBeTruthy();
44
+ });
45
+
46
+ it('shows an error state instead of the grid', () => {
47
+ render(<AppsLauncher items={items} error errorMessage="Nope" />);
48
+ fireEvent.click(screen.getByRole('button', { name: 'Apps' }));
49
+ expect(screen.queryByText('Home')).toBeNull();
50
+ expect(screen.getByText('Nope')).toBeTruthy();
51
+ });
52
+ });
@@ -4,7 +4,7 @@
4
4
  justify-content: center;
5
5
  border-radius: 50%;
6
6
  background: var(--sv-color-surface-raised);
7
- border: 1px solid var(--sv-color-border);
7
+ border: var(--sv-border-width-hairline) solid var(--sv-color-border);
8
8
  overflow: hidden;
9
9
  flex-shrink: 0;
10
10
  font-weight: var(--sv-font-weight-medium);
@@ -19,7 +19,7 @@
19
19
  /* role — neutral fill, border */
20
20
  .role {
21
21
  background: var(--sv-color-surface-sunken);
22
- border: 1px solid var(--sv-color-border);
22
+ border: var(--sv-border-width-hairline) solid var(--sv-color-border);
23
23
  color: var(--sv-color-text-primary);
24
24
  font-weight: var(--sv-font-weight-semibold);
25
25
  }
@@ -31,32 +31,32 @@
31
31
 
32
32
  .chipGreen {
33
33
  background: var(--sv-color-success-surface);
34
- border: 1px solid var(--sv-color-success-border);
34
+ border: var(--sv-border-width-hairline) solid var(--sv-color-success-border);
35
35
  color: var(--sv-color-success-text);
36
36
  }
37
37
 
38
38
  .chipAmber {
39
39
  background: var(--sv-color-warning-surface);
40
- border: 1px solid var(--sv-color-warning-border);
40
+ border: var(--sv-border-width-hairline) solid var(--sv-color-warning-border);
41
41
  color: var(--sv-color-warning-text);
42
42
  }
43
43
 
44
44
  .chipRed {
45
45
  background: var(--sv-color-error-surface);
46
- border: 1px solid var(--sv-color-error-border);
46
+ border: var(--sv-border-width-hairline) solid var(--sv-color-error-border);
47
47
  color: var(--sv-color-error-text);
48
48
  }
49
49
 
50
50
  .chipNeutral {
51
51
  background: var(--sv-color-surface-sunken);
52
- border: 1px solid var(--sv-color-border);
52
+ border: var(--sv-border-width-hairline) solid var(--sv-color-border);
53
53
  color: var(--sv-color-text-muted);
54
54
  }
55
55
 
56
56
  /* mono — monospace font, neutral surface, for type tags / versions */
57
57
  .mono {
58
58
  background: var(--sv-color-surface-sunken);
59
- border: 1px solid var(--sv-color-border);
59
+ border: var(--sv-border-width-hairline) solid var(--sv-color-border);
60
60
  color: var(--sv-color-text-muted);
61
61
  font-family: var(--sv-font-family-mono);
62
62
  }
@@ -66,7 +66,15 @@
66
66
  composed partials fighting over the same properties. `.role.md`,
67
67
  `.status.md`, and `.mono.md` reproduce the badge's original (and, until
68
68
  now, only) sizing exactly, so the default (no `size` prop) renders
69
- pixel-identical to before this scale existed. */
69
+ pixel-identical to before this scale existed. `xs` was added later, for a
70
+ badge sitting inline next to larger text where `sm` (already the smallest
71
+ type-scale token, `--sv-font-size-label`) still read oversized — it needed
72
+ a new, smaller primitive (`--sv-font-size-2xs`) rather than reusing one. */
73
+ .role.xs {
74
+ padding: 1px 3px;
75
+ font-size: var(--sv-font-size-2xs);
76
+ }
77
+
70
78
  .role.sm {
71
79
  padding: 1px var(--sv-space-1);
72
80
  font-size: var(--sv-font-size-label);
@@ -82,6 +90,11 @@
82
90
  font-size: var(--sv-font-size-sm);
83
91
  }
84
92
 
93
+ .mono.xs {
94
+ padding: 1px 3px;
95
+ font-size: var(--sv-font-size-2xs);
96
+ }
97
+
85
98
  .mono.sm {
86
99
  padding: 1px var(--sv-space-1);
87
100
  font-size: var(--sv-font-size-label);
@@ -100,6 +113,11 @@
100
113
  /* status keeps a tighter left edge than right at every size — the dot +
101
114
  gap already read as space before the text, so a full padding-inline-start
102
115
  would double it up. */
116
+ .status.xs {
117
+ padding: 1px 3px 1px 2px;
118
+ font-size: var(--sv-font-size-2xs);
119
+ }
120
+
103
121
  .status.sm {
104
122
  padding: 1px var(--sv-space-1) 1px 3px;
105
123
  font-size: var(--sv-font-size-label);
@@ -122,6 +140,11 @@
122
140
  flex-shrink: 0;
123
141
  }
124
142
 
143
+ .xs .dot {
144
+ width: 4px;
145
+ height: 4px;
146
+ }
147
+
125
148
  .sm .dot {
126
149
  width: 5px;
127
150
  height: 5px;