@sovereignfs/ui 0.45.0 → 0.83.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (323) 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/LegalLinks.module.css +24 -0
  26. package/dist/Markdown.module.css +73 -0
  27. package/dist/Menu.module.css +1 -0
  28. package/dist/Message.module.css +1 -1
  29. package/dist/MessageScroller.module.css +12 -1
  30. package/dist/MobileAppsDrawer.module.css +82 -0
  31. package/dist/MobileFooter.module.css +73 -0
  32. package/dist/MobileHeader.module.css +76 -0
  33. package/dist/NavList.module.css +130 -0
  34. package/dist/NavTabs.module.css +1 -1
  35. package/dist/NotificationsPanel.module.css +258 -0
  36. package/dist/OverlayHeader.module.css +1 -1
  37. package/dist/PageContainer.module.css +82 -0
  38. package/dist/PageHeader.module.css +54 -0
  39. package/dist/PageLayout.module.css +51 -0
  40. package/dist/Popover.module.css +1 -1
  41. package/dist/QuantityStepper.module.css +1 -1
  42. package/dist/Resizable.module.css +3 -3
  43. package/dist/RootLayout.module.css +12 -0
  44. package/dist/SegmentedControl.module.css +1 -1
  45. package/dist/Select.module.css +1 -1
  46. package/dist/Sheet.module.css +4 -1
  47. package/dist/SplitPane.module.css +4 -4
  48. package/dist/StatusBadge.module.css +1 -1
  49. package/dist/StepStrip.module.css +53 -0
  50. package/dist/SuggestionInput.module.css +2 -2
  51. package/dist/SwipableMobileCarousel.module.css +64 -0
  52. package/dist/SwipableMobileCarouselDots.module.css +64 -0
  53. package/dist/SwipableMobileCarouselSlideParts.module.css +29 -0
  54. package/dist/SwipeStack.module.css +101 -0
  55. package/dist/SystemBanner.module.css +10 -0
  56. package/dist/Table.module.css +2 -2
  57. package/dist/Tabs.module.css +1 -1
  58. package/dist/TagInput.module.css +2 -2
  59. package/dist/Textarea.module.css +9 -1
  60. package/dist/ThreeColumnLayout.module.css +35 -0
  61. package/dist/Toast.module.css +62 -3
  62. package/dist/Tooltip.module.css +1 -1
  63. package/dist/UserMenu.module.css +90 -0
  64. package/dist/index.d.ts +1680 -30
  65. package/dist/index.js +4618 -1443
  66. package/dist/tokens/primitives.css +36 -0
  67. package/dist/tokens/semantic.css +32 -0
  68. package/dist/tokens/theme-presets.css +33 -0
  69. package/package.json +32 -12
  70. package/src/__tests__/package-json.test.ts +8 -0
  71. package/src/components/Accordion/Accordion.module.css +1 -1
  72. package/src/components/Alert/Alert.module.css +1 -1
  73. package/src/components/AppsLauncher/AppsLauncher.module.css +96 -0
  74. package/src/components/AppsLauncher/AppsLauncher.stories.tsx +58 -0
  75. package/src/components/AppsLauncher/AppsLauncher.tsx +117 -0
  76. package/src/components/AppsLauncher/__tests__/AppsLauncher.test.tsx +52 -0
  77. package/src/components/Avatar/Avatar.module.css +1 -1
  78. package/src/components/Badge/Badge.module.css +30 -7
  79. package/src/components/Badge/Badge.stories.tsx +16 -4
  80. package/src/components/Badge/Badge.tsx +8 -11
  81. package/src/components/Badge/__tests__/Badge.test.tsx +4 -2
  82. package/src/components/BalanceChip/BalanceChip.module.css +1 -1
  83. package/src/components/Button/Button.module.css +18 -2
  84. package/src/components/Card/Card.module.css +1 -1
  85. package/src/components/CardTile/CardTile.module.css +181 -0
  86. package/src/components/CardTile/CardTile.stories.tsx +86 -0
  87. package/src/components/CardTile/CardTile.tsx +128 -0
  88. package/src/components/CardTile/__tests__/CardTile.test.tsx +56 -0
  89. package/src/components/CodeTextarea/CodeTextarea.module.css +1 -1
  90. package/src/components/Collapsible/Collapsible.module.css +1 -1
  91. package/src/components/ColorPicker/ColorPicker.module.css +132 -0
  92. package/src/components/ColorPicker/ColorPicker.stories.tsx +80 -0
  93. package/src/components/ColorPicker/ColorPicker.tsx +157 -0
  94. package/src/components/ColorPicker/__tests__/ColorPicker.test.tsx +117 -0
  95. package/src/components/Combobox/Combobox.module.css +2 -2
  96. package/src/components/Command/Command.module.css +1 -1
  97. package/src/components/ConfirmDialog/ConfirmDialog.module.css +2 -2
  98. package/src/components/ConfirmDialog/ConfirmDialog.tsx +28 -2
  99. package/src/components/ConfirmDialog/__tests__/ConfirmDialog.test.tsx +71 -3
  100. package/src/components/ConsentPrompt/ConsentPrompt.module.css +21 -0
  101. package/src/components/ConsentPrompt/ConsentPrompt.stories.tsx +31 -0
  102. package/src/components/ConsentPrompt/ConsentPrompt.tsx +66 -0
  103. package/src/components/ContextMenu/ContextMenu.tsx +1 -1
  104. package/src/components/DatePicker/DatePicker.module.css +1 -1
  105. package/src/components/DeviceOnlyGate/DeviceOnlyGate.tsx +58 -0
  106. package/src/components/DeviceOnlyGate/__tests__/DeviceOnlyGate.test.tsx +45 -0
  107. package/src/components/DeviceStorageKeyGate/DeviceStorageKeyGate.tsx +100 -0
  108. package/src/components/DeviceStorageKeyGate/__tests__/DeviceStorageKeyGate.test.tsx +119 -0
  109. package/src/components/Dialog/Dialog.module.css +130 -21
  110. package/src/components/Dialog/Dialog.stories.tsx +179 -2
  111. package/src/components/Dialog/Dialog.tsx +137 -94
  112. package/src/components/Dialog/__tests__/Dialog.test.tsx +190 -9
  113. package/src/components/DragHandleRow/DragHandleRow.tsx +2 -21
  114. package/src/components/Drawer/Drawer.module.css +36 -7
  115. package/src/components/Drawer/Drawer.stories.tsx +44 -0
  116. package/src/components/Drawer/Drawer.tsx +27 -66
  117. package/src/components/Drawer/__tests__/Drawer.test.tsx +55 -6
  118. package/src/components/GripIcon/GripIcon.tsx +27 -0
  119. package/src/components/Header/Header.module.css +62 -0
  120. package/src/components/Header/Header.stories.tsx +452 -0
  121. package/src/components/Header/Header.tsx +102 -0
  122. package/src/components/Header/__tests__/Header.test.tsx +104 -0
  123. package/src/components/HeaderFooterLayout/HeaderFooterLayout.module.css +21 -0
  124. package/src/components/HeaderFooterLayout/HeaderFooterLayout.stories.tsx +111 -0
  125. package/src/components/HeaderFooterLayout/HeaderFooterLayout.tsx +54 -0
  126. package/src/components/HeaderFooterLayout/__tests__/HeaderFooterLayout.test.tsx +58 -0
  127. package/src/components/Icon/icons/arrow-left-right.tsx +23 -0
  128. package/src/components/Icon/icons/bold.tsx +20 -0
  129. package/src/components/Icon/icons/calendar.tsx +4 -4
  130. package/src/components/Icon/icons/carrot.tsx +3 -3
  131. package/src/components/Icon/icons/circle-chevron-left.tsx +21 -0
  132. package/src/components/Icon/icons/code.tsx +21 -0
  133. package/src/components/Icon/icons/copy.tsx +21 -0
  134. package/src/components/Icon/icons/credit-card.tsx +21 -0
  135. package/src/components/Icon/icons/database.tsx +22 -0
  136. package/src/components/Icon/icons/download.tsx +22 -0
  137. package/src/components/Icon/icons/file-text.tsx +24 -0
  138. package/src/components/Icon/icons/folder-closed.tsx +21 -0
  139. package/src/components/Icon/icons/folder-open.tsx +20 -0
  140. package/src/components/Icon/icons/folder.tsx +20 -0
  141. package/src/components/Icon/icons/folders.tsx +21 -0
  142. package/src/components/Icon/icons/hat-glasses.tsx +24 -0
  143. package/src/components/Icon/icons/history.tsx +22 -0
  144. package/src/components/Icon/icons/inbox.tsx +21 -0
  145. package/src/components/Icon/icons/index.ts +169 -1
  146. package/src/components/Icon/icons/italic.tsx +22 -0
  147. package/src/components/Icon/icons/layers.tsx +22 -0
  148. package/src/components/Icon/icons/layout-dashboard.tsx +23 -0
  149. package/src/components/Icon/icons/layout-grid.tsx +23 -0
  150. package/src/components/Icon/icons/link.tsx +21 -0
  151. package/src/components/Icon/icons/list-ordered.tsx +25 -0
  152. package/src/components/Icon/icons/list.tsx +25 -0
  153. package/src/components/Icon/icons/luggage.tsx +24 -0
  154. package/src/components/Icon/icons/map-pin.tsx +21 -0
  155. package/src/components/Icon/icons/menu.tsx +22 -0
  156. package/src/components/Icon/icons/minus.tsx +20 -0
  157. package/src/components/Icon/icons/paint-bucket.tsx +23 -0
  158. package/src/components/Icon/icons/panel-left.tsx +21 -0
  159. package/src/components/Icon/icons/pin.tsx +21 -0
  160. package/src/components/Icon/icons/rectangle-ellipsis.tsx +23 -0
  161. package/src/components/Icon/icons/refresh-cw.tsx +23 -0
  162. package/src/components/Icon/icons/rotate-cw.tsx +21 -0
  163. package/src/components/Icon/icons/route.tsx +22 -0
  164. package/src/components/Icon/icons/send.tsx +21 -0
  165. package/src/components/Icon/icons/share-2.tsx +24 -0
  166. package/src/components/Icon/icons/sheet.tsx +24 -0
  167. package/src/components/Icon/icons/smartphone.tsx +21 -0
  168. package/src/components/Icon/icons/square-kanban.tsx +23 -0
  169. package/src/components/Icon/icons/table.tsx +23 -0
  170. package/src/components/Icon/icons/user-round-plus.tsx +23 -0
  171. package/src/components/Icon/icons/users.tsx +23 -0
  172. package/src/components/IconPicker/IconPicker.module.css +1 -1
  173. package/src/components/Input/Input.module.css +1 -1
  174. package/src/components/Input/Input.tsx +8 -2
  175. package/src/components/Kbd/Kbd.module.css +1 -1
  176. package/src/components/LegalLinks/LegalLinks.module.css +24 -0
  177. package/src/components/LegalLinks/LegalLinks.stories.tsx +22 -0
  178. package/src/components/LegalLinks/LegalLinks.tsx +40 -0
  179. package/src/components/Markdown/Markdown.module.css +73 -0
  180. package/src/components/Markdown/Markdown.stories.tsx +40 -0
  181. package/src/components/Markdown/Markdown.tsx +305 -0
  182. package/src/components/Markdown/__tests__/Markdown.test.tsx +144 -0
  183. package/src/components/Menu/Menu.module.css +1 -0
  184. package/src/components/Menu/Menu.tsx +72 -22
  185. package/src/components/Menu/__tests__/Menu.test.tsx +30 -0
  186. package/src/components/Menubar/Menubar.tsx +1 -1
  187. package/src/components/Message/Message.module.css +1 -1
  188. package/src/components/MessageScroller/MessageScroller.module.css +12 -1
  189. package/src/components/MessageScroller/MessageScroller.tsx +12 -0
  190. package/src/components/MobileAppsDrawer/MobileAppsDrawer.module.css +82 -0
  191. package/src/components/MobileAppsDrawer/MobileAppsDrawer.tsx +78 -0
  192. package/src/components/MobileAppsDrawer/__tests__/MobileAppsDrawer.test.tsx +80 -0
  193. package/src/components/MobileFooter/MobileFooter.module.css +73 -0
  194. package/src/components/MobileFooter/MobileFooter.stories.tsx +47 -0
  195. package/src/components/MobileFooter/MobileFooter.tsx +122 -0
  196. package/src/components/MobileFooter/__tests__/MobileFooter.test.tsx +129 -0
  197. package/src/components/MobileHeader/MobileHeader.module.css +76 -0
  198. package/src/components/MobileHeader/MobileHeader.stories.tsx +57 -0
  199. package/src/components/MobileHeader/MobileHeader.tsx +90 -0
  200. package/src/components/MobileHeader/__tests__/MobileHeader.test.tsx +113 -0
  201. package/src/components/NavList/NavList.module.css +130 -0
  202. package/src/components/NavList/NavList.tsx +136 -0
  203. package/src/components/NavList/__tests__/NavList.test.tsx +109 -0
  204. package/src/components/NavTabs/NavTabs.module.css +1 -1
  205. package/src/components/NavTabs/NavTabs.tsx +39 -11
  206. package/src/components/NotificationsPanel/NotificationsPanel.module.css +258 -0
  207. package/src/components/NotificationsPanel/NotificationsPanel.stories.tsx +61 -0
  208. package/src/components/NotificationsPanel/NotificationsPanel.tsx +187 -0
  209. package/src/components/NotificationsPanel/__tests__/NotificationsPanel.test.tsx +78 -0
  210. package/src/components/OfflineGate/OfflineGate.tsx +44 -0
  211. package/src/components/OfflineGate/__tests__/OfflineGate.test.tsx +66 -0
  212. package/src/components/OverlayHeader/OverlayHeader.module.css +1 -1
  213. package/src/components/OverlayHeader/OverlayHeader.tsx +19 -4
  214. package/src/components/OverlayHeader/__tests__/OverlayHeader.test.tsx +18 -0
  215. package/src/components/PageContainer/PageContainer.module.css +82 -0
  216. package/src/components/PageContainer/PageContainer.tsx +64 -0
  217. package/src/components/PageContainer/__tests__/PageContainer.test.tsx +70 -0
  218. package/src/components/PageHeader/PageHeader.module.css +54 -0
  219. package/src/components/PageHeader/PageHeader.tsx +157 -2
  220. package/src/components/PageHeader/__tests__/PageHeader.test.tsx +103 -0
  221. package/src/components/PageLayout/PageLayout.module.css +51 -0
  222. package/src/components/PageLayout/PageLayout.stories.tsx +73 -0
  223. package/src/components/PageLayout/PageLayout.tsx +51 -0
  224. package/src/components/PageLayout/__tests__/PageLayout.test.tsx +34 -0
  225. package/src/components/Popover/Popover.module.css +1 -1
  226. package/src/components/Popover/Popover.tsx +86 -4
  227. package/src/components/Popover/__tests__/Popover.test.tsx +165 -0
  228. package/src/components/QuantityStepper/QuantityStepper.module.css +1 -1
  229. package/src/components/Resizable/Resizable.module.css +3 -3
  230. package/src/components/ResponsiveSurface/ResponsiveSurface.tsx +24 -0
  231. package/src/components/ResponsiveSurface/__tests__/ResponsiveSurface.test.tsx +73 -0
  232. package/src/components/RootLayout/RootLayout.module.css +12 -0
  233. package/src/components/RootLayout/RootLayout.stories.tsx +125 -0
  234. package/src/components/RootLayout/RootLayout.tsx +185 -0
  235. package/src/components/RootLayout/__tests__/RootLayout.test.tsx +161 -0
  236. package/src/components/SegmentedControl/SegmentedControl.module.css +1 -1
  237. package/src/components/Select/Select.module.css +1 -1
  238. package/src/components/Sheet/Sheet.module.css +4 -1
  239. package/src/components/Sheet/Sheet.tsx +14 -56
  240. package/src/components/SplitPane/SplitPane.module.css +4 -4
  241. package/src/components/StatusBadge/StatusBadge.module.css +1 -1
  242. package/src/components/StepStrip/StepStrip.module.css +53 -0
  243. package/src/components/StepStrip/StepStrip.tsx +74 -0
  244. package/src/components/SuggestionInput/SuggestionInput.module.css +2 -2
  245. package/src/components/SwipableMobileCarousel/SwipableMobileCarousel.module.css +64 -0
  246. package/src/components/SwipableMobileCarousel/SwipableMobileCarousel.tsx +243 -0
  247. package/src/components/SwipableMobileCarousel/SwipableMobileCarouselSlide.tsx +35 -0
  248. package/src/components/SwipableMobileCarousel/SwipableMobileCarouselSlideBody.tsx +42 -0
  249. package/src/components/SwipableMobileCarousel/SwipableMobileCarouselSlideFooter.tsx +18 -0
  250. package/src/components/SwipableMobileCarousel/SwipableMobileCarouselSlideHeader.tsx +21 -0
  251. package/src/components/SwipableMobileCarousel/SwipableMobileCarouselSlideParts.module.css +29 -0
  252. package/src/components/SwipableMobileCarousel/__tests__/SwipableMobileCarousel.test.tsx +214 -0
  253. package/src/components/SwipableMobileCarousel/__tests__/SwipableMobileCarouselDevGuard.test.tsx +61 -0
  254. package/src/components/SwipableMobileCarousel/context.ts +13 -0
  255. package/src/components/SwipableMobileCarouselDots/SwipableMobileCarouselDots.module.css +64 -0
  256. package/src/components/SwipableMobileCarouselDots/SwipableMobileCarouselDots.tsx +73 -0
  257. package/src/components/SwipableMobileCarouselDots/__tests__/SwipableMobileCarouselDots.test.tsx +63 -0
  258. package/src/components/SwipeStack/SwipeStack.module.css +101 -0
  259. package/src/components/SwipeStack/SwipeStack.tsx +199 -0
  260. package/src/components/SwipeStack/SwipeStackCard.tsx +22 -0
  261. package/src/components/SwipeStack/__tests__/SwipeStack.test.tsx +169 -0
  262. package/src/components/SystemBanner/SystemBanner.module.css +10 -0
  263. package/src/components/Table/Table.module.css +2 -2
  264. package/src/components/Tabs/Tabs.module.css +1 -1
  265. package/src/components/TagInput/TagInput.module.css +2 -2
  266. package/src/components/Textarea/Textarea.module.css +9 -1
  267. package/src/components/Textarea/Textarea.tsx +28 -5
  268. package/src/components/Textarea/__tests__/Textarea.test.tsx +23 -1
  269. package/src/components/ThreeColumnLayout/ThreeColumnLayout.module.css +35 -0
  270. package/src/components/ThreeColumnLayout/ThreeColumnLayout.stories.tsx +152 -0
  271. package/src/components/ThreeColumnLayout/ThreeColumnLayout.tsx +87 -0
  272. package/src/components/ThreeColumnLayout/__tests__/ThreeColumnLayout.test.tsx +78 -0
  273. package/src/components/Toast/Toast.module.css +62 -3
  274. package/src/components/Toast/Toast.stories.tsx +1 -1
  275. package/src/components/Tooltip/Tooltip.module.css +1 -1
  276. package/src/components/UserMenu/UserMenu.module.css +90 -0
  277. package/src/components/UserMenu/UserMenu.stories.tsx +49 -0
  278. package/src/components/UserMenu/UserMenu.tsx +132 -0
  279. package/src/components/UserMenu/__tests__/UserMenu.test.tsx +77 -0
  280. package/src/hooks/__tests__/useCarouselRouteSync.test.tsx +126 -0
  281. package/src/hooks/__tests__/useIsOffline.test.tsx +53 -0
  282. package/src/hooks/__tests__/useOfflineTileState.test.tsx +69 -0
  283. package/src/hooks/__tests__/usePublishShellChromeHeight.test.tsx +118 -0
  284. package/src/hooks/__tests__/useReorderSensors.test.ts +26 -0
  285. package/src/hooks/__tests__/useResponsiveLayout.test.tsx +87 -0
  286. package/src/hooks/__tests__/useSnapCarousel.test.tsx +175 -0
  287. package/src/hooks/__tests__/useSwipeReveal.test.tsx +110 -0
  288. package/src/hooks/__tests__/useSwipeStack.test.tsx +155 -0
  289. package/src/hooks/index.ts +27 -0
  290. package/src/hooks/useCarouselRouteSync.ts +92 -0
  291. package/src/hooks/useIsOffline.ts +41 -0
  292. package/src/hooks/useOfflineTileState.ts +45 -0
  293. package/src/hooks/usePublishShellChromeHeight.ts +96 -0
  294. package/src/hooks/useReorderSensors.ts +88 -0
  295. package/src/hooks/useResponsiveLayout.ts +36 -0
  296. package/src/hooks/useSnapCarousel.ts +284 -0
  297. package/src/hooks/useSwipeReveal.ts +107 -0
  298. package/src/hooks/useSwipeStack.ts +235 -0
  299. package/src/index.ts +124 -3
  300. package/src/overlay-shell.ts +159 -0
  301. package/src/stories/DesignSystemOverview.stories.tsx +933 -6
  302. package/src/stories/DeviceOnlyGate.stories.tsx +35 -0
  303. package/src/stories/DeviceStorageKeyGate.stories.tsx +57 -0
  304. package/src/stories/GripIcon.stories.tsx +29 -0
  305. package/src/stories/InteractionHooks.stories.tsx +483 -0
  306. package/src/stories/LayoutSystem.stories.tsx +639 -0
  307. package/src/stories/MenuEntries.stories.tsx +86 -0
  308. package/src/stories/MobilePatterns.stories.tsx +247 -2
  309. package/src/stories/NavList.stories.tsx +147 -0
  310. package/src/stories/NavTabs.stories.tsx +18 -0
  311. package/src/stories/OfflineGate.stories.tsx +42 -0
  312. package/src/stories/PageContainer.stories.tsx +82 -0
  313. package/src/stories/PageHeader.stories.tsx +65 -1
  314. package/src/stories/ResponsiveSurface.stories.tsx +47 -0
  315. package/src/stories/StepStrip.stories.tsx +76 -0
  316. package/src/stories/SwipableMobileCarousel.stories.tsx +362 -0
  317. package/src/stories/SwipeStack.stories.tsx +200 -0
  318. package/src/stories/Textarea.stories.tsx +12 -0
  319. package/src/stories/TokenGallery.stories.tsx +115 -0
  320. package/src/tokens/primitives.css +36 -0
  321. package/src/tokens/semantic.css +32 -0
  322. package/src/tokens/theme-presets.css +33 -0
  323. 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,11 +73,25 @@
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;
78
83
  --sv-font-weight-bold: 700;
