@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,154 +0,0 @@
1
- 'use client';
2
-
3
- import { useMemo, useState } from 'react';
4
- import type { ReactNode } from 'react';
5
- import { Table, TableBody, TableCell, TableHead, TableHeaderCell, TableRow } from '../Table/Table';
6
- import { Icon } from '../Icon/Icon';
7
- import { EmptyState } from '../EmptyState/EmptyState';
8
- import styles from './DataTable.module.css';
9
-
10
- export type SortDirection = 'asc' | 'desc';
11
-
12
- export interface DataTableColumn<T> {
13
- key: string;
14
- header: string;
15
- sortable?: boolean;
16
- /** Cell content. Defaults to String(row[key]) when omitted. */
17
- render?: (row: T) => ReactNode;
18
- /** Value compared when sorting by this column. Defaults to row[key].
19
- * Required if `key` isn't a property on T (a computed/joined column). */
20
- sortValue?: (row: T) => string | number;
21
- align?: 'start' | 'end';
22
- }
23
-
24
- export interface DataTableProps<T> {
25
- columns: DataTableColumn<T>[];
26
- data: T[];
27
- getRowKey: (row: T) => string;
28
- /** Initial sort — uncontrolled after mount, like Table itself has no
29
- * externally-controlled state. */
30
- defaultSortKey?: string;
31
- defaultSortDirection?: SortDirection;
32
- emptyMessage?: string;
33
- className?: string;
34
- }
35
-
36
- function compareValues(a: string | number, b: string | number): number {
37
- if (typeof a === 'number' && typeof b === 'number') return a - b;
38
- return String(a).localeCompare(String(b));
39
- }
40
-
41
- /**
42
- * DataTable — sortable table built on Table's own primitives (no
43
- * virtualization or filtering — those stay out of scope, matching Table's
44
- * own "not a data grid" boundary). Column-driven: pass `columns` + `data`
45
- * rather than composing `<TableRow>`/`<TableCell>` by hand.
46
- *
47
- * Sort state is internal (uncontrolled) and click-cycles
48
- * ascending → descending → unsorted for a sortable column, mirroring the
49
- * "toggle → toggle → clear" pattern most sortable tables use rather than
50
- * being permanently stuck in whichever direction was clicked last.
51
- */
52
- export function DataTable<T>({
53
- columns,
54
- data,
55
- getRowKey,
56
- defaultSortKey,
57
- defaultSortDirection = 'asc',
58
- emptyMessage = 'No results.',
59
- className,
60
- }: DataTableProps<T>) {
61
- const [sortKey, setSortKey] = useState<string | undefined>(defaultSortKey);
62
- const [sortDirection, setSortDirection] = useState<SortDirection>(defaultSortDirection);
63
-
64
- const sortColumn = columns.find((c) => c.key === sortKey);
65
-
66
- const sortedData = useMemo(() => {
67
- if (!sortColumn) return data;
68
- const getValue =
69
- sortColumn.sortValue ??
70
- ((row: T) => (row as Record<string, unknown>)[sortColumn.key] as string | number);
71
- const sorted = [...data].sort((a, b) => compareValues(getValue(a), getValue(b)));
72
- return sortDirection === 'asc' ? sorted : sorted.reverse();
73
- }, [data, sortColumn, sortDirection]);
74
-
75
- function handleSort(column: DataTableColumn<T>) {
76
- if (!column.sortable) return;
77
- if (sortKey !== column.key) {
78
- setSortKey(column.key);
79
- setSortDirection('asc');
80
- } else if (sortDirection === 'asc') {
81
- setSortDirection('desc');
82
- } else {
83
- setSortKey(undefined);
84
- setSortDirection('asc');
85
- }
86
- }
87
-
88
- if (data.length === 0) {
89
- return <EmptyState heading={emptyMessage} />;
90
- }
91
-
92
- return (
93
- <Table className={className}>
94
- <TableHead>
95
- <TableRow>
96
- {columns.map((column) => {
97
- const isSorted = sortKey === column.key;
98
- const ariaSort = !column.sortable
99
- ? undefined
100
- : isSorted
101
- ? sortDirection === 'asc'
102
- ? 'ascending'
103
- : 'descending'
104
- : 'none';
105
- return (
106
- <TableHeaderCell
107
- key={column.key}
108
- aria-sort={ariaSort}
109
- style={column.align === 'end' ? { textAlign: 'end' } : undefined}
110
- >
111
- {column.sortable ? (
112
- <button
113
- type="button"
114
- className={styles.sortButton}
115
- onClick={() => handleSort(column)}
116
- >
117
- {column.header}
118
- <span className={styles.sortIcon}>
119
- {isSorted && (
120
- <Icon
121
- name={sortDirection === 'asc' ? 'chevron-up' : 'chevron-down'}
122
- size="sm"
123
- aria-hidden
124
- />
125
- )}
126
- </span>
127
- </button>
128
- ) : (
129
- column.header
130
- )}
131
- </TableHeaderCell>
132
- );
133
- })}
134
- </TableRow>
135
- </TableHead>
136
- <TableBody>
137
- {sortedData.map((row) => (
138
- <TableRow key={getRowKey(row)}>
139
- {columns.map((column) => (
140
- <TableCell
141
- key={column.key}
142
- style={column.align === 'end' ? { textAlign: 'end' } : undefined}
143
- >
144
- {column.render
145
- ? column.render(row)
146
- : String((row as Record<string, unknown>)[column.key] ?? '')}
147
- </TableCell>
148
- ))}
149
- </TableRow>
150
- ))}
151
- </TableBody>
152
- </Table>
153
- );
154
- }
@@ -1,123 +0,0 @@
1
- // @vitest-environment jsdom
2
- import { afterEach, describe, expect, it } from 'vitest';
3
- import { cleanup, fireEvent, render, screen, within } from '@testing-library/react';
4
- import { DataTable, type DataTableColumn } from '../DataTable';
5
-
6
- afterEach(cleanup);
7
-
8
- interface Plugin {
9
- id: string;
10
- name: string;
11
- installs: number;
12
- }
13
-
14
- const data: Plugin[] = [
15
- { id: 'a', name: 'Charlie', installs: 12 },
16
- { id: 'b', name: 'Alpha', installs: 30 },
17
- { id: 'c', name: 'Bravo', installs: 5 },
18
- ];
19
-
20
- const columns: DataTableColumn<Plugin>[] = [
21
- { key: 'name', header: 'Name', sortable: true },
22
- { key: 'installs', header: 'Installs', sortable: true, align: 'end' },
23
- ];
24
-
25
- function rowsText() {
26
- return screen
27
- .getAllByRole('row')
28
- .slice(1)
29
- .map((row) => within(row).getAllByRole('cell')[0]?.textContent);
30
- }
31
-
32
- describe('DataTable', () => {
33
- it('renders a row per data item and a header per column', () => {
34
- render(<DataTable columns={columns} data={data} getRowKey={(row) => row.id} />);
35
- expect(screen.getAllByRole('row')).toHaveLength(4); // 1 header + 3 body rows
36
- expect(screen.getByRole('columnheader', { name: 'Name' })).toBeDefined();
37
- expect(screen.getByRole('columnheader', { name: 'Installs' })).toBeDefined();
38
- });
39
-
40
- it('renders cell values from row[key] by default', () => {
41
- render(<DataTable columns={columns} data={data} getRowKey={(row) => row.id} />);
42
- expect(screen.getByText('Charlie')).toBeDefined();
43
- expect(screen.getByText('30')).toBeDefined();
44
- });
45
-
46
- it('uses a custom render function when provided', () => {
47
- const withRender: DataTableColumn<Plugin>[] = [
48
- { key: 'name', header: 'Name' },
49
- { key: 'installs', header: 'Installs', render: (row) => `${row.installs} installs` },
50
- ];
51
- render(<DataTable columns={withRender} data={data} getRowKey={(row) => row.id} />);
52
- expect(screen.getByText('30 installs')).toBeDefined();
53
- });
54
-
55
- it('shows an empty state instead of a table when data is empty', () => {
56
- render(
57
- <DataTable
58
- columns={columns}
59
- data={[]}
60
- getRowKey={(row) => row.id}
61
- emptyMessage="No plugins"
62
- />,
63
- );
64
- expect(screen.queryByRole('table')).toBeNull();
65
- expect(screen.getByText('No plugins')).toBeDefined();
66
- });
67
-
68
- it('non-sortable columns render as plain text, not a button', () => {
69
- const withPlain: DataTableColumn<Plugin>[] = [{ key: 'name', header: 'Name' }];
70
- render(<DataTable columns={withPlain} data={data} getRowKey={(row) => row.id} />);
71
- expect(screen.queryByRole('button', { name: 'Name' })).toBeNull();
72
- expect(screen.getByRole('columnheader', { name: 'Name' }).getAttribute('aria-sort')).toBeNull();
73
- });
74
-
75
- it('clicking a sortable header sorts ascending, then descending, then clears', () => {
76
- render(<DataTable columns={columns} data={data} getRowKey={(row) => row.id} />);
77
- const nameHeader = screen.getByRole('columnheader', { name: 'Name' });
78
- const button = within(nameHeader).getByRole('button');
79
-
80
- fireEvent.click(button);
81
- expect(rowsText()).toEqual(['Alpha', 'Bravo', 'Charlie']);
82
- expect(nameHeader.getAttribute('aria-sort')).toBe('ascending');
83
-
84
- fireEvent.click(button);
85
- expect(rowsText()).toEqual(['Charlie', 'Bravo', 'Alpha']);
86
- expect(nameHeader.getAttribute('aria-sort')).toBe('descending');
87
-
88
- fireEvent.click(button);
89
- expect(rowsText()).toEqual(['Charlie', 'Alpha', 'Bravo']);
90
- expect(nameHeader.getAttribute('aria-sort')).toBe('none');
91
- });
92
-
93
- it('sorts numeric columns numerically, not lexicographically', () => {
94
- render(<DataTable columns={columns} data={data} getRowKey={(row) => row.id} />);
95
- const installsHeader = screen.getByRole('columnheader', { name: 'Installs' });
96
- fireEvent.click(within(installsHeader).getByRole('button'));
97
- expect(rowsText()).toEqual(['Bravo', 'Charlie', 'Alpha']); // 5, 12, 30
98
- });
99
-
100
- it('switching the sort column resets to ascending', () => {
101
- render(<DataTable columns={columns} data={data} getRowKey={(row) => row.id} />);
102
- const nameHeader = screen.getByRole('columnheader', { name: 'Name' });
103
- const installsHeader = screen.getByRole('columnheader', { name: 'Installs' });
104
- fireEvent.click(within(nameHeader).getByRole('button'));
105
- fireEvent.click(within(nameHeader).getByRole('button')); // now descending
106
- fireEvent.click(within(installsHeader).getByRole('button'));
107
- expect(installsHeader.getAttribute('aria-sort')).toBe('ascending');
108
- expect(nameHeader.getAttribute('aria-sort')).toBe('none');
109
- });
110
-
111
- it('respects defaultSortKey/defaultSortDirection', () => {
112
- render(
113
- <DataTable
114
- columns={columns}
115
- data={data}
116
- getRowKey={(row) => row.id}
117
- defaultSortKey="installs"
118
- defaultSortDirection="desc"
119
- />,
120
- );
121
- expect(rowsText()).toEqual(['Alpha', 'Charlie', 'Bravo']); // 30, 12, 5
122
- });
123
- });
@@ -1,61 +0,0 @@
1
- /* DatePicker — trigger styled to match Input's height/border/radius/focus
2
- * ring for visual consistency as a form field. Kept in sync with Input's
3
- * own sizing by hand (36px rendered height via explicit height +
4
- * border-box, not a literal reuse of Input). */
5
-
6
- .trigger {
7
- display: flex;
8
- align-items: center;
9
- justify-content: space-between;
10
- gap: var(--sv-space-2);
11
- width: 100%;
12
- height: 36px;
13
- box-sizing: border-box;
14
- padding: 4px var(--sv-space-3);
15
- font-family: var(--sv-font-family);
16
- font-size: var(--sv-font-size-sm);
17
- line-height: 1.5;
18
- color: var(--sv-color-text-primary);
19
- background-color: var(--sv-color-surface);
20
- border: 1px solid var(--sv-color-border-strong);
21
- border-radius: var(--sv-radius-md);
22
- cursor: pointer;
23
- transition:
24
- border-color 0.15s ease,
25
- box-shadow 0.15s ease;
26
- }
27
-
28
- /* Same iOS auto-zoom fix as Input — see its own module.css for why. */
29
- @media (pointer: coarse) {
30
- .trigger {
31
- font-size: var(--sv-font-size-md);
32
- }
33
- }
34
-
35
- .trigger:focus-visible {
36
- outline: none;
37
- border-color: var(--sv-color-accent);
38
- box-shadow: 0 0 0 2px var(--sv-color-focus-ring);
39
- }
40
-
41
- .trigger:disabled {
42
- opacity: 0.5;
43
- cursor: not-allowed;
44
- }
45
-
46
- .triggerValue {
47
- color: var(--sv-color-text-primary);
48
- }
49
-
50
- .triggerPlaceholder {
51
- color: var(--sv-color-text-muted);
52
- }
53
-
54
- .popoverBody {
55
- padding: var(--sv-space-3);
56
- }
57
-
58
- .drawerBody {
59
- padding: var(--sv-space-4);
60
- padding-bottom: max(var(--sv-space-4), env(safe-area-inset-bottom));
61
- }
@@ -1,103 +0,0 @@
1
- 'use client';
2
-
3
- import { useState } from 'react';
4
- import { useIsMobile } from '../../hooks';
5
- import { Calendar } from '../Calendar/Calendar';
6
- import { formatDateShort } from '../Calendar/dateUtils';
7
- import { Drawer } from '../Drawer/Drawer';
8
- import { Icon } from '../Icon/Icon';
9
- import { Popover } from '../Popover/Popover';
10
- import styles from './DatePicker.module.css';
11
-
12
- export interface DatePickerProps {
13
- value: Date | null;
14
- onChange: (date: Date) => void;
15
- minDate?: Date;
16
- maxDate?: Date;
17
- placeholder?: string;
18
- 'aria-label': string;
19
- disabled?: boolean;
20
- }
21
-
22
- // Wide enough for the 7-column Calendar grid (44px touch-target cells) plus
23
- // its own padding, without being so wide it looks odd as a Popover panel.
24
- const POPOVER_WIDTH = 320;
25
-
26
- /**
27
- * DatePicker — a form field pairing a compact trigger with `Calendar`:
28
- * `Popover` on desktop, a bottom-sheet `Drawer` on mobile (the platform's
29
- * standard adaptive-surface pattern, matching `Menu`). Date-only (decision
30
- * D6 in the mobile design-system plan) — time and range selection are future
31
- * scope; recurrence UI stays plugin-side.
32
- *
33
- * Unlike `Menu`, the trigger is built in rather than supplied by the caller
34
- * — a date picker is a form field (consistent with `Input`/`Select`), not an
35
- * arbitrary action-menu trigger.
36
- */
37
- export function DatePicker({
38
- value,
39
- onChange,
40
- minDate,
41
- maxDate,
42
- placeholder = 'Select date',
43
- 'aria-label': ariaLabel,
44
- disabled = false,
45
- }: DatePickerProps) {
46
- const [open, setOpen] = useState(false);
47
- const isMobile = useIsMobile();
48
-
49
- function handleSelect(date: Date) {
50
- onChange(date);
51
- setOpen(false);
52
- }
53
-
54
- const trigger = (
55
- <button
56
- type="button"
57
- className={styles.trigger}
58
- disabled={disabled}
59
- aria-label={ariaLabel}
60
- aria-haspopup="dialog"
61
- aria-expanded={open}
62
- onClick={() => setOpen((o) => !o)}
63
- >
64
- <span className={value ? styles.triggerValue : styles.triggerPlaceholder}>
65
- {value ? formatDateShort(value) : placeholder}
66
- </span>
67
- <Icon name="calendar" size="sm" aria-hidden />
68
- </button>
69
- );
70
-
71
- const calendar = (
72
- <Calendar
73
- value={value}
74
- onChange={handleSelect}
75
- minDate={minDate}
76
- maxDate={maxDate}
77
- aria-label={ariaLabel}
78
- />
79
- );
80
-
81
- if (isMobile) {
82
- return (
83
- <>
84
- {trigger}
85
- <Drawer open={open} onClose={() => setOpen(false)} aria-label={ariaLabel}>
86
- <div className={styles.drawerBody}>{calendar}</div>
87
- </Drawer>
88
- </>
89
- );
90
- }
91
-
92
- return (
93
- <Popover
94
- trigger={trigger}
95
- open={open}
96
- onClose={() => setOpen(false)}
97
- aria-label={ariaLabel}
98
- width={POPOVER_WIDTH}
99
- >
100
- <div className={styles.popoverBody}>{calendar}</div>
101
- </Popover>
102
- );
103
- }
@@ -1,89 +0,0 @@
1
- // @vitest-environment jsdom
2
- import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
3
- import { cleanup, fireEvent, render, screen } from '@testing-library/react';
4
- import { DatePicker } from '../DatePicker';
5
-
6
- let mobile = false;
7
- function installMatchMedia() {
8
- vi.stubGlobal(
9
- 'matchMedia',
10
- vi.fn().mockImplementation((query: string) => ({
11
- // useIsMobile's query is '(max-width: ...)'; Drawer's own
12
- // prefers-reduced-motion check defaults to false — see Menu's
13
- // identical test setup for why.
14
- matches: query.includes('max-width') ? mobile : false,
15
- media: query,
16
- addEventListener: vi.fn(),
17
- removeEventListener: vi.fn(),
18
- addListener: vi.fn(),
19
- removeListener: vi.fn(),
20
- dispatchEvent: vi.fn(),
21
- })),
22
- );
23
- }
24
-
25
- const JAN_15_2026 = new Date(2026, 0, 15);
26
-
27
- describe('DatePicker', () => {
28
- beforeEach(() => {
29
- mobile = false;
30
- installMatchMedia();
31
- });
32
- afterEach(() => {
33
- vi.unstubAllGlobals();
34
- cleanup();
35
- });
36
-
37
- it('shows the placeholder when no date is selected', () => {
38
- render(
39
- <DatePicker
40
- value={null}
41
- onChange={() => {}}
42
- placeholder="Pick a date"
43
- aria-label="Due date"
44
- />,
45
- );
46
- expect(screen.getByRole('button', { name: 'Due date' }).textContent).toContain('Pick a date');
47
- });
48
-
49
- it('shows the formatted value when a date is selected', () => {
50
- render(<DatePicker value={JAN_15_2026} onChange={() => {}} aria-label="Due date" />);
51
- expect(screen.getByRole('button', { name: 'Due date' }).textContent).toContain('Jan 15, 2026');
52
- });
53
-
54
- it('opens the calendar as a Popover on desktop', () => {
55
- render(<DatePicker value={null} onChange={() => {}} aria-label="Due date" />);
56
- fireEvent.click(screen.getByRole('button', { name: 'Due date' }));
57
- const panel = screen.getByRole('dialog', { name: 'Due date' });
58
- expect(panel.getAttribute('aria-modal')).toBe('false');
59
- expect(screen.getByRole('grid')).toBeTruthy();
60
- });
61
-
62
- it('opens the calendar as a Drawer on mobile', () => {
63
- mobile = true;
64
- installMatchMedia();
65
- render(<DatePicker value={null} onChange={() => {}} aria-label="Due date" />);
66
- fireEvent.click(screen.getByRole('button', { name: 'Due date' }));
67
- const panel = screen.getByRole('dialog', { name: 'Due date' });
68
- expect(panel.getAttribute('aria-modal')).toBe('true');
69
- expect(screen.getByRole('grid')).toBeTruthy();
70
- });
71
-
72
- it('calls onChange and closes the picker when a date is selected', () => {
73
- const onChange = vi.fn();
74
- render(<DatePicker value={JAN_15_2026} onChange={onChange} aria-label="Due date" />);
75
- fireEvent.click(screen.getByRole('button', { name: 'Due date' }));
76
- fireEvent.click(screen.getByRole('button', { name: /January 20, 2026/ }));
77
- expect(onChange).toHaveBeenCalledOnce();
78
- const called = onChange.mock.calls[0]?.[0] as Date;
79
- expect(called.getDate()).toBe(20);
80
- expect(screen.queryByRole('grid')).toBeNull();
81
- });
82
-
83
- it('disables the trigger when disabled', () => {
84
- render(<DatePicker value={null} onChange={() => {}} aria-label="Due date" disabled />);
85
- expect((screen.getByRole('button', { name: 'Due date' }) as HTMLButtonElement).disabled).toBe(
86
- true,
87
- );
88
- });
89
- });
@@ -1,58 +0,0 @@
1
- 'use client';
2
-
3
- import type { ReactNode } from 'react';
4
- import { EmptyState } from '../EmptyState/EmptyState';
5
-
6
- export interface DeviceOnlyGateProps {
7
- children: ReactNode;
8
- /** Defaults to a generic "this app" phrasing — pass the plugin's own name. */
9
- surfaceName?: string;
10
- /**
11
- * The caller's own `isDeviceOnlyTierAvailable()` result (from
12
- * `@sovereignfs/sdk/device-client`) — taken as a plain boolean rather than
13
- * computed inside this component, so `@sovereignfs/ui` never depends on
14
- * `@sovereignfs/sdk` (this package must stay usable standalone, outside the
15
- * plugin runtime — RFC 0073).
16
- */
17
- available: boolean;
18
- className?: string;
19
- }
20
-
21
- /**
22
- * DeviceOnlyGate — blocks a `device-only`-tier plugin's content from
23
- * rendering on a surface that cannot provide the durable, encrypted,
24
- * device-auth-gated store that tier requires, showing an explanatory empty
25
- * state instead of a broken screen (research 0012, epic tasks 2.33 + 3.36).
26
- *
27
- * A `device-only` plugin wraps its own root content in this the same way
28
- * Console and Account wrap theirs in `OfflineGate` — an opt-in pattern each
29
- * surface applies to itself, not a platform-level route gate. The launcher
30
- * tile's own "Phone only" badge (`useOfflineTileState`) is advisory UI on top
31
- * of this; this component is the actual gate, since a user can always reach a
32
- * route directly (a bookmark, a deep link) without passing through the
33
- * launcher tile at all.
34
- *
35
- * **Not a security boundary.** Availability here is a capability signal, not
36
- * an authorization check — the real protection for `device-only` data is
37
- * that it is encrypted and the key requires device auth to release (epic task
38
- * 1.22), so it stays inaccessible regardless of whether this gate is present,
39
- * bypassed, or simply never reached (e.g. a hand-crafted request against the
40
- * plugin's own API routes).
41
- */
42
- export function DeviceOnlyGate({
43
- children,
44
- surfaceName,
45
- available,
46
- className,
47
- }: DeviceOnlyGateProps) {
48
- if (available) return <>{children}</>;
49
-
50
- return (
51
- <EmptyState
52
- icon="smartphone"
53
- heading="Phone only"
54
- description={`${surfaceName ?? 'This app'} is only available on a phone with secure storage set up.`}
55
- className={className}
56
- />
57
- );
58
- }
@@ -1,45 +0,0 @@
1
- // @vitest-environment jsdom
2
- import { afterEach, describe, expect, it } from 'vitest';
3
- import { cleanup, render, screen } from '@testing-library/react';
4
- import { DeviceOnlyGate } from '../DeviceOnlyGate';
5
-
6
- describe('DeviceOnlyGate', () => {
7
- afterEach(cleanup);
8
-
9
- it('renders children unchanged when available', () => {
10
- render(
11
- <DeviceOnlyGate available={true}>
12
- <p>Wallet content</p>
13
- </DeviceOnlyGate>,
14
- );
15
- expect(screen.getByText('Wallet content')).toBeDefined();
16
- });
17
-
18
- it('replaces children with an explanatory empty state when unavailable', () => {
19
- render(
20
- <DeviceOnlyGate available={false}>
21
- <p>Wallet content</p>
22
- </DeviceOnlyGate>,
23
- );
24
- expect(screen.queryByText('Wallet content')).toBeNull();
25
- expect(screen.getByText('Phone only')).toBeDefined();
26
- });
27
-
28
- it('names the surface in the message when provided', () => {
29
- render(
30
- <DeviceOnlyGate available={false} surfaceName="Wallet">
31
- <p>Wallet content</p>
32
- </DeviceOnlyGate>,
33
- );
34
- expect(screen.getByText(/Wallet is only available on a phone/)).toBeDefined();
35
- });
36
-
37
- it('falls back to generic phrasing with no surface name', () => {
38
- render(
39
- <DeviceOnlyGate available={false}>
40
- <p>Wallet content</p>
41
- </DeviceOnlyGate>,
42
- );
43
- expect(screen.getByText(/This app is only available on a phone/)).toBeDefined();
44
- });
45
- });