@robr0/design-system 0.2.0 → 0.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/README.md +8 -6
- package/components/Accordion/Accordion.js +0 -1
- package/components/Avatar/Avatar.js +1 -0
- package/components/Breadcrumb/Breadcrumb.d.ts +7 -1
- package/components/Breadcrumb/Breadcrumb.js +3 -2
- package/components/Button/Button.css +18 -0
- package/components/Button/Button.d.ts +6 -0
- package/components/Button/Button.js +13 -3
- package/components/Card/Card.css +6 -0
- package/components/CircularButton/CircularButton.css +11 -0
- package/components/CircularButton/CircularButton.d.ts +7 -1
- package/components/CircularButton/CircularButton.js +15 -4
- package/components/CodeBlock/CodeBlock.js +10 -1
- package/components/ColorPicker/ColorPicker.css +278 -0
- package/components/ColorPicker/ColorPicker.d.ts +50 -0
- package/components/ColorPicker/ColorPicker.js +338 -0
- package/components/Combobox/Combobox.css +0 -36
- package/components/Combobox/Combobox.js +90 -82
- package/components/CommandPalette/CommandPalette.css +1 -15
- package/components/CommandPalette/CommandPalette.js +6 -5
- package/components/ContextMenu/ContextMenu.css +262 -0
- package/components/ContextMenu/ContextMenu.d.ts +26 -0
- package/components/ContextMenu/ContextMenu.js +350 -0
- package/components/DateInput/DateInput.css +0 -36
- package/components/DateInput/DateInput.js +37 -33
- package/components/DatePicker/DatePicker.js +45 -34
- package/components/Dialog/Dialog.js +5 -4
- package/components/Drawer/Drawer.js +7 -2
- package/components/Dropdown/Dropdown.css +0 -36
- package/components/Dropdown/Dropdown.js +119 -109
- package/components/DropdownMenu/DropdownMenu.js +10 -7
- package/components/Field/Field.css +80 -0
- package/components/Field/Field.d.ts +50 -0
- package/components/Field/Field.js +58 -0
- package/components/Field/FieldContext.d.ts +42 -0
- package/components/Field/FieldContext.js +7 -0
- package/components/FileInput/FileInput.css +0 -36
- package/components/FileInput/FileInput.js +111 -103
- package/components/Input/Input.css +0 -43
- package/components/Input/Input.js +51 -46
- package/components/Kbd/Kbd.css +28 -0
- package/components/Kbd/Kbd.d.ts +19 -0
- package/components/Kbd/Kbd.js +14 -0
- package/components/Popover/Popover.d.ts +0 -6
- package/components/Popover/Popover.js +22 -16
- package/components/ProgressBar/ProgressBar.js +1 -1
- package/components/SelectionCard/SelectionCard.css +6 -2
- package/components/Skeleton/Skeleton.css +2 -0
- package/components/Spinner/Spinner.css +11 -0
- package/components/Spinner/Spinner.d.ts +2 -2
- package/components/Swatch/Swatch.css +52 -0
- package/components/Swatch/Swatch.d.ts +34 -0
- package/components/Swatch/Swatch.js +44 -0
- package/components/Table/Table.css +19 -0
- package/components/Table/Table.js +1 -1
- package/components/Textarea/Textarea.css +0 -43
- package/components/Textarea/Textarea.js +37 -35
- package/components/ToggleSwitch/ToggleSwitch.css +7 -2
- package/components/registry.d.ts +47 -10
- package/components/registry.js +5 -1
- package/components/registry.json +498 -56
- package/components/registry.json.d.ts +498 -56
- package/components/registry.json.js +4 -1
- package/index.d.ts +8 -1
- package/index.js +16 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -18,7 +18,7 @@ The **live site** is the portfolio built on the design system. **Storybook** is
|
|
|
18
18
|
| Part | Description |
|
|
19
19
|
|---|---|
|
|
20
20
|
| **Portfolio website** (`/website`) | Next.js app with case studies, work history, writing, and about pages — all built exclusively with the design system components below. **[Live site →](https://robertritacca.com/)** |
|
|
21
|
-
| **Design system** (`/src`) | <!-- component-count -->
|
|
21
|
+
| **Design system** (`/src`) | <!-- component-count -->61<!-- /component-count --> React components, a three-tier token architecture, dark mode, and a full documentation site. Built to production standards. **[Storybook →](https://design-system-iota-one.vercel.app/?path=/docs/robr0-ds--docs)** |
|
|
22
22
|
|
|
23
23
|
---
|
|
24
24
|
|
|
@@ -27,17 +27,19 @@ The **live site** is the portfolio built on the design system. **Storybook** is
|
|
|
27
27
|
### Components
|
|
28
28
|
|
|
29
29
|
<!-- component-list:start -->
|
|
30
|
-
Accordion · Alert · Alert dialog · App layout · App sidebar · Avatar · Badge · Breadcrumb · Button · Button group · Card · Carousel · Chart · Checkbox · Chip · Circular button · Code block · Combobox · Command palette · Contact card · Contribution graph · Date input · Date picker · Dialog · Divider · Drawer · Dropdown · Dropdown menu · Empty state · Entity card · Figure · File input · Input · Instructions · Link list ·
|
|
30
|
+
Accordion · Alert · Alert dialog · App layout · App sidebar · Avatar · Badge · Breadcrumb · Button · Button group · Card · Carousel · Chart · Checkbox · Chip · Circular button · Code block · Colour picker · Combobox · Command palette · Contact card · Context menu · Contribution graph · Date input · Date picker · Dialog · Divider · Drawer · Dropdown · Dropdown menu · Empty state · Entity card · Field · Figure · File input · Input · Instructions · Kbd · Link list · Navigation · Pagination · Popover · Progress bar · Quote · Radio button · Section title · Segmented control · Selection card · Skeleton · Slider · Spinner · Stat · Swatch · Table · Tabs · Textarea · Timeline · Toast · Toggle group · Toggle switch · Tooltip
|
|
31
31
|
<!-- component-list:end -->
|
|
32
32
|
|
|
33
33
|
### Using the package
|
|
34
34
|
|
|
35
|
-
The design system is published as [`@robr0/design-system`](https://www.npmjs.com/package/@robr0/design-system) (React 19+ is a peer dependency):
|
|
35
|
+
The design system is published as [`@robr0/design-system`](https://www.npmjs.com/package/@robr0/design-system) (React 19+ is a peer dependency) — the **[full setup guide](https://www.robertritacca.com/docs/get-started)** covers install, dark mode, fonts, and re-theming:
|
|
36
36
|
|
|
37
37
|
```bash
|
|
38
38
|
npm install @robr0/design-system
|
|
39
39
|
```
|
|
40
40
|
|
|
41
|
+
The package is ESM-only and resolved via `exports` subpaths: use a bundler that handles CSS and font imports from `node_modules` (Vite, Next.js, webpack), and set TypeScript's `moduleResolution` to `"bundler"` (or `"nodenext"`).
|
|
42
|
+
|
|
41
43
|
Import the token stylesheet once (it carries the primitives, semantic tokens, and both themes), then use components:
|
|
42
44
|
|
|
43
45
|
```tsx
|
|
@@ -51,7 +53,7 @@ Deep imports work too (`@robr0/design-system/components/Button/Button`). Chart c
|
|
|
51
53
|
import { BarChart, LineChart } from '@robr0/design-system/charts';
|
|
52
54
|
```
|
|
53
55
|
|
|
54
|
-
**Theming and customization** happen through CSS variables — no configuration API:
|
|
56
|
+
**Theming and customization** happen through CSS variables — no configuration API. Components are provider-free, with one exception: wrap your tree in `ToastProvider` if (and only if) you use the toast queue via `useToast`.
|
|
55
57
|
|
|
56
58
|
- **Dark mode**: set `data-theme="dark"` on the root element (light is the default).
|
|
57
59
|
- **Font**: the whole type scale chains to one token. Load any font you like and override it:
|
|
@@ -107,10 +109,10 @@ Full spec in [`design.md`](design.md).
|
|
|
107
109
|
|
|
108
110
|
## Quality & CI
|
|
109
111
|
|
|
110
|
-
Every push and pull request runs a GitHub Actions pipeline ([`ci.yml`](.github/workflows/ci.yml)) with four jobs: **lint + library build**, **story tests** (every Storybook story rendered in headless Chromium via Vitest), **Storybook build**, and **website build**. The same checklist runs locally with one command:
|
|
112
|
+
Every push and pull request runs a GitHub Actions pipeline ([`ci.yml`](.github/workflows/ci.yml)) with four jobs: **lint + library build**, **story tests** (every Storybook story rendered in headless Chromium via Vitest), **Storybook build**, and **website lint + build**. The same checklist runs locally with one command:
|
|
111
113
|
|
|
112
114
|
```bash
|
|
113
|
-
npm run verify # lint + library type-check + package build + story tests + Storybook build + website build
|
|
115
|
+
npm run verify # lint + library type-check + package build + story tests + Storybook build + website lint + build
|
|
114
116
|
```
|
|
115
117
|
|
|
116
118
|
CI also guards against documentation drift: generated surfaces (this README's component count and list, the website's skills pages, the published CLAUDE.md/design.md blueprints) are rebuilt from their source registries on every build, and CI fails if the committed copies are stale. The numbers on the site are never hand-written.
|
|
@@ -59,7 +59,6 @@ const Accordion = ({
|
|
|
59
59
|
"div",
|
|
60
60
|
{
|
|
61
61
|
id: `${baseClass}-panel-${item.id}`,
|
|
62
|
-
role: "region",
|
|
63
62
|
"aria-labelledby": `${baseClass}-header-${item.id}`,
|
|
64
63
|
className: `${baseClass}__panel`,
|
|
65
64
|
children: /* @__PURE__ */ jsx("div", { className: `${baseClass}__content`, children: item.content })
|
|
@@ -11,9 +11,15 @@ export interface BreadcrumbProps {
|
|
|
11
11
|
maxItems?: number;
|
|
12
12
|
/** Additional CSS classes */
|
|
13
13
|
className?: string;
|
|
14
|
+
/**
|
|
15
|
+
* Accessible name for the nav landmark. Override when more than one
|
|
16
|
+
* Breadcrumb can appear on a page — identically-named landmarks are
|
|
17
|
+
* indistinguishable to assistive technology.
|
|
18
|
+
*/
|
|
19
|
+
ariaLabel?: string;
|
|
14
20
|
}
|
|
15
21
|
/**
|
|
16
22
|
* Breadcrumb shows the user's location within a page hierarchy.
|
|
17
23
|
* Supports automatic collapsing of middle items when the list exceeds maxItems.
|
|
18
24
|
*/
|
|
19
|
-
export declare const Breadcrumb: ({ items, maxItems, className, }: BreadcrumbProps) => import("react").JSX.Element;
|
|
25
|
+
export declare const Breadcrumb: ({ items, maxItems, className, ariaLabel, }: BreadcrumbProps) => import("react").JSX.Element;
|
|
@@ -4,7 +4,8 @@ import "../../fonts/material-symbols.css";
|
|
|
4
4
|
const Breadcrumb = ({
|
|
5
5
|
items,
|
|
6
6
|
maxItems,
|
|
7
|
-
className = ""
|
|
7
|
+
className = "",
|
|
8
|
+
ariaLabel
|
|
8
9
|
}) => {
|
|
9
10
|
const baseClass = "ds-breadcrumb";
|
|
10
11
|
const classes = [baseClass, className].filter(Boolean).join(" ");
|
|
@@ -20,7 +21,7 @@ const Breadcrumb = ({
|
|
|
20
21
|
} else {
|
|
21
22
|
visibleItems = [...items];
|
|
22
23
|
}
|
|
23
|
-
return /* @__PURE__ */ jsx("nav", { className: classes, "aria-label": "Breadcrumb", children: /* @__PURE__ */ jsx("ol", { className: `${baseClass}__list`, children: visibleItems.map((item, index) => {
|
|
24
|
+
return /* @__PURE__ */ jsx("nav", { className: classes, "aria-label": ariaLabel || "Breadcrumb", children: /* @__PURE__ */ jsx("ol", { className: `${baseClass}__list`, children: visibleItems.map((item, index) => {
|
|
24
25
|
const isLast = index === visibleItems.length - 1;
|
|
25
26
|
if (item === "ellipsis") {
|
|
26
27
|
return /* @__PURE__ */ jsxs("li", { className: `${baseClass}__item`, "aria-hidden": "true", children: [
|
|
@@ -371,6 +371,24 @@
|
|
|
371
371
|
color: var(--color-status-error-bg);
|
|
372
372
|
}
|
|
373
373
|
|
|
374
|
+
/* ============================================
|
|
375
|
+
LOADING
|
|
376
|
+
Keeps the variant's full-colour appearance (no disabled dim);
|
|
377
|
+
the spinner occupies the left icon slot and inherits its colour.
|
|
378
|
+
============================================ */
|
|
379
|
+
|
|
380
|
+
.ds-button--loading {
|
|
381
|
+
cursor: progress;
|
|
382
|
+
pointer-events: none;
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
/* The generic icon-slot svg sizing must not distort the spinner —
|
|
386
|
+
it sizes itself from the Spinner size prop */
|
|
387
|
+
.ds-button .ds-button__icon .ds-spinner__circle {
|
|
388
|
+
width: 100%;
|
|
389
|
+
height: 100%;
|
|
390
|
+
}
|
|
391
|
+
|
|
374
392
|
/* ============================================
|
|
375
393
|
FOCUS
|
|
376
394
|
============================================ */
|
|
@@ -13,6 +13,12 @@ type ButtonOwnProps = {
|
|
|
13
13
|
size?: 'default' | 'compact';
|
|
14
14
|
/** Whether the button is disabled */
|
|
15
15
|
disabled?: boolean;
|
|
16
|
+
/**
|
|
17
|
+
* Shows a spinner in the left icon slot and blocks interaction while an
|
|
18
|
+
* async action runs. Keeps the variant's full-colour appearance (unlike
|
|
19
|
+
* `disabled`) and sets `aria-busy` on the rendered element.
|
|
20
|
+
*/
|
|
21
|
+
loading?: boolean;
|
|
16
22
|
/** Optional href — renders as <a> instead of <button> */
|
|
17
23
|
href?: string;
|
|
18
24
|
/** Optional target attribute for links */
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { jsx, jsxs, Fragment } from "react/jsx-runtime";
|
|
2
2
|
import React from "react";
|
|
3
|
+
import { Spinner } from "../Spinner/Spinner.js";
|
|
3
4
|
import "./Button.css";
|
|
4
5
|
import "../../fonts/material-symbols.css";
|
|
5
6
|
const Button = React.forwardRef(
|
|
@@ -10,6 +11,7 @@ const Button = React.forwardRef(
|
|
|
10
11
|
variant,
|
|
11
12
|
size = "default",
|
|
12
13
|
disabled,
|
|
14
|
+
loading,
|
|
13
15
|
href,
|
|
14
16
|
target,
|
|
15
17
|
rel,
|
|
@@ -25,27 +27,32 @@ const Button = React.forwardRef(
|
|
|
25
27
|
const resolvedVariant = variant ?? priority ?? "primary";
|
|
26
28
|
const isDisabled = disabled ?? state === "disabled";
|
|
27
29
|
const resolvedState = state ?? (isDisabled ? "disabled" : "default");
|
|
30
|
+
const isLoading = Boolean(loading);
|
|
28
31
|
const classes = [
|
|
29
32
|
baseClass,
|
|
30
33
|
`${baseClass}--${resolvedVariant}`,
|
|
31
34
|
`${baseClass}--${resolvedState}`,
|
|
32
35
|
`${baseClass}--${size}`,
|
|
36
|
+
isLoading && `${baseClass}--loading`,
|
|
33
37
|
className
|
|
34
38
|
].filter(Boolean).join(" ");
|
|
35
39
|
const iconClass = "material-symbols-rounded";
|
|
36
|
-
const
|
|
40
|
+
const spinnerSize = size === "compact" ? "sm" : "md";
|
|
41
|
+
const leftIcon = isLoading ? /* @__PURE__ */ jsx(Spinner, { size: spinnerSize, variant: "inherit" }) : iconLeft || icon;
|
|
37
42
|
const renderIcon = (iconProp) => {
|
|
38
43
|
if (typeof iconProp === "string") {
|
|
39
44
|
return /* @__PURE__ */ jsx("span", { className: `${baseClass}__icon ${iconClass}`, "aria-hidden": "true", children: iconProp });
|
|
40
45
|
}
|
|
41
46
|
return /* @__PURE__ */ jsx("span", { className: `${baseClass}__icon`, "aria-hidden": "true", children: iconProp });
|
|
42
47
|
};
|
|
48
|
+
const needsAccessibleName = !text || !label;
|
|
49
|
+
const fallbackAriaLabel = needsAccessibleName && label && !rest["aria-label"] ? label : void 0;
|
|
43
50
|
const content = /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
44
51
|
leftIcon && renderIcon(leftIcon),
|
|
45
52
|
text && /* @__PURE__ */ jsx("span", { className: `${baseClass}__text`, children: label }),
|
|
46
53
|
iconRight && renderIcon(iconRight)
|
|
47
54
|
] });
|
|
48
|
-
if (href && !isDisabled) {
|
|
55
|
+
if (href && !isDisabled && !isLoading) {
|
|
49
56
|
return /* @__PURE__ */ jsx(
|
|
50
57
|
"a",
|
|
51
58
|
{
|
|
@@ -56,6 +63,7 @@ const Button = React.forwardRef(
|
|
|
56
63
|
target,
|
|
57
64
|
rel,
|
|
58
65
|
"aria-current": ariaCurrent ? "page" : void 0,
|
|
66
|
+
"aria-label": fallbackAriaLabel ?? rest["aria-label"],
|
|
59
67
|
children: content
|
|
60
68
|
}
|
|
61
69
|
);
|
|
@@ -67,7 +75,9 @@ const Button = React.forwardRef(
|
|
|
67
75
|
ref,
|
|
68
76
|
type: "button",
|
|
69
77
|
className: classes,
|
|
70
|
-
disabled: isDisabled,
|
|
78
|
+
disabled: isDisabled || isLoading,
|
|
79
|
+
"aria-busy": isLoading || void 0,
|
|
80
|
+
"aria-label": fallbackAriaLabel ?? rest["aria-label"],
|
|
71
81
|
children: content
|
|
72
82
|
}
|
|
73
83
|
);
|
package/components/Card/Card.css
CHANGED
|
@@ -25,6 +25,8 @@
|
|
|
25
25
|
|
|
26
26
|
/* Interaction */
|
|
27
27
|
transition: transform var(--motion-duration-slow) var(--motion-ease-standard), box-shadow var(--motion-duration-slow) var(--motion-ease-standard);
|
|
28
|
+
/* ds-allow(color): transparent resting state of the sanctioned Card
|
|
29
|
+
hover-lift shadow (see design.md — Do's and Don'ts). */
|
|
28
30
|
box-shadow: 0 0 0 rgba(0, 0, 0, 0);
|
|
29
31
|
}
|
|
30
32
|
|
|
@@ -34,6 +36,8 @@
|
|
|
34
36
|
|
|
35
37
|
.ds-card--interactive:hover {
|
|
36
38
|
transform: translateY(-4px);
|
|
39
|
+
/* ds-allow(color): the sanctioned Card hover-lift shadow — deliberately
|
|
40
|
+
heavier than --shadow-floating (see design.md — Do's and Don'ts). */
|
|
37
41
|
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.3);
|
|
38
42
|
}
|
|
39
43
|
|
|
@@ -80,6 +84,8 @@
|
|
|
80
84
|
|
|
81
85
|
.ds-card--case-study:hover {
|
|
82
86
|
transform: translateY(-4px);
|
|
87
|
+
/* ds-allow(color): same sanctioned hover-lift shadow as the interactive
|
|
88
|
+
variant. */
|
|
83
89
|
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.3);
|
|
84
90
|
}
|
|
85
91
|
|
|
@@ -196,6 +196,17 @@
|
|
|
196
196
|
background-color: var(--color-action-passive-bg-active);
|
|
197
197
|
}
|
|
198
198
|
|
|
199
|
+
/* ============================================
|
|
200
|
+
LOADING
|
|
201
|
+
Keeps the variant's full-colour appearance (no disabled dim);
|
|
202
|
+
the spinner replaces the icon and inherits its colour.
|
|
203
|
+
============================================ */
|
|
204
|
+
|
|
205
|
+
.ds-circular-button--loading {
|
|
206
|
+
cursor: progress;
|
|
207
|
+
pointer-events: none;
|
|
208
|
+
}
|
|
209
|
+
|
|
199
210
|
/* ============================================
|
|
200
211
|
FOCUS
|
|
201
212
|
============================================ */
|
|
@@ -7,6 +7,12 @@ export interface CircularButtonProps {
|
|
|
7
7
|
state?: 'default' | 'hover' | 'active' | 'disabled';
|
|
8
8
|
/** Button size */
|
|
9
9
|
size?: 'default' | 'compact';
|
|
10
|
+
/**
|
|
11
|
+
* Shows a spinner in place of the icon and blocks interaction while an
|
|
12
|
+
* async action runs. Keeps the variant's full-colour appearance (unlike
|
|
13
|
+
* the disabled state) and sets `aria-busy` on the rendered element.
|
|
14
|
+
*/
|
|
15
|
+
loading?: boolean;
|
|
10
16
|
/** Accessible label */
|
|
11
17
|
ariaLabel: string;
|
|
12
18
|
/** Optional click handler */
|
|
@@ -27,4 +33,4 @@ export interface CircularButtonProps {
|
|
|
27
33
|
* A round button containing a single icon, available in
|
|
28
34
|
* primary and secondary variants with default and compact sizes.
|
|
29
35
|
*/
|
|
30
|
-
export declare const CircularButton: ({ icon, priority, state, size, ariaLabel, onClick, href, target, rel, "aria-describedby": ariaDescribedby, className, }: CircularButtonProps) => import("react").JSX.Element;
|
|
36
|
+
export declare const CircularButton: ({ icon, priority, state, size, loading, ariaLabel, onClick, href, target, rel, "aria-describedby": ariaDescribedby, className, }: CircularButtonProps) => import("react").JSX.Element;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { jsx } from "react/jsx-runtime";
|
|
2
|
+
import { Spinner } from "../Spinner/Spinner.js";
|
|
2
3
|
import "./CircularButton.css";
|
|
3
4
|
import "../../fonts/material-symbols.css";
|
|
4
5
|
const CircularButton = ({
|
|
@@ -6,6 +7,7 @@ const CircularButton = ({
|
|
|
6
7
|
priority = "primary",
|
|
7
8
|
state = "default",
|
|
8
9
|
size = "default",
|
|
10
|
+
loading,
|
|
9
11
|
ariaLabel,
|
|
10
12
|
onClick,
|
|
11
13
|
href,
|
|
@@ -18,10 +20,18 @@ const CircularButton = ({
|
|
|
18
20
|
const variantClass = `${baseClass}--${priority}`;
|
|
19
21
|
const stateClass = `${baseClass}--${state}`;
|
|
20
22
|
const sizeClass = `${baseClass}--${size}`;
|
|
21
|
-
const
|
|
23
|
+
const isLoading = Boolean(loading);
|
|
24
|
+
const classes = [
|
|
25
|
+
baseClass,
|
|
26
|
+
variantClass,
|
|
27
|
+
stateClass,
|
|
28
|
+
sizeClass,
|
|
29
|
+
isLoading && `${baseClass}--loading`,
|
|
30
|
+
className
|
|
31
|
+
].filter(Boolean).join(" ");
|
|
22
32
|
const isDisabled = state === "disabled";
|
|
23
|
-
const children = /* @__PURE__ */ jsx("span", { className: `${baseClass}__icon material-symbols-rounded`, "aria-hidden": "true", children: icon });
|
|
24
|
-
if (href && !isDisabled) {
|
|
33
|
+
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 });
|
|
34
|
+
if (href && !isDisabled && !isLoading) {
|
|
25
35
|
return /* @__PURE__ */ jsx(
|
|
26
36
|
"a",
|
|
27
37
|
{
|
|
@@ -42,7 +52,8 @@ const CircularButton = ({
|
|
|
42
52
|
type: "button",
|
|
43
53
|
className: classes,
|
|
44
54
|
onClick,
|
|
45
|
-
disabled: isDisabled,
|
|
55
|
+
disabled: isDisabled || isLoading,
|
|
56
|
+
"aria-busy": isLoading || void 0,
|
|
46
57
|
"aria-label": ariaLabel,
|
|
47
58
|
"aria-describedby": ariaDescribedby,
|
|
48
59
|
children
|
|
@@ -77,7 +77,16 @@ const CodeBlock = ({
|
|
|
77
77
|
}
|
|
78
78
|
)
|
|
79
79
|
] }),
|
|
80
|
-
/* @__PURE__ */ jsx("div", { id: panelId, className: `${baseClass}__panel`, "aria-hidden": collapsed || void 0, children: /* @__PURE__ */ jsx("div", { className: `${baseClass}__panel-inner`, children: /* @__PURE__ */ jsx(
|
|
80
|
+
/* @__PURE__ */ jsx("div", { id: panelId, className: `${baseClass}__panel`, "aria-hidden": collapsed || void 0, children: /* @__PURE__ */ jsx("div", { className: `${baseClass}__panel-inner`, children: /* @__PURE__ */ jsx(
|
|
81
|
+
"pre",
|
|
82
|
+
{
|
|
83
|
+
className: `${baseClass}__pre`,
|
|
84
|
+
tabIndex: collapsed ? -1 : 0,
|
|
85
|
+
role: "region",
|
|
86
|
+
"aria-label": filename ? `Code: ${filename}` : language ? `Code: ${language}` : "Code",
|
|
87
|
+
children: /* @__PURE__ */ jsx("code", { className: `${baseClass}__code`, children: code })
|
|
88
|
+
}
|
|
89
|
+
) }) })
|
|
81
90
|
] });
|
|
82
91
|
};
|
|
83
92
|
export {
|
|
@@ -0,0 +1,278 @@
|
|
|
1
|
+
/* ============================================
|
|
2
|
+
COLORPICKER COMPONENT
|
|
3
|
+
Swatch trigger + saturation/hue/alpha panel
|
|
4
|
+
|
|
5
|
+
Colour-mixing constants: the white/black
|
|
6
|
+
gradient overlays and the hue spectrum are
|
|
7
|
+
colour-space physics, not theme decisions —
|
|
8
|
+
they are written as hsl() literals because no
|
|
9
|
+
semantic token can represent them. Everything
|
|
10
|
+
themeable uses tokens as usual.
|
|
11
|
+
|
|
12
|
+
ds-allow-file(color): colour-space physics —
|
|
13
|
+
the gradients and spectrum cannot be theme
|
|
14
|
+
tokens.
|
|
15
|
+
============================================ */
|
|
16
|
+
|
|
17
|
+
/* Base */
|
|
18
|
+
|
|
19
|
+
.ds-colorpicker {
|
|
20
|
+
position: relative;
|
|
21
|
+
/* Field owns no layout — the root class stacks label, trigger and helper */
|
|
22
|
+
display: inline-flex;
|
|
23
|
+
flex-direction: column;
|
|
24
|
+
align-items: flex-start;
|
|
25
|
+
gap: var(--gap-sm);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/* Trigger */
|
|
29
|
+
|
|
30
|
+
.ds-colorpicker__trigger {
|
|
31
|
+
display: inline-flex;
|
|
32
|
+
align-items: center;
|
|
33
|
+
gap: var(--gap-sm);
|
|
34
|
+
padding: var(--padding-xxs);
|
|
35
|
+
border: var(--border-xs) solid var(--color-input-border-primary);
|
|
36
|
+
border-radius: var(--radius-md);
|
|
37
|
+
background-color: transparent;
|
|
38
|
+
cursor: pointer;
|
|
39
|
+
transition: border-color var(--motion-duration-base) var(--motion-ease-standard);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
.ds-colorpicker__trigger:hover:not(:disabled) {
|
|
43
|
+
border-color: var(--color-input-border-hover);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
.ds-colorpicker__trigger:focus-visible {
|
|
47
|
+
outline: none;
|
|
48
|
+
border-color: var(--color-input-border-selected);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
.ds-colorpicker--open .ds-colorpicker__trigger {
|
|
52
|
+
border-color: var(--color-input-border-selected);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
.ds-colorpicker__trigger-swatch {
|
|
56
|
+
width: 24px;
|
|
57
|
+
height: 24px;
|
|
58
|
+
border-radius: var(--radius-sm);
|
|
59
|
+
/* Checkerboard shows through translucent colours */
|
|
60
|
+
background-color: var(--color-bg-page-primary);
|
|
61
|
+
background-image:
|
|
62
|
+
linear-gradient(var(--ds-cp-color), var(--ds-cp-color)),
|
|
63
|
+
repeating-conic-gradient(var(--color-bg-container-tertiary) 0% 25%, transparent 0% 50%);
|
|
64
|
+
background-size: 100% 100%, 8px 8px;
|
|
65
|
+
box-shadow: inset 0 0 0 var(--border-xs) var(--color-bg-container-border);
|
|
66
|
+
flex-shrink: 0;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
.ds-colorpicker__trigger-text {
|
|
70
|
+
font-family: var(--font-paragraph-sm-family);
|
|
71
|
+
font-size: var(--font-paragraph-sm-size);
|
|
72
|
+
line-height: var(--font-paragraph-sm-line-height);
|
|
73
|
+
color: var(--color-text-primary);
|
|
74
|
+
font-variant-numeric: tabular-nums;
|
|
75
|
+
padding-right: var(--padding-xxs);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/* Sizes */
|
|
79
|
+
|
|
80
|
+
.ds-colorpicker--compact .ds-colorpicker__trigger-swatch {
|
|
81
|
+
width: 20px;
|
|
82
|
+
height: 20px;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/* Panel */
|
|
86
|
+
|
|
87
|
+
.ds-colorpicker__panel {
|
|
88
|
+
position: absolute;
|
|
89
|
+
top: 100%;
|
|
90
|
+
left: 0;
|
|
91
|
+
margin-top: var(--gap-xxs);
|
|
92
|
+
width: 240px;
|
|
93
|
+
padding: var(--padding-sm-md);
|
|
94
|
+
display: flex;
|
|
95
|
+
flex-direction: column;
|
|
96
|
+
gap: var(--gap-sm-md);
|
|
97
|
+
background-color: var(--color-bg-page-primary);
|
|
98
|
+
border: var(--border-xs) solid var(--color-input-border-primary);
|
|
99
|
+
border-radius: var(--radius-md);
|
|
100
|
+
box-shadow: var(--shadow-floating);
|
|
101
|
+
z-index: 10;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/* Saturation / brightness area */
|
|
105
|
+
|
|
106
|
+
.ds-colorpicker__saturation {
|
|
107
|
+
position: relative;
|
|
108
|
+
height: 150px;
|
|
109
|
+
border-radius: var(--radius-sm);
|
|
110
|
+
cursor: crosshair;
|
|
111
|
+
touch-action: none;
|
|
112
|
+
background:
|
|
113
|
+
linear-gradient(to top, hsl(0 0% 0%), transparent),
|
|
114
|
+
linear-gradient(to right, hsl(0 0% 100%), transparent),
|
|
115
|
+
hsl(var(--ds-cp-hue) 100% 50%);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
.ds-colorpicker__saturation:focus-visible {
|
|
119
|
+
outline: var(--border-md) solid var(--color-input-border-selected);
|
|
120
|
+
outline-offset: var(--border-md);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
.ds-colorpicker__handle {
|
|
124
|
+
position: absolute;
|
|
125
|
+
left: var(--ds-cp-x);
|
|
126
|
+
top: var(--ds-cp-y);
|
|
127
|
+
transform: translate(-50%, -50%);
|
|
128
|
+
width: 14px;
|
|
129
|
+
height: 14px;
|
|
130
|
+
border-radius: var(--radius-full);
|
|
131
|
+
border: var(--border-md) solid hsl(0 0% 100%);
|
|
132
|
+
box-shadow: var(--shadow-floating);
|
|
133
|
+
pointer-events: none;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/* Sliders + preview */
|
|
137
|
+
|
|
138
|
+
.ds-colorpicker__controls {
|
|
139
|
+
display: flex;
|
|
140
|
+
align-items: center;
|
|
141
|
+
gap: var(--gap-sm-md);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
.ds-colorpicker__sliders {
|
|
145
|
+
flex: 1;
|
|
146
|
+
display: flex;
|
|
147
|
+
flex-direction: column;
|
|
148
|
+
gap: var(--gap-sm);
|
|
149
|
+
min-width: 0;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
.ds-colorpicker__hue,
|
|
153
|
+
.ds-colorpicker__alpha {
|
|
154
|
+
appearance: none;
|
|
155
|
+
-webkit-appearance: none;
|
|
156
|
+
width: 100%;
|
|
157
|
+
height: 12px;
|
|
158
|
+
border-radius: var(--radius-full);
|
|
159
|
+
outline: none;
|
|
160
|
+
cursor: pointer;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
.ds-colorpicker__hue {
|
|
164
|
+
background: linear-gradient(
|
|
165
|
+
to right,
|
|
166
|
+
hsl(0 100% 50%),
|
|
167
|
+
hsl(60 100% 50%),
|
|
168
|
+
hsl(120 100% 50%),
|
|
169
|
+
hsl(180 100% 50%),
|
|
170
|
+
hsl(240 100% 50%),
|
|
171
|
+
hsl(300 100% 50%),
|
|
172
|
+
hsl(360 100% 50%)
|
|
173
|
+
);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
.ds-colorpicker__alpha {
|
|
177
|
+
background-color: var(--color-bg-page-primary);
|
|
178
|
+
background-image:
|
|
179
|
+
linear-gradient(to right, transparent, var(--ds-cp-solid)),
|
|
180
|
+
repeating-conic-gradient(var(--color-bg-container-tertiary) 0% 25%, transparent 0% 50%);
|
|
181
|
+
background-size: 100% 100%, 8px 8px;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
.ds-colorpicker__hue::-webkit-slider-thumb,
|
|
185
|
+
.ds-colorpicker__alpha::-webkit-slider-thumb {
|
|
186
|
+
appearance: none;
|
|
187
|
+
-webkit-appearance: none;
|
|
188
|
+
width: 16px;
|
|
189
|
+
height: 16px;
|
|
190
|
+
border-radius: var(--radius-full);
|
|
191
|
+
background: hsl(0 0% 100%);
|
|
192
|
+
border: var(--border-xs) solid var(--color-bg-container-border);
|
|
193
|
+
box-shadow: var(--shadow-floating);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
.ds-colorpicker__hue::-moz-range-thumb,
|
|
197
|
+
.ds-colorpicker__alpha::-moz-range-thumb {
|
|
198
|
+
width: 16px;
|
|
199
|
+
height: 16px;
|
|
200
|
+
border-radius: var(--radius-full);
|
|
201
|
+
background: hsl(0 0% 100%);
|
|
202
|
+
border: var(--border-xs) solid var(--color-bg-container-border);
|
|
203
|
+
box-shadow: var(--shadow-floating);
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
.ds-colorpicker__hue:focus-visible,
|
|
207
|
+
.ds-colorpicker__alpha:focus-visible {
|
|
208
|
+
outline: var(--border-md) solid var(--color-input-border-selected);
|
|
209
|
+
outline-offset: var(--border-md);
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
.ds-colorpicker__preview {
|
|
213
|
+
width: 32px;
|
|
214
|
+
height: 32px;
|
|
215
|
+
border-radius: var(--radius-sm);
|
|
216
|
+
background-color: var(--color-bg-page-primary);
|
|
217
|
+
background-image:
|
|
218
|
+
linear-gradient(var(--ds-cp-color), var(--ds-cp-color)),
|
|
219
|
+
repeating-conic-gradient(var(--color-bg-container-tertiary) 0% 25%, transparent 0% 50%);
|
|
220
|
+
background-size: 100% 100%, 8px 8px;
|
|
221
|
+
box-shadow: inset 0 0 0 var(--border-xs) var(--color-bg-container-border);
|
|
222
|
+
flex-shrink: 0;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
/* Hex + alpha inputs */
|
|
226
|
+
|
|
227
|
+
.ds-colorpicker__inputs {
|
|
228
|
+
display: flex;
|
|
229
|
+
align-items: center;
|
|
230
|
+
gap: var(--gap-sm);
|
|
231
|
+
padding: var(--padding-xxs) var(--padding-sm);
|
|
232
|
+
border: var(--border-xs) solid var(--color-input-border-primary);
|
|
233
|
+
border-radius: var(--radius-md);
|
|
234
|
+
transition: border-color var(--motion-duration-base) var(--motion-ease-standard);
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
.ds-colorpicker__inputs:focus-within {
|
|
238
|
+
border-color: var(--color-input-border-selected);
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
.ds-colorpicker__hash {
|
|
242
|
+
font-family: var(--font-paragraph-sm-family);
|
|
243
|
+
font-size: var(--font-paragraph-sm-size);
|
|
244
|
+
color: var(--color-text-tertiary);
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
.ds-colorpicker__hex-field {
|
|
248
|
+
flex: 1;
|
|
249
|
+
min-width: 0;
|
|
250
|
+
border: none;
|
|
251
|
+
background: transparent;
|
|
252
|
+
outline: none;
|
|
253
|
+
padding: 0;
|
|
254
|
+
font-family: var(--font-paragraph-sm-family);
|
|
255
|
+
font-size: var(--font-paragraph-sm-size);
|
|
256
|
+
line-height: var(--font-paragraph-sm-line-height);
|
|
257
|
+
color: var(--color-text-primary);
|
|
258
|
+
text-transform: uppercase;
|
|
259
|
+
font-variant-numeric: tabular-nums;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
.ds-colorpicker__alpha-value {
|
|
263
|
+
font-family: var(--font-paragraph-sm-family);
|
|
264
|
+
font-size: var(--font-paragraph-sm-size);
|
|
265
|
+
color: var(--color-text-secondary);
|
|
266
|
+
font-variant-numeric: tabular-nums;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
/* States */
|
|
270
|
+
|
|
271
|
+
.ds-colorpicker--disabled .ds-colorpicker__trigger {
|
|
272
|
+
opacity: 0.4;
|
|
273
|
+
cursor: not-allowed;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
.ds-colorpicker--error .ds-colorpicker__trigger {
|
|
277
|
+
border-color: var(--color-status-error-border);
|
|
278
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { default as React } from 'react';
|
|
2
|
+
/** Props owned by ColorPicker itself — everything else falls through to the trigger <button>. */
|
|
3
|
+
type ColorPickerOwnProps = {
|
|
4
|
+
/** Field label text */
|
|
5
|
+
label?: string;
|
|
6
|
+
/** Current colour as a hex string — 3, 6 or 8 digit, with or without `#` */
|
|
7
|
+
value?: string;
|
|
8
|
+
/** Initial colour for uncontrolled use */
|
|
9
|
+
defaultValue?: string;
|
|
10
|
+
/**
|
|
11
|
+
* Convenience callback receiving the colour as an uppercase hex string
|
|
12
|
+
* (`#RRGGBB`, or `#RRGGBBAA` when `showAlpha` and alpha < 100%). Fires
|
|
13
|
+
* live while dragging.
|
|
14
|
+
*/
|
|
15
|
+
onValueChange?: (value: string) => void;
|
|
16
|
+
/** Show the current hex value as text inside the trigger */
|
|
17
|
+
showText?: boolean;
|
|
18
|
+
/** Add an alpha (opacity) slider and emit 8-digit hex when alpha < 100% */
|
|
19
|
+
showAlpha?: boolean;
|
|
20
|
+
/** Component size */
|
|
21
|
+
size?: 'default' | 'compact';
|
|
22
|
+
/** Whether the picker is disabled */
|
|
23
|
+
disabled?: boolean;
|
|
24
|
+
/** Whether the field is required */
|
|
25
|
+
required?: boolean;
|
|
26
|
+
/** Error state */
|
|
27
|
+
error?: boolean;
|
|
28
|
+
/** Helper or error message */
|
|
29
|
+
helperText?: string;
|
|
30
|
+
/**
|
|
31
|
+
* When set, a hidden `<input type="hidden">` carries the current hex value
|
|
32
|
+
* under this name so the picker participates in native form submission.
|
|
33
|
+
*/
|
|
34
|
+
name?: string;
|
|
35
|
+
/** Additional CSS classes — applied to the wrapper, not the trigger */
|
|
36
|
+
className?: string;
|
|
37
|
+
};
|
|
38
|
+
export interface ColorPickerProps extends ColorPickerOwnProps, Omit<React.ComponentPropsWithoutRef<'button'>, keyof ColorPickerOwnProps | 'type'> {
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Colour picker: a swatch trigger that opens a panel with a
|
|
42
|
+
* saturation/brightness area, hue slider, optional alpha slider and a hex
|
|
43
|
+
* field. Works controlled (`value` + `onValueChange`) or uncontrolled
|
|
44
|
+
* (`defaultValue`).
|
|
45
|
+
*
|
|
46
|
+
* Forwards a ref to the trigger `<button>` and spreads unrecognised props
|
|
47
|
+
* onto it.
|
|
48
|
+
*/
|
|
49
|
+
export declare const ColorPicker: React.ForwardRefExoticComponent<ColorPickerProps & React.RefAttributes<HTMLButtonElement>>;
|
|
50
|
+
export {};
|