trc-ui 0.0.0-stage → 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/dist/index.js ADDED
@@ -0,0 +1,685 @@
1
+ "use client";
2
+
3
+ // src/utils/cx.ts
4
+ function cx(...classes) {
5
+ return classes.filter(Boolean).join(" ");
6
+ }
7
+
8
+ // src/components/typography.tsx
9
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
10
+ function AccentText({ text, accentLast = 0 }) {
11
+ if (text.includes("*")) {
12
+ return /* @__PURE__ */ jsx(Fragment, { children: text.split("*").map(
13
+ (part, i) => i % 2 === 1 ? /* @__PURE__ */ jsx("span", { className: "trc-accent", children: part }, i) : part
14
+ ) });
15
+ }
16
+ const words = text.split(" ");
17
+ if (!accentLast || words.length < 2) return /* @__PURE__ */ jsx(Fragment, { children: text });
18
+ const n = Math.min(accentLast, words.length - 1);
19
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
20
+ words.slice(0, -n).join(" "),
21
+ " ",
22
+ /* @__PURE__ */ jsx("span", { className: "trc-accent", children: words.slice(-n).join(" ") })
23
+ ] });
24
+ }
25
+ var plainText = (text) => text.replaceAll("*", "");
26
+ function Heading({ as: Tag = "h2", size = "xl", accentLast, className, children, ...props }) {
27
+ return /* @__PURE__ */ jsx(Tag, { className: cx("trc-display", `trc-heading--${size}`, className), ...props, children: typeof children === "string" ? /* @__PURE__ */ jsx(AccentText, { text: children, accentLast }) : children });
28
+ }
29
+ function Text({ as, size = "md", tone = "muted", className, ...props }) {
30
+ const Tag = as ?? "p";
31
+ return /* @__PURE__ */ jsx(Tag, { className: cx("trc-text", `trc-text--${size}`, `trc-text--${tone}`, className), ...props });
32
+ }
33
+ function StepNumber({ n, size = "md", className }) {
34
+ return /* @__PURE__ */ jsx("span", { "aria-hidden": true, className: cx("trc-step-number", size === "sm" && "trc-step-number--sm", className), children: String(n).padStart(2, "0") });
35
+ }
36
+
37
+ // src/components/pill.tsx
38
+ import {
39
+ forwardRef
40
+ } from "react";
41
+
42
+ // src/utils/icons.tsx
43
+ import { jsx as jsx2 } from "react/jsx-runtime";
44
+ var base = {
45
+ xmlns: "http://www.w3.org/2000/svg",
46
+ viewBox: "0 0 24 24",
47
+ fill: "none",
48
+ stroke: "currentColor",
49
+ strokeWidth: 2,
50
+ strokeLinecap: "round",
51
+ strokeLinejoin: "round",
52
+ "aria-hidden": true
53
+ };
54
+ var ArrowRight = (props) => /* @__PURE__ */ jsx2("svg", { ...base, ...props, children: /* @__PURE__ */ jsx2("path", { d: "M5 12h14M13 6l6 6-6 6" }) });
55
+ var ArrowLeft = (props) => /* @__PURE__ */ jsx2("svg", { ...base, ...props, children: /* @__PURE__ */ jsx2("path", { d: "M19 12H5M11 18l-6-6 6-6" }) });
56
+ var Spinner = (props) => /* @__PURE__ */ jsx2("svg", { ...base, ...props, children: /* @__PURE__ */ jsx2("path", { d: "M21 12a9 9 0 1 1-6.22-8.56" }) });
57
+ var ChevronDown = (props) => /* @__PURE__ */ jsx2("svg", { ...base, ...props, children: /* @__PURE__ */ jsx2("path", { d: "m6 9 6 6 6-6" }) });
58
+ var Check = (props) => /* @__PURE__ */ jsx2("svg", { ...base, ...props, children: /* @__PURE__ */ jsx2("path", { d: "M20 6 9 17l-5-5" }) });
59
+ var Menu = (props) => /* @__PURE__ */ jsx2("svg", { ...base, ...props, children: /* @__PURE__ */ jsx2("path", { d: "M4 7h16M4 12h16M4 17h16" }) });
60
+ var Close = (props) => /* @__PURE__ */ jsx2("svg", { ...base, ...props, children: /* @__PURE__ */ jsx2("path", { d: "M18 6 6 18M6 6l12 12" }) });
61
+ var ArrowUp = (props) => /* @__PURE__ */ jsx2("svg", { ...base, ...props, children: /* @__PURE__ */ jsx2("path", { d: "M12 19V5M6 11l6-6 6 6" }) });
62
+
63
+ // src/components/pill.tsx
64
+ import { Fragment as Fragment2, jsx as jsx3, jsxs as jsxs2 } from "react/jsx-runtime";
65
+ var pillClass = ({ solid, size }, className) => cx("trc-pill", size === "sm" && "trc-pill--sm", solid && "trc-pill--solid", className);
66
+ function PillInner({ icon, children }) {
67
+ return /* @__PURE__ */ jsxs2(Fragment2, { children: [
68
+ /* @__PURE__ */ jsx3("span", { "aria-hidden": true, className: "trc-pill__fill" }),
69
+ /* @__PURE__ */ jsx3("span", { "aria-hidden": true, className: "trc-pill__icon", children: icon ?? /* @__PURE__ */ jsx3(ArrowRight, {}) }),
70
+ /* @__PURE__ */ jsx3("span", { className: "trc-pill__label", children })
71
+ ] });
72
+ }
73
+ var PillButton = forwardRef(function PillButton2({ solid, size = "md", icon, loading = false, disabled, type = "button", className, children, ...props }, ref) {
74
+ return /* @__PURE__ */ jsx3(
75
+ "button",
76
+ {
77
+ ref,
78
+ type,
79
+ disabled: disabled || loading,
80
+ "aria-busy": loading || void 0,
81
+ className: pillClass({ solid, size }, className),
82
+ ...props,
83
+ children: /* @__PURE__ */ jsx3(PillInner, { icon: loading ? /* @__PURE__ */ jsx3(Spinner, { className: "trc-spin" }) : icon, children })
84
+ }
85
+ );
86
+ });
87
+ var PillLink = forwardRef(function PillLink2({ as, solid, size = "md", icon, className, children, ...props }, ref) {
88
+ const Tag = as ?? "a";
89
+ return /* @__PURE__ */ jsx3(Tag, { ref, className: pillClass({ solid, size }, className), ...props, children: /* @__PURE__ */ jsx3(PillInner, { icon, children }) });
90
+ });
91
+
92
+ // src/components/surfaces.tsx
93
+ import { jsx as jsx4, jsxs as jsxs3 } from "react/jsx-runtime";
94
+ function Container({ as, size = "default", className, ...props }) {
95
+ const Tag = as ?? "div";
96
+ return /* @__PURE__ */ jsx4(Tag, { className: cx("trc-container", size !== "default" && `trc-container--${size}`, className), ...props });
97
+ }
98
+ function Section({
99
+ as,
100
+ spacing = "md",
101
+ inset = false,
102
+ className,
103
+ children,
104
+ ...props
105
+ }) {
106
+ const Tag = as ?? "section";
107
+ if (inset) {
108
+ return /* @__PURE__ */ jsx4(Tag, { className: cx("trc-section-inset", className), ...props, children: /* @__PURE__ */ jsx4("div", { className: cx("trc-section-inset__card", spacing !== "none" && `trc-section--${spacing}`), children }) });
109
+ }
110
+ return /* @__PURE__ */ jsx4(Tag, { className: cx(spacing !== "none" && `trc-section--${spacing}`, className), ...props, children });
111
+ }
112
+ function Card({
113
+ as,
114
+ tone = "default",
115
+ padding = "md",
116
+ interactive = false,
117
+ watermark,
118
+ className,
119
+ children,
120
+ ...props
121
+ }) {
122
+ const Tag = as ?? "div";
123
+ return /* @__PURE__ */ jsxs3(
124
+ Tag,
125
+ {
126
+ className: cx(
127
+ "trc-card",
128
+ tone === "alt" && "trc-card--alt",
129
+ padding !== "none" && `trc-card--pad-${padding}`,
130
+ interactive && "trc-card--interactive",
131
+ !!watermark && "trc-card--watermark",
132
+ className
133
+ ),
134
+ ...props,
135
+ children: [
136
+ !!watermark && /* @__PURE__ */ jsx4("span", { "aria-hidden": true, className: "trc-card__watermark", children: watermark }),
137
+ children
138
+ ]
139
+ }
140
+ );
141
+ }
142
+
143
+ // src/components/field.tsx
144
+ import { createContext, forwardRef as forwardRef2, useContext, useId } from "react";
145
+ import { jsx as jsx5, jsxs as jsxs4 } from "react/jsx-runtime";
146
+ var FieldContext = createContext(null);
147
+ function Field({ label, hint, error, required = false, id, className, children }) {
148
+ const generated = useId();
149
+ const fieldId = id ?? generated;
150
+ const messageId = `${fieldId}-message`;
151
+ const message = error ?? hint;
152
+ return /* @__PURE__ */ jsx5(
153
+ FieldContext.Provider,
154
+ {
155
+ value: { id: fieldId, describedBy: message ? messageId : void 0, invalid: error != null, required },
156
+ children: /* @__PURE__ */ jsxs4("div", { className: cx("trc-field", className), children: [
157
+ /* @__PURE__ */ jsxs4("label", { htmlFor: fieldId, className: "trc-field__label", children: [
158
+ label,
159
+ required && /* @__PURE__ */ jsxs4("span", { "aria-hidden": true, className: "trc-field__required", children: [
160
+ " ",
161
+ "*"
162
+ ] })
163
+ ] }),
164
+ children,
165
+ message != null && /* @__PURE__ */ jsx5("p", { id: messageId, className: cx("trc-field__message", error != null && "trc-field__message--error"), children: message })
166
+ ] })
167
+ }
168
+ );
169
+ }
170
+ function useFieldProps(props) {
171
+ const field = useContext(FieldContext);
172
+ const invalid = props.invalid ?? field?.invalid ?? false;
173
+ return {
174
+ id: props.id ?? field?.id,
175
+ required: props.required ?? field?.required,
176
+ "aria-describedby": cx(field?.describedBy, props["aria-describedby"]) || void 0,
177
+ "aria-invalid": invalid || void 0
178
+ };
179
+ }
180
+ var Input = forwardRef2(function Input2({ invalid, className, ...props }, ref) {
181
+ const a11y = useFieldProps({ ...props, invalid });
182
+ return /* @__PURE__ */ jsx5("input", { ref, className: cx("trc-control", className), ...props, ...a11y });
183
+ });
184
+ var Textarea = forwardRef2(function Textarea2({ invalid, rows = 4, className, ...props }, ref) {
185
+ const a11y = useFieldProps({ ...props, invalid });
186
+ return /* @__PURE__ */ jsx5(
187
+ "textarea",
188
+ {
189
+ ref,
190
+ rows,
191
+ className: cx("trc-control", "trc-control--textarea", className),
192
+ ...props,
193
+ ...a11y
194
+ }
195
+ );
196
+ });
197
+ var Select = forwardRef2(function Select2({ invalid, placeholder, defaultValue, className, children, ...props }, ref) {
198
+ const a11y = useFieldProps({ ...props, invalid });
199
+ const initial = defaultValue ?? (props.value === void 0 && placeholder != null ? "" : void 0);
200
+ return /* @__PURE__ */ jsxs4("span", { className: cx("trc-select", className), children: [
201
+ /* @__PURE__ */ jsxs4("select", { ref, className: "trc-control trc-control--select", defaultValue: initial, ...props, ...a11y, children: [
202
+ placeholder != null && /* @__PURE__ */ jsx5("option", { value: "", disabled: true, children: placeholder }),
203
+ children
204
+ ] }),
205
+ /* @__PURE__ */ jsx5(ChevronDown, { className: "trc-select__chevron" })
206
+ ] });
207
+ });
208
+
209
+ // src/components/toggle.tsx
210
+ import { forwardRef as forwardRef3 } from "react";
211
+ import { jsx as jsx6, jsxs as jsxs5 } from "react/jsx-runtime";
212
+ function ToggleLabel({
213
+ kind,
214
+ label,
215
+ description,
216
+ className,
217
+ children
218
+ }) {
219
+ return /* @__PURE__ */ jsxs5("label", { className: cx("trc-toggle", `trc-toggle--${kind}`, className), children: [
220
+ children,
221
+ /* @__PURE__ */ jsxs5("span", { className: "trc-toggle__text", children: [
222
+ /* @__PURE__ */ jsx6("span", { className: "trc-toggle__label", children: label }),
223
+ description != null && /* @__PURE__ */ jsx6("span", { className: "trc-toggle__description", children: description })
224
+ ] })
225
+ ] });
226
+ }
227
+ var Checkbox = forwardRef3(function Checkbox2({ label, description, className, ...props }, ref) {
228
+ return /* @__PURE__ */ jsx6(ToggleLabel, { kind: "checkbox", label, description, className, children: /* @__PURE__ */ jsxs5("span", { className: "trc-checkbox", children: [
229
+ /* @__PURE__ */ jsx6("input", { ref, type: "checkbox", className: "trc-checkbox__input", ...props }),
230
+ /* @__PURE__ */ jsx6(Check, { className: "trc-checkbox__check" })
231
+ ] }) });
232
+ });
233
+ var Switch = forwardRef3(function Switch2({ label, description, className, ...props }, ref) {
234
+ return /* @__PURE__ */ jsx6(ToggleLabel, { kind: "switch", label, description, className, children: /* @__PURE__ */ jsxs5("span", { className: "trc-switch", children: [
235
+ /* @__PURE__ */ jsx6("input", { ref, type: "checkbox", role: "switch", className: "trc-switch__input", ...props }),
236
+ /* @__PURE__ */ jsx6("span", { "aria-hidden": true, className: "trc-switch__thumb" })
237
+ ] }) });
238
+ });
239
+
240
+ // src/components/site-header.tsx
241
+ import { useEffect as useEffect2, useId as useId2, useRef as useRef2, useState } from "react";
242
+ import { createPortal } from "react-dom";
243
+
244
+ // src/hooks/use-modal.ts
245
+ import { useEffect, useRef } from "react";
246
+ var FOCUSABLE = 'a[href], button:not([disabled]), input:not([disabled]), select:not([disabled]), textarea:not([disabled]), [tabindex]:not([tabindex="-1"])';
247
+ function useModal(open, panelRef, onClose) {
248
+ const close = useRef(onClose);
249
+ close.current = onClose;
250
+ useEffect(() => {
251
+ const panel = panelRef.current;
252
+ if (!open || !panel) return;
253
+ const previous = document.activeElement;
254
+ const overflow = document.body.style.overflow;
255
+ document.body.style.overflow = "hidden";
256
+ (panel.querySelector("[data-autofocus]") ?? panel.querySelector(FOCUSABLE) ?? panel).focus();
257
+ const onKeyDown = (e) => {
258
+ if (e.key === "Escape") {
259
+ e.stopPropagation();
260
+ close.current();
261
+ return;
262
+ }
263
+ if (e.key !== "Tab") return;
264
+ const items = [...panel.querySelectorAll(FOCUSABLE)].filter((el) => el.offsetParent !== null);
265
+ const first = items[0];
266
+ const last = items[items.length - 1];
267
+ if (!first || !last) {
268
+ e.preventDefault();
269
+ return;
270
+ }
271
+ if (e.shiftKey && document.activeElement === first) {
272
+ e.preventDefault();
273
+ last.focus();
274
+ } else if (!e.shiftKey && document.activeElement === last) {
275
+ e.preventDefault();
276
+ first.focus();
277
+ }
278
+ };
279
+ document.addEventListener("keydown", onKeyDown);
280
+ return () => {
281
+ document.removeEventListener("keydown", onKeyDown);
282
+ document.body.style.overflow = overflow;
283
+ previous?.focus();
284
+ };
285
+ }, [open, panelRef]);
286
+ }
287
+
288
+ // src/components/site-header.tsx
289
+ import { jsx as jsx7, jsxs as jsxs6 } from "react/jsx-runtime";
290
+ var isMenu = (item) => "items" in item;
291
+ function SiteHeader({
292
+ logo,
293
+ logoLabel,
294
+ homeHref = "/",
295
+ nav = [],
296
+ cta,
297
+ actions,
298
+ mobileFooter,
299
+ linkAs: LinkTag = "a",
300
+ scrollThreshold = 24,
301
+ menuLabel = "Menu",
302
+ closeLabel = "Close",
303
+ className
304
+ }) {
305
+ const [scrolled, setScrolled] = useState(false);
306
+ const [open, setOpen] = useState(false);
307
+ useEffect2(() => {
308
+ const update = () => setScrolled(window.scrollY > scrollThreshold);
309
+ update();
310
+ window.addEventListener("scroll", update, { passive: true });
311
+ return () => window.removeEventListener("scroll", update);
312
+ }, [scrollThreshold]);
313
+ return /* @__PURE__ */ jsxs6("header", { className: cx("trc-header", className), children: [
314
+ /* @__PURE__ */ jsxs6("div", { className: cx("trc-header__bar", scrolled && "trc-header__bar--scrolled"), children: [
315
+ /* @__PURE__ */ jsx7(LinkTag, { href: homeHref, "aria-label": logoLabel, className: "trc-header__logo", children: logo }),
316
+ nav.length > 0 && /* @__PURE__ */ jsx7("nav", { className: "trc-header__nav", children: nav.map(
317
+ (item, i) => isMenu(item) ? /* @__PURE__ */ jsx7(NavDropdown, { menu: item, linkAs: LinkTag }, i) : /* @__PURE__ */ jsx7(
318
+ LinkTag,
319
+ {
320
+ href: item.href,
321
+ "aria-current": item.active ? "page" : void 0,
322
+ className: cx("trc-header__link", item.active && "trc-header__link--active"),
323
+ children: item.label
324
+ },
325
+ i
326
+ )
327
+ ) }),
328
+ /* @__PURE__ */ jsxs6("div", { className: "trc-header__end", children: [
329
+ actions && /* @__PURE__ */ jsx7("div", { className: "trc-header__actions", children: actions }),
330
+ cta && /* @__PURE__ */ jsx7(PillLink, { as: LinkTag, href: cta.href, size: "sm", className: "trc-header__cta", children: cta.label }),
331
+ (nav.length > 0 || cta || mobileFooter) && /* @__PURE__ */ jsx7(
332
+ "button",
333
+ {
334
+ type: "button",
335
+ className: "trc-header__burger",
336
+ "aria-label": menuLabel,
337
+ "aria-expanded": open,
338
+ onClick: () => setOpen(true),
339
+ children: /* @__PURE__ */ jsx7(Menu, {})
340
+ }
341
+ )
342
+ ] })
343
+ ] }),
344
+ /* @__PURE__ */ jsx7(
345
+ MobileMenu,
346
+ {
347
+ open,
348
+ onClose: () => setOpen(false),
349
+ nav,
350
+ cta,
351
+ footer: mobileFooter,
352
+ linkAs: LinkTag,
353
+ title: menuLabel,
354
+ closeLabel
355
+ }
356
+ )
357
+ ] });
358
+ }
359
+ function NavDropdown({ menu, linkAs: LinkTag }) {
360
+ const id = useId2();
361
+ const [open, setOpen] = useState(false);
362
+ const closeTimer = useRef2(void 0);
363
+ const button = useRef2(null);
364
+ const show = () => {
365
+ window.clearTimeout(closeTimer.current);
366
+ setOpen(true);
367
+ };
368
+ const hide = () => {
369
+ closeTimer.current = window.setTimeout(() => setOpen(false), 120);
370
+ };
371
+ useEffect2(() => () => window.clearTimeout(closeTimer.current), []);
372
+ return /* @__PURE__ */ jsxs6(
373
+ "div",
374
+ {
375
+ className: "trc-dropdown",
376
+ onMouseEnter: show,
377
+ onMouseLeave: hide,
378
+ onKeyDown: (e) => {
379
+ if (e.key === "Escape" && open) {
380
+ setOpen(false);
381
+ button.current?.focus();
382
+ }
383
+ },
384
+ onBlur: (e) => !e.currentTarget.contains(e.relatedTarget) && setOpen(false),
385
+ children: [
386
+ /* @__PURE__ */ jsxs6(
387
+ "button",
388
+ {
389
+ ref: button,
390
+ type: "button",
391
+ "aria-expanded": open,
392
+ "aria-controls": id,
393
+ onClick: () => setOpen((o) => !o),
394
+ className: cx("trc-header__link", "trc-dropdown__trigger", open && "trc-dropdown__trigger--open"),
395
+ children: [
396
+ menu.label,
397
+ /* @__PURE__ */ jsx7(ChevronDown, { className: "trc-dropdown__chevron" })
398
+ ]
399
+ }
400
+ ),
401
+ /* @__PURE__ */ jsx7("div", { id, className: cx("trc-dropdown__panel", open && "trc-dropdown__panel--open"), children: /* @__PURE__ */ jsx7("ul", { className: "trc-dropdown__list", children: menu.items.map((item, i) => /* @__PURE__ */ jsx7("li", { children: /* @__PURE__ */ jsxs6(
402
+ LinkTag,
403
+ {
404
+ href: item.href,
405
+ "aria-current": item.active ? "page" : void 0,
406
+ onClick: () => setOpen(false),
407
+ className: "trc-dropdown__item",
408
+ children: [
409
+ /* @__PURE__ */ jsx7("span", { className: "trc-dropdown__label", children: item.label }),
410
+ item.description != null && /* @__PURE__ */ jsx7("span", { className: "trc-dropdown__description", children: item.description })
411
+ ]
412
+ }
413
+ ) }, i)) }) })
414
+ ]
415
+ }
416
+ );
417
+ }
418
+ function MobileMenu({
419
+ open,
420
+ onClose,
421
+ nav,
422
+ cta,
423
+ footer,
424
+ linkAs: LinkTag,
425
+ title,
426
+ closeLabel
427
+ }) {
428
+ const titleId = useId2();
429
+ const panel = useRef2(null);
430
+ const [mounted, setMounted] = useState(false);
431
+ useEffect2(() => setMounted(true), []);
432
+ useModal(open, panel, onClose);
433
+ if (!mounted) return null;
434
+ return createPortal(
435
+ /* @__PURE__ */ jsxs6("div", { className: cx("trc-drawer", open && "trc-drawer--open"), "aria-hidden": !open, children: [
436
+ /* @__PURE__ */ jsx7("div", { className: "trc-drawer__overlay", onClick: onClose }),
437
+ /* @__PURE__ */ jsxs6(
438
+ "div",
439
+ {
440
+ ref: panel,
441
+ role: "dialog",
442
+ "aria-modal": "true",
443
+ "aria-labelledby": titleId,
444
+ tabIndex: -1,
445
+ className: "trc-drawer__panel",
446
+ children: [
447
+ /* @__PURE__ */ jsxs6("div", { className: "trc-drawer__head", children: [
448
+ /* @__PURE__ */ jsx7("p", { id: titleId, className: "trc-drawer__title", children: title }),
449
+ /* @__PURE__ */ jsx7("button", { type: "button", className: "trc-drawer__close", "aria-label": closeLabel, onClick: onClose, children: /* @__PURE__ */ jsx7(Close, {}) })
450
+ ] }),
451
+ /* @__PURE__ */ jsx7("nav", { className: "trc-mobile-nav", children: nav.map(
452
+ (item, i) => isMenu(item) ? /* @__PURE__ */ jsxs6("div", { className: "trc-mobile-nav__group", children: [
453
+ /* @__PURE__ */ jsx7("p", { className: "trc-mobile-nav__heading", children: item.label }),
454
+ /* @__PURE__ */ jsx7("ul", { className: "trc-mobile-nav__sub", children: item.items.map((sub, j) => /* @__PURE__ */ jsx7("li", { children: /* @__PURE__ */ jsx7(
455
+ LinkTag,
456
+ {
457
+ href: sub.href,
458
+ "aria-current": sub.active ? "page" : void 0,
459
+ onClick: onClose,
460
+ className: "trc-mobile-nav__sublink",
461
+ children: sub.label
462
+ }
463
+ ) }, j)) })
464
+ ] }, i) : /* @__PURE__ */ jsx7(
465
+ LinkTag,
466
+ {
467
+ href: item.href,
468
+ "aria-current": item.active ? "page" : void 0,
469
+ onClick: onClose,
470
+ className: cx("trc-mobile-nav__link", item.active && "trc-mobile-nav__link--active"),
471
+ children: item.label
472
+ },
473
+ i
474
+ )
475
+ ) }),
476
+ cta && /* @__PURE__ */ jsx7(PillLink, { as: LinkTag, href: cta.href, onClick: onClose, className: "trc-drawer__cta", children: cta.label }),
477
+ footer != null && /* @__PURE__ */ jsx7("div", { className: "trc-drawer__footer", children: footer })
478
+ ]
479
+ }
480
+ )
481
+ ] }),
482
+ document.body
483
+ );
484
+ }
485
+
486
+ // src/components/site-footer.tsx
487
+ import { jsx as jsx8, jsxs as jsxs7 } from "react/jsx-runtime";
488
+ function SiteFooter({
489
+ logo,
490
+ logoLabel,
491
+ homeHref = "/",
492
+ tagline,
493
+ children,
494
+ columns = [],
495
+ columnsLabel = "Site map",
496
+ copyright,
497
+ legal = [],
498
+ legalLabel = "Legal",
499
+ linkAs: LinkTag = "a",
500
+ className
501
+ }) {
502
+ return /* @__PURE__ */ jsx8("footer", { className: cx("trc-footer", className), children: /* @__PURE__ */ jsxs7("div", { className: "trc-footer__card", children: [
503
+ /* @__PURE__ */ jsxs7("div", { className: "trc-footer__main", children: [
504
+ /* @__PURE__ */ jsxs7("div", { className: "trc-footer__brand", children: [
505
+ logo != null && /* @__PURE__ */ jsx8(LinkTag, { href: homeHref, "aria-label": logoLabel, className: "trc-footer__logo", children: logo }),
506
+ tagline != null && /* @__PURE__ */ jsx8("p", { className: "trc-footer__tagline", children: tagline }),
507
+ children != null && /* @__PURE__ */ jsx8("div", { className: "trc-footer__extra", children })
508
+ ] }),
509
+ columns.length > 0 && /* @__PURE__ */ jsx8("nav", { className: "trc-footer__columns", "aria-label": columnsLabel, children: columns.map((col, i) => /* @__PURE__ */ jsxs7("div", { children: [
510
+ /* @__PURE__ */ jsx8("p", { className: "trc-footer__heading", children: col.title }),
511
+ /* @__PURE__ */ jsx8("ul", { className: "trc-footer__list", children: col.links.map((link, j) => /* @__PURE__ */ jsx8("li", { children: /* @__PURE__ */ jsx8(LinkTag, { href: link.href, className: "trc-footer__link", children: link.label }) }, j)) })
512
+ ] }, i)) })
513
+ ] }),
514
+ (copyright != null || legal.length > 0) && /* @__PURE__ */ jsxs7("div", { className: "trc-footer__bottom", children: [
515
+ copyright != null && /* @__PURE__ */ jsx8("p", { className: "trc-footer__copyright", children: copyright }),
516
+ legal.length > 0 && /* @__PURE__ */ jsx8("nav", { "aria-label": legalLabel, className: "trc-footer__legal", children: legal.map((link, i) => /* @__PURE__ */ jsx8(LinkTag, { href: link.href, className: "trc-footer__legal-link", children: link.label }, i)) })
517
+ ] })
518
+ ] }) });
519
+ }
520
+
521
+ // src/components/hero.tsx
522
+ import { useEffect as useEffect3, useRef as useRef3, useState as useState2 } from "react";
523
+ import { jsx as jsx9, jsxs as jsxs8 } from "react/jsx-runtime";
524
+ var delay = (ms) => ({ "--trc-delay": `${ms}ms` });
525
+ function PageHero({
526
+ title,
527
+ accentLast,
528
+ description,
529
+ meta,
530
+ image,
531
+ imagePosition = "50% 50%",
532
+ children,
533
+ className
534
+ }) {
535
+ const words = title.split(" ").length;
536
+ return /* @__PURE__ */ jsx9("section", { className: cx("trc-hero-inset", className), children: /* @__PURE__ */ jsxs8("div", { className: "trc-page-hero", children: [
537
+ /* @__PURE__ */ jsx9("div", { "aria-hidden": true, className: "trc-hero__media", children: typeof image === "string" ? /* @__PURE__ */ jsx9("img", { src: image, alt: "", className: "trc-hero__image", style: { objectPosition: imagePosition } }) : image != null ? image : /* @__PURE__ */ jsx9("div", { className: "trc-page-hero__glow" }) }),
538
+ /* @__PURE__ */ jsx9("div", { "aria-hidden": true, className: "trc-page-hero__veil" }),
539
+ /* @__PURE__ */ jsxs8("div", { className: "trc-page-hero__content", children: [
540
+ /* @__PURE__ */ jsx9("h1", { className: "trc-display trc-page-hero__title trc-enter", style: delay(150), children: /* @__PURE__ */ jsx9(AccentText, { text: title, accentLast: accentLast ?? (words > 3 ? 2 : 1) }) }),
541
+ description != null && /* @__PURE__ */ jsx9("p", { className: "trc-page-hero__description trc-enter", style: delay(250), children: description }),
542
+ meta != null && /* @__PURE__ */ jsx9("div", { className: "trc-page-hero__meta trc-enter", style: delay(350), children: meta }),
543
+ children != null && /* @__PURE__ */ jsx9("div", { className: "trc-page-hero__actions trc-enter", style: delay(450), children })
544
+ ] })
545
+ ] }) });
546
+ }
547
+ var prefersReducedMotion = () => window.matchMedia("(prefers-reduced-motion: reduce)").matches;
548
+ function HeroVideo({ clips, children, aside, fade = 1.2, parallax = true, className }) {
549
+ const videos = useRef3([]);
550
+ const section = useRef3(null);
551
+ const media = useRef3(null);
552
+ const [active, setActive] = useState2(-1);
553
+ useEffect3(() => {
554
+ if (clips.length === 0) return;
555
+ const start = Math.floor(Math.random() * clips.length);
556
+ const video = videos.current[start];
557
+ if (!prefersReducedMotion()) void video?.play().catch(() => {
558
+ });
559
+ const frame = requestAnimationFrame(() => setActive(start));
560
+ return () => {
561
+ cancelAnimationFrame(frame);
562
+ video?.pause();
563
+ };
564
+ }, [clips.length]);
565
+ useEffect3(() => {
566
+ const el = section.current;
567
+ const layer = media.current;
568
+ if (!parallax || !el || !layer || prefersReducedMotion()) return;
569
+ let frame = 0;
570
+ const update = () => {
571
+ frame = 0;
572
+ const rect = el.getBoundingClientRect();
573
+ const progress = Math.min(Math.max(-rect.top / rect.height, 0), 1);
574
+ layer.style.transform = `translate3d(0, ${progress * 120}px, 0)`;
575
+ };
576
+ const onScroll = () => {
577
+ if (!frame) frame = requestAnimationFrame(update);
578
+ };
579
+ update();
580
+ window.addEventListener("scroll", onScroll, { passive: true });
581
+ return () => {
582
+ window.removeEventListener("scroll", onScroll);
583
+ cancelAnimationFrame(frame);
584
+ };
585
+ }, [parallax]);
586
+ const playNext = (i) => {
587
+ const next = videos.current[(i + 1) % clips.length];
588
+ if (!next) return;
589
+ next.currentTime = 0;
590
+ void next.play().catch(() => {
591
+ });
592
+ setActive((i + 1) % clips.length);
593
+ };
594
+ return /* @__PURE__ */ jsx9("section", { ref: section, className: cx("trc-hero-inset", className), children: /* @__PURE__ */ jsxs8("div", { className: "trc-hero-video", children: [
595
+ /* @__PURE__ */ jsx9("div", { ref: media, "aria-hidden": true, className: "trc-hero__media trc-hero-video__media", children: clips.map((clip, i) => /* @__PURE__ */ jsxs8(
596
+ "video",
597
+ {
598
+ ref: (el) => {
599
+ videos.current[i] = el;
600
+ },
601
+ className: cx("trc-hero__image", "trc-hero-video__clip", i === active && "trc-hero-video__clip--active"),
602
+ style: { transitionDuration: `${fade}s` },
603
+ muted: true,
604
+ playsInline: true,
605
+ preload: "metadata",
606
+ poster: clip.poster,
607
+ onTimeUpdate: (e) => {
608
+ const v = e.currentTarget;
609
+ if (i === active && v.duration && v.duration - v.currentTime <= fade) playNext(i);
610
+ },
611
+ onEnded: () => i === active && playNext(i),
612
+ children: [
613
+ clip.webm && /* @__PURE__ */ jsx9("source", { src: clip.webm, type: "video/webm" }),
614
+ clip.mp4 && /* @__PURE__ */ jsx9("source", { src: clip.mp4, type: "video/mp4" })
615
+ ]
616
+ },
617
+ i
618
+ )) }),
619
+ /* @__PURE__ */ jsx9("div", { "aria-hidden": true, className: "trc-hero-video__veil" }),
620
+ /* @__PURE__ */ jsxs8("div", { className: "trc-hero-video__content", children: [
621
+ /* @__PURE__ */ jsx9("div", { className: "trc-hero-video__main", children }),
622
+ aside != null && /* @__PURE__ */ jsx9("div", { className: "trc-hero-video__aside", children: aside })
623
+ ] })
624
+ ] }) });
625
+ }
626
+
627
+ // src/components/back-to-top.tsx
628
+ import { useEffect as useEffect4, useState as useState3 } from "react";
629
+ import { jsx as jsx10 } from "react/jsx-runtime";
630
+ function BackToTop({ label = "Back to top", threshold = 0.8, className }) {
631
+ const [visible, setVisible] = useState3(false);
632
+ useEffect4(() => {
633
+ const update = () => setVisible(window.scrollY > window.innerHeight * threshold);
634
+ update();
635
+ window.addEventListener("scroll", update, { passive: true });
636
+ return () => window.removeEventListener("scroll", update);
637
+ }, [threshold]);
638
+ return /* @__PURE__ */ jsx10(
639
+ "button",
640
+ {
641
+ type: "button",
642
+ onClick: () => window.scrollTo({
643
+ top: 0,
644
+ behavior: window.matchMedia("(prefers-reduced-motion: reduce)").matches ? "auto" : "smooth"
645
+ }),
646
+ "aria-label": label,
647
+ "aria-hidden": !visible,
648
+ tabIndex: visible ? 0 : -1,
649
+ className: cx("trc-back-to-top", visible && "trc-back-to-top--visible", className),
650
+ children: /* @__PURE__ */ jsx10(ArrowUp, {})
651
+ }
652
+ );
653
+ }
654
+ export {
655
+ AccentText,
656
+ ArrowLeft,
657
+ ArrowRight,
658
+ ArrowUp,
659
+ BackToTop,
660
+ Card,
661
+ Check,
662
+ Checkbox,
663
+ ChevronDown,
664
+ Close,
665
+ Container,
666
+ Field,
667
+ Heading,
668
+ HeroVideo,
669
+ Input,
670
+ Menu,
671
+ PageHero,
672
+ PillButton,
673
+ PillLink,
674
+ Section,
675
+ Select,
676
+ SiteFooter,
677
+ SiteHeader,
678
+ StepNumber,
679
+ Switch,
680
+ Text,
681
+ Textarea,
682
+ cx,
683
+ plainText
684
+ };
685
+ //# sourceMappingURL=index.js.map