@mkatogui/uds-svelte 0.2.1
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 +100 -0
- package/package.json +27 -0
- package/src/components/Accordion.svelte +99 -0
- package/src/components/Alert.svelte +67 -0
- package/src/components/Avatar.svelte +62 -0
- package/src/components/Badge.svelte +50 -0
- package/src/components/Breadcrumb.svelte +60 -0
- package/src/components/Button.svelte +53 -0
- package/src/components/Checkbox.svelte +62 -0
- package/src/components/CodeBlock.svelte +90 -0
- package/src/components/CommandPalette.svelte +133 -0
- package/src/components/DataTable.svelte +140 -0
- package/src/components/DatePicker.svelte +73 -0
- package/src/components/DropdownMenu.svelte +113 -0
- package/src/components/FeatureCard.svelte +63 -0
- package/src/components/FileUpload.svelte +120 -0
- package/src/components/Footer.svelte +72 -0
- package/src/components/FormInput.svelte +102 -0
- package/src/components/HeroSection.svelte +65 -0
- package/src/components/Modal.svelte +67 -0
- package/src/components/NavigationBar.svelte +56 -0
- package/src/components/Pagination.svelte +115 -0
- package/src/components/PricingTable.svelte +89 -0
- package/src/components/ProgressIndicator.svelte +86 -0
- package/src/components/Radio.svelte +64 -0
- package/src/components/Select.svelte +85 -0
- package/src/components/SideNavigation.svelte +130 -0
- package/src/components/Skeleton.svelte +53 -0
- package/src/components/SocialProofBar.svelte +90 -0
- package/src/components/Tabs.svelte +100 -0
- package/src/components/TestimonialCard.svelte +71 -0
- package/src/components/Toast.svelte +83 -0
- package/src/components/ToggleSwitch.svelte +61 -0
- package/src/components/Tooltip.svelte +67 -0
- package/src/index.d.ts +553 -0
- package/src/index.js +32 -0
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
interface FooterColumn {
|
|
3
|
+
title: string;
|
|
4
|
+
links: { label: string; href: string }[];
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
interface Props {
|
|
8
|
+
variant?: 'simple' | 'multi-column' | 'newsletter' | 'mega-footer';
|
|
9
|
+
size?: 'standard' | 'compact';
|
|
10
|
+
columns?: FooterColumn[];
|
|
11
|
+
copyright?: string;
|
|
12
|
+
class?: string;
|
|
13
|
+
children?: import('svelte').Snippet;
|
|
14
|
+
newsletter?: import('svelte').Snippet;
|
|
15
|
+
legal?: import('svelte').Snippet;
|
|
16
|
+
[key: string]: any;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
let {
|
|
20
|
+
variant = 'simple',
|
|
21
|
+
size = 'standard',
|
|
22
|
+
columns = [],
|
|
23
|
+
copyright = '',
|
|
24
|
+
class: className = '',
|
|
25
|
+
children,
|
|
26
|
+
newsletter,
|
|
27
|
+
legal,
|
|
28
|
+
...rest
|
|
29
|
+
}: Props = $props();
|
|
30
|
+
|
|
31
|
+
let classes = $derived(
|
|
32
|
+
[
|
|
33
|
+
'uds-footer',
|
|
34
|
+
`uds-footer--${variant}`,
|
|
35
|
+
`uds-footer--${size}`,
|
|
36
|
+
className,
|
|
37
|
+
]
|
|
38
|
+
.filter(Boolean)
|
|
39
|
+
.join(' ')
|
|
40
|
+
);
|
|
41
|
+
</script>
|
|
42
|
+
|
|
43
|
+
<footer class={classes} aria-label="Site footer" {...rest}>
|
|
44
|
+
{#if columns.length > 0}
|
|
45
|
+
<div class="uds-footer__columns">
|
|
46
|
+
{#each columns as column}
|
|
47
|
+
<div class="uds-footer__column">
|
|
48
|
+
<h3 class="uds-footer__column-title">{column.title}</h3>
|
|
49
|
+
<ul class="uds-footer__link-list">
|
|
50
|
+
{#each column.links as link}
|
|
51
|
+
<li><a href={link.href}>{link.label}</a></li>
|
|
52
|
+
{/each}
|
|
53
|
+
</ul>
|
|
54
|
+
</div>
|
|
55
|
+
{/each}
|
|
56
|
+
</div>
|
|
57
|
+
{/if}
|
|
58
|
+
{#if newsletter}
|
|
59
|
+
<div class="uds-footer__newsletter">
|
|
60
|
+
{@render newsletter()}
|
|
61
|
+
</div>
|
|
62
|
+
{/if}
|
|
63
|
+
{@render children?.()}
|
|
64
|
+
{#if legal}
|
|
65
|
+
<div class="uds-footer__legal">
|
|
66
|
+
{@render legal()}
|
|
67
|
+
</div>
|
|
68
|
+
{/if}
|
|
69
|
+
{#if copyright}
|
|
70
|
+
<div class="uds-footer__copyright">{copyright}</div>
|
|
71
|
+
{/if}
|
|
72
|
+
</footer>
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
interface Props {
|
|
3
|
+
variant?: 'text' | 'email' | 'password' | 'number' | 'search' | 'textarea';
|
|
4
|
+
size?: 'sm' | 'md' | 'lg';
|
|
5
|
+
state?: 'default' | 'focus' | 'error' | 'disabled' | 'readonly';
|
|
6
|
+
label?: string;
|
|
7
|
+
helperText?: string;
|
|
8
|
+
errorText?: string;
|
|
9
|
+
required?: boolean;
|
|
10
|
+
disabled?: boolean;
|
|
11
|
+
readonly?: boolean;
|
|
12
|
+
value?: string;
|
|
13
|
+
placeholder?: string;
|
|
14
|
+
id?: string;
|
|
15
|
+
class?: string;
|
|
16
|
+
[key: string]: any;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
let {
|
|
20
|
+
variant = 'text',
|
|
21
|
+
size = 'md',
|
|
22
|
+
state = 'default',
|
|
23
|
+
label = '',
|
|
24
|
+
helperText = '',
|
|
25
|
+
errorText = '',
|
|
26
|
+
required = false,
|
|
27
|
+
disabled = false,
|
|
28
|
+
readonly: readOnly = false,
|
|
29
|
+
value = $bindable(''),
|
|
30
|
+
placeholder = '',
|
|
31
|
+
id,
|
|
32
|
+
class: className = '',
|
|
33
|
+
...rest
|
|
34
|
+
}: Props = $props();
|
|
35
|
+
|
|
36
|
+
let inputId = $derived(id || `uds-input-${Math.random().toString(36).slice(2, 9)}`);
|
|
37
|
+
let helperId = $derived(`${inputId}-helper`);
|
|
38
|
+
let errorId = $derived(`${inputId}-error`);
|
|
39
|
+
let isError = $derived(state === 'error' || !!errorText);
|
|
40
|
+
|
|
41
|
+
let classes = $derived(
|
|
42
|
+
[
|
|
43
|
+
'uds-input',
|
|
44
|
+
`uds-input--${variant}`,
|
|
45
|
+
`uds-input--${size}`,
|
|
46
|
+
isError && 'uds-input--error',
|
|
47
|
+
disabled && 'uds-input--disabled',
|
|
48
|
+
readOnly && 'uds-input--readonly',
|
|
49
|
+
className,
|
|
50
|
+
]
|
|
51
|
+
.filter(Boolean)
|
|
52
|
+
.join(' ')
|
|
53
|
+
);
|
|
54
|
+
|
|
55
|
+
let describedBy = $derived(
|
|
56
|
+
[errorText && errorId, helperText && helperId].filter(Boolean).join(' ') || undefined
|
|
57
|
+
);
|
|
58
|
+
</script>
|
|
59
|
+
|
|
60
|
+
<div class={classes}>
|
|
61
|
+
{#if label}
|
|
62
|
+
<label class="uds-input__label" for={inputId}>
|
|
63
|
+
{label}
|
|
64
|
+
{#if required}
|
|
65
|
+
<span class="uds-input__required" aria-hidden="true">*</span>
|
|
66
|
+
{/if}
|
|
67
|
+
</label>
|
|
68
|
+
{/if}
|
|
69
|
+
{#if variant === 'textarea'}
|
|
70
|
+
<textarea
|
|
71
|
+
class="uds-input__field"
|
|
72
|
+
id={inputId}
|
|
73
|
+
bind:value
|
|
74
|
+
{placeholder}
|
|
75
|
+
{required}
|
|
76
|
+
{disabled}
|
|
77
|
+
readonly={readOnly}
|
|
78
|
+
aria-invalid={isError || undefined}
|
|
79
|
+
aria-describedby={describedBy}
|
|
80
|
+
{...rest}
|
|
81
|
+
></textarea>
|
|
82
|
+
{:else}
|
|
83
|
+
<input
|
|
84
|
+
class="uds-input__field"
|
|
85
|
+
type={variant}
|
|
86
|
+
id={inputId}
|
|
87
|
+
bind:value
|
|
88
|
+
{placeholder}
|
|
89
|
+
{required}
|
|
90
|
+
{disabled}
|
|
91
|
+
readonly={readOnly}
|
|
92
|
+
aria-invalid={isError || undefined}
|
|
93
|
+
aria-describedby={describedBy}
|
|
94
|
+
{...rest}
|
|
95
|
+
/>
|
|
96
|
+
{/if}
|
|
97
|
+
{#if errorText}
|
|
98
|
+
<p class="uds-input__error" id={errorId} role="alert">{errorText}</p>
|
|
99
|
+
{:else if helperText}
|
|
100
|
+
<p class="uds-input__helper" id={helperId}>{helperText}</p>
|
|
101
|
+
{/if}
|
|
102
|
+
</div>
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
interface Props {
|
|
3
|
+
variant?: 'centered' | 'product-screenshot' | 'video-bg' | 'gradient-mesh' | 'search-forward' | 'split';
|
|
4
|
+
size?: 'full' | 'compact';
|
|
5
|
+
headline?: string;
|
|
6
|
+
subheadline?: string;
|
|
7
|
+
class?: string;
|
|
8
|
+
children?: import('svelte').Snippet;
|
|
9
|
+
cta?: import('svelte').Snippet;
|
|
10
|
+
socialProof?: import('svelte').Snippet;
|
|
11
|
+
visual?: import('svelte').Snippet;
|
|
12
|
+
[key: string]: any;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
let {
|
|
16
|
+
variant = 'centered',
|
|
17
|
+
size = 'full',
|
|
18
|
+
headline = '',
|
|
19
|
+
subheadline = '',
|
|
20
|
+
class: className = '',
|
|
21
|
+
children,
|
|
22
|
+
cta,
|
|
23
|
+
socialProof,
|
|
24
|
+
visual,
|
|
25
|
+
...rest
|
|
26
|
+
}: Props = $props();
|
|
27
|
+
|
|
28
|
+
let classes = $derived(
|
|
29
|
+
[
|
|
30
|
+
'uds-hero',
|
|
31
|
+
`uds-hero--${variant}`,
|
|
32
|
+
`uds-hero--${size}`,
|
|
33
|
+
className,
|
|
34
|
+
]
|
|
35
|
+
.filter(Boolean)
|
|
36
|
+
.join(' ')
|
|
37
|
+
);
|
|
38
|
+
</script>
|
|
39
|
+
|
|
40
|
+
<section class={classes} {...rest}>
|
|
41
|
+
<div class="uds-hero__content">
|
|
42
|
+
{#if headline}
|
|
43
|
+
<h1 class="uds-hero__headline">{headline}</h1>
|
|
44
|
+
{/if}
|
|
45
|
+
{#if subheadline}
|
|
46
|
+
<p class="uds-hero__subheadline">{subheadline}</p>
|
|
47
|
+
{/if}
|
|
48
|
+
{#if cta}
|
|
49
|
+
<div class="uds-hero__cta">
|
|
50
|
+
{@render cta()}
|
|
51
|
+
</div>
|
|
52
|
+
{/if}
|
|
53
|
+
{#if socialProof}
|
|
54
|
+
<div class="uds-hero__social-proof">
|
|
55
|
+
{@render socialProof()}
|
|
56
|
+
</div>
|
|
57
|
+
{/if}
|
|
58
|
+
{@render children?.()}
|
|
59
|
+
</div>
|
|
60
|
+
{#if visual}
|
|
61
|
+
<div class="uds-hero__visual">
|
|
62
|
+
{@render visual()}
|
|
63
|
+
</div>
|
|
64
|
+
{/if}
|
|
65
|
+
</section>
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
interface Props {
|
|
3
|
+
variant?: 'confirmation' | 'task' | 'alert';
|
|
4
|
+
size?: 'sm' | 'md' | 'lg';
|
|
5
|
+
open?: boolean;
|
|
6
|
+
title?: string;
|
|
7
|
+
onClose?: () => void;
|
|
8
|
+
class?: string;
|
|
9
|
+
children?: import('svelte').Snippet;
|
|
10
|
+
actions?: import('svelte').Snippet;
|
|
11
|
+
[key: string]: any;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
let {
|
|
15
|
+
variant = 'confirmation',
|
|
16
|
+
size = 'md',
|
|
17
|
+
open = false,
|
|
18
|
+
title = '',
|
|
19
|
+
onClose,
|
|
20
|
+
class: className = '',
|
|
21
|
+
children,
|
|
22
|
+
actions,
|
|
23
|
+
...rest
|
|
24
|
+
}: Props = $props();
|
|
25
|
+
|
|
26
|
+
let classes = $derived(
|
|
27
|
+
[
|
|
28
|
+
'uds-modal',
|
|
29
|
+
`uds-modal--${variant}`,
|
|
30
|
+
`uds-modal--${size}`,
|
|
31
|
+
open && 'uds-modal--open',
|
|
32
|
+
className,
|
|
33
|
+
]
|
|
34
|
+
.filter(Boolean)
|
|
35
|
+
.join(' ')
|
|
36
|
+
);
|
|
37
|
+
|
|
38
|
+
function handleKeydown(event: KeyboardEvent) {
|
|
39
|
+
if (event.key === 'Escape' && open) {
|
|
40
|
+
onClose?.();
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
</script>
|
|
44
|
+
|
|
45
|
+
<svelte:window onkeydown={handleKeydown} />
|
|
46
|
+
|
|
47
|
+
{#if open}
|
|
48
|
+
<div class="uds-modal__overlay" onclick={onClose} aria-hidden="true"></div>
|
|
49
|
+
<div class={classes} role="dialog" aria-modal="true" aria-label={title} {...rest}>
|
|
50
|
+
<div class="uds-modal__header">
|
|
51
|
+
{#if title}
|
|
52
|
+
<h2 class="uds-modal__title">{title}</h2>
|
|
53
|
+
{/if}
|
|
54
|
+
<button class="uds-modal__close" onclick={onClose} aria-label="Close dialog">
|
|
55
|
+
<span aria-hidden="true">×</span>
|
|
56
|
+
</button>
|
|
57
|
+
</div>
|
|
58
|
+
<div class="uds-modal__body">
|
|
59
|
+
{@render children?.()}
|
|
60
|
+
</div>
|
|
61
|
+
{#if actions}
|
|
62
|
+
<div class="uds-modal__actions">
|
|
63
|
+
{@render actions()}
|
|
64
|
+
</div>
|
|
65
|
+
{/if}
|
|
66
|
+
</div>
|
|
67
|
+
{/if}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
interface Props {
|
|
3
|
+
variant?: 'standard' | 'minimal' | 'dark' | 'transparent';
|
|
4
|
+
sticky?: boolean;
|
|
5
|
+
blurOnScroll?: boolean;
|
|
6
|
+
megaMenu?: boolean;
|
|
7
|
+
darkModeToggle?: boolean;
|
|
8
|
+
mobileOpen?: boolean;
|
|
9
|
+
class?: string;
|
|
10
|
+
children?: import('svelte').Snippet;
|
|
11
|
+
ctaButton?: import('svelte').Snippet;
|
|
12
|
+
[key: string]: any;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
let {
|
|
16
|
+
variant = 'standard',
|
|
17
|
+
sticky = false,
|
|
18
|
+
blurOnScroll = false,
|
|
19
|
+
megaMenu = false,
|
|
20
|
+
darkModeToggle = false,
|
|
21
|
+
mobileOpen = false,
|
|
22
|
+
class: className = '',
|
|
23
|
+
children,
|
|
24
|
+
ctaButton,
|
|
25
|
+
...rest
|
|
26
|
+
}: Props = $props();
|
|
27
|
+
|
|
28
|
+
let classes = $derived(
|
|
29
|
+
[
|
|
30
|
+
'uds-navbar',
|
|
31
|
+
`uds-navbar--${variant}`,
|
|
32
|
+
sticky && 'uds-navbar--sticky',
|
|
33
|
+
blurOnScroll && 'uds-navbar--blur',
|
|
34
|
+
mobileOpen && 'uds-navbar--mobile-open',
|
|
35
|
+
className,
|
|
36
|
+
]
|
|
37
|
+
.filter(Boolean)
|
|
38
|
+
.join(' ')
|
|
39
|
+
);
|
|
40
|
+
</script>
|
|
41
|
+
|
|
42
|
+
<nav class={classes} aria-label="Main navigation" {...rest}>
|
|
43
|
+
{@render children?.()}
|
|
44
|
+
{#if ctaButton}
|
|
45
|
+
<div class="uds-navbar__cta">
|
|
46
|
+
{@render ctaButton()}
|
|
47
|
+
</div>
|
|
48
|
+
{/if}
|
|
49
|
+
<button
|
|
50
|
+
class="uds-navbar__mobile-toggle"
|
|
51
|
+
aria-expanded={mobileOpen}
|
|
52
|
+
aria-label="Toggle navigation menu"
|
|
53
|
+
>
|
|
54
|
+
<span class="uds-navbar__hamburger" aria-hidden="true"></span>
|
|
55
|
+
</button>
|
|
56
|
+
</nav>
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
interface Props {
|
|
3
|
+
variant?: 'numbered' | 'simple' | 'load-more' | 'infinite-scroll';
|
|
4
|
+
size?: 'sm' | 'md';
|
|
5
|
+
currentPage?: number;
|
|
6
|
+
totalPages?: number;
|
|
7
|
+
onPageChange?: (page: number) => void;
|
|
8
|
+
class?: string;
|
|
9
|
+
[key: string]: any;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
let {
|
|
13
|
+
variant = 'numbered',
|
|
14
|
+
size = 'md',
|
|
15
|
+
currentPage = 1,
|
|
16
|
+
totalPages = 1,
|
|
17
|
+
onPageChange,
|
|
18
|
+
class: className = '',
|
|
19
|
+
...rest
|
|
20
|
+
}: Props = $props();
|
|
21
|
+
|
|
22
|
+
let classes = $derived(
|
|
23
|
+
[
|
|
24
|
+
'uds-pagination',
|
|
25
|
+
`uds-pagination--${variant}`,
|
|
26
|
+
`uds-pagination--${size}`,
|
|
27
|
+
className,
|
|
28
|
+
]
|
|
29
|
+
.filter(Boolean)
|
|
30
|
+
.join(' ')
|
|
31
|
+
);
|
|
32
|
+
|
|
33
|
+
let pages = $derived(() => {
|
|
34
|
+
const p: (number | string)[] = [];
|
|
35
|
+
for (let i = 1; i <= totalPages; i++) {
|
|
36
|
+
if (i === 1 || i === totalPages || (i >= currentPage - 1 && i <= currentPage + 1)) {
|
|
37
|
+
p.push(i);
|
|
38
|
+
} else if (p[p.length - 1] !== '...') {
|
|
39
|
+
p.push('...');
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
return p;
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
function goTo(page: number) {
|
|
46
|
+
if (page >= 1 && page <= totalPages && page !== currentPage) {
|
|
47
|
+
onPageChange?.(page);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
</script>
|
|
51
|
+
|
|
52
|
+
<nav class={classes} aria-label="Pagination" {...rest}>
|
|
53
|
+
{#if variant === 'simple'}
|
|
54
|
+
<button
|
|
55
|
+
class="uds-pagination__prev"
|
|
56
|
+
disabled={currentPage <= 1}
|
|
57
|
+
onclick={() => goTo(currentPage - 1)}
|
|
58
|
+
aria-label="Previous page"
|
|
59
|
+
>
|
|
60
|
+
Previous
|
|
61
|
+
</button>
|
|
62
|
+
<span class="uds-pagination__info">Page {currentPage} of {totalPages}</span>
|
|
63
|
+
<button
|
|
64
|
+
class="uds-pagination__next"
|
|
65
|
+
disabled={currentPage >= totalPages}
|
|
66
|
+
onclick={() => goTo(currentPage + 1)}
|
|
67
|
+
aria-label="Next page"
|
|
68
|
+
>
|
|
69
|
+
Next
|
|
70
|
+
</button>
|
|
71
|
+
{:else if variant === 'load-more'}
|
|
72
|
+
<button
|
|
73
|
+
class="uds-pagination__load-more"
|
|
74
|
+
disabled={currentPage >= totalPages}
|
|
75
|
+
onclick={() => goTo(currentPage + 1)}
|
|
76
|
+
>
|
|
77
|
+
Load more
|
|
78
|
+
</button>
|
|
79
|
+
{:else}
|
|
80
|
+
<button
|
|
81
|
+
class="uds-pagination__prev"
|
|
82
|
+
disabled={currentPage <= 1}
|
|
83
|
+
onclick={() => goTo(currentPage - 1)}
|
|
84
|
+
aria-label="Previous page"
|
|
85
|
+
>
|
|
86
|
+
«
|
|
87
|
+
</button>
|
|
88
|
+
<ol class="uds-pagination__list">
|
|
89
|
+
{#each pages() as page}
|
|
90
|
+
{#if typeof page === 'number'}
|
|
91
|
+
<li>
|
|
92
|
+
<button
|
|
93
|
+
class="uds-pagination__page"
|
|
94
|
+
class:uds-pagination__page--active={page === currentPage}
|
|
95
|
+
aria-current={page === currentPage ? 'page' : undefined}
|
|
96
|
+
onclick={() => goTo(page)}
|
|
97
|
+
>
|
|
98
|
+
{page}
|
|
99
|
+
</button>
|
|
100
|
+
</li>
|
|
101
|
+
{:else}
|
|
102
|
+
<li class="uds-pagination__ellipsis" aria-hidden="true">…</li>
|
|
103
|
+
{/if}
|
|
104
|
+
{/each}
|
|
105
|
+
</ol>
|
|
106
|
+
<button
|
|
107
|
+
class="uds-pagination__next"
|
|
108
|
+
disabled={currentPage >= totalPages}
|
|
109
|
+
onclick={() => goTo(currentPage + 1)}
|
|
110
|
+
aria-label="Next page"
|
|
111
|
+
>
|
|
112
|
+
»
|
|
113
|
+
</button>
|
|
114
|
+
{/if}
|
|
115
|
+
</nav>
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
interface PricingPlan {
|
|
3
|
+
name: string;
|
|
4
|
+
price: string;
|
|
5
|
+
period?: string;
|
|
6
|
+
features: string[];
|
|
7
|
+
cta?: string;
|
|
8
|
+
highlighted?: boolean;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
interface Props {
|
|
12
|
+
variant?: '2-column' | '3-column' | '4-column' | 'toggle';
|
|
13
|
+
size?: 'standard' | 'compact';
|
|
14
|
+
plans?: PricingPlan[];
|
|
15
|
+
highlightedPlan?: string;
|
|
16
|
+
billingPeriod?: 'monthly' | 'annual';
|
|
17
|
+
onToggle?: (period: string) => void;
|
|
18
|
+
class?: string;
|
|
19
|
+
children?: import('svelte').Snippet;
|
|
20
|
+
[key: string]: any;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
let {
|
|
24
|
+
variant = '3-column',
|
|
25
|
+
size = 'standard',
|
|
26
|
+
plans = [],
|
|
27
|
+
highlightedPlan,
|
|
28
|
+
billingPeriod = 'monthly',
|
|
29
|
+
onToggle,
|
|
30
|
+
class: className = '',
|
|
31
|
+
children,
|
|
32
|
+
...rest
|
|
33
|
+
}: Props = $props();
|
|
34
|
+
|
|
35
|
+
let classes = $derived(
|
|
36
|
+
[
|
|
37
|
+
'uds-pricing',
|
|
38
|
+
`uds-pricing--${variant}`,
|
|
39
|
+
`uds-pricing--${size}`,
|
|
40
|
+
className,
|
|
41
|
+
]
|
|
42
|
+
.filter(Boolean)
|
|
43
|
+
.join(' ')
|
|
44
|
+
);
|
|
45
|
+
</script>
|
|
46
|
+
|
|
47
|
+
<div class={classes} {...rest}>
|
|
48
|
+
{#if variant === 'toggle'}
|
|
49
|
+
<div class="uds-pricing__toggle" role="group" aria-label="Billing period">
|
|
50
|
+
<button
|
|
51
|
+
class="uds-pricing__toggle-btn"
|
|
52
|
+
class:uds-pricing__toggle-btn--active={billingPeriod === 'monthly'}
|
|
53
|
+
onclick={() => onToggle?.('monthly')}
|
|
54
|
+
>
|
|
55
|
+
Monthly
|
|
56
|
+
</button>
|
|
57
|
+
<button
|
|
58
|
+
class="uds-pricing__toggle-btn"
|
|
59
|
+
class:uds-pricing__toggle-btn--active={billingPeriod === 'annual'}
|
|
60
|
+
onclick={() => onToggle?.('annual')}
|
|
61
|
+
>
|
|
62
|
+
Annual
|
|
63
|
+
</button>
|
|
64
|
+
</div>
|
|
65
|
+
{/if}
|
|
66
|
+
<div class="uds-pricing__plans">
|
|
67
|
+
{#each plans as plan}
|
|
68
|
+
<div
|
|
69
|
+
class="uds-pricing__plan"
|
|
70
|
+
class:uds-pricing__plan--highlighted={plan.highlighted || plan.name === highlightedPlan}
|
|
71
|
+
>
|
|
72
|
+
<h3 class="uds-pricing__plan-name">{plan.name}</h3>
|
|
73
|
+
<div class="uds-pricing__plan-price">{plan.price}</div>
|
|
74
|
+
{#if plan.period}
|
|
75
|
+
<div class="uds-pricing__plan-period">{plan.period}</div>
|
|
76
|
+
{/if}
|
|
77
|
+
<ul class="uds-pricing__features">
|
|
78
|
+
{#each plan.features as feature}
|
|
79
|
+
<li class="uds-pricing__feature">{feature}</li>
|
|
80
|
+
{/each}
|
|
81
|
+
</ul>
|
|
82
|
+
{#if plan.cta}
|
|
83
|
+
<button class="uds-pricing__cta">{plan.cta}</button>
|
|
84
|
+
{/if}
|
|
85
|
+
</div>
|
|
86
|
+
{/each}
|
|
87
|
+
</div>
|
|
88
|
+
{@render children?.()}
|
|
89
|
+
</div>
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
interface Props {
|
|
3
|
+
variant?: 'bar' | 'circular' | 'stepper';
|
|
4
|
+
size?: 'sm' | 'md' | 'lg';
|
|
5
|
+
value?: number;
|
|
6
|
+
max?: number;
|
|
7
|
+
label?: string;
|
|
8
|
+
showValue?: boolean;
|
|
9
|
+
indeterminate?: boolean;
|
|
10
|
+
class?: string;
|
|
11
|
+
children?: import('svelte').Snippet;
|
|
12
|
+
[key: string]: any;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
let {
|
|
16
|
+
variant = 'bar',
|
|
17
|
+
size = 'md',
|
|
18
|
+
value = 0,
|
|
19
|
+
max = 100,
|
|
20
|
+
label = '',
|
|
21
|
+
showValue = false,
|
|
22
|
+
indeterminate = false,
|
|
23
|
+
class: className = '',
|
|
24
|
+
children,
|
|
25
|
+
...rest
|
|
26
|
+
}: Props = $props();
|
|
27
|
+
|
|
28
|
+
let percentage = $derived(Math.min(Math.round((value / max) * 100), 100));
|
|
29
|
+
|
|
30
|
+
let classes = $derived(
|
|
31
|
+
[
|
|
32
|
+
'uds-progress',
|
|
33
|
+
`uds-progress--${variant}`,
|
|
34
|
+
`uds-progress--${size}`,
|
|
35
|
+
indeterminate && 'uds-progress--indeterminate',
|
|
36
|
+
className,
|
|
37
|
+
]
|
|
38
|
+
.filter(Boolean)
|
|
39
|
+
.join(' ')
|
|
40
|
+
);
|
|
41
|
+
|
|
42
|
+
let circumference = $derived(2 * Math.PI * 40);
|
|
43
|
+
let strokeOffset = $derived(circumference - (percentage / 100) * circumference);
|
|
44
|
+
</script>
|
|
45
|
+
|
|
46
|
+
<div
|
|
47
|
+
class={classes}
|
|
48
|
+
role="progressbar"
|
|
49
|
+
aria-valuenow={indeterminate ? undefined : value}
|
|
50
|
+
aria-valuemin={0}
|
|
51
|
+
aria-valuemax={max}
|
|
52
|
+
aria-label={label || undefined}
|
|
53
|
+
{...rest}
|
|
54
|
+
>
|
|
55
|
+
{#if variant === 'bar'}
|
|
56
|
+
<div class="uds-progress__track">
|
|
57
|
+
<div class="uds-progress__fill" style:width={indeterminate ? undefined : `${percentage}%`}></div>
|
|
58
|
+
</div>
|
|
59
|
+
{#if showValue && !indeterminate}
|
|
60
|
+
<span class="uds-progress__value">{percentage}%</span>
|
|
61
|
+
{/if}
|
|
62
|
+
{:else if variant === 'circular'}
|
|
63
|
+
<svg class="uds-progress__svg" viewBox="0 0 100 100">
|
|
64
|
+
<circle class="uds-progress__track-circle" cx="50" cy="50" r="40" fill="none" stroke-width="8" />
|
|
65
|
+
{#if !indeterminate}
|
|
66
|
+
<circle
|
|
67
|
+
class="uds-progress__fill-circle"
|
|
68
|
+
cx="50" cy="50" r="40"
|
|
69
|
+
fill="none"
|
|
70
|
+
stroke-width="8"
|
|
71
|
+
stroke-dasharray={circumference}
|
|
72
|
+
stroke-dashoffset={strokeOffset}
|
|
73
|
+
transform="rotate(-90 50 50)"
|
|
74
|
+
/>
|
|
75
|
+
{/if}
|
|
76
|
+
</svg>
|
|
77
|
+
{#if showValue && !indeterminate}
|
|
78
|
+
<span class="uds-progress__value">{percentage}%</span>
|
|
79
|
+
{/if}
|
|
80
|
+
{:else if variant === 'stepper'}
|
|
81
|
+
{@render children?.()}
|
|
82
|
+
{/if}
|
|
83
|
+
{#if label}
|
|
84
|
+
<span class="uds-progress__label">{label}</span>
|
|
85
|
+
{/if}
|
|
86
|
+
</div>
|