@expokit/cli 0.4.0 → 0.4.1

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 (92) hide show
  1. package/bin/lib.mjs +1 -1
  2. package/package.json +1 -1
  3. package/registry/_docs/accordion.json +1 -1
  4. package/registry/_docs/avatar.json +6 -6
  5. package/registry/_docs/badge.json +3 -3
  6. package/registry/_docs/bottom-nav.json +6 -6
  7. package/registry/_docs/bottom-sheet.json +3 -3
  8. package/registry/_docs/bullet.json +6 -0
  9. package/registry/_docs/button-3d.json +2 -2
  10. package/registry/_docs/button.json +2 -2
  11. package/registry/_docs/calendar.json +5 -5
  12. package/registry/_docs/card.json +1 -1
  13. package/registry/_docs/chip-group.json +1 -1
  14. package/registry/_docs/confetti.json +3 -3
  15. package/registry/_docs/date-picker.json +4 -4
  16. package/registry/_docs/drawer.json +4 -4
  17. package/registry/_docs/flip-card.json +4 -4
  18. package/registry/_docs/floating-input.json +1 -1
  19. package/registry/_docs/grain.json +2 -2
  20. package/registry/_docs/isometric-bar.json +6 -0
  21. package/registry/_docs/liquidglass-tab.json +1 -1
  22. package/registry/_docs/otp-input.json +1 -1
  23. package/registry/_docs/payment-card.json +3 -3
  24. package/registry/_docs/pixel-bar.json +6 -0
  25. package/registry/_docs/polar-area.json +6 -0
  26. package/registry/_docs/progress-steps.json +4 -4
  27. package/registry/_docs/progress.json +2 -2
  28. package/registry/_docs/radio-group.json +1 -1
  29. package/registry/_docs/search-bar.json +3 -3
  30. package/registry/_docs/segmented-control.json +1 -1
  31. package/registry/_docs/select.json +6 -6
  32. package/registry/_docs/skeleton.json +1 -1
  33. package/registry/_docs/slider.json +6 -6
  34. package/registry/_docs/spinner.json +2 -2
  35. package/registry/_docs/stateful-button.json +1 -1
  36. package/registry/_docs/stepper.json +3 -3
  37. package/registry/_docs/tabs.json +2 -2
  38. package/registry/_docs/text-animation.json +4 -4
  39. package/registry/_docs/textarea.json +1 -1
  40. package/registry/_docs/tilt-card.json +3 -3
  41. package/registry/_docs/time-picker.json +6 -6
  42. package/registry/_docs/timeline-chart.json +6 -0
  43. package/registry/_docs/timeline.json +4 -4
  44. package/registry/_docs/treemap.json +6 -0
  45. package/registry/_docs/waterfall.json +6 -0
  46. package/registry/accordion/files/src/components/ui/accordion.tsx +31 -3
  47. package/registry/action-sheet/files/src/components/ui/action-sheet.tsx +4 -1
  48. package/registry/alert/files/src/components/ui/alert.tsx +5 -2
  49. package/registry/alert/files/src/components/ui/button.tsx +32 -4
  50. package/registry/badge/files/src/components/ui/badge.tsx +2 -2
  51. package/registry/bottom-nav/files/src/components/ui/bottom-nav.tsx +11 -5
  52. package/registry/button/files/src/components/ui/button.tsx +32 -4
  53. package/registry/button-3d/files/src/components/ui/button-3d.tsx +33 -6
  54. package/registry/calendar/files/src/components/ui/calendar.tsx +62 -17
  55. package/registry/card/files/src/components/ui/card.tsx +25 -5
  56. package/registry/checkbox/files/src/components/ui/checkbox.tsx +26 -4
  57. package/registry/chip-group/files/src/components/ui/chip-group.tsx +35 -5
  58. package/registry/coupon/files/src/components/ui/coupon.tsx +45 -21
  59. package/registry/coupon/files/src/components/ui/flip-card.tsx +22 -3
  60. package/registry/coupon/files/src/components/ui/tilt-card.tsx +57 -16
  61. package/registry/date-picker/files/src/components/ui/calendar.tsx +62 -17
  62. package/registry/date-picker/files/src/components/ui/date-picker.tsx +96 -42
  63. package/registry/dialog/files/src/components/ui/dialog.tsx +9 -5
  64. package/registry/drawer/files/src/components/ui/drawer.tsx +8 -4
  65. package/registry/flip-card/files/src/components/ui/flip-card.tsx +22 -3
  66. package/registry/floating-input/files/src/components/ui/floating-input.tsx +75 -26
  67. package/registry/liquidglass-tab/files/src/components/GlassTabBar.tsx +1 -1
  68. package/registry/list-item/files/src/components/ui/list-item.tsx +10 -3
  69. package/registry/otp-input/files/src/components/ui/otp-input.tsx +50 -19
  70. package/registry/password-input/files/src/components/ui/floating-input.tsx +75 -26
  71. package/registry/password-input/files/src/components/ui/password-input.tsx +53 -19
  72. package/registry/payment-card/files/src/components/ui/flip-card.tsx +22 -3
  73. package/registry/payment-card/files/src/components/ui/payment-card.tsx +19 -15
  74. package/registry/payment-card/files/src/components/ui/tilt-card.tsx +57 -16
  75. package/registry/progress/files/src/components/ui/progress.tsx +9 -2
  76. package/registry/progress-steps/files/src/components/ui/progress-steps.tsx +11 -4
  77. package/registry/radio-group/files/src/components/ui/radio-group.tsx +39 -9
  78. package/registry/search-bar/files/src/components/ui/search-bar.tsx +70 -17
  79. package/registry/segmented-control/files/src/components/ui/segmented-control.tsx +37 -9
  80. package/registry/select/files/src/components/ui/select.tsx +158 -83
  81. package/registry/slider/files/src/components/ui/slider.tsx +53 -25
  82. package/registry/stateful-button/files/src/components/ui/stateful-button.tsx +12 -4
  83. package/registry/stepper/files/src/components/ui/stepper.tsx +76 -26
  84. package/registry/switch/files/src/components/ui/switch.tsx +56 -19
  85. package/registry/tabs/files/src/components/ui/tabs.tsx +7 -4
  86. package/registry/text-animation/files/src/components/ui/text-animation.tsx +7 -2
  87. package/registry/textarea/files/src/components/ui/textarea.tsx +104 -36
  88. package/registry/tilt-card/files/src/components/ui/tilt-card.tsx +57 -16
  89. package/registry/time-picker/files/src/components/ui/time-picker.tsx +154 -73
  90. package/registry/timeline/files/src/components/ui/timeline.tsx +9 -4
  91. package/registry/toast/files/src/components/ui/button.tsx +32 -4
  92. package/registry/toast/files/src/components/ui/toast.tsx +15 -6
package/bin/lib.mjs CHANGED
@@ -312,7 +312,7 @@ export function kitVersion(text) {
312
312
  }
313
313
 
314
314
  /**
315
- * Packages a registry item may depend on that work in the Expo Go app (SDK 58): native modules Expo Go
315
+ * Packages a registry item may depend on that work in the Expo Go app (SDK 57): native modules Expo Go
316
316
  * ships, plus pure-JS packages. Anything else needs a development build (`npx expo run:ios|android`).
317
317
  * Keep in sync with the Expo Go module list when the SDK moves.
318
318
  */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@expokit/cli",
3
- "version": "0.4.0",
3
+ "version": "0.4.1",
4
4
  "description": "shadcn-style CLI for Expo — add native UI blocks and components to your app",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -104,7 +104,7 @@
104
104
  {
105
105
  "name": "SeparatedCards",
106
106
  "title": "Separated Cards",
107
- "code": "import { Text, View } from 'react-native';\nimport { Accordion, AccordionItem } from '@/components/ui/accordion';\n\nexport default function SeparatedCards() {\n return (\n <Accordion variant=\"separated\">\n {[\n ['🔒', 'Is my data encrypted?', 'Everything is encrypted in transit with TLS 1.3 and at rest with AES-256. Keys rotate every 90 days.'],\n ['💳', 'Do you offer refunds?', 'If ExpoKit is not for you, email us within 14 days of purchase and we will refund you in full.'],\n ['🌍', 'Which regions do you host in?', 'Frankfurt, Virginia and Singapore. Pick a region when you create a project.'],\n ].map(([icon, q, a]) => (\n <AccordionItem key={q} value={q} title={q} icon={<Text style={{ fontSize: 18 }}>{icon}</Text>}>\n {a}\n </AccordionItem>\n ))}\n </Accordion>\n );\n}\n"
107
+ "code": "import { Text, View } from 'react-native';\nimport { Accordion, AccordionItem } from '@/components/ui/accordion';\n\n/** Controls stop at iOS's largest standard text size; content text stays uncapped. */\nconst FONT_CAP = 1.35;\n\nexport default function SeparatedCards() {\n return (\n <Accordion variant=\"separated\">\n {[\n ['🔒', 'Is my data encrypted?', 'Everything is encrypted in transit with TLS 1.3 and at rest with AES-256. Keys rotate every 90 days.'],\n ['💳', 'Do you offer refunds?', 'If ExpoKit is not for you, email us within 14 days of purchase and we will refund you in full.'],\n ['🌍', 'Which regions do you host in?', 'Frankfurt, Virginia and Singapore. Pick a region when you create a project.'],\n ].map(([icon, q, a]) => (\n <AccordionItem key={q} value={q} title={q} icon={<Text maxFontSizeMultiplier={FONT_CAP} style={{ fontSize: 18 }}>{icon}</Text>}>\n {a}\n </AccordionItem>\n ))}\n </Accordion>\n );\n}\n"
108
108
  },
