@tensor_1/react 2.12.0 → 2.13.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +16 -0
- package/dist/calendar-model.d.ts +31 -0
- package/dist/calendar-model.js +89 -0
- package/dist/color-model.d.ts +20 -0
- package/dist/color-model.js +74 -0
- package/dist/components/BrandProfiles.d.ts +45 -0
- package/dist/components/BrandProfiles.js +150 -0
- package/dist/components/BuilderCanvas.d.ts +34 -0
- package/dist/components/BuilderCanvas.js +117 -0
- package/dist/components/ColorPicker.d.ts +17 -0
- package/dist/components/ColorPicker.js +158 -0
- package/dist/components/DateRangePicker.d.ts +23 -0
- package/dist/components/DateRangePicker.js +123 -0
- package/dist/components/DiffViewer.d.ts +18 -0
- package/dist/components/DiffViewer.js +93 -0
- package/dist/components/LicensePositionBar.d.ts +17 -0
- package/dist/components/LicensePositionBar.js +50 -0
- package/dist/components/MatchReview.d.ts +39 -0
- package/dist/components/MatchReview.js +120 -0
- package/dist/components/MetricInput.d.ts +23 -0
- package/dist/components/MetricInput.js +92 -0
- package/dist/components/RelationshipGraph.d.ts +30 -0
- package/dist/components/RelationshipGraph.js +138 -0
- package/dist/components/RuleBuilder.d.ts +45 -0
- package/dist/components/RuleBuilder.js +163 -0
- package/dist/components/SchedulingCalendar.d.ts +42 -0
- package/dist/components/SchedulingCalendar.js +124 -0
- package/dist/diff-model.d.ts +23 -0
- package/dist/diff-model.js +50 -0
- package/dist/index.cjs +1753 -2
- package/dist/index.d.cts +439 -1
- package/dist/index.d.ts +18 -0
- package/dist/index.js +91 -1
- package/dist/json-value.d.ts +10 -0
- package/dist/json-value.js +19 -0
- package/dist/metric-model.d.ts +9 -0
- package/dist/metric-model.js +25 -0
- package/dist/relationship-graph-model.d.ts +31 -0
- package/dist/relationship-graph-model.js +68 -0
- package/dist/rule-builder-model.d.ts +41 -0
- package/dist/rule-builder-model.js +101 -0
- package/package.json +1 -1
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { useEffect, useId, useMemo, useRef, useState } from "react";
|
|
4
|
+
import { cx } from "../cx.js";
|
|
5
|
+
import { contrastRatio, hexToRgb, hsvToHex, normalizeHex, rgbToHsv } from "../color-model.js";
|
|
6
|
+
const EMPTY_COLORS = [];
|
|
7
|
+
function ColorPicker({
|
|
8
|
+
value,
|
|
9
|
+
onChange,
|
|
10
|
+
foreground,
|
|
11
|
+
presets = EMPTY_COLORS,
|
|
12
|
+
recent = EMPTY_COLORS,
|
|
13
|
+
minimumContrast = 4.5,
|
|
14
|
+
label = "Choose color",
|
|
15
|
+
disabled,
|
|
16
|
+
className
|
|
17
|
+
}) {
|
|
18
|
+
const id = useId();
|
|
19
|
+
const triggerRef = useRef(null);
|
|
20
|
+
const dialogRef = useRef(null);
|
|
21
|
+
const [open, setOpen] = useState(false);
|
|
22
|
+
const normalized = normalizeHex(value) ?? "#000000";
|
|
23
|
+
const [hexText, setHexText] = useState(normalized);
|
|
24
|
+
const hsv = useMemo(() => rgbToHsv(hexToRgb(normalized)), [normalized]);
|
|
25
|
+
const ratio = contrastRatio(normalized, foreground) ?? 0;
|
|
26
|
+
const passes = ratio >= minimumContrast;
|
|
27
|
+
useEffect(() => setHexText(normalized), [normalized]);
|
|
28
|
+
useEffect(() => {
|
|
29
|
+
if (!open) return;
|
|
30
|
+
const dialog = dialogRef.current;
|
|
31
|
+
dialog?.querySelector("input,button")?.focus();
|
|
32
|
+
const onPointer = (event) => {
|
|
33
|
+
if (!dialog?.contains(event.target) && !triggerRef.current?.contains(event.target)) setOpen(false);
|
|
34
|
+
};
|
|
35
|
+
document.addEventListener("mousedown", onPointer);
|
|
36
|
+
return () => document.removeEventListener("mousedown", onPointer);
|
|
37
|
+
}, [open]);
|
|
38
|
+
const close = () => {
|
|
39
|
+
setOpen(false);
|
|
40
|
+
requestAnimationFrame(() => triggerRef.current?.focus());
|
|
41
|
+
};
|
|
42
|
+
const onDialogKeyDown = (event) => {
|
|
43
|
+
if (event.key === "Escape") {
|
|
44
|
+
event.preventDefault();
|
|
45
|
+
close();
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
if (event.key !== "Tab") return;
|
|
49
|
+
const focusable = Array.from(dialogRef.current?.querySelectorAll('button:not([disabled]),input:not([disabled]),[tabindex="0"]') ?? []);
|
|
50
|
+
if (!focusable.length) return;
|
|
51
|
+
const first = focusable[0];
|
|
52
|
+
const last = focusable[focusable.length - 1];
|
|
53
|
+
if (event.shiftKey && document.activeElement === first) {
|
|
54
|
+
event.preventDefault();
|
|
55
|
+
last.focus();
|
|
56
|
+
} else if (!event.shiftKey && document.activeElement === last) {
|
|
57
|
+
event.preventDefault();
|
|
58
|
+
first.focus();
|
|
59
|
+
}
|
|
60
|
+
};
|
|
61
|
+
const updateHsv = (part) => onChange(hsvToHex({ ...hsv, ...part }));
|
|
62
|
+
const commitHex = () => {
|
|
63
|
+
const next = normalizeHex(hexText);
|
|
64
|
+
if (next) onChange(next);
|
|
65
|
+
};
|
|
66
|
+
const choose = (candidate) => {
|
|
67
|
+
const next = normalizeHex(candidate);
|
|
68
|
+
if (next) onChange(next);
|
|
69
|
+
};
|
|
70
|
+
const sampleStyle = { backgroundColor: normalized };
|
|
71
|
+
return /* @__PURE__ */ jsxs("div", { className: cx("uix-color-picker", className), children: [
|
|
72
|
+
/* @__PURE__ */ jsxs(
|
|
73
|
+
"button",
|
|
74
|
+
{
|
|
75
|
+
ref: triggerRef,
|
|
76
|
+
type: "button",
|
|
77
|
+
className: "uix-color-picker__trigger",
|
|
78
|
+
"aria-label": `${label}: ${normalized}`,
|
|
79
|
+
"aria-expanded": open,
|
|
80
|
+
"aria-controls": `${id}-dialog`,
|
|
81
|
+
onClick: () => setOpen((current) => !current),
|
|
82
|
+
disabled,
|
|
83
|
+
children: [
|
|
84
|
+
/* @__PURE__ */ jsx("span", { className: "uix-color-picker__swatch", style: sampleStyle, "aria-hidden": "true" }),
|
|
85
|
+
/* @__PURE__ */ jsx("span", { children: normalized })
|
|
86
|
+
]
|
|
87
|
+
}
|
|
88
|
+
),
|
|
89
|
+
open && /* @__PURE__ */ jsxs(
|
|
90
|
+
"div",
|
|
91
|
+
{
|
|
92
|
+
ref: dialogRef,
|
|
93
|
+
id: `${id}-dialog`,
|
|
94
|
+
role: "dialog",
|
|
95
|
+
"aria-modal": "true",
|
|
96
|
+
"aria-label": label,
|
|
97
|
+
className: "uix-color-picker__popover",
|
|
98
|
+
onKeyDown: onDialogKeyDown,
|
|
99
|
+
children: [
|
|
100
|
+
/* @__PURE__ */ jsx("div", { className: "uix-color-picker__preview", style: sampleStyle, children: /* @__PURE__ */ jsx("span", { style: { color: foreground }, children: "Aa" }) }),
|
|
101
|
+
/* @__PURE__ */ jsxs("label", { className: "uix-color-picker__range", children: [
|
|
102
|
+
"Hue ",
|
|
103
|
+
/* @__PURE__ */ jsxs("span", { children: [
|
|
104
|
+
Math.round(hsv.h),
|
|
105
|
+
"\xB0"
|
|
106
|
+
] }),
|
|
107
|
+
/* @__PURE__ */ jsx("input", { type: "range", min: "0", max: "359", value: Math.round(hsv.h), onChange: (event) => updateHsv({ h: Number(event.currentTarget.value) }) })
|
|
108
|
+
] }),
|
|
109
|
+
/* @__PURE__ */ jsxs("label", { className: "uix-color-picker__range", children: [
|
|
110
|
+
"Saturation ",
|
|
111
|
+
/* @__PURE__ */ jsxs("span", { children: [
|
|
112
|
+
Math.round(hsv.s * 100),
|
|
113
|
+
"%"
|
|
114
|
+
] }),
|
|
115
|
+
/* @__PURE__ */ jsx("input", { type: "range", min: "0", max: "100", value: Math.round(hsv.s * 100), onChange: (event) => updateHsv({ s: Number(event.currentTarget.value) / 100 }) })
|
|
116
|
+
] }),
|
|
117
|
+
/* @__PURE__ */ jsxs("label", { className: "uix-color-picker__range", children: [
|
|
118
|
+
"Brightness ",
|
|
119
|
+
/* @__PURE__ */ jsxs("span", { children: [
|
|
120
|
+
Math.round(hsv.v * 100),
|
|
121
|
+
"%"
|
|
122
|
+
] }),
|
|
123
|
+
/* @__PURE__ */ jsx("input", { type: "range", min: "0", max: "100", value: Math.round(hsv.v * 100), onChange: (event) => updateHsv({ v: Number(event.currentTarget.value) / 100 }) })
|
|
124
|
+
] }),
|
|
125
|
+
/* @__PURE__ */ jsxs("label", { className: "uix-color-picker__hex", children: [
|
|
126
|
+
"Hex",
|
|
127
|
+
/* @__PURE__ */ jsxs("span", { className: "uix-color-picker__hex-row", children: [
|
|
128
|
+
/* @__PURE__ */ jsx("input", { className: "uix-input", value: hexText, onChange: (event) => setHexText(event.currentTarget.value), onBlur: commitHex, onKeyDown: (event) => {
|
|
129
|
+
if (event.key === "Enter") commitHex();
|
|
130
|
+
}, "aria-invalid": !normalizeHex(hexText) }),
|
|
131
|
+
/* @__PURE__ */ jsx("button", { type: "button", className: "uix-btn uix-btn--secondary uix-btn--sm", onClick: commitHex, disabled: !normalizeHex(hexText), children: "Set" })
|
|
132
|
+
] })
|
|
133
|
+
] }),
|
|
134
|
+
(presets.length > 0 || recent.length > 0) && /* @__PURE__ */ jsxs("div", { className: "uix-color-picker__palettes", children: [
|
|
135
|
+
presets.length > 0 && /* @__PURE__ */ jsxs("fieldset", { children: [
|
|
136
|
+
/* @__PURE__ */ jsx("legend", { children: "Presets" }),
|
|
137
|
+
/* @__PURE__ */ jsx("div", { children: presets.map((color) => /* @__PURE__ */ jsx("button", { type: "button", className: "uix-color-picker__preset", style: { backgroundColor: normalizeHex(color) ?? color }, onClick: () => choose(color), "aria-label": `Use preset ${color}` }, color)) })
|
|
138
|
+
] }),
|
|
139
|
+
recent.length > 0 && /* @__PURE__ */ jsxs("fieldset", { children: [
|
|
140
|
+
/* @__PURE__ */ jsx("legend", { children: "Recent" }),
|
|
141
|
+
/* @__PURE__ */ jsx("div", { children: recent.map((color) => /* @__PURE__ */ jsx("button", { type: "button", className: "uix-color-picker__preset", style: { backgroundColor: normalizeHex(color) ?? color }, onClick: () => choose(color), "aria-label": `Use recent color ${color}` }, color)) })
|
|
142
|
+
] })
|
|
143
|
+
] }),
|
|
144
|
+
/* @__PURE__ */ jsxs("p", { className: cx("uix-color-picker__contrast", passes ? "uix-color-picker__contrast--pass" : "uix-color-picker__contrast--fail"), role: "status", "aria-live": "polite", children: [
|
|
145
|
+
"Contrast ",
|
|
146
|
+
ratio.toFixed(2),
|
|
147
|
+
":1 \u2014 ",
|
|
148
|
+
passes ? `passes WCAG AA (${minimumContrast}:1)` : `does not meet WCAG AA (${minimumContrast}:1)`
|
|
149
|
+
] }),
|
|
150
|
+
/* @__PURE__ */ jsx("button", { type: "button", className: "uix-btn uix-btn--primary uix-btn--sm", onClick: close, children: "Done" })
|
|
151
|
+
]
|
|
152
|
+
}
|
|
153
|
+
)
|
|
154
|
+
] });
|
|
155
|
+
}
|
|
156
|
+
export {
|
|
157
|
+
ColorPicker
|
|
158
|
+
};
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import * as react from 'react';
|
|
2
|
+
import { DateRangeValue } from '../calendar-model.js';
|
|
3
|
+
|
|
4
|
+
interface DateRangePickerProps {
|
|
5
|
+
value: DateRangeValue;
|
|
6
|
+
onChange: (value: DateRangeValue) => void;
|
|
7
|
+
visibleMonth?: string;
|
|
8
|
+
onVisibleMonthChange?: (month: string) => void;
|
|
9
|
+
months?: number;
|
|
10
|
+
min?: string;
|
|
11
|
+
max?: string;
|
|
12
|
+
isDateDisabled?: (date: string) => boolean;
|
|
13
|
+
locale?: string;
|
|
14
|
+
label?: string;
|
|
15
|
+
invalid?: boolean;
|
|
16
|
+
invalidMessage?: string;
|
|
17
|
+
disabled?: boolean;
|
|
18
|
+
className?: string;
|
|
19
|
+
}
|
|
20
|
+
/** Controlled, two-month range picker using the UIx calendar language. */
|
|
21
|
+
declare function DateRangePicker({ value, onChange, visibleMonth: controlledMonth, onVisibleMonthChange, months, min, max, isDateDisabled, locale, label, invalid, invalidMessage, disabled, className, }: DateRangePickerProps): react.JSX.Element;
|
|
22
|
+
|
|
23
|
+
export { DateRangePicker, type DateRangePickerProps };
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { useEffect, useId, useMemo, useRef, useState } from "react";
|
|
4
|
+
import { addCalendarDays, addCalendarMonths, buildMonthGrid, isDateInRange, isDateUnavailable, selectRangeDate, startOfMonth } from "../calendar-model.js";
|
|
5
|
+
import { cx } from "../cx.js";
|
|
6
|
+
const monthLabel = (date, locale) => new Intl.DateTimeFormat(locale, { month: "long", year: "numeric", timeZone: "UTC" }).format(/* @__PURE__ */ new Date(`${date}T00:00:00Z`));
|
|
7
|
+
const dateLabel = (date, locale) => new Intl.DateTimeFormat(locale, { dateStyle: "full", timeZone: "UTC" }).format(/* @__PURE__ */ new Date(`${date}T00:00:00Z`));
|
|
8
|
+
function DateRangePicker({
|
|
9
|
+
value,
|
|
10
|
+
onChange,
|
|
11
|
+
visibleMonth: controlledMonth,
|
|
12
|
+
onVisibleMonthChange,
|
|
13
|
+
months = 2,
|
|
14
|
+
min,
|
|
15
|
+
max,
|
|
16
|
+
isDateDisabled,
|
|
17
|
+
locale,
|
|
18
|
+
label = "Choose date range",
|
|
19
|
+
invalid,
|
|
20
|
+
invalidMessage = "The selected date range is invalid.",
|
|
21
|
+
disabled,
|
|
22
|
+
className
|
|
23
|
+
}) {
|
|
24
|
+
const id = useId();
|
|
25
|
+
const [internalMonth, setInternalMonth] = useState(() => startOfMonth(controlledMonth ?? value.start ?? (/* @__PURE__ */ new Date()).toISOString().slice(0, 10)));
|
|
26
|
+
const visibleMonth = startOfMonth(controlledMonth ?? internalMonth);
|
|
27
|
+
const [activeDate, setActiveDate] = useState(value.end ?? value.start ?? visibleMonth);
|
|
28
|
+
const rootRef = useRef(null);
|
|
29
|
+
const pendingFocus = useRef(false);
|
|
30
|
+
const count = Math.max(1, Math.min(3, months));
|
|
31
|
+
const calendars = useMemo(() => Array.from({ length: count }, (_, index) => {
|
|
32
|
+
const month = startOfMonth(addCalendarMonths(visibleMonth, index));
|
|
33
|
+
return { month, days: buildMonthGrid(month).filter((day) => day.inMonth) };
|
|
34
|
+
}), [visibleMonth, count]);
|
|
35
|
+
const rangeInvalid = invalid || !!value.start && !!value.end && value.start > value.end;
|
|
36
|
+
useEffect(() => {
|
|
37
|
+
const first = calendars[0]?.days[0]?.date;
|
|
38
|
+
const finalDays = calendars[calendars.length - 1]?.days;
|
|
39
|
+
const last = finalDays?.[finalDays.length - 1]?.date;
|
|
40
|
+
if (first && last && (activeDate < first || activeDate > last)) setActiveDate(first);
|
|
41
|
+
}, [activeDate, calendars]);
|
|
42
|
+
useEffect(() => {
|
|
43
|
+
if (!pendingFocus.current) return;
|
|
44
|
+
rootRef.current?.querySelector(`[data-date="${activeDate}"]`)?.focus();
|
|
45
|
+
pendingFocus.current = false;
|
|
46
|
+
}, [activeDate, visibleMonth]);
|
|
47
|
+
const setMonth = (month) => {
|
|
48
|
+
const next = startOfMonth(month);
|
|
49
|
+
if (controlledMonth === void 0) setInternalMonth(next);
|
|
50
|
+
onVisibleMonthChange?.(next);
|
|
51
|
+
};
|
|
52
|
+
const focusDate = (date) => {
|
|
53
|
+
setActiveDate(date);
|
|
54
|
+
if (date < calendars[0].month || date.slice(0, 7) > calendars[calendars.length - 1].month.slice(0, 7)) setMonth(startOfMonth(date));
|
|
55
|
+
pendingFocus.current = true;
|
|
56
|
+
};
|
|
57
|
+
const choose = (date) => {
|
|
58
|
+
if (disabled || isDateUnavailable(date, { min, max, isDisabled: isDateDisabled })) return;
|
|
59
|
+
onChange(selectRangeDate(value, date));
|
|
60
|
+
setActiveDate(date);
|
|
61
|
+
};
|
|
62
|
+
const onDateKeyDown = (event, date) => {
|
|
63
|
+
const offsets = { ArrowLeft: -1, ArrowRight: 1, ArrowUp: -7, ArrowDown: 7 };
|
|
64
|
+
if (event.key in offsets) {
|
|
65
|
+
event.preventDefault();
|
|
66
|
+
focusDate(addCalendarDays(date, offsets[event.key]));
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
if (event.key === "Home" || event.key === "End") {
|
|
70
|
+
event.preventDefault();
|
|
71
|
+
const weekday = (/* @__PURE__ */ new Date(`${date}T00:00:00Z`)).getUTCDay();
|
|
72
|
+
const mondayIndex = (weekday + 6) % 7;
|
|
73
|
+
focusDate(addCalendarDays(date, event.key === "Home" ? -mondayIndex : 6 - mondayIndex));
|
|
74
|
+
} else if (event.key === "PageUp" || event.key === "PageDown") {
|
|
75
|
+
event.preventDefault();
|
|
76
|
+
focusDate(addCalendarMonths(date, event.key === "PageUp" ? -1 : 1));
|
|
77
|
+
}
|
|
78
|
+
};
|
|
79
|
+
const announcement = !value.start ? "No date range selected." : !value.end ? `Start date ${dateLabel(value.start, locale)} selected. Choose an end date.` : `${dateLabel(value.start, locale)} to ${dateLabel(value.end, locale)} selected.`;
|
|
80
|
+
return /* @__PURE__ */ jsxs("section", { className: cx("uix-date-range-picker", rangeInvalid && "uix-date-range-picker--invalid", className), "aria-labelledby": `${id}-label`, children: [
|
|
81
|
+
/* @__PURE__ */ jsxs("div", { className: "uix-date-range-picker__header", children: [
|
|
82
|
+
/* @__PURE__ */ jsx("h3", { id: `${id}-label`, children: label }),
|
|
83
|
+
/* @__PURE__ */ jsxs("div", { children: [
|
|
84
|
+
/* @__PURE__ */ jsx("button", { type: "button", className: "uix-btn uix-btn--ghost uix-btn--sm", onClick: () => setMonth(addCalendarMonths(visibleMonth, -1)), "aria-label": "Show previous month", children: "Previous" }),
|
|
85
|
+
/* @__PURE__ */ jsx("button", { type: "button", className: "uix-btn uix-btn--ghost uix-btn--sm", onClick: () => setMonth(addCalendarMonths(visibleMonth, 1)), "aria-label": "Show next month", children: "Next" })
|
|
86
|
+
] })
|
|
87
|
+
] }),
|
|
88
|
+
/* @__PURE__ */ jsx("div", { ref: rootRef, className: "uix-date-range-picker__months", children: calendars.map(({ month, days }) => /* @__PURE__ */ jsxs("div", { className: "uix-date-range-picker__month", children: [
|
|
89
|
+
/* @__PURE__ */ jsx("h4", { children: monthLabel(month, locale) }),
|
|
90
|
+
/* @__PURE__ */ jsx("div", { className: "uix-date-range-picker__weekdays", "aria-hidden": "true", children: ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"].map((day) => /* @__PURE__ */ jsx("span", { children: day }, day)) }),
|
|
91
|
+
/* @__PURE__ */ jsxs("div", { className: "uix-date-range-picker__grid", role: "group", "aria-label": monthLabel(month, locale), children: [
|
|
92
|
+
Array.from({ length: ((/* @__PURE__ */ new Date(`${month}T00:00:00Z`)).getUTCDay() + 6) % 7 }, (_, index) => /* @__PURE__ */ jsx("span", { "aria-hidden": "true" }, `blank-${index}`)),
|
|
93
|
+
days.map((day) => {
|
|
94
|
+
const unavailable = disabled || isDateUnavailable(day.date, { min, max, isDisabled: isDateDisabled });
|
|
95
|
+
const edge = day.date === value.start ? "start" : day.date === value.end ? "end" : void 0;
|
|
96
|
+
return /* @__PURE__ */ jsx(
|
|
97
|
+
"button",
|
|
98
|
+
{
|
|
99
|
+
type: "button",
|
|
100
|
+
"data-date": day.date,
|
|
101
|
+
"data-range-edge": edge,
|
|
102
|
+
"data-in-range": isDateInRange(day.date, value) || void 0,
|
|
103
|
+
"aria-pressed": edge != null || isDateInRange(day.date, value),
|
|
104
|
+
"aria-label": `${dateLabel(day.date, locale)}${edge ? `, ${edge} of selected range` : isDateInRange(day.date, value) ? ", in selected range" : ""}`,
|
|
105
|
+
tabIndex: activeDate === day.date ? 0 : -1,
|
|
106
|
+
disabled: unavailable,
|
|
107
|
+
onFocus: () => setActiveDate(day.date),
|
|
108
|
+
onKeyDown: (event) => onDateKeyDown(event, day.date),
|
|
109
|
+
onClick: () => choose(day.date),
|
|
110
|
+
children: day.day
|
|
111
|
+
},
|
|
112
|
+
day.date
|
|
113
|
+
);
|
|
114
|
+
})
|
|
115
|
+
] })
|
|
116
|
+
] }, month)) }),
|
|
117
|
+
/* @__PURE__ */ jsx("p", { className: "uix-date-range-picker__selection", "aria-live": "polite", children: announcement }),
|
|
118
|
+
rangeInvalid && /* @__PURE__ */ jsx("p", { className: "uix-field__error", role: "alert", children: invalidMessage })
|
|
119
|
+
] });
|
|
120
|
+
}
|
|
121
|
+
export {
|
|
122
|
+
DateRangePicker
|
|
123
|
+
};
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import * as react from 'react';
|
|
2
|
+
import { JsonValue } from '../json-value.js';
|
|
3
|
+
import { DiffEntry, DiffResolution } from '../diff-model.js';
|
|
4
|
+
|
|
5
|
+
interface DiffViewerProps {
|
|
6
|
+
base?: JsonValue;
|
|
7
|
+
current?: JsonValue;
|
|
8
|
+
incoming?: JsonValue;
|
|
9
|
+
entries?: DiffEntry[];
|
|
10
|
+
resolutions?: Record<string, DiffResolution>;
|
|
11
|
+
onResolutionChange?: (path: string, resolution: DiffResolution) => void;
|
|
12
|
+
labels?: Partial<Record<'base' | 'current' | 'incoming', string>>;
|
|
13
|
+
className?: string;
|
|
14
|
+
}
|
|
15
|
+
/** Three-way configuration diff with per-entry keyboard-operable resolution. */
|
|
16
|
+
declare function DiffViewer({ base, current, incoming, entries: providedEntries, resolutions: controlledResolutions, onResolutionChange, labels, className, }: DiffViewerProps): react.JSX.Element;
|
|
17
|
+
|
|
18
|
+
export { DiffViewer, type DiffViewerProps };
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { useId, useMemo, useState } from "react";
|
|
4
|
+
import { buildThreeWayDiff, summarizeDiff } from "../diff-model.js";
|
|
5
|
+
import { cx } from "../cx.js";
|
|
6
|
+
import { StatusPill } from "./StatusPill.js";
|
|
7
|
+
const GROUPS = [
|
|
8
|
+
{ kind: "conflicted", label: "Conflicted" },
|
|
9
|
+
{ kind: "changed", label: "Changed" },
|
|
10
|
+
{ kind: "added", label: "Added" },
|
|
11
|
+
{ kind: "removed", label: "Removed" }
|
|
12
|
+
];
|
|
13
|
+
const DEFAULT_LABELS = {};
|
|
14
|
+
const renderValue = (value) => value === void 0 ? "Not present" : JSON.stringify(value, null, 2);
|
|
15
|
+
function DiffViewer({
|
|
16
|
+
base,
|
|
17
|
+
current,
|
|
18
|
+
incoming,
|
|
19
|
+
entries: providedEntries,
|
|
20
|
+
resolutions: controlledResolutions,
|
|
21
|
+
onResolutionChange,
|
|
22
|
+
labels = DEFAULT_LABELS,
|
|
23
|
+
className
|
|
24
|
+
}) {
|
|
25
|
+
const id = useId();
|
|
26
|
+
const entries = useMemo(() => providedEntries ?? buildThreeWayDiff(base, current, incoming), [providedEntries, base, current, incoming]);
|
|
27
|
+
const [internalResolutions, setInternalResolutions] = useState({});
|
|
28
|
+
const resolutions = controlledResolutions ?? internalResolutions;
|
|
29
|
+
const summary = summarizeDiff(entries, resolutions);
|
|
30
|
+
const resolve = (path, resolution) => {
|
|
31
|
+
if (controlledResolutions === void 0) setInternalResolutions((currentState) => ({ ...currentState, [path]: resolution }));
|
|
32
|
+
onResolutionChange?.(path, resolution);
|
|
33
|
+
};
|
|
34
|
+
if (entries.length === 0) return /* @__PURE__ */ jsxs("section", { className: cx("uix-diff-viewer uix-diff-viewer--empty", className), "aria-label": "Configuration differences", children: [
|
|
35
|
+
/* @__PURE__ */ jsx("h3", { children: "No differences" }),
|
|
36
|
+
/* @__PURE__ */ jsx("p", { children: "Base, current, and incoming values are aligned." })
|
|
37
|
+
] });
|
|
38
|
+
return /* @__PURE__ */ jsxs("section", { className: cx("uix-diff-viewer", className), "aria-label": "Configuration differences", children: [
|
|
39
|
+
/* @__PURE__ */ jsxs("div", { className: "uix-diff-viewer__summary", "aria-label": "Difference summary", children: [
|
|
40
|
+
GROUPS.map((group) => /* @__PURE__ */ jsxs("span", { children: [
|
|
41
|
+
/* @__PURE__ */ jsx("b", { children: summary[group.kind] }),
|
|
42
|
+
" ",
|
|
43
|
+
group.label.toLowerCase()
|
|
44
|
+
] }, group.kind)),
|
|
45
|
+
/* @__PURE__ */ jsxs("span", { children: [
|
|
46
|
+
/* @__PURE__ */ jsx("b", { children: summary.resolved }),
|
|
47
|
+
" resolved"
|
|
48
|
+
] }),
|
|
49
|
+
/* @__PURE__ */ jsxs("span", { children: [
|
|
50
|
+
/* @__PURE__ */ jsx("b", { children: summary.pending }),
|
|
51
|
+
" pending"
|
|
52
|
+
] })
|
|
53
|
+
] }),
|
|
54
|
+
GROUPS.map((group) => {
|
|
55
|
+
const grouped = entries.filter((entry) => entry.kind === group.kind);
|
|
56
|
+
if (!grouped.length) return null;
|
|
57
|
+
return /* @__PURE__ */ jsxs("section", { className: "uix-diff-viewer__group", "aria-labelledby": `${id}-${group.kind}`, children: [
|
|
58
|
+
/* @__PURE__ */ jsxs("h3", { id: `${id}-${group.kind}`, children: [
|
|
59
|
+
group.label,
|
|
60
|
+
" ",
|
|
61
|
+
/* @__PURE__ */ jsx("span", { children: grouped.length })
|
|
62
|
+
] }),
|
|
63
|
+
grouped.map((entry) => {
|
|
64
|
+
const resolution = resolutions[entry.path] ?? "pending";
|
|
65
|
+
return /* @__PURE__ */ jsxs("details", { className: "uix-diff-viewer__entry", "data-kind": entry.kind, open: entry.kind === "conflicted", children: [
|
|
66
|
+
/* @__PURE__ */ jsxs("summary", { children: [
|
|
67
|
+
/* @__PURE__ */ jsx("code", { children: entry.path }),
|
|
68
|
+
/* @__PURE__ */ jsx(StatusPill, { tone: resolution === "pending" ? "warning" : "success", children: resolution })
|
|
69
|
+
] }),
|
|
70
|
+
/* @__PURE__ */ jsx("div", { className: "uix-diff-viewer__versions", children: ["base", "current", "incoming"].map((version) => /* @__PURE__ */ jsxs("div", { role: "region", "aria-label": `${labels[version] ?? `${version[0].toUpperCase()}${version.slice(1)}`} value for ${entry.path}`, children: [
|
|
71
|
+
/* @__PURE__ */ jsx("strong", { children: labels[version] ?? `${version[0].toUpperCase()}${version.slice(1)}` }),
|
|
72
|
+
/* @__PURE__ */ jsx("pre", { children: renderValue(entry[version]) })
|
|
73
|
+
] }, version)) }),
|
|
74
|
+
/* @__PURE__ */ jsxs("div", { className: "uix-diff-viewer__actions", "aria-label": `Resolve ${entry.path}`, children: [
|
|
75
|
+
/* @__PURE__ */ jsx("button", { type: "button", className: "uix-btn uix-btn--primary uix-btn--sm", "aria-pressed": resolution === "accept", onClick: () => resolve(entry.path, "accept"), children: "Accept incoming" }),
|
|
76
|
+
/* @__PURE__ */ jsx("button", { type: "button", className: "uix-btn uix-btn--secondary uix-btn--sm", "aria-pressed": resolution === "skip", onClick: () => resolve(entry.path, "skip"), children: "Keep current" }),
|
|
77
|
+
/* @__PURE__ */ jsx("button", { type: "button", className: "uix-btn uix-btn--ghost uix-btn--sm", "aria-pressed": resolution === "pending", onClick: () => resolve(entry.path, "pending"), children: "Mark pending" })
|
|
78
|
+
] })
|
|
79
|
+
] }, entry.path);
|
|
80
|
+
})
|
|
81
|
+
] }, group.kind);
|
|
82
|
+
}),
|
|
83
|
+
/* @__PURE__ */ jsxs("p", { className: "uix-visually-hidden", "aria-live": "polite", children: [
|
|
84
|
+
summary.resolved,
|
|
85
|
+
" of ",
|
|
86
|
+
entries.length,
|
|
87
|
+
" differences resolved."
|
|
88
|
+
] })
|
|
89
|
+
] });
|
|
90
|
+
}
|
|
91
|
+
export {
|
|
92
|
+
DiffViewer
|
|
93
|
+
};
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import * as react from 'react';
|
|
2
|
+
import { HTMLAttributes } from 'react';
|
|
3
|
+
|
|
4
|
+
interface LicensePositionBarProps extends Omit<HTMLAttributes<HTMLDivElement>, 'children'> {
|
|
5
|
+
consumed: number;
|
|
6
|
+
entitled: number;
|
|
7
|
+
consumedLabel?: string;
|
|
8
|
+
entitledLabel?: string;
|
|
9
|
+
availableLabel?: string;
|
|
10
|
+
overLabel?: string;
|
|
11
|
+
unit?: string;
|
|
12
|
+
formatValue?: (value: number) => string;
|
|
13
|
+
}
|
|
14
|
+
/** Capacity-versus-limit recipe with a visible and textual over-allocation state. */
|
|
15
|
+
declare function LicensePositionBar({ consumed, entitled, consumedLabel, entitledLabel, availableLabel, overLabel, unit, formatValue, className, ...props }: LicensePositionBarProps): react.JSX.Element;
|
|
16
|
+
|
|
17
|
+
export { LicensePositionBar, type LicensePositionBarProps };
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { cx } from "../cx.js";
|
|
3
|
+
import { Meter } from "./Meter.js";
|
|
4
|
+
import { StatusPill } from "./StatusPill.js";
|
|
5
|
+
const defaultCapacityFormat = (value) => new Intl.NumberFormat().format(value);
|
|
6
|
+
function LicensePositionBar({
|
|
7
|
+
consumed,
|
|
8
|
+
entitled,
|
|
9
|
+
consumedLabel = "Consumed",
|
|
10
|
+
entitledLabel = "Entitled",
|
|
11
|
+
availableLabel = "available",
|
|
12
|
+
overLabel = "over limit",
|
|
13
|
+
unit = "",
|
|
14
|
+
formatValue = defaultCapacityFormat,
|
|
15
|
+
className,
|
|
16
|
+
...props
|
|
17
|
+
}) {
|
|
18
|
+
const safeConsumed = Math.max(0, consumed);
|
|
19
|
+
const safeEntitled = Math.max(0, entitled);
|
|
20
|
+
const over = Math.max(0, safeConsumed - safeEntitled);
|
|
21
|
+
const available = Math.max(0, safeEntitled - safeConsumed);
|
|
22
|
+
const percent = safeEntitled === 0 ? safeConsumed > 0 ? 100 : 0 : safeConsumed / safeEntitled * 100;
|
|
23
|
+
const format = (value) => `${formatValue(value)}${unit ? ` ${unit}` : ""}`;
|
|
24
|
+
const summary = `${consumedLabel}: ${format(safeConsumed)}. ${entitledLabel}: ${format(safeEntitled)}. ${over > 0 ? `${format(over)} ${overLabel}` : `${format(available)} ${availableLabel}`}.`;
|
|
25
|
+
return /* @__PURE__ */ jsxs("div", { className: cx("uix-license-position", over > 0 && "uix-license-position--over", className), ...props, children: [
|
|
26
|
+
/* @__PURE__ */ jsxs("div", { className: "uix-license-position__header", children: [
|
|
27
|
+
/* @__PURE__ */ jsxs("span", { className: "uix-license-position__value", children: [
|
|
28
|
+
format(safeConsumed),
|
|
29
|
+
" / ",
|
|
30
|
+
format(safeEntitled)
|
|
31
|
+
] }),
|
|
32
|
+
/* @__PURE__ */ jsx(StatusPill, { tone: over > 0 ? "danger" : percent >= 80 ? "warning" : "success", children: over > 0 ? `${format(over)} ${overLabel}` : `${format(available)} ${availableLabel}` })
|
|
33
|
+
] }),
|
|
34
|
+
/* @__PURE__ */ jsxs("div", { className: "uix-license-position__track", children: [
|
|
35
|
+
/* @__PURE__ */ jsx(Meter, { value: Math.min(100, percent), tone: over > 0 ? "danger" : percent >= 80 ? "warning" : "success", "aria-label": summary, "aria-valuetext": summary }),
|
|
36
|
+
over > 0 && /* @__PURE__ */ jsx(
|
|
37
|
+
"span",
|
|
38
|
+
{
|
|
39
|
+
className: "uix-license-position__overflow",
|
|
40
|
+
style: { width: `${Math.min(100, safeEntitled === 0 ? 100 : over / safeEntitled * 100)}%` },
|
|
41
|
+
"aria-hidden": "true"
|
|
42
|
+
}
|
|
43
|
+
)
|
|
44
|
+
] }),
|
|
45
|
+
/* @__PURE__ */ jsx("span", { className: "uix-visually-hidden", children: summary })
|
|
46
|
+
] });
|
|
47
|
+
}
|
|
48
|
+
export {
|
|
49
|
+
LicensePositionBar
|
|
50
|
+
};
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import * as react from 'react';
|
|
2
|
+
import { ReactNode } from 'react';
|
|
3
|
+
import { JsonValue } from '../json-value.js';
|
|
4
|
+
|
|
5
|
+
interface MatchReviewField {
|
|
6
|
+
id: string;
|
|
7
|
+
label: string;
|
|
8
|
+
render?: (value: JsonValue | undefined, record: Record<string, JsonValue>) => ReactNode;
|
|
9
|
+
}
|
|
10
|
+
interface MatchReviewCandidate {
|
|
11
|
+
id: string;
|
|
12
|
+
record: Record<string, JsonValue>;
|
|
13
|
+
confidence?: number;
|
|
14
|
+
status?: 'pending' | 'resolved';
|
|
15
|
+
decision?: 'accepted' | 'dismissed' | 'split' | 'other';
|
|
16
|
+
}
|
|
17
|
+
type MatchReviewAction = 'accept' | 'dismiss' | 'split' | 'pick-other';
|
|
18
|
+
interface MatchBulkResult {
|
|
19
|
+
succeeded: number;
|
|
20
|
+
failed?: number;
|
|
21
|
+
}
|
|
22
|
+
interface MatchReviewProps {
|
|
23
|
+
incoming: Record<string, JsonValue>;
|
|
24
|
+
incomingLabel?: string;
|
|
25
|
+
fields: MatchReviewField[];
|
|
26
|
+
candidates: MatchReviewCandidate[];
|
|
27
|
+
selectedIds?: Set<string>;
|
|
28
|
+
onSelectionChange?: (ids: Set<string>) => void;
|
|
29
|
+
onDecision: (candidateId: string, action: MatchReviewAction) => void;
|
|
30
|
+
onBulkDecision?: (candidateIds: string[], action: Extract<MatchReviewAction, 'accept' | 'dismiss'>) => void | MatchBulkResult | Promise<void | MatchBulkResult>;
|
|
31
|
+
loading?: boolean;
|
|
32
|
+
error?: string;
|
|
33
|
+
onRetry?: () => void;
|
|
34
|
+
className?: string;
|
|
35
|
+
}
|
|
36
|
+
/** Generic descriptor-driven incoming-record to candidate-match decision surface. */
|
|
37
|
+
declare function MatchReview({ incoming, incomingLabel, fields, candidates, selectedIds, onSelectionChange, onDecision, onBulkDecision, loading, error, onRetry, className, }: MatchReviewProps): react.JSX.Element;
|
|
38
|
+
|
|
39
|
+
export { type MatchBulkResult, MatchReview, type MatchReviewAction, type MatchReviewCandidate, type MatchReviewField, type MatchReviewProps };
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { useMemo, useState } from "react";
|
|
4
|
+
import { cx } from "../cx.js";
|
|
5
|
+
import { StatusPill } from "./StatusPill.js";
|
|
6
|
+
const valueText = (value) => value == null ? "\u2014" : typeof value === "string" ? value : JSON.stringify(value);
|
|
7
|
+
function MatchReview({
|
|
8
|
+
incoming,
|
|
9
|
+
incomingLabel = "Incoming record",
|
|
10
|
+
fields,
|
|
11
|
+
candidates,
|
|
12
|
+
selectedIds,
|
|
13
|
+
onSelectionChange,
|
|
14
|
+
onDecision,
|
|
15
|
+
onBulkDecision,
|
|
16
|
+
loading,
|
|
17
|
+
error,
|
|
18
|
+
onRetry,
|
|
19
|
+
className
|
|
20
|
+
}) {
|
|
21
|
+
const [internalSelection, setInternalSelection] = useState(/* @__PURE__ */ new Set());
|
|
22
|
+
const [announcement, setAnnouncement] = useState("");
|
|
23
|
+
const [bulkPending, setBulkPending] = useState(false);
|
|
24
|
+
const selection = selectedIds ?? internalSelection;
|
|
25
|
+
const pending = useMemo(() => candidates.filter((candidate) => candidate.status !== "resolved"), [candidates]);
|
|
26
|
+
const setSelection = (next) => {
|
|
27
|
+
if (selectedIds === void 0) setInternalSelection(next);
|
|
28
|
+
onSelectionChange?.(next);
|
|
29
|
+
};
|
|
30
|
+
const toggle = (id) => {
|
|
31
|
+
const next = new Set(selection);
|
|
32
|
+
if (next.has(id)) next.delete(id);
|
|
33
|
+
else next.add(id);
|
|
34
|
+
setSelection(next);
|
|
35
|
+
};
|
|
36
|
+
const runBulk = async (action) => {
|
|
37
|
+
if (!onBulkDecision || selection.size === 0 || bulkPending) return;
|
|
38
|
+
const ids = [...selection];
|
|
39
|
+
setBulkPending(true);
|
|
40
|
+
setAnnouncement(`${action === "accept" ? "Accepting" : "Dismissing"} ${ids.length} candidate${ids.length === 1 ? "" : "s"}\u2026`);
|
|
41
|
+
try {
|
|
42
|
+
const result = await onBulkDecision(ids, action);
|
|
43
|
+
const succeeded = result?.succeeded ?? ids.length;
|
|
44
|
+
const failed = result?.failed ?? 0;
|
|
45
|
+
setAnnouncement(`${succeeded} candidate${succeeded === 1 ? "" : "s"} ${action === "accept" ? "accepted" : "dismissed"}${failed ? `; ${failed} failed` : ""}.`);
|
|
46
|
+
if (!failed) setSelection(/* @__PURE__ */ new Set());
|
|
47
|
+
} catch {
|
|
48
|
+
setAnnouncement(`Bulk ${action} failed. Try again.`);
|
|
49
|
+
} finally {
|
|
50
|
+
setBulkPending(false);
|
|
51
|
+
}
|
|
52
|
+
};
|
|
53
|
+
if (loading) return /* @__PURE__ */ jsx("div", { className: cx("uix-match-review uix-match-review--state", className), role: "status", children: "Loading match candidates\u2026" });
|
|
54
|
+
if (error) return /* @__PURE__ */ jsxs("div", { className: cx("uix-match-review uix-match-review--state", className), role: "alert", children: [
|
|
55
|
+
/* @__PURE__ */ jsx("p", { children: error }),
|
|
56
|
+
onRetry && /* @__PURE__ */ jsx("button", { type: "button", className: "uix-btn uix-btn--secondary", onClick: onRetry, children: "Try again" })
|
|
57
|
+
] });
|
|
58
|
+
if (candidates.length === 0) return /* @__PURE__ */ jsxs("div", { className: cx("uix-match-review uix-match-review--state", className), children: [
|
|
59
|
+
/* @__PURE__ */ jsx("p", { children: "No candidate matches found." }),
|
|
60
|
+
/* @__PURE__ */ jsx("p", { children: "Choose another record source or continue without a match." })
|
|
61
|
+
] });
|
|
62
|
+
return /* @__PURE__ */ jsxs("section", { className: cx("uix-match-review", className), "aria-label": "Match review", children: [
|
|
63
|
+
/* @__PURE__ */ jsxs("div", { className: "uix-match-review__summary", children: [
|
|
64
|
+
/* @__PURE__ */ jsxs("div", { children: [
|
|
65
|
+
/* @__PURE__ */ jsx("strong", { children: incomingLabel }),
|
|
66
|
+
/* @__PURE__ */ jsxs("span", { children: [
|
|
67
|
+
pending.length,
|
|
68
|
+
" pending of ",
|
|
69
|
+
candidates.length
|
|
70
|
+
] })
|
|
71
|
+
] }),
|
|
72
|
+
/* @__PURE__ */ jsx("div", { className: "uix-match-review__incoming", children: fields.map((field) => /* @__PURE__ */ jsxs("span", { children: [
|
|
73
|
+
/* @__PURE__ */ jsx("b", { children: field.label }),
|
|
74
|
+
field.render?.(incoming[field.id], incoming) ?? valueText(incoming[field.id])
|
|
75
|
+
] }, field.id)) })
|
|
76
|
+
] }),
|
|
77
|
+
onBulkDecision && /* @__PURE__ */ jsxs("div", { className: "uix-match-review__bulk", "aria-label": "Bulk candidate actions", children: [
|
|
78
|
+
/* @__PURE__ */ jsxs("span", { children: [
|
|
79
|
+
selection.size,
|
|
80
|
+
" selected"
|
|
81
|
+
] }),
|
|
82
|
+
/* @__PURE__ */ jsx("button", { type: "button", className: "uix-btn uix-btn--secondary uix-btn--sm", disabled: selection.size === 0 || bulkPending, onClick: () => void runBulk("accept"), children: bulkPending ? "Applying\u2026" : "Accept selected" }),
|
|
83
|
+
/* @__PURE__ */ jsx("button", { type: "button", className: "uix-btn uix-btn--ghost uix-btn--sm", disabled: selection.size === 0 || bulkPending, onClick: () => void runBulk("dismiss"), children: bulkPending ? "Applying\u2026" : "Dismiss selected" })
|
|
84
|
+
] }),
|
|
85
|
+
/* @__PURE__ */ jsx("div", { className: "uix-match-review__table-wrap", children: /* @__PURE__ */ jsxs("table", { className: "uix-table uix-match-review__table", children: [
|
|
86
|
+
/* @__PURE__ */ jsx("caption", { className: "uix-visually-hidden", children: "Candidate matches and available decisions" }),
|
|
87
|
+
/* @__PURE__ */ jsx("thead", { children: /* @__PURE__ */ jsxs("tr", { children: [
|
|
88
|
+
/* @__PURE__ */ jsx("th", { scope: "col", children: /* @__PURE__ */ jsx("span", { className: "uix-visually-hidden", children: "Select" }) }),
|
|
89
|
+
/* @__PURE__ */ jsx("th", { scope: "col", children: "Confidence" }),
|
|
90
|
+
fields.map((field) => /* @__PURE__ */ jsx("th", { scope: "col", children: field.label }, field.id)),
|
|
91
|
+
/* @__PURE__ */ jsx("th", { scope: "col", children: "Status" }),
|
|
92
|
+
/* @__PURE__ */ jsx("th", { scope: "col", children: "Actions" })
|
|
93
|
+
] }) }),
|
|
94
|
+
/* @__PURE__ */ jsx("tbody", { children: candidates.map((candidate) => {
|
|
95
|
+
const resolved = candidate.status === "resolved";
|
|
96
|
+
return /* @__PURE__ */ jsxs("tr", { "data-status": candidate.status ?? "pending", children: [
|
|
97
|
+
/* @__PURE__ */ jsx("td", { children: /* @__PURE__ */ jsx("input", { type: "checkbox", checked: selection.has(candidate.id), onChange: () => toggle(candidate.id), "aria-label": `Select candidate ${candidate.id}` }) }),
|
|
98
|
+
/* @__PURE__ */ jsx("td", { children: /* @__PURE__ */ jsx("span", { className: "uix-match-review__confidence", children: candidate.confidence == null ? "Not scored" : `${Math.round(candidate.confidence * 100)}%` }) }),
|
|
99
|
+
fields.map((field) => /* @__PURE__ */ jsx("td", { children: field.render?.(candidate.record[field.id], candidate.record) ?? valueText(candidate.record[field.id]) }, field.id)),
|
|
100
|
+
/* @__PURE__ */ jsx("td", { children: /* @__PURE__ */ jsx(StatusPill, { tone: resolved ? "success" : "warning", children: resolved ? candidate.decision ?? "Resolved" : "Pending" }) }),
|
|
101
|
+
/* @__PURE__ */ jsx("td", { children: /* @__PURE__ */ jsx("div", { className: "uix-match-review__actions", children: ["accept", "dismiss", "split", "pick-other"].map((action) => /* @__PURE__ */ jsx(
|
|
102
|
+
"button",
|
|
103
|
+
{
|
|
104
|
+
type: "button",
|
|
105
|
+
className: "uix-btn uix-btn--ghost uix-btn--sm",
|
|
106
|
+
disabled: resolved,
|
|
107
|
+
onClick: () => onDecision(candidate.id, action),
|
|
108
|
+
children: action === "pick-other" ? "Pick other" : `${action[0].toUpperCase()}${action.slice(1)}`
|
|
109
|
+
},
|
|
110
|
+
action
|
|
111
|
+
)) }) })
|
|
112
|
+
] }, candidate.id);
|
|
113
|
+
}) })
|
|
114
|
+
] }) }),
|
|
115
|
+
/* @__PURE__ */ jsx("p", { className: "uix-visually-hidden", "aria-live": "polite", children: announcement })
|
|
116
|
+
] });
|
|
117
|
+
}
|
|
118
|
+
export {
|
|
119
|
+
MatchReview
|
|
120
|
+
};
|