@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
@@ -165,12 +165,12 @@
165
165
  {
166
166
  "name": "Review",
167
167
  "title": "Review",
168
- "code": "import React, { useState } from 'react';\nimport { Pressable, StyleSheet, Text, View } from 'react-native';\nimport { Textarea } from '@/components/ui/textarea';\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 Review() {\n const [review, setReview] = useState('Great coffee, but');\n const length = review.trim().length;\n return (\n <Card title=\"Rate Blue Bottle, Hayes Valley\" subtitle=\"Reviews are public and show your first name.\">\n <Textarea\n label=\"Your review\"\n value={review}\n onChangeText={setReview}\n maxLength={200}\n enforceMaxLength={false}\n minRows={3}\n error={length > 0 && length < 20 ? 'Tell us a little more — at least 20 characters' : length > 200 ? 'Keep it under 200 characters' : undefined}\n success={length >= 20 && length <= 200 ? 'Thanks — that will really help others' : undefined}\n />\n </Card>\n );\n}\n"
168
+ "code": "import React, { useState } from 'react';\nimport { Pressable, StyleSheet, Text, View } from 'react-native';\nimport { Textarea } from '@/components/ui/textarea';\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 screen\n ? { flex: 1, padding: 16, gap: 12, backgroundColor: colors.background }\n : {\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 >\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 Review({ screen }: DemoProps) {\n const [review, setReview] = useState('Great coffee, but');\n const length = review.trim().length;\n return (\n <Card screen={screen} title=\"Rate Blue Bottle, Hayes Valley\" subtitle=\"Reviews are public and show your first name.\">\n <Textarea\n label=\"Your review\"\n value={review}\n onChangeText={setReview}\n maxLength={200}\n enforceMaxLength={false}\n minRows={3}\n error={length > 0 && length < 20 ? 'Tell us a little more — at least 20 characters' : length > 200 ? 'Keep it under 200 characters' : undefined}\n success={length >= 20 && length <= 200 ? 'Thanks — that will really help others' : undefined}\n />\n </Card>\n );\n}\n"
169
169
  },
170
170
  {
171
171
  "name": "Feedback",
172
172
  "title": "Feedback",
173
- "code": "import React, { useState } from 'react';\nimport { Pressable, StyleSheet, Text, View } from 'react-native';\nimport { Textarea } from '@/components/ui/textarea';\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 Feedback() {\n return (\n <Card title=\"Send feedback\" subtitle=\"Goes straight to the product team.\">\n <Textarea\n variant=\"filled\"\n label=\"What could be better?\"\n defaultValue=\"Dark mode on the invoices page is a little too low-contrast for me.\"\n helper=\"Screenshots help — attach them in the next step.\"\n minRows={3}\n maxRows={6}\n />\n </Card>\n );\n}\n"
173
+ "code": "import React, { useState } from 'react';\nimport { Pressable, StyleSheet, Text, View } from 'react-native';\nimport { Textarea } from '@/components/ui/textarea';\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 screen\n ? { flex: 1, padding: 16, gap: 12, backgroundColor: colors.background }\n : {\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 >\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 Feedback({ screen }: DemoProps) {\n return (\n <Card screen={screen} title=\"Send feedback\" subtitle=\"Goes straight to the product team.\">\n <Textarea\n variant=\"filled\"\n label=\"What could be better?\"\n defaultValue=\"Dark mode on the invoices page is a little too low-contrast for me.\"\n helper=\"Screenshots help — attach them in the next step.\"\n minRows={3}\n maxRows={6}\n />\n </Card>\n );\n}\n"
174
174
  },
