@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,175 @@
|
|
|
1
|
+
import { useState } from "react";
|
|
2
|
+
import { View, Text, Pressable, useTheme, type StyleProp, type ViewStyle } from "../../style/index.js";
|
|
3
|
+
import { Button } from "../../atoms/button/button.js";
|
|
4
|
+
import { Icon } from "../../atoms/icon/icon.js";
|
|
5
|
+
import * as s from "./overlays.styles.js";
|
|
6
|
+
import { type Placement, type OverlaySkin } from "./overlays.styles.js";
|
|
7
|
+
|
|
8
|
+
// Shared Overlay shell. The structure (the optional trigger plus a CONTAINED dim
|
|
9
|
+
// backdrop holding the surface positioned per placement), the public
|
|
10
|
+
// boolean-prop API, the placement precedence, the controlled/uncontrolled open
|
|
11
|
+
// state, the trigger/Done handlers, and the footer action all live here once. A
|
|
12
|
+
// platform file supplies only its skin (the surface shape/fill/sizing and the
|
|
13
|
+
// grabber/drag handle) and calls createOverlay.
|
|
14
|
+
//
|
|
15
|
+
// Overlay: a dim backdrop with a surface anchored to a side or the center,
|
|
16
|
+
// rendered INLINE for the docs preview. Rather than a full-screen portal/Modal,
|
|
17
|
+
// this is a CONTAINED backdrop View (a rounded, clipped scrim with explicit
|
|
18
|
+
// presence in the preview) that holds the overlay surface positioned inside it:
|
|
19
|
+
// a right-side drawer, a bottom sheet, or a centered modal/panel.
|
|
20
|
+
//
|
|
21
|
+
// Boolean-prop API: one boolean per option, grouped by axis, first-match
|
|
22
|
+
// precedence within an axis (mirrors Dialog/Button). Placement axis (pick one):
|
|
23
|
+
//
|
|
24
|
+
// - `drawer`: a panel anchored to the right edge, full height (the default).
|
|
25
|
+
// - `sheet`: a panel anchored to the bottom edge, spanning the width.
|
|
26
|
+
// - `modal`: a centered card floating over the backdrop.
|
|
27
|
+
|
|
28
|
+
export interface OverlayProps {
|
|
29
|
+
// Content (strings).
|
|
30
|
+
title?: string;
|
|
31
|
+
description?: string;
|
|
32
|
+
// Trigger button label. When set, the overlay renders the button and opens
|
|
33
|
+
// itself on press (uncontrolled). Omit when you drive `open` yourself.
|
|
34
|
+
trigger?: string;
|
|
35
|
+
// Controlled open state. Omit for uncontrolled (the trigger opens it).
|
|
36
|
+
open?: boolean;
|
|
37
|
+
// Fired when the open state changes (trigger press, done).
|
|
38
|
+
onOpenChange?: (open: boolean) => void;
|
|
39
|
+
// Placement (pick one; first match wins, default is the right-side drawer).
|
|
40
|
+
drawer?: boolean;
|
|
41
|
+
sheet?: boolean;
|
|
42
|
+
modal?: boolean;
|
|
43
|
+
// Footer action label.
|
|
44
|
+
doneLabel?: string;
|
|
45
|
+
// Action handler.
|
|
46
|
+
onDone?: () => void;
|
|
47
|
+
/** Escape hatch for layout/positioning composition on the overlay surface. */
|
|
48
|
+
style?: StyleProp<ViewStyle>;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// Placement precedence when more than one is passed: first match wins.
|
|
52
|
+
function placementOf(p: OverlayProps): Placement {
|
|
53
|
+
if (p.drawer) return "drawer";
|
|
54
|
+
if (p.sheet) return "sheet";
|
|
55
|
+
if (p.modal) return "modal";
|
|
56
|
+
return "drawer";
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/** Build an Overlay component from a platform skin. */
|
|
60
|
+
export function createOverlay(skin: OverlaySkin) {
|
|
61
|
+
return function Overlay(props: OverlayProps) {
|
|
62
|
+
const { title, description, trigger, open: openProp, onOpenChange, doneLabel = "Done", onDone, style } = props;
|
|
63
|
+
const { tokens } = useTheme();
|
|
64
|
+
|
|
65
|
+
// Uncontrolled by default: the trigger opens the overlay and Done closes it;
|
|
66
|
+
// a controlled `open` prop overrides this.
|
|
67
|
+
const [internalOpen, setInternalOpen] = useState(false);
|
|
68
|
+
const open = openProp ?? internalOpen;
|
|
69
|
+
const setOpen = (next: boolean) => {
|
|
70
|
+
if (openProp === undefined) setInternalOpen(next);
|
|
71
|
+
onOpenChange?.(next);
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
const placement = placementOf(props);
|
|
75
|
+
|
|
76
|
+
// The grabber/drag handle renders on the `sheet` placement only, and only
|
|
77
|
+
// when the platform skin draws one (iOS/Android, not the web).
|
|
78
|
+
const showHandle = placement === "sheet" && skin.handle != null;
|
|
79
|
+
|
|
80
|
+
// The iOS 27 sheet header toolbar (leading circular close, centered title,
|
|
81
|
+
// trailing circular action) renders on the `sheet` placement only, and only
|
|
82
|
+
// when the platform skin supplies the header pieces (iOS, not web/Android).
|
|
83
|
+
// Web/Android leave `sheetHeader` undefined and keep the title/description/
|
|
84
|
+
// footer stack below unchanged.
|
|
85
|
+
const showSheetHeader =
|
|
86
|
+
placement === "sheet" && skin.sheetHeader != null && skin.headerButton != null;
|
|
87
|
+
|
|
88
|
+
const close = () => setOpen(false);
|
|
89
|
+
const done = () => {
|
|
90
|
+
onDone?.();
|
|
91
|
+
setOpen(false);
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
// Optional trigger button plus the overlay. The overlay is a contained dim
|
|
95
|
+
// backdrop: a rounded, clipped scrim with explicit presence in the preview
|
|
96
|
+
// (minHeight) so the surface reads as an overlay within the area.
|
|
97
|
+
return (
|
|
98
|
+
<View style={s.root}>
|
|
99
|
+
{trigger != null ? (
|
|
100
|
+
<View style={s.triggerWrap}>
|
|
101
|
+
<Button outline small onPress={() => setOpen(true)}>
|
|
102
|
+
{trigger}
|
|
103
|
+
</Button>
|
|
104
|
+
</View>
|
|
105
|
+
) : null}
|
|
106
|
+
{open ? (
|
|
107
|
+
<View
|
|
108
|
+
style={[
|
|
109
|
+
trigger != null ? s.backdropWithTrigger : null,
|
|
110
|
+
s.backdrop(),
|
|
111
|
+
s.backdropPlacement[placement],
|
|
112
|
+
{ minHeight: 280 },
|
|
113
|
+
]}
|
|
114
|
+
>
|
|
115
|
+
<View style={[skin.surface(tokens, placement), style]}>
|
|
116
|
+
{showHandle ? (
|
|
117
|
+
<View style={s.handleWrap}>
|
|
118
|
+
<View style={skin.handle!(tokens)} />
|
|
119
|
+
</View>
|
|
120
|
+
) : null}
|
|
121
|
+
{showSheetHeader ? (
|
|
122
|
+
// iOS 27 sheet header toolbar: leading circular close (X), centered
|
|
123
|
+
// title, trailing circular accent action. The title centers in the
|
|
124
|
+
// row; the side controls are equal-width slots so it stays centered.
|
|
125
|
+
<View style={skin.sheetHeader}>
|
|
126
|
+
<Pressable
|
|
127
|
+
accessibilityRole="button"
|
|
128
|
+
accessibilityLabel="Close"
|
|
129
|
+
onPress={close}
|
|
130
|
+
style={({ pressed }) => [
|
|
131
|
+
skin.headerButton!(tokens),
|
|
132
|
+
pressed ? { opacity: 0.8 } : null,
|
|
133
|
+
]}
|
|
134
|
+
>
|
|
135
|
+
<Icon x muted size={20} />
|
|
136
|
+
</Pressable>
|
|
137
|
+
<View style={s.sheetHeaderTitleWrap}>
|
|
138
|
+
{title != null ? (
|
|
139
|
+
<Text numberOfLines={1} style={(skin.headerTitle ?? skin.title)(tokens)}>
|
|
140
|
+
{title}
|
|
141
|
+
</Text>
|
|
142
|
+
) : null}
|
|
143
|
+
</View>
|
|
144
|
+
<Pressable
|
|
145
|
+
accessibilityRole="button"
|
|
146
|
+
accessibilityLabel={doneLabel}
|
|
147
|
+
onPress={done}
|
|
148
|
+
style={({ pressed }) => [
|
|
149
|
+
(skin.headerButtonAccent ?? skin.headerButton)!(tokens),
|
|
150
|
+
pressed ? { opacity: 0.8 } : null,
|
|
151
|
+
]}
|
|
152
|
+
>
|
|
153
|
+
<Icon upload primaryForeground size={20} />
|
|
154
|
+
</Pressable>
|
|
155
|
+
</View>
|
|
156
|
+
) : (
|
|
157
|
+
<>
|
|
158
|
+
{title != null ? <Text style={skin.title(tokens)}>{title}</Text> : null}
|
|
159
|
+
{description != null ? (
|
|
160
|
+
<Text style={skin.description(tokens)}>{description}</Text>
|
|
161
|
+
) : null}
|
|
162
|
+
<View style={skin.footer}>
|
|
163
|
+
<Button primary small onPress={done}>
|
|
164
|
+
{doneLabel}
|
|
165
|
+
</Button>
|
|
166
|
+
</View>
|
|
167
|
+
</>
|
|
168
|
+
)}
|
|
169
|
+
</View>
|
|
170
|
+
</View>
|
|
171
|
+
) : null}
|
|
172
|
+
</View>
|
|
173
|
+
);
|
|
174
|
+
};
|
|
175
|
+
}
|
|
@@ -0,0 +1,309 @@
|
|
|
1
|
+
import { type ViewStyle, type TextStyle } from "react-native";
|
|
2
|
+
import { type ColorTokens, shadow, alpha } from "../../style/index.js";
|
|
3
|
+
|
|
4
|
+
// Co-located Overlay 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` Done action stays the same);
|
|
8
|
+
// only the native SHAPE, sizing, surface treatment, and the grabber/handle change
|
|
9
|
+
// per OS:
|
|
10
|
+
// iOS (iOS 27 / Liquid Glass sheet): a sheet with the TOP corners rounded ~38
|
|
11
|
+
// presenting from the bottom (flat bottom), a small grabber handle (a ~36x5
|
|
12
|
+
// rounded `muted-foreground` pill) centered at the top, and an iOS 27 header
|
|
13
|
+
// toolbar directly below it: a leading CIRCULAR tinted button holding the close
|
|
14
|
+
// (X) glyph, a centered title, and a trailing CIRCULAR action button (a filled
|
|
15
|
+
// `primary` accent circle holding the action glyph) — both are ~40pt circles,
|
|
16
|
+
// not bare icons. The centered `modal` becomes a rounded form-sheet card
|
|
17
|
+
// (~13 radius). Press feedback on the surface is none (the surface itself is
|
|
18
|
+
// not pressable); the circular header buttons keep the Button's iOS press =
|
|
19
|
+
// opacity dim.
|
|
20
|
+
// Android (Material 3 bottom sheet): the `sheet` has its TOP corners rounded 28
|
|
21
|
+
// with a centered drag handle (~32x4 rounded `muted-foreground` pill), the
|
|
22
|
+
// `popover` surface, and M3 elevation; the `drawer` and `modal` keep the M3
|
|
23
|
+
// low-radius/elevated treatment.
|
|
24
|
+
// Web: the established Canvas look (the current overlay, lifted verbatim) — the
|
|
25
|
+
// contained dim scrim with the drawer (right, full height, border-l), sheet
|
|
26
|
+
// (bottom, rounded-t-lg, border-t), and modal (centered card, rounded-lg
|
|
27
|
+
// border) surfaces, each `popover` filled with a border and shadow-xl.
|
|
28
|
+
|
|
29
|
+
export type Placement = "drawer" | "sheet" | "modal";
|
|
30
|
+
|
|
31
|
+
// The contract a platform skin fulfills. The shell renders the contained dim
|
|
32
|
+
// backdrop, the surface positioned inside it per placement, an optional top
|
|
33
|
+
// grabber/drag handle (sheet only), the title, the description, and the footer
|
|
34
|
+
// action row; the skin maps the active platform's shape/fill/sizing onto each
|
|
35
|
+
// piece. `backdrop` and the contents type styles read the tokens so light/dark/
|
|
36
|
+
// glass keep working.
|
|
37
|
+
export interface OverlaySkin {
|
|
38
|
+
/** The overlay surface per placement: shape, fill, border, padding, shadow. */
|
|
39
|
+
surface: (t: ColorTokens, placement: Placement) => ViewStyle;
|
|
40
|
+
/** The top grabber/drag handle rendered on the `sheet` placement only. Null on
|
|
41
|
+
* platforms that do not draw one (web). The shell centers it; this is the pill. */
|
|
42
|
+
handle: ((t: ColorTokens) => ViewStyle) | null;
|
|
43
|
+
/** The title type/color. */
|
|
44
|
+
title: (t: ColorTokens) => TextStyle;
|
|
45
|
+
/** The description type/color. */
|
|
46
|
+
description: (t: ColorTokens) => TextStyle;
|
|
47
|
+
/** The footer action row layout. */
|
|
48
|
+
footer: ViewStyle;
|
|
49
|
+
|
|
50
|
+
// --- iOS 27 sheet header (optional; iOS skin only) ------------------------
|
|
51
|
+
// When a skin supplies `sheetHeader`, the shell renders an iOS 27 sheet header
|
|
52
|
+
// toolbar on the `sheet` placement instead of the title/description/footer
|
|
53
|
+
// stack: a leading CIRCULAR close (X) button, a centered title, and a trailing
|
|
54
|
+
// CIRCULAR action button. Web/Android leave these undefined and keep the plain
|
|
55
|
+
// title/description/footer layout byte-for-byte.
|
|
56
|
+
|
|
57
|
+
/** The header toolbar row (3-column: leading circle, centered title, trailing
|
|
58
|
+
* circle). Present only on platforms that draw the iOS 27 sheet header. */
|
|
59
|
+
sheetHeader?: ViewStyle;
|
|
60
|
+
/** The circular tinted header button (a ~40pt `muted` circle behind a glyph). */
|
|
61
|
+
headerButton?: (t: ColorTokens) => ViewStyle;
|
|
62
|
+
/** The filled-accent trailing header button (a ~40pt `primary` circle). */
|
|
63
|
+
headerButtonAccent?: (t: ColorTokens) => ViewStyle;
|
|
64
|
+
/** The centered header title type/color (the iOS 27 sheet navigation title). */
|
|
65
|
+
headerTitle?: (t: ColorTokens) => TextStyle;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// --- outer wrapper + trigger (identical across platforms) -------------------
|
|
69
|
+
|
|
70
|
+
// w-full: the overlay occupies the full width of its slot.
|
|
71
|
+
export const root: ViewStyle = { width: "100%" };
|
|
72
|
+
|
|
73
|
+
// self-start: the trigger button hugs the leading edge.
|
|
74
|
+
export const triggerWrap: ViewStyle = { alignSelf: "flex-start" };
|
|
75
|
+
|
|
76
|
+
// --- backdrop (identical across platforms) ----------------------------------
|
|
77
|
+
|
|
78
|
+
// The contained dim scrim: relative w-full overflow-hidden rounded-lg bg-black/50.
|
|
79
|
+
// (mt-3 is applied conditionally by the shell when a trigger is present.)
|
|
80
|
+
export function backdrop(): ViewStyle {
|
|
81
|
+
return {
|
|
82
|
+
position: "relative",
|
|
83
|
+
width: "100%",
|
|
84
|
+
overflow: "hidden",
|
|
85
|
+
borderRadius: 8,
|
|
86
|
+
backgroundColor: alpha("#000000", 0.5),
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// mt-3: gap below the trigger button, applied only when a trigger is rendered.
|
|
91
|
+
export const backdropWithTrigger: ViewStyle = { marginTop: 12 };
|
|
92
|
+
|
|
93
|
+
// The backdrop's alignment per placement. The modal centers its card; the
|
|
94
|
+
// drawer and sheet let the absolutely-positioned surface anchor itself.
|
|
95
|
+
export const backdropPlacement: Record<Placement, ViewStyle> = {
|
|
96
|
+
drawer: {},
|
|
97
|
+
sheet: {},
|
|
98
|
+
// items-center justify-center
|
|
99
|
+
modal: { alignItems: "center", justifyContent: "center" },
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
// The grabber/handle row: centered at the top of the sheet surface. The skin
|
|
103
|
+
// supplies the pill dimensions/color; this centers it and spaces it from the
|
|
104
|
+
// content below.
|
|
105
|
+
export const handleWrap: ViewStyle = { alignItems: "center", marginBottom: 12 };
|
|
106
|
+
|
|
107
|
+
// The centered title slot in the iOS 27 sheet header toolbar: it flexes to fill
|
|
108
|
+
// the space between the two equal-size circular controls so the title stays
|
|
109
|
+
// optically centered in the row. Used by the shell only when the active skin
|
|
110
|
+
// draws the iOS sheet header.
|
|
111
|
+
export const sheetHeaderTitleWrap: ViewStyle = { flex: 1, alignItems: "center", paddingHorizontal: 8 };
|
|
112
|
+
|
|
113
|
+
// --- shared surface fragments -----------------------------------------------
|
|
114
|
+
|
|
115
|
+
// The drawer anchors to the right edge, full height. Shared anchoring; the skin
|
|
116
|
+
// owns fill/border/radius/padding/shadow.
|
|
117
|
+
const DRAWER_ANCHOR: ViewStyle = { position: "absolute", top: 0, right: 0, bottom: 0, width: 300 };
|
|
118
|
+
|
|
119
|
+
// The sheet anchors to the bottom edge, spanning the width. Shared anchoring.
|
|
120
|
+
const SHEET_ANCHOR: ViewStyle = { position: "absolute", left: 0, right: 0, bottom: 0 };
|
|
121
|
+
|
|
122
|
+
// The modal is a self-centered floating card.
|
|
123
|
+
const MODAL_BOX: ViewStyle = { alignSelf: "center", marginVertical: 48, width: "100%", maxWidth: 420 };
|
|
124
|
+
|
|
125
|
+
// ---------- Web: the established Canvas look (lifted verbatim) ----------
|
|
126
|
+
// The current overlay surfaces: the drawer (absolute top-0 right-0 bottom-0
|
|
127
|
+
// w-[300px] bg-popover border-l border-border p-5 shadow-xl), the sheet (absolute
|
|
128
|
+
// left-0 right-0 bottom-0 bg-popover border-t border-border rounded-t-lg p-5
|
|
129
|
+
// shadow-xl), and the modal (self-center my-12 w-full max-w-[420px] bg-popover
|
|
130
|
+
// border border-border rounded-lg p-6 shadow-xl). No grabber handle on the web.
|
|
131
|
+
export const webSkin: OverlaySkin = {
|
|
132
|
+
surface: (t, placement) => {
|
|
133
|
+
switch (placement) {
|
|
134
|
+
case "drawer":
|
|
135
|
+
return {
|
|
136
|
+
...DRAWER_ANCHOR,
|
|
137
|
+
backgroundColor: t.popover,
|
|
138
|
+
borderLeftWidth: 1,
|
|
139
|
+
borderColor: t.border,
|
|
140
|
+
padding: 20,
|
|
141
|
+
...shadow("xl"),
|
|
142
|
+
};
|
|
143
|
+
case "sheet":
|
|
144
|
+
return {
|
|
145
|
+
...SHEET_ANCHOR,
|
|
146
|
+
backgroundColor: t.popover,
|
|
147
|
+
borderTopWidth: 1,
|
|
148
|
+
borderColor: t.border,
|
|
149
|
+
borderTopLeftRadius: 8,
|
|
150
|
+
borderTopRightRadius: 8,
|
|
151
|
+
padding: 20,
|
|
152
|
+
...shadow("xl"),
|
|
153
|
+
};
|
|
154
|
+
case "modal":
|
|
155
|
+
return {
|
|
156
|
+
...MODAL_BOX,
|
|
157
|
+
backgroundColor: t.popover,
|
|
158
|
+
borderWidth: 1,
|
|
159
|
+
borderColor: t.border,
|
|
160
|
+
borderRadius: 8,
|
|
161
|
+
padding: 24,
|
|
162
|
+
...shadow("xl"),
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
},
|
|
166
|
+
handle: null,
|
|
167
|
+
// text-base font-semibold text-popover-foreground
|
|
168
|
+
title: (t) => ({ fontSize: 16, lineHeight: 24, fontWeight: "600", color: t["popover-foreground"] }),
|
|
169
|
+
// text-sm text-muted-foreground mt-2
|
|
170
|
+
description: (t) => ({ fontSize: 14, lineHeight: 20, color: t["muted-foreground"], marginTop: 8 }),
|
|
171
|
+
// flex-row justify-end mt-6
|
|
172
|
+
footer: { flexDirection: "row", justifyContent: "flex-end", marginTop: 24 },
|
|
173
|
+
};
|
|
174
|
+
|
|
175
|
+
// ---------- iOS 27 (Liquid Glass sheet): top corners ~38, grabber + header toolbar ----------
|
|
176
|
+
// The iOS 27 sheet presents from the bottom with the TOP corners rounded ~38 (the
|
|
177
|
+
// large continuous-corner radius of the Liquid Glass sheet) and a flat bottom; a
|
|
178
|
+
// small grabber (a ~36x5 rounded muted-foreground pill) is centered at the top,
|
|
179
|
+
// and an iOS 27 header toolbar sits directly below it: a leading CIRCULAR close (X)
|
|
180
|
+
// button, a centered title, and a trailing CIRCULAR action button (a filled
|
|
181
|
+
// `primary` accent circle). The right `drawer` keeps the lineless rounded-left
|
|
182
|
+
// treatment; the centered `modal` becomes a rounded form-sheet card (~13 radius).
|
|
183
|
+
// The iOS sheet is lineless: no visible border, a soft elevation.
|
|
184
|
+
const IOS_SHEET_RADIUS = 38;
|
|
185
|
+
const IOS_CARD_RADIUS = 13;
|
|
186
|
+
// The header toolbar's circular control: a ~40pt circle behind the glyph.
|
|
187
|
+
const IOS_HEADER_BUTTON = 40;
|
|
188
|
+
export const iosSkin: OverlaySkin = {
|
|
189
|
+
surface: (t, placement) => {
|
|
190
|
+
switch (placement) {
|
|
191
|
+
case "drawer":
|
|
192
|
+
return {
|
|
193
|
+
...DRAWER_ANCHOR,
|
|
194
|
+
backgroundColor: t.popover,
|
|
195
|
+
// The leading edge rounds (the panel slides in from the right edge).
|
|
196
|
+
borderTopLeftRadius: IOS_SHEET_RADIUS,
|
|
197
|
+
borderBottomLeftRadius: IOS_SHEET_RADIUS,
|
|
198
|
+
padding: 20,
|
|
199
|
+
...shadow("xl"),
|
|
200
|
+
};
|
|
201
|
+
case "sheet":
|
|
202
|
+
return {
|
|
203
|
+
...SHEET_ANCHOR,
|
|
204
|
+
backgroundColor: t.popover,
|
|
205
|
+
// Top corners rounded ~38, flat bottom (presents from the bottom edge).
|
|
206
|
+
borderTopLeftRadius: IOS_SHEET_RADIUS,
|
|
207
|
+
borderTopRightRadius: IOS_SHEET_RADIUS,
|
|
208
|
+
paddingHorizontal: 16,
|
|
209
|
+
// A little more top padding to seat the grabber above the header toolbar.
|
|
210
|
+
paddingTop: 8,
|
|
211
|
+
paddingBottom: 24,
|
|
212
|
+
...shadow("xl"),
|
|
213
|
+
};
|
|
214
|
+
case "modal":
|
|
215
|
+
return {
|
|
216
|
+
...MODAL_BOX,
|
|
217
|
+
// The iOS form sheet is a rounded card, lineless.
|
|
218
|
+
backgroundColor: t.popover,
|
|
219
|
+
borderRadius: IOS_CARD_RADIUS,
|
|
220
|
+
padding: 24,
|
|
221
|
+
...shadow("xl"),
|
|
222
|
+
};
|
|
223
|
+
}
|
|
224
|
+
},
|
|
225
|
+
// The grabber: a ~36x5 rounded muted-foreground pill centered at the top.
|
|
226
|
+
handle: (t) => ({ width: 36, height: 5, borderRadius: 2.5, backgroundColor: t["muted-foreground"] }),
|
|
227
|
+
// SF-scale title: 17pt semibold (the iOS body emphasized).
|
|
228
|
+
title: (t) => ({ fontSize: 17, lineHeight: 22, fontWeight: "600", color: t["popover-foreground"] }),
|
|
229
|
+
// 15pt secondary text.
|
|
230
|
+
description: (t) => ({ fontSize: 15, lineHeight: 20, color: t["muted-foreground"], marginTop: 8 }),
|
|
231
|
+
footer: { flexDirection: "row", justifyContent: "flex-end", marginTop: 24 },
|
|
232
|
+
// iOS 27 sheet header toolbar: a 3-column row seating the leading/trailing
|
|
233
|
+
// circular controls and the centered title, just below the grabber.
|
|
234
|
+
sheetHeader: { flexDirection: "row", alignItems: "center", marginBottom: 12 },
|
|
235
|
+
// The circular tinted header button: a ~40pt `muted` circle centering its glyph.
|
|
236
|
+
headerButton: (t) => ({
|
|
237
|
+
width: IOS_HEADER_BUTTON,
|
|
238
|
+
height: IOS_HEADER_BUTTON,
|
|
239
|
+
borderRadius: IOS_HEADER_BUTTON / 2,
|
|
240
|
+
backgroundColor: t.muted,
|
|
241
|
+
alignItems: "center",
|
|
242
|
+
justifyContent: "center",
|
|
243
|
+
}),
|
|
244
|
+
// The filled-accent trailing header button: a ~40pt brand-`primary` circle.
|
|
245
|
+
headerButtonAccent: (t) => ({
|
|
246
|
+
width: IOS_HEADER_BUTTON,
|
|
247
|
+
height: IOS_HEADER_BUTTON,
|
|
248
|
+
borderRadius: IOS_HEADER_BUTTON / 2,
|
|
249
|
+
backgroundColor: t.primary,
|
|
250
|
+
alignItems: "center",
|
|
251
|
+
justifyContent: "center",
|
|
252
|
+
}),
|
|
253
|
+
// The centered header title: 17pt semibold, the iOS 27 sheet navigation title.
|
|
254
|
+
headerTitle: (t) => ({ fontSize: 17, lineHeight: 22, fontWeight: "600", color: t["popover-foreground"] }),
|
|
255
|
+
};
|
|
256
|
+
|
|
257
|
+
// ---------- Android (Material 3 bottom sheet): top corners 28, drag handle ----------
|
|
258
|
+
// M3 modal bottom sheet: the `sheet` has its TOP corners rounded 28 (the M3
|
|
259
|
+
// extra-large shape) with a centered drag handle (a ~32x4 rounded
|
|
260
|
+
// muted-foreground pill), the `popover` surface, and M3 elevation. The right
|
|
261
|
+
// `drawer` (navigation-drawer-like) keeps a large trailing radius; the centered
|
|
262
|
+
// `modal` is the M3 dialog shape (28 radius, elevated, no border).
|
|
263
|
+
const ANDROID_SHEET_RADIUS = 28;
|
|
264
|
+
const ANDROID_DIALOG_RADIUS = 28;
|
|
265
|
+
export const androidSkin: OverlaySkin = {
|
|
266
|
+
surface: (t, placement) => {
|
|
267
|
+
switch (placement) {
|
|
268
|
+
case "drawer":
|
|
269
|
+
return {
|
|
270
|
+
...DRAWER_ANCHOR,
|
|
271
|
+
backgroundColor: t.popover,
|
|
272
|
+
// M3 navigation drawer: the trailing (leading-in) edge gets the large radius.
|
|
273
|
+
borderTopLeftRadius: ANDROID_SHEET_RADIUS,
|
|
274
|
+
borderBottomLeftRadius: ANDROID_SHEET_RADIUS,
|
|
275
|
+
padding: 24,
|
|
276
|
+
...shadow("lg"),
|
|
277
|
+
};
|
|
278
|
+
case "sheet":
|
|
279
|
+
return {
|
|
280
|
+
...SHEET_ANCHOR,
|
|
281
|
+
backgroundColor: t.popover,
|
|
282
|
+
// Top corners radius 28, flat bottom (M3 modal bottom sheet).
|
|
283
|
+
borderTopLeftRadius: ANDROID_SHEET_RADIUS,
|
|
284
|
+
borderTopRightRadius: ANDROID_SHEET_RADIUS,
|
|
285
|
+
paddingHorizontal: 24,
|
|
286
|
+
// Top padding seats the drag handle; M3 bottom-sheet content inset.
|
|
287
|
+
paddingTop: 16,
|
|
288
|
+
paddingBottom: 24,
|
|
289
|
+
...shadow("lg"),
|
|
290
|
+
};
|
|
291
|
+
case "modal":
|
|
292
|
+
return {
|
|
293
|
+
...MODAL_BOX,
|
|
294
|
+
// M3 dialog shape: 28 radius, elevated, no border.
|
|
295
|
+
backgroundColor: t.popover,
|
|
296
|
+
borderRadius: ANDROID_DIALOG_RADIUS,
|
|
297
|
+
padding: 24,
|
|
298
|
+
...shadow("lg"),
|
|
299
|
+
};
|
|
300
|
+
}
|
|
301
|
+
},
|
|
302
|
+
// The M3 drag handle: a ~32x4 rounded muted-foreground pill centered at the top.
|
|
303
|
+
handle: (t) => ({ width: 32, height: 4, borderRadius: 2, backgroundColor: alpha(t["muted-foreground"], 0.4) }),
|
|
304
|
+
// M3 title: title-large-ish, 16sp medium on the surface.
|
|
305
|
+
title: (t) => ({ fontSize: 16, lineHeight: 24, fontWeight: "500", color: t["popover-foreground"] }),
|
|
306
|
+
// M3 body-medium supporting text.
|
|
307
|
+
description: (t) => ({ fontSize: 14, lineHeight: 20, color: t["muted-foreground"], marginTop: 8 }),
|
|
308
|
+
footer: { flexDirection: "row", justifyContent: "flex-end", marginTop: 24 },
|
|
309
|
+
};
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { createOverlay } from "./overlays.shared.js";
|
|
2
|
+
import { webSkin } from "./overlays.styles.js";
|
|
3
|
+
|
|
4
|
+
// Web Overlay (the base; Metro falls back to it on native, web bundlers resolve it).
|
|
5
|
+
export const Overlay = createOverlay(webSkin);
|
|
6
|
+
export type { OverlayProps } from "./overlays.shared.js";
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { createRowMenu } from "./row-menu.shared.js";
|
|
2
|
+
import { androidSkin } from "./row-menu.styles.js";
|
|
3
|
+
|
|
4
|
+
// Material 3 (menu) RowMenu. Metro resolves this file on Android; the docs import it for preview.
|
|
5
|
+
export const RowMenu = createRowMenu(androidSkin);
|
|
6
|
+
export type { RowMenuProps, RowMenuItem } from "./row-menu.shared.js";
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { createRowMenu } from "./row-menu.shared.js";
|
|
2
|
+
import { iosSkin } from "./row-menu.styles.js";
|
|
3
|
+
|
|
4
|
+
// iOS (HIG context menu) RowMenu. Metro resolves this file on iOS; the docs import it for preview.
|
|
5
|
+
export const RowMenu = createRowMenu(iosSkin);
|
|
6
|
+
export type { RowMenuProps, RowMenuItem } from "./row-menu.shared.js";
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
# Row Menu
|
|
2
|
+
|
|
3
|
+
Vertical action menu items and navigation links.
|
|
4
|
+
|
|
5
|
+
## Usage
|
|
6
|
+
|
|
7
|
+
```tsx
|
|
8
|
+
<RowMenu
|
|
9
|
+
items={[
|
|
10
|
+
{ label: "Edit" },
|
|
11
|
+
{ label: "Duplicate" },
|
|
12
|
+
{ label: "Delete", destructive: true, separatorBefore: true }
|
|
13
|
+
]}
|
|
14
|
+
/>
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
## Variants
|
|
18
|
+
|
|
19
|
+
### Kind - links
|
|
20
|
+
|
|
21
|
+
```tsx
|
|
22
|
+
<RowMenu
|
|
23
|
+
links
|
|
24
|
+
items={[
|
|
25
|
+
{ label: "Profile" },
|
|
26
|
+
{ label: "Billing" },
|
|
27
|
+
{ label: "Members" },
|
|
28
|
+
{ label: "Settings" }
|
|
29
|
+
]}
|
|
30
|
+
/>
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
### Section label
|
|
34
|
+
|
|
35
|
+
```tsx
|
|
36
|
+
<RowMenu
|
|
37
|
+
sectionLabel="Actions"
|
|
38
|
+
items={[
|
|
39
|
+
{ label: "Edit" },
|
|
40
|
+
{ label: "Duplicate" },
|
|
41
|
+
{ label: "Delete", destructive: true, separatorBefore: true }
|
|
42
|
+
]}
|
|
43
|
+
/>
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
### Leading icons
|
|
47
|
+
|
|
48
|
+
```tsx
|
|
49
|
+
<RowMenu
|
|
50
|
+
items={[
|
|
51
|
+
{ label: "Edit", icon: "✎" },
|
|
52
|
+
{ label: "Duplicate", icon: "⧉" },
|
|
53
|
+
{ label: "Delete", icon: "🗑", destructive: true, separatorBefore: true }
|
|
54
|
+
]}
|
|
55
|
+
/>
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
## Do & Don't
|
|
59
|
+
|
|
60
|
+
### Actions
|
|
61
|
+
|
|
62
|
+
**Do** — Click an item: place destructive actions last, color them, and split them off with a divider.
|
|
63
|
+
|
|
64
|
+
```tsx
|
|
65
|
+
<RowMenu open items={[
|
|
66
|
+
{ label: "Edit" },
|
|
67
|
+
{ label: "Duplicate" },
|
|
68
|
+
{ label: "Delete", destructive: true, separatorBefore: true }
|
|
69
|
+
]} />
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
**Don't** — Click an item: a destructive action sandwiched between routine ones invites a costly misclick.
|
|
73
|
+
|
|
74
|
+
```tsx
|
|
75
|
+
<RowMenu open items={[
|
|
76
|
+
{ label: "Edit" },
|
|
77
|
+
{ label: "Delete", destructive: true },
|
|
78
|
+
{ label: "Duplicate" }
|
|
79
|
+
]} />
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
### Links
|
|
83
|
+
|
|
84
|
+
**Do** — Use <a href> anchors so links behave like links, and mark the current page with an active highlight.
|
|
85
|
+
|
|
86
|
+
```tsx
|
|
87
|
+
<RowMenu open links items={[
|
|
88
|
+
{ label: "Profile" },
|
|
89
|
+
{ label: "Billing" },
|
|
90
|
+
{ label: "Members" }
|
|
91
|
+
]} />
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
**Don't** — Buttons can't be opened in a new tab, bookmarked, or middle-clicked, navigation needs real links.
|
|
95
|
+
|
|
96
|
+
```tsx
|
|
97
|
+
<RowMenu open items={[
|
|
98
|
+
{ label: "Profile" },
|
|
99
|
+
{ label: "Billing" },
|
|
100
|
+
{ label: "Members" }
|
|
101
|
+
]} />
|
|
102
|
+
```
|