@brandlab-digital/site-next 3.8.0 → 3.10.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.
- package/README.md +33 -0
- package/dist/contact/attachments-client.d.ts +3 -0
- package/dist/contact/attachments-client.d.ts.map +1 -1
- package/dist/contact/attachments-client.js +31 -8
- package/dist/contact/attachments-client.js.map +1 -1
- package/dist/contact/client-entry.d.ts +1 -1
- package/dist/contact/client-entry.d.ts.map +1 -1
- package/dist/contact/client-entry.js.map +1 -1
- package/dist/contact/contract.d.ts.map +1 -1
- package/dist/contact/contract.js +2 -2
- package/dist/contact/contract.js.map +1 -1
- package/dist/contact/email.d.ts.map +1 -1
- package/dist/contact/email.js +9 -0
- package/dist/contact/email.js.map +1 -1
- package/dist/contact/form-client.d.ts +4 -2
- package/dist/contact/form-client.d.ts.map +1 -1
- package/dist/contact/form-client.js +49 -13
- package/dist/contact/form-client.js.map +1 -1
- package/dist/contact/rate-limit.d.ts +59 -0
- package/dist/contact/rate-limit.d.ts.map +1 -0
- package/dist/contact/rate-limit.js +109 -0
- package/dist/contact/rate-limit.js.map +1 -0
- package/dist/contact/route-handler.d.ts +6 -0
- package/dist/contact/route-handler.d.ts.map +1 -1
- package/dist/contact/route-handler.js +4 -0
- package/dist/contact/route-handler.js.map +1 -1
- package/dist/contact/server-entry.d.ts +1 -1
- package/dist/contact/server-entry.d.ts.map +1 -1
- package/dist/contact/server-entry.js +1 -1
- package/dist/contact/server-entry.js.map +1 -1
- package/dist/contact/server.d.ts +9 -0
- package/dist/contact/server.d.ts.map +1 -1
- package/dist/contact/server.js +71 -7
- package/dist/contact/server.js.map +1 -1
- package/dist/contact/shared.d.ts +11 -0
- package/dist/contact/shared.d.ts.map +1 -1
- package/dist/contact/shared.js +11 -0
- package/dist/contact/shared.js.map +1 -1
- package/dist/contact/testing.d.ts +5 -0
- package/dist/contact/testing.d.ts.map +1 -1
- package/dist/contact/testing.js +1 -0
- package/dist/contact/testing.js.map +1 -1
- package/dist/instrumentation-client.d.ts +23 -4
- package/dist/instrumentation-client.d.ts.map +1 -1
- package/dist/instrumentation-client.js +65 -3
- package/dist/instrumentation-client.js.map +1 -1
- package/dist/lead-reporting.d.ts +6 -0
- package/dist/lead-reporting.d.ts.map +1 -1
- package/dist/lead-reporting.js +13 -1
- package/dist/lead-reporting.js.map +1 -1
- package/dist/navigation/constants.d.ts +25 -0
- package/dist/navigation/constants.d.ts.map +1 -0
- package/dist/navigation/constants.js +30 -0
- package/dist/navigation/constants.js.map +1 -0
- package/dist/navigation/index.d.ts +11 -0
- package/dist/navigation/index.d.ts.map +1 -0
- package/dist/navigation/index.js +11 -0
- package/dist/navigation/index.js.map +1 -0
- package/dist/navigation/use-back-to-top.d.ts +5 -0
- package/dist/navigation/use-back-to-top.d.ts.map +1 -0
- package/dist/navigation/use-back-to-top.js +32 -0
- package/dist/navigation/use-back-to-top.js.map +1 -0
- package/dist/navigation/use-header-metrics.d.ts +17 -0
- package/dist/navigation/use-header-metrics.d.ts.map +1 -0
- package/dist/navigation/use-header-metrics.js +52 -0
- package/dist/navigation/use-header-metrics.js.map +1 -0
- package/dist/navigation/use-navigation-menu.d.ts +39 -0
- package/dist/navigation/use-navigation-menu.d.ts.map +1 -0
- package/dist/navigation/use-navigation-menu.js +206 -0
- package/dist/navigation/use-navigation-menu.js.map +1 -0
- package/dist/navigation/use-sticky-call-bar.d.ts +15 -0
- package/dist/navigation/use-sticky-call-bar.d.ts.map +1 -0
- package/dist/navigation/use-sticky-call-bar.js +110 -0
- package/dist/navigation/use-sticky-call-bar.js.map +1 -0
- package/dist/ui-kit/brandlab-skin.d.ts +22 -0
- package/dist/ui-kit/brandlab-skin.d.ts.map +1 -1
- package/dist/ui-kit/brandlab-skin.js.map +1 -1
- package/dist/ui-kit/form/copy.d.ts +3 -0
- package/dist/ui-kit/form/copy.d.ts.map +1 -0
- package/dist/ui-kit/form/copy.js +5 -0
- package/dist/ui-kit/form/copy.js.map +1 -0
- package/dist/ui-kit/form/dialog.d.ts +24 -0
- package/dist/ui-kit/form/dialog.d.ts.map +1 -0
- package/dist/ui-kit/form/dialog.js +17 -0
- package/dist/ui-kit/form/dialog.js.map +1 -0
- package/dist/ui-kit/form/field.d.ts +30 -0
- package/dist/ui-kit/form/field.d.ts.map +1 -0
- package/dist/ui-kit/form/field.js +11 -0
- package/dist/ui-kit/form/field.js.map +1 -0
- package/dist/ui-kit/form/index.d.ts +7 -0
- package/dist/ui-kit/form/index.d.ts.map +1 -0
- package/dist/ui-kit/form/index.js +7 -0
- package/dist/ui-kit/form/index.js.map +1 -0
- package/dist/ui-kit/form/photo-upload.d.ts +37 -0
- package/dist/ui-kit/form/photo-upload.d.ts.map +1 -0
- package/dist/ui-kit/form/photo-upload.js +19 -0
- package/dist/ui-kit/form/photo-upload.js.map +1 -0
- package/dist/ui-kit/form/quote-dialog.d.ts +15 -0
- package/dist/ui-kit/form/quote-dialog.d.ts.map +1 -0
- package/dist/ui-kit/form/quote-dialog.js +68 -0
- package/dist/ui-kit/form/quote-dialog.js.map +1 -0
- package/dist/ui-kit/form/quote-form.d.ts +165 -0
- package/dist/ui-kit/form/quote-form.d.ts.map +1 -0
- package/dist/ui-kit/form/quote-form.js +235 -0
- package/dist/ui-kit/form/quote-form.js.map +1 -0
- package/dist/ui-kit/form/thank-you.d.ts +37 -0
- package/dist/ui-kit/form/thank-you.d.ts.map +1 -0
- package/dist/ui-kit/form/thank-you.js +36 -0
- package/dist/ui-kit/form/thank-you.js.map +1 -0
- package/dist/ui-kit/shell/back-to-top-client.d.ts +9 -0
- package/dist/ui-kit/shell/back-to-top-client.d.ts.map +1 -0
- package/dist/ui-kit/shell/back-to-top-client.js +8 -0
- package/dist/ui-kit/shell/back-to-top-client.js.map +1 -0
- package/dist/ui-kit/shell/back-to-top.d.ts +13 -0
- package/dist/ui-kit/shell/back-to-top.d.ts.map +1 -0
- package/dist/ui-kit/shell/back-to-top.js +14 -0
- package/dist/ui-kit/shell/back-to-top.js.map +1 -0
- package/dist/ui-kit/shell/call-bar-client.d.ts +7 -0
- package/dist/ui-kit/shell/call-bar-client.d.ts.map +1 -0
- package/dist/ui-kit/shell/call-bar-client.js +13 -0
- package/dist/ui-kit/shell/call-bar-client.js.map +1 -0
- package/dist/ui-kit/shell/current-link.d.ts +15 -0
- package/dist/ui-kit/shell/current-link.d.ts.map +1 -0
- package/dist/ui-kit/shell/current-link.js +34 -0
- package/dist/ui-kit/shell/current-link.js.map +1 -0
- package/dist/ui-kit/shell/drawer-client.d.ts +22 -0
- package/dist/ui-kit/shell/drawer-client.d.ts.map +1 -0
- package/dist/ui-kit/shell/drawer-client.js +70 -0
- package/dist/ui-kit/shell/drawer-client.js.map +1 -0
- package/dist/ui-kit/shell/header-client.d.ts +44 -0
- package/dist/ui-kit/shell/header-client.d.ts.map +1 -0
- package/dist/ui-kit/shell/header-client.js +59 -0
- package/dist/ui-kit/shell/header-client.js.map +1 -0
- package/dist/ui-kit/shell/index.d.ts +6 -0
- package/dist/ui-kit/shell/index.d.ts.map +1 -0
- package/dist/ui-kit/shell/index.js +6 -0
- package/dist/ui-kit/shell/index.js.map +1 -0
- package/dist/ui-kit/shell/mobile-call-bar.d.ts +16 -0
- package/dist/ui-kit/shell/mobile-call-bar.d.ts.map +1 -0
- package/dist/ui-kit/shell/mobile-call-bar.js +15 -0
- package/dist/ui-kit/shell/mobile-call-bar.js.map +1 -0
- package/dist/ui-kit/shell/mobile-drawer.d.ts +37 -0
- package/dist/ui-kit/shell/mobile-drawer.d.ts.map +1 -0
- package/dist/ui-kit/shell/mobile-drawer.js +25 -0
- package/dist/ui-kit/shell/mobile-drawer.js.map +1 -0
- package/dist/ui-kit/shell/shell-context.d.ts +3 -0
- package/dist/ui-kit/shell/shell-context.d.ts.map +1 -0
- package/dist/ui-kit/shell/shell-context.js +5 -0
- package/dist/ui-kit/shell/shell-context.js.map +1 -0
- package/dist/ui-kit/shell/site-header.d.ts +52 -0
- package/dist/ui-kit/shell/site-header.d.ts.map +1 -0
- package/dist/ui-kit/shell/site-header.js +36 -0
- package/dist/ui-kit/shell/site-header.js.map +1 -0
- package/dist/ui-kit/shell/social-links.d.ts +8 -0
- package/dist/ui-kit/shell/social-links.d.ts.map +1 -0
- package/dist/ui-kit/shell/social-links.js +9 -0
- package/dist/ui-kit/shell/social-links.js.map +1 -0
- package/dist/ui-kit/shell/utility-bar.d.ts +30 -0
- package/dist/ui-kit/shell/utility-bar.d.ts.map +1 -0
- package/dist/ui-kit/shell/utility-bar.js +15 -0
- package/dist/ui-kit/shell/utility-bar.js.map +1 -0
- package/dist/ui-kit/structure.css +791 -0
- package/dist/ui.d.ts +10 -1
- package/dist/ui.d.ts.map +1 -1
- package/dist/ui.js +3 -3
- package/dist/ui.js.map +1 -1
- package/package.json +14 -2
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dialog.js","sourceRoot":"","sources":["../../../src/ui-kit/form/dialog.tsx"],"names":[],"mappings":"AAAA,YAAY,CAAA;;AAEZ;;;;GAIG;AACH,OAAO,EAAE,MAAM,EAAkC,MAAM,OAAO,CAAA;AAC9D,OAAO,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAA;AACnD,OAAO,EAAE,IAAI,EAAE,MAAM,aAAa,CAAA;AAClC,OAAO,EAAE,IAAI,EAAkB,MAAM,YAAY,CAAA;AAmBjD,MAAM,UAAU,MAAM,CAAC,EAAE,EAAE,EAAE,IAAI,EAAE,OAAO,EAAE,KAAK,EAAE,UAAU,EAAE,cAAc,EAAE,OAAO,EAAE,QAAQ,EAAe;IAC7G,MAAM,QAAQ,GAAG,MAAM,CAAqB,IAAI,CAAC,CAAA;IACjD,MAAM,OAAO,GAAG,GAAG,EAAE,QAAQ,CAAA;IAC7B,OAAO,CACL,MAAC,cAAc,IACb,EAAE,EAAE,EAAE,EACN,IAAI,EAAE,IAAI,EACV,OAAO,EAAE,OAAO,EAChB,UAAU,EAAE,OAAO,EACnB,eAAe,EAAE,QAAQ,KACrB,CAAC,cAAc,CAAC,CAAC,CAAC,EAAE,cAAc,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,aACtC,QAAQ,gBACJ,IAAI,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,QAAQ,EACpC,SAAS,EAAE,OAAO,EAAE,IAAI,aAExB,kBAAS,IAAI,CAAC,OAAO,EAAE,QAAQ,CAAC,aAC9B,gBAAQ,IAAI,CAAC,OAAO,EAAE,OAAO,CAAC,EAAE,EAAE,EAAE,OAAO,EAAE,GAAG,EAAE,QAAQ,EAAE,QAAQ,EAAE,CAAC,CAAC,YACrE,KAAK,GACH,EACL,qBAAY,IAAI,CAAC,OAAO,EAAE,OAAO,CAAC,EAAE,IAAI,EAAC,QAAQ,EAAC,OAAO,EAAE,OAAO,aAChE,KAAC,IAAI,IAAC,IAAI,EAAC,OAAO,GAAG,EACrB,0CAA8B,EAAE,YAAE,UAAU,GAAQ,IAC7C,IACL,EACN,iBAAS,IAAI,CAAC,OAAO,EAAE,MAAM,CAAC,YAAG,QAAQ,GAAO,IACjC,CAClB,CAAA;AACH,CAAC","sourcesContent":["'use client'\n\n/**\n * `Dialog`: a modal dialog over `HeadlessDialog` (native `<dialog>` with `showModal()`, so the page behind is inert).\n * Its H2 title names it and takes focus when it opens, so a phone keyboard doesn't jump up; Escape and the close\n * button close it and focus returns to the control that opened it. The page doesn't scroll behind it (structure.css).\n */\nimport { useRef, type ReactNode, type RefObject } from 'react'\nimport { HeadlessDialog } from '../../ui-client.js'\nimport { Icon } from '../icons.js'\nimport { slot, type SkinEntry } from '../skin.js'\n\nexport type DialogSlot = 'root' | 'header' | 'title' | 'close' | 'body'\nexport type DialogSkin = SkinEntry<DialogSlot>\n\nexport interface DialogProps extends DialogSkin {\n /** The dialog's id; its title is `${id}-title`. */\n readonly id: string\n readonly open: boolean\n readonly onClose: () => void\n /** The H2 that names the dialog, for example \"Get a free quote\". */\n readonly title: string\n /** The close button's accessible name, for example \"Close\". */\n readonly closeLabel: string\n /** Where focus returns on close; by default the element focused when it opened. */\n readonly returnFocusRef?: RefObject<HTMLElement | null> | undefined\n readonly children: ReactNode\n}\n\nexport function Dialog({ id, open, onClose, title, closeLabel, returnFocusRef, classes, children }: DialogProps) {\n const titleRef = useRef<HTMLHeadingElement>(null)\n const titleId = `${id}-title`\n return (\n <HeadlessDialog\n id={id}\n open={open}\n onClose={onClose}\n labelledBy={titleId}\n initialFocusRef={titleRef}\n {...(returnFocusRef ? { returnFocusRef } : {})}\n data-bl=\"dialog\"\n data-state={open ? 'open' : 'closed'}\n className={classes?.root}\n >\n <div {...slot(classes, 'header')}>\n <h2 {...slot(classes, 'title')} id={titleId} ref={titleRef} tabIndex={-1}>\n {title}\n </h2>\n <button {...slot(classes, 'close')} type=\"button\" onClick={onClose}>\n <Icon name=\"close\" />\n <span data-bl-visually-hidden=\"\">{closeLabel}</span>\n </button>\n </div>\n <div {...slot(classes, 'body')}>{children}</div>\n </HeadlessDialog>\n )\n}\n"]}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `Field`: one labelled control over `HeadlessField`. The label sits above the control; a description and an error
|
|
3
|
+
* are wired to it with `aria-describedby`, the error is `role="alert"` and the control gets `aria-invalid` while it
|
|
4
|
+
* has one. A required control carries `required` and `aria-required`, never a visible "(required)". Server-safe: it
|
|
5
|
+
* holds no state, so it works in server and client components alike.
|
|
6
|
+
*/
|
|
7
|
+
import type { ReactNode } from 'react';
|
|
8
|
+
import type { SkinEntry } from '../skin.js';
|
|
9
|
+
export type FieldSlot = 'field' | 'label' | 'description' | 'error';
|
|
10
|
+
export type FieldSkin = SkinEntry<FieldSlot>;
|
|
11
|
+
/** Spread onto the control (`input`, `select`, `textarea` or a file input). */
|
|
12
|
+
export interface FieldControlProps {
|
|
13
|
+
readonly id: string;
|
|
14
|
+
readonly required: boolean;
|
|
15
|
+
readonly 'aria-required': true | undefined;
|
|
16
|
+
readonly 'aria-invalid': true | undefined;
|
|
17
|
+
readonly 'aria-describedby': string | undefined;
|
|
18
|
+
}
|
|
19
|
+
export interface FieldProps extends FieldSkin {
|
|
20
|
+
/** The control's id; the description and error ids are `${id}-description` and `${id}-error`. */
|
|
21
|
+
readonly id: string;
|
|
22
|
+
readonly label: string;
|
|
23
|
+
readonly description?: string | undefined;
|
|
24
|
+
/** The error message, shown only once there is one (after a submit attempt). */
|
|
25
|
+
readonly error?: string | undefined;
|
|
26
|
+
readonly required?: boolean;
|
|
27
|
+
readonly children: (control: FieldControlProps) => ReactNode;
|
|
28
|
+
}
|
|
29
|
+
export declare function Field({ id, label, description, error, required, classes, children }: FieldProps): import("react/jsx-runtime").JSX.Element;
|
|
30
|
+
//# sourceMappingURL=field.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"field.d.ts","sourceRoot":"","sources":["../../../src/ui-kit/form/field.tsx"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAA;AAEtC,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,YAAY,CAAA;AAE3C,MAAM,MAAM,SAAS,GAAG,OAAO,GAAG,OAAO,GAAG,aAAa,GAAG,OAAO,CAAA;AACnE,MAAM,MAAM,SAAS,GAAG,SAAS,CAAC,SAAS,CAAC,CAAA;AAE5C,+EAA+E;AAC/E,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAA;IACnB,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAA;IAC1B,QAAQ,CAAC,eAAe,EAAE,IAAI,GAAG,SAAS,CAAA;IAC1C,QAAQ,CAAC,cAAc,EAAE,IAAI,GAAG,SAAS,CAAA;IACzC,QAAQ,CAAC,kBAAkB,EAAE,MAAM,GAAG,SAAS,CAAA;CAChD;AAED,MAAM,WAAW,UAAW,SAAQ,SAAS;IAC3C,iGAAiG;IACjG,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAA;IACnB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;IACtB,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;IACzC,gFAAgF;IAChF,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;IACnC,QAAQ,CAAC,QAAQ,CAAC,EAAE,OAAO,CAAA;IAC3B,QAAQ,CAAC,QAAQ,EAAE,CAAC,OAAO,EAAE,iBAAiB,KAAK,SAAS,CAAA;CAC7D;AAED,wBAAgB,KAAK,CAAC,EAAE,EAAE,EAAE,KAAK,EAAE,WAAW,EAAE,KAAK,EAAE,QAAgB,EAAE,OAAO,EAAE,QAAQ,EAAE,EAAE,UAAU,2CAsBvG"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { HeadlessField } from '../../ui.js';
|
|
3
|
+
export function Field({ id, label, description, error, required = false, classes, children }) {
|
|
4
|
+
return (_jsx(HeadlessField, { id: id, label: label, description: description, error: error, required: required, className: classes?.field, labelClassName: classes?.label, descriptionClassName: classes?.description, errorClassName: classes?.error, dataAttributes: {
|
|
5
|
+
field: { 'data-bl': 'field', 'data-slot': 'field' },
|
|
6
|
+
label: { 'data-slot': 'label' },
|
|
7
|
+
description: { 'data-slot': 'description' },
|
|
8
|
+
error: { 'data-slot': 'error' },
|
|
9
|
+
}, children: (control) => children({ ...control, 'aria-required': required ? true : undefined }) }));
|
|
10
|
+
}
|
|
11
|
+
//# sourceMappingURL=field.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"field.js","sourceRoot":"","sources":["../../../src/ui-kit/form/field.tsx"],"names":[],"mappings":";AAOA,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAA;AA0B3C,MAAM,UAAU,KAAK,CAAC,EAAE,EAAE,EAAE,KAAK,EAAE,WAAW,EAAE,KAAK,EAAE,QAAQ,GAAG,KAAK,EAAE,OAAO,EAAE,QAAQ,EAAc;IACtG,OAAO,CACL,KAAC,aAAa,IACZ,EAAE,EAAE,EAAE,EACN,KAAK,EAAE,KAAK,EACZ,WAAW,EAAE,WAAW,EACxB,KAAK,EAAE,KAAK,EACZ,QAAQ,EAAE,QAAQ,EAClB,SAAS,EAAE,OAAO,EAAE,KAAK,EACzB,cAAc,EAAE,OAAO,EAAE,KAAK,EAC9B,oBAAoB,EAAE,OAAO,EAAE,WAAW,EAC1C,cAAc,EAAE,OAAO,EAAE,KAAK,EAC9B,cAAc,EAAE;YACd,KAAK,EAAE,EAAE,SAAS,EAAE,OAAO,EAAE,WAAW,EAAE,OAAO,EAAE;YACnD,KAAK,EAAE,EAAE,WAAW,EAAE,OAAO,EAAE;YAC/B,WAAW,EAAE,EAAE,WAAW,EAAE,aAAa,EAAE;YAC3C,KAAK,EAAE,EAAE,WAAW,EAAE,OAAO,EAAE;SAChC,YAEA,CAAC,OAAO,EAAE,EAAE,CAAC,QAAQ,CAAC,EAAE,GAAG,OAAO,EAAE,eAAe,EAAE,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,SAAS,EAAE,CAAC,GACtE,CACjB,CAAA;AACH,CAAC","sourcesContent":["/**\n * `Field`: one labelled control over `HeadlessField`. The label sits above the control; a description and an error\n * are wired to it with `aria-describedby`, the error is `role=\"alert\"` and the control gets `aria-invalid` while it\n * has one. A required control carries `required` and `aria-required`, never a visible \"(required)\". Server-safe: it\n * holds no state, so it works in server and client components alike.\n */\nimport type { ReactNode } from 'react'\nimport { HeadlessField } from '../../ui.js'\nimport type { SkinEntry } from '../skin.js'\n\nexport type FieldSlot = 'field' | 'label' | 'description' | 'error'\nexport type FieldSkin = SkinEntry<FieldSlot>\n\n/** Spread onto the control (`input`, `select`, `textarea` or a file input). */\nexport interface FieldControlProps {\n readonly id: string\n readonly required: boolean\n readonly 'aria-required': true | undefined\n readonly 'aria-invalid': true | undefined\n readonly 'aria-describedby': string | undefined\n}\n\nexport interface FieldProps extends FieldSkin {\n /** The control's id; the description and error ids are `${id}-description` and `${id}-error`. */\n readonly id: string\n readonly label: string\n readonly description?: string | undefined\n /** The error message, shown only once there is one (after a submit attempt). */\n readonly error?: string | undefined\n readonly required?: boolean\n readonly children: (control: FieldControlProps) => ReactNode\n}\n\nexport function Field({ id, label, description, error, required = false, classes, children }: FieldProps) {\n return (\n <HeadlessField\n id={id}\n label={label}\n description={description}\n error={error}\n required={required}\n className={classes?.field}\n labelClassName={classes?.label}\n descriptionClassName={classes?.description}\n errorClassName={classes?.error}\n dataAttributes={{\n field: { 'data-bl': 'field', 'data-slot': 'field' },\n label: { 'data-slot': 'label' },\n description: { 'data-slot': 'description' },\n error: { 'data-slot': 'error' },\n }}\n >\n {(control) => children({ ...control, 'aria-required': required ? true : undefined })}\n </HeadlessField>\n )\n}\n"]}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export { QUOTE_FORM_PLACEMENTS, QUOTE_FORM_PLACEMENT_VARIANTS, QUOTE_FORM_VARIANTS, QuoteForm, createQuoteForm, type BoundQuoteDialogProps, type BoundQuoteForm, type BoundQuoteFormProps, type CreateQuoteFormOptions, type QuoteFormCopy, type QuoteFormLayout, type QuoteFormPlacement, type QuoteFormProps, type QuoteFormSkin, type QuoteFormSlot, type QuoteFormVariant, } from './quote-form.js';
|
|
2
|
+
export { QuoteDialog, type QuoteDialogProps, type QuoteDialogSkin } from './quote-dialog.js';
|
|
3
|
+
export { PhotoUpload, type PhotoUploadProps, type PhotoUploadSkin, type PhotoUploadSlot } from './photo-upload.js';
|
|
4
|
+
export { Dialog, type DialogProps, type DialogSkin, type DialogSlot } from './dialog.js';
|
|
5
|
+
export { Field, type FieldControlProps, type FieldProps, type FieldSkin, type FieldSlot } from './field.js';
|
|
6
|
+
export { THANK_YOU_DEFAULT_REPLY_TIME, ThankYou, thankYouMetadata, type ThankYouProps, type ThankYouSkin, type ThankYouSlot, } from './thank-you.js';
|
|
7
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/ui-kit/form/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,qBAAqB,EACrB,6BAA6B,EAC7B,mBAAmB,EACnB,SAAS,EACT,eAAe,EACf,KAAK,qBAAqB,EAC1B,KAAK,cAAc,EACnB,KAAK,mBAAmB,EACxB,KAAK,sBAAsB,EAC3B,KAAK,aAAa,EAClB,KAAK,eAAe,EACpB,KAAK,kBAAkB,EACvB,KAAK,cAAc,EACnB,KAAK,aAAa,EAClB,KAAK,aAAa,EAClB,KAAK,gBAAgB,GACtB,MAAM,iBAAiB,CAAA;AACxB,OAAO,EAAE,WAAW,EAAE,KAAK,gBAAgB,EAAE,KAAK,eAAe,EAAE,MAAM,mBAAmB,CAAA;AAC5F,OAAO,EAAE,WAAW,EAAE,KAAK,gBAAgB,EAAE,KAAK,eAAe,EAAE,KAAK,eAAe,EAAE,MAAM,mBAAmB,CAAA;AAClH,OAAO,EAAE,MAAM,EAAE,KAAK,WAAW,EAAE,KAAK,UAAU,EAAE,KAAK,UAAU,EAAE,MAAM,aAAa,CAAA;AACxF,OAAO,EAAE,KAAK,EAAE,KAAK,iBAAiB,EAAE,KAAK,UAAU,EAAE,KAAK,SAAS,EAAE,KAAK,SAAS,EAAE,MAAM,YAAY,CAAA;AAC3G,OAAO,EACL,4BAA4B,EAC5B,QAAQ,EACR,gBAAgB,EAChB,KAAK,aAAa,EAClB,KAAK,YAAY,EACjB,KAAK,YAAY,GAClB,MAAM,gBAAgB,CAAA"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export { QUOTE_FORM_PLACEMENTS, QUOTE_FORM_PLACEMENT_VARIANTS, QUOTE_FORM_VARIANTS, QuoteForm, createQuoteForm, } from './quote-form.js';
|
|
2
|
+
export { QuoteDialog } from './quote-dialog.js';
|
|
3
|
+
export { PhotoUpload } from './photo-upload.js';
|
|
4
|
+
export { Dialog } from './dialog.js';
|
|
5
|
+
export { Field } from './field.js';
|
|
6
|
+
export { THANK_YOU_DEFAULT_REPLY_TIME, ThankYou, thankYouMetadata, } from './thank-you.js';
|
|
7
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/ui-kit/form/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,qBAAqB,EACrB,6BAA6B,EAC7B,mBAAmB,EACnB,SAAS,EACT,eAAe,GAYhB,MAAM,iBAAiB,CAAA;AACxB,OAAO,EAAE,WAAW,EAA+C,MAAM,mBAAmB,CAAA;AAC5F,OAAO,EAAE,WAAW,EAAqE,MAAM,mBAAmB,CAAA;AAClH,OAAO,EAAE,MAAM,EAAsD,MAAM,aAAa,CAAA;AACxF,OAAO,EAAE,KAAK,EAA2E,MAAM,YAAY,CAAA;AAC3G,OAAO,EACL,4BAA4B,EAC5B,QAAQ,EACR,gBAAgB,GAIjB,MAAM,gBAAgB,CAAA","sourcesContent":["export {\n QUOTE_FORM_PLACEMENTS,\n QUOTE_FORM_PLACEMENT_VARIANTS,\n QUOTE_FORM_VARIANTS,\n QuoteForm,\n createQuoteForm,\n type BoundQuoteDialogProps,\n type BoundQuoteForm,\n type BoundQuoteFormProps,\n type CreateQuoteFormOptions,\n type QuoteFormCopy,\n type QuoteFormLayout,\n type QuoteFormPlacement,\n type QuoteFormProps,\n type QuoteFormSkin,\n type QuoteFormSlot,\n type QuoteFormVariant,\n} from './quote-form.js'\nexport { QuoteDialog, type QuoteDialogProps, type QuoteDialogSkin } from './quote-dialog.js'\nexport { PhotoUpload, type PhotoUploadProps, type PhotoUploadSkin, type PhotoUploadSlot } from './photo-upload.js'\nexport { Dialog, type DialogProps, type DialogSkin, type DialogSlot } from './dialog.js'\nexport { Field, type FieldControlProps, type FieldProps, type FieldSkin, type FieldSlot } from './field.js'\nexport {\n THANK_YOU_DEFAULT_REPLY_TIME,\n ThankYou,\n thankYouMetadata,\n type ThankYouProps,\n type ThankYouSkin,\n type ThankYouSlot,\n} from './thank-you.js'\n"]}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `PhotoUpload`: the "Add photos" drop box over the shared photo preparation (`useBrandlabContactAttachments`). A
|
|
3
|
+
* real file input, visually hidden but focusable, so the keyboard opens the picker with Enter or Space; the whole
|
|
4
|
+
* box is its label and a drop target. `accept="image/*"` and never a `capture` attribute, so phones offer the
|
|
5
|
+
* library as well as the camera. The "0 of 5" counter is a `HeadlessStatus`, so each change is announced. Chosen
|
|
6
|
+
* photos show as thumbnails with a remove button each.
|
|
7
|
+
*/
|
|
8
|
+
import type { BrandlabContactAttachmentErrorReason, UseBrandlabContactAttachmentsResult } from '../../contact/attachments-client.js';
|
|
9
|
+
import { type SkinEntry } from '../skin.js';
|
|
10
|
+
export type PhotoUploadSlot = 'field' | 'label' | 'drop' | 'dropIcon' | 'counter' | 'photos' | 'photo' | 'thumb' | 'remove' | 'error';
|
|
11
|
+
export type PhotoUploadSkin = SkinEntry<PhotoUploadSlot>;
|
|
12
|
+
export interface PhotoUploadProps extends PhotoUploadSkin {
|
|
13
|
+
/** The file input's id. */
|
|
14
|
+
readonly id: string;
|
|
15
|
+
/** The drop box's label, for example "Add photos". */
|
|
16
|
+
readonly label: string;
|
|
17
|
+
/** The prepared photos, from `useBrandlabContactForm().attachments` or `useBrandlabContactAttachments()`. */
|
|
18
|
+
readonly attachments: UseBrandlabContactAttachmentsResult;
|
|
19
|
+
/** The counter, with `{count}` and `{max}`, for example "{count} of {max}". */
|
|
20
|
+
readonly counter: string;
|
|
21
|
+
/** Each remove button's name, with `{number}`, for example "Remove photo {number}". */
|
|
22
|
+
readonly removeLabel: string;
|
|
23
|
+
/** `image/*` by default; `image/*,application/pdf` where the contract takes PDFs. */
|
|
24
|
+
readonly accept?: string;
|
|
25
|
+
/**
|
|
26
|
+
* The site's message for each reason a photo is refused (`count` may use `{max}`). Without them the shared photo
|
|
27
|
+
* preparation's own wording shows.
|
|
28
|
+
*/
|
|
29
|
+
readonly errorMessages?: Readonly<Record<BrandlabContactAttachmentErrorReason, string>> | undefined;
|
|
30
|
+
/** A form-level message, for example when photos are required. A refused photo's message wins. */
|
|
31
|
+
readonly error?: string | undefined;
|
|
32
|
+
readonly required?: boolean;
|
|
33
|
+
/** Locks the box while an enquiry is sending. */
|
|
34
|
+
readonly disabled?: boolean;
|
|
35
|
+
}
|
|
36
|
+
export declare function PhotoUpload({ id, label, attachments, counter, removeLabel, accept, errorMessages, error, required, disabled, classes, }: PhotoUploadProps): import("react/jsx-runtime").JSX.Element;
|
|
37
|
+
//# sourceMappingURL=photo-upload.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"photo-upload.d.ts","sourceRoot":"","sources":["../../../src/ui-kit/form/photo-upload.tsx"],"names":[],"mappings":"AAEA;;;;;;GAMG;AACH,OAAO,KAAK,EACV,oCAAoC,EACpC,mCAAmC,EACpC,MAAM,qCAAqC,CAAA;AAG5C,OAAO,EAAQ,KAAK,SAAS,EAAE,MAAM,YAAY,CAAA;AAGjD,MAAM,MAAM,eAAe,GACvB,OAAO,GACP,OAAO,GACP,MAAM,GACN,UAAU,GACV,SAAS,GACT,QAAQ,GACR,OAAO,GACP,OAAO,GACP,QAAQ,GACR,OAAO,CAAA;AACX,MAAM,MAAM,eAAe,GAAG,SAAS,CAAC,eAAe,CAAC,CAAA;AAExD,MAAM,WAAW,gBAAiB,SAAQ,eAAe;IACvD,2BAA2B;IAC3B,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAA;IACnB,sDAAsD;IACtD,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;IACtB,6GAA6G;IAC7G,QAAQ,CAAC,WAAW,EAAE,mCAAmC,CAAA;IACzD,+EAA+E;IAC/E,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;IACxB,uFAAuF;IACvF,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAA;IAC5B,qFAAqF;IACrF,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAA;IACxB;;;OAGG;IACH,QAAQ,CAAC,aAAa,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,oCAAoC,EAAE,MAAM,CAAC,CAAC,GAAG,SAAS,CAAA;IACnG,kGAAkG;IAClG,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;IACnC,QAAQ,CAAC,QAAQ,CAAC,EAAE,OAAO,CAAA;IAC3B,iDAAiD;IACjD,QAAQ,CAAC,QAAQ,CAAC,EAAE,OAAO,CAAA;CAC5B;AAED,wBAAgB,WAAW,CAAC,EAC1B,EAAE,EACF,KAAK,EACL,WAAW,EACX,OAAO,EACP,WAAW,EACX,MAAkB,EAClB,aAAa,EACb,KAAK,EACL,QAAgB,EAChB,QAAgB,EAChB,OAAO,GACR,EAAE,gBAAgB,2CAgElB"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { createElement as _createElement } from "react";
|
|
3
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
4
|
+
import { HeadlessStatus } from '../../ui.js';
|
|
5
|
+
import { Icon } from '../icons.js';
|
|
6
|
+
import { slot } from '../skin.js';
|
|
7
|
+
import { fillCopy } from './copy.js';
|
|
8
|
+
export function PhotoUpload({ id, label, attachments, counter, removeLabel, accept = 'image/*', errorMessages, error, required = false, disabled = false, classes, }) {
|
|
9
|
+
const counterId = `${id}-count`;
|
|
10
|
+
const errorId = `${id}-error`;
|
|
11
|
+
const reason = attachments.errorReason;
|
|
12
|
+
const refused = reason && errorMessages ? fillCopy(errorMessages[reason], { max: attachments.maxImages }) : attachments.errorMessage;
|
|
13
|
+
const message = refused ?? error;
|
|
14
|
+
const state = attachments.isProcessing ? 'busy' : attachments.isDragging ? 'dragging' : 'idle';
|
|
15
|
+
return (_jsxs("div", { "data-bl": "photo-upload", ...slot(classes, 'field'), children: [_jsxs("div", { ...slot(classes, 'drop'), "data-state": state, "data-disabled": disabled ? 'true' : undefined, onDragOver: disabled ? undefined : attachments.handleDragOver, onDragLeave: disabled ? undefined : attachments.handleDragLeave, onDrop: disabled ? undefined : (event) => void attachments.handleDrop(event), children: [_jsx("input", { id: id, type: "file", accept: accept, multiple: true, disabled: disabled, "aria-required": required ? true : undefined, "aria-invalid": message ? true : undefined, "aria-describedby": [counterId, message ? errorId : undefined].filter(Boolean).join(' '), "data-bl-visually-hidden": "", onChange: (event) => void attachments.handleImageChange(event) }), _jsxs("label", { ...slot(classes, 'label'), htmlFor: id, children: [_jsx("span", { ...slot(classes, 'dropIcon'), children: _jsx(Icon, { name: "camera" }) }), _jsx("span", { children: label })] }), _jsx(HeadlessStatus, { ...slot(classes, 'counter'), id: counterId, children: fillCopy(counter, { count: attachments.images.length, max: attachments.maxImages }) })] }), attachments.images.length > 0 ? (_jsx("ul", { ...slot(classes, 'photos'), children: attachments.images.map((image, index) => (_createElement("li", { ...slot(classes, 'photo'), key: image.preview },
|
|
16
|
+
_jsx("img", { ...slot(classes, 'thumb'), src: image.preview, alt: "", width: 64, height: 64 }),
|
|
17
|
+
_jsxs("button", { ...slot(classes, 'remove'), type: "button", disabled: disabled, onClick: () => attachments.removeImage(index), children: [_jsx(Icon, { name: "close" }), _jsx("span", { "data-bl-visually-hidden": "", children: fillCopy(removeLabel, { number: index + 1 }) })] })))) })) : null, message ? (_jsx("div", { ...slot(classes, 'error'), id: errorId, role: "alert", children: message })) : null] }));
|
|
18
|
+
}
|
|
19
|
+
//# sourceMappingURL=photo-upload.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"photo-upload.js","sourceRoot":"","sources":["../../../src/ui-kit/form/photo-upload.tsx"],"names":[],"mappings":"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","sourcesContent":["'use client'\n\n/**\n * `PhotoUpload`: the \"Add photos\" drop box over the shared photo preparation (`useBrandlabContactAttachments`). A\n * real file input, visually hidden but focusable, so the keyboard opens the picker with Enter or Space; the whole\n * box is its label and a drop target. `accept=\"image/*\"` and never a `capture` attribute, so phones offer the\n * library as well as the camera. The \"0 of 5\" counter is a `HeadlessStatus`, so each change is announced. Chosen\n * photos show as thumbnails with a remove button each.\n */\nimport type {\n BrandlabContactAttachmentErrorReason,\n UseBrandlabContactAttachmentsResult,\n} from '../../contact/attachments-client.js'\nimport { HeadlessStatus } from '../../ui.js'\nimport { Icon } from '../icons.js'\nimport { slot, type SkinEntry } from '../skin.js'\nimport { fillCopy } from './copy.js'\n\nexport type PhotoUploadSlot =\n | 'field'\n | 'label'\n | 'drop'\n | 'dropIcon'\n | 'counter'\n | 'photos'\n | 'photo'\n | 'thumb'\n | 'remove'\n | 'error'\nexport type PhotoUploadSkin = SkinEntry<PhotoUploadSlot>\n\nexport interface PhotoUploadProps extends PhotoUploadSkin {\n /** The file input's id. */\n readonly id: string\n /** The drop box's label, for example \"Add photos\". */\n readonly label: string\n /** The prepared photos, from `useBrandlabContactForm().attachments` or `useBrandlabContactAttachments()`. */\n readonly attachments: UseBrandlabContactAttachmentsResult\n /** The counter, with `{count}` and `{max}`, for example \"{count} of {max}\". */\n readonly counter: string\n /** Each remove button's name, with `{number}`, for example \"Remove photo {number}\". */\n readonly removeLabel: string\n /** `image/*` by default; `image/*,application/pdf` where the contract takes PDFs. */\n readonly accept?: string\n /**\n * The site's message for each reason a photo is refused (`count` may use `{max}`). Without them the shared photo\n * preparation's own wording shows.\n */\n readonly errorMessages?: Readonly<Record<BrandlabContactAttachmentErrorReason, string>> | undefined\n /** A form-level message, for example when photos are required. A refused photo's message wins. */\n readonly error?: string | undefined\n readonly required?: boolean\n /** Locks the box while an enquiry is sending. */\n readonly disabled?: boolean\n}\n\nexport function PhotoUpload({\n id,\n label,\n attachments,\n counter,\n removeLabel,\n accept = 'image/*',\n errorMessages,\n error,\n required = false,\n disabled = false,\n classes,\n}: PhotoUploadProps) {\n const counterId = `${id}-count`\n const errorId = `${id}-error`\n const reason = attachments.errorReason\n const refused = reason && errorMessages ? fillCopy(errorMessages[reason], { max: attachments.maxImages }) : attachments.errorMessage\n const message = refused ?? error\n const state = attachments.isProcessing ? 'busy' : attachments.isDragging ? 'dragging' : 'idle'\n return (\n <div data-bl=\"photo-upload\" {...slot(classes, 'field')}>\n <div\n {...slot(classes, 'drop')}\n data-state={state}\n data-disabled={disabled ? 'true' : undefined}\n onDragOver={disabled ? undefined : attachments.handleDragOver}\n onDragLeave={disabled ? undefined : attachments.handleDragLeave}\n onDrop={disabled ? undefined : (event) => void attachments.handleDrop(event)}\n >\n <input\n id={id}\n type=\"file\"\n accept={accept}\n multiple\n disabled={disabled}\n aria-required={required ? true : undefined}\n aria-invalid={message ? true : undefined}\n aria-describedby={[counterId, message ? errorId : undefined].filter(Boolean).join(' ')}\n data-bl-visually-hidden=\"\"\n onChange={(event) => void attachments.handleImageChange(event)}\n />\n <label {...slot(classes, 'label')} htmlFor={id}>\n <span {...slot(classes, 'dropIcon')}>\n <Icon name=\"camera\" />\n </span>\n <span>{label}</span>\n </label>\n <HeadlessStatus {...slot(classes, 'counter')} id={counterId}>\n {fillCopy(counter, { count: attachments.images.length, max: attachments.maxImages })}\n </HeadlessStatus>\n </div>\n {attachments.images.length > 0 ? (\n <ul {...slot(classes, 'photos')}>\n {attachments.images.map((image, index) => (\n <li {...slot(classes, 'photo')} key={image.preview}>\n <img {...slot(classes, 'thumb')} src={image.preview} alt=\"\" width={64} height={64} />\n <button\n {...slot(classes, 'remove')}\n type=\"button\"\n disabled={disabled}\n onClick={() => attachments.removeImage(index)}\n >\n <Icon name=\"close\" />\n <span data-bl-visually-hidden=\"\">{fillCopy(removeLabel, { number: index + 1 })}</span>\n </button>\n </li>\n ))}\n </ul>\n ) : null}\n {message ? (\n <div {...slot(classes, 'error')} id={errorId} role=\"alert\">\n {message}\n </div>\n ) : null}\n </div>\n )\n}\n"]}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { type ReactNode } from 'react';
|
|
2
|
+
import { type DialogSkin } from './dialog.js';
|
|
3
|
+
export type QuoteDialogSkin = DialogSkin;
|
|
4
|
+
export interface QuoteDialogProps extends QuoteDialogSkin {
|
|
5
|
+
/** The dialog's H2, for example "Get a free quote". */
|
|
6
|
+
readonly title: string;
|
|
7
|
+
/** The close button's accessible name, for example "Close". */
|
|
8
|
+
readonly closeLabel: string;
|
|
9
|
+
/** The dialog's id (default `quote-dialog-panel`; the form inside is `quote-dialog`). */
|
|
10
|
+
readonly id?: string;
|
|
11
|
+
/** The form, rendered once the dialog first opens. */
|
|
12
|
+
readonly children: ReactNode;
|
|
13
|
+
}
|
|
14
|
+
export declare function QuoteDialog({ title, closeLabel, id, classes, children }: QuoteDialogProps): import("react/jsx-runtime").JSX.Element;
|
|
15
|
+
//# sourceMappingURL=quote-dialog.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"quote-dialog.d.ts","sourceRoot":"","sources":["../../../src/ui-kit/form/quote-dialog.tsx"],"names":[],"mappings":"AAWA,OAAO,EAA+B,KAAK,SAAS,EAAE,MAAM,OAAO,CAAA;AACnE,OAAO,EAAU,KAAK,UAAU,EAAE,MAAM,aAAa,CAAA;AAErD,MAAM,MAAM,eAAe,GAAG,UAAU,CAAA;AAExC,MAAM,WAAW,gBAAiB,SAAQ,eAAe;IACvD,uDAAuD;IACvD,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;IACtB,+DAA+D;IAC/D,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAA;IAC3B,yFAAyF;IACzF,QAAQ,CAAC,EAAE,CAAC,EAAE,MAAM,CAAA;IACpB,sDAAsD;IACtD,QAAQ,CAAC,QAAQ,EAAE,SAAS,CAAA;CAC7B;AA0BD,wBAAgB,WAAW,CAAC,EAAE,KAAK,EAAE,UAAU,EAAE,EAAyB,EAAE,OAAO,EAAE,QAAQ,EAAE,EAAE,gBAAgB,2CAwChH"}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
3
|
+
/**
|
|
4
|
+
* `QuoteDialog`: the quote form in a modal `Dialog`, opened by any `QuoteButton` (`[data-bl="quote-button"]`) on the
|
|
5
|
+
* page through one delegated click listener while the dialog is mounted. On the page the button links to (the form's
|
|
6
|
+
* own page, for example `/contact`) the link keeps its default and scrolls to the form. Without scripts every button
|
|
7
|
+
* stays a plain link to the form. The form mounts on the first open and stays mounted, so typed values survive
|
|
8
|
+
* closing and reopening; focus returns to the button that opened it. A client navigation (the consent link, for
|
|
9
|
+
* example) closes it.
|
|
10
|
+
*/
|
|
11
|
+
import { usePathname } from 'next/navigation';
|
|
12
|
+
import { useEffect, useRef, useState } from 'react';
|
|
13
|
+
import { Dialog } from './dialog.js';
|
|
14
|
+
function samePath(left, right) {
|
|
15
|
+
const trim = (path) => path.replace(/\/+$/, '') || '/';
|
|
16
|
+
return trim(left) === trim(right);
|
|
17
|
+
}
|
|
18
|
+
/** The quote button a click landed on, when the click should open the dialog. */
|
|
19
|
+
function quoteButtonFor(event) {
|
|
20
|
+
if (event.defaultPrevented || event.button !== 0 || event.metaKey || event.ctrlKey || event.shiftKey || event.altKey) {
|
|
21
|
+
return undefined;
|
|
22
|
+
}
|
|
23
|
+
const target = event.target instanceof Element ? event.target.closest('a[data-bl="quote-button"]') : null;
|
|
24
|
+
if (!(target instanceof HTMLAnchorElement) || (target.target && target.target !== '_self'))
|
|
25
|
+
return undefined;
|
|
26
|
+
let url;
|
|
27
|
+
try {
|
|
28
|
+
url = new URL(target.href, window.location.href);
|
|
29
|
+
}
|
|
30
|
+
catch {
|
|
31
|
+
return undefined;
|
|
32
|
+
}
|
|
33
|
+
if (url.origin !== window.location.origin)
|
|
34
|
+
return undefined;
|
|
35
|
+
// On the form's own page the link scrolls to the form instead.
|
|
36
|
+
if (samePath(url.pathname, window.location.pathname))
|
|
37
|
+
return undefined;
|
|
38
|
+
return target;
|
|
39
|
+
}
|
|
40
|
+
export function QuoteDialog({ title, closeLabel, id = 'quote-dialog-panel', classes, children }) {
|
|
41
|
+
const [open, setOpen] = useState(false);
|
|
42
|
+
const [mounted, setMounted] = useState(false);
|
|
43
|
+
const returnFocusRef = useRef(null);
|
|
44
|
+
const pathname = usePathname();
|
|
45
|
+
const shownPath = useRef(pathname);
|
|
46
|
+
useEffect(() => {
|
|
47
|
+
if (shownPath.current === pathname)
|
|
48
|
+
return;
|
|
49
|
+
shownPath.current = pathname;
|
|
50
|
+
setOpen(false);
|
|
51
|
+
}, [pathname]);
|
|
52
|
+
useEffect(() => {
|
|
53
|
+
// Capture phase: decided before a framework link handler starts a client navigation.
|
|
54
|
+
const onClick = (event) => {
|
|
55
|
+
const button = quoteButtonFor(event);
|
|
56
|
+
if (!button)
|
|
57
|
+
return;
|
|
58
|
+
event.preventDefault();
|
|
59
|
+
returnFocusRef.current = button;
|
|
60
|
+
setMounted(true);
|
|
61
|
+
setOpen(true);
|
|
62
|
+
};
|
|
63
|
+
document.addEventListener('click', onClick, true);
|
|
64
|
+
return () => document.removeEventListener('click', onClick, true);
|
|
65
|
+
}, []);
|
|
66
|
+
return (_jsx(Dialog, { id: id, open: open, onClose: () => setOpen(false), title: title, closeLabel: closeLabel, returnFocusRef: returnFocusRef, classes: classes, children: mounted ? children : null }));
|
|
67
|
+
}
|
|
68
|
+
//# sourceMappingURL=quote-dialog.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"quote-dialog.js","sourceRoot":"","sources":["../../../src/ui-kit/form/quote-dialog.tsx"],"names":[],"mappings":"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","sourcesContent":["'use client'\n\n/**\n * `QuoteDialog`: the quote form in a modal `Dialog`, opened by any `QuoteButton` (`[data-bl=\"quote-button\"]`) on the\n * page through one delegated click listener while the dialog is mounted. On the page the button links to (the form's\n * own page, for example `/contact`) the link keeps its default and scrolls to the form. Without scripts every button\n * stays a plain link to the form. The form mounts on the first open and stays mounted, so typed values survive\n * closing and reopening; focus returns to the button that opened it. A client navigation (the consent link, for\n * example) closes it.\n */\nimport { usePathname } from 'next/navigation'\nimport { useEffect, useRef, useState, type ReactNode } from 'react'\nimport { Dialog, type DialogSkin } from './dialog.js'\n\nexport type QuoteDialogSkin = DialogSkin\n\nexport interface QuoteDialogProps extends QuoteDialogSkin {\n /** The dialog's H2, for example \"Get a free quote\". */\n readonly title: string\n /** The close button's accessible name, for example \"Close\". */\n readonly closeLabel: string\n /** The dialog's id (default `quote-dialog-panel`; the form inside is `quote-dialog`). */\n readonly id?: string\n /** The form, rendered once the dialog first opens. */\n readonly children: ReactNode\n}\n\nfunction samePath(left: string, right: string) {\n const trim = (path: string) => path.replace(/\\/+$/, '') || '/'\n return trim(left) === trim(right)\n}\n\n/** The quote button a click landed on, when the click should open the dialog. */\nfunction quoteButtonFor(event: MouseEvent): HTMLAnchorElement | undefined {\n if (event.defaultPrevented || event.button !== 0 || event.metaKey || event.ctrlKey || event.shiftKey || event.altKey) {\n return undefined\n }\n const target = event.target instanceof Element ? event.target.closest('a[data-bl=\"quote-button\"]') : null\n if (!(target instanceof HTMLAnchorElement) || (target.target && target.target !== '_self')) return undefined\n let url: URL\n try {\n url = new URL(target.href, window.location.href)\n } catch {\n return undefined\n }\n if (url.origin !== window.location.origin) return undefined\n // On the form's own page the link scrolls to the form instead.\n if (samePath(url.pathname, window.location.pathname)) return undefined\n return target\n}\n\nexport function QuoteDialog({ title, closeLabel, id = 'quote-dialog-panel', classes, children }: QuoteDialogProps) {\n const [open, setOpen] = useState(false)\n const [mounted, setMounted] = useState(false)\n const returnFocusRef = useRef<HTMLElement | null>(null)\n const pathname = usePathname()\n const shownPath = useRef(pathname)\n\n useEffect(() => {\n if (shownPath.current === pathname) return\n shownPath.current = pathname\n setOpen(false)\n }, [pathname])\n\n useEffect(() => {\n // Capture phase: decided before a framework link handler starts a client navigation.\n const onClick = (event: MouseEvent) => {\n const button = quoteButtonFor(event)\n if (!button) return\n event.preventDefault()\n returnFocusRef.current = button\n setMounted(true)\n setOpen(true)\n }\n document.addEventListener('click', onClick, true)\n return () => document.removeEventListener('click', onClick, true)\n }, [])\n\n return (\n <Dialog\n id={id}\n open={open}\n onClose={() => setOpen(false)}\n title={title}\n closeLabel={closeLabel}\n returnFocusRef={returnFocusRef}\n classes={classes}\n >\n {mounted ? children : null}\n </Dialog>\n )\n}\n"]}
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `QuoteForm`: the one enquiry form for the hero, the quote dialog, the contact page and cost guides, on the shared
|
|
3
|
+
* contact runtime (`useBrandlabContactForm`, `POST /api/contact`, photo preparation, idempotent retries).
|
|
4
|
+
*
|
|
5
|
+
* Locked layout (F8): Name · Suburb; Phone · Email, both required; the message ("How can we help?"), 5 rows; the
|
|
6
|
+
* "Add photos" drop box with its "0 of 5" counter; the button; then one consent line linking the privacy policy. One
|
|
7
|
+
* column below 640 px. No job select, no "(required)", no helper or reassurance lines: every label and message is
|
|
8
|
+
* the site's copy. Errors appear only after a submit attempt, as a summary plus a message under each field; every
|
|
9
|
+
* typed value and chosen photo is kept on any error. The button stays disabled until the page has hydrated and is
|
|
10
|
+
* locked with a busy label while photos are prepared or the enquiry sends. An accepted enquiry sends one
|
|
11
|
+
* `contact_form` (with `has_photos`) and opens the thank-you page.
|
|
12
|
+
*
|
|
13
|
+
* `contract` and `analytics` can't cross from a server component, so a site binds them once with `createQuoteForm`
|
|
14
|
+
* in its own `'use client'` file and renders the bound `QuoteForm` and `QuoteDialog` from its pages.
|
|
15
|
+
*/
|
|
16
|
+
import { type BrandlabContactContract, type ContactAttachmentPolicy } from '@brandlab-digital/site-core/contact';
|
|
17
|
+
import { type JSX } from 'react';
|
|
18
|
+
import type { BrandlabAnalytics } from '../../analytics.js';
|
|
19
|
+
import type { BrandlabContactAttachmentErrorReason, UseBrandlabContactAttachmentsOptions } from '../../contact/attachments-client.js';
|
|
20
|
+
import { type SkinEntry } from '../skin.js';
|
|
21
|
+
import { type QuoteDialogSkin } from './quote-dialog.js';
|
|
22
|
+
export declare const QUOTE_FORM_VARIANTS: readonly ["card", "plain", "inline"];
|
|
23
|
+
export type QuoteFormVariant = (typeof QUOTE_FORM_VARIANTS)[number];
|
|
24
|
+
/** The form's id and its lead diagnostics' `form_id`, one per placement. */
|
|
25
|
+
export declare const QUOTE_FORM_PLACEMENTS: readonly ["quote-hero", "quote-dialog", "contact", "guide"];
|
|
26
|
+
export type QuoteFormPlacement = (typeof QUOTE_FORM_PLACEMENTS)[number];
|
|
27
|
+
/** The variant each placement takes unless the skin names one: the hero aside, the dialog and contact page, a guide. */
|
|
28
|
+
export declare const QUOTE_FORM_PLACEMENT_VARIANTS: Readonly<Record<QuoteFormPlacement, QuoteFormVariant>>;
|
|
29
|
+
export type QuoteFormSlot = 'root' | 'row' | 'field' | 'label' | 'input' | 'textarea' | 'drop' | 'dropIcon' | 'counter' | 'photos' | 'photo' | 'thumb' | 'remove' | 'submit' | 'consent' | 'errorSummary' | 'error' | 'status';
|
|
30
|
+
/** Labels always sit above their fields; density sets the spacing. */
|
|
31
|
+
export interface QuoteFormLayout {
|
|
32
|
+
readonly density?: 'comfortable' | 'compact';
|
|
33
|
+
}
|
|
34
|
+
export type QuoteFormSkin = SkinEntry<QuoteFormSlot, QuoteFormVariant, QuoteFormLayout>;
|
|
35
|
+
/** Every visible phrase of the form, in the site's Australian English. */
|
|
36
|
+
export interface QuoteFormCopy {
|
|
37
|
+
readonly labels: {
|
|
38
|
+
readonly name: string;
|
|
39
|
+
readonly suburb: string;
|
|
40
|
+
readonly phone: string;
|
|
41
|
+
readonly email: string;
|
|
42
|
+
/** For example "How can we help?". */
|
|
43
|
+
readonly message: string;
|
|
44
|
+
/** For example "Add photos". */
|
|
45
|
+
readonly photos: string;
|
|
46
|
+
};
|
|
47
|
+
/** The message placeholder, for example "Tell us about your project". */
|
|
48
|
+
readonly messagePlaceholder: string;
|
|
49
|
+
/** The site's recorded button label, for example "Get my free quote". */
|
|
50
|
+
readonly submit: string;
|
|
51
|
+
/** Busy labels: "Sending…", "Sending photos…" and "Preparing photos…". */
|
|
52
|
+
readonly sending: string;
|
|
53
|
+
readonly sendingPhotos: string;
|
|
54
|
+
readonly preparingPhotos: string;
|
|
55
|
+
/** The photo counter with `{count}` and `{max}`, for example "{count} of {max}". */
|
|
56
|
+
readonly photoCount: string;
|
|
57
|
+
/** Each remove button's name with `{number}`, for example "Remove photo {number}". */
|
|
58
|
+
readonly removePhoto: string;
|
|
59
|
+
/** The one consent line: the sentence, the words in it that link, and the privacy policy's path. */
|
|
60
|
+
readonly consent: {
|
|
61
|
+
readonly sentence: string;
|
|
62
|
+
readonly linkText: string;
|
|
63
|
+
readonly href: string;
|
|
64
|
+
};
|
|
65
|
+
/** The error summary's heading, for example "Please check these details". */
|
|
66
|
+
readonly errorSummary: string;
|
|
67
|
+
/** A message under each field after a submit attempt. `photos` is needed only when photos are required. */
|
|
68
|
+
readonly errors: {
|
|
69
|
+
readonly name: string;
|
|
70
|
+
readonly suburb: string;
|
|
71
|
+
readonly phone: string;
|
|
72
|
+
readonly phoneFormat: string;
|
|
73
|
+
readonly email: string;
|
|
74
|
+
readonly emailFormat: string;
|
|
75
|
+
readonly message: string;
|
|
76
|
+
readonly photos?: string;
|
|
77
|
+
};
|
|
78
|
+
/**
|
|
79
|
+
* Why a chosen photo was refused: not an image type, too large, all photos together too large, more than the
|
|
80
|
+
* limit (`{max}` allowed), or it couldn't be prepared (an iPhone photo that wouldn't convert). Needed when the
|
|
81
|
+
* contract takes photos.
|
|
82
|
+
*/
|
|
83
|
+
readonly photoErrors?: Readonly<Record<BrandlabContactAttachmentErrorReason, string>>;
|
|
84
|
+
/** Shown when sending fails: the photos were refused, or anything else (say how to call instead). */
|
|
85
|
+
readonly failed: {
|
|
86
|
+
readonly attachment: string;
|
|
87
|
+
readonly general: string;
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
export interface QuoteFormProps extends QuoteFormSkin {
|
|
91
|
+
readonly formId: QuoteFormPlacement;
|
|
92
|
+
/** The contact contract from src/config/contact.ts. */
|
|
93
|
+
readonly contract: BrandlabContactContract;
|
|
94
|
+
/** The site's defineBrandlabAnalytics binding. */
|
|
95
|
+
readonly analytics?: BrandlabAnalytics | undefined;
|
|
96
|
+
/** Photo limits; the contract's attachment mode decides the default. */
|
|
97
|
+
readonly attachmentPolicy?: ContactAttachmentPolicy | undefined;
|
|
98
|
+
/** The thank-you page opened after an accepted enquiry, for example `/thank-you`. */
|
|
99
|
+
readonly redirectTo: string;
|
|
100
|
+
readonly copy: QuoteFormCopy;
|
|
101
|
+
/** The enquiry email's page row, for example "Contact". */
|
|
102
|
+
readonly pageLabel?: string | undefined;
|
|
103
|
+
/** The runtime's honeypot field (default `website`). */
|
|
104
|
+
readonly honeypotField?: string | undefined;
|
|
105
|
+
/** The contact endpoint (default `/api/contact`). */
|
|
106
|
+
readonly contactEndpoint?: string | undefined;
|
|
107
|
+
/** Client navigation to the thank-you page, for example `router.push`; a full page load by default. */
|
|
108
|
+
readonly navigate?: ((href: string) => void) | undefined;
|
|
109
|
+
/** Test seams: a fetch implementation and the photo preparation's options. */
|
|
110
|
+
readonly fetcher?: typeof fetch | undefined;
|
|
111
|
+
readonly attachmentOptions?: Omit<UseBrandlabContactAttachmentsOptions, 'policy'> | undefined;
|
|
112
|
+
}
|
|
113
|
+
export declare function QuoteForm({ formId, contract, analytics, attachmentPolicy, redirectTo, copy, pageLabel, honeypotField, contactEndpoint, navigate, fetcher, attachmentOptions, variant, layout, classes, }: QuoteFormProps): import("react/jsx-runtime").JSX.Element;
|
|
114
|
+
export interface CreateQuoteFormOptions {
|
|
115
|
+
readonly contract: BrandlabContactContract;
|
|
116
|
+
readonly analytics?: BrandlabAnalytics | undefined;
|
|
117
|
+
readonly attachmentPolicy?: ContactAttachmentPolicy | undefined;
|
|
118
|
+
/** The thank-you page, for example `/thank-you`. */
|
|
119
|
+
readonly redirectTo: string;
|
|
120
|
+
readonly copy: QuoteFormCopy;
|
|
121
|
+
/** The site's quote form skin; leave `variant` unset so each placement takes its own. */
|
|
122
|
+
readonly skin?: QuoteFormSkin | undefined;
|
|
123
|
+
/** The quote dialog's skin. */
|
|
124
|
+
readonly dialogSkin?: QuoteDialogSkin | undefined;
|
|
125
|
+
readonly honeypotField?: string | undefined;
|
|
126
|
+
readonly contactEndpoint?: string | undefined;
|
|
127
|
+
readonly navigate?: ((href: string) => void) | undefined;
|
|
128
|
+
readonly fetcher?: typeof fetch | undefined;
|
|
129
|
+
readonly attachmentOptions?: Omit<UseBrandlabContactAttachmentsOptions, 'policy'> | undefined;
|
|
130
|
+
}
|
|
131
|
+
/** What a page passes to the bound form: plain data, so a server component can render it. */
|
|
132
|
+
export interface BoundQuoteFormProps {
|
|
133
|
+
readonly formId: QuoteFormPlacement;
|
|
134
|
+
readonly pageLabel?: string;
|
|
135
|
+
/** This placement's own placeholder, for example on a cost guide. */
|
|
136
|
+
readonly messagePlaceholder?: string;
|
|
137
|
+
readonly layout?: QuoteFormLayout;
|
|
138
|
+
}
|
|
139
|
+
export interface BoundQuoteDialogProps {
|
|
140
|
+
/** The dialog's H2, for example "Get a free quote". */
|
|
141
|
+
readonly title: string;
|
|
142
|
+
/** The close button's accessible name, for example "Close". */
|
|
143
|
+
readonly closeLabel: string;
|
|
144
|
+
readonly pageLabel?: string;
|
|
145
|
+
readonly messagePlaceholder?: string;
|
|
146
|
+
}
|
|
147
|
+
export interface BoundQuoteForm {
|
|
148
|
+
readonly QuoteForm: (props: BoundQuoteFormProps) => JSX.Element;
|
|
149
|
+
readonly QuoteDialog: (props: BoundQuoteDialogProps) => JSX.Element;
|
|
150
|
+
}
|
|
151
|
+
/**
|
|
152
|
+
* Binds the form to the site's contract, analytics, photo policy, thank-you page, copy and skin. Call it once in the
|
|
153
|
+
* site's own `'use client'` file (`src/site/quote-form.tsx`) and export the two components:
|
|
154
|
+
*
|
|
155
|
+
* ```tsx
|
|
156
|
+
* 'use client'
|
|
157
|
+
* const quote = createQuoteForm({ contract: contactContract, analytics, redirectTo: '/thank-you', copy, skin: skin.quoteForm })
|
|
158
|
+
* export const QuoteForm = quote.QuoteForm
|
|
159
|
+
* export const QuoteDialog = quote.QuoteDialog
|
|
160
|
+
* ```
|
|
161
|
+
*
|
|
162
|
+
* It checks the contract and copy when the module loads, so a form that couldn't send fails the build instead.
|
|
163
|
+
*/
|
|
164
|
+
export declare function createQuoteForm(options: CreateQuoteFormOptions): BoundQuoteForm;
|
|
165
|
+
//# sourceMappingURL=quote-form.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"quote-form.d.ts","sourceRoot":"","sources":["../../../src/ui-kit/form/quote-form.tsx"],"names":[],"mappings":"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"}
|