175
175
  {
176
176
  "name": "CharacterLimit",
@@ -2,7 +2,7 @@
2
2
  "name": "ticket-shape",
3
3
  "category": null,
4
4
  "aliases": [],
5
- "exportName": "TicketStamp",
5
+ "exportName": "TicketShape",
6
6
  "files": [
7
7
  "src/lib/ticket-shape.tsx"
8
8
  ],
@@ -136,22 +136,22 @@
136
136
  {
137
137
  "name": "TravelCard",
138
138
  "title": "Travel Card",
139
- "code": "import React, { useState } from 'react';\nimport { Image, Pressable, Text, View } from 'react-native';\nimport { TiltCard, TiltLayer } from '@/components/ui/tilt-card';\nimport { alpha, useKit } from '@/lib/kit';\n\n/** Any photo works (`require()` or a URL). `blur` gives soft, abstract art. */\nconst photo = (id: number, w: number, h: number, blur = false) => ({ uri: `https://picsum.photos/id/${id}/${w}/${h}${blur ? '?blur=10' : ''}` });\n\n/** A screen title, as it sits above the card in your app. */\nfunction Header({ title, sub, right }: { title: string; sub?: string; right?: React.ReactNode }) {\n const { colors } = useKit();\n return (\n <View style={{ flexDirection: 'row', alignItems: 'center', gap: 12 }}>\n <View style={{ flex: 1 }}>\n <Text style={{ color: colors.foreground, fontSize: 22, fontWeight: '700', letterSpacing: -0.4 }}>{title}</Text>\n {sub ? <Text style={{ color: colors.mutedForeground, fontSize: 13, marginTop: 2 }}>{sub}</Text> : null}\n </View>\n {right}\n </View>\n );\n}\n\nconst glass = {\n paddingHorizontal: 10,\n paddingVertical: 5,\n borderRadius: 999,\n backgroundColor: 'rgba(255,255,255,0.18)',\n borderWidth: 1,\n borderColor: 'rgba(255,255,255,0.32)',\n} as const;\n\nexport default function TravelCard() {\n const { colors } = useKit();\n const [tab, setTab] = useState('Coast');\n return (\n <View style={{ gap: 18 }}>\n <Header title=\"Explore\" sub=\"Stays our guests loved this month\" />\n <View style={{ flexDirection: 'row', gap: 8 }}>\n {['Coast', 'Mountains', 'Cities', 'Lakes'].map((t) => {\n const on = t === tab;\n return (\n <Pressable\n key={t}\n onPress={() => setTab(t)}\n accessibilityRole=\"tab\"\n accessibilityState={{ selected: on }}\n style={{ paddingHorizontal: 14, paddingVertical: 8, borderRadius: 999, backgroundColor: on ? colors.foreground : colors.muted }}\n >\n <Text style={{ color: on ? colors.background : colors.foreground, fontSize: 13, fontWeight: '600' }}>{t}</Text>\n </Pressable>\n );\n })}\n </View>\n <TiltCard idle image={photo(397, 540, 720)} radius={26} accessibilityLabel=\"Manarola, Cinque Terre\" style={{ width: 270, alignSelf: 'center' }}>\n <View style={{ height: 360 }} />\n <TiltLayer depth={1.2}>\n <View style={[glass, { position: 'absolute', top: 16, right: 16 }]}>\n <Text style={{ color: '#fff', fontSize: 12, fontWeight: '700' }}>★ 4.9</Text>\n </View>\n </TiltLayer>\n <TiltLayer depth={0.8}>\n <View style={{ position: 'absolute', left: 20, right: 20, bottom: 20, gap: 2 }}>\n <Text style={{ color: 'rgba(255,255,255,0.8)', fontSize: 12, fontWeight: '600', letterSpacing: 1 }}>CINQUE TERRE · ITALY</Text>\n <Text style={{ color: '#fff', fontSize: 30, fontWeight: '800', letterSpacing: -0.8 }}>Manarola</Text>\n <Text style={{ color: 'rgba(255,255,255,0.8)', fontSize: 13 }}>3 nights · from €140</Text>\n </View>\n </TiltLayer>\n </TiltCard>\n </View>\n );\n}\n"
139
+ "code": "import React, { useState } from 'react';\nimport { Image, Pressable, Text, View } from 'react-native';\nimport { TiltCard, TiltLayer } from '@/components/ui/tilt-card';\nimport { alpha, useKit } from '@/lib/kit';\n\n/** Any photo works (`require()` or a URL). */\nconst photo = (id: number, w: number, h: number) => ({ uri: `https://picsum.photos/id/${id}/${w}/${h}` });\n\n/** A screen title, as it sits above the card in your app. */\nfunction Header({ title, sub, right }: { title: string; sub?: string; right?: React.ReactNode }) {\n const { colors } = useKit();\n return (\n <View style={{ flexDirection: 'row', alignItems: 'center', gap: 12 }}>\n <View style={{ flex: 1 }}>\n <Text style={{ color: colors.foreground, fontSize: 22, fontWeight: '700', letterSpacing: -0.4 }}>{title}</Text>\n {sub ? <Text style={{ color: colors.mutedForeground, fontSize: 13, marginTop: 2 }}>{sub}</Text> : null}\n </View>\n {right}\n </View>\n );\n}\n\nconst glass = {\n paddingHorizontal: 10,\n paddingVertical: 5,\n borderRadius: 999,\n backgroundColor: 'rgba(255,255,255,0.18)',\n borderWidth: 1,\n borderColor: 'rgba(255,255,255,0.32)',\n} as const;\n\nexport default function TravelCard() {\n const { colors } = useKit();\n const [tab, setTab] = useState('Coast');\n return (\n <View style={{ gap: 18 }}>\n <Header title=\"Explore\" sub=\"Stays our guests loved this month\" />\n <View style={{ flexDirection: 'row', gap: 8 }}>\n {['Coast', 'Mountains', 'Cities', 'Lakes'].map((t) => {\n const on = t === tab;\n return (\n <Pressable\n key={t}\n onPress={() => setTab(t)}\n accessibilityRole=\"tab\"\n accessibilityState={{ selected: on }}\n style={{ paddingHorizontal: 14, paddingVertical: 8, borderRadius: 999, backgroundColor: on ? colors.foreground : colors.muted }}\n >\n <Text style={{ color: on ? colors.background : colors.foreground, fontSize: 13, fontWeight: '600' }}>{t}</Text>\n </Pressable>\n );\n })}\n </View>\n <TiltCard idle image={photo(397, 540, 720)} radius={26} accessibilityLabel=\"Manarola, Cinque Terre\" style={{ width: 270, alignSelf: 'center' }}>\n <View style={{ height: 360 }} />\n <TiltLayer depth={1.2}>\n <View style={[glass, { position: 'absolute', top: 16, right: 16 }]}>\n <Text style={{ color: '#fff', fontSize: 12, fontWeight: '700' }}>★ 4.9</Text>\n </View>\n </TiltLayer>\n <TiltLayer depth={0.8}>\n <View style={{ position: 'absolute', left: 20, right: 20, bottom: 20, gap: 2 }}>\n <Text style={{ color: 'rgba(255,255,255,0.8)', fontSize: 12, fontWeight: '600', letterSpacing: 1 }}>CINQUE TERRE · ITALY</Text>\n <Text style={{ color: '#fff', fontSize: 30, fontWeight: '800', letterSpacing: -0.8 }}>Manarola</Text>\n <Text style={{ color: 'rgba(255,255,255,0.8)', fontSize: 13 }}>3 nights · from €140</Text>\n </View>\n </TiltLayer>\n </TiltCard>\n </View>\n );\n}\n"
140
140
  },
141
141
  {
142
142
  "name": "NowPlaying",
143
143
  "title": "Now Playing",
144
- "code": "import React, { useState } from 'react';\nimport { Image, Pressable, Text, View } from 'react-native';\nimport { TiltCard, TiltLayer } from '@/components/ui/tilt-card';\nimport { alpha, useKit } from '@/lib/kit';\n\n/** Any photo works (`require()` or a URL). `blur` gives soft, abstract art. */\nconst photo = (id: number, w: number, h: number, blur = false) => ({ uri: `https://picsum.photos/id/${id}/${w}/${h}${blur ? '?blur=10' : ''}` });\n\nexport default function NowPlaying() {\n const { colors } = useKit();\n const [playing, setPlaying] = useState(true);\n return (\n <View style={{ alignItems: 'center', gap: 18 }}>\n <View style={{ alignSelf: 'stretch', flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between', marginBottom: 6 }}>\n <Text style={{ color: colors.foreground, fontSize: 20 }}>⌄</Text>\n <View style={{ alignItems: 'center' }}>\n <Text style={{ color: colors.mutedForeground, fontSize: 11, fontWeight: '700', letterSpacing: 1 }}>PLAYING FROM</Text>\n <Text style={{ color: colors.foreground, fontSize: 13, fontWeight: '600' }}>Late night drive</Text>\n </View>\n <Text style={{ color: colors.foreground, fontSize: 20 }}>⋯</Text>\n </View>\n <TiltCard idle image={photo(888, 300, 300, true)} shade={0.35} radius={20} accessibilityLabel=\"Midnight Bloom by Lumen\" style={{ width: 250 }}>\n <View style={{ height: 250 }} />\n <TiltLayer depth={1}>\n <View style={{ position: 'absolute', left: 18, bottom: 16 }}>\n <Text style={{ color: '#fff', fontSize: 30, fontWeight: '900', letterSpacing: -1, lineHeight: 30 }}>MIDNIGHT</Text>\n <Text style={{ color: '#fff', fontSize: 30, fontWeight: '300', letterSpacing: -1, lineHeight: 32 }}>bloom</Text>\n </View>\n </TiltLayer>\n </TiltCard>\n <View style={{ alignSelf: 'stretch', gap: 10 }}>\n <View>\n <Text style={{ color: colors.foreground, fontSize: 18, fontWeight: '700' }}>Midnight Bloom</Text>\n <Text style={{ color: colors.mutedForeground, fontSize: 14 }}>Lumen</Text>\n </View>\n <View style={{ height: 4, borderRadius: 2, backgroundColor: alpha(colors.mutedForeground, 0.25) }}>\n <View style={{ width: '42%', height: 4, borderRadius: 2, backgroundColor: colors.foreground }} />\n </View>\n <View style={{ flexDirection: 'row', justifyContent: 'space-between', marginTop: -4 }}>\n <Text style={{ color: colors.mutedForeground, fontSize: 11, fontVariant: ['tabular-nums'] }}>1:42</Text>\n <Text style={{ color: colors.mutedForeground, fontSize: 11, fontVariant: ['tabular-nums'] }}>−2:21</Text>\n </View>\n <View style={{ flexDirection: 'row', justifyContent: 'center', alignItems: 'center', gap: 34 }}>\n <Text style={{ color: colors.foreground, fontSize: 22 }}>⏮</Text>\n <Pressable\n onPress={() => setPlaying((p) => !p)}\n accessibilityRole=\"button\"\n accessibilityLabel={playing ? 'Pause' : 'Play'}\n style={{ width: 58, height: 58, borderRadius: 29, backgroundColor: colors.foreground, alignItems: 'center', justifyContent: 'center' }}\n >\n <Text style={{ color: colors.background, fontSize: 20 }}>{playing ? '❚❚' : '▶'}</Text>\n </Pressable>\n <Text style={{ color: colors.foreground, fontSize: 22 }}>⏭</Text>\n </View>\n </View>\n </View>\n );\n}\n"
144
+ "code": "import React, { useState } from 'react';\nimport { Image, Pressable, Text, View } from 'react-native';\nimport { GrainGradient } from '@/components/ui/grain';\nimport { TiltCard, TiltLayer } from '@/components/ui/tilt-card';\nimport { alpha, useKit } from '@/lib/kit';\n\nexport default function NowPlaying() {\n const { colors } = useKit();\n const [playing, setPlaying] = useState(true);\n return (\n <View style={{ alignItems: 'center', gap: 18 }}>\n <View style={{ alignSelf: 'stretch', flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between', marginBottom: 6 }}>\n <Text style={{ color: colors.foreground, fontSize: 20 }}>⌄</Text>\n <View style={{ alignItems: 'center' }}>\n <Text style={{ color: colors.mutedForeground, fontSize: 11, fontWeight: '700', letterSpacing: 1 }}>PLAYING FROM</Text>\n <Text style={{ color: colors.foreground, fontSize: 13, fontWeight: '600' }}>Late night drive</Text>\n </View>\n <Text style={{ color: colors.foreground, fontSize: 20 }}>⋯</Text>\n </View>\n <TiltCard idle radius={20} accessibilityLabel=\"Midnight Bloom by Lumen\" style={{ width: 250 }}>\n <GrainGradient palette=\"noir\" seed={1} style={{ height: 250 }} />\n <TiltLayer depth={1}>\n <View style={{ position: 'absolute', left: 18, bottom: 16 }}>\n <Text style={{ color: '#fff', fontSize: 30, fontWeight: '900', letterSpacing: -1, lineHeight: 30 }}>MIDNIGHT</Text>\n <Text style={{ color: '#fff', fontSize: 30, fontWeight: '300', letterSpacing: -1, lineHeight: 32 }}>bloom</Text>\n </View>\n </TiltLayer>\n </TiltCard>\n <View style={{ alignSelf: 'stretch', gap: 10 }}>\n <View>\n <Text style={{ color: colors.foreground, fontSize: 18, fontWeight: '700' }}>Midnight Bloom</Text>\n <Text style={{ color: colors.mutedForeground, fontSize: 14 }}>Lumen</Text>\n </View>\n <View style={{ height: 4, borderRadius: 2, backgroundColor: alpha(colors.mutedForeground, 0.25) }}>\n <View style={{ width: '42%', height: 4, borderRadius: 2, backgroundColor: colors.foreground }} />\n </View>\n <View style={{ flexDirection: 'row', justifyContent: 'space-between', marginTop: -4 }}>\n <Text style={{ color: colors.mutedForeground, fontSize: 11, fontVariant: ['tabular-nums'] }}>1:42</Text>\n <Text style={{ color: colors.mutedForeground, fontSize: 11, fontVariant: ['tabular-nums'] }}>−2:21</Text>\n </View>\n <View style={{ flexDirection: 'row', justifyContent: 'center', alignItems: 'center', gap: 34 }}>\n <Text style={{ color: colors.foreground, fontSize: 22 }}>⏮</Text>\n <Pressable\n onPress={() => setPlaying((p) => !p)}\n accessibilityRole=\"button\"\n accessibilityLabel={playing ? 'Pause' : 'Play'}\n style={{ width: 58, height: 58, borderRadius: 29, backgroundColor: colors.foreground, alignItems: 'center', justifyContent: 'center' }}\n >\n <Text style={{ color: colors.background, fontSize: 20 }}>{playing ? '❚❚' : '▶'}</Text>\n </Pressable>\n <Text style={{ color: colors.foreground, fontSize: 22 }}>⏭</Text>\n </View>\n </View>\n </View>\n );\n}\n"
145
145
  },
146
146
  {
147
147
  "name": "Product",
148
148
  "title": "Product",
149
- "code": "import React, { useState } from 'react';\nimport { Image, Pressable, Text, View } from 'react-native';\nimport { TiltCard, TiltLayer } from '@/components/ui/tilt-card';\nimport { alpha, useKit } from '@/lib/kit';\n\n/** Any photo works (`require()` or a URL). `blur` gives soft, abstract art. */\nconst photo = (id: number, w: number, h: number, blur = false) => ({ uri: `https://picsum.photos/id/${id}/${w}/${h}${blur ? '?blur=10' : ''}` });\n\n/** A screen title, as it sits above the card in your app. */\nfunction Header({ title, sub, right }: { title: string; sub?: string; right?: React.ReactNode }) {\n const { colors } = useKit();\n return (\n <View style={{ flexDirection: 'row', alignItems: 'center', gap: 12 }}>\n <View style={{ flex: 1 }}>\n <Text style={{ color: colors.foreground, fontSize: 22, fontWeight: '700', letterSpacing: -0.4 }}>{title}</Text>\n {sub ? <Text style={{ color: colors.mutedForeground, fontSize: 13, marginTop: 2 }}>{sub}</Text> : null}\n </View>\n {right}\n </View>\n );\n}\n\nexport default function Product() {\n const { colors } = useKit();\n return (\n <View style={{ gap: 18 }}>\n <Header\n title=\"New in\"\n sub=\"Film cameras, serviced and tested\"\n right={\n <View style={{ paddingHorizontal: 12, paddingVertical: 7, borderRadius: 999, backgroundColor: colors.muted }}>\n <Text style={{ color: colors.foreground, fontSize: 13, fontWeight: '600' }}>Bag · 2</Text>\n </View>\n }\n />\n <TiltCard maxTilt={14} radius={26} accessibilityLabel=\"Leica M6 film camera\" style={{ width: 280, alignSelf: 'center' }}>\n <View style={{ height: 360, backgroundColor: '#efece6' }} />\n <TiltLayer depth={1.4}>\n <View style={{ position: 'absolute', top: 26, left: 22, right: 22, height: 190, borderRadius: 18, overflow: 'hidden' }}>\n <Image alt=\"\" source={photo(250, 560, 380)} style={{ flex: 1 }} />\n </View>\n </TiltLayer>\n <TiltLayer depth={0.4}>\n <View style={{ position: 'absolute', left: 24, right: 24, bottom: 22, gap: 12 }}>\n <View>\n <Text style={{ color: '#8a8378', fontSize: 11, fontWeight: '700', letterSpacing: 1.2 }}>FILM CAMERA</Text>\n <Text style={{ color: '#1b1915', fontSize: 22, fontWeight: '800', letterSpacing: -0.4 }}>Rangefinder 35</Text>\n </View>\n <View style={{ flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between' }}>\n <Text style={{ color: '#1b1915', fontSize: 20, fontWeight: '700' }}>$1,290</Text>\n <View style={{ paddingHorizontal: 16, paddingVertical: 10, borderRadius: 999, backgroundColor: '#1b1915' }}>\n <Text style={{ color: '#fff', fontSize: 13, fontWeight: '700' }}>Add to bag</Text>\n </View>\n </View>\n </View>\n </TiltLayer>\n </TiltCard>\n <Text style={{ color: colors.mutedForeground, fontSize: 13, textAlign: 'center' }}>Free shipping · 2-year warranty</Text>\n </View>\n );\n}\n"
149
+ "code": "import React, { useState } from 'react';\nimport { Image, Pressable, Text, View } from 'react-native';\nimport { TiltCard, TiltLayer } from '@/components/ui/tilt-card';\nimport { alpha, useKit } from '@/lib/kit';\n\n/** Any photo works (`require()` or a URL). */\nconst photo = (id: number, w: number, h: number) => ({ uri: `https://picsum.photos/id/${id}/${w}/${h}` });\n\n/** A screen title, as it sits above the card in your app. */\nfunction Header({ title, sub, right }: { title: string; sub?: string; right?: React.ReactNode }) {\n const { colors } = useKit();\n return (\n <View style={{ flexDirection: 'row', alignItems: 'center', gap: 12 }}>\n <View style={{ flex: 1 }}>\n <Text style={{ color: colors.foreground, fontSize: 22, fontWeight: '700', letterSpacing: -0.4 }}>{title}</Text>\n {sub ? <Text style={{ color: colors.mutedForeground, fontSize: 13, marginTop: 2 }}>{sub}</Text> : null}\n </View>\n {right}\n </View>\n );\n}\n\nexport default function Product() {\n const { colors } = useKit();\n return (\n <View style={{ gap: 18 }}>\n <Header\n title=\"New in\"\n sub=\"Film cameras, serviced and tested\"\n right={\n <View style={{ paddingHorizontal: 12, paddingVertical: 7, borderRadius: 999, backgroundColor: colors.muted }}>\n <Text style={{ color: colors.foreground, fontSize: 13, fontWeight: '600' }}>Bag · 2</Text>\n </View>\n }\n />\n <TiltCard maxTilt={14} radius={26} accessibilityLabel=\"Leica M6 film camera\" style={{ width: 280, alignSelf: 'center' }}>\n <View style={{ height: 360, backgroundColor: '#efece6' }} />\n <TiltLayer depth={1.4}>\n <View style={{ position: 'absolute', top: 26, left: 22, right: 22, height: 190, borderRadius: 18, overflow: 'hidden' }}>\n <Image alt=\"\" source={photo(250, 560, 380)} style={{ flex: 1 }} />\n </View>\n </TiltLayer>\n <TiltLayer depth={0.4}>\n <View style={{ position: 'absolute', left: 24, right: 24, bottom: 22, gap: 12 }}>\n <View>\n <Text style={{ color: '#8a8378', fontSize: 11, fontWeight: '700', letterSpacing: 1.2 }}>FILM CAMERA</Text>\n <Text style={{ color: '#1b1915', fontSize: 22, fontWeight: '800', letterSpacing: -0.4 }}>Rangefinder 35</Text>\n </View>\n <View style={{ flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between' }}>\n <Text style={{ color: '#1b1915', fontSize: 20, fontWeight: '700' }}>$1,290</Text>\n <View style={{ paddingHorizontal: 16, paddingVertical: 10, borderRadius: 999, backgroundColor: '#1b1915' }}>\n <Text style={{ color: '#fff', fontSize: 13, fontWeight: '700' }}>Add to bag</Text>\n </View>\n </View>\n </View>\n </TiltLayer>\n </TiltCard>\n <Text style={{ color: colors.mutedForeground, fontSize: 13, textAlign: 'center' }}>Free shipping · 2-year warranty</Text>\n </View>\n );\n}\n"
150
150
  },
151
151
  {
152
152
  "name": "FeedCard",
153
153
  "title": "Feed Card",
154
- "code": "import React, { useState } from 'react';\nimport { Image, Pressable, Text, View } from 'react-native';\nimport { TiltCard, TiltLayer } from '@/components/ui/tilt-card';\nimport { alpha, useKit } from '@/lib/kit';\n\n/** Any photo works (`require()` or a URL). `blur` gives soft, abstract art. */\nconst photo = (id: number, w: number, h: number, blur = false) => ({ uri: `https://picsum.photos/id/${id}/${w}/${h}${blur ? '?blur=10' : ''}` });\n\n/** A screen title, as it sits above the card in your app. */\nfunction Header({ title, sub, right }: { title: string; sub?: string; right?: React.ReactNode }) {\n const { colors } = useKit();\n return (\n <View style={{ flexDirection: 'row', alignItems: 'center', gap: 12 }}>\n <View style={{ flex: 1 }}>\n <Text style={{ color: colors.foreground, fontSize: 22, fontWeight: '700', letterSpacing: -0.4 }}>{title}</Text>\n {sub ? <Text style={{ color: colors.mutedForeground, fontSize: 13, marginTop: 2 }}>{sub}</Text> : null}\n </View>\n {right}\n </View>\n );\n}\n\nexport default function FeedCard() {\n const { colors } = useKit();\n return (\n <View style={{ gap: 16 }}>\n <Header title=\"For you\" sub=\"Picked from places you saved\" />\n <TiltCard maxTilt={5} pressScale={1.015} glare=\"soft\" radius={20} onPress={() => {}} accessibilityLabel=\"Read: A fairytale weekend in Bavaria\" style={{ alignSelf: 'stretch' }}>\n <View style={{ backgroundColor: colors.card }}>\n <Image alt=\"\" source={photo(1040, 720, 360)} style={{ height: 170 }} />\n <View style={{ padding: 16, gap: 4 }}>\n <Text style={{ color: colors.mutedForeground, fontSize: 11, fontWeight: '700', letterSpacing: 1 }}>TRAVEL · 6 MIN READ</Text>\n <Text style={{ color: colors.foreground, fontSize: 18, fontWeight: '700', letterSpacing: -0.3 }}>A fairytale weekend in Bavaria</Text>\n <Text style={{ color: colors.mutedForeground, fontSize: 13, lineHeight: 19 }}>Castles, lakes and the best pretzels we had all year.</Text>\n </View>\n </View>\n </TiltCard>\n <View style={{ flexDirection: 'row', alignItems: 'center', gap: 12, padding: 10, borderRadius: 16, backgroundColor: colors.card }}>\n <Image alt=\"\" source={photo(419, 160, 160)} style={{ width: 56, height: 56, borderRadius: 12, backgroundColor: colors.muted }} />\n <View style={{ flex: 1 }}>\n <Text style={{ color: colors.mutedForeground, fontSize: 11, fontWeight: '700', letterSpacing: 1 }}>UP NEXT · 4 MIN READ</Text>\n <Text style={{ color: colors.foreground, fontSize: 15, fontWeight: '600' }}>Lisbon by tram, end to end</Text>\n </View>\n </View>\n </View>\n );\n}\n"
154
+ "code": "import React, { useState } from 'react';\nimport { Image, Pressable, Text, View } from 'react-native';\nimport { TiltCard, TiltLayer } from '@/components/ui/tilt-card';\nimport { alpha, useKit } from '@/lib/kit';\n\n/** Any photo works (`require()` or a URL). */\nconst photo = (id: number, w: number, h: number) => ({ uri: `https://picsum.photos/id/${id}/${w}/${h}` });\n\n/** A screen title, as it sits above the card in your app. */\nfunction Header({ title, sub, right }: { title: string; sub?: string; right?: React.ReactNode }) {\n const { colors } = useKit();\n return (\n <View style={{ flexDirection: 'row', alignItems: 'center', gap: 12 }}>\n <View style={{ flex: 1 }}>\n <Text style={{ color: colors.foreground, fontSize: 22, fontWeight: '700', letterSpacing: -0.4 }}>{title}</Text>\n {sub ? <Text style={{ color: colors.mutedForeground, fontSize: 13, marginTop: 2 }}>{sub}</Text> : null}\n </View>\n {right}\n </View>\n );\n}\n\nexport default function FeedCard() {\n const { colors } = useKit();\n return (\n <View style={{ gap: 16 }}>\n <Header title=\"For you\" sub=\"Picked from places you saved\" />\n <TiltCard maxTilt={5} pressScale={1.015} glare=\"soft\" radius={20} onPress={() => {}} accessibilityLabel=\"Read: A fairytale weekend in Bavaria\" style={{ alignSelf: 'stretch' }}>\n <View style={{ backgroundColor: colors.card }}>\n <Image alt=\"\" source={photo(1040, 720, 360)} style={{ height: 170 }} />\n <View style={{ padding: 16, gap: 4 }}>\n <Text style={{ color: colors.mutedForeground, fontSize: 11, fontWeight: '700', letterSpacing: 1 }}>TRAVEL · 6 MIN READ</Text>\n <Text style={{ color: colors.foreground, fontSize: 18, fontWeight: '700', letterSpacing: -0.3 }}>A fairytale weekend in Bavaria</Text>\n <Text style={{ color: colors.mutedForeground, fontSize: 13, lineHeight: 19 }}>Castles, lakes and the best pretzels we had all year.</Text>\n </View>\n </View>\n </TiltCard>\n <View style={{ flexDirection: 'row', alignItems: 'center', gap: 12, padding: 10, borderRadius: 16, backgroundColor: colors.card }}>\n <Image alt=\"\" source={photo(419, 160, 160)} style={{ width: 56, height: 56, borderRadius: 12, backgroundColor: colors.muted }} />\n <View style={{ flex: 1 }}>\n <Text style={{ color: colors.mutedForeground, fontSize: 11, fontWeight: '700', letterSpacing: 1 }}>UP NEXT · 4 MIN READ</Text>\n <Text style={{ color: colors.foreground, fontSize: 15, fontWeight: '600' }}>Lisbon by tram, end to end</Text>\n </View>\n </View>\n </View>\n );\n}\n"
155
155
  }
156
156
  ]
