@classic-homes/theme-svelte 0.1.52 → 0.1.54
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/__tests__/a11y/Alert.a11y.test.js +84 -0
- package/dist/__tests__/a11y/AlertDialog.a11y.test.js +126 -0
- package/dist/__tests__/a11y/Button.a11y.test.js +66 -0
- package/dist/__tests__/a11y/Card.a11y.test.js +77 -0
- package/dist/__tests__/a11y/Combobox.a11y.test.js +209 -0
- package/dist/__tests__/a11y/Dialog.a11y.test.js +103 -0
- package/dist/__tests__/a11y/DropdownMenu.a11y.test.js +104 -0
- package/dist/__tests__/a11y/FileUpload.a11y.test.js +158 -0
- package/dist/__tests__/a11y/Icon.a11y.test.js +119 -0
- package/dist/__tests__/a11y/Input.a11y.test.js +96 -0
- package/dist/__tests__/a11y/LiveRegion.a11y.test.js +102 -0
- package/dist/__tests__/a11y/Switch.a11y.test.js +95 -0
- package/dist/__tests__/a11y/Tabs.a11y.test.js +97 -0
- package/dist/__tests__/a11y/Textarea.a11y.test.js +111 -0
- package/dist/__tests__/setup.js +6 -0
- package/dist/lib/components/Alert.svelte +1 -1
- package/dist/lib/components/AlertDialog.svelte +1 -1
- package/dist/lib/components/AlertDialog.svelte.d.ts +2 -2
- package/dist/lib/components/Badge.svelte +49 -12
- package/dist/lib/components/Badge.svelte.d.ts +9 -0
- package/dist/lib/components/Button.svelte +14 -2
- package/dist/lib/components/CardTitle.svelte +9 -3
- package/dist/lib/components/CardTitle.svelte.d.ts +2 -0
- package/dist/lib/components/Combobox.svelte +19 -2
- package/dist/lib/components/DataPanelHeader.svelte +4 -4
- package/dist/lib/components/DataPanelTab.svelte +1 -1
- package/dist/lib/components/DataTable.svelte +7 -7
- package/dist/lib/components/DataTable.svelte.d.ts +5 -5
- package/dist/lib/components/DateTimePicker.svelte +7 -4
- package/dist/lib/components/Dialog.svelte +6 -6
- package/dist/lib/components/DropdownMenu.svelte +14 -10
- package/dist/lib/components/FileUpload.svelte +5 -10
- package/dist/lib/components/FormField.svelte +7 -1
- package/dist/lib/components/HeaderSearch.svelte +9 -9
- package/dist/lib/components/Icon.svelte +5 -5
- package/dist/lib/components/Input.svelte +32 -0
- package/dist/lib/components/Input.svelte.d.ts +8 -0
- package/dist/lib/components/LiveRegion.svelte +61 -0
- package/dist/lib/components/LiveRegion.svelte.d.ts +18 -0
- package/dist/lib/components/LoadingOverlay.svelte +148 -0
- package/dist/lib/components/LoadingOverlay.svelte.d.ts +29 -0
- package/dist/lib/components/LoadingPage.svelte +86 -0
- package/dist/lib/components/LoadingPage.svelte.d.ts +20 -0
- package/dist/lib/components/LoadingSection.svelte +54 -0
- package/dist/lib/components/LoadingSection.svelte.d.ts +25 -0
- package/dist/lib/components/MultiSelect.svelte +23 -2
- package/dist/lib/components/NumberInput.svelte +4 -4
- package/dist/lib/components/OTPInput.svelte +14 -0
- package/dist/lib/components/PasswordInput.svelte +14 -1
- package/dist/lib/components/PasswordInput.svelte.d.ts +4 -0
- package/dist/lib/components/ProgressBar.svelte +91 -0
- package/dist/lib/components/ProgressBar.svelte.d.ts +18 -0
- package/dist/lib/components/RadioGroup.svelte +5 -5
- package/dist/lib/components/Select.svelte +13 -1
- package/dist/lib/components/Signature.svelte +5 -5
- package/dist/lib/components/Skeleton.svelte +13 -3
- package/dist/lib/components/Skeleton.svelte.d.ts +2 -0
- package/dist/lib/components/SkipLink.svelte +67 -0
- package/dist/lib/components/SkipLink.svelte.d.ts +25 -0
- package/dist/lib/components/Slider.svelte +6 -2
- package/dist/lib/components/Switch.svelte +8 -0
- package/dist/lib/components/Switch.svelte.d.ts +4 -0
- package/dist/lib/components/Tabs.svelte +15 -5
- package/dist/lib/components/Tabs.svelte.d.ts +6 -0
- package/dist/lib/components/Textarea.svelte +19 -0
- package/dist/lib/components/Textarea.svelte.d.ts +8 -0
- package/dist/lib/components/Toast.svelte +1 -1
- package/dist/lib/components/layout/AuthLayout.svelte +1 -1
- package/dist/lib/components/layout/DashboardLayout.svelte +1 -1
- package/dist/lib/components/layout/Footer.svelte +1 -1
- package/dist/lib/components/layout/PublicLayout.svelte +2 -2
- package/dist/lib/components/layout/QuickLinks.svelte +3 -3
- package/dist/lib/components/layout/Sidebar.svelte +1 -1
- package/dist/lib/components/layout/sidebar/SidebarFlyout.svelte +1 -2
- package/dist/lib/components/layout/sidebar/SidebarNavItem.svelte +20 -21
- package/dist/lib/components/layout/sidebar/SidebarSection.svelte +2 -2
- package/dist/lib/components/notifications/NotificationFilters.svelte +1 -1
- package/dist/lib/components/notifications/NotificationItem.svelte +2 -1
- package/dist/lib/composables/index.d.ts +8 -0
- package/dist/lib/composables/index.js +9 -0
- package/dist/lib/composables/useAnnounce.svelte.d.ts +65 -0
- package/dist/lib/composables/useAnnounce.svelte.js +76 -0
- package/dist/lib/composables/useArrowNavigation.svelte.d.ts +42 -0
- package/dist/lib/composables/useArrowNavigation.svelte.js +138 -0
- package/dist/lib/composables/useClickOutside.svelte.d.ts +88 -0
- package/dist/lib/composables/useClickOutside.svelte.js +100 -0
- package/dist/lib/composables/useEscapeKey.svelte.d.ts +48 -0
- package/dist/lib/composables/useEscapeKey.svelte.js +50 -0
- package/dist/lib/composables/useFocusRef.svelte.d.ts +46 -0
- package/dist/lib/composables/useFocusRef.svelte.js +92 -0
- package/dist/lib/composables/useFocusTrap.svelte.d.ts +42 -0
- package/dist/lib/composables/useFocusTrap.svelte.js +136 -0
- package/dist/lib/composables/useReducedMotion.svelte.d.ts +47 -0
- package/dist/lib/composables/useReducedMotion.svelte.js +67 -0
- package/dist/lib/composables/useRovingTabindex.svelte.d.ts +54 -0
- package/dist/lib/composables/useRovingTabindex.svelte.js +128 -0
- package/dist/lib/index.d.ts +7 -1
- package/dist/lib/index.js +11 -1
- package/dist/lib/stores/theme.svelte.d.ts +1 -1
- package/dist/lib/utils.d.ts +4 -2
- package/dist/lib/utils.js +5 -11
- package/package.json +11 -5
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { cn } from '../utils.js';
|
|
3
|
+
import type { Snippet } from 'svelte';
|
|
4
|
+
|
|
5
|
+
interface Props {
|
|
6
|
+
/**
|
|
7
|
+
* ID of the target element to skip to (without the # prefix).
|
|
8
|
+
* @example targetId="main-content"
|
|
9
|
+
*/
|
|
10
|
+
targetId: string;
|
|
11
|
+
/**
|
|
12
|
+
* Custom class name for the skip link.
|
|
13
|
+
* Note: The component uses the `.skip-link` class from @classic-homes/theme-styles
|
|
14
|
+
* which handles the visually hidden to visible transition on focus.
|
|
15
|
+
*/
|
|
16
|
+
class?: string;
|
|
17
|
+
/**
|
|
18
|
+
* Content of the skip link. Defaults to "Skip to main content".
|
|
19
|
+
*/
|
|
20
|
+
children?: Snippet;
|
|
21
|
+
/**
|
|
22
|
+
* Additional attributes
|
|
23
|
+
*/
|
|
24
|
+
[key: string]: unknown;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
let { targetId, class: className, children, ...restProps }: Props = $props();
|
|
28
|
+
|
|
29
|
+
function handleClick(e: MouseEvent) {
|
|
30
|
+
e.preventDefault();
|
|
31
|
+
const target = document.getElementById(targetId);
|
|
32
|
+
if (target) {
|
|
33
|
+
// Set tabIndex to make the target focusable if it isn't already
|
|
34
|
+
if (target.tabIndex === -1) {
|
|
35
|
+
target.setAttribute('tabindex', '-1');
|
|
36
|
+
}
|
|
37
|
+
target.focus();
|
|
38
|
+
// Scroll into view
|
|
39
|
+
target.scrollIntoView();
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
</script>
|
|
43
|
+
|
|
44
|
+
<!--
|
|
45
|
+
SkipLink - Accessible skip navigation link
|
|
46
|
+
|
|
47
|
+
Provides keyboard users a way to skip repetitive navigation and jump directly
|
|
48
|
+
to the main content. The link is visually hidden until focused.
|
|
49
|
+
|
|
50
|
+
@see https://www.w3.org/WAI/WCAG21/Techniques/general/G1
|
|
51
|
+
|
|
52
|
+
@example
|
|
53
|
+
// Basic usage - place at the very beginning of your page/layout
|
|
54
|
+
<SkipLink targetId="main-content">Skip to main content</SkipLink>
|
|
55
|
+
|
|
56
|
+
// Then add the target ID to your main content
|
|
57
|
+
<main id="main-content" tabindex="-1">
|
|
58
|
+
...
|
|
59
|
+
</main>
|
|
60
|
+
-->
|
|
61
|
+
<a href={`#${targetId}`} class={cn('skip-link', className)} onclick={handleClick} {...restProps}>
|
|
62
|
+
{#if children}
|
|
63
|
+
{@render children()}
|
|
64
|
+
{:else}
|
|
65
|
+
Skip to main content
|
|
66
|
+
{/if}
|
|
67
|
+
</a>
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { Snippet } from 'svelte';
|
|
2
|
+
interface Props {
|
|
3
|
+
/**
|
|
4
|
+
* ID of the target element to skip to (without the # prefix).
|
|
5
|
+
* @example targetId="main-content"
|
|
6
|
+
*/
|
|
7
|
+
targetId: string;
|
|
8
|
+
/**
|
|
9
|
+
* Custom class name for the skip link.
|
|
10
|
+
* Note: The component uses the `.skip-link` class from @classic-homes/theme-styles
|
|
11
|
+
* which handles the visually hidden to visible transition on focus.
|
|
12
|
+
*/
|
|
13
|
+
class?: string;
|
|
14
|
+
/**
|
|
15
|
+
* Content of the skip link. Defaults to "Skip to main content".
|
|
16
|
+
*/
|
|
17
|
+
children?: Snippet;
|
|
18
|
+
/**
|
|
19
|
+
* Additional attributes
|
|
20
|
+
*/
|
|
21
|
+
[key: string]: unknown;
|
|
22
|
+
}
|
|
23
|
+
declare const SkipLink: import("svelte").Component<Props, {}, "">;
|
|
24
|
+
type SkipLink = ReturnType<typeof SkipLink>;
|
|
25
|
+
export default SkipLink;
|
|
@@ -143,11 +143,15 @@
|
|
|
143
143
|
aria-describedby={describedBy}
|
|
144
144
|
>
|
|
145
145
|
<SliderPrimitive.Range class={styles.range()} />
|
|
146
|
-
{#each value as
|
|
146
|
+
{#each value as thumbValue, i}
|
|
147
147
|
<SliderPrimitive.Thumb
|
|
148
148
|
index={i}
|
|
149
149
|
class={styles.thumb()}
|
|
150
|
-
aria-label={value.length > 1 ?
|
|
150
|
+
aria-label={value.length > 1 ? `${label || 'Slider'} thumb ${i + 1}` : label || 'Slider'}
|
|
151
|
+
aria-valuenow={thumbValue}
|
|
152
|
+
aria-valuemin={min}
|
|
153
|
+
aria-valuemax={max}
|
|
154
|
+
aria-valuetext={formatValue(thumbValue)}
|
|
151
155
|
/>
|
|
152
156
|
{/each}
|
|
153
157
|
</SliderPrimitive.Root>
|
|
@@ -10,6 +10,10 @@
|
|
|
10
10
|
value?: string;
|
|
11
11
|
id?: string;
|
|
12
12
|
class?: string;
|
|
13
|
+
/** Accessible label for screen readers */
|
|
14
|
+
'aria-label'?: string;
|
|
15
|
+
/** ID of element that labels this switch */
|
|
16
|
+
'aria-labelledby'?: string;
|
|
13
17
|
onchange?: (checked: boolean) => void;
|
|
14
18
|
}
|
|
15
19
|
|
|
@@ -21,6 +25,8 @@
|
|
|
21
25
|
value,
|
|
22
26
|
id,
|
|
23
27
|
class: className,
|
|
28
|
+
'aria-label': ariaLabel,
|
|
29
|
+
'aria-labelledby': ariaLabelledby,
|
|
24
30
|
onchange,
|
|
25
31
|
...restProps
|
|
26
32
|
}: Props = $props();
|
|
@@ -43,6 +49,8 @@
|
|
|
43
49
|
{name}
|
|
44
50
|
{value}
|
|
45
51
|
{id}
|
|
52
|
+
aria-label={ariaLabel}
|
|
53
|
+
aria-labelledby={ariaLabelledby}
|
|
46
54
|
onCheckedChange={handleCheckedChange}
|
|
47
55
|
class={cn(
|
|
48
56
|
'peer relative inline-flex h-7 w-12 shrink-0 cursor-pointer items-center rounded-full border-2 border-transparent transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-primary data-[state=unchecked]:bg-input',
|
|
@@ -6,6 +6,10 @@ interface Props {
|
|
|
6
6
|
value?: string;
|
|
7
7
|
id?: string;
|
|
8
8
|
class?: string;
|
|
9
|
+
/** Accessible label for screen readers */
|
|
10
|
+
'aria-label'?: string;
|
|
11
|
+
/** ID of element that labels this switch */
|
|
12
|
+
'aria-labelledby'?: string;
|
|
9
13
|
onchange?: (checked: boolean) => void;
|
|
10
14
|
}
|
|
11
15
|
declare const Switch: import("svelte").Component<Props, {}, "checked">;
|
|
@@ -11,6 +11,12 @@
|
|
|
11
11
|
value?: string;
|
|
12
12
|
/** Unique ID for ARIA relationships */
|
|
13
13
|
id?: string;
|
|
14
|
+
/**
|
|
15
|
+
* Accessible label for the tab list.
|
|
16
|
+
* Describes the purpose of the tabs to screen reader users.
|
|
17
|
+
* @example label="Account settings"
|
|
18
|
+
*/
|
|
19
|
+
label: string;
|
|
14
20
|
/** Additional classes for container */
|
|
15
21
|
class?: string;
|
|
16
22
|
/** Tab panel content */
|
|
@@ -25,11 +31,15 @@
|
|
|
25
31
|
tabs,
|
|
26
32
|
value = $bindable(tabs[0]?.id ?? ''),
|
|
27
33
|
id = generatedId,
|
|
34
|
+
label,
|
|
28
35
|
class: className,
|
|
29
36
|
children,
|
|
30
37
|
onValueChange,
|
|
31
38
|
}: Props = $props();
|
|
32
39
|
|
|
40
|
+
// Store references to tab button elements for direct focus management
|
|
41
|
+
let tabRefs: Record<string, HTMLButtonElement | undefined> = $state({});
|
|
42
|
+
|
|
33
43
|
// Create a reactive object for context
|
|
34
44
|
// Initialize with empty values - the effect will set them immediately
|
|
35
45
|
const tabsState = $state({ activeTab: '', tabsId: '' });
|
|
@@ -80,9 +90,8 @@
|
|
|
80
90
|
const newTab = enabledTabs[newIndex];
|
|
81
91
|
if (newTab) {
|
|
82
92
|
selectTab(newTab.id);
|
|
83
|
-
// Focus the new tab
|
|
84
|
-
|
|
85
|
-
tabElement?.focus();
|
|
93
|
+
// Focus the new tab using direct element reference
|
|
94
|
+
tabRefs[newTab.id]?.focus();
|
|
86
95
|
}
|
|
87
96
|
}
|
|
88
97
|
</script>
|
|
@@ -91,11 +100,12 @@
|
|
|
91
100
|
<!-- Tab List -->
|
|
92
101
|
<div
|
|
93
102
|
role="tablist"
|
|
94
|
-
aria-label=
|
|
103
|
+
aria-label={label}
|
|
95
104
|
class="inline-flex h-10 items-center justify-center rounded-md bg-muted p-1 text-muted-foreground"
|
|
96
105
|
>
|
|
97
106
|
{#each tabs as tab, index (tab.id)}
|
|
98
107
|
<button
|
|
108
|
+
bind:this={tabRefs[tab.id]}
|
|
99
109
|
id={`${id}-tab-${tab.id}`}
|
|
100
110
|
role="tab"
|
|
101
111
|
type="button"
|
|
@@ -104,7 +114,7 @@
|
|
|
104
114
|
tabindex={value === tab.id ? 0 : -1}
|
|
105
115
|
disabled={tab.disabled}
|
|
106
116
|
class={cn(
|
|
107
|
-
'inline-flex items-center justify-center whitespace-nowrap rounded-sm px-3 py-1.5 text-sm font-medium ring-offset-background
|
|
117
|
+
'inline-flex items-center justify-center whitespace-nowrap rounded-sm px-3 py-1.5 text-sm font-medium ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2',
|
|
108
118
|
value === tab.id
|
|
109
119
|
? 'bg-background text-foreground shadow-sm'
|
|
110
120
|
: 'hover:bg-background-hover hover:text-foreground',
|
|
@@ -7,6 +7,12 @@ interface Props {
|
|
|
7
7
|
value?: string;
|
|
8
8
|
/** Unique ID for ARIA relationships */
|
|
9
9
|
id?: string;
|
|
10
|
+
/**
|
|
11
|
+
* Accessible label for the tab list.
|
|
12
|
+
* Describes the purpose of the tabs to screen reader users.
|
|
13
|
+
* @example label="Account settings"
|
|
14
|
+
*/
|
|
15
|
+
label: string;
|
|
10
16
|
/** Additional classes for container */
|
|
11
17
|
class?: string;
|
|
12
18
|
/** Tab panel content */
|
|
@@ -11,6 +11,14 @@
|
|
|
11
11
|
id?: string;
|
|
12
12
|
rows?: number;
|
|
13
13
|
class?: string;
|
|
14
|
+
/** Accessible label for screen readers (alternative to visible label) */
|
|
15
|
+
'aria-label'?: string;
|
|
16
|
+
/** ID of element that labels this textarea */
|
|
17
|
+
'aria-labelledby'?: string;
|
|
18
|
+
/** ID of element that describes this textarea (e.g., hint or error message) */
|
|
19
|
+
'aria-describedby'?: string;
|
|
20
|
+
/** Whether the textarea has an error */
|
|
21
|
+
'aria-invalid'?: boolean | 'true' | 'false';
|
|
14
22
|
onValueChange?: (value: string) => void;
|
|
15
23
|
[key: string]: unknown;
|
|
16
24
|
}
|
|
@@ -25,6 +33,10 @@
|
|
|
25
33
|
id,
|
|
26
34
|
rows = 3,
|
|
27
35
|
class: className,
|
|
36
|
+
'aria-label': ariaLabel,
|
|
37
|
+
'aria-labelledby': ariaLabelledby,
|
|
38
|
+
'aria-describedby': ariaDescribedby,
|
|
39
|
+
'aria-invalid': ariaInvalid,
|
|
28
40
|
onValueChange,
|
|
29
41
|
...restProps
|
|
30
42
|
}: Props = $props();
|
|
@@ -45,8 +57,15 @@
|
|
|
45
57
|
{name}
|
|
46
58
|
{id}
|
|
47
59
|
{rows}
|
|
60
|
+
aria-label={ariaLabel}
|
|
61
|
+
aria-labelledby={ariaLabelledby}
|
|
62
|
+
aria-describedby={ariaDescribedby}
|
|
63
|
+
aria-invalid={ariaInvalid}
|
|
48
64
|
class={cn(
|
|
49
65
|
'flex min-h-[80px] w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background placeholder:text-gray-500 dark:placeholder:text-gray-400 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 resize-y',
|
|
66
|
+
ariaInvalid === true || ariaInvalid === 'true'
|
|
67
|
+
? 'border-destructive focus-visible:ring-destructive'
|
|
68
|
+
: '',
|
|
50
69
|
className
|
|
51
70
|
)}
|
|
52
71
|
oninput={handleInput}
|
|
@@ -8,6 +8,14 @@ interface Props {
|
|
|
8
8
|
id?: string;
|
|
9
9
|
rows?: number;
|
|
10
10
|
class?: string;
|
|
11
|
+
/** Accessible label for screen readers (alternative to visible label) */
|
|
12
|
+
'aria-label'?: string;
|
|
13
|
+
/** ID of element that labels this textarea */
|
|
14
|
+
'aria-labelledby'?: string;
|
|
15
|
+
/** ID of element that describes this textarea (e.g., hint or error message) */
|
|
16
|
+
'aria-describedby'?: string;
|
|
17
|
+
/** Whether the textarea has an error */
|
|
18
|
+
'aria-invalid'?: boolean | 'true' | 'false';
|
|
11
19
|
onValueChange?: (value: string) => void;
|
|
12
20
|
[key: string]: unknown;
|
|
13
21
|
}
|
|
@@ -119,7 +119,7 @@
|
|
|
119
119
|
{#each footerLinks as link}
|
|
120
120
|
<a
|
|
121
121
|
href={link.href}
|
|
122
|
-
class="text-sm text-muted-foreground
|
|
122
|
+
class="text-sm text-muted-foreground hover:text-foreground"
|
|
123
123
|
target={link.external ? '_blank' : undefined}
|
|
124
124
|
rel={link.external ? 'noopener noreferrer' : undefined}
|
|
125
125
|
>
|
|
@@ -255,7 +255,7 @@
|
|
|
255
255
|
|
|
256
256
|
<!-- Main Content Area -->
|
|
257
257
|
<div
|
|
258
|
-
class="flex flex-1 flex-col transition-
|
|
258
|
+
class="flex flex-1 flex-col transition-[margin] duration-300 motion-reduce:transition-none"
|
|
259
259
|
style="margin-left: {sidebarWidth}px;"
|
|
260
260
|
>
|
|
261
261
|
{#if showHeader}
|
|
@@ -97,7 +97,7 @@
|
|
|
97
97
|
<a
|
|
98
98
|
href={item.href}
|
|
99
99
|
class={cn(
|
|
100
|
-
'text-sm font-bold uppercase
|
|
100
|
+
'text-sm font-bold uppercase hover:text-primary',
|
|
101
101
|
item.active ? 'text-primary' : 'text-muted-foreground'
|
|
102
102
|
)}
|
|
103
103
|
target={item.external ? '_blank' : undefined}
|
|
@@ -122,7 +122,7 @@
|
|
|
122
122
|
<a
|
|
123
123
|
href={item.href}
|
|
124
124
|
class={cn(
|
|
125
|
-
'block rounded-md px-3 py-2 text-sm font-bold uppercase
|
|
125
|
+
'block rounded-md px-3 py-2 text-sm font-bold uppercase',
|
|
126
126
|
item.active
|
|
127
127
|
? 'bg-primary/10 text-primary'
|
|
128
128
|
: 'text-muted-foreground hover:bg-accent hover:text-accent-foreground'
|
|
@@ -50,7 +50,7 @@
|
|
|
50
50
|
<a
|
|
51
51
|
href={link.href}
|
|
52
52
|
class={cn(
|
|
53
|
-
'flex items-center
|
|
53
|
+
'flex items-center overflow-hidden',
|
|
54
54
|
// List mode styles
|
|
55
55
|
display === 'list' && 'gap-3 rounded-md px-3 py-2 text-sm',
|
|
56
56
|
display === 'list' &&
|
|
@@ -93,7 +93,7 @@
|
|
|
93
93
|
|
|
94
94
|
<span
|
|
95
95
|
class={cn(
|
|
96
|
-
'truncate whitespace-nowrap transition-
|
|
96
|
+
'truncate whitespace-nowrap transition-[width,opacity] duration-300 motion-reduce:transition-none',
|
|
97
97
|
display === 'icons' ? 'w-0 opacity-0 flex-none' : 'flex-1 opacity-100'
|
|
98
98
|
)}
|
|
99
99
|
>
|
|
@@ -111,7 +111,7 @@
|
|
|
111
111
|
{#if link.external}
|
|
112
112
|
<svg
|
|
113
113
|
class={cn(
|
|
114
|
-
'h-4 w-4 shrink-0 transition-
|
|
114
|
+
'h-4 w-4 shrink-0 transition-opacity duration-300',
|
|
115
115
|
display === 'icons' ? 'hidden' : 'opacity-50'
|
|
116
116
|
)}
|
|
117
117
|
fill="none"
|
|
@@ -437,7 +437,7 @@
|
|
|
437
437
|
<!-- Sidebar: z-50 positions above overlay and content, below modals (z-100+) -->
|
|
438
438
|
<aside
|
|
439
439
|
class={cn(
|
|
440
|
-
'fixed left-0 top-0 z-[var(--z-sidebar,50)] flex h-full flex-col transition-
|
|
440
|
+
'fixed left-0 top-0 z-[var(--z-sidebar,50)] flex h-full flex-col transition-[width,transform] duration-300 motion-reduce:transition-none touch-pan-y',
|
|
441
441
|
// Variant-based background and text colors
|
|
442
442
|
isLight ? 'bg-background text-foreground' : 'bg-sidebar text-sidebar-foreground',
|
|
443
443
|
strongBorder ? 'border-r-2 border-black' : 'border-r border-black',
|
|
@@ -110,7 +110,6 @@
|
|
|
110
110
|
bind:this={flyoutElement}
|
|
111
111
|
class={cn(
|
|
112
112
|
'fixed min-w-48 rounded-md border shadow-lg z-[calc(var(--z-sidebar,50)+10)]',
|
|
113
|
-
'motion-reduce:transition-none',
|
|
114
113
|
isLight ? 'bg-background border-border' : 'bg-sidebar border-sidebar-foreground/20'
|
|
115
114
|
)}
|
|
116
115
|
style="top: {top}px; left: {left}px;"
|
|
@@ -174,7 +173,7 @@
|
|
|
174
173
|
<a
|
|
175
174
|
href={child.disabled ? undefined : child.href}
|
|
176
175
|
class={cn(
|
|
177
|
-
'flex items-center gap-2 rounded-md px-3 py-2 text-sm
|
|
176
|
+
'flex items-center gap-2 rounded-md px-3 py-2 text-sm ml-2',
|
|
178
177
|
'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary focus-visible:ring-offset-1',
|
|
179
178
|
isLight
|
|
180
179
|
? 'text-foreground hover:bg-accent hover:text-accent-foreground'
|
|
@@ -84,11 +84,13 @@
|
|
|
84
84
|
|
|
85
85
|
function getItemStateClasses(active?: boolean, disabled?: boolean): string {
|
|
86
86
|
return cn(
|
|
87
|
-
isLight &&
|
|
87
|
+
isLight &&
|
|
88
|
+
!active &&
|
|
89
|
+
'text-foreground bg-background hover:bg-accent hover:text-accent-foreground',
|
|
88
90
|
isLight && active && 'bg-primary/10 text-primary',
|
|
89
91
|
!isLight &&
|
|
90
92
|
!active &&
|
|
91
|
-
'text-sidebar-foreground hover:bg-sidebar-accent hover:text-sidebar-accent-foreground',
|
|
93
|
+
'text-sidebar-foreground bg-sidebar hover:bg-sidebar-accent hover:text-sidebar-accent-foreground',
|
|
92
94
|
!isLight && active && 'bg-sidebar-primary text-sidebar-primary-foreground',
|
|
93
95
|
disabled && 'opacity-50 pointer-events-none cursor-not-allowed'
|
|
94
96
|
);
|
|
@@ -172,13 +174,13 @@
|
|
|
172
174
|
highlight: { before: string; match: string; after: string } | null
|
|
173
175
|
)}
|
|
174
176
|
{#if highlight}
|
|
175
|
-
<span class="flex-1 truncate">
|
|
177
|
+
<span class="flex-1 min-w-0 truncate">
|
|
176
178
|
{highlight.before}<mark class="bg-yellow-200 dark:bg-yellow-900 text-inherit rounded px-0.5"
|
|
177
179
|
>{highlight.match}</mark
|
|
178
180
|
>{highlight.after}
|
|
179
181
|
</span>
|
|
180
182
|
{:else}
|
|
181
|
-
<span class="flex-1 truncate">{name}</span>
|
|
183
|
+
<span class="flex-1 min-w-0 truncate">{name}</span>
|
|
182
184
|
{/if}
|
|
183
185
|
{/snippet}
|
|
184
186
|
|
|
@@ -186,7 +188,7 @@
|
|
|
186
188
|
<button
|
|
187
189
|
type="button"
|
|
188
190
|
class={cn(
|
|
189
|
-
'flex items-center justify-center p-1 rounded
|
|
191
|
+
'flex items-center justify-center p-1 rounded shrink-0',
|
|
190
192
|
'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary focus-visible:ring-offset-1',
|
|
191
193
|
// Show on hover for regular items, always show in pinned section
|
|
192
194
|
isUnpin ? 'opacity-100' : 'opacity-0 group-hover:opacity-100',
|
|
@@ -247,10 +249,10 @@
|
|
|
247
249
|
<div>
|
|
248
250
|
<div
|
|
249
251
|
class={cn(
|
|
250
|
-
'group flex w-full items-center rounded-md
|
|
251
|
-
isLight && !item.active && 'hover:bg-accent',
|
|
252
|
+
'group flex w-full items-center rounded-md',
|
|
253
|
+
isLight && !item.active && 'bg-background hover:bg-accent',
|
|
252
254
|
isLight && item.active && 'bg-primary/10',
|
|
253
|
-
!isLight && !item.active && 'hover:bg-sidebar-accent',
|
|
255
|
+
!isLight && !item.active && 'bg-sidebar hover:bg-sidebar-accent',
|
|
254
256
|
!isLight && item.active && 'bg-sidebar-primary'
|
|
255
257
|
)}
|
|
256
258
|
>
|
|
@@ -259,7 +261,7 @@
|
|
|
259
261
|
<a
|
|
260
262
|
href={item.disabled ? undefined : item.href}
|
|
261
263
|
class={cn(
|
|
262
|
-
'flex flex-1 items-center gap-3 px-3 py-2 text-sm font-medium
|
|
264
|
+
'flex flex-1 items-center gap-3 px-3 py-2 text-sm font-medium',
|
|
263
265
|
'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary focus-visible:ring-offset-1',
|
|
264
266
|
'group-hover:underline underline-offset-2',
|
|
265
267
|
isLight && !item.active && 'text-foreground group-hover:text-accent-foreground',
|
|
@@ -287,7 +289,7 @@
|
|
|
287
289
|
<button
|
|
288
290
|
type="button"
|
|
289
291
|
class={cn(
|
|
290
|
-
'flex flex-1 items-center gap-3 px-3 py-2 text-sm font-medium
|
|
292
|
+
'flex flex-1 items-center gap-3 px-3 py-2 text-sm font-medium text-left',
|
|
291
293
|
'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary focus-visible:ring-offset-1',
|
|
292
294
|
isLight && !item.active && 'text-foreground group-hover:text-accent-foreground',
|
|
293
295
|
isLight && item.active && 'text-primary',
|
|
@@ -312,7 +314,7 @@
|
|
|
312
314
|
<button
|
|
313
315
|
type="button"
|
|
314
316
|
class={cn(
|
|
315
|
-
'flex items-center justify-center p-2
|
|
317
|
+
'flex items-center justify-center p-2 rounded-md',
|
|
316
318
|
'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary focus-visible:ring-offset-1',
|
|
317
319
|
isLight
|
|
318
320
|
? 'hover:bg-accent-hover group-hover:text-accent-foreground'
|
|
@@ -353,16 +355,13 @@
|
|
|
353
355
|
</div>
|
|
354
356
|
|
|
355
357
|
{#if isExpanded}
|
|
356
|
-
<ul
|
|
357
|
-
class="mt-1 space-y-1 pl-4 motion-reduce:transition-none overflow-hidden"
|
|
358
|
-
transition:slide={{ duration: 200 }}
|
|
359
|
-
>
|
|
358
|
+
<ul class="mt-1 space-y-1 pl-4 overflow-hidden" transition:slide={{ duration: 200 }}>
|
|
360
359
|
{#each item.children as child}
|
|
361
360
|
<li>
|
|
362
361
|
<a
|
|
363
362
|
href={child.disabled ? undefined : child.href}
|
|
364
363
|
class={cn(
|
|
365
|
-
'flex items-center gap-3 rounded-md px-3 py-2 text-sm font-medium
|
|
364
|
+
'flex items-center gap-3 rounded-md px-3 py-2 text-sm font-medium',
|
|
366
365
|
'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary focus-visible:ring-offset-1',
|
|
367
366
|
getItemStateClasses(child.active, child.disabled)
|
|
368
367
|
)}
|
|
@@ -394,10 +393,10 @@
|
|
|
394
393
|
<!-- Wrap in group div for pin button hover behavior -->
|
|
395
394
|
<div
|
|
396
395
|
class={cn(
|
|
397
|
-
'group flex w-full items-center rounded-md
|
|
398
|
-
isLight && !item.active && 'hover:bg-accent',
|
|
396
|
+
'group flex w-full items-center rounded-md',
|
|
397
|
+
isLight && !item.active && 'bg-background hover:bg-accent',
|
|
399
398
|
isLight && item.active && 'bg-primary/10',
|
|
400
|
-
!isLight && !item.active && 'hover:bg-sidebar-accent',
|
|
399
|
+
!isLight && !item.active && 'bg-sidebar hover:bg-sidebar-accent',
|
|
401
400
|
!isLight && item.active && 'bg-sidebar-primary'
|
|
402
401
|
)}
|
|
403
402
|
>
|
|
@@ -406,7 +405,7 @@
|
|
|
406
405
|
<a
|
|
407
406
|
href={item.disabled ? undefined : item.href}
|
|
408
407
|
class={cn(
|
|
409
|
-
'flex flex-1 items-center gap-3 px-3 py-2 text-sm font-medium
|
|
408
|
+
'flex flex-1 items-center gap-3 px-3 py-2 text-sm font-medium relative',
|
|
410
409
|
'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary focus-visible:ring-offset-1',
|
|
411
410
|
isLight && !item.active && 'text-foreground group-hover:text-accent-foreground',
|
|
412
411
|
isLight && item.active && 'text-primary',
|
|
@@ -453,7 +452,7 @@
|
|
|
453
452
|
<button
|
|
454
453
|
type="button"
|
|
455
454
|
class={cn(
|
|
456
|
-
'flex flex-1 items-center gap-3 px-3 py-2 text-sm font-medium
|
|
455
|
+
'flex flex-1 items-center gap-3 px-3 py-2 text-sm font-medium text-left relative',
|
|
457
456
|
'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary focus-visible:ring-offset-1',
|
|
458
457
|
isLight && !item.active && 'text-foreground group-hover:text-accent-foreground',
|
|
459
458
|
isLight && item.active && 'text-primary',
|
|
@@ -67,7 +67,7 @@
|
|
|
67
67
|
<button
|
|
68
68
|
onclick={toggleSection}
|
|
69
69
|
class={cn(
|
|
70
|
-
'flex w-full items-center justify-between mb-2 px-4 text-xs font-semibold uppercase tracking-wider
|
|
70
|
+
'flex w-full items-center justify-between mb-2 px-4 text-xs font-semibold uppercase tracking-wider rounded-md overflow-hidden',
|
|
71
71
|
'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary focus-visible:ring-offset-1',
|
|
72
72
|
isLight
|
|
73
73
|
? 'text-muted-foreground hover:text-foreground'
|
|
@@ -132,7 +132,7 @@
|
|
|
132
132
|
{:else if isExpanded}
|
|
133
133
|
<!-- Collapsible and expanded: animate -->
|
|
134
134
|
<ul
|
|
135
|
-
class="space-y-1 px-2
|
|
135
|
+
class="space-y-1 px-2 overflow-hidden"
|
|
136
136
|
id={`sidebar-section-${section.id}`}
|
|
137
137
|
transition:slide={{ duration: 200 }}
|
|
138
138
|
>
|
|
@@ -81,7 +81,7 @@
|
|
|
81
81
|
aria-selected={filter === filterItem.id}
|
|
82
82
|
tabindex={filter === filterItem.id ? 0 : -1}
|
|
83
83
|
class={cn(
|
|
84
|
-
'inline-flex items-center justify-center gap-1.5 whitespace-nowrap rounded-sm px-3 py-1.5 text-sm font-medium ring-offset-background
|
|
84
|
+
'inline-flex items-center justify-center gap-1.5 whitespace-nowrap rounded-sm px-3 py-1.5 text-sm font-medium ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2',
|
|
85
85
|
filter === filterItem.id
|
|
86
86
|
? 'bg-background text-foreground shadow-sm'
|
|
87
87
|
: 'hover:bg-background-hover hover:text-foreground'
|
|
@@ -66,9 +66,10 @@
|
|
|
66
66
|
}
|
|
67
67
|
</script>
|
|
68
68
|
|
|
69
|
+
<!-- svelte-ignore a11y_no_noninteractive_tabindex -->
|
|
69
70
|
<div
|
|
70
71
|
class={cn(
|
|
71
|
-
'group relative flex gap-3 rounded-lg border p-4
|
|
72
|
+
'group relative flex gap-3 rounded-lg border p-4',
|
|
72
73
|
!notification.read && 'bg-blue-50/50 border-blue-200',
|
|
73
74
|
notification.read && 'bg-card border-border',
|
|
74
75
|
onClick && 'cursor-pointer hover:bg-muted/50',
|
|
@@ -24,3 +24,11 @@ export { useAsync, runAsync, type UseAsyncOptions, type UseAsyncReturn, } from '
|
|
|
24
24
|
* ```
|
|
25
25
|
*/
|
|
26
26
|
export { usePersistedForm, type UsePersistedFormOptions, type UsePersistedFormReturn, type InferPersistedFormData, } from './usePersistedForm.svelte.js';
|
|
27
|
+
export { useFocusTrap, type UseFocusTrapOptions } from './useFocusTrap.svelte.js';
|
|
28
|
+
export { useFocusRef, type UseFocusRefOptions } from './useFocusRef.svelte.js';
|
|
29
|
+
export { useReducedMotion } from './useReducedMotion.svelte.js';
|
|
30
|
+
export { useArrowNavigation, type UseArrowNavigationOptions, type ArrowNavigationOrientation, } from './useArrowNavigation.svelte.js';
|
|
31
|
+
export { useRovingTabindex, type UseRovingTabindexOptions } from './useRovingTabindex.svelte.js';
|
|
32
|
+
export { useAnnounce, type UseAnnounceOptions, type UseAnnounceReturn, } from './useAnnounce.svelte.js';
|
|
33
|
+
export { useEscapeKey, type UseEscapeKeyOptions } from './useEscapeKey.svelte.js';
|
|
34
|
+
export { useClickOutside, clickOutsideAction, type UseClickOutsideOptions, } from './useClickOutside.svelte.js';
|
|
@@ -26,3 +26,12 @@ export { useAsync, runAsync, } from './useAsync.svelte.js';
|
|
|
26
26
|
* ```
|
|
27
27
|
*/
|
|
28
28
|
export { usePersistedForm, } from './usePersistedForm.svelte.js';
|
|
29
|
+
// Accessibility composables
|
|
30
|
+
export { useFocusTrap } from './useFocusTrap.svelte.js';
|
|
31
|
+
export { useFocusRef } from './useFocusRef.svelte.js';
|
|
32
|
+
export { useReducedMotion } from './useReducedMotion.svelte.js';
|
|
33
|
+
export { useArrowNavigation, } from './useArrowNavigation.svelte.js';
|
|
34
|
+
export { useRovingTabindex } from './useRovingTabindex.svelte.js';
|
|
35
|
+
export { useAnnounce, } from './useAnnounce.svelte.js';
|
|
36
|
+
export { useEscapeKey } from './useEscapeKey.svelte.js';
|
|
37
|
+
export { useClickOutside, clickOutsideAction, } from './useClickOutside.svelte.js';
|