@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
@@ -0,0 +1,200 @@
1
+ import { useState } from 'react';
2
+ import type { Meta, StoryObj } from '@storybook/react-vite';
3
+ import { SwipeStack } from '../components/SwipeStack/SwipeStack';
4
+ import { SwipeStackCard } from '../components/SwipeStack/SwipeStackCard';
5
+
6
+ const AGENDA_TASKS = [
7
+ { id: 'water-plants', title: 'Water the plants', detail: 'Living room + balcony' },
8
+ { id: 'standup', title: 'Team standup', detail: '9:30am · 15 min' },
9
+ { id: 'groceries', title: 'Buy groceries', detail: 'Milk, eggs, bread' },
10
+ { id: 'review-pr', title: "Review teammate's PR", detail: 'sovereign/sovereign #482' },
11
+ { id: 'call-dentist', title: 'Call the dentist', detail: 'Reschedule cleaning' },
12
+ ];
13
+
14
+ function taskCardStyle(): React.CSSProperties {
15
+ return {
16
+ height: '100%',
17
+ boxSizing: 'border-box',
18
+ display: 'flex',
19
+ flexDirection: 'column',
20
+ justifyContent: 'center',
21
+ padding: 'var(--sv-space-6)',
22
+ fontFamily: 'var(--sv-font-family)',
23
+ };
24
+ }
25
+
26
+ function TaskCardDemo({ title, detail }: { title: string; detail: string }) {
27
+ return (
28
+ <div style={taskCardStyle()}>
29
+ <div
30
+ style={{
31
+ fontSize: 'var(--sv-font-size-lg)',
32
+ fontWeight: 'var(--sv-font-weight-semibold)',
33
+ color: 'var(--sv-color-text-primary)',
34
+ marginBottom: 'var(--sv-space-2)',
35
+ }}
36
+ >
37
+ {title}
38
+ </div>
39
+ <div style={{ fontSize: 'var(--sv-font-size-sm)', color: 'var(--sv-color-text-muted)' }}>
40
+ {detail}
41
+ </div>
42
+ </div>
43
+ );
44
+ }
45
+
46
+ function StackFrame({ children }: { children: React.ReactNode }) {
47
+ return (
48
+ <div
49
+ style={{
50
+ height: 420,
51
+ maxWidth: 375,
52
+ margin: '0 auto',
53
+ padding: 'var(--sv-space-4)',
54
+ border: '1px solid var(--sv-color-border)',
55
+ borderRadius: 'var(--sv-radius-md)',
56
+ boxSizing: 'border-box',
57
+ }}
58
+ >
59
+ {children}
60
+ </div>
61
+ );
62
+ }
63
+
64
+ function AgendaDemo() {
65
+ const [log, setLog] = useState<string[]>([]);
66
+ return (
67
+ <StackFrame>
68
+ <SwipeStack
69
+ aria-label="Today's agenda"
70
+ directions={{
71
+ up: { label: 'Done', icon: 'check' },
72
+ down: { label: 'Defer', icon: 'history' },
73
+ right: { label: 'Skip', icon: 'chevron-right' },
74
+ }}
75
+ onSwipe={(direction, cardId) =>
76
+ setLog((l) => [`${cardId} → ${direction}`, ...l].slice(0, 5))
77
+ }
78
+ >
79
+ {AGENDA_TASKS.map((task) => (
80
+ <SwipeStackCard key={task.id} cardId={task.id}>
81
+ <TaskCardDemo title={task.title} detail={task.detail} />
82
+ </SwipeStackCard>
83
+ ))}
84
+ </SwipeStack>
85
+ <div
86
+ style={{
87
+ marginTop: 'var(--sv-space-3)',
88
+ fontFamily: 'var(--sv-font-family)',
89
+ fontSize: 'var(--sv-font-size-xs)',
90
+ color: 'var(--sv-color-text-muted)',
91
+ }}
92
+ >
93
+ {log.map((entry, i) => (
94
+ <div key={i}>{entry}</div>
95
+ ))}
96
+ </div>
97
+ </StackFrame>
98
+ );
99
+ }
100
+
101
+ function TwoDirectionsDemo() {
102
+ return (
103
+ <StackFrame>
104
+ <SwipeStack
105
+ aria-label="Photo picker"
106
+ directions={{
107
+ left: { label: 'Skip' },
108
+ right: { label: 'Keep', icon: 'check' },
109
+ }}
110
+ onSwipe={() => {}}
111
+ >
112
+ {AGENDA_TASKS.map((task) => (
113
+ <SwipeStackCard key={task.id} cardId={task.id}>
114
+ <TaskCardDemo title={task.title} detail={task.detail} />
115
+ </SwipeStackCard>
116
+ ))}
117
+ </SwipeStack>
118
+ </StackFrame>
119
+ );
120
+ }
121
+
122
+ function EmptyDemo() {
123
+ return (
124
+ <StackFrame>
125
+ <SwipeStack
126
+ aria-label="Today's agenda"
127
+ directions={{ up: { label: 'Done', icon: 'check' } }}
128
+ onSwipe={() => {}}
129
+ >
130
+ {[]}
131
+ </SwipeStack>
132
+ </StackFrame>
133
+ );
134
+ }
135
+
136
+ const meta = {
137
+ title: 'Components/SwipeStack',
138
+ component: SwipeStack,
139
+ parameters: {
140
+ layout: 'padded',
141
+ docs: {
142
+ description: {
143
+ component:
144
+ 'A compound component for triaging one card at a time by dragging it left/right/up/down, plus an always-visible non-gesture fallback (a button per configured direction) wired to the identical commit path and exit animation as a real drag. Every direction is independently optional and has no baked-in meaning — the caller supplies label/icon per direction and reacts to onSwipe; once triggered, a direction always removes the card for good, there is no reversible "go back".',
145
+ },
146
+ },
147
+ },
148
+ // Every story below supplies its own render(), stateless or with its own
149
+ // useState — these args only satisfy the required-prop typing.
150
+ args: {
151
+ 'aria-label': "Today's agenda",
152
+ directions: {},
153
+ onSwipe: () => {},
154
+ children: null,
155
+ },
156
+ } satisfies Meta<typeof SwipeStack>;
157
+
158
+ export default meta;
159
+ type Story = StoryObj<typeof meta>;
160
+
161
+ export const Default: Story = {
162
+ render: () => <AgendaDemo />,
163
+ parameters: {
164
+ docs: {
165
+ description: {
166
+ story:
167
+ 'up/down/right configured, left intentionally omitted — SwipeStack does not require all 4 directions. The log below the stack shows onSwipe firing with (direction, cardId) for both a drag and a fallback-button click.',
168
+ },
169
+ },
170
+ },
171
+ };
172
+
173
+ export const TwoDirections: Story = {
174
+ render: () => <TwoDirectionsDemo />,
175
+ parameters: {
176
+ docs: {
177
+ description: {
178
+ story:
179
+ 'Only left/right configured — up/down are a wall, not just an unreachable outcome, so the card never visually drifts vertically, and touch-action stays pan-y so a page can still scroll vertically through the stack.',
180
+ },
181
+ },
182
+ },
183
+ };
184
+
185
+ export const Empty: Story = {
186
+ render: () => <EmptyDemo />,
187
+ parameters: {
188
+ docs: {
189
+ description: {
190
+ story:
191
+ 'No cards left — SwipeStack renders its empty region and disables the fallback button rather than showing any built-in empty-state copy; that vocabulary belongs to the caller, same as SwipableMobileCarousel has no opinion on where slide data lives.',
192
+ },
193
+ },
194
+ },
195
+ };
196
+
197
+ export const MobileViewport: Story = {
198
+ render: () => <AgendaDemo />,
199
+ parameters: { viewport: { defaultViewport: 'mobile1' } },
200
+ };
@@ -49,3 +49,15 @@ export const CustomRows: Story = {
49
49
  export const Disabled: Story = {
50
50
  render: (_args) => <Labeled label="Read-only field" defaultValue="Cannot be changed" disabled />,
51
51
  };
52
+
53
+ export const AutoGrow: Story = {
54
+ render: (_args) => (
55
+ <Labeled
56
+ label="Title (grows with content)"
57
+ autoGrow
58
+ rows={1}
59
+ defaultValue="A short title"
60
+ placeholder="Add a title…"
61
+ />
62
+ ),
63
+ };
@@ -1,5 +1,7 @@
1
1
  import { useEffect, useRef, useState } from 'react';
2
2
  import type { Meta, StoryObj } from '@storybook/react-vite';
3
+ import { Button } from '../components/Button/Button';
4
+ import { Card } from '../components/Card/Card';
3
5
 
4
6
  // ---------------------------------------------------------------------------
5
7
  // Token Gallery — live rendering of every CSS custom property tier.
@@ -16,8 +18,11 @@ const SEMANTIC_COLORS = [
16
18
  '--sv-color-text-muted',
17
19
  '--sv-color-text-subtle',
18
20
  '--sv-color-text-on-accent',
21
+ '--sv-color-text-on-light-fill',
22
+ '--sv-color-text-on-dark-fill',
19
23
  '--sv-color-border',
20
24
  '--sv-color-border-strong',
25
+ '--sv-color-shadow-strong',
21
26
  '--sv-color-accent',
22
27
  '--sv-color-accent-hover',
23
28
  '--sv-color-accent-subtle',
@@ -99,6 +104,7 @@ const ICON_SIZE_TOKENS = [
99
104
  ];
100
105
 
101
106
  const FONT_SIZE_TOKENS = [
107
+ '--sv-font-size-2xs',
102
108
  '--sv-font-size-label',
103
109
  '--sv-font-size-xs',
104
110
  '--sv-font-size-caption',
@@ -107,8 +113,23 @@ const FONT_SIZE_TOKENS = [
107
113
  '--sv-font-size-lg',
108
114
  '--sv-font-size-xl',
109
115
  '--sv-font-size-2xl',
116
+ '--sv-font-size-3xl',
110
117
  ];
111
118
 
119
+ // Task 10.2 — in-app text-size control. --sv-text-size-scale is theme-stable
120
+ // like --sv-radius-scale, but unlike radius (a calc() chain resolved once at
121
+ // :root's declaration site — see the RADIUS_PRESETS comment below), rem units
122
+ // genuinely re-resolve against the root element's live computed font-size, so
123
+ // these swatches compute the result directly in JS rather than needing a real
124
+ // [data-text-size] toggle on the document root.
125
+ const TEXT_SIZE_PRESETS = [
126
+ { preset: 'default', scale: 1 },
127
+ { preset: 'large', scale: 1.125 },
128
+ { preset: 'larger', scale: 1.25 },
129
+ ] as const;
130
+
131
+ const TEXT_SIZE_BASE_PX = { md: 16, lg: 18, '2xl': 24 };
132
+
112
133
  const FONT_WEIGHT_TOKENS = [
113
134
  '--sv-font-weight-normal',
114
135
  '--sv-font-weight-medium',
@@ -124,6 +145,10 @@ const SHADOW_TOKENS = [
124
145
  '--sv-shadow-control',
125
146
  ];
126
147
 
148
+ // RFC 0094 — theme-stable like the other scale tokens; a theme preset can
149
+ // override it (e.g. neobrutalism's thicker outline).
150
+ const BORDER_WIDTH_TOKENS = ['--sv-border-width-hairline'];
151
+
127
152
  // Realistic duration+easing pairings, not every combination — matches how
128
153
  // Dialog/Drawer actually use these tokens (see motion.ts).
129
154
  const MOTION_PAIRS: { durationToken: string; easeToken: string; use: string }[] = [
@@ -247,6 +272,44 @@ function RadiusScaleRow({ token }: { token: string }) {
247
272
  );
248
273
  }
249
274
 
275
+ function TextSizePresetRow({ preset, scale }: { preset: string; scale: number }) {
276
+ return (
277
+ <div style={row}>
278
+ <div style={{ display: 'flex', gap: 12, alignItems: 'baseline', flexShrink: 0, width: 180 }}>
279
+ <span
280
+ style={{
281
+ fontSize: `${scale * TEXT_SIZE_BASE_PX.md}px`,
282
+ color: 'var(--sv-color-text-primary)',
283
+ }}
284
+ title="body (--sv-font-size-md)"
285
+ >
286
+ Aa
287
+ </span>
288
+ <span
289
+ style={{
290
+ fontSize: `${scale * TEXT_SIZE_BASE_PX.lg}px`,
291
+ color: 'var(--sv-color-text-primary)',
292
+ }}
293
+ title="large (--sv-font-size-lg)"
294
+ >
295
+ Aa
296
+ </span>
297
+ <span
298
+ style={{
299
+ fontSize: `${scale * TEXT_SIZE_BASE_PX['2xl']}px`,
300
+ color: 'var(--sv-color-text-primary)',
301
+ }}
302
+ title="heading (--sv-font-size-2xl)"
303
+ >
304
+ Aa
305
+ </span>
306
+ </div>
307
+ <span style={label}>{preset}</span>
308
+ <span style={value}>scale {scale}</span>
309
+ </div>
310
+ );
311
+ }
312
+
250
313
  function RadiusPresetRow({ preset, scale }: { preset: string; scale: number }) {
251
314
  return (
252
315
  <div style={row}>
@@ -446,6 +509,19 @@ function TokenGalleryComponent() {
446
509
  ))}
447
510
  </Section>
448
511
 
512
+ <Section title="Text-size presets (task 10.2)">
513
+ <p style={{ color: 'var(--sv-color-text-muted)', fontSize: 13, marginBottom: 12 }}>
514
+ Account → Preferences → Appearance. Overrides <code>--sv-text-size-scale</code> via{' '}
515
+ <code>[data-text-size]</code> on <code>&lt;html&gt;</code> — every rem-based token above
516
+ (font sizes, but also spacing and radii) scales with it, since the root element&apos;s
517
+ font-size is what rem resolves against. Discharges the accessibility debt from disabling
518
+ pinch-zoom (<code>runtime/app/layout.tsx</code>).
519
+ </p>
520
+ {TEXT_SIZE_PRESETS.map((p) => (
521
+ <TextSizePresetRow key={p.preset} preset={p.preset} scale={p.scale} />
522
+ ))}
523
+ </Section>
524
+
449
525
  <Section title="Radius scale">
450
526
  {RADIUS_TOKENS.map((t) => (
451
527
  <RadiusScaleRow key={t} token={t} />
@@ -465,6 +541,45 @@ function TokenGalleryComponent() {
465
541
  ))}
466
542
  </Section>
467
543
 
544
+ <Section title="Border width">
545
+ {BORDER_WIDTH_TOKENS.map((t) => (
546
+ <ScaleRow
547
+ key={t}
548
+ token={t}
549
+ renderPreview={(_v) => (
550
+ <div
551
+ style={{
552
+ width: 48,
553
+ height: 28,
554
+ background: 'var(--sv-color-surface-raised)',
555
+ border: `var(${t}) solid var(--sv-color-border-strong)`,
556
+ flexShrink: 0,
557
+ }}
558
+ />
559
+ )}
560
+ />
561
+ ))}
562
+ </Section>
563
+
564
+ <Section title="Theme presets (RFC 0094)">
565
+ <p style={{ color: 'var(--sv-color-text-muted)', fontSize: 13, marginBottom: 12 }}>
566
+ A closed, built-in set of full-identity presets — border width, shadow shape, corner
567
+ radius, and the <code>Button</code> press interaction (<code>--sv-button-shadow</code>,{' '}
568
+ <code>--sv-button-press-x/-y</code>) all move together. Use the{' '}
569
+ <strong>Theme preset</strong> toolbar above (next to Radius) to switch — these are live
570
+ components, not a static swatch, so they respond immediately.
571
+ </p>
572
+ <div style={{ display: 'flex', gap: 16, alignItems: 'flex-start', flexWrap: 'wrap' }}>
573
+ <Button variant="primary">Primary button</Button>
574
+ <Button variant="secondary">Secondary button</Button>
575
+ <Card padding="md" style={{ width: 220 }}>
576
+ <p style={{ margin: 0, color: 'var(--sv-color-text-primary)', fontSize: 14 }}>
577
+ Card content
578
+ </p>
579
+ </Card>
580
+ </div>
581
+ </Section>
582
+
468
583
  <Section title="Icon sizes">
469
584
  {ICON_SIZE_TOKENS.map((t) => (
470
585
  <ScaleRow
@@ -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
+ }
@@ -0,0 +1,72 @@
1
+ /**
2
+ * Sovereign Design System — theme presets (RFC 0094).
3
+ *
4
+ * The single source of truth for every built-in theme preset's token
5
+ * overrides. Deliberately plain data (no CSS-in-JS, no build dependency) so
6
+ * it can be consumed two ways with zero duplication:
7
+ * - `scripts/generate-theme-presets-css.ts` (`pnpm generate:theme-presets`)
8
+ * reads this file and emits `theme-presets.css` for Storybook/standalone
9
+ * consumers.
10
+ * - `runtime/src/instance-style.ts` (RFC 0095) imports `THEME_PRESETS`
11
+ * directly and flattens the selected preset into the existing
12
+ * `:root { ... }` instance-style injection — no CSS parsing, no second
13
+ * copy of these values to drift out of sync.
14
+ *
15
+ * Presets are closed and built-in (RFC 0094's "Alternatives considered") —
16
+ * add a new one here and re-run the generator, never accept arbitrary
17
+ * operator-supplied values into this shape.
18
+ *
19
+ * `'default'` MUST stay `{ light: {}, dark: {} }` — an empty override set is
20
+ * what guarantees today's look stays byte-identical for every instance that
21
+ * never opts into a different preset.
22
+ */
23
+
24
+ export type ThemePresetName = 'default' | 'neobrutalism';
25
+
26
+ export const THEME_PRESET_NAMES: readonly ThemePresetName[] = ['default', 'neobrutalism'];
27
+
28
+ export interface ThemePresetTokens {
29
+ /** Sparse — only tokens this preset overrides. Everything else falls through to the default. */
30
+ light: Record<string, string>;
31
+ dark: Record<string, string>;
32
+ }
33
+
34
+ export const THEME_PRESETS: Record<ThemePresetName, ThemePresetTokens> = {
35
+ default: { light: {}, dark: {} },
36
+ neobrutalism: {
37
+ light: {
38
+ // --sv-radius-scale is a calc()-chain root: --sv-radius-sm..-3xl are
39
+ // all `calc(var(--sv-radius-scale) * ...)`, declared once at :root
40
+ // (primitives.css). This ONLY cascades correctly if the selector this
41
+ // override lands under matches :root itself (<html>) — a scoped
42
+ // descendant selector would set --sv-radius-scale there, but the
43
+ // dependent --sv-radius-* tokens were never re-declared at that node,
44
+ // so they'd keep inheriting their :root-resolved value unchanged. See
45
+ // the `withRadius` decorator comment in `.storybook/preview.ts` for
46
+ // the same constraint, discovered independently while building RFC
47
+ // 0077's Token Gallery preset demo.
48
+ '--sv-radius-scale': '0',
49
+ '--sv-border-width-hairline': '2px',
50
+ '--sv-shadow-card': '4px 4px 0 0 var(--sv-color-shadow-strong)',
51
+ '--sv-shadow-hover': '6px 6px 0 0 var(--sv-color-shadow-strong)',
52
+ '--sv-shadow-popover': '4px 4px 0 0 var(--sv-color-shadow-strong)',
53
+ '--sv-shadow-overlay': '8px 8px 0 0 var(--sv-color-shadow-strong)',
54
+ '--sv-shadow-control': '2px 2px 0 0 var(--sv-color-shadow-strong)',
55
+ '--sv-button-shadow': '4px 4px 0 0 var(--sv-color-shadow-strong)',
56
+ '--sv-button-press-x': '4px',
57
+ '--sv-button-press-y': '4px',
58
+ },
59
+ dark: {
60
+ '--sv-radius-scale': '0',
61
+ '--sv-border-width-hairline': '2px',
62
+ '--sv-shadow-card': '4px 4px 0 0 var(--sv-color-shadow-strong)',
63
+ '--sv-shadow-hover': '6px 6px 0 0 var(--sv-color-shadow-strong)',
64
+ '--sv-shadow-popover': '4px 4px 0 0 var(--sv-color-shadow-strong)',
65
+ '--sv-shadow-overlay': '8px 8px 0 0 var(--sv-color-shadow-strong)',
66
+ '--sv-shadow-control': '2px 2px 0 0 var(--sv-color-shadow-strong)',
67
+ '--sv-button-shadow': '4px 4px 0 0 var(--sv-color-shadow-strong)',
68
+ '--sv-button-press-x': '4px',
69
+ '--sv-button-press-y': '4px',
70
+ },
71
+ },
72
+ };