@expokit/cli 0.3.0 → 0.4.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 (165) hide show
  1. package/README.md +8 -4
  2. package/bin/expo-kit.js +137 -24
  3. package/bin/lib.mjs +110 -1
  4. package/bin/mcp.mjs +40 -18
  5. package/package.json +2 -2
  6. package/registry/_docs/assistant-app.json +47 -0
  7. package/registry/_docs/avatar.json +1 -1
  8. package/registry/_docs/badge.json +1 -1
  9. package/registry/_docs/bottom-nav.json +6 -6
  10. package/registry/_docs/button-3d.json +2 -2
  11. package/registry/_docs/calendar.json +5 -5
  12. package/registry/_docs/checkout.json +7 -1
  13. package/registry/_docs/confetti.json +3 -3
  14. package/registry/_docs/date-picker.json +4 -4
  15. package/registry/_docs/donut-shop.json +88 -0
  16. package/registry/_docs/drawer.json +4 -4
  17. package/registry/_docs/fintech-app.json +49 -0
  18. package/registry/_docs/floating-input.json +5 -5
  19. package/registry/_docs/food-app.json +47 -0
  20. package/registry/_docs/glass.json +1 -1
  21. package/registry/_docs/grain.json +80 -0
  22. package/registry/_docs/morphing-button-group.json +96 -0
  23. package/registry/_docs/orb-reveal.json +132 -0
  24. package/registry/_docs/otp-input.json +4 -4
  25. package/registry/_docs/password-input.json +3 -3
  26. package/registry/_docs/qr-code-button.json +122 -0
  27. package/registry/_docs/search-bar.json +3 -3
  28. package/registry/_docs/select.json +7 -7
  29. package/registry/_docs/settings.json +6 -0
  30. package/registry/_docs/skeleton.json +2 -2
  31. package/registry/_docs/slider.json +6 -6
  32. package/registry/_docs/spinner.json +1 -1
  33. package/registry/_docs/stepper.json +7 -7
  34. package/registry/_docs/textarea.json +2 -2
  35. package/registry/_docs/ticket-shape.json +1 -1
  36. package/registry/_docs/tilt-card.json +4 -4
  37. package/registry/_docs/time-picker.json +6 -6
  38. package/registry/_docs/timeline.json +2 -2
  39. package/registry/_docs/toast.json +8 -8
  40. package/registry/accordion/files/src/components/ui/accordion.tsx +5 -4
  41. package/registry/accordion/files/src/lib/kit.tsx +8 -5
  42. package/registry/action-sheet/files/src/components/ui/action-sheet.tsx +9 -8
  43. package/registry/action-sheet/files/src/lib/kit.tsx +8 -5
  44. package/registry/alert/files/src/components/ui/alert.tsx +7 -4
  45. package/registry/alert/files/src/components/ui/button.tsx +4 -3
  46. package/registry/alert/files/src/lib/kit.tsx +8 -5
  47. package/registry/assistant-app.json +49 -0
  48. package/registry/avatar/files/src/components/ui/avatar.tsx +7 -4
  49. package/registry/avatar/files/src/lib/kit.tsx +8 -5
  50. package/registry/badge/files/src/components/ui/badge.tsx +21 -7
  51. package/registry/badge/files/src/lib/kit.tsx +8 -5
  52. package/registry/bottom-nav/files/src/components/ui/bottom-nav.tsx +14 -7
  53. package/registry/bottom-nav/files/src/lib/kit.tsx +8 -5
  54. package/registry/bottom-sheet/files/src/components/ui/bottom-sheet.tsx +7 -6
  55. package/registry/bottom-sheet/files/src/lib/kit.tsx +8 -5
  56. package/registry/button/files/src/components/ui/button.tsx +4 -3
  57. package/registry/button/files/src/lib/kit.tsx +8 -5
  58. package/registry/button-3d/files/src/components/ui/button-3d.tsx +3 -2
  59. package/registry/button-3d/files/src/lib/kit.tsx +8 -5
  60. package/registry/calendar/files/src/components/ui/calendar.tsx +9 -5
  61. package/registry/calendar/files/src/lib/kit.tsx +8 -5
  62. package/registry/card/files/src/components/ui/card.tsx +8 -6
  63. package/registry/card/files/src/lib/kit.tsx +8 -5
  64. package/registry/checkbox/files/src/components/ui/checkbox.tsx +6 -5
  65. package/registry/checkbox/files/src/lib/kit.tsx +8 -5
  66. package/registry/chip-group/files/src/components/ui/chip-group.tsx +4 -2
  67. package/registry/chip-group/files/src/lib/kit.tsx +8 -5
  68. package/registry/confetti/files/src/lib/kit.tsx +8 -5
  69. package/registry/coupon/files/src/components/ui/coupon.tsx +23 -20
  70. package/registry/coupon/files/src/components/ui/flip-card.tsx +1 -1
  71. package/registry/coupon/files/src/components/ui/tilt-card.tsx +2 -2
  72. package/registry/coupon/files/src/lib/kit.tsx +8 -5
  73. package/registry/date-picker/files/src/components/ui/calendar.tsx +9 -5
  74. package/registry/date-picker/files/src/components/ui/date-picker.tsx +2 -2
  75. package/registry/date-picker/files/src/lib/kit.tsx +8 -5
  76. package/registry/dialog/files/src/components/ui/dialog.tsx +12 -6
  77. package/registry/dialog/files/src/lib/kit.tsx +8 -5
  78. package/registry/donut-shop.json +18 -0
  79. package/registry/drawer/files/src/components/ui/drawer.tsx +25 -19
  80. package/registry/drawer/files/src/lib/kit.tsx +8 -5
  81. package/registry/fintech-app.json +57 -0
  82. package/registry/flip-card/files/src/components/ui/flip-card.tsx +1 -1
  83. package/registry/flip-card/files/src/lib/kit.tsx +8 -5
  84. package/registry/floating-input/files/src/components/ui/floating-input.tsx +16 -6
  85. package/registry/floating-input/files/src/lib/kit.tsx +8 -5
  86. package/registry/food-app.json +55 -0
  87. package/registry/grain/files/src/components/ui/grain.tsx +113 -0
  88. package/registry/{tag-input → grain}/files/src/lib/kit.tsx +8 -5
  89. package/registry/grain.json +17 -0
  90. package/registry/kit/files/src/lib/kit.tsx +8 -5
  91. package/registry/liquidglass-tab/files/src/components/GlassTabBar.tsx +1 -1
  92. package/registry/liquidglass-tab/files/src/lib/tab-bar.ts +1 -1
  93. package/registry/list-item/files/src/components/ui/list-item.tsx +20 -16
  94. package/registry/list-item/files/src/lib/kit.tsx +8 -5
  95. package/registry/morphing-button-group.json +18 -0
  96. package/registry/orb-reveal.json +20 -0
  97. package/registry/otp-input/files/src/components/ui/otp-input.tsx +8 -6
  98. package/registry/otp-input/files/src/lib/kit.tsx +8 -5
  99. package/registry/password-input/files/src/components/ui/floating-input.tsx +16 -6
  100. package/registry/password-input/files/src/components/ui/password-input.tsx +2 -2
  101. package/registry/password-input/files/src/lib/kit.tsx +8 -5
  102. package/registry/payment-card/files/src/components/ui/flip-card.tsx +1 -1
  103. package/registry/payment-card/files/src/components/ui/payment-card.tsx +27 -14
  104. package/registry/payment-card/files/src/components/ui/tilt-card.tsx +2 -2
  105. package/registry/payment-card/files/src/lib/kit.tsx +8 -5
  106. package/registry/progress/files/src/components/ui/progress.tsx +8 -5
  107. package/registry/progress/files/src/lib/kit.tsx +8 -5
  108. package/registry/progress-steps/files/src/components/ui/progress-steps.tsx +15 -11
  109. package/registry/progress-steps/files/src/lib/kit.tsx +8 -5
  110. package/registry/qr-code-button.json +18 -0
  111. package/registry/radio-group/files/src/components/ui/radio-group.tsx +10 -9
  112. package/registry/radio-group/files/src/lib/kit.tsx +8 -5
  113. package/registry/search-bar/files/src/components/ui/search-bar.tsx +13 -11
  114. package/registry/search-bar/files/src/lib/kit.tsx +8 -5
  115. package/registry/segmented-control/files/src/components/ui/segmented-control.tsx +4 -2
  116. package/registry/segmented-control/files/src/lib/kit.tsx +8 -5
  117. package/registry/select/files/src/components/ui/select.tsx +3 -3
  118. package/registry/select/files/src/lib/kit.tsx +8 -5
  119. package/registry/skeleton/files/src/components/ui/skeleton.tsx +4 -4
  120. package/registry/skeleton/files/src/lib/kit.tsx +8 -5
  121. package/registry/slider/files/src/components/ui/slider.tsx +5 -4
  122. package/registry/slider/files/src/lib/kit.tsx +8 -5
  123. package/registry/spinner/files/src/components/ui/spinner.tsx +17 -12
  124. package/registry/spinner/files/src/lib/kit.tsx +8 -5
  125. package/registry/stateful-button/files/src/lib/kit.tsx +8 -5
  126. package/registry/stepper/files/src/components/ui/stepper.tsx +8 -4
  127. package/registry/stepper/files/src/lib/kit.tsx +8 -5
  128. package/registry/switch/files/src/components/ui/switch.tsx +4 -3
  129. package/registry/switch/files/src/lib/kit.tsx +8 -5
  130. package/registry/tabs/files/src/components/ui/tabs.tsx +10 -8
  131. package/registry/tabs/files/src/lib/kit.tsx +8 -5
  132. package/registry/text-animation/files/src/components/ui/text-animation.tsx +4 -4
  133. package/registry/text-animation/files/src/lib/kit.tsx +8 -5
  134. package/registry/textarea/files/src/components/ui/textarea.tsx +11 -9
  135. package/registry/textarea/files/src/lib/kit.tsx +8 -5
  136. package/registry/ticket-shape/files/src/lib/kit.tsx +8 -5
  137. package/registry/tilt-card/files/src/components/ui/tilt-card.tsx +2 -2
  138. package/registry/tilt-card/files/src/lib/kit.tsx +8 -5
  139. package/registry/time-picker/files/src/components/ui/time-picker.tsx +2 -2
  140. package/registry/time-picker/files/src/lib/kit.tsx +8 -5
  141. package/registry/timeline/files/src/components/ui/avatar.tsx +7 -4
  142. package/registry/timeline/files/src/components/ui/timeline.tsx +12 -10
  143. package/registry/timeline/files/src/lib/kit.tsx +8 -5
  144. package/registry/toast/files/src/components/ui/button.tsx +4 -3
  145. package/registry/toast/files/src/components/ui/toast.tsx +13 -11
  146. package/registry/toast/files/src/lib/kit.tsx +8 -5
  147. package/schema/components.schema.json +1 -1
  148. package/registry/_docs/color-swatch.json +0 -82
  149. package/registry/_docs/kpi-card.json +0 -14
  150. package/registry/_docs/like-button.json +0 -92
  151. package/registry/_docs/morph-button.json +0 -128
  152. package/registry/_docs/pagination-dots.json +0 -108
  153. package/registry/_docs/shimmer-button.json +0 -98
  154. package/registry/_docs/tag-input.json +0 -148
  155. package/registry/color-swatch.json +0 -17
  156. package/registry/kpi-card/files/src/components/charts/kpi-card.tsx +0 -45
  157. package/registry/kpi-card.json +0 -14
  158. package/registry/like-button.json +0 -18
  159. package/registry/morph-button.json +0 -18
  160. package/registry/pagination-dots/files/src/components/ui/pagination-dots.tsx +0 -262
  161. package/registry/pagination-dots/files/src/lib/kit.tsx +0 -219
  162. package/registry/pagination-dots.json +0 -15
  163. package/registry/shimmer-button.json +0 -17
  164. package/registry/tag-input/files/src/components/ui/tag-input.tsx +0 -720
  165. package/registry/tag-input.json +0 -17
