@delacour/react-native-ui 0.1.0-alpha.4 → 0.2.0-alpha.20261010061731

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 (493) hide show
  1. package/README.md +10 -6
  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 +10 -0
  34. package/src/components/badge/badge-close-button.tsx +2 -2
  35. package/src/components/badge/badge.context.tsx +2 -1
  36. package/src/components/badge/badge.tsx +13 -4
  37. package/src/components/badge/badge.variants.test.ts +26 -0
  38. package/src/components/badge/badge.variants.ts +21 -0
  39. package/src/components/bottom-sheet/AGENTS.md +186 -231
  40. package/src/components/bottom-sheet/bottom-sheet-background.tsx +31 -22
  41. package/src/components/bottom-sheet/bottom-sheet-close.tsx +23 -30
  42. package/src/components/bottom-sheet/bottom-sheet-container.tsx +54 -208
  43. package/src/components/bottom-sheet/bottom-sheet-content.tsx +25 -48
  44. package/src/components/bottom-sheet/bottom-sheet-description.tsx +8 -2
  45. package/src/components/bottom-sheet/bottom-sheet-flat-list.tsx +65 -0
  46. package/src/components/bottom-sheet/bottom-sheet-footer.tsx +53 -122
  47. package/src/components/bottom-sheet/bottom-sheet-handle.tsx +31 -26
  48. package/src/components/bottom-sheet/bottom-sheet-legend-list.tsx +69 -0
  49. package/src/components/bottom-sheet/bottom-sheet-overlay.tsx +25 -147
  50. package/src/components/bottom-sheet/bottom-sheet-provider.tsx +42 -0
  51. package/src/components/bottom-sheet/bottom-sheet-scroll-view.tsx +38 -62
  52. package/src/components/bottom-sheet/bottom-sheet-section-list.tsx +58 -0
  53. package/src/components/bottom-sheet/bottom-sheet-steps.tsx +88 -0
  54. package/src/components/bottom-sheet/bottom-sheet-text-input.tsx +44 -0
  55. package/src/components/bottom-sheet/bottom-sheet-title.tsx +12 -8
  56. package/src/components/bottom-sheet/bottom-sheet-trigger.tsx +19 -37
  57. package/src/components/bottom-sheet/bottom-sheet.tsx +113 -60
  58. package/src/components/bottom-sheet/bottom-sheet.variants.test.ts +47 -87
  59. package/src/components/bottom-sheet/bottom-sheet.variants.ts +50 -126
  60. package/src/components/bottom-sheet/index.ts +39 -17
  61. package/src/components/bottom-sheet/use-bottom-sheet-input.ts +16 -115
  62. package/src/components/button/AGENTS.md +51 -11
  63. package/src/components/button/button-group.tsx +44 -8
  64. package/src/components/button/button.context.tsx +10 -0
  65. package/src/components/button/button.tsx +7 -1
  66. package/src/components/button/button.variants.test.ts +190 -0
  67. package/src/components/button/button.variants.ts +87 -6
  68. package/src/components/button/index.ts +2 -0
  69. package/src/components/calendar/AGENTS.md +142 -0
  70. package/src/components/calendar/calendar-caption.tsx +116 -0
  71. package/src/components/calendar/calendar-day.tsx +103 -0
  72. package/src/components/calendar/calendar-grid.tsx +183 -0
  73. package/src/components/calendar/calendar-header.tsx +34 -0
  74. package/src/components/calendar/calendar-nav.tsx +51 -0
  75. package/src/components/calendar/calendar-picker.tsx +128 -0
  76. package/src/components/calendar/calendar-weekdays.tsx +48 -0
  77. package/src/components/calendar/calendar.context.tsx +108 -0
  78. package/src/components/calendar/calendar.date.test.ts +456 -0
  79. package/src/components/calendar/calendar.date.ts +584 -0
  80. package/src/components/calendar/calendar.tsx +389 -0
  81. package/src/components/calendar/calendar.types.ts +87 -0
  82. package/src/components/calendar/calendar.variants.test.ts +363 -0
  83. package/src/components/calendar/calendar.variants.ts +426 -0
  84. package/src/components/calendar/index.ts +96 -0
  85. package/src/components/card/AGENTS.md +92 -0
  86. package/src/components/card/card-action.tsx +16 -0
  87. package/src/components/card/card-content.tsx +12 -0
  88. package/src/components/card/card-description.tsx +12 -0
  89. package/src/components/card/card-footer.tsx +30 -0
  90. package/src/components/card/card-header.tsx +45 -0
  91. package/src/components/card/card-title.tsx +25 -0
  92. package/src/components/card/card.context.tsx +64 -0
  93. package/src/components/card/card.tsx +100 -0
  94. package/src/components/card/card.types.ts +13 -0
  95. package/src/components/card/card.variants.test.ts +240 -0
  96. package/src/components/card/card.variants.ts +177 -0
  97. package/src/components/card/index.ts +15 -0
  98. package/src/components/chart/AGENTS.md +8 -0
  99. package/src/components/chart/chart.tsx +8 -1
  100. package/src/components/chip/AGENTS.md +113 -0
  101. package/src/components/chip/chip-close-button.tsx +48 -0
  102. package/src/components/chip/chip-end-content.tsx +15 -0
  103. package/src/components/chip/chip-label.tsx +21 -0
  104. package/src/components/chip/chip-start-content.tsx +16 -0
  105. package/src/components/chip/chip.context.tsx +66 -0
  106. package/src/components/chip/chip.tsx +268 -0
  107. package/src/components/chip/chip.types.ts +10 -0
  108. package/src/components/chip/chip.variants.test.ts +335 -0
  109. package/src/components/chip/chip.variants.ts +321 -0
  110. package/src/components/chip/index.ts +24 -0
  111. package/src/components/collapsible/AGENTS.md +97 -0
  112. package/src/components/collapsible/collapsible-content.tsx +97 -0
  113. package/src/components/collapsible/collapsible-description.tsx +17 -0
  114. package/src/components/collapsible/collapsible-indicator.tsx +92 -0
  115. package/src/components/collapsible/collapsible-title.tsx +17 -0
  116. package/src/components/collapsible/collapsible-trigger.tsx +133 -0
  117. package/src/components/collapsible/collapsible.context.tsx +91 -0
  118. package/src/components/collapsible/collapsible.tsx +167 -0
  119. package/src/components/collapsible/collapsible.types.ts +9 -0
  120. package/src/components/collapsible/collapsible.variants.test.ts +338 -0
  121. package/src/components/collapsible/collapsible.variants.ts +228 -0
  122. package/src/components/collapsible/index.ts +32 -0
  123. package/src/components/context-menu/AGENTS.md +103 -0
  124. package/src/components/context-menu/context-menu-content.tsx +64 -0
  125. package/src/components/context-menu/context-menu-item.tsx +22 -0
  126. package/src/components/context-menu/context-menu-preview.tsx +60 -0
  127. package/src/components/context-menu/context-menu-trigger.tsx +160 -0
  128. package/src/components/context-menu/context-menu.context.tsx +91 -0
  129. package/src/components/context-menu/context-menu.tsx +90 -0
  130. package/src/components/context-menu/context-menu.variants.test.ts +266 -0
  131. package/src/components/context-menu/context-menu.variants.ts +147 -0
  132. package/src/components/context-menu/index.ts +18 -0
  133. package/src/components/dialog/AGENTS.md +97 -0
  134. package/src/components/dialog/dialog-body.tsx +21 -0
  135. package/src/components/dialog/dialog-close.tsx +82 -0
  136. package/src/components/dialog/dialog-content.tsx +153 -0
  137. package/src/components/dialog/dialog-description.tsx +23 -0
  138. package/src/components/dialog/dialog-footer.tsx +32 -0
  139. package/src/components/dialog/dialog-header.tsx +20 -0
  140. package/src/components/dialog/dialog-title.tsx +42 -0
  141. package/src/components/dialog/dialog-trigger.tsx +54 -0
  142. package/src/components/dialog/dialog.context.tsx +90 -0
  143. package/src/components/dialog/dialog.tsx +119 -0
  144. package/src/components/dialog/dialog.variants.test.ts +224 -0
  145. package/src/components/dialog/dialog.variants.ts +173 -0
  146. package/src/components/dialog/index.ts +30 -0
  147. package/src/components/drawer/AGENTS.md +123 -0
  148. package/src/components/drawer/drawer-body.tsx +46 -0
  149. package/src/components/drawer/drawer-close.tsx +76 -0
  150. package/src/components/drawer/drawer-content.tsx +186 -0
  151. package/src/components/drawer/drawer-description.tsx +23 -0
  152. package/src/components/drawer/drawer-footer.tsx +21 -0
  153. package/src/components/drawer/drawer-header.tsx +42 -0
  154. package/src/components/drawer/drawer-title.tsx +35 -0
  155. package/src/components/drawer/drawer-trigger.tsx +54 -0
  156. package/src/components/drawer/drawer.context.tsx +114 -0
  157. package/src/components/drawer/drawer.tsx +119 -0
  158. package/src/components/drawer/drawer.variants.test.ts +392 -0
  159. package/src/components/drawer/drawer.variants.ts +244 -0
  160. package/src/components/drawer/index.ts +41 -0
  161. package/src/components/drawer/use-drawer-pan.ts +108 -0
  162. package/src/components/empty-state/AGENTS.md +90 -0
  163. package/src/components/empty-state/empty-state-content.tsx +17 -0
  164. package/src/components/empty-state/empty-state-description.tsx +12 -0
  165. package/src/components/empty-state/empty-state-header.tsx +17 -0
  166. package/src/components/empty-state/empty-state-media.tsx +56 -0
  167. package/src/components/empty-state/empty-state-title.tsx +18 -0
  168. package/src/components/empty-state/empty-state.context.tsx +65 -0
  169. package/src/components/empty-state/empty-state.tsx +84 -0
  170. package/src/components/empty-state/empty-state.types.ts +14 -0
  171. package/src/components/empty-state/empty-state.variants.test.ts +245 -0
  172. package/src/components/empty-state/empty-state.variants.ts +132 -0
  173. package/src/components/empty-state/index.ts +20 -0
  174. package/src/components/fab/AGENTS.md +119 -0
  175. package/src/components/fab/fab-action.tsx +118 -0
  176. package/src/components/fab/fab-group.tsx +214 -0
  177. package/src/components/fab/fab-label.tsx +18 -0
  178. package/src/components/fab/fab.context.tsx +125 -0
  179. package/src/components/fab/fab.tsx +156 -0
  180. package/src/components/fab/fab.types.ts +17 -0
  181. package/src/components/fab/fab.variants.test.ts +274 -0
  182. package/src/components/fab/fab.variants.ts +234 -0
  183. package/src/components/fab/index.ts +37 -0
  184. package/src/components/feedback/AGENTS.md +95 -0
  185. package/src/components/feedback/feedback-action.tsx +16 -0
  186. package/src/components/feedback/feedback-cancel.tsx +34 -0
  187. package/src/components/feedback/feedback-close.tsx +21 -0
  188. package/src/components/feedback/feedback-content.tsx +24 -0
  189. package/src/components/feedback/feedback-field.tsx +70 -0
  190. package/src/components/feedback/feedback-footer.tsx +21 -0
  191. package/src/components/feedback/feedback-panel.tsx +54 -0
  192. package/src/components/feedback/feedback-submit.tsx +74 -0
  193. package/src/components/feedback/feedback-title.tsx +34 -0
  194. package/src/components/feedback/feedback-trigger.tsx +20 -0
  195. package/src/components/feedback/feedback.context.tsx +73 -0
  196. package/src/components/feedback/feedback.tsx +135 -0
  197. package/src/components/feedback/feedback.variants.test.ts +185 -0
  198. package/src/components/feedback/feedback.variants.ts +128 -0
  199. package/src/components/feedback/index.ts +24 -0
  200. package/src/components/field/AGENTS.md +19 -0
  201. package/src/components/field/field-label.tsx +19 -5
  202. package/src/components/field/field.context.tsx +17 -0
  203. package/src/components/field/field.tsx +15 -2
  204. package/src/components/field/field.variants.test.ts +24 -0
  205. package/src/components/field/field.variants.ts +20 -0
  206. package/src/components/field/index.ts +1 -0
  207. package/src/components/input/AGENTS.md +5 -0
  208. package/src/components/input/input.variants.test.ts +32 -0
  209. package/src/components/input/input.variants.ts +13 -1
  210. package/src/components/item/AGENTS.md +92 -0
  211. package/src/components/item/index.ts +25 -0
  212. package/src/components/item/item-actions.tsx +28 -0
  213. package/src/components/item/item-content.tsx +15 -0
  214. package/src/components/item/item-description.tsx +12 -0
  215. package/src/components/item/item-footer.tsx +13 -0
  216. package/src/components/item/item-group.tsx +32 -0
  217. package/src/components/item/item-header.tsx +13 -0
  218. package/src/components/item/item-media.tsx +37 -0
  219. package/src/components/item/item-separator.tsx +13 -0
  220. package/src/components/item/item-title.tsx +17 -0
  221. package/src/components/item/item.context.tsx +66 -0
  222. package/src/components/item/item.tsx +218 -0
  223. package/src/components/item/item.types.ts +12 -0
  224. package/src/components/item/item.variants.test.ts +263 -0
  225. package/src/components/item/item.variants.ts +200 -0
  226. package/src/components/kpi/AGENTS.md +125 -0
  227. package/src/components/kpi/index.ts +45 -0
  228. package/src/components/kpi/kpi-action.tsx +12 -0
  229. package/src/components/kpi/kpi-content.tsx +35 -0
  230. package/src/components/kpi/kpi-footer.tsx +52 -0
  231. package/src/components/kpi/kpi-group.tsx +70 -0
  232. package/src/components/kpi/kpi-header.tsx +25 -0
  233. package/src/components/kpi/kpi-icon.tsx +28 -0
  234. package/src/components/kpi/kpi-sparkline.tsx +145 -0
  235. package/src/components/kpi/kpi-stat.tsx +20 -0
  236. package/src/components/kpi/kpi-title.tsx +19 -0
  237. package/src/components/kpi/kpi-trend.tsx +105 -0
  238. package/src/components/kpi/kpi-value.tsx +32 -0
  239. package/src/components/kpi/kpi.context.tsx +111 -0
  240. package/src/components/kpi/kpi.tsx +154 -0
  241. package/src/components/kpi/kpi.types.ts +9 -0
  242. package/src/components/kpi/kpi.variants.test.ts +428 -0
  243. package/src/components/kpi/kpi.variants.ts +347 -0
  244. package/src/components/label/AGENTS.md +89 -0
  245. package/src/components/label/index.ts +10 -0
  246. package/src/components/label/label.tsx +97 -0
  247. package/src/components/label/label.variants.test.ts +176 -0
  248. package/src/components/label/label.variants.ts +123 -0
  249. package/src/components/menu/AGENTS.md +176 -0
  250. package/src/components/menu/index.ts +44 -0
  251. package/src/components/menu/menu-background.tsx +17 -0
  252. package/src/components/menu/menu-checkbox-item.tsx +57 -0
  253. package/src/components/menu/menu-content.tsx +303 -0
  254. package/src/components/menu/menu-item.tsx +162 -0
  255. package/src/components/menu/menu-label.tsx +14 -0
  256. package/src/components/menu/menu-radio-group.tsx +50 -0
  257. package/src/components/menu/menu-radio-item.tsx +57 -0
  258. package/src/components/menu/menu-separator.tsx +11 -0
  259. package/src/components/menu/menu-sub-content.tsx +72 -0
  260. package/src/components/menu/menu-sub-trigger.tsx +50 -0
  261. package/src/components/menu/menu-sub.tsx +75 -0
  262. package/src/components/menu/menu-trigger.tsx +57 -0
  263. package/src/components/menu/menu.context.tsx +214 -0
  264. package/src/components/menu/menu.tsx +88 -0
  265. package/src/components/menu/menu.types.ts +33 -0
  266. package/src/components/menu/menu.variants.test.ts +400 -0
  267. package/src/components/menu/menu.variants.ts +353 -0
  268. package/src/components/meter/AGENTS.md +135 -0
  269. package/src/components/meter/index.ts +43 -0
  270. package/src/components/meter/meter-fill.tsx +19 -0
  271. package/src/components/meter/meter-header.tsx +17 -0
  272. package/src/components/meter/meter-label.tsx +18 -0
  273. package/src/components/meter/meter-output.tsx +42 -0
  274. package/src/components/meter/meter-segments.tsx +83 -0
  275. package/src/components/meter/meter-track.tsx +16 -0
  276. package/src/components/meter/meter.context.tsx +62 -0
  277. package/src/components/meter/meter.tsx +286 -0
  278. package/src/components/meter/meter.types.ts +30 -0
  279. package/src/components/meter/meter.variants.test.ts +430 -0
  280. package/src/components/meter/meter.variants.ts +347 -0
  281. package/src/components/overlay/AGENTS.md +89 -0
  282. package/src/components/overlay/index.ts +27 -0
  283. package/src/components/overlay/overlay-portal.tsx +84 -0
  284. package/src/components/overlay/overlay-presence.test.ts +46 -0
  285. package/src/components/overlay/overlay-presence.ts +49 -0
  286. package/src/components/overlay/overlay-registry.test.ts +96 -0
  287. package/src/components/overlay/overlay-registry.ts +99 -0
  288. package/src/components/overlay/overlay-scrim.tsx +49 -0
  289. package/src/components/overlay/overlay.context.tsx +48 -0
  290. package/src/components/overlay/overlay.tsx +118 -0
  291. package/src/components/overlay/overlay.variants.test.ts +37 -0
  292. package/src/components/overlay/overlay.variants.ts +33 -0
  293. package/src/components/overlay/use-overlay-back-handler.ts +34 -0
  294. package/src/components/overlay/use-overlay-presence.ts +87 -0
  295. package/src/components/popover/AGENTS.md +138 -0
  296. package/src/components/popover/index.ts +48 -0
  297. package/src/components/popover/popover-anchor.tsx +49 -0
  298. package/src/components/popover/popover-arrow.tsx +89 -0
  299. package/src/components/popover/popover-close.tsx +73 -0
  300. package/src/components/popover/popover-content.tsx +235 -0
  301. package/src/components/popover/popover-description.tsx +18 -0
  302. package/src/components/popover/popover-title.tsx +33 -0
  303. package/src/components/popover/popover-trigger.tsx +56 -0
  304. package/src/components/popover/popover.context.tsx +71 -0
  305. package/src/components/popover/popover.position.test.ts +354 -0
  306. package/src/components/popover/popover.position.ts +286 -0
  307. package/src/components/popover/popover.tsx +123 -0
  308. package/src/components/popover/popover.variants.test.ts +135 -0
  309. package/src/components/popover/popover.variants.ts +85 -0
  310. package/src/components/popover/use-anchor-measure.ts +76 -0
  311. package/src/components/popover/use-anchored-content.ts +219 -0
  312. package/src/components/pressable/AGENTS.md +11 -1
  313. package/src/components/pressable/pressable.tsx +7 -2
  314. package/src/components/pressable/pressable.variants.test.ts +29 -0
  315. package/src/components/pressable/pressable.variants.ts +17 -0
  316. package/src/components/progress/AGENTS.md +120 -0
  317. package/src/components/progress/index.ts +40 -0
  318. package/src/components/progress/progress-fill.tsx +152 -0
  319. package/src/components/progress/progress-header.tsx +19 -0
  320. package/src/components/progress/progress-label.tsx +27 -0
  321. package/src/components/progress/progress-output.tsx +47 -0
  322. package/src/components/progress/progress-track.tsx +38 -0
  323. package/src/components/progress/progress.context.tsx +79 -0
  324. package/src/components/progress/progress.tsx +171 -0
  325. package/src/components/progress/progress.types.ts +23 -0
  326. package/src/components/progress/progress.variants.test.ts +335 -0
  327. package/src/components/progress/progress.variants.ts +284 -0
  328. package/src/components/progress-button/AGENTS.md +116 -0
  329. package/src/components/progress-button/index.ts +36 -0
  330. package/src/components/progress-button/progress-button-done.tsx +33 -0
  331. package/src/components/progress-button/progress-button-label.tsx +27 -0
  332. package/src/components/progress-button/progress-button.context.tsx +93 -0
  333. package/src/components/progress-button/progress-button.tsx +414 -0
  334. package/src/components/progress-button/progress-button.variants.test.ts +314 -0
  335. package/src/components/progress-button/progress-button.variants.ts +236 -0
  336. package/src/components/provider/AGENTS.md +38 -25
  337. package/src/components/provider/provider.tsx +38 -11
  338. package/src/components/radio/AGENTS.md +8 -10
  339. package/src/components/radio/radio.variants.test.ts +5 -6
  340. package/src/components/radio/radio.variants.ts +6 -7
  341. package/src/components/rating/AGENTS.md +143 -0
  342. package/src/components/rating/index.ts +43 -0
  343. package/src/components/rating/rating-output.tsx +39 -0
  344. package/src/components/rating/rating-star.tsx +70 -0
  345. package/src/components/rating/rating-stars.tsx +219 -0
  346. package/src/components/rating/rating.context.tsx +79 -0
  347. package/src/components/rating/rating.tsx +163 -0
  348. package/src/components/rating/rating.variants.test.ts +347 -0
  349. package/src/components/rating/rating.variants.ts +354 -0
  350. package/src/components/selection-mode/AGENTS.md +155 -0
  351. package/src/components/selection-mode/index.ts +43 -0
  352. package/src/components/selection-mode/selection-mode-action.tsx +65 -0
  353. package/src/components/selection-mode/selection-mode-bar.tsx +79 -0
  354. package/src/components/selection-mode/selection-mode-group.tsx +123 -0
  355. package/src/components/selection-mode/selection-mode-header.tsx +85 -0
  356. package/src/components/selection-mode/selection-mode-indicator.tsx +118 -0
  357. package/src/components/selection-mode/selection-mode-item.tsx +182 -0
  358. package/src/components/selection-mode/selection-mode.context.tsx +89 -0
  359. package/src/components/selection-mode/selection-mode.tsx +207 -0
  360. package/src/components/selection-mode/selection-mode.types.ts +36 -0
  361. package/src/components/selection-mode/selection-mode.variants.test.ts +292 -0
  362. package/src/components/selection-mode/selection-mode.variants.ts +329 -0
  363. package/src/components/skeleton/AGENTS.md +120 -0
  364. package/src/components/skeleton/index.ts +15 -0
  365. package/src/components/skeleton/skeleton-clock.ts +48 -0
  366. package/src/components/skeleton/skeleton-group.tsx +64 -0
  367. package/src/components/skeleton/skeleton-shimmer.tsx +80 -0
  368. package/src/components/skeleton/skeleton.context.tsx +45 -0
  369. package/src/components/skeleton/skeleton.tsx +227 -0
  370. package/src/components/skeleton/skeleton.variants.test.ts +245 -0
  371. package/src/components/skeleton/skeleton.variants.ts +222 -0
  372. package/src/components/slide-button/AGENTS.md +122 -0
  373. package/src/components/slide-button/index.ts +43 -0
  374. package/src/components/slide-button/slide-button-label.tsx +70 -0
  375. package/src/components/slide-button/slide-button-thumb.tsx +93 -0
  376. package/src/components/slide-button/slide-button.context.tsx +76 -0
  377. package/src/components/slide-button/slide-button.tsx +457 -0
  378. package/src/components/slide-button/slide-button.variants.test.ts +288 -0
  379. package/src/components/slide-button/slide-button.variants.ts +324 -0
  380. package/src/components/slider/index.ts +2 -0
  381. package/src/components/slider/slider-thumb.tsx +33 -1
  382. package/src/components/slider/slider.variants.test.ts +52 -0
  383. package/src/components/slider/slider.variants.ts +56 -2
  384. package/src/components/stack-card/AGENTS.md +169 -0
  385. package/src/components/stack-card/index.ts +37 -0
  386. package/src/components/stack-card/stack-card-action.tsx +66 -0
  387. package/src/components/stack-card/stack-card-actions.tsx +18 -0
  388. package/src/components/stack-card/stack-card-card.tsx +71 -0
  389. package/src/components/stack-card/stack-card-empty.tsx +30 -0
  390. package/src/components/stack-card/stack-card-slot.tsx +90 -0
  391. package/src/components/stack-card/stack-card-stamp.tsx +76 -0
  392. package/src/components/stack-card/stack-card.context.tsx +117 -0
  393. package/src/components/stack-card/stack-card.tsx +557 -0
  394. package/src/components/stack-card/stack-card.types.ts +26 -0
  395. package/src/components/stack-card/stack-card.variants.test.ts +355 -0
  396. package/src/components/stack-card/stack-card.variants.ts +360 -0
  397. package/src/components/steps/AGENTS.md +153 -0
  398. package/src/components/steps/index.ts +45 -0
  399. package/src/components/steps/steps-connector.tsx +31 -0
  400. package/src/components/steps/steps-description.tsx +26 -0
  401. package/src/components/steps/steps-indicator.tsx +106 -0
  402. package/src/components/steps/steps-item.tsx +225 -0
  403. package/src/components/steps/steps-panel.tsx +41 -0
  404. package/src/components/steps/steps-title.tsx +32 -0
  405. package/src/components/steps/steps-track.tsx +59 -0
  406. package/src/components/steps/steps.context.tsx +123 -0
  407. package/src/components/steps/steps.tsx +166 -0
  408. package/src/components/steps/steps.variants.test.ts +388 -0
  409. package/src/components/steps/steps.variants.ts +468 -0
  410. package/src/components/surface/AGENTS.md +82 -0
  411. package/src/components/surface/index.ts +15 -0
  412. package/src/components/surface/surface.context.tsx +56 -0
  413. package/src/components/surface/surface.tsx +87 -0
  414. package/src/components/surface/surface.variants.test.ts +227 -0
  415. package/src/components/surface/surface.variants.ts +165 -0
  416. package/src/components/swipe/AGENTS.md +140 -0
  417. package/src/components/swipe/index.ts +31 -0
  418. package/src/components/swipe/swipe-action.tsx +99 -0
  419. package/src/components/swipe/swipe-end.tsx +14 -0
  420. package/src/components/swipe/swipe-group.tsx +55 -0
  421. package/src/components/swipe/swipe-panel.tsx +55 -0
  422. package/src/components/swipe/swipe-start.tsx +15 -0
  423. package/src/components/swipe/swipe.context.tsx +135 -0
  424. package/src/components/swipe/swipe.tsx +393 -0
  425. package/src/components/swipe/swipe.types.ts +7 -0
  426. package/src/components/swipe/swipe.variants.test.ts +337 -0
  427. package/src/components/swipe/swipe.variants.ts +290 -0
  428. package/src/components/switch/switch.variants.ts +2 -2
  429. package/src/components/tabs/AGENTS.md +4 -3
  430. package/src/components/tabs/tabs.variants.test.ts +1 -1
  431. package/src/components/tabs/tabs.variants.ts +4 -6
  432. package/src/components/text/AGENTS.md +8 -2
  433. package/src/components/text/text.tsx +7 -0
  434. package/src/components/text/text.variants.test.ts +24 -1
  435. package/src/components/text/text.variants.ts +5 -0
  436. package/src/components/textarea/AGENTS.md +89 -0
  437. package/src/components/textarea/index.ts +16 -0
  438. package/src/components/textarea/textarea.tsx +157 -0
  439. package/src/components/textarea/textarea.variants.test.ts +219 -0
  440. package/src/components/textarea/textarea.variants.ts +171 -0
  441. package/src/components/toast/AGENTS.md +140 -0
  442. package/src/components/toast/index.ts +74 -0
  443. package/src/components/toast/toast-action.tsx +59 -0
  444. package/src/components/toast/toast-api.ts +32 -0
  445. package/src/components/toast/toast-close.tsx +44 -0
  446. package/src/components/toast/toast-content.tsx +11 -0
  447. package/src/components/toast/toast-description.tsx +11 -0
  448. package/src/components/toast/toast-indicator.tsx +34 -0
  449. package/src/components/toast/toast-item.tsx +308 -0
  450. package/src/components/toast/toast-title.tsx +13 -0
  451. package/src/components/toast/toast-viewport.tsx +135 -0
  452. package/src/components/toast/toast.context.tsx +69 -0
  453. package/src/components/toast/toast.store.test.ts +259 -0
  454. package/src/components/toast/toast.store.ts +338 -0
  455. package/src/components/toast/toast.timer.test.ts +64 -0
  456. package/src/components/toast/toast.timer.ts +51 -0
  457. package/src/components/toast/toast.tsx +132 -0
  458. package/src/components/toast/toast.variants.test.ts +393 -0
  459. package/src/components/toast/toast.variants.ts +304 -0
  460. package/src/components/toggle-button/AGENTS.md +106 -0
  461. package/src/components/toggle-button/index.ts +30 -0
  462. package/src/components/toggle-button/toggle-button-group.tsx +156 -0
  463. package/src/components/toggle-button/toggle-button-label.tsx +19 -0
  464. package/src/components/toggle-button/toggle-button.context.tsx +130 -0
  465. package/src/components/toggle-button/toggle-button.tsx +172 -0
  466. package/src/components/toggle-button/toggle-button.variants.test.ts +189 -0
  467. package/src/components/toggle-button/toggle-button.variants.ts +160 -0
  468. package/src/components/tooltip/AGENTS.md +133 -0
  469. package/src/components/tooltip/index.ts +28 -0
  470. package/src/components/tooltip/tooltip-arrow.tsx +40 -0
  471. package/src/components/tooltip/tooltip-content.tsx +179 -0
  472. package/src/components/tooltip/tooltip-description.tsx +19 -0
  473. package/src/components/tooltip/tooltip-text.tsx +21 -0
  474. package/src/components/tooltip/tooltip-title.tsx +20 -0
  475. package/src/components/tooltip/tooltip-trigger.tsx +103 -0
  476. package/src/components/tooltip/tooltip.context.tsx +68 -0
  477. package/src/components/tooltip/tooltip.tsx +213 -0
  478. package/src/components/tooltip/tooltip.variants.test.ts +231 -0
  479. package/src/components/tooltip/tooltip.variants.ts +177 -0
  480. package/src/hooks/use-calm-motion.tsx +31 -0
  481. package/src/lib/calm-motion.test.ts +17 -0
  482. package/src/lib/calm-motion.ts +23 -0
  483. package/src/lib/cn.parity.test.ts +62 -0
  484. package/src/lib/cn.test.ts +14 -0
  485. package/src/lib/cn.ts +11 -6
  486. package/src/no-gorhom.test.ts +54 -0
  487. package/src/styles/geometry.test.ts +1 -0
  488. package/src/styles/theme.css +4 -0
  489. package/src/styles/tokens.css +44 -0
  490. package/src/styles/tokens.test.ts +56 -0
  491. package/src/styles/tokens.ts +29 -1
  492. package/src/components/bottom-sheet/bottom-sheet-portal.tsx +0 -72
  493. package/src/components/bottom-sheet/bottom-sheet.context.tsx +0 -135
