@vincihairclinic/design-system 0.1.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 ADDED
@@ -0,0 +1,75 @@
1
+ # Vinci Hair Clinic Design System
2
+
3
+ Shared foundations, CSS utilities, and React components for Vinci Hair Clinic
4
+ applications and WordPress/Elementor sites.
5
+
6
+ ## Start here
7
+
8
+ ```sh
9
+ npm install
10
+ npm run storybook
11
+ ```
12
+
13
+ The documentation opens locally in Storybook. For repository-level context,
14
+ use these guides:
15
+
16
+ - [Product context](PRODUCT.md)
17
+ - [Visual design language](DESIGN.md)
18
+ - [Foundations](docs/foundations.md)
19
+ - [Responsive behavior](docs/responsive-behavior.md)
20
+ - [Tokens](docs/tokens.md)
21
+ - [Accessibility](docs/accessibility.md)
22
+ - [Forms](docs/components/forms.md)
23
+ - [Conversational form](docs/components/conversational-form.md)
24
+ - [Navigation](docs/components/navigation.md)
25
+ - [Cards and content](docs/components/cards.md)
26
+ - [React](docs/platforms/react.md)
27
+ - [WordPress and Elementor](docs/platforms/wordpress-elementor.md)
28
+ - [Contributing](docs/contributing.md)
29
+ - [Releasing](docs/releasing.md)
30
+
31
+ ## React
32
+
33
+ ```sh
34
+ npm install @vinci-hair-clinic/design-system
35
+ ```
36
+
37
+ ```tsx
38
+ import "@vinci-hair-clinic/design-system/styles.css";
39
+ import { Button } from "@vinci-hair-clinic/design-system";
40
+
41
+ export function BookingCallToAction() {
42
+ return <Button>Book a consultation</Button>;
43
+ }
44
+ ```
45
+
46
+ See the [Button API](docs/components/button.md) for variants and native props.
47
+
48
+ ## WordPress and Elementor
49
+
50
+ ```sh
51
+ make plugin
52
+ ```
53
+
54
+ Install `artifacts/vinci-design-system-wordpress-0.1.0.zip` from the WordPress
55
+ plugin screen. Apply documented `vinci-*` classes through Elementor's
56
+ **Advanced → CSS Classes** field.
57
+
58
+ ## Commands
59
+
60
+ | Command | Purpose |
61
+ | --- | --- |
62
+ | `npm run storybook` | Run the living documentation locally. |
63
+ | `make build` | Build ESM, CJS, declarations, and distributable CSS. |
64
+ | `make docs` | Build the static Storybook. |
65
+ | `make plugin` | Generate the versioned WordPress plugin ZIP. |
66
+ | `make test` | Run source, accessibility, and documentation checks. |
67
+ | `make verify` | Run the complete release gate. |
68
+ | `make clean` | Remove generated artifacts only. |
69
+
70
+ ## License and assets
71
+
72
+ The package is public for technical distribution but is `UNLICENSED`.
73
+ Myriad Pro is referenced but not distributed; production use requires a valid
74
+ Adobe webfont license. No patient photography, clinic logo, or medical claims
75
+ are included in this repository.
package/dist/index.cjs ADDED
@@ -0,0 +1,530 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/index.ts
21
+ var index_exports = {};
22
+ __export(index_exports, {
23
+ AnnouncementBar: () => AnnouncementBar,
24
+ Button: () => Button,
25
+ Checkbox: () => Checkbox,
26
+ ChoiceChip: () => ChoiceChip,
27
+ ConversationPanel: () => ConversationPanel,
28
+ FieldWithAction: () => FieldWithAction,
29
+ Footer: () => Footer,
30
+ Header: () => Header,
31
+ IconButton: () => IconButton,
32
+ LocationCard: () => LocationCard,
33
+ LocationCombobox: () => LocationCombobox,
34
+ MessageBubble: () => MessageBubble,
35
+ NorwoodOptionCard: () => NorwoodOptionCard,
36
+ PricingCard: () => PricingCard,
37
+ ProgressHeader: () => ProgressHeader,
38
+ ResultsCard: () => ResultsCard,
39
+ StickyCTABar: () => StickyCTABar,
40
+ Switch: () => Switch,
41
+ TestimonialCard: () => TestimonialCard,
42
+ TextInput: () => TextInput,
43
+ TypingIndicator: () => TypingIndicator
44
+ });
45
+ module.exports = __toCommonJS(index_exports);
46
+
47
+ // src/components/Button.tsx
48
+ var import_react = require("react");
49
+ var import_jsx_runtime = require("react/jsx-runtime");
50
+ var Button = (0, import_react.forwardRef)(function Button2({
51
+ variant = "primary",
52
+ size = "md",
53
+ type = "button",
54
+ className,
55
+ ...props
56
+ }, ref) {
57
+ const classes = [
58
+ "vinci-btn",
59
+ `vinci-btn--${variant}`,
60
+ `vinci-btn--${size}`,
61
+ className
62
+ ].filter(Boolean).join(" ");
63
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { ref, type, className: classes, ...props });
64
+ });
65
+
66
+ // src/components/TextInput.tsx
67
+ var import_react2 = require("react");
68
+ var import_jsx_runtime2 = require("react/jsx-runtime");
69
+ var TextInput = (0, import_react2.forwardRef)(function TextInput2({
70
+ label,
71
+ helperText,
72
+ error,
73
+ size = "md",
74
+ id,
75
+ className,
76
+ "aria-describedby": describedBy,
77
+ "aria-invalid": invalid,
78
+ ...props
79
+ }, ref) {
80
+ const generatedId = (0, import_react2.useId)();
81
+ const inputId = id ?? `vinci-input-${generatedId}`;
82
+ const helperId = `${inputId}-help`;
83
+ const errorId = `${inputId}-error`;
84
+ const description = [describedBy, helperText ? helperId : null, error ? errorId : null].filter(Boolean).join(" ") || void 0;
85
+ const classes = [
86
+ "vinci-input",
87
+ size !== "md" && `vinci-input--${size}`,
88
+ error && "is-error",
89
+ className
90
+ ].filter(Boolean).join(" ");
91
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "vinci-field", children: [
92
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("label", { className: "vinci-form-label", htmlFor: inputId, children: label }),
93
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
94
+ "input",
95
+ {
96
+ ...props,
97
+ ref,
98
+ id: inputId,
99
+ className: classes,
100
+ "aria-describedby": description,
101
+ "aria-invalid": error ? true : invalid
102
+ }
103
+ ),
104
+ helperText && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "vinci-form-helper", id: helperId, children: helperText }),
105
+ error && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "vinci-form-helper is-error", id: errorId, children: error })
106
+ ] });
107
+ });
108
+
109
+ // src/components/Checkbox.tsx
110
+ var import_react3 = require("react");
111
+ var import_jsx_runtime3 = require("react/jsx-runtime");
112
+ var Checkbox = (0, import_react3.forwardRef)(function Checkbox2({ label, className, ...props }, ref) {
113
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("label", { className: "vinci-control", children: [
114
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
115
+ "input",
116
+ {
117
+ ...props,
118
+ ref,
119
+ type: "checkbox",
120
+ className: ["vinci-checkbox", className].filter(Boolean).join(" ")
121
+ }
122
+ ),
123
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: "vinci-form-label-inline", children: label })
124
+ ] });
125
+ });
126
+
127
+ // src/components/Switch.tsx
128
+ var import_react4 = require("react");
129
+ var import_jsx_runtime4 = require("react/jsx-runtime");
130
+ var Switch = (0, import_react4.forwardRef)(function Switch2({ label, className, ...props }, ref) {
131
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("label", { className: "vinci-control", children: [
132
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
133
+ "input",
134
+ {
135
+ ...props,
136
+ ref,
137
+ type: "checkbox",
138
+ role: "switch",
139
+ className: ["vinci-switch", className].filter(Boolean).join(" ")
140
+ }
141
+ ),
142
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "vinci-form-label-inline", children: label })
143
+ ] });
144
+ });
145
+
146
+ // src/components/Navigation.tsx
147
+ var import_react5 = require("react");
148
+ var import_jsx_runtime5 = require("react/jsx-runtime");
149
+ var themeClass = (theme) => `vinci-nav-theme--${theme}`;
150
+ var actionClass = "vinci-btn vinci-btn--md vinci-nav-action";
151
+ function Header({
152
+ theme = "white",
153
+ logo,
154
+ logoHref,
155
+ logoLabel,
156
+ links,
157
+ cta,
158
+ menuLabel,
159
+ closeMenuLabel,
160
+ className,
161
+ ...props
162
+ }) {
163
+ const [open, setOpen] = (0, import_react5.useState)(false);
164
+ const menuId = (0, import_react5.useId)();
165
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("header", { className: ["vinci-site-header", themeClass(theme), className].filter(Boolean).join(" "), ...props, children: /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "vinci-site-header__inner vinci-container", children: [
166
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("a", { className: "vinci-site-header__logo", href: logoHref, "aria-label": logoLabel, children: logo }),
167
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
168
+ "button",
169
+ {
170
+ className: "vinci-site-header__toggle",
171
+ type: "button",
172
+ "aria-label": open ? closeMenuLabel : menuLabel,
173
+ "aria-expanded": open,
174
+ "aria-controls": menuId,
175
+ onClick: () => setOpen(!open),
176
+ children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { className: "vinci-site-header__toggle-lines", "aria-hidden": "true" })
177
+ }
178
+ ),
179
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("nav", { className: "vinci-site-header__nav", id: menuId, "aria-label": menuLabel, "data-open": open, children: [
180
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("ul", { className: "vinci-site-header__links", children: links.map(({ label, href }) => /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("li", { children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("a", { href, children: label }) }, `${href}-${label}`)) }),
181
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("a", { className: actionClass, href: cta.href, children: cta.label })
182
+ ] })
183
+ ] }) });
184
+ }
185
+ function AnnouncementBar({ theme = "white", benefits, className, ...props }) {
186
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("aside", { className: ["vinci-announcement", themeClass(theme), className].filter(Boolean).join(" "), ...props, children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("ul", { className: "vinci-announcement__list vinci-container", children: benefits.map((benefit, index) => /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("li", { children: benefit }, index)) }) });
187
+ }
188
+ function StickyCTABar({ theme = "white", benefits, cta, countdown, className, ...props }) {
189
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("aside", { className: ["vinci-sticky-cta", themeClass(theme), className].filter(Boolean).join(" "), ...props, children: /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "vinci-sticky-cta__inner vinci-container", children: [
190
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("ul", { className: "vinci-sticky-cta__benefits", children: benefits.map((benefit, index) => /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("li", { children: benefit }, index)) }),
191
+ countdown && /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("p", { className: "vinci-sticky-cta__countdown", children: [
192
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { children: countdown.label }),
193
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("strong", { children: countdown.value })
194
+ ] }),
195
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("a", { className: actionClass, href: cta.href, children: cta.label })
196
+ ] }) });
197
+ }
198
+ function Footer({
199
+ theme = "white",
200
+ logo,
201
+ logoHref,
202
+ logoLabel,
203
+ description,
204
+ links,
205
+ menuLabel,
206
+ contact,
207
+ contactLabel,
208
+ socialLinks,
209
+ socialLabel,
210
+ paymentMethods,
211
+ copyright,
212
+ backToTop,
213
+ cta,
214
+ className,
215
+ ...props
216
+ }) {
217
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("footer", { className: ["vinci-site-footer", themeClass(theme), className].filter(Boolean).join(" "), ...props, children: /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "vinci-container", children: [
218
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "vinci-site-footer__main", children: [
219
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "vinci-site-footer__brand", children: [
220
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("a", { className: "vinci-site-footer__logo", href: logoHref, "aria-label": logoLabel, children: logo }),
221
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("p", { children: description }),
222
+ cta && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("a", { className: actionClass, href: cta.href, children: cta.label })
223
+ ] }),
224
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("nav", { "aria-label": menuLabel, children: [
225
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("h2", { children: menuLabel }),
226
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("ul", { children: links.map(({ label, href }) => /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("li", { children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("a", { href, children: label }) }, `${href}-${label}`)) })
227
+ ] }),
228
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("section", { "aria-label": contactLabel, children: [
229
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("h2", { children: contactLabel }),
230
+ contact
231
+ ] }),
232
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("nav", { "aria-label": socialLabel, children: [
233
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("h2", { children: socialLabel }),
234
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("ul", { className: "vinci-site-footer__social", children: socialLinks.map(({ label, href }) => /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("li", { children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("a", { href, children: label }) }, `${href}-${label}`)) })
235
+ ] })
236
+ ] }),
237
+ paymentMethods && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "vinci-site-footer__payments", children: paymentMethods }),
238
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "vinci-site-footer__bottom", children: [
239
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("small", { children: copyright }),
240
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("a", { href: backToTop.href, children: backToTop.label })
241
+ ] })
242
+ ] }) });
243
+ }
244
+
245
+ // src/components/Cards.tsx
246
+ var import_jsx_runtime6 = require("react/jsx-runtime");
247
+ function ResultsCard({
248
+ name,
249
+ treatment,
250
+ grafts,
251
+ graftsLabel,
252
+ timeline,
253
+ timelineLabel,
254
+ image,
255
+ compact = false,
256
+ className,
257
+ ...props
258
+ }) {
259
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("article", { className: ["vinci-card", "vinci-card--results", compact && "vinci-card--results-compact", className].filter(Boolean).join(" "), ...props, children: [
260
+ image?.src && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("img", { className: "vinci-card__image vinci-card__image--square", ...image }),
261
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "vinci-card__body", children: [
262
+ name && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("h3", { className: "vinci-card__title", children: name }),
263
+ treatment && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("p", { className: "vinci-card__description", children: treatment }),
264
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("dl", { className: "vinci-card__metrics", children: [
265
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "vinci-card__metric vinci-card__metric--grafts", children: [
266
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("dt", { children: graftsLabel }),
267
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("dd", { children: grafts })
268
+ ] }),
269
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "vinci-card__metric vinci-card__metric--timeline", children: [
270
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("dt", { children: timelineLabel }),
271
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("dd", { children: timeline })
272
+ ] })
273
+ ] })
274
+ ] })
275
+ ] });
276
+ }
277
+ function PricingCard({
278
+ title,
279
+ priceLabel,
280
+ price,
281
+ financing,
282
+ benefits,
283
+ description,
284
+ action,
285
+ className,
286
+ ...props
287
+ }) {
288
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("article", { className: ["vinci-card", "vinci-card--pricing", className].filter(Boolean).join(" "), ...props, children: [
289
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("h3", { className: "vinci-card__title", children: title }),
290
+ priceLabel && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("p", { className: "vinci-card__price-label", children: priceLabel }),
291
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("p", { className: "vinci-card__price", children: price }),
292
+ financing && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("p", { className: "vinci-card__financing", children: financing }),
293
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
294
+ "a",
295
+ {
296
+ className: "vinci-btn vinci-btn--primary vinci-btn--md vinci-card__action",
297
+ href: action.href,
298
+ children: action.label
299
+ }
300
+ ),
301
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("ul", { className: "vinci-card__benefits", children: benefits.map((benefit, index) => /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("li", { children: benefit }, index)) }),
302
+ description && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("p", { className: "vinci-card__description", children: description })
303
+ ] });
304
+ }
305
+ function LocationCard({
306
+ city,
307
+ address,
308
+ image,
309
+ href,
310
+ className,
311
+ ...props
312
+ }) {
313
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("article", { className: ["vinci-card", "vinci-card--location", className].filter(Boolean).join(" "), ...props, children: [
314
+ image?.src && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("img", { className: "vinci-card__image", ...image }),
315
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "vinci-card__body", children: [
316
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("h3", { className: "vinci-card__title", children: href ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("a", { href, children: city }) : city }),
317
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("p", { className: "vinci-card__description", children: address })
318
+ ] })
319
+ ] });
320
+ }
321
+ function TestimonialCard({
322
+ quote,
323
+ author,
324
+ date,
325
+ rating,
326
+ ratingLabel,
327
+ portrait,
328
+ verified = false,
329
+ verifiedLabel = "Verified",
330
+ className,
331
+ ...props
332
+ }) {
333
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("figure", { className: ["vinci-card", "vinci-card--testimonial", className].filter(Boolean).join(" "), ...props, children: [
334
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("blockquote", { className: "vinci-card__quote", children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("p", { children: quote }) }),
335
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("figcaption", { className: "vinci-card__reviewer", children: [
336
+ portrait?.src && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("img", { className: "vinci-card__portrait", ...portrait }),
337
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("span", { className: "vinci-card__reviewer-text", children: [
338
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("strong", { children: author }),
339
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("span", { className: "vinci-card__reviewer-meta", children: [
340
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { className: "vinci-card__rating", role: "img", "aria-label": ratingLabel, children: /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("span", { "aria-hidden": "true", children: [
341
+ "\u2605".repeat(rating),
342
+ "\u2606".repeat(5 - rating)
343
+ ] }) }),
344
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("span", { children: [
345
+ date,
346
+ verified && /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(import_jsx_runtime6.Fragment, { children: [
347
+ " \xB7 ",
348
+ verifiedLabel
349
+ ] })
350
+ ] })
351
+ ] })
352
+ ] })
353
+ ] })
354
+ ] });
355
+ }
356
+
357
+ // src/components/ConversationalForm.tsx
358
+ var import_react6 = require("react");
359
+ var import_jsx_runtime7 = require("react/jsx-runtime");
360
+ function ConversationPanel({ header, children, composer, footer, transcriptLabel }) {
361
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "vinci-conversation-panel", children: [
362
+ header,
363
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("section", { className: "vinci-conversation-panel__transcript", "aria-label": transcriptLabel, children }),
364
+ composer && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { className: "vinci-conversation-panel__composer", children: composer }),
365
+ footer && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("footer", { className: "vinci-conversation-panel__footer", children: footer })
366
+ ] });
367
+ }
368
+ function ProgressHeader({ assistantName, current, total, encouragement, completeLabel = "Complete", questionLabel = (step, count) => `Question ${step} of ${count}` }) {
369
+ const safeTotal = Math.max(1, total);
370
+ const safeCurrent = Math.max(0, Math.min(current, safeTotal));
371
+ const complete = safeCurrent >= safeTotal;
372
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("header", { className: "vinci-conversation-progress", children: [
373
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "vinci-conversation-progress__row", children: [
374
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("strong", { children: assistantName }),
375
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { children: complete ? completeLabel : questionLabel(safeCurrent + 1, safeTotal) })
376
+ ] }),
377
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "vinci-conversation-progress__visual", children: [
378
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("progress", { className: "vinci-conversation-progress__track", value: safeCurrent, max: safeTotal, "aria-label": assistantName }),
379
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { className: "vinci-conversation-progress__fill", style: { transform: `scaleX(${safeCurrent / safeTotal})` }, "aria-hidden": "true" })
380
+ ] }),
381
+ encouragement && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("p", { className: "vinci-conversation-progress__encouragement", children: encouragement })
382
+ ] });
383
+ }
384
+ function MessageBubble({ sender, children, avatar, editLabel, onEdit }) {
385
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: `vinci-conversation-message vinci-conversation-message--${sender}`, children: [
386
+ sender === "assistant" && avatar && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { className: "vinci-conversation-message__avatar", "aria-hidden": "true", children: avatar }),
387
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { className: "vinci-conversation-message__bubble", children }),
388
+ sender === "user" && onEdit && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(IconButton, { label: editLabel || "Edit answer", onClick: onEdit, icon: "edit" })
389
+ ] });
390
+ }
391
+ function TypingIndicator({ label }) {
392
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("span", { className: "vinci-conversation-typing", role: "status", "aria-label": label, children: [
393
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", {}),
394
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", {}),
395
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", {})
396
+ ] });
397
+ }
398
+ var ChoiceChip = (0, import_react6.forwardRef)(function ChoiceChip2({ selected = false, className, type = "button", ...props }, ref) {
399
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("button", { ...props, ref, type, className: ["vinci-conversation-chip", className].filter(Boolean).join(" "), "aria-pressed": selected });
400
+ });
401
+ var IconButton = (0, import_react6.forwardRef)(function IconButton2({ label, icon, className, type = "button", children, ...props }, ref) {
402
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("button", { ...props, ref, type, className: ["vinci-conversation-icon-button", className].filter(Boolean).join(" "), "aria-label": label, children: children || /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("svg", { "aria-hidden": "true", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [
403
+ icon === "edit" && /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(import_jsx_runtime7.Fragment, { children: [
404
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("path", { d: "M4 20h4l11-11-4-4L4 16v4Z" }),
405
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("path", { d: "m13 7 4 4" })
406
+ ] }),
407
+ icon === "close" && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("path", { d: "M5 5l14 14M19 5 5 19" }),
408
+ icon === "arrow" && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("path", { d: "m5 9 7 7 7-7" }),
409
+ icon === "check" && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("path", { d: "m4 12 5 5L20 6" })
410
+ ] }) });
411
+ });
412
+ var FieldWithAction = (0, import_react6.forwardRef)(function FieldWithAction2({ label, actionLabel, error, id, className, "aria-describedby": describedBy, ...props }, ref) {
413
+ const generatedId = (0, import_react6.useId)();
414
+ const inputId = id || `vinci-conversation-input-${generatedId}`;
415
+ const errorId = `${inputId}-error`;
416
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "vinci-conversation-field", children: [
417
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("label", { className: "vinci-form-label", htmlFor: inputId, children: label }),
418
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "vinci-conversation-field__row", children: [
419
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("input", { ...props, ref, id: inputId, className: ["vinci-input", className].filter(Boolean).join(" "), "aria-invalid": error ? true : props["aria-invalid"], "aria-describedby": [describedBy, error && errorId].filter(Boolean).join(" ") || void 0 }),
420
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("button", { className: "vinci-conversation-field__submit", type: "submit", "aria-label": actionLabel, children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("svg", { "aria-hidden": "true", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("path", { d: "M5 12h14m-6-6 6 6-6 6" }) }) })
421
+ ] }),
422
+ error && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { className: "vinci-form-helper is-error", id: errorId, children: error })
423
+ ] });
424
+ });
425
+ function LocationCombobox({ label, options, value, onChange, placeholder, noResultsLabel = "No results", name, disabled }) {
426
+ const id = (0, import_react6.useId)();
427
+ const [query, setQuery] = (0, import_react6.useState)("");
428
+ const [open, setOpen] = (0, import_react6.useState)(false);
429
+ const [active, setActive] = (0, import_react6.useState)(-1);
430
+ const chosen = options.find((option) => option.value === value);
431
+ const matches = options.filter((option) => option.label.toLocaleLowerCase().includes(query.toLocaleLowerCase()));
432
+ const display = open ? query : chosen?.label || "";
433
+ function select(option) {
434
+ onChange(option.value);
435
+ setQuery("");
436
+ setOpen(false);
437
+ }
438
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "vinci-conversation-combobox", children: [
439
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("label", { className: "vinci-form-label", htmlFor: `${id}-input`, children: label }),
440
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
441
+ "input",
442
+ {
443
+ id: `${id}-input`,
444
+ className: "vinci-input vinci-conversation-combobox__input",
445
+ role: "combobox",
446
+ "aria-autocomplete": "list",
447
+ "aria-expanded": open,
448
+ "aria-controls": `${id}-list`,
449
+ "aria-activedescendant": open && matches[active] ? `${id}-option-${active}` : void 0,
450
+ autoComplete: "off",
451
+ disabled,
452
+ placeholder,
453
+ value: display,
454
+ onFocus: () => {
455
+ setQuery("");
456
+ setActive(-1);
457
+ setOpen(true);
458
+ },
459
+ onChange: (event) => {
460
+ setQuery(event.target.value);
461
+ setActive(0);
462
+ setOpen(true);
463
+ },
464
+ onBlur: (event) => {
465
+ if (!event.currentTarget.parentElement?.contains(event.relatedTarget)) {
466
+ setOpen(false);
467
+ setQuery("");
468
+ }
469
+ },
470
+ onKeyDown: (event) => {
471
+ if (event.key === "Escape") {
472
+ setOpen(false);
473
+ setQuery("");
474
+ }
475
+ if (event.key === "ArrowDown") {
476
+ event.preventDefault();
477
+ setActive((index) => open ? Math.min(index + 1, Math.max(0, matches.length - 1)) : 0);
478
+ setOpen(true);
479
+ }
480
+ if (event.key === "ArrowUp") {
481
+ event.preventDefault();
482
+ setOpen(true);
483
+ setActive((index) => Math.max(0, index - 1));
484
+ }
485
+ if (event.key === "Enter" && open && matches[active]) {
486
+ event.preventDefault();
487
+ select(matches[active]);
488
+ }
489
+ }
490
+ }
491
+ ),
492
+ name && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("input", { type: "hidden", name, value, disabled }),
493
+ open && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { id: `${id}-list`, className: "vinci-conversation-combobox__list", role: "listbox", children: matches.length ? matches.map((option, index) => /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { id: `${id}-option-${index}`, className: "vinci-conversation-combobox__option", role: "option", "aria-selected": option.value === value, "data-active": index === active, onMouseDown: (event) => event.preventDefault(), onClick: () => select(option), children: option.label }, option.value)) : /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("p", { className: "vinci-conversation-combobox__empty", children: noResultsLabel }) })
494
+ ] });
495
+ }
496
+ var NorwoodOptionCard = (0, import_react6.forwardRef)(function NorwoodOptionCard2({ label, typeLabel, image, selected = false, className, type = "button", ...props }, ref) {
497
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("button", { ...props, ref, type, "aria-pressed": selected, className: ["vinci-norwood-card", className].filter(Boolean).join(" "), children: [
498
+ image && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { className: "vinci-norwood-card__art", children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("img", { className: "vinci-norwood-card__image", src: image.src, alt: image.alt }) }),
499
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { className: "vinci-norwood-card__label", children: label }),
500
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("span", { className: "vinci-norwood-card__type", children: [
501
+ typeLabel,
502
+ selected && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { className: "vinci-norwood-card__check", "aria-hidden": "true", children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("path", { d: "m4 12 5 5L20 6" }) }) })
503
+ ] })
504
+ ] });
505
+ });
506
+ // Annotate the CommonJS export names for ESM import in node:
507
+ 0 && (module.exports = {
508
+ AnnouncementBar,
509
+ Button,
510
+ Checkbox,
511
+ ChoiceChip,
512
+ ConversationPanel,
513
+ FieldWithAction,
514
+ Footer,
515
+ Header,
516
+ IconButton,
517
+ LocationCard,
518
+ LocationCombobox,
519
+ MessageBubble,
520
+ NorwoodOptionCard,
521
+ PricingCard,
522
+ ProgressHeader,
523
+ ResultsCard,
524
+ StickyCTABar,
525
+ Switch,
526
+ TestimonialCard,
527
+ TextInput,
528
+ TypingIndicator
529
+ });
530
+ //# sourceMappingURL=index.cjs.map