@expokit/cli 0.0.0-stage → 0.3.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 (312) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +46 -2
  3. package/bin/expo-kit.js +1266 -0
  4. package/bin/lib.mjs +343 -0
  5. package/bin/mcp.mjs +345 -0
  6. package/package.json +49 -4
  7. package/registry/_docs/accordion.json +115 -0
  8. package/registry/_docs/action-sheet.json +138 -0
  9. package/registry/_docs/alert.json +155 -0
  10. package/registry/_docs/avatar.json +227 -0
  11. package/registry/_docs/badge.json +148 -0
  12. package/registry/_docs/bottom-nav.json +143 -0
  13. package/registry/_docs/bottom-sheet.json +186 -0
  14. package/registry/_docs/bullet.json +27 -0
  15. package/registry/_docs/button-3d.json +138 -0
  16. package/registry/_docs/button.json +131 -0
  17. package/registry/_docs/calendar.json +198 -0
  18. package/registry/_docs/card-stack.json +124 -0
  19. package/registry/_docs/card.json +131 -0
  20. package/registry/_docs/carousel.json +122 -0
  21. package/registry/_docs/chat.json +184 -0
  22. package/registry/_docs/checkbox.json +109 -0
  23. package/registry/_docs/checkout.json +105 -0
  24. package/registry/_docs/chip-group.json +132 -0
  25. package/registry/_docs/color-swatch.json +82 -0
  26. package/registry/_docs/confetti.json +102 -0
  27. package/registry/_docs/coupon.json +192 -0
  28. package/registry/_docs/cube-pager.json +100 -0
  29. package/registry/_docs/dashboard.json +123 -0
  30. package/registry/_docs/date-picker.json +269 -0
  31. package/registry/_docs/dialog.json +186 -0
  32. package/registry/_docs/drawer.json +199 -0
  33. package/registry/_docs/fab.json +144 -0
  34. package/registry/_docs/flip-card.json +127 -0
  35. package/registry/_docs/floating-input.json +211 -0
  36. package/registry/_docs/gift-card.json +124 -0
  37. package/registry/_docs/glass-button.json +142 -0
  38. package/registry/_docs/glass-nav-bar.json +102 -0
  39. package/registry/_docs/glass-search-bar.json +114 -0
  40. package/registry/_docs/glass-segmented.json +94 -0
  41. package/registry/_docs/glass-sheet.json +136 -0
  42. package/registry/_docs/glass-tab-bar.json +100 -0
  43. package/registry/_docs/glass.json +94 -0
  44. package/registry/_docs/inbox.json +75 -0
  45. package/registry/_docs/isometric-bar.json +75 -0
  46. package/registry/_docs/kit.json +14 -0
  47. package/registry/_docs/kpi-card.json +14 -0
  48. package/registry/_docs/like-button.json +92 -0
  49. package/registry/_docs/liquidglass-tab.json +30 -0
  50. package/registry/_docs/list-item.json +216 -0
  51. package/registry/_docs/loyalty-card.json +124 -0
  52. package/registry/_docs/morph-button.json +128 -0
  53. package/registry/_docs/number-ticker.json +112 -0
  54. package/registry/_docs/offers.json +81 -0
  55. package/registry/_docs/onboarding.json +69 -0
  56. package/registry/_docs/otp-input.json +158 -0
  57. package/registry/_docs/pagination-dots.json +108 -0
  58. package/registry/_docs/parallax-layers.json +104 -0
  59. package/registry/_docs/parallax-scroll.json +210 -0
  60. package/registry/_docs/password-input.json +112 -0
  61. package/registry/_docs/payment-card.json +169 -0
  62. package/registry/_docs/pixel-bar.json +69 -0
  63. package/registry/_docs/polar-area.json +81 -0
  64. package/registry/_docs/profile.json +87 -0
  65. package/registry/_docs/progress-steps.json +116 -0
  66. package/registry/_docs/progress.json +152 -0
  67. package/registry/_docs/radio-group.json +112 -0
  68. package/registry/_docs/rating.json +114 -0
  69. package/registry/_docs/ring-carousel.json +122 -0
  70. package/registry/_docs/scratch-card.json +110 -0
  71. package/registry/_docs/search-bar.json +160 -0
  72. package/registry/_docs/search-results.json +123 -0
  73. package/registry/_docs/segmented-control.json +123 -0
  74. package/registry/_docs/select.json +277 -0
  75. package/registry/_docs/settings.json +75 -0
  76. package/registry/_docs/shimmer-button.json +98 -0
  77. package/registry/_docs/sign-in.json +201 -0
  78. package/registry/_docs/skeleton.json +162 -0
  79. package/registry/_docs/slide-to-confirm.json +108 -0
  80. package/registry/_docs/slider.json +177 -0
  81. package/registry/_docs/spin-wheel.json +138 -0
  82. package/registry/_docs/spinner.json +121 -0
  83. package/registry/_docs/stateful-button.json +184 -0
  84. package/registry/_docs/stepper.json +184 -0
  85. package/registry/_docs/swipe-deck.json +118 -0
  86. package/registry/_docs/swipeable-row.json +95 -0
  87. package/registry/_docs/switch.json +124 -0
  88. package/registry/_docs/tabs.json +129 -0
  89. package/registry/_docs/tag-input.json +148 -0
  90. package/registry/_docs/text-animation.json +141 -0
  91. package/registry/_docs/textarea.json +191 -0
  92. package/registry/_docs/ticket-card.json +194 -0
  93. package/registry/_docs/ticket-shape.json +14 -0
  94. package/registry/_docs/tilt-card.json +157 -0
  95. package/registry/_docs/time-picker.json +269 -0
  96. package/registry/_docs/timeline-chart.json +33 -0
  97. package/registry/_docs/timeline.json +172 -0
  98. package/registry/_docs/toast.json +185 -0
  99. package/registry/_docs/treemap.json +33 -0
  100. package/registry/_docs/waterfall.json +39 -0
  101. package/registry/_docs/wheel-list.json +122 -0
  102. package/registry/accordion/files/src/components/ui/accordion.tsx +233 -0
  103. package/registry/accordion/files/src/lib/kit.tsx +219 -0
  104. package/registry/accordion.json +17 -0
  105. package/registry/action-sheet/files/src/components/ui/action-sheet.tsx +580 -0
  106. package/registry/action-sheet/files/src/lib/kit.tsx +219 -0
  107. package/registry/action-sheet.json +18 -0
  108. package/registry/alert/files/src/components/ui/alert.tsx +654 -0
  109. package/registry/alert/files/src/components/ui/button.tsx +274 -0
  110. package/registry/alert/files/src/lib/kit.tsx +219 -0
  111. package/registry/alert.json +20 -0
  112. package/registry/avatar/files/src/components/ui/avatar.tsx +790 -0
  113. package/registry/avatar/files/src/lib/kit.tsx +219 -0
  114. package/registry/avatar.json +18 -0
  115. package/registry/badge/files/src/components/ui/badge.tsx +626 -0
  116. package/registry/badge/files/src/lib/kit.tsx +219 -0
  117. package/registry/badge.json +17 -0
  118. package/registry/bottom-nav/files/src/components/ui/bottom-nav.tsx +1050 -0
  119. package/registry/bottom-nav/files/src/lib/kit.tsx +219 -0
  120. package/registry/bottom-nav.json +18 -0
  121. package/registry/bottom-sheet/files/src/components/ui/bottom-sheet.tsx +585 -0
  122. package/registry/bottom-sheet/files/src/lib/kit.tsx +219 -0
  123. package/registry/bottom-sheet.json +17 -0
  124. package/registry/bullet.json +14 -0
  125. package/registry/button/files/src/components/ui/button.tsx +274 -0
  126. package/registry/button/files/src/lib/kit.tsx +219 -0
  127. package/registry/button-3d/files/src/components/ui/button-3d.tsx +212 -0
  128. package/registry/button-3d/files/src/lib/kit.tsx +219 -0
  129. package/registry/button-3d.json +18 -0
  130. package/registry/button.json +18 -0
  131. package/registry/calendar/files/src/components/ui/calendar.tsx +822 -0
  132. package/registry/calendar/files/src/lib/kit.tsx +219 -0
  133. package/registry/calendar.json +17 -0
  134. package/registry/card/files/src/components/ui/card.tsx +300 -0
  135. package/registry/card/files/src/lib/kit.tsx +219 -0
  136. package/registry/card-stack.json +15 -0
  137. package/registry/card.json +17 -0
  138. package/registry/carousel.json +15 -0
  139. package/registry/chat.json +18 -0
  140. package/registry/checkbox/files/src/components/ui/checkbox.tsx +150 -0
  141. package/registry/checkbox/files/src/lib/kit.tsx +219 -0
  142. package/registry/checkbox.json +17 -0
  143. package/registry/checkout.json +26 -0
  144. package/registry/chip-group/files/src/components/ui/chip-group.tsx +269 -0
  145. package/registry/chip-group/files/src/lib/kit.tsx +219 -0
  146. package/registry/chip-group.json +17 -0
  147. package/registry/color-swatch.json +17 -0
  148. package/registry/confetti/files/src/components/ui/confetti.tsx +222 -0
  149. package/registry/confetti/files/src/lib/kit.tsx +219 -0
  150. package/registry/confetti.json +15 -0
  151. package/registry/coupon/files/src/components/ui/coupon.tsx +698 -0
  152. package/registry/coupon/files/src/components/ui/flip-card.tsx +205 -0
  153. package/registry/coupon/files/src/components/ui/tilt-card.tsx +635 -0
  154. package/registry/coupon/files/src/lib/kit.tsx +219 -0
  155. package/registry/coupon/files/src/lib/ticket-shape.tsx +543 -0
  156. package/registry/coupon.json +23 -0
  157. package/registry/cube-pager.json +15 -0
  158. package/registry/dashboard.json +32 -0
  159. package/registry/date-picker/files/src/components/ui/calendar.tsx +822 -0
  160. package/registry/date-picker/files/src/components/ui/date-picker.tsx +796 -0
  161. package/registry/date-picker/files/src/lib/kit.tsx +219 -0
  162. package/registry/date-picker.json +19 -0
  163. package/registry/dialog/files/src/components/ui/dialog.tsx +759 -0
  164. package/registry/dialog/files/src/lib/kit.tsx +219 -0
  165. package/registry/dialog.json +18 -0
  166. package/registry/drawer/files/src/components/ui/drawer.tsx +1135 -0
  167. package/registry/drawer/files/src/lib/kit.tsx +219 -0
  168. package/registry/drawer.json +18 -0
  169. package/registry/fab.json +18 -0
  170. package/registry/flip-card/files/src/components/ui/flip-card.tsx +205 -0
  171. package/registry/flip-card/files/src/lib/kit.tsx +219 -0
  172. package/registry/flip-card.json +15 -0
  173. package/registry/floating-input/files/src/components/ui/floating-input.tsx +528 -0
  174. package/registry/floating-input/files/src/lib/kit.tsx +219 -0
  175. package/registry/floating-input.json +17 -0
  176. package/registry/gift-card.json +21 -0
  177. package/registry/glass-button.json +22 -0
  178. package/registry/glass-nav-bar.json +21 -0
  179. package/registry/glass-search-bar.json +21 -0
  180. package/registry/glass-segmented.json +22 -0
  181. package/registry/glass-sheet.json +22 -0
  182. package/registry/glass-tab-bar.json +22 -0
  183. package/registry/glass.json +19 -0
  184. package/registry/inbox.json +30 -0
  185. package/registry/isometric-bar.json +14 -0
  186. package/registry/kit/files/src/lib/kit.tsx +219 -0
  187. package/registry/kit.json +11 -0
  188. package/registry/kpi-card/files/src/components/charts/kpi-card.tsx +45 -0
  189. package/registry/kpi-card.json +14 -0
  190. package/registry/like-button.json +18 -0
  191. package/registry/liquidglass-tab/files/src/app/(tabs)/_layout.tsx +97 -0
  192. package/registry/liquidglass-tab/files/src/app/(tabs)/alerts.tsx +5 -0
  193. package/registry/liquidglass-tab/files/src/app/(tabs)/explore.tsx +5 -0
  194. package/registry/liquidglass-tab/files/src/app/(tabs)/index.tsx +5 -0
  195. package/registry/liquidglass-tab/files/src/app/(tabs)/profile.tsx +5 -0
  196. package/registry/liquidglass-tab/files/src/app/_layout.tsx +33 -0
  197. package/registry/liquidglass-tab/files/src/components/GlassTabBar.tsx +259 -0
  198. package/registry/liquidglass-tab/files/src/components/TabScreen.tsx +122 -0
  199. package/registry/liquidglass-tab/files/src/lib/tab-bar.ts +61 -0
  200. package/registry/liquidglass-tab/files/src/lib/tab-icons.ts +130 -0
  201. package/registry/liquidglass-tab/files/src/lib/tabs.ts +108 -0
  202. package/registry/liquidglass-tab.json +23 -0
  203. package/registry/list-item/files/src/components/ui/list-item.tsx +508 -0
  204. package/registry/list-item/files/src/lib/kit.tsx +219 -0
  205. package/registry/list-item.json +17 -0
  206. package/registry/loyalty-card.json +23 -0
  207. package/registry/morph-button.json +18 -0
  208. package/registry/number-ticker.json +15 -0
  209. package/registry/offers.json +24 -0
  210. package/registry/onboarding.json +17 -0
  211. package/registry/otp-input/files/src/components/ui/otp-input.tsx +380 -0
  212. package/registry/otp-input/files/src/lib/kit.tsx +219 -0
  213. package/registry/otp-input.json +17 -0
  214. package/registry/pagination-dots/files/src/components/ui/pagination-dots.tsx +262 -0
  215. package/registry/pagination-dots/files/src/lib/kit.tsx +219 -0
  216. package/registry/pagination-dots.json +15 -0
  217. package/registry/parallax-layers.json +15 -0
  218. package/registry/parallax-scroll.json +17 -0
  219. package/registry/password-input/files/src/components/ui/floating-input.tsx +528 -0
  220. package/registry/password-input/files/src/components/ui/password-input.tsx +271 -0
  221. package/registry/password-input/files/src/lib/kit.tsx +219 -0
  222. package/registry/password-input.json +19 -0
  223. package/registry/payment-card/files/src/components/ui/flip-card.tsx +205 -0
  224. package/registry/payment-card/files/src/components/ui/payment-card.tsx +385 -0
  225. package/registry/payment-card/files/src/components/ui/tilt-card.tsx +635 -0
  226. package/registry/payment-card/files/src/lib/kit.tsx +219 -0
  227. package/registry/payment-card.json +21 -0
  228. package/registry/pixel-bar.json +14 -0
  229. package/registry/polar-area.json +14 -0
  230. package/registry/profile.json +17 -0
  231. package/registry/progress/files/src/components/ui/progress.tsx +883 -0
  232. package/registry/progress/files/src/lib/kit.tsx +219 -0
  233. package/registry/progress-steps/files/src/components/ui/progress-steps.tsx +559 -0
  234. package/registry/progress-steps/files/src/lib/kit.tsx +219 -0
  235. package/registry/progress-steps.json +18 -0
  236. package/registry/progress.json +17 -0
  237. package/registry/radio-group/files/src/components/ui/radio-group.tsx +327 -0
  238. package/registry/radio-group/files/src/lib/kit.tsx +219 -0
  239. package/registry/radio-group.json +18 -0
  240. package/registry/rating.json +18 -0
  241. package/registry/ring-carousel.json +15 -0
  242. package/registry/scratch-card.json +18 -0
  243. package/registry/search-bar/files/src/components/ui/search-bar.tsx +530 -0
  244. package/registry/search-bar/files/src/lib/kit.tsx +219 -0
  245. package/registry/search-bar.json +17 -0
  246. package/registry/search-results.json +30 -0
  247. package/registry/segmented-control/files/src/components/ui/segmented-control.tsx +341 -0
  248. package/registry/segmented-control/files/src/lib/kit.tsx +219 -0
  249. package/registry/segmented-control.json +18 -0
  250. package/registry/select/files/src/components/ui/select.tsx +1398 -0
  251. package/registry/select/files/src/lib/kit.tsx +219 -0
  252. package/registry/select.json +17 -0
  253. package/registry/settings.json +26 -0
  254. package/registry/shimmer-button.json +17 -0
  255. package/registry/sign-in.json +26 -0
  256. package/registry/skeleton/files/src/components/ui/skeleton.tsx +799 -0
  257. package/registry/skeleton/files/src/lib/kit.tsx +219 -0
  258. package/registry/skeleton.json +17 -0
  259. package/registry/slide-to-confirm.json +18 -0
  260. package/registry/slider/files/src/components/ui/slider.tsx +716 -0
  261. package/registry/slider/files/src/lib/kit.tsx +219 -0
  262. package/registry/slider.json +17 -0
  263. package/registry/spin-wheel.json +18 -0
  264. package/registry/spinner/files/src/components/ui/spinner.tsx +748 -0
  265. package/registry/spinner/files/src/lib/kit.tsx +219 -0
  266. package/registry/spinner.json +17 -0
  267. package/registry/stateful-button/files/src/components/ui/stateful-button.tsx +532 -0
  268. package/registry/stateful-button/files/src/lib/kit.tsx +219 -0
  269. package/registry/stateful-button.json +18 -0
  270. package/registry/stepper/files/src/components/ui/stepper.tsx +646 -0
  271. package/registry/stepper/files/src/lib/kit.tsx +219 -0
  272. package/registry/stepper.json +17 -0
  273. package/registry/swipe-deck.json +15 -0
  274. package/registry/swipeable-row.json +15 -0
  275. package/registry/switch/files/src/components/ui/switch.tsx +344 -0
  276. package/registry/switch/files/src/lib/kit.tsx +219 -0
  277. package/registry/switch.json +18 -0
  278. package/registry/tabs/files/src/components/ui/tabs.tsx +539 -0
  279. package/registry/tabs/files/src/lib/kit.tsx +219 -0
  280. package/registry/tabs.json +17 -0
  281. package/registry/tag-input/files/src/components/ui/tag-input.tsx +720 -0
  282. package/registry/tag-input/files/src/lib/kit.tsx +219 -0
  283. package/registry/tag-input.json +17 -0
  284. package/registry/text-animation/files/src/components/ui/text-animation.tsx +650 -0
  285. package/registry/text-animation/files/src/lib/kit.tsx +219 -0
  286. package/registry/text-animation.json +15 -0
  287. package/registry/textarea/files/src/components/ui/textarea.tsx +553 -0
  288. package/registry/textarea/files/src/lib/kit.tsx +219 -0
  289. package/registry/textarea.json +17 -0
  290. package/registry/ticket-card.json +24 -0
  291. package/registry/ticket-shape/files/src/lib/kit.tsx +219 -0
  292. package/registry/ticket-shape/files/src/lib/ticket-shape.tsx +543 -0
  293. package/registry/ticket-shape.json +17 -0
  294. package/registry/tilt-card/files/src/components/ui/tilt-card.tsx +635 -0
  295. package/registry/tilt-card/files/src/lib/kit.tsx +219 -0
  296. package/registry/tilt-card.json +17 -0
  297. package/registry/time-picker/files/src/components/ui/time-picker.tsx +1159 -0
  298. package/registry/time-picker/files/src/lib/kit.tsx +219 -0
  299. package/registry/time-picker.json +17 -0
  300. package/registry/timeline/files/src/components/ui/avatar.tsx +790 -0
  301. package/registry/timeline/files/src/components/ui/timeline.tsx +1059 -0
  302. package/registry/timeline/files/src/lib/kit.tsx +219 -0
  303. package/registry/timeline-chart.json +14 -0
  304. package/registry/timeline.json +20 -0
  305. package/registry/toast/files/src/components/ui/button.tsx +274 -0
  306. package/registry/toast/files/src/components/ui/toast.tsx +1284 -0
  307. package/registry/toast/files/src/lib/kit.tsx +219 -0
  308. package/registry/toast.json +20 -0
  309. package/registry/treemap.json +14 -0
  310. package/registry/waterfall.json +14 -0
  311. package/registry/wheel-list.json +17 -0
  312. package/schema/components.schema.json +13 -0
