@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,357 +0,0 @@
1
- import { useState } from 'react';
2
- import type { Meta, StoryObj } from '@storybook/react-vite';
3
- import { Popover } from './Popover';
4
- import { Button } from '../Button/Button';
5
-
6
- const meta = {
7
- title: 'Components/Popover',
8
- component: Popover,
9
- parameters: {
10
- layout: 'centered',
11
- docs: {
12
- description: {
13
- component:
14
- 'Floating panel anchored to a trigger, below it by default. Non-modal: closes on outside click or Escape. `align` controls which trigger edge the panel aligns to; `width` accepts a fixed px number or `"trigger"` to match the trigger\'s own rendered width. Automatically flips to open upward instead of downward if the panel wouldn\'t fit below the trigger within the viewport.',
15
- },
16
- },
17
- },
18
- args: {
19
- open: false,
20
- onClose: () => {},
21
- trigger: <button type="button">Trigger</button>,
22
- 'aria-label': 'Popover',
23
- children: null,
24
- },
25
- } satisfies Meta<typeof Popover>;
26
-
27
- export default meta;
28
- type Story = StoryObj<typeof meta>;
29
-
30
- // ---------------------------------------------------------------------------
31
-
32
- const rowStyle: React.CSSProperties = {
33
- display: 'flex',
34
- alignItems: 'center',
35
- gap: 12,
36
- padding: '10px 16px',
37
- fontFamily: 'var(--sv-font-family)',
38
- fontSize: 'var(--sv-font-size-sm)',
39
- color: 'var(--sv-color-text-primary)',
40
- borderBottom: '1px solid var(--sv-color-border)',
41
- cursor: 'pointer',
42
- };
43
-
44
- /** Notification bell popover — right-aligned, 288px. */
45
- export const Notifications: Story = {
46
- render: (_args) => {
47
- const [open, setOpen] = useState(false);
48
-
49
- return (
50
- <div style={{ padding: 40 }}>
51
- <Popover
52
- open={open}
53
- onClose={() => setOpen(false)}
54
- align="right"
55
- aria-label="Notifications"
56
- trigger={
57
- <Button variant="ghost" onClick={() => setOpen((o) => !o)} aria-label="Notifications">
58
- 🔔
59
- </Button>
60
- }
61
- >
62
- <div
63
- style={{
64
- display: 'flex',
65
- alignItems: 'center',
66
- justifyContent: 'space-between',
67
- padding: '12px 16px',
68
- borderBottom: '1px solid var(--sv-color-border)',
69
- fontFamily: 'var(--sv-font-family)',
70
- }}
71
- >
72
- <span
73
- style={{
74
- fontWeight: 'var(--sv-font-weight-semibold)',
75
- fontSize: 'var(--sv-font-size-sm)',
76
- color: 'var(--sv-color-text-primary)',
77
- }}
78
- >
79
- Notifications
80
- </span>
81
- <button
82
- type="button"
83
- style={{
84
- background: 'none',
85
- border: 'none',
86
- fontSize: 'var(--sv-font-size-caption)',
87
- color: 'var(--sv-color-text-muted)',
88
- cursor: 'pointer',
89
- fontFamily: 'var(--sv-font-family)',
90
- }}
91
- >
92
- Mark all read
93
- </button>
94
- </div>
95
- <div style={rowStyle}>
96
- <span style={{ fontSize: 20 }}>📦</span>
97
- <div style={{ flex: 1 }}>
98
- <div style={{ fontWeight: 'var(--sv-font-weight-medium)' }}>Plugin updated</div>
99
- <div
100
- style={{
101
- fontSize: 'var(--sv-font-size-caption)',
102
- color: 'var(--sv-color-text-muted)',
103
- }}
104
- >
105
- 1 hour ago
106
- </div>
107
- </div>
108
- </div>
109
- <div style={rowStyle}>
110
- <span style={{ fontSize: 20 }}>✉️</span>
111
- <div style={{ flex: 1 }}>
112
- <div style={{ fontWeight: 'var(--sv-font-weight-medium)' }}>Invite accepted</div>
113
- <div
114
- style={{
115
- fontSize: 'var(--sv-font-size-caption)',
116
- color: 'var(--sv-color-text-muted)',
117
- }}
118
- >
119
- 2 min ago
120
- </div>
121
- </div>
122
- </div>
123
- </Popover>
124
- </div>
125
- );
126
- },
127
- };
128
-
129
- /** User menu — right-aligned with identity header and nav rows. */
130
- export const UserMenu: Story = {
131
- render: (_args) => {
132
- const [open, setOpen] = useState(false);
133
-
134
- const menuRowStyle: React.CSSProperties = {
135
- ...rowStyle,
136
- gap: 10,
137
- cursor: 'default',
138
- };
139
-
140
- return (
141
- <div style={{ padding: 40 }}>
142
- <Popover
143
- open={open}
144
- onClose={() => setOpen(false)}
145
- align="right"
146
- aria-label="User menu"
147
- trigger={
148
- <button
149
- type="button"
150
- onClick={() => setOpen((o) => !o)}
151
- style={{
152
- width: 32,
153
- height: 32,
154
- borderRadius: '50%',
155
- background: 'var(--sv-color-accent)',
156
- color: 'var(--sv-color-text-on-accent)',
157
- border: 'none',
158
- cursor: 'pointer',
159
- fontFamily: 'var(--sv-font-family)',
160
- fontWeight: 600,
161
- fontSize: 13,
162
- }}
163
- aria-label="Open user menu"
164
- >
165
- KB
166
- </button>
167
- }
168
- >
169
- <div
170
- style={{
171
- padding: '12px 16px',
172
- borderBottom: '1px solid var(--sv-color-border)',
173
- fontFamily: 'var(--sv-font-family)',
174
- }}
175
- >
176
- <div
177
- style={{
178
- fontWeight: 'var(--sv-font-weight-semibold)',
179
- fontSize: 'var(--sv-font-size-sm)',
180
- color: 'var(--sv-color-text-primary)',
181
- }}
182
- >
183
- Kasun Benthara
184
- </div>
185
- <div
186
- style={{
187
- fontSize: 'var(--sv-font-size-caption)',
188
- color: 'var(--sv-color-text-muted)',
189
- }}
190
- >
191
- kasun@openfs.io
192
- </div>
193
- </div>
194
- {['Account', 'Console', 'Sign out'].map((item) => (
195
- <div key={item} style={menuRowStyle}>
196
- {item}
197
- </div>
198
- ))}
199
- </Popover>
200
- </div>
201
- );
202
- },
203
- };
204
-
205
- /** Left-aligned variant. */
206
- export const LeftAligned: Story = {
207
- render: (_args) => {
208
- const [open, setOpen] = useState(false);
209
- return (
210
- <div style={{ padding: 40 }}>
211
- <Popover
212
- open={open}
213
- onClose={() => setOpen(false)}
214
- align="left"
215
- aria-label="Options"
216
- trigger={<Button onClick={() => setOpen((o) => !o)}>Open (left-aligned)</Button>}
217
- >
218
- <div
219
- style={{
220
- padding: 16,
221
- fontFamily: 'var(--sv-font-family)',
222
- fontSize: 'var(--sv-font-size-sm)',
223
- color: 'var(--sv-color-text-muted)',
224
- }}
225
- >
226
- Panel aligns to the left edge of the trigger.
227
- </div>
228
- </Popover>
229
- </div>
230
- );
231
- },
232
- };
233
-
234
- /** `panelStyle` overrides the panel's own chrome (square corners here) —
235
- * an escape hatch for compact pickers where the default rounded panel
236
- * doesn't fit the content (e.g. a small colour-swatch grid). */
237
- export const SquareCorners: Story = {
238
- render: (_args) => {
239
- const [open, setOpen] = useState(false);
240
- return (
241
- <div style={{ padding: 40 }}>
242
- <Popover
243
- open={open}
244
- onClose={() => setOpen(false)}
245
- align="left"
246
- width={160}
247
- panelStyle={{ borderRadius: 0 }}
248
- aria-label="Colour"
249
- trigger={<Button onClick={() => setOpen((o) => !o)}>Open (square panel)</Button>}
250
- >
251
- <div
252
- style={{
253
- padding: 16,
254
- fontFamily: 'var(--sv-font-family)',
255
- fontSize: 'var(--sv-font-size-sm)',
256
- color: 'var(--sv-color-text-muted)',
257
- }}
258
- >
259
- No rounded corners.
260
- </div>
261
- </Popover>
262
- </div>
263
- );
264
- },
265
- };
266
-
267
- /** `width="trigger"` matches the panel's width to the trigger's own rendered
268
- * width — for a dropdown under a full-width form field, a fixed px value
269
- * would either overflow or leave a gap depending on the field's own width. */
270
- export const MatchesTriggerWidth: Story = {
271
- render: (_args) => {
272
- const [open, setOpen] = useState(false);
273
- return (
274
- <div style={{ padding: 40, width: 360 }}>
275
- <Popover
276
- open={open}
277
- onClose={() => setOpen(false)}
278
- align="left"
279
- width="trigger"
280
- aria-label="Set due date"
281
- trigger={
282
- <button
283
- type="button"
284
- onClick={() => setOpen((o) => !o)}
285
- style={{
286
- width: '100%',
287
- boxSizing: 'border-box',
288
- textAlign: 'left',
289
- padding: '10px 14px',
290
- border: '1px solid var(--sv-color-border)',
291
- borderRadius: 'var(--sv-radius-md)',
292
- background: 'var(--sv-color-surface-raised)',
293
- fontFamily: 'var(--sv-font-family)',
294
- fontSize: 'var(--sv-font-size-sm)',
295
- color: 'var(--sv-color-text-primary)',
296
- cursor: 'pointer',
297
- }}
298
- >
299
- No due date
300
- </button>
301
- }
302
- >
303
- <div
304
- style={{
305
- padding: 16,
306
- fontFamily: 'var(--sv-font-family)',
307
- fontSize: 'var(--sv-font-size-sm)',
308
- color: 'var(--sv-color-text-muted)',
309
- }}
310
- >
311
- Panel width matches the 360px trigger above exactly, instead of a fixed pixel value that
312
- would overflow or leave a gap at other trigger widths.
313
- </div>
314
- </Popover>
315
- </div>
316
- );
317
- },
318
- };
319
-
320
- /** Flips to open upward automatically when the panel wouldn't fit below the
321
- * trigger within the viewport — no prop needed, this is always on. */
322
- export const OpensUpward: Story = {
323
- render: (_args) => {
324
- const [open, setOpen] = useState(false);
325
- return (
326
- <div
327
- style={{
328
- height: '70vh',
329
- display: 'flex',
330
- alignItems: 'flex-end',
331
- justifyContent: 'center',
332
- paddingBottom: 24,
333
- }}
334
- >
335
- <Popover
336
- open={open}
337
- onClose={() => setOpen(false)}
338
- align="left"
339
- aria-label="Options"
340
- trigger={<Button onClick={() => setOpen((o) => !o)}>Open (near viewport bottom)</Button>}
341
- >
342
- <div
343
- style={{
344
- padding: 16,
345
- fontFamily: 'var(--sv-font-family)',
346
- fontSize: 'var(--sv-font-size-sm)',
347
- color: 'var(--sv-color-text-muted)',
348
- }}
349
- >
350
- This trigger sits near the bottom of the viewport, so the panel opens upward instead of
351
- getting clipped below.
352
- </div>
353
- </Popover>
354
- </div>
355
- );
356
- },
357
- };
@@ -1,208 +0,0 @@
1
- 'use client';
2
-
3
- import {
4
- useEffect,
5
- useLayoutEffect,
6
- useRef,
7
- useState,
8
- type CSSProperties,
9
- type ReactElement,
10
- type ReactNode,
11
- } from 'react';
12
- import styles from './Popover.module.css';
13
-
14
- export interface PopoverProps {
15
- /** The element that opens the popover — rendered as-is; caller wires its onClick. */
16
- trigger: ReactElement;
17
- open: boolean;
18
- onClose: () => void;
19
- /** Which edge of the trigger the panel aligns to. Default: 'right'. */
20
- align?: 'left' | 'right';
21
- /** Panel width in px, or `'trigger'` to match the trigger element's own
22
- * rendered width exactly (e.g. a dropdown under a full-width form field,
23
- * where a fixed pixel width would either overflow or leave a gap
24
- * depending on viewport size). Default: 288. */
25
- width?: number | 'trigger';
26
- /**
27
- * Inline style overrides for the panel, merged after `width`. Escape hatch
28
- * for callers that need to override the panel's own chrome (e.g. no
29
- * rounded corners for a compact swatch picker) — inline styles always win
30
- * regardless of CSS module load order, unlike passing an extra className.
31
- */
32
- panelStyle?: CSSProperties;
33
- 'aria-label': string;
34
- children: ReactNode;
35
- }
36
-
37
- // Matches the gap already applied via .panel's `calc(100% + var(--sv-space-2))`
38
- // offset — used here in plain px since layout math needs a number, not a
39
- // CSS custom property.
40
- const TRIGGER_GAP = 8;
41
- // Breathing room kept between the panel and the viewport edge it's closest
42
- // to — without this, a panel that just barely fits ends up flush against
43
- // the edge (e.g. touching a mobile footer nav bar with no gap at all). Kept
44
- // small deliberately: this subtracts directly from the space available
45
- // before the internal-scroll cap kicks in (see the layout effect below), so
46
- // a larger value here trades "more edge clearance" for "more panels that
47
- // would otherwise fit exactly get an unwanted scrollbar instead" — a bad
48
- // trade for a purely cosmetic gap.
49
- const VIEWPORT_MARGIN = 4;
50
-
51
- /**
52
- * Popover — floating panel anchored to a trigger element, below it by
53
- * default.
54
- *
55
- * Positioning is `position: absolute` inside a `position: relative` wrapper —
56
- * no floating-ui dependency. Supports left/right alignment against the trigger
57
- * edge, and automatically opens on whichever side (above or below the
58
- * trigger) has more room within the viewport.
59
- *
60
- * If the panel doesn't fit on *either* side as the trigger currently sits —
61
- * common for a tall panel (e.g. a full calendar month) nested partway down a
62
- * long scrollable mobile sheet — the trigger's scrollable ancestor is
63
- * scrolled so the trigger sits at the top of it first, maximizing the room
64
- * available below, then the panel opens downward against that. This mirrors
65
- * how most mobile date pickers behave (scroll the page, not the picker) and
66
- * avoids the panel needing its own internal scroll in the vast majority of
67
- * cases. If it's still too tall even after that (a genuinely tiny viewport,
68
- * or an unusually tall panel), it's capped to whatever room actually exists
69
- * and scrolls internally as a last resort, rather than rendering past the
70
- * edge of the viewport with no way to reach the rest of it.
71
- *
72
- * Closes on outside click or Escape. Does not trap focus (it is non-modal).
73
- */
74
- export function Popover({
75
- trigger,
76
- open,
77
- onClose,
78
- align = 'right',
79
- width = 288,
80
- panelStyle,
81
- 'aria-label': ariaLabel,
82
- children,
83
- }: PopoverProps) {
84
- const containerRef = useRef<HTMLDivElement>(null);
85
- const panelRef = useRef<HTMLDivElement>(null);
86
- const [triggerWidth, setTriggerWidth] = useState<number | null>(null);
87
- // Whether the panel opens upward instead of downward. Defaults to false
88
- // (today's only behavior) so nothing shifts before this can actually be
89
- // measured.
90
- const [openUpward, setOpenUpward] = useState(false);
91
- // Caps the panel to whatever room actually exists on its chosen side, only
92
- // when the panel is taller than that — null leaves it at its natural
93
- // height, unconstrained, for the common case where it already fits.
94
- const [maxHeight, setMaxHeight] = useState<number | null>(null);
95
-
96
- // width="trigger": measure the trigger's own rendered width and keep the
97
- // panel matching it, including live resizes (e.g. rotating the device, or
98
- // the trigger's own width changing for any other reason).
99
- useEffect(() => {
100
- if (width !== 'trigger') return;
101
- const el = containerRef.current?.firstElementChild as HTMLElement | null;
102
- if (!el) return;
103
- function recompute() {
104
- if (el) setTriggerWidth(el.getBoundingClientRect().width);
105
- }
106
- recompute();
107
- const ro = new ResizeObserver(recompute);
108
- ro.observe(el);
109
- return () => ro.disconnect();
110
- }, [width]);
111
-
112
- // Collision detection: once open, measure the room available above and
113
- // below the trigger within the viewport. If neither side has enough for
114
- // the panel as things currently sit, scroll the trigger's scrollable
115
- // ancestor so the trigger sits at the top of it (scrollIntoView with
116
- // behavior: 'instant' applies synchronously, so re-measuring right after
117
- // reflects the new position with no extra frame to wait for) — this is
118
- // usually enough on its own to make the panel fit below. Whatever's left
119
- // after that: open on whichever side has more room, and only if it still
120
- // doesn't fit, cap the panel's height to that room (.panel's overflow-y:
121
- // auto then lets it scroll internally as a last resort, rather than
122
- // extending past the viewport edge with no way to reach the clipped part).
123
- // Runs in a layout effect (before paint) so there's no visible flash of
124
- // "opens one way, then snaps to the other" — measured and corrected within
125
- // the same commit the browser paints.
126
- useLayoutEffect(() => {
127
- if (!open) {
128
- setOpenUpward(false);
129
- setMaxHeight(null);
130
- return;
131
- }
132
- const container = containerRef.current;
133
- const panel = panelRef.current;
134
- if (!container || !panel) return;
135
-
136
- // Arrow function expression, not a hoisted declaration — TypeScript only
137
- // preserves the non-null narrowing above for a closure defined after it.
138
- const measure = () => {
139
- const triggerRect = container.getBoundingClientRect();
140
- const panelHeight = panel.getBoundingClientRect().height;
141
- return {
142
- panelHeight,
143
- spaceBelow: window.innerHeight - triggerRect.bottom - TRIGGER_GAP - VIEWPORT_MARGIN,
144
- spaceAbove: triggerRect.top - TRIGGER_GAP - VIEWPORT_MARGIN,
145
- };
146
- };
147
-
148
- let { panelHeight, spaceBelow, spaceAbove } = measure();
149
- if (panelHeight > spaceBelow && panelHeight > spaceAbove) {
150
- container.scrollIntoView({ block: 'start', behavior: 'instant' });
151
- ({ panelHeight, spaceBelow, spaceAbove } = measure());
152
- }
153
-
154
- const upward = panelHeight > spaceBelow && spaceAbove > spaceBelow;
155
- setOpenUpward(upward);
156
- const available = upward ? spaceAbove : spaceBelow;
157
- setMaxHeight(panelHeight > available ? Math.max(available, 0) : null);
158
- // children included: content that changes the panel's height (e.g. a
159
- // calendar switching months to a row with fewer/more weeks) should
160
- // re-run this measurement, not just the open/close transition itself.
161
- }, [open, children]);
162
-
163
- // Outside-click dismissal
164
- useEffect(() => {
165
- if (!open) return;
166
- function handleMouseDown(e: MouseEvent) {
167
- if (!containerRef.current?.contains(e.target as Node)) {
168
- onClose();
169
- }
170
- }
171
- document.addEventListener('mousedown', handleMouseDown);
172
- return () => document.removeEventListener('mousedown', handleMouseDown);
173
- }, [open, onClose]);
174
-
175
- // Escape key dismissal
176
- useEffect(() => {
177
- if (!open) return;
178
- function handleKeyDown(e: KeyboardEvent) {
179
- if (e.key === 'Escape') onClose();
180
- }
181
- document.addEventListener('keydown', handleKeyDown);
182
- return () => document.removeEventListener('keydown', handleKeyDown);
183
- }, [open, onClose]);
184
-
185
- return (
186
- <div ref={containerRef} className={styles.container}>
187
- {trigger}
188
- {open && (
189
- <div
190
- ref={panelRef}
191
- role="dialog"
192
- aria-label={ariaLabel}
193
- aria-modal={false}
194
- className={[styles.panel, styles[align], openUpward ? styles.upward : '']
195
- .filter(Boolean)
196
- .join(' ')}
197
- style={{
198
- width: width === 'trigger' ? (triggerWidth ?? undefined) : width,
199
- maxHeight: maxHeight ?? undefined,
200
- ...panelStyle,
201
- }}
202
- >
203
- {children}
204
- </div>
205
- )}
206
- </div>
207
- );
208
- }
@@ -1,98 +0,0 @@
1
- // @vitest-environment jsdom
2
- import { afterEach, describe, expect, it, vi } from 'vitest';
3
- import { cleanup, render, screen, fireEvent } from '@testing-library/react';
4
- import { Popover } from '../Popover';
5
-
6
- afterEach(cleanup);
7
-
8
- const trigger = <button type="button">Open</button>;
9
-
10
- describe('Popover', () => {
11
- it('renders the trigger element', () => {
12
- render(
13
- <Popover open={false} onClose={() => {}} aria-label="Menu" trigger={trigger}>
14
- Content
15
- </Popover>,
16
- );
17
- expect(screen.getByRole('button', { name: 'Open' })).toBeDefined();
18
- });
19
-
20
- it('does not render the panel when closed', () => {
21
- render(
22
- <Popover open={false} onClose={() => {}} aria-label="Menu" trigger={trigger}>
23
- Content
24
- </Popover>,
25
- );
26
- expect(screen.queryByRole('dialog')).toBeNull();
27
- });
28
-
29
- it('renders the panel with children when open', () => {
30
- render(
31
- <Popover open={true} onClose={() => {}} aria-label="Menu" trigger={trigger}>
32
- Panel content
33
- </Popover>,
34
- );
35
- expect(screen.getByRole('dialog', { name: 'Menu' })).toBeDefined();
36
- expect(screen.getByText('Panel content')).toBeDefined();
37
- });
38
-
39
- it('applies the right alignment class by default', () => {
40
- render(
41
- <Popover open={true} onClose={() => {}} aria-label="Menu" trigger={trigger}>
42
- Content
43
- </Popover>,
44
- );
45
- expect(screen.getByRole('dialog').className).toContain('right');
46
- });
47
-
48
- it('applies the left alignment class when specified', () => {
49
- render(
50
- <Popover open={true} onClose={() => {}} align="left" aria-label="Menu" trigger={trigger}>
51
- Content
52
- </Popover>,
53
- );
54
- expect(screen.getByRole('dialog').className).toContain('left');
55
- });
56
-
57
- it('calls onClose when Escape is pressed', () => {
58
- const onClose = vi.fn();
59
- render(
60
- <Popover open={true} onClose={onClose} aria-label="Menu" trigger={trigger}>
61
- Content
62
- </Popover>,
63
- );
64
- fireEvent.keyDown(document, { key: 'Escape' });
65
- expect(onClose).toHaveBeenCalledOnce();
66
- });
67
-
68
- it('calls onClose when clicking outside the container', () => {
69
- const onClose = vi.fn();
70
- render(
71
- <Popover open={true} onClose={onClose} aria-label="Menu" trigger={trigger}>
72
- Content
73
- </Popover>,
74
- );
75
- fireEvent.mouseDown(document.body);
76
- expect(onClose).toHaveBeenCalledOnce();
77
- });
78
-
79
- it('does not call onClose when clicking inside the panel', () => {
80
- const onClose = vi.fn();
81
- render(
82
- <Popover open={true} onClose={onClose} aria-label="Menu" trigger={trigger}>
83
- <button type="button">Inside</button>
84
- </Popover>,
85
- );
86
- fireEvent.mouseDown(screen.getByRole('button', { name: 'Inside' }));
87
- expect(onClose).not.toHaveBeenCalled();
88
- });
89
-
90
- it('applies the provided width as inline style', () => {
91
- render(
92
- <Popover open={true} onClose={() => {}} aria-label="Menu" width={320} trigger={trigger}>
93
- Content
94
- </Popover>,
95
- );
96
- expect((screen.getByRole('dialog') as HTMLElement).style.width).toBe('320px');
97
- });
98
- });
@@ -1,20 +0,0 @@
1
- .track {
2
- width: 100%;
3
- height: 8px;
4
- border-radius: var(--sv-radius-full);
5
- background: var(--sv-color-surface-sunken);
6
- overflow: hidden;
7
- }
8
-
9
- .fill {
10
- height: 100%;
11
- border-radius: var(--sv-radius-full);
12
- background: var(--sv-color-accent);
13
- transition: width 0.2s ease;
14
- }
15
-
16
- @media (prefers-reduced-motion: reduce) {
17
- .fill {
18
- transition: none;
19
- }
20
- }