@sixthshift/design-system 0.7.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/DataTable/DataTable.d.ts +89 -0
- package/dist/components/DataTable/DataTable.d.ts.map +1 -0
- package/dist/components/DataTable/DataTable.js +107 -0
- package/dist/components/DataTable/DataTable.js.map +1 -0
- package/dist/components/DataTable/index.d.ts +2 -0
- package/dist/components/DataTable/index.d.ts.map +1 -0
- package/dist/components/DataTable/index.js +2 -0
- package/dist/components/DataTable/index.js.map +1 -0
- package/dist/components/FileDropzone/FileDropzone.d.ts +87 -0
- package/dist/components/FileDropzone/FileDropzone.d.ts.map +1 -0
- package/dist/components/FileDropzone/FileDropzone.js +146 -0
- package/dist/components/FileDropzone/FileDropzone.js.map +1 -0
- package/dist/components/FileDropzone/index.d.ts +2 -0
- package/dist/components/FileDropzone/index.d.ts.map +1 -0
- package/dist/components/FileDropzone/index.js +2 -0
- package/dist/components/FileDropzone/index.js.map +1 -0
- 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/ImageGallery/ImageGallery.d.ts +93 -0
- package/dist/components/ImageGallery/ImageGallery.d.ts.map +1 -0
- package/dist/components/ImageGallery/ImageGallery.js +121 -0
- package/dist/components/ImageGallery/ImageGallery.js.map +1 -0
- package/dist/components/ImageGallery/index.d.ts +2 -0
- package/dist/components/ImageGallery/index.d.ts.map +1 -0
- package/dist/components/ImageGallery/index.js +2 -0
- package/dist/components/ImageGallery/index.js.map +1 -0
- 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 +78 -0
- package/dist/components/NumberStepper/NumberStepper.d.ts.map +1 -0
- package/dist/components/NumberStepper/NumberStepper.js +129 -0
- package/dist/components/NumberStepper/NumberStepper.js.map +1 -0
- package/dist/components/NumberStepper/index.d.ts +2 -0
- package/dist/components/NumberStepper/index.d.ts.map +1 -0
- package/dist/components/NumberStepper/index.js +2 -0
- package/dist/components/NumberStepper/index.js.map +1 -0
- 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/Steps/Steps.d.ts +65 -0
- package/dist/components/Steps/Steps.d.ts.map +1 -0
- package/dist/components/Steps/Steps.js +62 -0
- package/dist/components/Steps/Steps.js.map +1 -0
- package/dist/components/Steps/index.d.ts +2 -0
- package/dist/components/Steps/index.d.ts.map +1 -0
- package/dist/components/Steps/index.js +2 -0
- package/dist/components/Steps/index.js.map +1 -0
- 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 +22 -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/DataTable/DataTable.tsx +261 -0
- package/src/components/DataTable/data-table.recipe.css +28 -0
- package/src/components/DataTable/index.ts +10 -0
- package/src/components/FileDropzone/FileDropzone.tsx +239 -0
- package/src/components/FileDropzone/file-dropzone.recipe.css +38 -0
- package/src/components/FileDropzone/index.ts +1 -0
- package/src/components/FormField/FormField.tsx +3 -2
- package/src/components/ImageGallery/ImageGallery.tsx +285 -0
- package/src/components/ImageGallery/image-gallery.recipe.css +50 -0
- package/src/components/ImageGallery/index.ts +7 -0
- 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/NumberStepper/NumberStepper.tsx +227 -0
- package/src/components/NumberStepper/index.ts +1 -0
- package/src/components/NumberStepper/number-stepper.recipe.css +31 -0
- 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/Steps/Steps.tsx +149 -0
- package/src/components/Steps/index.ts +1 -0
- package/src/components/Steps/steps.recipe.css +51 -0
- 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 +22 -1
- package/src/typography/Code.tsx +1 -1
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { cn } from "@sixthshift/design-system/utils";
|
|
4
|
+
import { cva } from "class-variance-authority";
|
|
5
|
+
import { Check } from "lucide-react";
|
|
6
|
+
import * as React from "react";
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Geometry only. Every colour reads a `--steps-*` component token whose value
|
|
10
|
+
* is decided by src/components/Steps/steps.recipe.css, selected by the
|
|
11
|
+
* `data-state` each item renders (`complete`, `current`, `upcoming`).
|
|
12
|
+
*/
|
|
13
|
+
const stepsVariants = cva("steps flex", {
|
|
14
|
+
variants: {
|
|
15
|
+
orientation: {
|
|
16
|
+
horizontal: "w-full flex-row items-start",
|
|
17
|
+
vertical: "flex-col",
|
|
18
|
+
},
|
|
19
|
+
},
|
|
20
|
+
defaultVariants: {
|
|
21
|
+
orientation: "horizontal",
|
|
22
|
+
},
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
export type StepsOrientation = "horizontal" | "vertical";
|
|
26
|
+
|
|
27
|
+
/** Where a step sits relative to `current`. Rendered as `data-state`. */
|
|
28
|
+
export type StepState = "complete" | "current" | "upcoming";
|
|
29
|
+
|
|
30
|
+
export type StepItem = {
|
|
31
|
+
/** Visible step name. */
|
|
32
|
+
label: React.ReactNode;
|
|
33
|
+
/** Secondary line under the label. */
|
|
34
|
+
description?: React.ReactNode;
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
export type StepsProps = Omit<React.OlHTMLAttributes<HTMLOListElement>, "children"> & {
|
|
38
|
+
/** The steps, in order. */
|
|
39
|
+
steps: readonly StepItem[];
|
|
40
|
+
/** Zero-based index of the step in progress. `steps.length` marks every step complete. */
|
|
41
|
+
current: number;
|
|
42
|
+
/**
|
|
43
|
+
* Makes completed steps clickable, for going back in a wizard. Upcoming
|
|
44
|
+
* steps never are — skipping ahead is the flow's decision, not the
|
|
45
|
+
* indicator's.
|
|
46
|
+
*/
|
|
47
|
+
onStepClick?: (index: number) => void;
|
|
48
|
+
/** Layout direction. `horizontal` collapses labels to the current step's on narrow screens. */
|
|
49
|
+
orientation?: StepsOrientation;
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
function stateOf(index: number, current: number): StepState {
|
|
53
|
+
if (index < current) return "complete";
|
|
54
|
+
if (index === current) return "current";
|
|
55
|
+
return "upcoming";
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Progress through a fixed sequence — checkout, onboarding, a multi-page
|
|
60
|
+
* form. Display-only: the step in progress is the `current` prop, owned by
|
|
61
|
+
* whatever runs the flow, so there is no internal state to control.
|
|
62
|
+
*
|
|
63
|
+
* Renders an `<ol>` (pass `aria-label`, e.g. "Checkout progress"); the
|
|
64
|
+
* current item carries `aria-current="step"`, and each indicator has a
|
|
65
|
+
* visually hidden status ("completed", "current") so the sequence reads
|
|
66
|
+
* correctly without the colours.
|
|
67
|
+
*
|
|
68
|
+
* With `onStepClick`, completed steps become buttons. Current and upcoming
|
|
69
|
+
* steps stay plain text: a step indicator that let you jump forward would be
|
|
70
|
+
* making the flow's validation decisions for it.
|
|
71
|
+
*
|
|
72
|
+
* Horizontally, every label shows from `sm` up; below that only the current
|
|
73
|
+
* step's label does, so four steps still fit a phone.
|
|
74
|
+
*/
|
|
75
|
+
export const Steps = React.forwardRef<HTMLOListElement, StepsProps>(({ className, steps, current, onStepClick, orientation = "horizontal", ...props }, ref) => {
|
|
76
|
+
const isHorizontal = orientation === "horizontal";
|
|
77
|
+
|
|
78
|
+
return (
|
|
79
|
+
<ol ref={ref} data-orientation={orientation} className={stepsVariants({ orientation, className })} {...props}>
|
|
80
|
+
{steps.map((step, index) => {
|
|
81
|
+
const state = stateOf(index, current);
|
|
82
|
+
const isLast = index === steps.length - 1;
|
|
83
|
+
const clickable = state === "complete" && onStepClick !== undefined;
|
|
84
|
+
|
|
85
|
+
const indicator = (
|
|
86
|
+
<span className="steps-indicator border-(color:--steps-indicator-border) relative z-10 flex size-8 shrink-0 items-center justify-center rounded-full border-2 bg-(--steps-indicator-bg) font-semibold text-(--steps-indicator-fg) text-sm transition-colors">
|
|
87
|
+
{state === "complete" ? <Check aria-hidden="true" className="size-4" strokeWidth={3} /> : index + 1}
|
|
88
|
+
</span>
|
|
89
|
+
);
|
|
90
|
+
|
|
91
|
+
const text = (
|
|
92
|
+
<span
|
|
93
|
+
className={cn("flex min-w-0 flex-col", isHorizontal ? "items-center text-center" : "pt-1", isHorizontal && state !== "current" && "max-sm:sr-only")}
|
|
94
|
+
>
|
|
95
|
+
<span className="font-medium text-(--steps-label-fg) text-sm">{step.label}</span>
|
|
96
|
+
{step.description !== undefined && <span className="text-(--steps-description-fg) text-xs">{step.description}</span>}
|
|
97
|
+
{state !== "upcoming" && <span className="sr-only">{state === "complete" ? "(completed)" : "(current)"}</span>}
|
|
98
|
+
</span>
|
|
99
|
+
);
|
|
100
|
+
|
|
101
|
+
const body = (
|
|
102
|
+
<>
|
|
103
|
+
{indicator}
|
|
104
|
+
{text}
|
|
105
|
+
</>
|
|
106
|
+
);
|
|
107
|
+
|
|
108
|
+
const bodyClass = cn("flex gap-2", isHorizontal ? "flex-col items-center" : "flex-row items-start gap-3");
|
|
109
|
+
|
|
110
|
+
return (
|
|
111
|
+
<li
|
|
112
|
+
// biome-ignore lint/suspicious/noArrayIndexKey: a step is its position — the sequence is fixed and never reorders
|
|
113
|
+
key={index}
|
|
114
|
+
data-state={state}
|
|
115
|
+
aria-current={state === "current" ? "step" : undefined}
|
|
116
|
+
className={cn("steps-item relative", isHorizontal ? "flex flex-1 justify-center" : "flex pb-6 last:pb-0")}
|
|
117
|
+
>
|
|
118
|
+
{!isLast && (
|
|
119
|
+
<span
|
|
120
|
+
aria-hidden="true"
|
|
121
|
+
className={cn(
|
|
122
|
+
"steps-connector absolute bg-(--steps-connector-bg)",
|
|
123
|
+
isHorizontal ? "top-4 left-1/2 h-0.5 w-full -translate-y-1/2" : "top-8 bottom-0 left-4 w-0.5 -translate-x-1/2"
|
|
124
|
+
)}
|
|
125
|
+
/>
|
|
126
|
+
)}
|
|
127
|
+
{clickable ? (
|
|
128
|
+
<button
|
|
129
|
+
type="button"
|
|
130
|
+
onClick={() => onStepClick(index)}
|
|
131
|
+
className={cn(
|
|
132
|
+
bodyClass,
|
|
133
|
+
"focus-visible:ring-(color:--steps-ring) cursor-pointer rounded-md focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-offset-2 [&:hover_.steps-indicator]:bg-(--steps-indicator-bg-hovered)"
|
|
134
|
+
)}
|
|
135
|
+
>
|
|
136
|
+
{body}
|
|
137
|
+
</button>
|
|
138
|
+
) : (
|
|
139
|
+
<span className={bodyClass}>{body}</span>
|
|
140
|
+
)}
|
|
141
|
+
</li>
|
|
142
|
+
);
|
|
143
|
+
})}
|
|
144
|
+
</ol>
|
|
145
|
+
);
|
|
146
|
+
});
|
|
147
|
+
Steps.displayName = "Steps";
|
|
148
|
+
|
|
149
|
+
export { stepsVariants };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { type StepItem, type StepState, Steps, type StepsOrientation, type StepsProps, stepsVariants } from "./Steps";
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Steps recipe — the component-token layer for Steps.
|
|
3
|
+
*
|
|
4
|
+
* One axis, and it is not `intent`: a step's colour says where it sits in the
|
|
5
|
+
* sequence, which the component renders as `data-state` on each item
|
|
6
|
+
* (`complete`, `current`, `upcoming`). Same shape as tabs.css keying its
|
|
7
|
+
* selected cell on `data-selected` rather than a variant.
|
|
8
|
+
*
|
|
9
|
+
* The grammar is `--steps-[{part}-]{context}[-{state}]`. Parts are the
|
|
10
|
+
* `indicator` (the numbered circle), the `connector` (the rule to the next
|
|
11
|
+
* step), and the `label`/`description` text. `-hovered` on the indicator only
|
|
12
|
+
* ever applies to a completed step with `onStepClick`, the one clickable case.
|
|
13
|
+
*
|
|
14
|
+
* The connector belongs to the step it leaves, so a completed step's
|
|
15
|
+
* connector takes the brand colour: the line fills up to the current step.
|
|
16
|
+
*
|
|
17
|
+
* `@layer components` is load-bearing, not tidiness — see button.css.
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
@layer components {
|
|
21
|
+
/* The floor, and also the upcoming look: an outlined, quiet step. An item
|
|
22
|
+
with a state a consumer added but did not style renders like this. */
|
|
23
|
+
.steps-item {
|
|
24
|
+
--steps-indicator-bg: var(--bg-normal);
|
|
25
|
+
--steps-indicator-bg-hovered: var(--steps-indicator-bg);
|
|
26
|
+
--steps-indicator-fg: var(--fg-subtle);
|
|
27
|
+
--steps-indicator-border: var(--border-normal);
|
|
28
|
+
--steps-connector-bg: var(--border-normal);
|
|
29
|
+
--steps-label-fg: var(--fg-subtle);
|
|
30
|
+
--steps-description-fg: var(--fg-subtle);
|
|
31
|
+
--steps-ring: var(--focus-ring);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/* current — outlined in brand, the label at full strength. */
|
|
35
|
+
.steps-item[data-state="current"] {
|
|
36
|
+
--steps-indicator-bg: var(--bg-brand-subtle);
|
|
37
|
+
--steps-indicator-fg: var(--fg-on-brand-subtle);
|
|
38
|
+
--steps-indicator-border: var(--border-brand);
|
|
39
|
+
--steps-label-fg: var(--fg-normal);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/* complete — a filled brand disc with the check reversed out. */
|
|
43
|
+
.steps-item[data-state="complete"] {
|
|
44
|
+
--steps-indicator-bg: var(--bg-brand);
|
|
45
|
+
--steps-indicator-bg-hovered: var(--bg-brand-hovered);
|
|
46
|
+
--steps-indicator-fg: var(--fg-on-brand);
|
|
47
|
+
--steps-indicator-border: var(--steps-indicator-bg);
|
|
48
|
+
--steps-connector-bg: var(--border-brand);
|
|
49
|
+
--steps-label-fg: var(--fg-normal);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
|
-
import { Badge } from "@sixthshift/design-system/badge";
|
|
4
3
|
import { cn } from "@sixthshift/design-system/utils";
|
|
5
4
|
import { X } from "lucide-react";
|
|
6
5
|
import * as React from "react";
|
|
@@ -24,20 +23,30 @@ export type TagChipProps = {
|
|
|
24
23
|
*
|
|
25
24
|
* Two disjoint modes, chosen by whether `onRemove` is passed: navigable (no
|
|
26
25
|
* `onRemove`; the caller wraps it in a link) or removable (`onRemove` renders
|
|
27
|
-
* an × — used by `TagInput` and edit surfaces).
|
|
28
|
-
*
|
|
26
|
+
* an × — used by `TagInput` and edit surfaces). It looks like an outline
|
|
27
|
+
* Badge but is not one: the shape is duplicated here and every colour is its
|
|
28
|
+
* own `--tag-chip-*` token, so importing TagChip pulls in nothing else. A tag
|
|
29
|
+
* is metadata, not a status, so it takes no intent.
|
|
29
30
|
*/
|
|
31
|
+
/** Duplicated from Badge's sizes, which TagChip used to borrow. */
|
|
32
|
+
const sizeStyles = {
|
|
33
|
+
sm: "px-1.5 py-0 text-[10px]",
|
|
34
|
+
md: "px-2.5 py-0.5 text-xs",
|
|
35
|
+
} as const;
|
|
36
|
+
|
|
30
37
|
export const TagChip = React.forwardRef<HTMLSpanElement, TagChipProps>(({ tag, onRemove, size = "sm", className }, ref) => {
|
|
31
38
|
const sep = tag.indexOf(":");
|
|
32
39
|
const namespace = sep > 0 ? tag.slice(0, sep) : null;
|
|
33
40
|
const value = sep > 0 ? tag.slice(sep + 1) : tag;
|
|
34
41
|
|
|
35
42
|
return (
|
|
36
|
-
<
|
|
43
|
+
<span
|
|
37
44
|
ref={ref}
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
45
|
+
className={cn(
|
|
46
|
+
"tag-chip border-(color:--tag-chip-border) focus:ring-(color:--tag-chip-ring) inline-flex items-center gap-1 rounded-md border bg-(--tag-chip-bg) font-normal text-(--tag-chip-fg) transition-colors focus:outline-hidden focus:ring-2 focus:ring-offset-2",
|
|
47
|
+
sizeStyles[size],
|
|
48
|
+
className
|
|
49
|
+
)}
|
|
41
50
|
>
|
|
42
51
|
{namespace && <span className="text-(--tag-chip-namespace-fg)">{namespace}:</span>}
|
|
43
52
|
<span>{value}</span>
|
|
@@ -51,7 +60,7 @@ export const TagChip = React.forwardRef<HTMLSpanElement, TagChipProps>(({ tag, o
|
|
|
51
60
|
<X className="h-3 w-3" />
|
|
52
61
|
</button>
|
|
53
62
|
)}
|
|
54
|
-
</
|
|
63
|
+
</span>
|
|
55
64
|
);
|
|
56
65
|
});
|
|
57
66
|
TagChip.displayName = "TagChip";
|
|
@@ -1,24 +1,24 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* TagChip recipe — the component-token layer for TagChip.
|
|
3
3
|
*
|
|
4
|
-
* TagChip
|
|
5
|
-
* it
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
* contributes itself are the muted namespace prefix and the remove ("×")
|
|
9
|
-
* button, which is why this file covers just those two.
|
|
4
|
+
* TagChip draws its own chip rather than rendering a Badge, so it owns every
|
|
5
|
+
* colour it paints: the surface (a resting background and a neutral border,
|
|
6
|
+
* like an outline badge), the quieter text a tag gets as metadata, the
|
|
7
|
+
* namespace prefix and the remove ("×") button.
|
|
10
8
|
*
|
|
11
|
-
* The grammar is `--tag-chip-{part}-{context}[-{state}]`.
|
|
12
|
-
* on `.tag-chip
|
|
13
|
-
*
|
|
14
|
-
* remove `<button>` need no class hooks of their own for that.
|
|
9
|
+
* The grammar is `--tag-chip-[{part}-]{context}[-{state}]`. Every token lives
|
|
10
|
+
* on `.tag-chip`, the root, and the namespace `<span>` and remove `<button>`
|
|
11
|
+
* read theirs by ordinary inheritance.
|
|
15
12
|
*
|
|
16
|
-
* `@layer components` is load-bearing, not tidiness — see button.css
|
|
17
|
-
* cascade reasoning.
|
|
13
|
+
* `@layer components` is load-bearing, not tidiness — see button.recipe.css.
|
|
18
14
|
*/
|
|
19
15
|
|
|
20
16
|
@layer components {
|
|
21
17
|
.tag-chip {
|
|
18
|
+
--tag-chip-bg: var(--bg-normal);
|
|
19
|
+
--tag-chip-border: var(--border-normal);
|
|
20
|
+
--tag-chip-ring: var(--border-brand);
|
|
21
|
+
--tag-chip-fg: var(--fg-subtle);
|
|
22
22
|
--tag-chip-namespace-fg: var(--fg-subtle);
|
|
23
23
|
--tag-chip-remove-fg: var(--fg-subtle);
|
|
24
24
|
--tag-chip-remove-fg-hovered: var(--fg-normal);
|
|
@@ -132,7 +132,7 @@ export const Toast = React.forwardRef<HTMLDivElement, ToastProps>(
|
|
|
132
132
|
{children}
|
|
133
133
|
{action && (
|
|
134
134
|
<div className="mt-2">
|
|
135
|
-
<Button
|
|
135
|
+
<Button inline size="sm" className="font-medium" onClick={onAction}>
|
|
136
136
|
{action}
|
|
137
137
|
</Button>
|
|
138
138
|
</div>
|
|
@@ -2,19 +2,64 @@
|
|
|
2
2
|
|
|
3
3
|
import { useControllableState } from "@sixthshift/design-system/hooks";
|
|
4
4
|
import { cn } from "@sixthshift/design-system/utils";
|
|
5
|
-
import type
|
|
5
|
+
import { cva, type VariantProps } from "class-variance-authority";
|
|
6
6
|
import * as React from "react";
|
|
7
|
-
import { type ButtonIntent, type ButtonVariant, buttonRecipe, type buttonVariants } from "../Button/Button";
|
|
8
7
|
|
|
9
8
|
/**
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
9
|
+
* Geometry and behaviour only; every colour reads a `--toggle-*` token decided
|
|
10
|
+
* by src/components/Toggle/toggle.recipe.css.
|
|
11
|
+
*
|
|
12
|
+
* Duplicated from Button on purpose, not shared: a toggle looks like a button
|
|
13
|
+
* but is its own component, so importing Toggle must not pull in Button. The
|
|
14
|
+
* two start identical and are free to drift.
|
|
13
15
|
*/
|
|
16
|
+
const toggleVariants = cva(
|
|
17
|
+
// One literal, deliberately — see Button.tsx for the sorter trap.
|
|
18
|
+
"toggle border-(color:--toggle-border) focus-visible:ring-(color:--toggle-ring) inline-flex cursor-pointer items-center justify-center gap-2 whitespace-nowrap rounded-md bg-(--toggle-bg) font-medium text-(--toggle-fg) text-sm transition-colors hover:bg-(--toggle-bg-hovered) focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-offset-2 active:bg-(--toggle-bg-pressed) disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0",
|
|
19
|
+
{
|
|
20
|
+
variants: {
|
|
21
|
+
size: {
|
|
22
|
+
xs: "h-7 rounded-md px-2 text-xs",
|
|
23
|
+
sm: "h-8 rounded-md px-3 text-xs",
|
|
24
|
+
md: "h-9 px-4 py-2",
|
|
25
|
+
lg: "h-10 rounded-md px-8",
|
|
26
|
+
xl: "h-12 rounded-md px-10 text-base",
|
|
27
|
+
},
|
|
28
|
+
},
|
|
29
|
+
defaultVariants: {
|
|
30
|
+
size: "md",
|
|
31
|
+
},
|
|
32
|
+
}
|
|
33
|
+
);
|
|
34
|
+
|
|
35
|
+
/** The non-colour half of `variant` — border width and elevation. */
|
|
36
|
+
const variantStructure: Record<string, string> = {
|
|
37
|
+
solid: "shadow",
|
|
38
|
+
outline: "border shadow-xs",
|
|
39
|
+
ghost: "",
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
/** Squares the box at whatever size is set, and drops the horizontal padding. */
|
|
43
|
+
const iconOnlyGeometry: Record<string, string> = {
|
|
44
|
+
xs: "w-7 px-0",
|
|
45
|
+
sm: "w-8 px-0",
|
|
46
|
+
md: "w-9 px-0",
|
|
47
|
+
lg: "w-10 px-0",
|
|
48
|
+
xl: "w-12 px-0",
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
/** Widened deliberately, as on every component: a consumer adds a variant or intent in CSS. */
|
|
52
|
+
type Loose<T extends string> = T | (string & {});
|
|
53
|
+
|
|
54
|
+
export type ToggleVariantName = "solid" | "outline" | "ghost";
|
|
55
|
+
export type ToggleIntentName = "neutral" | "brand" | "danger" | "success" | "warning";
|
|
56
|
+
export type ToggleVariant = Loose<ToggleVariantName>;
|
|
57
|
+
export type ToggleIntent = Loose<ToggleIntentName>;
|
|
58
|
+
|
|
14
59
|
export type ToggleProps = Omit<React.ButtonHTMLAttributes<HTMLButtonElement>, "onChange" | "value"> &
|
|
15
|
-
VariantProps<typeof
|
|
16
|
-
variant?:
|
|
17
|
-
intent?:
|
|
60
|
+
VariantProps<typeof toggleVariants> & {
|
|
61
|
+
variant?: ToggleVariant | undefined;
|
|
62
|
+
intent?: ToggleIntent | undefined;
|
|
18
63
|
/** Name of a brand the theme defines, rendered as `data-brand`. Omitted, the toggle follows the nearest `data-brand` ancestor. */
|
|
19
64
|
brand?: string | undefined;
|
|
20
65
|
/** Square the toggle at its current size, for an icon with no label. */
|
|
@@ -33,12 +78,12 @@ export type ToggleProps = Omit<React.ButtonHTMLAttributes<HTMLButtonElement>, "o
|
|
|
33
78
|
* for a set of mutually exclusive or multi-select options, use `ToggleGroup`
|
|
34
79
|
* instead.
|
|
35
80
|
*
|
|
36
|
-
*
|
|
37
|
-
* `
|
|
38
|
-
*
|
|
39
|
-
* pressed
|
|
40
|
-
* keyed off the `data-state="on" |
|
|
41
|
-
* alongside `aria-pressed`.
|
|
81
|
+
* Looks like a Button and takes the same `variant`/`intent`/`size` axes, plus
|
|
82
|
+
* `iconOnly` for a toolbar toggle whose whole content is an icon — but it is
|
|
83
|
+
* its own component, with its own classes and `--toggle-*` tokens, and
|
|
84
|
+
* imports nothing from Button. Every colour, pressed state included, lives in
|
|
85
|
+
* `src/components/Toggle/toggle.recipe.css`, keyed off the `data-state="on" |
|
|
86
|
+
* "off"` attribute this component renders alongside `aria-pressed`.
|
|
42
87
|
*
|
|
43
88
|
* Controlled via `pressed`/`defaultPressed`/`onPressedChange`
|
|
44
89
|
* (`useControllableState`).
|
|
@@ -47,8 +92,8 @@ const Toggle = React.forwardRef<HTMLButtonElement, ToggleProps>(
|
|
|
47
92
|
(
|
|
48
93
|
{
|
|
49
94
|
className,
|
|
50
|
-
variant,
|
|
51
|
-
intent,
|
|
95
|
+
variant = "solid",
|
|
96
|
+
intent = "neutral",
|
|
52
97
|
brand,
|
|
53
98
|
size,
|
|
54
99
|
iconOnly = false,
|
|
@@ -67,18 +112,19 @@ const Toggle = React.forwardRef<HTMLButtonElement, ToggleProps>(
|
|
|
67
112
|
onChange: onPressedChange,
|
|
68
113
|
});
|
|
69
114
|
|
|
70
|
-
const recipe = buttonRecipe({ variant, intent, brand, size, iconOnly });
|
|
71
|
-
|
|
72
115
|
return (
|
|
73
116
|
<button
|
|
74
117
|
type="button"
|
|
75
118
|
aria-pressed={pressed}
|
|
119
|
+
// What the recipe selects on; `data-brand` is omitted unless set.
|
|
76
120
|
data-state={pressed ? "on" : "off"}
|
|
121
|
+
data-variant={variant}
|
|
122
|
+
data-intent={intent}
|
|
123
|
+
data-brand={brand}
|
|
77
124
|
disabled={disabled}
|
|
78
125
|
ref={ref}
|
|
79
126
|
onClick={() => setPressed(!pressed)}
|
|
80
|
-
{
|
|
81
|
-
className={cn(recipe.className, className)}
|
|
127
|
+
className={cn(variantStructure[variant], toggleVariants({ size, className: cn(iconOnly ? iconOnlyGeometry[size ?? "md"] : undefined, className) }))}
|
|
82
128
|
{...props}
|
|
83
129
|
>
|
|
84
130
|
{children}
|
|
@@ -88,4 +134,4 @@ const Toggle = React.forwardRef<HTMLButtonElement, ToggleProps>(
|
|
|
88
134
|
);
|
|
89
135
|
Toggle.displayName = "Toggle";
|
|
90
136
|
|
|
91
|
-
export { Toggle };
|
|
137
|
+
export { Toggle, toggleVariants };
|
|
@@ -1,92 +1,64 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Toggle recipe — the
|
|
2
|
+
* Toggle recipe — the component-token layer for Toggle.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
4
|
+
* Toggle's own `--toggle-*` tokens on its own `.toggle` hook. It looks like a
|
|
5
|
+
* Button, and these cells start as copies of button.recipe.css's, but it is a
|
|
6
|
+
* separate component: re-pointing `--button-*` does not reach a toggle, and
|
|
7
|
+
* re-pointing these does not reach a button. Duplicated on purpose, so each is
|
|
8
|
+
* free to change without dragging the other.
|
|
8
9
|
*
|
|
9
|
-
*
|
|
10
|
-
* `[data-state="on"]
|
|
11
|
-
*
|
|
12
|
-
* `selected`, rather than a new attribute invented for this file. `state`
|
|
13
|
-
* never appears in a token *name* any more than `intent` does — see
|
|
14
|
-
* button.css's header for why: it selects, via the attribute, which existing
|
|
15
|
-
* `--button-bg`/`--button-bg-hovered` values apply.
|
|
10
|
+
* Every cell paints with the intent slots (src/theming/intents.css), so each
|
|
11
|
+
* variant works in every intent. The third selector is `[data-state="on"]`,
|
|
12
|
+
* the attribute Toggle renders alongside `aria-pressed`.
|
|
16
13
|
*
|
|
17
|
-
*
|
|
18
|
-
* press, so "on" carries no colour change.
|
|
19
|
-
*
|
|
20
|
-
* Every cell repeats its own resting value into `--button-bg-hovered`: the old
|
|
21
|
-
* classes paired `bg-*` with an identical `hover:bg-*`, i.e. a pressed toggle
|
|
22
|
-
* deliberately does not shift on hover. Restating it here (rather than relying
|
|
23
|
-
* on button.css's floor, which chains `-hovered` from `--button-bg`) keeps
|
|
24
|
-
* that "no shift" behaviour explicit and correct regardless of cascade order
|
|
25
|
-
* against the button.css cells these compound with.
|
|
14
|
+
* `@layer components` is load-bearing, not tidiness — see button.recipe.css.
|
|
26
15
|
*/
|
|
27
16
|
|
|
28
17
|
@layer components {
|
|
29
|
-
/*
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
--
|
|
33
|
-
--
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
--button-bg: var(--bg-strong-pressed);
|
|
38
|
-
--button-bg-hovered: var(--bg-strong-pressed);
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
.btn[data-state="on"][data-variant="solid"][data-intent="danger"] {
|
|
42
|
-
--button-bg: var(--bg-danger-pressed);
|
|
43
|
-
--button-bg-hovered: var(--bg-danger-pressed);
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
.btn[data-state="on"][data-variant="solid"][data-intent="success"] {
|
|
47
|
-
--button-bg: var(--bg-success-pressed);
|
|
48
|
-
--button-bg-hovered: var(--bg-success-pressed);
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
.btn[data-state="on"][data-variant="solid"][data-intent="warning"] {
|
|
52
|
-
--button-bg: var(--bg-warning-pressed);
|
|
53
|
-
--button-bg-hovered: var(--bg-warning-pressed);
|
|
18
|
+
/* The floor — a plain, legible toggle when no cell below matches. */
|
|
19
|
+
.toggle {
|
|
20
|
+
--toggle-bg: transparent;
|
|
21
|
+
--toggle-bg-hovered: var(--toggle-bg); /* no shift on hover */
|
|
22
|
+
--toggle-bg-pressed: var(--toggle-bg-hovered);
|
|
23
|
+
--toggle-fg: var(--fg-normal);
|
|
24
|
+
--toggle-border: var(--border-normal);
|
|
25
|
+
--toggle-ring: var(--focus-ring);
|
|
54
26
|
}
|
|
55
27
|
|
|
56
|
-
/*
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
--
|
|
61
|
-
--
|
|
62
|
-
--button-fg: var(--fg-normal);
|
|
28
|
+
/* solid — a filled surface, with text read on top of it. */
|
|
29
|
+
.toggle[data-variant="solid"] {
|
|
30
|
+
--toggle-bg: var(--intent-solid-bg);
|
|
31
|
+
--toggle-bg-hovered: var(--intent-solid-bg-hovered);
|
|
32
|
+
--toggle-bg-pressed: var(--intent-solid-bg-pressed);
|
|
33
|
+
--toggle-fg: var(--intent-solid-fg);
|
|
63
34
|
}
|
|
64
35
|
|
|
65
|
-
.
|
|
66
|
-
.
|
|
67
|
-
|
|
68
|
-
--
|
|
69
|
-
--
|
|
36
|
+
/* outline and ghost share the family's text colour and its tint wash. */
|
|
37
|
+
.toggle[data-variant="outline"],
|
|
38
|
+
.toggle[data-variant="ghost"] {
|
|
39
|
+
--toggle-bg-hovered: var(--intent-tint-bg-hovered);
|
|
40
|
+
--toggle-bg-pressed: var(--intent-tint-bg-pressed);
|
|
41
|
+
--toggle-fg: var(--intent-fg);
|
|
70
42
|
}
|
|
71
43
|
|
|
72
|
-
.
|
|
73
|
-
.
|
|
74
|
-
--
|
|
75
|
-
--
|
|
76
|
-
--button-fg: var(--fg-on-danger-subtle-pressed);
|
|
44
|
+
/* outline alone carries a resting surface and a border. */
|
|
45
|
+
.toggle[data-variant="outline"] {
|
|
46
|
+
--toggle-bg: var(--bg-normal);
|
|
47
|
+
--toggle-border: var(--intent-border);
|
|
77
48
|
}
|
|
78
49
|
|
|
79
|
-
.
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
--
|
|
50
|
+
/* On. Every cell repeats its resting value into `-hovered`: a pressed toggle
|
|
51
|
+
deliberately does not shift on hover. solid holds the fill's -pressed
|
|
52
|
+
step; outline and ghost hold the tint's, with its text partner. */
|
|
53
|
+
.toggle[data-state="on"][data-variant="solid"] {
|
|
54
|
+
--toggle-bg: var(--intent-solid-bg-pressed);
|
|
55
|
+
--toggle-bg-hovered: var(--intent-solid-bg-pressed);
|
|
84
56
|
}
|
|
85
57
|
|
|
86
|
-
.
|
|
87
|
-
.
|
|
88
|
-
--
|
|
89
|
-
--
|
|
90
|
-
--
|
|
58
|
+
.toggle[data-state="on"][data-variant="outline"],
|
|
59
|
+
.toggle[data-state="on"][data-variant="ghost"] {
|
|
60
|
+
--toggle-bg: var(--intent-tint-bg-pressed);
|
|
61
|
+
--toggle-bg-hovered: var(--intent-tint-bg-pressed);
|
|
62
|
+
--toggle-fg: var(--intent-tint-fg-pressed);
|
|
91
63
|
}
|
|
92
64
|
}
|
|
@@ -13,10 +13,10 @@ import type { ToggleGroupProps } from "./toggleGroup.types";
|
|
|
13
13
|
* `defaultValue`/`onValueChange` follow `type`: a string for single, a
|
|
14
14
|
* string array for multiple (`useControllableState`).
|
|
15
15
|
*
|
|
16
|
-
* Items
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
16
|
+
* Items look like Toggles but carry their own duplicated styling and
|
|
17
|
+
* `--toggle-group-item-*` tokens (toggle-group.recipe.css), and take
|
|
18
|
+
* the same `variant`/`intent`, with one difference: `size` excludes `"xl"`
|
|
19
|
+
* (too large for grouped toggles) — see
|
|
20
20
|
* `toggleGroup.types.ts`. `appearance` chooses `"segmented"` (items joined
|
|
21
21
|
* into one border) or `"separate"` (individually bordered, gapped) rendering.
|
|
22
22
|
*/
|