@@ -170,12 +170,12 @@
170
170
  {
171
171
  "name": "SignUp",
172
172
  "title": "Sign Up",
173
- "code": "import React, { useState } from 'react';\nimport { Pressable, StyleSheet, Text, View } from 'react-native';\nimport { FloatingInput } from '@/components/ui/floating-input';\nimport { readableOn, useKit } from '@/lib/kit';\n\nfunction Card({ title, subtitle, children }: { title: string; subtitle?: string; children: React.ReactNode }) {\n const { colors, radius } = useKit();\n return (\n <View\n style={{\n padding: 16,\n gap: 12,\n borderRadius: radius.xl,\n borderWidth: StyleSheet.hairlineWidth,\n borderColor: colors.border,\n backgroundColor: colors.card,\n }}\n >\n <View style={{ gap: 2, marginBottom: 2 }}>\n <Text style={{ fontSize: 16, fontWeight: '400', color: colors.foreground }}>{title}</Text>\n {subtitle ? <Text style={{ fontSize: 13, color: colors.mutedForeground }}>{subtitle}</Text> : null}\n </View>\n {children}\n </View>\n );\n}\n\nconst isEmail = (v: string) => /^\\S+@\\S+\\.\\S{2,}$/.test(v);\n\nexport default function SignUp() {\n const { colors } = useKit();\n const [name, setName] = useState('Priya Sharma');\n const [email, setEmail] = useState('priya@');\n const [phone, setPhone] = useState('');\n const emailError = email.length > 0 && !isEmail(email) ? 'Enter a valid email address' : undefined;\n return (\n <Card title=\"Create your account\" subtitle=\"It takes less than a minute.\">\n <FloatingInput label=\"Full name\" value={name} onChangeText={setName} autoComplete=\"name\" clearable />\n <FloatingInput\n label=\"Email\"\n value={email}\n onChangeText={setEmail}\n keyboardType=\"email-address\"\n autoCapitalize=\"none\"\n autoCorrect={false}\n error={emailError}\n success={isEmail(email) ? 'Looks good' : undefined}\n />\n <FloatingInput\n label=\"Mobile number\"\n prefix=\"+91\"\n value={phone}\n onChangeText={(v) => setPhone(v.replace(/\\D/g, ''))}\n keyboardType=\"phone-pad\"\n maxLength={10}\n success={phone.length === 10}\n helper=\"We'll text you a 6-digit code\"\n />\n <Text style={{ fontSize: 13, color: colors.mutedForeground }}>\n Already have an account? <Text style={{ fontWeight: '400', color: colors.accent }}>Sign in</Text>\n </Text>\n </Card>\n );\n}\n"
173
+ "code": "import React, { useState } from 'react';\nimport { Pressable, StyleSheet, Text, View } from 'react-native';\nimport { FloatingInput } from '@/components/ui/floating-input';\nimport { readableOn, useKit } from '@/lib/kit';\n\n/** From the Expo Go viewer: `screen` = this IS the phone screen, so no mock frame; `bottomInset` = home indicator. */\ntype DemoProps = { screen?: boolean; bottomInset?: number };\n\nfunction Card({ title, subtitle, screen, children }: { title: string; subtitle?: string; screen?: boolean; children: React.ReactNode }) {\n const { colors, radius } = useKit();\n return (\n <View\n style={[\n { padding: 16, gap: 12 },\n screen\n ? { flex: 1, backgroundColor: colors.background }\n : { borderRadius: radius.xl, borderWidth: StyleSheet.hairlineWidth, borderColor: colors.border, backgroundColor: colors.card },\n ]}\n >\n <View style={{ gap: 2, marginBottom: 2 }}>\n <Text style={{ fontSize: 16, fontWeight: '400', color: colors.foreground }}>{title}</Text>\n {subtitle ? <Text style={{ fontSize: 13, color: colors.mutedForeground }}>{subtitle}</Text> : null}\n </View>\n {children}\n </View>\n );\n}\n\nconst isEmail = (v: string) => /^\\S+@\\S+\\.\\S{2,}$/.test(v);\n\nexport default function SignUp({ screen }: DemoProps) {\n const { colors } = useKit();\n const [name, setName] = useState('Priya Sharma');\n const [email, setEmail] = useState('priya@');\n const [phone, setPhone] = useState('');\n const emailError = email.length > 0 && !isEmail(email) ? 'Enter a valid email address' : undefined;\n return (\n <Card title=\"Create your account\" subtitle=\"It takes less than a minute.\" screen={screen}>\n <FloatingInput label=\"Full name\" value={name} onChangeText={setName} autoComplete=\"name\" clearable />\n <FloatingInput\n label=\"Email\"\n value={email}\n onChangeText={setEmail}\n keyboardType=\"email-address\"\n autoCapitalize=\"none\"\n autoCorrect={false}\n error={emailError}\n success={isEmail(email) ? 'Looks good' : undefined}\n />\n <FloatingInput\n label=\"Mobile number\"\n prefix=\"+91\"\n value={phone}\n onChangeText={(v) => setPhone(v.replace(/\\D/g, ''))}\n keyboardType=\"phone-pad\"\n maxLength={10}\n success={phone.length === 10}\n helper=\"We'll text you a 6-digit code\"\n />\n <Text style={{ fontSize: 13, color: colors.mutedForeground }}>\n Already have an account? <Text style={{ fontWeight: '400', color: colors.accent }}>Sign in</Text>\n </Text>\n </Card>\n );\n}\n"
174
174
  },
