@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,315 @@
|
|
|
1
|
+
import { type ViewStyle, type TextStyle } from "react-native";
|
|
2
|
+
import { type ColorTokens, alpha } from "../../style/index.js";
|
|
3
|
+
|
|
4
|
+
// Co-located Calendar skins, one per platform. The shell resolves the density
|
|
5
|
+
// metrics (compact vs default cell sizing), the leading-blank padding, and the
|
|
6
|
+
// per-day selected/today/highlight state; the skin supplies only the native
|
|
7
|
+
// SHAPE, sizing, weekday-label style, day-cell fill, today treatment, and press
|
|
8
|
+
// feedback. The BRAND survives on every platform (the selected day fills with the
|
|
9
|
+
// indigo `primary` token, never a platform default), so each follows light/dark
|
|
10
|
+
// and reads as glass when the ThemeProvider's surface is "glass" (tokens.card is
|
|
11
|
+
// swapped translucent).
|
|
12
|
+
//
|
|
13
|
+
// iOS (HIG date picker): the SELECTED day is a filled `primary` circle
|
|
14
|
+
// (radius 9999) with `primary-foreground` text; TODAY is `primary`-colored
|
|
15
|
+
// text with no fill; weekday headers are ~13pt muted SF-style two-letter
|
|
16
|
+
// labels; the month label sits between two ghost chevrons. Press = opacity
|
|
17
|
+
// dim (~0.8).
|
|
18
|
+
// Android (M3 date picker): the SELECTED day is a filled `primary` circle;
|
|
19
|
+
// TODAY is an OUTLINED ring (1px `primary`) on a transparent fill; weekday
|
|
20
|
+
// headers are single-letter; day cells get a circular `android_ripple`
|
|
21
|
+
// (alpha(primary, 0.12)); slightly larger touch targets.
|
|
22
|
+
// Web: the established Canvas look (rounded-full primary fill for any
|
|
23
|
+
// highlighted day, Su/Mo two-letter weekday labels), lifted verbatim from the
|
|
24
|
+
// original file.
|
|
25
|
+
|
|
26
|
+
export type Density = "compact" | "default";
|
|
27
|
+
|
|
28
|
+
// Per-density box sizes, in px. The grid width is exactly seven cell widths so the
|
|
29
|
+
// flex-wrap row breaks after the seventh cell.
|
|
30
|
+
export interface CellMetrics {
|
|
31
|
+
/** A day cell (square). */
|
|
32
|
+
cell: ViewStyle;
|
|
33
|
+
/** The grid width = seven cell widths. */
|
|
34
|
+
gridWidth: number;
|
|
35
|
+
/** A weekday head cell. */
|
|
36
|
+
head: ViewStyle;
|
|
37
|
+
/** The day-label type scale. */
|
|
38
|
+
label: TextStyle;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// The per-day visual state the shell resolves and hands the skin.
|
|
42
|
+
export interface DayState {
|
|
43
|
+
/** The day equals `selected` (the primary highlight). */
|
|
44
|
+
selected: boolean;
|
|
45
|
+
/** The day equals `today` (the secondary highlight). */
|
|
46
|
+
today: boolean;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// The platform-varying surface. Everything color/shape-bearing the calendar needs
|
|
50
|
+
// lives here, built from the active tokens (so each follows light/dark/glass) and
|
|
51
|
+
// the active density metrics.
|
|
52
|
+
export interface CalendarSkin {
|
|
53
|
+
/** Per-density cell/grid/head/label metrics. */
|
|
54
|
+
metrics: Record<Density, CellMetrics>;
|
|
55
|
+
|
|
56
|
+
/** iOS/web dim the day cell on press; Android uses a ripple instead (null). */
|
|
57
|
+
pressedOpacity: number | null;
|
|
58
|
+
/** Android ripple over a pressed day cell; null on iOS/web. */
|
|
59
|
+
ripple: ((t: ColorTokens) => { color: string; borderless: boolean; radius?: number }) | null;
|
|
60
|
+
|
|
61
|
+
// --- container ---
|
|
62
|
+
/** The outer surface: border, radius, padding (layout-only). */
|
|
63
|
+
containerBase: ViewStyle;
|
|
64
|
+
/** The surface fill + border (tokens.card goes translucent under glass). */
|
|
65
|
+
containerSurface: (t: ColorTokens) => ViewStyle;
|
|
66
|
+
|
|
67
|
+
// --- header (month label + prev/next chevrons) ---
|
|
68
|
+
header: ViewStyle;
|
|
69
|
+
/** A ghost chevron button box. */
|
|
70
|
+
chevron: ViewStyle;
|
|
71
|
+
/** The chevron glyph. */
|
|
72
|
+
chevronText: (t: ColorTokens) => TextStyle;
|
|
73
|
+
/** The month/year label. */
|
|
74
|
+
monthLabel: (t: ColorTokens) => TextStyle;
|
|
75
|
+
|
|
76
|
+
// --- weekday header row ---
|
|
77
|
+
/** The weekday + day grid row (fixed width supplied per density). */
|
|
78
|
+
grid: ViewStyle;
|
|
79
|
+
/** A weekday head cell interior. */
|
|
80
|
+
headCell: ViewStyle;
|
|
81
|
+
/** The weekday label. */
|
|
82
|
+
weekdayLabel: (t: ColorTokens) => TextStyle;
|
|
83
|
+
/** The weekday header strings (single-letter on Android, two-letter elsewhere). */
|
|
84
|
+
weekdays: string[];
|
|
85
|
+
|
|
86
|
+
// --- day cell ---
|
|
87
|
+
/** A day cell interior (size from metrics; selected/today fill applied on top). */
|
|
88
|
+
dayCellBase: ViewStyle;
|
|
89
|
+
/** The day-cell fill/outline per state (selected circle, today ring, etc.). */
|
|
90
|
+
dayCellState: (t: ColorTokens, state: DayState) => ViewStyle;
|
|
91
|
+
/** The day-label color/weight per state. */
|
|
92
|
+
dayLabel: (t: ColorTokens, state: DayState) => TextStyle;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// --- shared weekday strings ------------------------------------------------
|
|
96
|
+
|
|
97
|
+
const WEEKDAYS_TWO = ["Su", "Mo", "Tu", "We", "Th", "Fr", "Sa"];
|
|
98
|
+
const WEEKDAYS_ONE = ["S", "M", "T", "W", "T", "F", "S"];
|
|
99
|
+
|
|
100
|
+
// =============================================================================
|
|
101
|
+
// Web: the established Canvas look (lifted verbatim from the original file).
|
|
102
|
+
// Any highlighted day (selected OR today) gets a `primary` rounded-full fill.
|
|
103
|
+
// =============================================================================
|
|
104
|
+
|
|
105
|
+
export const webSkin: CalendarSkin = {
|
|
106
|
+
metrics: {
|
|
107
|
+
compact: {
|
|
108
|
+
cell: { width: 32, height: 32 },
|
|
109
|
+
gridWidth: 224,
|
|
110
|
+
head: { width: 32, height: 28 },
|
|
111
|
+
label: { fontSize: 12, lineHeight: 16 },
|
|
112
|
+
},
|
|
113
|
+
default: {
|
|
114
|
+
cell: { width: 36, height: 36 },
|
|
115
|
+
gridWidth: 252,
|
|
116
|
+
head: { width: 36, height: 32 },
|
|
117
|
+
label: { fontSize: 14, lineHeight: 20 },
|
|
118
|
+
},
|
|
119
|
+
},
|
|
120
|
+
|
|
121
|
+
pressedOpacity: 0.9,
|
|
122
|
+
ripple: null,
|
|
123
|
+
|
|
124
|
+
// `self-start rounded-lg border p-3`.
|
|
125
|
+
containerBase: {
|
|
126
|
+
alignSelf: "flex-start",
|
|
127
|
+
borderRadius: 8,
|
|
128
|
+
borderWidth: 1,
|
|
129
|
+
padding: 12,
|
|
130
|
+
},
|
|
131
|
+
containerSurface: (t) => ({ borderColor: t.border, backgroundColor: t.card }),
|
|
132
|
+
|
|
133
|
+
// `mb-2 flex-row items-center justify-between`.
|
|
134
|
+
header: {
|
|
135
|
+
marginBottom: 8,
|
|
136
|
+
flexDirection: "row",
|
|
137
|
+
alignItems: "center",
|
|
138
|
+
justifyContent: "space-between",
|
|
139
|
+
},
|
|
140
|
+
// `h-7 w-7 items-center justify-center rounded-md bg-transparent`.
|
|
141
|
+
chevron: {
|
|
142
|
+
height: 28,
|
|
143
|
+
width: 28,
|
|
144
|
+
alignItems: "center",
|
|
145
|
+
justifyContent: "center",
|
|
146
|
+
borderRadius: 6,
|
|
147
|
+
backgroundColor: "transparent",
|
|
148
|
+
},
|
|
149
|
+
chevronText: (t) => ({ fontSize: 14, lineHeight: 20, color: t.foreground }),
|
|
150
|
+
monthLabel: (t) => ({ fontSize: 14, lineHeight: 20, fontWeight: "500", color: t.foreground }),
|
|
151
|
+
|
|
152
|
+
grid: { flexDirection: "row", flexWrap: "wrap" },
|
|
153
|
+
headCell: { alignItems: "center", justifyContent: "center" },
|
|
154
|
+
weekdayLabel: (t) => ({ fontSize: 12, lineHeight: 16, fontWeight: "500", color: t["muted-foreground"] }),
|
|
155
|
+
weekdays: WEEKDAYS_TWO,
|
|
156
|
+
|
|
157
|
+
// `items-center justify-center rounded-full`.
|
|
158
|
+
dayCellBase: { alignItems: "center", justifyContent: "center", borderRadius: 9999 },
|
|
159
|
+
// Any highlighted (today/selected) day fills `bg-primary`.
|
|
160
|
+
dayCellState: (t, st) => (st.selected || st.today ? { backgroundColor: t.primary } : {}),
|
|
161
|
+
// Highlighted -> `font-medium text-primary-foreground`, otherwise `text-foreground`.
|
|
162
|
+
dayLabel: (t, st) =>
|
|
163
|
+
st.selected || st.today
|
|
164
|
+
? { fontWeight: "500", color: t["primary-foreground"] }
|
|
165
|
+
: { color: t.foreground },
|
|
166
|
+
};
|
|
167
|
+
|
|
168
|
+
// =============================================================================
|
|
169
|
+
// iOS (HIG date picker): the SELECTED day is a filled `primary` circle with
|
|
170
|
+
// `primary-foreground` text; TODAY is `primary`-colored text with no fill;
|
|
171
|
+
// weekday headers ~13pt muted; the month label sits between two ghost chevrons.
|
|
172
|
+
// Press = opacity dim.
|
|
173
|
+
// =============================================================================
|
|
174
|
+
|
|
175
|
+
export const iosSkin: CalendarSkin = {
|
|
176
|
+
// Slightly larger, airier cells in the HIG date-picker spirit.
|
|
177
|
+
metrics: {
|
|
178
|
+
compact: {
|
|
179
|
+
cell: { width: 34, height: 34 },
|
|
180
|
+
gridWidth: 238,
|
|
181
|
+
head: { width: 34, height: 28 },
|
|
182
|
+
label: { fontSize: 15, lineHeight: 20 },
|
|
183
|
+
},
|
|
184
|
+
default: {
|
|
185
|
+
cell: { width: 38, height: 38 },
|
|
186
|
+
gridWidth: 266,
|
|
187
|
+
head: { width: 38, height: 30 },
|
|
188
|
+
label: { fontSize: 17, lineHeight: 22 },
|
|
189
|
+
},
|
|
190
|
+
},
|
|
191
|
+
|
|
192
|
+
pressedOpacity: 0.8, // HIG: dim on press
|
|
193
|
+
ripple: null,
|
|
194
|
+
|
|
195
|
+
containerBase: {
|
|
196
|
+
alignSelf: "flex-start",
|
|
197
|
+
borderRadius: 12, // HIG larger corner radius
|
|
198
|
+
borderWidth: 1,
|
|
199
|
+
padding: 12,
|
|
200
|
+
},
|
|
201
|
+
containerSurface: (t) => ({ borderColor: t.border, backgroundColor: t.card }),
|
|
202
|
+
|
|
203
|
+
header: {
|
|
204
|
+
marginBottom: 8,
|
|
205
|
+
flexDirection: "row",
|
|
206
|
+
alignItems: "center",
|
|
207
|
+
justifyContent: "space-between",
|
|
208
|
+
},
|
|
209
|
+
chevron: {
|
|
210
|
+
height: 30,
|
|
211
|
+
width: 30,
|
|
212
|
+
alignItems: "center",
|
|
213
|
+
justifyContent: "center",
|
|
214
|
+
borderRadius: 9999,
|
|
215
|
+
backgroundColor: "transparent",
|
|
216
|
+
},
|
|
217
|
+
// HIG: the chevrons carry the brand indigo (system-accent style), heavier glyph.
|
|
218
|
+
chevronText: (t) => ({ fontSize: 22, lineHeight: 24, fontWeight: "500", color: t.primary }),
|
|
219
|
+
// HIG: a bold ~17pt month/year title.
|
|
220
|
+
monthLabel: (t) => ({ fontSize: 17, lineHeight: 22, fontWeight: "600", color: t.foreground }),
|
|
221
|
+
|
|
222
|
+
grid: { flexDirection: "row", flexWrap: "wrap" },
|
|
223
|
+
headCell: { alignItems: "center", justifyContent: "center" },
|
|
224
|
+
// ~13pt muted SF-style weekday header.
|
|
225
|
+
weekdayLabel: (t) => ({ fontSize: 13, lineHeight: 16, fontWeight: "600", color: t["muted-foreground"] }),
|
|
226
|
+
weekdays: WEEKDAYS_TWO,
|
|
227
|
+
|
|
228
|
+
dayCellBase: { alignItems: "center", justifyContent: "center", borderRadius: 9999 },
|
|
229
|
+
// Selected wins: filled `primary` circle. Today (when not selected): no fill
|
|
230
|
+
// (the day label carries the brand indigo instead).
|
|
231
|
+
dayCellState: (t, st) => (st.selected ? { backgroundColor: t.primary } : {}),
|
|
232
|
+
// Selected -> `primary-foreground`; today (unselected) -> `primary` colored
|
|
233
|
+
// text, semibold; otherwise plain foreground.
|
|
234
|
+
dayLabel: (t, st) => {
|
|
235
|
+
if (st.selected) return { fontWeight: "600", color: t["primary-foreground"] };
|
|
236
|
+
if (st.today) return { fontWeight: "600", color: t.primary };
|
|
237
|
+
return { color: t.foreground };
|
|
238
|
+
},
|
|
239
|
+
};
|
|
240
|
+
|
|
241
|
+
// =============================================================================
|
|
242
|
+
// Android (M3 date picker): the SELECTED day is a filled `primary` circle;
|
|
243
|
+
// TODAY is an OUTLINED ring (1px `primary`) on transparent; weekday headers are
|
|
244
|
+
// single-letter; day cells get a circular `android_ripple`; larger touch targets.
|
|
245
|
+
// =============================================================================
|
|
246
|
+
|
|
247
|
+
export const androidSkin: CalendarSkin = {
|
|
248
|
+
// M3 calendar cells are ~40dp/48dp targets; bump the grid accordingly.
|
|
249
|
+
metrics: {
|
|
250
|
+
compact: {
|
|
251
|
+
cell: { width: 36, height: 36 },
|
|
252
|
+
gridWidth: 252,
|
|
253
|
+
head: { width: 36, height: 28 },
|
|
254
|
+
label: { fontSize: 12, lineHeight: 16 },
|
|
255
|
+
},
|
|
256
|
+
default: {
|
|
257
|
+
cell: { width: 40, height: 40 },
|
|
258
|
+
gridWidth: 280,
|
|
259
|
+
head: { width: 40, height: 32 },
|
|
260
|
+
label: { fontSize: 14, lineHeight: 20 },
|
|
261
|
+
},
|
|
262
|
+
},
|
|
263
|
+
|
|
264
|
+
pressedOpacity: null, // Android uses a ripple instead
|
|
265
|
+
// Circular ripple roughly matching the cell radius (default 40/2 = 20dp).
|
|
266
|
+
ripple: (t) => ({ color: alpha(t.primary, 0.12), borderless: false, radius: 20 }),
|
|
267
|
+
|
|
268
|
+
containerBase: {
|
|
269
|
+
alignSelf: "flex-start",
|
|
270
|
+
borderRadius: 12, // M3 large corner
|
|
271
|
+
borderWidth: 1,
|
|
272
|
+
padding: 12,
|
|
273
|
+
},
|
|
274
|
+
containerSurface: (t) => ({ borderColor: t.border, backgroundColor: t.card }),
|
|
275
|
+
|
|
276
|
+
header: {
|
|
277
|
+
marginBottom: 8,
|
|
278
|
+
flexDirection: "row",
|
|
279
|
+
alignItems: "center",
|
|
280
|
+
justifyContent: "space-between",
|
|
281
|
+
},
|
|
282
|
+
chevron: {
|
|
283
|
+
height: 40,
|
|
284
|
+
width: 40,
|
|
285
|
+
alignItems: "center",
|
|
286
|
+
justifyContent: "center",
|
|
287
|
+
borderRadius: 9999,
|
|
288
|
+
backgroundColor: "transparent",
|
|
289
|
+
},
|
|
290
|
+
chevronText: (t) => ({ fontSize: 22, lineHeight: 24, color: t["muted-foreground"] }),
|
|
291
|
+
// M3 labelLarge-ish month label.
|
|
292
|
+
monthLabel: (t) => ({ fontSize: 14, lineHeight: 20, fontWeight: "500", color: t.foreground }),
|
|
293
|
+
|
|
294
|
+
grid: { flexDirection: "row", flexWrap: "wrap" },
|
|
295
|
+
headCell: { alignItems: "center", justifyContent: "center" },
|
|
296
|
+
// M3 single-letter weekday headers, muted.
|
|
297
|
+
weekdayLabel: (t) => ({ fontSize: 12, lineHeight: 16, fontWeight: "500", color: t["muted-foreground"] }),
|
|
298
|
+
weekdays: WEEKDAYS_ONE,
|
|
299
|
+
|
|
300
|
+
dayCellBase: { alignItems: "center", justifyContent: "center", borderRadius: 9999 },
|
|
301
|
+
// Selected -> filled `primary` circle. Today (unselected) -> a 1px `primary`
|
|
302
|
+
// ring on a transparent fill.
|
|
303
|
+
dayCellState: (t, st) => {
|
|
304
|
+
if (st.selected) return { backgroundColor: t.primary };
|
|
305
|
+
if (st.today) return { borderWidth: 1, borderColor: t.primary, backgroundColor: "transparent" };
|
|
306
|
+
return {};
|
|
307
|
+
},
|
|
308
|
+
// Selected -> `primary-foreground`; today (unselected) -> `primary` text;
|
|
309
|
+
// otherwise plain foreground.
|
|
310
|
+
dayLabel: (t, st) => {
|
|
311
|
+
if (st.selected) return { fontWeight: "500", color: t["primary-foreground"] };
|
|
312
|
+
if (st.today) return { fontWeight: "500", color: t.primary };
|
|
313
|
+
return { color: t.foreground };
|
|
314
|
+
},
|
|
315
|
+
};
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { createCalendar } from "./calendar.shared.js";
|
|
2
|
+
import { webSkin } from "./calendar.styles.js";
|
|
3
|
+
|
|
4
|
+
// Web Calendar (the base; Metro falls back to it on native, web bundlers resolve it).
|
|
5
|
+
export const Calendar = createCalendar(webSkin);
|
|
6
|
+
export type { CalendarProps } from "./calendar.shared.js";
|
|
@@ -0,0 +1,326 @@
|
|
|
1
|
+
# Charts
|
|
2
|
+
|
|
3
|
+
Sparklines, bars, gauges, heatmaps. All SVG, all token-themed. No charting library required.
|
|
4
|
+
|
|
5
|
+
## Usage
|
|
6
|
+
|
|
7
|
+
```tsx
|
|
8
|
+
<Chart
|
|
9
|
+
title="Signups"
|
|
10
|
+
data={[
|
|
11
|
+
{ label: "Mon", value: 45 },
|
|
12
|
+
{ label: "Tue", value: 60 },
|
|
13
|
+
{ label: "Wed", value: 35 },
|
|
14
|
+
{ label: "Thu", value: 70 },
|
|
15
|
+
{ label: "Fri", value: 55 },
|
|
16
|
+
{ label: "Sat", value: 80 },
|
|
17
|
+
{ label: "Sun", value: 95 }
|
|
18
|
+
]}
|
|
19
|
+
max={100}
|
|
20
|
+
/>
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## Variants
|
|
24
|
+
|
|
25
|
+
### Chart type - sparkline
|
|
26
|
+
|
|
27
|
+
```tsx
|
|
28
|
+
<Chart
|
|
29
|
+
title="Signups"
|
|
30
|
+
data={[
|
|
31
|
+
{ label: "Mon", value: 45 },
|
|
32
|
+
{ label: "Tue", value: 60 },
|
|
33
|
+
{ label: "Wed", value: 35 },
|
|
34
|
+
{ label: "Thu", value: 70 },
|
|
35
|
+
{ label: "Fri", value: 55 },
|
|
36
|
+
{ label: "Sat", value: 80 },
|
|
37
|
+
{ label: "Sun", value: 95 }
|
|
38
|
+
]}
|
|
39
|
+
max={100}
|
|
40
|
+
horizontal
|
|
41
|
+
/>
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
### Chart type - stacked
|
|
45
|
+
|
|
46
|
+
```tsx
|
|
47
|
+
<Chart
|
|
48
|
+
title="Signups"
|
|
49
|
+
data={[
|
|
50
|
+
{ label: "Mon", value: 45 },
|
|
51
|
+
{ label: "Tue", value: 60 },
|
|
52
|
+
{ label: "Wed", value: 35 },
|
|
53
|
+
{ label: "Thu", value: 70 },
|
|
54
|
+
{ label: "Fri", value: 55 },
|
|
55
|
+
{ label: "Sat", value: 80 },
|
|
56
|
+
{ label: "Sun", value: 95 }
|
|
57
|
+
]}
|
|
58
|
+
max={100}
|
|
59
|
+
success
|
|
60
|
+
/>
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
### Chart type - gauge
|
|
64
|
+
|
|
65
|
+
```tsx
|
|
66
|
+
<Chart
|
|
67
|
+
title="Signups"
|
|
68
|
+
data={[
|
|
69
|
+
{ label: "Mon", value: 45 },
|
|
70
|
+
{ label: "Tue", value: 60 },
|
|
71
|
+
{ label: "Wed", value: 35 },
|
|
72
|
+
{ label: "Thu", value: 70 },
|
|
73
|
+
{ label: "Fri", value: 55 },
|
|
74
|
+
{ label: "Sat", value: 80 },
|
|
75
|
+
{ label: "Sun", value: 95 }
|
|
76
|
+
]}
|
|
77
|
+
max={100}
|
|
78
|
+
success
|
|
79
|
+
horizontal
|
|
80
|
+
/>
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
### Chart type - heatmap
|
|
84
|
+
|
|
85
|
+
```tsx
|
|
86
|
+
<Chart
|
|
87
|
+
title="Signups"
|
|
88
|
+
data={[
|
|
89
|
+
{ label: "Mon", value: 45 },
|
|
90
|
+
{ label: "Tue", value: 60 },
|
|
91
|
+
{ label: "Wed", value: 35 },
|
|
92
|
+
{ label: "Thu", value: 70 },
|
|
93
|
+
{ label: "Fri", value: 55 },
|
|
94
|
+
{ label: "Sat", value: 80 },
|
|
95
|
+
{ label: "Sun", value: 95 }
|
|
96
|
+
]}
|
|
97
|
+
max={100}
|
|
98
|
+
destructive
|
|
99
|
+
/>
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
## Do & Don't
|
|
103
|
+
|
|
104
|
+
### Bar
|
|
105
|
+
|
|
106
|
+
**Do** — Keep a labelled axis row and a single bar tone so the buckets read at a glance.
|
|
107
|
+
|
|
108
|
+
```tsx
|
|
109
|
+
<Chart title="Signups" max={100} style={{ maxWidth: 560 }} data={[
|
|
110
|
+
{ label: "Mon", value: 45 },
|
|
111
|
+
{ label: "Tue", value: 60 },
|
|
112
|
+
{ label: "Wed", value: 35 },
|
|
113
|
+
{ label: "Thu", value: 70 },
|
|
114
|
+
{ label: "Fri", value: 55 },
|
|
115
|
+
{ label: "Sat", value: 80 },
|
|
116
|
+
{ label: "Sun", value: 95 }
|
|
117
|
+
]} />
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
**Don't** — Every bar the same full-strength fill and no labels: nothing is emphasized and the axis is unreadable.
|
|
121
|
+
|
|
122
|
+
```tsx
|
|
123
|
+
<View style={{ borderRadius: 8, borderWidth: 1, borderColor: tokens.border, backgroundColor: tokens.card, padding: 20, maxWidth: 560 }}>
|
|
124
|
+
<View style={{ flexDirection: "row", alignItems: "flex-end", gap: 4, height: 120, width: 520 }}>
|
|
125
|
+
<View style={{ flexGrow: 1, flexShrink: 1, flexBasis: "0%", borderTopLeftRadius: 4, borderTopRightRadius: 4, backgroundColor: tokens.primary, height: 63 }} />
|
|
126
|
+
<View style={{ flexGrow: 1, flexShrink: 1, flexBasis: "0%", borderTopLeftRadius: 4, borderTopRightRadius: 4, backgroundColor: tokens.primary, height: 84 }} />
|
|
127
|
+
<View style={{ flexGrow: 1, flexShrink: 1, flexBasis: "0%", borderTopLeftRadius: 4, borderTopRightRadius: 4, backgroundColor: tokens.primary, height: 49 }} />
|
|
128
|
+
<View style={{ flexGrow: 1, flexShrink: 1, flexBasis: "0%", borderTopLeftRadius: 4, borderTopRightRadius: 4, backgroundColor: tokens.primary, height: 98 }} />
|
|
129
|
+
<View style={{ flexGrow: 1, flexShrink: 1, flexBasis: "0%", borderTopLeftRadius: 4, borderTopRightRadius: 4, backgroundColor: tokens.primary, height: 77 }} />
|
|
130
|
+
<View style={{ flexGrow: 1, flexShrink: 1, flexBasis: "0%", borderTopLeftRadius: 4, borderTopRightRadius: 4, backgroundColor: tokens.primary, height: 112 }} />
|
|
131
|
+
<View style={{ flexGrow: 1, flexShrink: 1, flexBasis: "0%", borderTopLeftRadius: 4, borderTopRightRadius: 4, backgroundColor: tokens.primary, height: 118 }} />
|
|
132
|
+
</View>
|
|
133
|
+
</View>
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
### Sparkline
|
|
137
|
+
|
|
138
|
+
**Do** — Pair the line with the current value and delta and an end dot so it anchors a stat.
|
|
139
|
+
|
|
140
|
+
```tsx
|
|
141
|
+
<View style={{ borderRadius: 8, borderWidth: 1, borderColor: tokens.border, backgroundColor: tokens.card, padding: 20, maxWidth: 200 }}>
|
|
142
|
+
<Text style={{ fontSize: 12, lineHeight: 16, color: tokens["muted-foreground"] }}>Tokens issued</Text>
|
|
143
|
+
<View style={{ marginTop: 4, flexDirection: "row", alignItems: "baseline", justifyContent: "space-between" }}>
|
|
144
|
+
<Text style={{ fontSize: 24, lineHeight: 32, fontWeight: "600", color: tokens["card-foreground"] }}>4,847</Text>
|
|
145
|
+
<Text style={{ fontSize: 12, lineHeight: 16, color: tokens.primary }}>+12%</Text>
|
|
146
|
+
</View>
|
|
147
|
+
<View style={{ marginTop: 8, flexDirection: "row", alignItems: "flex-end", gap: 1, height: 34, width: 180 }}>
|
|
148
|
+
<View style={{ flexGrow: 1, flexShrink: 1, flexBasis: "0%", borderTopLeftRadius: 4, borderTopRightRadius: 4, backgroundColor: tokens.primary, height: 6 }} />
|
|
149
|
+
<View style={{ flexGrow: 1, flexShrink: 1, flexBasis: "0%", borderTopLeftRadius: 4, borderTopRightRadius: 4, backgroundColor: tokens.primary, height: 8 }} />
|
|
150
|
+
<View style={{ flexGrow: 1, flexShrink: 1, flexBasis: "0%", borderTopLeftRadius: 4, borderTopRightRadius: 4, backgroundColor: tokens.primary, height: 12 }} />
|
|
151
|
+
<View style={{ flexGrow: 1, flexShrink: 1, flexBasis: "0%", borderTopLeftRadius: 4, borderTopRightRadius: 4, backgroundColor: tokens.primary, height: 10 }} />
|
|
152
|
+
<View style={{ flexGrow: 1, flexShrink: 1, flexBasis: "0%", borderTopLeftRadius: 4, borderTopRightRadius: 4, backgroundColor: tokens.primary, height: 16 }} />
|
|
153
|
+
<View style={{ flexGrow: 1, flexShrink: 1, flexBasis: "0%", borderTopLeftRadius: 4, borderTopRightRadius: 4, backgroundColor: tokens.primary, height: 20 }} />
|
|
154
|
+
<View style={{ flexGrow: 1, flexShrink: 1, flexBasis: "0%", borderTopLeftRadius: 4, borderTopRightRadius: 4, backgroundColor: tokens.primary, height: 18 }} />
|
|
155
|
+
<View style={{ flexGrow: 1, flexShrink: 1, flexBasis: "0%", borderTopLeftRadius: 4, borderTopRightRadius: 4, backgroundColor: tokens.primary, height: 25 }} />
|
|
156
|
+
<View style={{ flexGrow: 1, flexShrink: 1, flexBasis: "0%", borderTopLeftRadius: 4, borderTopRightRadius: 4, backgroundColor: tokens.primary, height: 23 }} />
|
|
157
|
+
<View style={{ flexGrow: 1, flexShrink: 1, flexBasis: "0%", borderTopLeftRadius: 4, borderTopRightRadius: 4, backgroundColor: tokens.primary, height: 29 }} />
|
|
158
|
+
<View style={{ height: 8, width: 8, alignSelf: "flex-start", borderRadius: 9999, backgroundColor: tokens.primary }} />
|
|
159
|
+
</View>
|
|
160
|
+
</View>
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
**Don't** — A bare line with no value or end dot reads as decoration: you cannot tell the current figure or where it ends.
|
|
164
|
+
|
|
165
|
+
```tsx
|
|
166
|
+
<View style={{ borderRadius: 8, borderWidth: 1, borderColor: tokens.border, backgroundColor: tokens.card, padding: 20, maxWidth: 200 }}>
|
|
167
|
+
<View style={{ flexDirection: "row", alignItems: "flex-end", gap: 1, height: 34, width: 180 }}>
|
|
168
|
+
<View style={{ flexGrow: 1, flexShrink: 1, flexBasis: "0%", borderTopLeftRadius: 4, borderTopRightRadius: 4, backgroundColor: tokens.primary, height: 6 }} />
|
|
169
|
+
<View style={{ flexGrow: 1, flexShrink: 1, flexBasis: "0%", borderTopLeftRadius: 4, borderTopRightRadius: 4, backgroundColor: tokens.primary, height: 8 }} />
|
|
170
|
+
<View style={{ flexGrow: 1, flexShrink: 1, flexBasis: "0%", borderTopLeftRadius: 4, borderTopRightRadius: 4, backgroundColor: tokens.primary, height: 12 }} />
|
|
171
|
+
<View style={{ flexGrow: 1, flexShrink: 1, flexBasis: "0%", borderTopLeftRadius: 4, borderTopRightRadius: 4, backgroundColor: tokens.primary, height: 10 }} />
|
|
172
|
+
<View style={{ flexGrow: 1, flexShrink: 1, flexBasis: "0%", borderTopLeftRadius: 4, borderTopRightRadius: 4, backgroundColor: tokens.primary, height: 16 }} />
|
|
173
|
+
<View style={{ flexGrow: 1, flexShrink: 1, flexBasis: "0%", borderTopLeftRadius: 4, borderTopRightRadius: 4, backgroundColor: tokens.primary, height: 20 }} />
|
|
174
|
+
<View style={{ flexGrow: 1, flexShrink: 1, flexBasis: "0%", borderTopLeftRadius: 4, borderTopRightRadius: 4, backgroundColor: tokens.primary, height: 18 }} />
|
|
175
|
+
<View style={{ flexGrow: 1, flexShrink: 1, flexBasis: "0%", borderTopLeftRadius: 4, borderTopRightRadius: 4, backgroundColor: tokens.primary, height: 25 }} />
|
|
176
|
+
<View style={{ flexGrow: 1, flexShrink: 1, flexBasis: "0%", borderTopLeftRadius: 4, borderTopRightRadius: 4, backgroundColor: tokens.primary, height: 23 }} />
|
|
177
|
+
<View style={{ flexGrow: 1, flexShrink: 1, flexBasis: "0%", borderTopLeftRadius: 4, borderTopRightRadius: 4, backgroundColor: tokens.primary, height: 29 }} />
|
|
178
|
+
<View style={{ flexGrow: 1, flexShrink: 1, flexBasis: "0%", borderTopLeftRadius: 4, borderTopRightRadius: 4, backgroundColor: tokens.primary, height: 31 }} />
|
|
179
|
+
</View>
|
|
180
|
+
</View>
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
### Stacked bar
|
|
184
|
+
|
|
185
|
+
**Do** — Always ship a legend with a colored dot, label, and percentage per segment.
|
|
186
|
+
|
|
187
|
+
```tsx
|
|
188
|
+
<View style={{ borderRadius: 8, borderWidth: 1, borderColor: tokens.border, backgroundColor: tokens.card, padding: 20, maxWidth: 560 }}>
|
|
189
|
+
<View style={{ marginBottom: 12, flexDirection: "row", overflow: "hidden", borderRadius: 9999, height: 10, width: 520 }}>
|
|
190
|
+
<View style={{ width: "42%", backgroundColor: "#6366f1" }} />
|
|
191
|
+
<View style={{ width: "28%", backgroundColor: "#14b8a6" }} />
|
|
192
|
+
<View style={{ width: "18%", backgroundColor: "#f59e0b" }} />
|
|
193
|
+
<View style={{ width: "12%", backgroundColor: "#f43f5e" }} />
|
|
194
|
+
</View>
|
|
195
|
+
<View style={{ flexDirection: "column", gap: 8 }}>
|
|
196
|
+
<View style={{ flexDirection: "row", alignItems: "center", gap: 10 }}>
|
|
197
|
+
<View style={{ borderRadius: 9999, height: 8, width: 8, backgroundColor: "#6366f1" }} />
|
|
198
|
+
<Text style={{ flexGrow: 1, flexShrink: 1, flexBasis: "0%", fontSize: 14, lineHeight: 20, color: tokens["card-foreground"] }}>Direct</Text>
|
|
199
|
+
<Text style={{ fontSize: 12, lineHeight: 16, color: tokens["muted-foreground"] }}>42%</Text>
|
|
200
|
+
</View>
|
|
201
|
+
<View style={{ flexDirection: "row", alignItems: "center", gap: 10 }}>
|
|
202
|
+
<View style={{ borderRadius: 9999, height: 8, width: 8, backgroundColor: "#14b8a6" }} />
|
|
203
|
+
<Text style={{ flexGrow: 1, flexShrink: 1, flexBasis: "0%", fontSize: 14, lineHeight: 20, color: tokens["card-foreground"] }}>Organic search</Text>
|
|
204
|
+
<Text style={{ fontSize: 12, lineHeight: 16, color: tokens["muted-foreground"] }}>28%</Text>
|
|
205
|
+
</View>
|
|
206
|
+
<View style={{ flexDirection: "row", alignItems: "center", gap: 10 }}>
|
|
207
|
+
<View style={{ borderRadius: 9999, height: 8, width: 8, backgroundColor: "#f59e0b" }} />
|
|
208
|
+
<Text style={{ flexGrow: 1, flexShrink: 1, flexBasis: "0%", fontSize: 14, lineHeight: 20, color: tokens["card-foreground"] }}>Social</Text>
|
|
209
|
+
<Text style={{ fontSize: 12, lineHeight: 16, color: tokens["muted-foreground"] }}>18%</Text>
|
|
210
|
+
</View>
|
|
211
|
+
<View style={{ flexDirection: "row", alignItems: "center", gap: 10 }}>
|
|
212
|
+
<View style={{ borderRadius: 9999, height: 8, width: 8, backgroundColor: "#f43f5e" }} />
|
|
213
|
+
<Text style={{ flexGrow: 1, flexShrink: 1, flexBasis: "0%", fontSize: 14, lineHeight: 20, color: tokens["card-foreground"] }}>Referral</Text>
|
|
214
|
+
<Text style={{ fontSize: 12, lineHeight: 16, color: tokens["muted-foreground"] }}>12%</Text>
|
|
215
|
+
</View>
|
|
216
|
+
</View>
|
|
217
|
+
</View>
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
**Don't** — Colored segments with no legend force the reader to guess which channel each band represents.
|
|
221
|
+
|
|
222
|
+
```tsx
|
|
223
|
+
<View style={{ borderRadius: 8, borderWidth: 1, borderColor: tokens.border, backgroundColor: tokens.card, padding: 20, maxWidth: 560 }}>
|
|
224
|
+
<View style={{ flexDirection: "row", overflow: "hidden", borderRadius: 9999, height: 10, width: 520 }}>
|
|
225
|
+
<View style={{ width: "42%", backgroundColor: "#6366f1" }} />
|
|
226
|
+
<View style={{ width: "28%", backgroundColor: "#14b8a6" }} />
|
|
227
|
+
<View style={{ width: "18%", backgroundColor: "#f59e0b" }} />
|
|
228
|
+
<View style={{ width: "12%", backgroundColor: "#f43f5e" }} />
|
|
229
|
+
</View>
|
|
230
|
+
</View>
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
### Gauge
|
|
234
|
+
|
|
235
|
+
**Do** — Put a muted track behind the fill and the numeric value plus label in the center.
|
|
236
|
+
|
|
237
|
+
```tsx
|
|
238
|
+
<View style={{ borderRadius: 8, borderWidth: 1, borderColor: tokens.border, backgroundColor: tokens.card, padding: 20, maxWidth: 200, alignItems: "center" }}>
|
|
239
|
+
<View style={{ alignItems: "center", justifyContent: "center" }}>
|
|
240
|
+
<View style={{ borderRadius: 9999, borderWidth: 8, borderColor: tokens.muted, height: 120, width: 120 }} />
|
|
241
|
+
<View style={{ position: "absolute", alignItems: "center", justifyContent: "center" }}>
|
|
242
|
+
<Text style={{ fontSize: 24, lineHeight: 32, fontWeight: "600", color: tokens["card-foreground"] }}>72%</Text>
|
|
243
|
+
<Text style={{ fontSize: 12, lineHeight: 16, color: tokens["muted-foreground"] }}>Uptime</Text>
|
|
244
|
+
</View>
|
|
245
|
+
</View>
|
|
246
|
+
</View>
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
**Don't** — An arc with no track and no number: there is no baseline to read the fill against and no exact value.
|
|
250
|
+
|
|
251
|
+
```tsx
|
|
252
|
+
<View style={{ borderRadius: 8, borderWidth: 1, borderColor: tokens.border, backgroundColor: tokens.card, padding: 20, maxWidth: 200, alignItems: "center" }}>
|
|
253
|
+
<View style={{ borderRadius: 9999, borderWidth: 8, borderColor: tokens.primary, height: 120, width: 120 }} />
|
|
254
|
+
</View>
|
|
255
|
+
```
|
|
256
|
+
|
|
257
|
+
### Heatmap
|
|
258
|
+
|
|
259
|
+
**Do** — Pair the grid with a discrete less-to-more legend so the density scale is legible.
|
|
260
|
+
|
|
261
|
+
```tsx
|
|
262
|
+
<View style={{ borderRadius: 8, borderWidth: 1, borderColor: tokens.border, backgroundColor: tokens.card, padding: 20, maxWidth: 260 }}>
|
|
263
|
+
<View style={{ flexDirection: "row", flexWrap: "wrap", gap: 4, maxWidth: 220 }}>
|
|
264
|
+
<View style={{ borderRadius: 2, height: 18, width: 18, backgroundColor: "rgba(99,102,241,0.15)" }} />
|
|
265
|
+
<View style={{ borderRadius: 2, height: 18, width: 18, backgroundColor: "rgba(99,102,241,0.4)" }} />
|
|
266
|
+
<View style={{ borderRadius: 2, height: 18, width: 18, backgroundColor: "rgba(99,102,241,0.7)" }} />
|
|
267
|
+
<View style={{ borderRadius: 2, height: 18, width: 18, backgroundColor: "rgba(99,102,241,1)" }} />
|
|
268
|
+
<View style={{ borderRadius: 2, height: 18, width: 18, backgroundColor: "rgba(99,102,241,0.55)" }} />
|
|
269
|
+
<View style={{ borderRadius: 2, height: 18, width: 18, backgroundColor: "rgba(99,102,241,0.25)" }} />
|
|
270
|
+
<View style={{ borderRadius: 2, height: 18, width: 18, backgroundColor: "rgba(99,102,241,0.85)" }} />
|
|
271
|
+
<View style={{ borderRadius: 2, height: 18, width: 18, backgroundColor: "rgba(99,102,241,0.35)" }} />
|
|
272
|
+
<View style={{ borderRadius: 2, height: 18, width: 18, backgroundColor: "rgba(99,102,241,0.6)" }} />
|
|
273
|
+
<View style={{ borderRadius: 2, height: 18, width: 18, backgroundColor: "rgba(99,102,241,0.9)" }} />
|
|
274
|
+
<View style={{ borderRadius: 2, height: 18, width: 18, backgroundColor: "rgba(99,102,241,0.2)" }} />
|
|
275
|
+
<View style={{ borderRadius: 2, height: 18, width: 18, backgroundColor: "rgba(99,102,241,0.5)" }} />
|
|
276
|
+
<View style={{ borderRadius: 2, height: 18, width: 18, backgroundColor: "rgba(99,102,241,0.75)" }} />
|
|
277
|
+
<View style={{ borderRadius: 2, height: 18, width: 18, backgroundColor: "rgba(99,102,241,0.3)" }} />
|
|
278
|
+
<View style={{ borderRadius: 2, height: 18, width: 18, backgroundColor: "rgba(99,102,241,0.95)" }} />
|
|
279
|
+
<View style={{ borderRadius: 2, height: 18, width: 18, backgroundColor: "rgba(99,102,241,0.45)" }} />
|
|
280
|
+
<View style={{ borderRadius: 2, height: 18, width: 18, backgroundColor: "rgba(99,102,241,0.65)" }} />
|
|
281
|
+
<View style={{ borderRadius: 2, height: 18, width: 18, backgroundColor: "rgba(99,102,241,0.1)" }} />
|
|
282
|
+
<View style={{ borderRadius: 2, height: 18, width: 18, backgroundColor: "rgba(99,102,241,0.8)" }} />
|
|
283
|
+
<View style={{ borderRadius: 2, height: 18, width: 18, backgroundColor: "rgba(99,102,241,0.4)" }} />
|
|
284
|
+
<View style={{ borderRadius: 2, height: 18, width: 18, backgroundColor: "rgba(99,102,241,0.7)" }} />
|
|
285
|
+
</View>
|
|
286
|
+
<View style={{ marginTop: 12, flexDirection: "row", alignItems: "center", gap: 8 }}>
|
|
287
|
+
<Text style={{ fontSize: 12, lineHeight: 16, color: tokens["muted-foreground"] }}>Less</Text>
|
|
288
|
+
<View style={{ borderRadius: 2, height: 12, width: 12, backgroundColor: "rgba(99,102,241,0.2)" }} />
|
|
289
|
+
<View style={{ borderRadius: 2, height: 12, width: 12, backgroundColor: "rgba(99,102,241,0.4)" }} />
|
|
290
|
+
<View style={{ borderRadius: 2, height: 12, width: 12, backgroundColor: "rgba(99,102,241,0.6)" }} />
|
|
291
|
+
<View style={{ borderRadius: 2, height: 12, width: 12, backgroundColor: "rgba(99,102,241,0.8)" }} />
|
|
292
|
+
<View style={{ borderRadius: 2, height: 12, width: 12, backgroundColor: "rgba(99,102,241,1)" }} />
|
|
293
|
+
<Text style={{ fontSize: 12, lineHeight: 16, color: tokens["muted-foreground"] }}>More</Text>
|
|
294
|
+
</View>
|
|
295
|
+
</View>
|
|
296
|
+
```
|
|
297
|
+
|
|
298
|
+
**Don't** — A density grid with no legend leaves the alpha-to-value mapping a mystery.
|
|
299
|
+
|
|
300
|
+
```tsx
|
|
301
|
+
<View style={{ borderRadius: 8, borderWidth: 1, borderColor: tokens.border, backgroundColor: tokens.card, padding: 20, maxWidth: 260 }}>
|
|
302
|
+
<View style={{ flexDirection: "row", flexWrap: "wrap", gap: 4, maxWidth: 220 }}>
|
|
303
|
+
<View style={{ borderRadius: 2, height: 18, width: 18, backgroundColor: "rgba(99,102,241,0.15)" }} />
|
|
304
|
+
<View style={{ borderRadius: 2, height: 18, width: 18, backgroundColor: "rgba(99,102,241,0.4)" }} />
|
|
305
|
+
<View style={{ borderRadius: 2, height: 18, width: 18, backgroundColor: "rgba(99,102,241,0.7)" }} />
|
|
306
|
+
<View style={{ borderRadius: 2, height: 18, width: 18, backgroundColor: "rgba(99,102,241,1)" }} />
|
|
307
|
+
<View style={{ borderRadius: 2, height: 18, width: 18, backgroundColor: "rgba(99,102,241,0.55)" }} />
|
|
308
|
+
<View style={{ borderRadius: 2, height: 18, width: 18, backgroundColor: "rgba(99,102,241,0.25)" }} />
|
|
309
|
+
<View style={{ borderRadius: 2, height: 18, width: 18, backgroundColor: "rgba(99,102,241,0.85)" }} />
|
|
310
|
+
<View style={{ borderRadius: 2, height: 18, width: 18, backgroundColor: "rgba(99,102,241,0.35)" }} />
|
|
311
|
+
<View style={{ borderRadius: 2, height: 18, width: 18, backgroundColor: "rgba(99,102,241,0.6)" }} />
|
|
312
|
+
<View style={{ borderRadius: 2, height: 18, width: 18, backgroundColor: "rgba(99,102,241,0.9)" }} />
|
|
313
|
+
<View style={{ borderRadius: 2, height: 18, width: 18, backgroundColor: "rgba(99,102,241,0.2)" }} />
|
|
314
|
+
<View style={{ borderRadius: 2, height: 18, width: 18, backgroundColor: "rgba(99,102,241,0.5)" }} />
|
|
315
|
+
<View style={{ borderRadius: 2, height: 18, width: 18, backgroundColor: "rgba(99,102,241,0.75)" }} />
|
|
316
|
+
<View style={{ borderRadius: 2, height: 18, width: 18, backgroundColor: "rgba(99,102,241,0.3)" }} />
|
|
317
|
+
<View style={{ borderRadius: 2, height: 18, width: 18, backgroundColor: "rgba(99,102,241,0.95)" }} />
|
|
318
|
+
<View style={{ borderRadius: 2, height: 18, width: 18, backgroundColor: "rgba(99,102,241,0.45)" }} />
|
|
319
|
+
<View style={{ borderRadius: 2, height: 18, width: 18, backgroundColor: "rgba(99,102,241,0.65)" }} />
|
|
320
|
+
<View style={{ borderRadius: 2, height: 18, width: 18, backgroundColor: "rgba(99,102,241,0.1)" }} />
|
|
321
|
+
<View style={{ borderRadius: 2, height: 18, width: 18, backgroundColor: "rgba(99,102,241,0.8)" }} />
|
|
322
|
+
<View style={{ borderRadius: 2, height: 18, width: 18, backgroundColor: "rgba(99,102,241,0.4)" }} />
|
|
323
|
+
<View style={{ borderRadius: 2, height: 18, width: 18, backgroundColor: "rgba(99,102,241,0.7)" }} />
|
|
324
|
+
</View>
|
|
325
|
+
</View>
|
|
326
|
+
```
|