@oods/components-react 0.3.3 → 0.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -32,4 +32,4 @@ Use matching versions of the OODS packages. These packages are separate from the
32
32
 
33
33
  ## License
34
34
 
35
- Apache License 2.0. See [LICENSE](https://cdn.jsdelivr.net/npm/@oods/components-react@0.3.3/LICENSE) and [NOTICE](https://cdn.jsdelivr.net/npm/@oods/components-react@0.3.3/NOTICE). Generated output belongs to its user; installed package dependencies retain their own licenses.
35
+ Apache License 2.0. See [LICENSE](https://cdn.jsdelivr.net/npm/@oods/components-react@0.4.1/LICENSE) and [NOTICE](https://cdn.jsdelivr.net/npm/@oods/components-react@0.4.1/NOTICE). Generated output belongs to its user; installed package dependencies retain their own licenses.
@@ -0,0 +1 @@
1
+ export { STATUS_DOMAINS, getBannerToneTokenSet, getStatusPresentation, getToneTokenSet, listStatuses } from '@oods/component-contracts';
@@ -1,5 +1,5 @@
1
- import { getStatusPresentation, getToneTokenSet, resolveStatusGlyph, getBannerToneTokenSet } from './chunk-G74PKSW3.js';
2
- import { dateTimeInputValue, currencyMinorUnits, formatDateTime, formatReadOnlyValue } from '@oods/component-contracts';
1
+ import { getStatusPresentation, getToneTokenSet, getBannerToneTokenSet } from './chunk-2VEQWKGK.js';
2
+ import { dateTimeInputValue, STATUS_ICON_SVG_ATTRS, resolveStatusIcon, currencyMinorUnits, formatDateTime, formatReadOnlyValue, STATUS_ICON_SHAPES } from '@oods/component-contracts';
3
3
  import * as React3 from 'react';
4
4
  import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
5
5
  import { Slot } from '@radix-ui/react-slot';
@@ -230,6 +230,84 @@ var Checkbox = React3.forwardRef(
230
230
  }
231
231
  );
232
232
  Checkbox.displayName = "OODS.Checkbox";
233
+ var Switch = React3.forwardRef(
234
+ ({
235
+ id,
236
+ label,
237
+ checked,
238
+ defaultChecked = false,
239
+ required,
240
+ help,
241
+ validation,
242
+ size = "md",
243
+ className,
244
+ style,
245
+ onClick,
246
+ onCheckedChange,
247
+ onUpdate,
248
+ "aria-describedby": nativeDescription,
249
+ ...rest
250
+ }, ref) => {
251
+ const [internalChecked, setInternalChecked] = React3.useState(defaultChecked);
252
+ const on = checked ?? internalChecked;
253
+ const validationId = validation?.id ?? `${id}-validation`;
254
+ const describedBy = [nativeDescription, help ? `${id}-help` : void 0, validation?.message ? validationId : void 0].filter(Boolean).join(" ") || void 0;
255
+ const tone = validation ? validation.state === "error" ? "critical" : validation.state : void 0;
256
+ const handleClick = (event) => {
257
+ onClick?.(event);
258
+ if (event.defaultPrevented) return;
259
+ const next = !on;
260
+ if (checked === void 0) setInternalChecked(next);
261
+ onCheckedChange?.(next);
262
+ onUpdate?.(next);
263
+ };
264
+ return /* @__PURE__ */ jsxs(
265
+ "div",
266
+ {
267
+ className: classes("oods-field", "oods-switch", className),
268
+ style: {
269
+ ...tone ? {
270
+ "--cmp-input-message-border": `var(--sys-status-${tone}-border)`,
271
+ "--cmp-input-message-text": `var(--sys-status-${tone}-text)`
272
+ } : {},
273
+ ...style
274
+ },
275
+ "data-oods-component": "Switch",
276
+ "data-size": size,
277
+ "data-validation-state": validation?.state,
278
+ children: [
279
+ /* @__PURE__ */ jsxs("div", { className: "oods-switch__row", children: [
280
+ /* @__PURE__ */ jsx(
281
+ "button",
282
+ {
283
+ ref,
284
+ id,
285
+ type: "button",
286
+ role: "switch",
287
+ className: "oods-switch__control",
288
+ "aria-checked": on,
289
+ "aria-labelledby": `${id}-label`,
290
+ "aria-describedby": describedBy,
291
+ "aria-required": required || void 0,
292
+ "aria-invalid": validation?.state === "error" || void 0,
293
+ onClick: handleClick,
294
+ ...rest,
295
+ children: /* @__PURE__ */ jsx("span", { className: "oods-switch__thumb", "aria-hidden": "true" })
296
+ }
297
+ ),
298
+ /* @__PURE__ */ jsxs("label", { id: `${id}-label`, className: "oods-field-label oods-switch__label", htmlFor: id, children: [
299
+ label,
300
+ required ? /* @__PURE__ */ jsx("span", { className: "oods-field-required", "aria-hidden": "true", children: "*" }) : null
301
+ ] })
302
+ ] }),
303
+ help ? /* @__PURE__ */ jsx("p", { id: `${id}-help`, className: "oods-field-help", children: help }) : null,
304
+ validation?.message ? /* @__PURE__ */ jsx("p", { id: validationId, className: "oods-field-error", role: validation.state === "error" ? "alert" : void 0, children: validation.message }) : null
305
+ ]
306
+ }
307
+ );
308
+ }
309
+ );
310
+ Switch.displayName = "OODS.Switch";
233
311
  var Select = React3.forwardRef(
234
312
  ({
235
313
  id,
@@ -368,6 +446,20 @@ var Textarea = React3.forwardRef(
368
446
  }
369
447
  );
370
448
  Textarea.displayName = "OODS.Textarea";
449
+ var REACT_NAMES = {
450
+ viewBox: "viewBox",
451
+ fill: "fill",
452
+ stroke: "stroke",
453
+ "stroke-width": "strokeWidth",
454
+ "stroke-linecap": "strokeLinecap",
455
+ "stroke-linejoin": "strokeLinejoin",
456
+ "aria-hidden": "aria-hidden",
457
+ focusable: "focusable"
458
+ };
459
+ var svgProps = Object.fromEntries(Object.entries(STATUS_ICON_SVG_ATTRS).map(([name, value]) => [REACT_NAMES[name] ?? name, value]));
460
+ function StatusIcon({ name }) {
461
+ return /* @__PURE__ */ jsx("svg", { ...svgProps, children: STATUS_ICON_SHAPES[name].map((shape, index) => React3.createElement(shape.tag, { key: index, ...shape.attrs })) });
462
+ }
371
463
  var classes2 = (...values) => values.filter(Boolean).join(" ");
372
464
  var Badge = React3.forwardRef(
373
465
  ({
@@ -390,7 +482,8 @@ var Badge = React3.forwardRef(
390
482
  const tone = toneOverride ?? presentation?.tone ?? "neutral";
391
483
  const tokenSet = toneOverride !== void 0 ? getToneTokenSet(tone) : emphasis === "solid" ? presentation?.badge.solid ?? getToneTokenSet(tone) : presentation?.badge.subtle ?? getToneTokenSet(tone);
392
484
  const resolvedContent = children ?? content ?? presentation?.label ?? status;
393
- const resolvedIcon = icon ?? resolveStatusGlyph(presentation?.iconName);
485
+ const statusIcon = resolveStatusIcon(presentation?.iconName);
486
+ const resolvedIcon = icon ?? (statusIcon ? /* @__PURE__ */ jsx(StatusIcon, { name: statusIcon }) : void 0);
394
487
  const resolvedShowIcon = showIcon ?? resolvedIcon !== void 0;
395
488
  const iconNode = resolvedShowIcon && resolvedIcon ? /* @__PURE__ */ jsx("span", { className: "oods-badge__icon", "aria-hidden": "true", children: resolvedIcon }) : null;
396
489
  return /* @__PURE__ */ jsxs(
@@ -448,7 +541,8 @@ var Banner = React3.forwardRef(
448
541
  const heading = title ?? presentation?.label ?? status;
449
542
  const body = detail ?? description ?? children ?? content ?? presentation?.description;
450
543
  const role = tone === "critical" || tone === "danger" ? "alert" : "status";
451
- const resolvedIcon = icon ?? resolveStatusGlyph(presentation?.iconName);
544
+ const statusIcon = resolveStatusIcon(presentation?.iconName);
545
+ const resolvedIcon = icon ?? (statusIcon ? /* @__PURE__ */ jsx(StatusIcon, { name: statusIcon }) : void 0);
452
546
  const resolvedShowIcon = showIcon ?? resolvedIcon !== void 0;
453
547
  return /* @__PURE__ */ jsxs(
454
548
  "div",
@@ -483,7 +577,7 @@ var Banner = React3.forwardRef(
483
577
  className: "oods-banner-dismiss oods-banner__dismiss",
484
578
  "aria-label": dismissLabel,
485
579
  onClick: () => onDismiss(),
486
- children: /* @__PURE__ */ jsx("span", { "aria-hidden": "true", children: "\xD7" })
580
+ children: /* @__PURE__ */ jsx(StatusIcon, { name: "x" })
487
581
  }
488
582
  ) : null
489
583
  ]
@@ -538,6 +632,131 @@ var Button = React3.forwardRef(
538
632
  }
539
633
  );
540
634
  Button.displayName = "OODS.Button";
635
+ var noSubscription = () => () => void 0;
636
+ var useIsomorphicLayoutEffect = typeof window === "undefined" ? React3.useEffect : React3.useLayoutEffect;
637
+ var canShowModal = () => typeof HTMLDialogElement !== "undefined" && typeof HTMLDialogElement.prototype.showModal === "function";
638
+ var hasContent = (node) => node !== void 0 && node !== null && node !== false && node !== "";
639
+ var pageLocks = 0;
640
+ var pageStyle;
641
+ function lockPageScroll() {
642
+ const page = document.documentElement;
643
+ if (pageLocks++ === 0) {
644
+ const scrollbar = window.innerWidth - page.clientWidth;
645
+ pageStyle = { overflow: page.style.overflow, paddingRight: page.style.paddingRight };
646
+ if (scrollbar > 0) page.style.paddingRight = `${parseFloat(getComputedStyle(page).paddingRight) + scrollbar}px`;
647
+ page.style.overflow = "hidden";
648
+ }
649
+ let held = true;
650
+ return () => {
651
+ if (!held) return;
652
+ held = false;
653
+ if (--pageLocks === 0 && pageStyle) {
654
+ page.style.overflow = pageStyle.overflow;
655
+ page.style.paddingRight = pageStyle.paddingRight;
656
+ pageStyle = void 0;
657
+ }
658
+ };
659
+ }
660
+ var outsideBox = (event, dialog) => {
661
+ const box = dialog.getBoundingClientRect();
662
+ return event.clientX < box.left || event.clientX > box.right || event.clientY < box.top || event.clientY > box.bottom;
663
+ };
664
+ var Dialog = React3.forwardRef(
665
+ ({
666
+ id,
667
+ open = false,
668
+ modal = true,
669
+ title,
670
+ description,
671
+ dismissLabel = "Close",
672
+ size = "md",
673
+ actions,
674
+ children,
675
+ className,
676
+ onClose,
677
+ onKeyDown,
678
+ onMouseDown,
679
+ onClick,
680
+ ...rest
681
+ }, ref) => {
682
+ const generatedId = React3.useId().replace(/[^\w-]/g, "");
683
+ const dialogId = id ?? `oods-dialog-${generatedId}`;
684
+ const element = React3.useRef(null);
685
+ const openRef = React3.useRef(open);
686
+ const scrollLock = React3.useRef(null);
687
+ const pressedBackdrop = React3.useRef(false);
688
+ const serverMarkup = React3.useSyncExternalStore(noSubscription, () => false, () => true);
689
+ const managed = modal && !serverMarkup && canShowModal();
690
+ const setRef = React3.useCallback((node) => {
691
+ element.current = node;
692
+ if (typeof ref === "function") ref(node);
693
+ else if (ref) ref.current = node;
694
+ }, [ref]);
695
+ const releaseScroll = React3.useCallback(() => {
696
+ scrollLock.current?.();
697
+ scrollLock.current = null;
698
+ }, []);
699
+ useIsomorphicLayoutEffect(() => {
700
+ openRef.current = open;
701
+ const dialog = element.current;
702
+ if (!dialog || !managed) return;
703
+ if (open && !dialog.open) dialog.showModal();
704
+ else if (!open && dialog.open) dialog.close();
705
+ if (open && dialog.open) scrollLock.current ??= lockPageScroll();
706
+ }, [open, managed]);
707
+ React3.useEffect(() => releaseScroll, [releaseScroll]);
708
+ const requestClose = (event) => {
709
+ event.preventDefault();
710
+ onClose?.();
711
+ };
712
+ return /* @__PURE__ */ jsxs(
713
+ "dialog",
714
+ {
715
+ ref: setRef,
716
+ id: dialogId,
717
+ className: classes2("oods-dialog", className),
718
+ "data-oods-component": "Dialog",
719
+ "data-size": size,
720
+ "aria-labelledby": `${dialogId}-title`,
721
+ "aria-describedby": hasContent(description) ? `${dialogId}-description` : void 0,
722
+ open: managed ? void 0 : open,
723
+ onKeyDown: (event) => {
724
+ onKeyDown?.(event);
725
+ if (event.key === "Escape" && !event.defaultPrevented) requestClose(event);
726
+ },
727
+ onCancel: requestClose,
728
+ onClose: () => {
729
+ if (element.current?.open) return;
730
+ releaseScroll();
731
+ if (openRef.current) onClose?.();
732
+ },
733
+ onMouseDown: (event) => {
734
+ onMouseDown?.(event);
735
+ pressedBackdrop.current = managed && event.target === event.currentTarget && outsideBox(event, event.currentTarget);
736
+ },
737
+ onClick: (event) => {
738
+ onClick?.(event);
739
+ const backdrop = pressedBackdrop.current && event.target === event.currentTarget && outsideBox(event, event.currentTarget);
740
+ pressedBackdrop.current = false;
741
+ if (backdrop && !event.defaultPrevented) onClose?.();
742
+ },
743
+ ...rest,
744
+ children: [
745
+ /* @__PURE__ */ jsxs("div", { className: "oods-dialog__header", children: [
746
+ /* @__PURE__ */ jsxs("div", { className: "oods-dialog__heading", children: [
747
+ /* @__PURE__ */ jsx("h2", { id: `${dialogId}-title`, className: "oods-dialog__title", children: title }),
748
+ hasContent(description) ? /* @__PURE__ */ jsx("p", { id: `${dialogId}-description`, className: "oods-dialog__description", children: description }) : null
749
+ ] }),
750
+ /* @__PURE__ */ jsx("button", { type: "button", className: "oods-dialog__close", "aria-label": dismissLabel, onClick: () => onClose?.(), children: /* @__PURE__ */ jsx(StatusIcon, { name: "x" }) })
751
+ ] }),
752
+ hasContent(children) ? /* @__PURE__ */ jsx("div", { className: "oods-dialog__body", children }) : null,
753
+ hasContent(actions) ? /* @__PURE__ */ jsx("div", { className: "oods-dialog__footer", children: actions }) : null
754
+ ]
755
+ }
756
+ );
757
+ }
758
+ );
759
+ Dialog.displayName = "OODS.Dialog";
541
760
  var Card = React3.forwardRef(
542
761
  ({ as = "div", elevated = false, className, ...rest }, ref) => {
543
762
  const Element = as;
@@ -973,7 +1192,7 @@ var SearchInput = React3.forwardRef(
973
1192
  "aria-label": "Clear search",
974
1193
  disabled,
975
1194
  onClick: clear,
976
- children: "\xD7"
1195
+ children: /* @__PURE__ */ jsx(StatusIcon, { name: "x" })
977
1196
  }
978
1197
  ) : null
979
1198
  ]
@@ -1161,7 +1380,7 @@ var RelativeTimestamp = React3.forwardRef(
1161
1380
  const date = asDate(raw);
1162
1381
  if (hideWhenEmpty && !date) return null;
1163
1382
  const nowDate = asDate(now) ?? /* @__PURE__ */ new Date();
1164
- const iso = date?.toISOString() ?? (raw === void 0 ? void 0 : String(raw));
1383
+ const iso = date?.toISOString() ?? (raw === void 0 || raw === "" ? void 0 : String(raw));
1165
1384
  const absolute = formatDateTime(date, { timeZone: timezone });
1166
1385
  const content = relative ?? label ?? text ?? (now !== void 0 && date ? relativeLabel(date, nowDate) : absolute || children || "Unknown time");
1167
1386
  return /* @__PURE__ */ jsx(
@@ -1181,4 +1400,4 @@ var RelativeTimestamp = React3.forwardRef(
1181
1400
  );
1182
1401
  RelativeTimestamp.displayName = "OODS.RelativeTimestamp";
1183
1402
 
1184
- export { AuditTimeline, Badge, Banner, Button, CancellationSummary, Card, Checkbox, DatePicker, Input, PaginationBar, PriceBadge, RelativeTimestamp, SearchInput, Select, StatusBadge, StatusTimeline, Text, Textarea };
1403
+ export { AuditTimeline, Badge, Banner, Button, CancellationSummary, Card, Checkbox, DatePicker, Dialog, Input, PaginationBar, PriceBadge, RelativeTimestamp, SearchInput, Select, StatusBadge, StatusIcon, StatusTimeline, Switch, Text, Textarea };
@@ -1,4 +1,4 @@
1
- import { getStatusPresentation, getToneTokenSet } from './chunk-G74PKSW3.js';
1
+ import { getStatusPresentation, getToneTokenSet } from './chunk-2VEQWKGK.js';
2
2
  import * as React from 'react';
3
3
  import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
4
4
 
@@ -120,6 +120,7 @@ TableCell.displayName = "OODS.Table.Cell";
120
120
  var TableRoot = React.forwardRef(
121
121
  ({
122
122
  caption,
123
+ showCaption = true,
123
124
  columns,
124
125
  rows,
125
126
  density = "comfortable",
@@ -145,7 +146,7 @@ var TableRoot = React.forwardRef(
145
146
  "data-density": density,
146
147
  ...rest,
147
148
  children: children ?? /* @__PURE__ */ jsxs(Fragment, { children: [
148
- caption ? /* @__PURE__ */ jsx(TableCaption, { children: caption }) : null,
149
+ caption ? /* @__PURE__ */ jsx(TableCaption, { "data-visually-hidden": showCaption ? void 0 : "true", children: caption }) : null,
149
150
  /* @__PURE__ */ jsx(TableHead, { children: /* @__PURE__ */ jsx("tr", { className: "oods-table__row", children: columns?.map((column) => /* @__PURE__ */ jsx(TableHeaderCell, { numeric: column.numeric, children: column.label }, column.key)) }) }),
150
151
  /* @__PURE__ */ jsx(TableBody, { children: (rows?.length ?? 0) === 0 ? /* @__PURE__ */ jsx("tr", { className: "oods-table__row", children: /* @__PURE__ */ jsx(TableCell, { colSpan: Math.max(columns?.length ?? 0, 1), children: "No rows available." }) }) : rows?.map((row) => /* @__PURE__ */ jsx(
151
152
  "tr",