@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,183 @@
1
+ import { type ReactElement, type ReactNode, useCallback, useLayoutEffect, useMemo, useRef } from "react";
2
+ import { type LayoutChangeEvent, View, type ViewProps } from "react-native";
3
+ import { Gesture, GestureDetector } from "react-native-gesture-handler";
4
+ import Animated, {
5
+ Easing,
6
+ useAnimatedStyle,
7
+ useSharedValue,
8
+ withSequence,
9
+ withSpring,
10
+ withTiming,
11
+ } from "react-native-reanimated";
12
+ import { scheduleOnRN } from "react-native-worklets";
13
+ import { useCalmMotion } from "../../hooks/use-calm-motion";
14
+ import { playHaptic } from "../pressable";
15
+ import { useCalendarPart } from "./calendar.context";
16
+ import { type CalendarCell, formatMonthYear, monthGrid, serialiseDate } from "./calendar.date";
17
+ import { calendarVariants, resolvePageDirection } from "./calendar.variants";
18
+ import { CalendarDay } from "./calendar-day";
19
+ import { CalendarPicker } from "./calendar-picker";
20
+
21
+ export type CalendarGridProps = Omit<ViewProps, "children"> & {
22
+ className?: string;
23
+ /** Draws one cell. Omitted, a `Calendar.Day` for its date. */
24
+ renderDay?: (cell: CalendarCell) => ReactNode;
25
+ };
26
+
27
+ /** How far a sideways drag travels before the grid claims it, and a vertical one before it gives up. */
28
+ const PAN_ACTIVATE_X = 12;
29
+ const PAN_FAIL_Y = 12;
30
+
31
+ /** How far a drag past a bound still follows the finger, as a fraction — enough to feel the edge. */
32
+ const BOUND_RESISTANCE = 0.2;
33
+
34
+ const SLIDE = { duration: 200, easing: Easing.out(Easing.cubic) } as const;
35
+ const SETTLE_SPRING = { damping: 20, stiffness: 260 } as const;
36
+
37
+ /**
38
+ * The six weeks of the visible month, and the surface a swipe pages on.
39
+ *
40
+ * **Paging is a slide out and a slide in.** A released swipe slides the old month off the side it
41
+ * was dragged towards, the month changes, and the new one slides in from the other side — the
42
+ * same entrance the arrows give it. Both off-screen positions are clipped, so the frame where the
43
+ * month swaps is never seen. With calm motion on, both slides are skipped and the page simply
44
+ * swaps; the page change itself is behaviour, not decoration, and always happens.
45
+ *
46
+ * **A drag towards a bound resists** rather than moving freely, then settles back with a light
47
+ * impact haptic — the edge is felt instead of the month silently refusing to turn.
48
+ *
49
+ * **The grid claims a sideways drag and gives up a vertical one**, so it lives inside a scrolling
50
+ * screen or a sheet without stealing the scroll.
51
+ *
52
+ * It also hosts `Calendar.Picker` over itself while a jump view is open, and hides its weeks
53
+ * rather than unmounting them, so the calendar's height never moves.
54
+ */
55
+ export function CalendarGrid({ className, renderDay, ...props }: CalendarGridProps): ReactElement {
56
+ const calendar = useCalendarPart("Calendar.Grid");
57
+ const {
58
+ canGoNext,
59
+ canGoPrev,
60
+ goNext,
61
+ goPrev,
62
+ isDisabled,
63
+ label,
64
+ locale,
65
+ pageDirection,
66
+ size,
67
+ swipeToPage,
68
+ variant,
69
+ view,
70
+ } = calendar;
71
+ const { visibleMonth, weekStartsOn } = calendar;
72
+ const slots = calendarVariants({ size, variant });
73
+ const isCalm = useCalmMotion();
74
+
75
+ const width = useSharedValue(0);
76
+ const translateX = useSharedValue(0);
77
+
78
+ const handleLayout = useCallback(
79
+ (event: LayoutChangeEvent) => {
80
+ width.value = event.nativeEvent.layout.width;
81
+ },
82
+ [width]
83
+ );
84
+
85
+ const page = useCallback((direction: -1 | 1) => (direction === 1 ? goNext() : goPrev()), [goNext, goPrev]);
86
+
87
+ const pan = useMemo(
88
+ () =>
89
+ Gesture.Pan()
90
+ .enabled(swipeToPage && view === "days" && !isDisabled)
91
+ .activeOffsetX([-PAN_ACTIVATE_X, PAN_ACTIVATE_X])
92
+ .failOffsetY([-PAN_FAIL_Y, PAN_FAIL_Y])
93
+ .onUpdate((event) => {
94
+ "worklet";
95
+ const blocked = (event.translationX > 0 && !canGoPrev) || (event.translationX < 0 && !canGoNext);
96
+ translateX.value = blocked ? event.translationX * BOUND_RESISTANCE : event.translationX;
97
+ })
98
+ .onEnd((event) => {
99
+ "worklet";
100
+ const direction = resolvePageDirection({
101
+ canGoNext,
102
+ canGoPrev,
103
+ translationX: event.translationX,
104
+ velocityX: event.velocityX,
105
+ width: width.value,
106
+ });
107
+
108
+ if (direction === 0) {
109
+ const blocked = (event.translationX > 0 && !canGoPrev) || (event.translationX < 0 && !canGoNext);
110
+ if (blocked) playHaptic("light");
111
+ translateX.value = withSpring(0, SETTLE_SPRING);
112
+ return;
113
+ }
114
+
115
+ playHaptic("selection");
116
+ if (isCalm) {
117
+ translateX.value = 0;
118
+ scheduleOnRN(page, direction);
119
+ return;
120
+ }
121
+ translateX.value = withTiming(-direction * width.value, SLIDE, (finished) => {
122
+ "worklet";
123
+ if (finished) scheduleOnRN(page, direction);
124
+ });
125
+ })
126
+ .onFinalize((_event, success) => {
127
+ "worklet";
128
+ if (!success) translateX.value = withSpring(0, SETTLE_SPRING);
129
+ }),
130
+ [canGoNext, canGoPrev, isCalm, isDisabled, page, swipeToPage, translateX, view, width]
131
+ );
132
+
133
+ // The new month slides in from the side the old one left by. Skipped on the first render — a
134
+ // calendar does not animate onto the screen — and for a jump, which has no side.
135
+ const monthKey = serialiseDate(visibleMonth);
136
+ const previousKey = useRef(monthKey);
137
+ useLayoutEffect(() => {
138
+ if (previousKey.current === monthKey) return;
139
+ previousKey.current = monthKey;
140
+ if (isCalm || pageDirection === 0 || width.value <= 0) {
141
+ translateX.value = 0;
142
+ return;
143
+ }
144
+ translateX.value = withSequence(withTiming(pageDirection * width.value, { duration: 0 }), withTiming(0, SLIDE));
145
+ }, [isCalm, monthKey, pageDirection, translateX, width]);
146
+
147
+ const slideStyle = useAnimatedStyle(() => ({ transform: [{ translateX: translateX.value }] }));
148
+
149
+ const weeks = useMemo(() => monthGrid(visibleMonth, weekStartsOn), [visibleMonth, weekStartsOn]);
150
+ const isPicking = view !== "days";
151
+
152
+ return (
153
+ <View className={slots.grid({ className })} onLayout={handleLayout} {...props}>
154
+ <GestureDetector gesture={pan}>
155
+ <Animated.View
156
+ accessibilityElementsHidden={isPicking}
157
+ accessibilityLabel={label ?? formatMonthYear(visibleMonth, locale)}
158
+ role="grid"
159
+ className={isPicking ? "opacity-0" : undefined}
160
+ importantForAccessibility={isPicking ? "no-hide-descendants" : "auto"}
161
+ pointerEvents={isPicking ? "none" : "auto"}
162
+ style={slideStyle}
163
+ >
164
+ {weeks.map((week) => (
165
+ <View className={slots.week()} key={serialiseDate(week[0]?.date ?? visibleMonth)}>
166
+ {week.map((cell) =>
167
+ renderDay ? (
168
+ <View className="flex-1" key={serialiseDate(cell.date)}>
169
+ {renderDay(cell)}
170
+ </View>
171
+ ) : (
172
+ <CalendarDay date={cell.date} key={serialiseDate(cell.date)} />
173
+ )
174
+ )}
175
+ </View>
176
+ ))}
177
+ </Animated.View>
178
+ </GestureDetector>
179
+ {isPicking ? <CalendarPicker /> : null}
180
+ </View>
181
+ );
182
+ }
183
+ CalendarGrid.displayName = "DelacourUI.Calendar.Grid";
@@ -0,0 +1,34 @@
1
+ import type { ReactElement } from "react";
2
+ import { View, type ViewProps } from "react-native";
3
+ import { useCalendarPart } from "./calendar.context";
4
+ import { calendarVariants } from "./calendar.variants";
5
+ import { CalendarCaption } from "./calendar-caption";
6
+ import { CalendarNav } from "./calendar-nav";
7
+
8
+ export type CalendarHeaderProps = ViewProps & {
9
+ className?: string;
10
+ };
11
+
12
+ /**
13
+ * The row above the grid: the previous arrow, the caption and the next arrow.
14
+ *
15
+ * Omit `children` for that default; pass them to rearrange it — both arrows on one side, say, or
16
+ * a caption with a "Today" button beside it.
17
+ */
18
+ export function CalendarHeader({ className, children, ...props }: CalendarHeaderProps): ReactElement {
19
+ const { size, variant } = useCalendarPart("Calendar.Header");
20
+ const slots = calendarVariants({ size, variant });
21
+
22
+ return (
23
+ <View className={slots.header({ className })} {...props}>
24
+ {children ?? (
25
+ <>
26
+ <CalendarNav direction="prev" />
27
+ <CalendarCaption />
28
+ <CalendarNav direction="next" />
29
+ </>
30
+ )}
31
+ </View>
32
+ );
33
+ }
34
+ CalendarHeader.displayName = "DelacourUI.Calendar.Header";
@@ -0,0 +1,51 @@
1
+ import type { ReactElement } from "react";
2
+ import { IconChevronLeft, IconChevronRight } from "../../icons/central";
3
+ import { Button, type ButtonProps } from "../button";
4
+ import { Icon } from "../icon";
5
+ import { useCalendarPart } from "./calendar.context";
6
+ import { resolvePartTestID } from "./calendar.variants";
7
+
8
+ export type CalendarNavProps = Omit<ButtonProps, "children" | "onPress"> & {
9
+ /** Which way the arrow pages. */
10
+ direction: "prev" | "next";
11
+ /** The arrow's accessible name. Defaults to "Previous month" / "Next month" — or year, in the months view. */
12
+ label?: string;
13
+ };
14
+
15
+ /**
16
+ * One paging arrow: a ghost icon button that disables itself at the bound it points at.
17
+ *
18
+ * In the month grid it pages a month; in the months view, a year; in the years view, where the
19
+ * list scrolls instead, it is disabled. A disabled calendar disables it too — a read-only one
20
+ * does not, because looking at another month changes nothing.
21
+ */
22
+ export function CalendarNav({ direction, label, isDisabled, ...props }: CalendarNavProps): ReactElement {
23
+ const {
24
+ canGoNext,
25
+ canGoPrev,
26
+ goNext,
27
+ goPrev,
28
+ isDisabled: calendarDisabled,
29
+ testID,
30
+ view,
31
+ } = useCalendarPart("Calendar.Nav");
32
+ const isPrev = direction === "prev";
33
+ const canGo = (isPrev ? canGoPrev : canGoNext) && view !== "years";
34
+ const unit = view === "months" ? "year" : "month";
35
+
36
+ return (
37
+ <Button
38
+ accessibilityLabel={label ?? `${isPrev ? "Previous" : "Next"} ${unit}`}
39
+ haptic="selection"
40
+ isDisabled={isDisabled ?? (calendarDisabled || !canGo)}
41
+ onPress={isPrev ? goPrev : goNext}
42
+ size="icon-sm"
43
+ testID={resolvePartTestID(testID, direction)}
44
+ variant="ghost"
45
+ {...props}
46
+ >
47
+ <Icon icon={isPrev ? IconChevronLeft : IconChevronRight} />
48
+ </Button>
49
+ );
50
+ }
51
+ CalendarNav.displayName = "DelacourUI.Calendar.Nav";
@@ -0,0 +1,128 @@
1
+ import { type ReactElement, useCallback, useMemo, useRef } from "react";
2
+ import { type LayoutChangeEvent, ScrollView, View, type ViewProps } from "react-native";
3
+ import Animated, { FadeIn, FadeOut } from "react-native-reanimated";
4
+ import { useCalmMotion } from "../../hooks/use-calm-motion";
5
+ import { cn } from "../../lib/cn";
6
+ import { Pressable } from "../pressable";
7
+ import { Text } from "../text";
8
+ import { useCalendarPart } from "./calendar.context";
9
+ import { compareMonths, formatYear, monthNames } from "./calendar.date";
10
+ import { calendarVariants, isMonthInBounds, resolvePartTestID } from "./calendar.variants";
11
+
12
+ export type CalendarPickerProps = Omit<ViewProps, "children"> & {
13
+ className?: string;
14
+ };
15
+
16
+ const CROSSFADE_MS = 150;
17
+
18
+ /**
19
+ * The months and years jump views, laid over the grid while one is open.
20
+ *
21
+ * **Months** is twelve names in a three-column grid: a tap shows that month and returns to the
22
+ * days. **Years** is a scrolling three-column list over the calendar's year span, opened already
23
+ * scrolled to the visible year: a tap returns to the months view in that year, so a birth date
24
+ * is three taps — caption, caption, year — then a month and a day.
25
+ *
26
+ * A month outside the paging bounds is disabled. The view crossfades with the grid it covers,
27
+ * and with calm motion on it simply appears.
28
+ *
29
+ * Rendered by `Calendar.Grid`, which sizes it — it fills the grid's own box, so opening it never
30
+ * changes the calendar's height. Exported for a custom grid that wants to host it itself.
31
+ */
32
+ export function CalendarPicker({ className, ...props }: CalendarPickerProps): ReactElement {
33
+ const calendar = useCalendarPart("Calendar.Picker");
34
+ const { goToMonth, isDisabled, locale, monthBounds, setView, size, variant, view, visibleMonth, yearSpan } = calendar;
35
+ const isCalm = useCalmMotion();
36
+ const slots = calendarVariants({ size, variant });
37
+
38
+ const names = useMemo(() => monthNames(locale, "short"), [locale]);
39
+ const years = useMemo(() => {
40
+ const list: number[] = [];
41
+ for (let year = yearSpan.from; year <= yearSpan.to; year += 1) list.push(year);
42
+ return list;
43
+ }, [yearSpan.from, yearSpan.to]);
44
+
45
+ const scroller = useRef<ScrollView>(null);
46
+ const hasScrolled = useRef(false);
47
+ const handleCurrentLayout = useCallback((event: LayoutChangeEvent) => {
48
+ if (hasScrolled.current) return;
49
+ hasScrolled.current = true;
50
+ scroller.current?.scrollTo({ animated: false, y: event.nativeEvent.layout.y });
51
+ }, []);
52
+
53
+ const pickMonth = useCallback(
54
+ (month: number) => {
55
+ goToMonth({ day: 1, month, year: visibleMonth.year });
56
+ setView("days");
57
+ },
58
+ [goToMonth, setView, visibleMonth.year]
59
+ );
60
+
61
+ const pickYear = useCallback(
62
+ (year: number) => {
63
+ goToMonth({ day: 1, month: visibleMonth.month, year });
64
+ setView("months");
65
+ },
66
+ [goToMonth, setView, visibleMonth.month]
67
+ );
68
+
69
+ const fade = isCalm ? undefined : FadeIn.duration(CROSSFADE_MS);
70
+ const fadeOut = isCalm ? undefined : FadeOut.duration(CROSSFADE_MS);
71
+
72
+ return (
73
+ <Animated.View className={cn("absolute inset-0", className)} entering={fade} exiting={fadeOut} {...props}>
74
+ {view === "months" ? (
75
+ <View accessibilityRole="list" className={slots.pickerGrid()}>
76
+ {names.map((name, index) => {
77
+ const month = index + 1;
78
+ const isCurrent = month === visibleMonth.month;
79
+ const inBounds = isMonthInBounds({ day: 1, month, year: visibleMonth.year }, monthBounds);
80
+ const item = calendarVariants({ size, tone: isCurrent ? "selected" : "plain", variant });
81
+ return (
82
+ <Pressable
83
+ accessibilityState={{ selected: isCurrent }}
84
+ className={item.pickerItem({ className: inBounds ? undefined : "opacity-40" })}
85
+ disabled={isDisabled || !inBounds}
86
+ feedback="fade"
87
+ haptic="selection"
88
+ key={name}
89
+ onPress={() => pickMonth(month)}
90
+ testID={resolvePartTestID(calendar.testID, `month-${month}`)}
91
+ >
92
+ <Text className={item.pickerItemLabel()}>{name}</Text>
93
+ </Pressable>
94
+ );
95
+ })}
96
+ </View>
97
+ ) : (
98
+ <ScrollView ref={scroller} showsVerticalScrollIndicator={false}>
99
+ <View accessibilityRole="list" className={slots.pickerGrid()}>
100
+ {years.map((year) => {
101
+ const isCurrent = year === visibleMonth.year;
102
+ const item = calendarVariants({ size, tone: isCurrent ? "selected" : "plain", variant });
103
+ const inBounds =
104
+ (!monthBounds.first || compareMonths({ day: 1, month: 12, year }, monthBounds.first) >= 0) &&
105
+ (!monthBounds.last || compareMonths({ day: 1, month: 1, year }, monthBounds.last) <= 0);
106
+ return (
107
+ <Pressable
108
+ accessibilityState={{ selected: isCurrent }}
109
+ className={item.pickerItem({ className: inBounds ? undefined : "opacity-40" })}
110
+ disabled={isDisabled || !inBounds}
111
+ feedback="fade"
112
+ haptic="selection"
113
+ key={year}
114
+ onLayout={isCurrent ? handleCurrentLayout : undefined}
115
+ onPress={() => pickYear(year)}
116
+ testID={resolvePartTestID(calendar.testID, `year-${year}`)}
117
+ >
118
+ <Text className={item.pickerItemLabel()}>{formatYear(year, locale)}</Text>
119
+ </Pressable>
120
+ );
121
+ })}
122
+ </View>
123
+ </ScrollView>
124
+ )}
125
+ </Animated.View>
126
+ );
127
+ }
128
+ CalendarPicker.displayName = "DelacourUI.Calendar.Picker";
@@ -0,0 +1,48 @@
1
+ import { type ReactElement, useMemo } from "react";
2
+ import { View, type ViewProps } from "react-native";
3
+ import { Text } from "../text";
4
+ import { useCalendarPart } from "./calendar.context";
5
+ import { weekdayLabels } from "./calendar.date";
6
+ import { calendarVariants } from "./calendar.variants";
7
+
8
+ export type CalendarWeekdaysProps = Omit<ViewProps, "children"> & {
9
+ className?: string;
10
+ /** How the names are drawn. The accessible name is always the full one. */
11
+ format?: "narrow" | "short";
12
+ };
13
+
14
+ /**
15
+ * The row of weekday names, in the grid's column order and the calendar's locale.
16
+ *
17
+ * Each column says its short name and is announced by its full one, so a screen reader reads
18
+ * "Monday" rather than "Mon" — or "M", which is two different days. Hidden, not removed, while a
19
+ * jump view is open, so the calendar's height holds still.
20
+ */
21
+ export function CalendarWeekdays({ className, format = "short", ...props }: CalendarWeekdaysProps): ReactElement {
22
+ const { locale, size, variant, view, weekStartsOn } = useCalendarPart("Calendar.Weekdays");
23
+ const slots = calendarVariants({ size, variant });
24
+ const isHidden = view !== "days";
25
+
26
+ const names = useMemo(
27
+ () => ({ shown: weekdayLabels(locale, weekStartsOn, format), spoken: weekdayLabels(locale, weekStartsOn, "long") }),
28
+ [format, locale, weekStartsOn]
29
+ );
30
+
31
+ return (
32
+ <View
33
+ accessibilityElementsHidden={isHidden}
34
+ className={slots.weekdays({ className: [isHidden ? "opacity-0" : undefined, className] })}
35
+ importantForAccessibility={isHidden ? "no-hide-descendants" : "auto"}
36
+ {...props}
37
+ >
38
+ {names.shown.map((name, index) => (
39
+ <View className={slots.weekday()} key={names.spoken[index]}>
40
+ <Text accessibilityLabel={names.spoken[index]} className={slots.weekdayLabel()}>
41
+ {name}
42
+ </Text>
43
+ </View>
44
+ ))}
45
+ </View>
46
+ );
47
+ }
48
+ CalendarWeekdays.displayName = "DelacourUI.Calendar.Weekdays";
@@ -0,0 +1,108 @@
1
+ import { createContext, type ReactElement, type ReactNode, use } from "react";
2
+ import type { CalendarDate, CalendarSelectionState, Weekday } from "./calendar.date";
3
+ import type { CalendarCaptionLayout } from "./calendar.types";
4
+ import type { CalendarRangeRole, CalendarSize, CalendarVariant, CalendarView } from "./calendar.variants";
5
+
6
+ export type CalendarContextValue = {
7
+ mode: CalendarSelectionState["mode"];
8
+ variant: CalendarVariant;
9
+ size: CalendarSize;
10
+ /** The tag names are formatted in, or `undefined` for the device's own. */
11
+ locale: string | undefined;
12
+ /** The first column's weekday, resolved — never `auto`. */
13
+ weekStartsOn: Weekday;
14
+ /** The first of the month the grid shows. */
15
+ visibleMonth: CalendarDate;
16
+ /**
17
+ * Which way the last page went: `1` forward, `-1` back, `0` for a jump or the first render. The
18
+ * grid reads it to slide the new month in from the side it came from.
19
+ */
20
+ pageDirection: -1 | 0 | 1;
21
+ /** The body's view: the month grid, or a jump view. */
22
+ view: CalendarView;
23
+ setView: (view: CalendarView) => void;
24
+ /** Shows the month `date` falls in, held inside the month bounds. */
25
+ goToMonth: (date: CalendarDate) => void;
26
+ /** One month back. In the months view, one year. */
27
+ goPrev: () => void;
28
+ /** One month forward. In the months view, one year. */
29
+ goNext: () => void;
30
+ canGoPrev: boolean;
31
+ canGoNext: boolean;
32
+ /** The first and last months paging reaches; `null` is unbounded. */
33
+ monthBounds: { first: CalendarDate | null; last: CalendarDate | null };
34
+ /** The years the year view lists. */
35
+ yearSpan: { from: number; to: number };
36
+ /** Whether a day cannot be picked — bounds and matchers, not the calendar's own state. */
37
+ isDateDisabled: (date: CalendarDate) => boolean;
38
+ isDateSelected: (date: CalendarDate) => boolean;
39
+ /** Where a day sits in the range, or `null`. Always `null` outside range mode. */
40
+ rangeRole: (date: CalendarDate) => CalendarRangeRole | null;
41
+ /** The selection, by mode. */
42
+ selection: CalendarSelectionState;
43
+ /** Applies a tap on `date` to the selection. Ignored while disabled, read-only, or on a disabled day. */
44
+ select: (date: CalendarDate) => void;
45
+ today: CalendarDate;
46
+ isDisabled: boolean;
47
+ isInvalid: boolean;
48
+ isReadOnly: boolean;
49
+ showOutsideDays: boolean;
50
+ selectOutsideDays: boolean;
51
+ swipeToPage: boolean;
52
+ captionLayout: CalendarCaptionLayout;
53
+ /** The calendar's own `testID`, which its parts derive theirs from — see `resolvePartTestID`. */
54
+ testID: string | undefined;
55
+ /** The enclosing `Field.Label`'s text, for the grid's accessible name. */
56
+ label: string | null;
57
+ };
58
+
59
+ const CalendarContext = createContext<CalendarContextValue | null>(null);
60
+
61
+ /**
62
+ * Supplies one calendar's state to its own parts.
63
+ *
64
+ * Lives in its own module, importing nothing but React and types, so a part can read it without
65
+ * importing `./calendar` and closing a cycle (AGENTS.md rule 3).
66
+ */
67
+ export function CalendarProvider({
68
+ value,
69
+ children,
70
+ }: {
71
+ value: CalendarContextValue;
72
+ children: ReactNode;
73
+ }): ReactElement {
74
+ return <CalendarContext value={value}>{children}</CalendarContext>;
75
+ }
76
+ CalendarProvider.displayName = "DelacourUI.Calendar.Provider";
77
+
78
+ /** The enclosing calendar's state, or null outside a `<Calendar>` — for another folder's control. */
79
+ export function useCalendarContext(): CalendarContextValue | null {
80
+ return use(CalendarContext);
81
+ }
82
+
83
+ /**
84
+ * Reads the enclosing calendar's state.
85
+ *
86
+ * For a custom day or header that has to match the calendar it sits in. Throws outside one — use
87
+ * {@link useCalendarContext} where the calendar is optional.
88
+ */
89
+ export function useCalendar(): CalendarContextValue {
90
+ const context = useCalendarContext();
91
+ if (!context) {
92
+ throw new Error("useCalendar must be called inside a <Calendar>.");
93
+ }
94
+ return context;
95
+ }
96
+
97
+ /**
98
+ * The enclosing calendar's state, for a compound part that cannot work without one.
99
+ *
100
+ * Internal: deliberately not re-exported from `index.ts`.
101
+ */
102
+ export function useCalendarPart(component: string): CalendarContextValue {
103
+ const context = useCalendarContext();
104
+ if (!context) {
105
+ throw new Error(`${component} must be rendered inside a <Calendar>.`);
106
+ }
107
+ return context;
108
+ }