79
84
 
85
+ /* In-app text-size control (task 10.2), discharging the pinch-zoom-disabled
86
+ accessibility debt (runtime/app/layout.tsx sets userScalable: false).
87
+ Every --sv-font-size-* above is rem-based, so scaling the root font size
88
+ scales the whole type system with no per-component changes. Driven by
89
+ [data-text-size] on <html>, set pre-paint by runtime/src/theme-script.ts
90
+ from the `sv-text-size` cookie — same mechanism as [data-theme]. Like
91
+ --sv-radius-scale, this is a theme-stable scale token with no separate
92
+ semantic tier. */
93
+ --sv-text-size-scale: 1;
94
+
80
95
  /* Icon sizes — scale tokens used directly by the Icon component */
81
96
  --sv-icon-size-xs: 0.75rem; /* 12px — inline metadata (e.g. a due-date badge) */
82
97
  --sv-icon-size-sm: 1rem; /* 16px — inline with body text */
@@ -106,6 +121,12 @@
106
121
  ); /* 20px @ scale 1 — bottom sheet (Drawer top corners) */
107
122
  --sv-radius-full: 9999px; /* pill / avatar — unaffected by --sv-radius-scale */
108
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
+
109
130
  /* Touch targets — minimum hit-area dimension for interactive controls.
110
131
  44px follows the WCAG 2.5.5 / Apple HIG / Material guideline for reliable
111
132
  touch without a pointing device. Applied to icon-only controls on mobile
@@ -130,3 +151,18 @@
130
151
  1
131
152
  ); /* slight overshoot — playful emphasis, use sparingly */
