zabi-components 5.0.17 → 5.0.19
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 +124 -23
- package/dist/SSRSafe.svelte +0 -3
- package/dist/atoms/Badge.svelte +19 -34
- package/dist/atoms/Badge.svelte.d.ts +6 -5
- package/dist/atoms/Button.svelte +10 -9
- package/dist/atoms/Button.svelte.d.ts +3 -2
- package/dist/atoms/Card.svelte +74 -23
- package/dist/atoms/Card.svelte.d.ts +3 -1
- package/dist/atoms/CardContent.svelte +24 -0
- package/dist/atoms/CardContent.svelte.d.ts +8 -0
- package/dist/atoms/CardDescription.svelte +24 -0
- package/dist/atoms/CardDescription.svelte.d.ts +8 -0
- package/dist/atoms/CardFooter.svelte +24 -0
- package/dist/atoms/CardFooter.svelte.d.ts +8 -0
- package/dist/atoms/CardHeader.svelte +24 -0
- package/dist/atoms/CardHeader.svelte.d.ts +8 -0
- package/dist/atoms/CardTitle.svelte +33 -0
- package/dist/atoms/CardTitle.svelte.d.ts +9 -0
- package/dist/atoms/Checkbox.svelte +0 -4
- package/dist/atoms/CodeBlock.svelte +0 -3
- package/dist/atoms/ColorPicker.svelte +33 -65
- package/dist/atoms/FeatureCard.svelte +0 -10
- package/dist/atoms/FeatureCard.svelte.d.ts +0 -8
- package/dist/atoms/Heading.svelte +0 -2
- package/dist/atoms/Input.svelte +3 -12
- package/dist/atoms/Input.svelte.d.ts +3 -2
- package/dist/atoms/Progress.svelte +0 -3
- package/dist/atoms/Select.svelte +0 -14
- package/dist/atoms/Textarea.svelte +3 -9
- package/dist/atoms/Textarea.svelte.d.ts +3 -1
- package/dist/atoms/ThemeToggle.svelte +0 -7
- package/dist/atoms/Toast.svelte +0 -1
- package/dist/atoms/Toggle.svelte +0 -6
- package/dist/atoms/Tooltip.svelte +0 -12
- package/dist/atoms/index.d.ts +5 -0
- package/dist/atoms/index.js +5 -1
- package/dist/components/atoms/index.d.ts +5 -0
- package/dist/components/atoms/index.d.ts.map +1 -1
- package/dist/components/index.d.ts.map +1 -1
- package/dist/components/molecules/index.d.ts.map +1 -1
- package/dist/components/organisms/index.d.ts.map +1 -1
- package/dist/index.js +0 -23
- package/dist/molecules/Alert.svelte +3 -13
- package/dist/molecules/Alert.svelte.d.ts +2 -1
- package/dist/molecules/ComponentDemo.svelte +0 -2
- package/dist/molecules/ContactForm.svelte +21 -12
- package/dist/molecules/Dropdown.svelte +100 -6
- package/dist/molecules/Dropdown.svelte.d.ts +1 -0
- package/dist/molecules/Form.svelte +0 -1
- package/dist/molecules/ImageUpload.svelte +0 -4
- package/dist/molecules/Modal.svelte +29 -5
- package/dist/molecules/SlideUp.svelte +0 -2
- package/dist/molecules/Tabs.svelte +0 -2
- package/dist/molecules/index.js +0 -1
- package/dist/organisms/Navbar.svelte +0 -7
- package/dist/organisms/Navigation.svelte +0 -6
- package/dist/organisms/index.js +0 -1
- package/dist/routes/lib/focus-utils.d.ts +23 -0
- package/dist/routes/lib/focus-utils.ts +112 -0
- package/dist/routes/lib/variant-utils.ts +3 -1
- package/dist/zabi-components-colors.css +246 -178
- package/dist/zabi-components-theme-dark-only.css +11 -2
- package/dist/zabi-components-theme-dark.css +11 -2
- package/dist/zabi-components-theme-only.css +32 -24
- package/dist/zabi-components-theme.css +32 -24
- package/dist/zabi-components.css +259 -83
- package/package.json +5 -4
|
@@ -1,4 +1,7 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
+
import { onMount } from "svelte";
|
|
3
|
+
import { trapFocus, saveFocus, returnFocus, focusFirstElement } from "../routes/lib/focus-utils.js";
|
|
4
|
+
|
|
2
5
|
type Size = "sm" | "md" | "lg";
|
|
3
6
|
|
|
4
7
|
interface Props {
|
|
@@ -20,6 +23,9 @@
|
|
|
20
23
|
...restProps
|
|
21
24
|
}: Props & { children?: any; footer?: any } = $props();
|
|
22
25
|
|
|
26
|
+
let modalContainer: HTMLDivElement;
|
|
27
|
+
let cleanupFocusTrap: (() => void) | null = null;
|
|
28
|
+
|
|
23
29
|
const sizeClasses = $derived(
|
|
24
30
|
{
|
|
25
31
|
sm: "w-full md:w-[24rem]",
|
|
@@ -30,11 +36,31 @@
|
|
|
30
36
|
|
|
31
37
|
function closeModal(event?: Event) {
|
|
32
38
|
isOpen = false;
|
|
39
|
+
if (cleanupFocusTrap) {
|
|
40
|
+
cleanupFocusTrap();
|
|
41
|
+
cleanupFocusTrap = null;
|
|
42
|
+
}
|
|
43
|
+
returnFocus();
|
|
33
44
|
if (onclick && event) {
|
|
34
45
|
onclick(event);
|
|
35
46
|
}
|
|
36
47
|
}
|
|
37
48
|
|
|
49
|
+
// Handle focus trap when modal opens
|
|
50
|
+
$effect(() => {
|
|
51
|
+
if (isOpen && modalContainer) {
|
|
52
|
+
saveFocus();
|
|
53
|
+
cleanupFocusTrap = trapFocus(modalContainer);
|
|
54
|
+
// Small delay to ensure modal is rendered
|
|
55
|
+
setTimeout(() => {
|
|
56
|
+
focusFirstElement(modalContainer);
|
|
57
|
+
}, 0);
|
|
58
|
+
} else if (!isOpen && cleanupFocusTrap) {
|
|
59
|
+
cleanupFocusTrap();
|
|
60
|
+
cleanupFocusTrap = null;
|
|
61
|
+
}
|
|
62
|
+
});
|
|
63
|
+
|
|
38
64
|
function handleBackdropClick(event: Event) {
|
|
39
65
|
if (event.target === event.currentTarget) {
|
|
40
66
|
closeModal(event);
|
|
@@ -54,7 +80,7 @@
|
|
|
54
80
|
|
|
55
81
|
{#if isOpen}
|
|
56
82
|
<div
|
|
57
|
-
class="fixed inset-0 bg-
|
|
83
|
+
class="fixed inset-0 bg-overlay flex items-end md:items-center justify-center p-0 md:p-4 z-modal"
|
|
58
84
|
onclick={handleBackdropClick}
|
|
59
85
|
onkeydown={handleKeydown}
|
|
60
86
|
role="dialog"
|
|
@@ -63,9 +89,9 @@
|
|
|
63
89
|
tabindex="-1"
|
|
64
90
|
>
|
|
65
91
|
<div
|
|
66
|
-
|
|
92
|
+
bind:this={modalContainer}
|
|
93
|
+
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"
|
|
67
94
|
>
|
|
68
|
-
<!-- Header -->
|
|
69
95
|
{#if title}
|
|
70
96
|
<div class="flex items-center justify-between px-6 pt-6 pb-4">
|
|
71
97
|
<h2
|
|
@@ -85,12 +111,10 @@
|
|
|
85
111
|
</div>
|
|
86
112
|
{/if}
|
|
87
113
|
|
|
88
|
-
<!-- Content -->
|
|
89
114
|
<div class="px-6 pb-6 flex-1">
|
|
90
115
|
{@render children?.()}
|
|
91
116
|
</div>
|
|
92
117
|
|
|
93
|
-
<!-- Footer -->
|
|
94
118
|
{#if footer}
|
|
95
119
|
<div class="flex justify-end gap-3 px-6 pb-6 pt-4">
|
|
96
120
|
{@render footer?.()}
|
|
@@ -50,7 +50,6 @@
|
|
|
50
50
|
<div
|
|
51
51
|
class="fixed bottom-0 left-0 right-0 bg-base-50 rounded-t-3xl shadow-xl z-modal max-h-[90vh] overflow-y-auto animate-[slideUp_0.3s_ease-out] flex flex-col"
|
|
52
52
|
>
|
|
53
|
-
<!-- Header -->
|
|
54
53
|
{#if title}
|
|
55
54
|
<div class="flex items-center justify-between px-6 pt-6 pb-4">
|
|
56
55
|
<h2
|
|
@@ -70,7 +69,6 @@
|
|
|
70
69
|
</div>
|
|
71
70
|
{/if}
|
|
72
71
|
|
|
73
|
-
<!-- Content -->
|
|
74
72
|
<div class="px-6 pb-6 flex-1">
|
|
75
73
|
{@render children?.()}
|
|
76
74
|
</div>
|
|
@@ -35,7 +35,6 @@
|
|
|
35
35
|
</script>
|
|
36
36
|
|
|
37
37
|
<div class="tabs-container">
|
|
38
|
-
<!-- Tab List -->
|
|
39
38
|
<div
|
|
40
39
|
class="flex border-b border-base-200"
|
|
41
40
|
role="tablist"
|
|
@@ -61,7 +60,6 @@
|
|
|
61
60
|
{/each}
|
|
62
61
|
</div>
|
|
63
62
|
|
|
64
|
-
<!-- Tab Content -->
|
|
65
63
|
<div class="mt-4">
|
|
66
64
|
{@render children?.({ activeTab })}
|
|
67
65
|
</div>
|
package/dist/molecules/index.js
CHANGED
|
@@ -25,8 +25,6 @@
|
|
|
25
25
|
}
|
|
26
26
|
|
|
27
27
|
function handleBrandClick() {
|
|
28
|
-
// Brand click is now handled by the parent component
|
|
29
|
-
// through event forwarding
|
|
30
28
|
}
|
|
31
29
|
</script>
|
|
32
30
|
|
|
@@ -36,7 +34,6 @@
|
|
|
36
34
|
>
|
|
37
35
|
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
|
38
36
|
<div class="flex justify-between items-center h-16">
|
|
39
|
-
<!-- Brand -->
|
|
40
37
|
<div class="shrink-0">
|
|
41
38
|
<button
|
|
42
39
|
type="button"
|
|
@@ -47,14 +44,12 @@
|
|
|
47
44
|
</button>
|
|
48
45
|
</div>
|
|
49
46
|
|
|
50
|
-
<!-- Desktop Navigation -->
|
|
51
47
|
<div class="hidden md:block">
|
|
52
48
|
<div class="ml-10 flex items-baseline space-x-4">
|
|
53
49
|
{@render nav?.()}
|
|
54
50
|
</div>
|
|
55
51
|
</div>
|
|
56
52
|
|
|
57
|
-
<!-- Right side -->
|
|
58
53
|
<div class="hidden md:block">
|
|
59
54
|
<div class="ml-4 flex items-center space-x-4">
|
|
60
55
|
{@render actions?.()}
|
|
@@ -64,7 +59,6 @@
|
|
|
64
59
|
</div>
|
|
65
60
|
</div>
|
|
66
61
|
|
|
67
|
-
<!-- Mobile menu button -->
|
|
68
62
|
<div class="md:hidden">
|
|
69
63
|
<button
|
|
70
64
|
type="button"
|
|
@@ -77,7 +71,6 @@
|
|
|
77
71
|
</div>
|
|
78
72
|
</div>
|
|
79
73
|
|
|
80
|
-
<!-- Mobile menu -->
|
|
81
74
|
{#if isMenuOpen}
|
|
82
75
|
<div class="md:hidden">
|
|
83
76
|
<div
|
|
@@ -32,21 +32,18 @@
|
|
|
32
32
|
}
|
|
33
33
|
}
|
|
34
34
|
|
|
35
|
-
// Derived class for ul container based on variant
|
|
36
35
|
const ulClasses = $derived(() => {
|
|
37
36
|
return variant === "sidebar"
|
|
38
37
|
? "flex flex-col gap-1"
|
|
39
38
|
: "flex items-center justify-between gap-1";
|
|
40
39
|
});
|
|
41
40
|
|
|
42
|
-
// Function to get nav item classes based on active state
|
|
43
41
|
function getNavItemClasses(itemHref: string): string {
|
|
44
42
|
const baseClasses = "flex flex-col gap-1 grow h-full items-center justify-center min-h-0 min-w-0 relative shrink-0 cursor-pointer";
|
|
45
43
|
|
|
46
44
|
return baseClasses;
|
|
47
45
|
}
|
|
48
46
|
|
|
49
|
-
// Function to get icon container classes based on active state
|
|
50
47
|
function getIconContainerClasses(itemHref: string): string {
|
|
51
48
|
const isActive = currentPath === itemHref;
|
|
52
49
|
const baseClasses = "flex flex-col items-center justify-center overflow-clip relative rounded-[20px] shrink-0 transition-colors duration-200";
|
|
@@ -57,12 +54,10 @@
|
|
|
57
54
|
return `${baseClasses}`;
|
|
58
55
|
}
|
|
59
56
|
|
|
60
|
-
// Function to get state layer classes
|
|
61
57
|
function getStateLayerClasses(): string {
|
|
62
58
|
return "box-border flex gap-1 h-10 items-center px-4 py-2 relative shrink-0";
|
|
63
59
|
}
|
|
64
60
|
|
|
65
|
-
// Function to get label classes based on active state
|
|
66
61
|
function getLabelClasses(itemHref: string): string {
|
|
67
62
|
const isActive = currentPath === itemHref;
|
|
68
63
|
const baseClasses = "font-medium leading-4 relative shrink-0 text-center text-nowrap tracking-[0.5px] whitespace-pre text-xs";
|
|
@@ -73,7 +68,6 @@
|
|
|
73
68
|
return `${baseClasses} text-description`;
|
|
74
69
|
}
|
|
75
70
|
|
|
76
|
-
// Function to get icon classes based on active state (matches text color)
|
|
77
71
|
function getIconClasses(itemHref: string): string {
|
|
78
72
|
const isActive = currentPath === itemHref;
|
|
79
73
|
|
package/dist/organisms/index.js
CHANGED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Focus management utilities for accessibility
|
|
3
|
+
*/
|
|
4
|
+
/**
|
|
5
|
+
* Get all focusable elements within a container
|
|
6
|
+
*/
|
|
7
|
+
export declare function getFocusableElements(container: HTMLElement): HTMLElement[];
|
|
8
|
+
/**
|
|
9
|
+
* Trap focus within a container element
|
|
10
|
+
*/
|
|
11
|
+
export declare function trapFocus(container: HTMLElement): () => void;
|
|
12
|
+
/**
|
|
13
|
+
* Save the currently focused element
|
|
14
|
+
*/
|
|
15
|
+
export declare function saveFocus(): void;
|
|
16
|
+
/**
|
|
17
|
+
* Return focus to the previously focused element
|
|
18
|
+
*/
|
|
19
|
+
export declare function returnFocus(): void;
|
|
20
|
+
/**
|
|
21
|
+
* Focus the first focusable element in a container
|
|
22
|
+
*/
|
|
23
|
+
export declare function focusFirstElement(container: HTMLElement): void;
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Focus management utilities for accessibility
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Get all focusable elements within a container
|
|
7
|
+
*/
|
|
8
|
+
export function getFocusableElements(container: HTMLElement): HTMLElement[] {
|
|
9
|
+
const selector = [
|
|
10
|
+
'button:not([disabled])',
|
|
11
|
+
'[href]',
|
|
12
|
+
'input:not([disabled])',
|
|
13
|
+
'select:not([disabled])',
|
|
14
|
+
'textarea:not([disabled])',
|
|
15
|
+
'[tabindex]:not([tabindex="-1"])',
|
|
16
|
+
].join(', ');
|
|
17
|
+
|
|
18
|
+
return Array.from(container.querySelectorAll<HTMLElement>(selector)).filter(
|
|
19
|
+
(el) => {
|
|
20
|
+
// Filter out elements that are not visible
|
|
21
|
+
const style = window.getComputedStyle(el);
|
|
22
|
+
return style.display !== 'none' && style.visibility !== 'hidden';
|
|
23
|
+
}
|
|
24
|
+
);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Trap focus within a container element
|
|
29
|
+
*/
|
|
30
|
+
export function trapFocus(container: HTMLElement): () => void {
|
|
31
|
+
const focusableElements = getFocusableElements(container);
|
|
32
|
+
|
|
33
|
+
if (focusableElements.length === 0) {
|
|
34
|
+
return () => {}; // No-op cleanup
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const firstElement = focusableElements[0];
|
|
38
|
+
const lastElement = focusableElements[focusableElements.length - 1];
|
|
39
|
+
|
|
40
|
+
// Focus first element
|
|
41
|
+
firstElement.focus();
|
|
42
|
+
|
|
43
|
+
function handleKeydown(event: KeyboardEvent) {
|
|
44
|
+
if (event.key !== 'Tab') {
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const currentFocus = document.activeElement as HTMLElement;
|
|
49
|
+
|
|
50
|
+
if (!focusableElements.includes(currentFocus)) {
|
|
51
|
+
event.preventDefault();
|
|
52
|
+
firstElement.focus();
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
if (event.shiftKey) {
|
|
57
|
+
// Shift + Tab
|
|
58
|
+
if (currentFocus === firstElement) {
|
|
59
|
+
event.preventDefault();
|
|
60
|
+
lastElement.focus();
|
|
61
|
+
}
|
|
62
|
+
} else {
|
|
63
|
+
// Tab
|
|
64
|
+
if (currentFocus === lastElement) {
|
|
65
|
+
event.preventDefault();
|
|
66
|
+
firstElement.focus();
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
container.addEventListener('keydown', handleKeydown);
|
|
72
|
+
|
|
73
|
+
// Return cleanup function
|
|
74
|
+
return () => {
|
|
75
|
+
container.removeEventListener('keydown', handleKeydown);
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Store the element that had focus before opening a modal/dialog
|
|
81
|
+
*/
|
|
82
|
+
let previousFocus: HTMLElement | null = null;
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Save the currently focused element
|
|
86
|
+
*/
|
|
87
|
+
export function saveFocus(): void {
|
|
88
|
+
if (typeof document !== 'undefined') {
|
|
89
|
+
previousFocus = document.activeElement as HTMLElement;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Return focus to the previously focused element
|
|
95
|
+
*/
|
|
96
|
+
export function returnFocus(): void {
|
|
97
|
+
if (previousFocus && typeof document !== 'undefined') {
|
|
98
|
+
previousFocus.focus();
|
|
99
|
+
previousFocus = null;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Focus the first focusable element in a container
|
|
105
|
+
*/
|
|
106
|
+
export function focusFirstElement(container: HTMLElement): void {
|
|
107
|
+
const focusableElements = getFocusableElements(container);
|
|
108
|
+
if (focusableElements.length > 0) {
|
|
109
|
+
focusableElements[0].focus();
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
@@ -2,7 +2,9 @@
|
|
|
2
2
|
* Utility functions for handling component variants with semantic colors
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
import type { SemanticVariant } from '../../types/variants.js';
|
|
6
|
+
|
|
7
|
+
export type Variant = SemanticVariant;
|
|
6
8
|
|
|
7
9
|
/**
|
|
8
10
|
* Get variant classes for input components (Input, Textarea)
|