zabi-components 5.0.22 → 7.0.0
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 +139 -1478
- package/THEME.md +473 -0
- package/THEMING.md +248 -0
- package/dist/atoms/ActionPanel.svelte +116 -0
- package/dist/atoms/ActionPanel.svelte.d.ts +19 -0
- package/dist/atoms/Badge.svelte +1 -1
- package/dist/atoms/Badge.svelte.d.ts +1 -1
- package/dist/atoms/Button.svelte +35 -16
- package/dist/atoms/Button.svelte.d.ts +3 -1
- package/dist/atoms/Card.svelte +49 -67
- package/dist/atoms/Card.svelte.d.ts +4 -4
- package/dist/atoms/CardHeader.svelte +15 -2
- package/dist/atoms/CardHeader.svelte.d.ts +5 -0
- package/dist/atoms/Checkbox.svelte +50 -76
- package/dist/atoms/Checkbox.svelte.d.ts +7 -2
- package/dist/atoms/CodeBlock.svelte +20 -34
- package/dist/atoms/CodeBlock.svelte.d.ts +1 -0
- package/dist/atoms/ColorPicker.svelte +3 -7
- package/dist/atoms/Container.svelte +41 -0
- package/dist/atoms/Container.svelte.d.ts +12 -0
- package/dist/atoms/Divider.svelte +68 -0
- package/dist/atoms/Divider.svelte.d.ts +13 -0
- package/dist/atoms/FeatureCard.svelte +35 -50
- package/dist/atoms/FeatureCard.svelte.d.ts +9 -4
- package/dist/atoms/IconButton.svelte +33 -24
- package/dist/atoms/IconButton.svelte.d.ts +4 -8
- package/dist/atoms/Input.svelte +58 -26
- package/dist/atoms/Input.svelte.d.ts +6 -1
- package/dist/atoms/List.svelte +2 -7
- package/dist/atoms/List.svelte.d.ts +1 -6
- package/dist/atoms/ListItem.svelte +54 -65
- package/dist/atoms/ListItem.svelte.d.ts +7 -13
- package/dist/atoms/ListItemLeading.svelte +19 -0
- package/dist/atoms/ListItemLeading.svelte.d.ts +9 -0
- package/dist/atoms/Progress.svelte +2 -2
- package/dist/atoms/Radio.svelte +51 -0
- package/dist/atoms/Radio.svelte.d.ts +14 -0
- package/dist/atoms/Select.svelte +28 -33
- package/dist/atoms/Select.svelte.d.ts +0 -10
- package/dist/atoms/SelectionControl.svelte +98 -0
- package/dist/atoms/SelectionControl.svelte.d.ts +26 -0
- package/dist/atoms/Skeleton.svelte +39 -15
- package/dist/atoms/Skeleton.svelte.d.ts +7 -3
- package/dist/atoms/Table.svelte +27 -0
- package/dist/atoms/Table.svelte.d.ts +10 -0
- package/dist/atoms/Text.svelte +46 -0
- package/dist/atoms/Text.svelte.d.ts +13 -0
- package/dist/atoms/Textarea.svelte +74 -53
- package/dist/atoms/Textarea.svelte.d.ts +6 -2
- package/dist/atoms/ThemeToggle.svelte +35 -24
- package/dist/atoms/ThemeToggle.svelte.d.ts +1 -0
- package/dist/atoms/Toast.svelte +55 -31
- package/dist/atoms/Toast.svelte.d.ts +4 -1
- package/dist/atoms/Toggle.svelte +37 -22
- package/dist/atoms/Toggle.svelte.d.ts +2 -1
- package/dist/atoms/Tooltip.svelte +90 -11
- package/dist/atoms/index.d.ts +6 -0
- package/dist/atoms/index.js +6 -0
- package/dist/atoms/selection-control.styles.d.ts +25 -0
- package/dist/atoms/selection-control.styles.js +30 -0
- package/dist/components/atoms/index.d.ts +6 -0
- package/dist/components/atoms/index.d.ts.map +1 -1
- package/dist/components/atoms/selection-control.styles.d.ts +26 -0
- package/dist/components/atoms/selection-control.styles.d.ts.map +1 -0
- package/dist/components/index.d.ts +42 -7
- package/dist/components/index.d.ts.map +1 -1
- package/dist/components/molecules/index.d.ts +10 -0
- package/dist/components/molecules/index.d.ts.map +1 -1
- package/dist/components/molecules/navigation-menu-context.d.ts +11 -0
- package/dist/components/molecules/navigation-menu-context.d.ts.map +1 -1
- package/dist/components/molecules/toast-store.d.ts +39 -0
- package/dist/components/molecules/toast-store.d.ts.map +1 -0
- package/dist/components/organisms/index.d.ts +3 -3
- package/dist/components/organisms/index.d.ts.map +1 -1
- package/dist/components/types/page.types.d.ts +69 -0
- package/dist/components/types/page.types.d.ts.map +1 -0
- package/dist/components/types/page.types.js +1 -0
- package/dist/components/types/variants.d.ts +64 -0
- package/dist/components/types/variants.d.ts.map +1 -0
- package/dist/components/types/variants.js +44 -0
- package/dist/components/util/fixed-sidebar-flyout.d.ts +23 -0
- package/dist/components/util/fixed-sidebar-flyout.d.ts.map +1 -0
- package/dist/components/util/focus-utils.d.ts +12 -0
- package/dist/components/util/focus-utils.d.ts.map +1 -0
- package/dist/components/util/ssr-safe.d.ts +17 -0
- package/dist/components/util/ssr-safe.d.ts.map +1 -0
- package/dist/index.d.ts +42 -7
- package/dist/index.js +45 -10
- package/dist/lib/layout-width-tokens.d.ts +17 -0
- package/dist/lib/layout-width-tokens.d.ts.map +1 -0
- package/dist/lib/showcase/component-docs/Button.d.ts +3 -0
- package/dist/lib/showcase/component-docs/Button.d.ts.map +1 -0
- package/dist/lib/showcase/component-docs/Input.d.ts +3 -0
- package/dist/lib/showcase/component-docs/Input.d.ts.map +1 -0
- package/dist/lib/showcase/component-docs/List.d.ts +3 -0
- package/dist/lib/showcase/component-docs/List.d.ts.map +1 -0
- package/dist/lib/showcase/component-docs/Modal.d.ts +3 -0
- package/dist/lib/showcase/component-docs/Modal.d.ts.map +1 -0
- package/dist/lib/showcase/component-docs/Radio.d.ts +3 -0
- package/dist/lib/showcase/component-docs/Radio.d.ts.map +1 -0
- package/dist/lib/showcase/component-docs/SidebarNavigation.d.ts +3 -0
- package/dist/lib/showcase/component-docs/SidebarNavigation.d.ts.map +1 -0
- package/dist/lib/showcase/component-docs/Skeleton.d.ts +3 -0
- package/dist/lib/showcase/component-docs/Skeleton.d.ts.map +1 -0
- package/dist/lib/showcase/component-docs/_shared.d.ts +12 -0
- package/dist/lib/showcase/component-docs/_shared.d.ts.map +1 -0
- package/dist/lib/showcase/component-docs/index.d.ts +4 -0
- package/dist/lib/showcase/component-docs/index.d.ts.map +1 -0
- package/dist/lib/showcase/components-catalog.d.ts +3 -0
- package/dist/lib/showcase/components-catalog.d.ts.map +1 -0
- package/dist/lib/showcase/components-showcase-constants.d.ts +89 -0
- package/dist/lib/showcase/components-showcase-constants.d.ts.map +1 -0
- package/dist/lib/showcase/docs-sidebar-helpers.d.ts +15 -0
- package/dist/lib/showcase/docs-sidebar-helpers.d.ts.map +1 -0
- package/dist/molecules/Alert.svelte +50 -23
- package/dist/molecules/Alert.svelte.d.ts +1 -1
- package/dist/molecules/ComponentDemo.svelte +54 -51
- package/dist/molecules/ContactForm.svelte +70 -65
- package/dist/molecules/Dropdown.svelte +99 -108
- package/dist/molecules/Dropdown.svelte.d.ts +12 -6
- package/dist/molecules/EmptyState.svelte +44 -0
- package/dist/molecules/EmptyState.svelte.d.ts +11 -0
- package/dist/molecules/FormField.svelte +89 -0
- package/dist/molecules/FormField.svelte.d.ts +23 -0
- package/dist/molecules/Header.svelte +41 -0
- package/dist/molecules/Header.svelte.d.ts +10 -0
- package/dist/molecules/ImageUpload.svelte +1 -1
- package/dist/molecules/Modal.svelte +39 -26
- package/dist/molecules/Modal.svelte.d.ts +4 -2
- package/dist/molecules/NavigationMenu.svelte +16 -0
- package/dist/molecules/NavigationMenu.svelte.d.ts +2 -0
- package/dist/molecules/NavigationMenuContent.svelte +9 -1
- package/dist/molecules/NavigationMenuLink.svelte +1 -1
- package/dist/molecules/NavigationMenuTrigger.svelte +9 -4
- package/dist/molecules/Page.svelte +16 -0
- package/dist/molecules/Page.svelte.d.ts +8 -0
- package/dist/molecules/PropsTable.svelte +61 -0
- package/dist/molecules/PropsTable.svelte.d.ts +8 -0
- package/dist/molecules/RadioGroup.svelte +197 -0
- package/dist/molecules/RadioGroup.svelte.d.ts +20 -0
- package/dist/molecules/Section.svelte +14 -23
- package/dist/molecules/Section.svelte.d.ts +2 -11
- package/dist/molecules/SidebarBrandHeader.svelte +68 -0
- package/dist/molecules/SidebarBrandHeader.svelte.d.ts +10 -0
- package/dist/molecules/SidebarFooter.svelte +119 -0
- package/dist/molecules/SidebarFooter.svelte.d.ts +23 -0
- package/dist/molecules/SidebarNavSection.svelte +49 -0
- package/dist/molecules/SidebarNavSection.svelte.d.ts +14 -0
- package/dist/molecules/SlideUp.svelte +21 -16
- package/dist/molecules/SlideUp.svelte.d.ts +1 -1
- package/dist/molecules/Tabs.svelte +6 -11
- package/dist/molecules/Toaster.svelte +21 -0
- package/dist/molecules/Toaster.svelte.d.ts +6 -0
- package/dist/molecules/ToasterToast.svelte +217 -0
- package/dist/molecules/ToasterToast.svelte.d.ts +7 -0
- package/dist/molecules/index.d.ts +10 -0
- package/dist/molecules/index.js +10 -0
- package/dist/molecules/navigation-menu-context.d.ts +11 -0
- package/dist/molecules/navigation-menu-context.js +18 -0
- package/dist/molecules/toast-store.d.ts +38 -0
- package/dist/molecules/toast-store.js +34 -0
- package/dist/organisms/SidebarAccountPanel.svelte +118 -0
- package/dist/organisms/SidebarAccountPanel.svelte.d.ts +20 -0
- package/dist/organisms/SidebarNavigation.svelte +286 -212
- package/dist/organisms/SidebarNavigation.svelte.d.ts +22 -1
- package/dist/organisms/{SidebarProjectPanel.svelte → SidebarPanel.svelte} +67 -34
- package/dist/organisms/{SidebarProjectPanel.svelte.d.ts → SidebarPanel.svelte.d.ts} +9 -6
- package/dist/organisms/TopNavbar.svelte +214 -0
- package/dist/organisms/TopNavbar.svelte.d.ts +35 -0
- package/dist/organisms/index.d.ts +3 -3
- package/dist/organisms/index.js +3 -3
- package/dist/routes/lib/focus-utils.d.ts +7 -18
- package/dist/routes/lib/focus-utils.ts +32 -34
- package/dist/routes/lib/ssr-safe.ts +5 -4
- package/dist/routes/lib/variant-utils.ts +48 -39
- package/dist/types/components.d.ts +187 -0
- package/dist/types/components.d.ts.map +1 -0
- package/dist/types/events.d.ts +200 -0
- package/dist/types/events.d.ts.map +1 -0
- package/dist/types/index.d.ts +171 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/page.types.d.ts +3 -0
- package/dist/types/page.types.d.ts.map +1 -0
- package/dist/types/page.types.js +1 -0
- package/dist/types/page.types.ts +79 -0
- package/dist/types/variants.d.ts +3 -0
- package/dist/types/variants.d.ts.map +1 -0
- package/dist/types/variants.js +1 -0
- package/dist/types/variants.ts +75 -0
- package/dist/util/fixed-sidebar-flyout.d.ts +22 -0
- package/dist/util/fixed-sidebar-flyout.js +148 -0
- package/dist/util/focus-utils.d.ts +11 -0
- package/dist/util/focus-utils.js +94 -0
- package/dist/util/ssr-safe.d.ts +16 -0
- package/dist/util/ssr-safe.js +48 -0
- package/dist/zabi-components-colors.css +360 -324
- package/dist/zabi-components-theme-dark-only.css +164 -151
- package/dist/zabi-components-theme-dark.css +164 -151
- package/dist/zabi-components-theme-only.css +253 -230
- package/dist/zabi-components-theme.css +253 -230
- package/dist/zabi-components.css +1262 -241
- package/docs/theme-imports.md +112 -0
- package/package.json +32 -10
- package/dist/organisms/Navbar.svelte +0 -92
- package/dist/organisms/Navbar.svelte.d.ts +0 -14
- package/dist/organisms/Navigation.svelte +0 -119
- package/dist/organisms/Navigation.svelte.d.ts +0 -21
|
@@ -1,11 +1,19 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
-
import
|
|
3
|
-
import {
|
|
2
|
+
import { generateId } from "../util/ssr-safe.js";
|
|
3
|
+
import type { Snippet } from 'svelte';
|
|
4
|
+
|
|
5
|
+
export type DropdownTriggerProps = {
|
|
6
|
+
'aria-expanded': boolean;
|
|
7
|
+
'aria-haspopup': 'menu' | 'listbox';
|
|
8
|
+
'aria-controls': string;
|
|
9
|
+
};
|
|
4
10
|
|
|
5
11
|
interface Props {
|
|
6
12
|
isOpen?: boolean;
|
|
7
|
-
placement?:
|
|
13
|
+
placement?: 'bottom-start' | 'bottom-end' | 'top-start' | 'top-end';
|
|
8
14
|
ariaLabel?: string;
|
|
15
|
+
/** `listbox` for Select-style; `menu` for actions. */
|
|
16
|
+
menuRole?: 'menu' | 'listbox';
|
|
9
17
|
selectedValue?: string | number | null;
|
|
10
18
|
options?: Array<{
|
|
11
19
|
value: string | number;
|
|
@@ -13,31 +21,40 @@
|
|
|
13
21
|
disabled?: boolean;
|
|
14
22
|
}>;
|
|
15
23
|
onOptionClick?: (value: string | number) => void;
|
|
24
|
+
trigger: Snippet<[DropdownTriggerProps]>;
|
|
25
|
+
children?: Snippet;
|
|
16
26
|
}
|
|
17
27
|
|
|
18
28
|
let {
|
|
19
|
-
isOpen = false,
|
|
20
|
-
placement =
|
|
21
|
-
ariaLabel =
|
|
29
|
+
isOpen = $bindable(false),
|
|
30
|
+
placement = 'bottom-start',
|
|
31
|
+
ariaLabel = 'Menu',
|
|
32
|
+
menuRole = 'menu',
|
|
22
33
|
selectedValue = null,
|
|
23
34
|
options = [],
|
|
24
35
|
onOptionClick,
|
|
25
|
-
children,
|
|
26
36
|
trigger,
|
|
37
|
+
children,
|
|
27
38
|
...restProps
|
|
28
|
-
}: Props
|
|
39
|
+
}: Props = $props();
|
|
40
|
+
|
|
41
|
+
const menuId = generateId('dropdown-menu');
|
|
42
|
+
let rootEl = $state<HTMLDivElement | null>(null);
|
|
29
43
|
|
|
30
|
-
const dropdownId = generateId("dropdown");
|
|
31
|
-
let triggerElement = $state<HTMLElement | null>(null);
|
|
32
|
-
let menuElement = $state<HTMLElement | null>(null);
|
|
33
44
|
let openedViaKeyboard = $state(false);
|
|
34
45
|
|
|
46
|
+
const triggerAria = $derived({
|
|
47
|
+
'aria-expanded': isOpen,
|
|
48
|
+
'aria-haspopup': menuRole === 'listbox' ? 'listbox' as const : 'menu' as const,
|
|
49
|
+
'aria-controls': menuId,
|
|
50
|
+
} satisfies DropdownTriggerProps);
|
|
51
|
+
|
|
35
52
|
function handleKeydown(event: KeyboardEvent) {
|
|
36
53
|
if (!isOpen) {
|
|
37
54
|
if (
|
|
38
|
-
event.key ===
|
|
39
|
-
event.key ===
|
|
40
|
-
event.key ===
|
|
55
|
+
event.key === 'Enter' ||
|
|
56
|
+
event.key === ' ' ||
|
|
57
|
+
event.key === 'ArrowDown'
|
|
41
58
|
) {
|
|
42
59
|
event.preventDefault();
|
|
43
60
|
openedViaKeyboard = true;
|
|
@@ -47,38 +64,39 @@
|
|
|
47
64
|
}
|
|
48
65
|
|
|
49
66
|
switch (event.key) {
|
|
50
|
-
case
|
|
67
|
+
case 'Escape':
|
|
51
68
|
event.preventDefault();
|
|
52
69
|
isOpen = false;
|
|
53
|
-
triggerElement?.focus();
|
|
54
70
|
break;
|
|
55
|
-
case
|
|
71
|
+
case 'ArrowDown':
|
|
56
72
|
event.preventDefault();
|
|
57
73
|
focusNextItem();
|
|
58
74
|
break;
|
|
59
|
-
case
|
|
75
|
+
case 'ArrowUp':
|
|
60
76
|
event.preventDefault();
|
|
61
77
|
focusPreviousItem();
|
|
62
78
|
break;
|
|
63
|
-
case
|
|
79
|
+
case 'Home':
|
|
64
80
|
event.preventDefault();
|
|
65
81
|
focusFirstItem();
|
|
66
82
|
break;
|
|
67
|
-
case
|
|
83
|
+
case 'End':
|
|
68
84
|
event.preventDefault();
|
|
69
85
|
focusLastItem();
|
|
70
86
|
break;
|
|
71
|
-
case
|
|
87
|
+
case 'Tab':
|
|
72
88
|
isOpen = false;
|
|
73
89
|
break;
|
|
74
90
|
}
|
|
75
91
|
}
|
|
76
92
|
|
|
93
|
+
let menuElement = $state<HTMLElement | null>(null);
|
|
94
|
+
|
|
77
95
|
function getMenuItems(): HTMLElement[] {
|
|
78
96
|
if (!menuElement) return [];
|
|
79
97
|
return Array.from(
|
|
80
98
|
menuElement.querySelectorAll<HTMLElement>(
|
|
81
|
-
'[role="menuitem"],
|
|
99
|
+
'[role="menuitem"], [role="option"]',
|
|
82
100
|
),
|
|
83
101
|
);
|
|
84
102
|
}
|
|
@@ -104,8 +122,7 @@
|
|
|
104
122
|
}
|
|
105
123
|
|
|
106
124
|
function focusFirstItem() {
|
|
107
|
-
|
|
108
|
-
items[0]?.focus();
|
|
125
|
+
getMenuItems()[0]?.focus();
|
|
109
126
|
}
|
|
110
127
|
|
|
111
128
|
function focusLastItem() {
|
|
@@ -113,126 +130,107 @@
|
|
|
113
130
|
items[items.length - 1]?.focus();
|
|
114
131
|
}
|
|
115
132
|
|
|
116
|
-
// Focus first item when dropdown opens via keyboard
|
|
117
133
|
$effect(() => {
|
|
118
134
|
if (isOpen && menuElement && openedViaKeyboard) {
|
|
119
|
-
setTimeout(() => {
|
|
135
|
+
const t = setTimeout(() => {
|
|
120
136
|
focusFirstItem();
|
|
121
137
|
openedViaKeyboard = false;
|
|
122
138
|
}, 0);
|
|
123
|
-
|
|
139
|
+
return () => clearTimeout(t);
|
|
140
|
+
}
|
|
141
|
+
if (!isOpen) {
|
|
124
142
|
openedViaKeyboard = false;
|
|
125
143
|
}
|
|
126
144
|
});
|
|
127
145
|
|
|
128
|
-
// Mark selected items
|
|
129
146
|
$effect(() => {
|
|
130
|
-
if (isOpen
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
const itemValue =
|
|
137
|
-
item.getAttribute("data-value") ||
|
|
138
|
-
item.textContent?.trim();
|
|
139
|
-
if (String(itemValue) === String(selectedValue)) {
|
|
140
|
-
item.setAttribute("data-selected", "true");
|
|
141
|
-
item.setAttribute("aria-selected", "true");
|
|
142
|
-
} else {
|
|
143
|
-
item.removeAttribute("data-selected");
|
|
144
|
-
item.removeAttribute("aria-selected");
|
|
145
|
-
}
|
|
146
|
-
} else {
|
|
147
|
-
item.removeAttribute("data-selected");
|
|
148
|
-
item.removeAttribute("aria-selected");
|
|
149
|
-
}
|
|
150
|
-
});
|
|
151
|
-
}, 0);
|
|
147
|
+
if (!isOpen) return;
|
|
148
|
+
function onDocMouseDown(e: MouseEvent) {
|
|
149
|
+
const t = e.target as Node;
|
|
150
|
+
if (rootEl && !rootEl.contains(t)) {
|
|
151
|
+
isOpen = false;
|
|
152
|
+
}
|
|
152
153
|
}
|
|
154
|
+
document.addEventListener('mousedown', onDocMouseDown);
|
|
155
|
+
return () => document.removeEventListener('mousedown', onDocMouseDown);
|
|
153
156
|
});
|
|
154
157
|
|
|
155
158
|
const placementClasses = $derived(() => {
|
|
156
|
-
const base =
|
|
157
|
-
const positioning = {
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
159
|
+
const base = 'absolute z-dropdown min-w-[12rem]';
|
|
160
|
+
const positioning: Record<typeof placement, string> = {
|
|
161
|
+
'bottom-start': 'top-full left-0 mt-2',
|
|
162
|
+
'bottom-end': 'top-full right-0 mt-2',
|
|
163
|
+
'top-start': 'bottom-full left-0 mb-2',
|
|
164
|
+
'top-end': 'bottom-full right-0 mb-2',
|
|
162
165
|
};
|
|
163
166
|
return `${base} ${positioning[placement]}`;
|
|
164
167
|
});
|
|
165
168
|
|
|
166
169
|
const transformClasses = $derived(() => {
|
|
167
170
|
if (!isOpen) {
|
|
168
|
-
const hiddenTransform = {
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
171
|
+
const hiddenTransform: Record<typeof placement, string> = {
|
|
172
|
+
'bottom-start': 'translate-y-1',
|
|
173
|
+
'bottom-end': 'translate-y-1',
|
|
174
|
+
'top-start': '-translate-y-1',
|
|
175
|
+
'top-end': '-translate-y-1',
|
|
173
176
|
};
|
|
174
|
-
return `opacity-0
|
|
177
|
+
return `invisible opacity-0 ${hiddenTransform[placement]}`;
|
|
175
178
|
}
|
|
176
|
-
return
|
|
179
|
+
return 'visible translate-y-0 opacity-100';
|
|
177
180
|
});
|
|
178
181
|
|
|
179
182
|
const dropdownContentClasses = $derived(() => {
|
|
180
|
-
return
|
|
181
|
-
|
|
182
|
-
bg-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
duration-200
|
|
189
|
-
ease-in-out
|
|
190
|
-
${transformClasses()}
|
|
191
|
-
`
|
|
192
|
-
.trim()
|
|
193
|
-
.replace(/\s+/g, " ");
|
|
183
|
+
return [
|
|
184
|
+
placementClasses(),
|
|
185
|
+
'rounded-lg border border-border bg-card py-2 shadow-lg transition-all duration-200 ease-in-out',
|
|
186
|
+
transformClasses(),
|
|
187
|
+
]
|
|
188
|
+
.join(' ')
|
|
189
|
+
.replace(/\s+/g, ' ')
|
|
190
|
+
.trim();
|
|
194
191
|
});
|
|
192
|
+
|
|
193
|
+
const itemRole = $derived(menuRole === 'listbox' ? 'option' : 'menuitem');
|
|
194
|
+
|
|
195
|
+
const optionClasses =
|
|
196
|
+
'focus-ring flex w-full items-center justify-start rounded-md px-3 py-2 text-left text-sm text-body transition-colors hover:bg-base-100 focus:outline-none disabled:cursor-not-allowed disabled:opacity-50';
|
|
195
197
|
</script>
|
|
196
198
|
|
|
197
199
|
<div
|
|
200
|
+
bind:this={rootEl}
|
|
198
201
|
class="relative inline-block"
|
|
199
202
|
data-placement={placement}
|
|
200
203
|
onkeydown={handleKeydown}
|
|
201
204
|
{...restProps}
|
|
202
205
|
>
|
|
203
|
-
|
|
204
|
-
{@render trigger?.()}
|
|
205
|
-
</div>
|
|
206
|
+
{@render trigger(triggerAria)}
|
|
206
207
|
|
|
207
208
|
{#if isOpen}
|
|
208
209
|
<div
|
|
209
210
|
bind:this={menuElement}
|
|
210
|
-
id={
|
|
211
|
+
id={menuId}
|
|
211
212
|
class={dropdownContentClasses()}
|
|
212
|
-
role=
|
|
213
|
+
role={menuRole === 'listbox' ? 'listbox' : 'menu'}
|
|
213
214
|
aria-label={ariaLabel}
|
|
214
|
-
tabindex="-1"
|
|
215
215
|
>
|
|
216
216
|
{#if options.length > 0}
|
|
217
|
-
<div class="px-2 py-
|
|
217
|
+
<div class="px-2 py-1">
|
|
218
218
|
{#each options as option (option.value)}
|
|
219
|
-
|
|
220
|
-
"
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
</Button>
|
|
235
|
-
</div>
|
|
219
|
+
<button
|
|
220
|
+
type="button"
|
|
221
|
+
role={itemRole}
|
|
222
|
+
aria-selected={menuRole === 'listbox' &&
|
|
223
|
+
selectedValue !== null &&
|
|
224
|
+
String(selectedValue) === String(option.value)
|
|
225
|
+
? true
|
|
226
|
+
: undefined}
|
|
227
|
+
data-value={String(option.value)}
|
|
228
|
+
disabled={option.disabled}
|
|
229
|
+
class={optionClasses}
|
|
230
|
+
onclick={() => onOptionClick?.(option.value)}
|
|
231
|
+
>
|
|
232
|
+
{option.label}
|
|
233
|
+
</button>
|
|
236
234
|
{/each}
|
|
237
235
|
</div>
|
|
238
236
|
{:else if children}
|
|
@@ -241,10 +239,3 @@
|
|
|
241
239
|
</div>
|
|
242
240
|
{/if}
|
|
243
241
|
</div>
|
|
244
|
-
|
|
245
|
-
<style>
|
|
246
|
-
:global([role="menu"] button) {
|
|
247
|
-
justify-content: flex-start;
|
|
248
|
-
text-align: left;
|
|
249
|
-
}
|
|
250
|
-
</style>
|
|
@@ -1,7 +1,15 @@
|
|
|
1
|
+
import type { Snippet } from 'svelte';
|
|
2
|
+
export type DropdownTriggerProps = {
|
|
3
|
+
'aria-expanded': boolean;
|
|
4
|
+
'aria-haspopup': 'menu' | 'listbox';
|
|
5
|
+
'aria-controls': string;
|
|
6
|
+
};
|
|
1
7
|
interface Props {
|
|
2
8
|
isOpen?: boolean;
|
|
3
|
-
placement?:
|
|
9
|
+
placement?: 'bottom-start' | 'bottom-end' | 'top-start' | 'top-end';
|
|
4
10
|
ariaLabel?: string;
|
|
11
|
+
/** `listbox` for Select-style; `menu` for actions. */
|
|
12
|
+
menuRole?: 'menu' | 'listbox';
|
|
5
13
|
selectedValue?: string | number | null;
|
|
6
14
|
options?: Array<{
|
|
7
15
|
value: string | number;
|
|
@@ -9,11 +17,9 @@ interface Props {
|
|
|
9
17
|
disabled?: boolean;
|
|
10
18
|
}>;
|
|
11
19
|
onOptionClick?: (value: string | number) => void;
|
|
20
|
+
trigger: Snippet<[DropdownTriggerProps]>;
|
|
21
|
+
children?: Snippet;
|
|
12
22
|
}
|
|
13
|
-
|
|
14
|
-
children?: any;
|
|
15
|
-
trigger?: any;
|
|
16
|
-
};
|
|
17
|
-
declare const Dropdown: import("svelte").Component<$$ComponentProps, {}, "">;
|
|
23
|
+
declare const Dropdown: import("svelte").Component<Props, {}, "isOpen">;
|
|
18
24
|
type Dropdown = ReturnType<typeof Dropdown>;
|
|
19
25
|
export default Dropdown;
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { Snippet } from "svelte";
|
|
3
|
+
import { generateId } from "../util/ssr-safe.js";
|
|
4
|
+
|
|
5
|
+
interface Props {
|
|
6
|
+
title: string;
|
|
7
|
+
description: string;
|
|
8
|
+
class?: string;
|
|
9
|
+
action?: Snippet;
|
|
10
|
+
media?: Snippet;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
let {
|
|
14
|
+
title,
|
|
15
|
+
description,
|
|
16
|
+
class: className = "",
|
|
17
|
+
action,
|
|
18
|
+
media,
|
|
19
|
+
}: Props = $props();
|
|
20
|
+
|
|
21
|
+
const titleId = generateId("empty-state-title");
|
|
22
|
+
</script>
|
|
23
|
+
|
|
24
|
+
<section
|
|
25
|
+
class="mx-auto flex flex-col items-center justify-center gap-4 px-6 py-12 text-center {className}"
|
|
26
|
+
aria-labelledby={titleId}
|
|
27
|
+
>
|
|
28
|
+
{#if media}
|
|
29
|
+
<div class="text-description" aria-hidden="true">
|
|
30
|
+
{@render media()}
|
|
31
|
+
</div>
|
|
32
|
+
{/if}
|
|
33
|
+
<div class="space-y-2">
|
|
34
|
+
<h2 id={titleId} class="text-lg font-semibold text-headline">
|
|
35
|
+
{title}
|
|
36
|
+
</h2>
|
|
37
|
+
<p class="text-sm text-description">{description}</p>
|
|
38
|
+
</div>
|
|
39
|
+
{#if action}
|
|
40
|
+
<div class="mt-2 flex flex-wrap items-center justify-center gap-2">
|
|
41
|
+
{@render action()}
|
|
42
|
+
</div>
|
|
43
|
+
{/if}
|
|
44
|
+
</section>
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { Snippet } from "svelte";
|
|
2
|
+
interface Props {
|
|
3
|
+
title: string;
|
|
4
|
+
description: string;
|
|
5
|
+
class?: string;
|
|
6
|
+
action?: Snippet;
|
|
7
|
+
media?: Snippet;
|
|
8
|
+
}
|
|
9
|
+
declare const EmptyState: import("svelte").Component<Props, {}, "">;
|
|
10
|
+
type EmptyState = ReturnType<typeof EmptyState>;
|
|
11
|
+
export default EmptyState;
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { Snippet } from 'svelte';
|
|
3
|
+
import { generateId } from "../util/ssr-safe.js";
|
|
4
|
+
|
|
5
|
+
export type FormFieldControlProps = {
|
|
6
|
+
id: string;
|
|
7
|
+
'aria-describedby'?: string;
|
|
8
|
+
'aria-invalid'?: 'true';
|
|
9
|
+
disabled?: boolean;
|
|
10
|
+
required?: boolean;
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
interface Props {
|
|
14
|
+
id?: string;
|
|
15
|
+
label: string;
|
|
16
|
+
description?: string;
|
|
17
|
+
error?: string;
|
|
18
|
+
required?: boolean;
|
|
19
|
+
disabled?: boolean;
|
|
20
|
+
class?: string;
|
|
21
|
+
labelClass?: string;
|
|
22
|
+
control: Snippet<[FormFieldControlProps]>;
|
|
23
|
+
meta?: Snippet;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
let {
|
|
27
|
+
id: idProp,
|
|
28
|
+
label,
|
|
29
|
+
description,
|
|
30
|
+
error,
|
|
31
|
+
required = false,
|
|
32
|
+
disabled = false,
|
|
33
|
+
class: className = '',
|
|
34
|
+
labelClass = '',
|
|
35
|
+
control,
|
|
36
|
+
meta,
|
|
37
|
+
}: Props = $props();
|
|
38
|
+
|
|
39
|
+
const fallbackId = generateId('field');
|
|
40
|
+
const fieldId = $derived(idProp ?? fallbackId);
|
|
41
|
+
const descriptionId = $derived(
|
|
42
|
+
description ? `${fieldId}-description` : undefined,
|
|
43
|
+
);
|
|
44
|
+
const errorId = $derived(error ? `${fieldId}-error` : undefined);
|
|
45
|
+
const describedBy = $derived(
|
|
46
|
+
[descriptionId, errorId].filter(Boolean).join(' ') || undefined,
|
|
47
|
+
);
|
|
48
|
+
</script>
|
|
49
|
+
|
|
50
|
+
<div class={['space-y-1.5', className].filter(Boolean).join(' ')}>
|
|
51
|
+
<div class="flex items-baseline justify-between gap-3">
|
|
52
|
+
<label
|
|
53
|
+
class={[
|
|
54
|
+
'text-sm font-medium text-label',
|
|
55
|
+
disabled ? 'opacity-60' : '',
|
|
56
|
+
labelClass,
|
|
57
|
+
]
|
|
58
|
+
.filter(Boolean)
|
|
59
|
+
.join(' ')}
|
|
60
|
+
for={fieldId}
|
|
61
|
+
>
|
|
62
|
+
{label}
|
|
63
|
+
{#if required}
|
|
64
|
+
<span class="ml-1 text-error" aria-hidden="true">*</span>
|
|
65
|
+
<span class="sr-only">(required)</span>
|
|
66
|
+
{/if}
|
|
67
|
+
</label>
|
|
68
|
+
|
|
69
|
+
{#if meta}
|
|
70
|
+
<div class="text-xs text-description">{@render meta()}</div>
|
|
71
|
+
{/if}
|
|
72
|
+
</div>
|
|
73
|
+
|
|
74
|
+
{@render control({
|
|
75
|
+
id: fieldId,
|
|
76
|
+
'aria-describedby': describedBy,
|
|
77
|
+
'aria-invalid': error ? 'true' : undefined,
|
|
78
|
+
disabled,
|
|
79
|
+
required,
|
|
80
|
+
})}
|
|
81
|
+
|
|
82
|
+
{#if description}
|
|
83
|
+
<p id={descriptionId} class="text-xs text-description">{description}</p>
|
|
84
|
+
{/if}
|
|
85
|
+
|
|
86
|
+
{#if error}
|
|
87
|
+
<p id={errorId} class="text-xs text-error" role="alert">{error}</p>
|
|
88
|
+
{/if}
|
|
89
|
+
</div>
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { Snippet } from 'svelte';
|
|
2
|
+
export type FormFieldControlProps = {
|
|
3
|
+
id: string;
|
|
4
|
+
'aria-describedby'?: string;
|
|
5
|
+
'aria-invalid'?: 'true';
|
|
6
|
+
disabled?: boolean;
|
|
7
|
+
required?: boolean;
|
|
8
|
+
};
|
|
9
|
+
interface Props {
|
|
10
|
+
id?: string;
|
|
11
|
+
label: string;
|
|
12
|
+
description?: string;
|
|
13
|
+
error?: string;
|
|
14
|
+
required?: boolean;
|
|
15
|
+
disabled?: boolean;
|
|
16
|
+
class?: string;
|
|
17
|
+
labelClass?: string;
|
|
18
|
+
control: Snippet<[FormFieldControlProps]>;
|
|
19
|
+
meta?: Snippet;
|
|
20
|
+
}
|
|
21
|
+
declare const FormField: import("svelte").Component<Props, {}, "">;
|
|
22
|
+
type FormField = ReturnType<typeof FormField>;
|
|
23
|
+
export default FormField;
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import Badge from "../atoms/Badge.svelte";
|
|
3
|
+
import Heading from "../atoms/Heading.svelte";
|
|
4
|
+
import type { ComponentCategory } from "../types/page.types";
|
|
5
|
+
|
|
6
|
+
interface Props {
|
|
7
|
+
title: string;
|
|
8
|
+
description: string;
|
|
9
|
+
category: ComponentCategory;
|
|
10
|
+
variantsStates?: string[];
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
let { title, description, category, variantsStates }: Props = $props();
|
|
14
|
+
</script>
|
|
15
|
+
|
|
16
|
+
<header class="space-y-4">
|
|
17
|
+
<div class="space-y-2">
|
|
18
|
+
<Heading level={1} text={title} />
|
|
19
|
+
<p class="text-lg whitespace-pre-line text-description">{description}</p>
|
|
20
|
+
</div>
|
|
21
|
+
|
|
22
|
+
<div class="flex flex-wrap items-center gap-2">
|
|
23
|
+
<Badge variant="info" text={category} />
|
|
24
|
+
{#if variantsStates?.length}
|
|
25
|
+
<Badge variant="info" text="{variantsStates.length} variants" />
|
|
26
|
+
{/if}
|
|
27
|
+
</div>
|
|
28
|
+
|
|
29
|
+
{#if variantsStates?.length}
|
|
30
|
+
<div class="flex flex-wrap items-center gap-2">
|
|
31
|
+
<span class="text-sm font-medium text-headline">
|
|
32
|
+
Variants / states:
|
|
33
|
+
</span>
|
|
34
|
+
{#each variantsStates as v (v)}
|
|
35
|
+
<span class="rounded-md bg-base-100 px-2 py-1 text-xs text-headline">
|
|
36
|
+
{v}
|
|
37
|
+
</span>
|
|
38
|
+
{/each}
|
|
39
|
+
</div>
|
|
40
|
+
{/if}
|
|
41
|
+
</header>
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { ComponentCategory } from "../types/page.types";
|
|
2
|
+
interface Props {
|
|
3
|
+
title: string;
|
|
4
|
+
description: string;
|
|
5
|
+
category: ComponentCategory;
|
|
6
|
+
variantsStates?: string[];
|
|
7
|
+
}
|
|
8
|
+
declare const Header: import("svelte").Component<Props, {}, "">;
|
|
9
|
+
type Header = ReturnType<typeof Header>;
|
|
10
|
+
export default Header;
|
|
@@ -75,7 +75,7 @@
|
|
|
75
75
|
class="w-full h-32 min-w-64 object-cover rounded-2xl border-0"
|
|
76
76
|
/>
|
|
77
77
|
<div
|
|
78
|
-
class="absolute inset-0 bg-input min-w-64 opacity-0 group-hover:opacity-100 transition-opacity rounded-2xl flex items-center justify-center"
|
|
78
|
+
class="absolute inset-0 border border-input-border bg-input min-w-64 opacity-0 group-hover:opacity-100 transition-opacity rounded-2xl flex items-center justify-center"
|
|
79
79
|
>
|
|
80
80
|
<div class="flex gap-2">
|
|
81
81
|
<Button
|