@sincpro/web-ui 1.6.0 → 1.7.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/dist/components/attachments/Attachments.d.ts +56 -0
- package/dist/components/attachments/Attachments.js +93 -0
- package/dist/components/attachments/files.d.ts +28 -0
- package/dist/components/attachments/files.js +100 -0
- package/dist/components/attachments/index.d.ts +2 -0
- package/dist/components/attachments/index.js +3 -0
- package/dist/components/flow/StepFlow.d.ts +37 -0
- package/dist/components/flow/StepFlow.js +74 -0
- package/dist/components/flow/index.d.ts +1 -0
- package/dist/components/flow/index.js +2 -0
- package/dist/components/index.d.ts +2 -0
- package/dist/components/index.js +2 -0
- package/dist/components/lines/LineItems.js +55 -45
- package/dist/components/listing/SortMenu.d.ts +24 -20
- package/dist/components/listing/SortMenu.js +51 -91
- package/dist/components/listing/index.d.ts +2 -1
- package/dist/components/listing/index.js +2 -1
- package/dist/components/listing/modelCells.d.ts +7 -0
- package/dist/components/listing/modelCells.js +56 -0
- package/dist/components/listing/modelColumns.d.ts +9 -0
- package/dist/components/listing/modelColumns.js +59 -14
- package/dist/components/search/index.d.ts +1 -1
- package/dist/components/search/index.js +1 -1
- package/dist/components/search/model.d.ts +1 -0
- package/dist/components/search/model.js +7 -0
- package/dist/components/search/query.js +2 -3
- package/dist/foundations/copy.d.ts +131 -0
- package/dist/foundations/copy.es.js +86 -0
- package/dist/foundations/copy.js +86 -0
- package/dist/foundations/form.d.ts +25 -0
- package/dist/foundations/form.js +27 -0
- package/dist/foundations/index.d.ts +1 -1
- package/dist/foundations/index.js +1 -1
- package/dist/primitives/button/Button.js +3 -1
- package/dist/primitives/choice/ComboBox.js +2 -2
- package/dist/primitives/choice/SearchField.js +2 -5
- package/dist/primitives/choice/SegmentedControl.js +2 -2
- package/dist/primitives/choice/Slider.js +2 -2
- package/dist/primitives/date/DatePicker.js +4 -4
- package/dist/primitives/date/DateTime.js +3 -3
- package/dist/primitives/field/CardNumberInput.d.ts +41 -0
- package/dist/primitives/field/CardNumberInput.js +117 -0
- package/dist/primitives/field/CodeInput.d.ts +18 -0
- package/dist/primitives/field/CodeInput.js +37 -0
- package/dist/primitives/field/ColorInput.d.ts +21 -0
- package/dist/primitives/field/ColorInput.js +42 -0
- package/dist/primitives/field/ContactInputs.d.ts +18 -0
- package/dist/primitives/field/ContactInputs.js +70 -0
- package/dist/primitives/field/DurationInput.d.ts +20 -0
- package/dist/primitives/field/DurationInput.js +54 -0
- package/dist/primitives/field/Field.d.ts +26 -5
- package/dist/primitives/field/Field.js +19 -6
- package/dist/primitives/field/ImageInput.d.ts +51 -0
- package/dist/primitives/field/ImageInput.js +267 -0
- package/dist/primitives/field/PasswordInput.d.ts +29 -0
- package/dist/primitives/field/PasswordInput.js +44 -0
- package/dist/primitives/field/PatternInput.d.ts +23 -0
- package/dist/primitives/field/PatternInput.js +48 -0
- package/dist/primitives/field/RichTextEditor.d.ts +18 -0
- package/dist/primitives/field/RichTextEditor.js +224 -0
- package/dist/primitives/field/RichTextInput.d.ts +30 -0
- package/dist/primitives/field/RichTextInput.js +38 -0
- package/dist/primitives/field/SignatureInput.d.ts +34 -0
- package/dist/primitives/field/SignatureInput.js +215 -0
- package/dist/primitives/field/Suggest.js +2 -2
- package/dist/primitives/field/card.d.ts +33 -0
- package/dist/primitives/field/card.js +173 -0
- package/dist/primitives/field/clipboard.js +3 -2
- package/dist/primitives/field/contact.d.ts +17 -0
- package/dist/primitives/field/contact.js +65 -0
- package/dist/primitives/field/duration.d.ts +5 -0
- package/dist/primitives/field/duration.js +48 -0
- package/dist/primitives/field/index.d.ts +14 -0
- package/dist/primitives/field/index.js +20 -0
- package/dist/primitives/field/mask.d.ts +33 -0
- package/dist/primitives/field/mask.js +56 -0
- package/dist/primitives/field/media.d.ts +99 -0
- package/dist/primitives/field/media.js +136 -0
- package/dist/primitives/field/pattern.d.ts +10 -0
- package/dist/primitives/field/pattern.js +68 -0
- package/dist/primitives/menu/Menu.d.ts +16 -0
- package/dist/primitives/menu/Menu.js +26 -8
- package/dist/primitives/menu/index.d.ts +1 -1
- package/dist/primitives/menu/index.js +1 -1
- package/dist/primitives/picker/Picker.js +2 -2
- package/dist/primitives/picker/TagsField.js +2 -2
- package/dist/primitives/table/DataTable.d.ts +6 -4
- package/dist/primitives/table/DataTable.js +12 -8
- package/dist/primitives/table/EditableTable.js +102 -99
- package/dist/primitives/table/Table.d.ts +5 -8
- package/dist/primitives/table/Table.js +63 -15
- package/dist/primitives/table/index.d.ts +2 -1
- package/dist/primitives/table/index.js +2 -1
- package/dist/primitives/table/sorting.d.ts +42 -0
- package/dist/primitives/table/sorting.js +110 -0
- package/dist/primitives/value/RichText.d.ts +10 -0
- package/dist/primitives/value/RichText.js +130 -0
- package/dist/primitives/value/Value.d.ts +20 -2
- package/dist/primitives/value/Value.js +26 -7
- package/dist/primitives/value/index.d.ts +3 -1
- package/dist/primitives/value/index.js +4 -1
- package/dist/primitives/value/rounding.d.ts +9 -0
- package/dist/primitives/value/rounding.js +48 -0
- package/dist/tokens.css +103 -0
- package/dist/views/list/List.d.ts +5 -4
- package/dist/views/list/ListView.js +2 -2
- package/dist/views/list/types.d.ts +6 -6
- package/package.json +9 -1
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
3
3
|
import { useUiCopy } from "../../foundations/copy.js";
|
|
4
4
|
import { byDensity, useDensity } from "../../foundations/density.js";
|
|
5
|
-
import { ControlStateContext } from "../../foundations/form.js";
|
|
5
|
+
import { ControlStateContext, FieldBoundary } from "../../foundations/form.js";
|
|
6
6
|
import { pathKey, pileIsOpen, pileItems, pilesOf, } from "../../foundations/grouping.js";
|
|
7
7
|
import { HANDHELD_WIDTH } from "../../foundations/layout.js";
|
|
8
8
|
import { stickyTop, useScrollBox } from "../../foundations/sticky.js";
|
|
@@ -816,106 +816,109 @@ groupTotal, height, layout = "auto", onOpen, onDiscard, bands, adds, flag, onReo
|
|
|
816
816
|
return null;
|
|
817
817
|
return (_jsxs("div", { className: "flex items-baseline justify-between gap-3 text-meta", children: [_jsx("dt", { className: "text-muted", children: c.header }), _jsx("dd", { className: "font-semibold text-ink tabular-nums", children: total })] }, c.id));
|
|
818
818
|
}) }))] }));
|
|
819
|
-
return (
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
(reorderable ? 1 : 0), className: `text-meta text-muted italic ${cell}`, children: emptyText }) })) : (view.rows.flatMap((slot, at) => {
|
|
839
|
-
const entry = drawn[slot.index];
|
|
840
|
-
if (entry === undefined)
|
|
841
|
-
return [];
|
|
842
|
-
const windowed = view.windowed(slot.index);
|
|
843
|
-
/* What is not drawn is a spacer of its height: the rows above the window
|
|
844
|
-
before the first, and the gap before a row kept only because it holds
|
|
845
|
-
the focus. */
|
|
846
|
-
const gap = view.pad(at === 0 ? 0 : view.rows[at - 1].end, slot.start, width, `gap:${slot.index}`);
|
|
847
|
-
if (entry.pile !== undefined) {
|
|
848
|
-
const pile = entry.pile;
|
|
849
|
-
const inside = pile.piles.length === 0 ? pile.items : pileItems(pile.piles);
|
|
850
|
-
return [
|
|
851
|
-
gap,
|
|
852
|
-
/* La banda respeta la cuadrícula: el rótulo en la primera
|
|
853
|
-
columna y cada total bajo la suya. Un total en un rincón
|
|
854
|
-
obliga a buscar de qué columna es; alineado no hay nada que
|
|
855
|
-
buscar. */
|
|
856
|
-
_jsxs("tr", { ...windowed, children: [reorderable ? _jsx("td", { className: "w-0 bg-ground" }) : null, columns.map((c, ci) => (_jsx("td", { className: `bg-ground text-meta ${cell} ${c.numeric === true ? "text-right tabular-nums" : ""}`, style: ci === 0 && pile.depth > 0
|
|
857
|
-
? {
|
|
858
|
-
paddingLeft: `calc(var(--spacing-indent) * ${pile.depth} + 0.625rem)`,
|
|
859
|
-
}
|
|
860
|
-
: undefined, children: ci === 0
|
|
861
|
-
? pileTag(pile, copy.locale)
|
|
862
|
-
: (groupTotal?.(c, inside) ?? null) }, c.id))), onRemove === undefined ? null : _jsx("td", { className: "w-0 bg-ground" })] }, `pile:${pathKey(pile.path)}`),
|
|
863
|
-
];
|
|
819
|
+
return (
|
|
820
|
+
/* A collection is many controls, each named by its column: no enclosing `Field`
|
|
821
|
+
reaches inside (see `FieldBoundary`). */
|
|
822
|
+
_jsx(FieldBoundary, { children: _jsxs("div", { ref: frame, className: "min-w-0", children: [_jsx("div", { ref: box.ref, tabIndex: box.tabIndex, className: `min-w-0 focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-primary ${box.className}`, style: box.style, children: stacked ? (list) : (_jsxs("table", { className: "w-full border-separate border-spacing-0 px-1 text-left", ...(view.on
|
|
823
|
+
? { "aria-rowcount": 1 + drawn.length + (totals === undefined ? 0 : 1) }
|
|
824
|
+
: {}), children: [_jsx("thead", { className: "sticky z-10 bg-ground", style: stickyTop, children: _jsxs("tr", { ...(view.on ? { "aria-rowindex": 1 } : {}), children: [reorderable ? _jsx("td", { className: "w-0 border-b border-line-strong" }) : null, columns.map((c) => c.header === "" ? (_jsx("td", { className: "w-0 border-b border-line-strong" }, c.id)) : (_jsx("th", { scope: "col", style: c.width === undefined ? undefined : { width: c.width },
|
|
825
|
+
/* A column of figures takes its figures' width (`w-0` in an auto
|
|
826
|
+
table is «as narrow as its content») and the text takes the rest:
|
|
827
|
+
the product name was cut while «Cantidad» had room for ten digits. */
|
|
828
|
+
className: `border-b border-line-strong ${HEADER_TEXT} ${cell} ${c.numeric === true ? "text-right" : "text-left"} ${c.numeric === true && c.width === undefined ? "w-0" : ""}`, children: c.header }, c.id))), onRemove === undefined ? null : (_jsx("td", { className: "w-0 border-b border-line-strong" }))] }) }), _jsxs("tbody", { ref: view.body.ref, ...(reorderable
|
|
829
|
+
? {
|
|
830
|
+
onDragOver: overRow,
|
|
831
|
+
onDrop: (event) => {
|
|
832
|
+
event.preventDefault();
|
|
833
|
+
if (dragged.current !== null && landing !== undefined)
|
|
834
|
+
land(dragged.current, landing);
|
|
835
|
+
dragged.current = null;
|
|
836
|
+
setLanding(undefined);
|
|
837
|
+
},
|
|
864
838
|
}
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
const
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
839
|
+
: {}), children: [rows.length === 0 ? (_jsx("tr", { children: _jsx("td", { colSpan: columns.length +
|
|
840
|
+
(onRemove === undefined ? 0 : 1) +
|
|
841
|
+
(reorderable ? 1 : 0), className: `text-meta text-muted italic ${cell}`, children: emptyText }) })) : (view.rows.flatMap((slot, at) => {
|
|
842
|
+
const entry = drawn[slot.index];
|
|
843
|
+
if (entry === undefined)
|
|
844
|
+
return [];
|
|
845
|
+
const windowed = view.windowed(slot.index);
|
|
846
|
+
/* What is not drawn is a spacer of its height: the rows above the window
|
|
847
|
+
before the first, and the gap before a row kept only because it holds
|
|
848
|
+
the focus. */
|
|
849
|
+
const gap = view.pad(at === 0 ? 0 : view.rows[at - 1].end, slot.start, width, `gap:${slot.index}`);
|
|
850
|
+
if (entry.pile !== undefined) {
|
|
851
|
+
const pile = entry.pile;
|
|
852
|
+
const inside = pile.piles.length === 0 ? pile.items : pileItems(pile.piles);
|
|
853
|
+
return [
|
|
854
|
+
gap,
|
|
855
|
+
/* La banda respeta la cuadrícula: el rótulo en la primera
|
|
856
|
+
columna y cada total bajo la suya. Un total en un rincón
|
|
857
|
+
obliga a buscar de qué columna es; alineado no hay nada que
|
|
858
|
+
buscar. */
|
|
859
|
+
_jsxs("tr", { ...windowed, children: [reorderable ? _jsx("td", { className: "w-0 bg-ground" }) : null, columns.map((c, ci) => (_jsx("td", { className: `bg-ground text-meta ${cell} ${c.numeric === true ? "text-right tabular-nums" : ""}`, style: ci === 0 && pile.depth > 0
|
|
860
|
+
? {
|
|
861
|
+
paddingLeft: `calc(var(--spacing-indent) * ${pile.depth} + 0.625rem)`,
|
|
862
|
+
}
|
|
863
|
+
: undefined, children: ci === 0
|
|
864
|
+
? pileTag(pile, copy.locale)
|
|
865
|
+
: (groupTotal?.(c, inside) ?? null) }, c.id))), onRemove === undefined ? null : _jsx("td", { className: "w-0 bg-ground" })] }, `pile:${pathKey(pile.path)}`),
|
|
866
|
+
];
|
|
867
|
+
}
|
|
868
|
+
const row = entry.row;
|
|
869
|
+
const id = getRowId(row);
|
|
870
|
+
const isEditing = editing === id;
|
|
871
|
+
const band = bandOf(row);
|
|
872
|
+
if (band !== null) {
|
|
873
|
+
const { span, figures } = bandFigures(row);
|
|
874
|
+
const bandWash = isEditing
|
|
875
|
+
? "bg-primary/5"
|
|
876
|
+
: band === "section"
|
|
877
|
+
? "bg-ground group-hover/row:bg-ink/6"
|
|
878
|
+
: "group-hover/row:bg-ink/4";
|
|
879
|
+
const bandMark = `border-b border-line first:rounded-l-control last:rounded-r-control ${bandWash}`;
|
|
880
|
+
const control = isEditing && editable && bands?.edit !== undefined;
|
|
881
|
+
return [
|
|
882
|
+
gap,
|
|
883
|
+
_jsxs(EditRow, { as: "tr", ...rowWiring(id, row), windowed: windowed, className: `group/row ${editable ? RING : ""} ${editable && !isEditing ? "cursor-text" : ""}`, children: [reorderable ? grip(id, bandMark) : null, _jsx("td", { colSpan: span, ...(control ? { "data-edit": "" } : {}), className: `text-meta ${bandMark} ${lineOver(id)} ${cell} ${band === "section"
|
|
884
|
+
? "font-semibold text-ink"
|
|
885
|
+
: "text-muted italic"}`, children: control ? bands?.edit?.(row, setter(id)) : bands?.render(row) }), columns.slice(span).map((c, i) => (_jsx("td", { className: `text-meta text-ink ${bandMark} ${cell} ${c.numeric === true
|
|
886
|
+
? "text-right font-semibold tabular-nums"
|
|
887
|
+
: ""}`, children: figures[span + i] ?? null }, c.id))), onRemove === undefined ? null : (_jsx("td", { className: `w-0 py-1 pr-2 pl-1 align-middle ${bandMark}`, children: _jsx("span", { className: "flex opacity-0 transition-opacity duration-(--duration-instant) group-hover/row:opacity-100 group-focus-visible/row:opacity-100 focus-within:opacity-100", onClick: (e) => e.stopPropagation(), children: _jsx(IconButton, { icon: Trash2, label: copy.editable.remove, size: "sm", tone: "danger", ...(editable && stop !== id ? { tabIndex: -1 } : {}), onClick: () => onRemove(id) }) }) }))] }, id),
|
|
888
|
+
];
|
|
889
|
+
}
|
|
890
|
+
/* The wash lives on the cells. A background on the row paints
|
|
891
|
+
edge to edge, and `border-collapse` would ignore a radius. */
|
|
892
|
+
const wash = isEditing ? "bg-primary/5" : "group-hover/row:bg-ink/4";
|
|
893
|
+
const mark = `border-b border-line first:rounded-l-control last:rounded-r-control ${wash}`;
|
|
894
|
+
const set = setter(id);
|
|
878
895
|
return [
|
|
879
896
|
gap,
|
|
880
|
-
_jsxs(EditRow, { as: "tr", ...rowWiring(id, row), windowed: windowed,
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
897
|
+
_jsxs(EditRow, { as: "tr", ...rowWiring(id, row), windowed: windowed,
|
|
898
|
+
/* The focus ring is drawn on the row, inset, so it is not cut by
|
|
899
|
+
the box and does not move the cells. */
|
|
900
|
+
className: `group/row ${editable ? RING : ""} ${editable && !isEditing && !locked(row) ? "cursor-text" : ""}`, children: [reorderable ? grip(id, mark) : null, columns.map((c, ci) => {
|
|
901
|
+
const state = stateOf(row, c);
|
|
902
|
+
const control = isEditing && writes(row, c, state);
|
|
903
|
+
/* Not `mark`: that name is the row's border and wash, above. Shadowed,
|
|
904
|
+
the cells were drawn with the class «null» — no hairline, no hover. */
|
|
905
|
+
const flagged = ci === 0 ? flagOf(row, true) : null;
|
|
906
|
+
const cellFlag = cellFlagOf(row, c, state, true);
|
|
907
|
+
const value = state?.hidden === true
|
|
908
|
+
? null
|
|
909
|
+
: control
|
|
910
|
+
? controlOf(row, c, state, set)
|
|
911
|
+
: c.render(row);
|
|
912
|
+
return (_jsxs("td", { ...(control ? { "data-edit": "" } : {}), className: `text-meta text-ink ${mark} ${lineOver(id)} ${cell} ${c.numeric === true ? "text-right tabular-nums" : ""}`, children: [flagged === null && cellFlag === null ? (value) : (_jsxs("span", { className: "flex min-w-0 items-start gap-1.5", children: [flagged, cellFlag, _jsx("span", { className: "min-w-0 flex-1", children: value })] })), errorOf(row, c, state)] }, c.id));
|
|
913
|
+
}), onRemove === undefined ? null : (_jsx("td", { className: `w-0 py-1 pr-2 pl-1 align-middle ${mark}`, children: _jsx("span", { className: "flex opacity-0 transition-opacity duration-(--duration-instant) group-hover/row:opacity-100 group-focus-visible/row:opacity-100 focus-within:opacity-100", onClick: (e) => e.stopPropagation(), children: _jsx(IconButton, { icon: Trash2, label: copy.editable.remove, size: "sm", tone: "danger", ...(editable && stop !== id ? { tabIndex: -1 } : {}), onClick: () => onRemove(id) }) }) }))] }, id),
|
|
885
914
|
];
|
|
886
|
-
}
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
/* The focus ring is drawn on the row, inset, so it is not cut by
|
|
896
|
-
the box and does not move the cells. */
|
|
897
|
-
className: `group/row ${editable ? RING : ""} ${editable && !isEditing && !locked(row) ? "cursor-text" : ""}`, children: [reorderable ? grip(id, mark) : null, columns.map((c, ci) => {
|
|
898
|
-
const state = stateOf(row, c);
|
|
899
|
-
const control = isEditing && writes(row, c, state);
|
|
900
|
-
/* Not `mark`: that name is the row's border and wash, above. Shadowed,
|
|
901
|
-
the cells were drawn with the class «null» — no hairline, no hover. */
|
|
902
|
-
const flagged = ci === 0 ? flagOf(row, true) : null;
|
|
903
|
-
const cellFlag = cellFlagOf(row, c, state, true);
|
|
904
|
-
const value = state?.hidden === true
|
|
905
|
-
? null
|
|
906
|
-
: control
|
|
907
|
-
? controlOf(row, c, state, set)
|
|
908
|
-
: c.render(row);
|
|
909
|
-
return (_jsxs("td", { ...(control ? { "data-edit": "" } : {}), className: `text-meta text-ink ${mark} ${lineOver(id)} ${cell} ${c.numeric === true ? "text-right tabular-nums" : ""}`, children: [flagged === null && cellFlag === null ? (value) : (_jsxs("span", { className: "flex min-w-0 items-start gap-1.5", children: [flagged, cellFlag, _jsx("span", { className: "min-w-0 flex-1", children: value })] })), errorOf(row, c, state)] }, c.id));
|
|
910
|
-
}), onRemove === undefined ? null : (_jsx("td", { className: `w-0 py-1 pr-2 pl-1 align-middle ${mark}`, children: _jsx("span", { className: "flex opacity-0 transition-opacity duration-(--duration-instant) group-hover/row:opacity-100 group-focus-visible/row:opacity-100 focus-within:opacity-100", onClick: (e) => e.stopPropagation(), children: _jsx(IconButton, { icon: Trash2, label: copy.editable.remove, size: "sm", tone: "danger", ...(editable && stop !== id ? { tabIndex: -1 } : {}), onClick: () => onRemove(id) }) }) }))] }, id),
|
|
911
|
-
];
|
|
912
|
-
})), rows.length === 0 ? null : view.tail(width)] }), totals === undefined ? null : (_jsx("tfoot", { children: _jsxs("tr", { children: [reorderable ? _jsx("td", { className: "border-t border-line-strong" }) : null, columns.map((c, i) => (_jsx("td", { className: `border-t border-line-strong text-meta font-semibold text-ink ${cell} ${c.numeric === true ? "text-right tabular-nums" : ""}`, children: totals[i] ?? null }, c.id))), onRemove === undefined ? null : (_jsx("td", { className: "border-t border-line-strong" }))] }) }))] })) }), onAdd === undefined && (adds === undefined || adds.length === 0) ? null : (
|
|
913
|
-
/* The «add» goes under the grid, as text, not as a button away from it:
|
|
914
|
-
what is about to happen is that one more row shows up right here.
|
|
915
|
-
The hover is the same inset mark as a row: the shell lines the label
|
|
916
|
-
up with the cells, and the button itself is the rounded wash. It sits
|
|
917
|
-
outside the scroll box, so a table with its own height keeps it in view.
|
|
918
|
-
A section and a note sit beside it, quieter: the line is what is added
|
|
919
|
-
most, so it keeps the lead and the whole free width. */
|
|
920
|
-
_jsxs("div", { className: "flex flex-wrap items-center gap-x-1 px-1 py-0.5", children: [onAdd === undefined ? null : (_jsxs("button", { ref: addButton, type: "button", onClick: () => add(), className: `flex items-center gap-1.5 rounded-control text-left text-meta text-primary transition-colors duration-(--duration-instant) hover:bg-primary/5 focus-visible:outline-2 focus-visible:-outline-offset-1 focus-visible:outline-primary ${adds === undefined || adds.length === 0 ? "w-full" : ""} ${byDensity(d, "px-3 py-1", "px-2 py-0.5")}`, children: [_jsx(Plus, { className: "size-(--size-icon-sm)", strokeWidth: 2.5, "aria-hidden": true }), addText] })), (adds ?? []).map((extra) => (_jsx("button", { type: "button", onClick: () => add(extra.onAdd), className: `rounded-control text-left text-meta text-muted transition-colors duration-(--duration-instant) hover:bg-primary/5 hover:text-primary focus-visible:outline-2 focus-visible:-outline-offset-1 focus-visible:outline-primary ${byDensity(d, "px-3 py-1", "px-2 py-0.5")}`, children: extra.label }, extra.label)))] }))] }));
|
|
915
|
+
})), rows.length === 0 ? null : view.tail(width)] }), totals === undefined ? null : (_jsx("tfoot", { children: _jsxs("tr", { children: [reorderable ? _jsx("td", { className: "border-t border-line-strong" }) : null, columns.map((c, i) => (_jsx("td", { className: `border-t border-line-strong text-meta font-semibold text-ink ${cell} ${c.numeric === true ? "text-right tabular-nums" : ""}`, children: totals[i] ?? null }, c.id))), onRemove === undefined ? null : (_jsx("td", { className: "border-t border-line-strong" }))] }) }))] })) }), onAdd === undefined && (adds === undefined || adds.length === 0) ? null : (
|
|
916
|
+
/* The «add» goes under the grid, as text, not as a button away from it:
|
|
917
|
+
what is about to happen is that one more row shows up right here.
|
|
918
|
+
The hover is the same inset mark as a row: the shell lines the label
|
|
919
|
+
up with the cells, and the button itself is the rounded wash. It sits
|
|
920
|
+
outside the scroll box, so a table with its own height keeps it in view.
|
|
921
|
+
A section and a note sit beside it, quieter: the line is what is added
|
|
922
|
+
most, so it keeps the lead and the whole free width. */
|
|
923
|
+
_jsxs("div", { className: "flex flex-wrap items-center gap-x-1 px-1 py-0.5", children: [onAdd === undefined ? null : (_jsxs("button", { ref: addButton, type: "button", onClick: () => add(), className: `flex items-center gap-1.5 rounded-control text-left text-meta text-primary transition-colors duration-(--duration-instant) hover:bg-primary/5 focus-visible:outline-2 focus-visible:-outline-offset-1 focus-visible:outline-primary ${adds === undefined || adds.length === 0 ? "w-full" : ""} ${byDensity(d, "px-3 py-1", "px-2 py-0.5")}`, children: [_jsx(Plus, { className: "size-(--size-icon-sm)", strokeWidth: 2.5, "aria-hidden": true }), addText] })), (adds ?? []).map((extra) => (_jsx("button", { type: "button", onClick: () => add(extra.onAdd), className: `rounded-control text-left text-meta text-muted transition-colors duration-(--duration-instant) hover:bg-primary/5 hover:text-primary focus-visible:outline-2 focus-visible:-outline-offset-1 focus-visible:outline-primary ${byDensity(d, "px-3 py-1", "px-2 py-0.5")}`, children: extra.label }, extra.label)))] }))] }) }));
|
|
921
924
|
}
|
|
@@ -1,11 +1,7 @@
|
|
|
1
1
|
import type { Tone } from "../../foundations/types.js";
|
|
2
2
|
import type { WindowedRow } from "./rowWindow.js";
|
|
3
|
+
import { type SortState } from "./sorting.js";
|
|
3
4
|
import { type ReactNode } from "react";
|
|
4
|
-
export type SortDirection = "asc" | "desc";
|
|
5
|
-
export interface SortState {
|
|
6
|
-
key: string;
|
|
7
|
-
direction: SortDirection;
|
|
8
|
-
}
|
|
9
5
|
export declare function Table({ children,
|
|
10
6
|
/** With a height, the table scrolls on its own and the header sticks.
|
|
11
7
|
* Without a height, the page scrolls and the header goes away. */
|
|
@@ -29,14 +25,15 @@ export declare function THead({ children, rowIndex, }: {
|
|
|
29
25
|
}): import("react").JSX.Element;
|
|
30
26
|
/** A column. If it gets a `sortKey`, it can be sorted.
|
|
31
27
|
*
|
|
32
|
-
* The icon is ALWAYS there, dimmed when the column is not
|
|
28
|
+
* The icon is ALWAYS there, dimmed when the column is not sorted: an
|
|
33
29
|
* indicator that only shows up once you sort does not say which columns are
|
|
34
30
|
* sortable, and then you have to try them one by one. */
|
|
35
31
|
export declare function Th({ children, sortKey, sort, onSort, numeric, align, width, }: {
|
|
36
32
|
children: ReactNode;
|
|
37
33
|
sortKey?: string;
|
|
38
|
-
|
|
39
|
-
|
|
34
|
+
/** The order of the set, outermost first. Empty is «as they came». */
|
|
35
|
+
sort?: readonly SortState[];
|
|
36
|
+
onSort?: (next: SortState[]) => void;
|
|
40
37
|
/** Numbers align to the right: that is what lets you compare orders of
|
|
41
38
|
* magnitude without reading the figures. */
|
|
42
39
|
numeric?: boolean;
|
|
@@ -1,8 +1,46 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
3
|
+
import { fillCopy, useUiCopy } from "../../foundations/copy.js";
|
|
3
4
|
import { stickyTop, useScrollBox } from "../../foundations/sticky.js";
|
|
5
|
+
import { pressSort, sortPosition, } from "./sorting.js";
|
|
4
6
|
import { ArrowDown, ArrowUp, ChevronRight, ChevronsUpDown } from "lucide-react";
|
|
5
7
|
import { createContext, useContext, useId } from "react";
|
|
8
|
+
/* ==================================================================== *
|
|
9
|
+
* TABLE — the way this product shows N records.
|
|
10
|
+
*
|
|
11
|
+
* Dense, but readable, and a single split is what makes that possible:
|
|
12
|
+
*
|
|
13
|
+
* the header in PROPORTIONAL is a word
|
|
14
|
+
* a number in TABULAR FIGURES is a value that lines up with the one below
|
|
15
|
+
*
|
|
16
|
+
* That is what makes a table readable at a glance: the eye tells the frame
|
|
17
|
+
* apart from the content without reading either of them. Everything in mono
|
|
18
|
+
* —which is where this product came from— turned the header into just another
|
|
19
|
+
* row of data, and the whole report into a terminal. Mono is now for codes
|
|
20
|
+
* only, and only when a cell asks for it.
|
|
21
|
+
*
|
|
22
|
+
* ── Three sort states, not two ──────────────────────────────────────
|
|
23
|
+
*
|
|
24
|
+
* Ascending, descending and **unsorted**. The third one is not a luxury: the
|
|
25
|
+
* natural order of a delivery queue is the order they arrived in, and a table that
|
|
26
|
+
* only toggles asc/desc has no way back to it. Losing the original order and
|
|
27
|
+
* not being able to get it back is the number one complaint about any
|
|
28
|
+
* sortable table.
|
|
29
|
+
*
|
|
30
|
+
* ── And more than one column ────────────────────────────────────────
|
|
31
|
+
*
|
|
32
|
+
* Shift+click adds the column as a TIE-BREAKER instead of replacing the order:
|
|
33
|
+
* «by date, then by customer». Each sorted header then carries its place (1,
|
|
34
|
+
* 2, 3) next to the arrow, because two arrows alone do not say which one
|
|
35
|
+
* decides. The model is in `sorting.ts`.
|
|
36
|
+
*
|
|
37
|
+
* ── The header stays pinned ─────────────────────────────────────────
|
|
38
|
+
*
|
|
39
|
+
* It is the difference between a long table you can read and one where, by
|
|
40
|
+
* row forty, nobody knows which column they are looking at any more. It needs
|
|
41
|
+
* the box to have a height: without `height` there is no scroll of its own
|
|
42
|
+
* and nothing to stick to.
|
|
43
|
+
* ==================================================================== */
|
|
6
44
|
export function Table({ children,
|
|
7
45
|
/** With a height, the table scrolls on its own and the header sticks.
|
|
8
46
|
* Without a height, the page scrolls and the header goes away. */
|
|
@@ -28,25 +66,35 @@ export function THead({ children, rowIndex, }) {
|
|
|
28
66
|
}
|
|
29
67
|
/** A column. If it gets a `sortKey`, it can be sorted.
|
|
30
68
|
*
|
|
31
|
-
* The icon is ALWAYS there, dimmed when the column is not
|
|
69
|
+
* The icon is ALWAYS there, dimmed when the column is not sorted: an
|
|
32
70
|
* indicator that only shows up once you sort does not say which columns are
|
|
33
71
|
* sortable, and then you have to try them one by one. */
|
|
34
72
|
export function Th({ children, sortKey, sort, onSort, numeric = false, align, width, }) {
|
|
35
|
-
const
|
|
36
|
-
const
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
onSort({ key: sortKey, direction: "asc" });
|
|
43
|
-
else if (dir === "asc")
|
|
44
|
-
onSort({ key: sortKey, direction: "desc" });
|
|
45
|
-
else
|
|
46
|
-
onSort(null);
|
|
47
|
-
}
|
|
73
|
+
const copy = useUiCopy();
|
|
74
|
+
const at = sortKey === undefined ? null : sortPosition(sort, sortKey);
|
|
75
|
+
const dir = at?.direction;
|
|
76
|
+
/* The place is drawn only when there is more than one criterion: «1» alone says nothing
|
|
77
|
+
the arrow did not. */
|
|
78
|
+
const ranked = at !== null && (sort?.length ?? 0) > 1;
|
|
79
|
+
const right = (align ?? (numeric ? "right" : "left")) === "right";
|
|
48
80
|
const Icon = dir === "asc" ? ArrowUp : dir === "desc" ? ArrowDown : ChevronsUpDown;
|
|
49
|
-
return (_jsx("th", { scope: "col", style: width === undefined ? undefined : { width }, "aria-sort":
|
|
81
|
+
return (_jsx("th", { scope: "col", style: width === undefined ? undefined : { width }, "aria-sort": sortKey === undefined
|
|
82
|
+
? undefined
|
|
83
|
+
: at === null
|
|
84
|
+
? "none"
|
|
85
|
+
: at.index > 0
|
|
86
|
+
? undefined
|
|
87
|
+
: dir === "asc"
|
|
88
|
+
? "ascending"
|
|
89
|
+
: "descending", className: `border-b border-line-strong px-3 py-2 ${HEADER_TEXT} ${right ? "text-right" : "text-left"}`, children: sortKey === undefined ? (children) : (_jsxs("button", { type: "button", title: copy.sorting.addHint,
|
|
90
|
+
/* Shift+click selects the header's text in some browsers before the click lands. */
|
|
91
|
+
onMouseDown: (event) => {
|
|
92
|
+
if (event.shiftKey)
|
|
93
|
+
event.preventDefault();
|
|
94
|
+
}, onClick: (event) => onSort?.(pressSort(sort ?? [], sortKey, event.shiftKey)), className: `inline-flex items-center gap-1 rounded-control transition-colors duration-(--duration-instant) select-none hover:text-ink focus-visible:outline-2 focus-visible:outline-offset-1 focus-visible:outline-primary ${right ? "flex-row-reverse" : ""} ${at === null ? "" : "text-ink"}`, children: [children, _jsxs("span", { className: "inline-flex shrink-0 items-center gap-px", children: [_jsx(Icon, { className: `size-(--size-icon-sm) shrink-0 ${at === null ? "text-line-strong" : "text-primary"}`, strokeWidth: 2, "aria-hidden": true }), ranked ? (_jsx("span", { "aria-hidden": true, className: "text-micro text-primary tabular-nums", children: at.index + 1 })) : null] }), ranked ? (_jsx("span", { className: "sr-only", children: fillCopy(copy.sorting.place, {
|
|
95
|
+
place: String(at.index + 1),
|
|
96
|
+
direction: dir === "asc" ? copy.sorting.asc : copy.sorting.desc,
|
|
97
|
+
}) })) : null] })) }));
|
|
50
98
|
}
|
|
51
99
|
/** The wash of a row lives on the cells, not on the `<tr>`. A background on the
|
|
52
100
|
* row paints edge to edge; on the cells the ends can round and the table's
|
|
@@ -5,4 +5,5 @@ export { PivotTable, type PivotTableProps, } from "./PivotTable.js";
|
|
|
5
5
|
export { type ReportColumn, type ReportRow, type ReportRowKind, ReportTable, } from "./ReportTable.js";
|
|
6
6
|
export { type RowAction, RowActions } from "./RowActions.js";
|
|
7
7
|
export { type SheetColumn, SheetTable } from "./SheetTable.js";
|
|
8
|
-
export {
|
|
8
|
+
export { compareBy, flipSort, moveSort, pressSort, type SortDirection, sortPosition, type SortState, } from "./sorting.js";
|
|
9
|
+
export { CELL_TONE, Table, Td, Th, THead, Tr } from "./Table.js";
|
|
@@ -7,4 +7,5 @@ export { PivotTable, } from "./PivotTable.js";
|
|
|
7
7
|
export { ReportTable, } from "./ReportTable.js";
|
|
8
8
|
export { RowActions } from "./RowActions.js";
|
|
9
9
|
export { SheetTable } from "./SheetTable.js";
|
|
10
|
-
export {
|
|
10
|
+
export { compareBy, flipSort, moveSort, pressSort, sortPosition, } from "./sorting.js";
|
|
11
|
+
export { CELL_TONE, Table, Td, Th, THead, Tr } from "./Table.js";
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
export type SortDirection = "asc" | "desc";
|
|
2
|
+
/** One criterion: a field and its direction. */
|
|
3
|
+
export interface SortState {
|
|
4
|
+
key: string;
|
|
5
|
+
direction: SortDirection;
|
|
6
|
+
}
|
|
7
|
+
/** Where a column stands in the order: its place (0 = the primary) and direction. */
|
|
8
|
+
export declare function sortPosition(sort: readonly SortState[] | undefined, key: string): {
|
|
9
|
+
index: number;
|
|
10
|
+
direction: SortDirection;
|
|
11
|
+
} | null;
|
|
12
|
+
/**
|
|
13
|
+
* What a press on a column header does to the order.
|
|
14
|
+
*
|
|
15
|
+
* Plain press — the column ALONE:
|
|
16
|
+
* - the only criterion: asc → desc → nothing. The third press gives the
|
|
17
|
+
* natural order back (see `Table.tsx`).
|
|
18
|
+
* - with others around it: the column alone, flipped if it was there, and
|
|
19
|
+
* never «nothing» — a press that also wipes the tie-breakers someone built
|
|
20
|
+
* in the panel would be a surprise, not a shortcut.
|
|
21
|
+
*
|
|
22
|
+
* `additive` (Shift) — one more tie-breaker, the others untouched:
|
|
23
|
+
* - absent: appended, ascending.
|
|
24
|
+
* - ascending: flipped in its place.
|
|
25
|
+
* - descending: taken out.
|
|
26
|
+
*/
|
|
27
|
+
export declare function pressSort(sort: readonly SortState[], key: string, additive: boolean): SortState[];
|
|
28
|
+
/** The criterion at `index` with its direction flipped. */
|
|
29
|
+
export declare function flipSort(sort: readonly SortState[], index: number): SortState[];
|
|
30
|
+
/** The criteria with the ones in `keys` moved to sit before `target` (or after it). The
|
|
31
|
+
* shape React Aria's drag-and-drop hands over: a set of dragged keys and a drop target. */
|
|
32
|
+
export declare function moveSort(sort: readonly SortState[], keys: ReadonlySet<string>, target: string, position: "before" | "after"): SortState[];
|
|
33
|
+
/**
|
|
34
|
+
* Compares two rows by every criterion, in order: the second one speaks only when the first
|
|
35
|
+
* one ties. `read` gives each row's value for a field; `undefined` there means «this field
|
|
36
|
+
* cannot be sorted here» and the criterion is skipped.
|
|
37
|
+
*
|
|
38
|
+
* Missing values go LAST whatever the direction. A blank is not «the smallest»: it is the
|
|
39
|
+
* absence of an answer, and putting it first when the order is flipped fills the first
|
|
40
|
+
* screen with the rows that say the least.
|
|
41
|
+
*/
|
|
42
|
+
export declare function compareBy<T>(sort: readonly SortState[], read: (key: string) => ((row: T) => string | number | null | undefined) | undefined): (a: T, b: T) => number;
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
/* ==================================================================== *
|
|
2
|
+
* SORTING — an ordered list of criteria, not one column.
|
|
3
|
+
*
|
|
4
|
+
* Context: the order of a set was ONE field and a direction. That answers
|
|
5
|
+
* «the newest first», and not the question people actually asked the list:
|
|
6
|
+
* «by date, and within the same date by customer, and then by product». The
|
|
7
|
+
* second and third fields are tie-breakers — they only decide between rows the
|
|
8
|
+
* first one left equal — and the engine's criteria already takes them
|
|
9
|
+
* (`order: [{ field, descending }, …]`). What could not say it was the screen.
|
|
10
|
+
*
|
|
11
|
+
* So the order is a LIST, outermost first. Empty is «as they came»: the
|
|
12
|
+
* third state of a header, which gives the natural order back.
|
|
13
|
+
*
|
|
14
|
+
* The two doors to it write the same list:
|
|
15
|
+
*
|
|
16
|
+
* header click this column alone — the quick question
|
|
17
|
+
* header Shift+click this column as one more tie-breaker
|
|
18
|
+
* the bar's panel the whole list: add, flip, reorder, remove
|
|
19
|
+
*
|
|
20
|
+
* Pure, and in the table family because the header (`Th`) is its first user;
|
|
21
|
+
* the list's bar reads it from here.
|
|
22
|
+
* ==================================================================== */
|
|
23
|
+
/** Where a column stands in the order: its place (0 = the primary) and direction. */
|
|
24
|
+
export function sortPosition(sort, key) {
|
|
25
|
+
const index = sort?.findIndex((one) => one.key === key) ?? -1;
|
|
26
|
+
if (index === -1 || sort === undefined)
|
|
27
|
+
return null;
|
|
28
|
+
return { index, direction: sort[index]?.direction ?? "asc" };
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* What a press on a column header does to the order.
|
|
32
|
+
*
|
|
33
|
+
* Plain press — the column ALONE:
|
|
34
|
+
* - the only criterion: asc → desc → nothing. The third press gives the
|
|
35
|
+
* natural order back (see `Table.tsx`).
|
|
36
|
+
* - with others around it: the column alone, flipped if it was there, and
|
|
37
|
+
* never «nothing» — a press that also wipes the tie-breakers someone built
|
|
38
|
+
* in the panel would be a surprise, not a shortcut.
|
|
39
|
+
*
|
|
40
|
+
* `additive` (Shift) — one more tie-breaker, the others untouched:
|
|
41
|
+
* - absent: appended, ascending.
|
|
42
|
+
* - ascending: flipped in its place.
|
|
43
|
+
* - descending: taken out.
|
|
44
|
+
*/
|
|
45
|
+
export function pressSort(sort, key, additive) {
|
|
46
|
+
const at = sortPosition(sort, key);
|
|
47
|
+
if (additive) {
|
|
48
|
+
if (at === null)
|
|
49
|
+
return [...sort, { key, direction: "asc" }];
|
|
50
|
+
if (at.direction === "asc") {
|
|
51
|
+
return sort.map((one, i) => (i === at.index ? { key, direction: "desc" } : one));
|
|
52
|
+
}
|
|
53
|
+
return sort.filter((_, i) => i !== at.index);
|
|
54
|
+
}
|
|
55
|
+
if (at === null)
|
|
56
|
+
return [{ key, direction: "asc" }];
|
|
57
|
+
if (sort.length === 1)
|
|
58
|
+
return at.direction === "asc" ? [{ key, direction: "desc" }] : [];
|
|
59
|
+
return [{ key, direction: at.direction === "asc" ? "desc" : "asc" }];
|
|
60
|
+
}
|
|
61
|
+
/** The criterion at `index` with its direction flipped. */
|
|
62
|
+
export function flipSort(sort, index) {
|
|
63
|
+
return sort.map((one, i) => i === index ? { ...one, direction: one.direction === "asc" ? "desc" : "asc" } : one);
|
|
64
|
+
}
|
|
65
|
+
/** The criteria with the ones in `keys` moved to sit before `target` (or after it). The
|
|
66
|
+
* shape React Aria's drag-and-drop hands over: a set of dragged keys and a drop target. */
|
|
67
|
+
export function moveSort(sort, keys, target, position) {
|
|
68
|
+
const moving = sort.filter((one) => keys.has(one.key));
|
|
69
|
+
const rest = sort.filter((one) => !keys.has(one.key));
|
|
70
|
+
const at = rest.findIndex((one) => one.key === target);
|
|
71
|
+
if (at === -1 || moving.length === 0)
|
|
72
|
+
return [...sort];
|
|
73
|
+
const cut = position === "before" ? at : at + 1;
|
|
74
|
+
return [...rest.slice(0, cut), ...moving, ...rest.slice(cut)];
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Compares two rows by every criterion, in order: the second one speaks only when the first
|
|
78
|
+
* one ties. `read` gives each row's value for a field; `undefined` there means «this field
|
|
79
|
+
* cannot be sorted here» and the criterion is skipped.
|
|
80
|
+
*
|
|
81
|
+
* Missing values go LAST whatever the direction. A blank is not «the smallest»: it is the
|
|
82
|
+
* absence of an answer, and putting it first when the order is flipped fills the first
|
|
83
|
+
* screen with the rows that say the least.
|
|
84
|
+
*/
|
|
85
|
+
export function compareBy(sort, read) {
|
|
86
|
+
const criteria = sort.flatMap((one) => {
|
|
87
|
+
const value = read(one.key);
|
|
88
|
+
return value === undefined ? [] : [{ value, sign: one.direction === "asc" ? 1 : -1 }];
|
|
89
|
+
});
|
|
90
|
+
return (a, b) => {
|
|
91
|
+
for (const { value, sign } of criteria) {
|
|
92
|
+
const left = value(a);
|
|
93
|
+
const right = value(b);
|
|
94
|
+
const leftMissing = left === null || left === undefined || left === "";
|
|
95
|
+
const rightMissing = right === null || right === undefined || right === "";
|
|
96
|
+
if (leftMissing && rightMissing)
|
|
97
|
+
continue;
|
|
98
|
+
if (leftMissing)
|
|
99
|
+
return 1;
|
|
100
|
+
if (rightMissing)
|
|
101
|
+
return -1;
|
|
102
|
+
const order = typeof left === "number" && typeof right === "number"
|
|
103
|
+
? left - right
|
|
104
|
+
: String(left).localeCompare(String(right));
|
|
105
|
+
if (order !== 0)
|
|
106
|
+
return order * sign;
|
|
107
|
+
}
|
|
108
|
+
return 0;
|
|
109
|
+
};
|
|
110
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/** The HTML with everything that runs taken out, or `null` where there is no DOM to do it. */
|
|
2
|
+
export declare function sanitizeRichText(html: string): string | null;
|
|
3
|
+
/** The words of an HTML, without its tags: what is shown where it cannot be cleaned. */
|
|
4
|
+
export declare function richTextPlain(html: string): string;
|
|
5
|
+
/** Nothing written: no text and no image — «<p></p>» from an emptied editor counts as nothing. */
|
|
6
|
+
export declare function isRichTextEmpty(html: string | null | undefined): boolean;
|
|
7
|
+
/** The HTML of a note, read: cleaned, in the system's text style (`rich-text` in tokens.css). */
|
|
8
|
+
export declare function RichTextValue({ value }: {
|
|
9
|
+
value: string | null | undefined;
|
|
10
|
+
}): import("react").JSX.Element;
|