@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.
Files changed (36) hide show
  1. package/README.md +100 -0
  2. package/package.json +27 -0
  3. package/src/components/Accordion.svelte +99 -0
  4. package/src/components/Alert.svelte +67 -0
  5. package/src/components/Avatar.svelte +62 -0
  6. package/src/components/Badge.svelte +50 -0
  7. package/src/components/Breadcrumb.svelte +60 -0
  8. package/src/components/Button.svelte +53 -0
  9. package/src/components/Checkbox.svelte +62 -0
  10. package/src/components/CodeBlock.svelte +90 -0
  11. package/src/components/CommandPalette.svelte +133 -0
  12. package/src/components/DataTable.svelte +140 -0
  13. package/src/components/DatePicker.svelte +73 -0
  14. package/src/components/DropdownMenu.svelte +113 -0
  15. package/src/components/FeatureCard.svelte +63 -0
  16. package/src/components/FileUpload.svelte +120 -0
  17. package/src/components/Footer.svelte +72 -0
  18. package/src/components/FormInput.svelte +102 -0
  19. package/src/components/HeroSection.svelte +65 -0
  20. package/src/components/Modal.svelte +67 -0
  21. package/src/components/NavigationBar.svelte +56 -0
  22. package/src/components/Pagination.svelte +115 -0
  23. package/src/components/PricingTable.svelte +89 -0
  24. package/src/components/ProgressIndicator.svelte +86 -0
  25. package/src/components/Radio.svelte +64 -0
  26. package/src/components/Select.svelte +85 -0
  27. package/src/components/SideNavigation.svelte +130 -0
  28. package/src/components/Skeleton.svelte +53 -0
  29. package/src/components/SocialProofBar.svelte +90 -0
  30. package/src/components/Tabs.svelte +100 -0
  31. package/src/components/TestimonialCard.svelte +71 -0
  32. package/src/components/Toast.svelte +83 -0
  33. package/src/components/ToggleSwitch.svelte +61 -0
  34. package/src/components/Tooltip.svelte +67 -0
  35. package/src/index.d.ts +553 -0
  36. package/src/index.js +32 -0
