@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.
Files changed (31) hide show
  1. package/AGENTS.md +2 -2
  2. package/dist/README.md +1 -1
  3. package/dist/components/Input/FieldOptions.svelte +89 -54
  4. package/dist/components/Input/FieldOptions.svelte.d.ts +13 -0
  5. package/dist/components/Input/README.md +144 -31
  6. package/dist/components/Input/_internal/FieldLikeChips.svelte +286 -0
  7. package/dist/components/Input/_internal/FieldLikeChips.svelte.d.ts +65 -0
  8. package/dist/components/Input/field-options-i18n-sk.d.ts +21 -0
  9. package/dist/components/Input/field-options-i18n-sk.js +61 -0
  10. package/dist/components/Input/field-options-i18n.d.ts +82 -0
  11. package/dist/components/Input/field-options-i18n.js +90 -0
  12. package/dist/components/Input/index.css +58 -0
  13. package/dist/components/Input/index.d.ts +2 -0
  14. package/dist/components/Input/index.js +2 -0
  15. package/dist/components/Pill/Pill.svelte +8 -2
  16. package/dist/components/Pill/Pill.svelte.d.ts +2 -0
  17. package/dist/components/Pill/README.md +35 -22
  18. package/dist/components/Pill/index.css +57 -18
  19. package/dist/components/Timeline/README.md +204 -0
  20. package/dist/components/Timeline/Timeline.svelte +246 -0
  21. package/dist/components/Timeline/Timeline.svelte.d.ts +87 -0
  22. package/dist/components/Timeline/index.css +311 -0
  23. package/dist/components/Timeline/index.d.ts +1 -0
  24. package/dist/components/Timeline/index.js +1 -0
  25. package/dist/index.css +1 -0
  26. package/dist/index.d.ts +1 -0
  27. package/dist/index.js +1 -0
  28. package/docs/domains/components.md +39 -1
  29. package/docs/maybe-todo.md +37 -13
  30. package/docs/upgrading.md +6 -0
  31. 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="Dismiss"
115
+ aria-label={dismissLabel}
113
116
  onclick={handleDismiss}
114
117
  {disabled}
115
118
  >
116
- <X strokeWidth={2} />
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 | 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
- | `dot` | `boolean` | `false` | Status dot rendered before content |
24
- | `contentBefore` | `THC` | - | Content rendered before children |
25
- | `contentAfter` | `THC` | - | Content rendered after children |
26
- | `el` | `HTMLElement` | - | Element reference (bindable) |
27
- | `class` | `string` | - | Additional CSS classes |
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` (the X button defines the height).
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 already provides
149
- visual height, extra padding-y just makes them oversized. */
150
- .stuic-pill[data-dismissible="true"][data-size="sm"],
151
- .stuic-pill[data-dismissible="true"][data-size="md"],
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: opacity var(--stuic-pill-transition, var(--stuic-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: 1px;
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
- .stuic-pill-dismiss svg {
484
- width: 1em;
485
- height: 1em;
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.