@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,270 @@
|
|
|
1
|
+
import { StyleSheet, type ViewStyle, type TextStyle } from "react-native";
|
|
2
|
+
import { type ColorTokens, shadow } from "../../style/index.js";
|
|
3
|
+
|
|
4
|
+
// Co-located Combobox skins, one per platform. A Combobox is a searchable
|
|
5
|
+
// single-select: an editable field that filters an open option list. The BRAND
|
|
6
|
+
// survives on every platform (the indigo `primary`/`accent` tokens stay; the
|
|
7
|
+
// focus accent is the `ring`, never a platform default) and only the native
|
|
8
|
+
// SHAPE, sizing, fill, border/underline treatment, popover elevation, and press
|
|
9
|
+
// feedback change per OS. The treatment mirrors Input/Select:
|
|
10
|
+
// iOS 27 (iOS 26+/Liquid Glass): a plain field, transparent, no capsule, just
|
|
11
|
+
// a bottom hairline (`border` at rest -> brand `primary` when open); the
|
|
12
|
+
// trailing chevron is `muted-foreground`; the open list is a large
|
|
13
|
+
// continuous-corner `popover` card (~27 radius) with a soft shadow and roomy
|
|
14
|
+
// rows. Press = opacity dim (~0.8).
|
|
15
|
+
// Android (Material 3 filled): a subtle `muted` fill, TOP corners ~4 radius and
|
|
16
|
+
// a flat bottom, a bottom active-indicator underline (1dp `border` at rest ->
|
|
17
|
+
// 2dp `ring` brand when open); the menu surface is a flat-cornered (~4)
|
|
18
|
+
// elevated `popover` sheet (M3 elevation, no soft drop shadow), full-width
|
|
19
|
+
// rows ~48dp tall; press = android_ripple.
|
|
20
|
+
// Web: the established Canvas look (the current combobox, lifted verbatim) —
|
|
21
|
+
// full 1px `input` border, 6 radius, `background` fill, h-8/9/10; the popover
|
|
22
|
+
// is a 6-radius bordered `popover` card with `shadow-lg`, 4px padding, 2-radius
|
|
23
|
+
// accent rows. Press dims nothing (the active accent fill is the feedback).
|
|
24
|
+
|
|
25
|
+
export type Size = "small" | "default" | "large";
|
|
26
|
+
|
|
27
|
+
// The contract a platform skin fulfills. The shell resolves the size and the
|
|
28
|
+
// open/selected/pressed/muted state and asks the skin to map them to RN style
|
|
29
|
+
// objects. The skin owns shape, fill, border/underline, popover elevation, the
|
|
30
|
+
// row layout, and the press-feedback channel (iOS/web opacity vs Android ripple).
|
|
31
|
+
export interface ComboboxSkin {
|
|
32
|
+
/** Type scale per size; the field text and the option rows share it. */
|
|
33
|
+
text: (size: Size) => TextStyle;
|
|
34
|
+
/** Stacked field label above the field. */
|
|
35
|
+
label: (t: ColorTokens, size: Size) => TextStyle;
|
|
36
|
+
/** The editable field surface: shape, fill, border/underline for the open state. */
|
|
37
|
+
field: (t: ColorTokens, size: Size, open: boolean) => ViewStyle;
|
|
38
|
+
/** The field's value text (foreground), or muted for the placeholder. */
|
|
39
|
+
fieldText: (t: ColorTokens, size: Size, muted: boolean) => TextStyle;
|
|
40
|
+
/** The trailing disclosure chevron. */
|
|
41
|
+
chevron: (t: ColorTokens, size: Size) => TextStyle;
|
|
42
|
+
/** The open option list surface (radius, elevation/shadow, padding). */
|
|
43
|
+
popover: (t: ColorTokens) => ViewStyle;
|
|
44
|
+
/** The "No results" row box. */
|
|
45
|
+
emptyRow: ViewStyle;
|
|
46
|
+
emptyText: (t: ColorTokens, size: Size) => TextStyle;
|
|
47
|
+
/** A single option row's layout (gutter, radius, padding). */
|
|
48
|
+
row: ViewStyle;
|
|
49
|
+
/** The accent surface for the selected row and the pressed/active row. */
|
|
50
|
+
rowAccent: (t: ColorTokens) => ViewStyle;
|
|
51
|
+
/** The leading check column. */
|
|
52
|
+
check: (t: ColorTokens, size: Size) => TextStyle;
|
|
53
|
+
/** The option label. */
|
|
54
|
+
optionText: (t: ColorTokens, size: Size) => TextStyle;
|
|
55
|
+
/** Helper line below the option list. */
|
|
56
|
+
helper: (t: ColorTokens) => TextStyle;
|
|
57
|
+
/** Opacity applied to the whole control when disabled. */
|
|
58
|
+
disabledOpacity: number;
|
|
59
|
+
/** iOS/web dim the field on press; Android uses a ripple instead (null). */
|
|
60
|
+
pressedOpacity: number | null;
|
|
61
|
+
/** Android ripple over the pressable surfaces; null on iOS/web. */
|
|
62
|
+
ripple: ((t: ColorTokens) => { color: string; borderless: boolean }) | null;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// `relative w-full`: the positioning context for the absolutely-placed popover.
|
|
66
|
+
export const wrapper: ViewStyle = { position: "relative", width: "100%" };
|
|
67
|
+
|
|
68
|
+
// When the list is open, the wrapper is lifted into its own stacking context
|
|
69
|
+
// above sibling content. react-native-web gives every positioned View an
|
|
70
|
+
// implicit stacking context, so the popover's own `zIndex` is scoped INSIDE the
|
|
71
|
+
// `relative` wrapper and cannot rise above a later sibling. Raising the wrapper's
|
|
72
|
+
// zIndex while open lifts the whole control — field and popover together — above
|
|
73
|
+
// everything painted after it.
|
|
74
|
+
export const wrapperLifted: ViewStyle = { zIndex: 50 };
|
|
75
|
+
|
|
76
|
+
// --- shared type scale (identical across platforms; brand type, not a face) --
|
|
77
|
+
const TEXT_SIZE: Record<Size, TextStyle> = {
|
|
78
|
+
small: { fontSize: 12, lineHeight: 16 },
|
|
79
|
+
default: { fontSize: 14, lineHeight: 20 },
|
|
80
|
+
large: { fontSize: 16, lineHeight: 24 },
|
|
81
|
+
};
|
|
82
|
+
function webText(size: Size): TextStyle {
|
|
83
|
+
return TEXT_SIZE[size];
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// Field height per size; mirrors Input's footprint per platform.
|
|
87
|
+
const WEB_FIELD_BOX: Record<Size, number> = { small: 32, default: 36, large: 40 };
|
|
88
|
+
|
|
89
|
+
// ---------- Web: the established Canvas look (lifted verbatim) ----------
|
|
90
|
+
export const webSkin: ComboboxSkin = {
|
|
91
|
+
text: webText,
|
|
92
|
+
label: (t, size) => ({ marginBottom: 6, fontWeight: "500", color: t.foreground, ...TEXT_SIZE[size] }),
|
|
93
|
+
field: (t, size) => ({
|
|
94
|
+
flexDirection: "row",
|
|
95
|
+
alignItems: "center",
|
|
96
|
+
justifyContent: "space-between",
|
|
97
|
+
borderRadius: 6,
|
|
98
|
+
borderWidth: 1,
|
|
99
|
+
borderColor: t.input,
|
|
100
|
+
backgroundColor: t.background,
|
|
101
|
+
paddingHorizontal: 12,
|
|
102
|
+
height: WEB_FIELD_BOX[size],
|
|
103
|
+
}),
|
|
104
|
+
fieldText: (t, size, muted) => ({ color: muted ? t["muted-foreground"] : t.foreground, ...TEXT_SIZE[size] }),
|
|
105
|
+
chevron: (t, size) => ({ color: t["muted-foreground"], ...TEXT_SIZE[size] }),
|
|
106
|
+
popover: (t) => ({
|
|
107
|
+
position: "absolute",
|
|
108
|
+
top: "100%",
|
|
109
|
+
left: 0,
|
|
110
|
+
right: 0,
|
|
111
|
+
zIndex: 50,
|
|
112
|
+
marginTop: 4,
|
|
113
|
+
maxHeight: 240,
|
|
114
|
+
borderRadius: 6,
|
|
115
|
+
borderWidth: 1,
|
|
116
|
+
borderColor: t.border,
|
|
117
|
+
backgroundColor: t.popover,
|
|
118
|
+
padding: 4,
|
|
119
|
+
...shadow("lg"),
|
|
120
|
+
}),
|
|
121
|
+
emptyRow: { paddingHorizontal: 8, paddingVertical: 6 },
|
|
122
|
+
emptyText: (t, size) => ({ color: t["muted-foreground"], ...TEXT_SIZE[size] }),
|
|
123
|
+
row: {
|
|
124
|
+
flexDirection: "row",
|
|
125
|
+
alignItems: "center",
|
|
126
|
+
gap: 8,
|
|
127
|
+
borderRadius: 2,
|
|
128
|
+
paddingHorizontal: 8,
|
|
129
|
+
paddingVertical: 6,
|
|
130
|
+
},
|
|
131
|
+
rowAccent: (t) => ({ backgroundColor: t.accent }),
|
|
132
|
+
check: (t, size) => ({ width: 14, color: t["popover-foreground"], ...TEXT_SIZE[size] }),
|
|
133
|
+
optionText: (t, size) => ({ color: t["popover-foreground"], ...TEXT_SIZE[size] }),
|
|
134
|
+
helper: (t) => ({ marginTop: 6, fontSize: 12, lineHeight: 16, color: t["muted-foreground"] }),
|
|
135
|
+
disabledOpacity: 0.5,
|
|
136
|
+
pressedOpacity: null, // web shows press via the active accent fill, not opacity
|
|
137
|
+
ripple: null,
|
|
138
|
+
};
|
|
139
|
+
|
|
140
|
+
// ---------- iOS 27 (iOS 26+/Liquid Glass): plain hairline field, large-radius glass menu ----------
|
|
141
|
+
// The iOS 27 combo box reads like the new iOS text field: NO filled capsule and
|
|
142
|
+
// no box, just the value text on a transparent surface above a thin bottom
|
|
143
|
+
// hairline (`border` at rest, the brand indigo `primary` when the list is open,
|
|
144
|
+
// echoing the field's blue caret in the reference). The open list is the iOS 26+
|
|
145
|
+
// menu surface: a large continuous-corner `popover` card (~27 radius, up from the
|
|
146
|
+
// old ~12) floating on a soft shadow, with roomy rows. Press dims the surface
|
|
147
|
+
// (~0.8); no ripple. The brand survives: the open hairline, the leading check,
|
|
148
|
+
// and the focus accent are all the indigo `primary`, never iOS system blue.
|
|
149
|
+
const IOS_MENU_RADIUS = 27;
|
|
150
|
+
const IOS_FIELD_BOX: Record<Size, number> = { small: 36, default: 44, large: 50 };
|
|
151
|
+
export const iosSkin: ComboboxSkin = {
|
|
152
|
+
text: webText,
|
|
153
|
+
label: (t, size) => ({ marginBottom: 6, fontWeight: "600", color: t.foreground, ...TEXT_SIZE[size] }),
|
|
154
|
+
// Plain field: transparent, square (no capsule), bottom hairline only. The
|
|
155
|
+
// hairline thickens and tints to the brand `primary` when the list is open.
|
|
156
|
+
field: (t, size, open) => ({
|
|
157
|
+
flexDirection: "row",
|
|
158
|
+
alignItems: "center",
|
|
159
|
+
justifyContent: "space-between",
|
|
160
|
+
borderRadius: 0,
|
|
161
|
+
backgroundColor: "transparent",
|
|
162
|
+
borderBottomWidth: open ? 1.5 : StyleSheet.hairlineWidth,
|
|
163
|
+
borderBottomColor: open ? t.primary : t.border,
|
|
164
|
+
paddingHorizontal: 0,
|
|
165
|
+
height: IOS_FIELD_BOX[size],
|
|
166
|
+
}),
|
|
167
|
+
fieldText: (t, size, muted) => ({ color: muted ? t["muted-foreground"] : t.foreground, ...TEXT_SIZE[size] }),
|
|
168
|
+
chevron: (t, size) => ({ color: t["muted-foreground"], ...TEXT_SIZE[size] }),
|
|
169
|
+
popover: (t) => ({
|
|
170
|
+
position: "absolute",
|
|
171
|
+
top: "100%",
|
|
172
|
+
left: 0,
|
|
173
|
+
right: 0,
|
|
174
|
+
zIndex: 50,
|
|
175
|
+
marginTop: 6,
|
|
176
|
+
maxHeight: 260,
|
|
177
|
+
borderRadius: IOS_MENU_RADIUS,
|
|
178
|
+
backgroundColor: t.popover,
|
|
179
|
+
padding: 6,
|
|
180
|
+
...shadow("lg"),
|
|
181
|
+
}),
|
|
182
|
+
emptyRow: { paddingHorizontal: 14, paddingVertical: 10 },
|
|
183
|
+
emptyText: (t, size) => ({ color: t["muted-foreground"], ...TEXT_SIZE[size] }),
|
|
184
|
+
row: {
|
|
185
|
+
flexDirection: "row",
|
|
186
|
+
alignItems: "center",
|
|
187
|
+
gap: 8,
|
|
188
|
+
borderRadius: 18,
|
|
189
|
+
paddingHorizontal: 14,
|
|
190
|
+
paddingVertical: 10,
|
|
191
|
+
},
|
|
192
|
+
rowAccent: (t) => ({ backgroundColor: t.accent }),
|
|
193
|
+
check: (t, size) => ({ width: 16, color: t.primary, ...TEXT_SIZE[size] }),
|
|
194
|
+
optionText: (t, size) => ({ color: t["popover-foreground"], ...TEXT_SIZE[size] }),
|
|
195
|
+
helper: (t) => ({ marginTop: 6, fontSize: 12, lineHeight: 16, color: t["muted-foreground"] }),
|
|
196
|
+
disabledOpacity: 0.5,
|
|
197
|
+
pressedOpacity: 0.8,
|
|
198
|
+
ripple: null,
|
|
199
|
+
};
|
|
200
|
+
|
|
201
|
+
// ---------- Android (Material 3 filled): subtle fill, top radius, bottom indicator, elevated menu ----------
|
|
202
|
+
// M3 exposed dropdown: the anchor is a filled field (`muted`) with the TOP
|
|
203
|
+
// corners rounded ~4dp and a flat bottom, plus a bottom active-indicator
|
|
204
|
+
// underline — 1dp `border` at rest, 2dp `ring` (brand) when open. The menu is a
|
|
205
|
+
// flat-cornered (~4) elevated `popover` sheet (M3 elevation via `elevation`, no
|
|
206
|
+
// soft iOS drop shadow), full-width rows ~48dp tall whose active/selected state
|
|
207
|
+
// is the `accent` state layer. The action feedback is android_ripple.
|
|
208
|
+
const ANDROID_TOP_RADIUS = 4;
|
|
209
|
+
const ANDROID_FIELD_BOX: Record<Size, number> = { small: 48, default: 56, large: 60 };
|
|
210
|
+
export const androidSkin: ComboboxSkin = {
|
|
211
|
+
// M3 body text is 16sp; nudge base/large up, keep small readable.
|
|
212
|
+
text: (size) => {
|
|
213
|
+
if (size === "large") return { fontSize: 18, lineHeight: 26 };
|
|
214
|
+
if (size === "small") return { fontSize: 14, lineHeight: 20 };
|
|
215
|
+
return { fontSize: 16, lineHeight: 24 };
|
|
216
|
+
},
|
|
217
|
+
label: (t, size) => ({ marginBottom: 6, fontWeight: "500", color: t.foreground, ...TEXT_SIZE[size] }),
|
|
218
|
+
field: (t, size, open) => ({
|
|
219
|
+
flexDirection: "row",
|
|
220
|
+
alignItems: "center",
|
|
221
|
+
justifyContent: "space-between",
|
|
222
|
+
borderTopLeftRadius: ANDROID_TOP_RADIUS,
|
|
223
|
+
borderTopRightRadius: ANDROID_TOP_RADIUS,
|
|
224
|
+
borderBottomLeftRadius: 0,
|
|
225
|
+
borderBottomRightRadius: 0,
|
|
226
|
+
borderBottomWidth: open ? 2 : 1,
|
|
227
|
+
// Rest baseline reads clearly (on-surface-variant ~ muted-foreground) so the M3
|
|
228
|
+
// filled field is distinct from the iOS lineless capsule.
|
|
229
|
+
borderBottomColor: open ? t.ring : t["muted-foreground"],
|
|
230
|
+
backgroundColor: t.muted,
|
|
231
|
+
paddingHorizontal: 16,
|
|
232
|
+
height: ANDROID_FIELD_BOX[size],
|
|
233
|
+
}),
|
|
234
|
+
fieldText: (t, size, muted) => ({ color: muted ? t["muted-foreground"] : t.foreground, ...TEXT_SIZE[size] }),
|
|
235
|
+
chevron: (t, size) => ({ color: t["muted-foreground"], ...TEXT_SIZE[size] }),
|
|
236
|
+
// M3 menu surface: flat 4dp corners, elevated (no soft drop shadow), zero
|
|
237
|
+
// padding so the full-bleed rows reach the edges.
|
|
238
|
+
popover: (t) => ({
|
|
239
|
+
position: "absolute",
|
|
240
|
+
top: "100%",
|
|
241
|
+
left: 0,
|
|
242
|
+
right: 0,
|
|
243
|
+
zIndex: 50,
|
|
244
|
+
marginTop: 4,
|
|
245
|
+
maxHeight: 280,
|
|
246
|
+
borderRadius: 4,
|
|
247
|
+
backgroundColor: t.popover,
|
|
248
|
+
paddingVertical: 8,
|
|
249
|
+
elevation: 8,
|
|
250
|
+
}),
|
|
251
|
+
emptyRow: { paddingHorizontal: 16, paddingVertical: 12 },
|
|
252
|
+
emptyText: (t, size) => ({ color: t["muted-foreground"], ...TEXT_SIZE[size] }),
|
|
253
|
+
// Full-bleed M3 list rows: square corners, ~48dp tall, 16dp gutter.
|
|
254
|
+
row: {
|
|
255
|
+
flexDirection: "row",
|
|
256
|
+
alignItems: "center",
|
|
257
|
+
gap: 12,
|
|
258
|
+
borderRadius: 0,
|
|
259
|
+
minHeight: 48,
|
|
260
|
+
paddingHorizontal: 16,
|
|
261
|
+
paddingVertical: 12,
|
|
262
|
+
},
|
|
263
|
+
rowAccent: (t) => ({ backgroundColor: t.accent }),
|
|
264
|
+
check: (t, size) => ({ width: 16, color: t.primary, ...TEXT_SIZE[size] }),
|
|
265
|
+
optionText: (t, size) => ({ color: t["popover-foreground"], ...TEXT_SIZE[size] }),
|
|
266
|
+
helper: (t) => ({ marginTop: 6, fontSize: 12, lineHeight: 16, color: t["muted-foreground"] }),
|
|
267
|
+
disabledOpacity: 0.38, // M3 disabled opacity
|
|
268
|
+
pressedOpacity: null, // Android uses a ripple instead
|
|
269
|
+
ripple: (t) => ({ color: t.accent, borderless: false }),
|
|
270
|
+
};
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { createCombobox } from "./combobox.shared.js";
|
|
2
|
+
import { webSkin } from "./combobox.styles.js";
|
|
3
|
+
|
|
4
|
+
// Web Combobox (the base; Metro falls back to it on native, web bundlers resolve it).
|
|
5
|
+
export const Combobox = createCombobox(webSkin);
|
|
6
|
+
export type { ComboboxProps } from "./combobox.shared.js";
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
# Dividers
|
|
2
|
+
|
|
3
|
+
Horizontal, vertical, with label, with action.
|
|
4
|
+
|
|
5
|
+
## Usage
|
|
6
|
+
|
|
7
|
+
```tsx
|
|
8
|
+
<Divider />
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Variants
|
|
12
|
+
|
|
13
|
+
### Orientation - vertical
|
|
14
|
+
|
|
15
|
+
```tsx
|
|
16
|
+
<Divider vertical />
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
### Variant - label
|
|
20
|
+
|
|
21
|
+
```tsx
|
|
22
|
+
<Divider>Or continue with</Divider>
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
### Variant - action
|
|
26
|
+
|
|
27
|
+
```tsx
|
|
28
|
+
<View style={{ width: 320 }}>
|
|
29
|
+
<View style={{ gap: 8 }}>
|
|
30
|
+
<View style={{ borderRadius: 6, borderWidth: 1, borderColor: tokens.border, paddingHorizontal: 12, paddingVertical: 8 }}>
|
|
31
|
+
<Text style={{ fontSize: 14, lineHeight: 20, color: tokens.foreground }}>Ada commented on the draft</Text>
|
|
32
|
+
</View>
|
|
33
|
+
<View style={{ borderRadius: 6, borderWidth: 1, borderColor: tokens.border, paddingHorizontal: 12, paddingVertical: 8 }}>
|
|
34
|
+
<Text style={{ fontSize: 14, lineHeight: 20, color: tokens.foreground }}>Grace approved the request</Text>
|
|
35
|
+
</View>
|
|
36
|
+
</View>
|
|
37
|
+
<Divider style={{ marginTop: 12 }} children={<Button ghost small>Show more</Button>} />
|
|
38
|
+
</View>
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## Do & Don't
|
|
42
|
+
|
|
43
|
+
### Plain
|
|
44
|
+
|
|
45
|
+
**Do** — Click a row: group with spacing and reserve a divider for a real break like Sign out.
|
|
46
|
+
|
|
47
|
+
```tsx
|
|
48
|
+
<View style={{ maxWidth: 280 }}>
|
|
49
|
+
<Text style={{ borderRadius: 6, paddingHorizontal: 8, paddingVertical: 6, fontSize: 14, lineHeight: 20 }}>Profile</Text>
|
|
50
|
+
<Text style={{ borderRadius: 6, paddingHorizontal: 8, paddingVertical: 6, fontSize: 14, lineHeight: 20 }}>Account</Text>
|
|
51
|
+
<Text style={{ borderRadius: 6, paddingHorizontal: 8, paddingVertical: 6, fontSize: 14, lineHeight: 20 }}>Notifications</Text>
|
|
52
|
+
<Divider style={{ marginVertical: 4 }} />
|
|
53
|
+
<Text style={{ borderRadius: 6, paddingHorizontal: 8, paddingVertical: 6, fontSize: 14, lineHeight: 20 }}>Sign out</Text>
|
|
54
|
+
</View>
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
**Don't** — Click a row: a divider between every one is noise that competes with the content.
|
|
58
|
+
|
|
59
|
+
```tsx
|
|
60
|
+
<View style={{ maxWidth: 280 }}>
|
|
61
|
+
<Text style={{ borderRadius: 6, paddingHorizontal: 8, paddingVertical: 6, fontSize: 14, lineHeight: 20 }}>Profile</Text>
|
|
62
|
+
<Divider />
|
|
63
|
+
<Text style={{ borderRadius: 6, paddingHorizontal: 8, paddingVertical: 6, fontSize: 14, lineHeight: 20 }}>Account</Text>
|
|
64
|
+
<Divider />
|
|
65
|
+
<Text style={{ borderRadius: 6, paddingHorizontal: 8, paddingVertical: 6, fontSize: 14, lineHeight: 20 }}>Notifications</Text>
|
|
66
|
+
<Divider />
|
|
67
|
+
<Text style={{ borderRadius: 6, paddingHorizontal: 8, paddingVertical: 6, fontSize: 14, lineHeight: 20 }}>Billing</Text>
|
|
68
|
+
</View>
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
### With label
|
|
72
|
+
|
|
73
|
+
**Do** — Click a provider: keep the label to a few words and let the buttons carry the options.
|
|
74
|
+
|
|
75
|
+
```tsx
|
|
76
|
+
<View style={{ width: 320, flexDirection: "column", gap: 8 }}>
|
|
77
|
+
<Button primary block>Sign in</Button>
|
|
78
|
+
<Divider>or continue with</Divider>
|
|
79
|
+
<View style={{ flexDirection: "row", gap: 8 }}>
|
|
80
|
+
<Button outline block style={{ flexGrow: 1, flexShrink: 1, flexBasis: "0%" }}>Google</Button>
|
|
81
|
+
<Button outline block style={{ flexGrow: 1, flexShrink: 1, flexBasis: "0%" }}>GitHub</Button>
|
|
82
|
+
</View>
|
|
83
|
+
</View>
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
**Don't** — Click Sign in: a full sentence in the label divider buries the choice.
|
|
87
|
+
|
|
88
|
+
```tsx
|
|
89
|
+
<View style={{ width: 320, flexDirection: "column", gap: 8 }}>
|
|
90
|
+
<Button primary block>Sign in</Button>
|
|
91
|
+
<Divider>or continue with one of your previously linked third-party accounts</Divider>
|
|
92
|
+
</View>
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
### With action
|
|
96
|
+
|
|
97
|
+
**Do** — Click Show more: the button toggles its label and reveals the rest.
|
|
98
|
+
|
|
99
|
+
```tsx
|
|
100
|
+
<View style={{ width: 320, gap: 6 }}>
|
|
101
|
+
<Text style={{ paddingVertical: 6, fontSize: 14, lineHeight: 20, color: tokens["muted-foreground"] }}>Logged in from 2 new devices · 3 more entries</Text>
|
|
102
|
+
<Divider>
|
|
103
|
+
<Button ghost small>Show less</Button>
|
|
104
|
+
</Divider>
|
|
105
|
+
</View>
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
**Don't** — Click the button: an action divider that does nothing is just decoration.
|
|
109
|
+
|
|
110
|
+
```tsx
|
|
111
|
+
<View style={{ width: 320 }}>
|
|
112
|
+
<Divider>
|
|
113
|
+
<Button ghost small>Show more</Button>
|
|
114
|
+
</Divider>
|
|
115
|
+
</View>
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
### Vertical
|
|
119
|
+
|
|
120
|
+
**Do** — Click an action: the vertical rule separates inline actions in a row.
|
|
121
|
+
|
|
122
|
+
```tsx
|
|
123
|
+
<View style={{ flexDirection: "row", alignItems: "center", gap: 12 }}>
|
|
124
|
+
<Text style={{ fontSize: 14, lineHeight: 20 }}>Edit</Text>
|
|
125
|
+
<Divider vertical style={{ height: 16 }} />
|
|
126
|
+
<Text style={{ fontSize: 14, lineHeight: 20 }}>Delete</Text>
|
|
127
|
+
<Divider vertical style={{ height: 16 }} />
|
|
128
|
+
<Text style={{ fontSize: 14, lineHeight: 20 }}>Share</Text>
|
|
129
|
+
</View>
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
**Don't** — Click an action: a vertical rule between stacked items reads as a glitch.
|
|
133
|
+
|
|
134
|
+
```tsx
|
|
135
|
+
<View style={{ flexDirection: "column", alignItems: "flex-start", gap: 8 }}>
|
|
136
|
+
<Text style={{ fontSize: 14, lineHeight: 20 }}>Edit</Text>
|
|
137
|
+
<Divider vertical style={{ height: 16 }} />
|
|
138
|
+
<Text style={{ fontSize: 14, lineHeight: 20 }}>Delete</Text>
|
|
139
|
+
</View>
|
|
140
|
+
```
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { type ViewStyle, type TextStyle } from "react-native";
|
|
2
|
+
import { type ColorTokens } from "../../style/index.js";
|
|
3
|
+
|
|
4
|
+
// Co-located Divider styles. The hairline rule is layout-only except for its
|
|
5
|
+
// fill, which reads a token (so it follows light/dark and the glass surface):
|
|
6
|
+
// `strong` tracks the standard border token, `soft` steps down to muted for a
|
|
7
|
+
// quieter rule. Layout fragments are static objects; the color is a function of
|
|
8
|
+
// the active tokens.
|
|
9
|
+
|
|
10
|
+
export type Orientation = "horizontal" | "vertical";
|
|
11
|
+
export type Emphasis = "soft" | "strong";
|
|
12
|
+
|
|
13
|
+
// The hairline fill color, token-backed. `strong` is the standard border;
|
|
14
|
+
// `soft` steps down to the muted token for a quieter rule (was bg-border / bg-muted).
|
|
15
|
+
export function ruleFill(tokens: ColorTokens, emphasis: Emphasis): ViewStyle {
|
|
16
|
+
return { backgroundColor: emphasis === "strong" ? tokens.border : tokens.muted };
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
// Vertical rule: 1px wide, stretching to the row height it sits in (w-px self-stretch).
|
|
20
|
+
export const verticalRule: ViewStyle = { width: 1, alignSelf: "stretch" };
|
|
21
|
+
|
|
22
|
+
// Plain horizontal hairline spanning the full width (h-px w-full).
|
|
23
|
+
export const horizontalRule: ViewStyle = { height: 1, width: "100%" };
|
|
24
|
+
|
|
25
|
+
// The labeled/action row: a centered node flanked by two hairlines
|
|
26
|
+
// (flex-row items-center gap-3).
|
|
27
|
+
export const labelRow: ViewStyle = { flexDirection: "row", alignItems: "center", gap: 12 };
|
|
28
|
+
|
|
29
|
+
// A flanking hairline inside the labeled row: 1px tall, growing to fill (h-px flex-1).
|
|
30
|
+
export const flankRule: ViewStyle = { height: 1, flexGrow: 1, flexShrink: 1, flexBasis: "0%" };
|
|
31
|
+
|
|
32
|
+
// The centered label text: xs muted (text-xs text-muted-foreground).
|
|
33
|
+
export function labelText(tokens: ColorTokens): TextStyle {
|
|
34
|
+
return { fontSize: 12, lineHeight: 16, color: tokens["muted-foreground"] };
|
|
35
|
+
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { type ReactNode } from "react";
|
|
2
|
+
import { View, Text, useTheme, type StyleProp, type ViewStyle } from "../../style/index.js";
|
|
3
|
+
import * as s from "./divider.styles.js";
|
|
4
|
+
import { type Orientation, type Emphasis } from "./divider.styles.js";
|
|
5
|
+
|
|
6
|
+
export interface DividerProps {
|
|
7
|
+
/**
|
|
8
|
+
* Optional middle content. With children, a horizontal divider renders a
|
|
9
|
+
* flanking-line + centered-label row (the "with label" / "with action"
|
|
10
|
+
* patterns). A string renders as muted label text; arbitrary nodes (e.g. a
|
|
11
|
+
* button) render as-is for the action pattern. Ignored when `vertical`.
|
|
12
|
+
*/
|
|
13
|
+
children?: ReactNode;
|
|
14
|
+
// Orientation (pick one; default is horizontal).
|
|
15
|
+
horizontal?: boolean;
|
|
16
|
+
vertical?: boolean;
|
|
17
|
+
// Emphasis (pick one; default tracks the border token).
|
|
18
|
+
soft?: boolean;
|
|
19
|
+
strong?: boolean;
|
|
20
|
+
/** Escape hatch for layout/positioning composition (width, margins, alignment). */
|
|
21
|
+
style?: StyleProp<ViewStyle>;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// First match wins when more than one orientation flag is passed.
|
|
25
|
+
function orientationOf(p: DividerProps): Orientation {
|
|
26
|
+
if (p.vertical) return "vertical";
|
|
27
|
+
return "horizontal";
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// First match wins when more than one emphasis flag is passed.
|
|
31
|
+
function emphasisOf(p: DividerProps): Emphasis {
|
|
32
|
+
if (p.soft) return "soft";
|
|
33
|
+
return "strong";
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export function Divider(props: DividerProps) {
|
|
37
|
+
const { children, style } = props;
|
|
38
|
+
const orientation = orientationOf(props);
|
|
39
|
+
const emphasis = emphasisOf(props);
|
|
40
|
+
const { tokens } = useTheme();
|
|
41
|
+
const ruleFill = s.ruleFill(tokens, emphasis);
|
|
42
|
+
|
|
43
|
+
if (orientation === "vertical") {
|
|
44
|
+
// A thin vertical rule that adapts to the row height it sits in.
|
|
45
|
+
return <View style={[s.verticalRule, ruleFill, style]} />;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// Horizontal with a label/action in the middle: a centered node flanked by
|
|
49
|
+
// two hairlines (the sepLabel pattern: gap-3, xs muted text).
|
|
50
|
+
if (children != null) {
|
|
51
|
+
const isText = typeof children === "string" || typeof children === "number";
|
|
52
|
+
return (
|
|
53
|
+
<View style={[s.labelRow, style]}>
|
|
54
|
+
<View style={[s.flankRule, ruleFill]} />
|
|
55
|
+
{isText ? (
|
|
56
|
+
<Text style={s.labelText(tokens)}>{children}</Text>
|
|
57
|
+
) : (
|
|
58
|
+
children
|
|
59
|
+
)}
|
|
60
|
+
<View style={[s.flankRule, ruleFill]} />
|
|
61
|
+
</View>
|
|
62
|
+
);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// Plain horizontal hairline spanning the full width.
|
|
66
|
+
return <View style={[s.horizontalRule, ruleFill, style]} />;
|
|
67
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { createDropdown } from "./dropdown.shared.js";
|
|
2
|
+
import { androidSkin } from "./dropdown.styles.js";
|
|
3
|
+
|
|
4
|
+
// Material 3 (menu) Dropdown. Metro resolves this file on Android; the docs import it for preview.
|
|
5
|
+
export const Dropdown = createDropdown(androidSkin);
|
|
6
|
+
export type { DropdownProps, DropdownItem } from "./dropdown.shared.js";
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { createDropdown } from "./dropdown.shared.js";
|
|
2
|
+
import { iosSkin } from "./dropdown.styles.js";
|
|
3
|
+
|
|
4
|
+
// iOS (HIG pull-down menu) Dropdown. Metro resolves this file on iOS; the docs import it for preview.
|
|
5
|
+
export const Dropdown = createDropdown(iosSkin);
|
|
6
|
+
export type { DropdownProps, DropdownItem } from "./dropdown.shared.js";
|