@sovereignfs/ui 0.54.0 → 0.89.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 (473) hide show
  1. package/LICENSE +373 -661
  2. package/NOTICE +8 -0
  3. package/README.md +4 -6
  4. package/dist/Accordion.module.css +1 -1
  5. package/dist/Alert.module.css +1 -1
  6. package/dist/AppsLauncher.module.css +102 -0
  7. package/dist/Avatar.module.css +1 -1
  8. package/dist/Badge.module.css +30 -7
  9. package/dist/BalanceChip.module.css +1 -1
  10. package/dist/Button.module.css +23 -2
  11. package/dist/Card.module.css +7 -1
  12. package/dist/CardTile.module.css +187 -0
  13. package/dist/CheckableListRow.module.css +8 -0
  14. package/dist/Checkbox.module.css +8 -0
  15. package/dist/CodeTextarea.module.css +7 -1
  16. package/dist/Collapsible.module.css +1 -1
  17. package/dist/ColorPicker.module.css +138 -0
  18. package/dist/Combobox.module.css +8 -2
  19. package/dist/Command.module.css +7 -1
  20. package/dist/ConfirmDialog.module.css +8 -2
  21. package/dist/ConsentPrompt.module.css +21 -0
  22. package/dist/DatePicker.module.css +7 -1
  23. package/dist/Dialog.module.css +130 -21
  24. package/dist/DragHandleRow.module.css +6 -0
  25. package/dist/Drawer.module.css +36 -7
  26. package/dist/FileDropzone.module.css +25 -1
  27. package/dist/Header.module.css +68 -0
  28. package/dist/HeaderFooterLayout.module.css +21 -0
  29. package/dist/IconPicker.module.css +15 -5
  30. package/dist/Input.module.css +7 -1
  31. package/dist/Kbd.module.css +1 -1
  32. package/dist/LegalLinks.module.css +9 -2
  33. package/dist/Markdown.module.css +29 -0
  34. package/dist/Menu.module.css +1 -0
  35. package/dist/Message.module.css +10 -1
  36. package/dist/MessageScroller.module.css +12 -1
  37. package/dist/MobileAppsDrawer.module.css +19 -14
  38. package/dist/MobileFooter.module.css +7 -1
  39. package/dist/MobileHeader.module.css +14 -1
  40. package/dist/NavList.module.css +139 -0
  41. package/dist/NavTabs.module.css +8 -2
  42. package/dist/NotificationsPanel.module.css +268 -0
  43. package/dist/OverlayHeader.module.css +1 -1
  44. package/dist/PageContainer.module.css +62 -0
  45. package/dist/PageHeader.module.css +54 -0
  46. package/dist/PageLayout.module.css +51 -0
  47. package/dist/Popover.module.css +1 -1
  48. package/dist/QrCode.module.css +17 -0
  49. package/dist/QuantityStepper.module.css +5 -3
  50. package/dist/RadioGroup.module.css +6 -0
  51. package/dist/Resizable.module.css +3 -3
  52. package/dist/RootLayout.module.css +12 -0
  53. package/dist/SegmentedControl.module.css +7 -1
  54. package/dist/Select.module.css +7 -1
  55. package/dist/Sheet.module.css +20 -2
  56. package/dist/SplitPane.module.css +5 -5
  57. package/dist/StatusBadge.module.css +1 -1
  58. package/dist/StepStrip.module.css +55 -0
  59. package/dist/SuggestionInput.module.css +14 -3
  60. package/dist/SwipableMobileCarousel.module.css +12 -3
  61. package/dist/SwipableMobileCarouselDots.module.css +21 -0
  62. package/dist/SwipeStack.module.css +101 -0
  63. package/dist/SystemBanner.module.css +16 -0
  64. package/dist/Table.module.css +2 -2
  65. package/dist/Tabs.module.css +7 -1
  66. package/dist/TagInput.module.css +8 -2
  67. package/dist/Textarea.module.css +15 -1
  68. package/dist/ThreeColumnLayout.module.css +35 -0
  69. package/dist/Toast.module.css +67 -3
  70. package/dist/Toggle.module.css +7 -0
  71. package/dist/Tooltip.module.css +7 -1
  72. package/dist/UserMenu.module.css +95 -0
  73. package/dist/fonts/OFL.txt +93 -0
  74. package/dist/fonts/hanken-grotesk-cyrillic-ext-wght-normal.woff2 +0 -0
  75. package/dist/fonts/hanken-grotesk-latin-ext-wght-normal.woff2 +0 -0
  76. package/dist/fonts/hanken-grotesk-latin-wght-normal.woff2 +0 -0
  77. package/dist/fonts/hanken-grotesk-vietnamese-wght-normal.woff2 +0 -0
  78. package/dist/index.d.ts +1156 -76
  79. package/dist/index.js +4223 -1513
  80. package/dist/tokens/fonts.css +53 -0
  81. package/dist/tokens/primitives.css +23 -12
  82. package/dist/tokens/semantic.css +32 -0
  83. package/dist/tokens/theme-presets.css +33 -0
  84. package/dist/tokens.css +2 -1
  85. package/package.json +46 -19
  86. package/src/__tests__/motion.test.tsx +0 -105
  87. package/src/components/Accordion/Accordion.module.css +0 -3
  88. package/src/components/Accordion/Accordion.stories.tsx +0 -110
  89. package/src/components/Accordion/Accordion.tsx +0 -53
  90. package/src/components/Accordion/__tests__/Accordion.test.tsx +0 -61
  91. package/src/components/Alert/Alert.module.css +0 -62
  92. package/src/components/Alert/Alert.stories.tsx +0 -96
  93. package/src/components/Alert/Alert.tsx +0 -58
  94. package/src/components/Alert/__tests__/Alert.test.tsx +0 -75
  95. package/src/components/AspectRatio/AspectRatio.module.css +0 -15
  96. package/src/components/AspectRatio/AspectRatio.stories.tsx +0 -78
  97. package/src/components/AspectRatio/AspectRatio.tsx +0 -21
  98. package/src/components/AspectRatio/__tests__/AspectRatio.test.tsx +0 -27
  99. package/src/components/Avatar/Avatar.module.css +0 -38
  100. package/src/components/Avatar/Avatar.tsx +0 -42
  101. package/src/components/Badge/Badge.module.css +0 -154
  102. package/src/components/Badge/Badge.stories.tsx +0 -226
  103. package/src/components/Badge/Badge.tsx +0 -81
  104. package/src/components/Badge/__tests__/Badge.test.tsx +0 -88
  105. package/src/components/BalanceChip/BalanceChip.module.css +0 -32
  106. package/src/components/BalanceChip/BalanceChip.stories.tsx +0 -45
  107. package/src/components/BalanceChip/BalanceChip.tsx +0 -33
  108. package/src/components/Breadcrumb/Breadcrumb.module.css +0 -49
  109. package/src/components/Breadcrumb/Breadcrumb.stories.tsx +0 -47
  110. package/src/components/Breadcrumb/Breadcrumb.tsx +0 -57
  111. package/src/components/Breadcrumb/__tests__/Breadcrumb.test.tsx +0 -52
  112. package/src/components/Button/Button.module.css +0 -153
  113. package/src/components/Button/Button.stories.tsx +0 -83
  114. package/src/components/Button/Button.tsx +0 -52
  115. package/src/components/Button/__tests__/Button.test.tsx +0 -66
  116. package/src/components/ButtonGroup/ButtonGroup.module.css +0 -29
  117. package/src/components/ButtonGroup/ButtonGroup.stories.tsx +0 -57
  118. package/src/components/ButtonGroup/ButtonGroup.tsx +0 -27
  119. package/src/components/ButtonGroup/__tests__/ButtonGroup.test.tsx +0 -29
  120. package/src/components/Calendar/Calendar.module.css +0 -126
  121. package/src/components/Calendar/Calendar.tsx +0 -213
  122. package/src/components/Calendar/__tests__/Calendar.test.tsx +0 -98
  123. package/src/components/Calendar/dateUtils.ts +0 -97
  124. package/src/components/Card/Card.module.css +0 -42
  125. package/src/components/Card/Card.tsx +0 -34
  126. package/src/components/CheckableListRow/CheckableListRow.module.css +0 -95
  127. package/src/components/CheckableListRow/CheckableListRow.stories.tsx +0 -99
  128. package/src/components/CheckableListRow/CheckableListRow.tsx +0 -89
  129. package/src/components/Checkbox/Checkbox.module.css +0 -111
  130. package/src/components/Checkbox/Checkbox.tsx +0 -78
  131. package/src/components/CodeTextarea/CodeTextarea.module.css +0 -53
  132. package/src/components/CodeTextarea/CodeTextarea.stories.tsx +0 -77
  133. package/src/components/CodeTextarea/CodeTextarea.tsx +0 -36
  134. package/src/components/Collapsible/Collapsible.module.css +0 -70
  135. package/src/components/Collapsible/Collapsible.stories.tsx +0 -62
  136. package/src/components/Collapsible/Collapsible.tsx +0 -66
  137. package/src/components/Collapsible/__tests__/Collapsible.test.tsx +0 -71
  138. package/src/components/Combobox/Combobox.module.css +0 -122
  139. package/src/components/Combobox/Combobox.stories.tsx +0 -79
  140. package/src/components/Combobox/Combobox.tsx +0 -186
  141. package/src/components/Combobox/__tests__/Combobox.test.tsx +0 -158
  142. package/src/components/Command/Command.module.css +0 -80
  143. package/src/components/Command/Command.stories.tsx +0 -104
  144. package/src/components/Command/Command.tsx +0 -168
  145. package/src/components/Command/__tests__/Command.test.tsx +0 -162
  146. package/src/components/ConfirmDialog/ConfirmDialog.module.css +0 -113
  147. package/src/components/ConfirmDialog/ConfirmDialog.tsx +0 -124
  148. package/src/components/ConfirmDialog/__tests__/ConfirmDialog.test.tsx +0 -146
  149. package/src/components/ContextMenu/ContextMenu.stories.tsx +0 -81
  150. package/src/components/ContextMenu/ContextMenu.tsx +0 -100
  151. package/src/components/ContextMenu/__tests__/ContextMenu.test.tsx +0 -87
  152. package/src/components/CurrencyInput/CurrencyInput.stories.tsx +0 -50
  153. package/src/components/CurrencyInput/CurrencyInput.tsx +0 -60
  154. package/src/components/DataTable/DataTable.module.css +0 -28
  155. package/src/components/DataTable/DataTable.stories.tsx +0 -74
  156. package/src/components/DataTable/DataTable.tsx +0 -154
  157. package/src/components/DataTable/__tests__/DataTable.test.tsx +0 -123
  158. package/src/components/DatePicker/DatePicker.module.css +0 -61
  159. package/src/components/DatePicker/DatePicker.tsx +0 -103
  160. package/src/components/DatePicker/__tests__/DatePicker.test.tsx +0 -89
  161. package/src/components/DeviceOnlyGate/DeviceOnlyGate.tsx +0 -58
  162. package/src/components/DeviceOnlyGate/__tests__/DeviceOnlyGate.test.tsx +0 -45
  163. package/src/components/DeviceStorageKeyGate/DeviceStorageKeyGate.tsx +0 -100
  164. package/src/components/DeviceStorageKeyGate/__tests__/DeviceStorageKeyGate.test.tsx +0 -119
  165. package/src/components/Dialog/Dialog.module.css +0 -226
  166. package/src/components/Dialog/Dialog.stories.tsx +0 -147
  167. package/src/components/Dialog/Dialog.tsx +0 -160
  168. package/src/components/Dialog/__tests__/Dialog.test.tsx +0 -137
  169. package/src/components/DragHandleRow/DragHandleRow.module.css +0 -57
  170. package/src/components/DragHandleRow/DragHandleRow.tsx +0 -61
  171. package/src/components/Drawer/Drawer.module.css +0 -92
  172. package/src/components/Drawer/Drawer.stories.tsx +0 -132
  173. package/src/components/Drawer/Drawer.tsx +0 -176
  174. package/src/components/Drawer/__tests__/Drawer.test.tsx +0 -199
  175. package/src/components/EmptyState/EmptyState.module.css +0 -27
  176. package/src/components/EmptyState/EmptyState.tsx +0 -27
  177. package/src/components/FileDropzone/FileDropzone.module.css +0 -63
  178. package/src/components/FileDropzone/FileDropzone.tsx +0 -109
  179. package/src/components/FileDropzone/__tests__/FileDropzone.test.tsx +0 -67
  180. package/src/components/FormField/FormField.module.css +0 -33
  181. package/src/components/FormField/FormField.tsx +0 -78
  182. package/src/components/FormField/__tests__/FormField.test.tsx +0 -83
  183. package/src/components/HoverCard/HoverCard.module.css +0 -3
  184. package/src/components/HoverCard/HoverCard.stories.tsx +0 -66
  185. package/src/components/HoverCard/HoverCard.tsx +0 -111
  186. package/src/components/HoverCard/__tests__/HoverCard.test.tsx +0 -99
  187. package/src/components/Icon/Icon.module.css +0 -27
  188. package/src/components/Icon/Icon.stories.tsx +0 -95
  189. package/src/components/Icon/Icon.tsx +0 -43
  190. package/src/components/Icon/__tests__/Icon.test.tsx +0 -40
  191. package/src/components/Icon/icons/activity.tsx +0 -20
  192. package/src/components/Icon/icons/alert-triangle.tsx +0 -22
  193. package/src/components/Icon/icons/apple.tsx +0 -21
  194. package/src/components/Icon/icons/banana.tsx +0 -21
  195. package/src/components/Icon/icons/beef.tsx +0 -22
  196. package/src/components/Icon/icons/beer.tsx +0 -24
  197. package/src/components/Icon/icons/bell.tsx +0 -21
  198. package/src/components/Icon/icons/calendar.tsx +0 -23
  199. package/src/components/Icon/icons/candy.tsx +0 -24
  200. package/src/components/Icon/icons/carrot.tsx +0 -22
  201. package/src/components/Icon/icons/check.tsx +0 -20
  202. package/src/components/Icon/icons/chevron-down.tsx +0 -20
  203. package/src/components/Icon/icons/chevron-left.tsx +0 -20
  204. package/src/components/Icon/icons/chevron-right.tsx +0 -20
  205. package/src/components/Icon/icons/chevron-up.tsx +0 -20
  206. package/src/components/Icon/icons/circle-check.tsx +0 -21
  207. package/src/components/Icon/icons/circle-x.tsx +0 -22
  208. package/src/components/Icon/icons/coffee.tsx +0 -23
  209. package/src/components/Icon/icons/cookie.tsx +0 -25
  210. package/src/components/Icon/icons/croissant.tsx +0 -24
  211. package/src/components/Icon/icons/cup-soda.tsx +0 -23
  212. package/src/components/Icon/icons/drumstick.tsx +0 -21
  213. package/src/components/Icon/icons/egg.tsx +0 -20
  214. package/src/components/Icon/icons/ellipsis-vertical.tsx +0 -22
  215. package/src/components/Icon/icons/external-link.tsx +0 -22
  216. package/src/components/Icon/icons/eye-off.tsx +0 -23
  217. package/src/components/Icon/icons/eye.tsx +0 -21
  218. package/src/components/Icon/icons/file.tsx +0 -21
  219. package/src/components/Icon/icons/fish.tsx +0 -25
  220. package/src/components/Icon/icons/grid-2x2.tsx +0 -22
  221. package/src/components/Icon/icons/house.tsx +0 -21
  222. package/src/components/Icon/icons/index.ts +0 -234
  223. package/src/components/Icon/icons/info.tsx +0 -22
  224. package/src/components/Icon/icons/lock.tsx +0 -21
  225. package/src/components/Icon/icons/log-out.tsx +0 -22
  226. package/src/components/Icon/icons/mail.tsx +0 -21
  227. package/src/components/Icon/icons/menu.tsx +0 -22
  228. package/src/components/Icon/icons/milk.tsx +0 -22
  229. package/src/components/Icon/icons/package.tsx +0 -23
  230. package/src/components/Icon/icons/pencil.tsx +0 -21
  231. package/src/components/Icon/icons/pizza.tsx +0 -24
  232. package/src/components/Icon/icons/plus.tsx +0 -21
  233. package/src/components/Icon/icons/rotate-ccw.tsx +0 -21
  234. package/src/components/Icon/icons/salad.tsx +0 -24
  235. package/src/components/Icon/icons/search.tsx +0 -21
  236. package/src/components/Icon/icons/settings.tsx +0 -21
  237. package/src/components/Icon/icons/shield.tsx +0 -20
  238. package/src/components/Icon/icons/shopping-basket.tsx +0 -26
  239. package/src/components/Icon/icons/sliders-horizontal.tsx +0 -28
  240. package/src/components/Icon/icons/smartphone.tsx +0 -21
  241. package/src/components/Icon/icons/snowflake.tsx +0 -31
  242. package/src/components/Icon/icons/spray-can.tsx +0 -29
  243. package/src/components/Icon/icons/trash-2.tsx +0 -24
  244. package/src/components/Icon/icons/upload.tsx +0 -22
  245. package/src/components/Icon/icons/user.tsx +0 -21
  246. package/src/components/Icon/icons/wine.tsx +0 -23
  247. package/src/components/Icon/icons/x.tsx +0 -21
  248. package/src/components/IconPicker/IconPicker.module.css +0 -63
  249. package/src/components/IconPicker/IconPicker.stories.tsx +0 -84
  250. package/src/components/IconPicker/IconPicker.tsx +0 -90
  251. package/src/components/Input/Input.module.css +0 -52
  252. package/src/components/Input/Input.stories.tsx +0 -86
  253. package/src/components/Input/Input.tsx +0 -14
  254. package/src/components/Input/__tests__/Input.test.tsx +0 -22
  255. package/src/components/Item/Item.module.css +0 -70
  256. package/src/components/Item/Item.stories.tsx +0 -62
  257. package/src/components/Item/Item.tsx +0 -54
  258. package/src/components/Item/__tests__/Item.test.tsx +0 -39
  259. package/src/components/Kbd/Kbd.module.css +0 -14
  260. package/src/components/Kbd/Kbd.stories.tsx +0 -32
  261. package/src/components/Kbd/Kbd.tsx +0 -12
  262. package/src/components/Kbd/__tests__/Kbd.test.tsx +0 -14
  263. package/src/components/Label/Label.module.css +0 -11
  264. package/src/components/Label/Label.stories.tsx +0 -40
  265. package/src/components/Label/Label.tsx +0 -24
  266. package/src/components/Label/__tests__/Label.test.tsx +0 -32
  267. package/src/components/LegalLinks/LegalLinks.module.css +0 -24
  268. package/src/components/LegalLinks/LegalLinks.stories.tsx +0 -22
  269. package/src/components/LegalLinks/LegalLinks.tsx +0 -40
  270. package/src/components/Markdown/Markdown.module.css +0 -49
  271. package/src/components/Markdown/Markdown.stories.tsx +0 -30
  272. package/src/components/Markdown/Markdown.tsx +0 -168
  273. package/src/components/Marker/Marker.module.css +0 -33
  274. package/src/components/Marker/Marker.stories.tsx +0 -39
  275. package/src/components/Marker/Marker.tsx +0 -34
  276. package/src/components/Marker/__tests__/Marker.test.tsx +0 -23
  277. package/src/components/MemberMultiSelect/MemberMultiSelect.module.css +0 -27
  278. package/src/components/MemberMultiSelect/MemberMultiSelect.stories.tsx +0 -74
  279. package/src/components/MemberMultiSelect/MemberMultiSelect.tsx +0 -67
  280. package/src/components/Menu/Menu.module.css +0 -89
  281. package/src/components/Menu/Menu.tsx +0 -159
  282. package/src/components/Menu/__tests__/Menu.test.tsx +0 -197
  283. package/src/components/Menubar/Menubar.stories.tsx +0 -52
  284. package/src/components/Menubar/Menubar.tsx +0 -42
  285. package/src/components/Menubar/__tests__/Menubar.test.tsx +0 -45
  286. package/src/components/Message/Message.module.css +0 -99
  287. package/src/components/Message/Message.stories.tsx +0 -79
  288. package/src/components/Message/Message.tsx +0 -41
  289. package/src/components/Message/__tests__/Message.test.tsx +0 -46
  290. package/src/components/MessageScroller/MessageScroller.module.css +0 -54
  291. package/src/components/MessageScroller/MessageScroller.stories.tsx +0 -84
  292. package/src/components/MessageScroller/MessageScroller.tsx +0 -72
  293. package/src/components/MessageScroller/__tests__/MessageScroller.test.tsx +0 -105
  294. package/src/components/MobileAppsDrawer/MobileAppsDrawer.module.css +0 -81
  295. package/src/components/MobileAppsDrawer/MobileAppsDrawer.tsx +0 -78
  296. package/src/components/MobileAppsDrawer/__tests__/MobileAppsDrawer.test.tsx +0 -80
  297. package/src/components/MobileFooter/MobileFooter.module.css +0 -73
  298. package/src/components/MobileFooter/MobileFooter.stories.tsx +0 -47
  299. package/src/components/MobileFooter/MobileFooter.tsx +0 -109
  300. package/src/components/MobileFooter/__tests__/MobileFooter.test.tsx +0 -71
  301. package/src/components/MobileHeader/MobileHeader.module.css +0 -63
  302. package/src/components/MobileHeader/MobileHeader.stories.tsx +0 -57
  303. package/src/components/MobileHeader/MobileHeader.tsx +0 -77
  304. package/src/components/MobileHeader/__tests__/MobileHeader.test.tsx +0 -58
  305. package/src/components/NavTabs/NavTabs.module.css +0 -50
  306. package/src/components/NavTabs/NavTabs.tsx +0 -61
  307. package/src/components/NavigationMenu/NavigationMenu.module.css +0 -53
  308. package/src/components/NavigationMenu/NavigationMenu.stories.tsx +0 -58
  309. package/src/components/NavigationMenu/NavigationMenu.tsx +0 -125
  310. package/src/components/NavigationMenu/__tests__/NavigationMenu.test.tsx +0 -93
  311. package/src/components/OfflineGate/OfflineGate.tsx +0 -44
  312. package/src/components/OfflineGate/__tests__/OfflineGate.test.tsx +0 -66
  313. package/src/components/OverlayHeader/OverlayHeader.module.css +0 -98
  314. package/src/components/OverlayHeader/OverlayHeader.tsx +0 -62
  315. package/src/components/OverlayHeader/__tests__/OverlayHeader.test.tsx +0 -51
  316. package/src/components/PageContainer/PageContainer.module.css +0 -20
  317. package/src/components/PageContainer/PageContainer.tsx +0 -31
  318. package/src/components/PageContainer/__tests__/PageContainer.test.tsx +0 -37
  319. package/src/components/PageHeader/PageHeader.module.css +0 -32
  320. package/src/components/PageHeader/PageHeader.tsx +0 -35
  321. package/src/components/Pagination/Pagination.module.css +0 -98
  322. package/src/components/Pagination/Pagination.stories.tsx +0 -49
  323. package/src/components/Pagination/Pagination.tsx +0 -87
  324. package/src/components/Pagination/__tests__/Pagination.test.tsx +0 -56
  325. package/src/components/Popover/Popover.module.css +0 -74
  326. package/src/components/Popover/Popover.stories.tsx +0 -357
  327. package/src/components/Popover/Popover.tsx +0 -208
  328. package/src/components/Popover/__tests__/Popover.test.tsx +0 -98
  329. package/src/components/Progress/Progress.module.css +0 -20
  330. package/src/components/Progress/Progress.stories.tsx +0 -40
  331. package/src/components/Progress/Progress.tsx +0 -35
  332. package/src/components/Progress/__tests__/Progress.test.tsx +0 -31
  333. package/src/components/QuantityStepper/QuantityStepper.module.css +0 -67
  334. package/src/components/QuantityStepper/QuantityStepper.stories.tsx +0 -79
  335. package/src/components/QuantityStepper/QuantityStepper.tsx +0 -107
  336. package/src/components/RadioGroup/RadioGroup.module.css +0 -88
  337. package/src/components/RadioGroup/RadioGroup.stories.tsx +0 -69
  338. package/src/components/RadioGroup/RadioGroup.tsx +0 -99
  339. package/src/components/RadioGroup/__tests__/RadioGroup.test.tsx +0 -59
  340. package/src/components/Resizable/Resizable.module.css +0 -70
  341. package/src/components/Resizable/Resizable.stories.tsx +0 -82
  342. package/src/components/Resizable/Resizable.tsx +0 -313
  343. package/src/components/Resizable/__tests__/Resizable.test.tsx +0 -128
  344. package/src/components/ResponsiveSurface/ResponsiveSurface.tsx +0 -24
  345. package/src/components/ResponsiveSurface/__tests__/ResponsiveSurface.test.tsx +0 -73
  346. package/src/components/ScrollArea/ScrollArea.module.css +0 -20
  347. package/src/components/ScrollArea/ScrollArea.stories.tsx +0 -57
  348. package/src/components/ScrollArea/ScrollArea.tsx +0 -27
  349. package/src/components/ScrollArea/__tests__/ScrollArea.test.tsx +0 -39
  350. package/src/components/SegmentedControl/SegmentedControl.module.css +0 -55
  351. package/src/components/SegmentedControl/SegmentedControl.stories.tsx +0 -118
  352. package/src/components/SegmentedControl/SegmentedControl.tsx +0 -56
  353. package/src/components/SegmentedControl/__tests__/SegmentedControl.test.tsx +0 -63
  354. package/src/components/Select/Select.module.css +0 -82
  355. package/src/components/Select/Select.stories.tsx +0 -115
  356. package/src/components/Select/Select.tsx +0 -48
  357. package/src/components/Select/__tests__/Select.test.tsx +0 -59
  358. package/src/components/Sheet/Sheet.module.css +0 -55
  359. package/src/components/Sheet/Sheet.tsx +0 -109
  360. package/src/components/Sheet/__tests__/Sheet.test.tsx +0 -99
  361. package/src/components/Slider/Slider.module.css +0 -95
  362. package/src/components/Slider/Slider.stories.tsx +0 -64
  363. package/src/components/Slider/Slider.tsx +0 -77
  364. package/src/components/Slider/__tests__/Slider.test.tsx +0 -45
  365. package/src/components/Spinner/Spinner.module.css +0 -39
  366. package/src/components/Spinner/Spinner.tsx +0 -23
  367. package/src/components/SplitMethodSelector/SplitMethodSelector.stories.tsx +0 -33
  368. package/src/components/SplitMethodSelector/SplitMethodSelector.tsx +0 -43
  369. package/src/components/SplitPane/SplitPane.module.css +0 -85
  370. package/src/components/SplitPane/SplitPane.stories.tsx +0 -160
  371. package/src/components/SplitPane/SplitPane.tsx +0 -149
  372. package/src/components/SplitPane/__tests__/SplitPane.test.tsx +0 -51
  373. package/src/components/StatusBadge/StatusBadge.module.css +0 -59
  374. package/src/components/StatusBadge/StatusBadge.stories.tsx +0 -74
  375. package/src/components/StatusBadge/StatusBadge.tsx +0 -65
  376. package/src/components/SuggestionInput/SuggestionInput.module.css +0 -103
  377. package/src/components/SuggestionInput/SuggestionInput.stories.tsx +0 -144
  378. package/src/components/SuggestionInput/SuggestionInput.tsx +0 -207
  379. package/src/components/SwipableMobileCarousel/SwipableMobileCarousel.module.css +0 -55
  380. package/src/components/SwipableMobileCarousel/SwipableMobileCarousel.tsx +0 -227
  381. package/src/components/SwipableMobileCarousel/SwipableMobileCarouselSlide.tsx +0 -35
  382. package/src/components/SwipableMobileCarousel/SwipableMobileCarouselSlideBody.tsx +0 -42
  383. package/src/components/SwipableMobileCarousel/SwipableMobileCarouselSlideFooter.tsx +0 -18
  384. package/src/components/SwipableMobileCarousel/SwipableMobileCarouselSlideHeader.tsx +0 -21
  385. package/src/components/SwipableMobileCarousel/SwipableMobileCarouselSlideParts.module.css +0 -29
  386. package/src/components/SwipableMobileCarousel/__tests__/SwipableMobileCarousel.test.tsx +0 -133
  387. package/src/components/SwipableMobileCarousel/__tests__/SwipableMobileCarouselDevGuard.test.tsx +0 -61
  388. package/src/components/SwipableMobileCarousel/context.ts +0 -13
  389. package/src/components/SwipableMobileCarouselDots/SwipableMobileCarouselDots.module.css +0 -49
  390. package/src/components/SwipableMobileCarouselDots/SwipableMobileCarouselDots.tsx +0 -62
  391. package/src/components/SwipableMobileCarouselDots/__tests__/SwipableMobileCarouselDots.test.tsx +0 -63
  392. package/src/components/SystemBanner/SystemBanner.module.css +0 -69
  393. package/src/components/SystemBanner/SystemBanner.stories.tsx +0 -86
  394. package/src/components/SystemBanner/SystemBanner.tsx +0 -50
  395. package/src/components/SystemBanner/__tests__/SystemBanner.test.tsx +0 -50
  396. package/src/components/Table/Table.module.css +0 -45
  397. package/src/components/Table/Table.stories.tsx +0 -125
  398. package/src/components/Table/Table.tsx +0 -51
  399. package/src/components/Table/__tests__/Table.test.tsx +0 -50
  400. package/src/components/Tabs/Tabs.module.css +0 -56
  401. package/src/components/Tabs/Tabs.stories.tsx +0 -100
  402. package/src/components/Tabs/Tabs.tsx +0 -46
  403. package/src/components/Tabs/__tests__/Tabs.test.tsx +0 -45
  404. package/src/components/TagInput/TagInput.module.css +0 -141
  405. package/src/components/TagInput/TagInput.stories.tsx +0 -109
  406. package/src/components/TagInput/TagInput.tsx +0 -174
  407. package/src/components/TagInput/__tests__/TagInput.test.tsx +0 -69
  408. package/src/components/Textarea/Textarea.module.css +0 -39
  409. package/src/components/Textarea/Textarea.tsx +0 -15
  410. package/src/components/Textarea/__tests__/Textarea.test.tsx +0 -29
  411. package/src/components/Toast/Toast.module.css +0 -132
  412. package/src/components/Toast/Toast.stories.tsx +0 -191
  413. package/src/components/Toast/Toast.tsx +0 -139
  414. package/src/components/Toggle/Toggle.module.css +0 -57
  415. package/src/components/Toggle/Toggle.stories.tsx +0 -95
  416. package/src/components/Toggle/Toggle.tsx +0 -39
  417. package/src/components/Toggle/__tests__/Toggle.test.tsx +0 -47
  418. package/src/components/Tooltip/Tooltip.module.css +0 -56
  419. package/src/components/Tooltip/Tooltip.tsx +0 -30
  420. package/src/components/Typography/Typography.module.css +0 -47
  421. package/src/components/Typography/Typography.stories.tsx +0 -62
  422. package/src/components/Typography/Typography.tsx +0 -30
  423. package/src/components/Typography/__tests__/Typography.test.tsx +0 -34
  424. package/src/css-modules.d.ts +0 -4
  425. package/src/hooks/__tests__/useCarouselRouteSync.test.tsx +0 -126
  426. package/src/hooks/__tests__/useDoubleTap.test.tsx +0 -117
  427. package/src/hooks/__tests__/useIsMobile.test.tsx +0 -80
  428. package/src/hooks/__tests__/useIsOffline.test.tsx +0 -53
  429. package/src/hooks/__tests__/useLongPress.test.tsx +0 -158
  430. package/src/hooks/__tests__/useOfflineTileState.test.tsx +0 -69
  431. package/src/hooks/__tests__/useResponsiveLayout.test.tsx +0 -87
  432. package/src/hooks/__tests__/useSnapCarousel.test.tsx +0 -93
  433. package/src/hooks/__tests__/useSwipeReveal.test.tsx +0 -110
  434. package/src/hooks/index.ts +0 -24
  435. package/src/hooks/useCarouselRouteSync.ts +0 -92
  436. package/src/hooks/useCommitOnEnterOrBlur.ts +0 -39
  437. package/src/hooks/useDoubleTap.ts +0 -91
  438. package/src/hooks/useIsMobile.ts +0 -33
  439. package/src/hooks/useIsOffline.ts +0 -41
  440. package/src/hooks/useLongPress.ts +0 -185
  441. package/src/hooks/useOfflineTileState.ts +0 -45
  442. package/src/hooks/useResponsiveLayout.ts +0 -36
  443. package/src/hooks/useSnapCarousel.ts +0 -79
  444. package/src/hooks/useSwipeReveal.ts +0 -107
  445. package/src/index.ts +0 -251
  446. package/src/motion.ts +0 -78
  447. package/src/overlay-shell.ts +0 -94
  448. package/src/scroll-lock.ts +0 -22
  449. package/src/stories/Avatar.stories.tsx +0 -44
  450. package/src/stories/Card.stories.tsx +0 -35
  451. package/src/stories/Checkbox.stories.tsx +0 -45
  452. package/src/stories/DesignSystemOverview.stories.tsx +0 -3187
  453. package/src/stories/DeviceOnlyGate.stories.tsx +0 -35
  454. package/src/stories/DeviceStorageKeyGate.stories.tsx +0 -57
  455. package/src/stories/DragHandleRow.stories.tsx +0 -41
  456. package/src/stories/EmptyState.stories.tsx +0 -33
  457. package/src/stories/FileDropzone.stories.tsx +0 -69
  458. package/src/stories/FormField.stories.tsx +0 -47
  459. package/src/stories/InteractionHooks.stories.tsx +0 -925
  460. package/src/stories/MobilePatterns.stories.tsx +0 -1536
  461. package/src/stories/NavTabs.stories.tsx +0 -53
  462. package/src/stories/OfflineGate.stories.tsx +0 -42
  463. package/src/stories/PageContainer.stories.tsx +0 -37
  464. package/src/stories/PageHeader.stories.tsx +0 -42
  465. package/src/stories/ResponsiveSurface.stories.tsx +0 -47
  466. package/src/stories/Spinner.stories.tsx +0 -25
  467. package/src/stories/SwipableMobileCarousel.stories.tsx +0 -304
  468. package/src/stories/Textarea.stories.tsx +0 -51
  469. package/src/stories/TokenGallery.stories.tsx +0 -602
  470. package/src/stories/Tooltip.stories.tsx +0 -59
  471. package/src/tokens/primitives.css +0 -157
  472. package/src/tokens/semantic.css +0 -130
  473. package/src/tokens.css +0 -5