157
157
  }
@@ -238,32 +238,32 @@
238
238
  {
239
239
  "name": "Alarm",
240
240
  "title": "Alarm",
241
- "code": "import React, { useState } from 'react';\nimport { Pressable, StyleSheet, Text, View, type TextProps, type ViewProps } from 'react-native';\nimport { TimePicker, formatTime, type TimeValue } from '@/components/ui/time-picker';\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 s.card,\n { borderRadius: radius.xl, borderColor: colors.border, backgroundColor: colors.card },\n style,\n ]}\n {...rest}\n />\n );\n}\n\n/** Text in the kit's ink: `tone=\"muted\"` for secondary copy. */\nfunction Ink({ tone, style, ...rest }: TextProps & { tone?: 'muted' }) {\n const { colors } = useKit();\n return <Text style={[{ color: tone === 'muted' ? colors.mutedForeground : colors.foreground }, style]} {...rest} />;\n}\n\n/** \"in 7 h 20 min\" until the next occurrence of `t`. */\nfunction until(t: TimeValue) {\n const now = new Date();\n let diff = t.hours * 60 + t.minutes - (now.getHours() * 60 + now.getMinutes());\n if (diff <= 0) diff += 24 * 60;\n const h = Math.floor(diff / 60);\n return h ? `in ${h} h ${diff % 60} min` : `in ${diff} min`;\n}\n\nconst s = StyleSheet.create({\n card: { padding: 16, borderWidth: StyleSheet.hairlineWidth, gap: 12 },\n head: { flexDirection: 'row', justifyContent: 'space-between', alignItems: 'flex-start', gap: 12 },\n headText: { flexShrink: 1, gap: 2 },\n title: { fontSize: 15, fontWeight: '400' },\n meta: { fontSize: 13, fontWeight: '400', fontVariant: ['tabular-nums'] },\n hint: { fontSize: 12.5, textAlign: 'center' },\n note: { paddingVertical: 10, alignItems: 'center' },\n button: { height: 46, alignItems: 'center', justifyContent: 'center' },\n buttonOff: { opacity: 0.45 },\n buttonText: { fontSize: 15, fontWeight: '400' },\n});\n\nexport default function Alarm() {\n const [time, setTime] = useState<TimeValue>({ hours: 6, minutes: 45 });\n return (\n <Card>\n <View style={s.head}>\n <View style={s.headText}>\n <Ink style={s.title}>Wake up</Ink>\n <Ink tone=\"muted\" style={s.meta}>Weekdays · Radar</Ink>\n </View>\n <Ink tone=\"muted\" style={s.meta}>{until(time)}</Ink>\n </View>\n <TimePicker value={time} onChange={setTime} hour12 />\n </Card>\n );\n}\n"
241
+ "code": "import React, { useState } from 'react';\nimport { Pressable, StyleSheet, Text, View, type TextProps, type ViewProps } from 'react-native';\nimport { TimePicker, formatTime, type TimeValue } from '@/components/ui/time-picker';\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 s.card,\n screen\n ? { flex: 1, borderWidth: 0, backgroundColor: colors.background }\n : { borderRadius: radius.xl, borderColor: colors.border, backgroundColor: colors.card },\n style,\n ]}\n {...rest}\n />\n );\n}\n\n/** Text in the kit's ink: `tone=\"muted\"` for secondary copy. */\nfunction Ink({ tone, style, ...rest }: TextProps & { tone?: 'muted' }) {\n const { colors } = useKit();\n return <Text style={[{ color: tone === 'muted' ? colors.mutedForeground : colors.foreground }, style]} {...rest} />;\n}\n\n/** \"in 7 h 20 min\" until the next occurrence of `t`. */\nfunction until(t: TimeValue) {\n const now = new Date();\n let diff = t.hours * 60 + t.minutes - (now.getHours() * 60 + now.getMinutes());\n if (diff <= 0) diff += 24 * 60;\n const h = Math.floor(diff / 60);\n return h ? `in ${h} h ${diff % 60} min` : `in ${diff} min`;\n}\n\nconst s = StyleSheet.create({\n card: { padding: 16, borderWidth: StyleSheet.hairlineWidth, gap: 12 },\n head: { flexDirection: 'row', justifyContent: 'space-between', alignItems: 'flex-start', gap: 12 },\n headText: { flexShrink: 1, gap: 2 },\n title: { fontSize: 15, fontWeight: '400' },\n meta: { fontSize: 13, fontWeight: '400', fontVariant: ['tabular-nums'] },\n hint: { fontSize: 12.5, textAlign: 'center' },\n note: { paddingVertical: 10, alignItems: 'center' },\n button: { height: 46, alignItems: 'center', justifyContent: 'center' },\n buttonOff: { opacity: 0.45 },\n buttonText: { fontSize: 15, fontWeight: '400' },\n});\n\nexport default function Alarm({ screen }: DemoProps) {\n const [time, setTime] = useState<TimeValue>({ hours: 6, minutes: 45 });\n return (\n <Card screen={screen}>\n <View style={s.head}>\n <View style={s.headText}>\n <Ink style={s.title}>Wake up</Ink>\n <Ink tone=\"muted\" style={s.meta}>Weekdays · Radar</Ink>\n </View>\n <Ink tone=\"muted\" style={s.meta}>{until(time)}</Ink>\n </View>\n <TimePicker value={time} onChange={setTime} hour12 />\n </Card>\n );\n}\n"
242
242
  },
243
243
  {
244
244
  "name": "Clock",
245
245
  "title": "Clock",
246
- "code": "import React, { useState } from 'react';\nimport { Pressable, StyleSheet, Text, View, type TextProps, type ViewProps } from 'react-native';\nimport { TimePicker, formatTime, type TimeValue } from '@/components/ui/time-picker';\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 s.card,\n { borderRadius: radius.xl, borderColor: colors.border, backgroundColor: colors.card },\n style,\n ]}\n {...rest}\n />\n );\n}\n\n/** Text in the kit's ink: `tone=\"muted\"` for secondary copy. */\nfunction Ink({ tone, style, ...rest }: TextProps & { tone?: 'muted' }) {\n const { colors } = useKit();\n return <Text style={[{ color: tone === 'muted' ? colors.mutedForeground : colors.foreground }, style]} {...rest} />;\n}\n\n/** \"in 7 h 20 min\" until the next occurrence of `t`. */\nfunction until(t: TimeValue) {\n const now = new Date();\n let diff = t.hours * 60 + t.minutes - (now.getHours() * 60 + now.getMinutes());\n if (diff <= 0) diff += 24 * 60;\n const h = Math.floor(diff / 60);\n return h ? `in ${h} h ${diff % 60} min` : `in ${diff} min`;\n}\n\nconst s = StyleSheet.create({\n card: { padding: 16, borderWidth: StyleSheet.hairlineWidth, gap: 12 },\n head: { flexDirection: 'row', justifyContent: 'space-between', alignItems: 'flex-start', gap: 12 },\n headText: { flexShrink: 1, gap: 2 },\n title: { fontSize: 15, fontWeight: '400' },\n meta: { fontSize: 13, fontWeight: '400', fontVariant: ['tabular-nums'] },\n hint: { fontSize: 12.5, textAlign: 'center' },\n note: { paddingVertical: 10, alignItems: 'center' },\n button: { height: 46, alignItems: 'center', justifyContent: 'center' },\n buttonOff: { opacity: 0.45 },\n buttonText: { fontSize: 15, fontWeight: '400' },\n});\n\nexport default function Clock() {\n const { colors, radius } = useKit();\n const [time, setTime] = useState<TimeValue>({ hours: 7, minutes: 30 });\n return (\n <Card>\n <View style={s.headText}>\n <Ink style={s.title}>Morning run</Ink>\n <Ink tone=\"muted\" style={s.meta}>Tap the hour or minute, then drag the dial.</Ink>\n </View>\n <TimePicker variant=\"clock\" value={time} onChange={setTime} hour12 />\n <View style={[s.note, { backgroundColor: colors.muted, borderRadius: radius.md }]}>\n <Ink tone=\"muted\" style={s.meta}>Rings {until(time)}</Ink>\n </View>\n </Card>\n );\n}\n"
246
+ "code": "import React, { useState } from 'react';\nimport { Pressable, StyleSheet, Text, View, type TextProps, type ViewProps } from 'react-native';\nimport { TimePicker, formatTime, type TimeValue } from '@/components/ui/time-picker';\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 s.card,\n screen\n ? { flex: 1, borderWidth: 0, backgroundColor: colors.background }\n : { borderRadius: radius.xl, borderColor: colors.border, backgroundColor: colors.card },\n style,\n ]}\n {...rest}\n />\n );\n}\n\n/** Text in the kit's ink: `tone=\"muted\"` for secondary copy. */\nfunction Ink({ tone, style, ...rest }: TextProps & { tone?: 'muted' }) {\n const { colors } = useKit();\n return <Text style={[{ color: tone === 'muted' ? colors.mutedForeground : colors.foreground }, style]} {...rest} />;\n}\n\n/** \"in 7 h 20 min\" until the next occurrence of `t`. */\nfunction until(t: TimeValue) {\n const now = new Date();\n let diff = t.hours * 60 + t.minutes - (now.getHours() * 60 + now.getMinutes());\n if (diff <= 0) diff += 24 * 60;\n const h = Math.floor(diff / 60);\n return h ? `in ${h} h ${diff % 60} min` : `in ${diff} min`;\n}\n\nconst s = StyleSheet.create({\n card: { padding: 16, borderWidth: StyleSheet.hairlineWidth, gap: 12 },\n head: { flexDirection: 'row', justifyContent: 'space-between', alignItems: 'flex-start', gap: 12 },\n headText: { flexShrink: 1, gap: 2 },\n title: { fontSize: 15, fontWeight: '400' },\n meta: { fontSize: 13, fontWeight: '400', fontVariant: ['tabular-nums'] },\n hint: { fontSize: 12.5, textAlign: 'center' },\n note: { paddingVertical: 10, alignItems: 'center' },\n button: { height: 46, alignItems: 'center', justifyContent: 'center' },\n buttonOff: { opacity: 0.45 },\n buttonText: { fontSize: 15, fontWeight: '400' },\n});\n\nexport default function Clock({ screen }: DemoProps) {\n const { colors, radius } = useKit();\n const [time, setTime] = useState<TimeValue>({ hours: 7, minutes: 30 });\n return (\n <Card screen={screen}>\n <View style={s.headText}>\n <Ink style={s.title}>Morning run</Ink>\n <Ink tone=\"muted\" style={s.meta}>Tap the hour or minute, then drag the dial.</Ink>\n </View>\n <TimePicker variant=\"clock\" value={time} onChange={setTime} hour12 />\n <View style={[s.note, { backgroundColor: colors.muted, borderRadius: radius.md }]}>\n <Ink tone=\"muted\" style={s.meta}>Rings {until(time)}</Ink>\n </View>\n </Card>\n );\n}\n"
247
247
  },
