@sixthshift/design-system 0.8.0 → 0.9.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 +5 -4
- package/dist/components/Badge/Badge.d.ts +17 -35
- package/dist/components/Badge/Badge.d.ts.map +1 -1
- package/dist/components/Badge/Badge.js +19 -22
- package/dist/components/Badge/Badge.js.map +1 -1
- package/dist/components/Badge/index.d.ts +1 -1
- package/dist/components/Badge/index.d.ts.map +1 -1
- package/dist/components/Badge/index.js +1 -1
- package/dist/components/Badge/index.js.map +1 -1
- package/dist/components/Button/Button.d.ts +23 -41
- package/dist/components/Button/Button.d.ts.map +1 -1
- package/dist/components/Button/Button.js +28 -35
- package/dist/components/Button/Button.js.map +1 -1
- package/dist/components/Button/index.d.ts +1 -1
- package/dist/components/Button/index.d.ts.map +1 -1
- package/dist/components/Button/index.js +1 -1
- package/dist/components/Button/index.js.map +1 -1
- package/dist/components/Card/Card.d.ts +2 -2
- package/dist/components/Card/Card.js +1 -1
- package/dist/components/Card/Card.js.map +1 -1
- package/dist/components/CheckboxGroup/CheckboxGroup.d.ts +20 -16
- package/dist/components/CheckboxGroup/CheckboxGroup.d.ts.map +1 -1
- package/dist/components/CheckboxGroup/CheckboxGroup.js +12 -10
- package/dist/components/CheckboxGroup/CheckboxGroup.js.map +1 -1
- package/dist/components/Code/Workspace/Toolbar.d.ts +2 -1
- package/dist/components/Code/Workspace/Toolbar.d.ts.map +1 -1
- package/dist/components/Code/Workspace/Toolbar.js.map +1 -1
- package/dist/components/FormField/FormField.d.ts +3 -1
- package/dist/components/FormField/FormField.d.ts.map +1 -1
- package/dist/components/FormField/FormField.js.map +1 -1
- package/dist/components/Input/Input.d.ts +3 -3
- package/dist/components/Input/Input.js +3 -3
- package/dist/components/Markdown/Markdown.js +1 -1
- package/dist/components/Markdown/Markdown.js.map +1 -1
- package/dist/components/NumberStepper/NumberStepper.d.ts +1 -1
- package/dist/components/ProgressBar/ProgressBar.d.ts +11 -0
- package/dist/components/ProgressBar/ProgressBar.d.ts.map +1 -1
- package/dist/components/ProgressBar/ProgressBar.js +6 -2
- package/dist/components/ProgressBar/ProgressBar.js.map +1 -1
- package/dist/components/ProgressBar/index.d.ts +1 -1
- package/dist/components/ProgressBar/index.d.ts.map +1 -1
- package/dist/components/ProgressBar/index.js.map +1 -1
- package/dist/components/RadioButtonGroup/RadioButtonGroup.d.ts +19 -16
- package/dist/components/RadioButtonGroup/RadioButtonGroup.d.ts.map +1 -1
- package/dist/components/RadioButtonGroup/RadioButtonGroup.js +13 -12
- package/dist/components/RadioButtonGroup/RadioButtonGroup.js.map +1 -1
- package/dist/components/SearchInput/SearchInput.d.ts +3 -2
- package/dist/components/SearchInput/SearchInput.d.ts.map +1 -1
- package/dist/components/SearchInput/SearchInput.js +13 -8
- package/dist/components/SearchInput/SearchInput.js.map +1 -1
- package/dist/components/Sparkline/Sparkline.d.ts +8 -4
- package/dist/components/Sparkline/Sparkline.d.ts.map +1 -1
- package/dist/components/Sparkline/Sparkline.js +1 -1
- package/dist/components/Sparkline/Sparkline.js.map +1 -1
- package/dist/components/Sparkline/index.d.ts +1 -1
- package/dist/components/Sparkline/index.d.ts.map +1 -1
- package/dist/components/Sparkline/index.js.map +1 -1
- package/dist/components/TagChip/TagChip.d.ts +0 -13
- package/dist/components/TagChip/TagChip.d.ts.map +1 -1
- package/dist/components/TagChip/TagChip.js +10 -4
- package/dist/components/TagChip/TagChip.js.map +1 -1
- package/dist/components/TextInline/TextInline.d.ts +2 -2
- package/dist/components/Toast/Toast.js +1 -1
- package/dist/components/Toast/Toast.js.map +1 -1
- package/dist/components/Toggle/Toggle.d.ts +29 -18
- package/dist/components/Toggle/Toggle.d.ts.map +1 -1
- package/dist/components/Toggle/Toggle.js +48 -11
- package/dist/components/Toggle/Toggle.js.map +1 -1
- package/dist/components/Toggle/index.d.ts +1 -1
- package/dist/components/Toggle/index.d.ts.map +1 -1
- package/dist/components/Toggle/index.js +1 -1
- package/dist/components/Toggle/index.js.map +1 -1
- package/dist/components/ToggleGroup/ToggleGroup.d.ts +4 -4
- package/dist/components/ToggleGroup/ToggleGroup.js +4 -4
- package/dist/components/ToggleGroup/ToggleGroupItem.d.ts +1 -1
- package/dist/components/ToggleGroup/ToggleGroupItem.d.ts.map +1 -1
- package/dist/components/ToggleGroup/ToggleGroupItem.js +37 -7
- package/dist/components/ToggleGroup/ToggleGroupItem.js.map +1 -1
- package/dist/components/ToggleGroup/ToggleGroupMultiple.d.ts +3 -3
- package/dist/components/ToggleGroup/ToggleGroupSingle.d.ts +3 -3
- package/dist/components/ToggleGroup/index.d.ts +1 -1
- package/dist/components/ToggleGroup/index.d.ts.map +1 -1
- package/dist/components/ToggleGroup/toggleGroup.types.d.ts +18 -16
- package/dist/components/ToggleGroup/toggleGroup.types.d.ts.map +1 -1
- package/dist/typography/Code.js +1 -1
- package/dist/typography/Code.js.map +1 -1
- package/package.json +2 -1
- package/src/components/Badge/Badge.tsx +38 -45
- package/src/components/Badge/badge.recipe.css +20 -101
- package/src/components/Badge/index.ts +0 -1
- package/src/components/Button/Button.tsx +57 -51
- package/src/components/Button/button.recipe.css +39 -122
- package/src/components/Button/index.ts +0 -1
- package/src/components/Card/Card.tsx +1 -1
- package/src/components/CheckboxGroup/CheckboxGroup.tsx +17 -15
- package/src/components/Code/Workspace/Toolbar.tsx +2 -2
- package/src/components/FormField/FormField.tsx +3 -2
- package/src/components/Input/Input.tsx +3 -3
- package/src/components/Markdown/Markdown.tsx +1 -1
- package/src/components/Message/message.recipe.css +13 -28
- package/src/components/ProgressBar/ProgressBar.tsx +16 -4
- package/src/components/ProgressBar/index.ts +1 -1
- package/src/components/ProgressBar/progress-bar.recipe.css +29 -0
- package/src/components/RadioButtonGroup/RadioButtonGroup.tsx +18 -17
- package/src/components/SearchInput/SearchInput.tsx +34 -20
- package/src/components/SearchInput/search-input.recipe.css +14 -6
- package/src/components/Sparkline/Sparkline.tsx +9 -4
- package/src/components/Sparkline/index.ts +1 -1
- package/src/components/TagChip/TagChip.tsx +17 -8
- package/src/components/TagChip/tag-chip.recipe.css +12 -12
- package/src/components/Toast/Toast.tsx +1 -1
- package/src/components/Toggle/Toggle.tsx +67 -21
- package/src/components/Toggle/index.ts +9 -1
- package/src/components/Toggle/toggle.recipe.css +45 -73
- package/src/components/ToggleGroup/ToggleGroup.tsx +4 -4
- package/src/components/ToggleGroup/ToggleGroupItem.tsx +46 -11
- package/src/components/ToggleGroup/index.ts +9 -1
- package/src/components/ToggleGroup/toggle-group.recipe.css +62 -0
- package/src/components/ToggleGroup/toggleGroup.types.ts +18 -16
- package/src/theme/linen/theme.css +21 -0
- package/src/theming/intents.css +112 -0
- package/src/theming/tailwind.css +17 -1
- package/src/typography/Code.tsx +1 -1
|
@@ -1,11 +1,17 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Button recipe — the component-token layer for Button.
|
|
3
3
|
*
|
|
4
|
-
* This is the mapping from `(variant,
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
4
|
+
* This is the mapping from `(variant, state)` to a colour. It used to live in
|
|
5
|
+
* `compoundVariants` inside Button.tsx, where it was compiled into class-name
|
|
6
|
+
* literals and no consumer could reach it. Here it is data, so a consumer can
|
|
7
|
+
* re-point a cell, scope a change to a subtree, or add a variant the library
|
|
8
|
+
* never shipped.
|
|
9
|
+
*
|
|
10
|
+
* Intent does not appear here at all. Each cell below paints with the intent
|
|
11
|
+
* slots (`--intent-solid-bg`, `--intent-fg`, …) that src/theming/intents.css
|
|
12
|
+
* declares per `data-intent`, so every variant works in every intent — including
|
|
13
|
+
* one a consumer adds — and this file has one cell per variant, not one per
|
|
14
|
+
* `(variant, intent)` pair.
|
|
9
15
|
*
|
|
10
16
|
* The grammar is `--button-{context}[-{state}]` — the same `context` and
|
|
11
17
|
* `state` words the semantic layer uses (src/theming/vocabulary.ts). Note what is
|
|
@@ -37,131 +43,42 @@
|
|
|
37
43
|
--button-ring: var(--focus-ring);
|
|
38
44
|
}
|
|
39
45
|
|
|
40
|
-
/* solid — a filled surface, with text read on top of it. The
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
--button-bg
|
|
46
|
-
--button-bg-
|
|
47
|
-
--button-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
/* `neutral` means *no colour family* — the grey ramp. It used to resolve to
|
|
51
|
-
the brand fill, which made `intent="neutral"` mean one thing on Button and
|
|
52
|
-
another on Badge and Message, where it was already grey. Button
|
|
53
|
-
was the outlier; this is the cell that ends that. A brand-coloured button
|
|
54
|
-
is now `intent="brand"`, which is also Button's default, so a bare
|
|
55
|
-
`<Button>` is unchanged. */
|
|
56
|
-
.btn[data-variant="solid"][data-intent="neutral"] {
|
|
57
|
-
--button-bg: var(--bg-strong);
|
|
58
|
-
--button-bg-hovered: var(--bg-strong-hovered);
|
|
59
|
-
--button-bg-pressed: var(--bg-strong-pressed);
|
|
60
|
-
--button-fg: var(--fg-on-strong);
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
.btn[data-variant="solid"][data-intent="danger"] {
|
|
64
|
-
--button-bg: var(--bg-danger);
|
|
65
|
-
--button-bg-hovered: var(--bg-danger-hovered);
|
|
66
|
-
--button-bg-pressed: var(--bg-danger-pressed);
|
|
67
|
-
--button-fg: var(--fg-on-danger);
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
.btn[data-variant="solid"][data-intent="success"] {
|
|
71
|
-
--button-bg: var(--bg-success);
|
|
72
|
-
--button-bg-hovered: var(--bg-success-hovered);
|
|
73
|
-
--button-bg-pressed: var(--bg-success-pressed);
|
|
74
|
-
--button-fg: var(--fg-on-success);
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
.btn[data-variant="solid"][data-intent="warning"] {
|
|
78
|
-
--button-bg: var(--bg-warning);
|
|
79
|
-
--button-bg-hovered: var(--bg-warning-hovered);
|
|
80
|
-
--button-bg-pressed: var(--bg-warning-pressed);
|
|
81
|
-
--button-fg: var(--fg-on-warning);
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
/* outline carries a resting surface; ghost does not. Beyond that their
|
|
85
|
-
colour recipes are identical, so the text colour and the hover/pressed wash
|
|
86
|
-
are shared per intent and only the two lines below differ. */
|
|
87
|
-
.btn[data-variant="outline"] {
|
|
88
|
-
--button-bg: var(--bg-normal);
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
/* `neutral` here needed no change: outline and ghost were already grey — it
|
|
92
|
-
was only the solid and link cells that read as brand. */
|
|
93
|
-
.btn[data-variant="outline"][data-intent="neutral"],
|
|
94
|
-
.btn[data-variant="ghost"][data-intent="neutral"] {
|
|
95
|
-
--button-bg-hovered: var(--bg-subtle-hovered);
|
|
96
|
-
--button-bg-pressed: var(--bg-subtle-pressed);
|
|
97
|
-
--button-fg: var(--fg-normal);
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
.btn[data-variant="outline"][data-intent="brand"],
|
|
101
|
-
.btn[data-variant="ghost"][data-intent="brand"] {
|
|
102
|
-
--button-bg-hovered: var(--bg-brand-subtle-hovered);
|
|
103
|
-
--button-bg-pressed: var(--bg-brand-subtle-pressed);
|
|
104
|
-
--button-fg: var(--fg-brand);
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
.btn[data-variant="outline"][data-intent="danger"],
|
|
108
|
-
.btn[data-variant="ghost"][data-intent="danger"] {
|
|
109
|
-
--button-bg-hovered: var(--bg-danger-subtle-hovered);
|
|
110
|
-
--button-bg-pressed: var(--bg-danger-subtle-pressed);
|
|
111
|
-
--button-fg: var(--fg-danger);
|
|
46
|
+
/* solid — a filled surface, with text read on top of it. The slot pair
|
|
47
|
+
resolves to a `bg-*` / `fg-on-*` partnership, which is the pairing
|
|
48
|
+
check-contrast.ts verifies, so this cell inherits that guarantee. For
|
|
49
|
+
`neutral`, the default, that is the grey `bg-strong` fill. */
|
|
50
|
+
.btn[data-variant="solid"] {
|
|
51
|
+
--button-bg: var(--intent-solid-bg);
|
|
52
|
+
--button-bg-hovered: var(--intent-solid-bg-hovered);
|
|
53
|
+
--button-bg-pressed: var(--intent-solid-bg-pressed);
|
|
54
|
+
--button-fg: var(--intent-solid-fg);
|
|
112
55
|
}
|
|
113
56
|
|
|
114
|
-
.
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
--button-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
.btn[data-variant="outline"][data-intent="warning"],
|
|
122
|
-
.btn[data-variant="ghost"][data-intent="warning"] {
|
|
123
|
-
--button-bg-hovered: var(--bg-warning-subtle-hovered);
|
|
124
|
-
--button-bg-pressed: var(--bg-warning-subtle-pressed);
|
|
125
|
-
--button-fg: var(--fg-warning);
|
|
57
|
+
/* outline carries a resting surface; ghost does not. Beyond that they share
|
|
58
|
+
the family's text colour and its tint wash on hover and press. */
|
|
59
|
+
.btn[data-variant="outline"],
|
|
60
|
+
.btn[data-variant="ghost"] {
|
|
61
|
+
--button-bg-hovered: var(--intent-tint-bg-hovered);
|
|
62
|
+
--button-bg-pressed: var(--intent-tint-bg-pressed);
|
|
63
|
+
--button-fg: var(--intent-fg);
|
|
126
64
|
}
|
|
127
65
|
|
|
128
66
|
/* The border belongs to outline alone. Ghost has no border width, so folding
|
|
129
|
-
|
|
67
|
+
this into the block above would be invisible today — and a silent
|
|
130
68
|
semantic change the moment anyone gave ghost a border. A consumer who wants
|
|
131
69
|
that can still say so in one line; it should be their decision, not a
|
|
132
|
-
side-effect of how this file was factored.
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
--button-border: var(--border
|
|
136
|
-
}
|
|
137
|
-
.btn[data-variant="outline"][data-intent="danger"] {
|
|
138
|
-
--button-border: var(--border-danger);
|
|
139
|
-
}
|
|
140
|
-
.btn[data-variant="outline"][data-intent="success"] {
|
|
141
|
-
--button-border: var(--border-success);
|
|
142
|
-
}
|
|
143
|
-
.btn[data-variant="outline"][data-intent="warning"] {
|
|
144
|
-
--button-border: var(--border-warning);
|
|
70
|
+
side-effect of how this file was factored. */
|
|
71
|
+
.btn[data-variant="outline"] {
|
|
72
|
+
--button-bg: var(--bg-normal);
|
|
73
|
+
--button-border: var(--intent-border);
|
|
145
74
|
}
|
|
146
75
|
|
|
147
|
-
/*
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
.btn[data-
|
|
153
|
-
--button-fg: var(--fg
|
|
154
|
-
}
|
|
155
|
-
.btn[data-variant="link"][data-intent="neutral"] {
|
|
156
|
-
--button-fg: var(--fg-normal);
|
|
157
|
-
}
|
|
158
|
-
.btn[data-variant="link"][data-intent="danger"] {
|
|
159
|
-
--button-fg: var(--fg-danger);
|
|
160
|
-
}
|
|
161
|
-
.btn[data-variant="link"][data-intent="success"] {
|
|
162
|
-
--button-fg: var(--fg-success);
|
|
163
|
-
}
|
|
164
|
-
.btn[data-variant="link"][data-intent="warning"] {
|
|
165
|
-
--button-fg: var(--fg-warning);
|
|
76
|
+
/* inline — not a variant: inline text with no box (Button drops
|
|
77
|
+
`data-variant` for it, so no cell above can match). Only the text carries
|
|
78
|
+
colour, so the floor's transparent background and its no-shift-on-hover
|
|
79
|
+
chain both stand. It is brand-coloured only with `intent="brand"`;
|
|
80
|
+
`neutral`, the default, is plain text colour. */
|
|
81
|
+
.btn[data-inline="true"] {
|
|
82
|
+
--button-fg: var(--intent-fg);
|
|
166
83
|
}
|
|
167
84
|
}
|
|
@@ -49,7 +49,7 @@ export const cardVariants = cva(
|
|
|
49
49
|
sm: "rounded-lg p-3",
|
|
50
50
|
md: "rounded-xl p-4",
|
|
51
51
|
lg: "rounded-xl p-6",
|
|
52
|
-
xl: "rounded-
|
|
52
|
+
xl: "rounded-xl p-8",
|
|
53
53
|
},
|
|
54
54
|
interactive: {
|
|
55
55
|
true: "hover:border-(color:--card-border-hovered) focus-visible:outline-(color:--card-ring) cursor-pointer transition-colors focus-visible:outline-2 focus-visible:outline-offset-2",
|
|
@@ -26,10 +26,11 @@ export type CheckboxGroupProps = Omit<React.HTMLAttributes<HTMLDivElement>, "onC
|
|
|
26
26
|
onValueChange?: (value: string[]) => void;
|
|
27
27
|
/** Layout orientation */
|
|
28
28
|
orientation?: "vertical" | "horizontal";
|
|
29
|
-
/**
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
29
|
+
/**
|
|
30
|
+
* How the options render: `control` (the default) is one `Checkbox` per option;
|
|
31
|
+
* `segmented` and `separate` are a row or column of buttons, joined or apart.
|
|
32
|
+
*/
|
|
33
|
+
appearance?: "control" | "segmented" | "separate";
|
|
33
34
|
/** Input name for form submission */
|
|
34
35
|
name?: string;
|
|
35
36
|
/** Disable all options */
|
|
@@ -43,17 +44,19 @@ export type CheckboxGroupProps = Omit<React.HTMLAttributes<HTMLDivElement>, "onC
|
|
|
43
44
|
* selection, via the `value`/`defaultValue`/`onValueChange` triad
|
|
44
45
|
* (`useControllableState`).
|
|
45
46
|
*
|
|
46
|
-
* `
|
|
47
|
+
* `appearance="control"` (the default) renders one `Checkbox` per option, each
|
|
47
48
|
* with its own visible label, laid out in a column or row per
|
|
48
|
-
* `orientation`. `
|
|
49
|
-
* re-skinned `Checkbox` — a row/column of plain
|
|
50
|
-
* `role="checkbox"` and no separate label; contiguous buttons
|
|
51
|
-
* share borders (`
|
|
52
|
-
*
|
|
49
|
+
* `orientation`. `appearance="segmented"` or `"separate"` is a separate
|
|
50
|
+
* rendering path — not a re-skinned `Checkbox` — a row/column of plain
|
|
51
|
+
* buttons with `role="checkbox"` and no separate label; contiguous buttons
|
|
52
|
+
* touch and share borders (`segmented`) or sit apart with their own rounded
|
|
53
|
+
* corners (`separate`). One prop rather than a
|
|
54
|
+
* `variant` + `appearance` pair, so "appearance without buttons" — which
|
|
55
|
+
* used to mean nothing — cannot be written.
|
|
53
56
|
*
|
|
54
|
-
* Form submission only happens when `name` is set.
|
|
57
|
+
* Form submission only happens when `name` is set. With `control`
|
|
55
58
|
* that means each underlying `Checkbox` gets `name`, so one hidden native
|
|
56
|
-
* checkbox input is rendered per option;
|
|
59
|
+
* checkbox input is rendered per option; with the button appearances the group
|
|
57
60
|
* itself renders one hidden `<input type="checkbox">` per *currently
|
|
58
61
|
* selected* value.
|
|
59
62
|
*
|
|
@@ -68,8 +71,7 @@ const CheckboxGroup = React.forwardRef<HTMLDivElement, CheckboxGroupProps>(
|
|
|
68
71
|
options,
|
|
69
72
|
onValueChange,
|
|
70
73
|
orientation = "vertical",
|
|
71
|
-
|
|
72
|
-
appearance = "segmented",
|
|
74
|
+
appearance = "control",
|
|
73
75
|
name,
|
|
74
76
|
disabled,
|
|
75
77
|
onBlur,
|
|
@@ -111,7 +113,7 @@ const CheckboxGroup = React.forwardRef<HTMLDivElement, CheckboxGroupProps>(
|
|
|
111
113
|
}
|
|
112
114
|
};
|
|
113
115
|
|
|
114
|
-
if (
|
|
116
|
+
if (appearance === "segmented" || appearance === "separate") {
|
|
115
117
|
const isVertical = orientation === "vertical";
|
|
116
118
|
const isSegmented = appearance === "segmented";
|
|
117
119
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
3
|
import type { HTMLAttributes, ReactNode } from "react";
|
|
4
|
-
import { Button } from "../../Button";
|
|
4
|
+
import { Button, type ButtonVariant } from "../../Button";
|
|
5
5
|
|
|
6
6
|
export type Action = {
|
|
7
7
|
id: string;
|
|
@@ -9,7 +9,7 @@ export type Action = {
|
|
|
9
9
|
icon?: ReactNode;
|
|
10
10
|
onClick: () => void;
|
|
11
11
|
disabled?: boolean;
|
|
12
|
-
variant?:
|
|
12
|
+
variant?: ButtonVariant;
|
|
13
13
|
};
|
|
14
14
|
|
|
15
15
|
export type ToolbarProps = HTMLAttributes<HTMLDivElement> & {
|
|
@@ -1,14 +1,15 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
3
|
import { Label } from "@sixthshift/design-system/label";
|
|
4
|
-
import { Message } from "@sixthshift/design-system/message";
|
|
4
|
+
import { Message, type MessageIntentName } from "@sixthshift/design-system/message";
|
|
5
5
|
import { Muted } from "@sixthshift/design-system/muted";
|
|
6
6
|
import { cn } from "@sixthshift/design-system/utils";
|
|
7
7
|
import * as React from "react";
|
|
8
8
|
|
|
9
9
|
export type FormFieldFeedback = {
|
|
10
10
|
message: string;
|
|
11
|
-
|
|
11
|
+
/** Message's intents, less `neutral` — feedback always reports an outcome. */
|
|
12
|
+
intent: Exclude<MessageIntentName, "neutral">;
|
|
12
13
|
};
|
|
13
14
|
|
|
14
15
|
export type FormFieldProps = React.HTMLAttributes<HTMLDivElement> & {
|
|
@@ -16,15 +16,15 @@ const iconSlotStyles = `absolute top-1/2 -translate-y-1/2 text-(--input-icon-fg)
|
|
|
16
16
|
*
|
|
17
17
|
* Renders a plain `<input>` unless `iconLeft` or `iconRight` is supplied, in
|
|
18
18
|
* which case it wraps the input in a relatively positioned container so an
|
|
19
|
-
* icon — or an icon-styled button
|
|
20
|
-
*
|
|
19
|
+
* icon — or an icon-styled button — can sit inside the field's padding
|
|
20
|
+
* without affecting layout.
|
|
21
21
|
*
|
|
22
22
|
* Controlled or uncontrolled exactly like a native input (`value`/`onChange`
|
|
23
23
|
* or `defaultValue`), and `type` is forwarded as-is, so `type="password"`,
|
|
24
24
|
* `type="number"`, etc. all work.
|
|
25
25
|
*
|
|
26
26
|
* Meant to be wrapped by `FormField` for label, description and validation
|
|
27
|
-
* wiring,
|
|
27
|
+
* wiring. For search, use `SearchInput`, which draws its own field.
|
|
28
28
|
*/
|
|
29
29
|
const Input = React.forwardRef<HTMLInputElement, InputProps>(({ className, type, iconLeft, iconRight, ...props }, ref) => {
|
|
30
30
|
if (iconLeft || iconRight) {
|
|
@@ -47,7 +47,7 @@ export const Markdown = React.forwardRef<HTMLDivElement, MarkdownProps>(({ child
|
|
|
47
47
|
ul: ({ children }) => <ul className="mt-2 flex list-disc flex-col gap-1 pl-5">{children}</ul>,
|
|
48
48
|
ol: ({ children }) => <ol className="mt-2 flex list-decimal flex-col gap-1 pl-5">{children}</ol>,
|
|
49
49
|
li: ({ children }) => <li className="text-fg-default text-sm">{children}</li>,
|
|
50
|
-
code: ({ children }) => <code className="rounded bg-bg-subtle px-1.5 py-0.5 font-mono text-fg-emphasis text-xs">{children}</code>,
|
|
50
|
+
code: ({ children }) => <code className="rounded-sm bg-bg-subtle px-1.5 py-0.5 font-mono text-fg-emphasis text-xs">{children}</code>,
|
|
51
51
|
pre: ({ children }) => <pre className="mt-2 overflow-x-auto rounded-md bg-bg-subtle p-3 font-mono text-xs">{children}</pre>,
|
|
52
52
|
}}
|
|
53
53
|
>
|
|
@@ -1,12 +1,11 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Message recipe — the component-token layer for Message.
|
|
3
3
|
*
|
|
4
|
-
* Message has one colour-bearing
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
* here concerns it.
|
|
4
|
+
* Message has one colour-bearing axis, `intent`, and one treatment: the
|
|
5
|
+
* family's tint, bordered. So the whole recipe is a single cell reading the
|
|
6
|
+
* intent slots from src/theming/intents.css — an intent a consumer adds there
|
|
7
|
+
* reaches Message with no change here. `size` is pure geometry (padding, gap,
|
|
8
|
+
* font-size) and stays in Message.tsx's `cva` call; nothing here concerns it.
|
|
10
9
|
*
|
|
11
10
|
* The grammar is `--message-{context}`, context in {bg, fg, border}. Intent
|
|
12
11
|
* never appears in a token name — see button.css's header for why: it would
|
|
@@ -18,30 +17,16 @@
|
|
|
18
17
|
*/
|
|
19
18
|
|
|
20
19
|
@layer components {
|
|
21
|
-
/* The floor. Applies when no cell below matches — an intent a consumer has
|
|
22
|
-
added but not yet styled still renders as a plain, legible message rather
|
|
23
|
-
than something broken. */
|
|
24
20
|
.message {
|
|
25
|
-
--message-bg: var(--bg
|
|
26
|
-
--message-fg: var(--fg
|
|
27
|
-
--message-border: var(--border
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
.message[data-intent="success"] {
|
|
31
|
-
--message-bg: var(--bg-success-subtle);
|
|
32
|
-
--message-fg: var(--fg-on-success-subtle);
|
|
33
|
-
--message-border: var(--border-success);
|
|
21
|
+
--message-bg: var(--intent-tint-bg);
|
|
22
|
+
--message-fg: var(--intent-tint-fg);
|
|
23
|
+
--message-border: var(--intent-border);
|
|
34
24
|
}
|
|
35
25
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
.message[data-intent="danger"] {
|
|
43
|
-
--message-bg: var(--bg-danger-subtle);
|
|
44
|
-
--message-fg: var(--fg-on-danger-subtle);
|
|
45
|
-
--message-border: var(--border-danger);
|
|
26
|
+
/* A neutral message sits on the resting surface, not the grey tint: it is a
|
|
27
|
+
plain bordered note, and a grey wash reads as disabled. The one place a
|
|
28
|
+
recipe overrides a slot for a single intent. */
|
|
29
|
+
.message[data-intent="neutral"] {
|
|
30
|
+
--message-bg: var(--bg-normal);
|
|
46
31
|
}
|
|
47
32
|
}
|
|
@@ -2,10 +2,18 @@ import { Caption } from "@sixthshift/design-system/caption";
|
|
|
2
2
|
import { cn } from "@sixthshift/design-system/utils";
|
|
3
3
|
import * as React from "react";
|
|
4
4
|
|
|
5
|
+
export type ProgressBarIntentName = "neutral" | "brand" | "danger" | "success" | "warning";
|
|
6
|
+
/** Widened, like every intent prop: a consumer adds an intent in CSS, not in a release. */
|
|
7
|
+
export type ProgressBarIntent = ProgressBarIntentName | (string & {});
|
|
8
|
+
|
|
5
9
|
export type ProgressBarProps = {
|
|
6
10
|
completed: number;
|
|
7
11
|
total: number;
|
|
8
12
|
showFraction?: boolean;
|
|
13
|
+
/** The fill's colour family. Defaults to `brand`. */
|
|
14
|
+
intent?: ProgressBarIntent | undefined;
|
|
15
|
+
/** Name of a brand the theme defines, rendered as `data-brand`. Omitted, the bar follows the nearest `data-brand` ancestor. */
|
|
16
|
+
brand?: string | undefined;
|
|
9
17
|
/** Accessible name for the progress bar. */
|
|
10
18
|
label?: string;
|
|
11
19
|
className?: string;
|
|
@@ -24,9 +32,13 @@ export type ProgressBarProps = {
|
|
|
24
32
|
* invalid CSS or an impossible reported value — though the fraction text
|
|
25
33
|
* still shows the raw, unclamped numbers. When `total` is not positive the
|
|
26
34
|
* bar renders as indeterminate: no `aria-valuenow`/`aria-valuemax`, 0% fill.
|
|
35
|
+
*
|
|
36
|
+
* `intent` colours the fill and defaults to `brand`: progress is not an
|
|
37
|
+
* outcome, so a bar at 40% is not `success`. Pass `success` when completion
|
|
38
|
+
* really is the good news, or `danger` for a quota running out.
|
|
27
39
|
*/
|
|
28
40
|
export const ProgressBar = React.forwardRef<HTMLDivElement, ProgressBarProps>(
|
|
29
|
-
({ completed, total, showFraction = true, label = "Progress", className }, ref) => {
|
|
41
|
+
({ completed, total, intent = "brand", brand, showFraction = true, label = "Progress", className }, ref) => {
|
|
30
42
|
// A caller can hand us anything; the bar must not render an out-of-range
|
|
31
43
|
// width (a negative `width` is invalid CSS) or report impossible progress.
|
|
32
44
|
const isMeasurable = total > 0;
|
|
@@ -34,7 +46,7 @@ export const ProgressBar = React.forwardRef<HTMLDivElement, ProgressBarProps>(
|
|
|
34
46
|
const percentage = isMeasurable ? (clamped / total) * 100 : 0;
|
|
35
47
|
|
|
36
48
|
return (
|
|
37
|
-
<div ref={ref} className={cn("flex items-center gap-3", className)}>
|
|
49
|
+
<div ref={ref} data-intent={intent} data-brand={brand} className={cn("progress-bar flex items-center gap-3", className)}>
|
|
38
50
|
<div
|
|
39
51
|
role="progressbar"
|
|
40
52
|
aria-label={label}
|
|
@@ -44,9 +56,9 @@ export const ProgressBar = React.forwardRef<HTMLDivElement, ProgressBarProps>(
|
|
|
44
56
|
// total there is no meaningful progress to report.
|
|
45
57
|
aria-valuenow={isMeasurable ? clamped : undefined}
|
|
46
58
|
aria-valuetext={isMeasurable ? `${clamped} of ${total}` : undefined}
|
|
47
|
-
className="h-2 flex-1 overflow-hidden rounded-full bg-bg
|
|
59
|
+
className="h-2 flex-1 overflow-hidden rounded-full bg-(--progress-bar-track-bg)"
|
|
48
60
|
>
|
|
49
|
-
<div className="h-full rounded-full bg-
|
|
61
|
+
<div className="h-full rounded-full bg-(--progress-bar-fill-bg) transition-all" style={{ width: `${percentage}%` }} />
|
|
50
62
|
</div>
|
|
51
63
|
{showFraction && (
|
|
52
64
|
<Caption className="shrink-0">
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export { ProgressBar, type ProgressBarProps } from "./ProgressBar";
|
|
1
|
+
export { ProgressBar, type ProgressBarIntent, type ProgressBarIntentName, type ProgressBarProps } from "./ProgressBar";
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ProgressBar recipe — the component-token layer for ProgressBar.
|
|
3
|
+
*
|
|
4
|
+
* Two parts: the track, which is the same grey in every intent, and the fill,
|
|
5
|
+
* which paints with the intent slots from src/theming/intents.css. The fill
|
|
6
|
+
* reads `--intent-fg`, the family's colour on a resting surface, rather than
|
|
7
|
+
* the solid slot: `--intent-solid-bg` for `neutral` is `bg-strong`, which is
|
|
8
|
+
* the track itself, so a neutral bar would vanish. `neutral` is the one
|
|
9
|
+
* override: its `--intent-fg` is `fg-normal`, which barely separates from a
|
|
10
|
+
* `bg-strong` track in either mode, so it takes the mid-grey `fg-subtle`,
|
|
11
|
+
* which separates from both the track and the page around it. (`fg-on-strong`
|
|
12
|
+
* contrasts with the track but matches the page, and reads as a gap.)
|
|
13
|
+
*
|
|
14
|
+
* The grammar is `--progress-bar-{part}-{context}`. Both tokens live on the
|
|
15
|
+
* root, which carries `data-intent`, and the fill reads them by inheritance.
|
|
16
|
+
*
|
|
17
|
+
* `@layer components` is load-bearing, not tidiness — see button.recipe.css.
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
@layer components {
|
|
21
|
+
.progress-bar {
|
|
22
|
+
--progress-bar-track-bg: var(--bg-strong);
|
|
23
|
+
--progress-bar-fill-bg: var(--intent-fg);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
.progress-bar[data-intent="neutral"] {
|
|
27
|
+
--progress-bar-fill-bg: var(--fg-subtle);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
@@ -27,10 +27,11 @@ export type RadioButtonGroupProps = Omit<React.HTMLAttributes<HTMLDivElement>, "
|
|
|
27
27
|
onValueChange?: (value: string) => void;
|
|
28
28
|
/** Layout orientation */
|
|
29
29
|
orientation?: "vertical" | "horizontal";
|
|
30
|
-
/**
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
30
|
+
/**
|
|
31
|
+
* How the options render: `control` (the default) is one `RadioButton` per option;
|
|
32
|
+
* `segmented` and `separate` are a row or column of buttons, joined or apart.
|
|
33
|
+
*/
|
|
34
|
+
appearance?: "control" | "segmented" | "separate";
|
|
34
35
|
/** Input name for form submission */
|
|
35
36
|
name?: string;
|
|
36
37
|
/** Disable all options */
|
|
@@ -44,21 +45,22 @@ export type RadioButtonGroupProps = Omit<React.HTMLAttributes<HTMLDivElement>, "
|
|
|
44
45
|
* uncontrolled `string` selection, via the `value`/`defaultValue`/
|
|
45
46
|
* `onValueChange` triad (`useControllableState`).
|
|
46
47
|
*
|
|
47
|
-
* `
|
|
48
|
-
* `
|
|
49
|
-
* `RadioButton` — a row/column of plain buttons with
|
|
50
|
-
* contiguous buttons touch and share borders (`
|
|
51
|
-
*
|
|
52
|
-
*
|
|
48
|
+
* `appearance="control"` (the default) renders one `RadioButton` per option.
|
|
49
|
+
* `appearance="segmented"` or `"separate"` is a separate rendering path — not
|
|
50
|
+
* a re-skinned `RadioButton` — a row/column of plain buttons with
|
|
51
|
+
* `role="radio"`; contiguous buttons touch and share borders (`segmented`) or
|
|
52
|
+
* sit apart with their own rounded corners (`separate`). One prop rather than
|
|
53
|
+
* a `variant` + `appearance` pair, so "appearance without buttons" — which
|
|
54
|
+
* used to mean nothing — cannot be written.
|
|
53
55
|
*
|
|
54
56
|
* Implements the WAI-ARIA radiogroup keyboard pattern: the group is one tab
|
|
55
57
|
* stop, landing on the checked option (or the first enabled option if none
|
|
56
58
|
* is checked), and arrow/Home/End keys move focus *and* selection between
|
|
57
59
|
* enabled options, wrapping at the ends.
|
|
58
60
|
*
|
|
59
|
-
* Form submission only happens when `name` is set.
|
|
61
|
+
* Form submission only happens when `name` is set. With `control`
|
|
60
62
|
* that means each underlying `RadioButton` gets `name`, so one hidden
|
|
61
|
-
* native radio input is rendered per option;
|
|
63
|
+
* native radio input is rendered per option; with the button appearances the
|
|
62
64
|
* group itself renders a single hidden `<input type="hidden">` carrying the
|
|
63
65
|
* selected value.
|
|
64
66
|
*
|
|
@@ -73,8 +75,7 @@ const RadioButtonGroup = React.forwardRef<HTMLDivElement, RadioButtonGroupProps>
|
|
|
73
75
|
options,
|
|
74
76
|
onValueChange,
|
|
75
77
|
orientation = "vertical",
|
|
76
|
-
|
|
77
|
-
appearance = "segmented",
|
|
78
|
+
appearance = "control",
|
|
78
79
|
name,
|
|
79
80
|
disabled,
|
|
80
81
|
onBlur,
|
|
@@ -111,8 +112,8 @@ const RadioButtonGroup = React.forwardRef<HTMLDivElement, RadioButtonGroupProps>
|
|
|
111
112
|
const handleOptionSelect = (optionValue: string) => {
|
|
112
113
|
// Re-selecting the current option reports nothing, as a native radio does
|
|
113
114
|
// not. The guard lives here as well as in `RadioButton` because the button
|
|
114
|
-
//
|
|
115
|
-
// the two
|
|
115
|
+
// appearances are a separate rendering path that never goes through it —
|
|
116
|
+
// and the two paths must not disagree about this. `ToggleGroup` in
|
|
116
117
|
// `"single"` mode already works this way.
|
|
117
118
|
if (optionValue === value) return;
|
|
118
119
|
setValue(optionValue);
|
|
@@ -139,7 +140,7 @@ const RadioButtonGroup = React.forwardRef<HTMLDivElement, RadioButtonGroupProps>
|
|
|
139
140
|
const hasCheckedOption = options.some((option) => option.value === value);
|
|
140
141
|
const tabStopValue = hasCheckedOption ? value : firstEnabledValue;
|
|
141
142
|
|
|
142
|
-
if (
|
|
143
|
+
if (appearance === "segmented" || appearance === "separate") {
|
|
143
144
|
const isVertical = orientation === "vertical";
|
|
144
145
|
const isSegmented = appearance === "segmented";
|
|
145
146
|
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
|
-
import { Input } from "@sixthshift/design-system/input";
|
|
4
3
|
import { cn } from "@sixthshift/design-system/utils";
|
|
5
4
|
import { Search, X } from "lucide-react";
|
|
6
5
|
import * as React from "react";
|
|
@@ -14,12 +13,19 @@ export type SearchInputProps = Omit<React.InputHTMLAttributes<HTMLInputElement>,
|
|
|
14
13
|
clearLabel?: string;
|
|
15
14
|
};
|
|
16
15
|
|
|
16
|
+
/** The icon slots, duplicated from Input's. */
|
|
17
|
+
const iconSlotStyles = `absolute top-1/2 -translate-y-1/2 text-(--search-input-icon-fg)
|
|
18
|
+
[&_svg]:h-4 [&_svg]:w-4
|
|
19
|
+
[&_button]:flex [&_button]:cursor-pointer [&_button]:appearance-none
|
|
20
|
+
[&_button]:border-0 [&_button]:bg-transparent`;
|
|
21
|
+
|
|
17
22
|
/**
|
|
18
23
|
* Text input specialised for search: a search icon fixed on the left and,
|
|
19
24
|
* once there is a value, a clear button on the right.
|
|
20
25
|
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
26
|
+
* Its own field, not a wrapped `Input`: the markup and classes are duplicated
|
|
27
|
+
* from Input on purpose, so importing SearchInput pulls in nothing else and
|
|
28
|
+
* its colours are its own `--search-input-*` tokens. Always controlled — `value` is required and
|
|
23
29
|
* there is no `defaultValue`/uncontrolled mode. `onValueChange` receives the
|
|
24
30
|
* string value directly; the native `onChange` keeps its event signature and
|
|
25
31
|
* fires too if passed. Clearing calls `onClear` if provided, otherwise calls
|
|
@@ -36,25 +42,33 @@ const SearchInput = React.forwardRef<HTMLInputElement, SearchInputProps>(
|
|
|
36
42
|
};
|
|
37
43
|
|
|
38
44
|
return (
|
|
39
|
-
<
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
45
|
+
<div className={cn("search-input relative h-9 w-full text-sm", className)}>
|
|
46
|
+
<span className={cn(iconSlotStyles, "left-3")}>
|
|
47
|
+
<Search />
|
|
48
|
+
</span>
|
|
49
|
+
<input
|
|
50
|
+
ref={ref}
|
|
51
|
+
type="text"
|
|
52
|
+
value={value}
|
|
53
|
+
onChange={(e) => {
|
|
54
|
+
onValueChange(e.target.value);
|
|
55
|
+
onChange?.(e);
|
|
56
|
+
}}
|
|
57
|
+
className={cn(
|
|
58
|
+
"border-(color:--search-input-border) focus-visible:ring-(color:--search-input-ring) flex h-full w-full rounded-md border bg-(--search-input-bg) py-1 text-[length:inherit] shadow-xs transition-colors placeholder:text-(--search-input-placeholder-fg) focus-visible:outline-hidden focus-visible:ring-2 disabled:cursor-not-allowed disabled:opacity-50",
|
|
59
|
+
"pl-9",
|
|
60
|
+
value ? "pr-9" : "pr-3"
|
|
61
|
+
)}
|
|
62
|
+
{...props}
|
|
63
|
+
/>
|
|
64
|
+
{value && (
|
|
65
|
+
<span className={cn(iconSlotStyles, "right-3")}>
|
|
66
|
+
<button type="button" onClick={handleClear} aria-label={clearLabel} className="rounded-sm hover:bg-(--search-input-clear-bg-hovered)">
|
|
51
67
|
<X />
|
|
52
68
|
</button>
|
|
53
|
-
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
{...props}
|
|
57
|
-
/>
|
|
69
|
+
</span>
|
|
70
|
+
)}
|
|
71
|
+
</div>
|
|
58
72
|
);
|
|
59
73
|
}
|
|
60
74
|
);
|