@robr0/design-system 0.11.0 → 0.13.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 -4
- package/charts.d.ts +1 -0
- package/charts.js +2 -0
- package/components/AppLayout/AppLayout.d.ts +7 -1
- package/components/AppLayout/AppLayout.js +2 -1
- package/components/AppSidebar/AppSidebar.css +104 -40
- package/components/AppSidebar/AppSidebar.d.ts +14 -1
- package/components/AppSidebar/AppSidebar.js +26 -17
- package/components/AvatarGroup/AvatarGroup.css +61 -0
- package/components/AvatarGroup/AvatarGroup.d.ts +32 -0
- package/components/AvatarGroup/AvatarGroup.js +31 -0
- package/components/Badge/Badge.css +4 -5
- package/components/Chart/AreaChart.d.ts +5 -1
- package/components/Chart/AreaChart.js +5 -4
- package/components/Chart/BarChart.d.ts +3 -1
- package/components/Chart/BarChart.js +3 -3
- package/components/Chart/Chart.css +45 -0
- package/components/Chart/ComboChart.d.ts +40 -0
- package/components/Chart/ComboChart.js +142 -0
- package/components/Chart/LineChart.d.ts +3 -1
- package/components/Chart/LineChart.js +3 -3
- package/components/Chart/PieChart.d.ts +3 -1
- package/components/Chart/PieChart.js +3 -3
- package/components/Chart/RadarChart.d.ts +3 -1
- package/components/Chart/RadarChart.js +3 -3
- package/components/Chart/RadialChart.d.ts +7 -1
- package/components/Chart/RadialChart.js +51 -43
- package/components/Chart/ScatterChart.d.ts +3 -1
- package/components/Chart/ScatterChart.js +3 -3
- package/components/Chart/StackedBarChart.d.ts +3 -1
- package/components/Chart/StackedBarChart.js +3 -3
- package/components/Chart/Treemap.d.ts +3 -1
- package/components/Chart/Treemap.js +3 -3
- package/components/Chart/palette.js +1 -3
- package/components/FilterBar/FilterBar.css +202 -0
- package/components/FilterBar/FilterBar.d.ts +55 -0
- package/components/FilterBar/FilterBar.js +249 -0
- package/components/FunnelChart/FunnelChart.css +41 -0
- package/components/FunnelChart/FunnelChart.d.ts +40 -0
- package/components/FunnelChart/FunnelChart.js +48 -0
- package/components/Gauge/Gauge.css +93 -0
- package/components/Gauge/Gauge.d.ts +56 -0
- package/components/Gauge/Gauge.js +95 -0
- package/components/LegendTile/LegendTile.css +60 -0
- package/components/LegendTile/LegendTile.d.ts +30 -0
- package/components/LegendTile/LegendTile.js +21 -0
- package/components/Panel/Panel.css +26 -0
- package/components/Panel/Panel.d.ts +22 -0
- package/components/Panel/Panel.js +19 -0
- package/components/SplitPane/SplitPane.css +106 -0
- package/components/SplitPane/SplitPane.d.ts +36 -0
- package/components/SplitPane/SplitPane.js +130 -0
- package/components/Stat/Stat.css +35 -2
- package/components/Stat/Stat.d.ts +3 -1
- package/components/Stat/Stat.js +7 -1
- package/components/StreamingText/StreamingText.css +40 -0
- package/components/StreamingText/StreamingText.d.ts +44 -0
- package/components/StreamingText/StreamingText.js +61 -0
- package/components/registry.json +73 -0
- package/components/registry.json.d.ts +73 -0
- package/components/registry.json.js +1 -1
- package/index.d.ts +8 -0
- package/index.js +16 -0
- package/package.json +1 -1
- package/tokens/motion.d.ts +7 -3
- package/tokens/motion.js +3 -1
- package/tokens/registry.json +4 -1
- package/tokens/registry.json.d.ts +4 -1
- package/tokens/registry.json.js +1 -1
- package/tokens/tokens-dark.css +3 -0
- package/tokens/tokens-light.css +11 -0
- package/tokens/tokens-primitives.css +2 -0
- package/tokens/tokens-typography.css +12 -5
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
2
|
+
import React from "react";
|
|
3
|
+
import "./FunnelChart.css";
|
|
4
|
+
const SERIES_COUNT = 7;
|
|
5
|
+
const FunnelChart = React.forwardRef(
|
|
6
|
+
({ data, height = 190, minStageShare = 16, className = "", style, ...rest }, ref) => {
|
|
7
|
+
const baseClass = "ds-funnel-chart";
|
|
8
|
+
const classes = [baseClass, className].filter(Boolean).join(" ");
|
|
9
|
+
const firstValue = data.length > 0 ? data[0].value : 0;
|
|
10
|
+
const clampShare = (share) => Math.min(Math.max(share, minStageShare), 100);
|
|
11
|
+
const stages = data.map((stage, i) => ({
|
|
12
|
+
...stage,
|
|
13
|
+
share: clampShare(firstValue > 0 ? stage.value / firstValue * 100 : 0),
|
|
14
|
+
color: `var(--color-chart-series-${i % SERIES_COUNT + 1})`
|
|
15
|
+
}));
|
|
16
|
+
const ariaLabel = stages.length > 0 ? `Funnel: ${stages.map((s) => `${s.label} ${s.displayValue ?? s.value.toLocaleString()}`).join(", ")}` : "Funnel, no data";
|
|
17
|
+
return /* @__PURE__ */ jsx(
|
|
18
|
+
"div",
|
|
19
|
+
{
|
|
20
|
+
...rest,
|
|
21
|
+
ref,
|
|
22
|
+
className: classes,
|
|
23
|
+
style: { ...style, height },
|
|
24
|
+
role: "img",
|
|
25
|
+
"aria-label": ariaLabel,
|
|
26
|
+
children: stages.map((stage, i) => /* @__PURE__ */ jsx(
|
|
27
|
+
"div",
|
|
28
|
+
{
|
|
29
|
+
className: `${baseClass}__stage`,
|
|
30
|
+
style: {
|
|
31
|
+
"--funnel-stage-size": stage.share,
|
|
32
|
+
"--funnel-stage-color": stage.color
|
|
33
|
+
},
|
|
34
|
+
children: /* @__PURE__ */ jsxs("span", { className: `${baseClass}__pct`, children: [
|
|
35
|
+
Math.round(firstValue > 0 ? stage.value / firstValue * 100 : 0),
|
|
36
|
+
"%"
|
|
37
|
+
] })
|
|
38
|
+
},
|
|
39
|
+
`${stage.label}-${i}`
|
|
40
|
+
))
|
|
41
|
+
}
|
|
42
|
+
);
|
|
43
|
+
}
|
|
44
|
+
);
|
|
45
|
+
FunnelChart.displayName = "FunnelChart";
|
|
46
|
+
export {
|
|
47
|
+
FunnelChart
|
|
48
|
+
};
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
/* ============================================
|
|
2
|
+
GAUGE COMPONENT
|
|
3
|
+
Radial dial for a single bounded reading.
|
|
4
|
+
Colour flows through `color` — the value arc
|
|
5
|
+
and reading draw with currentColor, so a tone
|
|
6
|
+
is one custom property swap.
|
|
7
|
+
============================================ */
|
|
8
|
+
|
|
9
|
+
/* Base */
|
|
10
|
+
|
|
11
|
+
.ds-gauge {
|
|
12
|
+
position: relative;
|
|
13
|
+
display: inline-flex;
|
|
14
|
+
align-items: center;
|
|
15
|
+
justify-content: center;
|
|
16
|
+
flex-shrink: 0;
|
|
17
|
+
color: var(--color-action-primary-bg);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
.ds-gauge__svg {
|
|
21
|
+
display: block;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/* Parts */
|
|
25
|
+
|
|
26
|
+
.ds-gauge__track {
|
|
27
|
+
fill: none;
|
|
28
|
+
stroke: var(--color-bg-container-tertiary);
|
|
29
|
+
stroke-linecap: round;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
.ds-gauge__arc {
|
|
33
|
+
fill: none;
|
|
34
|
+
stroke: currentColor;
|
|
35
|
+
stroke-linecap: round;
|
|
36
|
+
transition: stroke-dasharray var(--motion-duration-slow) var(--motion-ease-emphasized);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
.ds-gauge__center {
|
|
40
|
+
position: absolute;
|
|
41
|
+
inset: 0;
|
|
42
|
+
display: flex;
|
|
43
|
+
flex-direction: column;
|
|
44
|
+
align-items: center;
|
|
45
|
+
justify-content: center;
|
|
46
|
+
gap: var(--gap-xxs);
|
|
47
|
+
text-align: center;
|
|
48
|
+
pointer-events: none;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
.ds-gauge__value {
|
|
52
|
+
font-family: var(--font-heading-3-family);
|
|
53
|
+
font-size: var(--font-heading-3-size);
|
|
54
|
+
font-weight: var(--font-heading-3-weight);
|
|
55
|
+
line-height: var(--font-heading-3-line-height);
|
|
56
|
+
letter-spacing: var(--font-heading-3-letter-spacing);
|
|
57
|
+
color: var(--color-text-primary);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
.ds-gauge__label {
|
|
61
|
+
font-family: var(--font-caption-family);
|
|
62
|
+
font-size: var(--font-caption-size);
|
|
63
|
+
font-weight: var(--font-caption-weight);
|
|
64
|
+
line-height: var(--font-caption-line-height);
|
|
65
|
+
letter-spacing: var(--font-caption-letter-spacing);
|
|
66
|
+
color: var(--color-text-secondary);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/* Tones */
|
|
70
|
+
|
|
71
|
+
.ds-gauge--accent {
|
|
72
|
+
color: var(--color-action-primary-bg);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/* The status hues come from the border tokens, not the text tokens: the
|
|
76
|
+
border set is stable across themes, while the dark-mode text tints are
|
|
77
|
+
near-white and would wash three tones into one. */
|
|
78
|
+
|
|
79
|
+
.ds-gauge--positive {
|
|
80
|
+
color: var(--color-status-positive-border);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
.ds-gauge--warning {
|
|
84
|
+
color: var(--color-status-warning-border);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
.ds-gauge--error {
|
|
88
|
+
color: var(--color-status-error-border);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
.ds-gauge--neutral {
|
|
92
|
+
color: var(--color-text-secondary);
|
|
93
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { default as React } from 'react';
|
|
2
|
+
/** One colour switch point: at or above `value`, the dial takes `tone`. */
|
|
3
|
+
export interface GaugeThreshold {
|
|
4
|
+
/** Reading at which this tone takes over. */
|
|
5
|
+
value: number;
|
|
6
|
+
/** Tone applied from this reading upward. */
|
|
7
|
+
tone: 'accent' | 'positive' | 'warning' | 'error' | 'neutral';
|
|
8
|
+
}
|
|
9
|
+
/** Props owned by Gauge itself — everything else falls through to the root div. */
|
|
10
|
+
type GaugeOwnProps = {
|
|
11
|
+
/** Current reading. Clamped into the `min`–`max` range for drawing. */
|
|
12
|
+
value: number;
|
|
13
|
+
/** Lower bound of the dial. */
|
|
14
|
+
min?: number;
|
|
15
|
+
/** Upper bound of the dial. */
|
|
16
|
+
max?: number;
|
|
17
|
+
/**
|
|
18
|
+
* Colour role for the value arc and reading. Ignored while a threshold
|
|
19
|
+
* matches — thresholds exist so the dial recolours itself as the reading
|
|
20
|
+
* crosses them.
|
|
21
|
+
*/
|
|
22
|
+
tone?: 'accent' | 'positive' | 'warning' | 'error' | 'neutral';
|
|
23
|
+
/**
|
|
24
|
+
* Colour switch points, e.g. warning at 70 and error at 90. The highest
|
|
25
|
+
* threshold at or below the current reading wins; below them all, the
|
|
26
|
+
* `tone` prop applies.
|
|
27
|
+
*/
|
|
28
|
+
thresholds?: GaugeThreshold[];
|
|
29
|
+
/** Shows the reading in the centre of the dial. */
|
|
30
|
+
showValue?: boolean;
|
|
31
|
+
/** Formats the centre reading — for units, precision, or locale. */
|
|
32
|
+
formatValue?: (value: number) => string;
|
|
33
|
+
/**
|
|
34
|
+
* What the reading measures, shown as a caption under it and used as the
|
|
35
|
+
* accessible name, e.g. "CPU usage".
|
|
36
|
+
*/
|
|
37
|
+
label?: string;
|
|
38
|
+
/** Rendered diameter in pixels. The arc geometry scales with it. */
|
|
39
|
+
size?: number;
|
|
40
|
+
/** Arc thickness in pixels. */
|
|
41
|
+
strokeWidth?: number;
|
|
42
|
+
/** Additional CSS classes */
|
|
43
|
+
className?: string;
|
|
44
|
+
};
|
|
45
|
+
export interface GaugeProps extends GaugeOwnProps, Omit<React.ComponentPropsWithoutRef<'div'>, keyof GaugeOwnProps | 'children'> {
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Gauge — a radial dial for a single bounded reading: capacity, usage, a
|
|
49
|
+
* score against a target. Pure SVG computed from props — no charting library,
|
|
50
|
+
* no hooks — so it renders from a Server Component and drops straight into a
|
|
51
|
+
* Panel or Stat row. Thresholds recolour the dial through the status roles as
|
|
52
|
+
* the reading crosses them, and the arc animates between readings via a CSS
|
|
53
|
+
* transition. Announced as a `meter` with the label as its accessible name.
|
|
54
|
+
*/
|
|
55
|
+
export declare const Gauge: React.ForwardRefExoticComponent<GaugeProps & React.RefAttributes<HTMLDivElement>>;
|
|
56
|
+
export {};
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { jsxs, jsx } from "react/jsx-runtime";
|
|
2
|
+
import React from "react";
|
|
3
|
+
import "./Gauge.css";
|
|
4
|
+
const SWEEP_UNITS = 75;
|
|
5
|
+
const Gauge = React.forwardRef(
|
|
6
|
+
({
|
|
7
|
+
value,
|
|
8
|
+
min = 0,
|
|
9
|
+
max = 100,
|
|
10
|
+
tone = "accent",
|
|
11
|
+
thresholds,
|
|
12
|
+
showValue = true,
|
|
13
|
+
formatValue,
|
|
14
|
+
label,
|
|
15
|
+
size = 120,
|
|
16
|
+
strokeWidth = 10,
|
|
17
|
+
className = "",
|
|
18
|
+
...rest
|
|
19
|
+
}, ref) => {
|
|
20
|
+
const baseClass = "ds-gauge";
|
|
21
|
+
const range = max - min;
|
|
22
|
+
const fraction = range > 0 ? Math.min(Math.max((value - min) / range, 0), 1) : 0;
|
|
23
|
+
const activeThreshold = thresholds?.filter((t) => value >= t.value).sort((a, b) => b.value - a.value)[0];
|
|
24
|
+
const resolvedTone = activeThreshold?.tone ?? tone;
|
|
25
|
+
const classes = [baseClass, `${baseClass}--${resolvedTone}`, className].filter(Boolean).join(" ");
|
|
26
|
+
const radius = (size - strokeWidth) / 2;
|
|
27
|
+
const center = size / 2;
|
|
28
|
+
const valueUnits = Math.round(SWEEP_UNITS * fraction * 100) / 100;
|
|
29
|
+
const displayValue = formatValue ? formatValue(value) : String(Math.round(value));
|
|
30
|
+
return /* @__PURE__ */ jsxs(
|
|
31
|
+
"div",
|
|
32
|
+
{
|
|
33
|
+
...rest,
|
|
34
|
+
ref,
|
|
35
|
+
className: classes,
|
|
36
|
+
style: { width: size, height: size, ...rest.style },
|
|
37
|
+
role: "meter",
|
|
38
|
+
"aria-valuenow": value,
|
|
39
|
+
"aria-valuemin": min,
|
|
40
|
+
"aria-valuemax": max,
|
|
41
|
+
"aria-valuetext": displayValue,
|
|
42
|
+
"aria-label": label ?? rest["aria-label"],
|
|
43
|
+
children: [
|
|
44
|
+
/* @__PURE__ */ jsxs(
|
|
45
|
+
"svg",
|
|
46
|
+
{
|
|
47
|
+
className: `${baseClass}__svg`,
|
|
48
|
+
viewBox: `0 0 ${size} ${size}`,
|
|
49
|
+
width: size,
|
|
50
|
+
height: size,
|
|
51
|
+
xmlns: "http://www.w3.org/2000/svg",
|
|
52
|
+
"aria-hidden": "true",
|
|
53
|
+
children: [
|
|
54
|
+
/* @__PURE__ */ jsx(
|
|
55
|
+
"circle",
|
|
56
|
+
{
|
|
57
|
+
className: `${baseClass}__track`,
|
|
58
|
+
cx: center,
|
|
59
|
+
cy: center,
|
|
60
|
+
r: radius,
|
|
61
|
+
pathLength: 100,
|
|
62
|
+
strokeWidth,
|
|
63
|
+
strokeDasharray: `${SWEEP_UNITS} ${100 - SWEEP_UNITS}`,
|
|
64
|
+
transform: `rotate(135 ${center} ${center})`
|
|
65
|
+
}
|
|
66
|
+
),
|
|
67
|
+
valueUnits > 0 && /* @__PURE__ */ jsx(
|
|
68
|
+
"circle",
|
|
69
|
+
{
|
|
70
|
+
className: `${baseClass}__arc`,
|
|
71
|
+
cx: center,
|
|
72
|
+
cy: center,
|
|
73
|
+
r: radius,
|
|
74
|
+
pathLength: 100,
|
|
75
|
+
strokeWidth,
|
|
76
|
+
strokeDasharray: `${valueUnits} ${100 - valueUnits}`,
|
|
77
|
+
transform: `rotate(135 ${center} ${center})`
|
|
78
|
+
}
|
|
79
|
+
)
|
|
80
|
+
]
|
|
81
|
+
}
|
|
82
|
+
),
|
|
83
|
+
(showValue || label) && /* @__PURE__ */ jsxs("div", { className: `${baseClass}__center`, children: [
|
|
84
|
+
showValue && /* @__PURE__ */ jsx("span", { className: `${baseClass}__value`, children: displayValue }),
|
|
85
|
+
label && /* @__PURE__ */ jsx("span", { className: `${baseClass}__label`, children: label })
|
|
86
|
+
] })
|
|
87
|
+
]
|
|
88
|
+
}
|
|
89
|
+
);
|
|
90
|
+
}
|
|
91
|
+
);
|
|
92
|
+
Gauge.displayName = "Gauge";
|
|
93
|
+
export {
|
|
94
|
+
Gauge
|
|
95
|
+
};
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/* ============================================
|
|
2
|
+
LEGEND TILE COMPONENT
|
|
3
|
+
The labelled value tile under a chart: an
|
|
4
|
+
inset surface one step below its panel, a
|
|
5
|
+
caption label with an optional series dot,
|
|
6
|
+
and the value beneath. The dot's colour
|
|
7
|
+
arrives per instance via the
|
|
8
|
+
--legend-tile-swatch custom property, set
|
|
9
|
+
inline by the component, so this file stays
|
|
10
|
+
token-only.
|
|
11
|
+
============================================ */
|
|
12
|
+
|
|
13
|
+
/* Base */
|
|
14
|
+
|
|
15
|
+
.ds-legend-tile {
|
|
16
|
+
display: flex;
|
|
17
|
+
flex-direction: column;
|
|
18
|
+
gap: var(--gap-xxs);
|
|
19
|
+
min-width: 0;
|
|
20
|
+
background-color: var(--color-bg-page-primary);
|
|
21
|
+
border-radius: var(--radius-md);
|
|
22
|
+
padding: var(--padding-sm) var(--padding-sm-md);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/* Parts */
|
|
26
|
+
|
|
27
|
+
.ds-legend-tile__label {
|
|
28
|
+
display: flex;
|
|
29
|
+
align-items: center;
|
|
30
|
+
gap: var(--gap-xs);
|
|
31
|
+
min-width: 0;
|
|
32
|
+
font-family: var(--font-caption-family);
|
|
33
|
+
font-size: var(--font-caption-size);
|
|
34
|
+
font-weight: var(--font-caption-weight);
|
|
35
|
+
line-height: var(--font-caption-line-height);
|
|
36
|
+
letter-spacing: var(--font-caption-letter-spacing);
|
|
37
|
+
color: var(--color-text-secondary);
|
|
38
|
+
white-space: nowrap;
|
|
39
|
+
overflow: hidden;
|
|
40
|
+
text-overflow: ellipsis;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/* Fixed 8px series dot — same literal as the chart tooltip's dot in
|
|
44
|
+
Chart.css, which is the reference for this element. */
|
|
45
|
+
.ds-legend-tile__dot {
|
|
46
|
+
width: 8px;
|
|
47
|
+
height: 8px;
|
|
48
|
+
border-radius: var(--radius-full);
|
|
49
|
+
flex-shrink: 0;
|
|
50
|
+
background-color: var(--legend-tile-swatch, var(--color-text-secondary));
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
.ds-legend-tile__value {
|
|
54
|
+
font-family: var(--font-paragraph-sm-em-family);
|
|
55
|
+
font-size: var(--font-paragraph-sm-em-size);
|
|
56
|
+
font-weight: var(--font-title-body-weight);
|
|
57
|
+
line-height: var(--font-paragraph-sm-em-line-height);
|
|
58
|
+
letter-spacing: var(--font-paragraph-sm-em-letter-spacing);
|
|
59
|
+
color: var(--color-text-primary);
|
|
60
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { default as React } from 'react';
|
|
2
|
+
/** Props owned by LegendTile itself — everything else falls through to the root div. */
|
|
3
|
+
type LegendTileOwnProps = {
|
|
4
|
+
/** The series name shown above the value. Truncates with an ellipsis rather than wrapping. */
|
|
5
|
+
label: string;
|
|
6
|
+
/**
|
|
7
|
+
* The reading for this series. Numbers are formatted with `toLocaleString()`;
|
|
8
|
+
* pass a string when the value carries its own formatting or a unit.
|
|
9
|
+
*/
|
|
10
|
+
value: string | number;
|
|
11
|
+
/**
|
|
12
|
+
* Any CSS colour for the series dot — consumers typically pass a chart
|
|
13
|
+
* palette token, e.g. `var(--color-chart-series-1)`. When omitted, no dot
|
|
14
|
+
* renders.
|
|
15
|
+
*/
|
|
16
|
+
swatch?: string;
|
|
17
|
+
/** Additional CSS classes */
|
|
18
|
+
className?: string;
|
|
19
|
+
};
|
|
20
|
+
export interface LegendTileProps extends LegendTileOwnProps, Omit<React.ComponentPropsWithoutRef<'div'>, keyof LegendTileOwnProps> {
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* LegendTile — the labelled value tile that sits under a chart and ties a
|
|
24
|
+
* series to its number. An inset tile one surface step below its panel, with
|
|
25
|
+
* an optional series dot matching the chart's palette. Pure JSX and CSS: no
|
|
26
|
+
* charting library, no hooks, so it renders from a Server Component and a row
|
|
27
|
+
* of them costs nothing under a dense dashboard.
|
|
28
|
+
*/
|
|
29
|
+
export declare const LegendTile: React.ForwardRefExoticComponent<LegendTileProps & React.RefAttributes<HTMLDivElement>>;
|
|
30
|
+
export {};
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { jsxs, jsx } from "react/jsx-runtime";
|
|
2
|
+
import React from "react";
|
|
3
|
+
import "./LegendTile.css";
|
|
4
|
+
const LegendTile = React.forwardRef(
|
|
5
|
+
({ label, value, swatch, className = "", style, ...rest }, ref) => {
|
|
6
|
+
const baseClass = "ds-legend-tile";
|
|
7
|
+
const classes = [baseClass, className].filter(Boolean).join(" ");
|
|
8
|
+
const mergedStyle = swatch ? { ...style, "--legend-tile-swatch": swatch } : style;
|
|
9
|
+
return /* @__PURE__ */ jsxs("div", { ...rest, ref, className: classes, style: mergedStyle, children: [
|
|
10
|
+
/* @__PURE__ */ jsxs("span", { className: `${baseClass}__label`, children: [
|
|
11
|
+
swatch && /* @__PURE__ */ jsx("span", { className: `${baseClass}__dot`, "aria-hidden": "true" }),
|
|
12
|
+
label
|
|
13
|
+
] }),
|
|
14
|
+
/* @__PURE__ */ jsx("span", { className: `${baseClass}__value`, children: typeof value === "number" ? value.toLocaleString() : value })
|
|
15
|
+
] });
|
|
16
|
+
}
|
|
17
|
+
);
|
|
18
|
+
LegendTile.displayName = "LegendTile";
|
|
19
|
+
export {
|
|
20
|
+
LegendTile
|
|
21
|
+
};
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/* ============================================
|
|
2
|
+
PANEL COMPONENT
|
|
3
|
+
The plain dashboard surface
|
|
4
|
+
============================================ */
|
|
5
|
+
|
|
6
|
+
.ds-panel {
|
|
7
|
+
display: flex;
|
|
8
|
+
flex-direction: column;
|
|
9
|
+
gap: var(--gap-md);
|
|
10
|
+
min-width: 0;
|
|
11
|
+
background-color: var(--color-bg-container-primary);
|
|
12
|
+
border-radius: var(--radius-xl);
|
|
13
|
+
padding: var(--padding-lg);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/* ============================================
|
|
17
|
+
PADDING VARIANTS
|
|
18
|
+
============================================ */
|
|
19
|
+
|
|
20
|
+
.ds-panel--compact {
|
|
21
|
+
padding: var(--padding-md);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
.ds-panel--flush {
|
|
25
|
+
padding: 0;
|
|
26
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { default as React } from 'react';
|
|
2
|
+
/** Props owned by Panel itself; everything else falls through to the <div>. */
|
|
3
|
+
type PanelOwnProps = {
|
|
4
|
+
/** Interior padding: 'default' uses --padding-lg, 'compact' uses --padding-md, 'none' removes it */
|
|
5
|
+
padding?: 'default' | 'compact' | 'none';
|
|
6
|
+
/** Additional CSS classes */
|
|
7
|
+
className?: string;
|
|
8
|
+
/** Panel content */
|
|
9
|
+
children?: React.ReactNode;
|
|
10
|
+
};
|
|
11
|
+
export interface PanelProps extends PanelOwnProps, Omit<React.ComponentPropsWithoutRef<'div'>, keyof PanelOwnProps> {
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Panel component: the plain dashboard surface.
|
|
15
|
+
* A rounded container on the primary container background with no border and
|
|
16
|
+
* no shadow: just the surface, ready for whatever a dashboard region holds.
|
|
17
|
+
* Content stacks vertically with a medium gap.
|
|
18
|
+
*
|
|
19
|
+
* Forwards a ref to the `<div>` element and spreads unrecognised props onto it.
|
|
20
|
+
*/
|
|
21
|
+
export declare const Panel: React.ForwardRefExoticComponent<PanelProps & React.RefAttributes<HTMLDivElement>>;
|
|
22
|
+
export {};
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { jsx } from "react/jsx-runtime";
|
|
2
|
+
import React from "react";
|
|
3
|
+
import "./Panel.css";
|
|
4
|
+
const Panel = React.forwardRef(
|
|
5
|
+
({ padding = "default", className = "", children, ...rest }, ref) => {
|
|
6
|
+
const baseClass = "ds-panel";
|
|
7
|
+
const classes = [
|
|
8
|
+
baseClass,
|
|
9
|
+
padding === "compact" ? `${baseClass}--compact` : "",
|
|
10
|
+
padding === "none" ? `${baseClass}--flush` : "",
|
|
11
|
+
className
|
|
12
|
+
].filter(Boolean).join(" ");
|
|
13
|
+
return /* @__PURE__ */ jsx("div", { ...rest, ref, className: classes, children });
|
|
14
|
+
}
|
|
15
|
+
);
|
|
16
|
+
Panel.displayName = "Panel";
|
|
17
|
+
export {
|
|
18
|
+
Panel
|
|
19
|
+
};
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
/* ============================================
|
|
2
|
+
SPLIT PANE COMPONENT
|
|
3
|
+
Two resizable regions with a draggable
|
|
4
|
+
divider. The split rides a custom property
|
|
5
|
+
set from JS; everything visual is tokens.
|
|
6
|
+
============================================ */
|
|
7
|
+
|
|
8
|
+
/* Base */
|
|
9
|
+
|
|
10
|
+
.ds-split-pane {
|
|
11
|
+
display: flex;
|
|
12
|
+
width: 100%;
|
|
13
|
+
height: 100%;
|
|
14
|
+
min-width: 0;
|
|
15
|
+
min-height: 0;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
.ds-split-pane--horizontal {
|
|
19
|
+
flex-direction: row;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
.ds-split-pane--vertical {
|
|
23
|
+
flex-direction: column;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/* While dragging, suppress text selection everywhere in the container so a
|
|
27
|
+
fast drag doesn't paint selections through the panes. */
|
|
28
|
+
.ds-split-pane--dragging {
|
|
29
|
+
user-select: none;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/* Panes clip; scrolling belongs to a consumer-owned container inside the
|
|
33
|
+
pane, which can then be focusable (a bare scrollable region fails the
|
|
34
|
+
axe scrollable-region-focusable rule, and a pane that is always a tab
|
|
35
|
+
stop would be worse). */
|
|
36
|
+
|
|
37
|
+
.ds-split-pane__pane {
|
|
38
|
+
overflow: hidden;
|
|
39
|
+
min-width: 0;
|
|
40
|
+
min-height: 0;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
.ds-split-pane__pane--first {
|
|
44
|
+
flex: 0 0 var(--ds-split-pane-split, 50%);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
.ds-split-pane__pane--second {
|
|
48
|
+
flex: 1 1 0;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/* Separator */
|
|
52
|
+
|
|
53
|
+
.ds-split-pane__separator {
|
|
54
|
+
flex: 0 0 auto;
|
|
55
|
+
display: flex;
|
|
56
|
+
align-items: center;
|
|
57
|
+
justify-content: center;
|
|
58
|
+
background-color: transparent;
|
|
59
|
+
touch-action: none;
|
|
60
|
+
transition: background-color var(--motion-duration-fast) var(--motion-ease-standard);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
.ds-split-pane--horizontal .ds-split-pane__separator {
|
|
64
|
+
width: var(--gap-sm);
|
|
65
|
+
cursor: col-resize;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
.ds-split-pane--vertical .ds-split-pane__separator {
|
|
69
|
+
height: var(--gap-sm);
|
|
70
|
+
cursor: row-resize;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
.ds-split-pane__separator:hover,
|
|
74
|
+
.ds-split-pane--dragging .ds-split-pane__separator {
|
|
75
|
+
background-color: var(--color-bg-container-secondary);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
.ds-split-pane__separator:focus-visible {
|
|
79
|
+
outline: var(--border-md) solid var(--color-action-primary-bg);
|
|
80
|
+
outline-offset: calc(var(--border-md) * -1);
|
|
81
|
+
border-radius: var(--radius-xxs);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/* Grip */
|
|
85
|
+
|
|
86
|
+
.ds-split-pane__grip {
|
|
87
|
+
display: block;
|
|
88
|
+
border-radius: var(--radius-full);
|
|
89
|
+
background-color: var(--color-bg-container-border);
|
|
90
|
+
transition: background-color var(--motion-duration-fast) var(--motion-ease-standard);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
.ds-split-pane--horizontal .ds-split-pane__grip {
|
|
94
|
+
width: var(--gap-xxs);
|
|
95
|
+
height: var(--gap-xl);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
.ds-split-pane--vertical .ds-split-pane__grip {
|
|
99
|
+
width: var(--gap-xl);
|
|
100
|
+
height: var(--gap-xxs);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
.ds-split-pane__separator:hover .ds-split-pane__grip,
|
|
104
|
+
.ds-split-pane--dragging .ds-split-pane__grip {
|
|
105
|
+
background-color: var(--color-icon-secondary);
|
|
106
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { default as React } from 'react';
|
|
2
|
+
/** Props owned by SplitPane itself — everything else falls through to the root div. */
|
|
3
|
+
type SplitPaneOwnProps = {
|
|
4
|
+
/** The two panes, in order. Children beyond the first two are ignored. */
|
|
5
|
+
children: React.ReactNode;
|
|
6
|
+
/** Which way the panes sit: side by side, or stacked. */
|
|
7
|
+
direction?: 'horizontal' | 'vertical';
|
|
8
|
+
/** First pane's share as a percentage (controlled). Pair with `onSplitChange`. */
|
|
9
|
+
split?: number;
|
|
10
|
+
/** First pane's share as a percentage (uncontrolled initial value). */
|
|
11
|
+
defaultSplit?: number;
|
|
12
|
+
/** Smallest share the first pane can be dragged to, as a percentage. */
|
|
13
|
+
minSplit?: number;
|
|
14
|
+
/** Largest share the first pane can be dragged to, as a percentage. */
|
|
15
|
+
maxSplit?: number;
|
|
16
|
+
/** Fires with the new percentage on every drag step or keyboard resize. */
|
|
17
|
+
onSplitChange?: (split: number) => void;
|
|
18
|
+
/** Accessible name for the resize handle. */
|
|
19
|
+
separatorLabel?: string;
|
|
20
|
+
/** Additional CSS classes */
|
|
21
|
+
className?: string;
|
|
22
|
+
};
|
|
23
|
+
export interface SplitPaneProps extends SplitPaneOwnProps, Omit<React.ComponentPropsWithoutRef<'div'>, keyof SplitPaneOwnProps> {
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* SplitPane — two resizable regions with a draggable divider: the sidebar
|
|
27
|
+
* and canvas, the list and detail, the editor and preview. The split is a
|
|
28
|
+
* percentage, so it survives container resizes. The divider is a real
|
|
29
|
+
* `separator`: focusable, arrow keys nudge it (Shift for big steps, Home/End
|
|
30
|
+
* to the limits), and pointer drags use capture so a fast drag can't escape
|
|
31
|
+
* it. Panes clip their content rather than growing the page; a region that
|
|
32
|
+
* should scroll brings its own focusable scroll container, so keyboard
|
|
33
|
+
* users can reach it.
|
|
34
|
+
*/
|
|
35
|
+
export declare const SplitPane: React.ForwardRefExoticComponent<SplitPaneProps & React.RefAttributes<HTMLDivElement>>;
|
|
36
|
+
export {};
|