175
175
  {
176
176
  "name": "Payment",
177
177
  "title": "Payment",
178
- "code": "import React, { useState } from 'react';\nimport { Pressable, StyleSheet, Text, View } from 'react-native';\nimport { FloatingInput } from '@/components/ui/floating-input';\nimport { readableOn, useKit } from '@/lib/kit';\n\nfunction Card({ title, subtitle, children }: { title: string; subtitle?: string; children: React.ReactNode }) {\n const { colors, radius } = useKit();\n return (\n <View\n style={{\n padding: 16,\n gap: 12,\n borderRadius: radius.xl,\n borderWidth: StyleSheet.hairlineWidth,\n borderColor: colors.border,\n backgroundColor: colors.card,\n }}\n >\n <View style={{ gap: 2, marginBottom: 2 }}>\n <Text style={{ fontSize: 16, fontWeight: '400', color: colors.foreground }}>{title}</Text>\n {subtitle ? <Text style={{ fontSize: 13, color: colors.mutedForeground }}>{subtitle}</Text> : null}\n </View>\n {children}\n </View>\n );\n}\n\nconst formatCard = (v: string) =>\n v\n .replace(/\\D/g, '')\n .slice(0, 16)\n .replace(/(\\d{4})(?=\\d)/g, '$1 ');\n\nconst formatExpiry = (v: string) => {\n const d = v.replace(/\\D/g, '').slice(0, 4);\n return d.length > 2 ? `${d.slice(0, 2)}/${d.slice(2)}` : d;\n};\n\nconst cardBrand = (v: string) => {\n const d = v.replace(/\\D/g, '');\n if (/^4/.test(d)) return 'VISA';\n if (/^(5[1-5]|2[2-7])/.test(d)) return 'MC';\n if (/^3[47]/.test(d)) return 'AMEX';\n return undefined;\n};\n\n/** '' = not finished yet, otherwise an error message or null when valid. */\nconst expiryError = (v: string): string | null | '' => {\n if (v.length < 5) return '';\n const month = Number(v.slice(0, 2));\n const year = 2000 + Number(v.slice(3));\n if (month < 1 || month > 12) return 'Month must be 01–12';\n const now = new Date();\n if (year < now.getFullYear() || (year === now.getFullYear() && month < now.getMonth() + 1)) return 'This card has expired';\n return null;\n};\n\nconst rupees = (n: number) => {\n const [int, frac] = n.toFixed(2).split('.');\n return `₹${int.replace(/(\\d)(?=(\\d\\d)+\\d$)/g, '$1,')}${frac === '00' ? '' : `.${frac}`}`;\n};\n\nexport default function Payment() {\n const { colors, radius } = useKit();\n const [amount, setAmount] = useState('1299');\n const [card, setCard] = useState('4242 4242 4242 4242');\n const [expiry, setExpiry] = useState('');\n const [cvc, setCvc] = useState('');\n const [paid, setPaid] = useState(false);\n const brand = cardBrand(card);\n const cvcLen = brand === 'AMEX' ? 4 : 3;\n const expErr = expiryError(expiry);\n const ready = Number(amount) > 0 && card.length >= 18 && expErr === null && cvc.length === cvcLen;\n return (\n <Card title=\"Add funds\" subtitle=\"Charged securely — you can cancel anytime.\">\n <FloatingInput label=\"Amount\" prefix=\"₹\" suffix=\"INR\" keyboardType=\"decimal-pad\" value={amount} onChangeText={setAmount} />\n <FloatingInput\n label=\"Card number\"\n keyboardType=\"number-pad\"\n autoComplete=\"cc-number\"\n value={card}\n onChangeText={(v) => setCard(formatCard(v))}\n maxLength={19}\n suffix={brand}\n success={card.length === 19}\n />\n <View style={{ flexDirection: 'row', gap: 10, alignItems: 'flex-start' }}>\n <FloatingInput\n label=\"MM/YY\"\n keyboardType=\"number-pad\"\n autoComplete=\"cc-exp\"\n value={expiry}\n onChangeText={(v) => setExpiry(formatExpiry(v))}\n maxLength={5}\n error={expErr || undefined}\n success={expErr === null}\n style={{ flex: 1 }}\n />\n <FloatingInput\n label=\"CVC\"\n keyboardType=\"number-pad\"\n autoComplete=\"cc-csc\"\n secureTextEntry\n value={cvc}\n onChangeText={(v) => setCvc(v.replace(/\\D/g, '').slice(0, cvcLen))}\n maxLength={cvcLen}\n helper={`${cvcLen} digits on the ${brand === 'AMEX' ? 'front' : 'back'}`}\n style={{ flex: 1 }}\n />\n </View>\n <Pressable\n disabled={!ready}\n onPress={() => setPaid(true)}\n accessibilityRole=\"button\"\n accessibilityState={{ disabled: !ready }}\n style={({ pressed }) => ({\n height: 50,\n marginTop: 4,\n borderRadius: radius.md,\n alignItems: 'center',\n justifyContent: 'center',\n backgroundColor: ready ? colors.accent : colors.muted,\n opacity: pressed ? 0.85 : 1,\n })}\n >\n <Text style={{ fontSize: 16, fontWeight: '400', color: ready ? readableOn(colors.accent) : colors.mutedForeground }}>\n {paid ? 'Paid ✓' : `Pay ${rupees(Number(amount) || 0)}`}\n </Text>\n </Pressable>\n </Card>\n );\n}\n"
178
+ "code": "import React, { useState } from 'react';\nimport { Pressable, StyleSheet, Text, View } from 'react-native';\nimport { FloatingInput } from '@/components/ui/floating-input';\nimport { readableOn, useKit } from '@/lib/kit';\n\n/** From the Expo Go viewer: `screen` = this IS the phone screen, so no mock frame; `bottomInset` = home indicator. */\ntype DemoProps = { screen?: boolean; bottomInset?: number };\n\nfunction Card({ title, subtitle, screen, children }: { title: string; subtitle?: string; screen?: boolean; children: React.ReactNode }) {\n const { colors, radius } = useKit();\n return (\n <View\n style={[\n { padding: 16, gap: 12 },\n screen\n ? { flex: 1, backgroundColor: colors.background }\n : { borderRadius: radius.xl, borderWidth: StyleSheet.hairlineWidth, borderColor: colors.border, backgroundColor: colors.card },\n ]}\n >\n <View style={{ gap: 2, marginBottom: 2 }}>\n <Text style={{ fontSize: 16, fontWeight: '400', color: colors.foreground }}>{title}</Text>\n {subtitle ? <Text style={{ fontSize: 13, color: colors.mutedForeground }}>{subtitle}</Text> : null}\n </View>\n {children}\n </View>\n );\n}\n\nconst formatCard = (v: string) =>\n v\n .replace(/\\D/g, '')\n .slice(0, 16)\n .replace(/(\\d{4})(?=\\d)/g, '$1 ');\n\nconst formatExpiry = (v: string) => {\n const d = v.replace(/\\D/g, '').slice(0, 4);\n return d.length > 2 ? `${d.slice(0, 2)}/${d.slice(2)}` : d;\n};\n\nconst cardBrand = (v: string) => {\n const d = v.replace(/\\D/g, '');\n if (/^4/.test(d)) return 'VISA';\n if (/^(5[1-5]|2[2-7])/.test(d)) return 'MC';\n if (/^3[47]/.test(d)) return 'AMEX';\n return undefined;\n};\n\n/** '' = not finished yet, otherwise an error message or null when valid. */\nconst expiryError = (v: string): string | null | '' => {\n if (v.length < 5) return '';\n const month = Number(v.slice(0, 2));\n const year = 2000 + Number(v.slice(3));\n if (month < 1 || month > 12) return 'Month must be 01–12';\n const now = new Date();\n if (year < now.getFullYear() || (year === now.getFullYear() && month < now.getMonth() + 1)) return 'This card has expired';\n return null;\n};\n\nconst rupees = (n: number) => {\n const [int, frac] = n.toFixed(2).split('.');\n return `₹${int.replace(/(\\d)(?=(\\d\\d)+\\d$)/g, '$1,')}${frac === '00' ? '' : `.${frac}`}`;\n};\n\nexport default function Payment({ screen }: DemoProps) {\n const { colors, radius } = useKit();\n const [amount, setAmount] = useState('1299');\n const [card, setCard] = useState('4242 4242 4242 4242');\n const [expiry, setExpiry] = useState('');\n const [cvc, setCvc] = useState('');\n const [paid, setPaid] = useState(false);\n const brand = cardBrand(card);\n const cvcLen = brand === 'AMEX' ? 4 : 3;\n const expErr = expiryError(expiry);\n const ready = Number(amount) > 0 && card.length >= 18 && expErr === null && cvc.length === cvcLen;\n return (\n <Card title=\"Add funds\" subtitle=\"Charged securely — you can cancel anytime.\" screen={screen}>\n <FloatingInput label=\"Amount\" prefix=\"₹\" suffix=\"INR\" keyboardType=\"decimal-pad\" value={amount} onChangeText={setAmount} />\n <FloatingInput\n label=\"Card number\"\n keyboardType=\"number-pad\"\n autoComplete=\"cc-number\"\n value={card}\n onChangeText={(v) => setCard(formatCard(v))}\n maxLength={19}\n suffix={brand}\n success={card.length === 19}\n />\n <View style={{ flexDirection: 'row', gap: 10, alignItems: 'flex-start' }}>\n <FloatingInput\n label=\"MM/YY\"\n keyboardType=\"number-pad\"\n autoComplete=\"cc-exp\"\n value={expiry}\n onChangeText={(v) => setExpiry(formatExpiry(v))}\n maxLength={5}\n error={expErr || undefined}\n success={expErr === null}\n style={{ flex: 1 }}\n />\n <FloatingInput\n label=\"CVC\"\n keyboardType=\"number-pad\"\n autoComplete=\"cc-csc\"\n secureTextEntry\n value={cvc}\n onChangeText={(v) => setCvc(v.replace(/\\D/g, '').slice(0, cvcLen))}\n maxLength={cvcLen}\n helper={`${cvcLen} digits on the ${brand === 'AMEX' ? 'front' : 'back'}`}\n style={{ flex: 1 }}\n />\n </View>\n <Pressable\n disabled={!ready}\n onPress={() => setPaid(true)}\n accessibilityRole=\"button\"\n accessibilityState={{ disabled: !ready }}\n style={({ pressed }) => ({\n height: 50,\n marginTop: 4,\n borderRadius: radius.md,\n alignItems: 'center',\n justifyContent: 'center',\n backgroundColor: ready ? colors.accent : colors.muted,\n opacity: pressed ? 0.85 : 1,\n })}\n >\n <Text style={{ fontSize: 16, fontWeight: '400', color: ready ? readableOn(colors.accent) : colors.mutedForeground }}>\n {paid ? 'Paid ✓' : `Pay ${rupees(Number(amount) || 0)}`}\n </Text>\n </Pressable>\n </Card>\n );\n}\n"
179
179
  },