109
109
  {
110
110
  "name": "Controlled",
@@ -186,22 +186,22 @@
186
186
  {
187
187
  "name": "ProfileHeader",
188
188
  "title": "Profile Header",
189
- "code": "import { Pressable, ScrollView, Text, View } from 'react-native';\nimport { Avatar, AvatarGroup } from '@/components/ui/avatar';\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\nexport default function ProfileHeader({ screen }: DemoProps) {\n const { colors, radius, dark } = useKit();\n return (\n <View\n style={[\n { alignItems: 'center', gap: 12, paddingVertical: 20, paddingHorizontal: 16 },\n screen\n ? { flex: 1, justifyContent: 'center', backgroundColor: colors.background }\n : { borderRadius: radius.xl, backgroundColor: colors.card, borderWidth: 1, borderColor: alpha(colors.foreground, dark ? 0.1 : 0.06) },\n ]}\n >\n <Avatar\n size={88}\n src=\"https://i.pravatar.cc/240?img=47\"\n name=\"Ava Rossi\"\n ring=\"story\"\n verified\n surface={screen ? colors.background : colors.card}\n elevated\n />\n <View style={{ alignItems: 'center', gap: 2 }}>\n <Text style={{ fontSize: 19, fontWeight: '400', letterSpacing: -0.3, color: colors.foreground }}>Ava Rossi</Text>\n <Text style={{ fontSize: 14, color: colors.mutedForeground }}>Product designer · Milan</Text>\n </View>\n <View style={{ flexDirection: 'row', gap: 24, marginTop: 4 }}>\n {[\n ['248', 'Posts'],\n ['12.4k', 'Followers'],\n ['311', 'Following'],\n ].map(([n, l]) => (\n <View key={l} style={{ alignItems: 'center' }}>\n <Text style={{ fontSize: 16, color: colors.foreground, fontVariant: ['tabular-nums'] }}>{n}</Text>\n <Text style={{ fontSize: 12, color: colors.mutedForeground }}>{l}</Text>\n </View>\n ))}\n </View>\n </View>\n );\n}\n"
189
+ "code": "import { Pressable, ScrollView, Text, View } from 'react-native';\nimport { Avatar, AvatarGroup } from '@/components/ui/avatar';\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\nexport default function ProfileHeader({ screen }: DemoProps) {\n const { colors, radius, dark } = useKit();\n return (\n <View\n style={[\n { alignItems: 'center', gap: 12, paddingVertical: 20, paddingHorizontal: 16 },\n screen\n ? { flex: 1, justifyContent: 'center', backgroundColor: colors.background }\n : { borderRadius: radius.xl, backgroundColor: colors.card, borderWidth: 1, borderColor: alpha(colors.foreground, dark ? 0.1 : 0.06) },\n ]}\n >\n <Avatar\n size={88}\n src=\"https://i.pravatar.cc/240?img=47\"\n name=\"Ava Rossi\"\n ring=\"story\"\n verified\n surface={screen ? colors.background : colors.card}\n elevated\n />\n <View style={{ alignItems: 'center', gap: 2 }}>\n <Text style={{ fontSize: 19, fontWeight: '400', letterSpacing: -0.3, color: colors.foreground }}>Ava Rossi</Text>\n <Text style={{ fontSize: 14, color: colors.mutedForeground }}>Product designer · Milan</Text>\n </View>\n <View style={{ flexDirection: 'row', flexWrap: 'wrap', justifyContent: 'center', gap: 24, marginTop: 4 }}>\n {[\n ['248', 'Posts'],\n ['12.4k', 'Followers'],\n ['311', 'Following'],\n ].map(([n, l]) => (\n <View key={l} style={{ alignItems: 'center' }}>\n <Text style={{ fontSize: 16, color: colors.foreground, fontVariant: ['tabular-nums'] }}>{n}</Text>\n <Text style={{ fontSize: 12, color: colors.mutedForeground }}>{l}</Text>\n </View>\n ))}\n </View>\n </View>\n );\n}\n"
190
190
  },
191
191
  {
192
192
  "name": "ChatList",
193
193
  "title": "Chat List",
194
- "code": "import { Pressable, ScrollView, Text, View } from 'react-native';\nimport { Avatar, AvatarGroup } from '@/components/ui/avatar';\nimport { alpha, useKit } from '@/lib/kit';\n\nexport default function ChatList() {\n const { colors } = useKit();\n const chats = [\n { name: 'Noah Kim', src: 'https://i.pravatar.cc/150?img=59', status: 'online', msg: 'Pushed the fix — can you re-test?', time: '9:41', unread: 2 },\n { name: 'Priya Patel', status: 'away', msg: 'Sounds good, see you at 3.', time: '9:12', unread: 0 },\n { name: 'Diego Alvarez', src: 'https://i.pravatar.cc/150?img=12', status: 'busy', msg: 'In a call until noon', time: 'Mon', unread: 0 },\n { name: 'Hannah Lee', status: 'offline', msg: 'Thanks for the notes!', time: 'Sun', unread: 0 },\n ] as const;\n return (\n <View>\n {chats.map((c, i) => (\n <View\n key={c.name}\n style={{\n flexDirection: 'row',\n alignItems: 'center',\n gap: 12,\n paddingVertical: 10,\n borderTopWidth: i ? 1 : 0,\n borderTopColor: alpha(colors.foreground, 0.06),\n }}\n >\n <Avatar size={48} name={c.name} src={'src' in c ? c.src : undefined} status={c.status} />\n <View style={{ flex: 1, minWidth: 0 }}>\n <View style={{ flexDirection: 'row', justifyContent: 'space-between' }}>\n <Text style={{ fontSize: 15, color: colors.foreground }}>{c.name}</Text>\n <Text style={{ fontSize: 12, color: c.unread ? colors.accent : colors.mutedForeground }}>{c.time}</Text>\n </View>\n <Text numberOfLines={1} style={{ fontSize: 14, color: colors.mutedForeground, marginTop: 2 }}>\n {c.msg}\n </Text>\n </View>\n </View>\n ))}\n </View>\n );\n}\n"
194
+ "code": "import { Pressable, ScrollView, Text, View } from 'react-native';\nimport { Avatar, AvatarGroup } from '@/components/ui/avatar';\nimport { alpha, useKit } from '@/lib/kit';\n\nexport default function ChatList() {\n const { colors } = useKit();\n const chats = [\n { name: 'Noah Kim', src: 'https://i.pravatar.cc/150?img=59', status: 'online', msg: 'Pushed the fix — can you re-test?', time: '9:41', unread: 2 },\n { name: 'Priya Patel', status: 'away', msg: 'Sounds good, see you at 3.', time: '9:12', unread: 0 },\n { name: 'Diego Alvarez', src: 'https://i.pravatar.cc/150?img=12', status: 'busy', msg: 'In a call until noon', time: 'Mon', unread: 0 },\n { name: 'Hannah Lee', status: 'offline', msg: 'Thanks for the notes!', time: 'Sun', unread: 0 },\n ] as const;\n return (\n <View>\n {chats.map((c, i) => (\n <View\n key={c.name}\n style={{\n flexDirection: 'row',\n alignItems: 'center',\n gap: 12,\n paddingVertical: 10,\n borderTopWidth: i ? 1 : 0,\n borderTopColor: alpha(colors.foreground, 0.06),\n }}\n >\n <Avatar size={48} name={c.name} src={'src' in c ? c.src : undefined} status={c.status} />\n <View style={{ flex: 1, minWidth: 0 }}>\n <View style={{ flexDirection: 'row', justifyContent: 'space-between' }}>\n <Text style={{ flexShrink: 1, fontSize: 15, color: colors.foreground }}>{c.name}</Text>\n <Text style={{ fontSize: 12, color: c.unread ? colors.accent : colors.mutedForeground }}>{c.time}</Text>\n </View>\n <Text numberOfLines={1} style={{ fontSize: 14, color: colors.mutedForeground, marginTop: 2 }}>\n {c.msg}\n </Text>\n </View>\n </View>\n ))}\n </View>\n );\n}\n"
195
195
  },
196
196
  {
197
197
  "name": "Stories",
198
198
  "title": "Stories",
199
- "code": "import React, { useState } from 'react';\nimport { Pressable, ScrollView, Text, View } from 'react-native';\nimport { Avatar, AvatarGroup } from '@/components/ui/avatar';\nimport { alpha, useKit } from '@/lib/kit';\n\nexport default function Stories() {\n const { colors } = useKit();\n const [seen, setSeen] = useState<string[]>(['Theo']);\n const people = [\n { name: 'Kai Müller', short: 'Kai', img: 11 },\n { name: 'Mila Novak', short: 'Mila', img: 25 },\n { name: 'Theo Grant', short: 'Theo', img: 33 },\n { name: 'Nora Ali', short: 'Nora', img: 41 },\n { name: 'Sam Ortiz', short: 'Sam', img: 0 },\n { name: 'Lucía Romero', short: 'Lucía', img: 44 },\n ];\n return (\n <ScrollView horizontal showsHorizontalScrollIndicator={false} contentContainerStyle={{ gap: 14, paddingRight: 8 }}>\n {people.map((p, i) => (\n <View key={p.short} style={{ alignItems: 'center', gap: 6 }}>\n <Avatar\n size=\"lg\"\n src={p.img ? `https://i.pravatar.cc/150?img=${p.img}` : undefined}\n name={p.name}\n ring=\"story\"\n seen={seen.includes(p.short)}\n ringSpin={i === 1 && !seen.includes(p.short)}\n onPress={() => setSeen((s) => (s.includes(p.short) ? s.filter((n) => n !== p.short) : [...s, p.short]))}\n />\n <Text style={{ fontSize: 12, color: seen.includes(p.short) ? colors.mutedForeground : colors.foreground }}>{p.short}</Text>\n </View>\n ))}\n </ScrollView>\n );\n}\n"
199
+ "code": "import React, { useState } from 'react';\nimport { Pressable, ScrollView, Text, View } from 'react-native';\nimport { Avatar, AvatarGroup } from '@/components/ui/avatar';\nimport { alpha, useKit } from '@/lib/kit';\n\n/** Demo control text (buttons, chips, labels on fixed shapes) stops at iOS's largest standard text size. */\nconst FONT_CAP = 1.35;\n\nexport default function Stories() {\n const { colors } = useKit();\n const [seen, setSeen] = useState<string[]>(['Theo']);\n const people = [\n { name: 'Kai Müller', short: 'Kai', img: 11 },\n { name: 'Mila Novak', short: 'Mila', img: 25 },\n { name: 'Theo Grant', short: 'Theo', img: 33 },\n { name: 'Nora Ali', short: 'Nora', img: 41 },\n { name: 'Sam Ortiz', short: 'Sam', img: 0 },\n { name: 'Lucía Romero', short: 'Lucía', img: 44 },\n ];\n return (\n <ScrollView horizontal showsHorizontalScrollIndicator={false} contentContainerStyle={{ gap: 14, paddingRight: 8 }}>\n {people.map((p, i) => (\n <View key={p.short} style={{ alignItems: 'center', gap: 6 }}>\n <Avatar\n size=\"lg\"\n src={p.img ? `https://i.pravatar.cc/150?img=${p.img}` : undefined}\n name={p.name}\n ring=\"story\"\n seen={seen.includes(p.short)}\n ringSpin={i === 1 && !seen.includes(p.short)}\n onPress={() => setSeen((s) => (s.includes(p.short) ? s.filter((n) => n !== p.short) : [...s, p.short]))}\n />\n <Text maxFontSizeMultiplier={FONT_CAP} style={{ fontSize: 12, color: seen.includes(p.short) ? colors.mutedForeground : colors.foreground }}>{p.short}</Text>\n </View>\n ))}\n </ScrollView>\n );\n}\n"
200
200
  },
201
201
  {
202
202
  "name": "Group",
203
203
  "title": "Group",
204
- "code": "import { Pressable, ScrollView, Text, View } from 'react-native';\nimport { Avatar, AvatarGroup } from '@/components/ui/avatar';\nimport { alpha, useKit } from '@/lib/kit';\n\nexport default function Group() {\n const { colors, radius, dark } = useKit();\n return (\n <View style={{ gap: 14 }}>\n <View\n style={{\n padding: 14,\n gap: 12,\n borderRadius: radius.lg,\n backgroundColor: colors.card,\n borderWidth: 1,\n borderColor: alpha(colors.foreground, dark ? 0.1 : 0.06),\n }}\n >\n <View style={{ flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center' }}>\n <View>\n <Text style={{ fontSize: 15, color: colors.foreground }}>Mobile launch</Text>\n <Text style={{ fontSize: 13, color: colors.mutedForeground }}>Design · Engineering</Text>\n </View>\n <AvatarGroup max={4} total={9} surface={colors.card} size=\"sm\" onPressMore={() => {}}>\n {[8, 16, 26, 36, 48].map((n, i) => (\n <Avatar key={n} src={`https://i.pravatar.cc/150?img=${n}`} name={['Maya Chen', 'Leo Park', 'Ava Rossi', 'Noah Kim', 'Zoe Martin'][i]} />\n ))}\n </AvatarGroup>\n </View>\n </View>\n <View style={{ flexDirection: 'row', alignItems: 'center', gap: 10 }}>\n <AvatarGroup size=\"sm\" max={3} total={1284}>\n <Avatar name=\"Ana Silva\" />\n <Avatar name=\"Ben Carter\" />\n <Avatar name=\"Chloe Dubois\" />\n </AvatarGroup>\n <Text style={{ fontSize: 13, color: colors.mutedForeground }}>1,284 members</Text>\n </View>\n <AvatarGroup size=\"lg\" shape=\"rounded\" max={5}>\n {['Priya Patel', 'Omar Haddad', 'Sven Larsen', 'Iris Okafor'].map((n) => (\n <Avatar key={n} name={n} />\n ))}\n </AvatarGroup>\n </View>\n );\n}\n"
204
+ "code": "import { Pressable, ScrollView, Text, View } from 'react-native';\nimport { Avatar, AvatarGroup } from '@/components/ui/avatar';\nimport { alpha, useKit } from '@/lib/kit';\n\nexport default function Group() {\n const { colors, radius, dark } = useKit();\n return (\n <View style={{ gap: 14 }}>\n <View\n style={{\n padding: 14,\n gap: 12,\n borderRadius: radius.lg,\n backgroundColor: colors.card,\n borderWidth: 1,\n borderColor: alpha(colors.foreground, dark ? 0.1 : 0.06),\n }}\n >\n <View style={{ flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center' }}>\n <View style={{ flexShrink: 1 }}>\n <Text style={{ fontSize: 15, color: colors.foreground }}>Mobile launch</Text>\n <Text style={{ fontSize: 13, color: colors.mutedForeground }}>Design · Engineering</Text>\n </View>\n <AvatarGroup max={4} total={9} surface={colors.card} size=\"sm\" onPressMore={() => {}}>\n {[8, 16, 26, 36, 48].map((n, i) => (\n <Avatar key={n} src={`https://i.pravatar.cc/150?img=${n}`} name={['Maya Chen', 'Leo Park', 'Ava Rossi', 'Noah Kim', 'Zoe Martin'][i]} />\n ))}\n </AvatarGroup>\n </View>\n </View>\n <View style={{ flexDirection: 'row', alignItems: 'center', gap: 10 }}>\n <AvatarGroup size=\"sm\" max={3} total={1284}>\n <Avatar name=\"Ana Silva\" />\n <Avatar name=\"Ben Carter\" />\n <Avatar name=\"Chloe Dubois\" />\n </AvatarGroup>\n <Text style={{ flexShrink: 1, fontSize: 13, color: colors.mutedForeground }}>1,284 members</Text>\n </View>\n <AvatarGroup size=\"lg\" shape=\"rounded\" max={5}>\n {['Priya Patel', 'Omar Haddad', 'Sven Larsen', 'Iris Okafor'].map((n) => (\n <Avatar key={n} name={n} />\n ))}\n </AvatarGroup>\n </View>\n );\n}\n"
205
205
  },
206
206
  {
207
207
  "name": "Fallbacks",
@@ -216,12 +216,12 @@
216
216
  {
217
217
  "name": "Status",
218
218
  "title": "Status",
219
- "code": "import { Pressable, ScrollView, Text, View } from 'react-native';\nimport { Avatar, AvatarGroup } from '@/components/ui/avatar';\nimport { alpha, useKit } from '@/lib/kit';\n\nexport default function Status() {\n const { colors } = useKit();\n const rows = [\n { name: 'Ethan Brooks', img: 15, status: 'online' },\n { name: 'Lina Novak', img: 44, status: 'away' },\n { name: 'Marcus Hill', img: 68, status: 'busy' },\n { name: 'Iris Okafor', img: 0, status: 'offline' },\n ] as const;\n return (\n <View style={{ flexDirection: 'row', justifyContent: 'space-between' }}>\n {rows.map((r) => (\n <View key={r.name} style={{ alignItems: 'center', gap: 6 }}>\n <Avatar size=\"lg\" src={r.img ? `https://i.pravatar.cc/150?img=${r.img}` : undefined} name={r.name} status={r.status} />\n <Text style={{ fontSize: 12, color: colors.mutedForeground }}>{r.status[0].toUpperCase() + r.status.slice(1)}</Text>\n </View>\n ))}\n </View>\n );\n}\n"
219
+ "code": "import { Pressable, ScrollView, Text, View } from 'react-native';\nimport { Avatar, AvatarGroup } from '@/components/ui/avatar';\nimport { alpha, useKit } from '@/lib/kit';\n\n/** Demo control text (buttons, chips, labels on fixed shapes) stops at iOS's largest standard text size. */\nconst FONT_CAP = 1.35;\n\nexport default function Status() {\n const { colors } = useKit();\n const rows = [\n { name: 'Ethan Brooks', img: 15, status: 'online' },\n { name: 'Lina Novak', img: 44, status: 'away' },\n { name: 'Marcus Hill', img: 68, status: 'busy' },\n { name: 'Iris Okafor', img: 0, status: 'offline' },\n ] as const;\n return (\n <View style={{ flexDirection: 'row', justifyContent: 'space-between' }}>\n {rows.map((r) => (\n <View key={r.name} style={{ alignItems: 'center', gap: 6 }}>\n <Avatar size=\"lg\" src={r.img ? `https://i.pravatar.cc/150?img=${r.img}` : undefined} name={r.name} status={r.status} />\n <Text maxFontSizeMultiplier={FONT_CAP} style={{ fontSize: 12, color: colors.mutedForeground }}>{r.status[0].toUpperCase() + r.status.slice(1)}</Text>\n </View>\n ))}\n </View>\n );\n}\n"
220
220
  },
221
221
  {
222
222
  "name": "Badges",
223
223
  "title": "Badges",
224
- "code": "import React, { useState } from 'react';\nimport { Pressable, ScrollView, Text, View } from 'react-native';\nimport { Avatar, AvatarGroup } from '@/components/ui/avatar';\nimport { alpha, useKit } from '@/lib/kit';\n\nexport default function Badges() {\n const { colors } = useKit();\n const [count, setCount] = useState(3);\n return (\n <View style={{ flexDirection: 'row', alignItems: 'center', gap: 22 }}>\n <Pressable accessibilityRole=\"button\" accessibilityLabel=\"Add a notification\" onPress={() => setCount((c) => c + 1)} style={{ alignItems: 'center', gap: 6 }}>\n <Avatar size=\"lg\" src=\"https://i.pravatar.cc/150?img=22\" name=\"Maya Chen\" badge={count} />\n <Text style={{ fontSize: 12, color: colors.mutedForeground }}>Tap +1</Text>\n </Pressable>\n <View style={{ alignItems: 'center', gap: 6 }}>\n <Avatar size=\"lg\" src=\"https://i.pravatar.cc/150?img=9\" name=\"Leo Park\" badge={128} status=\"online\" />\n <Text style={{ fontSize: 12, color: colors.mutedForeground }}>Mentions</Text>\n </View>\n <View style={{ alignItems: 'center', gap: 6 }}>\n <Avatar size=\"lg\" name=\"Design Team\" shape=\"rounded\" badge verified />\n <Text style={{ fontSize: 12, color: colors.mutedForeground }}>Workspace</Text>\n </View>\n </View>\n );\n}\n"
224
+ "code": "import React, { useState } from 'react';\nimport { Pressable, ScrollView, Text, View } from 'react-native';\nimport { Avatar, AvatarGroup } from '@/components/ui/avatar';\nimport { alpha, useKit } from '@/lib/kit';\n\n/** Demo control text (buttons, chips, labels on fixed shapes) stops at iOS's largest standard text size. */\nconst FONT_CAP = 1.35;\n\nexport default function Badges() {\n const { colors } = useKit();\n const [count, setCount] = useState(3);\n return (\n <View style={{ flexDirection: 'row', alignItems: 'center', gap: 22 }}>\n <Pressable accessibilityRole=\"button\" accessibilityLabel=\"Add a notification\" onPress={() => setCount((c) => c + 1)} style={{ alignItems: 'center', gap: 6 }}>\n <Avatar size=\"lg\" src=\"https://i.pravatar.cc/150?img=22\" name=\"Maya Chen\" badge={count} />\n <Text maxFontSizeMultiplier={FONT_CAP} style={{ fontSize: 12, color: colors.mutedForeground }}>Tap +1</Text>\n </Pressable>\n <View style={{ alignItems: 'center', gap: 6 }}>\n <Avatar size=\"lg\" src=\"https://i.pravatar.cc/150?img=9\" name=\"Leo Park\" badge={128} status=\"online\" />\n <Text maxFontSizeMultiplier={FONT_CAP} style={{ fontSize: 12, color: colors.mutedForeground }}>Mentions</Text>\n </View>\n <View style={{ alignItems: 'center', gap: 6 }}>\n <Avatar size=\"lg\" name=\"Design Team\" shape=\"rounded\" badge verified />\n <Text maxFontSizeMultiplier={FONT_CAP} style={{ fontSize: 12, color: colors.mutedForeground }}>Workspace</Text>\n </View>\n </View>\n );\n}\n"
225
225
  }
226
226
  ]
227
227
  }
@@ -132,17 +132,17 @@
132
132
  {
133
133
  "name": "LiveStatus",
134
134
  "title": "Live Status",
135
- "code": "import React, { useEffect, useState } from 'react';\nimport { Pressable, Text, View } from 'react-native';\nimport { Badge, BadgeIcon, BadgeIconTile, NotificationBadge, type BadgeIconName, type BadgeTone } from '@/components/ui/badge';\nimport { alpha, useKit } from '@/lib/kit';\n\nconst DEPLOY: { label: string; tone: BadgeTone; icon: BadgeIconName }[] = [\n { label: 'Queued', tone: 'neutral', icon: 'clock' },\n { label: 'Building', tone: 'warning', icon: 'refresh' },\n { label: 'Deployed', tone: 'success', icon: 'check' },\n { label: 'Failed', tone: 'danger', icon: 'alert' },\n];\n\nexport default function LiveStatus() {\n const { colors } = useKit();\n const [step, setStep] = useState(0);\n useEffect(() => {\n const id = setInterval(() => setStep((i) => (i + 1) % DEPLOY.length), 1800);\n return () => clearInterval(id);\n }, []);\n const { label, tone, icon } = DEPLOY[step];\n const tint = { neutral: colors.mutedForeground, accent: colors.accent, success: colors.success, warning: colors.warning, danger: colors.danger }[tone];\n return (\n <View style={{ gap: 12 }}>\n <View style={{ flexDirection: 'row', alignItems: 'center', gap: 10 }}>\n <Text style={{ fontSize: 15, color: colors.foreground }}>web-checkout #482</Text>\n <Badge tone={tone} icon={<BadgeIcon name={icon} color={tint} size={14} />}>\n {label}\n </Badge>\n </View>\n <View style={{ flexDirection: 'row', alignItems: 'center', gap: 10 }}>\n <Text style={{ fontSize: 15, color: colors.foreground }}>api-gateway #117</Text>\n <Badge variant=\"solid\" tone={tone} size=\"lg\">\n {label}\n </Badge>\n <Badge variant=\"dot\" tone={tone} pulse={tone === 'warning'}>\n {label}\n </Badge>\n </View>\n </View>\n );\n}\n"
135
+ "code": "import React, { useEffect, useState } from 'react';\nimport { Pressable, Text, View } from 'react-native';\nimport { Badge, BadgeIcon, BadgeIconTile, NotificationBadge, type BadgeIconName, type BadgeTone } from '@/components/ui/badge';\nimport { alpha, useKit } from '@/lib/kit';\n\nconst DEPLOY: { label: string; tone: BadgeTone; icon: BadgeIconName }[] = [\n { label: 'Queued', tone: 'neutral', icon: 'clock' },\n { label: 'Building', tone: 'warning', icon: 'refresh' },\n { label: 'Deployed', tone: 'success', icon: 'check' },\n { label: 'Failed', tone: 'danger', icon: 'alert' },\n];\n\nexport default function LiveStatus() {\n const { colors } = useKit();\n const [step, setStep] = useState(0);\n useEffect(() => {\n const id = setInterval(() => setStep((i) => (i + 1) % DEPLOY.length), 1800);\n return () => clearInterval(id);\n }, []);\n const { label, tone, icon } = DEPLOY[step];\n const tint = { neutral: colors.mutedForeground, accent: colors.accent, success: colors.success, warning: colors.warning, danger: colors.danger }[tone];\n return (\n <View style={{ gap: 12 }}>\n <View style={{ flexDirection: 'row', flexWrap: 'wrap', alignItems: 'center', gap: 10 }}>\n <Text style={{ flexShrink: 1, fontSize: 15, color: colors.foreground }}>web-checkout #482</Text>\n <Badge tone={tone} icon={<BadgeIcon name={icon} color={tint} size={14} />}>\n {label}\n </Badge>\n </View>\n <View style={{ flexDirection: 'row', flexWrap: 'wrap', alignItems: 'center', gap: 10 }}>\n <Text style={{ flexShrink: 1, fontSize: 15, color: colors.foreground }}>api-gateway #117</Text>\n <Badge variant=\"solid\" tone={tone} size=\"lg\">\n {label}\n </Badge>\n <Badge variant=\"dot\" tone={tone} pulse={tone === 'warning'}>\n {label}\n </Badge>\n </View>\n </View>\n );\n}\n"
136
136
  },
137
137
  {
138
138
  "name": "Sizes",
139
139
  "title": "Sizes",
140
- "code": "import { Pressable, Text, View } from 'react-native';\nimport { Badge, BadgeIcon, BadgeIconTile, NotificationBadge, type BadgeIconName, type BadgeTone } from '@/components/ui/badge';\n\nexport default function Sizes() {\n return (\n <View style={{ flexDirection: 'row', alignItems: 'center', gap: 8 }}>\n <Badge size=\"sm\" tone=\"accent\">Small</Badge>\n <Badge size=\"md\" tone=\"accent\">Medium</Badge>\n <Badge size=\"lg\" tone=\"accent\">Large</Badge>\n </View>\n );\n}\n"
140
+ "code": "import { Pressable, Text, View } from 'react-native';\nimport { Badge, BadgeIcon, BadgeIconTile, NotificationBadge, type BadgeIconName, type BadgeTone } from '@/components/ui/badge';\n\nexport default function Sizes() {\n return (\n <View style={{ flexDirection: 'row', flexWrap: 'wrap', alignItems: 'center', gap: 8 }}>\n <Badge size=\"sm\" tone=\"accent\">Small</Badge>\n <Badge size=\"md\" tone=\"accent\">Medium</Badge>\n <Badge size=\"lg\" tone=\"accent\">Large</Badge>\n </View>\n );\n}\n"
141
141
  },
