@oods/components-react 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,1191 @@
1
+ import { getStatusPresentation, getToneTokenSet, resolveStatusGlyph, getBannerToneTokenSet } from './chunk-G74PKSW3.js';
2
+ import { dateTimeInputValue, currencyMinorUnits, formatDateTime, formatReadOnlyValue } from '@oods/component-contracts';
3
+ import * as React3 from 'react';
4
+ import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
5
+ import { Slot } from '@radix-ui/react-slot';
6
+
7
+ var classes = (...values) => values.filter(Boolean).join(" ");
8
+ var describedByIds = (id, help, validation, nativeDescription) => [
9
+ nativeDescription,
10
+ help ? `${id}-description` : void 0,
11
+ validation?.message ? validation.id ?? `${id}-validation` : void 0
12
+ ].filter(Boolean).join(" ") || void 0;
13
+ var RequiredIndicator = ({
14
+ required,
15
+ children
16
+ }) => required && children !== null ? children ?? /* @__PURE__ */ jsx("span", { className: "oods-field-required form-field__required-indicator", "aria-hidden": "true", children: "*" }) : null;
17
+ var FieldFrame = ({
18
+ id,
19
+ label,
20
+ help,
21
+ description,
22
+ validation,
23
+ density = "comfortable",
24
+ required,
25
+ requiredIndicator,
26
+ checkboxControl,
27
+ className,
28
+ style,
29
+ children
30
+ }) => {
31
+ const resolvedHelp = help ?? description;
32
+ const validationTone = validation?.state === "error" ? "critical" : validation?.state === "warning" ? "warning" : validation?.state;
33
+ const validationTokens = validationTone ? getToneTokenSet(validationTone) : void 0;
34
+ const validationStyle = validationTokens ? {
35
+ "--cmp-input-message-border": validationTokens.border,
36
+ "--cmp-input-message-text": validationTokens.foreground,
37
+ "--form-field-border": validationTokens.border,
38
+ "--form-field-border-active": validationTokens.border,
39
+ "--form-field-background-active": validationTokens.background,
40
+ "--form-field-validation-color": validationTokens.foreground,
41
+ "--form-field-focus-inner": validationTokens.border,
42
+ "--form-field-focus-outer": validationTokens.background,
43
+ "--form-field-accent": validationTokens.foreground
44
+ } : {};
45
+ return /* @__PURE__ */ jsxs(
46
+ "div",
47
+ {
48
+ className: classes(
49
+ "oods-field",
50
+ "form-field",
51
+ checkboxControl !== void 0 && "oods-checkbox",
52
+ className
53
+ ),
54
+ style: { ...validationStyle, ...style },
55
+ "data-density": density,
56
+ "data-validation-state": validation?.state,
57
+ children: [
58
+ /* @__PURE__ */ jsxs(
59
+ "label",
60
+ {
61
+ className: "form-field__label",
62
+ htmlFor: id,
63
+ children: [
64
+ checkboxControl,
65
+ /* @__PURE__ */ jsx("span", { className: "oods-field-label", children: label }),
66
+ /* @__PURE__ */ jsx(RequiredIndicator, { required, children: requiredIndicator })
67
+ ]
68
+ }
69
+ ),
70
+ checkboxControl === void 0 ? /* @__PURE__ */ jsx("div", { className: "form-field__control", children }) : null,
71
+ resolvedHelp ? /* @__PURE__ */ jsx("p", { id: `${id}-description`, className: "oods-field-help form-field__description", children: resolvedHelp }) : null,
72
+ validation?.message ? /* @__PURE__ */ jsx(
73
+ "p",
74
+ {
75
+ id: validation.id ?? `${id}-validation`,
76
+ className: "oods-field-error form-field__validation",
77
+ role: validation.state === "error" ? "alert" : "status",
78
+ "aria-live": validation.state === "error" ? "assertive" : "polite",
79
+ "data-state": validation.state,
80
+ children: validation.message
81
+ }
82
+ ) : null
83
+ ]
84
+ }
85
+ );
86
+ };
87
+ var InputPrimitive = React3.forwardRef(
88
+ ({
89
+ id,
90
+ label,
91
+ help,
92
+ description,
93
+ validation,
94
+ density,
95
+ requiredIndicator,
96
+ className,
97
+ style,
98
+ inputClassName,
99
+ inputStyle,
100
+ onChange,
101
+ onInput,
102
+ onValueChange,
103
+ onUpdate,
104
+ required,
105
+ "aria-describedby": nativeDescription,
106
+ type = "text",
107
+ ...rest
108
+ }, ref) => {
109
+ if (type === "datetime-local") {
110
+ if (rest.value !== void 0) rest.value = dateTimeInputValue(rest.value);
111
+ if (rest.defaultValue !== void 0) rest.defaultValue = dateTimeInputValue(rest.defaultValue);
112
+ }
113
+ const resolvedHelp = help ?? description;
114
+ const handleChange = (event) => {
115
+ onChange?.(event);
116
+ if (!event.defaultPrevented) {
117
+ onValueChange?.(event.currentTarget.value);
118
+ onUpdate?.(event.currentTarget.value);
119
+ }
120
+ };
121
+ return /* @__PURE__ */ jsx(
122
+ FieldFrame,
123
+ {
124
+ id,
125
+ label,
126
+ help: resolvedHelp,
127
+ validation,
128
+ density,
129
+ required,
130
+ requiredIndicator,
131
+ className,
132
+ style,
133
+ children: /* @__PURE__ */ jsx(
134
+ "input",
135
+ {
136
+ ref,
137
+ id,
138
+ type,
139
+ className: classes("oods-field-control", "form-field__input", inputClassName),
140
+ style: inputStyle,
141
+ "data-oods-component": "Input",
142
+ "data-validation-state": validation?.state,
143
+ "aria-describedby": describedByIds(id, resolvedHelp, validation, nativeDescription),
144
+ "aria-invalid": validation?.state === "error" || void 0,
145
+ required,
146
+ onInput,
147
+ onChange: handleChange,
148
+ ...rest
149
+ }
150
+ )
151
+ }
152
+ );
153
+ }
154
+ );
155
+ InputPrimitive.displayName = "OODS.Input";
156
+ var Input = InputPrimitive;
157
+ var DatePicker = React3.forwardRef(
158
+ ({ pickerClassName, pickerStyle, inputClassName, inputStyle, ...rest }, ref) => /* @__PURE__ */ jsx(
159
+ Input,
160
+ {
161
+ ref,
162
+ type: "date",
163
+ "data-oods-component": "DatePicker",
164
+ inputClassName: classes("form-field__date-picker", inputClassName, pickerClassName),
165
+ inputStyle: { ...inputStyle, ...pickerStyle },
166
+ ...rest
167
+ }
168
+ )
169
+ );
170
+ DatePicker.displayName = "OODS.DatePicker";
171
+ var Checkbox = React3.forwardRef(
172
+ ({
173
+ id,
174
+ label,
175
+ help,
176
+ description,
177
+ validation,
178
+ density,
179
+ requiredIndicator,
180
+ className,
181
+ style,
182
+ checkboxClassName,
183
+ checkboxStyle,
184
+ onChange,
185
+ onCheckedChange,
186
+ onUpdate,
187
+ required,
188
+ "aria-describedby": nativeDescription,
189
+ ...rest
190
+ }, ref) => {
191
+ const resolvedHelp = help ?? description;
192
+ const handleChange = (event) => {
193
+ onChange?.(event);
194
+ if (!event.defaultPrevented) {
195
+ onCheckedChange?.(event.currentTarget.checked);
196
+ onUpdate?.(event.currentTarget.checked);
197
+ }
198
+ };
199
+ const control = /* @__PURE__ */ jsx(
200
+ "input",
201
+ {
202
+ ref,
203
+ id,
204
+ type: "checkbox",
205
+ className: classes("form-field__checkbox", checkboxClassName),
206
+ style: checkboxStyle,
207
+ "data-oods-component": "Checkbox",
208
+ "data-validation-state": validation?.state,
209
+ "aria-describedby": describedByIds(id, resolvedHelp, validation, nativeDescription),
210
+ "aria-invalid": validation?.state === "error" || void 0,
211
+ required,
212
+ onChange: handleChange,
213
+ ...rest
214
+ }
215
+ );
216
+ return /* @__PURE__ */ jsx(
217
+ FieldFrame,
218
+ {
219
+ id,
220
+ label,
221
+ help: resolvedHelp,
222
+ validation,
223
+ density,
224
+ required,
225
+ requiredIndicator,
226
+ checkboxControl: control,
227
+ className,
228
+ style
229
+ }
230
+ );
231
+ }
232
+ );
233
+ Checkbox.displayName = "OODS.Checkbox";
234
+ var Select = React3.forwardRef(
235
+ ({
236
+ id,
237
+ label,
238
+ help,
239
+ description,
240
+ validation,
241
+ density,
242
+ requiredIndicator,
243
+ className,
244
+ style,
245
+ options,
246
+ placeholder,
247
+ selectClassName,
248
+ selectStyle,
249
+ onChange,
250
+ onValueChange,
251
+ onUpdate,
252
+ children,
253
+ required,
254
+ "aria-describedby": nativeDescription,
255
+ ...rest
256
+ }, ref) => {
257
+ const resolvedHelp = help ?? description;
258
+ const handleChange = (event) => {
259
+ onChange?.(event);
260
+ if (!event.defaultPrevented) {
261
+ onValueChange?.(event.currentTarget.value);
262
+ onUpdate?.(event.currentTarget.value);
263
+ }
264
+ };
265
+ return /* @__PURE__ */ jsx(
266
+ FieldFrame,
267
+ {
268
+ id,
269
+ label,
270
+ help: resolvedHelp,
271
+ validation,
272
+ density,
273
+ required,
274
+ requiredIndicator,
275
+ className,
276
+ style,
277
+ children: /* @__PURE__ */ jsxs(
278
+ "select",
279
+ {
280
+ ref,
281
+ id,
282
+ className: classes("oods-field-control", "form-field__select", selectClassName),
283
+ style: selectStyle,
284
+ "data-oods-component": "Select",
285
+ "data-validation-state": validation?.state,
286
+ "aria-describedby": describedByIds(id, resolvedHelp, validation, nativeDescription),
287
+ "aria-invalid": validation?.state === "error" || void 0,
288
+ required,
289
+ onChange: handleChange,
290
+ ...rest,
291
+ children: [
292
+ placeholder ? /* @__PURE__ */ jsx("option", { value: "", disabled: true, children: placeholder }) : null,
293
+ children ?? options?.map((option) => /* @__PURE__ */ jsx("option", { value: option.value, disabled: option.disabled, children: option.label }, option.value))
294
+ ]
295
+ }
296
+ )
297
+ }
298
+ );
299
+ }
300
+ );
301
+ Select.displayName = "OODS.Select";
302
+ var Textarea = React3.forwardRef(
303
+ ({
304
+ id,
305
+ label,
306
+ help,
307
+ description,
308
+ validation,
309
+ density,
310
+ requiredIndicator,
311
+ className,
312
+ style,
313
+ textareaClassName,
314
+ textareaStyle,
315
+ onChange,
316
+ onInput,
317
+ onValueChange,
318
+ onUpdate,
319
+ rows = 4,
320
+ required,
321
+ "aria-describedby": nativeDescription,
322
+ ...rest
323
+ }, ref) => {
324
+ const resolvedHelp = help ?? description;
325
+ const handleChange = (event) => {
326
+ onChange?.(event);
327
+ if (!event.defaultPrevented) {
328
+ onValueChange?.(event.currentTarget.value);
329
+ onUpdate?.(event.currentTarget.value);
330
+ }
331
+ };
332
+ return /* @__PURE__ */ jsx(
333
+ FieldFrame,
334
+ {
335
+ id,
336
+ label,
337
+ help: resolvedHelp,
338
+ validation,
339
+ density,
340
+ required,
341
+ requiredIndicator,
342
+ className,
343
+ style,
344
+ children: /* @__PURE__ */ jsx(
345
+ "textarea",
346
+ {
347
+ ref,
348
+ id,
349
+ className: classes(
350
+ "oods-field-control",
351
+ "form-field__input",
352
+ "form-field__textarea",
353
+ textareaClassName
354
+ ),
355
+ style: textareaStyle,
356
+ "data-oods-component": "Textarea",
357
+ "data-validation-state": validation?.state,
358
+ "aria-describedby": describedByIds(id, resolvedHelp, validation, nativeDescription),
359
+ "aria-invalid": validation?.state === "error" || void 0,
360
+ rows,
361
+ required,
362
+ onInput,
363
+ onChange: handleChange,
364
+ ...rest
365
+ }
366
+ )
367
+ }
368
+ );
369
+ }
370
+ );
371
+ Textarea.displayName = "OODS.Textarea";
372
+ var classes2 = (...values) => values.filter(Boolean).join(" ");
373
+ var Badge = React3.forwardRef(
374
+ ({
375
+ content,
376
+ status,
377
+ domain = "subscription",
378
+ tone: toneOverride,
379
+ emphasis = "subtle",
380
+ icon,
381
+ iconPosition = "start",
382
+ showIcon,
383
+ children,
384
+ className,
385
+ style,
386
+ title,
387
+ "aria-label": ariaLabel,
388
+ ...rest
389
+ }, ref) => {
390
+ const presentation = status ? getStatusPresentation(domain, status) : void 0;
391
+ const tone = toneOverride ?? presentation?.tone ?? "neutral";
392
+ const tokenSet = toneOverride !== void 0 ? getToneTokenSet(tone) : emphasis === "solid" ? presentation?.badge.solid ?? getToneTokenSet(tone) : presentation?.badge.subtle ?? getToneTokenSet(tone);
393
+ const resolvedContent = children ?? content ?? presentation?.label ?? status;
394
+ const resolvedIcon = icon ?? resolveStatusGlyph(presentation?.iconName);
395
+ const resolvedShowIcon = showIcon ?? resolvedIcon !== void 0;
396
+ const iconNode = resolvedShowIcon && resolvedIcon ? /* @__PURE__ */ jsx("span", { className: "oods-badge__icon statusable-badge__icon", "aria-hidden": "true", children: resolvedIcon }) : null;
397
+ return /* @__PURE__ */ jsxs(
398
+ "span",
399
+ {
400
+ ref,
401
+ className: classes2("oods-badge", "statusable-badge", className),
402
+ "data-oods-component": "Badge",
403
+ "data-status": status,
404
+ "data-domain": domain,
405
+ "data-tone": tone,
406
+ "data-emphasis": emphasis,
407
+ title: title ?? presentation?.description,
408
+ "aria-label": ariaLabel,
409
+ style: {
410
+ "--cmp-badge-background": tokenSet.background,
411
+ "--cmp-badge-border": tokenSet.border,
412
+ "--cmp-badge-text": tokenSet.foreground,
413
+ "--statusable-badge-background": tokenSet.background,
414
+ "--statusable-badge-border": tokenSet.border,
415
+ "--statusable-badge-foreground": tokenSet.foreground,
416
+ "--statusable-badge-icon-color": tokenSet.foreground,
417
+ ...style
418
+ },
419
+ ...rest,
420
+ children: [
421
+ iconPosition === "start" ? iconNode : null,
422
+ /* @__PURE__ */ jsx("span", { className: "oods-badge__label statusable-badge__label", children: resolvedContent }),
423
+ iconPosition === "end" ? iconNode : null
424
+ ]
425
+ }
426
+ );
427
+ }
428
+ );
429
+ Badge.displayName = "OODS.Badge";
430
+ var Banner = React3.forwardRef(
431
+ ({
432
+ title,
433
+ detail,
434
+ description,
435
+ content,
436
+ status,
437
+ domain = "subscription",
438
+ tone: toneOverride,
439
+ emphasis = "subtle",
440
+ icon,
441
+ actions,
442
+ onDismiss,
443
+ dismissLabel = "Dismiss notification",
444
+ showIcon,
445
+ children,
446
+ className,
447
+ style,
448
+ ...rest
449
+ }, ref) => {
450
+ const presentation = status ? getStatusPresentation(domain, status) : void 0;
451
+ const tone = toneOverride ?? presentation?.tone ?? "neutral";
452
+ const tokenSet = emphasis === "solid" ? presentation?.banner.solid ?? getToneTokenSet(tone) : presentation?.banner.subtle ?? getBannerToneTokenSet(tone);
453
+ const heading = title ?? presentation?.label ?? status;
454
+ const body = detail ?? description ?? children ?? content ?? presentation?.description;
455
+ const role = tone === "critical" || tone === "danger" ? "alert" : "status";
456
+ const resolvedIcon = icon ?? resolveStatusGlyph(presentation?.iconName);
457
+ const resolvedShowIcon = showIcon ?? resolvedIcon !== void 0;
458
+ return /* @__PURE__ */ jsxs(
459
+ "div",
460
+ {
461
+ ref,
462
+ role,
463
+ "aria-live": role === "alert" ? "assertive" : "polite",
464
+ className: classes2("oods-banner", "statusable-banner", className),
465
+ "data-oods-component": "Banner",
466
+ "data-status": status,
467
+ "data-domain": domain,
468
+ "data-tone": tone,
469
+ "data-emphasis": emphasis,
470
+ style: {
471
+ "--cmp-banner-background": tokenSet.background,
472
+ "--cmp-banner-border": tokenSet.border,
473
+ "--cmp-banner-text": tokenSet.foreground,
474
+ "--statusable-banner-background": tokenSet.background,
475
+ "--statusable-banner-border": tokenSet.border,
476
+ "--statusable-banner-foreground": tokenSet.foreground,
477
+ "--statusable-banner-icon-color": tokenSet.foreground,
478
+ ...style
479
+ },
480
+ ...rest,
481
+ children: [
482
+ resolvedShowIcon && resolvedIcon ? /* @__PURE__ */ jsx("span", { className: "oods-banner__icon statusable-banner__icon", "aria-hidden": "true", children: resolvedIcon }) : null,
483
+ /* @__PURE__ */ jsxs("div", { className: "oods-banner__content statusable-banner__content", children: [
484
+ heading ? /* @__PURE__ */ jsx("strong", { className: "oods-banner__title statusable-banner__title", children: heading }) : null,
485
+ body ? /* @__PURE__ */ jsx("p", { className: "oods-banner__detail statusable-banner__description", children: body }) : null,
486
+ actions ? /* @__PURE__ */ jsx("div", { className: "oods-banner__actions statusable-banner__actions", children: actions }) : null
487
+ ] }),
488
+ onDismiss ? /* @__PURE__ */ jsx(
489
+ "button",
490
+ {
491
+ type: "button",
492
+ className: "oods-banner-dismiss oods-banner__dismiss statusable-banner__dismiss",
493
+ "aria-label": dismissLabel,
494
+ onClick: () => onDismiss(),
495
+ children: /* @__PURE__ */ jsx("span", { "aria-hidden": "true", children: "\xD7" })
496
+ }
497
+ ) : null
498
+ ]
499
+ }
500
+ );
501
+ }
502
+ );
503
+ Banner.displayName = "OODS.Banner";
504
+ var Button = React3.forwardRef(
505
+ ({
506
+ asChild = false,
507
+ content,
508
+ children,
509
+ className,
510
+ intent = "neutral",
511
+ size = "md",
512
+ style,
513
+ type,
514
+ onClick,
515
+ onActivate,
516
+ ...rest
517
+ }, ref) => {
518
+ const Component = asChild ? Slot : "button";
519
+ const handleClick = (event) => {
520
+ onClick?.(event);
521
+ if (!event.defaultPrevented) onActivate?.(event);
522
+ };
523
+ const nativeProps = asChild ? {} : { type: type ?? "button" };
524
+ return /* @__PURE__ */ jsx(
525
+ Component,
526
+ {
527
+ ref,
528
+ className: classes2("oods-button", className),
529
+ "data-oods-component": "Button",
530
+ "data-intent": intent,
531
+ "data-size": size,
532
+ style: {
533
+ "--cmp-button-background": "var(--sys-surface-interactive-primary-default)",
534
+ "--cmp-button-background-hover": "var(--sys-surface-interactive-primary-hover)",
535
+ "--cmp-button-background-disabled": "var(--sys-surface-disabled)",
536
+ "--cmp-button-border": "var(--sys-border-subtle)",
537
+ "--cmp-button-text": "var(--sys-text-on-interactive)",
538
+ "--cmp-button-text-disabled": "var(--sys-text-disabled)",
539
+ ...style
540
+ },
541
+ onClick: handleClick,
542
+ ...nativeProps,
543
+ ...rest,
544
+ children: children ?? content
545
+ }
546
+ );
547
+ }
548
+ );
549
+ Button.displayName = "OODS.Button";
550
+ var Card = React3.forwardRef(
551
+ ({ as = "div", elevated = false, className, ...rest }, ref) => {
552
+ const Element = as;
553
+ return /* @__PURE__ */ jsx(
554
+ Element,
555
+ {
556
+ ref,
557
+ className: classes2("oods-card", className),
558
+ "data-oods-component": "Card",
559
+ "data-elevated": elevated ? "true" : "false",
560
+ ...rest
561
+ }
562
+ );
563
+ }
564
+ );
565
+ Card.displayName = "OODS.Card";
566
+ var Text = React3.forwardRef(
567
+ ({
568
+ as = "span",
569
+ content,
570
+ children,
571
+ label,
572
+ size = "md",
573
+ weight = "regular",
574
+ className,
575
+ "aria-description": ariaDescription,
576
+ ...rest
577
+ }, ref) => {
578
+ const Element = as;
579
+ return /* @__PURE__ */ jsx(
580
+ Element,
581
+ {
582
+ ref,
583
+ className: classes2("oods-text", className),
584
+ "data-oods-component": "Text",
585
+ "data-size": size,
586
+ "data-weight": weight,
587
+ "aria-description": ariaDescription ?? label,
588
+ ...rest,
589
+ children: children ?? content
590
+ }
591
+ );
592
+ }
593
+ );
594
+ Text.displayName = "OODS.Text";
595
+ var classes3 = (...values) => values.filter(Boolean).join(" ");
596
+ var humanize = (value) => value.split(/[_-]/).filter(Boolean).map((segment) => segment.charAt(0).toUpperCase() + segment.slice(1)).join(" ");
597
+ var StatusBadge = React3.forwardRef(
598
+ ({
599
+ status,
600
+ value,
601
+ label,
602
+ children,
603
+ content,
604
+ domain = "subscription",
605
+ tone,
606
+ emphasis = "subtle",
607
+ showIcon = true,
608
+ field,
609
+ statusField,
610
+ domainField,
611
+ readOnly,
612
+ compact,
613
+ variant,
614
+ title,
615
+ "aria-label": ariaLabel,
616
+ ...rest
617
+ }, ref) => {
618
+ const resolvedStatus = status ?? value ?? "unknown";
619
+ const presentation = getStatusPresentation(domain, resolvedStatus);
620
+ return /* @__PURE__ */ jsx(
621
+ Badge,
622
+ {
623
+ ref,
624
+ status: resolvedStatus,
625
+ domain,
626
+ tone: tone === "lifecycle" ? void 0 : tone,
627
+ emphasis: variant === "solid" ? "solid" : emphasis,
628
+ showIcon,
629
+ content: children ?? content,
630
+ title: title ?? presentation.description,
631
+ "aria-label": ariaLabel ?? label ?? `Status: ${presentation.label}`,
632
+ "data-oods-component": "StatusBadge",
633
+ ...rest
634
+ }
635
+ );
636
+ }
637
+ );
638
+ StatusBadge.displayName = "OODS.StatusBadge";
639
+ function formatPrice(amount, currency, fallback) {
640
+ if (amount === void 0 || !Number.isFinite(amount)) return fallback;
641
+ if (!currency) return new Intl.NumberFormat(void 0, { maximumFractionDigits: 2 }).format(amount);
642
+ try {
643
+ return new Intl.NumberFormat(void 0, {
644
+ style: "currency",
645
+ currency: currency.toUpperCase()
646
+ }).format(amount);
647
+ } catch {
648
+ return `${currency.toUpperCase()} ${amount.toFixed(2)}`;
649
+ }
650
+ }
651
+ var PriceBadge = React3.forwardRef(
652
+ ({
653
+ amountCents,
654
+ unitAmountCents,
655
+ minorUnits,
656
+ amount,
657
+ unitAmount,
658
+ currency,
659
+ currencyCode,
660
+ label,
661
+ value,
662
+ children,
663
+ emphasis = "subtle",
664
+ field,
665
+ amountField,
666
+ currencyField,
667
+ intervalField,
668
+ minorUnitsParameter,
669
+ className,
670
+ ...rest
671
+ }, ref) => {
672
+ const cents = amountCents ?? unitAmountCents;
673
+ const resolvedCurrency = currency ?? currencyCode;
674
+ const resolvedAmount = cents === void 0 ? amount ?? unitAmount : cents / currencyMinorUnits(resolvedCurrency, minorUnits);
675
+ const content = label ?? children ?? formatPrice(resolvedAmount, resolvedCurrency, value ?? "Price");
676
+ return /* @__PURE__ */ jsx(
677
+ Badge,
678
+ {
679
+ ref,
680
+ content,
681
+ emphasis,
682
+ className: classes3("oods-price-badge", className),
683
+ "data-oods-component": "PriceBadge",
684
+ "data-badge-variant": "price",
685
+ "data-price": "true",
686
+ "data-currency": resolvedCurrency?.toUpperCase(),
687
+ ...rest
688
+ }
689
+ );
690
+ }
691
+ );
692
+ PriceBadge.displayName = "OODS.PriceBadge";
693
+ function isRecord(value) {
694
+ return typeof value === "object" && value !== null && !Array.isArray(value);
695
+ }
696
+ function eventText(record, keys) {
697
+ for (const key of keys) {
698
+ const value = record[key];
699
+ if (typeof value === "string") return value;
700
+ if (typeof value === "number" || typeof value === "boolean") return String(value);
701
+ }
702
+ return void 0;
703
+ }
704
+ function normalizeTimelineEvents(values) {
705
+ return values.flatMap((value, index) => {
706
+ if (!isRecord(value)) {
707
+ return value === void 0 || value === null ? [] : [{ label: String(value) }];
708
+ }
709
+ const from = eventText(value, ["from"]);
710
+ const to = eventText(value, ["to"]);
711
+ return [{
712
+ id: eventText(value, ["id"]) ?? `event-${index}`,
713
+ label: eventText(value, ["label", "title"]) ?? (eventText(value, ["event", "status", "state"]) ? humanize(eventText(value, ["event", "status", "state"])) : void 0) ?? eventText(value, ["text", "name"]) ?? (to ? from ? `${humanize(from)} \u2192 ${humanize(to)}` : humanize(to) : void 0) ?? "Event",
714
+ from,
715
+ to,
716
+ status: eventText(value, ["status"]),
717
+ state: eventText(value, ["state"]),
718
+ timestamp: eventText(value, ["timestamp", "datetime", "time", "at", "createdAt", "updatedAt"]),
719
+ actor: eventText(value, ["actorId", "actor_id", "actor"]),
720
+ actorId: eventText(value, ["actorId", "actor_id"]),
721
+ reason: eventText(value, ["reason"]),
722
+ detail: eventText(value, ["detail", "description", "message"]) ?? (to ? from ? `${humanize(from)} \u2192 ${humanize(to)}` : humanize(to) : void 0)
723
+ }];
724
+ });
725
+ }
726
+ var TimelinePrimitive = React3.forwardRef(
727
+ ({
728
+ componentId,
729
+ defaultTitle,
730
+ kind,
731
+ source,
732
+ title = defaultTitle,
733
+ events,
734
+ history,
735
+ entries,
736
+ stateHistory,
737
+ status,
738
+ allowedTransitions,
739
+ showActorId = true,
740
+ showReason = true,
741
+ maxVisible,
742
+ children,
743
+ field,
744
+ historyField,
745
+ statesParameter,
746
+ auditLogField,
747
+ createdField,
748
+ updatedField,
749
+ eventField,
750
+ eventTimestampField,
751
+ eventOptionsParameter,
752
+ showFromState,
753
+ className,
754
+ ...rest
755
+ }, ref) => {
756
+ const allEvents = normalizeTimelineEvents(source ?? events ?? history ?? entries ?? stateHistory ?? []);
757
+ const visibleEvents = Number.isFinite(maxVisible) ? allEvents.slice(0, Math.max(0, Math.floor(maxVisible ?? 0))) : allEvents;
758
+ const transitionCount = allowedTransitions?.length ?? 0;
759
+ return /* @__PURE__ */ jsxs(
760
+ "div",
761
+ {
762
+ ref,
763
+ role: "log",
764
+ "aria-label": typeof title === "string" ? title : defaultTitle,
765
+ className: classes3("oods-timeline", `oods-${kind}-timeline`, className),
766
+ "data-oods-component": componentId,
767
+ "data-timeline-type": kind,
768
+ ...rest,
769
+ children: [
770
+ /* @__PURE__ */ jsx("h2", { className: "oods-timeline__title", children: title }),
771
+ kind === "status" && status ? /* @__PURE__ */ jsxs("p", { className: "oods-timeline__current", "data-timeline-current": "true", children: [
772
+ "Current status: ",
773
+ humanize(status)
774
+ ] }) : null,
775
+ transitionCount > 0 ? /* @__PURE__ */ jsxs("p", { children: [
776
+ "Allowed transitions: ",
777
+ allowedTransitions?.join(", ")
778
+ ] }) : null,
779
+ children ?? /* @__PURE__ */ jsx("ol", { className: "oods-timeline__events", children: visibleEvents.length === 0 ? /* @__PURE__ */ jsx("li", { "data-timeline-empty": "true", children: "No events" }) : visibleEvents.map((event, index) => {
780
+ const timestamp = event.timestamp ?? event.datetime;
781
+ const actor = event.actor ?? event.actorId ?? event.actor_id;
782
+ const eventLabel = event.label ?? event.title ?? (event.from && event.to ? `${humanize(event.from)} \u2192 ${humanize(event.to)}` : void 0) ?? (event.status ? humanize(event.status) : void 0) ?? (event.state ? humanize(event.state) : void 0) ?? "Event";
783
+ const detail = event.detail ?? event.description;
784
+ return /* @__PURE__ */ jsx("li", { children: /* @__PURE__ */ jsxs("article", { className: "oods-timeline__event", "data-timeline-event": "true", children: [
785
+ /* @__PURE__ */ jsx("p", { className: "oods-timeline__label", children: eventLabel }),
786
+ timestamp ? /* @__PURE__ */ jsx("time", { dateTime: timestamp, children: formatDateTime(timestamp) }) : null,
787
+ detail ? /* @__PURE__ */ jsx("p", { className: "oods-timeline__detail", children: detail }) : null,
788
+ showActorId && actor ? /* @__PURE__ */ jsxs("p", { className: "oods-timeline__actor", children: [
789
+ "Actor: ",
790
+ actor
791
+ ] }) : null,
792
+ showReason && event.reason ? /* @__PURE__ */ jsxs("p", { className: "oods-timeline__reason", children: [
793
+ "Reason: ",
794
+ event.reason
795
+ ] }) : null
796
+ ] }) }, event.id ?? `${timestamp ?? "event"}-${index}`);
797
+ }) })
798
+ ]
799
+ }
800
+ );
801
+ }
802
+ );
803
+ TimelinePrimitive.displayName = "OODS.TimelinePrimitive";
804
+ var StatusTimeline = React3.forwardRef(
805
+ (props, ref) => /* @__PURE__ */ jsx(
806
+ TimelinePrimitive,
807
+ {
808
+ ref,
809
+ componentId: "StatusTimeline",
810
+ defaultTitle: "Status Timeline",
811
+ kind: "status",
812
+ ...props
813
+ }
814
+ )
815
+ );
816
+ StatusTimeline.displayName = "OODS.StatusTimeline";
817
+ var AuditTimeline = React3.forwardRef(
818
+ ({ auditLog, ...props }, ref) => /* @__PURE__ */ jsx(
819
+ TimelinePrimitive,
820
+ {
821
+ ref,
822
+ componentId: "AuditTimeline",
823
+ defaultTitle: "Audit Timeline",
824
+ kind: "audit",
825
+ source: auditLog,
826
+ ...props
827
+ }
828
+ )
829
+ );
830
+ AuditTimeline.displayName = "OODS.AuditTimeline";
831
+ var CancellationSummary = React3.forwardRef(
832
+ ({
833
+ label,
834
+ title = label ?? "Cancellation Summary",
835
+ cancelAtPeriodEnd,
836
+ requestedAt,
837
+ reason,
838
+ code,
839
+ children,
840
+ field,
841
+ cancelAtPeriodEndField,
842
+ requestedAtField,
843
+ reasonField,
844
+ codeField,
845
+ className,
846
+ ...rest
847
+ }, ref) => {
848
+ const childValue = typeof children === "boolean" ? children ? "Yes" : "No" : children;
849
+ return /* @__PURE__ */ jsxs(
850
+ "section",
851
+ {
852
+ ref,
853
+ className: classes3("oods-cancellation-summary", className),
854
+ "data-oods-component": "CancellationSummary",
855
+ "data-summary-type": "cancellation",
856
+ "data-cancellation-state": cancelAtPeriodEnd === void 0 ? void 0 : String(cancelAtPeriodEnd),
857
+ ...rest,
858
+ children: [
859
+ /* @__PURE__ */ jsx("h2", { children: title }),
860
+ /* @__PURE__ */ jsxs("dl", { children: [
861
+ cancelAtPeriodEnd !== void 0 ? /* @__PURE__ */ jsxs("div", { "data-summary-item": "true", children: [
862
+ /* @__PURE__ */ jsx("dt", { children: "Cancel at period end" }),
863
+ /* @__PURE__ */ jsx("dd", { children: cancelAtPeriodEnd ? "Yes" : "No" })
864
+ ] }) : null,
865
+ requestedAt ? /* @__PURE__ */ jsxs("div", { "data-summary-item": "true", children: [
866
+ /* @__PURE__ */ jsx("dt", { children: "Requested at" }),
867
+ /* @__PURE__ */ jsx("dd", { children: formatDateTime(requestedAt) })
868
+ ] }) : null,
869
+ reason ? /* @__PURE__ */ jsxs("div", { "data-summary-item": "true", children: [
870
+ /* @__PURE__ */ jsx("dt", { children: "Reason" }),
871
+ /* @__PURE__ */ jsx("dd", { children: reason })
872
+ ] }) : null,
873
+ code ? /* @__PURE__ */ jsxs("div", { "data-summary-item": "true", children: [
874
+ /* @__PURE__ */ jsx("dt", { children: "Code" }),
875
+ /* @__PURE__ */ jsx("dd", { children: formatReadOnlyValue(code, "string", true) })
876
+ ] }) : null,
877
+ childValue !== void 0 && childValue !== null ? /* @__PURE__ */ jsxs("div", { "data-summary-item": "true", children: [
878
+ /* @__PURE__ */ jsx("dt", { children: label ?? "Cancellation" }),
879
+ /* @__PURE__ */ jsx("dd", { children: childValue })
880
+ ] }) : null
881
+ ] })
882
+ ]
883
+ }
884
+ );
885
+ }
886
+ );
887
+ CancellationSummary.displayName = "OODS.CancellationSummary";
888
+ var SearchInput = React3.forwardRef(
889
+ ({
890
+ id,
891
+ label = "Search",
892
+ value,
893
+ defaultValue = "",
894
+ placeholder = "Search\u2026",
895
+ clearable = true,
896
+ debounceMs,
897
+ debounce,
898
+ minQueryLength = 0,
899
+ field,
900
+ placeholderParameter,
901
+ debounceParameter,
902
+ minQueryLengthParameter,
903
+ clearableParameter,
904
+ disabled,
905
+ className,
906
+ onChange,
907
+ onValueChange,
908
+ onUpdate,
909
+ onSearch,
910
+ onClear,
911
+ onKeyDown,
912
+ ...rest
913
+ }, ref) => {
914
+ const generatedId = React3.useId();
915
+ const resolvedId = id ?? `oods-search-${generatedId.replace(/:/g, "")}`;
916
+ const [uncontrolledValue, setUncontrolledValue] = React3.useState(defaultValue);
917
+ const currentValue = value ?? uncontrolledValue;
918
+ const timerRef = React3.useRef(void 0);
919
+ const delay = Math.max(0, debounceMs ?? debounce ?? 0);
920
+ React3.useEffect(() => () => {
921
+ if (timerRef.current !== void 0) clearTimeout(timerRef.current);
922
+ }, []);
923
+ const publish = (nextValue) => {
924
+ if (timerRef.current !== void 0) clearTimeout(timerRef.current);
925
+ const emit = () => {
926
+ if (nextValue.length >= minQueryLength || nextValue.length === 0) {
927
+ onValueChange?.(nextValue);
928
+ onUpdate?.(nextValue);
929
+ onSearch?.(nextValue);
930
+ }
931
+ };
932
+ if (delay === 0) emit();
933
+ else timerRef.current = setTimeout(emit, delay);
934
+ };
935
+ const updateValue = (nextValue) => {
936
+ if (value === void 0) setUncontrolledValue(nextValue);
937
+ publish(nextValue);
938
+ };
939
+ const handleChange = (event) => {
940
+ onChange?.(event);
941
+ if (!event.defaultPrevented) updateValue(event.currentTarget.value);
942
+ };
943
+ const clear = () => {
944
+ onClear?.();
945
+ updateValue("");
946
+ };
947
+ return /* @__PURE__ */ jsxs(
948
+ "div",
949
+ {
950
+ role: "search",
951
+ "aria-label": label,
952
+ className: classes3("oods-search-input", className),
953
+ "data-oods-component": "SearchInput",
954
+ "data-behavioral": "search",
955
+ children: [
956
+ /* @__PURE__ */ jsx(
957
+ Input,
958
+ {
959
+ ...rest,
960
+ ref,
961
+ id: resolvedId,
962
+ type: "search",
963
+ label,
964
+ placeholder,
965
+ value: currentValue,
966
+ disabled,
967
+ inputClassName: "oods-search-input__control",
968
+ onChange: handleChange,
969
+ onKeyDown: (event) => {
970
+ onKeyDown?.(event);
971
+ if (!event.defaultPrevented && event.key === "Escape" && currentValue) clear();
972
+ }
973
+ }
974
+ ),
975
+ clearable && currentValue ? /* @__PURE__ */ jsx(
976
+ "button",
977
+ {
978
+ type: "button",
979
+ className: "oods-search-input__clear",
980
+ "aria-label": "Clear search",
981
+ disabled,
982
+ onClick: clear,
983
+ children: "\xD7"
984
+ }
985
+ ) : null
986
+ ]
987
+ }
988
+ );
989
+ }
990
+ );
991
+ SearchInput.displayName = "OODS.SearchInput";
992
+ function visiblePages(page, totalPages) {
993
+ if (totalPages <= 7) return Array.from({ length: totalPages }, (_, index) => index + 1);
994
+ const candidates = /* @__PURE__ */ new Set([1, totalPages, page - 1, page, page + 1]);
995
+ const pages = [...candidates].filter((candidate) => candidate >= 1 && candidate <= totalPages).sort((a, b) => a - b);
996
+ const result = [];
997
+ for (const candidate of pages) {
998
+ const previous = result[result.length - 1];
999
+ if (typeof previous === "number" && candidate - previous > 1) result.push("ellipsis");
1000
+ result.push(candidate);
1001
+ }
1002
+ return result;
1003
+ }
1004
+ var PaginationBar = React3.forwardRef(
1005
+ ({
1006
+ page = 1,
1007
+ pageSize = 25,
1008
+ totalItems = 0,
1009
+ totalPages: suppliedTotalPages,
1010
+ pageSizeOptions = [10, 25, 50, 100],
1011
+ showPageSizeSelector = false,
1012
+ showGotoPage = false,
1013
+ showItemRange = true,
1014
+ pageField,
1015
+ pageSizeField,
1016
+ totalItemsField,
1017
+ totalPagesField,
1018
+ pageSizeOptionsParameter,
1019
+ showPageSizeSelectorParameter,
1020
+ showGotoPageParameter,
1021
+ showItemRangeParameter,
1022
+ onPageChange,
1023
+ onPageSizeChange,
1024
+ onChange,
1025
+ onUpdate,
1026
+ className,
1027
+ "aria-label": ariaLabel = "Pagination",
1028
+ ...rest
1029
+ }, ref) => {
1030
+ const safePageSize = Number.isFinite(pageSize) && pageSize > 0 ? Math.floor(pageSize) : 25;
1031
+ const derivedTotalPages = safePageSize > 0 ? Math.ceil(Math.max(0, totalItems) / safePageSize) : 0;
1032
+ const totalPages = Math.max(0, Math.floor(suppliedTotalPages ?? derivedTotalPages));
1033
+ const currentPage = totalPages > 0 ? Math.max(1, Math.min(Math.floor(page), totalPages)) : 1;
1034
+ const goToPage = (nextPage) => {
1035
+ if (totalPages === 0) return;
1036
+ const target = Math.max(1, Math.min(Math.floor(nextPage), totalPages));
1037
+ if (target === currentPage) return;
1038
+ onPageChange?.(target);
1039
+ onChange?.(target);
1040
+ onUpdate?.(target);
1041
+ };
1042
+ return /* @__PURE__ */ jsxs(
1043
+ "nav",
1044
+ {
1045
+ ref,
1046
+ "aria-label": ariaLabel,
1047
+ className: classes3("oods-pagination-bar", className),
1048
+ "data-oods-component": "PaginationBar",
1049
+ "data-behavioral": "pagination",
1050
+ ...rest,
1051
+ children: [
1052
+ showItemRange ? /* @__PURE__ */ jsxs(Fragment, { children: [
1053
+ /* @__PURE__ */ jsx("span", { "data-pagination-count": "true", children: `${totalItems} ${totalItems === 1 ? "record" : "records"}` }),
1054
+ totalItems > 0 && totalPages > 1 && /* @__PURE__ */ jsx("span", { "data-pagination-range": "true", children: `Showing ${(currentPage - 1) * safePageSize + 1}\u2013${Math.min(currentPage * safePageSize, totalItems)} of ${totalItems}` })
1055
+ ] }) : null,
1056
+ totalPages > 1 ? /* @__PURE__ */ jsxs(Fragment, { children: [
1057
+ /* @__PURE__ */ jsx(
1058
+ "button",
1059
+ {
1060
+ type: "button",
1061
+ "aria-label": "Previous page",
1062
+ "data-pagination-prev": "true",
1063
+ disabled: totalPages === 0 || currentPage <= 1,
1064
+ onClick: () => goToPage(currentPage - 1),
1065
+ children: "\u2039"
1066
+ }
1067
+ ),
1068
+ /* @__PURE__ */ jsx("ol", { className: "oods-pagination-bar__pages", children: visiblePages(currentPage, totalPages).map((item, index) => item === "ellipsis" ? /* @__PURE__ */ jsx("li", { "aria-hidden": "true", children: "\u2026" }, `ellipsis-${index}`) : /* @__PURE__ */ jsx("li", { children: /* @__PURE__ */ jsx(
1069
+ "button",
1070
+ {
1071
+ type: "button",
1072
+ "aria-label": `Page ${item}`,
1073
+ "aria-current": item === currentPage ? "page" : void 0,
1074
+ "data-pagination-page": item,
1075
+ onClick: () => goToPage(item),
1076
+ children: item
1077
+ }
1078
+ ) }, item)) }),
1079
+ /* @__PURE__ */ jsx(
1080
+ "button",
1081
+ {
1082
+ type: "button",
1083
+ "aria-label": "Next page",
1084
+ "data-pagination-next": "true",
1085
+ disabled: totalPages === 0 || currentPage >= totalPages,
1086
+ onClick: () => goToPage(currentPage + 1),
1087
+ children: "\u203A"
1088
+ }
1089
+ ),
1090
+ /* @__PURE__ */ jsx("span", { "data-pagination-current": "true", children: `Page ${currentPage} of ${Math.max(1, totalPages)}` })
1091
+ ] }) : null,
1092
+ showPageSizeSelector ? /* @__PURE__ */ jsxs("label", { children: [
1093
+ "Items per page",
1094
+ /* @__PURE__ */ jsx(
1095
+ "select",
1096
+ {
1097
+ "aria-label": "Items per page",
1098
+ value: safePageSize,
1099
+ onChange: (event) => onPageSizeChange?.(Number(event.currentTarget.value)),
1100
+ children: pageSizeOptions.map((option) => /* @__PURE__ */ jsx("option", { value: option, children: option }, option))
1101
+ }
1102
+ )
1103
+ ] }) : null,
1104
+ showGotoPage ? /* @__PURE__ */ jsxs("label", { children: [
1105
+ "Go to page",
1106
+ /* @__PURE__ */ jsx(
1107
+ "input",
1108
+ {
1109
+ type: "number",
1110
+ min: 1,
1111
+ max: Math.max(1, totalPages),
1112
+ defaultValue: currentPage,
1113
+ onKeyDown: (event) => {
1114
+ if (event.key === "Enter") goToPage(Number(event.currentTarget.value));
1115
+ }
1116
+ }
1117
+ )
1118
+ ] }) : null
1119
+ ]
1120
+ }
1121
+ );
1122
+ }
1123
+ );
1124
+ PaginationBar.displayName = "OODS.PaginationBar";
1125
+ function asDate(value) {
1126
+ if (value === void 0) return void 0;
1127
+ const date = value instanceof Date ? value : new Date(value);
1128
+ return Number.isNaN(date.getTime()) ? void 0 : date;
1129
+ }
1130
+ function relativeLabel(date, now) {
1131
+ const seconds = Math.round((date.getTime() - now.getTime()) / 1e3);
1132
+ const units = [
1133
+ ["year", 31536e3],
1134
+ ["month", 2592e3],
1135
+ ["day", 86400],
1136
+ ["hour", 3600],
1137
+ ["minute", 60]
1138
+ ];
1139
+ const [unit, divisor] = units.find(([, candidate]) => Math.abs(seconds) >= candidate) ?? ["second", 1];
1140
+ return new Intl.RelativeTimeFormat(void 0, { numeric: "auto" }).format(
1141
+ Math.round(seconds / divisor),
1142
+ unit
1143
+ );
1144
+ }
1145
+ var RelativeTimestamp = React3.forwardRef(
1146
+ ({
1147
+ datetime,
1148
+ timestamp,
1149
+ value,
1150
+ updatedAt,
1151
+ createdAt,
1152
+ relative,
1153
+ label,
1154
+ text,
1155
+ timezone = "UTC",
1156
+ hideWhenEmpty,
1157
+ now,
1158
+ children,
1159
+ field,
1160
+ fallbackField,
1161
+ timezoneParameter,
1162
+ title,
1163
+ className,
1164
+ ...rest
1165
+ }, ref) => {
1166
+ const childRaw = typeof children === "string" || typeof children === "number" ? children : void 0;
1167
+ const raw = datetime ?? timestamp ?? value ?? updatedAt ?? createdAt ?? childRaw;
1168
+ const date = asDate(raw);
1169
+ if (hideWhenEmpty && !date) return null;
1170
+ const nowDate = asDate(now) ?? /* @__PURE__ */ new Date();
1171
+ const iso = date?.toISOString() ?? (raw === void 0 ? void 0 : String(raw));
1172
+ const absolute = formatDateTime(date, { timeZone: timezone });
1173
+ const content = relative ?? label ?? text ?? (now !== void 0 && date ? relativeLabel(date, nowDate) : absolute || children || "Unknown time");
1174
+ return /* @__PURE__ */ jsx(
1175
+ "time",
1176
+ {
1177
+ ref,
1178
+ dateTime: iso,
1179
+ title: title ?? absolute,
1180
+ className: classes3("oods-relative-timestamp", className),
1181
+ "data-oods-component": "RelativeTimestamp",
1182
+ suppressHydrationWarning: true,
1183
+ ...rest,
1184
+ children: content
1185
+ }
1186
+ );
1187
+ }
1188
+ );
1189
+ RelativeTimestamp.displayName = "OODS.RelativeTimestamp";
1190
+
1191
+ export { AuditTimeline, Badge, Banner, Button, CancellationSummary, Card, Checkbox, DatePicker, Input, PaginationBar, PriceBadge, RelativeTimestamp, SearchInput, Select, StatusBadge, StatusTimeline, Text, Textarea };