@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,272 @@
|
|
|
1
|
+
import { type ViewStyle, type TextStyle } from "react-native";
|
|
2
|
+
import { type ColorTokens, shadow, alpha } from "../../style/index.js";
|
|
3
|
+
|
|
4
|
+
// Co-located Dialog 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
|
|
7
|
+
// survives on every platform (the indigo `primary` confirm action, the
|
|
8
|
+
// `destructive` red for an irreversible confirm); only the native SHAPE, sizing,
|
|
9
|
+
// title alignment, body type, footer layout, and backdrop dimming change per OS:
|
|
10
|
+
// iOS (iOS 27 / Liquid Glass alert): a centered card (28 radius, `popover`
|
|
11
|
+
// fill, NO border, soft lg shadow) over a ~0.30 black backdrop; a LEFT-aligned
|
|
12
|
+
// ~20pt/700 title, a ~15pt muted LEFT body, and a side-by-side row of CAPSULE
|
|
13
|
+
// action buttons (a gray `secondary` Cancel capsule + a `primary` indigo
|
|
14
|
+
// Confirm capsule; a destructive confirm is a gray capsule with `destructive`
|
|
15
|
+
// red text), NO hairline dividers; press = opacity dim (~0.8).
|
|
16
|
+
// Android (Material 3 basic dialog): a card (28 radius, `popover` elevated
|
|
17
|
+
// surface, shadow lg) over a ~0.32 black scrim; a LEFT-aligned ~22pt title,
|
|
18
|
+
// a 14sp body, and TEXT-button actions (no fill) bottom-RIGHT in a row
|
|
19
|
+
// (Cancel then Confirm) in brand-indigo text, an android_ripple on each, and
|
|
20
|
+
// NO dividers.
|
|
21
|
+
// Web: the established Canvas look (the current dialog, lifted verbatim) — a
|
|
22
|
+
// bordered card (8 radius, `border`, `popover` fill, xl shadow) over a 0.50
|
|
23
|
+
// black backdrop; a 16pt/600 left title, a 14pt muted body, and a
|
|
24
|
+
// right-aligned action row of an outline Cancel + a primary/destructive
|
|
25
|
+
// Confirm Button.
|
|
26
|
+
|
|
27
|
+
export type Size = "xs" | "small" | "medium" | "default" | "large" | "wide";
|
|
28
|
+
|
|
29
|
+
// The dialog card's max width per size, narrowest to widest. The default sits
|
|
30
|
+
// one step wider than `medium`, roomy enough for a short form; `xs`/`small`
|
|
31
|
+
// tighten the panel for a terse message, `large`/`wide` open it up for a longer
|
|
32
|
+
// form. Pixel widths mirror Tailwind's max-w-xs..2xl scale.
|
|
33
|
+
export const PANEL_MAX_WIDTH: Record<Size, number> = {
|
|
34
|
+
xs: 320,
|
|
35
|
+
small: 384,
|
|
36
|
+
medium: 448,
|
|
37
|
+
default: 512,
|
|
38
|
+
large: 576,
|
|
39
|
+
wide: 672,
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
// How the footer renders its actions. The web/Android footer is a horizontal
|
|
43
|
+
// row of Buttons (web) / text buttons (Android); iOS renders a side-by-side row
|
|
44
|
+
// of CAPSULE buttons (the iOS 27 alert: a gray Cancel capsule + a primary
|
|
45
|
+
// Confirm capsule, no dividers). The shell reads this to pick the footer
|
|
46
|
+
// structure.
|
|
47
|
+
export type FooterKind = "buttons" | "capsules";
|
|
48
|
+
|
|
49
|
+
// The contract a platform skin fulfills. The shell renders the backdrop, the
|
|
50
|
+
// centered card (shape/fill/border/shadow from the skin), the title, the body,
|
|
51
|
+
// the optional data-driven form, and the footer; the skin maps each piece onto
|
|
52
|
+
// the active platform's look. The footer kind picks between a Button row
|
|
53
|
+
// (web/Android) and iOS's side-by-side capsule row.
|
|
54
|
+
export interface DialogSkin {
|
|
55
|
+
/** The dim backdrop behind the card (full black at the per-platform opacity). */
|
|
56
|
+
backdrop: (t: ColorTokens) => ViewStyle;
|
|
57
|
+
/** The card layout box: shape, fill, border (or lack of), shadow, padding. The
|
|
58
|
+
* shell supplies the per-size maxWidth inline. */
|
|
59
|
+
card: (t: ColorTokens) => ViewStyle;
|
|
60
|
+
/** The title type: size, weight, alignment, color. */
|
|
61
|
+
title: (t: ColorTokens) => TextStyle;
|
|
62
|
+
/** The body/description type: size, color, alignment. */
|
|
63
|
+
body: (t: ColorTokens) => TextStyle;
|
|
64
|
+
/** Whether the footer is a Button row or iOS's side-by-side capsule row. */
|
|
65
|
+
footerKind: FooterKind;
|
|
66
|
+
/** The footer container (the action row). */
|
|
67
|
+
footer: (t: ColorTokens) => ViewStyle;
|
|
68
|
+
// --- capsule (iOS) footer pieces; null on the Button-row platforms ---------
|
|
69
|
+
/** A single capsule action button's box (shape/padding); each capsule grows to
|
|
70
|
+
* share the row evenly. `confirm` true for the primary/confirm capsule. */
|
|
71
|
+
capsule: ((t: ColorTokens, confirm: boolean, destructive: boolean) => ViewStyle) | null;
|
|
72
|
+
/** The label inside a capsule; `confirm` true for the primary confirm action
|
|
73
|
+
* (drawn on the indigo fill), `destructive` true for an irreversible confirm
|
|
74
|
+
* (red text on a gray capsule). */
|
|
75
|
+
capsuleLabel: ((t: ColorTokens, confirm: boolean, destructive: boolean) => TextStyle) | null;
|
|
76
|
+
/** Opacity applied to a pressed capsule (iOS dims; null elsewhere). */
|
|
77
|
+
capsulePressedOpacity: number | null;
|
|
78
|
+
// --- text-button (Android) footer pieces; null elsewhere -------------------
|
|
79
|
+
/** An Android text-button touch target (no fill, brand-indigo label). */
|
|
80
|
+
textButton: ViewStyle | null;
|
|
81
|
+
/** The Android text-button label; `destructive` reds an irreversible confirm. */
|
|
82
|
+
textButtonLabel: ((t: ColorTokens, destructive: boolean) => TextStyle) | null;
|
|
83
|
+
/** The Android ripple over a text button; null on the other platforms. */
|
|
84
|
+
textButtonRipple: ((t: ColorTokens) => { color: string; borderless: boolean }) | null;
|
|
85
|
+
// --- data-driven body form (Amount + Reason) -------------------------------
|
|
86
|
+
/** Wrapper above the form fields. */
|
|
87
|
+
formBody: ViewStyle;
|
|
88
|
+
/** A field label above an input. */
|
|
89
|
+
fieldLabel: (t: ColorTokens) => TextStyle;
|
|
90
|
+
/** Extra top inset for the second field label. */
|
|
91
|
+
fieldLabelGap: ViewStyle;
|
|
92
|
+
/** The Amount row (currency glyph + input). */
|
|
93
|
+
amountRow: ViewStyle;
|
|
94
|
+
/** The leading currency glyph. */
|
|
95
|
+
currency: (t: ColorTokens) => TextStyle;
|
|
96
|
+
/** The Amount input grows to fill the row. */
|
|
97
|
+
amountInput: ViewStyle;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// --- outer shell (identical across platforms) -------------------------------
|
|
101
|
+
|
|
102
|
+
// The outermost wrapper shrinks to its content so the inline trigger sits flush.
|
|
103
|
+
export const root: ViewStyle = { alignSelf: "flex-start" };
|
|
104
|
+
|
|
105
|
+
// Inset between the trigger button and the backdrop when a trigger is rendered.
|
|
106
|
+
export const backdropTriggerGap: ViewStyle = { marginTop: 12 };
|
|
107
|
+
|
|
108
|
+
// The contained backdrop sizing (centered, with presence in the docs preview via
|
|
109
|
+
// an explicit minHeight). The per-platform fill/radius come from the skin.
|
|
110
|
+
export const backdropLayout: ViewStyle = {
|
|
111
|
+
alignItems: "center",
|
|
112
|
+
justifyContent: "center",
|
|
113
|
+
padding: 32,
|
|
114
|
+
minHeight: 220,
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
// The card layout box (width up to its per-size cap). Shape/fill/shadow come
|
|
118
|
+
// from the skin; this owns the box-model that every platform shares.
|
|
119
|
+
export const cardLayout: ViewStyle = { width: "100%", padding: 24 };
|
|
120
|
+
|
|
121
|
+
// Per-size max width cap.
|
|
122
|
+
export function cardWidth(size: Size): ViewStyle {
|
|
123
|
+
return { maxWidth: PANEL_MAX_WIDTH[size] };
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// ---------- Web: the established Canvas look (lifted verbatim) ----------
|
|
127
|
+
// The current dialog: a card (rounded-lg border bg-popover p-6 shadow-xl) over a
|
|
128
|
+
// bg-black/50 backdrop; a 16/24 600 title, a 14/20 muted-foreground body, and a
|
|
129
|
+
// right-aligned action row (gap-2, mt-6) of an outline Cancel + a primary/
|
|
130
|
+
// destructive Confirm Button.
|
|
131
|
+
export const webSkin: DialogSkin = {
|
|
132
|
+
backdrop: () => ({ borderRadius: 8, backgroundColor: alpha("#000000", 0.5) }),
|
|
133
|
+
card: (t) => ({
|
|
134
|
+
borderRadius: 8,
|
|
135
|
+
borderWidth: 1,
|
|
136
|
+
borderColor: t.border,
|
|
137
|
+
backgroundColor: t.popover,
|
|
138
|
+
...shadow("xl"),
|
|
139
|
+
}),
|
|
140
|
+
title: (t) => ({ fontSize: 16, lineHeight: 24, fontWeight: "600", color: t["popover-foreground"] }),
|
|
141
|
+
body: (t) => ({ fontSize: 14, lineHeight: 20, color: t["muted-foreground"], marginTop: 8 }),
|
|
142
|
+
footerKind: "buttons",
|
|
143
|
+
footer: () => ({ flexDirection: "row", justifyContent: "flex-end", gap: 8, marginTop: 24 }),
|
|
144
|
+
capsule: null,
|
|
145
|
+
capsuleLabel: null,
|
|
146
|
+
capsulePressedOpacity: null,
|
|
147
|
+
textButton: null,
|
|
148
|
+
textButtonLabel: null,
|
|
149
|
+
textButtonRipple: null,
|
|
150
|
+
formBody: { marginTop: 20 },
|
|
151
|
+
fieldLabel: (t) => ({ fontSize: 14, lineHeight: 20, fontWeight: "500", color: t.foreground, marginBottom: 6 }),
|
|
152
|
+
fieldLabelGap: { marginTop: 16 },
|
|
153
|
+
amountRow: { flexDirection: "row", alignItems: "center" },
|
|
154
|
+
currency: (t) => ({ fontSize: 14, lineHeight: 20, color: t["muted-foreground"], marginRight: 8 }),
|
|
155
|
+
amountInput: { flexGrow: 1, flexShrink: 1, flexBasis: "0%" },
|
|
156
|
+
};
|
|
157
|
+
|
|
158
|
+
// ---------- iOS (iOS 27 alert): rounded card, no border, side-by-side capsules ----------
|
|
159
|
+
// iOS 27 (iOS 26+ / Liquid Glass) alert: a centered card (28pt radius) over the
|
|
160
|
+
// `popover` fill with NO border and a soft shadow, on a ~0.30 black backdrop; a
|
|
161
|
+
// LEFT-aligned 20pt/700 title, a 15pt muted LEFT body, and a side-by-side row of
|
|
162
|
+
// CAPSULE action buttons — a gray `secondary` Cancel capsule + a `primary`
|
|
163
|
+
// indigo Confirm capsule, with NO hairline dividers. A destructive confirm keeps
|
|
164
|
+
// the gray `secondary` capsule but draws its label in the `destructive` red. Each
|
|
165
|
+
// capsule shares the row evenly; a pressed capsule dims (no ripple) at 0.8.
|
|
166
|
+
const IOS_RADIUS = 28;
|
|
167
|
+
const IOS_CAPSULE_RADIUS = 22;
|
|
168
|
+
export const iosSkin: DialogSkin = {
|
|
169
|
+
backdrop: () => ({ borderRadius: 8, backgroundColor: alpha("#000000", 0.3) }),
|
|
170
|
+
card: (t) => ({
|
|
171
|
+
borderRadius: IOS_RADIUS,
|
|
172
|
+
backgroundColor: t.popover,
|
|
173
|
+
...shadow("lg"),
|
|
174
|
+
}),
|
|
175
|
+
title: (t) => ({
|
|
176
|
+
fontSize: 20,
|
|
177
|
+
lineHeight: 25,
|
|
178
|
+
fontWeight: "700",
|
|
179
|
+
color: t["popover-foreground"],
|
|
180
|
+
textAlign: "left",
|
|
181
|
+
}),
|
|
182
|
+
body: (t) => ({
|
|
183
|
+
fontSize: 15,
|
|
184
|
+
lineHeight: 20,
|
|
185
|
+
color: t["muted-foreground"],
|
|
186
|
+
textAlign: "left",
|
|
187
|
+
marginTop: 8,
|
|
188
|
+
}),
|
|
189
|
+
footerKind: "capsules",
|
|
190
|
+
footer: () => ({ flexDirection: "row", gap: 12, marginTop: 20 }),
|
|
191
|
+
// A capsule sized to share the row evenly. The Confirm capsule fills with the
|
|
192
|
+
// indigo `primary`; Cancel and a destructive Confirm both fill with the gray
|
|
193
|
+
// `secondary` surface (the destructive variant only reds its label).
|
|
194
|
+
capsule: (t, confirm, destructive) => ({
|
|
195
|
+
flexGrow: 1,
|
|
196
|
+
flexShrink: 1,
|
|
197
|
+
flexBasis: "0%",
|
|
198
|
+
alignItems: "center",
|
|
199
|
+
justifyContent: "center",
|
|
200
|
+
paddingVertical: 14,
|
|
201
|
+
paddingHorizontal: 16,
|
|
202
|
+
minHeight: 50,
|
|
203
|
+
borderRadius: IOS_CAPSULE_RADIUS,
|
|
204
|
+
backgroundColor: confirm && !destructive ? t.primary : t.secondary,
|
|
205
|
+
}),
|
|
206
|
+
capsuleLabel: (t, confirm, destructive) => ({
|
|
207
|
+
fontSize: 17,
|
|
208
|
+
lineHeight: 22,
|
|
209
|
+
fontWeight: "600",
|
|
210
|
+
color: destructive
|
|
211
|
+
? t.destructive
|
|
212
|
+
: confirm
|
|
213
|
+
? t["primary-foreground"]
|
|
214
|
+
: t["secondary-foreground"],
|
|
215
|
+
textAlign: "center",
|
|
216
|
+
}),
|
|
217
|
+
capsulePressedOpacity: 0.8,
|
|
218
|
+
textButton: null,
|
|
219
|
+
textButtonLabel: null,
|
|
220
|
+
textButtonRipple: null,
|
|
221
|
+
formBody: { marginTop: 16 },
|
|
222
|
+
fieldLabel: (t) => ({ fontSize: 13, lineHeight: 18, fontWeight: "500", color: t.foreground, marginBottom: 6 }),
|
|
223
|
+
fieldLabelGap: { marginTop: 14 },
|
|
224
|
+
amountRow: { flexDirection: "row", alignItems: "center" },
|
|
225
|
+
currency: (t) => ({ fontSize: 15, lineHeight: 20, color: t["muted-foreground"], marginRight: 8 }),
|
|
226
|
+
amountInput: { flexGrow: 1, flexShrink: 1, flexBasis: "0%" },
|
|
227
|
+
};
|
|
228
|
+
|
|
229
|
+
// ---------- Android (Material 3 basic dialog): 28 radius, left title, text-button row ----------
|
|
230
|
+
// M3 basic dialog: a card (28dp radius) over the `popover` ELEVATED surface (soft
|
|
231
|
+
// shadow) on a ~0.32 black scrim; a LEFT-aligned ~22sp title, a 14sp body, and
|
|
232
|
+
// TEXT-button actions (no fill) bottom-RIGHT in a row — Cancel then Confirm — in
|
|
233
|
+
// brand-indigo text, an android_ripple on each, and NO dividers.
|
|
234
|
+
const ANDROID_RADIUS = 28;
|
|
235
|
+
export const androidSkin: DialogSkin = {
|
|
236
|
+
backdrop: () => ({ borderRadius: 8, backgroundColor: alpha("#000000", 0.32) }),
|
|
237
|
+
card: (t) => ({
|
|
238
|
+
borderRadius: ANDROID_RADIUS,
|
|
239
|
+
backgroundColor: t.popover,
|
|
240
|
+
...shadow("lg"),
|
|
241
|
+
}),
|
|
242
|
+
title: (t) => ({ fontSize: 22, lineHeight: 28, fontWeight: "500", color: t["popover-foreground"] }),
|
|
243
|
+
body: (t) => ({ fontSize: 14, lineHeight: 20, color: t["muted-foreground"], marginTop: 12 }),
|
|
244
|
+
footerKind: "buttons",
|
|
245
|
+
footer: () => ({ flexDirection: "row", justifyContent: "flex-end", alignItems: "center", gap: 8, marginTop: 24 }),
|
|
246
|
+
capsule: null,
|
|
247
|
+
capsuleLabel: null,
|
|
248
|
+
capsulePressedOpacity: null,
|
|
249
|
+
// A flat text-button: no fill, comfortable touch target, rounded for the ripple.
|
|
250
|
+
textButton: {
|
|
251
|
+
alignItems: "center",
|
|
252
|
+
justifyContent: "center",
|
|
253
|
+
paddingHorizontal: 12,
|
|
254
|
+
paddingVertical: 10,
|
|
255
|
+
borderRadius: 20,
|
|
256
|
+
minHeight: 40,
|
|
257
|
+
},
|
|
258
|
+
textButtonLabel: (t, destructive) => ({
|
|
259
|
+
fontSize: 14,
|
|
260
|
+
lineHeight: 20,
|
|
261
|
+
fontWeight: "500",
|
|
262
|
+
letterSpacing: 0.1,
|
|
263
|
+
color: destructive ? t.destructive : t.primary,
|
|
264
|
+
}),
|
|
265
|
+
textButtonRipple: (t) => ({ color: alpha(t.primary, 0.12), borderless: false }),
|
|
266
|
+
formBody: { marginTop: 20 },
|
|
267
|
+
fieldLabel: (t) => ({ fontSize: 14, lineHeight: 20, fontWeight: "500", color: t.foreground, marginBottom: 6 }),
|
|
268
|
+
fieldLabelGap: { marginTop: 16 },
|
|
269
|
+
amountRow: { flexDirection: "row", alignItems: "center" },
|
|
270
|
+
currency: (t) => ({ fontSize: 14, lineHeight: 20, color: t["muted-foreground"], marginRight: 8 }),
|
|
271
|
+
amountInput: { flexGrow: 1, flexShrink: 1, flexBasis: "0%" },
|
|
272
|
+
};
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { createDialog } from "./dialog.shared.js";
|
|
2
|
+
import { webSkin } from "./dialog.styles.js";
|
|
3
|
+
|
|
4
|
+
// Web Dialog (the base; Metro falls back to it on native, web bundlers resolve it).
|
|
5
|
+
export const Dialog = createDialog(webSkin);
|
|
6
|
+
export type { DialogProps } from "./dialog.shared.js";
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
# Filter Panels
|
|
2
|
+
|
|
3
|
+
Sidebar filter rail with chip pills for active filters.
|
|
4
|
+
|
|
5
|
+
## Usage
|
|
6
|
+
|
|
7
|
+
```tsx
|
|
8
|
+
<FilterPanel
|
|
9
|
+
bordered
|
|
10
|
+
activeCount={2}
|
|
11
|
+
groups={[
|
|
12
|
+
{ title: "Status", options: [
|
|
13
|
+
{ label: "Active", checked: true, count: "128" },
|
|
14
|
+
{ label: "Pending", count: "12", checked: false },
|
|
15
|
+
{ label: "Archived", count: "2", checked: false }
|
|
16
|
+
] },
|
|
17
|
+
{ title: "Schema", options: [
|
|
18
|
+
{ label: "Default", checked: true, count: "96" },
|
|
19
|
+
{ label: "Custom", count: "46", checked: false }
|
|
20
|
+
] },
|
|
21
|
+
{ title: "MFA", options: [
|
|
22
|
+
{ label: "Enabled", count: "84", checked: false },
|
|
23
|
+
{ label: "Disabled", count: "58", checked: false }
|
|
24
|
+
] }
|
|
25
|
+
]}
|
|
26
|
+
/>
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## Do & Don't
|
|
30
|
+
|
|
31
|
+
### Sidebar
|
|
32
|
+
|
|
33
|
+
**Do** — Give each chip a × so a single filter can be removed in place, and keep it in sync with the sidebar checkbox.
|
|
34
|
+
|
|
35
|
+
```tsx
|
|
36
|
+
<View style={{ flexDirection: "row", flexWrap: "wrap", gap: 8 }}>
|
|
37
|
+
<Pressable style={{ flexDirection: "row", alignItems: "center", alignSelf: "flex-start", gap: 4, borderRadius: 9999, backgroundColor: tokens.primary, paddingHorizontal: 10, paddingVertical: 4 }}>
|
|
38
|
+
<Text style={{ fontSize: 12, lineHeight: 16, fontWeight: "500", color: tokens["primary-foreground"] }}>Active</Text>
|
|
39
|
+
<Icon x primaryForeground size={12} />
|
|
40
|
+
</Pressable>
|
|
41
|
+
<Pressable style={{ flexDirection: "row", alignItems: "center", alignSelf: "flex-start", gap: 4, borderRadius: 9999, backgroundColor: tokens.primary, paddingHorizontal: 10, paddingVertical: 4 }}>
|
|
42
|
+
<Text style={{ fontSize: 12, lineHeight: 16, fontWeight: "500", color: tokens["primary-foreground"] }}>Default</Text>
|
|
43
|
+
<Icon x primaryForeground size={12} />
|
|
44
|
+
</Pressable>
|
|
45
|
+
</View>
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
**Don't** — Active-filter chips with no remove affordance leave no way to clear one filter without hunting back through the sidebar.
|
|
49
|
+
|
|
50
|
+
```tsx
|
|
51
|
+
<View style={{ flexDirection: "row", flexWrap: "wrap", gap: 8 }}>
|
|
52
|
+
<View style={{ flexDirection: "row", alignItems: "center", alignSelf: "flex-start", borderRadius: 9999, backgroundColor: tokens.primary, paddingHorizontal: 10, paddingVertical: 4 }}>
|
|
53
|
+
<Text style={{ fontSize: 12, lineHeight: 16, fontWeight: "500", color: tokens["primary-foreground"] }}>Active</Text>
|
|
54
|
+
</View>
|
|
55
|
+
<View style={{ flexDirection: "row", alignItems: "center", alignSelf: "flex-start", borderRadius: 9999, backgroundColor: tokens.primary, paddingHorizontal: 10, paddingVertical: 4 }}>
|
|
56
|
+
<Text style={{ fontSize: 12, lineHeight: 16, fontWeight: "500", color: tokens["primary-foreground"] }}>Default</Text>
|
|
57
|
+
</View>
|
|
58
|
+
</View>
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
### Inline
|
|
62
|
+
|
|
63
|
+
**Do** — Surface two or three primary filters and tuck the rest behind "+ Add filter" so the bar stays one scannable row.
|
|
64
|
+
|
|
65
|
+
```tsx
|
|
66
|
+
<View style={{ flexDirection: "row", flexWrap: "wrap", alignItems: "center", gap: 8 }}>
|
|
67
|
+
<Pressable style={{ height: 32, flexDirection: "row", alignItems: "center", gap: 8, borderRadius: 6, backgroundColor: tokens.primary, paddingHorizontal: 12 }}>
|
|
68
|
+
<Text style={{ fontSize: 12, lineHeight: 16, fontWeight: "500", color: tokens["primary-foreground"] }}>Status</Text>
|
|
69
|
+
<Icon chevronDown primaryForeground size={12} />
|
|
70
|
+
</Pressable>
|
|
71
|
+
<Pressable style={{ height: 32, flexDirection: "row", alignItems: "center", gap: 8, borderRadius: 6, borderWidth: 1, borderColor: tokens.input, backgroundColor: tokens.background, paddingHorizontal: 12 }}>
|
|
72
|
+
<Text style={{ fontSize: 12, lineHeight: 16, fontWeight: "500", color: tokens.foreground }}>Role</Text>
|
|
73
|
+
<Icon chevronDown size={12} />
|
|
74
|
+
</Pressable>
|
|
75
|
+
<Button ghost small style={{ color: tokens.primary }}>+ Add filter</Button>
|
|
76
|
+
</View>
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
**Don't** — Eight inline dropdowns wrap into a wall of buttons, which defeats the compact bar; that volume of filtering belongs in the sidebar rail.
|
|
80
|
+
|
|
81
|
+
```tsx
|
|
82
|
+
<View style={{ flexDirection: "row", flexWrap: "wrap", alignItems: "center", gap: 8 }}>
|
|
83
|
+
<Pressable style={{ height: 32, flexDirection: "row", alignItems: "center", gap: 8, borderRadius: 6, borderWidth: 1, borderColor: tokens.input, backgroundColor: tokens.background, paddingHorizontal: 12 }}>
|
|
84
|
+
<Text style={{ fontSize: 12, lineHeight: 16, fontWeight: "500", color: tokens.foreground }}>Status</Text>
|
|
85
|
+
<Icon chevronDown size={12} />
|
|
86
|
+
</Pressable>
|
|
87
|
+
<Pressable style={{ height: 32, flexDirection: "row", alignItems: "center", gap: 8, borderRadius: 6, borderWidth: 1, borderColor: tokens.input, backgroundColor: tokens.background, paddingHorizontal: 12 }}>
|
|
88
|
+
<Text style={{ fontSize: 12, lineHeight: 16, fontWeight: "500", color: tokens.foreground }}>Role</Text>
|
|
89
|
+
<Icon chevronDown size={12} />
|
|
90
|
+
</Pressable>
|
|
91
|
+
<Pressable style={{ height: 32, flexDirection: "row", alignItems: "center", gap: 8, borderRadius: 6, borderWidth: 1, borderColor: tokens.input, backgroundColor: tokens.background, paddingHorizontal: 12 }}>
|
|
92
|
+
<Text style={{ fontSize: 12, lineHeight: 16, fontWeight: "500", color: tokens.foreground }}>Schema</Text>
|
|
93
|
+
<Icon chevronDown size={12} />
|
|
94
|
+
</Pressable>
|
|
95
|
+
<Pressable style={{ height: 32, flexDirection: "row", alignItems: "center", gap: 8, borderRadius: 6, borderWidth: 1, borderColor: tokens.input, backgroundColor: tokens.background, paddingHorizontal: 12 }}>
|
|
96
|
+
<Text style={{ fontSize: 12, lineHeight: 16, fontWeight: "500", color: tokens.foreground }}>MFA</Text>
|
|
97
|
+
<Icon chevronDown size={12} />
|
|
98
|
+
</Pressable>
|
|
99
|
+
<Pressable style={{ height: 32, flexDirection: "row", alignItems: "center", gap: 8, borderRadius: 6, borderWidth: 1, borderColor: tokens.input, backgroundColor: tokens.background, paddingHorizontal: 12 }}>
|
|
100
|
+
<Text style={{ fontSize: 12, lineHeight: 16, fontWeight: "500", color: tokens.foreground }}>Region</Text>
|
|
101
|
+
<Icon chevronDown size={12} />
|
|
102
|
+
</Pressable>
|
|
103
|
+
<Pressable style={{ height: 32, flexDirection: "row", alignItems: "center", gap: 8, borderRadius: 6, borderWidth: 1, borderColor: tokens.input, backgroundColor: tokens.background, paddingHorizontal: 12 }}>
|
|
104
|
+
<Text style={{ fontSize: 12, lineHeight: 16, fontWeight: "500", color: tokens.foreground }}>Created</Text>
|
|
105
|
+
<Icon chevronDown size={12} />
|
|
106
|
+
</Pressable>
|
|
107
|
+
<Pressable style={{ height: 32, flexDirection: "row", alignItems: "center", gap: 8, borderRadius: 6, borderWidth: 1, borderColor: tokens.input, backgroundColor: tokens.background, paddingHorizontal: 12 }}>
|
|
108
|
+
<Text style={{ fontSize: 12, lineHeight: 16, fontWeight: "500", color: tokens.foreground }}>Last seen</Text>
|
|
109
|
+
<Icon chevronDown size={12} />
|
|
110
|
+
</Pressable>
|
|
111
|
+
<Pressable style={{ height: 32, flexDirection: "row", alignItems: "center", gap: 8, borderRadius: 6, borderWidth: 1, borderColor: tokens.input, backgroundColor: tokens.background, paddingHorizontal: 12 }}>
|
|
112
|
+
<Text style={{ fontSize: 12, lineHeight: 16, fontWeight: "500", color: tokens.foreground }}>Team</Text>
|
|
113
|
+
<Icon chevronDown size={12} />
|
|
114
|
+
</Pressable>
|
|
115
|
+
</View>
|
|
116
|
+
```
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { type ViewStyle, type TextStyle } from "react-native";
|
|
2
|
+
import { type ColorTokens } from "../../style/index.js";
|
|
3
|
+
|
|
4
|
+
// Co-located FilterPanel styles. Layout-only fragments (the fixed panel width,
|
|
5
|
+
// the header/group/row rows, the density gaps and paddings) are static objects;
|
|
6
|
+
// the surface fill + border reads the active tokens (so `bordered` follows
|
|
7
|
+
// light/dark and goes translucent under glass). Density (compact vs. base) is
|
|
8
|
+
// resolved by the component and selects the matching padding/gap fragment.
|
|
9
|
+
|
|
10
|
+
export type Density = "compact" | "base";
|
|
11
|
+
|
|
12
|
+
// --- panel surface ----------------------------------------------------------
|
|
13
|
+
|
|
14
|
+
// Fixed panel width (w-[280px]) and column layout, shared by both surfaces.
|
|
15
|
+
export const panelBase: ViewStyle = { width: 280, flexDirection: "column" };
|
|
16
|
+
|
|
17
|
+
// `bordered` wraps the panel as a rounded card: a border on the card fill. The
|
|
18
|
+
// card token goes translucent under glass, so don't hardcode its hex.
|
|
19
|
+
export function borderedSurface(tokens: ColorTokens): ViewStyle {
|
|
20
|
+
return { borderRadius: 8, borderWidth: 1, borderColor: tokens.border, backgroundColor: tokens.card };
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// Panel inset per density (p-3 compact / p-4 base).
|
|
24
|
+
export const panelPad: Record<Density, ViewStyle> = {
|
|
25
|
+
compact: { padding: 12 },
|
|
26
|
+
base: { padding: 16 },
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
// Vertical rhythm between the header and groups, and between groups
|
|
30
|
+
// (gap-3 compact / gap-5 base).
|
|
31
|
+
export const panelStack: Record<Density, ViewStyle> = {
|
|
32
|
+
compact: { gap: 12 },
|
|
33
|
+
base: { gap: 20 },
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
// Row spacing inside a group, and between a group's title and its options
|
|
37
|
+
// (gap-1.5 compact / gap-2 base). Shared by the group column and its option list.
|
|
38
|
+
export const groupGap: Record<Density, ViewStyle> = {
|
|
39
|
+
compact: { gap: 6 },
|
|
40
|
+
base: { gap: 8 },
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
// --- header -----------------------------------------------------------------
|
|
44
|
+
|
|
45
|
+
// Header row: title cluster on the left, the Clear action on the right.
|
|
46
|
+
export const headerRow: ViewStyle = {
|
|
47
|
+
flexDirection: "row",
|
|
48
|
+
alignItems: "center",
|
|
49
|
+
justifyContent: "space-between",
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
// Title + active-count cluster (a row with a small gap).
|
|
53
|
+
export const titleCluster: ViewStyle = { flexDirection: "row", alignItems: "center", gap: 8 };
|
|
54
|
+
|
|
55
|
+
// "Filters" heading: small, semibold, on the foreground token.
|
|
56
|
+
export function titleText(tokens: ColorTokens): TextStyle {
|
|
57
|
+
return { fontSize: 14, lineHeight: 20, fontWeight: "600", color: tokens.foreground };
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// --- groups -----------------------------------------------------------------
|
|
61
|
+
|
|
62
|
+
// A group column (the title above its option list).
|
|
63
|
+
export const groupColumn: ViewStyle = { flexDirection: "column" };
|
|
64
|
+
|
|
65
|
+
// Group heading: extra-small, medium, uppercase, wide tracking, muted.
|
|
66
|
+
export function groupTitle(tokens: ColorTokens): TextStyle {
|
|
67
|
+
return {
|
|
68
|
+
fontSize: 12,
|
|
69
|
+
lineHeight: 16,
|
|
70
|
+
fontWeight: "500",
|
|
71
|
+
textTransform: "uppercase",
|
|
72
|
+
letterSpacing: 0.4,
|
|
73
|
+
color: tokens["muted-foreground"],
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// One option row: checkbox on the left, optional count badge on the right.
|
|
78
|
+
export const optionRow: ViewStyle = {
|
|
79
|
+
flexDirection: "row",
|
|
80
|
+
alignItems: "center",
|
|
81
|
+
justifyContent: "space-between",
|
|
82
|
+
gap: 8,
|
|
83
|
+
};
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import { View, Text, useTheme, type StyleProp, type ViewStyle } from "../../style/index.js";
|
|
2
|
+
import { Badge } from "../../atoms/badge/badge.js";
|
|
3
|
+
import { Button } from "../../atoms/button/button.js";
|
|
4
|
+
import { Checkbox } from "../../atoms/checkbox/checkbox.js";
|
|
5
|
+
import * as s from "./filter-panel.styles.js";
|
|
6
|
+
|
|
7
|
+
export interface FilterOption {
|
|
8
|
+
/** Row label, shown beside the checkbox. */
|
|
9
|
+
label: string;
|
|
10
|
+
/** Controlled checked state for this option. */
|
|
11
|
+
checked?: boolean;
|
|
12
|
+
/** Optional trailing count, rendered as a secondary badge. */
|
|
13
|
+
count?: string;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export interface FilterGroup {
|
|
17
|
+
/** Group heading, rendered uppercase and muted. */
|
|
18
|
+
title: string;
|
|
19
|
+
/** The checkbox options under this group. */
|
|
20
|
+
options: FilterOption[];
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export interface FilterPanelProps {
|
|
24
|
+
/** Filter groups, each a heading plus its checkbox options. */
|
|
25
|
+
groups: FilterGroup[];
|
|
26
|
+
/** Active-filter count shown next to the "Filters" title in the header. */
|
|
27
|
+
activeCount?: number;
|
|
28
|
+
/** Fired when the header "Clear" action is pressed. */
|
|
29
|
+
onClear?: () => void;
|
|
30
|
+
/** Fired when an option row toggles, with its group/option indexes and next value. */
|
|
31
|
+
onChange?: (groupIndex: number, optionIndex: number, next: boolean) => void;
|
|
32
|
+
// Surface (pick one path): a rounded, bordered card vs. a bare panel.
|
|
33
|
+
bordered?: boolean;
|
|
34
|
+
// Density (pick one): tighten the panel's padding and row spacing.
|
|
35
|
+
compact?: boolean;
|
|
36
|
+
/** Escape hatch for layout/positioning composition (mainly width, margins). */
|
|
37
|
+
style?: StyleProp<ViewStyle>;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// Density precedence when more than one is passed: first match wins. There is a
|
|
41
|
+
// single density flag today, so this collapses to compact vs. the default.
|
|
42
|
+
function densityOf(p: FilterPanelProps): s.Density {
|
|
43
|
+
if (p.compact) return "compact";
|
|
44
|
+
return "base";
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export function FilterPanel(props: FilterPanelProps) {
|
|
48
|
+
const { groups, activeCount, onClear, onChange, bordered, style } = props;
|
|
49
|
+
const { tokens } = useTheme();
|
|
50
|
+
const density = densityOf(props);
|
|
51
|
+
|
|
52
|
+
return (
|
|
53
|
+
<View
|
|
54
|
+
style={[
|
|
55
|
+
s.panelBase,
|
|
56
|
+
// `bordered` wraps it as a rounded card with a border and a card fill;
|
|
57
|
+
// the bare panel keeps the same width but drops the chrome.
|
|
58
|
+
bordered ? s.borderedSurface(tokens) : null,
|
|
59
|
+
s.panelPad[density],
|
|
60
|
+
s.panelStack[density],
|
|
61
|
+
style,
|
|
62
|
+
]}
|
|
63
|
+
>
|
|
64
|
+
<View style={s.headerRow}>
|
|
65
|
+
<View style={s.titleCluster}>
|
|
66
|
+
<Text style={s.titleText(tokens)}>Filters</Text>
|
|
67
|
+
{activeCount != null ? <Badge secondary>{String(activeCount)}</Badge> : null}
|
|
68
|
+
</View>
|
|
69
|
+
<Button ghost small onPress={onClear}>
|
|
70
|
+
Clear
|
|
71
|
+
</Button>
|
|
72
|
+
</View>
|
|
73
|
+
|
|
74
|
+
{groups.map((group, gi) => (
|
|
75
|
+
<View key={gi} style={[s.groupColumn, s.groupGap[density]]}>
|
|
76
|
+
<Text style={s.groupTitle(tokens)}>{group.title}</Text>
|
|
77
|
+
<View style={[s.groupColumn, s.groupGap[density]]}>
|
|
78
|
+
{group.options.map((option, oi) => (
|
|
79
|
+
<View key={oi} style={s.optionRow}>
|
|
80
|
+
<Checkbox checked={option.checked} onChange={(next) => onChange?.(gi, oi, next)}>
|
|
81
|
+
{option.label}
|
|
82
|
+
</Checkbox>
|
|
83
|
+
{option.count != null ? <Badge secondary>{option.count}</Badge> : null}
|
|
84
|
+
</View>
|
|
85
|
+
))}
|
|
86
|
+
</View>
|
|
87
|
+
</View>
|
|
88
|
+
))}
|
|
89
|
+
</View>
|
|
90
|
+
);
|
|
91
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
// Organisms: the React Native UI kit components at the organisms atomic level.
|
|
2
|
+
export * from "./calendar/calendar.js";
|
|
3
|
+
export * from "./charts/charts.js";
|
|
4
|
+
export * from "./command/command.js";
|
|
5
|
+
export * from "./data-table/data-table.js";
|
|
6
|
+
export * from "./dialog/dialog.js";
|
|
7
|
+
export * from "./filter-panel/filter-panel.js";
|
|
8
|
+
export * from "./navbars/navbars.js";
|
|
9
|
+
export * from "./overlays/overlays.js";
|
|
10
|
+
export * from "./row-menu/row-menu.js";
|
|
11
|
+
export * from "./sidebar/sidebar.js";
|
|
12
|
+
export * from "./stepper/stepper.js";
|
|
13
|
+
export * from "./tabs/tabs.js";
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { createNavbar } from "./navbars.shared.js";
|
|
2
|
+
import { androidSkin } from "./navbars.styles.js";
|
|
3
|
+
|
|
4
|
+
// Material 3 (top app bar) Navbar. Metro resolves this file on Android; the docs import it for preview.
|
|
5
|
+
export const Navbar = createNavbar(androidSkin);
|
|
6
|
+
export type { NavbarProps } from "./navbars.shared.js";
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { createNavbar } from "./navbars.shared.js";
|
|
2
|
+
import { iosSkin } from "./navbars.styles.js";
|
|
3
|
+
|
|
4
|
+
// iOS (HIG navigation bar) Navbar. Metro resolves this file on iOS; the docs import it for preview.
|
|
5
|
+
export const Navbar = createNavbar(iosSkin);
|
|
6
|
+
export type { NavbarProps } from "./navbars.shared.js";
|