180
180
  {
181
181
  "name": "Variants",
@@ -190,7 +190,7 @@
190
190
  {
191
191
  "name": "Website",
192
192
  "title": "Website",
193
- "code": "import React, { useState } from 'react';\nimport { Pressable, StyleSheet, Text, View } from 'react-native';\nimport { FloatingInput } from '@/components/ui/floating-input';\nimport { readableOn, useKit } from '@/lib/kit';\n\nfunction Card({ title, subtitle, children }: { title: string; subtitle?: string; children: React.ReactNode }) {\n const { colors, radius } = useKit();\n return (\n <View\n style={{\n padding: 16,\n gap: 12,\n borderRadius: radius.xl,\n borderWidth: StyleSheet.hairlineWidth,\n borderColor: colors.border,\n backgroundColor: colors.card,\n }}\n >\n <View style={{ gap: 2, marginBottom: 2 }}>\n <Text style={{ fontSize: 16, fontWeight: '400', color: colors.foreground }}>{title}</Text>\n {subtitle ? <Text style={{ fontSize: 13, color: colors.mutedForeground }}>{subtitle}</Text> : null}\n </View>\n {children}\n </View>\n );\n}\n\nexport default function Website() {\n const [slug, setSlug] = useState('');\n return (\n <Card title=\"Your public page\">\n <FloatingInput\n label=\"Website\"\n prefix=\"https://\"\n keyboardType=\"url\"\n autoCapitalize=\"none\"\n autoCorrect={false}\n clearable\n />\n <FloatingInput\n label=\"Handle\"\n prefix=\"@\"\n suffix=\".expo.page\"\n value={slug}\n onChangeText={(v) => setSlug(v.toLowerCase().replace(/[^a-z0-9-]/g, ''))}\n autoCapitalize=\"none\"\n maxLength={20}\n counter\n helper=\"Letters, numbers and dashes\"\n />\n </Card>\n );\n}\n"
193
+ "code": "import React, { useState } from 'react';\nimport { Pressable, StyleSheet, Text, View } from 'react-native';\nimport { FloatingInput } from '@/components/ui/floating-input';\nimport { readableOn, useKit } from '@/lib/kit';\n\n/** From the Expo Go viewer: `screen` = this IS the phone screen, so no mock frame; `bottomInset` = home indicator. */\ntype DemoProps = { screen?: boolean; bottomInset?: number };\n\nfunction Card({ title, subtitle, screen, children }: { title: string; subtitle?: string; screen?: boolean; children: React.ReactNode }) {\n const { colors, radius } = useKit();\n return (\n <View\n style={[\n { padding: 16, gap: 12 },\n screen\n ? { flex: 1, backgroundColor: colors.background }\n : { borderRadius: radius.xl, borderWidth: StyleSheet.hairlineWidth, borderColor: colors.border, backgroundColor: colors.card },\n ]}\n >\n <View style={{ gap: 2, marginBottom: 2 }}>\n <Text style={{ fontSize: 16, fontWeight: '400', color: colors.foreground }}>{title}</Text>\n {subtitle ? <Text style={{ fontSize: 13, color: colors.mutedForeground }}>{subtitle}</Text> : null}\n </View>\n {children}\n </View>\n );\n}\n\nexport default function Website({ screen }: DemoProps) {\n const [slug, setSlug] = useState('');\n return (\n <Card title=\"Your public page\" screen={screen}>\n <FloatingInput\n label=\"Website\"\n prefix=\"https://\"\n keyboardType=\"url\"\n autoCapitalize=\"none\"\n autoCorrect={false}\n clearable\n />\n <FloatingInput\n label=\"Handle\"\n prefix=\"@\"\n suffix=\".expo.page\"\n value={slug}\n onChangeText={(v) => setSlug(v.toLowerCase().replace(/[^a-z0-9-]/g, ''))}\n autoCapitalize=\"none\"\n maxLength={20}\n counter\n helper=\"Letters, numbers and dashes\"\n />\n </Card>\n );\n}\n"
194
194
  },
195
195
  {
196
196
  "name": "Sizes",
@@ -200,12 +200,12 @@
200
200
  {
201
201
  "name": "LabelAbove",
202
202
  "title": "Label Above",
203
- "code": "import React, { useState } from 'react';\nimport { Pressable, StyleSheet, Text, View } from 'react-native';\nimport { FloatingInput } from '@/components/ui/floating-input';\nimport { readableOn, useKit } from '@/lib/kit';\n\nfunction Card({ title, subtitle, children }: { title: string; subtitle?: string; children: React.ReactNode }) {\n const { colors, radius } = useKit();\n return (\n <View\n style={{\n padding: 16,\n gap: 12,\n borderRadius: radius.xl,\n borderWidth: StyleSheet.hairlineWidth,\n borderColor: colors.border,\n backgroundColor: colors.card,\n }}\n >\n <View style={{ gap: 2, marginBottom: 2 }}>\n <Text style={{ fontSize: 16, fontWeight: '400', color: colors.foreground }}>{title}</Text>\n {subtitle ? <Text style={{ fontSize: 13, color: colors.mutedForeground }}>{subtitle}</Text> : null}\n </View>\n {children}\n </View>\n );\n}\n\nconst isEmail = (v: string) => /^\\S+@\\S+\\.\\S{2,}$/.test(v);\n\nexport default function LabelAbove() {\n const [name, setName] = useState('');\n const [email, setEmail] = useState('');\n const [bio, setBio] = useState('');\n return (\n <Card title=\"Edit profile\" subtitle=\"This is how others will see you.\">\n <FloatingInput floatingLabel={false} label=\"Full name\" placeholder=\"Priya Sharma\" value={name} onChangeText={setName} autoComplete=\"name\" clearable />\n <FloatingInput\n floatingLabel={false}\n label=\"Email\"\n placeholder=\"you@example.com\"\n value={email}\n onChangeText={setEmail}\n keyboardType=\"email-address\"\n autoCapitalize=\"none\"\n autoCorrect={false}\n error={email.length > 0 && !isEmail(email) ? 'Enter a valid email address' : undefined}\n helper=\"We'll never share it.\"\n />\n <FloatingInput\n floatingLabel={false}\n label=\"Bio\"\n placeholder=\"A line about you\"\n value={bio}\n onChangeText={setBio}\n maxLength={80}\n counter\n />\n </Card>\n );\n}\n"
203
+ "code": "import React, { useState } from 'react';\nimport { Pressable, StyleSheet, Text, View } from 'react-native';\nimport { FloatingInput } from '@/components/ui/floating-input';\nimport { readableOn, useKit } from '@/lib/kit';\n\n/** From the Expo Go viewer: `screen` = this IS the phone screen, so no mock frame; `bottomInset` = home indicator. */\ntype DemoProps = { screen?: boolean; bottomInset?: number };\n\nfunction Card({ title, subtitle, screen, children }: { title: string; subtitle?: string; screen?: boolean; children: React.ReactNode }) {\n const { colors, radius } = useKit();\n return (\n <View\n style={[\n { padding: 16, gap: 12 },\n screen\n ? { flex: 1, backgroundColor: colors.background }\n : { borderRadius: radius.xl, borderWidth: StyleSheet.hairlineWidth, borderColor: colors.border, backgroundColor: colors.card },\n ]}\n >\n <View style={{ gap: 2, marginBottom: 2 }}>\n <Text style={{ fontSize: 16, fontWeight: '400', color: colors.foreground }}>{title}</Text>\n {subtitle ? <Text style={{ fontSize: 13, color: colors.mutedForeground }}>{subtitle}</Text> : null}\n </View>\n {children}\n </View>\n );\n}\n\nconst isEmail = (v: string) => /^\\S+@\\S+\\.\\S{2,}$/.test(v);\n\nexport default function LabelAbove({ screen }: DemoProps) {\n const [name, setName] = useState('');\n const [email, setEmail] = useState('');\n const [bio, setBio] = useState('');\n return (\n <Card title=\"Edit profile\" subtitle=\"This is how others will see you.\" screen={screen}>\n <FloatingInput floatingLabel={false} label=\"Full name\" placeholder=\"Priya Sharma\" value={name} onChangeText={setName} autoComplete=\"name\" clearable />\n <FloatingInput\n floatingLabel={false}\n label=\"Email\"\n placeholder=\"you@example.com\"\n value={email}\n onChangeText={setEmail}\n keyboardType=\"email-address\"\n autoCapitalize=\"none\"\n autoCorrect={false}\n error={email.length > 0 && !isEmail(email) ? 'Enter a valid email address' : undefined}\n helper=\"We'll never share it.\"\n />\n <FloatingInput\n floatingLabel={false}\n label=\"Bio\"\n placeholder=\"A line about you\"\n value={bio}\n onChangeText={setBio}\n maxLength={80}\n counter\n />\n </Card>\n );\n}\n"
204
204
  },
205
205
  {
206
206
  "name": "CustomStyles",
207
207
  "title": "Custom Styles",
208
- "code": "import React, { useState } from 'react';\nimport { Pressable, StyleSheet, Text, View } from 'react-native';\nimport { FloatingInput } from '@/components/ui/floating-input';\nimport { readableOn, useKit } from '@/lib/kit';\n\nfunction Card({ title, subtitle, children }: { title: string; subtitle?: string; children: React.ReactNode }) {\n const { colors, radius } = useKit();\n return (\n <View\n style={{\n padding: 16,\n gap: 12,\n borderRadius: radius.xl,\n borderWidth: StyleSheet.hairlineWidth,\n borderColor: colors.border,\n backgroundColor: colors.card,\n }}\n >\n <View style={{ gap: 2, marginBottom: 2 }}>\n <Text style={{ fontSize: 16, fontWeight: '400', color: colors.foreground }}>{title}</Text>\n {subtitle ? <Text style={{ fontSize: 13, color: colors.mutedForeground }}>{subtitle}</Text> : null}\n </View>\n {children}\n </View>\n );\n}\n\nexport default function CustomStyles() {\n const { colors, radius } = useKit();\n const [handle, setHandle] = useState('priya.codes');\n return (\n <Card title=\"Custom styles\" subtitle=\"fieldStyle, labelStyle, inputStyle, messageStyle.\">\n <FloatingInput\n label=\"Username\"\n prefix=\"@\"\n value={handle}\n onChangeText={setHandle}\n autoCapitalize=\"none\"\n autoCorrect={false}\n helper=\"Letters, numbers and dots.\"\n fieldStyle={{ borderWidth: 2, borderColor: colors.border, borderRadius: radius.lg, backgroundColor: colors.card }}\n surface={colors.card}\n labelStyle={{ fontSize: 15, fontWeight: '400', letterSpacing: 0.2 }}\n inputStyle={{ fontSize: 17, fontWeight: '400', letterSpacing: 0.3 }}\n prefixStyle={{ color: colors.accent, fontWeight: '400' }}\n messageStyle={{ fontStyle: 'italic' }}\n />\n </Card>\n );\n}\n"
208
+ "code": "import React, { useState } from 'react';\nimport { Pressable, StyleSheet, Text, View } from 'react-native';\nimport { FloatingInput } from '@/components/ui/floating-input';\nimport { readableOn, useKit } from '@/lib/kit';\n\n/** From the Expo Go viewer: `screen` = this IS the phone screen, so no mock frame; `bottomInset` = home indicator. */\ntype DemoProps = { screen?: boolean; bottomInset?: number };\n\nfunction Card({ title, subtitle, screen, children }: { title: string; subtitle?: string; screen?: boolean; children: React.ReactNode }) {\n const { colors, radius } = useKit();\n return (\n <View\n style={[\n { padding: 16, gap: 12 },\n screen\n ? { flex: 1, backgroundColor: colors.background }\n : { borderRadius: radius.xl, borderWidth: StyleSheet.hairlineWidth, borderColor: colors.border, backgroundColor: colors.card },\n ]}\n >\n <View style={{ gap: 2, marginBottom: 2 }}>\n <Text style={{ fontSize: 16, fontWeight: '400', color: colors.foreground }}>{title}</Text>\n {subtitle ? <Text style={{ fontSize: 13, color: colors.mutedForeground }}>{subtitle}</Text> : null}\n </View>\n {children}\n </View>\n );\n}\n\nexport default function CustomStyles({ screen }: DemoProps) {\n const { colors, radius } = useKit();\n const [handle, setHandle] = useState('priya.codes');\n return (\n <Card title=\"Custom styles\" subtitle=\"fieldStyle, labelStyle, inputStyle, messageStyle.\" screen={screen}>\n <FloatingInput\n label=\"Username\"\n prefix=\"@\"\n value={handle}\n onChangeText={setHandle}\n autoCapitalize=\"none\"\n autoCorrect={false}\n helper=\"Letters, numbers and dots.\"\n fieldStyle={{ borderWidth: 2, borderColor: colors.border, borderRadius: radius.lg, backgroundColor: colors.card }}\n surface={colors.card}\n labelStyle={{ fontSize: 15, fontWeight: '400', letterSpacing: 0.2 }}\n inputStyle={{ fontSize: 17, fontWeight: '400', letterSpacing: 0.3 }}\n prefixStyle={{ color: colors.accent, fontWeight: '400' }}\n messageStyle={{ fontStyle: 'italic' }}\n />\n </Card>\n );\n}\n"
209
209
  }
210
210
  ]
211
211
  }