142
142
  {
143
143
  "name": "Notifications",
144
144
  "title": "Notifications",
145
- "code": "import React, { useEffect, useState } from 'react';\nimport { Pressable, Text, View } from 'react-native';\nimport { Badge, BadgeIcon, BadgeIconTile, NotificationBadge, type BadgeIconName, type BadgeTone } from '@/components/ui/badge';\nimport { alpha, useKit } from '@/lib/kit';\n\nexport default function Notifications() {\n const { colors, radius } = useKit();\n const [count, setCount] = useState(3);\n return (\n <View style={{ gap: 18 }}>\n <View style={{ flexDirection: 'row', gap: 26 }}>\n <NotificationBadge count={count}>\n <BadgeIconTile name=\"bell\" backgroundColor={alpha(colors.accent, 0.12)} color={colors.accent} />\n </NotificationBadge>\n <NotificationBadge count={count * 37} tone=\"accent\">\n <BadgeIconTile name=\"message\" backgroundColor={alpha(colors.accent, 0.1)} color={colors.accent} />\n </NotificationBadge>\n <NotificationBadge count={count} dot tone=\"success\">\n <BadgeIconTile name=\"package\" backgroundColor={alpha(colors.success, 0.12)} color={colors.success} />\n </NotificationBadge>\n </View>\n <View style={{ flexDirection: 'row', gap: 10 }}>\n {[\n ['+ New', () => setCount((c) => c + 1)],\n ['Mark all read', () => setCount(0)],\n ].map(([label, onPress]) => (\n <Pressable\n key={label as string}\n onPress={onPress as () => void}\n style={{ paddingHorizontal: 14, height: 36, borderRadius: radius.capsule(36), justifyContent: 'center', backgroundColor: colors.muted }}\n >\n <Text style={{ fontWeight: '400', color: colors.foreground }}>{label as string}</Text>\n </Pressable>\n ))}\n </View>\n </View>\n );\n}\n"
145
+ "code": "import React, { useEffect, useState } from 'react';\nimport { Pressable, Text, View } from 'react-native';\nimport { Badge, BadgeIcon, BadgeIconTile, NotificationBadge, type BadgeIconName, type BadgeTone } from '@/components/ui/badge';\nimport { alpha, useKit } from '@/lib/kit';\n\n/** Controls stop at iOS's largest standard text size; content text stays uncapped. */\nconst FONT_CAP = 1.35;\n\nexport default function Notifications() {\n const { colors, radius } = useKit();\n const [count, setCount] = useState(3);\n return (\n <View style={{ gap: 18 }}>\n <View style={{ flexDirection: 'row', gap: 26 }}>\n <NotificationBadge count={count}>\n <BadgeIconTile name=\"bell\" backgroundColor={alpha(colors.accent, 0.12)} color={colors.accent} />\n </NotificationBadge>\n <NotificationBadge count={count * 37} tone=\"accent\">\n <BadgeIconTile name=\"message\" backgroundColor={alpha(colors.accent, 0.1)} color={colors.accent} />\n </NotificationBadge>\n <NotificationBadge count={count} dot tone=\"success\">\n <BadgeIconTile name=\"package\" backgroundColor={alpha(colors.success, 0.12)} color={colors.success} />\n </NotificationBadge>\n </View>\n <View style={{ flexDirection: 'row', flexWrap: 'wrap', gap: 10 }}>\n {[\n ['+ New', () => setCount((c) => c + 1)],\n ['Mark all read', () => setCount(0)],\n ].map(([label, onPress]) => (\n <Pressable\n key={label as string}\n onPress={onPress as () => void}\n style={{ paddingHorizontal: 14, minHeight: 36, borderRadius: radius.capsule(36), justifyContent: 'center', backgroundColor: colors.muted }}\n >\n <Text maxFontSizeMultiplier={FONT_CAP} style={{ fontWeight: '400', color: colors.foreground }}>{label as string}</Text>\n </Pressable>\n ))}\n </View>\n </View>\n );\n}\n"
146
146
  }
147
147
  ]
148
148
  }
@@ -112,32 +112,32 @@
112
112
  {
113
113
  "name": "MinimalClean",
114
114
  "title": "Minimal Clean",
115
- "code": "import React, { useState } from 'react';\nimport { Text, View } from 'react-native';\nimport { BottomNav, type BottomNavItem } from '@/components/ui/bottom-nav';\nimport { alpha, useKit } from '@/lib/kit';\n\ntype Row = [title: string, subtitle: string, meta: string];\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/** The bottom of a phone screen: a title, a few rows, the bar docked at the bottom. */\nfunction Screen({ title, rows, screen, children }: { title: string; rows: Row[]; screen?: boolean; children: React.ReactNode }) {\n const { colors, radius, dark } = useKit();\n return (\n <View\n style={[\n { alignSelf: 'stretch', overflow: 'hidden', backgroundColor: colors.background, justifyContent: 'space-between' },\n screen\n ? { flex: 1 }\n : { height: 300, borderRadius: radius.scale(28), borderWidth: 1, borderColor: alpha(colors.foreground, dark ? 0.1 : 0.06) },\n ]}\n >\n <View style={{ paddingHorizontal: 18, paddingTop: 18, gap: 14 }}>\n <Text style={{ fontSize: 22, fontWeight: '400', letterSpacing: -0.5, color: colors.foreground }}>{title}</Text>\n {rows.map(([t, s, m]) => (\n <View key={t} style={{ flexDirection: 'row', alignItems: 'center', gap: 12 }}>\n <View\n style={{\n width: 36,\n height: 36,\n borderRadius: radius.capsule(36),\n backgroundColor: alpha(colors.accent, dark ? 0.22 : 0.12),\n alignItems: 'center',\n justifyContent: 'center',\n }}\n >\n <Text style={{ fontSize: 13, fontWeight: '400', color: colors.accent }}>{t.slice(0, 1)}</Text>\n </View>\n <View style={{ flex: 1 }}>\n <Text numberOfLines={1} style={{ fontSize: 14.5, fontWeight: '400', color: colors.foreground }}>{t}</Text>\n <Text numberOfLines={1} style={{ fontSize: 13, fontWeight: '400', color: colors.mutedForeground }}>{s}</Text>\n </View>\n <Text style={{ fontSize: 12, fontWeight: '400', color: colors.mutedForeground }}>{m}</Text>\n </View>\n ))}\n </View>\n {children}\n </View>\n );\n}\n\nconst ROWS: Record<string, Row[]> = {\n home: [\n ['Maya Chen', 'Shipped the onboarding redesign', '2m'],\n ['Leo Park', 'Booked the team offsite for June', '1h'],\n ],\n explore: [\n ['Design Systems Weekly', 'Motion tokens that scale', 'New'],\n ['Product Notes', 'How Linear plans a cycle', '4m read'],\n ],\n search: [\n ['Recent: invoices', 'Q2 invoices · 14 results', 'Today'],\n ['Recent: Maya', 'People · Design team', 'Mon'],\n ],\n projects: [\n ['Mobile app 2.0', '18 of 24 tasks done', '75%'],\n ['Brand refresh', 'Review on Thursday', '40%'],\n ],\n saved: [\n ['Kyoto travel guide', 'Saved from Atlas', 'Apr 2'],\n ['Sourdough basics', 'Saved from Recipes', 'Mar 28'],\n ],\n inbox: [\n ['Priya Nair', 'Can you review the pricing page?', '9:41'],\n ['GitHub', 'PR #482 was approved', '9:12'],\n ],\n analytics: [\n ['Weekly active users', '12,480 · up 8.2%', '7d'],\n ['Conversion', '3.4% · up 0.6 pts', '7d'],\n ],\n activity: [\n ['Leo Park', 'Commented on Launch plan', '5m'],\n ['Ana Ruiz', 'Moved Pricing to Done', '22m'],\n ],\n profile: [\n ['Karan Kumar', 'Product designer · Berlin', 'Edit'],\n ['Notifications', 'Mentions and replies only', 'On'],\n ],\n home2: [\n ['Late Night Drive', 'Playlist · 42 songs', '2h 31m'],\n ['Midnight City', 'M83 · Hurry Up, We\\'re Dreaming', '4:03'],\n ],\n browse: [\n ['New this week', 'Fresh releases picked for you', '24'],\n ['Focus', 'Instrumental, low tempo', '60'],\n ],\n find: [\n ['Recent: Tycho', 'Artist · Ambient', 'Yesterday'],\n ['Recent: lo-fi beats', 'Playlist · 120 songs', 'Sun'],\n ],\n library: [\n ['Liked songs', '1,204 songs', 'Pinned'],\n ['Downloaded', '86 songs · 412 MB', 'Offline'],\n ],\n};\n\nconst titleOf = (items: BottomNavItem[], key: string) => items.find((i) => i.key === key)?.label ?? '';\n\nexport default function MinimalClean({ screen, bottomInset }: DemoProps) {\n const items: BottomNavItem[] = [\n { key: 'home', label: 'Home', icon: 'home' },\n { key: 'explore', label: 'Explore', icon: 'explore' },\n { key: 'projects', label: 'Projects', icon: 'projects' },\n { key: 'saved', label: 'Saved', icon: 'saved' },\n { key: 'profile', label: 'Profile', icon: 'profile' },\n ];\n const [tab, setTab] = useState('home');\n return (\n <Screen screen={screen} title={titleOf(items, tab)} rows={ROWS[tab]}>\n <BottomNav bottomInset={bottomInset} variant=\"minimal\" items={items} value={tab} onValueChange={setTab} />\n </Screen>\n );\n}\n"
115
+ "code": "import React, { useState } from 'react';\nimport { Text, View } from 'react-native';\nimport { BottomNav, type BottomNavItem } from '@/components/ui/bottom-nav';\nimport { alpha, useKit } from '@/lib/kit';\n\n/** Control text stops at iOS's largest standard size. */\nconst FONT_CAP = 1.35;\n\ntype Row = [title: string, subtitle: string, meta: string];\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/** The bottom of a phone screen: a title, a few rows, the bar docked at the bottom. */\nfunction Screen({ title, rows, screen, children }: { title: string; rows: Row[]; screen?: boolean; children: React.ReactNode }) {\n const { colors, radius, dark } = useKit();\n return (\n <View\n style={[\n { alignSelf: 'stretch', overflow: 'hidden', backgroundColor: colors.background, justifyContent: 'space-between' },\n screen\n ? { flex: 1 }\n : { height: 300, borderRadius: radius.scale(28), borderWidth: 1, borderColor: alpha(colors.foreground, dark ? 0.1 : 0.06) },\n ]}\n >\n <View style={{ paddingHorizontal: 18, paddingTop: 18, gap: 14 }}>\n <Text maxFontSizeMultiplier={FONT_CAP} style={{ fontSize: 22, fontWeight: '400', letterSpacing: -0.5, color: colors.foreground }}>{title}</Text>\n {rows.map(([t, s, m]) => (\n <View key={t} style={{ flexDirection: 'row', alignItems: 'center', gap: 12 }}>\n <View\n style={{\n width: 36,\n height: 36,\n borderRadius: radius.capsule(36),\n backgroundColor: alpha(colors.accent, dark ? 0.22 : 0.12),\n alignItems: 'center',\n justifyContent: 'center',\n }}\n >\n <Text maxFontSizeMultiplier={FONT_CAP} style={{ fontSize: 13, fontWeight: '400', color: colors.accent }}>{t.slice(0, 1)}</Text>\n </View>\n <View style={{ flex: 1 }}>\n <Text maxFontSizeMultiplier={FONT_CAP} numberOfLines={1} style={{ fontSize: 14.5, fontWeight: '400', color: colors.foreground }}>{t}</Text>\n <Text maxFontSizeMultiplier={FONT_CAP} numberOfLines={1} style={{ fontSize: 13, fontWeight: '400', color: colors.mutedForeground }}>{s}</Text>\n </View>\n <Text maxFontSizeMultiplier={FONT_CAP} style={{ fontSize: 12, fontWeight: '400', color: colors.mutedForeground }}>{m}</Text>\n </View>\n ))}\n </View>\n {children}\n </View>\n );\n}\n\nconst ROWS: Record<string, Row[]> = {\n home: [\n ['Maya Chen', 'Shipped the onboarding redesign', '2m'],\n ['Leo Park', 'Booked the team offsite for June', '1h'],\n ],\n explore: [\n ['Design Systems Weekly', 'Motion tokens that scale', 'New'],\n ['Product Notes', 'How Linear plans a cycle', '4m read'],\n ],\n search: [\n ['Recent: invoices', 'Q2 invoices · 14 results', 'Today'],\n ['Recent: Maya', 'People · Design team', 'Mon'],\n ],\n projects: [\n ['Mobile app 2.0', '18 of 24 tasks done', '75%'],\n ['Brand refresh', 'Review on Thursday', '40%'],\n ],\n saved: [\n ['Kyoto travel guide', 'Saved from Atlas', 'Apr 2'],\n ['Sourdough basics', 'Saved from Recipes', 'Mar 28'],\n ],\n inbox: [\n ['Priya Nair', 'Can you review the pricing page?', '9:41'],\n ['GitHub', 'PR #482 was approved', '9:12'],\n ],\n analytics: [\n ['Weekly active users', '12,480 · up 8.2%', '7d'],\n ['Conversion', '3.4% · up 0.6 pts', '7d'],\n ],\n activity: [\n ['Leo Park', 'Commented on Launch plan', '5m'],\n ['Ana Ruiz', 'Moved Pricing to Done', '22m'],\n ],\n profile: [\n ['Karan Kumar', 'Product designer · Berlin', 'Edit'],\n ['Notifications', 'Mentions and replies only', 'On'],\n ],\n home2: [\n ['Late Night Drive', 'Playlist · 42 songs', '2h 31m'],\n ['Midnight City', 'M83 · Hurry Up, We\\'re Dreaming', '4:03'],\n ],\n browse: [\n ['New this week', 'Fresh releases picked for you', '24'],\n ['Focus', 'Instrumental, low tempo', '60'],\n ],\n find: [\n ['Recent: Tycho', 'Artist · Ambient', 'Yesterday'],\n ['Recent: lo-fi beats', 'Playlist · 120 songs', 'Sun'],\n ],\n library: [\n ['Liked songs', '1,204 songs', 'Pinned'],\n ['Downloaded', '86 songs · 412 MB', 'Offline'],\n ],\n};\n\nconst titleOf = (items: BottomNavItem[], key: string) => items.find((i) => i.key === key)?.label ?? '';\n\nexport default function MinimalClean({ screen, bottomInset }: DemoProps) {\n const items: BottomNavItem[] = [\n { key: 'home', label: 'Home', icon: 'home' },\n { key: 'explore', label: 'Explore', icon: 'explore' },\n { key: 'projects', label: 'Projects', icon: 'projects' },\n { key: 'saved', label: 'Saved', icon: 'saved' },\n { key: 'profile', label: 'Profile', icon: 'profile' },\n ];\n const [tab, setTab] = useState('home');\n return (\n <Screen screen={screen} title={titleOf(items, tab)} rows={ROWS[tab]}>\n <BottomNav bottomInset={bottomInset} variant=\"minimal\" items={items} value={tab} onValueChange={setTab} />\n </Screen>\n );\n}\n"
116
116
  },
