@expokit/cli 0.4.3 → 0.5.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 (242) hide show
  1. package/README.md +30 -3
  2. package/bin/expo-kit.js +749 -288
  3. package/bin/lib.mjs +313 -4
  4. package/package.json +1 -1
  5. package/registry/_docs/action-sheet.json +1 -1
  6. package/registry/_docs/alert.json +6 -0
  7. package/registry/_docs/bottom-sheet.json +6 -0
  8. package/registry/_docs/bullet.json +6 -0
  9. package/registry/_docs/dialog.json +1 -1
  10. package/registry/_docs/drawer.json +3 -3
  11. package/registry/_docs/flip-calendar.json +106 -0
  12. package/registry/_docs/glass-search-bar.json +1 -1
  13. package/registry/_docs/grain.json +6 -1
  14. package/registry/_docs/isometric-bar.json +6 -0
  15. package/registry/_docs/liquidglass-tab.json +1 -1
  16. package/registry/_docs/password-input.json +1 -1
  17. package/registry/_docs/paywall.json +144 -0
  18. package/registry/_docs/pixel-bar.json +6 -0
  19. package/registry/_docs/polar-area.json +6 -0
  20. package/registry/_docs/qr-code-button.json +9 -3
  21. package/registry/_docs/ruler-time-picker.json +147 -0
  22. package/registry/_docs/scratch-card.json +7 -1
  23. package/registry/_docs/search-bar.json +1 -1
  24. package/registry/_docs/select.json +2 -2
  25. package/registry/_docs/slide-to-confirm.json +2 -2
  26. package/registry/_docs/spinner.json +1 -1
  27. package/registry/_docs/stateful-button.json +2 -2
  28. package/registry/_docs/swipe-deck.json +1 -1
  29. package/registry/_docs/time-picker.json +2 -2
  30. package/registry/_docs/timeline-chart.json +6 -0
  31. package/registry/_docs/toast.json +15 -3
  32. package/registry/_docs/treemap.json +6 -0
  33. package/registry/_docs/waterfall.json +6 -0
  34. package/registry/accordion/files/src/components/ui/accordion.tsx +6 -4
  35. package/registry/accordion/files/src/lib/kit.tsx +462 -12
  36. package/registry/accordion.json +1 -0
  37. package/registry/action-sheet/files/src/components/ui/action-sheet.tsx +50 -58
  38. package/registry/action-sheet/files/src/lib/kit.tsx +462 -12
  39. package/registry/action-sheet.json +1 -0
  40. package/registry/alert/files/src/components/ui/alert.tsx +20 -13
  41. package/registry/alert/files/src/lib/kit.tsx +462 -12
  42. package/registry/alert.json +3 -4
  43. package/registry/assistant-app.json +1 -0
  44. package/registry/avatar/files/src/components/ui/avatar.tsx +89 -74
  45. package/registry/avatar/files/src/lib/kit.tsx +462 -12
  46. package/registry/avatar.json +1 -0
  47. package/registry/badge/files/src/components/ui/badge.tsx +36 -36
  48. package/registry/badge/files/src/lib/kit.tsx +462 -12
  49. package/registry/badge.json +1 -0
  50. package/registry/bottom-nav/files/src/components/ui/bottom-nav.tsx +39 -44
  51. package/registry/bottom-nav/files/src/lib/kit.tsx +462 -12
  52. package/registry/bottom-nav.json +5 -0
  53. package/registry/bottom-sheet/files/src/components/ui/bottom-sheet.tsx +357 -300
  54. package/registry/bottom-sheet/files/src/lib/kit.tsx +462 -12
  55. package/registry/bottom-sheet.json +5 -1
  56. package/registry/bullet.json +1 -0
  57. package/registry/button/files/src/components/ui/button.tsx +23 -34
  58. package/registry/button/files/src/lib/kit.tsx +462 -12
  59. package/registry/button-3d/files/src/components/ui/button-3d.tsx +6 -9
  60. package/registry/button-3d/files/src/lib/kit.tsx +462 -12
  61. package/registry/button-3d.json +1 -0
  62. package/registry/button.json +1 -0
  63. package/registry/calendar/files/src/components/ui/calendar.tsx +77 -79
  64. package/registry/calendar/files/src/lib/kit.tsx +462 -12
  65. package/registry/calendar.json +1 -0
  66. package/registry/card/files/src/components/ui/card.tsx +7 -6
  67. package/registry/card/files/src/lib/kit.tsx +462 -12
  68. package/registry/card-stack.json +1 -0
  69. package/registry/card.json +1 -0
  70. package/registry/carousel.json +1 -0
  71. package/registry/chat.json +7 -0
  72. package/registry/checkbox/files/src/components/ui/checkbox.tsx +4 -4
  73. package/registry/checkbox/files/src/lib/kit.tsx +462 -12
  74. package/registry/checkbox.json +1 -0
  75. package/registry/checkout.json +1 -0
  76. package/registry/chip-group/files/src/components/ui/chip-group.tsx +24 -24
  77. package/registry/chip-group/files/src/lib/kit.tsx +462 -12
  78. package/registry/chip-group.json +1 -0
  79. package/registry/confetti/files/src/components/ui/confetti.tsx +15 -14
  80. package/registry/confetti.json +3 -6
  81. package/registry/coupon/files/src/components/ui/coupon.tsx +47 -52
  82. package/registry/coupon/files/src/components/ui/flip-card.tsx +9 -14
  83. package/registry/coupon/files/src/components/ui/tilt-card.tsx +80 -101
  84. package/registry/coupon/files/src/lib/kit.tsx +462 -12
  85. package/registry/coupon/files/src/lib/ticket-shape.tsx +54 -44
  86. package/registry/coupon.json +1 -0
  87. package/registry/cube-pager.json +1 -0
  88. package/registry/dashboard.json +1 -0
  89. package/registry/date-picker/files/src/components/ui/calendar.tsx +77 -79
  90. package/registry/date-picker/files/src/components/ui/date-picker.tsx +64 -71
  91. package/registry/date-picker/files/src/lib/kit.tsx +462 -12
  92. package/registry/date-picker.json +1 -0
  93. package/registry/dialog/files/src/components/ui/dialog.tsx +66 -68
  94. package/registry/dialog/files/src/lib/kit.tsx +462 -12
  95. package/registry/dialog.json +1 -0
  96. package/registry/donut-shop.json +1 -0
  97. package/registry/drawer/files/src/components/ui/drawer.tsx +76 -88
  98. package/registry/drawer/files/src/lib/kit.tsx +462 -12
  99. package/registry/drawer.json +1 -0
  100. package/registry/fab.json +1 -0
  101. package/registry/fintech-app.json +1 -0
  102. package/registry/flip-calendar/files/src/components/ui/flip-calendar.tsx +632 -0
  103. package/registry/flip-calendar/files/src/lib/kit.tsx +672 -0
  104. package/registry/flip-calendar.json +24 -0
  105. package/registry/flip-card/files/src/components/ui/flip-card.tsx +9 -14
  106. package/registry/flip-card/files/src/lib/kit.tsx +462 -12
  107. package/registry/flip-card.json +1 -0
  108. package/registry/floating-input/files/src/components/ui/floating-input.tsx +33 -33
  109. package/registry/floating-input/files/src/lib/kit.tsx +462 -12
  110. package/registry/floating-input.json +1 -0
  111. package/registry/food-app.json +1 -0
  112. package/registry/gift-card.json +1 -0
  113. package/registry/glass-button.json +5 -0
  114. package/registry/glass-nav-bar.json +1 -0
  115. package/registry/glass-search-bar.json +1 -0
  116. package/registry/glass-segmented.json +1 -0
  117. package/registry/glass-sheet.json +1 -0
  118. package/registry/glass-tab-bar.json +5 -0
  119. package/registry/glass.json +1 -0
  120. package/registry/grain/files/src/components/ui/grain.tsx +16 -14
  121. package/registry/grain.json +8 -6
  122. package/registry/inbox.json +1 -0
  123. package/registry/isometric-bar.json +1 -0
  124. package/registry/kit/files/src/lib/kit.tsx +462 -12
  125. package/registry/kit.json +1 -0
  126. package/registry/liquidglass-tab/files/src/app/_layout.tsx +0 -1
  127. package/registry/liquidglass-tab/files/src/components/GlassTabBar.tsx +29 -16
  128. package/registry/liquidglass-tab/files/src/components/TabScreen.tsx +33 -19
  129. package/registry/liquidglass-tab/files/src/lib/tab-icons.ts +1 -1
  130. package/registry/liquidglass-tab.json +1 -0
  131. package/registry/list-item/files/src/components/ui/list-item.tsx +43 -43
  132. package/registry/list-item/files/src/lib/kit.tsx +462 -12
  133. package/registry/list-item.json +1 -0
  134. package/registry/loyalty-card.json +1 -0
  135. package/registry/morphing-button-group.json +1 -0
  136. package/registry/number-ticker.json +1 -0
  137. package/registry/offers.json +1 -0
  138. package/registry/onboarding.json +1 -0
  139. package/registry/orb-reveal.json +1 -0
  140. package/registry/otp-input/files/src/components/ui/otp-input.tsx +49 -30
  141. package/registry/otp-input/files/src/lib/kit.tsx +462 -12
  142. package/registry/otp-input.json +1 -0
  143. package/registry/parallax-layers.json +1 -0
  144. package/registry/parallax-scroll.json +1 -0
  145. package/registry/password-input/files/src/components/ui/floating-input.tsx +33 -33
  146. package/registry/password-input/files/src/components/ui/password-input.tsx +29 -25
  147. package/registry/password-input/files/src/lib/kit.tsx +462 -12
  148. package/registry/password-input.json +1 -0
  149. package/registry/payment-card/files/src/components/ui/flip-card.tsx +9 -14
  150. package/registry/payment-card/files/src/components/ui/payment-card.tsx +26 -20
  151. package/registry/payment-card/files/src/components/ui/tilt-card.tsx +80 -101
  152. package/registry/payment-card/files/src/lib/kit.tsx +462 -12
  153. package/registry/payment-card.json +1 -0
  154. package/registry/paywall.json +23 -0
  155. package/registry/pixel-bar.json +1 -0
  156. package/registry/polar-area.json +1 -0
  157. package/registry/profile.json +1 -0
  158. package/registry/progress/files/src/components/ui/progress.tsx +31 -29
  159. package/registry/progress/files/src/lib/kit.tsx +462 -12
  160. package/registry/progress-steps/files/src/components/ui/progress-steps.tsx +21 -21
  161. package/registry/progress-steps/files/src/lib/kit.tsx +462 -12
  162. package/registry/progress-steps.json +1 -0
  163. package/registry/progress.json +1 -0
  164. package/registry/qr-code-button.json +1 -0
  165. package/registry/radio-group/files/src/components/ui/radio-group.tsx +19 -19
  166. package/registry/radio-group/files/src/lib/kit.tsx +462 -12
  167. package/registry/radio-group.json +1 -0
  168. package/registry/rating.json +1 -0
  169. package/registry/ring-carousel.json +1 -0
  170. package/registry/ruler-time-picker/files/src/components/ui/ruler-time-picker.tsx +832 -0
  171. package/registry/ruler-time-picker/files/src/lib/kit.tsx +672 -0
  172. package/registry/ruler-time-picker.json +26 -0
  173. package/registry/scratch-card.json +1 -0
  174. package/registry/search-bar/files/src/components/ui/search-bar.tsx +28 -28
  175. package/registry/search-bar/files/src/lib/kit.tsx +462 -12
  176. package/registry/search-bar.json +1 -0
  177. package/registry/search-results.json +4 -0
  178. package/registry/segmented-control/files/src/components/ui/segmented-control.tsx +11 -17
  179. package/registry/segmented-control/files/src/lib/kit.tsx +462 -12
  180. package/registry/segmented-control.json +1 -0
  181. package/registry/select/files/src/components/ui/select.tsx +107 -104
  182. package/registry/select/files/src/lib/kit.tsx +462 -12
  183. package/registry/select.json +6 -0
  184. package/registry/settings.json +1 -0
  185. package/registry/sign-in.json +1 -0
  186. package/registry/skeleton/files/src/components/ui/skeleton.tsx +149 -80
  187. package/registry/skeleton/files/src/lib/kit.tsx +462 -12
  188. package/registry/skeleton.json +1 -0
  189. package/registry/slide-to-confirm.json +1 -0
  190. package/registry/slider/files/src/components/ui/slider.tsx +106 -108
  191. package/registry/slider/files/src/lib/kit.tsx +462 -12
  192. package/registry/slider.json +5 -0
  193. package/registry/spin-wheel.json +1 -0
  194. package/registry/spinner/files/src/components/ui/spinner.tsx +39 -29
  195. package/registry/spinner/files/src/lib/kit.tsx +462 -12
  196. package/registry/spinner.json +6 -0
  197. package/registry/stateful-button/files/src/components/ui/stateful-button.tsx +39 -32
  198. package/registry/stateful-button/files/src/lib/kit.tsx +462 -12
  199. package/registry/stateful-button.json +5 -0
  200. package/registry/stepper/files/src/components/ui/stepper.tsx +40 -38
  201. package/registry/stepper/files/src/lib/kit.tsx +462 -12
  202. package/registry/stepper.json +1 -0
  203. package/registry/swipe-deck.json +1 -0
  204. package/registry/swipeable-row.json +4 -0
  205. package/registry/switch/files/src/components/ui/switch.tsx +41 -47
  206. package/registry/switch/files/src/lib/kit.tsx +462 -12
  207. package/registry/switch.json +1 -0
  208. package/registry/tabs/files/src/components/ui/tabs.tsx +61 -64
  209. package/registry/tabs/files/src/lib/kit.tsx +462 -12
  210. package/registry/tabs.json +1 -0
  211. package/registry/text-animation/files/src/components/ui/text-animation.tsx +55 -42
  212. package/registry/text-animation/files/src/lib/kit.tsx +462 -12
  213. package/registry/text-animation.json +7 -0
  214. package/registry/textarea/files/src/components/ui/textarea.tsx +10 -10
  215. package/registry/textarea/files/src/lib/kit.tsx +462 -12
  216. package/registry/textarea.json +1 -0
  217. package/registry/ticket-card.json +1 -0
  218. package/registry/ticket-shape/files/src/lib/kit.tsx +462 -12
  219. package/registry/ticket-shape/files/src/lib/ticket-shape.tsx +54 -44
  220. package/registry/ticket-shape.json +1 -0
  221. package/registry/tilt-card/files/src/components/ui/tilt-card.tsx +80 -101
  222. package/registry/tilt-card/files/src/lib/kit.tsx +462 -12
  223. package/registry/tilt-card.json +1 -0
  224. package/registry/time-picker/files/src/components/ui/time-picker.tsx +136 -131
  225. package/registry/time-picker/files/src/lib/kit.tsx +462 -12
  226. package/registry/time-picker.json +1 -0
  227. package/registry/timeline/files/src/components/ui/avatar.tsx +89 -74
  228. package/registry/timeline/files/src/components/ui/timeline.tsx +49 -57
  229. package/registry/timeline/files/src/lib/kit.tsx +462 -12
  230. package/registry/timeline-chart.json +1 -0
  231. package/registry/timeline.json +1 -0
  232. package/registry/toast/files/src/components/ui/toast.tsx +543 -465
  233. package/registry/toast/files/src/lib/kit.tsx +462 -12
  234. package/registry/toast.json +7 -5
  235. package/registry/treemap.json +1 -0
  236. package/registry/waterfall.json +1 -0
  237. package/registry/wheel-list.json +1 -0
  238. package/schema/components.schema.json +1 -0
  239. package/registry/alert/files/src/components/ui/button.tsx +0 -303
  240. package/registry/confetti/files/src/lib/kit.tsx +0 -222
  241. package/registry/grain/files/src/lib/kit.tsx +0 -222
  242. package/registry/toast/files/src/components/ui/button.tsx +0 -303
