@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,161 @@
1
+ // @vitest-environment jsdom
2
+ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
3
+ import { cleanup, render, screen } from '@testing-library/react';
4
+ import { RootLayout } from '../RootLayout';
5
+
6
+ let matches = false;
7
+
8
+ function installMatchMedia() {
9
+ vi.stubGlobal(
10
+ 'matchMedia',
11
+ vi.fn().mockImplementation((query: string) => ({
12
+ matches,
13
+ media: query,
14
+ addEventListener: vi.fn(),
15
+ removeEventListener: vi.fn(),
16
+ addListener: vi.fn(),
17
+ removeListener: vi.fn(),
18
+ dispatchEvent: vi.fn(),
19
+ })),
20
+ );
21
+ }
22
+
23
+ describe('RootLayout', () => {
24
+ beforeEach(() => {
25
+ matches = false;
26
+ installMatchMedia();
27
+ });
28
+ afterEach(() => {
29
+ vi.unstubAllGlobals();
30
+ cleanup();
31
+ });
32
+
33
+ it('plain: renders the single child on web and mobile', () => {
34
+ render(
35
+ <RootLayout variant="plain">
36
+ <span>Main</span>
37
+ </RootLayout>,
38
+ );
39
+ expect(screen.getByText('Main')).toBeDefined();
40
+ cleanup();
41
+
42
+ matches = true;
43
+ installMatchMedia();
44
+ render(
45
+ <RootLayout variant="plain">
46
+ <span>Main</span>
47
+ </RootLayout>,
48
+ );
49
+ expect(screen.getByText('Main')).toBeDefined();
50
+ });
51
+
52
+ it('sidebar: renders sidebar + main on web, main only on mobile', () => {
53
+ render(
54
+ <RootLayout variant="sidebar">
55
+ <nav>Sidebar</nav>
56
+ <main>Main</main>
57
+ </RootLayout>,
58
+ );
59
+ expect(screen.getByText('Sidebar')).toBeDefined();
60
+ expect(screen.getByText('Main')).toBeDefined();
61
+ cleanup();
62
+
63
+ matches = true;
64
+ installMatchMedia();
65
+ render(
66
+ <RootLayout variant="sidebar">
67
+ <nav>Sidebar</nav>
68
+ <main>Main</main>
69
+ </RootLayout>,
70
+ );
71
+ expect(screen.queryByText('Sidebar')).toBeNull();
72
+ expect(screen.getByText('Main')).toBeDefined();
73
+ });
74
+
75
+ it('header: renders header + main identically on web and mobile', () => {
76
+ render(
77
+ <RootLayout variant="header">
78
+ <header>Header</header>
79
+ <main>Main</main>
80
+ </RootLayout>,
81
+ );
82
+ expect(screen.getByText('Header')).toBeDefined();
83
+ expect(screen.getByText('Main')).toBeDefined();
84
+ cleanup();
85
+
86
+ matches = true;
87
+ installMatchMedia();
88
+ render(
89
+ <RootLayout variant="header">
90
+ <header>Header</header>
91
+ <main>Main</main>
92
+ </RootLayout>,
93
+ );
94
+ expect(screen.getByText('Header')).toBeDefined();
95
+ expect(screen.getByText('Main')).toBeDefined();
96
+ });
97
+
98
+ it('header: applies headerHeight on web and mobileHeaderHeight on mobile', () => {
99
+ render(
100
+ <RootLayout variant="header">
101
+ <header>Header</header>
102
+ <main>Main</main>
103
+ </RootLayout>,
104
+ );
105
+ expect(screen.getByText('Header').parentElement?.style.height).toBe('48px');
106
+ cleanup();
107
+
108
+ matches = true;
109
+ installMatchMedia();
110
+ render(
111
+ <RootLayout variant="header">
112
+ <header>Header</header>
113
+ <main>Main</main>
114
+ </RootLayout>,
115
+ );
116
+ expect(screen.getByText('Header').parentElement?.style.height).toBe('60px');
117
+ });
118
+
119
+ it('shell: renders main only on web, header + main + footer on mobile', () => {
120
+ render(
121
+ <RootLayout variant="shell">
122
+ <header>Header</header>
123
+ <main>Main</main>
124
+ <footer>Footer</footer>
125
+ </RootLayout>,
126
+ );
127
+ expect(screen.queryByText('Header')).toBeNull();
128
+ expect(screen.getByText('Main')).toBeDefined();
129
+ expect(screen.queryByText('Footer')).toBeNull();
130
+ cleanup();
131
+
132
+ matches = true;
133
+ installMatchMedia();
134
+ render(
135
+ <RootLayout variant="shell">
136
+ <header>Header</header>
137
+ <main>Main</main>
138
+ <footer>Footer</footer>
139
+ </RootLayout>,
140
+ );
141
+ expect(screen.getByText('Header')).toBeDefined();
142
+ expect(screen.getByText('Main')).toBeDefined();
143
+ expect(screen.getByText('Footer')).toBeDefined();
144
+ });
145
+
146
+ it('warns in development when the child count does not match the variant', () => {
147
+ const warn = vi.spyOn(console, 'warn').mockImplementation(() => undefined);
148
+
149
+ render(
150
+ <RootLayout variant="sidebar">
151
+ <span>Only one child</span>
152
+ </RootLayout>,
153
+ );
154
+
155
+ expect(warn).toHaveBeenCalledWith(expect.stringContaining('variant="sidebar"'));
156
+ expect(warn).toHaveBeenCalledWith(expect.stringContaining('expects 2'));
157
+ expect(warn).toHaveBeenCalledWith(expect.stringContaining('received 1'));
158
+
159
+ warn.mockRestore();
160
+ });
161
+ });
@@ -2,7 +2,7 @@
2
2
  display: inline-flex;
