@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,164 @@
|
|
|
1
|
+
import { useState } from "react";
|
|
2
|
+
import { View, Text, Pressable, useTheme, type StyleProp, type ViewStyle } from "../../style/index.js";
|
|
3
|
+
import { Icon } from "../../atoms/icon/icon.js";
|
|
4
|
+
import { Kbd } from "../../atoms/kbd/kbd.js";
|
|
5
|
+
import * as s from "./command.styles.js";
|
|
6
|
+
|
|
7
|
+
// Command: a Cmd+K style command palette rendered as a floating card. A search
|
|
8
|
+
// row sits at the top (a leading magnifier glyph + a muted placeholder), then
|
|
9
|
+
// one or more groups of result rows. Each group can carry an optional uppercase
|
|
10
|
+
// heading; each row is a leading icon glyph + a label + an optional trailing
|
|
11
|
+
// shortcut rendered as a Kbd cap. The active row (a flat index across all
|
|
12
|
+
// groups) is highlighted with the accent surface.
|
|
13
|
+
//
|
|
14
|
+
// This is the OPEN, INLINE palette card on its own: no full-screen portal,
|
|
15
|
+
// Modal, or backdrop. `open` (default true) gates whether the card renders, so
|
|
16
|
+
// the docs playground can show the palette in its open state.
|
|
17
|
+
//
|
|
18
|
+
// Style is configured through semantic boolean props (Canvas's only styling
|
|
19
|
+
// API); there are no string-enum props.
|
|
20
|
+
|
|
21
|
+
export interface CommandItem {
|
|
22
|
+
/** The row's primary text. */
|
|
23
|
+
label: string;
|
|
24
|
+
/** Optional leading glyph (e.g. an emoji or single character). */
|
|
25
|
+
icon?: string;
|
|
26
|
+
/** Optional trailing keyboard shortcut, rendered in a Kbd cap. */
|
|
27
|
+
shortcut?: string;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export interface CommandGroup {
|
|
31
|
+
/** Optional uppercase section heading above the group's rows. */
|
|
32
|
+
heading?: string;
|
|
33
|
+
/** The rows in this group. */
|
|
34
|
+
items: CommandItem[];
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export interface CommandProps {
|
|
38
|
+
/** Placeholder shown in the (non-editable, display-only) search row. */
|
|
39
|
+
placeholder?: string;
|
|
40
|
+
/** Grouped result rows. */
|
|
41
|
+
groups?: CommandGroup[];
|
|
42
|
+
/** Flat index of the highlighted row, counted across all groups. */
|
|
43
|
+
active?: number;
|
|
44
|
+
/** Controlled open state. Omit for uncontrolled (the search trigger toggles it). */
|
|
45
|
+
open?: boolean;
|
|
46
|
+
/** Fired when the open state changes. */
|
|
47
|
+
onOpenChange?: (open: boolean) => void;
|
|
48
|
+
/**
|
|
49
|
+
* Render a collapsed full-width search trigger above the palette (a search
|
|
50
|
+
* glyph + "Search..." + a trailing kbd cap). The palette card still renders
|
|
51
|
+
* inline below the trigger (gated by `open`), mirroring how Dropdown shows
|
|
52
|
+
* its trigger plus the open menu in the docs.
|
|
53
|
+
*/
|
|
54
|
+
trigger?: boolean;
|
|
55
|
+
/**
|
|
56
|
+
* Append a footer hint bar below the list (↑ ↓ to navigate, ↵ to select,
|
|
57
|
+
* esc to close).
|
|
58
|
+
*/
|
|
59
|
+
footer?: boolean;
|
|
60
|
+
/** Called with the chosen item and its flat index when a row is pressed. */
|
|
61
|
+
onSelect?: (item: CommandItem, index: number) => void;
|
|
62
|
+
/** Escape hatch for layout/positioning composition (mainly width). */
|
|
63
|
+
style?: StyleProp<ViewStyle>;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export function Command(props: CommandProps) {
|
|
67
|
+
const {
|
|
68
|
+
placeholder = "Type a command or search...",
|
|
69
|
+
groups = [],
|
|
70
|
+
active = 0,
|
|
71
|
+
open: openProp,
|
|
72
|
+
trigger,
|
|
73
|
+
footer,
|
|
74
|
+
onOpenChange,
|
|
75
|
+
onSelect,
|
|
76
|
+
style,
|
|
77
|
+
} = props;
|
|
78
|
+
const { tokens } = useTheme();
|
|
79
|
+
|
|
80
|
+
// Uncontrolled by default: in trigger mode the palette starts closed and the
|
|
81
|
+
// collapsed search trigger toggles it; the bare card (no trigger) starts open.
|
|
82
|
+
const [internalOpen, setInternalOpen] = useState(() => !trigger);
|
|
83
|
+
const open = openProp ?? internalOpen;
|
|
84
|
+
const setOpen = (next: boolean) => {
|
|
85
|
+
if (openProp === undefined) setInternalOpen(next);
|
|
86
|
+
onOpenChange?.(next);
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
// In trigger mode the collapsed search button is always shown; the palette
|
|
90
|
+
// card below it is still gated by `open`. Otherwise the bare card is gated by
|
|
91
|
+
// `open` and renders nothing when closed.
|
|
92
|
+
if (!trigger && !open) return null;
|
|
93
|
+
|
|
94
|
+
// Walk a flat counter across every group so `active` indexes the whole list.
|
|
95
|
+
let flat = -1;
|
|
96
|
+
|
|
97
|
+
const card = open ? (
|
|
98
|
+
<View style={[s.card(tokens), trigger ? s.cardFloating : null]}>
|
|
99
|
+
<View style={s.searchRow(tokens)}>
|
|
100
|
+
<Text style={s.searchGlyph(tokens)}>🔍</Text>
|
|
101
|
+
<Text style={s.searchPlaceholder(tokens)}>{placeholder}</Text>
|
|
102
|
+
</View>
|
|
103
|
+
|
|
104
|
+
{groups.map((group, gi) => (
|
|
105
|
+
<View key={`group-${gi}`}>
|
|
106
|
+
{group.heading != null ? <Text style={s.groupHeading(tokens)}>{group.heading}</Text> : null}
|
|
107
|
+
{group.items.map((item, ii) => {
|
|
108
|
+
flat += 1;
|
|
109
|
+
const index = flat;
|
|
110
|
+
const isActive = index === active;
|
|
111
|
+
return (
|
|
112
|
+
<Pressable
|
|
113
|
+
key={`item-${gi}-${ii}`}
|
|
114
|
+
style={({ pressed }) => [
|
|
115
|
+
s.rowBase,
|
|
116
|
+
isActive || pressed ? s.rowAccent(tokens) : null,
|
|
117
|
+
]}
|
|
118
|
+
onPress={() => {
|
|
119
|
+
onSelect?.(item, index);
|
|
120
|
+
setOpen(false);
|
|
121
|
+
}}
|
|
122
|
+
>
|
|
123
|
+
{item.icon != null ? <Text style={s.rowIcon(tokens)}>{item.icon}</Text> : null}
|
|
124
|
+
<Text style={s.rowLabel(tokens)}>{item.label}</Text>
|
|
125
|
+
{item.shortcut != null ? <Kbd>{item.shortcut}</Kbd> : null}
|
|
126
|
+
</Pressable>
|
|
127
|
+
);
|
|
128
|
+
})}
|
|
129
|
+
</View>
|
|
130
|
+
))}
|
|
131
|
+
|
|
132
|
+
{footer ? (
|
|
133
|
+
<View style={s.footerBar(tokens)}>
|
|
134
|
+
<View style={s.footerHint}>
|
|
135
|
+
<Kbd>↑</Kbd>
|
|
136
|
+
<Kbd>↓</Kbd>
|
|
137
|
+
<Text style={s.footerText(tokens)}>to navigate</Text>
|
|
138
|
+
</View>
|
|
139
|
+
<View style={s.footerHint}>
|
|
140
|
+
<Kbd>↵</Kbd>
|
|
141
|
+
<Text style={s.footerText(tokens)}>to select</Text>
|
|
142
|
+
</View>
|
|
143
|
+
<View style={s.footerHint}>
|
|
144
|
+
<Kbd>esc</Kbd>
|
|
145
|
+
<Text style={s.footerText(tokens)}>to close</Text>
|
|
146
|
+
</View>
|
|
147
|
+
</View>
|
|
148
|
+
) : null}
|
|
149
|
+
</View>
|
|
150
|
+
) : null;
|
|
151
|
+
|
|
152
|
+
if (!trigger) return card;
|
|
153
|
+
|
|
154
|
+
return (
|
|
155
|
+
<View style={[s.triggerWrapper, open ? s.triggerWrapperLifted : null, style]}>
|
|
156
|
+
<Pressable style={s.triggerRow(tokens)} onPress={() => setOpen(!open)}>
|
|
157
|
+
<Icon search muted size={14} />
|
|
158
|
+
<Text style={s.triggerLabel(tokens)}>Search...</Text>
|
|
159
|
+
<Kbd style={s.triggerKbd}>⌘K</Kbd>
|
|
160
|
+
</Pressable>
|
|
161
|
+
{card}
|
|
162
|
+
</View>
|
|
163
|
+
);
|
|
164
|
+
}
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
# Data Tables
|
|
2
|
+
|
|
3
|
+
Every table is the same composition: bordered wrap → toolbar → scrollable table → footer. Density tweaks affect padding live.
|
|
4
|
+
|
|
5
|
+
## Usage
|
|
6
|
+
|
|
7
|
+
```tsx
|
|
8
|
+
<DataTable
|
|
9
|
+
columns={["Name", "Email", "Role", "Status"]}
|
|
10
|
+
rows={[
|
|
11
|
+
["Alice Johnson", "alice@example.com", "Admin", "Active"],
|
|
12
|
+
["Bob Smith", "bob@example.com", "Editor", "Inactive"],
|
|
13
|
+
["Rachel Chen", "rachel@example.com", "Admin", "Active"]
|
|
14
|
+
]}
|
|
15
|
+
bordered
|
|
16
|
+
/>
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## Variants
|
|
20
|
+
|
|
21
|
+
### Variant - bulk
|
|
22
|
+
|
|
23
|
+
```tsx
|
|
24
|
+
<DataTable
|
|
25
|
+
columns={["Name", "Email", "Role", "Status"]}
|
|
26
|
+
rows={[
|
|
27
|
+
["Alice Johnson", "alice@example.com", "Admin", "Active"],
|
|
28
|
+
["Bob Smith", "bob@example.com", "Editor", "Inactive"],
|
|
29
|
+
["Rachel Chen", "rachel@example.com", "Admin", "Active"]
|
|
30
|
+
]}
|
|
31
|
+
bordered
|
|
32
|
+
selectable
|
|
33
|
+
/>
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
### Density - compact
|
|
37
|
+
|
|
38
|
+
```tsx
|
|
39
|
+
<DataTable
|
|
40
|
+
columns={["Name", "Email", "Role", "Status"]}
|
|
41
|
+
rows={[
|
|
42
|
+
["Alice Johnson", "alice@example.com", "Admin", "Active"],
|
|
43
|
+
["Bob Smith", "bob@example.com", "Editor", "Inactive"],
|
|
44
|
+
["Rachel Chen", "rachel@example.com", "Admin", "Active"]
|
|
45
|
+
]}
|
|
46
|
+
bordered
|
|
47
|
+
compact
|
|
48
|
+
/>
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
## Do & Don't
|
|
52
|
+
|
|
53
|
+
### default
|
|
54
|
+
|
|
55
|
+
**Do** — Keep the count + pagination footer so the search result is always anchored to the total.
|
|
56
|
+
|
|
57
|
+
```tsx
|
|
58
|
+
<View style={{ overflow: "hidden", borderRadius: 8, borderWidth: 1, borderColor: tokens.border, maxWidth: 520 }}>
|
|
59
|
+
<View style={{ flexDirection: "row", alignItems: "center", gap: 8, borderBottomWidth: 1, borderColor: tokens.border, padding: 12 }}>
|
|
60
|
+
<Input small placeholder="Search users..." style={{ maxWidth: 240 }} />
|
|
61
|
+
<View style={{ flexGrow: 1, flexShrink: 1, flexBasis: "0%" }} />
|
|
62
|
+
<Button outline small>Export</Button>
|
|
63
|
+
</View>
|
|
64
|
+
<DataTable columns={["Name", "Email"]} rows={[
|
|
65
|
+
["Alice Johnson", "alice@example.com"],
|
|
66
|
+
["Bob Smith", "bob@example.com"],
|
|
67
|
+
["Rachel Chen", "rachel@example.com"]
|
|
68
|
+
]} />
|
|
69
|
+
<View style={{ flexDirection: "row", alignItems: "center", justifyContent: "space-between", borderTopWidth: 1, borderColor: tokens.border, paddingHorizontal: 16, paddingVertical: 10 }}>
|
|
70
|
+
<Text style={{ fontSize: 14, lineHeight: 20, color: tokens["muted-foreground"] }}>Showing 1–3 of 142</Text>
|
|
71
|
+
<View style={{ flexDirection: "row", gap: 4 }}>
|
|
72
|
+
<Button outline small disabled>«</Button>
|
|
73
|
+
<Button outline small>»</Button>
|
|
74
|
+
</View>
|
|
75
|
+
</View>
|
|
76
|
+
</View>
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
**Don't** — Wiring search but dropping the footer leaves the user with no result count or way to page through 142 rows.
|
|
80
|
+
|
|
81
|
+
```tsx
|
|
82
|
+
<View style={{ overflow: "hidden", borderRadius: 8, borderWidth: 1, borderColor: tokens.border, maxWidth: 520 }}>
|
|
83
|
+
<View style={{ flexDirection: "row", alignItems: "center", gap: 8, borderBottomWidth: 1, borderColor: tokens.border, padding: 12 }}>
|
|
84
|
+
<Input small placeholder="Search users..." style={{ maxWidth: 240 }} />
|
|
85
|
+
<View style={{ flexGrow: 1, flexShrink: 1, flexBasis: "0%" }} />
|
|
86
|
+
<Button outline small>Export</Button>
|
|
87
|
+
</View>
|
|
88
|
+
<DataTable columns={["Name", "Email"]} rows={[
|
|
89
|
+
["Alice Johnson", "alice@example.com"],
|
|
90
|
+
["Bob Smith", "bob@example.com"],
|
|
91
|
+
["Rachel Chen", "rachel@example.com"]
|
|
92
|
+
]} />
|
|
93
|
+
</View>
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
### bulk
|
|
97
|
+
|
|
98
|
+
**Do** — Lead with the non-destructive bulk action and keep Delete visually distinct on the right.
|
|
99
|
+
|
|
100
|
+
```tsx
|
|
101
|
+
<View style={{ flexDirection: "row", alignItems: "center", gap: 8, borderRadius: 8, borderWidth: 1, borderColor: tokens.border, padding: 12, maxWidth: 520 }}>
|
|
102
|
+
<Text style={{ fontSize: 12, lineHeight: 16, color: tokens["muted-foreground"] }}>3 selected</Text>
|
|
103
|
+
<View style={{ flexGrow: 1, flexShrink: 1, flexBasis: "0%" }} />
|
|
104
|
+
<Button outline small>Bulk edit</Button>
|
|
105
|
+
<Button destructive small>Delete</Button>
|
|
106
|
+
</View>
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
**Don't** — Surfacing only the destructive Delete on a selection invites accidental data loss with no safer path.
|
|
110
|
+
|
|
111
|
+
```tsx
|
|
112
|
+
<View style={{ flexDirection: "row", alignItems: "center", gap: 8, borderRadius: 8, borderWidth: 1, borderColor: tokens.border, padding: 12, maxWidth: 520 }}>
|
|
113
|
+
<Text style={{ fontSize: 12, lineHeight: 16, color: tokens["muted-foreground"] }}>3 selected</Text>
|
|
114
|
+
<View style={{ flexGrow: 1, flexShrink: 1, flexBasis: "0%" }} />
|
|
115
|
+
<Button destructive small>Delete</Button>
|
|
116
|
+
</View>
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
### filter
|
|
120
|
+
|
|
121
|
+
**Do** — Echo the live result count next to the filter so its effect is visible.
|
|
122
|
+
|
|
123
|
+
```tsx
|
|
124
|
+
<View style={{ flexDirection: "row", alignItems: "center", gap: 8, borderRadius: 8, borderWidth: 1, borderColor: tokens.border, padding: 12, maxWidth: 520 }}>
|
|
125
|
+
<Text style={{ fontSize: 12, lineHeight: 16, color: tokens["muted-foreground"] }}>Status:</Text>
|
|
126
|
+
<Select value="All" options={["All", "Active", "Inactive"]} small style={{ width: 120 }} />
|
|
127
|
+
<View style={{ flexGrow: 1, flexShrink: 1, flexBasis: "0%" }} />
|
|
128
|
+
<Text style={{ fontSize: 12, lineHeight: 16, color: tokens["muted-foreground"] }}>142 results</Text>
|
|
129
|
+
</View>
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
**Don't** — A filter control with no result count leaves the user guessing whether the filter narrowed anything.
|
|
133
|
+
|
|
134
|
+
```tsx
|
|
135
|
+
<View style={{ flexDirection: "row", alignItems: "center", gap: 8, borderRadius: 8, borderWidth: 1, borderColor: tokens.border, padding: 12, maxWidth: 520 }}>
|
|
136
|
+
<Text style={{ fontSize: 12, lineHeight: 16, color: tokens["muted-foreground"] }}>Status:</Text>
|
|
137
|
+
<Select value="All" options={["All", "Active", "Inactive"]} small style={{ width: 120 }} />
|
|
138
|
+
</View>
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
### empty
|
|
142
|
+
|
|
143
|
+
**Do** — Keep the header and span a single centered message row so the structure stays intact.
|
|
144
|
+
|
|
145
|
+
```tsx
|
|
146
|
+
<View style={{ overflow: "hidden", borderRadius: 8, borderWidth: 1, borderColor: tokens.border, maxWidth: 520 }}>
|
|
147
|
+
<DataTable columns={["Name", "Email", "Status"]} rows={[]} />
|
|
148
|
+
<View style={{ alignItems: "center", paddingHorizontal: 16, paddingVertical: 32 }}>
|
|
149
|
+
<Text style={{ fontSize: 14, lineHeight: 20, color: tokens["muted-foreground"] }}>No results found.</Text>
|
|
150
|
+
</View>
|
|
151
|
+
</View>
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
**Don't** — Hiding the body entirely on no results collapses the table and looks broken.
|
|
155
|
+
|
|
156
|
+
```tsx
|
|
157
|
+
<DataTable bordered columns={["Name", "Email", "Status"]} rows={[]} style={{ maxWidth: 520 }} />
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
### loading
|
|
161
|
+
|
|
162
|
+
**Do** — Show a spinner in a centered spanning row so the load reads as active and in place.
|
|
163
|
+
|
|
164
|
+
```tsx
|
|
165
|
+
<View style={{ overflow: "hidden", borderRadius: 8, borderWidth: 1, borderColor: tokens.border, maxWidth: 520 }}>
|
|
166
|
+
<DataTable columns={["Name", "Email", "Status"]} rows={[]} />
|
|
167
|
+
<View style={{ alignItems: "center", paddingHorizontal: 16, paddingVertical: 32 }}>
|
|
168
|
+
<Spinner small />
|
|
169
|
+
</View>
|
|
170
|
+
</View>
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
**Don't** — A bare "Loading…" string gives no sense of progress and reads like static content.
|
|
174
|
+
|
|
175
|
+
```tsx
|
|
176
|
+
<View style={{ overflow: "hidden", borderRadius: 8, borderWidth: 1, borderColor: tokens.border, maxWidth: 520 }}>
|
|
177
|
+
<DataTable columns={["Name", "Email", "Status"]} rows={[]} />
|
|
178
|
+
<View style={{ alignItems: "center", paddingHorizontal: 16, paddingVertical: 32 }}>
|
|
179
|
+
<Text style={{ fontSize: 14, lineHeight: 20, color: tokens["muted-foreground"] }}>Loading…</Text>
|
|
180
|
+
</View>
|
|
181
|
+
</View>
|
|
182
|
+
```
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import { type ViewStyle, type TextStyle } from "react-native";
|
|
2
|
+
import { type ColorTokens, alpha } from "../../style/index.js";
|
|
3
|
+
|
|
4
|
+
// Co-located DataTable styles. The table is laid out as flex rows of equal-width
|
|
5
|
+
// flex-1 cells (there is no CSS table primitive). Layout-only fragments are
|
|
6
|
+
// static objects; anything that reads a color is a function of the active brand
|
|
7
|
+
// tokens (so the header tint, hairlines, stripes, and press tint follow
|
|
8
|
+
// light/dark and the glass surface).
|
|
9
|
+
|
|
10
|
+
export type Density = "compact" | "regular";
|
|
11
|
+
|
|
12
|
+
// --- wrap (outer) -----------------------------------------------------------
|
|
13
|
+
|
|
14
|
+
// The table clips its rounded corners so the header tint and bottom hairlines
|
|
15
|
+
// stay inside the bordered outline.
|
|
16
|
+
export const wrap: ViewStyle = { overflow: "hidden" };
|
|
17
|
+
|
|
18
|
+
// The `bordered` outline: a rounded 1px border around the whole table (the
|
|
19
|
+
// engine had no ring, so the outline is a border). Reads the border token.
|
|
20
|
+
export function borderedOutline(tokens: ColorTokens): ViewStyle {
|
|
21
|
+
return { borderRadius: 8, borderWidth: 1, borderColor: tokens.border };
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// --- header row -------------------------------------------------------------
|
|
25
|
+
|
|
26
|
+
// The header band: a flex row on the muted surface.
|
|
27
|
+
export function headerRow(tokens: ColorTokens): ViewStyle {
|
|
28
|
+
return { flexDirection: "row", backgroundColor: tokens.muted };
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// Header vertical padding per density. Header sits a touch tighter than the body
|
|
32
|
+
// so the label band reads as a header.
|
|
33
|
+
export const headerPad: Record<Density, ViewStyle> = {
|
|
34
|
+
compact: { paddingHorizontal: 16, paddingVertical: 6 },
|
|
35
|
+
regular: { paddingHorizontal: 16, paddingVertical: 8 },
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
// A header column label: equal-width, small, muted, uppercase, wide-tracked.
|
|
39
|
+
export function headerCell(tokens: ColorTokens): TextStyle {
|
|
40
|
+
return {
|
|
41
|
+
flexGrow: 1,
|
|
42
|
+
flexShrink: 1,
|
|
43
|
+
flexBasis: "0%",
|
|
44
|
+
fontSize: 12,
|
|
45
|
+
lineHeight: 16,
|
|
46
|
+
fontWeight: "500",
|
|
47
|
+
textTransform: "uppercase",
|
|
48
|
+
letterSpacing: 0.4,
|
|
49
|
+
color: tokens["muted-foreground"],
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// --- selection column -------------------------------------------------------
|
|
54
|
+
|
|
55
|
+
// The leading checkbox column, kept narrow and fixed (flex-none) so it does not
|
|
56
|
+
// eat into the flex-1 content cells.
|
|
57
|
+
export const selectCol: ViewStyle = {
|
|
58
|
+
width: 40,
|
|
59
|
+
alignItems: "center",
|
|
60
|
+
justifyContent: "center",
|
|
61
|
+
flexGrow: 0,
|
|
62
|
+
flexShrink: 0,
|
|
63
|
+
flexBasis: "auto",
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
// --- data rows --------------------------------------------------------------
|
|
67
|
+
|
|
68
|
+
// A data row: a flex row, vertically centered, with a hairline beneath it.
|
|
69
|
+
export function dataRow(tokens: ColorTokens): ViewStyle {
|
|
70
|
+
return {
|
|
71
|
+
flexDirection: "row",
|
|
72
|
+
alignItems: "center",
|
|
73
|
+
borderBottomWidth: 1,
|
|
74
|
+
borderColor: tokens.border,
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// The striped tint on alternating (odd-index) rows: muted at 30% opacity.
|
|
79
|
+
export function stripeTint(tokens: ColorTokens): ViewStyle {
|
|
80
|
+
return { backgroundColor: alpha(tokens.muted, 0.3) };
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// The pressed tint for a pressable row (the old `active:bg-accent`).
|
|
84
|
+
export function pressTint(tokens: ColorTokens): ViewStyle {
|
|
85
|
+
return { backgroundColor: tokens.accent };
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// Cell vertical padding per density.
|
|
89
|
+
export const cellPad: Record<Density, ViewStyle> = {
|
|
90
|
+
compact: { paddingHorizontal: 16, paddingVertical: 8 },
|
|
91
|
+
regular: { paddingHorizontal: 16, paddingVertical: 12 },
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
// A content cell: equal-width column box (padding added per density).
|
|
95
|
+
export const dataCell: ViewStyle = { flexGrow: 1, flexShrink: 1, flexBasis: "0%" };
|
|
96
|
+
|
|
97
|
+
// The selection cell in a data row: the narrow fixed column plus row padding.
|
|
98
|
+
export const selectCell: ViewStyle = { ...selectCol };
|
|
99
|
+
|
|
100
|
+
// A data cell's text: small body type on the foreground color.
|
|
101
|
+
export function cellText(tokens: ColorTokens): TextStyle {
|
|
102
|
+
return { fontSize: 14, lineHeight: 20, color: tokens.foreground };
|
|
103
|
+
}
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import { type ReactNode } from "react";
|
|
2
|
+
import { View, Pressable, Text, useTheme, type StyleProp, type ViewStyle } from "../../style/index.js";
|
|
3
|
+
import { Checkbox } from "../../atoms/checkbox/checkbox.js";
|
|
4
|
+
import * as s from "./data-table.styles.js";
|
|
5
|
+
import { type Density } from "./data-table.styles.js";
|
|
6
|
+
|
|
7
|
+
// The data table lays a grid out as flex rows of equal-width flex-1 cells (RN
|
|
8
|
+
// has no CSS table primitive; tables are flex rows/columns). A header row
|
|
9
|
+
// carries small, muted, uppercase column labels on a tinted surface, and the
|
|
10
|
+
// data rows sit beneath it separated by hairlines.
|
|
11
|
+
//
|
|
12
|
+
// Boolean-prop API: one boolean per option, grouped by axis, first-match
|
|
13
|
+
// precedence within an axis (mirrors Button's intentOf). Density (`compact`)
|
|
14
|
+
// retunes the vertical padding; `striped` tints alternating rows; `bordered`
|
|
15
|
+
// wraps the whole table in a rounded outline; `selectable` prepends a leading
|
|
16
|
+
// checkbox column to every row.
|
|
17
|
+
|
|
18
|
+
export interface DataTableProps {
|
|
19
|
+
/** Column header labels, one per column. */
|
|
20
|
+
columns: string[];
|
|
21
|
+
/** Row data: an array of rows, each an array of cell strings (one per column). */
|
|
22
|
+
rows: string[][];
|
|
23
|
+
/** Tint every other data row for easier horizontal scanning. */
|
|
24
|
+
striped?: boolean;
|
|
25
|
+
/** Wrap the table in a rounded outer border. */
|
|
26
|
+
bordered?: boolean;
|
|
27
|
+
// Density (pick one; default is the regular row height).
|
|
28
|
+
/** Tighter vertical padding on header and data cells. */
|
|
29
|
+
compact?: boolean;
|
|
30
|
+
/** Prepend a leading checkbox column (header gets an empty selector cell). */
|
|
31
|
+
selectable?: boolean;
|
|
32
|
+
/** When set, each data row is pressable, reporting the row data and index. */
|
|
33
|
+
onRowPress?: (row: string[], index: number) => void;
|
|
34
|
+
/** Escape hatch for layout/positioning composition (mainly width). */
|
|
35
|
+
style?: StyleProp<ViewStyle>;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// Density precedence when more than one is passed: first match wins.
|
|
39
|
+
function densityOf(p: DataTableProps): Density {
|
|
40
|
+
if (p.compact) return "compact";
|
|
41
|
+
return "regular";
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export function DataTable(props: DataTableProps) {
|
|
45
|
+
const { columns, rows, striped, bordered, selectable, onRowPress, style } = props;
|
|
46
|
+
const density = densityOf(props);
|
|
47
|
+
const { tokens } = useTheme();
|
|
48
|
+
|
|
49
|
+
const wrap: StyleProp<ViewStyle> = [
|
|
50
|
+
s.wrap,
|
|
51
|
+
// RN has no ring; a rounded 1px border is the bordered outline.
|
|
52
|
+
bordered ? s.borderedOutline(tokens) : null,
|
|
53
|
+
style,
|
|
54
|
+
];
|
|
55
|
+
|
|
56
|
+
return (
|
|
57
|
+
<View style={wrap}>
|
|
58
|
+
<View style={[s.headerRow(tokens), s.headerPad[density]]}>
|
|
59
|
+
{selectable ? <View style={s.selectCol} /> : null}
|
|
60
|
+
{columns.map((label, i) => (
|
|
61
|
+
<Text key={`h-${i}`} style={s.headerCell(tokens)}>
|
|
62
|
+
{label}
|
|
63
|
+
</Text>
|
|
64
|
+
))}
|
|
65
|
+
</View>
|
|
66
|
+
{rows.map((row, r) => {
|
|
67
|
+
const cells = (
|
|
68
|
+
<>
|
|
69
|
+
{selectable ? (
|
|
70
|
+
<View style={[s.selectCell, s.cellPad[density]]}>
|
|
71
|
+
<Checkbox />
|
|
72
|
+
</View>
|
|
73
|
+
) : null}
|
|
74
|
+
{columns.map((_col, c) => (
|
|
75
|
+
<View key={`c-${r}-${c}`} style={[s.dataCell, s.cellPad[density]]}>
|
|
76
|
+
<Text style={s.cellText(tokens)}>{cellOf(row, c)}</Text>
|
|
77
|
+
</View>
|
|
78
|
+
))}
|
|
79
|
+
</>
|
|
80
|
+
);
|
|
81
|
+
// The striped tint sits on odd-index rows for either layout.
|
|
82
|
+
const stripe = striped && r % 2 === 1 ? s.stripeTint(tokens) : null;
|
|
83
|
+
return onRowPress ? (
|
|
84
|
+
<Pressable
|
|
85
|
+
key={`r-${r}`}
|
|
86
|
+
onPress={() => onRowPress(row, r)}
|
|
87
|
+
style={({ pressed }) => [s.dataRow(tokens), stripe, pressed ? s.pressTint(tokens) : null]}
|
|
88
|
+
>
|
|
89
|
+
{cells}
|
|
90
|
+
</Pressable>
|
|
91
|
+
) : (
|
|
92
|
+
<View key={`r-${r}`} style={[s.dataRow(tokens), stripe]}>
|
|
93
|
+
{cells}
|
|
94
|
+
</View>
|
|
95
|
+
);
|
|
96
|
+
})}
|
|
97
|
+
</View>
|
|
98
|
+
);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// Read a cell, tolerating short rows (missing trailing cells render empty).
|
|
102
|
+
function cellOf(row: string[], index: number): ReactNode {
|
|
103
|
+
const value = row[index];
|
|
104
|
+
return value == null ? "" : value;
|
|
105
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { createDialog } from "./dialog.shared.js";
|
|
2
|
+
import { androidSkin } from "./dialog.styles.js";
|
|
3
|
+
|
|
4
|
+
// Material 3 (basic dialog) Dialog. Metro resolves this file on Android; the docs import it for preview.
|
|
5
|
+
export const Dialog = createDialog(androidSkin);
|
|
6
|
+
export type { DialogProps } from "./dialog.shared.js";
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { createDialog } from "./dialog.shared.js";
|
|
2
|
+
import { iosSkin } from "./dialog.styles.js";
|
|
3
|
+
|
|
4
|
+
// iOS (HIG alert) Dialog. Metro resolves this file on iOS; the docs import it for preview.
|
|
5
|
+
export const Dialog = createDialog(iosSkin);
|
|
6
|
+
export type { DialogProps } from "./dialog.shared.js";
|