@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,219 @@
1
+ import React, { createContext, useContext, useMemo } from 'react';
2
+ import { Platform, useColorScheme, type TextStyle } from 'react-native';
3
+
4
+ /**
5
+ * ExpoKit design tokens. Every component reads them through useKit(), so one
6
+ * <KitProvider> (optional) re-skins the whole kit: light / dark, accent colour, radius.
7
+ * Without a provider, components follow the system colour scheme.
8
+ */
9
+
10
+ /**
11
+ * Bumped whenever components start using a new kit API (2: radius.scale / radius.capsule).
12
+ * The CLI won't keep an edited kit.tsx older than the component being added needs.
13
+ */
14
+ export const KIT_VERSION = 2;
15
+
16
+ export type ColorScheme = 'light' | 'dark';
17
+
18
+ export interface Colors {
19
+ background: string;
20
+ foreground: string;
21
+ /** Cards, sheets, inputs. */
22
+ card: string;
23
+ cardForeground: string;
24
+ /** Raised surfaces: popovers, toasts. */
25
+ popover: string;
26
+ muted: string;
27
+ mutedForeground: string;
28
+ border: string;
29
+ input: string;
30
+ primary: string;
31
+ primaryForeground: string;
32
+ secondary: string;
33
+ secondaryForeground: string;
34
+ accent: string;
35
+ accentForeground: string;
36
+ success: string;
37
+ warning: string;
38
+ danger: string;
39
+ dangerForeground: string;
40
+ ring: string;
41
+ overlay: string;
42
+ }
43
+
44
+ export interface Theme {
45
+ dark: boolean;
46
+ colors: Colors;
47
+ /** Corner radius scale. `radius.full` makes pills. */
48
+ radius: {
49
+ sm: number;
50
+ md: number;
51
+ lg: number;
52
+ xl: number;
53
+ full: number;
54
+ /** A corner designed at the default radius (12), scaled to the current one: `radius.scale(16)` is 16 at 12, 0 at 0. */
55
+ scale: (n: number) => number;
56
+ /** Corner for a bar or chip of height `h`: a pill at radius ≥ 12, softer below, square at 0. */
57
+ capsule: (h: number) => number;
58
+ };
59
+ /** 4pt spacing scale: space(4) === 16. */
60
+ space: (n: number) => number;
61
+ font: { family?: string; mono: string };
62
+ text: Record<'xs' | 'sm' | 'md' | 'lg' | 'xl' | 'xxl', TextStyle>;
63
+ }
64
+
65
+ const light: Colors = {
66
+ background: '#ffffff',
67
+ foreground: '#09090b',
68
+ card: '#ffffff',
69
+ cardForeground: '#09090b',
70
+ popover: '#ffffff',
71
+ muted: '#f4f4f5',
72
+ mutedForeground: '#71717a',
73
+ border: '#e4e4e7',
74
+ input: '#d4d4d8',
75
+ primary: '#18181b',
76
+ primaryForeground: '#fafafa',
77
+ secondary: '#f4f4f5',
78
+ secondaryForeground: '#18181b',
79
+ accent: '#18181b',
80
+ accentForeground: '#ffffff',
81
+ success: '#16a34a',
82
+ warning: '#f59e0b',
83
+ danger: '#e5484d',
84
+ dangerForeground: '#ffffff',
85
+ ring: '#18181b',
86
+ overlay: 'rgba(0,0,0,0.4)',
87
+ };
88
+
89
+ const dark: Colors = {
90
+ background: '#09090b',
91
+ foreground: '#fafafa',
92
+ card: '#111113',
93
+ cardForeground: '#fafafa',
94
+ popover: '#18181b',
95
+ muted: '#1c1c20',
96
+ mutedForeground: '#a1a1aa',
97
+ border: '#27272a',
98
+ input: '#34343a',
99
+ primary: '#fafafa',
100
+ primaryForeground: '#18181b',
101
+ secondary: '#1f1f23',
102
+ secondaryForeground: '#fafafa',
103
+ accent: '#fafafa',
104
+ accentForeground: '#09090b',
105
+ success: '#3dd68c',
106
+ warning: '#ffb224',
107
+ danger: '#ff6369',
108
+ dangerForeground: '#ffffff',
109
+ ring: '#d4d4d8',
110
+ overlay: 'rgba(0,0,0,0.6)',
111
+ };
112
+
113
+ export const palettes = { light, dark };
114
+
115
+ function build(scheme: ColorScheme, overrides?: ThemeOverrides): Theme {
116
+ const base = scheme === 'dark' ? dark : light;
117
+ const accent = overrides?.accent;
118
+ const colors: Colors = {
119
+ ...base,
120
+ ...(accent ? { accent, ring: accent, accentForeground: readableOn(accent) } : null),
121
+ ...overrides?.colors?.[scheme],
122
+ };
123
+ const r = overrides?.radius ?? 12;
124
+ return {
125
+ dark: scheme === 'dark',
126
+ colors,
127
+ radius: {
128
+ sm: Math.round(r * 0.5),
129
+ md: r,
130
+ lg: Math.round(r * 1.35),
131
+ xl: Math.round(r * 1.8),
132
+ full: 999,
133
+ scale: (n) => Math.round((n * r) / 12),
134
+ capsule: (h) => Math.round((h / 2) * Math.min(1, r / 12)),
135
+ },
136
+ space: (n) => n * 4,
137
+ font: {
138
+ family: overrides?.fontFamily,
139
+ mono: Platform.select({ ios: 'Menlo', android: 'monospace', default: 'ui-monospace, SFMono-Regular, Menlo, monospace' })!,
140
+ },
141
+ text: {
142
+ xs: { fontSize: 12, lineHeight: 16 },
143
+ sm: { fontSize: 14, lineHeight: 20 },
144
+ md: { fontSize: 16, lineHeight: 22 },
145
+ lg: { fontSize: 18, lineHeight: 24, fontWeight: '400' },
146
+ xl: { fontSize: 22, lineHeight: 28, fontWeight: '400', letterSpacing: -0.3 },
147
+ xxl: { fontSize: 30, lineHeight: 36, fontWeight: '400', letterSpacing: -0.8 },
148
+ },
149
+ };
150
+ }
151
+
152
+ export interface ThemeOverrides {
153
+ /** Brand colour used for focus rings, selected states and accent buttons. */
154
+ accent?: string;
155
+ /** Base corner radius in points (default 12). */
156
+ radius?: number;
157
+ fontFamily?: string;
158
+ /** Override any token per scheme. */
159
+ colors?: Partial<Record<ColorScheme, Partial<Colors>>>;
160
+ }
161
+
162
+ const KitContext = createContext<{ scheme?: ColorScheme; overrides?: ThemeOverrides } | null>(null);
163
+
164
+ export function KitProvider({
165
+ scheme,
166
+ children,
167
+ ...overrides
168
+ }: ThemeOverrides & { scheme?: ColorScheme | 'system'; children: React.ReactNode }) {
169
+ const value = useMemo(
170
+ () => ({ scheme: scheme === 'system' ? undefined : scheme, overrides }),
171
+ // eslint-disable-next-line react-hooks/exhaustive-deps
172
+ [scheme, overrides.accent, overrides.radius, overrides.fontFamily, overrides.colors]
173
+ );
174
+ return <KitContext.Provider value={value}>{children}</KitContext.Provider>;
175
+ }
176
+
177
+ export function useKit(): Theme {
178
+ const ctx = useContext(KitContext);
179
+ const system = useColorScheme();
180
+ const scheme: ColorScheme = ctx?.scheme ?? (system === 'dark' ? 'dark' : 'light');
181
+ return useMemo(() => build(scheme, ctx?.overrides), [scheme, ctx?.overrides]);
182
+ }
183
+
184
+ /** Shared motion presets so the whole kit moves the same way. */
185
+ export const motion = {
186
+ /** Toggles, selection indicators. */
187
+ snappy: { damping: 18, stiffness: 260, mass: 0.8 },
188
+ /** Press feedback, pops. */
189
+ bouncy: { damping: 11, stiffness: 220, mass: 0.7 },
190
+ /** Sheets, layout changes. */
191
+ gentle: { damping: 22, stiffness: 160, mass: 1 },
192
+ fast: 160,
193
+ normal: 240,
194
+ };
195
+
196
+ /** Adds alpha to a #rrggbb colour: alpha('#6366f1', 0.2). */
197
+ export function alpha(hex: string, a: number) {
198
+ if (!hex.startsWith('#') || (hex.length !== 7 && hex.length !== 4)) return hex;
199
+ const full = hex.length === 4 ? '#' + [...hex.slice(1)].map((c) => c + c).join('') : hex;
200
+ return full + Math.round(Math.max(0, Math.min(1, a)) * 255).toString(16).padStart(2, '0');
201
+ }
202
+
203
+ /** Black or white, whichever reads better on `hex`. */
204
+ export function readableOn(hex: string) {
205
+ const full = hex.length === 4 ? '#' + [...hex.slice(1)].map((c) => c + c).join('') : hex;
206
+ const n = parseInt(full.slice(1, 7), 16);
207
+ if (Number.isNaN(n)) return '#ffffff';
208
+ const [r, g, b] = [(n >> 16) & 255, (n >> 8) & 255, n & 255];
209
+ return (r * 299 + g * 587 + b * 114) / 1000 > 150 ? '#09090b' : '#ffffff';
210
+ }
211
+
212
+ /**
213
+ * True for children that must be wrapped in <Text>: strings, numbers and arrays of them —
214
+ * `{count} new` is the array [3, ' new'], and raw text outside <Text> crashes on native.
215
+ */
216
+ export function isTextNode(node: React.ReactNode): node is string | number | (string | number)[] {
217
+ if (typeof node === 'string' || typeof node === 'number') return true;
218
+ return Array.isArray(node) && node.length > 0 && node.every((n) => typeof n === 'string' || typeof n === 'number');
219
+ }
@@ -0,0 +1,18 @@
1
+ {
2
+ "name": "progress-steps",
3
+ "type": "component",
4
+ "title": "Progress Steps",
5
+ "description": "Multi-step progress for checkout and onboarding. Completed steps pop and draw their check, the current step glows with a breathing ring, upcoming steps stay hollow; advance and the connector pours the accent into the next step with a selection tick. Horizontal with labels, vertical with descriptions, or a compact \"Step 2 of 4\" segmented bar.",
6
+ "tier": "free",
7
+ "dependencies": [
8
+ "react-native-svg",
9
+ "expo-haptics"
10
+ ],
11
+ "registryDependencies": [
12
+ "kit"
13
+ ],
14
+ "sharedFiles": [
15
+ "lib/kit.tsx"
16
+ ],
17
+ "filesDir": "progress-steps/files"
18
+ }
@@ -0,0 +1,17 @@
1
+ {
2
+ "name": "progress",
3
+ "type": "component",
4
+ "title": "Progress",
5
+ "description": "Solid bar in the accent colour with a soft glowing head that rides the fill (or sweeps when indeterminate, or carries animated stripes), an SVG ring with a glowing arc tip whose percentage counts up and morphs into a drawn check, and segments that pop as they fill while the current step pulses. Reaching max plays a short success flash.",
6
+ "tier": "free",
7
+ "dependencies": [
8
+ "react-native-svg"
9
+ ],
10
+ "registryDependencies": [
11
+ "kit"
12
+ ],
13
+ "sharedFiles": [
14
+ "lib/kit.tsx"
15
+ ],
16
+ "filesDir": "progress/files"
17
+ }
@@ -0,0 +1,327 @@
1
+ import * as Haptics from 'expo-haptics';
2
+ import React, { useEffect, useRef, useState } from 'react';
3
+ import { Animated, Pressable, StyleSheet, Text, View, type StyleProp, type ViewStyle } from 'react-native';
4
+ import Svg, { Path } from 'react-native-svg';
5
+
6
+ import { alpha, motion, readableOn, useKit } from '@/lib/kit';
7
+
8
+ const AnimatedPath = Animated.createAnimatedComponent(Path);
9
+ const CHECK = 'M5 12.5l4.5 4.5L19 7.5';
10
+ const CHECK_LEN = 24;
11
+
12
+ export type RadioGroupVariant = 'default' | 'card' | 'segmented-list';
13
+
14
+ export interface RadioOption {
15
+ value: string;
16
+ label: string;
17
+ description?: string;
18
+ /** Leading icon or emoji (card and segmented-list variants). */
19
+ icon?: React.ReactNode;
20
+ /** Trailing text, e.g. a price ("₹499/mo"). */
21
+ hint?: string;
22
+ disabled?: boolean;
23
+ }
24
+
25
+ export interface RadioGroupProps {
26
+ options: RadioOption[];
27
+ /** Controlled selection. */
28
+ value?: string;
29
+ /** Initial selection when uncontrolled. */
30
+ defaultValue?: string;
31
+ onValueChange?: (value: string) => void;
32
+ variant?: RadioGroupVariant;
33
+ direction?: 'vertical' | 'horizontal';
34
+ /** Selected colour (default: theme accent). */
35
+ accent?: string;
36
+ disabled?: boolean;
37
+ /** Selection tick on change (default true). */
38
+ haptics?: boolean;
39
+ accessibilityLabel?: string;
40
+ style?: StyleProp<ViewStyle>;
41
+ }
42
+
43
+ /**
44
+ * Single-choice list. The dot springs in with a ripple, cards light up their border and pop a
45
+ * check badge that draws itself, and the grouped list draws a tick next to the chosen row.
46
+ */
47
+ export function RadioGroup({
48
+ options,
49
+ value,
50
+ defaultValue,
51
+ onValueChange,
52
+ variant = 'default',
53
+ direction = 'vertical',
54
+ accent,
55
+ disabled,
56
+ haptics = true,
57
+ accessibilityLabel,
58
+ style,
59
+ }: RadioGroupProps) {
60
+ const { colors, radius } = useKit();
61
+ const tint = accent ?? colors.accent;
62
+ const [inner, setInner] = useState(defaultValue);
63
+ const selected = value ?? inner;
64
+ const horizontal = direction === 'horizontal';
65
+
66
+ const select = (next: string) => {
67
+ if (next === selected) return;
68
+ if (haptics) Haptics.selectionAsync().catch(() => {});
69
+ if (value === undefined) setInner(next);
70
+ onValueChange?.(next);
71
+ };
72
+
73
+ const list = variant === 'segmented-list';
74
+
75
+ return (
76
+ <View
77
+ accessibilityRole="radiogroup"
78
+ accessibilityLabel={accessibilityLabel}
79
+ style={[
80
+ horizontal ? styles.horizontal : styles.vertical,
81
+ variant === 'default' && { gap: horizontal ? 20 : 14 },
82
+ variant === 'card' && { gap: 10 },
83
+ list && [styles.list, { borderRadius: radius.lg, borderColor: colors.border, backgroundColor: colors.card }],
84
+ style,
85
+ ]}
86
+ >
87
+ {options.map((o, i) => (
88
+ <RadioItem
89
+ key={o.value}
90
+ option={o}
91
+ active={o.value === selected}
92
+ onPress={() => select(o.value)}
93
+ variant={variant}
94
+ horizontal={horizontal}
95
+ first={i === 0}
96
+ tint={tint}
97
+ disabled={disabled || o.disabled}
98
+ />
99
+ ))}
100
+ </View>
101
+ );
102
+ }
103
+
104
+ interface RadioItemProps {
105
+ option: RadioOption;
106
+ active: boolean;
107
+ onPress: () => void;
108
+ variant: RadioGroupVariant;
109
+ horizontal: boolean;
110
+ first: boolean;
111
+ tint: string;
112
+ disabled?: boolean;
113
+ }
114
+
115
+ function RadioItem({ option, active, onPress, variant, horizontal, first, tint, disabled }: RadioItemProps) {
116
+ const { colors, radius } = useKit();
117
+ // p: colours + svg (JS driver). dot: scale/opacity pops (native). press: press feedback (native).
118
+ // ripple: a ring that expands and fades when the item gets selected, 0 → 1 (native, rests at 1).
119
+ const p = useRef(new Animated.Value(active ? 1 : 0)).current;
120
+ const dot = useRef(new Animated.Value(active ? 1 : 0)).current;
121
+ const press = useRef(new Animated.Value(1)).current;
122
+ const ripple = useRef(new Animated.Value(1)).current;
123
+ const mounted = useRef(false);
124
+
125
+ useEffect(() => {
126
+ Animated.spring(p, { toValue: active ? 1 : 0, useNativeDriver: false, ...motion.snappy }).start();
127
+ Animated.spring(dot, { toValue: active ? 1 : 0, useNativeDriver: true, ...motion.bouncy }).start();
128
+ if (active && mounted.current) {
129
+ ripple.setValue(0);
130
+ Animated.timing(ripple, { toValue: 1, duration: 560, useNativeDriver: true }).start();
131
+ }
132
+ mounted.current = true;
133
+ }, [active, p, dot, ripple]);
134
+
135
+ const rippleStyle = {
136
+ backgroundColor: alpha(tint, 0.35),
137
+ opacity: ripple.interpolate({ inputRange: [0, 1], outputRange: [1, 0] }),
138
+ transform: [{ scale: ripple.interpolate({ inputRange: [0, 1], outputRange: [1, 2.3] }) }],
139
+ };
140
+
141
+ const pressTo = (to: number) =>
142
+ Animated.spring(press, { toValue: to, useNativeDriver: true, ...(to < 1 ? motion.snappy : motion.bouncy) }).start();
143
+
144
+ const fg = colors.foreground;
145
+ const muted = colors.mutedForeground;
146
+ const a11y = {
147
+ accessibilityRole: 'radio' as const,
148
+ accessibilityState: { checked: active, disabled: !!disabled },
149
+ accessibilityLabel: option.label,
150
+ accessibilityHint: option.description,
151
+ disabled,
152
+ onPress,
153
+ };
154
+
155
+ const text = (
156
+ <View style={styles.text}>
157
+ <Text numberOfLines={2} style={[styles.label, { color: fg }]}>
158
+ {option.label}
159
+ </Text>
160
+ {option.description ? <Text style={[styles.description, { color: muted }]}>{option.description}</Text> : null}
161
+ </View>
162
+ );
163
+
164
+ if (variant === 'card') {
165
+ const lit = (from: string, to: string) => p.interpolate({ inputRange: [0, 1], outputRange: [from, to], extrapolate: 'clamp' });
166
+ const hint = option.hint ? (
167
+ <Animated.Text style={[styles.hint, horizontal && styles.hintColumn, { color: lit(fg, tint) }]}>{option.hint}</Animated.Text>
168
+ ) : null;
169
+ return (
170
+ <Animated.View style={[horizontal && styles.flex, { opacity: disabled ? 0.5 : 1, transform: [{ scale: press }] }]}>
171
+ <Pressable {...a11y} onPressIn={() => pressTo(0.97)} onPressOut={() => pressTo(1)} style={styles.flexGrow}>
172
+ {/* Focus ring: a soft accent glow just outside the selected card. */}
173
+ <Animated.View
174
+ pointerEvents="none"
175
+ style={[styles.glow, { borderRadius: radius.lg + 4, borderColor: alpha(tint, 0.2), opacity: p }]}
176
+ />
177
+ <Animated.View
178
+ style={[
179
+ styles.card,
180
+ horizontal && styles.cardColumn,
181
+ {
182
+ borderRadius: radius.lg,
183
+ borderColor: lit(colors.border, tint),
184
+ backgroundColor: lit(colors.card, alpha(tint, 0.08)),
185
+ },
186
+ ]}
187
+ >
188
+ {option.icon ? (
189
+ <Animated.View style={[styles.cardIcon, { borderRadius: radius.md, backgroundColor: lit(colors.muted, alpha(tint, 0.16)) }]}>
190
+ {option.icon}
191
+ </Animated.View>
192
+ ) : null}
193
+ <View style={[styles.text, horizontal && styles.cardTextColumn]}>
194
+ <Text numberOfLines={2} style={[styles.label, { color: fg }]}>
195
+ {option.label}
196
+ </Text>
197
+ {option.description ? <Text style={[styles.description, { color: muted }]}>{option.description}</Text> : null}
198
+ {horizontal ? hint : null}
199
+ </View>
200
+ {horizontal ? null : hint}
201
+ <View style={[styles.badgeSlot, horizontal && styles.badgeCorner]}>
202
+ <View style={[styles.badgeRing, { borderColor: colors.input }]} />
203
+ <Animated.View pointerEvents="none" style={[styles.ripple, rippleStyle]} />
204
+ <Animated.View
205
+ style={[styles.badge, { backgroundColor: tint, opacity: dot, transform: [{ scale: dot }] }]}
206
+ >
207
+ <Svg width={14} height={14} viewBox="0 0 24 24">
208
+ <AnimatedPath
209
+ d={CHECK}
210
+ stroke={readableOn(tint)}
211
+ strokeWidth={3.2}
212
+ strokeLinecap="round"
213
+ strokeLinejoin="round"
214
+ fill="none"
215
+ strokeDasharray={CHECK_LEN}
216
+ strokeDashoffset={p.interpolate({ inputRange: [0.3, 1], outputRange: [CHECK_LEN, 0], extrapolate: 'clamp' }) as unknown as number}
217
+ />
218
+ </Svg>
219
+ </Animated.View>
220
+ </View>
221
+ </Animated.View>
222
+ </Pressable>
223
+ </Animated.View>
224
+ );
225
+ }
226
+
227
+ if (variant === 'segmented-list') {
228
+ return (
229
+ <Pressable
230
+ {...a11y}
231
+ style={({ pressed }) => [
232
+ styles.listRow,
233
+ horizontal ? styles.flex : null,
234
+ !first && (horizontal ? styles.dividerLeft : styles.dividerTop),
235
+ !first && { borderColor: colors.border },
236
+ { backgroundColor: pressed ? colors.muted : 'transparent', opacity: disabled ? 0.5 : 1 },
237
+ ]}
238
+ >
239
+ <Animated.View pointerEvents="none" style={[StyleSheet.absoluteFill, { backgroundColor: alpha(tint, 0.08), opacity: p }]} />
240
+ {option.icon ? <View style={styles.listIcon}>{option.icon}</View> : null}
241
+ <View style={styles.text}>
242
+ <Animated.Text
243
+ numberOfLines={1}
244
+ style={[styles.label, { color: p.interpolate({ inputRange: [0, 1], outputRange: [fg, tint], extrapolate: 'clamp' }) }]}
245
+ >
246
+ {option.label}
247
+ </Animated.Text>
248
+ {option.description ? <Text style={[styles.description, { color: muted }]}>{option.description}</Text> : null}
249
+ </View>
250
+ <Svg width={20} height={20} viewBox="0 0 24 24">
251
+ <AnimatedPath
252
+ d={CHECK}
253
+ stroke={tint}
254
+ strokeWidth={2.8}
255
+ strokeLinecap="round"
256
+ strokeLinejoin="round"
257
+ fill="none"
258
+ strokeDasharray={CHECK_LEN}
259
+ strokeDashoffset={p.interpolate({ inputRange: [0, 1], outputRange: [CHECK_LEN, 0], extrapolate: 'clamp' }) as unknown as number}
260
+ />
261
+ </Svg>
262
+ </Pressable>
263
+ );
264
+ }
265
+
266
+ return (
267
+ <Pressable
268
+ {...a11y}
269
+ hitSlop={8}
270
+ onPressIn={() => pressTo(0.86)}
271
+ onPressOut={() => pressTo(1)}
272
+ style={[styles.row, horizontal && styles.rowCompact, { opacity: disabled ? 0.5 : 1 }]}
273
+ >
274
+ <Animated.View style={[styles.ringSlot, { transform: [{ scale: press }] }]}>
275
+ <Animated.View pointerEvents="none" style={[styles.ripple, rippleStyle]} />
276
+ <Animated.View
277
+ style={[
278
+ styles.ring,
279
+ {
280
+ borderColor: p.interpolate({ inputRange: [0, 1], outputRange: [colors.input, tint], extrapolate: 'clamp' }),
281
+ backgroundColor: p.interpolate({ inputRange: [0, 1], outputRange: [colors.card, tint], extrapolate: 'clamp' }),
282
+ },
283
+ ]}
284
+ >
285
+ <Animated.View style={[styles.dot, { backgroundColor: readableOn(tint), opacity: dot, transform: [{ scale: dot }] }]} />
286
+ </Animated.View>
287
+ </Animated.View>
288
+ {horizontal && !option.description ? (
289
+ <Text style={[styles.label, { color: fg }]}>{option.label}</Text>
290
+ ) : (
291
+ text
292
+ )}
293
+ </Pressable>
294
+ );
295
+ }
296
+
297
+ const styles = StyleSheet.create({
298
+ vertical: { flexDirection: 'column' },
299
+ horizontal: { flexDirection: 'row', flexWrap: 'wrap' },
300
+ flex: { flex: 1, minWidth: 0 },
301
+ flexGrow: { flexGrow: 1 },
302
+ row: { flexDirection: 'row', alignItems: 'flex-start', gap: 12 },
303
+ rowCompact: { alignItems: 'center' },
304
+ ring: { width: 22, height: 22, borderRadius: 11, borderWidth: 1.5, alignItems: 'center', justifyContent: 'center' },
305
+ dot: { width: 8, height: 8, borderRadius: 4 },
306
+ ringSlot: { alignItems: 'center', justifyContent: 'center' },
307
+ ripple: { position: 'absolute', width: 22, height: 22, borderRadius: 11 },
308
+ text: { flex: 1, minWidth: 0 },
309
+ label: { fontSize: 15.5, fontWeight: '400', lineHeight: 21 },
310
+ description: { fontSize: 13, lineHeight: 18, marginTop: 2 },
311
+ hint: { fontSize: 16, fontWeight: '400', fontVariant: ['tabular-nums'] },
312
+ hintColumn: { marginTop: 8 },
313
+ card: { flexDirection: 'row', alignItems: 'center', gap: 14, borderWidth: 1.5, padding: 16, flexGrow: 1 },
314
+ glow: { position: 'absolute', top: -4, left: -4, right: -4, bottom: -4, borderWidth: 3 },
315
+ cardColumn: { flexDirection: 'column', alignItems: 'flex-start', gap: 10, paddingTop: 14 },
316
+ cardTextColumn: { flex: 0, alignSelf: 'stretch', paddingRight: 18 },
317
+ cardIcon: { width: 42, height: 42, alignItems: 'center', justifyContent: 'center' },
318
+ badgeSlot: { width: 22, height: 22, alignItems: 'center', justifyContent: 'center' },
319
+ badgeCorner: { position: 'absolute', top: 12, right: 12 },
320
+ badgeRing: { position: 'absolute', width: 22, height: 22, borderRadius: 11, borderWidth: 1.5 },
321
+ badge: { width: 22, height: 22, borderRadius: 11, alignItems: 'center', justifyContent: 'center' },
322
+ list: { borderWidth: StyleSheet.hairlineWidth, overflow: 'hidden' },
323
+ listRow: { flexDirection: 'row', alignItems: 'center', gap: 12, minHeight: 50, paddingHorizontal: 16, paddingVertical: 12 },
324
+ listIcon: { width: 24, alignItems: 'center' },
325
+ dividerTop: { borderTopWidth: StyleSheet.hairlineWidth },
326
+ dividerLeft: { borderLeftWidth: StyleSheet.hairlineWidth },
327
+ });