zabi-components 5.0.19 → 5.0.21
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/LICENSE +21 -0
- package/README.md +354 -81
- package/dist/atoms/Button.svelte +6 -3
- package/dist/atoms/Button.svelte.d.ts +1 -0
- package/dist/atoms/Card.svelte +23 -47
- package/dist/atoms/CardContent.svelte +12 -5
- package/dist/atoms/CardContent.svelte.d.ts +2 -0
- package/dist/atoms/CardHeader.svelte +22 -5
- package/dist/atoms/CardHeader.svelte.d.ts +3 -0
- package/dist/atoms/ColorPicker.svelte +271 -7
- package/dist/atoms/IconButton.svelte +88 -0
- package/dist/atoms/IconButton.svelte.d.ts +21 -0
- package/dist/atoms/Input.svelte +7 -6
- package/dist/atoms/Input.svelte.d.ts +3 -1
- package/dist/atoms/Progress.svelte +1 -3
- package/dist/atoms/Select.svelte +109 -24
- package/dist/atoms/Select.svelte.d.ts +10 -0
- package/dist/atoms/Skeleton.svelte +12 -2
- package/dist/atoms/Skeleton.svelte.d.ts +1 -0
- package/dist/atoms/Tooltip.svelte +15 -17
- package/dist/atoms/index.d.ts +1 -2
- package/dist/atoms/index.js +1 -2
- package/dist/components/atoms/index.d.ts +1 -2
- package/dist/components/atoms/index.d.ts.map +1 -1
- package/dist/components/molecules/index.d.ts +8 -0
- package/dist/components/molecules/index.d.ts.map +1 -1
- package/dist/molecules/Dropdown.svelte +101 -14
- package/dist/molecules/Dropdown.svelte.d.ts +7 -0
- package/dist/molecules/ImageUpload.svelte +4 -4
- package/dist/molecules/Modal.svelte +42 -26
- package/dist/molecules/Modal.svelte.d.ts +1 -0
- package/dist/molecules/NavigationMenu.svelte +158 -0
- package/dist/molecules/NavigationMenu.svelte.d.ts +25 -0
- package/dist/molecules/NavigationMenuContent.svelte +83 -0
- package/dist/molecules/NavigationMenuContent.svelte.d.ts +9 -0
- package/dist/molecules/NavigationMenuItem.svelte +36 -0
- package/dist/molecules/NavigationMenuItem.svelte.d.ts +9 -0
- package/dist/molecules/NavigationMenuLink.svelte +53 -0
- package/dist/molecules/NavigationMenuLink.svelte.d.ts +10 -0
- package/dist/molecules/NavigationMenuList.svelte +31 -0
- package/dist/molecules/NavigationMenuList.svelte.d.ts +8 -0
- package/dist/molecules/NavigationMenuTrigger.svelte +81 -0
- package/dist/molecules/NavigationMenuTrigger.svelte.d.ts +9 -0
- package/dist/molecules/Section.svelte +158 -0
- package/dist/molecules/Section.svelte.d.ts +27 -0
- package/dist/molecules/Sidebar.svelte +324 -0
- package/dist/molecules/Sidebar.svelte.d.ts +30 -0
- package/dist/molecules/SlideUp.svelte +2 -2
- package/dist/molecules/index.d.ts +8 -0
- package/dist/molecules/index.js +8 -0
- package/dist/organisms/Navigation.svelte +2 -2
- package/dist/zabi-components-colors.css +2 -2
- package/dist/zabi-components-theme-only.css +2 -2
- package/dist/zabi-components-theme.css +2 -2
- package/dist/zabi-components.css +299 -24
- package/package.json +3 -5
- package/dist/atoms/CardDescription.svelte +0 -24
- package/dist/atoms/CardDescription.svelte.d.ts +0 -8
- package/dist/atoms/CardTitle.svelte +0 -33
- package/dist/atoms/CardTitle.svelte.d.ts +0 -9
package/dist/atoms/Input.svelte
CHANGED
|
@@ -21,10 +21,12 @@
|
|
|
21
21
|
variant?: SemanticVariant;
|
|
22
22
|
message?: string;
|
|
23
23
|
oninput?: (event: Event) => void;
|
|
24
|
+
onblur?: (event: Event) => void;
|
|
25
|
+
"aria-label"?: string;
|
|
24
26
|
}
|
|
25
27
|
|
|
26
28
|
let {
|
|
27
|
-
value = "",
|
|
29
|
+
value = $bindable(""),
|
|
28
30
|
type = "text",
|
|
29
31
|
name = "",
|
|
30
32
|
label = "",
|
|
@@ -34,6 +36,7 @@
|
|
|
34
36
|
variant = "default",
|
|
35
37
|
message = "",
|
|
36
38
|
oninput,
|
|
39
|
+
onblur,
|
|
37
40
|
...restProps
|
|
38
41
|
}: Props = $props();
|
|
39
42
|
|
|
@@ -74,7 +77,7 @@
|
|
|
74
77
|
const inputClasses = $derived(() => {
|
|
75
78
|
const sizeStyles = sizeClass();
|
|
76
79
|
const baseClasses =
|
|
77
|
-
"w-full bg-input hover:bg-input-hover focus:bg-input-focus disabled:bg-input-disabled rounded-lg transition-all duration-200 placeholder:text-description text-body focus:outline-none focus:ring-offset-0 disabled:opacity-50 disabled:cursor-not-allowed";
|
|
80
|
+
"w-full min-w-48 bg-input hover:bg-input-hover focus:bg-input-focus disabled:bg-input-disabled rounded-lg transition-all duration-200 placeholder:text-description text-body focus:outline-none focus:ring-offset-0 disabled:opacity-50 disabled:cursor-not-allowed";
|
|
78
81
|
|
|
79
82
|
return `${baseClasses} ${sizeStyles.padding} ${sizeStyles.text} ${sizeStyles.leading} ${variantClass()}`.trim();
|
|
80
83
|
});
|
|
@@ -102,9 +105,6 @@
|
|
|
102
105
|
});
|
|
103
106
|
|
|
104
107
|
function handleInput(event: Event) {
|
|
105
|
-
const target = event.target as HTMLInputElement;
|
|
106
|
-
value = target.value;
|
|
107
|
-
|
|
108
108
|
if (oninput) {
|
|
109
109
|
oninput(event);
|
|
110
110
|
}
|
|
@@ -119,11 +119,12 @@
|
|
|
119
119
|
id={inputId}
|
|
120
120
|
{type}
|
|
121
121
|
{name}
|
|
122
|
-
|
|
122
|
+
bind:value
|
|
123
123
|
{placeholder}
|
|
124
124
|
{disabled}
|
|
125
125
|
class={inputClasses()}
|
|
126
126
|
oninput={handleInput}
|
|
127
|
+
{onblur}
|
|
127
128
|
aria-invalid={variant === "error" ? "true" : undefined}
|
|
128
129
|
aria-describedby={message ? `${inputId}-message` : undefined}
|
|
129
130
|
{...restProps}
|
|
@@ -10,7 +10,9 @@ interface Props {
|
|
|
10
10
|
variant?: SemanticVariant;
|
|
11
11
|
message?: string;
|
|
12
12
|
oninput?: (event: Event) => void;
|
|
13
|
+
onblur?: (event: Event) => void;
|
|
14
|
+
"aria-label"?: string;
|
|
13
15
|
}
|
|
14
|
-
declare const Input: import("svelte").Component<Props, {}, "">;
|
|
16
|
+
declare const Input: import("svelte").Component<Props, {}, "value">;
|
|
15
17
|
type Input = ReturnType<typeof Input>;
|
|
16
18
|
export default Input;
|
|
@@ -41,9 +41,7 @@
|
|
|
41
41
|
|
|
42
42
|
<div
|
|
43
43
|
id={progressId}
|
|
44
|
-
class="w-full bg-
|
|
45
|
-
size
|
|
46
|
-
]}"
|
|
44
|
+
class="w-full bg-input rounded-full overflow-hidden {sizeClasses[size]}"
|
|
47
45
|
role="progressbar"
|
|
48
46
|
aria-valuenow={value}
|
|
49
47
|
aria-valuemin="0"
|
package/dist/atoms/Select.svelte
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import Dropdown from "../molecules/Dropdown.svelte";
|
|
3
|
+
import Button from "./Button.svelte";
|
|
3
4
|
import {
|
|
4
5
|
ChevronDown,
|
|
5
6
|
CheckCircle,
|
|
@@ -22,6 +23,16 @@
|
|
|
22
23
|
label: string;
|
|
23
24
|
disabled?: boolean;
|
|
24
25
|
}>;
|
|
26
|
+
/** Enables the search input inside the dropdown. */
|
|
27
|
+
searchable?: boolean;
|
|
28
|
+
/** Placeholder for the search input. */
|
|
29
|
+
searchPlaceholder?: string;
|
|
30
|
+
/** Max height for the options list (supports any valid CSS length). */
|
|
31
|
+
maxMenuHeight?: string;
|
|
32
|
+
/** Width for the dropdown menu (supports any valid CSS length). */
|
|
33
|
+
menuWidth?: string;
|
|
34
|
+
/** Message shown when no options match the search. */
|
|
35
|
+
noResultsText?: string;
|
|
25
36
|
placeholder?: string;
|
|
26
37
|
label?: string;
|
|
27
38
|
disabled?: boolean;
|
|
@@ -34,6 +45,11 @@
|
|
|
34
45
|
let {
|
|
35
46
|
value = undefined,
|
|
36
47
|
options = [],
|
|
48
|
+
searchable = true,
|
|
49
|
+
searchPlaceholder = "Search options",
|
|
50
|
+
maxMenuHeight = "60vh",
|
|
51
|
+
menuWidth = "100%",
|
|
52
|
+
noResultsText = "No results found",
|
|
37
53
|
placeholder = "Select an option",
|
|
38
54
|
label = "",
|
|
39
55
|
disabled = false,
|
|
@@ -45,6 +61,8 @@
|
|
|
45
61
|
}: Props = $props();
|
|
46
62
|
|
|
47
63
|
let isOpen = $state(false);
|
|
64
|
+
let selectContainer: HTMLDivElement;
|
|
65
|
+
let searchQuery = $state("");
|
|
48
66
|
|
|
49
67
|
const sizeClass = $derived(() => {
|
|
50
68
|
if (size === "sm") {
|
|
@@ -92,13 +110,13 @@
|
|
|
92
110
|
|
|
93
111
|
const messageClasses = $derived(() => {
|
|
94
112
|
if (variant === "error") {
|
|
95
|
-
return "text-error text-sm mt-1 flex items-center gap-1.5";
|
|
113
|
+
return "text-error text-sm mt-1 flex items-center gap-1.5 w-full";
|
|
96
114
|
} else if (variant === "success") {
|
|
97
|
-
return "text-success text-sm mt-1 flex items-center gap-1.5";
|
|
115
|
+
return "text-success text-sm mt-1 flex items-center gap-1.5 w-full";
|
|
98
116
|
} else if (variant === "warning") {
|
|
99
|
-
return "text-warning text-sm mt-1 flex items-center gap-1.5";
|
|
117
|
+
return "text-warning text-sm mt-1 flex items-center gap-1.5 w-full";
|
|
100
118
|
}
|
|
101
|
-
return "text-description text-sm mt-1 flex items-center gap-1.5";
|
|
119
|
+
return "text-description text-sm mt-1 flex items-center gap-1.5 w-full";
|
|
102
120
|
});
|
|
103
121
|
|
|
104
122
|
const getIcon = $derived(() => {
|
|
@@ -124,6 +142,33 @@
|
|
|
124
142
|
return value === undefined || value === null || value === "";
|
|
125
143
|
});
|
|
126
144
|
|
|
145
|
+
const normalizedOptions = $derived(() => {
|
|
146
|
+
const fallbackOptions = (restProps as { options?: Props["options"] })
|
|
147
|
+
.options;
|
|
148
|
+
return Array.from(options ?? fallbackOptions ?? []);
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
const hasSearchQuery = $derived(() => {
|
|
152
|
+
return searchQuery.trim().length > 0;
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
const filteredOptions = $derived(() => {
|
|
156
|
+
const query = searchQuery.trim().toLowerCase();
|
|
157
|
+
const availableOptions = normalizedOptions();
|
|
158
|
+
if (!searchable || query.length === 0) {
|
|
159
|
+
return availableOptions;
|
|
160
|
+
}
|
|
161
|
+
return availableOptions.filter((option) =>
|
|
162
|
+
option.label.toLowerCase().includes(query),
|
|
163
|
+
);
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
$effect(() => {
|
|
167
|
+
if (!isOpen && searchQuery.length > 0) {
|
|
168
|
+
searchQuery = "";
|
|
169
|
+
}
|
|
170
|
+
});
|
|
171
|
+
|
|
127
172
|
function handleOptionClick(optionValue: string | number) {
|
|
128
173
|
if (disabled) return;
|
|
129
174
|
value = optionValue;
|
|
@@ -146,9 +191,15 @@
|
|
|
146
191
|
}
|
|
147
192
|
|
|
148
193
|
function handleClickOutside(event: MouseEvent) {
|
|
194
|
+
if (!isOpen) return;
|
|
195
|
+
|
|
196
|
+
const target = event.target as HTMLElement;
|
|
197
|
+
const clickedSelectContainer = target.closest(".select-container");
|
|
198
|
+
|
|
199
|
+
// Close if clicking outside any select container, or clicking on a different select
|
|
149
200
|
if (
|
|
150
|
-
|
|
151
|
-
|
|
201
|
+
!clickedSelectContainer ||
|
|
202
|
+
clickedSelectContainer !== selectContainer
|
|
152
203
|
) {
|
|
153
204
|
isOpen = false;
|
|
154
205
|
}
|
|
@@ -163,12 +214,18 @@
|
|
|
163
214
|
|
|
164
215
|
<svelte:window onclick={handleClickOutside} onkeydown={handleKeydown} />
|
|
165
216
|
|
|
166
|
-
<div class="w-full select-container">
|
|
217
|
+
<div bind:this={selectContainer} class="w-full select-container">
|
|
167
218
|
{#if label}
|
|
168
219
|
<label for={selectId} class={labelClasses()}>{label}</label>
|
|
169
220
|
{/if}
|
|
170
221
|
|
|
171
|
-
<Dropdown
|
|
222
|
+
<Dropdown
|
|
223
|
+
{isOpen}
|
|
224
|
+
placement="bottom-start"
|
|
225
|
+
selectedValue={value}
|
|
226
|
+
onOptionClick={handleOptionClick}
|
|
227
|
+
ariaLabel="Select options"
|
|
228
|
+
>
|
|
172
229
|
{#snippet trigger()}
|
|
173
230
|
<button
|
|
174
231
|
type="button"
|
|
@@ -181,7 +238,7 @@
|
|
|
181
238
|
aria-describedby={message ? `${selectId}-message` : undefined}
|
|
182
239
|
>
|
|
183
240
|
<span
|
|
184
|
-
class="text-left flex-1 {isEmpty()
|
|
241
|
+
class="text-left flex-1 truncate {isEmpty()
|
|
185
242
|
? 'text-description'
|
|
186
243
|
: 'text-body'}"
|
|
187
244
|
>
|
|
@@ -199,21 +256,49 @@
|
|
|
199
256
|
</button>
|
|
200
257
|
{/snippet}
|
|
201
258
|
{#snippet children()}
|
|
202
|
-
<div class="px-2
|
|
203
|
-
{#
|
|
204
|
-
<
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
>
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
259
|
+
<div class="px-2 pb-2 pt-1" style:width={menuWidth}>
|
|
260
|
+
{#if searchable}
|
|
261
|
+
<div class="px-1 pb-2">
|
|
262
|
+
<input
|
|
263
|
+
type="text"
|
|
264
|
+
class="w-full rounded-md bg-input hover:bg-input-hover focus:bg-input-focus text-body placeholder:text-description px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-brand-500 disabled:opacity-50 disabled:cursor-not-allowed"
|
|
265
|
+
placeholder={searchPlaceholder}
|
|
266
|
+
bind:value={searchQuery}
|
|
267
|
+
aria-label={searchPlaceholder}
|
|
268
|
+
{disabled}
|
|
269
|
+
/>
|
|
270
|
+
</div>
|
|
271
|
+
{/if}
|
|
272
|
+
<div
|
|
273
|
+
class="overflow-y-auto px-1"
|
|
274
|
+
style:max-height={maxMenuHeight}
|
|
275
|
+
>
|
|
276
|
+
{#if filteredOptions().length > 0}
|
|
277
|
+
{#each filteredOptions() as option (option.value)}
|
|
278
|
+
{@const buttonRestProps = {
|
|
279
|
+
"data-value": String(option.value),
|
|
280
|
+
} as Record<string, string>}
|
|
281
|
+
<div class="w-full my-0.5">
|
|
282
|
+
<Button
|
|
283
|
+
variant={value === option.value
|
|
284
|
+
? "outline"
|
|
285
|
+
: "ghost"}
|
|
286
|
+
size="sm"
|
|
287
|
+
isFullWidth={true}
|
|
288
|
+
disabled={option.disabled}
|
|
289
|
+
onclick={() => handleOptionClick(option.value)}
|
|
290
|
+
{...buttonRestProps}
|
|
291
|
+
>
|
|
292
|
+
{option.label}
|
|
293
|
+
</Button>
|
|
294
|
+
</div>
|
|
295
|
+
{/each}
|
|
296
|
+
{:else if hasSearchQuery()}
|
|
297
|
+
<div class="px-3 py-2 text-sm text-description">
|
|
298
|
+
{noResultsText}
|
|
299
|
+
</div>
|
|
300
|
+
{/if}
|
|
301
|
+
</div>
|
|
217
302
|
</div>
|
|
218
303
|
{/snippet}
|
|
219
304
|
</Dropdown>
|
|
@@ -5,6 +5,16 @@ interface Props {
|
|
|
5
5
|
label: string;
|
|
6
6
|
disabled?: boolean;
|
|
7
7
|
}>;
|
|
8
|
+
/** Enables the search input inside the dropdown. */
|
|
9
|
+
searchable?: boolean;
|
|
10
|
+
/** Placeholder for the search input. */
|
|
11
|
+
searchPlaceholder?: string;
|
|
12
|
+
/** Max height for the options list (supports any valid CSS length). */
|
|
13
|
+
maxMenuHeight?: string;
|
|
14
|
+
/** Width for the dropdown menu (supports any valid CSS length). */
|
|
15
|
+
menuWidth?: string;
|
|
16
|
+
/** Message shown when no options match the search. */
|
|
17
|
+
noResultsText?: string;
|
|
8
18
|
placeholder?: string;
|
|
9
19
|
label?: string;
|
|
10
20
|
disabled?: boolean;
|
|
@@ -3,18 +3,28 @@
|
|
|
3
3
|
width?: string;
|
|
4
4
|
height?: string;
|
|
5
5
|
className?: string;
|
|
6
|
+
"aria-label"?: string;
|
|
6
7
|
}
|
|
7
8
|
|
|
8
9
|
let {
|
|
9
10
|
width = "100%",
|
|
10
11
|
height = "1rem",
|
|
11
12
|
className = "",
|
|
13
|
+
"aria-label": ariaLabel = "Loading...",
|
|
12
14
|
...restProps
|
|
13
15
|
}: Props = $props();
|
|
16
|
+
|
|
17
|
+
const classes = $derived(
|
|
18
|
+
`animate-pulse bg-action-secondary rounded${className ? ` ${className}` : ""}`,
|
|
19
|
+
);
|
|
20
|
+
const styles = $derived(`width: ${width}; height: ${height};`);
|
|
14
21
|
</script>
|
|
15
22
|
|
|
16
23
|
<div
|
|
17
|
-
class=
|
|
18
|
-
style=
|
|
24
|
+
class={classes}
|
|
25
|
+
style={styles}
|
|
26
|
+
aria-busy="true"
|
|
27
|
+
aria-label={ariaLabel}
|
|
28
|
+
role="status"
|
|
19
29
|
{...restProps}
|
|
20
30
|
></div>
|
|
@@ -77,7 +77,11 @@
|
|
|
77
77
|
onfocusout={handleBlur}
|
|
78
78
|
{...restProps}
|
|
79
79
|
>
|
|
80
|
-
<div
|
|
80
|
+
<div
|
|
81
|
+
bind:this={triggerElement}
|
|
82
|
+
id={triggerId}
|
|
83
|
+
aria-describedby={isVisible ? tooltipId : undefined}
|
|
84
|
+
>
|
|
81
85
|
{@render children?.()}
|
|
82
86
|
</div>
|
|
83
87
|
|
|
@@ -97,10 +101,10 @@
|
|
|
97
101
|
|
|
98
102
|
<style>
|
|
99
103
|
:root {
|
|
100
|
-
--tooltip-bg:
|
|
101
|
-
--tooltip-color:
|
|
104
|
+
--tooltip-bg: var(--color-base-800);
|
|
105
|
+
--tooltip-color: var(--color-base-50);
|
|
102
106
|
--tooltip-padding: 0.5rem 0.75rem;
|
|
103
|
-
--tooltip-radius: 0.
|
|
107
|
+
--tooltip-radius: 0.5rem;
|
|
104
108
|
--tooltip-font-size: 0.875rem;
|
|
105
109
|
--tooltip-line-height: 1.25rem;
|
|
106
110
|
--tooltip-max-width: 200px;
|
|
@@ -243,16 +247,6 @@
|
|
|
243
247
|
}
|
|
244
248
|
}
|
|
245
249
|
|
|
246
|
-
@media (prefers-color-scheme: dark) {
|
|
247
|
-
:root {
|
|
248
|
-
--tooltip-bg: #374151;
|
|
249
|
-
}
|
|
250
|
-
}
|
|
251
|
-
|
|
252
|
-
.dark :root {
|
|
253
|
-
--tooltip-bg: #374151;
|
|
254
|
-
}
|
|
255
|
-
|
|
256
250
|
@media (prefers-contrast: high) {
|
|
257
251
|
:root {
|
|
258
252
|
--tooltip-bg: #000000;
|
|
@@ -263,16 +257,20 @@
|
|
|
263
257
|
|
|
264
258
|
@media (prefers-reduced-motion: reduce) {
|
|
265
259
|
.tooltip {
|
|
266
|
-
transition:
|
|
260
|
+
transition:
|
|
261
|
+
opacity 0.1s ease-in-out,
|
|
262
|
+
visibility 0.1s ease-in-out;
|
|
267
263
|
}
|
|
268
264
|
|
|
269
265
|
.tooltip-container[data-placement="top"] .tooltip[data-visible="true"],
|
|
270
|
-
.tooltip-container[data-placement="bottom"]
|
|
266
|
+
.tooltip-container[data-placement="bottom"]
|
|
267
|
+
.tooltip[data-visible="true"] {
|
|
271
268
|
transform: translateX(-50%) !important;
|
|
272
269
|
}
|
|
273
270
|
|
|
274
271
|
.tooltip-container[data-placement="left"] .tooltip[data-visible="true"],
|
|
275
|
-
.tooltip-container[data-placement="right"]
|
|
272
|
+
.tooltip-container[data-placement="right"]
|
|
273
|
+
.tooltip[data-visible="true"] {
|
|
276
274
|
transform: translateY(-50%) !important;
|
|
277
275
|
}
|
|
278
276
|
}
|
package/dist/atoms/index.d.ts
CHANGED
|
@@ -1,12 +1,11 @@
|
|
|
1
1
|
export { default as Toggle } from './Toggle.svelte';
|
|
2
2
|
export { default as Badge } from './Badge.svelte';
|
|
3
3
|
export { default as Button } from './Button.svelte';
|
|
4
|
+
export { default as IconButton } from './IconButton.svelte';
|
|
4
5
|
export { default as Card } from './Card.svelte';
|
|
5
6
|
export { default as CardHeader } from './CardHeader.svelte';
|
|
6
7
|
export { default as CardContent } from './CardContent.svelte';
|
|
7
8
|
export { default as CardFooter } from './CardFooter.svelte';
|
|
8
|
-
export { default as CardTitle } from './CardTitle.svelte';
|
|
9
|
-
export { default as CardDescription } from './CardDescription.svelte';
|
|
10
9
|
export { default as Input } from './Input.svelte';
|
|
11
10
|
export { default as Textarea } from './Textarea.svelte';
|
|
12
11
|
export { default as Select } from './Select.svelte';
|
package/dist/atoms/index.js
CHANGED
|
@@ -1,12 +1,11 @@
|
|
|
1
1
|
export { default as Toggle } from './Toggle.svelte';
|
|
2
2
|
export { default as Badge } from './Badge.svelte';
|
|
3
3
|
export { default as Button } from './Button.svelte';
|
|
4
|
+
export { default as IconButton } from './IconButton.svelte';
|
|
4
5
|
export { default as Card } from './Card.svelte';
|
|
5
6
|
export { default as CardHeader } from './CardHeader.svelte';
|
|
6
7
|
export { default as CardContent } from './CardContent.svelte';
|
|
7
8
|
export { default as CardFooter } from './CardFooter.svelte';
|
|
8
|
-
export { default as CardTitle } from './CardTitle.svelte';
|
|
9
|
-
export { default as CardDescription } from './CardDescription.svelte';
|
|
10
9
|
export { default as Input } from './Input.svelte';
|
|
11
10
|
export { default as Textarea } from './Textarea.svelte';
|
|
12
11
|
export { default as Select } from './Select.svelte';
|
|
@@ -1,12 +1,11 @@
|
|
|
1
1
|
export { default as Toggle } from './Toggle.svelte';
|
|
2
2
|
export { default as Badge } from './Badge.svelte';
|
|
3
3
|
export { default as Button } from './Button.svelte';
|
|
4
|
+
export { default as IconButton } from './IconButton.svelte';
|
|
4
5
|
export { default as Card } from './Card.svelte';
|
|
5
6
|
export { default as CardHeader } from './CardHeader.svelte';
|
|
6
7
|
export { default as CardContent } from './CardContent.svelte';
|
|
7
8
|
export { default as CardFooter } from './CardFooter.svelte';
|
|
8
|
-
export { default as CardTitle } from './CardTitle.svelte';
|
|
9
|
-
export { default as CardDescription } from './CardDescription.svelte';
|
|
10
9
|
export { default as Input } from './Input.svelte';
|
|
11
10
|
export { default as Textarea } from './Textarea.svelte';
|
|
12
11
|
export { default as Select } from './Select.svelte';
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/components/atoms/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,IAAI,MAAM,EAAE,MAAM,iBAAiB,CAAC;AACpD,OAAO,EAAE,OAAO,IAAI,KAAK,EAAE,MAAM,gBAAgB,CAAC;AAClD,OAAO,EAAE,OAAO,IAAI,MAAM,EAAE,MAAM,iBAAiB,CAAC;AACpD,OAAO,EAAE,OAAO,IAAI,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/components/atoms/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,IAAI,MAAM,EAAE,MAAM,iBAAiB,CAAC;AACpD,OAAO,EAAE,OAAO,IAAI,KAAK,EAAE,MAAM,gBAAgB,CAAC;AAClD,OAAO,EAAE,OAAO,IAAI,MAAM,EAAE,MAAM,iBAAiB,CAAC;AACpD,OAAO,EAAE,OAAO,IAAI,UAAU,EAAE,MAAM,qBAAqB,CAAC;AAC5D,OAAO,EAAE,OAAO,IAAI,IAAI,EAAE,MAAM,eAAe,CAAC;AAChD,OAAO,EAAE,OAAO,IAAI,UAAU,EAAE,MAAM,qBAAqB,CAAC;AAC5D,OAAO,EAAE,OAAO,IAAI,WAAW,EAAE,MAAM,sBAAsB,CAAC;AAC9D,OAAO,EAAE,OAAO,IAAI,UAAU,EAAE,MAAM,qBAAqB,CAAC;AAC5D,OAAO,EAAE,OAAO,IAAI,KAAK,EAAE,MAAM,gBAAgB,CAAC;AAClD,OAAO,EAAE,OAAO,IAAI,QAAQ,EAAE,MAAM,mBAAmB,CAAC;AACxD,OAAO,EAAE,OAAO,IAAI,MAAM,EAAE,MAAM,iBAAiB,CAAC;AACpD,OAAO,EAAE,OAAO,IAAI,WAAW,EAAE,MAAM,sBAAsB,CAAC;AAC9D,OAAO,EAAE,OAAO,IAAI,OAAO,EAAE,MAAM,kBAAkB,CAAC;AACtD,OAAO,EAAE,OAAO,IAAI,WAAW,EAAE,MAAM,sBAAsB,CAAC"}
|
|
@@ -5,6 +5,14 @@ export { default as Dropdown } from './Dropdown.svelte';
|
|
|
5
5
|
export { default as Form } from './Form.svelte';
|
|
6
6
|
export { default as ImageUpload } from './ImageUpload.svelte';
|
|
7
7
|
export { default as Modal } from './Modal.svelte';
|
|
8
|
+
export { default as NavigationMenu } from './NavigationMenu.svelte';
|
|
9
|
+
export { default as NavigationMenuList } from './NavigationMenuList.svelte';
|
|
10
|
+
export { default as NavigationMenuItem } from './NavigationMenuItem.svelte';
|
|
11
|
+
export { default as NavigationMenuTrigger } from './NavigationMenuTrigger.svelte';
|
|
12
|
+
export { default as NavigationMenuContent } from './NavigationMenuContent.svelte';
|
|
13
|
+
export { default as NavigationMenuLink } from './NavigationMenuLink.svelte';
|
|
14
|
+
export { default as Section } from './Section.svelte';
|
|
15
|
+
export { default as Sidebar } from './Sidebar.svelte';
|
|
8
16
|
export { default as SlideUp } from './SlideUp.svelte';
|
|
9
17
|
export { default as Tabs } from './Tabs.svelte';
|
|
10
18
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/components/molecules/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,IAAI,KAAK,EAAE,MAAM,gBAAgB,CAAC;AAClD,OAAO,EAAE,OAAO,IAAI,aAAa,EAAE,MAAM,wBAAwB,CAAC;AAClE,OAAO,EAAE,OAAO,IAAI,WAAW,EAAE,MAAM,sBAAsB,CAAC;AAC9D,OAAO,EAAE,OAAO,IAAI,QAAQ,EAAE,MAAM,mBAAmB,CAAC;AACxD,OAAO,EAAE,OAAO,IAAI,IAAI,EAAE,MAAM,eAAe,CAAC;AAChD,OAAO,EAAE,OAAO,IAAI,WAAW,EAAE,MAAM,sBAAsB,CAAC;AAC9D,OAAO,EAAE,OAAO,IAAI,KAAK,EAAE,MAAM,gBAAgB,CAAC;AAClD,OAAO,EAAE,OAAO,IAAI,OAAO,EAAE,MAAM,kBAAkB,CAAC;AACtD,OAAO,EAAE,OAAO,IAAI,IAAI,EAAE,MAAM,eAAe,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/components/molecules/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,IAAI,KAAK,EAAE,MAAM,gBAAgB,CAAC;AAClD,OAAO,EAAE,OAAO,IAAI,aAAa,EAAE,MAAM,wBAAwB,CAAC;AAClE,OAAO,EAAE,OAAO,IAAI,WAAW,EAAE,MAAM,sBAAsB,CAAC;AAC9D,OAAO,EAAE,OAAO,IAAI,QAAQ,EAAE,MAAM,mBAAmB,CAAC;AACxD,OAAO,EAAE,OAAO,IAAI,IAAI,EAAE,MAAM,eAAe,CAAC;AAChD,OAAO,EAAE,OAAO,IAAI,WAAW,EAAE,MAAM,sBAAsB,CAAC;AAC9D,OAAO,EAAE,OAAO,IAAI,KAAK,EAAE,MAAM,gBAAgB,CAAC;AAClD,OAAO,EAAE,OAAO,IAAI,cAAc,EAAE,MAAM,yBAAyB,CAAC;AACpE,OAAO,EAAE,OAAO,IAAI,kBAAkB,EAAE,MAAM,6BAA6B,CAAC;AAC5E,OAAO,EAAE,OAAO,IAAI,kBAAkB,EAAE,MAAM,6BAA6B,CAAC;AAC5E,OAAO,EAAE,OAAO,IAAI,qBAAqB,EAAE,MAAM,gCAAgC,CAAC;AAClF,OAAO,EAAE,OAAO,IAAI,qBAAqB,EAAE,MAAM,gCAAgC,CAAC;AAClF,OAAO,EAAE,OAAO,IAAI,kBAAkB,EAAE,MAAM,6BAA6B,CAAC;AAC5E,OAAO,EAAE,OAAO,IAAI,OAAO,EAAE,MAAM,kBAAkB,CAAC;AACtD,OAAO,EAAE,OAAO,IAAI,OAAO,EAAE,MAAM,kBAAkB,CAAC;AACtD,OAAO,EAAE,OAAO,IAAI,OAAO,EAAE,MAAM,kBAAkB,CAAC;AACtD,OAAO,EAAE,OAAO,IAAI,IAAI,EAAE,MAAM,eAAe,CAAC"}
|
|
@@ -1,27 +1,45 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
+
import Button from "../atoms/Button.svelte";
|
|
3
|
+
|
|
2
4
|
interface Props {
|
|
3
5
|
isOpen?: boolean;
|
|
4
6
|
placement?: "bottom-start" | "bottom-end" | "top-start" | "top-end";
|
|
5
7
|
ariaLabel?: string;
|
|
8
|
+
selectedValue?: string | number | null;
|
|
9
|
+
options?: Array<{
|
|
10
|
+
value: string | number;
|
|
11
|
+
label: string;
|
|
12
|
+
disabled?: boolean;
|
|
13
|
+
}>;
|
|
14
|
+
onOptionClick?: (value: string | number) => void;
|
|
6
15
|
}
|
|
7
16
|
|
|
8
17
|
let {
|
|
9
18
|
isOpen = false,
|
|
10
19
|
placement = "bottom-start",
|
|
11
20
|
ariaLabel = "Menu",
|
|
21
|
+
selectedValue = null,
|
|
22
|
+
options = [],
|
|
23
|
+
onOptionClick,
|
|
12
24
|
children,
|
|
13
25
|
trigger,
|
|
14
26
|
...restProps
|
|
15
27
|
}: Props & { children?: any; trigger?: any } = $props();
|
|
16
28
|
|
|
17
29
|
let dropdownId = `dropdown-${Math.random().toString(36).substr(2, 9)}`;
|
|
18
|
-
let triggerElement
|
|
19
|
-
let menuElement
|
|
30
|
+
let triggerElement = $state<HTMLElement | null>(null);
|
|
31
|
+
let menuElement = $state<HTMLElement | null>(null);
|
|
32
|
+
let openedViaKeyboard = $state(false);
|
|
20
33
|
|
|
21
34
|
function handleKeydown(event: KeyboardEvent) {
|
|
22
35
|
if (!isOpen) {
|
|
23
|
-
if (
|
|
36
|
+
if (
|
|
37
|
+
event.key === "Enter" ||
|
|
38
|
+
event.key === " " ||
|
|
39
|
+
event.key === "ArrowDown"
|
|
40
|
+
) {
|
|
24
41
|
event.preventDefault();
|
|
42
|
+
openedViaKeyboard = true;
|
|
25
43
|
isOpen = true;
|
|
26
44
|
}
|
|
27
45
|
return;
|
|
@@ -58,21 +76,29 @@
|
|
|
58
76
|
function getMenuItems(): HTMLElement[] {
|
|
59
77
|
if (!menuElement) return [];
|
|
60
78
|
return Array.from(
|
|
61
|
-
menuElement.querySelectorAll<HTMLElement>(
|
|
79
|
+
menuElement.querySelectorAll<HTMLElement>(
|
|
80
|
+
'[role="menuitem"], button, a',
|
|
81
|
+
),
|
|
62
82
|
);
|
|
63
83
|
}
|
|
64
84
|
|
|
65
85
|
function focusNextItem() {
|
|
66
86
|
const items = getMenuItems();
|
|
67
|
-
const currentIndex = items.findIndex(
|
|
68
|
-
|
|
87
|
+
const currentIndex = items.findIndex(
|
|
88
|
+
(item) => item === document.activeElement,
|
|
89
|
+
);
|
|
90
|
+
const nextIndex =
|
|
91
|
+
currentIndex < items.length - 1 ? currentIndex + 1 : 0;
|
|
69
92
|
items[nextIndex]?.focus();
|
|
70
93
|
}
|
|
71
94
|
|
|
72
95
|
function focusPreviousItem() {
|
|
73
96
|
const items = getMenuItems();
|
|
74
|
-
const currentIndex = items.findIndex(
|
|
75
|
-
|
|
97
|
+
const currentIndex = items.findIndex(
|
|
98
|
+
(item) => item === document.activeElement,
|
|
99
|
+
);
|
|
100
|
+
const prevIndex =
|
|
101
|
+
currentIndex > 0 ? currentIndex - 1 : items.length - 1;
|
|
76
102
|
items[prevIndex]?.focus();
|
|
77
103
|
}
|
|
78
104
|
|
|
@@ -86,11 +112,41 @@
|
|
|
86
112
|
items[items.length - 1]?.focus();
|
|
87
113
|
}
|
|
88
114
|
|
|
89
|
-
// Focus first item when dropdown opens
|
|
115
|
+
// Focus first item when dropdown opens via keyboard
|
|
90
116
|
$effect(() => {
|
|
91
|
-
if (isOpen && menuElement) {
|
|
117
|
+
if (isOpen && menuElement && openedViaKeyboard) {
|
|
92
118
|
setTimeout(() => {
|
|
93
119
|
focusFirstItem();
|
|
120
|
+
openedViaKeyboard = false;
|
|
121
|
+
}, 0);
|
|
122
|
+
} else if (!isOpen) {
|
|
123
|
+
openedViaKeyboard = false;
|
|
124
|
+
}
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
// Mark selected items
|
|
128
|
+
$effect(() => {
|
|
129
|
+
if (isOpen && menuElement) {
|
|
130
|
+
// Small delay to ensure DOM is ready
|
|
131
|
+
setTimeout(() => {
|
|
132
|
+
const items = getMenuItems();
|
|
133
|
+
items.forEach((item) => {
|
|
134
|
+
if (selectedValue !== null && selectedValue !== undefined) {
|
|
135
|
+
const itemValue =
|
|
136
|
+
item.getAttribute("data-value") ||
|
|
137
|
+
item.textContent?.trim();
|
|
138
|
+
if (String(itemValue) === String(selectedValue)) {
|
|
139
|
+
item.setAttribute("data-selected", "true");
|
|
140
|
+
item.setAttribute("aria-selected", "true");
|
|
141
|
+
} else {
|
|
142
|
+
item.removeAttribute("data-selected");
|
|
143
|
+
item.removeAttribute("aria-selected");
|
|
144
|
+
}
|
|
145
|
+
} else {
|
|
146
|
+
item.removeAttribute("data-selected");
|
|
147
|
+
item.removeAttribute("aria-selected");
|
|
148
|
+
}
|
|
149
|
+
});
|
|
94
150
|
}, 0);
|
|
95
151
|
}
|
|
96
152
|
});
|
|
@@ -137,8 +193,8 @@
|
|
|
137
193
|
});
|
|
138
194
|
</script>
|
|
139
195
|
|
|
140
|
-
<div
|
|
141
|
-
class="relative inline-block"
|
|
196
|
+
<div
|
|
197
|
+
class="relative inline-block"
|
|
142
198
|
data-placement={placement}
|
|
143
199
|
onkeydown={handleKeydown}
|
|
144
200
|
{...restProps}
|
|
@@ -148,7 +204,7 @@
|
|
|
148
204
|
</div>
|
|
149
205
|
|
|
150
206
|
{#if isOpen}
|
|
151
|
-
<div
|
|
207
|
+
<div
|
|
152
208
|
bind:this={menuElement}
|
|
153
209
|
id={dropdownId}
|
|
154
210
|
class={dropdownContentClasses()}
|
|
@@ -156,7 +212,38 @@
|
|
|
156
212
|
aria-label={ariaLabel}
|
|
157
213
|
tabindex="-1"
|
|
158
214
|
>
|
|
159
|
-
{
|
|
215
|
+
{#if options.length > 0}
|
|
216
|
+
<div class="px-2 py-2">
|
|
217
|
+
{#each options as option (option.value)}
|
|
218
|
+
{@const buttonRestProps = {
|
|
219
|
+
"data-value": String(option.value),
|
|
220
|
+
} as any}
|
|
221
|
+
<div class="w-full my-0.5">
|
|
222
|
+
<Button
|
|
223
|
+
variant={selectedValue === option.value
|
|
224
|
+
? "outline"
|
|
225
|
+
: "ghost"}
|
|
226
|
+
size="sm"
|
|
227
|
+
isFullWidth={true}
|
|
228
|
+
disabled={option.disabled}
|
|
229
|
+
onclick={() => onOptionClick?.(option.value)}
|
|
230
|
+
{...buttonRestProps}
|
|
231
|
+
>
|
|
232
|
+
{option.label}
|
|
233
|
+
</Button>
|
|
234
|
+
</div>
|
|
235
|
+
{/each}
|
|
236
|
+
</div>
|
|
237
|
+
{:else if children}
|
|
238
|
+
{@render children?.()}
|
|
239
|
+
{/if}
|
|
160
240
|
</div>
|
|
161
241
|
{/if}
|
|
162
242
|
</div>
|
|
243
|
+
|
|
244
|
+
<style>
|
|
245
|
+
:global([role="menu"] button) {
|
|
246
|
+
justify-content: flex-start;
|
|
247
|
+
text-align: left;
|
|
248
|
+
}
|
|
249
|
+
</style>
|