132
153
  }
154
+
155
+ /* Root font size scales with --sv-text-size-scale — see the token comment
156
+ above. calc(... * 100%) keeps the browser/OS text-zoom default intact at
157
+ scale 1 rather than hardcoding a px value. */
158
+ html {
159
+ font-size: calc(var(--sv-text-size-scale) * 100%);
160
+ }
161
+
162
+ :root[data-text-size='large'] {
163
+ --sv-text-size-scale: 1.125; /* +12.5% */
164
+ }
165
+
166
+ :root[data-text-size='larger'] {
167
+ --sv-text-size-scale: 1.25; /* +25% */
168
+ }
@@ -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.45.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,28 +36,44 @@
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": {
39
- "@storybook/addon-a11y": "^10.4.6",
40
- "@storybook/addon-themes": "^10.4.6",
41
- "@storybook/react-vite": "^10.4.6",
53
+ "@dnd-kit/core": "^6.3.1",
54
+ "@dnd-kit/sortable": "^10.0.0",
55
+ "@playwright/test": "^1.62.1",
56
+ "@storybook/addon-a11y": "^10.6.0",
57
+ "@storybook/addon-themes": "^10.6.0",
58
+ "@storybook/react-vite": "^10.6.0",
42
59
  "@testing-library/dom": "^10.4.1",
