@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
|
@@ -238,32 +238,32 @@
|
|
|
238
238
|
{
|
|
239
239
|
"name": "Alarm",
|
|
240
240
|
"title": "Alarm",
|
|
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/** 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',
|
|
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', flexWrap: 'wrap', justifyContent: 'space-between', alignItems: 'flex-start', columnGap: 12, rowGap: 4 },\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: { minHeight: 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, { flexShrink: 1 }]}>{until(time)}</Ink>\n </View>\n <TimePicker value={time} onChange={setTime} hour12 />\n </Card>\n );\n}\n"
|
|
242
242
|
},
|
|
243
243
|
{
|
|
244
244
|
"name": "Clock",
|
|
245
245
|
"title": "Clock",
|
|
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/** 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',
|
|
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', flexWrap: 'wrap', justifyContent: 'space-between', alignItems: 'flex-start', columnGap: 12, rowGap: 4 },\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: { minHeight: 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"
|
|
247
247
|
},
|
|
248
248
|
{
|
|
249
249
|
"name": "Appointment",
|
|
250
250
|
"title": "Appointment",
|
|
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/** 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',
|
|
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/** Controls stop at iOS's largest standard text size; content text stays uncapped. */\nconst FONT_CAP = 1.35;\n\n/** From the Expo Go viewer: `screen` = this IS the phone screen, so no mock frame; `bottomInset` = home indicator. */\ntype DemoProps = { screen?: boolean; bottomInset?: number };\n\n/** Demo card that follows the kit theme, so the docs radius / accent picker reshapes it too. */\nfunction Card({ 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', flexWrap: 'wrap', justifyContent: 'space-between', alignItems: 'flex-start', columnGap: 12, rowGap: 4 },\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: { minHeight: 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 maxFontSizeMultiplier={FONT_CAP} style={[s.buttonText, { color: colors.accentForeground }]}>{slot ? `Book ${formatTime(slot)} · 30 min` : 'Choose a time'}</Text>\n </Pressable>\n </Card>\n );\n}\n"
|
|
252
252
|
},
|
|
253
253
|
{
|
|
254
254
|
"name": "Reminder",
|
|
255
255
|
"title": "Reminder",
|
|
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/** 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',
|
|
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', flexWrap: 'wrap', justifyContent: 'space-between', alignItems: 'flex-start', columnGap: 12, rowGap: 4 },\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: { minHeight: 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, { flexShrink: 1 }]}>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"
|
|
257
257
|
},
|
|
258
258
|
{
|
|
259
259
|
"name": "InSheet",
|
|
260
260
|
"title": "In Sheet",
|
|
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/** 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',
|
|
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', flexWrap: 'wrap', justifyContent: 'space-between', alignItems: 'flex-start', columnGap: 12, rowGap: 4 },\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: { minHeight: 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"
|
|
262
262
|
},
|
|
263
263
|
{
|
|
264
264
|
"name": "ClockInSheet",
|
|
265
265
|
"title": "Clock In Sheet",
|
|
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/** 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',
|
|
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', flexWrap: 'wrap', justifyContent: 'space-between', alignItems: 'flex-start', columnGap: 12, rowGap: 4 },\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: { minHeight: 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"
|
|
267
267
|
}
|
|
268
268
|
]
|
|
269
269
|
}
|
|
@@ -136,17 +136,17 @@
|
|
|
136
136
|
{
|
|
137
137
|
"name": "OrderTracking",
|
|
138
138
|
"title": "Order Tracking",
|
|
139
|
-
"code": "import { Text, View } from 'react-native';\nimport { Timeline } from '@/components/ui/timeline';\nimport { alpha, useKit } from '@/lib/kit';\n\nexport default function OrderTracking() {\n const { colors, radius, dark } = useKit();\n return (\n <View style={{ gap: 18 }}>\n <View style={{ flexDirection: 'row', alignItems: 'flex-end', justifyContent: 'space-between' }}>\n <View style={{ gap: 2 }}>\n <Text style={{ fontSize: 12.5, letterSpacing: 0.4, color: colors.mutedForeground }}>ORDER #4821</Text>\n <Text style={{ fontSize: 20, letterSpacing: -0.4, color: colors.foreground }}>Arriving today</Text>\n </View>\n <View\n style={{\n paddingHorizontal: 10,\n paddingVertical: 4,\n borderRadius: radius.capsule(24),\n backgroundColor: alpha(colors.accent, dark ? 0.2 : 0.1),\n }}\n >\n <Text style={{ fontSize: 12.5, color: colors.accent }}>by 6 PM</Text>\n </View>\n </View>\n <Timeline\n items={[\n { title: 'Ordered', time: 'Mon 9:12', description: 'Paid with Apple Pay · 2 items', icon: 'cart' },\n { title: 'Packed', time: 'Tue 16:40', description: 'Left the Rotterdam warehouse', icon: 'package' },\n {\n title: 'Out for delivery',\n time: '11:05',\n description: 'Jonas is 6 stops away in a white van',\n icon: 'truck',\n status: 'current',\n },\n { title: 'Delivered', time: 'Est. 17:30', description: 'Leave at the front door if no one answers', icon: 'home', status: 'upcoming' },\n ]}\n />\n </View>\n );\n}\n"
|
|
139
|
+
"code": "import { Text, View } from 'react-native';\nimport { Timeline } from '@/components/ui/timeline';\nimport { alpha, useKit } from '@/lib/kit';\n\n/** Control text stops at iOS's largest standard size. */\nconst FONT_CAP = 1.35;\n\nexport default function OrderTracking() {\n const { colors, radius, dark } = useKit();\n return (\n <View style={{ gap: 18 }}>\n <View style={{ flexDirection: 'row', alignItems: 'flex-end', justifyContent: 'space-between' }}>\n <View style={{ flexShrink: 1, gap: 2 }}>\n <Text style={{ fontSize: 12.5, letterSpacing: 0.4, color: colors.mutedForeground }}>ORDER #4821</Text>\n <Text style={{ fontSize: 20, letterSpacing: -0.4, color: colors.foreground }}>Arriving today</Text>\n </View>\n <View\n style={{\n paddingHorizontal: 10,\n paddingVertical: 4,\n borderRadius: radius.capsule(24),\n backgroundColor: alpha(colors.accent, dark ? 0.2 : 0.1),\n }}\n >\n <Text maxFontSizeMultiplier={FONT_CAP} style={{ fontSize: 12.5, color: colors.accent }}>by 6 PM</Text>\n </View>\n </View>\n <Timeline\n items={[\n { title: 'Ordered', time: 'Mon 9:12', description: 'Paid with Apple Pay · 2 items', icon: 'cart' },\n { title: 'Packed', time: 'Tue 16:40', description: 'Left the Rotterdam warehouse', icon: 'package' },\n {\n title: 'Out for delivery',\n time: '11:05',\n description: 'Jonas is 6 stops away in a white van',\n icon: 'truck',\n status: 'current',\n },\n { title: 'Delivered', time: 'Est. 17:30', description: 'Leave at the front door if no one answers', icon: 'home', status: 'upcoming' },\n ]}\n />\n </View>\n );\n}\n"
|
|
140
140
|
},
|
|
141
141
|
{
|
|
142
142
|
"name": "Activity",
|
|
143
143
|
"title": "Activity",
|
|
144
|
-
"code": "import { Text, View } from 'react-native';\nimport { Timeline } from '@/components/ui/timeline';\nimport { alpha, useKit } from '@/lib/kit';\n\nexport default function Activity() {\n const { colors, radius, dark } = useKit();\n return (\n <Timeline\n items={[\n {\n title: 'Priya Shah commented',\n time: '2m',\n avatar: { name: 'Priya Shah', src: 'https://i.pravatar.cc/120?img=32' },\n status: 'current',\n content: (\n <View\n style={{\n padding: 12,\n borderRadius: radius.md,\n backgroundColor: alpha(colors.foreground, dark ? 0.06 : 0.035),\n }}\n >\n <Text style={{ fontSize: 14, lineHeight: 20, color: colors.foreground }}>\n Love the new onboarding — can we try the shorter headline on step two?\n </Text>\n </View>\n ),\n },\n {\n title: 'Marco Bianchi uploaded 3 photos',\n time: '1h',\n description: 'Moodboard · Autumn campaign',\n avatar: { name: 'Marco Bianchi', src: 'https://i.pravatar.cc/120?img=15' },\n image: { uri: 'https://picsum.photos/id/1043/800/450', aspectRatio: 16 / 9 },\n },\n {\n title: 'Lena Fischer shared a file',\n time: '3h',\n avatar: { name: 'Lena Fischer' },\n content: (\n <View\n style={{\n flexDirection: 'row',\n alignItems: 'center',\n gap: 12,\n padding: 12,\n borderRadius: radius.md,\n borderWidth: 1,\n borderColor: alpha(colors.foreground, dark ? 0.1 : 0.06),\n backgroundColor: colors.card,\n }}\n >\n <View\n style={{\n width: 36,\n height: 36,\n borderRadius: radius.sm,\n backgroundColor: alpha(colors.danger, 0.12),\n alignItems: 'center',\n justifyContent: 'center',\n }}\n >\n <Text style={{ fontSize: 11, color: colors.danger }}>PDF</Text>\n </View>\n <View style={{ flex: 1 }}>\n <Text numberOfLines={1} style={{ fontSize: 14, color: colors.foreground }}>Q4 brand guidelines.pdf</Text>\n <Text style={{ fontSize: 12.5, color: colors.mutedForeground }}>4.2 MB · 18 pages</Text>\n </View>\n </View>\n ),\n },\n { title: 'Nightly build failed', time: 'Yesterday', description: 'iOS · Signing certificate expired', status: 'error', icon: 'bug' },\n ]}\n />\n );\n}\n"
|
|
144
|
+
"code": "import { Text, View } from 'react-native';\nimport { Timeline } from '@/components/ui/timeline';\nimport { alpha, useKit } from '@/lib/kit';\n\n/** Control text stops at iOS's largest standard size. */\nconst FONT_CAP = 1.35;\n\nexport default function Activity() {\n const { colors, radius, dark } = useKit();\n return (\n <Timeline\n items={[\n {\n title: 'Priya Shah commented',\n time: '2m',\n avatar: { name: 'Priya Shah', src: 'https://i.pravatar.cc/120?img=32' },\n status: 'current',\n content: (\n <View\n style={{\n padding: 12,\n borderRadius: radius.md,\n backgroundColor: alpha(colors.foreground, dark ? 0.06 : 0.035),\n }}\n >\n <Text style={{ fontSize: 14, lineHeight: 20, color: colors.foreground }}>\n Love the new onboarding — can we try the shorter headline on step two?\n </Text>\n </View>\n ),\n },\n {\n title: 'Marco Bianchi uploaded 3 photos',\n time: '1h',\n description: 'Moodboard · Autumn campaign',\n avatar: { name: 'Marco Bianchi', src: 'https://i.pravatar.cc/120?img=15' },\n image: { uri: 'https://picsum.photos/id/1043/800/450', aspectRatio: 16 / 9 },\n },\n {\n title: 'Lena Fischer shared a file',\n time: '3h',\n avatar: { name: 'Lena Fischer' },\n content: (\n <View\n style={{\n flexDirection: 'row',\n alignItems: 'center',\n gap: 12,\n padding: 12,\n borderRadius: radius.md,\n borderWidth: 1,\n borderColor: alpha(colors.foreground, dark ? 0.1 : 0.06),\n backgroundColor: colors.card,\n }}\n >\n <View\n style={{\n width: 36,\n height: 36,\n borderRadius: radius.sm,\n backgroundColor: alpha(colors.danger, 0.12),\n alignItems: 'center',\n justifyContent: 'center',\n }}\n >\n <Text maxFontSizeMultiplier={FONT_CAP} style={{ fontSize: 11, color: colors.danger }}>PDF</Text>\n </View>\n <View style={{ flex: 1 }}>\n <Text numberOfLines={1} style={{ fontSize: 14, color: colors.foreground }}>Q4 brand guidelines.pdf</Text>\n <Text style={{ fontSize: 12.5, color: colors.mutedForeground }}>4.2 MB · 18 pages</Text>\n </View>\n </View>\n ),\n },\n { title: 'Nightly build failed', time: 'Yesterday', description: 'iOS · Signing certificate expired', status: 'error', icon: 'bug' },\n ]}\n />\n );\n}\n"
|
|
145
145
|
},
|
|
146
146
|
{
|
|
147
147
|
"name": "Release",
|
|
148
148
|
"title": "Release",
|
|
149
|
-
"code": "import { Text, View } from 'react-native';\nimport { Timeline } from '@/components/ui/timeline';\nimport { alpha, useKit } from '@/lib/kit';\n\nexport default function Release() {\n const { colors, radius, dark } = useKit();\n return (\n <Timeline\n variant=\"card\"\n items={[\n { title: 'v3.0 — Widgets', time: 'Next week', description: 'Home-screen widgets for iOS and Android.', icon: 'rocket', status: 'upcoming' },\n {\n title: 'v2.4 — Offline mode',\n time: 'Oct 8',\n description: 'Edit notes without a connection; changes sync when you are back online.',\n icon: 'star',\n status: 'current',\n content: (\n <View style={{ flexDirection: 'row', flexWrap: 'wrap', gap: 6 }}>\n {['New', 'Sync', 'iOS', 'Android'].map((t) => (\n <View\n key={t}\n style={{\n paddingHorizontal: 9,\n paddingVertical: 3,\n borderRadius: radius.capsule(22),\n backgroundColor: alpha(colors.accent, dark ? 0.2 : 0.1),\n }}\n >\n <Text style={{ fontSize: 12, color: colors.accent }}>{t}</Text>\n </View>\n ))}\n </View>\n ),\n },\n { title: 'v2.3.2 — Fixes', time: 'Sep 29', description: 'Fixed a crash when sharing large PDFs.', icon: 'bug' },\n { title: 'v2.3 — Shared folders', time: 'Sep 12', description: 'Invite people to a folder and edit together.', icon: 'user' },\n ]}\n />\n );\n}\n"
|
|
149
|
+
"code": "import { Text, View } from 'react-native';\nimport { Timeline } from '@/components/ui/timeline';\nimport { alpha, useKit } from '@/lib/kit';\n\n/** Control text stops at iOS's largest standard size. */\nconst FONT_CAP = 1.35;\n\nexport default function Release() {\n const { colors, radius, dark } = useKit();\n return (\n <Timeline\n variant=\"card\"\n items={[\n { title: 'v3.0 — Widgets', time: 'Next week', description: 'Home-screen widgets for iOS and Android.', icon: 'rocket', status: 'upcoming' },\n {\n title: 'v2.4 — Offline mode',\n time: 'Oct 8',\n description: 'Edit notes without a connection; changes sync when you are back online.',\n icon: 'star',\n status: 'current',\n content: (\n <View style={{ flexDirection: 'row', flexWrap: 'wrap', gap: 6 }}>\n {['New', 'Sync', 'iOS', 'Android'].map((t) => (\n <View\n key={t}\n style={{\n paddingHorizontal: 9,\n paddingVertical: 3,\n borderRadius: radius.capsule(22),\n backgroundColor: alpha(colors.accent, dark ? 0.2 : 0.1),\n }}\n >\n <Text maxFontSizeMultiplier={FONT_CAP} style={{ fontSize: 12, color: colors.accent }}>{t}</Text>\n </View>\n ))}\n </View>\n ),\n },\n { title: 'v2.3.2 — Fixes', time: 'Sep 29', description: 'Fixed a crash when sharing large PDFs.', icon: 'bug' },\n { title: 'v2.3 — Shared folders', time: 'Sep 12', description: 'Invite people to a folder and edit together.', icon: 'user' },\n ]}\n />\n );\n}\n"
|
|
150
150
|
},
|
|
151
151
|
{
|
|
152
152
|
"name": "Compact",
|
|
@@ -156,17 +156,17 @@
|
|
|
156
156
|
{
|
|
157
157
|
"name": "Support",
|
|
158
158
|
"title": "Support",
|
|
159
|
-
"code": "import { Text, View } from 'react-native';\nimport { Avatar } from '@/components/ui/avatar';\nimport { Timeline } from '@/components/ui/timeline';\nimport { alpha, useKit } from '@/lib/kit';\n\nexport default function Support() {\n const { colors } = useKit();\n return (\n <View style={{ gap: 14 }}>\n <View style={{ flexDirection: 'row', alignItems: 'center', gap: 10 }}>\n <Avatar size=\"sm\" name=\"Support\" status=\"online\" />\n <Text style={{ fontSize: 15, color: colors.foreground }}>Refund request #RF-2207</Text>\n </View>\n <Timeline\n variant=\"compact\"\n accent={colors.success}\n items={[\n { title: 'Request received', time: 'Oct 9' },\n { title: 'Return label sent', time: 'Oct 9' },\n { title: 'Card refund declined', time: 'Oct 12', status: 'error' },\n { title: 'Refund to store credit', time: 'Now', status: 'current' },\n ]}\n />\n </View>\n );\n}\n"
|
|
159
|
+
"code": "import { Text, View } from 'react-native';\nimport { Avatar } from '@/components/ui/avatar';\nimport { Timeline } from '@/components/ui/timeline';\nimport { alpha, useKit } from '@/lib/kit';\n\nexport default function Support() {\n const { colors } = useKit();\n return (\n <View style={{ gap: 14 }}>\n <View style={{ flexDirection: 'row', alignItems: 'center', gap: 10 }}>\n <Avatar size=\"sm\" name=\"Support\" status=\"online\" />\n <Text style={{ flexShrink: 1, fontSize: 15, color: colors.foreground }}>Refund request #RF-2207</Text>\n </View>\n <Timeline\n variant=\"compact\"\n accent={colors.success}\n items={[\n { title: 'Request received', time: 'Oct 9' },\n { title: 'Return label sent', time: 'Oct 9' },\n { title: 'Card refund declined', time: 'Oct 12', status: 'error' },\n { title: 'Refund to store credit', time: 'Now', status: 'current' },\n ]}\n />\n </View>\n );\n}\n"
|
|
160
160
|
},
|
|
161
161
|
{
|
|
162
162
|
"name": "Journey",
|
|
163
163
|
"title": "Journey",
|
|
164
|
-
"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"
|
|
165
165
|
},
|
|
166
166
|
{
|
|
167
167
|
"name": "DeliveryScroll",
|
|
168
168
|
"title": "Delivery Scroll",
|
|
169
|
-
"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"
|
|
170
170
|
}
|
|
171
171
|
]
|
|
172
172
|
}
|
|
@@ -144,42 +144,42 @@
|
|
|
144
144
|
{
|
|
145
145
|
"name": "Default",
|
|
146
146
|
"title": "Default",
|
|
147
|
-
"code": "import { StyleSheet, View, type ViewProps } from 'react-native';\nimport { Button } from '@/components/ui/button';\nimport { toast, Toaster, ToastProvider, useToast } from '@/components/ui/toast';\nimport { useKit } from '@/lib/kit';\n\n/** Demo frame that follows the kit theme, so the docs radius / accent picker reshapes it too. */\nfunction Frame({ style, ...rest }: ViewProps) {\n const { colors, radius } = useKit();\n return (\n <View\n style={[\n {\n height: 300,\n
|
|
147
|
+
"code": "import { StyleSheet, View, type ViewProps } from 'react-native';\nimport { Button } from '@/components/ui/button';\nimport { toast, Toaster, ToastProvider, useToast } from '@/components/ui/toast';\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 frame that follows the kit theme, so the docs radius / accent picker reshapes it too. In screen mode it is the screen. */\nfunction Frame({ screen, style, ...rest }: ViewProps & { screen?: boolean }) {\n const { colors, radius } = useKit();\n return (\n <View\n style={[\n { padding: 16 },\n screen\n ? { flex: 1, backgroundColor: colors.background }\n : {\n height: 300,\n overflow: 'hidden',\n backgroundColor: colors.card,\n borderColor: colors.border,\n borderWidth: StyleSheet.hairlineWidth,\n borderRadius: radius.xl,\n },\n style,\n ]}\n {...rest}\n />\n );\n}\n\nconst row = { flexDirection: 'row' as const, flexWrap: 'wrap' as const, gap: 8 };\n\nexport default function Default({ screen }: DemoProps) {\n const to = { toaster: 'demo-default' };\n return (\n <Frame screen={screen}>\n <View style={row}>\n <Button\n size=\"sm\"\n variant=\"accent\"\n onPress={() => toast('Event created', { ...to, description: 'Friday, June 14 at 9:00 AM' })}\n >\n Default\n </Button>\n <Button\n size=\"sm\"\n variant=\"outline\"\n onPress={() => toast.success('Changes saved', { ...to, description: 'Synced to all your devices.' })}\n >\n Success\n </Button>\n <Button size=\"sm\" variant=\"outline\" onPress={() => toast.warning('Storage almost full', { ...to, description: '92% of 15 GB used.' })}>\n Warning\n </Button>\n <Button size=\"sm\" variant=\"outline\" onPress={() => toast.error('Could not reach server', to)}>\n Error\n </Button>\n </View>\n <Toaster id=\"demo-default\" />\n </Frame>\n );\n}\n"
|
|
148
148
|
},
|
|
149
149
|
{
|
|
150
150
|
"name": "RichColors",
|
|
151
151
|
"title": "Rich Colors",
|
|
152
|
-
"code": "import { StyleSheet, View, type ViewProps } from 'react-native';\nimport { Button } from '@/components/ui/button';\nimport { toast, Toaster, ToastProvider, useToast } from '@/components/ui/toast';\nimport { useKit } from '@/lib/kit';\n\n/** Demo frame that follows the kit theme, so the docs radius / accent picker reshapes it too. */\nfunction Frame({ style, ...rest }: ViewProps) {\n const { colors, radius } = useKit();\n return (\n <View\n style={[\n {\n height: 300,\n
|
|
152
|
+
"code": "import { StyleSheet, View, type ViewProps } from 'react-native';\nimport { Button } from '@/components/ui/button';\nimport { toast, Toaster, ToastProvider, useToast } from '@/components/ui/toast';\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 frame that follows the kit theme, so the docs radius / accent picker reshapes it too. In screen mode it is the screen. */\nfunction Frame({ screen, style, ...rest }: ViewProps & { screen?: boolean }) {\n const { colors, radius } = useKit();\n return (\n <View\n style={[\n { padding: 16 },\n screen\n ? { flex: 1, backgroundColor: colors.background }\n : {\n height: 300,\n overflow: 'hidden',\n backgroundColor: colors.card,\n borderColor: colors.border,\n borderWidth: StyleSheet.hairlineWidth,\n borderRadius: radius.xl,\n },\n style,\n ]}\n {...rest}\n />\n );\n}\n\nconst row = { flexDirection: 'row' as const, flexWrap: 'wrap' as const, gap: 8 };\n\nexport default function RichColors({ screen }: DemoProps) {\n const { colors } = useKit();\n const to = { toaster: 'demo-rich' };\n return (\n <Frame screen={screen}>\n <View style={row}>\n <Button\n size=\"sm\"\n variant=\"soft\"\n color={colors.success}\n onPress={() => toast.success('Payment received', { ...to, description: '₹18,500 from Acme Inc.' })}\n >\n Success\n </Button>\n <Button\n size=\"sm\"\n variant=\"soft\"\n color={colors.danger}\n onPress={() => toast.error('Card declined', { ...to, description: 'Try another payment method.' })}\n >\n Error\n </Button>\n <Button size=\"sm\" variant=\"soft\" color={colors.warning} onPress={() => toast.warning('You are offline', { ...to, description: 'Changes will sync when you reconnect.' })}>\n Warning\n </Button>\n <Button size=\"sm\" variant=\"soft\" onPress={() => toast.info('New version available', { ...to, description: 'Restart to update to 4.2.' })}>\n Info\n </Button>\n </View>\n <Toaster id=\"demo-rich\" variant=\"rich\" />\n </Frame>\n );\n}\n"
|
|
153
153
|
},
|
|
154
154
|
{
|
|
155
155
|
"name": "Island",
|
|
156
156
|
"title": "Island",
|
|
157
|
-
"code": "import { StyleSheet, View, type ViewProps } from 'react-native';\nimport { Button } from '@/components/ui/button';\nimport { toast, Toaster, ToastProvider, useToast } from '@/components/ui/toast';\nimport { useKit } from '@/lib/kit';\n\n/** Demo frame that follows the kit theme, so the docs radius / accent picker reshapes it too. */\nfunction Frame({ style, ...rest }: ViewProps) {\n const { colors, radius } = useKit();\n return (\n <View\n style={[\n {\n height: 300,\n
|
|
157
|
+
"code": "import { StyleSheet, View, type ViewProps } from 'react-native';\nimport { Button } from '@/components/ui/button';\nimport { toast, Toaster, ToastProvider, useToast } from '@/components/ui/toast';\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 frame that follows the kit theme, so the docs radius / accent picker reshapes it too. In screen mode it is the screen. */\nfunction Frame({ screen, style, ...rest }: ViewProps & { screen?: boolean }) {\n const { colors, radius } = useKit();\n return (\n <View\n style={[\n { padding: 16 },\n screen\n ? { flex: 1, backgroundColor: colors.background }\n : {\n height: 300,\n overflow: 'hidden',\n backgroundColor: colors.card,\n borderColor: colors.border,\n borderWidth: StyleSheet.hairlineWidth,\n borderRadius: radius.xl,\n },\n style,\n ]}\n {...rest}\n />\n );\n}\n\nconst row = { flexDirection: 'row' as const, flexWrap: 'wrap' as const, gap: 8 };\n\nconst wait = (ms: number) => new Promise<void>((resolve) => setTimeout(resolve, ms));\n\nexport default function Island({ screen }: DemoProps) {\n const to = { toaster: 'demo-island' };\n return (\n <Frame screen={screen} style={{ justifyContent: 'flex-end' }}>\n <View style={row}>\n <Button\n size=\"sm\"\n variant=\"secondary\"\n onPress={() => toast.success('AirPods connected', { ...to, description: 'Battery 84% · Case 100%' })}\n >\n Success\n </Button>\n <Button\n size=\"sm\"\n variant=\"secondary\"\n onPress={() =>\n toast.error('Payment failed', {\n ...to,\n description: 'Your card ending 4242 was declined by the bank. Update your billing details to keep Pro.',\n action: { label: 'Update', onPress: () => toast.info('Opening billing…', to) },\n })\n }\n >\n Error\n </Button>\n <Button\n size=\"sm\"\n variant=\"secondary\"\n onPress={() =>\n toast.promise(wait(2200), { loading: 'Sending to Maya…', success: 'Photo sent', error: 'Could not send' }, to)\n }\n >\n Promise\n </Button>\n <Button\n size=\"sm\"\n variant=\"secondary\"\n onPress={() => toast('Maya Chen', { ...to, description: 'Running 5 min late, grab us a table?' })}\n >\n Message\n </Button>\n </View>\n <Toaster id=\"demo-island\" variant=\"island\" />\n </Frame>\n );\n}\n"
|
|
158
158
|
},
|
|
159
159
|
{
|
|
160
160
|
"name": "PromiseMorph",
|
|
161
161
|
"title": "Promise Morph",
|
|
162
|
-
"code": "import { StyleSheet, View, type ViewProps } from 'react-native';\nimport { Button } from '@/components/ui/button';\nimport { toast, Toaster, ToastProvider, useToast } from '@/components/ui/toast';\nimport { useKit } from '@/lib/kit';\n\n/** Demo frame that follows the kit theme, so the docs radius / accent picker reshapes it too. */\nfunction Frame({ style, ...rest }: ViewProps) {\n const { colors, radius } = useKit();\n return (\n <View\n style={[\n {\n height: 300,\n
|
|
162
|
+
"code": "import { StyleSheet, View, type ViewProps } from 'react-native';\nimport { Button } from '@/components/ui/button';\nimport { toast, Toaster, ToastProvider, useToast } from '@/components/ui/toast';\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 frame that follows the kit theme, so the docs radius / accent picker reshapes it too. In screen mode it is the screen. */\nfunction Frame({ screen, style, ...rest }: ViewProps & { screen?: boolean }) {\n const { colors, radius } = useKit();\n return (\n <View\n style={[\n { padding: 16 },\n screen\n ? { flex: 1, backgroundColor: colors.background }\n : {\n height: 300,\n overflow: 'hidden',\n backgroundColor: colors.card,\n borderColor: colors.border,\n borderWidth: StyleSheet.hairlineWidth,\n borderRadius: radius.xl,\n },\n style,\n ]}\n {...rest}\n />\n );\n}\n\nconst row = { flexDirection: 'row' as const, flexWrap: 'wrap' as const, gap: 8 };\n\nconst wait = (ms: number) => new Promise<void>((resolve) => setTimeout(resolve, ms));\n\nconst fail = (ms: number) => new Promise<void>((_, reject) => setTimeout(() => reject(new Error('timeout')), ms));\n\nexport default function PromiseMorph({ screen }: DemoProps) {\n const to = { toaster: 'demo-promise' };\n return (\n <Frame screen={screen}>\n <View style={row}>\n <Button\n size=\"sm\"\n variant=\"accent\"\n onPress={() =>\n toast.promise(\n wait(1800),\n { loading: 'Uploading report.pdf…', success: 'Report uploaded', error: 'Upload failed' },\n { ...to, description: 'Q3 board pack · 4.2 MB' },\n )\n }\n >\n Upload (succeeds)\n </Button>\n <Button\n size=\"sm\"\n variant=\"outline\"\n onPress={() =>\n toast\n .promise(\n fail(1800),\n { loading: 'Publishing release…', success: 'Release published', error: 'Publish failed: build timed out' },\n to,\n )\n .catch(() => {})\n }\n >\n Publish (fails)\n </Button>\n </View>\n <Toaster id=\"demo-promise\" />\n </Frame>\n );\n}\n"
|
|
163
163
|
},
|
|
164
164
|
{
|
|
165
165
|
"name": "Minimal",
|
|
166
166
|
"title": "Minimal",
|
|
167
|
-
"code": "import { StyleSheet, View, type ViewProps } from 'react-native';\nimport { Button } from '@/components/ui/button';\nimport { toast, Toaster, ToastProvider, useToast } from '@/components/ui/toast';\nimport { useKit } from '@/lib/kit';\n\n/** Demo frame that follows the kit theme, so the docs radius / accent picker reshapes it too. */\nfunction Frame({ style, ...rest }: ViewProps) {\n const { colors, radius } = useKit();\n return (\n <View\n style={[\n {\n height: 300,\n
|
|
167
|
+
"code": "import { StyleSheet, View, type ViewProps } from 'react-native';\nimport { Button } from '@/components/ui/button';\nimport { toast, Toaster, ToastProvider, useToast } from '@/components/ui/toast';\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 frame that follows the kit theme, so the docs radius / accent picker reshapes it too. In screen mode it is the screen. */\nfunction Frame({ screen, style, ...rest }: ViewProps & { screen?: boolean }) {\n const { colors, radius } = useKit();\n return (\n <View\n style={[\n { padding: 16 },\n screen\n ? { flex: 1, backgroundColor: colors.background }\n : {\n height: 300,\n overflow: 'hidden',\n backgroundColor: colors.card,\n borderColor: colors.border,\n borderWidth: StyleSheet.hairlineWidth,\n borderRadius: radius.xl,\n },\n style,\n ]}\n {...rest}\n />\n );\n}\n\nconst row = { flexDirection: 'row' as const, flexWrap: 'wrap' as const, gap: 8 };\n\nexport default function Minimal({ screen }: DemoProps) {\n const to = { toaster: 'demo-minimal' };\n return (\n <Frame screen={screen} style={{ justifyContent: 'flex-end' }}>\n <View style={row}>\n <Button size=\"sm\" variant=\"secondary\" onPress={() => toast('Copied to clipboard', to)}>\n Copy link\n </Button>\n <Button size=\"sm\" variant=\"secondary\" onPress={() => toast.success('Added to favourites', to)}>\n Favourite\n </Button>\n <Button size=\"sm\" variant=\"secondary\" onPress={() => toast.loading('Syncing…', { ...to, id: 'sync', duration: 1500 })}>\n Sync\n </Button>\n </View>\n <Toaster id=\"demo-minimal\" variant=\"minimal\" position=\"top\" />\n </Frame>\n );\n}\n"
|
|
168
168
|
},
|
|
169
169
|
{
|
|
170
170
|
"name": "WithAction",
|
|
171
171
|
"title": "With Action",
|
|
172
|
-
"code": "import { StyleSheet, View, type ViewProps } from 'react-native';\nimport { Button } from '@/components/ui/button';\nimport { toast, Toaster, ToastProvider, useToast } from '@/components/ui/toast';\nimport { useKit } from '@/lib/kit';\n\n/** Demo frame that follows the kit theme, so the docs radius / accent picker reshapes it too. */\nfunction Frame({ style, ...rest }: ViewProps) {\n const { colors, radius } = useKit();\n return (\n <View\n style={[\n {\n height: 300,\n
|
|
172
|
+
"code": "import { StyleSheet, View, type ViewProps } from 'react-native';\nimport { Button } from '@/components/ui/button';\nimport { toast, Toaster, ToastProvider, useToast } from '@/components/ui/toast';\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 frame that follows the kit theme, so the docs radius / accent picker reshapes it too. In screen mode it is the screen. */\nfunction Frame({ screen, style, ...rest }: ViewProps & { screen?: boolean }) {\n const { colors, radius } = useKit();\n return (\n <View\n style={[\n { padding: 16 },\n screen\n ? { flex: 1, backgroundColor: colors.background }\n : {\n height: 300,\n overflow: 'hidden',\n backgroundColor: colors.card,\n borderColor: colors.border,\n borderWidth: StyleSheet.hairlineWidth,\n borderRadius: radius.xl,\n },\n style,\n ]}\n {...rest}\n />\n );\n}\n\nexport default function WithAction({ screen }: DemoProps) {\n const to = { toaster: 'demo-action' };\n return (\n <Frame screen={screen}>\n <Button\n variant=\"destructive\"\n size=\"sm\"\n style={{ alignSelf: 'flex-start' }}\n onPress={() =>\n toast('Conversation archived', {\n ...to,\n description: 'Moved 12 messages to Archive.',\n action: { label: 'Undo', onPress: () => toast.success('Conversation restored', to) },\n })\n }\n >\n Archive chat\n </Button>\n <Toaster id=\"demo-action\" closeButton duration={6000} />\n </Frame>\n );\n}\n"
|
|
173
173
|
},
|
|
174
174
|
{
|
|
175
175
|
"name": "Expanded",
|
|
176
176
|
"title": "Expanded",
|
|
177
|
-
"code": "import React, { useRef } from 'react';\nimport { StyleSheet, View, type ViewProps } from 'react-native';\nimport { Button } from '@/components/ui/button';\nimport { toast, Toaster, ToastProvider, useToast } from '@/components/ui/toast';\nimport { useKit } from '@/lib/kit';\n\n/** Demo frame that follows the kit theme, so the docs radius / accent picker reshapes it too. */\nfunction Frame({ style, ...rest }: ViewProps) {\n const { colors, radius } = useKit();\n return (\n <View\n style={[\n {\n height: 300,\n
|
|
177
|
+
"code": "import React, { useRef } from 'react';\nimport { StyleSheet, View, type ViewProps } from 'react-native';\nimport { Button } from '@/components/ui/button';\nimport { toast, Toaster, ToastProvider, useToast } from '@/components/ui/toast';\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 frame that follows the kit theme, so the docs radius / accent picker reshapes it too. In screen mode it is the screen. */\nfunction Frame({ screen, style, ...rest }: ViewProps & { screen?: boolean }) {\n const { colors, radius } = useKit();\n return (\n <View\n style={[\n { padding: 16 },\n screen\n ? { flex: 1, backgroundColor: colors.background }\n : {\n height: 300,\n overflow: 'hidden',\n backgroundColor: colors.card,\n borderColor: colors.border,\n borderWidth: StyleSheet.hairlineWidth,\n borderRadius: radius.xl,\n },\n style,\n ]}\n {...rest}\n />\n );\n}\n\nexport default function Expanded({ screen }: DemoProps) {\n const to = { toaster: 'demo-expanded' };\n const messages = [\n () => toast.info('Maya joined #design', to),\n () => toast.success('Build #1824 passed', { ...to, description: 'main · 3m 12s' }),\n () => toast('Invoice INV-0042 sent', to),\n () => toast.warning('Disk usage at 85%', to),\n ];\n const n = useRef(0);\n return (\n <Frame screen={screen} style={{ justifyContent: 'flex-end' }}>\n <Button size=\"sm\" style={{ alignSelf: 'flex-start' }} onPress={() => messages[n.current++ % messages.length]()}>\n Send notification\n </Button>\n <Toaster id=\"demo-expanded\" position=\"top\" expand visibleToasts={3} gap={8} />\n </Frame>\n );\n}\n"
|
|
178
178
|
},
|
|
179
179
|
{
|
|
180
180
|
"name": "WithProvider",
|
|
181
181
|
"title": "With Provider",
|
|
182
|
-
"code": "import { StyleSheet, View, type ViewProps } from 'react-native';\nimport { Button } from '@/components/ui/button';\nimport { toast, Toaster, ToastProvider, useToast } from '@/components/ui/toast';\nimport { useKit } from '@/lib/kit';\n\n/** Demo frame that follows the kit theme, so the docs radius / accent picker reshapes it too. */\nfunction Frame({ style, ...rest }: ViewProps) {\n const { colors, radius } = useKit();\n return (\n <View\n style={[\n {\n height: 300,\n
|
|
182
|
+
"code": "import { StyleSheet, View, type ViewProps } from 'react-native';\nimport { Button } from '@/components/ui/button';\nimport { toast, Toaster, ToastProvider, useToast } from '@/components/ui/toast';\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 frame that follows the kit theme, so the docs radius / accent picker reshapes it too. In screen mode it is the screen. */\nfunction Frame({ screen, style, ...rest }: ViewProps & { screen?: boolean }) {\n const { colors, radius } = useKit();\n return (\n <View\n style={[\n { padding: 16 },\n screen\n ? { flex: 1, backgroundColor: colors.background }\n : {\n height: 300,\n overflow: 'hidden',\n backgroundColor: colors.card,\n borderColor: colors.border,\n borderWidth: StyleSheet.hairlineWidth,\n borderRadius: radius.xl,\n },\n style,\n ]}\n {...rest}\n />\n );\n}\n\nconst row = { flexDirection: 'row' as const, flexWrap: 'wrap' as const, gap: 8 };\n\nfunction ProviderDemo({ screen }: { screen?: boolean }) {\n const toast = useToast();\n return (\n <Frame screen={screen}>\n <View style={row}>\n <Button\n size=\"sm\"\n variant=\"outline\"\n onPress={() => toast.success('Draft saved', { description: 'Only this Toaster sees it.' })}\n >\n Save draft\n </Button>\n <Button size=\"sm\" variant=\"outline\" onPress={() => toast('Reminder set for 6:00 PM')}>\n Remind me\n </Button>\n </View>\n <Toaster />\n </Frame>\n );\n}\n\nexport default function WithProvider({ screen }: DemoProps) {\n return (\n <ToastProvider>\n <ProviderDemo screen={screen} />\n </ToastProvider>\n );\n}\n"
|
|
183
183
|
}
|
|
184
184
|
]
|
|
185
185
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import React, { createContext, useCallback, useContext, useEffect, useMemo, useRef, useState } from 'react';
|
|
2
2
|
import {
|
|
3
|
+
AccessibilityInfo,
|
|
3
4
|
Animated,
|
|
4
5
|
Pressable,
|
|
5
6
|
StyleSheet,
|
|
@@ -36,6 +37,7 @@ interface Ctx {
|
|
|
36
37
|
toggle: (v: string) => void;
|
|
37
38
|
variant: AccordionVariant;
|
|
38
39
|
tint: string;
|
|
40
|
+
reduce: boolean;
|
|
39
41
|
}
|
|
40
42
|
const AccordionContext = createContext<Ctx | null>(null);
|
|
41
43
|
const PositionContext = createContext({ first: true, last: true });
|
|
@@ -53,6 +55,7 @@ export function Accordion({
|
|
|
53
55
|
style,
|
|
54
56
|
}: AccordionProps) {
|
|
55
57
|
const { colors, radius } = useKit();
|
|
58
|
+
const reduce = useReduceMotion();
|
|
56
59
|
const [inner, setInner] = useState(defaultValue);
|
|
57
60
|
const open = value ?? inner;
|
|
58
61
|
const openRef = useRef(open);
|
|
@@ -72,7 +75,10 @@ export function Accordion({
|
|
|
72
75
|
[type, collapsible, value, onValueChange]
|
|
73
76
|
);
|
|
74
77
|
|
|
75
|
-
const ctx = useMemo(
|
|
78
|
+
const ctx = useMemo(
|
|
79
|
+
() => ({ open, toggle, variant, tint: accent ?? colors.accent, reduce }),
|
|
80
|
+
[open, toggle, variant, accent, colors.accent, reduce]
|
|
81
|
+
);
|
|
76
82
|
const items = React.Children.toArray(children);
|
|
77
83
|
|
|
78
84
|
return (
|
|
@@ -117,9 +123,10 @@ export interface AccordionItemProps {
|
|
|
117
123
|
export function AccordionItem({ value, title, subtitle, icon, children, disabled, style }: AccordionItemProps) {
|
|
118
124
|
const ctx = useContext(AccordionContext);
|
|
119
125
|
if (!ctx) throw new Error('AccordionItem must be used inside <Accordion>');
|
|
120
|
-
const { open: openValues, toggle, variant, tint } = ctx;
|
|
126
|
+
const { open: openValues, toggle, variant, tint, reduce } = ctx;
|
|
121
127
|
const { last } = useContext(PositionContext);
|
|
122
|
-
const { colors, radius, dark } = useKit();
|
|
128
|
+
const { colors, radius, dark, font } = useKit();
|
|
129
|
+
const ff = font.family ? { fontFamily: font.family } : null;
|
|
123
130
|
const open = openValues.includes(value);
|
|
124
131
|
|
|
125
132
|
const [height, setHeight] = useState(0);
|
|
@@ -128,9 +135,15 @@ export function AccordionItem({ value, title, subtitle, icon, children, disabled
|
|
|
128
135
|
const turn = useRef(new Animated.Value(open ? 1 : 0)).current;
|
|
129
136
|
|
|
130
137
|
useEffect(() => {
|
|
138
|
+
// Reduce Motion: open and close at once, chevron flips without turning.
|
|
139
|
+
if (reduce) {
|
|
140
|
+
progress.setValue(open ? 1 : 0);
|
|
141
|
+
turn.setValue(open ? 1 : 0);
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
131
144
|
Animated.spring(progress, { toValue: open ? 1 : 0, useNativeDriver: false, ...motion.gentle, overshootClamping: true }).start();
|
|
132
145
|
Animated.spring(turn, { toValue: open ? 1 : 0, useNativeDriver: true, ...motion.snappy }).start();
|
|
133
|
-
}, [open, progress, turn]);
|
|
146
|
+
}, [open, progress, turn, reduce]);
|
|
134
147
|
|
|
135
148
|
const onLayout = (e: LayoutChangeEvent) => {
|
|
136
149
|
const h = Math.ceil(e.nativeEvent.layout.height);
|
|
@@ -168,11 +181,11 @@ export function AccordionItem({ value, title, subtitle, icon, children, disabled
|
|
|
168
181
|
{icon ? <View style={styles.icon}>{icon}</View> : null}
|
|
169
182
|
<View style={styles.titles}>
|
|
170
183
|
{isTextNode(title) ? (
|
|
171
|
-
<Text style={[styles.title, { color: open && variant === 'default' ? tint : colors.foreground }]}>{title}</Text>
|
|
184
|
+
<Text style={[styles.title, ff, { color: open && variant === 'default' ? tint : colors.foreground }]}>{title}</Text>
|
|
172
185
|
) : (
|
|
173
186
|
title
|
|
174
187
|
)}
|
|
175
|
-
{subtitle ? <Text style={[styles.subtitle, { color: colors.mutedForeground }]}>{subtitle}</Text> : null}
|
|
188
|
+
{subtitle ? <Text style={[styles.subtitle, ff, { color: colors.mutedForeground }]}>{subtitle}</Text> : null}
|
|
176
189
|
</View>
|
|
177
190
|
<Animated.View
|
|
178
191
|
style={{ transform: [{ rotate: turn.interpolate({ inputRange: [0, 1], outputRange: ['0deg', '180deg'] }) }] }}
|
|
@@ -209,7 +222,7 @@ export function AccordionItem({ value, title, subtitle, icon, children, disabled
|
|
|
209
222
|
]}
|
|
210
223
|
>
|
|
211
224
|
{isTextNode(children) ? (
|
|
212
|
-
<Text style={[styles.text, { color: colors.mutedForeground }]}>{children}</Text>
|
|
225
|
+
<Text style={[styles.text, ff, { color: colors.mutedForeground }]}>{children}</Text>
|
|
213
226
|
) : (
|
|
214
227
|
children
|
|
215
228
|
)}
|
|
@@ -221,6 +234,22 @@ export function AccordionItem({ value, title, subtitle, icon, children, disabled
|
|
|
221
234
|
|
|
222
235
|
Accordion.Item = AccordionItem;
|
|
223
236
|
|
|
237
|
+
function useReduceMotion() {
|
|
238
|
+
const [reduce, setReduce] = useState(false);
|
|
239
|
+
useEffect(() => {
|
|
240
|
+
let alive = true;
|
|
241
|
+
AccessibilityInfo.isReduceMotionEnabled()
|
|
242
|
+
.then((r) => alive && setReduce(r))
|
|
243
|
+
.catch(() => {});
|
|
244
|
+
const sub = AccessibilityInfo.addEventListener('reduceMotionChanged', setReduce);
|
|
245
|
+
return () => {
|
|
246
|
+
alive = false;
|
|
247
|
+
sub.remove();
|
|
248
|
+
};
|
|
249
|
+
}, []);
|
|
250
|
+
return reduce;
|
|
251
|
+
}
|
|
252
|
+
|
|
224
253
|
const styles = StyleSheet.create({
|
|
225
254
|
separated: { gap: 10 },
|
|
226
255
|
header: { flexDirection: 'row', alignItems: 'center', minHeight: 56, paddingVertical: 14, gap: 12 },
|
|
@@ -181,14 +181,17 @@ export function useKit(): Theme {
|
|
|
181
181
|
return useMemo(() => build(scheme, ctx?.overrides), [scheme, ctx?.overrides]);
|
|
182
182
|
}
|
|
183
183
|
|
|
184
|
-
/**
|
|
184
|
+
/**
|
|
185
|
+
* Shared motion presets so the whole kit moves the same way. Every spring is critically damped
|
|
186
|
+
* (damping ≥ 2·√(stiffness·mass)): it settles smoothly with no overshoot or wobble.
|
|
187
|
+
*/
|
|
185
188
|
export const motion = {
|
|
186
189
|
/** Toggles, selection indicators. */
|
|
187
|
-
snappy: { damping:
|
|
188
|
-
/** Press feedback, pops. */
|
|
189
|
-
bouncy: { damping:
|
|
190
|
+
snappy: { damping: 30, stiffness: 260, mass: 0.8 },
|
|
191
|
+
/** Press feedback, pops. (Kept under its old name; it no longer bounces.) */
|
|
192
|
+
bouncy: { damping: 26, stiffness: 220, mass: 0.7 },
|
|
190
193
|
/** Sheets, layout changes. */
|
|
191
|
-
gentle: { damping:
|
|
194
|
+
gentle: { damping: 27, stiffness: 160, mass: 1 },
|
|
192
195
|
fast: 160,
|
|
193
196
|
normal: 240,
|
|
194
197
|
};
|
|
@@ -104,6 +104,9 @@ export interface ActionSheetProps {
|
|
|
104
104
|
style?: StyleProp<ViewStyle>;
|
|
105
105
|
}
|
|
106
106
|
|
|
107
|
+
// Controls stop growing at iOS's largest standard text size (xxxL ≈ 1.35×) so they keep their shape;
|
|
108
|
+
// content text scales all the way.
|
|
109
|
+
const FONT_CAP = 1.35;
|
|
107
110
|
const DEFAULT_BOTTOM_INSET = Platform.select({ ios: 20, android: 12, default: 16 }) ?? 16;
|
|
108
111
|
|
|
109
112
|
/** iOS-style resistance: moves 1:1 at first, then ever more slowly towards `limit`. */
|
|
@@ -134,7 +137,8 @@ export function ActionSheet({
|
|
|
134
137
|
haptics = true,
|
|
135
138
|
style,
|
|
136
139
|
}: ActionSheetProps) {
|
|
137
|
-
const { colors, radius, dark } = useKit();
|
|
140
|
+
const { colors, radius, dark, font } = useKit();
|
|
141
|
+
const ff = font.family ? { fontFamily: font.family } : null;
|
|
138
142
|
const { height: winH } = useWindowDimensions();
|
|
139
143
|
const reduceMotion = useReduceMotion();
|
|
140
144
|
const tint = accent ?? colors.accent;
|
|
@@ -247,13 +251,13 @@ export function ActionSheet({
|
|
|
247
251
|
{title ? (
|
|
248
252
|
<Text
|
|
249
253
|
accessibilityRole="header"
|
|
250
|
-
style={[list ? styles.titleLarge : styles.title, { color: list ? colors.foreground : colors.mutedForeground }]}
|
|
254
|
+
style={[list ? styles.titleLarge : styles.title, ff, { color: list ? colors.foreground : colors.mutedForeground }]}
|
|
251
255
|
>
|
|
252
256
|
{title}
|
|
253
257
|
</Text>
|
|
254
258
|
) : null}
|
|
255
259
|
{message ? (
|
|
256
|
-
<Text style={[styles.message, { color: colors.mutedForeground, textAlign: list ? 'left' : 'center' }]}>
|
|
260
|
+
<Text style={[styles.message, ff, { color: colors.mutedForeground, textAlign: list ? 'left' : 'center' }]}>
|
|
257
261
|
{message}
|
|
258
262
|
</Text>
|
|
259
263
|
) : null}
|
|
@@ -265,7 +269,7 @@ export function ActionSheet({
|
|
|
265
269
|
cancelLabel !== null ? (
|
|
266
270
|
<Card corner={corner} bg={colors.popover} edge={edge} dark={dark}>
|
|
267
271
|
<Row onPress={dismiss} highlight={highlight} label={cancelLabel} style={styles.row}>
|
|
268
|
-
<Text style={[styles.rowLabel, styles.center, { color: variant === 'inset' ? tint : colors.foreground }]}>
|
|
272
|
+
<Text style={[styles.rowLabel, ff, styles.center, { color: variant === 'inset' ? tint : colors.foreground }]}>
|
|
269
273
|
{cancelLabel}
|
|
270
274
|
</Text>
|
|
271
275
|
</Row>
|
|
@@ -295,7 +299,7 @@ export function ActionSheet({
|
|
|
295
299
|
<View style={[styles.tileIcon, { backgroundColor: fill, borderRadius: radius.scale(16) }]}>
|
|
296
300
|
{icon(a, fg, 24)}
|
|
297
301
|
</View>
|
|
298
|
-
<Text numberOfLines={2} style={[styles.tileLabel, { color: a.destructive ? colors.danger : colors.foreground }]}>
|
|
302
|
+
<Text maxFontSizeMultiplier={FONT_CAP} numberOfLines={2} style={[styles.tileLabel, ff, { color: a.destructive ? colors.danger : colors.foreground }]}>
|
|
299
303
|
{a.label}
|
|
300
304
|
</Text>
|
|
301
305
|
</Row>
|
|
@@ -337,7 +341,7 @@ export function ActionSheet({
|
|
|
337
341
|
{a.icon ? (
|
|
338
342
|
<View style={styles.listIcon}>{icon(a, a.destructive ? colors.danger : colors.mutedForeground, 22)}</View>
|
|
339
343
|
) : null}
|
|
340
|
-
<Text style={[styles.listLabel, { color }]} numberOfLines={1}>
|
|
344
|
+
<Text style={[styles.listLabel, ff, { color }]} numberOfLines={1}>
|
|
341
345
|
{a.label}
|
|
342
346
|
</Text>
|
|
343
347
|
</Row>
|
|
@@ -347,7 +351,7 @@ export function ActionSheet({
|
|
|
347
351
|
<>
|
|
348
352
|
<View style={[styles.divider, { backgroundColor: separator }]} />
|
|
349
353
|
<Row onPress={dismiss} highlight={highlight} corner={radius.md} label={cancelLabel} style={styles.listRow}>
|
|
350
|
-
<Text style={[styles.listLabel, styles.center, { color: colors.mutedForeground }]}>{cancelLabel}</Text>
|
|
354
|
+
<Text style={[styles.listLabel, ff, styles.center, { color: colors.mutedForeground }]}>{cancelLabel}</Text>
|
|
351
355
|
</Row>
|
|
352
356
|
</>
|
|
353
357
|
) : null}
|
|
@@ -379,7 +383,7 @@ export function ActionSheet({
|
|
|
379
383
|
(i > 0 || header) && { borderTopWidth: StyleSheet.hairlineWidth, borderTopColor: separator },
|
|
380
384
|
]}
|
|
381
385
|
>
|
|
382
|
-
<Text style={[styles.rowLabel, withIcons ? styles.rowLabelMenu : styles.center, { color }]} numberOfLines={1}>
|
|
386
|
+
<Text style={[styles.rowLabel, ff, withIcons ? styles.rowLabelMenu : styles.center, { color }]} numberOfLines={1}>
|
|
383
387
|
{a.label}
|
|
384
388
|
</Text>
|
|
385
389
|
{withIcons ? <View style={styles.rowIcon}>{a.icon ? icon(a, color, 22) : null}</View> : null}
|