@@ -0,0 +1,47 @@
1
+ {
2
+ "name": "food-app",
3
+ "category": "Templates",
4
+ "aliases": [],
5
+ "exportName": null,
6
+ "files": [
7
+ "src/app/(tabs)/_layout.tsx",
8
+ "src/app/(tabs)/bag.tsx",
9
+ "src/app/(tabs)/index.tsx",
10
+ "src/app/(tabs)/offers.tsx",
11
+ "src/app/(tabs)/profile.tsx",
12
+ "src/app/_layout.tsx",
13
+ "src/app/onboarding.tsx",
14
+ "src/app/settings.tsx",
15
+ "src/app/sign-in.tsx",
16
+ "src/components/food-app/tabs.tsx",
17
+ "src/lib/food-app/cart.tsx",
18
+ "src/lib/food-app/data.tsx",
19
+ "src/lib/food-app/session.tsx"
20
+ ],
21
+ "summary": null,
22
+ "props": [],
23
+ "behavior": [
24
+ [
25
+ "Flow",
26
+ "Onboarding shows once, then sign in, then the tabs. Signing out returns to sign in. The session is saved on the device (AsyncStorage) and guarded with Expo Router’s Stack.Protected."
27
+ ],
28
+ [
29
+ "Tabs",
30
+ "Menu (rolling donut shelf), Offers (coupons that unlock with the bag total), Bag (checkout) and Profile. The kit’s BottomNav is the tab bar, with the bag count as a badge."
31
+ ],
32
+ [
33
+ "Bag",
34
+ "Donuts added on the Menu land in the bag; a coupon picked on Offers is applied at checkout. Pay is a stand-in — wire it to your payment sheet."
35
+ ],
36
+ [
37
+ "Settings",
38
+ "Pushed from Profile. Appearance (System / Light / Dark) re-themes every kit component instantly."
39
+ ],
40
+ [
41
+ "Your backend",
42
+ "Auth handlers in app/sign-in.tsx and payment in app/(tabs)/bag.tsx are stand-ins that wait and succeed. Menu, coupons and the user live in lib/food-app/data.tsx."
43
+ ]
44
+ ],
45
+ "usage": null,
46
+ "examples": []
47
+ }
@@ -2,7 +2,7 @@
2
2
  "name": "glass",
3
3
  "category": "Glass",
4
4
  "aliases": [],
5
- "exportName": "GlassProvider",
5
+ "exportName": "Glass",
6
6
  "files": [
7
7
  "src/components/ui/glass.tsx"
8
8
  ],
