rizzo-css 0.0.11 → 0.0.13
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/.env.example +12 -0
- package/LICENSE +21 -0
- package/README.md +17 -3
- package/bin/rizzo-css.js +98 -42
- package/dist/rizzo.min.css +3 -2
- package/package.json +5 -3
- package/scaffold/astro-app/README.md +13 -2
- package/scaffold/astro-app/src/components/Accordion.astro +178 -0
- package/scaffold/astro-app/src/components/Alert.astro +131 -0
- package/scaffold/astro-app/src/components/Avatar.astro +59 -0
- package/scaffold/astro-app/src/components/Badge.astro +24 -0
- package/scaffold/astro-app/src/components/Breadcrumb.astro +61 -0
- package/scaffold/astro-app/src/components/Button.astro +3 -0
- package/scaffold/astro-app/src/components/Card.astro +18 -0
- package/scaffold/astro-app/src/components/Checkbox.astro +38 -0
- package/scaffold/astro-app/src/components/CodeBlock.astro +393 -0
- package/scaffold/astro-app/src/components/CopyToClipboard.astro +219 -0
- package/scaffold/astro-app/src/components/Divider.astro +37 -0
- package/scaffold/astro-app/src/components/Dropdown.astro +807 -0
- package/scaffold/astro-app/src/components/FormGroup.astro +59 -0
- package/scaffold/astro-app/src/components/FrameworkSwitcher.astro +72 -0
- package/scaffold/astro-app/src/components/Input.astro +59 -0
- package/scaffold/astro-app/src/components/Modal.astro +212 -0
- package/scaffold/astro-app/src/components/Navbar.astro +701 -0
- package/scaffold/astro-app/src/components/Pagination.astro +240 -0
- package/scaffold/astro-app/src/components/ProgressBar.astro +65 -0
- package/scaffold/astro-app/src/components/Radio.astro +38 -0
- package/scaffold/astro-app/src/components/Search.astro +1259 -0
- package/scaffold/astro-app/src/components/Select.astro +49 -0
- package/scaffold/astro-app/src/components/Settings.astro +382 -0
- package/scaffold/astro-app/src/components/Spinner.astro +30 -0
- package/scaffold/astro-app/src/components/Table.astro +181 -0
- package/scaffold/astro-app/src/components/Tabs.astro +223 -0
- package/scaffold/astro-app/src/components/Textarea.astro +58 -0
- package/scaffold/astro-app/src/components/ThemeSwitcher.astro +504 -0
- package/scaffold/astro-app/src/components/Toast.astro +30 -0
- package/scaffold/astro-app/src/components/Tooltip.astro +32 -0
- package/scaffold/astro-app/src/components/icons/Brush.astro +10 -0
- package/scaffold/astro-app/src/components/icons/Cake.astro +11 -0
- package/scaffold/astro-app/src/components/icons/Check.astro +29 -0
- package/scaffold/astro-app/src/components/icons/Cherry.astro +11 -0
- package/scaffold/astro-app/src/components/icons/ChevronDown.astro +29 -0
- package/scaffold/astro-app/src/components/icons/Circle.astro +29 -0
- package/scaffold/astro-app/src/components/icons/Close.astro +30 -0
- package/scaffold/astro-app/src/components/icons/Cmd.astro +26 -0
- package/scaffold/astro-app/src/components/icons/Copy.astro +30 -0
- package/scaffold/astro-app/src/components/icons/Eye.astro +30 -0
- package/scaffold/astro-app/src/components/icons/Filter.astro +29 -0
- package/scaffold/astro-app/src/components/icons/Flame.astro +28 -0
- package/scaffold/astro-app/src/components/icons/Flower.astro +11 -0
- package/scaffold/astro-app/src/components/icons/Gear.astro +30 -0
- package/scaffold/astro-app/src/components/icons/Heart.astro +28 -0
- package/scaffold/astro-app/src/components/icons/IceCream.astro +31 -0
- package/scaffold/astro-app/src/components/icons/Leaf.astro +29 -0
- package/scaffold/astro-app/src/components/icons/Lemon.astro +11 -0
- package/scaffold/astro-app/src/components/icons/Moon.astro +29 -0
- package/scaffold/astro-app/src/components/icons/Owl.astro +34 -0
- package/scaffold/astro-app/src/components/icons/Palette.astro +33 -0
- package/scaffold/astro-app/src/components/icons/Rainbow.astro +31 -0
- package/scaffold/astro-app/src/components/icons/Search.astro +30 -0
- package/scaffold/astro-app/src/components/icons/Shield.astro +28 -0
- package/scaffold/astro-app/src/components/icons/Snowflake.astro +34 -0
- package/scaffold/astro-app/src/components/icons/Sort.astro +30 -0
- package/scaffold/astro-app/src/components/icons/Sun.astro +29 -0
- package/scaffold/astro-app/src/components/icons/Sunset.astro +10 -0
- package/scaffold/astro-app/src/components/icons/Zap.astro +9 -0
- package/scaffold/astro-app/src/components/icons/devicons/Astro.astro +53 -0
- package/scaffold/astro-app/src/components/icons/devicons/Bash.astro +34 -0
- package/scaffold/astro-app/src/components/icons/devicons/Css3.astro +29 -0
- package/scaffold/astro-app/src/components/icons/devicons/Git.astro +24 -0
- package/scaffold/astro-app/src/components/icons/devicons/Html5.astro +27 -0
- package/scaffold/astro-app/src/components/icons/devicons/Javascript.astro +25 -0
- package/scaffold/astro-app/src/components/icons/devicons/Nodejs.astro +47 -0
- package/scaffold/astro-app/src/components/icons/devicons/Plaintext.astro +33 -0
- package/scaffold/astro-app/src/components/icons/devicons/React.astro +27 -0
- package/scaffold/astro-app/src/components/icons/devicons/Svelte.astro +25 -0
- package/scaffold/astro-app/src/components/icons/devicons/Vue.astro +26 -0
- package/scaffold/astro-app/src/config/frameworks.ts +26 -0
- package/scaffold/astro-app/src/config/themes.ts +54 -0
- package/scaffold/astro-app/src/layouts/DocsLayout.astro +204 -0
- package/scaffold/astro-app/src/layouts/Layout.astro +11 -2
- package/scaffold/astro-app/src/pages/components/accordion.astro +172 -0
- package/scaffold/astro-app/src/pages/components/alert.astro +250 -0
- package/scaffold/astro-app/src/pages/components/avatar.astro +102 -0
- package/scaffold/astro-app/src/pages/components/badge.astro +119 -0
- package/scaffold/astro-app/src/pages/components/breadcrumb.astro +124 -0
- package/scaffold/astro-app/src/pages/components/button.astro +74 -0
- package/scaffold/astro-app/src/pages/components/cards.astro +247 -0
- package/scaffold/astro-app/src/pages/components/copy-to-clipboard.astro +49 -0
- package/scaffold/astro-app/src/pages/components/divider.astro +74 -0
- package/scaffold/astro-app/src/pages/components/dropdown.astro +394 -0
- package/scaffold/astro-app/src/pages/components/forms.astro +367 -0
- package/scaffold/astro-app/src/pages/components/icons.astro +246 -0
- package/scaffold/astro-app/src/pages/components/modal.astro +152 -0
- package/scaffold/astro-app/src/pages/components/navbar.astro +80 -0
- package/scaffold/astro-app/src/pages/components/pagination.astro +126 -0
- package/scaffold/astro-app/src/pages/components/progress-bar.astro +94 -0
- package/scaffold/astro-app/src/pages/components/search.astro +155 -0
- package/scaffold/astro-app/src/pages/components/settings.astro +78 -0
- package/scaffold/astro-app/src/pages/components/spinner.astro +81 -0
- package/scaffold/astro-app/src/pages/components/table.astro +144 -0
- package/scaffold/astro-app/src/pages/components/tabs.astro +220 -0
- package/scaffold/astro-app/src/pages/components/theme-switcher.astro +67 -0
- package/scaffold/astro-app/src/pages/components/toast.astro +157 -0
- package/scaffold/astro-app/src/pages/components/tooltip.astro +209 -0
- package/scaffold/astro-app/src/pages/components.astro +290 -0
- package/scaffold/astro-app/src/pages/docs/accessibility.astro +9 -0
- package/scaffold/astro-app/src/pages/docs/colors.astro +9 -0
- package/scaffold/astro-app/src/pages/docs/design-system.astro +9 -0
- package/scaffold/astro-app/src/pages/docs/getting-started.astro +9 -0
- package/scaffold/astro-app/src/pages/docs/index.astro +15 -0
- package/scaffold/astro-app/src/pages/docs/themes/[theme].astro +14 -0
- package/scaffold/astro-app/src/pages/docs/theming.astro +10 -0
- package/scaffold/astro-app/src/pages/index.astro +5 -11
- package/scaffold/svelte/Table.svelte +6 -5
- package/scaffold/svelte/Tabs.svelte +3 -1
- package/scaffold/svelte-app/README.md +9 -2
- package/scaffold/svelte-app/src/app.html +1 -1
- package/scaffold/svelte-app/src/lib/rizzo/Accordion.svelte +128 -0
- package/scaffold/svelte-app/src/lib/rizzo/Alert.svelte +85 -0
- package/scaffold/svelte-app/src/lib/rizzo/Avatar.svelte +39 -0
- package/scaffold/svelte-app/src/lib/rizzo/Badge.svelte +31 -0
- package/scaffold/svelte-app/src/lib/rizzo/Breadcrumb.svelte +49 -0
- package/scaffold/svelte-app/src/lib/rizzo/Button.svelte +27 -0
- package/scaffold/svelte-app/src/lib/rizzo/Card.svelte +17 -0
- package/scaffold/svelte-app/src/lib/rizzo/Checkbox.svelte +37 -0
- package/scaffold/svelte-app/src/lib/rizzo/CopyToClipboard.svelte +79 -0
- package/scaffold/svelte-app/src/lib/rizzo/Divider.svelte +28 -0
- package/scaffold/svelte-app/src/lib/rizzo/Dropdown.svelte +254 -0
- package/scaffold/svelte-app/src/lib/rizzo/FormGroup.svelte +51 -0
- package/scaffold/svelte-app/src/lib/rizzo/Input.svelte +59 -0
- package/scaffold/svelte-app/src/lib/rizzo/Modal.svelte +157 -0
- package/scaffold/svelte-app/src/lib/rizzo/Pagination.svelte +93 -0
- package/scaffold/svelte-app/src/lib/rizzo/ProgressBar.svelte +58 -0
- package/scaffold/svelte-app/src/lib/rizzo/Radio.svelte +38 -0
- package/scaffold/svelte-app/src/lib/rizzo/Select.svelte +51 -0
- package/scaffold/svelte-app/src/lib/rizzo/Spinner.svelte +14 -0
- package/scaffold/svelte-app/src/lib/rizzo/Table.svelte +158 -0
- package/scaffold/svelte-app/src/lib/rizzo/Tabs.svelte +117 -0
- package/scaffold/svelte-app/src/lib/rizzo/Textarea.svelte +59 -0
- package/scaffold/svelte-app/src/lib/rizzo/ThemeSwitcher.svelte +315 -0
- package/scaffold/svelte-app/src/lib/rizzo/Toast.svelte +33 -0
- package/scaffold/svelte-app/src/lib/rizzo/Tooltip.svelte +19 -0
- package/scaffold/svelte-app/src/lib/rizzo/icons/Check.svelte +29 -0
- package/scaffold/svelte-app/src/lib/rizzo/icons/ChevronDown.svelte +29 -0
- package/scaffold/svelte-app/src/lib/rizzo/icons/Circle.svelte +29 -0
- package/scaffold/svelte-app/src/lib/rizzo/icons/Close.svelte +30 -0
- package/scaffold/svelte-app/src/lib/rizzo/icons/Cmd.svelte +27 -0
- package/scaffold/svelte-app/src/lib/rizzo/icons/Copy.svelte +30 -0
- package/scaffold/svelte-app/src/lib/rizzo/icons/Eye.svelte +30 -0
- package/scaffold/svelte-app/src/lib/rizzo/icons/Filter.svelte +29 -0
- package/scaffold/svelte-app/src/lib/rizzo/icons/Gear.svelte +30 -0
- package/scaffold/svelte-app/src/lib/rizzo/icons/IceCream.svelte +31 -0
- package/scaffold/svelte-app/src/lib/rizzo/icons/Moon.svelte +29 -0
- package/scaffold/svelte-app/src/lib/rizzo/icons/Owl.svelte +34 -0
- package/scaffold/svelte-app/src/lib/rizzo/icons/Palette.svelte +33 -0
- package/scaffold/svelte-app/src/lib/rizzo/icons/Rainbow.svelte +31 -0
- package/scaffold/svelte-app/src/lib/rizzo/icons/Search.svelte +30 -0
- package/scaffold/svelte-app/src/lib/rizzo/icons/Snowflake.svelte +34 -0
- package/scaffold/svelte-app/src/lib/rizzo/icons/Sort.svelte +30 -0
- package/scaffold/svelte-app/src/lib/rizzo/icons/devicons/Astro.svelte +45 -0
- package/scaffold/svelte-app/src/lib/rizzo/icons/devicons/Bash.svelte +28 -0
- package/scaffold/svelte-app/src/lib/rizzo/icons/devicons/Css3.svelte +23 -0
- package/scaffold/svelte-app/src/lib/rizzo/icons/devicons/Git.svelte +18 -0
- package/scaffold/svelte-app/src/lib/rizzo/icons/devicons/Html5.svelte +21 -0
- package/scaffold/svelte-app/src/lib/rizzo/icons/devicons/Javascript.svelte +19 -0
- package/scaffold/svelte-app/src/lib/rizzo/icons/devicons/Nodejs.svelte +44 -0
- package/scaffold/svelte-app/src/lib/rizzo/icons/devicons/Plaintext.svelte +24 -0
- package/scaffold/svelte-app/src/lib/rizzo/icons/devicons/React.svelte +21 -0
- package/scaffold/svelte-app/src/lib/rizzo/icons/devicons/SvelteIcon.svelte +19 -0
- package/scaffold/svelte-app/src/lib/rizzo/icons/devicons/Vue.svelte +20 -0
- package/scaffold/svelte-app/src/lib/rizzo/index.ts +33 -0
- package/scaffold/svelte-app/src/lib/rizzo-docs/CodeBlock.svelte +239 -0
- package/scaffold/svelte-app/src/lib/rizzo-docs/SvelteDocPage.svelte +99 -0
- package/scaffold/svelte-app/src/lib/rizzo-docs/pages/AccordionDoc.svelte +53 -0
- package/scaffold/svelte-app/src/lib/rizzo-docs/pages/AlertDoc.svelte +114 -0
- package/scaffold/svelte-app/src/lib/rizzo-docs/pages/AvatarDoc.svelte +92 -0
- package/scaffold/svelte-app/src/lib/rizzo-docs/pages/BadgeDoc.svelte +60 -0
- package/scaffold/svelte-app/src/lib/rizzo-docs/pages/BreadcrumbDoc.svelte +55 -0
- package/scaffold/svelte-app/src/lib/rizzo-docs/pages/ButtonDoc.svelte +55 -0
- package/scaffold/svelte-app/src/lib/rizzo-docs/pages/CardsDoc.svelte +173 -0
- package/scaffold/svelte-app/src/lib/rizzo-docs/pages/ComingSoon.svelte +12 -0
- package/scaffold/svelte-app/src/lib/rizzo-docs/pages/ComponentsOverview.svelte +92 -0
- package/scaffold/svelte-app/src/lib/rizzo-docs/pages/CopyToClipboardDoc.svelte +26 -0
- package/scaffold/svelte-app/src/lib/rizzo-docs/pages/DividerDoc.svelte +105 -0
- package/scaffold/svelte-app/src/lib/rizzo-docs/pages/DropdownDoc.svelte +161 -0
- package/scaffold/svelte-app/src/lib/rizzo-docs/pages/FormsDoc.svelte +375 -0
- package/scaffold/svelte-app/src/lib/rizzo-docs/pages/IconsDoc.svelte +246 -0
- package/scaffold/svelte-app/src/lib/rizzo-docs/pages/Index.svelte +8 -0
- package/scaffold/svelte-app/src/lib/rizzo-docs/pages/ModalDoc.svelte +50 -0
- package/scaffold/svelte-app/src/lib/rizzo-docs/pages/NavbarDoc.svelte +79 -0
- package/scaffold/svelte-app/src/lib/rizzo-docs/pages/PaginationDoc.svelte +44 -0
- package/scaffold/svelte-app/src/lib/rizzo-docs/pages/ProgressBarDoc.svelte +95 -0
- package/scaffold/svelte-app/src/lib/rizzo-docs/pages/SearchDoc.svelte +147 -0
- package/scaffold/svelte-app/src/lib/rizzo-docs/pages/SettingsDoc.svelte +158 -0
- package/scaffold/svelte-app/src/lib/rizzo-docs/pages/SpinnerDoc.svelte +41 -0
- package/scaffold/svelte-app/src/lib/rizzo-docs/pages/TableDoc.svelte +116 -0
- package/scaffold/svelte-app/src/lib/rizzo-docs/pages/TabsDoc.svelte +152 -0
- package/scaffold/svelte-app/src/lib/rizzo-docs/pages/ThemeSwitcherDoc.svelte +181 -0
- package/scaffold/svelte-app/src/lib/rizzo-docs/pages/Theming.svelte +6 -0
- package/scaffold/svelte-app/src/lib/rizzo-docs/pages/ToastDoc.svelte +136 -0
- package/scaffold/svelte-app/src/lib/rizzo-docs/pages/TooltipDoc.svelte +57 -0
- package/scaffold/svelte-app/src/routes/+page.svelte +2 -2
- package/scaffold/svelte-app/src/routes/components/+page.svelte +4 -0
- package/scaffold/svelte-app/src/routes/components/[slug]/+page.svelte +7 -0
- package/scaffold/vanilla/README.md +20 -8
- package/scaffold/vanilla/components/accordion.html +187 -0
- package/scaffold/vanilla/components/alert.html +187 -0
- package/scaffold/vanilla/components/avatar.html +187 -0
- package/scaffold/vanilla/components/badge.html +187 -0
- package/scaffold/vanilla/components/breadcrumb.html +187 -0
- package/scaffold/vanilla/components/button.html +187 -0
- package/scaffold/vanilla/components/cards.html +187 -0
- package/scaffold/vanilla/components/copy-to-clipboard.html +187 -0
- package/scaffold/vanilla/components/divider.html +187 -0
- package/scaffold/vanilla/components/dropdown.html +187 -0
- package/scaffold/vanilla/components/forms.html +187 -0
- package/scaffold/vanilla/components/icons.html +187 -0
- package/scaffold/vanilla/components/index.html +212 -0
- package/scaffold/vanilla/components/modal.html +187 -0
- package/scaffold/vanilla/components/navbar.html +187 -0
- package/scaffold/vanilla/components/pagination.html +187 -0
- package/scaffold/vanilla/components/progress-bar.html +187 -0
- package/scaffold/vanilla/components/search.html +187 -0
- package/scaffold/vanilla/components/settings.html +187 -0
- package/scaffold/vanilla/components/spinner.html +187 -0
- package/scaffold/vanilla/components/table.html +187 -0
- package/scaffold/vanilla/components/tabs.html +187 -0
- package/scaffold/vanilla/components/theme-switcher.html +187 -0
- package/scaffold/vanilla/components/toast.html +187 -0
- package/scaffold/vanilla/components/tooltip.html +187 -0
- package/scaffold/vanilla/index.html +17 -283
- package/scaffold/vanilla/js/main.js +748 -0
|
@@ -0,0 +1,701 @@
|
|
|
1
|
+
---
|
|
2
|
+
import Gear from './icons/Gear.astro';
|
|
3
|
+
import Search from './Search.astro';
|
|
4
|
+
import { getFrameworkFromPath } from '../config/frameworks.js';
|
|
5
|
+
import type { ThemeIconKey } from '../config/themes';
|
|
6
|
+
import { THEMES_DARK, THEMES_LIGHT } from '../config/themes';
|
|
7
|
+
import Owl from './icons/Owl.astro';
|
|
8
|
+
import Palette from './icons/Palette.astro';
|
|
9
|
+
import Flame from './icons/Flame.astro';
|
|
10
|
+
import Sunset from './icons/Sunset.astro';
|
|
11
|
+
import Zap from './icons/Zap.astro';
|
|
12
|
+
import Shield from './icons/Shield.astro';
|
|
13
|
+
import Heart from './icons/Heart.astro';
|
|
14
|
+
import Sun from './icons/Sun.astro';
|
|
15
|
+
import Cake from './icons/Cake.astro';
|
|
16
|
+
import Lemon from './icons/Lemon.astro';
|
|
17
|
+
import Rainbow from './icons/Rainbow.astro';
|
|
18
|
+
import Leaf from './icons/Leaf.astro';
|
|
19
|
+
import Cherry from './icons/Cherry.astro';
|
|
20
|
+
import Brush from './icons/Brush.astro';
|
|
21
|
+
|
|
22
|
+
// Extend Window interface for openSettings
|
|
23
|
+
declare global {
|
|
24
|
+
interface Window {
|
|
25
|
+
openSettings?: () => void;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
interface Props {
|
|
30
|
+
siteName?: string;
|
|
31
|
+
logo?: string;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const { siteName = 'Rizzo CSS', logo } = Astro.props;
|
|
35
|
+
|
|
36
|
+
// Get current URL pathname and framework for component links only
|
|
37
|
+
const currentPath = Astro.url.pathname;
|
|
38
|
+
const { framework } = getFrameworkFromPath(currentPath);
|
|
39
|
+
const docsPrefix = framework.pathPrefix;
|
|
40
|
+
|
|
41
|
+
/** Use only for component routes; docs and themes are general (no framework prefix). */
|
|
42
|
+
function componentHref(path: string): string {
|
|
43
|
+
if (!path.startsWith('/docs')) return path;
|
|
44
|
+
return docsPrefix + path.slice('/docs'.length) || docsPrefix || '/';
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// Navigation links with optional sub-links (doc paths are rewritten by docHref)
|
|
48
|
+
interface NavLink {
|
|
49
|
+
href: string;
|
|
50
|
+
label: string;
|
|
51
|
+
subLinks?: Array<{ href: string; label: string }>;
|
|
52
|
+
/** Components dropdown: overview full width, then two columns of links */
|
|
53
|
+
componentsMenu?: {
|
|
54
|
+
overview: { href: string; label: string };
|
|
55
|
+
links: Array<{ href: string; label: string }>;
|
|
56
|
+
};
|
|
57
|
+
/** Themes dropdown: overview full width, then Dark | Light columns with icons */
|
|
58
|
+
themesMenu?: {
|
|
59
|
+
overview: { href: string; label: string };
|
|
60
|
+
dark: Array<{ href: string; label: string; icon: typeof Owl }>;
|
|
61
|
+
light: Array<{ href: string; label: string; icon: typeof Owl }>;
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const themeIconMap: Record<ThemeIconKey, typeof Owl> = {
|
|
66
|
+
gear: Gear,
|
|
67
|
+
owl: Owl,
|
|
68
|
+
palette: Palette,
|
|
69
|
+
flame: Flame,
|
|
70
|
+
sunset: Sunset,
|
|
71
|
+
zap: Zap,
|
|
72
|
+
shield: Shield,
|
|
73
|
+
heart: Heart,
|
|
74
|
+
sun: Sun,
|
|
75
|
+
cake: Cake,
|
|
76
|
+
lemon: Lemon,
|
|
77
|
+
rainbow: Rainbow,
|
|
78
|
+
leaf: Leaf,
|
|
79
|
+
cherry: Cherry,
|
|
80
|
+
brush: Brush,
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
const navLinks: NavLink[] = [
|
|
84
|
+
{ href: '/', label: 'Home' },
|
|
85
|
+
{
|
|
86
|
+
href: '/docs/getting-started',
|
|
87
|
+
label: 'Docs',
|
|
88
|
+
subLinks: [
|
|
89
|
+
{ href: '/docs/getting-started', label: 'Getting Started' },
|
|
90
|
+
{ href: '/docs/design-system', label: 'Design System' },
|
|
91
|
+
{ href: '/docs/accessibility', label: 'Accessibility' },
|
|
92
|
+
{ href: '/docs/colors', label: 'Colors' },
|
|
93
|
+
],
|
|
94
|
+
},
|
|
95
|
+
{
|
|
96
|
+
href: componentHref('/docs/components'),
|
|
97
|
+
label: 'Components',
|
|
98
|
+
componentsMenu: {
|
|
99
|
+
overview: { href: componentHref('/docs/components'), label: 'Overview' },
|
|
100
|
+
links: [
|
|
101
|
+
{ href: componentHref('/docs/components/accordion'), label: 'Accordion' },
|
|
102
|
+
{ href: componentHref('/docs/components/alert'), label: 'Alert' },
|
|
103
|
+
{ href: componentHref('/docs/components/avatar'), label: 'Avatar' },
|
|
104
|
+
{ href: componentHref('/docs/components/badge'), label: 'Badge' },
|
|
105
|
+
{ href: componentHref('/docs/components/breadcrumb'), label: 'Breadcrumb' },
|
|
106
|
+
{ href: componentHref('/docs/components/button'), label: 'Button' },
|
|
107
|
+
{ href: componentHref('/docs/components/cards'), label: 'Cards' },
|
|
108
|
+
{ href: componentHref('/docs/components/copy-to-clipboard'), label: 'CopyToClipboard' },
|
|
109
|
+
{ href: componentHref('/docs/components/divider'), label: 'Divider' },
|
|
110
|
+
{ href: componentHref('/docs/components/dropdown'), label: 'Dropdown' },
|
|
111
|
+
{ href: componentHref('/docs/components/forms'), label: 'Forms' },
|
|
112
|
+
{ href: componentHref('/docs/components/icons'), label: 'Icons' },
|
|
113
|
+
{ href: componentHref('/docs/components/modal'), label: 'Modal' },
|
|
114
|
+
{ href: componentHref('/docs/components/navbar'), label: 'Navbar' },
|
|
115
|
+
{ href: componentHref('/docs/components/pagination'), label: 'Pagination' },
|
|
116
|
+
{ href: componentHref('/docs/components/progress-bar'), label: 'Progress Bar' },
|
|
117
|
+
{ href: componentHref('/docs/components/search'), label: 'Search' },
|
|
118
|
+
{ href: componentHref('/docs/components/settings'), label: 'Settings' },
|
|
119
|
+
{ href: componentHref('/docs/components/spinner'), label: 'Spinner' },
|
|
120
|
+
{ href: componentHref('/docs/components/table'), label: 'Table' },
|
|
121
|
+
{ href: componentHref('/docs/components/tabs'), label: 'Tabs' },
|
|
122
|
+
{ href: componentHref('/docs/components/theme-switcher'), label: 'Theme Switcher' },
|
|
123
|
+
{ href: componentHref('/docs/components/toast'), label: 'Toast' },
|
|
124
|
+
{ href: componentHref('/docs/components/tooltip'), label: 'Tooltip' },
|
|
125
|
+
],
|
|
126
|
+
},
|
|
127
|
+
},
|
|
128
|
+
{
|
|
129
|
+
href: '/docs/theming',
|
|
130
|
+
label: 'Themes',
|
|
131
|
+
themesMenu: {
|
|
132
|
+
overview: { href: '/docs/theming', label: 'Overview' },
|
|
133
|
+
dark: THEMES_DARK.map((t) => ({
|
|
134
|
+
href: `/docs/themes/${t.value}`,
|
|
135
|
+
label: t.label,
|
|
136
|
+
icon: themeIconMap[t.iconKey],
|
|
137
|
+
})),
|
|
138
|
+
light: THEMES_LIGHT.map((t) => ({
|
|
139
|
+
href: `/docs/themes/${t.value}`,
|
|
140
|
+
label: t.label,
|
|
141
|
+
icon: themeIconMap[t.iconKey],
|
|
142
|
+
})),
|
|
143
|
+
},
|
|
144
|
+
},
|
|
145
|
+
];
|
|
146
|
+
---
|
|
147
|
+
|
|
148
|
+
<nav class="navbar" role="navigation" aria-label="Main navigation">
|
|
149
|
+
<div class="navbar__container">
|
|
150
|
+
<div class="navbar__brand">
|
|
151
|
+
{logo && (
|
|
152
|
+
<img src={logo} alt={`${siteName} logo`} class="navbar__logo" />
|
|
153
|
+
)}
|
|
154
|
+
<a href="/" class="navbar__brand-link" aria-label={`${siteName} home`}>
|
|
155
|
+
{siteName}
|
|
156
|
+
</a>
|
|
157
|
+
</div>
|
|
158
|
+
|
|
159
|
+
<div class="navbar__actions-desktop">
|
|
160
|
+
<Search />
|
|
161
|
+
<div class="tooltip-wrapper" aria-describedby="navbar-settings-tooltip">
|
|
162
|
+
<button
|
|
163
|
+
class="navbar__settings-btn"
|
|
164
|
+
type="button"
|
|
165
|
+
aria-label="Open settings"
|
|
166
|
+
onclick="window.openSettings && window.openSettings()"
|
|
167
|
+
>
|
|
168
|
+
<Gear class="navbar__settings-icon" width={20} height={20} />
|
|
169
|
+
<span class="navbar__settings-label">Settings</span>
|
|
170
|
+
</button>
|
|
171
|
+
<span class="tooltip tooltip--bottom" id="navbar-settings-tooltip" role="tooltip" aria-hidden="true">Settings</span>
|
|
172
|
+
</div>
|
|
173
|
+
</div>
|
|
174
|
+
|
|
175
|
+
<button
|
|
176
|
+
class="navbar__toggle"
|
|
177
|
+
type="button"
|
|
178
|
+
aria-expanded="false"
|
|
179
|
+
aria-controls="navbar-menu"
|
|
180
|
+
aria-label="Toggle navigation menu"
|
|
181
|
+
id="navbar-toggle"
|
|
182
|
+
>
|
|
183
|
+
<span class="sr-only">Menu</span>
|
|
184
|
+
<span class="navbar__toggle-icon" aria-hidden="true">
|
|
185
|
+
<span></span>
|
|
186
|
+
<span></span>
|
|
187
|
+
<span></span>
|
|
188
|
+
</span>
|
|
189
|
+
</button>
|
|
190
|
+
|
|
191
|
+
<div class="navbar__menu" id="navbar-menu" role="menu">
|
|
192
|
+
{navLinks.map((link) => {
|
|
193
|
+
const isActive = currentPath === link.href || (link.href !== '/' && currentPath.startsWith(link.href));
|
|
194
|
+
const hasSubLinks = (link.subLinks && link.subLinks.length > 0) || !!link.componentsMenu || !!link.themesMenu;
|
|
195
|
+
const subLinkId = hasSubLinks ? `navbar-submenu-${link.label.toLowerCase().replace(/\s+/g, '-')}` : undefined;
|
|
196
|
+
|
|
197
|
+
return (
|
|
198
|
+
<div class={`navbar__item ${hasSubLinks ? 'navbar__item--has-dropdown' : ''}`}>
|
|
199
|
+
{hasSubLinks ? (
|
|
200
|
+
<div
|
|
201
|
+
class="navbar__link"
|
|
202
|
+
role="menuitem"
|
|
203
|
+
tabindex={0}
|
|
204
|
+
aria-label={link.label}
|
|
205
|
+
aria-expanded="false"
|
|
206
|
+
aria-haspopup="true"
|
|
207
|
+
aria-controls={subLinkId}
|
|
208
|
+
data-dropdown-toggle={subLinkId}
|
|
209
|
+
data-dropdown-href={link.href}
|
|
210
|
+
>
|
|
211
|
+
{link.label}
|
|
212
|
+
<svg class="navbar__dropdown-icon" width="12" height="12" viewBox="0 0 12 12" fill="none" stroke="currentColor" stroke-width="2" aria-hidden="true">
|
|
213
|
+
<path d="M2 4l4 4 4-4"/>
|
|
214
|
+
</svg>
|
|
215
|
+
</div>
|
|
216
|
+
) : (
|
|
217
|
+
<div
|
|
218
|
+
class="navbar__link"
|
|
219
|
+
role="menuitem"
|
|
220
|
+
tabindex={0}
|
|
221
|
+
aria-label={link.label}
|
|
222
|
+
aria-current={isActive ? 'page' : undefined}
|
|
223
|
+
data-nav-href={link.href}
|
|
224
|
+
>
|
|
225
|
+
{link.label}
|
|
226
|
+
</div>
|
|
227
|
+
)}
|
|
228
|
+
{link.componentsMenu ? (
|
|
229
|
+
<ul class="navbar__submenu navbar__submenu--components" id={subLinkId} role="menu" aria-label={`${link.label} submenu`}>
|
|
230
|
+
<li class="navbar__submenu-overview" role="none">
|
|
231
|
+
<div
|
|
232
|
+
class="navbar__sublink navbar__sublink--overview"
|
|
233
|
+
role="menuitem"
|
|
234
|
+
tabindex={-1}
|
|
235
|
+
aria-label={link.componentsMenu.overview.label}
|
|
236
|
+
aria-current={currentPath === link.componentsMenu.overview.href ? 'page' : undefined}
|
|
237
|
+
data-nav-href={link.componentsMenu.overview.href}
|
|
238
|
+
>
|
|
239
|
+
{link.componentsMenu.overview.label}
|
|
240
|
+
</div>
|
|
241
|
+
</li>
|
|
242
|
+
<li class="navbar__submenu-components-grid" role="none">
|
|
243
|
+
<div class="navbar__submenu-column">
|
|
244
|
+
{link.componentsMenu.links.slice(0, Math.ceil(link.componentsMenu.links.length / 2)).map((subLink) => {
|
|
245
|
+
const isSubActive = currentPath === subLink.href;
|
|
246
|
+
return (
|
|
247
|
+
<div
|
|
248
|
+
class="navbar__sublink"
|
|
249
|
+
role="menuitem"
|
|
250
|
+
tabindex={-1}
|
|
251
|
+
aria-label={subLink.label}
|
|
252
|
+
aria-current={isSubActive ? 'page' : undefined}
|
|
253
|
+
data-nav-href={subLink.href}
|
|
254
|
+
>
|
|
255
|
+
{subLink.label}
|
|
256
|
+
</div>
|
|
257
|
+
);
|
|
258
|
+
})}
|
|
259
|
+
</div>
|
|
260
|
+
<div class="navbar__submenu-column">
|
|
261
|
+
{link.componentsMenu.links.slice(Math.ceil(link.componentsMenu.links.length / 2)).map((subLink) => {
|
|
262
|
+
const isSubActive = currentPath === subLink.href;
|
|
263
|
+
return (
|
|
264
|
+
<div
|
|
265
|
+
class="navbar__sublink"
|
|
266
|
+
role="menuitem"
|
|
267
|
+
tabindex={-1}
|
|
268
|
+
aria-label={subLink.label}
|
|
269
|
+
aria-current={isSubActive ? 'page' : undefined}
|
|
270
|
+
data-nav-href={subLink.href}
|
|
271
|
+
>
|
|
272
|
+
{subLink.label}
|
|
273
|
+
</div>
|
|
274
|
+
);
|
|
275
|
+
})}
|
|
276
|
+
</div>
|
|
277
|
+
</li>
|
|
278
|
+
</ul>
|
|
279
|
+
) : link.themesMenu ? (
|
|
280
|
+
<ul class="navbar__submenu navbar__submenu--themes" id={subLinkId} role="menu" aria-label={`${link.label} submenu`}>
|
|
281
|
+
<li class="navbar__submenu-overview" role="none">
|
|
282
|
+
<div
|
|
283
|
+
class="navbar__sublink navbar__sublink--overview"
|
|
284
|
+
role="menuitem"
|
|
285
|
+
tabindex={-1}
|
|
286
|
+
aria-label={link.themesMenu.overview.label}
|
|
287
|
+
aria-current={currentPath === link.themesMenu.overview.href ? 'page' : undefined}
|
|
288
|
+
data-nav-href={link.themesMenu.overview.href}
|
|
289
|
+
>
|
|
290
|
+
{link.themesMenu.overview.label}
|
|
291
|
+
</div>
|
|
292
|
+
</li>
|
|
293
|
+
<li class="navbar__submenu-themes-grid" role="none">
|
|
294
|
+
<div class="navbar__submenu-column">
|
|
295
|
+
<span class="navbar__submenu-column-label" aria-hidden="true">Dark themes</span>
|
|
296
|
+
{link.themesMenu.dark.map((theme) => {
|
|
297
|
+
const isSubActive = currentPath === theme.href;
|
|
298
|
+
const ThemeIcon = theme.icon;
|
|
299
|
+
return (
|
|
300
|
+
<div
|
|
301
|
+
class="navbar__sublink navbar__sublink--with-icon"
|
|
302
|
+
role="menuitem"
|
|
303
|
+
tabindex={-1}
|
|
304
|
+
aria-label={theme.label}
|
|
305
|
+
aria-current={isSubActive ? 'page' : undefined}
|
|
306
|
+
data-nav-href={theme.href}
|
|
307
|
+
>
|
|
308
|
+
<ThemeIcon width={16} height={16} class="navbar__sublink-icon" />
|
|
309
|
+
<span class="navbar__sublink-text">{theme.label}</span>
|
|
310
|
+
</div>
|
|
311
|
+
);
|
|
312
|
+
})}
|
|
313
|
+
</div>
|
|
314
|
+
<div class="navbar__submenu-column">
|
|
315
|
+
<span class="navbar__submenu-column-label" aria-hidden="true">Light themes</span>
|
|
316
|
+
{link.themesMenu.light.map((theme) => {
|
|
317
|
+
const isSubActive = currentPath === theme.href;
|
|
318
|
+
const ThemeIcon = theme.icon;
|
|
319
|
+
return (
|
|
320
|
+
<div
|
|
321
|
+
class="navbar__sublink navbar__sublink--with-icon"
|
|
322
|
+
role="menuitem"
|
|
323
|
+
tabindex={-1}
|
|
324
|
+
aria-label={theme.label}
|
|
325
|
+
aria-current={isSubActive ? 'page' : undefined}
|
|
326
|
+
data-nav-href={theme.href}
|
|
327
|
+
>
|
|
328
|
+
<ThemeIcon width={16} height={16} class="navbar__sublink-icon" />
|
|
329
|
+
<span class="navbar__sublink-text">{theme.label}</span>
|
|
330
|
+
</div>
|
|
331
|
+
);
|
|
332
|
+
})}
|
|
333
|
+
</div>
|
|
334
|
+
</li>
|
|
335
|
+
</ul>
|
|
336
|
+
) : hasSubLinks && link.subLinks ? (
|
|
337
|
+
<ul class="navbar__submenu" id={subLinkId} role="menu" aria-label={`${link.label} submenu`}>
|
|
338
|
+
{link.subLinks.map((subLink) => {
|
|
339
|
+
const isSubActive = currentPath === subLink.href;
|
|
340
|
+
return (
|
|
341
|
+
<li role="none">
|
|
342
|
+
<div
|
|
343
|
+
class="navbar__sublink"
|
|
344
|
+
role="menuitem"
|
|
345
|
+
tabindex={-1}
|
|
346
|
+
aria-label={subLink.label}
|
|
347
|
+
aria-current={isSubActive ? 'page' : undefined}
|
|
348
|
+
data-nav-href={subLink.href}
|
|
349
|
+
>
|
|
350
|
+
{subLink.label}
|
|
351
|
+
</div>
|
|
352
|
+
</li>
|
|
353
|
+
);
|
|
354
|
+
})}
|
|
355
|
+
</ul>
|
|
356
|
+
) : null}
|
|
357
|
+
</div>
|
|
358
|
+
);
|
|
359
|
+
})}
|
|
360
|
+
</div>
|
|
361
|
+
</div>
|
|
362
|
+
</nav>
|
|
363
|
+
|
|
364
|
+
<script>
|
|
365
|
+
(function initNavbar() {
|
|
366
|
+
const navbar = document.querySelector('.navbar');
|
|
367
|
+
if (!navbar) return;
|
|
368
|
+
|
|
369
|
+
const menuToggleBtn = document.getElementById('navbar-toggle');
|
|
370
|
+
const menu = navbar.querySelector('.navbar__menu');
|
|
371
|
+
|
|
372
|
+
if (!menuToggleBtn || !menu) return;
|
|
373
|
+
|
|
374
|
+
const toggleMenu = (open?: boolean) => {
|
|
375
|
+
const isOpen = open !== undefined ? open : menu.classList.contains('navbar__menu--open');
|
|
376
|
+
const willBeOpen = !isOpen;
|
|
377
|
+
|
|
378
|
+
// On mobile, close search if it's open when opening menu
|
|
379
|
+
const isMobile = window.innerWidth <= 1023;
|
|
380
|
+
if (isMobile && !isOpen) {
|
|
381
|
+
// Menu is about to open, close any open search instance
|
|
382
|
+
document.querySelectorAll('[data-search]').forEach((search) => {
|
|
383
|
+
const panel = search.querySelector('.search__panel');
|
|
384
|
+
if (panel?.getAttribute('aria-hidden') === 'false') {
|
|
385
|
+
const el = search as Element & { __searchInstance?: { closeSearch?: () => void } };
|
|
386
|
+
if (el?.__searchInstance?.closeSearch) {
|
|
387
|
+
el.__searchInstance.closeSearch();
|
|
388
|
+
} else {
|
|
389
|
+
const overlay = search.querySelector('[data-search-overlay]');
|
|
390
|
+
if (overlay instanceof HTMLElement) overlay.click();
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
});
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
// Toggle class on navbar to hide/show bottom border
|
|
397
|
+
if (willBeOpen) {
|
|
398
|
+
navbar.classList.add('navbar--menu-open');
|
|
399
|
+
} else {
|
|
400
|
+
navbar.classList.remove('navbar--menu-open');
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
menu.classList.toggle('navbar__menu--open', !isOpen);
|
|
404
|
+
menuToggleBtn.setAttribute('aria-expanded', (!isOpen).toString());
|
|
405
|
+
};
|
|
406
|
+
|
|
407
|
+
menuToggleBtn.addEventListener('click', (e) => {
|
|
408
|
+
const target = e.target as Element;
|
|
409
|
+
// Only skip if click was inside the menu (e.g. dropdown toggle) - not when clicking the hamburger
|
|
410
|
+
if (target.closest('#navbar-menu') && target.closest('[data-dropdown-toggle]')) {
|
|
411
|
+
return;
|
|
412
|
+
}
|
|
413
|
+
toggleMenu();
|
|
414
|
+
});
|
|
415
|
+
|
|
416
|
+
// Navigate when activating menu items that use data-nav-href (non-interactive element with role=menuitem)
|
|
417
|
+
const navHrefHandler = (e: Event) => {
|
|
418
|
+
const target = (e.target as Element).closest('[data-nav-href]');
|
|
419
|
+
if (target && target instanceof HTMLElement) {
|
|
420
|
+
const href = target.getAttribute('data-nav-href');
|
|
421
|
+
if (href) {
|
|
422
|
+
e.preventDefault();
|
|
423
|
+
window.location.href = href;
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
};
|
|
427
|
+
navbar.addEventListener('click', navHrefHandler);
|
|
428
|
+
navbar.addEventListener('keydown', (e: Event) => {
|
|
429
|
+
const keyEvent = e as KeyboardEvent;
|
|
430
|
+
if (keyEvent.key === 'Enter' || keyEvent.key === ' ') {
|
|
431
|
+
const target = (keyEvent.target as Element).closest('[data-nav-href]');
|
|
432
|
+
if (target && target instanceof HTMLElement) {
|
|
433
|
+
const href = target.getAttribute('data-nav-href');
|
|
434
|
+
if (href) {
|
|
435
|
+
keyEvent.preventDefault();
|
|
436
|
+
window.location.href = href;
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
});
|
|
441
|
+
|
|
442
|
+
// Dropdown menu handling
|
|
443
|
+
const dropdownToggles = navbar.querySelectorAll('[data-dropdown-toggle]');
|
|
444
|
+
dropdownToggles.forEach((dropdownToggleBtn) => {
|
|
445
|
+
const item = dropdownToggleBtn.closest('.navbar__item--has-dropdown');
|
|
446
|
+
if (!item) return;
|
|
447
|
+
|
|
448
|
+
const submenu = item.querySelector('.navbar__submenu');
|
|
449
|
+
const sublinks = submenu ? Array.from(submenu.querySelectorAll('.navbar__sublink')) : [];
|
|
450
|
+
|
|
451
|
+
// Adjust dropdown alignment to prevent overflow
|
|
452
|
+
const adjustDropdownPosition = () => {
|
|
453
|
+
if (!submenu || window.innerWidth <= 1023) return; // Skip on mobile
|
|
454
|
+
|
|
455
|
+
const submenuEl = submenu as HTMLElement;
|
|
456
|
+
|
|
457
|
+
// Temporarily make visible to measure
|
|
458
|
+
const wasVisible = submenuEl.style.visibility !== 'hidden';
|
|
459
|
+
if (!wasVisible) {
|
|
460
|
+
submenuEl.style.visibility = 'hidden';
|
|
461
|
+
submenuEl.style.opacity = '1';
|
|
462
|
+
submenuEl.style.display = 'block';
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
const itemRect = item.getBoundingClientRect();
|
|
466
|
+
const submenuWidth = submenuEl.offsetWidth || submenuEl.scrollWidth;
|
|
467
|
+
const viewportWidth = window.innerWidth;
|
|
468
|
+
const padding = 16; // Viewport padding
|
|
469
|
+
|
|
470
|
+
// Check if dropdown would overflow on the right
|
|
471
|
+
if (itemRect.left + submenuWidth > viewportWidth - padding) {
|
|
472
|
+
// Align to right edge of parent item
|
|
473
|
+
submenuEl.style.left = 'auto';
|
|
474
|
+
submenuEl.style.right = '0';
|
|
475
|
+
} else {
|
|
476
|
+
// Default: align to left edge
|
|
477
|
+
submenuEl.style.left = '0';
|
|
478
|
+
submenuEl.style.right = 'auto';
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
// Restore visibility state
|
|
482
|
+
if (!wasVisible) {
|
|
483
|
+
submenuEl.style.visibility = '';
|
|
484
|
+
submenuEl.style.opacity = '';
|
|
485
|
+
submenuEl.style.display = '';
|
|
486
|
+
}
|
|
487
|
+
};
|
|
488
|
+
|
|
489
|
+
const handleToggle = (e: Event) => {
|
|
490
|
+
e.preventDefault();
|
|
491
|
+
e.stopPropagation();
|
|
492
|
+
e.stopImmediatePropagation();
|
|
493
|
+
|
|
494
|
+
const isMobile = window.innerWidth <= 1023;
|
|
495
|
+
const isExpanded = item.getAttribute('aria-expanded') === 'true';
|
|
496
|
+
|
|
497
|
+
// On mobile, preserve scroll position to prevent jarring
|
|
498
|
+
let scrollTop = 0;
|
|
499
|
+
if (isMobile && menu) {
|
|
500
|
+
scrollTop = (menu as HTMLElement).scrollTop;
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
item.setAttribute('aria-expanded', (!isExpanded).toString());
|
|
504
|
+
|
|
505
|
+
// Adjust position when opening
|
|
506
|
+
if (!isExpanded) {
|
|
507
|
+
setTimeout(() => {
|
|
508
|
+
adjustDropdownPosition();
|
|
509
|
+
|
|
510
|
+
// Restore scroll position on mobile to prevent jarring
|
|
511
|
+
if (isMobile && menu) {
|
|
512
|
+
(menu as HTMLElement).scrollTop = scrollTop;
|
|
513
|
+
}
|
|
514
|
+
}, 0);
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
// Focus management - only on desktop
|
|
518
|
+
if (!isMobile && !isExpanded && sublinks.length > 0) {
|
|
519
|
+
setTimeout(() => (sublinks[0] as HTMLElement).focus(), 0);
|
|
520
|
+
}
|
|
521
|
+
};
|
|
522
|
+
|
|
523
|
+
// Always use capture phase to ensure we handle it before any other listeners
|
|
524
|
+
dropdownToggleBtn.addEventListener('click', handleToggle, true);
|
|
525
|
+
|
|
526
|
+
// Keyboard navigation on dropdown toggle
|
|
527
|
+
dropdownToggleBtn.addEventListener('keydown', (e: Event) => {
|
|
528
|
+
const keyEvent = e as KeyboardEvent;
|
|
529
|
+
if (keyEvent.key === 'Enter' || keyEvent.key === ' ') {
|
|
530
|
+
keyEvent.preventDefault();
|
|
531
|
+
handleToggle(e);
|
|
532
|
+
} else if (keyEvent.key === 'ArrowDown' && sublinks.length > 0) {
|
|
533
|
+
keyEvent.preventDefault();
|
|
534
|
+
const isExpanded = item.getAttribute('aria-expanded') === 'true';
|
|
535
|
+
if (!isExpanded) {
|
|
536
|
+
item.setAttribute('aria-expanded', 'true');
|
|
537
|
+
adjustDropdownPosition();
|
|
538
|
+
}
|
|
539
|
+
setTimeout(() => (sublinks[0] as HTMLElement).focus(), 0);
|
|
540
|
+
} else if (keyEvent.key === 'ArrowUp' && sublinks.length > 0) {
|
|
541
|
+
keyEvent.preventDefault();
|
|
542
|
+
const isExpanded = item.getAttribute('aria-expanded') === 'true';
|
|
543
|
+
if (!isExpanded) {
|
|
544
|
+
item.setAttribute('aria-expanded', 'true');
|
|
545
|
+
adjustDropdownPosition();
|
|
546
|
+
}
|
|
547
|
+
// Focus last item when ArrowUp from toggle
|
|
548
|
+
setTimeout(() => (sublinks[sublinks.length - 1] as HTMLElement).focus(), 0);
|
|
549
|
+
} else if (keyEvent.key === 'Escape') {
|
|
550
|
+
keyEvent.preventDefault();
|
|
551
|
+
item.setAttribute('aria-expanded', 'false');
|
|
552
|
+
}
|
|
553
|
+
});
|
|
554
|
+
|
|
555
|
+
// Keyboard navigation within submenu
|
|
556
|
+
if (submenu) {
|
|
557
|
+
submenu.addEventListener('keydown', (e: Event) => {
|
|
558
|
+
const keyEvent = e as KeyboardEvent;
|
|
559
|
+
const currentIndex = sublinks.findIndex(link => link === document.activeElement);
|
|
560
|
+
|
|
561
|
+
if (keyEvent.key === 'Escape') {
|
|
562
|
+
keyEvent.preventDefault();
|
|
563
|
+
item.setAttribute('aria-expanded', 'false');
|
|
564
|
+
(dropdownToggleBtn as HTMLElement).focus();
|
|
565
|
+
} else if (keyEvent.key === 'ArrowDown') {
|
|
566
|
+
keyEvent.preventDefault();
|
|
567
|
+
const nextIndex = (currentIndex + 1) % sublinks.length;
|
|
568
|
+
(sublinks[nextIndex] as HTMLElement).focus();
|
|
569
|
+
} else if (keyEvent.key === 'ArrowUp') {
|
|
570
|
+
keyEvent.preventDefault();
|
|
571
|
+
const prevIndex = currentIndex <= 0 ? sublinks.length - 1 : currentIndex - 1;
|
|
572
|
+
(sublinks[prevIndex] as HTMLElement).focus();
|
|
573
|
+
} else if (keyEvent.key === 'Home') {
|
|
574
|
+
keyEvent.preventDefault();
|
|
575
|
+
(sublinks[0] as HTMLElement).focus();
|
|
576
|
+
} else if (keyEvent.key === 'End') {
|
|
577
|
+
keyEvent.preventDefault();
|
|
578
|
+
(sublinks[sublinks.length - 1] as HTMLElement).focus();
|
|
579
|
+
} else if (keyEvent.key === 'Tab') {
|
|
580
|
+
// Close dropdown when Tab is pressed (allows natural tab flow)
|
|
581
|
+
item.setAttribute('aria-expanded', 'false');
|
|
582
|
+
} else if (keyEvent.key === 'Enter' || keyEvent.key === ' ') {
|
|
583
|
+
// Allow Enter/Space to activate links naturally
|
|
584
|
+
// The click handler will handle navigation
|
|
585
|
+
}
|
|
586
|
+
});
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
// Adjust on hover (for desktop)
|
|
590
|
+
if (window.innerWidth > 1023) {
|
|
591
|
+
item.addEventListener('mouseenter', () => {
|
|
592
|
+
// Small delay to ensure submenu is rendered
|
|
593
|
+
requestAnimationFrame(() => {
|
|
594
|
+
requestAnimationFrame(() => {
|
|
595
|
+
adjustDropdownPosition();
|
|
596
|
+
});
|
|
597
|
+
});
|
|
598
|
+
});
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
// Adjust on window resize
|
|
602
|
+
window.addEventListener('resize', adjustDropdownPosition);
|
|
603
|
+
|
|
604
|
+
// Close on outside click
|
|
605
|
+
document.addEventListener('click', (e) => {
|
|
606
|
+
const target = e.target;
|
|
607
|
+
// Don't close if clicking on the dropdown toggle or its children
|
|
608
|
+
const clickedToggle = target && target instanceof Element && (
|
|
609
|
+
target === dropdownToggleBtn ||
|
|
610
|
+
target.closest('[data-dropdown-toggle]') === dropdownToggleBtn
|
|
611
|
+
);
|
|
612
|
+
if (target && target instanceof Node && !item.contains(target) && !clickedToggle) {
|
|
613
|
+
item.setAttribute('aria-expanded', 'false');
|
|
614
|
+
}
|
|
615
|
+
});
|
|
616
|
+
});
|
|
617
|
+
|
|
618
|
+
// Close menu on outside click
|
|
619
|
+
document.addEventListener('click', (e) => {
|
|
620
|
+
const target = e.target as Node | null;
|
|
621
|
+
// Don't close if clicking inside the navbar (including dropdown toggles and their children)
|
|
622
|
+
if (target && navbar.contains(target as Node)) {
|
|
623
|
+
// Also check if it's specifically a dropdown toggle or submenu item
|
|
624
|
+
const element = target as Element;
|
|
625
|
+
if (element && (
|
|
626
|
+
element.closest('[data-dropdown-toggle]') ||
|
|
627
|
+
element.closest('.navbar__submenu') ||
|
|
628
|
+
element.closest('.navbar__sublink')
|
|
629
|
+
)) {
|
|
630
|
+
return; // Click is on dropdown-related element, don't close menu
|
|
631
|
+
}
|
|
632
|
+
return; // Click is inside navbar, don't close menu
|
|
633
|
+
}
|
|
634
|
+
// Only close if clicking completely outside the navbar
|
|
635
|
+
if (target && !navbar.contains(target as Node)) {
|
|
636
|
+
menu.classList.remove('navbar__menu--open');
|
|
637
|
+
menuToggleBtn.setAttribute('aria-expanded', 'false');
|
|
638
|
+
// Close all dropdowns
|
|
639
|
+
dropdownToggles.forEach((dt) => {
|
|
640
|
+
const item = dt.closest('.navbar__item--has-dropdown');
|
|
641
|
+
if (item) item.setAttribute('aria-expanded', 'false');
|
|
642
|
+
});
|
|
643
|
+
}
|
|
644
|
+
});
|
|
645
|
+
|
|
646
|
+
// Close menu on Escape key
|
|
647
|
+
document.addEventListener('keydown', (e: Event) => {
|
|
648
|
+
const keyEvent = e as KeyboardEvent;
|
|
649
|
+
if (keyEvent.key === 'Escape') {
|
|
650
|
+
if (menu.classList.contains('navbar__menu--open')) {
|
|
651
|
+
toggleMenu(false);
|
|
652
|
+
menuToggleBtn.focus();
|
|
653
|
+
}
|
|
654
|
+
// Close all dropdowns
|
|
655
|
+
dropdownToggles.forEach((dt) => {
|
|
656
|
+
const item = dt.closest('.navbar__item--has-dropdown');
|
|
657
|
+
if (item) {
|
|
658
|
+
item.setAttribute('aria-expanded', 'false');
|
|
659
|
+
(dt as HTMLElement).focus();
|
|
660
|
+
}
|
|
661
|
+
});
|
|
662
|
+
}
|
|
663
|
+
});
|
|
664
|
+
|
|
665
|
+
// Settings button keyboard handler
|
|
666
|
+
const settingsBtn = navbar.querySelector('.navbar__settings-btn');
|
|
667
|
+
if (settingsBtn) {
|
|
668
|
+
settingsBtn.addEventListener('click', () => {
|
|
669
|
+
if (window.openSettings) {
|
|
670
|
+
window.openSettings();
|
|
671
|
+
}
|
|
672
|
+
});
|
|
673
|
+
settingsBtn.addEventListener('keydown', (e: Event) => {
|
|
674
|
+
const keyEvent = e as KeyboardEvent;
|
|
675
|
+
if (keyEvent.key === 'Enter' || keyEvent.key === ' ') {
|
|
676
|
+
keyEvent.preventDefault();
|
|
677
|
+
if (window.openSettings) {
|
|
678
|
+
window.openSettings();
|
|
679
|
+
}
|
|
680
|
+
}
|
|
681
|
+
});
|
|
682
|
+
}
|
|
683
|
+
|
|
684
|
+
// Handle window resize
|
|
685
|
+
let resizeTimer: ReturnType<typeof setTimeout> | undefined;
|
|
686
|
+
window.addEventListener('resize', () => {
|
|
687
|
+
if (resizeTimer) clearTimeout(resizeTimer);
|
|
688
|
+
resizeTimer = setTimeout(() => {
|
|
689
|
+
if (window.innerWidth >= 1024) {
|
|
690
|
+
menu.classList.remove('navbar__menu--open');
|
|
691
|
+
menuToggleBtn.setAttribute('aria-expanded', 'false');
|
|
692
|
+
// Close all dropdowns
|
|
693
|
+
dropdownToggles.forEach((dt) => {
|
|
694
|
+
const item = dt.closest('.navbar__item--has-dropdown');
|
|
695
|
+
if (item) item.setAttribute('aria-expanded', 'false');
|
|
696
|
+
});
|
|
697
|
+
}
|
|
698
|
+
}, 250);
|
|
699
|
+
});
|
|
700
|
+
})();
|
|
701
|
+
</script>
|