248
248
  {
249
249
  "name": "Appointment",
250
250
  "title": "Appointment",
251
- "code": "import React, { useState } from 'react';\nimport { Pressable, StyleSheet, Text, View, type TextProps, type ViewProps } from 'react-native';\nimport { TimePicker, formatTime, type TimeValue } from '@/components/ui/time-picker';\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 s.card,\n { borderRadius: radius.xl, borderColor: colors.border, backgroundColor: colors.card },\n style,\n ]}\n {...rest}\n />\n );\n}\n\n/** Text in the kit's ink: `tone=\"muted\"` for secondary copy. */\nfunction Ink({ tone, style, ...rest }: TextProps & { tone?: 'muted' }) {\n const { colors } = useKit();\n return <Text style={[{ color: tone === 'muted' ? colors.mutedForeground : colors.foreground }, style]} {...rest} />;\n}\n\nconst s = StyleSheet.create({\n card: { padding: 16, borderWidth: StyleSheet.hairlineWidth, gap: 12 },\n head: { flexDirection: 'row', justifyContent: 'space-between', alignItems: 'flex-start', gap: 12 },\n headText: { flexShrink: 1, gap: 2 },\n title: { fontSize: 15, fontWeight: '400' },\n meta: { fontSize: 13, fontWeight: '400', fontVariant: ['tabular-nums'] },\n hint: { fontSize: 12.5, textAlign: 'center' },\n note: { paddingVertical: 10, alignItems: 'center' },\n button: { height: 46, alignItems: 'center', justifyContent: 'center' },\n buttonOff: { opacity: 0.45 },\n buttonText: { fontSize: 15, fontWeight: '400' },\n});\n\nexport default function Appointment() {\n const { colors, radius } = useKit();\n const [slot, setSlot] = useState<TimeValue | null>(null);\n return (\n <Card>\n <Ink style={s.title}>Dr. Amara Okafor · Dermatology</Ink>\n <TimePicker\n variant=\"chips\"\n value={slot}\n onChange={setSlot}\n hour12\n startTime=\"09:00\"\n endTime=\"18:00\"\n minuteInterval={30}\n bookedTimes={['09:30', '10:00', '11:30', '13:00', '14:30', '15:00', '17:30']}\n disabledTimes={['12:00', '12:30']}\n groupByPeriod\n />\n <Pressable\n disabled={!slot}\n style={[s.button, { backgroundColor: colors.accent, borderRadius: radius.md }, !slot && s.buttonOff]}\n accessibilityRole=\"button\"\n >\n <Text style={[s.buttonText, { color: colors.accentForeground }]}>{slot ? `Book ${formatTime(slot)} · 30 min` : 'Choose a time'}</Text>\n </Pressable>\n </Card>\n );\n}\n"
251
+ "code": "import React, { useState } from 'react';\nimport { Pressable, StyleSheet, Text, View, type TextProps, type ViewProps } from 'react-native';\nimport { TimePicker, formatTime, type TimeValue } from '@/components/ui/time-picker';\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 s.card,\n screen\n ? { flex: 1, borderWidth: 0, backgroundColor: colors.background }\n : { borderRadius: radius.xl, borderColor: colors.border, backgroundColor: colors.card },\n style,\n ]}\n {...rest}\n />\n );\n}\n\n/** Text in the kit's ink: `tone=\"muted\"` for secondary copy. */\nfunction Ink({ tone, style, ...rest }: TextProps & { tone?: 'muted' }) {\n const { colors } = useKit();\n return <Text style={[{ color: tone === 'muted' ? colors.mutedForeground : colors.foreground }, style]} {...rest} />;\n}\n\nconst s = StyleSheet.create({\n card: { padding: 16, borderWidth: StyleSheet.hairlineWidth, gap: 12 },\n head: { flexDirection: 'row', justifyContent: 'space-between', alignItems: 'flex-start', gap: 12 },\n headText: { flexShrink: 1, gap: 2 },\n title: { fontSize: 15, fontWeight: '400' },\n meta: { fontSize: 13, fontWeight: '400', fontVariant: ['tabular-nums'] },\n hint: { fontSize: 12.5, textAlign: 'center' },\n note: { paddingVertical: 10, alignItems: 'center' },\n button: { height: 46, alignItems: 'center', justifyContent: 'center' },\n buttonOff: { opacity: 0.45 },\n buttonText: { fontSize: 15, fontWeight: '400' },\n});\n\nexport default function Appointment({ screen }: DemoProps) {\n const { colors, radius } = useKit();\n const [slot, setSlot] = useState<TimeValue | null>(null);\n return (\n <Card screen={screen}>\n <Ink style={s.title}>Dr. Amara Okafor · Dermatology</Ink>\n <TimePicker\n variant=\"chips\"\n value={slot}\n onChange={setSlot}\n hour12\n startTime=\"09:00\"\n endTime=\"18:00\"\n minuteInterval={30}\n bookedTimes={['09:30', '10:00', '11:30', '13:00', '14:30', '15:00', '17:30']}\n disabledTimes={['12:00', '12:30']}\n groupByPeriod\n />\n <Pressable\n disabled={!slot}\n style={[s.button, { backgroundColor: colors.accent, borderRadius: radius.md }, !slot && s.buttonOff]}\n accessibilityRole=\"button\"\n >\n <Text style={[s.buttonText, { color: colors.accentForeground }]}>{slot ? `Book ${formatTime(slot)} · 30 min` : 'Choose a time'}</Text>\n </Pressable>\n </Card>\n );\n}\n"
252
252
  },
253
253
  {
254
254
  "name": "Reminder",
255
255
  "title": "Reminder",
256
- "code": "import React, { useState } from 'react';\nimport { Pressable, StyleSheet, Text, View, type TextProps, type ViewProps } from 'react-native';\nimport { TimePicker, formatTime, type TimeValue } from '@/components/ui/time-picker';\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 s.card,\n { borderRadius: radius.xl, borderColor: colors.border, backgroundColor: colors.card },\n style,\n ]}\n {...rest}\n />\n );\n}\n\n/** Text in the kit's ink: `tone=\"muted\"` for secondary copy. */\nfunction Ink({ tone, style, ...rest }: TextProps & { tone?: 'muted' }) {\n const { colors } = useKit();\n return <Text style={[{ color: tone === 'muted' ? colors.mutedForeground : colors.foreground }, style]} {...rest} />;\n}\n\nconst s = StyleSheet.create({\n card: { padding: 16, borderWidth: StyleSheet.hairlineWidth, gap: 12 },\n head: { flexDirection: 'row', justifyContent: 'space-between', alignItems: 'flex-start', gap: 12 },\n headText: { flexShrink: 1, gap: 2 },\n title: { fontSize: 15, fontWeight: '400' },\n meta: { fontSize: 13, fontWeight: '400', fontVariant: ['tabular-nums'] },\n hint: { fontSize: 12.5, textAlign: 'center' },\n note: { paddingVertical: 10, alignItems: 'center' },\n button: { height: 46, alignItems: 'center', justifyContent: 'center' },\n buttonOff: { opacity: 0.45 },\n buttonText: { fontSize: 15, fontWeight: '400' },\n});\n\nexport default function Reminder() {\n const [time, setTime] = useState<TimeValue>({ hours: 21, minutes: 30 });\n return (\n <Card>\n <View style={s.head}>\n <Ink style={s.title}>Daily journal reminder</Ink>\n <Ink tone=\"muted\" style={s.meta}>{formatTime(time, false)}</Ink>\n </View>\n <TimePicker value={time} onChange={setTime} hour12={false} minuteInterval={15} />\n <Ink tone=\"muted\" style={s.hint}>24-hour clock, 15-minute steps.</Ink>\n </Card>\n );\n}\n"
256
+ "code": "import React, { useState } from 'react';\nimport { Pressable, StyleSheet, Text, View, type TextProps, type ViewProps } from 'react-native';\nimport { TimePicker, formatTime, type TimeValue } from '@/components/ui/time-picker';\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 s.card,\n screen\n ? { flex: 1, borderWidth: 0, backgroundColor: colors.background }\n : { borderRadius: radius.xl, borderColor: colors.border, backgroundColor: colors.card },\n style,\n ]}\n {...rest}\n />\n );\n}\n\n/** Text in the kit's ink: `tone=\"muted\"` for secondary copy. */\nfunction Ink({ tone, style, ...rest }: TextProps & { tone?: 'muted' }) {\n const { colors } = useKit();\n return <Text style={[{ color: tone === 'muted' ? colors.mutedForeground : colors.foreground }, style]} {...rest} />;\n}\n\nconst s = StyleSheet.create({\n card: { padding: 16, borderWidth: StyleSheet.hairlineWidth, gap: 12 },\n head: { flexDirection: 'row', justifyContent: 'space-between', alignItems: 'flex-start', gap: 12 },\n headText: { flexShrink: 1, gap: 2 },\n title: { fontSize: 15, fontWeight: '400' },\n meta: { fontSize: 13, fontWeight: '400', fontVariant: ['tabular-nums'] },\n hint: { fontSize: 12.5, textAlign: 'center' },\n note: { paddingVertical: 10, alignItems: 'center' },\n button: { height: 46, alignItems: 'center', justifyContent: 'center' },\n buttonOff: { opacity: 0.45 },\n buttonText: { fontSize: 15, fontWeight: '400' },\n});\n\nexport default function Reminder({ screen }: DemoProps) {\n const [time, setTime] = useState<TimeValue>({ hours: 21, minutes: 30 });\n return (\n <Card screen={screen}>\n <View style={s.head}>\n <Ink style={s.title}>Daily journal reminder</Ink>\n <Ink tone=\"muted\" style={s.meta}>{formatTime(time, false)}</Ink>\n </View>\n <TimePicker value={time} onChange={setTime} hour12={false} minuteInterval={15} />\n <Ink tone=\"muted\" style={s.hint}>24-hour clock, 15-minute steps.</Ink>\n </Card>\n );\n}\n"
257
257
  },
258
258
  {
259
259
  "name": "InSheet",
260
260
  "title": "In Sheet",
261
- "code": "import React, { useState } from 'react';\nimport { Pressable, StyleSheet, Text, View, type TextProps, type ViewProps } from 'react-native';\nimport { TimePicker, formatTime, type TimeValue } from '@/components/ui/time-picker';\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 s.card,\n { borderRadius: radius.xl, borderColor: colors.border, backgroundColor: colors.card },\n style,\n ]}\n {...rest}\n />\n );\n}\n\n/** Text in the kit's ink: `tone=\"muted\"` for secondary copy. */\nfunction Ink({ tone, style, ...rest }: TextProps & { tone?: 'muted' }) {\n const { colors } = useKit();\n return <Text style={[{ color: tone === 'muted' ? colors.mutedForeground : colors.foreground }, style]} {...rest} />;\n}\n\nconst s = StyleSheet.create({\n card: { padding: 16, borderWidth: StyleSheet.hairlineWidth, gap: 12 },\n head: { flexDirection: 'row', justifyContent: 'space-between', alignItems: 'flex-start', gap: 12 },\n headText: { flexShrink: 1, gap: 2 },\n title: { fontSize: 15, fontWeight: '400' },\n meta: { fontSize: 13, fontWeight: '400', fontVariant: ['tabular-nums'] },\n hint: { fontSize: 12.5, textAlign: 'center' },\n note: { paddingVertical: 10, alignItems: 'center' },\n button: { height: 46, alignItems: 'center', justifyContent: 'center' },\n buttonOff: { opacity: 0.45 },\n buttonText: { fontSize: 15, fontWeight: '400' },\n});\n\nexport default function InSheet() {\n const [pickup, setPickup] = useState<TimeValue | null>({ hours: 18, minutes: 15 });\n const [delivery, setDelivery] = useState<TimeValue | null>(null);\n return (\n <Card>\n <Ink style={s.title}>Order #4821 · Green Basket</Ink>\n <TimePicker presentation=\"sheet\" label=\"Pickup time\" title=\"When will you pick up?\" value={pickup} onChange={setPickup} minuteInterval={5} hour12 />\n <TimePicker\n presentation=\"sheet\"\n variant=\"chips\"\n label=\"Delivery slot\"\n title=\"Choose a delivery slot\"\n value={delivery}\n onChange={setDelivery}\n hour12\n startTime=\"08:00\"\n endTime=\"21:00\"\n minuteInterval={60}\n bookedTimes={['08:00', '12:00', '18:00']}\n groupByPeriod\n success={delivery ? 'Free delivery in this slot' : undefined}\n helper=\"We'll text you when the driver is close.\"\n />\n </Card>\n );\n}\n"
261
+ "code": "import React, { useState } from 'react';\nimport { Pressable, StyleSheet, Text, View, type TextProps, type ViewProps } from 'react-native';\nimport { TimePicker, formatTime, type TimeValue } from '@/components/ui/time-picker';\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 s.card,\n screen\n ? { flex: 1, borderWidth: 0, backgroundColor: colors.background }\n : { borderRadius: radius.xl, borderColor: colors.border, backgroundColor: colors.card },\n style,\n ]}\n {...rest}\n />\n );\n}\n\n/** Text in the kit's ink: `tone=\"muted\"` for secondary copy. */\nfunction Ink({ tone, style, ...rest }: TextProps & { tone?: 'muted' }) {\n const { colors } = useKit();\n return <Text style={[{ color: tone === 'muted' ? colors.mutedForeground : colors.foreground }, style]} {...rest} />;\n}\n\nconst s = StyleSheet.create({\n card: { padding: 16, borderWidth: StyleSheet.hairlineWidth, gap: 12 },\n head: { flexDirection: 'row', justifyContent: 'space-between', alignItems: 'flex-start', gap: 12 },\n headText: { flexShrink: 1, gap: 2 },\n title: { fontSize: 15, fontWeight: '400' },\n meta: { fontSize: 13, fontWeight: '400', fontVariant: ['tabular-nums'] },\n hint: { fontSize: 12.5, textAlign: 'center' },\n note: { paddingVertical: 10, alignItems: 'center' },\n button: { height: 46, alignItems: 'center', justifyContent: 'center' },\n buttonOff: { opacity: 0.45 },\n buttonText: { fontSize: 15, fontWeight: '400' },\n});\n\nexport default function InSheet({ screen }: DemoProps) {\n const [pickup, setPickup] = useState<TimeValue | null>({ hours: 18, minutes: 15 });\n const [delivery, setDelivery] = useState<TimeValue | null>(null);\n return (\n <Card screen={screen}>\n <Ink style={s.title}>Order #4821 · Green Basket</Ink>\n <TimePicker presentation=\"sheet\" label=\"Pickup time\" title=\"When will you pick up?\" value={pickup} onChange={setPickup} minuteInterval={5} hour12 />\n <TimePicker\n presentation=\"sheet\"\n variant=\"chips\"\n label=\"Delivery slot\"\n title=\"Choose a delivery slot\"\n value={delivery}\n onChange={setDelivery}\n hour12\n startTime=\"08:00\"\n endTime=\"21:00\"\n minuteInterval={60}\n bookedTimes={['08:00', '12:00', '18:00']}\n groupByPeriod\n success={delivery ? 'Free delivery in this slot' : undefined}\n helper=\"We'll text you when the driver is close.\"\n />\n </Card>\n );\n}\n"
262
262
  },
