@oods/components-react 0.3.2 → 0.4.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/ported.cjs CHANGED
@@ -26,337 +26,6 @@ function _interopNamespace(e) {
26
26
  var React3__namespace = /*#__PURE__*/_interopNamespace(React3);
27
27
 
28
28
  // src/ported.tsx
29
-
30
- // src/status-map.json
31
- var status_map_default = {
32
- domains: {
33
- subscription: {
34
- future: {
35
- description: "Subscription is scheduled but not yet active.",
36
- chip: {
37
- foreground: "color.text.info",
38
- background: "color.background.info.subtle",
39
- border: "color.border.info",
40
- icon: "icon.status.scheduled"
41
- },
42
- banner: {
43
- foreground: "color.text.info",
44
- background: "color.background.surface.default",
45
- border: "color.border.info",
46
- icon: "icon.status.scheduled"
47
- }
48
- },
49
- trialing: {
50
- description: "Subscription is in a trial period before billing begins.",
51
- chip: {
52
- foreground: "color.text.accent",
53
- background: "color.background.accent.subtle",
54
- border: "color.border.accent",
55
- icon: "icon.status.trial"
56
- },
57
- banner: {
58
- foreground: "color.text.accent",
59
- background: "color.background.surface.default",
60
- border: "color.border.accent",
61
- icon: "icon.status.trial"
62
- }
63
- },
64
- active: {
65
- description: "Subscription is paid and service is provisioned.",
66
- chip: {
67
- foreground: "color.text.positive",
68
- background: "color.background.positive.subtle",
69
- border: "color.border.positive",
70
- icon: "icon.status.success"
71
- },
72
- banner: {
73
- foreground: "color.text.positive",
74
- background: "color.background.surface.default",
75
- border: "color.border.positive",
76
- icon: "icon.status.success"
77
- }
78
- },
79
- paused: {
80
- description: "Billing paused temporarily by finance or customer request.",
81
- chip: {
82
- foreground: "color.text.neutral",
83
- background: "color.background.neutral.subtle",
84
- border: "color.border.neutral",
85
- icon: "icon.status.paused"
86
- },
87
- banner: {
88
- foreground: "color.text.neutral",
89
- background: "color.background.surface.muted",
90
- border: "color.border.neutral",
91
- icon: "icon.status.paused"
92
- }
93
- },
94
- pending_cancellation: {
95
- description: "Subscription will cancel at period end but remains active now.",
96
- chip: {
97
- foreground: "color.text.info",
98
- background: "color.background.info.subtle",
99
- border: "color.border.info",
100
- icon: "icon.status.pending"
101
- },
102
- banner: {
103
- foreground: "color.text.info",
104
- background: "color.background.surface.default",
105
- border: "color.border.info",
106
- icon: "icon.status.pending"
107
- }
108
- },
109
- past_due: {
110
- description: "Renewal payment failed; smart retries ongoing and access retained during the grace window.",
111
- chip: {
112
- foreground: "color.text.danger",
113
- background: "color.background.danger.subtle",
114
- border: "color.border.danger",
115
- icon: "icon.status.warning"
116
- },
117
- banner: {
118
- foreground: "color.text.onbold",
119
- background: "color.background.danger.bold",
120
- border: "color.border.danger",
121
- icon: "icon.status.warning"
122
- }
123
- },
124
- unpaid: {
125
- description: "Payment retries exhausted; service access revoked.",
126
- chip: {
127
- foreground: "color.text.danger",
128
- background: "color.background.danger.subtle",
129
- border: "color.border.danger",
130
- icon: "icon.status.error"
131
- },
132
- banner: {
133
- foreground: "color.text.onbold",
134
- background: "color.background.danger.bold",
135
- border: "color.border.danger",
136
- icon: "icon.status.error"
137
- }
138
- },
139
- terminated: {
140
- description: "Subscription ended; service access revoked and billing stopped.",
141
- chip: {
142
- foreground: "color.text.disabled",
143
- background: "color.background.disabled",
144
- border: "color.border.disabled",
145
- icon: "icon.status.canceled"
146
- },
147
- banner: {
148
- foreground: "color.text.neutral",
149
- background: "color.background.neutral.subtle",
150
- border: "color.border.neutral",
151
- icon: "icon.status.canceled"
152
- }
153
- }
154
- },
155
- invoice: {
156
- draft: {
157
- description: "Invoice is editable and not yet finalized.",
158
- chip: {
159
- foreground: "color.text.neutral",
160
- background: "color.background.neutral.subtle",
161
- border: "color.border.neutral",
162
- icon: "icon.status.draft"
163
- },
164
- banner: {
165
- foreground: "color.text.neutral",
166
- background: "color.background.surface.muted",
167
- border: "color.border.neutral",
168
- icon: "icon.status.draft"
169
- }
170
- },
171
- posted: {
172
- description: "Invoice finalized with terms before collection.",
173
- chip: {
174
- foreground: "color.text.info",
175
- background: "color.background.info.subtle",
176
- border: "color.border.info",
177
- icon: "icon.status.pending"
178
- },
179
- banner: {
180
- foreground: "color.text.info",
181
- background: "color.background.surface.default",
182
- border: "color.border.info",
183
- icon: "icon.status.pending"
184
- }
185
- },
186
- paid: {
187
- description: "Invoice collected successfully in full.",
188
- chip: {
189
- foreground: "color.text.positive",
190
- background: "color.background.positive.subtle",
191
- border: "color.border.positive",
192
- icon: "icon.status.success"
193
- },
194
- banner: {
195
- foreground: "color.text.positive",
196
- background: "color.background.surface.default",
197
- border: "color.border.positive",
198
- icon: "icon.status.success"
199
- }
200
- },
201
- past_due: {
202
- description: "Invoice is unpaid past its due date.",
203
- chip: {
204
- foreground: "color.text.danger",
205
- background: "color.background.danger.subtle",
206
- border: "color.border.danger",
207
- icon: "icon.status.error"
208
- },
209
- banner: {
210
- foreground: "color.text.onbold",
211
- background: "color.background.danger.bold",
212
- border: "color.border.danger",
213
- icon: "icon.status.error"
214
- }
215
- },
216
- void: {
217
- description: "Invoice canceled and no longer payable.",
218
- chip: {
219
- foreground: "color.text.disabled",
220
- background: "color.background.disabled",
221
- border: "color.border.disabled",
222
- icon: "icon.status.void"
223
- },
224
- banner: {
225
- foreground: "color.text.neutral",
226
- background: "color.background.neutral.subtle",
227
- border: "color.border.neutral",
228
- icon: "icon.status.void"
229
- }
230
- }
231
- }
232
- }
233
- };
234
-
235
- // src/status.ts
236
- var TONE_ALIASES = {
237
- info: "info",
238
- informational: "info",
239
- accent: "accent",
240
- highlight: "accent",
241
- positive: "success",
242
- success: "success",
243
- ok: "success",
244
- warning: "warning",
245
- caution: "warning",
246
- danger: "critical",
247
- critical: "critical",
248
- error: "critical",
249
- negative: "critical",
250
- neutral: "neutral",
251
- pending: "info"
252
- };
253
- var toLabel = (status) => status.split(/[_-]/).filter(Boolean).map((segment) => segment.charAt(0).toUpperCase() + segment.slice(1)).join(" ");
254
- var inferTone = (entry) => {
255
- if (!entry) return "neutral";
256
- for (const reference of [entry.foreground, entry.background, entry.border]) {
257
- const segments = reference.replace(/^\{|\}$/g, "").split(/[\s./_-]+/).reverse();
258
- for (const segment of segments) {
259
- const tone = TONE_ALIASES[segment.toLowerCase()];
260
- if (tone) return tone;
261
- }
262
- }
263
- return "neutral";
264
- };
265
- var componentTokenSet = (tone) => {
266
- const normalized = tone === "positive" ? "success" : tone === "danger" ? "critical" : tone;
267
- return {
268
- background: `var(--sys-status-${normalized}-surface)`,
269
- border: `var(--sys-status-${normalized}-border)`,
270
- foreground: `var(--sys-status-${normalized}-text)`
271
- };
272
- };
273
- var rawTokenSet = (entry) => {
274
- const tone = inferTone(entry);
275
- return componentTokenSet(tone);
276
- };
277
- var bannerFallback = (tone) => tone === "neutral" ? {
278
- background: "var(--cmp-banner-background)",
279
- border: "var(--cmp-banner-border)",
280
- foreground: "var(--cmp-banner-text)"
281
- } : componentTokenSet(tone);
282
- var fallback = (domain, status, tone = "neutral") => ({
283
- domain,
284
- status,
285
- label: toLabel(status || "Unknown"),
286
- description: "Status not found in registry; falling back to the requested token set.",
287
- tone,
288
- badge: { subtle: componentTokenSet(tone), solid: componentTokenSet(tone) },
289
- banner: { subtle: bannerFallback(tone), solid: componentTokenSet(tone) }
290
- });
291
- var registry = /* @__PURE__ */ new Map();
292
- for (const [domain, entries] of Object.entries(status_map_default.domains)) {
293
- const domainEntries = /* @__PURE__ */ new Map();
294
- for (const [status, entry] of Object.entries(entries)) {
295
- const tone = inferTone(entry.chip);
296
- domainEntries.set(status.toLowerCase(), {
297
- domain,
298
- status,
299
- label: toLabel(status),
300
- description: entry.description,
301
- tone,
302
- iconName: entry.chip.icon ?? entry.banner?.icon,
303
- badge: { subtle: rawTokenSet(entry.chip), solid: componentTokenSet(tone) },
304
- banner: {
305
- subtle: entry.banner ? rawTokenSet(entry.banner) : bannerFallback(tone),
306
- solid: componentTokenSet(tone)
307
- }
308
- });
309
- }
310
- registry.set(domain, domainEntries);
311
- }
312
- var LEGACY_STATUS_ALIASES = {
313
- subscription: { delinquent: "unpaid" }
314
- };
315
- Object.freeze([...registry.keys()]);
316
- function getStatusPresentation(domain, status) {
317
- const entries = registry.get(domain);
318
- if (!entries) return fallback(domain, status);
319
- const normalized = status.toLowerCase();
320
- const direct = entries.get(normalized);
321
- if (direct) return direct;
322
- const alias = LEGACY_STATUS_ALIASES[domain]?.[normalized];
323
- const aliased = alias ? entries.get(alias) : void 0;
324
- return aliased ? { ...aliased, status, label: toLabel(status) } : fallback(domain, status);
325
- }
326
- function getToneTokenSet(tone) {
327
- return componentTokenSet(tone);
328
- }
329
- function getBannerToneTokenSet(tone) {
330
- return bannerFallback(tone);
331
- }
332
- var ICON_GLYPHS = {
333
- success: "\u2714\uFE0E",
334
- warning: "\u26A0\uFE0E",
335
- critical: "\u2A2F",
336
- danger: "\u2A2F",
337
- error: "\u2A2F",
338
- negative: "\u2A2F",
339
- pending: "\u2026",
340
- processing: "\u27F3",
341
- paused: "\u23F8",
342
- canceled: "\u2205",
343
- cancelled: "\u2205",
344
- draft: "\u270E",
345
- void: "\u2205",
346
- paid: "\u2714\uFE0E",
347
- info: "\u2139\uFE0E",
348
- trial: "\u2605",
349
- future: "\u23F2",
350
- scheduled: "\u23F2",
351
- refunded: "\u21BA",
352
- locked: "\u{1F512}",
353
- unlocked: "\u{1F513}",
354
- default: "\u2022"
355
- };
356
- function resolveStatusGlyph(iconName) {
357
- const segment = iconName?.split(".").pop()?.toLowerCase();
358
- return segment ? ICON_GLYPHS[segment] ?? ICON_GLYPHS.default : void 0;
359
- }
360
29
  var classes = (...values) => values.filter(Boolean).join(" ");
361
30
  var describedByIds = (id, help, validation, nativeDescription) => [
362
31
  nativeDescription,
@@ -383,7 +52,7 @@ var FieldFrame = ({
383
52
  }) => {
384
53
  const resolvedHelp = help ?? description;
385
54
  const validationTone = validation?.state === "error" ? "critical" : validation?.state === "warning" ? "warning" : validation?.state;
386
- const validationTokens = validationTone ? getToneTokenSet(validationTone) : void 0;
55
+ const validationTokens = validationTone ? componentContracts.getToneTokenSet(validationTone) : void 0;
387
56
  const validationStyle = validationTokens ? {
388
57
  "--cmp-input-message-border": validationTokens.border,
389
58
  "--cmp-input-message-text": validationTokens.foreground,
@@ -508,17 +177,16 @@ var InputPrimitive = React3__namespace.forwardRef(
508
177
  InputPrimitive.displayName = "OODS.Input";
509
178
  var Input = InputPrimitive;
510
179
  var DatePicker = React3__namespace.forwardRef(
511
- ({ pickerClassName, pickerStyle, inputClassName, inputStyle, ...rest }, ref) => /* @__PURE__ */ jsxRuntime.jsx(
180
+ ({ pickerClassName, pickerStyle, inputClassName, inputStyle, className, style, ...rest }, ref) => /* @__PURE__ */ jsxRuntime.jsx("div", { className: classes("oods-date-picker", className), style, "data-oods-component": "DatePicker", children: /* @__PURE__ */ jsxRuntime.jsx(
512
181
  Input,
513
182
  {
514
183
  ref,
515
184
  type: "date",
516
- "data-oods-component": "DatePicker",
517
185
  inputClassName: classes("form-field__date-picker", inputClassName, pickerClassName),
518
186
  inputStyle: { ...inputStyle, ...pickerStyle },
519
187
  ...rest
520
188
  }
521
- )
189
+ ) })
522
190
  );
523
191
  DatePicker.displayName = "OODS.DatePicker";
524
192
  var Checkbox = React3__namespace.forwardRef(
@@ -584,6 +252,84 @@ var Checkbox = React3__namespace.forwardRef(
584
252
  }
585
253
  );
586
254
  Checkbox.displayName = "OODS.Checkbox";
255
+ var Switch = React3__namespace.forwardRef(
256
+ ({
257
+ id,
258
+ label,
259
+ checked,
260
+ defaultChecked = false,
261
+ required,
262
+ help,
263
+ validation,
264
+ size = "md",
265
+ className,
266
+ style,
267
+ onClick,
268
+ onCheckedChange,
269
+ onUpdate,
270
+ "aria-describedby": nativeDescription,
271
+ ...rest
272
+ }, ref) => {
273
+ const [internalChecked, setInternalChecked] = React3__namespace.useState(defaultChecked);
274
+ const on = checked ?? internalChecked;
275
+ const validationId = validation?.id ?? `${id}-validation`;
276
+ const describedBy = [nativeDescription, help ? `${id}-help` : void 0, validation?.message ? validationId : void 0].filter(Boolean).join(" ") || void 0;
277
+ const tone = validation ? validation.state === "error" ? "critical" : validation.state : void 0;
278
+ const handleClick = (event) => {
279
+ onClick?.(event);
280
+ if (event.defaultPrevented) return;
281
+ const next = !on;
282
+ if (checked === void 0) setInternalChecked(next);
283
+ onCheckedChange?.(next);
284
+ onUpdate?.(next);
285
+ };
286
+ return /* @__PURE__ */ jsxRuntime.jsxs(
287
+ "div",
288
+ {
289
+ className: classes("oods-field", "oods-switch", className),
290
+ style: {
291
+ ...tone ? {
292
+ "--cmp-input-message-border": `var(--sys-status-${tone}-border)`,
293
+ "--cmp-input-message-text": `var(--sys-status-${tone}-text)`
294
+ } : {},
295
+ ...style
296
+ },
297
+ "data-oods-component": "Switch",
298
+ "data-size": size,
299
+ "data-validation-state": validation?.state,
300
+ children: [
301
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "oods-switch__row", children: [
302
+ /* @__PURE__ */ jsxRuntime.jsx(
303
+ "button",
304
+ {
305
+ ref,
306
+ id,
307
+ type: "button",
308
+ role: "switch",
309
+ className: "oods-switch__control",
310
+ "aria-checked": on,
311
+ "aria-labelledby": `${id}-label`,
312
+ "aria-describedby": describedBy,
313
+ "aria-required": required || void 0,
314
+ "aria-invalid": validation?.state === "error" || void 0,
315
+ onClick: handleClick,
316
+ ...rest,
317
+ children: /* @__PURE__ */ jsxRuntime.jsx("span", { className: "oods-switch__thumb", "aria-hidden": "true" })
318
+ }
319
+ ),
320
+ /* @__PURE__ */ jsxRuntime.jsxs("label", { id: `${id}-label`, className: "oods-field-label oods-switch__label", htmlFor: id, children: [
321
+ label,
322
+ required ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: "oods-field-required", "aria-hidden": "true", children: "*" }) : null
323
+ ] })
324
+ ] }),
325
+ help ? /* @__PURE__ */ jsxRuntime.jsx("p", { id: `${id}-help`, className: "oods-field-help", children: help }) : null,
326
+ validation?.message ? /* @__PURE__ */ jsxRuntime.jsx("p", { id: validationId, className: "oods-field-error", role: validation.state === "error" ? "alert" : void 0, children: validation.message }) : null
327
+ ]
328
+ }
329
+ );
330
+ }
331
+ );
332
+ Switch.displayName = "OODS.Switch";
587
333
  var Select = React3__namespace.forwardRef(
588
334
  ({
589
335
  id,
@@ -722,6 +468,20 @@ var Textarea = React3__namespace.forwardRef(
722
468
  }
723
469
  );
724
470
  Textarea.displayName = "OODS.Textarea";
471
+ var REACT_NAMES = {
472
+ viewBox: "viewBox",
473
+ fill: "fill",
474
+ stroke: "stroke",
475
+ "stroke-width": "strokeWidth",
476
+ "stroke-linecap": "strokeLinecap",
477
+ "stroke-linejoin": "strokeLinejoin",
478
+ "aria-hidden": "aria-hidden",
479
+ focusable: "focusable"
480
+ };
481
+ var svgProps = Object.fromEntries(Object.entries(componentContracts.STATUS_ICON_SVG_ATTRS).map(([name, value]) => [REACT_NAMES[name] ?? name, value]));
482
+ function StatusIcon({ name }) {
483
+ return /* @__PURE__ */ jsxRuntime.jsx("svg", { ...svgProps, children: componentContracts.STATUS_ICON_SHAPES[name].map((shape, index) => React3__namespace.createElement(shape.tag, { key: index, ...shape.attrs })) });
484
+ }
725
485
  var classes2 = (...values) => values.filter(Boolean).join(" ");
726
486
  var Badge = React3__namespace.forwardRef(
727
487
  ({
@@ -740,18 +500,19 @@ var Badge = React3__namespace.forwardRef(
740
500
  "aria-label": ariaLabel,
741
501
  ...rest
742
502
  }, ref) => {
743
- const presentation = status ? getStatusPresentation(domain, status) : void 0;
503
+ const presentation = status ? componentContracts.getStatusPresentation(domain, status) : void 0;
744
504
  const tone = toneOverride ?? presentation?.tone ?? "neutral";
745
- const tokenSet = toneOverride !== void 0 ? getToneTokenSet(tone) : emphasis === "solid" ? presentation?.badge.solid ?? getToneTokenSet(tone) : presentation?.badge.subtle ?? getToneTokenSet(tone);
505
+ const tokenSet = toneOverride !== void 0 ? componentContracts.getToneTokenSet(tone) : emphasis === "solid" ? presentation?.badge.solid ?? componentContracts.getToneTokenSet(tone) : presentation?.badge.subtle ?? componentContracts.getToneTokenSet(tone);
746
506
  const resolvedContent = children ?? content ?? presentation?.label ?? status;
747
- const resolvedIcon = icon ?? resolveStatusGlyph(presentation?.iconName);
507
+ const statusIcon = componentContracts.resolveStatusIcon(presentation?.iconName);
508
+ const resolvedIcon = icon ?? (statusIcon ? /* @__PURE__ */ jsxRuntime.jsx(StatusIcon, { name: statusIcon }) : void 0);
748
509
  const resolvedShowIcon = showIcon ?? resolvedIcon !== void 0;
749
- const iconNode = resolvedShowIcon && resolvedIcon ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: "oods-badge__icon statusable-badge__icon", "aria-hidden": "true", children: resolvedIcon }) : null;
510
+ const iconNode = resolvedShowIcon && resolvedIcon ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: "oods-badge__icon", "aria-hidden": "true", children: resolvedIcon }) : null;
750
511
  return /* @__PURE__ */ jsxRuntime.jsxs(
751
512
  "span",
752
513
  {
753
514
  ref,
754
- className: classes2("oods-badge", "statusable-badge", className),
515
+ className: classes2("oods-badge", className),
755
516
  "data-oods-component": "Badge",
756
517
  "data-status": status,
757
518
  "data-domain": domain,
@@ -763,16 +524,12 @@ var Badge = React3__namespace.forwardRef(
763
524
  "--cmp-badge-background": tokenSet.background,
764
525
  "--cmp-badge-border": tokenSet.border,
765
526
  "--cmp-badge-text": tokenSet.foreground,
766
- "--statusable-badge-background": tokenSet.background,
767
- "--statusable-badge-border": tokenSet.border,
768
- "--statusable-badge-foreground": tokenSet.foreground,
769
- "--statusable-badge-icon-color": tokenSet.foreground,
770
527
  ...style
771
528
  },
772
529
  ...rest,
773
530
  children: [
774
531
  iconPosition === "start" ? iconNode : null,
775
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "oods-badge__label statusable-badge__label", children: resolvedContent }),
532
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "oods-badge__label", children: resolvedContent }),
776
533
  iconPosition === "end" ? iconNode : null
777
534
  ]