117
117
  {
118
118
  "name": "Floating",
119
119
  "title": "Floating",
120
- "code": "import React, { useState } from 'react';\nimport { Text, View } from 'react-native';\nimport { BottomNav, type BottomNavItem } from '@/components/ui/bottom-nav';\nimport { alpha, useKit } from '@/lib/kit';\n\ntype Row = [title: string, subtitle: string, meta: string];\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/** The bottom of a phone screen: a title, a few rows, the bar docked at the bottom. */\nfunction Screen({ title, rows, screen, children }: { title: string; rows: Row[]; screen?: boolean; children: React.ReactNode }) {\n const { colors, radius, dark } = useKit();\n return (\n <View\n style={[\n { alignSelf: 'stretch', overflow: 'hidden', backgroundColor: colors.background, justifyContent: 'space-between' },\n screen\n ? { flex: 1 }\n : { height: 300, borderRadius: radius.scale(28), borderWidth: 1, borderColor: alpha(colors.foreground, dark ? 0.1 : 0.06) },\n ]}\n >\n <View style={{ paddingHorizontal: 18, paddingTop: 18, gap: 14 }}>\n <Text style={{ fontSize: 22, fontWeight: '400', letterSpacing: -0.5, color: colors.foreground }}>{title}</Text>\n {rows.map(([t, s, m]) => (\n <View key={t} style={{ flexDirection: 'row', alignItems: 'center', gap: 12 }}>\n <View\n style={{\n width: 36,\n height: 36,\n borderRadius: radius.capsule(36),\n backgroundColor: alpha(colors.accent, dark ? 0.22 : 0.12),\n alignItems: 'center',\n justifyContent: 'center',\n }}\n >\n <Text style={{ fontSize: 13, fontWeight: '400', color: colors.accent }}>{t.slice(0, 1)}</Text>\n </View>\n <View style={{ flex: 1 }}>\n <Text numberOfLines={1} style={{ fontSize: 14.5, fontWeight: '400', color: colors.foreground }}>{t}</Text>\n <Text numberOfLines={1} style={{ fontSize: 13, fontWeight: '400', color: colors.mutedForeground }}>{s}</Text>\n </View>\n <Text style={{ fontSize: 12, fontWeight: '400', color: colors.mutedForeground }}>{m}</Text>\n </View>\n ))}\n </View>\n {children}\n </View>\n );\n}\n\nconst ROWS: Record<string, Row[]> = {\n home: [\n ['Maya Chen', 'Shipped the onboarding redesign', '2m'],\n ['Leo Park', 'Booked the team offsite for June', '1h'],\n ],\n explore: [\n ['Design Systems Weekly', 'Motion tokens that scale', 'New'],\n ['Product Notes', 'How Linear plans a cycle', '4m read'],\n ],\n search: [\n ['Recent: invoices', 'Q2 invoices · 14 results', 'Today'],\n ['Recent: Maya', 'People · Design team', 'Mon'],\n ],\n projects: [\n ['Mobile app 2.0', '18 of 24 tasks done', '75%'],\n ['Brand refresh', 'Review on Thursday', '40%'],\n ],\n saved: [\n ['Kyoto travel guide', 'Saved from Atlas', 'Apr 2'],\n ['Sourdough basics', 'Saved from Recipes', 'Mar 28'],\n ],\n inbox: [\n ['Priya Nair', 'Can you review the pricing page?', '9:41'],\n ['GitHub', 'PR #482 was approved', '9:12'],\n ],\n analytics: [\n ['Weekly active users', '12,480 · up 8.2%', '7d'],\n ['Conversion', '3.4% · up 0.6 pts', '7d'],\n ],\n activity: [\n ['Leo Park', 'Commented on Launch plan', '5m'],\n ['Ana Ruiz', 'Moved Pricing to Done', '22m'],\n ],\n profile: [\n ['Karan Kumar', 'Product designer · Berlin', 'Edit'],\n ['Notifications', 'Mentions and replies only', 'On'],\n ],\n home2: [\n ['Late Night Drive', 'Playlist · 42 songs', '2h 31m'],\n ['Midnight City', 'M83 · Hurry Up, We\\'re Dreaming', '4:03'],\n ],\n browse: [\n ['New this week', 'Fresh releases picked for you', '24'],\n ['Focus', 'Instrumental, low tempo', '60'],\n ],\n find: [\n ['Recent: Tycho', 'Artist · Ambient', 'Yesterday'],\n ['Recent: lo-fi beats', 'Playlist · 120 songs', 'Sun'],\n ],\n library: [\n ['Liked songs', '1,204 songs', 'Pinned'],\n ['Downloaded', '86 songs · 412 MB', 'Offline'],\n ],\n};\n\nconst titleOf = (items: BottomNavItem[], key: string) => items.find((i) => i.key === key)?.label ?? '';\n\nexport default function Floating({ screen, bottomInset }: DemoProps) {\n const [tab, setTab] = useState('home');\n const [unread, setUnread] = useState(3);\n const [drafts, setDrafts] = useState(0);\n const items: BottomNavItem[] = [\n { key: 'home', label: 'Home', icon: 'home' },\n { key: 'search', label: 'Search', icon: 'search' },\n { key: 'create', label: 'New note', icon: 'create', action: true },\n { key: 'inbox', label: 'Inbox', icon: 'inbox', badge: unread || undefined },\n { key: 'profile', label: 'Profile', icon: 'profile' },\n ];\n const open = (k: string) => {\n setTab(k);\n if (k === 'inbox') setUnread(0);\n };\n return (\n <Screen screen={screen} title={drafts ? `${drafts} new ${drafts === 1 ? 'draft' : 'drafts'}` : titleOf(items, tab)} rows={ROWS[tab]}>\n <BottomNav bottomInset={bottomInset} variant=\"floating\" items={items} value={tab} onValueChange={open} onAction={() => setDrafts((n) => n + 1)} />\n </Screen>\n );\n}\n"
120
+ "code": "import React, { useState } from 'react';\nimport { Text, View } from 'react-native';\nimport { BottomNav, type BottomNavItem } from '@/components/ui/bottom-nav';\nimport { alpha, useKit } from '@/lib/kit';\n\n/** Control text stops at iOS's largest standard size. */\nconst FONT_CAP = 1.35;\n\ntype Row = [title: string, subtitle: string, meta: string];\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/** The bottom of a phone screen: a title, a few rows, the bar docked at the bottom. */\nfunction Screen({ title, rows, screen, children }: { title: string; rows: Row[]; screen?: boolean; children: React.ReactNode }) {\n const { colors, radius, dark } = useKit();\n return (\n <View\n style={[\n { alignSelf: 'stretch', overflow: 'hidden', backgroundColor: colors.background, justifyContent: 'space-between' },\n screen\n ? { flex: 1 }\n : { height: 300, borderRadius: radius.scale(28), borderWidth: 1, borderColor: alpha(colors.foreground, dark ? 0.1 : 0.06) },\n ]}\n >\n <View style={{ paddingHorizontal: 18, paddingTop: 18, gap: 14 }}>\n <Text maxFontSizeMultiplier={FONT_CAP} style={{ fontSize: 22, fontWeight: '400', letterSpacing: -0.5, color: colors.foreground }}>{title}</Text>\n {rows.map(([t, s, m]) => (\n <View key={t} style={{ flexDirection: 'row', alignItems: 'center', gap: 12 }}>\n <View\n style={{\n width: 36,\n height: 36,\n borderRadius: radius.capsule(36),\n backgroundColor: alpha(colors.accent, dark ? 0.22 : 0.12),\n alignItems: 'center',\n justifyContent: 'center',\n }}\n >\n <Text maxFontSizeMultiplier={FONT_CAP} style={{ fontSize: 13, fontWeight: '400', color: colors.accent }}>{t.slice(0, 1)}</Text>\n </View>\n <View style={{ flex: 1 }}>\n <Text maxFontSizeMultiplier={FONT_CAP} numberOfLines={1} style={{ fontSize: 14.5, fontWeight: '400', color: colors.foreground }}>{t}</Text>\n <Text maxFontSizeMultiplier={FONT_CAP} numberOfLines={1} style={{ fontSize: 13, fontWeight: '400', color: colors.mutedForeground }}>{s}</Text>\n </View>\n <Text maxFontSizeMultiplier={FONT_CAP} style={{ fontSize: 12, fontWeight: '400', color: colors.mutedForeground }}>{m}</Text>\n </View>\n ))}\n </View>\n {children}\n </View>\n );\n}\n\nconst ROWS: Record<string, Row[]> = {\n home: [\n ['Maya Chen', 'Shipped the onboarding redesign', '2m'],\n ['Leo Park', 'Booked the team offsite for June', '1h'],\n ],\n explore: [\n ['Design Systems Weekly', 'Motion tokens that scale', 'New'],\n ['Product Notes', 'How Linear plans a cycle', '4m read'],\n ],\n search: [\n ['Recent: invoices', 'Q2 invoices · 14 results', 'Today'],\n ['Recent: Maya', 'People · Design team', 'Mon'],\n ],\n projects: [\n ['Mobile app 2.0', '18 of 24 tasks done', '75%'],\n ['Brand refresh', 'Review on Thursday', '40%'],\n ],\n saved: [\n ['Kyoto travel guide', 'Saved from Atlas', 'Apr 2'],\n ['Sourdough basics', 'Saved from Recipes', 'Mar 28'],\n ],\n inbox: [\n ['Priya Nair', 'Can you review the pricing page?', '9:41'],\n ['GitHub', 'PR #482 was approved', '9:12'],\n ],\n analytics: [\n ['Weekly active users', '12,480 · up 8.2%', '7d'],\n ['Conversion', '3.4% · up 0.6 pts', '7d'],\n ],\n activity: [\n ['Leo Park', 'Commented on Launch plan', '5m'],\n ['Ana Ruiz', 'Moved Pricing to Done', '22m'],\n ],\n profile: [\n ['Karan Kumar', 'Product designer · Berlin', 'Edit'],\n ['Notifications', 'Mentions and replies only', 'On'],\n ],\n home2: [\n ['Late Night Drive', 'Playlist · 42 songs', '2h 31m'],\n ['Midnight City', 'M83 · Hurry Up, We\\'re Dreaming', '4:03'],\n ],\n browse: [\n ['New this week', 'Fresh releases picked for you', '24'],\n ['Focus', 'Instrumental, low tempo', '60'],\n ],\n find: [\n ['Recent: Tycho', 'Artist · Ambient', 'Yesterday'],\n ['Recent: lo-fi beats', 'Playlist · 120 songs', 'Sun'],\n ],\n library: [\n ['Liked songs', '1,204 songs', 'Pinned'],\n ['Downloaded', '86 songs · 412 MB', 'Offline'],\n ],\n};\n\nconst titleOf = (items: BottomNavItem[], key: string) => items.find((i) => i.key === key)?.label ?? '';\n\nexport default function Floating({ screen, bottomInset }: DemoProps) {\n const [tab, setTab] = useState('home');\n const [unread, setUnread] = useState(3);\n const [drafts, setDrafts] = useState(0);\n const items: BottomNavItem[] = [\n { key: 'home', label: 'Home', icon: 'home' },\n { key: 'search', label: 'Search', icon: 'search' },\n { key: 'create', label: 'New note', icon: 'create', action: true },\n { key: 'inbox', label: 'Inbox', icon: 'inbox', badge: unread || undefined },\n { key: 'profile', label: 'Profile', icon: 'profile' },\n ];\n const open = (k: string) => {\n setTab(k);\n if (k === 'inbox') setUnread(0);\n };\n return (\n <Screen screen={screen} title={drafts ? `${drafts} new ${drafts === 1 ? 'draft' : 'drafts'}` : titleOf(items, tab)} rows={ROWS[tab]}>\n <BottomNav bottomInset={bottomInset} variant=\"floating\" items={items} value={tab} onValueChange={open} onAction={() => setDrafts((n) => n + 1)} />\n </Screen>\n );\n}\n"
121
121
  },
122
122
  {
123
123
  "name": "PillHighlight",
124
124
  "title": "Pill Highlight",
125
- "code": "import React, { useState } from 'react';\nimport { Text, View } from 'react-native';\nimport { BottomNav, type BottomNavItem } from '@/components/ui/bottom-nav';\nimport { alpha, useKit } from '@/lib/kit';\n\ntype Row = [title: string, subtitle: string, meta: string];\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/** The bottom of a phone screen: a title, a few rows, the bar docked at the bottom. */\nfunction Screen({ title, rows, screen, children }: { title: string; rows: Row[]; screen?: boolean; children: React.ReactNode }) {\n const { colors, radius, dark } = useKit();\n return (\n <View\n style={[\n { alignSelf: 'stretch', overflow: 'hidden', backgroundColor: colors.background, justifyContent: 'space-between' },\n screen\n ? { flex: 1 }\n : { height: 300, borderRadius: radius.scale(28), borderWidth: 1, borderColor: alpha(colors.foreground, dark ? 0.1 : 0.06) },\n ]}\n >\n <View style={{ paddingHorizontal: 18, paddingTop: 18, gap: 14 }}>\n <Text style={{ fontSize: 22, fontWeight: '400', letterSpacing: -0.5, color: colors.foreground }}>{title}</Text>\n {rows.map(([t, s, m]) => (\n <View key={t} style={{ flexDirection: 'row', alignItems: 'center', gap: 12 }}>\n <View\n style={{\n width: 36,\n height: 36,\n borderRadius: radius.capsule(36),\n backgroundColor: alpha(colors.accent, dark ? 0.22 : 0.12),\n alignItems: 'center',\n justifyContent: 'center',\n }}\n >\n <Text style={{ fontSize: 13, fontWeight: '400', color: colors.accent }}>{t.slice(0, 1)}</Text>\n </View>\n <View style={{ flex: 1 }}>\n <Text numberOfLines={1} style={{ fontSize: 14.5, fontWeight: '400', color: colors.foreground }}>{t}</Text>\n <Text numberOfLines={1} style={{ fontSize: 13, fontWeight: '400', color: colors.mutedForeground }}>{s}</Text>\n </View>\n <Text style={{ fontSize: 12, fontWeight: '400', color: colors.mutedForeground }}>{m}</Text>\n </View>\n ))}\n </View>\n {children}\n </View>\n );\n}\n\nconst ROWS: Record<string, Row[]> = {\n home: [\n ['Maya Chen', 'Shipped the onboarding redesign', '2m'],\n ['Leo Park', 'Booked the team offsite for June', '1h'],\n ],\n explore: [\n ['Design Systems Weekly', 'Motion tokens that scale', 'New'],\n ['Product Notes', 'How Linear plans a cycle', '4m read'],\n ],\n search: [\n ['Recent: invoices', 'Q2 invoices · 14 results', 'Today'],\n ['Recent: Maya', 'People · Design team', 'Mon'],\n ],\n projects: [\n ['Mobile app 2.0', '18 of 24 tasks done', '75%'],\n ['Brand refresh', 'Review on Thursday', '40%'],\n ],\n saved: [\n ['Kyoto travel guide', 'Saved from Atlas', 'Apr 2'],\n ['Sourdough basics', 'Saved from Recipes', 'Mar 28'],\n ],\n inbox: [\n ['Priya Nair', 'Can you review the pricing page?', '9:41'],\n ['GitHub', 'PR #482 was approved', '9:12'],\n ],\n analytics: [\n ['Weekly active users', '12,480 · up 8.2%', '7d'],\n ['Conversion', '3.4% · up 0.6 pts', '7d'],\n ],\n activity: [\n ['Leo Park', 'Commented on Launch plan', '5m'],\n ['Ana Ruiz', 'Moved Pricing to Done', '22m'],\n ],\n profile: [\n ['Karan Kumar', 'Product designer · Berlin', 'Edit'],\n ['Notifications', 'Mentions and replies only', 'On'],\n ],\n home2: [\n ['Late Night Drive', 'Playlist · 42 songs', '2h 31m'],\n ['Midnight City', 'M83 · Hurry Up, We\\'re Dreaming', '4:03'],\n ],\n browse: [\n ['New this week', 'Fresh releases picked for you', '24'],\n ['Focus', 'Instrumental, low tempo', '60'],\n ],\n find: [\n ['Recent: Tycho', 'Artist · Ambient', 'Yesterday'],\n ['Recent: lo-fi beats', 'Playlist · 120 songs', 'Sun'],\n ],\n library: [\n ['Liked songs', '1,204 songs', 'Pinned'],\n ['Downloaded', '86 songs · 412 MB', 'Offline'],\n ],\n};\n\nconst titleOf = (items: BottomNavItem[], key: string) => items.find((i) => i.key === key)?.label ?? '';\n\nexport default function PillHighlight({ screen, bottomInset }: DemoProps) {\n const items: BottomNavItem[] = [\n { key: 'home', label: 'Home', icon: 'home' },\n { key: 'projects', label: 'Projects', icon: 'projects' },\n { key: 'inbox', label: 'Inbox', icon: 'inbox', badge: true },\n { key: 'profile', label: 'Profile', icon: 'profile' },\n ];\n const [tab, setTab] = useState('home');\n return (\n <Screen screen={screen} title={titleOf(items, tab)} rows={ROWS[tab]}>\n <BottomNav bottomInset={bottomInset} variant=\"pill\" items={items} value={tab} onValueChange={setTab} />\n </Screen>\n );\n}\n"
125
+ "code": "import React, { useState } from 'react';\nimport { Text, View } from 'react-native';\nimport { BottomNav, type BottomNavItem } from '@/components/ui/bottom-nav';\nimport { alpha, useKit } from '@/lib/kit';\n\n/** Control text stops at iOS's largest standard size. */\nconst FONT_CAP = 1.35;\n\ntype Row = [title: string, subtitle: string, meta: string];\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/** The bottom of a phone screen: a title, a few rows, the bar docked at the bottom. */\nfunction Screen({ title, rows, screen, children }: { title: string; rows: Row[]; screen?: boolean; children: React.ReactNode }) {\n const { colors, radius, dark } = useKit();\n return (\n <View\n style={[\n { alignSelf: 'stretch', overflow: 'hidden', backgroundColor: colors.background, justifyContent: 'space-between' },\n screen\n ? { flex: 1 }\n : { height: 300, borderRadius: radius.scale(28), borderWidth: 1, borderColor: alpha(colors.foreground, dark ? 0.1 : 0.06) },\n ]}\n >\n <View style={{ paddingHorizontal: 18, paddingTop: 18, gap: 14 }}>\n <Text maxFontSizeMultiplier={FONT_CAP} style={{ fontSize: 22, fontWeight: '400', letterSpacing: -0.5, color: colors.foreground }}>{title}</Text>\n {rows.map(([t, s, m]) => (\n <View key={t} style={{ flexDirection: 'row', alignItems: 'center', gap: 12 }}>\n <View\n style={{\n width: 36,\n height: 36,\n borderRadius: radius.capsule(36),\n backgroundColor: alpha(colors.accent, dark ? 0.22 : 0.12),\n alignItems: 'center',\n justifyContent: 'center',\n }}\n >\n <Text maxFontSizeMultiplier={FONT_CAP} style={{ fontSize: 13, fontWeight: '400', color: colors.accent }}>{t.slice(0, 1)}</Text>\n </View>\n <View style={{ flex: 1 }}>\n <Text maxFontSizeMultiplier={FONT_CAP} numberOfLines={1} style={{ fontSize: 14.5, fontWeight: '400', color: colors.foreground }}>{t}</Text>\n <Text maxFontSizeMultiplier={FONT_CAP} numberOfLines={1} style={{ fontSize: 13, fontWeight: '400', color: colors.mutedForeground }}>{s}</Text>\n </View>\n <Text maxFontSizeMultiplier={FONT_CAP} style={{ fontSize: 12, fontWeight: '400', color: colors.mutedForeground }}>{m}</Text>\n </View>\n ))}\n </View>\n {children}\n </View>\n );\n}\n\nconst ROWS: Record<string, Row[]> = {\n home: [\n ['Maya Chen', 'Shipped the onboarding redesign', '2m'],\n ['Leo Park', 'Booked the team offsite for June', '1h'],\n ],\n explore: [\n ['Design Systems Weekly', 'Motion tokens that scale', 'New'],\n ['Product Notes', 'How Linear plans a cycle', '4m read'],\n ],\n search: [\n ['Recent: invoices', 'Q2 invoices · 14 results', 'Today'],\n ['Recent: Maya', 'People · Design team', 'Mon'],\n ],\n projects: [\n ['Mobile app 2.0', '18 of 24 tasks done', '75%'],\n ['Brand refresh', 'Review on Thursday', '40%'],\n ],\n saved: [\n ['Kyoto travel guide', 'Saved from Atlas', 'Apr 2'],\n ['Sourdough basics', 'Saved from Recipes', 'Mar 28'],\n ],\n inbox: [\n ['Priya Nair', 'Can you review the pricing page?', '9:41'],\n ['GitHub', 'PR #482 was approved', '9:12'],\n ],\n analytics: [\n ['Weekly active users', '12,480 · up 8.2%', '7d'],\n ['Conversion', '3.4% · up 0.6 pts', '7d'],\n ],\n activity: [\n ['Leo Park', 'Commented on Launch plan', '5m'],\n ['Ana Ruiz', 'Moved Pricing to Done', '22m'],\n ],\n profile: [\n ['Karan Kumar', 'Product designer · Berlin', 'Edit'],\n ['Notifications', 'Mentions and replies only', 'On'],\n ],\n home2: [\n ['Late Night Drive', 'Playlist · 42 songs', '2h 31m'],\n ['Midnight City', 'M83 · Hurry Up, We\\'re Dreaming', '4:03'],\n ],\n browse: [\n ['New this week', 'Fresh releases picked for you', '24'],\n ['Focus', 'Instrumental, low tempo', '60'],\n ],\n find: [\n ['Recent: Tycho', 'Artist · Ambient', 'Yesterday'],\n ['Recent: lo-fi beats', 'Playlist · 120 songs', 'Sun'],\n ],\n library: [\n ['Liked songs', '1,204 songs', 'Pinned'],\n ['Downloaded', '86 songs · 412 MB', 'Offline'],\n ],\n};\n\nconst titleOf = (items: BottomNavItem[], key: string) => items.find((i) => i.key === key)?.label ?? '';\n\nexport default function PillHighlight({ screen, bottomInset }: DemoProps) {\n const items: BottomNavItem[] = [\n { key: 'home', label: 'Home', icon: 'home' },\n { key: 'projects', label: 'Projects', icon: 'projects' },\n { key: 'inbox', label: 'Inbox', icon: 'inbox', badge: true },\n { key: 'profile', label: 'Profile', icon: 'profile' },\n ];\n const [tab, setTab] = useState('home');\n return (\n <Screen screen={screen} title={titleOf(items, tab)} rows={ROWS[tab]}>\n <BottomNav bottomInset={bottomInset} variant=\"pill\" items={items} value={tab} onValueChange={setTab} />\n </Screen>\n );\n}\n"
126
126
  },
