@olympusoss/canvas 3.2.1 → 5.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +75 -65
- package/package.json +11 -5
- package/src/atoms/avatar/avatar.md +185 -0
- package/src/atoms/avatar/avatar.styles.ts +48 -0
- package/src/atoms/avatar/avatar.tsx +99 -0
- package/src/atoms/badge/badge.md +237 -0
- package/src/atoms/badge/badge.styles.ts +79 -0
- package/src/atoms/badge/badge.tsx +86 -0
- package/src/atoms/breadcrumb/breadcrumb.md +233 -0
- package/src/atoms/breadcrumb/breadcrumb.styles.ts +40 -0
- package/src/atoms/breadcrumb/breadcrumb.tsx +130 -0
- package/src/atoms/button/button.android.tsx +6 -0
- package/src/atoms/button/button.ios.tsx +6 -0
- package/src/atoms/button/button.md +184 -0
- package/src/atoms/button/button.shared.tsx +79 -0
- package/src/atoms/button/button.styles.ts +152 -0
- package/src/atoms/button/button.tsx +6 -0
- package/src/atoms/button-group/button-group.android.tsx +6 -0
- package/src/atoms/button-group/button-group.ios.tsx +6 -0
- package/src/atoms/button-group/button-group.md +120 -0
- package/src/atoms/button-group/button-group.shared.tsx +398 -0
- package/src/atoms/button-group/button-group.styles.ts +483 -0
- package/src/atoms/button-group/button-group.tsx +6 -0
- package/src/atoms/checkbox/checkbox.android.tsx +6 -0
- package/src/atoms/checkbox/checkbox.ios.tsx +6 -0
- package/src/atoms/checkbox/checkbox.md +150 -0
- package/src/atoms/checkbox/checkbox.shared.tsx +103 -0
- package/src/atoms/checkbox/checkbox.styles.ts +106 -0
- package/src/atoms/checkbox/checkbox.tsx +6 -0
- package/src/atoms/combobox/combobox.android.tsx +6 -0
- package/src/atoms/combobox/combobox.ios.tsx +6 -0
- package/src/atoms/combobox/combobox.md +213 -0
- package/src/atoms/combobox/combobox.shared.tsx +160 -0
- package/src/atoms/combobox/combobox.styles.ts +270 -0
- package/src/atoms/combobox/combobox.tsx +6 -0
- package/src/atoms/divider/divider.md +140 -0
- package/src/atoms/divider/divider.styles.ts +35 -0
- package/src/atoms/divider/divider.tsx +67 -0
- package/src/atoms/dropdown/dropdown.android.tsx +6 -0
- package/src/atoms/dropdown/dropdown.ios.tsx +6 -0
- package/src/atoms/dropdown/dropdown.md +221 -0
- package/src/atoms/dropdown/dropdown.shared.tsx +190 -0
- package/src/atoms/dropdown/dropdown.styles.ts +233 -0
- package/src/atoms/dropdown/dropdown.tsx +6 -0
- package/src/atoms/icon/icon.md +131 -0
- package/src/atoms/icon/icon.styles.ts +30 -0
- package/src/atoms/icon/icon.tsx +328 -0
- package/src/atoms/index.ts +24 -0
- package/src/atoms/input/input.android.tsx +6 -0
- package/src/atoms/input/input.ios.tsx +6 -0
- package/src/atoms/input/input.md +118 -0
- package/src/atoms/input/input.shared.tsx +203 -0
- package/src/atoms/input/input.styles.ts +286 -0
- package/src/atoms/input/input.tsx +6 -0
- package/src/atoms/kbd/kbd.md +91 -0
- package/src/atoms/kbd/kbd.styles.ts +33 -0
- package/src/atoms/kbd/kbd.tsx +27 -0
- package/src/atoms/listbox/listbox.md +177 -0
- package/src/atoms/listbox/listbox.styles.ts +60 -0
- package/src/atoms/listbox/listbox.tsx +113 -0
- package/src/atoms/pagination/pagination.android.tsx +6 -0
- package/src/atoms/pagination/pagination.ios.tsx +6 -0
- package/src/atoms/pagination/pagination.md +133 -0
- package/src/atoms/pagination/pagination.shared.tsx +289 -0
- package/src/atoms/pagination/pagination.styles.ts +245 -0
- package/src/atoms/pagination/pagination.tsx +6 -0
- package/src/atoms/popover/popover.android.tsx +8 -0
- package/src/atoms/popover/popover.ios.tsx +6 -0
- package/src/atoms/popover/popover.md +87 -0
- package/src/atoms/popover/popover.shared.tsx +124 -0
- package/src/atoms/popover/popover.styles.ts +144 -0
- package/src/atoms/popover/popover.tsx +6 -0
- package/src/atoms/radio/radio.android.tsx +6 -0
- package/src/atoms/radio/radio.ios.tsx +6 -0
- package/src/atoms/radio/radio.md +173 -0
- package/src/atoms/radio/radio.shared.tsx +98 -0
- package/src/atoms/radio/radio.styles.ts +109 -0
- package/src/atoms/radio/radio.tsx +6 -0
- package/src/atoms/select/select.android.tsx +6 -0
- package/src/atoms/select/select.ios.tsx +6 -0
- package/src/atoms/select/select.md +156 -0
- package/src/atoms/select/select.shared.tsx +143 -0
- package/src/atoms/select/select.styles.ts +310 -0
- package/src/atoms/select/select.tsx +6 -0
- package/src/atoms/skeleton/skeleton.md +135 -0
- package/src/atoms/skeleton/skeleton.styles.ts +117 -0
- package/src/atoms/skeleton/skeleton.tsx +145 -0
- package/src/atoms/spinner/spinner.android.tsx +7 -0
- package/src/atoms/spinner/spinner.ios.tsx +7 -0
- package/src/atoms/spinner/spinner.md +94 -0
- package/src/atoms/spinner/spinner.shared.tsx +92 -0
- package/src/atoms/spinner/spinner.styles.tsx +115 -0
- package/src/atoms/spinner/spinner.tsx +7 -0
- package/src/atoms/switch/switch.android.tsx +6 -0
- package/src/atoms/switch/switch.ios.tsx +6 -0
- package/src/atoms/switch/switch.md +91 -0
- package/src/atoms/switch/switch.shared.tsx +97 -0
- package/src/atoms/switch/switch.styles.ts +79 -0
- package/src/atoms/switch/switch.tsx +6 -0
- package/src/atoms/textarea/textarea.android.tsx +6 -0
- package/src/atoms/textarea/textarea.ios.tsx +6 -0
- package/src/atoms/textarea/textarea.md +140 -0
- package/src/atoms/textarea/textarea.shared.tsx +74 -0
- package/src/atoms/textarea/textarea.styles.ts +116 -0
- package/src/atoms/textarea/textarea.tsx +6 -0
- package/src/atoms/tooltip/tooltip.android.tsx +6 -0
- package/src/atoms/tooltip/tooltip.ios.tsx +7 -0
- package/src/atoms/tooltip/tooltip.md +122 -0
- package/src/atoms/tooltip/tooltip.shared.tsx +113 -0
- package/src/atoms/tooltip/tooltip.styles.ts +113 -0
- package/src/atoms/tooltip/tooltip.tsx +6 -0
- package/src/atoms/typography/typography.md +330 -0
- package/src/atoms/typography/typography.styles.ts +95 -0
- package/src/atoms/typography/typography.tsx +76 -0
- package/src/index.ts +12 -2
- package/src/molecules/action-panels/action-panels.md +133 -0
- package/src/molecules/action-panels/action-panels.styles.ts +39 -0
- package/src/molecules/action-panels/action-panels.tsx +113 -0
- package/src/molecules/alert/alert.md +119 -0
- package/src/molecules/alert/alert.styles.ts +88 -0
- package/src/molecules/alert/alert.tsx +74 -0
- package/src/molecules/alert-dialog/alert-dialog.android.tsx +6 -0
- package/src/molecules/alert-dialog/alert-dialog.ios.tsx +6 -0
- package/src/molecules/alert-dialog/alert-dialog.md +177 -0
- package/src/molecules/alert-dialog/alert-dialog.shared.tsx +187 -0
- package/src/molecules/alert-dialog/alert-dialog.styles.ts +248 -0
- package/src/molecules/alert-dialog/alert-dialog.tsx +6 -0
- package/src/molecules/card/card.md +190 -0
- package/src/molecules/card/card.styles.ts +67 -0
- package/src/molecules/card/card.tsx +176 -0
- package/src/molecules/code-block/code-block.md +159 -0
- package/src/molecules/code-block/code-block.styles.ts +167 -0
- package/src/molecules/code-block/code-block.tsx +176 -0
- package/src/molecules/description-lists/description-lists.md +129 -0
- package/src/molecules/description-lists/description-lists.styles.ts +102 -0
- package/src/molecules/description-lists/description-lists.tsx +133 -0
- package/src/molecules/empty-state/empty-state.md +218 -0
- package/src/molecules/empty-state/empty-state.styles.ts +63 -0
- package/src/molecules/empty-state/empty-state.tsx +77 -0
- package/src/molecules/feeds/feeds.md +102 -0
- package/src/molecules/feeds/feeds.styles.ts +120 -0
- package/src/molecules/feeds/feeds.tsx +167 -0
- package/src/molecules/field/field.md +117 -0
- package/src/molecules/field/field.styles.ts +85 -0
- package/src/molecules/field/field.tsx +175 -0
- package/src/molecules/fieldset/fieldset.md +141 -0
- package/src/molecules/fieldset/fieldset.styles.ts +79 -0
- package/src/molecules/fieldset/fieldset.tsx +182 -0
- package/src/molecules/form/form.md +137 -0
- package/src/molecules/form/form.styles.ts +39 -0
- package/src/molecules/form/form.tsx +246 -0
- package/src/molecules/grid-lists/grid-lists.md +114 -0
- package/src/molecules/grid-lists/grid-lists.styles.ts +79 -0
- package/src/molecules/grid-lists/grid-lists.tsx +157 -0
- package/src/molecules/index.ts +16 -0
- package/src/molecules/media-objects/media-objects.md +87 -0
- package/src/molecules/media-objects/media-objects.styles.ts +94 -0
- package/src/molecules/media-objects/media-objects.tsx +128 -0
- package/src/molecules/stacked-lists/stacked-lists.md +116 -0
- package/src/molecules/stacked-lists/stacked-lists.styles.ts +111 -0
- package/src/molecules/stacked-lists/stacked-lists.tsx +195 -0
- package/src/molecules/stats/stats.md +166 -0
- package/src/molecules/stats/stats.styles.ts +91 -0
- package/src/molecules/stats/stats.tsx +88 -0
- package/src/organisms/calendar/calendar.android.tsx +6 -0
- package/src/organisms/calendar/calendar.ios.tsx +6 -0
- package/src/organisms/calendar/calendar.md +114 -0
- package/src/organisms/calendar/calendar.shared.tsx +146 -0
- package/src/organisms/calendar/calendar.styles.ts +315 -0
- package/src/organisms/calendar/calendar.tsx +6 -0
- package/src/organisms/charts/charts.md +326 -0
- package/src/organisms/charts/charts.styles.ts +135 -0
- package/src/organisms/charts/charts.tsx +124 -0
- package/src/organisms/command/command.md +117 -0
- package/src/organisms/command/command.styles.ts +179 -0
- package/src/organisms/command/command.tsx +164 -0
- package/src/organisms/data-table/data-table.md +182 -0
- package/src/organisms/data-table/data-table.styles.ts +103 -0
- package/src/organisms/data-table/data-table.tsx +105 -0
- package/src/organisms/dialog/dialog.android.tsx +6 -0
- package/src/organisms/dialog/dialog.ios.tsx +6 -0
- package/src/organisms/dialog/dialog.md +271 -0
- package/src/organisms/dialog/dialog.shared.tsx +230 -0
- package/src/organisms/dialog/dialog.styles.ts +272 -0
- package/src/organisms/dialog/dialog.tsx +6 -0
- package/src/organisms/filter-panel/filter-panel.md +116 -0
- package/src/organisms/filter-panel/filter-panel.styles.ts +83 -0
- package/src/organisms/filter-panel/filter-panel.tsx +91 -0
- package/src/organisms/index.ts +13 -0
- package/src/organisms/navbars/navbars.android.tsx +6 -0
- package/src/organisms/navbars/navbars.ios.tsx +6 -0
- package/src/organisms/navbars/navbars.md +144 -0
- package/src/organisms/navbars/navbars.shared.tsx +137 -0
- package/src/organisms/navbars/navbars.styles.ts +251 -0
- package/src/organisms/navbars/navbars.tsx +6 -0
- package/src/organisms/overlays/overlays.android.tsx +6 -0
- package/src/organisms/overlays/overlays.ios.tsx +6 -0
- package/src/organisms/overlays/overlays.md +123 -0
- package/src/organisms/overlays/overlays.shared.tsx +175 -0
- package/src/organisms/overlays/overlays.styles.ts +309 -0
- package/src/organisms/overlays/overlays.tsx +6 -0
- package/src/organisms/row-menu/row-menu.android.tsx +6 -0
- package/src/organisms/row-menu/row-menu.ios.tsx +6 -0
- package/src/organisms/row-menu/row-menu.md +102 -0
- package/src/organisms/row-menu/row-menu.shared.tsx +105 -0
- package/src/organisms/row-menu/row-menu.styles.ts +262 -0
- package/src/organisms/row-menu/row-menu.tsx +6 -0
- package/src/organisms/sidebar/sidebar.android.tsx +6 -0
- package/src/organisms/sidebar/sidebar.ios.tsx +6 -0
- package/src/organisms/sidebar/sidebar.md +188 -0
- package/src/organisms/sidebar/sidebar.shared.tsx +167 -0
- package/src/organisms/sidebar/sidebar.styles.ts +262 -0
- package/src/organisms/sidebar/sidebar.tsx +6 -0
- package/src/organisms/stepper/stepper.android.tsx +6 -0
- package/src/organisms/stepper/stepper.ios.tsx +6 -0
- package/src/organisms/stepper/stepper.md +150 -0
- package/src/organisms/stepper/stepper.shared.tsx +158 -0
- package/src/organisms/stepper/stepper.styles.ts +280 -0
- package/src/organisms/stepper/stepper.tsx +6 -0
- package/src/organisms/tabs/tabs.android.tsx +6 -0
- package/src/organisms/tabs/tabs.ios.tsx +6 -0
- package/src/organisms/tabs/tabs.md +127 -0
- package/src/organisms/tabs/tabs.shared.tsx +281 -0
- package/src/organisms/tabs/tabs.styles.ts +398 -0
- package/src/organisms/tabs/tabs.tsx +6 -0
- package/src/style/color.ts +17 -0
- package/src/style/index.ts +14 -0
- package/src/style/primitives.ts +26 -0
- package/src/style/responsive.ts +45 -0
- package/src/style/shadow.ts +21 -0
- package/src/style/theme.tsx +56 -0
- package/src/style/tokens.ts +487 -0
- package/styles/canvas.css +127 -74
- package/tsconfig.json +4 -2
- package/src/cn.ts +0 -3
- package/styles/atoms/avatar.css +0 -22
- package/styles/atoms/badge.css +0 -83
- package/styles/atoms/breadcrumb.css +0 -35
- package/styles/atoms/button-group.css +0 -23
- package/styles/atoms/button.css +0 -107
- package/styles/atoms/checkbox.css +0 -55
- package/styles/atoms/combobox.css +0 -76
- package/styles/atoms/dropdown.css +0 -54
- package/styles/atoms/icon.css +0 -8
- package/styles/atoms/input-group.css +0 -45
- package/styles/atoms/input.css +0 -56
- package/styles/atoms/kbd.css +0 -15
- package/styles/atoms/pagination.css +0 -48
- package/styles/atoms/popover.css +0 -14
- package/styles/atoms/radio.css +0 -28
- package/styles/atoms/select.css +0 -57
- package/styles/atoms/separator.css +0 -32
- package/styles/atoms/skeleton.css +0 -32
- package/styles/atoms/spinner.css +0 -26
- package/styles/atoms/switch.css +0 -45
- package/styles/atoms/textarea.css +0 -31
- package/styles/atoms/tooltip.css +0 -53
- package/styles/atoms/typography.css +0 -105
- package/styles/base.css +0 -17
- package/styles/molecules/alert.css +0 -66
- package/styles/molecules/card.css +0 -58
- package/styles/molecules/code-block.css +0 -18
- package/styles/molecules/empty-state.css +0 -17
- package/styles/molecules/field.css +0 -27
- package/styles/molecules/form.css +0 -27
- package/styles/molecules/page-header.css +0 -52
- package/styles/molecules/section-card.css +0 -49
- package/styles/molecules/stat-card.css +0 -71
- package/styles/molecules/toast.css +0 -95
- package/styles/organisms/app-shell.css +0 -46
- package/styles/organisms/calendar.css +0 -73
- package/styles/organisms/command.css +0 -95
- package/styles/organisms/data-table.css +0 -142
- package/styles/organisms/dialog.css +0 -72
- package/styles/organisms/filter-panel.css +0 -58
- package/styles/organisms/row-menu.css +0 -69
- package/styles/organisms/sheet.css +0 -70
- package/styles/organisms/sidebar.css +0 -146
- package/styles/organisms/stepper.css +0 -63
- package/styles/organisms/tabs.css +0 -40
- package/styles/organisms/topbar.css +0 -24
- package/styles/patterns/backdrops.css +0 -35
- package/styles/patterns/density.css +0 -66
- package/styles/patterns/focus.css +0 -22
- package/styles/patterns/glass.css +0 -85
- package/styles/patterns/high-contrast.css +0 -70
- package/styles/patterns/reduced-motion.css +0 -12
- package/styles/patterns/scrollbar.css +0 -10
- package/styles/reset.css +0 -89
- package/styles/tokens/colors.css +0 -108
- package/styles/tokens/motion.css +0 -33
- package/styles/tokens/radius.css +0 -10
- package/styles/tokens/shadows.css +0 -35
- package/styles/tokens/spacing.css +0 -19
- package/styles/tokens/typography.css +0 -6
- package/styles/tokens/z-index.css +0 -12
- package/styles/utilities/display.css +0 -66
- package/styles/utilities/flexbox.css +0 -240
- package/styles/utilities/gap.css +0 -288
- package/styles/utilities/grid.css +0 -138
- package/styles/utilities/position.css +0 -78
- package/styles/utilities/sizing.css +0 -138
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import { type TextStyle } from "react-native";
|
|
2
|
+
import { type ColorTokens, alpha } from "../../style/index.js";
|
|
3
|
+
|
|
4
|
+
// Co-located Textarea skins, one per platform. The field is a multiline
|
|
5
|
+
// TextInput, so every fragment is a TextStyle. The BRAND survives on every
|
|
6
|
+
// platform (the focus/active cue is always the indigo `ring`/`primary` token,
|
|
7
|
+
// the error cue the `destructive` token, never a platform default); only the
|
|
8
|
+
// native SHAPE, fill, border/underline, and focus feedback change per OS:
|
|
9
|
+
// iOS (HIG, iOS 27 / Liquid Glass): the PLAIN multiline text view, a fully
|
|
10
|
+
// transparent field with NO fill and NO box, carrying only a subtle bottom
|
|
11
|
+
// hairline rule (the `input` token at rest), so it reads like the iOS 27
|
|
12
|
+
// plain text field rather than a filled gray capsule. The hairline brightens
|
|
13
|
+
// to the brand `primary` on focus and `destructive` on error; the brand
|
|
14
|
+
// cursor/selection is the indigo `primary` (set on the shell, never a system
|
|
15
|
+
// blue).
|
|
16
|
+
// Android (Material 3 filled): a subtle fill with a flat bottom active
|
|
17
|
+
// indicator (underline). Top corners ~4, square bottom. The indicator is a
|
|
18
|
+
// 1px resting line that thickens to 2px indigo on focus (destructive on
|
|
19
|
+
// error).
|
|
20
|
+
// Web: the established Canvas look (lifted verbatim) — full-width, 6 radius,
|
|
21
|
+
// 1px border, on the background fill; border is error > focus(ring) > input.
|
|
22
|
+
|
|
23
|
+
export type Size = "small" | "base" | "large";
|
|
24
|
+
|
|
25
|
+
// State the shell resolves and hands to the skin's field builder.
|
|
26
|
+
export interface TextareaFieldState {
|
|
27
|
+
/** Error/validation state (error or invalid prop). */
|
|
28
|
+
error: boolean;
|
|
29
|
+
/** The field currently holds keyboard focus. */
|
|
30
|
+
focused: boolean;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// The only thing a platform skin owns: the field surface for a given state.
|
|
34
|
+
// `sizeText` and `minHeight` are shared (the brand type scale and the rows math
|
|
35
|
+
// are identical across platforms), so the shell composes them around the skin.
|
|
36
|
+
export interface TextareaSkin {
|
|
37
|
+
field: (tokens: ColorTokens, state: TextareaFieldState) => TextStyle;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// --- shared, platform-neutral fragments -------------------------------------
|
|
41
|
+
|
|
42
|
+
// Text scale per size; mirrors the height the larger control reads as. Default
|
|
43
|
+
// is the base text-sm field (no size prop). Shared across platforms.
|
|
44
|
+
export function sizeText(size: Size): TextStyle {
|
|
45
|
+
if (size === "large") return { fontSize: 16, lineHeight: 24 }; // text-base
|
|
46
|
+
if (size === "small") return { fontSize: 12, lineHeight: 16 }; // text-xs
|
|
47
|
+
return { fontSize: 14, lineHeight: 20 }; // text-sm
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// Derived min height from the row count: each row ~22px plus the vertical
|
|
51
|
+
// padding. Falls back to the 80px floor when no rows are given. The field still
|
|
52
|
+
// grows with content past this floor. Shared across platforms.
|
|
53
|
+
export function minHeight(rows?: number): TextStyle {
|
|
54
|
+
return { minHeight: rows == null ? 80 : rows * 22 + 16 };
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// ---------- Web: the established Canvas look (lifted verbatim) ----------
|
|
58
|
+
// Full width, bordered, padded, on the background fill, with the foreground
|
|
59
|
+
// text color. Border resolves error > focus(ring) > default input.
|
|
60
|
+
export const webSkin: TextareaSkin = {
|
|
61
|
+
field: (t, st) => ({
|
|
62
|
+
width: "100%",
|
|
63
|
+
borderRadius: 6,
|
|
64
|
+
borderWidth: 1,
|
|
65
|
+
borderColor: st.error ? t.destructive : st.focused ? t.ring : t.input,
|
|
66
|
+
backgroundColor: t.background,
|
|
67
|
+
paddingHorizontal: 12,
|
|
68
|
+
paddingVertical: 8,
|
|
69
|
+
color: t.foreground,
|
|
70
|
+
}),
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
// ---------- iOS (HIG, iOS 27 / Liquid Glass): plain transparent field, bottom hairline ----------
|
|
74
|
+
// The iOS 27 plain multiline text view: no fill, no box, a transparent field
|
|
75
|
+
// with a single subtle bottom hairline rule. The rule is the `input` token at
|
|
76
|
+
// rest (1pt), brightening to the brand `primary` on focus and `destructive` on
|
|
77
|
+
// error, mirroring the new plain single-line input. Horizontal padding drops to
|
|
78
|
+
// 0 so the text and the rule align to the field's edges like the reference.
|
|
79
|
+
export const iosSkin: TextareaSkin = {
|
|
80
|
+
field: (t, st) => ({
|
|
81
|
+
width: "100%",
|
|
82
|
+
backgroundColor: "transparent",
|
|
83
|
+
// The only chrome: a bottom hairline that carries focus/error.
|
|
84
|
+
borderBottomWidth: 1,
|
|
85
|
+
borderBottomColor: st.error ? t.destructive : st.focused ? t.primary : t.input,
|
|
86
|
+
paddingHorizontal: 0,
|
|
87
|
+
paddingTop: 8,
|
|
88
|
+
paddingBottom: 8,
|
|
89
|
+
color: t.foreground,
|
|
90
|
+
}),
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
// ---------- Android (Material 3 filled): subtle fill + active indicator ------
|
|
94
|
+
// A subtle fill (~8% of the muted-foreground hue over the surface) with rounded
|
|
95
|
+
// top corners (~4) and a square bottom, carrying a bottom active indicator
|
|
96
|
+
// (underline). The indicator is a 1px resting line (the input token) that
|
|
97
|
+
// thickens to 2px indigo `primary` on focus, or destructive on error.
|
|
98
|
+
export const androidSkin: TextareaSkin = {
|
|
99
|
+
field: (t, st) => ({
|
|
100
|
+
width: "100%",
|
|
101
|
+
borderTopLeftRadius: 4,
|
|
102
|
+
borderTopRightRadius: 4,
|
|
103
|
+
borderBottomLeftRadius: 0,
|
|
104
|
+
borderBottomRightRadius: 0,
|
|
105
|
+
backgroundColor: alpha(t["muted-foreground"], 0.08),
|
|
106
|
+
// The active indicator: only the bottom edge is drawn.
|
|
107
|
+
borderBottomWidth: st.focused || st.error ? 2 : 1,
|
|
108
|
+
// Rest baseline must read clearly (on-surface-variant ~ muted-foreground) so the
|
|
109
|
+
// M3 filled field is distinct from the iOS lineless capsule.
|
|
110
|
+
borderBottomColor: st.error ? t.destructive : st.focused ? t.primary : t["muted-foreground"],
|
|
111
|
+
paddingHorizontal: 12,
|
|
112
|
+
paddingTop: 10,
|
|
113
|
+
paddingBottom: 8,
|
|
114
|
+
color: t.foreground,
|
|
115
|
+
}),
|
|
116
|
+
};
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { createTextarea } from "./textarea.shared.js";
|
|
2
|
+
import { webSkin } from "./textarea.styles.js";
|
|
3
|
+
|
|
4
|
+
// Web Textarea (the base; Metro falls back to it on native, web bundlers resolve it).
|
|
5
|
+
export const Textarea = createTextarea(webSkin);
|
|
6
|
+
export type { TextareaProps } from "./textarea.shared.js";
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { createTooltip } from "./tooltip.shared.js";
|
|
2
|
+
import { androidSkin } from "./tooltip.styles.js";
|
|
3
|
+
|
|
4
|
+
// Material 3 (plain tooltip) Tooltip. Metro resolves this file on Android; the docs import it for preview.
|
|
5
|
+
export const Tooltip = createTooltip(androidSkin);
|
|
6
|
+
export type { TooltipProps } from "./tooltip.shared.js";
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { createTooltip } from "./tooltip.shared.js";
|
|
2
|
+
import { iosSkin } from "./tooltip.styles.js";
|
|
3
|
+
|
|
4
|
+
// iOS Tooltip (no native tooltip; a small rounded inverse label). Metro resolves
|
|
5
|
+
// this file on iOS; the docs import it for preview.
|
|
6
|
+
export const Tooltip = createTooltip(iosSkin);
|
|
7
|
+
export type { TooltipProps } from "./tooltip.shared.js";
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
# Tooltips
|
|
2
|
+
|
|
3
|
+
Small floating helper text on hover or focus.
|
|
4
|
+
|
|
5
|
+
## Usage
|
|
6
|
+
|
|
7
|
+
```tsx
|
|
8
|
+
<Tooltip label="Open settings" iconTrigger trigger="Hover me" open top />
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Variants
|
|
12
|
+
|
|
13
|
+
### Side - right
|
|
14
|
+
|
|
15
|
+
```tsx
|
|
16
|
+
<Tooltip label="Open settings" iconTrigger trigger="Hover me" open right />
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
### Side - bottom
|
|
20
|
+
|
|
21
|
+
```tsx
|
|
22
|
+
<Tooltip label="Open settings" iconTrigger trigger="Hover me" open bottom />
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
### Side - left
|
|
26
|
+
|
|
27
|
+
```tsx
|
|
28
|
+
<Tooltip label="Open settings" iconTrigger trigger="Hover me" open left />
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
### Trigger - button
|
|
32
|
+
|
|
33
|
+
```tsx
|
|
34
|
+
<Tooltip label="Open settings" trigger="Hover me" open top />
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
### Trigger - text
|
|
38
|
+
|
|
39
|
+
```tsx
|
|
40
|
+
<Tooltip label="Open settings" trigger="hover this text" open top />
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
### Reveal - on hover
|
|
44
|
+
|
|
45
|
+
```tsx
|
|
46
|
+
<Tooltip label="Open settings" iconTrigger trigger="Hover me" top />
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
## Do & Don't
|
|
50
|
+
|
|
51
|
+
**Do** — Keep tooltips short and supplementary; put essential steps in visible copy.
|
|
52
|
+
|
|
53
|
+
```tsx
|
|
54
|
+
<Tooltip iconTrigger bottom open label="Rotate key" />
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
**Don't** — Long, essential instructions hidden in a tooltip are missed on touch and by screen readers.
|
|
58
|
+
|
|
59
|
+
```tsx
|
|
60
|
+
<Tooltip iconTrigger bottom open label="To rotate this key you must first revoke the old one in Settings, then confirm via email within 24 hours." />
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
### top
|
|
64
|
+
|
|
65
|
+
**Do** — Leave headroom above (or flip to bottom) so a top-placed tooltip stays fully on screen.
|
|
66
|
+
|
|
67
|
+
```tsx
|
|
68
|
+
<Tooltip trigger="Save" top open label="Saves your changes" />
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
**Don't** — A top tooltip on a trigger near the top edge clips above the viewport and goes unread.
|
|
72
|
+
|
|
73
|
+
```tsx
|
|
74
|
+
<Tooltip trigger="Save" top open label="Saves your changes" />
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
### right
|
|
78
|
+
|
|
79
|
+
**Do** — Keep room to the right, or flip the tooltip to the left when the trigger hugs the edge.
|
|
80
|
+
|
|
81
|
+
```tsx
|
|
82
|
+
<Tooltip iconTrigger right open label="More info" />
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
**Don't** — A right tooltip on a control flush against the right edge is cut off by the container.
|
|
86
|
+
|
|
87
|
+
```tsx
|
|
88
|
+
<View style={{ alignItems: "flex-end" }}>
|
|
89
|
+
<Tooltip iconTrigger right open label="More info" />
|
|
90
|
+
</View>
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
### bottom
|
|
94
|
+
|
|
95
|
+
**Do** — Give a bottom tooltip clearance so it never overlaps the interactive content below.
|
|
96
|
+
|
|
97
|
+
```tsx
|
|
98
|
+
<Tooltip trigger="Filters" bottom open label="Refine results" />
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
**Don't** — A bottom tooltip sits right on top of the next row, masking the control beneath it.
|
|
102
|
+
|
|
103
|
+
```tsx
|
|
104
|
+
<View style={{ alignItems: "flex-start", gap: 0 }}>
|
|
105
|
+
<Tooltip trigger="Filters" bottom open label="Refine results" />
|
|
106
|
+
<Button outline small style={{ marginTop: -12 }}>Clear all</Button>
|
|
107
|
+
</View>
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
### left
|
|
111
|
+
|
|
112
|
+
**Do** — Reserve space on the left, or flip to the right, so a left-placed tooltip is never cut off.
|
|
113
|
+
|
|
114
|
+
```tsx
|
|
115
|
+
<Tooltip iconTrigger left open label="Need help?" />
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
**Don't** — A left tooltip on a trigger at the left edge is clipped by the container's left boundary.
|
|
119
|
+
|
|
120
|
+
```tsx
|
|
121
|
+
<Tooltip iconTrigger left open label="Need help?" />
|
|
122
|
+
```
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import { useState } from "react";
|
|
2
|
+
import { View, Pressable, Text, useTheme, type StyleProp, type ViewStyle } from "../../style/index.js";
|
|
3
|
+
import { Button } from "../button/button.js";
|
|
4
|
+
import { Icon } from "../icon/icon.js";
|
|
5
|
+
import {
|
|
6
|
+
wrapper,
|
|
7
|
+
bubbleGap,
|
|
8
|
+
iconTrigger,
|
|
9
|
+
type Placement,
|
|
10
|
+
type TooltipSkin,
|
|
11
|
+
} from "./tooltip.styles.js";
|
|
12
|
+
|
|
13
|
+
// Shared Tooltip shell. The structure (the trigger plus the open bubble placed
|
|
14
|
+
// next to it via flex order), the public boolean-prop API, the placement
|
|
15
|
+
// precedence, the controlled/uncontrolled open state, the toggle handler, and
|
|
16
|
+
// accessibility all live here once. A platform file supplies only its skin (the
|
|
17
|
+
// bubble surface shape/fill and the label type) and calls createTooltip.
|
|
18
|
+
//
|
|
19
|
+
// Tooltip: a small dark bubble of helper text shown beside a trigger on hover
|
|
20
|
+
// or focus. This RN port renders the open state inline (no portal/Modal): a
|
|
21
|
+
// trigger button with the bubble positioned next to it via flex order, so the
|
|
22
|
+
// docs playground can show it without an overlay layer.
|
|
23
|
+
//
|
|
24
|
+
// Placement is a boolean axis (top default, bottom, left, right); first match
|
|
25
|
+
// wins. top/bottom stack the bubble above/below the trigger in a column;
|
|
26
|
+
// left/right place it beside the trigger in a row.
|
|
27
|
+
export interface TooltipProps {
|
|
28
|
+
// The tip text shown in the bubble.
|
|
29
|
+
label?: string;
|
|
30
|
+
// The element the tip describes; rendered as an <Button outline small>.
|
|
31
|
+
trigger?: string;
|
|
32
|
+
// Render the trigger as a ghost icon button (a settings glyph) instead of the
|
|
33
|
+
// text Button. When set, `trigger` (the label string) is ignored.
|
|
34
|
+
iconTrigger?: boolean;
|
|
35
|
+
// Controlled visibility. Omit for uncontrolled (tap the trigger to toggle).
|
|
36
|
+
open?: boolean;
|
|
37
|
+
// Fired when the bubble is shown/hidden.
|
|
38
|
+
onOpenChange?: (open: boolean) => void;
|
|
39
|
+
// Placement (pick one; default is top).
|
|
40
|
+
top?: boolean;
|
|
41
|
+
bottom?: boolean;
|
|
42
|
+
left?: boolean;
|
|
43
|
+
right?: boolean;
|
|
44
|
+
/** Escape hatch for layout/positioning composition (margins, alignment). */
|
|
45
|
+
style?: StyleProp<ViewStyle>;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// Placement precedence when more than one is passed: first match wins.
|
|
49
|
+
function placementOf(p: TooltipProps): Placement {
|
|
50
|
+
if (p.top) return "top";
|
|
51
|
+
if (p.bottom) return "bottom";
|
|
52
|
+
if (p.left) return "left";
|
|
53
|
+
if (p.right) return "right";
|
|
54
|
+
return "top";
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// Whether the bubble renders before the trigger in source order. top and left
|
|
58
|
+
// place the bubble first; bottom and right place it after.
|
|
59
|
+
const BUBBLE_FIRST: Record<Placement, boolean> = {
|
|
60
|
+
top: true,
|
|
61
|
+
bottom: false,
|
|
62
|
+
left: true,
|
|
63
|
+
right: false,
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
/** Build a Tooltip component from a platform skin. */
|
|
67
|
+
export function createTooltip(skin: TooltipSkin) {
|
|
68
|
+
return function Tooltip(props: TooltipProps) {
|
|
69
|
+
const { label, trigger, iconTrigger: isIconTrigger, onOpenChange, style } = props;
|
|
70
|
+
const placement = placementOf(props);
|
|
71
|
+
const { tokens } = useTheme();
|
|
72
|
+
// Uncontrolled by default: tapping the trigger toggles the bubble (a touch
|
|
73
|
+
// analogue of hover); a controlled `open` prop overrides this.
|
|
74
|
+
const [internalOpen, setInternalOpen] = useState(false);
|
|
75
|
+
const open = props.open ?? internalOpen;
|
|
76
|
+
const setOpen = (next: boolean) => {
|
|
77
|
+
if (props.open === undefined) setInternalOpen(next);
|
|
78
|
+
onOpenChange?.(next);
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
const tip = open ? (
|
|
82
|
+
<View style={[skin.bubble(tokens), bubbleGap[placement]]}>
|
|
83
|
+
<Text style={skin.label(tokens)}>{label}</Text>
|
|
84
|
+
</View>
|
|
85
|
+
) : null;
|
|
86
|
+
|
|
87
|
+
// Icon trigger: a ghost icon button (40px square) holding the settings glyph,
|
|
88
|
+
// matching a ghost icon Button. The glyph renders directly inside the
|
|
89
|
+
// Pressable (not via Button's <Text> children, which can't host an SVG).
|
|
90
|
+
const triggerEl = isIconTrigger ? (
|
|
91
|
+
<Pressable
|
|
92
|
+
style={({ pressed }) => [iconTrigger, pressed ? { opacity: 0.9 } : null]}
|
|
93
|
+
onPress={() => setOpen(!open)}
|
|
94
|
+
accessibilityRole="button"
|
|
95
|
+
accessibilityLabel={label}
|
|
96
|
+
>
|
|
97
|
+
<Icon settings size={16} />
|
|
98
|
+
</Pressable>
|
|
99
|
+
) : (
|
|
100
|
+
<Button outline small onPress={() => setOpen(!open)}>
|
|
101
|
+
{trigger}
|
|
102
|
+
</Button>
|
|
103
|
+
);
|
|
104
|
+
|
|
105
|
+
return (
|
|
106
|
+
<View style={[wrapper[placement], style]}>
|
|
107
|
+
{BUBBLE_FIRST[placement] ? tip : null}
|
|
108
|
+
{triggerEl}
|
|
109
|
+
{BUBBLE_FIRST[placement] ? null : tip}
|
|
110
|
+
</View>
|
|
111
|
+
);
|
|
112
|
+
};
|
|
113
|
+
}
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import { type ViewStyle, type TextStyle } from "react-native";
|
|
2
|
+
import { type ColorTokens, shadow } from "../../style/index.js";
|
|
3
|
+
|
|
4
|
+
// Co-located Tooltip skins, one per platform, all driven by the brand tokens
|
|
5
|
+
// (passed in from useTheme so they follow light/dark and the glass surface). A
|
|
6
|
+
// tooltip carries no brand fill of its own; the convention on every platform is
|
|
7
|
+
// an INVERSE label (a dark bubble in a light theme, painted on the `foreground`
|
|
8
|
+
// token with `background` text) so the tip reads against the page. Only the
|
|
9
|
+
// native SHAPE, radius, type scale, padding, and elevation change per OS:
|
|
10
|
+
// iOS (no native tooltip): a small rounded label, radius ~6, an inverse fill
|
|
11
|
+
// (`foreground` bg with `background` text), caption ~12pt, compact padding,
|
|
12
|
+
// a soft lift. There is no system tooltip, so this is the platform-
|
|
13
|
+
// appropriate label convention, not an invented control.
|
|
14
|
+
// Android (Material 3 plain tooltip): a small rounded rect (radius 4), an
|
|
15
|
+
// inverse-surface fill (dark in a light theme: `foreground` bg, `background`
|
|
16
|
+
// text), body-small ~12sp, padding 8x4, flat (no elevation per M3 plain).
|
|
17
|
+
// Web: the established Canvas look (the current tooltip, lifted verbatim) — a
|
|
18
|
+
// 6-radius dark pill on `foreground`, padding 8x4, `background` text at 12/16
|
|
19
|
+
// medium, with a soft `md` shadow.
|
|
20
|
+
|
|
21
|
+
export type Placement = "top" | "bottom" | "left" | "right";
|
|
22
|
+
|
|
23
|
+
// The contract a platform skin fulfills. Layout (wrapper direction per placement,
|
|
24
|
+
// the gap between bubble and trigger, the bubble-first ordering) is shared and
|
|
25
|
+
// lives in the shell; the skin supplies only the bubble surface and the label
|
|
26
|
+
// type, both functions of the active tokens so the inverse fill follows
|
|
27
|
+
// light/dark automatically.
|
|
28
|
+
export interface TooltipSkin {
|
|
29
|
+
/** The bubble surface: shape, radius, inverse fill, padding, elevation. */
|
|
30
|
+
bubble: (t: ColorTokens) => ViewStyle;
|
|
31
|
+
/** The tip label: size/weight, painted in the inverse text token. */
|
|
32
|
+
label: (t: ColorTokens) => TextStyle;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// --- shared layout fragments (identical across platforms) -------------------
|
|
36
|
+
|
|
37
|
+
// Wrapper layout per placement: a column for top/bottom (bubble stacked above or
|
|
38
|
+
// below the trigger), a row for left/right (bubble beside the trigger). Centered
|
|
39
|
+
// on the cross axis and shrunk to its content (self-start).
|
|
40
|
+
export const wrapper: Record<Placement, ViewStyle> = {
|
|
41
|
+
top: { flexDirection: "column", alignItems: "center", alignSelf: "flex-start" },
|
|
42
|
+
bottom: { flexDirection: "column", alignItems: "center", alignSelf: "flex-start" },
|
|
43
|
+
left: { flexDirection: "row", alignItems: "center", alignSelf: "flex-start" },
|
|
44
|
+
right: { flexDirection: "row", alignItems: "center", alignSelf: "flex-start" },
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
// Gap between bubble and trigger (the old `m{b,t,r,l}-1.5` = 6), applied to the
|
|
48
|
+
// bubble on the trigger-facing side: top -> below, bottom -> above, etc.
|
|
49
|
+
export const bubbleGap: Record<Placement, ViewStyle> = {
|
|
50
|
+
top: { marginBottom: 6 },
|
|
51
|
+
bottom: { marginTop: 6 },
|
|
52
|
+
left: { marginRight: 6 },
|
|
53
|
+
right: { marginLeft: 6 },
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
// The icon trigger: a 40px square ghost button holding the settings glyph,
|
|
57
|
+
// matching a ghost icon Button. Press feedback is applied by the component's
|
|
58
|
+
// Pressable (opacity dim on iOS/web, ripple on Android).
|
|
59
|
+
export const iconTrigger: ViewStyle = {
|
|
60
|
+
height: 40,
|
|
61
|
+
width: 40,
|
|
62
|
+
alignItems: "center",
|
|
63
|
+
justifyContent: "center",
|
|
64
|
+
borderRadius: 6,
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
// ---------- Web: the established Canvas look (lifted verbatim) ----------
|
|
68
|
+
// A small dark pill on the `foreground` token (so it inverts against the page),
|
|
69
|
+
// 6 radius, 8x4 padding, a soft `md` lift; the label is 12/16 medium painted in
|
|
70
|
+
// `background` so it reads as light text on the dark bubble.
|
|
71
|
+
export const webSkin: TooltipSkin = {
|
|
72
|
+
bubble: (t) => ({
|
|
73
|
+
borderRadius: 6,
|
|
74
|
+
backgroundColor: t.foreground,
|
|
75
|
+
paddingHorizontal: 8,
|
|
76
|
+
paddingVertical: 4,
|
|
77
|
+
...shadow("md"),
|
|
78
|
+
}),
|
|
79
|
+
label: (t) => ({ fontSize: 12, lineHeight: 16, fontWeight: "500", color: t.background }),
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
// ---------- iOS (no native tooltip): a small rounded inverse label ----------
|
|
83
|
+
// iOS ships no tooltip, so this is the platform-appropriate label convention: a
|
|
84
|
+
// small rounded rect (radius 6) over the inverse `foreground` fill with
|
|
85
|
+
// `background` text, a compact caption (12/16), tight 8x4 padding, and a soft
|
|
86
|
+
// `md` lift so it floats off the page.
|
|
87
|
+
export const iosSkin: TooltipSkin = {
|
|
88
|
+
bubble: (t) => ({
|
|
89
|
+
borderRadius: 6,
|
|
90
|
+
backgroundColor: t.foreground,
|
|
91
|
+
paddingHorizontal: 8,
|
|
92
|
+
paddingVertical: 4,
|
|
93
|
+
...shadow("md"),
|
|
94
|
+
}),
|
|
95
|
+
label: (t) => ({ fontSize: 12, lineHeight: 16, fontWeight: "500", color: t.background }),
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
// ---------- Android (Material 3 plain tooltip): inverse-surface rounded rect ----------
|
|
99
|
+
// M3 plain tooltip: a small rounded rect with a tighter 4dp corner radius, an
|
|
100
|
+
// inverse-surface fill (dark in a light theme: `foreground` bg, `background`
|
|
101
|
+
// text), body-small ~12sp, padding 8x4, and FLAT (no elevation) per the M3 plain
|
|
102
|
+
// tooltip spec. A 24dp minimum height keeps single-line tips at the spec height.
|
|
103
|
+
export const androidSkin: TooltipSkin = {
|
|
104
|
+
bubble: (t) => ({
|
|
105
|
+
borderRadius: 4,
|
|
106
|
+
backgroundColor: t.foreground,
|
|
107
|
+
paddingHorizontal: 8,
|
|
108
|
+
paddingVertical: 4,
|
|
109
|
+
minHeight: 24,
|
|
110
|
+
justifyContent: "center",
|
|
111
|
+
}),
|
|
112
|
+
label: (t) => ({ fontSize: 12, lineHeight: 16, fontWeight: "400", color: t.background }),
|
|
113
|
+
};
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { createTooltip } from "./tooltip.shared.js";
|
|
2
|
+
import { webSkin } from "./tooltip.styles.js";
|
|
3
|
+
|
|
4
|
+
// Web Tooltip (the base; Metro falls back to it on native, web bundlers resolve it).
|
|
5
|
+
export const Tooltip = createTooltip(webSkin);
|
|
6
|
+
export type { TooltipProps } from "./tooltip.shared.js";
|