778
535
  }
@@ -800,13 +557,14 @@ var Banner = React3__namespace.forwardRef(
800
557
  style,
801
558
  ...rest
802
559
  }, ref) => {
803
- const presentation = status ? getStatusPresentation(domain, status) : void 0;
560
+ const presentation = status ? componentContracts.getStatusPresentation(domain, status) : void 0;
804
561
  const tone = toneOverride ?? presentation?.tone ?? "neutral";
805
- const tokenSet = emphasis === "solid" ? presentation?.banner.solid ?? getToneTokenSet(tone) : presentation?.banner.subtle ?? getBannerToneTokenSet(tone);
562
+ const tokenSet = emphasis === "solid" ? presentation?.banner.solid ?? componentContracts.getToneTokenSet(tone) : presentation?.banner.subtle ?? componentContracts.getBannerToneTokenSet(tone);
806
563
  const heading = title ?? presentation?.label ?? status;
807
564
  const body = detail ?? description ?? children ?? content ?? presentation?.description;
808
565
  const role = tone === "critical" || tone === "danger" ? "alert" : "status";
809
- const resolvedIcon = icon ?? resolveStatusGlyph(presentation?.iconName);
566
+ const statusIcon = componentContracts.resolveStatusIcon(presentation?.iconName);
567
+ const resolvedIcon = icon ?? (statusIcon ? /* @__PURE__ */ jsxRuntime.jsx(StatusIcon, { name: statusIcon }) : void 0);
810
568
  const resolvedShowIcon = showIcon ?? resolvedIcon !== void 0;
811
569
  return /* @__PURE__ */ jsxRuntime.jsxs(
812
570
  "div",
@@ -814,7 +572,7 @@ var Banner = React3__namespace.forwardRef(
814
572
  ref,
815
573
  role,
816
574
  "aria-live": role === "alert" ? "assertive" : "polite",
817
- className: classes2("oods-banner", "statusable-banner", className),
575
+ className: classes2("oods-banner", className),
818
576
  "data-oods-component": "Banner",
819
577
  "data-status": status,
820
578
  "data-domain": domain,
@@ -824,28 +582,24 @@ var Banner = React3__namespace.forwardRef(
824
582
  "--cmp-banner-background": tokenSet.background,
825
583
  "--cmp-banner-border": tokenSet.border,
826
584
  "--cmp-banner-text": tokenSet.foreground,
827
- "--statusable-banner-background": tokenSet.background,
828
- "--statusable-banner-border": tokenSet.border,
829
- "--statusable-banner-foreground": tokenSet.foreground,
830
- "--statusable-banner-icon-color": tokenSet.foreground,
831
585
  ...style
832
586
  },
833
587
  ...rest,
834
588
  children: [
835
- resolvedShowIcon && resolvedIcon ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: "oods-banner__icon statusable-banner__icon", "aria-hidden": "true", children: resolvedIcon }) : null,
836
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "oods-banner__content statusable-banner__content", children: [
837
- heading ? /* @__PURE__ */ jsxRuntime.jsx("strong", { className: "oods-banner__title statusable-banner__title", children: heading }) : null,
838
- body ? /* @__PURE__ */ jsxRuntime.jsx("p", { className: "oods-banner__detail statusable-banner__description", children: body }) : null,
839
- actions ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "oods-banner__actions statusable-banner__actions", children: actions }) : null
589
+ resolvedShowIcon && resolvedIcon ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: "oods-banner__icon", "aria-hidden": "true", children: resolvedIcon }) : null,
590
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "oods-banner__content", children: [
591
+ heading ? /* @__PURE__ */ jsxRuntime.jsx("strong", { className: "oods-banner__title", children: heading }) : null,
592
+ body ? /* @__PURE__ */ jsxRuntime.jsx("p", { className: "oods-banner__detail", children: body }) : null,
593
+ actions ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "oods-banner__actions", children: actions }) : null
840
594
  ] }),
