@marianmeres/stuic 3.167.0 → 3.169.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/AGENTS.md +6 -6
- package/dist/README.md +1 -1
- package/dist/attachments/index.d.ts +1 -0
- package/dist/attachments/index.js +1 -0
- package/dist/attachments/long-press.d.ts +43 -0
- package/dist/attachments/long-press.js +107 -0
- package/dist/components/Breadcrumbs/Breadcrumbs.svelte +198 -0
- package/dist/components/Breadcrumbs/Breadcrumbs.svelte.d.ts +60 -0
- package/dist/components/Breadcrumbs/README.md +174 -0
- package/dist/components/Breadcrumbs/i18n-sk.d.ts +17 -0
- package/dist/components/Breadcrumbs/i18n-sk.js +19 -0
- package/dist/components/Breadcrumbs/i18n.d.ts +31 -0
- package/dist/components/Breadcrumbs/i18n.js +40 -0
- package/dist/components/Breadcrumbs/index.css +84 -0
- package/dist/components/Breadcrumbs/index.d.ts +4 -0
- package/dist/components/Breadcrumbs/index.js +4 -0
- package/dist/components/Breadcrumbs/json-ld.d.ts +58 -0
- package/dist/components/Breadcrumbs/json-ld.js +56 -0
- package/dist/components/Collapsible/Collapsible.svelte +84 -15
- package/dist/components/Collapsible/Collapsible.svelte.d.ts +29 -2
- package/dist/components/Collapsible/README.md +82 -23
- package/dist/components/Collapsible/index.css +38 -0
- package/dist/components/Collapsible/index.d.ts +1 -1
- package/dist/components/ContextMenu/ContextMenu.svelte +302 -0
- package/dist/components/ContextMenu/ContextMenu.svelte.d.ts +94 -0
- package/dist/components/ContextMenu/README.md +159 -0
- package/dist/components/ContextMenu/i18n-sk.d.ts +17 -0
- package/dist/components/ContextMenu/i18n-sk.js +18 -0
- package/dist/components/ContextMenu/i18n.d.ts +30 -0
- package/dist/components/ContextMenu/i18n.js +38 -0
- package/dist/components/ContextMenu/index.css +26 -0
- package/dist/components/ContextMenu/index.d.ts +3 -0
- package/dist/components/ContextMenu/index.js +3 -0
- package/dist/components/CopyButton/CopyButton.svelte +278 -0
- package/dist/components/CopyButton/CopyButton.svelte.d.ts +76 -0
- package/dist/components/CopyButton/README.md +241 -0
- package/dist/components/CopyButton/i18n-sk.d.ts +17 -0
- package/dist/components/CopyButton/i18n-sk.js +20 -0
- package/dist/components/CopyButton/i18n.d.ts +35 -0
- package/dist/components/CopyButton/i18n.js +43 -0
- package/dist/components/CopyButton/index.css +67 -0
- package/dist/components/CopyButton/index.d.ts +3 -0
- package/dist/components/CopyButton/index.js +3 -0
- package/dist/components/DataTable/DataTable.svelte +41 -34
- package/dist/components/DataTable/README.md +45 -0
- package/dist/components/DataTable/i18n-sk.js +1 -0
- package/dist/components/DataTable/i18n.d.ts +1 -0
- package/dist/components/DataTable/i18n.js +3 -0
- package/dist/components/DataTable/index.css +41 -3
- package/dist/components/EmptyState/EmptyState.svelte +121 -0
- package/dist/components/EmptyState/EmptyState.svelte.d.ts +40 -0
- package/dist/components/EmptyState/README.md +188 -0
- package/dist/components/EmptyState/index.css +154 -0
- package/dist/components/EmptyState/index.d.ts +1 -0
- package/dist/components/EmptyState/index.js +1 -0
- package/dist/components/Input/FieldOptions.svelte +92 -2
- package/dist/components/Input/FieldOptions.svelte.d.ts +13 -0
- package/dist/components/Input/README.md +66 -31
- package/dist/components/Input/_internal/FieldLikeChips.svelte +286 -0
- package/dist/components/Input/_internal/FieldLikeChips.svelte.d.ts +65 -0
- package/dist/components/Input/index.css +58 -0
- package/dist/components/Pagination/Pagination.svelte +250 -0
- package/dist/components/Pagination/Pagination.svelte.d.ts +69 -0
- package/dist/components/Pagination/README.md +152 -0
- package/dist/components/Pagination/i18n-sk.d.ts +17 -0
- package/dist/components/Pagination/i18n-sk.js +24 -0
- package/dist/components/Pagination/i18n.d.ts +41 -0
- package/dist/components/Pagination/i18n.js +49 -0
- package/dist/components/Pagination/index.css +50 -0
- package/dist/components/Pagination/index.d.ts +4 -0
- package/dist/components/Pagination/index.js +4 -0
- package/dist/components/Pagination/pagination-range.d.ts +21 -0
- package/dist/components/Pagination/pagination-range.js +41 -0
- package/dist/components/Pill/Pill.svelte +8 -2
- package/dist/components/Pill/Pill.svelte.d.ts +2 -0
- package/dist/components/Pill/README.md +35 -22
- package/dist/components/Pill/index.css +57 -18
- package/dist/components/Stat/README.md +157 -0
- package/dist/components/Stat/Stat.svelte +226 -0
- package/dist/components/Stat/Stat.svelte.d.ts +54 -0
- package/dist/components/Stat/index.css +217 -0
- package/dist/components/Stat/index.d.ts +1 -0
- package/dist/components/Stat/index.js +1 -0
- package/dist/components/Stepper/README.md +181 -0
- package/dist/components/Stepper/Stepper.svelte +240 -0
- package/dist/components/Stepper/Stepper.svelte.d.ts +86 -0
- package/dist/components/Stepper/i18n-sk.d.ts +17 -0
- package/dist/components/Stepper/i18n-sk.js +21 -0
- package/dist/components/Stepper/i18n.d.ts +35 -0
- package/dist/components/Stepper/i18n.js +43 -0
- package/dist/components/Stepper/index.css +302 -0
- package/dist/components/Stepper/index.d.ts +3 -0
- package/dist/components/Stepper/index.js +3 -0
- package/dist/components/TrendChart/README.md +123 -0
- package/dist/components/TrendChart/TrendChart.svelte +114 -0
- package/dist/components/TrendChart/TrendChart.svelte.d.ts +36 -0
- package/dist/components/TrendChart/index.css +51 -0
- package/dist/components/TrendChart/index.d.ts +1 -0
- package/dist/components/TrendChart/index.js +1 -0
- package/dist/icons/index.d.ts +4 -0
- package/dist/icons/index.js +4 -0
- package/dist/index.css +7 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.js +12 -0
- package/dist/utils/copy-to-clipboard.d.ts +43 -0
- package/dist/utils/copy-to-clipboard.js +102 -0
- package/dist/utils/index.d.ts +1 -0
- package/dist/utils/index.js +1 -0
- package/docs/architecture.md +1 -1
- package/docs/domains/attachments.md +41 -4
- package/docs/domains/components.md +280 -13
- package/docs/domains/utils.md +8 -6
- package/docs/maybe-todo.md +132 -0
- package/docs/upgrading.md +6 -0
- package/package.json +11 -1
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
/* ============================================================================
|
|
2
|
+
STAT COMPONENT TOKENS
|
|
3
|
+
Override globally: :root { --stuic-stat-radius: 0; }
|
|
4
|
+
Override locally: <Stat style="--stuic-stat-value-font-size: 3rem;">
|
|
5
|
+
============================================================================ */
|
|
6
|
+
|
|
7
|
+
:root {
|
|
8
|
+
--stuic-stat-bg: var(--stuic-color-card, var(--stuic-color-background));
|
|
9
|
+
--stuic-stat-bg-hover: var(--stuic-color-card-hover, var(--stuic-color-muted));
|
|
10
|
+
--stuic-stat-border: var(--stuic-color-border);
|
|
11
|
+
--stuic-stat-border-hover: var(--stuic-color-border-hover);
|
|
12
|
+
|
|
13
|
+
/* Layout */
|
|
14
|
+
--stuic-stat-padding: 1rem;
|
|
15
|
+
--stuic-stat-gap: 0.375rem;
|
|
16
|
+
--stuic-stat-meta-gap: 0.5rem;
|
|
17
|
+
--stuic-stat-delta-gap: 0.25rem;
|
|
18
|
+
|
|
19
|
+
/* Label */
|
|
20
|
+
--stuic-stat-label-font-size: var(--text-sm);
|
|
21
|
+
--stuic-stat-label-font-weight: var(--font-weight-medium);
|
|
22
|
+
--stuic-stat-label-text: var(--stuic-color-muted-foreground);
|
|
23
|
+
|
|
24
|
+
/* Value */
|
|
25
|
+
--stuic-stat-value-font-size: var(--text-2xl);
|
|
26
|
+
--stuic-stat-value-font-weight: var(--font-weight-semibold);
|
|
27
|
+
--stuic-stat-value-line-height: 1.2;
|
|
28
|
+
--stuic-stat-value-text: var(--stuic-color-foreground);
|
|
29
|
+
|
|
30
|
+
/* Delta */
|
|
31
|
+
--stuic-stat-delta-font-size: var(--text-sm);
|
|
32
|
+
--stuic-stat-delta-font-weight: var(--font-weight-medium);
|
|
33
|
+
--stuic-stat-delta-text-success: var(--stuic-color-success);
|
|
34
|
+
--stuic-stat-delta-text-destructive: var(--stuic-color-destructive);
|
|
35
|
+
--stuic-stat-delta-text-warning: var(--stuic-color-warning);
|
|
36
|
+
--stuic-stat-delta-text-neutral: var(--stuic-color-muted-foreground);
|
|
37
|
+
|
|
38
|
+
/* Hint */
|
|
39
|
+
--stuic-stat-hint-font-size: var(--text-sm);
|
|
40
|
+
--stuic-stat-hint-text: var(--stuic-color-muted-foreground);
|
|
41
|
+
|
|
42
|
+
/* Icon */
|
|
43
|
+
--stuic-stat-icon-text: var(--stuic-color-muted-foreground);
|
|
44
|
+
|
|
45
|
+
/* Focus ring */
|
|
46
|
+
--stuic-stat-ring-width: 3px;
|
|
47
|
+
--stuic-stat-ring-color: var(--stuic-color-ring);
|
|
48
|
+
|
|
49
|
+
/* Disabled */
|
|
50
|
+
--stuic-stat-opacity-disabled: 0.5;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
@layer components {
|
|
54
|
+
/* ============================================================================
|
|
55
|
+
BASE STYLES
|
|
56
|
+
============================================================================ */
|
|
57
|
+
|
|
58
|
+
.stuic-stat {
|
|
59
|
+
display: flex;
|
|
60
|
+
flex-direction: column;
|
|
61
|
+
gap: var(--stuic-stat-gap);
|
|
62
|
+
width: 100%;
|
|
63
|
+
height: 100%;
|
|
64
|
+
padding: var(--stuic-stat-padding);
|
|
65
|
+
background: var(--stuic-stat-bg);
|
|
66
|
+
border: var(--stuic-stat-border-width, var(--stuic-border-width)) solid
|
|
67
|
+
var(--stuic-stat-border);
|
|
68
|
+
border-radius: var(--stuic-stat-radius, var(--stuic-radius-container));
|
|
69
|
+
box-shadow: var(--stuic-stat-shadow, var(--stuic-shadow));
|
|
70
|
+
transition:
|
|
71
|
+
background var(--stuic-stat-transition, var(--stuic-transition)),
|
|
72
|
+
border-color var(--stuic-stat-transition, var(--stuic-transition)),
|
|
73
|
+
box-shadow var(--stuic-stat-transition, var(--stuic-transition));
|
|
74
|
+
text-decoration: none;
|
|
75
|
+
color: inherit;
|
|
76
|
+
text-align: start;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/* Reset button styling */
|
|
80
|
+
button.stuic-stat {
|
|
81
|
+
font: inherit;
|
|
82
|
+
cursor: pointer;
|
|
83
|
+
-webkit-tap-highlight-color: transparent;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/* ============================================================================
|
|
87
|
+
INTERACTIVE HOVER (only when href or onclick)
|
|
88
|
+
============================================================================ */
|
|
89
|
+
|
|
90
|
+
.stuic-stat[data-interactive]:hover:not([data-disabled]) {
|
|
91
|
+
background: var(--stuic-stat-bg-hover);
|
|
92
|
+
border-color: var(--stuic-stat-border-hover);
|
|
93
|
+
box-shadow: var(--stuic-stat-shadow-hover, var(--stuic-shadow-hover));
|
|
94
|
+
cursor: pointer;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/* ============================================================================
|
|
98
|
+
FOCUS
|
|
99
|
+
============================================================================ */
|
|
100
|
+
|
|
101
|
+
.stuic-stat:focus-visible {
|
|
102
|
+
outline: var(--stuic-stat-ring-width) solid var(--stuic-stat-ring-color);
|
|
103
|
+
outline-offset: 0px;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/* ============================================================================
|
|
107
|
+
DISABLED
|
|
108
|
+
============================================================================ */
|
|
109
|
+
|
|
110
|
+
.stuic-stat[data-disabled] {
|
|
111
|
+
opacity: var(--stuic-stat-opacity-disabled);
|
|
112
|
+
cursor: not-allowed;
|
|
113
|
+
pointer-events: none;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/* ============================================================================
|
|
117
|
+
HEADER (label + icon)
|
|
118
|
+
============================================================================ */
|
|
119
|
+
|
|
120
|
+
.stuic-stat-header {
|
|
121
|
+
display: flex;
|
|
122
|
+
align-items: flex-start;
|
|
123
|
+
justify-content: space-between;
|
|
124
|
+
gap: 0.5rem;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
.stuic-stat-label {
|
|
128
|
+
font-size: var(--stuic-stat-label-font-size);
|
|
129
|
+
font-weight: var(--stuic-stat-label-font-weight);
|
|
130
|
+
color: var(--stuic-stat-label-text);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
.stuic-stat-icon {
|
|
134
|
+
color: var(--stuic-stat-icon-text);
|
|
135
|
+
flex-shrink: 0;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/* ============================================================================
|
|
139
|
+
VALUE
|
|
140
|
+
============================================================================ */
|
|
141
|
+
|
|
142
|
+
.stuic-stat-value {
|
|
143
|
+
font-size: var(--stuic-stat-value-font-size);
|
|
144
|
+
font-weight: var(--stuic-stat-value-font-weight);
|
|
145
|
+
line-height: var(--stuic-stat-value-line-height);
|
|
146
|
+
color: var(--stuic-stat-value-text);
|
|
147
|
+
font-variant-numeric: tabular-nums;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/* ============================================================================
|
|
151
|
+
META (delta + hint)
|
|
152
|
+
============================================================================ */
|
|
153
|
+
|
|
154
|
+
.stuic-stat-meta {
|
|
155
|
+
display: flex;
|
|
156
|
+
align-items: baseline;
|
|
157
|
+
flex-wrap: wrap;
|
|
158
|
+
gap: var(--stuic-stat-meta-gap);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/* Deliberately NOT a flex container. A flex box exports its FIRST item's
|
|
162
|
+
baseline, which here is the arrow — and a replaced <svg> has no text
|
|
163
|
+
baseline, so it synthesizes one from its bottom edge. Centered on the line
|
|
164
|
+
that edge sits ~0.2em below the delta text's baseline, and the row's
|
|
165
|
+
baseline alignment then pushed the hint down by that much. Inline layout
|
|
166
|
+
exports the text baseline, so delta text and hint sit on the same line. */
|
|
167
|
+
.stuic-stat-delta {
|
|
168
|
+
font-size: var(--stuic-stat-delta-font-size);
|
|
169
|
+
font-weight: var(--stuic-stat-delta-font-weight);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/* Delta color by resolved trend intent (root data attribute) */
|
|
173
|
+
.stuic-stat[data-trend-intent="success"] .stuic-stat-delta {
|
|
174
|
+
color: var(--stuic-stat-delta-text-success);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
.stuic-stat[data-trend-intent="destructive"] .stuic-stat-delta {
|
|
178
|
+
color: var(--stuic-stat-delta-text-destructive);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
.stuic-stat[data-trend-intent="warning"] .stuic-stat-delta {
|
|
182
|
+
color: var(--stuic-stat-delta-text-warning);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
.stuic-stat[data-trend-intent="neutral"] .stuic-stat-delta {
|
|
186
|
+
color: var(--stuic-stat-delta-text-neutral);
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
.stuic-stat-trend-arrow {
|
|
190
|
+
display: inline-block;
|
|
191
|
+
margin-inline-end: var(--stuic-stat-delta-gap);
|
|
192
|
+
/* Optical centering: a 1em icon reads as centered on the text when its
|
|
193
|
+
box hangs slightly below the baseline. */
|
|
194
|
+
vertical-align: -0.125em;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
/* display:block so the icon defines no inner line box — the wrapper's
|
|
198
|
+
baseline stays its bottom edge, which is what vertical-align shifts. */
|
|
199
|
+
.stuic-stat-trend-arrow svg {
|
|
200
|
+
display: block;
|
|
201
|
+
width: 1em;
|
|
202
|
+
height: 1em;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
.stuic-stat-hint {
|
|
206
|
+
font-size: var(--stuic-stat-hint-font-size);
|
|
207
|
+
color: var(--stuic-stat-hint-text);
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
/* ============================================================================
|
|
211
|
+
FOOTER
|
|
212
|
+
============================================================================ */
|
|
213
|
+
|
|
214
|
+
.stuic-stat-footer {
|
|
215
|
+
margin-top: auto;
|
|
216
|
+
}
|
|
217
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default as Stat, type Props as StatProps, type StatTrend, type StatTrendIntent, } from "./Stat.svelte";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default as Stat, } from "./Stat.svelte";
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
# Stepper
|
|
2
|
+
|
|
3
|
+
Numbered multi-step progress header for wizard/checkout flows. Renders the steps as
|
|
4
|
+
an accessible `<nav>` landmark with an ordered list: completed steps get a check
|
|
5
|
+
mark and filled connector, the current step is highlighted (and marked
|
|
6
|
+
`aria-current="step"`), upcoming steps stay muted. Display-only by default; pass
|
|
7
|
+
`onSelect` to make steps clickable (completed-only by default — the "go back and
|
|
8
|
+
revisit" wizard rule).
|
|
9
|
+
|
|
10
|
+
Pairs naturally with [`@marianmeres/wizard`](https://github.com/marianmeres/wizard)
|
|
11
|
+
— `current` is a zero-based index, exactly the wizard's `step.index` (see below).
|
|
12
|
+
|
|
13
|
+
## Props
|
|
14
|
+
|
|
15
|
+
| Prop | Type | Default | Description |
|
|
16
|
+
| ------------------ | -------------------------------- | -------------- | -------------------------------------------------------------------------------------------- |
|
|
17
|
+
| `steps` | `(StepperStep \| string)[]` | required | The steps; plain strings are shorthand for `{ label }` |
|
|
18
|
+
| `current` | `number` | `0` | Zero-based current step index; `steps.length` = all completed (wizard done); clamped |
|
|
19
|
+
| `onSelect` | `(index, step) => void` | - | Navigation callback; when present, steps render as buttons |
|
|
20
|
+
| `clickable` | `"none" \| "completed" \| "all"` | `"completed"` | Which steps may be clicked when `onSelect` is present (the current step never fires) |
|
|
21
|
+
| `orientation` | `"horizontal" \| "vertical"` | `"horizontal"` | Layout direction |
|
|
22
|
+
| `labelPosition` | `"end" \| "below"` | `"end"` | Horizontal only: labels beside the indicator, or centered below it (classic checkout header) |
|
|
23
|
+
| `disabled` | `boolean` | `false` | Disable all interaction (e.g. while a step is submitting) |
|
|
24
|
+
| `t` | `TranslateFn` | English | i18n translate function (see below) |
|
|
25
|
+
| `unstyled` | `boolean` | `false` | Skip all default styling |
|
|
26
|
+
| `class` | `string` | - | Additional CSS classes (merged via twMerge) |
|
|
27
|
+
| `classStep` | `string` | - | Class for every step item (`li`) |
|
|
28
|
+
| `classIndicator` | `string` | - | Class for the indicator bubble |
|
|
29
|
+
| `classLabel` | `string` | - | Class for the label |
|
|
30
|
+
| `classDescription` | `string` | - | Class for the description |
|
|
31
|
+
| `classConnector` | `string` | - | Class for the connector line between steps |
|
|
32
|
+
| `el` | `HTMLElement` | - | Element reference (bindable) |
|
|
33
|
+
|
|
34
|
+
### `StepperStep`
|
|
35
|
+
|
|
36
|
+
| Field | Type | Description |
|
|
37
|
+
| ------------- | --------- | ------------------------------------------------------------------------------------------------- |
|
|
38
|
+
| `label` | `THC` | Step label (text, html, or component — see `Thc`) |
|
|
39
|
+
| `description` | `THC` | Optional secondary line under the label |
|
|
40
|
+
| `icon` | `string` | Raw svg/html string rendered in the indicator instead of the number (completed/error markers win) |
|
|
41
|
+
| `error` | `boolean` | Marks the step as failed — destructive coloring + "×" indicator |
|
|
42
|
+
| `disabled` | `boolean` | Never clickable, regardless of the `clickable` policy |
|
|
43
|
+
|
|
44
|
+
## Snippet Props
|
|
45
|
+
|
|
46
|
+
| Snippet | Description |
|
|
47
|
+
| ----------------- | -------------------------------------------------------------- |
|
|
48
|
+
| `renderIndicator` | Override the bubble content; receives `{ index, step, state }` |
|
|
49
|
+
|
|
50
|
+
## Usage
|
|
51
|
+
|
|
52
|
+
### Basic
|
|
53
|
+
|
|
54
|
+
```svelte
|
|
55
|
+
<script lang="ts">
|
|
56
|
+
import { Stepper } from "@marianmeres/stuic";
|
|
57
|
+
|
|
58
|
+
let current = $state(1);
|
|
59
|
+
</script>
|
|
60
|
+
|
|
61
|
+
<Stepper steps={["Account", "Payment", "Confirm"]} {current} />
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
### Clickable (go back to a completed step)
|
|
65
|
+
|
|
66
|
+
```svelte
|
|
67
|
+
<Stepper
|
|
68
|
+
steps={["Account", "Payment", "Confirm"]}
|
|
69
|
+
{current}
|
|
70
|
+
onSelect={(index) => (current = index)}
|
|
71
|
+
/>
|
|
72
|
+
|
|
73
|
+
<!-- free navigation to any non-disabled step -->
|
|
74
|
+
<Stepper {steps} {current} clickable="all" onSelect={(index) => (current = index)} />
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
### Descriptions, error state
|
|
78
|
+
|
|
79
|
+
```svelte
|
|
80
|
+
<Stepper
|
|
81
|
+
current={2}
|
|
82
|
+
steps={[
|
|
83
|
+
{ label: "Account", description: "Your credentials" },
|
|
84
|
+
{ label: "Payment", description: "Card details", error: true },
|
|
85
|
+
{ label: "Confirm", description: "Review the order" },
|
|
86
|
+
]}
|
|
87
|
+
/>
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
### Vertical / labels below
|
|
91
|
+
|
|
92
|
+
```svelte
|
|
93
|
+
<Stepper {steps} {current} orientation="vertical" />
|
|
94
|
+
<Stepper {steps} {current} labelPosition="below" />
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
### With `@marianmeres/wizard`
|
|
98
|
+
|
|
99
|
+
`current` is a zero-based index, exactly the wizard's `step.index`:
|
|
100
|
+
|
|
101
|
+
```svelte
|
|
102
|
+
<script lang="ts">
|
|
103
|
+
import { Stepper } from "@marianmeres/stuic";
|
|
104
|
+
import { createWizard } from "@marianmeres/wizard";
|
|
105
|
+
|
|
106
|
+
const wizard = createWizard("checkout", {
|
|
107
|
+
steps: [{ label: "Account" }, { label: "Payment" }, { label: "Confirm" }],
|
|
108
|
+
});
|
|
109
|
+
</script>
|
|
110
|
+
|
|
111
|
+
<Stepper
|
|
112
|
+
steps={$wizard.steps.map((s) => ({ label: s.label, error: !!s.error }))}
|
|
113
|
+
current={$wizard.isDone ? $wizard.steps.length : $wizard.step.index}
|
|
114
|
+
/>
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
### i18n
|
|
118
|
+
|
|
119
|
+
Built-in English; bundled Slovak (`STEPPER_MESSAGES_SK`) is opt-in. Only the `<nav>`
|
|
120
|
+
landmark label and the screen-reader-only step announcements are localized — the
|
|
121
|
+
step labels are yours.
|
|
122
|
+
|
|
123
|
+
```svelte
|
|
124
|
+
<script lang="ts">
|
|
125
|
+
import { Stepper, createStepperT, STEPPER_MESSAGES_SK } from "@marianmeres/stuic";
|
|
126
|
+
const t = createStepperT(STEPPER_MESSAGES_SK);
|
|
127
|
+
</script>
|
|
128
|
+
|
|
129
|
+
<Stepper {steps} {current} {t} />
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
### Custom indicator
|
|
133
|
+
|
|
134
|
+
```svelte
|
|
135
|
+
<Stepper {steps} {current}>
|
|
136
|
+
{#snippet renderIndicator({ index, state })}
|
|
137
|
+
{state === "completed" ? "✓" : index + 1}
|
|
138
|
+
{/snippet}
|
|
139
|
+
</Stepper>
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
## Accessibility
|
|
143
|
+
|
|
144
|
+
- Renders a `<nav aria-label="Progress">` landmark (label localized via `t`) with an
|
|
145
|
+
ordered list; the current step is marked `aria-current="step"`.
|
|
146
|
+
- The indicator bubble is `aria-hidden` — a visually hidden text announces
|
|
147
|
+
"Step X of Y" plus the completed/failed state instead, so icon-only information is
|
|
148
|
+
never lost to screen readers.
|
|
149
|
+
- Clickable steps are real `<button>`s; ineligible ones are disabled.
|
|
150
|
+
|
|
151
|
+
## CSS Variables
|
|
152
|
+
|
|
153
|
+
| Variable | Default | Description |
|
|
154
|
+
| ---------------------------------------------------------------------- | ---------------------------------- | ---------------------------------------- |
|
|
155
|
+
| `--stuic-stepper-gap` | `0.75rem` | Indicator↔label gap + step↔connector gap |
|
|
156
|
+
| `--stuic-stepper-gap-vertical` | `1.75rem` | Vertical: distance between steps |
|
|
157
|
+
| `--stuic-stepper-connector-min-length` | `1.5rem` | Horizontal: shortest connector |
|
|
158
|
+
| `--stuic-stepper-indicator-size` | `2rem` | Bubble diameter |
|
|
159
|
+
| `--stuic-stepper-indicator-border-width` | `2px` | Bubble border width |
|
|
160
|
+
| `--stuic-stepper-indicator-radius` | full circle | Bubble radius |
|
|
161
|
+
| `--stuic-stepper-indicator-font-size` | `--text-sm` | Bubble number font size |
|
|
162
|
+
| `--stuic-stepper-indicator-bg` / `-text` / `-border` | upcoming colors | Bubble, upcoming state |
|
|
163
|
+
| `--stuic-stepper-indicator-bg-current` / `-text-current` / … | `--stuic-color-primary` accents | Bubble, current state |
|
|
164
|
+
| `--stuic-stepper-indicator-bg-completed` / `-text-completed` / … | `--stuic-color-primary` filled | Bubble, completed state |
|
|
165
|
+
| `--stuic-stepper-indicator-bg-error` / `-text-error` / `-border-error` | `--stuic-color-destructive` filled | Bubble, error state |
|
|
166
|
+
| `--stuic-stepper-label-font-size` | `--text-base` | Label font size |
|
|
167
|
+
| `--stuic-stepper-label-text` / `-text-current` / `-text-completed` / … | muted / foreground / destructive | Label color per state |
|
|
168
|
+
| `--stuic-stepper-description-font-size` | `--text-sm` | Description font size |
|
|
169
|
+
| `--stuic-stepper-description-text` | `--stuic-color-muted-foreground` | Description color |
|
|
170
|
+
| `--stuic-stepper-connector-thickness` | `2px` | Connector line thickness |
|
|
171
|
+
| `--stuic-stepper-connector-bg` / `-bg-completed` | border / primary | Connector color |
|
|
172
|
+
| `--stuic-stepper-ring-width` / `--stuic-stepper-ring-color` | `3px` / `--stuic-color-ring` | Focus ring of clickable steps |
|
|
173
|
+
| `--stuic-stepper-opacity-disabled` | `0.5` | Disabled opacity |
|
|
174
|
+
|
|
175
|
+
## Data Attributes
|
|
176
|
+
|
|
177
|
+
- `data-orientation` - `"horizontal" | "vertical"` (root)
|
|
178
|
+
- `data-label-position` - `"end" | "below"` (root, horizontal only)
|
|
179
|
+
- `data-disabled` - empty attribute (root when `disabled`; step `li` when `step.disabled`)
|
|
180
|
+
- `data-state` - `"completed" | "current" | "upcoming"` (step `li`); `"completed"` on filled connectors
|
|
181
|
+
- `data-error` - empty attribute (step `li` when `step.error`)
|
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
<script lang="ts" module>
|
|
2
|
+
import type { HTMLAttributes } from "svelte/elements";
|
|
3
|
+
import type { Snippet } from "svelte";
|
|
4
|
+
import type { THC } from "../Thc/Thc.svelte";
|
|
5
|
+
import type { TranslateFn } from "../../types.js";
|
|
6
|
+
|
|
7
|
+
export type StepperOrientation = "horizontal" | "vertical";
|
|
8
|
+
|
|
9
|
+
export type StepperLabelPosition = "end" | "below";
|
|
10
|
+
|
|
11
|
+
export type StepperStepState = "completed" | "current" | "upcoming";
|
|
12
|
+
|
|
13
|
+
export type StepperClickable = "none" | "completed" | "all";
|
|
14
|
+
|
|
15
|
+
export interface StepperStep {
|
|
16
|
+
/** Step label */
|
|
17
|
+
label: THC;
|
|
18
|
+
/** Optional secondary line under the label */
|
|
19
|
+
description?: THC;
|
|
20
|
+
/**
|
|
21
|
+
* Raw svg/html string rendered inside the indicator instead of the step number
|
|
22
|
+
* (e.g. an `@marianmeres/icons-fns` render result). Completed/error markers
|
|
23
|
+
* still take precedence.
|
|
24
|
+
*/
|
|
25
|
+
icon?: string;
|
|
26
|
+
/** Marks the step as failed — destructive coloring + "×" indicator */
|
|
27
|
+
error?: boolean;
|
|
28
|
+
/** Never clickable, regardless of the `clickable` policy */
|
|
29
|
+
disabled?: boolean;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export interface Props extends Omit<HTMLAttributes<HTMLElement>, "children"> {
|
|
33
|
+
/** The steps to render. Plain strings are shorthand for `{ label }`. */
|
|
34
|
+
steps: (StepperStep | string)[];
|
|
35
|
+
/**
|
|
36
|
+
* Zero-based index of the current step (default 0) — aligns with
|
|
37
|
+
* `@marianmeres/wizard`'s `step.index`. Steps before it render as completed,
|
|
38
|
+
* steps after it as upcoming. Pass `steps.length` to mark every step
|
|
39
|
+
* completed (wizard done). Out-of-range values are clamped.
|
|
40
|
+
*/
|
|
41
|
+
current?: number;
|
|
42
|
+
/**
|
|
43
|
+
* Navigation callback. When provided (and `clickable` is not "none"), steps
|
|
44
|
+
* render as buttons and eligible ones fire this on click. The current step
|
|
45
|
+
* never fires.
|
|
46
|
+
*/
|
|
47
|
+
onSelect?: (index: number, step: StepperStep) => void;
|
|
48
|
+
/**
|
|
49
|
+
* Which steps may be clicked when `onSelect` is present. `"completed"`
|
|
50
|
+
* (default): only completed steps — the "go back and revisit" wizard rule.
|
|
51
|
+
* `"all"`: any non-disabled step. `"none"`: display-only.
|
|
52
|
+
*/
|
|
53
|
+
clickable?: StepperClickable;
|
|
54
|
+
/** Layout direction (default "horizontal") */
|
|
55
|
+
orientation?: StepperOrientation;
|
|
56
|
+
/**
|
|
57
|
+
* Horizontal orientation only: labels to the right of the indicator
|
|
58
|
+
* (`"end"`, default) or centered below it (`"below"` — the classic
|
|
59
|
+
* checkout header).
|
|
60
|
+
*/
|
|
61
|
+
labelPosition?: StepperLabelPosition;
|
|
62
|
+
/** Disable all interaction (e.g. while a step is submitting) */
|
|
63
|
+
disabled?: boolean;
|
|
64
|
+
/** i18n translate function (see `createStepperT`) */
|
|
65
|
+
t?: TranslateFn;
|
|
66
|
+
/** Override the indicator bubble content */
|
|
67
|
+
renderIndicator?: Snippet<
|
|
68
|
+
[{ index: number; step: StepperStep; state: StepperStepState }]
|
|
69
|
+
>;
|
|
70
|
+
/** Skip all default styling */
|
|
71
|
+
unstyled?: boolean;
|
|
72
|
+
/** Additional CSS classes */
|
|
73
|
+
class?: string;
|
|
74
|
+
/** Class for every step item (`li`) */
|
|
75
|
+
classStep?: string;
|
|
76
|
+
/** Class for the indicator bubble */
|
|
77
|
+
classIndicator?: string;
|
|
78
|
+
/** Class for the label */
|
|
79
|
+
classLabel?: string;
|
|
80
|
+
/** Class for the description */
|
|
81
|
+
classDescription?: string;
|
|
82
|
+
/** Class for the connector line between steps */
|
|
83
|
+
classConnector?: string;
|
|
84
|
+
/** Bindable element reference */
|
|
85
|
+
el?: HTMLElement;
|
|
86
|
+
}
|
|
87
|
+
</script>
|
|
88
|
+
|
|
89
|
+
<script lang="ts">
|
|
90
|
+
import { twMerge } from "../../utils/tw-merge.js";
|
|
91
|
+
import Thc from "../Thc/Thc.svelte";
|
|
92
|
+
import { iconCheck, iconX } from "../../icons/index.js";
|
|
93
|
+
import { t_default } from "./i18n.js";
|
|
94
|
+
|
|
95
|
+
let {
|
|
96
|
+
steps,
|
|
97
|
+
current = 0,
|
|
98
|
+
onSelect,
|
|
99
|
+
clickable = "completed",
|
|
100
|
+
orientation = "horizontal",
|
|
101
|
+
labelPosition = "end",
|
|
102
|
+
disabled = false,
|
|
103
|
+
t = t_default,
|
|
104
|
+
renderIndicator,
|
|
105
|
+
unstyled = false,
|
|
106
|
+
class: classProp,
|
|
107
|
+
classStep: classStepProp,
|
|
108
|
+
classIndicator: classIndicatorProp,
|
|
109
|
+
classLabel: classLabelProp,
|
|
110
|
+
classDescription: classDescriptionProp,
|
|
111
|
+
classConnector: classConnectorProp,
|
|
112
|
+
el = $bindable(),
|
|
113
|
+
...rest
|
|
114
|
+
}: Props = $props();
|
|
115
|
+
|
|
116
|
+
let _steps: StepperStep[] = $derived(
|
|
117
|
+
steps.map((s) => (typeof s === "string" ? { label: s } : s))
|
|
118
|
+
);
|
|
119
|
+
|
|
120
|
+
// clamped to [0, steps.length] — the extra slot means "all completed"
|
|
121
|
+
let _current = $derived(Math.min(Math.max(0, Math.floor(current)), _steps.length));
|
|
122
|
+
|
|
123
|
+
let _interactive = $derived(!!onSelect && clickable !== "none");
|
|
124
|
+
|
|
125
|
+
let _class = $derived(unstyled ? classProp : twMerge("stuic-stepper", classProp));
|
|
126
|
+
let _classStep = $derived(
|
|
127
|
+
unstyled ? classStepProp : twMerge("stuic-stepper-item", classStepProp)
|
|
128
|
+
);
|
|
129
|
+
let _classIndicator = $derived(
|
|
130
|
+
unstyled ? classIndicatorProp : twMerge("stuic-stepper-indicator", classIndicatorProp)
|
|
131
|
+
);
|
|
132
|
+
let _classLabel = $derived(
|
|
133
|
+
unstyled ? classLabelProp : twMerge("stuic-stepper-label", classLabelProp)
|
|
134
|
+
);
|
|
135
|
+
let _classDescription = $derived(
|
|
136
|
+
unstyled
|
|
137
|
+
? classDescriptionProp
|
|
138
|
+
: twMerge("stuic-stepper-description", classDescriptionProp)
|
|
139
|
+
);
|
|
140
|
+
let _classConnector = $derived(
|
|
141
|
+
unstyled ? classConnectorProp : twMerge("stuic-stepper-connector", classConnectorProp)
|
|
142
|
+
);
|
|
143
|
+
|
|
144
|
+
function stateOf(i: number): StepperStepState {
|
|
145
|
+
if (i < _current) return "completed";
|
|
146
|
+
if (i === _current) return "current";
|
|
147
|
+
return "upcoming";
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
function isEligible(step: StepperStep, state: StepperStepState): boolean {
|
|
151
|
+
if (disabled || step.disabled || state === "current") return false;
|
|
152
|
+
return clickable === "all" || state === "completed";
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
function select(i: number) {
|
|
156
|
+
const step = _steps[i];
|
|
157
|
+
if (!_interactive || !isEligible(step, stateOf(i))) return;
|
|
158
|
+
onSelect?.(i, step);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// icon-only state info for screen readers ("Step 2 of 4, Completed")
|
|
162
|
+
function srText(i: number, step: StepperStep, state: StepperStepState): string {
|
|
163
|
+
const parts = [t("step_x_of_y", { step: i + 1, stepCount: _steps.length })];
|
|
164
|
+
if (step.error) parts.push(t("failed"));
|
|
165
|
+
else if (state === "completed") parts.push(t("completed"));
|
|
166
|
+
return parts.join(", ");
|
|
167
|
+
}
|
|
168
|
+
</script>
|
|
169
|
+
|
|
170
|
+
{#snippet stepInner(i: number, step: StepperStep, state: StepperStepState)}
|
|
171
|
+
<span class={_classIndicator} aria-hidden="true">
|
|
172
|
+
{#if renderIndicator}
|
|
173
|
+
{@render renderIndicator({ index: i, step, state })}
|
|
174
|
+
{:else if step.error}
|
|
175
|
+
{@html iconX()}
|
|
176
|
+
{:else if state === "completed"}
|
|
177
|
+
{@html iconCheck()}
|
|
178
|
+
{:else if step.icon}
|
|
179
|
+
{@html step.icon}
|
|
180
|
+
{:else}
|
|
181
|
+
{i + 1}
|
|
182
|
+
{/if}
|
|
183
|
+
</span>
|
|
184
|
+
<span class="sr-only">{srText(i, step, state)}</span>
|
|
185
|
+
<span class={unstyled ? undefined : "stuic-stepper-text"}>
|
|
186
|
+
<span class={_classLabel}><Thc thc={step.label} /></span>
|
|
187
|
+
{#if step.description}
|
|
188
|
+
<span class={_classDescription}><Thc thc={step.description} /></span>
|
|
189
|
+
{/if}
|
|
190
|
+
</span>
|
|
191
|
+
{/snippet}
|
|
192
|
+
|
|
193
|
+
{#if _steps.length}
|
|
194
|
+
<nav
|
|
195
|
+
bind:this={el}
|
|
196
|
+
class={_class}
|
|
197
|
+
data-orientation={!unstyled ? orientation : undefined}
|
|
198
|
+
data-label-position={!unstyled && orientation === "horizontal"
|
|
199
|
+
? labelPosition
|
|
200
|
+
: undefined}
|
|
201
|
+
data-disabled={!unstyled && disabled ? "" : undefined}
|
|
202
|
+
aria-label={t("stepper", null, "Progress")}
|
|
203
|
+
{...rest}
|
|
204
|
+
>
|
|
205
|
+
<ol class={unstyled ? undefined : "stuic-stepper-list"}>
|
|
206
|
+
{#each _steps as step, i (i)}
|
|
207
|
+
{@const state = stateOf(i)}
|
|
208
|
+
<li
|
|
209
|
+
class={_classStep}
|
|
210
|
+
data-state={!unstyled ? state : undefined}
|
|
211
|
+
data-error={!unstyled && step.error ? "" : undefined}
|
|
212
|
+
data-disabled={!unstyled && step.disabled ? "" : undefined}
|
|
213
|
+
aria-current={state === "current" ? "step" : undefined}
|
|
214
|
+
>
|
|
215
|
+
{#if _interactive}
|
|
216
|
+
<button
|
|
217
|
+
type="button"
|
|
218
|
+
class={unstyled ? undefined : "stuic-stepper-trigger"}
|
|
219
|
+
disabled={!isEligible(step, state)}
|
|
220
|
+
onclick={() => select(i)}
|
|
221
|
+
>
|
|
222
|
+
{@render stepInner(i, step, state)}
|
|
223
|
+
</button>
|
|
224
|
+
{:else}
|
|
225
|
+
<div class={unstyled ? undefined : "stuic-stepper-trigger"}>
|
|
226
|
+
{@render stepInner(i, step, state)}
|
|
227
|
+
</div>
|
|
228
|
+
{/if}
|
|
229
|
+
{#if i < _steps.length - 1}
|
|
230
|
+
<span
|
|
231
|
+
class={_classConnector}
|
|
232
|
+
data-state={!unstyled && state === "completed" ? "completed" : undefined}
|
|
233
|
+
aria-hidden="true"
|
|
234
|
+
></span>
|
|
235
|
+
{/if}
|
|
236
|
+
</li>
|
|
237
|
+
{/each}
|
|
238
|
+
</ol>
|
|
239
|
+
</nav>
|
|
240
|
+
{/if}
|