@olympusoss/canvas 3.2.1 → 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 +75 -65
- package/package.json +11 -5
- 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/styles/canvas.css +127 -74
- package/tsconfig.json +4 -2
- package/src/cn.ts +0 -3
- package/styles/atoms/avatar.css +0 -22
- package/styles/atoms/badge.css +0 -83
- package/styles/atoms/breadcrumb.css +0 -35
- package/styles/atoms/button-group.css +0 -23
- package/styles/atoms/button.css +0 -107
- package/styles/atoms/checkbox.css +0 -55
- package/styles/atoms/combobox.css +0 -76
- package/styles/atoms/dropdown.css +0 -54
- package/styles/atoms/icon.css +0 -8
- package/styles/atoms/input-group.css +0 -45
- package/styles/atoms/input.css +0 -56
- package/styles/atoms/kbd.css +0 -15
- package/styles/atoms/pagination.css +0 -48
- package/styles/atoms/popover.css +0 -14
- package/styles/atoms/radio.css +0 -28
- package/styles/atoms/select.css +0 -57
- package/styles/atoms/separator.css +0 -32
- package/styles/atoms/skeleton.css +0 -32
- package/styles/atoms/spinner.css +0 -26
- package/styles/atoms/switch.css +0 -45
- package/styles/atoms/textarea.css +0 -31
- package/styles/atoms/tooltip.css +0 -53
- package/styles/atoms/typography.css +0 -105
- package/styles/base.css +0 -17
- package/styles/molecules/alert.css +0 -66
- package/styles/molecules/card.css +0 -58
- package/styles/molecules/code-block.css +0 -18
- package/styles/molecules/empty-state.css +0 -17
- package/styles/molecules/field.css +0 -27
- package/styles/molecules/form.css +0 -27
- package/styles/molecules/page-header.css +0 -52
- package/styles/molecules/section-card.css +0 -49
- package/styles/molecules/stat-card.css +0 -71
- package/styles/molecules/toast.css +0 -95
- package/styles/organisms/app-shell.css +0 -46
- package/styles/organisms/calendar.css +0 -73
- package/styles/organisms/command.css +0 -95
- package/styles/organisms/data-table.css +0 -142
- package/styles/organisms/dialog.css +0 -72
- package/styles/organisms/filter-panel.css +0 -58
- package/styles/organisms/row-menu.css +0 -69
- package/styles/organisms/sheet.css +0 -70
- package/styles/organisms/sidebar.css +0 -146
- package/styles/organisms/stepper.css +0 -63
- package/styles/organisms/tabs.css +0 -40
- package/styles/organisms/topbar.css +0 -24
- package/styles/patterns/backdrops.css +0 -35
- package/styles/patterns/density.css +0 -66
- package/styles/patterns/focus.css +0 -22
- 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 -108
- 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
- package/styles/utilities/display.css +0 -66
- package/styles/utilities/flexbox.css +0 -240
- package/styles/utilities/gap.css +0 -288
- package/styles/utilities/grid.css +0 -138
- package/styles/utilities/position.css +0 -78
- package/styles/utilities/sizing.css +0 -138
|
@@ -0,0 +1,483 @@
|
|
|
1
|
+
import { type ViewStyle, type TextStyle } from "react-native";
|
|
2
|
+
import { type ColorTokens, alpha, shadow } from "../../style/index.js";
|
|
3
|
+
import { type ButtonGroupSkin, type Size } from "./button-group.shared.js";
|
|
4
|
+
|
|
5
|
+
// Co-located ButtonGroup skins, one per platform. The group is laid out per
|
|
6
|
+
// segment in JS (there are no `first:`/`last:` style variants, so the joined-
|
|
7
|
+
// corner and shared-border math is computed here). The BRAND survives on every
|
|
8
|
+
// platform (the indigo `primary` token and the semantic tokens, never a platform
|
|
9
|
+
// default), and only the native SHAPE, sizing, structure, and press feedback
|
|
10
|
+
// change per OS:
|
|
11
|
+
// iOS (UISegmentedControl): a gray rounded CONTAINER (radius 8, muted fill,
|
|
12
|
+
// ~3px inset) holding segments; the SELECTED segment is a raised white pill
|
|
13
|
+
// (radius 6, small shadow) with NO visible dividers (iOS 13+ style); labels
|
|
14
|
+
// ~13pt. Press = opacity dim.
|
|
15
|
+
// Android (M3 SegmentedButton): the GROUP is a fully-rounded stadium (1dp
|
|
16
|
+
// `border` outline); segments share 1dp borders (no gap); the SELECTED
|
|
17
|
+
// segment is a tonal fill (alpha(primary, .12)) with a brand-indigo label and
|
|
18
|
+
// a leading check; press = android_ripple.
|
|
19
|
+
// Web: the established Canvas look (joined buttons; selected = solid primary
|
|
20
|
+
// fill; shared 1px borders overlapped by -1px), lifted verbatim.
|
|
21
|
+
|
|
22
|
+
// --- shared size scales (brand type/sizing, identical across platforms) ------
|
|
23
|
+
|
|
24
|
+
// Height + horizontal padding per size, mirroring the docs segSize scale
|
|
25
|
+
// (h-8 px-3 / h-9 px-4 / h-10 px-5).
|
|
26
|
+
export const sizeContainer: Record<Size, ViewStyle> = {
|
|
27
|
+
small: { height: 32, paddingHorizontal: 12 },
|
|
28
|
+
default: { height: 36, paddingHorizontal: 16 },
|
|
29
|
+
large: { height: 40, paddingHorizontal: 20 },
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
// Label type per size (text-xs for small, text-sm otherwise).
|
|
33
|
+
export const sizeLabel: Record<Size, TextStyle> = {
|
|
34
|
+
small: { fontSize: 12, lineHeight: 16 },
|
|
35
|
+
default: { fontSize: 14, lineHeight: 20 },
|
|
36
|
+
large: { fontSize: 14, lineHeight: 20 },
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
// Height-only per size, for cells whose padding differs from sizeContainer
|
|
40
|
+
// (the split chevron and the stepper's chevron cells: h-8 / h-9 / h-10).
|
|
41
|
+
export const sizeHeight: Record<Size, number> = {
|
|
42
|
+
small: 32,
|
|
43
|
+
default: 36,
|
|
44
|
+
large: 40,
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
// Chevron glyph px per size (small gets the tighter 14px arrow).
|
|
48
|
+
export const chevronSize: Record<Size, number> = {
|
|
49
|
+
small: 14,
|
|
50
|
+
default: 16,
|
|
51
|
+
large: 16,
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
// --- shared layout fragments (color-free; identical across platforms) --------
|
|
55
|
+
|
|
56
|
+
// Row of a centered label inside a cell. Border width is supplied by the skin
|
|
57
|
+
// (iOS segments are borderless; Android/web are 1px). Press feedback is applied
|
|
58
|
+
// by the component's Pressable.
|
|
59
|
+
export const segmentBase: ViewStyle = {
|
|
60
|
+
flexDirection: "row",
|
|
61
|
+
alignItems: "center",
|
|
62
|
+
justifyContent: "center",
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
// The dimmed look applied to a disabled group/segment (opacity-50).
|
|
66
|
+
export const dim: ViewStyle = { opacity: 0.5 };
|
|
67
|
+
|
|
68
|
+
// The split control's outer row, anchoring the (absolute) dropdown.
|
|
69
|
+
export const splitContainer: ViewStyle = {
|
|
70
|
+
position: "relative",
|
|
71
|
+
flexDirection: "row",
|
|
72
|
+
alignItems: "center",
|
|
73
|
+
alignSelf: "flex-start",
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
// When the split dropdown is open, the container is lifted into its own stacking
|
|
77
|
+
// context above sibling content. react-native-web gives every positioned View an
|
|
78
|
+
// implicit stacking context, so the menu's own `zIndex` is scoped INSIDE the
|
|
79
|
+
// `relative` container and cannot rise above a later sibling. Raising the
|
|
80
|
+
// container's zIndex while open lifts the whole control — buttons and menu
|
|
81
|
+
// together — above everything painted after it.
|
|
82
|
+
export const splitContainerLifted: ViewStyle = { zIndex: 50 };
|
|
83
|
+
|
|
84
|
+
// A split-menu dropdown row: padded, rounded; the pressed branch tints it.
|
|
85
|
+
export const splitMenuItem: ViewStyle = {
|
|
86
|
+
flexDirection: "row",
|
|
87
|
+
alignItems: "center",
|
|
88
|
+
borderRadius: 2,
|
|
89
|
+
paddingHorizontal: 8,
|
|
90
|
+
paddingVertical: 6,
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
// The stepper's outer row.
|
|
94
|
+
export const stepperContainer: ViewStyle = {
|
|
95
|
+
flexDirection: "row",
|
|
96
|
+
alignItems: "center",
|
|
97
|
+
alignSelf: "flex-start",
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
// A plain row of detached peers separated by a gap (gap-2).
|
|
101
|
+
export const spacedContainer: ViewStyle = {
|
|
102
|
+
flexDirection: "row",
|
|
103
|
+
alignItems: "center",
|
|
104
|
+
gap: 8,
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
// The attached-segment row (no gap; segments share borders).
|
|
108
|
+
export const segmentedContainer: ViewStyle = {
|
|
109
|
+
flexDirection: "row",
|
|
110
|
+
alignItems: "center",
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
// =============================================================================
|
|
114
|
+
// Web: the established Canvas look (lifted verbatim from the original file).
|
|
115
|
+
// =============================================================================
|
|
116
|
+
|
|
117
|
+
export const webSkin: ButtonGroupSkin = {
|
|
118
|
+
// No wrapper around segmented (the row is bare); selected lifts above its
|
|
119
|
+
// neighbors so its primary border wins on the shared edges.
|
|
120
|
+
segmentedWrap: () => null,
|
|
121
|
+
segmentBorderWidth: 1,
|
|
122
|
+
joinCorners(index, count) {
|
|
123
|
+
if (count === 1) return { borderRadius: 6 };
|
|
124
|
+
if (index === 0) return { borderTopLeftRadius: 6, borderBottomLeftRadius: 6 };
|
|
125
|
+
if (index === count - 1) return { borderTopRightRadius: 6, borderBottomRightRadius: 6 };
|
|
126
|
+
return {};
|
|
127
|
+
},
|
|
128
|
+
spacedCorners: { borderRadius: 6 },
|
|
129
|
+
// All but the leading segment overlap the previous border by 1px (-ml-px).
|
|
130
|
+
overlap: { marginLeft: -1 },
|
|
131
|
+
segmentSurface(t, selected) {
|
|
132
|
+
return selected
|
|
133
|
+
? { zIndex: 10, borderColor: t.primary, backgroundColor: t.primary }
|
|
134
|
+
: { borderColor: t.input, backgroundColor: t.background };
|
|
135
|
+
},
|
|
136
|
+
segmentLabel(t, selected) {
|
|
137
|
+
return { fontWeight: "500", color: selected ? t["primary-foreground"] : t.foreground };
|
|
138
|
+
},
|
|
139
|
+
showSelectedCheck: false,
|
|
140
|
+
|
|
141
|
+
// --- split ---
|
|
142
|
+
splitPrimary(t) {
|
|
143
|
+
return {
|
|
144
|
+
flexDirection: "row",
|
|
145
|
+
alignItems: "center",
|
|
146
|
+
justifyContent: "center",
|
|
147
|
+
borderTopLeftRadius: 6,
|
|
148
|
+
borderBottomLeftRadius: 6,
|
|
149
|
+
backgroundColor: t.primary,
|
|
150
|
+
};
|
|
151
|
+
},
|
|
152
|
+
splitPrimaryLabel(t) {
|
|
153
|
+
return { fontWeight: "500", color: t["primary-foreground"] };
|
|
154
|
+
},
|
|
155
|
+
splitDivider(t, height) {
|
|
156
|
+
return { width: 1, height, backgroundColor: alpha(t["primary-foreground"], 0.2) };
|
|
157
|
+
},
|
|
158
|
+
splitTrigger(t, height) {
|
|
159
|
+
return {
|
|
160
|
+
flexDirection: "row",
|
|
161
|
+
alignItems: "center",
|
|
162
|
+
justifyContent: "center",
|
|
163
|
+
borderTopRightRadius: 6,
|
|
164
|
+
borderBottomRightRadius: 6,
|
|
165
|
+
backgroundColor: t.primary,
|
|
166
|
+
paddingHorizontal: 8,
|
|
167
|
+
height,
|
|
168
|
+
};
|
|
169
|
+
},
|
|
170
|
+
splitChevronColor: "primaryForeground",
|
|
171
|
+
splitMenu(t) {
|
|
172
|
+
return {
|
|
173
|
+
position: "absolute",
|
|
174
|
+
top: "100%",
|
|
175
|
+
right: 0,
|
|
176
|
+
zIndex: 50,
|
|
177
|
+
marginTop: 4,
|
|
178
|
+
minWidth: 180,
|
|
179
|
+
borderRadius: 6,
|
|
180
|
+
borderWidth: 1,
|
|
181
|
+
borderColor: t.border,
|
|
182
|
+
backgroundColor: t.popover,
|
|
183
|
+
padding: 4,
|
|
184
|
+
...shadow("lg"),
|
|
185
|
+
};
|
|
186
|
+
},
|
|
187
|
+
splitMenuItemPressed(t) {
|
|
188
|
+
return { backgroundColor: t.accent };
|
|
189
|
+
},
|
|
190
|
+
splitMenuText(t) {
|
|
191
|
+
return { fontSize: 14, lineHeight: 20, color: t["popover-foreground"] };
|
|
192
|
+
},
|
|
193
|
+
|
|
194
|
+
// --- stepper ---
|
|
195
|
+
stepperArrow(t, height) {
|
|
196
|
+
return {
|
|
197
|
+
flexDirection: "row",
|
|
198
|
+
alignItems: "center",
|
|
199
|
+
justifyContent: "center",
|
|
200
|
+
borderWidth: 1,
|
|
201
|
+
borderColor: t.input,
|
|
202
|
+
backgroundColor: t.background,
|
|
203
|
+
paddingHorizontal: 8,
|
|
204
|
+
height,
|
|
205
|
+
};
|
|
206
|
+
},
|
|
207
|
+
stepperArrowLeft: { borderTopLeftRadius: 6, borderBottomLeftRadius: 6 },
|
|
208
|
+
stepperArrowRight: { marginLeft: -1, borderTopRightRadius: 6, borderBottomRightRadius: 6 },
|
|
209
|
+
stepperMiddle(t) {
|
|
210
|
+
return {
|
|
211
|
+
flexDirection: "row",
|
|
212
|
+
alignItems: "center",
|
|
213
|
+
justifyContent: "center",
|
|
214
|
+
borderWidth: 1,
|
|
215
|
+
marginLeft: -1,
|
|
216
|
+
borderColor: t.input,
|
|
217
|
+
backgroundColor: t.background,
|
|
218
|
+
};
|
|
219
|
+
},
|
|
220
|
+
stepperLabel(t) {
|
|
221
|
+
return { fontWeight: "500", color: t.foreground };
|
|
222
|
+
},
|
|
223
|
+
stepperChevronColor: "muted",
|
|
224
|
+
|
|
225
|
+
pressedOpacity: 0.9,
|
|
226
|
+
};
|
|
227
|
+
|
|
228
|
+
// =============================================================================
|
|
229
|
+
// iOS (UISegmentedControl): gray rounded container, raised white selected pill.
|
|
230
|
+
// =============================================================================
|
|
231
|
+
|
|
232
|
+
const IOS_PILL_SHADOW: ViewStyle = {
|
|
233
|
+
shadowColor: "#000000",
|
|
234
|
+
shadowOffset: { width: 0, height: 1 },
|
|
235
|
+
shadowOpacity: 0.18,
|
|
236
|
+
shadowRadius: 2,
|
|
237
|
+
elevation: 2,
|
|
238
|
+
};
|
|
239
|
+
|
|
240
|
+
export const iosSkin: ButtonGroupSkin = {
|
|
241
|
+
// The gray CAPSULE track that holds the segments (radius 9999, muted fill, a
|
|
242
|
+
// 3px inset so the selected pill floats inside). iOS 26+ (Liquid Glass) draws
|
|
243
|
+
// the segmented control as a capsule, not a rounded rectangle.
|
|
244
|
+
segmentedWrap(t) {
|
|
245
|
+
return {
|
|
246
|
+
flexDirection: "row",
|
|
247
|
+
alignItems: "center",
|
|
248
|
+
alignSelf: "flex-start",
|
|
249
|
+
padding: 3,
|
|
250
|
+
borderRadius: 9999,
|
|
251
|
+
backgroundColor: t.muted,
|
|
252
|
+
};
|
|
253
|
+
},
|
|
254
|
+
segmentBorderWidth: 0, // iOS 13+ has no visible dividers/borders
|
|
255
|
+
joinCorners() {
|
|
256
|
+
// Every segment is an independent CAPSULE pill (radius 9999) inside the
|
|
257
|
+
// track; the selected one floats as a raised white capsule.
|
|
258
|
+
return { borderRadius: 9999 };
|
|
259
|
+
},
|
|
260
|
+
spacedCorners: { borderRadius: 8 },
|
|
261
|
+
overlap: null, // no shared borders; the track's padding spaces them
|
|
262
|
+
segmentSurface(t, selected) {
|
|
263
|
+
return selected
|
|
264
|
+
? { ...IOS_PILL_SHADOW, backgroundColor: t.background }
|
|
265
|
+
: { backgroundColor: "transparent" };
|
|
266
|
+
},
|
|
267
|
+
segmentLabel(t, selected) {
|
|
268
|
+
// ~13pt SF label; selected reads slightly heavier. Both stay on-foreground
|
|
269
|
+
// (the selected pill is white/elevated, not a brand fill).
|
|
270
|
+
return { fontWeight: selected ? "600" : "500", color: t.foreground };
|
|
271
|
+
},
|
|
272
|
+
showSelectedCheck: false,
|
|
273
|
+
|
|
274
|
+
// --- split (HIG: primary action + chevron, brand fill, rounded ~8) ---
|
|
275
|
+
splitPrimary(t) {
|
|
276
|
+
return {
|
|
277
|
+
flexDirection: "row",
|
|
278
|
+
alignItems: "center",
|
|
279
|
+
justifyContent: "center",
|
|
280
|
+
borderTopLeftRadius: 8,
|
|
281
|
+
borderBottomLeftRadius: 8,
|
|
282
|
+
backgroundColor: t.primary,
|
|
283
|
+
};
|
|
284
|
+
},
|
|
285
|
+
splitPrimaryLabel(t) {
|
|
286
|
+
return { fontWeight: "600", color: t["primary-foreground"] };
|
|
287
|
+
},
|
|
288
|
+
splitDivider(t, height) {
|
|
289
|
+
return { width: 1, height, backgroundColor: alpha(t["primary-foreground"], 0.2) };
|
|
290
|
+
},
|
|
291
|
+
splitTrigger(t, height) {
|
|
292
|
+
return {
|
|
293
|
+
flexDirection: "row",
|
|
294
|
+
alignItems: "center",
|
|
295
|
+
justifyContent: "center",
|
|
296
|
+
borderTopRightRadius: 8,
|
|
297
|
+
borderBottomRightRadius: 8,
|
|
298
|
+
backgroundColor: t.primary,
|
|
299
|
+
paddingHorizontal: 10,
|
|
300
|
+
height,
|
|
301
|
+
};
|
|
302
|
+
},
|
|
303
|
+
splitChevronColor: "primaryForeground",
|
|
304
|
+
splitMenu(t) {
|
|
305
|
+
return {
|
|
306
|
+
position: "absolute",
|
|
307
|
+
top: "100%",
|
|
308
|
+
right: 0,
|
|
309
|
+
zIndex: 50,
|
|
310
|
+
marginTop: 6,
|
|
311
|
+
minWidth: 200,
|
|
312
|
+
borderRadius: 12, // HIG menu sheet
|
|
313
|
+
borderWidth: 0,
|
|
314
|
+
borderColor: t.border,
|
|
315
|
+
backgroundColor: t.popover,
|
|
316
|
+
padding: 6,
|
|
317
|
+
...shadow("lg"),
|
|
318
|
+
};
|
|
319
|
+
},
|
|
320
|
+
splitMenuItemPressed(t) {
|
|
321
|
+
return { backgroundColor: t.accent };
|
|
322
|
+
},
|
|
323
|
+
splitMenuText(t) {
|
|
324
|
+
return { fontSize: 15, lineHeight: 20, color: t["popover-foreground"] };
|
|
325
|
+
},
|
|
326
|
+
|
|
327
|
+
// --- stepper (HIG: gray-tracked prev/current/next, rounded 8) ---
|
|
328
|
+
stepperArrow(t, height) {
|
|
329
|
+
return {
|
|
330
|
+
flexDirection: "row",
|
|
331
|
+
alignItems: "center",
|
|
332
|
+
justifyContent: "center",
|
|
333
|
+
borderWidth: 0,
|
|
334
|
+
backgroundColor: t.muted,
|
|
335
|
+
paddingHorizontal: 10,
|
|
336
|
+
height,
|
|
337
|
+
};
|
|
338
|
+
},
|
|
339
|
+
stepperArrowLeft: { borderTopLeftRadius: 8, borderBottomLeftRadius: 8 },
|
|
340
|
+
stepperArrowRight: { marginLeft: 1, borderTopRightRadius: 8, borderBottomRightRadius: 8 },
|
|
341
|
+
stepperMiddle(t) {
|
|
342
|
+
return {
|
|
343
|
+
flexDirection: "row",
|
|
344
|
+
alignItems: "center",
|
|
345
|
+
justifyContent: "center",
|
|
346
|
+
borderWidth: 0,
|
|
347
|
+
marginLeft: 1,
|
|
348
|
+
backgroundColor: t.muted,
|
|
349
|
+
};
|
|
350
|
+
},
|
|
351
|
+
stepperLabel(t) {
|
|
352
|
+
return { fontWeight: "600", color: t.foreground };
|
|
353
|
+
},
|
|
354
|
+
stepperChevronColor: "foreground",
|
|
355
|
+
|
|
356
|
+
pressedOpacity: 0.8,
|
|
357
|
+
};
|
|
358
|
+
|
|
359
|
+
// =============================================================================
|
|
360
|
+
// Android (Material 3 SegmentedButton): stadium group, tonal selected fill.
|
|
361
|
+
// =============================================================================
|
|
362
|
+
|
|
363
|
+
export const androidSkin: ButtonGroupSkin = {
|
|
364
|
+
// The group is a fully-rounded stadium outlined in 1dp `border`; the segments
|
|
365
|
+
// sit inside and supply their own shared 1dp dividers.
|
|
366
|
+
segmentedWrap(t) {
|
|
367
|
+
return {
|
|
368
|
+
flexDirection: "row",
|
|
369
|
+
alignItems: "center",
|
|
370
|
+
alignSelf: "flex-start",
|
|
371
|
+
borderRadius: 9999,
|
|
372
|
+
borderWidth: 1,
|
|
373
|
+
borderColor: t.border,
|
|
374
|
+
overflow: "hidden",
|
|
375
|
+
};
|
|
376
|
+
},
|
|
377
|
+
segmentBorderWidth: 0, // the wrap draws the outer outline; dividers are drawn below
|
|
378
|
+
joinCorners() {
|
|
379
|
+
// The stadium wrap clips the corners; segments themselves are square.
|
|
380
|
+
return {};
|
|
381
|
+
},
|
|
382
|
+
spacedCorners: { borderRadius: 9999 },
|
|
383
|
+
// Each segment after the first draws a 1dp leading divider in the outline color.
|
|
384
|
+
overlap: null,
|
|
385
|
+
segmentDivider: (t) => ({ borderLeftWidth: 1, borderLeftColor: t.border }),
|
|
386
|
+
segmentSurface(t, selected) {
|
|
387
|
+
// Selected = tonal fill (secondaryContainer ≈ alpha(primary, .12)).
|
|
388
|
+
return selected ? { backgroundColor: alpha(t.primary, 0.12) } : { backgroundColor: "transparent" };
|
|
389
|
+
},
|
|
390
|
+
segmentLabel(t, selected) {
|
|
391
|
+
// labelMedium; selected reads in brand indigo (onSecondaryContainer ≈ primary).
|
|
392
|
+
return { fontWeight: "500", color: selected ? t.primary : t.foreground };
|
|
393
|
+
},
|
|
394
|
+
showSelectedCheck: true,
|
|
395
|
+
|
|
396
|
+
// --- split (M3: brand-filled primary + chevron, stadium) ---
|
|
397
|
+
splitPrimary(t) {
|
|
398
|
+
return {
|
|
399
|
+
flexDirection: "row",
|
|
400
|
+
alignItems: "center",
|
|
401
|
+
justifyContent: "center",
|
|
402
|
+
borderTopLeftRadius: 9999,
|
|
403
|
+
borderBottomLeftRadius: 9999,
|
|
404
|
+
backgroundColor: t.primary,
|
|
405
|
+
};
|
|
406
|
+
},
|
|
407
|
+
splitPrimaryLabel(t) {
|
|
408
|
+
return { fontWeight: "500", color: t["primary-foreground"] };
|
|
409
|
+
},
|
|
410
|
+
splitDivider(t, height) {
|
|
411
|
+
return { width: 1, height, backgroundColor: alpha(t["primary-foreground"], 0.24) };
|
|
412
|
+
},
|
|
413
|
+
splitTrigger(t, height) {
|
|
414
|
+
return {
|
|
415
|
+
flexDirection: "row",
|
|
416
|
+
alignItems: "center",
|
|
417
|
+
justifyContent: "center",
|
|
418
|
+
borderTopRightRadius: 9999,
|
|
419
|
+
borderBottomRightRadius: 9999,
|
|
420
|
+
backgroundColor: t.primary,
|
|
421
|
+
paddingHorizontal: 10,
|
|
422
|
+
height,
|
|
423
|
+
};
|
|
424
|
+
},
|
|
425
|
+
splitChevronColor: "primaryForeground",
|
|
426
|
+
splitMenu(t) {
|
|
427
|
+
return {
|
|
428
|
+
position: "absolute",
|
|
429
|
+
top: "100%",
|
|
430
|
+
right: 0,
|
|
431
|
+
zIndex: 50,
|
|
432
|
+
marginTop: 4,
|
|
433
|
+
minWidth: 200,
|
|
434
|
+
borderRadius: 4, // M3 menu container
|
|
435
|
+
borderWidth: 0,
|
|
436
|
+
borderColor: t.border,
|
|
437
|
+
backgroundColor: t.popover,
|
|
438
|
+
paddingVertical: 8,
|
|
439
|
+
...shadow("lg"),
|
|
440
|
+
};
|
|
441
|
+
},
|
|
442
|
+
splitMenuItemPressed(t) {
|
|
443
|
+
return { backgroundColor: alpha(t.primary, 0.12) };
|
|
444
|
+
},
|
|
445
|
+
splitMenuText(t) {
|
|
446
|
+
return { fontSize: 14, lineHeight: 20, color: t["popover-foreground"] };
|
|
447
|
+
},
|
|
448
|
+
|
|
449
|
+
// --- stepper (M3: outlined stadium prev/current/next) ---
|
|
450
|
+
stepperArrow(t, height) {
|
|
451
|
+
return {
|
|
452
|
+
flexDirection: "row",
|
|
453
|
+
alignItems: "center",
|
|
454
|
+
justifyContent: "center",
|
|
455
|
+
borderWidth: 1,
|
|
456
|
+
borderColor: t.border,
|
|
457
|
+
backgroundColor: "transparent",
|
|
458
|
+
paddingHorizontal: 10,
|
|
459
|
+
height,
|
|
460
|
+
};
|
|
461
|
+
},
|
|
462
|
+
stepperArrowLeft: { borderTopLeftRadius: 9999, borderBottomLeftRadius: 9999 },
|
|
463
|
+
stepperArrowRight: { marginLeft: -1, borderTopRightRadius: 9999, borderBottomRightRadius: 9999 },
|
|
464
|
+
stepperMiddle(t) {
|
|
465
|
+
return {
|
|
466
|
+
flexDirection: "row",
|
|
467
|
+
alignItems: "center",
|
|
468
|
+
justifyContent: "center",
|
|
469
|
+
borderTopWidth: 1,
|
|
470
|
+
borderBottomWidth: 1,
|
|
471
|
+
marginLeft: -1,
|
|
472
|
+
borderColor: t.border,
|
|
473
|
+
backgroundColor: "transparent",
|
|
474
|
+
};
|
|
475
|
+
},
|
|
476
|
+
stepperLabel(t) {
|
|
477
|
+
return { fontWeight: "500", color: t.foreground };
|
|
478
|
+
},
|
|
479
|
+
stepperChevronColor: "muted",
|
|
480
|
+
|
|
481
|
+
pressedOpacity: null, // Android uses a ripple instead
|
|
482
|
+
ripple: (t) => ({ color: alpha(t.primary, 0.12), borderless: false }),
|
|
483
|
+
};
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { createButtonGroup } from "./button-group.shared.js";
|
|
2
|
+
import { webSkin } from "./button-group.styles.js";
|
|
3
|
+
|
|
4
|
+
// Web ButtonGroup (the base; Metro falls back to it on native, web bundlers resolve it).
|
|
5
|
+
export const ButtonGroup = createButtonGroup(webSkin);
|
|
6
|
+
export type { ButtonGroupProps } from "./button-group.shared.js";
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { createCheckbox } from "./checkbox.shared.js";
|
|
2
|
+
import { androidSkin } from "./checkbox.styles.js";
|
|
3
|
+
|
|
4
|
+
// Material 3 Checkbox. Metro resolves this file on Android; the docs import it for preview.
|
|
5
|
+
export const Checkbox = createCheckbox(androidSkin);
|
|
6
|
+
export type { CheckboxProps } from "./checkbox.shared.js";
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { createCheckbox } from "./checkbox.shared.js";
|
|
2
|
+
import { iosSkin } from "./checkbox.styles.js";
|
|
3
|
+
|
|
4
|
+
// iOS (HIG) Checkbox. Metro resolves this file on iOS; the docs import it for preview.
|
|
5
|
+
export const Checkbox = createCheckbox(iosSkin);
|
|
6
|
+
export type { CheckboxProps } from "./checkbox.shared.js";
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
# Checkboxes
|
|
2
|
+
|
|
3
|
+
Multi-select option, single yes/no, grouped lists.
|
|
4
|
+
|
|
5
|
+
## Usage
|
|
6
|
+
|
|
7
|
+
```tsx
|
|
8
|
+
<View style={{ flexDirection: "row", alignItems: "flex-start", gap: 8 }}>
|
|
9
|
+
<Checkbox checked />
|
|
10
|
+
<View style={{ gap: 2 }}>
|
|
11
|
+
<Text style={{ fontSize: 14, lineHeight: 20, fontWeight: "500", color: tokens.foreground }}>Email notifications</Text>
|
|
12
|
+
<Text style={{ fontSize: 12, lineHeight: 16, color: tokens["muted-foreground"] }}>Get notified when activity happens on your account.</Text>
|
|
13
|
+
</View>
|
|
14
|
+
</View>
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
## Variants
|
|
18
|
+
|
|
19
|
+
### State - unchecked
|
|
20
|
+
|
|
21
|
+
```tsx
|
|
22
|
+
<View style={{ flexDirection: "row", alignItems: "flex-start", gap: 8 }}>
|
|
23
|
+
<Checkbox />
|
|
24
|
+
<View style={{ gap: 2 }}>
|
|
25
|
+
<Text style={{ fontSize: 14, lineHeight: 20, fontWeight: "500", color: tokens.foreground }}>Email notifications</Text>
|
|
26
|
+
<Text style={{ fontSize: 12, lineHeight: 16, color: tokens["muted-foreground"] }}>Get notified when activity happens on your account.</Text>
|
|
27
|
+
</View>
|
|
28
|
+
</View>
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
### State - disabled
|
|
32
|
+
|
|
33
|
+
```tsx
|
|
34
|
+
<View style={{ flexDirection: "row", alignItems: "flex-start", gap: 8 }}>
|
|
35
|
+
<Checkbox disabled />
|
|
36
|
+
<View style={{ gap: 2 }}>
|
|
37
|
+
<Text style={{ fontSize: 14, lineHeight: 20, fontWeight: "500", color: tokens.foreground }}>Email notifications</Text>
|
|
38
|
+
<Text style={{ fontSize: 12, lineHeight: 16, color: tokens["muted-foreground"] }}>Get notified when activity happens on your account.</Text>
|
|
39
|
+
</View>
|
|
40
|
+
</View>
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
## Do & Don't
|
|
44
|
+
|
|
45
|
+
### Unchecked
|
|
46
|
+
|
|
47
|
+
**Do** — Leave opt-in consent unchecked so agreeing is a deliberate act the user takes.
|
|
48
|
+
|
|
49
|
+
```tsx
|
|
50
|
+
<Checkbox>Email me product news, offers, and survey invitations.</Checkbox>
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
**Don't** — A consent box that starts checked opts users in by default; under GDPR pre-ticked consent is not consent.
|
|
54
|
+
|
|
55
|
+
```tsx
|
|
56
|
+
<Checkbox checked>Email me product news, offers, and survey invitations.</Checkbox>
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
### Checked
|
|
60
|
+
|
|
61
|
+
**Do** — Show the parent indeterminate (a dash, not a tick) when only some children are checked.
|
|
62
|
+
|
|
63
|
+
```tsx
|
|
64
|
+
<View style={{ gap: 8 }}>
|
|
65
|
+
<Checkbox indeterminate>Select all</Checkbox>
|
|
66
|
+
<View style={{ marginLeft: 24, gap: 8 }}>
|
|
67
|
+
<Checkbox checked>Read</Checkbox>
|
|
68
|
+
<Checkbox>Write</Checkbox>
|
|
69
|
+
<Checkbox>Delete</Checkbox>
|
|
70
|
+
</View>
|
|
71
|
+
</View>
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
**Don't** — A fully checked parent claims every child is selected when only one is, so the state reads as a lie.
|
|
75
|
+
|
|
76
|
+
```tsx
|
|
77
|
+
<View style={{ gap: 8 }}>
|
|
78
|
+
<Checkbox checked>Select all</Checkbox>
|
|
79
|
+
<View style={{ marginLeft: 24, gap: 8 }}>
|
|
80
|
+
<Checkbox checked>Read</Checkbox>
|
|
81
|
+
<Checkbox>Write</Checkbox>
|
|
82
|
+
<Checkbox>Delete</Checkbox>
|
|
83
|
+
</View>
|
|
84
|
+
</View>
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
### Disabled
|
|
88
|
+
|
|
89
|
+
**Do** — Say why it's unavailable, like a plan gate, or don't show it at all.
|
|
90
|
+
|
|
91
|
+
```tsx
|
|
92
|
+
<View style={{ flexDirection: "row", alignItems: "center", gap: 6 }}>
|
|
93
|
+
<Checkbox disabled>Export to CSV</Checkbox>
|
|
94
|
+
<Text style={{ fontSize: 12, lineHeight: 16, color: tokens["muted-foreground"] }}>(Pro plan)</Text>
|
|
95
|
+
</View>
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
**Don't** — A disabled option with no reason leaves users stuck and guessing.
|
|
99
|
+
|
|
100
|
+
```tsx
|
|
101
|
+
<Checkbox disabled>Export to CSV</Checkbox>
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
### Selection
|
|
105
|
+
|
|
106
|
+
**Do** — Radios for one-of-many; reserve checkboxes for independent multi-select.
|
|
107
|
+
|
|
108
|
+
```tsx
|
|
109
|
+
<View style={{ gap: 8 }}>
|
|
110
|
+
<Text style={{ marginBottom: 4, fontSize: 14, lineHeight: 20, fontWeight: "600", color: tokens.foreground }}>Plan</Text>
|
|
111
|
+
<Radio>Free</Radio>
|
|
112
|
+
<Radio checked>Pro</Radio>
|
|
113
|
+
<Radio>Enterprise</Radio>
|
|
114
|
+
</View>
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
**Don't** — Checkboxes allow multiple selections; for a one-of choice they let users pick contradictory options.
|
|
118
|
+
|
|
119
|
+
```tsx
|
|
120
|
+
<View style={{ gap: 8 }}>
|
|
121
|
+
<Text style={{ marginBottom: 4, fontSize: 14, lineHeight: 20, fontWeight: "600", color: tokens.foreground }}>Plan</Text>
|
|
122
|
+
<Checkbox>Free</Checkbox>
|
|
123
|
+
<Checkbox checked>Pro</Checkbox>
|
|
124
|
+
<Checkbox>Enterprise</Checkbox>
|
|
125
|
+
</View>
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
### With description
|
|
129
|
+
|
|
130
|
+
**Do** — Wrap the box, label, and description in a <label> so the whole row toggles.
|
|
131
|
+
|
|
132
|
+
```tsx
|
|
133
|
+
<Checkbox checked>
|
|
134
|
+
<Text style={{ fontSize: 14, lineHeight: 20, fontWeight: "500", color: tokens.foreground }}>Email notifications</Text>
|
|
135
|
+
<Text style={{ fontSize: 12, lineHeight: 16, fontWeight: "400", color: tokens["muted-foreground"] }}>
|
|
136
|
+
Get notified when activity happens on your account.</Text>
|
|
137
|
+
</Checkbox>
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
**Don't** — A bare div makes only the 16px box clickable; the label text does nothing.
|
|
141
|
+
|
|
142
|
+
```tsx
|
|
143
|
+
<View style={{ flexDirection: "row", alignItems: "flex-start", gap: 8 }}>
|
|
144
|
+
<Checkbox checked />
|
|
145
|
+
<View>
|
|
146
|
+
<Text style={{ fontSize: 14, lineHeight: 20, fontWeight: "500", color: tokens.foreground }}>Email notifications</Text>
|
|
147
|
+
<Text style={{ fontSize: 12, lineHeight: 16, color: tokens["muted-foreground"] }}>Get notified when activity happens on your account.</Text>
|
|
148
|
+
</View>
|
|
149
|
+
</View>
|
|
150
|
+
```
|