@vvedantb/native-ui 0.2.1 → 0.3.12

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.
Files changed (86) hide show
  1. package/README.md +160 -9
  2. package/package.json +80 -2
  3. package/src/AlertDialog.tsx +73 -0
  4. package/src/AmbientBackground.tsx +26 -11
  5. package/src/Avatar.tsx +141 -0
  6. package/src/Badge.tsx +108 -0
  7. package/src/BottomSheetContainer.tsx +7 -1
  8. package/src/Button.tsx +153 -52
  9. package/src/Card.tsx +111 -36
  10. package/src/Checkbox.tsx +78 -0
  11. package/src/Chip.tsx +5 -3
  12. package/src/ConfirmDialog.tsx +105 -0
  13. package/src/DateTimeField.tsx +6 -2
  14. package/src/Dialog.tsx +253 -0
  15. package/src/EmptyState.tsx +122 -7
  16. package/src/FloatingFAB.tsx +2 -4
  17. package/src/FloatingSurface.tsx +31 -9
  18. package/src/FloatingTabBar.tsx +39 -11
  19. package/src/Input.tsx +15 -4
  20. package/src/Label.tsx +31 -0
  21. package/src/LabeledSwitchRow.tsx +96 -0
  22. package/src/LiquidGlass.tsx +2 -10
  23. package/src/ListRow.tsx +7 -2
  24. package/src/PillTabs.tsx +28 -14
  25. package/src/Progress.tsx +67 -0
  26. package/src/SearchInput.tsx +163 -0
  27. package/src/Select.tsx +32 -6
  28. package/src/Separator.tsx +38 -0
  29. package/src/Sheet.tsx +107 -0
  30. package/src/Skeleton.tsx +25 -5
  31. package/src/Spinner.tsx +92 -0
  32. package/src/StatusDot.tsx +66 -0
  33. package/src/Switch.tsx +24 -7
  34. package/src/Tabs.tsx +190 -0
  35. package/src/Textarea.tsx +47 -0
  36. package/src/Toast.tsx +170 -0
  37. package/src/blur.ts +48 -0
  38. package/src/brand/AppIcons.tsx +106 -0
  39. package/src/brand/AppMark.tsx +83 -0
  40. package/src/brand/AppWordmark.tsx +97 -0
  41. package/src/brand/familyApps.ts +24 -0
  42. package/src/brand/index.ts +34 -0
  43. package/src/brand/svg.ts +263 -0
  44. package/src/brand/vProAppBenefits.ts +22 -0
  45. package/src/controlStyles.ts +360 -0
  46. package/src/dialogLayout.ts +20 -0
  47. package/src/entries/ambient.ts +3 -0
  48. package/src/entries/bottom-sheet.ts +2 -0
  49. package/src/entries/brand.ts +2 -0
  50. package/src/entries/button.ts +3 -0
  51. package/src/entries/date-time.ts +3 -0
  52. package/src/entries/floating.ts +24 -0
  53. package/src/entries/layout.ts +7 -0
  54. package/src/entries/marketing.ts +10 -0
  55. package/src/entries/primitives.ts +99 -0
  56. package/src/entries/search-input.ts +4 -0
  57. package/src/entries/tab-bar.ts +10 -0
  58. package/src/entries/theme.ts +15 -0
  59. package/src/entries/toast.ts +6 -0
  60. package/src/examples/ParityGallery.tsx +496 -0
  61. package/src/floating.ts +5 -1
  62. package/src/glassBlur.tsx +95 -0
  63. package/src/gradient.ts +21 -0
  64. package/src/haptics.ts +22 -2
  65. package/src/index.ts +99 -2
  66. package/src/initials.ts +10 -0
  67. package/src/marketing/PricingModal.tsx +410 -0
  68. package/src/marketing/UpgradeToProCard.tsx +103 -0
  69. package/src/marketing/pricing.ts +76 -0
  70. package/src/palette.ts +43 -0
  71. package/src/primitives/input.tsx +9 -2
  72. package/src/primitives/native-only-animated-view.tsx +10 -3
  73. package/src/primitives/overlay.tsx +179 -0
  74. package/src/primitives/select.tsx +25 -12
  75. package/src/primitives/switch.tsx +12 -13
  76. package/src/primitives/use-controllable-state.ts +24 -0
  77. package/src/settings/SettingsField.tsx +11 -2
  78. package/src/settings/SettingsSection.tsx +20 -6
  79. package/src/settings/SettingsToggleRow.tsx +7 -2
  80. package/src/tabBarRoutes.ts +52 -0
  81. package/src/toastChrome.ts +103 -0
  82. package/src/toastStore.ts +86 -0
  83. package/src/tokens.ts +19 -14
  84. package/src/typography.ts +24 -0
  85. package/src/useReduceMotion.ts +24 -0
  86. package/src/useScheme.ts +6 -0
