@expokit/cli 0.3.0 → 0.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +8 -4
- package/bin/expo-kit.js +137 -24
- package/bin/lib.mjs +110 -1
- package/bin/mcp.mjs +40 -18
- package/package.json +2 -2
- package/registry/_docs/accordion.json +1 -1
- package/registry/_docs/assistant-app.json +47 -0
- package/registry/_docs/avatar.json +6 -6
- package/registry/_docs/badge.json +4 -4
- 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 +3 -3
- package/registry/_docs/button.json +2 -2
- package/registry/_docs/calendar.json +5 -5
- package/registry/_docs/card.json +1 -1
- package/registry/_docs/checkout.json +7 -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/donut-shop.json +88 -0
- package/registry/_docs/drawer.json +4 -4
- package/registry/_docs/fintech-app.json +49 -0
- package/registry/_docs/flip-card.json +4 -4
- package/registry/_docs/floating-input.json +5 -5
- package/registry/_docs/food-app.json +47 -0
- package/registry/_docs/glass.json +1 -1
- package/registry/_docs/grain.json +80 -0
- package/registry/_docs/isometric-bar.json +6 -0
- package/registry/_docs/liquidglass-tab.json +1 -1
- package/registry/_docs/morphing-button-group.json +96 -0
- package/registry/_docs/orb-reveal.json +132 -0
- package/registry/_docs/otp-input.json +4 -4
- package/registry/_docs/password-input.json +3 -3
- 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/qr-code-button.json +122 -0
- package/registry/_docs/radio-group.json +1 -1
- package/registry/_docs/search-bar.json +4 -4
- package/registry/_docs/segmented-control.json +1 -1
- package/registry/_docs/select.json +7 -7
- package/registry/_docs/settings.json +6 -0
- package/registry/_docs/skeleton.json +3 -3
- package/registry/_docs/slider.json +6 -6
- package/registry/_docs/spinner.json +3 -3
- package/registry/_docs/stateful-button.json +1 -1
- package/registry/_docs/stepper.json +7 -7
- package/registry/_docs/tabs.json +2 -2
- package/registry/_docs/text-animation.json +4 -4
- package/registry/_docs/textarea.json +3 -3
- package/registry/_docs/ticket-shape.json +1 -1
- package/registry/_docs/tilt-card.json +4 -4
- package/registry/_docs/time-picker.json +6 -6
- package/registry/_docs/timeline-chart.json +6 -0
- package/registry/_docs/timeline.json +6 -6
- package/registry/_docs/toast.json +8 -8
- package/registry/_docs/treemap.json +6 -0
- package/registry/_docs/waterfall.json +6 -0
- package/registry/accordion/files/src/components/ui/accordion.tsx +36 -7
- package/registry/accordion/files/src/lib/kit.tsx +8 -5
- package/registry/action-sheet/files/src/components/ui/action-sheet.tsx +12 -8
- package/registry/action-sheet/files/src/lib/kit.tsx +8 -5
- package/registry/alert/files/src/components/ui/alert.tsx +11 -5
- package/registry/alert/files/src/components/ui/button.tsx +35 -6
- package/registry/alert/files/src/lib/kit.tsx +8 -5
- package/registry/assistant-app.json +49 -0
- package/registry/avatar/files/src/components/ui/avatar.tsx +7 -4
- package/registry/avatar/files/src/lib/kit.tsx +8 -5
- package/registry/badge/files/src/components/ui/badge.tsx +23 -9
- package/registry/badge/files/src/lib/kit.tsx +8 -5
- package/registry/bottom-nav/files/src/components/ui/bottom-nav.tsx +21 -8
- package/registry/bottom-nav/files/src/lib/kit.tsx +8 -5
- package/registry/bottom-sheet/files/src/components/ui/bottom-sheet.tsx +7 -6
- package/registry/bottom-sheet/files/src/lib/kit.tsx +8 -5
- package/registry/button/files/src/components/ui/button.tsx +35 -6
- package/registry/button/files/src/lib/kit.tsx +8 -5
- package/registry/button-3d/files/src/components/ui/button-3d.tsx +35 -7
- package/registry/button-3d/files/src/lib/kit.tsx +8 -5
- package/registry/calendar/files/src/components/ui/calendar.tsx +68 -19
- package/registry/calendar/files/src/lib/kit.tsx +8 -5
- package/registry/card/files/src/components/ui/card.tsx +32 -10
- package/registry/card/files/src/lib/kit.tsx +8 -5
- package/registry/checkbox/files/src/components/ui/checkbox.tsx +31 -8
- package/registry/checkbox/files/src/lib/kit.tsx +8 -5
- package/registry/chip-group/files/src/components/ui/chip-group.tsx +39 -7
- package/registry/chip-group/files/src/lib/kit.tsx +8 -5
- package/registry/confetti/files/src/lib/kit.tsx +8 -5
- package/registry/coupon/files/src/components/ui/coupon.tsx +55 -28
- package/registry/coupon/files/src/components/ui/flip-card.tsx +23 -4
- package/registry/coupon/files/src/components/ui/tilt-card.tsx +59 -18
- package/registry/coupon/files/src/lib/kit.tsx +8 -5
- package/registry/date-picker/files/src/components/ui/calendar.tsx +68 -19
- package/registry/date-picker/files/src/components/ui/date-picker.tsx +98 -44
- package/registry/date-picker/files/src/lib/kit.tsx +8 -5
- package/registry/dialog/files/src/components/ui/dialog.tsx +19 -9
- package/registry/dialog/files/src/lib/kit.tsx +8 -5
- package/registry/donut-shop.json +18 -0
- package/registry/drawer/files/src/components/ui/drawer.tsx +31 -21
- package/registry/drawer/files/src/lib/kit.tsx +8 -5
- package/registry/fintech-app.json +57 -0
- package/registry/flip-card/files/src/components/ui/flip-card.tsx +23 -4
- package/registry/flip-card/files/src/lib/kit.tsx +8 -5
- package/registry/floating-input/files/src/components/ui/floating-input.tsx +91 -32
- package/registry/floating-input/files/src/lib/kit.tsx +8 -5
- package/registry/food-app.json +55 -0
- package/registry/grain/files/src/components/ui/grain.tsx +113 -0
- package/registry/{tag-input → grain}/files/src/lib/kit.tsx +8 -5
- package/registry/grain.json +17 -0
- package/registry/kit/files/src/lib/kit.tsx +8 -5
- package/registry/liquidglass-tab/files/src/components/GlassTabBar.tsx +2 -2
- package/registry/liquidglass-tab/files/src/lib/tab-bar.ts +1 -1
- package/registry/list-item/files/src/components/ui/list-item.tsx +28 -17
- package/registry/list-item/files/src/lib/kit.tsx +8 -5
- package/registry/morphing-button-group.json +18 -0
- package/registry/orb-reveal.json +20 -0
- package/registry/otp-input/files/src/components/ui/otp-input.tsx +57 -24
- package/registry/otp-input/files/src/lib/kit.tsx +8 -5
- package/registry/password-input/files/src/components/ui/floating-input.tsx +91 -32
- package/registry/password-input/files/src/components/ui/password-input.tsx +55 -21
- package/registry/password-input/files/src/lib/kit.tsx +8 -5
- package/registry/payment-card/files/src/components/ui/flip-card.tsx +23 -4
- package/registry/payment-card/files/src/components/ui/payment-card.tsx +35 -18
- package/registry/payment-card/files/src/components/ui/tilt-card.tsx +59 -18
- package/registry/payment-card/files/src/lib/kit.tsx +8 -5
- package/registry/progress/files/src/components/ui/progress.tsx +16 -6
- package/registry/progress/files/src/lib/kit.tsx +8 -5
- package/registry/progress-steps/files/src/components/ui/progress-steps.tsx +22 -11
- package/registry/progress-steps/files/src/lib/kit.tsx +8 -5
- package/registry/qr-code-button.json +18 -0
- package/registry/radio-group/files/src/components/ui/radio-group.tsx +48 -17
- package/registry/radio-group/files/src/lib/kit.tsx +8 -5
- package/registry/search-bar/files/src/components/ui/search-bar.tsx +81 -26
- package/registry/search-bar/files/src/lib/kit.tsx +8 -5
- package/registry/segmented-control/files/src/components/ui/segmented-control.tsx +40 -10
- package/registry/segmented-control/files/src/lib/kit.tsx +8 -5
- package/registry/select/files/src/components/ui/select.tsx +161 -86
- package/registry/select/files/src/lib/kit.tsx +8 -5
- package/registry/skeleton/files/src/components/ui/skeleton.tsx +4 -4
- package/registry/skeleton/files/src/lib/kit.tsx +8 -5
- package/registry/slider/files/src/components/ui/slider.tsx +56 -27
- package/registry/slider/files/src/lib/kit.tsx +8 -5
- package/registry/spinner/files/src/components/ui/spinner.tsx +17 -12
- package/registry/spinner/files/src/lib/kit.tsx +8 -5
- package/registry/stateful-button/files/src/components/ui/stateful-button.tsx +12 -4
- package/registry/stateful-button/files/src/lib/kit.tsx +8 -5
- package/registry/stepper/files/src/components/ui/stepper.tsx +83 -29
- package/registry/stepper/files/src/lib/kit.tsx +8 -5
- package/registry/switch/files/src/components/ui/switch.tsx +60 -22
- package/registry/switch/files/src/lib/kit.tsx +8 -5
- package/registry/tabs/files/src/components/ui/tabs.tsx +15 -10
- package/registry/tabs/files/src/lib/kit.tsx +8 -5
- package/registry/text-animation/files/src/components/ui/text-animation.tsx +11 -6
- package/registry/text-animation/files/src/lib/kit.tsx +8 -5
- package/registry/textarea/files/src/components/ui/textarea.tsx +115 -45
- package/registry/textarea/files/src/lib/kit.tsx +8 -5
- package/registry/ticket-shape/files/src/lib/kit.tsx +8 -5
- package/registry/tilt-card/files/src/components/ui/tilt-card.tsx +59 -18
- package/registry/tilt-card/files/src/lib/kit.tsx +8 -5
- package/registry/time-picker/files/src/components/ui/time-picker.tsx +156 -75
- package/registry/time-picker/files/src/lib/kit.tsx +8 -5
- package/registry/timeline/files/src/components/ui/avatar.tsx +7 -4
- package/registry/timeline/files/src/components/ui/timeline.tsx +19 -12
- package/registry/timeline/files/src/lib/kit.tsx +8 -5
- package/registry/toast/files/src/components/ui/button.tsx +35 -6
- package/registry/toast/files/src/components/ui/toast.tsx +25 -14
- package/registry/toast/files/src/lib/kit.tsx +8 -5
- package/schema/components.schema.json +1 -1
- package/registry/_docs/color-swatch.json +0 -82
- package/registry/_docs/kpi-card.json +0 -14
- package/registry/_docs/like-button.json +0 -92
- package/registry/_docs/morph-button.json +0 -128
- package/registry/_docs/pagination-dots.json +0 -108
- package/registry/_docs/shimmer-button.json +0 -98
- package/registry/_docs/tag-input.json +0 -148
- package/registry/color-swatch.json +0 -17
- package/registry/kpi-card/files/src/components/charts/kpi-card.tsx +0 -45
- package/registry/kpi-card.json +0 -14
- package/registry/like-button.json +0 -18
- package/registry/morph-button.json +0 -18
- package/registry/pagination-dots/files/src/components/ui/pagination-dots.tsx +0 -262
- package/registry/pagination-dots/files/src/lib/kit.tsx +0 -219
- package/registry/pagination-dots.json +0 -15
- package/registry/shimmer-button.json +0 -17
- package/registry/tag-input/files/src/components/ui/tag-input.tsx +0 -720
- package/registry/tag-input.json +0 -17
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "food-app",
|
|
3
|
+
"category": "Templates",
|
|
4
|
+
"aliases": [],
|
|
5
|
+
"exportName": null,
|
|
6
|
+
"files": [
|
|
7
|
+
"src/app/(tabs)/_layout.tsx",
|
|
8
|
+
"src/app/(tabs)/bag.tsx",
|
|
9
|
+
"src/app/(tabs)/index.tsx",
|
|
10
|
+
"src/app/(tabs)/offers.tsx",
|
|
11
|
+
"src/app/(tabs)/profile.tsx",
|
|
12
|
+
"src/app/_layout.tsx",
|
|
13
|
+
"src/app/onboarding.tsx",
|
|
14
|
+
"src/app/settings.tsx",
|
|
15
|
+
"src/app/sign-in.tsx",
|
|
16
|
+
"src/components/food-app/tabs.tsx",
|
|
17
|
+
"src/lib/food-app/cart.tsx",
|
|
18
|
+
"src/lib/food-app/data.tsx",
|
|
19
|
+
"src/lib/food-app/session.tsx"
|
|
20
|
+
],
|
|
21
|
+
"summary": null,
|
|
22
|
+
"props": [],
|
|
23
|
+
"behavior": [
|
|
24
|
+
[
|
|
25
|
+
"Flow",
|
|
26
|
+
"Onboarding shows once, then sign in, then the tabs. Signing out returns to sign in. The session is saved on the device (AsyncStorage) and guarded with Expo Router’s Stack.Protected."
|
|
27
|
+
],
|
|
28
|
+
[
|
|
29
|
+
"Tabs",
|
|
30
|
+
"Menu (rolling donut shelf), Offers (coupons that unlock with the bag total), Bag (checkout) and Profile. The kit’s BottomNav is the tab bar, with the bag count as a badge."
|
|
31
|
+
],
|
|
32
|
+
[
|
|
33
|
+
"Bag",
|
|
34
|
+
"Donuts added on the Menu land in the bag; a coupon picked on Offers is applied at checkout. Pay is a stand-in — wire it to your payment sheet."
|
|
35
|
+
],
|
|
36
|
+
[
|
|
37
|
+
"Settings",
|
|
38
|
+
"Pushed from Profile. Appearance (System / Light / Dark) re-themes every kit component instantly."
|
|
39
|
+
],
|
|
40
|
+
[
|
|
41
|
+
"Your backend",
|
|
42
|
+
"Auth handlers in app/sign-in.tsx and payment in app/(tabs)/bag.tsx are stand-ins that wait and succeed. Menu, coupons and the user live in lib/food-app/data.tsx."
|
|
43
|
+
]
|
|
44
|
+
],
|
|
45
|
+
"usage": null,
|
|
46
|
+
"examples": []
|
|
47
|
+
}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "grain",
|
|
3
|
+
"category": "Display",
|
|
4
|
+
"aliases": [],
|
|
5
|
+
"exportName": "GrainGradient",
|
|
6
|
+
"files": [
|
|
7
|
+
"src/components/ui/grain.tsx"
|
|
8
|
+
],
|
|
9
|
+
"summary": "A soft blob gradient with real film grain, plus a grain overlay for anything.",
|
|
10
|
+
"props": [
|
|
11
|
+
[
|
|
12
|
+
"palette",
|
|
13
|
+
"sunset | ocean | aurora | dusk | peach | graphite | ember | mint | lavender | candy | forest | noir",
|
|
14
|
+
"\"sunset\"",
|
|
15
|
+
"Built-in colour set."
|
|
16
|
+
],
|
|
17
|
+
[
|
|
18
|
+
"colors",
|
|
19
|
+
"string[]",
|
|
20
|
+
"—",
|
|
21
|
+
"Base colour first, then up to four blob colours. Overrides palette."
|
|
22
|
+
],
|
|
23
|
+
[
|
|
24
|
+
"grain",
|
|
25
|
+
"number",
|
|
26
|
+
"0.55",
|
|
27
|
+
"Grain strength, 0–1."
|
|
28
|
+
],
|
|
29
|
+
[
|
|
30
|
+
"seed",
|
|
31
|
+
"number",
|
|
32
|
+
"0",
|
|
33
|
+
"Shuffles where the blobs sit."
|
|
34
|
+
],
|
|
35
|
+
[
|
|
36
|
+
"style",
|
|
37
|
+
"StyleProp<ViewStyle>",
|
|
38
|
+
"—",
|
|
39
|
+
"Root style; give it a size or flex: 1."
|
|
40
|
+
],
|
|
41
|
+
[
|
|
42
|
+
"GrainOverlay grain",
|
|
43
|
+
"number",
|
|
44
|
+
"0.55",
|
|
45
|
+
"Grain only, laid over its parent."
|
|
46
|
+
]
|
|
47
|
+
],
|
|
48
|
+
"behavior": [
|
|
49
|
+
[
|
|
50
|
+
"Gradient",
|
|
51
|
+
"A base colour with up to four radial blobs, drawn with react-native-svg so it scales to any size."
|
|
52
|
+
],
|
|
53
|
+
[
|
|
54
|
+
"Grain",
|
|
55
|
+
"A tiny tiling noise image drawn at 2×, so the speckle stays fine on every screen; grain sets its strength."
|
|
56
|
+
],
|
|
57
|
+
[
|
|
58
|
+
"Touch",
|
|
59
|
+
"The gradient and grain never take touches; children render on top."
|
|
60
|
+
]
|
|
61
|
+
],
|
|
62
|
+
"usage": "import { GrainGradient, GrainOverlay } from '@/components/ui/grain';\n\n<GrainGradient palette=\"aurora\" style={{ flex: 1 }}>\n {/* your screen */}\n</GrainGradient>",
|
|
63
|
+
"examples": [
|
|
64
|
+
{
|
|
65
|
+
"name": "Hero",
|
|
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/** 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
|
+
},
|
|
69
|
+
{
|
|
70
|
+
"name": "Palettes",
|
|
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/** 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
|
+
},
|
|
74
|
+
{
|
|
75
|
+
"name": "Custom",
|
|
76
|
+
"title": "Custom",
|
|
77
|
+
"code": "import { 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 Custom({ screen }: DemoProps) {\n const { radius } = useKit();\n return (\n <GrainGradient colors={['#0c0a1f', '#ff4d6d', '#ffd166', '#06d6a0']} grain={0.9} seed={2} style={screen ? { flex: 1 } : { height: 400, borderRadius: radius.xl }} />\n );\n}\n"
|
|
78
|
+
}
|
|
79
|
+
]
|
|
80
|
+
}
|
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
{
|
|
25
25
|
"name": "Preview",
|
|
26
26
|
"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"
|
|
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\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
28
|
}
|
|
29
29
|
]
|
|
30
30
|
}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "morphing-button-group",
|
|
3
|
+
"category": "Buttons",
|
|
4
|
+
"aliases": [],
|
|
5
|
+
"exportName": "MorphingButtonGroup",
|
|
6
|
+
"files": [
|
|
7
|
+
"src/components/ui/morphing-button-group.tsx"
|
|
8
|
+
],
|
|
9
|
+
"summary": "Buttons that split, join and stretch between sets — with a signature sheet built on it.",
|
|
10
|
+
"props": [
|
|
11
|
+
[
|
|
12
|
+
"buttons",
|
|
13
|
+
"{ key, label, icon?, variant?, onPress? }[]",
|
|
14
|
+
"—",
|
|
15
|
+
"MorphingButtonGroup: the buttons to show now; changing it morphs the row."
|
|
16
|
+
],
|
|
17
|
+
[
|
|
18
|
+
"height",
|
|
19
|
+
"number",
|
|
20
|
+
"44",
|
|
21
|
+
"Button height."
|
|
22
|
+
],
|
|
23
|
+
[
|
|
24
|
+
"gap",
|
|
25
|
+
"number",
|
|
26
|
+
"10",
|
|
27
|
+
"Space between buttons."
|
|
28
|
+
],
|
|
29
|
+
[
|
|
30
|
+
"duration",
|
|
31
|
+
"number",
|
|
32
|
+
"380",
|
|
33
|
+
"Morph length in ms."
|
|
34
|
+
],
|
|
35
|
+
[
|
|
36
|
+
"SignatureSheet label",
|
|
37
|
+
"string",
|
|
38
|
+
"'Sign'",
|
|
39
|
+
"The pill's text."
|
|
40
|
+
],
|
|
41
|
+
[
|
|
42
|
+
"SignatureSheet title",
|
|
43
|
+
"string",
|
|
44
|
+
"'Add Signature'",
|
|
45
|
+
"Card title."
|
|
46
|
+
],
|
|
47
|
+
[
|
|
48
|
+
"SignatureSheet onConfirm",
|
|
49
|
+
"({ paths, width, height }) => void",
|
|
50
|
+
"—",
|
|
51
|
+
"The finished signature."
|
|
52
|
+
],
|
|
53
|
+
[
|
|
54
|
+
"SignatureSheet onCancel",
|
|
55
|
+
"() => void",
|
|
56
|
+
"—",
|
|
57
|
+
"Closed without signing."
|
|
58
|
+
],
|
|
59
|
+
[
|
|
60
|
+
"SignatureSheet cardHeight",
|
|
61
|
+
"number",
|
|
62
|
+
"340",
|
|
63
|
+
"Height of the open card."
|
|
64
|
+
],
|
|
65
|
+
[
|
|
66
|
+
"style",
|
|
67
|
+
"StyleProp<ViewStyle>",
|
|
68
|
+
"—",
|
|
69
|
+
"Root style."
|
|
70
|
+
]
|
|
71
|
+
],
|
|
72
|
+
"behavior": [
|
|
73
|
+
[
|
|
74
|
+
"Morph",
|
|
75
|
+
"Buttons with the same key keep their place and resize; new ones grow in from nothing and leaving ones shrink away, on one smooth ease (no bounce)."
|
|
76
|
+
],
|
|
77
|
+
[
|
|
78
|
+
"Labels",
|
|
79
|
+
"A label change cross-fades while the button resizes."
|
|
80
|
+
],
|
|
81
|
+
[
|
|
82
|
+
"Signature",
|
|
83
|
+
"The pad draws smoothed strokes; undo removes the last one, Reset clears, Confirm asks once more. onConfirm gets SVG path data and the pad size."
|
|
84
|
+
],
|
|
85
|
+
[
|
|
86
|
+
"Haptics",
|
|
87
|
+
"A tick on each button, a light tap opening the sheet, success on confirm."
|
|
88
|
+
],
|
|
89
|
+
[
|
|
90
|
+
"Reduced motion",
|
|
91
|
+
"Morphs are instant."
|
|
92
|
+
]
|
|
93
|
+
],
|
|
94
|
+
"usage": null,
|
|
95
|
+
"examples": []
|
|
96
|
+
}
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "orb-reveal",
|
|
3
|
+
"category": "Motion",
|
|
4
|
+
"aliases": [],
|
|
5
|
+
"exportName": "OrbReveal",
|
|
6
|
+
"files": [
|
|
7
|
+
"src/components/ui/orb-reveal.tsx"
|
|
8
|
+
],
|
|
9
|
+
"summary": "Drag a liquid-glass orb up: it shows your title through the lens, then fountains glass marbles into a heart.",
|
|
10
|
+
"props": [
|
|
11
|
+
[
|
|
12
|
+
"headline",
|
|
13
|
+
"string",
|
|
14
|
+
"'A new era of…'",
|
|
15
|
+
"Copy shown before the reveal."
|
|
16
|
+
],
|
|
17
|
+
[
|
|
18
|
+
"title",
|
|
19
|
+
"string",
|
|
20
|
+
"'Meet Wabi.'",
|
|
21
|
+
"The line the lens uncovers."
|
|
22
|
+
],
|
|
23
|
+
[
|
|
24
|
+
"subtitle",
|
|
25
|
+
"string",
|
|
26
|
+
"'The first personal…'",
|
|
27
|
+
"Lines under the title."
|
|
28
|
+
],
|
|
29
|
+
[
|
|
30
|
+
"hint",
|
|
31
|
+
"string",
|
|
32
|
+
"'Swipe up to enter'",
|
|
33
|
+
"Faint hint under the resting orb."
|
|
34
|
+
],
|
|
35
|
+
[
|
|
36
|
+
"images",
|
|
37
|
+
"string[]",
|
|
38
|
+
"—",
|
|
39
|
+
"Image URIs for the marbles (app icons, avatars)."
|
|
40
|
+
],
|
|
41
|
+
[
|
|
42
|
+
"emoji",
|
|
43
|
+
"string[]",
|
|
44
|
+
"24 emoji",
|
|
45
|
+
"Marble pictures when there are no images, each on a bright gradient."
|
|
46
|
+
],
|
|
47
|
+
[
|
|
48
|
+
"count",
|
|
49
|
+
"number",
|
|
50
|
+
"52",
|
|
51
|
+
"Marbles in the heart (max 60), plus a trail of specks."
|
|
52
|
+
],
|
|
53
|
+
[
|
|
54
|
+
"glowColors",
|
|
55
|
+
"[string, string]",
|
|
56
|
+
"blue, violet",
|
|
57
|
+
"The glow while you hold the orb."
|
|
58
|
+
],
|
|
59
|
+
[
|
|
60
|
+
"background",
|
|
61
|
+
"string",
|
|
62
|
+
"theme background",
|
|
63
|
+
"Page colour."
|
|
64
|
+
],
|
|
65
|
+
[
|
|
66
|
+
"footer",
|
|
67
|
+
"ReactNode",
|
|
68
|
+
"—",
|
|
69
|
+
"Fades in after the reveal: sign-in buttons, a CTA."
|
|
70
|
+
],
|
|
71
|
+
[
|
|
72
|
+
"revealed",
|
|
73
|
+
"boolean",
|
|
74
|
+
"—",
|
|
75
|
+
"Controlled state."
|
|
76
|
+
],
|
|
77
|
+
[
|
|
78
|
+
"defaultRevealed",
|
|
79
|
+
"boolean",
|
|
80
|
+
"false",
|
|
81
|
+
"Uncontrolled initial state."
|
|
82
|
+
],
|
|
83
|
+
[
|
|
84
|
+
"onRevealChange",
|
|
85
|
+
"(revealed: boolean) => void",
|
|
86
|
+
"—",
|
|
87
|
+
"The orb settled in a new state."
|
|
88
|
+
],
|
|
89
|
+
[
|
|
90
|
+
"style",
|
|
91
|
+
"StyleProp<ViewStyle>",
|
|
92
|
+
"flex: 1",
|
|
93
|
+
"Root style; give it a screen or a tall card."
|
|
94
|
+
]
|
|
95
|
+
],
|
|
96
|
+
"behavior": [
|
|
97
|
+
[
|
|
98
|
+
"Hold",
|
|
99
|
+
"The orb swells and lifts, a blue-violet smile of light glows through it and the headline fades."
|
|
100
|
+
],
|
|
101
|
+
[
|
|
102
|
+
"Drag",
|
|
103
|
+
"Drag up past halfway (or flick) to reveal; drag the small orb down to go back. A quick tap toggles; a hold or a drag back to the start stays put."
|
|
104
|
+
],
|
|
105
|
+
[
|
|
106
|
+
"Lens",
|
|
107
|
+
"Two Skia shaders refract the page under the orb and the title the lens uncovers: magnified middle, mirrored bevel, red/blue split at the edge."
|
|
108
|
+
],
|
|
109
|
+
[
|
|
110
|
+
"Marbles",
|
|
111
|
+
"Fountain out of the button on arcs, pop to size and settle into a heart, then drift. Going back down, they float off the top."
|
|
112
|
+
],
|
|
113
|
+
[
|
|
114
|
+
"Copy",
|
|
115
|
+
"The title rides with the orb. It and the footer appear as the orb settles and stay until it is most of the way back down."
|
|
116
|
+
],
|
|
117
|
+
[
|
|
118
|
+
"Haptics",
|
|
119
|
+
"A tick when you pick the orb up and a medium impact on reveal."
|
|
120
|
+
],
|
|
121
|
+
[
|
|
122
|
+
"Reduced motion",
|
|
123
|
+
"The orb snaps between states and the marbles appear in place."
|
|
124
|
+
],
|
|
125
|
+
[
|
|
126
|
+
"Web",
|
|
127
|
+
"Needs CanvasKit loaded (LoadSkiaWeb); without it the revealed copy and footer are shown."
|
|
128
|
+
]
|
|
129
|
+
],
|
|
130
|
+
"usage": null,
|
|
131
|
+
"examples": []
|
|
132
|
+
}
|
|
@@ -132,22 +132,22 @@
|
|
|
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/** Demo card that follows the kit theme, so the docs radius / accent picker reshapes it too. */\nfunction Card({ style, ...rest }: ViewProps) {\n const { colors, radius } = useKit();\n return (\n <View\n style={[\n { padding: 16, gap: 12, 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() {\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>\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",
|
|
139
139
|
"title": "Pin",
|
|
140
|
-
"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/** Demo card that follows the kit theme, so the docs radius / accent picker reshapes it too. */\nfunction Card({ style, ...rest }: ViewProps) {\n const { colors, radius } = useKit();\n return (\n <View\n style={[\n { padding: 16, gap: 12, 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 Pin() {\n const [pin, setPin] = useState('');\n return (\n <Card>\n <Header title=\"Enter your PIN\" body=\"Confirm the transfer of ₹2,400 to Ananya\" />\n <OtpInput variant=\"pill\" length={4} secure value={pin} onChangeText={setPin} maxCellSize={56} />\n </Card>\n );\n}\n"
|
|
140
|
+
"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 Pin({ screen }: DemoProps) {\n const [pin, setPin] = useState('');\n return (\n <Card screen={screen}>\n <Header title=\"Enter your PIN\" body=\"Confirm the transfer of ₹2,400 to Ananya\" />\n <OtpInput variant=\"pill\" length={4} secure value={pin} onChangeText={setPin} maxCellSize={56} />\n </Card>\n );\n}\n"
|
|
141
141
|
},
|
|
142
142
|
{
|
|
143
143
|
"name": "Underline",
|
|
144
144
|
"title": "Underline",
|
|
145
|
-
"code": "import { Pressable, StyleSheet, Text, View, type ViewProps } from 'react-native';\nimport { OtpInput } from '@/components/ui/otp-input';\nimport { useKit } from '@/lib/kit';\n\n/** Demo card that follows the kit theme, so the docs radius / accent picker reshapes it too. */\nfunction Card({ style, ...rest }: ViewProps) {\n const { colors, radius } = useKit();\n return (\n <View\n style={[\n { padding: 16, gap: 12, 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 Underline() {\n return (\n <Card>\n <Header title=\"Check your inbox\" body=\"We emailed a 6-digit code to karan@…\" />\n <OtpInput variant=\"underline\" helperText=\"Code expires in 10 minutes\" />\n </Card>\n );\n}\n"
|
|
145
|
+
"code": "import { 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 Underline({ screen }: DemoProps) {\n return (\n <Card screen={screen}>\n <Header title=\"Check your inbox\" body=\"We emailed a 6-digit code to karan@…\" />\n <OtpInput variant=\"underline\" helperText=\"Code expires in 10 minutes\" />\n </Card>\n );\n}\n"
|
|
146
146
|
},
|
|
147
147
|
{
|
|
148
148
|
"name": "WithSeparator",
|
|
149
149
|
"title": "With Separator",
|
|
150
|
-
"code": "import { Pressable, StyleSheet, Text, View, type ViewProps } from 'react-native';\nimport { OtpInput } from '@/components/ui/otp-input';\nimport { useKit } from '@/lib/kit';\n\n/** Demo card that follows the kit theme, so the docs radius / accent picker reshapes it too. */\nfunction Card({ style, ...rest }: ViewProps) {\n const { colors, radius } = useKit();\n return (\n <View\n style={[\n { padding: 16, gap: 12, 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 WithSeparator() {\n return (\n <Card>\n <Header title=\"Two-factor authentication\" body=\"Open your authenticator app\" />\n <OtpInput separatorAfter={3} maxCellSize={46} />\n </Card>\n );\n}\n"
|
|
150
|
+
"code": "import { 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 WithSeparator({ screen }: DemoProps) {\n return (\n <Card screen={screen}>\n <Header title=\"Two-factor authentication\" body=\"Open your authenticator app\" />\n <OtpInput separatorAfter={3} maxCellSize={46} />\n </Card>\n );\n}\n"
|
|
151
151
|
},
|
|
152
152
|
{
|
|
153
153
|
"name": "States",
|
|
@@ -86,12 +86,12 @@
|
|
|
86
86
|
{
|
|
87
87
|
"name": "CreatePassword",
|
|
88
88
|
"title": "Create Password",
|
|
89
|
-
"code": "import React, { useState } from 'react';\nimport { StyleSheet, Text, View } from 'react-native';\nimport { PasswordInput } from '@/components/ui/password-input';\nimport { useKit } from '@/lib/kit';\n\nfunction Card({ title, subtitle, children }: { title: string; subtitle?: string; children: React.ReactNode }) {\n const { colors, radius } = useKit();\n return (\n <View\n style={
|
|
89
|
+
"code": "import React, { useState } from 'react';\nimport { StyleSheet, Text, View } from 'react-native';\nimport { PasswordInput } from '@/components/ui/password-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\nfunction Card({ title, subtitle, screen, children }: { title: string; subtitle?: string; screen?: boolean; children: React.ReactNode }) {\n const { colors, radius } = useKit();\n return (\n <View\n style={[\n { padding: 16, gap: 12 },\n screen\n ? { flex: 1, backgroundColor: colors.background }\n : {\n borderRadius: radius.xl,\n borderWidth: StyleSheet.hairlineWidth,\n borderColor: colors.border,\n backgroundColor: colors.card,\n },\n ]}\n >\n <View style={{ gap: 2, marginBottom: 2 }}>\n <Text style={{ fontSize: 16, fontWeight: '400', color: colors.foreground }}>{title}</Text>\n {subtitle ? <Text style={{ fontSize: 13, color: colors.mutedForeground }}>{subtitle}</Text> : null}\n </View>\n {children}\n </View>\n );\n}\n\nexport default function CreatePassword({ screen }: DemoProps) {\n const [password, setPassword] = useState('Sunset');\n const [confirm, setConfirm] = useState('');\n return (\n <Card screen={screen} title=\"Choose a password\" subtitle=\"You'll use it to sign in on new devices.\">\n <PasswordInput label=\"New password\" value={password} onChangeText={setPassword} requirements />\n <PasswordInput label=\"Confirm password\" value={confirm} onChangeText={setConfirm} confirmOf={password} />\n </Card>\n );\n}\n"
|
|
90
90
|
},
|
|
91
91
|
{
|
|
92
92
|
"name": "SignIn",
|
|
93
93
|
"title": "Sign In",
|
|
94
|
-
"code": "import React, { useState } from 'react';\nimport { StyleSheet, Text, View } from 'react-native';\nimport { FloatingInput } from '@/components/ui/floating-input';\nimport { PasswordInput } from '@/components/ui/password-input';\nimport { useKit } from '@/lib/kit';\n\nfunction Card({ title, subtitle, children }: { title: string; subtitle?: string; children: React.ReactNode }) {\n const { colors, radius } = useKit();\n return (\n <View\n style={
|
|
94
|
+
"code": "import React, { useState } from 'react';\nimport { StyleSheet, Text, View } from 'react-native';\nimport { FloatingInput } from '@/components/ui/floating-input';\nimport { PasswordInput } from '@/components/ui/password-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\nfunction Card({ title, subtitle, screen, children }: { title: string; subtitle?: string; screen?: boolean; children: React.ReactNode }) {\n const { colors, radius } = useKit();\n return (\n <View\n style={[\n { padding: 16, gap: 12 },\n screen\n ? { flex: 1, backgroundColor: colors.background }\n : {\n borderRadius: radius.xl,\n borderWidth: StyleSheet.hairlineWidth,\n borderColor: colors.border,\n backgroundColor: colors.card,\n },\n ]}\n >\n <View style={{ gap: 2, marginBottom: 2 }}>\n <Text style={{ fontSize: 16, fontWeight: '400', color: colors.foreground }}>{title}</Text>\n {subtitle ? <Text style={{ fontSize: 13, color: colors.mutedForeground }}>{subtitle}</Text> : null}\n </View>\n {children}\n </View>\n );\n}\n\nexport default function SignIn({ screen }: DemoProps) {\n const { colors } = useKit();\n return (\n <Card screen={screen} title=\"Welcome back\">\n <FloatingInput label=\"Email\" defaultValue=\"maya@studio.io\" keyboardType=\"email-address\" autoCapitalize=\"none\" />\n <PasswordInput label=\"Password\" strength={false} defaultValue=\"correct horse\" />\n <Text style={{ alignSelf: 'flex-end', fontSize: 13, fontWeight: '400', color: colors.accent }}>Forgot password?</Text>\n </Card>\n );\n}\n"
|
|
95
95
|
},
|
|
96
96
|
{
|
|
97
97
|
"name": "StrengthMeter",
|
|
@@ -101,7 +101,7 @@
|
|
|
101
101
|
{
|
|
102
102
|
"name": "CustomRules",
|
|
103
103
|
"title": "Custom Rules",
|
|
104
|
-
"code": "import React, { useState } from 'react';\nimport { StyleSheet, Text, View } from 'react-native';\nimport { PasswordInput } from '@/components/ui/password-input';\nimport { useKit } from '@/lib/kit';\n\nfunction Card({ title, subtitle, children }: { title: string; subtitle?: string; children: React.ReactNode }) {\n const { colors, radius } = useKit();\n return (\n <View\n style={
|
|
104
|
+
"code": "import React, { useState } from 'react';\nimport { StyleSheet, Text, View } from 'react-native';\nimport { PasswordInput } from '@/components/ui/password-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\nfunction Card({ title, subtitle, screen, children }: { title: string; subtitle?: string; screen?: boolean; children: React.ReactNode }) {\n const { colors, radius } = useKit();\n return (\n <View\n style={[\n { padding: 16, gap: 12 },\n screen\n ? { flex: 1, backgroundColor: colors.background }\n : {\n borderRadius: radius.xl,\n borderWidth: StyleSheet.hairlineWidth,\n borderColor: colors.border,\n backgroundColor: colors.card,\n },\n ]}\n >\n <View style={{ gap: 2, marginBottom: 2 }}>\n <Text style={{ fontSize: 16, fontWeight: '400', color: colors.foreground }}>{title}</Text>\n {subtitle ? <Text style={{ fontSize: 13, color: colors.mutedForeground }}>{subtitle}</Text> : null}\n </View>\n {children}\n </View>\n );\n}\n\nexport default function CustomRules({ screen }: DemoProps) {\n return (\n <Card screen={screen} title=\"Wi-Fi password\" subtitle=\"Your router needs 12+ characters and no spaces.\">\n <PasswordInput\n variant=\"filled\"\n label=\"Network password\"\n strength={false}\n defaultValue=\"cafe latte\"\n requirements={[\n { label: '12+ characters', test: (v) => v.length >= 12 },\n { label: 'No spaces', test: (v) => v.length > 0 && !/\\s/.test(v) },\n ]}\n />\n </Card>\n );\n}\n"
|
|
105
105
|
},
|
|
106
106
|
{
|
|
107
107
|
"name": "WrongPassword",
|
|
@@ -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
|
}
|