@@ -0,0 +1,80 @@
1
+ {
2
+ "name": "grain",
3
+ "category": "Display",
4
+ "aliases": [],
5
+ "exportName": "GrainGradient",
6
+ "files": [
7
+ "src/components/ui/grain.tsx"
8
+ ],
9
+ "summary": "A soft blob gradient with real film grain, plus a grain overlay for anything.",
10
+ "props": [
11
+ [
12
+ "palette",
13
+ "sunset | ocean | aurora | dusk | peach | graphite | ember | mint | lavender | candy | forest | noir",
14
+ "\"sunset\"",
15
+ "Built-in colour set."
16
+ ],
17
+ [
18
+ "colors",
19
+ "string[]",
20
+ "—",
21
+ "Base colour first, then up to four blob colours. Overrides palette."
22
+ ],
23
+ [
24
+ "grain",
25
+ "number",
26
+ "0.55",
27
+ "Grain strength, 0–1."
28
+ ],
29
+ [
30
+ "seed",
31
+ "number",
32
+ "0",
33
+ "Shuffles where the blobs sit."
34
+ ],
35
+ [
36
+ "style",
37
+ "StyleProp<ViewStyle>",
38
+ "—",
39
+ "Root style; give it a size or flex: 1."
40
+ ],
41
+ [
42
+ "GrainOverlay grain",
43
+ "number",
44
+ "0.55",
45
+ "Grain only, laid over its parent."
46
+ ]
47
+ ],
48
+ "behavior": [
49
+ [
50
+ "Gradient",
51
+ "A base colour with up to four radial blobs, drawn with react-native-svg so it scales to any size."
52
+ ],
53
+ [
54
+ "Grain",
55
+ "A tiny tiling noise image drawn at 2×, so the speckle stays fine on every screen; grain sets its strength."
56
+ ],
57
+ [
58
+ "Touch",
59
+ "The gradient and grain never take touches; children render on top."
60
+ ]
61
+ ],
62
+ "usage": "import { GrainGradient, GrainOverlay } from '@/components/ui/grain';\n\n<GrainGradient palette=\"aurora\" style={{ flex: 1 }}>\n {/* your screen */}\n</GrainGradient>",
63
+ "examples": [
64
+ {
65
+ "name": "Hero",
66
+ "title": "Hero",
67
+ "code": "import { Text, View } from 'react-native';\nimport { GRAIN_PALETTES, GrainGradient } from '@/components/ui/grain';\nimport { useKit } from '@/lib/kit';\n\n/** From the Expo Go viewer: `screen` = this IS the phone screen, so no mock frame; `bottomInset` = home indicator. */\ntype DemoProps = { screen?: boolean; bottomInset?: number };\n\nexport default function Hero({ screen }: DemoProps) {\n const { radius } = useKit();\n return (\n <GrainGradient palette=\"ember\" seed={1} style={screen ? { flex: 1 } : { height: 460, borderRadius: radius.xl }}>\n <View style={{ flex: 1, justifyContent: 'flex-end', padding: 24, gap: 6 }}>\n <Text style={{ color: '#ffffff', fontSize: 34, fontWeight: '700', letterSpacing: -0.8 }}>Golden hour</Text>\n <Text style={{ color: 'rgba(255,255,255,0.8)', fontSize: 15 }}>Warm light, soft grain, no photo needed.</Text>\n </View>\n </GrainGradient>\n );\n}\n"
68
+ },
69
+ {
70
+ "name": "Palettes",
71
+ "title": "Palettes",
72
+ "code": "import { Text, View } from 'react-native';\nimport { GRAIN_PALETTES, GrainGradient } from '@/components/ui/grain';\nimport { useKit } from '@/lib/kit';\n\n/** From the Expo Go viewer: `screen` = this IS the phone screen, so no mock frame; `bottomInset` = home indicator. */\ntype DemoProps = { screen?: boolean; bottomInset?: number };\n\nexport default function Palettes({ screen }: DemoProps) {\n const { radius } = useKit();\n return (\n <View style={[{ flexDirection: 'row', flexWrap: 'wrap', gap: 12 }, screen && { padding: 16 }]}>\n {GRAIN_PALETTES.map((p, i) => (\n <GrainGradient key={p} palette={p} seed={i} style={{ width: '47%', flexGrow: 1, height: 120, borderRadius: radius.lg }}>\n <Text style={{ margin: 12, color: ['peach', 'mint', 'lavender'].includes(p) ? '#1d1a26' : '#ffffff', fontWeight: '600', textTransform: 'capitalize' }}>{p}</Text>\n </GrainGradient>\n ))}\n </View>\n );\n}\n"
73
+ },
74
+ {
75
+ "name": "Custom",
76
+ "title": "Custom",
77
+ "code": "import { GRAIN_PALETTES, GrainGradient } from '@/components/ui/grain';\nimport { useKit } from '@/lib/kit';\n\n/** From the Expo Go viewer: `screen` = this IS the phone screen, so no mock frame; `bottomInset` = home indicator. */\ntype DemoProps = { screen?: boolean; bottomInset?: number };\n\nexport default function Custom({ screen }: DemoProps) {\n const { radius } = useKit();\n return (\n <GrainGradient colors={['#0c0a1f', '#ff4d6d', '#ffd166', '#06d6a0']} grain={0.9} seed={2} style={screen ? { flex: 1 } : { height: 400, borderRadius: radius.xl }} />\n );\n}\n"
78
+ }
79
+ ]
80
+ }
@@ -0,0 +1,96 @@
1
+ {
2
+ "name": "morphing-button-group",
3
+ "category": "Buttons",
4
+ "aliases": [],
5
+ "exportName": "MorphingButtonGroup",
6
+ "files": [
7
+ "src/components/ui/morphing-button-group.tsx"
8
+ ],
9
+ "summary": "Buttons that split, join and stretch between sets — with a signature sheet built on it.",
10
+ "props": [
11
+ [
12
+ "buttons",
13
+ "{ key, label, icon?, variant?, onPress? }[]",
14
+ "—",
15
+ "MorphingButtonGroup: the buttons to show now; changing it morphs the row."
16
+ ],
17
+ [
18
+ "height",
19
+ "number",
20
+ "44",
21
+ "Button height."
22
+ ],
23
+ [
24
+ "gap",
25
+ "number",
26
+ "10",
27
+ "Space between buttons."
28
+ ],
29
+ [
30
+ "duration",
31
+ "number",
32
+ "380",
33
+ "Morph length in ms."
34
+ ],
35
+ [
36
+ "SignatureSheet label",
37
+ "string",
38
+ "'Sign'",
39
+ "The pill's text."
40
+ ],
41
+ [
42
+ "SignatureSheet title",
43
+ "string",
44
+ "'Add Signature'",
45
+ "Card title."
46
+ ],
47
+ [
48
+ "SignatureSheet onConfirm",
49
+ "({ paths, width, height }) => void",
50
+ "—",
51
+ "The finished signature."
52
+ ],
53
+ [
54
+ "SignatureSheet onCancel",
55
+ "() => void",
56
+ "—",
57
+ "Closed without signing."
58
+ ],
59
+ [
60
+ "SignatureSheet cardHeight",
61
+ "number",
62
+ "340",
63
+ "Height of the open card."
64
+ ],
65
+ [
66
+ "style",
67
+ "StyleProp<ViewStyle>",
68
+ "—",
69
+ "Root style."
70
+ ]
71
+ ],
72
+ "behavior": [
73
+ [
74
+ "Morph",
75
+ "Buttons with the same key keep their place and resize; new ones grow in from nothing and leaving ones shrink away, on one smooth ease (no bounce)."
76
+ ],
77
+ [
78
+ "Labels",
79
+ "A label change cross-fades while the button resizes."
80
+ ],
81
+ [
82
+ "Signature",
83
+ "The pad draws smoothed strokes; undo removes the last one, Reset clears, Confirm asks once more. onConfirm gets SVG path data and the pad size."
84
+ ],
85
+ [
86
+ "Haptics",
87
+ "A tick on each button, a light tap opening the sheet, success on confirm."
88
+ ],
89
+ [
90
+ "Reduced motion",
91
+ "Morphs are instant."
92
+ ]
93
+ ],
94
+ "usage": null,
95
+ "examples": []
96
+ }
@@ -0,0 +1,132 @@
1
+ {
2
+ "name": "orb-reveal",
3
+ "category": "Motion",
4
+ "aliases": [],
5
+ "exportName": "OrbReveal",
6
+ "files": [
7
+ "src/components/ui/orb-reveal.tsx"
8
+ ],
9
+ "summary": "Drag a liquid-glass orb up: it shows your title through the lens, then fountains glass marbles into a heart.",
10
+ "props": [
11
+ [
12
+ "headline",
13
+ "string",
14
+ "'A new era of…'",
15
+ "Copy shown before the reveal."
16
+ ],
17
+ [
18
+ "title",
19
+ "string",
20
+ "'Meet Wabi.'",
21
+ "The line the lens uncovers."
22
+ ],
23
+ [
24
+ "subtitle",
25
+ "string",
26
+ "'The first personal…'",
27
+ "Lines under the title."
28
+ ],
29
+ [
30
+ "hint",
31
+ "string",
32
+ "'Swipe up to enter'",
33
+ "Faint hint under the resting orb."
34
+ ],
35
+ [
36
+ "images",
37
+ "string[]",
38
+ "—",
39
+ "Image URIs for the marbles (app icons, avatars)."
40
+ ],
41
+ [
42
+ "emoji",
43
+ "string[]",
44
+ "24 emoji",
45
+ "Marble pictures when there are no images, each on a bright gradient."
46
+ ],
47
+ [
48
+ "count",
49
+ "number",
50
+ "52",
51
+ "Marbles in the heart (max 60), plus a trail of specks."
52
+ ],
53
+ [
54
+ "glowColors",
55
+ "[string, string]",
56
+ "blue, violet",
57
+ "The glow while you hold the orb."
58
+ ],
59
+ [
60
+ "background",
61
+ "string",
62
+ "theme background",
63
+ "Page colour."
64
+ ],
65
+ [
66
+ "footer",
67
+ "ReactNode",
68
+ "—",
69
+ "Fades in after the reveal: sign-in buttons, a CTA."
70
+ ],
71
+ [
72
+ "revealed",
73
+ "boolean",
74
+ "—",
75
+ "Controlled state."
76
+ ],
77
+ [
78
+ "defaultRevealed",
79
+ "boolean",
80
+ "false",
81
+ "Uncontrolled initial state."
82
+ ],
83
+ [
84
+ "onRevealChange",
85
+ "(revealed: boolean) => void",
86
+ "—",
87
+ "The orb settled in a new state."
88
+ ],
89
+ [
90
+ "style",
91
+ "StyleProp<ViewStyle>",
92
+ "flex: 1",
93
+ "Root style; give it a screen or a tall card."
94
+ ]
95
+ ],
96
+ "behavior": [
97
+ [
98
+ "Hold",
99
+ "The orb swells and lifts, a blue-violet smile of light glows through it and the headline fades."
100
+ ],
101
+ [
102
+ "Drag",
103
+ "Drag up past halfway (or flick) to reveal; drag the small orb down to go back. A quick tap toggles; a hold or a drag back to the start stays put."
104
+ ],
105
+ [
106
+ "Lens",
107
+ "Two Skia shaders refract the page under the orb and the title the lens uncovers: magnified middle, mirrored bevel, red/blue split at the edge."
108
+ ],
109
+ [
110
+ "Marbles",
111
+ "Fountain out of the button on arcs, pop to size and settle into a heart, then drift. Going back down, they float off the top."
112
+ ],
113
+ [
114
+ "Copy",
115
+ "The title rides with the orb. It and the footer appear as the orb settles and stay until it is most of the way back down."
116
+ ],
117
+ [
118
+ "Haptics",
119
+ "A tick when you pick the orb up and a medium impact on reveal."
120
+ ],
121
+ [
122
+ "Reduced motion",
123
+ "The orb snaps between states and the marbles appear in place."
124
+ ],
125
+ [
126
+ "Web",
127
+ "Needs CanvasKit loaded (LoadSkiaWeb); without it the revealed copy and footer are shown."
128
+ ]
129
+ ],
130
+ "usage": null,
131
+ "examples": []
132
+ }
@@ -132,22 +132,22 @@
132
132
  {
133
133
  "name": "VerifyPhone",
134
134
  "title": "Verify Phone",
135
- "code": "import React, { useEffect, useState } from 'react';\nimport { Pressable, StyleSheet, Text, View, type ViewProps } from 'react-native';\nimport { OtpInput } from '@/components/ui/otp-input';\nimport { useKit } from '@/lib/kit';\n\n/** Demo card that follows the kit theme, so the docs radius / accent picker reshapes it too. */\nfunction Card({ style, ...rest }: ViewProps) {\n const { colors, radius } = useKit();\n return (\n <View\n style={[\n { padding: 16, gap: 12, borderRadius: radius.xl, borderWidth: StyleSheet.hairlineWidth, borderColor: colors.border, backgroundColor: colors.card },\n style,\n ]}\n {...rest}\n />\n );\n}\n\nfunction Header({ title, body }: { title: string; body: string }) {\n const { colors } = useKit();\n return (\n <View style={{ gap: 4, alignItems: 'center' }}>\n <Text style={{ fontSize: 16, fontWeight: '400', color: colors.foreground, textAlign: 'center' }}>{title}</Text>\n <Text style={{ fontSize: 13, color: colors.mutedForeground, textAlign: 'center' }}>{body}</Text>\n </View>\n );\n}\n\nexport default function VerifyPhone() {\n const { colors } = useKit();\n const [code, setCode] = useState('');\n const [left, setLeft] = useState(24);\n useEffect(() => {\n if (left <= 0) return;\n const id = setTimeout(() => setLeft((s) => s - 1), 1000);\n return () => clearTimeout(id);\n }, [left]);\n const done = code.length === 6;\n return (\n <Card>\n <Header title=\"Verify your number\" body=\"Enter the code sent to +91 98•••• 4210\" />\n <OtpInput\n value={code}\n onChangeText={setCode}\n success={code === '123456'}\n error={done && code !== '123456'}\n successText=\"Number verified\"\n errorText=\"That code didn't match. Try 123456.\"\n helperText=\"Tip: the demo code is 123456\"\n />\n <View style={{ flexDirection: 'row', justifyContent: 'center', gap: 16 }}>\n <Pressable disabled={left > 0} onPress={() => { setLeft(24); setCode(''); }} hitSlop={10}>\n <Text style={{ fontSize: 13, fontWeight: '400', color: left > 0 ? colors.mutedForeground : colors.accent }}>\n {left > 0 ? `Resend in 0:${String(left).padStart(2, '0')}` : 'Resend code'}\n </Text>\n </Pressable>\n <Pressable hitSlop={10} accessibilityRole=\"button\">\n <Text style={{ fontSize: 13, fontWeight: '400', color: colors.accent }}>Change number</Text>\n </Pressable>\n </View>\n </Card>\n );\n}\n"
135
+ "code": "import React, { useEffect, useState } from 'react';\nimport { Pressable, StyleSheet, Text, View, type ViewProps } from 'react-native';\nimport { OtpInput } from '@/components/ui/otp-input';\nimport { useKit } from '@/lib/kit';\n\n/** From the Expo Go viewer: `screen` = this IS the phone screen, so no mock frame; `bottomInset` = home indicator. */\ntype DemoProps = { screen?: boolean; bottomInset?: number };\n\n/** Demo card that follows the kit theme, so the docs radius / accent picker reshapes it too. */\nfunction Card({ style, screen, ...rest }: ViewProps & { screen?: boolean }) {\n const { colors, radius } = useKit();\n return (\n <View\n style={[\n { padding: 16, gap: 12 },\n screen\n ? { flex: 1, backgroundColor: colors.background }\n : { borderRadius: radius.xl, borderWidth: StyleSheet.hairlineWidth, borderColor: colors.border, backgroundColor: colors.card },\n style,\n ]}\n {...rest}\n />\n );\n}\n\nfunction Header({ title, body }: { title: string; body: string }) {\n const { colors } = useKit();\n return (\n <View style={{ gap: 4, alignItems: 'center' }}>\n <Text style={{ fontSize: 16, fontWeight: '400', color: colors.foreground, textAlign: 'center' }}>{title}</Text>\n <Text style={{ fontSize: 13, color: colors.mutedForeground, textAlign: 'center' }}>{body}</Text>\n </View>\n );\n}\n\nexport default function VerifyPhone({ screen }: DemoProps) {\n const { colors } = useKit();\n const [code, setCode] = useState('');\n const [left, setLeft] = useState(24);\n useEffect(() => {\n if (left <= 0) return;\n const id = setTimeout(() => setLeft((s) => s - 1), 1000);\n return () => clearTimeout(id);\n }, [left]);\n const done = code.length === 6;\n return (\n <Card screen={screen}>\n <Header title=\"Verify your number\" body=\"Enter the code sent to +91 98•••• 4210\" />\n <OtpInput\n value={code}\n onChangeText={setCode}\n success={code === '123456'}\n error={done && code !== '123456'}\n successText=\"Number verified\"\n errorText=\"That code didn't match. Try 123456.\"\n helperText=\"Tip: the demo code is 123456\"\n />\n <View style={{ flexDirection: 'row', justifyContent: 'center', gap: 16 }}>\n <Pressable disabled={left > 0} onPress={() => { setLeft(24); setCode(''); }} hitSlop={10}>\n <Text style={{ fontSize: 13, fontWeight: '400', color: left > 0 ? colors.mutedForeground : colors.accent }}>\n {left > 0 ? `Resend in 0:${String(left).padStart(2, '0')}` : 'Resend code'}\n </Text>\n </Pressable>\n <Pressable hitSlop={10} accessibilityRole=\"button\">\n <Text style={{ fontSize: 13, fontWeight: '400', color: colors.accent }}>Change number</Text>\n </Pressable>\n </View>\n </Card>\n );\n}\n"
136
136
  },