263
263
  {
264
264
  "name": "ClockInSheet",
265
265
  "title": "Clock In Sheet",
266
- "code": "import React, { useState } from 'react';\nimport { Pressable, StyleSheet, Text, View, type TextProps, type ViewProps } from 'react-native';\nimport { TimePicker, formatTime, type TimeValue } from '@/components/ui/time-picker';\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 s.card,\n { borderRadius: radius.xl, borderColor: colors.border, backgroundColor: colors.card },\n style,\n ]}\n {...rest}\n />\n );\n}\n\n/** Text in the kit's ink: `tone=\"muted\"` for secondary copy. */\nfunction Ink({ tone, style, ...rest }: TextProps & { tone?: 'muted' }) {\n const { colors } = useKit();\n return <Text style={[{ color: tone === 'muted' ? colors.mutedForeground : colors.foreground }, style]} {...rest} />;\n}\n\nconst s = StyleSheet.create({\n card: { padding: 16, borderWidth: StyleSheet.hairlineWidth, gap: 12 },\n head: { flexDirection: 'row', justifyContent: 'space-between', alignItems: 'flex-start', gap: 12 },\n headText: { flexShrink: 1, gap: 2 },\n title: { fontSize: 15, fontWeight: '400' },\n meta: { fontSize: 13, fontWeight: '400', fontVariant: ['tabular-nums'] },\n hint: { fontSize: 12.5, textAlign: 'center' },\n note: { paddingVertical: 10, alignItems: 'center' },\n button: { height: 46, alignItems: 'center', justifyContent: 'center' },\n buttonOff: { opacity: 0.45 },\n buttonText: { fontSize: 15, fontWeight: '400' },\n});\n\nexport default function ClockInSheet() {\n const [start, setStart] = useState<TimeValue | null>({ hours: 9, minutes: 0 });\n const [end, setEnd] = useState<TimeValue | null>(null);\n return (\n <Card>\n <Ink style={s.title}>Focus block · Thursday</Ink>\n <TimePicker presentation=\"sheet\" variant=\"clock\" label=\"Starts\" title=\"Start time\" value={start} onChange={setStart} hour12 />\n <TimePicker\n presentation=\"sheet\"\n variant=\"clock\"\n label=\"Ends\"\n title=\"End time\"\n value={end}\n onChange={setEnd}\n minuteInterval={5}\n hour12\n helper=\"Snaps to 5-minute steps.\"\n />\n </Card>\n );\n}\n"
266
+ "code": "import React, { useState } from 'react';\nimport { Pressable, StyleSheet, Text, View, type TextProps, type ViewProps } from 'react-native';\nimport { TimePicker, formatTime, type TimeValue } from '@/components/ui/time-picker';\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 s.card,\n screen\n ? { flex: 1, borderWidth: 0, backgroundColor: colors.background }\n : { borderRadius: radius.xl, borderColor: colors.border, backgroundColor: colors.card },\n style,\n ]}\n {...rest}\n />\n );\n}\n\n/** Text in the kit's ink: `tone=\"muted\"` for secondary copy. */\nfunction Ink({ tone, style, ...rest }: TextProps & { tone?: 'muted' }) {\n const { colors } = useKit();\n return <Text style={[{ color: tone === 'muted' ? colors.mutedForeground : colors.foreground }, style]} {...rest} />;\n}\n\nconst s = StyleSheet.create({\n card: { padding: 16, borderWidth: StyleSheet.hairlineWidth, gap: 12 },\n head: { flexDirection: 'row', justifyContent: 'space-between', alignItems: 'flex-start', gap: 12 },\n headText: { flexShrink: 1, gap: 2 },\n title: { fontSize: 15, fontWeight: '400' },\n meta: { fontSize: 13, fontWeight: '400', fontVariant: ['tabular-nums'] },\n hint: { fontSize: 12.5, textAlign: 'center' },\n note: { paddingVertical: 10, alignItems: 'center' },\n button: { height: 46, alignItems: 'center', justifyContent: 'center' },\n buttonOff: { opacity: 0.45 },\n buttonText: { fontSize: 15, fontWeight: '400' },\n});\n\nexport default function ClockInSheet({ screen }: DemoProps) {\n const [start, setStart] = useState<TimeValue | null>({ hours: 9, minutes: 0 });\n const [end, setEnd] = useState<TimeValue | null>(null);\n return (\n <Card screen={screen}>\n <Ink style={s.title}>Focus block · Thursday</Ink>\n <TimePicker presentation=\"sheet\" variant=\"clock\" label=\"Starts\" title=\"Start time\" value={start} onChange={setStart} hour12 />\n <TimePicker\n presentation=\"sheet\"\n variant=\"clock\"\n label=\"Ends\"\n title=\"End time\"\n value={end}\n onChange={setEnd}\n minuteInterval={5}\n hour12\n helper=\"Snaps to 5-minute steps.\"\n />\n </Card>\n );\n}\n"
267
267
  }
268
268
  ]
269
269
  }
@@ -161,12 +161,12 @@
161
161
  {
162
162
  "name": "Journey",
163
163
  "title": "Journey",
164
- "code": "import { Text, View } from 'react-native';\nimport { Timeline } from '@/components/ui/timeline';\nimport { alpha, useKit } from '@/lib/kit';\n\nconst pic = (seed: string) => `https://picsum.photos/seed/${seed}/600/400`;\n\nexport default function Journey() {\n const { colors } = useKit();\n return (\n <Timeline\n variant=\"scroll\"\n style={{ height: 560 }}\n header={\n <View style={{ gap: 2, paddingBottom: 20 }}>\n <Text style={{ fontSize: 12.5, letterSpacing: 0.4, color: colors.mutedForeground }}>12 DAYS · 1,140 KM</Text>\n <Text style={{ fontSize: 22, letterSpacing: -0.5, color: colors.foreground }}>Our journey through Portugal</Text>\n </View>\n }\n items={[\n { title: 'Landed in Lisbon', time: 'Day 1', description: 'Late flight, pastel de nata at the airport, tram 28 into Alfama before the rain.', images: [{ uri: pic('lisbon'), aspectRatio: 3 / 2 }], tags: ['Lisbon'] },\n { title: 'Lost in Alfama', time: 'Day 2', description: 'Stairs, tiles, a fado bar that only had six chairs. We stayed until two.', images: [pic('alfama'), pic('tiles')] },\n { title: 'Sintra day trip', time: 'Day 3', description: 'Fog on Pena Palace in the morning, sun in the Moorish castle by noon.', images: [pic('sintra'), pic('palace'), pic('forest'), pic('castle')] },\n { title: 'Rented the car', time: 'Day 4', description: 'A tiny Fiat with a stubborn second gear. Named it Rosa.' },\n { title: 'Comporta beaches', time: 'Day 5', description: 'Rice fields on one side, empty dunes on the other.', images: [{ uri: pic('dunes'), aspectRatio: 16 / 9 }], tags: ['Beach', 'Alentejo'] },\n { title: 'Évora', time: 'Day 6', description: 'Roman temple at sunset and the quietest night of the trip.', images: [pic('evora'), pic('temple'), pic('plains')] },\n { title: 'Wine in the Alentejo', time: 'Day 7', description: 'A cellar tour that turned into lunch with the owner’s family. Ordered a case home.' },\n { title: 'Down to the Algarve', time: 'Day 8', description: 'Benagil caves by kayak — go early, the tour boats arrive at ten.', images: [pic('caves'), pic('kayak')], links: [{ label: 'Kayak rental we used', onPress: () => {} }] },\n { title: 'Lagos', time: 'Day 9', description: 'Cliff walk to Ponta da Piedade and grilled sardines at the harbour.', images: [{ uri: pic('lagos'), aspectRatio: 3 / 2 }] },\n { title: 'Long drive north', time: 'Day 10', description: 'Six hours, two coffee stops, one wrong turn into a cork forest.' },\n { title: 'Porto', time: 'Day 11', status: 'current', description: 'Ribeira, the bridge at blue hour, port lodges across the river.', images: [pic('porto'), pic('bridge'), pic('river')], tags: ['Now'] },\n { title: 'Douro Valley', time: 'Day 12', status: 'upcoming', description: 'Train along the river to Pinhão, then home.' },\n ]}\n />\n );\n}\n"
164
+ "code": "import { Text, View } from 'react-native';\nimport { Timeline } from '@/components/ui/timeline';\nimport { alpha, 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\nconst pic = (seed: string) => `https://picsum.photos/seed/${seed}/600/400`;\n\nexport default function Journey({ screen }: DemoProps) {\n const { colors } = useKit();\n return (\n <Timeline\n variant=\"scroll\"\n style={screen ? { flex: 1 } : { height: 560 }}\n header={\n <View style={{ gap: 2, paddingBottom: 20 }}>\n <Text style={{ fontSize: 12.5, letterSpacing: 0.4, color: colors.mutedForeground }}>12 DAYS · 1,140 KM</Text>\n <Text style={{ fontSize: 22, letterSpacing: -0.5, color: colors.foreground }}>Our journey through Portugal</Text>\n </View>\n }\n items={[\n { title: 'Landed in Lisbon', time: 'Day 1', description: 'Late flight, pastel de nata at the airport, tram 28 into Alfama before the rain.', images: [{ uri: pic('lisbon'), aspectRatio: 3 / 2 }], tags: ['Lisbon'] },\n { title: 'Lost in Alfama', time: 'Day 2', description: 'Stairs, tiles, a fado bar that only had six chairs. We stayed until two.', images: [pic('alfama'), pic('tiles')] },\n { title: 'Sintra day trip', time: 'Day 3', description: 'Fog on Pena Palace in the morning, sun in the Moorish castle by noon.', images: [pic('sintra'), pic('palace'), pic('forest'), pic('castle')] },\n { title: 'Rented the car', time: 'Day 4', description: 'A tiny Fiat with a stubborn second gear. Named it Rosa.' },\n { title: 'Comporta beaches', time: 'Day 5', description: 'Rice fields on one side, empty dunes on the other.', images: [{ uri: pic('dunes'), aspectRatio: 16 / 9 }], tags: ['Beach', 'Alentejo'] },\n { title: 'Évora', time: 'Day 6', description: 'Roman temple at sunset and the quietest night of the trip.', images: [pic('evora'), pic('temple'), pic('plains')] },\n { title: 'Wine in the Alentejo', time: 'Day 7', description: 'A cellar tour that turned into lunch with the owner’s family. Ordered a case home.' },\n { title: 'Down to the Algarve', time: 'Day 8', description: 'Benagil caves by kayak — go early, the tour boats arrive at ten.', images: [pic('caves'), pic('kayak')], links: [{ label: 'Kayak rental we used', onPress: () => {} }] },\n { title: 'Lagos', time: 'Day 9', description: 'Cliff walk to Ponta da Piedade and grilled sardines at the harbour.', images: [{ uri: pic('lagos'), aspectRatio: 3 / 2 }] },\n { title: 'Long drive north', time: 'Day 10', description: 'Six hours, two coffee stops, one wrong turn into a cork forest.' },\n { title: 'Porto', time: 'Day 11', status: 'current', description: 'Ribeira, the bridge at blue hour, port lodges across the river.', images: [pic('porto'), pic('bridge'), pic('river')], tags: ['Now'] },\n { title: 'Douro Valley', time: 'Day 12', status: 'upcoming', description: 'Train along the river to Pinhão, then home.' },\n ]}\n />\n );\n}\n"
165
165
  },
