@delacour/react-native-ui 0.1.0-alpha.3 → 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 (482) hide show
  1. package/README.md +11 -7
  2. package/package.json +49 -5
  3. package/src/components/accordion/AGENTS.md +21 -9
  4. package/src/components/accordion/accordion-content.tsx +7 -4
  5. package/src/components/accordion/accordion-item.tsx +15 -13
  6. package/src/components/accordion/accordion.context.tsx +11 -5
  7. package/src/components/accordion/accordion.variants.test.ts +64 -3
  8. package/src/components/accordion/accordion.variants.ts +33 -9
  9. package/src/components/alert/AGENTS.md +102 -0
  10. package/src/components/alert/alert-action.tsx +17 -0
  11. package/src/components/alert/alert-close-button.tsx +50 -0
  12. package/src/components/alert/alert-content.tsx +15 -0
  13. package/src/components/alert/alert-description.tsx +16 -0
  14. package/src/components/alert/alert-glyphs.ts +25 -0
  15. package/src/components/alert/alert-indicator.tsx +40 -0
  16. package/src/components/alert/alert-title.tsx +18 -0
  17. package/src/components/alert/alert.context.tsx +69 -0
  18. package/src/components/alert/alert.tsx +160 -0
  19. package/src/components/alert/alert.types.ts +10 -0
  20. package/src/components/alert/alert.variants.test.ts +218 -0
  21. package/src/components/alert/alert.variants.ts +180 -0
  22. package/src/components/alert/index.ts +23 -0
  23. package/src/components/avatar/AGENTS.md +106 -0
  24. package/src/components/avatar/avatar-badge.tsx +51 -0
  25. package/src/components/avatar/avatar-group.tsx +139 -0
  26. package/src/components/avatar/avatar.context.tsx +93 -0
  27. package/src/components/avatar/avatar.tsx +201 -0
  28. package/src/components/avatar/avatar.utils.test.ts +174 -0
  29. package/src/components/avatar/avatar.utils.ts +141 -0
  30. package/src/components/avatar/avatar.variants.test.ts +251 -0
  31. package/src/components/avatar/avatar.variants.ts +270 -0
  32. package/src/components/avatar/index.ts +39 -0
  33. package/src/components/badge/AGENTS.md +5 -0
  34. package/src/components/badge/badge.tsx +6 -1
  35. package/src/components/bottom-sheet/AGENTS.md +186 -221
  36. package/src/components/bottom-sheet/bottom-sheet-background.tsx +31 -22
  37. package/src/components/bottom-sheet/bottom-sheet-close.tsx +23 -30
  38. package/src/components/bottom-sheet/bottom-sheet-container.tsx +54 -207
  39. package/src/components/bottom-sheet/bottom-sheet-content.tsx +25 -48
  40. package/src/components/bottom-sheet/bottom-sheet-description.tsx +8 -2
  41. package/src/components/bottom-sheet/bottom-sheet-flat-list.tsx +65 -0
  42. package/src/components/bottom-sheet/bottom-sheet-footer.tsx +53 -120
  43. package/src/components/bottom-sheet/bottom-sheet-handle.tsx +31 -26
  44. package/src/components/bottom-sheet/bottom-sheet-legend-list.tsx +69 -0
  45. package/src/components/bottom-sheet/bottom-sheet-overlay.tsx +25 -147
  46. package/src/components/bottom-sheet/bottom-sheet-provider.tsx +42 -0
  47. package/src/components/bottom-sheet/bottom-sheet-scroll-view.tsx +38 -52
  48. package/src/components/bottom-sheet/bottom-sheet-section-list.tsx +58 -0
  49. package/src/components/bottom-sheet/bottom-sheet-steps.tsx +88 -0
  50. package/src/components/bottom-sheet/bottom-sheet-text-input.tsx +44 -0
  51. package/src/components/bottom-sheet/bottom-sheet-title.tsx +12 -8
  52. package/src/components/bottom-sheet/bottom-sheet-trigger.tsx +19 -37
  53. package/src/components/bottom-sheet/bottom-sheet.tsx +113 -60
  54. package/src/components/bottom-sheet/bottom-sheet.variants.test.ts +47 -70
  55. package/src/components/bottom-sheet/bottom-sheet.variants.ts +50 -105
  56. package/src/components/bottom-sheet/index.ts +39 -17
  57. package/src/components/bottom-sheet/use-bottom-sheet-input.ts +16 -115
  58. package/src/components/button/AGENTS.md +45 -11
  59. package/src/components/button/button-group.tsx +44 -8
  60. package/src/components/button/button.context.tsx +10 -0
  61. package/src/components/button/button.tsx +2 -1
  62. package/src/components/button/button.variants.test.ts +154 -0
  63. package/src/components/button/button.variants.ts +71 -6
  64. package/src/components/button/index.ts +2 -0
  65. package/src/components/calendar/AGENTS.md +142 -0
  66. package/src/components/calendar/calendar-caption.tsx +116 -0
  67. package/src/components/calendar/calendar-day.tsx +103 -0
  68. package/src/components/calendar/calendar-grid.tsx +183 -0
  69. package/src/components/calendar/calendar-header.tsx +34 -0
  70. package/src/components/calendar/calendar-nav.tsx +51 -0
  71. package/src/components/calendar/calendar-picker.tsx +128 -0
  72. package/src/components/calendar/calendar-weekdays.tsx +48 -0
  73. package/src/components/calendar/calendar.context.tsx +108 -0
  74. package/src/components/calendar/calendar.date.test.ts +456 -0
  75. package/src/components/calendar/calendar.date.ts +584 -0
  76. package/src/components/calendar/calendar.tsx +389 -0
  77. package/src/components/calendar/calendar.types.ts +87 -0
  78. package/src/components/calendar/calendar.variants.test.ts +363 -0
  79. package/src/components/calendar/calendar.variants.ts +426 -0
  80. package/src/components/calendar/index.ts +96 -0
  81. package/src/components/card/AGENTS.md +88 -0
  82. package/src/components/card/card-action.tsx +16 -0
  83. package/src/components/card/card-content.tsx +12 -0
  84. package/src/components/card/card-description.tsx +12 -0
  85. package/src/components/card/card-footer.tsx +30 -0
  86. package/src/components/card/card-header.tsx +45 -0
  87. package/src/components/card/card-title.tsx +25 -0
  88. package/src/components/card/card.context.tsx +64 -0
  89. package/src/components/card/card.tsx +94 -0
  90. package/src/components/card/card.types.ts +13 -0
  91. package/src/components/card/card.variants.test.ts +240 -0
  92. package/src/components/card/card.variants.ts +177 -0
  93. package/src/components/card/index.ts +15 -0
  94. package/src/components/chart/AGENTS.md +9 -1
  95. package/src/components/chart/chart.tsx +8 -1
  96. package/src/components/chip/AGENTS.md +113 -0
  97. package/src/components/chip/chip-close-button.tsx +48 -0
  98. package/src/components/chip/chip-end-content.tsx +15 -0
  99. package/src/components/chip/chip-label.tsx +21 -0
  100. package/src/components/chip/chip-start-content.tsx +16 -0
  101. package/src/components/chip/chip.context.tsx +66 -0
  102. package/src/components/chip/chip.tsx +268 -0
  103. package/src/components/chip/chip.types.ts +10 -0
  104. package/src/components/chip/chip.variants.test.ts +335 -0
  105. package/src/components/chip/chip.variants.ts +321 -0
  106. package/src/components/chip/index.ts +24 -0
  107. package/src/components/collapsible/AGENTS.md +97 -0
  108. package/src/components/collapsible/collapsible-content.tsx +97 -0
  109. package/src/components/collapsible/collapsible-description.tsx +17 -0
  110. package/src/components/collapsible/collapsible-indicator.tsx +92 -0
  111. package/src/components/collapsible/collapsible-title.tsx +17 -0
  112. package/src/components/collapsible/collapsible-trigger.tsx +133 -0
  113. package/src/components/collapsible/collapsible.context.tsx +91 -0
  114. package/src/components/collapsible/collapsible.tsx +167 -0
  115. package/src/components/collapsible/collapsible.types.ts +9 -0
  116. package/src/components/collapsible/collapsible.variants.test.ts +338 -0
  117. package/src/components/collapsible/collapsible.variants.ts +228 -0
  118. package/src/components/collapsible/index.ts +32 -0
  119. package/src/components/context-menu/AGENTS.md +103 -0
  120. package/src/components/context-menu/context-menu-content.tsx +64 -0
  121. package/src/components/context-menu/context-menu-item.tsx +22 -0
  122. package/src/components/context-menu/context-menu-preview.tsx +60 -0
  123. package/src/components/context-menu/context-menu-trigger.tsx +160 -0
  124. package/src/components/context-menu/context-menu.context.tsx +91 -0
  125. package/src/components/context-menu/context-menu.tsx +90 -0
  126. package/src/components/context-menu/context-menu.variants.test.ts +266 -0
  127. package/src/components/context-menu/context-menu.variants.ts +147 -0
  128. package/src/components/context-menu/index.ts +18 -0
  129. package/src/components/dialog/AGENTS.md +97 -0
  130. package/src/components/dialog/dialog-body.tsx +21 -0
  131. package/src/components/dialog/dialog-close.tsx +82 -0
  132. package/src/components/dialog/dialog-content.tsx +153 -0
  133. package/src/components/dialog/dialog-description.tsx +23 -0
  134. package/src/components/dialog/dialog-footer.tsx +32 -0
  135. package/src/components/dialog/dialog-header.tsx +20 -0
  136. package/src/components/dialog/dialog-title.tsx +42 -0
  137. package/src/components/dialog/dialog-trigger.tsx +54 -0
  138. package/src/components/dialog/dialog.context.tsx +90 -0
  139. package/src/components/dialog/dialog.tsx +119 -0
  140. package/src/components/dialog/dialog.variants.test.ts +224 -0
  141. package/src/components/dialog/dialog.variants.ts +173 -0
  142. package/src/components/dialog/index.ts +30 -0
  143. package/src/components/drawer/AGENTS.md +123 -0
  144. package/src/components/drawer/drawer-body.tsx +46 -0
  145. package/src/components/drawer/drawer-close.tsx +76 -0
  146. package/src/components/drawer/drawer-content.tsx +186 -0
  147. package/src/components/drawer/drawer-description.tsx +23 -0
  148. package/src/components/drawer/drawer-footer.tsx +21 -0
  149. package/src/components/drawer/drawer-header.tsx +42 -0
  150. package/src/components/drawer/drawer-title.tsx +35 -0
  151. package/src/components/drawer/drawer-trigger.tsx +54 -0
  152. package/src/components/drawer/drawer.context.tsx +114 -0
  153. package/src/components/drawer/drawer.tsx +119 -0
  154. package/src/components/drawer/drawer.variants.test.ts +392 -0
  155. package/src/components/drawer/drawer.variants.ts +244 -0
  156. package/src/components/drawer/index.ts +41 -0
  157. package/src/components/drawer/use-drawer-pan.ts +108 -0
  158. package/src/components/empty-state/AGENTS.md +90 -0
  159. package/src/components/empty-state/empty-state-content.tsx +17 -0
  160. package/src/components/empty-state/empty-state-description.tsx +12 -0
  161. package/src/components/empty-state/empty-state-header.tsx +17 -0
  162. package/src/components/empty-state/empty-state-media.tsx +56 -0
  163. package/src/components/empty-state/empty-state-title.tsx +18 -0
  164. package/src/components/empty-state/empty-state.context.tsx +65 -0
  165. package/src/components/empty-state/empty-state.tsx +84 -0
  166. package/src/components/empty-state/empty-state.types.ts +14 -0
  167. package/src/components/empty-state/empty-state.variants.test.ts +245 -0
  168. package/src/components/empty-state/empty-state.variants.ts +132 -0
  169. package/src/components/empty-state/index.ts +20 -0
  170. package/src/components/fab/AGENTS.md +119 -0
  171. package/src/components/fab/fab-action.tsx +118 -0
  172. package/src/components/fab/fab-group.tsx +214 -0
  173. package/src/components/fab/fab-label.tsx +18 -0
  174. package/src/components/fab/fab.context.tsx +125 -0
  175. package/src/components/fab/fab.tsx +156 -0
  176. package/src/components/fab/fab.types.ts +17 -0
  177. package/src/components/fab/fab.variants.test.ts +274 -0
  178. package/src/components/fab/fab.variants.ts +234 -0
  179. package/src/components/fab/index.ts +37 -0
  180. package/src/components/feedback/AGENTS.md +95 -0
  181. package/src/components/feedback/feedback-action.tsx +16 -0
  182. package/src/components/feedback/feedback-cancel.tsx +34 -0
  183. package/src/components/feedback/feedback-close.tsx +21 -0
  184. package/src/components/feedback/feedback-content.tsx +24 -0
  185. package/src/components/feedback/feedback-field.tsx +70 -0
  186. package/src/components/feedback/feedback-footer.tsx +21 -0
  187. package/src/components/feedback/feedback-panel.tsx +54 -0
  188. package/src/components/feedback/feedback-submit.tsx +74 -0
  189. package/src/components/feedback/feedback-title.tsx +34 -0
  190. package/src/components/feedback/feedback-trigger.tsx +20 -0
  191. package/src/components/feedback/feedback.context.tsx +73 -0
  192. package/src/components/feedback/feedback.tsx +135 -0
  193. package/src/components/feedback/feedback.variants.test.ts +185 -0
  194. package/src/components/feedback/feedback.variants.ts +128 -0
  195. package/src/components/feedback/index.ts +24 -0
  196. package/src/components/field/AGENTS.md +19 -0
  197. package/src/components/field/field-label.tsx +19 -5
  198. package/src/components/field/field.context.tsx +17 -0
  199. package/src/components/field/field.tsx +15 -2
  200. package/src/components/field/field.variants.test.ts +24 -0
  201. package/src/components/field/field.variants.ts +20 -0
  202. package/src/components/field/index.ts +1 -0
  203. package/src/components/item/AGENTS.md +92 -0
  204. package/src/components/item/index.ts +25 -0
  205. package/src/components/item/item-actions.tsx +28 -0
  206. package/src/components/item/item-content.tsx +15 -0
  207. package/src/components/item/item-description.tsx +12 -0
  208. package/src/components/item/item-footer.tsx +13 -0
  209. package/src/components/item/item-group.tsx +32 -0
  210. package/src/components/item/item-header.tsx +13 -0
  211. package/src/components/item/item-media.tsx +37 -0
  212. package/src/components/item/item-separator.tsx +13 -0
  213. package/src/components/item/item-title.tsx +17 -0
  214. package/src/components/item/item.context.tsx +66 -0
  215. package/src/components/item/item.tsx +218 -0
  216. package/src/components/item/item.types.ts +12 -0
  217. package/src/components/item/item.variants.test.ts +263 -0
  218. package/src/components/item/item.variants.ts +200 -0
  219. package/src/components/kpi/AGENTS.md +125 -0
  220. package/src/components/kpi/index.ts +45 -0
  221. package/src/components/kpi/kpi-action.tsx +12 -0
  222. package/src/components/kpi/kpi-content.tsx +35 -0
  223. package/src/components/kpi/kpi-footer.tsx +52 -0
  224. package/src/components/kpi/kpi-group.tsx +70 -0
  225. package/src/components/kpi/kpi-header.tsx +25 -0
  226. package/src/components/kpi/kpi-icon.tsx +28 -0
  227. package/src/components/kpi/kpi-sparkline.tsx +145 -0
  228. package/src/components/kpi/kpi-stat.tsx +20 -0
  229. package/src/components/kpi/kpi-title.tsx +19 -0
  230. package/src/components/kpi/kpi-trend.tsx +105 -0
  231. package/src/components/kpi/kpi-value.tsx +32 -0
  232. package/src/components/kpi/kpi.context.tsx +111 -0
  233. package/src/components/kpi/kpi.tsx +154 -0
  234. package/src/components/kpi/kpi.types.ts +9 -0
  235. package/src/components/kpi/kpi.variants.test.ts +428 -0
  236. package/src/components/kpi/kpi.variants.ts +347 -0
  237. package/src/components/label/AGENTS.md +89 -0
  238. package/src/components/label/index.ts +10 -0
  239. package/src/components/label/label.tsx +97 -0
  240. package/src/components/label/label.variants.test.ts +176 -0
  241. package/src/components/label/label.variants.ts +123 -0
  242. package/src/components/menu/AGENTS.md +176 -0
  243. package/src/components/menu/index.ts +44 -0
  244. package/src/components/menu/menu-background.tsx +17 -0
  245. package/src/components/menu/menu-checkbox-item.tsx +57 -0
  246. package/src/components/menu/menu-content.tsx +303 -0
  247. package/src/components/menu/menu-item.tsx +162 -0
  248. package/src/components/menu/menu-label.tsx +14 -0
  249. package/src/components/menu/menu-radio-group.tsx +50 -0
  250. package/src/components/menu/menu-radio-item.tsx +57 -0
  251. package/src/components/menu/menu-separator.tsx +11 -0
  252. package/src/components/menu/menu-sub-content.tsx +72 -0
  253. package/src/components/menu/menu-sub-trigger.tsx +50 -0
  254. package/src/components/menu/menu-sub.tsx +75 -0
  255. package/src/components/menu/menu-trigger.tsx +57 -0
  256. package/src/components/menu/menu.context.tsx +214 -0
  257. package/src/components/menu/menu.tsx +88 -0
  258. package/src/components/menu/menu.types.ts +33 -0
  259. package/src/components/menu/menu.variants.test.ts +400 -0
  260. package/src/components/menu/menu.variants.ts +353 -0
  261. package/src/components/meter/AGENTS.md +135 -0
  262. package/src/components/meter/index.ts +43 -0
  263. package/src/components/meter/meter-fill.tsx +19 -0
  264. package/src/components/meter/meter-header.tsx +17 -0
  265. package/src/components/meter/meter-label.tsx +18 -0
  266. package/src/components/meter/meter-output.tsx +42 -0
  267. package/src/components/meter/meter-segments.tsx +83 -0
  268. package/src/components/meter/meter-track.tsx +16 -0
  269. package/src/components/meter/meter.context.tsx +62 -0
  270. package/src/components/meter/meter.tsx +286 -0
  271. package/src/components/meter/meter.types.ts +30 -0
  272. package/src/components/meter/meter.variants.test.ts +430 -0
  273. package/src/components/meter/meter.variants.ts +347 -0
  274. package/src/components/overlay/AGENTS.md +89 -0
  275. package/src/components/overlay/index.ts +27 -0
  276. package/src/components/overlay/overlay-portal.tsx +84 -0
  277. package/src/components/overlay/overlay-presence.test.ts +46 -0
  278. package/src/components/overlay/overlay-presence.ts +49 -0
  279. package/src/components/overlay/overlay-registry.test.ts +96 -0
  280. package/src/components/overlay/overlay-registry.ts +99 -0
  281. package/src/components/overlay/overlay-scrim.tsx +49 -0
  282. package/src/components/overlay/overlay.context.tsx +48 -0
  283. package/src/components/overlay/overlay.tsx +118 -0
  284. package/src/components/overlay/overlay.variants.test.ts +37 -0
  285. package/src/components/overlay/overlay.variants.ts +33 -0
  286. package/src/components/overlay/use-overlay-back-handler.ts +34 -0
  287. package/src/components/overlay/use-overlay-presence.ts +87 -0
  288. package/src/components/popover/AGENTS.md +138 -0
  289. package/src/components/popover/index.ts +48 -0
  290. package/src/components/popover/popover-anchor.tsx +49 -0
  291. package/src/components/popover/popover-arrow.tsx +89 -0
  292. package/src/components/popover/popover-close.tsx +73 -0
  293. package/src/components/popover/popover-content.tsx +235 -0
  294. package/src/components/popover/popover-description.tsx +18 -0
  295. package/src/components/popover/popover-title.tsx +33 -0
  296. package/src/components/popover/popover-trigger.tsx +56 -0
  297. package/src/components/popover/popover.context.tsx +71 -0
  298. package/src/components/popover/popover.position.test.ts +354 -0
  299. package/src/components/popover/popover.position.ts +286 -0
  300. package/src/components/popover/popover.tsx +123 -0
  301. package/src/components/popover/popover.variants.test.ts +135 -0
  302. package/src/components/popover/popover.variants.ts +85 -0
  303. package/src/components/popover/use-anchor-measure.ts +76 -0
  304. package/src/components/popover/use-anchored-content.ts +219 -0
  305. package/src/components/pressable/AGENTS.md +11 -1
  306. package/src/components/pressable/pressable.tsx +7 -2
  307. package/src/components/pressable/pressable.variants.test.ts +29 -0
  308. package/src/components/pressable/pressable.variants.ts +17 -0
  309. package/src/components/progress/AGENTS.md +120 -0
  310. package/src/components/progress/index.ts +40 -0
  311. package/src/components/progress/progress-fill.tsx +152 -0
  312. package/src/components/progress/progress-header.tsx +19 -0
  313. package/src/components/progress/progress-label.tsx +27 -0
  314. package/src/components/progress/progress-output.tsx +47 -0
  315. package/src/components/progress/progress-track.tsx +38 -0
  316. package/src/components/progress/progress.context.tsx +79 -0
  317. package/src/components/progress/progress.tsx +171 -0
  318. package/src/components/progress/progress.types.ts +23 -0
  319. package/src/components/progress/progress.variants.test.ts +335 -0
  320. package/src/components/progress/progress.variants.ts +284 -0
  321. package/src/components/progress-button/AGENTS.md +116 -0
  322. package/src/components/progress-button/index.ts +36 -0
  323. package/src/components/progress-button/progress-button-done.tsx +33 -0
  324. package/src/components/progress-button/progress-button-label.tsx +27 -0
  325. package/src/components/progress-button/progress-button.context.tsx +93 -0
  326. package/src/components/progress-button/progress-button.tsx +414 -0
  327. package/src/components/progress-button/progress-button.variants.test.ts +314 -0
  328. package/src/components/progress-button/progress-button.variants.ts +236 -0
  329. package/src/components/provider/AGENTS.md +38 -25
  330. package/src/components/provider/provider.tsx +38 -11
  331. package/src/components/radio/AGENTS.md +8 -10
  332. package/src/components/radio/radio.variants.test.ts +5 -6
  333. package/src/components/radio/radio.variants.ts +6 -7
  334. package/src/components/rating/AGENTS.md +143 -0
  335. package/src/components/rating/index.ts +43 -0
  336. package/src/components/rating/rating-output.tsx +39 -0
  337. package/src/components/rating/rating-star.tsx +70 -0
  338. package/src/components/rating/rating-stars.tsx +219 -0
  339. package/src/components/rating/rating.context.tsx +79 -0
  340. package/src/components/rating/rating.tsx +163 -0
  341. package/src/components/rating/rating.variants.test.ts +347 -0
  342. package/src/components/rating/rating.variants.ts +354 -0
  343. package/src/components/screen/screen-navbar-back-button.tsx +1 -1
  344. package/src/components/selection-mode/AGENTS.md +155 -0
  345. package/src/components/selection-mode/index.ts +43 -0
  346. package/src/components/selection-mode/selection-mode-action.tsx +65 -0
  347. package/src/components/selection-mode/selection-mode-bar.tsx +79 -0
  348. package/src/components/selection-mode/selection-mode-group.tsx +123 -0
  349. package/src/components/selection-mode/selection-mode-header.tsx +85 -0
  350. package/src/components/selection-mode/selection-mode-indicator.tsx +118 -0
  351. package/src/components/selection-mode/selection-mode-item.tsx +182 -0
  352. package/src/components/selection-mode/selection-mode.context.tsx +89 -0
  353. package/src/components/selection-mode/selection-mode.tsx +207 -0
  354. package/src/components/selection-mode/selection-mode.types.ts +36 -0
  355. package/src/components/selection-mode/selection-mode.variants.test.ts +292 -0
  356. package/src/components/selection-mode/selection-mode.variants.ts +329 -0
  357. package/src/components/skeleton/AGENTS.md +120 -0
  358. package/src/components/skeleton/index.ts +15 -0
  359. package/src/components/skeleton/skeleton-clock.ts +48 -0
  360. package/src/components/skeleton/skeleton-group.tsx +64 -0
  361. package/src/components/skeleton/skeleton-shimmer.tsx +80 -0
  362. package/src/components/skeleton/skeleton.context.tsx +45 -0
  363. package/src/components/skeleton/skeleton.tsx +227 -0
  364. package/src/components/skeleton/skeleton.variants.test.ts +245 -0
  365. package/src/components/skeleton/skeleton.variants.ts +222 -0
  366. package/src/components/slide-button/AGENTS.md +122 -0
  367. package/src/components/slide-button/index.ts +43 -0
  368. package/src/components/slide-button/slide-button-label.tsx +70 -0
  369. package/src/components/slide-button/slide-button-thumb.tsx +93 -0
  370. package/src/components/slide-button/slide-button.context.tsx +76 -0
  371. package/src/components/slide-button/slide-button.tsx +457 -0
  372. package/src/components/slide-button/slide-button.variants.test.ts +288 -0
  373. package/src/components/slide-button/slide-button.variants.ts +324 -0
  374. package/src/components/slider/index.ts +2 -0
  375. package/src/components/slider/slider-thumb.tsx +33 -1
  376. package/src/components/slider/slider.variants.test.ts +52 -0
  377. package/src/components/slider/slider.variants.ts +56 -2
  378. package/src/components/stack-card/AGENTS.md +169 -0
  379. package/src/components/stack-card/index.ts +37 -0
  380. package/src/components/stack-card/stack-card-action.tsx +66 -0
  381. package/src/components/stack-card/stack-card-actions.tsx +18 -0
  382. package/src/components/stack-card/stack-card-card.tsx +71 -0
  383. package/src/components/stack-card/stack-card-empty.tsx +30 -0
  384. package/src/components/stack-card/stack-card-slot.tsx +90 -0
  385. package/src/components/stack-card/stack-card-stamp.tsx +76 -0
  386. package/src/components/stack-card/stack-card.context.tsx +117 -0
  387. package/src/components/stack-card/stack-card.tsx +557 -0
  388. package/src/components/stack-card/stack-card.types.ts +26 -0
  389. package/src/components/stack-card/stack-card.variants.test.ts +355 -0
  390. package/src/components/stack-card/stack-card.variants.ts +360 -0
  391. package/src/components/steps/AGENTS.md +153 -0
  392. package/src/components/steps/index.ts +45 -0
  393. package/src/components/steps/steps-connector.tsx +31 -0
  394. package/src/components/steps/steps-description.tsx +26 -0
  395. package/src/components/steps/steps-indicator.tsx +106 -0
  396. package/src/components/steps/steps-item.tsx +225 -0
  397. package/src/components/steps/steps-panel.tsx +41 -0
  398. package/src/components/steps/steps-title.tsx +32 -0
  399. package/src/components/steps/steps-track.tsx +59 -0
  400. package/src/components/steps/steps.context.tsx +123 -0
  401. package/src/components/steps/steps.tsx +166 -0
  402. package/src/components/steps/steps.variants.test.ts +388 -0
  403. package/src/components/steps/steps.variants.ts +468 -0
  404. package/src/components/surface/AGENTS.md +72 -0
  405. package/src/components/surface/index.ts +15 -0
  406. package/src/components/surface/surface.context.tsx +54 -0
  407. package/src/components/surface/surface.tsx +70 -0
  408. package/src/components/surface/surface.variants.test.ts +180 -0
  409. package/src/components/surface/surface.variants.ts +133 -0
  410. package/src/components/swipe/AGENTS.md +140 -0
  411. package/src/components/swipe/index.ts +31 -0
  412. package/src/components/swipe/swipe-action.tsx +99 -0
  413. package/src/components/swipe/swipe-end.tsx +14 -0
  414. package/src/components/swipe/swipe-group.tsx +55 -0
  415. package/src/components/swipe/swipe-panel.tsx +55 -0
  416. package/src/components/swipe/swipe-start.tsx +15 -0
  417. package/src/components/swipe/swipe.context.tsx +135 -0
  418. package/src/components/swipe/swipe.tsx +393 -0
  419. package/src/components/swipe/swipe.types.ts +7 -0
  420. package/src/components/swipe/swipe.variants.test.ts +337 -0
  421. package/src/components/swipe/swipe.variants.ts +290 -0
  422. package/src/components/switch/switch.variants.ts +2 -2
  423. package/src/components/tabs/AGENTS.md +4 -3
  424. package/src/components/tabs/tabs.variants.test.ts +1 -1
  425. package/src/components/tabs/tabs.variants.ts +4 -6
  426. package/src/components/textarea/AGENTS.md +86 -0
  427. package/src/components/textarea/index.ts +16 -0
  428. package/src/components/textarea/textarea.tsx +157 -0
  429. package/src/components/textarea/textarea.variants.test.ts +219 -0
  430. package/src/components/textarea/textarea.variants.ts +171 -0
  431. package/src/components/toast/AGENTS.md +140 -0
  432. package/src/components/toast/index.ts +74 -0
  433. package/src/components/toast/toast-action.tsx +59 -0
  434. package/src/components/toast/toast-api.ts +32 -0
  435. package/src/components/toast/toast-close.tsx +44 -0
  436. package/src/components/toast/toast-content.tsx +11 -0
  437. package/src/components/toast/toast-description.tsx +11 -0
  438. package/src/components/toast/toast-indicator.tsx +34 -0
  439. package/src/components/toast/toast-item.tsx +308 -0
  440. package/src/components/toast/toast-title.tsx +13 -0
  441. package/src/components/toast/toast-viewport.tsx +135 -0
  442. package/src/components/toast/toast.context.tsx +69 -0
  443. package/src/components/toast/toast.store.test.ts +259 -0
  444. package/src/components/toast/toast.store.ts +338 -0
  445. package/src/components/toast/toast.timer.test.ts +64 -0
  446. package/src/components/toast/toast.timer.ts +51 -0
  447. package/src/components/toast/toast.tsx +132 -0
  448. package/src/components/toast/toast.variants.test.ts +393 -0
  449. package/src/components/toast/toast.variants.ts +304 -0
  450. package/src/components/toggle-button/AGENTS.md +106 -0
  451. package/src/components/toggle-button/index.ts +30 -0
  452. package/src/components/toggle-button/toggle-button-group.tsx +156 -0
  453. package/src/components/toggle-button/toggle-button-label.tsx +19 -0
  454. package/src/components/toggle-button/toggle-button.context.tsx +130 -0
  455. package/src/components/toggle-button/toggle-button.tsx +172 -0
  456. package/src/components/toggle-button/toggle-button.variants.test.ts +189 -0
  457. package/src/components/toggle-button/toggle-button.variants.ts +160 -0
  458. package/src/components/tooltip/AGENTS.md +133 -0
  459. package/src/components/tooltip/index.ts +28 -0
  460. package/src/components/tooltip/tooltip-arrow.tsx +40 -0
  461. package/src/components/tooltip/tooltip-content.tsx +179 -0
  462. package/src/components/tooltip/tooltip-description.tsx +19 -0
  463. package/src/components/tooltip/tooltip-text.tsx +21 -0
  464. package/src/components/tooltip/tooltip-title.tsx +20 -0
  465. package/src/components/tooltip/tooltip-trigger.tsx +103 -0
  466. package/src/components/tooltip/tooltip.context.tsx +68 -0
  467. package/src/components/tooltip/tooltip.tsx +213 -0
  468. package/src/components/tooltip/tooltip.variants.test.ts +231 -0
  469. package/src/components/tooltip/tooltip.variants.ts +177 -0
  470. package/src/hooks/use-calm-motion.tsx +31 -0
  471. package/src/lib/calm-motion.test.ts +17 -0
  472. package/src/lib/calm-motion.ts +23 -0
  473. package/src/lib/cn.test.ts +14 -0
  474. package/src/lib/cn.ts +2 -2
  475. package/src/no-gorhom.test.ts +54 -0
  476. package/src/styles/geometry.test.ts +1 -0
  477. package/src/styles/theme.css +4 -0
  478. package/src/styles/tokens.css +11 -0
  479. package/src/styles/tokens.test.ts +22 -0
  480. package/src/styles/tokens.ts +20 -0
  481. package/src/components/bottom-sheet/bottom-sheet-portal.tsx +0 -72
  482. package/src/components/bottom-sheet/bottom-sheet.context.tsx +0 -135
