@sovereignfs/ui 0.54.0 → 0.83.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (277) hide show
  1. package/dist/Accordion.module.css +1 -1
  2. package/dist/Alert.module.css +1 -1
  3. package/dist/AppsLauncher.module.css +96 -0
  4. package/dist/Avatar.module.css +1 -1
  5. package/dist/Badge.module.css +30 -7
  6. package/dist/BalanceChip.module.css +1 -1
  7. package/dist/Button.module.css +18 -2
  8. package/dist/Card.module.css +1 -1
  9. package/dist/CardTile.module.css +181 -0
  10. package/dist/CodeTextarea.module.css +1 -1
  11. package/dist/Collapsible.module.css +1 -1
  12. package/dist/ColorPicker.module.css +132 -0
  13. package/dist/Combobox.module.css +2 -2
  14. package/dist/Command.module.css +1 -1
  15. package/dist/ConfirmDialog.module.css +2 -2
  16. package/dist/ConsentPrompt.module.css +21 -0
  17. package/dist/DatePicker.module.css +1 -1
  18. package/dist/Dialog.module.css +130 -21
  19. package/dist/Drawer.module.css +36 -7
  20. package/dist/Header.module.css +62 -0
  21. package/dist/HeaderFooterLayout.module.css +21 -0
  22. package/dist/IconPicker.module.css +1 -1
  23. package/dist/Input.module.css +1 -1
  24. package/dist/Kbd.module.css +1 -1
  25. package/dist/Markdown.module.css +24 -0
  26. package/dist/Menu.module.css +1 -0
  27. package/dist/Message.module.css +1 -1
  28. package/dist/MessageScroller.module.css +12 -1
  29. package/dist/MobileAppsDrawer.module.css +10 -9
  30. package/dist/MobileFooter.module.css +1 -1
  31. package/dist/MobileHeader.module.css +14 -1
  32. package/dist/NavList.module.css +130 -0
  33. package/dist/NavTabs.module.css +1 -1
  34. package/dist/NotificationsPanel.module.css +258 -0
  35. package/dist/OverlayHeader.module.css +1 -1
  36. package/dist/PageContainer.module.css +62 -0
  37. package/dist/PageHeader.module.css +54 -0
  38. package/dist/PageLayout.module.css +51 -0
  39. package/dist/Popover.module.css +1 -1
  40. package/dist/QuantityStepper.module.css +1 -1
  41. package/dist/Resizable.module.css +3 -3
  42. package/dist/RootLayout.module.css +12 -0
  43. package/dist/SegmentedControl.module.css +1 -1
  44. package/dist/Select.module.css +1 -1
  45. package/dist/Sheet.module.css +4 -1
  46. package/dist/SplitPane.module.css +4 -4
  47. package/dist/StatusBadge.module.css +1 -1
  48. package/dist/StepStrip.module.css +53 -0
  49. package/dist/SuggestionInput.module.css +2 -2
  50. package/dist/SwipableMobileCarousel.module.css +12 -3
  51. package/dist/SwipableMobileCarouselDots.module.css +15 -0
  52. package/dist/SwipeStack.module.css +101 -0
  53. package/dist/SystemBanner.module.css +10 -0
  54. package/dist/Table.module.css +2 -2
  55. package/dist/Tabs.module.css +1 -1
  56. package/dist/TagInput.module.css +2 -2
  57. package/dist/Textarea.module.css +9 -1
  58. package/dist/ThreeColumnLayout.module.css +35 -0
  59. package/dist/Toast.module.css +62 -3
  60. package/dist/Tooltip.module.css +1 -1
  61. package/dist/UserMenu.module.css +90 -0
  62. package/dist/index.d.ts +1047 -45
  63. package/dist/index.js +3908 -1456
  64. package/dist/tokens/primitives.css +11 -0
  65. package/dist/tokens/semantic.css +32 -0
  66. package/dist/tokens/theme-presets.css +33 -0
  67. package/package.json +25 -9
  68. package/src/__tests__/package-json.test.ts +8 -0
  69. package/src/components/Accordion/Accordion.module.css +1 -1
  70. package/src/components/Alert/Alert.module.css +1 -1
  71. package/src/components/AppsLauncher/AppsLauncher.module.css +96 -0
  72. package/src/components/AppsLauncher/AppsLauncher.stories.tsx +58 -0
  73. package/src/components/AppsLauncher/AppsLauncher.tsx +117 -0
  74. package/src/components/AppsLauncher/__tests__/AppsLauncher.test.tsx +52 -0
  75. package/src/components/Avatar/Avatar.module.css +1 -1
  76. package/src/components/Badge/Badge.module.css +30 -7
  77. package/src/components/Badge/Badge.stories.tsx +16 -4
  78. package/src/components/Badge/Badge.tsx +7 -4
  79. package/src/components/Badge/__tests__/Badge.test.tsx +4 -2
  80. package/src/components/BalanceChip/BalanceChip.module.css +1 -1
  81. package/src/components/Button/Button.module.css +18 -2
  82. package/src/components/Card/Card.module.css +1 -1
  83. package/src/components/CardTile/CardTile.module.css +181 -0
  84. package/src/components/CardTile/CardTile.stories.tsx +86 -0
  85. package/src/components/CardTile/CardTile.tsx +128 -0
  86. package/src/components/CardTile/__tests__/CardTile.test.tsx +56 -0
  87. package/src/components/CodeTextarea/CodeTextarea.module.css +1 -1
  88. package/src/components/Collapsible/Collapsible.module.css +1 -1
  89. package/src/components/ColorPicker/ColorPicker.module.css +132 -0
  90. package/src/components/ColorPicker/ColorPicker.stories.tsx +80 -0
  91. package/src/components/ColorPicker/ColorPicker.tsx +157 -0
  92. package/src/components/ColorPicker/__tests__/ColorPicker.test.tsx +117 -0
  93. package/src/components/Combobox/Combobox.module.css +2 -2
  94. package/src/components/Command/Command.module.css +1 -1
  95. package/src/components/ConfirmDialog/ConfirmDialog.module.css +2 -2
  96. package/src/components/ConfirmDialog/ConfirmDialog.tsx +28 -2
  97. package/src/components/ConfirmDialog/__tests__/ConfirmDialog.test.tsx +71 -3
  98. package/src/components/ConsentPrompt/ConsentPrompt.module.css +21 -0
  99. package/src/components/ConsentPrompt/ConsentPrompt.stories.tsx +31 -0
  100. package/src/components/ConsentPrompt/ConsentPrompt.tsx +66 -0
  101. package/src/components/ContextMenu/ContextMenu.tsx +1 -1
  102. package/src/components/DatePicker/DatePicker.module.css +1 -1
  103. package/src/components/Dialog/Dialog.module.css +130 -21
  104. package/src/components/Dialog/Dialog.stories.tsx +179 -2
  105. package/src/components/Dialog/Dialog.tsx +129 -35
  106. package/src/components/Dialog/__tests__/Dialog.test.tsx +190 -9
  107. package/src/components/DragHandleRow/DragHandleRow.tsx +2 -21
  108. package/src/components/Drawer/Drawer.module.css +36 -7
  109. package/src/components/Drawer/Drawer.tsx +9 -10
  110. package/src/components/Drawer/__tests__/Drawer.test.tsx +33 -6
  111. package/src/components/GripIcon/GripIcon.tsx +27 -0
  112. package/src/components/Header/Header.module.css +62 -0
  113. package/src/components/Header/Header.stories.tsx +452 -0
  114. package/src/components/Header/Header.tsx +102 -0
  115. package/src/components/Header/__tests__/Header.test.tsx +104 -0
  116. package/src/components/HeaderFooterLayout/HeaderFooterLayout.module.css +21 -0
  117. package/src/components/HeaderFooterLayout/HeaderFooterLayout.stories.tsx +111 -0
  118. package/src/components/HeaderFooterLayout/HeaderFooterLayout.tsx +54 -0
  119. package/src/components/HeaderFooterLayout/__tests__/HeaderFooterLayout.test.tsx +58 -0
  120. package/src/components/Icon/icons/arrow-left-right.tsx +23 -0
  121. package/src/components/Icon/icons/bold.tsx +20 -0
  122. package/src/components/Icon/icons/calendar.tsx +4 -4
  123. package/src/components/Icon/icons/carrot.tsx +3 -3
  124. package/src/components/Icon/icons/circle-chevron-left.tsx +21 -0
  125. package/src/components/Icon/icons/code.tsx +21 -0
  126. package/src/components/Icon/icons/copy.tsx +21 -0
  127. package/src/components/Icon/icons/credit-card.tsx +21 -0
  128. package/src/components/Icon/icons/database.tsx +22 -0
  129. package/src/components/Icon/icons/download.tsx +22 -0
  130. package/src/components/Icon/icons/file-text.tsx +24 -0
  131. package/src/components/Icon/icons/folder-closed.tsx +21 -0
  132. package/src/components/Icon/icons/folder-open.tsx +20 -0
  133. package/src/components/Icon/icons/folder.tsx +20 -0
  134. package/src/components/Icon/icons/folders.tsx +21 -0
  135. package/src/components/Icon/icons/hat-glasses.tsx +24 -0
  136. package/src/components/Icon/icons/history.tsx +22 -0
  137. package/src/components/Icon/icons/inbox.tsx +21 -0
  138. package/src/components/Icon/icons/index.ts +161 -1
  139. package/src/components/Icon/icons/italic.tsx +22 -0
  140. package/src/components/Icon/icons/layers.tsx +22 -0
  141. package/src/components/Icon/icons/layout-dashboard.tsx +23 -0
  142. package/src/components/Icon/icons/layout-grid.tsx +23 -0
  143. package/src/components/Icon/icons/link.tsx +21 -0
  144. package/src/components/Icon/icons/list-ordered.tsx +25 -0
  145. package/src/components/Icon/icons/list.tsx +25 -0
  146. package/src/components/Icon/icons/luggage.tsx +24 -0
  147. package/src/components/Icon/icons/map-pin.tsx +21 -0
  148. package/src/components/Icon/icons/minus.tsx +20 -0
  149. package/src/components/Icon/icons/paint-bucket.tsx +23 -0
  150. package/src/components/Icon/icons/panel-left.tsx +21 -0
  151. package/src/components/Icon/icons/pin.tsx +21 -0
  152. package/src/components/Icon/icons/rectangle-ellipsis.tsx +23 -0
  153. package/src/components/Icon/icons/refresh-cw.tsx +23 -0
  154. package/src/components/Icon/icons/rotate-cw.tsx +21 -0
  155. package/src/components/Icon/icons/route.tsx +22 -0
  156. package/src/components/Icon/icons/send.tsx +21 -0
  157. package/src/components/Icon/icons/share-2.tsx +24 -0
  158. package/src/components/Icon/icons/sheet.tsx +24 -0
  159. package/src/components/Icon/icons/square-kanban.tsx +23 -0
  160. package/src/components/Icon/icons/table.tsx +23 -0
  161. package/src/components/Icon/icons/user-round-plus.tsx +23 -0
  162. package/src/components/Icon/icons/users.tsx +23 -0
  163. package/src/components/IconPicker/IconPicker.module.css +1 -1
  164. package/src/components/Input/Input.module.css +1 -1
  165. package/src/components/Input/Input.tsx +8 -2
  166. package/src/components/Kbd/Kbd.module.css +1 -1
  167. package/src/components/Markdown/Markdown.module.css +24 -0
  168. package/src/components/Markdown/Markdown.stories.tsx +10 -0
  169. package/src/components/Markdown/Markdown.tsx +166 -29
  170. package/src/components/Markdown/__tests__/Markdown.test.tsx +144 -0
  171. package/src/components/Menu/Menu.module.css +1 -0
  172. package/src/components/Menu/Menu.tsx +72 -22
  173. package/src/components/Menu/__tests__/Menu.test.tsx +30 -0
  174. package/src/components/Menubar/Menubar.tsx +1 -1
  175. package/src/components/Message/Message.module.css +1 -1
  176. package/src/components/MessageScroller/MessageScroller.module.css +12 -1
  177. package/src/components/MessageScroller/MessageScroller.tsx +12 -0
  178. package/src/components/MobileAppsDrawer/MobileAppsDrawer.module.css +10 -9
  179. package/src/components/MobileFooter/MobileFooter.module.css +1 -1
  180. package/src/components/MobileFooter/MobileFooter.tsx +15 -2
  181. package/src/components/MobileFooter/__tests__/MobileFooter.test.tsx +58 -0
  182. package/src/components/MobileHeader/MobileHeader.module.css +14 -1
  183. package/src/components/MobileHeader/MobileHeader.tsx +15 -2
  184. package/src/components/MobileHeader/__tests__/MobileHeader.test.tsx +56 -1
  185. package/src/components/NavList/NavList.module.css +130 -0
  186. package/src/components/NavList/NavList.tsx +136 -0
  187. package/src/components/NavList/__tests__/NavList.test.tsx +109 -0
  188. package/src/components/NavTabs/NavTabs.module.css +1 -1
  189. package/src/components/NotificationsPanel/NotificationsPanel.module.css +258 -0
  190. package/src/components/NotificationsPanel/NotificationsPanel.stories.tsx +61 -0
  191. package/src/components/NotificationsPanel/NotificationsPanel.tsx +187 -0
  192. package/src/components/NotificationsPanel/__tests__/NotificationsPanel.test.tsx +78 -0
  193. package/src/components/OverlayHeader/OverlayHeader.module.css +1 -1
  194. package/src/components/OverlayHeader/OverlayHeader.tsx +19 -4
  195. package/src/components/OverlayHeader/__tests__/OverlayHeader.test.tsx +18 -0
  196. package/src/components/PageContainer/PageContainer.module.css +62 -0
  197. package/src/components/PageContainer/PageContainer.tsx +37 -4
  198. package/src/components/PageContainer/__tests__/PageContainer.test.tsx +41 -8
  199. package/src/components/PageHeader/PageHeader.module.css +54 -0
  200. package/src/components/PageHeader/PageHeader.tsx +146 -5
  201. package/src/components/PageHeader/__tests__/PageHeader.test.tsx +103 -0
  202. package/src/components/PageLayout/PageLayout.module.css +51 -0
  203. package/src/components/PageLayout/PageLayout.stories.tsx +73 -0
  204. package/src/components/PageLayout/PageLayout.tsx +51 -0
  205. package/src/components/PageLayout/__tests__/PageLayout.test.tsx +34 -0
  206. package/src/components/Popover/Popover.module.css +1 -1
  207. package/src/components/Popover/Popover.tsx +86 -4
  208. package/src/components/Popover/__tests__/Popover.test.tsx +165 -0
  209. package/src/components/QuantityStepper/QuantityStepper.module.css +1 -1
  210. package/src/components/Resizable/Resizable.module.css +3 -3
  211. package/src/components/RootLayout/RootLayout.module.css +12 -0
  212. package/src/components/RootLayout/RootLayout.stories.tsx +125 -0
  213. package/src/components/RootLayout/RootLayout.tsx +185 -0
  214. package/src/components/RootLayout/__tests__/RootLayout.test.tsx +161 -0
  215. package/src/components/SegmentedControl/SegmentedControl.module.css +1 -1
  216. package/src/components/Select/Select.module.css +1 -1
  217. package/src/components/Sheet/Sheet.module.css +4 -1
  218. package/src/components/Sheet/Sheet.tsx +5 -5
  219. package/src/components/SplitPane/SplitPane.module.css +4 -4
  220. package/src/components/StatusBadge/StatusBadge.module.css +1 -1
  221. package/src/components/StepStrip/StepStrip.module.css +53 -0
  222. package/src/components/StepStrip/StepStrip.tsx +74 -0
  223. package/src/components/SuggestionInput/SuggestionInput.module.css +2 -2
  224. package/src/components/SwipableMobileCarousel/SwipableMobileCarousel.module.css +12 -3
  225. package/src/components/SwipableMobileCarousel/SwipableMobileCarousel.tsx +25 -9
  226. package/src/components/SwipableMobileCarousel/__tests__/SwipableMobileCarousel.test.tsx +81 -0
  227. package/src/components/SwipableMobileCarouselDots/SwipableMobileCarouselDots.module.css +15 -0
  228. package/src/components/SwipableMobileCarouselDots/SwipableMobileCarouselDots.tsx +12 -1
  229. package/src/components/SwipeStack/SwipeStack.module.css +101 -0
  230. package/src/components/SwipeStack/SwipeStack.tsx +199 -0
  231. package/src/components/SwipeStack/SwipeStackCard.tsx +22 -0
  232. package/src/components/SwipeStack/__tests__/SwipeStack.test.tsx +169 -0
  233. package/src/components/SystemBanner/SystemBanner.module.css +10 -0
  234. package/src/components/Table/Table.module.css +2 -2
  235. package/src/components/Tabs/Tabs.module.css +1 -1
  236. package/src/components/TagInput/TagInput.module.css +2 -2
  237. package/src/components/Textarea/Textarea.module.css +9 -1
  238. package/src/components/Textarea/Textarea.tsx +28 -5
  239. package/src/components/Textarea/__tests__/Textarea.test.tsx +23 -1
  240. package/src/components/ThreeColumnLayout/ThreeColumnLayout.module.css +35 -0
  241. package/src/components/ThreeColumnLayout/ThreeColumnLayout.stories.tsx +152 -0
  242. package/src/components/ThreeColumnLayout/ThreeColumnLayout.tsx +87 -0
  243. package/src/components/ThreeColumnLayout/__tests__/ThreeColumnLayout.test.tsx +78 -0
  244. package/src/components/Toast/Toast.module.css +62 -3
  245. package/src/components/Toast/Toast.stories.tsx +1 -1
  246. package/src/components/Tooltip/Tooltip.module.css +1 -1
  247. package/src/components/UserMenu/UserMenu.module.css +90 -0
  248. package/src/components/UserMenu/UserMenu.stories.tsx +49 -0
  249. package/src/components/UserMenu/UserMenu.tsx +132 -0
  250. package/src/components/UserMenu/__tests__/UserMenu.test.tsx +77 -0
  251. package/src/hooks/__tests__/usePublishShellChromeHeight.test.tsx +118 -0
  252. package/src/hooks/__tests__/useReorderSensors.test.ts +26 -0
  253. package/src/hooks/__tests__/useSnapCarousel.test.tsx +82 -0
  254. package/src/hooks/__tests__/useSwipeStack.test.tsx +155 -0
  255. package/src/hooks/index.ts +9 -0
  256. package/src/hooks/usePublishShellChromeHeight.ts +96 -0
  257. package/src/hooks/useReorderSensors.ts +88 -0
  258. package/src/hooks/useSnapCarousel.ts +211 -6
  259. package/src/hooks/useSwipeStack.ts +235 -0
  260. package/src/index.ts +61 -1
  261. package/src/overlay-shell.ts +65 -0
  262. package/src/stories/DesignSystemOverview.stories.tsx +617 -11
  263. package/src/stories/GripIcon.stories.tsx +29 -0
  264. package/src/stories/LayoutSystem.stories.tsx +639 -0
  265. package/src/stories/MenuEntries.stories.tsx +86 -0
  266. package/src/stories/NavList.stories.tsx +147 -0
  267. package/src/stories/PageContainer.stories.tsx +52 -7
  268. package/src/stories/PageHeader.stories.tsx +54 -1
  269. package/src/stories/StepStrip.stories.tsx +76 -0
  270. package/src/stories/SwipableMobileCarousel.stories.tsx +58 -0
  271. package/src/stories/SwipeStack.stories.tsx +200 -0
  272. package/src/stories/Textarea.stories.tsx +12 -0
  273. package/src/stories/TokenGallery.stories.tsx +50 -0
  274. package/src/tokens/primitives.css +11 -0
  275. package/src/tokens/semantic.css +32 -0
  276. package/src/tokens/theme-presets.css +33 -0
  277. package/src/tokens/theme-presets.ts +72 -0
