@robr0/design-system 0.13.0 → 0.15.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 +15 -6
- package/charts.d.ts +1 -0
- package/charts.js +2 -0
- package/components/AnchorNav/AnchorNav.css +74 -0
- package/components/AnchorNav/AnchorNav.d.ts +7 -0
- package/components/AnchorNav/AnchorNav.js +74 -21
- package/components/Banner/Banner.css +110 -0
- package/components/Banner/Banner.d.ts +36 -0
- package/components/Banner/Banner.js +59 -0
- package/components/Button/Button.css +51 -0
- package/components/Button/Button.d.ts +2 -2
- package/components/Chart/Chart.css +4 -1
- package/components/CircularButton/CircularButton.css +46 -0
- package/components/CircularButton/CircularButton.d.ts +13 -2
- package/components/CircularButton/CircularButton.js +19 -16
- package/components/CommandPalette/CommandPalette.css +26 -1
- package/components/CommandPalette/CommandPalette.js +11 -7
- package/components/Composer/Composer.css +18 -0
- package/components/Composer/Composer.d.ts +4 -2
- package/components/Composer/Composer.js +1 -0
- package/components/ContributionGraph/ContributionGraph.css +28 -1
- package/components/ContributionGraph/ContributionGraph.d.ts +9 -1
- package/components/ContributionGraph/ContributionGraph.js +66 -42
- package/components/EmptyState/EmptyState.css +5 -0
- package/components/Figure/Figure.css +14 -4
- package/components/Figure/Figure.js +11 -20
- package/components/FunnelChart/FunnelChart.css +11 -35
- package/components/FunnelChart/FunnelChart.d.ts +26 -15
- package/components/FunnelChart/FunnelChart.js +82 -34
- package/components/Gauge/Gauge.css +39 -12
- package/components/Gauge/Gauge.d.ts +16 -8
- package/components/Gauge/Gauge.js +78 -62
- package/components/HoverCard/HoverCard.css +97 -0
- package/components/HoverCard/HoverCard.d.ts +29 -0
- package/components/HoverCard/HoverCard.js +83 -0
- package/components/ImageCompare/ImageCompare.css +112 -0
- package/components/ImageCompare/ImageCompare.d.ts +40 -0
- package/components/ImageCompare/ImageCompare.js +134 -0
- package/components/LinkList/LinkList.d.ts +3 -1
- package/components/LinkList/LinkList.js +4 -3
- package/components/Meter/Meter.css +98 -0
- package/components/Meter/Meter.d.ts +36 -0
- package/components/Meter/Meter.js +48 -0
- package/components/NotificationCenter/NotificationCenter.css +11 -3
- package/components/ProgressBar/ProgressBar.css +1 -1
- package/components/Rating/Rating.css +74 -0
- package/components/Rating/Rating.d.ts +41 -0
- package/components/Rating/Rating.js +124 -0
- package/components/SegmentedControl/SegmentedControl.css +23 -0
- package/components/SegmentedControl/SegmentedControl.d.ts +3 -1
- package/components/SegmentedControl/SegmentedControl.js +3 -1
- package/components/Slider/Slider.css +1 -1
- package/components/Slider/Slider.js +1 -1
- package/components/SourceTrail/SourceTrail.css +209 -0
- package/components/SourceTrail/SourceTrail.d.ts +49 -0
- package/components/SourceTrail/SourceTrail.js +108 -0
- package/components/Sparkline/Sparkline.css +26 -2
- package/components/Sparkline/Sparkline.d.ts +5 -3
- package/components/Sparkline/Sparkline.js +32 -2
- package/components/Spinner/Spinner.css +1 -1
- package/components/SplitButton/SplitButton.css +93 -0
- package/components/SplitButton/SplitButton.d.ts +43 -0
- package/components/SplitButton/SplitButton.js +67 -0
- package/components/StreamingText/StreamingText.d.ts +27 -9
- package/components/StreamingText/StreamingText.js +17 -29
- package/components/StreamingText/useStreamReveal.d.ts +70 -0
- package/components/StreamingText/useStreamReveal.js +121 -0
- package/components/ToggleGroup/ToggleGroup.css +17 -0
- package/components/ToggleGroup/ToggleGroup.d.ts +2 -0
- package/components/ToggleGroup/ToggleGroup.js +2 -0
- package/components/ToolCall/ToolCall.css +7 -2
- package/components/Tooltip/Tooltip.css +24 -7
- package/components/Tooltip/Tooltip.d.ts +2 -1
- package/components/UsageCard/UsageCard.css +30 -0
- package/components/UsageCard/UsageCard.d.ts +48 -0
- package/components/UsageCard/UsageCard.js +54 -0
- package/components/registry.d.ts +8 -2
- package/components/registry.json +89 -14
- package/components/registry.json.d.ts +89 -14
- package/components/registry.json.js +1 -1
- package/index.d.ts +8 -1
- package/index.js +19 -2
- package/package.json +5 -1
- package/tokens/motion.d.ts +9 -4
- package/tokens/motion.js +5 -1
- package/tokens/registry.json +4 -0
- package/tokens/registry.json.d.ts +4 -0
- package/tokens/registry.json.js +1 -1
- package/tokens/tokens-dark.css +11 -2
- package/tokens/tokens-light.css +25 -11
- package/tokens/tokens-primitives.css +3 -0
|
@@ -196,6 +196,43 @@
|
|
|
196
196
|
background-color: var(--color-action-passive-bg-active);
|
|
197
197
|
}
|
|
198
198
|
|
|
199
|
+
/* ============================================
|
|
200
|
+
NEUTRAL VARIANT (SOLID GREY)
|
|
201
|
+
Filled like primary but in the neutral greys —
|
|
202
|
+
unlike tertiary, the background is always shown.
|
|
203
|
+
============================================ */
|
|
204
|
+
|
|
205
|
+
.ds-circular-button--neutral {
|
|
206
|
+
background-color: var(--color-action-neutral-bg);
|
|
207
|
+
border: none;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
.ds-circular-button--neutral .ds-circular-button__icon {
|
|
211
|
+
color: var(--color-action-neutral-text);
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
.ds-circular-button--neutral.ds-circular-button--hover {
|
|
215
|
+
background-color: var(--color-action-neutral-bg-hover);
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
.ds-circular-button--neutral.ds-circular-button--active {
|
|
219
|
+
background-color: var(--color-action-neutral-bg-active);
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
.ds-circular-button--neutral.ds-circular-button--disabled {
|
|
223
|
+
opacity: 0.4;
|
|
224
|
+
cursor: not-allowed;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
/* Interactive hover/active */
|
|
228
|
+
.ds-circular-button--neutral:not(.ds-circular-button--disabled):hover {
|
|
229
|
+
background-color: var(--color-action-neutral-bg-hover);
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
.ds-circular-button--neutral:not(.ds-circular-button--disabled):active {
|
|
233
|
+
background-color: var(--color-action-neutral-bg-active);
|
|
234
|
+
}
|
|
235
|
+
|
|
199
236
|
/* ============================================
|
|
200
237
|
LOADING
|
|
201
238
|
Keeps the variant's full-colour appearance (no disabled dim);
|
|
@@ -207,6 +244,15 @@
|
|
|
207
244
|
pointer-events: none;
|
|
208
245
|
}
|
|
209
246
|
|
|
247
|
+
/* The spinner is a box, not a glyph: left inline, it sits on the text
|
|
248
|
+
baseline and the strut's descender pushes it ~1px above the button's
|
|
249
|
+
centre. Flex centres it geometrically. */
|
|
250
|
+
.ds-circular-button--loading .ds-circular-button__icon {
|
|
251
|
+
display: inline-flex;
|
|
252
|
+
align-items: center;
|
|
253
|
+
justify-content: center;
|
|
254
|
+
}
|
|
255
|
+
|
|
210
256
|
/* ============================================
|
|
211
257
|
FOCUS
|
|
212
258
|
============================================ */
|
|
@@ -4,7 +4,7 @@ type CircularButtonOwnProps = {
|
|
|
4
4
|
/** Material Symbol icon name */
|
|
5
5
|
icon: string;
|
|
6
6
|
/** Visual treatment */
|
|
7
|
-
variant?: 'primary' | 'secondary' | 'tertiary';
|
|
7
|
+
variant?: 'primary' | 'secondary' | 'tertiary' | 'neutral';
|
|
8
8
|
/**
|
|
9
9
|
* Legacy alias for `variant`.
|
|
10
10
|
*
|
|
@@ -34,6 +34,17 @@ type CircularButtonOwnProps = {
|
|
|
34
34
|
loading?: boolean;
|
|
35
35
|
/** Accessible label — required, because the button has no visible text */
|
|
36
36
|
ariaLabel: string;
|
|
37
|
+
/**
|
|
38
|
+
* The hover/focus tooltip. An icon-only control names itself: by default
|
|
39
|
+
* the button wears a Tooltip carrying `ariaLabel`. Pass a string to show
|
|
40
|
+
* different wording, or `false` to opt out — for a host that labels the
|
|
41
|
+
* control another way, or one that owns the button's box directly
|
|
42
|
+
* (SplitButton's trigger stretches to its sibling segment, which the
|
|
43
|
+
* tooltip wrapper would block).
|
|
44
|
+
*/
|
|
45
|
+
tooltip?: string | false;
|
|
46
|
+
/** Which side the tooltip opens on. */
|
|
47
|
+
tooltipPosition?: 'top' | 'bottom' | 'left' | 'right';
|
|
37
48
|
/** Optional href — renders as <a> instead of <button> */
|
|
38
49
|
href?: string;
|
|
39
50
|
/** Optional target attribute for links */
|
|
@@ -48,7 +59,7 @@ export interface CircularButtonProps extends CircularButtonOwnProps, Omit<React.
|
|
|
48
59
|
/**
|
|
49
60
|
* Circular icon button component.
|
|
50
61
|
* A round button containing a single icon, available in
|
|
51
|
-
* primary, secondary and
|
|
62
|
+
* primary, secondary, tertiary and neutral variants with default and compact sizes.
|
|
52
63
|
*
|
|
53
64
|
* Renders a `<button>`, or an `<a>` when `href` is supplied. Forwards a ref to
|
|
54
65
|
* whichever element it renders, and spreads unrecognised props onto it.
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { jsx } from "react/jsx-runtime";
|
|
2
2
|
import React from "react";
|
|
3
3
|
import { Spinner } from "../Spinner/Spinner.js";
|
|
4
|
+
import { Tooltip } from "../Tooltip/Tooltip.js";
|
|
4
5
|
import "./CircularButton.css";
|
|
5
6
|
import "../../fonts/material-symbols.css";
|
|
6
7
|
const CircularButton = React.forwardRef(
|
|
@@ -13,6 +14,8 @@ const CircularButton = React.forwardRef(
|
|
|
13
14
|
size = "default",
|
|
14
15
|
loading,
|
|
15
16
|
ariaLabel,
|
|
17
|
+
tooltip,
|
|
18
|
+
tooltipPosition = "top",
|
|
16
19
|
href,
|
|
17
20
|
target,
|
|
18
21
|
rel,
|
|
@@ -33,22 +36,19 @@ const CircularButton = React.forwardRef(
|
|
|
33
36
|
className
|
|
34
37
|
].filter(Boolean).join(" ");
|
|
35
38
|
const children = isLoading ? /* @__PURE__ */ jsx("span", { className: `${baseClass}__icon`, "aria-hidden": "true", children: /* @__PURE__ */ jsx(Spinner, { size: size === "compact" ? "sm" : "md", variant: "inherit" }) }) : /* @__PURE__ */ jsx("span", { className: `${baseClass}__icon material-symbols-rounded`, "aria-hidden": "true", children: icon });
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
);
|
|
50
|
-
}
|
|
51
|
-
return /* @__PURE__ */ jsx(
|
|
39
|
+
const control = href && !isDisabled && !isLoading ? /* @__PURE__ */ jsx(
|
|
40
|
+
"a",
|
|
41
|
+
{
|
|
42
|
+
...rest,
|
|
43
|
+
ref,
|
|
44
|
+
className: classes,
|
|
45
|
+
href,
|
|
46
|
+
target,
|
|
47
|
+
rel,
|
|
48
|
+
"aria-label": ariaLabel,
|
|
49
|
+
children
|
|
50
|
+
}
|
|
51
|
+
) : /* @__PURE__ */ jsx(
|
|
52
52
|
"button",
|
|
53
53
|
{
|
|
54
54
|
...rest,
|
|
@@ -61,6 +61,9 @@ const CircularButton = React.forwardRef(
|
|
|
61
61
|
children
|
|
62
62
|
}
|
|
63
63
|
);
|
|
64
|
+
const tooltipContent = tooltip === false ? null : tooltip ?? ariaLabel;
|
|
65
|
+
if (!tooltipContent) return control;
|
|
66
|
+
return /* @__PURE__ */ jsx(Tooltip, { content: tooltipContent, position: tooltipPosition, children: control });
|
|
64
67
|
}
|
|
65
68
|
);
|
|
66
69
|
CircularButton.displayName = "CircularButton";
|
|
@@ -14,13 +14,20 @@
|
|
|
14
14
|
opacity: 0;
|
|
15
15
|
visibility: hidden; /* also removes the input from the tab order */
|
|
16
16
|
pointer-events: none;
|
|
17
|
-
|
|
17
|
+
/* Visibility waits out the fade only on close (duration token as delay);
|
|
18
|
+
on open it must flip instantly — the open effect focuses the input a
|
|
19
|
+
frame later, and focus() on a still-hidden element is a no-op, which
|
|
20
|
+
left the palette opening without its caret. */
|
|
21
|
+
transition: opacity var(--motion-duration-fast) var(--motion-ease-standard),
|
|
22
|
+
visibility 0s linear var(--motion-duration-fast);
|
|
18
23
|
}
|
|
19
24
|
|
|
20
25
|
.ds-command-palette--open {
|
|
21
26
|
opacity: 1;
|
|
22
27
|
visibility: visible;
|
|
23
28
|
pointer-events: auto;
|
|
29
|
+
transition: opacity var(--motion-duration-fast) var(--motion-ease-standard),
|
|
30
|
+
visibility 0s;
|
|
24
31
|
}
|
|
25
32
|
|
|
26
33
|
/* ============================================
|
|
@@ -70,6 +77,13 @@
|
|
|
70
77
|
padding: var(--padding-sm-md) var(--padding-md); /* 12px 16px */
|
|
71
78
|
border-bottom: var(--border-xs) solid var(--color-divider);
|
|
72
79
|
flex-shrink: 0;
|
|
80
|
+
transition: border-color var(--motion-duration-fast) var(--motion-ease-standard);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/* The focused field marks the whole row with the active input border, the
|
|
84
|
+
same signal a focused Input gives its own edge. */
|
|
85
|
+
.ds-command-palette__search:focus-within {
|
|
86
|
+
border-bottom-color: var(--color-input-border-selected);
|
|
73
87
|
}
|
|
74
88
|
|
|
75
89
|
.ds-command-palette__search-icon {
|
|
@@ -135,6 +149,17 @@
|
|
|
135
149
|
min-height: 0;
|
|
136
150
|
overflow-y: auto;
|
|
137
151
|
padding: var(--padding-xxs);
|
|
152
|
+
/* Quiet scrollbar, ChatThread's recipe: thin and token-coloured instead
|
|
153
|
+
of the 15px native chrome rail a classic-scrollbar platform would draw
|
|
154
|
+
inside the panel. The stable gutter matters here because filtering
|
|
155
|
+
toggles overflow — without it every keystroke that crosses the
|
|
156
|
+
threshold reflows the rows sideways. */
|
|
157
|
+
scrollbar-width: thin;
|
|
158
|
+
scrollbar-gutter: stable;
|
|
159
|
+
scrollbar-color: var(--color-divider) transparent;
|
|
160
|
+
/* The scroll lock lets wheel and touch through only inside this list, so
|
|
161
|
+
hitting its ends must not chain the leftover delta to the page. */
|
|
162
|
+
overscroll-behavior: contain;
|
|
138
163
|
}
|
|
139
164
|
|
|
140
165
|
.ds-command-palette__status {
|
|
@@ -80,15 +80,19 @@ const CommandPalette = ({
|
|
|
80
80
|
}
|
|
81
81
|
}, [open]);
|
|
82
82
|
useEffect(() => {
|
|
83
|
-
if (open)
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
83
|
+
if (!open) return;
|
|
84
|
+
const block = (event) => {
|
|
85
|
+
const target = event.target instanceof Element ? event.target : null;
|
|
86
|
+
if (target?.closest(`.${baseClass}__list`)) return;
|
|
87
|
+
event.preventDefault();
|
|
88
|
+
};
|
|
89
|
+
document.addEventListener("wheel", block, { passive: false });
|
|
90
|
+
document.addEventListener("touchmove", block, { passive: false });
|
|
88
91
|
return () => {
|
|
89
|
-
document.
|
|
92
|
+
document.removeEventListener("wheel", block);
|
|
93
|
+
document.removeEventListener("touchmove", block);
|
|
90
94
|
};
|
|
91
|
-
}, [open]);
|
|
95
|
+
}, [open, baseClass]);
|
|
92
96
|
useEffect(() => {
|
|
93
97
|
if (!open) return;
|
|
94
98
|
const handleTrapKeyDown = (e) => {
|
|
@@ -140,6 +140,24 @@
|
|
|
140
140
|
}
|
|
141
141
|
}
|
|
142
142
|
|
|
143
|
+
/* Working: while a response streams, the ring stays lit and keeps turning
|
|
144
|
+
whether or not the field holds focus — the visible half of the `streaming`
|
|
145
|
+
contract for glowing composers. The pairing is defined in design.md's
|
|
146
|
+
Composer section; streaming without aiGlow changes nothing. */
|
|
147
|
+
.ds-composer--ai-glow.ds-composer--streaming {
|
|
148
|
+
border-color: transparent;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
.ds-composer--ai-glow.ds-composer--streaming::before {
|
|
152
|
+
opacity: 1;
|
|
153
|
+
animation-play-state: running;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
.ds-composer--ai-glow.ds-composer--streaming::after {
|
|
157
|
+
opacity: 0.3;
|
|
158
|
+
animation-play-state: running;
|
|
159
|
+
}
|
|
160
|
+
|
|
143
161
|
/* Reduced motion: the ring and glow stay, the turning stops. */
|
|
144
162
|
@media (prefers-reduced-motion: reduce) {
|
|
145
163
|
.ds-composer--ai-glow::before,
|
|
@@ -21,7 +21,8 @@ type ComposerOwnProps = {
|
|
|
21
21
|
onSubmit?: (value: string) => void;
|
|
22
22
|
/**
|
|
23
23
|
* A response is streaming: the send button becomes a stop button, submit
|
|
24
|
-
* is blocked, and Enter is inert.
|
|
24
|
+
* is blocked, and Enter is inert. On a glowing composer (`aiGlow`) the
|
|
25
|
+
* gradient ring also stays lit and keeps turning while this is true.
|
|
25
26
|
*/
|
|
26
27
|
streaming?: boolean;
|
|
27
28
|
/** Fires when the stop button is pressed while `streaming`. */
|
|
@@ -32,7 +33,8 @@ type ComposerOwnProps = {
|
|
|
32
33
|
* While focused, the shell wears AiButton's slowly rotating gradient ring
|
|
33
34
|
* and glow in place of the plain selected border — the system's "a model
|
|
34
35
|
* answers here" signal, for composers whose messages are answered by one.
|
|
35
|
-
*
|
|
36
|
+
* While `streaming`, the ring stays lit and turning whether or not the
|
|
37
|
+
* field holds focus. Off by default.
|
|
36
38
|
*/
|
|
37
39
|
aiGlow?: boolean;
|
|
38
40
|
/**
|
|
@@ -4,14 +4,20 @@
|
|
|
4
4
|
day, coloured by the contribution colour ramp
|
|
5
5
|
============================================ */
|
|
6
6
|
|
|
7
|
+
/* Card chrome, header, and padding come from the shared chart styles
|
|
8
|
+
(Chart.css); this file owns the grid, legend, and cell ramp. */
|
|
9
|
+
|
|
7
10
|
.ds-contribution-graph {
|
|
8
11
|
--ds-contribution-cell: 12px;
|
|
9
12
|
--ds-contribution-gap: 3px;
|
|
10
13
|
|
|
14
|
+
min-width: 0;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
.ds-contribution-graph__content {
|
|
11
18
|
display: flex;
|
|
12
19
|
flex-direction: column;
|
|
13
20
|
gap: var(--gap-sm);
|
|
14
|
-
width: 100%;
|
|
15
21
|
min-width: 0;
|
|
16
22
|
}
|
|
17
23
|
|
|
@@ -73,6 +79,27 @@
|
|
|
73
79
|
width: 100%;
|
|
74
80
|
height: auto;
|
|
75
81
|
aspect-ratio: 1 / 1;
|
|
82
|
+
/* The year sweeps in left to right on mount: each column's delay steps by
|
|
83
|
+
a fraction of the entrance duration (--ds-contribution-week, set inline
|
|
84
|
+
per column). Token-composed, so reduced motion collapses the sweep. */
|
|
85
|
+
animation: ds-contribution-fade var(--motion-duration-slow) var(--motion-ease-standard)
|
|
86
|
+
backwards;
|
|
87
|
+
animation-delay: calc(
|
|
88
|
+
var(--ds-contribution-week, 0) * var(--motion-duration-slower) / 40
|
|
89
|
+
);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
@keyframes ds-contribution-fade {
|
|
93
|
+
from {
|
|
94
|
+
opacity: 0;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/* Hover lifts the day under the pointer, so the title tooltip has a visible
|
|
99
|
+
anchor. */
|
|
100
|
+
.ds-contribution-graph__week .ds-contribution-graph__cell:not(.ds-contribution-graph__cell--empty):hover {
|
|
101
|
+
outline: var(--border-xs) solid var(--color-text-tertiary);
|
|
102
|
+
outline-offset: calc(-1 * var(--border-xs));
|
|
76
103
|
}
|
|
77
104
|
|
|
78
105
|
.ds-contribution-graph__cell--empty {
|
|
@@ -9,6 +9,12 @@ export interface ContributionDay {
|
|
|
9
9
|
export interface ContributionGraphProps {
|
|
10
10
|
/** One entry per day, ordered oldest to newest */
|
|
11
11
|
days: ContributionDay[];
|
|
12
|
+
/** Chart title, in the shared chart header. */
|
|
13
|
+
title?: string;
|
|
14
|
+
/** Description text below the title. */
|
|
15
|
+
subtitle?: string;
|
|
16
|
+
/** Strip the card chrome (border, padding, fill) when the chart sits inside another panel that supplies the surface */
|
|
17
|
+
bare?: boolean;
|
|
12
18
|
/** Summary line shown under the grid, e.g. "496 contributions in the last year" */
|
|
13
19
|
caption?: string;
|
|
14
20
|
/** Show month labels above the grid */
|
|
@@ -22,5 +28,7 @@ export interface ContributionGraphProps {
|
|
|
22
28
|
* ContributionGraph renders a GitHub-style contribution heatmap:
|
|
23
29
|
* one cell per day, arranged in weekday rows and week columns,
|
|
24
30
|
* coloured by activity level via the --color-chart-contribution-* ramp.
|
|
31
|
+
* It wears the chart family's card chrome (title, subtitle, padding) and
|
|
32
|
+
* takes `bare` to drop it inside a panel that supplies the surface.
|
|
25
33
|
*/
|
|
26
|
-
export declare const ContributionGraph: ({ days, caption, showMonthLabels, showLegend, className, }: ContributionGraphProps) => import("react").JSX.Element;
|
|
34
|
+
export declare const ContributionGraph: ({ days, title, subtitle, bare, caption, showMonthLabels, showLegend, className, }: ContributionGraphProps) => import("react").JSX.Element;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { jsxs, jsx } from "react/jsx-runtime";
|
|
2
|
+
import "../Chart/Chart.css";
|
|
2
3
|
import "./ContributionGraph.css";
|
|
3
4
|
const MONTH_LABELS = [
|
|
4
5
|
"Jan",
|
|
@@ -62,6 +63,9 @@ const buildMonthLabels = (weeks) => {
|
|
|
62
63
|
};
|
|
63
64
|
const ContributionGraph = ({
|
|
64
65
|
days,
|
|
66
|
+
title,
|
|
67
|
+
subtitle,
|
|
68
|
+
bare = false,
|
|
65
69
|
caption,
|
|
66
70
|
showMonthLabels = true,
|
|
67
71
|
showLegend = true,
|
|
@@ -69,53 +73,73 @@ const ContributionGraph = ({
|
|
|
69
73
|
}) => {
|
|
70
74
|
const weeks = buildWeeks(days);
|
|
71
75
|
const monthLabels = buildMonthLabels(weeks);
|
|
72
|
-
const
|
|
76
|
+
const chartClass = "ds-chart";
|
|
77
|
+
const classes = [
|
|
78
|
+
chartClass,
|
|
79
|
+
bare && `${chartClass}--bare`,
|
|
80
|
+
"ds-contribution-graph",
|
|
81
|
+
className
|
|
82
|
+
].filter(Boolean).join(" ");
|
|
73
83
|
const columnTemplate = `repeat(${weeks.length}, minmax(var(--ds-contribution-cell), 1fr))`;
|
|
74
84
|
return /* @__PURE__ */ jsxs("div", { className: classes, children: [
|
|
75
|
-
/* @__PURE__ */
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
"
|
|
100
|
-
|
|
101
|
-
|
|
85
|
+
(title || subtitle) && /* @__PURE__ */ jsx("div", { className: `${chartClass}__header`, children: /* @__PURE__ */ jsxs("div", { className: `${chartClass}__header-text`, children: [
|
|
86
|
+
title && /* @__PURE__ */ jsx("h3", { className: `${chartClass}__title`, children: title }),
|
|
87
|
+
subtitle && /* @__PURE__ */ jsx("p", { className: `${chartClass}__subtitle`, children: subtitle })
|
|
88
|
+
] }) }),
|
|
89
|
+
/* @__PURE__ */ jsxs("div", { className: `${chartClass}__body ds-contribution-graph__content`, children: [
|
|
90
|
+
/* @__PURE__ */ jsxs("div", { className: "ds-contribution-graph__scroll", children: [
|
|
91
|
+
showMonthLabels && /* @__PURE__ */ jsx(
|
|
92
|
+
"div",
|
|
93
|
+
{
|
|
94
|
+
className: "ds-contribution-graph__months",
|
|
95
|
+
style: { gridTemplateColumns: columnTemplate },
|
|
96
|
+
"aria-hidden": "true",
|
|
97
|
+
children: monthLabels.map(({ weekIndex, label }) => /* @__PURE__ */ jsx(
|
|
98
|
+
"span",
|
|
99
|
+
{
|
|
100
|
+
className: "ds-contribution-graph__month-label",
|
|
101
|
+
style: { gridColumnStart: weekIndex + 1 },
|
|
102
|
+
children: label
|
|
103
|
+
},
|
|
104
|
+
`${label}-${weekIndex}`
|
|
105
|
+
))
|
|
106
|
+
}
|
|
107
|
+
),
|
|
108
|
+
/* @__PURE__ */ jsx(
|
|
109
|
+
"div",
|
|
110
|
+
{
|
|
111
|
+
className: "ds-contribution-graph__grid",
|
|
112
|
+
style: { gridTemplateColumns: columnTemplate },
|
|
113
|
+
role: "img",
|
|
114
|
+
"aria-label": caption ?? "Contribution activity",
|
|
115
|
+
children: weeks.map((week, weekIndex) => /* @__PURE__ */ jsx(
|
|
102
116
|
"div",
|
|
103
117
|
{
|
|
104
|
-
className:
|
|
105
|
-
|
|
118
|
+
className: "ds-contribution-graph__week",
|
|
119
|
+
style: { "--ds-contribution-week": weekIndex },
|
|
120
|
+
children: week.map(
|
|
121
|
+
(day, dayIndex) => day ? /* @__PURE__ */ jsx(
|
|
122
|
+
"div",
|
|
123
|
+
{
|
|
124
|
+
className: `ds-contribution-graph__cell ds-contribution-graph__cell--level-${day.level}`,
|
|
125
|
+
title: formatTooltip(day)
|
|
126
|
+
},
|
|
127
|
+
day.date
|
|
128
|
+
) : /* @__PURE__ */ jsx("div", { className: "ds-contribution-graph__cell ds-contribution-graph__cell--empty" }, `pad-${dayIndex}`)
|
|
129
|
+
)
|
|
106
130
|
},
|
|
107
|
-
|
|
108
|
-
)
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
)
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
131
|
+
weekIndex
|
|
132
|
+
))
|
|
133
|
+
}
|
|
134
|
+
)
|
|
135
|
+
] }),
|
|
136
|
+
(caption || showLegend) && /* @__PURE__ */ jsxs("div", { className: "ds-contribution-graph__footer", children: [
|
|
137
|
+
caption && /* @__PURE__ */ jsx("span", { className: "ds-contribution-graph__caption", children: caption }),
|
|
138
|
+
showLegend && /* @__PURE__ */ jsxs("div", { className: "ds-contribution-graph__legend", "aria-hidden": "true", children: [
|
|
139
|
+
/* @__PURE__ */ jsx("span", { className: "ds-contribution-graph__legend-label", children: "Less" }),
|
|
140
|
+
[0, 1, 2, 3, 4].map((level) => /* @__PURE__ */ jsx("div", { className: `ds-contribution-graph__cell ds-contribution-graph__cell--level-${level}` }, level)),
|
|
141
|
+
/* @__PURE__ */ jsx("span", { className: "ds-contribution-graph__legend-label", children: "More" })
|
|
142
|
+
] })
|
|
119
143
|
] })
|
|
120
144
|
] })
|
|
121
145
|
] });
|
|
@@ -13,6 +13,11 @@
|
|
|
13
13
|
gap: var(--gap-md); /* 16px */
|
|
14
14
|
padding: var(--padding-xl); /* 32px */
|
|
15
15
|
width: 100%;
|
|
16
|
+
/* width: 100% plus own padding — border-box locally (as ContactCard,
|
|
17
|
+
Checkbox, Composer, SelectionCard and Textarea do), so a consumer
|
|
18
|
+
without a global reset doesn't get a horizontal scrollbar wherever
|
|
19
|
+
this is embedded. */
|
|
20
|
+
box-sizing: border-box;
|
|
16
21
|
}
|
|
17
22
|
|
|
18
23
|
/* ============================================
|
|
@@ -47,15 +47,25 @@
|
|
|
47
47
|
ZOOMABLE (has onClick)
|
|
48
48
|
============================================ */
|
|
49
49
|
|
|
50
|
-
|
|
50
|
+
/* The zoom control is a real <button> wrapping the media (ARIA forbids
|
|
51
|
+
role="button" on <figure>), stripped back to a bare block. */
|
|
52
|
+
.ds-figure__zoom {
|
|
53
|
+
display: block;
|
|
54
|
+
width: 100%;
|
|
55
|
+
padding: 0;
|
|
56
|
+
border: 0;
|
|
57
|
+
background: none;
|
|
58
|
+
font: inherit;
|
|
51
59
|
cursor: zoom-in;
|
|
52
60
|
}
|
|
53
61
|
|
|
54
|
-
.ds-
|
|
62
|
+
.ds-figure__zoom:hover .ds-figure__media img {
|
|
55
63
|
opacity: 0.88;
|
|
56
64
|
}
|
|
57
65
|
|
|
58
|
-
|
|
66
|
+
/* Inset ring: the button sits inside the figure's overflow clip, so an
|
|
67
|
+
outset outline would be cropped away. */
|
|
68
|
+
.ds-figure__zoom:focus-visible {
|
|
59
69
|
outline: 2px solid var(--color-action-primary-bg);
|
|
60
|
-
outline-offset: 2px;
|
|
70
|
+
outline-offset: -2px;
|
|
61
71
|
}
|
|
@@ -9,26 +9,17 @@ const Figure = ({
|
|
|
9
9
|
}) => {
|
|
10
10
|
const baseClass = "ds-figure";
|
|
11
11
|
const classes = [baseClass, onClick && `${baseClass}--zoomable`, className].filter(Boolean).join(" ");
|
|
12
|
-
const
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
onKeyDown,
|
|
24
|
-
role: onClick ? "button" : void 0,
|
|
25
|
-
tabIndex: onClick ? 0 : void 0,
|
|
26
|
-
children: [
|
|
27
|
-
/* @__PURE__ */ jsx("div", { className: `${baseClass}__media`, children }),
|
|
28
|
-
caption && /* @__PURE__ */ jsx("figcaption", { className: `${baseClass}__caption`, children: caption })
|
|
29
|
-
]
|
|
30
|
-
}
|
|
31
|
-
);
|
|
12
|
+
const media = /* @__PURE__ */ jsx("div", { className: `${baseClass}__media`, children });
|
|
13
|
+
return /* @__PURE__ */ jsxs("figure", { className: classes, children: [
|
|
14
|
+
onClick ? (
|
|
15
|
+
// A real button rather than role="button" on the figure: ARIA
|
|
16
|
+
// forbids the button role on <figure>, and the element brings
|
|
17
|
+
// Enter/Space activation itself. Its accessible name is the
|
|
18
|
+
// child image's alt text.
|
|
19
|
+
/* @__PURE__ */ jsx("button", { type: "button", className: `${baseClass}__zoom`, onClick, children: media })
|
|
20
|
+
) : media,
|
|
21
|
+
caption && /* @__PURE__ */ jsx("figcaption", { className: `${baseClass}__caption`, children: caption })
|
|
22
|
+
] });
|
|
32
23
|
};
|
|
33
24
|
export {
|
|
34
25
|
Figure
|
|
@@ -1,41 +1,17 @@
|
|
|
1
1
|
/* ============================================
|
|
2
2
|
FUNNEL CHART COMPONENT
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
3
|
+
Centred trapezoid funnel built on Recharts'
|
|
4
|
+
native Funnel. Card chrome, header, padding
|
|
5
|
+
and the tooltip surface all come from the
|
|
6
|
+
shared chart styles (Chart.css); colour
|
|
7
|
+
arrives per stage as a chart-series token
|
|
8
|
+
resolved live in SVG paint. This file owns
|
|
9
|
+
only what the shared styles cannot reach.
|
|
10
10
|
============================================ */
|
|
11
11
|
|
|
12
|
-
/*
|
|
12
|
+
/* The stage-name labels draw as SVG text, which inherits no typography from
|
|
13
|
+
the shared chart classes — pin them to the family's primary face. */
|
|
13
14
|
|
|
14
|
-
.ds-funnel-chart {
|
|
15
|
-
|
|
16
|
-
align-items: center;
|
|
17
|
-
gap: var(--gap-xxs);
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
/* Parts */
|
|
21
|
-
|
|
22
|
-
.ds-funnel-chart__stage {
|
|
23
|
-
flex: 1;
|
|
24
|
-
min-width: 0;
|
|
25
|
-
height: calc(var(--funnel-stage-size) * 1%);
|
|
26
|
-
border-radius: var(--radius-sm);
|
|
27
|
-
background-color: var(--funnel-stage-color);
|
|
28
|
-
display: grid;
|
|
29
|
-
place-items: center;
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
.ds-funnel-chart__pct {
|
|
33
|
-
background-color: var(--color-bg-container-primary);
|
|
34
|
-
color: var(--color-text-primary);
|
|
35
|
-
border-radius: var(--radius-full);
|
|
36
|
-
padding: var(--padding-xxxs) var(--padding-xs);
|
|
37
|
-
font-family: var(--font-caption-family);
|
|
38
|
-
font-size: var(--font-caption-size);
|
|
39
|
-
font-weight: var(--font-paragraph-sm-em-weight);
|
|
40
|
-
line-height: var(--font-caption-line-height);
|
|
15
|
+
.ds-funnel-chart .recharts-label-list text {
|
|
16
|
+
font-family: var(--font-family-primary);
|
|
41
17
|
}
|