@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.
Files changed (103) hide show
  1. package/AGENTS.md +6 -6
  2. package/dist/attachments/index.d.ts +1 -0
  3. package/dist/attachments/index.js +1 -0
  4. package/dist/attachments/long-press.d.ts +43 -0
  5. package/dist/attachments/long-press.js +107 -0
  6. package/dist/components/Breadcrumbs/Breadcrumbs.svelte +198 -0
  7. package/dist/components/Breadcrumbs/Breadcrumbs.svelte.d.ts +60 -0
  8. package/dist/components/Breadcrumbs/README.md +174 -0
  9. package/dist/components/Breadcrumbs/i18n-sk.d.ts +17 -0
  10. package/dist/components/Breadcrumbs/i18n-sk.js +19 -0
  11. package/dist/components/Breadcrumbs/i18n.d.ts +31 -0
  12. package/dist/components/Breadcrumbs/i18n.js +40 -0
  13. package/dist/components/Breadcrumbs/index.css +84 -0
  14. package/dist/components/Breadcrumbs/index.d.ts +4 -0
  15. package/dist/components/Breadcrumbs/index.js +4 -0
  16. package/dist/components/Breadcrumbs/json-ld.d.ts +58 -0
  17. package/dist/components/Breadcrumbs/json-ld.js +56 -0
  18. package/dist/components/Collapsible/Collapsible.svelte +84 -15
  19. package/dist/components/Collapsible/Collapsible.svelte.d.ts +29 -2
  20. package/dist/components/Collapsible/README.md +82 -23
  21. package/dist/components/Collapsible/index.css +38 -0
  22. package/dist/components/Collapsible/index.d.ts +1 -1
  23. package/dist/components/ContextMenu/ContextMenu.svelte +302 -0
  24. package/dist/components/ContextMenu/ContextMenu.svelte.d.ts +94 -0
  25. package/dist/components/ContextMenu/README.md +159 -0
  26. package/dist/components/ContextMenu/i18n-sk.d.ts +17 -0
  27. package/dist/components/ContextMenu/i18n-sk.js +18 -0
  28. package/dist/components/ContextMenu/i18n.d.ts +30 -0
  29. package/dist/components/ContextMenu/i18n.js +38 -0
  30. package/dist/components/ContextMenu/index.css +26 -0
  31. package/dist/components/ContextMenu/index.d.ts +3 -0
  32. package/dist/components/ContextMenu/index.js +3 -0
  33. package/dist/components/CopyButton/CopyButton.svelte +278 -0
  34. package/dist/components/CopyButton/CopyButton.svelte.d.ts +76 -0
  35. package/dist/components/CopyButton/README.md +241 -0
  36. package/dist/components/CopyButton/i18n-sk.d.ts +17 -0
  37. package/dist/components/CopyButton/i18n-sk.js +20 -0
  38. package/dist/components/CopyButton/i18n.d.ts +35 -0
  39. package/dist/components/CopyButton/i18n.js +43 -0
  40. package/dist/components/CopyButton/index.css +67 -0
  41. package/dist/components/CopyButton/index.d.ts +3 -0
  42. package/dist/components/CopyButton/index.js +3 -0
  43. package/dist/components/DataTable/DataTable.svelte +41 -34
  44. package/dist/components/DataTable/README.md +45 -0
  45. package/dist/components/DataTable/i18n-sk.js +1 -0
  46. package/dist/components/DataTable/i18n.d.ts +1 -0
  47. package/dist/components/DataTable/i18n.js +3 -0
  48. package/dist/components/DataTable/index.css +41 -3
  49. package/dist/components/EmptyState/EmptyState.svelte +121 -0
  50. package/dist/components/EmptyState/EmptyState.svelte.d.ts +40 -0
  51. package/dist/components/EmptyState/README.md +188 -0
  52. package/dist/components/EmptyState/index.css +154 -0
  53. package/dist/components/EmptyState/index.d.ts +1 -0
  54. package/dist/components/EmptyState/index.js +1 -0
  55. package/dist/components/Pagination/Pagination.svelte +250 -0
  56. package/dist/components/Pagination/Pagination.svelte.d.ts +69 -0
  57. package/dist/components/Pagination/README.md +152 -0
  58. package/dist/components/Pagination/i18n-sk.d.ts +17 -0
  59. package/dist/components/Pagination/i18n-sk.js +24 -0
  60. package/dist/components/Pagination/i18n.d.ts +41 -0
  61. package/dist/components/Pagination/i18n.js +49 -0
  62. package/dist/components/Pagination/index.css +50 -0
  63. package/dist/components/Pagination/index.d.ts +4 -0
  64. package/dist/components/Pagination/index.js +4 -0
  65. package/dist/components/Pagination/pagination-range.d.ts +21 -0
  66. package/dist/components/Pagination/pagination-range.js +41 -0
  67. package/dist/components/Stat/README.md +157 -0
  68. package/dist/components/Stat/Stat.svelte +226 -0
  69. package/dist/components/Stat/Stat.svelte.d.ts +54 -0
  70. package/dist/components/Stat/index.css +217 -0
  71. package/dist/components/Stat/index.d.ts +1 -0
  72. package/dist/components/Stat/index.js +1 -0
  73. package/dist/components/Stepper/README.md +181 -0
  74. package/dist/components/Stepper/Stepper.svelte +240 -0
  75. package/dist/components/Stepper/Stepper.svelte.d.ts +86 -0
  76. package/dist/components/Stepper/i18n-sk.d.ts +17 -0
  77. package/dist/components/Stepper/i18n-sk.js +21 -0
  78. package/dist/components/Stepper/i18n.d.ts +35 -0
  79. package/dist/components/Stepper/i18n.js +43 -0
  80. package/dist/components/Stepper/index.css +302 -0
  81. package/dist/components/Stepper/index.d.ts +3 -0
  82. package/dist/components/Stepper/index.js +3 -0
  83. package/dist/components/TrendChart/README.md +123 -0
  84. package/dist/components/TrendChart/TrendChart.svelte +114 -0
  85. package/dist/components/TrendChart/TrendChart.svelte.d.ts +36 -0
  86. package/dist/components/TrendChart/index.css +51 -0
  87. package/dist/components/TrendChart/index.d.ts +1 -0
  88. package/dist/components/TrendChart/index.js +1 -0
  89. package/dist/icons/index.d.ts +4 -0
  90. package/dist/icons/index.js +4 -0
  91. package/dist/index.css +7 -0
  92. package/dist/index.d.ts +7 -0
  93. package/dist/index.js +12 -0
  94. package/dist/utils/copy-to-clipboard.d.ts +43 -0
  95. package/dist/utils/copy-to-clipboard.js +102 -0
  96. package/dist/utils/index.d.ts +1 -0
  97. package/dist/utils/index.js +1 -0
  98. package/docs/architecture.md +1 -1
  99. package/docs/domains/attachments.md +41 -4
  100. package/docs/domains/components.md +280 -13
  101. package/docs/domains/utils.md +8 -6
  102. package/docs/maybe-todo.md +111 -0
  103. 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`)