@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,17 @@
1
+ {
2
+ "name": "calendar",
3
+ "type": "component",
4
+ "title": "Calendar",
5
+ "description": "A month calendar for single dates, continuous ranges or multiple picks. Months slide in from the direction you travel (chevrons or a swipe), the title flips to a month / year grid, selected days pop with a spring and ranges draw one band with rounded ends that wraps across weeks. Locale-aware weekday and month names via Intl, Sunday or Monday weeks, min / max / custom disabled days, a today ring, coloured event dots and whole-day colour highlights. Day tiles follow the kit radius — square at 0, circles from 16 up.",
6
+ "tier": "free",
7
+ "dependencies": [
8
+ "react-native-svg"
9
+ ],
10
+ "registryDependencies": [
11
+ "kit"
12
+ ],
13
+ "sharedFiles": [
14
+ "lib/kit.tsx"
15
+ ],
16
+ "filesDir": "calendar/files"
17
+ }
@@ -0,0 +1,300 @@
1
+ import React, { createContext, useContext, useId, useRef, useState } from 'react';
2
+ import {
3
+ Animated,
4
+ Image,
5
+ Platform,
6
+ Pressable,
7
+ StyleSheet,
8
+ Text,
9
+ View,
10
+ type GestureResponderEvent,
11
+ type ImageSourcePropType,
12
+ type LayoutChangeEvent,
13
+ type StyleProp,
14
+ type TextStyle,
15
+ type ViewStyle,
16
+ } from 'react-native';
17
+ import Svg, { Defs, RadialGradient, Rect, Stop } from 'react-native-svg';
18
+
19
+ import { alpha, motion, useKit } from '@/lib/kit';
20
+
21
+ export type CardVariant = 'default' | 'outline' | 'elevated';
22
+ export type CardHighlight = 'none' | 'spotlight' | 'border';
23
+
24
+ export interface CardProps {
25
+ children?: React.ReactNode;
26
+ variant?: CardVariant;
27
+ /** Inner padding in points (default 18). Sections are spaced by `gap`. */
28
+ padding?: number;
29
+ /** Space between Header, Content and Footer (default 14). */
30
+ gap?: number;
31
+ /** Makes the whole card a button with spring press feedback. */
32
+ onPress?: (e: GestureResponderEvent) => void;
33
+ onLongPress?: (e: GestureResponderEvent) => void;
34
+ /** Spring press feedback without an onPress (e.g. the card wraps its own buttons). */
35
+ pressable?: boolean;
36
+ /** Light up on press: a soft spotlight that follows the finger, or a glowing accent border. */
37
+ highlight?: CardHighlight;
38
+ /** Colour of the highlight (default: theme accent). */
39
+ accent?: string;
40
+ disabled?: boolean;
41
+ accessibilityLabel?: string;
42
+ style?: StyleProp<ViewStyle>;
43
+ }
44
+
45
+ const CardContext = createContext({ padding: 18 });
46
+
47
+ /**
48
+ * Surface for grouping content. Compose with Card.Header, Card.Title, Card.Description,
49
+ * Card.Content, Card.Media and Card.Footer. Optional spring press with a finger-following spotlight.
50
+ */
51
+ export function Card({
52
+ children,
53
+ variant = 'default',
54
+ padding = 18,
55
+ gap = 14,
56
+ onPress,
57
+ onLongPress,
58
+ pressable,
59
+ highlight = 'none',
60
+ accent,
61
+ disabled,
62
+ accessibilityLabel,
63
+ style,
64
+ }: CardProps) {
65
+ const { colors, radius, dark } = useKit();
66
+ const tint = accent ?? colors.accent;
67
+ const interactive = !!(onPress || onLongPress || pressable);
68
+ const corner = radius.scale(18);
69
+ const gid = 'card' + useId().replace(/[^a-zA-Z0-9]/g, '');
70
+
71
+ const scale = useRef(new Animated.Value(1)).current;
72
+ const glow = useRef(new Animated.Value(0)).current;
73
+ const spot = useRef(new Animated.ValueXY({ x: 0, y: 0 })).current;
74
+ const [size, setSize] = useState({ w: 0, h: 0 });
75
+ const spotD = Math.max(size.w, size.h) * 1.1;
76
+
77
+ const surfaces: Record<CardVariant, ViewStyle> = {
78
+ default: {
79
+ backgroundColor: colors.card,
80
+ borderWidth: StyleSheet.hairlineWidth * 2,
81
+ borderColor: colors.border,
82
+ ...shadow(dark ? 0 : 0.04, 2, 6),
83
+ },
84
+ outline: { backgroundColor: 'transparent', borderWidth: 1, borderColor: colors.border },
85
+ elevated: { backgroundColor: dark ? colors.popover : colors.card, ...shadow(dark ? 0.5 : 0.1, 10, 28, 8) },
86
+ };
87
+
88
+ const border = (surfaces[variant].borderWidth as number | undefined) ?? 0;
89
+
90
+ const pressIn = (e: GestureResponderEvent) => {
91
+ Animated.spring(scale, { toValue: 0.975, useNativeDriver: true, ...motion.snappy }).start();
92
+ if (highlight === 'none') return;
93
+ moveSpot(e);
94
+ Animated.spring(glow, { toValue: 1, useNativeDriver: true, ...motion.gentle }).start();
95
+ };
96
+ const pressOut = () => {
97
+ Animated.spring(scale, { toValue: 1, useNativeDriver: true, ...motion.bouncy }).start();
98
+ if (highlight !== 'none') Animated.timing(glow, { toValue: 0, duration: 420, useNativeDriver: true }).start();
99
+ };
100
+ // Touch coordinates are relative to whichever child was hit, so work from page coordinates.
101
+ const clipRef = useRef<View>(null);
102
+ const origin = useRef({ x: 0, y: 0 });
103
+ const placeSpot = (pageX: number, pageY: number) =>
104
+ spot.setValue({ x: pageX - origin.current.x - spotD / 2, y: pageY - origin.current.y - spotD / 2 });
105
+ const moveSpot = (e: GestureResponderEvent) => {
106
+ const { pageX, pageY } = e.nativeEvent;
107
+ clipRef.current?.measure((_x, _y, _w, _h, px, py) => {
108
+ origin.current = { x: px, y: py };
109
+ placeSpot(pageX, pageY);
110
+ });
111
+ };
112
+ const followSpot = (e: GestureResponderEvent) => placeSpot(e.nativeEvent.pageX, e.nativeEvent.pageY);
113
+
114
+ const content = (
115
+ <View ref={clipRef} style={[styles.clip, { borderRadius: Math.max(0, corner - border) }]}>
116
+ {highlight === 'spotlight' && spotD > 0 ? (
117
+ <Animated.View
118
+ pointerEvents="none"
119
+ style={[
120
+ styles.spot,
121
+ {
122
+ width: spotD,
123
+ height: spotD,
124
+ opacity: glow,
125
+ transform: [
126
+ { translateX: spot.x },
127
+ { translateY: spot.y },
128
+ { scale: glow.interpolate({ inputRange: [0, 1], outputRange: [0.4, 1] }) },
129
+ ],
130
+ },
131
+ ]}
132
+ >
133
+ <Svg width={spotD} height={spotD}>
134
+ <Defs>
135
+ <RadialGradient id={gid} cx="50%" cy="50%" r="50%">
136
+ <Stop offset="0" stopColor={tint} stopOpacity={dark ? 0.32 : 0.2} />
137
+ <Stop offset="0.55" stopColor={tint} stopOpacity={dark ? 0.1 : 0.06} />
138
+ <Stop offset="1" stopColor={tint} stopOpacity={0} />
139
+ </RadialGradient>
140
+ </Defs>
141
+ <Rect width={spotD} height={spotD} fill={`url(#${gid})`} />
142
+ </Svg>
143
+ </Animated.View>
144
+ ) : null}
145
+ <View style={{ padding, gap }}>{children}</View>
146
+ {highlight !== 'none' ? (
147
+ <Animated.View
148
+ pointerEvents="none"
149
+ style={[styles.ring, { borderRadius: Math.max(0, corner - border), borderColor: alpha(tint, 0.85), opacity: glow }]}
150
+ />
151
+ ) : null}
152
+ </View>
153
+ );
154
+
155
+ const onLayout = (e: LayoutChangeEvent) => {
156
+ const { width: w, height: h } = e.nativeEvent.layout;
157
+ if (w !== size.w || h !== size.h) setSize({ w, h });
158
+ };
159
+
160
+ return (
161
+ <CardContext.Provider value={{ padding }}>
162
+ <Animated.View
163
+ onLayout={onLayout}
164
+ style={[
165
+ { borderRadius: corner, opacity: disabled ? 0.55 : 1 },
166
+ surfaces[variant],
167
+ interactive && { transform: [{ scale }] },
168
+ style,
169
+ ]}
170
+ >
171
+ {interactive ? (
172
+ <Pressable
173
+ accessibilityRole={onPress || onLongPress ? 'button' : undefined}
174
+ accessibilityLabel={accessibilityLabel}
175
+ accessibilityState={{ disabled: !!disabled }}
176
+ disabled={disabled}
177
+ onPress={onPress}
178
+ onLongPress={onLongPress}
179
+ onPressIn={pressIn}
180
+ onPressOut={pressOut}
181
+ onTouchMove={highlight === 'spotlight' ? followSpot : undefined}
182
+ style={Platform.OS === 'web' ? ({ cursor: onPress ? 'pointer' : 'auto' } as ViewStyle) : undefined}
183
+ >
184
+ {content}
185
+ </Pressable>
186
+ ) : (
187
+ content
188
+ )}
189
+ </Animated.View>
190
+ </CardContext.Provider>
191
+ );
192
+ }
193
+
194
+ export interface CardHeaderProps {
195
+ children?: React.ReactNode;
196
+ /** Element pinned to the top-right (menu button, badge, avatar). */
197
+ action?: React.ReactNode;
198
+ style?: StyleProp<ViewStyle>;
199
+ }
200
+
201
+ /** Title + description stack, with an optional action on the right. */
202
+ export function CardHeader({ children, action, style }: CardHeaderProps) {
203
+ return (
204
+ <View style={[styles.header, style]}>
205
+ <View style={styles.headerText}>{children}</View>
206
+ {action ? <View>{action}</View> : null}
207
+ </View>
208
+ );
209
+ }
210
+
211
+ export interface CardTextProps {
212
+ children?: React.ReactNode;
213
+ numberOfLines?: number;
214
+ style?: StyleProp<TextStyle>;
215
+ }
216
+
217
+ export function CardTitle({ children, numberOfLines, style }: CardTextProps) {
218
+ const { colors } = useKit();
219
+ return (
220
+ <Text accessibilityRole="header" numberOfLines={numberOfLines} style={[styles.title, { color: colors.cardForeground }, style]}>
221
+ {children}
222
+ </Text>
223
+ );
224
+ }
225
+
226
+ export function CardDescription({ children, numberOfLines, style }: CardTextProps) {
227
+ const { colors } = useKit();
228
+ return (
229
+ <Text numberOfLines={numberOfLines} style={[styles.description, { color: colors.mutedForeground }, style]}>
230
+ {children}
231
+ </Text>
232
+ );
233
+ }
234
+
235
+ export interface CardSectionProps {
236
+ children?: React.ReactNode;
237
+ style?: StyleProp<ViewStyle>;
238
+ }
239
+
240
+ export function CardContent({ children, style }: CardSectionProps) {
241
+ return <View style={style}>{children}</View>;
242
+ }
243
+
244
+ /** Row of actions at the bottom. */
245
+ export function CardFooter({ children, style }: CardSectionProps) {
246
+ return <View style={[styles.footer, style]}>{children}</View>;
247
+ }
248
+
249
+ export interface CardMediaProps {
250
+ source: ImageSourcePropType;
251
+ /** Image height (default 168). */
252
+ height?: number;
253
+ /** Optional overlay content (e.g. a badge) placed on top of the image. */
254
+ children?: React.ReactNode;
255
+ accessibilityLabel?: string;
256
+ style?: StyleProp<ViewStyle>;
257
+ }
258
+
259
+ /** Full-bleed image. Put it first inside the Card — it cancels the card padding. */
260
+ export function CardMedia({ source, height = 168, children, accessibilityLabel, style }: CardMediaProps) {
261
+ const { padding } = useContext(CardContext);
262
+ const { colors } = useKit();
263
+ return (
264
+ <View style={[{ height, marginTop: -padding, marginHorizontal: -padding, backgroundColor: colors.muted }, style]}>
265
+ <Image
266
+ source={source}
267
+ accessibilityLabel={accessibilityLabel}
268
+ accessibilityIgnoresInvertColors
269
+ resizeMode="cover"
270
+ style={styles.media}
271
+ />
272
+ {children ? <View style={[styles.mediaOverlay, { padding: padding - 4 }]}>{children}</View> : null}
273
+ </View>
274
+ );
275
+ }
276
+
277
+ Card.Header = CardHeader;
278
+ Card.Title = CardTitle;
279
+ Card.Description = CardDescription;
280
+ Card.Content = CardContent;
281
+ Card.Footer = CardFooter;
282
+ Card.Media = CardMedia;
283
+
284
+ function shadow(opacity: number, y: number, blur: number, elevation = 2): ViewStyle {
285
+ if (!opacity) return {};
286
+ return { shadowColor: '#000', shadowOpacity: opacity, shadowRadius: blur / 2, shadowOffset: { width: 0, height: y }, elevation };
287
+ }
288
+
289
+ const styles = StyleSheet.create({
290
+ clip: { overflow: 'hidden' },
291
+ spot: { position: 'absolute', top: 0, left: 0 },
292
+ ring: { position: 'absolute', top: 0, left: 0, right: 0, bottom: 0, borderWidth: 1.5 },
293
+ header: { flexDirection: 'row', alignItems: 'flex-start', gap: 12 },
294
+ headerText: { flex: 1, gap: 4 },
295
+ title: { fontSize: 17, lineHeight: 22, fontWeight: '400', letterSpacing: -0.2 },
296
+ description: { fontSize: 14, lineHeight: 20 },
297
+ footer: { flexDirection: 'row', alignItems: 'center', gap: 10, marginTop: 2 },
298
+ media: { width: '100%', height: '100%' },
299
+ mediaOverlay: { position: 'absolute', top: 0, left: 0, right: 0, bottom: 0, justifyContent: 'space-between' },
300
+ });