@cronos-labs/ui 0.5.1 → 0.6.0

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.
@@ -1,3 +1,10 @@
1
1
  import type { NavigationContent } from '../navigationContent.js';
2
- import type { WaitlistFormMessages } from '../waitlist/types.js';
3
- export declare const resolveWaitlistFormContent: (navContent: NavigationContent) => WaitlistFormMessages;
2
+ import type { FooterWaitlistContent } from './types.js';
3
+ /**
4
+ * Falls back to the bundled Cronos copy for a locale.
5
+ *
6
+ * The dictionary shipped with this package is a convenience for the Cronos
7
+ * properties, not a definition of what the component says — a differently
8
+ * branded site passes its own `content` instead and never reads this.
9
+ */
10
+ export declare const resolveFooterWaitlistContent: (navContent: NavigationContent) => FooterWaitlistContent;
@@ -1,7 +1,22 @@
1
- export const resolveWaitlistFormContent = (navContent) => ({
2
- successMessage: navContent.footer.waitlistForm.successMessage,
3
- emptyEmailMessage: navContent.footer.waitlistForm.emptyEmailMessage,
4
- invalidEmailMessage: navContent.footer.waitlistForm.invalidEmailMessage,
5
- errorMessage: navContent.footer.waitlistForm.errorMessage,
6
- submittingLabel: navContent.footer.waitlistForm.submittingLabel,
1
+ /**
2
+ * Falls back to the bundled Cronos copy for a locale.
3
+ *
4
+ * The dictionary shipped with this package is a convenience for the Cronos
5
+ * properties, not a definition of what the component says — a differently
6
+ * branded site passes its own `content` instead and never reads this.
7
+ */
8
+ export const resolveFooterWaitlistContent = (navContent) => ({
9
+ ctaTitle: navContent.footer.ctaTitle,
10
+ emailPlaceholder: navContent.footer.emailPlaceholder,
11
+ submitLabel: navContent.footer.submitLabel,
12
+ consentLabel: navContent.footer.consentLabel,
13
+ consentNote: navContent.footer.consentNote,
14
+ socialHeading: navContent.footer.socialHeading,
15
+ messages: {
16
+ successMessage: navContent.footer.waitlistForm.successMessage,
17
+ emptyEmailMessage: navContent.footer.waitlistForm.emptyEmailMessage,
18
+ invalidEmailMessage: navContent.footer.waitlistForm.invalidEmailMessage,
19
+ errorMessage: navContent.footer.waitlistForm.errorMessage,
20
+ submittingLabel: navContent.footer.waitlistForm.submittingLabel,
21
+ },
7
22
  });
@@ -1,5 +1,6 @@
1
1
  import { type ReactNode } from 'react';
2
2
  import type { WaitlistRequest, WaitlistSource } from '../waitlist/types.js';
3
+ import type { FooterWaitlistContent } from './types.js';
3
4
  import { type FooterWaitlistVariant } from './styles.js';
4
5
  export interface FooterWaitlistProps {
5
6
  /** Identifies which form on the site was submitted. */
@@ -14,6 +15,14 @@ export interface FooterWaitlistProps {
14
15
  onWaitlistError?: ((message: string) => void) | undefined;
15
16
  /** Fires when submit is pressed, before validation. For analytics. */
16
17
  onSubmitClick?: (() => void) | undefined;
18
+ /**
19
+ * Every string this renders, already localized.
20
+ *
21
+ * Omit it and the component falls back to the Cronos copy bundled with this
22
+ * package for the active locale. Any site that is not Cronos should pass its
23
+ * own — the bundled dictionary is a convenience, not the component's voice.
24
+ */
25
+ content?: FooterWaitlistContent | undefined;
17
26
  }
18
27
  /**
19
28
  * Email capture card with the site's social row beneath it.
@@ -21,4 +30,4 @@ export interface FooterWaitlistProps {
21
30
  * Wordmark and social links come from `<BrandProvider>`, so two sites render
22
31
  * this identically while showing entirely different artwork and destinations.
23
32
  */
24
- export declare function FooterWaitlist({ source, submit, attributionKey, variant, legalMeta, onWaitlistSuccess, onWaitlistError, onSubmitClick, }: FooterWaitlistProps): React.JSX.Element;
33
+ export declare function FooterWaitlist({ source, submit, attributionKey, variant, legalMeta, onWaitlistSuccess, onWaitlistError, onSubmitClick, content, }: FooterWaitlistProps): React.JSX.Element;
@@ -5,7 +5,7 @@ import { navigationContent } from '../navigationContent.js';
5
5
  import { useCurrentLocale } from '../locale/hooks.js';
6
6
  import { resetAutocompleteFillMarkersAfterInput } from '../waitlist/autocompleteFillMarkers.js';
7
7
  import { useWaitlistForm } from '../waitlist/useWaitlistForm.js';
8
- import { resolveWaitlistFormContent } from './helper.js';
8
+ import { resolveFooterWaitlistContent } from './helper.js';
9
9
  import { WaitlistCard, WaitlistCardContent, WaitlistCardTitle, WaitlistCheckbox, WaitlistCheckboxLabel, WaitlistCheckboxRow, WaitlistContainer, WaitlistDisclaimer, WaitlistFieldGroup, WaitlistForm, WaitlistInput, WaitlistInputMeasure, WaitlistLowerContent, WaitlistSectionShell, WaitlistSocialBlock, WaitlistSocialIcon, WaitlistSocialLink, WaitlistSocialLinks, WaitlistStatusText, WaitlistSubmitButton, } from './styles.js';
10
10
  // Below this the address is unreadable; the field scrolls instead of shrinking further.
11
11
  const minWaitlistInputScale = 0.5;
@@ -15,18 +15,17 @@ const minWaitlistInputScale = 0.5;
15
15
  * Wordmark and social links come from `<BrandProvider>`, so two sites render
16
16
  * this identically while showing entirely different artwork and destinations.
17
17
  */
18
- export function FooterWaitlist({ source, submit, attributionKey, variant = 'footer', legalMeta, onWaitlistSuccess, onWaitlistError, onSubmitClick, }) {
18
+ export function FooterWaitlist({ source, submit, attributionKey, variant = 'footer', legalMeta, onWaitlistSuccess, onWaitlistError, onSubmitClick, content, }) {
19
19
  const locale = useCurrentLocale();
20
20
  const brand = useBrand();
21
- const navContent = navigationContent[locale];
22
- const waitlistContent = resolveWaitlistFormContent(navContent);
21
+ const copy = content ?? resolveFooterWaitlistContent(navigationContent[locale]);
23
22
  const inputRef = useRef(null);
24
23
  const inputMeasureRef = useRef(null);
25
24
  const [waitlistInputScale, setWaitlistInputScale] = useState(1);
26
25
  const waitlistMessages = {
27
- ...waitlistContent,
28
- emailPlaceholder: navContent.footer.emailPlaceholder,
29
- submitLabel: navContent.footer.submitLabel,
26
+ ...copy.messages,
27
+ emailPlaceholder: copy.emailPlaceholder,
28
+ submitLabel: copy.submitLabel,
30
29
  };
31
30
  const waitlistOptions = {
32
31
  source,
@@ -80,7 +79,7 @@ export function FooterWaitlist({ source, submit, attributionKey, variant = 'foot
80
79
  window.removeEventListener('resize', updateWaitlistInputScale);
81
80
  };
82
81
  }, [updateWaitlistInputScale]);
83
- return (_jsx(WaitlistSectionShell, { "$variant": variant, children: _jsx(WaitlistContainer, { "$variant": variant, children: _jsx(WaitlistCard, { children: _jsxs(WaitlistCardContent, { "$variant": variant, children: [_jsx(WaitlistCardTitle, { "$variant": variant, children: navContent.footer.ctaTitle }), _jsxs(WaitlistForm, { "$variant": variant, noValidate: true, onSubmit: (event) => {
82
+ return (_jsx(WaitlistSectionShell, { "$variant": variant, children: _jsx(WaitlistContainer, { "$variant": variant, children: _jsx(WaitlistCard, { children: _jsxs(WaitlistCardContent, { "$variant": variant, children: [_jsx(WaitlistCardTitle, { "$variant": variant, children: copy.ctaTitle }), _jsxs(WaitlistForm, { "$variant": variant, noValidate: true, onSubmit: (event) => {
84
83
  void onSubmit(event);
85
- }, children: [_jsxs(WaitlistFieldGroup, { "$variant": variant, children: [_jsx(WaitlistInputMeasure, { ref: inputMeasureRef, "$variant": variant, "aria-hidden": "true", children: email }), _jsx(WaitlistInput, { ref: setWaitlistInputRef, "$variant": variant, "$fontScale": waitlistInputScale, "aria-describedby": describedBy, "aria-invalid": emailError ? true : undefined, "aria-label": waitlistMessages.emailPlaceholder, autoComplete: "email", placeholder: waitlistMessages.emailPlaceholder, type: "email", value: email, onChange: handleWaitlistEmailChange, onFocus: (event) => resetAutocompleteFillMarkersAfterInput(event.currentTarget) }), _jsx(WaitlistSubmitButton, { "$variant": variant, type: "submit", disabled: isSubmitting, onClick: onSubmitClick, children: isSubmitting ? waitlistMessages.submittingLabel : waitlistMessages.submitLabel })] }), statusMessage ? (_jsx(WaitlistStatusText, { "$tone": statusTone, id: `${variant}-waitlist-status`, "aria-live": "polite", role: "status", children: statusMessage })) : null, _jsx(WaitlistDisclaimer, { children: navContent.footer.consentNote })] }), _jsxs(WaitlistLowerContent, { "$variant": variant, children: [_jsxs(WaitlistCheckboxRow, { htmlFor: `${variant}-waitlist-marketing`, children: [_jsx(WaitlistCheckbox, { id: `${variant}-waitlist-marketing`, checked: consentMarketing, type: "checkbox", onChange: (event) => setConsentMarketing(event.target.checked) }), _jsx(WaitlistCheckboxLabel, { children: navContent.footer.consentLabel })] }), brand.socialLinks.length > 0 ? (_jsx(WaitlistSocialBlock, { "$variant": variant, "aria-label": navContent.footer.socialHeading, children: _jsx(WaitlistSocialLinks, { children: brand.socialLinks.map((socialLink) => (_jsx(WaitlistSocialLink, { href: socialLink.href, target: "_blank", rel: "noreferrer", "aria-label": socialLink.label, children: _jsx(WaitlistSocialIcon, { src: socialLink.iconSrc, alt: "", "aria-hidden": "true", loading: "lazy" }) }, socialLink.key))) }) })) : null, legalMeta] })] }) }) }) }));
84
+ }, children: [_jsxs(WaitlistFieldGroup, { "$variant": variant, children: [_jsx(WaitlistInputMeasure, { ref: inputMeasureRef, "$variant": variant, "aria-hidden": "true", children: email }), _jsx(WaitlistInput, { ref: setWaitlistInputRef, "$variant": variant, "$fontScale": waitlistInputScale, "aria-describedby": describedBy, "aria-invalid": emailError ? true : undefined, "aria-label": waitlistMessages.emailPlaceholder, autoComplete: "email", placeholder: waitlistMessages.emailPlaceholder, type: "email", value: email, onChange: handleWaitlistEmailChange, onFocus: (event) => resetAutocompleteFillMarkersAfterInput(event.currentTarget) }), _jsx(WaitlistSubmitButton, { "$variant": variant, type: "submit", disabled: isSubmitting, onClick: onSubmitClick, children: isSubmitting ? waitlistMessages.submittingLabel : waitlistMessages.submitLabel })] }), statusMessage ? (_jsx(WaitlistStatusText, { "$tone": statusTone, id: `${variant}-waitlist-status`, "aria-live": "polite", role: "status", children: statusMessage })) : null, _jsx(WaitlistDisclaimer, { children: copy.consentNote })] }), _jsxs(WaitlistLowerContent, { "$variant": variant, children: [_jsxs(WaitlistCheckboxRow, { htmlFor: `${variant}-waitlist-marketing`, children: [_jsx(WaitlistCheckbox, { id: `${variant}-waitlist-marketing`, checked: consentMarketing, type: "checkbox", onChange: (event) => setConsentMarketing(event.target.checked) }), _jsx(WaitlistCheckboxLabel, { children: copy.consentLabel })] }), brand.socialLinks.length > 0 ? (_jsx(WaitlistSocialBlock, { "$variant": variant, "aria-label": copy.socialHeading, children: _jsx(WaitlistSocialLinks, { children: brand.socialLinks.map((socialLink) => (_jsx(WaitlistSocialLink, { href: socialLink.href, target: "_blank", rel: "noreferrer", "aria-label": socialLink.label, children: _jsx(WaitlistSocialIcon, { src: socialLink.iconSrc, alt: "", "aria-hidden": "true", loading: "lazy" }) }, socialLink.key))) }) })) : null, legalMeta] })] }) }) }) }));
86
85
  }
@@ -0,0 +1,21 @@
1
+ import type { WaitlistFormMessages } from '../waitlist/types.js';
2
+ /**
3
+ * Every string `FooterWaitlist` renders.
4
+ *
5
+ * Supplied per site so the component carries no brand's wording. Localize
6
+ * before passing it in — this package does not translate.
7
+ */
8
+ export interface FooterWaitlistContent {
9
+ /** Heading above the form. */
10
+ ctaTitle: string;
11
+ emailPlaceholder: string;
12
+ submitLabel: string;
13
+ /** Label beside the marketing-consent checkbox. */
14
+ consentLabel: string;
15
+ /** Fine print under the form, e.g. a privacy-notice acknowledgement. */
16
+ consentNote: string;
17
+ /** Accessible name for the social-links group. */
18
+ socialHeading: string;
19
+ /** Validation and submission feedback. */
20
+ messages: WaitlistFormMessages;
21
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -94,7 +94,7 @@ export function Header({ brandWordmarkSrc, content, forceDocumentNavigation = fa
94
94
  setIsMobileMenuOpen(false);
95
95
  waitlistCta?.onClick?.(event, ctaLocation);
96
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": "Cronos home", onClick: forceDocumentNavigation
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
98
  ? undefined
99
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
100
  const label = item.label;
package/dist/index.d.ts CHANGED
@@ -25,6 +25,8 @@ export { isOneTrustEventTarget, openCookiePreferences } from './onetrust.js';
25
25
  export { BrandProvider, useBrand, useOptionalBrand } from './brand/index.js';
26
26
  export type { BrandAssets, BrandSocialLink } from './brand/index.js';
27
27
  export { FooterWaitlist, type FooterWaitlistProps } from './FooterWaitlist/index.js';
28
+ export type { FooterWaitlistContent } from './FooterWaitlist/types.js';
29
+ export { resolveFooterWaitlistContent } from './FooterWaitlist/helper.js';
28
30
  export { useWaitlistForm } from './waitlist/useWaitlistForm.js';
29
31
  export type { UseWaitlistFormOptions, WaitlistStatusTone } from './waitlist/useWaitlistForm.js';
30
32
  export type { WaitlistRequest, WaitlistResponse, WaitlistSource, WaitlistFormMessages, } from './waitlist/types.js';
package/dist/index.js CHANGED
@@ -21,6 +21,7 @@ export { fetchJson, HttpError } from './fetch.js';
21
21
  export { isOneTrustEventTarget, openCookiePreferences } from './onetrust.js';
22
22
  export { BrandProvider, useBrand, useOptionalBrand } from './brand/index.js';
23
23
  export { FooterWaitlist } from './FooterWaitlist/index.js';
24
+ export { resolveFooterWaitlistContent } from './FooterWaitlist/helper.js';
24
25
  export { useWaitlistForm } from './waitlist/useWaitlistForm.js';
25
26
  export { resetAutocompleteFillMarkersAfterInput } from './waitlist/autocompleteFillMarkers.js';
26
27
  export { clearWaitlistAttribution, getWaitlistBrowserContext } from './waitlist/attribution.js';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cronos-labs/ui",
3
- "version": "0.5.1",
3
+ "version": "0.6.0",
4
4
  "description": "Shared Header, Footer, Seo, locale and theme primitives for Cronos web properties.",
5
5
  "license": "UNLICENSED",
6
6
  "type": "module",