127
127
  {
128
128
  "name": "CenterFab",
129
129
  "title": "Center Fab",
130
- "code": "import React, { useState } from 'react';\nimport { Text, View } from 'react-native';\nimport { BottomNav, type BottomNavItem } from '@/components/ui/bottom-nav';\nimport { alpha, useKit } from '@/lib/kit';\n\ntype Row = [title: string, subtitle: string, meta: string];\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/** The bottom of a phone screen: a title, a few rows, the bar docked at the bottom. */\nfunction Screen({ title, rows, screen, children }: { title: string; rows: Row[]; screen?: boolean; children: React.ReactNode }) {\n const { colors, radius, dark } = useKit();\n return (\n <View\n style={[\n { alignSelf: 'stretch', overflow: 'hidden', backgroundColor: colors.background, justifyContent: 'space-between' },\n screen\n ? { flex: 1 }\n : { height: 300, borderRadius: radius.scale(28), borderWidth: 1, borderColor: alpha(colors.foreground, dark ? 0.1 : 0.06) },\n ]}\n >\n <View style={{ paddingHorizontal: 18, paddingTop: 18, gap: 14 }}>\n <Text style={{ fontSize: 22, fontWeight: '400', letterSpacing: -0.5, color: colors.foreground }}>{title}</Text>\n {rows.map(([t, s, m]) => (\n <View key={t} style={{ flexDirection: 'row', alignItems: 'center', gap: 12 }}>\n <View\n style={{\n width: 36,\n height: 36,\n borderRadius: radius.capsule(36),\n backgroundColor: alpha(colors.accent, dark ? 0.22 : 0.12),\n alignItems: 'center',\n justifyContent: 'center',\n }}\n >\n <Text style={{ fontSize: 13, fontWeight: '400', color: colors.accent }}>{t.slice(0, 1)}</Text>\n </View>\n <View style={{ flex: 1 }}>\n <Text numberOfLines={1} style={{ fontSize: 14.5, fontWeight: '400', color: colors.foreground }}>{t}</Text>\n <Text numberOfLines={1} style={{ fontSize: 13, fontWeight: '400', color: colors.mutedForeground }}>{s}</Text>\n </View>\n <Text style={{ fontSize: 12, fontWeight: '400', color: colors.mutedForeground }}>{m}</Text>\n </View>\n ))}\n </View>\n {children}\n </View>\n );\n}\n\nconst ROWS: Record<string, Row[]> = {\n home: [\n ['Maya Chen', 'Shipped the onboarding redesign', '2m'],\n ['Leo Park', 'Booked the team offsite for June', '1h'],\n ],\n explore: [\n ['Design Systems Weekly', 'Motion tokens that scale', 'New'],\n ['Product Notes', 'How Linear plans a cycle', '4m read'],\n ],\n search: [\n ['Recent: invoices', 'Q2 invoices · 14 results', 'Today'],\n ['Recent: Maya', 'People · Design team', 'Mon'],\n ],\n projects: [\n ['Mobile app 2.0', '18 of 24 tasks done', '75%'],\n ['Brand refresh', 'Review on Thursday', '40%'],\n ],\n saved: [\n ['Kyoto travel guide', 'Saved from Atlas', 'Apr 2'],\n ['Sourdough basics', 'Saved from Recipes', 'Mar 28'],\n ],\n inbox: [\n ['Priya Nair', 'Can you review the pricing page?', '9:41'],\n ['GitHub', 'PR #482 was approved', '9:12'],\n ],\n analytics: [\n ['Weekly active users', '12,480 · up 8.2%', '7d'],\n ['Conversion', '3.4% · up 0.6 pts', '7d'],\n ],\n activity: [\n ['Leo Park', 'Commented on Launch plan', '5m'],\n ['Ana Ruiz', 'Moved Pricing to Done', '22m'],\n ],\n profile: [\n ['Karan Kumar', 'Product designer · Berlin', 'Edit'],\n ['Notifications', 'Mentions and replies only', 'On'],\n ],\n home2: [\n ['Late Night Drive', 'Playlist · 42 songs', '2h 31m'],\n ['Midnight City', 'M83 · Hurry Up, We\\'re Dreaming', '4:03'],\n ],\n browse: [\n ['New this week', 'Fresh releases picked for you', '24'],\n ['Focus', 'Instrumental, low tempo', '60'],\n ],\n find: [\n ['Recent: Tycho', 'Artist · Ambient', 'Yesterday'],\n ['Recent: lo-fi beats', 'Playlist · 120 songs', 'Sun'],\n ],\n library: [\n ['Liked songs', '1,204 songs', 'Pinned'],\n ['Downloaded', '86 songs · 412 MB', 'Offline'],\n ],\n};\n\nconst titleOf = (items: BottomNavItem[], key: string) => items.find((i) => i.key === key)?.label ?? '';\n\nexport default function CenterFab({ screen, bottomInset }: DemoProps) {\n const [tab, setTab] = useState('home');\n const [posts, setPosts] = useState(0);\n const items: BottomNavItem[] = [\n { key: 'home', label: 'Home', icon: 'home' },\n { key: 'explore', label: 'Explore', icon: 'explore' },\n { key: 'create', label: 'Post', icon: 'create', action: true },\n { key: 'inbox', label: 'Inbox', icon: 'inbox', badge: 12 + posts },\n { key: 'profile', label: 'Profile', icon: 'profile' },\n ];\n return (\n <Screen screen={screen} title={posts ? `Posted ${posts} ${posts === 1 ? 'time' : 'times'}` : titleOf(items, tab)} rows={ROWS[tab]}>\n <BottomNav bottomInset={bottomInset} variant=\"center-fab\" items={items} value={tab} onValueChange={setTab} onAction={() => setPosts((n) => n + 1)} />\n </Screen>\n );\n}\n"
130
+ "code": "import React, { useState } from 'react';\nimport { Text, View } from 'react-native';\nimport { BottomNav, type BottomNavItem } from '@/components/ui/bottom-nav';\nimport { alpha, useKit } from '@/lib/kit';\n\n/** Control text stops at iOS's largest standard size. */\nconst FONT_CAP = 1.35;\n\ntype Row = [title: string, subtitle: string, meta: string];\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/** The bottom of a phone screen: a title, a few rows, the bar docked at the bottom. */\nfunction Screen({ title, rows, screen, children }: { title: string; rows: Row[]; screen?: boolean; children: React.ReactNode }) {\n const { colors, radius, dark } = useKit();\n return (\n <View\n style={[\n { alignSelf: 'stretch', overflow: 'hidden', backgroundColor: colors.background, justifyContent: 'space-between' },\n screen\n ? { flex: 1 }\n : { height: 300, borderRadius: radius.scale(28), borderWidth: 1, borderColor: alpha(colors.foreground, dark ? 0.1 : 0.06) },\n ]}\n >\n <View style={{ paddingHorizontal: 18, paddingTop: 18, gap: 14 }}>\n <Text maxFontSizeMultiplier={FONT_CAP} style={{ fontSize: 22, fontWeight: '400', letterSpacing: -0.5, color: colors.foreground }}>{title}</Text>\n {rows.map(([t, s, m]) => (\n <View key={t} style={{ flexDirection: 'row', alignItems: 'center', gap: 12 }}>\n <View\n style={{\n width: 36,\n height: 36,\n borderRadius: radius.capsule(36),\n backgroundColor: alpha(colors.accent, dark ? 0.22 : 0.12),\n alignItems: 'center',\n justifyContent: 'center',\n }}\n >\n <Text maxFontSizeMultiplier={FONT_CAP} style={{ fontSize: 13, fontWeight: '400', color: colors.accent }}>{t.slice(0, 1)}</Text>\n </View>\n <View style={{ flex: 1 }}>\n <Text maxFontSizeMultiplier={FONT_CAP} numberOfLines={1} style={{ fontSize: 14.5, fontWeight: '400', color: colors.foreground }}>{t}</Text>\n <Text maxFontSizeMultiplier={FONT_CAP} numberOfLines={1} style={{ fontSize: 13, fontWeight: '400', color: colors.mutedForeground }}>{s}</Text>\n </View>\n <Text maxFontSizeMultiplier={FONT_CAP} style={{ fontSize: 12, fontWeight: '400', color: colors.mutedForeground }}>{m}</Text>\n </View>\n ))}\n </View>\n {children}\n </View>\n );\n}\n\nconst ROWS: Record<string, Row[]> = {\n home: [\n ['Maya Chen', 'Shipped the onboarding redesign', '2m'],\n ['Leo Park', 'Booked the team offsite for June', '1h'],\n ],\n explore: [\n ['Design Systems Weekly', 'Motion tokens that scale', 'New'],\n ['Product Notes', 'How Linear plans a cycle', '4m read'],\n ],\n search: [\n ['Recent: invoices', 'Q2 invoices · 14 results', 'Today'],\n ['Recent: Maya', 'People · Design team', 'Mon'],\n ],\n projects: [\n ['Mobile app 2.0', '18 of 24 tasks done', '75%'],\n ['Brand refresh', 'Review on Thursday', '40%'],\n ],\n saved: [\n ['Kyoto travel guide', 'Saved from Atlas', 'Apr 2'],\n ['Sourdough basics', 'Saved from Recipes', 'Mar 28'],\n ],\n inbox: [\n ['Priya Nair', 'Can you review the pricing page?', '9:41'],\n ['GitHub', 'PR #482 was approved', '9:12'],\n ],\n analytics: [\n ['Weekly active users', '12,480 · up 8.2%', '7d'],\n ['Conversion', '3.4% · up 0.6 pts', '7d'],\n ],\n activity: [\n ['Leo Park', 'Commented on Launch plan', '5m'],\n ['Ana Ruiz', 'Moved Pricing to Done', '22m'],\n ],\n profile: [\n ['Karan Kumar', 'Product designer · Berlin', 'Edit'],\n ['Notifications', 'Mentions and replies only', 'On'],\n ],\n home2: [\n ['Late Night Drive', 'Playlist · 42 songs', '2h 31m'],\n ['Midnight City', 'M83 · Hurry Up, We\\'re Dreaming', '4:03'],\n ],\n browse: [\n ['New this week', 'Fresh releases picked for you', '24'],\n ['Focus', 'Instrumental, low tempo', '60'],\n ],\n find: [\n ['Recent: Tycho', 'Artist · Ambient', 'Yesterday'],\n ['Recent: lo-fi beats', 'Playlist · 120 songs', 'Sun'],\n ],\n library: [\n ['Liked songs', '1,204 songs', 'Pinned'],\n ['Downloaded', '86 songs · 412 MB', 'Offline'],\n ],\n};\n\nconst titleOf = (items: BottomNavItem[], key: string) => items.find((i) => i.key === key)?.label ?? '';\n\nexport default function CenterFab({ screen, bottomInset }: DemoProps) {\n const [tab, setTab] = useState('home');\n const [posts, setPosts] = useState(0);\n const items: BottomNavItem[] = [\n { key: 'home', label: 'Home', icon: 'home' },\n { key: 'explore', label: 'Explore', icon: 'explore' },\n { key: 'create', label: 'Post', icon: 'create', action: true },\n { key: 'inbox', label: 'Inbox', icon: 'inbox', badge: 12 + posts },\n { key: 'profile', label: 'Profile', icon: 'profile' },\n ];\n return (\n <Screen screen={screen} title={posts ? `Posted ${posts} ${posts === 1 ? 'time' : 'times'}` : titleOf(items, tab)} rows={ROWS[tab]}>\n <BottomNav bottomInset={bottomInset} variant=\"center-fab\" items={items} value={tab} onValueChange={setTab} onAction={() => setPosts((n) => n + 1)} />\n </Screen>\n );\n}\n"
131
131
  },
132
132
  {
133
133
  "name": "TabWithIndicator",
134
134
  "title": "Tab With Indicator",
135
- "code": "import React, { useState } from 'react';\nimport { Text, View } from 'react-native';\nimport { BottomNav, type BottomNavItem } from '@/components/ui/bottom-nav';\nimport { alpha, useKit } from '@/lib/kit';\n\ntype Row = [title: string, subtitle: string, meta: string];\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/** The bottom of a phone screen: a title, a few rows, the bar docked at the bottom. */\nfunction Screen({ title, rows, screen, children }: { title: string; rows: Row[]; screen?: boolean; children: React.ReactNode }) {\n const { colors, radius, dark } = useKit();\n return (\n <View\n style={[\n { alignSelf: 'stretch', overflow: 'hidden', backgroundColor: colors.background, justifyContent: 'space-between' },\n screen\n ? { flex: 1 }\n : { height: 300, borderRadius: radius.scale(28), borderWidth: 1, borderColor: alpha(colors.foreground, dark ? 0.1 : 0.06) },\n ]}\n >\n <View style={{ paddingHorizontal: 18, paddingTop: 18, gap: 14 }}>\n <Text style={{ fontSize: 22, fontWeight: '400', letterSpacing: -0.5, color: colors.foreground }}>{title}</Text>\n {rows.map(([t, s, m]) => (\n <View key={t} style={{ flexDirection: 'row', alignItems: 'center', gap: 12 }}>\n <View\n style={{\n width: 36,\n height: 36,\n borderRadius: radius.capsule(36),\n backgroundColor: alpha(colors.accent, dark ? 0.22 : 0.12),\n alignItems: 'center',\n justifyContent: 'center',\n }}\n >\n <Text style={{ fontSize: 13, fontWeight: '400', color: colors.accent }}>{t.slice(0, 1)}</Text>\n </View>\n <View style={{ flex: 1 }}>\n <Text numberOfLines={1} style={{ fontSize: 14.5, fontWeight: '400', color: colors.foreground }}>{t}</Text>\n <Text numberOfLines={1} style={{ fontSize: 13, fontWeight: '400', color: colors.mutedForeground }}>{s}</Text>\n </View>\n <Text style={{ fontSize: 12, fontWeight: '400', color: colors.mutedForeground }}>{m}</Text>\n </View>\n ))}\n </View>\n {children}\n </View>\n );\n}\n\nconst ROWS: Record<string, Row[]> = {\n home: [\n ['Maya Chen', 'Shipped the onboarding redesign', '2m'],\n ['Leo Park', 'Booked the team offsite for June', '1h'],\n ],\n explore: [\n ['Design Systems Weekly', 'Motion tokens that scale', 'New'],\n ['Product Notes', 'How Linear plans a cycle', '4m read'],\n ],\n search: [\n ['Recent: invoices', 'Q2 invoices · 14 results', 'Today'],\n ['Recent: Maya', 'People · Design team', 'Mon'],\n ],\n projects: [\n ['Mobile app 2.0', '18 of 24 tasks done', '75%'],\n ['Brand refresh', 'Review on Thursday', '40%'],\n ],\n saved: [\n ['Kyoto travel guide', 'Saved from Atlas', 'Apr 2'],\n ['Sourdough basics', 'Saved from Recipes', 'Mar 28'],\n ],\n inbox: [\n ['Priya Nair', 'Can you review the pricing page?', '9:41'],\n ['GitHub', 'PR #482 was approved', '9:12'],\n ],\n analytics: [\n ['Weekly active users', '12,480 · up 8.2%', '7d'],\n ['Conversion', '3.4% · up 0.6 pts', '7d'],\n ],\n activity: [\n ['Leo Park', 'Commented on Launch plan', '5m'],\n ['Ana Ruiz', 'Moved Pricing to Done', '22m'],\n ],\n profile: [\n ['Karan Kumar', 'Product designer · Berlin', 'Edit'],\n ['Notifications', 'Mentions and replies only', 'On'],\n ],\n home2: [\n ['Late Night Drive', 'Playlist · 42 songs', '2h 31m'],\n ['Midnight City', 'M83 · Hurry Up, We\\'re Dreaming', '4:03'],\n ],\n browse: [\n ['New this week', 'Fresh releases picked for you', '24'],\n ['Focus', 'Instrumental, low tempo', '60'],\n ],\n find: [\n ['Recent: Tycho', 'Artist · Ambient', 'Yesterday'],\n ['Recent: lo-fi beats', 'Playlist · 120 songs', 'Sun'],\n ],\n library: [\n ['Liked songs', '1,204 songs', 'Pinned'],\n ['Downloaded', '86 songs · 412 MB', 'Offline'],\n ],\n};\n\nconst titleOf = (items: BottomNavItem[], key: string) => items.find((i) => i.key === key)?.label ?? '';\n\nexport default function TabWithIndicator({ screen, bottomInset }: DemoProps) {\n const items: BottomNavItem[] = [\n { key: 'home', label: 'Home', icon: 'home' },\n { key: 'analytics', label: 'Insights', icon: 'analytics' },\n { key: 'activity', label: 'Activity', icon: 'activity' },\n { key: 'inbox', label: 'Inbox', icon: 'inbox', badge: 5 },\n { key: 'profile', label: 'Profile', icon: 'profile' },\n ];\n const [tab, setTab] = useState('analytics');\n return (\n <Screen screen={screen} title={titleOf(items, tab)} rows={ROWS[tab]}>\n <BottomNav bottomInset={bottomInset} variant=\"indicator\" items={items} value={tab} onValueChange={setTab} />\n </Screen>\n );\n}\n"
135
+ "code": "import React, { useState } from 'react';\nimport { Text, View } from 'react-native';\nimport { BottomNav, type BottomNavItem } from '@/components/ui/bottom-nav';\nimport { alpha, useKit } from '@/lib/kit';\n\n/** Control text stops at iOS's largest standard size. */\nconst FONT_CAP = 1.35;\n\ntype Row = [title: string, subtitle: string, meta: string];\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/** The bottom of a phone screen: a title, a few rows, the bar docked at the bottom. */\nfunction Screen({ title, rows, screen, children }: { title: string; rows: Row[]; screen?: boolean; children: React.ReactNode }) {\n const { colors, radius, dark } = useKit();\n return (\n <View\n style={[\n { alignSelf: 'stretch', overflow: 'hidden', backgroundColor: colors.background, justifyContent: 'space-between' },\n screen\n ? { flex: 1 }\n : { height: 300, borderRadius: radius.scale(28), borderWidth: 1, borderColor: alpha(colors.foreground, dark ? 0.1 : 0.06) },\n ]}\n >\n <View style={{ paddingHorizontal: 18, paddingTop: 18, gap: 14 }}>\n <Text maxFontSizeMultiplier={FONT_CAP} style={{ fontSize: 22, fontWeight: '400', letterSpacing: -0.5, color: colors.foreground }}>{title}</Text>\n {rows.map(([t, s, m]) => (\n <View key={t} style={{ flexDirection: 'row', alignItems: 'center', gap: 12 }}>\n <View\n style={{\n width: 36,\n height: 36,\n borderRadius: radius.capsule(36),\n backgroundColor: alpha(colors.accent, dark ? 0.22 : 0.12),\n alignItems: 'center',\n justifyContent: 'center',\n }}\n >\n <Text maxFontSizeMultiplier={FONT_CAP} style={{ fontSize: 13, fontWeight: '400', color: colors.accent }}>{t.slice(0, 1)}</Text>\n </View>\n <View style={{ flex: 1 }}>\n <Text maxFontSizeMultiplier={FONT_CAP} numberOfLines={1} style={{ fontSize: 14.5, fontWeight: '400', color: colors.foreground }}>{t}</Text>\n <Text maxFontSizeMultiplier={FONT_CAP} numberOfLines={1} style={{ fontSize: 13, fontWeight: '400', color: colors.mutedForeground }}>{s}</Text>\n </View>\n <Text maxFontSizeMultiplier={FONT_CAP} style={{ fontSize: 12, fontWeight: '400', color: colors.mutedForeground }}>{m}</Text>\n </View>\n ))}\n </View>\n {children}\n </View>\n );\n}\n\nconst ROWS: Record<string, Row[]> = {\n home: [\n ['Maya Chen', 'Shipped the onboarding redesign', '2m'],\n ['Leo Park', 'Booked the team offsite for June', '1h'],\n ],\n explore: [\n ['Design Systems Weekly', 'Motion tokens that scale', 'New'],\n ['Product Notes', 'How Linear plans a cycle', '4m read'],\n ],\n search: [\n ['Recent: invoices', 'Q2 invoices · 14 results', 'Today'],\n ['Recent: Maya', 'People · Design team', 'Mon'],\n ],\n projects: [\n ['Mobile app 2.0', '18 of 24 tasks done', '75%'],\n ['Brand refresh', 'Review on Thursday', '40%'],\n ],\n saved: [\n ['Kyoto travel guide', 'Saved from Atlas', 'Apr 2'],\n ['Sourdough basics', 'Saved from Recipes', 'Mar 28'],\n ],\n inbox: [\n ['Priya Nair', 'Can you review the pricing page?', '9:41'],\n ['GitHub', 'PR #482 was approved', '9:12'],\n ],\n analytics: [\n ['Weekly active users', '12,480 · up 8.2%', '7d'],\n ['Conversion', '3.4% · up 0.6 pts', '7d'],\n ],\n activity: [\n ['Leo Park', 'Commented on Launch plan', '5m'],\n ['Ana Ruiz', 'Moved Pricing to Done', '22m'],\n ],\n profile: [\n ['Karan Kumar', 'Product designer · Berlin', 'Edit'],\n ['Notifications', 'Mentions and replies only', 'On'],\n ],\n home2: [\n ['Late Night Drive', 'Playlist · 42 songs', '2h 31m'],\n ['Midnight City', 'M83 · Hurry Up, We\\'re Dreaming', '4:03'],\n ],\n browse: [\n ['New this week', 'Fresh releases picked for you', '24'],\n ['Focus', 'Instrumental, low tempo', '60'],\n ],\n find: [\n ['Recent: Tycho', 'Artist · Ambient', 'Yesterday'],\n ['Recent: lo-fi beats', 'Playlist · 120 songs', 'Sun'],\n ],\n library: [\n ['Liked songs', '1,204 songs', 'Pinned'],\n ['Downloaded', '86 songs · 412 MB', 'Offline'],\n ],\n};\n\nconst titleOf = (items: BottomNavItem[], key: string) => items.find((i) => i.key === key)?.label ?? '';\n\nexport default function TabWithIndicator({ screen, bottomInset }: DemoProps) {\n const items: BottomNavItem[] = [\n { key: 'home', label: 'Home', icon: 'home' },\n { key: 'analytics', label: 'Insights', icon: 'analytics' },\n { key: 'activity', label: 'Activity', icon: 'activity' },\n { key: 'inbox', label: 'Inbox', icon: 'inbox', badge: 5 },\n { key: 'profile', label: 'Profile', icon: 'profile' },\n ];\n const [tab, setTab] = useState('analytics');\n return (\n <Screen screen={screen} title={titleOf(items, tab)} rows={ROWS[tab]}>\n <BottomNav bottomInset={bottomInset} variant=\"indicator\" items={items} value={tab} onValueChange={setTab} />\n </Screen>\n );\n}\n"
136
136
  },