3
3
  background: var(--sv-color-surface-sunken);
4
4
  border-radius: var(--sv-radius-full);
5
- border: 1px solid var(--sv-color-border);
5
+ border: var(--sv-border-width-hairline) solid var(--sv-color-border);
6
6
  padding: 2px;
7
7
  gap: 2px;
8
8
  }
@@ -18,7 +18,7 @@
18
18
  line-height: 1.5;
19
19
  color: var(--sv-color-text-primary);
20
20
  background-color: var(--sv-color-surface);
21
- border: 1px solid var(--sv-color-border-strong);
21
+ border: var(--sv-border-width-hairline) solid var(--sv-color-border-strong);
22
22
  border-radius: var(--sv-radius-md);
23
23
  cursor: pointer;
24
24
  /* Remove native OS chevron */
@@ -1,6 +1,9 @@
1
1
  /* Sheet — fills the same rect as a plugin's content area (between the shell's
2
2
  * sticky header and footer nav — see --sv-shell-header-height /
3
- * --sv-shell-footer-height, set by the shell), sliding in from an edge
3
+ * --sv-shell-footer-height, set by the platform shell or, for a plugin that
4
+ * self-renders MobileHeader/MobileFooter instead (shellConfig.mobileHeader/
5
+ * mobileFooter: false), by that component itself — see
6
+ * usePublishShellChromeHeight), sliding in from an edge
4
7
  * instead of Dialog's centered box or Drawer's partial-height sheet-over-
5
8
  * scrim. z-index matches Dialog/Drawer's scrim so it layers consistently
6
9
  * with any other overlay. No scrim of its own — see the component doc
@@ -1,8 +1,13 @@
1
1
  'use client';
2
2
 
3
- import { type ReactNode, useEffect, useRef } from 'react';
4
- import { lockBodyScroll, unlockBodyScroll } from '../../scroll-lock';
3
+ import { type ReactNode, useRef } from 'react';
5
4
  import { useMountTransition, usePrefersReducedMotion } from '../../motion';
5
+ import {
6
+ OVERLAY_MOTION_DURATION_MS,
7
+ useOverlayFocusCapture,
8
+ useOverlayKeyboardTrap,
9
+ useOverlayScrollLock,
10
+ } from '../../overlay-shell';
6
11
  import { OverlayHeader } from '../OverlayHeader/OverlayHeader';
7
12
  import styles from './Sheet.module.css';
8
13
 
@@ -36,13 +41,6 @@ export interface SheetProps {
36
41
  children: ReactNode;
37
42
  }
38
43
 
39
- const FOCUSABLE =
40
- 'a[href], button:not([disabled]), textarea:not([disabled]), input:not([disabled]), select:not([disabled]), [tabindex]:not([tabindex="-1"])';
41
-
42
- // Matches --sv-motion-duration-base (Sheet.module.css) — see Dialog.tsx's
43
- // identical constant for why this stays a plain JS number.
44
- const MOTION_DURATION_MS = 250;
45
-
46
44
  /**
47
45
  * Sheet — a full-page overlay that replaces a plugin's content area between
48
46
  * the shell's fixed header and footer (RFC 0013's mobile chrome stays visible
@@ -71,55 +69,15 @@ export function Sheet({
71
69
  children,
72
70
  }: SheetProps) {
73
71
  const panelRef = useRef<HTMLDivElement>(null);
74
- const previouslyFocused = useRef<HTMLElement | null>(null);
75
72
  const reducedMotion = usePrefersReducedMotion();
76
- const { mounted, phase } = useMountTransition(open, reducedMotion ? 0 : MOTION_DURATION_MS);
77
-
78
- useEffect(() => {
79
- if (!mounted) return;
80
- lockBodyScroll();
81
- return unlockBodyScroll;
82
- }, [mounted]);
83
-
84
- useEffect(() => {
85
- if (!mounted) return;
86
- previouslyFocused.current = document.activeElement as HTMLElement | null;
87
- const panel = panelRef.current;
88
- const first = panel?.querySelector<HTMLElement>(FOCUSABLE);
89
- (first ?? panel)?.focus();
90
- return () => previouslyFocused.current?.focus();
91
- }, [mounted]);
73
+ const { mounted, phase } = useMountTransition(
74
+ open,
75
+ reducedMotion ? 0 : OVERLAY_MOTION_DURATION_MS,
76
+ );
92
77
 
93
- useEffect(() => {
94
- if (!open) return;
95
- function handleKeyDown(e: KeyboardEvent) {
96
- if (e.key === 'Escape') {
97
- e.stopPropagation();
98
- onClose();
99
- return;
100
- }
101
- if (e.key !== 'Tab') return;
102
- const panel = panelRef.current;
103
- if (!panel) return;
104
- const focusable = [...panel.querySelectorAll<HTMLElement>(FOCUSABLE)];
105
- const first = focusable[0];
106
- const last = focusable.at(-1);
107
- if (!first || !last) {
108
- e.preventDefault();
109
- return;
110
- }
111
- const active = document.activeElement;
112
- if (e.shiftKey && active === first) {
113
- e.preventDefault();
114
- last.focus();
115
- } else if (!e.shiftKey && active === last) {
116
- e.preventDefault();
117
- first.focus();
118
- }
119
- }
120
- document.addEventListener('keydown', handleKeyDown);
121
- return () => document.removeEventListener('keydown', handleKeyDown);
122
- }, [open, onClose]);
78
+ useOverlayScrollLock(mounted);
79
+ useOverlayFocusCapture(panelRef, mounted);
80
+ useOverlayKeyboardTrap(panelRef, open, onClose);
123
81
 
124
82
  if (!mounted) return null;
125
83
 
@@ -5,7 +5,7 @@
5
5
  minmax(0, calc(100% - var(--split-pane-primary-size, 50%)));
6
6
  width: 100%;
7
7
  min-height: 320px;
8
- border: 1px solid var(--sv-color-border);
8
+ border: var(--sv-border-width-hairline) solid var(--sv-color-border);
9
9
  border-radius: var(--sv-radius-lg);
10
10
  overflow: hidden;
11
11
  background: var(--sv-color-surface);
@@ -25,7 +25,7 @@
25
25
  }
26
26
 
27
27
  .pane + .pane {
28
- border-left: 1px solid var(--sv-color-border);
28
+ border-left: var(--sv-border-width-hairline) solid var(--sv-color-border);
29
29
  }
30
30
 
31
31
  .handle {
@@ -34,7 +34,7 @@
34
34
  min-width: 12px;
35
35
  padding: 0;
36
36
  border: 0;
37
- border-inline: 1px solid var(--sv-color-border);
37
+ border-inline: var(--sv-border-width-hairline) solid var(--sv-color-border);
38
38
  cursor: col-resize;
39
39
  background: var(--sv-color-surface-sunken);
40
40
  }
@@ -79,7 +79,7 @@
79
79
  }
80
80
 
81
81
  .pane + .pane {
82
- border-top: 1px solid var(--sv-color-border);
82
+ border-top: var(--sv-border-width-hairline) solid var(--sv-color-border);
83
83
  border-left: 0;
84
84
  }
85
85
  }
@@ -5,7 +5,7 @@
5
5
  min-height: 24px;
6
6
  gap: var(--sv-space-1);
7
7
  padding: 2px var(--sv-space-2);
8
- border: 1px solid var(--sv-color-border);
8
+ border: var(--sv-border-width-hairline) solid var(--sv-color-border);
9
9
  border-radius: var(--sv-radius-full);
10
10
  font-family: var(--sv-font-family);
11
11
  font-size: var(--sv-font-size-xs);
@@ -0,0 +1,53 @@
1
+ .strip {
2
+ display: flex;
3
+ align-items: stretch;
4
+ overflow-x: auto;
5
+ }
6
+
7
+ .step {
8
+ display: flex;
9
+ align-items: center;
10
+ flex-shrink: 0;
11
+ }
12
+
13
+ .connector {
14
+ width: var(--sv-space-4);
15
+ height: 2px;
16
+ flex-shrink: 0;
17
+ background: var(--sv-color-border);
18
+ }
19
+
20
+ .connectorDashed {
21
+ background-image: repeating-linear-gradient(
22
+ to right,
23
+ var(--sv-color-border) 0,
24
+ var(--sv-color-border) 4px,
25
+ transparent 4px,
26
+ transparent 7px
27
+ );
28
+ }
29
+
30
+ .addChip {
31
+ all: unset;
32
+ box-sizing: border-box;
33
+ flex-shrink: 0;
34
+ display: flex;
35
+ align-items: center;
36
+ gap: var(--sv-space-1);
37
+ padding: var(--sv-space-3) var(--sv-space-4);
38
+ border: var(--sv-border-width-hairline) dashed var(--sv-color-border);
39
+ border-radius: var(--sv-radius-lg);
40
+ color: var(--sv-color-text-muted);
41
+ font-size: var(--sv-font-size-sm);
42
+ cursor: pointer;
43
+ }
44
+
45
+ .addChip:hover {
46
+ background: var(--sv-color-surface-sunken);
47
+ color: var(--sv-color-text-primary);
48
+ }
49
+
50
+ .addChip:focus-visible {
51
+ outline: 2px solid var(--sv-color-focus-ring);
52
+ outline-offset: 2px;
53
+ }
@@ -0,0 +1,74 @@
1
+ import type { ReactNode } from 'react';
2
+ import { Icon } from '../Icon/Icon';
3
+ import styles from './StepStrip.module.css';
4
+
5
+ export interface StepStripItem {
6
+ id: string;
7
+ }
8
+
9
+ export interface StepStripProps<T extends StepStripItem> {
10
+ items: T[];
11
+ activeId: string | null;
12
+ /** Full control over each chip's content, click handling, and (if needed) drag-reorder wiring. */
13
+ renderItem: (item: T, state: { isActive: boolean; index: number }) => ReactNode;
14
+ /** Trailing dashed "add" chip — omit to render the strip with no add affordance. */
15
+ onAdd?: () => void;
16
+ addLabel?: string;
17
+ className?: string;
18
+ 'aria-label'?: string;
19
+ }
20
+
21
+ /**
22
+ * StepStrip — a horizontal, connected sequence of steps (a route or
23
+ * itinerary strip): ordered chips joined by a thin connector line, with an
24
+ * optional trailing dashed "add" chip. Purely presentational — it owns
25
+ * layout and the connector lines, nothing else.
26
+ *
27
+ * Each chip's content, active styling, and click handling are entirely up
28
+ * to `renderItem`; if a consumer needs drag-reorder, `renderItem` attaches
29
+ * its own dnd-kit `useSortable` ref/listeners to whatever it returns. This
30
+ * mirrors `DragHandleRow`'s existing split (DS owns chrome, consumer wires
31
+ * dnd-kit via passed-through props) generalized for a horizontal,
32
+ * handle-less strip where the whole rendered item — not a separate handle
33
+ * icon — is both the click target and the drag surface.
34
+ */
35
+ export function StepStrip<T extends StepStripItem>({
36
+ items,
37
+ activeId,
38
+ renderItem,
39
+ onAdd,
40
+ addLabel = 'Add',
41
+ className,
42
+ 'aria-label': ariaLabel,
43
+ }: StepStripProps<T>) {
44
+ return (
45
+ <div
46
+ className={[styles.strip, className].filter(Boolean).join(' ')}
47
+ role="list"
48
+ aria-label={ariaLabel}
49
+ >
50
+ {items.map((item, index) => (
51
+ <div key={item.id} className={styles.step} role="listitem">
52
+ {renderItem(item, { isActive: item.id === activeId, index })}
53
+ {(index < items.length - 1 || onAdd) && (
54
+ <div
55
+ className={[
56
+ styles.connector,
57
+ index === items.length - 1 ? styles.connectorDashed : '',
58
+ ]
59
+ .filter(Boolean)
60
+ .join(' ')}
61
+ aria-hidden="true"
62
+ />
63
+ )}
64
+ </div>
65
+ ))}
66
+ {onAdd && (
67
+ <button type="button" className={styles.addChip} onClick={onAdd}>
68
+ <Icon name="plus" size="sm" aria-hidden={true} />
69
+ {addLabel}
70
+ </button>
71
+ )}
72
+ </div>
73
+ );
74
+ }
@@ -7,7 +7,7 @@
7
7
  line-height: 1.5;
