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
|
@@ -2,6 +2,13 @@ interface Props {
|
|
|
2
2
|
isOpen?: boolean;
|
|
3
3
|
placement?: "bottom-start" | "bottom-end" | "top-start" | "top-end";
|
|
4
4
|
ariaLabel?: string;
|
|
5
|
+
selectedValue?: string | number | null;
|
|
6
|
+
options?: Array<{
|
|
7
|
+
value: string | number;
|
|
8
|
+
label: string;
|
|
9
|
+
disabled?: boolean;
|
|
10
|
+
}>;
|
|
11
|
+
onOptionClick?: (value: string | number) => void;
|
|
5
12
|
}
|
|
6
13
|
type $$ComponentProps = Props & {
|
|
7
14
|
children?: any;
|
|
@@ -57,10 +57,10 @@
|
|
|
57
57
|
<img
|
|
58
58
|
src={value}
|
|
59
59
|
alt=""
|
|
60
|
-
class="w-full h-32 object-cover rounded-
|
|
60
|
+
class="w-full h-32 min-w-64 object-cover rounded-2xl border-0"
|
|
61
61
|
/>
|
|
62
62
|
<div
|
|
63
|
-
class="absolute inset-0 bg-
|
|
63
|
+
class="absolute inset-0 bg-input min-w-64 opacity-0 group-hover:opacity-100 transition-opacity rounded-2xl flex items-center justify-center"
|
|
64
64
|
>
|
|
65
65
|
<div class="flex gap-2">
|
|
66
66
|
<Button
|
|
@@ -84,7 +84,7 @@
|
|
|
84
84
|
</div>
|
|
85
85
|
{:else}
|
|
86
86
|
<div
|
|
87
|
-
class="border-2 border-dashed border-
|
|
87
|
+
class="border-2 border-dashed border-input rounded-2xl min-w-64 p-6 text-center hover:border-brand-500 transition-colors {disabled
|
|
88
88
|
? 'cursor-not-allowed opacity-50'
|
|
89
89
|
: 'cursor-pointer'}"
|
|
90
90
|
onclick={triggerFileSelect}
|
|
@@ -96,7 +96,7 @@
|
|
|
96
96
|
>
|
|
97
97
|
<div class="space-y-3">
|
|
98
98
|
<div
|
|
99
|
-
class="w-12 h-12 mx-auto bg-
|
|
99
|
+
class="w-12 h-12 mx-auto bg-action-secondary rounded-lg flex items-center justify-center"
|
|
100
100
|
>
|
|
101
101
|
<Image size={24} class="text-description" />
|
|
102
102
|
</div>
|
|
@@ -1,12 +1,16 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
-
import { onMount } from "svelte";
|
|
3
2
|
import { trapFocus, saveFocus, returnFocus, focusFirstElement } from "../routes/lib/focus-utils.js";
|
|
3
|
+
import Card from "../atoms/Card.svelte";
|
|
4
|
+
import CardHeader from "../atoms/CardHeader.svelte";
|
|
5
|
+
import CardContent from "../atoms/CardContent.svelte";
|
|
6
|
+
import CardFooter from "../atoms/CardFooter.svelte";
|
|
4
7
|
|
|
5
8
|
type Size = "sm" | "md" | "lg";
|
|
6
9
|
|
|
7
10
|
interface Props {
|
|
8
11
|
isOpen?: boolean;
|
|
9
12
|
title?: string;
|
|
13
|
+
description?: string;
|
|
10
14
|
size?: Size;
|
|
11
15
|
onclick?: (event: Event) => void;
|
|
12
16
|
onkeydown?: (event: Event) => void;
|
|
@@ -15,6 +19,7 @@
|
|
|
15
19
|
let {
|
|
16
20
|
isOpen = false,
|
|
17
21
|
title = "",
|
|
22
|
+
description = "",
|
|
18
23
|
size = "md",
|
|
19
24
|
onclick,
|
|
20
25
|
onkeydown,
|
|
@@ -88,38 +93,49 @@
|
|
|
88
93
|
aria-labelledby={title ? "modal-title" : undefined}
|
|
89
94
|
tabindex="-1"
|
|
90
95
|
>
|
|
91
|
-
<
|
|
96
|
+
<Card
|
|
92
97
|
bind:this={modalContainer}
|
|
93
|
-
|
|
98
|
+
variant="default"
|
|
99
|
+
fullWidth={false}
|
|
100
|
+
class="bg-card rounded-t-3xl md:rounded-3xl shadow-xl min-w-[320px] {sizeClasses} max-h-[90vh] overflow-y-auto animate-[slideUp_0.3s_ease-out] md:animate-none flex flex-col p-0"
|
|
94
101
|
>
|
|
95
|
-
{#if title}
|
|
96
|
-
<
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
102
|
+
{#if title || description}
|
|
103
|
+
<CardHeader
|
|
104
|
+
{description}
|
|
105
|
+
className="px-6 pt-6 pb-4"
|
|
106
|
+
>
|
|
107
|
+
{#if title}
|
|
108
|
+
<div class="flex items-center justify-between">
|
|
109
|
+
<h2
|
|
110
|
+
id="modal-title"
|
|
111
|
+
class="text-2xl font-normal leading-8 text-headline tracking-normal"
|
|
112
|
+
>
|
|
113
|
+
{title}
|
|
114
|
+
</h2>
|
|
115
|
+
<button
|
|
116
|
+
type="button"
|
|
117
|
+
onclick={closeModal}
|
|
118
|
+
class="text-description hover:text-headline text-2xl cursor-pointer transition-colors w-8 h-8 flex items-center justify-center rounded-full hover:bg-base-100"
|
|
119
|
+
aria-label="Close"
|
|
120
|
+
>
|
|
121
|
+
×
|
|
122
|
+
</button>
|
|
123
|
+
</div>
|
|
124
|
+
{/if}
|
|
125
|
+
</CardHeader>
|
|
112
126
|
{/if}
|
|
113
127
|
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
128
|
+
{#if children}
|
|
129
|
+
<CardContent className="flex-1">
|
|
130
|
+
{@render children?.()}
|
|
131
|
+
</CardContent>
|
|
132
|
+
{/if}
|
|
117
133
|
|
|
118
134
|
{#if footer}
|
|
119
|
-
<
|
|
135
|
+
<CardFooter className="flex justify-end gap-3 pt-4">
|
|
120
136
|
{@render footer?.()}
|
|
121
|
-
</
|
|
137
|
+
</CardFooter>
|
|
122
138
|
{/if}
|
|
123
|
-
</
|
|
139
|
+
</Card>
|
|
124
140
|
</div>
|
|
125
141
|
{/if}
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { Snippet } from "svelte";
|
|
3
|
+
import { getContext, setContext } from "svelte";
|
|
4
|
+
import { onMount } from "svelte";
|
|
5
|
+
import NavigationMenuList from "./NavigationMenuList.svelte";
|
|
6
|
+
import NavigationMenuItem from "./NavigationMenuItem.svelte";
|
|
7
|
+
import NavigationMenuTrigger from "./NavigationMenuTrigger.svelte";
|
|
8
|
+
import NavigationMenuContent from "./NavigationMenuContent.svelte";
|
|
9
|
+
import NavigationMenuLink from "./NavigationMenuLink.svelte";
|
|
10
|
+
|
|
11
|
+
interface NavigationMenuContext {
|
|
12
|
+
activeItem: string | null;
|
|
13
|
+
setActiveItem: (item: string | null) => void;
|
|
14
|
+
isMobile: boolean;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const NAVIGATION_MENU_CONTEXT = Symbol("navigation-menu");
|
|
18
|
+
|
|
19
|
+
export interface NavigationMenuItemData {
|
|
20
|
+
value: string;
|
|
21
|
+
label: string;
|
|
22
|
+
href?: string;
|
|
23
|
+
content?: NavigationMenuLinkData[];
|
|
24
|
+
children?: Snippet;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export interface NavigationMenuLinkData {
|
|
28
|
+
href: string;
|
|
29
|
+
label?: string;
|
|
30
|
+
description?: string;
|
|
31
|
+
children?: Snippet;
|
|
32
|
+
asChild?: boolean;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
interface Props {
|
|
36
|
+
viewport?: boolean | "mobile";
|
|
37
|
+
className?: string;
|
|
38
|
+
items?: NavigationMenuItemData[];
|
|
39
|
+
children?: Snippet;
|
|
40
|
+
listClassName?: string;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
let {
|
|
44
|
+
viewport = true,
|
|
45
|
+
className = "",
|
|
46
|
+
items = [],
|
|
47
|
+
children,
|
|
48
|
+
listClassName = "",
|
|
49
|
+
...restProps
|
|
50
|
+
}: Props = $props();
|
|
51
|
+
|
|
52
|
+
let activeItem = $state<string | null>(null);
|
|
53
|
+
let isMobile = $state(false);
|
|
54
|
+
let containerElement = $state<HTMLElement | null>(null);
|
|
55
|
+
|
|
56
|
+
function setActiveItem(item: string | null) {
|
|
57
|
+
activeItem = item;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function checkMobile() {
|
|
61
|
+
if (viewport === "mobile") {
|
|
62
|
+
isMobile = true;
|
|
63
|
+
} else if (viewport === true) {
|
|
64
|
+
isMobile = window.innerWidth < 768;
|
|
65
|
+
} else {
|
|
66
|
+
isMobile = false;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
onMount(() => {
|
|
71
|
+
checkMobile();
|
|
72
|
+
if (viewport !== false) {
|
|
73
|
+
const handleResize = () => checkMobile();
|
|
74
|
+
window.addEventListener("resize", handleResize);
|
|
75
|
+
return () => window.removeEventListener("resize", handleResize);
|
|
76
|
+
}
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
const context: NavigationMenuContext = {
|
|
80
|
+
get activeItem() {
|
|
81
|
+
return activeItem;
|
|
82
|
+
},
|
|
83
|
+
setActiveItem,
|
|
84
|
+
get isMobile() {
|
|
85
|
+
return isMobile;
|
|
86
|
+
},
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
setContext(NAVIGATION_MENU_CONTEXT, context);
|
|
90
|
+
</script>
|
|
91
|
+
|
|
92
|
+
<nav
|
|
93
|
+
bind:this={containerElement}
|
|
94
|
+
class="relative {className}"
|
|
95
|
+
aria-label="Main navigation"
|
|
96
|
+
{...restProps}
|
|
97
|
+
>
|
|
98
|
+
{#if items.length > 0}
|
|
99
|
+
<NavigationMenuList className={listClassName}>
|
|
100
|
+
{#each items as item}
|
|
101
|
+
<NavigationMenuItem value={item.value}>
|
|
102
|
+
{#if item.content && item.content.length > 0}
|
|
103
|
+
<NavigationMenuTrigger value={item.value}>
|
|
104
|
+
{item.label}
|
|
105
|
+
</NavigationMenuTrigger>
|
|
106
|
+
<NavigationMenuContent value={item.value}>
|
|
107
|
+
{#if item.children}
|
|
108
|
+
{@render item.children()}
|
|
109
|
+
{:else}
|
|
110
|
+
<ul class="grid gap-2">
|
|
111
|
+
{#each item.content as link}
|
|
112
|
+
<li>
|
|
113
|
+
<NavigationMenuLink
|
|
114
|
+
href={link.href}
|
|
115
|
+
asChild={link.asChild}
|
|
116
|
+
>
|
|
117
|
+
{#if link.children}
|
|
118
|
+
{@render link.children()}
|
|
119
|
+
{:else if link.label}
|
|
120
|
+
<div
|
|
121
|
+
class="text-sm font-medium leading-none"
|
|
122
|
+
>
|
|
123
|
+
{link.label}
|
|
124
|
+
</div>
|
|
125
|
+
{#if link.description}
|
|
126
|
+
<p
|
|
127
|
+
class="line-clamp-2 text-sm leading-snug text-description"
|
|
128
|
+
>
|
|
129
|
+
{link.description}
|
|
130
|
+
</p>
|
|
131
|
+
{/if}
|
|
132
|
+
{/if}
|
|
133
|
+
</NavigationMenuLink>
|
|
134
|
+
</li>
|
|
135
|
+
{/each}
|
|
136
|
+
</ul>
|
|
137
|
+
{/if}
|
|
138
|
+
</NavigationMenuContent>
|
|
139
|
+
{:else if item.href}
|
|
140
|
+
<NavigationMenuLink href={item.href}>
|
|
141
|
+
{item.label}
|
|
142
|
+
</NavigationMenuLink>
|
|
143
|
+
{:else if item.children}
|
|
144
|
+
{@render item.children()}
|
|
145
|
+
{/if}
|
|
146
|
+
</NavigationMenuItem>
|
|
147
|
+
{/each}
|
|
148
|
+
</NavigationMenuList>
|
|
149
|
+
{:else if children}
|
|
150
|
+
{@render children()}
|
|
151
|
+
{/if}
|
|
152
|
+
</nav>
|
|
153
|
+
|
|
154
|
+
<style>
|
|
155
|
+
:global([data-navigation-menu]) {
|
|
156
|
+
position: relative;
|
|
157
|
+
}
|
|
158
|
+
</style>
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { Snippet } from "svelte";
|
|
2
|
+
export interface NavigationMenuLinkData {
|
|
3
|
+
href: string;
|
|
4
|
+
label?: string;
|
|
5
|
+
description?: string;
|
|
6
|
+
children?: Snippet;
|
|
7
|
+
asChild?: boolean;
|
|
8
|
+
}
|
|
9
|
+
export interface NavigationMenuItemData {
|
|
10
|
+
value: string;
|
|
11
|
+
label: string;
|
|
12
|
+
href?: string;
|
|
13
|
+
content?: NavigationMenuLinkData[];
|
|
14
|
+
children?: Snippet;
|
|
15
|
+
}
|
|
16
|
+
interface Props {
|
|
17
|
+
viewport?: boolean | "mobile";
|
|
18
|
+
className?: string;
|
|
19
|
+
items?: NavigationMenuItemData[];
|
|
20
|
+
children?: Snippet;
|
|
21
|
+
listClassName?: string;
|
|
22
|
+
}
|
|
23
|
+
declare const NavigationMenu: import("svelte").Component<Props, {}, "">;
|
|
24
|
+
type NavigationMenu = ReturnType<typeof NavigationMenu>;
|
|
25
|
+
export default NavigationMenu;
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { Snippet } from "svelte";
|
|
3
|
+
import { getContext } from "svelte";
|
|
4
|
+
import { onMount, onDestroy } from "svelte";
|
|
5
|
+
|
|
6
|
+
const NAVIGATION_MENU_CONTEXT = Symbol("navigation-menu");
|
|
7
|
+
|
|
8
|
+
interface Props {
|
|
9
|
+
value?: string;
|
|
10
|
+
className?: string;
|
|
11
|
+
children?: Snippet;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
let {
|
|
15
|
+
value = "",
|
|
16
|
+
className = "",
|
|
17
|
+
children,
|
|
18
|
+
...restProps
|
|
19
|
+
}: Props = $props();
|
|
20
|
+
|
|
21
|
+
let contentElement = $state<HTMLElement | null>(null);
|
|
22
|
+
let parentItem = $state<HTMLElement | null>(null);
|
|
23
|
+
|
|
24
|
+
const context = getContext<{
|
|
25
|
+
activeItem: string | null;
|
|
26
|
+
setActiveItem: (item: string | null) => void;
|
|
27
|
+
isMobile: boolean;
|
|
28
|
+
}>(NAVIGATION_MENU_CONTEXT);
|
|
29
|
+
|
|
30
|
+
const isActive = $derived(context?.activeItem === value);
|
|
31
|
+
|
|
32
|
+
function handleKeydown(event: KeyboardEvent) {
|
|
33
|
+
if (event.key === "Escape") {
|
|
34
|
+
event.preventDefault();
|
|
35
|
+
context?.setActiveItem(null);
|
|
36
|
+
const trigger = parentItem?.querySelector('[data-navigation-menu-trigger]') as HTMLElement;
|
|
37
|
+
trigger?.focus();
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function handleClickOutside(event: MouseEvent) {
|
|
42
|
+
if (
|
|
43
|
+
contentElement &&
|
|
44
|
+
!contentElement.contains(event.target as Node) &&
|
|
45
|
+
parentItem &&
|
|
46
|
+
!parentItem.contains(event.target as Node)
|
|
47
|
+
) {
|
|
48
|
+
context?.setActiveItem(null);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
onMount(() => {
|
|
53
|
+
if (contentElement) {
|
|
54
|
+
parentItem = contentElement.closest('[data-navigation-menu-item]') as HTMLElement;
|
|
55
|
+
}
|
|
56
|
+
if (isActive) {
|
|
57
|
+
document.addEventListener("mousedown", handleClickOutside);
|
|
58
|
+
}
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
$effect(() => {
|
|
62
|
+
if (isActive) {
|
|
63
|
+
document.addEventListener("mousedown", handleClickOutside);
|
|
64
|
+
return () => {
|
|
65
|
+
document.removeEventListener("mousedown", handleClickOutside);
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
});
|
|
69
|
+
</script>
|
|
70
|
+
|
|
71
|
+
{#if isActive}
|
|
72
|
+
<div
|
|
73
|
+
bind:this={contentElement}
|
|
74
|
+
class="absolute left-0 top-full mt-2 bg-input rounded-lg shadow-lg border border-border p-4 z-dropdown min-w-[200px] transition-all duration-200 ease-in-out {className}"
|
|
75
|
+
role="menu"
|
|
76
|
+
onkeydown={handleKeydown}
|
|
77
|
+
data-navigation-menu-content
|
|
78
|
+
tabindex="-1"
|
|
79
|
+
{...restProps}
|
|
80
|
+
>
|
|
81
|
+
{@render children?.()}
|
|
82
|
+
</div>
|
|
83
|
+
{/if}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { Snippet } from "svelte";
|
|
2
|
+
interface Props {
|
|
3
|
+
value?: string;
|
|
4
|
+
className?: string;
|
|
5
|
+
children?: Snippet;
|
|
6
|
+
}
|
|
7
|
+
declare const NavigationMenuContent: import("svelte").Component<Props, {}, "">;
|
|
8
|
+
type NavigationMenuContent = ReturnType<typeof NavigationMenuContent>;
|
|
9
|
+
export default NavigationMenuContent;
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { Snippet } from "svelte";
|
|
3
|
+
import { getContext } from "svelte";
|
|
4
|
+
|
|
5
|
+
const NAVIGATION_MENU_CONTEXT = Symbol("navigation-menu");
|
|
6
|
+
|
|
7
|
+
interface Props {
|
|
8
|
+
value?: string;
|
|
9
|
+
className?: string;
|
|
10
|
+
children?: Snippet;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
let {
|
|
14
|
+
value = "",
|
|
15
|
+
className = "",
|
|
16
|
+
children,
|
|
17
|
+
...restProps
|
|
18
|
+
}: Props = $props();
|
|
19
|
+
|
|
20
|
+
const context = getContext<{
|
|
21
|
+
activeItem: string | null;
|
|
22
|
+
setActiveItem: (item: string | null) => void;
|
|
23
|
+
}>(NAVIGATION_MENU_CONTEXT);
|
|
24
|
+
|
|
25
|
+
const isActive = $derived(context?.activeItem === value);
|
|
26
|
+
</script>
|
|
27
|
+
|
|
28
|
+
<li
|
|
29
|
+
class="relative {className}"
|
|
30
|
+
role="none"
|
|
31
|
+
data-navigation-menu-item
|
|
32
|
+
data-active={isActive}
|
|
33
|
+
{...restProps}
|
|
34
|
+
>
|
|
35
|
+
{@render children?.()}
|
|
36
|
+
</li>
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { Snippet } from "svelte";
|
|
2
|
+
interface Props {
|
|
3
|
+
value?: string;
|
|
4
|
+
className?: string;
|
|
5
|
+
children?: Snippet;
|
|
6
|
+
}
|
|
7
|
+
declare const NavigationMenuItem: import("svelte").Component<Props, {}, "">;
|
|
8
|
+
type NavigationMenuItem = ReturnType<typeof NavigationMenuItem>;
|
|
9
|
+
export default NavigationMenuItem;
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { Snippet } from "svelte";
|
|
3
|
+
import { getContext } from "svelte";
|
|
4
|
+
|
|
5
|
+
const NAVIGATION_MENU_CONTEXT = Symbol("navigation-menu");
|
|
6
|
+
|
|
7
|
+
interface Props {
|
|
8
|
+
href?: string;
|
|
9
|
+
asChild?: boolean;
|
|
10
|
+
className?: string;
|
|
11
|
+
children?: Snippet;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
let {
|
|
15
|
+
href = "#",
|
|
16
|
+
asChild = false,
|
|
17
|
+
className = "",
|
|
18
|
+
children,
|
|
19
|
+
...restProps
|
|
20
|
+
}: Props = $props();
|
|
21
|
+
|
|
22
|
+
const context = getContext<{
|
|
23
|
+
setActiveItem: (item: string | null) => void;
|
|
24
|
+
}>(NAVIGATION_MENU_CONTEXT);
|
|
25
|
+
|
|
26
|
+
function handleClick() {
|
|
27
|
+
context?.setActiveItem(null);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const linkClasses =
|
|
31
|
+
"block px-4 py-2 text-sm text-headline hover:bg-base-100 rounded-md transition-colors focus:outline-none focus:ring-2 focus:ring-brand-500 focus:ring-offset-2 no-underline {className}";
|
|
32
|
+
</script>
|
|
33
|
+
|
|
34
|
+
{#if asChild}
|
|
35
|
+
<div
|
|
36
|
+
class={linkClasses}
|
|
37
|
+
onclick={handleClick}
|
|
38
|
+
data-navigation-menu-link
|
|
39
|
+
{...restProps}
|
|
40
|
+
>
|
|
41
|
+
{@render children?.()}
|
|
42
|
+
</div>
|
|
43
|
+
{:else}
|
|
44
|
+
<a
|
|
45
|
+
{href}
|
|
46
|
+
class={linkClasses}
|
|
47
|
+
onclick={handleClick}
|
|
48
|
+
data-navigation-menu-link
|
|
49
|
+
{...restProps}
|
|
50
|
+
>
|
|
51
|
+
{@render children?.()}
|
|
52
|
+
</a>
|
|
53
|
+
{/if}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { Snippet } from "svelte";
|
|
2
|
+
interface Props {
|
|
3
|
+
href?: string;
|
|
4
|
+
asChild?: boolean;
|
|
5
|
+
className?: string;
|
|
6
|
+
children?: Snippet;
|
|
7
|
+
}
|
|
8
|
+
declare const NavigationMenuLink: import("svelte").Component<Props, {}, "">;
|
|
9
|
+
type NavigationMenuLink = ReturnType<typeof NavigationMenuLink>;
|
|
10
|
+
export default NavigationMenuLink;
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { Snippet } from "svelte";
|
|
3
|
+
import { getContext } from "svelte";
|
|
4
|
+
|
|
5
|
+
const NAVIGATION_MENU_CONTEXT = Symbol("navigation-menu");
|
|
6
|
+
|
|
7
|
+
interface Props {
|
|
8
|
+
className?: string;
|
|
9
|
+
children?: Snippet;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
let {
|
|
13
|
+
className = "",
|
|
14
|
+
children,
|
|
15
|
+
...restProps
|
|
16
|
+
}: Props = $props();
|
|
17
|
+
|
|
18
|
+
const context = getContext<{
|
|
19
|
+
isMobile: boolean;
|
|
20
|
+
}>(NAVIGATION_MENU_CONTEXT);
|
|
21
|
+
</script>
|
|
22
|
+
|
|
23
|
+
<ul
|
|
24
|
+
class="flex flex-row items-center gap-1 list-none m-0 p-0 {className}"
|
|
25
|
+
role="menubar"
|
|
26
|
+
aria-orientation="horizontal"
|
|
27
|
+
data-navigation-menu-list
|
|
28
|
+
{...restProps}
|
|
29
|
+
>
|
|
30
|
+
{@render children?.()}
|
|
31
|
+
</ul>
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { Snippet } from "svelte";
|
|
2
|
+
interface Props {
|
|
3
|
+
className?: string;
|
|
4
|
+
children?: Snippet;
|
|
5
|
+
}
|
|
6
|
+
declare const NavigationMenuList: import("svelte").Component<Props, {}, "">;
|
|
7
|
+
type NavigationMenuList = ReturnType<typeof NavigationMenuList>;
|
|
8
|
+
export default NavigationMenuList;
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { Snippet } from "svelte";
|
|
3
|
+
import { getContext } from "svelte";
|
|
4
|
+
|
|
5
|
+
const NAVIGATION_MENU_CONTEXT = Symbol("navigation-menu");
|
|
6
|
+
|
|
7
|
+
interface Props {
|
|
8
|
+
value?: string;
|
|
9
|
+
className?: string;
|
|
10
|
+
children?: Snippet;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
let {
|
|
14
|
+
value = "",
|
|
15
|
+
className = "",
|
|
16
|
+
children,
|
|
17
|
+
...restProps
|
|
18
|
+
}: Props = $props();
|
|
19
|
+
|
|
20
|
+
let triggerElement = $state<HTMLElement | null>(null);
|
|
21
|
+
|
|
22
|
+
const context = getContext<{
|
|
23
|
+
activeItem: string | null;
|
|
24
|
+
setActiveItem: (item: string | null) => void;
|
|
25
|
+
isMobile: boolean;
|
|
26
|
+
}>(NAVIGATION_MENU_CONTEXT);
|
|
27
|
+
|
|
28
|
+
const isActive = $derived(context?.activeItem === value);
|
|
29
|
+
|
|
30
|
+
function handleClick() {
|
|
31
|
+
if (context) {
|
|
32
|
+
if (isActive) {
|
|
33
|
+
context.setActiveItem(null);
|
|
34
|
+
} else {
|
|
35
|
+
context.setActiveItem(value);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function handleKeydown(event: KeyboardEvent) {
|
|
41
|
+
if (event.key === "Enter" || event.key === " ") {
|
|
42
|
+
event.preventDefault();
|
|
43
|
+
handleClick();
|
|
44
|
+
} else if (event.key === "Escape" && isActive) {
|
|
45
|
+
event.preventDefault();
|
|
46
|
+
context?.setActiveItem(null);
|
|
47
|
+
} else if (event.key === "ArrowDown" && !isActive) {
|
|
48
|
+
event.preventDefault();
|
|
49
|
+
handleClick();
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
</script>
|
|
53
|
+
|
|
54
|
+
<button
|
|
55
|
+
bind:this={triggerElement}
|
|
56
|
+
type="button"
|
|
57
|
+
class="inline-flex items-center justify-center gap-2 px-4 py-2 text-sm font-medium text-headline bg-transparent hover:bg-base-100 rounded-lg transition-colors focus:outline-none focus:ring-2 focus:ring-brand-500 focus:ring-offset-2 {className}"
|
|
58
|
+
aria-expanded={isActive ? 'true' : 'false'}
|
|
59
|
+
aria-haspopup="true"
|
|
60
|
+
onclick={handleClick}
|
|
61
|
+
onkeydown={handleKeydown}
|
|
62
|
+
data-navigation-menu-trigger
|
|
63
|
+
data-active={isActive}
|
|
64
|
+
{...restProps}
|
|
65
|
+
>
|
|
66
|
+
{@render children?.()}
|
|
67
|
+
<svg
|
|
68
|
+
class="ml-1 h-4 w-4 transition-transform {isActive ? 'rotate-180' : ''}"
|
|
69
|
+
fill="none"
|
|
70
|
+
stroke="currentColor"
|
|
71
|
+
viewBox="0 0 24 24"
|
|
72
|
+
aria-hidden="true"
|
|
73
|
+
>
|
|
74
|
+
<path
|
|
75
|
+
stroke-linecap="round"
|
|
76
|
+
stroke-linejoin="round"
|
|
77
|
+
stroke-width="2"
|
|
78
|
+
d="M19 9l-7 7-7-7"
|
|
79
|
+
/>
|
|
80
|
+
</svg>
|
|
81
|
+
</button>
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { Snippet } from "svelte";
|
|
2
|
+
interface Props {
|
|
3
|
+
value?: string;
|
|
4
|
+
className?: string;
|
|
5
|
+
children?: Snippet;
|
|
6
|
+
}
|
|
7
|
+
declare const NavigationMenuTrigger: import("svelte").Component<Props, {}, "">;
|
|
8
|
+
type NavigationMenuTrigger = ReturnType<typeof NavigationMenuTrigger>;
|
|
9
|
+
export default NavigationMenuTrigger;
|