137
137
  {
138
138
  "name": "MusicIconsOnly",
139
139
  "title": "Music Icons Only",
140
- "code": "import React, { useState } from 'react';\nimport { Text, View } from 'react-native';\nimport { BottomNav, type BottomNavItem } from '@/components/ui/bottom-nav';\nimport { alpha, useKit } from '@/lib/kit';\n\ntype Row = [title: string, subtitle: string, meta: string];\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/** The bottom of a phone screen: a title, a few rows, the bar docked at the bottom. */\nfunction Screen({ title, rows, screen, children }: { title: string; rows: Row[]; screen?: boolean; children: React.ReactNode }) {\n const { colors, radius, dark } = useKit();\n return (\n <View\n style={[\n { alignSelf: 'stretch', overflow: 'hidden', backgroundColor: colors.background, justifyContent: 'space-between' },\n screen\n ? { flex: 1 }\n : { height: 300, borderRadius: radius.scale(28), borderWidth: 1, borderColor: alpha(colors.foreground, dark ? 0.1 : 0.06) },\n ]}\n >\n <View style={{ paddingHorizontal: 18, paddingTop: 18, gap: 14 }}>\n <Text style={{ fontSize: 22, fontWeight: '400', letterSpacing: -0.5, color: colors.foreground }}>{title}</Text>\n {rows.map(([t, s, m]) => (\n <View key={t} style={{ flexDirection: 'row', alignItems: 'center', gap: 12 }}>\n <View\n style={{\n width: 36,\n height: 36,\n borderRadius: radius.capsule(36),\n backgroundColor: alpha(colors.accent, dark ? 0.22 : 0.12),\n alignItems: 'center',\n justifyContent: 'center',\n }}\n >\n <Text style={{ fontSize: 13, fontWeight: '400', color: colors.accent }}>{t.slice(0, 1)}</Text>\n </View>\n <View style={{ flex: 1 }}>\n <Text numberOfLines={1} style={{ fontSize: 14.5, fontWeight: '400', color: colors.foreground }}>{t}</Text>\n <Text numberOfLines={1} style={{ fontSize: 13, fontWeight: '400', color: colors.mutedForeground }}>{s}</Text>\n </View>\n <Text style={{ fontSize: 12, fontWeight: '400', color: colors.mutedForeground }}>{m}</Text>\n </View>\n ))}\n </View>\n {children}\n </View>\n );\n}\n\nconst ROWS: Record<string, Row[]> = {\n home: [\n ['Maya Chen', 'Shipped the onboarding redesign', '2m'],\n ['Leo Park', 'Booked the team offsite for June', '1h'],\n ],\n explore: [\n ['Design Systems Weekly', 'Motion tokens that scale', 'New'],\n ['Product Notes', 'How Linear plans a cycle', '4m read'],\n ],\n search: [\n ['Recent: invoices', 'Q2 invoices · 14 results', 'Today'],\n ['Recent: Maya', 'People · Design team', 'Mon'],\n ],\n projects: [\n ['Mobile app 2.0', '18 of 24 tasks done', '75%'],\n ['Brand refresh', 'Review on Thursday', '40%'],\n ],\n saved: [\n ['Kyoto travel guide', 'Saved from Atlas', 'Apr 2'],\n ['Sourdough basics', 'Saved from Recipes', 'Mar 28'],\n ],\n inbox: [\n ['Priya Nair', 'Can you review the pricing page?', '9:41'],\n ['GitHub', 'PR #482 was approved', '9:12'],\n ],\n analytics: [\n ['Weekly active users', '12,480 · up 8.2%', '7d'],\n ['Conversion', '3.4% · up 0.6 pts', '7d'],\n ],\n activity: [\n ['Leo Park', 'Commented on Launch plan', '5m'],\n ['Ana Ruiz', 'Moved Pricing to Done', '22m'],\n ],\n profile: [\n ['Karan Kumar', 'Product designer · Berlin', 'Edit'],\n ['Notifications', 'Mentions and replies only', 'On'],\n ],\n home2: [\n ['Late Night Drive', 'Playlist · 42 songs', '2h 31m'],\n ['Midnight City', 'M83 · Hurry Up, We\\'re Dreaming', '4:03'],\n ],\n browse: [\n ['New this week', 'Fresh releases picked for you', '24'],\n ['Focus', 'Instrumental, low tempo', '60'],\n ],\n find: [\n ['Recent: Tycho', 'Artist · Ambient', 'Yesterday'],\n ['Recent: lo-fi beats', 'Playlist · 120 songs', 'Sun'],\n ],\n library: [\n ['Liked songs', '1,204 songs', 'Pinned'],\n ['Downloaded', '86 songs · 412 MB', 'Offline'],\n ],\n};\n\nconst titleOf = (items: BottomNavItem[], key: string) => items.find((i) => i.key === key)?.label ?? '';\n\nexport default function MusicIconsOnly({ screen, bottomInset }: DemoProps) {\n const items: BottomNavItem[] = [\n { key: 'home2', label: 'Listen now', icon: 'music' },\n { key: 'browse', label: 'Browse', icon: 'browse' },\n { key: 'library', label: 'Library', icon: 'library' },\n { key: 'find', label: 'Search', icon: 'search' },\n ];\n const [tab, setTab] = useState('home2');\n return (\n <Screen screen={screen} title={titleOf(items, tab)} rows={ROWS[tab]}>\n <BottomNav bottomInset={bottomInset} variant=\"floating\" showLabels={false} items={items} value={tab} onValueChange={setTab} />\n </Screen>\n );\n}\n"
140
+ "code": "import React, { useState } from 'react';\nimport { Text, View } from 'react-native';\nimport { BottomNav, type BottomNavItem } from '@/components/ui/bottom-nav';\nimport { alpha, useKit } from '@/lib/kit';\n\n/** Control text stops at iOS's largest standard size. */\nconst FONT_CAP = 1.35;\n\ntype Row = [title: string, subtitle: string, meta: string];\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/** The bottom of a phone screen: a title, a few rows, the bar docked at the bottom. */\nfunction Screen({ title, rows, screen, children }: { title: string; rows: Row[]; screen?: boolean; children: React.ReactNode }) {\n const { colors, radius, dark } = useKit();\n return (\n <View\n style={[\n { alignSelf: 'stretch', overflow: 'hidden', backgroundColor: colors.background, justifyContent: 'space-between' },\n screen\n ? { flex: 1 }\n : { height: 300, borderRadius: radius.scale(28), borderWidth: 1, borderColor: alpha(colors.foreground, dark ? 0.1 : 0.06) },\n ]}\n >\n <View style={{ paddingHorizontal: 18, paddingTop: 18, gap: 14 }}>\n <Text maxFontSizeMultiplier={FONT_CAP} style={{ fontSize: 22, fontWeight: '400', letterSpacing: -0.5, color: colors.foreground }}>{title}</Text>\n {rows.map(([t, s, m]) => (\n <View key={t} style={{ flexDirection: 'row', alignItems: 'center', gap: 12 }}>\n <View\n style={{\n width: 36,\n height: 36,\n borderRadius: radius.capsule(36),\n backgroundColor: alpha(colors.accent, dark ? 0.22 : 0.12),\n alignItems: 'center',\n justifyContent: 'center',\n }}\n >\n <Text maxFontSizeMultiplier={FONT_CAP} style={{ fontSize: 13, fontWeight: '400', color: colors.accent }}>{t.slice(0, 1)}</Text>\n </View>\n <View style={{ flex: 1 }}>\n <Text maxFontSizeMultiplier={FONT_CAP} numberOfLines={1} style={{ fontSize: 14.5, fontWeight: '400', color: colors.foreground }}>{t}</Text>\n <Text maxFontSizeMultiplier={FONT_CAP} numberOfLines={1} style={{ fontSize: 13, fontWeight: '400', color: colors.mutedForeground }}>{s}</Text>\n </View>\n <Text maxFontSizeMultiplier={FONT_CAP} style={{ fontSize: 12, fontWeight: '400', color: colors.mutedForeground }}>{m}</Text>\n </View>\n ))}\n </View>\n {children}\n </View>\n );\n}\n\nconst ROWS: Record<string, Row[]> = {\n home: [\n ['Maya Chen', 'Shipped the onboarding redesign', '2m'],\n ['Leo Park', 'Booked the team offsite for June', '1h'],\n ],\n explore: [\n ['Design Systems Weekly', 'Motion tokens that scale', 'New'],\n ['Product Notes', 'How Linear plans a cycle', '4m read'],\n ],\n search: [\n ['Recent: invoices', 'Q2 invoices · 14 results', 'Today'],\n ['Recent: Maya', 'People · Design team', 'Mon'],\n ],\n projects: [\n ['Mobile app 2.0', '18 of 24 tasks done', '75%'],\n ['Brand refresh', 'Review on Thursday', '40%'],\n ],\n saved: [\n ['Kyoto travel guide', 'Saved from Atlas', 'Apr 2'],\n ['Sourdough basics', 'Saved from Recipes', 'Mar 28'],\n ],\n inbox: [\n ['Priya Nair', 'Can you review the pricing page?', '9:41'],\n ['GitHub', 'PR #482 was approved', '9:12'],\n ],\n analytics: [\n ['Weekly active users', '12,480 · up 8.2%', '7d'],\n ['Conversion', '3.4% · up 0.6 pts', '7d'],\n ],\n activity: [\n ['Leo Park', 'Commented on Launch plan', '5m'],\n ['Ana Ruiz', 'Moved Pricing to Done', '22m'],\n ],\n profile: [\n ['Karan Kumar', 'Product designer · Berlin', 'Edit'],\n ['Notifications', 'Mentions and replies only', 'On'],\n ],\n home2: [\n ['Late Night Drive', 'Playlist · 42 songs', '2h 31m'],\n ['Midnight City', 'M83 · Hurry Up, We\\'re Dreaming', '4:03'],\n ],\n browse: [\n ['New this week', 'Fresh releases picked for you', '24'],\n ['Focus', 'Instrumental, low tempo', '60'],\n ],\n find: [\n ['Recent: Tycho', 'Artist · Ambient', 'Yesterday'],\n ['Recent: lo-fi beats', 'Playlist · 120 songs', 'Sun'],\n ],\n library: [\n ['Liked songs', '1,204 songs', 'Pinned'],\n ['Downloaded', '86 songs · 412 MB', 'Offline'],\n ],\n};\n\nconst titleOf = (items: BottomNavItem[], key: string) => items.find((i) => i.key === key)?.label ?? '';\n\nexport default function MusicIconsOnly({ screen, bottomInset }: DemoProps) {\n const items: BottomNavItem[] = [\n { key: 'home2', label: 'Listen now', icon: 'music' },\n { key: 'browse', label: 'Browse', icon: 'browse' },\n { key: 'library', label: 'Library', icon: 'library' },\n { key: 'find', label: 'Search', icon: 'search' },\n ];\n const [tab, setTab] = useState('home2');\n return (\n <Screen screen={screen} title={titleOf(items, tab)} rows={ROWS[tab]}>\n <BottomNav bottomInset={bottomInset} variant=\"floating\" showLabels={false} items={items} value={tab} onValueChange={setTab} />\n </Screen>\n );\n}\n"
141
141
  }
142
142
  ]
143
143
  }
