@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
|
@@ -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;
|
|
@@ -0,0 +1,311 @@
|
|
|
1
|
+
/* ============================================================================
|
|
2
|
+
TIMELINE COMPONENT TOKENS
|
|
3
|
+
Override globally: :root { --stuic-timeline-marker-size: 1rem; }
|
|
4
|
+
Override locally: <Timeline style="--stuic-timeline-gap-vertical: 1rem;">
|
|
5
|
+
============================================================================ */
|
|
6
|
+
|
|
7
|
+
/* prettier-ignore */
|
|
8
|
+
:root {
|
|
9
|
+
/* Layout */
|
|
10
|
+
--stuic-timeline-gap: 1rem; /* column gap: opposite ↔ rail ↔ content */
|
|
11
|
+
--stuic-timeline-gap-vertical: 1.5rem; /* distance between items */
|
|
12
|
+
--stuic-timeline-line-height: 1.5rem; /* first-line box the marker centers on */
|
|
13
|
+
|
|
14
|
+
/* Connector (the vertical rail line) */
|
|
15
|
+
--stuic-timeline-connector-thickness: 2px;
|
|
16
|
+
--stuic-timeline-connector-bg: var(--stuic-color-border);
|
|
17
|
+
|
|
18
|
+
/* Marker: dot (no icon) */
|
|
19
|
+
--stuic-timeline-marker-size: 0.75rem;
|
|
20
|
+
--stuic-timeline-marker-bg: var(--stuic-color-muted-foreground);
|
|
21
|
+
|
|
22
|
+
/* Marker: icon bubble (item.icon) and custom (renderMarker) */
|
|
23
|
+
--stuic-timeline-marker-size-icon: 2rem;
|
|
24
|
+
--stuic-timeline-marker-bg-icon: var(--stuic-color-muted);
|
|
25
|
+
--stuic-timeline-marker-text-icon: var(--stuic-color-muted-foreground);
|
|
26
|
+
--stuic-timeline-marker-icon-size: 1rem; /* svg inside the bubble */
|
|
27
|
+
|
|
28
|
+
/* Marker ring: separates the marker from the line running behind it */
|
|
29
|
+
--stuic-timeline-marker-ring-width: 2px;
|
|
30
|
+
--stuic-timeline-marker-ring-color: var(--stuic-color-background);
|
|
31
|
+
|
|
32
|
+
/* Time */
|
|
33
|
+
--stuic-timeline-time-font-size: var(--text-sm);
|
|
34
|
+
--stuic-timeline-time-text: var(--stuic-color-muted-foreground);
|
|
35
|
+
|
|
36
|
+
/* Title */
|
|
37
|
+
--stuic-timeline-title-font-size: var(--text-base);
|
|
38
|
+
--stuic-timeline-title-font-weight: var(--font-weight-medium);
|
|
39
|
+
--stuic-timeline-title-text: var(--stuic-color-foreground);
|
|
40
|
+
|
|
41
|
+
/* Description */
|
|
42
|
+
--stuic-timeline-description-font-size: var(--text-sm);
|
|
43
|
+
--stuic-timeline-description-text: var(--stuic-color-muted-foreground);
|
|
44
|
+
--stuic-timeline-description-margin-top: 0.125rem;
|
|
45
|
+
|
|
46
|
+
/* Footer */
|
|
47
|
+
--stuic-timeline-footer-gap: 0.5rem;
|
|
48
|
+
--stuic-timeline-footer-margin-top: 0.5rem;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
@layer components {
|
|
52
|
+
/* ============================================================================
|
|
53
|
+
BASE — the list is a grid; every item is a subgrid row, so the rail (and
|
|
54
|
+
the opposite time column) line up across items regardless of content.
|
|
55
|
+
============================================================================ */
|
|
56
|
+
|
|
57
|
+
.stuic-timeline {
|
|
58
|
+
display: grid;
|
|
59
|
+
column-gap: var(--stuic-timeline-gap);
|
|
60
|
+
list-style: none;
|
|
61
|
+
margin: 0;
|
|
62
|
+
padding: 0;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
.stuic-timeline[data-align="start"][data-time-position="inline"] {
|
|
66
|
+
grid-template-columns: auto minmax(0, 1fr);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
.stuic-timeline[data-align="start"][data-time-position="opposite"] {
|
|
70
|
+
grid-template-columns: auto auto minmax(0, 1fr);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
.stuic-timeline[data-align="alternate"] {
|
|
74
|
+
grid-template-columns: minmax(0, 1fr) auto minmax(0, 1fr);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
.stuic-timeline-item {
|
|
78
|
+
display: grid;
|
|
79
|
+
grid-column: 1 / -1;
|
|
80
|
+
grid-template-columns: subgrid;
|
|
81
|
+
padding-bottom: var(--stuic-timeline-gap-vertical);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
.stuic-timeline-item:last-child {
|
|
85
|
+
padding-bottom: 0;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/* every cell sits on the item's single row; columns are assigned below */
|
|
89
|
+
.stuic-timeline-opposite,
|
|
90
|
+
.stuic-timeline-rail,
|
|
91
|
+
.stuic-timeline-content {
|
|
92
|
+
grid-row: 1;
|
|
93
|
+
min-width: 0;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/* ============================================================================
|
|
97
|
+
COLUMN PLACEMENT
|
|
98
|
+
============================================================================ */
|
|
99
|
+
|
|
100
|
+
/* start + inline: rail | content */
|
|
101
|
+
.stuic-timeline[data-align="start"][data-time-position="inline"] .stuic-timeline-rail {
|
|
102
|
+
grid-column: 1;
|
|
103
|
+
}
|
|
104
|
+
.stuic-timeline[data-align="start"][data-time-position="inline"]
|
|
105
|
+
.stuic-timeline-content {
|
|
106
|
+
grid-column: 2;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/* start + opposite: time | rail | content */
|
|
110
|
+
.stuic-timeline[data-align="start"][data-time-position="opposite"]
|
|
111
|
+
.stuic-timeline-opposite {
|
|
112
|
+
grid-column: 1;
|
|
113
|
+
text-align: end;
|
|
114
|
+
}
|
|
115
|
+
.stuic-timeline[data-align="start"][data-time-position="opposite"]
|
|
116
|
+
.stuic-timeline-rail {
|
|
117
|
+
grid-column: 2;
|
|
118
|
+
}
|
|
119
|
+
.stuic-timeline[data-align="start"][data-time-position="opposite"]
|
|
120
|
+
.stuic-timeline-content {
|
|
121
|
+
grid-column: 3;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/* alternate: centered rail; odd items → content end side, even → start side */
|
|
125
|
+
.stuic-timeline[data-align="alternate"] .stuic-timeline-rail {
|
|
126
|
+
grid-column: 2;
|
|
127
|
+
}
|
|
128
|
+
.stuic-timeline[data-align="alternate"]
|
|
129
|
+
.stuic-timeline-item:nth-child(odd)
|
|
130
|
+
.stuic-timeline-content {
|
|
131
|
+
grid-column: 3;
|
|
132
|
+
}
|
|
133
|
+
.stuic-timeline[data-align="alternate"]
|
|
134
|
+
.stuic-timeline-item:nth-child(odd)
|
|
135
|
+
.stuic-timeline-opposite {
|
|
136
|
+
grid-column: 1;
|
|
137
|
+
text-align: end;
|
|
138
|
+
}
|
|
139
|
+
.stuic-timeline[data-align="alternate"]
|
|
140
|
+
.stuic-timeline-item:nth-child(even)
|
|
141
|
+
.stuic-timeline-content {
|
|
142
|
+
grid-column: 1;
|
|
143
|
+
text-align: end;
|
|
144
|
+
}
|
|
145
|
+
.stuic-timeline[data-align="alternate"]
|
|
146
|
+
.stuic-timeline-item:nth-child(even)
|
|
147
|
+
.stuic-timeline-opposite {
|
|
148
|
+
grid-column: 3;
|
|
149
|
+
}
|
|
150
|
+
.stuic-timeline[data-align="alternate"]
|
|
151
|
+
.stuic-timeline-item:nth-child(even)
|
|
152
|
+
.stuic-timeline-footer {
|
|
153
|
+
justify-content: flex-end;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/* ============================================================================
|
|
157
|
+
RAIL + CONNECTOR
|
|
158
|
+
The line runs behind the marker in two halves (top → marker center, marker
|
|
159
|
+
center → bottom) so no item needs to know its neighbour's marker size. The
|
|
160
|
+
marker's center is always at half the first-line height.
|
|
161
|
+
============================================================================ */
|
|
162
|
+
|
|
163
|
+
.stuic-timeline-rail {
|
|
164
|
+
position: relative;
|
|
165
|
+
display: flex;
|
|
166
|
+
flex-direction: column;
|
|
167
|
+
align-items: center;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
.stuic-timeline-rail::before,
|
|
171
|
+
.stuic-timeline-rail::after {
|
|
172
|
+
content: "";
|
|
173
|
+
position: absolute;
|
|
174
|
+
left: 50%;
|
|
175
|
+
width: var(--stuic-timeline-connector-thickness);
|
|
176
|
+
transform: translateX(-50%);
|
|
177
|
+
background: var(--stuic-timeline-connector-bg);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
.stuic-timeline-rail::before {
|
|
181
|
+
top: 0;
|
|
182
|
+
height: calc(var(--stuic-timeline-line-height) / 2);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/* the item's bottom padding (gap-vertical) lies outside the grid row, so reach
|
|
186
|
+
through it — the next item's ::before picks the line up at its top edge */
|
|
187
|
+
.stuic-timeline-rail::after {
|
|
188
|
+
top: calc(var(--stuic-timeline-line-height) / 2);
|
|
189
|
+
bottom: calc(-1 * var(--stuic-timeline-gap-vertical));
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
.stuic-timeline-item:first-child .stuic-timeline-rail::before,
|
|
193
|
+
.stuic-timeline-item:last-child .stuic-timeline-rail::after {
|
|
194
|
+
display: none;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
/* ============================================================================
|
|
198
|
+
MARKER
|
|
199
|
+
============================================================================ */
|
|
200
|
+
|
|
201
|
+
.stuic-timeline-marker {
|
|
202
|
+
position: relative;
|
|
203
|
+
z-index: 1;
|
|
204
|
+
display: inline-flex;
|
|
205
|
+
align-items: center;
|
|
206
|
+
justify-content: center;
|
|
207
|
+
flex-shrink: 0;
|
|
208
|
+
box-sizing: border-box;
|
|
209
|
+
width: var(--_marker-size);
|
|
210
|
+
height: var(--_marker-size);
|
|
211
|
+
/* center on the first line of the content (may go negative for big markers) */
|
|
212
|
+
margin-top: calc((var(--stuic-timeline-line-height) - var(--_marker-size)) / 2);
|
|
213
|
+
border-radius: var(--stuic-timeline-marker-radius, calc(infinity * 1px));
|
|
214
|
+
box-shadow: 0 0 0 var(--stuic-timeline-marker-ring-width)
|
|
215
|
+
var(--stuic-timeline-marker-ring-color);
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
.stuic-timeline-marker[data-marker="dot"] {
|
|
219
|
+
--_marker-size: var(--stuic-timeline-marker-size);
|
|
220
|
+
background: var(--stuic-timeline-marker-bg);
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
.stuic-timeline-marker[data-marker="icon"] {
|
|
224
|
+
--_marker-size: var(--stuic-timeline-marker-size-icon);
|
|
225
|
+
background: var(--stuic-timeline-marker-bg-icon);
|
|
226
|
+
color: var(--stuic-timeline-marker-text-icon);
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
.stuic-timeline-marker[data-marker="icon"] svg {
|
|
230
|
+
display: block;
|
|
231
|
+
width: var(--stuic-timeline-marker-icon-size);
|
|
232
|
+
height: var(--stuic-timeline-marker-icon-size);
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
/* custom (renderMarker): only the box — the snippet brings its own look */
|
|
236
|
+
.stuic-timeline-marker[data-marker="custom"] {
|
|
237
|
+
--_marker-size: var(--stuic-timeline-marker-size-icon);
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
/* ============================================================================
|
|
241
|
+
INTENT — dot fills with the color, the bubble takes a soft tint
|
|
242
|
+
============================================================================ */
|
|
243
|
+
|
|
244
|
+
.stuic-timeline-item[data-intent="primary"] {
|
|
245
|
+
--_intent: var(--stuic-color-primary);
|
|
246
|
+
}
|
|
247
|
+
.stuic-timeline-item[data-intent="accent"] {
|
|
248
|
+
--_intent: var(--stuic-color-accent);
|
|
249
|
+
}
|
|
250
|
+
.stuic-timeline-item[data-intent="destructive"] {
|
|
251
|
+
--_intent: var(--stuic-color-destructive);
|
|
252
|
+
}
|
|
253
|
+
.stuic-timeline-item[data-intent="warning"] {
|
|
254
|
+
--_intent: var(--stuic-color-warning);
|
|
255
|
+
}
|
|
256
|
+
.stuic-timeline-item[data-intent="success"] {
|
|
257
|
+
--_intent: var(--stuic-color-success);
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
.stuic-timeline-item[data-intent] .stuic-timeline-marker[data-marker="dot"] {
|
|
261
|
+
background: var(--_intent);
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
.stuic-timeline-item[data-intent] .stuic-timeline-marker[data-marker="icon"] {
|
|
265
|
+
background: color-mix(in srgb, var(--_intent) 15%, var(--stuic-color-background));
|
|
266
|
+
color: var(--_intent);
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
/* ============================================================================
|
|
270
|
+
CONTENT: time, title, description, footer
|
|
271
|
+
============================================================================ */
|
|
272
|
+
|
|
273
|
+
.stuic-timeline-time {
|
|
274
|
+
display: block;
|
|
275
|
+
font-size: var(--stuic-timeline-time-font-size);
|
|
276
|
+
line-height: var(--stuic-timeline-line-height);
|
|
277
|
+
color: var(--stuic-timeline-time-text);
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
.stuic-timeline-title {
|
|
281
|
+
font-size: var(--stuic-timeline-title-font-size);
|
|
282
|
+
font-weight: var(--stuic-timeline-title-font-weight);
|
|
283
|
+
line-height: var(--stuic-timeline-line-height);
|
|
284
|
+
color: var(--stuic-timeline-title-text);
|
|
285
|
+
overflow-wrap: anywhere;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
.stuic-timeline-link {
|
|
289
|
+
color: inherit;
|
|
290
|
+
text-decoration: none;
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
.stuic-timeline-link:hover {
|
|
294
|
+
text-decoration: underline;
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
.stuic-timeline-description {
|
|
298
|
+
margin-top: var(--stuic-timeline-description-margin-top);
|
|
299
|
+
font-size: var(--stuic-timeline-description-font-size);
|
|
300
|
+
color: var(--stuic-timeline-description-text);
|
|
301
|
+
overflow-wrap: anywhere;
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
.stuic-timeline-footer {
|
|
305
|
+
display: flex;
|
|
306
|
+
flex-wrap: wrap;
|
|
307
|
+
align-items: center;
|
|
308
|
+
gap: var(--stuic-timeline-footer-gap);
|
|
309
|
+
margin-top: var(--stuic-timeline-footer-margin-top);
|
|
310
|
+
}
|
|
311
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default as Timeline, type Props as TimelineProps, type TimelineItem, type TimelineAlign, type TimelineTimePosition, type TimelineMarkerKind, type TimelineSnippetArg, } from "./Timeline.svelte";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default as Timeline, } from "./Timeline.svelte";
|
package/dist/index.css
CHANGED
|
@@ -112,6 +112,7 @@ In practice:
|
|
|
112
112
|
@import "./components/Switch/index.css";
|
|
113
113
|
@import "./components/TabbedMenu/index.css";
|
|
114
114
|
@import "./components/ThemePreview/index.css";
|
|
115
|
+
@import "./components/Timeline/index.css";
|
|
115
116
|
@import "./components/Tree/index.css";
|
|
116
117
|
@import "./components/TwCheck/index.css";
|
|
117
118
|
@import "./components/UserAvatarMenu/index.css";
|
package/dist/index.d.ts
CHANGED
|
@@ -84,6 +84,7 @@ export * from "./components/Switch/index.js";
|
|
|
84
84
|
export * from "./components/TabbedMenu/index.js";
|
|
85
85
|
export * from "./components/Thc/index.js";
|
|
86
86
|
export * from "./components/ThemePreview/index.js";
|
|
87
|
+
export * from "./components/Timeline/index.js";
|
|
87
88
|
export * from "./components/Tree/index.js";
|
|
88
89
|
export * from "./components/TwCheck/index.js";
|
|
89
90
|
export * from "./components/TypeaheadInput/index.js";
|
package/dist/index.js
CHANGED
|
@@ -90,6 +90,7 @@ export * from "./components/Switch/index.js";
|
|
|
90
90
|
export * from "./components/TabbedMenu/index.js";
|
|
91
91
|
export * from "./components/Thc/index.js";
|
|
92
92
|
export * from "./components/ThemePreview/index.js";
|
|
93
|
+
export * from "./components/Timeline/index.js";
|
|
93
94
|
// NOTE: TrendChart is deliberately NOT exported here. It statically imports the
|
|
94
95
|
// optional @marianmeres/trend-chart peer, which would then be pulled into the
|
|
95
96
|
// root entry graph of every consumer. It ships as the `@marianmeres/stuic/trend-chart`
|