zabi-components 5.0.18 → 5.0.20
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/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/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 +17 -22
- 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 +0 -2
- package/dist/atoms/index.js +0 -2
- package/dist/components/atoms/index.d.ts +0 -2
- package/dist/components/atoms/index.d.ts.map +1 -1
- package/dist/components/molecules/index.d.ts +7 -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 +43 -27
- 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/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 +7 -0
- package/dist/molecules/index.js +7 -0
- package/dist/routes/lib/focus-utils.d.ts +23 -0
- package/dist/routes/lib/focus-utils.ts +112 -0
- 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 +192 -27
- package/package.json +4 -3
- package/dist/atoms/CardDescription.svelte +0 -24
- package/dist/atoms/CardDescription.svelte.d.ts +0 -8
- package/dist/atoms/CardTitle.svelte +0 -33
- package/dist/atoms/CardTitle.svelte.d.ts +0 -9
package/dist/atoms/Select.svelte
CHANGED
|
@@ -45,6 +45,7 @@
|
|
|
45
45
|
}: Props = $props();
|
|
46
46
|
|
|
47
47
|
let isOpen = $state(false);
|
|
48
|
+
let selectContainer: HTMLDivElement;
|
|
48
49
|
|
|
49
50
|
const sizeClass = $derived(() => {
|
|
50
51
|
if (size === "sm") {
|
|
@@ -146,9 +147,15 @@
|
|
|
146
147
|
}
|
|
147
148
|
|
|
148
149
|
function handleClickOutside(event: MouseEvent) {
|
|
150
|
+
if (!isOpen) return;
|
|
151
|
+
|
|
152
|
+
const target = event.target as HTMLElement;
|
|
153
|
+
const clickedSelectContainer = target.closest(".select-container");
|
|
154
|
+
|
|
155
|
+
// Close if clicking outside any select container, or clicking on a different select
|
|
149
156
|
if (
|
|
150
|
-
|
|
151
|
-
|
|
157
|
+
!clickedSelectContainer ||
|
|
158
|
+
clickedSelectContainer !== selectContainer
|
|
152
159
|
) {
|
|
153
160
|
isOpen = false;
|
|
154
161
|
}
|
|
@@ -163,12 +170,18 @@
|
|
|
163
170
|
|
|
164
171
|
<svelte:window onclick={handleClickOutside} onkeydown={handleKeydown} />
|
|
165
172
|
|
|
166
|
-
<div class="w-full select-container">
|
|
173
|
+
<div bind:this={selectContainer} class="w-full select-container">
|
|
167
174
|
{#if label}
|
|
168
175
|
<label for={selectId} class={labelClasses()}>{label}</label>
|
|
169
176
|
{/if}
|
|
170
177
|
|
|
171
|
-
<Dropdown
|
|
178
|
+
<Dropdown
|
|
179
|
+
{isOpen}
|
|
180
|
+
placement="bottom-start"
|
|
181
|
+
selectedValue={value}
|
|
182
|
+
{options}
|
|
183
|
+
onOptionClick={handleOptionClick}
|
|
184
|
+
>
|
|
172
185
|
{#snippet trigger()}
|
|
173
186
|
<button
|
|
174
187
|
type="button"
|
|
@@ -198,24 +211,6 @@
|
|
|
198
211
|
/>
|
|
199
212
|
</button>
|
|
200
213
|
{/snippet}
|
|
201
|
-
{#snippet children()}
|
|
202
|
-
<div class="px-2 py-2">
|
|
203
|
-
{#each options as option (option.value)}
|
|
204
|
-
<button
|
|
205
|
-
type="button"
|
|
206
|
-
class="w-full text-left px-4 py-2 text-body hover:bg-base-100 transition-colors rounded-md my-0.5 {option.disabled
|
|
207
|
-
? 'opacity-50 cursor-not-allowed'
|
|
208
|
-
: 'cursor-pointer'} {value === option.value
|
|
209
|
-
? 'bg-base-100'
|
|
210
|
-
: ''}"
|
|
211
|
-
onclick={() => handleOptionClick(option.value)}
|
|
212
|
-
disabled={option.disabled}
|
|
213
|
-
>
|
|
214
|
-
{option.label}
|
|
215
|
-
</button>
|
|
216
|
-
{/each}
|
|
217
|
-
</div>
|
|
218
|
-
{/snippet}
|
|
219
214
|
</Dropdown>
|
|
220
215
|
{#if message && variant !== "default"}
|
|
221
216
|
<p id={`${selectId}-message`} class={messageClasses()} role="alert">
|
|
@@ -3,18 +3,28 @@
|
|
|
3
3
|
width?: string;
|
|
4
4
|
height?: string;
|
|
5
5
|
className?: string;
|
|
6
|
+
"aria-label"?: string;
|
|
6
7
|
}
|
|
7
8
|
|
|
8
9
|
let {
|
|
9
10
|
width = "100%",
|
|
10
11
|
height = "1rem",
|
|
11
12
|
className = "",
|
|
13
|
+
"aria-label": ariaLabel = "Loading...",
|
|
12
14
|
...restProps
|
|
13
15
|
}: Props = $props();
|
|
16
|
+
|
|
17
|
+
const classes = $derived(
|
|
18
|
+
`animate-pulse bg-action-secondary rounded${className ? ` ${className}` : ""}`,
|
|
19
|
+
);
|
|
20
|
+
const styles = $derived(`width: ${width}; height: ${height};`);
|
|
14
21
|
</script>
|
|
15
22
|
|
|
16
23
|
<div
|
|
17
|
-
class=
|
|
18
|
-
style=
|
|
24
|
+
class={classes}
|
|
25
|
+
style={styles}
|
|
26
|
+
aria-busy="true"
|
|
27
|
+
aria-label={ariaLabel}
|
|
28
|
+
role="status"
|
|
19
29
|
{...restProps}
|
|
20
30
|
></div>
|
|
@@ -77,7 +77,11 @@
|
|
|
77
77
|
onfocusout={handleBlur}
|
|
78
78
|
{...restProps}
|
|
79
79
|
>
|
|
80
|
-
<div
|
|
80
|
+
<div
|
|
81
|
+
bind:this={triggerElement}
|
|
82
|
+
id={triggerId}
|
|
83
|
+
aria-describedby={isVisible ? tooltipId : undefined}
|
|
84
|
+
>
|
|
81
85
|
{@render children?.()}
|
|
82
86
|
</div>
|
|
83
87
|
|
|
@@ -97,10 +101,10 @@
|
|
|
97
101
|
|
|
98
102
|
<style>
|
|
99
103
|
:root {
|
|
100
|
-
--tooltip-bg:
|
|
101
|
-
--tooltip-color:
|
|
104
|
+
--tooltip-bg: var(--color-base-800);
|
|
105
|
+
--tooltip-color: var(--color-base-50);
|
|
102
106
|
--tooltip-padding: 0.5rem 0.75rem;
|
|
103
|
-
--tooltip-radius: 0.
|
|
107
|
+
--tooltip-radius: 0.5rem;
|
|
104
108
|
--tooltip-font-size: 0.875rem;
|
|
105
109
|
--tooltip-line-height: 1.25rem;
|
|
106
110
|
--tooltip-max-width: 200px;
|
|
@@ -243,16 +247,6 @@
|
|
|
243
247
|
}
|
|
244
248
|
}
|
|
245
249
|
|
|
246
|
-
@media (prefers-color-scheme: dark) {
|
|
247
|
-
:root {
|
|
248
|
-
--tooltip-bg: #374151;
|
|
249
|
-
}
|
|
250
|
-
}
|
|
251
|
-
|
|
252
|
-
.dark :root {
|
|
253
|
-
--tooltip-bg: #374151;
|
|
254
|
-
}
|
|
255
|
-
|
|
256
250
|
@media (prefers-contrast: high) {
|
|
257
251
|
:root {
|
|
258
252
|
--tooltip-bg: #000000;
|
|
@@ -263,16 +257,20 @@
|
|
|
263
257
|
|
|
264
258
|
@media (prefers-reduced-motion: reduce) {
|
|
265
259
|
.tooltip {
|
|
266
|
-
transition:
|
|
260
|
+
transition:
|
|
261
|
+
opacity 0.1s ease-in-out,
|
|
262
|
+
visibility 0.1s ease-in-out;
|
|
267
263
|
}
|
|
268
264
|
|
|
269
265
|
.tooltip-container[data-placement="top"] .tooltip[data-visible="true"],
|
|
270
|
-
.tooltip-container[data-placement="bottom"]
|
|
266
|
+
.tooltip-container[data-placement="bottom"]
|
|
267
|
+
.tooltip[data-visible="true"] {
|
|
271
268
|
transform: translateX(-50%) !important;
|
|
272
269
|
}
|
|
273
270
|
|
|
274
271
|
.tooltip-container[data-placement="left"] .tooltip[data-visible="true"],
|
|
275
|
-
.tooltip-container[data-placement="right"]
|
|
272
|
+
.tooltip-container[data-placement="right"]
|
|
273
|
+
.tooltip[data-visible="true"] {
|
|
276
274
|
transform: translateY(-50%) !important;
|
|
277
275
|
}
|
|
278
276
|
}
|
package/dist/atoms/index.d.ts
CHANGED
|
@@ -5,8 +5,6 @@ export { default as Card } from './Card.svelte';
|
|
|
5
5
|
export { default as CardHeader } from './CardHeader.svelte';
|
|
6
6
|
export { default as CardContent } from './CardContent.svelte';
|
|
7
7
|
export { default as CardFooter } from './CardFooter.svelte';
|
|
8
|
-
export { default as CardTitle } from './CardTitle.svelte';
|
|
9
|
-
export { default as CardDescription } from './CardDescription.svelte';
|
|
10
8
|
export { default as Input } from './Input.svelte';
|
|
11
9
|
export { default as Textarea } from './Textarea.svelte';
|
|
12
10
|
export { default as Select } from './Select.svelte';
|
package/dist/atoms/index.js
CHANGED
|
@@ -5,8 +5,6 @@ export { default as Card } from './Card.svelte';
|
|
|
5
5
|
export { default as CardHeader } from './CardHeader.svelte';
|
|
6
6
|
export { default as CardContent } from './CardContent.svelte';
|
|
7
7
|
export { default as CardFooter } from './CardFooter.svelte';
|
|
8
|
-
export { default as CardTitle } from './CardTitle.svelte';
|
|
9
|
-
export { default as CardDescription } from './CardDescription.svelte';
|
|
10
8
|
export { default as Input } from './Input.svelte';
|
|
11
9
|
export { default as Textarea } from './Textarea.svelte';
|
|
12
10
|
export { default as Select } from './Select.svelte';
|
|
@@ -5,8 +5,6 @@ export { default as Card } from './Card.svelte';
|
|
|
5
5
|
export { default as CardHeader } from './CardHeader.svelte';
|
|
6
6
|
export { default as CardContent } from './CardContent.svelte';
|
|
7
7
|
export { default as CardFooter } from './CardFooter.svelte';
|
|
8
|
-
export { default as CardTitle } from './CardTitle.svelte';
|
|
9
|
-
export { default as CardDescription } from './CardDescription.svelte';
|
|
10
8
|
export { default as Input } from './Input.svelte';
|
|
11
9
|
export { default as Textarea } from './Textarea.svelte';
|
|
12
10
|
export { default as Select } from './Select.svelte';
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/components/atoms/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,IAAI,MAAM,EAAE,MAAM,iBAAiB,CAAC;AACpD,OAAO,EAAE,OAAO,IAAI,KAAK,EAAE,MAAM,gBAAgB,CAAC;AAClD,OAAO,EAAE,OAAO,IAAI,MAAM,EAAE,MAAM,iBAAiB,CAAC;AACpD,OAAO,EAAE,OAAO,IAAI,IAAI,EAAE,MAAM,eAAe,CAAC;AAChD,OAAO,EAAE,OAAO,IAAI,UAAU,EAAE,MAAM,qBAAqB,CAAC;AAC5D,OAAO,EAAE,OAAO,IAAI,WAAW,EAAE,MAAM,sBAAsB,CAAC;AAC9D,OAAO,EAAE,OAAO,IAAI,UAAU,EAAE,MAAM,qBAAqB,CAAC;AAC5D,OAAO,EAAE,OAAO,IAAI,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/components/atoms/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,IAAI,MAAM,EAAE,MAAM,iBAAiB,CAAC;AACpD,OAAO,EAAE,OAAO,IAAI,KAAK,EAAE,MAAM,gBAAgB,CAAC;AAClD,OAAO,EAAE,OAAO,IAAI,MAAM,EAAE,MAAM,iBAAiB,CAAC;AACpD,OAAO,EAAE,OAAO,IAAI,IAAI,EAAE,MAAM,eAAe,CAAC;AAChD,OAAO,EAAE,OAAO,IAAI,UAAU,EAAE,MAAM,qBAAqB,CAAC;AAC5D,OAAO,EAAE,OAAO,IAAI,WAAW,EAAE,MAAM,sBAAsB,CAAC;AAC9D,OAAO,EAAE,OAAO,IAAI,UAAU,EAAE,MAAM,qBAAqB,CAAC;AAC5D,OAAO,EAAE,OAAO,IAAI,KAAK,EAAE,MAAM,gBAAgB,CAAC;AAClD,OAAO,EAAE,OAAO,IAAI,QAAQ,EAAE,MAAM,mBAAmB,CAAC;AACxD,OAAO,EAAE,OAAO,IAAI,MAAM,EAAE,MAAM,iBAAiB,CAAC;AACpD,OAAO,EAAE,OAAO,IAAI,WAAW,EAAE,MAAM,sBAAsB,CAAC;AAC9D,OAAO,EAAE,OAAO,IAAI,OAAO,EAAE,MAAM,kBAAkB,CAAC;AACtD,OAAO,EAAE,OAAO,IAAI,WAAW,EAAE,MAAM,sBAAsB,CAAC"}
|
|
@@ -5,6 +5,13 @@ export { default as Dropdown } from './Dropdown.svelte';
|
|
|
5
5
|
export { default as Form } from './Form.svelte';
|
|
6
6
|
export { default as ImageUpload } from './ImageUpload.svelte';
|
|
7
7
|
export { default as Modal } from './Modal.svelte';
|
|
8
|
+
export { default as NavigationMenu } from './NavigationMenu.svelte';
|
|
9
|
+
export { default as NavigationMenuList } from './NavigationMenuList.svelte';
|
|
10
|
+
export { default as NavigationMenuItem } from './NavigationMenuItem.svelte';
|
|
11
|
+
export { default as NavigationMenuTrigger } from './NavigationMenuTrigger.svelte';
|
|
12
|
+
export { default as NavigationMenuContent } from './NavigationMenuContent.svelte';
|
|
13
|
+
export { default as NavigationMenuLink } from './NavigationMenuLink.svelte';
|
|
14
|
+
export { default as Sidebar } from './Sidebar.svelte';
|
|
8
15
|
export { default as SlideUp } from './SlideUp.svelte';
|
|
9
16
|
export { default as Tabs } from './Tabs.svelte';
|
|
10
17
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/components/molecules/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,IAAI,KAAK,EAAE,MAAM,gBAAgB,CAAC;AAClD,OAAO,EAAE,OAAO,IAAI,aAAa,EAAE,MAAM,wBAAwB,CAAC;AAClE,OAAO,EAAE,OAAO,IAAI,WAAW,EAAE,MAAM,sBAAsB,CAAC;AAC9D,OAAO,EAAE,OAAO,IAAI,QAAQ,EAAE,MAAM,mBAAmB,CAAC;AACxD,OAAO,EAAE,OAAO,IAAI,IAAI,EAAE,MAAM,eAAe,CAAC;AAChD,OAAO,EAAE,OAAO,IAAI,WAAW,EAAE,MAAM,sBAAsB,CAAC;AAC9D,OAAO,EAAE,OAAO,IAAI,KAAK,EAAE,MAAM,gBAAgB,CAAC;AAClD,OAAO,EAAE,OAAO,IAAI,OAAO,EAAE,MAAM,kBAAkB,CAAC;AACtD,OAAO,EAAE,OAAO,IAAI,IAAI,EAAE,MAAM,eAAe,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/components/molecules/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,IAAI,KAAK,EAAE,MAAM,gBAAgB,CAAC;AAClD,OAAO,EAAE,OAAO,IAAI,aAAa,EAAE,MAAM,wBAAwB,CAAC;AAClE,OAAO,EAAE,OAAO,IAAI,WAAW,EAAE,MAAM,sBAAsB,CAAC;AAC9D,OAAO,EAAE,OAAO,IAAI,QAAQ,EAAE,MAAM,mBAAmB,CAAC;AACxD,OAAO,EAAE,OAAO,IAAI,IAAI,EAAE,MAAM,eAAe,CAAC;AAChD,OAAO,EAAE,OAAO,IAAI,WAAW,EAAE,MAAM,sBAAsB,CAAC;AAC9D,OAAO,EAAE,OAAO,IAAI,KAAK,EAAE,MAAM,gBAAgB,CAAC;AAClD,OAAO,EAAE,OAAO,IAAI,cAAc,EAAE,MAAM,yBAAyB,CAAC;AACpE,OAAO,EAAE,OAAO,IAAI,kBAAkB,EAAE,MAAM,6BAA6B,CAAC;AAC5E,OAAO,EAAE,OAAO,IAAI,kBAAkB,EAAE,MAAM,6BAA6B,CAAC;AAC5E,OAAO,EAAE,OAAO,IAAI,qBAAqB,EAAE,MAAM,gCAAgC,CAAC;AAClF,OAAO,EAAE,OAAO,IAAI,qBAAqB,EAAE,MAAM,gCAAgC,CAAC;AAClF,OAAO,EAAE,OAAO,IAAI,kBAAkB,EAAE,MAAM,6BAA6B,CAAC;AAC5E,OAAO,EAAE,OAAO,IAAI,OAAO,EAAE,MAAM,kBAAkB,CAAC;AACtD,OAAO,EAAE,OAAO,IAAI,OAAO,EAAE,MAAM,kBAAkB,CAAC;AACtD,OAAO,EAAE,OAAO,IAAI,IAAI,EAAE,MAAM,eAAe,CAAC"}
|
|
@@ -1,27 +1,45 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
+
import Button from "../atoms/Button.svelte";
|
|
3
|
+
|
|
2
4
|
interface Props {
|
|
3
5
|
isOpen?: boolean;
|
|
4
6
|
placement?: "bottom-start" | "bottom-end" | "top-start" | "top-end";
|
|
5
7
|
ariaLabel?: string;
|
|
8
|
+
selectedValue?: string | number | null;
|
|
9
|
+
options?: Array<{
|
|
10
|
+
value: string | number;
|
|
11
|
+
label: string;
|
|
12
|
+
disabled?: boolean;
|
|
13
|
+
}>;
|
|
14
|
+
onOptionClick?: (value: string | number) => void;
|
|
6
15
|
}
|
|
7
16
|
|
|
8
17
|
let {
|
|
9
18
|
isOpen = false,
|
|
10
19
|
placement = "bottom-start",
|
|
11
20
|
ariaLabel = "Menu",
|
|
21
|
+
selectedValue = null,
|
|
22
|
+
options = [],
|
|
23
|
+
onOptionClick,
|
|
12
24
|
children,
|
|
13
25
|
trigger,
|
|
14
26
|
...restProps
|
|
15
27
|
}: Props & { children?: any; trigger?: any } = $props();
|
|
16
28
|
|
|
17
29
|
let dropdownId = `dropdown-${Math.random().toString(36).substr(2, 9)}`;
|
|
18
|
-
let triggerElement
|
|
19
|
-
let menuElement
|
|
30
|
+
let triggerElement = $state<HTMLElement | null>(null);
|
|
31
|
+
let menuElement = $state<HTMLElement | null>(null);
|
|
32
|
+
let openedViaKeyboard = $state(false);
|
|
20
33
|
|
|
21
34
|
function handleKeydown(event: KeyboardEvent) {
|
|
22
35
|
if (!isOpen) {
|
|
23
|
-
if (
|
|
36
|
+
if (
|
|
37
|
+
event.key === "Enter" ||
|
|
38
|
+
event.key === " " ||
|
|
39
|
+
event.key === "ArrowDown"
|
|
40
|
+
) {
|
|
24
41
|
event.preventDefault();
|
|
42
|
+
openedViaKeyboard = true;
|
|
25
43
|
isOpen = true;
|
|
26
44
|
}
|
|
27
45
|
return;
|
|
@@ -58,21 +76,29 @@
|
|
|
58
76
|
function getMenuItems(): HTMLElement[] {
|
|
59
77
|
if (!menuElement) return [];
|
|
60
78
|
return Array.from(
|
|
61
|
-
menuElement.querySelectorAll<HTMLElement>(
|
|
79
|
+
menuElement.querySelectorAll<HTMLElement>(
|
|
80
|
+
'[role="menuitem"], button, a',
|
|
81
|
+
),
|
|
62
82
|
);
|
|
63
83
|
}
|
|
64
84
|
|
|
65
85
|
function focusNextItem() {
|
|
66
86
|
const items = getMenuItems();
|
|
67
|
-
const currentIndex = items.findIndex(
|
|
68
|
-
|
|
87
|
+
const currentIndex = items.findIndex(
|
|
88
|
+
(item) => item === document.activeElement,
|
|
89
|
+
);
|
|
90
|
+
const nextIndex =
|
|
91
|
+
currentIndex < items.length - 1 ? currentIndex + 1 : 0;
|
|
69
92
|
items[nextIndex]?.focus();
|
|
70
93
|
}
|
|
71
94
|
|
|
72
95
|
function focusPreviousItem() {
|
|
73
96
|
const items = getMenuItems();
|
|
74
|
-
const currentIndex = items.findIndex(
|
|
75
|
-
|
|
97
|
+
const currentIndex = items.findIndex(
|
|
98
|
+
(item) => item === document.activeElement,
|
|
99
|
+
);
|
|
100
|
+
const prevIndex =
|
|
101
|
+
currentIndex > 0 ? currentIndex - 1 : items.length - 1;
|
|
76
102
|
items[prevIndex]?.focus();
|
|
77
103
|
}
|
|
78
104
|
|
|
@@ -86,11 +112,41 @@
|
|
|
86
112
|
items[items.length - 1]?.focus();
|
|
87
113
|
}
|
|
88
114
|
|
|
89
|
-
// Focus first item when dropdown opens
|
|
115
|
+
// Focus first item when dropdown opens via keyboard
|
|
90
116
|
$effect(() => {
|
|
91
|
-
if (isOpen && menuElement) {
|
|
117
|
+
if (isOpen && menuElement && openedViaKeyboard) {
|
|
92
118
|
setTimeout(() => {
|
|
93
119
|
focusFirstItem();
|
|
120
|
+
openedViaKeyboard = false;
|
|
121
|
+
}, 0);
|
|
122
|
+
} else if (!isOpen) {
|
|
123
|
+
openedViaKeyboard = false;
|
|
124
|
+
}
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
// Mark selected items
|
|
128
|
+
$effect(() => {
|
|
129
|
+
if (isOpen && menuElement) {
|
|
130
|
+
// Small delay to ensure DOM is ready
|
|
131
|
+
setTimeout(() => {
|
|
132
|
+
const items = getMenuItems();
|
|
133
|
+
items.forEach((item) => {
|
|
134
|
+
if (selectedValue !== null && selectedValue !== undefined) {
|
|
135
|
+
const itemValue =
|
|
136
|
+
item.getAttribute("data-value") ||
|
|
137
|
+
item.textContent?.trim();
|
|
138
|
+
if (String(itemValue) === String(selectedValue)) {
|
|
139
|
+
item.setAttribute("data-selected", "true");
|
|
140
|
+
item.setAttribute("aria-selected", "true");
|
|
141
|
+
} else {
|
|
142
|
+
item.removeAttribute("data-selected");
|
|
143
|
+
item.removeAttribute("aria-selected");
|
|
144
|
+
}
|
|
145
|
+
} else {
|
|
146
|
+
item.removeAttribute("data-selected");
|
|
147
|
+
item.removeAttribute("aria-selected");
|
|
148
|
+
}
|
|
149
|
+
});
|
|
94
150
|
}, 0);
|
|
95
151
|
}
|
|
96
152
|
});
|
|
@@ -137,8 +193,8 @@
|
|
|
137
193
|
});
|
|
138
194
|
</script>
|
|
139
195
|
|
|
140
|
-
<div
|
|
141
|
-
class="relative inline-block"
|
|
196
|
+
<div
|
|
197
|
+
class="relative inline-block"
|
|
142
198
|
data-placement={placement}
|
|
143
199
|
onkeydown={handleKeydown}
|
|
144
200
|
{...restProps}
|
|
@@ -148,7 +204,7 @@
|
|
|
148
204
|
</div>
|
|
149
205
|
|
|
150
206
|
{#if isOpen}
|
|
151
|
-
<div
|
|
207
|
+
<div
|
|
152
208
|
bind:this={menuElement}
|
|
153
209
|
id={dropdownId}
|
|
154
210
|
class={dropdownContentClasses()}
|
|
@@ -156,7 +212,38 @@
|
|
|
156
212
|
aria-label={ariaLabel}
|
|
157
213
|
tabindex="-1"
|
|
158
214
|
>
|
|
159
|
-
{
|
|
215
|
+
{#if options.length > 0}
|
|
216
|
+
<div class="px-2 py-2">
|
|
217
|
+
{#each options as option (option.value)}
|
|
218
|
+
{@const buttonRestProps = {
|
|
219
|
+
"data-value": String(option.value),
|
|
220
|
+
} as any}
|
|
221
|
+
<div class="w-full my-0.5">
|
|
222
|
+
<Button
|
|
223
|
+
variant={selectedValue === option.value
|
|
224
|
+
? "outline"
|
|
225
|
+
: "ghost"}
|
|
226
|
+
size="sm"
|
|
227
|
+
isFullWidth={true}
|
|
228
|
+
disabled={option.disabled}
|
|
229
|
+
onclick={() => onOptionClick?.(option.value)}
|
|
230
|
+
{...buttonRestProps}
|
|
231
|
+
>
|
|
232
|
+
{option.label}
|
|
233
|
+
</Button>
|
|
234
|
+
</div>
|
|
235
|
+
{/each}
|
|
236
|
+
</div>
|
|
237
|
+
{:else if children}
|
|
238
|
+
{@render children?.()}
|
|
239
|
+
{/if}
|
|
160
240
|
</div>
|
|
161
241
|
{/if}
|
|
162
242
|
</div>
|
|
243
|
+
|
|
244
|
+
<style>
|
|
245
|
+
:global([role="menu"] button) {
|
|
246
|
+
justify-content: flex-start;
|
|
247
|
+
text-align: left;
|
|
248
|
+
}
|
|
249
|
+
</style>
|
|
@@ -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 {
|
|
3
|
-
import
|
|
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}
|