@olympusoss/canvas 4.0.0 → 5.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +108 -0
- package/package.json +14 -3
- package/src/atoms/avatar/avatar.md +185 -0
- package/src/atoms/avatar/avatar.styles.ts +48 -0
- package/src/atoms/avatar/avatar.tsx +99 -0
- package/src/atoms/badge/badge.md +237 -0
- package/src/atoms/badge/badge.styles.ts +79 -0
- package/src/atoms/badge/badge.tsx +86 -0
- package/src/atoms/breadcrumb/breadcrumb.md +233 -0
- package/src/atoms/breadcrumb/breadcrumb.styles.ts +40 -0
- package/src/atoms/breadcrumb/breadcrumb.tsx +130 -0
- package/src/atoms/button/button.android.tsx +6 -0
- package/src/atoms/button/button.ios.tsx +6 -0
- package/src/atoms/button/button.md +184 -0
- package/src/atoms/button/button.shared.tsx +79 -0
- package/src/atoms/button/button.styles.ts +152 -0
- package/src/atoms/button/button.tsx +6 -0
- package/src/atoms/button-group/button-group.android.tsx +6 -0
- package/src/atoms/button-group/button-group.ios.tsx +6 -0
- package/src/atoms/button-group/button-group.md +120 -0
- package/src/atoms/button-group/button-group.shared.tsx +398 -0
- package/src/atoms/button-group/button-group.styles.ts +483 -0
- package/src/atoms/button-group/button-group.tsx +6 -0
- package/src/atoms/checkbox/checkbox.android.tsx +6 -0
- package/src/atoms/checkbox/checkbox.ios.tsx +6 -0
- package/src/atoms/checkbox/checkbox.md +150 -0
- package/src/atoms/checkbox/checkbox.shared.tsx +103 -0
- package/src/atoms/checkbox/checkbox.styles.ts +106 -0
- package/src/atoms/checkbox/checkbox.tsx +6 -0
- package/src/atoms/combobox/combobox.android.tsx +6 -0
- package/src/atoms/combobox/combobox.ios.tsx +6 -0
- package/src/atoms/combobox/combobox.md +213 -0
- package/src/atoms/combobox/combobox.shared.tsx +160 -0
- package/src/atoms/combobox/combobox.styles.ts +270 -0
- package/src/atoms/combobox/combobox.tsx +6 -0
- package/src/atoms/divider/divider.md +140 -0
- package/src/atoms/divider/divider.styles.ts +35 -0
- package/src/atoms/divider/divider.tsx +67 -0
- package/src/atoms/dropdown/dropdown.android.tsx +6 -0
- package/src/atoms/dropdown/dropdown.ios.tsx +6 -0
- package/src/atoms/dropdown/dropdown.md +221 -0
- package/src/atoms/dropdown/dropdown.shared.tsx +190 -0
- package/src/atoms/dropdown/dropdown.styles.ts +233 -0
- package/src/atoms/dropdown/dropdown.tsx +6 -0
- package/src/atoms/icon/icon.md +131 -0
- package/src/atoms/icon/icon.styles.ts +30 -0
- package/src/atoms/icon/icon.tsx +328 -0
- package/src/atoms/index.ts +24 -0
- package/src/atoms/input/input.android.tsx +6 -0
- package/src/atoms/input/input.ios.tsx +6 -0
- package/src/atoms/input/input.md +118 -0
- package/src/atoms/input/input.shared.tsx +203 -0
- package/src/atoms/input/input.styles.ts +286 -0
- package/src/atoms/input/input.tsx +6 -0
- package/src/atoms/kbd/kbd.md +91 -0
- package/src/atoms/kbd/kbd.styles.ts +33 -0
- package/src/atoms/kbd/kbd.tsx +27 -0
- package/src/atoms/listbox/listbox.md +177 -0
- package/src/atoms/listbox/listbox.styles.ts +60 -0
- package/src/atoms/listbox/listbox.tsx +113 -0
- package/src/atoms/pagination/pagination.android.tsx +6 -0
- package/src/atoms/pagination/pagination.ios.tsx +6 -0
- package/src/atoms/pagination/pagination.md +133 -0
- package/src/atoms/pagination/pagination.shared.tsx +289 -0
- package/src/atoms/pagination/pagination.styles.ts +245 -0
- package/src/atoms/pagination/pagination.tsx +6 -0
- package/src/atoms/popover/popover.android.tsx +8 -0
- package/src/atoms/popover/popover.ios.tsx +6 -0
- package/src/atoms/popover/popover.md +87 -0
- package/src/atoms/popover/popover.shared.tsx +124 -0
- package/src/atoms/popover/popover.styles.ts +144 -0
- package/src/atoms/popover/popover.tsx +6 -0
- package/src/atoms/radio/radio.android.tsx +6 -0
- package/src/atoms/radio/radio.ios.tsx +6 -0
- package/src/atoms/radio/radio.md +173 -0
- package/src/atoms/radio/radio.shared.tsx +98 -0
- package/src/atoms/radio/radio.styles.ts +109 -0
- package/src/atoms/radio/radio.tsx +6 -0
- package/src/atoms/select/select.android.tsx +6 -0
- package/src/atoms/select/select.ios.tsx +6 -0
- package/src/atoms/select/select.md +156 -0
- package/src/atoms/select/select.shared.tsx +143 -0
- package/src/atoms/select/select.styles.ts +310 -0
- package/src/atoms/select/select.tsx +6 -0
- package/src/atoms/skeleton/skeleton.md +135 -0
- package/src/atoms/skeleton/skeleton.styles.ts +117 -0
- package/src/atoms/skeleton/skeleton.tsx +145 -0
- package/src/atoms/spinner/spinner.android.tsx +7 -0
- package/src/atoms/spinner/spinner.ios.tsx +7 -0
- package/src/atoms/spinner/spinner.md +94 -0
- package/src/atoms/spinner/spinner.shared.tsx +92 -0
- package/src/atoms/spinner/spinner.styles.tsx +115 -0
- package/src/atoms/spinner/spinner.tsx +7 -0
- package/src/atoms/switch/switch.android.tsx +6 -0
- package/src/atoms/switch/switch.ios.tsx +6 -0
- package/src/atoms/switch/switch.md +91 -0
- package/src/atoms/switch/switch.shared.tsx +97 -0
- package/src/atoms/switch/switch.styles.ts +79 -0
- package/src/atoms/switch/switch.tsx +6 -0
- package/src/atoms/textarea/textarea.android.tsx +6 -0
- package/src/atoms/textarea/textarea.ios.tsx +6 -0
- package/src/atoms/textarea/textarea.md +140 -0
- package/src/atoms/textarea/textarea.shared.tsx +74 -0
- package/src/atoms/textarea/textarea.styles.ts +116 -0
- package/src/atoms/textarea/textarea.tsx +6 -0
- package/src/atoms/tooltip/tooltip.android.tsx +6 -0
- package/src/atoms/tooltip/tooltip.ios.tsx +7 -0
- package/src/atoms/tooltip/tooltip.md +122 -0
- package/src/atoms/tooltip/tooltip.shared.tsx +113 -0
- package/src/atoms/tooltip/tooltip.styles.ts +113 -0
- package/src/atoms/tooltip/tooltip.tsx +6 -0
- package/src/atoms/typography/typography.md +330 -0
- package/src/atoms/typography/typography.styles.ts +95 -0
- package/src/atoms/typography/typography.tsx +76 -0
- package/src/index.ts +12 -2
- package/src/molecules/action-panels/action-panels.md +133 -0
- package/src/molecules/action-panels/action-panels.styles.ts +39 -0
- package/src/molecules/action-panels/action-panels.tsx +113 -0
- package/src/molecules/alert/alert.md +119 -0
- package/src/molecules/alert/alert.styles.ts +88 -0
- package/src/molecules/alert/alert.tsx +74 -0
- package/src/molecules/alert-dialog/alert-dialog.android.tsx +6 -0
- package/src/molecules/alert-dialog/alert-dialog.ios.tsx +6 -0
- package/src/molecules/alert-dialog/alert-dialog.md +177 -0
- package/src/molecules/alert-dialog/alert-dialog.shared.tsx +187 -0
- package/src/molecules/alert-dialog/alert-dialog.styles.ts +248 -0
- package/src/molecules/alert-dialog/alert-dialog.tsx +6 -0
- package/src/molecules/card/card.md +190 -0
- package/src/molecules/card/card.styles.ts +67 -0
- package/src/molecules/card/card.tsx +176 -0
- package/src/molecules/code-block/code-block.md +159 -0
- package/src/molecules/code-block/code-block.styles.ts +167 -0
- package/src/molecules/code-block/code-block.tsx +176 -0
- package/src/molecules/description-lists/description-lists.md +129 -0
- package/src/molecules/description-lists/description-lists.styles.ts +102 -0
- package/src/molecules/description-lists/description-lists.tsx +133 -0
- package/src/molecules/empty-state/empty-state.md +218 -0
- package/src/molecules/empty-state/empty-state.styles.ts +63 -0
- package/src/molecules/empty-state/empty-state.tsx +77 -0
- package/src/molecules/feeds/feeds.md +102 -0
- package/src/molecules/feeds/feeds.styles.ts +120 -0
- package/src/molecules/feeds/feeds.tsx +167 -0
- package/src/molecules/field/field.md +117 -0
- package/src/molecules/field/field.styles.ts +85 -0
- package/src/molecules/field/field.tsx +175 -0
- package/src/molecules/fieldset/fieldset.md +141 -0
- package/src/molecules/fieldset/fieldset.styles.ts +79 -0
- package/src/molecules/fieldset/fieldset.tsx +182 -0
- package/src/molecules/form/form.md +137 -0
- package/src/molecules/form/form.styles.ts +39 -0
- package/src/molecules/form/form.tsx +246 -0
- package/src/molecules/grid-lists/grid-lists.md +114 -0
- package/src/molecules/grid-lists/grid-lists.styles.ts +79 -0
- package/src/molecules/grid-lists/grid-lists.tsx +157 -0
- package/src/molecules/index.ts +16 -0
- package/src/molecules/media-objects/media-objects.md +87 -0
- package/src/molecules/media-objects/media-objects.styles.ts +94 -0
- package/src/molecules/media-objects/media-objects.tsx +128 -0
- package/src/molecules/stacked-lists/stacked-lists.md +116 -0
- package/src/molecules/stacked-lists/stacked-lists.styles.ts +111 -0
- package/src/molecules/stacked-lists/stacked-lists.tsx +195 -0
- package/src/molecules/stats/stats.md +166 -0
- package/src/molecules/stats/stats.styles.ts +91 -0
- package/src/molecules/stats/stats.tsx +88 -0
- package/src/organisms/calendar/calendar.android.tsx +6 -0
- package/src/organisms/calendar/calendar.ios.tsx +6 -0
- package/src/organisms/calendar/calendar.md +114 -0
- package/src/organisms/calendar/calendar.shared.tsx +146 -0
- package/src/organisms/calendar/calendar.styles.ts +315 -0
- package/src/organisms/calendar/calendar.tsx +6 -0
- package/src/organisms/charts/charts.md +326 -0
- package/src/organisms/charts/charts.styles.ts +135 -0
- package/src/organisms/charts/charts.tsx +124 -0
- package/src/organisms/command/command.md +117 -0
- package/src/organisms/command/command.styles.ts +179 -0
- package/src/organisms/command/command.tsx +164 -0
- package/src/organisms/data-table/data-table.md +182 -0
- package/src/organisms/data-table/data-table.styles.ts +103 -0
- package/src/organisms/data-table/data-table.tsx +105 -0
- package/src/organisms/dialog/dialog.android.tsx +6 -0
- package/src/organisms/dialog/dialog.ios.tsx +6 -0
- package/src/organisms/dialog/dialog.md +271 -0
- package/src/organisms/dialog/dialog.shared.tsx +230 -0
- package/src/organisms/dialog/dialog.styles.ts +272 -0
- package/src/organisms/dialog/dialog.tsx +6 -0
- package/src/organisms/filter-panel/filter-panel.md +116 -0
- package/src/organisms/filter-panel/filter-panel.styles.ts +83 -0
- package/src/organisms/filter-panel/filter-panel.tsx +91 -0
- package/src/organisms/index.ts +13 -0
- package/src/organisms/navbars/navbars.android.tsx +6 -0
- package/src/organisms/navbars/navbars.ios.tsx +6 -0
- package/src/organisms/navbars/navbars.md +144 -0
- package/src/organisms/navbars/navbars.shared.tsx +137 -0
- package/src/organisms/navbars/navbars.styles.ts +251 -0
- package/src/organisms/navbars/navbars.tsx +6 -0
- package/src/organisms/overlays/overlays.android.tsx +6 -0
- package/src/organisms/overlays/overlays.ios.tsx +6 -0
- package/src/organisms/overlays/overlays.md +123 -0
- package/src/organisms/overlays/overlays.shared.tsx +175 -0
- package/src/organisms/overlays/overlays.styles.ts +309 -0
- package/src/organisms/overlays/overlays.tsx +6 -0
- package/src/organisms/row-menu/row-menu.android.tsx +6 -0
- package/src/organisms/row-menu/row-menu.ios.tsx +6 -0
- package/src/organisms/row-menu/row-menu.md +102 -0
- package/src/organisms/row-menu/row-menu.shared.tsx +105 -0
- package/src/organisms/row-menu/row-menu.styles.ts +262 -0
- package/src/organisms/row-menu/row-menu.tsx +6 -0
- package/src/organisms/sidebar/sidebar.android.tsx +6 -0
- package/src/organisms/sidebar/sidebar.ios.tsx +6 -0
- package/src/organisms/sidebar/sidebar.md +188 -0
- package/src/organisms/sidebar/sidebar.shared.tsx +167 -0
- package/src/organisms/sidebar/sidebar.styles.ts +262 -0
- package/src/organisms/sidebar/sidebar.tsx +6 -0
- package/src/organisms/stepper/stepper.android.tsx +6 -0
- package/src/organisms/stepper/stepper.ios.tsx +6 -0
- package/src/organisms/stepper/stepper.md +150 -0
- package/src/organisms/stepper/stepper.shared.tsx +158 -0
- package/src/organisms/stepper/stepper.styles.ts +280 -0
- package/src/organisms/stepper/stepper.tsx +6 -0
- package/src/organisms/tabs/tabs.android.tsx +6 -0
- package/src/organisms/tabs/tabs.ios.tsx +6 -0
- package/src/organisms/tabs/tabs.md +127 -0
- package/src/organisms/tabs/tabs.shared.tsx +281 -0
- package/src/organisms/tabs/tabs.styles.ts +398 -0
- package/src/organisms/tabs/tabs.tsx +6 -0
- package/src/style/color.ts +17 -0
- package/src/style/index.ts +14 -0
- package/src/style/primitives.ts +26 -0
- package/src/style/responsive.ts +45 -0
- package/src/style/shadow.ts +21 -0
- package/src/style/theme.tsx +56 -0
- package/src/style/tokens.ts +487 -0
- package/src/theme.ts +21 -0
- package/styles/canvas.css +128 -67
- package/tsconfig.json +4 -2
- package/src/cn.ts +0 -3
- package/styles/base.css +0 -17
- package/styles/components/alert.css +0 -66
- package/styles/components/app-shell.css +0 -46
- package/styles/components/avatar.css +0 -15
- package/styles/components/badge.css +0 -83
- package/styles/components/breadcrumb.css +0 -35
- package/styles/components/button-group.css +0 -23
- package/styles/components/button.css +0 -107
- package/styles/components/calendar.css +0 -73
- package/styles/components/card.css +0 -58
- package/styles/components/checkbox.css +0 -55
- package/styles/components/code-block.css +0 -18
- package/styles/components/combobox.css +0 -75
- package/styles/components/command.css +0 -94
- package/styles/components/data-table.css +0 -142
- package/styles/components/dialog.css +0 -72
- package/styles/components/dropdown.css +0 -54
- package/styles/components/empty-state.css +0 -17
- package/styles/components/field.css +0 -27
- package/styles/components/filter-panel.css +0 -58
- package/styles/components/form.css +0 -27
- package/styles/components/icon.css +0 -8
- package/styles/components/input-group.css +0 -45
- package/styles/components/input.css +0 -56
- package/styles/components/kbd.css +0 -15
- package/styles/components/page-header.css +0 -52
- package/styles/components/pagination.css +0 -48
- package/styles/components/popover.css +0 -14
- package/styles/components/radio.css +0 -28
- package/styles/components/row-menu.css +0 -69
- package/styles/components/section-card.css +0 -49
- package/styles/components/select.css +0 -57
- package/styles/components/separator.css +0 -32
- package/styles/components/sheet.css +0 -70
- package/styles/components/sidebar.css +0 -146
- package/styles/components/skeleton.css +0 -32
- package/styles/components/spinner.css +0 -26
- package/styles/components/stat-card.css +0 -71
- package/styles/components/stepper.css +0 -63
- package/styles/components/switch.css +0 -45
- package/styles/components/tabs.css +0 -40
- package/styles/components/textarea.css +0 -31
- package/styles/components/toast.css +0 -95
- package/styles/components/tooltip.css +0 -53
- package/styles/components/topbar.css +0 -24
- package/styles/components/typography.css +0 -105
- package/styles/patterns/backdrops.css +0 -35
- package/styles/patterns/density.css +0 -66
- package/styles/patterns/focus.css +0 -38
- package/styles/patterns/glass.css +0 -85
- package/styles/patterns/high-contrast.css +0 -70
- package/styles/patterns/reduced-motion.css +0 -12
- package/styles/patterns/scrollbar.css +0 -10
- package/styles/reset.css +0 -89
- package/styles/tokens/colors.css +0 -106
- package/styles/tokens/motion.css +0 -33
- package/styles/tokens/radius.css +0 -10
- package/styles/tokens/shadows.css +0 -35
- package/styles/tokens/spacing.css +0 -19
- package/styles/tokens/typography.css +0 -6
- package/styles/tokens/z-index.css +0 -12
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
import { useState } from "react";
|
|
2
|
+
import { View, Text, Pressable, useTheme, type StyleProp, type ViewStyle } from "../../style/index.js";
|
|
3
|
+
import { Button } from "../../atoms/button/button.js";
|
|
4
|
+
import { Input } from "../../atoms/input/input.js";
|
|
5
|
+
import * as s from "./alert-dialog.styles.js";
|
|
6
|
+
import { type Width, type AlertDialogSkin } from "./alert-dialog.styles.js";
|
|
7
|
+
|
|
8
|
+
// Shared AlertDialog shell. The structure (optional trigger + dim backdrop +
|
|
9
|
+
// centered card + title/description + optional confirmation field + action row),
|
|
10
|
+
// the uncontrolled/controlled open state, the width/destructive precedence, and
|
|
11
|
+
// the confirm/cancel handlers live here once; a platform file supplies only its
|
|
12
|
+
// skin (card shape, type, action layout, press feedback) and calls
|
|
13
|
+
// createAlertDialog.
|
|
14
|
+
//
|
|
15
|
+
// AlertDialog: a terse yes/no confirmation modal, the compact sibling of Dialog.
|
|
16
|
+
// It poses a question (title), an optional short description, and an action row of
|
|
17
|
+
// a Cancel plus a single confirm. Reserve it for decisions that must block the
|
|
18
|
+
// rest of the app, especially irreversible ones (pass `destructive` to render the
|
|
19
|
+
// confirm as a red, destructive action).
|
|
20
|
+
//
|
|
21
|
+
// In the docs preview the overlay is rendered INLINE: a contained dim backdrop
|
|
22
|
+
// View wraps the centered card, so it reads as a modal within the preview area
|
|
23
|
+
// rather than a full-screen portal that would cover it.
|
|
24
|
+
//
|
|
25
|
+
// Boolean-prop API: one boolean per option, grouped by axis, first-match
|
|
26
|
+
// precedence within an axis (mirrors Button's intentOf). Axes:
|
|
27
|
+
//
|
|
28
|
+
// - Width: `narrow` < `small` < (default medium) < `large`, the panel max-width.
|
|
29
|
+
// Pass at most one; first match wins in that order (narrow, small, large).
|
|
30
|
+
// - Confirm intent: `destructive` renders the confirm action as a destructive
|
|
31
|
+
// action (for an irreversible action); omit for the default primary confirm.
|
|
32
|
+
|
|
33
|
+
export interface AlertDialogProps {
|
|
34
|
+
// Content (strings).
|
|
35
|
+
title?: string;
|
|
36
|
+
description?: string;
|
|
37
|
+
// Trigger button label. When set, the dialog renders the button and opens
|
|
38
|
+
// itself on press (uncontrolled). Omit when you drive `open` yourself.
|
|
39
|
+
trigger?: string;
|
|
40
|
+
// Action labels.
|
|
41
|
+
confirmLabel?: string;
|
|
42
|
+
cancelLabel?: string;
|
|
43
|
+
// Controlled open state. Omit for uncontrolled (the trigger opens it).
|
|
44
|
+
open?: boolean;
|
|
45
|
+
// Fired when the open state changes (trigger press, confirm, cancel).
|
|
46
|
+
onOpenChange?: (open: boolean) => void;
|
|
47
|
+
// Width (pick one; default is the medium panel).
|
|
48
|
+
narrow?: boolean;
|
|
49
|
+
small?: boolean;
|
|
50
|
+
large?: boolean;
|
|
51
|
+
// Body: render a confirmation field ("Type DELETE to confirm") in the panel.
|
|
52
|
+
withInput?: boolean;
|
|
53
|
+
// Confirm intent (default is a primary confirm).
|
|
54
|
+
destructive?: boolean;
|
|
55
|
+
// Action handlers.
|
|
56
|
+
onConfirm?: () => void;
|
|
57
|
+
onCancel?: () => void;
|
|
58
|
+
/** Escape hatch for layout/positioning composition (mainly width). */
|
|
59
|
+
style?: StyleProp<ViewStyle>;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// Width precedence when more than one is passed: first match wins.
|
|
63
|
+
function widthOf(p: AlertDialogProps): Width {
|
|
64
|
+
if (p.narrow) return "narrow";
|
|
65
|
+
if (p.small) return "small";
|
|
66
|
+
if (p.large) return "large";
|
|
67
|
+
return "medium";
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/** Build an AlertDialog component from a platform skin. */
|
|
71
|
+
export function createAlertDialog(skin: AlertDialogSkin) {
|
|
72
|
+
return function AlertDialog(props: AlertDialogProps) {
|
|
73
|
+
const {
|
|
74
|
+
title,
|
|
75
|
+
description,
|
|
76
|
+
confirmLabel = "Continue",
|
|
77
|
+
cancelLabel = "Cancel",
|
|
78
|
+
trigger,
|
|
79
|
+
open: openProp,
|
|
80
|
+
onOpenChange,
|
|
81
|
+
withInput,
|
|
82
|
+
destructive,
|
|
83
|
+
onConfirm,
|
|
84
|
+
onCancel,
|
|
85
|
+
style,
|
|
86
|
+
} = props;
|
|
87
|
+
|
|
88
|
+
const { tokens } = useTheme();
|
|
89
|
+
|
|
90
|
+
// Uncontrolled by default: the trigger opens the dialog and an action closes
|
|
91
|
+
// it; a controlled `open` prop overrides this.
|
|
92
|
+
const [internalOpen, setInternalOpen] = useState(false);
|
|
93
|
+
const open = openProp ?? internalOpen;
|
|
94
|
+
const setOpen = (next: boolean) => {
|
|
95
|
+
if (openProp === undefined) setInternalOpen(next);
|
|
96
|
+
onOpenChange?.(next);
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
const width = widthOf(props);
|
|
100
|
+
|
|
101
|
+
const handleConfirm = () => {
|
|
102
|
+
onConfirm?.();
|
|
103
|
+
setOpen(false);
|
|
104
|
+
};
|
|
105
|
+
const handleCancel = () => {
|
|
106
|
+
onCancel?.();
|
|
107
|
+
setOpen(false);
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
// The action row. iOS renders two capsule buttons side by side (no divider)
|
|
111
|
+
// drawn by the skin; web/Android render a right-aligned row of the shell's
|
|
112
|
+
// Buttons.
|
|
113
|
+
const ripple = skin.ripple ? skin.ripple(tokens) : undefined;
|
|
114
|
+
const actionRow =
|
|
115
|
+
skin.actionLayout === "capsule" ? (
|
|
116
|
+
<View style={skin.capsuleRow!}>
|
|
117
|
+
<Pressable
|
|
118
|
+
onPress={handleCancel}
|
|
119
|
+
accessibilityRole="button"
|
|
120
|
+
android_ripple={ripple}
|
|
121
|
+
style={({ pressed }) => [
|
|
122
|
+
skin.capsuleCell!,
|
|
123
|
+
skin.cancelFill!(tokens),
|
|
124
|
+
skin.pressedOpacity != null && pressed ? { opacity: skin.pressedOpacity } : null,
|
|
125
|
+
]}
|
|
126
|
+
>
|
|
127
|
+
<Text style={skin.cancelLabelStyle!(tokens)}>{cancelLabel}</Text>
|
|
128
|
+
</Pressable>
|
|
129
|
+
<Pressable
|
|
130
|
+
onPress={handleConfirm}
|
|
131
|
+
accessibilityRole="button"
|
|
132
|
+
android_ripple={ripple}
|
|
133
|
+
style={({ pressed }) => [
|
|
134
|
+
skin.capsuleCell!,
|
|
135
|
+
skin.confirmFill!(tokens, !!destructive),
|
|
136
|
+
skin.pressedOpacity != null && pressed ? { opacity: skin.pressedOpacity } : null,
|
|
137
|
+
]}
|
|
138
|
+
>
|
|
139
|
+
<Text style={skin.confirmLabelStyle!(tokens, !!destructive)}>{confirmLabel}</Text>
|
|
140
|
+
</Pressable>
|
|
141
|
+
</View>
|
|
142
|
+
) : (
|
|
143
|
+
<View style={skin.actions}>
|
|
144
|
+
<Button {...skin.cancelButton} small={skin.buttonSmall} onPress={handleCancel}>
|
|
145
|
+
{cancelLabel}
|
|
146
|
+
</Button>
|
|
147
|
+
{destructive ? (
|
|
148
|
+
<Button destructive small={skin.buttonSmall} onPress={handleConfirm}>
|
|
149
|
+
{confirmLabel}
|
|
150
|
+
</Button>
|
|
151
|
+
) : (
|
|
152
|
+
<Button primary small={skin.buttonSmall} onPress={handleConfirm}>
|
|
153
|
+
{confirmLabel}
|
|
154
|
+
</Button>
|
|
155
|
+
)}
|
|
156
|
+
</View>
|
|
157
|
+
);
|
|
158
|
+
|
|
159
|
+
// Optional trigger button plus the modal. The modal is a contained dim
|
|
160
|
+
// backdrop: a centered, rounded scrim with presence in the preview (explicit
|
|
161
|
+
// minHeight) so the card reads as a modal within the area.
|
|
162
|
+
return (
|
|
163
|
+
<View style={s.root}>
|
|
164
|
+
{trigger != null ? (
|
|
165
|
+
<Button outline small onPress={() => setOpen(true)}>
|
|
166
|
+
{trigger}
|
|
167
|
+
</Button>
|
|
168
|
+
) : null}
|
|
169
|
+
{open ? (
|
|
170
|
+
<View style={[skin.backdrop, trigger != null ? s.triggerGap : null, { minHeight: 200 }]}>
|
|
171
|
+
<View style={[s.cardBase, skin.card(tokens), s.panelWidth[width], style]}>
|
|
172
|
+
{title != null ? <Text style={skin.title(tokens)}>{title}</Text> : null}
|
|
173
|
+
{description != null ? <Text style={skin.description(tokens)}>{description}</Text> : null}
|
|
174
|
+
{withInput ? (
|
|
175
|
+
<View style={skin.inputBlock}>
|
|
176
|
+
<Text style={skin.inputLabel(tokens)}>Type DELETE to confirm</Text>
|
|
177
|
+
<Input placeholder="DELETE" />
|
|
178
|
+
</View>
|
|
179
|
+
) : null}
|
|
180
|
+
{actionRow}
|
|
181
|
+
</View>
|
|
182
|
+
</View>
|
|
183
|
+
) : null}
|
|
184
|
+
</View>
|
|
185
|
+
);
|
|
186
|
+
};
|
|
187
|
+
}
|
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
import { type ViewStyle, type TextStyle } from "react-native";
|
|
2
|
+
import { type ColorTokens, shadow, alpha } from "../../style/index.js";
|
|
3
|
+
|
|
4
|
+
// Co-located AlertDialog skins, one per platform, all driven by the brand tokens
|
|
5
|
+
// (passed in from useTheme so they follow light/dark and read as glass when
|
|
6
|
+
// tokens.popover is swapped translucent at the theming level). The BRAND survives
|
|
7
|
+
// on every platform (the `primary` confirm and the `destructive` red); only the
|
|
8
|
+
// native SHAPE, sizing, title alignment, action layout, and press feedback change
|
|
9
|
+
// per OS:
|
|
10
|
+
// iOS (iOS 27 / Liquid Glass alert): a rounded card (~28 radius, `popover`
|
|
11
|
+
// fill, soft shadow, no border), a LEFT-aligned bold title and a left-aligned
|
|
12
|
+
// `muted-foreground` message; exactly two actions rendered as two CAPSULES
|
|
13
|
+
// side by side (no divider) — a gray Cancel capsule (`secondary` fill) and a
|
|
14
|
+
// filled Confirm capsule (`primary`, or `destructive` when destructive) with
|
|
15
|
+
// a white label. Press = opacity dim (~0.85).
|
|
16
|
+
// Android (Material 3 AlertDialog): an elevated surface (28 radius, `popover`
|
|
17
|
+
// fill, soft shadow), a LEFT-aligned title and left-aligned body; two M3 TEXT
|
|
18
|
+
// buttons bottom-right (Cancel then Confirm/Delete), the confirm tinted with
|
|
19
|
+
// the brand `primary` and a destructive confirm in the `destructive` red.
|
|
20
|
+
// Press = android_ripple (brand state layer).
|
|
21
|
+
// Web: the established Canvas look (the current alert-dialog, lifted verbatim) —
|
|
22
|
+
// a bordered, dim-backed card (8 radius, 1px `border`, `popover` fill,
|
|
23
|
+
// shadow-xl, padding 24), a left-aligned 16pt/600 title and 14pt
|
|
24
|
+
// `muted-foreground` description, with a right-aligned action row of an
|
|
25
|
+
// `outline` Cancel Button plus a `primary`/`destructive` confirm Button.
|
|
26
|
+
|
|
27
|
+
export type Width = "narrow" | "small" | "medium" | "large";
|
|
28
|
+
|
|
29
|
+
// Panel max-width per width axis (mirroring Tailwind's max-w-xs..lg). Shared by
|
|
30
|
+
// every platform; the native skins narrow the medium/large footprint a touch but
|
|
31
|
+
// the axis mapping is one source of truth.
|
|
32
|
+
export const panelWidth: Record<Width, ViewStyle> = {
|
|
33
|
+
narrow: { maxWidth: 320 },
|
|
34
|
+
small: { maxWidth: 384 },
|
|
35
|
+
medium: { maxWidth: 448 },
|
|
36
|
+
large: { maxWidth: 512 },
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
// How the action row is structured. "buttons" renders the shell's Button-based
|
|
40
|
+
// right-aligned row (web/Android); "capsule" renders the iOS side-by-side pair of
|
|
41
|
+
// pill buttons drawn by the skin's own action styles (no divider).
|
|
42
|
+
export type ActionLayout = "buttons" | "capsule";
|
|
43
|
+
|
|
44
|
+
// The contract a platform skin fulfills. The shell renders the wrapper, the dim
|
|
45
|
+
// backdrop, the card, the title/description, the optional confirmation field, and
|
|
46
|
+
// the action row; the skin maps the active platform's shape/fill/sizing/type/
|
|
47
|
+
// feedback onto each piece.
|
|
48
|
+
export interface AlertDialogSkin {
|
|
49
|
+
/** The contained dim scrim that centers the card within the preview area. */
|
|
50
|
+
backdrop: ViewStyle;
|
|
51
|
+
/** The card base: shape, border (or lack of), padding, shadow. tokens drive fill. */
|
|
52
|
+
card: (t: ColorTokens) => ViewStyle;
|
|
53
|
+
/** Title type + alignment (centered on iOS, left on Android/web). */
|
|
54
|
+
title: (t: ColorTokens) => TextStyle;
|
|
55
|
+
/** Description type + alignment + color. */
|
|
56
|
+
description: (t: ColorTokens) => TextStyle;
|
|
57
|
+
/** The confirmation-field block container ("Type DELETE to confirm" + input). */
|
|
58
|
+
inputBlock: ViewStyle;
|
|
59
|
+
/** The confirmation-field label. */
|
|
60
|
+
inputLabel: (t: ColorTokens) => TextStyle;
|
|
61
|
+
/** Which action structure the shell renders. */
|
|
62
|
+
actionLayout: ActionLayout;
|
|
63
|
+
// --- "buttons" layout (web/Android): a right-aligned row of shell Buttons ----
|
|
64
|
+
/** The right-aligned Button row container (the "buttons" layout). */
|
|
65
|
+
actions: ViewStyle;
|
|
66
|
+
/** The size prop passed to the shell's Cancel/Confirm Buttons. */
|
|
67
|
+
buttonSmall: boolean;
|
|
68
|
+
/** The intent props for the Cancel Button (web = outline; Android = ghost/text). */
|
|
69
|
+
cancelButton: { outline?: boolean; ghost?: boolean };
|
|
70
|
+
// --- "capsule" layout (iOS): two side-by-side pill buttons, no divider --------
|
|
71
|
+
/** The action row that holds the two equal-width capsules with a gap. */
|
|
72
|
+
capsuleRow: ViewStyle | null;
|
|
73
|
+
/** A single capsule cell base (shape/sizing); fill comes from the *Fill helpers. */
|
|
74
|
+
capsuleCell: ViewStyle | null;
|
|
75
|
+
/** The Cancel (gray) capsule fill. */
|
|
76
|
+
cancelFill: ((t: ColorTokens) => ViewStyle) | null;
|
|
77
|
+
/** The Confirm capsule fill; `destructive` swaps `primary` for `destructive`. */
|
|
78
|
+
confirmFill: ((t: ColorTokens, destructive: boolean) => ViewStyle) | null;
|
|
79
|
+
/** The Cancel capsule label (regular weight, on-secondary color). */
|
|
80
|
+
cancelLabelStyle: ((t: ColorTokens) => TextStyle) | null;
|
|
81
|
+
/** The Confirm capsule label (weight 600, on-fill foreground). */
|
|
82
|
+
confirmLabelStyle: ((t: ColorTokens, destructive: boolean) => TextStyle) | null;
|
|
83
|
+
/** iOS/web dim on press; Android uses a ripple instead (null). */
|
|
84
|
+
pressedOpacity: number | null;
|
|
85
|
+
/** Android ripple over the action cells; null on iOS/web. */
|
|
86
|
+
ripple: ((t: ColorTokens) => { color: string; borderless: boolean }) | null;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// --- shared layout fragments (identical across platforms) -------------------
|
|
90
|
+
|
|
91
|
+
// The outer wrapper aligns the dialog (and its optional trigger) to the start.
|
|
92
|
+
export const root: ViewStyle = { alignSelf: "flex-start" };
|
|
93
|
+
|
|
94
|
+
// Gap between the trigger button and the modal when the trigger renders above it.
|
|
95
|
+
export const triggerGap: ViewStyle = { marginTop: 12 };
|
|
96
|
+
|
|
97
|
+
// The card is full-width within its max-width; the per-platform skin supplies the
|
|
98
|
+
// shape/fill/padding/shadow on top of this.
|
|
99
|
+
export const cardBase: ViewStyle = { width: "100%" };
|
|
100
|
+
|
|
101
|
+
// ---------- Web: the established Canvas look (lifted verbatim) ----------
|
|
102
|
+
// The current alert-dialog: a contained dim scrim (rounded-md, black/50) centering
|
|
103
|
+
// a bordered card (rounded-md border bg-popover p-6 shadow-xl); a left-aligned
|
|
104
|
+
// 16pt/600 title and 14pt muted description; a right-aligned action row (gap-2,
|
|
105
|
+
// mt-6) of an outline Cancel Button plus a primary/destructive confirm Button.
|
|
106
|
+
export const webSkin: AlertDialogSkin = {
|
|
107
|
+
backdrop: {
|
|
108
|
+
alignItems: "center",
|
|
109
|
+
justifyContent: "center",
|
|
110
|
+
borderRadius: 8,
|
|
111
|
+
backgroundColor: alpha("#000000", 0.5),
|
|
112
|
+
padding: 32,
|
|
113
|
+
},
|
|
114
|
+
card: (t) => ({
|
|
115
|
+
borderRadius: 8,
|
|
116
|
+
borderWidth: 1,
|
|
117
|
+
borderColor: t.border,
|
|
118
|
+
backgroundColor: t.popover,
|
|
119
|
+
padding: 24,
|
|
120
|
+
...shadow("xl"),
|
|
121
|
+
}),
|
|
122
|
+
title: (t) => ({ fontSize: 16, lineHeight: 24, fontWeight: "600", color: t["popover-foreground"] }),
|
|
123
|
+
description: (t) => ({ marginTop: 8, fontSize: 14, lineHeight: 20, color: t["muted-foreground"] }),
|
|
124
|
+
inputBlock: { marginTop: 16 },
|
|
125
|
+
inputLabel: (t) => ({ marginBottom: 6, fontSize: 14, lineHeight: 20, fontWeight: "500", color: t.foreground }),
|
|
126
|
+
actionLayout: "buttons",
|
|
127
|
+
actions: { flexDirection: "row", justifyContent: "flex-end", gap: 8, marginTop: 24 },
|
|
128
|
+
buttonSmall: true,
|
|
129
|
+
cancelButton: { outline: true },
|
|
130
|
+
capsuleRow: null,
|
|
131
|
+
capsuleCell: null,
|
|
132
|
+
cancelFill: null,
|
|
133
|
+
confirmFill: null,
|
|
134
|
+
cancelLabelStyle: null,
|
|
135
|
+
confirmLabelStyle: null,
|
|
136
|
+
pressedOpacity: null,
|
|
137
|
+
ripple: null,
|
|
138
|
+
};
|
|
139
|
+
|
|
140
|
+
// ---------- iOS 27 (Liquid Glass alert): rounded card, left text, capsule actions ----------
|
|
141
|
+
// iOS 26+/27 alert (per Apple's design kit): a rounded card (~28pt radius) over the
|
|
142
|
+
// `popover` fill with a soft shadow and NO border; a LEFT-aligned bold title and a
|
|
143
|
+
// LEFT-aligned `muted-foreground` message. The two actions are CAPSULES side by
|
|
144
|
+
// side (no hairline divider): a gray Cancel capsule (`secondary` fill,
|
|
145
|
+
// `secondary-foreground` text) and a filled Confirm capsule (`primary`, or
|
|
146
|
+
// `destructive` when destructive) with white/`*-foreground` label. The brand
|
|
147
|
+
// survives: the iOS system blue becomes the indigo `primary` token. Press = opacity
|
|
148
|
+
// dim (~0.85).
|
|
149
|
+
const IOS_RADIUS = 28;
|
|
150
|
+
const IOS_CAPSULE_RADIUS = 999;
|
|
151
|
+
export const iosSkin: AlertDialogSkin = {
|
|
152
|
+
backdrop: {
|
|
153
|
+
alignItems: "center",
|
|
154
|
+
justifyContent: "center",
|
|
155
|
+
borderRadius: 8,
|
|
156
|
+
backgroundColor: alpha("#000000", 0.4),
|
|
157
|
+
padding: 32,
|
|
158
|
+
},
|
|
159
|
+
// Rounded card with content padding; no border, soft shadow.
|
|
160
|
+
card: (t) => ({
|
|
161
|
+
borderRadius: IOS_RADIUS,
|
|
162
|
+
backgroundColor: t.popover,
|
|
163
|
+
padding: 24,
|
|
164
|
+
...shadow("lg"),
|
|
165
|
+
}),
|
|
166
|
+
title: (t) => ({
|
|
167
|
+
fontSize: 20,
|
|
168
|
+
lineHeight: 25,
|
|
169
|
+
fontWeight: "700",
|
|
170
|
+
color: t["popover-foreground"],
|
|
171
|
+
}),
|
|
172
|
+
description: (t) => ({
|
|
173
|
+
marginTop: 8,
|
|
174
|
+
fontSize: 16,
|
|
175
|
+
lineHeight: 21,
|
|
176
|
+
color: t["muted-foreground"],
|
|
177
|
+
}),
|
|
178
|
+
inputBlock: { marginTop: 16 },
|
|
179
|
+
inputLabel: (t) => ({ marginBottom: 6, fontSize: 14, lineHeight: 18, fontWeight: "600", color: t.foreground }),
|
|
180
|
+
actionLayout: "capsule",
|
|
181
|
+
// Unused in the capsule layout, but the contract requires concrete values.
|
|
182
|
+
actions: { flexDirection: "row" },
|
|
183
|
+
buttonSmall: true,
|
|
184
|
+
cancelButton: {},
|
|
185
|
+
// Two equal-width capsules side by side, separated by a gap (no divider).
|
|
186
|
+
capsuleRow: { flexDirection: "row", gap: 12, marginTop: 24 },
|
|
187
|
+
capsuleCell: {
|
|
188
|
+
flex: 1,
|
|
189
|
+
alignItems: "center",
|
|
190
|
+
justifyContent: "center",
|
|
191
|
+
paddingVertical: 14,
|
|
192
|
+
paddingHorizontal: 16,
|
|
193
|
+
minHeight: 50,
|
|
194
|
+
borderRadius: IOS_CAPSULE_RADIUS,
|
|
195
|
+
},
|
|
196
|
+
cancelFill: (t) => ({ backgroundColor: t.secondary }),
|
|
197
|
+
confirmFill: (t, destructive) => ({ backgroundColor: destructive ? t.destructive : t.primary }),
|
|
198
|
+
cancelLabelStyle: (t) => ({ fontSize: 17, lineHeight: 22, fontWeight: "600", color: t["secondary-foreground"] }),
|
|
199
|
+
confirmLabelStyle: (t, destructive) => ({
|
|
200
|
+
fontSize: 17,
|
|
201
|
+
lineHeight: 22,
|
|
202
|
+
fontWeight: "700",
|
|
203
|
+
color: destructive ? t["destructive-foreground"] : t["primary-foreground"],
|
|
204
|
+
}),
|
|
205
|
+
pressedOpacity: 0.85,
|
|
206
|
+
ripple: null,
|
|
207
|
+
};
|
|
208
|
+
|
|
209
|
+
// ---------- Android (Material 3 AlertDialog): elevated surface, text buttons ----------
|
|
210
|
+
// M3 basic dialog: an elevated surface (28dp radius) over `popover` with a soft
|
|
211
|
+
// shadow; a LEFT-aligned 24sp/headline-small title and a left-aligned 14sp body in
|
|
212
|
+
// `muted-foreground`. Two M3 TEXT buttons sit bottom-right (Cancel then Confirm/
|
|
213
|
+
// Delete) — the confirm carries the brand `primary`, a destructive confirm the
|
|
214
|
+
// `destructive` red. The shell renders these with its ghost (text) Buttons; press =
|
|
215
|
+
// android_ripple, supplied here as the brand state layer.
|
|
216
|
+
export const androidSkin: AlertDialogSkin = {
|
|
217
|
+
backdrop: {
|
|
218
|
+
alignItems: "center",
|
|
219
|
+
justifyContent: "center",
|
|
220
|
+
borderRadius: 8,
|
|
221
|
+
backgroundColor: alpha("#000000", 0.32),
|
|
222
|
+
padding: 32,
|
|
223
|
+
},
|
|
224
|
+
card: (t) => ({
|
|
225
|
+
borderRadius: 28,
|
|
226
|
+
backgroundColor: t.popover,
|
|
227
|
+
padding: 24,
|
|
228
|
+
...shadow("md"),
|
|
229
|
+
}),
|
|
230
|
+
title: (t) => ({ fontSize: 24, lineHeight: 32, fontWeight: "400", color: t["popover-foreground"] }),
|
|
231
|
+
description: (t) => ({ marginTop: 16, fontSize: 14, lineHeight: 20, color: t["muted-foreground"] }),
|
|
232
|
+
inputBlock: { marginTop: 16 },
|
|
233
|
+
inputLabel: (t) => ({ marginBottom: 6, fontSize: 14, lineHeight: 20, fontWeight: "500", color: t.foreground }),
|
|
234
|
+
actionLayout: "buttons",
|
|
235
|
+
// M3 places the action buttons bottom-right with 8dp gap and 24dp above.
|
|
236
|
+
actions: { flexDirection: "row", justifyContent: "flex-end", gap: 8, marginTop: 24 },
|
|
237
|
+
buttonSmall: true,
|
|
238
|
+
// M3 dialog actions are text (ghost) buttons, not outlined.
|
|
239
|
+
cancelButton: { ghost: true },
|
|
240
|
+
capsuleRow: null,
|
|
241
|
+
capsuleCell: null,
|
|
242
|
+
cancelFill: null,
|
|
243
|
+
confirmFill: null,
|
|
244
|
+
cancelLabelStyle: null,
|
|
245
|
+
confirmLabelStyle: null,
|
|
246
|
+
pressedOpacity: null,
|
|
247
|
+
ripple: (t) => ({ color: alpha(t.primary, 0.12), borderless: false }),
|
|
248
|
+
};
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { createAlertDialog } from "./alert-dialog.shared.js";
|
|
2
|
+
import { webSkin } from "./alert-dialog.styles.js";
|
|
3
|
+
|
|
4
|
+
// Web AlertDialog (the base; Metro falls back to it on native, web bundlers resolve it).
|
|
5
|
+
export const AlertDialog = createAlertDialog(webSkin);
|
|
6
|
+
export type { AlertDialogProps } from "./alert-dialog.shared.js";
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
# Cards
|
|
2
|
+
|
|
3
|
+
Three families. `StatCard` = a single metric, big number + delta. `SectionCard` = a labeled content surface with optional header and divider. Generic `card` = bring your own structure. Density: pass `compact` or `comfortable` to tighten or relax the card's own padding and the gap between flat children (`compact` takes precedence, and a density prop pads the surface on its own).
|
|
4
|
+
|
|
5
|
+
## Usage
|
|
6
|
+
|
|
7
|
+
```tsx
|
|
8
|
+
<Card style={{ width: 280, padding: 20 }}>
|
|
9
|
+
<View style={{ flexDirection: "row", alignItems: "flex-start", justifyContent: "space-between" }}>
|
|
10
|
+
<View>
|
|
11
|
+
<Text style={{ fontSize: 12, lineHeight: 16, fontWeight: "500", textTransform: "uppercase", letterSpacing: 0.4, color: tokens["muted-foreground"] }}>Active identities</Text>
|
|
12
|
+
<Text style={{ marginTop: 4, fontSize: 24, lineHeight: 32, fontWeight: "700", color: tokens["card-foreground"] }}>12,348</Text>
|
|
13
|
+
<Text style={{ marginTop: 2, fontSize: 11, color: tokens["muted-foreground"] }}>+142 today</Text>
|
|
14
|
+
</View>
|
|
15
|
+
<View style={{ height: 40, width: 40, alignItems: "center", justifyContent: "center", borderRadius: 8, backgroundColor: alpha(palette["blue-500"], 0.1), color: palette["blue-600"] }}>
|
|
16
|
+
<Text style={{ fontSize: 14, lineHeight: 20, fontWeight: "600", color: palette["blue-600"] }}>U</Text>
|
|
17
|
+
</View>
|
|
18
|
+
</View>
|
|
19
|
+
</Card>
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
## Variants
|
|
23
|
+
|
|
24
|
+
### Type - section
|
|
25
|
+
|
|
26
|
+
```tsx
|
|
27
|
+
<Card
|
|
28
|
+
title="Recent activity"
|
|
29
|
+
body="A labeled content surface. Drop fields, a list, or any module of content here."
|
|
30
|
+
/>
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
### Type - generic
|
|
34
|
+
|
|
35
|
+
```tsx
|
|
36
|
+
<Card
|
|
37
|
+
padded
|
|
38
|
+
title="Anything goes here"
|
|
39
|
+
body="The card surface gives you the border, radius, and shadow. You bring the content."
|
|
40
|
+
/>
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
### Icon tone - success
|
|
44
|
+
|
|
45
|
+
```tsx
|
|
46
|
+
<Card style={{ width: 280, padding: 20 }}>
|
|
47
|
+
<View style={{ flexDirection: "row", alignItems: "flex-start", justifyContent: "space-between" }}>
|
|
48
|
+
<View>
|
|
49
|
+
<Text style={{ fontSize: 12, lineHeight: 16, fontWeight: "500", textTransform: "uppercase", letterSpacing: 0.4, color: tokens["muted-foreground"] }}>Active identities</Text>
|
|
50
|
+
<Text style={{ marginTop: 4, fontSize: 24, lineHeight: 32, fontWeight: "700", color: tokens["card-foreground"] }}>12,348</Text>
|
|
51
|
+
<Text style={{ marginTop: 2, fontSize: 11, color: tokens["muted-foreground"] }}>+142 today</Text>
|
|
52
|
+
</View>
|
|
53
|
+
<View style={{ height: 40, width: 40, alignItems: "center", justifyContent: "center", borderRadius: 8, backgroundColor: alpha(palette["green-500"], 0.1), color: palette["green-600"] }}>
|
|
54
|
+
<Text style={{ fontSize: 14, lineHeight: 20, fontWeight: "600", color: palette["green-600"] }}>S</Text>
|
|
55
|
+
</View>
|
|
56
|
+
</View>
|
|
57
|
+
</Card>
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
### Icon tone - purple
|
|
61
|
+
|
|
62
|
+
```tsx
|
|
63
|
+
<Card style={{ width: 280, padding: 20 }}>
|
|
64
|
+
<View style={{ flexDirection: "row", alignItems: "flex-start", justifyContent: "space-between" }}>
|
|
65
|
+
<View>
|
|
66
|
+
<Text style={{ fontSize: 12, lineHeight: 16, fontWeight: "500", textTransform: "uppercase", letterSpacing: 0.4, color: tokens["muted-foreground"] }}>Active identities</Text>
|
|
67
|
+
<Text style={{ marginTop: 4, fontSize: 24, lineHeight: 32, fontWeight: "700", color: tokens["card-foreground"] }}>12,348</Text>
|
|
68
|
+
<Text style={{ marginTop: 2, fontSize: 11, color: tokens["muted-foreground"] }}>+142 today</Text>
|
|
69
|
+
</View>
|
|
70
|
+
<View style={{ height: 40, width: 40, alignItems: "center", justifyContent: "center", borderRadius: 8, backgroundColor: alpha(palette["purple-500"], 0.1), color: palette["purple-600"] }}>
|
|
71
|
+
<Text style={{ fontSize: 14, lineHeight: 20, fontWeight: "600", color: palette["purple-600"] }}>O</Text>
|
|
72
|
+
</View>
|
|
73
|
+
</View>
|
|
74
|
+
</Card>
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
### Icon tone - destructive
|
|
78
|
+
|
|
79
|
+
```tsx
|
|
80
|
+
<Card style={{ width: 280, padding: 20 }}>
|
|
81
|
+
<View style={{ flexDirection: "row", alignItems: "flex-start", justifyContent: "space-between" }}>
|
|
82
|
+
<View>
|
|
83
|
+
<Text style={{ fontSize: 12, lineHeight: 16, fontWeight: "500", textTransform: "uppercase", letterSpacing: 0.4, color: tokens["muted-foreground"] }}>Active identities</Text>
|
|
84
|
+
<Text style={{ marginTop: 4, fontSize: 24, lineHeight: 32, fontWeight: "700", color: tokens["card-foreground"] }}>12,348</Text>
|
|
85
|
+
<Text style={{ marginTop: 2, fontSize: 11, color: tokens["muted-foreground"] }}>+142 today</Text>
|
|
86
|
+
</View>
|
|
87
|
+
<View style={{ height: 40, width: 40, alignItems: "center", justifyContent: "center", borderRadius: 8, backgroundColor: alpha(tokens.destructive, 0.1), color: tokens.destructive }}>
|
|
88
|
+
<Text style={{ fontSize: 14, lineHeight: 20, fontWeight: "600", color: tokens.destructive }}>!</Text>
|
|
89
|
+
</View>
|
|
90
|
+
</View>
|
|
91
|
+
</Card>
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
### Icon tone - amber
|
|
95
|
+
|
|
96
|
+
```tsx
|
|
97
|
+
<Card style={{ width: 280, padding: 20 }}>
|
|
98
|
+
<View style={{ flexDirection: "row", alignItems: "flex-start", justifyContent: "space-between" }}>
|
|
99
|
+
<View>
|
|
100
|
+
<Text style={{ fontSize: 12, lineHeight: 16, fontWeight: "500", textTransform: "uppercase", letterSpacing: 0.4, color: tokens["muted-foreground"] }}>Active identities</Text>
|
|
101
|
+
<Text style={{ marginTop: 4, fontSize: 24, lineHeight: 32, fontWeight: "700", color: tokens["card-foreground"] }}>12,348</Text>
|
|
102
|
+
<Text style={{ marginTop: 2, fontSize: 11, color: tokens["muted-foreground"] }}>+142 today</Text>
|
|
103
|
+
</View>
|
|
104
|
+
<View style={{ height: 40, width: 40, alignItems: "center", justifyContent: "center", borderRadius: 8, backgroundColor: alpha(palette["amber-500"], 0.1), color: palette["amber-600"] }}>
|
|
105
|
+
<Text style={{ fontSize: 14, lineHeight: 20, fontWeight: "600", color: palette["amber-600"] }}>T</Text>
|
|
106
|
+
</View>
|
|
107
|
+
</View>
|
|
108
|
+
</Card>
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
## Do & Don't
|
|
112
|
+
|
|
113
|
+
### stat
|
|
114
|
+
|
|
115
|
+
**Do** — One big number, a short label, a small delta. The metric is scannable in a glance.
|
|
116
|
+
|
|
117
|
+
```tsx
|
|
118
|
+
<Card padded style={{ maxWidth: 280 }}>
|
|
119
|
+
<View style={{ flexDirection: "row", alignItems: "flex-start", justifyContent: "space-between" }}>
|
|
120
|
+
<View>
|
|
121
|
+
<Text style={{ fontSize: 12, lineHeight: 16, fontWeight: "500", textTransform: "uppercase", letterSpacing: 0.4, color: tokens["muted-foreground"] }}>Active identities</Text>
|
|
122
|
+
<Text style={{ marginTop: 4, fontSize: 24, lineHeight: 32, fontWeight: "700", color: tokens["card-foreground"] }}>12,348</Text>
|
|
123
|
+
<Text style={{ marginTop: 2, fontSize: 11, color: tokens["muted-foreground"] }}>+142 today</Text>
|
|
124
|
+
</View>
|
|
125
|
+
<View style={{ height: 40, width: 40, alignItems: "center", justifyContent: "center", borderRadius: 8, backgroundColor: alpha(palette["blue-500"], 0.1) }}>
|
|
126
|
+
<Text style={{ fontSize: 14, lineHeight: 20, fontWeight: "600", color: palette["blue-600"] }}>U</Text>
|
|
127
|
+
</View>
|
|
128
|
+
</View>
|
|
129
|
+
</Card>
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
**Don't** — Prose where the number should be: the eye has nothing big to land on, so the card stops being a stat.
|
|
133
|
+
|
|
134
|
+
```tsx
|
|
135
|
+
<Card padded style={{ maxWidth: 280 }}>
|
|
136
|
+
<Text style={{ fontSize: 12, lineHeight: 16, fontWeight: "500", textTransform: "uppercase", letterSpacing: 0.4, color: tokens["muted-foreground"] }}>This month</Text>
|
|
137
|
+
<Text style={{ marginTop: 4, fontSize: 14, lineHeight: 20, fontWeight: "500", color: tokens["card-foreground"] }}>We onboarded 12,348 active identities, up 142 today, with churn holding steady.</Text>
|
|
138
|
+
</Card>
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
### section
|
|
142
|
+
|
|
143
|
+
**Do** — Keep the divider between header and body; it anchors the title.
|
|
144
|
+
|
|
145
|
+
```tsx
|
|
146
|
+
<Card style={{ maxWidth: 360 }}>
|
|
147
|
+
<CardHeader>
|
|
148
|
+
<CardTitle>Recent activity</CardTitle>
|
|
149
|
+
</CardHeader>
|
|
150
|
+
<CardSeparator />
|
|
151
|
+
<CardContent>
|
|
152
|
+
<Text style={{ fontSize: 14, lineHeight: 20, color: tokens["card-foreground"] }}>Two events today.</Text>
|
|
153
|
+
</CardContent>
|
|
154
|
+
</Card>
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
**Don't** — Without the divider the header floats and stops reading as a header.
|
|
158
|
+
|
|
159
|
+
```tsx
|
|
160
|
+
<Card style={{ maxWidth: 360 }}>
|
|
161
|
+
<CardHeader>
|
|
162
|
+
<CardTitle>Recent activity</CardTitle>
|
|
163
|
+
</CardHeader>
|
|
164
|
+
<CardContent>
|
|
165
|
+
<Text style={{ fontSize: 14, lineHeight: 20, color: tokens["card-foreground"] }}>Two events today.</Text>
|
|
166
|
+
</CardContent>
|
|
167
|
+
</Card>
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
### generic
|
|
171
|
+
|
|
172
|
+
**Do** — Use the surface once and layout the content with plain spacing inside it.
|
|
173
|
+
|
|
174
|
+
```tsx
|
|
175
|
+
<Card padded style={{ maxWidth: 360 }}>
|
|
176
|
+
<Text style={{ marginBottom: 4, fontSize: 15, fontWeight: "600", color: tokens["card-foreground"] }}>Anything goes here</Text>
|
|
177
|
+
<Text style={{ fontSize: 14, lineHeight: 20, color: tokens["muted-foreground"] }}>The card surface gives you the border, radius, and shadow. You bring the content.</Text>
|
|
178
|
+
</Card>
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
**Don't** — Nesting one card surface inside another stacks border on border and shadow on shadow; the inner block looks dropped in.
|
|
182
|
+
|
|
183
|
+
```tsx
|
|
184
|
+
<Card padded style={{ maxWidth: 360 }}>
|
|
185
|
+
<Card padded>
|
|
186
|
+
<Text style={{ marginBottom: 4, fontSize: 15, fontWeight: "600", color: tokens["card-foreground"] }}>Nested surface</Text>
|
|
187
|
+
<Text style={{ fontSize: 14, lineHeight: 20, color: tokens["muted-foreground"] }}>A card inside a card doubles the border and shadow.</Text>
|
|
188
|
+
</Card>
|
|
189
|
+
</Card>
|
|
190
|
+
```
|