@@ -0,0 +1,119 @@
1
+ import { type ReactElement, type ReactNode, useCallback, useId, useMemo, useRef } from "react";
2
+ import { useControllableState } from "../../hooks/use-controllable-state";
3
+ import { type DrawerFocusTarget, DrawerProvider, type DrawerRootValue } from "./drawer.context";
4
+ import { DrawerBody } from "./drawer-body";
5
+ import { DrawerClose } from "./drawer-close";
6
+ import { DrawerContent } from "./drawer-content";
7
+ import { DrawerDescription } from "./drawer-description";
8
+ import { DrawerFooter } from "./drawer-footer";
9
+ import { DrawerHeader } from "./drawer-header";
10
+ import { DrawerTitle } from "./drawer-title";
11
+ import { DrawerTrigger } from "./drawer-trigger";
12
+
13
+ export type DrawerProps = {
14
+ /** Whether the drawer is open. Pass it to control the drawer; omit it and the drawer holds its own state. */
15
+ isOpen?: boolean;
16
+ /** Whether an uncontrolled drawer starts open. Default false. */
17
+ defaultOpen?: boolean;
18
+ /** Called with the new value whenever the drawer opens or closes, from any path. */
19
+ onOpenChange?: (isOpen: boolean) => void;
20
+ /** Whether a scrim tap, Android back and the iOS escape gesture close it. Default true. */
21
+ isDismissible?: boolean;
22
+ children: ReactNode;
23
+ };
24
+
25
+ function DrawerRoot({
26
+ isOpen,
27
+ defaultOpen = false,
28
+ onOpenChange,
29
+ isDismissible = true,
30
+ children,
31
+ }: DrawerProps): ReactElement {
32
+ const [open, setControllableOpen] = useControllableState({
33
+ defaultValue: defaultOpen,
34
+ onChange: onOpenChange,
35
+ value: isOpen,
36
+ });
37
+
38
+ // Every path — trigger, close, scrim, back, escape, swipe — lands here, and
39
+ // a second close while the first is still exiting would report twice.
40
+ const setOpen = useCallback(
41
+ (next: boolean) => {
42
+ if (next !== open) setControllableOpen(next);
43
+ },
44
+ [open, setControllableOpen]
45
+ );
46
+ const close = useCallback(() => setOpen(false), [setOpen]);
47
+
48
+ const titleId = useId();
49
+ const descriptionId = useId();
50
+ const triggerRef = useRef<DrawerFocusTarget | null>(null);
51
+ const titleRef = useRef<DrawerFocusTarget | null>(null);
52
+
53
+ const value = useMemo<DrawerRootValue>(
54
+ () => ({ close, descriptionId, isDismissible, isOpen: open, setOpen, titleId, titleRef, triggerRef }),
55
+ [close, descriptionId, isDismissible, open, setOpen, titleId]
56
+ );
57
+
58
+ return <DrawerProvider value={value}>{children}</DrawerProvider>;
59
+ }
60
+
61
+ /**
62
+ * A panel that slides in from an edge and covers the app until dismissed — a
63
+ * navigation menu from the start edge, a filter panel from the end, a
64
+ * notifications tray from the top.
65
+ *
66
+ * It covers the app rather than pushing it aside, and it can be swiped back
67
+ * toward its edge. `start` and `end` follow the layout direction. A bottom
68
+ * drawer is for short, fixed content: anything with snap points, a keyboard or
69
+ * a long list is a `BottomSheet`.
70
+ *
71
+ * Draws through `Overlay.Portal`, so the app needs `OverlayProvider` mounted
72
+ * once at its root.
73
+ *
74
+ * @example
75
+ * <Drawer>
76
+ * <Drawer.Trigger asChild>
77
+ * <Button accessibilityLabel="Menu" size="icon-md" variant="ghost">
78
+ * <Icon icon={IconMenu} />
79
+ * </Button>
80
+ * </Drawer.Trigger>
81
+ * <Drawer.Content side="start">
82
+ * <Drawer.Header>
83
+ * <Drawer.Title>Menu</Drawer.Title>
84
+ * <Drawer.Description>Signed in as aria@harbour.studio</Drawer.Description>
85
+ * </Drawer.Header>
86
+ * <Drawer.Body>…rows…</Drawer.Body>
87
+ * <Drawer.Footer>
88
+ * <Drawer.Close asChild>
89
+ * <Button variant="secondary">Sign out</Button>
90
+ * </Drawer.Close>
91
+ * </Drawer.Footer>
92
+ * </Drawer.Content>
93
+ * </Drawer>
94
+ *
95
+ * @example
96
+ * // Controlled, from the end edge.
97
+ * <Drawer isOpen={isOpen} onOpenChange={setIsOpen}>
98
+ * <Drawer.Content side="end" size="lg">…</Drawer.Content>
99
+ * </Drawer>
100
+ */
101
+ export const Drawer = Object.assign(DrawerRoot, {
102
+ /** The control that opens it. `asChild` donates the press to a `Button`. */
103
+ Trigger: DrawerTrigger,
104
+ /** The portal, scrim and sliding panel. Renders nothing while closed. */
105
+ Content: DrawerContent,
106
+ /** The title block, with a ✕ at its trailing edge. */
107
+ Header: DrawerHeader,
108
+ /** The heading — a `Text.Header` the panel is labelled by. */
109
+ Title: DrawerTitle,
110
+ /** Muted supporting copy — a `Text.Paragraph`. */
111
+ Description: DrawerDescription,
112
+ /** The panel's content, scrolling by default. */
113
+ Body: DrawerBody,
114
+ /** Actions pinned to the panel's end, under a hairline. */
115
+ Footer: DrawerFooter,
116
+ /** A ✕, or — with `asChild` — any control that closes it. */
117
+ Close: DrawerClose,
118
+ displayName: "DelacourUI.Drawer",
119
+ });
@@ -0,0 +1,392 @@
1
+ import { describe, expect, test } from "bun:test";
2
+ import { declaredTokens } from "../../styles/theme-tokens.test";
3
+ import { OVERLAY_SCRIM_TOKEN } from "../overlay/overlay.variants";
4
+ import {
5
+ DRAWER_CLOSE_HIT_SLOP,
6
+ DRAWER_DISMISS_FRACTION,
7
+ DRAWER_EDGES,
8
+ DRAWER_FLING_VELOCITY,
9
+ DRAWER_PAN_ACTIVE_OFFSET,
10
+ DRAWER_RELEASE_MS,
11
+ DRAWER_RUBBER_BAND,
12
+ DRAWER_SCRIM_TOKEN,
13
+ DRAWER_SIDES,
14
+ DRAWER_SIZE_EXTENT,
15
+ DRAWER_SIZES,
16
+ drawerVariants,
17
+ resolveDrawerDrag,
18
+ resolveDrawerDragFraction,
19
+ resolveDrawerEdge,
20
+ resolveDrawerExitDuration,
21
+ resolveDrawerExtent,
22
+ resolveDrawerFrameExtent,
23
+ resolveDrawerInsets,
24
+ resolveDrawerOffset,
25
+ resolveDrawerRelease,
26
+ } from "./drawer.variants";
27
+
28
+ const LIGHT = declaredTokens("light");
29
+ const DARK = declaredTokens("dark");
30
+
31
+ /** `border-t` and friends set a width, not a colour, and name no token. */
32
+ const STRUCTURAL_BORDER_SUFFIXES = new Set(["t", "b", "l", "r", "x", "y", "s", "e"]);
33
+
34
+ /** Every theme token a class string paints with, with any `/alpha` suffix dropped. */
35
+ function colorTokens(cls: string): string[] {
36
+ const tokens: string[] = [];
37
+ for (const [, utility, token] of cls.matchAll(/\b(bg|border|text)-([a-z][\w-]*)(?:\/\d+)?\b/g)) {
38
+ if (utility === "border" && STRUCTURAL_BORDER_SUFFIXES.has(token)) continue;
39
+ tokens.push(token);
40
+ }
41
+ return tokens;
42
+ }
43
+
44
+ /** The slots this component declares, pinned so a new one has to be added before the sweeps can miss it. */
45
+ const SLOT_NAMES = [
46
+ "scrim",
47
+ "positioner",
48
+ "content",
49
+ "inner",
50
+ "header",
51
+ "heading",
52
+ "body",
53
+ "bodyContent",
54
+ "footer",
55
+ "close",
56
+ ] as const;
57
+
58
+ describe("constants", () => {
59
+ test("the sides, edges and sizes are the ones the spec names", () => {
60
+ expect([...DRAWER_SIDES]).toEqual(["start", "end", "top", "bottom"]);
61
+ expect([...DRAWER_EDGES]).toEqual(["left", "right", "top", "bottom"]);
62
+ expect([...DRAWER_SIZES]).toEqual(["sm", "md", "lg", "full"]);
63
+ });
64
+
65
+ test("each size is a fraction of the window with a cap, and full is uncapped", () => {
66
+ expect(DRAWER_SIZE_EXTENT).toEqual({
67
+ full: { fraction: 0.94, max: Number.POSITIVE_INFINITY },
68
+ lg: { fraction: 0.88, max: 400 },
69
+ md: { fraction: 0.78, max: 320 },
70
+ sm: { fraction: 0.62, max: 280 },
71
+ });
72
+ });
73
+
74
+ test("the release rules: 40% of the extent, or a fling faster than 800 pt/s", () => {
75
+ expect(DRAWER_DISMISS_FRACTION).toBe(0.4);
76
+ expect(DRAWER_FLING_VELOCITY).toBe(800);
77
+ expect(DRAWER_RUBBER_BAND).toBe(40);
78
+ });
79
+
80
+ test("the pan waits for real travel before it claims the touch", () => {
81
+ expect(DRAWER_PAN_ACTIVE_OFFSET).toBeGreaterThanOrEqual(8);
82
+ expect(DRAWER_PAN_ACTIVE_OFFSET).toBeLessThanOrEqual(16);
83
+ });
84
+
85
+ test("the scrim token is the foundation's", () => {
86
+ expect(DRAWER_SCRIM_TOKEN).toBe(OVERLAY_SCRIM_TOKEN);
87
+ });
88
+
89
+ test("the close glyph gets slop toward 44pt", () => {
90
+ expect(DRAWER_CLOSE_HIT_SLOP).toBe(8);
91
+ });
92
+
93
+ test("a release animation is finite and ordered", () => {
94
+ expect(DRAWER_RELEASE_MS.min).toBeGreaterThan(0);
95
+ expect(DRAWER_RELEASE_MS.max).toBeGreaterThan(DRAWER_RELEASE_MS.min);
96
+ });
97
+ });
98
+
99
+ describe("resolveDrawerEdge", () => {
100
+ test("start is left and end is right in a left-to-right layout", () => {
101
+ expect(resolveDrawerEdge("start", false)).toBe("left");
102
+ expect(resolveDrawerEdge("end", false)).toBe("right");
103
+ });
104
+
105
+ test("start and end swap in a right-to-left layout", () => {
106
+ expect(resolveDrawerEdge("start", true)).toBe("right");
107
+ expect(resolveDrawerEdge("end", true)).toBe("left");
108
+ });
109
+
110
+ test("top and bottom do not depend on the direction", () => {
111
+ for (const isRTL of [false, true]) {
112
+ expect(resolveDrawerEdge("top", isRTL)).toBe("top");
113
+ expect(resolveDrawerEdge("bottom", isRTL)).toBe("bottom");
114
+ }
115
+ });
116
+ });
117
+
118
+ describe("resolveDrawerExtent", () => {
119
+ test("a phone takes the fraction of each size", () => {
120
+ // 62%, 78%, 88% of 320 sit under every cap.
121
+ expect(resolveDrawerExtent("sm", 320)).toBe(198);
122
+ expect(resolveDrawerExtent("md", 320)).toBe(250);
123
+ expect(resolveDrawerExtent("lg", 320)).toBe(282);
124
+ expect(resolveDrawerExtent("full", 320)).toBe(301);
125
+ });
126
+
127
+ test("a wide window stops at each size's cap, except full", () => {
128
+ expect(resolveDrawerExtent("sm", 1024)).toBe(280);
129
+ expect(resolveDrawerExtent("md", 1024)).toBe(320);
130
+ expect(resolveDrawerExtent("lg", 1024)).toBe(400);
131
+ expect(resolveDrawerExtent("full", 1024)).toBe(963);
132
+ });
133
+
134
+ test("a 393pt phone reaches the md cap only just", () => {
135
+ expect(resolveDrawerExtent("md", 393)).toBe(307);
136
+ expect(resolveDrawerExtent("md", 430)).toBe(320);
137
+ });
138
+
139
+ test("an unmeasured window is zero, never negative", () => {
140
+ expect(resolveDrawerExtent("md", 0)).toBe(0);
141
+ expect(resolveDrawerExtent("md", -10)).toBe(0);
142
+ });
143
+ });
144
+
145
+ describe("resolveDrawerOffset", () => {
146
+ test("open is no translate on any edge", () => {
147
+ for (const edge of DRAWER_EDGES) {
148
+ const offset = resolveDrawerOffset(edge, 300, 1);
149
+ expect(offset.translateX + 0).toBe(0);
150
+ expect(offset.translateY + 0).toBe(0);
151
+ }
152
+ });
153
+
154
+ test("closed is the whole extent off the docked edge", () => {
155
+ expect(resolveDrawerOffset("left", 300, 0)).toEqual({ translateX: -300, translateY: 0 });
156
+ expect(resolveDrawerOffset("right", 300, 0)).toEqual({ translateX: 300, translateY: 0 });
157
+ expect(resolveDrawerOffset("top", 300, 0)).toEqual({ translateX: 0, translateY: -300 });
158
+ expect(resolveDrawerOffset("bottom", 300, 0)).toEqual({ translateX: 0, translateY: 300 });
159
+ });
160
+
161
+ test("half way is half the extent", () => {
162
+ expect(resolveDrawerOffset("left", 300, 0.5).translateX).toBe(-150);
163
+ expect(resolveDrawerOffset("bottom", 300, 0.25).translateY).toBe(225);
164
+ });
165
+ });
166
+
167
+ describe("resolveDrawerDrag", () => {
168
+ test("movement toward the docked edge follows the finger one to one", () => {
169
+ expect(resolveDrawerDrag("left", -120)).toBe(-120);
170
+ expect(resolveDrawerDrag("right", 120)).toBe(120);
171
+ expect(resolveDrawerDrag("top", -60)).toBe(-60);
172
+ expect(resolveDrawerDrag("bottom", 60)).toBe(60);
173
+ });
174
+
175
+ test("movement away from the edge rubber-bands", () => {
176
+ // 40 / (1 + 40 / 40) = 20.
177
+ expect(resolveDrawerDrag("left", 40)).toBe(20);
178
+ expect(resolveDrawerDrag("right", -40)).toBe(-20);
179
+ expect(resolveDrawerDrag("top", 40)).toBe(20);
180
+ expect(resolveDrawerDrag("bottom", -40)).toBe(-20);
181
+ });
182
+
183
+ test("the rubber band never reaches its cap however far the finger goes", () => {
184
+ const far = resolveDrawerDrag("left", 10_000);
185
+ expect(far).toBeGreaterThan(39);
186
+ expect(far).toBeLessThan(DRAWER_RUBBER_BAND);
187
+ });
188
+
189
+ test("no movement is no drag", () => {
190
+ expect(resolveDrawerDrag("left", 0) + 0).toBe(0);
191
+ });
192
+ });
193
+
194
+ describe("resolveDrawerDragFraction", () => {
195
+ test("how much of the extent has been dragged toward the edge, 0 to 1", () => {
196
+ expect(resolveDrawerDragFraction("left", -150, 300)).toBe(0.5);
197
+ expect(resolveDrawerDragFraction("right", 75, 300)).toBe(0.25);
198
+ expect(resolveDrawerDragFraction("bottom", 600, 300)).toBe(1);
199
+ });
200
+
201
+ test("a drag away from the edge is no fraction", () => {
202
+ expect(resolveDrawerDragFraction("left", 20, 300)).toBe(0);
203
+ expect(resolveDrawerDragFraction("top", 20, 300)).toBe(0);
204
+ });
205
+
206
+ test("an unmeasured panel is no fraction", () => {
207
+ expect(resolveDrawerDragFraction("left", -100, 0)).toBe(0);
208
+ });
209
+ });
210
+
211
+ describe("resolveDrawerRelease", () => {
212
+ const extent = 300;
213
+
214
+ test("a short, slow drag restores", () => {
215
+ expect(resolveDrawerRelease({ edge: "left", extent, translation: -60, velocity: -100 })).toBe("restore");
216
+ });
217
+
218
+ test("past 40% of the extent dismisses", () => {
219
+ expect(resolveDrawerRelease({ edge: "left", extent, translation: -121, velocity: 0 })).toBe("dismiss");
220
+ expect(resolveDrawerRelease({ edge: "right", extent, translation: 121, velocity: 0 })).toBe("dismiss");
221
+ expect(resolveDrawerRelease({ edge: "top", extent, translation: -121, velocity: 0 })).toBe("dismiss");
222
+ expect(resolveDrawerRelease({ edge: "bottom", extent, translation: 121, velocity: 0 })).toBe("dismiss");
223
+ });
224
+
225
+ test("just under the threshold restores", () => {
226
+ expect(resolveDrawerRelease({ edge: "left", extent, translation: -119, velocity: 0 })).toBe("restore");
227
+ });
228
+
229
+ test("a fling toward the edge dismisses from a short drag", () => {
230
+ expect(resolveDrawerRelease({ edge: "left", extent, translation: -20, velocity: -900 })).toBe("dismiss");
231
+ expect(resolveDrawerRelease({ edge: "bottom", extent, translation: 20, velocity: 900 })).toBe("dismiss");
232
+ });
233
+
234
+ test("a fling away from the edge restores, even past the threshold", () => {
235
+ expect(resolveDrawerRelease({ edge: "left", extent, translation: -200, velocity: 900 })).toBe("restore");
236
+ expect(resolveDrawerRelease({ edge: "right", extent, translation: 200, velocity: -900 })).toBe("restore");
237
+ });
238
+
239
+ test("a drag away from the edge restores", () => {
240
+ expect(resolveDrawerRelease({ edge: "left", extent, translation: 30, velocity: 0 })).toBe("restore");
241
+ });
242
+
243
+ test("an unmeasured panel restores", () => {
244
+ expect(resolveDrawerRelease({ edge: "left", extent: 0, translation: -200, velocity: 0 })).toBe("restore");
245
+ });
246
+ });
247
+
248
+ describe("resolveDrawerExitDuration", () => {
249
+ test("a fast fling covers the rest quickly, but never faster than the floor", () => {
250
+ expect(resolveDrawerExitDuration({ remaining: 100, velocity: 2000 })).toBe(DRAWER_RELEASE_MS.min);
251
+ });
252
+
253
+ test("the remaining distance at the release velocity", () => {
254
+ expect(resolveDrawerExitDuration({ remaining: 150, velocity: 1000 })).toBe(150);
255
+ });
256
+
257
+ test("accepts the velocity's sign either way", () => {
258
+ expect(resolveDrawerExitDuration({ remaining: 150, velocity: -1000 })).toBe(150);
259
+ });
260
+
261
+ test("a slow release takes the ceiling, never longer", () => {
262
+ expect(resolveDrawerExitDuration({ remaining: 200, velocity: 0 })).toBe(DRAWER_RELEASE_MS.max);
263
+ expect(resolveDrawerExitDuration({ remaining: 200, velocity: 50 })).toBe(DRAWER_RELEASE_MS.max);
264
+ });
265
+
266
+ test("nothing left to cover is the floor", () => {
267
+ expect(resolveDrawerExitDuration({ remaining: 0, velocity: 500 })).toBe(DRAWER_RELEASE_MS.min);
268
+ });
269
+ });
270
+
271
+ describe("resolveDrawerInsets", () => {
272
+ const insets = { bottom: 34, left: 3, right: 5, top: 59 };
273
+
274
+ test("a left drawer pads the three sides that meet the screen, not the one facing the app", () => {
275
+ expect(resolveDrawerInsets("left", insets)).toEqual({ bottom: 34, left: 3, right: 0, top: 59 });
276
+ });
277
+
278
+ test("a right drawer", () => {
279
+ expect(resolveDrawerInsets("right", insets)).toEqual({ bottom: 34, left: 0, right: 5, top: 59 });
280
+ });
281
+
282
+ test("a top drawer clears the status bar, not the home indicator", () => {
283
+ expect(resolveDrawerInsets("top", insets)).toEqual({ bottom: 0, left: 3, right: 5, top: 59 });
284
+ });
285
+
286
+ test("a bottom drawer clears the home indicator, not the status bar", () => {
287
+ expect(resolveDrawerInsets("bottom", insets)).toEqual({ bottom: 34, left: 3, right: 5, top: 0 });
288
+ });
289
+ });
290
+
291
+ describe("resolveDrawerFrameExtent", () => {
292
+ const insets = { bottom: 34, left: 0, right: 0, top: 62 };
293
+
294
+ test("the docked edge's inset is added, so the size measures the content, not the status bar", () => {
295
+ expect(resolveDrawerFrameExtent("top", 280, insets)).toBe(342);
296
+ expect(resolveDrawerFrameExtent("bottom", 280, insets)).toBe(314);
297
+ });
298
+
299
+ test("a side drawer in portrait has no inset on its docked edge", () => {
300
+ expect(resolveDrawerFrameExtent("left", 320, insets)).toBe(320);
301
+ expect(resolveDrawerFrameExtent("right", 320, { ...insets, right: 47 })).toBe(367);
302
+ });
303
+ });
304
+
305
+ describe("drawerVariants", () => {
306
+ test("the panel is a popover surface", () => {
307
+ expect(drawerVariants().content()).toContain("bg-popover");
308
+ expect(drawerVariants().content()).toContain("absolute");
309
+ });
310
+
311
+ test("defaults to the left edge", () => {
312
+ expect(drawerVariants().content()).toContain("left-0");
313
+ });
314
+
315
+ test("each edge docks to its side and rounds only the free corners", () => {
316
+ const left = drawerVariants({ edge: "left" }).content();
317
+ expect(left).toContain("left-0");
318
+ expect(left).toContain("inset-y-0");
319
+ expect(left).toContain("rounded-r-lg");
320
+ expect(left).not.toMatch(/rounded-(l|t|b)-/);
321
+
322
+ const right = drawerVariants({ edge: "right" }).content();
323
+ expect(right).toContain("right-0");
324
+ expect(right).toContain("inset-y-0");
325
+ expect(right).toContain("rounded-l-lg");
326
+ expect(right).not.toMatch(/rounded-(r|t|b)-/);
327
+
328
+ const top = drawerVariants({ edge: "top" }).content();
329
+ expect(top).toContain("top-0");
330
+ expect(top).toContain("inset-x-0");
331
+ expect(top).toContain("rounded-b-lg");
332
+ expect(top).not.toMatch(/rounded-(l|r|t)-/);
333
+
334
+ const bottom = drawerVariants({ edge: "bottom" }).content();
335
+ expect(bottom).toContain("bottom-0");
336
+ expect(bottom).toContain("inset-x-0");
337
+ expect(bottom).toContain("rounded-t-lg");
338
+ expect(bottom).not.toMatch(/rounded-(l|r|b)-/);
339
+ });
340
+
341
+ test("a caller's className reaches the panel and wins a conflict", () => {
342
+ const content = drawerVariants().content({ className: "bg-card" });
343
+ expect(content).toContain("bg-card");
344
+ expect(content).not.toContain("bg-popover");
345
+ });
346
+
347
+ test("the positioner fills the window the panel is placed in", () => {
348
+ expect(drawerVariants().positioner()).toBe("absolute inset-0");
349
+ });
350
+
351
+ test("the scrim paints the foundation's token", () => {
352
+ expect(drawerVariants().scrim()).toContain(`bg-${DRAWER_SCRIM_TOKEN}`);
353
+ });
354
+
355
+ test("the header is a row with the heading taking the slack", () => {
356
+ expect(drawerVariants().header()).toContain("flex-row");
357
+ expect(drawerVariants().heading()).toContain("flex-1");
358
+ });
359
+
360
+ test("the footer sits under a hairline", () => {
361
+ const footer = drawerVariants().footer();
362
+ expect(footer).toContain("border-t");
363
+ expect(footer).toContain("border-border");
364
+ });
365
+
366
+ test("the footer pins to the panel's end even with no body above it", () => {
367
+ expect(drawerVariants().footer()).toContain("mt-auto");
368
+ });
369
+
370
+ test("the body takes the panel's remaining height", () => {
371
+ expect(drawerVariants().body()).toContain("flex-1");
372
+ });
373
+ });
374
+
375
+ describe("every token the slots name", () => {
376
+ test("is declared in both variants of theme.css", () => {
377
+ for (const edge of DRAWER_EDGES) {
378
+ const slots = drawerVariants({ edge });
379
+ for (const name of SLOT_NAMES) {
380
+ for (const token of colorTokens(slots[name]() ?? "")) {
381
+ expect({ inLight: LIGHT.has(token), slot: name, token }).toEqual({ inLight: true, slot: name, token });
382
+ expect({ inDark: DARK.has(token), slot: name, token }).toEqual({ inDark: true, slot: name, token });
383
+ }
384
+ }
385
+ }
386
+ });
387
+
388
+ test("the reader found tokens at all", () => {
389
+ expect(colorTokens(drawerVariants().content())).toContain("popover");
390
+ expect(colorTokens(drawerVariants().footer())).toContain("border");
391
+ });
392
+ });