@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
package/README.md
ADDED
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
# @mkatogui/uds-svelte
|
|
2
|
+
|
|
3
|
+
Svelte 5 components for the Universal Design System. 32 accessible, themeable components built with Svelte 5 runes, BEM naming, and full WCAG 2.1 AA compliance.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install @mkatogui/uds-svelte
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
Requires Svelte 5 or later as a peer dependency.
|
|
12
|
+
|
|
13
|
+
## Usage
|
|
14
|
+
|
|
15
|
+
```svelte
|
|
16
|
+
<script>
|
|
17
|
+
import { Button, Alert, Modal } from '@mkatogui/uds-svelte';
|
|
18
|
+
</script>
|
|
19
|
+
|
|
20
|
+
<Button variant="primary" size="md" onclick={() => console.log('clicked')}>
|
|
21
|
+
Get Started
|
|
22
|
+
</Button>
|
|
23
|
+
|
|
24
|
+
<Alert variant="success" title="Done" message="Operation completed successfully." dismissible />
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## Theming
|
|
28
|
+
|
|
29
|
+
Components use UDS CSS custom properties (`--color-*`, `--space-*`, `--font-size-*`). Apply a palette with the `data-theme` attribute on a parent element:
|
|
30
|
+
|
|
31
|
+
```html
|
|
32
|
+
<div data-theme="minimal-saas">
|
|
33
|
+
<!-- All UDS components inside inherit the palette -->
|
|
34
|
+
</div>
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
Available palettes: `minimal-saas`, `ai-futuristic`, `gradient-startup`, `corporate`, `apple-minimal`, `illustration`, `dashboard`, `bold-lifestyle`, `minimal-corporate`.
|
|
38
|
+
|
|
39
|
+
## Components
|
|
40
|
+
|
|
41
|
+
| Component | CSS Class | Variants |
|
|
42
|
+
|---|---|---|
|
|
43
|
+
| Button | `uds-btn` | primary, secondary, ghost, gradient, destructive, icon-only |
|
|
44
|
+
| NavigationBar | `uds-navbar` | standard, minimal, dark, transparent |
|
|
45
|
+
| HeroSection | `uds-hero` | centered, product-screenshot, video-bg, gradient-mesh, search-forward, split |
|
|
46
|
+
| FeatureCard | `uds-card` | icon-top, image-top, horizontal, stat-card, dashboard-preview |
|
|
47
|
+
| PricingTable | `uds-pricing` | 2-column, 3-column, 4-column, toggle |
|
|
48
|
+
| SocialProofBar | `uds-social-proof` | logo-strip, stats-counter, testimonial-mini, combined |
|
|
49
|
+
| TestimonialCard | `uds-testimonial` | quote-card, video, metric, carousel |
|
|
50
|
+
| Footer | `uds-footer` | simple, multi-column, newsletter, mega-footer |
|
|
51
|
+
| CodeBlock | `uds-code-block` | syntax-highlighted, terminal, multi-tab |
|
|
52
|
+
| Modal | `uds-modal` | confirmation, task, alert |
|
|
53
|
+
| FormInput | `uds-input` | text, email, password, number, search, textarea |
|
|
54
|
+
| Select | `uds-select` | native, custom |
|
|
55
|
+
| Checkbox | `uds-checkbox` | standard, indeterminate |
|
|
56
|
+
| Radio | `uds-radio` | standard, card |
|
|
57
|
+
| ToggleSwitch | `uds-toggle` | standard, with-label |
|
|
58
|
+
| Alert | `uds-alert` | success, warning, error, info |
|
|
59
|
+
| Badge | `uds-badge` | status, count, tag |
|
|
60
|
+
| Tabs | `uds-tabs` | line, pill, segmented |
|
|
61
|
+
| Accordion | `uds-accordion` | single, multi, flush |
|
|
62
|
+
| Breadcrumb | `uds-breadcrumb` | standard, truncated |
|
|
63
|
+
| Tooltip | `uds-tooltip` | simple, rich |
|
|
64
|
+
| DropdownMenu | `uds-dropdown` | action, context, nav-sub |
|
|
65
|
+
| Avatar | `uds-avatar` | image, initials, icon, group |
|
|
66
|
+
| Skeleton | `uds-skeleton` | text, card, avatar, table |
|
|
67
|
+
| Toast | `uds-toast` | success, error, warning, info, neutral |
|
|
68
|
+
| Pagination | `uds-pagination` | numbered, simple, load-more, infinite-scroll |
|
|
69
|
+
| DataTable | `uds-data-table` | basic, sortable, selectable, expandable |
|
|
70
|
+
| DatePicker | `uds-date-picker` | single, range, with-time |
|
|
71
|
+
| CommandPalette | `uds-command-palette` | standard |
|
|
72
|
+
| ProgressIndicator | `uds-progress` | bar, circular, stepper |
|
|
73
|
+
| SideNavigation | `uds-side-nav` | default, collapsed, with-sections |
|
|
74
|
+
| FileUpload | `uds-file-upload` | dropzone, button, avatar-upload |
|
|
75
|
+
|
|
76
|
+
## Accessibility
|
|
77
|
+
|
|
78
|
+
All components follow WCAG 2.1 AA guidelines:
|
|
79
|
+
|
|
80
|
+
- Proper ARIA roles and attributes (dialog, alert, switch, tablist, etc.)
|
|
81
|
+
- Keyboard navigation support (arrow keys, Enter, Space, Escape)
|
|
82
|
+
- Focus management and visible focus indicators
|
|
83
|
+
- Label associations (`for`/`id` pairing on form controls)
|
|
84
|
+
- Semantic HTML elements (nav, blockquote, fieldset, etc.)
|
|
85
|
+
- Live regions for dynamic content (role=alert, role=status, aria-live)
|
|
86
|
+
|
|
87
|
+
## Svelte 5 Patterns
|
|
88
|
+
|
|
89
|
+
Components use Svelte 5 features:
|
|
90
|
+
|
|
91
|
+
- `$props()` rune for prop declarations
|
|
92
|
+
- `$derived()` rune for computed values
|
|
93
|
+
- `$state()` rune for internal state
|
|
94
|
+
- `$effect()` rune for side effects
|
|
95
|
+
- `$bindable()` for two-way binding on form values
|
|
96
|
+
- `{@render children?.()}` for snippet-based slot content
|
|
97
|
+
|
|
98
|
+
## License
|
|
99
|
+
|
|
100
|
+
MIT
|
package/package.json
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@mkatogui/uds-svelte",
|
|
3
|
+
"version": "0.2.1",
|
|
4
|
+
"description": "Svelte 5 components for Universal Design System — 31 accessible, themeable components",
|
|
5
|
+
"svelte": "./src/index.js",
|
|
6
|
+
"main": "./src/index.js",
|
|
7
|
+
"module": "./src/index.js",
|
|
8
|
+
"types": "./src/index.d.ts",
|
|
9
|
+
"exports": {
|
|
10
|
+
".": {
|
|
11
|
+
"svelte": "./src/index.js",
|
|
12
|
+
"types": "./src/index.d.ts"
|
|
13
|
+
}
|
|
14
|
+
},
|
|
15
|
+
"files": ["src/"],
|
|
16
|
+
"peerDependencies": {
|
|
17
|
+
"svelte": ">=5.0.0"
|
|
18
|
+
},
|
|
19
|
+
"keywords": ["svelte", "design-system", "components", "accessibility", "wcag"],
|
|
20
|
+
"author": "Marcelo Katogui",
|
|
21
|
+
"license": "MIT",
|
|
22
|
+
"repository": {
|
|
23
|
+
"type": "git",
|
|
24
|
+
"url": "git+https://github.com/mkatogui/universal-design-system.git",
|
|
25
|
+
"directory": "packages/svelte"
|
|
26
|
+
}
|
|
27
|
+
}
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
interface AccordionItem {
|
|
3
|
+
id: string;
|
|
4
|
+
title: string;
|
|
5
|
+
content: string;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
interface Props {
|
|
9
|
+
variant?: 'single' | 'multi' | 'flush';
|
|
10
|
+
items?: AccordionItem[];
|
|
11
|
+
defaultExpanded?: string[];
|
|
12
|
+
allowMultiple?: boolean;
|
|
13
|
+
class?: string;
|
|
14
|
+
children?: import('svelte').Snippet;
|
|
15
|
+
[key: string]: any;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
let {
|
|
19
|
+
variant = 'single',
|
|
20
|
+
items = [],
|
|
21
|
+
defaultExpanded = [],
|
|
22
|
+
allowMultiple = false,
|
|
23
|
+
class: className = '',
|
|
24
|
+
children,
|
|
25
|
+
...rest
|
|
26
|
+
}: Props = $props();
|
|
27
|
+
|
|
28
|
+
let expandedItems = $state<Set<string>>(new Set(defaultExpanded));
|
|
29
|
+
|
|
30
|
+
let classes = $derived(
|
|
31
|
+
[
|
|
32
|
+
'uds-accordion',
|
|
33
|
+
`uds-accordion--${variant}`,
|
|
34
|
+
className,
|
|
35
|
+
]
|
|
36
|
+
.filter(Boolean)
|
|
37
|
+
.join(' ')
|
|
38
|
+
);
|
|
39
|
+
|
|
40
|
+
function toggle(id: string) {
|
|
41
|
+
if (expandedItems.has(id)) {
|
|
42
|
+
expandedItems.delete(id);
|
|
43
|
+
expandedItems = new Set(expandedItems);
|
|
44
|
+
} else {
|
|
45
|
+
if (allowMultiple || variant === 'multi') {
|
|
46
|
+
expandedItems.add(id);
|
|
47
|
+
expandedItems = new Set(expandedItems);
|
|
48
|
+
} else {
|
|
49
|
+
expandedItems = new Set([id]);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function handleKeydown(event: KeyboardEvent, index: number) {
|
|
55
|
+
let nextIndex: number | undefined;
|
|
56
|
+
if (event.key === 'ArrowDown') {
|
|
57
|
+
nextIndex = (index + 1) % items.length;
|
|
58
|
+
} else if (event.key === 'ArrowUp') {
|
|
59
|
+
nextIndex = (index - 1 + items.length) % items.length;
|
|
60
|
+
}
|
|
61
|
+
if (nextIndex !== undefined) {
|
|
62
|
+
event.preventDefault();
|
|
63
|
+
const buttons = (event.currentTarget as HTMLElement)
|
|
64
|
+
.closest('.uds-accordion')?.querySelectorAll('.uds-accordion__trigger') as NodeListOf<HTMLElement>;
|
|
65
|
+
buttons?.[nextIndex]?.focus();
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
</script>
|
|
69
|
+
|
|
70
|
+
<div class={classes} {...rest}>
|
|
71
|
+
{#each items as item, i}
|
|
72
|
+
{@const isExpanded = expandedItems.has(item.id)}
|
|
73
|
+
<div class="uds-accordion__item" class:uds-accordion__item--expanded={isExpanded}>
|
|
74
|
+
<h3 class="uds-accordion__header">
|
|
75
|
+
<button
|
|
76
|
+
class="uds-accordion__trigger"
|
|
77
|
+
aria-expanded={isExpanded}
|
|
78
|
+
aria-controls="accordion-panel-{item.id}"
|
|
79
|
+
id="accordion-header-{item.id}"
|
|
80
|
+
onclick={() => toggle(item.id)}
|
|
81
|
+
onkeydown={(e) => handleKeydown(e, i)}
|
|
82
|
+
>
|
|
83
|
+
{item.title}
|
|
84
|
+
<span class="uds-accordion__icon" aria-hidden="true"></span>
|
|
85
|
+
</button>
|
|
86
|
+
</h3>
|
|
87
|
+
<div
|
|
88
|
+
class="uds-accordion__panel"
|
|
89
|
+
id="accordion-panel-{item.id}"
|
|
90
|
+
role="region"
|
|
91
|
+
aria-labelledby="accordion-header-{item.id}"
|
|
92
|
+
hidden={!isExpanded}
|
|
93
|
+
>
|
|
94
|
+
<div class="uds-accordion__content">{item.content}</div>
|
|
95
|
+
</div>
|
|
96
|
+
</div>
|
|
97
|
+
{/each}
|
|
98
|
+
{@render children?.()}
|
|
99
|
+
</div>
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
interface Props {
|
|
3
|
+
variant?: 'success' | 'warning' | 'error' | 'info';
|
|
4
|
+
size?: 'sm' | 'md' | 'lg';
|
|
5
|
+
title?: string;
|
|
6
|
+
message?: string;
|
|
7
|
+
dismissible?: boolean;
|
|
8
|
+
onDismiss?: () => void;
|
|
9
|
+
class?: string;
|
|
10
|
+
children?: import('svelte').Snippet;
|
|
11
|
+
[key: string]: any;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
let {
|
|
15
|
+
variant = 'info',
|
|
16
|
+
size = 'md',
|
|
17
|
+
title = '',
|
|
18
|
+
message = '',
|
|
19
|
+
dismissible = false,
|
|
20
|
+
onDismiss,
|
|
21
|
+
class: className = '',
|
|
22
|
+
children,
|
|
23
|
+
...rest
|
|
24
|
+
}: Props = $props();
|
|
25
|
+
|
|
26
|
+
let dismissed = $state(false);
|
|
27
|
+
|
|
28
|
+
let alertRole = $derived(
|
|
29
|
+
variant === 'error' || variant === 'warning' ? 'alert' : 'status'
|
|
30
|
+
);
|
|
31
|
+
|
|
32
|
+
let classes = $derived(
|
|
33
|
+
[
|
|
34
|
+
'uds-alert',
|
|
35
|
+
`uds-alert--${variant}`,
|
|
36
|
+
`uds-alert--${size}`,
|
|
37
|
+
dismissed && 'uds-alert--dismissing',
|
|
38
|
+
className,
|
|
39
|
+
]
|
|
40
|
+
.filter(Boolean)
|
|
41
|
+
.join(' ')
|
|
42
|
+
);
|
|
43
|
+
|
|
44
|
+
function handleDismiss() {
|
|
45
|
+
dismissed = true;
|
|
46
|
+
onDismiss?.();
|
|
47
|
+
}
|
|
48
|
+
</script>
|
|
49
|
+
|
|
50
|
+
{#if !dismissed}
|
|
51
|
+
<div class={classes} role={alertRole} {...rest}>
|
|
52
|
+
<div class="uds-alert__content">
|
|
53
|
+
{#if title}
|
|
54
|
+
<p class="uds-alert__title">{title}</p>
|
|
55
|
+
{/if}
|
|
56
|
+
{#if message}
|
|
57
|
+
<p class="uds-alert__message">{message}</p>
|
|
58
|
+
{/if}
|
|
59
|
+
{@render children?.()}
|
|
60
|
+
</div>
|
|
61
|
+
{#if dismissible}
|
|
62
|
+
<button class="uds-alert__dismiss" onclick={handleDismiss} aria-label="Dismiss alert">
|
|
63
|
+
<span aria-hidden="true">×</span>
|
|
64
|
+
</button>
|
|
65
|
+
{/if}
|
|
66
|
+
</div>
|
|
67
|
+
{/if}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
interface Props {
|
|
3
|
+
variant?: 'image' | 'initials' | 'icon' | 'group';
|
|
4
|
+
size?: 'xs' | 'sm' | 'md' | 'lg' | 'xl';
|
|
5
|
+
src?: string;
|
|
6
|
+
alt?: string;
|
|
7
|
+
initials?: string;
|
|
8
|
+
status?: 'online' | 'offline' | 'busy';
|
|
9
|
+
fallback?: string;
|
|
10
|
+
class?: string;
|
|
11
|
+
icon?: import('svelte').Snippet;
|
|
12
|
+
children?: import('svelte').Snippet;
|
|
13
|
+
[key: string]: any;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
let {
|
|
17
|
+
variant = 'image',
|
|
18
|
+
size = 'md',
|
|
19
|
+
src,
|
|
20
|
+
alt = '',
|
|
21
|
+
initials = '',
|
|
22
|
+
status,
|
|
23
|
+
fallback,
|
|
24
|
+
class: className = '',
|
|
25
|
+
icon,
|
|
26
|
+
children,
|
|
27
|
+
...rest
|
|
28
|
+
}: Props = $props();
|
|
29
|
+
|
|
30
|
+
let imgError = $state(false);
|
|
31
|
+
|
|
32
|
+
let classes = $derived(
|
|
33
|
+
[
|
|
34
|
+
'uds-avatar',
|
|
35
|
+
`uds-avatar--${variant}`,
|
|
36
|
+
`uds-avatar--${size}`,
|
|
37
|
+
status && `uds-avatar--${status}`,
|
|
38
|
+
className,
|
|
39
|
+
]
|
|
40
|
+
.filter(Boolean)
|
|
41
|
+
.join(' ')
|
|
42
|
+
);
|
|
43
|
+
</script>
|
|
44
|
+
|
|
45
|
+
{#if variant === 'group'}
|
|
46
|
+
<div class={classes} role="group" aria-label="User avatars" {...rest}>
|
|
47
|
+
{@render children?.()}
|
|
48
|
+
</div>
|
|
49
|
+
{:else}
|
|
50
|
+
<div class={classes} {...rest}>
|
|
51
|
+
{#if variant === 'image' && src && !imgError}
|
|
52
|
+
<img class="uds-avatar__image" src={src} alt={alt} onerror={() => (imgError = true)} />
|
|
53
|
+
{:else if variant === 'icon' && icon}
|
|
54
|
+
<span class="uds-avatar__icon" aria-hidden="true">{@render icon()}</span>
|
|
55
|
+
{:else}
|
|
56
|
+
<span class="uds-avatar__initials" aria-label={alt || initials}>{initials || fallback || '?'}</span>
|
|
57
|
+
{/if}
|
|
58
|
+
{#if status}
|
|
59
|
+
<span class="uds-avatar__status" aria-label={status}></span>
|
|
60
|
+
{/if}
|
|
61
|
+
</div>
|
|
62
|
+
{/if}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
interface Props {
|
|
3
|
+
variant?: 'status' | 'count' | 'tag';
|
|
4
|
+
size?: 'sm' | 'md';
|
|
5
|
+
label?: string;
|
|
6
|
+
color?: string;
|
|
7
|
+
removable?: boolean;
|
|
8
|
+
onRemove?: () => void;
|
|
9
|
+
class?: string;
|
|
10
|
+
children?: import('svelte').Snippet;
|
|
11
|
+
[key: string]: any;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
let {
|
|
15
|
+
variant = 'status',
|
|
16
|
+
size = 'md',
|
|
17
|
+
label = '',
|
|
18
|
+
color,
|
|
19
|
+
removable = false,
|
|
20
|
+
onRemove,
|
|
21
|
+
class: className = '',
|
|
22
|
+
children,
|
|
23
|
+
...rest
|
|
24
|
+
}: Props = $props();
|
|
25
|
+
|
|
26
|
+
let classes = $derived(
|
|
27
|
+
[
|
|
28
|
+
'uds-badge',
|
|
29
|
+
`uds-badge--${variant}`,
|
|
30
|
+
`uds-badge--${size}`,
|
|
31
|
+
color && `uds-badge--${color}`,
|
|
32
|
+
removable && 'uds-badge--removable',
|
|
33
|
+
className,
|
|
34
|
+
]
|
|
35
|
+
.filter(Boolean)
|
|
36
|
+
.join(' ')
|
|
37
|
+
);
|
|
38
|
+
</script>
|
|
39
|
+
|
|
40
|
+
<span class={classes} aria-label={label || undefined} {...rest}>
|
|
41
|
+
{#if label}
|
|
42
|
+
{label}
|
|
43
|
+
{/if}
|
|
44
|
+
{@render children?.()}
|
|
45
|
+
{#if removable}
|
|
46
|
+
<button class="uds-badge__remove" onclick={onRemove} aria-label="Remove {label}">
|
|
47
|
+
<span aria-hidden="true">×</span>
|
|
48
|
+
</button>
|
|
49
|
+
{/if}
|
|
50
|
+
</span>
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
interface BreadcrumbItem {
|
|
3
|
+
label: string;
|
|
4
|
+
href?: string;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
interface Props {
|
|
8
|
+
variant?: 'standard' | 'truncated';
|
|
9
|
+
items?: BreadcrumbItem[];
|
|
10
|
+
separator?: string;
|
|
11
|
+
maxItems?: number;
|
|
12
|
+
class?: string;
|
|
13
|
+
[key: string]: any;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
let {
|
|
17
|
+
variant = 'standard',
|
|
18
|
+
items = [],
|
|
19
|
+
separator = '/',
|
|
20
|
+
maxItems,
|
|
21
|
+
class: className = '',
|
|
22
|
+
...rest
|
|
23
|
+
}: Props = $props();
|
|
24
|
+
|
|
25
|
+
let classes = $derived(
|
|
26
|
+
[
|
|
27
|
+
'uds-breadcrumb',
|
|
28
|
+
`uds-breadcrumb--${variant}`,
|
|
29
|
+
className,
|
|
30
|
+
]
|
|
31
|
+
.filter(Boolean)
|
|
32
|
+
.join(' ')
|
|
33
|
+
);
|
|
34
|
+
|
|
35
|
+
let displayItems = $derived(() => {
|
|
36
|
+
if (variant === 'truncated' && maxItems && items.length > maxItems) {
|
|
37
|
+
const first = items.slice(0, 1);
|
|
38
|
+
const last = items.slice(-(maxItems - 1));
|
|
39
|
+
return [...first, { label: '...', href: undefined }, ...last];
|
|
40
|
+
}
|
|
41
|
+
return items;
|
|
42
|
+
});
|
|
43
|
+
</script>
|
|
44
|
+
|
|
45
|
+
<nav class={classes} aria-label="Breadcrumb" {...rest}>
|
|
46
|
+
<ol class="uds-breadcrumb__list">
|
|
47
|
+
{#each displayItems() as item, i}
|
|
48
|
+
<li class="uds-breadcrumb__item">
|
|
49
|
+
{#if i > 0}
|
|
50
|
+
<span class="uds-breadcrumb__separator" aria-hidden="true">{separator}</span>
|
|
51
|
+
{/if}
|
|
52
|
+
{#if item.href && i < displayItems().length - 1}
|
|
53
|
+
<a class="uds-breadcrumb__link" href={item.href}>{item.label}</a>
|
|
54
|
+
{:else}
|
|
55
|
+
<span class="uds-breadcrumb__current" aria-current={i === displayItems().length - 1 ? 'page' : undefined}>{item.label}</span>
|
|
56
|
+
{/if}
|
|
57
|
+
</li>
|
|
58
|
+
{/each}
|
|
59
|
+
</ol>
|
|
60
|
+
</nav>
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
interface Props {
|
|
3
|
+
variant?: 'primary' | 'secondary' | 'ghost' | 'gradient' | 'destructive' | 'icon-only';
|
|
4
|
+
size?: 'sm' | 'md' | 'lg' | 'xl';
|
|
5
|
+
loading?: boolean;
|
|
6
|
+
fullWidth?: boolean;
|
|
7
|
+
disabled?: boolean;
|
|
8
|
+
class?: string;
|
|
9
|
+
iconLeft?: import('svelte').Snippet;
|
|
10
|
+
iconRight?: import('svelte').Snippet;
|
|
11
|
+
children?: import('svelte').Snippet;
|
|
12
|
+
[key: string]: any;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
let {
|
|
16
|
+
variant = 'primary',
|
|
17
|
+
size = 'md',
|
|
18
|
+
loading = false,
|
|
19
|
+
fullWidth = false,
|
|
20
|
+
disabled = false,
|
|
21
|
+
class: className = '',
|
|
22
|
+
iconLeft,
|
|
23
|
+
iconRight,
|
|
24
|
+
children,
|
|
25
|
+
...rest
|
|
26
|
+
}: Props = $props();
|
|
27
|
+
|
|
28
|
+
let classes = $derived(
|
|
29
|
+
[
|
|
30
|
+
'uds-btn',
|
|
31
|
+
`uds-btn--${variant}`,
|
|
32
|
+
`uds-btn--${size}`,
|
|
33
|
+
fullWidth && 'uds-btn--full-width',
|
|
34
|
+
loading && 'uds-btn--loading',
|
|
35
|
+
className,
|
|
36
|
+
]
|
|
37
|
+
.filter(Boolean)
|
|
38
|
+
.join(' ')
|
|
39
|
+
);
|
|
40
|
+
</script>
|
|
41
|
+
|
|
42
|
+
<button class={classes} disabled={disabled || loading} aria-busy={loading || undefined} aria-disabled={disabled || undefined} {...rest}>
|
|
43
|
+
{#if loading}
|
|
44
|
+
<span class="uds-btn__spinner" aria-hidden="true"></span>
|
|
45
|
+
{/if}
|
|
46
|
+
{#if iconLeft}
|
|
47
|
+
<span class="uds-btn__icon-left">{@render iconLeft()}</span>
|
|
48
|
+
{/if}
|
|
49
|
+
{@render children?.()}
|
|
50
|
+
{#if iconRight}
|
|
51
|
+
<span class="uds-btn__icon-right">{@render iconRight()}</span>
|
|
52
|
+
{/if}
|
|
53
|
+
</button>
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
interface Props {
|
|
3
|
+
checked?: boolean;
|
|
4
|
+
indeterminate?: boolean;
|
|
5
|
+
disabled?: boolean;
|
|
6
|
+
label?: string;
|
|
7
|
+
name?: string;
|
|
8
|
+
value?: string;
|
|
9
|
+
id?: string;
|
|
10
|
+
class?: string;
|
|
11
|
+
[key: string]: any;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
let {
|
|
15
|
+
checked = $bindable(false),
|
|
16
|
+
indeterminate = false,
|
|
17
|
+
disabled = false,
|
|
18
|
+
label = '',
|
|
19
|
+
name,
|
|
20
|
+
value,
|
|
21
|
+
id,
|
|
22
|
+
class: className = '',
|
|
23
|
+
...rest
|
|
24
|
+
}: Props = $props();
|
|
25
|
+
|
|
26
|
+
let checkboxId = $derived(id || `uds-checkbox-${Math.random().toString(36).slice(2, 9)}`);
|
|
27
|
+
|
|
28
|
+
let classes = $derived(
|
|
29
|
+
[
|
|
30
|
+
'uds-checkbox',
|
|
31
|
+
indeterminate && 'uds-checkbox--indeterminate',
|
|
32
|
+
disabled && 'uds-checkbox--disabled',
|
|
33
|
+
className,
|
|
34
|
+
]
|
|
35
|
+
.filter(Boolean)
|
|
36
|
+
.join(' ')
|
|
37
|
+
);
|
|
38
|
+
|
|
39
|
+
function bindIndeterminate(node: HTMLInputElement) {
|
|
40
|
+
$effect(() => {
|
|
41
|
+
node.indeterminate = indeterminate;
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
</script>
|
|
45
|
+
|
|
46
|
+
<div class={classes}>
|
|
47
|
+
<input
|
|
48
|
+
class="uds-checkbox__input"
|
|
49
|
+
type="checkbox"
|
|
50
|
+
id={checkboxId}
|
|
51
|
+
bind:checked
|
|
52
|
+
{disabled}
|
|
53
|
+
{name}
|
|
54
|
+
{value}
|
|
55
|
+
aria-checked={indeterminate ? 'mixed' : checked}
|
|
56
|
+
use:bindIndeterminate
|
|
57
|
+
{...rest}
|
|
58
|
+
/>
|
|
59
|
+
{#if label}
|
|
60
|
+
<label class="uds-checkbox__label" for={checkboxId}>{label}</label>
|
|
61
|
+
{/if}
|
|
62
|
+
</div>
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
interface Tab {
|
|
3
|
+
label: string;
|
|
4
|
+
language: string;
|
|
5
|
+
code: string;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
interface Props {
|
|
9
|
+
variant?: 'syntax-highlighted' | 'terminal' | 'multi-tab';
|
|
10
|
+
size?: 'sm' | 'md' | 'lg';
|
|
11
|
+
language?: string;
|
|
12
|
+
code?: string;
|
|
13
|
+
showLineNumbers?: boolean;
|
|
14
|
+
showCopy?: boolean;
|
|
15
|
+
tabs?: Tab[];
|
|
16
|
+
class?: string;
|
|
17
|
+
[key: string]: any;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
let {
|
|
21
|
+
variant = 'syntax-highlighted',
|
|
22
|
+
size = 'md',
|
|
23
|
+
language = '',
|
|
24
|
+
code = '',
|
|
25
|
+
showLineNumbers = false,
|
|
26
|
+
showCopy = true,
|
|
27
|
+
tabs = [],
|
|
28
|
+
class: className = '',
|
|
29
|
+
...rest
|
|
30
|
+
}: Props = $props();
|
|
31
|
+
|
|
32
|
+
let copied = $state(false);
|
|
33
|
+
let activeTab = $state(0);
|
|
34
|
+
|
|
35
|
+
let classes = $derived(
|
|
36
|
+
[
|
|
37
|
+
'uds-code-block',
|
|
38
|
+
`uds-code-block--${variant}`,
|
|
39
|
+
`uds-code-block--${size}`,
|
|
40
|
+
showLineNumbers && 'uds-code-block--line-numbers',
|
|
41
|
+
className,
|
|
42
|
+
]
|
|
43
|
+
.filter(Boolean)
|
|
44
|
+
.join(' ')
|
|
45
|
+
);
|
|
46
|
+
|
|
47
|
+
let displayCode = $derived(
|
|
48
|
+
variant === 'multi-tab' && tabs.length > 0 ? tabs[activeTab].code : code
|
|
49
|
+
);
|
|
50
|
+
|
|
51
|
+
let displayLanguage = $derived(
|
|
52
|
+
variant === 'multi-tab' && tabs.length > 0 ? tabs[activeTab].language : language
|
|
53
|
+
);
|
|
54
|
+
|
|
55
|
+
async function copyToClipboard() {
|
|
56
|
+
try {
|
|
57
|
+
await navigator.clipboard.writeText(displayCode);
|
|
58
|
+
copied = true;
|
|
59
|
+
setTimeout(() => (copied = false), 2000);
|
|
60
|
+
} catch {
|
|
61
|
+
// Clipboard API not available
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
</script>
|
|
65
|
+
|
|
66
|
+
<div class={classes} {...rest}>
|
|
67
|
+
{#if variant === 'multi-tab' && tabs.length > 0}
|
|
68
|
+
<div class="uds-code-block__tabs" role="tablist">
|
|
69
|
+
{#each tabs as tab, i}
|
|
70
|
+
<button
|
|
71
|
+
class="uds-code-block__tab"
|
|
72
|
+
class:uds-code-block__tab--active={i === activeTab}
|
|
73
|
+
role="tab"
|
|
74
|
+
aria-selected={i === activeTab}
|
|
75
|
+
onclick={() => (activeTab = i)}
|
|
76
|
+
>
|
|
77
|
+
{tab.label}
|
|
78
|
+
</button>
|
|
79
|
+
{/each}
|
|
80
|
+
</div>
|
|
81
|
+
{/if}
|
|
82
|
+
<div class="uds-code-block__container">
|
|
83
|
+
{#if showCopy}
|
|
84
|
+
<button class="uds-code-block__copy" onclick={copyToClipboard} aria-label="Copy code">
|
|
85
|
+
{copied ? 'Copied' : 'Copy'}
|
|
86
|
+
</button>
|
|
87
|
+
{/if}
|
|
88
|
+
<pre class="uds-code-block__pre"><code class="uds-code-block__code language-{displayLanguage}">{displayCode}</code></pre>
|
|
89
|
+
</div>
|
|
90
|
+
</div>
|