@@ -0,0 +1,87 @@
1
+ import { type ReactElement, type ReactNode, useMemo } from "react";
2
+ import { View, type ViewProps } from "react-native";
3
+ import { type SurfaceContextValue, SurfaceProvider, useSurfaceContext } from "./surface.context";
4
+ import {
5
+ resolveSurfacePlane,
6
+ resolveSurfaceVariant,
7
+ type SurfaceMaterial,
8
+ type SurfacePadding,
9
+ type SurfaceVariant,
10
+ surfaceVariants,
11
+ } from "./surface.variants";
12
+
13
+ export type SurfaceProps = ViewProps & {
14
+ /**
15
+ * The fill. Omitted, a top-level surface is the `default` card and a nested
16
+ * one steps to the next fill, so it never vanishes into the surface it sits on.
17
+ */
18
+ variant?: SurfaceVariant;
19
+ /** Inner spacing. `none` also clips, for content bled to the corners. */
20
+ padding?: SurfacePadding;
21
+ /** `flat` by default. `etched` sets a highlight into the edge; `tray` is the muted frame that holds panels. */
22
+ material?: SurfaceMaterial;
23
+ className?: string;
24
+ children?: ReactNode;
25
+ };
26
+
27
+ /**
28
+ * A rounded container on one of the theme's surface fills — the plane a card,
29
+ * an alert or a settings panel is drawn on.
30
+ *
31
+ * The variants are a ladder rather than a palette. `default` is the card,
32
+ * hairlined so it holds its edge on the page; `secondary` and `tertiary` are
33
+ * the fills beneath it; `transparent` keeps the padding and corner and paints
34
+ * nothing. A surface that names no variant reads the one it sits in and steps
35
+ * to the next fill, so nesting builds depth without naming a colour.
36
+ *
37
+ * It publishes no text treatment. `Text`'s presets already carry their own
38
+ * colour, so a class cascade would reach only a bare `Text` — and
39
+ * would mark every `Text` beneath it as nested, which strips `Text.Code` of its
40
+ * chip. A caller that wants the fill's own token reads
41
+ * `SURFACE_FOREGROUND_TOKENS[useSurface().plane]`.
42
+ *
43
+ * @example
44
+ * <Surface>
45
+ * <Text.Label>Account</Text.Label>
46
+ * <Surface className="mt-3">
47
+ * <Text color="muted">Signed in as sam@example.com</Text>
48
+ * </Surface>
49
+ * </Surface>
50
+ *
51
+ * @example
52
+ * <Surface padding="none">
53
+ * <Image className="h-40 w-full" source={cover} />
54
+ * </Surface>
55
+ */
56
+ export function Surface({
57
+ variant,
58
+ padding = "md",
59
+ material = "flat",
60
+ className,
61
+ children,
62
+ ...props
63
+ }: SurfaceProps): ReactElement {
64
+ const parent = useSurfaceContext();
65
+ const parentPlane = parent?.plane ?? null;
66
+
67
+ const resolved = resolveSurfaceVariant({ parentPlane, variant });
68
+ const plane = resolveSurfacePlane({ material, parentPlane, variant: resolved });
69
+ const inTray = parent?.material === "tray";
70
+
71
+ const context = useMemo<SurfaceContextValue>(
72
+ () => ({ material, plane, variant: resolved }),
73
+ [material, plane, resolved]
74
+ );
75
+
76
+ return (
77
+ <SurfaceProvider value={context}>
78
+ <View
79
+ className={surfaceVariants({ inTray, material, padding, variant: resolved }).root({ className })}
80
+ {...props}
81
+ >
82
+ {children}
83
+ </View>
84
+ </SurfaceProvider>
85
+ );
86
+ }
87
+ Surface.displayName = "DelacourUI.Surface";
@@ -0,0 +1,227 @@
1
+ import { describe, expect, test } from "bun:test";
2
+ import { readFileSync } from "node:fs";
3
+ import { join } from "node:path";
4
+ import {
5
+ resolveSurfacePlane,
6
+ resolveSurfaceVariant,
7
+ SURFACE_FILLED_VARIANTS,
8
+ SURFACE_FOREGROUND_TOKENS,
9
+ SURFACE_MATERIALS,
10
+ SURFACE_PADDINGS,
11
+ SURFACE_VARIANTS,
12
+ surfaceVariants,
13
+ } from "./surface.variants";
14
+
15
+ /** Every class string the variant function can produce, one per combination. */
16
+ function everyRoot(): string[] {
17
+ return SURFACE_VARIANTS.flatMap((variant) =>
18
+ SURFACE_PADDINGS.map((padding) => surfaceVariants({ padding, variant }).root())
19
+ );
20
+ }
21
+
22
+ describe("surfaceVariants root slot", () => {
23
+ test("defaults to the card surface with md padding", () => {
24
+ const cls = surfaceVariants().root();
25
+ expect(cls).toContain("bg-card");
26
+ expect(cls).toContain("border-border");
27
+ expect(cls).toContain("p-4");
28
+ });
29
+
30
+ test("maps each variant to its fill", () => {
31
+ expect(surfaceVariants({ variant: "default" }).root()).toContain("bg-card");
32
+ expect(surfaceVariants({ variant: "secondary" }).root()).toContain("bg-secondary");
33
+ expect(surfaceVariants({ variant: "tertiary" }).root()).toContain("bg-tertiary");
34
+ expect(surfaceVariants({ variant: "transparent" }).root()).toContain("bg-transparent");
35
+ });
36
+
37
+ test("gives every variant a distinct surface", () => {
38
+ const seen = new Set(SURFACE_VARIANTS.map((variant) => surfaceVariants({ variant }).root()));
39
+ expect(seen.size).toBe(SURFACE_VARIANTS.length);
40
+ });
41
+
42
+ // A card on a page is white on near-white in light. The hairline is what
43
+ // holds its edge; the filled rungs below it are separated by their fill.
44
+ test("only the default variant draws a visible border", () => {
45
+ expect(surfaceVariants({ variant: "default" }).root()).toContain("border-border");
46
+ for (const variant of SURFACE_VARIANTS.filter((name) => name !== "default")) {
47
+ expect(surfaceVariants({ variant }).root()).not.toContain("border-border");
48
+ }
49
+ });
50
+
51
+ // Every variant carries the same one-point border box, so switching variant
52
+ // never moves the content by a pixel.
53
+ test("every variant reserves the same border width", () => {
54
+ for (const cls of everyRoot()) {
55
+ expect(cls).toMatch(/(^|\s)border(\s|$)/);
56
+ }
57
+ });
58
+
59
+ test("is card-shaped, with a continuous corner, at every variant and padding", () => {
60
+ for (const cls of everyRoot()) {
61
+ expect(cls).toContain("rounded-lg");
62
+ expect(cls).toContain("border-continuous");
63
+ }
64
+ });
65
+
66
+ test("maps each padding to its step", () => {
67
+ expect(surfaceVariants({ padding: "none" }).root()).toContain("p-0");
68
+ expect(surfaceVariants({ padding: "sm" }).root()).toContain("p-3");
69
+ expect(surfaceVariants({ padding: "md" }).root()).toContain("p-4");
70
+ expect(surfaceVariants({ padding: "lg" }).root()).toContain("p-6");
71
+ });
72
+
73
+ // `none` is for content that bleeds to the edge — an image, a chart — and
74
+ // that content has to take the corner with it.
75
+ test("clips only when its content touches the edge", () => {
76
+ expect(surfaceVariants({ padding: "none" }).root()).toContain("overflow-hidden");
77
+ for (const padding of SURFACE_PADDINGS.filter((name) => name !== "none")) {
78
+ expect(surfaceVariants({ padding }).root()).not.toContain("overflow-hidden");
79
+ }
80
+ });
81
+
82
+ // Rule 1: a React Native View does not cascade colour to a Text descendant.
83
+ test("carries no text treatment on the root", () => {
84
+ for (const cls of everyRoot()) {
85
+ expect(cls).not.toMatch(/\btext-/);
86
+ }
87
+ });
88
+
89
+ test("draws no shadow anywhere", () => {
90
+ for (const cls of everyRoot()) {
91
+ expect(cls).not.toMatch(/shadow/);
92
+ }
93
+ });
94
+
95
+ test("merges an incoming className last", () => {
96
+ expect(surfaceVariants().root({ className: "mt-3" })).toContain("mt-3");
97
+ expect(surfaceVariants().root({ className: "bg-popover" })).not.toContain("bg-card");
98
+ expect(surfaceVariants().root({ className: "p-8" })).not.toMatch(/\bp-4\b/);
99
+ });
100
+
101
+ // `border-continuous` is Uniwind's, and a merger that read it as a colour
102
+ // would drop it the moment a caller tinted the border.
103
+ test("keeps its corner curve when a caller recolours the border", () => {
104
+ expect(surfaceVariants().root({ className: "border-destructive" })).toContain("border-continuous");
105
+ });
106
+ });
107
+
108
+ describe("SURFACE_FOREGROUND_TOKENS", () => {
109
+ test("names the X-foreground token for each fill", () => {
110
+ expect(SURFACE_FOREGROUND_TOKENS).toEqual({
111
+ default: "card-foreground",
112
+ secondary: "secondary-foreground",
113
+ tertiary: "tertiary-foreground",
114
+ });
115
+ });
116
+
117
+ // Every token named here has to exist, or the class compiles to nothing and
118
+ // the text keeps whatever colour it inherited.
119
+ test("every token is declared in theme.css", () => {
120
+ const css = readFileSync(join(import.meta.dir, "../../styles/theme.css"), "utf8");
121
+ for (const token of Object.values(SURFACE_FOREGROUND_TOKENS)) {
122
+ expect(css).toContain(`--color-${token}: var(--${token})`);
123
+ }
124
+ });
125
+ });
126
+
127
+ describe("resolveSurfaceVariant", () => {
128
+ test("an explicit variant always wins", () => {
129
+ for (const variant of SURFACE_VARIANTS) {
130
+ expect(resolveSurfaceVariant({ parentPlane: null, variant })).toBe(variant);
131
+ expect(resolveSurfaceVariant({ parentPlane: "secondary", variant })).toBe(variant);
132
+ }
133
+ });
134
+
135
+ test("a top-level surface is the default card", () => {
136
+ expect(resolveSurfaceVariant({ parentPlane: null })).toBe("default");
137
+ });
138
+
139
+ test("a nested surface steps to the next fill", () => {
140
+ expect(resolveSurfaceVariant({ parentPlane: "default" })).toBe("secondary");
141
+ expect(resolveSurfaceVariant({ parentPlane: "secondary" })).toBe("tertiary");
142
+ expect(resolveSurfaceVariant({ parentPlane: "tertiary" })).toBe("secondary");
143
+ });
144
+
145
+ // The point of the ladder: whatever depth it is nested at, a surface never
146
+ // resolves to the fill it is sitting on, or it would vanish into it.
147
+ test("never resolves to the fill of the plane it sits on", () => {
148
+ for (const parentPlane of SURFACE_FILLED_VARIANTS) {
149
+ expect(resolveSurfaceVariant({ parentPlane })).not.toBe(parentPlane);
150
+ }
151
+ });
152
+
153
+ test("never resolves to transparent on its own", () => {
154
+ for (const parentPlane of [null, ...SURFACE_FILLED_VARIANTS]) {
155
+ expect(resolveSurfaceVariant({ parentPlane })).not.toBe("transparent");
156
+ }
157
+ });
158
+ });
159
+
160
+ describe("resolveSurfacePlane", () => {
161
+ test("a filled surface is the plane its children sit on", () => {
162
+ for (const variant of SURFACE_FILLED_VARIANTS) {
163
+ expect(resolveSurfacePlane({ parentPlane: null, variant })).toBe(variant);
164
+ expect(resolveSurfacePlane({ parentPlane: "tertiary", variant })).toBe(variant);
165
+ }
166
+ });
167
+
168
+ // A transparent surface paints nothing, so what its children sit on is
169
+ // still whatever it sits on.
170
+ test("a transparent surface passes its parent's plane through", () => {
171
+ expect(resolveSurfacePlane({ parentPlane: null, variant: "transparent" })).toBeNull();
172
+ for (const parentPlane of SURFACE_FILLED_VARIANTS) {
173
+ expect(resolveSurfacePlane({ parentPlane, variant: "transparent" })).toBe(parentPlane);
174
+ }
175
+ });
176
+
177
+ test("a surface inside a transparent one steps from the plane beneath both", () => {
178
+ const through = resolveSurfacePlane({ parentPlane: "default", variant: "transparent" });
179
+ expect(resolveSurfaceVariant({ parentPlane: through })).toBe("secondary");
180
+ });
181
+ });
182
+
183
+ describe("surface materials", () => {
184
+ test("flat is the default and adds nothing", () => {
185
+ expect(surfaceVariants().root()).toBe(surfaceVariants({ material: "flat" }).root());
186
+ expect(surfaceVariants().root()).not.toMatch(/shadow/);
187
+ });
188
+
189
+ test("etched lifts the edge with the light and dark highlight", () => {
190
+ const cls = surfaceVariants({ material: "etched" }).root();
191
+ expect(cls).toContain("shadow-etched");
192
+ expect(cls).toContain("dark:shadow-etched-dark");
193
+ expect(cls).toContain("bg-card");
194
+ });
195
+
196
+ test("a tray is a muted, squarely padded 2xl frame", () => {
197
+ const cls = surfaceVariants({ material: "tray", padding: "md" }).root();
198
+ expect(cls).toContain("bg-muted/70");
199
+ expect(cls).toContain("rounded-2xl");
200
+ expect(cls).toContain("p-1");
201
+ expect(cls).not.toMatch(/\bp-4\b/);
202
+ expect(cls).not.toContain("bg-card");
203
+ });
204
+
205
+ test("a panel in a tray takes the xl corner", () => {
206
+ expect(surfaceVariants({ inTray: true }).root()).toContain("rounded-xl");
207
+ expect(surfaceVariants({ inTray: true }).root()).not.toContain("rounded-lg");
208
+ });
209
+
210
+ test("lists every material", () => {
211
+ expect(SURFACE_MATERIALS).toEqual(["flat", "etched", "tray"]);
212
+ });
213
+ });
214
+
215
+ describe("tray planes", () => {
216
+ // The tray paints its own muted fill, so what sits in it steps from nothing:
217
+ // a default card panel, not a secondary one on a muted ground.
218
+ test("a tray resets the plane so its panels resolve to the card fill", () => {
219
+ const through = resolveSurfacePlane({ parentPlane: "tertiary", variant: "default", material: "tray" });
220
+ expect(through).toBeNull();
221
+ expect(resolveSurfaceVariant({ parentPlane: through })).toBe("default");
222
+ });
223
+
224
+ test("other materials leave the plane alone", () => {
225
+ expect(resolveSurfacePlane({ parentPlane: null, variant: "secondary", material: "etched" })).toBe("secondary");
226
+ });
227
+ });
@@ -0,0 +1,165 @@
1
+ import type { VariantProps } from "tailwind-variants";
2
+ import { tv } from "../../lib/tv";
3
+
4
+ export const SURFACE_VARIANTS = ["default", "secondary", "tertiary", "transparent"] as const;
5
+
6
+ export const SURFACE_PADDINGS = ["none", "sm", "md", "lg"] as const;
7
+
8
+ /**
9
+ * What the surface is made of, orthogonal to which plane it fills.
10
+ *
11
+ * `flat` is the library default and draws nothing extra. `etched` sets a
12
+ * one-pixel highlight into the edge with a very soft drop — see the
13
+ * `--shadow-etched` tokens. `tray` is the muted frame that holds panels: a
14
+ * 2xl corner, 4pt of padding and a translucent muted fill, so the panels in it
15
+ * read as inset cards rather than as loose boxes on a page.
16
+ */
17
+ export const SURFACE_MATERIALS = ["flat", "etched", "tray"] as const;
18
+
19
+ export type SurfaceMaterial = (typeof SURFACE_MATERIALS)[number];
20
+ export type SurfaceVariant = (typeof SURFACE_VARIANTS)[number];
21
+ export type SurfacePadding = (typeof SURFACE_PADDINGS)[number];
22
+
23
+ /** The variants that paint a fill — every one but `transparent`. */
24
+ export const SURFACE_FILLED_VARIANTS = ["default", "secondary", "tertiary"] as const;
25
+
26
+ /**
27
+ * A fill something can sit on. `transparent` is not one: it paints nothing, so
28
+ * whatever is inside it still sits on the plane beneath.
29
+ */
30
+ export type SurfacePlane = (typeof SURFACE_FILLED_VARIANTS)[number];
31
+
32
+ /**
33
+ * Theme token for content drawn on each fill — `X-foreground` on an `X`
34
+ * surface, per the package's token rule. A class is `text-${token}`; a value,
35
+ * for an icon or a gradient stop, is `useThemeColor(token)`.
36
+ */
37
+ export const SURFACE_FOREGROUND_TOKENS = {
38
+ default: "card-foreground",
39
+ secondary: "secondary-foreground",
40
+ tertiary: "tertiary-foreground",
41
+ } as const satisfies Record<SurfacePlane, string>;
42
+
43
+ /**
44
+ * The fill a nested surface takes when it names none, keyed by the plane it
45
+ * sits on.
46
+ *
47
+ * `tertiary` is quieter than `secondary` — half a step back toward the page —
48
+ * so a third level returns to `secondary` rather than stopping. What matters is
49
+ * that each surface differs from its immediate parent; a strictly ascending
50
+ * ladder would run out of fills at the fourth level and start vanishing into
51
+ * its own parent.
52
+ */
53
+ const NEXT_PLANE = {
54
+ default: "secondary",
55
+ secondary: "tertiary",
56
+ tertiary: "secondary",
57
+ } as const satisfies Record<SurfacePlane, SurfacePlane>;
58
+
59
+ /**
60
+ * Styling for a surface.
61
+ *
62
+ * The root is card-shaped — `rounded-lg`, the corner `--radius` names, see the
63
+ * corner-scale note in the package AGENTS.md — with `border-continuous`, which
64
+ * gives iOS's continuous corner rather than a circular arc. Android ignores it.
65
+ *
66
+ * Every variant reserves the same one-point `border`, transparent unless the
67
+ * variant colours it, so changing variant never moves the content. Only
68
+ * `default` colours it: `--card` sits a percent above `--background` in light,
69
+ * and without the hairline a card on a page is white on near-white. The filled
70
+ * rungs below it are told apart by their fill.
71
+ *
72
+ * `padding: "none"` clips, and nothing else does. It is the one step whose
73
+ * content touches the edge — an image or a chart bled to the corners — and that
74
+ * content has to take the corner with it. Clipping everywhere would crop
75
+ * anything a child draws past its own box.
76
+ *
77
+ * No shadow, on purpose. Nothing in this package casts one — React Native's
78
+ * shadow props disagree between platforms — so depth here is carried by fill.
79
+ *
80
+ * The root carries no `text-*`: a React Native `View` does not cascade colour
81
+ * to a `Text` descendant, so a class here would reach nothing.
82
+ *
83
+ * Free of React Native imports so it stays unit-testable — `bun test` cannot
84
+ * parse React Native's Flow-typed source. See AGENTS.md.
85
+ */
86
+ export const surfaceVariants = tv({
87
+ slots: {
88
+ root: "rounded-lg border border-continuous border-transparent",
89
+ },
90
+ variants: {
91
+ variant: {
92
+ default: { root: "border-border bg-card" },
93
+ secondary: { root: "bg-secondary" },
94
+ tertiary: { root: "bg-tertiary" },
95
+ transparent: { root: "bg-transparent" },
96
+ },
97
+ padding: {
98
+ none: { root: "overflow-hidden p-0" },
99
+ sm: { root: "p-3" },
100
+ md: { root: "p-4" },
101
+ lg: { root: "p-6" },
102
+ },
103
+ material: {
104
+ flat: {},
105
+ etched: { root: "shadow-etched dark:shadow-etched-dark" },
106
+ tray: {},
107
+ },
108
+ // A panel sitting in a tray: one corner step under the tray's own, so
109
+ // the gap between the two curves stays even round the edge.
110
+ inTray: { true: {}, false: {} },
111
+ },
112
+ compoundVariants: [
113
+ // Beats the padding axis, so a tray is `p-1` whatever `padding` says.
114
+ { material: "tray", class: { root: "rounded-2xl border-transparent bg-muted/70 p-1" } },
115
+ { inTray: true, class: { root: "rounded-xl" } },
116
+ ],
117
+ defaultVariants: {
118
+ variant: "default",
119
+ padding: "md",
120
+ material: "flat",
121
+ inTray: false,
122
+ },
123
+ });
124
+
125
+ export type SurfaceVariantProps = VariantProps<typeof surfaceVariants>;
126
+
127
+ /**
128
+ * The fill a surface draws.
129
+ *
130
+ * An explicit `variant` always wins. Without one, a surface at the top of the
131
+ * tree is the `default` card, and a surface nested in another steps to the next
132
+ * fill, so a panel inside a card is told apart from the card with nothing said
133
+ * at the call site — and a `Card` built on this gets the same for free.
134
+ */
135
+ export function resolveSurfaceVariant({
136
+ parentPlane,
137
+ variant,
138
+ }: {
139
+ parentPlane: SurfacePlane | null;
140
+ variant?: SurfaceVariant;
141
+ }): SurfaceVariant {
142
+ if (variant) {
143
+ return variant;
144
+ }
145
+ return parentPlane ? NEXT_PLANE[parentPlane] : "default";
146
+ }
147
+
148
+ /**
149
+ * The plane a surface's children sit on: its own fill, or — for a transparent
150
+ * surface, which paints none — the plane it sits on itself.
151
+ */
152
+ export function resolveSurfacePlane({
153
+ material = "flat",
154
+ parentPlane,
155
+ variant,
156
+ }: {
157
+ material?: SurfaceMaterial;
158
+ parentPlane: SurfacePlane | null;
159
+ variant: SurfaceVariant;
160
+ }): SurfacePlane | null {
161
+ // A tray paints its own muted fill, so what it holds steps from no plane at
162
+ // all and resolves to the card fill rather than the next rung down.
163
+ if (material === "tray") return null;
164
+ return variant === "transparent" ? parentPlane : variant;
165
+ }
@@ -0,0 +1,140 @@
1
+ # Swipe
2
+
3
+ A row that slides aside to reveal actions behind it — delete, archive, snooze, mark done. Compound
4
+ root plus `Swipe.Start`, `Swipe.End`, `Swipe.Action` and `Swipe.Group`, with `useSwipe()` and
5
+ `useSwipeGroup()`. It acts on one row; for many rows at once, a selection mode is the pattern.
6
+
7
+ `import { Swipe } from "@delacour/react-native-ui/swipe";`
8
+
9
+ ## Files
10
+
11
+ | File | What it holds |
12
+ | --- | --- |
13
+ | `index.ts` | → `@delacour/react-native-ui/swipe` |
14
+ | `swipe.tsx` | Root — the partition, the pan, the release, the imperative handle and the accessibility actions |
15
+ | `swipe-start.tsx` | `Swipe.Start`, a marker that renders nothing |
16
+ | `swipe-end.tsx` | `Swipe.End`, a marker that renders nothing |
17
+ | `swipe-action.tsx` | `Swipe.Action`, one tile, positioned by its own animated style |
18
+ | `swipe-panel.tsx` | The layer behind one edge — internal, one per side that has tiles |
19
+ | `swipe-group.tsx` | `Swipe.Group`, the registry that keeps one row open |
20
+ | `swipe.context.tsx` | Row, tile and group contexts, `useSwipe()`, `useSwipeGroup()` |
21
+ | `swipe.types.ts` | `SwipePanelProps`, shared by the two markers |
22
+ | `swipe.variants.ts` | Pure `tv()` slots + the partition, release and tile-layout resolvers, no RN imports |
23
+ | `swipe.variants.test.ts` | |
24
+
25
+ ## Design
26
+
27
+ - **Colours**: `default`, `primary`, `success`, `warning`, `info`, `destructive` — Badge's set. A
28
+ tile is `bg-{color}` with its glyph and label in `{color}-foreground`; `default` is `bg-muted`
29
+ with `text-foreground`, since there is no `muted` surface ink stronger than that.
30
+ - **Tiles are 72pt, all of them.** A panel's width is a count times `SWIPE_TILE_WIDTH`, so nothing
31
+ is measured and the release can be judged on the UI thread the moment the finger lifts. The
32
+ `tileContent` slot spells the same number as `w-18`, and a test pins the pair.
33
+ - **The panel is exactly as wide as the gap the row has left.** Never wider, so it cannot paint
34
+ over a row that has no background of its own; never narrower, so no hole opens. The row slot
35
+ sets no `bg-*` and a test holds it to that — the two never overlap, so the row needs none.
36
+ - **Past the tiles' total the outermost tile grows, and the panel wears its colour.** An overshoot
37
+ then reads as *more of that action* rather than as an empty strip. The outermost is the last
38
+ tile on `end` and the first on `start` — `resolveOutermostIndex`.
39
+ - **Tiles emerge from under the row.** Short of fully open each is overlapped by the one outside
40
+ it, spread in proportion to the gap; they are whole and edge to edge exactly when the gap
41
+ reaches their total. `resolveTileLayout` is the rule, and a test sweeps it to prove the tiles
42
+ always cover the gap and never reach past it once open.
43
+ - **A tile's content box is pinned to its inner edge**, so when the outermost grows its glyph
44
+ stays beside its neighbours rather than drifting to the middle of a widening slab.
45
+
46
+ ## Anatomy
47
+
48
+ - **`Swipe.Start` and `Swipe.End` are markers, lifted out by element type.** Only the root knows
49
+ the gap, so only the root can lay the tiles out; a marker that rendered its own children would
50
+ need the offset passed back down to it anyway. Everything that is not a marker is the row, and
51
+ order does not matter.
52
+ - **The match is on `displayName`, not on function identity.** The package ships raw source, so
53
+ React Compiler or a second module instance can hand the root an element whose `type` is a
54
+ different object standing for the same component — the failure
55
+ [Switch](../switch/AGENTS.md)'s thumb detection records. `partitionSwipeChildren` reads only
56
+ `type.displayName` and `props.children`, which is also what lets the test feed it plain objects.
57
+ - **A tile has to be written directly inside its marker.** `Swipe.Action` reads a tile context the
58
+ root supplies as it lays each tile out, and throws by name if it is rendered anywhere else.
59
+ Wrapping actions in a custom component inside the marker therefore does not work — the wrapper
60
+ would be the tile. This is the trade the lift-out makes.
61
+ - **The icon is a component prop, and that bends the house rule on purpose.** Icons are composed
62
+ everywhere else, but a tile has to size its glyph to `size-icon-md` and tint it to its
63
+ foreground. It takes the same `IconComponent` type `Icon`'s `icon` prop takes and renders
64
+ `<Icon icon={…} />` inside an `IconDefaultsProvider`, so the glyph still flows through the one
65
+ icon path the package has.
66
+
67
+ ## Gesture
68
+
69
+ - **`activeOffsetX([-10, 10])` under `failOffsetY([-8, 8])`**, [Tabs](../tabs/AGENTS.md)' pairing.
70
+ The pan waits for clearly sideways intent and fails the moment the finger commits vertically, so
71
+ a list of rows scrolls normally. This is the opposite of [Slider](../slider/AGENTS.md)'s
72
+ `minDistance(0)`: a slider owns every touch on its track, a row owns only the sideways ones.
73
+ - **A side with no panel rubber-bands at 0.2 and returns.** The row still answers the finger, so
74
+ the touch does not feel dead, but it never promises an action that is not there.
75
+ - **The release is judged on a projection**: `offset + velocity × 0.1` against half the panel. A
76
+ projection that crosses the rest position closes rather than flinging the row open on the other
77
+ side.
78
+ - **A full swipe needs the finger itself past the point.** `|offset| > panel + 0.35 × row`, with
79
+ the projection still beyond it. A flick alone never fires the outermost action, because that
80
+ action is routinely Delete; and pulling back from past the point before letting go cancels it.
81
+ This is the one reading of "or a projected equivalent" that cannot delete by accident. The
82
+ haptic ticks once at exactly that crossing and re-arms if the finger goes back, so the person
83
+ letting go knows what will happen.
84
+ - **The fly-off fires the action when it lands, then brings the row back a beat later.** An action
85
+ that removes the row from state unmounts it during that beat, so it never visibly slides back
86
+ in; one that does not (Done, Archive into the same list) sees its row return closed.
87
+ - **Tapping the open row closes it and swallows the tap.** A transparent shield covers the row
88
+ only while a side is open, behind its own `Gesture.Tap()`, so the row's own `Pressable`s never
89
+ see that tap.
90
+ - **The release runs in `onFinalize`, gated on the pan having started.** `onFinalize` also fires
91
+ for a touch that never activated; without the gate a stray tap would re-judge, and stomp on, an
92
+ imperative `open()` still springing.
93
+ - **Nothing re-renders during a drag.** The offset, the grab, the full-swipe latch and the measured
94
+ width are shared values; React state changes only when a release settles a side.
95
+ - **Every worklet body is self-contained.** `resolveSwipeRelease`, `resolveSwipeDrag` and
96
+ `resolveTileLayout` are restated inside the gesture and the animated styles rather than called,
97
+ and the tests pin the pure versions. A module-scope worklet calling a sibling is the crash
98
+ [Slider](../slider/AGENTS.md) documents.
99
+
100
+ ## Group
101
+
102
+ - **Rows register through context, never by the group walking its children.** A row nested in a
103
+ `ListGroup`, a `.map()` or a custom component still belongs. The registry is a ref, so joining
104
+ or leaving it re-renders nothing.
105
+ - **The group closes only rows that are open.** A close aimed at a shut row would restart its
106
+ spring and cancel a drag already under way on it.
107
+ - **`useSwipeGroup()` outside a group is a no-op, not a throw**, so a screen can call
108
+ `closeAll()` whether or not its rows are grouped.
109
+
110
+ ## Motion
111
+
112
+ - **Under reduced motion every spring becomes a 160ms timing, and the fly-off becomes a fade.**
113
+ The row still follows the finger — direct manipulation is not decorative — and the fade keeps
114
+ the outcome visible rather than cutting.
115
+
116
+ ## RTL
117
+
118
+ - **`I18nManager.isRTL` flips one sign — the row's translation and the pan's reading of it.** The
119
+ offset is held in logical points (positive reveals `start`), and the panels and tiles are pinned
120
+ with Yoga's logical `start` / `end`, which mirror on their own. `end` is always the edge text
121
+ runs toward.
122
+
123
+ ## Accessibility
124
+
125
+ - **Every action is published on the row as an accessibility action**, named and labelled by its
126
+ `label`, and dispatched by name. A swipe is invisible to a screen reader, so without this the
127
+ actions would not exist for one.
128
+ - **The row is a single accessibility element** (`accessible`), the shape a list row with custom
129
+ actions takes; `accessibilityLabel` and `accessibilityHint` passed to `Swipe` land on it. A row
130
+ that needs its own separately focusable controls is not a swipe row.
131
+ - **Tiles are `button`s, hidden while the row is shut** (`accessibilityElementsHidden` and
132
+ `importantForAccessibility="no-hide-descendants"` on each panel), so a screen reader does not
133
+ walk into actions nobody can see.
134
+
135
+ ## Out of scope
136
+
137
+ - A controlled `openSide` prop. The imperative handle and `onOpenChange` cover the cases that came
138
+ up; a controlled pair can follow through `useControllableState` if one appears.
139
+ - Wrapped tiles (see Anatomy), per-tile widths, and vertical swipes.
140
+ - Undo. A demo keeps local state; a toast belongs to the overlays.
@@ -0,0 +1,31 @@
1
+ export { Swipe, type SwipeHandle, type SwipeProps } from "./swipe";
2
+ export {
3
+ type SwipeContextValue,
4
+ type SwipeGroupContextValue,
5
+ useSwipe,
6
+ useSwipeContext,
7
+ useSwipeGroup,
8
+ } from "./swipe.context";
9
+ export type { SwipePanelProps } from "./swipe.types";
10
+ export {
11
+ partitionSwipeChildren,
12
+ resolveOutermostIndex,
13
+ resolveSwipeActionForegroundToken,
14
+ resolveSwipeDrag,
15
+ resolveSwipeRelease,
16
+ resolveTileLayout,
17
+ SWIPE_ACTION_COLORS,
18
+ SWIPE_ACTION_FOREGROUND_TOKEN,
19
+ SWIPE_SIDES,
20
+ SWIPE_TILE_WIDTH,
21
+ type SwipeActionColor,
22
+ type SwipeChildren,
23
+ type SwipeOpenSide,
24
+ type SwipeRelease,
25
+ type SwipeSide,
26
+ type SwipeVariantProps,
27
+ swipeVariants,
28
+ toPhysicalOffset,
29
+ } from "./swipe.variants";
30
+ export type { SwipeActionProps } from "./swipe-action";
31
+ export type { SwipeGroupProps } from "./swipe-group";