166
166
  {
167
167
  "name": "DeliveryScroll",
168
168
  "title": "Delivery Scroll",
169
- "code": "import { Text, View } from 'react-native';\nimport { Timeline } from '@/components/ui/timeline';\nimport { alpha, useKit } from '@/lib/kit';\n\nconst pic = (seed: string) => `https://picsum.photos/seed/${seed}/600/400`;\n\nexport default function DeliveryScroll() {\n const { colors } = useKit();\n return (\n <Timeline\n variant=\"scroll\"\n style={{ height: 560 }}\n header={\n <View style={{ gap: 2, paddingBottom: 20 }}>\n <Text style={{ fontSize: 12.5, letterSpacing: 0.4, color: colors.mutedForeground }}>PARCEL DHL 4471 9902 31</Text>\n <Text style={{ fontSize: 20, letterSpacing: -0.4, color: colors.foreground }}>Out for delivery</Text>\n </View>\n }\n items={[\n { title: 'Order placed', time: 'Oct 6 · 18:22', description: 'Walnut desk lamp, linen shade · paid with Visa', icon: 'cart' },\n { title: 'Payment confirmed', time: 'Oct 6 · 18:23', icon: 'tag' },\n { title: 'Packed', time: 'Oct 7 · 10:40', description: 'Double-boxed with paper padding. Photo from the packing station:', icon: 'package', images: [{ uri: pic('parcel'), aspectRatio: 3 / 2 }] },\n { title: 'Handed to carrier', time: 'Oct 7 · 16:05', description: 'Picked up from the Leipzig warehouse', icon: 'truck' },\n { title: 'Left the sorting hub', time: 'Oct 8 · 02:14', description: 'Hauptumschlagbasis Leipzig', icon: 'truck' },\n { title: 'Arrived in the Netherlands', time: 'Oct 9 · 07:50', description: 'Customs cleared, no fees due', icon: 'home' },\n { title: 'Delivery attempt failed', time: 'Oct 10 · 14:31', description: 'No one home and no safe place. A card was left in the mailbox.', status: 'error', icon: 'home' },\n { title: 'Rescheduled', time: 'Oct 10 · 19:02', description: 'You picked Monday, 12:00–18:00', icon: 'chat' },\n { title: 'Out for delivery', time: '11:05', description: 'Jonas is 6 stops away in a white van', icon: 'truck', status: 'current', links: [{ label: 'Track on map', onPress: () => {} }] },\n { title: 'Delivered', time: 'Est. 15:30', description: 'Leave at the front door if no one answers', icon: 'home', status: 'upcoming' },\n { title: 'Rate your delivery', status: 'upcoming', icon: 'star' },\n ]}\n />\n );\n}\n"
169
+ "code": "import { Text, View } from 'react-native';\nimport { Timeline } from '@/components/ui/timeline';\nimport { alpha, 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\nconst pic = (seed: string) => `https://picsum.photos/seed/${seed}/600/400`;\n\nexport default function DeliveryScroll({ screen }: DemoProps) {\n const { colors } = useKit();\n return (\n <Timeline\n variant=\"scroll\"\n style={screen ? { flex: 1 } : { height: 560 }}\n header={\n <View style={{ gap: 2, paddingBottom: 20 }}>\n <Text style={{ fontSize: 12.5, letterSpacing: 0.4, color: colors.mutedForeground }}>PARCEL DHL 4471 9902 31</Text>\n <Text style={{ fontSize: 20, letterSpacing: -0.4, color: colors.foreground }}>Out for delivery</Text>\n </View>\n }\n items={[\n { title: 'Order placed', time: 'Oct 6 · 18:22', description: 'Walnut desk lamp, linen shade · paid with Visa', icon: 'cart' },\n { title: 'Payment confirmed', time: 'Oct 6 · 18:23', icon: 'tag' },\n { title: 'Packed', time: 'Oct 7 · 10:40', description: 'Double-boxed with paper padding. Photo from the packing station:', icon: 'package', images: [{ uri: pic('parcel'), aspectRatio: 3 / 2 }] },\n { title: 'Handed to carrier', time: 'Oct 7 · 16:05', description: 'Picked up from the Leipzig warehouse', icon: 'truck' },\n { title: 'Left the sorting hub', time: 'Oct 8 · 02:14', description: 'Hauptumschlagbasis Leipzig', icon: 'truck' },\n { title: 'Arrived in the Netherlands', time: 'Oct 9 · 07:50', description: 'Customs cleared, no fees due', icon: 'home' },\n { title: 'Delivery attempt failed', time: 'Oct 10 · 14:31', description: 'No one home and no safe place. A card was left in the mailbox.', status: 'error', icon: 'home' },\n { title: 'Rescheduled', time: 'Oct 10 · 19:02', description: 'You picked Monday, 12:00–18:00', icon: 'chat' },\n { title: 'Out for delivery', time: '11:05', description: 'Jonas is 6 stops away in a white van', icon: 'truck', status: 'current', links: [{ label: 'Track on map', onPress: () => {} }] },\n { title: 'Delivered', time: 'Est. 15:30', description: 'Leave at the front door if no one answers', icon: 'home', status: 'upcoming' },\n { title: 'Rate your delivery', status: 'upcoming', icon: 'star' },\n ]}\n />\n );\n}\n"
170
170
  }
171
171
  ]
172
172
  }
@@ -144,42 +144,42 @@
144
144
  {
145
145
  "name": "Default",
146
146
  "title": "Default",
147
- "code": "import { StyleSheet, View, type ViewProps } from 'react-native';\nimport { Button } from '@/components/ui/button';\nimport { toast, Toaster, ToastProvider, useToast } from '@/components/ui/toast';\nimport { useKit } from '@/lib/kit';\n\n/** Demo frame that follows the kit theme, so the docs radius / accent picker reshapes it too. */\nfunction Frame({ style, ...rest }: ViewProps) {\n const { colors, radius } = useKit();\n return (\n <View\n style={[\n {\n height: 300,\n overflow: 'hidden',\n padding: 16,\n backgroundColor: colors.card,\n borderColor: colors.border,\n borderWidth: StyleSheet.hairlineWidth,\n borderRadius: radius.xl,\n },\n style,\n ]}\n {...rest}\n />\n );\n}\n\nconst row = { flexDirection: 'row' as const, flexWrap: 'wrap' as const, gap: 8 };\n\nexport default function Default() {\n const to = { toaster: 'demo-default' };\n return (\n <Frame>\n <View style={row}>\n <Button\n size=\"sm\"\n variant=\"accent\"\n onPress={() => toast('Event created', { ...to, description: 'Friday, June 14 at 9:00 AM' })}\n >\n Default\n </Button>\n <Button\n size=\"sm\"\n variant=\"outline\"\n onPress={() => toast.success('Changes saved', { ...to, description: 'Synced to all your devices.' })}\n >\n Success\n </Button>\n <Button size=\"sm\" variant=\"outline\" onPress={() => toast.warning('Storage almost full', { ...to, description: '92% of 15 GB used.' })}>\n Warning\n </Button>\n <Button size=\"sm\" variant=\"outline\" onPress={() => toast.error('Could not reach server', to)}>\n Error\n </Button>\n </View>\n <Toaster id=\"demo-default\" />\n </Frame>\n );\n}\n"
147
+ "code": "import { StyleSheet, View, type ViewProps } from 'react-native';\nimport { Button } from '@/components/ui/button';\nimport { toast, Toaster, ToastProvider, useToast } from '@/components/ui/toast';\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 frame that follows the kit theme, so the docs radius / accent picker reshapes it too. In screen mode it is the screen. */\nfunction Frame({ screen, style, ...rest }: ViewProps & { screen?: boolean }) {\n const { colors, radius } = useKit();\n return (\n <View\n style={[\n { padding: 16 },\n screen\n ? { flex: 1, backgroundColor: colors.background }\n : {\n height: 300,\n overflow: 'hidden',\n backgroundColor: colors.card,\n borderColor: colors.border,\n borderWidth: StyleSheet.hairlineWidth,\n borderRadius: radius.xl,\n },\n style,\n ]}\n {...rest}\n />\n );\n}\n\nconst row = { flexDirection: 'row' as const, flexWrap: 'wrap' as const, gap: 8 };\n\nexport default function Default({ screen }: DemoProps) {\n const to = { toaster: 'demo-default' };\n return (\n <Frame screen={screen}>\n <View style={row}>\n <Button\n size=\"sm\"\n variant=\"accent\"\n onPress={() => toast('Event created', { ...to, description: 'Friday, June 14 at 9:00 AM' })}\n >\n Default\n </Button>\n <Button\n size=\"sm\"\n variant=\"outline\"\n onPress={() => toast.success('Changes saved', { ...to, description: 'Synced to all your devices.' })}\n >\n Success\n </Button>\n <Button size=\"sm\" variant=\"outline\" onPress={() => toast.warning('Storage almost full', { ...to, description: '92% of 15 GB used.' })}>\n Warning\n </Button>\n <Button size=\"sm\" variant=\"outline\" onPress={() => toast.error('Could not reach server', to)}>\n Error\n </Button>\n </View>\n <Toaster id=\"demo-default\" />\n </Frame>\n );\n}\n"
148
148
  },
149
149
  {
150
150
  "name": "RichColors",
151
151
  "title": "Rich Colors",
152
- "code": "import { StyleSheet, View, type ViewProps } from 'react-native';\nimport { Button } from '@/components/ui/button';\nimport { toast, Toaster, ToastProvider, useToast } from '@/components/ui/toast';\nimport { useKit } from '@/lib/kit';\n\n/** Demo frame that follows the kit theme, so the docs radius / accent picker reshapes it too. */\nfunction Frame({ style, ...rest }: ViewProps) {\n const { colors, radius } = useKit();\n return (\n <View\n style={[\n {\n height: 300,\n overflow: 'hidden',\n padding: 16,\n backgroundColor: colors.card,\n borderColor: colors.border,\n borderWidth: StyleSheet.hairlineWidth,\n borderRadius: radius.xl,\n },\n style,\n ]}\n {...rest}\n />\n );\n}\n\nconst row = { flexDirection: 'row' as const, flexWrap: 'wrap' as const, gap: 8 };\n\nexport default function RichColors() {\n const { colors } = useKit();\n const to = { toaster: 'demo-rich' };\n return (\n <Frame>\n <View style={row}>\n <Button\n size=\"sm\"\n variant=\"soft\"\n color={colors.success}\n onPress={() => toast.success('Payment received', { ...to, description: '₹18,500 from Acme Inc.' })}\n >\n Success\n </Button>\n <Button\n size=\"sm\"\n variant=\"soft\"\n color={colors.danger}\n onPress={() => toast.error('Card declined', { ...to, description: 'Try another payment method.' })}\n >\n Error\n </Button>\n <Button size=\"sm\" variant=\"soft\" color={colors.warning} onPress={() => toast.warning('You are offline', { ...to, description: 'Changes will sync when you reconnect.' })}>\n Warning\n </Button>\n <Button size=\"sm\" variant=\"soft\" onPress={() => toast.info('New version available', { ...to, description: 'Restart to update to 4.2.' })}>\n Info\n </Button>\n </View>\n <Toaster id=\"demo-rich\" variant=\"rich\" />\n </Frame>\n );\n}\n"
152
+ "code": "import { StyleSheet, View, type ViewProps } from 'react-native';\nimport { Button } from '@/components/ui/button';\nimport { toast, Toaster, ToastProvider, useToast } from '@/components/ui/toast';\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 frame that follows the kit theme, so the docs radius / accent picker reshapes it too. In screen mode it is the screen. */\nfunction Frame({ screen, style, ...rest }: ViewProps & { screen?: boolean }) {\n const { colors, radius } = useKit();\n return (\n <View\n style={[\n { padding: 16 },\n screen\n ? { flex: 1, backgroundColor: colors.background }\n : {\n height: 300,\n overflow: 'hidden',\n backgroundColor: colors.card,\n borderColor: colors.border,\n borderWidth: StyleSheet.hairlineWidth,\n borderRadius: radius.xl,\n },\n style,\n ]}\n {...rest}\n />\n );\n}\n\nconst row = { flexDirection: 'row' as const, flexWrap: 'wrap' as const, gap: 8 };\n\nexport default function RichColors({ screen }: DemoProps) {\n const { colors } = useKit();\n const to = { toaster: 'demo-rich' };\n return (\n <Frame screen={screen}>\n <View style={row}>\n <Button\n size=\"sm\"\n variant=\"soft\"\n color={colors.success}\n onPress={() => toast.success('Payment received', { ...to, description: '₹18,500 from Acme Inc.' })}\n >\n Success\n </Button>\n <Button\n size=\"sm\"\n variant=\"soft\"\n color={colors.danger}\n onPress={() => toast.error('Card declined', { ...to, description: 'Try another payment method.' })}\n >\n Error\n </Button>\n <Button size=\"sm\" variant=\"soft\" color={colors.warning} onPress={() => toast.warning('You are offline', { ...to, description: 'Changes will sync when you reconnect.' })}>\n Warning\n </Button>\n <Button size=\"sm\" variant=\"soft\" onPress={() => toast.info('New version available', { ...to, description: 'Restart to update to 4.2.' })}>\n Info\n </Button>\n </View>\n <Toaster id=\"demo-rich\" variant=\"rich\" />\n </Frame>\n );\n}\n"
153
153
  },
154
154
  {
155
155
  "name": "Island",
156
156
  "title": "Island",
157
- "code": "import { StyleSheet, View, type ViewProps } from 'react-native';\nimport { Button } from '@/components/ui/button';\nimport { toast, Toaster, ToastProvider, useToast } from '@/components/ui/toast';\nimport { useKit } from '@/lib/kit';\n\n/** Demo frame that follows the kit theme, so the docs radius / accent picker reshapes it too. */\nfunction Frame({ style, ...rest }: ViewProps) {\n const { colors, radius } = useKit();\n return (\n <View\n style={[\n {\n height: 300,\n overflow: 'hidden',\n padding: 16,\n backgroundColor: colors.card,\n borderColor: colors.border,\n borderWidth: StyleSheet.hairlineWidth,\n borderRadius: radius.xl,\n },\n style,\n ]}\n {...rest}\n />\n );\n}\n\nconst row = { flexDirection: 'row' as const, flexWrap: 'wrap' as const, gap: 8 };\n\nconst wait = (ms: number) => new Promise<void>((resolve) => setTimeout(resolve, ms));\n\nexport default function Island() {\n const to = { toaster: 'demo-island' };\n return (\n <Frame style={{ justifyContent: 'flex-end' }}>\n <View style={row}>\n <Button\n size=\"sm\"\n variant=\"secondary\"\n onPress={() => toast.success('AirPods connected', { ...to, description: 'Battery 84% · Case 100%' })}\n >\n Success\n </Button>\n <Button\n size=\"sm\"\n variant=\"secondary\"\n onPress={() =>\n toast.error('Payment failed', {\n ...to,\n description: 'Your card ending 4242 was declined by the bank. Update your billing details to keep Pro.',\n action: { label: 'Update', onPress: () => toast.info('Opening billing…', to) },\n })\n }\n >\n Error\n </Button>\n <Button\n size=\"sm\"\n variant=\"secondary\"\n onPress={() =>\n toast.promise(wait(2200), { loading: 'Sending to Maya…', success: 'Photo sent', error: 'Could not send' }, to)\n }\n >\n Promise\n </Button>\n <Button\n size=\"sm\"\n variant=\"secondary\"\n onPress={() => toast('Maya Chen', { ...to, description: 'Running 5 min late, grab us a table?' })}\n >\n Message\n </Button>\n </View>\n <Toaster id=\"demo-island\" variant=\"island\" />\n </Frame>\n );\n}\n"
157
+ "code": "import { StyleSheet, View, type ViewProps } from 'react-native';\nimport { Button } from '@/components/ui/button';\nimport { toast, Toaster, ToastProvider, useToast } from '@/components/ui/toast';\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 frame that follows the kit theme, so the docs radius / accent picker reshapes it too. In screen mode it is the screen. */\nfunction Frame({ screen, style, ...rest }: ViewProps & { screen?: boolean }) {\n const { colors, radius } = useKit();\n return (\n <View\n style={[\n { padding: 16 },\n screen\n ? { flex: 1, backgroundColor: colors.background }\n : {\n height: 300,\n overflow: 'hidden',\n backgroundColor: colors.card,\n borderColor: colors.border,\n borderWidth: StyleSheet.hairlineWidth,\n borderRadius: radius.xl,\n },\n style,\n ]}\n {...rest}\n />\n );\n}\n\nconst row = { flexDirection: 'row' as const, flexWrap: 'wrap' as const, gap: 8 };\n\nconst wait = (ms: number) => new Promise<void>((resolve) => setTimeout(resolve, ms));\n\nexport default function Island({ screen }: DemoProps) {\n const to = { toaster: 'demo-island' };\n return (\n <Frame screen={screen} style={{ justifyContent: 'flex-end' }}>\n <View style={row}>\n <Button\n size=\"sm\"\n variant=\"secondary\"\n onPress={() => toast.success('AirPods connected', { ...to, description: 'Battery 84% · Case 100%' })}\n >\n Success\n </Button>\n <Button\n size=\"sm\"\n variant=\"secondary\"\n onPress={() =>\n toast.error('Payment failed', {\n ...to,\n description: 'Your card ending 4242 was declined by the bank. Update your billing details to keep Pro.',\n action: { label: 'Update', onPress: () => toast.info('Opening billing…', to) },\n })\n }\n >\n Error\n </Button>\n <Button\n size=\"sm\"\n variant=\"secondary\"\n onPress={() =>\n toast.promise(wait(2200), { loading: 'Sending to Maya…', success: 'Photo sent', error: 'Could not send' }, to)\n }\n >\n Promise\n </Button>\n <Button\n size=\"sm\"\n variant=\"secondary\"\n onPress={() => toast('Maya Chen', { ...to, description: 'Running 5 min late, grab us a table?' })}\n >\n Message\n </Button>\n </View>\n <Toaster id=\"demo-island\" variant=\"island\" />\n </Frame>\n );\n}\n"
158
158
  },