@@ -160,17 +160,17 @@
160
160
  {
161
161
  "name": "Basic",
162
162
  "title": "Basic",
163
- "code": "import React, { useState } from 'react';\nimport { Pressable, ScrollView, Text, TextInput, View } from 'react-native';\nimport { Avatar } from '@/components/ui/avatar';\nimport { BottomSheet } from '@/components/ui/bottom-sheet';\nimport { Button } from '@/components/ui/button';\nimport { ListIcon, ListItem, ListSection } from '@/components/ui/list-item';\nimport { alpha, readableOn, useKit } from '@/lib/kit';\n\nexport default function Basic() {\n const { colors, radius } = useKit();\n const [open, setOpen] = useState(false);\n const apps: [string, 'mail' | 'phone' | 'globe' | 'heart', string][] = [\n ['Mail', 'mail', colors.accent],\n ['Messages', 'phone', colors.success],\n ['Browser', 'globe', colors.warning],\n ['Favorites', 'heart', colors.danger],\n ];\n return (\n <View style={{ alignItems: 'center' }}>\n <Button variant=\"accent\" onPress={() => setOpen(true)}>\n Share album\n </Button>\n <BottomSheet\n open={open}\n onClose={() => setOpen(false)}\n title=\"Share “Lisbon, May 12–16”\"\n subtitle=\"24 photos · 3 videos\"\n contentStyle={{ paddingHorizontal: 0 }}\n >\n <ScrollView horizontal showsHorizontalScrollIndicator={false} contentContainerStyle={{ gap: 18, paddingHorizontal: 20, paddingVertical: 8 }}>\n {['Ana Costa', 'Luis Moreno', 'Priya Shah', 'Tom Becker', 'Mei Lin'].map((name) => (\n <Pressable key={name} accessibilityRole=\"button\" accessibilityLabel={`Send to ${name}`} style={{ alignItems: 'center', gap: 6, width: 58 }}>\n <Avatar name={name} size={54} />\n <Text numberOfLines={1} style={{ fontSize: 12, color: colors.foreground }}>\n {name.split(' ')[0]}\n </Text>\n </Pressable>\n ))}\n </ScrollView>\n <View style={{ flexDirection: 'row', justifyContent: 'space-between', paddingHorizontal: 20, paddingVertical: 14 }}>\n {apps.map(([label, icon, color]) => (\n <Pressable key={label} accessibilityRole=\"button\" accessibilityLabel={label} style={{ alignItems: 'center', gap: 6, flex: 1 }}>\n <View\n style={{\n width: 54,\n height: 54,\n borderRadius: radius.scale(16),\n backgroundColor: color,\n alignItems: 'center',\n justifyContent: 'center',\n }}\n >\n <ListIcon name={icon} color={readableOn(color)} size={24} />\n </View>\n <Text style={{ fontSize: 12, color: colors.mutedForeground }}>{label}</Text>\n </Pressable>\n ))}\n </View>\n <ListSection>\n <ListItem title=\"Copy link\" icon=\"globe\" iconVariant=\"tinted\" chevron={false} size=\"sm\" onPress={() => setOpen(false)} />\n <ListItem title=\"Add to shared album\" icon=\"user\" iconVariant=\"tinted\" chevron={false} size=\"sm\" onPress={() => setOpen(false)} />\n <ListItem title=\"Mark as favorite\" icon=\"star\" iconVariant=\"tinted\" chevron={false} size=\"sm\" onPress={() => setOpen(false)} />\n </ListSection>\n </BottomSheet>\n </View>\n );\n}\n"
163
+ "code": "import React, { useState } from 'react';\nimport { Pressable, ScrollView, Text, TextInput, View } from 'react-native';\nimport { Avatar } from '@/components/ui/avatar';\nimport { BottomSheet } from '@/components/ui/bottom-sheet';\nimport { Button } from '@/components/ui/button';\nimport { ListIcon, ListItem, ListSection } from '@/components/ui/list-item';\nimport { alpha, readableOn, useKit } from '@/lib/kit';\n\n/** Demo control text (buttons, chips, labels on fixed shapes) stops at iOS's largest standard text size. */\nconst FONT_CAP = 1.35;\n\nexport default function Basic() {\n const { colors, radius } = useKit();\n const [open, setOpen] = useState(false);\n const apps: [string, 'mail' | 'phone' | 'globe' | 'heart', string][] = [\n ['Mail', 'mail', colors.accent],\n ['Messages', 'phone', colors.success],\n ['Browser', 'globe', colors.warning],\n ['Favorites', 'heart', colors.danger],\n ];\n return (\n <View style={{ alignItems: 'center' }}>\n <Button variant=\"accent\" onPress={() => setOpen(true)}>\n Share album\n </Button>\n <BottomSheet\n open={open}\n onClose={() => setOpen(false)}\n title=\"Share “Lisbon, May 12–16”\"\n subtitle=\"24 photos · 3 videos\"\n contentStyle={{ paddingHorizontal: 0 }}\n >\n <ScrollView horizontal showsHorizontalScrollIndicator={false} contentContainerStyle={{ gap: 18, paddingHorizontal: 20, paddingVertical: 8 }}>\n {['Ana Costa', 'Luis Moreno', 'Priya Shah', 'Tom Becker', 'Mei Lin'].map((name) => (\n <Pressable key={name} accessibilityRole=\"button\" accessibilityLabel={`Send to ${name}`} style={{ alignItems: 'center', gap: 6, width: 58 }}>\n <Avatar name={name} size={54} />\n <Text maxFontSizeMultiplier={FONT_CAP} numberOfLines={1} style={{ fontSize: 12, color: colors.foreground }}>\n {name.split(' ')[0]}\n </Text>\n </Pressable>\n ))}\n </ScrollView>\n <View style={{ flexDirection: 'row', justifyContent: 'space-between', paddingHorizontal: 20, paddingVertical: 14 }}>\n {apps.map(([label, icon, color]) => (\n <Pressable key={label} accessibilityRole=\"button\" accessibilityLabel={label} style={{ alignItems: 'center', gap: 6, flex: 1 }}>\n <View\n style={{\n width: 54,\n height: 54,\n borderRadius: radius.scale(16),\n backgroundColor: color,\n alignItems: 'center',\n justifyContent: 'center',\n }}\n >\n <ListIcon name={icon} color={readableOn(color)} size={24} />\n </View>\n <Text maxFontSizeMultiplier={FONT_CAP} style={{ fontSize: 12, color: colors.mutedForeground }}>{label}</Text>\n </Pressable>\n ))}\n </View>\n <ListSection>\n <ListItem title=\"Copy link\" icon=\"globe\" iconVariant=\"tinted\" chevron={false} size=\"sm\" onPress={() => setOpen(false)} />\n <ListItem title=\"Add to shared album\" icon=\"user\" iconVariant=\"tinted\" chevron={false} size=\"sm\" onPress={() => setOpen(false)} />\n <ListItem title=\"Mark as favorite\" icon=\"star\" iconVariant=\"tinted\" chevron={false} size=\"sm\" onPress={() => setOpen(false)} />\n </ListSection>\n </BottomSheet>\n </View>\n );\n}\n"
164
164
  },
165
165
  {
166
166
  "name": "SnapPoints",
167
167
  "title": "Snap Points",
168
- "code": "import React, { useState } from 'react';\nimport { Pressable, ScrollView, Text, TextInput, View } from 'react-native';\nimport { Avatar } from '@/components/ui/avatar';\nimport { BottomSheet } from '@/components/ui/bottom-sheet';\nimport { Button } from '@/components/ui/button';\nimport { ListIcon, ListItem, ListSection } from '@/components/ui/list-item';\nimport { alpha, readableOn, useKit } from '@/lib/kit';\n\nfunction useMuted() {\n const { colors } = useKit();\n return { fontSize: 14, lineHeight: 20, color: colors.mutedForeground };\n}\n\nexport default function SnapPoints() {\n const { colors, radius } = useKit();\n const muted = useMuted();\n const [open, setOpen] = useState(false);\n const [snap, setSnap] = useState(0);\n return (\n <View style={{ alignItems: 'center' }}>\n <Button variant=\"accent\" onPress={() => setOpen(true)}>\n Track your ride\n </Button>\n <BottomSheet\n open={open}\n onClose={() => {\n setOpen(false);\n setSnap(0);\n }}\n snapPoints={['40%', '90%']}\n snapIndex={snap}\n onSnapChange={setSnap}\n scrollable\n accessibilityLabel=\"Ride details\"\n >\n <View style={{ flexDirection: 'row', alignItems: 'flex-start', justifyContent: 'space-between' }}>\n <View>\n <Text style={{ fontSize: 24, letterSpacing: -0.5, color: colors.foreground }}>Arriving in 4 min</Text>\n <Text style={[muted, { marginTop: 2 }]}>Silver Toyota Prius · 7KJ 482</Text>\n </View>\n <View\n style={{\n paddingHorizontal: 10,\n height: 28,\n justifyContent: 'center',\n borderRadius: radius.capsule(28),\n backgroundColor: alpha(colors.success, 0.14),\n }}\n >\n <Text style={{ fontSize: 13, color: colors.success }}>On time</Text>\n </View>\n </View>\n <View style={{ flexDirection: 'row', alignItems: 'center', gap: 12, marginTop: 18 }}>\n <Avatar name=\"Daniel Ortiz\" size={48} />\n <View style={{ flex: 1 }}>\n <Text style={{ fontSize: 16, color: colors.foreground }}>Daniel Ortiz</Text>\n <Text style={muted}>★ 4.92 · 2,184 trips</Text>\n </View>\n </View>\n <View style={{ flexDirection: 'row', gap: 10, marginTop: 18 }}>\n <Button variant=\"secondary\" style={{ flex: 1 }}>\n Message\n </Button>\n <Button variant=\"secondary\" style={{ flex: 1 }}>\n Call\n </Button>\n <Button variant=\"secondary\" style={{ flex: 1 }} onPress={() => setSnap(1)}>\n {snap === 0 ? 'Details' : 'Share'}\n </Button>\n </View>\n\n <Text style={[muted, { marginTop: 28, marginBottom: 6, fontSize: 12, letterSpacing: 0.8 }]}>TRIP</Text>\n {[\n ['Pickup', 'Rua Augusta 24, Baixa', colors.accent],\n ['Drop-off', 'Lisbon Airport, Terminal 1', colors.foreground],\n ].map(([label, place, color]) => (\n <View key={label} style={{ flexDirection: 'row', gap: 12, paddingVertical: 10 }}>\n <View style={{ width: 10, height: 10, marginTop: 5, borderRadius: radius.capsule(10), backgroundColor: color }} />\n <View>\n <Text style={muted}>{label}</Text>\n <Text style={{ fontSize: 15, color: colors.foreground }}>{place}</Text>\n </View>\n </View>\n ))}\n <Text style={[muted, { marginTop: 22, marginBottom: 6, fontSize: 12, letterSpacing: 0.8 }]}>FARE</Text>\n {[\n ['Comfort · 14.2 km', '₹333'],\n ['Airport fee', '₹50'],\n ['Promo SPRING10', '−₹35'],\n ['Total · Visa ••42', '₹348'],\n ].map(([item, price], i, all) => (\n <View key={item} style={{ flexDirection: 'row', justifyContent: 'space-between', paddingVertical: 10 }}>\n <Text style={[muted, i === all.length - 1 && { color: colors.foreground }]}>{item}</Text>\n <Text style={{ fontSize: 14, color: colors.foreground }}>{price}</Text>\n </View>\n ))}\n <Text style={[muted, { marginTop: 22, marginBottom: 6, fontSize: 12, letterSpacing: 0.8 }]}>SAFETY</Text>\n <ListSection>\n <ListItem title=\"Share trip status\" icon=\"user\" iconVariant=\"tinted\" size=\"sm\" onPress={() => {}} />\n <ListItem title=\"Emergency assistance\" icon=\"shield\" iconVariant=\"tinted\" size=\"sm\" onPress={() => {}} />\n <ListItem title=\"Cancel ride\" icon=\"trash\" destructive size=\"sm\" chevron={false} onPress={() => setOpen(false)} />\n </ListSection>\n </BottomSheet>\n </View>\n );\n}\n"
168
+ "code": "import React, { useState } from 'react';\nimport { Pressable, ScrollView, Text, TextInput, View } from 'react-native';\nimport { Avatar } from '@/components/ui/avatar';\nimport { BottomSheet } from '@/components/ui/bottom-sheet';\nimport { Button } from '@/components/ui/button';\nimport { ListIcon, ListItem, ListSection } from '@/components/ui/list-item';\nimport { alpha, readableOn, useKit } from '@/lib/kit';\n\n/** Demo control text (buttons, chips, labels on fixed shapes) stops at iOS's largest standard text size. */\nconst FONT_CAP = 1.35;\n\nfunction useMuted() {\n const { colors } = useKit();\n return { fontSize: 14, lineHeight: 20, color: colors.mutedForeground };\n}\n\nexport default function SnapPoints() {\n const { colors, radius } = useKit();\n const muted = useMuted();\n const [open, setOpen] = useState(false);\n const [snap, setSnap] = useState(0);\n return (\n <View style={{ alignItems: 'center' }}>\n <Button variant=\"accent\" onPress={() => setOpen(true)}>\n Track your ride\n </Button>\n <BottomSheet\n open={open}\n onClose={() => {\n setOpen(false);\n setSnap(0);\n }}\n snapPoints={['40%', '90%']}\n snapIndex={snap}\n onSnapChange={setSnap}\n scrollable\n accessibilityLabel=\"Ride details\"\n >\n <View style={{ flexDirection: 'row', alignItems: 'flex-start', justifyContent: 'space-between' }}>\n <View style={{ flexShrink: 1 }}>\n <Text style={{ fontSize: 24, letterSpacing: -0.5, color: colors.foreground }}>Arriving in 4 min</Text>\n <Text style={[muted, { marginTop: 2 }]}>Silver Toyota Prius · 7KJ 482</Text>\n </View>\n <View\n style={{\n paddingHorizontal: 10,\n minHeight: 28,\n justifyContent: 'center',\n borderRadius: radius.capsule(28),\n backgroundColor: alpha(colors.success, 0.14),\n }}\n >\n <Text maxFontSizeMultiplier={FONT_CAP} style={{ fontSize: 13, color: colors.success }}>On time</Text>\n </View>\n </View>\n <View style={{ flexDirection: 'row', alignItems: 'center', gap: 12, marginTop: 18 }}>\n <Avatar name=\"Daniel Ortiz\" size={48} />\n <View style={{ flex: 1 }}>\n <Text style={{ fontSize: 16, color: colors.foreground }}>Daniel Ortiz</Text>\n <Text style={muted}>★ 4.92 · 2,184 trips</Text>\n </View>\n </View>\n <View style={{ flexDirection: 'row', gap: 10, marginTop: 18 }}>\n <Button variant=\"secondary\" style={{ flex: 1 }}>\n Message\n </Button>\n <Button variant=\"secondary\" style={{ flex: 1 }}>\n Call\n </Button>\n <Button variant=\"secondary\" style={{ flex: 1 }} onPress={() => setSnap(1)}>\n {snap === 0 ? 'Details' : 'Share'}\n </Button>\n </View>\n\n <Text style={[muted, { marginTop: 28, marginBottom: 6, fontSize: 12, letterSpacing: 0.8 }]}>TRIP</Text>\n {[\n ['Pickup', 'Rua Augusta 24, Baixa', colors.accent],\n ['Drop-off', 'Lisbon Airport, Terminal 1', colors.foreground],\n ].map(([label, place, color]) => (\n <View key={label} style={{ flexDirection: 'row', gap: 12, paddingVertical: 10 }}>\n <View style={{ width: 10, height: 10, marginTop: 5, borderRadius: radius.capsule(10), backgroundColor: color }} />\n <View style={{ flexShrink: 1 }}>\n <Text style={muted}>{label}</Text>\n <Text style={{ fontSize: 15, color: colors.foreground }}>{place}</Text>\n </View>\n </View>\n ))}\n <Text style={[muted, { marginTop: 22, marginBottom: 6, fontSize: 12, letterSpacing: 0.8 }]}>FARE</Text>\n {[\n ['Comfort · 14.2 km', '₹333'],\n ['Airport fee', '₹50'],\n ['Promo SPRING10', '−₹35'],\n ['Total · Visa ••42', '₹348'],\n ].map(([item, price], i, all) => (\n <View key={item} style={{ flexDirection: 'row', justifyContent: 'space-between', gap: 12, paddingVertical: 10 }}>\n <Text style={[muted, { flexShrink: 1 }, i === all.length - 1 && { color: colors.foreground }]}>{item}</Text>\n <Text style={{ fontSize: 14, color: colors.foreground }}>{price}</Text>\n </View>\n ))}\n <Text style={[muted, { marginTop: 22, marginBottom: 6, fontSize: 12, letterSpacing: 0.8 }]}>SAFETY</Text>\n <ListSection>\n <ListItem title=\"Share trip status\" icon=\"user\" iconVariant=\"tinted\" size=\"sm\" onPress={() => {}} />\n <ListItem title=\"Emergency assistance\" icon=\"shield\" iconVariant=\"tinted\" size=\"sm\" onPress={() => {}} />\n <ListItem title=\"Cancel ride\" icon=\"trash\" destructive size=\"sm\" chevron={false} onPress={() => setOpen(false)} />\n </ListSection>\n </BottomSheet>\n </View>\n );\n}\n"
169
169
  },
170
170
  {
171
171
  "name": "Filters",
172
172
  "title": "Filters",
173
- "code": "import React, { useState } from 'react';\nimport { Pressable, ScrollView, Text, TextInput, View } from 'react-native';\nimport { BottomSheet } from '@/components/ui/bottom-sheet';\nimport { Button } from '@/components/ui/button';\nimport { ChipGroup } from '@/components/ui/chip-group';\nimport { Switch } from '@/components/ui/switch';\nimport { alpha, readableOn, useKit } from '@/lib/kit';\n\nexport default function Filters() {\n const { colors } = useKit();\n const [open, setOpen] = useState(false);\n const [type, setType] = useState<string[]>(['Entire home']);\n const [price, setPrice] = useState<string[]>(['₹5,000 – ₹10,000']);\n const [rooms, setRooms] = useState<string[]>(['2']);\n const [amenities, setAmenities] = useState<string[]>(['Wi-Fi', 'Kitchen']);\n const [instant, setInstant] = useState(true);\n const [flexible, setFlexible] = useState(false);\n const count = 214 - amenities.length * 23 - (instant ? 31 : 0) - (flexible ? 18 : 0);\n const reset = () => {\n setType([]);\n setPrice([]);\n setRooms([]);\n setAmenities([]);\n setInstant(false);\n setFlexible(false);\n };\n const heading = (t: string) => <Text style={{ fontSize: 16, color: colors.foreground, marginTop: 20, marginBottom: 10 }}>{t}</Text>;\n return (\n <View style={{ alignItems: 'center' }}>\n <Button variant=\"outline\" onPress={() => setOpen(true)}>\n Filters · Porto\n </Button>\n <BottomSheet\n open={open}\n onClose={() => setOpen(false)}\n snapPoints={['62%', '92%']}\n scrollable\n title=\"Filters\"\n headerRight={\n <Pressable onPress={reset} accessibilityRole=\"button\" hitSlop={12}>\n <Text style={{ fontSize: 15, color: colors.accent }}>Reset</Text>\n </Pressable>\n }\n footer={\n <Button variant=\"accent\" block onPress={() => setOpen(false)}>\n {`Show ${count} homes`}\n </Button>\n }\n >\n {heading('Type of place')}\n <ChipGroup options={['Entire home', 'Private room', 'Shared room']} value={type} onValueChange={setType} />\n {heading('Price per night')}\n <ChipGroup options={['Under ₹5,000', '₹5,000 – ₹10,000', '₹10,000+']} value={price} onValueChange={setPrice} />\n {heading('Bedrooms')}\n <ChipGroup options={['Any', '1', '2', '3', '4+']} value={rooms} onValueChange={setRooms} />\n {heading('Amenities')}\n <ChipGroup\n multiple\n options={['Wi-Fi', 'Kitchen', 'Washer', 'Air conditioning', 'Free parking', 'Workspace', 'Pool']}\n value={amenities}\n onValueChange={setAmenities}\n />\n {heading('Booking options')}\n <View style={{ gap: 14 }}>\n <Switch label=\"Instant book\" description=\"Book without waiting for host approval\" value={instant} onValueChange={setInstant} />\n <Switch label=\"Free cancellation\" description=\"Cancel up to 48 h before check-in\" value={flexible} onValueChange={setFlexible} />\n </View>\n </BottomSheet>\n </View>\n );\n}\n"
173
+ "code": "import React, { useState } from 'react';\nimport { Pressable, ScrollView, Text, TextInput, View } from 'react-native';\nimport { BottomSheet } from '@/components/ui/bottom-sheet';\nimport { Button } from '@/components/ui/button';\nimport { ChipGroup } from '@/components/ui/chip-group';\nimport { Switch } from '@/components/ui/switch';\nimport { alpha, readableOn, useKit } from '@/lib/kit';\n\n/** Demo control text (buttons, chips, labels on fixed shapes) stops at iOS's largest standard text size. */\nconst FONT_CAP = 1.35;\n\nexport default function Filters() {\n const { colors } = useKit();\n const [open, setOpen] = useState(false);\n const [type, setType] = useState<string[]>(['Entire home']);\n const [price, setPrice] = useState<string[]>(['₹5,000 – ₹10,000']);\n const [rooms, setRooms] = useState<string[]>(['2']);\n const [amenities, setAmenities] = useState<string[]>(['Wi-Fi', 'Kitchen']);\n const [instant, setInstant] = useState(true);\n const [flexible, setFlexible] = useState(false);\n const count = 214 - amenities.length * 23 - (instant ? 31 : 0) - (flexible ? 18 : 0);\n const reset = () => {\n setType([]);\n setPrice([]);\n setRooms([]);\n setAmenities([]);\n setInstant(false);\n setFlexible(false);\n };\n const heading = (t: string) => <Text style={{ fontSize: 16, color: colors.foreground, marginTop: 20, marginBottom: 10 }}>{t}</Text>;\n return (\n <View style={{ alignItems: 'center' }}>\n <Button variant=\"outline\" onPress={() => setOpen(true)}>\n Filters · Porto\n </Button>\n <BottomSheet\n open={open}\n onClose={() => setOpen(false)}\n snapPoints={['62%', '92%']}\n scrollable\n title=\"Filters\"\n headerRight={\n <Pressable onPress={reset} accessibilityRole=\"button\" hitSlop={12}>\n <Text maxFontSizeMultiplier={FONT_CAP} style={{ fontSize: 15, color: colors.accent }}>Reset</Text>\n </Pressable>\n }\n footer={\n <Button variant=\"accent\" block onPress={() => setOpen(false)}>\n {`Show ${count} homes`}\n </Button>\n }\n >\n {heading('Type of place')}\n <ChipGroup options={['Entire home', 'Private room', 'Shared room']} value={type} onValueChange={setType} />\n {heading('Price per night')}\n <ChipGroup options={['Under ₹5,000', '₹5,000 – ₹10,000', '₹10,000+']} value={price} onValueChange={setPrice} />\n {heading('Bedrooms')}\n <ChipGroup options={['Any', '1', '2', '3', '4+']} value={rooms} onValueChange={setRooms} />\n {heading('Amenities')}\n <ChipGroup\n multiple\n options={['Wi-Fi', 'Kitchen', 'Washer', 'Air conditioning', 'Free parking', 'Workspace', 'Pool']}\n value={amenities}\n onValueChange={setAmenities}\n />\n {heading('Booking options')}\n <View style={{ gap: 14 }}>\n <Switch label=\"Instant book\" description=\"Book without waiting for host approval\" value={instant} onValueChange={setInstant} />\n <Switch label=\"Free cancellation\" description=\"Cancel up to 48 h before check-in\" value={flexible} onValueChange={setFlexible} />\n </View>\n </BottomSheet>\n </View>\n );\n}\n"
174
174
  },
