@expokit/cli 0.3.0 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (165) hide show
  1. package/README.md +8 -4
  2. package/bin/expo-kit.js +137 -24
  3. package/bin/lib.mjs +110 -1
  4. package/bin/mcp.mjs +40 -18
  5. package/package.json +2 -2
  6. package/registry/_docs/assistant-app.json +47 -0
  7. package/registry/_docs/avatar.json +1 -1
  8. package/registry/_docs/badge.json +1 -1
  9. package/registry/_docs/bottom-nav.json +6 -6
  10. package/registry/_docs/button-3d.json +2 -2
  11. package/registry/_docs/calendar.json +5 -5
  12. package/registry/_docs/checkout.json +7 -1
  13. package/registry/_docs/confetti.json +3 -3
  14. package/registry/_docs/date-picker.json +4 -4
  15. package/registry/_docs/donut-shop.json +88 -0
  16. package/registry/_docs/drawer.json +4 -4
  17. package/registry/_docs/fintech-app.json +49 -0
  18. package/registry/_docs/floating-input.json +5 -5
  19. package/registry/_docs/food-app.json +47 -0
  20. package/registry/_docs/glass.json +1 -1
  21. package/registry/_docs/grain.json +80 -0
  22. package/registry/_docs/morphing-button-group.json +96 -0
  23. package/registry/_docs/orb-reveal.json +132 -0
  24. package/registry/_docs/otp-input.json +4 -4
  25. package/registry/_docs/password-input.json +3 -3
  26. package/registry/_docs/qr-code-button.json +122 -0
  27. package/registry/_docs/search-bar.json +3 -3
  28. package/registry/_docs/select.json +7 -7
  29. package/registry/_docs/settings.json +6 -0
  30. package/registry/_docs/skeleton.json +2 -2
  31. package/registry/_docs/slider.json +6 -6
  32. package/registry/_docs/spinner.json +1 -1
  33. package/registry/_docs/stepper.json +7 -7
  34. package/registry/_docs/textarea.json +2 -2
  35. package/registry/_docs/ticket-shape.json +1 -1
  36. package/registry/_docs/tilt-card.json +4 -4
  37. package/registry/_docs/time-picker.json +6 -6
  38. package/registry/_docs/timeline.json +2 -2
  39. package/registry/_docs/toast.json +8 -8
  40. package/registry/accordion/files/src/components/ui/accordion.tsx +5 -4
  41. package/registry/accordion/files/src/lib/kit.tsx +8 -5
  42. package/registry/action-sheet/files/src/components/ui/action-sheet.tsx +9 -8
  43. package/registry/action-sheet/files/src/lib/kit.tsx +8 -5
  44. package/registry/alert/files/src/components/ui/alert.tsx +7 -4
  45. package/registry/alert/files/src/components/ui/button.tsx +4 -3
  46. package/registry/alert/files/src/lib/kit.tsx +8 -5
  47. package/registry/assistant-app.json +49 -0
  48. package/registry/avatar/files/src/components/ui/avatar.tsx +7 -4
  49. package/registry/avatar/files/src/lib/kit.tsx +8 -5
  50. package/registry/badge/files/src/components/ui/badge.tsx +21 -7
  51. package/registry/badge/files/src/lib/kit.tsx +8 -5
  52. package/registry/bottom-nav/files/src/components/ui/bottom-nav.tsx +14 -7
  53. package/registry/bottom-nav/files/src/lib/kit.tsx +8 -5
  54. package/registry/bottom-sheet/files/src/components/ui/bottom-sheet.tsx +7 -6
  55. package/registry/bottom-sheet/files/src/lib/kit.tsx +8 -5
  56. package/registry/button/files/src/components/ui/button.tsx +4 -3
  57. package/registry/button/files/src/lib/kit.tsx +8 -5
  58. package/registry/button-3d/files/src/components/ui/button-3d.tsx +3 -2
  59. package/registry/button-3d/files/src/lib/kit.tsx +8 -5
  60. package/registry/calendar/files/src/components/ui/calendar.tsx +9 -5
  61. package/registry/calendar/files/src/lib/kit.tsx +8 -5
  62. package/registry/card/files/src/components/ui/card.tsx +8 -6
  63. package/registry/card/files/src/lib/kit.tsx +8 -5
  64. package/registry/checkbox/files/src/components/ui/checkbox.tsx +6 -5
  65. package/registry/checkbox/files/src/lib/kit.tsx +8 -5
  66. package/registry/chip-group/files/src/components/ui/chip-group.tsx +4 -2
  67. package/registry/chip-group/files/src/lib/kit.tsx +8 -5
  68. package/registry/confetti/files/src/lib/kit.tsx +8 -5
  69. package/registry/coupon/files/src/components/ui/coupon.tsx +23 -20
  70. package/registry/coupon/files/src/components/ui/flip-card.tsx +1 -1
  71. package/registry/coupon/files/src/components/ui/tilt-card.tsx +2 -2
  72. package/registry/coupon/files/src/lib/kit.tsx +8 -5
  73. package/registry/date-picker/files/src/components/ui/calendar.tsx +9 -5
  74. package/registry/date-picker/files/src/components/ui/date-picker.tsx +2 -2
  75. package/registry/date-picker/files/src/lib/kit.tsx +8 -5
  76. package/registry/dialog/files/src/components/ui/dialog.tsx +12 -6
  77. package/registry/dialog/files/src/lib/kit.tsx +8 -5
  78. package/registry/donut-shop.json +18 -0
  79. package/registry/drawer/files/src/components/ui/drawer.tsx +25 -19
  80. package/registry/drawer/files/src/lib/kit.tsx +8 -5
  81. package/registry/fintech-app.json +57 -0
  82. package/registry/flip-card/files/src/components/ui/flip-card.tsx +1 -1
  83. package/registry/flip-card/files/src/lib/kit.tsx +8 -5
  84. package/registry/floating-input/files/src/components/ui/floating-input.tsx +16 -6
  85. package/registry/floating-input/files/src/lib/kit.tsx +8 -5
  86. package/registry/food-app.json +55 -0
  87. package/registry/grain/files/src/components/ui/grain.tsx +113 -0
  88. package/registry/{tag-input → grain}/files/src/lib/kit.tsx +8 -5
  89. package/registry/grain.json +17 -0
  90. package/registry/kit/files/src/lib/kit.tsx +8 -5
  91. package/registry/liquidglass-tab/files/src/components/GlassTabBar.tsx +1 -1
  92. package/registry/liquidglass-tab/files/src/lib/tab-bar.ts +1 -1
  93. package/registry/list-item/files/src/components/ui/list-item.tsx +20 -16
  94. package/registry/list-item/files/src/lib/kit.tsx +8 -5
  95. package/registry/morphing-button-group.json +18 -0
  96. package/registry/orb-reveal.json +20 -0
  97. package/registry/otp-input/files/src/components/ui/otp-input.tsx +8 -6
  98. package/registry/otp-input/files/src/lib/kit.tsx +8 -5
  99. package/registry/password-input/files/src/components/ui/floating-input.tsx +16 -6
  100. package/registry/password-input/files/src/components/ui/password-input.tsx +2 -2
  101. package/registry/password-input/files/src/lib/kit.tsx +8 -5
  102. package/registry/payment-card/files/src/components/ui/flip-card.tsx +1 -1
  103. package/registry/payment-card/files/src/components/ui/payment-card.tsx +27 -14
  104. package/registry/payment-card/files/src/components/ui/tilt-card.tsx +2 -2
  105. package/registry/payment-card/files/src/lib/kit.tsx +8 -5
  106. package/registry/progress/files/src/components/ui/progress.tsx +8 -5
  107. package/registry/progress/files/src/lib/kit.tsx +8 -5
  108. package/registry/progress-steps/files/src/components/ui/progress-steps.tsx +15 -11
  109. package/registry/progress-steps/files/src/lib/kit.tsx +8 -5
  110. package/registry/qr-code-button.json +18 -0
  111. package/registry/radio-group/files/src/components/ui/radio-group.tsx +10 -9
  112. package/registry/radio-group/files/src/lib/kit.tsx +8 -5
  113. package/registry/search-bar/files/src/components/ui/search-bar.tsx +13 -11
  114. package/registry/search-bar/files/src/lib/kit.tsx +8 -5
  115. package/registry/segmented-control/files/src/components/ui/segmented-control.tsx +4 -2
  116. package/registry/segmented-control/files/src/lib/kit.tsx +8 -5
  117. package/registry/select/files/src/components/ui/select.tsx +3 -3
  118. package/registry/select/files/src/lib/kit.tsx +8 -5
  119. package/registry/skeleton/files/src/components/ui/skeleton.tsx +4 -4
  120. package/registry/skeleton/files/src/lib/kit.tsx +8 -5
  121. package/registry/slider/files/src/components/ui/slider.tsx +5 -4
  122. package/registry/slider/files/src/lib/kit.tsx +8 -5
  123. package/registry/spinner/files/src/components/ui/spinner.tsx +17 -12
  124. package/registry/spinner/files/src/lib/kit.tsx +8 -5
  125. package/registry/stateful-button/files/src/lib/kit.tsx +8 -5
  126. package/registry/stepper/files/src/components/ui/stepper.tsx +8 -4
  127. package/registry/stepper/files/src/lib/kit.tsx +8 -5
  128. package/registry/switch/files/src/components/ui/switch.tsx +4 -3
  129. package/registry/switch/files/src/lib/kit.tsx +8 -5
  130. package/registry/tabs/files/src/components/ui/tabs.tsx +10 -8
  131. package/registry/tabs/files/src/lib/kit.tsx +8 -5
  132. package/registry/text-animation/files/src/components/ui/text-animation.tsx +4 -4
  133. package/registry/text-animation/files/src/lib/kit.tsx +8 -5
  134. package/registry/textarea/files/src/components/ui/textarea.tsx +11 -9
  135. package/registry/textarea/files/src/lib/kit.tsx +8 -5
  136. package/registry/ticket-shape/files/src/lib/kit.tsx +8 -5
  137. package/registry/tilt-card/files/src/components/ui/tilt-card.tsx +2 -2
  138. package/registry/tilt-card/files/src/lib/kit.tsx +8 -5
  139. package/registry/time-picker/files/src/components/ui/time-picker.tsx +2 -2
  140. package/registry/time-picker/files/src/lib/kit.tsx +8 -5
  141. package/registry/timeline/files/src/components/ui/avatar.tsx +7 -4
  142. package/registry/timeline/files/src/components/ui/timeline.tsx +12 -10
  143. package/registry/timeline/files/src/lib/kit.tsx +8 -5
  144. package/registry/toast/files/src/components/ui/button.tsx +4 -3
  145. package/registry/toast/files/src/components/ui/toast.tsx +13 -11
  146. package/registry/toast/files/src/lib/kit.tsx +8 -5
  147. package/schema/components.schema.json +1 -1
  148. package/registry/_docs/color-swatch.json +0 -82
  149. package/registry/_docs/kpi-card.json +0 -14
  150. package/registry/_docs/like-button.json +0 -92
  151. package/registry/_docs/morph-button.json +0 -128
  152. package/registry/_docs/pagination-dots.json +0 -108
  153. package/registry/_docs/shimmer-button.json +0 -98
  154. package/registry/_docs/tag-input.json +0 -148
  155. package/registry/color-swatch.json +0 -17
  156. package/registry/kpi-card/files/src/components/charts/kpi-card.tsx +0 -45
  157. package/registry/kpi-card.json +0 -14
  158. package/registry/like-button.json +0 -18
  159. package/registry/morph-button.json +0 -18
  160. package/registry/pagination-dots/files/src/components/ui/pagination-dots.tsx +0 -262
  161. package/registry/pagination-dots/files/src/lib/kit.tsx +0 -219
  162. package/registry/pagination-dots.json +0 -15
  163. package/registry/shimmer-button.json +0 -17
  164. package/registry/tag-input/files/src/components/ui/tag-input.tsx +0 -720
  165. package/registry/tag-input.json +0 -17