@@ -1,110 +0,0 @@
1
- // @vitest-environment jsdom
2
- import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
3
- import { cleanup, fireEvent, render } from '@testing-library/react';
4
- import { useSwipeReveal, type UseSwipeRevealOptions } from '../useSwipeReveal';
5
-
6
- // jsdom doesn't implement pointer capture — stub it as a no-op so
7
- // `e.currentTarget.setPointerCapture(...)` doesn't throw.
8
- beforeEach(() => {
9
- if (!HTMLElement.prototype.setPointerCapture) {
10
- HTMLElement.prototype.setPointerCapture = vi.fn();
11
- }
12
- });
13
-
14
- afterEach(cleanup);
15
-
16
- function Harness(props: UseSwipeRevealOptions) {
17
- const { rowRef, handlers } = useSwipeReveal(props);
18
- return (
19
- <div data-testid="row" ref={rowRef} {...handlers}>
20
- row
21
- </div>
22
- );
23
- }
24
-
25
- function pointer(x: number, y: number) {
26
- return { clientX: x, clientY: y, pointerId: 1 };
27
- }
28
-
29
- describe('useSwipeReveal', () => {
30
- it('calls onOpen when a horizontal drag crosses the halfway point', () => {
31
- const onOpen = vi.fn();
32
- const onClose = vi.fn();
33
- const { getByTestId } = render(
34
- <Harness revealWidth={100} open={false} onOpen={onOpen} onClose={onClose} />,
35
- );
36
- const el = getByTestId('row');
37
- fireEvent.pointerDown(el, pointer(0, 0));
38
- fireEvent.pointerMove(el, pointer(-60, 0));
39
- fireEvent.pointerUp(el, pointer(-60, 0));
40
- expect(onOpen).toHaveBeenCalledOnce();
41
- expect(onClose).not.toHaveBeenCalled();
42
- });
43
-
44
- it('calls onClose when the drag does not cross the halfway point', () => {
45
- const onOpen = vi.fn();
46
- const onClose = vi.fn();
47
- const { getByTestId } = render(
48
- <Harness revealWidth={100} open={false} onOpen={onOpen} onClose={onClose} />,
49
- );
50
- const el = getByTestId('row');
51
- fireEvent.pointerDown(el, pointer(0, 0));
52
- fireEvent.pointerMove(el, pointer(-20, 0));
53
- fireEvent.pointerUp(el, pointer(-20, 0));
54
- expect(onClose).toHaveBeenCalledOnce();
55
- expect(onOpen).not.toHaveBeenCalled();
56
- });
57
-
58
- it('locks to vertical and ignores a mostly-vertical drag', () => {
59
- const onOpen = vi.fn();
60
- const onClose = vi.fn();
61
- const { getByTestId } = render(
62
- <Harness revealWidth={100} open={false} onOpen={onOpen} onClose={onClose} />,
63
- );
64
- const el = getByTestId('row');
65
- fireEvent.pointerDown(el, pointer(0, 0));
66
- fireEvent.pointerMove(el, pointer(2, 60));
67
- fireEvent.pointerUp(el, pointer(2, 60));
68
- expect(onOpen).not.toHaveBeenCalled();
69
- expect(onClose).not.toHaveBeenCalled();
70
- });
71
-
72
- it('does nothing when disabled', () => {
73
- const onOpen = vi.fn();
74
- const onClose = vi.fn();
75
- const { getByTestId } = render(
76
- <Harness revealWidth={100} open={false} onOpen={onOpen} onClose={onClose} disabled />,
77
- );
78
- const el = getByTestId('row');
79
- fireEvent.pointerDown(el, pointer(0, 0));
80
- fireEvent.pointerMove(el, pointer(-60, 0));
81
- fireEvent.pointerUp(el, pointer(-60, 0));
82
- expect(onOpen).not.toHaveBeenCalled();
83
- expect(onClose).not.toHaveBeenCalled();
84
- });
85
-
86
- it('closes from an already-open state when dragged back past halfway', () => {
87
- const onOpen = vi.fn();
88
- const onClose = vi.fn();
89
- const { getByTestId } = render(
90
- <Harness revealWidth={100} open onOpen={onOpen} onClose={onClose} />,
91
- );
92
- const el = getByTestId('row');
93
- fireEvent.pointerDown(el, pointer(0, 0));
94
- fireEvent.pointerMove(el, pointer(60, 0));
95
- fireEvent.pointerUp(el, pointer(60, 0));
96
- expect(onClose).toHaveBeenCalledOnce();
97
- expect(onOpen).not.toHaveBeenCalled();
98
- });
99
-
100
- it('ignores pointerup without a preceding pointerdown', () => {
101
- const onOpen = vi.fn();
102
- const onClose = vi.fn();
103
- const { getByTestId } = render(
104
- <Harness revealWidth={100} open={false} onOpen={onOpen} onClose={onClose} />,
105
- );
106
- fireEvent.pointerUp(getByTestId('row'), pointer(-60, 0));
107
- expect(onOpen).not.toHaveBeenCalled();
108
- expect(onClose).not.toHaveBeenCalled();
109
- });
110
- });
@@ -1,24 +0,0 @@
1
- export { useLongPress } from './useLongPress';
2
- export type { UseLongPressOptions, LongPressHandlers } from './useLongPress';
3
- export { useDoubleTapHandler, useSingleOrDoubleTap } from './useDoubleTap';
4
- export { useIsMobile, MOBILE_BREAKPOINT_PX } from './useIsMobile';
5
- export { useIsOffline } from './useIsOffline';
6
- export { useOfflineTileState } from './useOfflineTileState';
7
- export type { OfflineTileState } from './useOfflineTileState';
8
- export { useCommitOnEnterOrBlur } from './useCommitOnEnterOrBlur';
9
- export type { CommitOnEnterOrBlurHandlers } from './useCommitOnEnterOrBlur';
10
- export { useSwipeReveal } from './useSwipeReveal';
11
- export type {
12
- UseSwipeRevealOptions,
13
- UseSwipeRevealHandlers,
14
- UseSwipeRevealResult,
15
- } from './useSwipeReveal';
16
- export { useSnapCarousel } from './useSnapCarousel';
17
- export type { UseSnapCarouselOptions, UseSnapCarouselResult } from './useSnapCarousel';
18
- export { useResponsiveLayout } from './useResponsiveLayout';
19
- export type { UseResponsiveLayoutOptions, UseResponsiveLayoutResult } from './useResponsiveLayout';
20
- export { useCarouselRouteSync } from './useCarouselRouteSync';
21
- export type {
22
- UseCarouselRouteSyncOptions,
23
- UseCarouselRouteSyncResult,
24
- } from './useCarouselRouteSync';
@@ -1,92 +0,0 @@
1
- 'use client';
2
-
3
- import { useEffect, useRef, useState } from 'react';
4
-
5
- export interface UseCarouselRouteSyncOptions {
6
- /** Maps the current pathname to a slide index — the caller's own routing
7
- * logic (e.g. special-casing a virtual "starred" slide), unchanged from
8
- * whatever a plugin already has today. This hook has no idea what a
9
- * plugin's routes look like, by design — it never imports a router. */
10
- indexForPathname: (pathname: string) => number;
11
- /** Inverse mapping — given a settled slide index, the path to navigate to. */
12
- pathForIndex: (index: number) => string;
13
- /** The current pathname. The caller reads this via their own router (e.g.
14
- * `usePathname()` from `next/navigation`) and passes it in every render —
15
- * this package stays framework-generic and has zero runtime dependencies. */
16
- pathname: string;
17
- /** Called when this hook wants to navigate — typically
18
- * `(path) => router.replace(path, { scroll: false })`. */
19
- onNavigate: (path: string) => void;
20
- }
21
-
22
- export interface UseCarouselRouteSyncResult {
23
- /** Feed directly into SwipableMobileCarousel's `activeIndex` prop. */
24
- activeIndex: number;
25
- /** Feed directly into SwipableMobileCarousel's `onSettle` prop. */
26
- onSettle: (index: number) => void;
27
- }
28
-
29
- /**
30
- * useCarouselRouteSync — centralizes the pathname↔slide-index mapping and the
31
- * "was this pathname change our own settle, or an external navigation"
32
- * distinction that carousel-driving plugins otherwise hand-roll per plugin
33
- * (a tapped `<Link>`, browser back/forward, vs. this hook's own
34
- * `router.replace` after a swipe settles). Router-agnostic on purpose: the
35
- * caller owns the actual `usePathname()`/`router.replace()` calls and only
36
- * hands this hook plain values/callbacks.
37
- *
38
- * Deliberately does not own neighbor-prefetch (SwipableMobileCarousel's
39
- * `prefetchDistance`/mount-window covers that) or scroll mechanics (the
40
- * carousel component reacts to `activeIndex` changes itself) — this hook is
41
- * pathname↔index mapping only, keeping routing glue decoupled from
42
- * data-fetching and DOM scrolling.
43
- */
44
- export function useCarouselRouteSync({
45
- indexForPathname,
46
- pathForIndex,
47
- pathname,
48
- onNavigate,
49
- }: UseCarouselRouteSyncOptions): UseCarouselRouteSyncResult {
50
- // Kept in refs and refreshed every render (not effect deps) so an inline
51
- // arrow function passed by the caller each render doesn't retrigger the
52
- // pathname-sync effect below — only an actual pathname change should.
53
- const indexForPathnameRef = useRef(indexForPathname);
54
- indexForPathnameRef.current = indexForPathname;
55
- const pathForIndexRef = useRef(pathForIndex);
56
- pathForIndexRef.current = pathForIndex;
57
- const onNavigateRef = useRef(onNavigate);
58
- onNavigateRef.current = onNavigate;
59
-
60
- const [activeIndex, setActiveIndex] = useState(() => indexForPathname(pathname));
61
- // Set synchronously inside onSettle, right before this hook's own
62
- // navigation call — lets the pathname-sync effect below tell "this
63
- // pathname change is our own settle, already reflected in activeIndex"
64
- // apart from a genuinely external navigation.
65
- const isInternalNavRef = useRef(false);
66
- const didMountRef = useRef(false);
67
-
68
- useEffect(() => {
69
- if (!didMountRef.current) {
70
- // The very first render's activeIndex already reflects pathname via
71
- // the lazy useState initializer above — nothing to resync on mount.
72
- didMountRef.current = true;
73
- return;
74
- }
75
- if (isInternalNavRef.current) {
76
- isInternalNavRef.current = false;
77
- return;
78
- }
79
- const newIndex = indexForPathnameRef.current(pathname);
80
- setActiveIndex((current) => (newIndex === current ? current : newIndex));
81
- // Only re-run when pathname itself changes — indexForPathname is read
82
- // through a ref precisely so its identity doesn't matter here.
83
- }, [pathname]);
84
-
85
- function onSettle(index: number) {
86
- isInternalNavRef.current = true;
87
- setActiveIndex(index);
88
- onNavigateRef.current(pathForIndexRef.current(index));
89
- }
90
-
91
- return { activeIndex, onSettle };
92
- }
@@ -1,39 +0,0 @@
1
- 'use client';
2
-
3
- import type { KeyboardEvent } from 'react';
4
-
5
- export interface CommitOnEnterOrBlurHandlers {
6
- onKeyDown: (e: KeyboardEvent<HTMLInputElement | HTMLTextAreaElement>) => void;
7
- onBlur: () => void;
8
- }
9
-
10
- /**
11
- * useCommitOnEnterOrBlur — spreads onto a quick-entry input/textarea so
12
- * Enter and losing focus both commit through the same callback.
13
- *
14
- * iOS Safari adds its own "Previous / Next / Done" toolbar above the
15
- * software keyboard whenever more than one focusable field is nearby (a
16
- * Sheet with several fields, a form, etc.) — there is no supported way to
17
- * suppress it, since it's WebKit's own field-detection heuristic, not
18
- * something the page controls. Tapping that toolbar's Done/checkmark only
19
- * ever fires a native `blur`; it is not a form submit and dispatches no
20
- * keydown, so a field that commits only on Enter silently discards whatever
21
- * was typed the moment a user dismisses the keyboard that way instead of
22
- * pressing the on-screen Return key — the two dismissal paths look
23
- * identical to the user but produce different outcomes.
24
- *
25
- * `onCommit` is called unconditionally on both Enter and blur — it owns any
26
- * empty-value no-op itself, matching how every call site's own commit
27
- * function already guards against an empty value before doing anything.
28
- * Callers needing more key handling (e.g. Escape-to-cancel) compose their
29
- * own `onKeyDown` around the returned one rather than this hook growing
30
- * options for every caller's own key bindings.
31
- */
32
- export function useCommitOnEnterOrBlur(onCommit: () => void): CommitOnEnterOrBlurHandlers {
33
- return {
34
- onKeyDown: (e) => {
35
- if (e.key === 'Enter') onCommit();
36
- },
37
- onBlur: onCommit,
38
- };
39
- }
@@ -1,91 +0,0 @@
1
- 'use client';
2
-
3
- import { useRef } from 'react';
4
-
5
- // Real double-clicks (desktop mouse) report e.detail === 2 natively — no extra
6
- // work needed there. Touch-generated click events always report detail === 1
7
- // (no browser synthesizes a dblclick-style count from two taps), so
8
- // double-tap has to be detected by timing two clicks against each other
9
- // instead. Sharing one implementation for both means every double-click-style
10
- // affordance behaves the same way on mouse and touch without the caller
11
- // duplicating the two code paths.
12
- const DOUBLE_TAP_MS = 350;
13
-
14
- /**
15
- * useDoubleTapHandler — fires `onDoubleTap` on a real double-click (desktop)
16
- * or two taps within `DOUBLE_TAP_MS` (touch).
17
- *
18
- * Only safe when the *single* tap/click has no default action of its own to
19
- * preempt — e.g. a colour swatch with nothing to cancel. If the single tap
20
- * navigates, opens something, or otherwise does something a following double
21
- * needs to be able to undo before it happens, use `useSingleOrDoubleTap`
22
- * instead: this hook fires `onDoubleTap` only, leaving any single-tap handling
23
- * to the caller's own `onClick`, which already ran immediately and can't be
24
- * un-done here.
25
- *
26
- * The event is passed through to `onDoubleTap` rather than calling
27
- * `preventDefault` internally — not every call site needs to cancel
28
- * something, so the decision is left to the caller.
29
- */
30
- export function useDoubleTapHandler<E extends { detail: number }>(
31
- onDoubleTap: (e: E) => void,
32
- ): (e: E) => void {
33
- const lastTime = useRef(0);
34
- return (e: E) => {
35
- if (e.detail === 2) {
36
- onDoubleTap(e);
37
- return;
38
- }
39
- const now = Date.now();
40
- if (now - lastTime.current < DOUBLE_TAP_MS) {
41
- lastTime.current = 0;
42
- onDoubleTap(e);
43
- } else {
44
- lastTime.current = now;
45
- }
46
- };
47
- }
48
-
49
- /**
50
- * useSingleOrDoubleTap — for call sites where the single tap/click *does*
51
- * have a default action (e.g. navigating) that a following double-tap must be
52
- * able to preempt.
53
- *
54
- * A real double-click gets away with firing the single action immediately and
55
- * only detecting the double afterward, because `e.detail === 2` is the
56
- * browser's own resolved signal, arriving on the very click that matters. A
57
- * touch double-tap has no equivalent "hold on, there might be a second one"
58
- * signal — the only way to know is to wait out the window before committing
59
- * to the single action, which is what this hook does: `onSingle` is deferred
60
- * by `DOUBLE_TAP_MS` and only actually runs if no second tap arrives in that
61
- * time. This means every single tap through this hook incurs that latency —
62
- * only use it where a genuine double-tap gesture must be able to preempt the
63
- * single action; `useDoubleTapHandler` above has no such delay.
64
- */
65
- export function useSingleOrDoubleTap<E extends { detail: number }>(
66
- onSingle: (e: E) => void,
67
- onDouble: (e: E) => void,
68
- ): (e: E) => void {
69
- const timer = useRef<ReturnType<typeof setTimeout> | null>(null);
70
-
71
- return (e: E) => {
72
- if (e.detail === 2) {
73
- if (timer.current) {
74
- clearTimeout(timer.current);
75
- timer.current = null;
76
- }
77
- onDouble(e);
78
- return;
79
- }
80
- if (timer.current) {
81
- clearTimeout(timer.current);
82
- timer.current = null;
83
- onDouble(e);
84
- return;
85
- }
86
- timer.current = setTimeout(() => {
87
- timer.current = null;
88
- onSingle(e);
89
- }, DOUBLE_TAP_MS);
90
- };
91
- }
@@ -1,33 +0,0 @@
1
- 'use client';
2
-
3
- import { useEffect, useState } from 'react';
4
-
5
- // The platform's canonical mobile breakpoint — matches the shell chrome and
6
- // Dialog's own full-screen-sheet switch (`runtime/app/globals.css`,
7
- // `Dialog.module.css`). A plugin whose own layout genuinely needs a different
8
- // threshold may still hardcode its own value (e.g. a three-column layout that
9
- // only needs to fork earlier than 768px) — this constant is the *default* every
10
- // consumer should reach for first, so the platform's idea of "mobile" doesn't
11
- // silently drift across components again.
12
- export const MOBILE_BREAKPOINT_PX = 768;
13
-
14
- /**
15
- * useIsMobile — whether the viewport is at or below the platform's mobile
16
- * breakpoint. SSR-safe: defaults to `false` (desktop) until the client mounts
17
- * and reads the real viewport, avoiding a hydration mismatch on first paint.
18
- */
19
- export function useIsMobile(breakpointPx: number = MOBILE_BREAKPOINT_PX): boolean {
20
- const [isMobile, setIsMobile] = useState(false);
21
-
22
- useEffect(() => {
23
- const mql = window.matchMedia(`(max-width: ${breakpointPx}px)`);
24
- setIsMobile(mql.matches);
25
- function handleChange(e: MediaQueryListEvent) {
26
- setIsMobile(e.matches);
27
- }
28
- mql.addEventListener('change', handleChange);
29
- return () => mql.removeEventListener('change', handleChange);
30
- }, [breakpointPx]);
31
-
32
- return isMobile;
33
- }
@@ -1,41 +0,0 @@
1
- 'use client';
2
-
3
- import { useEffect, useState } from 'react';
4
-
5
- /**
6
- * useIsOffline — whether the browser currently has no network connection.
7
- *
8
- * SSR-safe: always initialises to `false` (online) so the server-rendered HTML
9
- * matches the first client render, then reads the real `navigator.onLine`
10
- * value in `useEffect`. This picks up the case where the page loaded already
11
- * offline (e.g. served from the service-worker cache) at the cost of an
12
- * imperceptible one-frame delay before the offline state appears — the same
13
- * trade-off `OfflineBanner` makes, which this hook was extracted from so every
14
- * consumer shares one connectivity source instead of re-deriving it slightly
15
- * differently (research 0012, epic task 2.32).
16
- *
17
- * Reflects only `navigator.onLine` — true whenever the OS reports a network
18
- * interface is up, even without real upstream connectivity (e.g. Wi-Fi with no
19
- * internet). That is a known limitation of the browser API itself, not of this
20
- * hook; there is currently no better cross-browser signal available.
21
- */
22
- export function useIsOffline(): boolean {
23
- const [isOffline, setIsOffline] = useState(false);
24
-
25
- useEffect(() => {
26
- setIsOffline(!navigator.onLine);
27
-
28
- const handleOffline = () => setIsOffline(true);
29
- const handleOnline = () => setIsOffline(false);
30
-
31
- window.addEventListener('offline', handleOffline);
32
- window.addEventListener('online', handleOnline);
33
-
34
- return () => {
35
- window.removeEventListener('offline', handleOffline);
36
- window.removeEventListener('online', handleOnline);
37
- };
38
- }, []);
39
-
40
- return isOffline;
41
- }
@@ -1,185 +0,0 @@
1
- 'use client';
2
-
3
- import { useCallback, useEffect, useRef, useState } from 'react';
4
- import type {
5
- CSSProperties,
6
- MouseEvent as ReactMouseEvent,
7
- PointerEvent as ReactPointerEvent,
8
- } from 'react';
9
-
10
- export interface UseLongPressOptions {
11
- /** Called once the press has been held for `delay` without exceeding `moveTolerance`. */
12
- onLongPress: () => void;
13
- /** Hold duration in ms before `onLongPress` fires. Default 500. */
14
- delay?: number;
15
- /** Accumulated pointer movement (px) that cancels a pending press. Default 10 —
16
- * a real finger jitters a couple of px on a still hold; this must be forgiving
17
- * of that or the gesture never fires. */
18
- moveTolerance?: number;
19
- /** How long (ms) after firing to swallow the click that may or may not follow.
20
- * Default 700. Time-boxed rather than "clear on next click" — iOS frequently
21
- * sends no click at all after a long hold, so a flag that only clears on the
22
- * next click stays armed forever and silently eats the user's next real tap. */
23
- suppressClickMs?: number;
24
- /** Fire `navigator.vibrate(10)` when the press triggers, where supported. Default true. */
25
- vibrate?: boolean;
26
- /** Skip entirely (hooks can't be called conditionally, so this is the escape hatch
27
- * for a caller whose long-press action isn't available in the current state). */
28
- disabled?: boolean;
29
- }
30
-
31
- export interface LongPressHandlers {
32
- onPointerDown: (e: ReactPointerEvent) => void;
33
- onPointerMove: (e: ReactPointerEvent) => void;
34
- onPointerUp: (e: ReactPointerEvent) => void;
35
- onPointerCancel: (e: ReactPointerEvent) => void;
36
- onPointerLeave: (e: ReactPointerEvent) => void;
37
- onContextMenu: (e: ReactMouseEvent) => void;
38
- onClick: (e: ReactMouseEvent) => void;
39
- /** Spread onto the target element. Empty on devices whose primary pointer
40
- * isn't coarse (desktop mouse/trackpad) — see the coarse-pointer gate below. */
41
- style: CSSProperties;
42
- }
43
-
44
- const DEFAULT_DELAY_MS = 500;
45
- const DEFAULT_MOVE_TOLERANCE_PX = 10;
46
- const DEFAULT_SUPPRESS_CLICK_MS = 700;
47
-
48
- // SSR-safe, same defer-to-client-mount pattern as useIsMobile: defaults to
49
- // false (no suppression styles) until the client can actually read the
50
- // device's primary pointer, avoiding a hydration mismatch.
51
- function usePrefersCoarsePointer(): boolean {
52
- const [coarse, setCoarse] = useState(false);
53
-
54
- useEffect(() => {
55
- const mql = window.matchMedia('(pointer: coarse)');
56
- setCoarse(mql.matches);
57
- function handleChange(e: MediaQueryListEvent) {
58
- setCoarse(e.matches);
59
- }
60
- mql.addEventListener('change', handleChange);
61
- return () => mql.removeEventListener('change', handleChange);
62
- }, []);
63
-
64
- return coarse;
65
- }
66
-
67
- /**
68
- * useLongPress — touch-and-hold gesture with the full suppression recipe a
69
- * long-press needs to behave, not just a bare timer.
70
- *
71
- * A naive `setTimeout` started on `pointerdown` and cleared on `pointermove`
72
- * fails in three specific ways this hook exists to fix:
73
- * 1. Any movement at all cancels it — but a real finger jitters a few px even
74
- * on a "still" hold, so the gesture misfires constantly. `moveTolerance`
75
- * requires deliberate movement before cancelling.
76
- * 2. `pointercancel` (the browser converting the touch into a scroll) isn't
77
- * handled — the timer survives and fires mid-scroll. Handled here.
78
- * 3. Nothing suppresses the OS's own reaction to a long touch-hold: iOS shows
79
- * its link-preview callout, Android starts text selection or opens a
80
- * context menu, and either can fire *alongside* the gesture this hook
81
- * triggers. `onContextMenu` + the returned `style` (touch-callout,
82
- * user-select, touch-action) close all three, gated to coarse-pointer
83
- * devices only — unconditionally disabling `user-select` would break
84
- * mouse text selection on desktop, which never held this long-press to
85
- * begin with (see `onPointerDown`'s own `pointerType` check).
86
- *
87
- * The click that may or may not follow a fired long-press (iOS often sends
88
- * none at all) is swallowed for `suppressClickMs`, not indefinitely — an
89
- * unbounded suppression flag left armed by a browser that never sends that
90
- * click silently eats the user's *next* unrelated tap on the same element.
91
- */
92
- export function useLongPress({
93
- onLongPress,
94
- delay = DEFAULT_DELAY_MS,
95
- moveTolerance = DEFAULT_MOVE_TOLERANCE_PX,
96
- suppressClickMs = DEFAULT_SUPPRESS_CLICK_MS,
97
- vibrate = true,
98
- disabled = false,
99
- }: UseLongPressOptions): LongPressHandlers {
100
- const timerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
101
- const startRef = useRef<{ x: number; y: number } | null>(null);
102
- const suppressUntilRef = useRef(0);
103
-
104
- const clearTimer = useCallback(() => {
105
- if (timerRef.current) {
106
- clearTimeout(timerRef.current);
107
- timerRef.current = null;
108
- }
109
- }, []);
110
-
111
- // A press pending when the component unmounts (its target left the view
112
- // mid-hold — e.g. the row it's on was deleted by a sync elsewhere) must not
113
- // fire onLongPress against state that's no longer there.
114
- useEffect(() => clearTimer, [clearTimer]);
115
-
116
- const onPointerDown = useCallback(
117
- (e: ReactPointerEvent) => {
118
- if (disabled || e.pointerType !== 'touch') return;
119
- startRef.current = { x: e.clientX, y: e.clientY };
120
- clearTimer();
121
- timerRef.current = setTimeout(() => {
122
- timerRef.current = null;
123
- suppressUntilRef.current = Date.now() + suppressClickMs;
124
- if (vibrate && typeof navigator !== 'undefined' && navigator.vibrate) {
125
- navigator.vibrate(10);
126
- }
127
- onLongPress();
128
- }, delay);
129
- },
130
- [disabled, delay, suppressClickMs, vibrate, onLongPress, clearTimer],
131
- );
132
-
133
- const onPointerMove = useCallback(
134
- (e: ReactPointerEvent) => {
135
- const start = startRef.current;
136
- if (!start || !timerRef.current) return;
137
- const dx = e.clientX - start.x;
138
- const dy = e.clientY - start.y;
139
- if (Math.hypot(dx, dy) > moveTolerance) clearTimer();
140
- },
141
- [moveTolerance, clearTimer],
142
- );
143
-
144
- const cancelPress = useCallback(() => {
145
- clearTimer();
146
- startRef.current = null;
147
- }, [clearTimer]);
148
-
149
- // Preempts the OS's own long-press menu (Android's link/image context menu)
150
- // whenever our gesture is pending or has just fired — either way, this
151
- // hook's action is the one that should win, not a native menu appearing on
152
- // top of (or instead of) it. iOS's equivalent (the touch-callout link
153
- // preview) isn't a JS event at all — that one is closed by `style` below.
154
- const onContextMenu = useCallback((e: ReactMouseEvent) => {
155
- if (timerRef.current || Date.now() < suppressUntilRef.current) e.preventDefault();
156
- }, []);
157
-
158
- const onClick = useCallback((e: ReactMouseEvent) => {
159
- if (Date.now() < suppressUntilRef.current) {
160
- e.preventDefault();
161
- e.stopPropagation();
162
- }
163
- }, []);
164
-
165
- const isCoarsePointer = usePrefersCoarsePointer();
166
- const style: CSSProperties = isCoarsePointer
167
- ? {
168
- WebkitTouchCallout: 'none',
169
- WebkitUserSelect: 'none',
170
- userSelect: 'none',
171
- touchAction: 'manipulation',
172
- }
173
- : {};
174
-
175
- return {
176
- onPointerDown,
177
- onPointerMove,
178
- onPointerUp: cancelPress,
179
- onPointerCancel: cancelPress,
180
- onPointerLeave: cancelPress,
181
- onContextMenu,
182
- onClick,
183
- style,
184
- };
185
- }
@@ -1,45 +0,0 @@
1
- 'use client';
2
-
3
- import { useIsOffline } from './useIsOffline';
4
-
5
- /**
6
- * The two reasons an app tile can be non-interactive, kept structurally
7
- * distinct because they have different causes and must never read as the same
8
- * thing to a user (research 0012, epic task 2.33):
9
- *
10
- * - `'connectivity-dimmed'` — a no-offline-tier app, dimmed only while the
11
- * device is **actually offline right now**. Reactive and temporary; clears
12
- * the instant connectivity returns.
13
- * - `'capability-restricted'` — a `device-only` app on a surface without the
14
- * durable, encrypted, device-auth-gated store it needs. Static and
15
- * unrelated to connectivity — it stays restricted even when fully online,
16
- * and must never say "offline" to a user who plainly isn't.
17
- */
18
- export type OfflineTileState = 'connectivity-dimmed' | 'capability-restricted' | null;
19
-
20
- /**
21
- * Which of the two states, if any, applies to an app tile given its declared
22
- * offline tier.
23
- *
24
- * `deviceOnlyAvailable` is the caller's own
25
- * `isDeviceOnlyTierAvailable()` result (from
26
- * `@sovereignfs/sdk/device-client`) — deliberately taken as a plain boolean
27
- * parameter rather than computed inside this hook, so `@sovereignfs/ui` never
28
- * depends on `@sovereignfs/sdk` (this package must stay usable standalone,
29
- * outside the plugin runtime — RFC 0073).
30
- *
31
- * An `offline-first` app is never restricted by either state: that tier's
32
- * whole point is working with no network, so it stays fully interactive both
33
- * online and offline. A `device-only` app that *is* available behaves the
34
- * same way — the restriction is about availability, not connectivity.
35
- */
36
- export function useOfflineTileState(
37
- offline: 'offline-first' | 'device-only' | undefined,
38
- deviceOnlyAvailable: boolean,
39
- ): OfflineTileState {
40
- const isOffline = useIsOffline();
41
-
42
- if (offline === 'device-only' && !deviceOnlyAvailable) return 'capability-restricted';
43
- if (offline === undefined && isOffline) return 'connectivity-dimmed';
44
- return null;
45
- }