@cronos-labs/ui 0.5.1 → 0.7.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;
@@ -0,0 +1,32 @@
1
+ export declare enum CronosEnvironment {
2
+ Staging = "staging",
3
+ Prod = "prod"
4
+ }
5
+ export interface CronosUrls {
6
+ cronos: string;
7
+ bridge: string;
8
+ launch: string;
9
+ }
10
+ /**
11
+ * Narrows a raw `VITE_ENVIRONMENT` value to a known environment.
12
+ *
13
+ * Staging is the fallback, not prod: an unset variable means a local dev
14
+ * server or a misconfigured preview, and pointing those at production
15
+ * traffic is the more expensive mistake. Production deploys set the
16
+ * variable explicitly.
17
+ *
18
+ * @param value Raw environment variable value, of unknown shape.
19
+ * @returns The matching environment, or staging when the value is unrecognised.
20
+ */
21
+ export declare const resolveCronosEnvironment: (value: unknown) => CronosEnvironment;
22
+ /**
23
+ * Resolves the origins of the three Cronos web apps for an environment.
24
+ *
25
+ * The apps link to each other across origins, so these have to move as a
26
+ * set — a staging build linking to a production sibling is the failure this
27
+ * prevents. Origins carry no trailing slash; callers append their own path.
28
+ *
29
+ * @param environment The environment the app is running in.
30
+ * @returns The sibling app origins for that environment.
31
+ */
32
+ export declare const resolveCronosUrls: (environment: CronosEnvironment) => CronosUrls;
@@ -0,0 +1,38 @@
1
+ export var CronosEnvironment;
2
+ (function (CronosEnvironment) {
3
+ CronosEnvironment["Staging"] = "staging";
4
+ CronosEnvironment["Prod"] = "prod";
5
+ })(CronosEnvironment || (CronosEnvironment = {}));
6
+ const PROD_URLS = {
7
+ cronos: 'https://cronos.com',
8
+ bridge: 'https://bridge.cronos.com',
9
+ launch: 'https://launch.cronos.com',
10
+ };
11
+ const STAGING_URLS = {
12
+ cronos: 'https://psta-cronos-landingpage-webui.crorc.co',
13
+ bridge: 'https://psta-cronos-bridge-webui.crorc.co',
14
+ launch: 'https://psta-cronos-launch-webui.crorc.co',
15
+ };
16
+ /**
17
+ * Narrows a raw `VITE_ENVIRONMENT` value to a known environment.
18
+ *
19
+ * Staging is the fallback, not prod: an unset variable means a local dev
20
+ * server or a misconfigured preview, and pointing those at production
21
+ * traffic is the more expensive mistake. Production deploys set the
22
+ * variable explicitly.
23
+ *
24
+ * @param value Raw environment variable value, of unknown shape.
25
+ * @returns The matching environment, or staging when the value is unrecognised.
26
+ */
27
+ export const resolveCronosEnvironment = (value) => value === CronosEnvironment.Prod ? CronosEnvironment.Prod : CronosEnvironment.Staging;
28
+ /**
29
+ * Resolves the origins of the three Cronos web apps for an environment.
30
+ *
31
+ * The apps link to each other across origins, so these have to move as a
32
+ * set — a staging build linking to a production sibling is the failure this
33
+ * prevents. Origins carry no trailing slash; callers append their own path.
34
+ *
35
+ * @param environment The environment the app is running in.
36
+ * @returns The sibling app origins for that environment.
37
+ */
38
+ export const resolveCronosUrls = (environment) => environment === CronosEnvironment.Prod ? PROD_URLS : STAGING_URLS;
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';
@@ -32,3 +34,5 @@ export { resetAutocompleteFillMarkersAfterInput } from './waitlist/autocompleteF
32
34
  export { clearWaitlistAttribution, getWaitlistBrowserContext } from './waitlist/attribution.js';
33
35
  export { cardSurface, cardHoverSurface, wellSurface } from './surfaces.js';
34
36
  export { Button, type ButtonProps, type ButtonVariant } from './Button/index.js';
37
+ export { CronosEnvironment, resolveCronosEnvironment, resolveCronosUrls } from './environment.js';
38
+ export type { CronosUrls } from './environment.js';
package/dist/index.js CHANGED
@@ -21,8 +21,10 @@ 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';
27
28
  export { cardSurface, cardHoverSurface, wellSurface } from './surfaces.js';
28
29
  export { Button } from './Button/index.js';
30
+ export { CronosEnvironment, resolveCronosEnvironment, resolveCronosUrls } from './environment.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.7.0",
4
4
  "description": "Shared Header, Footer, Seo, locale and theme primitives for Cronos web properties.",
5
5
  "license": "UNLICENSED",
6
6
  "type": "module",