841
595
  onDismiss ? /* @__PURE__ */ jsxRuntime.jsx(
842
596
  "button",
843
597
  {
844
598
  type: "button",
845
- className: "oods-banner-dismiss oods-banner__dismiss statusable-banner__dismiss",
599
+ className: "oods-banner-dismiss oods-banner__dismiss",
846
600
  "aria-label": dismissLabel,
847
601
  onClick: () => onDismiss(),
848
- children: /* @__PURE__ */ jsxRuntime.jsx("span", { "aria-hidden": "true", children: "\xD7" })
602
+ children: /* @__PURE__ */ jsxRuntime.jsx(StatusIcon, { name: "x" })
849
603
  }
850
604
  ) : null
851
605
  ]
@@ -900,6 +654,84 @@ var Button = React3__namespace.forwardRef(
900
654
  }
901
655
  );
902
656
  Button.displayName = "OODS.Button";
657
+ var noSubscription = () => () => void 0;
658
+ var useIsomorphicLayoutEffect = typeof window === "undefined" ? React3__namespace.useEffect : React3__namespace.useLayoutEffect;
659
+ var canShowModal = () => typeof HTMLDialogElement !== "undefined" && typeof HTMLDialogElement.prototype.showModal === "function";
660
+ var hasContent = (node) => node !== void 0 && node !== null && node !== false && node !== "";
661
+ var Dialog = React3__namespace.forwardRef(
662
+ ({
663
+ id,
664
+ open = false,
665
+ modal = true,
666
+ title,
667
+ description,
668
+ dismissLabel = "Close",
669
+ size = "md",
670
+ actions,
671
+ children,
672
+ className,
673
+ onClose,
674
+ onKeyDown,
675
+ ...rest
676
+ }, ref) => {
677
+ const generatedId = React3__namespace.useId().replace(/[^\w-]/g, "");
678
+ const dialogId = id ?? `oods-dialog-${generatedId}`;
679
+ const element = React3__namespace.useRef(null);
680
+ const openRef = React3__namespace.useRef(open);
681
+ const serverMarkup = React3__namespace.useSyncExternalStore(noSubscription, () => false, () => true);
682
+ const managed = modal && !serverMarkup && canShowModal();
683
+ const setRef = React3__namespace.useCallback((node) => {
684
+ element.current = node;
685
+ if (typeof ref === "function") ref(node);
686
+ else if (ref) ref.current = node;
687
+ }, [ref]);
688
+ useIsomorphicLayoutEffect(() => {
689
+ openRef.current = open;
690
+ const dialog = element.current;
691
+ if (!dialog || !managed) return;
692
+ if (open && !dialog.open) dialog.showModal();
693
+ else if (!open && dialog.open) dialog.close();
694
+ }, [open, managed]);
695
+ const requestClose = (event) => {
696
+ event.preventDefault();
697
+ onClose?.();
698
+ };
699
+ return /* @__PURE__ */ jsxRuntime.jsxs(
700
+ "dialog",
701
+ {
702
+ ref: setRef,
703
+ id: dialogId,
704
+ className: classes2("oods-dialog", className),
705
+ "data-oods-component": "Dialog",
706
+ "data-size": size,
707
+ "aria-labelledby": `${dialogId}-title`,
708
+ "aria-describedby": hasContent(description) ? `${dialogId}-description` : void 0,
709
+ open: managed ? void 0 : open,
710
+ onKeyDown: (event) => {
711
+ onKeyDown?.(event);
712
+ if (event.key === "Escape" && !event.defaultPrevented) requestClose(event);
713
+ },
714
+ onCancel: requestClose,
715
+ onClose: () => {
716
+ if (openRef.current && !element.current?.open) onClose?.();
717
+ },
718
+ ...rest,
719
+ children: [
720
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "oods-dialog__header", children: [
721
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "oods-dialog__heading", children: [
722
+ /* @__PURE__ */ jsxRuntime.jsx("h2", { id: `${dialogId}-title`, className: "oods-dialog__title", children: title }),
723
+ hasContent(description) ? /* @__PURE__ */ jsxRuntime.jsx("p", { id: `${dialogId}-description`, className: "oods-dialog__description", children: description }) : null
724
+ ] }),
725
+ /* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", className: "oods-dialog__close", "aria-label": dismissLabel, onClick: () => onClose?.(), children: /* @__PURE__ */ jsxRuntime.jsx(StatusIcon, { name: "x" }) })
726
+ ] }),
727
+ hasContent(children) ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "oods-dialog__body", children }) : null,
728
+ hasContent(actions) ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "oods-dialog__footer", children: actions }) : null
729
+ ]
730
+ }
731
+ );
732
+ }
733
+ );
734
+ Dialog.displayName = "OODS.Dialog";
903
735
  var Card = React3__namespace.forwardRef(
904
736
  ({ as = "div", elevated = false, className, ...rest }, ref) => {
905
737
  const Element = as;
@@ -965,11 +797,12 @@ var StatusBadge = React3__namespace.forwardRef(
965
797
  compact,
966
798
  variant,
967
799
  title,
800
+ className,
968
801
  "aria-label": ariaLabel,
969
802
  ...rest
970
803
  }, ref) => {
971
804
  const resolvedStatus = status ?? value ?? "unknown";
972
- const presentation = getStatusPresentation(domain, resolvedStatus);
805
+ const presentation = componentContracts.getStatusPresentation(domain, resolvedStatus);
973
806
  return /* @__PURE__ */ jsxRuntime.jsx(
974
807
  Badge,
975
808
  {
@@ -982,6 +815,7 @@ var StatusBadge = React3__namespace.forwardRef(
982
815
  content: children ?? content,
983
816
  title: title ?? presentation.description,
984
817
  "aria-label": ariaLabel ?? label ?? `Status: ${presentation.label}`,
818
+ className: classes3("oods-status-badge", className),
985
819
  "data-oods-component": "StatusBadge",
986
820
  ...rest
987
821
  }
@@ -989,8 +823,8 @@ var StatusBadge = React3__namespace.forwardRef(
989
823
  }
990
824
  );
991
825
  StatusBadge.displayName = "OODS.StatusBadge";
992
- function formatPrice(amount, currency, fallback2) {
993
- if (amount === void 0 || !Number.isFinite(amount)) return fallback2;
826
+ function formatPrice(amount, currency, fallback) {
827
+ if (amount === void 0 || !Number.isFinite(amount)) return fallback;
994
828
  if (!currency) return new Intl.NumberFormat(void 0, { maximumFractionDigits: 2 }).format(amount);
995
829
  try {
996
830
  return new Intl.NumberFormat(void 0, {
@@ -1120,29 +954,29 @@ var TimelinePrimitive = React3__namespace.forwardRef(
1120
954
  "data-timeline-type": kind,
1121
955
  ...rest,
1122
956
  children: [
1123
- /* @__PURE__ */ jsxRuntime.jsx("h2", { className: "oods-timeline__title", children: title }),
957
+ /* @__PURE__ */ jsxRuntime.jsx("h2", { className: "oods-timeline__title", "data-timeline-title": "true", children: title }),
1124
958
  kind === "status" && status ? /* @__PURE__ */ jsxRuntime.jsxs("p", { className: "oods-timeline__current", "data-timeline-current": "true", children: [
1125
959
  "Current status: ",
1126
960
  humanize(status)
1127
961
  ] }) : null,
1128
- transitionCount > 0 ? /* @__PURE__ */ jsxRuntime.jsxs("p", { children: [
962
+ transitionCount > 0 ? /* @__PURE__ */ jsxRuntime.jsxs("p", { "data-timeline-transitions": "true", children: [
1129
963
  "Allowed transitions: ",
1130
964
  allowedTransitions?.join(", ")
1131
965
  ] }) : null,
1132
- children ?? /* @__PURE__ */ jsxRuntime.jsx("ol", { className: "oods-timeline__events", children: visibleEvents.length === 0 ? /* @__PURE__ */ jsxRuntime.jsx("li", { "data-timeline-empty": "true", children: "No events" }) : visibleEvents.map((event, index) => {
966
+ children ?? /* @__PURE__ */ jsxRuntime.jsx("ol", { className: "oods-timeline__events", "data-timeline-events": "true", children: visibleEvents.length === 0 ? /* @__PURE__ */ jsxRuntime.jsx("li", { "data-timeline-empty": "true", children: "No events" }) : visibleEvents.map((event, index) => {
1133
967
  const timestamp = event.timestamp ?? event.datetime;
1134
968
  const actor = event.actor ?? event.actorId ?? event.actor_id;
1135
969
  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";
1136
970
  const detail = event.detail ?? event.description;
1137
971
  return /* @__PURE__ */ jsxRuntime.jsx("li", { children: /* @__PURE__ */ jsxRuntime.jsxs("article", { className: "oods-timeline__event", "data-timeline-event": "true", children: [
1138
- /* @__PURE__ */ jsxRuntime.jsx("p", { className: "oods-timeline__label", children: eventLabel }),
1139
- timestamp ? /* @__PURE__ */ jsxRuntime.jsx("time", { dateTime: timestamp, children: componentContracts.formatDateTime(timestamp) }) : null,
1140
- detail ? /* @__PURE__ */ jsxRuntime.jsx("p", { className: "oods-timeline__detail", children: detail }) : null,
1141
- showActorId && actor ? /* @__PURE__ */ jsxRuntime.jsxs("p", { className: "oods-timeline__actor", children: [
972
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "oods-timeline__label", "data-timeline-label": "true", children: eventLabel }),
973
+ timestamp ? /* @__PURE__ */ jsxRuntime.jsx("time", { "data-timeline-time": "true", dateTime: timestamp, children: componentContracts.formatDateTime(timestamp) }) : null,
974
+ detail ? /* @__PURE__ */ jsxRuntime.jsx("p", { className: "oods-timeline__detail", "data-timeline-detail": "true", children: detail }) : null,
975
+ showActorId && actor ? /* @__PURE__ */ jsxRuntime.jsxs("p", { className: "oods-timeline__actor", "data-timeline-actor": "true", children: [
1142
976
  "Actor: ",
1143
977
  actor
1144
978
  ] }) : null,
1145
- showReason && event.reason ? /* @__PURE__ */ jsxRuntime.jsxs("p", { className: "oods-timeline__reason", children: [
979
+ showReason && event.reason ? /* @__PURE__ */ jsxRuntime.jsxs("p", { className: "oods-timeline__reason", "data-timeline-reason": "true", children: [
1146
980
  "Reason: ",
1147
981
  event.reason
1148
982
  ] }) : null
@@ -1333,7 +1167,7 @@ var SearchInput = React3__namespace.forwardRef(
1333
1167
  "aria-label": "Clear search",
1334
1168
  disabled,
1335
1169
  onClick: clear,
1336
- children: "\xD7"
1170
+ children: /* @__PURE__ */ jsxRuntime.jsx(StatusIcon, { name: "x" })
1337
1171
  }
1338
1172
  ) : null
1339
1173
  ]
@@ -1521,7 +1355,7 @@ var RelativeTimestamp = React3__namespace.forwardRef(
1521
1355
  const date = asDate(raw);
1522
1356
  if (hideWhenEmpty && !date) return null;
1523
1357
  const nowDate = asDate(now) ?? /* @__PURE__ */ new Date();
1524
- const iso = date?.toISOString() ?? (raw === void 0 ? void 0 : String(raw));
1358
+ const iso = date?.toISOString() ?? (raw === void 0 || raw === "" ? void 0 : String(raw));
1525
1359
  const absolute = componentContracts.formatDateTime(date, { timeZone: timezone });
1526
1360
  const content = relative ?? label ?? text ?? (now !== void 0 && date ? relativeLabel(date, nowDate) : absolute || children || "Unknown time");
1527
1361
  return /* @__PURE__ */ jsxRuntime.jsx(