@alfadocs/ui-kit-debug 1.70.0 → 1.71.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.
@@ -0,0 +1,98 @@
1
+ import { jsxs as m, jsx as r, Fragment as v } from "react/jsx-runtime";
2
+ import { useEffect as f, forwardRef as h } from "react";
3
+ import { c as l } from "./index-D2ZczOXr.js";
4
+ import { useTranslation as g } from "react-i18next";
5
+ import { A as c } from "./alert-9kEwDza3.js";
6
+ import { i as u } from "./is-development-CLUAnsU7.js";
7
+ import { a as p } from "./safe-href-5dbsfbQn.js";
8
+ import { B as b } from "./use-registration-submit-eWAECgUC.js";
9
+ const x = "Alfadocs", w = l(
10
+ "ds:flex ds:min-w-0 ds:flex-col ds:gap-[var(--spacing-xs)]",
11
+ {
12
+ variants: {
13
+ centred: { true: "ds:items-center ds:text-center", false: "" }
14
+ },
15
+ defaultVariants: { centred: !0 }
16
+ }
17
+ ), A = l(
18
+ "ds:focus-visible:outline-solid ds:focus-visible:outline-[length:var(--focus-ring-width)] ds:focus-visible:outline-[var(--ring)] ds:focus-visible:outline-offset-[length:var(--focus-ring-offset)] ds:forced-colors:focus-visible:outline-[CanvasText]"
19
+ ), N = l(
20
+ 'type-body-sm ds:relative ds:rounded-[var(--radius-sm)] ds:underline ds:underline-offset-4 ds:text-[var(--foreground)] ds:before:absolute ds:before:inset-x-0 ds:before:inset-y-[calc((var(--min-target-size)-100%)/-2)] ds:before:content-[""] ds:focus-visible:outline-solid ds:focus-visible:outline-[length:var(--focus-ring-width)] ds:focus-visible:outline-[var(--ring)] ds:focus-visible:outline-offset-[length:var(--focus-ring-offset)] ds:forced-colors:focus-visible:outline-[CanvasText]',
21
+ {
22
+ variants: {
23
+ /** Centred under a button, or at the end edge under a field. */
24
+ align: { center: "ds:self-center", end: "ds:self-end" }
25
+ },
26
+ defaultVariants: { align: "center" }
27
+ }
28
+ );
29
+ function B({
30
+ href: e,
31
+ action: s,
32
+ align: t,
33
+ children: a
34
+ }) {
35
+ return /* @__PURE__ */ r("a", { href: e, "data-action": s, className: N({ align: t }), children: a });
36
+ }
37
+ function S({
38
+ id: e,
39
+ title: s,
40
+ description: t,
41
+ level: a,
42
+ showLogo: n,
43
+ logoMotion: d
44
+ }) {
45
+ const i = `h${a}`;
46
+ return /* @__PURE__ */ m("div", { className: w({ centred: n }), children: [
47
+ n && /* @__PURE__ */ r(b, { motion: d, label: x }),
48
+ /* @__PURE__ */ r(i, { id: e, className: "type-title-card", children: s }),
49
+ t && /* @__PURE__ */ r("p", { className: "type-body-sm ds:text-[var(--muted-foreground)]", children: t })
50
+ ] });
51
+ }
52
+ const y = h(
53
+ ({ id: e, announce: s, children: t }, a) => /* @__PURE__ */ r(
54
+ c,
55
+ {
56
+ ref: a,
57
+ id: e,
58
+ variant: "error",
59
+ announce: s,
60
+ tabIndex: -1,
61
+ "data-field": "form",
62
+ className: A(),
63
+ children: /* @__PURE__ */ r(c.Description, { children: t })
64
+ }
65
+ )
66
+ );
67
+ y.displayName = "AuthFormMessage";
68
+ function C(e, s, t, a) {
69
+ const n = new Set(Object.values(t ?? {})), d = s ? Object.entries(a ?? {}) : [], i = d.filter(([o]) => n.has(o)).map(([o]) => o).join(", ");
70
+ return f(() => {
71
+ i && u() && console.warn(
72
+ `${e}: hiddenFields "${i}" is also named in fieldNames and was left out. The form's own field wins.`
73
+ );
74
+ }, [i, e]), d.filter(([o]) => !n.has(o));
75
+ }
76
+ function D(e, s, t) {
77
+ const a = s && t && p(t) ? t : void 0, n = t ? s ? a ? void 0 : "`action` is not a path or an address on this origin and was left out. The form posts to the current address." : "`action` does nothing without `fieldNames`: only a native form post has an address." : void 0;
78
+ return f(() => {
79
+ n && u() && console.warn(`${e}: ${n}`);
80
+ }, [e, n]), a;
81
+ }
82
+ function H({ fields: e }) {
83
+ return /* @__PURE__ */ r(v, { children: e.map(([s, t]) => /* @__PURE__ */ r("input", { type: "hidden", name: s, value: t }, s)) });
84
+ }
85
+ function O({ busy: e }) {
86
+ const { t: s } = g("ui");
87
+ return /* @__PURE__ */ r("p", { role: "status", className: "ds:sr-only", children: e ? s("chat.message.status.sending") : "" });
88
+ }
89
+ export {
90
+ S as A,
91
+ H as E,
92
+ C as a,
93
+ y as b,
94
+ O as c,
95
+ B as d,
96
+ D as u
97
+ };
98
+ //# sourceMappingURL=auth-form-parts-DHv6LVV2.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"auth-form-parts-B40zvD-a.js","sources":["../../src/patterns/_shared/auth-form-parts.tsx"],"sourcesContent":["/* -------------------------------------------------------------------- */\n/* The parts LoginForm and ForgotPasswordForm share: the top of the */\n/* card, the message above the fields, and the host's extra hidden */\n/* inputs for a native post. They follow RegistrationForm, so the three */\n/* cards of the signed-out pages look and behave as one family. */\n/* */\n/* Shipped with the patterns that import it; not an export of its own. */\n/* -------------------------------------------------------------------- */\n\nimport { forwardRef, useEffect, type ReactNode } from 'react';\nimport { cva } from 'class-variance-authority';\nimport { useTranslation } from 'react-i18next';\nimport { Alert, type AlertAnnounce } from '../../components/alert';\nimport { isDevelopment } from '../../components/_shared/is-development';\nimport { isSameSiteHref } from '../../components/_shared/safe-href';\nimport { BrandMark, type BrandMarkMotion } from './brand-mark';\n\n// A brand name, not interface copy: it reads the same in every language,\n// so it has no translation key.\nconst BRAND_NAME = 'Alfadocs';\n\n/** The top of the card: the mark, the heading and its line. */\nconst headingBlockVariants = cva(\n 'ds:flex ds:min-w-0 ds:flex-col ds:gap-[var(--spacing-xs)]',\n {\n variants: {\n centred: { true: 'ds:items-center ds:text-center', false: '' },\n },\n defaultVariants: { centred: true },\n },\n);\n\n// The message above the fields can take focus, so it shows a ring like\n// anything else that does.\nconst formMessageVariants = cva(\n 'ds:focus-visible:outline-solid ds:focus-visible:outline-[length:var(--focus-ring-width)] ds:focus-visible:outline-[var(--ring)] ds:focus-visible:outline-offset-[length:var(--focus-ring-offset)] ds:forced-colors:focus-visible:outline-[CanvasText]',\n);\n\n// A link under the card's button takes the text colour and an underline.\n// The kit Link's own colour (`--primary`) falls just below AA on a card in\n// the dark theme, as RegistrationForm's links found. The `before` box\n// makes the touch target 44px high without moving the text.\nconst textLinkVariants = cva(\n 'type-body-sm ds:relative ds:self-center ds:rounded-[var(--radius-sm)] ds:underline ds:underline-offset-4 ds:text-[var(--foreground)] ds:before:absolute ds:before:inset-x-0 ds:before:inset-y-[calc((var(--min-target-size)-100%)/-2)] ds:before:content-[\"\"] ds:focus-visible:outline-solid ds:focus-visible:outline-[length:var(--focus-ring-width)] ds:focus-visible:outline-[var(--ring)] ds:focus-visible:outline-offset-[length:var(--focus-ring-offset)] ds:forced-colors:focus-visible:outline-[CanvasText]',\n);\n\nexport interface AuthTextLinkProps {\n href: string;\n /** Emitted as `data-action`. */\n action: string;\n children: ReactNode;\n}\n\n/** The quiet link under the card's button: \"Forgot password?\", \"Back to\n * sign in\". */\nexport function AuthTextLink({ href, action, children }: AuthTextLinkProps) {\n return (\n <a href={href} data-action={action} className={textLinkVariants()}>\n {children}\n </a>\n );\n}\n\nexport interface AuthCardHeadingProps {\n /** The id the form is labelled by. */\n id: string;\n title: string;\n description?: string;\n level: 1 | 2 | 3;\n showLogo: boolean;\n logoMotion: BrandMarkMotion;\n}\n\n/** The mark, the heading and the line under it. */\nexport function AuthCardHeading({\n id,\n title,\n description,\n level,\n showLogo,\n logoMotion,\n}: AuthCardHeadingProps) {\n const Heading = `h${level}` as const;\n return (\n <div className={headingBlockVariants({ centred: showLogo })}>\n {/* The page's logo: read out once, before the heading. */}\n {showLogo && <BrandMark motion={logoMotion} label={BRAND_NAME} />}\n <Heading id={id} className=\"type-title-card\">\n {title}\n </Heading>\n {description && (\n <p className=\"type-body-sm ds:text-[var(--muted-foreground)]\">\n {description}\n </p>\n )}\n </div>\n );\n}\n\nexport interface AuthFormMessageProps {\n id?: string;\n /** Off while the error summary reads the message (see\n * useFormMessageAnnounce); the Alert's default otherwise. */\n announce?: AlertAnnounce;\n children: ReactNode;\n}\n\n/** The server's message about the whole form, above the fields. */\nexport const AuthFormMessage = forwardRef<HTMLDivElement, AuthFormMessageProps>(\n ({ id, announce, children }, ref) => (\n <Alert\n ref={ref}\n id={id}\n variant=\"error\"\n announce={announce}\n tabIndex={-1}\n data-field=\"form\"\n className={formMessageVariants()}\n >\n <Alert.Description>{children}</Alert.Description>\n </Alert>\n ),\n);\nAuthFormMessage.displayName = 'AuthFormMessage';\n\n/**\n * The host's extra hidden inputs for a native post: a CSRF token, a\n * return address. The form's own fields keep their names, so an extra\n * one that asks for the same name is left out, and said so in\n * development.\n */\nexport function useExtraFields(\n component: string,\n native: boolean,\n fieldNames: Readonly<Record<string, string | undefined>> | undefined,\n hiddenFields: Readonly<Record<string, string>> | undefined,\n): [string, string][] {\n const ownNames = new Set(Object.values(fieldNames ?? {}));\n const all = native ? Object.entries(hiddenFields ?? {}) : [];\n const clashes = all\n .filter(([name]) => ownNames.has(name))\n .map(([name]) => name)\n .join(', ');\n useEffect(() => {\n if (clashes && isDevelopment()) {\n console.warn(\n `${component}: hiddenFields \"${clashes}\" is also named in fieldNames and was left out. The form's own field wins.`,\n );\n }\n }, [clashes, component]);\n return all.filter(([name]) => !ownNames.has(name));\n}\n\n/**\n * The address a native post may go to: a path, or an address on the\n * page's own origin. Anything else is refused, and the form then posts to\n * the current address. Wiring that cannot work as written is said so in\n * development, because nothing on the page shows it.\n */\nexport function useFormAction(\n component: string,\n native: boolean,\n action: string | undefined,\n): string | undefined {\n const accepted =\n native && action && isSameSiteHref(action) ? action : undefined;\n const problem = !action\n ? undefined\n : !native\n ? '`action` does nothing without `fieldNames`: only a native form post has an address.'\n : !accepted\n ? '`action` is not a path or an address on this origin and was left out. The form posts to the current address.'\n : undefined;\n useEffect(() => {\n if (problem && isDevelopment()) console.warn(`${component}: ${problem}`);\n }, [component, problem]);\n return accepted;\n}\n\n/** The extra hidden inputs themselves. */\nexport function ExtraFields({ fields }: { fields: [string, string][] }) {\n return (\n <>\n {fields.map(([name, value]) => (\n <input key={name} type=\"hidden\" name={name} value={value} />\n ))}\n </>\n );\n}\n\n/**\n * Says \"sending\" in words while the form is busy: the button's spinner is\n * decorative and the button is disabled. Always mounted, because a live\n * region announces a change, not its first content.\n */\nexport function AuthFormStatus({ busy }: { busy: boolean }) {\n const { t } = useTranslation('ui');\n return (\n <p role=\"status\" className=\"ds:sr-only\">\n {busy ? t('chat.message.status.sending') : ''}\n </p>\n );\n}\n"],"names":["BRAND_NAME","headingBlockVariants","cva","formMessageVariants","textLinkVariants","AuthTextLink","href","action","children","jsx","AuthCardHeading","id","title","description","level","showLogo","logoMotion","Heading","jsxs","BrandMark","AuthFormMessage","forwardRef","announce","ref","Alert","useExtraFields","component","native","fieldNames","hiddenFields","ownNames","all","clashes","name","useEffect","isDevelopment","useFormAction","accepted","isSameSiteHref","problem","ExtraFields","fields","value","AuthFormStatus","busy","t","useTranslation"],"mappings":"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"}
1
+ {"version":3,"file":"auth-form-parts-DHv6LVV2.js","sources":["../../src/patterns/_shared/auth-form-parts.tsx"],"sourcesContent":["/* -------------------------------------------------------------------- */\n/* The parts LoginForm and ForgotPasswordForm share: the top of the */\n/* card, the message above the fields, and the host's extra hidden */\n/* inputs for a native post. They follow RegistrationForm, so the three */\n/* cards of the signed-out pages look and behave as one family. */\n/* */\n/* Shipped with the patterns that import it; not an export of its own. */\n/* -------------------------------------------------------------------- */\n\nimport { forwardRef, useEffect, type ReactNode } from 'react';\nimport { cva } from 'class-variance-authority';\nimport { useTranslation } from 'react-i18next';\nimport { Alert, type AlertAnnounce } from '../../components/alert';\nimport { isDevelopment } from '../../components/_shared/is-development';\nimport { isSameSiteHref } from '../../components/_shared/safe-href';\nimport { BrandMark, type BrandMarkMotion } from './brand-mark';\n\n// A brand name, not interface copy: it reads the same in every language,\n// so it has no translation key.\nconst BRAND_NAME = 'Alfadocs';\n\n/** The top of the card: the mark, the heading and its line. */\nconst headingBlockVariants = cva(\n 'ds:flex ds:min-w-0 ds:flex-col ds:gap-[var(--spacing-xs)]',\n {\n variants: {\n centred: { true: 'ds:items-center ds:text-center', false: '' },\n },\n defaultVariants: { centred: true },\n },\n);\n\n// The message above the fields can take focus, so it shows a ring like\n// anything else that does.\nconst formMessageVariants = cva(\n 'ds:focus-visible:outline-solid ds:focus-visible:outline-[length:var(--focus-ring-width)] ds:focus-visible:outline-[var(--ring)] ds:focus-visible:outline-offset-[length:var(--focus-ring-offset)] ds:forced-colors:focus-visible:outline-[CanvasText]',\n);\n\n// A link in the card takes the text colour and an underline.\n// The kit Link's own colour (`--primary`) falls just below AA on a card in\n// the dark theme, as RegistrationForm's links found. The `before` box\n// makes the touch target 44px high without moving the text.\nconst textLinkVariants = cva(\n 'type-body-sm ds:relative ds:rounded-[var(--radius-sm)] ds:underline ds:underline-offset-4 ds:text-[var(--foreground)] ds:before:absolute ds:before:inset-x-0 ds:before:inset-y-[calc((var(--min-target-size)-100%)/-2)] ds:before:content-[\"\"] ds:focus-visible:outline-solid ds:focus-visible:outline-[length:var(--focus-ring-width)] ds:focus-visible:outline-[var(--ring)] ds:focus-visible:outline-offset-[length:var(--focus-ring-offset)] ds:forced-colors:focus-visible:outline-[CanvasText]',\n {\n variants: {\n /** Centred under a button, or at the end edge under a field. */\n align: { center: 'ds:self-center', end: 'ds:self-end' },\n },\n defaultVariants: { align: 'center' },\n },\n);\n\nexport interface AuthTextLinkProps {\n href: string;\n /** Emitted as `data-action`. */\n action: string;\n /** Default `center`. */\n align?: 'center' | 'end';\n children: ReactNode;\n}\n\n/** A quiet link in the card: \"Forgot password?\", \"Back to sign in\". */\nexport function AuthTextLink({\n href,\n action,\n align,\n children,\n}: AuthTextLinkProps) {\n return (\n <a href={href} data-action={action} className={textLinkVariants({ align })}>\n {children}\n </a>\n );\n}\n\nexport interface AuthCardHeadingProps {\n /** The id the form is labelled by. */\n id: string;\n title: string;\n description?: string;\n level: 1 | 2 | 3;\n showLogo: boolean;\n logoMotion: BrandMarkMotion;\n}\n\n/** The mark, the heading and the line under it. */\nexport function AuthCardHeading({\n id,\n title,\n description,\n level,\n showLogo,\n logoMotion,\n}: AuthCardHeadingProps) {\n const Heading = `h${level}` as const;\n return (\n <div className={headingBlockVariants({ centred: showLogo })}>\n {/* The page's logo: read out once, before the heading. */}\n {showLogo && <BrandMark motion={logoMotion} label={BRAND_NAME} />}\n <Heading id={id} className=\"type-title-card\">\n {title}\n </Heading>\n {description && (\n <p className=\"type-body-sm ds:text-[var(--muted-foreground)]\">\n {description}\n </p>\n )}\n </div>\n );\n}\n\nexport interface AuthFormMessageProps {\n id?: string;\n /** Off while the error summary reads the message (see\n * useFormMessageAnnounce); the Alert's default otherwise. */\n announce?: AlertAnnounce;\n children: ReactNode;\n}\n\n/** The server's message about the whole form, above the fields. */\nexport const AuthFormMessage = forwardRef<HTMLDivElement, AuthFormMessageProps>(\n ({ id, announce, children }, ref) => (\n <Alert\n ref={ref}\n id={id}\n variant=\"error\"\n announce={announce}\n tabIndex={-1}\n data-field=\"form\"\n className={formMessageVariants()}\n >\n <Alert.Description>{children}</Alert.Description>\n </Alert>\n ),\n);\nAuthFormMessage.displayName = 'AuthFormMessage';\n\n/**\n * The host's extra hidden inputs for a native post: a CSRF token, a\n * return address. The form's own fields keep their names, so an extra\n * one that asks for the same name is left out, and said so in\n * development.\n */\nexport function useExtraFields(\n component: string,\n native: boolean,\n fieldNames: Readonly<Record<string, string | undefined>> | undefined,\n hiddenFields: Readonly<Record<string, string>> | undefined,\n): [string, string][] {\n const ownNames = new Set(Object.values(fieldNames ?? {}));\n const all = native ? Object.entries(hiddenFields ?? {}) : [];\n const clashes = all\n .filter(([name]) => ownNames.has(name))\n .map(([name]) => name)\n .join(', ');\n useEffect(() => {\n if (clashes && isDevelopment()) {\n console.warn(\n `${component}: hiddenFields \"${clashes}\" is also named in fieldNames and was left out. The form's own field wins.`,\n );\n }\n }, [clashes, component]);\n return all.filter(([name]) => !ownNames.has(name));\n}\n\n/**\n * The address a native post may go to: a path, or an address on the\n * page's own origin. Anything else is refused, and the form then posts to\n * the current address. Wiring that cannot work as written is said so in\n * development, because nothing on the page shows it.\n */\nexport function useFormAction(\n component: string,\n native: boolean,\n action: string | undefined,\n): string | undefined {\n const accepted =\n native && action && isSameSiteHref(action) ? action : undefined;\n const problem = !action\n ? undefined\n : !native\n ? '`action` does nothing without `fieldNames`: only a native form post has an address.'\n : !accepted\n ? '`action` is not a path or an address on this origin and was left out. The form posts to the current address.'\n : undefined;\n useEffect(() => {\n if (problem && isDevelopment()) console.warn(`${component}: ${problem}`);\n }, [component, problem]);\n return accepted;\n}\n\n/** The extra hidden inputs themselves. */\nexport function ExtraFields({ fields }: { fields: [string, string][] }) {\n return (\n <>\n {fields.map(([name, value]) => (\n <input key={name} type=\"hidden\" name={name} value={value} />\n ))}\n </>\n );\n}\n\n/**\n * Says \"sending\" in words while the form is busy: the button's spinner is\n * decorative and the button is disabled. Always mounted, because a live\n * region announces a change, not its first content.\n */\nexport function AuthFormStatus({ busy }: { busy: boolean }) {\n const { t } = useTranslation('ui');\n return (\n <p role=\"status\" className=\"ds:sr-only\">\n {busy ? t('chat.message.status.sending') : ''}\n </p>\n );\n}\n"],"names":["BRAND_NAME","headingBlockVariants","cva","formMessageVariants","textLinkVariants","AuthTextLink","href","action","align","children","jsx","AuthCardHeading","id","title","description","level","showLogo","logoMotion","Heading","jsxs","BrandMark","AuthFormMessage","forwardRef","announce","ref","Alert","useExtraFields","component","native","fieldNames","hiddenFields","ownNames","all","clashes","name","useEffect","isDevelopment","useFormAction","accepted","isSameSiteHref","problem","ExtraFields","fields","value","AuthFormStatus","busy","t","useTranslation"],"mappings":"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"}
@@ -10,7 +10,7 @@ import { E as bo } from "./error-summary-mGxIyDAY.js";
10
10
  import { F as wo } from "./form-field-Ani72sVY.js";