@@ -0,0 +1,185 @@
1
+ {
2
+ "name": "toast",
3
+ "category": "Feedback",
4
+ "aliases": [],
5
+ "exportName": "ToastProvider",
6
+ "files": [
7
+ "src/components/ui/toast.tsx"
8
+ ],
9
+ "summary": "Sonner-style toasts whose tone icon draws itself while a hairline drains away.",
10
+ "props": [
11
+ [
12
+ "toast(title, options?)",
13
+ "(string, ToastOptions) => id",
14
+ "—",
15
+ "Global API. Also toast.success / error / warning / info / loading / promise / dismiss(id?)."
16
+ ],
17
+ [
18
+ "ToastProvider / useToast()",
19
+ "context",
20
+ "—",
21
+ "Scoped store: wrap UI + <Toaster />, call useToast() instead of the global toast()."
22
+ ],
23
+ [
24
+ "options.description",
25
+ "string",
26
+ "—",
27
+ "Second line."
28
+ ],
29
+ [
30
+ "options.action",
31
+ "{ label, onPress }",
32
+ "—",
33
+ "Inline button (dismisses after press)."
34
+ ],
35
+ [
36
+ "options.icon",
37
+ "ReactNode | false",
38
+ "type icon",
39
+ "Replace the type icon, or false for none."
40
+ ],
41
+ [
42
+ "options.duration",
43
+ "number",
44
+ "Toaster duration",
45
+ "ms before auto-dismiss; Infinity to keep."
46
+ ],
47
+ [
48
+ "options.id",
49
+ "string",
50
+ "auto",
51
+ "Reuse to update a toast in place."
52
+ ],
53
+ [
54
+ "options.toaster",
55
+ "string",
56
+ "—",
57
+ "Target a <Toaster id>."
58
+ ],
59
+ [
60
+ "<Toaster> position",
61
+ "\"top\" | \"bottom\"",
62
+ "\"bottom\"",
63
+ "Edge the stack sits on (island always sits top-centre)."
64
+ ],
65
+ [
66
+ "<Toaster> variant",
67
+ "\"default\" | \"rich\" | \"minimal\" | \"island\"",
68
+ "\"default\"",
69
+ "Cards with a tone glow, cards filled with the solid tone, compact inverted pills, or a black Dynamic Island capsule that springs open, morphs between toasts (tap to expand, swipe up to dismiss)."
70
+ ],
71
+ [
72
+ "<Toaster> duration",
73
+ "number",
74
+ "4000",
75
+ "Default auto-dismiss in ms."
76
+ ],
77
+ [
78
+ "<Toaster> visibleToasts",
79
+ "number",
80
+ "3",
81
+ "Cards visible in the stack."
82
+ ],
83
+ [
84
+ "<Toaster> expand",
85
+ "boolean",
86
+ "false",
87
+ "Always fanned out."
88
+ ],
89
+ [
90
+ "<Toaster> offset / gap",
91
+ "number",
92
+ "16 (island 10) / 10",
93
+ "Edge distance and fan-out spacing. No safe-area dependency: pass insets.top + 8 to clear a notch."
94
+ ],
95
+ [
96
+ "<Toaster> closeButton",
97
+ "boolean",
98
+ "false",
99
+ "✕ on every toast."
100
+ ],
101
+ [
102
+ "<Toaster> id",
103
+ "string",
104
+ "—",
105
+ "Only show toasts sent with { toaster: id }."
106
+ ],
107
+ [
108
+ "<Toaster> haptics",
109
+ "boolean",
110
+ "true",
111
+ "Success / warning / error notification haptic when a toast appears or resolves."
112
+ ],
113
+ [
114
+ "style",
115
+ "StyleProp<ViewStyle>",
116
+ "—",
117
+ "Style for the <Toaster> host that fills the screen and positions the stack."
118
+ ]
119
+ ],
120
+ "behavior": [
121
+ [
122
+ "Touch",
123
+ "Tap the stack to fan it out, swipe a toast sideways or toward the edge to dismiss it; timers and the drain line pause while you touch."
124
+ ],
125
+ [
126
+ "Motion",
127
+ "Tone icons draw their outline inside a halo; toast.promise() closes the spinning arc into the success or error mark. variant=\"island\" morphs like a Dynamic Island."
128
+ ],
129
+ [
130
+ "Reduced motion",
131
+ "Tone icons appear fully drawn without the spin or pop, and the island skips its squash and staggered morph."
132
+ ],
133
+ [
134
+ "Haptics",
135
+ "A success, warning or error notification when a toast of that type appears, landing as the loading circle closes; turn it off with haptics={false}."
136
+ ],
137
+ [
138
+ "Screen readers",
139
+ "Each toast is an alert in a polite live region, labelled by its title and description, with action and Dismiss buttons."
140
+ ]
141
+ ],
142
+ "usage": "import { toast, Toaster } from '@/components/ui/toast';\n\n// once, at the root of your app\n<Toaster />\n\n// anywhere\ntoast.success('Invoice INV-0042 sent', { description: 'Maya will get it by email.' });",
143
+ "examples": [
144
+ {
145
+ "name": "Default",
146
+ "title": "Default",
147
+ "code": "import { StyleSheet, View, type ViewProps } from 'react-native';\nimport { Button } from '@/components/ui/button';\nimport { toast, Toaster, ToastProvider, useToast } from '@/components/ui/toast';\nimport { useKit } from '@/lib/kit';\n\n/** Demo frame that follows the kit theme, so the docs radius / accent picker reshapes it too. */\nfunction Frame({ style, ...rest }: ViewProps) {\n const { colors, radius } = useKit();\n return (\n <View\n style={[\n {\n height: 300,\n overflow: 'hidden',\n padding: 16,\n backgroundColor: colors.card,\n borderColor: colors.border,\n borderWidth: StyleSheet.hairlineWidth,\n borderRadius: radius.xl,\n },\n style,\n ]}\n {...rest}\n />\n );\n}\n\nconst row = { flexDirection: 'row' as const, flexWrap: 'wrap' as const, gap: 8 };\n\nexport default function Default() {\n const to = { toaster: 'demo-default' };\n return (\n <Frame>\n <View style={row}>\n <Button\n size=\"sm\"\n variant=\"accent\"\n onPress={() => toast('Event created', { ...to, description: 'Friday, June 14 at 9:00 AM' })}\n >\n Default\n </Button>\n <Button\n size=\"sm\"\n variant=\"outline\"\n onPress={() => toast.success('Changes saved', { ...to, description: 'Synced to all your devices.' })}\n >\n Success\n </Button>\n <Button size=\"sm\" variant=\"outline\" onPress={() => toast.warning('Storage almost full', { ...to, description: '92% of 15 GB used.' })}>\n Warning\n </Button>\n <Button size=\"sm\" variant=\"outline\" onPress={() => toast.error('Could not reach server', to)}>\n Error\n </Button>\n </View>\n <Toaster id=\"demo-default\" />\n </Frame>\n );\n}\n"
148
+ },
149
+ {
150
+ "name": "RichColors",
151
+ "title": "Rich Colors",
152
+ "code": "import { StyleSheet, View, type ViewProps } from 'react-native';\nimport { Button } from '@/components/ui/button';\nimport { toast, Toaster, ToastProvider, useToast } from '@/components/ui/toast';\nimport { useKit } from '@/lib/kit';\n\n/** Demo frame that follows the kit theme, so the docs radius / accent picker reshapes it too. */\nfunction Frame({ style, ...rest }: ViewProps) {\n const { colors, radius } = useKit();\n return (\n <View\n style={[\n {\n height: 300,\n overflow: 'hidden',\n padding: 16,\n backgroundColor: colors.card,\n borderColor: colors.border,\n borderWidth: StyleSheet.hairlineWidth,\n borderRadius: radius.xl,\n },\n style,\n ]}\n {...rest}\n />\n );\n}\n\nconst row = { flexDirection: 'row' as const, flexWrap: 'wrap' as const, gap: 8 };\n\nexport default function RichColors() {\n const { colors } = useKit();\n const to = { toaster: 'demo-rich' };\n return (\n <Frame>\n <View style={row}>\n <Button\n size=\"sm\"\n variant=\"soft\"\n color={colors.success}\n onPress={() => toast.success('Payment received', { ...to, description: '₹18,500 from Acme Inc.' })}\n >\n Success\n </Button>\n <Button\n size=\"sm\"\n variant=\"soft\"\n color={colors.danger}\n onPress={() => toast.error('Card declined', { ...to, description: 'Try another payment method.' })}\n >\n Error\n </Button>\n <Button size=\"sm\" variant=\"soft\" color={colors.warning} onPress={() => toast.warning('You are offline', { ...to, description: 'Changes will sync when you reconnect.' })}>\n Warning\n </Button>\n <Button size=\"sm\" variant=\"soft\" onPress={() => toast.info('New version available', { ...to, description: 'Restart to update to 4.2.' })}>\n Info\n </Button>\n </View>\n <Toaster id=\"demo-rich\" variant=\"rich\" />\n </Frame>\n );\n}\n"
153
+ },
154
+ {
155
+ "name": "Island",
156
+ "title": "Island",
157
+ "code": "import { StyleSheet, View, type ViewProps } from 'react-native';\nimport { Button } from '@/components/ui/button';\nimport { toast, Toaster, ToastProvider, useToast } from '@/components/ui/toast';\nimport { useKit } from '@/lib/kit';\n\n/** Demo frame that follows the kit theme, so the docs radius / accent picker reshapes it too. */\nfunction Frame({ style, ...rest }: ViewProps) {\n const { colors, radius } = useKit();\n return (\n <View\n style={[\n {\n height: 300,\n overflow: 'hidden',\n padding: 16,\n backgroundColor: colors.card,\n borderColor: colors.border,\n borderWidth: StyleSheet.hairlineWidth,\n borderRadius: radius.xl,\n },\n style,\n ]}\n {...rest}\n />\n );\n}\n\nconst row = { flexDirection: 'row' as const, flexWrap: 'wrap' as const, gap: 8 };\n\nconst wait = (ms: number) => new Promise<void>((resolve) => setTimeout(resolve, ms));\n\nexport default function Island() {\n const to = { toaster: 'demo-island' };\n return (\n <Frame style={{ justifyContent: 'flex-end' }}>\n <View style={row}>\n <Button\n size=\"sm\"\n variant=\"secondary\"\n onPress={() => toast.success('AirPods connected', { ...to, description: 'Battery 84% · Case 100%' })}\n >\n Success\n </Button>\n <Button\n size=\"sm\"\n variant=\"secondary\"\n onPress={() =>\n toast.error('Payment failed', {\n ...to,\n description: 'Your card ending 4242 was declined by the bank. Update your billing details to keep Pro.',\n action: { label: 'Update', onPress: () => toast.info('Opening billing…', to) },\n })\n }\n >\n Error\n </Button>\n <Button\n size=\"sm\"\n variant=\"secondary\"\n onPress={() =>\n toast.promise(wait(2200), { loading: 'Sending to Maya…', success: 'Photo sent', error: 'Could not send' }, to)\n }\n >\n Promise\n </Button>\n <Button\n size=\"sm\"\n variant=\"secondary\"\n onPress={() => toast('Maya Chen', { ...to, description: 'Running 5 min late, grab us a table?' })}\n >\n Message\n </Button>\n </View>\n <Toaster id=\"demo-island\" variant=\"island\" />\n </Frame>\n );\n}\n"
158
+ },
159
+ {
160
+ "name": "PromiseMorph",
161
+ "title": "Promise Morph",
162
+ "code": "import { StyleSheet, View, type ViewProps } from 'react-native';\nimport { Button } from '@/components/ui/button';\nimport { toast, Toaster, ToastProvider, useToast } from '@/components/ui/toast';\nimport { useKit } from '@/lib/kit';\n\n/** Demo frame that follows the kit theme, so the docs radius / accent picker reshapes it too. */\nfunction Frame({ style, ...rest }: ViewProps) {\n const { colors, radius } = useKit();\n return (\n <View\n style={[\n {\n height: 300,\n overflow: 'hidden',\n padding: 16,\n backgroundColor: colors.card,\n borderColor: colors.border,\n borderWidth: StyleSheet.hairlineWidth,\n borderRadius: radius.xl,\n },\n style,\n ]}\n {...rest}\n />\n );\n}\n\nconst row = { flexDirection: 'row' as const, flexWrap: 'wrap' as const, gap: 8 };\n\nconst wait = (ms: number) => new Promise<void>((resolve) => setTimeout(resolve, ms));\n\nconst fail = (ms: number) => new Promise<void>((_, reject) => setTimeout(() => reject(new Error('timeout')), ms));\n\nexport default function PromiseMorph() {\n const to = { toaster: 'demo-promise' };\n return (\n <Frame>\n <View style={row}>\n <Button\n size=\"sm\"\n variant=\"accent\"\n onPress={() =>\n toast.promise(\n wait(1800),\n { loading: 'Uploading report.pdf…', success: 'Report uploaded', error: 'Upload failed' },\n { ...to, description: 'Q3 board pack · 4.2 MB' },\n )\n }\n >\n Upload (succeeds)\n </Button>\n <Button\n size=\"sm\"\n variant=\"outline\"\n onPress={() =>\n toast\n .promise(\n fail(1800),\n { loading: 'Publishing release…', success: 'Release published', error: 'Publish failed: build timed out' },\n to,\n )\n .catch(() => {})\n }\n >\n Publish (fails)\n </Button>\n </View>\n <Toaster id=\"demo-promise\" />\n </Frame>\n );\n}\n"
163
+ },
164
+ {
165
+ "name": "Minimal",
166
+ "title": "Minimal",
167
+ "code": "import { StyleSheet, View, type ViewProps } from 'react-native';\nimport { Button } from '@/components/ui/button';\nimport { toast, Toaster, ToastProvider, useToast } from '@/components/ui/toast';\nimport { useKit } from '@/lib/kit';\n\n/** Demo frame that follows the kit theme, so the docs radius / accent picker reshapes it too. */\nfunction Frame({ style, ...rest }: ViewProps) {\n const { colors, radius } = useKit();\n return (\n <View\n style={[\n {\n height: 300,\n overflow: 'hidden',\n padding: 16,\n backgroundColor: colors.card,\n borderColor: colors.border,\n borderWidth: StyleSheet.hairlineWidth,\n borderRadius: radius.xl,\n },\n style,\n ]}\n {...rest}\n />\n );\n}\n\nconst row = { flexDirection: 'row' as const, flexWrap: 'wrap' as const, gap: 8 };\n\nexport default function Minimal() {\n const to = { toaster: 'demo-minimal' };\n return (\n <Frame style={{ justifyContent: 'flex-end' }}>\n <View style={row}>\n <Button size=\"sm\" variant=\"secondary\" onPress={() => toast('Copied to clipboard', to)}>\n Copy link\n </Button>\n <Button size=\"sm\" variant=\"secondary\" onPress={() => toast.success('Added to favourites', to)}>\n Favourite\n </Button>\n <Button size=\"sm\" variant=\"secondary\" onPress={() => toast.loading('Syncing…', { ...to, id: 'sync', duration: 1500 })}>\n Sync\n </Button>\n </View>\n <Toaster id=\"demo-minimal\" variant=\"minimal\" position=\"top\" />\n </Frame>\n );\n}\n"
168
+ },
169
+ {
170
+ "name": "WithAction",
171
+ "title": "With Action",
172
+ "code": "import { StyleSheet, View, type ViewProps } from 'react-native';\nimport { Button } from '@/components/ui/button';\nimport { toast, Toaster, ToastProvider, useToast } from '@/components/ui/toast';\nimport { useKit } from '@/lib/kit';\n\n/** Demo frame that follows the kit theme, so the docs radius / accent picker reshapes it too. */\nfunction Frame({ style, ...rest }: ViewProps) {\n const { colors, radius } = useKit();\n return (\n <View\n style={[\n {\n height: 300,\n overflow: 'hidden',\n padding: 16,\n backgroundColor: colors.card,\n borderColor: colors.border,\n borderWidth: StyleSheet.hairlineWidth,\n borderRadius: radius.xl,\n },\n style,\n ]}\n {...rest}\n />\n );\n}\n\nexport default function WithAction() {\n const to = { toaster: 'demo-action' };\n return (\n <Frame>\n <Button\n variant=\"destructive\"\n size=\"sm\"\n style={{ alignSelf: 'flex-start' }}\n onPress={() =>\n toast('Conversation archived', {\n ...to,\n description: 'Moved 12 messages to Archive.',\n action: { label: 'Undo', onPress: () => toast.success('Conversation restored', to) },\n })\n }\n >\n Archive chat\n </Button>\n <Toaster id=\"demo-action\" closeButton duration={6000} />\n </Frame>\n );\n}\n"
173
+ },
174
+ {
175
+ "name": "Expanded",
176
+ "title": "Expanded",
177
+ "code": "import React, { useRef } from 'react';\nimport { StyleSheet, View, type ViewProps } from 'react-native';\nimport { Button } from '@/components/ui/button';\nimport { toast, Toaster, ToastProvider, useToast } from '@/components/ui/toast';\nimport { useKit } from '@/lib/kit';\n\n/** Demo frame that follows the kit theme, so the docs radius / accent picker reshapes it too. */\nfunction Frame({ style, ...rest }: ViewProps) {\n const { colors, radius } = useKit();\n return (\n <View\n style={[\n {\n height: 300,\n overflow: 'hidden',\n padding: 16,\n backgroundColor: colors.card,\n borderColor: colors.border,\n borderWidth: StyleSheet.hairlineWidth,\n borderRadius: radius.xl,\n },\n style,\n ]}\n {...rest}\n />\n );\n}\n\nexport default function Expanded() {\n const to = { toaster: 'demo-expanded' };\n const messages = [\n () => toast.info('Maya joined #design', to),\n () => toast.success('Build #1824 passed', { ...to, description: 'main · 3m 12s' }),\n () => toast('Invoice INV-0042 sent', to),\n () => toast.warning('Disk usage at 85%', to),\n ];\n const n = useRef(0);\n return (\n <Frame style={{ justifyContent: 'flex-end' }}>\n <Button size=\"sm\" style={{ alignSelf: 'flex-start' }} onPress={() => messages[n.current++ % messages.length]()}>\n Send notification\n </Button>\n <Toaster id=\"demo-expanded\" position=\"top\" expand visibleToasts={3} gap={8} />\n </Frame>\n );\n}\n"
178
+ },
179
+ {
180
+ "name": "WithProvider",
181
+ "title": "With Provider",
182
+ "code": "import { StyleSheet, View, type ViewProps } from 'react-native';\nimport { Button } from '@/components/ui/button';\nimport { toast, Toaster, ToastProvider, useToast } from '@/components/ui/toast';\nimport { useKit } from '@/lib/kit';\n\n/** Demo frame that follows the kit theme, so the docs radius / accent picker reshapes it too. */\nfunction Frame({ style, ...rest }: ViewProps) {\n const { colors, radius } = useKit();\n return (\n <View\n style={[\n {\n height: 300,\n overflow: 'hidden',\n padding: 16,\n backgroundColor: colors.card,\n borderColor: colors.border,\n borderWidth: StyleSheet.hairlineWidth,\n borderRadius: radius.xl,\n },\n style,\n ]}\n {...rest}\n />\n );\n}\n\nconst row = { flexDirection: 'row' as const, flexWrap: 'wrap' as const, gap: 8 };\n\nfunction ProviderDemo() {\n const toast = useToast();\n return (\n <Frame>\n <View style={row}>\n <Button\n size=\"sm\"\n variant=\"outline\"\n onPress={() => toast.success('Draft saved', { description: 'Only this Toaster sees it.' })}\n >\n Save draft\n </Button>\n <Button size=\"sm\" variant=\"outline\" onPress={() => toast('Reminder set for 6:00 PM')}>\n Remind me\n </Button>\n </View>\n <Toaster />\n </Frame>\n );\n}\n\nexport default function WithProvider() {\n return (\n <ToastProvider>\n <ProviderDemo />\n </ToastProvider>\n );\n}\n"
183
+ }
184
+ ]
185
+ }
@@ -0,0 +1,33 @@
1
+ {
2
+ "name": "treemap",
3
+ "category": "Charts",
4
+ "aliases": [],
5
+ "exportName": "TreemapChart",
6
+ "files": [
7
+ "src/components/charts/treemap.tsx"
8
+ ],
9
+ "summary": null,
10
+ "props": [
11
+ [
12
+ "data",
13
+ "{ label, value, color? }[]",
14
+ "—",
15
+ "Tiles."
16
+ ],
17
+ [
18
+ "gap / radius",
19
+ "number",
20
+ "3 / 8",
21
+ "Tile spacing & corners."
22
+ ],
23
+ [
24
+ "colors",
25
+ "string[]",
26
+ "theme palette",
27
+ "Tile colours."
28
+ ]
29
+ ],
30
+ "behavior": [],
31
+ "usage": null,
32
+ "examples": []
33
+ }
@@ -0,0 +1,39 @@
1
+ {
2
+ "name": "waterfall",
3
+ "category": "Charts",
4
+ "aliases": [],
5
+ "exportName": "WaterfallChart",
6
+ "files": [
7
+ "src/components/charts/waterfall.tsx"
8
+ ],
9
+ "summary": null,
10
+ "props": [
11
+ [
12
+ "data",
13
+ "{ label, value, total? }[]",
14
+ "—",
15
+ "Steps."
16
+ ],
17
+ [
18
+ "increaseColor / decreaseColor / totalColor",
19
+ "string",
20
+ "theme",
21
+ "Colours."
22
+ ],
23
+ [
24
+ "radius",
25
+ "number",
26
+ "4",
27
+ "Corners."
28
+ ],
29
+ [
30
+ "showLabels",
31
+ "boolean",
32
+ "true",
33
+ "Change labels."
34
+ ]
35
+ ],
36
+ "behavior": [],
37
+ "usage": null,
38
+ "examples": []
39
+ }
@@ -0,0 +1,122 @@
1
+ {
2
+ "name": "wheel-list",
3
+ "category": "3D",
4
+ "aliases": [],
5
+ "exportName": "WheelList",
6
+ "files": [
7
+ "src/components/ui/wheel-list.tsx"
8
+ ],
9
+ "summary": "An iOS alarm-style picker whose rows curve away on a 3D cylinder.",
10
+ "props": [
11
+ [
12
+ "data",
13
+ "T[]",
14
+ "—",
15
+ "Rows."
16
+ ],
17
+ [
18
+ "getLabel",
19
+ "(item: T) => string",
20
+ "String(item)",
21
+ "Text for default rows."
22
+ ],
23
+ [
24
+ "renderItem",
25
+ "({ item, index, active }) => ReactNode",
26
+ "—",
27
+ "Custom row; `active` is the row in the band."
28
+ ],
29
+ [
30
+ "value",
31
+ "number",
32
+ "—",
33
+ "Controlled selected index."
34
+ ],
35
+ [
36
+ "defaultValue",
37
+ "number",
38
+ "0",
39
+ "Uncontrolled initial index."
40
+ ],
41
+ [
42
+ "onValueChange",
43
+ "(index: number, item: T) => void",
44
+ "—",
45
+ "Fires as the selection changes."
46
+ ],
47
+ [
48
+ "itemHeight",
49
+ "number",
50
+ "44",
51
+ "Row height."
52
+ ],
53
+ [
54
+ "visible",
55
+ "number",
56
+ "5",
57
+ "Rows visible at once (odd numbers centre the band)."
58
+ ],
59
+ [
60
+ "curve",
61
+ "number",
62
+ "22",
63
+ "Degrees each row curves away from the band."
64
+ ],
65
+ [
66
+ "bandColor",
67
+ "string",
68
+ "faint foreground",
69
+ "Selection band colour."
70
+ ],
71
+ [
72
+ "textColor",
73
+ "string",
74
+ "theme foreground",
75
+ "Text colour of default rows — white on a photo or dark panel."
76
+ ],
77
+ [
78
+ "fadeColor",
79
+ "string",
80
+ "—",
81
+ "Colour of the surface behind the wheel; rows fade into it top and bottom."
82
+ ],
83
+ [
84
+ "align",
85
+ "\"left\" | \"center\" | \"right\"",
86
+ "\"center\"",
87
+ "Text alignment for default rows."
88
+ ],
89
+ [
90
+ "accessibilityLabel",
91
+ "string",
92
+ "—",
93
+ "What the wheel picks, for screen readers."
94
+ ],
95
+ [
96
+ "style",
97
+ "StyleProp<ViewStyle>",
98
+ "—",
99
+ "Style for the root (width)."
100
+ ]
101
+ ],
102
+ "behavior": [
103
+ [
104
+ "Touch",
105
+ "Flick to spin and it snaps to a row; tap a row to scroll it into the band."
106
+ ],
107
+ [
108
+ "Motion",
109
+ "Each row rotates, scales and fades by its distance from the band, interpolated from the scroll position on the native driver."
110
+ ],
111
+ [
112
+ "Screen readers",
113
+ "One adjustable element with increment / decrement actions; its value is the selected label, or \"3 / 12\" for custom rows."
114
+ ],
115
+ [
116
+ "Web",
117
+ "Browsers lack snap momentum, so it settles on the nearest row shortly after scrolling stops."
118
+ ]
119
+ ],
120
+ "usage": null,
121
+ "examples": []
122
+ }
@@ -0,0 +1,233 @@
1
+ import React, { createContext, useCallback, useContext, useEffect, useMemo, useRef, useState } from 'react';
2
+ import {
3
+ Animated,
4
+ Pressable,
5
+ StyleSheet,
6
+ Text,
7
+ View,
8
+ type LayoutChangeEvent,
9
+ type StyleProp,
10
+ type ViewStyle,
11
+ } from 'react-native';
12
+ import Svg, { Path } from 'react-native-svg';
13
+
14
+ import { alpha, isTextNode, motion, useKit } from '@/lib/kit';
15
+
16
+ export type AccordionVariant = 'default' | 'bordered' | 'separated';
17
+
18
+ export interface AccordionProps {
19
+ children: React.ReactNode;
20
+ /** 'single' keeps one item open at a time; 'multiple' lets any number stay open. */
21
+ type?: 'single' | 'multiple';
22
+ /** Open item values (controlled). */
23
+ value?: string[];
24
+ defaultValue?: string[];
25
+ onValueChange?: (value: string[]) => void;
26
+ /** In 'single' mode, allow closing the open item (default true). */
27
+ collapsible?: boolean;
28
+ variant?: AccordionVariant;
29
+ /** Chevron colour of open items (default: theme accent). */
30
+ accent?: string;
31
+ style?: StyleProp<ViewStyle>;
32
+ }
33
+
34
+ interface Ctx {
35
+ open: string[];
36
+ toggle: (v: string) => void;
37
+ variant: AccordionVariant;
38
+ tint: string;
39
+ }
40
+ const AccordionContext = createContext<Ctx | null>(null);
41
+ const PositionContext = createContext({ first: true, last: true });
42
+
43
+ /** Expanding sections with measured-height spring motion and a rotating chevron. */
44
+ export function Accordion({
45
+ children,
46
+ type = 'single',
47
+ value,
48
+ defaultValue = [],
49
+ onValueChange,
50
+ collapsible = true,
51
+ variant = 'default',
52
+ accent,
53
+ style,
54
+ }: AccordionProps) {
55
+ const { colors, radius } = useKit();
56
+ const [inner, setInner] = useState(defaultValue);
57
+ const open = value ?? inner;
58
+ const openRef = useRef(open);
59
+ openRef.current = open;
60
+
61
+ const toggle = useCallback(
62
+ (v: string) => {
63
+ const cur = openRef.current;
64
+ const isOpen = cur.includes(v);
65
+ let next: string[];
66
+ if (type === 'multiple') next = isOpen ? cur.filter((x) => x !== v) : [...cur, v];
67
+ else next = isOpen ? (collapsible ? [] : cur) : [v];
68
+ if (next === cur) return;
69
+ if (value === undefined) setInner(next);
70
+ onValueChange?.(next);
71
+ },
72
+ [type, collapsible, value, onValueChange]
73
+ );
74
+
75
+ const ctx = useMemo(() => ({ open, toggle, variant, tint: accent ?? colors.accent }), [open, toggle, variant, accent, colors.accent]);
76
+ const items = React.Children.toArray(children);
77
+
78
+ return (
79
+ <AccordionContext.Provider value={ctx}>
80
+ <View
81
+ style={[
82
+ variant === 'bordered' && {
83
+ borderWidth: StyleSheet.hairlineWidth * 2,
84
+ borderColor: colors.border,
85
+ borderRadius: radius.lg,
86
+ backgroundColor: colors.card,
87
+ overflow: 'hidden',
88
+ },
89
+ variant === 'separated' && styles.separated,
90
+ style,
91
+ ]}
92
+ >
93
+ {items.map((child, i) => (
94
+ <PositionContext.Provider key={(child as React.ReactElement).key ?? i} value={{ first: i === 0, last: i === items.length - 1 }}>
95
+ {child}
96
+ </PositionContext.Provider>
97
+ ))}
98
+ </View>
99
+ </AccordionContext.Provider>
100
+ );
101
+ }
102
+
103
+ export interface AccordionItemProps {
104
+ /** Unique id used by the Accordion's value. */
105
+ value: string;
106
+ title: React.ReactNode;
107
+ /** Secondary line under the title. */
108
+ subtitle?: string;
109
+ /** Leading icon. */
110
+ icon?: React.ReactNode;
111
+ children?: React.ReactNode;
112
+ disabled?: boolean;
113
+ style?: StyleProp<ViewStyle>;
114
+ }
115
+
116
+ /** One collapsible section. Content is measured, so any height animates smoothly. */
117
+ export function AccordionItem({ value, title, subtitle, icon, children, disabled, style }: AccordionItemProps) {
118
+ const ctx = useContext(AccordionContext);
119
+ if (!ctx) throw new Error('AccordionItem must be used inside <Accordion>');
120
+ const { open: openValues, toggle, variant, tint } = ctx;
121
+ const { last } = useContext(PositionContext);
122
+ const { colors, radius, dark } = useKit();
123
+ const open = openValues.includes(value);
124
+
125
+ const [height, setHeight] = useState(0);
126
+ // `progress` drives height (layout → JS driver); `turn` rotates the chevron on the native driver.
127
+ const progress = useRef(new Animated.Value(open ? 1 : 0)).current;
128
+ const turn = useRef(new Animated.Value(open ? 1 : 0)).current;
129
+
130
+ useEffect(() => {
131
+ Animated.spring(progress, { toValue: open ? 1 : 0, useNativeDriver: false, ...motion.gentle, overshootClamping: true }).start();
132
+ Animated.spring(turn, { toValue: open ? 1 : 0, useNativeDriver: true, ...motion.snappy }).start();
133
+ }, [open, progress, turn]);
134
+
135
+ const onLayout = (e: LayoutChangeEvent) => {
136
+ const h = Math.ceil(e.nativeEvent.layout.height);
137
+ if (h !== height) setHeight(h);
138
+ };
139
+
140
+ const pad = variant === 'default' ? 0 : 16;
141
+ const container: ViewStyle =
142
+ variant === 'separated'
143
+ ? {
144
+ backgroundColor: colors.card,
145
+ borderRadius: radius.lg,
146
+ borderWidth: StyleSheet.hairlineWidth * 2,
147
+ borderColor: open ? alpha(tint, dark ? 0.45 : 0.35) : colors.border,
148
+ overflow: 'hidden',
149
+ }
150
+ : variant === 'default' || !last
151
+ ? { borderBottomWidth: StyleSheet.hairlineWidth * 2, borderBottomColor: colors.border }
152
+ : {};
153
+
154
+ return (
155
+ <View style={[container, style]}>
156
+ <Pressable
157
+ accessibilityRole="button"
158
+ accessibilityState={{ expanded: open, disabled: !!disabled }}
159
+ disabled={disabled}
160
+ onPress={() => toggle(value)}
161
+ style={({ pressed }) => [
162
+ styles.header,
163
+ { paddingHorizontal: pad, opacity: disabled ? 0.45 : 1 },
164
+ pressed && variant !== 'default' && { backgroundColor: colors.muted },
165
+ pressed && variant === 'default' && { opacity: 0.6 },
166
+ ]}
167
+ >
168
+ {icon ? <View style={styles.icon}>{icon}</View> : null}
169
+ <View style={styles.titles}>
170
+ {isTextNode(title) ? (
171
+ <Text style={[styles.title, { color: open && variant === 'default' ? tint : colors.foreground }]}>{title}</Text>
172
+ ) : (
173
+ title
174
+ )}
175
+ {subtitle ? <Text style={[styles.subtitle, { color: colors.mutedForeground }]}>{subtitle}</Text> : null}
176
+ </View>
177
+ <Animated.View
178
+ style={{ transform: [{ rotate: turn.interpolate({ inputRange: [0, 1], outputRange: ['0deg', '180deg'] }) }] }}
179
+ >
180
+ <Svg width={18} height={18} viewBox="0 0 24 24">
181
+ <Path
182
+ d="M6 9l6 6 6-6"
183
+ stroke={open ? tint : colors.mutedForeground}
184
+ strokeWidth={2.2}
185
+ strokeLinecap="round"
186
+ strokeLinejoin="round"
187
+ fill="none"
188
+ />
189
+ </Svg>
190
+ </Animated.View>
191
+ </Pressable>
192
+
193
+ <Animated.View
194
+ style={{ height: progress.interpolate({ inputRange: [0, 1], outputRange: [0, height] }), overflow: 'hidden' }}
195
+ accessibilityElementsHidden={!open}
196
+ importantForAccessibility={open ? 'auto' : 'no-hide-descendants'}
197
+ >
198
+ <Animated.View
199
+ onLayout={onLayout}
200
+ pointerEvents={open ? 'auto' : 'none'}
201
+ style={[
202
+ styles.body,
203
+ {
204
+ paddingHorizontal: pad,
205
+ paddingLeft: pad + (icon ? 36 : 0),
206
+ opacity: progress.interpolate({ inputRange: [0, 0.6, 1], outputRange: [0, 0.4, 1] }),
207
+ transform: [{ translateY: progress.interpolate({ inputRange: [0, 1], outputRange: [-8, 0] }) }],
208
+ },
209
+ ]}
210
+ >
211
+ {isTextNode(children) ? (
212
+ <Text style={[styles.text, { color: colors.mutedForeground }]}>{children}</Text>
213
+ ) : (
214
+ children
215
+ )}
216
+ </Animated.View>
217
+ </Animated.View>
218
+ </View>
219
+ );
220
+ }
221
+
222
+ Accordion.Item = AccordionItem;
223
+
224
+ const styles = StyleSheet.create({
225
+ separated: { gap: 10 },
226
+ header: { flexDirection: 'row', alignItems: 'center', minHeight: 56, paddingVertical: 14, gap: 12 },
227
+ icon: { width: 24, alignItems: 'center' },
228
+ titles: { flex: 1, gap: 2 },
229
+ title: { fontSize: 15.5, fontWeight: '400', letterSpacing: -0.1 },
230
+ subtitle: { fontSize: 13, lineHeight: 18 },
231
+ body: { position: 'absolute', top: 0, left: 0, right: 0, paddingBottom: 16 },
232
+ text: { fontSize: 14.5, lineHeight: 21 },
233
+ });