@expokit/cli 0.0.0-stage → 0.2.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 (302) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +32 -2
  3. package/bin/expo-kit.js +1204 -0
  4. package/bin/lib.mjs +343 -0
  5. package/bin/mcp.mjs +338 -0
  6. package/package.json +44 -4
  7. package/registry/_docs/accordion.json +99 -0
  8. package/registry/_docs/action-sheet.json +110 -0
  9. package/registry/_docs/alert.json +127 -0
  10. package/registry/_docs/avatar.json +203 -0
  11. package/registry/_docs/badge.json +117 -0
  12. package/registry/_docs/bottom-nav.json +119 -0
  13. package/registry/_docs/bottom-sheet.json +158 -0
  14. package/registry/_docs/bullet.json +24 -0
  15. package/registry/_docs/button-3d.json +122 -0
  16. package/registry/_docs/button.json +115 -0
  17. package/registry/_docs/calendar.json +182 -0
  18. package/registry/_docs/card-stack.json +108 -0
  19. package/registry/_docs/card.json +111 -0
  20. package/registry/_docs/carousel.json +102 -0
  21. package/registry/_docs/checkbox.json +93 -0
  22. package/registry/_docs/checkout.json +102 -0
  23. package/registry/_docs/chip-group.json +116 -0
  24. package/registry/_docs/color-swatch.json +66 -0
  25. package/registry/_docs/confetti.json +82 -0
  26. package/registry/_docs/coupon.json +176 -0
  27. package/registry/_docs/cube-pager.json +84 -0
  28. package/registry/_docs/dashboard.json +120 -0
  29. package/registry/_docs/date-picker.json +249 -0
  30. package/registry/_docs/dialog.json +162 -0
  31. package/registry/_docs/drawer.json +171 -0
  32. package/registry/_docs/fab.json +120 -0
  33. package/registry/_docs/flip-card.json +111 -0
  34. package/registry/_docs/floating-input.json +191 -0
  35. package/registry/_docs/gift-card.json +108 -0
  36. package/registry/_docs/glass-button.json +118 -0
  37. package/registry/_docs/glass-nav-bar.json +78 -0
  38. package/registry/_docs/glass-search-bar.json +90 -0
  39. package/registry/_docs/glass-segmented.json +66 -0
  40. package/registry/_docs/glass-sheet.json +108 -0
  41. package/registry/_docs/glass-tab-bar.json +76 -0
  42. package/registry/_docs/glass.json +78 -0
  43. package/registry/_docs/inbox.json +72 -0
  44. package/registry/_docs/isometric-bar.json +72 -0
  45. package/registry/_docs/kit.json +11 -0
  46. package/registry/_docs/kpi-card.json +11 -0
  47. package/registry/_docs/like-button.json +72 -0
  48. package/registry/_docs/liquidglass-tab.json +27 -0
  49. package/registry/_docs/list-item.json +200 -0
  50. package/registry/_docs/loyalty-card.json +108 -0
  51. package/registry/_docs/morph-button.json +108 -0
  52. package/registry/_docs/number-ticker.json +96 -0
  53. package/registry/_docs/offers.json +78 -0
  54. package/registry/_docs/onboarding.json +66 -0
  55. package/registry/_docs/otp-input.json +134 -0
  56. package/registry/_docs/pagination-dots.json +88 -0
  57. package/registry/_docs/parallax-layers.json +84 -0
  58. package/registry/_docs/parallax-scroll.json +186 -0
  59. package/registry/_docs/password-input.json +92 -0
  60. package/registry/_docs/payment-card.json +153 -0
  61. package/registry/_docs/pixel-bar.json +66 -0
  62. package/registry/_docs/polar-area.json +78 -0
  63. package/registry/_docs/profile.json +84 -0
  64. package/registry/_docs/progress-steps.json +92 -0
  65. package/registry/_docs/progress.json +132 -0
  66. package/registry/_docs/radio-group.json +92 -0
  67. package/registry/_docs/rating.json +90 -0
  68. package/registry/_docs/ring-carousel.json +102 -0
  69. package/registry/_docs/scratch-card.json +90 -0
  70. package/registry/_docs/search-bar.json +140 -0
  71. package/registry/_docs/search-results.json +120 -0
  72. package/registry/_docs/segmented-control.json +103 -0
  73. package/registry/_docs/select.json +252 -0
  74. package/registry/_docs/settings.json +72 -0
  75. package/registry/_docs/shimmer-button.json +78 -0
  76. package/registry/_docs/sign-in.json +198 -0
  77. package/registry/_docs/skeleton.json +142 -0
  78. package/registry/_docs/slide-to-confirm.json +84 -0
  79. package/registry/_docs/slider.json +157 -0
  80. package/registry/_docs/spin-wheel.json +114 -0
  81. package/registry/_docs/spinner.json +91 -0
  82. package/registry/_docs/stepper.json +168 -0
  83. package/registry/_docs/swipe-deck.json +102 -0
  84. package/registry/_docs/swipeable-row.json +72 -0
  85. package/registry/_docs/switch.json +104 -0
  86. package/registry/_docs/tabs.json +105 -0
  87. package/registry/_docs/tag-input.json +128 -0
  88. package/registry/_docs/textarea.json +175 -0
  89. package/registry/_docs/ticket-card.json +174 -0
  90. package/registry/_docs/ticket-shape.json +11 -0
  91. package/registry/_docs/tilt-card.json +141 -0
  92. package/registry/_docs/time-picker.json +253 -0
  93. package/registry/_docs/timeline-chart.json +30 -0
  94. package/registry/_docs/timeline.json +156 -0
  95. package/registry/_docs/toast.json +161 -0
  96. package/registry/_docs/treemap.json +30 -0
  97. package/registry/_docs/waterfall.json +36 -0
  98. package/registry/_docs/wheel-list.json +102 -0
  99. package/registry/accordion/files/src/components/ui/accordion.tsx +233 -0
  100. package/registry/accordion/files/src/lib/kit.tsx +219 -0
  101. package/registry/accordion.json +17 -0
  102. package/registry/action-sheet/files/src/components/ui/action-sheet.tsx +580 -0
  103. package/registry/action-sheet/files/src/lib/kit.tsx +219 -0
  104. package/registry/action-sheet.json +18 -0
  105. package/registry/alert/files/src/components/ui/alert.tsx +654 -0
  106. package/registry/alert/files/src/components/ui/button.tsx +274 -0
  107. package/registry/alert/files/src/lib/kit.tsx +219 -0
  108. package/registry/alert.json +20 -0
  109. package/registry/avatar/files/src/components/ui/avatar.tsx +790 -0
  110. package/registry/avatar/files/src/lib/kit.tsx +219 -0
  111. package/registry/avatar.json +18 -0
  112. package/registry/badge/files/src/components/ui/badge.tsx +316 -0
  113. package/registry/badge/files/src/lib/kit.tsx +219 -0
  114. package/registry/badge.json +17 -0
  115. package/registry/bottom-nav/files/src/components/ui/bottom-nav.tsx +1050 -0
  116. package/registry/bottom-nav/files/src/lib/kit.tsx +219 -0
  117. package/registry/bottom-nav.json +18 -0
  118. package/registry/bottom-sheet/files/src/components/ui/bottom-sheet.tsx +585 -0
  119. package/registry/bottom-sheet/files/src/lib/kit.tsx +219 -0
  120. package/registry/bottom-sheet.json +17 -0
  121. package/registry/bullet.json +14 -0
  122. package/registry/button/files/src/components/ui/button.tsx +274 -0
  123. package/registry/button/files/src/lib/kit.tsx +219 -0
  124. package/registry/button-3d/files/src/components/ui/button-3d.tsx +212 -0
  125. package/registry/button-3d/files/src/lib/kit.tsx +219 -0
  126. package/registry/button-3d.json +18 -0
  127. package/registry/button.json +18 -0
  128. package/registry/calendar/files/src/components/ui/calendar.tsx +822 -0
  129. package/registry/calendar/files/src/lib/kit.tsx +219 -0
  130. package/registry/calendar.json +17 -0
  131. package/registry/card/files/src/components/ui/card.tsx +300 -0
  132. package/registry/card/files/src/lib/kit.tsx +219 -0
  133. package/registry/card-stack.json +15 -0
  134. package/registry/card.json +17 -0
  135. package/registry/carousel.json +15 -0
  136. package/registry/checkbox/files/src/components/ui/checkbox.tsx +150 -0
  137. package/registry/checkbox/files/src/lib/kit.tsx +219 -0
  138. package/registry/checkbox.json +17 -0
  139. package/registry/checkout.json +26 -0
  140. package/registry/chip-group/files/src/components/ui/chip-group.tsx +269 -0
  141. package/registry/chip-group/files/src/lib/kit.tsx +219 -0
  142. package/registry/chip-group.json +17 -0
  143. package/registry/color-swatch.json +17 -0
  144. package/registry/confetti/files/src/components/ui/confetti.tsx +222 -0
  145. package/registry/confetti/files/src/lib/kit.tsx +219 -0
  146. package/registry/confetti.json +15 -0
  147. package/registry/coupon/files/src/components/ui/coupon.tsx +698 -0
  148. package/registry/coupon/files/src/components/ui/flip-card.tsx +205 -0
  149. package/registry/coupon/files/src/components/ui/tilt-card.tsx +635 -0
  150. package/registry/coupon/files/src/lib/kit.tsx +219 -0
  151. package/registry/coupon/files/src/lib/ticket-shape.tsx +543 -0
  152. package/registry/coupon.json +23 -0
  153. package/registry/cube-pager.json +15 -0
  154. package/registry/dashboard.json +32 -0
  155. package/registry/date-picker/files/src/components/ui/calendar.tsx +822 -0
  156. package/registry/date-picker/files/src/components/ui/date-picker.tsx +796 -0
  157. package/registry/date-picker/files/src/lib/kit.tsx +219 -0
  158. package/registry/date-picker.json +19 -0
  159. package/registry/dialog/files/src/components/ui/dialog.tsx +759 -0
  160. package/registry/dialog/files/src/lib/kit.tsx +219 -0
  161. package/registry/dialog.json +18 -0
  162. package/registry/drawer/files/src/components/ui/drawer.tsx +1135 -0
  163. package/registry/drawer/files/src/lib/kit.tsx +219 -0
  164. package/registry/drawer.json +18 -0
  165. package/registry/fab.json +18 -0
  166. package/registry/flip-card/files/src/components/ui/flip-card.tsx +205 -0
  167. package/registry/flip-card/files/src/lib/kit.tsx +219 -0
  168. package/registry/flip-card.json +15 -0
  169. package/registry/floating-input/files/src/components/ui/floating-input.tsx +528 -0
  170. package/registry/floating-input/files/src/lib/kit.tsx +219 -0
  171. package/registry/floating-input.json +17 -0
  172. package/registry/gift-card.json +21 -0
  173. package/registry/glass-button.json +22 -0
  174. package/registry/glass-nav-bar.json +21 -0
  175. package/registry/glass-search-bar.json +21 -0
  176. package/registry/glass-segmented.json +22 -0
  177. package/registry/glass-sheet.json +22 -0
  178. package/registry/glass-tab-bar.json +22 -0
  179. package/registry/glass.json +19 -0
  180. package/registry/inbox.json +30 -0
  181. package/registry/isometric-bar.json +14 -0
  182. package/registry/kit/files/src/lib/kit.tsx +219 -0
  183. package/registry/kit.json +11 -0
  184. package/registry/kpi-card/files/src/components/charts/kpi-card.tsx +45 -0
  185. package/registry/kpi-card.json +14 -0
  186. package/registry/like-button.json +18 -0
  187. package/registry/liquidglass-tab/files/src/app/(tabs)/_layout.tsx +97 -0
  188. package/registry/liquidglass-tab/files/src/app/(tabs)/alerts.tsx +5 -0
  189. package/registry/liquidglass-tab/files/src/app/(tabs)/explore.tsx +5 -0
  190. package/registry/liquidglass-tab/files/src/app/(tabs)/index.tsx +5 -0
  191. package/registry/liquidglass-tab/files/src/app/(tabs)/profile.tsx +5 -0
  192. package/registry/liquidglass-tab/files/src/app/_layout.tsx +33 -0
  193. package/registry/liquidglass-tab/files/src/components/GlassTabBar.tsx +259 -0
  194. package/registry/liquidglass-tab/files/src/components/TabScreen.tsx +122 -0
  195. package/registry/liquidglass-tab/files/src/lib/tab-bar.ts +61 -0
  196. package/registry/liquidglass-tab/files/src/lib/tab-icons.ts +130 -0
  197. package/registry/liquidglass-tab/files/src/lib/tabs.ts +108 -0
  198. package/registry/liquidglass-tab.json +23 -0
  199. package/registry/list-item/files/src/components/ui/list-item.tsx +508 -0
  200. package/registry/list-item/files/src/lib/kit.tsx +219 -0
  201. package/registry/list-item.json +17 -0
  202. package/registry/loyalty-card.json +23 -0
  203. package/registry/morph-button.json +18 -0
  204. package/registry/number-ticker.json +15 -0
  205. package/registry/offers.json +24 -0
  206. package/registry/onboarding.json +17 -0
  207. package/registry/otp-input/files/src/components/ui/otp-input.tsx +380 -0
  208. package/registry/otp-input/files/src/lib/kit.tsx +219 -0
  209. package/registry/otp-input.json +17 -0
  210. package/registry/pagination-dots/files/src/components/ui/pagination-dots.tsx +262 -0
  211. package/registry/pagination-dots/files/src/lib/kit.tsx +219 -0
  212. package/registry/pagination-dots.json +15 -0
  213. package/registry/parallax-layers.json +15 -0
  214. package/registry/parallax-scroll.json +17 -0
  215. package/registry/password-input/files/src/components/ui/floating-input.tsx +528 -0
  216. package/registry/password-input/files/src/components/ui/password-input.tsx +271 -0
  217. package/registry/password-input/files/src/lib/kit.tsx +219 -0
  218. package/registry/password-input.json +19 -0
  219. package/registry/payment-card/files/src/components/ui/flip-card.tsx +205 -0
  220. package/registry/payment-card/files/src/components/ui/payment-card.tsx +385 -0
  221. package/registry/payment-card/files/src/components/ui/tilt-card.tsx +635 -0
  222. package/registry/payment-card/files/src/lib/kit.tsx +219 -0
  223. package/registry/payment-card.json +21 -0
  224. package/registry/pixel-bar.json +14 -0
  225. package/registry/polar-area.json +14 -0
  226. package/registry/profile.json +17 -0
  227. package/registry/progress/files/src/components/ui/progress.tsx +883 -0
  228. package/registry/progress/files/src/lib/kit.tsx +219 -0
  229. package/registry/progress-steps/files/src/components/ui/progress-steps.tsx +559 -0
  230. package/registry/progress-steps/files/src/lib/kit.tsx +219 -0
  231. package/registry/progress-steps.json +18 -0
  232. package/registry/progress.json +17 -0
  233. package/registry/radio-group/files/src/components/ui/radio-group.tsx +327 -0
  234. package/registry/radio-group/files/src/lib/kit.tsx +219 -0
  235. package/registry/radio-group.json +18 -0
  236. package/registry/rating.json +18 -0
  237. package/registry/ring-carousel.json +15 -0
  238. package/registry/scratch-card.json +18 -0
  239. package/registry/search-bar/files/src/components/ui/search-bar.tsx +530 -0
  240. package/registry/search-bar/files/src/lib/kit.tsx +219 -0
  241. package/registry/search-bar.json +17 -0
  242. package/registry/search-results.json +30 -0
  243. package/registry/segmented-control/files/src/components/ui/segmented-control.tsx +341 -0
  244. package/registry/segmented-control/files/src/lib/kit.tsx +219 -0
  245. package/registry/segmented-control.json +18 -0
  246. package/registry/select/files/src/components/ui/select.tsx +1398 -0
  247. package/registry/select/files/src/lib/kit.tsx +219 -0
  248. package/registry/select.json +17 -0
  249. package/registry/settings.json +26 -0
  250. package/registry/shimmer-button.json +17 -0
  251. package/registry/sign-in.json +26 -0
  252. package/registry/skeleton/files/src/components/ui/skeleton.tsx +799 -0
  253. package/registry/skeleton/files/src/lib/kit.tsx +219 -0
  254. package/registry/skeleton.json +17 -0
  255. package/registry/slide-to-confirm.json +18 -0
  256. package/registry/slider/files/src/components/ui/slider.tsx +716 -0
  257. package/registry/slider/files/src/lib/kit.tsx +219 -0
  258. package/registry/slider.json +17 -0
  259. package/registry/spin-wheel.json +18 -0
  260. package/registry/spinner/files/src/components/ui/spinner.tsx +335 -0
  261. package/registry/spinner/files/src/lib/kit.tsx +219 -0
  262. package/registry/spinner.json +17 -0
  263. package/registry/stepper/files/src/components/ui/stepper.tsx +646 -0
  264. package/registry/stepper/files/src/lib/kit.tsx +219 -0
  265. package/registry/stepper.json +17 -0
  266. package/registry/swipe-deck.json +15 -0
  267. package/registry/swipeable-row.json +15 -0
  268. package/registry/switch/files/src/components/ui/switch.tsx +344 -0
  269. package/registry/switch/files/src/lib/kit.tsx +219 -0
  270. package/registry/switch.json +18 -0
  271. package/registry/tabs/files/src/components/ui/tabs.tsx +539 -0
  272. package/registry/tabs/files/src/lib/kit.tsx +219 -0
  273. package/registry/tabs.json +17 -0
  274. package/registry/tag-input/files/src/components/ui/tag-input.tsx +720 -0
  275. package/registry/tag-input/files/src/lib/kit.tsx +219 -0
  276. package/registry/tag-input.json +17 -0
  277. package/registry/textarea/files/src/components/ui/textarea.tsx +553 -0
  278. package/registry/textarea/files/src/lib/kit.tsx +219 -0
  279. package/registry/textarea.json +17 -0
  280. package/registry/ticket-card.json +24 -0
  281. package/registry/ticket-shape/files/src/lib/kit.tsx +219 -0
  282. package/registry/ticket-shape/files/src/lib/ticket-shape.tsx +543 -0
  283. package/registry/ticket-shape.json +17 -0
  284. package/registry/tilt-card/files/src/components/ui/tilt-card.tsx +635 -0
  285. package/registry/tilt-card/files/src/lib/kit.tsx +219 -0
  286. package/registry/tilt-card.json +17 -0
  287. package/registry/time-picker/files/src/components/ui/time-picker.tsx +1159 -0
  288. package/registry/time-picker/files/src/lib/kit.tsx +219 -0
  289. package/registry/time-picker.json +17 -0
  290. package/registry/timeline/files/src/components/ui/avatar.tsx +790 -0
  291. package/registry/timeline/files/src/components/ui/timeline.tsx +1059 -0
  292. package/registry/timeline/files/src/lib/kit.tsx +219 -0
  293. package/registry/timeline-chart.json +14 -0
  294. package/registry/timeline.json +20 -0
  295. package/registry/toast/files/src/components/ui/button.tsx +274 -0
  296. package/registry/toast/files/src/components/ui/toast.tsx +1284 -0
  297. package/registry/toast/files/src/lib/kit.tsx +219 -0
  298. package/registry/toast.json +20 -0
  299. package/registry/treemap.json +14 -0
  300. package/registry/waterfall.json +14 -0
  301. package/registry/wheel-list.json +17 -0
  302. 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
+ });