@@ -0,0 +1,133 @@
1
+ <script lang="ts">
2
+ interface CommandAction {
3
+ id: string;
4
+ label: string;
5
+ group?: string;
6
+ shortcut?: string;
7
+ icon?: string;
8
+ }
9
+
10
+ interface Props {
11
+ open?: boolean;
12
+ actions?: CommandAction[];
13
+ groups?: string[];
14
+ placeholder?: string;
15
+ recentLimit?: number;
16
+ onSelect?: (actionId: string) => void;
17
+ onClose?: () => void;
18
+ class?: string;
19
+ [key: string]: any;
20
+ }
21
+
22
+ let {
23
+ open = false,
24
+ actions = [],
25
+ groups = [],
26
+ placeholder = 'Type a command...',
27
+ recentLimit = 5,
28
+ onSelect,
29
+ onClose,
30
+ class: className = '',
31
+ ...rest
32
+ }: Props = $props();
33
+
34
+ let query = $state('');
35
+ let activeIndex = $state(0);
36
+
37
+ let classes = $derived(
38
+ [
39
+ 'uds-command-palette',
40
+ open && 'uds-command-palette--open',
41
+ className,
42
+ ]
43
+ .filter(Boolean)
44
+ .join(' ')
45
+ );
46
+
47
+ let filteredActions = $derived(
48
+ query
49
+ ? actions.filter((a) => a.label.toLowerCase().includes(query.toLowerCase()))
50
+ : actions.slice(0, recentLimit)
51
+ );
52
+
53
+ let groupedActions = $derived(() => {
54
+ if (groups.length === 0) return [{ group: '', actions: filteredActions }];
55
+ const grouped: { group: string; actions: CommandAction[] }[] = [];
56
+ for (const group of groups) {
57
+ const items = filteredActions.filter((a) => a.group === group);
58
+ if (items.length > 0) grouped.push({ group, actions: items });
59
+ }
60
+ const ungrouped = filteredActions.filter((a) => !a.group || !groups.includes(a.group));
61
+ if (ungrouped.length > 0) grouped.push({ group: '', actions: ungrouped });
62
+ return grouped;
63
+ });
64
+
65
+ function handleSelect(action: CommandAction) {
66
+ onSelect?.(action.id);
67
+ query = '';
68
+ }
69
+
70
+ function handleKeydown(event: KeyboardEvent) {
71
+ if (event.key === 'ArrowDown') {
72
+ event.preventDefault();
73
+ activeIndex = Math.min(activeIndex + 1, filteredActions.length - 1);
74
+ } else if (event.key === 'ArrowUp') {
75
+ event.preventDefault();
76
+ activeIndex = Math.max(activeIndex - 1, 0);
77
+ } else if (event.key === 'Enter' && filteredActions[activeIndex]) {
78
+ event.preventDefault();
79
+ handleSelect(filteredActions[activeIndex]);
80
+ } else if (event.key === 'Escape') {
81
+ event.preventDefault();
82
+ onClose?.();
83
+ }
84
+ }
85
+
86
+ $effect(() => {
87
+ activeIndex = 0;
88
+ });
89
+ </script>
90
+
91
+ {#if open}
92
+ <div class="uds-command-palette__overlay" onclick={onClose} aria-hidden="true"></div>
93
+ <div class={classes} role="dialog" aria-label="Command palette" {...rest}>
94
+ <div class="uds-command-palette__input-wrapper">
95
+ <input
96
+ class="uds-command-palette__input"
97
+ type="text"
98
+ bind:value={query}
99
+ {placeholder}
100
+ role="combobox"
101
+ aria-expanded="true"
102
+ aria-controls="command-palette-list"
103
+ aria-activedescendant={filteredActions[activeIndex] ? `command-${filteredActions[activeIndex].id}` : undefined}
104
+ onkeydown={handleKeydown}
105
+ />
106
+ </div>
107
+ <ul class="uds-command-palette__list" id="command-palette-list" role="listbox">
108
+ {#each groupedActions() as group}
109
+ {#if group.group}
110
+ <li class="uds-command-palette__group-label" role="presentation">{group.group}</li>
111
+ {/if}
112
+ {#each group.actions as action, i}
113
+ <li
114
+ class="uds-command-palette__item"
115
+ class:uds-command-palette__item--active={filteredActions.indexOf(action) === activeIndex}
116
+ id="command-{action.id}"
117
+ role="option"
118
+ aria-selected={filteredActions.indexOf(action) === activeIndex}
119
+ onclick={() => handleSelect(action)}
120
+ >
121
+ <span class="uds-command-palette__item-label">{action.label}</span>
122
+ {#if action.shortcut}
123
+ <kbd class="uds-command-palette__shortcut">{action.shortcut}</kbd>
124
+ {/if}
125
+ </li>
126
+ {/each}
127
+ {/each}
128
+ {#if filteredActions.length === 0}
129
+ <li class="uds-command-palette__empty" role="presentation">No results found</li>
130
+ {/if}
131
+ </ul>
132
+ </div>
133
+ {/if}
@@ -0,0 +1,140 @@
1
+ <script lang="ts">
2
+ interface Column {
3
+ key: string;
4
+ label: string;
5
+ sortable?: boolean;
6
+ width?: string;
7
+ }
8
+
9
+ interface Props {
10
+ variant?: 'basic' | 'sortable' | 'selectable' | 'expandable';
11
+ density?: 'compact' | 'default' | 'comfortable';
12
+ columns?: Column[];
13
+ data?: Record<string, any>[];
14
+ sortable?: boolean;
15
+ selectable?: boolean;
16
+ sortKey?: string;
17
+ sortDirection?: 'asc' | 'desc';
18
+ selectedRows?: Set<number>;
19
+ onSort?: (key: string, direction: 'asc' | 'desc') => void;
20
+ onSelect?: (selected: Set<number>) => void;
21
+ class?: string;
22
+ children?: import('svelte').Snippet;
23
+ [key: string]: any;
24
+ }
25
+
26
+ let {
27
+ variant = 'basic',
28
+ density = 'default',
29
+ columns = [],
30
+ data = [],
31
+ sortable = false,
32
+ selectable = false,
33
+ sortKey = '',
34
+ sortDirection = 'asc',
35
+ selectedRows = new Set<number>(),
36
+ onSort,
37
+ onSelect,
38
+ class: className = '',
39
+ children,
40
+ ...rest
41
+ }: Props = $props();
42
+
43
+ let classes = $derived(
44
+ [
45
+ 'uds-data-table',
46
+ `uds-data-table--${variant}`,
47
+ `uds-data-table--${density}`,
48
+ className,
49
+ ]
50
+ .filter(Boolean)
51
+ .join(' ')
52
+ );
53
+
54
+ function handleSort(key: string) {
55
+ const direction = sortKey === key && sortDirection === 'asc' ? 'desc' : 'asc';
56
+ onSort?.(key, direction);
57
+ }
58
+
59
+ function handleSelectAll() {
60
+ if (selectedRows.size === data.length) {
61
+ onSelect?.(new Set());
62
+ } else {
63
+ onSelect?.(new Set(data.map((_, i) => i)));
64
+ }
65
+ }
66
+
67
+ function handleSelectRow(index: number) {
68
+ const next = new Set(selectedRows);
69
+ if (next.has(index)) {
70
+ next.delete(index);
71
+ } else {
72
+ next.add(index);
73
+ }
74
+ onSelect?.(next);
75
+ }
76
+ </script>
77
+
78
+ <div class={classes} {...rest}>
79
+ <table class="uds-data-table__table" role="table">
80
+ <thead class="uds-data-table__head">
81
+ <tr>
82
+ {#if selectable}
83
+ <th class="uds-data-table__th uds-data-table__th--select" scope="col">
84
+ <input
85
+ type="checkbox"
86
+ checked={selectedRows.size === data.length && data.length > 0}
87
+ onchange={handleSelectAll}
88
+ aria-label="Select all rows"
89
+ />
90
+ </th>
91
+ {/if}
92
+ {#each columns as column}
93
+ <th
94
+ class="uds-data-table__th"
95
+ scope="col"
96
+ aria-sort={sortKey === column.key ? (sortDirection === 'asc' ? 'ascending' : 'descending') : undefined}
97
+ style:width={column.width}
98
+ >
99
+ {#if (sortable || column.sortable) && variant !== 'basic'}
100
+ <button class="uds-data-table__sort" onclick={() => handleSort(column.key)}>
101
+ {column.label}
102
+ <span class="uds-data-table__sort-icon" aria-hidden="true"></span>
103
+ </button>
104
+ {:else}
105
+ {column.label}
106
+ {/if}
107
+ </th>
108
+ {/each}
109
+ </tr>
110
+ </thead>
111
+ <tbody class="uds-data-table__body">
112
+ {#if data.length === 0}
113
+ <tr>
114
+ <td class="uds-data-table__empty" colspan={columns.length + (selectable ? 1 : 0)}>
115
+ No data available
116
+ </td>
117
+ </tr>
118
+ {:else}
119
+ {#each data as row, i}
120
+ <tr class="uds-data-table__row" class:uds-data-table__row--selected={selectedRows.has(i)}>
121
+ {#if selectable}
122
+ <td class="uds-data-table__td uds-data-table__td--select">
123
+ <input
124
+ type="checkbox"
125
+ checked={selectedRows.has(i)}
126
+ onchange={() => handleSelectRow(i)}
127
+ aria-label="Select row {i + 1}"
128
+ />
129
+ </td>
130
+ {/if}
131
+ {#each columns as column}
132
+ <td class="uds-data-table__td">{row[column.key] ?? ''}</td>
133
+ {/each}
134
+ </tr>
135
+ {/each}
136
+ {/if}
137
+ </tbody>
138
+ </table>
139
+ {@render children?.()}
140
+ </div>
@@ -0,0 +1,73 @@
1
+ <script lang="ts">
2
+ interface Props {
3
+ variant?: 'single' | 'range' | 'with-time';
4
+ size?: 'md' | 'lg';
5
+ value?: string;
6
+ min?: string;
7
+ max?: string;
8
+ disabled?: boolean;
9
+ locale?: string;
10
+ placeholder?: string;
11
+ label?: string;
12
+ id?: string;
13
+ onChange?: (value: string) => void;
14
+ class?: string;
15
+ [key: string]: any;
16
+ }
17
+
18
+ let {
19
+ variant = 'single',
20
+ size = 'md',
21
+ value = $bindable(''),
22
+ min,
23
+ max,
24
+ disabled = false,
25
+ locale = 'en-US',
26
+ placeholder = 'Select date',
27
+ label = '',
28
+ id,
29
+ onChange,
30
+ class: className = '',
31
+ ...rest
32
+ }: Props = $props();
33
+
34
+ let pickerId = $derived(id || `uds-date-picker-${Math.random().toString(36).slice(2, 9)}`);
35
+
36
+ let classes = $derived(
37
+ [
38
+ 'uds-date-picker',
39
+ `uds-date-picker--${variant}`,
40
+ `uds-date-picker--${size}`,
41
+ disabled && 'uds-date-picker--disabled',
42
+ className,
43
+ ]
44
+ .filter(Boolean)
45
+ .join(' ')
46
+ );
47
+
48
+ let inputType = $derived(variant === 'with-time' ? 'datetime-local' : 'date');
49
+
50
+ function handleChange(event: Event) {
51
+ const target = event.target as HTMLInputElement;
52
+ value = target.value;
53
+ onChange?.(value);
54
+ }
55
+ </script>
56
+
57
+ <div class={classes} {...rest}>
58
+ {#if label}
59
+ <label class="uds-date-picker__label" for={pickerId}>{label}</label>
60
+ {/if}
61
+ <input
62
+ class="uds-date-picker__input"
63
+ type={inputType}
64
+ id={pickerId}
65
+ {value}
66
+ {min}
67
+ {max}
68
+ {disabled}
69
+ {placeholder}
70
+ aria-label={label || placeholder}
71
+ onchange={handleChange}
72
+ />
73
+ </div>
@@ -0,0 +1,113 @@
1
+ <script lang="ts">
2
+ interface DropdownItem {
3
+ id: string;
4
+ label: string;
5
+ icon?: string;
6
+ disabled?: boolean;
7
+ separator?: boolean;
8
+ }
9
+
10
+ interface Props {
11
+ variant?: 'action' | 'context' | 'nav-sub';
12
+ size?: 'sm' | 'md' | 'lg';
13
+ items?: DropdownItem[];
14
+ position?: 'bottom-start' | 'bottom-end' | 'top-start' | 'top-end';
15
+ onSelect?: (itemId: string) => void;
16
+ class?: string;
17
+ trigger?: import('svelte').Snippet;
18
+ [key: string]: any;
19
+ }
20
+
21
+ let {
22
+ variant = 'action',
23
+ size = 'md',
24
+ items = [],
25
+ position = 'bottom-start',
26
+ onSelect,
27
+ class: className = '',
28
+ trigger,
29
+ ...rest
30
+ }: Props = $props();
31
+
32
+ let open = $state(false);
33
+ let activeIndex = $state(-1);
34
+
35
+ let classes = $derived(
36
+ [
37
+ 'uds-dropdown',
38
+ `uds-dropdown--${variant}`,
39
+ `uds-dropdown--${size}`,
40
+ `uds-dropdown--${position}`,
41
+ open && 'uds-dropdown--open',
42
+ className,
43
+ ]
44
+ .filter(Boolean)
45
+ .join(' ')
46
+ );
47
+
48
+ function toggleMenu() {
49
+ open = !open;
50
+ if (open) activeIndex = -1;
51
+ }
52
+
53
+ function selectItem(item: DropdownItem) {
54
+ if (!item.disabled && !item.separator) {
55
+ onSelect?.(item.id);
56
+ open = false;
57
+ }
58
+ }
59
+
60
+ function handleKeydown(event: KeyboardEvent) {
61
+ if (!open) return;
62
+ const enabledItems = items.filter((item) => !item.disabled && !item.separator);
63
+
64
+ if (event.key === 'ArrowDown') {
65
+ event.preventDefault();
66
+ activeIndex = (activeIndex + 1) % enabledItems.length;
67
+ } else if (event.key === 'ArrowUp') {
68
+ event.preventDefault();
69
+ activeIndex = (activeIndex - 1 + enabledItems.length) % enabledItems.length;
70
+ } else if (event.key === 'Enter' && activeIndex >= 0) {
71
+ event.preventDefault();
72
+ selectItem(enabledItems[activeIndex]);
73
+ } else if (event.key === 'Escape') {
74
+ event.preventDefault();
75
+ open = false;
76
+ }
77
+ }
78
+ </script>
79
+
80
+ <div class={classes} onkeydown={handleKeydown} {...rest}>
81
+ <button
82
+ class="uds-dropdown__trigger"
83
+ aria-haspopup="true"
84
+ aria-expanded={open}
85
+ onclick={toggleMenu}
86
+ >
87
+ {#if trigger}
88
+ {@render trigger()}
89
+ {:else}
90
+ Menu
91
+ {/if}
92
+ </button>
93
+ {#if open}
94
+ <ul class="uds-dropdown__menu" role="menu">
95
+ {#each items as item, i}
96
+ {#if item.separator}
97
+ <li class="uds-dropdown__separator" role="separator"></li>
98
+ {:else}
99
+ <li
100
+ class="uds-dropdown__item"
101
+ class:uds-dropdown__item--disabled={item.disabled}
102
+ class:uds-dropdown__item--active={i === activeIndex}
103
+ role="menuitem"
104
+ aria-disabled={item.disabled || undefined}
105
+ onclick={() => selectItem(item)}
106
+ >
107
+ {item.label}
108
+ </li>
109
+ {/if}
110
+ {/each}
111
+ </ul>
112
+ {/if}
113
+ </div>
@@ -0,0 +1,63 @@
1
+ <script lang="ts">
2
+ interface Props {
3
+ variant?: 'icon-top' | 'image-top' | 'horizontal' | 'stat-card' | 'dashboard-preview';
4
+ size?: 'sm' | 'md' | 'lg';
5
+ title?: string;
6
+ description?: string;
7
+ link?: string;
8
+ image?: string;
9
+ imageAlt?: string;
10
+ class?: string;
11
+ icon?: import('svelte').Snippet;
12
+ children?: import('svelte').Snippet;
13
+ [key: string]: any;
14
+ }
15
+
16
+ let {
17
+ variant = 'icon-top',
18
+ size = 'md',
19
+ title = '',
20
+ description = '',
21
+ link,
22
+ image,
23
+ imageAlt = '',
24
+ class: className = '',
25
+ icon,
26
+ children,
27
+ ...rest
28
+ }: Props = $props();
29
+
30
+ let classes = $derived(
31
+ [
32
+ 'uds-card',
33
+ `uds-card--${variant}`,
34
+ `uds-card--${size}`,
35
+ className,
36
+ ]
37
+ .filter(Boolean)
38
+ .join(' ')
39
+ );
40
+ </script>
41
+
42
+ <div class={classes} {...rest}>
43
+ {#if icon}
44
+ <div class="uds-card__icon">
45
+ {@render icon()}
46
+ </div>
47
+ {/if}
48
+ {#if image}
49
+ <img class="uds-card__image" src={image} alt={imageAlt} />
50
+ {/if}
51
+ <div class="uds-card__body">
52
+ {#if title}
53
+ <h3 class="uds-card__title">{title}</h3>
54
+ {/if}
55
+ {#if description}
56
+ <p class="uds-card__description">{description}</p>
57
+ {/if}
58
+ {@render children?.()}
59
+ </div>
60
+ {#if link}
61
+ <a class="uds-card__link" href={link}>Learn more</a>
62
+ {/if}
63
+ </div>
@@ -0,0 +1,120 @@
1
+ <script lang="ts">
2
+ interface Props {
3
+ variant?: 'dropzone' | 'button' | 'avatar-upload';
4
+ size?: 'sm' | 'md' | 'lg';
5
+ accept?: string;
6
+ maxSize?: number;
7
+ maxFiles?: number;
8
+ multiple?: boolean;
9
+ disabled?: boolean;
10
+ state?: 'idle' | 'drag-over' | 'uploading' | 'success' | 'error';
11
+ label?: string;
12
+ onUpload?: (files: FileList) => void;
13
+ onRemove?: (index: number) => void;
14
+ class?: string;
15
+ children?: import('svelte').Snippet;
16
+ [key: string]: any;
17
+ }
18
+
19
+ let {
20
+ variant = 'dropzone',
21
+ size = 'md',
22
+ accept,
23
+ maxSize,
24
+ maxFiles = 1,
25
+ multiple = false,
26
+ disabled = false,
27
+ state = 'idle',
28
+ label = 'Upload file',
29
+ onUpload,
30
+ onRemove,
31
+ class: className = '',
32
+ children,
33
+ ...rest
34
+ }: Props = $props();
35
+
36
+ let dragOver = $state(false);
37
+ let inputId = $derived(`uds-file-upload-${Math.random().toString(36).slice(2, 9)}`);
38
+
39
+ let currentState = $derived(dragOver ? 'drag-over' : state);
40
+
41
+ let classes = $derived(
42
+ [
43
+ 'uds-file-upload',
44
+ `uds-file-upload--${variant}`,
45
+ `uds-file-upload--${size}`,
46
+ `uds-file-upload--${currentState}`,
47
+ disabled && 'uds-file-upload--disabled',
48
+ className,
49
+ ]
50
+ .filter(Boolean)
51
+ .join(' ')
52
+ );
53
+
54
+ function handleFileChange(event: Event) {
55
+ const target = event.target as HTMLInputElement;
56
+ if (target.files && target.files.length > 0) {
57
+ onUpload?.(target.files);
58
+ }
59
+ }
60
+
61
+ function handleDrop(event: DragEvent) {
62
+ event.preventDefault();
63
+ dragOver = false;
64
+ if (event.dataTransfer?.files && event.dataTransfer.files.length > 0) {
65
+ onUpload?.(event.dataTransfer.files);
66
+ }
67
+ }
68
+
69
+ function handleDragOver(event: DragEvent) {
70
+ event.preventDefault();
71
+ dragOver = true;
72
+ }
73
+
74
+ function handleDragLeave() {
75
+ dragOver = false;
76
+ }
77
+ </script>
78
+
79
+ <div class={classes} {...rest}>
80
+ {#if variant === 'dropzone'}
81
+ <div
82
+ class="uds-file-upload__dropzone"
83
+ role="button"
84
+ tabindex="0"
85
+ aria-label={label}
86
+ ondrop={handleDrop}
87
+ ondragover={handleDragOver}
88
+ ondragleave={handleDragLeave}
89
+ onclick={() => document.getElementById(inputId)?.click()}
90
+ onkeydown={(e) => { if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); document.getElementById(inputId)?.click(); } }}
91
+ >
92
+ <p class="uds-file-upload__text">{label}</p>
93
+ <p class="uds-file-upload__hint">Drag and drop or click to browse</p>
94
+ {@render children?.()}
95
+ </div>
96
+ {:else}
97
+ <button
98
+ class="uds-file-upload__button"
99
+ type="button"
100
+ {disabled}
101
+ onclick={() => document.getElementById(inputId)?.click()}
102
+ >
103
+ {label}
104
+ </button>
105
+ {/if}
106
+ <input
107
+ class="uds-file-upload__input"
108
+ type="file"
109
+ id={inputId}
110
+ {accept}
111
+ {multiple}
112
+ {disabled}
113
+ aria-label={label}
114
+ onchange={handleFileChange}
115
+ style="display:none"
116
+ />
117
+ {#if currentState === 'uploading'}
118
+ <div class="uds-file-upload__progress" aria-live="polite">Uploading...</div>
119
+ {/if}
120
+ </div>