@@ -0,0 +1,47 @@
1
+ {
2
+ "name": "assistant-app",
3
+ "category": "Templates",
4
+ "aliases": [],
5
+ "exportName": null,
6
+ "files": [
7
+ "src/app/(tabs)/_layout.tsx",
8
+ "src/app/(tabs)/history.tsx",
9
+ "src/app/(tabs)/index.tsx",
10
+ "src/app/(tabs)/profile.tsx",
11
+ "src/app/_layout.tsx",
12
+ "src/app/onboarding.tsx",
13
+ "src/app/settings.tsx",
14
+ "src/app/sign-in.tsx",
15
+ "src/components/assistant-app/tabs.tsx",
16
+ "src/lib/assistant-app/assistant.ts",
17
+ "src/lib/assistant-app/conversations.tsx",
18
+ "src/lib/assistant-app/data.ts",
19
+ "src/lib/assistant-app/session.tsx"
20
+ ],
21
+ "summary": null,
22
+ "props": [],
23
+ "behavior": [
24
+ [
25
+ "Flow",
26
+ "Onboarding shows once, then sign in (Apple, Google or a 6-digit email code), then the tabs. Signing out returns to sign in. The session is saved on the device (AsyncStorage) and guarded with Expo Router’s Stack.Protected."
27
+ ],
28
+ [
29
+ "Chat",
30
+ "Fully controlled: your message goes in, the reply streams in word by word with a Stop button, and suggestion chips fill the empty state. The tab bar steps aside while the keyboard is up."
31
+ ],
32
+ [
33
+ "History",
34
+ "Every conversation, newest first. Tap to reopen it in Chat, swipe to delete, or compose a new one."
35
+ ],
36
+ [
37
+ "Settings",
38
+ "Pushed from Profile. Appearance (System / Light / Dark) re-themes every kit component instantly."
39
+ ],
40
+ [
41
+ "Your model",
42
+ "lib/assistant-app/assistant.ts streams a canned answer so the app runs with no backend. Replace `reply` with a call to your server, which calls the model API — never ship an API key in the app."
43
+ ]
44
+ ],
45
+ "usage": null,
46
+ "examples": []
47
+ }
@@ -186,7 +186,7 @@
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\nexport default function ProfileHeader() {\n const { colors, radius, dark } = useKit();\n return (\n <View\n style={{\n alignItems: 'center',\n gap: 12,\n paddingVertical: 20,\n paddingHorizontal: 16,\n borderRadius: radius.xl,\n backgroundColor: colors.card,\n borderWidth: 1,\n 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={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', 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",
@@ -2,7 +2,7 @@
2
2
  "name": "badge",
3
3
  "category": "Display",
4
4
  "aliases": [],
5
- "exportName": "BadgeIcon",
5
+ "exportName": "Badge",
6
6
  "files": [
7
7
  "src/components/ui/badge.tsx"
8
8
  ],
@@ -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/** The bottom of a phone screen: a title, a few rows, the bar docked at the bottom. */\nfunction Screen({ title, rows, children }: { title: string; rows: Row[]; children: React.ReactNode }) {\n const { colors, radius, dark } = useKit();\n return (\n <View\n style={{\n height: 300,\n alignSelf: 'stretch',\n borderRadius: radius.scale(28),\n overflow: 'hidden',\n borderWidth: 1,\n borderColor: alpha(colors.foreground, dark ? 0.1 : 0.06),\n backgroundColor: colors.background,\n justifyContent: 'space-between',\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() {\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 title={titleOf(items, tab)} rows={ROWS[tab]}>\n <BottomNav 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\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"
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/** The bottom of a phone screen: a title, a few rows, the bar docked at the bottom. */\nfunction Screen({ title, rows, children }: { title: string; rows: Row[]; children: React.ReactNode }) {\n const { colors, radius, dark } = useKit();\n return (\n <View\n style={{\n height: 300,\n alignSelf: 'stretch',\n borderRadius: radius.scale(28),\n overflow: 'hidden',\n borderWidth: 1,\n borderColor: alpha(colors.foreground, dark ? 0.1 : 0.06),\n backgroundColor: colors.background,\n justifyContent: 'space-between',\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() {\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 title={drafts ? `${drafts} new ${drafts === 1 ? 'draft' : 'drafts'}` : titleOf(items, tab)} rows={ROWS[tab]}>\n <BottomNav 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\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"
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/** The bottom of a phone screen: a title, a few rows, the bar docked at the bottom. */\nfunction Screen({ title, rows, children }: { title: string; rows: Row[]; children: React.ReactNode }) {\n const { colors, radius, dark } = useKit();\n return (\n <View\n style={{\n height: 300,\n alignSelf: 'stretch',\n borderRadius: radius.scale(28),\n overflow: 'hidden',\n borderWidth: 1,\n borderColor: alpha(colors.foreground, dark ? 0.1 : 0.06),\n backgroundColor: colors.background,\n justifyContent: 'space-between',\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() {\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 title={titleOf(items, tab)} rows={ROWS[tab]}>\n <BottomNav 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\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"
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/** The bottom of a phone screen: a title, a few rows, the bar docked at the bottom. */\nfunction Screen({ title, rows, children }: { title: string; rows: Row[]; children: React.ReactNode }) {\n const { colors, radius, dark } = useKit();\n return (\n <View\n style={{\n height: 300,\n alignSelf: 'stretch',\n borderRadius: radius.scale(28),\n overflow: 'hidden',\n borderWidth: 1,\n borderColor: alpha(colors.foreground, dark ? 0.1 : 0.06),\n backgroundColor: colors.background,\n justifyContent: 'space-between',\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() {\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 title={posts ? `Posted ${posts} ${posts === 1 ? 'time' : 'times'}` : titleOf(items, tab)} rows={ROWS[tab]}>\n <BottomNav 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\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"
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/** The bottom of a phone screen: a title, a few rows, the bar docked at the bottom. */\nfunction Screen({ title, rows, children }: { title: string; rows: Row[]; children: React.ReactNode }) {\n const { colors, radius, dark } = useKit();\n return (\n <View\n style={{\n height: 300,\n alignSelf: 'stretch',\n borderRadius: radius.scale(28),\n overflow: 'hidden',\n borderWidth: 1,\n borderColor: alpha(colors.foreground, dark ? 0.1 : 0.06),\n backgroundColor: colors.background,\n justifyContent: 'space-between',\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() {\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 title={titleOf(items, tab)} rows={ROWS[tab]}>\n <BottomNav 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\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"
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/** The bottom of a phone screen: a title, a few rows, the bar docked at the bottom. */\nfunction Screen({ title, rows, children }: { title: string; rows: Row[]; children: React.ReactNode }) {\n const { colors, radius, dark } = useKit();\n return (\n <View\n style={{\n height: 300,\n alignSelf: 'stretch',\n borderRadius: radius.scale(28),\n overflow: 'hidden',\n borderWidth: 1,\n borderColor: alpha(colors.foreground, dark ? 0.1 : 0.06),\n backgroundColor: colors.background,\n justifyContent: 'space-between',\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() {\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 title={titleOf(items, tab)} rows={ROWS[tab]}>\n <BottomNav 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\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"
141
141
  }
142
142
  ]
143
143
  }
@@ -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 { 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 OnPhoto() {\n const { colors } = useKit();\n return (\n <View style={{ gap: 18 }}>\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={{ height: 440, borderRadius: 28, overflow: 'hidden', backgroundColor: '#1a2a22' }}>\n <Image alt=\"\" source={photo(1039, 700, 880)} 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 }}>OREGON · 3 DAYS</Text>\n <Text style={{ color: '#fff', fontSize: 30, fontWeight: '800', letterSpacing: -0.8 }}>Multnomah Falls</Text>\n <Text style={{ color: 'rgba(255,255,255,0.8)', fontSize: 13, marginTop: 2 }}>Waterfalls, old-growth forest and a lodge by the river</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/** 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"
116
116
  },
117
117
  {
118
118
  "name": "Keypad",
@@ -132,7 +132,7 @@
132
132
  {
133
133
  "name": "Materials",
134
134
  "title": "Materials",
135
- "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 Materials() {\n return (\n <View style={{ gap: 18 }}>\n <Header title=\"Materials\" sub=\"Light, dark, frosted glass — or any colour\" />\n <View style={{ flexDirection: 'row', gap: 10, justifyContent: 'center' }}>\n <Button3D appearance=\"light\">Light</Button3D>\n <Button3D appearance=\"dark\">Dark</Button3D>\n <Button3D color=\"#ff5a5f\">Custom</Button3D>\n </View>\n <View style={{ borderRadius: 22, overflow: 'hidden', padding: 18, alignItems: 'center', backgroundColor: '#3a2b55' }}>\n <Image alt=\"\" source={photo(888, 300, 160, true)} style={{ position: 'absolute', top: 0, left: 0, right: 0, bottom: 0 }} />\n <View style={{ flexDirection: 'row', gap: 10 }}>\n <Button3D appearance=\"glass\">Glass</Button3D>\n <Button3D appearance=\"glass\" shape=\"pill\">\n On any photo\n </Button3D>\n </View>\n </View>\n </View>\n );\n}\n"
135
+ "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/** 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 Materials() {\n return (\n <View style={{ gap: 18 }}>\n <Header title=\"Materials\" sub=\"Light, dark, frosted glass — or any colour\" />\n <View style={{ flexDirection: 'row', gap: 10, justifyContent: 'center' }}>\n <Button3D appearance=\"light\">Light</Button3D>\n <Button3D appearance=\"dark\">Dark</Button3D>\n <Button3D color=\"#ff5a5f\">Custom</Button3D>\n </View>\n <View style={{ borderRadius: 22, overflow: 'hidden', padding: 18, alignItems: 'center', backgroundColor: '#3a2b55' }}>\n <GrainGradient palette=\"candy\" seed={1} style={{ position: 'absolute', top: 0, left: 0, right: 0, bottom: 0 }} />\n <View style={{ flexDirection: 'row', gap: 10 }}>\n <Button3D appearance=\"glass\">Glass</Button3D>\n <Button3D appearance=\"glass\" shape=\"pill\">\n On any backdrop\n </Button3D>\n </View>\n </View>\n </View>\n );\n}\n"
136
136
  }
137
137
  ]
138
138
  }
@@ -172,27 +172,27 @@
172
172
  {
173
173
  "name": "Single",
174
174
  "title": "Single",
175
- "code": "import React, { useMemo, useState } from 'react';\nimport { StyleSheet, Text, View, type TextProps, type ViewProps } from 'react-native';\nimport { Calendar, addDays, startOfDay, toDateKey, type DateRange } from '@/components/ui/calendar';\nimport { alpha, useKit } from '@/lib/kit';\n\nconst today = startOfDay(new Date());\n\nconst day = (d: Date | null) => (d ? d.toLocaleDateString(undefined, { weekday: 'short', day: 'numeric', month: 'short' }) : '—');\n\n/** Demo card that follows the kit theme, so the docs radius / accent picker reshapes it too. */\nfunction Card({ style, ...rest }: ViewProps) {\n const { colors, radius } = useKit();\n return <View style={[s.card, { backgroundColor: colors.card, borderColor: colors.border, borderRadius: radius.xl }, style]} {...rest} />;\n}\n\n/** Text in the kit's ink: `tone=\"muted\"` for secondary copy. */\nfunction Ink({ tone, style, ...rest }: TextProps & { tone?: 'muted' }) {\n const { colors } = useKit();\n return <Text style={[{ color: tone === 'muted' ? colors.mutedForeground : colors.foreground }, style]} {...rest} />;\n}\n\nconst s = StyleSheet.create({\n card: { padding: 16, borderWidth: StyleSheet.hairlineWidth, gap: 12 },\n title: { fontSize: 15, fontWeight: '400' },\n summary: { flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center' },\n row: { flexDirection: 'row', alignItems: 'center', gap: 12 },\n col: { flex: 1, gap: 2 },\n muted: { fontSize: 13 },\n strong: { fontSize: 15, fontWeight: '400' },\n pill: { paddingHorizontal: 10, paddingVertical: 6 },\n pillText: { fontSize: 13, fontWeight: '400' },\n legend: { flexDirection: 'row', flexWrap: 'wrap', columnGap: 14, rowGap: 6, justifyContent: 'center' },\n legendItem: { flexDirection: 'row', alignItems: 'center', gap: 6 },\n dot: { width: 8, height: 8, borderRadius: 4 },\n});\n\nexport default function Single() {\n const [date, setDate] = useState<Date>(addDays(today, 3));\n return (\n <Card>\n <Ink style={s.title}>Delivery day</Ink>\n <Calendar value={date} onChange={setDate} minDate={today} />\n <View style={s.summary}>\n <Ink tone=\"muted\" style={s.muted}>Arriving</Ink>\n <Ink style={s.strong}>{day(date)}</Ink>\n </View>\n </Card>\n );\n}\n"
175
+ "code": "import React, { useMemo, useState } from 'react';\nimport { StyleSheet, Text, View, type TextProps, type ViewProps } from 'react-native';\nimport { Calendar, addDays, startOfDay, toDateKey, type DateRange } from '@/components/ui/calendar';\nimport { alpha, useKit } from '@/lib/kit';\n\n/** From the Expo Go viewer: `screen` = this IS the phone screen, so no mock frame; `bottomInset` = home indicator. */\ntype DemoProps = { screen?: boolean; bottomInset?: number };\n\nconst today = startOfDay(new Date());\n\nconst day = (d: Date | null) => (d ? d.toLocaleDateString(undefined, { weekday: 'short', day: 'numeric', month: 'short' }) : '—');\n\n/** Demo card that follows the kit theme, so the docs radius / accent picker reshapes it too. In `screen` mode it is the page itself. */\nfunction Card({ screen, style, ...rest }: ViewProps & { screen?: boolean }) {\n const { colors, radius } = useKit();\n if (screen) return <View style={[s.screen, { backgroundColor: colors.background }, style]} {...rest} />;\n return <View style={[s.card, { backgroundColor: colors.card, borderColor: colors.border, borderRadius: radius.xl }, style]} {...rest} />;\n}\n\n/** Text in the kit's ink: `tone=\"muted\"` for secondary copy. */\nfunction Ink({ tone, style, ...rest }: TextProps & { tone?: 'muted' }) {\n const { colors } = useKit();\n return <Text style={[{ color: tone === 'muted' ? colors.mutedForeground : colors.foreground }, style]} {...rest} />;\n}\n\nconst s = StyleSheet.create({\n card: { padding: 16, borderWidth: StyleSheet.hairlineWidth, gap: 12 },\n screen: { flex: 1, justifyContent: 'center', padding: 16, gap: 12 },\n title: { fontSize: 15, fontWeight: '400' },\n summary: { flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center' },\n row: { flexDirection: 'row', alignItems: 'center', gap: 12 },\n col: { flex: 1, gap: 2 },\n muted: { fontSize: 13 },\n strong: { fontSize: 15, fontWeight: '400' },\n pill: { paddingHorizontal: 10, paddingVertical: 6 },\n pillText: { fontSize: 13, fontWeight: '400' },\n legend: { flexDirection: 'row', flexWrap: 'wrap', columnGap: 14, rowGap: 6, justifyContent: 'center' },\n legendItem: { flexDirection: 'row', alignItems: 'center', gap: 6 },\n dot: { width: 8, height: 8, borderRadius: 4 },\n});\n\nexport default function Single({ screen }: DemoProps) {\n const [date, setDate] = useState<Date>(addDays(today, 3));\n return (\n <Card screen={screen}>\n <Ink style={s.title}>Delivery day</Ink>\n <Calendar value={date} onChange={setDate} minDate={today} />\n <View style={s.summary}>\n <Ink tone=\"muted\" style={s.muted}>Arriving</Ink>\n <Ink style={s.strong}>{day(date)}</Ink>\n </View>\n </Card>\n );\n}\n"
176
176
  },
177
177
  {
178
178
  "name": "Range",
179
179
  "title": "Range",
180
- "code": "import React, { useMemo, useState } from 'react';\nimport { StyleSheet, Text, View, type TextProps, type ViewProps } from 'react-native';\nimport { Calendar, addDays, startOfDay, toDateKey, type DateRange } from '@/components/ui/calendar';\nimport { alpha, useKit } from '@/lib/kit';\n\nconst today = startOfDay(new Date());\n\nconst day = (d: Date | null) => (d ? d.toLocaleDateString(undefined, { weekday: 'short', day: 'numeric', month: 'short' }) : '—');\n\n/** Demo card that follows the kit theme, so the docs radius / accent picker reshapes it too. */\nfunction Card({ style, ...rest }: ViewProps) {\n const { colors, radius } = useKit();\n return <View style={[s.card, { backgroundColor: colors.card, borderColor: colors.border, borderRadius: radius.xl }, style]} {...rest} />;\n}\n\n/** Text in the kit's ink: `tone=\"muted\"` for secondary copy. */\nfunction Ink({ tone, style, ...rest }: TextProps & { tone?: 'muted' }) {\n const { colors } = useKit();\n return <Text style={[{ color: tone === 'muted' ? colors.mutedForeground : colors.foreground }, style]} {...rest} />;\n}\n\nconst s = StyleSheet.create({\n card: { padding: 16, borderWidth: StyleSheet.hairlineWidth, gap: 12 },\n title: { fontSize: 15, fontWeight: '400' },\n summary: { flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center' },\n row: { flexDirection: 'row', alignItems: 'center', gap: 12 },\n col: { flex: 1, gap: 2 },\n muted: { fontSize: 13 },\n strong: { fontSize: 15, fontWeight: '400' },\n pill: { paddingHorizontal: 10, paddingVertical: 6 },\n pillText: { fontSize: 13, fontWeight: '400' },\n legend: { flexDirection: 'row', flexWrap: 'wrap', columnGap: 14, rowGap: 6, justifyContent: 'center' },\n legendItem: { flexDirection: 'row', alignItems: 'center', gap: 6 },\n dot: { width: 8, height: 8, borderRadius: 4 },\n});\n\nexport default function Range() {\n const [stay, setStay] = useState<DateRange>({ start: addDays(today, 4), end: addDays(today, 8) });\n const { colors, radius } = useKit();\n const nights = stay.start && stay.end ? Math.round((stay.end.getTime() - stay.start.getTime()) / 86400000) : 0;\n return (\n <Card>\n <Ink style={s.title}>Your stay · Lisbon</Ink>\n <Calendar mode=\"range\" value={stay} onChange={setStay} minDate={today} />\n <View style={s.row}>\n <View style={s.col}>\n <Ink tone=\"muted\" style={s.muted}>Check-in</Ink>\n <Ink style={s.strong}>{day(stay.start)}</Ink>\n </View>\n <View style={s.col}>\n <Ink tone=\"muted\" style={s.muted}>Check-out</Ink>\n <Ink style={s.strong}>{stay.end ? day(stay.end) : 'Select a date'}</Ink>\n </View>\n <View style={[s.pill, { opacity: nights ? 1 : 0.4, backgroundColor: alpha(colors.accent, 0.14), borderRadius: radius.capsule(30) }]}>\n <Text style={[s.pillText, { color: colors.accent }]}>{nights ? `${nights} night${nights > 1 ? 's' : ''}` : '0 nights'}</Text>\n </View>\n </View>\n </Card>\n );\n}\n"
180
+ "code": "import React, { useMemo, useState } from 'react';\nimport { StyleSheet, Text, View, type TextProps, type ViewProps } from 'react-native';\nimport { Calendar, addDays, startOfDay, toDateKey, type DateRange } from '@/components/ui/calendar';\nimport { alpha, useKit } from '@/lib/kit';\n\n/** From the Expo Go viewer: `screen` = this IS the phone screen, so no mock frame; `bottomInset` = home indicator. */\ntype DemoProps = { screen?: boolean; bottomInset?: number };\n\nconst today = startOfDay(new Date());\n\nconst day = (d: Date | null) => (d ? d.toLocaleDateString(undefined, { weekday: 'short', day: 'numeric', month: 'short' }) : '—');\n\n/** Demo card that follows the kit theme, so the docs radius / accent picker reshapes it too. In `screen` mode it is the page itself. */\nfunction Card({ screen, style, ...rest }: ViewProps & { screen?: boolean }) {\n const { colors, radius } = useKit();\n if (screen) return <View style={[s.screen, { backgroundColor: colors.background }, style]} {...rest} />;\n return <View style={[s.card, { backgroundColor: colors.card, borderColor: colors.border, borderRadius: radius.xl }, style]} {...rest} />;\n}\n\n/** Text in the kit's ink: `tone=\"muted\"` for secondary copy. */\nfunction Ink({ tone, style, ...rest }: TextProps & { tone?: 'muted' }) {\n const { colors } = useKit();\n return <Text style={[{ color: tone === 'muted' ? colors.mutedForeground : colors.foreground }, style]} {...rest} />;\n}\n\nconst s = StyleSheet.create({\n card: { padding: 16, borderWidth: StyleSheet.hairlineWidth, gap: 12 },\n screen: { flex: 1, justifyContent: 'center', padding: 16, gap: 12 },\n title: { fontSize: 15, fontWeight: '400' },\n summary: { flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center' },\n row: { flexDirection: 'row', alignItems: 'center', gap: 12 },\n col: { flex: 1, gap: 2 },\n muted: { fontSize: 13 },\n strong: { fontSize: 15, fontWeight: '400' },\n pill: { paddingHorizontal: 10, paddingVertical: 6 },\n pillText: { fontSize: 13, fontWeight: '400' },\n legend: { flexDirection: 'row', flexWrap: 'wrap', columnGap: 14, rowGap: 6, justifyContent: 'center' },\n legendItem: { flexDirection: 'row', alignItems: 'center', gap: 6 },\n dot: { width: 8, height: 8, borderRadius: 4 },\n});\n\nexport default function Range({ screen }: DemoProps) {\n const [stay, setStay] = useState<DateRange>({ start: addDays(today, 4), end: addDays(today, 8) });\n const { colors, radius } = useKit();\n const nights = stay.start && stay.end ? Math.round((stay.end.getTime() - stay.start.getTime()) / 86400000) : 0;\n return (\n <Card screen={screen}>\n <Ink style={s.title}>Your stay · Lisbon</Ink>\n <Calendar mode=\"range\" value={stay} onChange={setStay} minDate={today} />\n <View style={s.row}>\n <View style={s.col}>\n <Ink tone=\"muted\" style={s.muted}>Check-in</Ink>\n <Ink style={s.strong}>{day(stay.start)}</Ink>\n </View>\n <View style={s.col}>\n <Ink tone=\"muted\" style={s.muted}>Check-out</Ink>\n <Ink style={s.strong}>{stay.end ? day(stay.end) : 'Select a date'}</Ink>\n </View>\n <View style={[s.pill, { opacity: nights ? 1 : 0.4, backgroundColor: alpha(colors.accent, 0.14), borderRadius: radius.capsule(30) }]}>\n <Text style={[s.pillText, { color: colors.accent }]}>{nights ? `${nights} night${nights > 1 ? 's' : ''}` : '0 nights'}</Text>\n </View>\n </View>\n </Card>\n );\n}\n"
181
181
  },
182
182
  {
183
183
  "name": "Marked",
184
184
  "title": "Marked",
185
- "code": "import React, { useMemo, useState } from 'react';\nimport { StyleSheet, Text, View, type TextProps, type ViewProps } from 'react-native';\nimport { Calendar, addDays, startOfDay, toDateKey, type DateRange } from '@/components/ui/calendar';\nimport { alpha, useKit } from '@/lib/kit';\n\nconst today = startOfDay(new Date());\n\nconst day = (d: Date | null) => (d ? d.toLocaleDateString(undefined, { weekday: 'short', day: 'numeric', month: 'short' }) : '—');\n\n/** Demo card that follows the kit theme, so the docs radius / accent picker reshapes it too. */\nfunction Card({ style, ...rest }: ViewProps) {\n const { colors, radius } = useKit();\n return <View style={[s.card, { backgroundColor: colors.card, borderColor: colors.border, borderRadius: radius.xl }, style]} {...rest} />;\n}\n\n/** Text in the kit's ink: `tone=\"muted\"` for secondary copy. */\nfunction Ink({ tone, style, ...rest }: TextProps & { tone?: 'muted' }) {\n const { colors } = useKit();\n return <Text style={[{ color: tone === 'muted' ? colors.mutedForeground : colors.foreground }, style]} {...rest} />;\n}\n\nconst MEETING = '#38bdf8';\n\nconst BIRTHDAY = '#f472b6';\n\nconst TRIP = '#f59e0b';\n\nconst DONE = '#22c55e';\n\nconst DUE = '#ef4444';\n\nconst s = StyleSheet.create({\n card: { padding: 16, borderWidth: StyleSheet.hairlineWidth, gap: 12 },\n title: { fontSize: 15, fontWeight: '400' },\n summary: { flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center' },\n row: { flexDirection: 'row', alignItems: 'center', gap: 12 },\n col: { flex: 1, gap: 2 },\n muted: { fontSize: 13 },\n strong: { fontSize: 15, fontWeight: '400' },\n pill: { paddingHorizontal: 10, paddingVertical: 6 },\n pillText: { fontSize: 13, fontWeight: '400' },\n legend: { flexDirection: 'row', flexWrap: 'wrap', columnGap: 14, rowGap: 6, justifyContent: 'center' },\n legendItem: { flexDirection: 'row', alignItems: 'center', gap: 6 },\n dot: { width: 8, height: 8, borderRadius: 4 },\n});\n\nexport default function Marked() {\n const [date, setDate] = useState<Date>(today);\n const { highlights, marks } = useMemo(() => {\n const y = today.getFullYear();\n const m = today.getMonth();\n const k = (d: number) => toDateKey(new Date(y, m, d));\n return {\n // Whole-day fills: who / what owns the day.\n highlights: { [k(2)]: MEETING, [k(14)]: BIRTHDAY, [k(18)]: TRIP, [k(21)]: MEETING, [k(31)]: BIRTHDAY },\n // Dots underneath: smaller things on that day.\n marks: { [k(3)]: DONE, [k(8)]: DUE, [k(12)]: DONE, [k(17)]: DONE, [k(22)]: [DONE, DUE], [k(26)]: DUE },\n };\n }, []);\n return (\n <Card>\n <Ink style={s.title}>Schedule</Ink>\n <Calendar value={date} onChange={setDate} highlightedDates={highlights} markedDates={marks} weekStartsOn={1} />\n <View style={s.legend}>\n {[\n ['Meeting', MEETING],\n ['Birthday', BIRTHDAY],\n ['Trip', TRIP],\n ['Done', DONE],\n ['Due', DUE],\n ].map(([label, color]) => (\n <View key={label} style={s.legendItem}>\n <View style={[s.dot, { backgroundColor: color }]} />\n <Ink tone=\"muted\" style={s.muted}>{label}</Ink>\n </View>\n ))}\n </View>\n </Card>\n );\n}\n"
185
+ "code": "import React, { useMemo, useState } from 'react';\nimport { StyleSheet, Text, View, type TextProps, type ViewProps } from 'react-native';\nimport { Calendar, addDays, startOfDay, toDateKey, type DateRange } from '@/components/ui/calendar';\nimport { alpha, useKit } from '@/lib/kit';\n\n/** From the Expo Go viewer: `screen` = this IS the phone screen, so no mock frame; `bottomInset` = home indicator. */\ntype DemoProps = { screen?: boolean; bottomInset?: number };\n\nconst today = startOfDay(new Date());\n\nconst day = (d: Date | null) => (d ? d.toLocaleDateString(undefined, { weekday: 'short', day: 'numeric', month: 'short' }) : '—');\n\n/** Demo card that follows the kit theme, so the docs radius / accent picker reshapes it too. In `screen` mode it is the page itself. */\nfunction Card({ screen, style, ...rest }: ViewProps & { screen?: boolean }) {\n const { colors, radius } = useKit();\n if (screen) return <View style={[s.screen, { backgroundColor: colors.background }, style]} {...rest} />;\n return <View style={[s.card, { backgroundColor: colors.card, borderColor: colors.border, borderRadius: radius.xl }, style]} {...rest} />;\n}\n\n/** Text in the kit's ink: `tone=\"muted\"` for secondary copy. */\nfunction Ink({ tone, style, ...rest }: TextProps & { tone?: 'muted' }) {\n const { colors } = useKit();\n return <Text style={[{ color: tone === 'muted' ? colors.mutedForeground : colors.foreground }, style]} {...rest} />;\n}\n\nconst MEETING = '#38bdf8';\n\nconst BIRTHDAY = '#f472b6';\n\nconst TRIP = '#f59e0b';\n\nconst DONE = '#22c55e';\n\nconst DUE = '#ef4444';\n\nconst s = StyleSheet.create({\n card: { padding: 16, borderWidth: StyleSheet.hairlineWidth, gap: 12 },\n screen: { flex: 1, justifyContent: 'center', padding: 16, gap: 12 },\n title: { fontSize: 15, fontWeight: '400' },\n summary: { flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center' },\n row: { flexDirection: 'row', alignItems: 'center', gap: 12 },\n col: { flex: 1, gap: 2 },\n muted: { fontSize: 13 },\n strong: { fontSize: 15, fontWeight: '400' },\n pill: { paddingHorizontal: 10, paddingVertical: 6 },\n pillText: { fontSize: 13, fontWeight: '400' },\n legend: { flexDirection: 'row', flexWrap: 'wrap', columnGap: 14, rowGap: 6, justifyContent: 'center' },\n legendItem: { flexDirection: 'row', alignItems: 'center', gap: 6 },\n dot: { width: 8, height: 8, borderRadius: 4 },\n});\n\nexport default function Marked({ screen }: DemoProps) {\n const [date, setDate] = useState<Date>(today);\n const { highlights, marks } = useMemo(() => {\n const y = today.getFullYear();\n const m = today.getMonth();\n const k = (d: number) => toDateKey(new Date(y, m, d));\n return {\n // Whole-day fills: who / what owns the day.\n highlights: { [k(2)]: MEETING, [k(14)]: BIRTHDAY, [k(18)]: TRIP, [k(21)]: MEETING, [k(31)]: BIRTHDAY },\n // Dots underneath: smaller things on that day.\n marks: { [k(3)]: DONE, [k(8)]: DUE, [k(12)]: DONE, [k(17)]: DONE, [k(22)]: [DONE, DUE], [k(26)]: DUE },\n };\n }, []);\n return (\n <Card screen={screen}>\n <Ink style={s.title}>Schedule</Ink>\n <Calendar value={date} onChange={setDate} highlightedDates={highlights} markedDates={marks} weekStartsOn={1} />\n <View style={s.legend}>\n {[\n ['Meeting', MEETING],\n ['Birthday', BIRTHDAY],\n ['Trip', TRIP],\n ['Done', DONE],\n ['Due', DUE],\n ].map(([label, color]) => (\n <View key={label} style={s.legendItem}>\n <View style={[s.dot, { backgroundColor: color }]} />\n <Ink tone=\"muted\" style={s.muted}>{label}</Ink>\n </View>\n ))}\n </View>\n </Card>\n );\n}\n"
186
186
  },
187
187
  {
188
188
  "name": "Booking",
189
189
  "title": "Booking",
190
- "code": "import React, { useMemo, useState } from 'react';\nimport { StyleSheet, Text, View, type TextProps, type ViewProps } from 'react-native';\nimport { Calendar, addDays, startOfDay, toDateKey, type DateRange } from '@/components/ui/calendar';\nimport { alpha, useKit } from '@/lib/kit';\n\nconst today = startOfDay(new Date());\n\nconst day = (d: Date | null) => (d ? d.toLocaleDateString(undefined, { weekday: 'short', day: 'numeric', month: 'short' }) : '—');\n\n/** Demo card that follows the kit theme, so the docs radius / accent picker reshapes it too. */\nfunction Card({ style, ...rest }: ViewProps) {\n const { colors, radius } = useKit();\n return <View style={[s.card, { backgroundColor: colors.card, borderColor: colors.border, borderRadius: radius.xl }, style]} {...rest} />;\n}\n\n/** Text in the kit's ink: `tone=\"muted\"` for secondary copy. */\nfunction Ink({ tone, style, ...rest }: TextProps & { tone?: 'muted' }) {\n const { colors } = useKit();\n return <Text style={[{ color: tone === 'muted' ? colors.mutedForeground : colors.foreground }, style]} {...rest} />;\n}\n\nconst s = StyleSheet.create({\n card: { padding: 16, borderWidth: StyleSheet.hairlineWidth, gap: 12 },\n title: { fontSize: 15, fontWeight: '400' },\n summary: { flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center' },\n row: { flexDirection: 'row', alignItems: 'center', gap: 12 },\n col: { flex: 1, gap: 2 },\n muted: { fontSize: 13 },\n strong: { fontSize: 15, fontWeight: '400' },\n pill: { paddingHorizontal: 10, paddingVertical: 6 },\n pillText: { fontSize: 13, fontWeight: '400' },\n legend: { flexDirection: 'row', flexWrap: 'wrap', columnGap: 14, rowGap: 6, justifyContent: 'center' },\n legendItem: { flexDirection: 'row', alignItems: 'center', gap: 6 },\n dot: { width: 8, height: 8, borderRadius: 4 },\n});\n\nexport default function Booking() {\n const booked = useMemo(() => new Set([2, 5, 6, 11, 19].map((n) => toDateKey(addDays(today, n)))), []);\n const [date, setDate] = useState<Date | null>(null);\n return (\n <Card>\n <Ink style={s.title}>Book a consultation</Ink>\n <Calendar\n value={date}\n onChange={setDate}\n minDate={today}\n maxDate={addDays(today, 45)}\n isDateDisabled={(d) => d.getDay() === 0 || booked.has(toDateKey(d))}\n showOutsideDays\n fixedWeeks\n />\n <Ink tone=\"muted\" style={s.muted}>\n {date ? `Next: choose a time on ${day(date)}` : 'Faded days are fully booked or closed on Sundays.'}\n </Ink>\n </Card>\n );\n}\n"
190
+ "code": "import React, { useMemo, useState } from 'react';\nimport { StyleSheet, Text, View, type TextProps, type ViewProps } from 'react-native';\nimport { Calendar, addDays, startOfDay, toDateKey, type DateRange } from '@/components/ui/calendar';\nimport { alpha, useKit } from '@/lib/kit';\n\n/** From the Expo Go viewer: `screen` = this IS the phone screen, so no mock frame; `bottomInset` = home indicator. */\ntype DemoProps = { screen?: boolean; bottomInset?: number };\n\nconst today = startOfDay(new Date());\n\nconst day = (d: Date | null) => (d ? d.toLocaleDateString(undefined, { weekday: 'short', day: 'numeric', month: 'short' }) : '—');\n\n/** Demo card that follows the kit theme, so the docs radius / accent picker reshapes it too. In `screen` mode it is the page itself. */\nfunction Card({ screen, style, ...rest }: ViewProps & { screen?: boolean }) {\n const { colors, radius } = useKit();\n if (screen) return <View style={[s.screen, { backgroundColor: colors.background }, style]} {...rest} />;\n return <View style={[s.card, { backgroundColor: colors.card, borderColor: colors.border, borderRadius: radius.xl }, style]} {...rest} />;\n}\n\n/** Text in the kit's ink: `tone=\"muted\"` for secondary copy. */\nfunction Ink({ tone, style, ...rest }: TextProps & { tone?: 'muted' }) {\n const { colors } = useKit();\n return <Text style={[{ color: tone === 'muted' ? colors.mutedForeground : colors.foreground }, style]} {...rest} />;\n}\n\nconst s = StyleSheet.create({\n card: { padding: 16, borderWidth: StyleSheet.hairlineWidth, gap: 12 },\n screen: { flex: 1, justifyContent: 'center', padding: 16, gap: 12 },\n title: { fontSize: 15, fontWeight: '400' },\n summary: { flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center' },\n row: { flexDirection: 'row', alignItems: 'center', gap: 12 },\n col: { flex: 1, gap: 2 },\n muted: { fontSize: 13 },\n strong: { fontSize: 15, fontWeight: '400' },\n pill: { paddingHorizontal: 10, paddingVertical: 6 },\n pillText: { fontSize: 13, fontWeight: '400' },\n legend: { flexDirection: 'row', flexWrap: 'wrap', columnGap: 14, rowGap: 6, justifyContent: 'center' },\n legendItem: { flexDirection: 'row', alignItems: 'center', gap: 6 },\n dot: { width: 8, height: 8, borderRadius: 4 },\n});\n\nexport default function Booking({ screen }: DemoProps) {\n const booked = useMemo(() => new Set([2, 5, 6, 11, 19].map((n) => toDateKey(addDays(today, n)))), []);\n const [date, setDate] = useState<Date | null>(null);\n return (\n <Card screen={screen}>\n <Ink style={s.title}>Book a consultation</Ink>\n <Calendar\n value={date}\n onChange={setDate}\n minDate={today}\n maxDate={addDays(today, 45)}\n isDateDisabled={(d) => d.getDay() === 0 || booked.has(toDateKey(d))}\n showOutsideDays\n fixedWeeks\n />\n <Ink tone=\"muted\" style={s.muted}>\n {date ? `Next: choose a time on ${day(date)}` : 'Faded days are fully booked or closed on Sundays.'}\n </Ink>\n </Card>\n );\n}\n"
191
191
  },
192
192
  {
193
193
  "name": "WeekStartsMonday",
194
194
  "title": "Week Starts Monday",
195
- "code": "import React, { useMemo, useState } from 'react';\nimport { StyleSheet, Text, View, type TextProps, type ViewProps } from 'react-native';\nimport { Calendar, addDays, startOfDay, toDateKey, type DateRange } from '@/components/ui/calendar';\nimport { alpha, useKit } from '@/lib/kit';\n\nconst today = startOfDay(new Date());\n\nconst day = (d: Date | null) => (d ? d.toLocaleDateString(undefined, { weekday: 'short', day: 'numeric', month: 'short' }) : '—');\n\n/** Demo card that follows the kit theme, so the docs radius / accent picker reshapes it too. */\nfunction Card({ style, ...rest }: ViewProps) {\n const { colors, radius } = useKit();\n return <View style={[s.card, { backgroundColor: colors.card, borderColor: colors.border, borderRadius: radius.xl }, style]} {...rest} />;\n}\n\n/** Text in the kit's ink: `tone=\"muted\"` for secondary copy. */\nfunction Ink({ tone, style, ...rest }: TextProps & { tone?: 'muted' }) {\n const { colors } = useKit();\n return <Text style={[{ color: tone === 'muted' ? colors.mutedForeground : colors.foreground }, style]} {...rest} />;\n}\n\nconst s = StyleSheet.create({\n card: { padding: 16, borderWidth: StyleSheet.hairlineWidth, gap: 12 },\n title: { fontSize: 15, fontWeight: '400' },\n summary: { flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center' },\n row: { flexDirection: 'row', alignItems: 'center', gap: 12 },\n col: { flex: 1, gap: 2 },\n muted: { fontSize: 13 },\n strong: { fontSize: 15, fontWeight: '400' },\n pill: { paddingHorizontal: 10, paddingVertical: 6 },\n pillText: { fontSize: 13, fontWeight: '400' },\n legend: { flexDirection: 'row', flexWrap: 'wrap', columnGap: 14, rowGap: 6, justifyContent: 'center' },\n legendItem: { flexDirection: 'row', alignItems: 'center', gap: 6 },\n dot: { width: 8, height: 8, borderRadius: 4 },\n});\n\nexport default function WeekStartsMonday() {\n const [days, setDays] = useState<Date[]>([addDays(today, 1), addDays(today, 3)]);\n return (\n <Card>\n <Ink style={s.title}>Training days · pick up to 3</Ink>\n <Calendar mode=\"multiple\" value={days} onChange={setDays} max={3} weekStartsOn={1} locale=\"en-GB\" showOutsideDays />\n <Ink tone=\"muted\" style={s.muted}>{days.length ? days.map((d) => day(d)).join(' · ') : 'No sessions planned'}</Ink>\n </Card>\n );\n}\n"
195
+ "code": "import React, { useMemo, useState } from 'react';\nimport { StyleSheet, Text, View, type TextProps, type ViewProps } from 'react-native';\nimport { Calendar, addDays, startOfDay, toDateKey, type DateRange } from '@/components/ui/calendar';\nimport { alpha, useKit } from '@/lib/kit';\n\n/** From the Expo Go viewer: `screen` = this IS the phone screen, so no mock frame; `bottomInset` = home indicator. */\ntype DemoProps = { screen?: boolean; bottomInset?: number };\n\nconst today = startOfDay(new Date());\n\nconst day = (d: Date | null) => (d ? d.toLocaleDateString(undefined, { weekday: 'short', day: 'numeric', month: 'short' }) : '—');\n\n/** Demo card that follows the kit theme, so the docs radius / accent picker reshapes it too. In `screen` mode it is the page itself. */\nfunction Card({ screen, style, ...rest }: ViewProps & { screen?: boolean }) {\n const { colors, radius } = useKit();\n if (screen) return <View style={[s.screen, { backgroundColor: colors.background }, style]} {...rest} />;\n return <View style={[s.card, { backgroundColor: colors.card, borderColor: colors.border, borderRadius: radius.xl }, style]} {...rest} />;\n}\n\n/** Text in the kit's ink: `tone=\"muted\"` for secondary copy. */\nfunction Ink({ tone, style, ...rest }: TextProps & { tone?: 'muted' }) {\n const { colors } = useKit();\n return <Text style={[{ color: tone === 'muted' ? colors.mutedForeground : colors.foreground }, style]} {...rest} />;\n}\n\nconst s = StyleSheet.create({\n card: { padding: 16, borderWidth: StyleSheet.hairlineWidth, gap: 12 },\n screen: { flex: 1, justifyContent: 'center', padding: 16, gap: 12 },\n title: { fontSize: 15, fontWeight: '400' },\n summary: { flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center' },\n row: { flexDirection: 'row', alignItems: 'center', gap: 12 },\n col: { flex: 1, gap: 2 },\n muted: { fontSize: 13 },\n strong: { fontSize: 15, fontWeight: '400' },\n pill: { paddingHorizontal: 10, paddingVertical: 6 },\n pillText: { fontSize: 13, fontWeight: '400' },\n legend: { flexDirection: 'row', flexWrap: 'wrap', columnGap: 14, rowGap: 6, justifyContent: 'center' },\n legendItem: { flexDirection: 'row', alignItems: 'center', gap: 6 },\n dot: { width: 8, height: 8, borderRadius: 4 },\n});\n\nexport default function WeekStartsMonday({ screen }: DemoProps) {\n const [days, setDays] = useState<Date[]>([addDays(today, 1), addDays(today, 3)]);\n return (\n <Card screen={screen}>\n <Ink style={s.title}>Training days · pick up to 3</Ink>\n <Calendar mode=\"multiple\" value={days} onChange={setDays} max={3} weekStartsOn={1} locale=\"en-GB\" showOutsideDays />\n <Ink tone=\"muted\" style={s.muted}>{days.length ? days.map((d) => day(d)).join(' · ') : 'No sessions planned'}</Ink>\n </Card>\n );\n}\n"
196
196
  }
197
197
  ]
198
198
  }
@@ -12,7 +12,7 @@
12
12
  "items",
13
13
  "{ id, name, variant?, price, quantity, image? }[]",
14
14
  "3 sample products",
15
- "Initial bag. Quantities and removals are kept in state."
15
+ "Initial bag. Quantities and removals are kept in state. Each item can carry a `thumbnail` node (your own drawing) instead of an `image` URL."
16
16
  ],
17
17
  [
18
18
  "onItemsChange",
@@ -50,6 +50,12 @@
50
50
  "—",
51
51
  "Return the discount fraction (0.1 = 10%) or null. Omit to hide the promo row."
52
52
  ],
53
+ [
54
+ "defaultPromo",
55
+ "string",
56
+ "—",
57
+ "A code to apply on mount (checked with onApplyPromo), e.g. a coupon picked on another screen."
58
+ ],
53
59
  [
54
60
  "taxRate",
55
61
  "number",
@@ -86,17 +86,17 @@
86
86
  {
87
87
  "name": "Celebrate",
88
88
  "title": "Celebrate",
89
- "code": "import React, { useRef, useState } from 'react';\nimport { Pressable, Text, View } from 'react-native';\nimport { Confetti, type ConfettiHandle } from '@/components/ui/confetti';\nimport { useKit } from '@/lib/kit';\n\nexport default function Celebrate() {\n const { colors, radius } = useKit();\n const confetti = useRef<ConfettiHandle>(null);\n return (\n <View style={{ height: 420, borderRadius: radius.xl, backgroundColor: colors.muted, overflow: 'hidden', alignItems: 'center', justifyContent: 'center', gap: 10, padding: 24 }}>\n <Text style={{ fontSize: 44 }}>🎉</Text>\n <Text style={{ color: colors.foreground, fontSize: 24, fontWeight: '800', letterSpacing: -0.5 }}>Order placed!</Text>\n <Text style={{ color: colors.mutedForeground, fontSize: 14, textAlign: 'center' }}>You saved ₹240 with coupon GLOW50. Arriving Thursday.</Text>\n <Pressable\n onPress={() => confetti.current?.burst()}\n accessibilityRole=\"button\"\n style={{ marginTop: 12, paddingHorizontal: 22, paddingVertical: 13, borderRadius: 999, backgroundColor: colors.primary }}\n >\n <Text style={{ color: colors.primaryForeground, fontSize: 15, fontWeight: '700' }}>Celebrate again</Text>\n </Pressable>\n <Confetti ref={confetti} autoplay />\n </View>\n );\n}\n"
89
+ "code": "import React, { useRef, useState } from 'react';\nimport { Pressable, Text, View } from 'react-native';\nimport { Confetti, type ConfettiHandle } from '@/components/ui/confetti';\nimport { useKit } from '@/lib/kit';\n\n/** From the Expo Go viewer: `screen` = this IS the phone screen, so no mock frame; `bottomInset` = home indicator. */\ntype DemoProps = { screen?: boolean; bottomInset?: number };\n\nexport default function Celebrate({ screen }: DemoProps) {\n const { colors, radius } = useKit();\n const confetti = useRef<ConfettiHandle>(null);\n return (\n <View\n style={[\n { overflow: 'hidden', alignItems: 'center', justifyContent: 'center', gap: 10, padding: 24 },\n screen ? { flex: 1, backgroundColor: colors.background } : { height: 420, borderRadius: radius.xl, backgroundColor: colors.muted },\n ]}\n >\n <Text style={{ fontSize: 44 }}>🎉</Text>\n <Text style={{ color: colors.foreground, fontSize: 24, fontWeight: '800', letterSpacing: -0.5 }}>Order placed!</Text>\n <Text style={{ color: colors.mutedForeground, fontSize: 14, textAlign: 'center' }}>You saved ₹240 with coupon GLOW50. Arriving Thursday.</Text>\n <Pressable\n onPress={() => confetti.current?.burst()}\n accessibilityRole=\"button\"\n style={{ marginTop: 12, paddingHorizontal: 22, paddingVertical: 13, borderRadius: 999, backgroundColor: colors.primary }}\n >\n <Text style={{ color: colors.primaryForeground, fontSize: 15, fontWeight: '700' }}>Celebrate again</Text>\n </Pressable>\n <Confetti ref={confetti} autoplay />\n </View>\n );\n}\n"
90
90
  },
91
91
  {
92
92
  "name": "Rain",
93
93
  "title": "Rain",
94
- "code": "import React, { useRef, useState } from 'react';\nimport { Pressable, Text, View } from 'react-native';\nimport { Confetti, type ConfettiHandle } from '@/components/ui/confetti';\nimport { useKit } from '@/lib/kit';\n\nexport default function Rain() {\n const { colors, radius } = useKit();\n const [shower, setShower] = useState(0);\n return (\n <View style={{ height: 360, borderRadius: radius.xl, backgroundColor: colors.muted, overflow: 'hidden', alignItems: 'center', justifyContent: 'center' }}>\n <Pressable onPress={() => setShower((n) => n + 1)} accessibilityRole=\"button\" style={{ paddingHorizontal: 22, paddingVertical: 13, borderRadius: 999, borderWidth: 1.5, borderColor: colors.foreground }}>\n <Text style={{ color: colors.foreground, fontSize: 15, fontWeight: '700' }}>Make it rain</Text>\n </Pressable>\n <Confetti origin=\"top\" count={120} trigger={shower} colors={['#ffd23f', '#ffb000', '#fff3b0', '#ffffff']} />\n </View>\n );\n}\n"
94
+ "code": "import React, { useRef, useState } from 'react';\nimport { Pressable, Text, View } from 'react-native';\nimport { Confetti, type ConfettiHandle } from '@/components/ui/confetti';\nimport { useKit } from '@/lib/kit';\n\n/** From the Expo Go viewer: `screen` = this IS the phone screen, so no mock frame; `bottomInset` = home indicator. */\ntype DemoProps = { screen?: boolean; bottomInset?: number };\n\nexport default function Rain({ screen }: DemoProps) {\n const { colors, radius } = useKit();\n const [shower, setShower] = useState(0);\n return (\n <View\n style={[\n { overflow: 'hidden', alignItems: 'center', justifyContent: 'center' },\n screen ? { flex: 1, backgroundColor: colors.background } : { height: 360, borderRadius: radius.xl, backgroundColor: colors.muted },\n ]}\n >\n <Pressable onPress={() => setShower((n) => n + 1)} accessibilityRole=\"button\" style={{ paddingHorizontal: 22, paddingVertical: 13, borderRadius: 999, borderWidth: 1.5, borderColor: colors.foreground }}>\n <Text style={{ color: colors.foreground, fontSize: 15, fontWeight: '700' }}>Make it rain</Text>\n </Pressable>\n <Confetti origin=\"top\" count={120} trigger={shower} colors={['#ffd23f', '#ffb000', '#fff3b0', '#ffffff']} />\n </View>\n );\n}\n"
95
95
  },
96
96
  {
97
97
  "name": "TapToPop",
98
98
  "title": "Tap To Pop",
99
- "code": "import React, { useRef, useState } from 'react';\nimport { Pressable, Text, View } from 'react-native';\nimport { Confetti, type ConfettiHandle } from '@/components/ui/confetti';\nimport { useKit } from '@/lib/kit';\n\nexport default function TapToPop() {\n const { colors, radius } = useKit();\n const confetti = useRef<ConfettiHandle>(null);\n return (\n <Pressable\n onPress={(e) => confetti.current?.burst({ origin: 'center', x: e.nativeEvent.locationX, y: e.nativeEvent.locationY, count: 50 })}\n accessibilityRole=\"button\"\n accessibilityLabel=\"Tap to pop confetti\"\n style={{ height: 360, borderRadius: radius.xl, backgroundColor: colors.muted, overflow: 'hidden', alignItems: 'center', justifyContent: 'center' }}\n >\n {/* Not touchable, so locationX/Y are always relative to the panel. */}\n <View pointerEvents=\"none\">\n <Text style={{ color: colors.mutedForeground, fontSize: 15, fontWeight: '600' }}>Tap anywhere</Text>\n </View>\n <Confetti ref={confetti} />\n </Pressable>\n );\n}\n"
99
+ "code": "import React, { useRef, useState } from 'react';\nimport { Pressable, Text, View } from 'react-native';\nimport { Confetti, type ConfettiHandle } from '@/components/ui/confetti';\nimport { useKit } from '@/lib/kit';\n\n/** From the Expo Go viewer: `screen` = this IS the phone screen, so no mock frame; `bottomInset` = home indicator. */\ntype DemoProps = { screen?: boolean; bottomInset?: number };\n\nexport default function TapToPop({ screen }: DemoProps) {\n const { colors, radius } = useKit();\n const confetti = useRef<ConfettiHandle>(null);\n return (\n <Pressable\n onPress={(e) => confetti.current?.burst({ origin: 'center', x: e.nativeEvent.locationX, y: e.nativeEvent.locationY, count: 50 })}\n accessibilityRole=\"button\"\n accessibilityLabel=\"Tap to pop confetti\"\n style={[\n { overflow: 'hidden', alignItems: 'center', justifyContent: 'center' },\n screen ? { flex: 1, backgroundColor: colors.background } : { height: 360, borderRadius: radius.xl, backgroundColor: colors.muted },\n ]}\n >\n {/* Not touchable, so locationX/Y are always relative to the panel. */}\n <View pointerEvents=\"none\">\n <Text style={{ color: colors.mutedForeground, fontSize: 15, fontWeight: '600' }}>Tap anywhere</Text>\n </View>\n <Confetti ref={confetti} />\n </Pressable>\n );\n}\n"
100
100
  }
101
101
  ]
102
102
  }