@expokit/cli 0.2.0 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +20 -2
- package/bin/expo-kit.js +219 -44
- package/bin/lib.mjs +110 -1
- package/bin/mcp.mjs +48 -19
- package/package.json +9 -4
- package/registry/_docs/accordion.json +16 -0
- package/registry/_docs/action-sheet.json +28 -0
- package/registry/_docs/alert.json +28 -0
- package/registry/_docs/assistant-app.json +47 -0
- package/registry/_docs/avatar.json +25 -1
- package/registry/_docs/badge.json +36 -5
- package/registry/_docs/bottom-nav.json +30 -6
- package/registry/_docs/bottom-sheet.json +28 -0
- package/registry/_docs/bullet.json +3 -0
- package/registry/_docs/button-3d.json +18 -2
- package/registry/_docs/button.json +16 -0
- package/registry/_docs/calendar.json +21 -5
- package/registry/_docs/card-stack.json +16 -0
- package/registry/_docs/card.json +20 -0
- package/registry/_docs/carousel.json +20 -0
- package/registry/_docs/chat.json +184 -0
- package/registry/_docs/checkbox.json +16 -0
- package/registry/_docs/checkout.json +10 -1
- package/registry/_docs/chip-group.json +16 -0
- package/registry/_docs/confetti.json +23 -3
- package/registry/_docs/coupon.json +16 -0
- package/registry/_docs/cube-pager.json +16 -0
- package/registry/_docs/dashboard.json +3 -0
- package/registry/_docs/date-picker.json +24 -4
- package/registry/_docs/dialog.json +24 -0
- package/registry/_docs/donut-shop.json +88 -0
- package/registry/_docs/drawer.json +32 -4
- package/registry/_docs/fab.json +24 -0
- package/registry/_docs/fintech-app.json +49 -0
- package/registry/_docs/flip-card.json +16 -0
- package/registry/_docs/floating-input.json +25 -5
- package/registry/_docs/food-app.json +47 -0
- package/registry/_docs/gift-card.json +16 -0
- package/registry/_docs/glass-button.json +24 -0
- package/registry/_docs/glass-nav-bar.json +24 -0
- package/registry/_docs/glass-search-bar.json +24 -0
- package/registry/_docs/glass-segmented.json +28 -0
- package/registry/_docs/glass-sheet.json +28 -0
- package/registry/_docs/glass-tab-bar.json +24 -0
- package/registry/_docs/glass.json +17 -1
- package/registry/_docs/grain.json +80 -0
- package/registry/_docs/inbox.json +3 -0
- package/registry/_docs/isometric-bar.json +3 -0
- package/registry/_docs/kit.json +3 -0
- package/registry/_docs/liquidglass-tab.json +3 -0
- package/registry/_docs/list-item.json +16 -0
- package/registry/_docs/loyalty-card.json +16 -0
- package/registry/_docs/morphing-button-group.json +96 -0
- package/registry/_docs/number-ticker.json +16 -0
- package/registry/_docs/offers.json +3 -0
- package/registry/_docs/onboarding.json +3 -0
- package/registry/_docs/orb-reveal.json +132 -0
- package/registry/_docs/otp-input.json +28 -4
- package/registry/_docs/parallax-layers.json +20 -0
- package/registry/_docs/parallax-scroll.json +24 -0
- package/registry/_docs/password-input.json +23 -3
- package/registry/_docs/payment-card.json +16 -0
- package/registry/_docs/pixel-bar.json +3 -0
- package/registry/_docs/polar-area.json +3 -0
- package/registry/_docs/profile.json +3 -0
- package/registry/_docs/progress-steps.json +24 -0
- package/registry/_docs/progress.json +20 -0
- package/registry/_docs/qr-code-button.json +122 -0
- package/registry/_docs/radio-group.json +20 -0
- package/registry/_docs/rating.json +24 -0
- package/registry/_docs/ring-carousel.json +20 -0
- package/registry/_docs/scratch-card.json +20 -0
- package/registry/_docs/search-bar.json +23 -3
- package/registry/_docs/search-results.json +3 -0
- package/registry/_docs/segmented-control.json +20 -0
- package/registry/_docs/select.json +33 -8
- package/registry/_docs/settings.json +9 -0
- package/registry/_docs/sign-in.json +3 -0
- package/registry/_docs/skeleton.json +22 -2
- package/registry/_docs/slide-to-confirm.json +24 -0
- package/registry/_docs/slider.json +27 -7
- package/registry/_docs/spin-wheel.json +24 -0
- package/registry/_docs/spinner.json +41 -11
- package/registry/_docs/stateful-button.json +184 -0
- package/registry/_docs/stepper.json +23 -7
- package/registry/_docs/swipe-deck.json +16 -0
- package/registry/_docs/swipeable-row.json +24 -1
- package/registry/_docs/switch.json +20 -0
- package/registry/_docs/tabs.json +24 -0
- package/registry/_docs/text-animation.json +141 -0
- package/registry/_docs/textarea.json +18 -2
- package/registry/_docs/ticket-card.json +20 -0
- package/registry/_docs/ticket-shape.json +4 -1
- package/registry/_docs/tilt-card.json +20 -4
- package/registry/_docs/time-picker.json +22 -6
- package/registry/_docs/timeline-chart.json +3 -0
- package/registry/_docs/timeline.json +18 -2
- package/registry/_docs/toast.json +32 -8
- package/registry/_docs/treemap.json +3 -0
- package/registry/_docs/waterfall.json +3 -0
- package/registry/_docs/wheel-list.json +20 -0
- package/registry/accordion/files/src/components/ui/accordion.tsx +5 -4
- package/registry/accordion/files/src/lib/kit.tsx +8 -5
- package/registry/action-sheet/files/src/components/ui/action-sheet.tsx +9 -8
- package/registry/action-sheet/files/src/lib/kit.tsx +8 -5
- package/registry/alert/files/src/components/ui/alert.tsx +7 -4
- package/registry/alert/files/src/components/ui/button.tsx +4 -3
- package/registry/alert/files/src/lib/kit.tsx +8 -5
- package/registry/assistant-app.json +49 -0
- package/registry/avatar/files/src/components/ui/avatar.tsx +7 -4
- package/registry/avatar/files/src/lib/kit.tsx +8 -5
- package/registry/badge/files/src/components/ui/badge.tsx +345 -21
- package/registry/badge/files/src/lib/kit.tsx +8 -5
- package/registry/badge.json +1 -1
- package/registry/bottom-nav/files/src/components/ui/bottom-nav.tsx +14 -7
- package/registry/bottom-nav/files/src/lib/kit.tsx +8 -5
- package/registry/bottom-sheet/files/src/components/ui/bottom-sheet.tsx +7 -6
- package/registry/bottom-sheet/files/src/lib/kit.tsx +8 -5
- package/registry/button/files/src/components/ui/button.tsx +4 -3
- package/registry/button/files/src/lib/kit.tsx +8 -5
- package/registry/button-3d/files/src/components/ui/button-3d.tsx +3 -2
- package/registry/button-3d/files/src/lib/kit.tsx +8 -5
- package/registry/calendar/files/src/components/ui/calendar.tsx +9 -5
- package/registry/calendar/files/src/lib/kit.tsx +8 -5
- package/registry/card/files/src/components/ui/card.tsx +8 -6
- package/registry/card/files/src/lib/kit.tsx +8 -5
- package/registry/chat.json +18 -0
- package/registry/checkbox/files/src/components/ui/checkbox.tsx +6 -5
- package/registry/checkbox/files/src/lib/kit.tsx +8 -5
- package/registry/chip-group/files/src/components/ui/chip-group.tsx +4 -2
- package/registry/chip-group/files/src/lib/kit.tsx +8 -5
- package/registry/confetti/files/src/lib/kit.tsx +8 -5
- package/registry/coupon/files/src/components/ui/coupon.tsx +23 -20
- package/registry/coupon/files/src/components/ui/flip-card.tsx +1 -1
- package/registry/coupon/files/src/components/ui/tilt-card.tsx +2 -2
- package/registry/coupon/files/src/lib/kit.tsx +8 -5
- package/registry/date-picker/files/src/components/ui/calendar.tsx +9 -5
- package/registry/date-picker/files/src/components/ui/date-picker.tsx +2 -2
- package/registry/date-picker/files/src/lib/kit.tsx +8 -5
- package/registry/dialog/files/src/components/ui/dialog.tsx +12 -6
- package/registry/dialog/files/src/lib/kit.tsx +8 -5
- package/registry/donut-shop.json +18 -0
- package/registry/drawer/files/src/components/ui/drawer.tsx +25 -19
- package/registry/drawer/files/src/lib/kit.tsx +8 -5
- package/registry/fintech-app.json +57 -0
- package/registry/flip-card/files/src/components/ui/flip-card.tsx +1 -1
- package/registry/flip-card/files/src/lib/kit.tsx +8 -5
- package/registry/floating-input/files/src/components/ui/floating-input.tsx +16 -6
- package/registry/floating-input/files/src/lib/kit.tsx +8 -5
- package/registry/food-app.json +55 -0
- package/registry/grain/files/src/components/ui/grain.tsx +113 -0
- package/registry/{tag-input → grain}/files/src/lib/kit.tsx +8 -5
- package/registry/grain.json +17 -0
- package/registry/kit/files/src/lib/kit.tsx +8 -5
- package/registry/liquidglass-tab/files/src/components/GlassTabBar.tsx +1 -1
- package/registry/liquidglass-tab/files/src/lib/tab-bar.ts +1 -1
- package/registry/list-item/files/src/components/ui/list-item.tsx +20 -16
- package/registry/list-item/files/src/lib/kit.tsx +8 -5
- package/registry/morphing-button-group.json +18 -0
- package/registry/orb-reveal.json +20 -0
- package/registry/otp-input/files/src/components/ui/otp-input.tsx +8 -6
- package/registry/otp-input/files/src/lib/kit.tsx +8 -5
- package/registry/password-input/files/src/components/ui/floating-input.tsx +16 -6
- package/registry/password-input/files/src/components/ui/password-input.tsx +2 -2
- package/registry/password-input/files/src/lib/kit.tsx +8 -5
- package/registry/payment-card/files/src/components/ui/flip-card.tsx +1 -1
- package/registry/payment-card/files/src/components/ui/payment-card.tsx +27 -14
- package/registry/payment-card/files/src/components/ui/tilt-card.tsx +2 -2
- package/registry/payment-card/files/src/lib/kit.tsx +8 -5
- package/registry/progress/files/src/components/ui/progress.tsx +8 -5
- package/registry/progress/files/src/lib/kit.tsx +8 -5
- package/registry/progress-steps/files/src/components/ui/progress-steps.tsx +15 -11
- package/registry/progress-steps/files/src/lib/kit.tsx +8 -5
- package/registry/qr-code-button.json +18 -0
- package/registry/radio-group/files/src/components/ui/radio-group.tsx +10 -9
- package/registry/radio-group/files/src/lib/kit.tsx +8 -5
- package/registry/search-bar/files/src/components/ui/search-bar.tsx +13 -11
- package/registry/search-bar/files/src/lib/kit.tsx +8 -5
- package/registry/segmented-control/files/src/components/ui/segmented-control.tsx +4 -2
- package/registry/segmented-control/files/src/lib/kit.tsx +8 -5
- package/registry/select/files/src/components/ui/select.tsx +3 -3
- package/registry/select/files/src/lib/kit.tsx +8 -5
- package/registry/skeleton/files/src/components/ui/skeleton.tsx +4 -4
- package/registry/skeleton/files/src/lib/kit.tsx +8 -5
- package/registry/slider/files/src/components/ui/slider.tsx +5 -4
- package/registry/slider/files/src/lib/kit.tsx +8 -5
- package/registry/spinner/files/src/components/ui/spinner.tsx +452 -34
- package/registry/spinner/files/src/lib/kit.tsx +8 -5
- package/registry/spinner.json +1 -1
- package/registry/stateful-button/files/src/components/ui/stateful-button.tsx +532 -0
- package/registry/{pagination-dots → stateful-button}/files/src/lib/kit.tsx +8 -5
- package/registry/stateful-button.json +18 -0
- package/registry/stepper/files/src/components/ui/stepper.tsx +8 -4
- package/registry/stepper/files/src/lib/kit.tsx +8 -5
- package/registry/switch/files/src/components/ui/switch.tsx +4 -3
- package/registry/switch/files/src/lib/kit.tsx +8 -5
- package/registry/tabs/files/src/components/ui/tabs.tsx +10 -8
- package/registry/tabs/files/src/lib/kit.tsx +8 -5
- package/registry/text-animation/files/src/components/ui/text-animation.tsx +650 -0
- package/registry/text-animation/files/src/lib/kit.tsx +222 -0
- package/registry/text-animation.json +15 -0
- package/registry/textarea/files/src/components/ui/textarea.tsx +11 -9
- package/registry/textarea/files/src/lib/kit.tsx +8 -5
- package/registry/ticket-shape/files/src/lib/kit.tsx +8 -5
- package/registry/tilt-card/files/src/components/ui/tilt-card.tsx +2 -2
- package/registry/tilt-card/files/src/lib/kit.tsx +8 -5
- package/registry/time-picker/files/src/components/ui/time-picker.tsx +2 -2
- package/registry/time-picker/files/src/lib/kit.tsx +8 -5
- package/registry/timeline/files/src/components/ui/avatar.tsx +7 -4
- package/registry/timeline/files/src/components/ui/timeline.tsx +12 -10
- package/registry/timeline/files/src/lib/kit.tsx +8 -5
- package/registry/toast/files/src/components/ui/button.tsx +4 -3
- package/registry/toast/files/src/components/ui/toast.tsx +13 -11
- package/registry/toast/files/src/lib/kit.tsx +8 -5
- package/schema/components.schema.json +1 -1
- package/registry/_docs/color-swatch.json +0 -66
- package/registry/_docs/kpi-card.json +0 -11
- package/registry/_docs/like-button.json +0 -72
- package/registry/_docs/morph-button.json +0 -108
- package/registry/_docs/pagination-dots.json +0 -88
- package/registry/_docs/shimmer-button.json +0 -78
- package/registry/_docs/tag-input.json +0 -128
- package/registry/color-swatch.json +0 -17
- package/registry/kpi-card/files/src/components/charts/kpi-card.tsx +0 -45
- package/registry/kpi-card.json +0 -14
- package/registry/like-button.json +0 -18
- package/registry/morph-button.json +0 -18
- package/registry/pagination-dots/files/src/components/ui/pagination-dots.tsx +0 -262
- package/registry/pagination-dots.json +0 -15
- package/registry/shimmer-button.json +0 -17
- package/registry/tag-input/files/src/components/ui/tag-input.tsx +0 -720
- package/registry/tag-input.json +0 -17
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "text-animation",
|
|
3
|
+
"category": "Motion",
|
|
4
|
+
"aliases": [
|
|
5
|
+
"text-effect",
|
|
6
|
+
"animated-text",
|
|
7
|
+
"typewriter",
|
|
8
|
+
"shimmer-text",
|
|
9
|
+
"scramble-text"
|
|
10
|
+
],
|
|
11
|
+
"exportName": "AnimatedText",
|
|
12
|
+
"files": [
|
|
13
|
+
"src/components/ui/text-animation.tsx"
|
|
14
|
+
],
|
|
15
|
+
"summary": "Reveal, shimmer, cascade, scramble and typewriter text effects.",
|
|
16
|
+
"props": [
|
|
17
|
+
[
|
|
18
|
+
"children",
|
|
19
|
+
"string",
|
|
20
|
+
"—",
|
|
21
|
+
"The text to animate."
|
|
22
|
+
],
|
|
23
|
+
[
|
|
24
|
+
"variant",
|
|
25
|
+
"\"reveal\" | \"shimmer\" | \"cascade\" | \"scramble\" | \"typewriter\"",
|
|
26
|
+
"\"reveal\"",
|
|
27
|
+
"Which effect to play."
|
|
28
|
+
],
|
|
29
|
+
[
|
|
30
|
+
"trigger",
|
|
31
|
+
"\"mount\" | \"change\"",
|
|
32
|
+
"\"change\" for cascade / typewriter, else \"mount\"",
|
|
33
|
+
"Animate once on mount, or also every time the text changes. A typewriter keeps typing when the new text extends the old (streaming)."
|
|
34
|
+
],
|
|
35
|
+
[
|
|
36
|
+
"delay",
|
|
37
|
+
"number",
|
|
38
|
+
"0",
|
|
39
|
+
"ms before the first animation."
|
|
40
|
+
],
|
|
41
|
+
[
|
|
42
|
+
"stagger",
|
|
43
|
+
"number",
|
|
44
|
+
"70 reveal · 80 shimmer · 28 cascade · 45 scramble · 32 typewriter",
|
|
45
|
+
"ms between words (reveal) or letters. Shimmer: ms of sweep per letter."
|
|
46
|
+
],
|
|
47
|
+
[
|
|
48
|
+
"speed",
|
|
49
|
+
"number",
|
|
50
|
+
"1",
|
|
51
|
+
"Speed multiplier: 2 plays twice as fast."
|
|
52
|
+
],
|
|
53
|
+
[
|
|
54
|
+
"color",
|
|
55
|
+
"string",
|
|
56
|
+
"theme foreground",
|
|
57
|
+
"Text colour."
|
|
58
|
+
],
|
|
59
|
+
[
|
|
60
|
+
"caret",
|
|
61
|
+
"boolean",
|
|
62
|
+
"true",
|
|
63
|
+
"Blinking caret for the typewriter."
|
|
64
|
+
],
|
|
65
|
+
[
|
|
66
|
+
"replayKey",
|
|
67
|
+
"string | number",
|
|
68
|
+
"—",
|
|
69
|
+
"Change it to replay the animation."
|
|
70
|
+
],
|
|
71
|
+
[
|
|
72
|
+
"onComplete",
|
|
73
|
+
"() => void",
|
|
74
|
+
"—",
|
|
75
|
+
"Called when an animation finishes (not for the looping shimmer)."
|
|
76
|
+
],
|
|
77
|
+
[
|
|
78
|
+
"textStyle",
|
|
79
|
+
"StyleProp<TextStyle>",
|
|
80
|
+
"—",
|
|
81
|
+
"fontSize, fontWeight, lineHeight, letterSpacing, textAlign… for every letter."
|
|
82
|
+
],
|
|
83
|
+
[
|
|
84
|
+
"style",
|
|
85
|
+
"StyleProp<ViewStyle>",
|
|
86
|
+
"—",
|
|
87
|
+
"Style for the root view."
|
|
88
|
+
]
|
|
89
|
+
],
|
|
90
|
+
"behavior": [
|
|
91
|
+
[
|
|
92
|
+
"Motion",
|
|
93
|
+
"Reveal, shimmer and cascade run one native-driver clock per animation with spring curves pre-sampled into interpolations; scramble and typewriter use a light JS timer. Text wraps by word."
|
|
94
|
+
],
|
|
95
|
+
[
|
|
96
|
+
"Reduced motion",
|
|
97
|
+
"Shows the final text immediately; shimmer stays static and the typewriter caret is hidden."
|
|
98
|
+
],
|
|
99
|
+
[
|
|
100
|
+
"Screen readers",
|
|
101
|
+
"The whole string is one text element; the per-letter views are hidden. Cascade and typewriter announce new text when it changes (typewriter once streaming settles)."
|
|
102
|
+
],
|
|
103
|
+
[
|
|
104
|
+
"Platforms",
|
|
105
|
+
"iOS, Android and web. Arabic, Hebrew, Indic, Thai and other connected scripts animate a word at a time so shaping is never broken; right-to-left text flows from the right."
|
|
106
|
+
]
|
|
107
|
+
],
|
|
108
|
+
"usage": "import { AnimatedText } from '@/components/ui/text-animation';\n\n<AnimatedText textStyle={{ fontSize: 28, fontWeight: '600' }}>\n Ship native apps that feel alive.\n</AnimatedText>\n\n<AnimatedText variant=\"shimmer\">Thinking…</AnimatedText>\n<AnimatedText variant=\"cascade\">{status}</AnimatedText>",
|
|
109
|
+
"examples": [
|
|
110
|
+
{
|
|
111
|
+
"name": "Reveal",
|
|
112
|
+
"title": "Reveal",
|
|
113
|
+
"code": "import React, { useEffect, useState } from 'react';\nimport { Pressable, Text, View } from 'react-native';\nimport { AnimatedText } from '@/components/ui/text-animation';\nimport { useKit } from '@/lib/kit';\n\nfunction Chip({ label, onPress }: { label: string; onPress: () => void }) {\n const { colors, radius } = useKit();\n return (\n <Pressable\n accessibilityRole=\"button\"\n onPress={onPress}\n hitSlop={4}\n style={{ height: 36, paddingHorizontal: 14, borderRadius: radius.capsule(36), justifyContent: 'center', alignSelf: 'flex-start', backgroundColor: colors.muted }}\n >\n <Text style={{ color: colors.foreground, fontSize: 14 }}>{label}</Text>\n </Pressable>\n );\n}\n\nexport default function Reveal() {\n const [run, setRun] = useState(0);\n return (\n <View style={{ gap: 16 }}>\n <AnimatedText replayKey={run} textStyle={{ fontSize: 30, fontWeight: '600', letterSpacing: -0.8, lineHeight: 36 }}>\n Ship native apps that feel alive.\n </AnimatedText>\n <Chip label=\"Replay\" onPress={() => setRun((r) => r + 1)} />\n </View>\n );\n}\n"
|
|
114
|
+
},
|
|
115
|
+
{
|
|
116
|
+
"name": "Thinking",
|
|
117
|
+
"title": "Thinking",
|
|
118
|
+
"code": "import { Pressable, Text, View } from 'react-native';\nimport { AnimatedText } from '@/components/ui/text-animation';\nimport { useKit } from '@/lib/kit';\n\nexport default function Thinking() {\n const { colors, radius } = useKit();\n return (\n <View style={{ flexDirection: 'row', alignItems: 'center', gap: 10, padding: 14, borderRadius: radius.lg, backgroundColor: colors.card, borderWidth: 1, borderColor: colors.border }}>\n <View style={{ width: 28, height: 28, borderRadius: radius.capsule(28), backgroundColor: colors.accent }} />\n <AnimatedText variant=\"shimmer\" textStyle={{ fontSize: 16 }}>\n Thinking…\n </AnimatedText>\n </View>\n );\n}\n"
|
|
119
|
+
},
|
|
120
|
+
{
|
|
121
|
+
"name": "DeployStatus",
|
|
122
|
+
"title": "Deploy Status",
|
|
123
|
+
"code": "import React, { useEffect, useState } from 'react';\nimport { Pressable, Text, View } from 'react-native';\nimport { AnimatedText } from '@/components/ui/text-animation';\nimport { useKit } from '@/lib/kit';\n\nfunction Chip({ label, onPress }: { label: string; onPress: () => void }) {\n const { colors, radius } = useKit();\n return (\n <Pressable\n accessibilityRole=\"button\"\n onPress={onPress}\n hitSlop={4}\n style={{ height: 36, paddingHorizontal: 14, borderRadius: radius.capsule(36), justifyContent: 'center', alignSelf: 'flex-start', backgroundColor: colors.muted }}\n >\n <Text style={{ color: colors.foreground, fontSize: 14 }}>{label}</Text>\n </Pressable>\n );\n}\n\nconst STATUSES = ['Queued', 'Building', 'Deploying', 'Live'];\n\nexport default function DeployStatus() {\n const { colors, radius } = useKit();\n const [i, setI] = useState(0);\n const status = STATUSES[i];\n const live = status === 'Live';\n return (\n <View style={{ gap: 16 }}>\n <View style={{ flexDirection: 'row', alignItems: 'center', gap: 10 }}>\n <View style={{ width: 10, height: 10, borderRadius: radius.capsule(10), backgroundColor: live ? colors.success : colors.warning }} />\n <Text style={{ fontSize: 17, color: colors.mutedForeground }}>expokit.dev</Text>\n <AnimatedText variant=\"cascade\" color={live ? colors.success : colors.foreground} textStyle={{ fontSize: 17, fontWeight: '600' }}>\n {status}\n </AnimatedText>\n </View>\n <Chip label=\"Next status\" onPress={() => setI((n) => (n + 1) % STATUSES.length)} />\n </View>\n );\n}\n"
|
|
124
|
+
},
|
|
125
|
+
{
|
|
126
|
+
"name": "Scramble",
|
|
127
|
+
"title": "Scramble",
|
|
128
|
+
"code": "import React, { useEffect, useState } from 'react';\nimport { Pressable, Text, View } from 'react-native';\nimport { AnimatedText } from '@/components/ui/text-animation';\nimport { useKit } from '@/lib/kit';\n\nfunction Chip({ label, onPress }: { label: string; onPress: () => void }) {\n const { colors, radius } = useKit();\n return (\n <Pressable\n accessibilityRole=\"button\"\n onPress={onPress}\n hitSlop={4}\n style={{ height: 36, paddingHorizontal: 14, borderRadius: radius.capsule(36), justifyContent: 'center', alignSelf: 'flex-start', backgroundColor: colors.muted }}\n >\n <Text style={{ color: colors.foreground, fontSize: 14 }}>{label}</Text>\n </Pressable>\n );\n}\n\nexport default function Scramble() {\n const { colors, font } = useKit();\n const [run, setRun] = useState(0);\n return (\n <View style={{ gap: 16 }}>\n <View style={{ gap: 4 }}>\n <Text style={{ fontSize: 13, color: colors.mutedForeground }}>Your referral code</Text>\n <AnimatedText variant=\"scramble\" replayKey={run} textStyle={{ fontSize: 28, fontWeight: '600', letterSpacing: 2, fontFamily: font.mono }}>\n SPRING-7Q2X\n </AnimatedText>\n </View>\n <Chip label=\"Generate again\" onPress={() => setRun((r) => r + 1)} />\n </View>\n );\n}\n"
|
|
129
|
+
},
|
|
130
|
+
{
|
|
131
|
+
"name": "Streaming",
|
|
132
|
+
"title": "Streaming",
|
|
133
|
+
"code": "import React, { useEffect, useState } from 'react';\nimport { Pressable, Text, View } from 'react-native';\nimport { AnimatedText } from '@/components/ui/text-animation';\nimport { useKit } from '@/lib/kit';\n\nfunction Chip({ label, onPress }: { label: string; onPress: () => void }) {\n const { colors, radius } = useKit();\n return (\n <Pressable\n accessibilityRole=\"button\"\n onPress={onPress}\n hitSlop={4}\n style={{ height: 36, paddingHorizontal: 14, borderRadius: radius.capsule(36), justifyContent: 'center', alignSelf: 'flex-start', backgroundColor: colors.muted }}\n >\n <Text style={{ color: colors.foreground, fontSize: 14 }}>{label}</Text>\n </Pressable>\n );\n}\n\nconst ANSWER =\n 'Spring animations feel natural because they keep momentum: a flick carries its speed into the motion, and nothing stops dead.';\n\nexport default function Streaming() {\n const { colors, radius } = useKit();\n const [text, setText] = useState('');\n const [run, setRun] = useState(0);\n useEffect(() => {\n // Simulates tokens arriving from a model, a few words at a time.\n const words = ANSWER.split(' ');\n let n = 0;\n const id = setInterval(() => {\n n = Math.min(words.length, n + 1 + Math.floor(Math.random() * 3));\n setText(words.slice(0, n).join(' '));\n if (n >= words.length) clearInterval(id);\n }, 260);\n return () => clearInterval(id);\n }, [run]);\n return (\n <View style={{ gap: 16 }}>\n <View style={{ padding: 14, borderRadius: radius.lg, backgroundColor: colors.muted }}>\n <AnimatedText key={run} variant=\"typewriter\" textStyle={{ fontSize: 16, lineHeight: 23 }}>\n {text}\n </AnimatedText>\n </View>\n <Chip\n label=\"Ask again\"\n onPress={() => {\n setText('');\n setRun((r) => r + 1);\n }}\n />\n </View>\n );\n}\n"
|
|
134
|
+
},
|
|
135
|
+
{
|
|
136
|
+
"name": "Hero",
|
|
137
|
+
"title": "Hero",
|
|
138
|
+
"code": "import React, { useEffect, useState } from 'react';\nimport { Pressable, Text, View } from 'react-native';\nimport { AnimatedText } from '@/components/ui/text-animation';\nimport { useKit } from '@/lib/kit';\n\nexport default function Hero() {\n const { colors } = useKit();\n const [run, setRun] = useState(0);\n return (\n <Pressable accessibilityRole=\"button\" accessibilityHint=\"Replays the animation\" onPress={() => setRun((r) => r + 1)} style={{ alignItems: 'center', gap: 10, paddingVertical: 12 }}>\n <AnimatedText variant=\"scramble\" replayKey={run} color={colors.accent} style={{ alignSelf: 'stretch' }} textStyle={{ fontSize: 13, fontWeight: '600', letterSpacing: 1.5, textAlign: 'center' }}>\n NEW IN EXPOKIT\n </AnimatedText>\n <AnimatedText replayKey={run} delay={250} style={{ alignSelf: 'stretch' }} textStyle={{ fontSize: 34, fontWeight: '700', letterSpacing: -1, lineHeight: 40, textAlign: 'center' }}>\n Motion your users can feel\n </AnimatedText>\n <AnimatedText variant=\"typewriter\" replayKey={run} delay={900} caret={false} style={{ alignSelf: 'stretch' }} color={colors.mutedForeground} textStyle={{ fontSize: 15, textAlign: 'center' }}>\n Tap anywhere to play it again.\n </AnimatedText>\n </Pressable>\n );\n}\n"
|
|
139
|
+
}
|
|
140
|
+
]
|
|
141
|
+
}
|
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
"files": [
|
|
7
7
|
"src/components/ui/textarea.tsx"
|
|
8
8
|
],
|
|
9
|
+
"summary": "A multiline field that glides taller line by line, with a ring-style counter.",
|
|
9
10
|
"props": [
|
|
10
11
|
[
|
|
11
12
|
"label",
|
|
@@ -140,6 +141,21 @@
|
|
|
140
141
|
"value, defaultValue, onChangeText, placeholder, …"
|
|
141
142
|
]
|
|
142
143
|
],
|
|
144
|
+
"behavior": [
|
|
145
|
+
[
|
|
146
|
+
"Motion",
|
|
147
|
+
"The box grows smoothly between minRows and maxRows as you type; the label floats up on focus and a halo glows around the field."
|
|
148
|
+
],
|
|
149
|
+
[
|
|
150
|
+
"Screen readers",
|
|
151
|
+
"The input is labelled by label or placeholder; the counter reads like '42 of 200 characters left' and error / success messages land in a polite live region."
|
|
152
|
+
],
|
|
153
|
+
[
|
|
154
|
+
"Web",
|
|
155
|
+
"The browser focus outline is removed; the field draws its own focus halo."
|
|
156
|
+
]
|
|
157
|
+
],
|
|
158
|
+
"usage": "import { useState } from 'react';\nimport { Textarea } from '@/components/ui/textarea';\n\nconst [review, setReview] = useState('');\n<Textarea label=\"Your review\" value={review} onChangeText={setReview} maxLength={200} />",
|
|
143
159
|
"examples": [
|
|
144
160
|
{
|
|
145
161
|
"name": "Composer",
|
|
@@ -149,12 +165,12 @@
|
|
|
149
165
|
{
|
|
150
166
|
"name": "Review",
|
|
151
167
|
"title": "Review",
|
|
152
|
-
"code": "import React, { useState } from 'react';\nimport { Pressable, StyleSheet, Text, View } from 'react-native';\nimport { Textarea } from '@/components/ui/textarea';\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={
|
|
168
|
+
"code": "import React, { useState } from 'react';\nimport { Pressable, StyleSheet, Text, View } from 'react-native';\nimport { Textarea } from '@/components/ui/textarea';\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 screen\n ? { flex: 1, padding: 16, gap: 12, backgroundColor: colors.background }\n : {\n padding: 16,\n gap: 12,\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 Review({ screen }: DemoProps) {\n const [review, setReview] = useState('Great coffee, but');\n const length = review.trim().length;\n return (\n <Card screen={screen} title=\"Rate Blue Bottle, Hayes Valley\" subtitle=\"Reviews are public and show your first name.\">\n <Textarea\n label=\"Your review\"\n value={review}\n onChangeText={setReview}\n maxLength={200}\n enforceMaxLength={false}\n minRows={3}\n error={length > 0 && length < 20 ? 'Tell us a little more — at least 20 characters' : length > 200 ? 'Keep it under 200 characters' : undefined}\n success={length >= 20 && length <= 200 ? 'Thanks — that will really help others' : undefined}\n />\n </Card>\n );\n}\n"
|
|
153
169
|
},
|
|
154
170
|
{
|
|
155
171
|
"name": "Feedback",
|
|
156
172
|
"title": "Feedback",
|
|
157
|
-
"code": "import React, { useState } from 'react';\nimport { Pressable, StyleSheet, Text, View } from 'react-native';\nimport { Textarea } from '@/components/ui/textarea';\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={
|
|
173
|
+
"code": "import React, { useState } from 'react';\nimport { Pressable, StyleSheet, Text, View } from 'react-native';\nimport { Textarea } from '@/components/ui/textarea';\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 screen\n ? { flex: 1, padding: 16, gap: 12, backgroundColor: colors.background }\n : {\n padding: 16,\n gap: 12,\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 Feedback({ screen }: DemoProps) {\n return (\n <Card screen={screen} title=\"Send feedback\" subtitle=\"Goes straight to the product team.\">\n <Textarea\n variant=\"filled\"\n label=\"What could be better?\"\n defaultValue=\"Dark mode on the invoices page is a little too low-contrast for me.\"\n helper=\"Screenshots help — attach them in the next step.\"\n minRows={3}\n maxRows={6}\n />\n </Card>\n );\n}\n"
|
|
158
174
|
},
|
|
159
175
|
{
|
|
160
176
|
"name": "CharacterLimit",
|
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
"files": [
|
|
7
7
|
"src/components/ui/ticket-card.tsx"
|
|
8
8
|
],
|
|
9
|
+
"summary": "A boarding pass or event ticket with real notches and a stub you can tear off.",
|
|
9
10
|
"props": [
|
|
10
11
|
[
|
|
11
12
|
"brand / logo",
|
|
@@ -170,5 +171,24 @@
|
|
|
170
171
|
"Style for the root (width, margins)."
|
|
171
172
|
]
|
|
172
173
|
],
|
|
174
|
+
"behavior": [
|
|
175
|
+
[
|
|
176
|
+
"Touch",
|
|
177
|
+
"The card tilts toward your finger (tilt={false} turns it off). Tap to flip it to the big code and notes; with onTear set, swipe the stub sideways to tear it off."
|
|
178
|
+
],
|
|
179
|
+
[
|
|
180
|
+
"Motion",
|
|
181
|
+
"A holo badge shimmers as the card leans, a stamp thumps down when set, and a torn stub falls away along the tear line."
|
|
182
|
+
],
|
|
183
|
+
[
|
|
184
|
+
"Haptics",
|
|
185
|
+
"A success notification when the stub tears off."
|
|
186
|
+
],
|
|
187
|
+
[
|
|
188
|
+
"Screen readers",
|
|
189
|
+
"One element whose label joins the brand, title or route, every field and the stamp; it is a button when there is a back to flip to."
|
|
190
|
+
]
|
|
191
|
+
],
|
|
192
|
+
"usage": null,
|
|
173
193
|
"examples": []
|
|
174
194
|
}
|
|
@@ -2,10 +2,13 @@
|
|
|
2
2
|
"name": "ticket-shape",
|
|
3
3
|
"category": null,
|
|
4
4
|
"aliases": [],
|
|
5
|
-
"exportName": "
|
|
5
|
+
"exportName": "TicketShape",
|
|
6
6
|
"files": [
|
|
7
7
|
"src/lib/ticket-shape.tsx"
|
|
8
8
|
],
|
|
9
|
+
"summary": null,
|
|
9
10
|
"props": [],
|
|
11
|
+
"behavior": [],
|
|
12
|
+
"usage": null,
|
|
10
13
|
"examples": []
|
|
11
14
|
}
|
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
"files": [
|
|
7
7
|
"src/components/ui/tilt-card.tsx"
|
|
8
8
|
],
|
|
9
|
+
"summary": "A card that leans toward your finger in 3D while a glare slides across it.",
|
|
9
10
|
"props": [
|
|
10
11
|
[
|
|
11
12
|
"children",
|
|
@@ -116,26 +117,41 @@
|
|
|
116
117
|
"The card's live tilt values (native-driver friendly) for your own effects."
|
|
117
118
|
]
|
|
118
119
|
],
|
|
120
|
+
"behavior": [
|
|
121
|
+
[
|
|
122
|
+
"Touch",
|
|
123
|
+
"Press and drag to tilt up to maxTilt degrees; the card scales by pressScale and springs flat on release. onPress fires on a tap, not after a drag."
|
|
124
|
+
],
|
|
125
|
+
[
|
|
126
|
+
"Motion",
|
|
127
|
+
"The shadow slides away from the light and the glare (soft, sharp or holo) glides across. TiltLayer children float at their own depth, and idle makes it sway gently at rest."
|
|
128
|
+
],
|
|
129
|
+
[
|
|
130
|
+
"Screen readers",
|
|
131
|
+
"Exposed as one element when it has onPress or accessibilityLabel; with onPress it is a button with an 'activate' action."
|
|
132
|
+
]
|
|
133
|
+
],
|
|
134
|
+
"usage": "import { TiltCard, TiltLayer } from '@/components/ui/tilt-card';\n\n<TiltCard image={{ uri: photoUrl }} idle accessibilityLabel=\"Manarola, Cinque Terre\" style={{ width: 270, height: 360 }}>\n <TiltLayer depth={1}>\n <PlaceCaption place={place} />\n </TiltLayer>\n</TiltCard>",
|
|
119
135
|
"examples": [
|
|
120
136
|
{
|
|
121
137
|
"name": "TravelCard",
|
|
122
138
|
"title": "Travel Card",
|
|
123
|
-
"code": "import React, { useState } from 'react';\nimport { Image, Pressable, Text, View } from 'react-native';\nimport { TiltCard, TiltLayer } from '@/components/ui/tilt-card';\nimport { alpha, useKit } from '@/lib/kit';\n\n/** Any photo works (`require()` or a URL).
|
|
139
|
+
"code": "import React, { useState } from 'react';\nimport { Image, Pressable, Text, View } from 'react-native';\nimport { TiltCard, TiltLayer } from '@/components/ui/tilt-card';\nimport { alpha, useKit } from '@/lib/kit';\n\n/** Any photo works (`require()` or a URL). */\nconst photo = (id: number, w: number, h: number) => ({ uri: `https://picsum.photos/id/${id}/${w}/${h}` });\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 glass = {\n paddingHorizontal: 10,\n paddingVertical: 5,\n borderRadius: 999,\n backgroundColor: 'rgba(255,255,255,0.18)',\n borderWidth: 1,\n borderColor: 'rgba(255,255,255,0.32)',\n} as const;\n\nexport default function TravelCard() {\n const { colors } = useKit();\n const [tab, setTab] = useState('Coast');\n return (\n <View style={{ gap: 18 }}>\n <Header title=\"Explore\" sub=\"Stays our guests loved this month\" />\n <View style={{ flexDirection: 'row', gap: 8 }}>\n {['Coast', 'Mountains', 'Cities', 'Lakes'].map((t) => {\n const on = t === tab;\n return (\n <Pressable\n key={t}\n onPress={() => setTab(t)}\n accessibilityRole=\"tab\"\n accessibilityState={{ selected: on }}\n style={{ paddingHorizontal: 14, paddingVertical: 8, borderRadius: 999, backgroundColor: on ? colors.foreground : colors.muted }}\n >\n <Text style={{ color: on ? colors.background : colors.foreground, fontSize: 13, fontWeight: '600' }}>{t}</Text>\n </Pressable>\n );\n })}\n </View>\n <TiltCard idle image={photo(397, 540, 720)} radius={26} accessibilityLabel=\"Manarola, Cinque Terre\" style={{ width: 270, alignSelf: 'center' }}>\n <View style={{ height: 360 }} />\n <TiltLayer depth={1.2}>\n <View style={[glass, { position: 'absolute', top: 16, right: 16 }]}>\n <Text style={{ color: '#fff', fontSize: 12, fontWeight: '700' }}>★ 4.9</Text>\n </View>\n </TiltLayer>\n <TiltLayer depth={0.8}>\n <View style={{ position: 'absolute', left: 20, right: 20, bottom: 20, gap: 2 }}>\n <Text style={{ color: 'rgba(255,255,255,0.8)', fontSize: 12, fontWeight: '600', letterSpacing: 1 }}>CINQUE TERRE · ITALY</Text>\n <Text style={{ color: '#fff', fontSize: 30, fontWeight: '800', letterSpacing: -0.8 }}>Manarola</Text>\n <Text style={{ color: 'rgba(255,255,255,0.8)', fontSize: 13 }}>3 nights · from €140</Text>\n </View>\n </TiltLayer>\n </TiltCard>\n </View>\n );\n}\n"
|
|
124
140
|
},
|
|
125
141
|
{
|
|
126
142
|
"name": "NowPlaying",
|
|
127
143
|
"title": "Now Playing",
|
|
128
|
-
"code": "import React, { useState } from 'react';\nimport { Image, Pressable, Text, View } from 'react-native';\nimport { TiltCard, TiltLayer } from '@/components/ui/tilt-card';\nimport { alpha, useKit } from '@/lib/kit';\n\
|
|
144
|
+
"code": "import React, { useState } from 'react';\nimport { Image, Pressable, Text, View } from 'react-native';\nimport { GrainGradient } from '@/components/ui/grain';\nimport { TiltCard, TiltLayer } from '@/components/ui/tilt-card';\nimport { alpha, useKit } from '@/lib/kit';\n\nexport default function NowPlaying() {\n const { colors } = useKit();\n const [playing, setPlaying] = useState(true);\n return (\n <View style={{ alignItems: 'center', gap: 18 }}>\n <View style={{ alignSelf: 'stretch', flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between', marginBottom: 6 }}>\n <Text style={{ color: colors.foreground, fontSize: 20 }}>⌄</Text>\n <View style={{ alignItems: 'center' }}>\n <Text style={{ color: colors.mutedForeground, fontSize: 11, fontWeight: '700', letterSpacing: 1 }}>PLAYING FROM</Text>\n <Text style={{ color: colors.foreground, fontSize: 13, fontWeight: '600' }}>Late night drive</Text>\n </View>\n <Text style={{ color: colors.foreground, fontSize: 20 }}>⋯</Text>\n </View>\n <TiltCard idle radius={20} accessibilityLabel=\"Midnight Bloom by Lumen\" style={{ width: 250 }}>\n <GrainGradient palette=\"noir\" seed={1} style={{ height: 250 }} />\n <TiltLayer depth={1}>\n <View style={{ position: 'absolute', left: 18, bottom: 16 }}>\n <Text style={{ color: '#fff', fontSize: 30, fontWeight: '900', letterSpacing: -1, lineHeight: 30 }}>MIDNIGHT</Text>\n <Text style={{ color: '#fff', fontSize: 30, fontWeight: '300', letterSpacing: -1, lineHeight: 32 }}>bloom</Text>\n </View>\n </TiltLayer>\n </TiltCard>\n <View style={{ alignSelf: 'stretch', gap: 10 }}>\n <View>\n <Text style={{ color: colors.foreground, fontSize: 18, fontWeight: '700' }}>Midnight Bloom</Text>\n <Text style={{ color: colors.mutedForeground, fontSize: 14 }}>Lumen</Text>\n </View>\n <View style={{ height: 4, borderRadius: 2, backgroundColor: alpha(colors.mutedForeground, 0.25) }}>\n <View style={{ width: '42%', height: 4, borderRadius: 2, backgroundColor: colors.foreground }} />\n </View>\n <View style={{ flexDirection: 'row', justifyContent: 'space-between', marginTop: -4 }}>\n <Text style={{ color: colors.mutedForeground, fontSize: 11, fontVariant: ['tabular-nums'] }}>1:42</Text>\n <Text style={{ color: colors.mutedForeground, fontSize: 11, fontVariant: ['tabular-nums'] }}>−2:21</Text>\n </View>\n <View style={{ flexDirection: 'row', justifyContent: 'center', alignItems: 'center', gap: 34 }}>\n <Text style={{ color: colors.foreground, fontSize: 22 }}>⏮</Text>\n <Pressable\n onPress={() => setPlaying((p) => !p)}\n accessibilityRole=\"button\"\n accessibilityLabel={playing ? 'Pause' : 'Play'}\n style={{ width: 58, height: 58, borderRadius: 29, backgroundColor: colors.foreground, alignItems: 'center', justifyContent: 'center' }}\n >\n <Text style={{ color: colors.background, fontSize: 20 }}>{playing ? '❚❚' : '▶'}</Text>\n </Pressable>\n <Text style={{ color: colors.foreground, fontSize: 22 }}>⏭</Text>\n </View>\n </View>\n </View>\n );\n}\n"
|
|
129
145
|
},
|
|
130
146
|
{
|
|
131
147
|
"name": "Product",
|
|
132
148
|
"title": "Product",
|
|
133
|
-
"code": "import React, { useState } from 'react';\nimport { Image, Pressable, Text, View } from 'react-native';\nimport { TiltCard, TiltLayer } from '@/components/ui/tilt-card';\nimport { alpha, useKit } from '@/lib/kit';\n\n/** Any photo works (`require()` or a URL).
|
|
149
|
+
"code": "import React, { useState } from 'react';\nimport { Image, Pressable, Text, View } from 'react-native';\nimport { TiltCard, TiltLayer } from '@/components/ui/tilt-card';\nimport { alpha, useKit } from '@/lib/kit';\n\n/** Any photo works (`require()` or a URL). */\nconst photo = (id: number, w: number, h: number) => ({ uri: `https://picsum.photos/id/${id}/${w}/${h}` });\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 Product() {\n const { colors } = useKit();\n return (\n <View style={{ gap: 18 }}>\n <Header\n title=\"New in\"\n sub=\"Film cameras, serviced and tested\"\n right={\n <View style={{ paddingHorizontal: 12, paddingVertical: 7, borderRadius: 999, backgroundColor: colors.muted }}>\n <Text style={{ color: colors.foreground, fontSize: 13, fontWeight: '600' }}>Bag · 2</Text>\n </View>\n }\n />\n <TiltCard maxTilt={14} radius={26} accessibilityLabel=\"Leica M6 film camera\" style={{ width: 280, alignSelf: 'center' }}>\n <View style={{ height: 360, backgroundColor: '#efece6' }} />\n <TiltLayer depth={1.4}>\n <View style={{ position: 'absolute', top: 26, left: 22, right: 22, height: 190, borderRadius: 18, overflow: 'hidden' }}>\n <Image alt=\"\" source={photo(250, 560, 380)} style={{ flex: 1 }} />\n </View>\n </TiltLayer>\n <TiltLayer depth={0.4}>\n <View style={{ position: 'absolute', left: 24, right: 24, bottom: 22, gap: 12 }}>\n <View>\n <Text style={{ color: '#8a8378', fontSize: 11, fontWeight: '700', letterSpacing: 1.2 }}>FILM CAMERA</Text>\n <Text style={{ color: '#1b1915', fontSize: 22, fontWeight: '800', letterSpacing: -0.4 }}>Rangefinder 35</Text>\n </View>\n <View style={{ flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between' }}>\n <Text style={{ color: '#1b1915', fontSize: 20, fontWeight: '700' }}>$1,290</Text>\n <View style={{ paddingHorizontal: 16, paddingVertical: 10, borderRadius: 999, backgroundColor: '#1b1915' }}>\n <Text style={{ color: '#fff', fontSize: 13, fontWeight: '700' }}>Add to bag</Text>\n </View>\n </View>\n </View>\n </TiltLayer>\n </TiltCard>\n <Text style={{ color: colors.mutedForeground, fontSize: 13, textAlign: 'center' }}>Free shipping · 2-year warranty</Text>\n </View>\n );\n}\n"
|
|
134
150
|
},
|
|
135
151
|
{
|
|
136
152
|
"name": "FeedCard",
|
|
137
153
|
"title": "Feed Card",
|
|
138
|
-
"code": "import React, { useState } from 'react';\nimport { Image, Pressable, Text, View } from 'react-native';\nimport { TiltCard, TiltLayer } from '@/components/ui/tilt-card';\nimport { alpha, useKit } from '@/lib/kit';\n\n/** Any photo works (`require()` or a URL).
|
|
154
|
+
"code": "import React, { useState } from 'react';\nimport { Image, Pressable, Text, View } from 'react-native';\nimport { TiltCard, TiltLayer } from '@/components/ui/tilt-card';\nimport { alpha, useKit } from '@/lib/kit';\n\n/** Any photo works (`require()` or a URL). */\nconst photo = (id: number, w: number, h: number) => ({ uri: `https://picsum.photos/id/${id}/${w}/${h}` });\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 FeedCard() {\n const { colors } = useKit();\n return (\n <View style={{ gap: 16 }}>\n <Header title=\"For you\" sub=\"Picked from places you saved\" />\n <TiltCard maxTilt={5} pressScale={1.015} glare=\"soft\" radius={20} onPress={() => {}} accessibilityLabel=\"Read: A fairytale weekend in Bavaria\" style={{ alignSelf: 'stretch' }}>\n <View style={{ backgroundColor: colors.card }}>\n <Image alt=\"\" source={photo(1040, 720, 360)} style={{ height: 170 }} />\n <View style={{ padding: 16, gap: 4 }}>\n <Text style={{ color: colors.mutedForeground, fontSize: 11, fontWeight: '700', letterSpacing: 1 }}>TRAVEL · 6 MIN READ</Text>\n <Text style={{ color: colors.foreground, fontSize: 18, fontWeight: '700', letterSpacing: -0.3 }}>A fairytale weekend in Bavaria</Text>\n <Text style={{ color: colors.mutedForeground, fontSize: 13, lineHeight: 19 }}>Castles, lakes and the best pretzels we had all year.</Text>\n </View>\n </View>\n </TiltCard>\n <View style={{ flexDirection: 'row', alignItems: 'center', gap: 12, padding: 10, borderRadius: 16, backgroundColor: colors.card }}>\n <Image alt=\"\" source={photo(419, 160, 160)} style={{ width: 56, height: 56, borderRadius: 12, backgroundColor: colors.muted }} />\n <View style={{ flex: 1 }}>\n <Text style={{ color: colors.mutedForeground, fontSize: 11, fontWeight: '700', letterSpacing: 1 }}>UP NEXT · 4 MIN READ</Text>\n <Text style={{ color: colors.foreground, fontSize: 15, fontWeight: '600' }}>Lisbon by tram, end to end</Text>\n </View>\n </View>\n </View>\n );\n}\n"
|
|
139
155
|
}
|
|
140
156
|
]
|
|
141
157
|
}
|
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
"files": [
|
|
7
7
|
"src/components/ui/time-picker.tsx"
|
|
8
8
|
],
|
|
9
|
+
"summary": "A time picker as 3D wheel drums, a draggable clock dial or bookable time chips.",
|
|
9
10
|
"props": [
|
|
10
11
|
[
|
|
11
12
|
"value",
|
|
@@ -218,36 +219,51 @@
|
|
|
218
219
|
"Sheet: the sheet / dialog panel — background, border, radius, padding, shadow (transform and opacity are animated)."
|
|
219
220
|
]
|
|
220
221
|
],
|
|
222
|
+
"behavior": [
|
|
223
|
+
[
|
|
224
|
+
"Touch",
|
|
225
|
+
"Wheel drums snap with momentum and a tapped row jumps into the band. On the clock, drag the hand anywhere on the dial; it snaps to hours, then moves on to minutes when you let go."
|
|
226
|
+
],
|
|
227
|
+
[
|
|
228
|
+
"Motion",
|
|
229
|
+
"Wheel rows tilt, shrink and fade past the selection band, driven by scroll on the native driver; the clock header crossfades between hours and minutes."
|
|
230
|
+
],
|
|
231
|
+
[
|
|
232
|
+
"Screen readers",
|
|
233
|
+
"Each wheel column and the clock dial are adjustable with increment / decrement actions; AM / PM and the time chips are radio groups, with booked or unavailable slots named. The sheet field is a button with expanded state."
|
|
234
|
+
]
|
|
235
|
+
],
|
|
236
|
+
"usage": "import { useState } from 'react';\nimport { TimePicker, type TimeValue } from '@/components/ui/time-picker';\n\nconst [time, setTime] = useState<TimeValue | null>({ hours: 7, minutes: 30 });\n<TimePicker value={time} onChange={setTime} hour12 />",
|
|
221
237
|
"examples": [
|
|
222
238
|
{
|
|
223
239
|
"name": "Alarm",
|
|
224
240
|
"title": "Alarm",
|
|
225
|
-
"code": "import React, { useState } from 'react';\nimport { Pressable, StyleSheet, Text, View, type TextProps, type ViewProps } from 'react-native';\nimport { TimePicker, formatTime, type TimeValue } from '@/components/ui/time-picker';\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 s.card,\n { borderRadius: radius.xl, borderColor: colors.border, backgroundColor: colors.card },\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\n/** \"in 7 h 20 min\" until the next occurrence of `t`. */\nfunction until(t: TimeValue) {\n const now = new Date();\n let diff = t.hours * 60 + t.minutes - (now.getHours() * 60 + now.getMinutes());\n if (diff <= 0) diff += 24 * 60;\n const h = Math.floor(diff / 60);\n return h ? `in ${h} h ${diff % 60} min` : `in ${diff} min`;\n}\n\nconst s = StyleSheet.create({\n card: { padding: 16, borderWidth: StyleSheet.hairlineWidth, gap: 12 },\n head: { flexDirection: 'row', justifyContent: 'space-between', alignItems: 'flex-start', gap: 12 },\n headText: { flexShrink: 1, gap: 2 },\n title: { fontSize: 15, fontWeight: '400' },\n meta: { fontSize: 13, fontWeight: '400', fontVariant: ['tabular-nums'] },\n hint: { fontSize: 12.5, textAlign: 'center' },\n note: { paddingVertical: 10, alignItems: 'center' },\n button: { height: 46, alignItems: 'center', justifyContent: 'center' },\n buttonOff: { opacity: 0.45 },\n buttonText: { fontSize: 15, fontWeight: '400' },\n});\n\nexport default function Alarm() {\n const [time, setTime] = useState<TimeValue>({ hours: 6, minutes: 45 });\n return (\n <Card>\n <View style={s.head}>\n <View style={s.headText}>\n <Ink style={s.title}>Wake up</Ink>\n <Ink tone=\"muted\" style={s.meta}>Weekdays · Radar</Ink>\n </View>\n <Ink tone=\"muted\" style={s.meta}>{until(time)}</Ink>\n </View>\n <TimePicker value={time} onChange={setTime} hour12 />\n </Card>\n );\n}\n"
|
|
241
|
+
"code": "import React, { useState } from 'react';\nimport { Pressable, StyleSheet, Text, View, type TextProps, type ViewProps } from 'react-native';\nimport { TimePicker, formatTime, type TimeValue } from '@/components/ui/time-picker';\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 s.card,\n screen\n ? { flex: 1, borderWidth: 0, backgroundColor: colors.background }\n : { borderRadius: radius.xl, borderColor: colors.border, backgroundColor: colors.card },\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\n/** \"in 7 h 20 min\" until the next occurrence of `t`. */\nfunction until(t: TimeValue) {\n const now = new Date();\n let diff = t.hours * 60 + t.minutes - (now.getHours() * 60 + now.getMinutes());\n if (diff <= 0) diff += 24 * 60;\n const h = Math.floor(diff / 60);\n return h ? `in ${h} h ${diff % 60} min` : `in ${diff} min`;\n}\n\nconst s = StyleSheet.create({\n card: { padding: 16, borderWidth: StyleSheet.hairlineWidth, gap: 12 },\n head: { flexDirection: 'row', justifyContent: 'space-between', alignItems: 'flex-start', gap: 12 },\n headText: { flexShrink: 1, gap: 2 },\n title: { fontSize: 15, fontWeight: '400' },\n meta: { fontSize: 13, fontWeight: '400', fontVariant: ['tabular-nums'] },\n hint: { fontSize: 12.5, textAlign: 'center' },\n note: { paddingVertical: 10, alignItems: 'center' },\n button: { height: 46, alignItems: 'center', justifyContent: 'center' },\n buttonOff: { opacity: 0.45 },\n buttonText: { fontSize: 15, fontWeight: '400' },\n});\n\nexport default function Alarm({ screen }: DemoProps) {\n const [time, setTime] = useState<TimeValue>({ hours: 6, minutes: 45 });\n return (\n <Card screen={screen}>\n <View style={s.head}>\n <View style={s.headText}>\n <Ink style={s.title}>Wake up</Ink>\n <Ink tone=\"muted\" style={s.meta}>Weekdays · Radar</Ink>\n </View>\n <Ink tone=\"muted\" style={s.meta}>{until(time)}</Ink>\n </View>\n <TimePicker value={time} onChange={setTime} hour12 />\n </Card>\n );\n}\n"
|
|
226
242
|
},
|
|
227
243
|
{
|
|
228
244
|
"name": "Clock",
|
|
229
245
|
"title": "Clock",
|
|
230
|
-
"code": "import React, { useState } from 'react';\nimport { Pressable, StyleSheet, Text, View, type TextProps, type ViewProps } from 'react-native';\nimport { TimePicker, formatTime, type TimeValue } from '@/components/ui/time-picker';\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 s.card,\n { borderRadius: radius.xl, borderColor: colors.border, backgroundColor: colors.card },\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\n/** \"in 7 h 20 min\" until the next occurrence of `t`. */\nfunction until(t: TimeValue) {\n const now = new Date();\n let diff = t.hours * 60 + t.minutes - (now.getHours() * 60 + now.getMinutes());\n if (diff <= 0) diff += 24 * 60;\n const h = Math.floor(diff / 60);\n return h ? `in ${h} h ${diff % 60} min` : `in ${diff} min`;\n}\n\nconst s = StyleSheet.create({\n card: { padding: 16, borderWidth: StyleSheet.hairlineWidth, gap: 12 },\n head: { flexDirection: 'row', justifyContent: 'space-between', alignItems: 'flex-start', gap: 12 },\n headText: { flexShrink: 1, gap: 2 },\n title: { fontSize: 15, fontWeight: '400' },\n meta: { fontSize: 13, fontWeight: '400', fontVariant: ['tabular-nums'] },\n hint: { fontSize: 12.5, textAlign: 'center' },\n note: { paddingVertical: 10, alignItems: 'center' },\n button: { height: 46, alignItems: 'center', justifyContent: 'center' },\n buttonOff: { opacity: 0.45 },\n buttonText: { fontSize: 15, fontWeight: '400' },\n});\n\nexport default function Clock() {\n const { colors, radius } = useKit();\n const [time, setTime] = useState<TimeValue>({ hours: 7, minutes: 30 });\n return (\n <Card>\n <View style={s.headText}>\n <Ink style={s.title}>Morning run</Ink>\n <Ink tone=\"muted\" style={s.meta}>Tap the hour or minute, then drag the dial.</Ink>\n </View>\n <TimePicker variant=\"clock\" value={time} onChange={setTime} hour12 />\n <View style={[s.note, { backgroundColor: colors.muted, borderRadius: radius.md }]}>\n <Ink tone=\"muted\" style={s.meta}>Rings {until(time)}</Ink>\n </View>\n </Card>\n );\n}\n"
|
|
246
|
+
"code": "import React, { useState } from 'react';\nimport { Pressable, StyleSheet, Text, View, type TextProps, type ViewProps } from 'react-native';\nimport { TimePicker, formatTime, type TimeValue } from '@/components/ui/time-picker';\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 s.card,\n screen\n ? { flex: 1, borderWidth: 0, backgroundColor: colors.background }\n : { borderRadius: radius.xl, borderColor: colors.border, backgroundColor: colors.card },\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\n/** \"in 7 h 20 min\" until the next occurrence of `t`. */\nfunction until(t: TimeValue) {\n const now = new Date();\n let diff = t.hours * 60 + t.minutes - (now.getHours() * 60 + now.getMinutes());\n if (diff <= 0) diff += 24 * 60;\n const h = Math.floor(diff / 60);\n return h ? `in ${h} h ${diff % 60} min` : `in ${diff} min`;\n}\n\nconst s = StyleSheet.create({\n card: { padding: 16, borderWidth: StyleSheet.hairlineWidth, gap: 12 },\n head: { flexDirection: 'row', justifyContent: 'space-between', alignItems: 'flex-start', gap: 12 },\n headText: { flexShrink: 1, gap: 2 },\n title: { fontSize: 15, fontWeight: '400' },\n meta: { fontSize: 13, fontWeight: '400', fontVariant: ['tabular-nums'] },\n hint: { fontSize: 12.5, textAlign: 'center' },\n note: { paddingVertical: 10, alignItems: 'center' },\n button: { height: 46, alignItems: 'center', justifyContent: 'center' },\n buttonOff: { opacity: 0.45 },\n buttonText: { fontSize: 15, fontWeight: '400' },\n});\n\nexport default function Clock({ screen }: DemoProps) {\n const { colors, radius } = useKit();\n const [time, setTime] = useState<TimeValue>({ hours: 7, minutes: 30 });\n return (\n <Card screen={screen}>\n <View style={s.headText}>\n <Ink style={s.title}>Morning run</Ink>\n <Ink tone=\"muted\" style={s.meta}>Tap the hour or minute, then drag the dial.</Ink>\n </View>\n <TimePicker variant=\"clock\" value={time} onChange={setTime} hour12 />\n <View style={[s.note, { backgroundColor: colors.muted, borderRadius: radius.md }]}>\n <Ink tone=\"muted\" style={s.meta}>Rings {until(time)}</Ink>\n </View>\n </Card>\n );\n}\n"
|
|
231
247
|
},
|
|
232
248
|
{
|
|
233
249
|
"name": "Appointment",
|
|
234
250
|
"title": "Appointment",
|
|
235
|
-
"code": "import React, { useState } from 'react';\nimport { Pressable, StyleSheet, Text, View, type TextProps, type ViewProps } from 'react-native';\nimport { TimePicker, formatTime, type TimeValue } from '@/components/ui/time-picker';\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 s.card,\n { borderRadius: radius.xl, borderColor: colors.border, backgroundColor: colors.card },\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\nconst s = StyleSheet.create({\n card: { padding: 16, borderWidth: StyleSheet.hairlineWidth, gap: 12 },\n head: { flexDirection: 'row', justifyContent: 'space-between', alignItems: 'flex-start', gap: 12 },\n headText: { flexShrink: 1, gap: 2 },\n title: { fontSize: 15, fontWeight: '400' },\n meta: { fontSize: 13, fontWeight: '400', fontVariant: ['tabular-nums'] },\n hint: { fontSize: 12.5, textAlign: 'center' },\n note: { paddingVertical: 10, alignItems: 'center' },\n button: { height: 46, alignItems: 'center', justifyContent: 'center' },\n buttonOff: { opacity: 0.45 },\n buttonText: { fontSize: 15, fontWeight: '400' },\n});\n\nexport default function Appointment() {\n const { colors, radius } = useKit();\n const [slot, setSlot] = useState<TimeValue | null>(null);\n return (\n <Card>\n <Ink style={s.title}>Dr. Amara Okafor · Dermatology</Ink>\n <TimePicker\n variant=\"chips\"\n value={slot}\n onChange={setSlot}\n hour12\n startTime=\"09:00\"\n endTime=\"18:00\"\n minuteInterval={30}\n bookedTimes={['09:30', '10:00', '11:30', '13:00', '14:30', '15:00', '17:30']}\n disabledTimes={['12:00', '12:30']}\n groupByPeriod\n />\n <Pressable\n disabled={!slot}\n style={[s.button, { backgroundColor: colors.accent, borderRadius: radius.md }, !slot && s.buttonOff]}\n accessibilityRole=\"button\"\n >\n <Text style={[s.buttonText, { color: colors.accentForeground }]}>{slot ? `Book ${formatTime(slot)} · 30 min` : 'Choose a time'}</Text>\n </Pressable>\n </Card>\n );\n}\n"
|
|
251
|
+
"code": "import React, { useState } from 'react';\nimport { Pressable, StyleSheet, Text, View, type TextProps, type ViewProps } from 'react-native';\nimport { TimePicker, formatTime, type TimeValue } from '@/components/ui/time-picker';\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 s.card,\n screen\n ? { flex: 1, borderWidth: 0, backgroundColor: colors.background }\n : { borderRadius: radius.xl, borderColor: colors.border, backgroundColor: colors.card },\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\nconst s = StyleSheet.create({\n card: { padding: 16, borderWidth: StyleSheet.hairlineWidth, gap: 12 },\n head: { flexDirection: 'row', justifyContent: 'space-between', alignItems: 'flex-start', gap: 12 },\n headText: { flexShrink: 1, gap: 2 },\n title: { fontSize: 15, fontWeight: '400' },\n meta: { fontSize: 13, fontWeight: '400', fontVariant: ['tabular-nums'] },\n hint: { fontSize: 12.5, textAlign: 'center' },\n note: { paddingVertical: 10, alignItems: 'center' },\n button: { height: 46, alignItems: 'center', justifyContent: 'center' },\n buttonOff: { opacity: 0.45 },\n buttonText: { fontSize: 15, fontWeight: '400' },\n});\n\nexport default function Appointment({ screen }: DemoProps) {\n const { colors, radius } = useKit();\n const [slot, setSlot] = useState<TimeValue | null>(null);\n return (\n <Card screen={screen}>\n <Ink style={s.title}>Dr. Amara Okafor · Dermatology</Ink>\n <TimePicker\n variant=\"chips\"\n value={slot}\n onChange={setSlot}\n hour12\n startTime=\"09:00\"\n endTime=\"18:00\"\n minuteInterval={30}\n bookedTimes={['09:30', '10:00', '11:30', '13:00', '14:30', '15:00', '17:30']}\n disabledTimes={['12:00', '12:30']}\n groupByPeriod\n />\n <Pressable\n disabled={!slot}\n style={[s.button, { backgroundColor: colors.accent, borderRadius: radius.md }, !slot && s.buttonOff]}\n accessibilityRole=\"button\"\n >\n <Text style={[s.buttonText, { color: colors.accentForeground }]}>{slot ? `Book ${formatTime(slot)} · 30 min` : 'Choose a time'}</Text>\n </Pressable>\n </Card>\n );\n}\n"
|
|
236
252
|
},
|
|
237
253
|
{
|
|
238
254
|
"name": "Reminder",
|
|
239
255
|
"title": "Reminder",
|
|
240
|
-
"code": "import React, { useState } from 'react';\nimport { Pressable, StyleSheet, Text, View, type TextProps, type ViewProps } from 'react-native';\nimport { TimePicker, formatTime, type TimeValue } from '@/components/ui/time-picker';\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 s.card,\n { borderRadius: radius.xl, borderColor: colors.border, backgroundColor: colors.card },\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\nconst s = StyleSheet.create({\n card: { padding: 16, borderWidth: StyleSheet.hairlineWidth, gap: 12 },\n head: { flexDirection: 'row', justifyContent: 'space-between', alignItems: 'flex-start', gap: 12 },\n headText: { flexShrink: 1, gap: 2 },\n title: { fontSize: 15, fontWeight: '400' },\n meta: { fontSize: 13, fontWeight: '400', fontVariant: ['tabular-nums'] },\n hint: { fontSize: 12.5, textAlign: 'center' },\n note: { paddingVertical: 10, alignItems: 'center' },\n button: { height: 46, alignItems: 'center', justifyContent: 'center' },\n buttonOff: { opacity: 0.45 },\n buttonText: { fontSize: 15, fontWeight: '400' },\n});\n\nexport default function Reminder() {\n const [time, setTime] = useState<TimeValue>({ hours: 21, minutes: 30 });\n return (\n <Card>\n <View style={s.head}>\n <Ink style={s.title}>Daily journal reminder</Ink>\n <Ink tone=\"muted\" style={s.meta}>{formatTime(time, false)}</Ink>\n </View>\n <TimePicker value={time} onChange={setTime} hour12={false} minuteInterval={15} />\n <Ink tone=\"muted\" style={s.hint}>24-hour clock, 15-minute steps.</Ink>\n </Card>\n );\n}\n"
|
|
256
|
+
"code": "import React, { useState } from 'react';\nimport { Pressable, StyleSheet, Text, View, type TextProps, type ViewProps } from 'react-native';\nimport { TimePicker, formatTime, type TimeValue } from '@/components/ui/time-picker';\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 s.card,\n screen\n ? { flex: 1, borderWidth: 0, backgroundColor: colors.background }\n : { borderRadius: radius.xl, borderColor: colors.border, backgroundColor: colors.card },\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\nconst s = StyleSheet.create({\n card: { padding: 16, borderWidth: StyleSheet.hairlineWidth, gap: 12 },\n head: { flexDirection: 'row', justifyContent: 'space-between', alignItems: 'flex-start', gap: 12 },\n headText: { flexShrink: 1, gap: 2 },\n title: { fontSize: 15, fontWeight: '400' },\n meta: { fontSize: 13, fontWeight: '400', fontVariant: ['tabular-nums'] },\n hint: { fontSize: 12.5, textAlign: 'center' },\n note: { paddingVertical: 10, alignItems: 'center' },\n button: { height: 46, alignItems: 'center', justifyContent: 'center' },\n buttonOff: { opacity: 0.45 },\n buttonText: { fontSize: 15, fontWeight: '400' },\n});\n\nexport default function Reminder({ screen }: DemoProps) {\n const [time, setTime] = useState<TimeValue>({ hours: 21, minutes: 30 });\n return (\n <Card screen={screen}>\n <View style={s.head}>\n <Ink style={s.title}>Daily journal reminder</Ink>\n <Ink tone=\"muted\" style={s.meta}>{formatTime(time, false)}</Ink>\n </View>\n <TimePicker value={time} onChange={setTime} hour12={false} minuteInterval={15} />\n <Ink tone=\"muted\" style={s.hint}>24-hour clock, 15-minute steps.</Ink>\n </Card>\n );\n}\n"
|
|
241
257
|
},
|
|
242
258
|
{
|
|
243
259
|
"name": "InSheet",
|
|
244
260
|
"title": "In Sheet",
|
|
245
|
-
"code": "import React, { useState } from 'react';\nimport { Pressable, StyleSheet, Text, View, type TextProps, type ViewProps } from 'react-native';\nimport { TimePicker, formatTime, type TimeValue } from '@/components/ui/time-picker';\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 s.card,\n { borderRadius: radius.xl, borderColor: colors.border, backgroundColor: colors.card },\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\nconst s = StyleSheet.create({\n card: { padding: 16, borderWidth: StyleSheet.hairlineWidth, gap: 12 },\n head: { flexDirection: 'row', justifyContent: 'space-between', alignItems: 'flex-start', gap: 12 },\n headText: { flexShrink: 1, gap: 2 },\n title: { fontSize: 15, fontWeight: '400' },\n meta: { fontSize: 13, fontWeight: '400', fontVariant: ['tabular-nums'] },\n hint: { fontSize: 12.5, textAlign: 'center' },\n note: { paddingVertical: 10, alignItems: 'center' },\n button: { height: 46, alignItems: 'center', justifyContent: 'center' },\n buttonOff: { opacity: 0.45 },\n buttonText: { fontSize: 15, fontWeight: '400' },\n});\n\nexport default function InSheet() {\n const [pickup, setPickup] = useState<TimeValue | null>({ hours: 18, minutes: 15 });\n const [delivery, setDelivery] = useState<TimeValue | null>(null);\n return (\n <Card>\n <Ink style={s.title}>Order #4821 · Green Basket</Ink>\n <TimePicker presentation=\"sheet\" label=\"Pickup time\" title=\"When will you pick up?\" value={pickup} onChange={setPickup} minuteInterval={5} hour12 />\n <TimePicker\n presentation=\"sheet\"\n variant=\"chips\"\n label=\"Delivery slot\"\n title=\"Choose a delivery slot\"\n value={delivery}\n onChange={setDelivery}\n hour12\n startTime=\"08:00\"\n endTime=\"21:00\"\n minuteInterval={60}\n bookedTimes={['08:00', '12:00', '18:00']}\n groupByPeriod\n success={delivery ? 'Free delivery in this slot' : undefined}\n helper=\"We'll text you when the driver is close.\"\n />\n </Card>\n );\n}\n"
|
|
261
|
+
"code": "import React, { useState } from 'react';\nimport { Pressable, StyleSheet, Text, View, type TextProps, type ViewProps } from 'react-native';\nimport { TimePicker, formatTime, type TimeValue } from '@/components/ui/time-picker';\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 s.card,\n screen\n ? { flex: 1, borderWidth: 0, backgroundColor: colors.background }\n : { borderRadius: radius.xl, borderColor: colors.border, backgroundColor: colors.card },\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\nconst s = StyleSheet.create({\n card: { padding: 16, borderWidth: StyleSheet.hairlineWidth, gap: 12 },\n head: { flexDirection: 'row', justifyContent: 'space-between', alignItems: 'flex-start', gap: 12 },\n headText: { flexShrink: 1, gap: 2 },\n title: { fontSize: 15, fontWeight: '400' },\n meta: { fontSize: 13, fontWeight: '400', fontVariant: ['tabular-nums'] },\n hint: { fontSize: 12.5, textAlign: 'center' },\n note: { paddingVertical: 10, alignItems: 'center' },\n button: { height: 46, alignItems: 'center', justifyContent: 'center' },\n buttonOff: { opacity: 0.45 },\n buttonText: { fontSize: 15, fontWeight: '400' },\n});\n\nexport default function InSheet({ screen }: DemoProps) {\n const [pickup, setPickup] = useState<TimeValue | null>({ hours: 18, minutes: 15 });\n const [delivery, setDelivery] = useState<TimeValue | null>(null);\n return (\n <Card screen={screen}>\n <Ink style={s.title}>Order #4821 · Green Basket</Ink>\n <TimePicker presentation=\"sheet\" label=\"Pickup time\" title=\"When will you pick up?\" value={pickup} onChange={setPickup} minuteInterval={5} hour12 />\n <TimePicker\n presentation=\"sheet\"\n variant=\"chips\"\n label=\"Delivery slot\"\n title=\"Choose a delivery slot\"\n value={delivery}\n onChange={setDelivery}\n hour12\n startTime=\"08:00\"\n endTime=\"21:00\"\n minuteInterval={60}\n bookedTimes={['08:00', '12:00', '18:00']}\n groupByPeriod\n success={delivery ? 'Free delivery in this slot' : undefined}\n helper=\"We'll text you when the driver is close.\"\n />\n </Card>\n );\n}\n"
|
|
246
262
|
},
|
|
247
263
|
{
|
|
248
264
|
"name": "ClockInSheet",
|
|
249
265
|
"title": "Clock In Sheet",
|
|
250
|
-
"code": "import React, { useState } from 'react';\nimport { Pressable, StyleSheet, Text, View, type TextProps, type ViewProps } from 'react-native';\nimport { TimePicker, formatTime, type TimeValue } from '@/components/ui/time-picker';\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 s.card,\n { borderRadius: radius.xl, borderColor: colors.border, backgroundColor: colors.card },\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\nconst s = StyleSheet.create({\n card: { padding: 16, borderWidth: StyleSheet.hairlineWidth, gap: 12 },\n head: { flexDirection: 'row', justifyContent: 'space-between', alignItems: 'flex-start', gap: 12 },\n headText: { flexShrink: 1, gap: 2 },\n title: { fontSize: 15, fontWeight: '400' },\n meta: { fontSize: 13, fontWeight: '400', fontVariant: ['tabular-nums'] },\n hint: { fontSize: 12.5, textAlign: 'center' },\n note: { paddingVertical: 10, alignItems: 'center' },\n button: { height: 46, alignItems: 'center', justifyContent: 'center' },\n buttonOff: { opacity: 0.45 },\n buttonText: { fontSize: 15, fontWeight: '400' },\n});\n\nexport default function ClockInSheet() {\n const [start, setStart] = useState<TimeValue | null>({ hours: 9, minutes: 0 });\n const [end, setEnd] = useState<TimeValue | null>(null);\n return (\n <Card>\n <Ink style={s.title}>Focus block · Thursday</Ink>\n <TimePicker presentation=\"sheet\" variant=\"clock\" label=\"Starts\" title=\"Start time\" value={start} onChange={setStart} hour12 />\n <TimePicker\n presentation=\"sheet\"\n variant=\"clock\"\n label=\"Ends\"\n title=\"End time\"\n value={end}\n onChange={setEnd}\n minuteInterval={5}\n hour12\n helper=\"Snaps to 5-minute steps.\"\n />\n </Card>\n );\n}\n"
|
|
266
|
+
"code": "import React, { useState } from 'react';\nimport { Pressable, StyleSheet, Text, View, type TextProps, type ViewProps } from 'react-native';\nimport { TimePicker, formatTime, type TimeValue } from '@/components/ui/time-picker';\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 s.card,\n screen\n ? { flex: 1, borderWidth: 0, backgroundColor: colors.background }\n : { borderRadius: radius.xl, borderColor: colors.border, backgroundColor: colors.card },\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\nconst s = StyleSheet.create({\n card: { padding: 16, borderWidth: StyleSheet.hairlineWidth, gap: 12 },\n head: { flexDirection: 'row', justifyContent: 'space-between', alignItems: 'flex-start', gap: 12 },\n headText: { flexShrink: 1, gap: 2 },\n title: { fontSize: 15, fontWeight: '400' },\n meta: { fontSize: 13, fontWeight: '400', fontVariant: ['tabular-nums'] },\n hint: { fontSize: 12.5, textAlign: 'center' },\n note: { paddingVertical: 10, alignItems: 'center' },\n button: { height: 46, alignItems: 'center', justifyContent: 'center' },\n buttonOff: { opacity: 0.45 },\n buttonText: { fontSize: 15, fontWeight: '400' },\n});\n\nexport default function ClockInSheet({ screen }: DemoProps) {\n const [start, setStart] = useState<TimeValue | null>({ hours: 9, minutes: 0 });\n const [end, setEnd] = useState<TimeValue | null>(null);\n return (\n <Card screen={screen}>\n <Ink style={s.title}>Focus block · Thursday</Ink>\n <TimePicker presentation=\"sheet\" variant=\"clock\" label=\"Starts\" title=\"Start time\" value={start} onChange={setStart} hour12 />\n <TimePicker\n presentation=\"sheet\"\n variant=\"clock\"\n label=\"Ends\"\n title=\"End time\"\n value={end}\n onChange={setEnd}\n minuteInterval={5}\n hour12\n helper=\"Snaps to 5-minute steps.\"\n />\n </Card>\n );\n}\n"
|
|
251
267
|
}
|
|
252
268
|
]
|
|
253
269
|
}
|
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
"files": [
|
|
7
7
|
"src/components/ui/timeline.tsx"
|
|
8
8
|
],
|
|
9
|
+
"summary": "A vertical timeline whose connector fills down to the current step as dots check off.",
|
|
9
10
|
"props": [
|
|
10
11
|
[
|
|
11
12
|
"items",
|
|
@@ -116,6 +117,21 @@
|
|
|
116
117
|
"Root style — for variant=\"scroll\" this is the scroll view, so set a height."
|
|
117
118
|
]
|
|
118
119
|
],
|
|
120
|
+
"behavior": [
|
|
121
|
+
[
|
|
122
|
+
"Motion",
|
|
123
|
+
"Items rise in, then the line fills to the current step: done dots draw their check as it arrives and the current dot pulses. The scroll variant ties the fill to the scroll position."
|
|
124
|
+
],
|
|
125
|
+
[
|
|
126
|
+
"Reduced motion",
|
|
127
|
+
"Everything renders in its final state with no rise, fill or pulse, the same as animated={false}."
|
|
128
|
+
],
|
|
129
|
+
[
|
|
130
|
+
"Screen readers",
|
|
131
|
+
"The timeline is a list; each entry is read as its title, time, description and status, and links are exposed as links."
|
|
132
|
+
]
|
|
133
|
+
],
|
|
134
|
+
"usage": "import { Timeline } from '@/components/ui/timeline';\n\n<Timeline\n items={[\n { title: 'Order placed', time: '9:41 AM', status: 'done' },\n { title: 'Out for delivery', time: '1:15 PM', status: 'current' },\n { title: 'Delivered', status: 'upcoming' },\n ]}\n/>",
|
|
119
135
|
"examples": [
|
|
120
136
|
{
|
|
121
137
|
"name": "OrderTracking",
|
|
@@ -145,12 +161,12 @@
|
|
|
145
161
|
{
|
|
146
162
|
"name": "Journey",
|
|
147
163
|
"title": "Journey",
|
|
148
|
-
"code": "import { Text, View } from 'react-native';\nimport { Timeline } from '@/components/ui/timeline';\nimport { alpha, useKit } from '@/lib/kit';\n\nconst pic = (seed: string) => `https://picsum.photos/seed/${seed}/600/400`;\n\nexport default function Journey() {\n const { colors } = useKit();\n return (\n <Timeline\n variant=\"scroll\"\n style={{ height: 560 }}\n header={\n <View style={{ gap: 2, paddingBottom: 20 }}>\n <Text style={{ fontSize: 12.5, letterSpacing: 0.4, color: colors.mutedForeground }}>12 DAYS · 1,140 KM</Text>\n <Text style={{ fontSize: 22, letterSpacing: -0.5, color: colors.foreground }}>Our journey through Portugal</Text>\n </View>\n }\n items={[\n { title: 'Landed in Lisbon', time: 'Day 1', description: 'Late flight, pastel de nata at the airport, tram 28 into Alfama before the rain.', images: [{ uri: pic('lisbon'), aspectRatio: 3 / 2 }], tags: ['Lisbon'] },\n { title: 'Lost in Alfama', time: 'Day 2', description: 'Stairs, tiles, a fado bar that only had six chairs. We stayed until two.', images: [pic('alfama'), pic('tiles')] },\n { title: 'Sintra day trip', time: 'Day 3', description: 'Fog on Pena Palace in the morning, sun in the Moorish castle by noon.', images: [pic('sintra'), pic('palace'), pic('forest'), pic('castle')] },\n { title: 'Rented the car', time: 'Day 4', description: 'A tiny Fiat with a stubborn second gear. Named it Rosa.' },\n { title: 'Comporta beaches', time: 'Day 5', description: 'Rice fields on one side, empty dunes on the other.', images: [{ uri: pic('dunes'), aspectRatio: 16 / 9 }], tags: ['Beach', 'Alentejo'] },\n { title: 'Évora', time: 'Day 6', description: 'Roman temple at sunset and the quietest night of the trip.', images: [pic('evora'), pic('temple'), pic('plains')] },\n { title: 'Wine in the Alentejo', time: 'Day 7', description: 'A cellar tour that turned into lunch with the owner’s family. Ordered a case home.' },\n { title: 'Down to the Algarve', time: 'Day 8', description: 'Benagil caves by kayak — go early, the tour boats arrive at ten.', images: [pic('caves'), pic('kayak')], links: [{ label: 'Kayak rental we used', onPress: () => {} }] },\n { title: 'Lagos', time: 'Day 9', description: 'Cliff walk to Ponta da Piedade and grilled sardines at the harbour.', images: [{ uri: pic('lagos'), aspectRatio: 3 / 2 }] },\n { title: 'Long drive north', time: 'Day 10', description: 'Six hours, two coffee stops, one wrong turn into a cork forest.' },\n { title: 'Porto', time: 'Day 11', status: 'current', description: 'Ribeira, the bridge at blue hour, port lodges across the river.', images: [pic('porto'), pic('bridge'), pic('river')], tags: ['Now'] },\n { title: 'Douro Valley', time: 'Day 12', status: 'upcoming', description: 'Train along the river to Pinhão, then home.' },\n ]}\n />\n );\n}\n"
|
|
164
|
+
"code": "import { Text, View } from 'react-native';\nimport { Timeline } from '@/components/ui/timeline';\nimport { alpha, useKit } from '@/lib/kit';\n\n/** From the Expo Go viewer: `screen` = this IS the phone screen, so no mock frame; `bottomInset` = home indicator. */\ntype DemoProps = { screen?: boolean; bottomInset?: number };\n\nconst pic = (seed: string) => `https://picsum.photos/seed/${seed}/600/400`;\n\nexport default function Journey({ screen }: DemoProps) {\n const { colors } = useKit();\n return (\n <Timeline\n variant=\"scroll\"\n style={screen ? { flex: 1 } : { height: 560 }}\n header={\n <View style={{ gap: 2, paddingBottom: 20 }}>\n <Text style={{ fontSize: 12.5, letterSpacing: 0.4, color: colors.mutedForeground }}>12 DAYS · 1,140 KM</Text>\n <Text style={{ fontSize: 22, letterSpacing: -0.5, color: colors.foreground }}>Our journey through Portugal</Text>\n </View>\n }\n items={[\n { title: 'Landed in Lisbon', time: 'Day 1', description: 'Late flight, pastel de nata at the airport, tram 28 into Alfama before the rain.', images: [{ uri: pic('lisbon'), aspectRatio: 3 / 2 }], tags: ['Lisbon'] },\n { title: 'Lost in Alfama', time: 'Day 2', description: 'Stairs, tiles, a fado bar that only had six chairs. We stayed until two.', images: [pic('alfama'), pic('tiles')] },\n { title: 'Sintra day trip', time: 'Day 3', description: 'Fog on Pena Palace in the morning, sun in the Moorish castle by noon.', images: [pic('sintra'), pic('palace'), pic('forest'), pic('castle')] },\n { title: 'Rented the car', time: 'Day 4', description: 'A tiny Fiat with a stubborn second gear. Named it Rosa.' },\n { title: 'Comporta beaches', time: 'Day 5', description: 'Rice fields on one side, empty dunes on the other.', images: [{ uri: pic('dunes'), aspectRatio: 16 / 9 }], tags: ['Beach', 'Alentejo'] },\n { title: 'Évora', time: 'Day 6', description: 'Roman temple at sunset and the quietest night of the trip.', images: [pic('evora'), pic('temple'), pic('plains')] },\n { title: 'Wine in the Alentejo', time: 'Day 7', description: 'A cellar tour that turned into lunch with the owner’s family. Ordered a case home.' },\n { title: 'Down to the Algarve', time: 'Day 8', description: 'Benagil caves by kayak — go early, the tour boats arrive at ten.', images: [pic('caves'), pic('kayak')], links: [{ label: 'Kayak rental we used', onPress: () => {} }] },\n { title: 'Lagos', time: 'Day 9', description: 'Cliff walk to Ponta da Piedade and grilled sardines at the harbour.', images: [{ uri: pic('lagos'), aspectRatio: 3 / 2 }] },\n { title: 'Long drive north', time: 'Day 10', description: 'Six hours, two coffee stops, one wrong turn into a cork forest.' },\n { title: 'Porto', time: 'Day 11', status: 'current', description: 'Ribeira, the bridge at blue hour, port lodges across the river.', images: [pic('porto'), pic('bridge'), pic('river')], tags: ['Now'] },\n { title: 'Douro Valley', time: 'Day 12', status: 'upcoming', description: 'Train along the river to Pinhão, then home.' },\n ]}\n />\n );\n}\n"
|
|
149
165
|
},
|
|
150
166
|
{
|
|
151
167
|
"name": "DeliveryScroll",
|
|
152
168
|
"title": "Delivery Scroll",
|
|
153
|
-
"code": "import { Text, View } from 'react-native';\nimport { Timeline } from '@/components/ui/timeline';\nimport { alpha, useKit } from '@/lib/kit';\n\nconst pic = (seed: string) => `https://picsum.photos/seed/${seed}/600/400`;\n\nexport default function DeliveryScroll() {\n const { colors } = useKit();\n return (\n <Timeline\n variant=\"scroll\"\n style={{ height: 560 }}\n header={\n <View style={{ gap: 2, paddingBottom: 20 }}>\n <Text style={{ fontSize: 12.5, letterSpacing: 0.4, color: colors.mutedForeground }}>PARCEL DHL 4471 9902 31</Text>\n <Text style={{ fontSize: 20, letterSpacing: -0.4, color: colors.foreground }}>Out for delivery</Text>\n </View>\n }\n items={[\n { title: 'Order placed', time: 'Oct 6 · 18:22', description: 'Walnut desk lamp, linen shade · paid with Visa', icon: 'cart' },\n { title: 'Payment confirmed', time: 'Oct 6 · 18:23', icon: 'tag' },\n { title: 'Packed', time: 'Oct 7 · 10:40', description: 'Double-boxed with paper padding. Photo from the packing station:', icon: 'package', images: [{ uri: pic('parcel'), aspectRatio: 3 / 2 }] },\n { title: 'Handed to carrier', time: 'Oct 7 · 16:05', description: 'Picked up from the Leipzig warehouse', icon: 'truck' },\n { title: 'Left the sorting hub', time: 'Oct 8 · 02:14', description: 'Hauptumschlagbasis Leipzig', icon: 'truck' },\n { title: 'Arrived in the Netherlands', time: 'Oct 9 · 07:50', description: 'Customs cleared, no fees due', icon: 'home' },\n { title: 'Delivery attempt failed', time: 'Oct 10 · 14:31', description: 'No one home and no safe place. A card was left in the mailbox.', status: 'error', icon: 'home' },\n { title: 'Rescheduled', time: 'Oct 10 · 19:02', description: 'You picked Monday, 12:00–18:00', icon: 'chat' },\n { title: 'Out for delivery', time: '11:05', description: 'Jonas is 6 stops away in a white van', icon: 'truck', status: 'current', links: [{ label: 'Track on map', onPress: () => {} }] },\n { title: 'Delivered', time: 'Est. 15:30', description: 'Leave at the front door if no one answers', icon: 'home', status: 'upcoming' },\n { title: 'Rate your delivery', status: 'upcoming', icon: 'star' },\n ]}\n />\n );\n}\n"
|
|
169
|
+
"code": "import { Text, View } from 'react-native';\nimport { Timeline } from '@/components/ui/timeline';\nimport { alpha, useKit } from '@/lib/kit';\n\n/** From the Expo Go viewer: `screen` = this IS the phone screen, so no mock frame; `bottomInset` = home indicator. */\ntype DemoProps = { screen?: boolean; bottomInset?: number };\n\nconst pic = (seed: string) => `https://picsum.photos/seed/${seed}/600/400`;\n\nexport default function DeliveryScroll({ screen }: DemoProps) {\n const { colors } = useKit();\n return (\n <Timeline\n variant=\"scroll\"\n style={screen ? { flex: 1 } : { height: 560 }}\n header={\n <View style={{ gap: 2, paddingBottom: 20 }}>\n <Text style={{ fontSize: 12.5, letterSpacing: 0.4, color: colors.mutedForeground }}>PARCEL DHL 4471 9902 31</Text>\n <Text style={{ fontSize: 20, letterSpacing: -0.4, color: colors.foreground }}>Out for delivery</Text>\n </View>\n }\n items={[\n { title: 'Order placed', time: 'Oct 6 · 18:22', description: 'Walnut desk lamp, linen shade · paid with Visa', icon: 'cart' },\n { title: 'Payment confirmed', time: 'Oct 6 · 18:23', icon: 'tag' },\n { title: 'Packed', time: 'Oct 7 · 10:40', description: 'Double-boxed with paper padding. Photo from the packing station:', icon: 'package', images: [{ uri: pic('parcel'), aspectRatio: 3 / 2 }] },\n { title: 'Handed to carrier', time: 'Oct 7 · 16:05', description: 'Picked up from the Leipzig warehouse', icon: 'truck' },\n { title: 'Left the sorting hub', time: 'Oct 8 · 02:14', description: 'Hauptumschlagbasis Leipzig', icon: 'truck' },\n { title: 'Arrived in the Netherlands', time: 'Oct 9 · 07:50', description: 'Customs cleared, no fees due', icon: 'home' },\n { title: 'Delivery attempt failed', time: 'Oct 10 · 14:31', description: 'No one home and no safe place. A card was left in the mailbox.', status: 'error', icon: 'home' },\n { title: 'Rescheduled', time: 'Oct 10 · 19:02', description: 'You picked Monday, 12:00–18:00', icon: 'chat' },\n { title: 'Out for delivery', time: '11:05', description: 'Jonas is 6 stops away in a white van', icon: 'truck', status: 'current', links: [{ label: 'Track on map', onPress: () => {} }] },\n { title: 'Delivered', time: 'Est. 15:30', description: 'Leave at the front door if no one answers', icon: 'home', status: 'upcoming' },\n { title: 'Rate your delivery', status: 'upcoming', icon: 'star' },\n ]}\n />\n );\n}\n"
|
|
154
170
|
}
|
|
155
171
|
]
|
|
156
172
|
}
|