@noahbaumgartner/svelte-ui 0.0.3 → 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.
Files changed (36) hide show
  1. package/README.md +69 -38
  2. package/dist/components/accordion/Accordion.svelte +34 -0
  3. package/dist/components/accordion/Accordion.svelte.d.ts +11 -0
  4. package/dist/components/accordion/AccordionItem.svelte +106 -0
  5. package/dist/components/accordion/AccordionItem.svelte.d.ts +13 -0
  6. package/dist/components/accordion/context.d.ts +5 -0
  7. package/dist/components/accordion/context.js +2 -0
  8. package/dist/components/avatar-group/AvatarGroup.svelte +76 -0
  9. package/dist/components/avatar-group/AvatarGroup.svelte.d.ts +15 -0
  10. package/dist/components/drawer/Drawer.svelte +368 -0
  11. package/dist/components/drawer/Drawer.svelte.d.ts +23 -0
  12. package/dist/components/empty-state/EmptyState.svelte +86 -0
  13. package/dist/components/empty-state/EmptyState.svelte.d.ts +16 -0
  14. package/dist/components/entry/Entry.svelte +83 -0
  15. package/dist/components/entry/Entry.svelte.d.ts +15 -0
  16. package/dist/components/footer/Footer.svelte +89 -0
  17. package/dist/components/footer/Footer.svelte.d.ts +13 -0
  18. package/dist/components/footer/FooterColumn.svelte +34 -0
  19. package/dist/components/footer/FooterColumn.svelte.d.ts +10 -0
  20. package/dist/components/mesh-gradient/MeshGradient.svelte +86 -0
  21. package/dist/components/mesh-gradient/MeshGradient.svelte.d.ts +12 -0
  22. package/dist/components/number-stepper/NumberStepper.svelte +82 -0
  23. package/dist/components/number-stepper/NumberStepper.svelte.d.ts +16 -0
  24. package/dist/components/preview-card/PreviewCard.svelte +166 -0
  25. package/dist/components/preview-card/PreviewCard.svelte.d.ts +20 -0
  26. package/dist/components/progress/Progress.svelte +88 -0
  27. package/dist/components/progress/Progress.svelte.d.ts +12 -0
  28. package/dist/components/select/Select.svelte +391 -0
  29. package/dist/components/select/Select.svelte.d.ts +39 -0
  30. package/dist/components/sidebar/Sidebar.svelte +37 -0
  31. package/dist/components/sidebar/SidebarHeader.svelte +26 -2
  32. package/dist/components/tabs/Tabs.svelte +280 -0
  33. package/dist/components/tabs/Tabs.svelte.d.ts +48 -0
  34. package/dist/index.d.ts +15 -0
  35. package/dist/index.js +14 -0
  36. package/package.json +1 -1