@@ -0,0 +1,3 @@
1
+ // `@vvedantb/native-ui/date-time`. Imports
2
+ // `@react-native-community/datetimepicker` and `dayjs`.
3
+ export { DateTimeField } from "../DateTimeField";
@@ -0,0 +1,24 @@
1
+ // `@vvedantb/native-ui/floating`: floating surfaces, glass blur targets and
2
+ // metrics. Reaches `expo-blur` only through its optional-peer loader.
3
+ export {
4
+ FLOATING_INSET,
5
+ FLOATING_TAB_BAR_HEIGHT,
6
+ FLOATING_TAB_BAR_HEIGHT_LARGE,
7
+ FLOATING_RADIUS,
8
+ FLOATING_ROW_RADIUS,
9
+ FLOATING_PILL_RADIUS,
10
+ floatingShadow,
11
+ floatingHairline,
12
+ floatingFill,
13
+ floatingSolidFill,
14
+ tabBarReserve,
15
+ } from "../floating";
16
+ export { useReduceTransparency } from "../useReduceTransparency";
17
+ export { FloatingSurface, GlassSheetBackground } from "../FloatingSurface";
18
+ export {
19
+ LiquidGlass,
20
+ GlassBlurProvider,
21
+ GlassBlurTarget,
22
+ } from "../LiquidGlass";
23
+ export type { GlassBlurTargetProps } from "../LiquidGlass";
24
+ export { FloatingFAB } from "../FloatingFAB";
@@ -0,0 +1,7 @@
1
+ // `@vvedantb/native-ui/layout`: screen containers and settings chrome.
2
+ export { SafeAreaContainer } from "../SafeAreaContainer";
3
+ export { ScreenContainer } from "../ScreenContainer";
4
+ export { SettingsSection } from "../settings/SettingsSection";
5
+ export { SettingsField } from "../settings/SettingsField";
6
+ export { SettingsToggleRow } from "../settings/SettingsToggleRow";
7
+ export { SettingsStack } from "../settings/SettingsStack";
@@ -0,0 +1,10 @@
1
+ // `@vvedantb/native-ui/marketing`: the upgrade card and pricing modal.
2
+ export { UpgradeToProCard } from "../marketing/UpgradeToProCard";
3
+ export type { UpgradeToProCardProps } from "../marketing/UpgradeToProCard";
4
+ export { PricingModal } from "../marketing/PricingModal";
5
+ export type { PricingModalProps } from "../marketing/PricingModal";
6
+ export type {
7
+ PricingModalPeriod,
8
+ PricingPlan,
9
+ PricingPlanBenefit,
10
+ } from "../marketing/pricing";
@@ -0,0 +1,99 @@
1
+ // `@vvedantb/native-ui/primitives`: fields, display, tabs, overlays and
2
+ // toasts. `BottomSheetContainer`, `DateTimeField` and `AmbientBackground` are
3
+ // not here; they have their own entries.
4
+ export { Button } from "../Button";
5
+ export type { ButtonProps, ButtonSize, ButtonVariant } from "../Button";
6
+ export { Input } from "../Input";
7
+ export type { InputProps } from "../Input";
8
+ export { SearchInput } from "../SearchInput";
9
+ export type { SearchInputProps } from "../SearchInput";
10
+ export { Textarea } from "../Textarea";
11
+ export type { TextareaProps } from "../Textarea";
12
+ export { Label } from "../Label";
13
+ export type { LabelProps } from "../Label";
14
+ export { Select } from "../Select";
15
+ export type { SelectOption, SelectProps } from "../Select";
16
+ export { Switch } from "../Switch";
17
+ export type { SwitchProps } from "../Switch";
18
+ export { LabeledSwitchRow } from "../LabeledSwitchRow";
19
+ export type { LabeledSwitchRowProps } from "../LabeledSwitchRow";
20
+ export { Checkbox } from "../Checkbox";
21
+ export type { CheckboxProps } from "../Checkbox";
22
+ export { Badge } from "../Badge";
23
+ export type { BadgeProps, BadgeVariant } from "../Badge";
24
+ export { StatusDot } from "../StatusDot";
25
+ export type { StatusDotProps, StatusTone } from "../StatusDot";
26
+ export { Separator } from "../Separator";
27
+ export type { SeparatorProps } from "../Separator";
28
+ export { Progress } from "../Progress";
29
+ export type { ProgressProps } from "../Progress";
30
+ export { Spinner, CenteredSpinner } from "../Spinner";
31
+ export type { SpinnerProps } from "../Spinner";
32
+ export { Avatar, AvatarImage, AvatarFallback } from "../Avatar";
33
+ export type { AvatarProps, AvatarImageProps, AvatarFallbackProps } from "../Avatar";
34
+ export { initials } from "../initials";
35
+ export { Chip } from "../Chip";
36
+ export { PillTabs } from "../PillTabs";
37
+ export { Tabs, TabsList, TabsTrigger, TabsContent } from "../Tabs";
38
+ export type {
39
+ TabsProps,
40
+ TabsListProps,
41
+ TabsListVariant,
42
+ TabsTriggerProps,
43
+ TabsContentProps,
44
+ } from "../Tabs";
45
+ export { ListRow } from "../ListRow";
46
+ export { HeaderIconButton } from "../HeaderIconButton";
47
+ export {
48
+ Card,
49
+ CardHeader,
50
+ CardTitle,
51
+ CardDescription,
52
+ CardContent,
53
+ CardFooter,
54
+ } from "../Card";
55
+ export { Skeleton, CardSkeleton } from "../Skeleton";
56
+ export { EmptyState } from "../EmptyState";
57
+ export type { EmptyStateVariant } from "../EmptyState";
58
+ export {
59
+ Dialog,
60
+ DialogTrigger,
61
+ DialogClose,
62
+ DialogContent,
63
+ DialogHeader,
64
+ DialogTitle,
65
+ DialogDescription,
66
+ DialogBody,
67
+ DialogFooter,
68
+ } from "../Dialog";
69
+ export type { DialogProps, DialogContentProps } from "../Dialog";
70
+ export {
71
+ AlertDialog,
72
+ AlertDialogTrigger,
73
+ AlertDialogContent,
74
+ AlertDialogHeader,
75
+ AlertDialogTitle,
76
+ AlertDialogDescription,
77
+ AlertDialogFooter,
78
+ AlertDialogAction,
79
+ AlertDialogCancel,
80
+ } from "../AlertDialog";
81
+ export type { AlertDialogProps, AlertDialogContentProps } from "../AlertDialog";
82
+ export { ConfirmDialog } from "../ConfirmDialog";
83
+ export type { ConfirmDialogProps } from "../ConfirmDialog";
84
+ export {
85
+ Sheet,
86
+ SheetTrigger,
87
+ SheetClose,
88
+ SheetContent,
89
+ SheetHandle,
90
+ SheetHeader,
91
+ SheetTitle,
92
+ SheetDescription,
93
+ SheetBody,
94
+ SheetFooter,
95
+ } from "../Sheet";
96
+ export type { SheetProps, SheetContentProps } from "../Sheet";
97
+ export { Toaster } from "../Toast";
98
+ export { toast } from "../toastStore";
99
+ export type { ToastOptions, ToastVariant } from "../toastStore";
@@ -0,0 +1,4 @@
1
+ // `@vvedantb/native-ui/search-input`: `SearchInput` on its own. Reaches no
2
+ // optional peer, not even a loader.
3
+ export { SearchInput } from "../SearchInput";
4
+ export type { SearchInputProps } from "../SearchInput";
@@ -0,0 +1,10 @@
1
+ // `@vvedantb/native-ui/tab-bar`. Does not import `expo-router`; reaches
2
+ // `expo-blur` and `expo-haptics` only through their optional-peer loaders.
3
+ export { FloatingTabBar } from "../FloatingTabBar";
4
+ export type { FloatingTabBarProps } from "../FloatingTabBar";
5
+ export {
6
+ FLOATING_INSET,
7
+ FLOATING_TAB_BAR_HEIGHT,
8
+ FLOATING_TAB_BAR_HEIGHT_LARGE,
9
+ tabBarReserve,
10
+ } from "../floating";
@@ -0,0 +1,15 @@
1
+ // `@vvedantb/native-ui/theme`: the provider, tokens and colour helpers.
2
+ export { cn } from "../cn";
3
+ export {
4
+ tokens,
5
+ NativeUiProvider,
6
+ useNativeUi,
7
+ useLargerText,
8
+ useAccentColor,
9
+ DEFAULT_ACCENT,
10
+ } from "../theme";
11
+ export type { NativeUiTheme } from "../theme";
12
+ export { palette, withAlpha } from "../palette";
13
+ export type { Palette } from "../palette";
14
+ export { fontClass } from "../typography";
15
+ export { PINK_50, PINK_100, PINK_600, PINK_700, NEUTRAL_100, NEUTRAL_500, NEUTRAL_900, Colors } from "../colors";
@@ -0,0 +1,6 @@
1
+ // `@vvedantb/native-ui/toast`: `toast()` and `<Toaster />`. Reaches no heavy
2
+ // optional peer, not even a loader (the toast surface is flat from 0.3.12): no
3
+ // `expo-blur`, bottom sheet, date picker, `dayjs` or `expo-router`.
4
+ export { Toaster } from "../Toast";
5
+ export { toast } from "../toastStore";
6
+ export type { ToastOptions, ToastVariant } from "../toastStore";
@@ -0,0 +1,496 @@
1
+ // Reference screen for the components that mirror `@vvedantb/ui`. Render it in
2
+ // an Expo route to compare against the web: it needs the same NativeWind setup
3
+ // as the rest of the package and a `SafeAreaProvider` above it. It always
4
+ // renders the default sizes (larger text off), as the web does. Prices below
5
+ // are placeholders, not real plan prices.
6
+ import { useState, type ReactNode } from "react";
7
+ import { ScrollView, Text, View, type TextProps } from "react-native";
8
+ import { IconInbox } from "@tabler/icons-react-native";
9
+ import {
10
+ APP_ICONS,
11
+ APP_NAMES,
12
+ AlertDialog,
13
+ AlertDialogAction,
14
+ AlertDialogCancel,
15
+ AlertDialogContent,
16
+ AlertDialogDescription,
17
+ AlertDialogFooter,
18
+ AlertDialogHeader,
19
+ AlertDialogTitle,
20
+ AlertDialogTrigger,
21
+ AppMark,
22
+ AppWordmark,
23
+ Avatar,
24
+ AvatarFallback,
25
+ Badge,
26
+ Button,
27
+ Card,
28
+ CardContent,
29
+ CardDescription,
30
+ CardFooter,
31
+ CardHeader,
32
+ CardTitle,
33
+ CenteredSpinner,
34
+ Checkbox,
35
+ ConfirmDialog,
36
+ Dialog,
37
+ DialogBody,
38
+ DialogClose,
39
+ DialogContent,
40
+ DialogDescription,
41
+ DialogFooter,
42
+ DialogHeader,
43
+ DialogTitle,
44
+ DialogTrigger,
45
+ EmptyState,
46
+ Input,
47
+ Label,
48
+ LabeledSwitchRow,
49
+ NativeUiProvider,
50
+ PricingModal,
51
+ Progress,
52
+ SearchInput,
53
+ Select,
54
+ Separator,
55
+ Sheet,
56
+ SheetContent,
57
+ SheetDescription,
58
+ SheetFooter,
59
+ SheetHeader,
60
+ SheetTitle,
61
+ SheetTrigger,
62
+ Skeleton,
63
+ Spinner,
64
+ StatusDot,
65
+ Switch,
66
+ Tabs,
67
+ TabsContent,
68
+ TabsList,
69
+ TabsTrigger,
70
+ Textarea,
71
+ Toaster,
72
+ UpgradeToProCard,
73
+ initials,
74
+ toast,
75
+ tokens,
76
+ useNativeUi,
77
+ type BadgeVariant,
78
+ type ButtonVariant,
79
+ type PricingPlan,
80
+ type StatusTone,
81
+ } from "../index";
82
+ import { typeStyle } from "../controlStyles";
83
+ import { useScheme } from "../useScheme";
84
+
85
+ const BUTTON_VARIANTS: ButtonVariant[] = [
86
+ "default",
87
+ "secondary",
88
+ "outline",
89
+ "destructive",
90
+ "ghost",
91
+ "link",
92
+ ];
93
+
94
+ const BADGE_VARIANTS: BadgeVariant[] = [
95
+ "default",
96
+ "secondary",
97
+ "destructive",
98
+ "destructive-soft",
99
+ "outline",
100
+ "success",
101
+ "warning",
102
+ ];
103
+
104
+ const STATUS_TONES: StatusTone[] = [
105
+ "progress",
106
+ "business-review",
107
+ "code-review",
108
+ "done",
109
+ "cancelled",
110
+ "active",
111
+ "critical",
112
+ "neutral",
113
+ ];
114
+
115
+ // Space after the last section, so Card, Upgrade and Overlays can scroll to the
116
+ // top of an 844-tall phone for screenshot crops.
117
+ const GALLERY_END_SPACE = 720;
118
+
119
+ const PLANS: PricingPlan[] = [
120
+ {
121
+ id: "free",
122
+ name: "Free",
123
+ price: { month: "£0", year: "£0" },
124
+ current: true,
125
+ benefits: [{ label: "Core features" }],
126
+ },
127
+ {
128
+ id: "pro",
129
+ name: "V Pro",
130
+ price: { month: "£X", year: "£Y" },
131
+ featured: true,
132
+ intro: "Everything in Free, and:",
133
+ },
134
+ ];
135
+
136
+ /** Body text in the scheme's foreground (a style, so it reads in dark mode too). */
137
+ function GalleryText({ className, style, ...props }: TextProps & { className?: string }) {
138
+ const scheme = useScheme();
139
+ return (
140
+ <Text
141
+ className={className ?? "font-instrument-sans text-base"}
142
+ style={[{ color: tokens.color.foreground[scheme] }, style]}
143
+ {...props}
144
+ />
145
+ );
146
+ }
147
+
148
+ function Section({ title, children }: { title: string; children: ReactNode }) {
149
+ return (
150
+ <View className="gap-3">
151
+ <GalleryText
152
+ accessibilityRole="header"
153
+ className="font-instrument-sans-semibold text-lg"
154
+ style={typeStyle("semibold")}
155
+ >
156
+ {title}
157
+ </GalleryText>
158
+ {children}
159
+ </View>
160
+ );
161
+ }
162
+
163
+ export function ParityGallery() {
164
+ const { accentColor } = useNativeUi();
165
+ return (
166
+ <NativeUiProvider largerText={false} accentColor={accentColor}>
167
+ <GalleryScreen />
168
+ </NativeUiProvider>
169
+ );
170
+ }
171
+
172
+ function GalleryScreen() {
173
+ const [checked, setChecked] = useState<boolean | "indeterminate">("indeterminate");
174
+ const [switchOn, setSwitchOn] = useState(true);
175
+ const [colour, setColour] = useState<string | null | undefined>(undefined);
176
+ const [emptySearch, setEmptySearch] = useState("");
177
+ const [search, setSearch] = useState("Pink");
178
+ const [progress, setProgress] = useState(40);
179
+ const [pricingOpen, setPricingOpen] = useState(false);
180
+ const [confirmOpen, setConfirmOpen] = useState(false);
181
+
182
+ return (
183
+ <View className="flex-1">
184
+ <ScrollView
185
+ // So the SearchInput clear button takes the first tap while the keyboard is up.
186
+ keyboardShouldPersistTaps="handled"
187
+ contentContainerClassName="gap-8 px-4 pt-4"
188
+ contentContainerStyle={{ paddingBottom: GALLERY_END_SPACE }}
189
+ >
190
+ <Section title="Brand">
191
+ {(["vibot", "verve", "velth", "vmem", "vhub"] as const).map((app) => (
192
+ <AppWordmark key={app} app={app} />
193
+ ))}
194
+ <View className="flex-row flex-wrap items-center gap-3">
195
+ {APP_NAMES.map((app) => {
196
+ const Icon = APP_ICONS[app];
197
+ return <Icon key={app} size={28} />;
198
+ })}
199
+ <AppMark app="vibot" variant="tile" size={40} />
200
+ <AppMark app="vibot" variant="tile-light" size={40} />
201
+ </View>
202
+ </Section>
203
+
204
+ <Section title="Button">
205
+ {/* The web gallery: a full-width column of variants, then the sm row. */}
206
+ <View className="gap-2">
207
+ {BUTTON_VARIANTS.map((variant) => (
208
+ <Button key={variant} variant={variant} onPress={() => toast(variant)}>
209
+ {variant}
210
+ </Button>
211
+ ))}
212
+ <View className="flex-row gap-2">
213
+ <Button size="sm" fullWidth={false} loading>
214
+ Saving
215
+ </Button>
216
+ <Button size="sm" fullWidth={false} disabled>
217
+ Disabled
218
+ </Button>
219
+ </View>
220
+ </View>
221
+ <View className="flex-row flex-wrap items-center gap-2">
222
+ <Button size="sm" fullWidth={false}>
223
+ Small
224
+ </Button>
225
+ <Button fullWidth={false}>Default</Button>
226
+ <Button size="lg" fullWidth={false}>
227
+ Large
228
+ </Button>
229
+ </View>
230
+ </Section>
231
+
232
+ <Section title="Badge">
233
+ <View className="flex-row flex-wrap gap-2">
234
+ {BADGE_VARIANTS.map((variant) => (
235
+ <Badge key={variant} variant={variant}>
236
+ {variant}
237
+ </Badge>
238
+ ))}
239
+ </View>
240
+ </Section>
241
+
242
+ <Section title="StatusDot">
243
+ <View className="flex-row flex-wrap items-center gap-3">
244
+ {STATUS_TONES.map((tone) => (
245
+ <StatusDot key={tone} tone={tone} size="md" />
246
+ ))}
247
+ </View>
248
+ </Section>
249
+
250
+ <Section title="Fields">
251
+ <View className="gap-2">
252
+ <Label nativeID="gallery-name">Name</Label>
253
+ <Input placeholder="Your name" accessibilityLabel="Name" />
254
+ </View>
255
+ {/* Empty (`sm`: icon and placeholder) and filled (`md`: the clear button). */}
256
+ <SearchInput value={emptySearch} onChange={setEmptySearch} />
257
+ <SearchInput value={search} onChange={setSearch} size="md" label="Search colours" />
258
+ <Textarea placeholder="Notes" accessibilityLabel="Notes" />
259
+ <Select
260
+ value={colour}
261
+ onValueChange={setColour}
262
+ placeholder="Pick a colour"
263
+ options={[
264
+ { label: "Pink", value: "pink" },
265
+ { label: "None", value: null },
266
+ ]}
267
+ />
268
+ <View className="flex-row items-center gap-3">
269
+ <Checkbox
270
+ checked={checked}
271
+ onCheckedChange={setChecked}
272
+ accessibilityLabel="Remember me"
273
+ />
274
+ <Label nativeID="gallery-remember">Remember me</Label>
275
+ <Switch checked={switchOn} onCheckedChange={setSwitchOn} />
276
+ </View>
277
+ <LabeledSwitchRow
278
+ label="Reminders"
279
+ description="A daily nudge at 9:00."
280
+ checked={switchOn}
281
+ onCheckedChange={setSwitchOn}
282
+ />
283
+ </Section>
284
+
285
+ <Section title="Tabs">
286
+ <Tabs defaultValue="one">
287
+ <TabsList variant="segmented">
288
+ <TabsTrigger value="one">One</TabsTrigger>
289
+ <TabsTrigger value="two">Two</TabsTrigger>
290
+ <TabsTrigger value="three" disabled>
291
+ Three
292
+ </TabsTrigger>
293
+ </TabsList>
294
+ <TabsContent value="one">
295
+ <GalleryText>First panel</GalleryText>
296
+ </TabsContent>
297
+ <TabsContent value="two">
298
+ <GalleryText>Second panel</GalleryText>
299
+ </TabsContent>
300
+ </Tabs>
301
+ </Section>
302
+
303
+ <Section title="Avatar, Progress, Spinner, Skeleton">
304
+ <View className="flex-row items-center gap-3">
305
+ <Avatar>
306
+ <AvatarFallback>{initials("Ada Lovelace")}</AvatarFallback>
307
+ </Avatar>
308
+ <Avatar size={56}>
309
+ <AvatarFallback>{initials("vibot")}</AvatarFallback>
310
+ </Avatar>
311
+ <Spinner />
312
+ <Spinner size="lg" />
313
+ </View>
314
+ <Progress value={progress} />
315
+ <Button
316
+ variant="secondary"
317
+ size="sm"
318
+ onPress={() => setProgress((value) => (value + 20) % 120)}
319
+ >
320
+ Step progress
321
+ </Button>
322
+ <Skeleton className="h-4 w-32" />
323
+ <Separator />
324
+ <View className="h-24">
325
+ <CenteredSpinner label="Loading list" />
326
+ </View>
327
+ </Section>
328
+
329
+ <Section title="Card">
330
+ <Card title="Today" description="Card props, as before.">
331
+ <GalleryText>Body</GalleryText>
332
+ </Card>
333
+ <Card>
334
+ <CardHeader>
335
+ <CardTitle>Composed</CardTitle>
336
+ <CardDescription>Web part names inside the native card.</CardDescription>
337
+ </CardHeader>
338
+ <CardContent>
339
+ <GalleryText>Content</GalleryText>
340
+ </CardContent>
341
+ <CardFooter>
342
+ <Button size="sm" fullWidth={false} variant="secondary">
343
+ Later
344
+ </Button>
345
+ <Button size="sm" fullWidth={false}>
346
+ Save
347
+ </Button>
348
+ </CardFooter>
349
+ </Card>
350
+ </Section>
351
+
352
+ <Section title="Upgrade">
353
+ <UpgradeToProCard onUpgrade={() => setPricingOpen(true)} />
354
+ <UpgradeToProCard collapsed onUpgrade={() => setPricingOpen(true)} />
355
+ </Section>
356
+
357
+ <Section title="Overlays and toast">
358
+ <Dialog>
359
+ <DialogTrigger asChild>
360
+ <Button variant="secondary">Open dialog</Button>
361
+ </DialogTrigger>
362
+ <DialogContent>
363
+ <DialogHeader>
364
+ <DialogTitle>Rename list</DialogTitle>
365
+ <DialogDescription>Pick a short name.</DialogDescription>
366
+ </DialogHeader>
367
+ <DialogBody>
368
+ <Input placeholder="Name" accessibilityLabel="List name" />
369
+ </DialogBody>
370
+ <DialogFooter>
371
+ <DialogClose asChild>
372
+ <Button variant="secondary" shadowType="flat" fullWidth={false}>
373
+ Cancel
374
+ </Button>
375
+ </DialogClose>
376
+ <DialogClose asChild>
377
+ <Button
378
+ shadowType="flat"
379
+ fullWidth={false}
380
+ onPress={() => toast.success("Renamed")}
381
+ >
382
+ Save
383
+ </Button>
384
+ </DialogClose>
385
+ </DialogFooter>
386
+ </DialogContent>
387
+ </Dialog>
388
+
389
+ <AlertDialog>
390
+ <AlertDialogTrigger asChild>
391
+ <Button variant="secondary">Open alert dialog</Button>
392
+ </AlertDialogTrigger>
393
+ <AlertDialogContent>
394
+ <AlertDialogHeader>
395
+ <AlertDialogTitle>Delete this list?</AlertDialogTitle>
396
+ <AlertDialogDescription>This cannot be undone.</AlertDialogDescription>
397
+ </AlertDialogHeader>
398
+ <AlertDialogFooter>
399
+ <AlertDialogCancel>Cancel</AlertDialogCancel>
400
+ <AlertDialogAction variant="destructive" onPress={() => toast.error("Deleted")}>
401
+ Delete
402
+ </AlertDialogAction>
403
+ </AlertDialogFooter>
404
+ </AlertDialogContent>
405
+ </AlertDialog>
406
+
407
+ <Button variant="secondary" onPress={() => setConfirmOpen(true)}>
408
+ Open confirm dialog
409
+ </Button>
410
+ <ConfirmDialog
411
+ open={confirmOpen}
412
+ onOpenChange={setConfirmOpen}
413
+ title="Sign out?"
414
+ description="You can sign back in at any time."
415
+ confirmLabel="Sign out"
416
+ variant="destructive"
417
+ onConfirm={() => toast.success("Signed out")}
418
+ />
419
+
420
+ <Sheet>
421
+ <SheetTrigger asChild>
422
+ <Button variant="secondary">Open sheet</Button>
423
+ </SheetTrigger>
424
+ <SheetContent>
425
+ <SheetHeader>
426
+ <SheetTitle>Filters</SheetTitle>
427
+ <SheetDescription>Narrow the list.</SheetDescription>
428
+ </SheetHeader>
429
+ <LabeledSwitchRow
430
+ label="Done items"
431
+ checked={switchOn}
432
+ onCheckedChange={setSwitchOn}
433
+ />
434
+ <SheetFooter>
435
+ <Button shadowType="flat">Apply</Button>
436
+ </SheetFooter>
437
+ </SheetContent>
438
+ </Sheet>
439
+
440
+ <View className="flex-row flex-wrap gap-2">
441
+ <Button size="sm" fullWidth={false} onPress={() => toast.success("Saved")}>
442
+ Success
443
+ </Button>
444
+ <Button size="sm" fullWidth={false} onPress={() => toast.warning("Offline")}>
445
+ Warning
446
+ </Button>
447
+ <Button
448
+ size="sm"
449
+ fullWidth={false}
450
+ onPress={() => {
451
+ const id = toast.loading("Syncing");
452
+ setTimeout(() => toast.success("Synced", { id }), 1500);
453
+ }}
454
+ >
455
+ Loading
456
+ </Button>
457
+ <Button
458
+ size="sm"
459
+ fullWidth={false}
460
+ onPress={() =>
461
+ toast("Archived", {
462
+ description: "Moved to the archive.",
463
+ action: { label: "Undo", onPress: () => toast.info("Restored") },
464
+ })
465
+ }
466
+ >
467
+ Action
468
+ </Button>
469
+ </View>
470
+ </Section>
471
+
472
+ <Section title="EmptyState">
473
+ <EmptyState title="Nothing yet" description="Items you add show here." compact />
474
+ <EmptyState.Root variant="panel">
475
+ <EmptyState.Icon icon={IconInbox} />
476
+ <EmptyState.Title>No messages</EmptyState.Title>
477
+ <EmptyState.Description>New messages land here.</EmptyState.Description>
478
+ </EmptyState.Root>
479
+ </Section>
480
+ </ScrollView>
481
+
482
+ <PricingModal
483
+ open={pricingOpen}
484
+ onClose={() => setPricingOpen(false)}
485
+ title="Upgrade to V Pro"
486
+ apps={["vibot", "verve", "velth", "vmem", "vhub"]}
487
+ currentApp="vibot"
488
+ headline="One plan. Every V app."
489
+ plans={PLANS}
490
+ finePrint="Placeholder prices for the gallery."
491
+ onCheckout={(plan, period) => toast(`${plan.name} (${period})`)}
492
+ />
493
+ <Toaster />
494
+ </View>
495
+ );
496
+ }