@@ -0,0 +1,672 @@
1
+ import React, { createContext, useContext, useLayoutEffect, useMemo, useRef, useState } from 'react';
2
+ import {
3
+ PanResponder,
4
+ Platform,
5
+ useColorScheme,
6
+ useWindowDimensions,
7
+ type GestureResponderHandlers,
8
+ type PanResponderCallbacks,
9
+ type TextStyle,
10
+ type ViewStyle,
11
+ } from 'react-native';
12
+
13
+ /**
14
+ * ExpoKit design tokens. Every component reads them through useKit(), so one
15
+ * <KitProvider> (optional) re-skins the whole kit: light / dark, accent colour, radius.
16
+ * Without a provider, components follow the system colour scheme.
17
+ */
18
+
19
+ /**
20
+ * Bumped whenever components start using a new kit API (2: radius.scale / radius.capsule,
21
+ * 3: useLayout / shadow, 4: labels, 5: visible labels such as Cancel / Done / Today, AM / PM, usePanResponder / useLatest). The CLI won't keep an edited kit.tsx older than the component being added needs.
22
+ */
23
+ export const KIT_VERSION = 5;
24
+
25
+ export type ColorScheme = 'light' | 'dark';
26
+
27
+ export interface Colors {
28
+ background: string;
29
+ foreground: string;
30
+ /** Cards, sheets, inputs. */
31
+ card: string;
32
+ cardForeground: string;
33
+ /** Raised surfaces: popovers, toasts. */
34
+ popover: string;
35
+ muted: string;
36
+ mutedForeground: string;
37
+ border: string;
38
+ input: string;
39
+ primary: string;
40
+ primaryForeground: string;
41
+ secondary: string;
42
+ secondaryForeground: string;
43
+ accent: string;
44
+ accentForeground: string;
45
+ success: string;
46
+ warning: string;
47
+ danger: string;
48
+ dangerForeground: string;
49
+ ring: string;
50
+ overlay: string;
51
+ }
52
+
53
+ export interface Theme {
54
+ dark: boolean;
55
+ colors: Colors;
56
+ /** Corner radius scale. `radius.full` makes pills. */
57
+ radius: {
58
+ sm: number;
59
+ md: number;
60
+ lg: number;
61
+ xl: number;
62
+ full: number;
63
+ /** A corner designed at the default radius (12), scaled to the current one: `radius.scale(16)` is 16 at 12, 0 at 0. */
64
+ scale: (n: number) => number;
65
+ /** Corner for a bar or chip of height `h`: a pill at radius ≥ 12, softer below, square at 0. */
66
+ capsule: (h: number) => number;
67
+ };
68
+ /** 4pt spacing scale: space(4) === 16. */
69
+ space: (n: number) => number;
70
+ /** The same soft shadow on iOS, Android and web: `style={[styles.card, shadow('md')]}`. */
71
+ shadow: (level: ShadowLevel, color?: string) => ViewStyle;
72
+ font: { family?: string | undefined; mono: string };
73
+ text: Record<'xs' | 'sm' | 'md' | 'lg' | 'xl' | 'xxl', TextStyle>;
74
+ /** Text the kit shows or speaks on its own (buttons, placeholders, screen-reader names). Translate with <KitProvider labels={...}>. */
75
+ labels: Labels;
76
+ }
77
+
78
+ /**
79
+ * Every string the kit shows or speaks on its own: button text and placeholders (Cancel, Done, Today,
80
+ * Search…) and what screen readers say (close buttons, grabbers, hints). A prop on a component, like
81
+ * `cancelLabel` or `placeholder`, still wins over these.
82
+ * Pass a partial map to <KitProvider labels={...}>; keys you leave out stay English.
83
+ * Functions get the dynamic part, so word order can change per language.
84
+ */
85
+ export interface Labels {
86
+ close: string;
87
+ dismiss: string;
88
+ closeSheet: string;
89
+ sheetGrabber: string;
90
+ sheetResizeHint: string;
91
+ sheetResizeOrCloseHint: string;
92
+ navigationDrawer: string;
93
+ closeDrawer: string;
94
+ closeMenu: string;
95
+ switchAccount: string;
96
+ openActions: string;
97
+ closeActions: string;
98
+ action: string;
99
+ loading: string;
100
+ clearSearch: string;
101
+ cancelSearch: string;
102
+ clearField: (field: string) => string;
103
+ back: string;
104
+ previous: string;
105
+ next: string;
106
+ goToToday: string;
107
+ goToSlide: (n: number) => string;
108
+ showAllCards: string;
109
+ expandNotification: string;
110
+ collapseNotification: string;
111
+ moreCount: (n: number) => string;
112
+ peopleCount: (n: number) => string;
113
+ namesAndMore: (names: string, n: number) => string;
114
+ newCount: (badge: string) => string;
115
+ remove: (item: string) => string;
116
+ selectedCount: (n: number) => string;
117
+ noneSelected: string;
118
+ notSet: string;
119
+ date: string;
120
+ time: string;
121
+ hour: string;
122
+ minute: string;
123
+ hours: (n: string) => string;
124
+ minutes: (n: number) => string;
125
+ booked: string;
126
+ unavailable: string;
127
+ timeDialHint: string;
128
+ flipCalendarHint: string;
129
+ increase: string;
130
+ decrease: string;
131
+ increaseItem: (item: string) => string;
132
+ decreaseItem: (item: string) => string;
133
+ range: string;
134
+ rangeMinimum: (name: string) => string;
135
+ rangeMaximum: (name: string) => string;
136
+ ratingStars: string;
137
+ ratingHearts: string;
138
+ showPassword: string;
139
+ hidePassword: string;
140
+ passwordStrength: (level: string) => string;
141
+ ruleMet: (rule: string) => string;
142
+ ruleUnmet: (rule: string) => string;
143
+ stepOf: (step: number, total: number, label: string) => string;
144
+ allStepsComplete: string;
145
+ stepCurrent: string;
146
+ stepCompleted: string;
147
+ stepNeedsAttention: string;
148
+ stepNotStarted: string;
149
+ goBackToStep: string;
150
+ activateCodeField: string;
151
+ qrCodeFor: (value: string) => string;
152
+ undoStroke: string;
153
+ signaturePad: string;
154
+ signaturePadSigned: string;
155
+ flipToBack: string;
156
+ flipToFront: string;
157
+ revealHint: string;
158
+ confirmHint: string;
159
+ prizeWheel: string;
160
+ spinHint: string;
161
+ prizes: (n: number, list: string) => string;
162
+ orbReveal: string;
163
+ orbBack: string;
164
+ // Visible text
165
+ cancel: string;
166
+ done: string;
167
+ doneCount: (n: number) => string;
168
+ doneDays: (n: number) => string;
169
+ clear: string;
170
+ continue: string;
171
+ delete: string;
172
+ failed: string;
173
+ search: string;
174
+ noResults: string;
175
+ noOptions: string;
176
+ nothingMatches: (query: string) => string;
177
+ selectPlaceholder: string;
178
+ selectDate: string;
179
+ selectDates: string;
180
+ pickStartDate: string;
181
+ pickEndDate: string;
182
+ selectTime: string;
183
+ setTime: (time: string) => string;
184
+ /** Clock periods, shown on 12-hour pickers and times. */
185
+ am: string;
186
+ pm: string;
187
+ /** Read for the AM / PM column. */
188
+ amPm: string;
189
+ morning: string;
190
+ afternoon: string;
191
+ evening: string;
192
+ slotsOpen: (n: number) => string;
193
+ full: string;
194
+ today: string;
195
+ tomorrow: string;
196
+ nextMonday: string;
197
+ inAWeek: string;
198
+ inAMonth: string;
199
+ next7Days: string;
200
+ nextWeekend: string;
201
+ thisMonth: string;
202
+ last7Days: string;
203
+ last30Days: string;
204
+ chooseOneOrMore: string;
205
+ /** After a rolling count, as in "2 selected". */
206
+ selectedSuffix: string;
207
+ chooseMonth: string;
208
+ chooseYear: string;
209
+ backToDays: string;
210
+ dayAndWeek: (day: number, week: number) => string;
211
+ daysLeft: (n: number) => string;
212
+ /** Too short, Weak, Okay, Strong, Very strong. */
213
+ passwordLevels: [string, string, string, string, string];
214
+ passwordsMatch: string;
215
+ passwordsDontMatch: string;
216
+ showQrCode: string;
217
+ copy: string;
218
+ copied: string;
219
+ slideToConfirm: string;
220
+ confirmed: string;
221
+ like: string;
222
+ nope: string;
223
+ swipeRight: string;
224
+ swipeLeft: string;
225
+ }
226
+
227
+ export const defaultLabels: Labels = {
228
+ close: 'Close',
229
+ dismiss: 'Dismiss',
230
+ closeSheet: 'Close sheet',
231
+ sheetGrabber: 'Sheet grabber',
232
+ sheetResizeHint: 'Swipe up or down to resize',
233
+ sheetResizeOrCloseHint: 'Swipe up or down to resize or close',
234
+ navigationDrawer: 'Navigation drawer',
235
+ closeDrawer: 'Close drawer',
236
+ closeMenu: 'Close menu',
237
+ switchAccount: 'Switch account',
238
+ openActions: 'Open actions',
239
+ closeActions: 'Close actions',
240
+ action: 'Action',
241
+ loading: 'Loading',
242
+ clearSearch: 'Clear search',
243
+ cancelSearch: 'Cancel search',
244
+ clearField: (field) => `Clear ${field}`,
245
+ back: 'Back',
246
+ previous: 'Previous',
247
+ next: 'Next',
248
+ goToToday: 'Go to today',
249
+ goToSlide: (n) => `Go to slide ${n}`,
250
+ showAllCards: 'Shows all cards',
251
+ expandNotification: 'Expands the notification',
252
+ collapseNotification: 'Collapses the notification',
253
+ moreCount: (n) => `${n} more`,
254
+ peopleCount: (n) => `${n} people`,
255
+ namesAndMore: (names, n) => `${names} and ${n} more`,
256
+ newCount: (badge) => `${badge} new`,
257
+ remove: (item) => `Remove ${item}`,
258
+ selectedCount: (n) => `${n} selected`,
259
+ noneSelected: 'None selected',
260
+ notSet: 'Not set',
261
+ date: 'Date',
262
+ time: 'Time',
263
+ hour: 'Hour',
264
+ minute: 'Minute',
265
+ hours: (n) => `${n} hours`,
266
+ minutes: (n) => `${n} minutes`,
267
+ booked: 'booked',
268
+ unavailable: 'unavailable',
269
+ timeDialHint: 'Drag around the dial, or swipe up or down to adjust.',
270
+ flipCalendarHint: 'Swipe up to turn to the next day, down for the previous one.',
271
+ increase: 'Increase',
272
+ decrease: 'Decrease',
273
+ increaseItem: (item) => `Increase ${item}`,
274
+ decreaseItem: (item) => `Decrease ${item}`,
275
+ range: 'Range',
276
+ rangeMinimum: (name) => `${name} minimum`,
277
+ rangeMaximum: (name) => `${name} maximum`,
278
+ ratingStars: 'Rating, stars',
279
+ ratingHearts: 'Rating, hearts',
280
+ showPassword: 'Show password',
281
+ hidePassword: 'Hide password',
282
+ passwordStrength: (level) => `Password strength: ${level}`,
283
+ ruleMet: (rule) => `${rule}: done`,
284
+ ruleUnmet: (rule) => `${rule}: not yet`,
285
+ stepOf: (step, total, label) => `Step ${step} of ${total}, ${label}`,
286
+ allStepsComplete: 'All steps complete',
287
+ stepCurrent: 'current',
288
+ stepCompleted: 'completed',
289
+ stepNeedsAttention: 'needs attention',
290
+ stepNotStarted: 'not started',
291
+ goBackToStep: 'Goes back to this step',
292
+ activateCodeField: 'Activates the code field',
293
+ qrCodeFor: (value) => `QR code for ${value}`,
294
+ undoStroke: 'Undo last stroke',
295
+ signaturePad: 'Signature pad, draw your signature',
296
+ signaturePadSigned: 'Signature pad, signed',
297
+ flipToBack: 'Tap to flip to the back.',
298
+ flipToFront: 'Showing the back. Tap to flip to the front.',
299
+ revealHint: 'Double-tap to reveal',
300
+ confirmHint: 'Double tap to confirm',
301
+ prizeWheel: 'Prize wheel',
302
+ spinHint: 'Spins the wheel for a prize',
303
+ prizes: (n, list) => `${n} prizes: ${list}`,
304
+ orbReveal: 'Reveal',
305
+ orbBack: 'Glass orb, drag down to go back',
306
+ cancel: 'Cancel',
307
+ done: 'Done',
308
+ doneCount: (n) => `Done · ${n}`,
309
+ doneDays: (n) => `Done · ${n} days`,
310
+ clear: 'Clear',
311
+ continue: 'Continue',
312
+ delete: 'Delete',
313
+ failed: 'Failed',
314
+ search: 'Search',
315
+ noResults: 'No results',
316
+ noOptions: 'No options',
317
+ nothingMatches: (query) => `Nothing matches “${query}”.`,
318
+ selectPlaceholder: 'Select…',
319
+ selectDate: 'Select a date',
320
+ selectDates: 'Select dates',
321
+ pickStartDate: 'Pick a start date',
322
+ pickEndDate: 'pick an end date',
323
+ selectTime: 'Select a time',
324
+ setTime: (time) => `Set ${time}`,
325
+ am: 'AM',
326
+ pm: 'PM',
327
+ amPm: 'AM or PM',
328
+ morning: 'Morning',
329
+ afternoon: 'Afternoon',
330
+ evening: 'Evening',
331
+ slotsOpen: (n) => `${n} open`,
332
+ full: 'Full',
333
+ today: 'Today',
334
+ tomorrow: 'Tomorrow',
335
+ nextMonday: 'Next Monday',
336
+ inAWeek: 'In a week',
337
+ inAMonth: 'In a month',
338
+ next7Days: 'Next 7 days',
339
+ nextWeekend: 'Next weekend',
340
+ thisMonth: 'This month',
341
+ last7Days: 'Last 7 days',
342
+ last30Days: 'Last 30 days',
343
+ chooseOneOrMore: 'Choose one or more',
344
+ selectedSuffix: 'selected',
345
+ chooseMonth: 'choose month',
346
+ chooseYear: 'choose year',
347
+ backToDays: 'back to days',
348
+ dayAndWeek: (day, week) => `Day ${day} · Week ${week}`,
349
+ daysLeft: (n) => `${n} left`,
350
+ passwordLevels: ['Too short', 'Weak', 'Okay', 'Strong', 'Very strong'],
351
+ passwordsMatch: 'Passwords match',
352
+ passwordsDontMatch: "Passwords don't match",
353
+ showQrCode: 'Show QR Code',
354
+ copy: 'Copy',
355
+ copied: 'Copied',
356
+ slideToConfirm: 'Slide to confirm',
357
+ confirmed: 'Confirmed',
358
+ like: 'LIKE',
359
+ nope: 'NOPE',
360
+ swipeRight: 'Right',
361
+ swipeLeft: 'Left',
362
+ };
363
+
364
+ const light: Colors = {
365
+ background: '#ffffff',
366
+ foreground: '#09090b',
367
+ card: '#ffffff',
368
+ cardForeground: '#09090b',
369
+ popover: '#ffffff',
370
+ muted: '#f4f4f5',
371
+ mutedForeground: '#71717a',
372
+ border: '#e4e4e7',
373
+ input: '#d4d4d8',
374
+ primary: '#18181b',
375
+ primaryForeground: '#fafafa',
376
+ secondary: '#f4f4f5',
377
+ secondaryForeground: '#18181b',
378
+ accent: '#18181b',
379
+ accentForeground: '#ffffff',
380
+ success: '#16a34a',
381
+ warning: '#f59e0b',
382
+ danger: '#e5484d',
383
+ dangerForeground: '#ffffff',
384
+ ring: '#18181b',
385
+ overlay: 'rgba(0,0,0,0.4)',
386
+ };
387
+
388
+ const dark: Colors = {
389
+ background: '#09090b',
390
+ foreground: '#fafafa',
391
+ card: '#111113',
392
+ cardForeground: '#fafafa',
393
+ popover: '#18181b',
394
+ muted: '#1c1c20',
395
+ mutedForeground: '#a1a1aa',
396
+ border: '#27272a',
397
+ input: '#34343a',
398
+ primary: '#fafafa',
399
+ primaryForeground: '#18181b',
400
+ secondary: '#1f1f23',
401
+ secondaryForeground: '#fafafa',
402
+ accent: '#fafafa',
403
+ accentForeground: '#09090b',
404
+ success: '#3dd68c',
405
+ warning: '#ffb224',
406
+ danger: '#ff6369',
407
+ dangerForeground: '#ffffff',
408
+ ring: '#d4d4d8',
409
+ overlay: 'rgba(0,0,0,0.6)',
410
+ };
411
+
412
+ export const palettes = { light, dark };
413
+
414
+ function build(scheme: ColorScheme, overrides?: ThemeOverrides): Theme {
415
+ const base = scheme === 'dark' ? dark : light;
416
+ const accent = overrides?.accent;
417
+ const colors: Colors = {
418
+ ...base,
419
+ ...(accent ? { accent, ring: accent, accentForeground: readableOn(accent) } : null),
420
+ ...overrides?.colors?.[scheme],
421
+ };
422
+ const r = overrides?.radius ?? 12;
423
+ return {
424
+ dark: scheme === 'dark',
425
+ colors,
426
+ radius: {
427
+ sm: Math.round(r * 0.5),
428
+ md: r,
429
+ lg: Math.round(r * 1.35),
430
+ xl: Math.round(r * 1.8),
431
+ full: 999,
432
+ scale: (n) => Math.round((n * r) / 12),
433
+ capsule: (h) => Math.round((h / 2) * Math.min(1, r / 12)),
434
+ },
435
+ space: (n) => n * 4,
436
+ shadow: (level, color) => shadowFor(level, scheme === 'dark', color),
437
+ font: {
438
+ family: overrides?.fontFamily,
439
+ mono: Platform.select({ ios: 'Menlo', android: 'monospace', default: 'ui-monospace, SFMono-Regular, Menlo, monospace' })!,
440
+ },
441
+ text: {
442
+ xs: { fontSize: 12, lineHeight: 16 },
443
+ sm: { fontSize: 14, lineHeight: 20 },
444
+ md: { fontSize: 16, lineHeight: 22 },
445
+ lg: { fontSize: 18, lineHeight: 24, fontWeight: '400' },
446
+ xl: { fontSize: 22, lineHeight: 28, fontWeight: '400', letterSpacing: -0.3 },
447
+ xxl: { fontSize: 30, lineHeight: 36, fontWeight: '400', letterSpacing: -0.8 },
448
+ },
449
+ labels: overrides?.labels ? { ...defaultLabels, ...overrides.labels } : defaultLabels,
450
+ };
451
+ }
452
+
453
+ export interface ThemeOverrides {
454
+ /** Brand colour used for focus rings, selected states and accent buttons. */
455
+ accent?: string | undefined;
456
+
457
+ /** Base corner radius in points (default 12). */
458
+ radius?: number | undefined;
459
+
460
+ fontFamily?: string | undefined;
461
+
462
+ /** Override any token per scheme. */
463
+ colors?: Partial<Record<ColorScheme, Partial<Colors>>> | undefined;
464
+
465
+ /** Screen-reader strings in your language, e.g. { close: "बंद करें" }. Keys you leave out stay English. */
466
+ labels?: Partial<Labels> | undefined;
467
+ }
468
+
469
+ const KitContext = createContext<{ scheme?: ColorScheme | undefined; overrides?: ThemeOverrides | undefined } | null>(null);
470
+
471
+ export function KitProvider({
472
+ scheme,
473
+ children,
474
+ ...overrides
475
+ }: ThemeOverrides & { scheme?: ColorScheme | 'system'; children: React.ReactNode }) {
476
+ const value = useMemo(
477
+ () => ({ scheme: scheme === 'system' ? undefined : scheme, overrides }),
478
+ // eslint-disable-next-line react-hooks/exhaustive-deps
479
+ [scheme, overrides.accent, overrides.radius, overrides.fontFamily, overrides.colors, overrides.labels]
480
+ );
481
+ return <KitContext.Provider value={value}>{children}</KitContext.Provider>;
482
+ }
483
+
484
+ export function useKit(): Theme {
485
+ const ctx = useContext(KitContext);
486
+ const system = useColorScheme();
487
+ const scheme: ColorScheme = ctx?.scheme ?? (system === 'dark' ? 'dark' : 'light');
488
+ return useMemo(() => build(scheme, ctx?.overrides), [scheme, ctx?.overrides]);
489
+ }
490
+
491
+ export type ShadowLevel = 'none' | 'sm' | 'md' | 'lg' | 'xl';
492
+
493
+ const SHADOWS: Record<Exclude<ShadowLevel, 'none'>, [y: number, blur: number, opacity: number]> = {
494
+ sm: [1, 3, 0.08],
495
+ md: [4, 14, 0.1],
496
+ lg: [10, 30, 0.14],
497
+ xl: [18, 48, 0.2],
498
+ };
499
+
500
+ /**
501
+ * A shadow as `boxShadow`, which React Native (0.76+) draws identically on iOS, Android and web —
502
+ * unlike `elevation`, whose Android shadow looks nothing like the iOS one. Darker in dark mode so
503
+ * it still reads on near-black. `color` is a #rrggbb tint (default black).
504
+ */
505
+ export function shadowFor(level: ShadowLevel, dark: boolean, color = '#000000'): ViewStyle {
506
+ if (level === 'none') return {};
507
+ const [y, blur, opacity] = SHADOWS[level];
508
+ const a = Math.min(0.6, dark ? opacity * 2.8 : opacity);
509
+ const full = color.length === 4 ? '#' + [...color.slice(1)].map((c) => c + c).join('') : color;
510
+ const n = parseInt(full.slice(1, 7), 16);
511
+ const [r, g, b] = Number.isNaN(n) ? [0, 0, 0] : [(n >> 16) & 255, (n >> 8) & 255, n & 255];
512
+ return { boxShadow: `0px ${y}px ${blur}px rgba(${r},${g},${b},${+a.toFixed(3)})` };
513
+ }
514
+
515
+ /**
516
+ * Screen size classes, shared by every component so phones, foldables and tablets all get the
517
+ * right layout without any work from you. `compact` — phones (any orientation); `medium` — open
518
+ * foldables (Galaxy Z Fold, Pixel Fold), Surface Duo and small tablets; `expanded` — iPad and
519
+ * larger. Decided by the screen's short side, so a phone on its side stays a phone.
520
+ */
521
+ export type SizeClass = 'compact' | 'medium' | 'expanded';
522
+
523
+ export const breakpoints = { medium: 600, expanded: 840 };
524
+
525
+ /** Widest a sheet / picker / popover card and a screen's content column get on big screens. */
526
+ export const layoutLimits = { sheet: 560, content: 720, gutter: 24 };
527
+
528
+ export function sizeClassOf(shortSide: number): SizeClass {
529
+ if (!(shortSide >= breakpoints.medium)) return 'compact';
530
+ return shortSide >= breakpoints.expanded ? 'expanded' : 'medium';
531
+ }
532
+
533
+ export interface Layout {
534
+ width: number;
535
+ height: number;
536
+ size: SizeClass;
537
+ /** Foldable, tablet or desktop — anything bigger than a phone. */
538
+ wide: boolean;
539
+ landscape: boolean;
540
+ fontScale: number;
541
+ /** Width for a bottom sheet, picker or action sheet: the full screen on a portrait phone, a centred card on anything wider. */
542
+ sheetWidth: number;
543
+ /** Width for a screen's main column (forms, feeds, blocks): full on phones, a readable centred column on tablets. */
544
+ contentWidth: number;
545
+ /** Grid columns that fit the current width: 1 phone, 2 foldable / landscape phone, 3 tablet. */
546
+ columns: number;
547
+ /** A value per size class; a missing one falls back to the next smaller: `pick({ compact: 16, expanded: 24 })`. */
548
+ pick: <T>(values: { compact: T; medium?: T; expanded?: T }) => T;
549
+ }
550
+
551
+ const finite = (n: number) => (Number.isFinite(n) && n > 0 ? n : 0);
552
+
553
+ /** The layout for a window of `width` × `height` points (the pure part of useLayout). */
554
+ export function layoutFor(rawWidth: number, rawHeight: number, fontScale = 1): Layout {
555
+ const width = finite(rawWidth);
556
+ const height = finite(rawHeight);
557
+ const size = sizeClassOf(Math.min(width, height || width));
558
+ const roomy = width >= breakpoints.medium;
559
+ const { sheet, content, gutter } = layoutLimits;
560
+ return {
561
+ width,
562
+ height,
563
+ size,
564
+ wide: size !== 'compact',
565
+ landscape: width > height,
566
+ fontScale,
567
+ sheetWidth: roomy ? Math.min(sheet, width - gutter * 2) : width,
568
+ contentWidth: roomy ? Math.min(content, width - gutter * 2) : width,
569
+ columns: width >= breakpoints.expanded + 160 ? 3 : roomy ? 2 : 1,
570
+ pick: (values) =>
571
+ (size === 'expanded' ? values.expanded ?? values.medium : size === 'medium' ? values.medium : undefined) ??
572
+ values.compact,
573
+ };
574
+ }
575
+
576
+ /**
577
+ * The current screen's layout; re-renders on rotation, split view, Stage Manager and fold / unfold.
578
+ * Components use it so they look right on every device; use it in your own screens the same way:
579
+ * `const { contentWidth } = useLayout()` → `<View style={{ width: '100%', maxWidth: contentWidth, alignSelf: 'center' }}>`.
580
+ */
581
+ export function useLayout(): Layout {
582
+ const { width, height, fontScale } = useWindowDimensions();
583
+ return useMemo(() => layoutFor(width, height, fontScale), [width, height, fontScale]);
584
+ }
585
+
586
+ /**
587
+ * Shared motion presets so the whole kit moves the same way. Every spring is critically damped
588
+ * (damping ≥ 2·√(stiffness·mass)): it settles smoothly with no overshoot or wobble.
589
+ */
590
+ export const motion = {
591
+ /** Toggles, selection indicators. */
592
+ snappy: { damping: 30, stiffness: 260, mass: 0.8 },
593
+ /** Press feedback, pops. (Kept under its old name; it no longer bounces.) */
594
+ bouncy: { damping: 26, stiffness: 220, mass: 0.7 },
595
+ /** Sheets, layout changes. */
596
+ gentle: { damping: 27, stiffness: 160, mass: 1 },
597
+ fast: 160,
598
+ normal: 240,
599
+ };
600
+
601
+ /** Adds alpha to a #rrggbb colour: alpha('#6366f1', 0.2). */
602
+ export function alpha(hex: string, a: number) {
603
+ if (!hex.startsWith('#') || (hex.length !== 7 && hex.length !== 4)) return hex;
604
+ const full = hex.length === 4 ? '#' + [...hex.slice(1)].map((c) => c + c).join('') : hex;
605
+ return full + Math.round(Math.max(0, Math.min(1, a)) * 255).toString(16).padStart(2, '0');
606
+ }
607
+
608
+ /** Black or white, whichever reads better on `hex`. */
609
+ export function readableOn(hex: string) {
610
+ const full = hex.length === 4 ? '#' + [...hex.slice(1)].map((c) => c + c).join('') : hex;
611
+ const n = parseInt(full.slice(1, 7), 16);
612
+ if (Number.isNaN(n)) return '#ffffff';
613
+ const [r, g, b] = [(n >> 16) & 255, (n >> 8) & 255, n & 255];
614
+ return (r * 299 + g * 587 + b * 114) / 1000 > 150 ? '#09090b' : '#ffffff';
615
+ }
616
+
617
+ /**
618
+ * True for children that must be wrapped in <Text>: strings, numbers and arrays of them —
619
+ * `{count} new` is the array [3, ' new'], and raw text outside <Text> crashes on native.
620
+ */
621
+ export function isTextNode(node: React.ReactNode): node is string | number | (string | number)[] {
622
+ if (typeof node === 'string' || typeof node === 'number') return true;
623
+ return Array.isArray(node) && node.length > 0 && node.every((n) => typeof n === 'string' || typeof n === 'number');
624
+ }
625
+
626
+ /**
627
+ * A ref that always holds the latest `value`, for gesture and timer callbacks that must not go stale.
628
+ * Read `.current` only inside callbacks and effects, never while rendering.
629
+ */
630
+ export function useLatest<T>(value: T): React.MutableRefObject<T> {
631
+ const ref = useRef(value);
632
+ useLayoutEffect(() => {
633
+ ref.current = value;
634
+ });
635
+ return ref;
636
+ }
637
+
638
+ const panKeys = [
639
+ 'onStartShouldSetPanResponder',
640
+ 'onStartShouldSetPanResponderCapture',
641
+ 'onMoveShouldSetPanResponder',
642
+ 'onMoveShouldSetPanResponderCapture',
643
+ 'onPanResponderGrant',
644
+ 'onPanResponderStart',
645
+ 'onPanResponderMove',
646
+ 'onPanResponderEnd',
647
+ 'onPanResponderRelease',
648
+ 'onPanResponderReject',
649
+ 'onPanResponderTerminate',
650
+ ] as const;
651
+
652
+ /**
653
+ * PanResponder handlers that always call the latest `config`, created once. Write the callbacks inline:
654
+ * they may read refs and props freely, and nothing is recreated between renders, so a drag in progress
655
+ * is never cut off. Spread the result: `<View {...pan} />`.
656
+ */
657
+ export function usePanResponder(config: PanResponderCallbacks): GestureResponderHandlers {
658
+ const latest = useLatest(config);
659
+ const [handlers] = useState(() => {
660
+ const forward: Record<string, (...args: never[]) => unknown> = {};
661
+ for (const key of panKeys) {
662
+ forward[key] = (...args) => (latest.current[key] as ((...a: never[]) => unknown) | undefined)?.(...args);
663
+ }
664
+ // React Native's defaults when these are left out are `true`, so keep them when the config has none.
665
+ forward.onPanResponderTerminationRequest = (...args) =>
666
+ (latest.current.onPanResponderTerminationRequest as ((...a: never[]) => boolean) | undefined)?.(...args) ?? true;
667
+ forward.onShouldBlockNativeResponder = (...args) =>
668
+ (latest.current.onShouldBlockNativeResponder as ((...a: never[]) => boolean) | undefined)?.(...args) ?? true;
669
+ return PanResponder.create(forward as PanResponderCallbacks).panHandlers;
670
+ });
671
+ return handlers;
672
+ }
@@ -0,0 +1,24 @@
1
+ {
2
+ "name": "flip-calendar",
3
+ "type": "component",
4
+ "title": "Flip Calendar",
5
+ "description": "A date picker that's a top-bound desk calendar: lift a page and it turns up over the rings toward you in 3D, its plain back showing once it passes upright, with the next day underneath; pull down and yesterday's page swings back over the top onto the pad. The page stays under your finger the whole way, a flick finishes the turn and a hard flick turns several days. Each page has a month band, a huge day number (Sundays in the accent), the weekday and a day-of-year / week footer.",
6
+ "tier": "free",
7
+ "aliases": [
8
+ "flip-date-picker",
9
+ "desk-calendar",
10
+ "daily-calendar",
11
+ "page-calendar",
12
+ "tear-off-calendar"
13
+ ],
14
+ "dependencies": [
15
+ "expo-haptics"
16
+ ],
17
+ "registryDependencies": [
18
+ "kit"
19
+ ],
20
+ "sharedFiles": [
21
+ "lib/kit.tsx"
22
+ ],
23
+ "filesDir": "flip-calendar/files"
24
+ }