@marianmeres/stuic 3.167.0 → 3.168.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/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/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/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 +111 -0
- package/package.json +11 -1
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
<script lang="ts" module>
|
|
2
|
+
import type { HTMLAttributes, HTMLAnchorAttributes } from "svelte/elements";
|
|
3
|
+
import type { Snippet } from "svelte";
|
|
4
|
+
import type { THC } from "../Thc/Thc.svelte";
|
|
5
|
+
|
|
6
|
+
export type StatTrend = "up" | "down" | "flat";
|
|
7
|
+
|
|
8
|
+
export type StatTrendIntent =
|
|
9
|
+
"auto" | "success" | "destructive" | "warning" | "neutral";
|
|
10
|
+
|
|
11
|
+
export interface Props extends Omit<
|
|
12
|
+
HTMLAttributes<HTMLDivElement>,
|
|
13
|
+
"children" | "title"
|
|
14
|
+
> {
|
|
15
|
+
/** Small label above the value (metric name, e.g. "Revenue") */
|
|
16
|
+
label?: THC;
|
|
17
|
+
/** The primary metric value, preformatted (e.g. "$45,231.89") */
|
|
18
|
+
value?: THC | number;
|
|
19
|
+
/** Delta/change indicator, preformatted (e.g. "+20.1%") */
|
|
20
|
+
delta?: THC | number;
|
|
21
|
+
/** Trend direction — drives the arrow and (via `trendIntent="auto"`) the delta color */
|
|
22
|
+
trend?: StatTrend;
|
|
23
|
+
/**
|
|
24
|
+
* Delta color semantics. "auto" (default) maps up→success, down→destructive,
|
|
25
|
+
* flat→neutral. Set explicitly for metrics where down is good (churn, costs):
|
|
26
|
+
* e.g. `trend="down" trendIntent="success"`.
|
|
27
|
+
*/
|
|
28
|
+
trendIntent?: StatTrendIntent;
|
|
29
|
+
/** Show the trend direction arrow next to the delta */
|
|
30
|
+
showTrendArrow?: boolean;
|
|
31
|
+
/** Secondary text next to the delta (e.g. "vs. last month") */
|
|
32
|
+
hint?: THC;
|
|
33
|
+
/** When provided, the stat renders as <a> */
|
|
34
|
+
href?: string;
|
|
35
|
+
/** Disabled state */
|
|
36
|
+
disabled?: boolean;
|
|
37
|
+
/** Override the entire stat body */
|
|
38
|
+
children?: Snippet;
|
|
39
|
+
/** Icon area in the top-right corner (next to the label) */
|
|
40
|
+
renderIcon?: Snippet;
|
|
41
|
+
/** Footer area (sparkline, actions, metadata, etc.) */
|
|
42
|
+
renderFooter?: Snippet;
|
|
43
|
+
/** Skip all default styling */
|
|
44
|
+
unstyled?: boolean;
|
|
45
|
+
/** Additional CSS classes */
|
|
46
|
+
class?: string;
|
|
47
|
+
/** Class for the label */
|
|
48
|
+
classLabel?: string;
|
|
49
|
+
/** Class for the value */
|
|
50
|
+
classValue?: string;
|
|
51
|
+
/** Class for the delta */
|
|
52
|
+
classDelta?: string;
|
|
53
|
+
/** Class for the hint */
|
|
54
|
+
classHint?: string;
|
|
55
|
+
/** Class for the footer area */
|
|
56
|
+
classFooter?: string;
|
|
57
|
+
/** Bindable element reference */
|
|
58
|
+
el?: HTMLElement;
|
|
59
|
+
}
|
|
60
|
+
</script>
|
|
61
|
+
|
|
62
|
+
<script lang="ts">
|
|
63
|
+
import { twMerge } from "../../utils/tw-merge.js";
|
|
64
|
+
import Thc from "../Thc/Thc.svelte";
|
|
65
|
+
import { iconTrendingUp, iconTrendingDown, iconMinus } from "../../icons/index.js";
|
|
66
|
+
|
|
67
|
+
let {
|
|
68
|
+
label,
|
|
69
|
+
value,
|
|
70
|
+
delta,
|
|
71
|
+
trend,
|
|
72
|
+
trendIntent = "auto",
|
|
73
|
+
showTrendArrow = true,
|
|
74
|
+
hint,
|
|
75
|
+
href,
|
|
76
|
+
disabled = false,
|
|
77
|
+
children,
|
|
78
|
+
renderIcon,
|
|
79
|
+
renderFooter,
|
|
80
|
+
unstyled = false,
|
|
81
|
+
class: classProp,
|
|
82
|
+
classLabel: classLabelProp,
|
|
83
|
+
classValue: classValueProp,
|
|
84
|
+
classDelta: classDeltaProp,
|
|
85
|
+
classHint: classHintProp,
|
|
86
|
+
classFooter: classFooterProp,
|
|
87
|
+
el = $bindable(),
|
|
88
|
+
onclick,
|
|
89
|
+
...rest
|
|
90
|
+
}: Props = $props();
|
|
91
|
+
|
|
92
|
+
const TREND_ICONS: Record<StatTrend, () => string> = {
|
|
93
|
+
up: () => iconTrendingUp(),
|
|
94
|
+
down: () => iconTrendingDown(),
|
|
95
|
+
flat: () => iconMinus(),
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
let _value = $derived(typeof value === "number" ? String(value) : value);
|
|
99
|
+
let _delta = $derived(typeof delta === "number" ? String(delta) : delta);
|
|
100
|
+
|
|
101
|
+
let _resolvedTrendIntent = $derived.by(() => {
|
|
102
|
+
if (trendIntent !== "auto") return trendIntent;
|
|
103
|
+
if (trend === "up") return "success";
|
|
104
|
+
if (trend === "down") return "destructive";
|
|
105
|
+
return "neutral";
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
let _showArrow = $derived(showTrendArrow && !!trend);
|
|
109
|
+
let _hasDelta = $derived((delta !== undefined && delta !== null) || _showArrow);
|
|
110
|
+
let _hasHeader = $derived(!!(label || renderIcon));
|
|
111
|
+
let _hasMeta = $derived(_hasDelta || !!hint);
|
|
112
|
+
|
|
113
|
+
let _class = $derived(unstyled ? classProp : twMerge("stuic-stat", classProp));
|
|
114
|
+
let _classLabel = $derived(
|
|
115
|
+
unstyled ? classLabelProp : twMerge("stuic-stat-label", classLabelProp)
|
|
116
|
+
);
|
|
117
|
+
let _classValue = $derived(
|
|
118
|
+
unstyled ? classValueProp : twMerge("stuic-stat-value", classValueProp)
|
|
119
|
+
);
|
|
120
|
+
let _classDelta = $derived(
|
|
121
|
+
unstyled ? classDeltaProp : twMerge("stuic-stat-delta", classDeltaProp)
|
|
122
|
+
);
|
|
123
|
+
let _classHint = $derived(
|
|
124
|
+
unstyled ? classHintProp : twMerge("stuic-stat-hint", classHintProp)
|
|
125
|
+
);
|
|
126
|
+
let _classFooter = $derived(
|
|
127
|
+
unstyled ? classFooterProp : twMerge("stuic-stat-footer", classFooterProp)
|
|
128
|
+
);
|
|
129
|
+
</script>
|
|
130
|
+
|
|
131
|
+
{#snippet statInner()}
|
|
132
|
+
{#if children}
|
|
133
|
+
{@render children()}
|
|
134
|
+
{:else}
|
|
135
|
+
{#if _hasHeader}
|
|
136
|
+
<div class={unstyled ? undefined : "stuic-stat-header"}>
|
|
137
|
+
{#if label}
|
|
138
|
+
<div class={_classLabel}>
|
|
139
|
+
<Thc thc={label} />
|
|
140
|
+
</div>
|
|
141
|
+
{/if}
|
|
142
|
+
{#if renderIcon}
|
|
143
|
+
<div class={unstyled ? undefined : "stuic-stat-icon"}>
|
|
144
|
+
{@render renderIcon()}
|
|
145
|
+
</div>
|
|
146
|
+
{/if}
|
|
147
|
+
</div>
|
|
148
|
+
{/if}
|
|
149
|
+
{#if _value !== undefined && _value !== null}
|
|
150
|
+
<div class={_classValue}>
|
|
151
|
+
<Thc thc={_value} />
|
|
152
|
+
</div>
|
|
153
|
+
{/if}
|
|
154
|
+
{#if _hasMeta}
|
|
155
|
+
<div class={unstyled ? undefined : "stuic-stat-meta"}>
|
|
156
|
+
{#if _hasDelta}
|
|
157
|
+
<span class={_classDelta}>
|
|
158
|
+
{#if _showArrow && trend}
|
|
159
|
+
<span
|
|
160
|
+
class={unstyled ? undefined : "stuic-stat-trend-arrow"}
|
|
161
|
+
aria-hidden="true"
|
|
162
|
+
>
|
|
163
|
+
{@html TREND_ICONS[trend]()}
|
|
164
|
+
</span>
|
|
165
|
+
{/if}
|
|
166
|
+
{#if _delta !== undefined && _delta !== null}
|
|
167
|
+
<Thc thc={_delta} />
|
|
168
|
+
{/if}
|
|
169
|
+
</span>
|
|
170
|
+
{/if}
|
|
171
|
+
{#if hint}
|
|
172
|
+
<span class={_classHint}>
|
|
173
|
+
<Thc thc={hint} />
|
|
174
|
+
</span>
|
|
175
|
+
{/if}
|
|
176
|
+
</div>
|
|
177
|
+
{/if}
|
|
178
|
+
{#if renderFooter}
|
|
179
|
+
<div class={_classFooter}>
|
|
180
|
+
{@render renderFooter()}
|
|
181
|
+
</div>
|
|
182
|
+
{/if}
|
|
183
|
+
{/if}
|
|
184
|
+
{/snippet}
|
|
185
|
+
|
|
186
|
+
{#if href}
|
|
187
|
+
<a
|
|
188
|
+
{href}
|
|
189
|
+
bind:this={el}
|
|
190
|
+
class={_class}
|
|
191
|
+
data-trend={!unstyled && trend ? trend : undefined}
|
|
192
|
+
data-trend-intent={!unstyled && _hasDelta ? _resolvedTrendIntent : undefined}
|
|
193
|
+
data-interactive={!unstyled ? "" : undefined}
|
|
194
|
+
data-disabled={!unstyled && disabled ? "" : undefined}
|
|
195
|
+
aria-disabled={disabled ? "true" : undefined}
|
|
196
|
+
{...rest as HTMLAnchorAttributes}
|
|
197
|
+
>
|
|
198
|
+
{@render statInner()}
|
|
199
|
+
</a>
|
|
200
|
+
{:else if onclick}
|
|
201
|
+
<button
|
|
202
|
+
type="button"
|
|
203
|
+
bind:this={el}
|
|
204
|
+
class={_class}
|
|
205
|
+
data-trend={!unstyled && trend ? trend : undefined}
|
|
206
|
+
data-trend-intent={!unstyled && _hasDelta ? _resolvedTrendIntent : undefined}
|
|
207
|
+
data-interactive={!unstyled ? "" : undefined}
|
|
208
|
+
data-disabled={!unstyled && disabled ? "" : undefined}
|
|
209
|
+
{disabled}
|
|
210
|
+
{onclick}
|
|
211
|
+
{...rest as any}
|
|
212
|
+
>
|
|
213
|
+
{@render statInner()}
|
|
214
|
+
</button>
|
|
215
|
+
{:else}
|
|
216
|
+
<div
|
|
217
|
+
bind:this={el}
|
|
218
|
+
class={_class}
|
|
219
|
+
data-trend={!unstyled && trend ? trend : undefined}
|
|
220
|
+
data-trend-intent={!unstyled && _hasDelta ? _resolvedTrendIntent : undefined}
|
|
221
|
+
data-disabled={!unstyled && disabled ? "" : undefined}
|
|
222
|
+
{...rest}
|
|
223
|
+
>
|
|
224
|
+
{@render statInner()}
|
|
225
|
+
</div>
|
|
226
|
+
{/if}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import type { HTMLAttributes } from "svelte/elements";
|
|
2
|
+
import type { Snippet } from "svelte";
|
|
3
|
+
import type { THC } from "../Thc/Thc.svelte";
|
|
4
|
+
export type StatTrend = "up" | "down" | "flat";
|
|
5
|
+
export type StatTrendIntent = "auto" | "success" | "destructive" | "warning" | "neutral";
|
|
6
|
+
export interface Props extends Omit<HTMLAttributes<HTMLDivElement>, "children" | "title"> {
|
|
7
|
+
/** Small label above the value (metric name, e.g. "Revenue") */
|
|
8
|
+
label?: THC;
|
|
9
|
+
/** The primary metric value, preformatted (e.g. "$45,231.89") */
|
|
10
|
+
value?: THC | number;
|
|
11
|
+
/** Delta/change indicator, preformatted (e.g. "+20.1%") */
|
|
12
|
+
delta?: THC | number;
|
|
13
|
+
/** Trend direction — drives the arrow and (via `trendIntent="auto"`) the delta color */
|
|
14
|
+
trend?: StatTrend;
|
|
15
|
+
/**
|
|
16
|
+
* Delta color semantics. "auto" (default) maps up→success, down→destructive,
|
|
17
|
+
* flat→neutral. Set explicitly for metrics where down is good (churn, costs):
|
|
18
|
+
* e.g. `trend="down" trendIntent="success"`.
|
|
19
|
+
*/
|
|
20
|
+
trendIntent?: StatTrendIntent;
|
|
21
|
+
/** Show the trend direction arrow next to the delta */
|
|
22
|
+
showTrendArrow?: boolean;
|
|
23
|
+
/** Secondary text next to the delta (e.g. "vs. last month") */
|
|
24
|
+
hint?: THC;
|
|
25
|
+
/** When provided, the stat renders as <a> */
|
|
26
|
+
href?: string;
|
|
27
|
+
/** Disabled state */
|
|
28
|
+
disabled?: boolean;
|
|
29
|
+
/** Override the entire stat body */
|
|
30
|
+
children?: Snippet;
|
|
31
|
+
/** Icon area in the top-right corner (next to the label) */
|
|
32
|
+
renderIcon?: Snippet;
|
|
33
|
+
/** Footer area (sparkline, actions, metadata, etc.) */
|
|
34
|
+
renderFooter?: Snippet;
|
|
35
|
+
/** Skip all default styling */
|
|
36
|
+
unstyled?: boolean;
|
|
37
|
+
/** Additional CSS classes */
|
|
38
|
+
class?: string;
|
|
39
|
+
/** Class for the label */
|
|
40
|
+
classLabel?: string;
|
|
41
|
+
/** Class for the value */
|
|
42
|
+
classValue?: string;
|
|
43
|
+
/** Class for the delta */
|
|
44
|
+
classDelta?: string;
|
|
45
|
+
/** Class for the hint */
|
|
46
|
+
classHint?: string;
|
|
47
|
+
/** Class for the footer area */
|
|
48
|
+
classFooter?: string;
|
|
49
|
+
/** Bindable element reference */
|
|
50
|
+
el?: HTMLElement;
|
|
51
|
+
}
|
|
52
|
+
declare const Stat: import("svelte").Component<Props, {}, "el">;
|
|
53
|
+
type Stat = ReturnType<typeof Stat>;
|
|
54
|
+
export default Stat;
|
|
@@ -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`)
|