43
- "@testing-library/react": "^16.3.2",
44
- "@types/react": "^19.0.0",
45
- "@types/react-dom": "^19.0.0",
46
- "react": "^19.0.0",
47
- "react-dom": "^19.0.0",
48
- "storybook": "^10.4.6",
60
+ "@testing-library/react": "^16.3.3",
61
+ "@types/react": "^19.2.18",
62
+ "@types/react-dom": "^19.2.5",
63
+ "react": "^19.2.8",
64
+ "react-dom": "^19.2.8",
65
+ "storybook": "^10.6.0",
49
66
  "tsup": "^8.5.1",
50
- "typescript": "^5.9.3",
67
+ "typescript": "^6.0.3",
51
68
  "@sovereignfs/tsconfig": "0.0.0"
52
69
  },
53
70
  "scripts": {
54
71
  "build": "tsup",
55
72
  "storybook": "storybook dev -p 6006",
73
+ "storybook:visual": "storybook dev -p 6100 --ci --quiet",
56
74
  "build-storybook": "storybook build --output-dir storybook-static",
75
+ "test:visual": "playwright test --config=playwright.visual.config.ts",
76
+ "test:visual:update": "playwright test --config=playwright.visual.config.ts --update-snapshots",
57
77
  "typecheck": "tsc --noEmit"
58
78
  },
59
79
  "types": "./dist/index.d.ts"
@@ -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);