@marianmeres/stuic 3.168.0 → 3.170.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 +2 -2
- package/dist/README.md +1 -1
- package/dist/components/Input/FieldOptions.svelte +89 -54
- package/dist/components/Input/FieldOptions.svelte.d.ts +13 -0
- package/dist/components/Input/README.md +144 -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/field-options-i18n-sk.d.ts +21 -0
- package/dist/components/Input/field-options-i18n-sk.js +61 -0
- package/dist/components/Input/field-options-i18n.d.ts +82 -0
- package/dist/components/Input/field-options-i18n.js +90 -0
- package/dist/components/Input/index.css +58 -0
- package/dist/components/Input/index.d.ts +2 -0
- package/dist/components/Input/index.js +2 -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/Timeline/README.md +204 -0
- package/dist/components/Timeline/Timeline.svelte +246 -0
- package/dist/components/Timeline/Timeline.svelte.d.ts +87 -0
- package/dist/components/Timeline/index.css +311 -0
- package/dist/components/Timeline/index.d.ts +1 -0
- package/dist/components/Timeline/index.js +1 -0
- package/dist/index.css +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/docs/domains/components.md +39 -1
- package/docs/maybe-todo.md +37 -13
- package/docs/upgrading.md +6 -0
- package/package.json +1 -1
|
@@ -8,6 +8,8 @@ export { default as FieldInput, } from "./FieldInput.svelte";
|
|
|
8
8
|
export { default as FieldMoney, } from "./FieldMoney.svelte";
|
|
9
9
|
export { default as FieldLikeButton, } from "./FieldLikeButton.svelte";
|
|
10
10
|
export { default as FieldOptions, } from "./FieldOptions.svelte";
|
|
11
|
+
export { createFieldOptionsT, FIELD_OPTIONS_MESSAGES_EN, } from "./field-options-i18n.js";
|
|
12
|
+
export { FIELD_OPTIONS_MESSAGES_SK } from "./field-options-i18n-sk.js";
|
|
11
13
|
export { default as FieldRadios, } from "./FieldRadios.svelte";
|
|
12
14
|
export { default as FieldSelect, } from "./FieldSelect.svelte";
|
|
13
15
|
export { default as Fieldset } from "./Fieldset.svelte";
|
|
@@ -42,6 +42,8 @@
|
|
|
42
42
|
dismissible?: boolean;
|
|
43
43
|
/** Called when X is clicked. Stops propagation so parent onclick is unaffected. */
|
|
44
44
|
ondismiss?: (e: MouseEvent) => void;
|
|
45
|
+
/** Accessible name of the built-in X dismiss button */
|
|
46
|
+
dismissLabel?: string;
|
|
45
47
|
/** Status dot rendered before content (uses current intent color) */
|
|
46
48
|
dot?: boolean;
|
|
47
49
|
/** Content rendered before children */
|
|
@@ -76,6 +78,7 @@
|
|
|
76
78
|
disabled,
|
|
77
79
|
dismissible = false,
|
|
78
80
|
ondismiss,
|
|
81
|
+
dismissLabel = "Dismiss",
|
|
79
82
|
dot = false,
|
|
80
83
|
contentBefore,
|
|
81
84
|
contentAfter,
|
|
@@ -109,11 +112,14 @@
|
|
|
109
112
|
<button
|
|
110
113
|
type="button"
|
|
111
114
|
class="stuic-pill-dismiss"
|
|
112
|
-
aria-label=
|
|
115
|
+
aria-label={dismissLabel}
|
|
113
116
|
onclick={handleDismiss}
|
|
114
117
|
{disabled}
|
|
115
118
|
>
|
|
116
|
-
|
|
119
|
+
<!-- The icon box is sized in CSS (tokens); the X itself just fills it. -->
|
|
120
|
+
<span class="stuic-pill-dismiss-icon" aria-hidden="true">
|
|
121
|
+
<X class="size-full" strokeWidth={2.5} />
|
|
122
|
+
</span>
|
|
117
123
|
</button>
|
|
118
124
|
{/snippet}
|
|
119
125
|
|
|
@@ -35,6 +35,8 @@ export interface Props extends Omit<HTMLAttributes<HTMLElement>, "children"> {
|
|
|
35
35
|
dismissible?: boolean;
|
|
36
36
|
/** Called when X is clicked. Stops propagation so parent onclick is unaffected. */
|
|
37
37
|
ondismiss?: (e: MouseEvent) => void;
|
|
38
|
+
/** Accessible name of the built-in X dismiss button */
|
|
39
|
+
dismissLabel?: string;
|
|
38
40
|
/** Status dot rendered before content (uses current intent color) */
|
|
39
41
|
dot?: boolean;
|
|
40
42
|
/** Content rendered before children */
|
|
@@ -4,27 +4,28 @@ A small rounded inline element for tags, badges, status indicators, and filter c
|
|
|
4
4
|
|
|
5
5
|
## Props
|
|
6
6
|
|
|
7
|
-
| Prop | Type | Default
|
|
8
|
-
| --------------- | ------------------------------------------------------------------ |
|
|
9
|
-
| `intent` | `"primary" \| "accent" \| "destructive" \| "warning" \| "success"` | -
|
|
10
|
-
| `variant` | `"solid" \| "outline" \| "ghost" \| "soft" \| "link"` | `"soft"`
|
|
11
|
-
| `size` | `"sm" \| "md" \| "lg"` | `"md"`
|
|
12
|
-
| `muted` | `boolean` | `false`
|
|
13
|
-
| `active` | `boolean` | `false`
|
|
14
|
-
| `roundedFull` | `boolean` | `true`
|
|
15
|
-
| `block` | `boolean` | `false`
|
|
16
|
-
| `unstyled` | `boolean` | `false`
|
|
17
|
-
| `href` | `string` | -
|
|
18
|
-
| `target` | `string` | -
|
|
19
|
-
| `onclick` | `(e: MouseEvent) => void` | -
|
|
20
|
-
| `disabled` | `boolean` | -
|
|
21
|
-
| `dismissible` | `boolean` | `false`
|
|
22
|
-
| `ondismiss` | `(e: MouseEvent) => void` | -
|
|
23
|
-
| `
|
|
24
|
-
| `
|
|
25
|
-
| `
|
|
26
|
-
| `
|
|
27
|
-
| `
|
|
7
|
+
| Prop | Type | Default | Description |
|
|
8
|
+
| --------------- | ------------------------------------------------------------------ | ----------- | --------------------------------------------------------------------- |
|
|
9
|
+
| `intent` | `"primary" \| "accent" \| "destructive" \| "warning" \| "success"` | - | Semantic color intent |
|
|
10
|
+
| `variant` | `"solid" \| "outline" \| "ghost" \| "soft" \| "link"` | `"soft"` | Visual variant (how colors are applied) |
|
|
11
|
+
| `size` | `"sm" \| "md" \| "lg"` | `"md"` | Pill size |
|
|
12
|
+
| `muted` | `boolean` | `false` | Reduce emphasis (lower opacity) |
|
|
13
|
+
| `active` | `boolean` | `false` | Selected/active state (filter-chip behavior) |
|
|
14
|
+
| `roundedFull` | `boolean` | `true` | Fully rounded corners (9999px). Set `false` to use the element radius |
|
|
15
|
+
| `block` | `boolean` | `false` | Render as block-level flex (full width). `inline-flex` by default |
|
|
16
|
+
| `unstyled` | `boolean` | `false` | Skip all default styling |
|
|
17
|
+
| `href` | `string` | - | Render as `<a>` with this URL |
|
|
18
|
+
| `target` | `string` | - | Link target (only when `href` is set) |
|
|
19
|
+
| `onclick` | `(e: MouseEvent) => void` | - | Render as `<button>` with this handler (when no `href`) |
|
|
20
|
+
| `disabled` | `boolean` | - | Disabled state (interactive variants only) |
|
|
21
|
+
| `dismissible` | `boolean` | `false` | Show built-in X dismiss button |
|
|
22
|
+
| `ondismiss` | `(e: MouseEvent) => void` | - | Called when X is clicked. Stops propagation |
|
|
23
|
+
| `dismissLabel` | `string` | `"Dismiss"` | Accessible name of the X dismiss button |
|
|
24
|
+
| `dot` | `boolean` | `false` | Status dot rendered before content |
|
|
25
|
+
| `contentBefore` | `THC` | - | Content rendered before children |
|
|
26
|
+
| `contentAfter` | `THC` | - | Content rendered after children |
|
|
27
|
+
| `el` | `HTMLElement` | - | Element reference (bindable) |
|
|
28
|
+
| `class` | `string` | - | Additional CSS classes |
|
|
28
29
|
|
|
29
30
|
## Element Resolution
|
|
30
31
|
|
|
@@ -172,6 +173,15 @@ A small rounded inline element for tags, badges, status indicators, and filter c
|
|
|
172
173
|
| `--stuic-pill-gap` | `0.375rem` | Gap between dot/before/children/after/dismiss |
|
|
173
174
|
| `--stuic-pill-dot-size` | `0.5rem` | Status dot diameter |
|
|
174
175
|
|
|
176
|
+
### Dismiss Button Tokens
|
|
177
|
+
|
|
178
|
+
| Variable | Default | Description |
|
|
179
|
+
| -------------------------------- | --------------------------------------------------- | ------------------------------------------------------------------- |
|
|
180
|
+
| `--stuic-pill-dismiss-min-size` | `1.5rem` | Touch-target floor for the X button (its square never gets smaller) |
|
|
181
|
+
| `--stuic-pill-dismiss-icon-size` | `1.125em` | Size of the X glyph box (relative to the pill font size) |
|
|
182
|
+
| `--stuic-pill-dismiss-bg-hover` | `color-mix(in srgb, currentColor 12%, transparent)` | Hover background of the whole X square |
|
|
183
|
+
| `--stuic-pill-dismiss-bg-active` | `color-mix(in srgb, currentColor 20%, transparent)` | Pressed background of the whole X square |
|
|
184
|
+
|
|
175
185
|
### Size Tokens
|
|
176
186
|
|
|
177
187
|
Each size (sm, md, lg) has corresponding tokens:
|
|
@@ -181,7 +191,10 @@ Each size (sm, md, lg) has corresponding tokens:
|
|
|
181
191
|
- `--stuic-pill-font-size-{size}`
|
|
182
192
|
- `--stuic-pill-min-height-{size}`
|
|
183
193
|
|
|
184
|
-
Dismissible pills override `padding-y` to `0
|
|
194
|
+
Dismissible pills override `padding-y` and `padding-inline-end` to `0`: the X button is a
|
|
195
|
+
full-height square (side = the pill's `min-height`, floored at `--stuic-pill-dismiss-min-size`)
|
|
196
|
+
sitting flush with the pill's end edge, and the whole square is the hover/press surface — not just
|
|
197
|
+
the glyph. Its corners inherit the pill radius, so on rounded pills the hover surface is the end cap.
|
|
185
198
|
|
|
186
199
|
### Intent Color Tokens
|
|
187
200
|
|
|
@@ -21,6 +21,12 @@
|
|
|
21
21
|
/* Status dot */
|
|
22
22
|
--stuic-pill-dot-size: 0.5rem;
|
|
23
23
|
|
|
24
|
+
/* Dismiss (X) button: a full-height square flush with the pill's end edge */
|
|
25
|
+
--stuic-pill-dismiss-min-size: 1.5rem; /* touch target floor (24px), whatever the pill size */
|
|
26
|
+
--stuic-pill-dismiss-icon-size: 1.125em; /* the X glyph box, relative to the pill font size */
|
|
27
|
+
--stuic-pill-dismiss-bg-hover: color-mix(in srgb, currentColor 12%, transparent);
|
|
28
|
+
--stuic-pill-dismiss-bg-active: color-mix(in srgb, currentColor 20%, transparent);
|
|
29
|
+
|
|
24
30
|
/* Size: sm */
|
|
25
31
|
--stuic-pill-padding-x-sm: 0.5rem;
|
|
26
32
|
--stuic-pill-padding-y-sm: 0.125rem;
|
|
@@ -145,13 +151,36 @@
|
|
|
145
151
|
min-height: var(--stuic-pill-min-height-lg);
|
|
146
152
|
}
|
|
147
153
|
|
|
148
|
-
/* Dismissible pills: drop vertical padding — the X button
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
.stuic-pill[data-dismissible="true"]
|
|
152
|
-
.stuic-pill[data-dismissible="true"][data-size="lg"] {
|
|
154
|
+
/* Dismissible pills: drop vertical + end padding — the X button spans the full
|
|
155
|
+
height and sits flush with the end edge (its hover surface IS the end cap),
|
|
156
|
+
so any padding there would only make the pill oversized. */
|
|
157
|
+
.stuic-pill[data-dismissible="true"] {
|
|
153
158
|
padding-top: 0;
|
|
154
159
|
padding-bottom: 0;
|
|
160
|
+
padding-inline-end: 0;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/* The X button is a square whose side follows the pill's min-height, but never
|
|
164
|
+
drops below the touch-target floor (so sm pills keep a 24px X). */
|
|
165
|
+
.stuic-pill[data-dismissible="true"][data-size="sm"] {
|
|
166
|
+
--_dismiss-size: max(
|
|
167
|
+
var(--stuic-pill-min-height-sm),
|
|
168
|
+
var(--stuic-pill-dismiss-min-size)
|
|
169
|
+
);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
.stuic-pill[data-dismissible="true"][data-size="md"] {
|
|
173
|
+
--_dismiss-size: max(
|
|
174
|
+
var(--stuic-pill-min-height-md),
|
|
175
|
+
var(--stuic-pill-dismiss-min-size)
|
|
176
|
+
);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
.stuic-pill[data-dismissible="true"][data-size="lg"] {
|
|
180
|
+
--_dismiss-size: max(
|
|
181
|
+
var(--stuic-pill-min-height-lg),
|
|
182
|
+
var(--stuic-pill-dismiss-min-size)
|
|
183
|
+
);
|
|
155
184
|
}
|
|
156
185
|
|
|
157
186
|
/* ============================================================================
|
|
@@ -448,31 +477,45 @@
|
|
|
448
477
|
cursor: not-allowed;
|
|
449
478
|
}
|
|
450
479
|
|
|
451
|
-
/* Dismiss X button
|
|
480
|
+
/* Dismiss X button: a full-height square segment flush with the pill's end edge.
|
|
481
|
+
The whole square (not just the glyph) is the hover/press surface — the same
|
|
482
|
+
treatment as SplitButton's secondary trigger. Radius is inherited from the pill,
|
|
483
|
+
so on rounded-full pills the hover surface coincides with the end cap. */
|
|
452
484
|
.stuic-pill-dismiss {
|
|
453
485
|
display: inline-flex;
|
|
454
486
|
align-items: center;
|
|
455
487
|
justify-content: center;
|
|
488
|
+
align-self: stretch;
|
|
489
|
+
min-width: var(--_dismiss-size, var(--stuic-pill-dismiss-min-size));
|
|
490
|
+
min-height: var(--_dismiss-size, var(--stuic-pill-dismiss-min-size));
|
|
456
491
|
appearance: none;
|
|
457
492
|
background: transparent;
|
|
458
493
|
border: 0;
|
|
494
|
+
border-radius: inherit;
|
|
459
495
|
padding: 0;
|
|
460
496
|
margin: 0;
|
|
461
497
|
color: inherit;
|
|
462
498
|
cursor: pointer;
|
|
463
499
|
opacity: 0.7;
|
|
464
|
-
transition:
|
|
500
|
+
transition:
|
|
501
|
+
opacity var(--stuic-pill-transition, var(--stuic-transition)),
|
|
502
|
+
background var(--stuic-pill-transition, var(--stuic-transition));
|
|
465
503
|
flex-shrink: 0;
|
|
466
504
|
}
|
|
467
505
|
|
|
468
506
|
.stuic-pill-dismiss:hover:not(:disabled) {
|
|
469
507
|
opacity: 1;
|
|
508
|
+
background: var(--stuic-pill-dismiss-bg-hover);
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
.stuic-pill-dismiss:active:not(:disabled) {
|
|
512
|
+
background: var(--stuic-pill-dismiss-bg-active);
|
|
470
513
|
}
|
|
471
514
|
|
|
515
|
+
/* Flush with the pill edge, so the ring goes inside the square */
|
|
472
516
|
.stuic-pill-dismiss:focus-visible {
|
|
473
517
|
outline: var(--stuic-pill-ring-width) solid var(--_ring, var(--stuic-pill-ring-color));
|
|
474
|
-
outline-offset:
|
|
475
|
-
border-radius: 9999px;
|
|
518
|
+
outline-offset: calc(-1 * var(--stuic-pill-ring-width));
|
|
476
519
|
}
|
|
477
520
|
|
|
478
521
|
.stuic-pill-dismiss:disabled {
|
|
@@ -480,14 +523,10 @@
|
|
|
480
523
|
cursor: not-allowed;
|
|
481
524
|
}
|
|
482
525
|
|
|
483
|
-
.
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
/* Tighten dismiss icon size per pill size */
|
|
489
|
-
.stuic-pill[data-size="sm"] .stuic-pill-dismiss svg {
|
|
490
|
-
width: 0.875em;
|
|
491
|
-
height: 0.875em;
|
|
526
|
+
/* The glyph box — deliberately smaller than the button, which keeps the target. */
|
|
527
|
+
.stuic-pill-dismiss-icon {
|
|
528
|
+
display: inline-flex;
|
|
529
|
+
width: var(--stuic-pill-dismiss-icon-size);
|
|
530
|
+
height: var(--stuic-pill-dismiss-icon-size);
|
|
492
531
|
}
|
|
493
532
|
}
|
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
# Timeline
|
|
2
|
+
|
|
3
|
+
Vertical event list with markers on a rail — activity feeds, audit logs, order
|
|
4
|
+
tracking, "our history" pages. Renders an ordered list (`<ol>`) where every item
|
|
5
|
+
has a marker (dot, icon bubble, or anything via snippet), an optional time label,
|
|
6
|
+
a title, a description and an optional footer. Data-driven: pass `items`, override
|
|
7
|
+
parts with snippets. Static by design — there is no "current" step; for
|
|
8
|
+
progress-style flows use `Stepper` (vertical orientation).
|
|
9
|
+
|
|
10
|
+
## Props
|
|
11
|
+
|
|
12
|
+
| Prop | Type | Default | Description |
|
|
13
|
+
| ------------------ | ------------------------- | ---------- | ------------------------------------------------------------------------------------- |
|
|
14
|
+
| `items` | `TimelineItem[]` | required | The events, in the order they should appear (sort them yourself) |
|
|
15
|
+
| `align` | `"start" \| "alternate"` | `"start"` | Rail on the start side with content after it, or centered rail with alternating sides |
|
|
16
|
+
| `timePosition` | `"inline" \| "opposite"` | `"inline"` | Time label above the title, or in its own column on the other side of the rail |
|
|
17
|
+
| `formatTime` | `(datetime, item) => THC` | - | Fallback label for items that have `datetime` but no `time` |
|
|
18
|
+
| `unstyled` | `boolean` | `false` | Skip all default styling |
|
|
19
|
+
| `class` | `string` | - | Additional CSS classes (merged via twMerge) |
|
|
20
|
+
| `classItem` | `string` | - | Class for every item (`li`) |
|
|
21
|
+
| `classMarker` | `string` | - | Class for the marker |
|
|
22
|
+
| `classContent` | `string` | - | Class for the content cell |
|
|
23
|
+
| `classTime` | `string` | - | Class for the time label (inline or opposite) |
|
|
24
|
+
| `classTitle` | `string` | - | Class for the title |
|
|
25
|
+
| `classDescription` | `string` | - | Class for the description |
|
|
26
|
+
| `classFooter` | `string` | - | Class for the footer area |
|
|
27
|
+
| `el` | `HTMLOListElement` | - | Element reference (bindable) |
|
|
28
|
+
|
|
29
|
+
Any other attribute (`aria-label`, `data-*`, …) is passed to the `<ol>`.
|
|
30
|
+
|
|
31
|
+
### `TimelineItem`
|
|
32
|
+
|
|
33
|
+
| Field | Type | Description |
|
|
34
|
+
| ------------- | ---------------- | ----------------------------------------------------------------------------------------------------- |
|
|
35
|
+
| `title` | `THC` | Primary line (text, html, or component — see `Thc`) |
|
|
36
|
+
| `description` | `THC` | Secondary text under the title |
|
|
37
|
+
| `time` | `THC` | Time label, preformatted ("2 hours ago"); falls back to `formatTime(datetime)` when omitted |
|
|
38
|
+
| `datetime` | `string \| Date` | Machine-readable timestamp → `<time datetime="…">` (a `Date` becomes ISO 8601); never displayed alone |
|
|
39
|
+
| `icon` | `THC` | Marker content (e.g. `{ html: iconCheck() }`); switches the marker from dot to icon bubble |
|
|
40
|
+
| `intent` | `IntentColorKey` | `"primary" \| "accent" \| "success" \| "warning" \| "destructive"` — colors the marker |
|
|
41
|
+
| `href` | `string` | Renders the title as a link |
|
|
42
|
+
|
|
43
|
+
## Snippet Props
|
|
44
|
+
|
|
45
|
+
All three receive `{ item, index }`.
|
|
46
|
+
|
|
47
|
+
| Snippet | Description |
|
|
48
|
+
| -------------- | ---------------------------------------------------------------------- |
|
|
49
|
+
| `renderMarker` | Override the marker content entirely (avatars, badges…) for every item |
|
|
50
|
+
| `renderItem` | Override the whole content cell (time, title, description, footer) |
|
|
51
|
+
| `renderFooter` | Per-item footer area below the description (actions, attachments…) |
|
|
52
|
+
|
|
53
|
+
## Usage
|
|
54
|
+
|
|
55
|
+
### Basic
|
|
56
|
+
|
|
57
|
+
```svelte
|
|
58
|
+
<script lang="ts">
|
|
59
|
+
import { Timeline } from "@marianmeres/stuic";
|
|
60
|
+
</script>
|
|
61
|
+
|
|
62
|
+
<Timeline
|
|
63
|
+
items={[
|
|
64
|
+
{ title: "Order placed", description: "Ref #1001", time: "09:00" },
|
|
65
|
+
{ title: "Payment received", time: "09:05", intent: "success" },
|
|
66
|
+
{ title: "Shipped", description: "Tracking number sent", time: "Yesterday" },
|
|
67
|
+
]}
|
|
68
|
+
/>
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
### Icons and intents
|
|
72
|
+
|
|
73
|
+
`icon` turns the dot into a bubble; `intent` colors either.
|
|
74
|
+
|
|
75
|
+
```svelte
|
|
76
|
+
<script lang="ts">
|
|
77
|
+
import { Timeline, iconCheck, iconAlertWarning } from "@marianmeres/stuic";
|
|
78
|
+
</script>
|
|
79
|
+
|
|
80
|
+
<Timeline
|
|
81
|
+
items={[
|
|
82
|
+
{ title: "Deployed", icon: { html: iconCheck() }, intent: "success" },
|
|
83
|
+
{
|
|
84
|
+
title: "Health check failed",
|
|
85
|
+
icon: { html: iconAlertWarning() },
|
|
86
|
+
intent: "destructive",
|
|
87
|
+
},
|
|
88
|
+
{ title: "Rolled back", intent: "warning" },
|
|
89
|
+
]}
|
|
90
|
+
/>
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
### Machine-readable times + a formatter
|
|
94
|
+
|
|
95
|
+
`datetime` is metadata (`<time datetime>`); `time` is what people see. Give every item
|
|
96
|
+
a `datetime` and one `formatTime` and skip per-item labels:
|
|
97
|
+
|
|
98
|
+
```svelte
|
|
99
|
+
<script lang="ts">
|
|
100
|
+
const fmt = new Intl.DateTimeFormat("en", { dateStyle: "medium", timeStyle: "short" });
|
|
101
|
+
</script>
|
|
102
|
+
|
|
103
|
+
<Timeline
|
|
104
|
+
items={events.map((e) => ({ title: e.message, datetime: e.createdAt }))}
|
|
105
|
+
formatTime={(d) => fmt.format(new Date(d))}
|
|
106
|
+
/>
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
### Audit-log layout (time column)
|
|
110
|
+
|
|
111
|
+
```svelte
|
|
112
|
+
<Timeline {items} timePosition="opposite" />
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
### Alternating sides (history page)
|
|
116
|
+
|
|
117
|
+
```svelte
|
|
118
|
+
<Timeline {items} align="alternate" />
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
The rail is centered and content alternates sides (`timePosition="opposite"` puts the
|
|
122
|
+
time on the free side). There is no automatic collapse on narrow screens — switch to
|
|
123
|
+
`align="start"` yourself below your breakpoint if needed.
|
|
124
|
+
|
|
125
|
+
### Custom markers (avatars)
|
|
126
|
+
|
|
127
|
+
```svelte
|
|
128
|
+
<Timeline {items}>
|
|
129
|
+
{#snippet renderMarker({ item })}
|
|
130
|
+
<Avatar src={item.actorAvatar} size="sm" />
|
|
131
|
+
{/snippet}
|
|
132
|
+
</Timeline>
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
`renderMarker` replaces the marker for every item (`data-marker="custom"`): the box is
|
|
136
|
+
sized like the icon bubble but carries no background — the snippet brings its own look.
|
|
137
|
+
|
|
138
|
+
### Footer actions / custom item content
|
|
139
|
+
|
|
140
|
+
```svelte
|
|
141
|
+
<Timeline {items}>
|
|
142
|
+
{#snippet renderFooter({ item })}
|
|
143
|
+
<Button size="sm" variant="outline" onclick={() => open(item)}>Details</Button>
|
|
144
|
+
{/snippet}
|
|
145
|
+
</Timeline>
|
|
146
|
+
|
|
147
|
+
<!-- or take over the whole content cell -->
|
|
148
|
+
<Timeline {items}>
|
|
149
|
+
{#snippet renderItem({ item })}
|
|
150
|
+
<strong>{item.title}</strong> — <em>{item.time}</em>
|
|
151
|
+
{/snippet}
|
|
152
|
+
</Timeline>
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
### Grouping
|
|
156
|
+
|
|
157
|
+
There is no built-in "Today / Yesterday" grouping — render one `Timeline` per group
|
|
158
|
+
under your own headings.
|
|
159
|
+
|
|
160
|
+
## Accessibility
|
|
161
|
+
|
|
162
|
+
- Renders a real `<ol>` (explicit `role="list"`, so Safari keeps the list semantics
|
|
163
|
+
despite `list-style: none`); pass `aria-label` if the page has several lists.
|
|
164
|
+
- Markers are `aria-hidden` — they are decorative; the time and title carry the
|
|
165
|
+
information. Reading order is time → title → description → footer (in the opposite
|
|
166
|
+
layout the time cell still comes first in the DOM).
|
|
167
|
+
- `datetime` renders a `<time>` element; without it the label is a plain `<span>`
|
|
168
|
+
(the spec requires a valid date string in a bare `<time>`).
|
|
169
|
+
|
|
170
|
+
## CSS Variables
|
|
171
|
+
|
|
172
|
+
| Variable | Default | Description |
|
|
173
|
+
| ------------------------------------------------------------------ | ------------------------------------------- | --------------------------------------------------------------------------------------------------------- |
|
|
174
|
+
| `--stuic-timeline-gap` | `1rem` | Column gap (opposite ↔ rail ↔ content) |
|
|
175
|
+
| `--stuic-timeline-gap-vertical` | `1.5rem` | Distance between items |
|
|
176
|
+
| `--stuic-timeline-line-height` | `1.5rem` | Height of the first content line; the marker centers on it |
|
|
177
|
+
| `--stuic-timeline-connector-thickness` | `2px` | Rail line thickness |
|
|
178
|
+
| `--stuic-timeline-connector-bg` | `--stuic-color-border` | Rail line color |
|
|
179
|
+
| `--stuic-timeline-marker-size` | `0.75rem` | Dot diameter |
|
|
180
|
+
| `--stuic-timeline-marker-bg` | `--stuic-color-muted-foreground` | Dot color (no intent) |
|
|
181
|
+
| `--stuic-timeline-marker-size-icon` | `2rem` | Icon bubble / custom marker diameter |
|
|
182
|
+
| `--stuic-timeline-marker-bg-icon` / `-text-icon` | muted / muted-foreground | Icon bubble colors (no intent); an intent applies a soft tint |
|
|
183
|
+
| `--stuic-timeline-marker-icon-size` | `1rem` | Size of an svg inside the bubble |
|
|
184
|
+
| `--stuic-timeline-marker-radius` | full circle | Marker radius |
|
|
185
|
+
| `--stuic-timeline-marker-ring-width` / `-ring-color` | `2px` / `--stuic-color-background` | Ring separating the marker from the line behind it (set the color to your card's background inside cards) |
|
|
186
|
+
| `--stuic-timeline-time-font-size` / `-text` | `--text-sm` / muted-foreground | Time label |
|
|
187
|
+
| `--stuic-timeline-title-font-size` / `-font-weight` / `-text` | `--text-base` / medium / foreground | Title |
|
|
188
|
+
| `--stuic-timeline-description-font-size` / `-text` / `-margin-top` | `--text-sm` / muted-foreground / `0.125rem` | Description |
|
|
189
|
+
| `--stuic-timeline-footer-gap` / `-margin-top` | `0.5rem` / `0.5rem` | Footer area |
|
|
190
|
+
|
|
191
|
+
## Data Attributes
|
|
192
|
+
|
|
193
|
+
- `data-align` - `"start" | "alternate"` (root)
|
|
194
|
+
- `data-time-position` - `"inline" | "opposite"` (root)
|
|
195
|
+
- `data-intent` - the item's intent (`li`, only when set)
|
|
196
|
+
- `data-marker` - `"dot" | "icon" | "custom"` (marker element)
|
|
197
|
+
|
|
198
|
+
## Layout notes
|
|
199
|
+
|
|
200
|
+
The list is a CSS grid and every item a `subgrid` row, so the rail column (sized by
|
|
201
|
+
the widest marker) and the opposite time column stay aligned across items without
|
|
202
|
+
any fixed widths. Markers of different sizes (dots and bubbles in one list) stay on
|
|
203
|
+
the same axis, and the rail line runs behind them — the ring (`--stuic-timeline-marker-ring-*`)
|
|
204
|
+
is what visually separates marker and line.
|