@@ -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;
@@ -0,0 +1,166 @@
1
+ <script lang="ts">
2
+ import type { Snippet } from 'svelte';
3
+ import type { LucideIcon } from '@lucide/svelte';
4
+
5
+ type Props = {
6
+ /** Makes the whole card a link. */
7
+ href?: string;
8
+ /** Bold lead-in before the description, followed by a colon. */
9
+ title: string;
10
+ description?: string;
11
+ /** Muted text below, e.g. a date. */
12
+ meta?: string;
13
+ /** Any Lucide icon (import from `svelte-ui/icons`), shown in the top-left corner of the media. */
14
+ icon?: LucideIcon;
15
+ orientation?: 'vertical' | 'horizontal';
16
+ /** Square media that fills the frame, e.g. a MeshGradient or an `<img>`. */
17
+ media: Snippet;
18
+ class?: string;
19
+ };
20
+
21
+ let {
22
+ href,
23
+ title,
24
+ description,
25
+ meta,
26
+ icon: Icon,
27
+ orientation = 'vertical',
28
+ media,
29
+ class: className
30
+ }: Props = $props();
31
+ </script>
32
+
33
+ <svelte:element
34
+ this={href ? 'a' : 'div'}
35
+ {href}
36
+ class={['preview-card', `preview-card--${orientation}`, className]}
37
+ >
38
+ <div class="preview-card-media">
39
+ {@render media()}
40
+ {#if Icon}
41
+ <span class="preview-card-icon"><Icon aria-hidden="true" /></span>
42
+ {/if}
43
+ </div>
44
+ <div class="preview-card-info">
45
+ <p class="preview-card-headline">
46
+ <span class="preview-card-title">{title}{description ? ':' : ''}</span>
47
+ {description}
48
+ </p>
49
+ {#if meta}
50
+ <span class="preview-card-meta">{meta}</span>
51
+ {/if}
52
+ </div>
53
+ </svelte:element>
54
+
55
+ <style>
56
+ .preview-card {
57
+ display: flex;
58
+ gap: 20px;
59
+ color: var(--color-text);
60
+ text-decoration: none;
61
+ border-radius: 14px;
62
+ outline: none;
63
+ }
64
+
65
+ a.preview-card:focus-visible {
66
+ outline: 2px solid var(--color-accent);
67
+ outline-offset: 2px;
68
+ }
69
+
70
+ .preview-card--vertical {
71
+ flex-direction: column;
72
+ }
73
+
74
+ .preview-card--horizontal {
75
+ align-items: center;
76
+ }
77
+
78
+ .preview-card-media {
79
+ position: relative;
80
+ flex-shrink: 0;
81
+ aspect-ratio: 1;
82
+ overflow: hidden;
83
+ border-radius: 14px;
84
+ }
85
+
86
+ .preview-card--horizontal .preview-card-media {
87
+ width: 140px;
88
+ }
89
+
90
+ .preview-card-media > :global(:not(.preview-card-icon)) {
91
+ position: absolute;
92
+ inset: 0;
93
+ width: 100%;
94
+ height: 100%;
95
+ object-fit: cover;
96
+ border-radius: inherit;
97
+ transition: transform 300ms ease;
98
+ }
99
+
100
+ a.preview-card:hover .preview-card-media > :global(:not(.preview-card-icon)) {
101
+ transform: scale(1.05);
102
+ }
103
+
104
+ .preview-card-icon {
105
+ position: absolute;
106
+ top: 0;
107
+ left: 0;
108
+ z-index: 1;
109
+ display: flex;
110
+ align-items: center;
111
+ justify-content: center;
112
+ width: 40px;
113
+ height: 40px;
114
+ color: var(--color-base);
115
+ background-color: var(--color-ink);
116
+ border-radius: 14px 0 0 0;
117
+ }
118
+
119
+ .preview-card-icon :global(svg) {
120
+ width: 20px;
121
+ height: 20px;
122
+ }
123
+
124
+ .preview-card--horizontal .preview-card-icon {
125
+ width: 32px;
126
+ height: 32px;
127
+ }
128
+
129
+ .preview-card--horizontal .preview-card-icon :global(svg) {
130
+ width: 16px;
131
+ height: 16px;
132
+ }
133
+
134
+ .preview-card-info {
135
+ display: flex;
136
+ flex-direction: column;
137
+ gap: 8px;
138
+ min-width: 0;
139
+ }
140
+
141
+ .preview-card-headline {
142
+ margin: 0;
143
+ font-size: 16px;
144
+ }
145
+
146
+ .preview-card-title {
147
+ font-weight: 600;
148
+ }
149
+
150
+ .preview-card-meta {
151
+ font-size: 14px;
152
+ color: var(--color-text-muted);
153
+ }
154
+
155
+ @media (min-width: 640px) {
156
+ .preview-card--horizontal .preview-card-media {
157
+ width: 168px;
158
+ }
159
+ }
160
+
161
+ @media (prefers-reduced-motion: reduce) {
162
+ .preview-card-media > :global(*) {
163
+ transition: none;
164
+ }
165
+ }
166
+ </style>
@@ -0,0 +1,20 @@
1
+ import type { Snippet } from 'svelte';
2
+ import type { LucideIcon } from '@lucide/svelte';
3
+ type Props = {
4
+ /** Makes the whole card a link. */
5
+ href?: string;
6
+ /** Bold lead-in before the description, followed by a colon. */
7
+ title: string;
8
+ description?: string;
9
+ /** Muted text below, e.g. a date. */
10
+ meta?: string;
11
+ /** Any Lucide icon (import from `svelte-ui/icons`), shown in the top-left corner of the media. */
12
+ icon?: LucideIcon;
13
+ orientation?: 'vertical' | 'horizontal';
14
+ /** Square media that fills the frame, e.g. a MeshGradient or an `<img>`. */
15
+ media: Snippet;
16
+ class?: string;
17
+ };
18
+ declare const PreviewCard: import("svelte").Component<Props, {}, "">;
19
+ type PreviewCard = ReturnType<typeof PreviewCard>;
20
+ export default PreviewCard;
@@ -0,0 +1,88 @@
1
+ <script lang="ts">
2
+ import type { HTMLAttributes } from 'svelte/elements';
3
+
4
+ type Props = Omit<HTMLAttributes<HTMLDivElement>, 'children'> & {
5
+ /** Progress between 0 and `max`. Omit while the amount is unknown: the bar then runs back and forth. */
6
+ value?: number;
7
+ max?: number;
8
+ size?: 'sm' | 'md';
9
+ /** Accessible name, e.g. what is loading. */
10
+ label?: string;
11
+ };
12
+
13
+ let { value, max = 100, size = 'md', label, class: className, ...rest }: Props = $props();
14
+
15
+ let fraction = $derived(value === undefined ? undefined : Math.min(Math.max(value / max, 0), 1));
16
+ </script>
17
+
18
+ <div
19
+ {...rest}
20
+ role="progressbar"
21
+ aria-label={label}
22
+ aria-valuemin={0}
23
+ aria-valuemax={max}
24
+ aria-valuenow={fraction === undefined ? undefined : fraction * max}
25
+ class={[
26
+ 'progress',
27
+ `progress--${size}`,
28
+ { 'progress--indeterminate': fraction === undefined },
29
+ className
30
+ ]}
31
+ >
32
+ <div
33
+ class="progress-bar"
34
+ style:width={fraction === undefined ? undefined : `${fraction * 100}%`}
35
+ ></div>
36
+ </div>
37
+
38
+ <style>
39
+ .progress {
40
+ width: 100%;
41
+ overflow: hidden;
42
+ background-color: var(--color-surface);
43
+ border-radius: 999px;
44
+ }
45
+
46
+ .progress--sm {
47
+ height: 4px;
48
+ }
49
+
50
+ .progress--md {
51
+ height: 8px;
52
+ }
53
+
54
+ .progress-bar {
55
+ height: 100%;
56
+ background-color: var(--color-accent);
57
+ border-radius: inherit;
58
+ transition: width 300ms ease;
59
+ }
60
+
61
+ .progress--indeterminate .progress-bar {
62
+ width: 40%;
63
+ animation: progress-slide 1.4s ease-in-out infinite;
64
+ }
65
+
66
+ @keyframes progress-slide {
67
+ from {
68
+ translate: -100% 0;
69
+ }
70
+
71
+ to {
72
+ translate: 250% 0;
73
+ }
74
+ }
75
+
76
+ @media (prefers-reduced-motion: reduce) {
77
+ .progress-bar {
78
+ transition: none;
79
+ }
80
+
81
+ /* A full, dimmed bar instead of the movement */
82
+ .progress--indeterminate .progress-bar {
83
+ width: 100%;
84
+ opacity: 0.4;
85
+ animation: none;
86
+ }
87
+ }
88
+ </style>
@@ -0,0 +1,12 @@
1
+ import type { HTMLAttributes } from 'svelte/elements';
2
+ type Props = Omit<HTMLAttributes<HTMLDivElement>, 'children'> & {
3
+ /** Progress between 0 and `max`. Omit while the amount is unknown: the bar then runs back and forth. */
4
+ value?: number;
5
+ max?: number;
6
+ size?: 'sm' | 'md';
7
+ /** Accessible name, e.g. what is loading. */
8
+ label?: string;
9
+ };
10
+ declare const Progress: import("svelte").Component<Props, {}, "">;
11
+ type Progress = ReturnType<typeof Progress>;
12
+ export default Progress;