@cronos-labs/ui 0.8.0 → 0.8.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +60 -16
- package/dist/Footer/index.d.ts +7 -38
- package/dist/Footer/index.js +9 -63
- package/dist/Footer/static.d.ts +16 -0
- package/dist/Footer/static.js +11 -0
- package/dist/Footer/styles.d.ts +2 -2
- package/dist/Footer/styles.js +29 -27
- package/dist/Footer/types.d.ts +41 -0
- package/dist/Footer/types.js +1 -0
- package/dist/Footer/view.d.ts +2 -0
- package/dist/Footer/view.js +55 -0
- package/dist/Header/NavDropdown.d.ts +1 -1
- package/dist/Header/NavDropdown.js +3 -3
- package/dist/Header/index.d.ts +7 -56
- package/dist/Header/index.js +9 -186
- package/dist/Header/static.d.ts +15 -0
- package/dist/Header/static.js +11 -0
- package/dist/Header/styles.d.ts +4 -1
- package/dist/Header/styles.js +27 -4
- package/dist/Header/types.d.ts +61 -0
- package/dist/Header/types.js +1 -0
- package/dist/Header/view.d.ts +2 -0
- package/dist/Header/view.js +184 -0
- package/dist/LocaleSelector/index.d.ts +8 -9
- package/dist/LocaleSelector/index.js +10 -61
- package/dist/LocaleSelector/view.d.ts +8 -0
- package/dist/LocaleSelector/view.js +59 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +4 -0
- package/dist/navigation/context.d.ts +26 -0
- package/dist/navigation/context.js +18 -0
- package/dist/navigation/router.d.ts +11 -0
- package/dist/navigation/router.js +25 -0
- package/dist/navigation/static.d.ts +15 -0
- package/dist/navigation/static.js +19 -0
- package/dist/navigationContent.d.ts +6 -0
- package/dist/navigationContent.js +27 -0
- package/package.json +2 -1
package/dist/Header/index.d.ts
CHANGED
|
@@ -1,56 +1,7 @@
|
|
|
1
|
-
|
|
2
|
-
export
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
export interface HeaderNavChild {
|
|
9
|
-
key: string;
|
|
10
|
-
label: string;
|
|
11
|
-
route?: string;
|
|
12
|
-
external?: boolean;
|
|
13
|
-
/**
|
|
14
|
-
* Renders the child greyed and non-interactive, with `badge` beside it if
|
|
15
|
-
* given — for a destination that is not open yet. A disabled child needs
|
|
16
|
-
* no `route`.
|
|
17
|
-
*/
|
|
18
|
-
disabled?: boolean;
|
|
19
|
-
badge?: string;
|
|
20
|
-
}
|
|
21
|
-
export interface HeaderNavItem {
|
|
22
|
-
label: string;
|
|
23
|
-
route?: string;
|
|
24
|
-
enabled: boolean;
|
|
25
|
-
order: number;
|
|
26
|
-
external?: boolean;
|
|
27
|
-
placeholder?: boolean;
|
|
28
|
-
badge?: string;
|
|
29
|
-
children?: HeaderNavChild[];
|
|
30
|
-
}
|
|
31
|
-
export interface HeaderContent {
|
|
32
|
-
joinWaitlist: string;
|
|
33
|
-
waitlistEmailCta: string;
|
|
34
|
-
localeSelectorAriaLabel: string;
|
|
35
|
-
}
|
|
36
|
-
export interface HeaderWaitlistCta {
|
|
37
|
-
href: string;
|
|
38
|
-
onClick?: (event: React.MouseEvent<HTMLAnchorElement>, ctaLocation: 'nav_desktop' | 'nav_mobile') => void;
|
|
39
|
-
/** Renders the CTA greyed and non-interactive, keeping its place and label. */
|
|
40
|
-
disabled?: boolean;
|
|
41
|
-
}
|
|
42
|
-
interface HeaderProps {
|
|
43
|
-
/** Overrides the wordmark from `<BrandProvider>`. Optional when one is present. */
|
|
44
|
-
brandWordmarkSrc?: string;
|
|
45
|
-
content: HeaderContent;
|
|
46
|
-
forceDocumentNavigation?: boolean;
|
|
47
|
-
homeHref: string;
|
|
48
|
-
isExternalHref: (href: string) => boolean;
|
|
49
|
-
mobilePresentation?: HeaderPresentationState | undefined;
|
|
50
|
-
navItems: HeaderNavItem[];
|
|
51
|
-
presentation: HeaderPresentationState;
|
|
52
|
-
shouldAnimatePresentation: boolean;
|
|
53
|
-
waitlistCta?: HeaderWaitlistCta | undefined;
|
|
54
|
-
}
|
|
55
|
-
export declare function Header({ brandWordmarkSrc, content, forceDocumentNavigation, homeHref, isExternalHref, mobilePresentation, navItems, presentation, shouldAnimatePresentation, waitlistCta, }: HeaderProps): React.JSX.Element;
|
|
56
|
-
export {};
|
|
1
|
+
import type { HeaderProps } from './types.js';
|
|
2
|
+
export type { HeaderContent, HeaderNavChild, HeaderNavItem, HeaderPresentationState, HeaderPresentationVariant, HeaderProps, HeaderWaitlistCta, } from './types.js';
|
|
3
|
+
/**
|
|
4
|
+
* The site header for apps with a react-router `<Router>` above it. Static
|
|
5
|
+
* sites without a router render `HeaderStatic` instead.
|
|
6
|
+
*/
|
|
7
|
+
export declare function Header(props: HeaderProps): React.JSX.Element;
|
package/dist/Header/index.js
CHANGED
|
@@ -1,187 +1,10 @@
|
|
|
1
|
-
import { jsx as _jsx
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
import { NavDropdown } from './NavDropdown.js';
|
|
11
|
-
import { BrandWordmarkMask, HeaderAction, HeaderActionArrow, HeaderBrandLink, HeaderFrame, HeaderMobileNav, HeaderMobileNavDisabledItem, HeaderMobileNavGroup, HeaderMobileNavGroupChevron, HeaderMobileNavGroupPanel, HeaderMobileNavGroupPanelInner, HeaderMobileNavGroupToggle, HeaderMobileNavSubDisabledItem, HeaderMobileNavSubLink, HeaderMobileNavSubList, HeaderMobileNavLink, HeaderMobileOverlay, HeaderMobileLocaleSlot, HeaderMobileSheet, HeaderMobileSoonBadge, HeaderMobileToggle, HeaderMobileToggleIcon, HeaderMobileWaitlistArrow, HeaderMobileWaitlistLabel, HeaderMobileWaitlistLink, HeaderShell, Nav, NavBadge, NavBadgeLabel, Wordmark, } from './styles.js';
|
|
12
|
-
function MobileNavGroup({ label, items }) {
|
|
13
|
-
const [isExpanded, setIsExpanded] = useState(false);
|
|
14
|
-
const panelId = `mobile-nav-group-${label.toLowerCase().replace(/\s+/gu, '-')}`;
|
|
15
|
-
return (_jsxs(HeaderMobileNavGroup, { children: [_jsxs(HeaderMobileNavGroupToggle, { type: "button", "aria-expanded": isExpanded, "aria-controls": panelId, onClick: () => setIsExpanded((currentValue) => !currentValue), children: [label, _jsx(HeaderMobileNavGroupChevron, { "$open": isExpanded, "aria-hidden": "true" })] }), _jsx(HeaderMobileNavGroupPanel, { "$open": isExpanded, id: panelId, children: _jsx(HeaderMobileNavGroupPanelInner, { "$open": isExpanded, children: _jsx(HeaderMobileNavSubList, { "aria-hidden": !isExpanded, children: items.map((item) => item.disabled ? (_jsxs(HeaderMobileNavSubDisabledItem, { "aria-disabled": "true", "aria-label": `${item.label} ${item.badge ?? ''}`.trim(), children: [item.label, item.badge ? _jsx(HeaderMobileSoonBadge, { children: item.badge }) : null] }, item.key)) : (_jsx(HeaderMobileNavSubLink, { href: item.href, "aria-label": item.ariaLabel, tabIndex: isExpanded ? undefined : -1, onClick: item.onClick, ...(item.external ? { target: '_blank', rel: 'noreferrer' } : {}), children: item.label }, item.key))) }) }) })] }));
|
|
16
|
-
}
|
|
17
|
-
export function Header({ brandWordmarkSrc, content, forceDocumentNavigation = false, homeHref, isExternalHref, mobilePresentation, navItems, presentation, shouldAnimatePresentation, waitlistCta, }) {
|
|
18
|
-
const brand = useOptionalBrand();
|
|
19
|
-
const wordmarkSrc = brandWordmarkSrc ?? brand?.wordmarkSrc;
|
|
20
|
-
const brandName = brand?.name ?? '';
|
|
21
|
-
// Rendering a header with no wordmark is never what the caller meant, and a
|
|
22
|
-
// broken image is harder to diagnose than a named error.
|
|
23
|
-
if (wordmarkSrc === undefined) {
|
|
24
|
-
throw new Error('<Header> needs a brandWordmarkSrc prop or a <BrandProvider>.');
|
|
25
|
-
}
|
|
26
|
-
const [isMobileMenuOpen, setIsMobileMenuOpen] = useState(false);
|
|
27
|
-
const locale = useCurrentLocale();
|
|
28
|
-
const location = useLocation();
|
|
29
|
-
const navigate = useNavigate();
|
|
30
|
-
const localizedHomePath = normalizePathname(homeHref);
|
|
31
|
-
const isHomeRoute = normalizePathname(location.pathname) === localizedHomePath;
|
|
32
|
-
const enabledNavItems = navItems.filter((item) => item.enabled).sort((a, b) => a.order - b.order);
|
|
33
|
-
const canonicalizeDocumentHref = (href) => {
|
|
34
|
-
const hashStart = href.indexOf('#');
|
|
35
|
-
const path = hashStart === -1 ? href : href.slice(0, hashStart);
|
|
36
|
-
const hash = hashStart === -1 ? '' : href.slice(hashStart);
|
|
37
|
-
if (path === '/' || path.endsWith('/')) {
|
|
38
|
-
return href;
|
|
39
|
-
}
|
|
40
|
-
return `${path}/${hash}`;
|
|
41
|
-
};
|
|
42
|
-
useEffect(() => {
|
|
43
|
-
setIsMobileMenuOpen(false);
|
|
44
|
-
}, [location.pathname, location.hash]);
|
|
45
|
-
useEffect(() => {
|
|
46
|
-
const desktopMediaQuery = window.matchMedia(desktopViewportQuery);
|
|
47
|
-
const handleChange = (event) => {
|
|
48
|
-
if (event.matches) {
|
|
49
|
-
setIsMobileMenuOpen(false);
|
|
50
|
-
}
|
|
51
|
-
};
|
|
52
|
-
desktopMediaQuery.addEventListener('change', handleChange);
|
|
53
|
-
return () => {
|
|
54
|
-
desktopMediaQuery.removeEventListener('change', handleChange);
|
|
55
|
-
};
|
|
56
|
-
}, []);
|
|
57
|
-
const resolveHref = (route) => {
|
|
58
|
-
const href = route.startsWith('#')
|
|
59
|
-
? isHomeRoute
|
|
60
|
-
? route
|
|
61
|
-
: `${localizedHomePath}${route}`
|
|
62
|
-
: route.startsWith('/')
|
|
63
|
-
? buildLocalizedPath(route, locale)
|
|
64
|
-
: route;
|
|
65
|
-
return forceDocumentNavigation || isExternalHref(stripLocalePrefix(normalizePathname(href)))
|
|
66
|
-
? canonicalizeDocumentHref(href)
|
|
67
|
-
: href;
|
|
68
|
-
};
|
|
69
|
-
const handleInternalNavigation = (event, href) => {
|
|
70
|
-
if (event.defaultPrevented ||
|
|
71
|
-
event.button !== 0 ||
|
|
72
|
-
event.metaKey ||
|
|
73
|
-
event.altKey ||
|
|
74
|
-
event.ctrlKey ||
|
|
75
|
-
event.shiftKey) {
|
|
76
|
-
return;
|
|
77
|
-
}
|
|
78
|
-
const isSameRouteNavigation = normalizePathname(href) === normalizePathname(location.pathname);
|
|
79
|
-
if (isSameRouteNavigation) {
|
|
80
|
-
event.preventDefault();
|
|
81
|
-
window.scrollTo(0, 0);
|
|
82
|
-
document
|
|
83
|
-
.querySelector('[data-home-shell="true"]')
|
|
84
|
-
?.scrollTo({ top: 0, left: 0, behavior: 'auto' });
|
|
85
|
-
return;
|
|
86
|
-
}
|
|
87
|
-
if (isExternalHref(stripLocalePrefix(normalizePathname(href)))) {
|
|
88
|
-
return;
|
|
89
|
-
}
|
|
90
|
-
event.preventDefault();
|
|
91
|
-
void navigate(href);
|
|
92
|
-
};
|
|
93
|
-
const handleWaitlistCtaClick = (event, ctaLocation) => {
|
|
94
|
-
setIsMobileMenuOpen(false);
|
|
95
|
-
waitlistCta?.onClick?.(event, ctaLocation);
|
|
96
|
-
};
|
|
97
|
-
return (_jsxs(HeaderFrame, { "data-header-frame": "true", "$shouldAnimatePresentation": shouldAnimatePresentation, "$backgroundColor": presentation.backgroundColor, "$mobileBackgroundColor": mobilePresentation?.backgroundColor, "$contrast": presentation.contrast, children: [_jsxs(HeaderShell, { "$shouldAnimatePresentation": shouldAnimatePresentation, "$textColor": presentation.textColor, "$mobileTextColor": mobilePresentation?.textColor, children: [_jsx(HeaderBrandLink, { href: forceDocumentNavigation ? canonicalizeDocumentHref(homeHref) : homeHref, "aria-label": brandName ? `${brandName} home` : 'Home', onClick: forceDocumentNavigation
|
|
98
|
-
? undefined
|
|
99
|
-
: (event) => handleInternalNavigation(event, homeHref), children: presentation.contrast ? (_jsx(BrandWordmarkMask, { "$url": wordmarkSrc, role: "img", "aria-label": brandName })) : (_jsx(Wordmark, { "$shouldAnimatePresentation": shouldAnimatePresentation, "$variant": presentation.variant, "$mobileVariant": mobilePresentation?.variant, src: wordmarkSrc, alt: brandName })) }), _jsx(Nav, { "$variant": presentation.variant, "$contrast": presentation.contrast, "aria-label": "Global navigation", children: enabledNavItems.map((item) => {
|
|
100
|
-
const label = item.label;
|
|
101
|
-
if (item.children && item.children.length > 0) {
|
|
102
|
-
const dropdownItems = item.children
|
|
103
|
-
.filter((child) => Boolean(child.route) || child.disabled)
|
|
104
|
-
.map((child) => {
|
|
105
|
-
if (child.disabled || !child.route) {
|
|
106
|
-
return {
|
|
107
|
-
key: child.key,
|
|
108
|
-
label: child.label,
|
|
109
|
-
disabled: true,
|
|
110
|
-
badge: child.badge,
|
|
111
|
-
};
|
|
112
|
-
}
|
|
113
|
-
const childHref = resolveHref(child.route);
|
|
114
|
-
return {
|
|
115
|
-
key: child.key,
|
|
116
|
-
label: child.label,
|
|
117
|
-
href: childHref,
|
|
118
|
-
external: child.external,
|
|
119
|
-
onClick: child.external || forceDocumentNavigation
|
|
120
|
-
? undefined
|
|
121
|
-
: (event) => {
|
|
122
|
-
handleInternalNavigation(event, childHref);
|
|
123
|
-
},
|
|
124
|
-
};
|
|
125
|
-
});
|
|
126
|
-
return (_jsx(NavDropdown, { label: label, items: dropdownItems, variant: presentation.variant, contrast: presentation.contrast }, item.label));
|
|
127
|
-
}
|
|
128
|
-
const content_ = (_jsxs(_Fragment, { children: [label, item.badge ? (_jsx(NavBadge, { children: _jsx(NavBadgeLabel, { children: item.badge }) })) : null] }));
|
|
129
|
-
if (item.placeholder || !item.route) {
|
|
130
|
-
return (_jsx("span", { "aria-disabled": "true", "aria-label": `${label} ${item.badge ?? ''}`.trim(), "data-has-badge": item.badge ? 'true' : undefined, children: content_ }, item.label));
|
|
131
|
-
}
|
|
132
|
-
const href = resolveHref(item.route);
|
|
133
|
-
return (_jsx("a", { href: href, "data-has-badge": item.badge ? 'true' : undefined, onClick: item.external
|
|
134
|
-
? undefined
|
|
135
|
-
: forceDocumentNavigation
|
|
136
|
-
? undefined
|
|
137
|
-
: (event) => {
|
|
138
|
-
handleInternalNavigation(event, href);
|
|
139
|
-
}, ...(item.external ? { target: '_blank', rel: 'noreferrer' } : {}), children: content_ }, item.label));
|
|
140
|
-
}) }), _jsx(HeaderMobileToggle, { type: "button", "aria-label": isMobileMenuOpen ? 'Close navigation menu' : 'Open navigation menu', "aria-expanded": isMobileMenuOpen, "aria-controls": "mobile-navigation", onClick: () => setIsMobileMenuOpen((currentValue) => !currentValue), children: _jsx(HeaderMobileToggleIcon, { "$open": isMobileMenuOpen, children: _jsx("span", {}) }) }), waitlistCta?.disabled ? (_jsxs(HeaderAction, { as: "span", "$variant": presentation.variant, "$disabled": true, "aria-disabled": "true", children: [content.joinWaitlist, _jsx(HeaderActionArrow, { "aria-hidden": "true", children: "\u2192" })] })) : waitlistCta ? (_jsxs(HeaderAction, { "$variant": presentation.variant, href: resolveHref(waitlistCta.href), onClick: forceDocumentNavigation
|
|
141
|
-
? undefined
|
|
142
|
-
: (event) => handleWaitlistCtaClick(event, 'nav_desktop'), children: [content.joinWaitlist, _jsx(HeaderActionArrow, { "aria-hidden": "true", children: "\u2192" })] })) : null] }), isMobileMenuOpen ? (_jsx(HeaderMobileOverlay, { onClick: () => setIsMobileMenuOpen(false), children: _jsxs(HeaderMobileSheet, { id: "mobile-navigation", role: "dialog", "aria-modal": "true", "aria-label": "Navigation menu", onClick: (event) => event.stopPropagation(), children: [_jsx(HeaderMobileNav, { "aria-label": "Mobile navigation", children: enabledNavItems.map((item) => {
|
|
143
|
-
const label = item.label;
|
|
144
|
-
if (item.children && item.children.length > 0) {
|
|
145
|
-
const groupItems = item.children
|
|
146
|
-
.filter((child) => Boolean(child.route) || child.disabled)
|
|
147
|
-
.map((child) => {
|
|
148
|
-
if (child.disabled || !child.route) {
|
|
149
|
-
return {
|
|
150
|
-
key: child.key,
|
|
151
|
-
label: child.label,
|
|
152
|
-
disabled: true,
|
|
153
|
-
badge: child.badge,
|
|
154
|
-
};
|
|
155
|
-
}
|
|
156
|
-
const childHref = resolveHref(child.route);
|
|
157
|
-
return {
|
|
158
|
-
key: child.key,
|
|
159
|
-
label: child.label,
|
|
160
|
-
href: childHref,
|
|
161
|
-
external: child.external,
|
|
162
|
-
onClick: child.external || forceDocumentNavigation
|
|
163
|
-
? () => setIsMobileMenuOpen(false)
|
|
164
|
-
: (event) => {
|
|
165
|
-
setIsMobileMenuOpen(false);
|
|
166
|
-
handleInternalNavigation(event, childHref);
|
|
167
|
-
},
|
|
168
|
-
};
|
|
169
|
-
});
|
|
170
|
-
return _jsx(MobileNavGroup, { label: label, items: groupItems }, item.label);
|
|
171
|
-
}
|
|
172
|
-
if (item.placeholder || !item.route) {
|
|
173
|
-
return (_jsxs(HeaderMobileNavDisabledItem, { "aria-disabled": "true", "aria-label": `${label} ${item.badge ?? ''}`.trim(), children: [label, item.badge ? (_jsx(HeaderMobileSoonBadge, { children: item.badge })) : null] }, item.label));
|
|
174
|
-
}
|
|
175
|
-
const href = resolveHref(item.route);
|
|
176
|
-
return (_jsxs(HeaderMobileNavLink, { href: href, onClick: item.external
|
|
177
|
-
? () => setIsMobileMenuOpen(false)
|
|
178
|
-
: forceDocumentNavigation
|
|
179
|
-
? () => setIsMobileMenuOpen(false)
|
|
180
|
-
: (event) => {
|
|
181
|
-
setIsMobileMenuOpen(false);
|
|
182
|
-
handleInternalNavigation(event, href);
|
|
183
|
-
}, ...(item.external ? { target: '_blank', rel: 'noreferrer' } : {}), children: [label, item.badge ? (_jsx(HeaderMobileSoonBadge, { children: item.badge })) : null] }, item.label));
|
|
184
|
-
}) }), _jsx(HeaderMobileLocaleSlot, { children: _jsx(LocaleSelector, { ariaLabel: content.localeSelectorAriaLabel, forceDocumentNavigation: forceDocumentNavigation, inlineMenu: true, menuPlacement: "down", onLocaleSelect: () => setIsMobileMenuOpen(false) }) }), waitlistCta?.disabled ? (_jsxs(HeaderMobileWaitlistLink, { as: "span", "$disabled": true, "aria-disabled": "true", children: [_jsx(HeaderMobileWaitlistLabel, { children: content.waitlistEmailCta }), _jsx(HeaderMobileWaitlistArrow, { "aria-hidden": "true", children: "\u2192" })] })) : waitlistCta ? (_jsxs(HeaderMobileWaitlistLink, { href: resolveHref(waitlistCta.href), onClick: forceDocumentNavigation
|
|
185
|
-
? () => setIsMobileMenuOpen(false)
|
|
186
|
-
: (event) => handleWaitlistCtaClick(event, 'nav_mobile'), children: [_jsx(HeaderMobileWaitlistLabel, { children: content.waitlistEmailCta }), _jsx(HeaderMobileWaitlistArrow, { "aria-hidden": "true", children: "\u2192" })] })) : null] }) })) : null] }));
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { RouterNavigationProvider } from '../navigation/router.js';
|
|
3
|
+
import { HeaderView } from './view.js';
|
|
4
|
+
/**
|
|
5
|
+
* The site header for apps with a react-router `<Router>` above it. Static
|
|
6
|
+
* sites without a router render `HeaderStatic` instead.
|
|
7
|
+
*/
|
|
8
|
+
export function Header(props) {
|
|
9
|
+
return (_jsx(RouterNavigationProvider, { children: _jsx(HeaderView, { ...props }) }));
|
|
187
10
|
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { AppLocale } from '../locale/types.js';
|
|
2
|
+
import type { HeaderProps } from './types.js';
|
|
3
|
+
export interface HeaderStaticProps extends Omit<HeaderProps, 'forceDocumentNavigation'> {
|
|
4
|
+
/** The page's locale, resolved by the host (Astro) rather than a router param. */
|
|
5
|
+
locale: AppLocale;
|
|
6
|
+
/** The page's pathname; drives the home-route check and the mobile menu's close-on-navigate. */
|
|
7
|
+
pathname: string;
|
|
8
|
+
hash?: string | undefined;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* The site header for statically rendered pages with no client router. Every
|
|
12
|
+
* link is a document navigation, so this variant never imports react-router
|
|
13
|
+
* and needs no `<MemoryRouter>` around the island.
|
|
14
|
+
*/
|
|
15
|
+
export declare function HeaderStatic({ locale, pathname, hash, ...props }: HeaderStaticProps): React.JSX.Element;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { StaticNavigationProvider } from '../navigation/static.js';
|
|
3
|
+
import { HeaderView } from './view.js';
|
|
4
|
+
/**
|
|
5
|
+
* The site header for statically rendered pages with no client router. Every
|
|
6
|
+
* link is a document navigation, so this variant never imports react-router
|
|
7
|
+
* and needs no `<MemoryRouter>` around the island.
|
|
8
|
+
*/
|
|
9
|
+
export function HeaderStatic({ locale, pathname, hash, ...props }) {
|
|
10
|
+
return (_jsx(StaticNavigationProvider, { locale: locale, pathname: pathname, hash: hash, children: _jsx(HeaderView, { ...props, forceDocumentNavigation: true }) }));
|
|
11
|
+
}
|
package/dist/Header/styles.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { HeaderPresentationVariant } from './
|
|
1
|
+
import type { HeaderPresentationVariant } from './types.js';
|
|
2
2
|
export declare const HeaderFrame: import("styled-components/dist/types").IStyledComponentBase<"web", import("styled-components/dist/types").Merged<Omit<import("react").DetailedHTMLProps<import("react").HTMLAttributes<HTMLDivElement>, HTMLDivElement>, "style"> & {
|
|
3
3
|
style?: import("react").CSSProperties | import("styled-components/dist/types").CSSPropertiesWithVars | undefined;
|
|
4
4
|
}, {
|
|
@@ -7,6 +7,9 @@ export declare const HeaderFrame: import("styled-components/dist/types").IStyled
|
|
|
7
7
|
$mobileBackgroundColor?: string | undefined;
|
|
8
8
|
$contrast?: boolean | undefined;
|
|
9
9
|
}>> & string;
|
|
10
|
+
export declare const HeaderSkipLink: import("styled-components/dist/types").IStyledComponentBase<"web", Omit<import("react").DetailedHTMLProps<import("react").AnchorHTMLAttributes<HTMLAnchorElement>, HTMLAnchorElement>, "style"> & {
|
|
11
|
+
style?: import("react").CSSProperties | import("styled-components/dist/types").CSSPropertiesWithVars | undefined;
|
|
12
|
+
}> & string;
|
|
10
13
|
export declare const HeaderShell: import("styled-components/dist/types").IStyledComponentBase<"web", import("styled-components/dist/types").Merged<Omit<import("react").DetailedHTMLProps<import("react").HTMLAttributes<HTMLElement>, HTMLElement>, "style"> & {
|
|
11
14
|
style?: import("react").CSSProperties | import("styled-components/dist/types").CSSPropertiesWithVars | undefined;
|
|
12
15
|
}, {
|
package/dist/Header/styles.js
CHANGED
|
@@ -88,6 +88,29 @@ const verticalSwapText = (top, bottom) => `
|
|
|
88
88
|
-webkit-background-clip: text;
|
|
89
89
|
background-clip: text;
|
|
90
90
|
`;
|
|
91
|
+
// Off-canvas until focused, then a solid pill over the header so a keyboard
|
|
92
|
+
// user's first Tab lands on a visible control rather than the wordmark.
|
|
93
|
+
export const HeaderSkipLink = styled.a `
|
|
94
|
+
position: absolute;
|
|
95
|
+
top: ${({ theme }) => theme.sizes.xs};
|
|
96
|
+
left: ${({ theme }) => theme.sizes.xs};
|
|
97
|
+
z-index: 1;
|
|
98
|
+
padding: 10px 16px;
|
|
99
|
+
border-radius: ${({ theme }) => theme.borderRadius.pill};
|
|
100
|
+
background: ${({ theme }) => theme.colors.ink};
|
|
101
|
+
color: ${({ theme }) => theme.colors.light};
|
|
102
|
+
font-size: 0.875rem;
|
|
103
|
+
font-weight: ${({ theme }) => theme.fontWeight.semibold};
|
|
104
|
+
line-height: 1.25;
|
|
105
|
+
text-decoration: none;
|
|
106
|
+
transform: translateY(calc(-100% - ${({ theme }) => theme.sizes.md}));
|
|
107
|
+
|
|
108
|
+
&:focus-visible {
|
|
109
|
+
outline: 2px solid ${({ theme }) => theme.colors.light};
|
|
110
|
+
outline-offset: 2px;
|
|
111
|
+
transform: none;
|
|
112
|
+
}
|
|
113
|
+
`;
|
|
91
114
|
export const HeaderShell = styled.header `
|
|
92
115
|
width: min(
|
|
93
116
|
calc(100% - (2 * ${({ theme }) => theme.layout.pagePaddingInline})),
|
|
@@ -428,7 +451,7 @@ export const NavDropdownOptionBadge = styled.span `
|
|
|
428
451
|
border-radius: ${({ theme }) => theme.borderRadius.pill};
|
|
429
452
|
background: rgb(0 0 0 / 0.08);
|
|
430
453
|
color: currentColor;
|
|
431
|
-
font-size: 0.
|
|
454
|
+
font-size: 0.6875rem;
|
|
432
455
|
font-weight: ${({ theme }) => theme.fontWeight.black};
|
|
433
456
|
letter-spacing: 0.1em;
|
|
434
457
|
line-height: ${({ theme }) => theme.typography.lineHeight.tight};
|
|
@@ -444,8 +467,8 @@ export const NavBadge = styled.span `
|
|
|
444
467
|
padding: 0 8px;
|
|
445
468
|
border-radius: ${({ theme }) => theme.borderRadius.pill};
|
|
446
469
|
background: ${({ theme }) => theme.colors.accentSky};
|
|
447
|
-
color: ${({ theme }) => theme.colors.
|
|
448
|
-
font-size: 0.
|
|
470
|
+
color: ${({ theme }) => theme.colors.ink};
|
|
471
|
+
font-size: 0.6875rem;
|
|
449
472
|
font-weight: ${({ theme }) => theme.fontWeight.black};
|
|
450
473
|
letter-spacing: 0.1em;
|
|
451
474
|
line-height: ${({ theme }) => theme.typography.lineHeight.tight};
|
|
@@ -791,7 +814,7 @@ export const HeaderMobileSoonBadge = styled.span `
|
|
|
791
814
|
background: rgb(255 255 255 / 0.42);
|
|
792
815
|
color: currentColor;
|
|
793
816
|
box-shadow: inset 0 0 0 1px rgb(0 0 0 / 0.08);
|
|
794
|
-
font-size: 0.
|
|
817
|
+
font-size: 0.6875rem;
|
|
795
818
|
font-weight: ${({ theme }) => theme.fontWeight.black};
|
|
796
819
|
letter-spacing: 0.1em;
|
|
797
820
|
line-height: ${({ theme }) => theme.typography.lineHeight.tight};
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
export type HeaderPresentationVariant = 'default' | 'inverse';
|
|
2
|
+
export interface HeaderPresentationState {
|
|
3
|
+
backgroundColor: string;
|
|
4
|
+
contrast?: boolean;
|
|
5
|
+
textColor: string;
|
|
6
|
+
variant: HeaderPresentationVariant;
|
|
7
|
+
}
|
|
8
|
+
export interface HeaderNavChild {
|
|
9
|
+
key: string;
|
|
10
|
+
label: string;
|
|
11
|
+
route?: string;
|
|
12
|
+
external?: boolean;
|
|
13
|
+
/**
|
|
14
|
+
* Renders the child greyed and non-interactive, with `badge` beside it if
|
|
15
|
+
* given — for a destination that is not open yet. A disabled child needs
|
|
16
|
+
* no `route`.
|
|
17
|
+
*/
|
|
18
|
+
disabled?: boolean;
|
|
19
|
+
badge?: string;
|
|
20
|
+
}
|
|
21
|
+
export interface HeaderNavItem {
|
|
22
|
+
label: string;
|
|
23
|
+
route?: string;
|
|
24
|
+
enabled: boolean;
|
|
25
|
+
order: number;
|
|
26
|
+
external?: boolean;
|
|
27
|
+
placeholder?: boolean;
|
|
28
|
+
badge?: string;
|
|
29
|
+
children?: HeaderNavChild[];
|
|
30
|
+
}
|
|
31
|
+
export interface HeaderContent {
|
|
32
|
+
joinWaitlist: string;
|
|
33
|
+
waitlistEmailCta: string;
|
|
34
|
+
localeSelectorAriaLabel: string;
|
|
35
|
+
/**
|
|
36
|
+
* Label of the skip-to-content link rendered as the header's first focusable
|
|
37
|
+
* element. Omit to render no link (existing consumers keep their markup).
|
|
38
|
+
*/
|
|
39
|
+
skipToContent?: string | undefined;
|
|
40
|
+
}
|
|
41
|
+
export interface HeaderWaitlistCta {
|
|
42
|
+
href: string;
|
|
43
|
+
onClick?: (event: React.MouseEvent<HTMLAnchorElement>, ctaLocation: 'nav_desktop' | 'nav_mobile') => void;
|
|
44
|
+
/** Renders the CTA greyed and non-interactive, keeping its place and label. */
|
|
45
|
+
disabled?: boolean;
|
|
46
|
+
}
|
|
47
|
+
export interface HeaderProps {
|
|
48
|
+
/** Overrides the wordmark from `<BrandProvider>`. Optional when one is present. */
|
|
49
|
+
brandWordmarkSrc?: string;
|
|
50
|
+
content: HeaderContent;
|
|
51
|
+
forceDocumentNavigation?: boolean;
|
|
52
|
+
homeHref: string;
|
|
53
|
+
isExternalHref: (href: string) => boolean;
|
|
54
|
+
/** `id` of the page's main landmark the skip link targets. Defaults to `main`. */
|
|
55
|
+
mainContentId?: string | undefined;
|
|
56
|
+
mobilePresentation?: HeaderPresentationState | undefined;
|
|
57
|
+
navItems: HeaderNavItem[];
|
|
58
|
+
presentation: HeaderPresentationState;
|
|
59
|
+
shouldAnimatePresentation: boolean;
|
|
60
|
+
waitlistCta?: HeaderWaitlistCta | undefined;
|
|
61
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import type { HeaderProps } from './types.js';
|
|
2
|
+
export declare function HeaderView({ brandWordmarkSrc, content, forceDocumentNavigation, homeHref, isExternalHref, mainContentId, mobilePresentation, navItems, presentation, shouldAnimatePresentation, waitlistCta, }: HeaderProps): React.JSX.Element;
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
+
import { useEffect, useState } from 'react';
|
|
3
|
+
import { LocaleSelectorView } from '../LocaleSelector/view.js';
|
|
4
|
+
import { buildLocalizedPath, stripLocalePrefix } from '../locale/routes.js';
|
|
5
|
+
import { useNavigationLocation } from '../navigation/context.js';
|
|
6
|
+
import { desktopViewportQuery } from '../tokens.js';
|
|
7
|
+
import { useOptionalBrand } from '../brand/index.js';
|
|
8
|
+
import { normalizePathname } from './helper.js';
|
|
9
|
+
import { NavDropdown } from './NavDropdown.js';
|
|
10
|
+
import { BrandWordmarkMask, HeaderAction, HeaderActionArrow, HeaderBrandLink, HeaderFrame, HeaderMobileNav, HeaderMobileNavDisabledItem, HeaderMobileNavGroup, HeaderMobileNavGroupChevron, HeaderMobileNavGroupPanel, HeaderMobileNavGroupPanelInner, HeaderMobileNavGroupToggle, HeaderMobileNavSubDisabledItem, HeaderMobileNavSubLink, HeaderMobileNavSubList, HeaderMobileNavLink, HeaderMobileOverlay, HeaderMobileLocaleSlot, HeaderMobileSheet, HeaderMobileSoonBadge, HeaderMobileToggle, HeaderMobileToggleIcon, HeaderMobileWaitlistArrow, HeaderMobileWaitlistLabel, HeaderMobileWaitlistLink, HeaderShell, HeaderSkipLink, Nav, NavBadge, NavBadgeLabel, Wordmark, } from './styles.js';
|
|
11
|
+
function MobileNavGroup({ label, items }) {
|
|
12
|
+
const [isExpanded, setIsExpanded] = useState(false);
|
|
13
|
+
const panelId = `mobile-nav-group-${label.toLowerCase().replace(/\s+/gu, '-')}`;
|
|
14
|
+
return (_jsxs(HeaderMobileNavGroup, { children: [_jsxs(HeaderMobileNavGroupToggle, { type: "button", "aria-expanded": isExpanded, "aria-controls": panelId, onClick: () => setIsExpanded((currentValue) => !currentValue), children: [label, _jsx(HeaderMobileNavGroupChevron, { "$open": isExpanded, "aria-hidden": "true" })] }), _jsx(HeaderMobileNavGroupPanel, { "$open": isExpanded, id: panelId, children: _jsx(HeaderMobileNavGroupPanelInner, { "$open": isExpanded, children: _jsx(HeaderMobileNavSubList, { "aria-hidden": !isExpanded, children: items.map((item) => item.disabled ? (_jsxs(HeaderMobileNavSubDisabledItem, { "aria-disabled": "true", "aria-label": `${item.label} ${item.badge ?? ''}`.trim(), children: [item.label, item.badge ? _jsx(HeaderMobileSoonBadge, { children: item.badge }) : null] }, item.key)) : (_jsx(HeaderMobileNavSubLink, { href: item.href, "aria-label": item.ariaLabel, tabIndex: isExpanded ? undefined : -1, onClick: item.onClick, ...(item.external ? { target: '_blank', rel: 'noreferrer' } : {}), children: item.label }, item.key))) }) }) })] }));
|
|
15
|
+
}
|
|
16
|
+
export function HeaderView({ brandWordmarkSrc, content, forceDocumentNavigation = false, homeHref, isExternalHref, mainContentId = 'main', mobilePresentation, navItems, presentation, shouldAnimatePresentation, waitlistCta, }) {
|
|
17
|
+
const brand = useOptionalBrand();
|
|
18
|
+
const wordmarkSrc = brandWordmarkSrc ?? brand?.wordmarkSrc;
|
|
19
|
+
const brandName = brand?.name ?? '';
|
|
20
|
+
// Rendering a header with no wordmark is never what the caller meant, and a
|
|
21
|
+
// broken image is harder to diagnose than a named error.
|
|
22
|
+
if (wordmarkSrc === undefined) {
|
|
23
|
+
throw new Error('<Header> needs a brandWordmarkSrc prop or a <BrandProvider>.');
|
|
24
|
+
}
|
|
25
|
+
const [isMobileMenuOpen, setIsMobileMenuOpen] = useState(false);
|
|
26
|
+
const { locale, pathname, hash, navigate } = useNavigationLocation();
|
|
27
|
+
const localizedHomePath = normalizePathname(homeHref);
|
|
28
|
+
const isHomeRoute = normalizePathname(pathname) === localizedHomePath;
|
|
29
|
+
const enabledNavItems = navItems.filter((item) => item.enabled).sort((a, b) => a.order - b.order);
|
|
30
|
+
const canonicalizeDocumentHref = (href) => {
|
|
31
|
+
const hashStart = href.indexOf('#');
|
|
32
|
+
const path = hashStart === -1 ? href : href.slice(0, hashStart);
|
|
33
|
+
const hash = hashStart === -1 ? '' : href.slice(hashStart);
|
|
34
|
+
if (path === '/' || path.endsWith('/')) {
|
|
35
|
+
return href;
|
|
36
|
+
}
|
|
37
|
+
return `${path}/${hash}`;
|
|
38
|
+
};
|
|
39
|
+
useEffect(() => {
|
|
40
|
+
setIsMobileMenuOpen(false);
|
|
41
|
+
}, [pathname, hash]);
|
|
42
|
+
useEffect(() => {
|
|
43
|
+
const desktopMediaQuery = window.matchMedia(desktopViewportQuery);
|
|
44
|
+
const handleChange = (event) => {
|
|
45
|
+
if (event.matches) {
|
|
46
|
+
setIsMobileMenuOpen(false);
|
|
47
|
+
}
|
|
48
|
+
};
|
|
49
|
+
desktopMediaQuery.addEventListener('change', handleChange);
|
|
50
|
+
return () => {
|
|
51
|
+
desktopMediaQuery.removeEventListener('change', handleChange);
|
|
52
|
+
};
|
|
53
|
+
}, []);
|
|
54
|
+
const resolveHref = (route) => {
|
|
55
|
+
const href = route.startsWith('#')
|
|
56
|
+
? isHomeRoute
|
|
57
|
+
? route
|
|
58
|
+
: `${localizedHomePath}${route}`
|
|
59
|
+
: route.startsWith('/')
|
|
60
|
+
? buildLocalizedPath(route, locale)
|
|
61
|
+
: route;
|
|
62
|
+
return forceDocumentNavigation || isExternalHref(stripLocalePrefix(normalizePathname(href)))
|
|
63
|
+
? canonicalizeDocumentHref(href)
|
|
64
|
+
: href;
|
|
65
|
+
};
|
|
66
|
+
const handleInternalNavigation = (event, href) => {
|
|
67
|
+
if (event.defaultPrevented ||
|
|
68
|
+
event.button !== 0 ||
|
|
69
|
+
event.metaKey ||
|
|
70
|
+
event.altKey ||
|
|
71
|
+
event.ctrlKey ||
|
|
72
|
+
event.shiftKey) {
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
const isSameRouteNavigation = normalizePathname(href) === normalizePathname(pathname);
|
|
76
|
+
if (isSameRouteNavigation) {
|
|
77
|
+
event.preventDefault();
|
|
78
|
+
window.scrollTo(0, 0);
|
|
79
|
+
document
|
|
80
|
+
.querySelector('[data-home-shell="true"]')
|
|
81
|
+
?.scrollTo({ top: 0, left: 0, behavior: 'auto' });
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
if (isExternalHref(stripLocalePrefix(normalizePathname(href)))) {
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
event.preventDefault();
|
|
88
|
+
navigate(href);
|
|
89
|
+
};
|
|
90
|
+
const handleWaitlistCtaClick = (event, ctaLocation) => {
|
|
91
|
+
setIsMobileMenuOpen(false);
|
|
92
|
+
waitlistCta?.onClick?.(event, ctaLocation);
|
|
93
|
+
};
|
|
94
|
+
return (_jsxs(HeaderFrame, { "data-header-frame": "true", "$shouldAnimatePresentation": shouldAnimatePresentation, "$backgroundColor": presentation.backgroundColor, "$mobileBackgroundColor": mobilePresentation?.backgroundColor, "$contrast": presentation.contrast, children: [content.skipToContent !== undefined ? (_jsx(HeaderSkipLink, { href: `#${mainContentId}`, children: content.skipToContent })) : null, _jsxs(HeaderShell, { "$shouldAnimatePresentation": shouldAnimatePresentation, "$textColor": presentation.textColor, "$mobileTextColor": mobilePresentation?.textColor, children: [_jsx(HeaderBrandLink, { href: forceDocumentNavigation ? canonicalizeDocumentHref(homeHref) : homeHref, "aria-label": brandName ? `${brandName} home` : 'Home', onClick: forceDocumentNavigation
|
|
95
|
+
? undefined
|
|
96
|
+
: (event) => handleInternalNavigation(event, homeHref), children: presentation.contrast ? (_jsx(BrandWordmarkMask, { "$url": wordmarkSrc, role: "img", "aria-label": brandName })) : (_jsx(Wordmark, { "$shouldAnimatePresentation": shouldAnimatePresentation, "$variant": presentation.variant, "$mobileVariant": mobilePresentation?.variant, src: wordmarkSrc, alt: brandName })) }), _jsx(Nav, { "$variant": presentation.variant, "$contrast": presentation.contrast, "aria-label": "Global navigation", children: enabledNavItems.map((item) => {
|
|
97
|
+
const label = item.label;
|
|
98
|
+
if (item.children && item.children.length > 0) {
|
|
99
|
+
const dropdownItems = item.children
|
|
100
|
+
.filter((child) => Boolean(child.route) || child.disabled)
|
|
101
|
+
.map((child) => {
|
|
102
|
+
if (child.disabled || !child.route) {
|
|
103
|
+
return {
|
|
104
|
+
key: child.key,
|
|
105
|
+
label: child.label,
|
|
106
|
+
disabled: true,
|
|
107
|
+
badge: child.badge,
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
const childHref = resolveHref(child.route);
|
|
111
|
+
return {
|
|
112
|
+
key: child.key,
|
|
113
|
+
label: child.label,
|
|
114
|
+
href: childHref,
|
|
115
|
+
external: child.external,
|
|
116
|
+
onClick: child.external || forceDocumentNavigation
|
|
117
|
+
? undefined
|
|
118
|
+
: (event) => {
|
|
119
|
+
handleInternalNavigation(event, childHref);
|
|
120
|
+
},
|
|
121
|
+
};
|
|
122
|
+
});
|
|
123
|
+
return (_jsx(NavDropdown, { label: label, items: dropdownItems, variant: presentation.variant, contrast: presentation.contrast }, item.label));
|
|
124
|
+
}
|
|
125
|
+
const content_ = (_jsxs(_Fragment, { children: [label, item.badge ? (_jsx(NavBadge, { children: _jsx(NavBadgeLabel, { children: item.badge }) })) : null] }));
|
|
126
|
+
if (item.placeholder || !item.route) {
|
|
127
|
+
return (_jsx("span", { "aria-disabled": "true", "aria-label": `${label} ${item.badge ?? ''}`.trim(), "data-has-badge": item.badge ? 'true' : undefined, children: content_ }, item.label));
|
|
128
|
+
}
|
|
129
|
+
const href = resolveHref(item.route);
|
|
130
|
+
return (_jsx("a", { href: href, "data-has-badge": item.badge ? 'true' : undefined, onClick: item.external
|
|
131
|
+
? undefined
|
|
132
|
+
: forceDocumentNavigation
|
|
133
|
+
? undefined
|
|
134
|
+
: (event) => {
|
|
135
|
+
handleInternalNavigation(event, href);
|
|
136
|
+
}, ...(item.external ? { target: '_blank', rel: 'noreferrer' } : {}), children: content_ }, item.label));
|
|
137
|
+
}) }), _jsx(HeaderMobileToggle, { type: "button", "aria-label": isMobileMenuOpen ? 'Close navigation menu' : 'Open navigation menu', "aria-expanded": isMobileMenuOpen, "aria-controls": "mobile-navigation", onClick: () => setIsMobileMenuOpen((currentValue) => !currentValue), children: _jsx(HeaderMobileToggleIcon, { "$open": isMobileMenuOpen, children: _jsx("span", {}) }) }), waitlistCta?.disabled ? (_jsxs(HeaderAction, { as: "span", "$variant": presentation.variant, "$disabled": true, "aria-disabled": "true", children: [content.joinWaitlist, _jsx(HeaderActionArrow, { "aria-hidden": "true", children: "\u2192" })] })) : waitlistCta ? (_jsxs(HeaderAction, { "$variant": presentation.variant, href: resolveHref(waitlistCta.href), onClick: forceDocumentNavigation
|
|
138
|
+
? undefined
|
|
139
|
+
: (event) => handleWaitlistCtaClick(event, 'nav_desktop'), children: [content.joinWaitlist, _jsx(HeaderActionArrow, { "aria-hidden": "true", children: "\u2192" })] })) : null] }), isMobileMenuOpen ? (_jsx(HeaderMobileOverlay, { onClick: () => setIsMobileMenuOpen(false), children: _jsxs(HeaderMobileSheet, { id: "mobile-navigation", role: "dialog", "aria-modal": "true", "aria-label": "Navigation menu", onClick: (event) => event.stopPropagation(), children: [_jsx(HeaderMobileNav, { "aria-label": "Mobile navigation", children: enabledNavItems.map((item) => {
|
|
140
|
+
const label = item.label;
|
|
141
|
+
if (item.children && item.children.length > 0) {
|
|
142
|
+
const groupItems = item.children
|
|
143
|
+
.filter((child) => Boolean(child.route) || child.disabled)
|
|
144
|
+
.map((child) => {
|
|
145
|
+
if (child.disabled || !child.route) {
|
|
146
|
+
return {
|
|
147
|
+
key: child.key,
|
|
148
|
+
label: child.label,
|
|
149
|
+
disabled: true,
|
|
150
|
+
badge: child.badge,
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
const childHref = resolveHref(child.route);
|
|
154
|
+
return {
|
|
155
|
+
key: child.key,
|
|
156
|
+
label: child.label,
|
|
157
|
+
href: childHref,
|
|
158
|
+
external: child.external,
|
|
159
|
+
onClick: child.external || forceDocumentNavigation
|
|
160
|
+
? () => setIsMobileMenuOpen(false)
|
|
161
|
+
: (event) => {
|
|
162
|
+
setIsMobileMenuOpen(false);
|
|
163
|
+
handleInternalNavigation(event, childHref);
|
|
164
|
+
},
|
|
165
|
+
};
|
|
166
|
+
});
|
|
167
|
+
return _jsx(MobileNavGroup, { label: label, items: groupItems }, item.label);
|
|
168
|
+
}
|
|
169
|
+
if (item.placeholder || !item.route) {
|
|
170
|
+
return (_jsxs(HeaderMobileNavDisabledItem, { "aria-disabled": "true", "aria-label": `${label} ${item.badge ?? ''}`.trim(), children: [label, item.badge ? (_jsx(HeaderMobileSoonBadge, { children: item.badge })) : null] }, item.label));
|
|
171
|
+
}
|
|
172
|
+
const href = resolveHref(item.route);
|
|
173
|
+
return (_jsxs(HeaderMobileNavLink, { href: href, onClick: item.external
|
|
174
|
+
? () => setIsMobileMenuOpen(false)
|
|
175
|
+
: forceDocumentNavigation
|
|
176
|
+
? () => setIsMobileMenuOpen(false)
|
|
177
|
+
: (event) => {
|
|
178
|
+
setIsMobileMenuOpen(false);
|
|
179
|
+
handleInternalNavigation(event, href);
|
|
180
|
+
}, ...(item.external ? { target: '_blank', rel: 'noreferrer' } : {}), children: [label, item.badge ? (_jsx(HeaderMobileSoonBadge, { children: item.badge })) : null] }, item.label));
|
|
181
|
+
}) }), _jsx(HeaderMobileLocaleSlot, { children: _jsx(LocaleSelectorView, { ariaLabel: content.localeSelectorAriaLabel, forceDocumentNavigation: forceDocumentNavigation, inlineMenu: true, menuPlacement: "down", onLocaleSelect: () => setIsMobileMenuOpen(false) }) }), waitlistCta?.disabled ? (_jsxs(HeaderMobileWaitlistLink, { as: "span", "$disabled": true, "aria-disabled": "true", children: [_jsx(HeaderMobileWaitlistLabel, { children: content.waitlistEmailCta }), _jsx(HeaderMobileWaitlistArrow, { "aria-hidden": "true", children: "\u2192" })] })) : waitlistCta ? (_jsxs(HeaderMobileWaitlistLink, { href: resolveHref(waitlistCta.href), onClick: forceDocumentNavigation
|
|
182
|
+
? () => setIsMobileMenuOpen(false)
|
|
183
|
+
: (event) => handleWaitlistCtaClick(event, 'nav_mobile'), children: [_jsx(HeaderMobileWaitlistLabel, { children: content.waitlistEmailCta }), _jsx(HeaderMobileWaitlistArrow, { "aria-hidden": "true", children: "\u2192" })] })) : null] }) })) : null] }));
|
|
184
|
+
}
|