175
175
  {
176
176
  "name": "KeyboardAware",
@@ -19,6 +19,12 @@
19
19
  "number",
20
20
  "52 / 14",
21
21
  "Sizing."
22
+ ],
23
+ [
24
+ "accessibilityLabel",
25
+ "string",
26
+ "auto summary",
27
+ "What screen readers announce; defaults to a summary of the data."
22
28
  ]
23
29
  ],
24
30
  "behavior": [],
@@ -112,7 +112,7 @@
112
112
  {
113
113
  "name": "OnPhoto",
114
114
  "title": "On Photo",
115
- "code": "import React, { useState } from 'react';\nimport { Image, Text, View } from 'react-native';\nimport { Button3D } from '@/components/ui/button-3d';\nimport { GrainGradient } from '@/components/ui/grain';\nimport { useKit } from '@/lib/kit';\n\n/** From the Expo Go viewer: `screen` = this IS the phone screen, so no mock frame; `bottomInset` = home indicator. */\ntype DemoProps = { screen?: boolean; bottomInset?: number };\n\n/** A screen title, as it sits above the content 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 OnPhoto({ screen }: DemoProps) {\n const { colors } = useKit();\n return (\n <View style={[{ gap: 18 }, screen && { flex: 1 }]}>\n <Header\n title=\"Good morning, Maya\"\n sub=\"Your trip starts in 3 days\"\n right={\n <View style={{ width: 40, height: 40, borderRadius: 20, backgroundColor: colors.muted, alignItems: 'center', justifyContent: 'center' }}>\n <Text style={{ color: colors.foreground, fontSize: 14, fontWeight: '700' }}>MP</Text>\n </View>\n }\n />\n <View style={[{ borderRadius: 28, overflow: 'hidden', backgroundColor: '#1a2a22' }, screen ? { flex: 1, minHeight: 440 } : { height: 440 }]}>\n <GrainGradient palette=\"aurora\" seed={2} style={{ position: 'absolute', top: 0, left: 0, right: 0, bottom: 0 }} />\n <View style={{ position: 'absolute', top: 0, left: 0, right: 0, bottom: 0, backgroundColor: 'rgba(0,0,0,0.28)' }} />\n <View style={{ flex: 1, justifyContent: 'flex-end', padding: 22, gap: 16 }}>\n <View>\n <Text style={{ color: 'rgba(255,255,255,0.85)', fontSize: 12, fontWeight: '700', letterSpacing: 1.2 }}>NORTHERN LIGHTS · 4 NIGHTS</Text>\n <Text style={{ color: '#fff', fontSize: 30, fontWeight: '800', letterSpacing: -0.8 }}>Aurora Weekend</Text>\n <Text style={{ color: 'rgba(255,255,255,0.8)', fontSize: 13, marginTop: 2 }}>Glass cabins, hot springs and skies that glow green</Text>\n </View>\n <View style={{ flexDirection: 'row', gap: 10, alignItems: 'center' }}>\n <Button3D appearance=\"glass\" size=\"lg\" shape=\"pill\" style={{ flex: 1 }} block>\n Start the trip\n </Button3D>\n <Button3D appearance=\"glass\" size=\"lg\" shape=\"circle\" accessibilityLabel=\"Save\">\n ♡\n </Button3D>\n <Button3D appearance=\"glass\" size=\"lg\" shape=\"circle\" accessibilityLabel=\"Share\">\n ↗\n </Button3D>\n </View>\n </View>\n </View>\n </View>\n );\n}\n"
115
+ "code": "import React, { useState } from 'react';\nimport { Image, Text, View } from 'react-native';\nimport { Button3D } from '@/components/ui/button-3d';\nimport { GrainGradient } from '@/components/ui/grain';\nimport { useKit } from '@/lib/kit';\n\n/** Demo control text (buttons, chips, labels on fixed shapes) stops at iOS's largest standard text size. */\nconst FONT_CAP = 1.35;\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/** A screen title, as it sits above the content 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 OnPhoto({ screen }: DemoProps) {\n const { colors } = useKit();\n return (\n <View style={[{ gap: 18 }, screen && { flex: 1 }]}>\n <Header\n title=\"Good morning, Maya\"\n sub=\"Your trip starts in 3 days\"\n right={\n <View style={{ width: 40, height: 40, borderRadius: 20, backgroundColor: colors.muted, alignItems: 'center', justifyContent: 'center' }}>\n <Text maxFontSizeMultiplier={FONT_CAP} style={{ color: colors.foreground, fontSize: 14, fontWeight: '700' }}>MP</Text>\n </View>\n }\n />\n <View style={[{ borderRadius: 28, overflow: 'hidden', backgroundColor: '#1a2a22' }, screen ? { flex: 1, minHeight: 440 } : { height: 440 }]}>\n <GrainGradient palette=\"aurora\" seed={2} style={{ position: 'absolute', top: 0, left: 0, right: 0, bottom: 0 }} />\n <View style={{ position: 'absolute', top: 0, left: 0, right: 0, bottom: 0, backgroundColor: 'rgba(0,0,0,0.28)' }} />\n <View style={{ flex: 1, justifyContent: 'flex-end', padding: 22, gap: 16 }}>\n <View>\n <Text maxFontSizeMultiplier={FONT_CAP} style={{ color: 'rgba(255,255,255,0.85)', fontSize: 12, fontWeight: '700', letterSpacing: 1.2 }}>NORTHERN LIGHTS · 4 NIGHTS</Text>\n <Text maxFontSizeMultiplier={FONT_CAP} style={{ color: '#fff', fontSize: 30, fontWeight: '800', letterSpacing: -0.8 }}>Aurora Weekend</Text>\n <Text maxFontSizeMultiplier={FONT_CAP} style={{ color: 'rgba(255,255,255,0.8)', fontSize: 13, marginTop: 2 }}>Glass cabins, hot springs and skies that glow green</Text>\n </View>\n <View style={{ flexDirection: 'row', gap: 10, alignItems: 'center' }}>\n <Button3D appearance=\"glass\" size=\"lg\" shape=\"pill\" style={{ flex: 1 }} block>\n Start the trip\n </Button3D>\n <Button3D appearance=\"glass\" size=\"lg\" shape=\"circle\" accessibilityLabel=\"Save\">\n ♡\n </Button3D>\n <Button3D appearance=\"glass\" size=\"lg\" shape=\"circle\" accessibilityLabel=\"Share\">\n ↗\n </Button3D>\n </View>\n </View>\n </View>\n </View>\n );\n}\n"
116
116
  },
117
117
  {
118
118
  "name": "Keypad",
@@ -122,7 +122,7 @@
122
122
  {
123
123
  "name": "Checkout",
124
124
  "title": "Checkout",
125
- "code": "import React, { useState } from 'react';\nimport { Image, Text, View } from 'react-native';\nimport { Button3D } from '@/components/ui/button-3d';\nimport { 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 content 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 Checkout() {\n const { colors } = useKit();\n const [busy, setBusy] = useState(false);\n const items = [\n { img: 766, name: 'House blend beans', detail: '250 g · ×2', price: '₹880' },\n { img: 1060, name: 'Pour-over filters', detail: 'Pack of 100', price: '₹120' },\n ];\n const line = (label: string, value: string, strong = false) => (\n <View style={{ flexDirection: 'row', justifyContent: 'space-between' }}>\n <Text style={{ color: strong ? colors.foreground : colors.mutedForeground, fontSize: strong ? 16 : 14, fontWeight: strong ? '700' : '400' }}>{label}</Text>\n <Text style={{ color: colors.foreground, fontSize: strong ? 16 : 14, fontWeight: strong ? '700' : '500' }}>{value}</Text>\n </View>\n );\n return (\n <View style={{ gap: 18 }}>\n <Header title=\"Checkout\" sub=\"2 items · arrives tomorrow\" />\n <View style={{ gap: 12 }}>\n {items.map((it) => (\n <View key={it.name} style={{ flexDirection: 'row', alignItems: 'center', gap: 12 }}>\n <Image alt=\"\" source={photo(it.img, 120, 120)} style={{ width: 52, height: 52, borderRadius: 12, backgroundColor: colors.muted }} />\n <View style={{ flex: 1 }}>\n <Text style={{ color: colors.foreground, fontSize: 15, fontWeight: '600' }}>{it.name}</Text>\n <Text style={{ color: colors.mutedForeground, fontSize: 12 }}>{it.detail}</Text>\n </View>\n <Text style={{ color: colors.foreground, fontSize: 14, fontWeight: '600' }}>{it.price}</Text>\n </View>\n ))}\n </View>\n <View style={{ gap: 8, paddingTop: 14, borderTopWidth: 1, borderTopColor: colors.border }}>\n {line('Subtotal', '₹1,000')}\n {line('Express delivery', '₹240')}\n {line('Total', '₹1,240', true)}\n </View>\n <View style={{ gap: 12 }}>\n <Button3D\n block\n size=\"lg\"\n loading={busy}\n onPress={() => {\n setBusy(true);\n setTimeout(() => setBusy(false), 1500);\n }}\n >\n Pay ₹1,240\n </Button3D>\n <Button3D block size=\"md\" color={colors.muted}>\n Add a promo code\n </Button3D>\n </View>\n </View>\n );\n}\n"
125
+ "code": "import React, { useState } from 'react';\nimport { Image, Text, View } from 'react-native';\nimport { Button3D } from '@/components/ui/button-3d';\nimport { 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 content 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 Checkout() {\n const { colors } = useKit();\n const [busy, setBusy] = useState(false);\n const items = [\n { img: 766, name: 'House blend beans', detail: '250 g · ×2', price: '₹880' },\n { img: 1060, name: 'Pour-over filters', detail: 'Pack of 100', price: '₹120' },\n ];\n const line = (label: string, value: string, strong = false) => (\n <View style={{ flexDirection: 'row', justifyContent: 'space-between' }}>\n <Text style={{ flexShrink: 1, color: strong ? colors.foreground : colors.mutedForeground, fontSize: strong ? 16 : 14, fontWeight: strong ? '700' : '400' }}>{label}</Text>\n <Text style={{ color: colors.foreground, fontSize: strong ? 16 : 14, fontWeight: strong ? '700' : '500' }}>{value}</Text>\n </View>\n );\n return (\n <View style={{ gap: 18 }}>\n <Header title=\"Checkout\" sub=\"2 items · arrives tomorrow\" />\n <View style={{ gap: 12 }}>\n {items.map((it) => (\n <View key={it.name} style={{ flexDirection: 'row', alignItems: 'center', gap: 12 }}>\n <Image alt=\"\" source={photo(it.img, 120, 120)} style={{ width: 52, height: 52, borderRadius: 12, backgroundColor: colors.muted }} />\n <View style={{ flex: 1 }}>\n <Text style={{ color: colors.foreground, fontSize: 15, fontWeight: '600' }}>{it.name}</Text>\n <Text style={{ color: colors.mutedForeground, fontSize: 12 }}>{it.detail}</Text>\n </View>\n <Text style={{ color: colors.foreground, fontSize: 14, fontWeight: '600' }}>{it.price}</Text>\n </View>\n ))}\n </View>\n <View style={{ gap: 8, paddingTop: 14, borderTopWidth: 1, borderTopColor: colors.border }}>\n {line('Subtotal', '₹1,000')}\n {line('Express delivery', '₹240')}\n {line('Total', '₹1,240', true)}\n </View>\n <View style={{ gap: 12 }}>\n <Button3D\n block\n size=\"lg\"\n loading={busy}\n onPress={() => {\n setBusy(true);\n setTimeout(() => setBusy(false), 1500);\n }}\n >\n Pay ₹1,240\n </Button3D>\n <Button3D block size=\"md\" color={colors.muted}>\n Add a promo code\n </Button3D>\n </View>\n </View>\n );\n}\n"
126
126
  },
127
127
  {
128
128
  "name": "Keycaps",
@@ -105,7 +105,7 @@
105
105
  {
106
106
  "name": "GradientAndGlow",
107
107
  "title": "Gradient And Glow",
108
- "code": "import { Pressable, Text, View } from 'react-native';\nimport { Button } from '@/components/ui/button';\nimport { readableOn, useKit } from '@/lib/kit';\n\nexport default function GradientAndGlow() {\n const { colors } = useKit();\n return (\n <View style={{ gap: 12 }}>\n <Button\n variant=\"gradient\"\n size=\"lg\"\n block\n pill\n right={<Text style={{ color: readableOn(colors.accent), fontSize: 18 }}>→</Text>}\n >\n Get started\n </Button>\n <View style={{ flexDirection: 'row', gap: 10 }}>\n <Button variant=\"glow\" pill style={{ flex: 1 }}>\n Go live\n </Button>\n <Button variant=\"gradient\" pill style={{ flex: 1 }}>\n Boost\n </Button>\n </View>\n </View>\n );\n}\n"
108
+ "code": "import { Pressable, Text, View } from 'react-native';\nimport { Button } from '@/components/ui/button';\nimport { readableOn, useKit } from '@/lib/kit';\n\n/** Controls stop at iOS's largest standard text size; content text stays uncapped. */\nconst FONT_CAP = 1.35;\n\nexport default function GradientAndGlow() {\n const { colors } = useKit();\n return (\n <View style={{ gap: 12 }}>\n <Button\n variant=\"gradient\"\n size=\"lg\"\n block\n pill\n right={<Text maxFontSizeMultiplier={FONT_CAP} style={{ color: readableOn(colors.accent), fontSize: 18 }}>→</Text>}\n >\n Get started\n </Button>\n <View style={{ flexDirection: 'row', gap: 10 }}>\n <Button variant=\"glow\" pill style={{ flex: 1 }}>\n Go live\n </Button>\n <Button variant=\"gradient\" pill style={{ flex: 1 }}>\n Boost\n </Button>\n </View>\n </View>\n );\n}\n"
109
109
  },
110
110
  {
111
111
  "name": "DynamicColor",
@@ -115,7 +115,7 @@
115
115
  {
116
116
  "name": "Sizes",
117
117
  "title": "Sizes",
118
- "code": "import { Pressable, Text, View } from 'react-native';\nimport { Button } from '@/components/ui/button';\n\nexport default function Sizes() {\n return (\n <View style={{ flexDirection: 'row', alignItems: 'center', gap: 10 }}>\n <Button size=\"sm\">Small</Button>\n <Button size=\"md\">Medium</Button>\n <Button size=\"lg\">Large</Button>\n <Button size=\"icon\" variant=\"outline\">\n <Text style={{ fontSize: 18 }}>♥</Text>\n </Button>\n </View>\n );\n}\n"
118
+ "code": "import { Pressable, Text, View } from 'react-native';\nimport { Button } from '@/components/ui/button';\n\n/** Controls stop at iOS's largest standard text size; content text stays uncapped. */\nconst FONT_CAP = 1.35;\n\nexport default function Sizes() {\n return (\n <View style={{ flexDirection: 'row', flexWrap: 'wrap', alignItems: 'center', gap: 10 }}>\n <Button size=\"sm\">Small</Button>\n <Button size=\"md\">Medium</Button>\n <Button size=\"lg\">Large</Button>\n <Button size=\"icon\" variant=\"outline\">\n <Text maxFontSizeMultiplier={FONT_CAP} style={{ fontSize: 18 }}>♥</Text>\n </Button>\n </View>\n );\n}\n"
119
119
  },
120
120
  {
121
121
  "name": "Loading",