@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,176 +0,0 @@
1
- 'use client';
2
-
3
- import { type PointerEvent as ReactPointerEvent, type ReactNode, useRef } from 'react';
4
- import { useMountTransition, usePrefersReducedMotion } from '../../motion';
5
- import {
6
- useOverlayFocusCapture,
7
- useOverlayKeyboardTrap,
8
- useOverlayScrollLock,
9
- } from '../../overlay-shell';
10
- import { OverlayHeader } from '../OverlayHeader/OverlayHeader';
11
- import styles from './Drawer.module.css';
12
-
13
- // Matches --sv-motion-duration-base (Drawer.module.css) — see Dialog.tsx's
14
- // identical constant for why this stays a plain JS number instead of being
15
- // read from the CSS custom property.
16
- const MOTION_DURATION_MS = 250;
17
-
18
- export interface DrawerProps {
19
- /** Whether the drawer is shown. When false, nothing renders. */
20
- open: boolean;
21
- /** Called on Esc, scrim click, or a swipe-down past the dismiss threshold. */
22
- onClose: () => void;
23
- /** Accessible name for the drawer panel (sets `aria-label`). */
24
- 'aria-label'?: string;
25
- /** `'content'` (default) sizes to content, capped at 80dvh. `'half'` fixes
26
- * the panel to 50dvh regardless of content — per the design-system's
27
- * "half screen or less" convention for Drawer, this is the only other
28
- * supported size; taller content should use `Sheet` instead. */
29
- snapHeight?: 'content' | 'half';
30
- /** Renders a built-in `OverlayHeader` (title + close) below the grab
31
- * handle. Omit when the content supplies its own header, or none is
32
- * needed at all — e.g. the mobile shell's Apps drawer, whose grab handle
33
- * and swipe-down/scrim-tap dismissal are enough on their own. Mirrors
34
- * `Sheet`'s identical `title` prop. */
35
- title?: string;
36
- children: ReactNode;
37
- }
38
-
39
- // Downward drag distance (px) past which releasing dismisses the drawer
40
- // instead of snapping back open. Distance-only, no velocity tracking — matches
41
- // the swipe-to-reveal gesture already established in consuming plugins (see
42
- // the mobile design-system plan), not a separate technique invented here.
43
- const SWIPE_DISMISS_THRESHOLD_PX = 100;
44
-
45
- /**
46
- * Drawer — a dismissable bottom-sheet panel. Used by the mobile shell for
47
- * plugin navigation; also available to plugins for any bottom-up surface.
48
- *
49
- * Behaviour: Esc, scrim click, or dragging the grab handle down past
50
- * `SWIPE_DISMISS_THRESHOLD_PX` all dismiss; focus moves to the first
51
- * focusable element on open and is restored on close; Tab is trapped within
52
- * the panel. The panel respects `env(safe-area-inset-bottom)` so it clears
53
- * the home indicator in standalone/fullscreen mode. Shares dismissal
54
- * conventions and tokens (`--sv-color-scrim`, `--sv-shadow-overlay`) with
55
- * `Dialog`.
56
- *
57
- * Animated open/close: slides up from the bottom, scrim fades. The `open`/
58
- * `onClose` API is unchanged — closing stays mounted internally for the exit
59
- * transition before actually unmounting; `prefers-reduced-motion: reduce`
60
- * collapses it to near-instant.
61
- *
62
- * The grab handle is the *only* drag-initiation region (not the whole panel)
63
- * — dragging from body content would fight that content's own internal
64
- * scroll, the same reasoning behind the tasks plugin's edge-zone-only
65
- * swipe-to-reveal technique.
66
- */
67
- export function Drawer({
68
- open,
69
- onClose,
70
- 'aria-label': ariaLabel,
71
- snapHeight = 'content',
72
- title,
73
- children,
74
- }: DrawerProps) {
75
- const panelRef = useRef<HTMLDivElement>(null);
76
- const reducedMotion = usePrefersReducedMotion();
77
- const { mounted, phase } = useMountTransition(open, reducedMotion ? 0 : MOTION_DURATION_MS);
78
- // Tracks an in-progress handle drag. Kept in a ref (not state) so pointermove
79
- // updates the DOM directly at 60fps instead of re-rendering on every event —
80
- // same technique as the tasks plugin's swipe-to-reveal rows.
81
- const dragStartY = useRef<number | null>(null);
82
-
83
- useOverlayScrollLock(mounted);
84
- useOverlayFocusCapture(panelRef, open);
85
- useOverlayKeyboardTrap(panelRef, open, onClose);
86
-
87
- // Swipe-down-to-dismiss, initiated only from the grab handle (see the
88
- // component doc comment for why not the whole panel). Drags the panel
89
- // directly via inline style while active (transition: none, so it tracks
90
- // the finger 1:1 with no lag) and lets go on release: clearing the inline
91
- // transform in the same commit that re-enables the CSS transition makes
92
- // the panel animate from wherever the drag ended to its resolved position
93
- // — open (snap back) or closed (the exit transition, same as a
94
- // programmatic close) — rather than jump-cutting there. Same technique as
95
- // the tasks plugin's row swipe-to-reveal.
96
- function handleHandlePointerDown(e: ReactPointerEvent) {
97
- e.currentTarget.setPointerCapture(e.pointerId);
98
- dragStartY.current = e.clientY;
99
- }
100
-
101
- function handleHandlePointerMove(e: ReactPointerEvent) {
102
- const startY = dragStartY.current;
103
- if (startY === null) return;
104
- const dy = Math.max(0, e.clientY - startY); // upward drag has no effect — already fully open
105
- const panel = panelRef.current;
106
- if (!panel) return;
107
- panel.style.transition = 'none';
108
- panel.style.transform = `translateY(${dy}px)`;
109
- }
110
-
111
- // Releases the drag and resolves it to open (snap back) or closed, based on
112
- // distance. Shared by pointerup (a completed gesture) and pointercancel
113
- // (the browser took the gesture away, e.g. for a system gesture) — cancel
114
- // always snaps back rather than honoring the distance, since it wasn't a
115
- // deliberate release.
116
- function releaseDrag(e: ReactPointerEvent, honorDistance: boolean) {
117
- const startY = dragStartY.current;
118
- dragStartY.current = null;
119
- if (startY === null) return;
120
- const dy = Math.max(0, e.clientY - startY);
121
- const panel = panelRef.current;
122
- if (panel) {
123
- panel.style.transform = '';
124
- panel.style.transition = '';
125
- }
126
- if (honorDistance && dy > SWIPE_DISMISS_THRESHOLD_PX) onClose();
127
- }
128
-
129
- if (!mounted) return null;
130
- const isOpenPhase = phase === 'open';
131
-
132
- return (
133
- // role="presentation" removes the scrim from the AT (it is purely visual).
134
- // e.target check lets clicks inside the panel bubble without triggering dismiss.
135
- <div
136
- className={[styles.scrim, isOpenPhase ? styles.scrimOpen : ''].filter(Boolean).join(' ')}
137
- role="presentation"
138
- onClick={(e) => {
139
- if (e.target === e.currentTarget) onClose();
140
- }}
141
- onKeyDown={(e) => {
142
- if (e.key === 'Escape') onClose();
143
- }}
144
- >
145
- <div
146
- ref={panelRef}
147
- role="dialog"
148
- aria-modal="true"
149
- aria-label={ariaLabel}
150
- tabIndex={-1}
151
- className={[
152
- styles.panel,
153
- snapHeight === 'half' ? styles.panelHalf : '',
154
- isOpenPhase ? styles.panelOpen : '',
155
- ]
156
- .filter(Boolean)
157
- .join(' ')}
158
- >
159
- {/* The only drag-initiation region for swipe-down-to-dismiss — see the
160
- component doc comment for why not the whole panel. Decorative +
161
- functional, so no aria-label of its own; Esc and the scrim remain
162
- the accessible dismiss paths. */}
163
- <div
164
- className={styles.handle}
165
- aria-hidden="true"
166
- onPointerDown={handleHandlePointerDown}
167
- onPointerMove={handleHandlePointerMove}
168
- onPointerUp={(e) => releaseDrag(e, true)}
169
- onPointerCancel={(e) => releaseDrag(e, false)}
170
- />
171
- {title && <OverlayHeader title={title} onClose={onClose} />}
172
- {children}
173
- </div>
174
- </div>
175
- );
176
- }
@@ -1,199 +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 { Drawer } from '../Drawer';
5
-
6
- // Drawer's exit animation reads prefers-reduced-motion via matchMedia, which
7
- // jsdom does not implement. `matches: false` (motion enabled) exercises the
8
- // normal animated path; the actual reduced-motion behaviour is covered by
9
- // motion.ts's own tests.
10
- function installMatchMedia() {
11
- vi.stubGlobal(
12
- 'matchMedia',
13
- vi.fn().mockImplementation((query: string) => ({
14
- matches: 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
- // jsdom does not implement Element.setPointerCapture/releasePointerCapture
26
- // (another documented gap, alongside matchMedia and HTMLDialogElement) — the
27
- // grab handle's pointerdown handler calls setPointerCapture unconditionally.
28
- function installPointerCapture() {
29
- if (!Element.prototype.setPointerCapture) {
30
- Element.prototype.setPointerCapture = () => {};
31
- }
32
- if (!Element.prototype.releasePointerCapture) {
33
- Element.prototype.releasePointerCapture = () => {};
34
- }
35
- }
36
-
37
- describe('Drawer', () => {
38
- beforeEach(() => {
39
- installMatchMedia();
40
- installPointerCapture();
41
- });
42
- afterEach(() => {
43
- vi.unstubAllGlobals();
44
- cleanup();
45
- });
46
-
47
- it('renders nothing when closed', () => {
48
- render(
49
- <Drawer open={false} onClose={() => {}}>
50
- Body
51
- </Drawer>,
52
- );
53
- expect(screen.queryByRole('dialog')).toBeNull();
54
- });
55
-
56
- it('renders the panel with its accessible name when open', () => {
57
- render(
58
- <Drawer open onClose={() => {}} aria-label="Navigation">
59
- Body
60
- </Drawer>,
61
- );
62
- const panel = screen.getByRole('dialog', { name: 'Navigation' });
63
- expect(panel.getAttribute('aria-modal')).toBe('true');
64
- });
65
-
66
- it('calls onClose on Escape', () => {
67
- const onClose = vi.fn();
68
- render(
69
- <Drawer open onClose={onClose}>
70
- Body
71
- </Drawer>,
72
- );
73
- // Keyboard handler is registered on document (not the drawer element) so
74
- // screen readers and keyboard users can dismiss from anywhere in the page.
75
- fireEvent.keyDown(document, { key: 'Escape' });
76
- expect(onClose).toHaveBeenCalledOnce();
77
- });
78
-
79
- it('calls onClose on scrim click but not on panel click', () => {
80
- const onClose = vi.fn();
81
- const { container } = render(
82
- <Drawer open onClose={onClose} aria-label="Nav">
83
- <button>Action</button>
84
- </Drawer>,
85
- );
86
- // The scrim is the outermost div; the panel is a child.
87
- fireEvent.click(screen.getByRole('dialog')); // panel — should not close
88
- expect(onClose).not.toHaveBeenCalled();
89
- // Click the scrim background (the container's first child).
90
- const scrim = container.firstElementChild as HTMLElement;
91
- fireEvent.click(scrim);
92
- expect(onClose).toHaveBeenCalledOnce();
93
- });
94
-
95
- it('applies the half snapHeight class', () => {
96
- render(
97
- <Drawer open onClose={() => {}} aria-label="Nav" snapHeight="half">
98
- Body
99
- </Drawer>,
100
- );
101
- expect(screen.getByRole('dialog').className).toMatch(/panelHalf/);
102
- });
103
-
104
- it('does not apply the half snapHeight class by default (content-sized)', () => {
105
- render(
106
- <Drawer open onClose={() => {}} aria-label="Nav">
107
- Body
108
- </Drawer>,
109
- );
110
- expect(screen.getByRole('dialog').className).not.toMatch(/panelHalf/);
111
- });
112
-
113
- it('does not render OverlayHeader when title is omitted (hidden by default)', () => {
114
- render(
115
- <Drawer open onClose={() => {}} aria-label="Nav">
116
- <button type="button">Own action</button>
117
- </Drawer>,
118
- );
119
- // Only the content's own button — no OverlayHeader close button.
120
- expect(screen.getAllByRole('button')).toHaveLength(1);
121
- });
122
-
123
- it('renders OverlayHeader (title + close) when title is provided', () => {
124
- const onClose = vi.fn();
125
- render(
126
- <Drawer open onClose={onClose} aria-label="Nav" title="Apps">
127
- Body
128
- </Drawer>,
129
- );
130
- expect(screen.getByText('Apps')).toBeTruthy();
131
- fireEvent.click(screen.getByRole('button', { name: 'Close' }));
132
- expect(onClose).toHaveBeenCalledOnce();
133
- });
134
-
135
- describe('swipe-down-to-dismiss (grab handle)', () => {
136
- // The handle is the panel's first child, aria-hidden and unlabeled —
137
- // same "select via DOM position" approach the scrim-click test above
138
- // already uses for a similarly unlabeled element.
139
- function getHandle() {
140
- return screen.getByRole('dialog').firstElementChild as HTMLElement;
141
- }
142
-
143
- it('dismisses when dragged past the threshold', () => {
144
- const onClose = vi.fn();
145
- render(
146
- <Drawer open onClose={onClose} aria-label="Nav">
147
- Body
148
- </Drawer>,
149
- );
150
- const handle = getHandle();
151
- fireEvent.pointerDown(handle, { clientY: 0 });
152
- fireEvent.pointerMove(handle, { clientY: 150 });
153
- fireEvent.pointerUp(handle, { clientY: 150 }); // 150px > 100px threshold
154
- expect(onClose).toHaveBeenCalledOnce();
155
- });
156
-
157
- it('snaps back without dismissing when released under the threshold', () => {
158
- const onClose = vi.fn();
159
- render(
160
- <Drawer open onClose={onClose} aria-label="Nav">
161
- Body
162
- </Drawer>,
163
- );
164
- const handle = getHandle();
165
- fireEvent.pointerDown(handle, { clientY: 0 });
166
- fireEvent.pointerMove(handle, { clientY: 40 });
167
- fireEvent.pointerUp(handle, { clientY: 40 }); // 40px < 100px threshold
168
- expect(onClose).not.toHaveBeenCalled();
169
- });
170
-
171
- it('snaps back without dismissing on pointercancel, regardless of distance', () => {
172
- const onClose = vi.fn();
173
- render(
174
- <Drawer open onClose={onClose} aria-label="Nav">
175
- Body
176
- </Drawer>,
177
- );
178
- const handle = getHandle();
179
- fireEvent.pointerDown(handle, { clientY: 0 });
180
- fireEvent.pointerMove(handle, { clientY: 200 }); // well past the threshold
181
- fireEvent.pointerCancel(handle, { clientY: 200 });
182
- expect(onClose).not.toHaveBeenCalled();
183
- });
184
-
185
- it('ignores upward drags (already fully open)', () => {
186
- const onClose = vi.fn();
187
- render(
188
- <Drawer open onClose={onClose} aria-label="Nav">
189
- Body
190
- </Drawer>,
191
- );
192
- const handle = getHandle();
193
- fireEvent.pointerDown(handle, { clientY: 100 });
194
- fireEvent.pointerMove(handle, { clientY: 0 }); // upward
195
- fireEvent.pointerUp(handle, { clientY: 0 });
196
- expect(onClose).not.toHaveBeenCalled();
197
- });
198
- });
199
- });
@@ -1,27 +0,0 @@
1
- .root {
2
- display: flex;
3
- flex-direction: column;
4
- align-items: center;
5
- justify-content: center;
6
- gap: var(--sv-space-3);
7
- padding: var(--sv-space-12) var(--sv-space-6);
8
- text-align: center;
9
- }
10
-
11
- .icon {
12
- color: var(--sv-color-text-subtle);
13
- }
14
-
15
- .heading {
16
- margin: 0;
17
- font-size: var(--sv-font-size-md);
18
- font-weight: var(--sv-font-weight-semibold);
19
- color: var(--sv-color-text-primary);
20
- }
21
-
22
- .description {
23
- margin: 0;
24
- font-size: var(--sv-font-size-sm);
25
- color: var(--sv-color-text-muted);
26
- max-width: 36ch;
27
- }
@@ -1,27 +0,0 @@
1
- import type { ReactNode } from 'react';
2
- import { Icon } from '../Icon/Icon';
3
- import type { IconName } from '../Icon/Icon';
4
- import styles from './EmptyState.module.css';
5
-
6
- export interface EmptyStateProps {
7
- icon?: IconName;
8
- heading: string;
9
- description?: string;
10
- action?: ReactNode;
11
- className?: string;
12
- }
13
-
14
- export function EmptyState({ icon, heading, description, action, className }: EmptyStateProps) {
15
- return (
16
- <div className={[styles.root, className].filter(Boolean).join(' ')}>
17
- {icon && (
18
- <span className={styles.icon}>
19
- <Icon name={icon} size="lg" aria-hidden={true} />
20
- </span>
21
- )}
22
- <h2 className={styles.heading}>{heading}</h2>
23
- {description && <p className={styles.description}>{description}</p>}
24
- {action}
25
- </div>
26
- );
27
- }
@@ -1,63 +0,0 @@
1
- .dropzone {
2
- display: flex;
3
- align-items: center;
4
- gap: var(--sv-space-4);
5
- width: 100%;
6
- box-sizing: border-box;
7
- padding: var(--sv-space-4) var(--sv-space-5);
8
- border: 1.5px dashed var(--sv-color-border);
9
- border-radius: var(--sv-radius-md);
10
- background-color: var(--sv-color-surface-raised);
11
- cursor: pointer;
12
- transition:
13
- background-color 0.12s ease,
14
- border-color 0.12s ease;
15
- }
16
-
17
- @media (hover: hover) {
18
- .dropzone:hover {
19
- background-color: var(--sv-color-surface-sunken);
20
- border-color: var(--sv-color-border-strong);
21
- }
22
- }
23
-
24
- .dropzoneActive {
25
- background-color: var(--sv-color-surface-sunken);
26
- border-color: var(--sv-color-border-strong);
27
- }
28
-
29
- .dropzoneDisabled {
30
- opacity: 0.5;
31
- cursor: not-allowed;
32
- }
33
-
34
- .icon {
35
- display: inline-flex;
36
- flex-shrink: 0;
37
- color: var(--sv-color-text-muted);
38
- }
39
-
40
- .text {
41
- display: flex;
42
- flex-direction: column;
43
- gap: 2px;
44
- min-width: 0;
45
- }
46
-
47
- .title {
48
- font-size: var(--sv-font-size-sm);
49
- font-weight: var(--sv-font-weight-medium);
50
- color: var(--sv-color-text-primary);
51
- overflow: hidden;
52
- text-overflow: ellipsis;
53
- white-space: nowrap;
54
- }
55
-
56
- .hint {
57
- font-size: var(--sv-font-size-xs);
58
- color: var(--sv-color-text-muted);
59
- }
60
-
61
- .hiddenInput {
62
- display: none;
63
- }
@@ -1,109 +0,0 @@
1
- 'use client';
2
-
3
- import type { DragEvent, ReactNode } from 'react';
4
- import { useRef, useState } from 'react';
5
- import { Icon } from '../Icon/Icon';
6
- import styles from './FileDropzone.module.css';
7
-
8
- export interface FileDropzoneProps {
9
- /** Native `<input type="file">` `name` attribute — set this to submit the
10
- * file as part of a native `<form action={...}>` (Server Action) request. */
11
- name?: string;
12
- /** Native `<input type="file">` `accept` attribute, e.g. `".zip,application/zip"`. */
13
- accept?: string;
14
- /** Icon shown beside the label — a file-type glyph. Defaults to a generic file icon. */
15
- icon?: ReactNode;
16
- /** Primary label — e.g. "Choose a ZIP file", or the selected file's name. */
17
- label: string;
18
- /** Secondary hint — e.g. "or drag and drop here", or the selected file's size. */
19
- hint?: string;
20
- /** Called with the picked or dropped file, or `null` if the input is cleared. */
21
- onFileSelect: (file: File | null) => void;
22
- /** Accessible name for the control — describe what will be uploaded. */
23
- ariaLabel: string;
24
- disabled?: boolean;
25
- className?: string;
26
- }
27
-
28
- /**
29
- * A styled drag-and-drop file picker: a dashed-border dropzone wrapping a
30
- * visually-hidden native `<input type="file">`, so the native picker still
31
- * opens on click and the field participates in normal form semantics. A
32
- * dropped file is written into the input's own `FileList` (via `DataTransfer`)
33
- * so `name` + native `<form action>` submission sees it exactly like a
34
- * click-picked file — not just the `onFileSelect` callback.
35
- *
36
- * Caller owns the selected-file state and passes `label`/`hint` accordingly
37
- * (e.g. swap to the picked file's name/size) — this component is otherwise
38
- * uncontrolled and stateless beyond its own drag-hover visual.
39
- *
40
- * Do not pass `required` through to a wrapping form's validation via this
41
- * field — `display: none` on the hidden input does not exempt it from HTML5
42
- * constraint validation, so a `required` hidden input can block submission
43
- * with no visible field for the user to focus. Validate `onFileSelect`'s
44
- * result in application code instead.
45
- */
46
- export function FileDropzone({
47
- name,
48
- accept,
49
- icon,
50
- label,
51
- hint,
52
- onFileSelect,
53
- ariaLabel,
54
- disabled,
55
- className,
56
- }: FileDropzoneProps) {
57
- const [dragging, setDragging] = useState(false);
58
- const inputRef = useRef<HTMLInputElement>(null);
59
-
60
- return (
61
- <label
62
- className={[
63
- styles.dropzone,
64
- dragging ? styles.dropzoneActive : '',
65
- disabled ? styles.dropzoneDisabled : '',
66
- className,
67
- ]
68
- .filter(Boolean)
69
- .join(' ')}
70
- onDragOver={(event: DragEvent) => {
71
- if (disabled) return;
72
- event.preventDefault();
73
- setDragging(true);
74
- }}
75
- onDragLeave={() => setDragging(false)}
76
- onDrop={(event: DragEvent) => {
77
- if (disabled) return;
78
- event.preventDefault();
79
- setDragging(false);
80
- const dropped = event.dataTransfer.files[0];
81
- if (!dropped) return;
82
- if (inputRef.current && typeof DataTransfer !== 'undefined') {
83
- const transfer = new DataTransfer();
84
- transfer.items.add(dropped);
85
- inputRef.current.files = transfer.files;
86
- }
87
- onFileSelect(dropped);
88
- }}
89
- >
90
- <span className={styles.icon} aria-hidden="true">
91
- {icon ?? <Icon name="file" size="lg" aria-hidden />}
92
- </span>
93
- <span className={styles.text}>
94
- <span className={styles.title}>{label}</span>
95
- {hint && <span className={styles.hint}>{hint}</span>}
96
- </span>
97
- <input
98
- ref={inputRef}
99
- type="file"
100
- name={name}
101
- aria-label={ariaLabel}
102
- accept={accept}
103
- disabled={disabled}
104
- onChange={(event) => onFileSelect(event.target.files?.[0] ?? null)}
105
- className={styles.hiddenInput}
106
- />
107
- </label>
108
- );
109
- }
@@ -1,67 +0,0 @@
1
- // @vitest-environment jsdom
2
- import { afterEach, describe, expect, it, vi } from 'vitest';
3
- import { cleanup, fireEvent, render, screen } from '@testing-library/react';
4
- import { FileDropzone } from '../FileDropzone';
5
-
6
- afterEach(cleanup);
7
-
8
- describe('FileDropzone', () => {
9
- it('renders the label and hint', () => {
10
- render(
11
- <FileDropzone
12
- label="Choose a ZIP file"
13
- hint="or drag and drop here"
14
- ariaLabel="Upload ZIP file"
15
- onFileSelect={vi.fn()}
16
- />,
17
- );
18
- expect(screen.getByText('Choose a ZIP file')).toBeDefined();
19
- expect(screen.getByText('or drag and drop here')).toBeDefined();
20
- });
21
-
22
- it('exposes an accessible name via aria-label', () => {
23
- render(
24
- <FileDropzone label="Choose a file" ariaLabel="Upload font file" onFileSelect={vi.fn()} />,
25
- );
26
- expect(screen.getByLabelText('Upload font file')).toBeDefined();
27
- });
28
-
29
- it('forwards accept to the native file input', () => {
30
- render(
31
- <FileDropzone
32
- label="Choose a file"
33
- ariaLabel="Upload"
34
- accept=".zip"
35
- onFileSelect={vi.fn()}
36
- />,
37
- );
38
- const input = screen.getByLabelText('Upload') as HTMLInputElement;
39
- expect(input.accept).toBe('.zip');
40
- });
41
-
42
- it('calls onFileSelect with the picked file', () => {
43
- const onFileSelect = vi.fn();
44
- render(<FileDropzone label="Choose a file" ariaLabel="Upload" onFileSelect={onFileSelect} />);
45
- const input = screen.getByLabelText('Upload') as HTMLInputElement;
46
- const file = new File(['content'], 'font.woff2');
47
- fireEvent.change(input, { target: { files: [file] } });
48
- expect(onFileSelect).toHaveBeenCalledWith(file);
49
- });
50
-
51
- it('calls onFileSelect with the dropped file', () => {
52
- const onFileSelect = vi.fn();
53
- render(<FileDropzone label="Choose a file" ariaLabel="Upload" onFileSelect={onFileSelect} />);
54
- const dropzone = screen.getByLabelText('Upload').closest('label');
55
- if (!dropzone) throw new Error('dropzone label not found');
56
- const file = new File(['content'], 'font.woff2');
57
- fireEvent.drop(dropzone, { dataTransfer: { files: [file] } });
58
- expect(onFileSelect).toHaveBeenCalledWith(file);
59
- });
60
-
61
- it('is disabled when the disabled prop is set', () => {
62
- render(
63
- <FileDropzone label="Choose a file" ariaLabel="Upload" disabled onFileSelect={vi.fn()} />,
64
- );
65
- expect((screen.getByLabelText('Upload') as HTMLInputElement).disabled).toBe(true);
66
- });
67
- });