@expokit/cli 0.4.0 → 0.4.2
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 +17 -0
- package/bin/lib.mjs +1 -1
- package/bin/liquidglass.mjs +281 -0
- package/package.json +17 -3
- package/registry/_docs/accordion.json +1 -1
- package/registry/_docs/avatar.json +6 -6
- package/registry/_docs/badge.json +3 -3
- package/registry/_docs/bottom-nav.json +6 -6
- package/registry/_docs/bottom-sheet.json +3 -3
- package/registry/_docs/bullet.json +6 -0
- package/registry/_docs/button-3d.json +2 -2
- package/registry/_docs/button.json +2 -2
- package/registry/_docs/calendar.json +5 -5
- package/registry/_docs/card.json +1 -1
- package/registry/_docs/chip-group.json +1 -1
- package/registry/_docs/confetti.json +3 -3
- package/registry/_docs/date-picker.json +4 -4
- package/registry/_docs/drawer.json +4 -4
- package/registry/_docs/flip-card.json +4 -4
- package/registry/_docs/floating-input.json +1 -1
- package/registry/_docs/grain.json +2 -2
- package/registry/_docs/isometric-bar.json +6 -0
- package/registry/_docs/liquidglass-tab.json +2 -1
- package/registry/_docs/otp-input.json +1 -1
- package/registry/_docs/payment-card.json +3 -3
- package/registry/_docs/pixel-bar.json +6 -0
- package/registry/_docs/polar-area.json +6 -0
- package/registry/_docs/progress-steps.json +4 -4
- package/registry/_docs/progress.json +2 -2
- package/registry/_docs/radio-group.json +1 -1
- package/registry/_docs/search-bar.json +3 -3
- package/registry/_docs/segmented-control.json +1 -1
- package/registry/_docs/select.json +6 -6
- package/registry/_docs/skeleton.json +1 -1
- package/registry/_docs/slider.json +6 -6
- package/registry/_docs/spinner.json +2 -2
- package/registry/_docs/stateful-button.json +1 -1
- package/registry/_docs/stepper.json +3 -3
- package/registry/_docs/tabs.json +2 -2
- package/registry/_docs/text-animation.json +4 -4
- package/registry/_docs/textarea.json +1 -1
- package/registry/_docs/tilt-card.json +3 -3
- package/registry/_docs/time-picker.json +6 -6
- package/registry/_docs/timeline-chart.json +6 -0
- package/registry/_docs/timeline.json +4 -4
- package/registry/_docs/treemap.json +6 -0
- package/registry/_docs/waterfall.json +6 -0
- package/registry/accordion/files/src/components/ui/accordion.tsx +31 -3
- package/registry/action-sheet/files/src/components/ui/action-sheet.tsx +4 -1
- package/registry/alert/files/src/components/ui/alert.tsx +5 -2
- package/registry/alert/files/src/components/ui/button.tsx +32 -4
- package/registry/badge/files/src/components/ui/badge.tsx +2 -2
- package/registry/bottom-nav/files/src/components/ui/bottom-nav.tsx +11 -5
- package/registry/button/files/src/components/ui/button.tsx +32 -4
- package/registry/button-3d/files/src/components/ui/button-3d.tsx +33 -6
- package/registry/calendar/files/src/components/ui/calendar.tsx +62 -17
- package/registry/card/files/src/components/ui/card.tsx +25 -5
- package/registry/checkbox/files/src/components/ui/checkbox.tsx +26 -4
- package/registry/chip-group/files/src/components/ui/chip-group.tsx +35 -5
- package/registry/coupon/files/src/components/ui/coupon.tsx +45 -21
- package/registry/coupon/files/src/components/ui/flip-card.tsx +22 -3
- package/registry/coupon/files/src/components/ui/tilt-card.tsx +57 -16
- package/registry/date-picker/files/src/components/ui/calendar.tsx +62 -17
- package/registry/date-picker/files/src/components/ui/date-picker.tsx +96 -42
- package/registry/dialog/files/src/components/ui/dialog.tsx +9 -5
- package/registry/drawer/files/src/components/ui/drawer.tsx +8 -4
- package/registry/flip-card/files/src/components/ui/flip-card.tsx +22 -3
- package/registry/floating-input/files/src/components/ui/floating-input.tsx +75 -26
- package/registry/liquidglass-tab/files/src/app/(tabs)/_layout.tsx +3 -94
- package/registry/liquidglass-tab/files/src/app/(tabs)/alerts.tsx +2 -1
- package/registry/liquidglass-tab/files/src/app/_layout.tsx +2 -2
- package/registry/liquidglass-tab/files/src/components/GlassTabBar.tsx +2 -1
- package/registry/liquidglass-tab/files/src/components/LiquidGlassTabs.tsx +110 -0
- package/registry/liquidglass-tab/files/src/components/TabScreen.tsx +31 -6
- package/registry/liquidglass-tab/files/src/lib/tab-bar.ts +12 -4
- package/registry/liquidglass-tab/files/src/lib/tab-icons.ts +6 -3
- package/registry/liquidglass-tab/files/src/lib/tabs.ts +4 -2
- package/registry/list-item/files/src/components/ui/list-item.tsx +10 -3
- package/registry/otp-input/files/src/components/ui/otp-input.tsx +50 -19
- package/registry/password-input/files/src/components/ui/floating-input.tsx +75 -26
- package/registry/password-input/files/src/components/ui/password-input.tsx +53 -19
- package/registry/payment-card/files/src/components/ui/flip-card.tsx +22 -3
- package/registry/payment-card/files/src/components/ui/payment-card.tsx +19 -15
- package/registry/payment-card/files/src/components/ui/tilt-card.tsx +57 -16
- package/registry/progress/files/src/components/ui/progress.tsx +9 -2
- package/registry/progress-steps/files/src/components/ui/progress-steps.tsx +11 -4
- package/registry/radio-group/files/src/components/ui/radio-group.tsx +39 -9
- package/registry/search-bar/files/src/components/ui/search-bar.tsx +70 -17
- package/registry/segmented-control/files/src/components/ui/segmented-control.tsx +37 -9
- package/registry/select/files/src/components/ui/select.tsx +158 -83
- package/registry/slider/files/src/components/ui/slider.tsx +53 -25
- package/registry/stateful-button/files/src/components/ui/stateful-button.tsx +12 -4
- package/registry/stepper/files/src/components/ui/stepper.tsx +76 -26
- package/registry/switch/files/src/components/ui/switch.tsx +56 -19
- package/registry/tabs/files/src/components/ui/tabs.tsx +7 -4
- package/registry/text-animation/files/src/components/ui/text-animation.tsx +7 -2
- package/registry/textarea/files/src/components/ui/textarea.tsx +104 -36
- package/registry/tilt-card/files/src/components/ui/tilt-card.tsx +57 -16
- package/registry/time-picker/files/src/components/ui/time-picker.tsx +154 -73
- package/registry/timeline/files/src/components/ui/timeline.tsx +9 -4
- package/registry/toast/files/src/components/ui/button.tsx +32 -4
- package/registry/toast/files/src/components/ui/toast.tsx +15 -6
|
@@ -64,12 +64,12 @@
|
|
|
64
64
|
{
|
|
65
65
|
"name": "Hero",
|
|
66
66
|
"title": "Hero",
|
|
67
|
-
"code": "import { Text, View } from 'react-native';\nimport { GRAIN_PALETTES, GrainGradient } from '@/components/ui/grain';\nimport { useKit } from '@/lib/kit';\n\n/** From the Expo Go viewer: `screen` = this IS the phone screen, so no mock frame; `bottomInset` = home indicator. */\ntype DemoProps = { screen?: boolean; bottomInset?: number };\n\nexport default function Hero({ screen }: DemoProps) {\n const { radius } = useKit();\n return (\n <GrainGradient palette=\"ember\" seed={1} style={screen ? { flex: 1 } : { height: 460, borderRadius: radius.xl }}>\n <View style={{ flex: 1, justifyContent: 'flex-end', padding: 24, gap: 6 }}>\n <Text style={{ color: '#ffffff', fontSize: 34, fontWeight: '700', letterSpacing: -0.8 }}>Golden hour</Text>\n <Text style={{ color: 'rgba(255,255,255,0.8)', fontSize: 15 }}>Warm light, soft grain, no photo needed.</Text>\n </View>\n </GrainGradient>\n );\n}\n"
|
|
67
|
+
"code": "import { Text, View } from 'react-native';\nimport { GRAIN_PALETTES, GrainGradient } from '@/components/ui/grain';\nimport { useKit } from '@/lib/kit';\n\n/** Controls stop at iOS's largest standard text size; content text stays uncapped. */\nconst FONT_CAP = 1.35;\n\n/** From the Expo Go viewer: `screen` = this IS the phone screen, so no mock frame; `bottomInset` = home indicator. */\ntype DemoProps = { screen?: boolean; bottomInset?: number };\n\nexport default function Hero({ screen }: DemoProps) {\n const { radius } = useKit();\n return (\n <GrainGradient palette=\"ember\" seed={1} style={screen ? { flex: 1 } : { height: 460, borderRadius: radius.xl }}>\n <View style={{ flex: 1, justifyContent: 'flex-end', padding: 24, gap: 6 }}>\n <Text maxFontSizeMultiplier={FONT_CAP} style={{ color: '#ffffff', fontSize: 34, fontWeight: '700', letterSpacing: -0.8 }}>Golden hour</Text>\n <Text maxFontSizeMultiplier={FONT_CAP} style={{ color: 'rgba(255,255,255,0.8)', fontSize: 15 }}>Warm light, soft grain, no photo needed.</Text>\n </View>\n </GrainGradient>\n );\n}\n"
|
|
68
68
|
},
|
|
69
69
|
{
|
|
70
70
|
"name": "Palettes",
|
|
71
71
|
"title": "Palettes",
|
|
72
|
-
"code": "import { Text, View } from 'react-native';\nimport { GRAIN_PALETTES, GrainGradient } from '@/components/ui/grain';\nimport { useKit } from '@/lib/kit';\n\n/** From the Expo Go viewer: `screen` = this IS the phone screen, so no mock frame; `bottomInset` = home indicator. */\ntype DemoProps = { screen?: boolean; bottomInset?: number };\n\nexport default function Palettes({ screen }: DemoProps) {\n const { radius } = useKit();\n return (\n <View style={[{ flexDirection: 'row', flexWrap: 'wrap', gap: 12 }, screen && { padding: 16 }]}>\n {GRAIN_PALETTES.map((p, i) => (\n <GrainGradient key={p} palette={p} seed={i} style={{ width: '47%', flexGrow: 1, height: 120, borderRadius: radius.lg }}>\n <Text style={{ margin: 12, color: ['peach', 'mint', 'lavender'].includes(p) ? '#1d1a26' : '#ffffff', fontWeight: '600', textTransform: 'capitalize' }}>{p}</Text>\n </GrainGradient>\n ))}\n </View>\n );\n}\n"
|
|
72
|
+
"code": "import { Text, View } from 'react-native';\nimport { GRAIN_PALETTES, GrainGradient } from '@/components/ui/grain';\nimport { useKit } from '@/lib/kit';\n\n/** Controls stop at iOS's largest standard text size; content text stays uncapped. */\nconst FONT_CAP = 1.35;\n\n/** From the Expo Go viewer: `screen` = this IS the phone screen, so no mock frame; `bottomInset` = home indicator. */\ntype DemoProps = { screen?: boolean; bottomInset?: number };\n\nexport default function Palettes({ screen }: DemoProps) {\n const { radius } = useKit();\n return (\n <View style={[{ flexDirection: 'row', flexWrap: 'wrap', gap: 12 }, screen && { padding: 16 }]}>\n {GRAIN_PALETTES.map((p, i) => (\n <GrainGradient key={p} palette={p} seed={i} style={{ width: '47%', flexGrow: 1, height: 120, borderRadius: radius.lg }}>\n <Text maxFontSizeMultiplier={FONT_CAP} style={{ margin: 12, color: ['peach', 'mint', 'lavender'].includes(p) ? '#1d1a26' : '#ffffff', fontWeight: '600', textTransform: 'capitalize' }}>{p}</Text>\n </GrainGradient>\n ))}\n </View>\n );\n}\n"
|
|
73
73
|
},
|
|
74
74
|
{
|
|
75
75
|
"name": "Custom",
|
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
"src/app/(tabs)/profile.tsx",
|
|
12
12
|
"src/app/_layout.tsx",
|
|
13
13
|
"src/components/GlassTabBar.tsx",
|
|
14
|
+
"src/components/LiquidGlassTabs.tsx",
|
|
14
15
|
"src/components/TabScreen.tsx",
|
|
15
16
|
"src/lib/tab-bar.ts",
|
|
16
17
|
"src/lib/tab-icons.ts",
|
|
@@ -24,7 +25,7 @@
|
|
|
24
25
|
{
|
|
25
26
|
"name": "Preview",
|
|
26
27
|
"title": "Preview",
|
|
27
|
-
"code": "import React, { useEffect, useRef, useState } from 'react';\nimport { Animated, Image, Platform, Pressable, ScrollView, StyleSheet, Text, View, type LayoutChangeEvent, type NativeScrollEvent, type NativeSyntheticEvent } from 'react-native';\nimport { BlurView } from 'expo-blur';\nimport Svg, { Circle, Path } from 'react-native-svg';\nimport { useKit } from '@/lib/kit';\n\ntype TabKey = 'index' | 'explore' | 'alerts' | 'profile';\n\nconst TABS: { key: TabKey; title: string; badge?: string; icon: (c: string) => React.ReactNode }[] = [\n { key: 'index', title: 'Home', icon: (c) => <Path d=\"M3 10.5 12 3l9 7.5V20a1 1 0 0 1-1 1h-5v-6H9v6H4a1 1 0 0 1-1-1z\" stroke={c} strokeWidth={2} strokeLinejoin=\"round\" fill=\"none\" /> },\n {\n key: 'explore',\n title: 'Explore',\n icon: (c) => (\n <>\n <Circle cx={12} cy={12} r={9} stroke={c} strokeWidth={2} fill=\"none\" />\n <Path d=\"m15.5 8.5-2 5-5 2 2-5z\" stroke={c} strokeWidth={2} strokeLinejoin=\"round\" fill=\"none\" />\n </>\n ),\n },\n { key: 'alerts', title: 'Alerts', badge: '3', icon: (c) => <Path d=\"M6 8a6 6 0 1 1 12 0c0 7 3 9 3 9H3s3-2 3-9M10 21h4\" stroke={c} strokeWidth={2} strokeLinecap=\"round\" strokeLinejoin=\"round\" fill=\"none\" /> },\n {\n key: 'profile',\n title: 'Profile',\n icon: (c) => (\n <>\n <Circle cx={12} cy={8} r={4} stroke={c} strokeWidth={2} fill=\"none\" />\n <Path d=\"M4 21c1.5-4 4.5-6 8-6s6.5 2 8 6\" stroke={c} strokeWidth={2} strokeLinecap=\"round\" fill=\"none\" />\n </>\n ),\n },\n];\n\nconst PHOTOS = [\n 'photo-1501785888041-af3ef285b470',\n 'photo-1469474968028-56623f02e42e',\n 'photo-1500530855697-b586d89ba3ee',\n 'photo-1470071459604-3b5ec3a7fe05',\n 'photo-1441974231531-c6227db76b6e',\n].map((id) => `https://images.unsplash.com/${id}?w=700&auto=format&fit=crop`);\n\nconst COPY: Record<TabKey, { title: string; sub: string }> = {\n index: { title: 'For you', sub: 'Places picked for this weekend' },\n explore: { title: 'Explore', sub: 'Trending trails near you' },\n alerts: { title: 'Alerts', sub: '3 new since yesterday' },\n profile: { title: 'Profile', sub: 'Trips, saves and settings' },\n};\n\nconst BAR_H = 64;\n\nfunction GlassBar({\n value,\n onChange,\n dark,\n collapsed,\n onExpand,\n}: {\n value: TabKey;\n onChange: (k: TabKey) => void;\n dark: boolean;\n collapsed: boolean;\n onExpand: () => void;\n}) {\n const [w, setW] = useState(0);\n // 0 = full bar, 1 = folded into a circle (JS driver: it animates the bar's width).\n const fold = useRef(new Animated.Value(0)).current;\n useEffect(() => {\n Animated.spring(fold, { toValue: collapsed ? 1 : 0, useNativeDriver: false, damping: 20, stiffness: 220, mass: 0.9 }).start();\n }, [collapsed, fold]);\n const width = fold.interpolate({ inputRange: [0, 1], outputRange: [w || 1, BAR_H] });\n const rowOpacity = fold.interpolate({ inputRange: [0, 0.45], outputRange: [1, 0], extrapolate: 'clamp' });\n const dotOpacity = fold.interpolate({ inputRange: [0.55, 1], outputRange: [0, 1], extrapolate: 'clamp' });\n const dotScale = fold.interpolate({ inputRange: [0.55, 1], outputRange: [0.6, 1], extrapolate: 'clamp' });\n const active = TABS.find((t) => t.key === value) ?? TABS[0];\n const x = useRef(new Animated.Value(0)).current;\n const stretch = useRef(new Animated.Value(1)).current;\n const index = TABS.findIndex((t) => t.key === value);\n const itemW = w ? (w - 8) / TABS.length : 0;\n const tint = dark ? '#0a84ff' : '#007aff';\n const inactive = dark ? 'rgba(235,235,245,0.6)' : 'rgba(60,60,67,0.6)';\n\n useEffect(() => {\n if (!itemW) return;\n Animated.parallel([\n Animated.spring(x, { toValue: index * itemW, useNativeDriver: true, damping: 18, stiffness: 220, mass: 0.9 }),\n Animated.sequence([\n Animated.timing(stretch, { toValue: 1.22, duration: 110, useNativeDriver: true }),\n Animated.spring(stretch, { toValue: 1, useNativeDriver: true, damping: 10, stiffness: 260 }),\n ]),\n ]).start();\n }, [index, itemW, x, stretch]);\n\n return (\n <View pointerEvents=\"box-none\" style={styles.host} onLayout={(e: LayoutChangeEvent) => setW(Math.round(e.nativeEvent.layout.width))}>\n <Animated.View style={[styles.shadow, dark ? styles.shadowDark : styles.shadowLight, w ? { width } : null]}>\n <View style={[styles.glass, { borderColor: dark ? 'rgba(255,255,255,0.14)' : 'rgba(255,255,255,0.8)' }]}>\n <BlurView intensity={Platform.OS === 'ios' ? 70 : 40} tint={dark ? 'dark' : 'light'} style={[StyleSheet.absoluteFill, styles.round]} />\n <View style={[StyleSheet.absoluteFill, { backgroundColor: dark ? 'rgba(28,28,34,0.5)' : 'rgba(255,255,255,0.55)' }]} />\n <View style={[styles.rim, { backgroundColor: dark ? 'rgba(255,255,255,0.18)' : 'rgba(255,255,255,0.95)' }]} />\n {itemW ? (\n <Animated.View\n style={[\n styles.bubble,\n {\n width: itemW,\n backgroundColor: dark ? 'rgba(255,255,255,0.14)' : 'rgba(0,0,0,0.06)',\n transform: [{ translateX: x }, { scaleX: stretch }, { scaleY: stretch.interpolate({ inputRange: [1, 1.22], outputRange: [1, 0.88] }) }],\n },\n ]}\n />\n ) : null}\n <Animated.View pointerEvents={collapsed ? 'none' : 'auto'} style={[styles.row, w ? { width: w } : null, { opacity: rowOpacity }]}>\n {TABS.map((t) => {\n const on = t.key === value;\n const c = on ? tint : inactive;\n return (\n <Pressable\n key={t.key}\n onPress={() => onChange(t.key)}\n accessibilityRole=\"tab\"\n accessibilityState={{ selected: on }}\n accessibilityLabel={t.title}\n style={styles.item}\n >\n <View>\n <Svg width={24} height={24} viewBox=\"0 0 24 24\">\n {t.icon(c)}\n </Svg>\n {t.badge ? (\n <View style={styles.badge}>\n <Text style={styles.badgeText}>{t.badge}</Text>\n </View>\n ) : null}\n </View>\n <Text style={[styles.label, { color: c }]}>{t.title}</Text>\n </Pressable>\n );\n })}\n </Animated.View>\n <Animated.View pointerEvents={collapsed ? 'auto' : 'none'} style={[styles.dot, { opacity: dotOpacity, transform: [{ scale: dotScale }] }]}>\n <Pressable onPress={onExpand} accessibilityRole=\"button\" accessibilityLabel={`${active.title}. Show tab bar`} style={styles.dotPress}>\n <Svg width={26} height={26} viewBox=\"0 0 24 24\">\n {active.icon(tint)}\n </Svg>\n </Pressable>\n </Animated.View>\n </View>\n </Animated.View>\n </View>\n );\n}\n\nconst styles = StyleSheet.create({\n root: { flex: 1 },\n feed: { paddingHorizontal: 16, paddingTop: 12, paddingBottom: 120, gap: 12 },\n large: { fontSize: 32, lineHeight: 38, letterSpacing: -0.8, fontWeight: '400' },\n sub: { fontSize: 15, marginTop: -6, marginBottom: 6 },\n card: { height: 190, borderRadius: 22, overflow: 'hidden', justifyContent: 'flex-end', padding: 16 },\n cardShade: { position: 'absolute', top: 0, left: 0, right: 0, bottom: 0, backgroundColor: 'rgba(0,0,0,0.18)' },\n cardTitle: { color: '#ffffff', fontSize: 20, fontWeight: '400', letterSpacing: -0.3 },\n host: { position: 'absolute', left: 20, right: 20, bottom: 24 },\n shadow: { height: BAR_H, borderRadius: BAR_H / 2 },\n shadowLight: { boxShadow: '0 10px 28px -8px rgba(0,0,0,0.22)' },\n shadowDark: { boxShadow: '0 10px 28px -8px rgba(0,0,0,0.7)' },\n glass: { flex: 1, borderRadius: 32, overflow: 'hidden', borderWidth: StyleSheet.hairlineWidth * 2 },\n round: { borderRadius: 32 },\n rim: { position: 'absolute', top: 0, left: 24, right: 24, height: 1 },\n bubble: { position: 'absolute', top: 4, bottom: 4, left: 4, borderRadius: 28 },\n row: { flex: 1, flexDirection: 'row', paddingHorizontal: 4 },\n dot: { position: 'absolute', top: 0, left: 0, width: BAR_H, height: BAR_H },\n dotPress: { flex: 1, alignItems: 'center', justifyContent: 'center' },\n item: { flex: 1, alignItems: 'center', justifyContent: 'center', gap: 2 },\n label: { fontSize: 10.5, fontWeight: '400' },\n badge: { position: 'absolute', top: -4, right: -10, minWidth: 17, height: 17, borderRadius: 9, paddingHorizontal: 4, backgroundColor: '#ff3b30', alignItems: 'center', justifyContent: 'center' },\n badgeText: { color: '#ffffff', fontSize: 10.5, fontWeight: '400' },\n});\n\nexport default function Preview() {\n const { dark } = useKit();\n const [tab, setTab] = useState<TabKey>('index');\n const [collapsed, setCollapsed] = useState(false);\n const lastY = useRef(0);\n const onScroll = (e: NativeSyntheticEvent<NativeScrollEvent>) => {\n const y = e.nativeEvent.contentOffset.y;\n const dy = y - lastY.current;\n lastY.current = y;\n if (y < 24) setCollapsed(false);\n else if (dy > 6) setCollapsed(true);\n else if (dy < -6) setCollapsed(false);\n };\n const fg = dark ? '#ffffff' : '#1c1c1e';\n const sub = dark ? 'rgba(235,235,245,0.6)' : 'rgba(60,60,67,0.6)';\n const offset = TABS.findIndex((t) => t.key === tab);\n const copy = COPY[tab];\n\n return (\n <View style={[styles.root, { backgroundColor: dark ? '#000000' : '#f2f2f7' }]}>\n <ScrollView contentContainerStyle={styles.feed} showsVerticalScrollIndicator={false} onScroll={onScroll} scrollEventThrottle={16}>\n <Text style={[styles.large, { color: fg }]}>{copy.title}</Text>\n <Text style={[styles.sub, { color: sub }]}>{copy.sub}</Text>\n {[0, 1, 2, 3, 4].map((i) => {\n const p = PHOTOS[(i + offset) % PHOTOS.length];\n return (\n <View key={`${tab}-${i}`} style={styles.card}>\n <Image source={{ uri: p }} alt=\"\" style={StyleSheet.absoluteFill} resizeMode=\"cover\" />\n <View style={styles.cardShade} />\n <Text style={styles.cardTitle}>{['Lake Braies', 'Dolomites', 'Valley trail', 'Blue ridge', 'Old forest'][(i + offset) % 5]}</Text>\n </View>\n );\n })}\n </ScrollView>\n <GlassBar value={tab} onChange={setTab} dark={dark} collapsed={collapsed} onExpand={() => setCollapsed(false)} />\n </View>\n );\n}\n"
|
|
28
|
+
"code": "import React, { useEffect, useRef, useState } from 'react';\nimport { Animated, Image, Platform, Pressable, ScrollView, StyleSheet, Text, View, type LayoutChangeEvent, type NativeScrollEvent, type NativeSyntheticEvent } from 'react-native';\nimport { BlurView } from 'expo-blur';\nimport Svg, { Circle, Path } from 'react-native-svg';\nimport { useKit } from '@/lib/kit';\n\n/** Control text stops at iOS's largest standard size. */\nconst FONT_CAP = 1.35;\n\ntype TabKey = 'index' | 'explore' | 'alerts' | 'profile';\n\nconst TABS: { key: TabKey; title: string; badge?: string; icon: (c: string) => React.ReactNode }[] = [\n { key: 'index', title: 'Home', icon: (c) => <Path d=\"M3 10.5 12 3l9 7.5V20a1 1 0 0 1-1 1h-5v-6H9v6H4a1 1 0 0 1-1-1z\" stroke={c} strokeWidth={2} strokeLinejoin=\"round\" fill=\"none\" /> },\n {\n key: 'explore',\n title: 'Explore',\n icon: (c) => (\n <>\n <Circle cx={12} cy={12} r={9} stroke={c} strokeWidth={2} fill=\"none\" />\n <Path d=\"m15.5 8.5-2 5-5 2 2-5z\" stroke={c} strokeWidth={2} strokeLinejoin=\"round\" fill=\"none\" />\n </>\n ),\n },\n { key: 'alerts', title: 'Alerts', badge: '3', icon: (c) => <Path d=\"M6 8a6 6 0 1 1 12 0c0 7 3 9 3 9H3s3-2 3-9M10 21h4\" stroke={c} strokeWidth={2} strokeLinecap=\"round\" strokeLinejoin=\"round\" fill=\"none\" /> },\n {\n key: 'profile',\n title: 'Profile',\n icon: (c) => (\n <>\n <Circle cx={12} cy={8} r={4} stroke={c} strokeWidth={2} fill=\"none\" />\n <Path d=\"M4 21c1.5-4 4.5-6 8-6s6.5 2 8 6\" stroke={c} strokeWidth={2} strokeLinecap=\"round\" fill=\"none\" />\n </>\n ),\n },\n];\n\nconst PHOTOS = [\n 'photo-1501785888041-af3ef285b470',\n 'photo-1469474968028-56623f02e42e',\n 'photo-1500530855697-b586d89ba3ee',\n 'photo-1470071459604-3b5ec3a7fe05',\n 'photo-1441974231531-c6227db76b6e',\n].map((id) => `https://images.unsplash.com/${id}?w=700&auto=format&fit=crop`);\n\nconst COPY: Record<TabKey, { title: string; sub: string }> = {\n index: { title: 'For you', sub: 'Places picked for this weekend' },\n explore: { title: 'Explore', sub: 'Trending trails near you' },\n alerts: { title: 'Alerts', sub: '3 new since yesterday' },\n profile: { title: 'Profile', sub: 'Trips, saves and settings' },\n};\n\nconst BAR_H = 64;\n\nfunction GlassBar({\n value,\n onChange,\n dark,\n collapsed,\n onExpand,\n}: {\n value: TabKey;\n onChange: (k: TabKey) => void;\n dark: boolean;\n collapsed: boolean;\n onExpand: () => void;\n}) {\n const [w, setW] = useState(0);\n // 0 = full bar, 1 = folded into a circle (JS driver: it animates the bar's width).\n const fold = useRef(new Animated.Value(0)).current;\n useEffect(() => {\n Animated.spring(fold, { toValue: collapsed ? 1 : 0, useNativeDriver: false, damping: 29, stiffness: 220, mass: 0.9 }).start();\n }, [collapsed, fold]);\n const width = fold.interpolate({ inputRange: [0, 1], outputRange: [w || 1, BAR_H] });\n const rowOpacity = fold.interpolate({ inputRange: [0, 0.45], outputRange: [1, 0], extrapolate: 'clamp' });\n const dotOpacity = fold.interpolate({ inputRange: [0.55, 1], outputRange: [0, 1], extrapolate: 'clamp' });\n const dotScale = fold.interpolate({ inputRange: [0.55, 1], outputRange: [0.6, 1], extrapolate: 'clamp' });\n const active = TABS.find((t) => t.key === value) ?? TABS[0];\n const x = useRef(new Animated.Value(0)).current;\n const stretch = useRef(new Animated.Value(1)).current;\n const index = TABS.findIndex((t) => t.key === value);\n const itemW = w ? (w - 8) / TABS.length : 0;\n const tint = dark ? '#0a84ff' : '#007aff';\n const inactive = dark ? 'rgba(235,235,245,0.6)' : 'rgba(60,60,67,0.6)';\n\n useEffect(() => {\n if (!itemW) return;\n Animated.parallel([\n Animated.spring(x, { toValue: index * itemW, useNativeDriver: true, damping: 29, stiffness: 220, mass: 0.9 }),\n Animated.sequence([\n Animated.timing(stretch, { toValue: 1.22, duration: 110, useNativeDriver: true }),\n Animated.spring(stretch, { toValue: 1, useNativeDriver: true, damping: 33, stiffness: 260 }),\n ]),\n ]).start();\n }, [index, itemW, x, stretch]);\n\n return (\n <View pointerEvents=\"box-none\" style={styles.host} onLayout={(e: LayoutChangeEvent) => setW(Math.round(e.nativeEvent.layout.width))}>\n <Animated.View style={[styles.shadow, dark ? styles.shadowDark : styles.shadowLight, w ? { width } : null]}>\n <View style={[styles.glass, { borderColor: dark ? 'rgba(255,255,255,0.14)' : 'rgba(255,255,255,0.8)' }]}>\n <BlurView intensity={Platform.OS === 'ios' ? 70 : 40} tint={dark ? 'dark' : 'light'} style={[StyleSheet.absoluteFill, styles.round]} />\n <View style={[StyleSheet.absoluteFill, { backgroundColor: dark ? 'rgba(28,28,34,0.5)' : 'rgba(255,255,255,0.55)' }]} />\n <View style={[styles.rim, { backgroundColor: dark ? 'rgba(255,255,255,0.18)' : 'rgba(255,255,255,0.95)' }]} />\n {itemW ? (\n <Animated.View\n style={[\n styles.bubble,\n {\n width: itemW,\n backgroundColor: dark ? 'rgba(255,255,255,0.14)' : 'rgba(0,0,0,0.06)',\n transform: [{ translateX: x }, { scaleX: stretch }, { scaleY: stretch.interpolate({ inputRange: [1, 1.22], outputRange: [1, 0.88] }) }],\n },\n ]}\n />\n ) : null}\n <Animated.View pointerEvents={collapsed ? 'none' : 'auto'} style={[styles.row, w ? { width: w } : null, { opacity: rowOpacity }]}>\n {TABS.map((t) => {\n const on = t.key === value;\n const c = on ? tint : inactive;\n return (\n <Pressable\n key={t.key}\n onPress={() => onChange(t.key)}\n accessibilityRole=\"tab\"\n accessibilityState={{ selected: on }}\n accessibilityLabel={t.title}\n style={styles.item}\n >\n <View>\n <Svg width={24} height={24} viewBox=\"0 0 24 24\">\n {t.icon(c)}\n </Svg>\n {t.badge ? (\n <View style={styles.badge}>\n <Text maxFontSizeMultiplier={FONT_CAP} style={styles.badgeText}>{t.badge}</Text>\n </View>\n ) : null}\n </View>\n <Text maxFontSizeMultiplier={FONT_CAP} style={[styles.label, { color: c }]}>{t.title}</Text>\n </Pressable>\n );\n })}\n </Animated.View>\n <Animated.View pointerEvents={collapsed ? 'auto' : 'none'} style={[styles.dot, { opacity: dotOpacity, transform: [{ scale: dotScale }] }]}>\n <Pressable onPress={onExpand} accessibilityRole=\"button\" accessibilityLabel={`${active.title}. Show tab bar`} style={styles.dotPress}>\n <Svg width={26} height={26} viewBox=\"0 0 24 24\">\n {active.icon(tint)}\n </Svg>\n </Pressable>\n </Animated.View>\n </View>\n </Animated.View>\n </View>\n );\n}\n\nconst styles = StyleSheet.create({\n root: { flex: 1 },\n feed: { paddingHorizontal: 16, paddingTop: 12, paddingBottom: 120, gap: 12 },\n large: { fontSize: 32, lineHeight: 38, letterSpacing: -0.8, fontWeight: '400' },\n sub: { fontSize: 15, marginTop: -6, marginBottom: 6 },\n card: { height: 190, borderRadius: 22, overflow: 'hidden', justifyContent: 'flex-end', padding: 16 },\n cardShade: { position: 'absolute', top: 0, left: 0, right: 0, bottom: 0, backgroundColor: 'rgba(0,0,0,0.18)' },\n cardTitle: { color: '#ffffff', fontSize: 20, fontWeight: '400', letterSpacing: -0.3 },\n host: { position: 'absolute', left: 20, right: 20, bottom: 24 },\n shadow: { height: BAR_H, borderRadius: BAR_H / 2 },\n shadowLight: { boxShadow: '0 10px 28px -8px rgba(0,0,0,0.22)' },\n shadowDark: { boxShadow: '0 10px 28px -8px rgba(0,0,0,0.7)' },\n glass: { flex: 1, borderRadius: 32, overflow: 'hidden', borderWidth: StyleSheet.hairlineWidth * 2 },\n round: { borderRadius: 32 },\n rim: { position: 'absolute', top: 0, left: 24, right: 24, height: 1 },\n bubble: { position: 'absolute', top: 4, bottom: 4, left: 4, borderRadius: 28 },\n row: { flex: 1, flexDirection: 'row', paddingHorizontal: 4 },\n dot: { position: 'absolute', top: 0, left: 0, width: BAR_H, height: BAR_H },\n dotPress: { flex: 1, alignItems: 'center', justifyContent: 'center' },\n item: { flex: 1, alignItems: 'center', justifyContent: 'center', gap: 2 },\n label: { fontSize: 10.5, fontWeight: '400' },\n badge: { position: 'absolute', top: -4, right: -10, minWidth: 17, minHeight: 17, borderRadius: 9, paddingHorizontal: 4, backgroundColor: '#ff3b30', alignItems: 'center', justifyContent: 'center' },\n badgeText: { color: '#ffffff', fontSize: 10.5, fontWeight: '400' },\n});\n\nexport default function Preview() {\n const { dark } = useKit();\n const [tab, setTab] = useState<TabKey>('index');\n const [collapsed, setCollapsed] = useState(false);\n const lastY = useRef(0);\n const onScroll = (e: NativeSyntheticEvent<NativeScrollEvent>) => {\n const y = e.nativeEvent.contentOffset.y;\n const dy = y - lastY.current;\n lastY.current = y;\n if (y < 24) setCollapsed(false);\n else if (dy > 6) setCollapsed(true);\n else if (dy < -6) setCollapsed(false);\n };\n const fg = dark ? '#ffffff' : '#1c1c1e';\n const sub = dark ? 'rgba(235,235,245,0.6)' : 'rgba(60,60,67,0.6)';\n const offset = TABS.findIndex((t) => t.key === tab);\n const copy = COPY[tab];\n\n return (\n <View style={[styles.root, { backgroundColor: dark ? '#000000' : '#f2f2f7' }]}>\n <ScrollView contentContainerStyle={styles.feed} showsVerticalScrollIndicator={false} onScroll={onScroll} scrollEventThrottle={16}>\n <Text style={[styles.large, { color: fg }]}>{copy.title}</Text>\n <Text style={[styles.sub, { color: sub }]}>{copy.sub}</Text>\n {[0, 1, 2, 3, 4].map((i) => {\n const p = PHOTOS[(i + offset) % PHOTOS.length];\n return (\n <View key={`${tab}-${i}`} style={styles.card}>\n <Image source={{ uri: p }} alt=\"\" style={StyleSheet.absoluteFill} resizeMode=\"cover\" />\n <View style={styles.cardShade} />\n <Text maxFontSizeMultiplier={FONT_CAP} style={styles.cardTitle}>{['Lake Braies', 'Dolomites', 'Valley trail', 'Blue ridge', 'Old forest'][(i + offset) % 5]}</Text>\n </View>\n );\n })}\n </ScrollView>\n <GlassBar value={tab} onChange={setTab} dark={dark} collapsed={collapsed} onExpand={() => setCollapsed(false)} />\n </View>\n );\n}\n"
|
|
28
29
|
}
|
|
29
30
|
]
|
|
30
31
|
}
|
|
@@ -132,7 +132,7 @@
|
|
|
132
132
|
{
|
|
133
133
|
"name": "VerifyPhone",
|
|
134
134
|
"title": "Verify Phone",
|
|
135
|
-
"code": "import React, { useEffect, useState } from 'react';\nimport { Pressable, StyleSheet, Text, View, type ViewProps } from 'react-native';\nimport { OtpInput } from '@/components/ui/otp-input';\nimport { useKit } from '@/lib/kit';\n\n/** From the Expo Go viewer: `screen` = this IS the phone screen, so no mock frame; `bottomInset` = home indicator. */\ntype DemoProps = { screen?: boolean; bottomInset?: number };\n\n/** Demo card that follows the kit theme, so the docs radius / accent picker reshapes it too. */\nfunction Card({ style, screen, ...rest }: ViewProps & { screen?: boolean }) {\n const { colors, radius } = useKit();\n return (\n <View\n style={[\n { padding: 16, gap: 12 },\n screen\n ? { flex: 1, backgroundColor: colors.background }\n : { borderRadius: radius.xl, borderWidth: StyleSheet.hairlineWidth, borderColor: colors.border, backgroundColor: colors.card },\n style,\n ]}\n {...rest}\n />\n );\n}\n\nfunction Header({ title, body }: { title: string; body: string }) {\n const { colors } = useKit();\n return (\n <View style={{ gap: 4, alignItems: 'center' }}>\n <Text style={{ fontSize: 16, fontWeight: '400', color: colors.foreground, textAlign: 'center' }}>{title}</Text>\n <Text style={{ fontSize: 13, color: colors.mutedForeground, textAlign: 'center' }}>{body}</Text>\n </View>\n );\n}\n\nexport default function VerifyPhone({ screen }: DemoProps) {\n const { colors } = useKit();\n const [code, setCode] = useState('');\n const [left, setLeft] = useState(24);\n useEffect(() => {\n if (left <= 0) return;\n const id = setTimeout(() => setLeft((s) => s - 1), 1000);\n return () => clearTimeout(id);\n }, [left]);\n const done = code.length === 6;\n return (\n <Card screen={screen}>\n <Header title=\"Verify your number\" body=\"Enter the code sent to +91 98•••• 4210\" />\n <OtpInput\n value={code}\n onChangeText={setCode}\n success={code === '123456'}\n error={done && code !== '123456'}\n successText=\"Number verified\"\n errorText=\"That code didn't match. Try 123456.\"\n helperText=\"Tip: the demo code is 123456\"\n />\n <View style={{ flexDirection: 'row', justifyContent: 'center',
|
|
135
|
+
"code": "import React, { useEffect, useState } from 'react';\nimport { Pressable, StyleSheet, Text, View, type ViewProps } from 'react-native';\nimport { OtpInput } from '@/components/ui/otp-input';\nimport { useKit } from '@/lib/kit';\n\n/** From the Expo Go viewer: `screen` = this IS the phone screen, so no mock frame; `bottomInset` = home indicator. */\ntype DemoProps = { screen?: boolean; bottomInset?: number };\n\n/** Demo card that follows the kit theme, so the docs radius / accent picker reshapes it too. */\nfunction Card({ style, screen, ...rest }: ViewProps & { screen?: boolean }) {\n const { colors, radius } = useKit();\n return (\n <View\n style={[\n { padding: 16, gap: 12 },\n screen\n ? { flex: 1, backgroundColor: colors.background }\n : { borderRadius: radius.xl, borderWidth: StyleSheet.hairlineWidth, borderColor: colors.border, backgroundColor: colors.card },\n style,\n ]}\n {...rest}\n />\n );\n}\n\nfunction Header({ title, body }: { title: string; body: string }) {\n const { colors } = useKit();\n return (\n <View style={{ gap: 4, alignItems: 'center' }}>\n <Text style={{ fontSize: 16, fontWeight: '400', color: colors.foreground, textAlign: 'center' }}>{title}</Text>\n <Text style={{ fontSize: 13, color: colors.mutedForeground, textAlign: 'center' }}>{body}</Text>\n </View>\n );\n}\n\nexport default function VerifyPhone({ screen }: DemoProps) {\n const { colors } = useKit();\n const [code, setCode] = useState('');\n const [left, setLeft] = useState(24);\n useEffect(() => {\n if (left <= 0) return;\n const id = setTimeout(() => setLeft((s) => s - 1), 1000);\n return () => clearTimeout(id);\n }, [left]);\n const done = code.length === 6;\n return (\n <Card screen={screen}>\n <Header title=\"Verify your number\" body=\"Enter the code sent to +91 98•••• 4210\" />\n <OtpInput\n value={code}\n onChangeText={setCode}\n success={code === '123456'}\n error={done && code !== '123456'}\n successText=\"Number verified\"\n errorText=\"That code didn't match. Try 123456.\"\n helperText=\"Tip: the demo code is 123456\"\n />\n <View style={{ flexDirection: 'row', flexWrap: 'wrap', justifyContent: 'center', columnGap: 16, rowGap: 8 }}>\n <Pressable disabled={left > 0} onPress={() => { setLeft(24); setCode(''); }} hitSlop={10} style={{ flexShrink: 1 }}>\n <Text style={{ fontSize: 13, fontWeight: '400', textAlign: 'center', color: left > 0 ? colors.mutedForeground : colors.accent }}>\n {left > 0 ? `Resend in 0:${String(left).padStart(2, '0')}` : 'Resend code'}\n </Text>\n </Pressable>\n <Pressable hitSlop={10} accessibilityRole=\"button\" style={{ flexShrink: 1 }}>\n <Text style={{ fontSize: 13, fontWeight: '400', textAlign: 'center', color: colors.accent }}>Change number</Text>\n </Pressable>\n </View>\n </Card>\n );\n}\n"
|
|
136
136
|
},
|
|
137
137
|
{
|
|
138
138
|
"name": "Pin",
|
|
@@ -148,7 +148,7 @@
|
|
|
148
148
|
{
|
|
149
149
|
"name": "WalletScreen",
|
|
150
150
|
"title": "Wallet Screen",
|
|
151
|
-
"code": "import { Image, Pressable, Text, View } from 'react-native';\nimport { PaymentCard } from '@/components/ui/payment-card';\nimport { alpha, useKit } from '@/lib/kit';\n\n/** Soft, blurred photos make premium card art — any image works (`require()` or a URL). */\nconst art = (id: number) => ({ uri: `https://picsum.photos/id/${id}/240/151?blur=10` });\n\nexport default function WalletScreen() {\n const { colors } = useKit();\n const actions = [\n ['↑', 'Send'],\n ['↓', 'Request'],\n ['+', 'Top up'],\n ['⋯', 'More'],\n ];\n const activity = [\n ['B', 'Blue Tokai Coffee', 'Today · 09:12', '−₹240'],\n ['U', 'Uber', 'Yesterday · 21:40', '−₹386'],\n ['S', 'Salary · Northwind Labs', 'Mon · 10:00', '+₹82,000'],\n ];\n return (\n <View style={{ gap: 20 }}>\n <View style={{ flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center' }}>\n <View>\n <Text style={{ color: colors.mutedForeground, fontSize: 13 }}>Total balance</Text>\n <Text style={{ color: colors.foreground, fontSize: 30, fontWeight: '700', letterSpacing: -0.8, fontVariant: ['tabular-nums'] }}>₹1,24,580.20</Text>\n <Text style={{ color: colors.success, fontSize: 12, fontWeight: '500' }}>+₹2,140 this week</Text>\n </View>\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 </View>\n\n <PaymentCard\n image={art(189)}\n bank=\"Northwind\"\n label=\"World Elite\"\n number=\"5412 7590 1234 0317\"\n name=\"Maya Patel\"\n expiry=\"09/29\"\n cvv=\"482\"\n brand=\"mastercard\"\n />\n\n <View style={{ flexDirection: 'row', justifyContent: 'space-between' }}>\n {actions.map(([glyph, label]) => (\n <View key={label} style={{ alignItems: 'center', gap: 6, width: 64 }}>\n <View style={{ width: 52, height: 52, borderRadius: 26, backgroundColor: colors.muted, alignItems: 'center', justifyContent: 'center' }}>\n <Text style={{ color: colors.foreground, fontSize: 20, fontWeight: '500' }}>{glyph}</Text>\n </View>\n <Text style={{ color: colors.mutedForeground, fontSize: 12 }}>{label}</Text>\n </View>\n ))}\n </View>\n\n <View>\n <Text style={{ color: colors.foreground, fontSize: 16, fontWeight: '600', marginBottom: 4 }}>Recent activity</Text>\n {activity.map(([initial, what, when, amount]) => (\n <View key={what} style={{ flexDirection: 'row', alignItems: 'center', gap: 12, paddingVertical: 10 }}>\n <View style={{ width: 38, height: 38, borderRadius: 12, backgroundColor: colors.muted, alignItems: 'center', justifyContent: 'center' }}>\n <Text style={{ color: colors.foreground, fontSize: 14, fontWeight: '700' }}>{initial}</Text>\n </View>\n <View style={{ flex: 1 }}>\n <Text style={{ color: colors.foreground, fontSize: 14, fontWeight: '500' }}>{what}</Text>\n <Text style={{ color: colors.mutedForeground, fontSize: 12 }}>{when}</Text>\n </View>\n <Text style={{ color: amount.startsWith('+') ? colors.success : colors.foreground, fontSize: 14, fontWeight: '600', fontVariant: ['tabular-nums'] }}>{amount}</Text>\n </View>\n ))}\n </View>\n </View>\n );\n}\n"
|
|
151
|
+
"code": "import { Image, Pressable, Text, View } from 'react-native';\nimport { PaymentCard } from '@/components/ui/payment-card';\nimport { alpha, useKit } from '@/lib/kit';\n\n/** Demo control text (buttons, chips, labels on fixed shapes) stops at iOS's largest standard text size. */\nconst FONT_CAP = 1.35;\n\n/** Soft, blurred photos make premium card art — any image works (`require()` or a URL). */\nconst art = (id: number) => ({ uri: `https://picsum.photos/id/${id}/240/151?blur=10` });\n\nexport default function WalletScreen() {\n const { colors } = useKit();\n const actions = [\n ['↑', 'Send'],\n ['↓', 'Request'],\n ['+', 'Top up'],\n ['⋯', 'More'],\n ];\n const activity = [\n ['B', 'Blue Tokai Coffee', 'Today · 09:12', '−₹240'],\n ['U', 'Uber', 'Yesterday · 21:40', '−₹386'],\n ['S', 'Salary · Northwind Labs', 'Mon · 10:00', '+₹82,000'],\n ];\n return (\n <View style={{ gap: 20 }}>\n <View style={{ flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center' }}>\n <View style={{ flexShrink: 1 }}>\n <Text style={{ color: colors.mutedForeground, fontSize: 13 }}>Total balance</Text>\n <Text style={{ color: colors.foreground, fontSize: 30, fontWeight: '700', letterSpacing: -0.8, fontVariant: ['tabular-nums'] }}>₹1,24,580.20</Text>\n <Text style={{ color: colors.success, fontSize: 12, fontWeight: '500' }}>+₹2,140 this week</Text>\n </View>\n <View style={{ width: 40, height: 40, borderRadius: 20, backgroundColor: colors.muted, alignItems: 'center', justifyContent: 'center' }}>\n <Text maxFontSizeMultiplier={FONT_CAP} style={{ color: colors.foreground, fontSize: 14, fontWeight: '700' }}>MP</Text>\n </View>\n </View>\n\n <PaymentCard\n image={art(189)}\n bank=\"Northwind\"\n label=\"World Elite\"\n number=\"5412 7590 1234 0317\"\n name=\"Maya Patel\"\n expiry=\"09/29\"\n cvv=\"482\"\n brand=\"mastercard\"\n />\n\n <View style={{ flexDirection: 'row', justifyContent: 'space-between' }}>\n {actions.map(([glyph, label]) => (\n <View key={label} style={{ alignItems: 'center', gap: 6, width: 64 }}>\n <View style={{ width: 52, height: 52, borderRadius: 26, backgroundColor: colors.muted, alignItems: 'center', justifyContent: 'center' }}>\n <Text maxFontSizeMultiplier={FONT_CAP} style={{ color: colors.foreground, fontSize: 20, fontWeight: '500' }}>{glyph}</Text>\n </View>\n <Text maxFontSizeMultiplier={FONT_CAP} style={{ color: colors.mutedForeground, fontSize: 12 }}>{label}</Text>\n </View>\n ))}\n </View>\n\n <View>\n <Text style={{ color: colors.foreground, fontSize: 16, fontWeight: '600', marginBottom: 4 }}>Recent activity</Text>\n {activity.map(([initial, what, when, amount]) => (\n <View key={what} style={{ flexDirection: 'row', alignItems: 'center', gap: 12, paddingVertical: 10 }}>\n <View style={{ width: 38, height: 38, borderRadius: 12, backgroundColor: colors.muted, alignItems: 'center', justifyContent: 'center' }}>\n <Text maxFontSizeMultiplier={FONT_CAP} style={{ color: colors.foreground, fontSize: 14, fontWeight: '700' }}>{initial}</Text>\n </View>\n <View style={{ flex: 1 }}>\n <Text style={{ color: colors.foreground, fontSize: 14, fontWeight: '500' }}>{what}</Text>\n <Text style={{ color: colors.mutedForeground, fontSize: 12 }}>{when}</Text>\n </View>\n <Text style={{ color: amount.startsWith('+') ? colors.success : colors.foreground, fontSize: 14, fontWeight: '600', fontVariant: ['tabular-nums'] }}>{amount}</Text>\n </View>\n ))}\n </View>\n </View>\n );\n}\n"
|
|
152
152
|
},
|
|
153
153
|
{
|
|
154
154
|
"name": "CardControls",
|
|
@@ -158,12 +158,12 @@
|
|
|
158
158
|
{
|
|
159
159
|
"name": "ChooseDesign",
|
|
160
160
|
"title": "Choose Design",
|
|
161
|
-
"code": "import React, { useState } from 'react';\nimport { Image, Pressable, Text, View } from 'react-native';\nimport { PaymentCard } from '@/components/ui/payment-card';\nimport { alpha, useKit } from '@/lib/kit';\n\n/** Soft, blurred photos make premium card art — any image works (`require()` or a URL). */\nconst art = (id: number) => ({ uri: `https://picsum.photos/id/${id}/240/151?blur=10` });\n\n/** A screen title, as it sits above the card in your app. */\nfunction Header({ title, sub, right }: { title: string; sub?: string; right?: React.ReactNode }) {\n const { colors } = useKit();\n return (\n <View style={{ flexDirection: 'row', alignItems: 'center', gap: 12 }}>\n <View style={{ flex: 1 }}>\n <Text style={{ color: colors.foreground, fontSize: 22, fontWeight: '700', letterSpacing: -0.4 }}>{title}</Text>\n {sub ? <Text style={{ color: colors.mutedForeground, fontSize: 13, marginTop: 2 }}>{sub}</Text> : null}\n </View>\n {right}\n </View>\n );\n}\n\nconst DESIGNS = [\n { key: 'meadow', image: art(189) },\n { key: 'lagoon', image: art(846) },\n { key: 'violet', image: art(888) },\n { key: 'dusk', image: art(721) },\n { key: 'night', image: art(765) },\n];\n\nexport default function ChooseDesign() {\n const { colors } = useKit();\n const [pick, setPick] = useState(0);\n return (\n <View style={{ gap: 16 }}>\n <Header title=\"Pick your card\" sub=\"Your design ships in 3–5 days\" />\n <PaymentCard\n image={DESIGNS[pick].image}\n bank=\"Northwind\"\n label=\"Debit\"\n number=\"6521 4400 9911 4410\"\n name=\"Maya Patel\"\n expiry=\"01/30\"\n brand=\"rupay\"\n />\n <View style={{ flexDirection: 'row', justifyContent: 'space-between' }}>\n {DESIGNS.map((d, i) => {\n const on = i === pick;\n return (\n <Pressable\n key={d.key}\n onPress={() => setPick(i)}\n accessibilityRole=\"radio\"\n accessibilityLabel={d.key}\n accessibilityState={{ selected: on }}\n style={{ padding: 2, borderRadius: 10, borderWidth: 2, borderColor: on ? colors.foreground : 'transparent' }}\n >\n <Image alt=\"\" source={d.image} style={{ width: 52, height: 33, borderRadius: 6, backgroundColor: colors.muted }} />\n </Pressable>\n );\n })}\n </View>\n <Pressable accessibilityRole=\"button\" style={{ alignItems: 'center', paddingVertical: 14, borderRadius: 14, backgroundColor: colors.primary, marginTop: 8 }}>\n <Text style={{ color: colors.primaryForeground, fontSize: 15, fontWeight: '600' }}>Order this card</Text>\n </Pressable>\n </View>\n );\n}\n"
|
|
161
|
+
"code": "import React, { useState } from 'react';\nimport { Image, Pressable, Text, View } from 'react-native';\nimport { PaymentCard } from '@/components/ui/payment-card';\nimport { alpha, useKit } from '@/lib/kit';\n\n/** Demo control text (buttons, chips, labels on fixed shapes) stops at iOS's largest standard text size. */\nconst FONT_CAP = 1.35;\n\n/** Soft, blurred photos make premium card art — any image works (`require()` or a URL). */\nconst art = (id: number) => ({ uri: `https://picsum.photos/id/${id}/240/151?blur=10` });\n\n/** A screen title, as it sits above the card in your app. */\nfunction Header({ title, sub, right }: { title: string; sub?: string; right?: React.ReactNode }) {\n const { colors } = useKit();\n return (\n <View style={{ flexDirection: 'row', alignItems: 'center', gap: 12 }}>\n <View style={{ flex: 1 }}>\n <Text style={{ color: colors.foreground, fontSize: 22, fontWeight: '700', letterSpacing: -0.4 }}>{title}</Text>\n {sub ? <Text style={{ color: colors.mutedForeground, fontSize: 13, marginTop: 2 }}>{sub}</Text> : null}\n </View>\n {right}\n </View>\n );\n}\n\nconst DESIGNS = [\n { key: 'meadow', image: art(189) },\n { key: 'lagoon', image: art(846) },\n { key: 'violet', image: art(888) },\n { key: 'dusk', image: art(721) },\n { key: 'night', image: art(765) },\n];\n\nexport default function ChooseDesign() {\n const { colors } = useKit();\n const [pick, setPick] = useState(0);\n return (\n <View style={{ gap: 16 }}>\n <Header title=\"Pick your card\" sub=\"Your design ships in 3–5 days\" />\n <PaymentCard\n image={DESIGNS[pick].image}\n bank=\"Northwind\"\n label=\"Debit\"\n number=\"6521 4400 9911 4410\"\n name=\"Maya Patel\"\n expiry=\"01/30\"\n brand=\"rupay\"\n />\n <View style={{ flexDirection: 'row', justifyContent: 'space-between' }}>\n {DESIGNS.map((d, i) => {\n const on = i === pick;\n return (\n <Pressable\n key={d.key}\n onPress={() => setPick(i)}\n accessibilityRole=\"radio\"\n accessibilityLabel={d.key}\n accessibilityState={{ selected: on }}\n style={{ padding: 2, borderRadius: 10, borderWidth: 2, borderColor: on ? colors.foreground : 'transparent' }}\n >\n <Image alt=\"\" source={d.image} style={{ width: 52, height: 33, borderRadius: 6, backgroundColor: colors.muted }} />\n </Pressable>\n );\n })}\n </View>\n <Pressable accessibilityRole=\"button\" style={{ alignItems: 'center', paddingVertical: 14, borderRadius: 14, backgroundColor: colors.primary, marginTop: 8 }}>\n <Text maxFontSizeMultiplier={FONT_CAP} style={{ color: colors.primaryForeground, fontSize: 15, fontWeight: '600' }}>Order this card</Text>\n </Pressable>\n </View>\n );\n}\n"
|
|
162
162
|
},
|
|
163
163
|
{
|
|
164
164
|
"name": "AddCard",
|
|
165
165
|
"title": "Add Card",
|
|
166
|
-
"code": "import React, { useState } from 'react';\nimport { Image, Pressable, Text, View } from 'react-native';\nimport { PaymentCard } from '@/components/ui/payment-card';\nimport { alpha, useKit } from '@/lib/kit';\n\n/** A screen title, as it sits above the card in your app. */\nfunction Header({ title, sub, right }: { title: string; sub?: string; right?: React.ReactNode }) {\n const { colors } = useKit();\n return (\n <View style={{ flexDirection: 'row', alignItems: 'center', gap: 12 }}>\n <View style={{ flex: 1 }}>\n <Text style={{ color: colors.foreground, fontSize: 22, fontWeight: '700', letterSpacing: -0.4 }}>{title}</Text>\n {sub ? <Text style={{ color: colors.mutedForeground, fontSize: 13, marginTop: 2 }}>{sub}</Text> : null}\n </View>\n {right}\n </View>\n );\n}\n\nexport default function AddCard() {\n const { colors } = useKit();\n const [digits, setDigits] = useState('4821');\n const keys = ['1', '2', '3', '4', '5', '6', '7', '8', '9', '0'];\n return (\n <View style={{ gap: 16 }}>\n <Header title=\"Add a card\" sub=\"Type the number on the front\" />\n <PaymentCard bank=\"New card\" number={digits.padEnd(16, '•')} name=\"Your name\" expiry=\"MM/YY\" brand=\"visa\" chip=\"silver\" masked={false} />\n <View style={{ flexDirection: 'row', flexWrap: 'wrap', gap: 8, justifyContent: 'center' }}>\n {keys.map((k) => (\n <Pressable\n key={k}\n onPress={() => setDigits((d) => (d.length >= 16 ? '4821' : d + k))}\n accessibilityRole=\"button\"\n style={{ width: 58,
|
|
166
|
+
"code": "import React, { useState } from 'react';\nimport { Image, Pressable, Text, View } from 'react-native';\nimport { PaymentCard } from '@/components/ui/payment-card';\nimport { alpha, useKit } from '@/lib/kit';\n\n/** Demo control text (buttons, chips, labels on fixed shapes) stops at iOS's largest standard text size. */\nconst FONT_CAP = 1.35;\n\n/** A screen title, as it sits above the card in your app. */\nfunction Header({ title, sub, right }: { title: string; sub?: string; right?: React.ReactNode }) {\n const { colors } = useKit();\n return (\n <View style={{ flexDirection: 'row', alignItems: 'center', gap: 12 }}>\n <View style={{ flex: 1 }}>\n <Text style={{ color: colors.foreground, fontSize: 22, fontWeight: '700', letterSpacing: -0.4 }}>{title}</Text>\n {sub ? <Text style={{ color: colors.mutedForeground, fontSize: 13, marginTop: 2 }}>{sub}</Text> : null}\n </View>\n {right}\n </View>\n );\n}\n\nexport default function AddCard() {\n const { colors } = useKit();\n const [digits, setDigits] = useState('4821');\n const keys = ['1', '2', '3', '4', '5', '6', '7', '8', '9', '0'];\n return (\n <View style={{ gap: 16 }}>\n <Header title=\"Add a card\" sub=\"Type the number on the front\" />\n <PaymentCard bank=\"New card\" number={digits.padEnd(16, '•')} name=\"Your name\" expiry=\"MM/YY\" brand=\"visa\" chip=\"silver\" masked={false} />\n <View style={{ flexDirection: 'row', flexWrap: 'wrap', gap: 8, justifyContent: 'center' }}>\n {keys.map((k) => (\n <Pressable\n key={k}\n onPress={() => setDigits((d) => (d.length >= 16 ? '4821' : d + k))}\n accessibilityRole=\"button\"\n style={{ width: 58, minHeight: 46, borderRadius: 12, alignItems: 'center', justifyContent: 'center', backgroundColor: colors.muted }}\n >\n <Text maxFontSizeMultiplier={FONT_CAP} style={{ color: colors.foreground, fontSize: 19, fontWeight: '500' }}>{k}</Text>\n </Pressable>\n ))}\n </View>\n </View>\n );\n}\n"
|
|
167
167
|
}
|
|
168
168
|
]
|
|
169
169
|
}
|
|
@@ -90,22 +90,22 @@
|
|
|
90
90
|
{
|
|
91
91
|
"name": "Checkout",
|
|
92
92
|
"title": "Checkout",
|
|
93
|
-
"code": "import React, { useState } from 'react';\nimport { Pressable, Text, View } from 'react-native';\nimport { ProgressSteps } from '@/components/ui/progress-steps';\nimport { alpha, useKit } from '@/lib/kit';\n\nfunction Buttons({ step, max, set, last = 'Place order' }: { step: number; max: number; set: (n: number) => void; last?: string }) {\n const { colors, radius } = useKit();\n const done = step >= max;\n return (\n <View style={{ flexDirection: 'row', gap: 10 }}>\n <Pressable\n onPress={() => set(Math.max(0, step - 1))}\n disabled={step === 0}\n accessibilityRole=\"button\"\n style={({ pressed }) => ({\n flex: 1,\n
|
|
93
|
+
"code": "import React, { useState } from 'react';\nimport { Pressable, Text, View } from 'react-native';\nimport { ProgressSteps } from '@/components/ui/progress-steps';\nimport { alpha, useKit } from '@/lib/kit';\n\n/** Controls stop at iOS's largest standard text size; content text stays uncapped. */\nconst FONT_CAP = 1.35;\n\nfunction Buttons({ step, max, set, last = 'Place order' }: { step: number; max: number; set: (n: number) => void; last?: string }) {\n const { colors, radius } = useKit();\n const done = step >= max;\n return (\n <View style={{ flexDirection: 'row', gap: 10 }}>\n <Pressable\n onPress={() => set(Math.max(0, step - 1))}\n disabled={step === 0}\n accessibilityRole=\"button\"\n style={({ pressed }) => ({\n flex: 1,\n minHeight: 48,\n borderRadius: radius.capsule(48),\n backgroundColor: alpha(colors.foreground, pressed ? 0.1 : 0.05),\n alignItems: 'center',\n justifyContent: 'center',\n opacity: step === 0 ? 0.4 : 1,\n })}\n >\n <Text maxFontSizeMultiplier={FONT_CAP} style={{ fontSize: 15, color: colors.foreground }}>Back</Text>\n </Pressable>\n <Pressable\n onPress={() => set(done ? 0 : step + 1)}\n accessibilityRole=\"button\"\n style={({ pressed }) => ({\n flex: 1.5,\n minHeight: 48,\n borderRadius: radius.capsule(48),\n backgroundColor: colors.accent,\n alignItems: 'center',\n justifyContent: 'center',\n transform: [{ scale: pressed ? 0.98 : 1 }],\n })}\n >\n <Text maxFontSizeMultiplier={FONT_CAP} style={{ fontSize: 15, color: colors.accentForeground }}>\n {done ? 'Start over' : step === max - 1 ? last : 'Continue'}\n </Text>\n </Pressable>\n </View>\n );\n}\n\nexport default function Checkout() {\n const { colors } = useKit();\n const [step, setStep] = useState(1);\n const copy = [\n '2 items · ₹2,798',\n 'Delivering to 14 Rua das Flores, Lisbon',\n 'Visa ending 4242',\n 'Arrives Thursday, 16 October',\n ];\n return (\n <View style={{ gap: 22 }}>\n <ProgressSteps\n steps={['Cart', 'Shipping', 'Payment', 'Review']}\n current={step}\n pressableSteps=\"completed\"\n onStepPress={setStep}\n />\n <Text style={{ fontSize: 14, textAlign: 'center', color: colors.mutedForeground }}>\n {step >= 4 ? 'Order #4821 placed — receipt sent to amelia@hart.studio' : copy[step]}\n </Text>\n <Buttons step={step} max={4} set={setStep} />\n </View>\n );\n}\n"
|
|
94
94
|
},
|
|
95
95
|
{
|
|
96
96
|
"name": "Onboarding",
|
|
97
97
|
"title": "Onboarding",
|
|
98
|
-
"code": "import React, { useState } from 'react';\nimport { Pressable, Text, View } from 'react-native';\nimport { ProgressSteps } from '@/components/ui/progress-steps';\nimport { alpha, useKit } from '@/lib/kit';\n\nfunction Buttons({ step, max, set, last = 'Place order' }: { step: number; max: number; set: (n: number) => void; last?: string }) {\n const { colors, radius } = useKit();\n const done = step >= max;\n return (\n <View style={{ flexDirection: 'row', gap: 10 }}>\n <Pressable\n onPress={() => set(Math.max(0, step - 1))}\n disabled={step === 0}\n accessibilityRole=\"button\"\n style={({ pressed }) => ({\n flex: 1,\n
|
|
98
|
+
"code": "import React, { useState } from 'react';\nimport { Pressable, Text, View } from 'react-native';\nimport { ProgressSteps } from '@/components/ui/progress-steps';\nimport { alpha, useKit } from '@/lib/kit';\n\n/** Controls stop at iOS's largest standard text size; content text stays uncapped. */\nconst FONT_CAP = 1.35;\n\nfunction Buttons({ step, max, set, last = 'Place order' }: { step: number; max: number; set: (n: number) => void; last?: string }) {\n const { colors, radius } = useKit();\n const done = step >= max;\n return (\n <View style={{ flexDirection: 'row', gap: 10 }}>\n <Pressable\n onPress={() => set(Math.max(0, step - 1))}\n disabled={step === 0}\n accessibilityRole=\"button\"\n style={({ pressed }) => ({\n flex: 1,\n minHeight: 48,\n borderRadius: radius.capsule(48),\n backgroundColor: alpha(colors.foreground, pressed ? 0.1 : 0.05),\n alignItems: 'center',\n justifyContent: 'center',\n opacity: step === 0 ? 0.4 : 1,\n })}\n >\n <Text maxFontSizeMultiplier={FONT_CAP} style={{ fontSize: 15, color: colors.foreground }}>Back</Text>\n </Pressable>\n <Pressable\n onPress={() => set(done ? 0 : step + 1)}\n accessibilityRole=\"button\"\n style={({ pressed }) => ({\n flex: 1.5,\n minHeight: 48,\n borderRadius: radius.capsule(48),\n backgroundColor: colors.accent,\n alignItems: 'center',\n justifyContent: 'center',\n transform: [{ scale: pressed ? 0.98 : 1 }],\n })}\n >\n <Text maxFontSizeMultiplier={FONT_CAP} style={{ fontSize: 15, color: colors.accentForeground }}>\n {done ? 'Start over' : step === max - 1 ? last : 'Continue'}\n </Text>\n </Pressable>\n </View>\n );\n}\n\nexport default function Onboarding() {\n const [step, setStep] = useState(2);\n return (\n <View style={{ gap: 20 }}>\n <ProgressSteps\n variant=\"vertical\"\n current={step}\n pressableSteps=\"completed\"\n onStepPress={setStep}\n steps={[\n { label: 'Create your account', description: 'Signed up with amelia@hart.studio' },\n { label: 'Verify your email', description: 'Confirmed with a 6-digit code.' },\n { label: 'Connect your bank', description: 'Read-only access. You can disconnect any time.' },\n { label: 'Set your first budget', description: 'Pick categories and a monthly limit.' },\n ]}\n />\n <Buttons step={step} max={4} set={setStep} last=\"Finish setup\" />\n </View>\n );\n}\n"
|
|
99
99
|
},
|
|
100
100
|
{
|
|
101
101
|
"name": "Compact",
|
|
102
102
|
"title": "Compact",
|
|
103
|
-
"code": "import React, { useState } from 'react';\nimport { Pressable, Text, View } from 'react-native';\nimport { ProgressSteps } from '@/components/ui/progress-steps';\nimport { alpha, useKit } from '@/lib/kit';\n\nfunction Buttons({ step, max, set, last = 'Place order' }: { step: number; max: number; set: (n: number) => void; last?: string }) {\n const { colors, radius } = useKit();\n const done = step >= max;\n return (\n <View style={{ flexDirection: 'row', gap: 10 }}>\n <Pressable\n onPress={() => set(Math.max(0, step - 1))}\n disabled={step === 0}\n accessibilityRole=\"button\"\n style={({ pressed }) => ({\n flex: 1,\n
|
|
103
|
+
"code": "import React, { useState } from 'react';\nimport { Pressable, Text, View } from 'react-native';\nimport { ProgressSteps } from '@/components/ui/progress-steps';\nimport { alpha, useKit } from '@/lib/kit';\n\n/** Controls stop at iOS's largest standard text size; content text stays uncapped. */\nconst FONT_CAP = 1.35;\n\nfunction Buttons({ step, max, set, last = 'Place order' }: { step: number; max: number; set: (n: number) => void; last?: string }) {\n const { colors, radius } = useKit();\n const done = step >= max;\n return (\n <View style={{ flexDirection: 'row', gap: 10 }}>\n <Pressable\n onPress={() => set(Math.max(0, step - 1))}\n disabled={step === 0}\n accessibilityRole=\"button\"\n style={({ pressed }) => ({\n flex: 1,\n minHeight: 48,\n borderRadius: radius.capsule(48),\n backgroundColor: alpha(colors.foreground, pressed ? 0.1 : 0.05),\n alignItems: 'center',\n justifyContent: 'center',\n opacity: step === 0 ? 0.4 : 1,\n })}\n >\n <Text maxFontSizeMultiplier={FONT_CAP} style={{ fontSize: 15, color: colors.foreground }}>Back</Text>\n </Pressable>\n <Pressable\n onPress={() => set(done ? 0 : step + 1)}\n accessibilityRole=\"button\"\n style={({ pressed }) => ({\n flex: 1.5,\n minHeight: 48,\n borderRadius: radius.capsule(48),\n backgroundColor: colors.accent,\n alignItems: 'center',\n justifyContent: 'center',\n transform: [{ scale: pressed ? 0.98 : 1 }],\n })}\n >\n <Text maxFontSizeMultiplier={FONT_CAP} style={{ fontSize: 15, color: colors.accentForeground }}>\n {done ? 'Start over' : step === max - 1 ? last : 'Continue'}\n </Text>\n </Pressable>\n </View>\n );\n}\n\nexport default function Compact() {\n const [step, setStep] = useState(1);\n return (\n <View style={{ gap: 20 }}>\n <ProgressSteps variant=\"bar\" steps={['Shipping address', 'Delivery options', 'Payment', 'Review order']} current={step} />\n <Buttons step={step} max={4} set={setStep} />\n </View>\n );\n}\n"
|
|
104
104
|
},
|
|
105
105
|
{
|
|
106
106
|
"name": "WithDescriptions",
|
|
107
107
|
"title": "With Descriptions",
|
|
108
|
-
"code": "import React, { useState } from 'react';\nimport { Pressable, Text, View } from 'react-native';\nimport { ProgressSteps } from '@/components/ui/progress-steps';\nimport { alpha, useKit } from '@/lib/kit';\n\nfunction Buttons({ step, max, set, last = 'Place order' }: { step: number; max: number; set: (n: number) => void; last?: string }) {\n const { colors, radius } = useKit();\n const done = step >= max;\n return (\n <View style={{ flexDirection: 'row', gap: 10 }}>\n <Pressable\n onPress={() => set(Math.max(0, step - 1))}\n disabled={step === 0}\n accessibilityRole=\"button\"\n style={({ pressed }) => ({\n flex: 1,\n
|
|
108
|
+
"code": "import React, { useState } from 'react';\nimport { Pressable, Text, View } from 'react-native';\nimport { ProgressSteps } from '@/components/ui/progress-steps';\nimport { alpha, useKit } from '@/lib/kit';\n\n/** Controls stop at iOS's largest standard text size; content text stays uncapped. */\nconst FONT_CAP = 1.35;\n\nfunction Buttons({ step, max, set, last = 'Place order' }: { step: number; max: number; set: (n: number) => void; last?: string }) {\n const { colors, radius } = useKit();\n const done = step >= max;\n return (\n <View style={{ flexDirection: 'row', gap: 10 }}>\n <Pressable\n onPress={() => set(Math.max(0, step - 1))}\n disabled={step === 0}\n accessibilityRole=\"button\"\n style={({ pressed }) => ({\n flex: 1,\n minHeight: 48,\n borderRadius: radius.capsule(48),\n backgroundColor: alpha(colors.foreground, pressed ? 0.1 : 0.05),\n alignItems: 'center',\n justifyContent: 'center',\n opacity: step === 0 ? 0.4 : 1,\n })}\n >\n <Text maxFontSizeMultiplier={FONT_CAP} style={{ fontSize: 15, color: colors.foreground }}>Back</Text>\n </Pressable>\n <Pressable\n onPress={() => set(done ? 0 : step + 1)}\n accessibilityRole=\"button\"\n style={({ pressed }) => ({\n flex: 1.5,\n minHeight: 48,\n borderRadius: radius.capsule(48),\n backgroundColor: colors.accent,\n alignItems: 'center',\n justifyContent: 'center',\n transform: [{ scale: pressed ? 0.98 : 1 }],\n })}\n >\n <Text maxFontSizeMultiplier={FONT_CAP} style={{ fontSize: 15, color: colors.accentForeground }}>\n {done ? 'Start over' : step === max - 1 ? last : 'Continue'}\n </Text>\n </Pressable>\n </View>\n );\n}\n\nexport default function WithDescriptions() {\n const [step, setStep] = useState(1);\n return (\n <View style={{ gap: 22 }}>\n <ProgressSteps\n current={step}\n steps={[\n { label: 'Profile', description: 'Name, photo' },\n { label: 'Workspace', description: 'Invite team' },\n { label: 'Plan', description: 'Free or Pro' },\n ]}\n />\n <Buttons step={step} max={3} set={setStep} last=\"Create workspace\" />\n </View>\n );\n}\n"
|
|
109
109
|
},
|
|
110
110
|
{
|
|
111
111
|
"name": "WithError",
|
|
@@ -116,7 +116,7 @@
|
|
|
116
116
|
{
|
|
117
117
|
"name": "Linear",
|
|
118
118
|
"title": "Linear",
|
|
119
|
-
"code": "import React, { useEffect, useState } from 'react';\nimport { Text, View } from 'react-native';\nimport { Button } from '@/components/ui/button';\nimport { Progress } from '@/components/ui/progress';\n\nexport default function Linear() {\n const [value, setValue] = useState(32);\n return (\n <View style={{ gap: 18 }}>\n <Progress value={value} label=\"Uploading trip-to-lisbon.mov\" showValue />\n <Progress value={value} size=\"sm\" />\n <Progress value={value} size=\"lg\" />\n <View style={{ flexDirection: 'row', gap: 10 }}>\n <Button size=\"sm\" variant=\"outline\" onPress={() => setValue((v) => Math.max(0, v - 20))}>\n − 20\n </Button>\n <Button size=\"sm\" variant=\"outline\" onPress={() => setValue((v) => Math.min(100, v + 20))}>\n + 20\n </Button>\n </View>\n </View>\n );\n}\n"
|
|
119
|
+
"code": "import React, { useEffect, useState } from 'react';\nimport { Text, View } from 'react-native';\nimport { Button } from '@/components/ui/button';\nimport { Progress } from '@/components/ui/progress';\n\nexport default function Linear() {\n const [value, setValue] = useState(32);\n return (\n <View style={{ gap: 18 }}>\n <Progress value={value} label=\"Uploading trip-to-lisbon.mov\" showValue />\n <Progress value={value} size=\"sm\" />\n <Progress value={value} size=\"lg\" />\n <View style={{ flexDirection: 'row', flexWrap: 'wrap', gap: 10 }}>\n <Button size=\"sm\" variant=\"outline\" onPress={() => setValue((v) => Math.max(0, v - 20))}>\n − 20\n </Button>\n <Button size=\"sm\" variant=\"outline\" onPress={() => setValue((v) => Math.min(100, v + 20))}>\n + 20\n </Button>\n </View>\n </View>\n );\n}\n"
|
|
120
120
|
},
|
|
121
121
|
{
|
|
122
122
|
"name": "Download",
|
|
@@ -146,7 +146,7 @@
|
|
|
146
146
|
{
|
|
147
147
|
"name": "Steps",
|
|
148
148
|
"title": "Steps",
|
|
149
|
-
"code": "import React, { useEffect, useState } from 'react';\nimport { Text, View } from 'react-native';\nimport { Button } from '@/components/ui/button';\nimport { Progress } from '@/components/ui/progress';\n\nexport default function Steps() {\n const [step, setStep] = useState(2);\n return (\n <View style={{ gap: 16 }}>\n <Progress\n variant=\"steps\"\n steps={5}\n value={step}\n label={step >= 5 ? 'Profile ready' : 'Set up your profile'}\n showValue\n formatValue={(v, max) => `Step ${Math.min(max, v + 1)} of ${max}`}\n />\n <View style={{ flexDirection: 'row', gap: 10 }}>\n <Button size=\"sm\" variant=\"outline\" onPress={() => setStep((s) => Math.max(0, s - 1))}>\n Back\n </Button>\n <Button size=\"sm\" variant=\"accent\" onPress={() => setStep((s) => Math.min(5, s + 1))}>\n Continue\n </Button>\n </View>\n </View>\n );\n}\n"
|
|
149
|
+
"code": "import React, { useEffect, useState } from 'react';\nimport { Text, View } from 'react-native';\nimport { Button } from '@/components/ui/button';\nimport { Progress } from '@/components/ui/progress';\n\nexport default function Steps() {\n const [step, setStep] = useState(2);\n return (\n <View style={{ gap: 16 }}>\n <Progress\n variant=\"steps\"\n steps={5}\n value={step}\n label={step >= 5 ? 'Profile ready' : 'Set up your profile'}\n showValue\n formatValue={(v, max) => `Step ${Math.min(max, v + 1)} of ${max}`}\n />\n <View style={{ flexDirection: 'row', flexWrap: 'wrap', gap: 10 }}>\n <Button size=\"sm\" variant=\"outline\" onPress={() => setStep((s) => Math.max(0, s - 1))}>\n Back\n </Button>\n <Button size=\"sm\" variant=\"accent\" onPress={() => setStep((s) => Math.min(5, s + 1))}>\n Continue\n </Button>\n </View>\n </View>\n );\n}\n"
|
|
150
150
|
}
|
|
151
151
|
]
|
|
152
152
|
}
|
|
@@ -101,7 +101,7 @@
|
|
|
101
101
|
{
|
|
102
102
|
"name": "CardsHorizontal",
|
|
103
103
|
"title": "Cards Horizontal",
|
|
104
|
-
"code": "import { Text } from 'react-native';\nimport { RadioGroup } from '@/components/ui/radio-group';\n\nexport default function CardsHorizontal() {\n return (\n <RadioGroup\n variant=\"card\"\n direction=\"horizontal\"\n defaultValue=\"card\"\n options={[\n { value: 'card', label: 'Card', description: 'Visa ·· 4242', icon: <Text style={{ fontSize: 20 }}>💳</Text> },\n { value: 'wallet', label: 'Wallet', description: 'Apple Pay', icon: <Text style={{ fontSize: 20 }}>📱</Text> },\n ]}\n />\n );\n}\n"
|
|
104
|
+
"code": "import { Text } from 'react-native';\nimport { RadioGroup } from '@/components/ui/radio-group';\n\n/** Controls stop at iOS's largest standard text size; content text stays uncapped. */\nconst FONT_CAP = 1.35;\n\nexport default function CardsHorizontal() {\n return (\n <RadioGroup\n variant=\"card\"\n direction=\"horizontal\"\n defaultValue=\"card\"\n options={[\n { value: 'card', label: 'Card', description: 'Visa ·· 4242', icon: <Text maxFontSizeMultiplier={FONT_CAP} style={{ fontSize: 20 }}>💳</Text> },\n { value: 'wallet', label: 'Wallet', description: 'Apple Pay', icon: <Text maxFontSizeMultiplier={FONT_CAP} style={{ fontSize: 20 }}>📱</Text> },\n ]}\n />\n );\n}\n"
|
|
105
105
|
},
|
|
106
106
|
{
|
|
107
107
|
"name": "GroupedList",
|
|
@@ -139,17 +139,17 @@
|
|
|
139
139
|
{
|
|
140
140
|
"name": "LiveResults",
|
|
141
141
|
"title": "Live Results",
|
|
142
|
-
"code": "import React, { useEffect, useState } from 'react';\nimport { Pressable, StyleSheet, Text, View, type ViewProps } from 'react-native';\nimport { SearchBar, type SearchSuggestion } from '@/components/ui/search-bar';\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\n/** Demo card that follows the kit theme, so the docs radius / accent picker reshapes it too. */\nfunction Card({ screen, style, ...rest }: ViewProps & { screen?: boolean }) {\n const { colors, radius } = useKit();\n return (\n <View\n style={[\n { padding: 16, gap: 12 },\n screen\n ? { flex: 1, backgroundColor: colors.background }\n : { borderRadius: radius.xl, borderWidth: StyleSheet.hairlineWidth, borderColor: colors.border, backgroundColor: colors.card },\n style,\n ]}\n {...rest}\n />\n );\n}\n\nconst CONTACTS = [\n { name: 'Aarav Mehta', role: 'Design lead' },\n { name: 'Priya Nair', role: 'Product manager' },\n { name: 'Rohan Kapoor', role: 'iOS engineer' },\n { name: 'Sara Ali', role: 'Marketing' },\n { name: 'Vikram Rao', role: 'Backend engineer' },\n];\n\nexport default function LiveResults({ screen }: DemoProps) {\n const { colors } = useKit();\n const [query, setQuery] = useState('');\n const [loading, setLoading] = useState(false);\n useEffect(() => {\n if (!query) return setLoading(false);\n setLoading(true);\n const id = setTimeout(() => setLoading(false), 600);\n return () => clearTimeout(id);\n }, [query]);\n const q = query.trim().toLowerCase();\n const results = CONTACTS.filter((c) => c.name.toLowerCase().includes(q) || c.role.toLowerCase().includes(q));\n return (\n <Card screen={screen}>\n <SearchBar variant=\"filled\" value={query} onChangeText={setQuery} loading={loading} placeholder=\"Search people\" />\n {loading ? (\n <Text style={{ fontSize: 13, color: colors.mutedForeground }}>Searching your team…</Text>\n ) : results.length ? (\n results.slice(0, 4).map((c) => (\n <View key={c.name} style={{ flexDirection: 'row', alignItems: 'center', gap: 12 }}>\n <View style={{ width: 34, height: 34, borderRadius: 17, backgroundColor: alpha(colors.accent, 0.16), alignItems: 'center', justifyContent: 'center' }}>\n <Text style={{ fontSize: 13, fontWeight: '400', color: colors.accent }}>{c.name.split(' ').map((p) => p[0]).join('')}</Text>\n </View>\n <View>\n <Text style={{ fontSize: 15, fontWeight: '400', color: colors.foreground }}>{c.name}</Text>\n <Text style={{ fontSize: 12.5, color: colors.mutedForeground }}>{c.role}</Text>\n </View>\n </View>\n ))\n ) : (\n <Text style={{ fontSize: 13, color: colors.mutedForeground }}>No one matches “{query}”</Text>\n )}\n </Card>\n );\n}\n"
|
|
142
|
+
"code": "import React, { useEffect, useState } from 'react';\nimport { Pressable, StyleSheet, Text, View, type ViewProps } from 'react-native';\nimport { SearchBar, type SearchSuggestion } from '@/components/ui/search-bar';\nimport { alpha, useKit } from '@/lib/kit';\n\n/** Control text stops at iOS's largest standard size. */\nconst FONT_CAP = 1.35;\n\n/** From the Expo Go viewer: `screen` = this IS the phone screen, so no mock frame; `bottomInset` = home indicator. */\ntype DemoProps = { screen?: boolean; bottomInset?: number };\n\n/** Demo card that follows the kit theme, so the docs radius / accent picker reshapes it too. */\nfunction Card({ screen, style, ...rest }: ViewProps & { screen?: boolean }) {\n const { colors, radius } = useKit();\n return (\n <View\n style={[\n { padding: 16, gap: 12 },\n screen\n ? { flex: 1, backgroundColor: colors.background }\n : { borderRadius: radius.xl, borderWidth: StyleSheet.hairlineWidth, borderColor: colors.border, backgroundColor: colors.card },\n style,\n ]}\n {...rest}\n />\n );\n}\n\nconst CONTACTS = [\n { name: 'Aarav Mehta', role: 'Design lead' },\n { name: 'Priya Nair', role: 'Product manager' },\n { name: 'Rohan Kapoor', role: 'iOS engineer' },\n { name: 'Sara Ali', role: 'Marketing' },\n { name: 'Vikram Rao', role: 'Backend engineer' },\n];\n\nexport default function LiveResults({ screen }: DemoProps) {\n const { colors } = useKit();\n const [query, setQuery] = useState('');\n const [loading, setLoading] = useState(false);\n useEffect(() => {\n if (!query) return setLoading(false);\n setLoading(true);\n const id = setTimeout(() => setLoading(false), 600);\n return () => clearTimeout(id);\n }, [query]);\n const q = query.trim().toLowerCase();\n const results = CONTACTS.filter((c) => c.name.toLowerCase().includes(q) || c.role.toLowerCase().includes(q));\n return (\n <Card screen={screen}>\n <SearchBar variant=\"filled\" value={query} onChangeText={setQuery} loading={loading} placeholder=\"Search people\" />\n {loading ? (\n <Text style={{ fontSize: 13, color: colors.mutedForeground }}>Searching your team…</Text>\n ) : results.length ? (\n results.slice(0, 4).map((c) => (\n <View key={c.name} style={{ flexDirection: 'row', alignItems: 'center', gap: 12 }}>\n <View style={{ width: 34, height: 34, borderRadius: 17, backgroundColor: alpha(colors.accent, 0.16), alignItems: 'center', justifyContent: 'center' }}>\n <Text maxFontSizeMultiplier={FONT_CAP} style={{ fontSize: 13, fontWeight: '400', color: colors.accent }}>{c.name.split(' ').map((p) => p[0]).join('')}</Text>\n </View>\n <View style={{ flexShrink: 1 }}>\n <Text style={{ fontSize: 15, fontWeight: '400', color: colors.foreground }}>{c.name}</Text>\n <Text style={{ fontSize: 12.5, color: colors.mutedForeground }}>{c.role}</Text>\n </View>\n </View>\n ))\n ) : (\n <Text style={{ fontSize: 13, color: colors.mutedForeground }}>No one matches “{query}”</Text>\n )}\n </Card>\n );\n}\n"
|
|
143
143
|
},
|
|
144
144
|
{
|
|
145
145
|
"name": "Minimal",
|
|
146
146
|
"title": "Minimal",
|
|
147
|
-
"code": "import { Pressable, StyleSheet, Text, View, type ViewProps } from 'react-native';\nimport { SearchBar, type SearchSuggestion } from '@/components/ui/search-bar';\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\n/** Demo card that follows the kit theme, so the docs radius / accent picker reshapes it too. */\nfunction Card({ screen, style, ...rest }: ViewProps & { screen?: boolean }) {\n const { colors, radius } = useKit();\n return (\n <View\n style={[\n { padding: 16, gap: 12 },\n screen\n ? { flex: 1, backgroundColor: colors.background }\n : { borderRadius: radius.xl, borderWidth: StyleSheet.hairlineWidth, borderColor: colors.border, backgroundColor: colors.card },\n style,\n ]}\n {...rest}\n />\n );\n}\n\nexport default function Minimal({ screen }: DemoProps) {\n const { colors } = useKit();\n return (\n <Card screen={screen} style={{ flexDirection: 'row', alignItems: 'center', gap: 12 }}>\n <Text style={{ fontSize: 22, fontWeight: '400', letterSpacing: -0.4, color: colors.foreground }}>Inbox</Text>\n <SearchBar variant=\"minimal\" align=\"right\" placeholder=\"Search mail\" style={{ flex: 1 }} />\n </Card>\n );\n}\n"
|
|
147
|
+
"code": "import { Pressable, StyleSheet, Text, View, type ViewProps } from 'react-native';\nimport { SearchBar, type SearchSuggestion } from '@/components/ui/search-bar';\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\n/** Demo card that follows the kit theme, so the docs radius / accent picker reshapes it too. */\nfunction Card({ screen, style, ...rest }: ViewProps & { screen?: boolean }) {\n const { colors, radius } = useKit();\n return (\n <View\n style={[\n { padding: 16, gap: 12 },\n screen\n ? { flex: 1, backgroundColor: colors.background }\n : { borderRadius: radius.xl, borderWidth: StyleSheet.hairlineWidth, borderColor: colors.border, backgroundColor: colors.card },\n style,\n ]}\n {...rest}\n />\n );\n}\n\nexport default function Minimal({ screen }: DemoProps) {\n const { colors } = useKit();\n return (\n <Card screen={screen} style={{ flexDirection: 'row', alignItems: 'center', gap: 12 }}>\n <Text style={{ flexShrink: 1, fontSize: 22, fontWeight: '400', letterSpacing: -0.4, color: colors.foreground }}>Inbox</Text>\n <SearchBar variant=\"minimal\" align=\"right\" placeholder=\"Search mail\" style={{ flex: 1 }} />\n </Card>\n );\n}\n"
|
|
148
148
|
},
|
|
149
149
|
{
|
|
150
150
|
"name": "WithFilter",
|
|
151
151
|
"title": "With Filter",
|
|
152
|
-
"code": "import React, { useEffect, useState } from 'react';\nimport { Pressable, StyleSheet, Text, View, type ViewProps } from 'react-native';\nimport { SearchBar, type SearchSuggestion } from '@/components/ui/search-bar';\nimport { alpha, useKit } from '@/lib/kit';\n\nfunction FilterButton({ count, onPress }: { count: number; onPress: () => void }) {\n const { colors, radius } = useKit();\n return (\n <Pressable\n onPress={onPress}\n hitSlop={8}\n accessibilityRole=\"button\"\n accessibilityLabel={`Filters, ${count} active`}\n style={{
|
|
152
|
+
"code": "import React, { useEffect, useState } from 'react';\nimport { Pressable, StyleSheet, Text, View, type ViewProps } from 'react-native';\nimport { SearchBar, type SearchSuggestion } from '@/components/ui/search-bar';\nimport { alpha, useKit } from '@/lib/kit';\n\n/** Control text stops at iOS's largest standard size. */\nconst FONT_CAP = 1.35;\n\nfunction FilterButton({ count, onPress }: { count: number; onPress: () => void }) {\n const { colors, radius } = useKit();\n return (\n <Pressable\n onPress={onPress}\n hitSlop={8}\n accessibilityRole=\"button\"\n accessibilityLabel={`Filters, ${count} active`}\n style={{ minHeight: 32, paddingHorizontal: 10, borderRadius: radius.capsule(32), backgroundColor: count ? colors.accent : colors.muted, flexDirection: 'row', alignItems: 'center', gap: 6 }}\n >\n <View style={{ gap: 3, alignItems: 'center' }}>\n {[14, 10, 6].map((w) => (\n <View key={w} style={{ width: w, height: 2, borderRadius: 1, backgroundColor: count ? colors.accentForeground : colors.mutedForeground }} />\n ))}\n </View>\n {count ? <Text maxFontSizeMultiplier={FONT_CAP} style={{ fontSize: 12.5, fontWeight: '400', color: colors.accentForeground }}>{count}</Text> : null}\n </Pressable>\n );\n}\n\nexport default function WithFilter() {\n const { colors } = useKit();\n const [count, setCount] = useState(2);\n return (\n <View style={{ gap: 10 }}>\n <SearchBar\n placeholder=\"Stays in Goa\"\n showCancel={false}\n trailing={<FilterButton count={count} onPress={() => setCount((c) => (c + 1) % 4)} />}\n />\n <Text style={{ fontSize: 13, color: colors.mutedForeground }}>{count ? `${count} filters · Pool, Under ₹8,000` : 'No filters applied'}</Text>\n </View>\n );\n}\n"
|
|
153
153
|
},
|
|
154
154
|
{
|
|
155
155
|
"name": "Sizes",
|
|
@@ -107,7 +107,7 @@
|
|
|
107
107
|
{
|
|
108
108
|
"name": "WithIcons",
|
|
109
109
|
"title": "With Icons",
|
|
110
|
-
"code": "import { Text, View } from 'react-native';\nimport { SegmentedControl } from '@/components/ui/segmented-control';\n\nexport default function WithIcons() {\n const glyph = (g: string) => ({ color }: { color: string }) => <Text style={{ fontSize: 15, color }}>{g}</Text>;\n return (\n <SegmentedControl\n defaultValue=\"grid\"\n options={[\n { value: 'list', label: 'List', icon: glyph('☰') },\n { value: 'grid', label: 'Grid', icon: glyph('▦') },\n { value: 'map', label: 'Map', icon: glyph('◎') },\n ]}\n />\n );\n}\n"
|
|
110
|
+
"code": "import { Text, View } from 'react-native';\nimport { SegmentedControl } from '@/components/ui/segmented-control';\n\n/** Controls stop at iOS's largest standard text size; content text stays uncapped. */\nconst FONT_CAP = 1.35;\n\nexport default function WithIcons() {\n const glyph = (g: string) => ({ color }: { color: string }) => <Text maxFontSizeMultiplier={FONT_CAP} style={{ fontSize: 15, color }}>{g}</Text>;\n return (\n <SegmentedControl\n defaultValue=\"grid\"\n options={[\n { value: 'list', label: 'List', icon: glyph('☰') },\n { value: 'grid', label: 'Grid', icon: glyph('▦') },\n { value: 'map', label: 'Map', icon: glyph('◎') },\n ]}\n />\n );\n}\n"
|
|
111
111
|
},
|
|
112
112
|
{
|
|
113
113
|
"name": "Accent",
|
|
@@ -241,27 +241,27 @@
|
|
|
241
241
|
{
|
|
242
242
|
"name": "Validation",
|
|
243
243
|
"title": "Validation",
|
|
244
|
-
"code": "import React, { useState } from 'react';\nimport { Pressable, StyleSheet, Text, View, type TextProps, type ViewProps } from 'react-native';\nimport { Select } from '@/components/ui/select';\nimport { alpha, readableOn, useKit } from '@/lib/kit';\n\n/** From the Expo Go viewer: `screen` = this IS the phone screen, so no mock frame; `bottomInset` = home indicator. */\ntype DemoProps = { screen?: boolean; bottomInset?: number };\n\n/** Demo card that follows the kit theme, so the docs radius / accent picker reshapes it too. */\nfunction Card({ screen, style, ...rest }: ViewProps & { screen?: boolean }) {\n const { colors, radius } = useKit();\n return (\n <View\n style={[\n screen\n ? { flex: 1, backgroundColor: colors.background, padding: 18, gap: 14 }\n : {\n backgroundColor: colors.card,\n borderColor: colors.border,\n borderWidth: StyleSheet.hairlineWidth,\n borderRadius: radius.xl,\n padding: 18,\n gap: 14,\n },\n style,\n ]}\n {...rest}\n />\n );\n}\n\n/** Text in the kit's ink: `tone=\"muted\"` for secondary copy. */\nfunction Ink({ tone, style, ...rest }: TextProps & { tone?: 'muted' }) {\n const { colors } = useKit();\n return <Text style={[{ color: tone === 'muted' ? colors.mutedForeground : colors.foreground }, style]} {...rest} />;\n}\n\nfunction Heading({ title, sub }: { title: string; sub?: string }) {\n return (\n <View style={s.heading}>\n <Ink style={s.title}>{title}</Ink>\n {sub ? (\n <Ink tone=\"muted\" style={s.sub}>\n {sub}\n </Ink>\n ) : null}\n </View>\n );\n}\n\nconst flag = (f: string) => <Text style={s.flag}>{f}</Text>;\n\nconst avatar = (initials: string, color: string) => (\n <View style={[s.avatar, { backgroundColor: color }]}>\n <Text style={[s.initials, { color: readableOn(color) }]}>{initials}</Text>\n </View>\n);\n\nconst s = StyleSheet.create({\n heading: { gap: 3, marginBottom: 2 },\n title: { fontSize: 16, fontWeight: '400', letterSpacing: -0.2 },\n sub: { fontSize: 13, lineHeight: 18 },\n glyph: { width: 28, height: 28, alignItems: 'center', justifyContent: 'center' },\n flag: { fontSize: 18 },\n avatar: { width: 26, height: 26, borderRadius: 13, alignItems: 'center', justifyContent: 'center' },\n initials: { fontSize: 10.5, fontWeight: '400' },\n button: {
|
|
244
|
+
"code": "import React, { useState } from 'react';\nimport { Pressable, StyleSheet, Text, View, type TextProps, type ViewProps } from 'react-native';\nimport { Select } from '@/components/ui/select';\nimport { alpha, readableOn, useKit } from '@/lib/kit';\n\n/** Controls stop at iOS's largest standard text size; content text stays uncapped. */\nconst FONT_CAP = 1.35;\n\n/** From the Expo Go viewer: `screen` = this IS the phone screen, so no mock frame; `bottomInset` = home indicator. */\ntype DemoProps = { screen?: boolean; bottomInset?: number };\n\n/** Demo card that follows the kit theme, so the docs radius / accent picker reshapes it too. */\nfunction Card({ screen, style, ...rest }: ViewProps & { screen?: boolean }) {\n const { colors, radius } = useKit();\n return (\n <View\n style={[\n screen\n ? { flex: 1, backgroundColor: colors.background, padding: 18, gap: 14 }\n : {\n backgroundColor: colors.card,\n borderColor: colors.border,\n borderWidth: StyleSheet.hairlineWidth,\n borderRadius: radius.xl,\n padding: 18,\n gap: 14,\n },\n style,\n ]}\n {...rest}\n />\n );\n}\n\n/** Text in the kit's ink: `tone=\"muted\"` for secondary copy. */\nfunction Ink({ tone, style, ...rest }: TextProps & { tone?: 'muted' }) {\n const { colors } = useKit();\n return <Text style={[{ color: tone === 'muted' ? colors.mutedForeground : colors.foreground }, style]} {...rest} />;\n}\n\nfunction Heading({ title, sub }: { title: string; sub?: string }) {\n return (\n <View style={s.heading}>\n <Ink style={s.title}>{title}</Ink>\n {sub ? (\n <Ink tone=\"muted\" style={s.sub}>\n {sub}\n </Ink>\n ) : null}\n </View>\n );\n}\n\nconst flag = (f: string) => <Text maxFontSizeMultiplier={FONT_CAP} style={s.flag}>{f}</Text>;\n\nconst avatar = (initials: string, color: string) => (\n <View style={[s.avatar, { backgroundColor: color }]}>\n <Text maxFontSizeMultiplier={FONT_CAP} style={[s.initials, { color: readableOn(color) }]}>{initials}</Text>\n </View>\n);\n\nconst s = StyleSheet.create({\n heading: { gap: 3, marginBottom: 2 },\n title: { fontSize: 16, fontWeight: '400', letterSpacing: -0.2 },\n sub: { fontSize: 13, lineHeight: 18 },\n glyph: { width: 28, height: 28, alignItems: 'center', justifyContent: 'center' },\n flag: { fontSize: 18 },\n avatar: { width: 26, height: 26, borderRadius: 13, alignItems: 'center', justifyContent: 'center' },\n initials: { fontSize: 10.5, fontWeight: '400' },\n button: { minHeight: 44, alignItems: 'center', justifyContent: 'center', marginTop: 2 },\n buttonText: { fontSize: 15, fontWeight: '400' },\n});\n\nexport default function Validation({ screen }: DemoProps) {\n const { colors, radius } = useKit();\n const [size, setSize] = useState<string | null>(null);\n const [touched, setTouched] = useState(false);\n const missing = touched && !size;\n return (\n <Card screen={screen}>\n <Heading title=\"Merino crew sweater\" sub=\"₹3,499 · Oatmeal\" />\n <Select\n label=\"Size\"\n floatingLabel={false}\n placeholder=\"Choose a size\"\n size=\"sm\"\n value={size}\n surface={colors.card}\n onValueChange={(v) => {\n setSize(v);\n setTouched(true);\n }}\n options={[\n { label: 'XS', value: 'XS', disabled: true, description: 'Sold out' },\n { label: 'S', value: 'S', description: 'Chest 34–36\"' },\n { label: 'M', value: 'M', description: 'Chest 38–40\"' },\n { label: 'L', value: 'L', description: 'Chest 42–44\"' },\n { label: 'XL', value: 'XL', description: 'Chest 46–48\"' },\n ]}\n error={missing ? 'Pick a size to add this to your bag' : undefined}\n helper={missing ? undefined : 'Runs true to size · free exchanges'}\n />\n <Pressable\n onPress={() => setTouched(true)}\n accessibilityRole=\"button\"\n style={({ pressed }) => [s.button, { backgroundColor: colors.primary, borderRadius: radius.md, opacity: pressed ? 0.88 : 1 }]}\n >\n <Text maxFontSizeMultiplier={FONT_CAP} style={[s.buttonText, { color: colors.primaryForeground }]}>Add to bag</Text>\n </Pressable>\n </Card>\n );\n}\n"
|
|
245
245
|
},
|
|
246
246
|
{
|
|
247
247
|
"name": "Plan",
|
|
248
248
|
"title": "Plan",
|
|
249
|
-
"code": "import { Pressable, StyleSheet, Text, View, type TextProps, type ViewProps } from 'react-native';\nimport Svg, { Circle, Path } from 'react-native-svg';\nimport { Select } from '@/components/ui/select';\nimport { alpha, readableOn, useKit } from '@/lib/kit';\n\n/** From the Expo Go viewer: `screen` = this IS the phone screen, so no mock frame; `bottomInset` = home indicator. */\ntype DemoProps = { screen?: boolean; bottomInset?: number };\n\n/** Demo card that follows the kit theme, so the docs radius / accent picker reshapes it too. */\nfunction Card({ screen, style, ...rest }: ViewProps & { screen?: boolean }) {\n const { colors, radius } = useKit();\n return (\n <View\n style={[\n screen\n ? { flex: 1, backgroundColor: colors.background, padding: 18, gap: 14 }\n : {\n backgroundColor: colors.card,\n borderColor: colors.border,\n borderWidth: StyleSheet.hairlineWidth,\n borderRadius: radius.xl,\n padding: 18,\n gap: 14,\n },\n style,\n ]}\n {...rest}\n />\n );\n}\n\n/** Text in the kit's ink: `tone=\"muted\"` for secondary copy. */\nfunction Ink({ tone, style, ...rest }: TextProps & { tone?: 'muted' }) {\n const { colors } = useKit();\n return <Text style={[{ color: tone === 'muted' ? colors.mutedForeground : colors.foreground }, style]} {...rest} />;\n}\n\nfunction Heading({ title, sub }: { title: string; sub?: string }) {\n return (\n <View style={s.heading}>\n <Ink style={s.title}>{title}</Ink>\n {sub ? (\n <Ink tone=\"muted\" style={s.sub}>\n {sub}\n </Ink>\n ) : null}\n </View>\n );\n}\n\n/** Small icon tile for option rows. */\nfunction Glyph({ d, tone }: { d: string; tone?: 'accent' | 'success' | 'warning' }) {\n const { colors, radius } = useKit();\n const c = tone ? colors[tone] : colors.mutedForeground;\n return (\n <View style={[s.glyph, { borderRadius: radius.scale(8), backgroundColor: tone ? alpha(c, 0.14) : colors.muted }]}>\n <Svg width={15} height={15} viewBox=\"0 0 24 24\">\n <Path d={d} stroke={c} strokeWidth={2} strokeLinecap=\"round\" strokeLinejoin=\"round\" fill=\"none\" />\n </Svg>\n </View>\n );\n}\n\nconst ICON = {\n leaf: 'M11 20A7 7 0 0 1 9.8 6.1C15.5 5 17 4.5 19 2c1 2 2 4.2 2 8 0 5.5-4.8 10-10 10ZM2 21c0-3 1.9-5.4 5.2-6',\n bolt: 'M13 2 3 14h9l-1 8 10-12h-9l1-8Z',\n users: 'M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2M9 11a4 4 0 1 0 0-8 4 4 0 0 0 0 8ZM22 21v-2a4 4 0 0 0-3-3.9M16 3.1a4 4 0 0 1 0 7.8',\n building: 'M6 22V4a2 2 0 0 1 2-2h8a2 2 0 0 1 2 2v18ZM6 12H4a2 2 0 0 0-2 2v8h4M18 9h2a2 2 0 0 1 2 2v11h-4M10 6h4M10 10h4M10 14h4M10 18h4',\n};\n\nconst flag = (f: string) => <Text style={s.flag}>{f}</Text>;\n\nconst avatar = (initials: string, color: string) => (\n <View style={[s.avatar, { backgroundColor: color }]}>\n <Text style={[s.initials, { color: readableOn(color) }]}>{initials}</Text>\n </View>\n);\n\nconst s = StyleSheet.create({\n heading: { gap: 3, marginBottom: 2 },\n title: { fontSize: 16, fontWeight: '400', letterSpacing: -0.2 },\n sub: { fontSize: 13, lineHeight: 18 },\n glyph: { width: 28, height: 28, alignItems: 'center', justifyContent: 'center' },\n flag: { fontSize: 18 },\n avatar: { width: 26, height: 26, borderRadius: 13, alignItems: 'center', justifyContent: 'center' },\n initials: { fontSize: 10.5, fontWeight: '400' },\n button: {
|
|
249
|
+
"code": "import { Pressable, StyleSheet, Text, View, type TextProps, type ViewProps } from 'react-native';\nimport Svg, { Circle, Path } from 'react-native-svg';\nimport { Select } from '@/components/ui/select';\nimport { alpha, readableOn, useKit } from '@/lib/kit';\n\n/** Controls stop at iOS's largest standard text size; content text stays uncapped. */\nconst FONT_CAP = 1.35;\n\n/** From the Expo Go viewer: `screen` = this IS the phone screen, so no mock frame; `bottomInset` = home indicator. */\ntype DemoProps = { screen?: boolean; bottomInset?: number };\n\n/** Demo card that follows the kit theme, so the docs radius / accent picker reshapes it too. */\nfunction Card({ screen, style, ...rest }: ViewProps & { screen?: boolean }) {\n const { colors, radius } = useKit();\n return (\n <View\n style={[\n screen\n ? { flex: 1, backgroundColor: colors.background, padding: 18, gap: 14 }\n : {\n backgroundColor: colors.card,\n borderColor: colors.border,\n borderWidth: StyleSheet.hairlineWidth,\n borderRadius: radius.xl,\n padding: 18,\n gap: 14,\n },\n style,\n ]}\n {...rest}\n />\n );\n}\n\n/** Text in the kit's ink: `tone=\"muted\"` for secondary copy. */\nfunction Ink({ tone, style, ...rest }: TextProps & { tone?: 'muted' }) {\n const { colors } = useKit();\n return <Text style={[{ color: tone === 'muted' ? colors.mutedForeground : colors.foreground }, style]} {...rest} />;\n}\n\nfunction Heading({ title, sub }: { title: string; sub?: string }) {\n return (\n <View style={s.heading}>\n <Ink style={s.title}>{title}</Ink>\n {sub ? (\n <Ink tone=\"muted\" style={s.sub}>\n {sub}\n </Ink>\n ) : null}\n </View>\n );\n}\n\n/** Small icon tile for option rows. */\nfunction Glyph({ d, tone }: { d: string; tone?: 'accent' | 'success' | 'warning' }) {\n const { colors, radius } = useKit();\n const c = tone ? colors[tone] : colors.mutedForeground;\n return (\n <View style={[s.glyph, { borderRadius: radius.scale(8), backgroundColor: tone ? alpha(c, 0.14) : colors.muted }]}>\n <Svg width={15} height={15} viewBox=\"0 0 24 24\">\n <Path d={d} stroke={c} strokeWidth={2} strokeLinecap=\"round\" strokeLinejoin=\"round\" fill=\"none\" />\n </Svg>\n </View>\n );\n}\n\nconst ICON = {\n leaf: 'M11 20A7 7 0 0 1 9.8 6.1C15.5 5 17 4.5 19 2c1 2 2 4.2 2 8 0 5.5-4.8 10-10 10ZM2 21c0-3 1.9-5.4 5.2-6',\n bolt: 'M13 2 3 14h9l-1 8 10-12h-9l1-8Z',\n users: 'M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2M9 11a4 4 0 1 0 0-8 4 4 0 0 0 0 8ZM22 21v-2a4 4 0 0 0-3-3.9M16 3.1a4 4 0 0 1 0 7.8',\n building: 'M6 22V4a2 2 0 0 1 2-2h8a2 2 0 0 1 2 2v18ZM6 12H4a2 2 0 0 0-2 2v8h4M18 9h2a2 2 0 0 1 2 2v11h-4M10 6h4M10 10h4M10 14h4M10 18h4',\n};\n\nconst flag = (f: string) => <Text maxFontSizeMultiplier={FONT_CAP} style={s.flag}>{f}</Text>;\n\nconst avatar = (initials: string, color: string) => (\n <View style={[s.avatar, { backgroundColor: color }]}>\n <Text maxFontSizeMultiplier={FONT_CAP} style={[s.initials, { color: readableOn(color) }]}>{initials}</Text>\n </View>\n);\n\nconst s = StyleSheet.create({\n heading: { gap: 3, marginBottom: 2 },\n title: { fontSize: 16, fontWeight: '400', letterSpacing: -0.2 },\n sub: { fontSize: 13, lineHeight: 18 },\n glyph: { width: 28, height: 28, alignItems: 'center', justifyContent: 'center' },\n flag: { fontSize: 18 },\n avatar: { width: 26, height: 26, borderRadius: 13, alignItems: 'center', justifyContent: 'center' },\n initials: { fontSize: 10.5, fontWeight: '400' },\n button: { minHeight: 44, alignItems: 'center', justifyContent: 'center', marginTop: 2 },\n buttonText: { fontSize: 15, fontWeight: '400' },\n});\n\nexport default function Plan({ screen }: DemoProps) {\n const { colors } = useKit();\n return (\n <Card screen={screen}>\n <Heading title=\"Workspace plan\" sub=\"Billed monthly · change anytime\" />\n <Select\n label=\"Plan\"\n floatingLabel={false}\n placeholder=\"Select a plan\"\n size=\"sm\"\n defaultValue=\"pro\"\n surface={colors.card}\n helper=\"You can switch plans later from billing.\"\n options={[\n { label: 'Hobby', value: 'hobby', description: 'Free · 1 project', icon: <Glyph d={ICON.leaf} tone=\"success\" /> },\n { label: 'Pro', value: 'pro', description: '₹499 / seat · unlimited projects', icon: <Glyph d={ICON.bolt} tone=\"accent\" /> },\n { label: 'Team', value: 'team', description: '₹1,499 / seat · SSO and audit log', icon: <Glyph d={ICON.users} tone=\"warning\" /> },\n { label: 'Enterprise', value: 'enterprise', description: 'Talk to sales', icon: <Glyph d={ICON.building} />, disabled: true },\n ]}\n />\n </Card>\n );\n}\n"
|
|
250
250
|
},
|
|
251
251
|
{
|
|
252
252
|
"name": "Country",
|
|
253
253
|
"title": "Country",
|
|
254
|
-
"code": "import { Pressable, StyleSheet, Text, View, type TextProps, type ViewProps } from 'react-native';\nimport { Select } from '@/components/ui/select';\nimport { alpha, readableOn, useKit } from '@/lib/kit';\n\n/** From the Expo Go viewer: `screen` = this IS the phone screen, so no mock frame; `bottomInset` = home indicator. */\ntype DemoProps = { screen?: boolean; bottomInset?: number };\n\n/** Demo card that follows the kit theme, so the docs radius / accent picker reshapes it too. */\nfunction Card({ screen, style, ...rest }: ViewProps & { screen?: boolean }) {\n const { colors, radius } = useKit();\n return (\n <View\n style={[\n screen\n ? { flex: 1, backgroundColor: colors.background, padding: 18, gap: 14 }\n : {\n backgroundColor: colors.card,\n borderColor: colors.border,\n borderWidth: StyleSheet.hairlineWidth,\n borderRadius: radius.xl,\n padding: 18,\n gap: 14,\n },\n style,\n ]}\n {...rest}\n />\n );\n}\n\n/** Text in the kit's ink: `tone=\"muted\"` for secondary copy. */\nfunction Ink({ tone, style, ...rest }: TextProps & { tone?: 'muted' }) {\n const { colors } = useKit();\n return <Text style={[{ color: tone === 'muted' ? colors.mutedForeground : colors.foreground }, style]} {...rest} />;\n}\n\nfunction Heading({ title, sub }: { title: string; sub?: string }) {\n return (\n <View style={s.heading}>\n <Ink style={s.title}>{title}</Ink>\n {sub ? (\n <Ink tone=\"muted\" style={s.sub}>\n {sub}\n </Ink>\n ) : null}\n </View>\n );\n}\n\nconst flag = (f: string) => <Text style={s.flag}>{f}</Text>;\n\nconst avatar = (initials: string, color: string) => (\n <View style={[s.avatar, { backgroundColor: color }]}>\n <Text style={[s.initials, { color: readableOn(color) }]}>{initials}</Text>\n </View>\n);\n\nconst s = StyleSheet.create({\n heading: { gap: 3, marginBottom: 2 },\n title: { fontSize: 16, fontWeight: '400', letterSpacing: -0.2 },\n sub: { fontSize: 13, lineHeight: 18 },\n glyph: { width: 28, height: 28, alignItems: 'center', justifyContent: 'center' },\n flag: { fontSize: 18 },\n avatar: { width: 26, height: 26, borderRadius: 13, alignItems: 'center', justifyContent: 'center' },\n initials: { fontSize: 10.5, fontWeight: '400' },\n button: {
|
|
254
|
+
"code": "import { Pressable, StyleSheet, Text, View, type TextProps, type ViewProps } from 'react-native';\nimport { Select } from '@/components/ui/select';\nimport { alpha, readableOn, useKit } from '@/lib/kit';\n\n/** Controls stop at iOS's largest standard text size; content text stays uncapped. */\nconst FONT_CAP = 1.35;\n\n/** From the Expo Go viewer: `screen` = this IS the phone screen, so no mock frame; `bottomInset` = home indicator. */\ntype DemoProps = { screen?: boolean; bottomInset?: number };\n\n/** Demo card that follows the kit theme, so the docs radius / accent picker reshapes it too. */\nfunction Card({ screen, style, ...rest }: ViewProps & { screen?: boolean }) {\n const { colors, radius } = useKit();\n return (\n <View\n style={[\n screen\n ? { flex: 1, backgroundColor: colors.background, padding: 18, gap: 14 }\n : {\n backgroundColor: colors.card,\n borderColor: colors.border,\n borderWidth: StyleSheet.hairlineWidth,\n borderRadius: radius.xl,\n padding: 18,\n gap: 14,\n },\n style,\n ]}\n {...rest}\n />\n );\n}\n\n/** Text in the kit's ink: `tone=\"muted\"` for secondary copy. */\nfunction Ink({ tone, style, ...rest }: TextProps & { tone?: 'muted' }) {\n const { colors } = useKit();\n return <Text style={[{ color: tone === 'muted' ? colors.mutedForeground : colors.foreground }, style]} {...rest} />;\n}\n\nfunction Heading({ title, sub }: { title: string; sub?: string }) {\n return (\n <View style={s.heading}>\n <Ink style={s.title}>{title}</Ink>\n {sub ? (\n <Ink tone=\"muted\" style={s.sub}>\n {sub}\n </Ink>\n ) : null}\n </View>\n );\n}\n\nconst flag = (f: string) => <Text maxFontSizeMultiplier={FONT_CAP} style={s.flag}>{f}</Text>;\n\nconst avatar = (initials: string, color: string) => (\n <View style={[s.avatar, { backgroundColor: color }]}>\n <Text maxFontSizeMultiplier={FONT_CAP} style={[s.initials, { color: readableOn(color) }]}>{initials}</Text>\n </View>\n);\n\nconst s = StyleSheet.create({\n heading: { gap: 3, marginBottom: 2 },\n title: { fontSize: 16, fontWeight: '400', letterSpacing: -0.2 },\n sub: { fontSize: 13, lineHeight: 18 },\n glyph: { width: 28, height: 28, alignItems: 'center', justifyContent: 'center' },\n flag: { fontSize: 18 },\n avatar: { width: 26, height: 26, borderRadius: 13, alignItems: 'center', justifyContent: 'center' },\n initials: { fontSize: 10.5, fontWeight: '400' },\n button: { minHeight: 44, alignItems: 'center', justifyContent: 'center', marginTop: 2 },\n buttonText: { fontSize: 15, fontWeight: '400' },\n});\n\nexport default function Country({ screen }: DemoProps) {\n const { colors } = useKit();\n return (\n <Card screen={screen}>\n <Heading title=\"Shipping address\" sub=\"Where should we send this order?\" />\n <Select\n searchable\n label=\"Country\"\n floatingLabel={false}\n placeholder=\"Select country\"\n searchPlaceholder=\"Search countries\"\n size=\"sm\"\n defaultValue=\"DE\"\n surface={colors.card}\n options={[\n { label: 'United States', value: 'US', icon: flag('🇺🇸'), group: 'Americas' },\n { label: 'Canada', value: 'CA', icon: flag('🇨🇦'), group: 'Americas' },\n { label: 'Brazil', value: 'BR', icon: flag('🇧🇷'), group: 'Americas' },\n { label: 'United Kingdom', value: 'GB', icon: flag('🇬🇧'), group: 'Europe' },\n { label: 'Germany', value: 'DE', icon: flag('🇩🇪'), group: 'Europe' },\n { label: 'France', value: 'FR', icon: flag('🇫🇷'), group: 'Europe' },\n { label: 'India', value: 'IN', icon: flag('🇮🇳'), group: 'Asia Pacific' },\n { label: 'Japan', value: 'JP', icon: flag('🇯🇵'), group: 'Asia Pacific' },\n { label: 'Australia', value: 'AU', icon: flag('🇦🇺'), group: 'Asia Pacific' },\n ]}\n />\n </Card>\n );\n}\n"
|
|
255
255
|
},
|
|
256
256
|
{
|
|
257
257
|
"name": "Assignees",
|
|
258
258
|
"title": "Assignees",
|
|
259
|
-
"code": "import React, { useState } from 'react';\nimport { Pressable, StyleSheet, Text, View, type TextProps, type ViewProps } from 'react-native';\nimport { Select } from '@/components/ui/select';\nimport { alpha, readableOn, useKit } from '@/lib/kit';\n\n/** From the Expo Go viewer: `screen` = this IS the phone screen, so no mock frame; `bottomInset` = home indicator. */\ntype DemoProps = { screen?: boolean; bottomInset?: number };\n\n/** Demo card that follows the kit theme, so the docs radius / accent picker reshapes it too. */\nfunction Card({ screen, style, ...rest }: ViewProps & { screen?: boolean }) {\n const { colors, radius } = useKit();\n return (\n <View\n style={[\n screen\n ? { flex: 1, backgroundColor: colors.background, padding: 18, gap: 14 }\n : {\n backgroundColor: colors.card,\n borderColor: colors.border,\n borderWidth: StyleSheet.hairlineWidth,\n borderRadius: radius.xl,\n padding: 18,\n gap: 14,\n },\n style,\n ]}\n {...rest}\n />\n );\n}\n\n/** Text in the kit's ink: `tone=\"muted\"` for secondary copy. */\nfunction Ink({ tone, style, ...rest }: TextProps & { tone?: 'muted' }) {\n const { colors } = useKit();\n return <Text style={[{ color: tone === 'muted' ? colors.mutedForeground : colors.foreground }, style]} {...rest} />;\n}\n\nfunction Heading({ title, sub }: { title: string; sub?: string }) {\n return (\n <View style={s.heading}>\n <Ink style={s.title}>{title}</Ink>\n {sub ? (\n <Ink tone=\"muted\" style={s.sub}>\n {sub}\n </Ink>\n ) : null}\n </View>\n );\n}\n\nconst flag = (f: string) => <Text style={s.flag}>{f}</Text>;\n\nconst avatar = (initials: string, color: string) => (\n <View style={[s.avatar, { backgroundColor: color }]}>\n <Text style={[s.initials, { color: readableOn(color) }]}>{initials}</Text>\n </View>\n);\n\nconst s = StyleSheet.create({\n heading: { gap: 3, marginBottom: 2 },\n title: { fontSize: 16, fontWeight: '400', letterSpacing: -0.2 },\n sub: { fontSize: 13, lineHeight: 18 },\n glyph: { width: 28, height: 28, alignItems: 'center', justifyContent: 'center' },\n flag: { fontSize: 18 },\n avatar: { width: 26, height: 26, borderRadius: 13, alignItems: 'center', justifyContent: 'center' },\n initials: { fontSize: 10.5, fontWeight: '400' },\n button: {
|
|
259
|
+
"code": "import React, { useState } from 'react';\nimport { Pressable, StyleSheet, Text, View, type TextProps, type ViewProps } from 'react-native';\nimport { Select } from '@/components/ui/select';\nimport { alpha, readableOn, useKit } from '@/lib/kit';\n\n/** Controls stop at iOS's largest standard text size; content text stays uncapped. */\nconst FONT_CAP = 1.35;\n\n/** From the Expo Go viewer: `screen` = this IS the phone screen, so no mock frame; `bottomInset` = home indicator. */\ntype DemoProps = { screen?: boolean; bottomInset?: number };\n\n/** Demo card that follows the kit theme, so the docs radius / accent picker reshapes it too. */\nfunction Card({ screen, style, ...rest }: ViewProps & { screen?: boolean }) {\n const { colors, radius } = useKit();\n return (\n <View\n style={[\n screen\n ? { flex: 1, backgroundColor: colors.background, padding: 18, gap: 14 }\n : {\n backgroundColor: colors.card,\n borderColor: colors.border,\n borderWidth: StyleSheet.hairlineWidth,\n borderRadius: radius.xl,\n padding: 18,\n gap: 14,\n },\n style,\n ]}\n {...rest}\n />\n );\n}\n\n/** Text in the kit's ink: `tone=\"muted\"` for secondary copy. */\nfunction Ink({ tone, style, ...rest }: TextProps & { tone?: 'muted' }) {\n const { colors } = useKit();\n return <Text style={[{ color: tone === 'muted' ? colors.mutedForeground : colors.foreground }, style]} {...rest} />;\n}\n\nfunction Heading({ title, sub }: { title: string; sub?: string }) {\n return (\n <View style={s.heading}>\n <Ink style={s.title}>{title}</Ink>\n {sub ? (\n <Ink tone=\"muted\" style={s.sub}>\n {sub}\n </Ink>\n ) : null}\n </View>\n );\n}\n\nconst flag = (f: string) => <Text maxFontSizeMultiplier={FONT_CAP} style={s.flag}>{f}</Text>;\n\nconst avatar = (initials: string, color: string) => (\n <View style={[s.avatar, { backgroundColor: color }]}>\n <Text maxFontSizeMultiplier={FONT_CAP} style={[s.initials, { color: readableOn(color) }]}>{initials}</Text>\n </View>\n);\n\nconst s = StyleSheet.create({\n heading: { gap: 3, marginBottom: 2 },\n title: { fontSize: 16, fontWeight: '400', letterSpacing: -0.2 },\n sub: { fontSize: 13, lineHeight: 18 },\n glyph: { width: 28, height: 28, alignItems: 'center', justifyContent: 'center' },\n flag: { fontSize: 18 },\n avatar: { width: 26, height: 26, borderRadius: 13, alignItems: 'center', justifyContent: 'center' },\n initials: { fontSize: 10.5, fontWeight: '400' },\n button: { minHeight: 44, alignItems: 'center', justifyContent: 'center', marginTop: 2 },\n buttonText: { fontSize: 15, fontWeight: '400' },\n});\n\nexport default function Assignees({ screen }: DemoProps) {\n const { colors } = useKit();\n const [people, setPeople] = useState(['maya', 'leo']);\n return (\n <Card screen={screen}>\n <Heading title=\"Fix checkout crash on Android 14\" sub=\"ENG-2142 · In progress\" />\n <Select\n multiple\n searchable\n label=\"Assignees\"\n floatingLabel={false}\n placeholder=\"Add teammates\"\n searchPlaceholder=\"Search teammates\"\n size=\"sm\"\n values={people}\n onValuesChange={setPeople}\n surface={colors.card}\n options={[\n { label: 'Maya Chen', value: 'maya', description: 'Design', icon: avatar('MC', '#f472b6') },\n { label: 'Leo Martins', value: 'leo', description: 'iOS', icon: avatar('LM', '#60a5fa') },\n { label: 'Ana Ruiz', value: 'ana', description: 'Android', icon: avatar('AR', '#34d399') },\n { label: 'Sam Okafor', value: 'sam', description: 'Backend', icon: avatar('SO', '#fbbf24') },\n { label: 'Priya Nair', value: 'priya', description: 'QA', icon: avatar('PN', '#a78bfa') },\n ]}\n />\n </Card>\n );\n}\n"
|
|
260
260
|
},
|
|
261
261
|
{
|
|
262
262
|
"name": "Sheet",
|
|
263
263
|
"title": "Sheet",
|
|
264
|
-
"code": "import { Pressable, StyleSheet, Text, View, type TextProps, type ViewProps } from 'react-native';\nimport { Select } from '@/components/ui/select';\nimport { alpha, readableOn, useKit } from '@/lib/kit';\n\n/** From the Expo Go viewer: `screen` = this IS the phone screen, so no mock frame; `bottomInset` = home indicator. */\ntype DemoProps = { screen?: boolean; bottomInset?: number };\n\n/** Demo card that follows the kit theme, so the docs radius / accent picker reshapes it too. */\nfunction Card({ screen, style, ...rest }: ViewProps & { screen?: boolean }) {\n const { colors, radius } = useKit();\n return (\n <View\n style={[\n screen\n ? { flex: 1, backgroundColor: colors.background, padding: 18, gap: 14 }\n : {\n backgroundColor: colors.card,\n borderColor: colors.border,\n borderWidth: StyleSheet.hairlineWidth,\n borderRadius: radius.xl,\n padding: 18,\n gap: 14,\n },\n style,\n ]}\n {...rest}\n />\n );\n}\n\n/** Text in the kit's ink: `tone=\"muted\"` for secondary copy. */\nfunction Ink({ tone, style, ...rest }: TextProps & { tone?: 'muted' }) {\n const { colors } = useKit();\n return <Text style={[{ color: tone === 'muted' ? colors.mutedForeground : colors.foreground }, style]} {...rest} />;\n}\n\nfunction Heading({ title, sub }: { title: string; sub?: string }) {\n return (\n <View style={s.heading}>\n <Ink style={s.title}>{title}</Ink>\n {sub ? (\n <Ink tone=\"muted\" style={s.sub}>\n {sub}\n </Ink>\n ) : null}\n </View>\n );\n}\n\nconst flag = (f: string) => <Text style={s.flag}>{f}</Text>;\n\nconst avatar = (initials: string, color: string) => (\n <View style={[s.avatar, { backgroundColor: color }]}>\n <Text style={[s.initials, { color: readableOn(color) }]}>{initials}</Text>\n </View>\n);\n\nconst s = StyleSheet.create({\n heading: { gap: 3, marginBottom: 2 },\n title: { fontSize: 16, fontWeight: '400', letterSpacing: -0.2 },\n sub: { fontSize: 13, lineHeight: 18 },\n glyph: { width: 28, height: 28, alignItems: 'center', justifyContent: 'center' },\n flag: { fontSize: 18 },\n avatar: { width: 26, height: 26, borderRadius: 13, alignItems: 'center', justifyContent: 'center' },\n initials: { fontSize: 10.5, fontWeight: '400' },\n button: {
|
|
264
|
+
"code": "import { Pressable, StyleSheet, Text, View, type TextProps, type ViewProps } from 'react-native';\nimport { Select } from '@/components/ui/select';\nimport { alpha, readableOn, useKit } from '@/lib/kit';\n\n/** Controls stop at iOS's largest standard text size; content text stays uncapped. */\nconst FONT_CAP = 1.35;\n\n/** From the Expo Go viewer: `screen` = this IS the phone screen, so no mock frame; `bottomInset` = home indicator. */\ntype DemoProps = { screen?: boolean; bottomInset?: number };\n\n/** Demo card that follows the kit theme, so the docs radius / accent picker reshapes it too. */\nfunction Card({ screen, style, ...rest }: ViewProps & { screen?: boolean }) {\n const { colors, radius } = useKit();\n return (\n <View\n style={[\n screen\n ? { flex: 1, backgroundColor: colors.background, padding: 18, gap: 14 }\n : {\n backgroundColor: colors.card,\n borderColor: colors.border,\n borderWidth: StyleSheet.hairlineWidth,\n borderRadius: radius.xl,\n padding: 18,\n gap: 14,\n },\n style,\n ]}\n {...rest}\n />\n );\n}\n\n/** Text in the kit's ink: `tone=\"muted\"` for secondary copy. */\nfunction Ink({ tone, style, ...rest }: TextProps & { tone?: 'muted' }) {\n const { colors } = useKit();\n return <Text style={[{ color: tone === 'muted' ? colors.mutedForeground : colors.foreground }, style]} {...rest} />;\n}\n\nfunction Heading({ title, sub }: { title: string; sub?: string }) {\n return (\n <View style={s.heading}>\n <Ink style={s.title}>{title}</Ink>\n {sub ? (\n <Ink tone=\"muted\" style={s.sub}>\n {sub}\n </Ink>\n ) : null}\n </View>\n );\n}\n\nconst flag = (f: string) => <Text maxFontSizeMultiplier={FONT_CAP} style={s.flag}>{f}</Text>;\n\nconst avatar = (initials: string, color: string) => (\n <View style={[s.avatar, { backgroundColor: color }]}>\n <Text maxFontSizeMultiplier={FONT_CAP} style={[s.initials, { color: readableOn(color) }]}>{initials}</Text>\n </View>\n);\n\nconst s = StyleSheet.create({\n heading: { gap: 3, marginBottom: 2 },\n title: { fontSize: 16, fontWeight: '400', letterSpacing: -0.2 },\n sub: { fontSize: 13, lineHeight: 18 },\n glyph: { width: 28, height: 28, alignItems: 'center', justifyContent: 'center' },\n flag: { fontSize: 18 },\n avatar: { width: 26, height: 26, borderRadius: 13, alignItems: 'center', justifyContent: 'center' },\n initials: { fontSize: 10.5, fontWeight: '400' },\n button: { minHeight: 44, alignItems: 'center', justifyContent: 'center', marginTop: 2 },\n buttonText: { fontSize: 15, fontWeight: '400' },\n});\n\nexport default function Sheet({ screen }: DemoProps) {\n const { colors } = useKit();\n return (\n <Card screen={screen}>\n <Heading title=\"Notifications\" sub=\"Opens as a sheet instead of a dropdown\" />\n <Select\n presentation=\"sheet\"\n label=\"Email digest\"\n floatingLabel={false}\n placeholder=\"How often?\"\n title=\"How often?\"\n size=\"sm\"\n defaultValue=\"weekly\"\n surface={colors.card}\n options={[\n { label: 'Daily', value: 'daily', description: 'Every morning at 8:00' },\n { label: 'Weekly', value: 'weekly', description: 'Mondays at 8:00' },\n { label: 'Monthly', value: 'monthly', description: 'First of the month' },\n { label: 'Never', value: 'never', description: 'Only security alerts' },\n ]}\n />\n </Card>\n );\n}\n"
|
|
265
265
|
},
|
|
266
266
|
{
|
|
267
267
|
"name": "Sizes",
|
|
@@ -271,7 +271,7 @@
|
|
|
271
271
|
{
|
|
272
272
|
"name": "Colors",
|
|
273
273
|
"title": "Colors",
|
|
274
|
-
"code": "import { Pressable, StyleSheet, Text, View, type TextProps, type ViewProps } from 'react-native';\nimport Svg, { Circle, Path } from 'react-native-svg';\nimport { Select } from '@/components/ui/select';\nimport { alpha, readableOn, useKit } from '@/lib/kit';\n\n/** From the Expo Go viewer: `screen` = this IS the phone screen, so no mock frame; `bottomInset` = home indicator. */\ntype DemoProps = { screen?: boolean; bottomInset?: number };\n\n/** Demo card that follows the kit theme, so the docs radius / accent picker reshapes it too. */\nfunction Card({ screen, style, ...rest }: ViewProps & { screen?: boolean }) {\n const { colors, radius } = useKit();\n return (\n <View\n style={[\n screen\n ? { flex: 1, backgroundColor: colors.background, padding: 18, gap: 14 }\n : {\n backgroundColor: colors.card,\n borderColor: colors.border,\n borderWidth: StyleSheet.hairlineWidth,\n borderRadius: radius.xl,\n padding: 18,\n gap: 14,\n },\n style,\n ]}\n {...rest}\n />\n );\n}\n\n/** Text in the kit's ink: `tone=\"muted\"` for secondary copy. */\nfunction Ink({ tone, style, ...rest }: TextProps & { tone?: 'muted' }) {\n const { colors } = useKit();\n return <Text style={[{ color: tone === 'muted' ? colors.mutedForeground : colors.foreground }, style]} {...rest} />;\n}\n\nfunction Heading({ title, sub }: { title: string; sub?: string }) {\n return (\n <View style={s.heading}>\n <Ink style={s.title}>{title}</Ink>\n {sub ? (\n <Ink tone=\"muted\" style={s.sub}>\n {sub}\n </Ink>\n ) : null}\n </View>\n );\n}\n\nconst flag = (f: string) => <Text style={s.flag}>{f}</Text>;\n\nconst avatar = (initials: string, color: string) => (\n <View style={[s.avatar, { backgroundColor: color }]}>\n <Text style={[s.initials, { color: readableOn(color) }]}>{initials}</Text>\n </View>\n);\n\nfunction Dot({ color }: { color: string }) {\n return (\n <Svg width={14} height={14} viewBox=\"0 0 14 14\">\n <Circle cx={7} cy={7} r={5} fill={color} />\n </Svg>\n );\n}\n\nconst s = StyleSheet.create({\n heading: { gap: 3, marginBottom: 2 },\n title: { fontSize: 16, fontWeight: '400', letterSpacing: -0.2 },\n sub: { fontSize: 13, lineHeight: 18 },\n glyph: { width: 28, height: 28, alignItems: 'center', justifyContent: 'center' },\n flag: { fontSize: 18 },\n avatar: { width: 26, height: 26, borderRadius: 13, alignItems: 'center', justifyContent: 'center' },\n initials: { fontSize: 10.5, fontWeight: '400' },\n button: {
|
|
274
|
+
"code": "import { Pressable, StyleSheet, Text, View, type TextProps, type ViewProps } from 'react-native';\nimport Svg, { Circle, Path } from 'react-native-svg';\nimport { Select } from '@/components/ui/select';\nimport { alpha, readableOn, useKit } from '@/lib/kit';\n\n/** Controls stop at iOS's largest standard text size; content text stays uncapped. */\nconst FONT_CAP = 1.35;\n\n/** From the Expo Go viewer: `screen` = this IS the phone screen, so no mock frame; `bottomInset` = home indicator. */\ntype DemoProps = { screen?: boolean; bottomInset?: number };\n\n/** Demo card that follows the kit theme, so the docs radius / accent picker reshapes it too. */\nfunction Card({ screen, style, ...rest }: ViewProps & { screen?: boolean }) {\n const { colors, radius } = useKit();\n return (\n <View\n style={[\n screen\n ? { flex: 1, backgroundColor: colors.background, padding: 18, gap: 14 }\n : {\n backgroundColor: colors.card,\n borderColor: colors.border,\n borderWidth: StyleSheet.hairlineWidth,\n borderRadius: radius.xl,\n padding: 18,\n gap: 14,\n },\n style,\n ]}\n {...rest}\n />\n );\n}\n\n/** Text in the kit's ink: `tone=\"muted\"` for secondary copy. */\nfunction Ink({ tone, style, ...rest }: TextProps & { tone?: 'muted' }) {\n const { colors } = useKit();\n return <Text style={[{ color: tone === 'muted' ? colors.mutedForeground : colors.foreground }, style]} {...rest} />;\n}\n\nfunction Heading({ title, sub }: { title: string; sub?: string }) {\n return (\n <View style={s.heading}>\n <Ink style={s.title}>{title}</Ink>\n {sub ? (\n <Ink tone=\"muted\" style={s.sub}>\n {sub}\n </Ink>\n ) : null}\n </View>\n );\n}\n\nconst flag = (f: string) => <Text maxFontSizeMultiplier={FONT_CAP} style={s.flag}>{f}</Text>;\n\nconst avatar = (initials: string, color: string) => (\n <View style={[s.avatar, { backgroundColor: color }]}>\n <Text maxFontSizeMultiplier={FONT_CAP} style={[s.initials, { color: readableOn(color) }]}>{initials}</Text>\n </View>\n);\n\nfunction Dot({ color }: { color: string }) {\n return (\n <Svg width={14} height={14} viewBox=\"0 0 14 14\">\n <Circle cx={7} cy={7} r={5} fill={color} />\n </Svg>\n );\n}\n\nconst s = StyleSheet.create({\n heading: { gap: 3, marginBottom: 2 },\n title: { fontSize: 16, fontWeight: '400', letterSpacing: -0.2 },\n sub: { fontSize: 13, lineHeight: 18 },\n glyph: { width: 28, height: 28, alignItems: 'center', justifyContent: 'center' },\n flag: { fontSize: 18 },\n avatar: { width: 26, height: 26, borderRadius: 13, alignItems: 'center', justifyContent: 'center' },\n initials: { fontSize: 10.5, fontWeight: '400' },\n button: { minHeight: 44, alignItems: 'center', justifyContent: 'center', marginTop: 2 },\n buttonText: { fontSize: 15, fontWeight: '400' },\n});\n\nexport default function Colors({ screen }: DemoProps) {\n const { colors } = useKit();\n return (\n <Card screen={screen}>\n <Heading title=\"Project labels\" sub=\"Accent colour drives focus, checks and chips\" />\n <Select\n multiple\n label=\"Labels\"\n floatingLabel={false}\n placeholder=\"Add labels\"\n size=\"sm\"\n accent={colors.success}\n defaultValues={['frontend', 'perf']}\n surface={colors.card}\n options={[\n { label: 'Frontend', value: 'frontend' },\n { label: 'Performance', value: 'perf' },\n { label: 'Accessibility', value: 'a11y' },\n { label: 'Docs', value: 'docs' },\n ]}\n />\n <Select\n label=\"Priority\"\n floatingLabel={false}\n placeholder=\"Set priority\"\n size=\"sm\"\n accent={colors.warning}\n defaultValue=\"p1\"\n surface={colors.card}\n options={[\n { label: 'Urgent', value: 'p0', icon: <Dot color={colors.danger} /> },\n { label: 'High', value: 'p1', icon: <Dot color={colors.warning} /> },\n { label: 'Medium', value: 'p2', icon: <Dot color={colors.accent} /> },\n { label: 'Low', value: 'p3', icon: <Dot color={colors.mutedForeground} /> },\n ]}\n />\n </Card>\n );\n}\n"
|
|
275
275
|
}
|
|
276
276
|
]
|
|
277
277
|
}
|