159
159
  {
160
160
  "name": "PromiseMorph",
161
161
  "title": "Promise Morph",
162
- "code": "import { StyleSheet, View, type ViewProps } from 'react-native';\nimport { Button } from '@/components/ui/button';\nimport { toast, Toaster, ToastProvider, useToast } from '@/components/ui/toast';\nimport { useKit } from '@/lib/kit';\n\n/** Demo frame that follows the kit theme, so the docs radius / accent picker reshapes it too. */\nfunction Frame({ style, ...rest }: ViewProps) {\n const { colors, radius } = useKit();\n return (\n <View\n style={[\n {\n height: 300,\n overflow: 'hidden',\n padding: 16,\n backgroundColor: colors.card,\n borderColor: colors.border,\n borderWidth: StyleSheet.hairlineWidth,\n borderRadius: radius.xl,\n },\n style,\n ]}\n {...rest}\n />\n );\n}\n\nconst row = { flexDirection: 'row' as const, flexWrap: 'wrap' as const, gap: 8 };\n\nconst wait = (ms: number) => new Promise<void>((resolve) => setTimeout(resolve, ms));\n\nconst fail = (ms: number) => new Promise<void>((_, reject) => setTimeout(() => reject(new Error('timeout')), ms));\n\nexport default function PromiseMorph() {\n const to = { toaster: 'demo-promise' };\n return (\n <Frame>\n <View style={row}>\n <Button\n size=\"sm\"\n variant=\"accent\"\n onPress={() =>\n toast.promise(\n wait(1800),\n { loading: 'Uploading report.pdf…', success: 'Report uploaded', error: 'Upload failed' },\n { ...to, description: 'Q3 board pack · 4.2 MB' },\n )\n }\n >\n Upload (succeeds)\n </Button>\n <Button\n size=\"sm\"\n variant=\"outline\"\n onPress={() =>\n toast\n .promise(\n fail(1800),\n { loading: 'Publishing release…', success: 'Release published', error: 'Publish failed: build timed out' },\n to,\n )\n .catch(() => {})\n }\n >\n Publish (fails)\n </Button>\n </View>\n <Toaster id=\"demo-promise\" />\n </Frame>\n );\n}\n"
162
+ "code": "import { StyleSheet, View, type ViewProps } from 'react-native';\nimport { Button } from '@/components/ui/button';\nimport { toast, Toaster, ToastProvider, useToast } from '@/components/ui/toast';\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 frame that follows the kit theme, so the docs radius / accent picker reshapes it too. In screen mode it is the screen. */\nfunction Frame({ screen, style, ...rest }: ViewProps & { screen?: boolean }) {\n const { colors, radius } = useKit();\n return (\n <View\n style={[\n { padding: 16 },\n screen\n ? { flex: 1, backgroundColor: colors.background }\n : {\n height: 300,\n overflow: 'hidden',\n backgroundColor: colors.card,\n borderColor: colors.border,\n borderWidth: StyleSheet.hairlineWidth,\n borderRadius: radius.xl,\n },\n style,\n ]}\n {...rest}\n />\n );\n}\n\nconst row = { flexDirection: 'row' as const, flexWrap: 'wrap' as const, gap: 8 };\n\nconst wait = (ms: number) => new Promise<void>((resolve) => setTimeout(resolve, ms));\n\nconst fail = (ms: number) => new Promise<void>((_, reject) => setTimeout(() => reject(new Error('timeout')), ms));\n\nexport default function PromiseMorph({ screen }: DemoProps) {\n const to = { toaster: 'demo-promise' };\n return (\n <Frame screen={screen}>\n <View style={row}>\n <Button\n size=\"sm\"\n variant=\"accent\"\n onPress={() =>\n toast.promise(\n wait(1800),\n { loading: 'Uploading report.pdf…', success: 'Report uploaded', error: 'Upload failed' },\n { ...to, description: 'Q3 board pack · 4.2 MB' },\n )\n }\n >\n Upload (succeeds)\n </Button>\n <Button\n size=\"sm\"\n variant=\"outline\"\n onPress={() =>\n toast\n .promise(\n fail(1800),\n { loading: 'Publishing release…', success: 'Release published', error: 'Publish failed: build timed out' },\n to,\n )\n .catch(() => {})\n }\n >\n Publish (fails)\n </Button>\n </View>\n <Toaster id=\"demo-promise\" />\n </Frame>\n );\n}\n"
163
163
  },
164
164
  {
165
165
  "name": "Minimal",
166
166
  "title": "Minimal",
167
- "code": "import { StyleSheet, View, type ViewProps } from 'react-native';\nimport { Button } from '@/components/ui/button';\nimport { toast, Toaster, ToastProvider, useToast } from '@/components/ui/toast';\nimport { useKit } from '@/lib/kit';\n\n/** Demo frame that follows the kit theme, so the docs radius / accent picker reshapes it too. */\nfunction Frame({ style, ...rest }: ViewProps) {\n const { colors, radius } = useKit();\n return (\n <View\n style={[\n {\n height: 300,\n overflow: 'hidden',\n padding: 16,\n backgroundColor: colors.card,\n borderColor: colors.border,\n borderWidth: StyleSheet.hairlineWidth,\n borderRadius: radius.xl,\n },\n style,\n ]}\n {...rest}\n />\n );\n}\n\nconst row = { flexDirection: 'row' as const, flexWrap: 'wrap' as const, gap: 8 };\n\nexport default function Minimal() {\n const to = { toaster: 'demo-minimal' };\n return (\n <Frame style={{ justifyContent: 'flex-end' }}>\n <View style={row}>\n <Button size=\"sm\" variant=\"secondary\" onPress={() => toast('Copied to clipboard', to)}>\n Copy link\n </Button>\n <Button size=\"sm\" variant=\"secondary\" onPress={() => toast.success('Added to favourites', to)}>\n Favourite\n </Button>\n <Button size=\"sm\" variant=\"secondary\" onPress={() => toast.loading('Syncing…', { ...to, id: 'sync', duration: 1500 })}>\n Sync\n </Button>\n </View>\n <Toaster id=\"demo-minimal\" variant=\"minimal\" position=\"top\" />\n </Frame>\n );\n}\n"
167
+ "code": "import { StyleSheet, View, type ViewProps } from 'react-native';\nimport { Button } from '@/components/ui/button';\nimport { toast, Toaster, ToastProvider, useToast } from '@/components/ui/toast';\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 frame that follows the kit theme, so the docs radius / accent picker reshapes it too. In screen mode it is the screen. */\nfunction Frame({ screen, style, ...rest }: ViewProps & { screen?: boolean }) {\n const { colors, radius } = useKit();\n return (\n <View\n style={[\n { padding: 16 },\n screen\n ? { flex: 1, backgroundColor: colors.background }\n : {\n height: 300,\n overflow: 'hidden',\n backgroundColor: colors.card,\n borderColor: colors.border,\n borderWidth: StyleSheet.hairlineWidth,\n borderRadius: radius.xl,\n },\n style,\n ]}\n {...rest}\n />\n );\n}\n\nconst row = { flexDirection: 'row' as const, flexWrap: 'wrap' as const, gap: 8 };\n\nexport default function Minimal({ screen }: DemoProps) {\n const to = { toaster: 'demo-minimal' };\n return (\n <Frame screen={screen} style={{ justifyContent: 'flex-end' }}>\n <View style={row}>\n <Button size=\"sm\" variant=\"secondary\" onPress={() => toast('Copied to clipboard', to)}>\n Copy link\n </Button>\n <Button size=\"sm\" variant=\"secondary\" onPress={() => toast.success('Added to favourites', to)}>\n Favourite\n </Button>\n <Button size=\"sm\" variant=\"secondary\" onPress={() => toast.loading('Syncing…', { ...to, id: 'sync', duration: 1500 })}>\n Sync\n </Button>\n </View>\n <Toaster id=\"demo-minimal\" variant=\"minimal\" position=\"top\" />\n </Frame>\n );\n}\n"
168
168
  },
169
169
  {
170
170
  "name": "WithAction",
171
171
  "title": "With Action",
172
- "code": "import { StyleSheet, View, type ViewProps } from 'react-native';\nimport { Button } from '@/components/ui/button';\nimport { toast, Toaster, ToastProvider, useToast } from '@/components/ui/toast';\nimport { useKit } from '@/lib/kit';\n\n/** Demo frame that follows the kit theme, so the docs radius / accent picker reshapes it too. */\nfunction Frame({ style, ...rest }: ViewProps) {\n const { colors, radius } = useKit();\n return (\n <View\n style={[\n {\n height: 300,\n overflow: 'hidden',\n padding: 16,\n backgroundColor: colors.card,\n borderColor: colors.border,\n borderWidth: StyleSheet.hairlineWidth,\n borderRadius: radius.xl,\n },\n style,\n ]}\n {...rest}\n />\n );\n}\n\nexport default function WithAction() {\n const to = { toaster: 'demo-action' };\n return (\n <Frame>\n <Button\n variant=\"destructive\"\n size=\"sm\"\n style={{ alignSelf: 'flex-start' }}\n onPress={() =>\n toast('Conversation archived', {\n ...to,\n description: 'Moved 12 messages to Archive.',\n action: { label: 'Undo', onPress: () => toast.success('Conversation restored', to) },\n })\n }\n >\n Archive chat\n </Button>\n <Toaster id=\"demo-action\" closeButton duration={6000} />\n </Frame>\n );\n}\n"
172
+ "code": "import { StyleSheet, View, type ViewProps } from 'react-native';\nimport { Button } from '@/components/ui/button';\nimport { toast, Toaster, ToastProvider, useToast } from '@/components/ui/toast';\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 frame that follows the kit theme, so the docs radius / accent picker reshapes it too. In screen mode it is the screen. */\nfunction Frame({ screen, style, ...rest }: ViewProps & { screen?: boolean }) {\n const { colors, radius } = useKit();\n return (\n <View\n style={[\n { padding: 16 },\n screen\n ? { flex: 1, backgroundColor: colors.background }\n : {\n height: 300,\n overflow: 'hidden',\n backgroundColor: colors.card,\n borderColor: colors.border,\n borderWidth: StyleSheet.hairlineWidth,\n borderRadius: radius.xl,\n },\n style,\n ]}\n {...rest}\n />\n );\n}\n\nexport default function WithAction({ screen }: DemoProps) {\n const to = { toaster: 'demo-action' };\n return (\n <Frame screen={screen}>\n <Button\n variant=\"destructive\"\n size=\"sm\"\n style={{ alignSelf: 'flex-start' }}\n onPress={() =>\n toast('Conversation archived', {\n ...to,\n description: 'Moved 12 messages to Archive.',\n action: { label: 'Undo', onPress: () => toast.success('Conversation restored', to) },\n })\n }\n >\n Archive chat\n </Button>\n <Toaster id=\"demo-action\" closeButton duration={6000} />\n </Frame>\n );\n}\n"
173
173
  },
174
174
  {
175
175
  "name": "Expanded",
176
176
  "title": "Expanded",
177
- "code": "import React, { useRef } from 'react';\nimport { StyleSheet, View, type ViewProps } from 'react-native';\nimport { Button } from '@/components/ui/button';\nimport { toast, Toaster, ToastProvider, useToast } from '@/components/ui/toast';\nimport { useKit } from '@/lib/kit';\n\n/** Demo frame that follows the kit theme, so the docs radius / accent picker reshapes it too. */\nfunction Frame({ style, ...rest }: ViewProps) {\n const { colors, radius } = useKit();\n return (\n <View\n style={[\n {\n height: 300,\n overflow: 'hidden',\n padding: 16,\n backgroundColor: colors.card,\n borderColor: colors.border,\n borderWidth: StyleSheet.hairlineWidth,\n borderRadius: radius.xl,\n },\n style,\n ]}\n {...rest}\n />\n );\n}\n\nexport default function Expanded() {\n const to = { toaster: 'demo-expanded' };\n const messages = [\n () => toast.info('Maya joined #design', to),\n () => toast.success('Build #1824 passed', { ...to, description: 'main · 3m 12s' }),\n () => toast('Invoice INV-0042 sent', to),\n () => toast.warning('Disk usage at 85%', to),\n ];\n const n = useRef(0);\n return (\n <Frame style={{ justifyContent: 'flex-end' }}>\n <Button size=\"sm\" style={{ alignSelf: 'flex-start' }} onPress={() => messages[n.current++ % messages.length]()}>\n Send notification\n </Button>\n <Toaster id=\"demo-expanded\" position=\"top\" expand visibleToasts={3} gap={8} />\n </Frame>\n );\n}\n"
177
+ "code": "import React, { useRef } from 'react';\nimport { StyleSheet, View, type ViewProps } from 'react-native';\nimport { Button } from '@/components/ui/button';\nimport { toast, Toaster, ToastProvider, useToast } from '@/components/ui/toast';\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 frame that follows the kit theme, so the docs radius / accent picker reshapes it too. In screen mode it is the screen. */\nfunction Frame({ screen, style, ...rest }: ViewProps & { screen?: boolean }) {\n const { colors, radius } = useKit();\n return (\n <View\n style={[\n { padding: 16 },\n screen\n ? { flex: 1, backgroundColor: colors.background }\n : {\n height: 300,\n overflow: 'hidden',\n backgroundColor: colors.card,\n borderColor: colors.border,\n borderWidth: StyleSheet.hairlineWidth,\n borderRadius: radius.xl,\n },\n style,\n ]}\n {...rest}\n />\n );\n}\n\nexport default function Expanded({ screen }: DemoProps) {\n const to = { toaster: 'demo-expanded' };\n const messages = [\n () => toast.info('Maya joined #design', to),\n () => toast.success('Build #1824 passed', { ...to, description: 'main · 3m 12s' }),\n () => toast('Invoice INV-0042 sent', to),\n () => toast.warning('Disk usage at 85%', to),\n ];\n const n = useRef(0);\n return (\n <Frame screen={screen} style={{ justifyContent: 'flex-end' }}>\n <Button size=\"sm\" style={{ alignSelf: 'flex-start' }} onPress={() => messages[n.current++ % messages.length]()}>\n Send notification\n </Button>\n <Toaster id=\"demo-expanded\" position=\"top\" expand visibleToasts={3} gap={8} />\n </Frame>\n );\n}\n"
178
178
  },
