@apliteni/apliteni-ui 0.108.0 → 0.114.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/guidelines/labels-and-titles.md +6 -6
- package/package.json +1 -1
- package/react/README.md +69 -1
- package/react/dist/index.d.ts +82 -1
- package/react/dist/index.js +722 -31
- package/src/index.css +1 -0
- package/src/index.d.ts +18 -0
- package/src/index.js +1 -0
- package/src/inline.js +2 -0
- package/src/logic/chart.js +143 -0
- package/src/styles/chart.css +282 -0
- package/src/styles/layout.css +1 -1
package/src/index.css
CHANGED
package/src/index.d.ts
CHANGED
|
@@ -86,6 +86,24 @@ export type NumericValueOptions = { value?: string | number | null; unit?: strin
|
|
|
86
86
|
export type DeltaValueOptions = { value?: string | null; tone?: 'success' | 'danger' | 'neutral'; basisId?: string; missing?: string };
|
|
87
87
|
export declare function formatNumericValue(options?: NumericValueOptions): { text: string; unit: string; missing: string | undefined };
|
|
88
88
|
export declare function formatDeltaValue(options?: DeltaValueOptions): { text: string; className: string; basisId: string | undefined };
|
|
89
|
+
|
|
90
|
+
/** The smallest band a series is drawn in, as a share of its own reach. */
|
|
91
|
+
export declare const CHART_FLOOR: number;
|
|
92
|
+
export type ChartScaleOptions = { ticks?: number; zero?: boolean; floor?: number; nice?: boolean };
|
|
93
|
+
export type ChartScale = { min: number; max: number; step: number; ticks: number[] };
|
|
94
|
+
export declare function chartScale(values: Iterable<number>, options?: ChartScaleOptions): ChartScale;
|
|
95
|
+
export type BridgeStepInput = { label: string; value?: number; kind?: 'total' | 'change' };
|
|
96
|
+
export type BridgeBar<S> = S & { kind: 'total' | 'change'; value: number; from: number; to: number };
|
|
97
|
+
export declare function bridgeWalk<S extends BridgeStepInput>(steps: Iterable<S>): BridgeBar<S>[];
|
|
98
|
+
export type ChartBox = { left: number; right: number; top: number; bottom: number };
|
|
99
|
+
export type PointTargetLimits = {
|
|
100
|
+
floor: number; min?: number; box: ChartBox;
|
|
101
|
+
column: [number, number]; apart?: Iterable<number>;
|
|
102
|
+
};
|
|
103
|
+
export type ChartTarget = { x: number; y: number; w: number; h: number };
|
|
104
|
+
export declare function pointTarget(
|
|
105
|
+
point: { x: number; y: number }, limits: PointTargetLimits,
|
|
106
|
+
): ChartTarget;
|
|
89
107
|
export declare const SCORE: Record<'exact' | 'prefix' | 'wordStart' | 'contains' | 'keyword' | 'description' | 'subsequence', number>;
|
|
90
108
|
export declare const iconNames: string[];
|
|
91
109
|
export declare const iconCategories: { name: string; names: string[] }[];
|
package/src/index.js
CHANGED
|
@@ -35,3 +35,4 @@ export { PAGE_SIZES, DEFAULT_PAGE_SIZE } from './logic/pagination.js';
|
|
|
35
35
|
export { calloutIcons } from './logic/callout.js';
|
|
36
36
|
export { formatNumericValue, formatDeltaValue } from './logic/table-values.js';
|
|
37
37
|
export { accentSwatchStyle } from './logic/accents.js';
|
|
38
|
+
export { CHART_FLOOR, chartScale, bridgeWalk, pointTarget } from './logic/chart.js';
|
package/src/inline.js
CHANGED
|
@@ -50,6 +50,7 @@ export const styles = {
|
|
|
50
50
|
filterBar: read('styles/filter-bar.css'),
|
|
51
51
|
pagination: read('styles/pagination.css'),
|
|
52
52
|
stat: read('styles/stat.css'),
|
|
53
|
+
chart: read('styles/chart.css'),
|
|
53
54
|
empty: read('styles/empty.css'),
|
|
54
55
|
callout: read('styles/callout.css'),
|
|
55
56
|
code: read('styles/code.css'),
|
|
@@ -86,6 +87,7 @@ export const cssText = [
|
|
|
86
87
|
styles.filterBar,
|
|
87
88
|
styles.pagination,
|
|
88
89
|
styles.stat,
|
|
90
|
+
styles.chart,
|
|
89
91
|
styles.empty,
|
|
90
92
|
styles.callout,
|
|
91
93
|
styles.code,
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
// Chart arithmetic, shared by the React Chart and by anything that draws the
|
|
2
|
+
// same shapes without it. No DOM, no pixels: a scale and a walk, both pure.
|
|
3
|
+
// why: docs/components.md#react-charts
|
|
4
|
+
|
|
5
|
+
/** The steps a whole-unit axis may take, per decade, smallest first. 2.5 is
|
|
6
|
+
* among them — 25000 is a whole step and the one that turns a four-tick axis
|
|
7
|
+
* into four ticks — but it is taken only where it lands on a whole number, so
|
|
8
|
+
* nothing below the units decade can put 2.5 on an axis. */
|
|
9
|
+
const STEPS = [1, 2, 2.5, 5, 10];
|
|
10
|
+
|
|
11
|
+
/** The smallest band a series is drawn in, as a share of its own reach. Below
|
|
12
|
+
* this, a series that barely moves would be stretched into a cliff. */
|
|
13
|
+
export const CHART_FLOOR = 0.25;
|
|
14
|
+
|
|
15
|
+
const round = (n) => Math.round(n * 1e6) / 1e6;
|
|
16
|
+
|
|
17
|
+
/** The smallest nice step at or above `raw` that is a whole unit. */
|
|
18
|
+
function niceStep(raw) {
|
|
19
|
+
if (!(raw > 0)) return 1;
|
|
20
|
+
const decade = 10 ** Math.floor(Math.log10(raw));
|
|
21
|
+
for (const s of STEPS) {
|
|
22
|
+
const step = round(s * decade);
|
|
23
|
+
if (step >= raw - 1e-9 && step >= 1 && Number.isInteger(step)) return step;
|
|
24
|
+
}
|
|
25
|
+
return Math.max(1, Math.ceil(raw));
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* The band a series is drawn in, and the ticks written beside it.
|
|
30
|
+
*
|
|
31
|
+
* @param {Iterable<number>} values every number that has to fit
|
|
32
|
+
* @param {object} [options]
|
|
33
|
+
* @param {number} [options.ticks] how many steps to aim for
|
|
34
|
+
* @param {boolean} [options.zero] keep the zero line inside the band — true for
|
|
35
|
+
* bars, which are read from zero, false for a line read against itself
|
|
36
|
+
* @param {number} [options.floor] the smallest band, as a share of the series'
|
|
37
|
+
* own reach; `CHART_FLOOR` for a sparkline
|
|
38
|
+
* @param {boolean} [options.nice] round the band out to whole steps and return
|
|
39
|
+
* them; false for a chart with no axis
|
|
40
|
+
* @returns {{min: number, max: number, step: number, ticks: number[]}}
|
|
41
|
+
*/
|
|
42
|
+
export function chartScale(values, { ticks = 4, zero = true, floor = 0, nice = true } = {}) {
|
|
43
|
+
const finite = [...values].filter((v) => Number.isFinite(v));
|
|
44
|
+
if (finite.length === 0) return { min: 0, max: 1, step: 1, ticks: [0, 1] };
|
|
45
|
+
|
|
46
|
+
let min = Math.min(...finite);
|
|
47
|
+
let max = Math.max(...finite);
|
|
48
|
+
if (zero) { min = Math.min(min, 0); max = Math.max(max, 0); }
|
|
49
|
+
|
|
50
|
+
const reach = Math.max(Math.abs(min), Math.abs(max)) || 1;
|
|
51
|
+
const want = reach * floor;
|
|
52
|
+
if (max - min < want) {
|
|
53
|
+
const pad = (want - (max - min)) / 2;
|
|
54
|
+
min -= pad;
|
|
55
|
+
max += pad;
|
|
56
|
+
if (zero) { min = Math.min(min, 0); max = Math.max(max, 0); }
|
|
57
|
+
}
|
|
58
|
+
if (min === max) { min -= 0.5; max += 0.5; }
|
|
59
|
+
|
|
60
|
+
if (!nice) return { min: round(min), max: round(max), step: 0, ticks: [] };
|
|
61
|
+
|
|
62
|
+
const step = niceStep((max - min) / Math.max(ticks, 1));
|
|
63
|
+
min = Math.floor(round(min / step)) * step;
|
|
64
|
+
max = Math.ceil(round(max / step)) * step;
|
|
65
|
+
const out = [];
|
|
66
|
+
for (let t = min; t <= max + 1e-9; t += step) out.push(round(t));
|
|
67
|
+
return { min: round(min), max: round(max), step, ticks: out };
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* One period walked from a starting total through its components to a result.
|
|
72
|
+
*
|
|
73
|
+
* The first step is a total unless it says otherwise; every later step is a
|
|
74
|
+
* component added to the running total. A step marked `total` with no value of
|
|
75
|
+
* its own takes the running total, which is how the closing column is written
|
|
76
|
+
* without repeating a sum the caller already gave piece by piece.
|
|
77
|
+
*
|
|
78
|
+
* @param {Iterable<{label: string, value?: number, kind?: 'total'|'change'}>} steps
|
|
79
|
+
* @returns {Array<{label: string, kind: 'total'|'change', value: number, from: number, to: number}>}
|
|
80
|
+
* each column's own amount and the two ends of the bar that draws it
|
|
81
|
+
*/
|
|
82
|
+
export function bridgeWalk(steps) {
|
|
83
|
+
let running = 0;
|
|
84
|
+
return [...steps].map((step, index) => {
|
|
85
|
+
const kind = step.kind || (index === 0 ? 'total' : 'change');
|
|
86
|
+
if (kind === 'total') {
|
|
87
|
+
const value = Number.isFinite(step.value) ? step.value : running;
|
|
88
|
+
running = value;
|
|
89
|
+
return { ...step, kind, value: round(value), from: 0, to: round(value) };
|
|
90
|
+
}
|
|
91
|
+
const value = Number.isFinite(step.value) ? step.value : 0;
|
|
92
|
+
const from = running;
|
|
93
|
+
running += value;
|
|
94
|
+
return { ...step, kind, value: round(value), from: round(from), to: round(running) };
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
const clamp = (v, lo, hi) => Math.min(Math.max(v, lo), hi);
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* The pointer target over one drawn point, as a rectangle.
|
|
102
|
+
*
|
|
103
|
+
* A point is a mark a pointer has to reach rather than a control, so its target
|
|
104
|
+
* is a transparent box laid over it. The box grows to the floor this pointer
|
|
105
|
+
* asks for, stops at half the clear space to the nearest point on each side,
|
|
106
|
+
* and is then held inside the box the chart draws in — a target that reached
|
|
107
|
+
* past the chart's edge would be clipped there, and a nominal 44 that a
|
|
108
|
+
* drawing clips to 19 is not a target. `min` is the floor it keeps where the
|
|
109
|
+
* column has room. A narrower column owns the whole column instead, so no point
|
|
110
|
+
* answers for the period beside it.
|
|
111
|
+
* why: guidelines/accessibility-floor.md#tap-target-on-a-phone
|
|
112
|
+
*
|
|
113
|
+
* @param {{x: number, y: number}} point the point's centre
|
|
114
|
+
* @param {object} limits
|
|
115
|
+
* @param {number} limits.floor the target this pointer asks for, each way
|
|
116
|
+
* @param {number} [limits.min] the smallest target, where the column has room
|
|
117
|
+
* @param {{left: number, right: number, top: number, bottom: number}} limits.box
|
|
118
|
+
* the chart's own drawn box, which nothing may reach past
|
|
119
|
+
* @param {[number, number]} limits.column the point's own column, left and right
|
|
120
|
+
* @param {Iterable<number>} [limits.apart] the centre, down, of every other
|
|
121
|
+
* point in the same column
|
|
122
|
+
* @returns {{x: number, y: number, w: number, h: number}}
|
|
123
|
+
*/
|
|
124
|
+
export function pointTarget({ x, y }, { floor, min = floor, box, column, apart = [] }) {
|
|
125
|
+
const fit = (centre, [outerLo, outerHi], [spaceLo, spaceHi], neighbours) => {
|
|
126
|
+
let lo = spaceLo;
|
|
127
|
+
let hi = spaceHi;
|
|
128
|
+
for (const n of neighbours) {
|
|
129
|
+
const mid = (centre + n) / 2;
|
|
130
|
+
if (n < centre) lo = Math.max(lo, mid);
|
|
131
|
+
if (n > centre) hi = Math.min(hi, mid);
|
|
132
|
+
}
|
|
133
|
+
const size = Math.min(floor, Math.max(0, hi - lo));
|
|
134
|
+
// Centred on the point, then held inside the space it may own, then inside
|
|
135
|
+
// the chart's box. The box is last so the point on the chart's own edge
|
|
136
|
+
// keeps a whole target instead of half of one.
|
|
137
|
+
const owned = clamp(centre - size / 2, lo, Math.max(lo, hi - size));
|
|
138
|
+
return { at: clamp(owned, outerLo, Math.max(outerLo, outerHi - size)), size };
|
|
139
|
+
};
|
|
140
|
+
const across = fit(x, [box.left, box.right], column, []);
|
|
141
|
+
const down = fit(y, [box.top, box.bottom], [box.top, box.bottom], apart);
|
|
142
|
+
return { x: across.at, y: down.at, w: across.size, h: down.size };
|
|
143
|
+
}
|
|
@@ -0,0 +1,282 @@
|
|
|
1
|
+
/* ============================================================================
|
|
2
|
+
* Chart — .ui-chart, the shapes a money dashboard keeps redrawing.
|
|
3
|
+
* .ui-chart__legend one key per series, plus the unfinished-period key
|
|
4
|
+
* .ui-chart__frame the tab stop; the value axis beside the scroller
|
|
5
|
+
* .ui-chart__scroll the scroller, masked on whichever side hides columns
|
|
6
|
+
* .ui-chart__plot the drawing and its column labels
|
|
7
|
+
* .ui-chart__data the same numbers as a table
|
|
8
|
+
* The component sets --ui-chart-h, the plot's height, which the axis matches.
|
|
9
|
+
* why: docs/components.md#react-charts
|
|
10
|
+
* ========================================================================== */
|
|
11
|
+
|
|
12
|
+
.ui-chart {
|
|
13
|
+
--ui-chart-col: var(--space-12);
|
|
14
|
+
/* The ground the marks are drawn on, so an unfinished period and the rim the
|
|
15
|
+
line is carried on read as cut out of it. A chart outside a card names its
|
|
16
|
+
own. */
|
|
17
|
+
--ui-chart-ground: var(--surface);
|
|
18
|
+
/* The tick's own line box. The axis spans the band the gridlines are drawn
|
|
19
|
+
in — the plot less the room a stroke needs at each edge — and is half a
|
|
20
|
+
line box taller at each end, so every tick's middle lands on its gridline
|
|
21
|
+
without taking a single label out of flow. */
|
|
22
|
+
--ui-chart-tick: calc(var(--text-xs) * 1.45);
|
|
23
|
+
display: flex;
|
|
24
|
+
flex-direction: column;
|
|
25
|
+
gap: var(--space-3);
|
|
26
|
+
font-size: var(--text-xs);
|
|
27
|
+
color: var(--text);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
.ui-chart--bridge { --ui-chart-col: calc(var(--space-16) + var(--space-6)); }
|
|
31
|
+
.ui-chart--spark {
|
|
32
|
+
--ui-chart-col: var(--space-6);
|
|
33
|
+
gap: 0;
|
|
34
|
+
min-width: 0;
|
|
35
|
+
}
|
|
36
|
+
/* No axis, so the scroller takes the whole frame rather than the auto column
|
|
37
|
+
the axis would have held. */
|
|
38
|
+
.ui-chart--spark .ui-chart__frame { grid-template-columns: minmax(0, 1fr); }
|
|
39
|
+
/* The box, not the drawing: a phone target does not fit in 32px. Written here
|
|
40
|
+
rather than left to the height attribute, which `.ui-stat__trend svg` beats.
|
|
41
|
+
why: docs/components.md#react-charts */
|
|
42
|
+
.ui-chart--spark .ui-chart__svg { height: var(--ui-chart-box); }
|
|
43
|
+
|
|
44
|
+
/* -- the legend ------------------------------------------------------------ */
|
|
45
|
+
|
|
46
|
+
.ui-chart__legend {
|
|
47
|
+
/* muted-ink: chrome — the key names the marks and is not the chart's words */
|
|
48
|
+
color: var(--muted);
|
|
49
|
+
display: flex;
|
|
50
|
+
flex-wrap: wrap;
|
|
51
|
+
align-items: center;
|
|
52
|
+
column-gap: var(--space-4);
|
|
53
|
+
row-gap: var(--space-1);
|
|
54
|
+
margin: 0;
|
|
55
|
+
padding: 0;
|
|
56
|
+
list-style: none;
|
|
57
|
+
}
|
|
58
|
+
.ui-chart__key {
|
|
59
|
+
display: flex;
|
|
60
|
+
align-items: center;
|
|
61
|
+
gap: var(--space-2);
|
|
62
|
+
white-space: nowrap;
|
|
63
|
+
}
|
|
64
|
+
.ui-chart__key-mark { flex: none; }
|
|
65
|
+
.ui-chart__key-swatch { fill: var(--ui-chart-tone); }
|
|
66
|
+
.ui-chart__key-line { fill: none; stroke: var(--ui-chart-tone); stroke-width: 2; stroke-linecap: round; }
|
|
67
|
+
.ui-chart__key-estimated {
|
|
68
|
+
fill: var(--ui-chart-tone);
|
|
69
|
+
stroke: var(--ui-chart-ground);
|
|
70
|
+
stroke-width: 1;
|
|
71
|
+
stroke-dasharray: 2 2;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/* -- the frame ------------------------------------------------------------- */
|
|
75
|
+
|
|
76
|
+
.ui-chart__frame {
|
|
77
|
+
display: grid;
|
|
78
|
+
grid-template-columns: auto minmax(0, 1fr);
|
|
79
|
+
align-items: start;
|
|
80
|
+
column-gap: var(--space-2);
|
|
81
|
+
padding-block: calc(var(--ring-width, 2px) + var(--ring-offset, 1px));
|
|
82
|
+
border-radius: var(--radius-xs);
|
|
83
|
+
}
|
|
84
|
+
.ui-chart__frame:focus-visible {
|
|
85
|
+
outline-offset: var(--ring-offset, 1px);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
.ui-chart__axis {
|
|
89
|
+
/* muted-ink: chrome — a tick names the scale; exact values are reachable through the readout */
|
|
90
|
+
color: var(--muted);
|
|
91
|
+
display: flex;
|
|
92
|
+
flex-direction: column;
|
|
93
|
+
justify-content: space-between;
|
|
94
|
+
height: calc(var(--ui-chart-h) - var(--ui-chart-pad) * 2 + var(--ui-chart-tick));
|
|
95
|
+
margin-block: calc(var(--ui-chart-pad) - var(--ui-chart-tick) / 2);
|
|
96
|
+
text-align: right;
|
|
97
|
+
font-variant-numeric: tabular-nums;
|
|
98
|
+
}
|
|
99
|
+
.ui-chart__tick { line-height: 1.45; }
|
|
100
|
+
|
|
101
|
+
/* The mask fades whichever side still hides periods. It paints no ground of its
|
|
102
|
+
own, so a chart on the card and one on the page both fade correctly — and it
|
|
103
|
+
sits on the scroller rather than the tab stop, which would mask its ring. */
|
|
104
|
+
.ui-chart__scroll {
|
|
105
|
+
overflow-x: auto;
|
|
106
|
+
overflow-y: hidden;
|
|
107
|
+
overscroll-behavior-x: contain;
|
|
108
|
+
}
|
|
109
|
+
.ui-chart__scroll.is-more-before {
|
|
110
|
+
mask-image: linear-gradient(to right, transparent 0, #000 var(--space-6));
|
|
111
|
+
}
|
|
112
|
+
.ui-chart__scroll.is-more-after {
|
|
113
|
+
mask-image: linear-gradient(to right, #000 calc(100% - var(--space-6)), transparent 100%);
|
|
114
|
+
}
|
|
115
|
+
.ui-chart__scroll.is-more-before.is-more-after {
|
|
116
|
+
mask-image: linear-gradient(
|
|
117
|
+
to right, transparent 0, #000 var(--space-6),
|
|
118
|
+
#000 calc(100% - var(--space-6)), transparent 100%);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
.ui-chart__plot { min-width: calc(var(--ui-chart-col) * var(--ui-chart-cols, 1)); }
|
|
122
|
+
.ui-chart__svg { display: block; }
|
|
123
|
+
|
|
124
|
+
.ui-chart__periods {
|
|
125
|
+
display: grid;
|
|
126
|
+
grid-auto-flow: column;
|
|
127
|
+
grid-auto-columns: 1fr;
|
|
128
|
+
/* The row stood 4px under the plot plus the 2px rule every label reserved for
|
|
129
|
+
the picked one. The rule is gone and the gap keeps that step on the scale
|
|
130
|
+
rather than between two of its steps. */
|
|
131
|
+
padding-top: var(--space-2);
|
|
132
|
+
}
|
|
133
|
+
.ui-chart__period {
|
|
134
|
+
/* muted-ink: chrome — a period label names a column; its values are reachable through the readout */
|
|
135
|
+
color: var(--muted);
|
|
136
|
+
padding-inline: var(--space-1);
|
|
137
|
+
text-align: center;
|
|
138
|
+
line-height: 1.45;
|
|
139
|
+
overflow-wrap: anywhere;
|
|
140
|
+
}
|
|
141
|
+
/* The picked column's label: the body ink the rest of the row gives up, at the
|
|
142
|
+
weight a reader who separates no colours still reads. Selection is otherwise
|
|
143
|
+
the band behind the column and the band alone — the outline the chart draws
|
|
144
|
+
belongs to focus. */
|
|
145
|
+
.ui-chart__period.is-selected {
|
|
146
|
+
color: var(--strong);
|
|
147
|
+
font-weight: 600;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/* -- the marks ------------------------------------------------------------- */
|
|
151
|
+
|
|
152
|
+
/* The rules. A hairline at each tick carries a bar's height across twelve columns
|
|
153
|
+
to the label that names it, which is the one job a grid has; it is the kit's
|
|
154
|
+
faintest edge, so it is found when a reader looks for it and not before. The
|
|
155
|
+
zero rule keeps --border-strong above it: the line a signed series is measured
|
|
156
|
+
from leads the ones it is measured against. Both sit under every mark. */
|
|
157
|
+
.ui-chart__grid { stroke: var(--border); stroke-width: 1; }
|
|
158
|
+
.ui-chart__zero { stroke: var(--border-strong); stroke-width: 1; }
|
|
159
|
+
|
|
160
|
+
/* The carry a bridge draws between two steps: the running total leaves one
|
|
161
|
+
column at the height it reached and meets the next where it starts from. It
|
|
162
|
+
takes --border-strong, the zero rule's weight rather than the grid's, because
|
|
163
|
+
it is read along rather than looked up. Solid, like every other rule here: a
|
|
164
|
+
dash would read as a forecast. */
|
|
165
|
+
.ui-chart__carry { stroke: var(--border-strong); stroke-width: 1; }
|
|
166
|
+
|
|
167
|
+
/* The picked column, behind every mark and every rule. A quiet fill rather than
|
|
168
|
+
the accent, which the chart spends on a series; it is wider than any bar, so
|
|
169
|
+
the column is found before the bar in it is read. */
|
|
170
|
+
.ui-chart__band { fill: var(--surface-3); }
|
|
171
|
+
|
|
172
|
+
.ui-chart__bar {
|
|
173
|
+
/* One flat tone, the whole bar. A ramp down the bar was the one thing in the
|
|
174
|
+
drawing that carried no number: it let the middle go while both edges held,
|
|
175
|
+
so a reader comparing two months compared two shaded cylinders. */
|
|
176
|
+
fill: var(--ui-chart-tone);
|
|
177
|
+
stroke: none;
|
|
178
|
+
}
|
|
179
|
+
/* Keep each series' fill weight. Ground-coloured dashes mark unfinished data
|
|
180
|
+
without joining the mirrored bars across the zero channel. */
|
|
181
|
+
.ui-chart__bar.is-estimated {
|
|
182
|
+
fill: var(--ui-chart-tone);
|
|
183
|
+
stroke: var(--ui-chart-ground);
|
|
184
|
+
stroke-width: 1;
|
|
185
|
+
stroke-dasharray: 3 2;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/* An isolated value has no segment to draw it. Connected points need no dot. */
|
|
189
|
+
.ui-chart__isolated { fill: var(--ui-chart-tone); stroke: var(--ui-chart-ground); stroke-width: 1; }
|
|
190
|
+
|
|
191
|
+
.ui-chart__line {
|
|
192
|
+
fill: none;
|
|
193
|
+
stroke: var(--ui-chart-tone);
|
|
194
|
+
stroke-width: 2;
|
|
195
|
+
stroke-linecap: round;
|
|
196
|
+
}
|
|
197
|
+
.ui-chart__line.is-estimated { stroke-dasharray: 5 3; }
|
|
198
|
+
|
|
199
|
+
/* The ground carried under the line. The line crosses bars of its own family,
|
|
200
|
+
where hue alone separates them and luminance does not: a stroke of the ground
|
|
201
|
+
each side gives the line the 3:1 it cannot get from the bar behind it. It is
|
|
202
|
+
not drawn where there is nothing to cross — a sparkline has no bars, and at
|
|
203
|
+
text size the casing would eat the line. The line carries no marker of its
|
|
204
|
+
own; a dot on every point drew nothing the line had not already drawn. */
|
|
205
|
+
.ui-chart__line-casing {
|
|
206
|
+
fill: none;
|
|
207
|
+
stroke: var(--ui-chart-ground);
|
|
208
|
+
stroke-width: 4;
|
|
209
|
+
stroke-linecap: round;
|
|
210
|
+
}
|
|
211
|
+
.ui-chart--spark .ui-chart__line-casing { display: none; }
|
|
212
|
+
|
|
213
|
+
.ui-chart--spark .ui-chart__line { stroke-width: 1.6; }
|
|
214
|
+
|
|
215
|
+
/* The edge the readout is placed against: the mark's top. Geometry only — it
|
|
216
|
+
paints nothing. The readout opens above that edge where the frame has the
|
|
217
|
+
room and flips below it where it has not, which near the top of the plot
|
|
218
|
+
means covering the mark and the first axis labels. The guideline asks for
|
|
219
|
+
that flip rather than for a readout outside the part.
|
|
220
|
+
why: guidelines/hover-readouts.md#prefer-above-the-mark */
|
|
221
|
+
.ui-chart__anchor { fill: none; pointer-events: none; }
|
|
222
|
+
|
|
223
|
+
/* The hit area a pointer lands on. It is the series' own band in the column, so
|
|
224
|
+
a pointer between two bars still has a bar to answer with. */
|
|
225
|
+
.ui-chart__hit { fill: transparent; }
|
|
226
|
+
|
|
227
|
+
/* A chart whose columns can be picked says so under the pointer. The key
|
|
228
|
+
compound names the element rather than a class, which is how the kit's
|
|
229
|
+
clickable-chrome gate reads `.ui-seg button` and `.ui-check input`: the mark
|
|
230
|
+
is this <g> and cannot be moved onto a <button>, so there is no button
|
|
231
|
+
chrome to measure. */
|
|
232
|
+
.ui-chart--pick [data-mark] { cursor: pointer; }
|
|
233
|
+
|
|
234
|
+
/* The tones. A series names one; the token behind it is the kit's, and the same
|
|
235
|
+
in both themes' terms. The neutral one is --muted, which is what
|
|
236
|
+
--signal-solid-neutral already is: a mid grey in both themes, where --dim
|
|
237
|
+
would paint a bridge's totals near-black in light and near-white in dark. */
|
|
238
|
+
.ui-chart__tone--accent { --ui-chart-tone: var(--accent); }
|
|
239
|
+
/* The accent's second weight: the same hue, let down towards the ground rather
|
|
240
|
+
than given an opacity, so a mark painted with it is opaque and a reader
|
|
241
|
+
measures one colour. The mix crosses 3:1 at 67% against the card AND against
|
|
242
|
+
the page, in both themes, under all four accents, so 70% ships with room over
|
|
243
|
+
the bar rather than sitting on it — the 40% the restyle's mock drew measures
|
|
244
|
+
1.99:1 and 2.03:1 and does not ship. */
|
|
245
|
+
.ui-chart__tone--accent-soft {
|
|
246
|
+
--ui-chart-tone: color-mix(in srgb, var(--accent) 70%, var(--ui-chart-ground));
|
|
247
|
+
}
|
|
248
|
+
.ui-chart__tone--good { --ui-chart-tone: var(--green); }
|
|
249
|
+
.ui-chart__tone--bad { --ui-chart-tone: var(--pink); }
|
|
250
|
+
.ui-chart__tone--warn { --ui-chart-tone: var(--amber); }
|
|
251
|
+
.ui-chart__tone--info { --ui-chart-tone: var(--cyan); }
|
|
252
|
+
.ui-chart__tone--neutral { --ui-chart-tone: var(--muted); }
|
|
253
|
+
|
|
254
|
+
/* -- the table ------------------------------------------------------------- */
|
|
255
|
+
|
|
256
|
+
.ui-chart__data > summary {
|
|
257
|
+
width: max-content;
|
|
258
|
+
min-height: var(--tap-min, 44px);
|
|
259
|
+
display: inline-flex;
|
|
260
|
+
align-items: center;
|
|
261
|
+
box-sizing: border-box;
|
|
262
|
+
padding: var(--space-1) var(--space-2);
|
|
263
|
+
border-radius: var(--radius-xs);
|
|
264
|
+
cursor: pointer;
|
|
265
|
+
font-size: var(--text-sm);
|
|
266
|
+
}
|
|
267
|
+
.ui-chart__data[open] > summary { margin-bottom: var(--space-3); }
|
|
268
|
+
/* The table is the one place a phone reader gets exact values, and how wide it
|
|
269
|
+
is depends on the caller's series: it carries them sideways inside the kit's
|
|
270
|
+
own scroller, so the card and the page keep their width whatever it holds.
|
|
271
|
+
Sideways only — there are as many rows as the chart has columns, and capping
|
|
272
|
+
their height would hide the last period behind a second scrollbar. */
|
|
273
|
+
.ui-chart__data .ui-table-scroll { --ui-table-height: none; }
|
|
274
|
+
.ui-chart__data .ui-table { margin: 0; }
|
|
275
|
+
/* Why a period is not final is a sentence, not a label, so it wraps under the
|
|
276
|
+
period it qualifies instead of carrying the money columns another two hundred
|
|
277
|
+
pixels to the right of a 390px card. */
|
|
278
|
+
.ui-chart__data .ui-table tbody th { white-space: normal; }
|
|
279
|
+
.ui-chart__state {
|
|
280
|
+
/* muted-ink: state — the word that marks a period as not final */
|
|
281
|
+
color: var(--muted);
|
|
282
|
+
}
|
package/src/styles/layout.css
CHANGED
|
@@ -119,7 +119,7 @@
|
|
|
119
119
|
resting step. */
|
|
120
120
|
/* Re-tuned at #295, which took the light rail down a step with the page. Below 720px
|
|
121
121
|
the glyph is the whole row, so it is held at text grade rather than at 1.4.11's bar —
|
|
122
|
-
stories/apps/shell-states.test.js:
|
|
122
|
+
stories/apps/shell-states.test.js:1706 `a resting glyph is dimmer but still legible`. */
|
|
123
123
|
.ui-app__rail .ui-nav__ic svg { opacity: 0.66; }
|
|
124
124
|
|
|
125
125
|
/* A destructive row of the rail is a navigation row and rests like one; the danger
|