@cronos-labs/ui 0.7.2 → 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 -35
- package/dist/Footer/index.js +9 -60
- package/dist/Footer/static.d.ts +16 -0
- package/dist/Footer/static.js +11 -0
- package/dist/Footer/styles.d.ts +8 -2
- package/dist/Footer/styles.js +61 -26
- 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 +6 -2
- package/dist/Header/NavDropdown.js +5 -5
- package/dist/Header/index.d.ts +7 -47
- package/dist/Header/index.js +9 -170
- package/dist/Header/static.d.ts +15 -0
- package/dist/Header/static.js +11 -0
- package/dist/Header/styles.d.ts +18 -3
- package/dist/Header/styles.js +95 -3
- 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
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { useEffect, useRef, useState } from 'react';
|
|
3
|
-
import {
|
|
4
|
-
import { NavDropdown as NavDropdownRoot, NavDropdownChevron, NavDropdownLabel, NavDropdownList, NavDropdownMenu, NavDropdownOption, NavDropdownTrigger, } from './styles.js';
|
|
3
|
+
import { useNavigationLocation } from '../navigation/context.js';
|
|
4
|
+
import { NavDropdown as NavDropdownRoot, NavDropdownChevron, NavDropdownDisabledOption, NavDropdownLabel, NavDropdownList, NavDropdownMenu, NavDropdownOption, NavDropdownOptionBadge, NavDropdownTrigger, } from './styles.js';
|
|
5
5
|
const buildMenuId = (label) => `header-nav-dropdown-${label.toLowerCase().replace(/\s+/gu, '-')}`;
|
|
6
6
|
export function NavDropdown({ label, items, variant, contrast, }) {
|
|
7
7
|
const [isOpen, setIsOpen] = useState(false);
|
|
8
8
|
const rootRef = useRef(null);
|
|
9
|
-
const
|
|
9
|
+
const { pathname, hash } = useNavigationLocation();
|
|
10
10
|
const menuId = buildMenuId(label);
|
|
11
11
|
useEffect(() => {
|
|
12
12
|
setIsOpen(false);
|
|
13
|
-
}, [
|
|
13
|
+
}, [pathname, hash]);
|
|
14
14
|
useEffect(() => {
|
|
15
15
|
if (!isOpen)
|
|
16
16
|
return;
|
|
@@ -32,7 +32,7 @@ export function NavDropdown({ label, items, variant, contrast, }) {
|
|
|
32
32
|
document.removeEventListener('keydown', handleKeyDown);
|
|
33
33
|
};
|
|
34
34
|
}, [isOpen]);
|
|
35
|
-
return (_jsxs(NavDropdownRoot, { ref: rootRef, onMouseEnter: () => setIsOpen(true), onMouseLeave: () => setIsOpen(false), children: [_jsxs(NavDropdownTrigger, { type: "button", "$variant": variant, "$contrast": contrast, "aria-haspopup": "menu", "aria-expanded": isOpen, "aria-controls": menuId, onClick: () => setIsOpen((currentValue) => !currentValue), children: [_jsx(NavDropdownLabel, { "$contrast": contrast, children: label }), _jsx(NavDropdownChevron, { "$open": isOpen, "aria-hidden": "true" })] }), _jsx(NavDropdownMenu, { "$open": isOpen, id: menuId, role: "menu", "aria-label": label, children: _jsx(NavDropdownList, { children: items.map((item) => (_jsx(NavDropdownOption, { role: "menuitem", href: item.href, "aria-label": item.ariaLabel, onClick: (event) => {
|
|
35
|
+
return (_jsxs(NavDropdownRoot, { ref: rootRef, onMouseEnter: () => setIsOpen(true), onMouseLeave: () => setIsOpen(false), children: [_jsxs(NavDropdownTrigger, { type: "button", "$variant": variant, "$contrast": contrast, "aria-haspopup": "menu", "aria-expanded": isOpen, "aria-controls": menuId, onClick: () => setIsOpen((currentValue) => !currentValue), children: [_jsx(NavDropdownLabel, { "$contrast": contrast, children: label }), _jsx(NavDropdownChevron, { "$open": isOpen, "aria-hidden": "true" })] }), _jsx(NavDropdownMenu, { "$open": isOpen, id: menuId, role: "menu", "aria-label": label, children: _jsx(NavDropdownList, { children: items.map((item) => item.disabled ? (_jsxs(NavDropdownDisabledOption, { role: "menuitem", "aria-disabled": "true", "aria-label": `${item.label} ${item.badge ?? ''}`.trim(), children: [item.label, item.badge ? _jsx(NavDropdownOptionBadge, { children: item.badge }) : null] }, item.key)) : (_jsx(NavDropdownOption, { role: "menuitem", href: item.href, "aria-label": item.ariaLabel, onClick: (event) => {
|
|
36
36
|
setIsOpen(false);
|
|
37
37
|
item.onClick?.(event);
|
|
38
38
|
}, ...(item.external ? { target: '_blank', rel: 'noreferrer' } : {}), children: item.label }, item.key))) }) })] }));
|
package/dist/Header/index.d.ts
CHANGED
|
@@ -1,47 +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
|
-
export interface HeaderNavItem {
|
|
15
|
-
label: string;
|
|
16
|
-
route?: string;
|
|
17
|
-
enabled: boolean;
|
|
18
|
-
order: number;
|
|
19
|
-
external?: boolean;
|
|
20
|
-
placeholder?: boolean;
|
|
21
|
-
badge?: string;
|
|
22
|
-
children?: HeaderNavChild[];
|
|
23
|
-
}
|
|
24
|
-
export interface HeaderContent {
|
|
25
|
-
joinWaitlist: string;
|
|
26
|
-
waitlistEmailCta: string;
|
|
27
|
-
localeSelectorAriaLabel: string;
|
|
28
|
-
}
|
|
29
|
-
export interface HeaderWaitlistCta {
|
|
30
|
-
href: string;
|
|
31
|
-
onClick?: (event: React.MouseEvent<HTMLAnchorElement>, ctaLocation: 'nav_desktop' | 'nav_mobile') => void;
|
|
32
|
-
}
|
|
33
|
-
interface HeaderProps {
|
|
34
|
-
/** Overrides the wordmark from `<BrandProvider>`. Optional when one is present. */
|
|
35
|
-
brandWordmarkSrc?: string;
|
|
36
|
-
content: HeaderContent;
|
|
37
|
-
forceDocumentNavigation?: boolean;
|
|
38
|
-
homeHref: string;
|
|
39
|
-
isExternalHref: (href: string) => boolean;
|
|
40
|
-
mobilePresentation?: HeaderPresentationState | undefined;
|
|
41
|
-
navItems: HeaderNavItem[];
|
|
42
|
-
presentation: HeaderPresentationState;
|
|
43
|
-
shouldAnimatePresentation: boolean;
|
|
44
|
-
waitlistCta?: HeaderWaitlistCta | undefined;
|
|
45
|
-
}
|
|
46
|
-
export declare function Header({ brandWordmarkSrc, content, forceDocumentNavigation, homeHref, isExternalHref, mobilePresentation, navItems, presentation, shouldAnimatePresentation, waitlistCta, }: HeaderProps): React.JSX.Element;
|
|
47
|
-
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,171 +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, 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) => (_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))
|
|
104
|
-
.map((child) => {
|
|
105
|
-
const childHref = resolveHref(child.route);
|
|
106
|
-
return {
|
|
107
|
-
key: child.key,
|
|
108
|
-
label: child.label,
|
|
109
|
-
href: childHref,
|
|
110
|
-
external: child.external,
|
|
111
|
-
onClick: child.external || forceDocumentNavigation
|
|
112
|
-
? undefined
|
|
113
|
-
: (event) => {
|
|
114
|
-
handleInternalNavigation(event, childHref);
|
|
115
|
-
},
|
|
116
|
-
};
|
|
117
|
-
});
|
|
118
|
-
return (_jsx(NavDropdown, { label: label, items: dropdownItems, variant: presentation.variant, contrast: presentation.contrast }, item.label));
|
|
119
|
-
}
|
|
120
|
-
const content_ = (_jsxs(_Fragment, { children: [label, item.badge ? (_jsx(NavBadge, { children: _jsx(NavBadgeLabel, { children: item.badge }) })) : null] }));
|
|
121
|
-
if (item.placeholder || !item.route) {
|
|
122
|
-
return (_jsx("span", { "aria-disabled": "true", "aria-label": `${label} ${item.badge ?? ''}`.trim(), "data-has-badge": item.badge ? 'true' : undefined, children: content_ }, item.label));
|
|
123
|
-
}
|
|
124
|
-
const href = resolveHref(item.route);
|
|
125
|
-
return (_jsx("a", { href: href, "data-has-badge": item.badge ? 'true' : undefined, onClick: item.external
|
|
126
|
-
? undefined
|
|
127
|
-
: forceDocumentNavigation
|
|
128
|
-
? undefined
|
|
129
|
-
: (event) => {
|
|
130
|
-
handleInternalNavigation(event, href);
|
|
131
|
-
}, ...(item.external ? { target: '_blank', rel: 'noreferrer' } : {}), children: content_ }, item.label));
|
|
132
|
-
}) }), _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 ? (_jsxs(HeaderAction, { "$variant": presentation.variant, href: resolveHref(waitlistCta.href), onClick: forceDocumentNavigation
|
|
133
|
-
? undefined
|
|
134
|
-
: (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) => {
|
|
135
|
-
const label = item.label;
|
|
136
|
-
if (item.children && item.children.length > 0) {
|
|
137
|
-
const groupItems = item.children
|
|
138
|
-
.filter((child) => Boolean(child.route))
|
|
139
|
-
.map((child) => {
|
|
140
|
-
const childHref = resolveHref(child.route);
|
|
141
|
-
return {
|
|
142
|
-
key: child.key,
|
|
143
|
-
label: child.label,
|
|
144
|
-
href: childHref,
|
|
145
|
-
external: child.external,
|
|
146
|
-
onClick: child.external || forceDocumentNavigation
|
|
147
|
-
? () => setIsMobileMenuOpen(false)
|
|
148
|
-
: (event) => {
|
|
149
|
-
setIsMobileMenuOpen(false);
|
|
150
|
-
handleInternalNavigation(event, childHref);
|
|
151
|
-
},
|
|
152
|
-
};
|
|
153
|
-
});
|
|
154
|
-
return _jsx(MobileNavGroup, { label: label, items: groupItems }, item.label);
|
|
155
|
-
}
|
|
156
|
-
if (item.placeholder || !item.route) {
|
|
157
|
-
return (_jsxs(HeaderMobileNavDisabledItem, { "aria-disabled": "true", "aria-label": `${label} ${item.badge ?? ''}`.trim(), children: [label, item.badge ? (_jsx(HeaderMobileSoonBadge, { children: item.badge })) : null] }, item.label));
|
|
158
|
-
}
|
|
159
|
-
const href = resolveHref(item.route);
|
|
160
|
-
return (_jsxs(HeaderMobileNavLink, { href: href, onClick: item.external
|
|
161
|
-
? () => setIsMobileMenuOpen(false)
|
|
162
|
-
: forceDocumentNavigation
|
|
163
|
-
? () => setIsMobileMenuOpen(false)
|
|
164
|
-
: (event) => {
|
|
165
|
-
setIsMobileMenuOpen(false);
|
|
166
|
-
handleInternalNavigation(event, href);
|
|
167
|
-
}, ...(item.external ? { target: '_blank', rel: 'noreferrer' } : {}), children: [label, item.badge ? (_jsx(HeaderMobileSoonBadge, { children: item.badge })) : null] }, item.label));
|
|
168
|
-
}) }), _jsx(HeaderMobileLocaleSlot, { children: _jsx(LocaleSelector, { ariaLabel: content.localeSelectorAriaLabel, forceDocumentNavigation: forceDocumentNavigation, inlineMenu: true, menuPlacement: "down", onLocaleSelect: () => setIsMobileMenuOpen(false) }) }), waitlistCta ? (_jsxs(HeaderMobileWaitlistLink, { href: resolveHref(waitlistCta.href), onClick: forceDocumentNavigation
|
|
169
|
-
? () => setIsMobileMenuOpen(false)
|
|
170
|
-
: (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 }) }));
|
|
171
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
|
}, {
|
|
@@ -77,6 +80,12 @@ export declare const NavDropdownList: import("styled-components/dist/types").ISt
|
|
|
77
80
|
export declare const NavDropdownOption: import("styled-components/dist/types").IStyledComponentBase<"web", Omit<import("react").DetailedHTMLProps<import("react").AnchorHTMLAttributes<HTMLAnchorElement>, HTMLAnchorElement>, "style"> & {
|
|
78
81
|
style?: import("react").CSSProperties | import("styled-components/dist/types").CSSPropertiesWithVars | undefined;
|
|
79
82
|
}> & string;
|
|
83
|
+
export declare const NavDropdownDisabledOption: import("styled-components/dist/types").IStyledComponentBase<"web", Omit<import("react").DetailedHTMLProps<import("react").HTMLAttributes<HTMLSpanElement>, HTMLSpanElement>, "style"> & {
|
|
84
|
+
style?: import("react").CSSProperties | import("styled-components/dist/types").CSSPropertiesWithVars | undefined;
|
|
85
|
+
}> & string;
|
|
86
|
+
export declare const NavDropdownOptionBadge: import("styled-components/dist/types").IStyledComponentBase<"web", Omit<import("react").DetailedHTMLProps<import("react").HTMLAttributes<HTMLSpanElement>, HTMLSpanElement>, "style"> & {
|
|
87
|
+
style?: import("react").CSSProperties | import("styled-components/dist/types").CSSPropertiesWithVars | undefined;
|
|
88
|
+
}> & string;
|
|
80
89
|
export declare const NavBadge: import("styled-components/dist/types").IStyledComponentBase<"web", Omit<import("react").DetailedHTMLProps<import("react").HTMLAttributes<HTMLSpanElement>, HTMLSpanElement>, "style"> & {
|
|
81
90
|
style?: import("react").CSSProperties | import("styled-components/dist/types").CSSPropertiesWithVars | undefined;
|
|
82
91
|
}> & string;
|
|
@@ -87,6 +96,7 @@ export declare const HeaderAction: import("styled-components/dist/types").IStyle
|
|
|
87
96
|
style?: import("react").CSSProperties | import("styled-components/dist/types").CSSPropertiesWithVars | undefined;
|
|
88
97
|
}, {
|
|
89
98
|
$variant: HeaderPresentationVariant;
|
|
99
|
+
$disabled?: boolean | undefined;
|
|
90
100
|
}>> & string;
|
|
91
101
|
export declare const HeaderActionArrow: import("styled-components/dist/types").IStyledComponentBase<"web", Omit<import("react").DetailedHTMLProps<import("react").HTMLAttributes<HTMLSpanElement>, HTMLSpanElement>, "style"> & {
|
|
92
102
|
style?: import("react").CSSProperties | import("styled-components/dist/types").CSSPropertiesWithVars | undefined;
|
|
@@ -138,6 +148,9 @@ export declare const HeaderMobileNavSubList: import("styled-components/dist/type
|
|
|
138
148
|
export declare const HeaderMobileNavSubLink: import("styled-components/dist/types").IStyledComponentBase<"web", Omit<import("react").DetailedHTMLProps<import("react").AnchorHTMLAttributes<HTMLAnchorElement>, HTMLAnchorElement>, "style"> & {
|
|
139
149
|
style?: import("react").CSSProperties | import("styled-components/dist/types").CSSPropertiesWithVars | undefined;
|
|
140
150
|
}> & string;
|
|
151
|
+
export declare const HeaderMobileNavSubDisabledItem: import("styled-components/dist/types").IStyledComponentBase<"web", Omit<import("react").DetailedHTMLProps<import("react").HTMLAttributes<HTMLSpanElement>, HTMLSpanElement>, "style"> & {
|
|
152
|
+
style?: import("react").CSSProperties | import("styled-components/dist/types").CSSPropertiesWithVars | undefined;
|
|
153
|
+
}> & string;
|
|
141
154
|
export declare const HeaderMobileNavDisabledItem: import("styled-components/dist/types").IStyledComponentBase<"web", Omit<import("react").DetailedHTMLProps<import("react").HTMLAttributes<HTMLSpanElement>, HTMLSpanElement>, "style"> & {
|
|
142
155
|
style?: import("react").CSSProperties | import("styled-components/dist/types").CSSPropertiesWithVars | undefined;
|
|
143
156
|
}> & string;
|
|
@@ -147,9 +160,11 @@ export declare const HeaderMobileSoonBadge: import("styled-components/dist/types
|
|
|
147
160
|
export declare const HeaderMobileLocaleSlot: import("styled-components/dist/types").IStyledComponentBase<"web", Omit<import("react").DetailedHTMLProps<import("react").HTMLAttributes<HTMLDivElement>, HTMLDivElement>, "style"> & {
|
|
148
161
|
style?: import("react").CSSProperties | import("styled-components/dist/types").CSSPropertiesWithVars | undefined;
|
|
149
162
|
}> & string;
|
|
150
|
-
export declare const HeaderMobileWaitlistLink: import("styled-components/dist/types").IStyledComponentBase<"web", Omit<import("react").DetailedHTMLProps<import("react").AnchorHTMLAttributes<HTMLAnchorElement>, HTMLAnchorElement>, "style"> & {
|
|
163
|
+
export declare const HeaderMobileWaitlistLink: import("styled-components/dist/types").IStyledComponentBase<"web", import("styled-components/dist/types").Merged<Omit<import("react").DetailedHTMLProps<import("react").AnchorHTMLAttributes<HTMLAnchorElement>, HTMLAnchorElement>, "style"> & {
|
|
151
164
|
style?: import("react").CSSProperties | import("styled-components/dist/types").CSSPropertiesWithVars | undefined;
|
|
152
|
-
}
|
|
165
|
+
}, {
|
|
166
|
+
$disabled?: boolean | undefined;
|
|
167
|
+
}>> & string;
|
|
153
168
|
export declare const HeaderMobileWaitlistLabel: import("styled-components/dist/types").IStyledComponentBase<"web", Omit<import("react").DetailedHTMLProps<import("react").HTMLAttributes<HTMLSpanElement>, HTMLSpanElement>, "style"> & {
|
|
154
169
|
style?: import("react").CSSProperties | import("styled-components/dist/types").CSSPropertiesWithVars | undefined;
|
|
155
170
|
}> & string;
|
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})),
|
|
@@ -398,6 +421,43 @@ export const NavDropdownOption = styled.a `
|
|
|
398
421
|
outline-offset: -3px;
|
|
399
422
|
}
|
|
400
423
|
`;
|
|
424
|
+
/* A greyed dropdown entry: the option's shape without its hover or link
|
|
425
|
+
semantics, the badge in the disabled nav item's muted pill. */
|
|
426
|
+
export const NavDropdownDisabledOption = styled.span `
|
|
427
|
+
display: flex;
|
|
428
|
+
align-items: center;
|
|
429
|
+
gap: 8px;
|
|
430
|
+
width: 100%;
|
|
431
|
+
min-height: 44px;
|
|
432
|
+
padding: 12px;
|
|
433
|
+
border-radius: 8px;
|
|
434
|
+
color: inherit;
|
|
435
|
+
font-size: 1rem;
|
|
436
|
+
font-weight: ${({ theme }) => theme.fontWeight.semibold};
|
|
437
|
+
letter-spacing: -0.02em;
|
|
438
|
+
line-height: ${({ theme }) => theme.typography.lineHeight.tight};
|
|
439
|
+
text-align: left;
|
|
440
|
+
white-space: nowrap;
|
|
441
|
+
opacity: 0.5;
|
|
442
|
+
cursor: default;
|
|
443
|
+
`;
|
|
444
|
+
export const NavDropdownOptionBadge = styled.span `
|
|
445
|
+
display: inline-flex;
|
|
446
|
+
align-items: center;
|
|
447
|
+
justify-content: center;
|
|
448
|
+
min-width: 44px;
|
|
449
|
+
height: 18px;
|
|
450
|
+
padding: 0 8px;
|
|
451
|
+
border-radius: ${({ theme }) => theme.borderRadius.pill};
|
|
452
|
+
background: rgb(0 0 0 / 0.08);
|
|
453
|
+
color: currentColor;
|
|
454
|
+
font-size: 0.6875rem;
|
|
455
|
+
font-weight: ${({ theme }) => theme.fontWeight.black};
|
|
456
|
+
letter-spacing: 0.1em;
|
|
457
|
+
line-height: ${({ theme }) => theme.typography.lineHeight.tight};
|
|
458
|
+
text-transform: uppercase;
|
|
459
|
+
white-space: nowrap;
|
|
460
|
+
`;
|
|
401
461
|
export const NavBadge = styled.span `
|
|
402
462
|
display: inline-flex;
|
|
403
463
|
align-items: center;
|
|
@@ -407,8 +467,8 @@ export const NavBadge = styled.span `
|
|
|
407
467
|
padding: 0 8px;
|
|
408
468
|
border-radius: ${({ theme }) => theme.borderRadius.pill};
|
|
409
469
|
background: ${({ theme }) => theme.colors.accentSky};
|
|
410
|
-
color: ${({ theme }) => theme.colors.
|
|
411
|
-
font-size: 0.
|
|
470
|
+
color: ${({ theme }) => theme.colors.ink};
|
|
471
|
+
font-size: 0.6875rem;
|
|
412
472
|
font-weight: ${({ theme }) => theme.fontWeight.black};
|
|
413
473
|
letter-spacing: 0.1em;
|
|
414
474
|
line-height: ${({ theme }) => theme.typography.lineHeight.tight};
|
|
@@ -458,6 +518,20 @@ export const HeaderAction = styled.a `
|
|
|
458
518
|
outline-offset: 3px;
|
|
459
519
|
}
|
|
460
520
|
|
|
521
|
+
/* Disabled: rendered as a span, so no hover lift and no focus ring;
|
|
522
|
+
dimmed in place so the layout does not move when it goes live. */
|
|
523
|
+
${({ $disabled }) => $disabled
|
|
524
|
+
? `
|
|
525
|
+
opacity: 0.55;
|
|
526
|
+
cursor: default;
|
|
527
|
+
|
|
528
|
+
&:hover {
|
|
529
|
+
opacity: 0.55;
|
|
530
|
+
transform: none;
|
|
531
|
+
}
|
|
532
|
+
`
|
|
533
|
+
: ''}
|
|
534
|
+
|
|
461
535
|
${({ theme }) => theme.mediaQueries.lg} {
|
|
462
536
|
display: inline-flex;
|
|
463
537
|
min-width: ${({ theme }) => theme.layout.headerActionWidth};
|
|
@@ -698,6 +772,22 @@ export const HeaderMobileNavSubLink = styled.a `
|
|
|
698
772
|
outline-offset: -4px;
|
|
699
773
|
}
|
|
700
774
|
`;
|
|
775
|
+
/* A greyed entry inside an expanded mobile group: the sub-link's shape,
|
|
776
|
+
dimmed, with no hover. */
|
|
777
|
+
export const HeaderMobileNavSubDisabledItem = styled.span `
|
|
778
|
+
display: flex;
|
|
779
|
+
align-items: center;
|
|
780
|
+
gap: 6px;
|
|
781
|
+
min-height: 40px;
|
|
782
|
+
padding: 8px 12px;
|
|
783
|
+
color: ${({ theme }) => theme.colors.textDarkSubtle};
|
|
784
|
+
font-size: ${({ theme }) => theme.typography.fontSize.bodySm};
|
|
785
|
+
font-weight: ${({ theme }) => theme.fontWeight.semibold};
|
|
786
|
+
letter-spacing: -0.02em;
|
|
787
|
+
line-height: ${({ theme }) => theme.typography.lineHeight.compact};
|
|
788
|
+
opacity: 0.55;
|
|
789
|
+
cursor: default;
|
|
790
|
+
`;
|
|
701
791
|
export const HeaderMobileNavDisabledItem = styled.span `
|
|
702
792
|
display: inline-flex;
|
|
703
793
|
align-items: center;
|
|
@@ -724,7 +814,7 @@ export const HeaderMobileSoonBadge = styled.span `
|
|
|
724
814
|
background: rgb(255 255 255 / 0.42);
|
|
725
815
|
color: currentColor;
|
|
726
816
|
box-shadow: inset 0 0 0 1px rgb(0 0 0 / 0.08);
|
|
727
|
-
font-size: 0.
|
|
817
|
+
font-size: 0.6875rem;
|
|
728
818
|
font-weight: ${({ theme }) => theme.fontWeight.black};
|
|
729
819
|
letter-spacing: 0.1em;
|
|
730
820
|
line-height: ${({ theme }) => theme.typography.lineHeight.tight};
|
|
@@ -751,6 +841,8 @@ export const HeaderMobileWaitlistLink = styled.a `
|
|
|
751
841
|
color: ${({ theme }) => theme.colors.textDarkSubtle};
|
|
752
842
|
text-decoration: none;
|
|
753
843
|
overflow: hidden;
|
|
844
|
+
opacity: ${({ $disabled }) => ($disabled ? 0.55 : 1)};
|
|
845
|
+
cursor: ${({ $disabled }) => ($disabled ? 'default' : 'pointer')};
|
|
754
846
|
|
|
755
847
|
&:focus-visible {
|
|
756
848
|
outline: 2px solid ${({ theme }) => theme.colors.ink};
|
|
@@ -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;
|