137
137
  {
138
138
  "name": "Pin",
139
139
  "title": "Pin",
140
- "code": "import React, { useEffect, useState } from 'react';\nimport { Pressable, StyleSheet, Text, View, type ViewProps } from 'react-native';\nimport { OtpInput } from '@/components/ui/otp-input';\nimport { useKit } from '@/lib/kit';\n\n/** Demo card that follows the kit theme, so the docs radius / accent picker reshapes it too. */\nfunction Card({ style, ...rest }: ViewProps) {\n const { colors, radius } = useKit();\n return (\n <View\n style={[\n { padding: 16, gap: 12, borderRadius: radius.xl, borderWidth: StyleSheet.hairlineWidth, borderColor: colors.border, backgroundColor: colors.card },\n style,\n ]}\n {...rest}\n />\n );\n}\n\nfunction Header({ title, body }: { title: string; body: string }) {\n const { colors } = useKit();\n return (\n <View style={{ gap: 4, alignItems: 'center' }}>\n <Text style={{ fontSize: 16, fontWeight: '400', color: colors.foreground, textAlign: 'center' }}>{title}</Text>\n <Text style={{ fontSize: 13, color: colors.mutedForeground, textAlign: 'center' }}>{body}</Text>\n </View>\n );\n}\n\nexport default function Pin() {\n const [pin, setPin] = useState('');\n return (\n <Card>\n <Header title=\"Enter your PIN\" body=\"Confirm the transfer of ₹2,400 to Ananya\" />\n <OtpInput variant=\"pill\" length={4} secure value={pin} onChangeText={setPin} maxCellSize={56} />\n </Card>\n );\n}\n"
140
+ "code": "import React, { useEffect, useState } from 'react';\nimport { Pressable, StyleSheet, Text, View, type ViewProps } from 'react-native';\nimport { OtpInput } from '@/components/ui/otp-input';\nimport { useKit } from '@/lib/kit';\n\n/** From the Expo Go viewer: `screen` = this IS the phone screen, so no mock frame; `bottomInset` = home indicator. */\ntype DemoProps = { screen?: boolean; bottomInset?: number };\n\n/** Demo card that follows the kit theme, so the docs radius / accent picker reshapes it too. */\nfunction Card({ style, screen, ...rest }: ViewProps & { screen?: boolean }) {\n const { colors, radius } = useKit();\n return (\n <View\n style={[\n { padding: 16, gap: 12 },\n screen\n ? { flex: 1, backgroundColor: colors.background }\n : { borderRadius: radius.xl, borderWidth: StyleSheet.hairlineWidth, borderColor: colors.border, backgroundColor: colors.card },\n style,\n ]}\n {...rest}\n />\n );\n}\n\nfunction Header({ title, body }: { title: string; body: string }) {\n const { colors } = useKit();\n return (\n <View style={{ gap: 4, alignItems: 'center' }}>\n <Text style={{ fontSize: 16, fontWeight: '400', color: colors.foreground, textAlign: 'center' }}>{title}</Text>\n <Text style={{ fontSize: 13, color: colors.mutedForeground, textAlign: 'center' }}>{body}</Text>\n </View>\n );\n}\n\nexport default function Pin({ screen }: DemoProps) {\n const [pin, setPin] = useState('');\n return (\n <Card screen={screen}>\n <Header title=\"Enter your PIN\" body=\"Confirm the transfer of ₹2,400 to Ananya\" />\n <OtpInput variant=\"pill\" length={4} secure value={pin} onChangeText={setPin} maxCellSize={56} />\n </Card>\n );\n}\n"
141
141
  },
142
142
  {
143
143
  "name": "Underline",
144
144
  "title": "Underline",
145
- "code": "import { Pressable, StyleSheet, Text, View, type ViewProps } from 'react-native';\nimport { OtpInput } from '@/components/ui/otp-input';\nimport { useKit } from '@/lib/kit';\n\n/** Demo card that follows the kit theme, so the docs radius / accent picker reshapes it too. */\nfunction Card({ style, ...rest }: ViewProps) {\n const { colors, radius } = useKit();\n return (\n <View\n style={[\n { padding: 16, gap: 12, borderRadius: radius.xl, borderWidth: StyleSheet.hairlineWidth, borderColor: colors.border, backgroundColor: colors.card },\n style,\n ]}\n {...rest}\n />\n );\n}\n\nfunction Header({ title, body }: { title: string; body: string }) {\n const { colors } = useKit();\n return (\n <View style={{ gap: 4, alignItems: 'center' }}>\n <Text style={{ fontSize: 16, fontWeight: '400', color: colors.foreground, textAlign: 'center' }}>{title}</Text>\n <Text style={{ fontSize: 13, color: colors.mutedForeground, textAlign: 'center' }}>{body}</Text>\n </View>\n );\n}\n\nexport default function Underline() {\n return (\n <Card>\n <Header title=\"Check your inbox\" body=\"We emailed a 6-digit code to karan@…\" />\n <OtpInput variant=\"underline\" helperText=\"Code expires in 10 minutes\" />\n </Card>\n );\n}\n"
145
+ "code": "import { Pressable, StyleSheet, Text, View, type ViewProps } from 'react-native';\nimport { OtpInput } from '@/components/ui/otp-input';\nimport { useKit } from '@/lib/kit';\n\n/** From the Expo Go viewer: `screen` = this IS the phone screen, so no mock frame; `bottomInset` = home indicator. */\ntype DemoProps = { screen?: boolean; bottomInset?: number };\n\n/** Demo card that follows the kit theme, so the docs radius / accent picker reshapes it too. */\nfunction Card({ style, screen, ...rest }: ViewProps & { screen?: boolean }) {\n const { colors, radius } = useKit();\n return (\n <View\n style={[\n { padding: 16, gap: 12 },\n screen\n ? { flex: 1, backgroundColor: colors.background }\n : { borderRadius: radius.xl, borderWidth: StyleSheet.hairlineWidth, borderColor: colors.border, backgroundColor: colors.card },\n style,\n ]}\n {...rest}\n />\n );\n}\n\nfunction Header({ title, body }: { title: string; body: string }) {\n const { colors } = useKit();\n return (\n <View style={{ gap: 4, alignItems: 'center' }}>\n <Text style={{ fontSize: 16, fontWeight: '400', color: colors.foreground, textAlign: 'center' }}>{title}</Text>\n <Text style={{ fontSize: 13, color: colors.mutedForeground, textAlign: 'center' }}>{body}</Text>\n </View>\n );\n}\n\nexport default function Underline({ screen }: DemoProps) {\n return (\n <Card screen={screen}>\n <Header title=\"Check your inbox\" body=\"We emailed a 6-digit code to karan@…\" />\n <OtpInput variant=\"underline\" helperText=\"Code expires in 10 minutes\" />\n </Card>\n );\n}\n"
146
146
  },
147
147
  {
148
148
  "name": "WithSeparator",
149
149
  "title": "With Separator",
150
- "code": "import { Pressable, StyleSheet, Text, View, type ViewProps } from 'react-native';\nimport { OtpInput } from '@/components/ui/otp-input';\nimport { useKit } from '@/lib/kit';\n\n/** Demo card that follows the kit theme, so the docs radius / accent picker reshapes it too. */\nfunction Card({ style, ...rest }: ViewProps) {\n const { colors, radius } = useKit();\n return (\n <View\n style={[\n { padding: 16, gap: 12, borderRadius: radius.xl, borderWidth: StyleSheet.hairlineWidth, borderColor: colors.border, backgroundColor: colors.card },\n style,\n ]}\n {...rest}\n />\n );\n}\n\nfunction Header({ title, body }: { title: string; body: string }) {\n const { colors } = useKit();\n return (\n <View style={{ gap: 4, alignItems: 'center' }}>\n <Text style={{ fontSize: 16, fontWeight: '400', color: colors.foreground, textAlign: 'center' }}>{title}</Text>\n <Text style={{ fontSize: 13, color: colors.mutedForeground, textAlign: 'center' }}>{body}</Text>\n </View>\n );\n}\n\nexport default function WithSeparator() {\n return (\n <Card>\n <Header title=\"Two-factor authentication\" body=\"Open your authenticator app\" />\n <OtpInput separatorAfter={3} maxCellSize={46} />\n </Card>\n );\n}\n"
150
+ "code": "import { Pressable, StyleSheet, Text, View, type ViewProps } from 'react-native';\nimport { OtpInput } from '@/components/ui/otp-input';\nimport { useKit } from '@/lib/kit';\n\n/** From the Expo Go viewer: `screen` = this IS the phone screen, so no mock frame; `bottomInset` = home indicator. */\ntype DemoProps = { screen?: boolean; bottomInset?: number };\n\n/** Demo card that follows the kit theme, so the docs radius / accent picker reshapes it too. */\nfunction Card({ style, screen, ...rest }: ViewProps & { screen?: boolean }) {\n const { colors, radius } = useKit();\n return (\n <View\n style={[\n { padding: 16, gap: 12 },\n screen\n ? { flex: 1, backgroundColor: colors.background }\n : { borderRadius: radius.xl, borderWidth: StyleSheet.hairlineWidth, borderColor: colors.border, backgroundColor: colors.card },\n style,\n ]}\n {...rest}\n />\n );\n}\n\nfunction Header({ title, body }: { title: string; body: string }) {\n const { colors } = useKit();\n return (\n <View style={{ gap: 4, alignItems: 'center' }}>\n <Text style={{ fontSize: 16, fontWeight: '400', color: colors.foreground, textAlign: 'center' }}>{title}</Text>\n <Text style={{ fontSize: 13, color: colors.mutedForeground, textAlign: 'center' }}>{body}</Text>\n </View>\n );\n}\n\nexport default function WithSeparator({ screen }: DemoProps) {\n return (\n <Card screen={screen}>\n <Header title=\"Two-factor authentication\" body=\"Open your authenticator app\" />\n <OtpInput separatorAfter={3} maxCellSize={46} />\n </Card>\n );\n}\n"
151
151
  },