11
11
  import { i as xo } from "./safe-href-5dbsfbQn.js";
12
12
  import { u as Eo } from "./use-state-backed-input-DhRQo6gM.js";
13
- import { u as Io, a as Ao, A as Po, b as yo, E as So, c as Ro, d as To } from "./auth-form-parts-B40zvD-a.js";
13
+ import { u as Io, a as Ao, A as Po, b as yo, E as So, c as Ro, d as To } from "./auth-form-parts-DHv6LVV2.js";
14
14
  import { u as Bo, a as Mo, b as No } from "./use-registration-submit-eWAECgUC.js";
15
15
  const Vo = ["email"], Do = U(
16
16
  "ds:mx-auto ds:w-full ds:max-w-[400px] ds:text-start ds:text-[var(--foreground)]"
@@ -231,4 +231,4 @@ export {
231
231
  Vo as F,
232
232
  _o as a
233
233
  };
234
- //# sourceMappingURL=forgot-password-form-Dqi7R-NL.js.map
234
+ //# sourceMappingURL=forgot-password-form-BGPc8TGr.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"forgot-password-form-Dqi7R-NL.js","sources":["../../src/patterns/forgot-password-form/forgot-password-form.tsx"],"sourcesContent":["/* -------------------------------------------------------------------- */\n/* ForgotPasswordForm — the card that asks for a password reset link. */\n/* */\n/* One email field and a button, then a confirmation. The confirmation */\n/* is vague on purpose: it never says whether an account exists for the */\n/* address, and never repeats the address. The host sends the email and */\n/* owns every server rule; the card checks what a browser can check, */\n/* then hands the email to `onSubmit` or posts a real form. */\n/* -------------------------------------------------------------------- */\n\nimport {\n forwardRef,\n useEffect,\n useId,\n useRef,\n useState,\n type HTMLAttributes,\n type ReactNode,\n} from 'react';\nimport { useTranslation } from 'react-i18next';\nimport { cva } from 'class-variance-authority';\n// The validator EmailInput itself uses, so the two never disagree.\nimport isEmail from 'validator/lib/isEmail';\nimport { Alert } from '../../components/alert';\nimport { Button } from '../../components/button';\nimport { Card } from '../../components/card';\nimport { EmailInput } from '../../components/email-input';\nimport { ErrorSummary } from '../../components/error-summary';\nimport { FormField } from '../../components/form-field';\nimport { isSafeHref } from '../../components/_shared/safe-href';\nimport { useStateBackedInput } from '../../components/_shared/use-state-backed-input';\nimport {\n AuthCardHeading,\n AuthFormMessage,\n AuthFormStatus,\n AuthTextLink,\n ExtraFields,\n useExtraFields,\n useFormAction,\n} from '../_shared/auth-form-parts';\nimport type { BrandMarkMotion } from '../_shared/brand-mark';\nimport {\n useErrorSummary,\n useFormMessageAnnounce,\n} from '../_shared/use-error-summary';\nimport { useRegistrationSubmit } from '../registration-form/use-registration-submit';\n\n/* -------------------------------------------------------------------- */\n/* Types */\n/* -------------------------------------------------------------------- */\n\n/** The form's one field. */\nexport const FORGOT_PASSWORD_FORM_FIELDS = ['email'] as const;\n\nexport type ForgotPasswordFormField =\n (typeof FORGOT_PASSWORD_FORM_FIELDS)[number];\n\n/** What `onSubmit` receives once the email has passed its check. */\nexport interface ForgotPasswordFormValues {\n email: string;\n}\n\n/** The email to show when the form opens. */\nexport type ForgotPasswordFormInitialValues = Partial<ForgotPasswordFormValues>;\n\n/** Messages from the server, already translated: one for the email,\n * plus `form` for a message about the whole form, such as \"too many\n * requests, try again later\". */\nexport type ForgotPasswordFormErrors = Partial<\n Record<ForgotPasswordFormField | 'form', string>\n>;\n\n/** The host's own form field name, for a native form post. It is needed:\n * a field without a name is not posted. */\nexport type ForgotPasswordFormFieldNames = Record<\n ForgotPasswordFormField,\n string\n>;\n\nexport interface ForgotPasswordFormProps extends Omit<\n HTMLAttributes<HTMLDivElement>,\n 'title' | 'onSubmit'\n> {\n /** Replaces the heading \"Reset your password\". Pass it translated. */\n title?: string;\n /**\n * Replaces the line under the heading (\"Enter the email linked to your\n * account and we will send a reset link.\"). Pass it translated.\n */\n description?: string;\n /**\n * The heading's level. Default 1: this page usually has no other\n * heading. Pass 2 on a page that has its own `h1`.\n */\n headingLevel?: 1 | 2 | 3;\n /**\n * Shows the Alfadocs mark above the heading, with the heading and its\n * line centred under it. Default true.\n */\n showLogo?: boolean;\n /** How the mark arrives when the card opens. Default `assemble`. It\n * plays once and rests. */\n logoMotion?: BrandMarkMotion;\n /**\n * Host content between the heading and the field, such as a notice. It\n * sits outside the form, so nothing in it is posted.\n */\n children?: ReactNode;\n /** The email to show when the form opens. Read once. */\n initialValues?: ForgotPasswordFormInitialValues;\n /**\n * Messages from the server, already translated. The email's message\n * shows on the field until the user changes it; `form` shows above the\n * field. When messages arrive, focus moves to the error summary at the\n * top of the form when the email has a message, or to the message\n * above the field when only that says something.\n *\n * Do not use a message to say that no account has the address: that\n * tells a stranger who has an account. Show `sent` for every address.\n */\n errors?: ForgotPasswordFormErrors;\n /**\n * Shows the confirmation in place of the form: \"Check your inbox\". Set\n * it once the request has been accepted, whether or not an account\n * exists for the address. The confirmation says \"if an account exists\"\n * and never shows the address.\n */\n sent?: boolean;\n /**\n * Adds \"Back to sign in\" under the card's content. A path on the same\n * site, or an address written in full. Anything else shows no link.\n */\n loginHref?: string;\n /** Replaces the button's \"Send reset link\". Pass it translated. */\n submitLabel?: string;\n /**\n * True while the host sends the email: the button shows a spinner and\n * a second submit is ignored. Set it inside `onSubmit`, before anything\n * is awaited: until it is true, a second press sends a second request.\n */\n submitting?: boolean;\n /** For React hosts: called with the email once it has passed its\n * check. Not called when `fieldNames` is set. */\n onSubmit?: (values: ForgotPasswordFormValues) => void;\n /**\n * For a native form post: the host's own field name, e.g.\n * `{ email: 'email' }`. When set, the input carries that name and the\n * browser posts the form.\n */\n fieldNames?: ForgotPasswordFormFieldNames;\n /**\n * Where a native form post goes. Default: the current address. Only a\n * path, or an address on the page's own origin, is accepted: another\n * origin is never sent what the user typed, and the post then goes to\n * the current address. Does nothing without `fieldNames`.\n */\n action?: string;\n /**\n * Extra hidden inputs posted with a native form, by name: a CSRF\n * token. Not used with `onSubmit`. A name that is also the one in\n * `fieldNames` is left out: the form's own field wins.\n */\n hiddenFields?: Readonly<Record<string, string>>;\n}\n\n/* -------------------------------------------------------------------- */\n/* Styles */\n/* -------------------------------------------------------------------- */\n\n// 400px at most, in px: a host whose root font size is not 16px would\n// stretch or squeeze a width in rem.\nconst forgotPasswordFormVariants = cva(\n 'ds:mx-auto ds:w-full ds:max-w-[400px] ds:text-start ds:text-[var(--foreground)]',\n);\n\n// The confirmation takes focus when it appears, so it shows a ring.\nconst confirmationVariants = cva(\n 'ds:focus-visible:outline-solid ds:focus-visible:outline-[length:var(--focus-ring-width)] ds:focus-visible:outline-[var(--ring)] ds:focus-visible:outline-offset-[length:var(--focus-ring-offset)] ds:forced-colors:focus-visible:outline-[CanvasText]',\n);\n\n/* -------------------------------------------------------------------- */\n/* Helpers */\n/* -------------------------------------------------------------------- */\n\ntype FocusTarget = 'email' | 'form';\n\n/** The form's one field, as the error summary lists it. */\nconst FIELDS = ['email'] as const;\n\n/** The confirmation's heading, by the level of the card's own. */\nconst CONFIRMATION_HEADING = { 1: 'h2', 2: 'h3', 3: 'h4' } as const;\n\nconst hasMessage = (errors: ForgotPasswordFormErrors): boolean =>\n Object.values(errors).some(Boolean);\n\nconst focusTargetFor = (\n messages: ForgotPasswordFormErrors,\n): FocusTarget | undefined =>\n messages.email ? 'email' : messages.form ? 'form' : undefined;\n\n/* -------------------------------------------------------------------- */\n/* Component */\n/* -------------------------------------------------------------------- */\n\n/**\n * The card that asks for a password reset link. Mount it on the\n * forgot-password page, inside `SignedOutFrame` or the host's own chrome.\n */\nexport const ForgotPasswordForm = forwardRef<\n HTMLDivElement,\n ForgotPasswordFormProps\n>(\n (\n {\n title,\n description,\n headingLevel = 1,\n showLogo = true,\n logoMotion = 'assemble',\n children,\n initialValues,\n errors,\n sent = false,\n loginHref,\n submitLabel,\n submitting = false,\n onSubmit,\n fieldNames,\n action,\n hiddenFields,\n className,\n ...rest\n },\n ref,\n ) => {\n const { t } = useTranslation('ui');\n const titleId = useId();\n const sentTitleId = useId();\n const sentDescriptionId = useId();\n const emailFieldId = useId();\n const formMessageId = useId();\n const native = fieldNames !== undefined;\n\n /* ------------------------------ State ----------------------------- */\n\n const [email, setEmail] = useState(() => initialValues?.email ?? '');\n // True once the user has filled in the field and left it. An email\n // that arrives filled in counts: a bad one is then flagged at once,\n // with its message. An empty field is not flagged for being passed\n // through; its message waits for a submit.\n const [touched, setTouched] = useState(() => Boolean(initialValues?.email));\n const [submitted, setSubmitted] = useState(false);\n const [serverErrors, setServerErrors] = useState<ForgotPasswordFormErrors>(\n () => errors ?? {},\n );\n\n const emailInput = useRef<HTMLInputElement>(null);\n // The address stays out of the input's `value` attribute, where a\n // copied outerHTML or a session replay could read it.\n const emailField = useStateBackedInput(email, emailInput);\n /**\n * What the input holds now. Read from the input, not from state: a\n * browser can fill the field without telling React, and the form must\n * not refuse a field that is visibly filled.\n */\n const held = (): string => {\n const value = emailInput.current?.value ?? email;\n if (value !== email) setEmail(value);\n return value;\n };\n\n /* ---------------------------- Validation -------------------------- */\n\n const invalid = !isEmail(email);\n // The message shows once the user has left the field or tried to\n // submit.\n const ownError =\n (submitted || touched) && invalid\n ? email === ''\n ? t('forgotPasswordForm.emailRequired')\n : t('inputs.email.invalid')\n : undefined;\n const emailError = serverErrors.email ?? ownError;\n\n /* ------------------------------ Focus ----------------------------- */\n\n const formMessage = useRef<HTMLDivElement>(null);\n const confirmation = useRef<HTMLDivElement>(null);\n\n // The email's message after a refused submit, or after a server\n // answer, is listed at the top of the form, and focus goes there. A\n // page the server rendered after a failed post opens that way too: it\n // announces nothing itself.\n const [openedWithEmailMessage] = useState(() => Boolean(errors?.email));\n const summary = useErrorSummary(\n FIELDS,\n () => emailError,\n openedWithEmailMessage,\n );\n // The message about the whole form: quiet while the summary lists the\n // email, because the summary reads it then.\n const formMessageAnnounce = useFormMessageAnnounce(\n serverErrors.form,\n summary.listed.length > 0,\n );\n\n // Any other place focus goes: the message above the field, when only\n // it says something. A new object for each request, so asking twice\n // for the same place moves focus twice.\n const [focusRequest, setFocusRequest] = useState<{\n target: FocusTarget;\n } | null>(() => {\n const target = openedWithEmailMessage\n ? undefined\n : focusTargetFor(errors ?? {});\n return target ? { target } : null;\n });\n useEffect(() => {\n if (!focusRequest) return;\n (focusRequest.target === 'email'\n ? emailInput\n : formMessage\n ).current?.focus();\n }, [focusRequest]);\n\n // The form, and the button that had focus, are gone once the\n // confirmation shows. Move focus to it, so a keyboard or screen\n // reader user lands on what happened. Not on a page that opens\n // already sent: that one is read from the top like any other.\n const wasSent = useRef(sent);\n useEffect(() => {\n if (sent && !wasSent.current) confirmation.current?.focus();\n wasSent.current = sent;\n }, [sent]);\n\n /* ------------------------------ Submit ---------------------------- */\n\n const submit = useRegistrationSubmit<'email'>({\n errors,\n submitting,\n native,\n captcha: false,\n firstProblem: () => (isEmail(held()) ? undefined : 'email'),\n onRefused: () => {\n setSubmitted(true);\n summary.open(() => setFocusRequest({ target: 'email' }));\n },\n // Only a React host gets here: a native post is the browser's own.\n deliver: () => onSubmit?.({ email: held() }),\n onAnswer: (answer) => {\n const next: ForgotPasswordFormErrors = answer;\n setServerErrors(next);\n if (!hasMessage(next)) return;\n setSubmitted(false);\n // The summary when the email has a message, else the message above.\n summary.open(() => {\n const target = focusTargetFor(next);\n if (target) setFocusRequest({ target });\n });\n },\n });\n const { busy } = submit;\n\n /* ------------------------------ Render ---------------------------- */\n\n const formAction = useFormAction('ForgotPasswordForm', native, action);\n const loginLink =\n loginHref && isSafeHref(loginHref) ? loginHref : undefined;\n const extraFields = useExtraFields(\n 'ForgotPasswordForm',\n native,\n fieldNames,\n hiddenFields,\n );\n\n return (\n <div\n ref={ref}\n {...rest}\n data-component=\"forgot-password-form\"\n data-state={sent ? 'sent' : 'asking'}\n className={forgotPasswordFormVariants({ className })}\n >\n <Card variant=\"elevated\">\n <Card.Body className=\"ds:flex ds:flex-col ds:gap-[var(--spacing-lg)]\">\n <AuthCardHeading\n id={titleId}\n title={title ?? t('forgotPasswordForm.title')}\n // The instruction belongs to the form: once it is sent there\n // is nothing left to enter.\n description={\n sent\n ? undefined\n : (description ?? t('forgotPasswordForm.description'))\n }\n level={headingLevel}\n showLogo={showLogo}\n logoMotion={logoMotion}\n />\n\n {children ? (\n <div\n data-slot=\"host-content\"\n className=\"ds:flex ds:flex-col ds:gap-[var(--spacing-md)]\"\n >\n {children}\n </div>\n ) : null}\n\n {sent ? (\n // Named and described, so a screen reader that lands here\n // reads the whole confirmation, not only \"status\".\n <Alert\n ref={confirmation}\n variant=\"success\"\n tabIndex={-1}\n aria-labelledby={sentTitleId}\n aria-describedby={sentDescriptionId}\n data-slot=\"confirmation\"\n className={confirmationVariants()}\n >\n {/* One level under the card's own heading. */}\n <Alert.Title\n id={sentTitleId}\n as={CONFIRMATION_HEADING[headingLevel]}\n >\n {t('forgotPasswordForm.sentTitle')}\n </Alert.Title>\n <Alert.Description id={sentDescriptionId}>\n {t('forgotPasswordForm.sentDescription')}\n </Alert.Description>\n </Alert>\n ) : (\n <>\n <form\n noValidate\n method=\"post\"\n action={formAction}\n aria-labelledby={titleId}\n aria-busy={busy || undefined}\n onSubmit={submit.handleSubmit}\n className=\"ds:flex ds:flex-col ds:gap-[var(--spacing-md)]\"\n >\n <ErrorSummary\n ref={summary.ref}\n headingLevel={(headingLevel + 1) as 2 | 3 | 4}\n // The message about the whole form is read with it.\n aria-describedby={\n serverErrors.form ? formMessageId : undefined\n }\n errors={summary.listed.map((field) => ({\n field,\n message: emailError ?? '',\n targetId: emailFieldId,\n }))}\n />\n\n {serverErrors.form && (\n <AuthFormMessage\n ref={formMessage}\n id={formMessageId}\n announce={formMessageAnnounce}\n >\n {serverErrors.form}\n </AuthFormMessage>\n )}\n\n <FormField\n id={emailFieldId}\n label={t('forgotPasswordForm.email')}\n required\n error={emailError}\n // Quiet while the summary lists it: the summary reads it.\n announceError={!summary.lists('email')}\n data-field=\"email\"\n >\n {/* One check, above, drives the message, the red mark and\n aria-invalid through FormField. EmailInput's own check\n is off so the two cannot disagree. */}\n <EmailInput\n ref={emailField.ref}\n name={native ? fieldNames.email : 'email'}\n disableBuiltInValidation\n onChange={emailField.quiet((event) => {\n setEmail(event.target.value);\n // The server's message was about the old value.\n setServerErrors((previous) =>\n previous.email\n ? { ...previous, email: undefined }\n : previous,\n );\n })}\n onBlur={(event) => {\n // Whatever a browser filled in without saying so.\n setEmail(event.target.value);\n if (event.target.value !== '') setTouched(true);\n }}\n />\n </FormField>\n\n <ExtraFields fields={extraFields} />\n\n <Button\n type=\"submit\"\n intent=\"primary\"\n size=\"lg\"\n loading={busy}\n className=\"ds:w-full\"\n data-action=\"submit\"\n >\n {submitLabel ?? t('forgotPasswordForm.submit')}\n </Button>\n </form>\n {/* Outside the form: a screen reader may hold back what\n changes inside a busy region. */}\n <AuthFormStatus busy={busy} />\n </>\n )}\n\n {loginLink && (\n <AuthTextLink href={loginLink} action=\"login\">\n {t('forgotPasswordForm.back')}\n </AuthTextLink>\n )}\n </Card.Body>\n </Card>\n </div>\n );\n },\n);\nForgotPasswordForm.displayName = 'ForgotPasswordForm';\n"],"names":["FORGOT_PASSWORD_FORM_FIELDS","forgotPasswordFormVariants","cva","confirmationVariants","FIELDS","CONFIRMATION_HEADING","hasMessage","errors","focusTargetFor","messages","ForgotPasswordForm","forwardRef","title","description","headingLevel","showLogo","logoMotion","children","initialValues","sent","loginHref","submitLabel","submitting","onSubmit","fieldNames","action","hiddenFields","className","rest","ref","useTranslation","titleId","useId","sentTitleId","sentDescriptionId","emailFieldId","formMessageId","native","email","setEmail","useState","touched","setTouched","submitted","setSubmitted","serverErrors","setServerErrors","emailInput","useRef","emailField","useStateBackedInput","held","value","_a","invalid","isEmail","ownError","emailError","formMessage","confirmation","openedWithEmailMessage","summary","useErrorSummary","formMessageAnnounce","useFormMessageAnnounce","focusRequest","setFocusRequest","target","useEffect","wasSent","submit","useRegistrationSubmit","answer","next","busy","formAction","useFormAction","loginLink","isSafeHref","extraFields","useExtraFields","jsx","Card","AuthCardHeading","jsxs","Alert","Fragment","ErrorSummary","field","AuthFormMessage","FormField","EmailInput","event","previous","ExtraFields","Button","AuthFormStatus","AuthTextLink"],"mappings":"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"}
1
+ {"version":3,"file":"forgot-password-form-BGPc8TGr.js","sources":["../../src/patterns/forgot-password-form/forgot-password-form.tsx"],"sourcesContent":["/* -------------------------------------------------------------------- */\n/* ForgotPasswordForm — the card that asks for a password reset link. */\n/* */\n/* One email field and a button, then a confirmation. The confirmation */\n/* is vague on purpose: it never says whether an account exists for the */\n/* address, and never repeats the address. The host sends the email and */\n/* owns every server rule; the card checks what a browser can check, */\n/* then hands the email to `onSubmit` or posts a real form. */\n/* -------------------------------------------------------------------- */\n\nimport {\n forwardRef,\n useEffect,\n useId,\n useRef,\n useState,\n type HTMLAttributes,\n type ReactNode,\n} from 'react';\nimport { useTranslation } from 'react-i18next';\nimport { cva } from 'class-variance-authority';\n// The validator EmailInput itself uses, so the two never disagree.\nimport isEmail from 'validator/lib/isEmail';\nimport { Alert } from '../../components/alert';\nimport { Button } from '../../components/button';\nimport { Card } from '../../components/card';\nimport { EmailInput } from '../../components/email-input';\nimport { ErrorSummary } from '../../components/error-summary';\nimport { FormField } from '../../components/form-field';\nimport { isSafeHref } from '../../components/_shared/safe-href';\nimport { useStateBackedInput } from '../../components/_shared/use-state-backed-input';\nimport {\n AuthCardHeading,\n AuthFormMessage,\n AuthFormStatus,\n AuthTextLink,\n ExtraFields,\n useExtraFields,\n useFormAction,\n} from '../_shared/auth-form-parts';\nimport type { BrandMarkMotion } from '../_shared/brand-mark';\nimport {\n useErrorSummary,\n useFormMessageAnnounce,\n} from '../_shared/use-error-summary';\nimport { useRegistrationSubmit } from '../registration-form/use-registration-submit';\n\n/* -------------------------------------------------------------------- */\n/* Types */\n/* -------------------------------------------------------------------- */\n\n/** The form's one field. */\nexport const FORGOT_PASSWORD_FORM_FIELDS = ['email'] as const;\n\nexport type ForgotPasswordFormField =\n (typeof FORGOT_PASSWORD_FORM_FIELDS)[number];\n\n/** What `onSubmit` receives once the email has passed its check. */\nexport interface ForgotPasswordFormValues {\n email: string;\n}\n\n/** The email to show when the form opens. */\nexport type ForgotPasswordFormInitialValues = Partial<ForgotPasswordFormValues>;\n\n/** Messages from the server, already translated: one for the email,\n * plus `form` for a message about the whole form, such as \"too many\n * requests, try again later\". */\nexport type ForgotPasswordFormErrors = Partial<\n Record<ForgotPasswordFormField | 'form', string>\n>;\n\n/** The host's own form field name, for a native form post. It is needed:\n * a field without a name is not posted. */\nexport type ForgotPasswordFormFieldNames = Record<\n ForgotPasswordFormField,\n string\n>;\n\nexport interface ForgotPasswordFormProps extends Omit<\n HTMLAttributes<HTMLDivElement>,\n 'title' | 'onSubmit'\n> {\n /** Replaces the heading \"Reset your password\". Pass it translated. */\n title?: string;\n /**\n * Replaces the line under the heading (\"Enter the email linked to your\n * account and we will send a reset link.\"). Pass it translated.\n */\n description?: string;\n /**\n * The heading's level. Default 1: this page usually has no other\n * heading. Pass 2 on a page that has its own `h1`.\n */\n headingLevel?: 1 | 2 | 3;\n /**\n * Shows the Alfadocs mark above the heading, with the heading and its\n * line centred under it. Default true.\n */\n showLogo?: boolean;\n /** How the mark arrives when the card opens. Default `assemble`. It\n * plays once and rests. */\n logoMotion?: BrandMarkMotion;\n /**\n * Host content between the heading and the field, such as a notice. It\n * sits outside the form, so nothing in it is posted.\n */\n children?: ReactNode;\n /** The email to show when the form opens. Read once. */\n initialValues?: ForgotPasswordFormInitialValues;\n /**\n * Messages from the server, already translated. The email's message\n * shows on the field until the user changes it; `form` shows above the\n * field. When messages arrive, focus moves to the error summary at the\n * top of the form when the email has a message, or to the message\n * above the field when only that says something.\n *\n * Do not use a message to say that no account has the address: that\n * tells a stranger who has an account. Show `sent` for every address.\n */\n errors?: ForgotPasswordFormErrors;\n /**\n * Shows the confirmation in place of the form: \"Check your inbox\". Set\n * it once the request has been accepted, whether or not an account\n * exists for the address. The confirmation says \"if an account exists\"\n * and never shows the address.\n */\n sent?: boolean;\n /**\n * Adds \"Back to sign in\" under the card's content. A path on the same\n * site, or an address written in full. Anything else shows no link.\n */\n loginHref?: string;\n /** Replaces the button's \"Send reset link\". Pass it translated. */\n submitLabel?: string;\n /**\n * True while the host sends the email: the button shows a spinner and\n * a second submit is ignored. Set it inside `onSubmit`, before anything\n * is awaited: until it is true, a second press sends a second request.\n */\n submitting?: boolean;\n /** For React hosts: called with the email once it has passed its\n * check. Not called when `fieldNames` is set. */\n onSubmit?: (values: ForgotPasswordFormValues) => void;\n /**\n * For a native form post: the host's own field name, e.g.\n * `{ email: 'email' }`. When set, the input carries that name and the\n * browser posts the form.\n */\n fieldNames?: ForgotPasswordFormFieldNames;\n /**\n * Where a native form post goes. Default: the current address. Only a\n * path, or an address on the page's own origin, is accepted: another\n * origin is never sent what the user typed, and the post then goes to\n * the current address. Does nothing without `fieldNames`.\n */\n action?: string;\n /**\n * Extra hidden inputs posted with a native form, by name: a CSRF\n * token. Not used with `onSubmit`. A name that is also the one in\n * `fieldNames` is left out: the form's own field wins.\n */\n hiddenFields?: Readonly<Record<string, string>>;\n}\n\n/* -------------------------------------------------------------------- */\n/* Styles */\n/* -------------------------------------------------------------------- */\n\n// 400px at most, in px: a host whose root font size is not 16px would\n// stretch or squeeze a width in rem.\nconst forgotPasswordFormVariants = cva(\n 'ds:mx-auto ds:w-full ds:max-w-[400px] ds:text-start ds:text-[var(--foreground)]',\n);\n\n// The confirmation takes focus when it appears, so it shows a ring.\nconst confirmationVariants = cva(\n 'ds:focus-visible:outline-solid ds:focus-visible:outline-[length:var(--focus-ring-width)] ds:focus-visible:outline-[var(--ring)] ds:focus-visible:outline-offset-[length:var(--focus-ring-offset)] ds:forced-colors:focus-visible:outline-[CanvasText]',\n);\n\n/* -------------------------------------------------------------------- */\n/* Helpers */\n/* -------------------------------------------------------------------- */\n\ntype FocusTarget = 'email' | 'form';\n\n/** The form's one field, as the error summary lists it. */\nconst FIELDS = ['email'] as const;\n\n/** The confirmation's heading, by the level of the card's own. */\nconst CONFIRMATION_HEADING = { 1: 'h2', 2: 'h3', 3: 'h4' } as const;\n\nconst hasMessage = (errors: ForgotPasswordFormErrors): boolean =>\n Object.values(errors).some(Boolean);\n\nconst focusTargetFor = (\n messages: ForgotPasswordFormErrors,\n): FocusTarget | undefined =>\n messages.email ? 'email' : messages.form ? 'form' : undefined;\n\n/* -------------------------------------------------------------------- */\n/* Component */\n/* -------------------------------------------------------------------- */\n\n/**\n * The card that asks for a password reset link. Mount it on the\n * forgot-password page, inside `SignedOutFrame` or the host's own chrome.\n */\nexport const ForgotPasswordForm = forwardRef<\n HTMLDivElement,\n ForgotPasswordFormProps\n>(\n (\n {\n title,\n description,\n headingLevel = 1,\n showLogo = true,\n logoMotion = 'assemble',\n children,\n initialValues,\n errors,\n sent = false,\n loginHref,\n submitLabel,\n submitting = false,\n onSubmit,\n fieldNames,\n action,\n hiddenFields,\n className,\n ...rest\n },\n ref,\n ) => {\n const { t } = useTranslation('ui');\n const titleId = useId();\n const sentTitleId = useId();\n const sentDescriptionId = useId();\n const emailFieldId = useId();\n const formMessageId = useId();\n const native = fieldNames !== undefined;\n\n /* ------------------------------ State ----------------------------- */\n\n const [email, setEmail] = useState(() => initialValues?.email ?? '');\n // True once the user has filled in the field and left it. An email\n // that arrives filled in counts: a bad one is then flagged at once,\n // with its message. An empty field is not flagged for being passed\n // through; its message waits for a submit.\n const [touched, setTouched] = useState(() => Boolean(initialValues?.email));\n const [submitted, setSubmitted] = useState(false);\n const [serverErrors, setServerErrors] = useState<ForgotPasswordFormErrors>(\n () => errors ?? {},\n );\n\n const emailInput = useRef<HTMLInputElement>(null);\n // The address stays out of the input's `value` attribute, where a\n // copied outerHTML or a session replay could read it.\n const emailField = useStateBackedInput(email, emailInput);\n /**\n * What the input holds now. Read from the input, not from state: a\n * browser can fill the field without telling React, and the form must\n * not refuse a field that is visibly filled.\n */\n const held = (): string => {\n const value = emailInput.current?.value ?? email;\n if (value !== email) setEmail(value);\n return value;\n };\n\n /* ---------------------------- Validation -------------------------- */\n\n const invalid = !isEmail(email);\n // The message shows once the user has left the field or tried to\n // submit.\n const ownError =\n (submitted || touched) && invalid\n ? email === ''\n ? t('forgotPasswordForm.emailRequired')\n : t('inputs.email.invalid')\n : undefined;\n const emailError = serverErrors.email ?? ownError;\n\n /* ------------------------------ Focus ----------------------------- */\n\n const formMessage = useRef<HTMLDivElement>(null);\n const confirmation = useRef<HTMLDivElement>(null);\n\n // The email's message after a refused submit, or after a server\n // answer, is listed at the top of the form, and focus goes there. A\n // page the server rendered after a failed post opens that way too: it\n // announces nothing itself.\n const [openedWithEmailMessage] = useState(() => Boolean(errors?.email));\n const summary = useErrorSummary(\n FIELDS,\n () => emailError,\n openedWithEmailMessage,\n );\n // The message about the whole form: quiet while the summary lists the\n // email, because the summary reads it then.\n const formMessageAnnounce = useFormMessageAnnounce(\n serverErrors.form,\n summary.listed.length > 0,\n );\n\n // Any other place focus goes: the message above the field, when only\n // it says something. A new object for each request, so asking twice\n // for the same place moves focus twice.\n const [focusRequest, setFocusRequest] = useState<{\n target: FocusTarget;\n } | null>(() => {\n const target = openedWithEmailMessage\n ? undefined\n : focusTargetFor(errors ?? {});\n return target ? { target } : null;\n });\n useEffect(() => {\n if (!focusRequest) return;\n (focusRequest.target === 'email'\n ? emailInput\n : formMessage\n ).current?.focus();\n }, [focusRequest]);\n\n // The form, and the button that had focus, are gone once the\n // confirmation shows. Move focus to it, so a keyboard or screen\n // reader user lands on what happened. Not on a page that opens\n // already sent: that one is read from the top like any other.\n const wasSent = useRef(sent);\n useEffect(() => {\n if (sent && !wasSent.current) confirmation.current?.focus();\n wasSent.current = sent;\n }, [sent]);\n\n /* ------------------------------ Submit ---------------------------- */\n\n const submit = useRegistrationSubmit<'email'>({\n errors,\n submitting,\n native,\n captcha: false,\n firstProblem: () => (isEmail(held()) ? undefined : 'email'),\n onRefused: () => {\n setSubmitted(true);\n summary.open(() => setFocusRequest({ target: 'email' }));\n },\n // Only a React host gets here: a native post is the browser's own.\n deliver: () => onSubmit?.({ email: held() }),\n onAnswer: (answer) => {\n const next: ForgotPasswordFormErrors = answer;\n setServerErrors(next);\n if (!hasMessage(next)) return;\n setSubmitted(false);\n // The summary when the email has a message, else the message above.\n summary.open(() => {\n const target = focusTargetFor(next);\n if (target) setFocusRequest({ target });\n });\n },\n });\n const { busy } = submit;\n\n /* ------------------------------ Render ---------------------------- */\n\n const formAction = useFormAction('ForgotPasswordForm', native, action);\n const loginLink =\n loginHref && isSafeHref(loginHref) ? loginHref : undefined;\n const extraFields = useExtraFields(\n 'ForgotPasswordForm',\n native,\n fieldNames,\n hiddenFields,\n );\n\n return (\n <div\n ref={ref}\n {...rest}\n data-component=\"forgot-password-form\"\n data-state={sent ? 'sent' : 'asking'}\n className={forgotPasswordFormVariants({ className })}\n >\n <Card variant=\"elevated\">\n <Card.Body className=\"ds:flex ds:flex-col ds:gap-[var(--spacing-lg)]\">\n <AuthCardHeading\n id={titleId}\n title={title ?? t('forgotPasswordForm.title')}\n // The instruction belongs to the form: once it is sent there\n // is nothing left to enter.\n description={\n sent\n ? undefined\n : (description ?? t('forgotPasswordForm.description'))\n }\n level={headingLevel}\n showLogo={showLogo}\n logoMotion={logoMotion}\n />\n\n {children ? (\n <div\n data-slot=\"host-content\"\n className=\"ds:flex ds:flex-col ds:gap-[var(--spacing-md)]\"\n >\n {children}\n </div>\n ) : null}\n\n {sent ? (\n // Named and described, so a screen reader that lands here\n // reads the whole confirmation, not only \"status\".\n <Alert\n ref={confirmation}\n variant=\"success\"\n tabIndex={-1}\n aria-labelledby={sentTitleId}\n aria-describedby={sentDescriptionId}\n data-slot=\"confirmation\"\n className={confirmationVariants()}\n >\n {/* One level under the card's own heading. */}\n <Alert.Title\n id={sentTitleId}\n as={CONFIRMATION_HEADING[headingLevel]}\n >\n {t('forgotPasswordForm.sentTitle')}\n </Alert.Title>\n <Alert.Description id={sentDescriptionId}>\n {t('forgotPasswordForm.sentDescription')}\n </Alert.Description>\n </Alert>\n ) : (\n <>\n <form\n noValidate\n method=\"post\"\n action={formAction}\n aria-labelledby={titleId}\n aria-busy={busy || undefined}\n onSubmit={submit.handleSubmit}\n className=\"ds:flex ds:flex-col ds:gap-[var(--spacing-md)]\"\n >\n <ErrorSummary\n ref={summary.ref}\n headingLevel={(headingLevel + 1) as 2 | 3 | 4}\n // The message about the whole form is read with it.\n aria-describedby={\n serverErrors.form ? formMessageId : undefined\n }\n errors={summary.listed.map((field) => ({\n field,\n message: emailError ?? '',\n targetId: emailFieldId,\n }))}\n />\n\n {serverErrors.form && (\n <AuthFormMessage\n ref={formMessage}\n id={formMessageId}\n announce={formMessageAnnounce}\n >\n {serverErrors.form}\n </AuthFormMessage>\n )}\n\n <FormField\n id={emailFieldId}\n label={t('forgotPasswordForm.email')}\n required\n error={emailError}\n // Quiet while the summary lists it: the summary reads it.\n announceError={!summary.lists('email')}\n data-field=\"email\"\n >\n {/* One check, above, drives the message, the red mark and\n aria-invalid through FormField. EmailInput's own check\n is off so the two cannot disagree. */}\n <EmailInput\n ref={emailField.ref}\n name={native ? fieldNames.email : 'email'}\n disableBuiltInValidation\n onChange={emailField.quiet((event) => {\n setEmail(event.target.value);\n // The server's message was about the old value.\n setServerErrors((previous) =>\n previous.email\n ? { ...previous, email: undefined }\n : previous,\n );\n })}\n onBlur={(event) => {\n // Whatever a browser filled in without saying so.\n setEmail(event.target.value);\n if (event.target.value !== '') setTouched(true);\n }}\n />\n </FormField>\n\n <ExtraFields fields={extraFields} />\n\n <Button\n type=\"submit\"\n intent=\"primary\"\n size=\"lg\"\n loading={busy}\n className=\"ds:w-full\"\n data-action=\"submit\"\n >\n {submitLabel ?? t('forgotPasswordForm.submit')}\n </Button>\n </form>\n {/* Outside the form: a screen reader may hold back what\n changes inside a busy region. */}\n <AuthFormStatus busy={busy} />\n </>\n )}\n\n {loginLink && (\n <AuthTextLink href={loginLink} action=\"login\">\n {t('forgotPasswordForm.back')}\n </AuthTextLink>\n )}\n </Card.Body>\n </Card>\n </div>\n );\n },\n);\nForgotPasswordForm.displayName = 'ForgotPasswordForm';\n"],"names":["FORGOT_PASSWORD_FORM_FIELDS","forgotPasswordFormVariants","cva","confirmationVariants","FIELDS","CONFIRMATION_HEADING","hasMessage","errors","focusTargetFor","messages","ForgotPasswordForm","forwardRef","title","description","headingLevel","showLogo","logoMotion","children","initialValues","sent","loginHref","submitLabel","submitting","onSubmit","fieldNames","action","hiddenFields","className","rest","ref","useTranslation","titleId","useId","sentTitleId","sentDescriptionId","emailFieldId","formMessageId","native","email","setEmail","useState","touched","setTouched","submitted","setSubmitted","serverErrors","setServerErrors","emailInput","useRef","emailField","useStateBackedInput","held","value","_a","invalid","isEmail","ownError","emailError","formMessage","confirmation","openedWithEmailMessage","summary","useErrorSummary","formMessageAnnounce","useFormMessageAnnounce","focusRequest","setFocusRequest","target","useEffect","wasSent","submit","useRegistrationSubmit","answer","next","busy","formAction","useFormAction","loginLink","isSafeHref","extraFields","useExtraFields","jsx","Card","AuthCardHeading","jsxs","Alert","Fragment","ErrorSummary","field","AuthFormMessage","FormField","EmailInput","event","previous","ExtraFields","Button","AuthFormStatus","AuthTextLink"],"mappings":"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"}
@@ -0,0 +1,332 @@
1
+ import { jsxs as m, jsx as e } from "react/jsx-runtime";
2
+ import { forwardRef as cs, useId as N, useState as n, useRef as os, useEffect as ks } from "react";
3
+ import { useTranslation as Bs } from "react-i18next";
4
+ import { c as E } from "./index-D2ZczOXr.js";
5
+ import { i as ds, E as Rs } from "./email-input-C7THAwh2.js";
6
+ import { B as ms } from "./button-D-MQxsA_.js";
7
+ import { C as v } from "./card-BA4Ig_kn.js";
8
+ import { E as As } from "./error-summary-mGxIyDAY.js";
9
+ import { F as ls } from "./form-field-Ani72sVY.js";
10
+ import { P as Cs } from "./password-input-BokZ_rnT.js";
11
+ import { i as us } from "./safe-href-5dbsfbQn.js";
12
+ import { u as Ms } from "./use-state-backed-input-DhRQo6gM.js";
13
+ import { u as Ss, a as qs, A as Ps, b as js, d as D, E as zs, c as Hs } from "./auth-form-parts-DHv6LVV2.js";
14
+ import { u as Ds, b as Os, a as Ts } from "./use-registration-submit-eWAECgUC.js";
15
+ import { M as $s } from "./matrix-rain-sDOUUwZp.js";
16
+ import { a as _s } from "./error-page-illustration-BdJLuMdV.js";
17
+ const T = ["email", "password"], Gs = E(
18
+ "ds:flex ds:w-full ds:flex-col ds:items-center ds:justify-center ds:gap-[var(--spacing-lg)] ds:text-start ds:text-[var(--foreground)] ds:lg:flex-row ds:lg:items-stretch"
19
+ ), is = E("ds:w-full ds:max-w-[400px]"), Ws = E(
20
+ "ds:flex ds:flex-wrap ds:justify-center ds:gap-x-[var(--spacing-lg)] ds:gap-y-[var(--spacing-lg)]"
21
+ ), O = (a) => a && us(a) ? a : void 0, Js = (a) => Object.values(a).some(Boolean), Ks = (a) => T.some((l) => !!a[l]), ns = (a) => T.find((l) => a[l]) ?? (a.form ? "password" : void 0), Qs = cs(
22
+ ({
23
+ title: a,
24
+ description: l,
25
+ headingLevel: h = 1,
26
+ showLogo: I = !0,
27
+ logoMotion: L = "assemble",
28
+ children: w,
29
+ initialValues: o,
30
+ errors: c,
31
+ forgotPasswordHref: k,
32
+ registerDoctorHref: d,
33
+ registerPatientHref: B,
34
+ submitLabel: fs,
35
+ submitting: gs = !1,
36
+ onSubmit: R,
37
+ fieldNames: x,
38
+ action: ps,
39
+ hiddenFields: vs,
40
+ devCard: $,
41
+ className: hs,
42
+ ...ws
43
+ }, xs) => {
44
+ const { t } = Bs("ui"), _ = N(), A = N(), Fs = N(), C = (s) => `${Fs}-${s}`, u = x !== void 0, [f, M] = n(() => (o == null ? void 0 : o.email) ?? ""), [F, b] = n(""), [bs, G] = n(() => ({ email: !!(o != null && o.email) })), [ys, W] = n(!1), [J, K] = n(
45
+ () => c ?? {}
46
+ ), Q = (s) => G(
47
+ (r) => r[s] ? r : { ...r, [s]: !0 }
48
+ ), U = (s) => K(
49
+ (r) => r[s] ? { ...r, [s]: void 0 } : r
50
+ ), S = os(null), q = os(null), X = Ms(f, S), Y = () => {
51
+ b(""), G((s) => ({ ...s, password: !1 }));
52
+ }, Z = () => {
53
+ var r, p;
54
+ const s = {
55
+ email: ((r = S.current) == null ? void 0 : r.value) ?? f,
56
+ password: ((p = q.current) == null ? void 0 : p.value) ?? F
57
+ };
58
+ return s.email !== f && M(s.email), s.password !== F && b(s.password), s;
59
+ }, V = {
60
+ email: !ds(f),
61
+ password: F === ""
62
+ }, ss = (s) => ys || bs[s] === !0, Ns = {
63
+ email: ss("email") && V.email ? t(f === "" ? "loginForm.emailRequired" : "inputs.email.invalid") : void 0,
64
+ password: ss("password") && V.password ? t("loginForm.passwordRequired") : void 0
65
+ }, y = (s) => J[s] ?? Ns[s], [es] = n(
66
+ () => Ks(c ?? {})
67
+ ), i = Ds(
68
+ T,
69
+ y,
70
+ es
71
+ ), [P, as] = n(() => {
72
+ const s = es ? void 0 : ns(c ?? {});
73
+ return s ? { target: s } : null;
74
+ });
75
+ ks(() => {
76
+ var s;
77
+ P && ((s = (P.target === "email" ? S : q).current) == null || s.focus());
78
+ }, [P]);
79
+ const rs = Os({
80
+ errors: c,
81
+ submitting: gs,
82
+ native: u,
83
+ captcha: !1,
84
+ firstProblem: () => {
85
+ const s = Z();
86
+ return ds(s.email) ? s.password === "" ? "password" : void 0 : "email";
87
+ },
88
+ onRefused: (s) => {
89
+ W(!0), i.open(() => as({ target: s }));
90
+ },
91
+ // Only a React host gets here: a native post is the browser's own.
92
+ deliver: () => R == null ? void 0 : R(Z()),
93
+ onAnswer: (s) => {
94
+ const r = s;
95
+ K(r), Js(r) && (Y(), W(!1), i.open(() => {
96
+ const p = ns(r);
97
+ p && as({ target: p });
98
+ }));
99
+ },
100
+ // The page came back from the browser's cache as it was left: the
101
+ // password must not come back with it.
102
+ onRestore: Y
103
+ }), { busy: j } = rs, Es = Ss("LoginForm", u, ps), ts = O(k), z = O(d), H = O(B), Is = qs(
104
+ "LoginForm",
105
+ u,
106
+ x,
107
+ vs
108
+ ), g = J.form, Ls = Ts(
109
+ g,
110
+ i.listed.length > 0
111
+ );
112
+ return /* @__PURE__ */ m(
113
+ "div",
114
+ {
115
+ ref: xs,
116
+ ...ws,
117
+ "data-component": "login-form",
118
+ className: Gs({ className: hs }),
119
+ children: [
120
+ /* @__PURE__ */ e("div", { className: is(), children: /* @__PURE__ */ e(v, { variant: "elevated", className: "ds:h-full", children: /* @__PURE__ */ m(v.Body, { className: "ds:flex ds:flex-col ds:gap-[var(--spacing-lg)]", children: [
121
+ /* @__PURE__ */ e(
122
+ Ps,
123
+ {
124
+ id: _,
125
+ title: a ?? t("loginForm.title"),
126
+ description: l,
127
+ level: h,
128
+ showLogo: I,
129
+ logoMotion: L
130
+ }
131
+ ),
132
+ w ? /* @__PURE__ */ e(
133
+ "div",
134
+ {
135
+ "data-slot": "host-content",
136
+ className: "ds:flex ds:flex-col ds:gap-[var(--spacing-md)]",
137
+ children: w
138
+ }
139
+ ) : null,
140
+ /* @__PURE__ */ m(
141
+ "form",
142
+ {
143
+ noValidate: !0,
144
+ method: "post",
145
+ action: Es,
146
+ "aria-labelledby": _,
147
+ "aria-busy": j || void 0,
148
+ onSubmit: rs.handleSubmit,
149
+ className: "ds:flex ds:flex-col ds:gap-[var(--spacing-md)]",
150
+ children: [
151
+ /* @__PURE__ */ e(
152
+ As,
153
+ {
154
+ ref: i.ref,
155
+ headingLevel: h + 1,
156
+ "aria-describedby": g ? A : void 0,
157
+ errors: i.listed.map((s) => ({
158
+ field: s,
159
+ message: y(s) ?? "",
160
+ targetId: C(s)
161
+ }))
162
+ }
163
+ ),
164
+ g && /* @__PURE__ */ e(
165
+ js,
166
+ {
167
+ id: A,
168
+ announce: Ls,
169
+ children: g
170
+ }
171
+ ),
172
+ /* @__PURE__ */ e(
173
+ ls,
174
+ {
175
+ id: C("email"),
176
+ label: t("loginForm.email"),
177
+ required: !0,
178
+ error: y("email"),
179
+ announceError: !i.lists("email"),
180
+ "data-field": "email",
181
+ children: /* @__PURE__ */ e(
182
+ Rs,
183
+ {
184
+ ref: X.ref,
185
+ name: u ? x.email : "email",
186
+ autoComplete: "username",
187
+ disableBuiltInValidation: !0,
188
+ onChange: X.quiet((s) => {
189
+ M(s.target.value), U("email");
190
+ }),
191
+ onBlur: (s) => {
192
+ M(s.target.value), s.target.value !== "" && Q("email");
193
+ }
194
+ }
195
+ )
196
+ }
197
+ ),
198
+ /* @__PURE__ */ e(
199
+ ls,
200
+ {
201
+ id: C("password"),
202
+ label: t("loginForm.password"),
203
+ required: !0,
204
+ error: y("password"),
205
+ announceError: !i.lists("password"),
206
+ "data-field": "password",
207
+ children: /* @__PURE__ */ e(
208
+ Cs,
209
+ {
210
+ ref: q,
211
+ name: u ? x.password : "password",
212
+ autoComplete: "current-password",
213
+ "aria-describedby": g ? A : void 0,
214
+ value: F,
215
+ onChange: (s) => {
216
+ b(s.target.value), U("password");
217
+ },
218
+ onBlur: (s) => {
219
+ b(s.target.value), s.target.value !== "" && Q("password");
220
+ }
221
+ }
222
+ )
223
+ }
224
+ ),
225
+ ts && /* @__PURE__ */ e(
226
+ D,
227
+ {
228
+ href: ts,
229
+ action: "forgot-password",
230
+ align: "end",
231
+ children: t("loginForm.forgot")
232
+ }
233
+ ),
234
+ /* @__PURE__ */ e(zs, { fields: Is }),
235
+ /* @__PURE__ */ e(
236
+ ms,
237
+ {
238
+ type: "submit",
239
+ intent: "primary",
240
+ size: "lg",
241
+ loading: j,
242
+ className: "ds:w-full",
243
+ "data-action": "submit",
244
+ children: fs ?? t("loginForm.submit")
245
+ }
246
+ )
247
+ ]
248
+ }
249
+ ),
250
+ (z || H) && /* @__PURE__ */ m(
251
+ "div",
252
+ {
253
+ "data-slot": "register-links",
254
+ className: Ws(),
255
+ children: [
256
+ z && /* @__PURE__ */ e(D, { href: z, action: "register-doctor", children: t("signedOutHeader.registerDoctor") }),
257
+ H && /* @__PURE__ */ e(D, { href: H, action: "register-patient", children: t("signedOutHeader.registerPatient") })
258
+ ]
259
+ }
260
+ ),
261
+ /* @__PURE__ */ e(Hs, { busy: j })
262
+ ] }) }) }),
263
+ $ ? /* @__PURE__ */ e("div", { className: is(), children: $ }) : null
264
+ ]
265
+ }
266
+ );
267
+ }
268
+ );
269
+ Qs.displayName = "LoginForm";
270
+ const Us = E("ds:h-full ds:w-full"), Xs = cs(
271
+ ({ title: a, options: l, headingLevel: h = 2, className: I, ...L }, w) => {
272
+ const o = N(), c = `h${h}`, k = l.filter((d) => us(d.href));
273
+ return /* @__PURE__ */ e(
274
+ "div",
275
+ {
276
+ ref: w,
277
+ ...L,
278
+ "data-component": "login-dev-card",
279
+ className: Us({ className: I }),
280
+ children: /* @__PURE__ */ m(
281
+ v,
282
+ {
283
+ variant: "elevated",
284
+ "aria-labelledby": o,
285
+ className: "ds:relative ds:isolate ds:flex ds:h-full ds:flex-col ds:overflow-hidden",
286
+ children: [
287
+ /* @__PURE__ */ e("div", { className: "ds:absolute ds:inset-0 ds:z-0 ds:[display:var(--accessible-off,block)]", children: /* @__PURE__ */ e(
288
+ $s,
289
+ {
290
+ glyphs: "hex",
291
+ decay: "fast",
292
+ mask: "center",
293
+ maskRadius: "xl",
294
+ className: "ds:size-full"
295
+ }
296
+ ) }),
297
+ /* @__PURE__ */ e(v.Header, { className: "ds:relative ds:z-[1]", children: /* @__PURE__ */ m("div", { className: "ds:flex ds:flex-col ds:items-center ds:gap-[var(--spacing-sm)] ds:text-center", children: [
298
+ /* @__PURE__ */ e("div", { className: "ds:[&>div]:w-24", children: /* @__PURE__ */ e(_s, { mood: "melt" }) }),
299
+ /* @__PURE__ */ e(
300
+ c,
301
+ {
302
+ id: o,
303
+ className: "type-title-card ds:text-[var(--foreground)]",
304
+ children: a
305
+ }
306
+ )
307
+ ] }) }),
308
+ /* @__PURE__ */ e(v.Body, { className: "ds:relative ds:z-[1] ds:flex ds:flex-1 ds:flex-col ds:justify-center", children: /* @__PURE__ */ e("div", { className: "ds:mt-[var(--spacing-md)] ds:flex ds:flex-col ds:gap-[var(--spacing-xs)]", children: k.map((d, B) => /* @__PURE__ */ e(
309
+ ms,
310
+ {
311
+ asChild: !0,
312
+ intent: "primary",
313
+ startIcon: d.icon,
314
+ className: "ds:min-h-[var(--min-target-size)] ds:w-full",
315
+ children: /* @__PURE__ */ e("a", { href: d.href, "data-action": "dev-login", children: d.label })
316
+ },
317
+ `${B}:${d.href}`
318
+ )) }) })
319
+ ]
320
+ }
321
+ )
322
+ }
323
+ );
324
+ }
325
+ );
326
+ Xs.displayName = "LoginDevCard";
327
+ export {
328
+ T as L,
329
+ Xs as a,
330
+ Qs as b
331
+ };
332
+ //# sourceMappingURL=login-dev-card-CWpoTuVY.js.map