@noahbaumgartner/svelte-ui 0.0.7 → 0.0.8
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/dist/components/button/Button.svelte +0 -10
- package/dist/components/lightbox/Lightbox.svelte +136 -0
- package/dist/components/lightbox/Lightbox.svelte.d.ts +14 -0
- package/dist/components/media-preview/MediaPreview.svelte +110 -0
- package/dist/components/media-preview/MediaPreview.svelte.d.ts +11 -0
- package/dist/components/mesh-gradient/MeshGradient.svelte +19 -2
- package/dist/components/page-header/PageHeader.svelte +83 -0
- package/dist/components/page-header/PageHeader.svelte.d.ts +13 -0
- package/dist/components/section/Section.svelte +9 -5
- package/dist/components/section/Section.svelte.d.ts +6 -3
- package/dist/components/select/Select.svelte.d.ts +1 -1
- package/dist/components/table-of-contents/TableOfContents.svelte +296 -0
- package/dist/components/table-of-contents/TableOfContents.svelte.d.ts +7 -0
- package/dist/components/theme-toggle/ThemeToggle.svelte +40 -0
- package/dist/components/theme-toggle/ThemeToggle.svelte.d.ts +7 -0
- package/dist/components/timeline/Timeline.svelte +122 -0
- package/dist/components/timeline/Timeline.svelte.d.ts +21 -0
- package/dist/icon.d.ts +7 -7
- package/dist/icons/GithubIcon.svelte +25 -0
- package/dist/icons/GithubIcon.svelte.d.ts +4 -0
- package/dist/index.d.ts +8 -2
- package/dist/index.js +7 -1
- package/dist/internal/icons/Monitor.svelte +15 -0
- package/dist/internal/icons/Monitor.svelte.d.ts +4 -0
- package/dist/internal/icons/Moon.svelte +18 -0
- package/dist/internal/icons/Moon.svelte.d.ts +4 -0
- package/dist/internal/icons/Pause.svelte +14 -0
- package/dist/internal/icons/Pause.svelte.d.ts +4 -0
- package/dist/internal/icons/Play.svelte +16 -0
- package/dist/internal/icons/Play.svelte.d.ts +4 -0
- package/dist/internal/icons/Sun.svelte +21 -0
- package/dist/internal/icons/Sun.svelte.d.ts +4 -0
- package/dist/styles/tokens.css +7 -0
- package/package.json +4 -2
- package/dist/components/entry/Entry.svelte +0 -94
- package/dist/components/entry/Entry.svelte.d.ts +0 -16
|
@@ -0,0 +1,296 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { tick } from 'svelte';
|
|
3
|
+
import { SvelteSet } from 'svelte/reactivity';
|
|
4
|
+
import ChevronDown from '../../internal/icons/ChevronDown.svelte';
|
|
5
|
+
|
|
6
|
+
let {
|
|
7
|
+
container,
|
|
8
|
+
variant = 'sidebar'
|
|
9
|
+
}: {
|
|
10
|
+
container: HTMLElement | undefined;
|
|
11
|
+
variant?: 'sidebar' | 'mobile';
|
|
12
|
+
} = $props();
|
|
13
|
+
|
|
14
|
+
type TocItem = { id: string; text: string; level: number };
|
|
15
|
+
|
|
16
|
+
let items: TocItem[] = $state([]);
|
|
17
|
+
let activeId: string | null = $state(null);
|
|
18
|
+
let expanded = $state(false);
|
|
19
|
+
|
|
20
|
+
let activeItem = $derived(items.find((item) => item.id === activeId) ?? items[0]);
|
|
21
|
+
|
|
22
|
+
function slugify(text: string) {
|
|
23
|
+
return text
|
|
24
|
+
.toLowerCase()
|
|
25
|
+
.trim()
|
|
26
|
+
.replace(/[äáàâ]/g, 'a')
|
|
27
|
+
.replace(/[öóòô]/g, 'o')
|
|
28
|
+
.replace(/[üúùû]/g, 'u')
|
|
29
|
+
.replace(/ß/g, 'ss')
|
|
30
|
+
.replace(/[^a-z0-9]+/g, '-')
|
|
31
|
+
.replace(/^-+|-+$/g, '');
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
$effect(() => {
|
|
35
|
+
if (!container) return;
|
|
36
|
+
|
|
37
|
+
const headings = Array.from(container.querySelectorAll('h1, h2')) as HTMLElement[];
|
|
38
|
+
const usedIds = new SvelteSet<string>();
|
|
39
|
+
|
|
40
|
+
items = headings.map((heading) => {
|
|
41
|
+
if (!heading.id) {
|
|
42
|
+
let id = slugify(heading.textContent ?? '');
|
|
43
|
+
let unique = id;
|
|
44
|
+
let i = 2;
|
|
45
|
+
while (usedIds.has(unique)) {
|
|
46
|
+
unique = `${id}-${i}`;
|
|
47
|
+
i++;
|
|
48
|
+
}
|
|
49
|
+
heading.id = unique;
|
|
50
|
+
}
|
|
51
|
+
usedIds.add(heading.id);
|
|
52
|
+
return {
|
|
53
|
+
id: heading.id,
|
|
54
|
+
text: heading.textContent ?? '',
|
|
55
|
+
level: heading.tagName === 'H1' ? 1 : 2
|
|
56
|
+
};
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
const observer = new IntersectionObserver(
|
|
60
|
+
(entries) => {
|
|
61
|
+
for (const entry of entries) {
|
|
62
|
+
if (entry.isIntersecting) {
|
|
63
|
+
activeId = (entry.target as HTMLElement).id;
|
|
64
|
+
break;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
},
|
|
68
|
+
{ rootMargin: '0px 0px -70% 0px', threshold: 0 }
|
|
69
|
+
);
|
|
70
|
+
|
|
71
|
+
headings.forEach((heading) => observer.observe(heading));
|
|
72
|
+
|
|
73
|
+
return () => observer.disconnect();
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
const scrollOffset = $derived(variant === 'mobile' ? 130 : 64);
|
|
77
|
+
|
|
78
|
+
async function handleClick(event: MouseEvent, id: string) {
|
|
79
|
+
event.preventDefault();
|
|
80
|
+
expanded = false;
|
|
81
|
+
// Wait for the mobile panel's collapse to actually reach the DOM before
|
|
82
|
+
// measuring the heading position, otherwise the still-expanded panel's
|
|
83
|
+
// extra height gets baked into the scroll target and it overshoots.
|
|
84
|
+
await tick();
|
|
85
|
+
const target = document.getElementById(id);
|
|
86
|
+
if (!target) return;
|
|
87
|
+
const top = target.getBoundingClientRect().top + window.scrollY - scrollOffset;
|
|
88
|
+
|
|
89
|
+
// Mobile Safari can silently drop a new smooth scroll if one is still
|
|
90
|
+
// animating, so snap to the current position first to clear it before
|
|
91
|
+
// requesting the next smooth scroll on the following frame.
|
|
92
|
+
window.scrollTo({ top: window.scrollY, behavior: 'instant' });
|
|
93
|
+
requestAnimationFrame(() => {
|
|
94
|
+
window.scrollTo({ top, behavior: 'smooth' });
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
history.replaceState(null, '', `#${id}`);
|
|
98
|
+
}
|
|
99
|
+
</script>
|
|
100
|
+
|
|
101
|
+
{#if items.length > 0}
|
|
102
|
+
{#if variant === 'mobile'}
|
|
103
|
+
<div class="toc-mobile">
|
|
104
|
+
{#if expanded}
|
|
105
|
+
<div class="toc-mobile-trigger">
|
|
106
|
+
<a
|
|
107
|
+
href="#{items[0].id}"
|
|
108
|
+
class="toc-item toc-mobile-title"
|
|
109
|
+
class:toc-item--sub={items[0].level === 2}
|
|
110
|
+
class:toc-item--active={activeId === items[0].id}
|
|
111
|
+
onclick={(event) => handleClick(event, items[0].id)}
|
|
112
|
+
>
|
|
113
|
+
{items[0].text}
|
|
114
|
+
</a>
|
|
115
|
+
<button
|
|
116
|
+
type="button"
|
|
117
|
+
class="toc-mobile-chevron-btn"
|
|
118
|
+
onclick={() => (expanded = !expanded)}
|
|
119
|
+
aria-expanded={expanded}
|
|
120
|
+
aria-label="Inhaltsverzeichnis einklappen"
|
|
121
|
+
>
|
|
122
|
+
<ChevronDown class="toc-mobile-chevron" style="transform: rotate(180deg)" />
|
|
123
|
+
</button>
|
|
124
|
+
</div>
|
|
125
|
+
{:else}
|
|
126
|
+
<button
|
|
127
|
+
type="button"
|
|
128
|
+
class="toc-mobile-trigger"
|
|
129
|
+
onclick={() => (expanded = !expanded)}
|
|
130
|
+
aria-expanded={expanded}
|
|
131
|
+
aria-label="Inhaltsverzeichnis ausklappen"
|
|
132
|
+
>
|
|
133
|
+
<span class="toc-mobile-title">{activeItem?.text}</span>
|
|
134
|
+
<ChevronDown class="toc-mobile-chevron" />
|
|
135
|
+
</button>
|
|
136
|
+
{/if}
|
|
137
|
+
|
|
138
|
+
{#if expanded}
|
|
139
|
+
<nav class="toc-mobile-panel" aria-label="Inhaltsverzeichnis">
|
|
140
|
+
{#each items.slice(1) as item (item.id)}
|
|
141
|
+
<a
|
|
142
|
+
href="#{item.id}"
|
|
143
|
+
class="toc-item"
|
|
144
|
+
class:toc-item--sub={item.level === 2}
|
|
145
|
+
class:toc-item--active={activeId === item.id}
|
|
146
|
+
onclick={(event) => handleClick(event, item.id)}
|
|
147
|
+
>
|
|
148
|
+
{item.text}
|
|
149
|
+
</a>
|
|
150
|
+
{/each}
|
|
151
|
+
</nav>
|
|
152
|
+
{/if}
|
|
153
|
+
</div>
|
|
154
|
+
{:else}
|
|
155
|
+
<nav class="toc" aria-label="Inhaltsverzeichnis">
|
|
156
|
+
{#each items as item (item.id)}
|
|
157
|
+
<a
|
|
158
|
+
href="#{item.id}"
|
|
159
|
+
class="toc-item"
|
|
160
|
+
class:toc-item--sub={item.level === 2}
|
|
161
|
+
class:toc-item--active={activeId === item.id}
|
|
162
|
+
onclick={(event) => handleClick(event, item.id)}
|
|
163
|
+
>
|
|
164
|
+
{item.text}
|
|
165
|
+
</a>
|
|
166
|
+
{/each}
|
|
167
|
+
</nav>
|
|
168
|
+
{/if}
|
|
169
|
+
{/if}
|
|
170
|
+
|
|
171
|
+
<style>
|
|
172
|
+
.toc {
|
|
173
|
+
display: flex;
|
|
174
|
+
flex-direction: column;
|
|
175
|
+
gap: 10px;
|
|
176
|
+
user-select: none;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
.toc-item {
|
|
180
|
+
font-size: 12px;
|
|
181
|
+
color: var(--color-text-muted);
|
|
182
|
+
text-decoration: none;
|
|
183
|
+
line-height: 1.4;
|
|
184
|
+
border-radius: 4px;
|
|
185
|
+
outline: none;
|
|
186
|
+
transition: color 150ms ease;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
.toc-item:hover {
|
|
190
|
+
color: var(--color-text);
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
.toc-item:focus-visible {
|
|
194
|
+
outline: 2px solid var(--color-ink);
|
|
195
|
+
outline-offset: 2px;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
.toc-item--sub {
|
|
199
|
+
padding-left: 16px;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
.toc-item--active {
|
|
203
|
+
color: var(--color-text);
|
|
204
|
+
font-weight: 600;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
.toc-mobile {
|
|
208
|
+
background-color: var(--color-bg);
|
|
209
|
+
border-bottom: 1px solid var(--color-border);
|
|
210
|
+
user-select: none;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
.toc-mobile-trigger {
|
|
214
|
+
display: flex;
|
|
215
|
+
align-items: center;
|
|
216
|
+
justify-content: space-between;
|
|
217
|
+
gap: 12px;
|
|
218
|
+
width: 100%;
|
|
219
|
+
padding: 14px 24px;
|
|
220
|
+
font-size: 14px;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
button.toc-mobile-trigger {
|
|
224
|
+
background-color: transparent;
|
|
225
|
+
border: none;
|
|
226
|
+
font: inherit;
|
|
227
|
+
font-size: 14px;
|
|
228
|
+
color: var(--color-text);
|
|
229
|
+
text-align: left;
|
|
230
|
+
outline: none;
|
|
231
|
+
cursor: pointer;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
button.toc-mobile-trigger:focus-visible {
|
|
235
|
+
outline: 2px solid var(--color-ink);
|
|
236
|
+
outline-offset: -2px;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
.toc-mobile-title {
|
|
240
|
+
flex: 1;
|
|
241
|
+
overflow: hidden;
|
|
242
|
+
text-overflow: ellipsis;
|
|
243
|
+
white-space: nowrap;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
a.toc-mobile-title {
|
|
247
|
+
font-size: 14px;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
span.toc-mobile-title {
|
|
251
|
+
color: var(--color-text);
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
.toc-mobile-chevron-btn {
|
|
255
|
+
display: flex;
|
|
256
|
+
align-items: center;
|
|
257
|
+
justify-content: center;
|
|
258
|
+
flex-shrink: 0;
|
|
259
|
+
padding: 0;
|
|
260
|
+
background-color: transparent;
|
|
261
|
+
border: none;
|
|
262
|
+
border-radius: 4px;
|
|
263
|
+
color: var(--color-text);
|
|
264
|
+
outline: none;
|
|
265
|
+
cursor: pointer;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
.toc-mobile-chevron-btn:focus-visible {
|
|
269
|
+
outline: 2px solid var(--color-ink);
|
|
270
|
+
outline-offset: 2px;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
.toc-mobile-trigger :global(.toc-mobile-chevron) {
|
|
274
|
+
width: 16px;
|
|
275
|
+
height: 16px;
|
|
276
|
+
flex-shrink: 0;
|
|
277
|
+
transition: transform 200ms ease;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
.toc-mobile-panel {
|
|
281
|
+
display: flex;
|
|
282
|
+
flex-direction: column;
|
|
283
|
+
gap: 16px;
|
|
284
|
+
padding: 4px 24px 20px;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
.toc-mobile-panel .toc-item {
|
|
288
|
+
font-size: 14px;
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
@media (min-width: 640px) {
|
|
292
|
+
.toc-mobile {
|
|
293
|
+
display: none;
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
</style>
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
type $$ComponentProps = {
|
|
2
|
+
container: HTMLElement | undefined;
|
|
3
|
+
variant?: 'sidebar' | 'mobile';
|
|
4
|
+
};
|
|
5
|
+
declare const TableOfContents: import("svelte").Component<$$ComponentProps, {}, "">;
|
|
6
|
+
type TableOfContents = ReturnType<typeof TableOfContents>;
|
|
7
|
+
export default TableOfContents;
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { onMount } from 'svelte';
|
|
3
|
+
import Sun from '../../internal/icons/Sun.svelte';
|
|
4
|
+
import Moon from '../../internal/icons/Moon.svelte';
|
|
5
|
+
import Button from '../button/Button.svelte';
|
|
6
|
+
|
|
7
|
+
type Theme = 'light' | 'dark';
|
|
8
|
+
|
|
9
|
+
/** Accessible name of the button for each current theme; the click switches to the other one. */
|
|
10
|
+
let {
|
|
11
|
+
labels = { light: 'Theme: Light', dark: 'Theme: Dark' }
|
|
12
|
+
}: { labels?: Record<Theme, string> } = $props();
|
|
13
|
+
|
|
14
|
+
let theme: Theme = $state('light');
|
|
15
|
+
|
|
16
|
+
const icons = { light: Sun, dark: Moon };
|
|
17
|
+
|
|
18
|
+
onMount(() => {
|
|
19
|
+
const query = window.matchMedia('(prefers-color-scheme: dark)');
|
|
20
|
+
const systemTheme = (): Theme => (query.matches ? 'dark' : 'light');
|
|
21
|
+
|
|
22
|
+
const stored = localStorage.getItem('theme');
|
|
23
|
+
theme = stored === 'light' || stored === 'dark' ? stored : systemTheme();
|
|
24
|
+
|
|
25
|
+
// Without an explicit choice the toggle keeps following the system.
|
|
26
|
+
const onSystemChange = () => {
|
|
27
|
+
if (!localStorage.getItem('theme')) theme = systemTheme();
|
|
28
|
+
};
|
|
29
|
+
query.addEventListener('change', onSystemChange);
|
|
30
|
+
return () => query.removeEventListener('change', onSystemChange);
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
function toggle() {
|
|
34
|
+
theme = theme === 'light' ? 'dark' : 'light';
|
|
35
|
+
document.documentElement.dataset.theme = theme;
|
|
36
|
+
localStorage.setItem('theme', theme);
|
|
37
|
+
}
|
|
38
|
+
</script>
|
|
39
|
+
|
|
40
|
+
<Button variant="ghost" size="lg" icon={icons[theme]} label={labels[theme]} onclick={toggle} />
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
<script module lang="ts">
|
|
2
|
+
export type TimelineItem = {
|
|
3
|
+
/** Muted text left of the item, e.g. "2021 - 2025". */
|
|
4
|
+
period: string;
|
|
5
|
+
/** Rendered as an `<h3>`. */
|
|
6
|
+
title: string;
|
|
7
|
+
/** Muted line below the title, e.g. a company or institution. */
|
|
8
|
+
subtitle?: string;
|
|
9
|
+
/** Turns the subtitle into a Link. */
|
|
10
|
+
href?: string;
|
|
11
|
+
description?: string;
|
|
12
|
+
};
|
|
13
|
+
</script>
|
|
14
|
+
|
|
15
|
+
<script lang="ts">
|
|
16
|
+
import type { HTMLAttributes } from 'svelte/elements';
|
|
17
|
+
import Link from '../link/Link.svelte';
|
|
18
|
+
|
|
19
|
+
type Props = Omit<HTMLAttributes<HTMLElement>, 'title'> & {
|
|
20
|
+
/** Rendered as an `<h2>` next to the list. */
|
|
21
|
+
title?: string;
|
|
22
|
+
items: TimelineItem[];
|
|
23
|
+
ref?: HTMLElement | null;
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
let { title, items, class: className, ref = $bindable(null), ...rest }: Props = $props();
|
|
27
|
+
</script>
|
|
28
|
+
|
|
29
|
+
<section {...rest} bind:this={ref} class={['timeline', className]}>
|
|
30
|
+
{#if title}
|
|
31
|
+
<h2 class="timeline-title">{title}</h2>
|
|
32
|
+
{/if}
|
|
33
|
+
<ol class="timeline-list">
|
|
34
|
+
{#each items as item (item.title + item.period)}
|
|
35
|
+
<li class="timeline-item">
|
|
36
|
+
<span class="timeline-period">{item.period}</span>
|
|
37
|
+
<div class="timeline-body">
|
|
38
|
+
<h3 class="timeline-item-title">{item.title}</h3>
|
|
39
|
+
{#if item.subtitle}
|
|
40
|
+
<p class="timeline-subtitle">
|
|
41
|
+
{#if item.href}
|
|
42
|
+
<Link href={item.href}>{item.subtitle}</Link>
|
|
43
|
+
{:else}
|
|
44
|
+
{item.subtitle}
|
|
45
|
+
{/if}
|
|
46
|
+
</p>
|
|
47
|
+
{/if}
|
|
48
|
+
{#if item.description}
|
|
49
|
+
<p class="timeline-description">{item.description}</p>
|
|
50
|
+
{/if}
|
|
51
|
+
</div>
|
|
52
|
+
</li>
|
|
53
|
+
{/each}
|
|
54
|
+
</ol>
|
|
55
|
+
</section>
|
|
56
|
+
|
|
57
|
+
<style>
|
|
58
|
+
.timeline {
|
|
59
|
+
display: grid;
|
|
60
|
+
gap: 32px;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
.timeline-title {
|
|
64
|
+
font-size: clamp(24px, 2vw + 16px, 32px);
|
|
65
|
+
font-weight: 500;
|
|
66
|
+
letter-spacing: -0.01em;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
.timeline-list {
|
|
70
|
+
border-bottom: 1px solid var(--color-border);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
.timeline-item {
|
|
74
|
+
display: grid;
|
|
75
|
+
gap: 8px;
|
|
76
|
+
padding-block: 24px;
|
|
77
|
+
border-top: 1px solid var(--color-border);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
.timeline-period {
|
|
81
|
+
font-size: 14px;
|
|
82
|
+
color: var(--color-text-muted);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
.timeline-body {
|
|
86
|
+
display: flex;
|
|
87
|
+
flex-direction: column;
|
|
88
|
+
gap: 4px;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
.timeline-item-title {
|
|
92
|
+
font-size: 20px;
|
|
93
|
+
font-weight: 500;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
.timeline-subtitle {
|
|
97
|
+
font-size: 15px;
|
|
98
|
+
color: var(--color-text-muted);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
.timeline-description {
|
|
102
|
+
margin-top: 8px;
|
|
103
|
+
max-width: 640px;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
@media (min-width: 768px) {
|
|
107
|
+
.timeline {
|
|
108
|
+
grid-template-columns: 1fr 2fr;
|
|
109
|
+
gap: 64px;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
.timeline-item {
|
|
113
|
+
grid-template-columns: 180px 1fr;
|
|
114
|
+
gap: 32px;
|
|
115
|
+
padding-block: 32px;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
.timeline-period {
|
|
119
|
+
padding-top: 4px;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
</style>
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
export type TimelineItem = {
|
|
2
|
+
/** Muted text left of the item, e.g. "2021 - 2025". */
|
|
3
|
+
period: string;
|
|
4
|
+
/** Rendered as an `<h3>`. */
|
|
5
|
+
title: string;
|
|
6
|
+
/** Muted line below the title, e.g. a company or institution. */
|
|
7
|
+
subtitle?: string;
|
|
8
|
+
/** Turns the subtitle into a Link. */
|
|
9
|
+
href?: string;
|
|
10
|
+
description?: string;
|
|
11
|
+
};
|
|
12
|
+
import type { HTMLAttributes } from 'svelte/elements';
|
|
13
|
+
type Props = Omit<HTMLAttributes<HTMLElement>, 'title'> & {
|
|
14
|
+
/** Rendered as an `<h2>` next to the list. */
|
|
15
|
+
title?: string;
|
|
16
|
+
items: TimelineItem[];
|
|
17
|
+
ref?: HTMLElement | null;
|
|
18
|
+
};
|
|
19
|
+
declare const Timeline: import("svelte").Component<Props, {}, "ref">;
|
|
20
|
+
type Timeline = ReturnType<typeof Timeline>;
|
|
21
|
+
export default Timeline;
|
package/dist/icon.d.ts
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { LucideIcon, LucideProps } from '@lucide/svelte';
|
|
2
|
+
export type { LucideProps as IconProps };
|
|
2
3
|
/**
|
|
3
|
-
* A Svelte component that renders an icon
|
|
4
|
-
* (
|
|
4
|
+
* A Svelte component that renders an icon and takes the props of a Lucide icon
|
|
5
|
+
* (`size`, `strokeWidth`, `class`, `aria-hidden`, …). Any `@lucide/svelte` icon
|
|
6
|
+
* qualifies, as does an inline SVG component typed with `IconProps` (see
|
|
7
|
+
* `icons/`).
|
|
5
8
|
*
|
|
6
9
|
* Components only pass `class` and `aria-hidden`; the size comes from CSS, so
|
|
7
10
|
* the icon should fill its box (`width`/`height: 1em` or a fixed size is fine).
|
|
8
11
|
*/
|
|
9
|
-
export type IconComponent =
|
|
10
|
-
class?: string;
|
|
11
|
-
'aria-hidden'?: boolean | 'true' | 'false';
|
|
12
|
-
}>;
|
|
12
|
+
export type IconComponent = LucideIcon;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { IconProps } from '../icon.js';
|
|
3
|
+
|
|
4
|
+
// Lucide dropped its brand icons in v1, so the GitHub mark lives here. It
|
|
5
|
+
// takes the same props as a Lucide icon so it can be passed as `icon`.
|
|
6
|
+
let { size = 24, strokeWidth = 2, class: className, ...rest }: IconProps = $props();
|
|
7
|
+
</script>
|
|
8
|
+
|
|
9
|
+
<svg
|
|
10
|
+
{...rest}
|
|
11
|
+
class={className}
|
|
12
|
+
width={size}
|
|
13
|
+
height={size}
|
|
14
|
+
viewBox="0 0 24 24"
|
|
15
|
+
fill="none"
|
|
16
|
+
stroke="currentColor"
|
|
17
|
+
stroke-width={strokeWidth}
|
|
18
|
+
stroke-linecap="round"
|
|
19
|
+
stroke-linejoin="round"
|
|
20
|
+
>
|
|
21
|
+
<path
|
|
22
|
+
d="M15 22v-4a4.8 4.8 0 0 0-1-3.5c3 0 6-2 6-5.5.08-1.25-.27-2.48-1-3.5.28-1.15.28-2.35 0-3.5 0 0-1 0-3 1.5-2.64-.5-5.36-.5-8 0C6 2 5 2 5 2c-.3 1.15-.3 2.35 0 3.5A5.403 5.403 0 0 0 4 9c0 3.5 3 5.5 6 5.5-.39.49-.68 1.05-.85 1.65-.17.6-.22 1.23-.15 1.85v4"
|
|
23
|
+
/>
|
|
24
|
+
<path d="M9 18c-4.51 2-5-2-7-2" />
|
|
25
|
+
</svg>
|
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
export type { IconComponent } from './icon.js';
|
|
1
|
+
export type { IconComponent, IconProps } from './icon.js';
|
|
2
|
+
export { default as GithubIcon } from './icons/GithubIcon.svelte';
|
|
2
3
|
export { default as Accordion } from './components/accordion/Accordion.svelte';
|
|
3
4
|
export { default as AccordionItem } from './components/accordion/AccordionItem.svelte';
|
|
4
5
|
export { default as AlertDialog } from './components/alert-dialog/AlertDialog.svelte';
|
|
@@ -28,7 +29,6 @@ export { default as ContextMenuInfo } from './components/context-menu/ContextMen
|
|
|
28
29
|
export { default as Dialog } from './components/dialog/Dialog.svelte';
|
|
29
30
|
export { default as Drawer } from './components/drawer/Drawer.svelte';
|
|
30
31
|
export { default as EmptyState } from './components/empty-state/EmptyState.svelte';
|
|
31
|
-
export { default as Entry } from './components/entry/Entry.svelte';
|
|
32
32
|
export { default as Field } from './components/field/Field.svelte';
|
|
33
33
|
export { default as FieldContent } from './components/field/FieldContent.svelte';
|
|
34
34
|
export { default as FieldDescription } from './components/field/FieldDescription.svelte';
|
|
@@ -41,18 +41,21 @@ export { default as Footer } from './components/footer/Footer.svelte';
|
|
|
41
41
|
export { default as FooterColumn } from './components/footer/FooterColumn.svelte';
|
|
42
42
|
export { default as Image } from './components/image/Image.svelte';
|
|
43
43
|
export { default as Input } from './components/input/Input.svelte';
|
|
44
|
+
export { default as Lightbox } from './components/lightbox/Lightbox.svelte';
|
|
44
45
|
export { default as Kbd } from './components/kbd/Kbd.svelte';
|
|
45
46
|
export { default as KbdGroup } from './components/kbd/KbdGroup.svelte';
|
|
46
47
|
export { default as InputGroup } from './components/input-group/InputGroup.svelte';
|
|
47
48
|
export { default as InputGroupAddon } from './components/input-group/InputGroupAddon.svelte';
|
|
48
49
|
export { default as Label } from './components/label/Label.svelte';
|
|
49
50
|
export { default as Link } from './components/link/Link.svelte';
|
|
51
|
+
export { default as MediaPreview } from './components/media-preview/MediaPreview.svelte';
|
|
50
52
|
export { default as MeshGradient } from './components/mesh-gradient/MeshGradient.svelte';
|
|
51
53
|
export { default as Navbar } from './components/navbar/Navbar.svelte';
|
|
52
54
|
export { default as NavigationMenu } from './components/navigation-menu/NavigationMenu.svelte';
|
|
53
55
|
export { default as NavigationMenuItem } from './components/navigation-menu/NavigationMenuItem.svelte';
|
|
54
56
|
export { default as NavigationMenuLink } from './components/navigation-menu/NavigationMenuLink.svelte';
|
|
55
57
|
export { default as NumberStepper } from './components/number-stepper/NumberStepper.svelte';
|
|
58
|
+
export { default as PageHeader } from './components/page-header/PageHeader.svelte';
|
|
56
59
|
export { default as Popover } from './components/popover/Popover.svelte';
|
|
57
60
|
export { default as PreviewCard } from './components/preview-card/PreviewCard.svelte';
|
|
58
61
|
export { default as Progress } from './components/progress/Progress.svelte';
|
|
@@ -77,10 +80,13 @@ export { default as Skeleton } from './components/skeleton/Skeleton.svelte';
|
|
|
77
80
|
export { default as Spinner } from './components/spinner/Spinner.svelte';
|
|
78
81
|
export { default as Switch } from './components/switch/Switch.svelte';
|
|
79
82
|
export { default as Table, type TableColumn, type TableSort, type TableSortValue } from './components/table/Table.svelte';
|
|
83
|
+
export { default as TableOfContents } from './components/table-of-contents/TableOfContents.svelte';
|
|
80
84
|
export { default as Tabs, type TabsOption } from './components/tabs/Tabs.svelte';
|
|
81
85
|
export { default as Textarea } from './components/textarea/Textarea.svelte';
|
|
82
86
|
export { default as Toaster } from './components/toaster/Toaster.svelte';
|
|
83
87
|
export { toast, type ToastOptions, type ToastAction } from './components/toaster/toast.svelte.js';
|
|
88
|
+
export { default as ThemeToggle } from './components/theme-toggle/ThemeToggle.svelte';
|
|
89
|
+
export { default as Timeline, type TimelineItem } from './components/timeline/Timeline.svelte';
|
|
84
90
|
export { default as Toggle } from './components/toggle/Toggle.svelte';
|
|
85
91
|
export { default as Tooltip } from './components/tooltip/Tooltip.svelte';
|
|
86
92
|
export { default as TimePicker } from './components/time-picker/TimePicker.svelte';
|
package/dist/index.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
export { default as GithubIcon } from './icons/GithubIcon.svelte';
|
|
1
2
|
export { default as Accordion } from './components/accordion/Accordion.svelte';
|
|
2
3
|
export { default as AccordionItem } from './components/accordion/AccordionItem.svelte';
|
|
3
4
|
export { default as AlertDialog } from './components/alert-dialog/AlertDialog.svelte';
|
|
@@ -27,7 +28,6 @@ export { default as ContextMenuInfo } from './components/context-menu/ContextMen
|
|
|
27
28
|
export { default as Dialog } from './components/dialog/Dialog.svelte';
|
|
28
29
|
export { default as Drawer } from './components/drawer/Drawer.svelte';
|
|
29
30
|
export { default as EmptyState } from './components/empty-state/EmptyState.svelte';
|
|
30
|
-
export { default as Entry } from './components/entry/Entry.svelte';
|
|
31
31
|
export { default as Field } from './components/field/Field.svelte';
|
|
32
32
|
export { default as FieldContent } from './components/field/FieldContent.svelte';
|
|
33
33
|
export { default as FieldDescription } from './components/field/FieldDescription.svelte';
|
|
@@ -40,18 +40,21 @@ export { default as Footer } from './components/footer/Footer.svelte';
|
|
|
40
40
|
export { default as FooterColumn } from './components/footer/FooterColumn.svelte';
|
|
41
41
|
export { default as Image } from './components/image/Image.svelte';
|
|
42
42
|
export { default as Input } from './components/input/Input.svelte';
|
|
43
|
+
export { default as Lightbox } from './components/lightbox/Lightbox.svelte';
|
|
43
44
|
export { default as Kbd } from './components/kbd/Kbd.svelte';
|
|
44
45
|
export { default as KbdGroup } from './components/kbd/KbdGroup.svelte';
|
|
45
46
|
export { default as InputGroup } from './components/input-group/InputGroup.svelte';
|
|
46
47
|
export { default as InputGroupAddon } from './components/input-group/InputGroupAddon.svelte';
|
|
47
48
|
export { default as Label } from './components/label/Label.svelte';
|
|
48
49
|
export { default as Link } from './components/link/Link.svelte';
|
|
50
|
+
export { default as MediaPreview } from './components/media-preview/MediaPreview.svelte';
|
|
49
51
|
export { default as MeshGradient } from './components/mesh-gradient/MeshGradient.svelte';
|
|
50
52
|
export { default as Navbar } from './components/navbar/Navbar.svelte';
|
|
51
53
|
export { default as NavigationMenu } from './components/navigation-menu/NavigationMenu.svelte';
|
|
52
54
|
export { default as NavigationMenuItem } from './components/navigation-menu/NavigationMenuItem.svelte';
|
|
53
55
|
export { default as NavigationMenuLink } from './components/navigation-menu/NavigationMenuLink.svelte';
|
|
54
56
|
export { default as NumberStepper } from './components/number-stepper/NumberStepper.svelte';
|
|
57
|
+
export { default as PageHeader } from './components/page-header/PageHeader.svelte';
|
|
55
58
|
export { default as Popover } from './components/popover/Popover.svelte';
|
|
56
59
|
export { default as PreviewCard } from './components/preview-card/PreviewCard.svelte';
|
|
57
60
|
export { default as Progress } from './components/progress/Progress.svelte';
|
|
@@ -75,10 +78,13 @@ export { default as Skeleton } from './components/skeleton/Skeleton.svelte';
|
|
|
75
78
|
export { default as Spinner } from './components/spinner/Spinner.svelte';
|
|
76
79
|
export { default as Switch } from './components/switch/Switch.svelte';
|
|
77
80
|
export { default as Table } from './components/table/Table.svelte';
|
|
81
|
+
export { default as TableOfContents } from './components/table-of-contents/TableOfContents.svelte';
|
|
78
82
|
export { default as Tabs } from './components/tabs/Tabs.svelte';
|
|
79
83
|
export { default as Textarea } from './components/textarea/Textarea.svelte';
|
|
80
84
|
export { default as Toaster } from './components/toaster/Toaster.svelte';
|
|
81
85
|
export { toast } from './components/toaster/toast.svelte.js';
|
|
86
|
+
export { default as ThemeToggle } from './components/theme-toggle/ThemeToggle.svelte';
|
|
87
|
+
export { default as Timeline } from './components/timeline/Timeline.svelte';
|
|
82
88
|
export { default as Toggle } from './components/toggle/Toggle.svelte';
|
|
83
89
|
export { default as Tooltip } from './components/tooltip/Tooltip.svelte';
|
|
84
90
|
export { default as TimePicker } from './components/time-picker/TimePicker.svelte';
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { SVGAttributes } from 'svelte/elements';
|
|
3
|
+
import Icon from './Icon.svelte';
|
|
4
|
+
|
|
5
|
+
let props: SVGAttributes<SVGSVGElement> = $props();
|
|
6
|
+
</script>
|
|
7
|
+
|
|
8
|
+
<Icon
|
|
9
|
+
{...props}
|
|
10
|
+
nodes={[
|
|
11
|
+
['rect', { width: 20, height: 14, x: 2, y: 3, rx: 2 }],
|
|
12
|
+
['line', { x1: 8, x2: 16, y1: 21, y2: 21 }],
|
|
13
|
+
['line', { x1: 12, x2: 12, y1: 17, y2: 21 }]
|
|
14
|
+
]}
|
|
15
|
+
/>
|