179
179
  {
180
180
  "name": "WithProvider",
181
181
  "title": "With Provider",
182
- "code": "import { StyleSheet, View, type ViewProps } from 'react-native';\nimport { Button } from '@/components/ui/button';\nimport { toast, Toaster, ToastProvider, useToast } from '@/components/ui/toast';\nimport { useKit } from '@/lib/kit';\n\n/** Demo frame that follows the kit theme, so the docs radius / accent picker reshapes it too. */\nfunction Frame({ style, ...rest }: ViewProps) {\n const { colors, radius } = useKit();\n return (\n <View\n style={[\n {\n height: 300,\n overflow: 'hidden',\n padding: 16,\n backgroundColor: colors.card,\n borderColor: colors.border,\n borderWidth: StyleSheet.hairlineWidth,\n borderRadius: radius.xl,\n },\n style,\n ]}\n {...rest}\n />\n );\n}\n\nconst row = { flexDirection: 'row' as const, flexWrap: 'wrap' as const, gap: 8 };\n\nfunction ProviderDemo() {\n const toast = useToast();\n return (\n <Frame>\n <View style={row}>\n <Button\n size=\"sm\"\n variant=\"outline\"\n onPress={() => toast.success('Draft saved', { description: 'Only this Toaster sees it.' })}\n >\n Save draft\n </Button>\n <Button size=\"sm\" variant=\"outline\" onPress={() => toast('Reminder set for 6:00 PM')}>\n Remind me\n </Button>\n </View>\n <Toaster />\n </Frame>\n );\n}\n\nexport default function WithProvider() {\n return (\n <ToastProvider>\n <ProviderDemo />\n </ToastProvider>\n );\n}\n"
182
+ "code": "import { StyleSheet, View, type ViewProps } from 'react-native';\nimport { Button } from '@/components/ui/button';\nimport { toast, Toaster, ToastProvider, useToast } from '@/components/ui/toast';\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 frame that follows the kit theme, so the docs radius / accent picker reshapes it too. In screen mode it is the screen. */\nfunction Frame({ screen, style, ...rest }: ViewProps & { screen?: boolean }) {\n const { colors, radius } = useKit();\n return (\n <View\n style={[\n { padding: 16 },\n screen\n ? { flex: 1, backgroundColor: colors.background }\n : {\n height: 300,\n overflow: 'hidden',\n backgroundColor: colors.card,\n borderColor: colors.border,\n borderWidth: StyleSheet.hairlineWidth,\n borderRadius: radius.xl,\n },\n style,\n ]}\n {...rest}\n />\n );\n}\n\nconst row = { flexDirection: 'row' as const, flexWrap: 'wrap' as const, gap: 8 };\n\nfunction ProviderDemo({ screen }: { screen?: boolean }) {\n const toast = useToast();\n return (\n <Frame screen={screen}>\n <View style={row}>\n <Button\n size=\"sm\"\n variant=\"outline\"\n onPress={() => toast.success('Draft saved', { description: 'Only this Toaster sees it.' })}\n >\n Save draft\n </Button>\n <Button size=\"sm\" variant=\"outline\" onPress={() => toast('Reminder set for 6:00 PM')}>\n Remind me\n </Button>\n </View>\n <Toaster />\n </Frame>\n );\n}\n\nexport default function WithProvider({ screen }: DemoProps) {\n return (\n <ToastProvider>\n <ProviderDemo screen={screen} />\n </ToastProvider>\n );\n}\n"
183
183
  }
184
184
  ]
185
185
  }
@@ -119,7 +119,8 @@ export function AccordionItem({ value, title, subtitle, icon, children, disabled
119
119
  if (!ctx) throw new Error('AccordionItem must be used inside <Accordion>');
120
120
  const { open: openValues, toggle, variant, tint } = ctx;
121
121
  const { last } = useContext(PositionContext);
122
- const { colors, radius, dark } = useKit();
122
+ const { colors, radius, dark, font } = useKit();
123
+ const ff = font.family ? { fontFamily: font.family } : null;
123
124
  const open = openValues.includes(value);
124
125
 
125
126
  const [height, setHeight] = useState(0);
@@ -168,11 +169,11 @@ export function AccordionItem({ value, title, subtitle, icon, children, disabled
168
169
  {icon ? <View style={styles.icon}>{icon}</View> : null}
169
170
  <View style={styles.titles}>
170
171
  {isTextNode(title) ? (
171
- <Text style={[styles.title, { color: open && variant === 'default' ? tint : colors.foreground }]}>{title}</Text>
172
+ <Text style={[styles.title, ff, { color: open && variant === 'default' ? tint : colors.foreground }]}>{title}</Text>
172
173
  ) : (
173
174
  title
174
175
  )}
175
- {subtitle ? <Text style={[styles.subtitle, { color: colors.mutedForeground }]}>{subtitle}</Text> : null}
176
+ {subtitle ? <Text style={[styles.subtitle, ff, { color: colors.mutedForeground }]}>{subtitle}</Text> : null}
176
177
  </View>
177
178
  <Animated.View
178
179
  style={{ transform: [{ rotate: turn.interpolate({ inputRange: [0, 1], outputRange: ['0deg', '180deg'] }) }] }}
@@ -209,7 +210,7 @@ export function AccordionItem({ value, title, subtitle, icon, children, disabled
209
210
  ]}
210
211
  >
211
212
  {isTextNode(children) ? (
212
- <Text style={[styles.text, { color: colors.mutedForeground }]}>{children}</Text>
213
+ <Text style={[styles.text, ff, { color: colors.mutedForeground }]}>{children}</Text>
213
214
  ) : (
214
215
  children
215
216
  )}
@@ -181,14 +181,17 @@ export function useKit(): Theme {
181
181
  return useMemo(() => build(scheme, ctx?.overrides), [scheme, ctx?.overrides]);
182
182
  }
183
183
 
184
- /** Shared motion presets so the whole kit moves the same way. */
184
+ /**
185
+ * Shared motion presets so the whole kit moves the same way. Every spring is critically damped
186
+ * (damping ≥ 2·√(stiffness·mass)): it settles smoothly with no overshoot or wobble.
187
+ */
185
188
  export const motion = {
186
189
  /** Toggles, selection indicators. */
187
- snappy: { damping: 18, stiffness: 260, mass: 0.8 },
188
- /** Press feedback, pops. */
189
- bouncy: { damping: 11, stiffness: 220, mass: 0.7 },
190
+ snappy: { damping: 30, stiffness: 260, mass: 0.8 },
191
+ /** Press feedback, pops. (Kept under its old name; it no longer bounces.) */
192
+ bouncy: { damping: 26, stiffness: 220, mass: 0.7 },
190
193
  /** Sheets, layout changes. */
191
- gentle: { damping: 22, stiffness: 160, mass: 1 },
194
+ gentle: { damping: 27, stiffness: 160, mass: 1 },
192
195
  fast: 160,
193
196
  normal: 240,
194
197
  };
@@ -134,7 +134,8 @@ export function ActionSheet({
134
134
  haptics = true,
135
135
  style,
136
136
  }: ActionSheetProps) {
137
- const { colors, radius, dark } = useKit();
137
+ const { colors, radius, dark, font } = useKit();
138
+ const ff = font.family ? { fontFamily: font.family } : null;
138
139
  const { height: winH } = useWindowDimensions();
139
140
  const reduceMotion = useReduceMotion();
140
141
  const tint = accent ?? colors.accent;
@@ -247,13 +248,13 @@ export function ActionSheet({
247
248
  {title ? (
248
249
  <Text
249
250
  accessibilityRole="header"
250
- style={[list ? styles.titleLarge : styles.title, { color: list ? colors.foreground : colors.mutedForeground }]}
251
+ style={[list ? styles.titleLarge : styles.title, ff, { color: list ? colors.foreground : colors.mutedForeground }]}
251
252
  >
252
253
  {title}
253
254
  </Text>
254
255
  ) : null}
255
256
  {message ? (
256
- <Text style={[styles.message, { color: colors.mutedForeground, textAlign: list ? 'left' : 'center' }]}>
257
+ <Text style={[styles.message, ff, { color: colors.mutedForeground, textAlign: list ? 'left' : 'center' }]}>
257
258
  {message}
258
259
  </Text>
259
260
  ) : null}
@@ -265,7 +266,7 @@ export function ActionSheet({
265
266
  cancelLabel !== null ? (
266
267
  <Card corner={corner} bg={colors.popover} edge={edge} dark={dark}>
267
268
  <Row onPress={dismiss} highlight={highlight} label={cancelLabel} style={styles.row}>
268
- <Text style={[styles.rowLabel, styles.center, { color: variant === 'inset' ? tint : colors.foreground }]}>
269
+ <Text style={[styles.rowLabel, ff, styles.center, { color: variant === 'inset' ? tint : colors.foreground }]}>
269
270
  {cancelLabel}
270
271
  </Text>
271
272
  </Row>
@@ -295,7 +296,7 @@ export function ActionSheet({
295
296
  <View style={[styles.tileIcon, { backgroundColor: fill, borderRadius: radius.scale(16) }]}>
296
297
  {icon(a, fg, 24)}
297
298
  </View>
298
- <Text numberOfLines={2} style={[styles.tileLabel, { color: a.destructive ? colors.danger : colors.foreground }]}>
299
+ <Text numberOfLines={2} style={[styles.tileLabel, ff, { color: a.destructive ? colors.danger : colors.foreground }]}>
299
300
  {a.label}
300
301
  </Text>
301
302
  </Row>
@@ -337,7 +338,7 @@ export function ActionSheet({
337
338
  {a.icon ? (
338
339
  <View style={styles.listIcon}>{icon(a, a.destructive ? colors.danger : colors.mutedForeground, 22)}</View>
339
340
  ) : null}
340
- <Text style={[styles.listLabel, { color }]} numberOfLines={1}>
341
+ <Text style={[styles.listLabel, ff, { color }]} numberOfLines={1}>
341
342
  {a.label}
342
343
  </Text>
343
344
  </Row>
@@ -347,7 +348,7 @@ export function ActionSheet({
347
348
  <>
348
349
  <View style={[styles.divider, { backgroundColor: separator }]} />
349
350
  <Row onPress={dismiss} highlight={highlight} corner={radius.md} label={cancelLabel} style={styles.listRow}>
350
- <Text style={[styles.listLabel, styles.center, { color: colors.mutedForeground }]}>{cancelLabel}</Text>
351
+ <Text style={[styles.listLabel, ff, styles.center, { color: colors.mutedForeground }]}>{cancelLabel}</Text>
351
352
  </Row>
352
353
  </>
353
354
  ) : null}
@@ -379,7 +380,7 @@ export function ActionSheet({
379
380
  (i > 0 || header) && { borderTopWidth: StyleSheet.hairlineWidth, borderTopColor: separator },
380
381
  ]}
381
382
  >
382
- <Text style={[styles.rowLabel, withIcons ? styles.rowLabelMenu : styles.center, { color }]} numberOfLines={1}>
383
+ <Text style={[styles.rowLabel, ff, withIcons ? styles.rowLabelMenu : styles.center, { color }]} numberOfLines={1}>
383
384
  {a.label}
384
385
  </Text>
385
386
  {withIcons ? <View style={styles.rowIcon}>{a.icon ? icon(a, color, 22) : null}</View> : null}
@@ -181,14 +181,17 @@ export function useKit(): Theme {
181
181
  return useMemo(() => build(scheme, ctx?.overrides), [scheme, ctx?.overrides]);
182
182
  }
183
183
 
184
- /** Shared motion presets so the whole kit moves the same way. */
184
+ /**
185
+ * Shared motion presets so the whole kit moves the same way. Every spring is critically damped
186
+ * (damping ≥ 2·√(stiffness·mass)): it settles smoothly with no overshoot or wobble.
187
+ */
185
188
  export const motion = {
186
189
  /** Toggles, selection indicators. */
187
- snappy: { damping: 18, stiffness: 260, mass: 0.8 },
188
- /** Press feedback, pops. */
189
- bouncy: { damping: 11, stiffness: 220, mass: 0.7 },
190
+ snappy: { damping: 30, stiffness: 260, mass: 0.8 },
191
+ /** Press feedback, pops. (Kept under its old name; it no longer bounces.) */
192
+ bouncy: { damping: 26, stiffness: 220, mass: 0.7 },
190
193
  /** Sheets, layout changes. */
191
- gentle: { damping: 22, stiffness: 160, mass: 1 },
194
+ gentle: { damping: 27, stiffness: 160, mass: 1 },
192
195
  fast: 160,
193
196
  normal: 240,
194
197
  };