@rebasepro/ui 0.14.1 → 0.14.2-canary.g27a129e
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/dist/components/Typography.d.ts +4 -0
- package/dist/index.css +56 -4
- package/dist/index.d.ts +1 -0
- package/dist/index.es.js +298 -58
- package/dist/index.es.js.map +1 -1
- package/dist/src/index.css +56 -4
- package/dist/src/theme.css +8 -5
- package/dist/styles.d.ts +29 -1
- package/dist/theme.css +8 -5
- package/dist/util/chip_colors.d.ts +14 -0
- package/dist/util/lazy_chunk.d.ts +30 -0
- package/dist/views/CollectionView/CollectionView.d.ts +22 -2
- package/dist/views/CollectionView/CollectionViewToolbar.d.ts +11 -1
- package/dist/views/CollectionView/CollectionViewTypes.d.ts +40 -2
- package/package.json +1 -1
- package/src/components/Chip.tsx +10 -0
- package/src/components/ErrorBoundary.tsx +51 -12
- package/src/components/Typography.tsx +15 -3
- package/src/index.css +56 -4
- package/src/index.ts +1 -0
- package/src/styles.ts +47 -1
- package/src/theme.css +8 -5
- package/src/util/chip_colors.ts +119 -5
- package/src/util/lazy_chunk.ts +101 -0
- package/src/views/CollectionView/CollectionView.tsx +56 -0
- package/src/views/CollectionView/CollectionViewToolbar.tsx +20 -7
- package/src/views/CollectionView/CollectionViewTypes.ts +44 -2
package/dist/src/index.css
CHANGED
|
@@ -36,26 +36,40 @@ html.dark {
|
|
|
36
36
|
size goes up. Seven consecutive levels of semibold was a Rubik-era setting,
|
|
37
37
|
and holding 600 all the way down to text-sm made every heading shout equally
|
|
38
38
|
— a heading stops being a headline somewhere, and the scale should say where.
|
|
39
|
-
|
|
39
|
+
|
|
40
|
+
The ladder now spans three weights rather than two. 600 everywhere at the top
|
|
41
|
+
meant a page title and the section heading inside it were the same voice at
|
|
42
|
+
two sizes, so a screen had no clear first thing to read. 700 is the *display*
|
|
43
|
+
tier only — h1 and h2, page titles and stat numbers — and nothing in the UI
|
|
44
|
+
chrome (nav, labels, buttons, table headers) goes above 600.
|
|
45
|
+
|
|
46
|
+
This costs nothing on the wire: both faces are loaded as VARIABLE fonts
|
|
47
|
+
(@fontsource-variable/inter, @fontsource-variable/instrument-sans), so the
|
|
48
|
+
whole weight axis is already in the file that ships. The earlier "never above
|
|
49
|
+
600" rule was written when static weights meant every step was another
|
|
50
|
+
download; that constraint no longer exists.
|
|
40
51
|
|
|
41
52
|
With a single face carrying both headings and body, weight and tracking are
|
|
42
53
|
the only things separating a heading from the copy around it — so the tiers
|
|
43
54
|
have to actually differ, rather than all sitting at 600. */
|
|
44
55
|
|
|
45
56
|
.typography-h1 {
|
|
46
|
-
@apply text-4xl font-headers font-
|
|
57
|
+
@apply text-4xl font-headers font-bold tracking-display;
|
|
47
58
|
}
|
|
48
59
|
|
|
49
60
|
.typography-h2 {
|
|
50
|
-
@apply text-3xl font-headers font-
|
|
61
|
+
@apply text-3xl font-headers font-bold tracking-display;
|
|
51
62
|
}
|
|
52
63
|
|
|
53
64
|
.typography-h3 {
|
|
54
65
|
@apply text-2xl font-headers font-semibold tracking-title;
|
|
55
66
|
}
|
|
56
67
|
|
|
68
|
+
/* Semibold, not medium: h4 is the section heading inside a page, and at 20px
|
|
69
|
+
medium sits close enough to body copy that a long page reads as one
|
|
70
|
+
undifferentiated column. */
|
|
57
71
|
.typography-h4 {
|
|
58
|
-
@apply text-xl font-headers font-
|
|
72
|
+
@apply text-xl font-headers font-semibold tracking-title;
|
|
59
73
|
}
|
|
60
74
|
|
|
61
75
|
.typography-h5 {
|
|
@@ -98,6 +112,44 @@ html.dark {
|
|
|
98
112
|
@apply text-sm font-semibold tracking-wide;
|
|
99
113
|
}
|
|
100
114
|
|
|
115
|
+
/* ---------------------------------------------------------------------------
|
|
116
|
+
* Three tiers the product kept improvising because the system had no name for
|
|
117
|
+
* them. Each existed as hand-written utilities in several places before it
|
|
118
|
+
* existed here, which is the definition of a missing token.
|
|
119
|
+
* ------------------------------------------------------------------------- */
|
|
120
|
+
|
|
121
|
+
/* The sentence under a page title. `body2` (12px) was standing in for it, which
|
|
122
|
+
made the one line explaining what a page is FOR smaller than the page's own
|
|
123
|
+
table rows. A lead is read once, deliberately, and it gets room to be read. */
|
|
124
|
+
.typography-lead {
|
|
125
|
+
@apply text-base leading-relaxed;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/* The micro-label above a value: SCANNED, KEPT, TOOK, WHEN. Uppercase and
|
|
129
|
+
tracked so it reads as a field name rather than as copy, and small enough
|
|
130
|
+
that it never competes with the value it names.
|
|
131
|
+
|
|
132
|
+
This is the one product tier below `text-xs`. It earns the exception by never
|
|
133
|
+
carrying a sentence — it is always one or two words naming the thing directly
|
|
134
|
+
underneath it, and at 11px uppercase with this tracking it stays legible
|
|
135
|
+
where 11px prose would not. */
|
|
136
|
+
.typography-micro {
|
|
137
|
+
@apply text-2xs font-medium uppercase tracking-[0.08em];
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/* A measured value: a count, a duration, a timestamp, an id, a size.
|
|
141
|
+
`tabular-nums` is the point — proportional digits make a column of numbers
|
|
142
|
+
ragged and a live-updating counter jitter as its glyphs change width. */
|
|
143
|
+
.typography-mono {
|
|
144
|
+
@apply font-mono tabular-nums;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/* The headline number on a stat tile. Display weight, display tracking, and
|
|
148
|
+
tabular figures so a row of tiles shares a baseline grid. */
|
|
149
|
+
.typography-stat {
|
|
150
|
+
@apply text-3xl font-headers font-bold tracking-display tabular-nums;
|
|
151
|
+
}
|
|
152
|
+
|
|
101
153
|
:focus-visible {
|
|
102
154
|
@apply outline-none focus-visible:ring-2 focus-visible:ring-primary focus-visible:ring-offset-0 focus-visible:ring-offset-transparent
|
|
103
155
|
}
|
package/dist/src/theme.css
CHANGED
|
@@ -30,11 +30,14 @@
|
|
|
30
30
|
--tracking-title: -0.018em; /* 20-24px — section titles */
|
|
31
31
|
--tracking-heading: -0.01em; /* <= 18px — labels; still a heading, not a headline */
|
|
32
32
|
|
|
33
|
-
/* Sub-`xs` type.
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
33
|
+
/* Sub-`xs` type. These exist because the marketing site had ~450
|
|
34
|
+
hand-written `text-[10px]` / `text-[11px]` classes: the tier was real, it
|
|
35
|
+
just wasn't in the system, so every page improvised it.
|
|
36
|
+
|
|
37
|
+
In product UI there is exactly ONE sanctioned use — `typography-micro`,
|
|
38
|
+
the uppercase field label above a value — and it earns the exception by
|
|
39
|
+
never carrying a sentence. Everything a person actually *reads* stays at
|
|
40
|
+
`text-xs` (12px) or above. `--text-3xs` remains marketing-only. */
|
|
38
41
|
--text-2xs: 11px;
|
|
39
42
|
--text-2xs--line-height: 1.45;
|
|
40
43
|
--text-3xs: 10px;
|
package/dist/styles.d.ts
CHANGED
|
@@ -35,6 +35,34 @@ export declare const fieldBackgroundDisabledMixin = "bg-surface-accent-200/50 da
|
|
|
35
35
|
export declare const fieldBackgroundHoverMixin = "hover:bg-surface-accent-200/70 hover:dark:bg-white/[0.09]";
|
|
36
36
|
export declare const defaultBorderMixin = "border-surface-200 dark:border-surface-700/60 ";
|
|
37
37
|
export declare const paperMixin = "bg-white rounded-lg dark:bg-surface-900 border border-surface-200 dark:border-surface-700";
|
|
38
|
-
export declare const cardMixin = "bg-white dark:bg-surface-900 rounded-
|
|
38
|
+
export declare const cardMixin = "bg-white dark:bg-surface-900 rounded-xl border border-surface-200 dark:border-surface-700/60";
|
|
39
|
+
/**
|
|
40
|
+
* An inset well: code, a query, a log tail, a connection string.
|
|
41
|
+
*
|
|
42
|
+
* It must read as *recessed into* the surface holding it, which means darker
|
|
43
|
+
* than that surface in dark mode — `surface-950` under a `surface-900` card.
|
|
44
|
+
* The obvious-looking `surface-800` is a trap: `#111111` is LIGHTER than the
|
|
45
|
+
* `#0a0a0a` card around it, so the well appears to float above the thing it is
|
|
46
|
+
* set into. Pair with `font-mono`; this mixin carries the surface only.
|
|
47
|
+
*/
|
|
48
|
+
export declare const codeSurfaceMixin = "bg-surface-100 dark:bg-surface-950 rounded-md";
|
|
49
|
+
/**
|
|
50
|
+
* The accent, used as TEXT.
|
|
51
|
+
*
|
|
52
|
+
* `--color-primary` (#0070F4) is tuned to be a fill — white text on it, it on
|
|
53
|
+
* white. Read as text on our dark surfaces it lands at **4.36:1 on a
|
|
54
|
+
* `surface-900` card** (measured), which is below AA for body-sized text, and
|
|
55
|
+
* every accent link in the product sits on exactly that surface. On the page's
|
|
56
|
+
* `surface-950` it only reaches 4.62:1, so the margin was never real.
|
|
57
|
+
*
|
|
58
|
+
* `--color-primary-light` is the same hue lifted 0.15 in OKLCH lightness and
|
|
59
|
+
* measures **7.34:1** on the same card. It is indistinguishable as "the blue"
|
|
60
|
+
* and comfortably legible, so dark mode swaps to it for text.
|
|
61
|
+
*
|
|
62
|
+
* Use this for links, accent labels and any accent-coloured type. It is NOT for
|
|
63
|
+
* fills — a filled button keeps `bg-primary`, where the contrast question runs
|
|
64
|
+
* the other way and #0070F4 is already correct.
|
|
65
|
+
*/
|
|
66
|
+
export declare const accentTextMixin = "text-primary dark:text-primary-light";
|
|
39
67
|
export declare const cardClickableMixin = "hover:bg-primary/5 dark:hover:bg-primary/5 cursor-pointer transition-colors duration-150";
|
|
40
68
|
export declare const cardSelectedMixin = "bg-primary-bg/30 dark:bg-primary-bg/10 ring-1 ring-primary/75";
|
package/dist/theme.css
CHANGED
|
@@ -30,11 +30,14 @@
|
|
|
30
30
|
--tracking-title: -0.018em; /* 20-24px — section titles */
|
|
31
31
|
--tracking-heading: -0.01em; /* <= 18px — labels; still a heading, not a headline */
|
|
32
32
|
|
|
33
|
-
/* Sub-`xs` type.
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
33
|
+
/* Sub-`xs` type. These exist because the marketing site had ~450
|
|
34
|
+
hand-written `text-[10px]` / `text-[11px]` classes: the tier was real, it
|
|
35
|
+
just wasn't in the system, so every page improvised it.
|
|
36
|
+
|
|
37
|
+
In product UI there is exactly ONE sanctioned use — `typography-micro`,
|
|
38
|
+
the uppercase field label above a value — and it earns the exception by
|
|
39
|
+
never carrying a sentence. Everything a person actually *reads* stays at
|
|
40
|
+
`text-xs` (12px) or above. `--text-3xs` remains marketing-only. */
|
|
38
41
|
--text-2xs: 11px;
|
|
39
42
|
--text-2xs--line-height: 1.45;
|
|
40
43
|
--text-3xs: 10px;
|
|
@@ -5,7 +5,21 @@ export type ChipColorScheme = {
|
|
|
5
5
|
darkColor?: string;
|
|
6
6
|
/** Text color override for dark mode */
|
|
7
7
|
darkText?: string;
|
|
8
|
+
/**
|
|
9
|
+
* Ink for the `outlined` variant, which has no fill of its own.
|
|
10
|
+
*
|
|
11
|
+
* `text`/`darkText` are the ink ON the chip's own background. An outlined
|
|
12
|
+
* chip drops that background and sits on the PAGE, so the same value is
|
|
13
|
+
* being asked to be legible against two different surfaces at once — and
|
|
14
|
+
* for most hues it cannot be. Splitting the roles is what lets the filled
|
|
15
|
+
* ink follow its fill (often dark ink on a bright chip) without dragging
|
|
16
|
+
* the outlined variant down with it.
|
|
17
|
+
*/
|
|
18
|
+
outlineText?: string;
|
|
19
|
+
/** Outlined-variant ink in dark mode. */
|
|
20
|
+
darkOutlineText?: string;
|
|
8
21
|
};
|
|
22
|
+
export declare function contrastRatio(a: string, b: string): number;
|
|
9
23
|
export declare const CHIP_HUES: readonly ["blue", "cyan", "teal", "green", "yellow", "orange", "red", "pink", "purple", "gray", "indigo", "violet", "fuchsia", "rose", "emerald"];
|
|
10
24
|
export type ChipHue = typeof CHIP_HUES[number];
|
|
11
25
|
/**
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
/**
|
|
3
|
+
* Does this error mean a chunk could not be loaded — rather than the chunk's
|
|
4
|
+
* own code throwing once it did load?
|
|
5
|
+
*
|
|
6
|
+
* Errors raised anywhere are matched, not just ones `loadChunk` produced: React
|
|
7
|
+
* `lazy()` calls that still import directly, and Vite's own CSS preloads, fail
|
|
8
|
+
* the same way and deserve the same recovery.
|
|
9
|
+
*/
|
|
10
|
+
export declare function isChunkLoadError(error: unknown): boolean;
|
|
11
|
+
/**
|
|
12
|
+
* Run a dynamic import, retrying once before declaring the chunk unreachable.
|
|
13
|
+
*
|
|
14
|
+
* Errors that are not chunk load failures — the imported module throwing while
|
|
15
|
+
* it evaluates, say — are re-thrown untouched and never retried: running a
|
|
16
|
+
* module's side effects twice is worse than the original failure.
|
|
17
|
+
*/
|
|
18
|
+
export declare function loadChunk<T>(loader: () => Promise<T>): Promise<T>;
|
|
19
|
+
/**
|
|
20
|
+
* `React.lazy` with the retry above. A drop-in replacement — use it for every
|
|
21
|
+
* lazy route or dialog, since any one of them can be the first chunk a stale
|
|
22
|
+
* tab reaches for.
|
|
23
|
+
*
|
|
24
|
+
* Note that React caches the rejection: once a lazy component has failed, later
|
|
25
|
+
* renders re-throw without calling the loader again. Resetting an error boundary
|
|
26
|
+
* therefore cannot recover it, which is why the boundary offers a reload.
|
|
27
|
+
*/
|
|
28
|
+
export declare function lazyChunk<T extends React.ComponentType<any>>(loader: () => Promise<{
|
|
29
|
+
default: T;
|
|
30
|
+
}>): React.LazyExoticComponent<T>;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import React from "react";
|
|
2
|
-
import type { CollectionViewMode, CollectionViewSize, CollectionPropertyConfig, CollectionDataController, CellRendererOverride, CollectionSelectionController, KanbanPropertyOption } from "./CollectionViewTypes";
|
|
2
|
+
import type { CollectionViewMode, CollectionViewSize, CollectionPropertyConfig, CollectionDataController, CellRendererOverride, CollectionCustomViewEntry, CollectionSelectionController, KanbanPropertyOption } from "./CollectionViewTypes";
|
|
3
3
|
/**
|
|
4
4
|
* Props for the headless {@link CollectionView} component.
|
|
5
5
|
*
|
|
@@ -76,6 +76,26 @@ export interface CollectionViewProps<T = Record<string, unknown>> {
|
|
|
76
76
|
cellRenderer?: CellRendererOverride<T>;
|
|
77
77
|
/** Custom empty state component */
|
|
78
78
|
emptyComponent?: React.ReactNode;
|
|
79
|
+
/**
|
|
80
|
+
* Additional ways to render these rows, keyed by view mode. A key here can
|
|
81
|
+
* be named by `viewMode`, `defaultViewMode` and `enabledViews`, and gets
|
|
82
|
+
* its own entry in the view switcher.
|
|
83
|
+
*
|
|
84
|
+
* A custom view is another rendering of the *same* `dataController`, so it
|
|
85
|
+
* inherits the toolbar's search, the loading state and selection. A
|
|
86
|
+
* component that fetches its own data does not want to be a view mode —
|
|
87
|
+
* the toolbar above it would be describing a query it does not render.
|
|
88
|
+
*
|
|
89
|
+
* @example
|
|
90
|
+
* ```tsx
|
|
91
|
+
* <CollectionView
|
|
92
|
+
* customViews={{ map: { name: "Map", Component: MapView } }}
|
|
93
|
+
* enabledViews={["table", "map"]}
|
|
94
|
+
* defaultViewMode="map"
|
|
95
|
+
* />
|
|
96
|
+
* ```
|
|
97
|
+
*/
|
|
98
|
+
customViews?: Record<string, CollectionCustomViewEntry<T>>;
|
|
79
99
|
/** Actions rendered at the start (left) of the toolbar */
|
|
80
100
|
toolbarActionsStart?: React.ReactNode;
|
|
81
101
|
/** Actions rendered at the end (right) of the toolbar */
|
|
@@ -118,4 +138,4 @@ export interface CollectionViewProps<T = Record<string, unknown>> {
|
|
|
118
138
|
*
|
|
119
139
|
* @group Collection View
|
|
120
140
|
*/
|
|
121
|
-
export declare function CollectionView<T extends Record<string, unknown> = Record<string, unknown>>({ dataController, properties, propertiesOrder, displayedColumnIds, idProperty, titleProperty, title, viewMode: viewModeProp, defaultViewMode, enabledViews, onViewModeChange: onViewModeChangeProp, size: sizeProp, defaultSize, onSizeChange: onSizeChangeProp, kanbanProperty: kanbanPropertyProp, kanbanPropertyOptions, onKanbanPropertyChange: onKanbanPropertyChangeProp, onRowClick, onRowCreate, canCreate, selectionEnabled, selectionController, highlightedItems, onMultipleDelete, cellRenderer, emptyComponent, toolbarActionsStart, toolbarActionsEnd, hideToolbar, hoverRow, onColumnResize, onColumnsOrderChange, className }: CollectionViewProps<T>): React.JSX.Element;
|
|
141
|
+
export declare function CollectionView<T extends Record<string, unknown> = Record<string, unknown>>({ dataController, properties, propertiesOrder, displayedColumnIds, idProperty, titleProperty, title, viewMode: viewModeProp, defaultViewMode, enabledViews, onViewModeChange: onViewModeChangeProp, size: sizeProp, defaultSize, onSizeChange: onSizeChangeProp, kanbanProperty: kanbanPropertyProp, kanbanPropertyOptions, onKanbanPropertyChange: onKanbanPropertyChangeProp, onRowClick, onRowCreate, canCreate, selectionEnabled, selectionController, highlightedItems, onMultipleDelete, cellRenderer, emptyComponent, customViews, toolbarActionsStart, toolbarActionsEnd, hideToolbar, hoverRow, onColumnResize, onColumnsOrderChange, className }: CollectionViewProps<T>): React.JSX.Element;
|
|
@@ -8,6 +8,16 @@ export type CollectionViewToolbarProps = {
|
|
|
8
8
|
viewMode: CollectionViewMode;
|
|
9
9
|
onViewModeChange?: (mode: CollectionViewMode) => void;
|
|
10
10
|
enabledViews?: CollectionViewMode[];
|
|
11
|
+
/**
|
|
12
|
+
* Custom view modes, supplying the label and icon for their switcher
|
|
13
|
+
* entries. Without these an `enabledViews` key outside the four built-ins
|
|
14
|
+
* has no label and no icon to draw.
|
|
15
|
+
*/
|
|
16
|
+
customViews?: {
|
|
17
|
+
key: string;
|
|
18
|
+
name: string;
|
|
19
|
+
icon?: React.ReactNode;
|
|
20
|
+
}[];
|
|
11
21
|
size?: CollectionViewSize;
|
|
12
22
|
onSizeChange?: (size: CollectionViewSize) => void;
|
|
13
23
|
searchString?: string;
|
|
@@ -28,4 +38,4 @@ export type CollectionViewToolbarProps = {
|
|
|
28
38
|
*
|
|
29
39
|
* @group Collection View
|
|
30
40
|
*/
|
|
31
|
-
export declare function CollectionViewToolbar({ viewMode, onViewModeChange, enabledViews, size, onSizeChange, searchString, onSearchChange, loading, actionsStart, actionsEnd, compact, kanbanPropertyOptions, selectedKanbanProperty, onKanbanPropertyChange, }: CollectionViewToolbarProps): React.JSX.Element;
|
|
41
|
+
export declare function CollectionViewToolbar({ viewMode, onViewModeChange, enabledViews, customViews, size, onSizeChange, searchString, onSearchChange, loading, actionsStart, actionsEnd, compact, kanbanPropertyOptions, selectedKanbanProperty, onKanbanPropertyChange, }: CollectionViewToolbarProps): React.JSX.Element;
|
|
@@ -1,7 +1,12 @@
|
|
|
1
1
|
import React from "react";
|
|
2
2
|
import type { VirtualTableSortKey } from "../../components/VirtualTable/VirtualTableProps";
|
|
3
|
-
/**
|
|
4
|
-
|
|
3
|
+
/**
|
|
4
|
+
* Supported view modes.
|
|
5
|
+
*
|
|
6
|
+
* Any other string is a key into the `customViews` prop. The `(string & {})`
|
|
7
|
+
* arm keeps the four built-ins in autocomplete while admitting those keys.
|
|
8
|
+
*/
|
|
9
|
+
export type CollectionViewMode = "table" | "cards" | "kanban" | "list" | (string & {});
|
|
5
10
|
/** Supported collection sizes */
|
|
6
11
|
export type CollectionViewSize = "xs" | "s" | "m" | "l" | "xl";
|
|
7
12
|
/**
|
|
@@ -106,3 +111,36 @@ export interface KanbanPropertyOption {
|
|
|
106
111
|
key: string;
|
|
107
112
|
label: string;
|
|
108
113
|
}
|
|
114
|
+
/**
|
|
115
|
+
* What a custom view's component receives — the same set the built-in table,
|
|
116
|
+
* card, list and kanban views are given, so a custom view starts at parity
|
|
117
|
+
* with them and inherits the toolbar's search, the empty state and selection.
|
|
118
|
+
*/
|
|
119
|
+
export interface CollectionCustomViewProps<T = Record<string, unknown>> {
|
|
120
|
+
dataController: CollectionDataController<T>;
|
|
121
|
+
properties: Record<string, CollectionPropertyConfig>;
|
|
122
|
+
propertiesOrder?: string[];
|
|
123
|
+
idProperty?: string;
|
|
124
|
+
titleProperty?: string;
|
|
125
|
+
onRowClick?: (row: T) => void;
|
|
126
|
+
onRowCreate?: (defaults?: Record<string, unknown>) => void;
|
|
127
|
+
canCreate?: boolean;
|
|
128
|
+
cellRenderer?: CellRendererOverride<T>;
|
|
129
|
+
selectionEnabled?: boolean;
|
|
130
|
+
selectionController?: CollectionSelectionController<T>;
|
|
131
|
+
highlightedItems?: T[];
|
|
132
|
+
emptyComponent?: React.ReactNode;
|
|
133
|
+
size?: CollectionViewSize;
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* A custom view registration. Either the component on its own, or the
|
|
137
|
+
* component with the label and icon its switcher entry should carry.
|
|
138
|
+
*
|
|
139
|
+
* Without a `name` the switcher falls back to the key, which is legible but
|
|
140
|
+
* rarely what you want on screen.
|
|
141
|
+
*/
|
|
142
|
+
export type CollectionCustomViewEntry<T = Record<string, unknown>> = React.ComponentType<CollectionCustomViewProps<T>> | {
|
|
143
|
+
name?: string;
|
|
144
|
+
icon?: React.ReactNode;
|
|
145
|
+
Component: React.ComponentType<CollectionCustomViewProps<T>>;
|
|
146
|
+
};
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rebasepro/ui",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "0.14.
|
|
4
|
+
"version": "0.14.2-canary.g27a129e",
|
|
5
5
|
"description": "Rebase design system — React components and design tokens built on Radix UI, Tailwind CSS and lucide-react",
|
|
6
6
|
"funding": {
|
|
7
7
|
"url": "https://github.com/sponsors/rebaseco"
|
package/src/components/Chip.tsx
CHANGED
|
@@ -82,6 +82,16 @@ export function Chip({
|
|
|
82
82
|
|
|
83
83
|
if (hasScheme) {
|
|
84
84
|
if (outlined) {
|
|
85
|
+
// An outlined chip has no fill, so it needs the ink meant for the
|
|
86
|
+
// PAGE, not the ink meant for the fill it just dropped. Those are
|
|
87
|
+
// different colours for most hues — a bright `solid` chip carries
|
|
88
|
+
// dark ink, which on a near-black page would be invisible.
|
|
89
|
+
if (!error && usedColorScheme) {
|
|
90
|
+
const outlineInk = dark
|
|
91
|
+
? usedColorScheme.darkOutlineText ?? usedColorScheme.darkText
|
|
92
|
+
: usedColorScheme.outlineText;
|
|
93
|
+
if (outlineInk) textColor = outlineInk;
|
|
94
|
+
}
|
|
85
95
|
bgColor = getRgba(textColor, dark ? 0.1 : 0.06);
|
|
86
96
|
border = `1px solid ${getRgba(textColor, dark ? 0.2 : 0.14)}`;
|
|
87
97
|
} else if (error) {
|
|
@@ -6,6 +6,16 @@ import { iconSize } from "../icons/Icon";
|
|
|
6
6
|
import { Typography } from "./Typography";
|
|
7
7
|
import { Button } from "./Button";
|
|
8
8
|
import { cls } from "../util";
|
|
9
|
+
import { isChunkLoadError } from "../util/lazy_chunk";
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* The one error a user can fix themselves. A deploy replaces the hashed chunk
|
|
13
|
+
* files, so a tab opened before it cannot load any lazy view it has not already
|
|
14
|
+
* fetched — with the browser's raw message ("Failed to fetch dynamically
|
|
15
|
+
* imported module: …") this reads as a broken build rather than a stale tab.
|
|
16
|
+
*/
|
|
17
|
+
const CHUNK_ERROR_DESCRIPTION =
|
|
18
|
+
"This app was updated while the tab was open, so part of it could not be loaded. Reload to continue.";
|
|
9
19
|
|
|
10
20
|
/**
|
|
11
21
|
* Checks whether the error message relates to missing permissions or
|
|
@@ -86,15 +96,29 @@ showDetails: false });
|
|
|
86
96
|
}
|
|
87
97
|
|
|
88
98
|
private renderInline() {
|
|
99
|
+
const isStaleChunk = isChunkLoadError(this.state.error);
|
|
89
100
|
return (
|
|
90
|
-
<div className="flex flex-col m-2">
|
|
101
|
+
<div className="flex flex-col m-2 items-start">
|
|
91
102
|
<div className="flex items-center m-2">
|
|
92
|
-
|
|
93
|
-
|
|
103
|
+
{isStaleChunk
|
|
104
|
+
? <RefreshCwIcon className={"text-text-secondary dark:text-text-secondary-dark"}
|
|
105
|
+
size={iconSize.small}/>
|
|
106
|
+
: <AlertCircleIcon className={"text-red-500 dark:text-red-400"} size={iconSize.small}/>}
|
|
107
|
+
<div className="ml-4">{isStaleChunk ? "New version available" : "Error"}</div>
|
|
94
108
|
</div>
|
|
95
109
|
<Typography variant={"caption"}>
|
|
96
|
-
{
|
|
110
|
+
{isStaleChunk
|
|
111
|
+
? CHUNK_ERROR_DESCRIPTION
|
|
112
|
+
: this.state.error?.message ?? "See the error in the console"}
|
|
97
113
|
</Typography>
|
|
114
|
+
{isStaleChunk && <Button variant="outlined"
|
|
115
|
+
color="neutral"
|
|
116
|
+
size="small"
|
|
117
|
+
className="mt-3 ml-2"
|
|
118
|
+
onClick={this.handleReload}>
|
|
119
|
+
<RefreshCwIcon size={16}/>
|
|
120
|
+
Reload
|
|
121
|
+
</Button>}
|
|
98
122
|
</div>
|
|
99
123
|
);
|
|
100
124
|
}
|
|
@@ -102,14 +126,20 @@ showDetails: false });
|
|
|
102
126
|
private renderFullPage() {
|
|
103
127
|
const { error, showDetails } = this.state;
|
|
104
128
|
const isPermission = isPermissionError(error);
|
|
129
|
+
// Neither a fault nor a denial: the app is fine, this tab is behind it.
|
|
130
|
+
const isStaleChunk = !isPermission && isChunkLoadError(error);
|
|
105
131
|
|
|
106
|
-
const Icon = isPermission ? ShieldAlertIcon : AlertCircleIcon;
|
|
132
|
+
const Icon = isPermission ? ShieldAlertIcon : isStaleChunk ? RefreshCwIcon : AlertCircleIcon;
|
|
107
133
|
const title = isPermission
|
|
108
134
|
? "Access denied"
|
|
109
|
-
:
|
|
135
|
+
: isStaleChunk
|
|
136
|
+
? "New version available"
|
|
137
|
+
: "Something went wrong";
|
|
110
138
|
const description = isPermission
|
|
111
139
|
? "You don't have permission to access this resource. Please check your account permissions or contact your administrator."
|
|
112
|
-
:
|
|
140
|
+
: isStaleChunk
|
|
141
|
+
? CHUNK_ERROR_DESCRIPTION
|
|
142
|
+
: "An unexpected error occurred. You can try reloading the page or going back.";
|
|
113
143
|
|
|
114
144
|
return (
|
|
115
145
|
<div className={cls(
|
|
@@ -121,13 +151,17 @@ showDetails: false });
|
|
|
121
151
|
"flex items-center justify-center w-14 h-14 rounded-xl mb-6",
|
|
122
152
|
isPermission
|
|
123
153
|
? "bg-amber-100 dark:bg-amber-900/30"
|
|
124
|
-
:
|
|
154
|
+
: isStaleChunk
|
|
155
|
+
? "bg-surface-100 dark:bg-surface-800"
|
|
156
|
+
: "bg-red-100 dark:bg-red-900/30"
|
|
125
157
|
)}>
|
|
126
158
|
<Icon
|
|
127
159
|
size={28}
|
|
128
160
|
className={isPermission
|
|
129
161
|
? "text-amber-600 dark:text-amber-400"
|
|
130
|
-
:
|
|
162
|
+
: isStaleChunk
|
|
163
|
+
? "text-text-secondary dark:text-text-secondary-dark"
|
|
164
|
+
: "text-red-500 dark:text-red-400"}
|
|
131
165
|
/>
|
|
132
166
|
</div>
|
|
133
167
|
|
|
@@ -142,7 +176,10 @@ showDetails: false });
|
|
|
142
176
|
</Typography>
|
|
143
177
|
|
|
144
178
|
<div className="flex gap-3">
|
|
145
|
-
|
|
179
|
+
{/* React caches a `lazy()` rejection: re-mounting the
|
|
180
|
+
children re-throws without re-running the import, so
|
|
181
|
+
"Try again" cannot recover a missing chunk. */}
|
|
182
|
+
{!isStaleChunk && <Button
|
|
146
183
|
variant="outlined"
|
|
147
184
|
color="neutral"
|
|
148
185
|
size="medium"
|
|
@@ -150,7 +187,7 @@ showDetails: false });
|
|
|
150
187
|
>
|
|
151
188
|
<ArrowLeftIcon size={16}/>
|
|
152
189
|
Try again
|
|
153
|
-
</Button>
|
|
190
|
+
</Button>}
|
|
154
191
|
<Button
|
|
155
192
|
variant="filled"
|
|
156
193
|
color="primary"
|
|
@@ -162,7 +199,9 @@ showDetails: false });
|
|
|
162
199
|
</Button>
|
|
163
200
|
</div>
|
|
164
201
|
|
|
165
|
-
{
|
|
202
|
+
{/* For a stale chunk the message *is* the description
|
|
203
|
+
above; repeating it under a disclosure adds nothing. */}
|
|
204
|
+
{!isStaleChunk && error?.message && (
|
|
166
205
|
<div className="mt-8 w-full">
|
|
167
206
|
<button
|
|
168
207
|
onClick={this.toggleDetails}
|
|
@@ -31,7 +31,11 @@ const typographyVariants = {
|
|
|
31
31
|
body2: "p",
|
|
32
32
|
inherit: "p",
|
|
33
33
|
caption: "p",
|
|
34
|
-
button: "span"
|
|
34
|
+
button: "span",
|
|
35
|
+
lead: "p",
|
|
36
|
+
micro: "span",
|
|
37
|
+
mono: "span",
|
|
38
|
+
stat: "span"
|
|
35
39
|
};
|
|
36
40
|
|
|
37
41
|
const colorToClasses = {
|
|
@@ -57,7 +61,11 @@ const gutterBottomClasses = {
|
|
|
57
61
|
inherit: "mb-3",
|
|
58
62
|
caption: "mb-2",
|
|
59
63
|
button: "mb-2",
|
|
60
|
-
label: "mb-2"
|
|
64
|
+
label: "mb-2",
|
|
65
|
+
lead: "mb-4",
|
|
66
|
+
micro: "mb-1",
|
|
67
|
+
mono: "mb-2",
|
|
68
|
+
stat: "mb-1"
|
|
61
69
|
};
|
|
62
70
|
|
|
63
71
|
const variantToClasses = {
|
|
@@ -74,7 +82,11 @@ const variantToClasses = {
|
|
|
74
82
|
label: "typography-label",
|
|
75
83
|
inherit: "typography-inherit",
|
|
76
84
|
caption: "typography-caption",
|
|
77
|
-
button: "typography-button"
|
|
85
|
+
button: "typography-button",
|
|
86
|
+
lead: "typography-lead",
|
|
87
|
+
micro: "typography-micro",
|
|
88
|
+
mono: "typography-mono",
|
|
89
|
+
stat: "typography-stat"
|
|
78
90
|
};
|
|
79
91
|
|
|
80
92
|
export function Typography<C extends React.ElementType = "span">(
|
package/src/index.css
CHANGED
|
@@ -36,26 +36,40 @@ html.dark {
|
|
|
36
36
|
size goes up. Seven consecutive levels of semibold was a Rubik-era setting,
|
|
37
37
|
and holding 600 all the way down to text-sm made every heading shout equally
|
|
38
38
|
— a heading stops being a headline somewhere, and the scale should say where.
|
|
39
|
-
|
|
39
|
+
|
|
40
|
+
The ladder now spans three weights rather than two. 600 everywhere at the top
|
|
41
|
+
meant a page title and the section heading inside it were the same voice at
|
|
42
|
+
two sizes, so a screen had no clear first thing to read. 700 is the *display*
|
|
43
|
+
tier only — h1 and h2, page titles and stat numbers — and nothing in the UI
|
|
44
|
+
chrome (nav, labels, buttons, table headers) goes above 600.
|
|
45
|
+
|
|
46
|
+
This costs nothing on the wire: both faces are loaded as VARIABLE fonts
|
|
47
|
+
(@fontsource-variable/inter, @fontsource-variable/instrument-sans), so the
|
|
48
|
+
whole weight axis is already in the file that ships. The earlier "never above
|
|
49
|
+
600" rule was written when static weights meant every step was another
|
|
50
|
+
download; that constraint no longer exists.
|
|
40
51
|
|
|
41
52
|
With a single face carrying both headings and body, weight and tracking are
|
|
42
53
|
the only things separating a heading from the copy around it — so the tiers
|
|
43
54
|
have to actually differ, rather than all sitting at 600. */
|
|
44
55
|
|
|
45
56
|
.typography-h1 {
|
|
46
|
-
@apply text-4xl font-headers font-
|
|
57
|
+
@apply text-4xl font-headers font-bold tracking-display;
|
|
47
58
|
}
|
|
48
59
|
|
|
49
60
|
.typography-h2 {
|
|
50
|
-
@apply text-3xl font-headers font-
|
|
61
|
+
@apply text-3xl font-headers font-bold tracking-display;
|
|
51
62
|
}
|
|
52
63
|
|
|
53
64
|
.typography-h3 {
|
|
54
65
|
@apply text-2xl font-headers font-semibold tracking-title;
|
|
55
66
|
}
|
|
56
67
|
|
|
68
|
+
/* Semibold, not medium: h4 is the section heading inside a page, and at 20px
|
|
69
|
+
medium sits close enough to body copy that a long page reads as one
|
|
70
|
+
undifferentiated column. */
|
|
57
71
|
.typography-h4 {
|
|
58
|
-
@apply text-xl font-headers font-
|
|
72
|
+
@apply text-xl font-headers font-semibold tracking-title;
|
|
59
73
|
}
|
|
60
74
|
|
|
61
75
|
.typography-h5 {
|
|
@@ -98,6 +112,44 @@ html.dark {
|
|
|
98
112
|
@apply text-sm font-semibold tracking-wide;
|
|
99
113
|
}
|
|
100
114
|
|
|
115
|
+
/* ---------------------------------------------------------------------------
|
|
116
|
+
* Three tiers the product kept improvising because the system had no name for
|
|
117
|
+
* them. Each existed as hand-written utilities in several places before it
|
|
118
|
+
* existed here, which is the definition of a missing token.
|
|
119
|
+
* ------------------------------------------------------------------------- */
|
|
120
|
+
|
|
121
|
+
/* The sentence under a page title. `body2` (12px) was standing in for it, which
|
|
122
|
+
made the one line explaining what a page is FOR smaller than the page's own
|
|
123
|
+
table rows. A lead is read once, deliberately, and it gets room to be read. */
|
|
124
|
+
.typography-lead {
|
|
125
|
+
@apply text-base leading-relaxed;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/* The micro-label above a value: SCANNED, KEPT, TOOK, WHEN. Uppercase and
|
|
129
|
+
tracked so it reads as a field name rather than as copy, and small enough
|
|
130
|
+
that it never competes with the value it names.
|
|
131
|
+
|
|
132
|
+
This is the one product tier below `text-xs`. It earns the exception by never
|
|
133
|
+
carrying a sentence — it is always one or two words naming the thing directly
|
|
134
|
+
underneath it, and at 11px uppercase with this tracking it stays legible
|
|
135
|
+
where 11px prose would not. */
|
|
136
|
+
.typography-micro {
|
|
137
|
+
@apply text-2xs font-medium uppercase tracking-[0.08em];
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/* A measured value: a count, a duration, a timestamp, an id, a size.
|
|
141
|
+
`tabular-nums` is the point — proportional digits make a column of numbers
|
|
142
|
+
ragged and a live-updating counter jitter as its glyphs change width. */
|
|
143
|
+
.typography-mono {
|
|
144
|
+
@apply font-mono tabular-nums;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/* The headline number on a stat tile. Display weight, display tracking, and
|
|
148
|
+
tabular figures so a row of tiles shares a baseline grid. */
|
|
149
|
+
.typography-stat {
|
|
150
|
+
@apply text-3xl font-headers font-bold tracking-display tabular-nums;
|
|
151
|
+
}
|
|
152
|
+
|
|
101
153
|
:focus-visible {
|
|
102
154
|
@apply outline-none focus-visible:ring-2 focus-visible:ring-primary focus-visible:ring-offset-0 focus-visible:ring-offset-transparent
|
|
103
155
|
}
|
package/src/index.ts
CHANGED
|
@@ -18,6 +18,7 @@ export * from "./icons";
|
|
|
18
18
|
// =============================================================================
|
|
19
19
|
export * from "./styles";
|
|
20
20
|
export { cls } from "./util/cls";
|
|
21
|
+
export { lazyChunk, loadChunk, isChunkLoadError } from "./util/lazy_chunk";
|
|
21
22
|
export { CHIP_COLORS, CHIP_HUES, CHIP_SEED_KEYS, getColorSchemeForKey, getColorSchemeForSeed } from "./util/chip_colors";
|
|
22
23
|
export type { ChipHue, ChipTone } from "./util/chip_colors";
|
|
23
24
|
|