@expokit/cli 0.2.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.
- package/README.md +20 -2
- package/bin/expo-kit.js +219 -44
- package/bin/lib.mjs +110 -1
- package/bin/mcp.mjs +48 -19
- package/package.json +9 -4
- package/registry/_docs/accordion.json +16 -0
- package/registry/_docs/action-sheet.json +28 -0
- package/registry/_docs/alert.json +28 -0
- package/registry/_docs/assistant-app.json +47 -0
- package/registry/_docs/avatar.json +25 -1
- package/registry/_docs/badge.json +36 -5
- package/registry/_docs/bottom-nav.json +30 -6
- package/registry/_docs/bottom-sheet.json +28 -0
- package/registry/_docs/bullet.json +3 -0
- package/registry/_docs/button-3d.json +18 -2
- package/registry/_docs/button.json +16 -0
- package/registry/_docs/calendar.json +21 -5
- package/registry/_docs/card-stack.json +16 -0
- package/registry/_docs/card.json +20 -0
- package/registry/_docs/carousel.json +20 -0
- package/registry/_docs/chat.json +184 -0
- package/registry/_docs/checkbox.json +16 -0
- package/registry/_docs/checkout.json +10 -1
- package/registry/_docs/chip-group.json +16 -0
- package/registry/_docs/confetti.json +23 -3
- package/registry/_docs/coupon.json +16 -0
- package/registry/_docs/cube-pager.json +16 -0
- package/registry/_docs/dashboard.json +3 -0
- package/registry/_docs/date-picker.json +24 -4
- package/registry/_docs/dialog.json +24 -0
- package/registry/_docs/donut-shop.json +88 -0
- package/registry/_docs/drawer.json +32 -4
- package/registry/_docs/fab.json +24 -0
- package/registry/_docs/fintech-app.json +49 -0
- package/registry/_docs/flip-card.json +16 -0
- package/registry/_docs/floating-input.json +25 -5
- package/registry/_docs/food-app.json +47 -0
- package/registry/_docs/gift-card.json +16 -0
- package/registry/_docs/glass-button.json +24 -0
- package/registry/_docs/glass-nav-bar.json +24 -0
- package/registry/_docs/glass-search-bar.json +24 -0
- package/registry/_docs/glass-segmented.json +28 -0
- package/registry/_docs/glass-sheet.json +28 -0
- package/registry/_docs/glass-tab-bar.json +24 -0
- package/registry/_docs/glass.json +17 -1
- package/registry/_docs/grain.json +80 -0
- package/registry/_docs/inbox.json +3 -0
- package/registry/_docs/isometric-bar.json +3 -0
- package/registry/_docs/kit.json +3 -0
- package/registry/_docs/liquidglass-tab.json +3 -0
- package/registry/_docs/list-item.json +16 -0
- package/registry/_docs/loyalty-card.json +16 -0
- package/registry/_docs/morphing-button-group.json +96 -0
- package/registry/_docs/number-ticker.json +16 -0
- package/registry/_docs/offers.json +3 -0
- package/registry/_docs/onboarding.json +3 -0
- package/registry/_docs/orb-reveal.json +132 -0
- package/registry/_docs/otp-input.json +28 -4
- package/registry/_docs/parallax-layers.json +20 -0
- package/registry/_docs/parallax-scroll.json +24 -0
- package/registry/_docs/password-input.json +23 -3
- package/registry/_docs/payment-card.json +16 -0
- package/registry/_docs/pixel-bar.json +3 -0
- package/registry/_docs/polar-area.json +3 -0
- package/registry/_docs/profile.json +3 -0
- package/registry/_docs/progress-steps.json +24 -0
- package/registry/_docs/progress.json +20 -0
- package/registry/_docs/qr-code-button.json +122 -0
- package/registry/_docs/radio-group.json +20 -0
- package/registry/_docs/rating.json +24 -0
- package/registry/_docs/ring-carousel.json +20 -0
- package/registry/_docs/scratch-card.json +20 -0
- package/registry/_docs/search-bar.json +23 -3
- package/registry/_docs/search-results.json +3 -0
- package/registry/_docs/segmented-control.json +20 -0
- package/registry/_docs/select.json +33 -8
- package/registry/_docs/settings.json +9 -0
- package/registry/_docs/sign-in.json +3 -0
- package/registry/_docs/skeleton.json +22 -2
- package/registry/_docs/slide-to-confirm.json +24 -0
- package/registry/_docs/slider.json +27 -7
- package/registry/_docs/spin-wheel.json +24 -0
- package/registry/_docs/spinner.json +41 -11
- package/registry/_docs/stateful-button.json +184 -0
- package/registry/_docs/stepper.json +23 -7
- package/registry/_docs/swipe-deck.json +16 -0
- package/registry/_docs/swipeable-row.json +24 -1
- package/registry/_docs/switch.json +20 -0
- package/registry/_docs/tabs.json +24 -0
- package/registry/_docs/text-animation.json +141 -0
- package/registry/_docs/textarea.json +18 -2
- package/registry/_docs/ticket-card.json +20 -0
- package/registry/_docs/ticket-shape.json +4 -1
- package/registry/_docs/tilt-card.json +20 -4
- package/registry/_docs/time-picker.json +22 -6
- package/registry/_docs/timeline-chart.json +3 -0
- package/registry/_docs/timeline.json +18 -2
- package/registry/_docs/toast.json +32 -8
- package/registry/_docs/treemap.json +3 -0
- package/registry/_docs/waterfall.json +3 -0
- package/registry/_docs/wheel-list.json +20 -0
- package/registry/accordion/files/src/components/ui/accordion.tsx +5 -4
- package/registry/accordion/files/src/lib/kit.tsx +8 -5
- package/registry/action-sheet/files/src/components/ui/action-sheet.tsx +9 -8
- package/registry/action-sheet/files/src/lib/kit.tsx +8 -5
- package/registry/alert/files/src/components/ui/alert.tsx +7 -4
- package/registry/alert/files/src/components/ui/button.tsx +4 -3
- package/registry/alert/files/src/lib/kit.tsx +8 -5
- package/registry/assistant-app.json +49 -0
- package/registry/avatar/files/src/components/ui/avatar.tsx +7 -4
- package/registry/avatar/files/src/lib/kit.tsx +8 -5
- package/registry/badge/files/src/components/ui/badge.tsx +345 -21
- package/registry/badge/files/src/lib/kit.tsx +8 -5
- package/registry/badge.json +1 -1
- package/registry/bottom-nav/files/src/components/ui/bottom-nav.tsx +14 -7
- package/registry/bottom-nav/files/src/lib/kit.tsx +8 -5
- package/registry/bottom-sheet/files/src/components/ui/bottom-sheet.tsx +7 -6
- package/registry/bottom-sheet/files/src/lib/kit.tsx +8 -5
- package/registry/button/files/src/components/ui/button.tsx +4 -3
- package/registry/button/files/src/lib/kit.tsx +8 -5
- package/registry/button-3d/files/src/components/ui/button-3d.tsx +3 -2
- package/registry/button-3d/files/src/lib/kit.tsx +8 -5
- package/registry/calendar/files/src/components/ui/calendar.tsx +9 -5
- package/registry/calendar/files/src/lib/kit.tsx +8 -5
- package/registry/card/files/src/components/ui/card.tsx +8 -6
- package/registry/card/files/src/lib/kit.tsx +8 -5
- package/registry/chat.json +18 -0
- package/registry/checkbox/files/src/components/ui/checkbox.tsx +6 -5
- package/registry/checkbox/files/src/lib/kit.tsx +8 -5
- package/registry/chip-group/files/src/components/ui/chip-group.tsx +4 -2
- package/registry/chip-group/files/src/lib/kit.tsx +8 -5
- package/registry/confetti/files/src/lib/kit.tsx +8 -5
- package/registry/coupon/files/src/components/ui/coupon.tsx +23 -20
- package/registry/coupon/files/src/components/ui/flip-card.tsx +1 -1
- package/registry/coupon/files/src/components/ui/tilt-card.tsx +2 -2
- package/registry/coupon/files/src/lib/kit.tsx +8 -5
- package/registry/date-picker/files/src/components/ui/calendar.tsx +9 -5
- package/registry/date-picker/files/src/components/ui/date-picker.tsx +2 -2
- package/registry/date-picker/files/src/lib/kit.tsx +8 -5
- package/registry/dialog/files/src/components/ui/dialog.tsx +12 -6
- package/registry/dialog/files/src/lib/kit.tsx +8 -5
- package/registry/donut-shop.json +18 -0
- package/registry/drawer/files/src/components/ui/drawer.tsx +25 -19
- package/registry/drawer/files/src/lib/kit.tsx +8 -5
- package/registry/fintech-app.json +57 -0
- package/registry/flip-card/files/src/components/ui/flip-card.tsx +1 -1
- package/registry/flip-card/files/src/lib/kit.tsx +8 -5
- package/registry/floating-input/files/src/components/ui/floating-input.tsx +16 -6
- package/registry/floating-input/files/src/lib/kit.tsx +8 -5
- package/registry/food-app.json +55 -0
- package/registry/grain/files/src/components/ui/grain.tsx +113 -0
- package/registry/{tag-input → grain}/files/src/lib/kit.tsx +8 -5
- package/registry/grain.json +17 -0
- package/registry/kit/files/src/lib/kit.tsx +8 -5
- package/registry/liquidglass-tab/files/src/components/GlassTabBar.tsx +1 -1
- package/registry/liquidglass-tab/files/src/lib/tab-bar.ts +1 -1
- package/registry/list-item/files/src/components/ui/list-item.tsx +20 -16
- package/registry/list-item/files/src/lib/kit.tsx +8 -5
- package/registry/morphing-button-group.json +18 -0
- package/registry/orb-reveal.json +20 -0
- package/registry/otp-input/files/src/components/ui/otp-input.tsx +8 -6
- package/registry/otp-input/files/src/lib/kit.tsx +8 -5
- package/registry/password-input/files/src/components/ui/floating-input.tsx +16 -6
- package/registry/password-input/files/src/components/ui/password-input.tsx +2 -2
- package/registry/password-input/files/src/lib/kit.tsx +8 -5
- package/registry/payment-card/files/src/components/ui/flip-card.tsx +1 -1
- package/registry/payment-card/files/src/components/ui/payment-card.tsx +27 -14
- package/registry/payment-card/files/src/components/ui/tilt-card.tsx +2 -2
- package/registry/payment-card/files/src/lib/kit.tsx +8 -5
- package/registry/progress/files/src/components/ui/progress.tsx +8 -5
- package/registry/progress/files/src/lib/kit.tsx +8 -5
- package/registry/progress-steps/files/src/components/ui/progress-steps.tsx +15 -11
- package/registry/progress-steps/files/src/lib/kit.tsx +8 -5
- package/registry/qr-code-button.json +18 -0
- package/registry/radio-group/files/src/components/ui/radio-group.tsx +10 -9
- package/registry/radio-group/files/src/lib/kit.tsx +8 -5
- package/registry/search-bar/files/src/components/ui/search-bar.tsx +13 -11
- package/registry/search-bar/files/src/lib/kit.tsx +8 -5
- package/registry/segmented-control/files/src/components/ui/segmented-control.tsx +4 -2
- package/registry/segmented-control/files/src/lib/kit.tsx +8 -5
- package/registry/select/files/src/components/ui/select.tsx +3 -3
- package/registry/select/files/src/lib/kit.tsx +8 -5
- package/registry/skeleton/files/src/components/ui/skeleton.tsx +4 -4
- package/registry/skeleton/files/src/lib/kit.tsx +8 -5
- package/registry/slider/files/src/components/ui/slider.tsx +5 -4
- package/registry/slider/files/src/lib/kit.tsx +8 -5
- package/registry/spinner/files/src/components/ui/spinner.tsx +452 -34
- package/registry/spinner/files/src/lib/kit.tsx +8 -5
- package/registry/spinner.json +1 -1
- package/registry/stateful-button/files/src/components/ui/stateful-button.tsx +532 -0
- package/registry/{pagination-dots → stateful-button}/files/src/lib/kit.tsx +8 -5
- package/registry/stateful-button.json +18 -0
- package/registry/stepper/files/src/components/ui/stepper.tsx +8 -4
- package/registry/stepper/files/src/lib/kit.tsx +8 -5
- package/registry/switch/files/src/components/ui/switch.tsx +4 -3
- package/registry/switch/files/src/lib/kit.tsx +8 -5
- package/registry/tabs/files/src/components/ui/tabs.tsx +10 -8
- package/registry/tabs/files/src/lib/kit.tsx +8 -5
- package/registry/text-animation/files/src/components/ui/text-animation.tsx +650 -0
- package/registry/text-animation/files/src/lib/kit.tsx +222 -0
- package/registry/text-animation.json +15 -0
- package/registry/textarea/files/src/components/ui/textarea.tsx +11 -9
- package/registry/textarea/files/src/lib/kit.tsx +8 -5
- package/registry/ticket-shape/files/src/lib/kit.tsx +8 -5
- package/registry/tilt-card/files/src/components/ui/tilt-card.tsx +2 -2
- package/registry/tilt-card/files/src/lib/kit.tsx +8 -5
- package/registry/time-picker/files/src/components/ui/time-picker.tsx +2 -2
- package/registry/time-picker/files/src/lib/kit.tsx +8 -5
- package/registry/timeline/files/src/components/ui/avatar.tsx +7 -4
- package/registry/timeline/files/src/components/ui/timeline.tsx +12 -10
- package/registry/timeline/files/src/lib/kit.tsx +8 -5
- package/registry/toast/files/src/components/ui/button.tsx +4 -3
- package/registry/toast/files/src/components/ui/toast.tsx +13 -11
- package/registry/toast/files/src/lib/kit.tsx +8 -5
- package/schema/components.schema.json +1 -1
- package/registry/_docs/color-swatch.json +0 -66
- package/registry/_docs/kpi-card.json +0 -11
- package/registry/_docs/like-button.json +0 -72
- package/registry/_docs/morph-button.json +0 -108
- package/registry/_docs/pagination-dots.json +0 -88
- package/registry/_docs/shimmer-button.json +0 -78
- package/registry/_docs/tag-input.json +0 -128
- package/registry/color-swatch.json +0 -17
- package/registry/kpi-card/files/src/components/charts/kpi-card.tsx +0 -45
- package/registry/kpi-card.json +0 -14
- package/registry/like-button.json +0 -18
- package/registry/morph-button.json +0 -18
- package/registry/pagination-dots/files/src/components/ui/pagination-dots.tsx +0 -262
- package/registry/pagination-dots.json +0 -15
- package/registry/shimmer-button.json +0 -17
- package/registry/tag-input/files/src/components/ui/tag-input.tsx +0 -720
- package/registry/tag-input.json +0 -17
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
"files": [
|
|
11
11
|
"src/components/ui/bottom-nav.tsx"
|
|
12
12
|
],
|
|
13
|
+
"summary": "A tab bar whose indicator stretches between tabs, leading edge first.",
|
|
13
14
|
"props": [
|
|
14
15
|
[
|
|
15
16
|
"items",
|
|
@@ -84,36 +85,59 @@
|
|
|
84
85
|
"Style of the bar (the outer shadow view for `floating`)."
|
|
85
86
|
]
|
|
86
87
|
],
|
|
88
|
+
"behavior": [
|
|
89
|
+
[
|
|
90
|
+
"Touch",
|
|
91
|
+
"Tap a tab to select it. Items marked action call onAction instead and never become the active tab."
|
|
92
|
+
],
|
|
93
|
+
[
|
|
94
|
+
"Motion",
|
|
95
|
+
"The indicator springs its leading edge first and the trailing edge catches up. Icons morph from outline to filled and badges pop in."
|
|
96
|
+
],
|
|
97
|
+
[
|
|
98
|
+
"Reduced motion",
|
|
99
|
+
"The indicator glides with a short timing and no stretch, and icon changes use quick timings instead of springs."
|
|
100
|
+
],
|
|
101
|
+
[
|
|
102
|
+
"Haptics",
|
|
103
|
+
"A selection tick on tab change and a light impact on the action button. Turn off with haptics={false}."
|
|
104
|
+
],
|
|
105
|
+
[
|
|
106
|
+
"Screen readers",
|
|
107
|
+
"The bar is a tablist and each tab carries selected state, with the badge count in its label. Action items are buttons."
|
|
108
|
+
]
|
|
109
|
+
],
|
|
110
|
+
"usage": "import { useState } from 'react';\nimport { BottomNav } from '@/components/ui/bottom-nav';\n\nconst [tab, setTab] = useState('home');\n<BottomNav\n items={[{ key: 'home', label: 'Home', icon: 'home' }, { key: 'inbox', label: 'Inbox', icon: 'inbox', badge: 3 }]}\n value={tab}\n onValueChange={setTab}\n/>",
|
|
87
111
|
"examples": [
|
|
88
112
|
{
|
|
89
113
|
"name": "MinimalClean",
|
|
90
114
|
"title": "Minimal Clean",
|
|
91
|
-
"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={
|
|
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"
|
|
92
116
|
},
|
|
93
117
|
{
|
|
94
118
|
"name": "Floating",
|
|
95
119
|
"title": "Floating",
|
|
96
|
-
"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={
|
|
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"
|
|
97
121
|
},
|
|
98
122
|
{
|
|
99
123
|
"name": "PillHighlight",
|
|
100
124
|
"title": "Pill Highlight",
|
|
101
|
-
"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={
|
|
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"
|
|
102
126
|
},
|
|
103
127
|
{
|
|
104
128
|
"name": "CenterFab",
|
|
105
129
|
"title": "Center Fab",
|
|
106
|
-
"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={
|
|
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"
|
|
107
131
|
},
|
|
108
132
|
{
|
|
109
133
|
"name": "TabWithIndicator",
|
|
110
134
|
"title": "Tab With Indicator",
|
|
111
|
-
"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={
|
|
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"
|
|
112
136
|
},
|
|
113
137
|
{
|
|
114
138
|
"name": "MusicIconsOnly",
|
|
115
139
|
"title": "Music Icons Only",
|
|
116
|
-
"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={
|
|
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"
|
|
117
141
|
}
|
|
118
142
|
]
|
|
119
143
|
}
|
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
"files": [
|
|
7
7
|
"src/components/ui/bottom-sheet.tsx"
|
|
8
8
|
],
|
|
9
|
+
"summary": "A draggable sheet with snap points whose grabber stretches as you pull it.",
|
|
9
10
|
"props": [
|
|
10
11
|
[
|
|
11
12
|
"open",
|
|
@@ -128,6 +129,33 @@
|
|
|
128
129
|
"Sheet and content styles."
|
|
129
130
|
]
|
|
130
131
|
],
|
|
132
|
+
"behavior": [
|
|
133
|
+
[
|
|
134
|
+
"Touch",
|
|
135
|
+
"Drag the grabber or header between snap points; release velocity picks the detent and dragging past the limits rubber-bands. Tap the scrim or drag down to close."
|
|
136
|
+
],
|
|
137
|
+
[
|
|
138
|
+
"Motion",
|
|
139
|
+
"Glides between detents on a firm spring while the scrim dims with its height. The keyboard lifts the sheet when avoidKeyboard is on."
|
|
140
|
+
],
|
|
141
|
+
[
|
|
142
|
+
"Reduced motion",
|
|
143
|
+
"Snapping uses a short timing instead of a spring."
|
|
144
|
+
],
|
|
145
|
+
[
|
|
146
|
+
"Haptics",
|
|
147
|
+
"A light impact on open and a selection tick on each snap. Turn off with haptics={false}."
|
|
148
|
+
],
|
|
149
|
+
[
|
|
150
|
+
"Screen readers",
|
|
151
|
+
"The sheet is a modal view. The grabber is adjustable with increment and decrement actions to change snap point, and the title is a header."
|
|
152
|
+
],
|
|
153
|
+
[
|
|
154
|
+
"Keyboard",
|
|
155
|
+
"Escape closes it on web when dismissible; the Android back button closes it too."
|
|
156
|
+
]
|
|
157
|
+
],
|
|
158
|
+
"usage": "import { useState } from 'react';\nimport { BottomSheet } from '@/components/ui/bottom-sheet';\n\nconst [open, setOpen] = useState(false);\n<BottomSheet\n open={open}\n onClose={() => setOpen(false)}\n snapPoints={['40%', '85%']}\n title=\"Share album\"\n/>",
|
|
131
159
|
"examples": [
|
|
132
160
|
{
|
|
133
161
|
"name": "Basic",
|
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
"files": [
|
|
7
7
|
"src/components/ui/button-3d.tsx"
|
|
8
8
|
],
|
|
9
|
+
"summary": "A keycap-style button whose lit face sinks into its edge when pressed.",
|
|
9
10
|
"props": [
|
|
10
11
|
[
|
|
11
12
|
"children",
|
|
@@ -92,11 +93,26 @@
|
|
|
92
93
|
"Style for the root (width, margins)."
|
|
93
94
|
]
|
|
94
95
|
],
|
|
96
|
+
"behavior": [
|
|
97
|
+
[
|
|
98
|
+
"Touch",
|
|
99
|
+
"The face sinks into its edge on press-in and springs back on release. Small sizes pad their hit area to 44pt."
|
|
100
|
+
],
|
|
101
|
+
[
|
|
102
|
+
"Haptics",
|
|
103
|
+
"A light impact on press-in on iOS and on press on Android, so scrolling past it doesn't buzz. Turn off with haptics={false}."
|
|
104
|
+
],
|
|
105
|
+
[
|
|
106
|
+
"Screen readers",
|
|
107
|
+
"A button with disabled and busy state. Pass accessibilityLabel for circle keys that show only a glyph."
|
|
108
|
+
]
|
|
109
|
+
],
|
|
110
|
+
"usage": "import { Button3D } from '@/components/ui/button-3d';\n\n<Button3D size=\"lg\" shape=\"pill\" onPress={startTrip}>\n Start the trip\n</Button3D>",
|
|
95
111
|
"examples": [
|
|
96
112
|
{
|
|
97
113
|
"name": "OnPhoto",
|
|
98
114
|
"title": "On Photo",
|
|
99
|
-
"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/**
|
|
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"
|
|
100
116
|
},
|
|
101
117
|
{
|
|
102
118
|
"name": "Keypad",
|
|
@@ -116,7 +132,7 @@
|
|
|
116
132
|
{
|
|
117
133
|
"name": "Materials",
|
|
118
134
|
"title": "Materials",
|
|
119
|
-
"code": "import React, { useState } from 'react';\nimport { Image, Text, View } from 'react-native';\nimport { Button3D } from '@/components/ui/button-3d';\nimport {
|
|
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"
|
|
120
136
|
}
|
|
121
137
|
]
|
|
122
138
|
}
|
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
"files": [
|
|
7
7
|
"src/components/ui/button.tsx"
|
|
8
8
|
],
|
|
9
|
+
"summary": "A button that springs down on press, in eleven variants including gradient and glow.",
|
|
9
10
|
"props": [
|
|
10
11
|
[
|
|
11
12
|
"variant",
|
|
@@ -80,6 +81,21 @@
|
|
|
80
81
|
"onPress, disabled, …"
|
|
81
82
|
]
|
|
82
83
|
],
|
|
84
|
+
"behavior": [
|
|
85
|
+
[
|
|
86
|
+
"Touch",
|
|
87
|
+
"Scales down with a spring on press-in and bounces back on release. loading swaps the label for a spinner and blocks presses."
|
|
88
|
+
],
|
|
89
|
+
[
|
|
90
|
+
"Haptics",
|
|
91
|
+
"Opt-in light impact on press with haptics; off by default."
|
|
92
|
+
],
|
|
93
|
+
[
|
|
94
|
+
"Screen readers",
|
|
95
|
+
"A button with disabled and busy state."
|
|
96
|
+
]
|
|
97
|
+
],
|
|
98
|
+
"usage": "import { Button } from '@/components/ui/button';\n\n<Button variant=\"accent\" size=\"lg\" block onPress={checkout}>\n Continue to payment\n</Button>",
|
|
83
99
|
"examples": [
|
|
84
100
|
{
|
|
85
101
|
"name": "Variants",
|
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
"files": [
|
|
7
7
|
"src/components/ui/calendar.tsx"
|
|
8
8
|
],
|
|
9
|
+
"summary": "A month calendar where months slide in from the side you travel and ranges draw one band.",
|
|
9
10
|
"props": [
|
|
10
11
|
[
|
|
11
12
|
"mode",
|
|
@@ -152,31 +153,46 @@
|
|
|
152
153
|
"Root container style."
|
|
153
154
|
]
|
|
154
155
|
],
|
|
156
|
+
"behavior": [
|
|
157
|
+
[
|
|
158
|
+
"Touch",
|
|
159
|
+
"Tap a day to select it. Swipe sideways or use the chevrons to change month, and tap the title for a month and year grid."
|
|
160
|
+
],
|
|
161
|
+
[
|
|
162
|
+
"Motion",
|
|
163
|
+
"Months slide in with a spring, selected days pop with a bouncy spring and range bands fade in."
|
|
164
|
+
],
|
|
165
|
+
[
|
|
166
|
+
"Screen readers",
|
|
167
|
+
"Days are buttons with selected and disabled state, labelled with the full date plus \"today\" or \"has events\" where they apply."
|
|
168
|
+
]
|
|
169
|
+
],
|
|
170
|
+
"usage": "import { useState } from 'react';\nimport { Calendar } from '@/components/ui/calendar';\n\nconst [date, setDate] = useState(new Date());\n<Calendar value={date} onChange={setDate} minDate={new Date()} />",
|
|
155
171
|
"examples": [
|
|
156
172
|
{
|
|
157
173
|
"name": "Single",
|
|
158
174
|
"title": "Single",
|
|
159
|
-
"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"
|
|
160
176
|
},
|
|
161
177
|
{
|
|
162
178
|
"name": "Range",
|
|
163
179
|
"title": "Range",
|
|
164
|
-
"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"
|
|
165
181
|
},
|
|
166
182
|
{
|
|
167
183
|
"name": "Marked",
|
|
168
184
|
"title": "Marked",
|
|
169
|
-
"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"
|
|
170
186
|
},
|
|
171
187
|
{
|
|
172
188
|
"name": "Booking",
|
|
173
189
|
"title": "Booking",
|
|
174
|
-
"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"
|
|
175
191
|
},
|
|
176
192
|
{
|
|
177
193
|
"name": "WeekStartsMonday",
|
|
178
194
|
"title": "Week Starts Monday",
|
|
179
|
-
"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"
|
|
180
196
|
}
|
|
181
197
|
]
|
|
182
198
|
}
|
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
"files": [
|
|
7
7
|
"src/components/ui/card-stack.tsx"
|
|
8
8
|
],
|
|
9
|
+
"summary": "A Wallet-style stack that fans open on tap and slides the picked card to the front.",
|
|
9
10
|
"props": [
|
|
10
11
|
[
|
|
11
12
|
"data",
|
|
@@ -104,5 +105,20 @@
|
|
|
104
105
|
"Style for the root view."
|
|
105
106
|
]
|
|
106
107
|
],
|
|
108
|
+
"behavior": [
|
|
109
|
+
[
|
|
110
|
+
"Touch",
|
|
111
|
+
"Tap the closed stack to fan it open, then tap a card to bring it to the front as the stack closes. With onPressFront, a tap on the front card calls it instead."
|
|
112
|
+
],
|
|
113
|
+
[
|
|
114
|
+
"Motion",
|
|
115
|
+
"Cards behind the front one shrink and tilt back in 3D depth. Opening, closing and reordering all run on springs."
|
|
116
|
+
],
|
|
117
|
+
[
|
|
118
|
+
"Screen readers",
|
|
119
|
+
"Each card is a button with selected and expanded state, labelled \"Card 2 of 4\" by default or by getAccessibilityLabel."
|
|
120
|
+
]
|
|
121
|
+
],
|
|
122
|
+
"usage": null,
|
|
107
123
|
"examples": []
|
|
108
124
|
}
|
package/registry/_docs/card.json
CHANGED
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
"files": [
|
|
7
7
|
"src/components/ui/card.tsx"
|
|
8
8
|
],
|
|
9
|
+
"summary": "A composable card that springs down on press, with a spotlight that follows your finger.",
|
|
9
10
|
"props": [
|
|
10
11
|
[
|
|
11
12
|
"variant",
|
|
@@ -86,6 +87,25 @@
|
|
|
86
87
|
"Style for the card surface (merged after the variant background, border and radius)."
|
|
87
88
|
]
|
|
88
89
|
],
|
|
90
|
+
"behavior": [
|
|
91
|
+
[
|
|
92
|
+
"Touch",
|
|
93
|
+
"With onPress, onLongPress or pressable it scales down with a spring and bounces back on release."
|
|
94
|
+
],
|
|
95
|
+
[
|
|
96
|
+
"Motion",
|
|
97
|
+
"highlight=\"spotlight\" springs in a soft glow under your finger; highlight=\"border\" fades in a glowing accent ring."
|
|
98
|
+
],
|
|
99
|
+
[
|
|
100
|
+
"Screen readers",
|
|
101
|
+
"Becomes a button with disabled state when it has onPress or onLongPress. Card.Title is a header."
|
|
102
|
+
],
|
|
103
|
+
[
|
|
104
|
+
"Web",
|
|
105
|
+
"Shows a pointer cursor when it has onPress."
|
|
106
|
+
]
|
|
107
|
+
],
|
|
108
|
+
"usage": "import { Card } from '@/components/ui/card';\n\n<Card variant=\"elevated\" highlight=\"spotlight\" onPress={openGuide}>\n <Card.Header>\n <Card.Title>Kyoto in autumn</Card.Title>\n <Card.Description>Temples, maple-lined paths and the best matcha spots.</Card.Description>\n </Card.Header>\n</Card>",
|
|
89
109
|
"examples": [
|
|
90
110
|
{
|
|
91
111
|
"name": "Default",
|