@cronos-labs/ui 0.2.1 → 0.5.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/dist/Button/index.d.ts +32 -0
- package/dist/Button/index.js +5 -0
- package/dist/Button/styles.d.ts +18 -0
- package/dist/Button/styles.js +83 -0
- package/dist/Footer/index.d.ts +2 -1
- package/dist/Footer/index.js +7 -1
- package/dist/FooterWaitlist/helper.d.ts +3 -0
- package/dist/FooterWaitlist/helper.js +7 -0
- package/dist/FooterWaitlist/index.d.ts +24 -0
- package/dist/FooterWaitlist/index.js +86 -0
- package/dist/FooterWaitlist/styles.d.ts +71 -0
- package/dist/FooterWaitlist/styles.js +303 -0
- package/dist/Header/index.d.ts +2 -1
- package/dist/Header/index.js +10 -1
- package/dist/Toast/index.d.ts +21 -0
- package/dist/Toast/index.js +5 -0
- package/dist/Toast/styles.d.ts +27 -0
- package/dist/Toast/styles.js +180 -0
- package/dist/brand/index.d.ts +46 -0
- package/dist/brand/index.js +23 -0
- package/dist/fetch.d.ts +14 -0
- package/dist/fetch.js +44 -0
- package/dist/index.d.ts +13 -0
- package/dist/index.js +10 -0
- package/dist/onetrust.d.ts +11 -0
- package/dist/onetrust.js +17 -0
- package/dist/surfaces.d.ts +35 -0
- package/dist/surfaces.js +45 -0
- package/dist/waitlist/attribution.d.ts +9 -0
- package/dist/waitlist/attribution.js +79 -0
- package/dist/waitlist/autocompleteFillMarkers.d.ts +3 -0
- package/dist/waitlist/autocompleteFillMarkers.js +17 -0
- package/dist/waitlist/types.d.ts +29 -0
- package/dist/waitlist/types.js +1 -0
- package/dist/waitlist/useWaitlistForm.d.ts +43 -0
- package/dist/waitlist/useWaitlistForm.js +115 -0
- package/package.json +1 -1
|
@@ -0,0 +1,303 @@
|
|
|
1
|
+
import styled, { css } from 'styled-components';
|
|
2
|
+
import { compactBodyTypography, metaTypography, sectionTitleTypography } from '../typography.js';
|
|
3
|
+
export const WaitlistSectionShell = styled.section `
|
|
4
|
+
background: ${({ theme }) => theme.colors.brandSky};
|
|
5
|
+
color: ${({ theme }) => theme.colors.ink};
|
|
6
|
+
overflow: clip;
|
|
7
|
+
padding: ${({ $variant }) => ($variant === 'footer' ? '0' : '28px 0 16px')};
|
|
8
|
+
|
|
9
|
+
${({ theme }) => theme.mediaQueries.sm} {
|
|
10
|
+
padding: ${({ $variant }) => ($variant === 'footer' ? '0' : '36px 0 18px')};
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
${({ theme }) => theme.mediaQueries.lg} {
|
|
14
|
+
padding: ${({ $variant }) => ($variant === 'footer' ? '0' : '40px 0 20px')};
|
|
15
|
+
}
|
|
16
|
+
`;
|
|
17
|
+
export const WaitlistContainer = styled.div `
|
|
18
|
+
width: ${({ $variant, theme }) => $variant === 'footer'
|
|
19
|
+
? '100%'
|
|
20
|
+
: `min(calc(100% - (2 * ${theme.layout.pagePaddingInline})), ${theme.layout.landingWideWidth})`};
|
|
21
|
+
margin: 0 auto;
|
|
22
|
+
|
|
23
|
+
${({ theme }) => theme.mediaQueries.lg} {
|
|
24
|
+
width: ${({ $variant, theme: currentTheme }) => $variant === 'footer' ? '100%' : currentTheme.layout.landingWideWidth};
|
|
25
|
+
}
|
|
26
|
+
`;
|
|
27
|
+
export const WaitlistCard = styled.section `
|
|
28
|
+
width: 100%;
|
|
29
|
+
`;
|
|
30
|
+
export const WaitlistCardContent = styled.div `
|
|
31
|
+
display: flex;
|
|
32
|
+
flex-direction: column;
|
|
33
|
+
gap: ${({ $variant }) => ($variant === 'footer' ? '40px' : '18px')};
|
|
34
|
+
min-width: 0;
|
|
35
|
+
max-width: ${({ $variant }) => ($variant === 'footer' ? '100%' : '520px')};
|
|
36
|
+
|
|
37
|
+
${({ theme }) => theme.mediaQueries.lg} {
|
|
38
|
+
max-width: ${({ $variant }) => ($variant === 'footer' ? '100%' : '535px')};
|
|
39
|
+
gap: ${({ $variant }) => ($variant === 'footer' ? '14px' : '16px')};
|
|
40
|
+
}
|
|
41
|
+
`;
|
|
42
|
+
export const WaitlistCardTitle = styled.h2 `
|
|
43
|
+
margin: 0;
|
|
44
|
+
${sectionTitleTypography}
|
|
45
|
+
font-weight: ${({ theme }) => theme.fontWeight.extrabold};
|
|
46
|
+
text-wrap: balance;
|
|
47
|
+
max-width: ${({ $variant }) => ($variant === 'footer' ? '270px' : 'none')};
|
|
48
|
+
|
|
49
|
+
${({ theme }) => theme.mediaQueries.lg} {
|
|
50
|
+
max-width: 402px;
|
|
51
|
+
}
|
|
52
|
+
`;
|
|
53
|
+
export const WaitlistForm = styled.form `
|
|
54
|
+
display: grid;
|
|
55
|
+
gap: ${({ $variant }) => ($variant === 'footer' ? '12px' : '10px')};
|
|
56
|
+
width: 100%;
|
|
57
|
+
max-width: ${({ $variant }) => ($variant === 'footer' ? '100%' : '497px')};
|
|
58
|
+
margin-top: 2px;
|
|
59
|
+
|
|
60
|
+
${({ theme }) => theme.mediaQueries.lg} {
|
|
61
|
+
max-width: ${({ $variant }) => ($variant === 'footer' ? '100%' : '497px')};
|
|
62
|
+
gap: ${({ $variant }) => ($variant === 'footer' ? '8px' : '10px')};
|
|
63
|
+
}
|
|
64
|
+
`;
|
|
65
|
+
export const WaitlistLowerContent = styled.div `
|
|
66
|
+
display: grid;
|
|
67
|
+
gap: ${({ $variant }) => ($variant === 'footer' ? '24px' : '14px')};
|
|
68
|
+
|
|
69
|
+
${({ theme }) => theme.mediaQueries.lg} {
|
|
70
|
+
gap: ${({ $variant }) => ($variant === 'footer' ? '28px' : '14px')};
|
|
71
|
+
}
|
|
72
|
+
`;
|
|
73
|
+
export const WaitlistFieldGroup = styled.div `
|
|
74
|
+
box-sizing: border-box;
|
|
75
|
+
position: relative;
|
|
76
|
+
display: grid;
|
|
77
|
+
grid-template-columns: minmax(0, 1fr);
|
|
78
|
+
gap: ${({ $variant }) => ($variant === 'footer' ? '8px' : '8px')};
|
|
79
|
+
width: 100%;
|
|
80
|
+
padding: ${({ $variant }) => ($variant === 'footer' ? '8px' : '4px')};
|
|
81
|
+
border-radius: ${({ theme }) => theme.borderRadius.pill};
|
|
82
|
+
background: #eefbff;
|
|
83
|
+
overflow: hidden;
|
|
84
|
+
|
|
85
|
+
${({ $variant }) => $variant === 'footer'
|
|
86
|
+
? css `
|
|
87
|
+
grid-template-columns: minmax(0, 1fr) auto;
|
|
88
|
+
align-items: center;
|
|
89
|
+
min-height: 65px;
|
|
90
|
+
`
|
|
91
|
+
: null}
|
|
92
|
+
|
|
93
|
+
${({ theme }) => theme.mediaQueries.sm} {
|
|
94
|
+
grid-template-columns: minmax(0, 1fr) auto;
|
|
95
|
+
align-items: center;
|
|
96
|
+
min-height: 65px;
|
|
97
|
+
}
|
|
98
|
+
`;
|
|
99
|
+
export const WaitlistInputMeasure = styled.span `
|
|
100
|
+
position: absolute;
|
|
101
|
+
top: 0;
|
|
102
|
+
left: 0;
|
|
103
|
+
width: max-content;
|
|
104
|
+
height: 0;
|
|
105
|
+
overflow: hidden;
|
|
106
|
+
visibility: hidden;
|
|
107
|
+
white-space: pre;
|
|
108
|
+
pointer-events: none;
|
|
109
|
+
font-size: ${({ $variant }) => ($variant === 'footer' ? '1.5rem' : '1.125rem')};
|
|
110
|
+
font-weight: ${({ theme }) => theme.fontWeight.medium};
|
|
111
|
+
line-height: 1;
|
|
112
|
+
|
|
113
|
+
${({ theme }) => theme.mediaQueries.lg} {
|
|
114
|
+
font-size: 1.5rem;
|
|
115
|
+
line-height: 0.9;
|
|
116
|
+
}
|
|
117
|
+
`;
|
|
118
|
+
export const WaitlistInput = styled.input `
|
|
119
|
+
min-width: 0;
|
|
120
|
+
border: 0;
|
|
121
|
+
padding: ${({ $variant }) => ($variant === 'footer' ? '0 11px 0 24px' : '0 20px')};
|
|
122
|
+
background: transparent;
|
|
123
|
+
-webkit-tap-highlight-color: transparent;
|
|
124
|
+
caret-color: ${({ theme }) => theme.colors.ink};
|
|
125
|
+
color: ${({ theme }) => theme.colors.heroTextMuted};
|
|
126
|
+
font-size: ${({ $variant, $fontScale }) => `calc(${$variant === 'footer' ? '1.5rem' : '1.125rem'} * ${$fontScale})`};
|
|
127
|
+
font-weight: ${({ theme }) => theme.fontWeight.medium};
|
|
128
|
+
line-height: 1;
|
|
129
|
+
outline: none;
|
|
130
|
+
transition: color ${({ theme }) => theme.motion.duration.fast}
|
|
131
|
+
${({ theme }) => theme.motion.easing.standard};
|
|
132
|
+
|
|
133
|
+
&::placeholder {
|
|
134
|
+
color: ${({ theme }) => theme.colors.heroTextMuted};
|
|
135
|
+
opacity: 1;
|
|
136
|
+
transition: color ${({ theme }) => theme.motion.duration.fast}
|
|
137
|
+
${({ theme }) => theme.motion.easing.standard};
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
&:focus {
|
|
141
|
+
color: ${({ theme }) => theme.colors.ink};
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
&:focus::placeholder {
|
|
145
|
+
color: transparent;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
&:not(:placeholder-shown) {
|
|
149
|
+
color: ${({ theme }) => theme.colors.ink};
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
${({ theme }) => theme.mediaQueries.lg} {
|
|
153
|
+
padding: ${({ $variant }) => $variant === 'footer' ? '0 16px 0 32px' : '0 22px'};
|
|
154
|
+
font-size: calc(1.5rem * ${({ $fontScale }) => $fontScale});
|
|
155
|
+
line-height: 0.9;
|
|
156
|
+
}
|
|
157
|
+
`;
|
|
158
|
+
export const WaitlistSubmitButton = styled.button `
|
|
159
|
+
min-height: 50px;
|
|
160
|
+
border: 0;
|
|
161
|
+
border-radius: ${({ theme }) => theme.borderRadius.pill};
|
|
162
|
+
padding: ${({ $variant }) => ($variant === 'footer' ? '0 20px' : '0 18px')};
|
|
163
|
+
min-width: ${({ $variant }) => ($variant === 'footer' ? '129px' : '0')};
|
|
164
|
+
background: ${({ theme }) => theme.colors.brandSky};
|
|
165
|
+
-webkit-tap-highlight-color: transparent;
|
|
166
|
+
color: ${({ theme }) => theme.colors.ink};
|
|
167
|
+
${compactBodyTypography}
|
|
168
|
+
font-weight: ${({ theme }) => theme.fontWeight.bold};
|
|
169
|
+
white-space: nowrap;
|
|
170
|
+
cursor: pointer;
|
|
171
|
+
user-select: none;
|
|
172
|
+
-webkit-user-select: none;
|
|
173
|
+
transition:
|
|
174
|
+
transform ${({ theme }) => theme.motion.duration.fast}
|
|
175
|
+
${({ theme }) => theme.motion.easing.standard},
|
|
176
|
+
opacity ${({ theme }) => theme.motion.duration.fast}
|
|
177
|
+
${({ theme }) => theme.motion.easing.standard};
|
|
178
|
+
|
|
179
|
+
&:hover:not(:disabled) {
|
|
180
|
+
transform: translateY(-1px);
|
|
181
|
+
opacity: 0.94;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
&:disabled {
|
|
185
|
+
opacity: 0.72;
|
|
186
|
+
cursor: wait;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
&::selection {
|
|
190
|
+
background: transparent;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
${({ theme }) => theme.mediaQueries.sm} {
|
|
194
|
+
min-width: ${({ $variant }) => ($variant === 'footer' ? '159px' : '117px')};
|
|
195
|
+
padding-inline: ${({ $variant }) => ($variant === 'footer' ? '12px' : '22px')};
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
${({ theme }) => theme.mediaQueries.lg} {
|
|
199
|
+
min-width: ${({ $variant }) => ($variant === 'footer' ? '159px' : '116px')};
|
|
200
|
+
}
|
|
201
|
+
`;
|
|
202
|
+
export const WaitlistDisclaimer = styled.p `
|
|
203
|
+
margin: 0;
|
|
204
|
+
color: ${({ theme }) => theme.colors.textMutedStrong};
|
|
205
|
+
${metaTypography}
|
|
206
|
+
font-size: 0.6875rem;
|
|
207
|
+
font-weight: ${({ theme }) => theme.fontWeight.default};
|
|
208
|
+
line-height: 1.2727;
|
|
209
|
+
max-width: 467.665px;
|
|
210
|
+
`;
|
|
211
|
+
export const WaitlistCheckboxRow = styled.label `
|
|
212
|
+
display: inline-flex;
|
|
213
|
+
align-items: center;
|
|
214
|
+
gap: 11px;
|
|
215
|
+
width: fit-content;
|
|
216
|
+
cursor: pointer;
|
|
217
|
+
`;
|
|
218
|
+
export const WaitlistCheckbox = styled.input `
|
|
219
|
+
appearance: none;
|
|
220
|
+
display: inline-flex;
|
|
221
|
+
align-items: center;
|
|
222
|
+
justify-content: center;
|
|
223
|
+
width: 25px;
|
|
224
|
+
height: 25px;
|
|
225
|
+
margin: 0;
|
|
226
|
+
border: 0;
|
|
227
|
+
border-radius: 6px;
|
|
228
|
+
background: ${({ theme }) => theme.colors.footerBadgeSurface};
|
|
229
|
+
cursor: pointer;
|
|
230
|
+
|
|
231
|
+
&::after {
|
|
232
|
+
content: '';
|
|
233
|
+
width: 10px;
|
|
234
|
+
height: 6px;
|
|
235
|
+
border-bottom: 2px solid ${({ theme }) => theme.colors.ink};
|
|
236
|
+
border-left: 2px solid ${({ theme }) => theme.colors.ink};
|
|
237
|
+
transform: rotate(-45deg) scale(0);
|
|
238
|
+
transform-origin: center;
|
|
239
|
+
transition: transform ${({ theme }) => theme.motion.duration.fast}
|
|
240
|
+
${({ theme }) => theme.motion.easing.standard};
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
&:checked::after {
|
|
244
|
+
transform: rotate(-45deg) scale(1);
|
|
245
|
+
}
|
|
246
|
+
`;
|
|
247
|
+
export const WaitlistCheckboxLabel = styled.span `
|
|
248
|
+
${metaTypography}
|
|
249
|
+
font-weight: ${({ theme }) => theme.fontWeight.default};
|
|
250
|
+
`;
|
|
251
|
+
export const WaitlistStatusText = styled.p `
|
|
252
|
+
margin: 0;
|
|
253
|
+
${metaTypography}
|
|
254
|
+
font-weight: ${({ theme }) => theme.fontWeight.medium};
|
|
255
|
+
|
|
256
|
+
${({ $tone, theme }) => $tone === 'success'
|
|
257
|
+
? css `
|
|
258
|
+
color: ${theme.colors.success};
|
|
259
|
+
`
|
|
260
|
+
: $tone === 'error'
|
|
261
|
+
? css `
|
|
262
|
+
color: ${theme.colors.error};
|
|
263
|
+
`
|
|
264
|
+
: css `
|
|
265
|
+
color: ${theme.colors.ink};
|
|
266
|
+
`}
|
|
267
|
+
`;
|
|
268
|
+
export const WaitlistSocialBlock = styled.div `
|
|
269
|
+
display: grid;
|
|
270
|
+
margin-top: ${({ $variant }) => ($variant === 'footer' ? '0' : '14px')};
|
|
271
|
+
|
|
272
|
+
${({ theme }) => theme.mediaQueries.lg} {
|
|
273
|
+
margin-top: ${({ $variant }) => ($variant === 'footer' ? '10px' : '14px')};
|
|
274
|
+
}
|
|
275
|
+
`;
|
|
276
|
+
export const WaitlistSocialLinks = styled.div `
|
|
277
|
+
display: inline-flex;
|
|
278
|
+
align-items: center;
|
|
279
|
+
gap: 24px;
|
|
280
|
+
`;
|
|
281
|
+
export const WaitlistSocialLink = styled.a `
|
|
282
|
+
display: inline-flex;
|
|
283
|
+
align-items: center;
|
|
284
|
+
justify-content: center;
|
|
285
|
+
width: 32px;
|
|
286
|
+
height: 32px;
|
|
287
|
+
color: ${({ theme }) => theme.colors.ink};
|
|
288
|
+
transition:
|
|
289
|
+
transform ${({ theme }) => theme.motion.duration.fast}
|
|
290
|
+
${({ theme }) => theme.motion.easing.standard},
|
|
291
|
+
opacity ${({ theme }) => theme.motion.duration.fast}
|
|
292
|
+
${({ theme }) => theme.motion.easing.standard};
|
|
293
|
+
|
|
294
|
+
&:hover {
|
|
295
|
+
transform: translateY(-1px);
|
|
296
|
+
opacity: 0.72;
|
|
297
|
+
}
|
|
298
|
+
`;
|
|
299
|
+
export const WaitlistSocialIcon = styled.img `
|
|
300
|
+
display: block;
|
|
301
|
+
width: 32px;
|
|
302
|
+
height: 32px;
|
|
303
|
+
`;
|
package/dist/Header/index.d.ts
CHANGED
|
@@ -31,7 +31,8 @@ export interface HeaderWaitlistCta {
|
|
|
31
31
|
onClick?: (event: React.MouseEvent<HTMLAnchorElement>, ctaLocation: 'nav_desktop' | 'nav_mobile') => void;
|
|
32
32
|
}
|
|
33
33
|
interface HeaderProps {
|
|
34
|
-
|
|
34
|
+
/** Overrides the wordmark from `<BrandProvider>`. Optional when one is present. */
|
|
35
|
+
brandWordmarkSrc?: string;
|
|
35
36
|
content: HeaderContent;
|
|
36
37
|
forceDocumentNavigation?: boolean;
|
|
37
38
|
homeHref: string;
|
package/dist/Header/index.js
CHANGED
|
@@ -5,6 +5,7 @@ import { LocaleSelector } from '../LocaleSelector/index.js';
|
|
|
5
5
|
import { buildLocalizedPath, stripLocalePrefix } from '../locale/routes.js';
|
|
6
6
|
import { useCurrentLocale } from '../locale/hooks.js';
|
|
7
7
|
import { desktopViewportQuery } from '../tokens.js';
|
|
8
|
+
import { useOptionalBrand } from '../brand/index.js';
|
|
8
9
|
import { normalizePathname } from './helper.js';
|
|
9
10
|
import { NavDropdown } from './NavDropdown.js';
|
|
10
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';
|
|
@@ -14,6 +15,14 @@ function MobileNavGroup({ label, items }) {
|
|
|
14
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))) }) }) })] }));
|
|
15
16
|
}
|
|
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
|
+
}
|
|
17
26
|
const [isMobileMenuOpen, setIsMobileMenuOpen] = useState(false);
|
|
18
27
|
const locale = useCurrentLocale();
|
|
19
28
|
const location = useLocation();
|
|
@@ -87,7 +96,7 @@ export function Header({ brandWordmarkSrc, content, forceDocumentNavigation = fa
|
|
|
87
96
|
};
|
|
88
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": "Cronos home", onClick: forceDocumentNavigation
|
|
89
98
|
? undefined
|
|
90
|
-
: (event) => handleInternalNavigation(event, homeHref), children: presentation.contrast ? (_jsx(BrandWordmarkMask, { "$url":
|
|
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) => {
|
|
91
100
|
const label = item.label;
|
|
92
101
|
if (item.children && item.children.length > 0) {
|
|
93
102
|
const dropdownItems = item.children
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
export type ToastTone = 'success' | 'error';
|
|
2
|
+
/**
|
|
3
|
+
* Resolved icon URLs. Each app resolves its own assets its bundler's way
|
|
4
|
+
* (Astro's `.src`, Vite's plain import) and passes the strings in, so a site
|
|
5
|
+
* can ship its own iconography without forking the component.
|
|
6
|
+
*/
|
|
7
|
+
export interface ToastIcons {
|
|
8
|
+
/** Shown alongside a `success` message. */
|
|
9
|
+
success: string;
|
|
10
|
+
/** Shown alongside an `error` message. */
|
|
11
|
+
error: string;
|
|
12
|
+
/** Decorative flourish on success only. Omit it for no decoration. */
|
|
13
|
+
stars?: string;
|
|
14
|
+
}
|
|
15
|
+
interface ToastProps {
|
|
16
|
+
message: string;
|
|
17
|
+
tone: ToastTone;
|
|
18
|
+
icons: ToastIcons;
|
|
19
|
+
}
|
|
20
|
+
export declare function Toast({ message, tone, icons }: ToastProps): React.JSX.Element;
|
|
21
|
+
export {};
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { ToastBody, ToastIcon, ToastIconAsset, ToastStarsAsset, ToastStarsCluster, ToastSurface, ToastText, } from './styles.js';
|
|
3
|
+
export function Toast({ message, tone, icons }) {
|
|
4
|
+
return (_jsxs(ToastSurface, { "$tone": tone, "aria-live": "polite", role: "status", children: [_jsxs(ToastBody, { "$tone": tone, children: [_jsx(ToastIcon, { "$tone": tone, "aria-hidden": "true", children: _jsx(ToastIconAsset, { src: tone === 'success' ? icons.success : icons.error, alt: "" }) }), _jsx(ToastText, { children: message })] }), tone === 'success' && icons.stars !== undefined ? (_jsx(ToastStarsCluster, { "aria-hidden": "true", children: _jsx(ToastStarsAsset, { src: icons.stars, alt: "" }) })) : null] }));
|
|
5
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
export declare const ToastSurface: import("styled-components/dist/types").IStyledComponentBase<"web", import("styled-components/dist/types").Merged<Omit<import("react").DetailedHTMLProps<import("react").HTMLAttributes<HTMLDivElement>, HTMLDivElement>, "style"> & {
|
|
2
|
+
style?: import("react").CSSProperties | import("styled-components/dist/types").CSSPropertiesWithVars | undefined;
|
|
3
|
+
}, {
|
|
4
|
+
$tone: "success" | "error";
|
|
5
|
+
}>> & string;
|
|
6
|
+
export declare const ToastBody: import("styled-components/dist/types").IStyledComponentBase<"web", import("styled-components/dist/types").Merged<Omit<import("react").DetailedHTMLProps<import("react").HTMLAttributes<HTMLDivElement>, HTMLDivElement>, "style"> & {
|
|
7
|
+
style?: import("react").CSSProperties | import("styled-components/dist/types").CSSPropertiesWithVars | undefined;
|
|
8
|
+
}, {
|
|
9
|
+
$tone: "success" | "error";
|
|
10
|
+
}>> & string;
|
|
11
|
+
export declare const ToastIcon: import("styled-components/dist/types").IStyledComponentBase<"web", import("styled-components/dist/types").Merged<Omit<import("react").DetailedHTMLProps<import("react").HTMLAttributes<HTMLSpanElement>, HTMLSpanElement>, "style"> & {
|
|
12
|
+
style?: import("react").CSSProperties | import("styled-components/dist/types").CSSPropertiesWithVars | undefined;
|
|
13
|
+
}, {
|
|
14
|
+
$tone: "success" | "error";
|
|
15
|
+
}>> & string;
|
|
16
|
+
export declare const ToastIconAsset: import("styled-components/dist/types").IStyledComponentBase<"web", Omit<import("react").DetailedHTMLProps<import("react").ImgHTMLAttributes<HTMLImageElement>, HTMLImageElement>, "style"> & {
|
|
17
|
+
style?: import("react").CSSProperties | import("styled-components/dist/types").CSSPropertiesWithVars | undefined;
|
|
18
|
+
}> & string;
|
|
19
|
+
export declare const ToastText: import("styled-components/dist/types").IStyledComponentBase<"web", Omit<import("react").DetailedHTMLProps<import("react").HTMLAttributes<HTMLParagraphElement>, HTMLParagraphElement>, "style"> & {
|
|
20
|
+
style?: import("react").CSSProperties | import("styled-components/dist/types").CSSPropertiesWithVars | undefined;
|
|
21
|
+
}> & string;
|
|
22
|
+
export declare const ToastStarsCluster: import("styled-components/dist/types").IStyledComponentBase<"web", Omit<import("react").DetailedHTMLProps<import("react").HTMLAttributes<HTMLSpanElement>, HTMLSpanElement>, "style"> & {
|
|
23
|
+
style?: import("react").CSSProperties | import("styled-components/dist/types").CSSPropertiesWithVars | undefined;
|
|
24
|
+
}> & string;
|
|
25
|
+
export declare const ToastStarsAsset: import("styled-components/dist/types").IStyledComponentBase<"web", Omit<import("react").DetailedHTMLProps<import("react").ImgHTMLAttributes<HTMLImageElement>, HTMLImageElement>, "style"> & {
|
|
26
|
+
style?: import("react").CSSProperties | import("styled-components/dist/types").CSSPropertiesWithVars | undefined;
|
|
27
|
+
}> & string;
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
import styled, { css, keyframes } from 'styled-components';
|
|
2
|
+
const toastSurfaceLife = keyframes `
|
|
3
|
+
0% {
|
|
4
|
+
opacity: 0;
|
|
5
|
+
transform: translate3d(18px, -10px, 0) scale(0.86);
|
|
6
|
+
filter: blur(6px);
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
7% {
|
|
10
|
+
opacity: 1;
|
|
11
|
+
transform: translate3d(-4px, 2px, 0) scale(1.035);
|
|
12
|
+
filter: blur(0);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
12%,
|
|
16
|
+
84% {
|
|
17
|
+
opacity: 1;
|
|
18
|
+
transform: translate3d(0, 0, 0) scale(1);
|
|
19
|
+
filter: blur(0);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
92% {
|
|
23
|
+
opacity: 0;
|
|
24
|
+
transform: translate3d(8px, -4px, 0) scale(0.96);
|
|
25
|
+
filter: blur(3px);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
100% {
|
|
29
|
+
opacity: 0;
|
|
30
|
+
transform: translate3d(8px, -4px, 0) scale(0.96);
|
|
31
|
+
filter: blur(3px);
|
|
32
|
+
}
|
|
33
|
+
`;
|
|
34
|
+
const toastBodyReveal = keyframes `
|
|
35
|
+
0% {
|
|
36
|
+
opacity: 0;
|
|
37
|
+
transform: translate3d(12px, 0, 0);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
45% {
|
|
41
|
+
opacity: 1;
|
|
42
|
+
transform: translate3d(-2px, 0, 0);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
100% {
|
|
46
|
+
opacity: 1;
|
|
47
|
+
transform: translate3d(0, 0, 0);
|
|
48
|
+
}
|
|
49
|
+
`;
|
|
50
|
+
const toastIconPop = keyframes `
|
|
51
|
+
0% {
|
|
52
|
+
opacity: 0;
|
|
53
|
+
transform: rotate(-18deg) scale(0.34);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
42% {
|
|
57
|
+
opacity: 1;
|
|
58
|
+
transform: rotate(8deg) scale(1.18);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
68% {
|
|
62
|
+
opacity: 1;
|
|
63
|
+
transform: rotate(-4deg) scale(0.96);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
100% {
|
|
67
|
+
opacity: 1;
|
|
68
|
+
transform: rotate(0deg) scale(1);
|
|
69
|
+
}
|
|
70
|
+
`;
|
|
71
|
+
const toastStarsTwinkle = keyframes `
|
|
72
|
+
0% {
|
|
73
|
+
opacity: 0;
|
|
74
|
+
transform: translate3d(10px, -8px, 0) rotate(-10deg) scale(0.45);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
48% {
|
|
78
|
+
opacity: 1;
|
|
79
|
+
transform: translate3d(-2px, 1px, 0) rotate(4deg) scale(1.08);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
72% {
|
|
83
|
+
opacity: 0.76;
|
|
84
|
+
transform: translate3d(0, 0, 0) rotate(-2deg) scale(0.94);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
100% {
|
|
88
|
+
opacity: 1;
|
|
89
|
+
transform: translate3d(0, 0, 0) rotate(0deg) scale(1);
|
|
90
|
+
}
|
|
91
|
+
`;
|
|
92
|
+
export const ToastSurface = styled.div `
|
|
93
|
+
position: fixed;
|
|
94
|
+
top: calc(${({ theme }) => theme.sizes.xxxl} + ${({ theme }) => theme.sizes.lg});
|
|
95
|
+
right: ${({ theme }) => theme.sizes.lg};
|
|
96
|
+
z-index: ${({ theme }) => theme.zIndex.overlay};
|
|
97
|
+
width: min(calc(100% - ${({ theme }) => theme.sizes.xl}), 335px);
|
|
98
|
+
min-height: 72px;
|
|
99
|
+
padding: ${({ theme }) => theme.sizes.sm};
|
|
100
|
+
border-radius: 12px;
|
|
101
|
+
backdrop-filter: blur(50px);
|
|
102
|
+
color: ${({ theme }) => theme.colors.light};
|
|
103
|
+
text-align: left;
|
|
104
|
+
pointer-events: none;
|
|
105
|
+
overflow: clip;
|
|
106
|
+
transform-origin: top right;
|
|
107
|
+
will-change: opacity, transform, filter;
|
|
108
|
+
animation: ${toastSurfaceLife} 4000ms ${({ theme }) => theme.motion.easing.emphasized} both;
|
|
109
|
+
|
|
110
|
+
${({ theme }) => theme.mediaQueries.lg} {
|
|
111
|
+
top: calc(${({ theme }) => theme.sizes['4xl']} + ${({ theme }) => theme.sizes.lg});
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
${({ $tone }) => $tone === 'success'
|
|
115
|
+
? css `
|
|
116
|
+
background: rgb(114 114 114 / 0.4);
|
|
117
|
+
box-shadow: none;
|
|
118
|
+
`
|
|
119
|
+
: css `
|
|
120
|
+
background: rgb(114 114 114 / 0.4);
|
|
121
|
+
box-shadow: none;
|
|
122
|
+
`}
|
|
123
|
+
`;
|
|
124
|
+
export const ToastBody = styled.div `
|
|
125
|
+
display: flex;
|
|
126
|
+
align-items: flex-start;
|
|
127
|
+
gap: ${({ theme }) => theme.sizes.xxs};
|
|
128
|
+
min-width: 0;
|
|
129
|
+
padding-right: ${({ $tone, theme }) => ($tone === 'success' ? theme.sizes.lg : theme.sizes.zero)};
|
|
130
|
+
animation: ${toastBodyReveal} 420ms 90ms ${({ theme }) => theme.motion.easing.emphasized} both;
|
|
131
|
+
`;
|
|
132
|
+
export const ToastIcon = styled.span `
|
|
133
|
+
position: relative;
|
|
134
|
+
flex: 0 0 auto;
|
|
135
|
+
width: 24px;
|
|
136
|
+
height: 24px;
|
|
137
|
+
margin-top: 1px;
|
|
138
|
+
transform-origin: center;
|
|
139
|
+
will-change: opacity, transform;
|
|
140
|
+
animation: ${toastIconPop} 520ms 120ms ${({ theme }) => theme.motion.easing.emphasized} both;
|
|
141
|
+
|
|
142
|
+
${({ $tone, theme }) => $tone === 'success'
|
|
143
|
+
? css `
|
|
144
|
+
box-shadow: ${theme.shadow.positiveGlow};
|
|
145
|
+
`
|
|
146
|
+
: css `
|
|
147
|
+
box-shadow: none;
|
|
148
|
+
`}
|
|
149
|
+
`;
|
|
150
|
+
export const ToastIconAsset = styled.img `
|
|
151
|
+
display: block;
|
|
152
|
+
width: 24px;
|
|
153
|
+
height: 24px;
|
|
154
|
+
object-fit: contain;
|
|
155
|
+
`;
|
|
156
|
+
export const ToastText = styled.p `
|
|
157
|
+
margin: 0;
|
|
158
|
+
min-width: 0;
|
|
159
|
+
color: ${({ theme }) => theme.colors.light};
|
|
160
|
+
font-size: ${({ theme }) => theme.typography.fontSize.body};
|
|
161
|
+
font-weight: ${({ theme }) => theme.fontWeight.bold};
|
|
162
|
+
line-height: 20px;
|
|
163
|
+
`;
|
|
164
|
+
export const ToastStarsCluster = styled.span `
|
|
165
|
+
position: absolute;
|
|
166
|
+
top: 7px;
|
|
167
|
+
right: 3.16px;
|
|
168
|
+
width: 31.844px;
|
|
169
|
+
height: 36.147px;
|
|
170
|
+
pointer-events: none;
|
|
171
|
+
transform-origin: 58% 42%;
|
|
172
|
+
will-change: opacity, transform;
|
|
173
|
+
animation: ${toastStarsTwinkle} 620ms 180ms ${({ theme }) => theme.motion.easing.emphasized} both;
|
|
174
|
+
`;
|
|
175
|
+
export const ToastStarsAsset = styled.img `
|
|
176
|
+
display: block;
|
|
177
|
+
width: 100%;
|
|
178
|
+
height: 100%;
|
|
179
|
+
object-fit: contain;
|
|
180
|
+
`;
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { type ReactNode } from 'react';
|
|
2
|
+
/**
|
|
3
|
+
* A social destination shown in the footer.
|
|
4
|
+
*
|
|
5
|
+
* `iconSrc` is a resolved URL: each site resolves its own asset its bundler's
|
|
6
|
+
* way (Astro's `.src`, Vite's plain import) and passes the string in. This
|
|
7
|
+
* package imports no assets of its own, so no site inherits another's artwork.
|
|
8
|
+
*/
|
|
9
|
+
export interface BrandSocialLink {
|
|
10
|
+
/** Stable identifier, used as the React key and for analytics. */
|
|
11
|
+
key: string;
|
|
12
|
+
/** Accessible name, e.g. "Instagram". Localize before passing it in. */
|
|
13
|
+
label: string;
|
|
14
|
+
href: string;
|
|
15
|
+
iconSrc: string;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Everything visually identifying one site, supplied once at the layout root.
|
|
19
|
+
*
|
|
20
|
+
* Sites are expected to differ here — that is the point. Two properties can
|
|
21
|
+
* share every component in this package while shipping entirely different
|
|
22
|
+
* wordmarks and social sets.
|
|
23
|
+
*/
|
|
24
|
+
export interface BrandAssets {
|
|
25
|
+
/** The site's name, used as the wordmark's accessible label. */
|
|
26
|
+
name: string;
|
|
27
|
+
/** Primary wordmark, used by Header and Footer. */
|
|
28
|
+
wordmarkSrc: string;
|
|
29
|
+
/** Wordmark for inverted surfaces. Falls back to `wordmarkSrc` when absent. */
|
|
30
|
+
wordmarkInverseSrc?: string;
|
|
31
|
+
/** Rendered in the order given. An empty array renders no social row. */
|
|
32
|
+
socialLinks: BrandSocialLink[];
|
|
33
|
+
}
|
|
34
|
+
export declare function BrandProvider({ brand, children, }: {
|
|
35
|
+
brand: BrandAssets;
|
|
36
|
+
children: ReactNode;
|
|
37
|
+
}): React.JSX.Element;
|
|
38
|
+
/**
|
|
39
|
+
* Reads the site's brand assets.
|
|
40
|
+
*
|
|
41
|
+
* Throws when no provider is present rather than returning placeholder
|
|
42
|
+
* artwork — shipping the wrong wordmark is worse than failing loudly.
|
|
43
|
+
*/
|
|
44
|
+
export declare function useBrand(): BrandAssets;
|
|
45
|
+
/** Reads brand assets where a component must also work without a provider. */
|
|
46
|
+
export declare function useOptionalBrand(): BrandAssets | null;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { createContext, useContext } from 'react';
|
|
3
|
+
const BrandContext = createContext(null);
|
|
4
|
+
export function BrandProvider({ brand, children, }) {
|
|
5
|
+
return _jsx(BrandContext.Provider, { value: brand, children: children });
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* Reads the site's brand assets.
|
|
9
|
+
*
|
|
10
|
+
* Throws when no provider is present rather than returning placeholder
|
|
11
|
+
* artwork — shipping the wrong wordmark is worse than failing loudly.
|
|
12
|
+
*/
|
|
13
|
+
export function useBrand() {
|
|
14
|
+
const brand = useContext(BrandContext);
|
|
15
|
+
if (brand === null) {
|
|
16
|
+
throw new Error('useBrand must be used inside a <BrandProvider>.');
|
|
17
|
+
}
|
|
18
|
+
return brand;
|
|
19
|
+
}
|
|
20
|
+
/** Reads brand assets where a component must also work without a provider. */
|
|
21
|
+
export function useOptionalBrand() {
|
|
22
|
+
return useContext(BrandContext);
|
|
23
|
+
}
|
package/dist/fetch.d.ts
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export declare class HttpError extends Error {
|
|
2
|
+
readonly status: number;
|
|
3
|
+
constructor(status: number, message: string);
|
|
4
|
+
}
|
|
5
|
+
/**
|
|
6
|
+
* Performs a JSON request with timeout support.
|
|
7
|
+
* @param input Fetch URL or Request object.
|
|
8
|
+
* @param init Optional fetch init.
|
|
9
|
+
* @param timeoutMs Timeout in milliseconds.
|
|
10
|
+
* @returns Parsed JSON payload.
|
|
11
|
+
* @throws {DOMException} When request is aborted by timeout.
|
|
12
|
+
* @throws {HttpError} When response status is not 2xx.
|
|
13
|
+
*/
|
|
14
|
+
export declare const fetchJson: <T>(input: RequestInfo | URL, init?: RequestInit, timeoutMs?: number) => Promise<T>;
|