zabi-components 6.0.0 → 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 +137 -1473
- 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 +12 -3
- package/dist/atoms/Card.svelte.d.ts +2 -2
- package/dist/atoms/CardHeader.svelte +8 -4
- package/dist/atoms/CardHeader.svelte.d.ts +4 -2
- 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 +2 -6
- 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 +1 -1
- package/dist/atoms/Radio.svelte +51 -0
- package/dist/atoms/Radio.svelte.d.ts +14 -0
- package/dist/atoms/Select.svelte +23 -30
- 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 +41 -5
- 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 +2 -1
- 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 +41 -5
- package/dist/index.js +44 -8
- 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.map +1 -1
- package/dist/lib/showcase/components-showcase-constants.d.ts +26 -0
- package/dist/lib/showcase/components-showcase-constants.d.ts.map +1 -1
- package/dist/molecules/Alert.svelte +50 -23
- package/dist/molecules/Alert.svelte.d.ts +1 -1
- package/dist/molecules/ComponentDemo.svelte +2 -2
- package/dist/molecules/ContactForm.svelte +1 -1
- package/dist/molecules/Dropdown.svelte +99 -109
- 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/Modal.svelte +37 -24
- 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 -1
- 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 +2 -11
- 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 +19 -14
- package/dist/molecules/SlideUp.svelte.d.ts +1 -1
- package/dist/molecules/Tabs.svelte +4 -9
- 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 +267 -237
- package/dist/organisms/SidebarNavigation.svelte.d.ts +21 -5
- package/dist/organisms/SidebarPanel.svelte +54 -22
- package/dist/organisms/SidebarPanel.svelte.d.ts +3 -0
- package/dist/organisms/{Navbar.svelte → TopNavbar.svelte} +22 -17
- package/dist/organisms/TopNavbar.svelte.d.ts +35 -0
- package/dist/organisms/index.d.ts +2 -1
- package/dist/organisms/index.js +2 -1
- 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 +2 -31
- package/dist/types/page.types.d.ts.map +1 -1
- 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 +5 -6
- 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 +14 -1
- package/dist/zabi-components-theme-dark-only.css +5 -0
- package/dist/zabi-components-theme-dark.css +5 -0
- package/dist/zabi-components-theme-only.css +9 -1
- package/dist/zabi-components-theme.css +9 -1
- package/dist/zabi-components.css +877 -192
- package/package.json +19 -7
- package/dist/organisms/Navbar.svelte.d.ts +0 -41
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
-
import type { ExtendedSemanticVariant } from "
|
|
2
|
+
import type { ExtendedSemanticVariant } from "../types/variants.js";
|
|
3
3
|
type AlertVisualVariant = Exclude<ExtendedSemanticVariant, "default">;
|
|
4
4
|
|
|
5
5
|
interface Props {
|
|
@@ -44,28 +44,55 @@
|
|
|
44
44
|
let alertRole = $derived(
|
|
45
45
|
variant === "success" || variant === "info" ? "status" : "alert",
|
|
46
46
|
);
|
|
47
|
+
</script>
|
|
47
48
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
<path
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
49
|
+
{#snippet icon()}
|
|
50
|
+
{#if visualVariant === "info"}
|
|
51
|
+
<svg class="h-5 w-5" fill="currentColor" viewBox="0 0 20 20">
|
|
52
|
+
<path
|
|
53
|
+
fill-rule="evenodd"
|
|
54
|
+
d="M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-7-4a1 1 0 11-2 0 1 1 0 012 0zM9 9a1 1 0 000 2v3a1 1 0 001 1h1a1 1 0 100-2v-3a1 1 0 00-1-1H9z"
|
|
55
|
+
clip-rule="evenodd"
|
|
56
|
+
/>
|
|
57
|
+
</svg>
|
|
58
|
+
{:else if visualVariant === "success"}
|
|
59
|
+
<svg class="h-5 w-5" fill="currentColor" viewBox="0 0 20 20">
|
|
60
|
+
<path
|
|
61
|
+
fill-rule="evenodd"
|
|
62
|
+
d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z"
|
|
63
|
+
clip-rule="evenodd"
|
|
64
|
+
/>
|
|
65
|
+
</svg>
|
|
66
|
+
{:else if visualVariant === "warning"}
|
|
67
|
+
<svg class="h-5 w-5" fill="currentColor" viewBox="0 0 20 20">
|
|
68
|
+
<path
|
|
69
|
+
fill-rule="evenodd"
|
|
70
|
+
d="M8.257 3.099c.765-1.36 2.722-1.36 3.486 0l5.58 9.92c.75 1.334-.213 2.98-1.742 2.98H4.42c-1.53 0-2.493-1.646-1.743-2.98l5.58-9.92zM11 13a1 1 0 11-2 0 1 1 0 012 0zm-1-8a1 1 0 00-1 1v3a1 1 0 002 0V6a1 1 0 00-1-1z"
|
|
71
|
+
clip-rule="evenodd"
|
|
72
|
+
/>
|
|
73
|
+
</svg>
|
|
74
|
+
{:else if visualVariant === "error"}
|
|
75
|
+
<svg class="h-5 w-5" fill="currentColor" viewBox="0 0 20 20">
|
|
76
|
+
<path
|
|
77
|
+
fill-rule="evenodd"
|
|
78
|
+
d="M10 18a8 8 0 100-16 8 8 0 000 16zM8.707 7.293a1 1 0 00-1.414 1.414L8.586 10l-1.293 1.293a1 1 0 101.414 1.414L10 11.414l1.293 1.293a1 1 0 001.414-1.414L11.414 10l1.293-1.293a1 1 0 00-1.414-1.414L10 8.586 8.707 7.293z"
|
|
79
|
+
clip-rule="evenodd"
|
|
80
|
+
/>
|
|
81
|
+
</svg>
|
|
82
|
+
{:else if visualVariant === "neutral"}
|
|
83
|
+
<svg class="h-5 w-5" fill="currentColor" viewBox="0 0 20 20">
|
|
84
|
+
<path
|
|
85
|
+
fill-rule="evenodd"
|
|
86
|
+
d="M18 10a8 8 0 11-16 0 8 8 0 0116 0zM10 9a1 1 0 100 2 1 1 0 000-2z"
|
|
87
|
+
clip-rule="evenodd"
|
|
88
|
+
/>
|
|
89
|
+
</svg>
|
|
90
|
+
{:else}
|
|
91
|
+
<svg class="h-5 w-5" fill="currentColor" viewBox="0 0 20 20">
|
|
65
92
|
<path d="M11 3L4 14h6l-1 7 7-11h-6l1-7z" />
|
|
66
|
-
</svg
|
|
67
|
-
}
|
|
68
|
-
|
|
93
|
+
</svg>
|
|
94
|
+
{/if}
|
|
95
|
+
{/snippet}
|
|
69
96
|
|
|
70
97
|
<div
|
|
71
98
|
class="relative rounded-md p-4 border {alertClasses[
|
|
@@ -81,7 +108,7 @@
|
|
|
81
108
|
{#if closable}
|
|
82
109
|
<button
|
|
83
110
|
onclick={handleDismiss}
|
|
84
|
-
class="group absolute right-2 top-2 z-10 flex h-6 w-6 cursor-pointer items-center justify-center rounded-full text-description transition-colors duration-200 hover:text-body motion-reduce:transition-none focus
|
|
111
|
+
class="group absolute right-2 top-2 z-10 flex h-6 w-6 cursor-pointer items-center justify-center rounded-full text-description transition-colors duration-200 hover:text-body motion-reduce:transition-none focus-ring"
|
|
85
112
|
aria-label="Dismiss alert"
|
|
86
113
|
type="button"
|
|
87
114
|
>
|
|
@@ -103,7 +130,7 @@
|
|
|
103
130
|
|
|
104
131
|
<div class="flex items-start gap-3 {closable ? 'pr-8' : ''}">
|
|
105
132
|
<div class="shrink-0 mt-0.5">
|
|
106
|
-
{@
|
|
133
|
+
{@render icon()}
|
|
107
134
|
</div>
|
|
108
135
|
|
|
109
136
|
<div class="flex-1 min-w-0">
|
|
@@ -29,7 +29,7 @@
|
|
|
29
29
|
<Card
|
|
30
30
|
size="lg"
|
|
31
31
|
fullWidth={true}
|
|
32
|
-
className="overflow-
|
|
32
|
+
className="overflow-visible {className}"
|
|
33
33
|
{...restProps}
|
|
34
34
|
>
|
|
35
35
|
<CardHeader {title} {description} />
|
|
@@ -81,7 +81,7 @@
|
|
|
81
81
|
{#if showCode}
|
|
82
82
|
<CodeBlock {code} {language} />
|
|
83
83
|
{:else}
|
|
84
|
-
<div class="min-h-[100px]
|
|
84
|
+
<div class="min-h-[100px] w-full min-w-0 py-4">
|
|
85
85
|
{@render children?.()}
|
|
86
86
|
</div>
|
|
87
87
|
{/if}
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
import Card from "../atoms/Card.svelte";
|
|
8
8
|
import CardHeader from "../atoms/CardHeader.svelte";
|
|
9
9
|
import CardContent from "../atoms/CardContent.svelte";
|
|
10
|
-
import type { ContactFormData } from "
|
|
10
|
+
import type { ContactFormData } from "../types/page.types";
|
|
11
11
|
|
|
12
12
|
interface Props {
|
|
13
13
|
className?: string;
|
|
@@ -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,127 +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
|
-
transition-all
|
|
189
|
-
duration-200
|
|
190
|
-
ease-in-out
|
|
191
|
-
${transformClasses()}
|
|
192
|
-
`
|
|
193
|
-
.trim()
|
|
194
|
-
.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();
|
|
195
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';
|
|
196
197
|
</script>
|
|
197
198
|
|
|
198
199
|
<div
|
|
200
|
+
bind:this={rootEl}
|
|
199
201
|
class="relative inline-block"
|
|
200
202
|
data-placement={placement}
|
|
201
203
|
onkeydown={handleKeydown}
|
|
202
204
|
{...restProps}
|
|
203
205
|
>
|
|
204
|
-
|
|
205
|
-
{@render trigger?.()}
|
|
206
|
-
</div>
|
|
206
|
+
{@render trigger(triggerAria)}
|
|
207
207
|
|
|
208
208
|
{#if isOpen}
|
|
209
209
|
<div
|
|
210
210
|
bind:this={menuElement}
|
|
211
|
-
id={
|
|
211
|
+
id={menuId}
|
|
212
212
|
class={dropdownContentClasses()}
|
|
213
|
-
role=
|
|
213
|
+
role={menuRole === 'listbox' ? 'listbox' : 'menu'}
|
|
214
214
|
aria-label={ariaLabel}
|
|
215
|
-
tabindex="-1"
|
|
216
215
|
>
|
|
217
216
|
{#if options.length > 0}
|
|
218
|
-
<div class="px-2 py-
|
|
217
|
+
<div class="px-2 py-1">
|
|
219
218
|
{#each options as option (option.value)}
|
|
220
|
-
|
|
221
|
-
"
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
</Button>
|
|
236
|
-
</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>
|
|
237
234
|
{/each}
|
|
238
235
|
</div>
|
|
239
236
|
{:else if children}
|
|
@@ -242,10 +239,3 @@
|
|
|
242
239
|
</div>
|
|
243
240
|
{/if}
|
|
244
241
|
</div>
|
|
245
|
-
|
|
246
|
-
<style>
|
|
247
|
-
:global([role="menu"] button) {
|
|
248
|
-
justify-content: flex-start;
|
|
249
|
-
text-align: left;
|
|
250
|
-
}
|
|
251
|
-
</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>
|