@rebasepro/ui 0.17.3 → 0.18.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 +4 -0
- package/dist/components/MultiSelect.d.ts +10 -0
- package/dist/index.es.js +2 -1
- package/dist/index.es.js.map +1 -1
- package/package.json +32 -23
- package/src/components/Alert.tsx +0 -118
- package/src/components/Autocomplete.tsx +0 -93
- package/src/components/Avatar.tsx +0 -73
- package/src/components/Badge.tsx +0 -44
- package/src/components/BooleanSwitch.tsx +0 -116
- package/src/components/BooleanSwitchWithLabel.tsx +0 -120
- package/src/components/Button.tsx +0 -110
- package/src/components/Card.tsx +0 -43
- package/src/components/CenteredView.tsx +0 -35
- package/src/components/Checkbox.tsx +0 -129
- package/src/components/Chip.tsx +0 -131
- package/src/components/CircularProgress.tsx +0 -53
- package/src/components/CircularProgressCenter.tsx +0 -27
- package/src/components/Collapse.tsx +0 -66
- package/src/components/ColorPicker.tsx +0 -140
- package/src/components/Container.tsx +0 -51
- package/src/components/DateTimeField.tsx +0 -363
- package/src/components/DebouncedTextField.tsx +0 -76
- package/src/components/Dialog.tsx +0 -159
- package/src/components/DialogActions.tsx +0 -29
- package/src/components/DialogContent.tsx +0 -27
- package/src/components/DialogTitle.tsx +0 -41
- package/src/components/ErrorBoundary.tsx +0 -236
- package/src/components/ExpandablePanel.tsx +0 -130
- package/src/components/FileUpload.tsx +0 -103
- package/src/components/FilterChip.tsx +0 -89
- package/src/components/IconButton.tsx +0 -101
- package/src/components/InfoLabel.tsx +0 -23
- package/src/components/InputLabel.tsx +0 -40
- package/src/components/Label.tsx +0 -32
- package/src/components/LoadingButton.tsx +0 -32
- package/src/components/Markdown.tsx +0 -55
- package/src/components/Menu.tsx +0 -114
- package/src/components/Menubar.tsx +0 -355
- package/src/components/MultiSelect.tsx +0 -433
- package/src/components/Paper.tsx +0 -23
- package/src/components/Popover.tsx +0 -162
- package/src/components/RadioGroup.tsx +0 -74
- package/src/components/ResizablePanels.tsx +0 -189
- package/src/components/SearchBar.tsx +0 -136
- package/src/components/Select.tsx +0 -326
- package/src/components/Separator.tsx +0 -28
- package/src/components/Sheet.tsx +0 -135
- package/src/components/Skeleton.tsx +0 -54
- package/src/components/Slider.tsx +0 -110
- package/src/components/Table.tsx +0 -178
- package/src/components/Tabs.tsx +0 -185
- package/src/components/TextField.tsx +0 -270
- package/src/components/TextareaAutosize.tsx +0 -368
- package/src/components/ToggleButtonGroup.tsx +0 -69
- package/src/components/Tooltip.tsx +0 -153
- package/src/components/Typography.tsx +0 -132
- package/src/components/VirtualTable/VirtualTable.performance.test.tsx +0 -90
- package/src/components/VirtualTable/VirtualTable.tsx +0 -700
- package/src/components/VirtualTable/VirtualTableCell.tsx +0 -59
- package/src/components/VirtualTable/VirtualTableHeader.tsx +0 -327
- package/src/components/VirtualTable/VirtualTableHeaderRow.tsx +0 -250
- package/src/components/VirtualTable/VirtualTableProps.tsx +0 -339
- package/src/components/VirtualTable/VirtualTableRow.tsx +0 -51
- package/src/components/VirtualTable/fields/VirtualTableDateField.tsx +0 -44
- package/src/components/VirtualTable/fields/VirtualTableInput.tsx +0 -89
- package/src/components/VirtualTable/fields/VirtualTableNumberInput.tsx +0 -71
- package/src/components/VirtualTable/fields/VirtualTableSelect.tsx +0 -107
- package/src/components/VirtualTable/fields/VirtualTableSwitch.tsx +0 -28
- package/src/components/VirtualTable/index.tsx +0 -10
- package/src/components/VirtualTable/selection/SelectionContext.tsx +0 -36
- package/src/components/VirtualTable/selection/SelectionStore.ts +0 -54
- package/src/components/VirtualTable/types.tsx +0 -47
- package/src/components/common/SelectInputLabel.tsx +0 -23
- package/src/components/index.tsx +0 -62
- package/src/hooks/PortalContainerContext.tsx +0 -50
- package/src/hooks/index.ts +0 -7
- package/src/hooks/useDebounceCallback.tsx +0 -21
- package/src/hooks/useDebounceValue.tsx +0 -20
- package/src/hooks/useDebouncedCallback.ts +0 -39
- package/src/hooks/useInjectStyles.tsx +0 -31
- package/src/hooks/useOutsideAlerter.tsx +0 -46
- package/src/icons/GitHubIcon.tsx +0 -21
- package/src/icons/HandleIcon.tsx +0 -23
- package/src/icons/Icon.tsx +0 -33
- package/src/icons/LucideIconByName.tsx +0 -117
- package/src/icons/cool_icon_keys.ts +0 -12
- package/src/icons/icon_keys.ts +0 -1936
- package/src/icons/index.ts +0 -179
- package/src/index.css +0 -169
- package/src/index.ts +0 -47
- package/src/scripts/fixIconSizes.cjs +0 -92
- package/src/scripts/fixIconSizesToConst.cjs +0 -106
- package/src/scripts/generateIconKeys.ts +0 -41
- package/src/scripts/generateIcons.ts +0 -4
- package/src/scripts/saveIconFiles.ts +0 -89
- package/src/styles.ts +0 -141
- package/src/theme.css +0 -168
- package/src/util/chip_colors.ts +0 -242
- package/src/util/cls.ts +0 -8
- package/src/util/debounce.ts +0 -28
- package/src/util/hash.ts +0 -11
- package/src/util/index.ts +0 -4
- package/src/util/key_to_icon_component.ts +0 -11
- package/src/util/lazy_chunk.ts +0 -101
- package/src/views/CardView.tsx +0 -281
- package/src/views/CollectionView/CollectionCardView.tsx +0 -265
- package/src/views/CollectionView/CollectionKanbanView.tsx +0 -246
- package/src/views/CollectionView/CollectionListView.tsx +0 -240
- package/src/views/CollectionView/CollectionTableView.tsx +0 -229
- package/src/views/CollectionView/CollectionView.tsx +0 -450
- package/src/views/CollectionView/CollectionViewToolbar.tsx +0 -242
- package/src/views/CollectionView/CollectionViewTypes.ts +0 -181
- package/src/views/CollectionView/DefaultCellRenderer.tsx +0 -404
- package/src/views/CollectionView/index.ts +0 -39
- package/src/views/CollectionView/resolveTitleProperty.ts +0 -52
- package/src/views/CollectionView/utils.ts +0 -14
- package/src/views/Kanban/Board.tsx +0 -384
- package/src/views/Kanban/BoardColumn.tsx +0 -138
- package/src/views/Kanban/BoardColumnTitle.tsx +0 -47
- package/src/views/Kanban/BoardSortableList.tsx +0 -182
- package/src/views/Kanban/board_types.ts +0 -80
- package/src/views/Kanban/index.tsx +0 -5
- package/src/views/Kanban/placement.ts +0 -62
- package/src/views/KanbanView.tsx +0 -32
- package/src/views/ListView.tsx +0 -296
- package/src/views/index.ts +0 -5
|
@@ -1,89 +0,0 @@
|
|
|
1
|
-
import fs from "fs";
|
|
2
|
-
import path from "path";
|
|
3
|
-
import { fileURLToPath } from "url";
|
|
4
|
-
|
|
5
|
-
export function saveIconFiles(iconKeys: string[]) {
|
|
6
|
-
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
7
|
-
const componentsDir = path.join(__dirname, "../icons/components");
|
|
8
|
-
|
|
9
|
-
// Clean out old generated components to remove stale files (e.g. Lucide-prefixed duplicates)
|
|
10
|
-
if (fs.existsSync(componentsDir)) {
|
|
11
|
-
fs.rmSync(componentsDir, { recursive: true });
|
|
12
|
-
}
|
|
13
|
-
fs.mkdirSync(componentsDir, { recursive: true });
|
|
14
|
-
|
|
15
|
-
// Initialise index with manual exports
|
|
16
|
-
fs.writeFileSync(path.join(__dirname, "../icons/index.ts"), 'export * from "./icon_keys";\nexport * from "./cool_icon_keys";\nexport * from "./Icon";\nexport * from "./GitHubIcon";\nexport * from "./HandleIcon";\n');
|
|
17
|
-
|
|
18
|
-
const generatedComponents = new Set<string>();
|
|
19
|
-
|
|
20
|
-
iconKeys.forEach((key: string) => {
|
|
21
|
-
if (!key) return;
|
|
22
|
-
|
|
23
|
-
// Ensure key works as component name
|
|
24
|
-
let componentName = `${key}Icon`;
|
|
25
|
-
|
|
26
|
-
// Handle names that might start with numbers like "10k" -> "_10k"
|
|
27
|
-
if (/^[0-9]/.test(componentName)) {
|
|
28
|
-
componentName = `_${componentName}`;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
if (componentName === "Icon") {
|
|
32
|
-
return;
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
if (generatedComponents.has(componentName.toLowerCase())) {
|
|
36
|
-
return;
|
|
37
|
-
}
|
|
38
|
-
generatedComponents.add(componentName.toLowerCase());
|
|
39
|
-
|
|
40
|
-
const iconComponent = `import React from "react";
|
|
41
|
-
import { IconProps } from "../Icon";
|
|
42
|
-
import { ${key} as LucideIcon } from "lucide-react";
|
|
43
|
-
import { cls } from "../../util";
|
|
44
|
-
|
|
45
|
-
/**
|
|
46
|
-
* @group Icons
|
|
47
|
-
*/
|
|
48
|
-
export const ${componentName} = React.forwardRef<SVGSVGElement, IconProps>((props, ref) => {
|
|
49
|
-
let sizeInPx: number;
|
|
50
|
-
switch (props.size) {
|
|
51
|
-
case "smallest": sizeInPx = 16; break;
|
|
52
|
-
case "small": sizeInPx = 20; break;
|
|
53
|
-
case "medium": sizeInPx = 24; break;
|
|
54
|
-
case "large": sizeInPx = 28; break;
|
|
55
|
-
default: sizeInPx = typeof props.size === "number" ? props.size : 24;
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
const colorClassesMapping: Record<string, string> = {
|
|
59
|
-
inherit: "",
|
|
60
|
-
primary: "text-primary",
|
|
61
|
-
success: "text-green-500",
|
|
62
|
-
warning: "text-yellow-500",
|
|
63
|
-
secondary: "text-secondary",
|
|
64
|
-
disabled: "text-text-disabled dark:text-text-disabled-dark",
|
|
65
|
-
error: "text-red-500"
|
|
66
|
-
};
|
|
67
|
-
|
|
68
|
-
return <LucideIcon
|
|
69
|
-
ref={ref}
|
|
70
|
-
size={sizeInPx}
|
|
71
|
-
className={cls(
|
|
72
|
-
props.color ? colorClassesMapping[props.color] : "",
|
|
73
|
-
"select-none shrink-0",
|
|
74
|
-
props.className
|
|
75
|
-
)}
|
|
76
|
-
style={props.style}
|
|
77
|
-
onClick={props.onClick}
|
|
78
|
-
/>;
|
|
79
|
-
});
|
|
80
|
-
|
|
81
|
-
${componentName}.displayName = "${componentName}";
|
|
82
|
-
`;
|
|
83
|
-
|
|
84
|
-
const filePath = path.join(componentsDir, `${componentName}.tsx`);
|
|
85
|
-
fs.writeFileSync(filePath, iconComponent);
|
|
86
|
-
|
|
87
|
-
fs.appendFileSync(path.join(__dirname, "../icons/index.ts"), `export * from "./components/${componentName}";\n`, { flag: "a" });
|
|
88
|
-
});
|
|
89
|
-
}
|
package/src/styles.ts
DELETED
|
@@ -1,141 +0,0 @@
|
|
|
1
|
-
// ---------------------------------------------------------------------------
|
|
2
|
-
// Control size scale — the single source of truth for how tall an inline
|
|
3
|
-
// control is.
|
|
4
|
-
//
|
|
5
|
-
// Every control that sits on a line with another control (buttons, text
|
|
6
|
-
// fields, selects, search bars, date fields, icon buttons, checkboxes) resolves
|
|
7
|
-
// its height from this map, so a Button and a TextField at the same `size` are
|
|
8
|
-
// pixel-identical and share a baseline. Before this existed each component
|
|
9
|
-
// carried its own map and `size="large"` meant 42px on a Button and 64px on a
|
|
10
|
-
// TextField.
|
|
11
|
-
//
|
|
12
|
-
// The scale is 28/32/40/48 — the rhythm IconButton and Checkbox already used.
|
|
13
|
-
// `xl`/`2xl` continue above it and are button-only.
|
|
14
|
-
// ---------------------------------------------------------------------------
|
|
15
|
-
|
|
16
|
-
export type ControlSize = "smallest" | "small" | "medium" | "large";
|
|
17
|
-
export type ButtonSize = ControlSize | "xl" | "2xl";
|
|
18
|
-
|
|
19
|
-
export const CONTROL_HEIGHT = {
|
|
20
|
-
smallest: 28,
|
|
21
|
-
small: 32,
|
|
22
|
-
medium: 40,
|
|
23
|
-
large: 48,
|
|
24
|
-
xl: 56,
|
|
25
|
-
"2xl": 64
|
|
26
|
-
} as const satisfies Record<ButtonSize, number>;
|
|
27
|
-
|
|
28
|
-
/** Tailwind min-height class per control size. Keep in sync with CONTROL_HEIGHT. */
|
|
29
|
-
export const controlHeightMixin = {
|
|
30
|
-
smallest: "min-h-[28px]",
|
|
31
|
-
small: "min-h-[32px]",
|
|
32
|
-
medium: "min-h-[40px]",
|
|
33
|
-
large: "min-h-[48px]",
|
|
34
|
-
xl: "min-h-[56px]",
|
|
35
|
-
"2xl": "min-h-[64px]"
|
|
36
|
-
} as const satisfies Record<ButtonSize, string>;
|
|
37
|
-
|
|
38
|
-
/** Horizontal padding paired with each size, so gutters scale with height. */
|
|
39
|
-
export const controlPaddingMixin = {
|
|
40
|
-
smallest: "px-2",
|
|
41
|
-
small: "px-2",
|
|
42
|
-
medium: "px-3",
|
|
43
|
-
large: "px-4",
|
|
44
|
-
xl: "px-6",
|
|
45
|
-
"2xl": "px-10"
|
|
46
|
-
} as const satisfies Record<ButtonSize, string>;
|
|
47
|
-
|
|
48
|
-
export const focusedDisabled = "focus-visible:ring-0 focus-visible:ring-offset-0";
|
|
49
|
-
export const focusedInvisibleMixin = "focus:bg-opacity-70 focus:bg-surface-accent-100 focus:dark:bg-white/[0.07] focus:bg-surface-accent-100/70";
|
|
50
|
-
|
|
51
|
-
/**
|
|
52
|
-
* The focus ring — the single most-seen interaction state in the product, and
|
|
53
|
-
* until now the least tended.
|
|
54
|
-
*
|
|
55
|
-
* Three dead declarations were removed rather than reshuffled:
|
|
56
|
-
* `outline-hidden` + `outline-none` — the same instruction twice.
|
|
57
|
-
* `ring-primary` + `ring-primary/50` — the first could never win.
|
|
58
|
-
* `ring-opacity-50` — Tailwind v3 syntax. v4 expresses
|
|
59
|
-
* opacity with the `/50` slash form, so
|
|
60
|
-
* this generated nothing at all.
|
|
61
|
-
*
|
|
62
|
-
* What is left is the ring that was actually rendering, at 60% rather than 50%:
|
|
63
|
-
* on `surface-900` the old value sat close enough to the field's own border to
|
|
64
|
-
* be missed at a glance, which is the one thing a focus ring may not be.
|
|
65
|
-
* `ring-offset-0` + a transparent offset stay — without them the ring draws a
|
|
66
|
-
* white halo on dark surfaces.
|
|
67
|
-
*/
|
|
68
|
-
export const focusedClasses = "z-30 outline-none ring-2 ring-primary/60 ring-offset-0 ring-offset-transparent";
|
|
69
|
-
|
|
70
|
-
/**
|
|
71
|
-
* Field surfaces.
|
|
72
|
-
*
|
|
73
|
-
* The inset top hairline is the whole difference between a field that reads as
|
|
74
|
-
* a filled rectangle and one that reads as recessed into the surface. It is a
|
|
75
|
-
* 1px inset highlight, not a gradient — the same device `.frame` already uses
|
|
76
|
-
* on the marketing site, and the flat-surface rule stands.
|
|
77
|
-
*
|
|
78
|
-
* Light mode gets none: on a white page the highlight has nothing to catch.
|
|
79
|
-
*/
|
|
80
|
-
export const fieldBackgroundMixin = "bg-surface-accent-200/50 dark:bg-white/[0.055] dark:shadow-[inset_0_1px_0_rgb(255_255_255/0.045)]";
|
|
81
|
-
export const fieldBackgroundInvisibleMixin = "bg-surface-accent-200/0 dark:bg-white/0";
|
|
82
|
-
export const fieldBackgroundDisabledMixin = "bg-surface-accent-200/50 dark:bg-white/[0.03]";
|
|
83
|
-
|
|
84
|
-
/**
|
|
85
|
-
* `transition-colors` here, because every other interactive surface in the kit
|
|
86
|
-
* has it and fields did not — `cardClickableMixin` eases, a TextField snapped.
|
|
87
|
-
* On a form of eight fields that difference is the whole feel of the screen.
|
|
88
|
-
*/
|
|
89
|
-
export const fieldBackgroundHoverMixin = "transition-colors duration-150 hover:bg-surface-accent-200/70 hover:dark:bg-white/[0.09]";
|
|
90
|
-
|
|
91
|
-
export const defaultBorderMixin = "border-surface-200 dark:border-surface-700/60";
|
|
92
|
-
|
|
93
|
-
// ---------------------------------------------------------------------------
|
|
94
|
-
// Surfaces: two kinds, and the difference is what the border is for.
|
|
95
|
-
//
|
|
96
|
-
// A **floating** surface — a menu, a dialog, a popover — sits OVER the page. It
|
|
97
|
-
// has to be legible against whatever happens to be underneath it, so its edge
|
|
98
|
-
// is definite: solid `surface-700`. That is `paperMixin`.
|
|
99
|
-
//
|
|
100
|
-
// A **page** surface — a card in the document flow — sits ON the page, and the
|
|
101
|
-
// page is already `surface-950`/`surface-900`. A solid edge there reads as a
|
|
102
|
-
// box drawn around content rather than as the content having a surface, which
|
|
103
|
-
// is why every serious caller was overriding it: 53 `<Card>` sites in the SaaS
|
|
104
|
-
// console alone re-declared this border at `/60`, the same value
|
|
105
|
-
// `defaultBorderMixin` above has always used. The component was wrong and the
|
|
106
|
-
// callers were right, so the component now says what they meant.
|
|
107
|
-
// ---------------------------------------------------------------------------
|
|
108
|
-
export const paperMixin = "bg-white rounded-lg dark:bg-surface-900 border border-surface-200 dark:border-surface-700";
|
|
109
|
-
export const cardMixin = "bg-white dark:bg-surface-900 rounded-xl border border-surface-200 dark:border-surface-700/60";
|
|
110
|
-
|
|
111
|
-
/**
|
|
112
|
-
* An inset well: code, a query, a log tail, a connection string.
|
|
113
|
-
*
|
|
114
|
-
* It must read as *recessed into* the surface holding it, which means darker
|
|
115
|
-
* than that surface in dark mode — `surface-950` under a `surface-900` card.
|
|
116
|
-
* The obvious-looking `surface-800` is a trap: `#111111` is LIGHTER than the
|
|
117
|
-
* `#0a0a0a` card around it, so the well appears to float above the thing it is
|
|
118
|
-
* set into. Pair with `font-mono`; this mixin carries the surface only.
|
|
119
|
-
*/
|
|
120
|
-
export const codeSurfaceMixin = "bg-surface-100 dark:bg-surface-950 rounded-md";
|
|
121
|
-
|
|
122
|
-
/**
|
|
123
|
-
* The accent, used as TEXT.
|
|
124
|
-
*
|
|
125
|
-
* `--color-primary` (#0070F4) is tuned to be a fill — white text on it, it on
|
|
126
|
-
* white. Read as text on our dark surfaces it lands at **4.36:1 on a
|
|
127
|
-
* `surface-900` card** (measured), which is below AA for body-sized text, and
|
|
128
|
-
* every accent link in the product sits on exactly that surface. On the page's
|
|
129
|
-
* `surface-950` it only reaches 4.62:1, so the margin was never real.
|
|
130
|
-
*
|
|
131
|
-
* `--color-primary-light` is the same hue lifted 0.15 in OKLCH lightness and
|
|
132
|
-
* measures **7.34:1** on the same card. It is indistinguishable as "the blue"
|
|
133
|
-
* and comfortably legible, so dark mode swaps to it for text.
|
|
134
|
-
*
|
|
135
|
-
* Use this for links, accent labels and any accent-coloured type. It is NOT for
|
|
136
|
-
* fills — a filled button keeps `bg-primary`, where the contrast question runs
|
|
137
|
-
* the other way and #0070F4 is already correct.
|
|
138
|
-
*/
|
|
139
|
-
export const accentTextMixin = "text-primary dark:text-primary-light";
|
|
140
|
-
export const cardClickableMixin = "hover:bg-primary/5 dark:hover:bg-primary/5 cursor-pointer transition-colors duration-150";
|
|
141
|
-
export const cardSelectedMixin = "bg-primary-bg/30 dark:bg-primary-bg/10 ring-1 ring-primary/75";
|
package/src/theme.css
DELETED
|
@@ -1,168 +0,0 @@
|
|
|
1
|
-
/* ---------------------------------------------------------------------------
|
|
2
|
-
* Design tokens — the single source for fonts, type scale and colour.
|
|
3
|
-
*
|
|
4
|
-
* This lives in its own file for one reason: Tailwind only picks up `@theme`
|
|
5
|
-
* from an UNLAYERED import. Consumers that pull the component styles in with
|
|
6
|
-
* `@import "@rebasepro/ui/index.css" layer(base)` — which the marketing site
|
|
7
|
-
* and the docs both do, to keep component CSS below their own utilities — get
|
|
8
|
-
* the layer wrapped around everything transitively, and the theme silently
|
|
9
|
-
* does not register. That is why the font stacks used to be copy-pasted into
|
|
10
|
-
* every consumer's own `@theme`.
|
|
11
|
-
*
|
|
12
|
-
* Import this file unlayered, alongside the layered index.css:
|
|
13
|
-
*
|
|
14
|
-
* @import "@rebasepro/ui/theme.css";
|
|
15
|
-
* @import "@rebasepro/ui/index.css" layer(base);
|
|
16
|
-
* ------------------------------------------------------------------------- */
|
|
17
|
-
|
|
18
|
-
@theme {
|
|
19
|
-
/* Font Families — Instrument Sans for headings, Inter for UI and body copy. */
|
|
20
|
-
--font-sans: 'Inter Variable', 'Inter', ui-sans-serif, system-ui, 'Helvetica', 'Arial', sans-serif;
|
|
21
|
-
--font-headers: 'Instrument Sans Variable', 'Instrument Sans', 'Inter Variable', 'Inter', ui-sans-serif, system-ui, sans-serif;
|
|
22
|
-
--font-mono: 'JetBrains Mono', 'Space Mono', 'Lucida Console', monospace;
|
|
23
|
-
|
|
24
|
-
/* Type scale — three tiers keyed to RENDERED SIZE, not tag depth.
|
|
25
|
-
Tailwind turns each into a `tracking-*` utility, so the admin panel, the
|
|
26
|
-
console and the marketing site all spell the same intent the same way
|
|
27
|
-
instead of each hardcoding its own em value. Pair display/title with
|
|
28
|
-
font-semibold and heading with font-medium. */
|
|
29
|
-
/* -0.025em, revised 2026-08-24 from -0.022em. The website's dev-only
|
|
30
|
-
TypeDevPanel has shipped `td: -25` as its "SHIPPED" default all along, so
|
|
31
|
-
every local preview and every design decision taken in front of one was
|
|
32
|
-
made at -0.025em while the built site served -0.022em. Of the panel's
|
|
33
|
-
eleven defaults these two — tracking and display weight — were the only
|
|
34
|
-
ones that did not match the tokens. Both are now true. */
|
|
35
|
-
--tracking-display: -0.025em; /* >= 30px — the headline tier; matches the marketing site */
|
|
36
|
-
--tracking-title: -0.018em; /* 20-24px — section titles */
|
|
37
|
-
--tracking-heading: -0.01em; /* <= 18px — labels; still a heading, not a headline */
|
|
38
|
-
|
|
39
|
-
/* Sub-`xs` type. These exist because the marketing site had ~450
|
|
40
|
-
hand-written `text-[10px]` / `text-[11px]` classes: the tier was real, it
|
|
41
|
-
just wasn't in the system, so every page improvised it.
|
|
42
|
-
|
|
43
|
-
In product UI there is exactly ONE sanctioned use — `typography-micro`,
|
|
44
|
-
the uppercase field label above a value — and it earns the exception by
|
|
45
|
-
never carrying a sentence. Everything a person actually *reads* stays at
|
|
46
|
-
`text-xs` (12px) or above. `--text-3xs` remains marketing-only. */
|
|
47
|
-
--text-2xs: 11px;
|
|
48
|
-
--text-2xs--line-height: 1.45;
|
|
49
|
-
--text-3xs: 10px;
|
|
50
|
-
--text-3xs--line-height: 1.4;
|
|
51
|
-
|
|
52
|
-
/* Display tier — above `text-4xl`, where the scale stops being a heading
|
|
53
|
-
and starts being a statement. Added 2026-08-24.
|
|
54
|
-
|
|
55
|
-
Fluid rather than fixed because the same token has to survive four
|
|
56
|
-
locales: German and French run 15-25% longer than English, and a fixed
|
|
57
|
-
112px headline that fits "The Postgres you already have" does not fit
|
|
58
|
-
"Das Postgres, das Sie bereits haben". The `vw` middle term shrinks
|
|
59
|
-
before it can overflow; the `rem` floor protects small screens, where
|
|
60
|
-
the type is already proportionally large.
|
|
61
|
-
|
|
62
|
-
Both carry their own line-height and tracking: at this size 1.1 reads as
|
|
63
|
-
a gap and -0.025em reads as loose. Weight stays on the 600 ceiling —
|
|
64
|
-
this tier separates itself by SIZE, which is the whole point of adding
|
|
65
|
-
it rather than reaching for 700. */
|
|
66
|
-
--text-display-2: clamp(2.25rem, 4.4vw, 4rem); /* 36 -> 64px */
|
|
67
|
-
--text-display-2--line-height: 1.0;
|
|
68
|
-
--text-display-2--letter-spacing: -0.028em;
|
|
69
|
-
/* 7.2vw capped at 7rem was too much: on a wide window the headline filled
|
|
70
|
-
the viewport on its own and pushed the install terminal below the fold,
|
|
71
|
-
which breaks the rule the hero exists to satisfy (SITE-STORY §5 — the
|
|
72
|
-
hero carries a product artifact). Still ~1.7x the 48px it replaced. */
|
|
73
|
-
--text-display-1: clamp(2.5rem, 5.2vw, 4.75rem); /* 40 -> 76px */
|
|
74
|
-
--text-display-1--line-height: 0.94;
|
|
75
|
-
--text-display-1--letter-spacing: -0.032em;
|
|
76
|
-
|
|
77
|
-
/* Control heights — the one scale every inline control resolves to, so a
|
|
78
|
-
Button, TextField and Select at the same size share a baseline. Mirrors
|
|
79
|
-
CONTROL_HEIGHT in styles.ts; that is the source for React components,
|
|
80
|
-
these are for everyone else.
|
|
81
|
-
|
|
82
|
-
Marketing pages and docs are plain markup and cannot import a JS mixin,
|
|
83
|
-
so without these they hand-roll their own paddings and drift: the site's
|
|
84
|
-
two hero CTAs sat at 52px and 54px, on no scale and not even matching
|
|
85
|
-
each other. Reach for `min-h-(--control-xl)` instead of a magic number. */
|
|
86
|
-
--control-smallest: 28px;
|
|
87
|
-
--control-small: 32px;
|
|
88
|
-
--control-medium: 40px;
|
|
89
|
-
--control-large: 48px;
|
|
90
|
-
--control-xl: 56px; /* buttons only */
|
|
91
|
-
--control-2xl: 64px; /* buttons only */
|
|
92
|
-
|
|
93
|
-
/* Colors */
|
|
94
|
-
--color-primary: #0070F4;
|
|
95
|
-
--color-primary-light: oklch(from var(--color-primary) calc(l + 0.15) c h);
|
|
96
|
-
--color-primary-dark: oklch(from var(--color-primary) calc(l - 0.15) c h);
|
|
97
|
-
/* rose-600. #FF5B79 was only 2.99:1 against white, so white text on it
|
|
98
|
-
and it as text both failed WCAG AA. Same hue family, 4.70:1. */
|
|
99
|
-
--color-secondary: #E11D48;
|
|
100
|
-
--color-secondary-light: oklch(from var(--color-secondary) calc(l + 0.15) c h);
|
|
101
|
-
--color-secondary-dark: oklch(from var(--color-secondary) calc(l - 0.15) c h);
|
|
102
|
-
|
|
103
|
-
--color-primary-bg: oklch(from var(--color-primary) l c h / 0.1);
|
|
104
|
-
--color-secondary-bg: oklch(from var(--color-secondary) l c h / 0.1);
|
|
105
|
-
|
|
106
|
-
/* Field Colors */
|
|
107
|
-
--color-field-disabled: rgb(224 224 226);
|
|
108
|
-
--color-field-disabled-dark: rgb(35 35 37);
|
|
109
|
-
|
|
110
|
-
/* Text Colors — using opaque values so icon strokes render solidly */
|
|
111
|
-
--color-text-primary: #212121;
|
|
112
|
-
/* #737373, not #757575. Two units, and they are the two that matter.
|
|
113
|
-
The app's light page ground is #fafafa, not #ffffff, and #757575 lands at
|
|
114
|
-
4.41:1 there — under AA for the 14px type this token carries. It clears
|
|
115
|
-
4.5 only against pure white, which is not a surface the product actually
|
|
116
|
-
uses. Every field label in light mode was failing by a whisker.
|
|
117
|
-
#737373 measures 4.54:1 on #fafafa and 4.74:1 on #ffffff, so it passes on
|
|
118
|
-
both grounds instead of the theoretical one. Same fix, and the same
|
|
119
|
-
reasoning, as the `--color-surface-500` move recorded below. */
|
|
120
|
-
--color-text-secondary: #737373;
|
|
121
|
-
/* #8b8b8b, not #9e9e9e. Measured against the field fill a disabled label
|
|
122
|
-
actually sits on (`surface-accent-200/50` over #fafafa ≈ #eef1f5),
|
|
123
|
-
#9e9e9e reached 2.36:1 — present, but not readable.
|
|
124
|
-
Full AA is not achievable here and should not be chased: 4.5:1 on that
|
|
125
|
-
fill needs #6e6e6e, which is DARKER than the #737373 an ENABLED label
|
|
126
|
-
uses, so the disabled state would read as the emphasised one. WCAG 1.4.3
|
|
127
|
-
exempts inactive controls for exactly this reason.
|
|
128
|
-
#8b8b8b lands at ~3:1 — legible at a glance, still unmistakably muted
|
|
129
|
-
against its enabled neighbour. */
|
|
130
|
-
--color-text-disabled: #8b8b8b;
|
|
131
|
-
--color-text-primary-dark: #ffffff;
|
|
132
|
-
--color-text-secondary-dark: #a0a0a9;
|
|
133
|
-
--color-text-disabled-dark: #757580;
|
|
134
|
-
|
|
135
|
-
/* Surface Colors */
|
|
136
|
-
--color-surface-50: #fafafa;
|
|
137
|
-
--color-surface-100: #f5f5f5;
|
|
138
|
-
--color-surface-200: #e5e5e5;
|
|
139
|
-
--color-surface-300: #d4d4d4;
|
|
140
|
-
--color-surface-400: #a3a3a3;
|
|
141
|
-
/* Muted text: captions, nav descriptions, table headers, eyebrows.
|
|
142
|
-
#737373 was tuned for light and measured 4.74:1 on white but only
|
|
143
|
-
4.43:1 on #000 and 4.18:1 on #0a0a0a, so every dark surface — the
|
|
144
|
-
marketing site, the console, the admin panel in dark mode — carried a
|
|
145
|
-
muted tier below AA. #797979 is the balance point: it is the single
|
|
146
|
-
value where the white and #111111 curves cross, at 4.35:1 on each,
|
|
147
|
-
and it clears 4.5:1 outright on the two backgrounds that carry most of
|
|
148
|
-
this token (4.82:1 on #000, 4.55:1 on #0a0a0a). */
|
|
149
|
-
--color-surface-500: #797979;
|
|
150
|
-
--color-surface-600: #404040;
|
|
151
|
-
--color-surface-700: #262626;
|
|
152
|
-
--color-surface-800: #111111;
|
|
153
|
-
--color-surface-900: #0a0a0a;
|
|
154
|
-
--color-surface-950: #000000;
|
|
155
|
-
|
|
156
|
-
/* Surface Accent Colors */
|
|
157
|
-
--color-surface-accent-50: #f8fafc;
|
|
158
|
-
--color-surface-accent-100: #f1f5f9;
|
|
159
|
-
--color-surface-accent-200: #e2e8f0;
|
|
160
|
-
--color-surface-accent-300: #cbd5e1;
|
|
161
|
-
--color-surface-accent-400: #94a3b8;
|
|
162
|
-
--color-surface-accent-500: #64748b;
|
|
163
|
-
--color-surface-accent-600: #475569;
|
|
164
|
-
--color-surface-accent-700: #334155;
|
|
165
|
-
--color-surface-accent-800: #1e293b;
|
|
166
|
-
--color-surface-accent-900: #172033;
|
|
167
|
-
--color-surface-accent-950: #0f172a;
|
|
168
|
-
}
|
package/src/util/chip_colors.ts
DELETED
|
@@ -1,242 +0,0 @@
|
|
|
1
|
-
import { hashString } from "./hash";
|
|
2
|
-
|
|
3
|
-
export type ChipColorScheme = {
|
|
4
|
-
color: string;
|
|
5
|
-
text: string;
|
|
6
|
-
/** Background color override for dark mode */
|
|
7
|
-
darkColor?: string;
|
|
8
|
-
/** Text color override for dark mode */
|
|
9
|
-
darkText?: string;
|
|
10
|
-
/**
|
|
11
|
-
* Ink for the `outlined` variant, which has no fill of its own.
|
|
12
|
-
*
|
|
13
|
-
* `text`/`darkText` are the ink ON the chip's own background. An outlined
|
|
14
|
-
* chip drops that background and sits on the PAGE, so the same value is
|
|
15
|
-
* being asked to be legible against two different surfaces at once — and
|
|
16
|
-
* for most hues it cannot be. Splitting the roles is what lets the filled
|
|
17
|
-
* ink follow its fill (often dark ink on a bright chip) without dragging
|
|
18
|
-
* the outlined variant down with it.
|
|
19
|
-
*/
|
|
20
|
-
outlineText?: string;
|
|
21
|
-
/** Outlined-variant ink in dark mode. */
|
|
22
|
-
darkOutlineText?: string;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
/* ────────────────────────────────────────────────────────────────────────────
|
|
26
|
-
Contrast
|
|
27
|
-
────────────────────────────────────────────────────────────────────────────
|
|
28
|
-
|
|
29
|
-
Chip ink is DERIVED, not hand-picked. It used to be written down per tone —
|
|
30
|
-
`"#fff"` on every `solid` background, the hue's `pale` on every `deep` one —
|
|
31
|
-
and an audit of all 120 hue/tone/mode pairs found **63 of them below WCAG AA**.
|
|
32
|
-
The worst was white on `teal.solid` at **1.76:1**, which is not a near miss;
|
|
33
|
-
it is unreadable. The palette is an Airtable-style one whose mid stops are
|
|
34
|
-
bright enough to need dark ink, and hardcoding light ink ignored that.
|
|
35
|
-
|
|
36
|
-
So the ink is measured against the background it will actually sit on, and
|
|
37
|
-
pushed toward black or white only as far as it must go to clear the floor.
|
|
38
|
-
Starting from the hue's own dark/light tints rather than from flat `#000`
|
|
39
|
-
and `#fff` keeps the family looking like a family: `blue.solid` gets a very
|
|
40
|
-
dark navy, not black.
|
|
41
|
-
|
|
42
|
-
Deriving it also means a new hue cannot be added below AA — there is no
|
|
43
|
-
per-tone ink to forget to check. */
|
|
44
|
-
|
|
45
|
-
/** WCAG floor, plus a little margin so rounding cannot drop a pair below 4.5. */
|
|
46
|
-
const CONTRAST_TARGET = 4.6;
|
|
47
|
-
|
|
48
|
-
/** Page backgrounds the outlined variant sits on: `bg-white` and `surface-950`. */
|
|
49
|
-
const PAGE_LIGHT = "#ffffff";
|
|
50
|
-
const PAGE_DARK = "#0a0a0a";
|
|
51
|
-
|
|
52
|
-
function toRgb(hex: string): [number, number, number] {
|
|
53
|
-
let h = hex.replace("#", "");
|
|
54
|
-
if (h.length === 3) h = h.split("").map(c => c + c).join("");
|
|
55
|
-
return [0, 2, 4].map(i => parseInt(h.slice(i, i + 2), 16)) as [number, number, number];
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
function toHex(rgb: number[]): string {
|
|
59
|
-
return "#" + rgb.map(v => Math.round(Math.max(0, Math.min(255, v))).toString(16).padStart(2, "0")).join("");
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
function relativeLuminance(hex: string): number {
|
|
63
|
-
const [r, g, b] = toRgb(hex).map(v => {
|
|
64
|
-
const c = v / 255;
|
|
65
|
-
return c <= 0.03928 ? c / 12.92 : Math.pow((c + 0.055) / 1.055, 2.4);
|
|
66
|
-
});
|
|
67
|
-
return 0.2126 * r + 0.7152 * g + 0.0722 * b;
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
export function contrastRatio(a: string, b: string): number {
|
|
71
|
-
const x = relativeLuminance(a);
|
|
72
|
-
const y = relativeLuminance(b);
|
|
73
|
-
const [hi, lo] = x > y ? [x, y] : [y, x];
|
|
74
|
-
return (hi + 0.05) / (lo + 0.05);
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
function mixHex(from: string, to: string, t: number): string {
|
|
78
|
-
const A = toRgb(from);
|
|
79
|
-
const B = toRgb(to);
|
|
80
|
-
return toHex([0, 1, 2].map(i => A[i] + (B[i] - A[i]) * t));
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
/**
|
|
84
|
-
* Walk a tinted ink toward black or white until it clears the floor on `bg`.
|
|
85
|
-
*
|
|
86
|
-
* Stops at the first passing step rather than going all the way, so the ink
|
|
87
|
-
* keeps as much of its hue as legibility allows.
|
|
88
|
-
*/
|
|
89
|
-
function push(base: string, toward: string, bg: string): string {
|
|
90
|
-
for (let t = 0; t <= 1.0001; t += 0.02) {
|
|
91
|
-
const candidate = mixHex(base, toward, t);
|
|
92
|
-
if (contrastRatio(candidate, bg) >= CONTRAST_TARGET) return candidate;
|
|
93
|
-
}
|
|
94
|
-
return toward;
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
/** The ink for a chip filled with `bg`: whichever direction has more headroom. */
|
|
98
|
-
function inkOn(stops: HueStops, bg: string): string {
|
|
99
|
-
const dark = push(stops.text, "#000000", bg);
|
|
100
|
-
const light = push(stops.onDeep ?? stops.pale, "#ffffff", bg);
|
|
101
|
-
return contrastRatio(dark, bg) >= contrastRatio(light, bg) ? dark : light;
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
/**
|
|
105
|
-
* The hues a chip can take, and the four stops each one is built from.
|
|
106
|
-
*
|
|
107
|
-
* `pale` and `mid` are backgrounds for dark text; `solid` and `deep` are
|
|
108
|
-
* backgrounds for light text. `text` is the dark ink used on the two pale
|
|
109
|
-
* stops, and `onDeep` the light ink on `deep` — it defaults to `pale`, and is
|
|
110
|
-
* only set where a hue needs a tint of its own to stay legible.
|
|
111
|
-
*/
|
|
112
|
-
type HueStops = {
|
|
113
|
-
pale: string;
|
|
114
|
-
mid: string;
|
|
115
|
-
solid: string;
|
|
116
|
-
deep: string;
|
|
117
|
-
text: string;
|
|
118
|
-
onDeep?: string;
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
export const CHIP_HUES = [
|
|
122
|
-
"blue", "cyan", "teal", "green", "yellow", "orange", "red", "pink", "purple", "gray",
|
|
123
|
-
"indigo", "violet", "fuchsia", "rose", "emerald"
|
|
124
|
-
] as const;
|
|
125
|
-
|
|
126
|
-
export type ChipHue = typeof CHIP_HUES[number];
|
|
127
|
-
|
|
128
|
-
/**
|
|
129
|
-
* How light or saturated a chip of a given hue is.
|
|
130
|
-
*
|
|
131
|
-
* A hue on its own (`"blue"`) means `Lighter` — the tone the picker offers and
|
|
132
|
-
* the one seeded chips used to be limited to.
|
|
133
|
-
*/
|
|
134
|
-
export type ChipTone = "Lighter" | "Light" | "Dark" | "Darker";
|
|
135
|
-
|
|
136
|
-
export type ChipColorKey = ChipHue | `${ChipHue}${ChipTone}`;
|
|
137
|
-
|
|
138
|
-
const HUE_STOPS: Record<ChipHue, HueStops> = {
|
|
139
|
-
blue: { pale: "#cfdfff", mid: "#9cc7ff", solid: "#2d7ff9", deep: "#2750ae", text: "#102046" },
|
|
140
|
-
cyan: { pale: "#d0f0fd", mid: "#77d1f3", solid: "#18bfff", deep: "#0b76b7", text: "#04283f" },
|
|
141
|
-
teal: { pale: "#c2f5e9", mid: "#72ddc3", solid: "#20d9d2", deep: "#06a09b", text: "#012524", onDeep: "#daf3e9" },
|
|
142
|
-
green: { pale: "#d1f7c4", mid: "#93e088", solid: "#20c933", deep: "#338a17", text: "#0b1d05" },
|
|
143
|
-
yellow: { pale: "#ffeab6", mid: "#ffd66e", solid: "#fcb400", deep: "#b87503", text: "#3b2501" },
|
|
144
|
-
orange: { pale: "#fee2d5", mid: "#ffa981", solid: "#ff6f2c", deep: "#d74d26", text: "#6b2613" },
|
|
145
|
-
red: { pale: "#ffdce5", mid: "#ff9eb7", solid: "#f82b60", deep: "#ba1e45", text: "#4c0c1c" },
|
|
146
|
-
pink: { pale: "#ffdaf6", mid: "#f99de2", solid: "#ff08c2", deep: "#b2158b", text: "#400832" },
|
|
147
|
-
purple: { pale: "#ede2fe", mid: "#cdb0ff", solid: "#8b46ff", deep: "#6b1cb0", text: "#280b42" },
|
|
148
|
-
gray: { pale: "#eeeeee", mid: "#cccccc", solid: "#666666", deep: "#444444", text: "#040404" },
|
|
149
|
-
indigo: { pale: "#e0e7ff", mid: "#a5b4fc", solid: "#6366f1", deep: "#4f46e5", text: "#312e81" },
|
|
150
|
-
violet: { pale: "#ede9fe", mid: "#c4b5fd", solid: "#8b5cf6", deep: "#7c3aed", text: "#4c1d95" },
|
|
151
|
-
fuchsia: { pale: "#fae8ff", mid: "#f0abfc", solid: "#d946ef", deep: "#c026d3", text: "#701a75" },
|
|
152
|
-
rose: { pale: "#ffe4e6", mid: "#fda4af", solid: "#f43f5e", deep: "#e11d48", text: "#881337" },
|
|
153
|
-
emerald: { pale: "#d1fae5", mid: "#6ee7b7", solid: "#10b981", deep: "#059669", text: "#064e3b" }
|
|
154
|
-
};
|
|
155
|
-
|
|
156
|
-
/**
|
|
157
|
-
* One tone of one hue.
|
|
158
|
-
*
|
|
159
|
-
* Backgrounds are the palette's, unchanged — the stops were not the problem and
|
|
160
|
-
* moving them would have restyled every chip in the product. Only the ink moved,
|
|
161
|
-
* and it moved to wherever the measurement says it has to be.
|
|
162
|
-
*/
|
|
163
|
-
function tone(stops: HueStops, chipTone: ChipTone): ChipColorScheme {
|
|
164
|
-
// The outlined variant is a property of the HUE, not of the tone: it has no
|
|
165
|
-
// fill, so every tone of a hue sits on the same page background and wants
|
|
166
|
-
// the same ink.
|
|
167
|
-
const outline = {
|
|
168
|
-
outlineText: push(stops.text, "#000000", PAGE_LIGHT),
|
|
169
|
-
darkOutlineText: push(stops.onDeep ?? stops.pale, "#ffffff", PAGE_DARK)
|
|
170
|
-
};
|
|
171
|
-
|
|
172
|
-
const filled = (light: string, dark: string): ChipColorScheme => ({
|
|
173
|
-
color: light,
|
|
174
|
-
text: inkOn(stops, light),
|
|
175
|
-
darkColor: dark,
|
|
176
|
-
darkText: inkOn(stops, dark),
|
|
177
|
-
...outline
|
|
178
|
-
});
|
|
179
|
-
|
|
180
|
-
switch (chipTone) {
|
|
181
|
-
case "Light":
|
|
182
|
-
return filled(stops.mid, stops.solid);
|
|
183
|
-
case "Dark":
|
|
184
|
-
return filled(stops.solid, stops.solid);
|
|
185
|
-
case "Darker":
|
|
186
|
-
return filled(stops.deep, stops.deep);
|
|
187
|
-
case "Lighter":
|
|
188
|
-
default:
|
|
189
|
-
return filled(stops.pale, stops.deep);
|
|
190
|
-
}
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
const CHIP_TONES: ChipTone[] = ["Lighter", "Light", "Dark", "Darker"];
|
|
194
|
-
|
|
195
|
-
function buildChipColors(): Record<ChipColorKey, ChipColorScheme> {
|
|
196
|
-
const colors = {} as Record<ChipColorKey, ChipColorScheme>;
|
|
197
|
-
for (const hue of CHIP_HUES) {
|
|
198
|
-
const stops = HUE_STOPS[hue];
|
|
199
|
-
for (const chipTone of CHIP_TONES) {
|
|
200
|
-
colors[`${hue}${chipTone}` as ChipColorKey] = tone(stops, chipTone);
|
|
201
|
-
}
|
|
202
|
-
// The bare hue is the lightest tone, which is what every existing
|
|
203
|
-
// `color: "blue"` in a collection config already means.
|
|
204
|
-
colors[hue] = tone(stops, "Lighter");
|
|
205
|
-
}
|
|
206
|
-
return colors;
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
/**
|
|
210
|
-
* Every chip scheme, keyed by hue and tone.
|
|
211
|
-
*
|
|
212
|
-
* This table used to hold four tones per hue and was flattened to one, which
|
|
213
|
-
* left `blueDark`, `redDarker` and friends resolving to `undefined` — a chip
|
|
214
|
-
* with a colour in its config rendering with no colour at all — and left seeded
|
|
215
|
-
* chips picking from ten schemes, so a five-value enum routinely drew the same
|
|
216
|
-
* background three times. The tones are generated from {@link HUE_STOPS} now,
|
|
217
|
-
* so a new hue brings its whole family with it.
|
|
218
|
-
*/
|
|
219
|
-
export const CHIP_COLORS: Record<ChipColorKey, ChipColorScheme> = buildChipColors();
|
|
220
|
-
|
|
221
|
-
/**
|
|
222
|
-
* The keys a seeded chip may be assigned, in a stable order.
|
|
223
|
-
*
|
|
224
|
-
* The bare-hue aliases are excluded: they are the same scheme as `<hue>Lighter`
|
|
225
|
-
* and would make the palest tone twice as likely as any other.
|
|
226
|
-
*/
|
|
227
|
-
export const CHIP_SEED_KEYS: ChipColorKey[] = CHIP_HUES.flatMap(
|
|
228
|
-
hue => CHIP_TONES.map(chipTone => `${hue}${chipTone}` as ChipColorKey));
|
|
229
|
-
|
|
230
|
-
// `string & {}` keeps the known keys in autocomplete while still accepting the
|
|
231
|
-
// arbitrary column colours a board hands over.
|
|
232
|
-
export function getColorSchemeForKey(key: ChipColorKey | (string & {})): ChipColorScheme {
|
|
233
|
-
// An unknown key is a config that names a colour this build does not have —
|
|
234
|
-
// a renamed hue, or a value typed by hand. Seeding from the key keeps it
|
|
235
|
-
// coloured and keeps it stable, rather than dropping it to no colour.
|
|
236
|
-
return CHIP_COLORS[key as ChipColorKey] ?? getColorSchemeForSeed(String(key));
|
|
237
|
-
}
|
|
238
|
-
|
|
239
|
-
export function getColorSchemeForSeed(seed: string): ChipColorScheme {
|
|
240
|
-
const hash: number = hashString(seed);
|
|
241
|
-
return CHIP_COLORS[CHIP_SEED_KEYS[hash % CHIP_SEED_KEYS.length]];
|
|
242
|
-
}
|
package/src/util/cls.ts
DELETED
package/src/util/debounce.ts
DELETED
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @ignore
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
export function debounce<T extends (...args: any[]) => unknown>(func: T, wait = 166) {
|
|
6
|
-
let timeout: ReturnType<typeof setTimeout>;
|
|
7
|
-
|
|
8
|
-
function debounced(this: unknown, ...args: Parameters<T>) {
|
|
9
|
-
const later = () => {
|
|
10
|
-
func.apply(this, args);
|
|
11
|
-
};
|
|
12
|
-
clearTimeout(timeout);
|
|
13
|
-
timeout = setTimeout(later, wait);
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
debounced.clear = () => {
|
|
17
|
-
clearTimeout(timeout);
|
|
18
|
-
};
|
|
19
|
-
|
|
20
|
-
return debounced as T & Cancelable;
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
/**
|
|
24
|
-
* @ignore
|
|
25
|
-
*/
|
|
26
|
-
export interface Cancelable {
|
|
27
|
-
clear(): void;
|
|
28
|
-
}
|