152
152
  {
153
153
  "name": "States",
@@ -86,12 +86,12 @@
86
86
  {
87
87
  "name": "CreatePassword",
88
88
  "title": "Create Password",
89
- "code": "import React, { useState } from 'react';\nimport { StyleSheet, Text, View } from 'react-native';\nimport { PasswordInput } from '@/components/ui/password-input';\nimport { useKit } from '@/lib/kit';\n\nfunction Card({ title, subtitle, children }: { title: string; subtitle?: string; children: React.ReactNode }) {\n const { colors, radius } = useKit();\n return (\n <View\n style={{\n padding: 16,\n gap: 12,\n borderRadius: radius.xl,\n borderWidth: StyleSheet.hairlineWidth,\n borderColor: colors.border,\n backgroundColor: colors.card,\n }}\n >\n <View style={{ gap: 2, marginBottom: 2 }}>\n <Text style={{ fontSize: 16, fontWeight: '400', color: colors.foreground }}>{title}</Text>\n {subtitle ? <Text style={{ fontSize: 13, color: colors.mutedForeground }}>{subtitle}</Text> : null}\n </View>\n {children}\n </View>\n );\n}\n\nexport default function CreatePassword() {\n const [password, setPassword] = useState('Sunset');\n const [confirm, setConfirm] = useState('');\n return (\n <Card title=\"Choose a password\" subtitle=\"You'll use it to sign in on new devices.\">\n <PasswordInput label=\"New password\" value={password} onChangeText={setPassword} requirements />\n <PasswordInput label=\"Confirm password\" value={confirm} onChangeText={setConfirm} confirmOf={password} />\n </Card>\n );\n}\n"
89
+ "code": "import React, { useState } from 'react';\nimport { StyleSheet, Text, View } from 'react-native';\nimport { PasswordInput } from '@/components/ui/password-input';\nimport { useKit } from '@/lib/kit';\n\n/** From the Expo Go viewer: `screen` = this IS the phone screen, so no mock frame; `bottomInset` = home indicator. */\ntype DemoProps = { screen?: boolean; bottomInset?: number };\n\nfunction Card({ title, subtitle, screen, children }: { title: string; subtitle?: string; screen?: boolean; children: React.ReactNode }) {\n const { colors, radius } = useKit();\n return (\n <View\n style={[\n { padding: 16, gap: 12 },\n screen\n ? { flex: 1, backgroundColor: colors.background }\n : {\n borderRadius: radius.xl,\n borderWidth: StyleSheet.hairlineWidth,\n borderColor: colors.border,\n backgroundColor: colors.card,\n },\n ]}\n >\n <View style={{ gap: 2, marginBottom: 2 }}>\n <Text style={{ fontSize: 16, fontWeight: '400', color: colors.foreground }}>{title}</Text>\n {subtitle ? <Text style={{ fontSize: 13, color: colors.mutedForeground }}>{subtitle}</Text> : null}\n </View>\n {children}\n </View>\n );\n}\n\nexport default function CreatePassword({ screen }: DemoProps) {\n const [password, setPassword] = useState('Sunset');\n const [confirm, setConfirm] = useState('');\n return (\n <Card screen={screen} title=\"Choose a password\" subtitle=\"You'll use it to sign in on new devices.\">\n <PasswordInput label=\"New password\" value={password} onChangeText={setPassword} requirements />\n <PasswordInput label=\"Confirm password\" value={confirm} onChangeText={setConfirm} confirmOf={password} />\n </Card>\n );\n}\n"
90
90
  },
91
91
  {
92
92
  "name": "SignIn",
93
93
  "title": "Sign In",
94
- "code": "import React, { useState } from 'react';\nimport { StyleSheet, Text, View } from 'react-native';\nimport { FloatingInput } from '@/components/ui/floating-input';\nimport { PasswordInput } from '@/components/ui/password-input';\nimport { useKit } from '@/lib/kit';\n\nfunction Card({ title, subtitle, children }: { title: string; subtitle?: string; children: React.ReactNode }) {\n const { colors, radius } = useKit();\n return (\n <View\n style={{\n padding: 16,\n gap: 12,\n borderRadius: radius.xl,\n borderWidth: StyleSheet.hairlineWidth,\n borderColor: colors.border,\n backgroundColor: colors.card,\n }}\n >\n <View style={{ gap: 2, marginBottom: 2 }}>\n <Text style={{ fontSize: 16, fontWeight: '400', color: colors.foreground }}>{title}</Text>\n {subtitle ? <Text style={{ fontSize: 13, color: colors.mutedForeground }}>{subtitle}</Text> : null}\n </View>\n {children}\n </View>\n );\n}\n\nexport default function SignIn() {\n const { colors } = useKit();\n return (\n <Card title=\"Welcome back\">\n <FloatingInput label=\"Email\" defaultValue=\"maya@studio.io\" keyboardType=\"email-address\" autoCapitalize=\"none\" />\n <PasswordInput label=\"Password\" strength={false} defaultValue=\"correct horse\" />\n <Text style={{ alignSelf: 'flex-end', fontSize: 13, fontWeight: '400', color: colors.accent }}>Forgot password?</Text>\n </Card>\n );\n}\n"
94
+ "code": "import React, { useState } from 'react';\nimport { StyleSheet, Text, View } from 'react-native';\nimport { FloatingInput } from '@/components/ui/floating-input';\nimport { PasswordInput } from '@/components/ui/password-input';\nimport { useKit } from '@/lib/kit';\n\n/** From the Expo Go viewer: `screen` = this IS the phone screen, so no mock frame; `bottomInset` = home indicator. */\ntype DemoProps = { screen?: boolean; bottomInset?: number };\n\nfunction Card({ title, subtitle, screen, children }: { title: string; subtitle?: string; screen?: boolean; children: React.ReactNode }) {\n const { colors, radius } = useKit();\n return (\n <View\n style={[\n { padding: 16, gap: 12 },\n screen\n ? { flex: 1, backgroundColor: colors.background }\n : {\n borderRadius: radius.xl,\n borderWidth: StyleSheet.hairlineWidth,\n borderColor: colors.border,\n backgroundColor: colors.card,\n },\n ]}\n >\n <View style={{ gap: 2, marginBottom: 2 }}>\n <Text style={{ fontSize: 16, fontWeight: '400', color: colors.foreground }}>{title}</Text>\n {subtitle ? <Text style={{ fontSize: 13, color: colors.mutedForeground }}>{subtitle}</Text> : null}\n </View>\n {children}\n </View>\n );\n}\n\nexport default function SignIn({ screen }: DemoProps) {\n const { colors } = useKit();\n return (\n <Card screen={screen} title=\"Welcome back\">\n <FloatingInput label=\"Email\" defaultValue=\"maya@studio.io\" keyboardType=\"email-address\" autoCapitalize=\"none\" />\n <PasswordInput label=\"Password\" strength={false} defaultValue=\"correct horse\" />\n <Text style={{ alignSelf: 'flex-end', fontSize: 13, fontWeight: '400', color: colors.accent }}>Forgot password?</Text>\n </Card>\n );\n}\n"
95
95
  },
96
96
  {
97
97
  "name": "StrengthMeter",
@@ -101,7 +101,7 @@
101
101
  {
102
102
  "name": "CustomRules",
103
103
  "title": "Custom Rules",
104
- "code": "import React, { useState } from 'react';\nimport { StyleSheet, Text, View } from 'react-native';\nimport { PasswordInput } from '@/components/ui/password-input';\nimport { useKit } from '@/lib/kit';\n\nfunction Card({ title, subtitle, children }: { title: string; subtitle?: string; children: React.ReactNode }) {\n const { colors, radius } = useKit();\n return (\n <View\n style={{\n padding: 16,\n gap: 12,\n borderRadius: radius.xl,\n borderWidth: StyleSheet.hairlineWidth,\n borderColor: colors.border,\n backgroundColor: colors.card,\n }}\n >\n <View style={{ gap: 2, marginBottom: 2 }}>\n <Text style={{ fontSize: 16, fontWeight: '400', color: colors.foreground }}>{title}</Text>\n {subtitle ? <Text style={{ fontSize: 13, color: colors.mutedForeground }}>{subtitle}</Text> : null}\n </View>\n {children}\n </View>\n );\n}\n\nexport default function CustomRules() {\n return (\n <Card title=\"Wi-Fi password\" subtitle=\"Your router needs 12+ characters and no spaces.\">\n <PasswordInput\n variant=\"filled\"\n label=\"Network password\"\n strength={false}\n defaultValue=\"cafe latte\"\n requirements={[\n { label: '12+ characters', test: (v) => v.length >= 12 },\n { label: 'No spaces', test: (v) => v.length > 0 && !/\\s/.test(v) },\n ]}\n />\n </Card>\n );\n}\n"
104
+ "code": "import React, { useState } from 'react';\nimport { StyleSheet, Text, View } from 'react-native';\nimport { PasswordInput } from '@/components/ui/password-input';\nimport { useKit } from '@/lib/kit';\n\n/** From the Expo Go viewer: `screen` = this IS the phone screen, so no mock frame; `bottomInset` = home indicator. */\ntype DemoProps = { screen?: boolean; bottomInset?: number };\n\nfunction Card({ title, subtitle, screen, children }: { title: string; subtitle?: string; screen?: boolean; children: React.ReactNode }) {\n const { colors, radius } = useKit();\n return (\n <View\n style={[\n { padding: 16, gap: 12 },\n screen\n ? { flex: 1, backgroundColor: colors.background }\n : {\n borderRadius: radius.xl,\n borderWidth: StyleSheet.hairlineWidth,\n borderColor: colors.border,\n backgroundColor: colors.card,\n },\n ]}\n >\n <View style={{ gap: 2, marginBottom: 2 }}>\n <Text style={{ fontSize: 16, fontWeight: '400', color: colors.foreground }}>{title}</Text>\n {subtitle ? <Text style={{ fontSize: 13, color: colors.mutedForeground }}>{subtitle}</Text> : null}\n </View>\n {children}\n </View>\n );\n}\n\nexport default function CustomRules({ screen }: DemoProps) {\n return (\n <Card screen={screen} title=\"Wi-Fi password\" subtitle=\"Your router needs 12+ characters and no spaces.\">\n <PasswordInput\n variant=\"filled\"\n label=\"Network password\"\n strength={false}\n defaultValue=\"cafe latte\"\n requirements={[\n { label: '12+ characters', test: (v) => v.length >= 12 },\n { label: 'No spaces', test: (v) => v.length > 0 && !/\\s/.test(v) },\n ]}\n />\n </Card>\n );\n}\n"
105
105
  },
106
106
  {
107
107
  "name": "WrongPassword",