@noahbaumgartner/svelte-ui 0.0.4 → 0.0.5
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/README.md +8 -8
- package/dist/components/accordion/Accordion.svelte +34 -0
- package/dist/components/accordion/Accordion.svelte.d.ts +11 -0
- package/dist/components/accordion/AccordionItem.svelte +106 -0
- package/dist/components/accordion/AccordionItem.svelte.d.ts +13 -0
- package/dist/components/accordion/context.d.ts +5 -0
- package/dist/components/accordion/context.js +2 -0
- package/dist/components/avatar-group/AvatarGroup.svelte +76 -0
- package/dist/components/avatar-group/AvatarGroup.svelte.d.ts +15 -0
- package/dist/components/drawer/Drawer.svelte +368 -0
- package/dist/components/drawer/Drawer.svelte.d.ts +23 -0
- package/dist/components/empty-state/EmptyState.svelte +86 -0
- package/dist/components/empty-state/EmptyState.svelte.d.ts +16 -0
- package/dist/components/entry/Entry.svelte +83 -0
- package/dist/components/entry/Entry.svelte.d.ts +15 -0
- package/dist/components/footer/Footer.svelte +89 -0
- package/dist/components/footer/Footer.svelte.d.ts +13 -0
- package/dist/components/footer/FooterColumn.svelte +34 -0
- package/dist/components/footer/FooterColumn.svelte.d.ts +10 -0
- package/dist/components/mesh-gradient/MeshGradient.svelte +86 -0
- package/dist/components/mesh-gradient/MeshGradient.svelte.d.ts +12 -0
- package/dist/components/number-stepper/NumberStepper.svelte +82 -0
- package/dist/components/number-stepper/NumberStepper.svelte.d.ts +16 -0
- package/dist/components/preview-card/PreviewCard.svelte +166 -0
- package/dist/components/preview-card/PreviewCard.svelte.d.ts +20 -0
- package/dist/components/progress/Progress.svelte +88 -0
- package/dist/components/progress/Progress.svelte.d.ts +12 -0
- package/dist/components/select/Select.svelte +391 -0
- package/dist/components/select/Select.svelte.d.ts +39 -0
- package/dist/components/sidebar/Sidebar.svelte +37 -0
- package/dist/components/sidebar/SidebarHeader.svelte +26 -2
- package/dist/components/tabs/Tabs.svelte +280 -0
- package/dist/components/tabs/Tabs.svelte.d.ts +48 -0
- package/dist/index.d.ts +15 -0
- package/dist/index.js +14 -0
- package/package.json +1 -1
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { Snippet } from 'svelte';
|
|
3
|
+
import type { HTMLAttributes } from 'svelte/elements';
|
|
4
|
+
import type { LucideIcon } from '@lucide/svelte';
|
|
5
|
+
|
|
6
|
+
type Props = Omit<HTMLAttributes<HTMLDivElement>, 'children' | 'title'> & {
|
|
7
|
+
/** Any Lucide icon (import from `svelte-ui/icons`). */
|
|
8
|
+
icon?: LucideIcon;
|
|
9
|
+
title: string;
|
|
10
|
+
description?: string;
|
|
11
|
+
/** `outline` has a dashed border, for empty areas that can be filled. */
|
|
12
|
+
variant?: 'secondary' | 'outline';
|
|
13
|
+
/** Optional actions below the text, e.g. a Button that creates the first entry. */
|
|
14
|
+
children?: Snippet;
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
let {
|
|
18
|
+
icon: Icon,
|
|
19
|
+
title,
|
|
20
|
+
description,
|
|
21
|
+
variant = 'secondary',
|
|
22
|
+
class: className,
|
|
23
|
+
children,
|
|
24
|
+
...rest
|
|
25
|
+
}: Props = $props();
|
|
26
|
+
</script>
|
|
27
|
+
|
|
28
|
+
<div {...rest} class={['empty-state', `empty-state--${variant}`, className]}>
|
|
29
|
+
{#if Icon}<Icon class="empty-state-icon" aria-hidden="true" />{/if}
|
|
30
|
+
<p class="empty-state-title">{title}</p>
|
|
31
|
+
{#if description}<p class="empty-state-description">{description}</p>{/if}
|
|
32
|
+
{#if children}<div class="empty-state-actions">{@render children()}</div>{/if}
|
|
33
|
+
</div>
|
|
34
|
+
|
|
35
|
+
<style>
|
|
36
|
+
.empty-state {
|
|
37
|
+
display: flex;
|
|
38
|
+
flex-direction: column;
|
|
39
|
+
align-items: center;
|
|
40
|
+
gap: 6px;
|
|
41
|
+
box-sizing: border-box;
|
|
42
|
+
padding: 32px 16px;
|
|
43
|
+
font-size: 13px;
|
|
44
|
+
line-height: 1.5;
|
|
45
|
+
color: var(--color-text);
|
|
46
|
+
text-align: center;
|
|
47
|
+
border-radius: 14px;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
.empty-state--secondary {
|
|
51
|
+
background-color: var(--color-surface);
|
|
52
|
+
border: 1px solid transparent;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
.empty-state--outline {
|
|
56
|
+
border: 1px dashed var(--color-border-strong);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
.empty-state :global(.empty-state-icon) {
|
|
60
|
+
width: 20px;
|
|
61
|
+
height: 20px;
|
|
62
|
+
margin-bottom: 4px;
|
|
63
|
+
color: var(--color-text-muted);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
p {
|
|
67
|
+
max-width: 44ch;
|
|
68
|
+
margin: 0;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
.empty-state-title {
|
|
72
|
+
font-weight: 500;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
.empty-state-description {
|
|
76
|
+
color: var(--color-text-muted);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
.empty-state-actions {
|
|
80
|
+
display: flex;
|
|
81
|
+
flex-wrap: wrap;
|
|
82
|
+
justify-content: center;
|
|
83
|
+
gap: 8px;
|
|
84
|
+
margin-top: 8px;
|
|
85
|
+
}
|
|
86
|
+
</style>
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { Snippet } from 'svelte';
|
|
2
|
+
import type { HTMLAttributes } from 'svelte/elements';
|
|
3
|
+
import type { LucideIcon } from '@lucide/svelte';
|
|
4
|
+
type Props = Omit<HTMLAttributes<HTMLDivElement>, 'children' | 'title'> & {
|
|
5
|
+
/** Any Lucide icon (import from `svelte-ui/icons`). */
|
|
6
|
+
icon?: LucideIcon;
|
|
7
|
+
title: string;
|
|
8
|
+
description?: string;
|
|
9
|
+
/** `outline` has a dashed border, for empty areas that can be filled. */
|
|
10
|
+
variant?: 'secondary' | 'outline';
|
|
11
|
+
/** Optional actions below the text, e.g. a Button that creates the first entry. */
|
|
12
|
+
children?: Snippet;
|
|
13
|
+
};
|
|
14
|
+
declare const EmptyState: import("svelte").Component<Props, {}, "">;
|
|
15
|
+
type EmptyState = ReturnType<typeof EmptyState>;
|
|
16
|
+
export default EmptyState;
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { Snippet } from 'svelte';
|
|
3
|
+
import type { HTMLAttributes } from 'svelte/elements';
|
|
4
|
+
|
|
5
|
+
type Props = Omit<HTMLAttributes<HTMLDivElement>, 'children' | 'title'> & {
|
|
6
|
+
/** Rendered as an `<h3>`. */
|
|
7
|
+
title: string;
|
|
8
|
+
/** Muted text next to the title, e.g. a period like "2021 - 2025". */
|
|
9
|
+
meta?: string;
|
|
10
|
+
/** The description. */
|
|
11
|
+
children?: Snippet;
|
|
12
|
+
/** Below the description, e.g. a Link. */
|
|
13
|
+
footer?: Snippet;
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
let { title, meta, class: className, children, footer, ...rest }: Props = $props();
|
|
17
|
+
</script>
|
|
18
|
+
|
|
19
|
+
<div {...rest} class={['entry', className]}>
|
|
20
|
+
<div class="entry-heading">
|
|
21
|
+
<h3 class="entry-title">{title}</h3>
|
|
22
|
+
{#if meta}
|
|
23
|
+
<span class="entry-meta">{meta}</span>
|
|
24
|
+
{/if}
|
|
25
|
+
</div>
|
|
26
|
+
{#if children}
|
|
27
|
+
<p class="entry-description">{@render children()}</p>
|
|
28
|
+
{/if}
|
|
29
|
+
{#if footer}
|
|
30
|
+
<div class="entry-footer">{@render footer()}</div>
|
|
31
|
+
{/if}
|
|
32
|
+
</div>
|
|
33
|
+
|
|
34
|
+
<style>
|
|
35
|
+
.entry {
|
|
36
|
+
display: flex;
|
|
37
|
+
flex-direction: column;
|
|
38
|
+
align-items: flex-start;
|
|
39
|
+
gap: 10px;
|
|
40
|
+
box-sizing: border-box;
|
|
41
|
+
padding: 24px;
|
|
42
|
+
color: var(--color-text);
|
|
43
|
+
border: 1px solid var(--color-border-strong);
|
|
44
|
+
border-radius: 14px;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
.entry-heading {
|
|
48
|
+
display: flex;
|
|
49
|
+
flex-wrap: wrap;
|
|
50
|
+
align-items: baseline;
|
|
51
|
+
column-gap: 8px;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
.entry-title {
|
|
55
|
+
margin: 0;
|
|
56
|
+
font-size: 20px;
|
|
57
|
+
font-weight: 500;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
.entry-meta {
|
|
61
|
+
font-size: 14px;
|
|
62
|
+
color: var(--color-text-muted);
|
|
63
|
+
white-space: nowrap;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
.entry-description {
|
|
67
|
+
margin: 0;
|
|
68
|
+
max-width: 640px;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
.entry-footer {
|
|
72
|
+
display: flex;
|
|
73
|
+
flex-wrap: wrap;
|
|
74
|
+
align-items: center;
|
|
75
|
+
gap: 8px;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
@media (min-width: 768px) {
|
|
79
|
+
.entry {
|
|
80
|
+
padding: 32px;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
</style>
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { Snippet } from 'svelte';
|
|
2
|
+
import type { HTMLAttributes } from 'svelte/elements';
|
|
3
|
+
type Props = Omit<HTMLAttributes<HTMLDivElement>, 'children' | 'title'> & {
|
|
4
|
+
/** Rendered as an `<h3>`. */
|
|
5
|
+
title: string;
|
|
6
|
+
/** Muted text next to the title, e.g. a period like "2021 - 2025". */
|
|
7
|
+
meta?: string;
|
|
8
|
+
/** The description. */
|
|
9
|
+
children?: Snippet;
|
|
10
|
+
/** Below the description, e.g. a Link. */
|
|
11
|
+
footer?: Snippet;
|
|
12
|
+
};
|
|
13
|
+
declare const Entry: import("svelte").Component<Props, {}, "">;
|
|
14
|
+
type Entry = ReturnType<typeof Entry>;
|
|
15
|
+
export default Entry;
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { Snippet } from 'svelte';
|
|
3
|
+
import type { HTMLAttributes } from 'svelte/elements';
|
|
4
|
+
|
|
5
|
+
type Props = Omit<HTMLAttributes<HTMLElement>, 'children' | 'title'> & {
|
|
6
|
+
/** Message in the panel, rendered as an `<h2>`; `\n` breaks the line. */
|
|
7
|
+
title?: string;
|
|
8
|
+
/** Owner shown below the panel as "© <current year> <copyright>". */
|
|
9
|
+
copyright?: string;
|
|
10
|
+
/** FooterColumns. */
|
|
11
|
+
children?: Snippet;
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
let { title, copyright, class: className, children, ...rest }: Props = $props();
|
|
15
|
+
|
|
16
|
+
const year = new Date().getFullYear();
|
|
17
|
+
</script>
|
|
18
|
+
|
|
19
|
+
<footer {...rest} class={['footer', className]}>
|
|
20
|
+
<div class="footer-panel">
|
|
21
|
+
{#if title}
|
|
22
|
+
<h2 class="footer-title">{title}</h2>
|
|
23
|
+
{/if}
|
|
24
|
+
{#if children}
|
|
25
|
+
<div class="footer-columns">{@render children()}</div>
|
|
26
|
+
{/if}
|
|
27
|
+
</div>
|
|
28
|
+
{#if copyright}
|
|
29
|
+
<p class="footer-copyright">© {year} {copyright}</p>
|
|
30
|
+
{/if}
|
|
31
|
+
</footer>
|
|
32
|
+
|
|
33
|
+
<style>
|
|
34
|
+
.footer {
|
|
35
|
+
display: flex;
|
|
36
|
+
flex-direction: column;
|
|
37
|
+
gap: 20px;
|
|
38
|
+
font-size: 14px;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
.footer-panel {
|
|
42
|
+
display: flex;
|
|
43
|
+
flex-direction: column;
|
|
44
|
+
gap: 40px;
|
|
45
|
+
box-sizing: border-box;
|
|
46
|
+
min-height: 256px;
|
|
47
|
+
padding: 40px;
|
|
48
|
+
color: #fff;
|
|
49
|
+
background-color: #000;
|
|
50
|
+
border-radius: 14px;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
.footer-title {
|
|
54
|
+
margin: 0;
|
|
55
|
+
font-size: 24px;
|
|
56
|
+
font-weight: 600;
|
|
57
|
+
line-height: 1.25;
|
|
58
|
+
white-space: pre-line;
|
|
59
|
+
user-select: none;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
.footer-columns {
|
|
63
|
+
display: flex;
|
|
64
|
+
flex-direction: column;
|
|
65
|
+
gap: 40px;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
.footer-copyright {
|
|
69
|
+
margin: 0;
|
|
70
|
+
text-align: center;
|
|
71
|
+
color: var(--color-text-muted);
|
|
72
|
+
user-select: none;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
@media (min-width: 640px) {
|
|
76
|
+
.footer-panel {
|
|
77
|
+
flex-direction: row;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
.footer-title {
|
|
81
|
+
flex: 1;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
.footer-columns {
|
|
85
|
+
flex: 1;
|
|
86
|
+
flex-direction: row;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
</style>
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { Snippet } from 'svelte';
|
|
2
|
+
import type { HTMLAttributes } from 'svelte/elements';
|
|
3
|
+
type Props = Omit<HTMLAttributes<HTMLElement>, 'children' | 'title'> & {
|
|
4
|
+
/** Message in the panel, rendered as an `<h2>`; `\n` breaks the line. */
|
|
5
|
+
title?: string;
|
|
6
|
+
/** Owner shown below the panel as "© <current year> <copyright>". */
|
|
7
|
+
copyright?: string;
|
|
8
|
+
/** FooterColumns. */
|
|
9
|
+
children?: Snippet;
|
|
10
|
+
};
|
|
11
|
+
declare const Footer: import("svelte").Component<Props, {}, "">;
|
|
12
|
+
type Footer = ReturnType<typeof Footer>;
|
|
13
|
+
export default Footer;
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { Snippet } from 'svelte';
|
|
3
|
+
import type { HTMLAttributes } from 'svelte/elements';
|
|
4
|
+
|
|
5
|
+
type Props = Omit<HTMLAttributes<HTMLDivElement>, 'children'> & {
|
|
6
|
+
heading: string;
|
|
7
|
+
/** Links, one per line. */
|
|
8
|
+
children: Snippet;
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
let { heading, class: className, children, ...rest }: Props = $props();
|
|
12
|
+
</script>
|
|
13
|
+
|
|
14
|
+
<div {...rest} class={['footer-column', className]}>
|
|
15
|
+
<span class="footer-column-heading">{heading}</span>
|
|
16
|
+
{@render children()}
|
|
17
|
+
</div>
|
|
18
|
+
|
|
19
|
+
<style>
|
|
20
|
+
.footer-column {
|
|
21
|
+
display: flex;
|
|
22
|
+
flex: 1;
|
|
23
|
+
flex-direction: column;
|
|
24
|
+
align-items: flex-start;
|
|
25
|
+
gap: 8px;
|
|
26
|
+
color: color-mix(in srgb, #fff, #000 17%);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
.footer-column-heading {
|
|
30
|
+
font-weight: 600;
|
|
31
|
+
color: #fff;
|
|
32
|
+
user-select: none;
|
|
33
|
+
}
|
|
34
|
+
</style>
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { Snippet } from 'svelte';
|
|
2
|
+
import type { HTMLAttributes } from 'svelte/elements';
|
|
3
|
+
type Props = Omit<HTMLAttributes<HTMLDivElement>, 'children'> & {
|
|
4
|
+
heading: string;
|
|
5
|
+
/** Links, one per line. */
|
|
6
|
+
children: Snippet;
|
|
7
|
+
};
|
|
8
|
+
declare const FooterColumn: import("svelte").Component<Props, {}, "">;
|
|
9
|
+
type FooterColumn = ReturnType<typeof FooterColumn>;
|
|
10
|
+
export default FooterColumn;
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { HTMLAttributes } from 'svelte/elements';
|
|
3
|
+
|
|
4
|
+
type Props = Omit<HTMLAttributes<HTMLDivElement>, 'children'> & {
|
|
5
|
+
/** First colour is the background, every colour becomes a blurred blob. */
|
|
6
|
+
colors: string[];
|
|
7
|
+
/** Same seed gives the same layout; defaults to the joined colours. */
|
|
8
|
+
seed?: string;
|
|
9
|
+
/** Film grain overlay. */
|
|
10
|
+
grain?: boolean;
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
let { colors, seed = '', grain = true, class: className, ...rest }: Props = $props();
|
|
14
|
+
|
|
15
|
+
function hashSeed(str: string) {
|
|
16
|
+
let h = 0;
|
|
17
|
+
for (let i = 0; i < str.length; i++) {
|
|
18
|
+
h = (Math.imul(31, h) + str.charCodeAt(i)) | 0;
|
|
19
|
+
}
|
|
20
|
+
return h >>> 0;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function mulberry32(a: number) {
|
|
24
|
+
return function () {
|
|
25
|
+
a |= 0;
|
|
26
|
+
a = (a + 0x6d2b79f5) | 0;
|
|
27
|
+
let t = Math.imul(a ^ (a >>> 15), 1 | a);
|
|
28
|
+
t = (t + Math.imul(t ^ (t >>> 7), 61 | t)) ^ t;
|
|
29
|
+
return ((t ^ (t >>> 14)) >>> 0) / 4294967296;
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const blobs = $derived.by(() => {
|
|
34
|
+
const rand = mulberry32(hashSeed(seed || colors.join('')));
|
|
35
|
+
return colors.map((color) => ({
|
|
36
|
+
color,
|
|
37
|
+
x: 10 + rand() * 80,
|
|
38
|
+
y: 10 + rand() * 80,
|
|
39
|
+
size: 70 + rand() * 60,
|
|
40
|
+
blur: rand() * 20
|
|
41
|
+
}));
|
|
42
|
+
});
|
|
43
|
+
</script>
|
|
44
|
+
|
|
45
|
+
<div
|
|
46
|
+
{...rest}
|
|
47
|
+
class={['mesh-gradient', grain && 'mesh-gradient--grain', className]}
|
|
48
|
+
style:background-color={colors[0]}
|
|
49
|
+
aria-hidden="true"
|
|
50
|
+
>
|
|
51
|
+
{#each blobs as blob, i (i)}
|
|
52
|
+
<div
|
|
53
|
+
class="mesh-gradient-blob"
|
|
54
|
+
style:left="{blob.x}%"
|
|
55
|
+
style:top="{blob.y}%"
|
|
56
|
+
style:width="{blob.size}%"
|
|
57
|
+
style:height="{blob.size}%"
|
|
58
|
+
style:background-color={blob.color}
|
|
59
|
+
style:filter="blur({blob.blur}px)"
|
|
60
|
+
></div>
|
|
61
|
+
{/each}
|
|
62
|
+
</div>
|
|
63
|
+
|
|
64
|
+
<style>
|
|
65
|
+
.mesh-gradient {
|
|
66
|
+
position: relative;
|
|
67
|
+
overflow: hidden;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
.mesh-gradient-blob {
|
|
71
|
+
position: absolute;
|
|
72
|
+
border-radius: 50%;
|
|
73
|
+
transform: translate(-50%, -50%);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
.mesh-gradient--grain::after {
|
|
77
|
+
content: '';
|
|
78
|
+
position: absolute;
|
|
79
|
+
inset: 0;
|
|
80
|
+
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='2' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E");
|
|
81
|
+
background-size: 160px 160px;
|
|
82
|
+
opacity: 0.55;
|
|
83
|
+
mix-blend-mode: overlay;
|
|
84
|
+
pointer-events: none;
|
|
85
|
+
}
|
|
86
|
+
</style>
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { HTMLAttributes } from 'svelte/elements';
|
|
2
|
+
type Props = Omit<HTMLAttributes<HTMLDivElement>, 'children'> & {
|
|
3
|
+
/** First colour is the background, every colour becomes a blurred blob. */
|
|
4
|
+
colors: string[];
|
|
5
|
+
/** Same seed gives the same layout; defaults to the joined colours. */
|
|
6
|
+
seed?: string;
|
|
7
|
+
/** Film grain overlay. */
|
|
8
|
+
grain?: boolean;
|
|
9
|
+
};
|
|
10
|
+
declare const MeshGradient: import("svelte").Component<Props, {}, "">;
|
|
11
|
+
type MeshGradient = ReturnType<typeof MeshGradient>;
|
|
12
|
+
export default MeshGradient;
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { HTMLAttributes } from 'svelte/elements';
|
|
3
|
+
import { Minus, Plus } from '@lucide/svelte';
|
|
4
|
+
import Button from '../button/Button.svelte';
|
|
5
|
+
|
|
6
|
+
type Props = Omit<HTMLAttributes<HTMLDivElement>, 'children' | 'onchange'> & {
|
|
7
|
+
/** The count. Bindable. */
|
|
8
|
+
value?: number;
|
|
9
|
+
min?: number;
|
|
10
|
+
max?: number;
|
|
11
|
+
step?: number;
|
|
12
|
+
size?: 'sm' | 'md' | 'lg';
|
|
13
|
+
disabled?: boolean;
|
|
14
|
+
/** Names what is counted; the accessible name of the group and part of the button labels. */
|
|
15
|
+
label: string;
|
|
16
|
+
onchange?: (value: number) => void;
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
let {
|
|
20
|
+
value = $bindable(0),
|
|
21
|
+
min = 0,
|
|
22
|
+
max = Infinity,
|
|
23
|
+
step = 1,
|
|
24
|
+
size = 'sm',
|
|
25
|
+
disabled = false,
|
|
26
|
+
label,
|
|
27
|
+
class: className,
|
|
28
|
+
onchange,
|
|
29
|
+
...rest
|
|
30
|
+
}: Props = $props();
|
|
31
|
+
|
|
32
|
+
function set(next: number) {
|
|
33
|
+
value = Math.min(Math.max(next, min), max);
|
|
34
|
+
onchange?.(value);
|
|
35
|
+
}
|
|
36
|
+
</script>
|
|
37
|
+
|
|
38
|
+
<div
|
|
39
|
+
{...rest}
|
|
40
|
+
role="group"
|
|
41
|
+
aria-label={label}
|
|
42
|
+
class={['number-stepper', `number-stepper--${size}`, className]}
|
|
43
|
+
>
|
|
44
|
+
<Button
|
|
45
|
+
variant="outline"
|
|
46
|
+
{size}
|
|
47
|
+
icon={Minus}
|
|
48
|
+
label="Decrease {label}"
|
|
49
|
+
disabled={disabled || value <= min}
|
|
50
|
+
onclick={() => set(value - step)}
|
|
51
|
+
/>
|
|
52
|
+
<output class="number-stepper-value" aria-live="polite">{value}</output>
|
|
53
|
+
<Button
|
|
54
|
+
variant="outline"
|
|
55
|
+
{size}
|
|
56
|
+
icon={Plus}
|
|
57
|
+
label="Increase {label}"
|
|
58
|
+
disabled={disabled || value >= max}
|
|
59
|
+
onclick={() => set(value + step)}
|
|
60
|
+
/>
|
|
61
|
+
</div>
|
|
62
|
+
|
|
63
|
+
<style>
|
|
64
|
+
.number-stepper {
|
|
65
|
+
display: inline-flex;
|
|
66
|
+
align-items: center;
|
|
67
|
+
gap: 4px;
|
|
68
|
+
font-size: 13px;
|
|
69
|
+
color: var(--color-text);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
.number-stepper--sm {
|
|
73
|
+
font-size: 12px;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
.number-stepper-value {
|
|
77
|
+
min-width: 24px;
|
|
78
|
+
font-weight: 600;
|
|
79
|
+
font-variant-numeric: tabular-nums;
|
|
80
|
+
text-align: center;
|
|
81
|
+
}
|
|
82
|
+
</style>
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { HTMLAttributes } from 'svelte/elements';
|
|
2
|
+
type Props = Omit<HTMLAttributes<HTMLDivElement>, 'children' | 'onchange'> & {
|
|
3
|
+
/** The count. Bindable. */
|
|
4
|
+
value?: number;
|
|
5
|
+
min?: number;
|
|
6
|
+
max?: number;
|
|
7
|
+
step?: number;
|
|
8
|
+
size?: 'sm' | 'md' | 'lg';
|
|
9
|
+
disabled?: boolean;
|
|
10
|
+
/** Names what is counted; the accessible name of the group and part of the button labels. */
|
|
11
|
+
label: string;
|
|
12
|
+
onchange?: (value: number) => void;
|
|
13
|
+
};
|
|
14
|
+
declare const NumberStepper: import("svelte").Component<Props, {}, "value">;
|
|
15
|
+
type NumberStepper = ReturnType<typeof NumberStepper>;
|
|
16
|
+
export default NumberStepper;
|