@lotics/ui 42.0.0 → 42.3.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/docs/catalog.md +15 -0
- package/docs/composition.md +23 -2
- package/package.json +10 -3
- package/src/avatar.tsx +6 -1
- package/src/avatar.web.tsx +6 -1
- package/src/avatar_size.ts +10 -1
- package/src/color_tokens.ts +422 -0
- package/src/colors.ts +14 -383
- package/src/colors.web.ts +43 -0
- package/src/summary.tsx +217 -0
- package/src/summary_line.tsx +23 -0
- package/src/theme.tsx +13 -50
- package/src/theme.web.tsx +64 -0
- package/src/theme_context.ts +45 -0
package/docs/catalog.md
CHANGED
|
@@ -1225,6 +1225,21 @@ source (`src/<module>.tsx`/`.ts`) is the API reference.
|
|
|
1225
1225
|
a record's identity band reporting that record's own state**: derived verdicts on one record
|
|
1226
1226
|
("documents incomplete", "2 fields need checking") are the checklist a record must not carry
|
|
1227
1227
|
(§`checklist`) and restate at a distance what the sections below state in place.
|
|
1228
|
+
- **`summary`** — `Summary`: the register summary band, for a set with a SHAPE worth showing —
|
|
1229
|
+
a headline figure, a distribution bar, and the buckets named. A COMPOUND:
|
|
1230
|
+
`Summary.Header` / `Summary.Total` / `Summary.Metric` / `Summary.Distribution` /
|
|
1231
|
+
`Summary.Facts`, composed as children. **Declare the buckets ONCE on the root and every part
|
|
1232
|
+
derives from them** — the headline is their sum (there is no `total` prop to go stale), the
|
|
1233
|
+
segments and the legend swatches are the buckets' own colours. That correspondence is the
|
|
1234
|
+
reason it exists: a `segments` array passed beside an `items` array beside a `total` drifts
|
|
1235
|
+
three ways, invisibly. A `SummaryBucket` must PARTITION the set; an aggregate over the same
|
|
1236
|
+
rows that does not (a condition like "overdue" beside "draft / sent / paid", counted again
|
|
1237
|
+
from a different question) goes to `Summary.Facts` as `extra`, where it renders with NO swatch
|
|
1238
|
+
— the visual difference says it is not a slice of the bar. Ordering is the caller's and is
|
|
1239
|
+
never re-ranked, because for a pipeline the sequence is the process. Pick between the four
|
|
1240
|
+
summaries by what you have: only aggregates → `summary_line`; aggregates + a shape →
|
|
1241
|
+
`summary`; a boxed dashboard stat band → `kpi_strip`; a drill-down facet browser over a large
|
|
1242
|
+
population → `breakdown`.
|
|
1228
1243
|
- **`use_selection`** — `useSelection`: always-on multi-select state for a register/list —
|
|
1229
1244
|
the `selected` Set + `toggle`/`setAll`/`allSelected`/`indeterminate`/`count`/`clear`;
|
|
1230
1245
|
selectability gating stays with the caller. The checkbox-always-visible counterpart to
|
package/docs/composition.md
CHANGED
|
@@ -264,9 +264,13 @@ objects — hairlines between them blur into one run-on list.
|
|
|
264
264
|
| Altitude | Component | Where | Shape |
|
|
265
265
|
|---|---|---|---|
|
|
266
266
|
| Dashboard | `<KPIStrip items={[{label, value, trend?, caption?, tone?, compact?, info?}…]}>` | TOP of a dashboard | a boxed stat band — a `Card` of `KPICard` columns |
|
|
267
|
-
| Register / list | `<SummaryLine items={[{label, value, tone?, compact?, trend?, info?}…]}>` | BELOW the toolbar | a light inline line — a small `Metric` + muted label per item |
|
|
267
|
+
| Register / list | `<SummaryLine items={[{label, value, tone?, compact?, trend?, info?, color?}…]}>` | BELOW the toolbar | a light inline line — a small `Metric` + muted label per item |
|
|
268
|
+
| Register / list, with a SHAPE | `<Summary buckets={…}><Summary.Header>…</Summary.Header><Summary.Distribution/><Summary.Facts/></Summary>` | BELOW the toolbar | headline figure + distribution bar + the buckets named, all derived from one bucket list |
|
|
268
269
|
|
|
269
|
-
|
|
270
|
+
`SummaryLine` is `Summary`'s `Facts` row on its own — reach for the compound when the set has a
|
|
271
|
+
distribution worth drawing, and for the bare line when it is only a few aggregates. A
|
|
272
|
+
`SummaryLine`'s `color` exists so it can double as the bar's legend without giving up its `info`
|
|
273
|
+
popovers. Both are recomputed from the FILTERED rows so they always describe WHAT'S SHOWN. Both are
|
|
270
274
|
INFORMATIONAL — they never filter or navigate (that's the tabs/chips' job); if there are no
|
|
271
275
|
cross-cutting numbers, drop them. Do NOT put a `KPIStrip` on a register page or a `SummaryLine` on
|
|
272
276
|
a dashboard. Card stat rails use `KPICard`.
|
|
@@ -474,6 +478,23 @@ content per column and set `flex` from that. A column whose cell holds a PROPORT
|
|
|
474
478
|
bar) is the exception in one direction only: cap it, because a gauge stops reading as an
|
|
475
479
|
instrument once its track is long enough to be a rule across the row.
|
|
476
480
|
|
|
481
|
+
**The FIXED columns' widths are the flexible one's budget — so audit them by what they CARRY,
|
|
482
|
+
not by what they are called.** With one `flex` column among N fixed, every pixel a fixed column
|
|
483
|
+
takes comes out of the flexible one, silently and with no measurement pointing at the culprit:
|
|
484
|
+
the symptom appears somewhere else entirely, as truncated content in the column that was
|
|
485
|
+
supposed to have room. The usual offender is a column sized by its HEADER over single-character
|
|
486
|
+
data — a long label above a column of one-digit counts reserves width for a word nobody reads
|
|
487
|
+
twice, and the register's identity column pays for it. Measure the header row's cells and the
|
|
488
|
+
widest CELL content per column; where a column's width is set by its label rather than its
|
|
489
|
+
values, that difference is the identity column's, not its own.
|
|
490
|
+
|
|
491
|
+
**Space by the RATIO of within-group to between-group, never by absolutes.** Collect the gap
|
|
492
|
+
inside a composed band and the gap between that band and its neighbours, and divide. Near 1 is
|
|
493
|
+
the defect however sensible each number is alone — at 14 inside against 16 between, a headline,
|
|
494
|
+
a chart and a register read as one undifferentiated stack, because nothing on the screen is
|
|
495
|
+
grouped. Aim for roughly 2 or more, and change the BETWEEN gap first: tightening the inside
|
|
496
|
+
crowds the group to buy separation the page should have provided.
|
|
497
|
+
|
|
477
498
|
Make a register SELECTABLE with the `Table` `leading` gutter + `selectAll` slot — a
|
|
478
499
|
`CheckboxInput` per `TableRow` (its `leading` slot) + a select-all in the header band, the ticked
|
|
479
500
|
rows `marked`, paired with a `FloatingActionBar` (its "Clear" escape is locale-resolved — pass
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lotics/ui",
|
|
3
|
-
"version": "42.
|
|
3
|
+
"version": "42.3.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"exports": {
|
|
6
6
|
"./vite": {
|
|
@@ -8,7 +8,10 @@
|
|
|
8
8
|
"default": "./src/vite.mjs"
|
|
9
9
|
},
|
|
10
10
|
"./tokens": "./src/tokens.ts",
|
|
11
|
-
"./colors":
|
|
11
|
+
"./colors": {
|
|
12
|
+
"react-native": "./src/colors.ts",
|
|
13
|
+
"default": "./src/colors.web.ts"
|
|
14
|
+
},
|
|
12
15
|
"./deadline": "./src/deadline.ts",
|
|
13
16
|
"./option_badge": "./src/option_badge.tsx",
|
|
14
17
|
"./member_chip": "./src/member_chip.tsx",
|
|
@@ -67,6 +70,7 @@
|
|
|
67
70
|
"./kpi_card": "./src/kpi_card.tsx",
|
|
68
71
|
"./kpi_strip": "./src/kpi_strip.tsx",
|
|
69
72
|
"./summary_line": "./src/summary_line.tsx",
|
|
73
|
+
"./summary": "./src/summary.tsx",
|
|
70
74
|
"./empty_state": "./src/empty_state.tsx",
|
|
71
75
|
"./error_state": "./src/error_state.tsx",
|
|
72
76
|
"./format_date": "./src/format_date.ts",
|
|
@@ -85,7 +89,10 @@
|
|
|
85
89
|
"./trend_footer": "./src/trend_footer.tsx",
|
|
86
90
|
"./locale": "./src/locale.tsx",
|
|
87
91
|
"./spacing": "./src/spacing.ts",
|
|
88
|
-
"./theme":
|
|
92
|
+
"./theme": {
|
|
93
|
+
"react-native": "./src/theme.tsx",
|
|
94
|
+
"default": "./src/theme.web.tsx"
|
|
95
|
+
},
|
|
89
96
|
"./progress_bar": "./src/progress_bar.tsx",
|
|
90
97
|
"./form_date_picker": "./src/form_date_picker.tsx",
|
|
91
98
|
"./form_picker": "./src/form_picker.tsx",
|
package/src/avatar.tsx
CHANGED
|
@@ -39,7 +39,12 @@ export function Avatar(props: AvatarProps) {
|
|
|
39
39
|
aria-hidden={decorative || undefined}
|
|
40
40
|
style={[
|
|
41
41
|
styles.base,
|
|
42
|
-
|
|
42
|
+
// The initials disc is the one place a member LOOKS like the product,
|
|
43
|
+
// so it carries the brand accent rather than a fixed blue. NOT a
|
|
44
|
+
// restyle: `colors.accent` DEFAULTS to this exact blue-600, so an app
|
|
45
|
+
// that themes nothing renders identical pixels, while one that sets an
|
|
46
|
+
// accent gets its own hue without this file knowing.
|
|
47
|
+
{ backgroundColor: colors.accent, width: px, height: px },
|
|
43
48
|
style,
|
|
44
49
|
]}
|
|
45
50
|
>
|
package/src/avatar.web.tsx
CHANGED
|
@@ -46,7 +46,12 @@ export function Avatar(props: AvatarProps) {
|
|
|
46
46
|
aria-hidden={decorative || undefined}
|
|
47
47
|
style={[
|
|
48
48
|
styles.base,
|
|
49
|
-
|
|
49
|
+
// The initials disc is the one place a member LOOKS like the product,
|
|
50
|
+
// so it carries the brand accent rather than a fixed blue. NOT a
|
|
51
|
+
// restyle: `colors.accent` DEFAULTS to this exact blue-600, so an app
|
|
52
|
+
// that themes nothing renders identical pixels, while one that sets an
|
|
53
|
+
// accent gets its own hue without this file knowing.
|
|
54
|
+
{ backgroundColor: colors.accent, width: px, height: px },
|
|
50
55
|
style,
|
|
51
56
|
]}
|
|
52
57
|
>
|
package/src/avatar_size.ts
CHANGED
|
@@ -46,7 +46,16 @@ export const AVATAR_PX: Record<AvatarSize, number> = {
|
|
|
46
46
|
* account-menu trigger in a top bar, a prominent row's leading figure. Equals
|
|
47
47
|
* `CONTROL_HEIGHT` (`control_surface`), so it lines up with the icon buttons
|
|
48
48
|
* beside it. It was 48, which anchored to nothing: the bar's other controls
|
|
49
|
-
* are 40, and a 48px circle among them reads as the one thing that got out.
|
|
49
|
+
* are 40, and a 48px circle among them reads as the one thing that got out.
|
|
50
|
+
*
|
|
51
|
+
* **In a register, the rung follows what the row IS ABOUT, not the density.**
|
|
52
|
+
* Where a person is one FIELD of the row — an owner, an assignee beside a
|
|
53
|
+
* dozen other columns — the figure is a value like any other and takes `md`.
|
|
54
|
+
* Where the person is the row's SUBJECT (a league table of reps, a roster,
|
|
55
|
+
* anything grouped BY member), the avatar is that row's leading figure and
|
|
56
|
+
* takes this rung; at `md` such a register reads as a list of numbers that
|
|
57
|
+
* happens to carry faces. Budget for it: this rung is 12px wider than `md`,
|
|
58
|
+
* and in a table it takes those 12 from the flexible column the name is in. */
|
|
50
59
|
lg: 40,
|
|
51
60
|
/** A profile's own header. */
|
|
52
61
|
xl: 72,
|
|
@@ -0,0 +1,422 @@
|
|
|
1
|
+
const palette = {
|
|
2
|
+
black: "rgba(0,0,0,1)",
|
|
3
|
+
white: "rgba(255,255,255,1)",
|
|
4
|
+
|
|
5
|
+
// We use RGBA so that we can use React Native Animations with colors
|
|
6
|
+
red: {
|
|
7
|
+
50: "rgba(254, 242, 242, 1)",
|
|
8
|
+
100: "rgba(254, 226, 226, 1)",
|
|
9
|
+
200: "rgba(254, 202, 202, 1)",
|
|
10
|
+
300: "rgba(252, 165, 165, 1)",
|
|
11
|
+
400: "rgba(248, 113, 113, 1)",
|
|
12
|
+
500: "rgba(239, 68, 68, 1)",
|
|
13
|
+
600: "rgba(220, 38, 38, 1)",
|
|
14
|
+
700: "rgba(185, 28, 28, 1)",
|
|
15
|
+
800: "rgba(153, 27, 27, 1)",
|
|
16
|
+
900: "rgba(127, 29, 29, 1)",
|
|
17
|
+
950: "rgba(69, 10, 10, 1)",
|
|
18
|
+
},
|
|
19
|
+
orange: {
|
|
20
|
+
50: "rgba(255, 247, 237, 1)",
|
|
21
|
+
100: "rgba(255, 237, 213, 1)",
|
|
22
|
+
200: "rgba(254, 215, 170, 1)",
|
|
23
|
+
300: "rgba(253, 186, 116, 1)",
|
|
24
|
+
400: "rgba(251, 146, 60, 1)",
|
|
25
|
+
500: "rgba(249, 115, 22, 1)",
|
|
26
|
+
600: "rgba(234, 88, 12, 1)",
|
|
27
|
+
700: "rgba(194, 65, 12, 1)",
|
|
28
|
+
800: "rgba(154, 52, 18, 1)",
|
|
29
|
+
900: "rgba(124, 45, 18, 1)",
|
|
30
|
+
950: "rgba(67, 20, 7, 1)",
|
|
31
|
+
},
|
|
32
|
+
amber: {
|
|
33
|
+
50: "rgba(255, 251, 235, 1)",
|
|
34
|
+
100: "rgba(254, 243, 199, 1)",
|
|
35
|
+
200: "rgba(253, 230, 138, 1)",
|
|
36
|
+
300: "rgba(252, 211, 77, 1)",
|
|
37
|
+
400: "rgba(251, 191, 36, 1)",
|
|
38
|
+
500: "rgba(245, 158, 11, 1)",
|
|
39
|
+
600: "rgba(217, 119, 6, 1)",
|
|
40
|
+
700: "rgba(180, 83, 9, 1)",
|
|
41
|
+
800: "rgba(146, 64, 14, 1)",
|
|
42
|
+
900: "rgba(120, 53, 15, 1)",
|
|
43
|
+
950: "rgba(69, 26, 3, 1)",
|
|
44
|
+
},
|
|
45
|
+
yellow: {
|
|
46
|
+
50: "rgba(254, 252, 232, 1)",
|
|
47
|
+
100: "rgba(254, 249, 195, 1)",
|
|
48
|
+
200: "rgba(254, 240, 138, 1)",
|
|
49
|
+
300: "rgba(253, 224, 71, 1)",
|
|
50
|
+
400: "rgba(250, 204, 21, 1)",
|
|
51
|
+
500: "rgba(234, 179, 8, 1)",
|
|
52
|
+
600: "rgba(202, 138, 4, 1)",
|
|
53
|
+
700: "rgba(161, 98, 7, 1)",
|
|
54
|
+
800: "rgba(133, 77, 14, 1)",
|
|
55
|
+
900: "rgba(113, 63, 18, 1)",
|
|
56
|
+
950: "rgba(66, 32, 6, 1)",
|
|
57
|
+
},
|
|
58
|
+
lime: {
|
|
59
|
+
50: "rgba(247, 254, 231, 1)",
|
|
60
|
+
100: "rgba(236, 252, 203, 1)",
|
|
61
|
+
200: "rgba(217, 249, 157, 1)",
|
|
62
|
+
300: "rgba(190, 242, 100, 1)",
|
|
63
|
+
400: "rgba(163, 230, 53, 1)",
|
|
64
|
+
500: "rgba(132, 204, 22, 1)",
|
|
65
|
+
600: "rgba(101, 163, 13, 1)",
|
|
66
|
+
700: "rgba(77, 124, 15, 1)",
|
|
67
|
+
800: "rgba(63, 98, 18, 1)",
|
|
68
|
+
900: "rgba(54, 83, 20, 1)",
|
|
69
|
+
950: "rgba(26, 46, 5, 1)",
|
|
70
|
+
},
|
|
71
|
+
green: {
|
|
72
|
+
50: "rgba(240, 253, 244, 1)",
|
|
73
|
+
100: "rgba(220, 252, 231, 1)",
|
|
74
|
+
200: "rgba(187, 247, 208, 1)",
|
|
75
|
+
300: "rgba(134, 239, 172, 1)",
|
|
76
|
+
400: "rgba(74, 222, 128, 1)",
|
|
77
|
+
500: "rgba(34, 197, 94, 1)",
|
|
78
|
+
600: "rgba(22, 163, 74, 1)",
|
|
79
|
+
700: "rgba(21, 128, 61, 1)",
|
|
80
|
+
800: "rgba(22, 101, 52, 1)",
|
|
81
|
+
900: "rgba(20, 83, 45, 1)",
|
|
82
|
+
950: "rgba(5, 46, 22, 1)",
|
|
83
|
+
},
|
|
84
|
+
emerald: {
|
|
85
|
+
50: "rgba(236, 253, 245, 1)",
|
|
86
|
+
100: "rgba(209, 250, 229, 1)",
|
|
87
|
+
200: "rgba(167, 243, 208, 1)",
|
|
88
|
+
300: "rgba(110, 231, 183, 1)",
|
|
89
|
+
400: "rgba(52, 211, 153, 1)",
|
|
90
|
+
500: "rgba(16, 185, 129, 1)",
|
|
91
|
+
600: "rgba(5, 150, 105, 1)",
|
|
92
|
+
700: "rgba(4, 120, 87, 1)",
|
|
93
|
+
800: "rgba(6, 95, 70, 1)",
|
|
94
|
+
900: "rgba(6, 78, 59, 1)",
|
|
95
|
+
950: "rgba(2, 44, 34, 1)",
|
|
96
|
+
},
|
|
97
|
+
teal: {
|
|
98
|
+
50: "rgba(240, 253, 250, 1)",
|
|
99
|
+
100: "rgba(204, 251, 241, 1)",
|
|
100
|
+
200: "rgba(153, 246, 228, 1)",
|
|
101
|
+
300: "rgba(94, 234, 212, 1)",
|
|
102
|
+
400: "rgba(45, 212, 191, 1)",
|
|
103
|
+
500: "rgba(20, 184, 166, 1)",
|
|
104
|
+
600: "rgba(13, 148, 136, 1)",
|
|
105
|
+
700: "rgba(15, 118, 110, 1)",
|
|
106
|
+
800: "rgba(17, 94, 89, 1)",
|
|
107
|
+
900: "rgba(19, 78, 74, 1)",
|
|
108
|
+
950: "rgba(4, 47, 46, 1)",
|
|
109
|
+
},
|
|
110
|
+
cyan: {
|
|
111
|
+
50: "rgba(236, 254, 255, 1)",
|
|
112
|
+
100: "rgba(207, 250, 254, 1)",
|
|
113
|
+
200: "rgba(165, 243, 252, 1)",
|
|
114
|
+
300: "rgba(103, 232, 249, 1)",
|
|
115
|
+
400: "rgba(34, 211, 238, 1)",
|
|
116
|
+
500: "rgba(6, 182, 212, 1)",
|
|
117
|
+
600: "rgba(8, 145, 178, 1)",
|
|
118
|
+
700: "rgba(14, 116, 144, 1)",
|
|
119
|
+
800: "rgba(21, 94, 117, 1)",
|
|
120
|
+
900: "rgba(22, 78, 99, 1)",
|
|
121
|
+
950: "rgba(8, 51, 68, 1)",
|
|
122
|
+
},
|
|
123
|
+
sky: {
|
|
124
|
+
50: "rgba(240, 249, 255, 1)",
|
|
125
|
+
100: "rgba(224, 242, 254, 1)",
|
|
126
|
+
200: "rgba(186, 230, 253, 1)",
|
|
127
|
+
300: "rgba(125, 211, 252, 1)",
|
|
128
|
+
400: "rgba(56, 189, 248, 1)",
|
|
129
|
+
500: "rgba(14, 165, 233, 1)",
|
|
130
|
+
600: "rgba(2, 132, 199, 1)",
|
|
131
|
+
700: "rgba(3, 105, 161, 1)",
|
|
132
|
+
800: "rgba(7, 89, 133, 1)",
|
|
133
|
+
900: "rgba(12, 74, 110, 1)",
|
|
134
|
+
950: "rgba(8, 47, 73, 1)",
|
|
135
|
+
},
|
|
136
|
+
blue: {
|
|
137
|
+
50: "rgba(239, 246, 255, 1)",
|
|
138
|
+
100: "rgba(219, 234, 254, 1)",
|
|
139
|
+
200: "rgba(191, 219, 254, 1)",
|
|
140
|
+
300: "rgba(147, 197, 253, 1)",
|
|
141
|
+
400: "rgba(96, 165, 250, 1)",
|
|
142
|
+
500: "rgba(59, 130, 246, 1)",
|
|
143
|
+
600: "rgba(37, 99, 235, 1)",
|
|
144
|
+
700: "rgba(29, 78, 216, 1)",
|
|
145
|
+
800: "rgba(30, 64, 175, 1)",
|
|
146
|
+
900: "rgba(30, 58, 138, 1)",
|
|
147
|
+
950: "rgba(23, 37, 84, 1)",
|
|
148
|
+
},
|
|
149
|
+
indigo: {
|
|
150
|
+
50: "rgba(238, 242, 255, 1)",
|
|
151
|
+
100: "rgba(224, 231, 255, 1)",
|
|
152
|
+
200: "rgba(199, 210, 254, 1)",
|
|
153
|
+
300: "rgba(165, 180, 252, 1)",
|
|
154
|
+
400: "rgba(129, 140, 248, 1)",
|
|
155
|
+
500: "rgba(99, 102, 241, 1)",
|
|
156
|
+
600: "rgba(79, 70, 229, 1)",
|
|
157
|
+
700: "rgba(67, 56, 202, 1)",
|
|
158
|
+
800: "rgba(55, 48, 163, 1)",
|
|
159
|
+
900: "rgba(49, 46, 129, 1)",
|
|
160
|
+
950: "rgba(30, 27, 75, 1)",
|
|
161
|
+
},
|
|
162
|
+
violet: {
|
|
163
|
+
50: "rgba(245, 243, 255, 1)",
|
|
164
|
+
100: "rgba(237, 233, 254, 1)",
|
|
165
|
+
200: "rgba(221, 214, 254, 1)",
|
|
166
|
+
300: "rgba(196, 181, 253, 1)",
|
|
167
|
+
400: "rgba(167, 139, 250, 1)",
|
|
168
|
+
500: "rgba(139, 92, 246, 1)",
|
|
169
|
+
600: "rgba(124, 58, 237, 1)",
|
|
170
|
+
700: "rgba(109, 40, 217, 1)",
|
|
171
|
+
800: "rgba(91, 33, 182, 1)",
|
|
172
|
+
900: "rgba(76, 29, 149, 1)",
|
|
173
|
+
950: "rgba(46, 16, 101, 1)",
|
|
174
|
+
},
|
|
175
|
+
purple: {
|
|
176
|
+
50: "rgba(250, 245, 255, 1)",
|
|
177
|
+
100: "rgba(243, 232, 255, 1)",
|
|
178
|
+
200: "rgba(233, 213, 255, 1)",
|
|
179
|
+
300: "rgba(216, 180, 254, 1)",
|
|
180
|
+
400: "rgba(192, 132, 252, 1)",
|
|
181
|
+
500: "rgba(168, 85, 247, 1)",
|
|
182
|
+
600: "rgba(147, 51, 234, 1)",
|
|
183
|
+
700: "rgba(126, 34, 206, 1)",
|
|
184
|
+
800: "rgba(107, 33, 168, 1)",
|
|
185
|
+
900: "rgba(88, 28, 135, 1)",
|
|
186
|
+
950: "rgba(59, 7, 100, 1)",
|
|
187
|
+
},
|
|
188
|
+
fuchsia: {
|
|
189
|
+
50: "rgba(253, 244, 255, 1)",
|
|
190
|
+
100: "rgba(250, 232, 255, 1)",
|
|
191
|
+
200: "rgba(245, 208, 254, 1)",
|
|
192
|
+
300: "rgba(240, 171, 252, 1)",
|
|
193
|
+
400: "rgba(232, 121, 249, 1)",
|
|
194
|
+
500: "rgba(217, 70, 239, 1)",
|
|
195
|
+
600: "rgba(192, 38, 211, 1)",
|
|
196
|
+
700: "rgba(162, 28, 175, 1)",
|
|
197
|
+
800: "rgba(134, 25, 143, 1)",
|
|
198
|
+
900: "rgba(112, 26, 117, 1)",
|
|
199
|
+
950: "rgba(74, 4, 78, 1)",
|
|
200
|
+
},
|
|
201
|
+
pink: {
|
|
202
|
+
50: "rgba(253, 242, 248, 1)",
|
|
203
|
+
100: "rgba(252, 231, 243, 1)",
|
|
204
|
+
200: "rgba(251, 207, 232, 1)",
|
|
205
|
+
300: "rgba(249, 168, 212, 1)",
|
|
206
|
+
400: "rgba(244, 114, 182, 1)",
|
|
207
|
+
500: "rgba(236, 72, 153, 1)",
|
|
208
|
+
600: "rgba(219, 39, 119, 1)",
|
|
209
|
+
700: "rgba(190, 24, 93, 1)",
|
|
210
|
+
800: "rgba(157, 23, 77, 1)",
|
|
211
|
+
900: "rgba(131, 24, 67, 1)",
|
|
212
|
+
950: "rgba(80, 7, 36, 1)",
|
|
213
|
+
},
|
|
214
|
+
rose: {
|
|
215
|
+
50: "rgba(255, 241, 242, 1)",
|
|
216
|
+
100: "rgba(255, 228, 230, 1)",
|
|
217
|
+
200: "rgba(254, 205, 211, 1)",
|
|
218
|
+
300: "rgba(253, 164, 175, 1)",
|
|
219
|
+
400: "rgba(251, 113, 133, 1)",
|
|
220
|
+
500: "rgba(244, 63, 94, 1)",
|
|
221
|
+
600: "rgba(225, 29, 72, 1)",
|
|
222
|
+
700: "rgba(190, 18, 60, 1)",
|
|
223
|
+
800: "rgba(159, 18, 57, 1)",
|
|
224
|
+
900: "rgba(136, 19, 55, 1)",
|
|
225
|
+
950: "rgba(76, 5, 25, 1)",
|
|
226
|
+
},
|
|
227
|
+
slate: {
|
|
228
|
+
50: "rgba(248, 250, 252, 1)",
|
|
229
|
+
100: "rgba(241, 245, 249, 1)",
|
|
230
|
+
200: "rgba(226, 232, 240, 1)",
|
|
231
|
+
300: "rgba(203, 213, 225, 1)",
|
|
232
|
+
400: "rgba(148, 163, 184, 1)",
|
|
233
|
+
500: "rgba(100, 116, 139, 1)",
|
|
234
|
+
600: "rgba(71, 85, 105, 1)",
|
|
235
|
+
700: "rgba(51, 65, 85, 1)",
|
|
236
|
+
800: "rgba(30, 41, 59, 1)",
|
|
237
|
+
900: "rgba(15, 23, 42, 1)",
|
|
238
|
+
950: "rgba(2, 6, 23, 1)",
|
|
239
|
+
},
|
|
240
|
+
gray: {
|
|
241
|
+
50: "rgba(249, 250, 251, 1)",
|
|
242
|
+
100: "rgba(243, 244, 246, 1)",
|
|
243
|
+
200: "rgba(229, 231, 235, 1)",
|
|
244
|
+
300: "rgba(209, 213, 219, 1)",
|
|
245
|
+
400: "rgba(156, 163, 175, 1)",
|
|
246
|
+
500: "rgba(107, 114, 128, 1)",
|
|
247
|
+
600: "rgba(75, 85, 99, 1)",
|
|
248
|
+
700: "rgba(55, 65, 81, 1)",
|
|
249
|
+
800: "rgba(31, 41, 55, 1)",
|
|
250
|
+
900: "rgba(17, 24, 39, 1)",
|
|
251
|
+
950: "rgba(3, 7, 18, 1)",
|
|
252
|
+
},
|
|
253
|
+
zinc: {
|
|
254
|
+
50: "rgba(250, 250, 250, 1)",
|
|
255
|
+
100: "rgba(244, 244, 245, 1)",
|
|
256
|
+
200: "rgba(228, 228, 231, 1)",
|
|
257
|
+
300: "rgba(212, 212, 216, 1)",
|
|
258
|
+
400: "rgba(161, 161, 170, 1)",
|
|
259
|
+
500: "rgba(113, 113, 122, 1)",
|
|
260
|
+
600: "rgba(82, 82, 91, 1)",
|
|
261
|
+
700: "rgba(63, 63, 70, 1)",
|
|
262
|
+
800: "rgba(39, 39, 42, 1)",
|
|
263
|
+
900: "rgba(24, 24, 27, 1)",
|
|
264
|
+
950: "rgba(9, 9, 11, 1)",
|
|
265
|
+
},
|
|
266
|
+
neutral: {
|
|
267
|
+
50: "rgba(250, 250, 250, 1)",
|
|
268
|
+
100: "rgba(245, 245, 245, 1)",
|
|
269
|
+
200: "rgba(229, 229, 229, 1)",
|
|
270
|
+
300: "rgba(212, 212, 212, 1)",
|
|
271
|
+
400: "rgba(163, 163, 163, 1)",
|
|
272
|
+
500: "rgba(115, 115, 115, 1)",
|
|
273
|
+
600: "rgba(82, 82, 82, 1)",
|
|
274
|
+
700: "rgba(64, 64, 64, 1)",
|
|
275
|
+
800: "rgba(38, 38, 38, 1)",
|
|
276
|
+
900: "rgba(23, 23, 23, 1)",
|
|
277
|
+
950: "rgba(10, 10, 10, 1)",
|
|
278
|
+
},
|
|
279
|
+
stone: {
|
|
280
|
+
50: "rgba(250, 250, 249, 1)",
|
|
281
|
+
100: "rgba(245, 245, 244, 1)",
|
|
282
|
+
200: "rgba(231, 229, 228, 1)",
|
|
283
|
+
300: "rgba(214, 211, 209, 1)",
|
|
284
|
+
400: "rgba(168, 162, 158, 1)",
|
|
285
|
+
500: "rgba(120, 113, 108, 1)",
|
|
286
|
+
600: "rgba(87, 83, 78, 1)",
|
|
287
|
+
700: "rgba(68, 64, 60, 1)",
|
|
288
|
+
800: "rgba(41, 37, 36, 1)",
|
|
289
|
+
900: "rgba(28, 25, 23, 1)",
|
|
290
|
+
950: "rgba(12, 10, 9, 1)",
|
|
291
|
+
},
|
|
292
|
+
};
|
|
293
|
+
|
|
294
|
+
/**
|
|
295
|
+
* THE THREE THEMEABLE ROLES are `background`, `border` and `accent` — and
|
|
296
|
+
* deliberately nothing else. See `colors.web.ts`, which redefines exactly
|
|
297
|
+
* these as CSS variables so an app can carry its own identity.
|
|
298
|
+
*
|
|
299
|
+
* The line is drawn at MEANING, not at convenience. A palette family
|
|
300
|
+
* (`red`, `amber`, `emerald`, `zinc`…) is how this system says what something
|
|
301
|
+
* IS — a status, a valence, a series — so letting an app repaint those would
|
|
302
|
+
* not restyle the product, it would make it lie. What is left over is chrome:
|
|
303
|
+
* the paper, the paper's edge, and the one hue that is the customer's rather
|
|
304
|
+
* than ours. An app can own all of that and change no claim the UI makes.
|
|
305
|
+
*
|
|
306
|
+
* This file is the CONTRACT and stays literal: TypeScript resolves `./colors`
|
|
307
|
+
* here (never the `.web` variant), so both files must carry the same keys or an
|
|
308
|
+
* app reading `colors.accent` would fail to typecheck against the platform it
|
|
309
|
+
* actually runs on. It is also what native renders, where `var()` means nothing.
|
|
310
|
+
*/
|
|
311
|
+
export const colors = {
|
|
312
|
+
...palette,
|
|
313
|
+
border: palette.zinc["200"],
|
|
314
|
+
border_shadow: `0px 1px 2px 0px ${palette.zinc["200"]}`,
|
|
315
|
+
/** A RAISED surface — Card, Drawer, Modal, the things that sit on the page. */
|
|
316
|
+
background: palette.white,
|
|
317
|
+
/**
|
|
318
|
+
* The one brand hue, and its reach is narrower than the word suggests: it
|
|
319
|
+
* paints IDENTITY marks — today the avatar's initials disc, the one place a
|
|
320
|
+
* person is rendered as a coloured shape, on every register in every app.
|
|
321
|
+
*
|
|
322
|
+
* It deliberately does NOT paint interaction chrome. The focus ring is
|
|
323
|
+
* `zinc[900]` and the primary action is near-black because those are the KIT's
|
|
324
|
+
* semantics, shared across every app so a reader learns them once; repainting
|
|
325
|
+
* them per tenant would make "what does a focus ring look like" a per-app
|
|
326
|
+
* question. And never for DATA: a series, a status and a valence all come from
|
|
327
|
+
* a palette family, so an accent can never overwrite a meaning.
|
|
328
|
+
*/
|
|
329
|
+
accent: palette.blue["600"],
|
|
330
|
+
shadow: `0px 0px 6px 1px ${palette.zinc["300"]}`,
|
|
331
|
+
};
|
|
332
|
+
|
|
333
|
+
/**
|
|
334
|
+
* A low-alpha wash of a palette color (hover tints, dimmed chart segments,
|
|
335
|
+
* icon discs). Handles the palette's rgba() strings plus rgb()/#RRGGBB.
|
|
336
|
+
*/
|
|
337
|
+
export function withAlpha(color: string, alpha: number): string {
|
|
338
|
+
if (color.startsWith("rgba(")) return color.replace(/,\s*[\d.]+\s*\)\s*$/, `, ${alpha})`);
|
|
339
|
+
if (color.startsWith("rgb(")) return color.replace("rgb(", "rgba(").replace(/\)\s*$/, `, ${alpha})`);
|
|
340
|
+
if (color.startsWith("#") && color.length === 7) {
|
|
341
|
+
return `${color}${Math.round(alpha * 255)
|
|
342
|
+
.toString(16)
|
|
343
|
+
.padStart(2, "0")}`;
|
|
344
|
+
}
|
|
345
|
+
return color;
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
/**
|
|
349
|
+
* A palette FAMILY name — the single, semantic way to reference a color across
|
|
350
|
+
* the system (Badge, status indicators, chart series, breakdown segments).
|
|
351
|
+
* Reference colors by NAME and let the component resolve the shade it needs;
|
|
352
|
+
* never thread a raw hex through props — it drifts (one call site picks 500,
|
|
353
|
+
* another 600, and the same "status" renders two greens). Excludes every
|
|
354
|
+
* non-scale role key — the four themeable roles included, since `solid("accent")`
|
|
355
|
+
* would otherwise typecheck and hand back `undefined` from a family that has no
|
|
356
|
+
* shades.
|
|
357
|
+
*/
|
|
358
|
+
export type ColorName = Exclude<
|
|
359
|
+
keyof typeof colors,
|
|
360
|
+
"border" | "border_shadow" | "background" | "accent" | "shadow" | "black" | "white"
|
|
361
|
+
>;
|
|
362
|
+
|
|
363
|
+
/**
|
|
364
|
+
* The canonical SOLID shade of a family (500) — status dots, status-grid
|
|
365
|
+
* cells, breakdown segments, chart series. Defined ONCE so every indicator of
|
|
366
|
+
* the same color agrees on the shade.
|
|
367
|
+
*/
|
|
368
|
+
export function solid(name: ColorName): string {
|
|
369
|
+
return colors[name][500];
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
/** A low-alpha wash of a family's solid shade — cell/segment tints, dimmed
|
|
373
|
+
* states. `tint("emerald", 0.2)` === `withAlpha(solid("emerald"), 0.2)`. */
|
|
374
|
+
export function tint(name: ColorName, alpha: number): string {
|
|
375
|
+
return withAlpha(solid(name), alpha);
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
/** Dark → light stops a `ramp` spans (the usable mid range of a scale). */
|
|
379
|
+
const RAMP_STOPS = [700, 600, 500, 400, 300, 200] as const;
|
|
380
|
+
|
|
381
|
+
/**
|
|
382
|
+
* `count` distinct shades of ONE family, strong → light — the monochrome
|
|
383
|
+
* "one hue family per dimension" data ramp (a Breakdown's segments, an ordered
|
|
384
|
+
* funnel, any multi-category breakdown of a single dimension). Use this for a
|
|
385
|
+
* COHERENT dimension instead of hand-picking shades or scattering hues; the
|
|
386
|
+
* segment LABEL carries identity, the shade only orders. (For semantic
|
|
387
|
+
* categories whose color carries MEANING — status — give each its own
|
|
388
|
+
* `ColorName` and `solid()` it, don't ramp.)
|
|
389
|
+
*/
|
|
390
|
+
export function ramp(name: ColorName, count: number): string[] {
|
|
391
|
+
const scale = colors[name];
|
|
392
|
+
if (count <= 1) return [scale[600]];
|
|
393
|
+
return Array.from({ length: count }, (_, i) => {
|
|
394
|
+
const idx = Math.round((i / (count - 1)) * (RAMP_STOPS.length - 1));
|
|
395
|
+
return scale[RAMP_STOPS[idx]];
|
|
396
|
+
});
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
/**
|
|
400
|
+
* Is `value` a usable {@link ColorName} — a palette FAMILY, not a role key
|
|
401
|
+
* (`border`/`background`/…) and not `black`/`white`? A family resolves to a
|
|
402
|
+
* shade object; the role/scalar keys resolve to a string, so "value is an
|
|
403
|
+
* object" is the test (no name list to keep in sync).
|
|
404
|
+
*/
|
|
405
|
+
export function isColorName(value: unknown): value is ColorName {
|
|
406
|
+
return (
|
|
407
|
+
typeof value === "string" &&
|
|
408
|
+
value in colors &&
|
|
409
|
+
typeof (colors as Record<string, unknown>)[value] === "object"
|
|
410
|
+
);
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
/**
|
|
414
|
+
* Coerce an arbitrary color token to a {@link ColorName}, falling back to a
|
|
415
|
+
* neutral. The single graceful-degradation point for stored option/status
|
|
416
|
+
* colors: a select option's `color` may be a token a newer table config
|
|
417
|
+
* introduced that this UI build predates, or absent entirely — either way a
|
|
418
|
+
* component renders a neutral badge instead of breaking. Used by `OptionBadge`.
|
|
419
|
+
*/
|
|
420
|
+
export function asColorName(value: unknown, fallback: ColorName = "zinc"): ColorName {
|
|
421
|
+
return isColorName(value) ? value : fallback;
|
|
422
|
+
}
|