@classic-homes/theme-svelte 0.1.4 → 0.1.6
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/lib/components/CardHeader.svelte +22 -2
- package/dist/lib/components/CardHeader.svelte.d.ts +5 -4
- package/dist/lib/components/Combobox.svelte +187 -0
- package/dist/lib/components/Combobox.svelte.d.ts +38 -0
- package/dist/lib/components/DateTimePicker.svelte +415 -0
- package/dist/lib/components/DateTimePicker.svelte.d.ts +31 -0
- package/dist/lib/components/HeaderSearch.svelte +340 -0
- package/dist/lib/components/HeaderSearch.svelte.d.ts +37 -0
- package/dist/lib/components/MultiSelect.svelte +244 -0
- package/dist/lib/components/MultiSelect.svelte.d.ts +40 -0
- package/dist/lib/components/NumberInput.svelte +205 -0
- package/dist/lib/components/NumberInput.svelte.d.ts +33 -0
- package/dist/lib/components/OTPInput.svelte +213 -0
- package/dist/lib/components/OTPInput.svelte.d.ts +23 -0
- package/dist/lib/components/PageHeader.svelte +6 -0
- package/dist/lib/components/PageHeader.svelte.d.ts +1 -1
- package/dist/lib/components/RadioGroup.svelte +124 -0
- package/dist/lib/components/RadioGroup.svelte.d.ts +31 -0
- package/dist/lib/components/Signature.svelte +1070 -0
- package/dist/lib/components/Signature.svelte.d.ts +74 -0
- package/dist/lib/components/Slider.svelte +136 -0
- package/dist/lib/components/Slider.svelte.d.ts +30 -0
- package/dist/lib/components/layout/AuthLayout.svelte +133 -0
- package/dist/lib/components/layout/AuthLayout.svelte.d.ts +48 -0
- package/dist/lib/components/layout/DashboardLayout.svelte +100 -74
- package/dist/lib/components/layout/DashboardLayout.svelte.d.ts +17 -10
- package/dist/lib/components/layout/ErrorLayout.svelte +206 -0
- package/dist/lib/components/layout/ErrorLayout.svelte.d.ts +52 -0
- package/dist/lib/components/layout/FormPageLayout.svelte +2 -8
- package/dist/lib/components/layout/Header.svelte +232 -41
- package/dist/lib/components/layout/Header.svelte.d.ts +71 -5
- package/dist/lib/components/layout/PublicLayout.svelte +54 -80
- package/dist/lib/components/layout/PublicLayout.svelte.d.ts +3 -1
- package/dist/lib/components/layout/QuickLinks.svelte +49 -29
- package/dist/lib/components/layout/QuickLinks.svelte.d.ts +4 -2
- package/dist/lib/components/layout/Sidebar.svelte +345 -86
- package/dist/lib/components/layout/Sidebar.svelte.d.ts +12 -0
- package/dist/lib/components/layout/sidebar/SidebarFlyout.svelte +182 -0
- package/dist/lib/components/layout/sidebar/SidebarFlyout.svelte.d.ts +18 -0
- package/dist/lib/components/layout/sidebar/SidebarNavItem.svelte +378 -0
- package/dist/lib/components/layout/sidebar/SidebarNavItem.svelte.d.ts +25 -0
- package/dist/lib/components/layout/sidebar/SidebarSearch.svelte +121 -0
- package/dist/lib/components/layout/sidebar/SidebarSearch.svelte.d.ts +17 -0
- package/dist/lib/components/layout/sidebar/SidebarSection.svelte +144 -0
- package/dist/lib/components/layout/sidebar/SidebarSection.svelte.d.ts +25 -0
- package/dist/lib/components/layout/sidebar/index.d.ts +10 -0
- package/dist/lib/components/layout/sidebar/index.js +10 -0
- package/dist/lib/index.d.ts +13 -2
- package/dist/lib/index.js +11 -0
- package/dist/lib/schemas/auth.d.ts +6 -6
- package/dist/lib/stores/sidebar.svelte.d.ts +54 -0
- package/dist/lib/stores/sidebar.svelte.js +171 -1
- package/dist/lib/types/components.d.ts +105 -0
- package/dist/lib/types/layout.d.ts +203 -3
- package/package.json +1 -1
|
@@ -1,27 +1,93 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Header -
|
|
2
|
+
* Header - Unified application header component
|
|
3
|
+
*
|
|
4
|
+
* A flexible header component that supports both dashboard and public layouts.
|
|
3
5
|
*
|
|
4
6
|
* Features:
|
|
5
7
|
* - Hamburger menu button for mobile
|
|
6
|
-
* -
|
|
7
|
-
* -
|
|
8
|
-
* -
|
|
8
|
+
* - Collapse/expand button for desktop sidebar
|
|
9
|
+
* - Customizable start, nav, and end slots
|
|
10
|
+
* - Mobile navigation drawer support
|
|
11
|
+
* - Optional page title
|
|
12
|
+
* - Backdrop blur support
|
|
13
|
+
* - Configurable responsive breakpoint (sm, md, lg)
|
|
14
|
+
* - Responsive design with container width options
|
|
9
15
|
* - Stronger border styling to match brand guidelines
|
|
16
|
+
*
|
|
17
|
+
* @example Dashboard layout usage:
|
|
18
|
+
* ```svelte
|
|
19
|
+
* <Header
|
|
20
|
+
* showMenuButton={isMobile}
|
|
21
|
+
* menuOpen={sidebarOpen}
|
|
22
|
+
* onMenuClick={() => toggleSidebar()}
|
|
23
|
+
* showCollapseButton={!isMobile}
|
|
24
|
+
* sidebarCollapsed={!sidebarOpen}
|
|
25
|
+
* onCollapseClick={() => toggleSidebar()}
|
|
26
|
+
* title="Dashboard"
|
|
27
|
+
* />
|
|
28
|
+
* ```
|
|
29
|
+
*
|
|
30
|
+
* @example Public layout usage:
|
|
31
|
+
* ```svelte
|
|
32
|
+
* <Header
|
|
33
|
+
* showMenuButton={true}
|
|
34
|
+
* menuOpen={mobileMenuOpen}
|
|
35
|
+
* onMenuClick={() => mobileMenuOpen = !mobileMenuOpen}
|
|
36
|
+
* mobileBreakpoint="md"
|
|
37
|
+
* backdropBlur
|
|
38
|
+
* elevated
|
|
39
|
+
* maxWidth="7xl"
|
|
40
|
+
* >
|
|
41
|
+
* {#snippet start()}
|
|
42
|
+
* <a href="/"><Logo /></a>
|
|
43
|
+
* {/snippet}
|
|
44
|
+
* {#snippet nav()}
|
|
45
|
+
* <NavLinks />
|
|
46
|
+
* {/snippet}
|
|
47
|
+
* {#snippet mobileNav()}
|
|
48
|
+
* <MobileNavDrawer />
|
|
49
|
+
* {/snippet}
|
|
50
|
+
* </Header>
|
|
51
|
+
* ```
|
|
10
52
|
*/
|
|
11
53
|
import type { Snippet } from 'svelte';
|
|
54
|
+
import type { HeaderSearchConfig } from '../../types/layout.js';
|
|
55
|
+
type Breakpoint = 'sm' | 'md' | 'lg';
|
|
12
56
|
interface Props {
|
|
13
57
|
/** Whether to show hamburger menu button (mobile) */
|
|
14
58
|
showMenuButton?: boolean;
|
|
59
|
+
/** Whether the mobile menu is currently open (for aria-expanded) */
|
|
60
|
+
menuOpen?: boolean;
|
|
15
61
|
/** Callback when menu button is clicked */
|
|
16
62
|
onMenuClick?: () => void;
|
|
17
|
-
/**
|
|
63
|
+
/** Whether to show collapse/expand button (desktop sidebar toggle) */
|
|
64
|
+
showCollapseButton?: boolean;
|
|
65
|
+
/** Whether the sidebar is collapsed (for aria-expanded and icon direction) */
|
|
66
|
+
sidebarCollapsed?: boolean;
|
|
67
|
+
/** Callback when collapse button is clicked */
|
|
68
|
+
onCollapseClick?: () => void;
|
|
69
|
+
/** Page title displayed in header */
|
|
70
|
+
title?: string;
|
|
71
|
+
/** Custom start content (left side, after menu/collapse buttons) */
|
|
18
72
|
start?: Snippet;
|
|
19
73
|
/** Navigation content (center/right on desktop, hidden on mobile) */
|
|
20
74
|
nav?: Snippet;
|
|
21
75
|
/** Custom end content (right side) */
|
|
22
76
|
end?: Snippet;
|
|
77
|
+
/** Mobile navigation drawer content (appears below header bar when menu is open) */
|
|
78
|
+
mobileNav?: Snippet;
|
|
79
|
+
/** Breakpoint at which to switch between mobile and desktop layouts */
|
|
80
|
+
mobileBreakpoint?: Breakpoint;
|
|
23
81
|
/** Use stronger/thicker border */
|
|
24
82
|
strongBorder?: boolean;
|
|
83
|
+
/** Enable backdrop blur effect */
|
|
84
|
+
backdropBlur?: boolean;
|
|
85
|
+
/** Use elevated z-index (z-50 instead of z-30) */
|
|
86
|
+
elevated?: boolean;
|
|
87
|
+
/** Constrain content to max-width container */
|
|
88
|
+
maxWidth?: 'none' | 'sm' | 'md' | 'lg' | 'xl' | '2xl' | '3xl' | '4xl' | '5xl' | '6xl' | '7xl';
|
|
89
|
+
/** Search configuration */
|
|
90
|
+
search?: HeaderSearchConfig;
|
|
25
91
|
/** Additional classes */
|
|
26
92
|
class?: string;
|
|
27
93
|
}
|
|
@@ -10,10 +10,11 @@
|
|
|
10
10
|
* - Strong border options for brand consistency
|
|
11
11
|
*/
|
|
12
12
|
import type { Snippet } from 'svelte';
|
|
13
|
-
import type { NavItem, NavSection } from '../../types/layout.js';
|
|
13
|
+
import type { NavItem, NavSection, HeaderSearchConfig } from '../../types/layout.js';
|
|
14
14
|
import { cn } from '../../utils.js';
|
|
15
15
|
import AppShell from './AppShell.svelte';
|
|
16
16
|
import Footer from './Footer.svelte';
|
|
17
|
+
import Header from './Header.svelte';
|
|
17
18
|
import LogoMain from '../LogoMain.svelte';
|
|
18
19
|
|
|
19
20
|
interface Props {
|
|
@@ -37,6 +38,8 @@
|
|
|
37
38
|
strongFooterBorder?: boolean;
|
|
38
39
|
/** Use dark footer variant */
|
|
39
40
|
darkFooter?: boolean;
|
|
41
|
+
/** Header search configuration */
|
|
42
|
+
headerSearch?: HeaderSearchConfig;
|
|
40
43
|
/** Main content */
|
|
41
44
|
children: Snippet;
|
|
42
45
|
}
|
|
@@ -52,6 +55,7 @@
|
|
|
52
55
|
headerEnd,
|
|
53
56
|
strongFooterBorder = false,
|
|
54
57
|
darkFooter = false,
|
|
58
|
+
headerSearch,
|
|
55
59
|
children,
|
|
56
60
|
}: Props = $props();
|
|
57
61
|
|
|
@@ -59,12 +63,17 @@
|
|
|
59
63
|
</script>
|
|
60
64
|
|
|
61
65
|
<AppShell>
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
66
|
+
<Header
|
|
67
|
+
showMenuButton={navigation.length > 0}
|
|
68
|
+
menuOpen={mobileMenuOpen}
|
|
69
|
+
onMenuClick={() => (mobileMenuOpen = !mobileMenuOpen)}
|
|
70
|
+
mobileBreakpoint="md"
|
|
71
|
+
backdropBlur
|
|
72
|
+
elevated
|
|
73
|
+
maxWidth="7xl"
|
|
74
|
+
search={headerSearch}
|
|
65
75
|
>
|
|
66
|
-
|
|
67
|
-
<!-- Logo -->
|
|
76
|
+
{#snippet start()}
|
|
68
77
|
<a href="/" class="flex items-center">
|
|
69
78
|
{#if logo}
|
|
70
79
|
{@render logo()}
|
|
@@ -78,90 +87,55 @@
|
|
|
78
87
|
/>
|
|
79
88
|
{/if}
|
|
80
89
|
</a>
|
|
90
|
+
{/snippet}
|
|
81
91
|
|
|
82
|
-
|
|
83
|
-
{#
|
|
84
|
-
<
|
|
85
|
-
{
|
|
92
|
+
{#snippet nav()}
|
|
93
|
+
{#each navigation as item}
|
|
94
|
+
<a
|
|
95
|
+
href={item.href}
|
|
96
|
+
class={cn(
|
|
97
|
+
'text-sm font-bold uppercase transition-colors hover:text-primary',
|
|
98
|
+
item.active ? 'text-primary' : 'text-muted-foreground'
|
|
99
|
+
)}
|
|
100
|
+
target={item.external ? '_blank' : undefined}
|
|
101
|
+
rel={item.external ? 'noopener noreferrer' : undefined}
|
|
102
|
+
aria-current={item.active ? 'page' : undefined}
|
|
103
|
+
>
|
|
104
|
+
{item.name}
|
|
105
|
+
</a>
|
|
106
|
+
{/each}
|
|
107
|
+
{/snippet}
|
|
108
|
+
|
|
109
|
+
{#snippet end()}
|
|
110
|
+
{#if headerEnd}
|
|
111
|
+
{@render headerEnd()}
|
|
112
|
+
{/if}
|
|
113
|
+
{/snippet}
|
|
114
|
+
|
|
115
|
+
{#snippet mobileNav()}
|
|
116
|
+
<ul class="space-y-2 border-b border-black px-4 py-4">
|
|
117
|
+
{#each navigation as item}
|
|
118
|
+
<li>
|
|
86
119
|
<a
|
|
87
120
|
href={item.href}
|
|
88
121
|
class={cn(
|
|
89
|
-
'text-sm font-bold uppercase transition-colors
|
|
90
|
-
item.active
|
|
122
|
+
'block rounded-md px-3 py-2 text-sm font-bold uppercase transition-colors',
|
|
123
|
+
item.active
|
|
124
|
+
? 'bg-primary/10 text-primary'
|
|
125
|
+
: 'text-muted-foreground hover:bg-accent hover:text-accent-foreground'
|
|
91
126
|
)}
|
|
92
127
|
target={item.external ? '_blank' : undefined}
|
|
93
128
|
rel={item.external ? 'noopener noreferrer' : undefined}
|
|
129
|
+
aria-current={item.active ? 'page' : undefined}
|
|
130
|
+
onclick={() => (mobileMenuOpen = false)}
|
|
94
131
|
>
|
|
95
132
|
{item.name}
|
|
96
133
|
</a>
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
<div class="flex items-center gap-4">
|
|
103
|
-
{#if headerEnd}
|
|
104
|
-
{@render headerEnd()}
|
|
105
|
-
{/if}
|
|
106
|
-
|
|
107
|
-
<!-- Mobile Menu Button -->
|
|
108
|
-
{#if navigation.length > 0}
|
|
109
|
-
<button
|
|
110
|
-
class="rounded-md p-2 hover:bg-accent hover:text-accent-foreground md:hidden"
|
|
111
|
-
onclick={() => (mobileMenuOpen = !mobileMenuOpen)}
|
|
112
|
-
aria-label={mobileMenuOpen ? 'Close menu' : 'Open menu'}
|
|
113
|
-
aria-expanded={mobileMenuOpen}
|
|
114
|
-
>
|
|
115
|
-
{#if mobileMenuOpen}
|
|
116
|
-
<svg class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
117
|
-
<path
|
|
118
|
-
stroke-linecap="round"
|
|
119
|
-
stroke-linejoin="round"
|
|
120
|
-
stroke-width="2"
|
|
121
|
-
d="M6 18L18 6M6 6l12 12"
|
|
122
|
-
/>
|
|
123
|
-
</svg>
|
|
124
|
-
{:else}
|
|
125
|
-
<svg class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
126
|
-
<path
|
|
127
|
-
stroke-linecap="round"
|
|
128
|
-
stroke-linejoin="round"
|
|
129
|
-
stroke-width="2"
|
|
130
|
-
d="M4 6h16M4 12h16M4 18h16"
|
|
131
|
-
/>
|
|
132
|
-
</svg>
|
|
133
|
-
{/if}
|
|
134
|
-
</button>
|
|
135
|
-
{/if}
|
|
136
|
-
</div>
|
|
137
|
-
</div>
|
|
138
|
-
|
|
139
|
-
<!-- Mobile Navigation -->
|
|
140
|
-
{#if mobileMenuOpen && navigation.length > 0}
|
|
141
|
-
<nav class="border-t border-black px-4 py-4 md:hidden">
|
|
142
|
-
<ul class="space-y-2">
|
|
143
|
-
{#each navigation as item}
|
|
144
|
-
<li>
|
|
145
|
-
<a
|
|
146
|
-
href={item.href}
|
|
147
|
-
class={cn(
|
|
148
|
-
'block rounded-md px-3 py-2 text-sm font-bold uppercase transition-colors',
|
|
149
|
-
item.active
|
|
150
|
-
? 'bg-primary/10 text-primary'
|
|
151
|
-
: 'text-muted-foreground hover:bg-accent hover:text-accent-foreground'
|
|
152
|
-
)}
|
|
153
|
-
target={item.external ? '_blank' : undefined}
|
|
154
|
-
rel={item.external ? 'noopener noreferrer' : undefined}
|
|
155
|
-
onclick={() => (mobileMenuOpen = false)}
|
|
156
|
-
>
|
|
157
|
-
{item.name}
|
|
158
|
-
</a>
|
|
159
|
-
</li>
|
|
160
|
-
{/each}
|
|
161
|
-
</ul>
|
|
162
|
-
</nav>
|
|
163
|
-
{/if}
|
|
164
|
-
</header>
|
|
134
|
+
</li>
|
|
135
|
+
{/each}
|
|
136
|
+
</ul>
|
|
137
|
+
{/snippet}
|
|
138
|
+
</Header>
|
|
165
139
|
|
|
166
140
|
<!-- Main Content -->
|
|
167
141
|
<main id="main-content" class="flex-1 bg-content-bg">
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
* - Strong border options for brand consistency
|
|
10
10
|
*/
|
|
11
11
|
import type { Snippet } from 'svelte';
|
|
12
|
-
import type { NavItem, NavSection } from '../../types/layout.js';
|
|
12
|
+
import type { NavItem, NavSection, HeaderSearchConfig } from '../../types/layout.js';
|
|
13
13
|
interface Props {
|
|
14
14
|
/** Navigation items for header */
|
|
15
15
|
navigation?: NavItem[];
|
|
@@ -31,6 +31,8 @@ interface Props {
|
|
|
31
31
|
strongFooterBorder?: boolean;
|
|
32
32
|
/** Use dark footer variant */
|
|
33
33
|
darkFooter?: boolean;
|
|
34
|
+
/** Header search configuration */
|
|
35
|
+
headerSearch?: HeaderSearchConfig;
|
|
34
36
|
/** Main content */
|
|
35
37
|
children: Snippet;
|
|
36
38
|
}
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* QuickLinks - Quick navigation links section for sidebar
|
|
4
4
|
*
|
|
5
5
|
* Features:
|
|
6
|
-
* - Two display modes: 'list' (stacked with labels) and 'icons' (
|
|
6
|
+
* - Two display modes: 'list' (stacked with labels) and 'icons' (stacked centered icons only)
|
|
7
7
|
* - Light/dark variant support
|
|
8
8
|
* - External link indicator
|
|
9
9
|
* - Custom icon renderer support
|
|
@@ -16,37 +16,41 @@
|
|
|
16
16
|
interface Props {
|
|
17
17
|
/** Array of quick link items */
|
|
18
18
|
links: QuickLink[];
|
|
19
|
-
/** Display mode: 'list' for stacked with labels, 'icons' for
|
|
19
|
+
/** Display mode: 'list' for stacked with labels, 'icons' for stacked centered icons only */
|
|
20
20
|
display?: 'list' | 'icons';
|
|
21
21
|
/** Visual variant - light (default) or dark */
|
|
22
22
|
variant?: 'light' | 'dark';
|
|
23
23
|
/** Custom icon renderer */
|
|
24
24
|
icon?: Snippet<[QuickLink]>;
|
|
25
|
+
/** Accessible label for the navigation region */
|
|
26
|
+
ariaLabel?: string;
|
|
25
27
|
/** Additional classes */
|
|
26
28
|
class?: string;
|
|
27
29
|
}
|
|
28
30
|
|
|
29
|
-
let {
|
|
31
|
+
let {
|
|
32
|
+
links,
|
|
33
|
+
display = 'list',
|
|
34
|
+
variant = 'light',
|
|
35
|
+
icon,
|
|
36
|
+
ariaLabel = 'Quick links',
|
|
37
|
+
class: className,
|
|
38
|
+
}: Props = $props();
|
|
30
39
|
|
|
31
40
|
const isLight = $derived(variant === 'light');
|
|
32
41
|
</script>
|
|
33
42
|
|
|
34
43
|
{#if links.length > 0}
|
|
35
44
|
<div
|
|
36
|
-
class={cn(
|
|
37
|
-
display === 'list'
|
|
38
|
-
? 'flex flex-col gap-1'
|
|
39
|
-
: 'flex flex-row items-center justify-center gap-2',
|
|
40
|
-
className
|
|
41
|
-
)}
|
|
45
|
+
class={cn('flex flex-col gap-1', display === 'icons' && 'items-center', className)}
|
|
42
46
|
role="navigation"
|
|
43
|
-
aria-label=
|
|
47
|
+
aria-label={ariaLabel}
|
|
44
48
|
>
|
|
45
49
|
{#each links as link}
|
|
46
50
|
<a
|
|
47
51
|
href={link.href}
|
|
48
52
|
class={cn(
|
|
49
|
-
'flex items-center transition-
|
|
53
|
+
'flex items-center transition-all duration-300 motion-reduce:transition-none overflow-hidden',
|
|
50
54
|
// List mode styles
|
|
51
55
|
display === 'list' && 'gap-3 rounded-md px-3 py-2 text-sm',
|
|
52
56
|
display === 'list' &&
|
|
@@ -56,7 +60,7 @@
|
|
|
56
60
|
!isLight &&
|
|
57
61
|
'text-sidebar-foreground/70 hover:bg-sidebar-accent hover:text-sidebar-accent-foreground',
|
|
58
62
|
// Icon mode styles
|
|
59
|
-
display === 'icons' && 'rounded-md p-2',
|
|
63
|
+
display === 'icons' && 'rounded-md p-2 gap-0',
|
|
60
64
|
display === 'icons' &&
|
|
61
65
|
isLight &&
|
|
62
66
|
'text-muted-foreground hover:bg-accent hover:text-accent-foreground',
|
|
@@ -87,24 +91,40 @@
|
|
|
87
91
|
</span>
|
|
88
92
|
{/if}
|
|
89
93
|
|
|
90
|
-
|
|
91
|
-
|
|
94
|
+
<span
|
|
95
|
+
class={cn(
|
|
96
|
+
'truncate whitespace-nowrap transition-all duration-300 motion-reduce:transition-none',
|
|
97
|
+
display === 'icons' ? 'w-0 opacity-0 flex-none' : 'flex-1 opacity-100'
|
|
98
|
+
)}
|
|
99
|
+
>
|
|
100
|
+
{link.label}
|
|
101
|
+
</span>
|
|
92
102
|
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
103
|
+
{#if link.badge !== undefined && display === 'list'}
|
|
104
|
+
<span
|
|
105
|
+
class="ml-auto rounded-full bg-primary px-2 py-0.5 text-xs font-medium text-primary-foreground whitespace-nowrap"
|
|
106
|
+
>
|
|
107
|
+
{link.badge}
|
|
108
|
+
</span>
|
|
109
|
+
{/if}
|
|
110
|
+
|
|
111
|
+
{#if link.external}
|
|
112
|
+
<svg
|
|
113
|
+
class={cn(
|
|
114
|
+
'h-4 w-4 shrink-0 transition-all duration-300',
|
|
115
|
+
display === 'icons' ? 'hidden' : 'opacity-50'
|
|
116
|
+
)}
|
|
117
|
+
fill="none"
|
|
118
|
+
viewBox="0 0 24 24"
|
|
119
|
+
stroke="currentColor"
|
|
120
|
+
>
|
|
121
|
+
<path
|
|
122
|
+
stroke-linecap="round"
|
|
123
|
+
stroke-linejoin="round"
|
|
124
|
+
stroke-width="2"
|
|
125
|
+
d="M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14"
|
|
126
|
+
/>
|
|
127
|
+
</svg>
|
|
108
128
|
{/if}
|
|
109
129
|
</a>
|
|
110
130
|
{/each}
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* QuickLinks - Quick navigation links section for sidebar
|
|
3
3
|
*
|
|
4
4
|
* Features:
|
|
5
|
-
* - Two display modes: 'list' (stacked with labels) and 'icons' (
|
|
5
|
+
* - Two display modes: 'list' (stacked with labels) and 'icons' (stacked centered icons only)
|
|
6
6
|
* - Light/dark variant support
|
|
7
7
|
* - External link indicator
|
|
8
8
|
* - Custom icon renderer support
|
|
@@ -13,12 +13,14 @@ import type { QuickLink } from '../../types/layout.js';
|
|
|
13
13
|
interface Props {
|
|
14
14
|
/** Array of quick link items */
|
|
15
15
|
links: QuickLink[];
|
|
16
|
-
/** Display mode: 'list' for stacked with labels, 'icons' for
|
|
16
|
+
/** Display mode: 'list' for stacked with labels, 'icons' for stacked centered icons only */
|
|
17
17
|
display?: 'list' | 'icons';
|
|
18
18
|
/** Visual variant - light (default) or dark */
|
|
19
19
|
variant?: 'light' | 'dark';
|
|
20
20
|
/** Custom icon renderer */
|
|
21
21
|
icon?: Snippet<[QuickLink]>;
|
|
22
|
+
/** Accessible label for the navigation region */
|
|
23
|
+
ariaLabel?: string;
|
|
22
24
|
/** Additional classes */
|
|
23
25
|
class?: string;
|
|
24
26
|
}
|