@marianmeres/stuic 3.169.0 → 3.171.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/components/ContextMenu/ContextMenu.svelte +1 -1
- package/dist/components/ContextMenu/ContextMenu.svelte.d.ts +1 -1
- package/dist/components/ContextMenu/README.md +1 -1
- package/dist/components/DropdownMenu/DropdownMenu.svelte +57 -3
- package/dist/components/DropdownMenu/DropdownMenu.svelte.d.ts +4 -2
- package/dist/components/DropdownMenu/README.md +1 -0
- package/dist/components/Input/FieldOptions.svelte +1 -56
- package/dist/components/Input/README.md +78 -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.d.ts +2 -0
- package/dist/components/Input/index.js +2 -0
- 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 +5 -2
- package/package.json +1 -1
|
@@ -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.
|
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
<script lang="ts" module>
|
|
2
|
+
import type { HTMLOlAttributes } from "svelte/elements";
|
|
3
|
+
import type { Snippet } from "svelte";
|
|
4
|
+
import type { THC } from "../Thc/Thc.svelte";
|
|
5
|
+
import type { IntentColorKey } from "../../utils/design-tokens.js";
|
|
6
|
+
|
|
7
|
+
/** Which side of the rail the content sits on */
|
|
8
|
+
export type TimelineAlign = "start" | "alternate";
|
|
9
|
+
|
|
10
|
+
/** Where the time label renders */
|
|
11
|
+
export type TimelineTimePosition = "inline" | "opposite";
|
|
12
|
+
|
|
13
|
+
/** What the marker renders as (`data-marker` on the marker element) */
|
|
14
|
+
export type TimelineMarkerKind = "dot" | "icon" | "custom";
|
|
15
|
+
|
|
16
|
+
export interface TimelineItem {
|
|
17
|
+
/** Primary line (the event) */
|
|
18
|
+
title?: THC;
|
|
19
|
+
/** Secondary text under the title */
|
|
20
|
+
description?: THC;
|
|
21
|
+
/**
|
|
22
|
+
* Time label, preformatted ("2 hours ago", "Sep 1, 14:32"). When omitted and
|
|
23
|
+
* `datetime` is set, the component's `formatTime` (if any) produces it.
|
|
24
|
+
*/
|
|
25
|
+
time?: THC;
|
|
26
|
+
/**
|
|
27
|
+
* Machine-readable timestamp — renders the label as `<time datetime="…">`
|
|
28
|
+
* (a `Date` is serialized to ISO 8601). Never displayed on its own.
|
|
29
|
+
*/
|
|
30
|
+
datetime?: string | Date;
|
|
31
|
+
/**
|
|
32
|
+
* Marker content (typically `{ html: iconCheck() }`). Its presence switches the
|
|
33
|
+
* marker from the small dot to the icon bubble.
|
|
34
|
+
*/
|
|
35
|
+
icon?: THC;
|
|
36
|
+
/** Semantic marker color (dot fill, or the bubble's soft tint) */
|
|
37
|
+
intent?: IntentColorKey;
|
|
38
|
+
/** When set, the title renders as a link */
|
|
39
|
+
href?: string;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export interface TimelineSnippetArg {
|
|
43
|
+
item: TimelineItem;
|
|
44
|
+
index: number;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export interface Props extends Omit<HTMLOlAttributes, "children"> {
|
|
48
|
+
/** The events, in the order they should appear (sort them yourself) */
|
|
49
|
+
items: TimelineItem[];
|
|
50
|
+
/**
|
|
51
|
+
* `"start"` (default): rail on the start side, content after it.
|
|
52
|
+
* `"alternate"`: centered rail, content alternating sides (history pages).
|
|
53
|
+
*/
|
|
54
|
+
align?: TimelineAlign;
|
|
55
|
+
/**
|
|
56
|
+
* `"inline"` (default): the time label above the title, inside the content.
|
|
57
|
+
* `"opposite"`: its own column on the other side of the rail (audit-log look).
|
|
58
|
+
*/
|
|
59
|
+
timePosition?: TimelineTimePosition;
|
|
60
|
+
/**
|
|
61
|
+
* Fallback formatter for items that have `datetime` but no `time`. Return
|
|
62
|
+
* value is THC, so a plain string is fine.
|
|
63
|
+
*/
|
|
64
|
+
formatTime?: (datetime: string | Date, item: TimelineItem) => THC;
|
|
65
|
+
/** Override the marker content entirely (avatars, badges…) */
|
|
66
|
+
renderMarker?: Snippet<[TimelineSnippetArg]>;
|
|
67
|
+
/** Override the whole content cell (time, title, description, footer) */
|
|
68
|
+
renderItem?: Snippet<[TimelineSnippetArg]>;
|
|
69
|
+
/** Per-item footer area below the description (actions, attachments…) */
|
|
70
|
+
renderFooter?: Snippet<[TimelineSnippetArg]>;
|
|
71
|
+
/** Skip all default styling */
|
|
72
|
+
unstyled?: boolean;
|
|
73
|
+
/** Additional CSS classes */
|
|
74
|
+
class?: string;
|
|
75
|
+
/** Class for every item (`li`) */
|
|
76
|
+
classItem?: string;
|
|
77
|
+
/** Class for the marker */
|
|
78
|
+
classMarker?: string;
|
|
79
|
+
/** Class for the content cell */
|
|
80
|
+
classContent?: string;
|
|
81
|
+
/** Class for the time label (inline or opposite) */
|
|
82
|
+
classTime?: string;
|
|
83
|
+
/** Class for the title */
|
|
84
|
+
classTitle?: string;
|
|
85
|
+
/** Class for the description */
|
|
86
|
+
classDescription?: string;
|
|
87
|
+
/** Class for the footer area */
|
|
88
|
+
classFooter?: string;
|
|
89
|
+
/** Bindable element reference */
|
|
90
|
+
el?: HTMLOListElement;
|
|
91
|
+
}
|
|
92
|
+
</script>
|
|
93
|
+
|
|
94
|
+
<script lang="ts">
|
|
95
|
+
import { twMerge } from "../../utils/tw-merge.js";
|
|
96
|
+
import Thc from "../Thc/Thc.svelte";
|
|
97
|
+
|
|
98
|
+
let {
|
|
99
|
+
items,
|
|
100
|
+
align = "start",
|
|
101
|
+
timePosition = "inline",
|
|
102
|
+
formatTime,
|
|
103
|
+
renderMarker,
|
|
104
|
+
renderItem,
|
|
105
|
+
renderFooter,
|
|
106
|
+
unstyled = false,
|
|
107
|
+
class: classProp,
|
|
108
|
+
classItem: classItemProp,
|
|
109
|
+
classMarker: classMarkerProp,
|
|
110
|
+
classContent: classContentProp,
|
|
111
|
+
classTime: classTimeProp,
|
|
112
|
+
classTitle: classTitleProp,
|
|
113
|
+
classDescription: classDescriptionProp,
|
|
114
|
+
classFooter: classFooterProp,
|
|
115
|
+
el = $bindable(),
|
|
116
|
+
...rest
|
|
117
|
+
}: Props = $props();
|
|
118
|
+
|
|
119
|
+
let _class = $derived(unstyled ? classProp : twMerge("stuic-timeline", classProp));
|
|
120
|
+
let _classItem = $derived(
|
|
121
|
+
unstyled ? classItemProp : twMerge("stuic-timeline-item", classItemProp)
|
|
122
|
+
);
|
|
123
|
+
let _classMarker = $derived(
|
|
124
|
+
unstyled ? classMarkerProp : twMerge("stuic-timeline-marker", classMarkerProp)
|
|
125
|
+
);
|
|
126
|
+
let _classContent = $derived(
|
|
127
|
+
unstyled ? classContentProp : twMerge("stuic-timeline-content", classContentProp)
|
|
128
|
+
);
|
|
129
|
+
let _classTime = $derived(
|
|
130
|
+
unstyled ? classTimeProp : twMerge("stuic-timeline-time", classTimeProp)
|
|
131
|
+
);
|
|
132
|
+
let _classTitle = $derived(
|
|
133
|
+
unstyled ? classTitleProp : twMerge("stuic-timeline-title", classTitleProp)
|
|
134
|
+
);
|
|
135
|
+
let _classDescription = $derived(
|
|
136
|
+
unstyled
|
|
137
|
+
? classDescriptionProp
|
|
138
|
+
: twMerge("stuic-timeline-description", classDescriptionProp)
|
|
139
|
+
);
|
|
140
|
+
let _classFooter = $derived(
|
|
141
|
+
unstyled ? classFooterProp : twMerge("stuic-timeline-footer", classFooterProp)
|
|
142
|
+
);
|
|
143
|
+
|
|
144
|
+
function markerKind(item: TimelineItem): TimelineMarkerKind {
|
|
145
|
+
if (renderMarker) return "custom";
|
|
146
|
+
if (item.icon) return "icon";
|
|
147
|
+
return "dot";
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
function timeLabel(item: TimelineItem): THC | undefined {
|
|
151
|
+
if (item.time !== undefined && item.time !== null && item.time !== "")
|
|
152
|
+
return item.time;
|
|
153
|
+
if (item.datetime !== undefined && formatTime) return formatTime(item.datetime, item);
|
|
154
|
+
return undefined;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
function datetimeAttr(item: TimelineItem): string | undefined {
|
|
158
|
+
if (item.datetime === undefined) return undefined;
|
|
159
|
+
return item.datetime instanceof Date ? item.datetime.toISOString() : item.datetime;
|
|
160
|
+
}
|
|
161
|
+
</script>
|
|
162
|
+
|
|
163
|
+
{#snippet timeEl(item: TimelineItem, label: THC)}
|
|
164
|
+
{@const datetime = datetimeAttr(item)}
|
|
165
|
+
<!-- <time> only when it can carry a machine-readable value -->
|
|
166
|
+
<svelte:element this={datetime ? "time" : "span"} class={_classTime} {datetime}>
|
|
167
|
+
<Thc thc={label} />
|
|
168
|
+
</svelte:element>
|
|
169
|
+
{/snippet}
|
|
170
|
+
|
|
171
|
+
{#if items.length}
|
|
172
|
+
<!-- svelte-ignore a11y_no_redundant_roles -->
|
|
173
|
+
<ol
|
|
174
|
+
bind:this={el}
|
|
175
|
+
class={_class}
|
|
176
|
+
role="list"
|
|
177
|
+
data-align={!unstyled ? align : undefined}
|
|
178
|
+
data-time-position={!unstyled ? timePosition : undefined}
|
|
179
|
+
{...rest}
|
|
180
|
+
>
|
|
181
|
+
{#each items as item, index (index)}
|
|
182
|
+
{@const label = timeLabel(item)}
|
|
183
|
+
{@const kind = markerKind(item)}
|
|
184
|
+
<li
|
|
185
|
+
class={_classItem}
|
|
186
|
+
data-intent={!unstyled && item.intent ? item.intent : undefined}
|
|
187
|
+
>
|
|
188
|
+
{#if timePosition === "opposite"}
|
|
189
|
+
<div class={unstyled ? undefined : "stuic-timeline-opposite"}>
|
|
190
|
+
{#if label !== undefined}
|
|
191
|
+
{@render timeEl(item, label)}
|
|
192
|
+
{/if}
|
|
193
|
+
</div>
|
|
194
|
+
{/if}
|
|
195
|
+
|
|
196
|
+
<div class={unstyled ? undefined : "stuic-timeline-rail"}>
|
|
197
|
+
<span
|
|
198
|
+
class={_classMarker}
|
|
199
|
+
data-marker={!unstyled ? kind : undefined}
|
|
200
|
+
aria-hidden="true"
|
|
201
|
+
>
|
|
202
|
+
{#if renderMarker}
|
|
203
|
+
{@render renderMarker({ item, index })}
|
|
204
|
+
{:else if item.icon}
|
|
205
|
+
<Thc thc={item.icon} />
|
|
206
|
+
{/if}
|
|
207
|
+
</span>
|
|
208
|
+
</div>
|
|
209
|
+
|
|
210
|
+
<div class={_classContent}>
|
|
211
|
+
{#if renderItem}
|
|
212
|
+
{@render renderItem({ item, index })}
|
|
213
|
+
{:else}
|
|
214
|
+
{#if timePosition === "inline" && label !== undefined}
|
|
215
|
+
{@render timeEl(item, label)}
|
|
216
|
+
{/if}
|
|
217
|
+
{#if item.title}
|
|
218
|
+
<div class={_classTitle}>
|
|
219
|
+
{#if item.href}
|
|
220
|
+
<a
|
|
221
|
+
href={item.href}
|
|
222
|
+
class={unstyled ? undefined : "stuic-timeline-link"}
|
|
223
|
+
>
|
|
224
|
+
<Thc thc={item.title} />
|
|
225
|
+
</a>
|
|
226
|
+
{:else}
|
|
227
|
+
<Thc thc={item.title} />
|
|
228
|
+
{/if}
|
|
229
|
+
</div>
|
|
230
|
+
{/if}
|
|
231
|
+
{#if item.description}
|
|
232
|
+
<div class={_classDescription}>
|
|
233
|
+
<Thc thc={item.description} />
|
|
234
|
+
</div>
|
|
235
|
+
{/if}
|
|
236
|
+
{#if renderFooter}
|
|
237
|
+
<div class={_classFooter}>
|
|
238
|
+
{@render renderFooter({ item, index })}
|
|
239
|
+
</div>
|
|
240
|
+
{/if}
|
|
241
|
+
{/if}
|
|
242
|
+
</div>
|
|
243
|
+
</li>
|
|
244
|
+
{/each}
|
|
245
|
+
</ol>
|
|
246
|
+
{/if}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import type { HTMLOlAttributes } from "svelte/elements";
|
|
2
|
+
import type { Snippet } from "svelte";
|
|
3
|
+
import type { THC } from "../Thc/Thc.svelte";
|
|
4
|
+
import type { IntentColorKey } from "../../utils/design-tokens.js";
|
|
5
|
+
/** Which side of the rail the content sits on */
|
|
6
|
+
export type TimelineAlign = "start" | "alternate";
|
|
7
|
+
/** Where the time label renders */
|
|
8
|
+
export type TimelineTimePosition = "inline" | "opposite";
|
|
9
|
+
/** What the marker renders as (`data-marker` on the marker element) */
|
|
10
|
+
export type TimelineMarkerKind = "dot" | "icon" | "custom";
|
|
11
|
+
export interface TimelineItem {
|
|
12
|
+
/** Primary line (the event) */
|
|
13
|
+
title?: THC;
|
|
14
|
+
/** Secondary text under the title */
|
|
15
|
+
description?: THC;
|
|
16
|
+
/**
|
|
17
|
+
* Time label, preformatted ("2 hours ago", "Sep 1, 14:32"). When omitted and
|
|
18
|
+
* `datetime` is set, the component's `formatTime` (if any) produces it.
|
|
19
|
+
*/
|
|
20
|
+
time?: THC;
|
|
21
|
+
/**
|
|
22
|
+
* Machine-readable timestamp — renders the label as `<time datetime="…">`
|
|
23
|
+
* (a `Date` is serialized to ISO 8601). Never displayed on its own.
|
|
24
|
+
*/
|
|
25
|
+
datetime?: string | Date;
|
|
26
|
+
/**
|
|
27
|
+
* Marker content (typically `{ html: iconCheck() }`). Its presence switches the
|
|
28
|
+
* marker from the small dot to the icon bubble.
|
|
29
|
+
*/
|
|
30
|
+
icon?: THC;
|
|
31
|
+
/** Semantic marker color (dot fill, or the bubble's soft tint) */
|
|
32
|
+
intent?: IntentColorKey;
|
|
33
|
+
/** When set, the title renders as a link */
|
|
34
|
+
href?: string;
|
|
35
|
+
}
|
|
36
|
+
export interface TimelineSnippetArg {
|
|
37
|
+
item: TimelineItem;
|
|
38
|
+
index: number;
|
|
39
|
+
}
|
|
40
|
+
export interface Props extends Omit<HTMLOlAttributes, "children"> {
|
|
41
|
+
/** The events, in the order they should appear (sort them yourself) */
|
|
42
|
+
items: TimelineItem[];
|
|
43
|
+
/**
|
|
44
|
+
* `"start"` (default): rail on the start side, content after it.
|
|
45
|
+
* `"alternate"`: centered rail, content alternating sides (history pages).
|
|
46
|
+
*/
|
|
47
|
+
align?: TimelineAlign;
|
|
48
|
+
/**
|
|
49
|
+
* `"inline"` (default): the time label above the title, inside the content.
|
|
50
|
+
* `"opposite"`: its own column on the other side of the rail (audit-log look).
|
|
51
|
+
*/
|
|
52
|
+
timePosition?: TimelineTimePosition;
|
|
53
|
+
/**
|
|
54
|
+
* Fallback formatter for items that have `datetime` but no `time`. Return
|
|
55
|
+
* value is THC, so a plain string is fine.
|
|
56
|
+
*/
|
|
57
|
+
formatTime?: (datetime: string | Date, item: TimelineItem) => THC;
|
|
58
|
+
/** Override the marker content entirely (avatars, badges…) */
|
|
59
|
+
renderMarker?: Snippet<[TimelineSnippetArg]>;
|
|
60
|
+
/** Override the whole content cell (time, title, description, footer) */
|
|
61
|
+
renderItem?: Snippet<[TimelineSnippetArg]>;
|
|
62
|
+
/** Per-item footer area below the description (actions, attachments…) */
|
|
63
|
+
renderFooter?: Snippet<[TimelineSnippetArg]>;
|
|
64
|
+
/** Skip all default styling */
|
|
65
|
+
unstyled?: boolean;
|
|
66
|
+
/** Additional CSS classes */
|
|
67
|
+
class?: string;
|
|
68
|
+
/** Class for every item (`li`) */
|
|
69
|
+
classItem?: string;
|
|
70
|
+
/** Class for the marker */
|
|
71
|
+
classMarker?: string;
|
|
72
|
+
/** Class for the content cell */
|
|
73
|
+
classContent?: string;
|
|
74
|
+
/** Class for the time label (inline or opposite) */
|
|
75
|
+
classTime?: string;
|
|
76
|
+
/** Class for the title */
|
|
77
|
+
classTitle?: string;
|
|
78
|
+
/** Class for the description */
|
|
79
|
+
classDescription?: string;
|
|
80
|
+
/** Class for the footer area */
|
|
81
|
+
classFooter?: string;
|
|
82
|
+
/** Bindable element reference */
|
|
83
|
+
el?: HTMLOListElement;
|
|
84
|
+
}
|
|
85
|
+
declare const Timeline: import("svelte").Component<Props, {}, "el">;
|
|
86
|
+
type Timeline = ReturnType<typeof Timeline>;
|
|
87
|
+
export default Timeline;
|