zabi-components 5.0.20 → 5.0.22
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 +355 -81
- package/dist/atoms/Button.svelte +1 -1
- package/dist/atoms/CardContent.svelte +1 -1
- package/dist/atoms/CardHeader.svelte +1 -1
- package/dist/atoms/Checkbox.svelte +1 -7
- package/dist/atoms/CodeBlock.svelte +6 -6
- package/dist/atoms/ColorPicker.svelte +80 -72
- package/dist/atoms/IconButton.svelte +88 -0
- package/dist/atoms/IconButton.svelte.d.ts +21 -0
- package/dist/atoms/Input.svelte +14 -10
- package/dist/atoms/Input.svelte.d.ts +2 -0
- package/dist/atoms/List.svelte +47 -0
- package/dist/atoms/List.svelte.d.ts +19 -0
- package/dist/atoms/ListItem.svelte +148 -0
- package/dist/atoms/ListItem.svelte.d.ts +35 -0
- package/dist/atoms/Progress.svelte +3 -5
- package/dist/atoms/Select.svelte +152 -19
- package/dist/atoms/Select.svelte.d.ts +23 -1
- package/dist/atoms/Textarea.svelte +11 -9
- package/dist/atoms/Textarea.svelte.d.ts +1 -0
- package/dist/atoms/Toggle.svelte +1 -7
- package/dist/atoms/Tooltip.svelte +8 -15
- package/dist/atoms/index.d.ts +3 -0
- package/dist/atoms/index.js +3 -0
- package/dist/components/atoms/index.d.ts +3 -0
- package/dist/components/atoms/index.d.ts.map +1 -1
- package/dist/components/index.d.ts +4 -0
- package/dist/components/index.d.ts.map +1 -1
- package/dist/components/molecules/index.d.ts +1 -1
- package/dist/components/molecules/navigation-menu-context.d.ts +7 -0
- package/dist/components/molecules/navigation-menu-context.d.ts.map +1 -0
- package/dist/components/organisms/index.d.ts +2 -0
- package/dist/components/organisms/index.d.ts.map +1 -1
- package/dist/index.d.ts +4 -0
- package/dist/index.js +5 -8
- package/dist/molecules/Alert.svelte +8 -3
- package/dist/molecules/ContactForm.svelte +63 -31
- package/dist/molecules/ContactForm.svelte.d.ts +1 -4
- package/dist/molecules/Dropdown.svelte +2 -1
- package/dist/molecules/ImageUpload.svelte +26 -3
- package/dist/molecules/ImageUpload.svelte.d.ts +1 -0
- package/dist/molecules/Modal.svelte +44 -43
- package/dist/molecules/NavigationMenu.svelte +7 -10
- package/dist/molecules/NavigationMenuContent.svelte +8 -8
- package/dist/molecules/NavigationMenuItem.svelte +7 -6
- package/dist/molecules/NavigationMenuLink.svelte +7 -5
- package/dist/molecules/NavigationMenuList.svelte +0 -7
- package/dist/molecules/NavigationMenuTrigger.svelte +7 -7
- package/dist/molecules/Section.svelte +158 -0
- package/dist/molecules/Section.svelte.d.ts +27 -0
- package/dist/molecules/SlideUp.svelte +30 -0
- package/dist/molecules/Tabs.svelte +71 -4
- package/dist/molecules/index.d.ts +1 -1
- package/dist/molecules/index.js +1 -1
- package/dist/molecules/navigation-menu-context.d.ts +6 -0
- package/dist/molecules/navigation-menu-context.js +1 -0
- package/dist/organisms/Navigation.svelte +9 -6
- package/dist/organisms/Navigation.svelte.d.ts +1 -0
- package/dist/organisms/SidebarNavigation.svelte +420 -0
- package/dist/organisms/SidebarNavigation.svelte.d.ts +44 -0
- package/dist/organisms/SidebarProjectPanel.svelte +174 -0
- package/dist/organisms/SidebarProjectPanel.svelte.d.ts +32 -0
- package/dist/organisms/index.d.ts +2 -0
- package/dist/organisms/index.js +2 -0
- package/dist/zabi-components.css +412 -0
- package/package.json +7 -8
- package/dist/molecules/Sidebar.svelte +0 -324
- package/dist/molecules/Sidebar.svelte.d.ts +0 -30
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { ArrowRight } from "@lucide/svelte";
|
|
3
|
+
import type { Component } from "svelte";
|
|
4
|
+
|
|
5
|
+
export interface ListItemData {
|
|
6
|
+
/** Unique identifier for stable rendering and active-state tracking. */
|
|
7
|
+
id: string;
|
|
8
|
+
/** Primary item label. */
|
|
9
|
+
label: string;
|
|
10
|
+
/** Optional secondary text shown under the label. */
|
|
11
|
+
description?: string;
|
|
12
|
+
/** Optional URL; when set, the item renders as an anchor. */
|
|
13
|
+
href?: string;
|
|
14
|
+
/** Optional leading icon rendered before text. */
|
|
15
|
+
icon?: Component<{ size?: number; class?: string }>;
|
|
16
|
+
/** Link target, only used when href is provided. */
|
|
17
|
+
target?: "_self" | "_blank" | "_parent" | "_top";
|
|
18
|
+
/** Link rel attribute, only used when href is provided. */
|
|
19
|
+
rel?: string;
|
|
20
|
+
/** Disabled state for the item. */
|
|
21
|
+
disabled?: boolean;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
interface Props {
|
|
25
|
+
/** Item data to render. */
|
|
26
|
+
item: ListItemData;
|
|
27
|
+
/** Active-state styling flag. */
|
|
28
|
+
selected?: boolean;
|
|
29
|
+
/** Whether to show the right-arrow icon. */
|
|
30
|
+
showArrow?: boolean;
|
|
31
|
+
/** Item click callback. */
|
|
32
|
+
onclick?: (item: ListItemData, event: MouseEvent) => void;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
let {
|
|
36
|
+
item,
|
|
37
|
+
selected = false,
|
|
38
|
+
showArrow = true,
|
|
39
|
+
onclick,
|
|
40
|
+
...restProps
|
|
41
|
+
}: Props = $props();
|
|
42
|
+
|
|
43
|
+
const itemClasses = $derived.by(() => {
|
|
44
|
+
const baseClasses =
|
|
45
|
+
"group flex w-full items-center gap-3 rounded-xl border border-border px-4 py-3 text-left transition-all duration-150 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-focus-ring focus-visible:ring-inset";
|
|
46
|
+
const interactiveClasses = item.disabled
|
|
47
|
+
? "cursor-not-allowed opacity-50"
|
|
48
|
+
: "cursor-pointer hover:ring-1 hover:ring-focus focus-visible:bg-base-100";
|
|
49
|
+
const selectedClasses = selected
|
|
50
|
+
? "bg-action-primary-subtle border-brand-500"
|
|
51
|
+
: "";
|
|
52
|
+
return `${baseClasses} ${interactiveClasses} ${selectedClasses}`.trim();
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
const ariaCurrent = $derived(item.href && selected ? "page" : undefined);
|
|
56
|
+
|
|
57
|
+
const rel = $derived.by(() => {
|
|
58
|
+
if (item.rel) {
|
|
59
|
+
return item.rel;
|
|
60
|
+
}
|
|
61
|
+
if (item.target === "_blank") {
|
|
62
|
+
return "noopener noreferrer";
|
|
63
|
+
}
|
|
64
|
+
return undefined;
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
const handleItemClick = (event: MouseEvent): void => {
|
|
68
|
+
if (item.disabled) {
|
|
69
|
+
event.preventDefault();
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
onclick?.(item, event);
|
|
73
|
+
};
|
|
74
|
+
</script>
|
|
75
|
+
|
|
76
|
+
{#if item.href}
|
|
77
|
+
<a
|
|
78
|
+
href={item.href}
|
|
79
|
+
target={item.target}
|
|
80
|
+
{rel}
|
|
81
|
+
aria-current={ariaCurrent}
|
|
82
|
+
aria-disabled={item.disabled ? "true" : undefined}
|
|
83
|
+
tabindex={item.disabled ? -1 : undefined}
|
|
84
|
+
class={itemClasses}
|
|
85
|
+
onclick={handleItemClick}
|
|
86
|
+
{...restProps}
|
|
87
|
+
>
|
|
88
|
+
{#if item.icon}
|
|
89
|
+
<span
|
|
90
|
+
class="flex size-5 shrink-0 items-center justify-center text-description"
|
|
91
|
+
aria-hidden="true"
|
|
92
|
+
>
|
|
93
|
+
<item.icon size={16} />
|
|
94
|
+
</span>
|
|
95
|
+
{/if}
|
|
96
|
+
<span class="min-w-0 flex-1">
|
|
97
|
+
<span class="block text-sm font-medium text-headline"
|
|
98
|
+
>{item.label}</span
|
|
99
|
+
>
|
|
100
|
+
{#if item.description}
|
|
101
|
+
<span class="mt-0.5 block text-sm text-description"
|
|
102
|
+
>{item.description}</span
|
|
103
|
+
>
|
|
104
|
+
{/if}
|
|
105
|
+
</span>
|
|
106
|
+
{#if showArrow}
|
|
107
|
+
<ArrowRight
|
|
108
|
+
size={16}
|
|
109
|
+
class="shrink-0 text-description transition-transform duration-150 group-hover:translate-x-0.5 group-focus-visible:translate-x-0.5"
|
|
110
|
+
aria-hidden="true"
|
|
111
|
+
/>
|
|
112
|
+
{/if}
|
|
113
|
+
</a>
|
|
114
|
+
{:else}
|
|
115
|
+
<button
|
|
116
|
+
type="button"
|
|
117
|
+
class={itemClasses}
|
|
118
|
+
disabled={item.disabled}
|
|
119
|
+
onclick={handleItemClick}
|
|
120
|
+
{...restProps}
|
|
121
|
+
>
|
|
122
|
+
{#if item.icon}
|
|
123
|
+
<span
|
|
124
|
+
class="flex size-5 shrink-0 items-center justify-center text-description"
|
|
125
|
+
aria-hidden="true"
|
|
126
|
+
>
|
|
127
|
+
<item.icon size={16} />
|
|
128
|
+
</span>
|
|
129
|
+
{/if}
|
|
130
|
+
<span class="min-w-0 flex-1">
|
|
131
|
+
<span class="block text-sm font-medium text-headline"
|
|
132
|
+
>{item.label}</span
|
|
133
|
+
>
|
|
134
|
+
{#if item.description}
|
|
135
|
+
<span class="mt-0.5 block text-sm text-description"
|
|
136
|
+
>{item.description}</span
|
|
137
|
+
>
|
|
138
|
+
{/if}
|
|
139
|
+
</span>
|
|
140
|
+
{#if showArrow}
|
|
141
|
+
<ArrowRight
|
|
142
|
+
size={16}
|
|
143
|
+
class="shrink-0 text-description transition-transform duration-150 group-hover:translate-x-0.5 group-focus-visible:translate-x-0.5"
|
|
144
|
+
aria-hidden="true"
|
|
145
|
+
/>
|
|
146
|
+
{/if}
|
|
147
|
+
</button>
|
|
148
|
+
{/if}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import type { Component } from "svelte";
|
|
2
|
+
export interface ListItemData {
|
|
3
|
+
/** Unique identifier for stable rendering and active-state tracking. */
|
|
4
|
+
id: string;
|
|
5
|
+
/** Primary item label. */
|
|
6
|
+
label: string;
|
|
7
|
+
/** Optional secondary text shown under the label. */
|
|
8
|
+
description?: string;
|
|
9
|
+
/** Optional URL; when set, the item renders as an anchor. */
|
|
10
|
+
href?: string;
|
|
11
|
+
/** Optional leading icon rendered before text. */
|
|
12
|
+
icon?: Component<{
|
|
13
|
+
size?: number;
|
|
14
|
+
class?: string;
|
|
15
|
+
}>;
|
|
16
|
+
/** Link target, only used when href is provided. */
|
|
17
|
+
target?: "_self" | "_blank" | "_parent" | "_top";
|
|
18
|
+
/** Link rel attribute, only used when href is provided. */
|
|
19
|
+
rel?: string;
|
|
20
|
+
/** Disabled state for the item. */
|
|
21
|
+
disabled?: boolean;
|
|
22
|
+
}
|
|
23
|
+
interface Props {
|
|
24
|
+
/** Item data to render. */
|
|
25
|
+
item: ListItemData;
|
|
26
|
+
/** Active-state styling flag. */
|
|
27
|
+
selected?: boolean;
|
|
28
|
+
/** Whether to show the right-arrow icon. */
|
|
29
|
+
showArrow?: boolean;
|
|
30
|
+
/** Item click callback. */
|
|
31
|
+
onclick?: (item: ListItemData, event: MouseEvent) => void;
|
|
32
|
+
}
|
|
33
|
+
declare const ListItem: Component<Props, {}, "">;
|
|
34
|
+
type ListItem = ReturnType<typeof ListItem>;
|
|
35
|
+
export default ListItem;
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
+
import { generateId } from "../../routes/lib/ssr-safe.js";
|
|
3
|
+
|
|
2
4
|
interface Props {
|
|
3
5
|
value?: number;
|
|
4
6
|
max?: number;
|
|
@@ -14,11 +16,7 @@
|
|
|
14
16
|
...restProps
|
|
15
17
|
}: Props = $props();
|
|
16
18
|
|
|
17
|
-
|
|
18
|
-
typeof window !== "undefined"
|
|
19
|
-
? `progress-${Math.random().toString(36).substr(2, 9)}`
|
|
20
|
-
: `progress-ssr-${Date.now()}`,
|
|
21
|
-
);
|
|
19
|
+
const progressId = generateId("progress");
|
|
22
20
|
|
|
23
21
|
let percentage = $derived(Math.min(Math.max((value / max) * 100, 0), 100));
|
|
24
22
|
|
package/dist/atoms/Select.svelte
CHANGED
|
@@ -1,19 +1,13 @@
|
|
|
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,
|
|
6
7
|
AlertTriangle,
|
|
7
8
|
AlertCircle,
|
|
8
9
|
} from "@lucide/svelte";
|
|
9
|
-
|
|
10
|
-
function generateId(prefix: string = "id"): string {
|
|
11
|
-
if (typeof window !== "undefined") {
|
|
12
|
-
return `${prefix}-${Math.random().toString(36).substr(2, 9)}`;
|
|
13
|
-
} else {
|
|
14
|
-
return `${prefix}-ssr-${Date.now()}`;
|
|
15
|
-
}
|
|
16
|
-
}
|
|
10
|
+
import { generateId } from "../../routes/lib/ssr-safe.js";
|
|
17
11
|
|
|
18
12
|
interface Props {
|
|
19
13
|
value?: string | number | undefined;
|
|
@@ -22,30 +16,65 @@
|
|
|
22
16
|
label: string;
|
|
23
17
|
disabled?: boolean;
|
|
24
18
|
}>;
|
|
19
|
+
/** Enables the search input inside the dropdown. */
|
|
20
|
+
searchable?: boolean;
|
|
21
|
+
/** Placeholder for the search input. */
|
|
22
|
+
searchPlaceholder?: string;
|
|
23
|
+
/** Max height for the options list (supports any valid CSS length). */
|
|
24
|
+
maxMenuHeight?: string;
|
|
25
|
+
/** Width for the dropdown menu (supports any valid CSS length). */
|
|
26
|
+
menuWidth?: string;
|
|
27
|
+
/** Message shown when no options match the search. */
|
|
28
|
+
noResultsText?: string;
|
|
29
|
+
/** Shows a loading state while options are being fetched. */
|
|
30
|
+
isLoading?: boolean;
|
|
31
|
+
/** Loading text shown in trigger and dropdown body. */
|
|
32
|
+
loadingText?: string;
|
|
33
|
+
/** Empty state title shown when there are no available options. */
|
|
34
|
+
emptyStateTitle?: string;
|
|
35
|
+
/** Empty state description shown when there are no available options. */
|
|
36
|
+
emptyStateDescription?: string;
|
|
37
|
+
/** Optional empty state action label. */
|
|
38
|
+
emptyStateActionLabel?: string;
|
|
25
39
|
placeholder?: string;
|
|
26
40
|
label?: string;
|
|
41
|
+
required?: boolean;
|
|
27
42
|
disabled?: boolean;
|
|
28
43
|
size?: "sm" | "md" | "lg";
|
|
29
44
|
variant?: "default" | "success" | "warning" | "error";
|
|
30
45
|
message?: string;
|
|
31
46
|
onchange?: (event: Event) => void;
|
|
47
|
+
onEmptyStateAction?: () => void;
|
|
32
48
|
}
|
|
33
49
|
|
|
34
50
|
let {
|
|
35
|
-
value = undefined,
|
|
51
|
+
value = $bindable(undefined),
|
|
36
52
|
options = [],
|
|
53
|
+
searchable = true,
|
|
54
|
+
searchPlaceholder = "Search options",
|
|
55
|
+
maxMenuHeight = "60vh",
|
|
56
|
+
menuWidth = "100%",
|
|
57
|
+
noResultsText = "No results found",
|
|
58
|
+
isLoading = false,
|
|
59
|
+
loadingText = "Loading options...",
|
|
60
|
+
emptyStateTitle = "No options available",
|
|
61
|
+
emptyStateDescription = "Add an option to start making selections.",
|
|
62
|
+
emptyStateActionLabel = "",
|
|
37
63
|
placeholder = "Select an option",
|
|
38
64
|
label = "",
|
|
65
|
+
required = false,
|
|
39
66
|
disabled = false,
|
|
40
67
|
size = "md",
|
|
41
68
|
variant = "default",
|
|
42
69
|
message = "",
|
|
43
70
|
onchange,
|
|
71
|
+
onEmptyStateAction,
|
|
44
72
|
...restProps
|
|
45
73
|
}: Props = $props();
|
|
46
74
|
|
|
47
75
|
let isOpen = $state(false);
|
|
48
76
|
let selectContainer: HTMLDivElement;
|
|
77
|
+
let searchQuery = $state("");
|
|
49
78
|
|
|
50
79
|
const sizeClass = $derived(() => {
|
|
51
80
|
if (size === "sm") {
|
|
@@ -93,13 +122,13 @@
|
|
|
93
122
|
|
|
94
123
|
const messageClasses = $derived(() => {
|
|
95
124
|
if (variant === "error") {
|
|
96
|
-
return "text-error text-sm mt-1 flex items-center gap-1.5";
|
|
125
|
+
return "text-error text-sm mt-1 flex items-center gap-1.5 w-full";
|
|
97
126
|
} else if (variant === "success") {
|
|
98
|
-
return "text-success text-sm mt-1 flex items-center gap-1.5";
|
|
127
|
+
return "text-success text-sm mt-1 flex items-center gap-1.5 w-full";
|
|
99
128
|
} else if (variant === "warning") {
|
|
100
|
-
return "text-warning text-sm mt-1 flex items-center gap-1.5";
|
|
129
|
+
return "text-warning text-sm mt-1 flex items-center gap-1.5 w-full";
|
|
101
130
|
}
|
|
102
|
-
return "text-description text-sm mt-1 flex items-center gap-1.5";
|
|
131
|
+
return "text-description text-sm mt-1 flex items-center gap-1.5 w-full";
|
|
103
132
|
});
|
|
104
133
|
|
|
105
134
|
const getIcon = $derived(() => {
|
|
@@ -125,8 +154,35 @@
|
|
|
125
154
|
return value === undefined || value === null || value === "";
|
|
126
155
|
});
|
|
127
156
|
|
|
157
|
+
const normalizedOptions = $derived(() => {
|
|
158
|
+
const fallbackOptions = (restProps as { options?: Props["options"] })
|
|
159
|
+
.options;
|
|
160
|
+
return Array.from(options ?? fallbackOptions ?? []);
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
const hasSearchQuery = $derived(() => {
|
|
164
|
+
return searchQuery.trim().length > 0;
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
const filteredOptions = $derived(() => {
|
|
168
|
+
const query = searchQuery.trim().toLowerCase();
|
|
169
|
+
const availableOptions = normalizedOptions();
|
|
170
|
+
if (!searchable || query.length === 0) {
|
|
171
|
+
return availableOptions;
|
|
172
|
+
}
|
|
173
|
+
return availableOptions.filter((option) =>
|
|
174
|
+
option.label.toLowerCase().includes(query),
|
|
175
|
+
);
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
$effect(() => {
|
|
179
|
+
if (!isOpen && searchQuery.length > 0) {
|
|
180
|
+
searchQuery = "";
|
|
181
|
+
}
|
|
182
|
+
});
|
|
183
|
+
|
|
128
184
|
function handleOptionClick(optionValue: string | number) {
|
|
129
|
-
if (disabled) return;
|
|
185
|
+
if (disabled || isLoading) return;
|
|
130
186
|
value = optionValue;
|
|
131
187
|
isOpen = false;
|
|
132
188
|
|
|
@@ -141,7 +197,7 @@
|
|
|
141
197
|
}
|
|
142
198
|
|
|
143
199
|
function handleTriggerClick(event: MouseEvent) {
|
|
144
|
-
if (disabled) return;
|
|
200
|
+
if (disabled || isLoading) return;
|
|
145
201
|
event.stopPropagation();
|
|
146
202
|
isOpen = !isOpen;
|
|
147
203
|
}
|
|
@@ -179,8 +235,8 @@
|
|
|
179
235
|
{isOpen}
|
|
180
236
|
placement="bottom-start"
|
|
181
237
|
selectedValue={value}
|
|
182
|
-
{options}
|
|
183
238
|
onOptionClick={handleOptionClick}
|
|
239
|
+
ariaLabel="Select options"
|
|
184
240
|
>
|
|
185
241
|
{#snippet trigger()}
|
|
186
242
|
<button
|
|
@@ -194,14 +250,20 @@
|
|
|
194
250
|
aria-describedby={message ? `${selectId}-message` : undefined}
|
|
195
251
|
>
|
|
196
252
|
<span
|
|
197
|
-
class="text-left flex-1 {
|
|
253
|
+
class="text-left flex-1 truncate {isLoading
|
|
254
|
+
? 'text-description'
|
|
255
|
+
: isEmpty()
|
|
198
256
|
? 'text-description'
|
|
199
257
|
: 'text-body'}"
|
|
200
258
|
>
|
|
201
|
-
{
|
|
259
|
+
{#if isLoading}
|
|
260
|
+
{loadingText}
|
|
261
|
+
{:else}
|
|
262
|
+
{isEmpty()
|
|
202
263
|
? placeholder
|
|
203
264
|
: options.find((opt) => opt.value === value)?.label ||
|
|
204
265
|
placeholder}
|
|
266
|
+
{/if}
|
|
205
267
|
</span>
|
|
206
268
|
<ChevronDown
|
|
207
269
|
size={20}
|
|
@@ -211,9 +273,80 @@
|
|
|
211
273
|
/>
|
|
212
274
|
</button>
|
|
213
275
|
{/snippet}
|
|
276
|
+
{#snippet children()}
|
|
277
|
+
<div class="px-2 pb-2 pt-1" style:width={menuWidth}>
|
|
278
|
+
{#if searchable && !isLoading}
|
|
279
|
+
<div class="px-1 pb-2">
|
|
280
|
+
<input
|
|
281
|
+
type="text"
|
|
282
|
+
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"
|
|
283
|
+
placeholder={searchPlaceholder}
|
|
284
|
+
bind:value={searchQuery}
|
|
285
|
+
aria-label={searchPlaceholder}
|
|
286
|
+
{disabled}
|
|
287
|
+
/>
|
|
288
|
+
</div>
|
|
289
|
+
{/if}
|
|
290
|
+
<div
|
|
291
|
+
class="overflow-y-auto px-1"
|
|
292
|
+
style:max-height={maxMenuHeight}
|
|
293
|
+
>
|
|
294
|
+
{#if isLoading}
|
|
295
|
+
<div class="space-y-2 px-2 py-2" role="status" aria-live="polite">
|
|
296
|
+
<div class="h-8 w-full animate-pulse rounded-md bg-base-200"></div>
|
|
297
|
+
<div class="h-8 w-full animate-pulse rounded-md bg-base-200"></div>
|
|
298
|
+
<p class="text-xs text-description">{loadingText}</p>
|
|
299
|
+
</div>
|
|
300
|
+
{:else if filteredOptions().length > 0}
|
|
301
|
+
{#each filteredOptions() as option (option.value)}
|
|
302
|
+
{@const buttonRestProps = {
|
|
303
|
+
"data-value": String(option.value),
|
|
304
|
+
} as Record<string, string>}
|
|
305
|
+
<div class="w-full my-0.5">
|
|
306
|
+
<Button
|
|
307
|
+
variant={value === option.value
|
|
308
|
+
? "outline"
|
|
309
|
+
: "ghost"}
|
|
310
|
+
size="sm"
|
|
311
|
+
isFullWidth={true}
|
|
312
|
+
disabled={option.disabled}
|
|
313
|
+
onclick={() => handleOptionClick(option.value)}
|
|
314
|
+
{...buttonRestProps}
|
|
315
|
+
>
|
|
316
|
+
{option.label}
|
|
317
|
+
</Button>
|
|
318
|
+
</div>
|
|
319
|
+
{/each}
|
|
320
|
+
{:else if hasSearchQuery()}
|
|
321
|
+
<div class="px-3 py-2 text-sm text-description">
|
|
322
|
+
{noResultsText}
|
|
323
|
+
</div>
|
|
324
|
+
{:else}
|
|
325
|
+
<div class="rounded-md border border-border bg-base-50 px-3 py-3 text-sm">
|
|
326
|
+
<p class="font-medium text-headline">{emptyStateTitle}</p>
|
|
327
|
+
<p class="mt-1 text-description">{emptyStateDescription}</p>
|
|
328
|
+
{#if emptyStateActionLabel && onEmptyStateAction}
|
|
329
|
+
<button
|
|
330
|
+
type="button"
|
|
331
|
+
class="mt-3 inline-flex min-h-11 items-center rounded-lg bg-action-primary px-3 py-2 text-sm text-action-primary"
|
|
332
|
+
onclick={onEmptyStateAction}
|
|
333
|
+
>
|
|
334
|
+
{emptyStateActionLabel}
|
|
335
|
+
</button>
|
|
336
|
+
{/if}
|
|
337
|
+
</div>
|
|
338
|
+
{/if}
|
|
339
|
+
</div>
|
|
340
|
+
</div>
|
|
341
|
+
{/snippet}
|
|
214
342
|
</Dropdown>
|
|
215
343
|
{#if message && variant !== "default"}
|
|
216
|
-
<p
|
|
344
|
+
<p
|
|
345
|
+
id={`${selectId}-message`}
|
|
346
|
+
class={messageClasses()}
|
|
347
|
+
role={variant === "error" ? "alert" : "status"}
|
|
348
|
+
aria-live={variant === "error" ? "assertive" : "polite"}
|
|
349
|
+
>
|
|
217
350
|
{#if getIcon()}
|
|
218
351
|
{@const Icon = getIcon()}
|
|
219
352
|
<Icon size={14} class="shrink-0" />
|
|
@@ -5,14 +5,36 @@ 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;
|
|
18
|
+
/** Shows a loading state while options are being fetched. */
|
|
19
|
+
isLoading?: boolean;
|
|
20
|
+
/** Loading text shown in trigger and dropdown body. */
|
|
21
|
+
loadingText?: string;
|
|
22
|
+
/** Empty state title shown when there are no available options. */
|
|
23
|
+
emptyStateTitle?: string;
|
|
24
|
+
/** Empty state description shown when there are no available options. */
|
|
25
|
+
emptyStateDescription?: string;
|
|
26
|
+
/** Optional empty state action label. */
|
|
27
|
+
emptyStateActionLabel?: string;
|
|
8
28
|
placeholder?: string;
|
|
9
29
|
label?: string;
|
|
30
|
+
required?: boolean;
|
|
10
31
|
disabled?: boolean;
|
|
11
32
|
size?: "sm" | "md" | "lg";
|
|
12
33
|
variant?: "default" | "success" | "warning" | "error";
|
|
13
34
|
message?: string;
|
|
14
35
|
onchange?: (event: Event) => void;
|
|
36
|
+
onEmptyStateAction?: () => void;
|
|
15
37
|
}
|
|
16
|
-
declare const Select: import("svelte").Component<Props, {}, "">;
|
|
38
|
+
declare const Select: import("svelte").Component<Props, {}, "value">;
|
|
17
39
|
type Select = ReturnType<typeof Select>;
|
|
18
40
|
export default Select;
|
|
@@ -1,20 +1,14 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import { CheckCircle, AlertTriangle, AlertCircle } from "@lucide/svelte";
|
|
3
3
|
import type { SemanticVariant, SizeVariant } from "../../types/variants.js";
|
|
4
|
-
|
|
5
|
-
function generateId(prefix: string = "id"): string {
|
|
6
|
-
if (typeof window !== "undefined") {
|
|
7
|
-
return `${prefix}-${Math.random().toString(36).substr(2, 9)}`;
|
|
8
|
-
} else {
|
|
9
|
-
return `${prefix}-ssr-${Date.now()}`;
|
|
10
|
-
}
|
|
11
|
-
}
|
|
4
|
+
import { generateId } from "../../routes/lib/ssr-safe.js";
|
|
12
5
|
|
|
13
6
|
interface Props {
|
|
14
7
|
value?: string;
|
|
15
8
|
name?: string;
|
|
16
9
|
label?: string;
|
|
17
10
|
placeholder?: string;
|
|
11
|
+
required?: boolean;
|
|
18
12
|
disabled?: boolean;
|
|
19
13
|
rows?: number;
|
|
20
14
|
size?: SizeVariant;
|
|
@@ -28,6 +22,7 @@
|
|
|
28
22
|
name = "",
|
|
29
23
|
label = "",
|
|
30
24
|
placeholder = "",
|
|
25
|
+
required = false,
|
|
31
26
|
disabled = false,
|
|
32
27
|
rows = 4,
|
|
33
28
|
variant = "default",
|
|
@@ -96,16 +91,23 @@
|
|
|
96
91
|
{name}
|
|
97
92
|
{value}
|
|
98
93
|
{placeholder}
|
|
94
|
+
{required}
|
|
99
95
|
{disabled}
|
|
100
96
|
{rows}
|
|
101
97
|
class={textareaClasses()}
|
|
102
98
|
oninput={handleInput}
|
|
103
99
|
aria-invalid={variant === "error" ? "true" : undefined}
|
|
100
|
+
aria-required={required ? "true" : undefined}
|
|
104
101
|
aria-describedby={message ? `${textareaId}-message` : undefined}
|
|
105
102
|
{...restProps}
|
|
106
103
|
></textarea>
|
|
107
104
|
{#if message && variant !== "default"}
|
|
108
|
-
<p
|
|
105
|
+
<p
|
|
106
|
+
id={`${textareaId}-message`}
|
|
107
|
+
class={messageClasses()}
|
|
108
|
+
role={variant === "error" ? "alert" : "status"}
|
|
109
|
+
aria-live={variant === "error" ? "assertive" : "polite"}
|
|
110
|
+
>
|
|
109
111
|
{#if getIcon()}
|
|
110
112
|
{@const Icon = getIcon()}
|
|
111
113
|
<Icon size={14} class="shrink-0" />
|
package/dist/atoms/Toggle.svelte
CHANGED
|
@@ -1,11 +1,5 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
-
|
|
3
|
-
if (typeof window !== "undefined") {
|
|
4
|
-
return `${prefix}-${Math.random().toString(36).substr(2, 9)}`;
|
|
5
|
-
} else {
|
|
6
|
-
return `${prefix}-ssr-${Date.now()}`;
|
|
7
|
-
}
|
|
8
|
-
}
|
|
2
|
+
import { generateId } from "../../routes/lib/ssr-safe.js";
|
|
9
3
|
|
|
10
4
|
interface Props {
|
|
11
5
|
checked?: boolean;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
-
import {
|
|
2
|
+
import { generateId } from "../../routes/lib/ssr-safe.js";
|
|
3
3
|
|
|
4
4
|
interface Props {
|
|
5
5
|
content?: string;
|
|
@@ -17,8 +17,8 @@
|
|
|
17
17
|
...restProps
|
|
18
18
|
}: Props & { children?: any } = $props();
|
|
19
19
|
|
|
20
|
-
|
|
21
|
-
|
|
20
|
+
const triggerId = generateId("tooltip-trigger");
|
|
21
|
+
const tooltipId = generateId("tooltip");
|
|
22
22
|
let isVisible = $state(false);
|
|
23
23
|
let triggerElement: HTMLElement | null = $state(null);
|
|
24
24
|
|
|
@@ -56,17 +56,10 @@
|
|
|
56
56
|
function handleMouseLeave() {
|
|
57
57
|
isVisible = false;
|
|
58
58
|
}
|
|
59
|
-
|
|
60
|
-
onMount(() => {
|
|
61
|
-
if (typeof window !== "undefined") {
|
|
62
|
-
window.addEventListener("keydown", handleKeydown);
|
|
63
|
-
return () => {
|
|
64
|
-
window.removeEventListener("keydown", handleKeydown);
|
|
65
|
-
};
|
|
66
|
-
}
|
|
67
|
-
});
|
|
68
59
|
</script>
|
|
69
60
|
|
|
61
|
+
<svelte:window onkeydown={handleKeydown} />
|
|
62
|
+
|
|
70
63
|
<div
|
|
71
64
|
class="tooltip-container relative inline-block"
|
|
72
65
|
data-placement={placement}
|
|
@@ -249,8 +242,8 @@
|
|
|
249
242
|
|
|
250
243
|
@media (prefers-contrast: high) {
|
|
251
244
|
:root {
|
|
252
|
-
--tooltip-bg:
|
|
253
|
-
--tooltip-color:
|
|
245
|
+
--tooltip-bg: CanvasText;
|
|
246
|
+
--tooltip-color: Canvas;
|
|
254
247
|
--tooltip-padding: 0.75rem 1rem;
|
|
255
248
|
}
|
|
256
249
|
}
|
|
@@ -276,7 +269,7 @@
|
|
|
276
269
|
}
|
|
277
270
|
|
|
278
271
|
.tooltip-container:focus-visible {
|
|
279
|
-
outline: 2px solid var(--color-focus
|
|
272
|
+
outline: 2px solid var(--color-focus);
|
|
280
273
|
outline-offset: 2px;
|
|
281
274
|
border-radius: var(--tooltip-radius);
|
|
282
275
|
}
|
package/dist/atoms/index.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
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';
|
|
@@ -9,5 +10,7 @@ export { default as Input } from './Input.svelte';
|
|
|
9
10
|
export { default as Textarea } from './Textarea.svelte';
|
|
10
11
|
export { default as Select } from './Select.svelte';
|
|
11
12
|
export { default as ColorPicker } from './ColorPicker.svelte';
|
|
13
|
+
export { default as List } from './List.svelte';
|
|
14
|
+
export { default as ListItem } from './ListItem.svelte';
|
|
12
15
|
export { default as Heading } from './Heading.svelte';
|
|
13
16
|
export { default as ThemeToggle } from './ThemeToggle.svelte';
|
package/dist/atoms/index.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
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';
|
|
@@ -9,5 +10,7 @@ export { default as Input } from './Input.svelte';
|
|
|
9
10
|
export { default as Textarea } from './Textarea.svelte';
|
|
10
11
|
export { default as Select } from './Select.svelte';
|
|
11
12
|
export { default as ColorPicker } from './ColorPicker.svelte';
|
|
13
|
+
export { default as List } from './List.svelte';
|
|
14
|
+
export { default as ListItem } from './ListItem.svelte';
|
|
12
15
|
export { default as Heading } from './Heading.svelte';
|
|
13
16
|
export { default as ThemeToggle } from './ThemeToggle.svelte';
|