@@ -1,6 +1,6 @@
1
1
  'use client';
2
2
 
3
- import { useEffect, useRef } from 'react';
3
+ import { useCallback, useEffect, useRef, useState } from 'react';
4
4
  import type { RefObject } from 'react';
5
5
 
6
6
  export interface UseSnapCarouselOptions {
@@ -14,6 +14,14 @@ export interface UseSnapCarouselOptions {
14
14
  * extracted from. Debounced rather than keyed to the `scrollend` event,
15
15
  * which pre-17.4 iOS Safari/WKWebView doesn't support. */
16
16
  debounceMs?: number;
17
+ /** The index to seed `liveIndex` with before any scroll event has fired —
18
+ * must match whatever the caller initially scrolls/renders to (typically
19
+ * the same value used for `SwipableMobileCarousel`'s `activeIndex`).
20
+ * Without this, `liveIndex` would start at 0 regardless of where the
21
+ * carousel actually opens (e.g. a deep link straight to slide 8),
22
+ * briefly unmounting every slide except the first few until the first
23
+ * scroll event arrives to correct it. */
24
+ initialIndex?: number;
17
25
  }
18
26
 
19
27
  export interface UseSnapCarouselResult {
@@ -21,6 +29,11 @@ export interface UseSnapCarouselResult {
21
29
  scrollRef: RefObject<HTMLDivElement | null>;
22
30
  /** Imperatively scrolls the container to a slide index. */
23
31
  scrollToIndex: (index: number, behavior?: ScrollBehavior) => void;
32
+ /** The slide index nearest the container's *current* scroll position,
33
+ * updated synchronously on every `scroll` event — not debounced, and not
34
+ * the same thing as the last-reported `onSettle` index. See its own
35
+ * assignment below for why a consumer needs both. */
36
+ liveIndex: number;
24
37
  }
25
38
 
26
39
  /**
@@ -35,21 +48,128 @@ export function useSnapCarousel({
35
48
  itemCount,
36
49
  onSettle,
37
50
  debounceMs = 120,
51
+ initialIndex = 0,
38
52
  }: UseSnapCarouselOptions): UseSnapCarouselResult {
39
53
  const scrollRef = useRef<HTMLDivElement>(null);
40
54
  const timerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
41
55
  const lastIndexRef = useRef<number | null>(null);
56
+ // Cleanup for scrollToIndex's scroll-snap-suspension workaround (see that
57
+ // function's own comment) — lets an overlapping call cancel a still-
58
+ // pending restore from an earlier one instead of two racing to re-enable
59
+ // snap at the wrong time.
60
+ const restoreSnapRef = useRef<(() => void) | null>(null);
61
+ // Real-time (synchronous, not debounced) read of "which slide is the
62
+ // container's scroll position nearest right now." A fast flick can carry
63
+ // native scroll-snap momentum straight past an intermediate slide to one
64
+ // two or more away in a single continuous gesture — the settle-detection
65
+ // above deliberately still waits out the full debounce window before
66
+ // reporting that via onSettle (so it can tell a real settle from drift,
67
+ // see chainTrustedRef's own comment). But `SwipableMobileCarousel`'s mount
68
+ // window is keyed off the *settled* index, so during that debounce window
69
+ // the slide the container had already scrolled to — visually, right now —
70
+ // was never in the mount window and had never been rendered at all: a
71
+ // real, reproduced-on-video gap where the carousel shows nothing (not even
72
+ // a loading skeleton) for the length of one full debounce cycle after a
73
+ // multi-slide flick. `liveIndex` exists so the mount window can track
74
+ // wherever the container currently visually is, independent of whether
75
+ // that position has been confirmed as a real settle yet.
76
+ const [liveIndex, setLiveIndex] = useState(initialIndex);
42
77
  // Kept in a ref so the scroll listener always calls the latest callback
43
78
  // without needing to be torn down and re-attached on every render.
44
79
  const onSettleRef = useRef(onSettle);
45
80
  onSettleRef.current = onSettle;
81
+ // Timestamp (Date.now()) of the most recent real input (touch, wheel/
82
+ // trackpad, or a mouse drag) — see the doc comment above `noteGesture`
83
+ // below for why settle-detection is gated on this instead of firing for
84
+ // any `scroll` event.
85
+ const lastGestureAtRef = useRef(0);
86
+ // Whether the *current* run of scroll events (since the last time the
87
+ // container went quiet for debounceMs) was kicked off by real input. Set
88
+ // at the start of each run by checking lastGestureAtRef, then held for the
89
+ // whole run regardless of how long it continues — see `handleScroll`'s own
90
+ // comment for why this can't just be "was there a gesture event within the
91
+ // last debounceMs" re-checked at settle time: native momentum/snap-
92
+ // correction after a real swipe can keep producing scroll events for
93
+ // longer than any fixed slack window without a single further touch event,
94
+ // and a first version of this fix that compared elapsed time against a
95
+ // fixed `debounceMs * 2` slack dropped exactly those settles — silently
96
+ // leaving activeIndex stuck on the old slide while the container had
97
+ // already visually scrolled to the new one, which is worse than the bug
98
+ // being fixed (the mount window then unmounts whatever's actually on
99
+ // screen, since it's keyed off the now-stale index — a blank, un-
100
+ // recoverable-without-reload carousel). `noteGesture` can also upgrade
101
+ // trust mid-run, for a real gesture that starts while an untrusted run's
102
+ // debounce timer is still pending.
103
+ const chainTrustedRef = useRef(false);
46
104
 
47
105
  useEffect(() => {
48
106
  const el = scrollRef.current;
49
107
  if (!el) return;
108
+ // A `scroll-snap-type: mandatory` container has no legitimate reason to
109
+ // move on its own — every real position change is either (a) driven by
110
+ // the finger/wheel/mouse still in contact, (b) the browser's own
111
+ // momentum/snap-correction settling immediately after release, or (c)
112
+ // our own scrollToIndex() call, which already updates lastIndexRef
113
+ // synchronously so settle-detection for it is a no-op regardless. A
114
+ // `scroll` event that arrives with none of the above recently active is
115
+ // drift, not a settle — most commonly a slide's own content changing
116
+ // size while it's still loading, which can nudge scrollLeft by a
117
+ // fraction of a slide width. Previously any such event that happened to
118
+ // round to a different index than the currently active one was treated
119
+ // as a genuine settle, silently "auto-swiping" the carousel back to
120
+ // whatever slide that rounding landed on — reproduced live via a user
121
+ // screen recording (swiping forward to a still-loading slide, then the
122
+ // carousel snapping back to the previous one ~150ms later with no
123
+ // further touch input) and confirmed via a unit test showing a second,
124
+ // unrelated `scroll` event alone was enough to trigger a second,
125
+ // contradicting `onSettle` call.
126
+ function noteGesture() {
127
+ lastGestureAtRef.current = Date.now();
128
+ // Upgrades the currently in-flight run too, not just the next one —
129
+ // covers a real gesture starting while an untrusted run's debounce
130
+ // timer (started by an earlier, unrelated drift scroll event) is
131
+ // still pending.
132
+ chainTrustedRef.current = true;
133
+ }
134
+ function handlePointerMove(event: PointerEvent) {
135
+ // Ignore hover — only a held button (mouse drag) counts as input.
136
+ if (event.buttons) noteGesture();
137
+ }
138
+ function updateLiveIndexNow() {
139
+ const current = scrollRef.current;
140
+ if (!current) return;
141
+ const width = current.clientWidth;
142
+ if (!width) return;
143
+ const idx = Math.max(0, Math.min(itemCount - 1, Math.round(current.scrollLeft / width)));
144
+ // Bails out (same reference, no re-render) when the index hasn't
145
+ // actually changed — a scroll container fires many `scroll` events
146
+ // while passing through the same slide's territory, not just at its
147
+ // boundaries. Synchronous rather than rAF-throttled: jsdom (this
148
+ // hook's own test environment) doesn't reliably run
149
+ // requestAnimationFrame callbacks under fake timers, and browsers
150
+ // already coalesce native scroll events to roughly once per frame on
151
+ // their own, so a second throttle here bought nothing but test
152
+ // fragility.
153
+ setLiveIndex((prev) => (prev === idx ? prev : idx));
154
+ }
50
155
  function handleScroll() {
51
- if (timerRef.current) clearTimeout(timerRef.current);
156
+ updateLiveIndexNow();
157
+ if (timerRef.current) {
158
+ clearTimeout(timerRef.current);
159
+ } else {
160
+ // No debounce timer pending means the container had been fully
161
+ // quiet — this scroll event starts a new run. Trust it only if real
162
+ // input happened recently; once granted, trust holds for the whole
163
+ // run (see chainTrustedRef's own doc comment above) no matter how
164
+ // long momentum/snap-correction keeps producing scroll events after
165
+ // this point with no further input.
166
+ chainTrustedRef.current = Date.now() - lastGestureAtRef.current <= debounceMs;
167
+ }
52
168
  timerRef.current = setTimeout(() => {
169
+ timerRef.current = null;
170
+ const trusted = chainTrustedRef.current;
171
+ chainTrustedRef.current = false;
172
+ if (!trusted) return;
53
173
  const current = scrollRef.current;
54
174
  if (!current) return;
55
175
  const width = current.clientWidth;
@@ -62,18 +182,103 @@ export function useSnapCarousel({
62
182
  }, debounceMs);
63
183
  }
64
184
  el.addEventListener('scroll', handleScroll, { passive: true });
185
+ el.addEventListener('touchstart', noteGesture, { passive: true });
186
+ el.addEventListener('touchmove', noteGesture, { passive: true });
187
+ el.addEventListener('wheel', noteGesture, { passive: true });
188
+ el.addEventListener('pointerdown', noteGesture);
189
+ el.addEventListener('pointermove', handlePointerMove);
65
190
  return () => {
66
191
  el.removeEventListener('scroll', handleScroll);
192
+ el.removeEventListener('touchstart', noteGesture);
193
+ el.removeEventListener('touchmove', noteGesture);
194
+ el.removeEventListener('wheel', noteGesture);
195
+ el.removeEventListener('pointerdown', noteGesture);
196
+ el.removeEventListener('pointermove', handlePointerMove);
67
197
  if (timerRef.current) clearTimeout(timerRef.current);
68
198
  };
69
199
  }, [itemCount, debounceMs]);
70
200
 
71
- function scrollToIndex(index: number, behavior: ScrollBehavior = 'smooth') {
201
+ // Memoized so its reference stays stable across renders — SwipableMobile
202
+ // Carousel depends on it in a useEffect deps array alongside
203
+ // clampedActiveIndex, and an unmemoized function recreated on every
204
+ // render (e.g. every time liveIndex itself changes below) would make that
205
+ // effect re-fire on every scroll, calling scrollToIndex again and — since
206
+ // it also resets liveIndex to whatever index it was told to scroll to —
207
+ // immediately stomping the very liveIndex update that triggered the
208
+ // re-render in the first place. Caught by this hook's own test suite
209
+ // (a live-index test that failed until this was memoized), not just
210
+ // reasoned about — worth keeping as a regression guard, not just a
211
+ // performance nicety.
212
+ const scrollToIndex = useCallback((index: number, behavior: ScrollBehavior = 'smooth') => {
72
213
  const el = scrollRef.current;
73
214
  if (!el) return;
74
215
  lastIndexRef.current = index;
75
- el.scrollTo({ left: index * el.clientWidth, behavior });
76
- }
216
+ // Belt-and-suspenders: a real scrollTo() also fires its own 'scroll'
217
+ // event, which updates liveIndex synchronously too — but jsdom's mocked
218
+ // scrollTo (this hook's own test environment) doesn't, and the initial
219
+ // mount scroll / reorder-jump fix both need the target slide mounted
220
+ // immediately regardless.
221
+ setLiveIndex(index);
222
+
223
+ // Cancel any still-pending restore from an earlier smooth scroll before
224
+ // starting this one, so they don't race to re-enable snap at the wrong
225
+ // time (e.g. a dot-jump fired while a previous jump's restore timer is
226
+ // still pending).
227
+ if (restoreSnapRef.current) {
228
+ restoreSnapRef.current();
229
+ restoreSnapRef.current = null;
230
+ }
231
+
232
+ const target = index * el.clientWidth;
233
+
234
+ if (behavior === 'smooth') {
235
+ // A programmatic `scrollTo({ behavior: 'smooth' })` on this element
236
+ // can silently do nothing at all — not a jank/timing issue, the
237
+ // container just never moves, with no error and no scroll events.
238
+ // Reproduced live via the design system's own footer/dot-jump
239
+ // navigation in sovereign-tasks (routes external activeIndex changes
240
+ // through this exact path): the identical call with
241
+ // `behavior: 'instant'` moved the container immediately, `'smooth'`
242
+ // did nothing — reproduced both with `scroll-snap-type: mandatory` in
243
+ // place *and*, isolating further, on a plain scroll-snap-free element
244
+ // in the same environment, so `scroll-snap-type` alone doesn't fully
245
+ // explain it. Two independent mitigations, since the exact trigger
246
+ // isn't nailed down: (1) suspend snap for the duration of the
247
+ // animated scroll on the chance it *is* a snap/animation conflict in
248
+ // some engines; (2) below, a correctness-net instant scrollTo if the
249
+ // container still isn't at the target once the animation should have
250
+ // finished — cheap and idempotent when the smooth scroll *did* work
251
+ // (already at the target, the check is a no-op), and the difference
252
+ // between a graceful animation and an abrupt correction if it didn't,
253
+ // rather than a carousel that silently desyncs forever with nothing
254
+ // to ever correct it.
255
+ // Narrowed, stable binding for the closures below — TS can't carry
256
+ // the `if (!el) return` narrowing of the outer `el` into a nested
257
+ // function declaration.
258
+ const scrollEl = el;
259
+ const prevSnapType = scrollEl.style.scrollSnapType;
260
+ scrollEl.style.scrollSnapType = 'none';
261
+ let settled = false;
262
+ // Matches typical browser smooth-scroll duration; also the fallback
263
+ // for engines with no `scrollend` (pre-17.4 iOS Safari/WKWebView).
264
+ const timeoutId = setTimeout(finish, 500);
265
+ function finish() {
266
+ if (settled) return;
267
+ settled = true;
268
+ clearTimeout(timeoutId);
269
+ scrollEl.removeEventListener('scrollend', finish);
270
+ scrollEl.style.scrollSnapType = prevSnapType;
271
+ restoreSnapRef.current = null;
272
+ if (Math.abs(scrollEl.scrollLeft - target) > 1) {
273
+ scrollEl.scrollTo({ left: target, behavior: 'instant' });
274
+ }
275
+ }
276
+ scrollEl.addEventListener('scrollend', finish, { once: true });
277
+ restoreSnapRef.current = finish;
278
+ }
279
+
280
+ el.scrollTo({ left: target, behavior });
281
+ }, []);
77
282
 
78
- return { scrollRef, scrollToIndex };
283
+ return { scrollRef, scrollToIndex, liveIndex };
79
284
  }
@@ -0,0 +1,235 @@
1
+ 'use client';
2
+
3
+ import { useRef } from 'react';
4
+ import type { PointerEvent as ReactPointerEvent, RefObject } from 'react';
5
+
6
+ export type SwipeDirection = 'left' | 'right' | 'up' | 'down';
7
+
8
+ /** Minimum effective drag distance (px) before a direction is read at all —
9
+ * keeps a tap or a tiny jitter from registering as a swipe. Matches
10
+ * useSwipeReveal's own deadzone. */
11
+ const DEADZONE_PX = 8;
12
+
13
+ /** Horizontal drag distance (px) that reaches the full tilt cap. */
14
+ const ROTATION_RANGE_PX = 240;
15
+ const ROTATION_MAX_DEG = 12;
16
+
17
+ /** Duration (ms) of both the commit fling-out and the spring-back-under-
18
+ * threshold transition. Exported so SwipeStack can hold a just-committed
19
+ * card in place for exactly this long before advancing to the next one. */
20
+ export const SWIPE_STACK_TRANSITION_MS = 220;
21
+
22
+ const ALL_DIRECTIONS: readonly SwipeDirection[] = ['left', 'right', 'up', 'down'];
23
+
24
+ /** Unit vector per direction — reused for both the fling-out offset and the
25
+ * tilt sign (horizontal directions tilt, vertical ones don't: x is ±1/0). */
26
+ const UNIT_VECTOR: Record<SwipeDirection, readonly [number, number]> = {
27
+ left: [-1, 0],
28
+ right: [1, 0],
29
+ up: [0, -1],
30
+ down: [0, 1],
31
+ };
32
+
33
+ export interface UseSwipeStackOptions {
34
+ /** Directions this drag can move toward and resolve to. A direction left
35
+ * out of this list is a wall, not just an unreachable outcome — the card
36
+ * will not visually move that way at all (see clampToLiveDirections). */
37
+ directions: readonly SwipeDirection[];
38
+ /** Effective distance (px) a drag must cross before release commits to
39
+ * that direction instead of springing back. No velocity component — a
40
+ * fast short flick under this distance still springs back. */
41
+ threshold?: number;
42
+ /** Fires once, synchronously, when a drag (or triggerCommit) resolves to
43
+ * a live direction past threshold. This hook has already started the
44
+ * fling-out animation on cardRef's element by the time this is called —
45
+ * the caller only needs to react to the outcome, not drive the motion. */
46
+ onCommit: (direction: SwipeDirection) => void;
47
+ /** Disables both the drag and triggerCommit — e.g. while there is no
48
+ * current card to act on. */
49
+ disabled?: boolean;
50
+ }
51
+
52
+ export interface UseSwipeStackHandlers {
53
+ onPointerDown: (e: ReactPointerEvent) => void;
54
+ onPointerMove: (e: ReactPointerEvent) => void;
55
+ onPointerUp: (e: ReactPointerEvent) => void;
56
+ onPointerCancel: (e: ReactPointerEvent) => void;
57
+ }
58
+
59
+ export interface UseSwipeStackResult {
60
+ /** Attach to the card element that should translate/rotate with the drag. */
61
+ cardRef: RefObject<HTMLDivElement | null>;
62
+ /** Attach to the ancestor of both the card and any direction-stamp
63
+ * overlays. The hook writes live drag progress here as CSS custom
64
+ * properties (--sv-stack-progress-left/right/up/down, each 0–1) so
65
+ * stamps — siblings of the card, not the card itself — can react without
66
+ * a re-render per pointermove. */
67
+ wrapRef: RefObject<HTMLDivElement | null>;
68
+ handlers: UseSwipeStackHandlers;
69
+ /** CSS touch-action for the card, derived from which directions are live
70
+ * — an axis with nothing configured on it stays native (pan-x/pan-y) so
71
+ * a page can still scroll through a stack that only uses the other axis;
72
+ * `none` only once both axes are actually needed. */
73
+ touchAction: string;
74
+ /** Programmatically commits a direction — the same fling animation and
75
+ * onCommit call a real drag past threshold would produce. Backs the
76
+ * non-gesture fallback buttons: they are not a separate code path, just
77
+ * a different way to call this. No-ops if the direction isn't live or
78
+ * the hook is disabled. */
79
+ triggerCommit: (direction: SwipeDirection) => void;
80
+ }
81
+
82
+ function clamp(value: number, min: number, max: number): number {
83
+ return Math.min(max, Math.max(min, value));
84
+ }
85
+
86
+ /** Zeroes out movement toward any direction not in `live` — the card
87
+ * behaves as if a wall exists there instead of just being an unreachable
88
+ * commit outcome, so e.g. a left/right-only stack never visually drifts
89
+ * vertically. */
90
+ function clampToLiveDirections(
91
+ dx: number,
92
+ dy: number,
93
+ live: ReadonlySet<SwipeDirection>,
94
+ ): [number, number] {
95
+ const x = dx > 0 ? (live.has('right') ? dx : 0) : live.has('left') ? dx : 0;
96
+ const y = dy > 0 ? (live.has('down') ? dy : 0) : live.has('up') ? dy : 0;
97
+ return [x, y];
98
+ }
99
+
100
+ function resolveDirection(
101
+ dx: number,
102
+ dy: number,
103
+ live: ReadonlySet<SwipeDirection>,
104
+ ): SwipeDirection | null {
105
+ const absX = Math.abs(dx);
106
+ const absY = Math.abs(dy);
107
+ if (absX < DEADZONE_PX && absY < DEADZONE_PX) return null;
108
+ const candidate: SwipeDirection =
109
+ absX > absY ? (dx > 0 ? 'right' : 'left') : dy > 0 ? 'down' : 'up';
110
+ return live.has(candidate) ? candidate : null;
111
+ }
112
+
113
+ /**
114
+ * useSwipeStack — hand-rolled 4-directional free drag, extending
115
+ * useSwipeReveal's pattern (raw Pointer Events, a deadzone, transform
116
+ * written straight to a ref, resolved only on release) from one axis with
117
+ * two outcomes to four directions with a tilt. Threshold-only on purpose,
118
+ * matching useSwipeReveal — no velocity tracking, so a fast short flick
119
+ * under `threshold` still springs back.
120
+ */
121
+ export function useSwipeStack({
122
+ directions,
123
+ threshold = 96,
124
+ onCommit,
125
+ disabled = false,
126
+ }: UseSwipeStackOptions): UseSwipeStackResult {
127
+ const cardRef = useRef<HTMLDivElement>(null);
128
+ const wrapRef = useRef<HTMLDivElement>(null);
129
+ const dragState = useRef<{ startX: number; startY: number } | null>(null);
130
+ const live = new Set(directions);
131
+
132
+ function writeProgress(direction: SwipeDirection | null, progress: number) {
133
+ const wrap = wrapRef.current;
134
+ if (!wrap) return;
135
+ for (const d of ALL_DIRECTIONS) {
136
+ wrap.style.setProperty(`--sv-stack-progress-${d}`, d === direction ? String(progress) : '0');
137
+ }
138
+ }
139
+
140
+ function writeTransform(dx: number, dy: number) {
141
+ const card = cardRef.current;
142
+ if (!card) return;
143
+ const rotation = clamp(
144
+ (dx / ROTATION_RANGE_PX) * ROTATION_MAX_DEG,
145
+ -ROTATION_MAX_DEG,
146
+ ROTATION_MAX_DEG,
147
+ );
148
+ card.style.transform = `translate(${dx}px, ${dy}px) rotate(${rotation}deg)`;
149
+ }
150
+
151
+ function springBack() {
152
+ const card = cardRef.current;
153
+ if (!card) return;
154
+ card.style.transition = `transform ${SWIPE_STACK_TRANSITION_MS}ms ease`;
155
+ card.style.transform = '';
156
+ }
157
+
158
+ function flingOut(direction: SwipeDirection) {
159
+ const card = cardRef.current;
160
+ if (!card) return;
161
+ const distance = 1.4 * Math.max(window.innerWidth, window.innerHeight);
162
+ const [ux, uy] = UNIT_VECTOR[direction];
163
+ card.style.transition = `transform ${SWIPE_STACK_TRANSITION_MS}ms ease`;
164
+ card.style.transform = `translate(${ux * distance}px, ${uy * distance}px) rotate(${ux * ROTATION_MAX_DEG}deg)`;
165
+ }
166
+
167
+ function onPointerDown(e: ReactPointerEvent) {
168
+ if (disabled) return;
169
+ e.currentTarget.setPointerCapture(e.pointerId);
170
+ dragState.current = { startX: e.clientX, startY: e.clientY };
171
+ if (cardRef.current) cardRef.current.style.transition = '';
172
+ }
173
+
174
+ function onPointerMove(e: ReactPointerEvent) {
175
+ const state = dragState.current;
176
+ if (!state) return;
177
+ const [dx, dy] = clampToLiveDirections(
178
+ e.clientX - state.startX,
179
+ e.clientY - state.startY,
180
+ live,
181
+ );
182
+ writeTransform(dx, dy);
183
+ const direction = resolveDirection(dx, dy, live);
184
+ if (direction) e.preventDefault();
185
+ writeProgress(
186
+ direction,
187
+ direction ? clamp(Math.max(Math.abs(dx), Math.abs(dy)) / threshold, 0, 1) : 0,
188
+ );
189
+ }
190
+
191
+ function release(e: ReactPointerEvent) {
192
+ const state = dragState.current;
193
+ dragState.current = null;
194
+ if (!state) return;
195
+ const [dx, dy] = clampToLiveDirections(
196
+ e.clientX - state.startX,
197
+ e.clientY - state.startY,
198
+ live,
199
+ );
200
+ const direction = resolveDirection(dx, dy, live);
201
+ writeProgress(null, 0);
202
+ const distance = Math.max(Math.abs(dx), Math.abs(dy));
203
+ if (direction && distance >= threshold) {
204
+ flingOut(direction);
205
+ onCommit(direction);
206
+ } else {
207
+ springBack();
208
+ }
209
+ }
210
+
211
+ function triggerCommit(direction: SwipeDirection) {
212
+ if (disabled || !live.has(direction)) return;
213
+ flingOut(direction);
214
+ onCommit(direction);
215
+ }
216
+
217
+ const hasHorizontal = live.has('left') || live.has('right');
218
+ const hasVertical = live.has('up') || live.has('down');
219
+ const touchAction =
220
+ hasHorizontal && hasVertical
221
+ ? 'none'
222
+ : hasHorizontal
223
+ ? 'pan-y'
224
+ : hasVertical
225
+ ? 'pan-x'
226
+ : 'auto';
227
+
228
+ return {
229
+ cardRef,
230
+ wrapRef,
231
+ handlers: { onPointerDown, onPointerMove, onPointerUp: release, onPointerCancel: release },
232
+ touchAction,
233
+ triggerCommit,
234
+ };
235
+ }
package/src/index.ts CHANGED
@@ -1,5 +1,7 @@
1
1
  export { LegalLinks } from './components/LegalLinks/LegalLinks';
2
2
  export type { LegalLinksProps } from './components/LegalLinks/LegalLinks';
3
+ export { ConsentPrompt } from './components/ConsentPrompt/ConsentPrompt';
4
+ export type { ConsentPromptProps } from './components/ConsentPrompt/ConsentPrompt';
3
5
  export { Markdown } from './components/Markdown/Markdown';
4
6
  export type { MarkdownProps } from './components/Markdown/Markdown';
5
7
  export { Badge } from './components/Badge/Badge';
@@ -53,16 +55,34 @@ export type {
53
55
  MenuSeparator,
54
56
  MenuEntry,
55
57
  } from './components/Menu/Menu';
58
+ export { UserMenu } from './components/UserMenu/UserMenu';
59
+ export type { UserMenuProps, UserMenuItem } from './components/UserMenu/UserMenu';
60
+ export { AppsLauncher } from './components/AppsLauncher/AppsLauncher';
61
+ export type { AppsLauncherProps, AppsLauncherItem } from './components/AppsLauncher/AppsLauncher';
62
+ export { NotificationsPanel } from './components/NotificationsPanel/NotificationsPanel';
63
+ export type {
64
+ NotificationsPanelProps,
65
+ NotificationsPanelItem,
66
+ } from './components/NotificationsPanel/NotificationsPanel';
56
67
  export { ToastProvider, useToast } from './components/Toast/Toast';
57
68
  export type { ToastItem, ToastContextValue } from './components/Toast/Toast';
58
69
  export { Card } from './components/Card/Card';
59
70
  export type { CardProps } from './components/Card/Card';
71
+
72
+ export { CardTile, CardTileGrid, NewCardTile } from './components/CardTile/CardTile';
73
+ export type {
74
+ CardTileProps,
75
+ CardTileGridProps,
76
+ NewCardTileProps,
77
+ } from './components/CardTile/CardTile';
60
78
  export { FormField } from './components/FormField/FormField';
61
79
  export type { FormFieldProps, FormFieldRenderProps } from './components/FormField/FormField';
62
80
  export { PageHeader } from './components/PageHeader/PageHeader';
63
81
  export type { PageHeaderProps } from './components/PageHeader/PageHeader';
64
82
  export { PageContainer } from './components/PageContainer/PageContainer';
65
83
  export type { PageContainerProps } from './components/PageContainer/PageContainer';
84
+ export { Header } from './components/Header/Header';
85
+ export type { HeaderProps, HeaderPlugin } from './components/Header/Header';
66
86
  export { MobileHeader } from './components/MobileHeader/MobileHeader';
67
87
  export type { MobileHeaderProps } from './components/MobileHeader/MobileHeader';
68
88
  export { MobileFooter } from './components/MobileFooter/MobileFooter';
@@ -172,6 +192,10 @@ export { Combobox } from './components/Combobox/Combobox';
172
192
  export type { ComboboxProps, ComboboxOption } from './components/Combobox/Combobox';
173
193
  export { DragHandleRow } from './components/DragHandleRow/DragHandleRow';
174
194
  export type { DragHandleRowProps } from './components/DragHandleRow/DragHandleRow';
195
+ export { GripIcon } from './components/GripIcon/GripIcon';
196
+ export type { GripIconProps } from './components/GripIcon/GripIcon';
197
+ export { StepStrip } from './components/StepStrip/StepStrip';
198
+ export type { StepStripProps, StepStripItem } from './components/StepStrip/StepStrip';
175
199
  export { FileDropzone } from './components/FileDropzone/FileDropzone';
176
200
  export type { FileDropzoneProps } from './components/FileDropzone/FileDropzone';
177
201
  export { Calendar } from './components/Calendar/Calendar';
@@ -185,6 +209,8 @@ export type {
185
209
  } from './components/SuggestionInput/SuggestionInput';
186
210
  export { IconPicker } from './components/IconPicker/IconPicker';
187
211
  export type { IconPickerProps } from './components/IconPicker/IconPicker';
212
+ export { ColorPicker } from './components/ColorPicker/ColorPicker';
213
+ export type { ColorPickerProps, ColorPickerSwatch } from './components/ColorPicker/ColorPicker';
188
214
  export { QuantityStepper } from './components/QuantityStepper/QuantityStepper';
189
215
  export type { QuantityStepperProps } from './components/QuantityStepper/QuantityStepper';
190
216
  export { CheckableListRow } from './components/CheckableListRow/CheckableListRow';
@@ -215,9 +241,12 @@ export {
215
241
  useSnapCarousel,
216
242
  useResponsiveLayout,
217
243
  useCarouselRouteSync,
244
+ useReorderSensors,
245
+ shouldHandleDndEvent,
246
+ useSwipeStack,
218
247
  } from './hooks';
219
248
  export type { OfflineTileState } from './hooks';
220
- export { MOBILE_BREAKPOINT_PX } from './hooks';
249
+ export { MOBILE_BREAKPOINT_PX, SWIPE_STACK_TRANSITION_MS } from './hooks';
221
250
  export type {
222
251
  UseLongPressOptions,
223
252
  LongPressHandlers,
@@ -231,6 +260,11 @@ export type {
231
260
  UseResponsiveLayoutResult,
232
261
  UseCarouselRouteSyncOptions,
233
262
  UseCarouselRouteSyncResult,
263
+ UseReorderSensorsOptions,
264
+ SwipeDirection,
265
+ UseSwipeStackOptions,
266
+ UseSwipeStackHandlers,
267
+ UseSwipeStackResult,
234
268
  } from './hooks';
235
269
  export { ResponsiveSurface } from './components/ResponsiveSurface/ResponsiveSurface';
236
270
  export type { ResponsiveSurfaceProps } from './components/ResponsiveSurface/ResponsiveSurface';
@@ -249,3 +283,29 @@ export { SwipableMobileCarouselSlideBody } from './components/SwipableMobileCaro
249
283
  export type { SwipableMobileCarouselSlideBodyProps } from './components/SwipableMobileCarousel/SwipableMobileCarouselSlideBody';
250
284
  export { SwipableMobileCarouselSlideFooter } from './components/SwipableMobileCarousel/SwipableMobileCarouselSlideFooter';
251
285
  export type { SwipableMobileCarouselSlideFooterProps } from './components/SwipableMobileCarousel/SwipableMobileCarouselSlideFooter';
286
+ export { SwipeStack } from './components/SwipeStack/SwipeStack';
287
+ export type { SwipeStackProps, SwipeDirectionMeta } from './components/SwipeStack/SwipeStack';
288
+ export { SwipeStackCard } from './components/SwipeStack/SwipeStackCard';
289
+ export type { SwipeStackCardProps } from './components/SwipeStack/SwipeStackCard';
290
+ export { ThreeColumnLayout } from './components/ThreeColumnLayout/ThreeColumnLayout';
291
+ export type { ThreeColumnLayoutProps } from './components/ThreeColumnLayout/ThreeColumnLayout';
292
+ export { HeaderFooterLayout } from './components/HeaderFooterLayout/HeaderFooterLayout';
293
+ export type { HeaderFooterLayoutProps } from './components/HeaderFooterLayout/HeaderFooterLayout';
294
+ export { RootLayout } from './components/RootLayout/RootLayout';
295
+ export type { RootLayoutProps, RootLayoutVariant } from './components/RootLayout/RootLayout';
296
+ export { PageLayout } from './components/PageLayout/PageLayout';
297
+ export type { PageLayoutProps, PageLayoutPadding } from './components/PageLayout/PageLayout';
298
+ export { NavList } from './components/NavList/NavList';
299
+ export type {
300
+ NavListProps,
301
+ NavListGroup,
302
+ NavListItem,
303
+ NavListLinkProps,
304
+ } from './components/NavList/NavList';
305
+
306
+ // Theme presets (RFC 0094) — the closed, built-in preset data. Consumed
307
+ // directly by runtime/src/instance-style.ts (RFC 0095); Storybook and
308
+ // standalone consumers instead import the generated
309
+ // '@sovereignfs/ui/tokens/theme-presets.css'.
310
+ export { THEME_PRESETS, THEME_PRESET_NAMES } from './tokens/theme-presets';
311
+ export type { ThemePresetName, ThemePresetTokens } from './tokens/theme-presets';