@teamofsilicons/silicon-ui 0.0.0-stage → 0.1.0

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 (352) hide show
  1. package/LICENSE +22 -0
  2. package/README.md +63 -2
  3. package/components/accordion/accordion.d.ts +13 -0
  4. package/components/accordion/accordion.js +35 -0
  5. package/components/accordion/accordion.module.css +15 -0
  6. package/components/action-button/action-button.d.ts +11 -0
  7. package/components/action-button/action-button.js +57 -0
  8. package/components/action-button/action-button.module.css +15 -0
  9. package/components/activity-heatmap/activity-heatmap.d.ts +37 -0
  10. package/components/activity-heatmap/activity-heatmap.js +303 -0
  11. package/components/activity-heatmap/activity-heatmap.module.css +83 -0
  12. package/components/alert/alert.d.ts +13 -0
  13. package/components/alert/alert.js +79 -0
  14. package/components/alert/alert.module.css +15 -0
  15. package/components/animated-counter/animated-counter.d.ts +13 -0
  16. package/components/animated-counter/animated-counter.js +73 -0
  17. package/components/animated-counter/animated-counter.module.css +12 -0
  18. package/components/announcement-bar/announcement-bar.d.ts +53 -0
  19. package/components/announcement-bar/announcement-bar.js +187 -0
  20. package/components/announcement-bar/announcement-bar.module.css +60 -0
  21. package/components/avatar/avatar.d.ts +8 -0
  22. package/components/avatar/avatar.js +18 -0
  23. package/components/avatar/avatar.module.css +60 -0
  24. package/components/avatar-group/avatar-group.d.ts +13 -0
  25. package/components/avatar-group/avatar-group.js +24 -0
  26. package/components/avatar-group/avatar-group.module.css +27 -0
  27. package/components/badge/badge.d.ts +10 -0
  28. package/components/badge/badge.js +66 -0
  29. package/components/badge/badge.module.css +52 -0
  30. package/components/bar-chart/bar-chart.d.ts +32 -0
  31. package/components/bar-chart/bar-chart.js +327 -0
  32. package/components/bar-chart/bar-chart.module.css +49 -0
  33. package/components/billing-toggle/billing-toggle.d.ts +35 -0
  34. package/components/billing-toggle/billing-toggle.js +83 -0
  35. package/components/billing-toggle/billing-toggle.module.css +139 -0
  36. package/components/bottom-sheet/bottom-sheet.d.ts +27 -0
  37. package/components/bottom-sheet/bottom-sheet.js +386 -0
  38. package/components/bottom-sheet/bottom-sheet.module.css +69 -0
  39. package/components/breadcrumb/breadcrumb.d.ts +13 -0
  40. package/components/breadcrumb/breadcrumb.js +19 -0
  41. package/components/breadcrumb/breadcrumb.module.css +12 -0
  42. package/components/brush-chart/brush-chart.d.ts +45 -0
  43. package/components/brush-chart/brush-chart.js +426 -0
  44. package/components/brush-chart/brush-chart.module.css +69 -0
  45. package/components/button/button.d.ts +10 -0
  46. package/components/button/button.js +97 -0
  47. package/components/button/button.module.css +39 -0
  48. package/components/button-group/button-group.d.ts +59 -0
  49. package/components/button-group/button-group.js +280 -0
  50. package/components/button-group/button-group.module.css +87 -0
  51. package/components/button-link/button-link.d.ts +24 -0
  52. package/components/button-link/button-link.js +11 -0
  53. package/components/button-link/button-link.module.css +7 -0
  54. package/components/calendar/calendar.d.ts +22 -0
  55. package/components/calendar/calendar.js +253 -0
  56. package/components/calendar/calendar.module.css +51 -0
  57. package/components/card/card.d.ts +19 -0
  58. package/components/card/card.js +108 -0
  59. package/components/card/card.module.css +51 -0
  60. package/components/card-stack/card-stack.d.ts +34 -0
  61. package/components/card-stack/card-stack.js +291 -0
  62. package/components/card-stack/card-stack.module.css +63 -0
  63. package/components/carousel/carousel.d.ts +28 -0
  64. package/components/carousel/carousel.js +332 -0
  65. package/components/carousel/carousel.module.css +42 -0
  66. package/components/chat-thread/chat-thread.d.ts +135 -0
  67. package/components/chat-thread/chat-thread.js +406 -0
  68. package/components/chat-thread/chat-thread.module.css +242 -0
  69. package/components/checkbox/checkbox.d.ts +7 -0
  70. package/components/checkbox/checkbox.js +23 -0
  71. package/components/checkbox/checkbox.module.css +15 -0
  72. package/components/chip-group/chip-group.d.ts +24 -0
  73. package/components/chip-group/chip-group.js +196 -0
  74. package/components/chip-group/chip-group.module.css +38 -0
  75. package/components/code-block/code-block.d.ts +11 -0
  76. package/components/code-block/code-block.js +133 -0
  77. package/components/code-block/code-block.module.css +43 -0
  78. package/components/color-picker/color-picker.d.ts +40 -0
  79. package/components/color-picker/color-picker.js +449 -0
  80. package/components/color-picker/color-picker.module.css +111 -0
  81. package/components/combobox/combobox.d.ts +19 -0
  82. package/components/combobox/combobox.js +167 -0
  83. package/components/combobox/combobox.module.css +28 -0
  84. package/components/command-palette/command-palette.d.ts +20 -0
  85. package/components/command-palette/command-palette.js +151 -0
  86. package/components/command-palette/command-palette.module.css +94 -0
  87. package/components/comment-thread/comment-thread.d.ts +120 -0
  88. package/components/comment-thread/comment-thread.js +343 -0
  89. package/components/comment-thread/comment-thread.module.css +195 -0
  90. package/components/confirm-morph/confirm-morph.d.ts +65 -0
  91. package/components/confirm-morph/confirm-morph.js +355 -0
  92. package/components/confirm-morph/confirm-morph.module.css +146 -0
  93. package/components/context-menu/context-menu.d.ts +18 -0
  94. package/components/context-menu/context-menu.js +148 -0
  95. package/components/context-menu/context-menu.module.css +17 -0
  96. package/components/copy-button/copy-button.d.ts +10 -0
  97. package/components/copy-button/copy-button.js +69 -0
  98. package/components/copy-button/copy-button.module.css +21 -0
  99. package/components/countdown/countdown.d.ts +31 -0
  100. package/components/countdown/countdown.js +177 -0
  101. package/components/countdown/countdown.module.css +49 -0
  102. package/components/date-picker/date-picker.d.ts +17 -0
  103. package/components/date-picker/date-picker.js +100 -0
  104. package/components/date-picker/date-picker.module.css +34 -0
  105. package/components/date-range-picker/date-range-picker.d.ts +44 -0
  106. package/components/date-range-picker/date-range-picker.js +534 -0
  107. package/components/date-range-picker/date-range-picker.module.css +214 -0
  108. package/components/dialog/dialog.d.ts +11 -0
  109. package/components/dialog/dialog.js +52 -0
  110. package/components/dialog/dialog.module.css +36 -0
  111. package/components/donut-chart/donut-chart.d.ts +46 -0
  112. package/components/donut-chart/donut-chart.js +382 -0
  113. package/components/donut-chart/donut-chart.module.css +48 -0
  114. package/components/drawer/drawer.d.ts +20 -0
  115. package/components/drawer/drawer.js +111 -0
  116. package/components/drawer/drawer.module.css +97 -0
  117. package/components/dropdown-menu/dropdown-menu.d.ts +16 -0
  118. package/components/dropdown-menu/dropdown-menu.js +39 -0
  119. package/components/dropdown-menu/dropdown-menu.module.css +38 -0
  120. package/components/elastic-slider/elastic-slider.d.ts +39 -0
  121. package/components/elastic-slider/elastic-slider.js +261 -0
  122. package/components/elastic-slider/elastic-slider.module.css +40 -0
  123. package/components/empty-state/empty-state.d.ts +12 -0
  124. package/components/empty-state/empty-state.js +69 -0
  125. package/components/empty-state/empty-state.module.css +13 -0
  126. package/components/expandable-card/expandable-card.d.ts +13 -0
  127. package/components/expandable-card/expandable-card.js +89 -0
  128. package/components/expandable-card/expandable-card.module.css +22 -0
  129. package/components/expanding-button-group/expanding-button-group.d.ts +41 -0
  130. package/components/expanding-button-group/expanding-button-group.js +258 -0
  131. package/components/expanding-button-group/expanding-button-group.module.css +44 -0
  132. package/components/expanding-search/expanding-search.d.ts +38 -0
  133. package/components/expanding-search/expanding-search.js +242 -0
  134. package/components/expanding-search/expanding-search.module.css +52 -0
  135. package/components/file-dropzone/file-dropzone.d.ts +44 -0
  136. package/components/file-dropzone/file-dropzone.js +379 -0
  137. package/components/file-dropzone/file-dropzone.module.css +115 -0
  138. package/components/file-upload/file-upload.d.ts +22 -0
  139. package/components/file-upload/file-upload.js +192 -0
  140. package/components/file-upload/file-upload.module.css +49 -0
  141. package/components/filter-toolbar/filter-toolbar.d.ts +48 -0
  142. package/components/filter-toolbar/filter-toolbar.js +446 -0
  143. package/components/filter-toolbar/filter-toolbar.module.css +70 -0
  144. package/components/floating-button-group/floating-button-group.d.ts +51 -0
  145. package/components/floating-button-group/floating-button-group.js +215 -0
  146. package/components/floating-button-group/floating-button-group.module.css +74 -0
  147. package/components/gauge/gauge.d.ts +18 -0
  148. package/components/gauge/gauge.js +101 -0
  149. package/components/gauge/gauge.module.css +22 -0
  150. package/components/hold-to-confirm/hold-to-confirm.d.ts +25 -0
  151. package/components/hold-to-confirm/hold-to-confirm.js +165 -0
  152. package/components/hold-to-confirm/hold-to-confirm.module.css +20 -0
  153. package/components/hover-card/hover-card.d.ts +38 -0
  154. package/components/hover-card/hover-card.js +145 -0
  155. package/components/hover-card/hover-card.module.css +20 -0
  156. package/components/image-compare/image-compare.d.ts +27 -0
  157. package/components/image-compare/image-compare.js +267 -0
  158. package/components/image-compare/image-compare.module.css +25 -0
  159. package/components/in-view-title/in-view-title.d.ts +17 -0
  160. package/components/in-view-title/in-view-title.js +70 -0
  161. package/components/in-view-title/in-view-title.module.css +13 -0
  162. package/components/inline-edit/inline-edit.d.ts +27 -0
  163. package/components/inline-edit/inline-edit.js +315 -0
  164. package/components/inline-edit/inline-edit.module.css +73 -0
  165. package/components/input/input.d.ts +7 -0
  166. package/components/input/input.js +60 -0
  167. package/components/input/input.module.css +22 -0
  168. package/components/json-viewer/json-viewer.d.ts +48 -0
  169. package/components/json-viewer/json-viewer.js +463 -0
  170. package/components/json-viewer/json-viewer.module.css +92 -0
  171. package/components/line-chart/line-chart.d.ts +58 -0
  172. package/components/line-chart/line-chart.js +383 -0
  173. package/components/line-chart/line-chart.module.css +60 -0
  174. package/components/mention-input/mention-input.d.ts +72 -0
  175. package/components/mention-input/mention-input.js +417 -0
  176. package/components/mention-input/mention-input.module.css +134 -0
  177. package/components/metric-card/metric-card.d.ts +8 -0
  178. package/components/metric-card/metric-card.js +44 -0
  179. package/components/metric-card/metric-card.module.css +18 -0
  180. package/components/money-input/money-input.d.ts +48 -0
  181. package/components/money-input/money-input.js +503 -0
  182. package/components/money-input/money-input.module.css +77 -0
  183. package/components/morph-select/morph-select.d.ts +50 -0
  184. package/components/morph-select/morph-select.js +395 -0
  185. package/components/morph-select/morph-select.module.css +88 -0
  186. package/components/multi-select/multi-select.d.ts +18 -0
  187. package/components/multi-select/multi-select.js +105 -0
  188. package/components/multi-select/multi-select.module.css +37 -0
  189. package/components/notification-center/notification-center.d.ts +24 -0
  190. package/components/notification-center/notification-center.js +146 -0
  191. package/components/notification-center/notification-center.module.css +79 -0
  192. package/components/number-field/number-field.d.ts +41 -0
  193. package/components/number-field/number-field.js +455 -0
  194. package/components/number-field/number-field.module.css +71 -0
  195. package/components/otp-input/otp-input.d.ts +13 -0
  196. package/components/otp-input/otp-input.js +129 -0
  197. package/components/otp-input/otp-input.module.css +28 -0
  198. package/components/pagination/pagination.d.ts +7 -0
  199. package/components/pagination/pagination.js +60 -0
  200. package/components/pagination/pagination.module.css +16 -0
  201. package/components/password-field/password-field.d.ts +14 -0
  202. package/components/password-field/password-field.js +51 -0
  203. package/components/password-field/password-field.module.css +27 -0
  204. package/components/password-strength/password-strength.d.ts +107 -0
  205. package/components/password-strength/password-strength.js +161 -0
  206. package/components/password-strength/password-strength.module.css +57 -0
  207. package/components/phone-input/phone-input.d.ts +73 -0
  208. package/components/phone-input/phone-input.js +612 -0
  209. package/components/phone-input/phone-input.module.css +98 -0
  210. package/components/popover/popover.d.ts +6 -0
  211. package/components/popover/popover.js +19 -0
  212. package/components/popover/popover.module.css +32 -0
  213. package/components/progress/progress.d.ts +8 -0
  214. package/components/progress/progress.js +42 -0
  215. package/components/progress/progress.module.css +1 -0
  216. package/components/radio-cards/radio-cards.d.ts +35 -0
  217. package/components/radio-cards/radio-cards.js +110 -0
  218. package/components/radio-cards/radio-cards.module.css +84 -0
  219. package/components/radio-group/radio-group.d.ts +13 -0
  220. package/components/radio-group/radio-group.js +55 -0
  221. package/components/radio-group/radio-group.module.css +30 -0
  222. package/components/resizable-panels/resizable-panels.d.ts +37 -0
  223. package/components/resizable-panels/resizable-panels.js +338 -0
  224. package/components/resizable-panels/resizable-panels.module.css +40 -0
  225. package/components/rich-text-editor/rich-text-editor.d.ts +55 -0
  226. package/components/rich-text-editor/rich-text-editor.js +1733 -0
  227. package/components/rich-text-editor/rich-text-editor.module.css +201 -0
  228. package/components/ridgeline/ridgeline.d.ts +37 -0
  229. package/components/ridgeline/ridgeline.js +346 -0
  230. package/components/ridgeline/ridgeline.module.css +58 -0
  231. package/components/scroll-area/scroll-area.d.ts +40 -0
  232. package/components/scroll-area/scroll-area.js +153 -0
  233. package/components/scroll-area/scroll-area.module.css +79 -0
  234. package/components/search-field/search-field.d.ts +7 -0
  235. package/components/search-field/search-field.js +20 -0
  236. package/components/search-field/search-field.module.css +17 -0
  237. package/components/segmented-control/segmented-control.d.ts +16 -0
  238. package/components/segmented-control/segmented-control.js +56 -0
  239. package/components/segmented-control/segmented-control.module.css +15 -0
  240. package/components/select/select.d.ts +15 -0
  241. package/components/select/select.js +36 -0
  242. package/components/select/select.module.css +35 -0
  243. package/components/shortcut-recorder/shortcut-recorder.d.ts +108 -0
  244. package/components/shortcut-recorder/shortcut-recorder.js +380 -0
  245. package/components/shortcut-recorder/shortcut-recorder.module.css +85 -0
  246. package/components/signature-pad/signature-pad.d.ts +72 -0
  247. package/components/signature-pad/signature-pad.js +450 -0
  248. package/components/signature-pad/signature-pad.module.css +84 -0
  249. package/components/skeleton/skeleton.d.ts +13 -0
  250. package/components/skeleton/skeleton.js +53 -0
  251. package/components/skeleton/skeleton.module.css +11 -0
  252. package/components/slider/slider.d.ts +45 -0
  253. package/components/slider/slider.js +347 -0
  254. package/components/slider/slider.module.css +45 -0
  255. package/components/slope-chart/slope-chart.d.ts +38 -0
  256. package/components/slope-chart/slope-chart.js +183 -0
  257. package/components/slope-chart/slope-chart.module.css +51 -0
  258. package/components/slot-text/slot-text.d.ts +26 -0
  259. package/components/slot-text/slot-text.js +119 -0
  260. package/components/slot-text/slot-text.module.css +19 -0
  261. package/components/sortable-data-table/sortable-data-table.d.ts +38 -0
  262. package/components/sortable-data-table/sortable-data-table.js +240 -0
  263. package/components/sortable-data-table/sortable-data-table.module.css +116 -0
  264. package/components/sparkline/sparkline.d.ts +19 -0
  265. package/components/sparkline/sparkline.js +239 -0
  266. package/components/sparkline/sparkline.module.css +26 -0
  267. package/components/split-button/split-button.d.ts +19 -0
  268. package/components/split-button/split-button.js +33 -0
  269. package/components/split-button/split-button.module.css +51 -0
  270. package/components/stepper/stepper.d.ts +36 -0
  271. package/components/stepper/stepper.js +108 -0
  272. package/components/stepper/stepper.module.css +74 -0
  273. package/components/streamgraph/streamgraph.d.ts +51 -0
  274. package/components/streamgraph/streamgraph.js +376 -0
  275. package/components/streamgraph/streamgraph.module.css +55 -0
  276. package/components/swipe-actions/swipe-actions.d.ts +35 -0
  277. package/components/swipe-actions/swipe-actions.js +330 -0
  278. package/components/swipe-actions/swipe-actions.module.css +64 -0
  279. package/components/switch/switch.d.ts +7 -0
  280. package/components/switch/switch.js +45 -0
  281. package/components/switch/switch.module.css +12 -0
  282. package/components/tabs/tabs.d.ts +8 -0
  283. package/components/tabs/tabs.js +138 -0
  284. package/components/tabs/tabs.module.css +27 -0
  285. package/components/tag-input/tag-input.d.ts +15 -0
  286. package/components/tag-input/tag-input.js +150 -0
  287. package/components/tag-input/tag-input.module.css +34 -0
  288. package/components/text-morph/text-morph.d.ts +13 -0
  289. package/components/text-morph/text-morph.js +69 -0
  290. package/components/text-morph/text-morph.module.css +8 -0
  291. package/components/text-reveal/text-reveal.d.ts +14 -0
  292. package/components/text-reveal/text-reveal.js +20 -0
  293. package/components/text-reveal/text-reveal.module.css +13 -0
  294. package/components/text-shimmer/text-shimmer.d.ts +18 -0
  295. package/components/text-shimmer/text-shimmer.js +90 -0
  296. package/components/text-shimmer/text-shimmer.module.css +9 -0
  297. package/components/textarea/textarea.d.ts +7 -0
  298. package/components/textarea/textarea.js +56 -0
  299. package/components/textarea/textarea.module.css +22 -0
  300. package/components/theme-switch/theme-switch.d.ts +11 -0
  301. package/components/theme-switch/theme-switch.js +37 -0
  302. package/components/theme-switch/theme-switch.module.css +31 -0
  303. package/components/time-picker/time-picker.d.ts +13 -0
  304. package/components/time-picker/time-picker.js +95 -0
  305. package/components/time-picker/time-picker.module.css +23 -0
  306. package/components/timeline/timeline.d.ts +47 -0
  307. package/components/timeline/timeline.js +171 -0
  308. package/components/timeline/timeline.module.css +51 -0
  309. package/components/toast/toast.d.ts +8 -0
  310. package/components/toast/toast.js +118 -0
  311. package/components/toast/toast.module.css +100 -0
  312. package/components/toast-stack/toast-stack.d.ts +57 -0
  313. package/components/toast-stack/toast-stack.js +399 -0
  314. package/components/toast-stack/toast-stack.module.css +61 -0
  315. package/components/tooltip/tooltip.d.ts +8 -0
  316. package/components/tooltip/tooltip.js +62 -0
  317. package/components/tooltip/tooltip.module.css +18 -0
  318. package/components/tree-view/tree-view.d.ts +17 -0
  319. package/components/tree-view/tree-view.js +147 -0
  320. package/components/tree-view/tree-view.module.css +22 -0
  321. package/components/treemap/treemap.d.ts +35 -0
  322. package/components/treemap/treemap.js +375 -0
  323. package/components/treemap/treemap.module.css +59 -0
  324. package/components/usage-meter/usage-meter.d.ts +24 -0
  325. package/components/usage-meter/usage-meter.js +296 -0
  326. package/components/usage-meter/usage-meter.module.css +61 -0
  327. package/components/user-menu/user-menu.d.ts +62 -0
  328. package/components/user-menu/user-menu.js +316 -0
  329. package/components/user-menu/user-menu.module.css +97 -0
  330. package/components/waffle-chart/waffle-chart.d.ts +39 -0
  331. package/components/waffle-chart/waffle-chart.js +244 -0
  332. package/components/waffle-chart/waffle-chart.module.css +52 -0
  333. package/components.json +1330 -0
  334. package/lib/escape.d.ts +19 -0
  335. package/lib/escape.js +50 -0
  336. package/lib/media.d.ts +353 -0
  337. package/lib/media.js +65 -0
  338. package/lib/menu-highlight.d.ts +28 -0
  339. package/lib/menu-highlight.js +43 -0
  340. package/lib/motion-tokens.d.ts +60 -0
  341. package/lib/motion-tokens.js +25 -0
  342. package/lib/return-focus.d.ts +9 -0
  343. package/lib/return-focus.js +53 -0
  344. package/lib/silicon-provider.d.ts +60 -0
  345. package/lib/silicon-provider.js +47 -0
  346. package/lib/use-copy-feedback.d.ts +8 -0
  347. package/lib/use-copy-feedback.js +36 -0
  348. package/lib/use-today.d.ts +2 -0
  349. package/lib/use-today.js +29 -0
  350. package/package.json +66 -4
  351. package/styles.css +218 -0
  352. package/styles.css.d.ts +1 -0
