@sixthshift/design-system 0.7.0 → 0.8.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/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/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/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/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/package.json +21 -1
- 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/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/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/Steps/Steps.tsx +149 -0
- package/src/components/Steps/index.ts +1 -0
- package/src/components/Steps/steps.recipe.css +51 -0
- package/src/theming/tailwind.css +5 -0
|
@@ -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
|
+
}
|
package/src/theming/tailwind.css
CHANGED
|
@@ -41,6 +41,11 @@
|
|
|
41
41
|
@import "../components/Sheet/sheet.recipe.css";
|
|
42
42
|
@import "../components/Select/select.recipe.css";
|
|
43
43
|
@import "../components/TagChip/tag-chip.recipe.css";
|
|
44
|
+
@import "../components/NumberStepper/number-stepper.recipe.css";
|
|
45
|
+
@import "../components/Steps/steps.recipe.css";
|
|
46
|
+
@import "../components/ImageGallery/image-gallery.recipe.css";
|
|
47
|
+
@import "../components/DataTable/data-table.recipe.css";
|
|
48
|
+
@import "../components/FileDropzone/file-dropzone.recipe.css";
|
|
44
49
|
|
|
45
50
|
/* =============================================================================
|
|
46
51
|
3. TAILWIND THEME
|