8
8
  color: var(--sv-color-text-primary);
9
9
  background-color: var(--sv-color-surface);
10
- border: 1px solid var(--sv-color-border-strong);
10
+ border: var(--sv-border-width-hairline) solid var(--sv-color-border-strong);
11
11
  border-radius: var(--sv-radius-md);
12
12
  transition:
13
13
  border-color 0.15s ease,
@@ -99,5 +99,5 @@
99
99
 
100
100
  .divider {
101
101
  margin: var(--sv-space-1) var(--sv-space-2);
102
- border-top: 1px solid var(--sv-color-border);
102
+ border-top: var(--sv-border-width-hairline) solid var(--sv-color-border);
103
103
  }
@@ -0,0 +1,64 @@
1
+ .wrap {
2
+ position: relative;
3
+ height: 100%;
4
+ width: 100%;
5
+ }
6
+
7
+ .scroller {
8
+ height: 100%;
9
+ width: 100%;
10
+ display: flex;
11
+ overflow-x: auto;
12
+ overflow-y: hidden;
13
+ scroll-snap-type: x mandatory;
14
+ /* Momentum + rubber-banding come from the browser's native scroll physics —
15
+ no hand-rolled pointer/touch dragging. Deliberately NOT
16
+ -webkit-overflow-scrolling: touch: that legacy iOS <13 hint promotes its
17
+ element to its own compositing layer for GPU-accelerated momentum
18
+ scroll, and that layer's paint order relative to siblings/fixed
19
+ descendants elsewhere in the tree isn't always reliable on real iOS
20
+ Safari (confirmed via sovereign-tasks' and sovereign-shopper's own
21
+ carousels, which hit and fixed this exact bug — a position:fixed
22
+ overlay opened above the scroller painted BEHIND it instead of above).
23
+ Removing it doesn't disable momentum scrolling on any iOS version this
24
+ platform realistically targets. */
25
+ }
26
+
27
+ .slide {
28
+ flex: 0 0 100%;
29
+ width: 100%;
30
+ height: 100%;
31
+ min-width: 0;
32
+ overflow: hidden;
33
+ scroll-snap-align: start;
34
+ scroll-snap-stop: always;
35
+ /* Slide content is a flex column so Header/Footer stay their natural size
36
+ and Body (flex: 1 1 auto, min-height: 0) fills and scrolls the rest —
37
+ see SwipableMobileCarouselSlideParts.module.css. */
38
+ display: flex;
39
+ flex-direction: column;
40
+ }
41
+
42
+ .indicatorSlot {
43
+ /* Overlays the bottom of .wrap rather than taking its own layout row —
44
+ matches both sovereign-tasks' and sovereign-shopper's existing dots
45
+ placement. Applied by the carousel itself around WHATEVER
46
+ renderIndicator returns (the default SwipableMobileCarouselDots or a
47
+ caller-supplied override) — a custom renderIndicator has no way to
48
+ import this module's locally-scoped class itself (CSS Modules classnames
49
+ aren't cross-file importable), so positioning must live here, not be
50
+ something each indicator implementation opts into. Previously this class
51
+ was only ever applied to the *default* indicator branch, which silently
52
+ left any custom renderIndicator unpositioned — a static, in-flow sibling
53
+ pushed below the full-height .scroller and clipped out of view instead
54
+ of overlaid on top of it. */
55
+ position: absolute;
56
+ left: 0;
57
+ right: 0;
58
+ bottom: var(--sv-space-3);
59
+ pointer-events: none;
60
+ }
61
+
62
+ .indicatorSlot > * {
63
+ pointer-events: auto;
64
+ }