@@ -0,0 +1,27 @@
1
+ import * as DialogPrimitive from "@radix-ui/react-dialog";
2
+ import type { ReactNode } from "react";
3
+ /**
4
+ * A sheet that rises from the bottom edge and rests at one or more heights (detents).
5
+ * Use it for secondary tasks where a peek is useful before committing to the full view: trip details, share options, filters.
6
+ * Use a dialog when the decision must interrupt, and a drawer for side panels on wide layouts.
7
+ */
8
+ export interface BottomSheetProps {
9
+ /** The control that opens the sheet, usually a button. Focus returns to it when the sheet closes. */
10
+ trigger?: ReactNode;
11
+ open?: boolean;
12
+ defaultOpen?: boolean;
13
+ onOpenChange?: (open: boolean) => void;
14
+ title: string;
15
+ description?: string;
16
+ /** Resting heights as fractions of the viewport height. The sheet opens at `initialDetent` and never grows past the largest. */
17
+ detents?: number[];
18
+ /** Index into the sorted detents the sheet opens at. Defaults to the smallest. */
19
+ initialDetent?: number;
20
+ onDetentChange?: (index: number) => void;
21
+ closeLabel?: string;
22
+ className?: string;
23
+ children: ReactNode;
24
+ }
25
+ export declare function BottomSheet({ trigger, open: openProp, defaultOpen, onOpenChange, ...props }: BottomSheetProps): import("react").JSX.Element;
26
+ export declare const BottomSheetClose: import("react").ForwardRefExoticComponent<DialogPrimitive.DialogCloseProps & import("react").RefAttributes<HTMLButtonElement>>;
27
+ export default BottomSheet;
@@ -0,0 +1,386 @@
1
+ "use client";
2
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
3
+ import * as DialogPrimitive from "@radix-ui/react-dialog";
4
+ import { useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState } from "react";
5
+ import { AnimatePresence, animate, motion, useMotionValue, usePresence, useReducedMotion, useTransform } from "motion/react";
6
+ import { X } from "lucide-react";
7
+ import { ESCAPE_LAYER_ATTRIBUTE, layerEscape } from "../../lib/escape.js";
8
+ import { motionTokens } from "../../lib/motion-tokens.js";
9
+ import { useReturnFocus } from "../../lib/return-focus.js";
10
+ import styles from "./bottom-sheet.module.css";
11
+ /** Extra surface below the viewport, so an upward stretch never shows a gap under the sheet. */
12
+ const EXTENSION = 160;
13
+ /** Enough travel below the edge to hide the floating shadow as well as the sheet. */
14
+ const CLOSED_GAP = 40;
15
+ /** How far a release coasts, in seconds of its velocity, before the nearest detent is chosen. */
16
+ const PROJECTION = .2;
17
+ /** A release faster than this (px/s) always moves at least one detent in its direction. */
18
+ const FLICK = 320;
19
+ /** The limit an upward stretch approaches past the tallest detent. */
20
+ const STRETCH = 120;
21
+ /** How much of the full dim remains at the smallest detent. */
22
+ const LOW_DIM = .78;
23
+ const settle = motionTokens.spring.smooth;
24
+ /** Leaving is shorter than arriving; a flick keeps its velocity through the same spring. */
25
+ const leave = { ...motionTokens.spring.smooth, visualDuration: .3 };
26
+ const fade = { duration: motionTokens.duration.fast, ease: [...motionTokens.ease.standard] };
27
+ /** iOS-style resistance: follows the finger at first, then approaches STRETCH. */
28
+ const rubber = (distance) => (1 - 1 / (distance * .55 / STRETCH + 1)) * STRETCH;
29
+ const unrubber = (stretch) => (1 / (1 - Math.min(stretch, STRETCH - 1) / STRETCH) - 1) * STRETCH / .55;
30
+ const clamp = (value, min, max) => Math.min(max, Math.max(min, value));
31
+ function velocityOf(samples, now) {
32
+ const recent = samples.filter(sample => now - sample.t <= 80);
33
+ const first = recent[0], last = recent[recent.length - 1];
34
+ // A pause before letting go drops the momentum, so a slow, deliberate release settles where it is.
35
+ if (!first || !last || last === first || now - last.t > 60)
36
+ return 0;
37
+ return (last.y - first.y) / ((last.t - first.t) / 1000);
38
+ }
39
+ export function BottomSheet({ trigger, open: openProp, defaultOpen = false, onOpenChange, ...props }) {
40
+ const [uncontrolled, setUncontrolled] = useState(defaultOpen);
41
+ const open = openProp ?? uncontrolled;
42
+ const setOpen = useCallback((next) => {
43
+ if (openProp === undefined)
44
+ setUncontrolled(next);
45
+ onOpenChange?.(next);
46
+ }, [openProp, onOpenChange]);
47
+ const dismiss = useCallback(() => setOpen(false), [setOpen]);
48
+ return _jsxs(DialogPrimitive.Root, { open: open, onOpenChange: setOpen, children: [trigger ? _jsx(DialogPrimitive.Trigger, { asChild: true, children: trigger }) : null, _jsx(AnimatePresence, { children: open && _jsx(DialogPrimitive.Portal, { forceMount: true, children: _jsx(Sheet, { ...props, onDismiss: dismiss }) }, "sheet") })] });
49
+ }
50
+ function Sheet({ title, description, detents = [.45, .92], initialDetent = 0, onDetentChange, closeLabel = "Close", className, children, onDismiss }) {
51
+ const [isPresent, safeToRemove] = usePresence();
52
+ const reduced = useReducedMotion() ?? false;
53
+ const detentKey = detents.join(",");
54
+ const stops = useMemo(() => detentKey.split(",").map(Number).filter(stop => stop > 0).sort((a, b) => a - b), [detentKey]);
55
+ const top = stops.length - 1;
56
+ const [detent, setDetent] = useState(() => clamp(Math.round(initialDetent), 0, top));
57
+ const [announcement, setAnnouncement] = useState("");
58
+ const sheetRef = useRef(null);
59
+ const bodyRef = useRef(null);
60
+ /** Offset of the sheet below its tallest detent, in px. Starts far below the fold so the first frame never flashes. */
61
+ const y = useMotionValue(4000);
62
+ const height = useMotionValue(0);
63
+ const presence = useMotionValue(1);
64
+ const detentRef = useRef(detent);
65
+ const aim = useRef(detent);
66
+ const drag = useRef(null);
67
+ const arrive = useRef(undefined);
68
+ const suppressClick = useRef(false);
69
+ const mounted = useRef(false);
70
+ /** When the sheet last started entering or leaving, so a press that began while it was leaving never dismisses it once it is reopened. */
71
+ const presenceChange = useRef({ present: isPresent, at: 0 });
72
+ useLayoutEffect(() => { presenceChange.current = { present: isPresent, at: performance.now() }; }, [isPresent]);
73
+ // Focus starts on the sheet itself, and goes back to what opened it when it closes, trigger or not (lib/return-focus.ts).
74
+ const returnFocus = useReturnFocus(isPresent, event => { event.preventDefault(); sheetRef.current?.focus({ preventScroll: true }); });
75
+ const offset = useCallback((stop) => {
76
+ const full = height.get();
77
+ return stop === "closed" ? full + CLOSED_GAP : full * (1 - stops[stop] / stops[top]);
78
+ }, [height, stops, top]);
79
+ // The backdrop is a function of where the sheet is, so it darkens and clears with a drag instead of on a timer.
80
+ const backdrop = useTransform([y, height, presence], ([offsetY = 0, full = 0, shown = 0]) => {
81
+ if (!full)
82
+ return 0;
83
+ const low = full * (1 - stops[0] / stops[top]), closed = full + CLOSED_GAP;
84
+ const dim = offsetY <= low ? 1 - (1 - LOW_DIM) * (low ? offsetY / low : 0) : LOW_DIM * (1 - (offsetY - low) / (closed - low));
85
+ return clamp(dim, 0, 1) * shown;
86
+ });
87
+ // Every move retargets the one motion value from wherever it is. A new target drops the previous arrival callback.
88
+ const go = useCallback((stop, velocity, onArrive) => {
89
+ aim.current = stop;
90
+ arrive.current = onArrive;
91
+ const target = offset(stop);
92
+ const done = () => { const callback = arrive.current; arrive.current = undefined; callback?.(); };
93
+ if (reduced) {
94
+ y.jump(target);
95
+ done();
96
+ return;
97
+ }
98
+ animate(y, target, { ...(stop === "closed" ? leave : settle), ...(velocity === undefined ? {} : { velocity }), onComplete: done });
99
+ }, [offset, reduced, y]);
100
+ const rest = useCallback((stop, velocity) => {
101
+ if (stop !== detentRef.current) {
102
+ detentRef.current = stop;
103
+ setDetent(stop);
104
+ onDetentChange?.(stop);
105
+ setAnnouncement(stop === top ? "Sheet expanded" : stop === 0 ? "Sheet collapsed" : `Sheet at ${Math.round(stops[stop] * 100)} percent height`);
106
+ }
107
+ // Collapsing takes the content back to its start, so the peek always shows the top of it.
108
+ const body = bodyRef.current;
109
+ if (stop !== top && body && body.scrollTop > 0)
110
+ body.scrollTo({ top: 0, behavior: reduced ? "auto" : "smooth" });
111
+ go(stop, velocity);
112
+ }, [go, onDetentChange, reduced, stops, top]);
113
+ // Enter, leave, and a reopen caught mid-exit all retarget the same motion value from wherever it is.
114
+ useLayoutEffect(() => {
115
+ const sheet = sheetRef.current;
116
+ if (!sheet)
117
+ return;
118
+ if (!mounted.current) {
119
+ mounted.current = true;
120
+ height.set(sheet.offsetHeight - EXTENSION);
121
+ y.jump(offset("closed"));
122
+ if (reduced) {
123
+ y.jump(offset(detentRef.current));
124
+ presence.jump(0);
125
+ }
126
+ }
127
+ if (isPresent) {
128
+ if (reduced) {
129
+ y.jump(offset(detentRef.current));
130
+ animate(presence, 1, fade);
131
+ return;
132
+ }
133
+ presence.jump(1);
134
+ go(detentRef.current);
135
+ return;
136
+ }
137
+ if (reduced) {
138
+ animate(presence, 0, { ...fade, onComplete: safeToRemove });
139
+ return;
140
+ }
141
+ // A flick has already started the exit with its own velocity; only the unmount waits for it.
142
+ if (aim.current === "closed" && y.isAnimating())
143
+ arrive.current = safeToRemove;
144
+ else
145
+ go("closed", undefined, safeToRemove);
146
+ }, [go, height, isPresent, offset, presence, reduced, safeToRemove, y]);
147
+ // A viewport change (rotation, the mobile toolbar) moves every detent. Keep the sheet on its detent, retargeting any flight.
148
+ useEffect(() => {
149
+ const sheet = sheetRef.current;
150
+ if (!sheet || typeof ResizeObserver === "undefined")
151
+ return;
152
+ const observer = new ResizeObserver(() => {
153
+ const next = sheet.offsetHeight - EXTENSION;
154
+ if (next === height.get())
155
+ return;
156
+ height.set(next);
157
+ const stop = aim.current;
158
+ if (drag.current || stop === "closed")
159
+ return;
160
+ if (y.isAnimating())
161
+ go(stop);
162
+ else
163
+ y.jump(offset(stop));
164
+ });
165
+ observer.observe(sheet);
166
+ return () => observer.disconnect();
167
+ }, [go, height, offset, y]);
168
+ const beginDrag = useCallback((clientY, time) => {
169
+ // Catch the sheet where it is, even mid-flight; an overstretched sheet converts back to finger distance so nothing jumps.
170
+ y.stop();
171
+ const current = y.get();
172
+ drag.current = { startY: clientY, origin: current < 0 ? -unrubber(-current) : current, from: detentRef.current, moved: false, samples: [{ t: time, y: clientY }] };
173
+ }, [y]);
174
+ const moveDrag = useCallback((clientY, time) => {
175
+ const state = drag.current;
176
+ if (!state)
177
+ return;
178
+ const delta = clientY - state.startY;
179
+ if (!state.moved) {
180
+ if (Math.abs(delta) < 3)
181
+ return;
182
+ state.moved = true;
183
+ sheetRef.current?.setAttribute("data-dragging", "");
184
+ }
185
+ const raw = state.origin + delta;
186
+ // Down follows the finger 1:1 so it can dismiss; up past the tallest detent resists like a rubber band.
187
+ y.set(raw < 0 ? -rubber(-raw) : raw);
188
+ state.samples.push({ t: time, y: clientY });
189
+ if (state.samples.length > 12)
190
+ state.samples.shift();
191
+ }, [y]);
192
+ const endDrag = useCallback((time) => {
193
+ const state = drag.current;
194
+ drag.current = null;
195
+ sheetRef.current?.removeAttribute("data-dragging");
196
+ if (!state)
197
+ return;
198
+ if (!state.moved) {
199
+ const stop = aim.current;
200
+ if (stop !== "closed")
201
+ go(stop);
202
+ return;
203
+ }
204
+ suppressClick.current = true;
205
+ const current = y.get();
206
+ // A stretched sheet springs back without the finger's speed, so it never launches past the stretch.
207
+ const velocity = current < 0 ? 0 : velocityOf(state.samples, time);
208
+ const projected = current + velocity * PROJECTION;
209
+ const candidates = [...stops.map((_, index) => index), "closed"];
210
+ let target = candidates.reduce((best, stop) => Math.abs(offset(stop) - projected) < Math.abs(offset(best) - projected) ? stop : best);
211
+ // A quick flick always travels at least one detent, and a flick down from the smallest one closes the sheet.
212
+ if (Math.abs(velocity) > FLICK && target === state.from)
213
+ target = velocity < 0 ? Math.min(state.from + 1, top) : state.from === 0 ? "closed" : state.from - 1;
214
+ // The exit starts in this frame with the finger's velocity; closing the dialog only waits for it to land.
215
+ if (target === "closed") {
216
+ if (!reduced)
217
+ go("closed", velocity);
218
+ onDismiss();
219
+ return;
220
+ }
221
+ rest(target, velocity);
222
+ }, [go, offset, onDismiss, reduced, rest, stops, top, y]);
223
+ // Touch drags on the content move the sheet until it is fully open; once open, the content scrolls and a pull down from its top moves the sheet again.
224
+ useEffect(() => {
225
+ const body = bodyRef.current;
226
+ if (!body)
227
+ return;
228
+ let gesture = null;
229
+ const start = (event) => {
230
+ const touch = event.touches[0];
231
+ gesture = event.touches.length === 1 && touch ? { x: touch.clientX, y: touch.clientY, mode: "pending" } : null;
232
+ };
233
+ const move = (event) => {
234
+ const touch = event.touches[0];
235
+ if (!gesture || !touch || event.touches.length !== 1)
236
+ return;
237
+ const dx = touch.clientX - gesture.x, dy = touch.clientY - gesture.y;
238
+ if (gesture.mode === "pending") {
239
+ const expanded = detentRef.current === top;
240
+ if (expanded) {
241
+ const scrollable = body.scrollHeight > body.clientHeight + 1;
242
+ gesture.mode = Math.abs(dy) >= Math.abs(dx) && (!scrollable || (body.scrollTop <= 0 && dy > 0)) ? "sheet" : "native";
243
+ }
244
+ else {
245
+ if (Math.hypot(dx, dy) < 4)
246
+ return;
247
+ gesture.mode = Math.abs(dy) >= Math.abs(dx) ? "sheet" : "native";
248
+ }
249
+ if (gesture.mode === "sheet")
250
+ beginDrag(touch.clientY, event.timeStamp);
251
+ }
252
+ if (gesture.mode !== "sheet")
253
+ return;
254
+ if (event.cancelable)
255
+ event.preventDefault();
256
+ moveDrag(touch.clientY, event.timeStamp);
257
+ };
258
+ const end = (event) => {
259
+ if (gesture?.mode === "sheet")
260
+ endDrag(event.timeStamp);
261
+ gesture = null;
262
+ };
263
+ body.addEventListener("touchstart", start, { passive: true });
264
+ body.addEventListener("touchmove", move, { passive: false });
265
+ body.addEventListener("touchend", end);
266
+ body.addEventListener("touchcancel", end);
267
+ return () => {
268
+ body.removeEventListener("touchstart", start);
269
+ body.removeEventListener("touchmove", move);
270
+ body.removeEventListener("touchend", end);
271
+ body.removeEventListener("touchcancel", end);
272
+ };
273
+ }, [beginDrag, endDrag, moveDrag, top]);
274
+ // The scroll hairline under the header is toggled on the element directly, so scrolling never re-renders.
275
+ useEffect(() => {
276
+ const body = bodyRef.current, sheet = sheetRef.current;
277
+ if (!body || !sheet)
278
+ return;
279
+ const update = () => sheet.toggleAttribute("data-scrolled", body.scrollTop > 1);
280
+ body.addEventListener("scroll", update, { passive: true });
281
+ return () => body.removeEventListener("scroll", update);
282
+ }, []);
283
+ function headerDown(event) {
284
+ const target = event.target instanceof Element ? event.target : null;
285
+ if (event.button !== 0 || !event.isPrimary)
286
+ return;
287
+ if (target?.closest("button, a, input, select, textarea, [role='button']") && !target.closest("[data-grabber]"))
288
+ return;
289
+ beginDrag(event.clientY, event.timeStamp);
290
+ // Moves arrive once per frame, so a fast flick can leave the header before its first move lands.
291
+ // Until the header captures the pointer, the window follows it; once captured, the header's own handlers take over.
292
+ const header = event.currentTarget, id = event.pointerId;
293
+ const outside = (next) => next.pointerId === id && !(next.target instanceof Node && header.contains(next.target));
294
+ const move = (next) => {
295
+ if (!outside(next) || !drag.current)
296
+ return;
297
+ moveDrag(next.clientY, next.timeStamp);
298
+ if (drag.current?.moved && !header.hasPointerCapture(id)) {
299
+ try {
300
+ header.setPointerCapture(id);
301
+ }
302
+ catch { /* The pointer is already gone. */ }
303
+ }
304
+ };
305
+ const up = (next) => {
306
+ if (next.pointerId !== id)
307
+ return;
308
+ window.removeEventListener("pointermove", move);
309
+ window.removeEventListener("pointerup", up);
310
+ window.removeEventListener("pointercancel", up);
311
+ if (outside(next))
312
+ endDrag(next.timeStamp);
313
+ };
314
+ window.addEventListener("pointermove", move);
315
+ window.addEventListener("pointerup", up);
316
+ window.addEventListener("pointercancel", up);
317
+ }
318
+ // Capture only once the pointer has really moved, so a plain click still lands on the grabber button.
319
+ function headerMove(event) {
320
+ if (!drag.current)
321
+ return;
322
+ if (event.pointerType === "mouse" && event.buttons === 0) {
323
+ endDrag(event.timeStamp);
324
+ return;
325
+ }
326
+ moveDrag(event.clientY, event.timeStamp);
327
+ if (drag.current.moved && !event.currentTarget.hasPointerCapture(event.pointerId))
328
+ event.currentTarget.setPointerCapture(event.pointerId);
329
+ }
330
+ function headerUp(event) {
331
+ if (event.currentTarget.hasPointerCapture(event.pointerId))
332
+ event.currentTarget.releasePointerCapture(event.pointerId);
333
+ endDrag(event.timeStamp);
334
+ }
335
+ function grabberClick() {
336
+ if (suppressClick.current) {
337
+ suppressClick.current = false;
338
+ return;
339
+ }
340
+ rest(detentRef.current === top ? 0 : top);
341
+ }
342
+ function grabberKey(event) {
343
+ const current = detentRef.current;
344
+ const next = event.key === "ArrowUp" ? Math.min(current + 1, top) : event.key === "ArrowDown" ? Math.max(current - 1, 0) : event.key === "Home" ? top : event.key === "End" ? 0 : null;
345
+ if (next === null)
346
+ return;
347
+ event.preventDefault();
348
+ rest(next);
349
+ }
350
+ // Scrolling or tabbing into content that is still below the fold opens the sheet the rest of the way.
351
+ function bodyWheel(event) {
352
+ if (detentRef.current !== top && event.deltaY > 4 && !drag.current)
353
+ rest(top);
354
+ }
355
+ function bodyFocus(event) {
356
+ if (detentRef.current === top || !(event.target instanceof Element))
357
+ return;
358
+ if (event.target.getBoundingClientRect().bottom > window.innerHeight - 8)
359
+ rest(top);
360
+ }
361
+ // With focus on the sheet or its content, reading keys open the sheet first and then scroll the content.
362
+ function sheetKey(event) {
363
+ const body = bodyRef.current;
364
+ if (!body || (event.target !== event.currentTarget && event.target !== body))
365
+ return;
366
+ const down = event.key === "ArrowDown" || event.key === "PageDown" || (event.key === " " && !event.shiftKey);
367
+ const up = event.key === "ArrowUp" || event.key === "PageUp" || (event.key === " " && event.shiftKey);
368
+ if (!down && !up)
369
+ return;
370
+ event.preventDefault();
371
+ if (down && detentRef.current !== top) {
372
+ rest(top);
373
+ return;
374
+ }
375
+ const step = event.key.startsWith("Arrow") ? 48 : body.clientHeight * .85;
376
+ body.scrollBy({ top: down ? step : -step, behavior: reduced ? "auto" : "smooth" });
377
+ }
378
+ const expanded = detent === top;
379
+ const style = { y, opacity: presence, "--sheet-max": stops[top], "--sheet-extension": `${EXTENSION}px` };
380
+ return _jsxs(_Fragment, { children: [_jsx(DialogPrimitive.Overlay, { asChild: true, forceMount: true, children: _jsx(motion.div, { className: styles.overlay, style: { opacity: backdrop } }) }), _jsx(DialogPrimitive.Content, { asChild: true, forceMount: true, ...(description ? {} : { "aria-describedby": undefined }), [ESCAPE_LAYER_ATTRIBUTE]: "", onEscapeKeyDown: layerEscape(), ...returnFocus,
381
+ // Radix waits for the click before treating a press as outside. A press on the trigger while the sheet leaves reopens it first, so that press must not close it again.
382
+ onPointerDownOutside: event => { const { present, at } = presenceChange.current; if (!present || event.detail.originalEvent.timeStamp < at)
383
+ event.preventDefault(); }, children: _jsxs(motion.div, { ref: sheetRef, className: [styles.sheet, className].filter(Boolean).join(" "), style: style, "data-expanded": expanded ? "" : undefined, onKeyDown: sheetKey, children: [_jsxs("div", { className: styles.header, onPointerDown: headerDown, onPointerMove: headerMove, onPointerUp: headerUp, onPointerCancel: headerUp, children: [_jsx("button", { type: "button", className: styles.grabber, "data-grabber": "", "aria-label": expanded ? "Collapse sheet" : "Expand sheet", "aria-expanded": expanded, onClick: grabberClick, onKeyDown: grabberKey, children: _jsx("span", { className: styles.grabberBar, "aria-hidden": "true" }) }), _jsxs("div", { className: styles.headRow, children: [_jsxs("div", { className: styles.headText, children: [_jsx(DialogPrimitive.Title, { className: styles.title, children: title }), description ? _jsx(DialogPrimitive.Description, { className: styles.description, children: description }) : null] }), _jsx(DialogPrimitive.Close, { className: styles.close, "aria-label": closeLabel, children: _jsx(X, { size: 16, strokeWidth: 1.75, "aria-hidden": "true" }) })] })] }), _jsx("div", { ref: bodyRef, className: styles.body, onWheel: bodyWheel, onFocus: bodyFocus, children: children }), _jsx("span", { className: styles.srOnly, role: "status", "aria-live": "polite", children: announcement })] }) })] });
384
+ }
385
+ export const BottomSheetClose = DialogPrimitive.Close;
386
+ export default BottomSheet;
@@ -0,0 +1,69 @@
1
+ /* The dim follows the sheet's position in JS; only its color lives here. */
2
+ .overlay { position: fixed; inset: 0; z-index: 50; background: oklch(16% 0 0 / .34); }
3
+ :global(:root[data-theme="dark"]) .overlay { background: oklch(0% 0 0 / .52); }
4
+ /* While it leaves, nothing underneath is blocked and the sheet cannot be grabbed again.
5
+ Radix sets pointer-events: auto inline on the overlay, so only !important lets a click reach the trigger and reverse the exit. */
6
+ .overlay[data-state="closed"], .sheet[data-state="closed"] { pointer-events: none !important; }
7
+
8
+ /* The sheet is as tall as its largest detent plus a hidden extension below the fold; detents are translateY offsets from there. */
9
+ .sheet {
10
+ position: fixed;
11
+ z-index: 51;
12
+ right: 0;
13
+ bottom: calc(var(--sheet-extension) * -1);
14
+ left: 0;
15
+ display: flex;
16
+ flex-direction: column;
17
+ width: min(100%, 36rem);
18
+ height: calc(var(--sheet-max) * 100vh + var(--sheet-extension));
19
+ height: calc(var(--sheet-max) * 100dvh + var(--sheet-extension));
20
+ margin-inline: auto;
21
+ overflow: hidden;
22
+ padding-bottom: calc(var(--sheet-extension) + env(safe-area-inset-bottom, 0px));
23
+ border: 1px solid var(--border);
24
+ border-bottom: 0;
25
+ border-radius: var(--radius-overlay) var(--radius-overlay) 0 0; corner-shape: var(--corner-shape);
26
+ background: var(--surface-raised);
27
+ color: var(--foreground);
28
+ box-shadow: var(--shadow-floating);
29
+ font-family: var(--font-body);
30
+ letter-spacing: var(--tracking-body);
31
+ outline: none;
32
+ will-change: transform;
33
+ }
34
+
35
+ /* The whole header is the handle, so a drag can start anywhere above the content. */
36
+ .header { position: relative; flex: none; padding: var(--space-related) var(--space-comfort) var(--space-inset); cursor: grab; touch-action: none; user-select: none; -webkit-user-select: none; }
37
+ .sheet[data-dragging] .header { cursor: grabbing; }
38
+ /* A hairline appears only once the content scrolls under the header. */
39
+ .header::after { position: absolute; right: 0; bottom: 0; left: 0; height: 1px; background: var(--border); content: ""; opacity: 0; transition: opacity var(--duration-fast) var(--ease-standard); }
40
+ .sheet[data-scrolled] .header::after { opacity: 1; }
41
+
42
+ .grabber { display: flex; width: 3.5rem; height: 1.25rem; align-items: center; justify-content: center; margin: 0 auto var(--space-tight); padding: 0; border: 0; border-radius: var(--radius-full); corner-shape: round; background: transparent; cursor: inherit; -webkit-tap-highlight-color: transparent; }
43
+ .grabberBar { width: 2.25rem; height: 5px; border-radius: var(--radius-full); corner-shape: round; background: var(--border-strong); transition: background-color var(--duration-fast) var(--ease-standard); }
44
+ @media (hover: hover) and (pointer: fine) { .grabber:hover .grabberBar { background: var(--text-muted); } }
45
+ .sheet[data-dragging] .grabberBar { background: var(--text-muted); }
46
+
47
+ .headRow { display: flex; align-items: flex-start; justify-content: space-between; gap: var(--space-inset); }
48
+ .headText { min-width: 0; }
49
+ .title { margin: 0; font-family: var(--font-body); font-size: var(--text-lg); font-weight: 500; letter-spacing: var(--tracking-body); line-height: var(--leading-body); }
50
+ .description { margin: 2px 0 0; color: var(--text-secondary); font-size: var(--text-sm); line-height: var(--leading-body); }
51
+
52
+ .close { display: grid; flex: none; width: 2rem; height: 2rem; place-items: center; margin-top: -2px; padding: 0; border: 1px solid var(--border); border-radius: var(--radius-full); corner-shape: round; background: var(--surface-muted); color: var(--text-secondary); cursor: pointer; -webkit-tap-highlight-color: transparent; transition: background-color var(--duration-fast) var(--ease-standard), color var(--duration-fast) var(--ease-standard); }
53
+ @media (hover: hover) and (pointer: fine) { .close:hover { color: var(--foreground); border-color: var(--border-strong); } }
54
+ .close:active { color: var(--foreground); }
55
+
56
+ /* The content scrolls only at the tallest detent; below it, a drag or scroll opens the sheet first. */
57
+ .body { flex: 1 1 auto; min-height: 0; overflow-y: auto; padding: var(--space-tight) var(--space-comfort) var(--space-section); overscroll-behavior: contain; scrollbar-width: thin; touch-action: pan-y; font-size: var(--text-sm); line-height: var(--leading-body); }
58
+ .sheet:not([data-expanded]) .body { overflow: hidden; touch-action: none; }
59
+
60
+ .srOnly { position: absolute; width: 1px; height: 1px; overflow: hidden; clip-path: inset(50%); white-space: nowrap; }
61
+
62
+ @media (max-width: 40rem) {
63
+ .header { padding-inline: var(--space-inset); }
64
+ .body { padding-inline: var(--space-inset); }
65
+ }
66
+
67
+ @media (prefers-reduced-motion: reduce) {
68
+ .header::after, .grabberBar, .close { transition: none; }
69
+ }
@@ -0,0 +1,13 @@
1
+ import type { MouseEvent } from "react";
2
+ export interface BreadcrumbItem {
3
+ label: string;
4
+ href?: string;
5
+ /** Runs when the crumb is chosen. Without an href the crumb renders as a button, for paths that live in local state. */
6
+ onClick?: (event: MouseEvent<HTMLElement>) => void;
7
+ }
8
+ export interface BreadcrumbProps {
9
+ items: BreadcrumbItem[];
10
+ ariaLabel?: string;
11
+ }
12
+ /** Crumbs present on first render stay still; crumbs added later slide in from the path before them. */
13
+ export declare function Breadcrumb({ items, ariaLabel }: BreadcrumbProps): import("react").JSX.Element;
@@ -0,0 +1,19 @@
1
+ "use client";
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { ChevronRight as NavArrowRight } from "lucide-react";
4
+ import { AnimatePresence, motion, useReducedMotion } from "motion/react";
5
+ import { SiliconLink } from "../../lib/silicon-provider.js";
6
+ import { motionTokens } from "../../lib/motion-tokens.js";
7
+ import styles from "./breadcrumb.module.css";
8
+ /** Crumbs present on first render stay still; crumbs added later slide in from the path before them. */
9
+ export function Breadcrumb({ items, ariaLabel = "Breadcrumb" }) {
10
+ const reduced = useReducedMotion() ?? false;
11
+ const still = { duration: 0 };
12
+ const path = items.map(item => item.label).join("/");
13
+ return _jsx("nav", { "aria-label": ariaLabel, children: _jsx("ol", { className: styles.list, children: _jsx(AnimatePresence, { mode: "popLayout", initial: false, children: items.map((item, index) => {
14
+ const current = index === items.length - 1;
15
+ return _jsxs(motion.li, { layout: reduced ? false : "position", layoutDependency: path, initial: reduced ? false : { opacity: 0, x: -8, filter: `blur(${motionTokens.blur.subtle}px)` }, animate: { opacity: 1, x: 0, filter: "blur(0px)" }, exit: reduced ? { opacity: 0, transition: still } : { opacity: 0, x: -4, filter: `blur(${motionTokens.blur.subtle}px)`, transition: { duration: motionTokens.duration.instant, ease: [...motionTokens.ease.standard] } }, transition: reduced ? still : { duration: motionTokens.duration.standard, ease: [...motionTokens.ease.enter], layout: motionTokens.spring.smooth }, children: [index > 0 && _jsx(NavArrowRight, { width: 14, height: 14, "aria-hidden": "true" }), !current && item.href ? _jsx(SiliconLink, { href: item.href, "data-label": item.label, onClick: item.onClick, children: item.label })
16
+ : !current && item.onClick ? _jsx("button", { type: "button", "data-label": item.label, onClick: item.onClick, children: item.label })
17
+ : _jsx("span", { "aria-current": current ? "page" : undefined, "data-label": item.label, children: item.label })] }, `${item.label}-${index}`);
18
+ }) }) }) });
19
+ }
@@ -0,0 +1,12 @@
1
+ /* The gap between crumbs matches the gap inside one, so every chevron sits centered between its two labels. */
2
+ .list { position: relative; display: flex; flex-wrap: wrap; align-items: center; gap: 2px 6px; margin: 0; padding: 0; list-style: none; }
3
+ .list li { display: inline-flex; align-items: center; gap: 6px; color: var(--text-muted); font-size: var(--text-sm); line-height: var(--leading-body); white-space: nowrap; }
4
+ .list li > svg { width: 16px; height: 16px; flex: 0 0 auto; color: var(--border-strong); stroke-width: 1.75; }
5
+ /* Each crumb reserves the width of its medium weight label, so becoming the current page never shifts the path. */
6
+ .list a, .list li > span, .list li > button { display: inline-flex; flex-direction: column; border-radius: var(--radius-detail); padding: 4px 2px; }
7
+ .list a::after, .list li > span::after, .list li > button::after { height: 0; overflow: hidden; content: attr(data-label); font-weight: 500; visibility: hidden; user-select: none; pointer-events: none; }
8
+ .list li > button { border: 0; margin: 0; background: none; font: inherit; line-height: inherit; text-align: start; cursor: pointer; }
9
+ .list a, .list li > button { color: var(--text-secondary); text-decoration: underline 1px; text-decoration-color: transparent; text-underline-offset: 4px; transition: color var(--duration-fast) var(--ease-standard), text-decoration-color var(--duration-fast) var(--ease-standard); }
10
+ .list [aria-current="page"] { color: var(--accent-strong); font-weight: 500; transition: color var(--duration-fast) var(--ease-standard); }
11
+ @media (hover: hover) and (pointer: fine) { .list a:hover, .list li > button:hover { color: var(--accent-strong); text-decoration-color: color-mix(in oklab, currentColor 45%, transparent); } }
12
+ @media (prefers-reduced-motion: reduce) { .list a, .list li > button, .list [aria-current="page"] { transition: none; } }
@@ -0,0 +1,45 @@
1
+ import { type Ref } from "react";
2
+ export interface BrushChartDatum {
3
+ /** A point in time, as a Date or epoch milliseconds. Points must be in ascending order. */
4
+ date: Date | number;
5
+ value: number;
6
+ }
7
+ export interface BrushChartAnnotation {
8
+ date: Date | number;
9
+ /** Short label printed beside the marker, such as "v2 launch". */
10
+ label: string;
11
+ /** Longer note for the tooltip and screen readers. */
12
+ description?: string;
13
+ }
14
+ /** Use a brush chart for a long, dense time series where people need the whole history and a close look at any stretch of it. */
15
+ export interface BrushChartProps {
16
+ data: BrushChartDatum[];
17
+ /** What is measured, such as "Daily active users". Names the chart for assistive technology. */
18
+ label: string;
19
+ /** Unit after each value, such as "users". */
20
+ unit?: string;
21
+ formatValue?: (value: number) => string;
22
+ /** Formats the value axis. Defaults to a compact number, such as 12K. */
23
+ formatTick?: (value: number) => string;
24
+ /** Formats a date in the tooltip and announcements. */
25
+ formatDate?: (date: Date) => string;
26
+ /** Events drawn as markers on both charts. */
27
+ annotations?: BrushChartAnnotation[];
28
+ /** Controlled window as [start, end] epoch milliseconds. A new value glides the window there. */
29
+ range?: [number, number];
30
+ /** Initial window when uncontrolled. Defaults to the whole series. */
31
+ defaultRange?: [number, number];
32
+ /** Called while the window is dragged, resized, stepped, or reset. */
33
+ onRangeChange?: (range: [number, number]) => void;
34
+ /** Smallest window in milliseconds. Defaults to seven days. */
35
+ minSpan?: number;
36
+ /** Main plot height in pixels. */
37
+ height?: number;
38
+ /** Overview strip height in pixels. */
39
+ overviewHeight?: number;
40
+ emptyLabel?: string;
41
+ ref?: Ref<HTMLElement>;
42
+ className?: string;
43
+ }
44
+ export declare function BrushChart({ data, label, unit, formatValue, formatTick, formatDate, annotations, range, defaultRange, onRangeChange, minSpan, height, overviewHeight, emptyLabel, ref, className }: BrushChartProps): import("react").JSX.Element;
45
+ export default BrushChart;