@crisp-ui-kit/crisp 0.52.6 → 0.53.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/button.css +2 -2
- package/dist/button.layered.css +2 -2
- package/dist/cjs/components/button/button.recipe.js +10 -2
- package/dist/cjs/components/datatable/datatable.js +45 -7
- package/dist/cjs/components/tabletoolbar/tabletoolbar.js +40 -7
- package/dist/cjs/core/i18n.js +75 -0
- package/dist/cjs/core/index.js +5 -1
- package/dist/esm/components/button/button.recipe.js +10 -2
- package/dist/esm/components/datatable/datatable.js +46 -8
- package/dist/esm/components/tabletoolbar/tabletoolbar.js +41 -8
- package/dist/esm/core/i18n.js +37 -0
- package/dist/esm/core/index.js +1 -0
- package/dist/styles.css +2 -2
- package/dist/styles.layered.css +2 -2
- package/dist/types/components/button/button.recipe.d.ts +2 -2
- package/dist/types/components/datatable/datatable.d.ts +21 -1
- package/dist/types/components/tabletoolbar/tabletoolbar.d.ts +5 -1
- package/dist/types/core/i18n.d.ts +58 -0
- package/dist/types/core/index.d.ts +1 -0
- package/package.json +1 -1
package/dist/button.css
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
&:disabled { opacity: 0.5; cursor: not-allowed; }
|
|
4
4
|
}
|
|
5
5
|
.crisp-button--intent_primary {
|
|
6
|
-
background: var(--crisp-bg-brand-solid); color: var(--crisp-fg-on-brand); box-shadow: inset 0 0 0 1px rgba(
|
|
6
|
+
background: var(--crisp-bg-brand-solid); color: var(--crisp-fg-on-brand); box-shadow: inset 0 0 0 1px rgba(0,0,0,0.06), 0 2px 4px -2px rgba(38,109,240,0.12), 0 3px 6px -2px rgba(38,109,240,0.08);
|
|
7
7
|
&:hover { background: var(--crisp-bg-brand-solid-pressed); }
|
|
8
8
|
&:active { background: var(--crisp-bg-brand-solid-pressed); }
|
|
9
9
|
}
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
&:hover { background: var(--crisp-bg-raised); }
|
|
13
13
|
}
|
|
14
14
|
.crisp-button--intent_danger {
|
|
15
|
-
background: var(--crisp-bg-danger-solid); color: var(--crisp-fg-on-brand); box-shadow: inset 0 0 0 1px rgba(
|
|
15
|
+
background: var(--crisp-bg-danger-solid); color: var(--crisp-fg-on-brand); box-shadow: inset 0 0 0 1px rgba(0,0,0,0.06), 0 2px 4px -2px rgba(186,37,37,0.20), 0 3px 6px -2px rgba(186,37,37,0.12);
|
|
16
16
|
&:hover { filter: brightness(0.96); }
|
|
17
17
|
&:active { filter: brightness(0.92); }
|
|
18
18
|
}
|
package/dist/button.layered.css
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
&:disabled { opacity: 0.5; cursor: not-allowed; }
|
|
5
5
|
}
|
|
6
6
|
.crisp-button--intent_primary {
|
|
7
|
-
background: var(--crisp-bg-brand-solid); color: var(--crisp-fg-on-brand); box-shadow: inset 0 0 0 1px rgba(
|
|
7
|
+
background: var(--crisp-bg-brand-solid); color: var(--crisp-fg-on-brand); box-shadow: inset 0 0 0 1px rgba(0,0,0,0.06), 0 2px 4px -2px rgba(38,109,240,0.12), 0 3px 6px -2px rgba(38,109,240,0.08);
|
|
8
8
|
&:hover { background: var(--crisp-bg-brand-solid-pressed); }
|
|
9
9
|
&:active { background: var(--crisp-bg-brand-solid-pressed); }
|
|
10
10
|
}
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
&:hover { background: var(--crisp-bg-raised); }
|
|
14
14
|
}
|
|
15
15
|
.crisp-button--intent_danger {
|
|
16
|
-
background: var(--crisp-bg-danger-solid); color: var(--crisp-fg-on-brand); box-shadow: inset 0 0 0 1px rgba(
|
|
16
|
+
background: var(--crisp-bg-danger-solid); color: var(--crisp-fg-on-brand); box-shadow: inset 0 0 0 1px rgba(0,0,0,0.06), 0 2px 4px -2px rgba(186,37,37,0.20), 0 3px 6px -2px rgba(186,37,37,0.12);
|
|
17
17
|
&:hover { filter: brightness(0.96); }
|
|
18
18
|
&:active { filter: brightness(0.92); }
|
|
19
19
|
}
|
|
@@ -28,7 +28,12 @@ const spec = (0, core_1.defineSpec)({
|
|
|
28
28
|
decls: {
|
|
29
29
|
background: tokens_1.contract.bgBrandSolid,
|
|
30
30
|
color: tokens_1.contract.fgOnBrand,
|
|
31
|
-
|
|
31
|
+
// crisp#494: the inset ring is a black tint, not white -- live-
|
|
32
|
+
// measured on Attio's own "New Company" button as
|
|
33
|
+
// rgba(0,0,0,0.06), not the rgba(255,255,255,0.07) both solid
|
|
34
|
+
// intents shared. The other two shadow layers already matched
|
|
35
|
+
// exactly.
|
|
36
|
+
boxShadow: "inset 0 0 0 1px rgba(0,0,0,0.06), 0 2px 4px -2px rgba(38,109,240,0.12), 0 3px 6px -2px rgba(38,109,240,0.08)",
|
|
32
37
|
},
|
|
33
38
|
parts: [
|
|
34
39
|
{ selector: "&:hover", decls: { background: tokens_1.contract.bgBrandSolidPressed } },
|
|
@@ -50,7 +55,10 @@ const spec = (0, core_1.defineSpec)({
|
|
|
50
55
|
decls: {
|
|
51
56
|
background: tokens_1.contract.bgDangerSolid,
|
|
52
57
|
color: tokens_1.contract.fgOnBrand,
|
|
53
|
-
|
|
58
|
+
// Same corrected inset ring as primary (crisp#494) -- shared
|
|
59
|
+
// literal, same solid-fill button family; the coloured layers
|
|
60
|
+
// themselves weren't independently re-measured this round.
|
|
61
|
+
boxShadow: "inset 0 0 0 1px rgba(0,0,0,0.06), 0 2px 4px -2px rgba(186,37,37,0.20), 0 3px 6px -2px rgba(186,37,37,0.12)",
|
|
54
62
|
},
|
|
55
63
|
parts: [
|
|
56
64
|
{ selector: "&:hover", decls: { filter: "brightness(0.96)" } },
|
|
@@ -330,9 +330,47 @@ function DataReferenceEditor({ col, value, isPending, onCommit, onCancel, }) {
|
|
|
330
330
|
onCommit(v);
|
|
331
331
|
} }) }));
|
|
332
332
|
}
|
|
333
|
-
function DataTable({ columns: columnsProp, data, selectable = false, selectedIds, onSelectionChange, selectAllLabel
|
|
334
|
-
|
|
335
|
-
|
|
333
|
+
function DataTable({ columns: columnsProp, data, selectable = false, selectedIds, onSelectionChange, selectAllLabel: selectAllLabelProp, selectRowLabel = (row) => `Select row ${row.id}`, selectionAnnouncement, columnMenu = false, onColumnsChange, groupBy, onRowsReorder, error, sort: sortProp, defaultSort, onSortChange, filterable = false, searchPlaceholder: searchPlaceholderProp, searchLabel: searchLabelProp, onEdit, stickyFirst = true, stickyLast = false, locale: localeProp, avatarTone = "brand", surface, interactive = false, onRowClick, onAddRow, addRowLabel: addRowLabelProp, empty: emptyProp, showCount = true, countLabel, toolbar, className, reorderRowLabel: reorderRowLabelProp, sortAscendingLabel: sortAscendingLabelProp, sortDescendingLabel: sortDescendingLabelProp, moveColumnLeftLabel: moveColumnLeftLabelProp, moveColumnRightLabel: moveColumnRightLabelProp, editColumnLabel: editColumnLabelProp, hideColumnLabel: hideColumnLabelProp, renameColumnLabel: renameColumnLabelProp, viewSettingsLabel: viewSettingsLabelProp, columnsGroupLabel: columnsGroupLabelProp, ...rest }) {
|
|
334
|
+
// crisp/#471: same resolution order as TableToolbar's own — an explicit
|
|
335
|
+
// prop wins, then the provider's translation of the built-in English
|
|
336
|
+
// default, then that default itself with no provider in the tree.
|
|
337
|
+
const crispMessages = (0, core_1.useCrispMessages)();
|
|
338
|
+
const selectAllLabel = (0, core_1.useCrispMessage)(selectAllLabelProp, "Select all rows");
|
|
339
|
+
const searchPlaceholder = (0, core_1.useCrispMessage)(searchPlaceholderProp, "Search…");
|
|
340
|
+
const searchLabel = (0, core_1.useCrispMessage)(searchLabelProp, "Search table");
|
|
341
|
+
// Also `React.ReactNode`-typed, same reasoning as `empty` below.
|
|
342
|
+
const addRowLabel = addRowLabelProp ?? crispMessages["New record"] ?? "New record";
|
|
343
|
+
// `empty` is typed `React.ReactNode`, not `string` — a consumer can pass a
|
|
344
|
+
// whole custom empty-state element, not just replacement text, so it
|
|
345
|
+
// can't go through `useCrispMessage` (string-only). A ReactNode override
|
|
346
|
+
// passes straight through unchanged (it's already whatever the consumer
|
|
347
|
+
// wants shown); only the built-in string default translates.
|
|
348
|
+
const empty = emptyProp ?? crispMessages["No results."] ?? "No results.";
|
|
349
|
+
const reorderRowLabel = (0, core_1.useCrispMessage)(reorderRowLabelProp, "Reorder row");
|
|
350
|
+
const sortAscendingLabel = (0, core_1.useCrispMessage)(sortAscendingLabelProp, "Sort ascending");
|
|
351
|
+
const sortDescendingLabel = (0, core_1.useCrispMessage)(sortDescendingLabelProp, "Sort descending");
|
|
352
|
+
const moveColumnLeftLabel = (0, core_1.useCrispMessage)(moveColumnLeftLabelProp, "Move left");
|
|
353
|
+
const moveColumnRightLabel = (0, core_1.useCrispMessage)(moveColumnRightLabelProp, "Move right");
|
|
354
|
+
const editColumnLabel = (0, core_1.useCrispMessage)(editColumnLabelProp, "Edit column label");
|
|
355
|
+
const hideColumnLabel = (0, core_1.useCrispMessage)(hideColumnLabelProp, "Hide from view");
|
|
356
|
+
const renameColumnLabel = (0, core_1.useCrispMessage)(renameColumnLabelProp, "Rename column");
|
|
357
|
+
const viewSettingsLabel = (0, core_1.useCrispMessage)(viewSettingsLabelProp, "View settings");
|
|
358
|
+
const columnsGroupLabel = (0, core_1.useCrispMessage)(columnsGroupLabelProp, "Columns");
|
|
359
|
+
// countLabel/selectionAnnouncement stay formatter props, same escape hatch
|
|
360
|
+
// as TableToolbar's sortTriggerLabel — word order and pluralisation rules
|
|
361
|
+
// differ too much across languages for a template. Their BUILT-IN default
|
|
362
|
+
// (when the consumer supplies neither) still translates its own static
|
|
363
|
+
// fragments through the provider, same as TableToolbar's "Sorted by".
|
|
364
|
+
const resolvedCountLabel = countLabel ??
|
|
365
|
+
((shown, total) => shown === total
|
|
366
|
+
? `${total} ${total === 1
|
|
367
|
+
? (crispMessages.row ?? "row")
|
|
368
|
+
: (crispMessages.rows ?? "rows")}`
|
|
369
|
+
: `${shown} ${crispMessages.of ?? "of"} ${total}`);
|
|
370
|
+
const resolvedSelectionAnnouncement = selectionAnnouncement ??
|
|
371
|
+
((count) => count === 0
|
|
372
|
+
? (crispMessages["Selection cleared"] ?? "Selection cleared")
|
|
373
|
+
: `${count} ${crispMessages.selected ?? "selected"}`);
|
|
336
374
|
// Two different defaults on purpose. FORMATTING falls back to "en-US", which
|
|
337
375
|
// is what the built-in date/number/currency renderers have always produced,
|
|
338
376
|
// so no existing output moves. SORTING falls back to `undefined`, which is
|
|
@@ -426,7 +464,7 @@ function DataTable({ columns: columnsProp, data, selectable = false, selectedIds
|
|
|
426
464
|
const [selectionText, setSelectionText] = React.useState("");
|
|
427
465
|
if (prevSelectedCount.current !== selected.size) {
|
|
428
466
|
prevSelectedCount.current = selected.size;
|
|
429
|
-
setSelectionText(
|
|
467
|
+
setSelectionText(resolvedSelectionAnnouncement(selected.size));
|
|
430
468
|
}
|
|
431
469
|
const [editing, setEditing] = React.useState(null);
|
|
432
470
|
// Set only while an async `onEdit` is in flight (#385) -- a sync `onEdit`
|
|
@@ -772,7 +810,7 @@ function DataTable({ columns: columnsProp, data, selectable = false, selectedIds
|
|
|
772
810
|
return dir === "asc" ? ((0, jsx_runtime_1.jsx)(lucide_react_1.ArrowUp, { className: "crisp-datatable-chevron", size: 13, strokeWidth: 2 })) : ((0, jsx_runtime_1.jsx)(lucide_react_1.ArrowDown, { className: "crisp-datatable-chevron", size: 13, strokeWidth: 2 }));
|
|
773
811
|
return ((0, jsx_runtime_1.jsx)(lucide_react_1.ChevronsUpDown, { className: "crisp-datatable-chevron", size: 13, strokeWidth: 2 }));
|
|
774
812
|
}
|
|
775
|
-
return ((0, jsx_runtime_1.jsxs)("div", { ref: gridRef, className: (0, datatable_recipe_1.datatableRecipe)({ className }), tabIndex: -1, onKeyDown: onGridKeyDown, ...rest, children: [(0, jsx_runtime_1.jsx)("div", { className: "crisp-datatable-live", role: "status", "aria-live": "polite", children: selectionText }), (filterable || showCount || toolbar || columnMenu) && ((0, jsx_runtime_1.jsxs)("div", { className: "crisp-datatable-toolbar", children: [columnMenu && ((0, jsx_runtime_1.jsxs)(menu_1.Menu, { align: "start", trigger: (0, jsx_runtime_1.jsxs)("button", { type: "button", className: "crisp-datatable-viewsettings", children: [(0, jsx_runtime_1.jsx)(lucide_react_1.SlidersHorizontal, { size: 14, strokeWidth: 1.75 }),
|
|
813
|
+
return ((0, jsx_runtime_1.jsxs)("div", { ref: gridRef, className: (0, datatable_recipe_1.datatableRecipe)({ className }), tabIndex: -1, onKeyDown: onGridKeyDown, ...rest, children: [(0, jsx_runtime_1.jsx)("div", { className: "crisp-datatable-live", role: "status", "aria-live": "polite", children: selectionText }), (filterable || showCount || toolbar || columnMenu) && ((0, jsx_runtime_1.jsxs)("div", { className: "crisp-datatable-toolbar", children: [columnMenu && ((0, jsx_runtime_1.jsxs)(menu_1.Menu, { align: "start", trigger: (0, jsx_runtime_1.jsxs)("button", { type: "button", className: "crisp-datatable-viewsettings", children: [(0, jsx_runtime_1.jsx)(lucide_react_1.SlidersHorizontal, { size: 14, strokeWidth: 1.75 }), viewSettingsLabel] }), children: [(0, jsx_runtime_1.jsx)("div", { className: "crisp-datatable-group-label", children: columnsGroupLabel }), allColumns.map((c) => ((0, jsx_runtime_1.jsxs)(menu_1.MenuItem, { onSelect: () => c.hidden ? showColumn(c.key) : hideColumn(c.key), children: [(0, jsx_runtime_1.jsx)(lucide_react_1.Check, { size: 14, strokeWidth: 2, style: { opacity: c.hidden ? 0 : 1 } }), typeof c.header === "string" ? c.header : c.key] }, c.key)))] })), filterable && ((0, jsx_runtime_1.jsxs)("span", { className: "crisp-datatable-search", children: [(0, jsx_runtime_1.jsx)(lucide_react_1.Search, { size: 15, strokeWidth: 1.75, "aria-hidden": "true" }), (0, jsx_runtime_1.jsx)("input", { type: "search", placeholder: searchPlaceholder, value: query, onChange: (e) => setQuery(e.target.value), "aria-label": searchLabel })] })), showCount && ((0, jsx_runtime_1.jsx)("span", { className: "crisp-datatable-count", children: resolvedCountLabel(rows.length, data.length) })), toolbar] })), (0, jsx_runtime_1.jsxs)(table_1.Table, { interactive: interactive, surface: surface, style: { minWidth }, scrollProps: {
|
|
776
814
|
ref: scrollRef,
|
|
777
815
|
onScroll: (e) => {
|
|
778
816
|
const el = e.currentTarget;
|
|
@@ -802,7 +840,7 @@ function DataTable({ columns: columnsProp, data, selectable = false, selectedIds
|
|
|
802
840
|
const inner = ((0, jsx_runtime_1.jsxs)("span", { className: "crisp-datatable-th-inner", children: [Icon ? (0, jsx_runtime_1.jsx)(Icon, { size: 16, strokeWidth: 1.75 }) : null, (0, jsx_runtime_1.jsx)("span", { className: "crisp-datatable-th-label", children: col.header }), col.sortable ? (0, jsx_runtime_1.jsx)(ChevronFor, { colKey: col.key }) : null] }));
|
|
803
841
|
return ((0, jsx_runtime_1.jsxs)(table_1.TableHead, { align: alignFor(col), "aria-sort": ariaSort, ...dataStickyCol(i), children: [renaming === col.key ? ((0, jsx_runtime_1.jsx)("input", {
|
|
804
842
|
// biome-ignore lint/a11y/noAutofocus: the rename item opens this field
|
|
805
|
-
autoFocus: true, className: "crisp-datatable-th-rename", defaultValue: typeof col.header === "string" ? col.header : "", "aria-label":
|
|
843
|
+
autoFocus: true, className: "crisp-datatable-th-rename", defaultValue: typeof col.header === "string" ? col.header : "", "aria-label": renameColumnLabel, onBlur: (e) => {
|
|
806
844
|
renameColumn(col.key, e.currentTarget.value);
|
|
807
845
|
setRenaming(null);
|
|
808
846
|
}, onKeyDown: (e) => {
|
|
@@ -814,7 +852,7 @@ function DataTable({ columns: columnsProp, data, selectable = false, selectedIds
|
|
|
814
852
|
setRenaming(null);
|
|
815
853
|
} })) : columnMenu ? ((0, jsx_runtime_1.jsxs)(menu_1.Menu, { align: "start", trigger: (0, jsx_runtime_1.jsx)("button", { type: "button", className: "crisp-datatable-sort", "data-sorted": sortDirByKey.get(col.key), "aria-label": `${typeof col.header === "string"
|
|
816
854
|
? col.header
|
|
817
|
-
: col.key} column options`, children: inner }), children: [(0, jsx_runtime_1.jsx)(menu_1.MenuItem, { onSelect: () => setSort({ key: col.key, dir: "asc" }), children:
|
|
855
|
+
: col.key} ${crispMessages["column options"] ?? "column options"}`, children: inner }), children: [(0, jsx_runtime_1.jsx)(menu_1.MenuItem, { onSelect: () => setSort({ key: col.key, dir: "asc" }), children: sortAscendingLabel }), (0, jsx_runtime_1.jsx)(menu_1.MenuItem, { onSelect: () => setSort({ key: col.key, dir: "desc" }), children: sortDescendingLabel }), col.managed === false ? null : ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsx)(menu_1.MenuItem, { onSelect: () => moveColumn(col.key, -1), children: moveColumnLeftLabel }), (0, jsx_runtime_1.jsx)(menu_1.MenuItem, { onSelect: () => moveColumn(col.key, 1), children: moveColumnRightLabel }), (0, jsx_runtime_1.jsx)(menu_1.MenuItem, { onSelect: () => setRenaming(col.key), children: editColumnLabel }), (0, jsx_runtime_1.jsx)(menu_1.MenuItem, { onSelect: () => hideColumn(col.key), children: hideColumnLabel })] }))] })) : col.sortable ? ((0, jsx_runtime_1.jsx)("button", { type: "button", className: "crisp-datatable-sort", "data-sorted": sortDirByKey.get(col.key), onClick: () => cycleSort(col.key), children: inner })) : (inner), col.resizable !== false && ((0, jsx_runtime_1.jsx)("span", { className: "crisp-datatable-resize", "data-resizing": resizingKey === col.key ? "" : undefined, onPointerDown: (e) => startResize(col, e), onPointerMove: moveResize, onPointerUp: endResize }))] }, col.key));
|
|
818
856
|
})] }) }), (0, jsx_runtime_1.jsx)(table_1.TableBody, { children: error ? ((0, jsx_runtime_1.jsx)(table_1.TableRow, { children: (0, jsx_runtime_1.jsx)(table_1.TableCell, { colSpan: totalCols, children: (0, jsx_runtime_1.jsx)("div", { className: "crisp-datatable-empty", children: error }) }) })) : rows.length === 0 ? ((0, jsx_runtime_1.jsx)(table_1.TableRow, { children: (0, jsx_runtime_1.jsx)(table_1.TableCell, { colSpan: totalCols, children: (0, jsx_runtime_1.jsx)("div", { className: "crisp-datatable-empty", children: empty }) }) })) : (groupedRows.map(({ group, rows: groupRows }) => ((0, jsx_runtime_1.jsxs)(React.Fragment, { children: [group !== null && ((0, jsx_runtime_1.jsx)(table_1.TableRow, { children: (0, jsx_runtime_1.jsxs)(table_1.TableCell, { colSpan: totalCols, className: "crisp-datatable-group", children: [(0, jsx_runtime_1.jsx)("span", { className: "crisp-datatable-group-label", children: group }), (0, jsx_runtime_1.jsx)("span", { className: "crisp-datatable-group-count", children: groupRows.length })] }) })), groupRows.map((row) => ((0, jsx_runtime_1.jsxs)(table_1.TableRow, { "data-selected": selected.has(row.id) ? "" : undefined, "data-drop-target": showGrip &&
|
|
819
857
|
dragRowId &&
|
|
820
858
|
dragRowId !== row.id &&
|
|
@@ -156,7 +156,33 @@ function applySorts(rows, sorts, get = (row, key) => row[key]) {
|
|
|
156
156
|
* this (Sort: Due date/Assignee/Creation date; Filter: Assignee/Record/
|
|
157
157
|
* Created by), which one shared list can't reproduce.
|
|
158
158
|
*/
|
|
159
|
-
function TableToolbar({ columns, sortColumns: sortColumnsProp, filterColumns: filterColumnsProp, sorts: sortsProp, defaultSorts, onSortsChange, filters: filtersProp, defaultFilters, onFiltersChange, count, children, helpHref, className, sortLabel
|
|
159
|
+
function TableToolbar({ columns, sortColumns: sortColumnsProp, filterColumns: filterColumnsProp, sorts: sortsProp, defaultSorts, onSortsChange, filters: filtersProp, defaultFilters, onFiltersChange, count, children, helpHref, className, sortLabel: sortLabelProp, filterLabel: filterLabelProp, reorderSortLabel: reorderSortLabelProp, noSortsLabel: noSortsLabelProp, addSortLabel: addSortLabelProp, learnSortingLabel: learnSortingLabelProp, selectConditionLabel: selectConditionLabelProp, sortFieldLabel: sortFieldLabelProp, sortDirectionLabel: sortDirectionLabelProp, removeSortLabel: removeSortLabelProp, filterFieldLabel: filterFieldLabelProp, filterFieldPlaceholder: filterFieldPlaceholderProp, filterValuePlaceholder: filterValuePlaceholderProp, ascendingLabel: ascendingLabelProp, descendingLabel: descendingLabelProp, sortTriggerLabel, filterTriggerLabel, inline, ...rest }) {
|
|
160
|
+
// crisp/#471: an explicit prop always wins; missing, it reads the nearest
|
|
161
|
+
// CrispMessagesProvider's translation of the English default below, or
|
|
162
|
+
// that default itself with no provider — unchanged from before this
|
|
163
|
+
// existed. One resolved constant per prop, same name every existing usage
|
|
164
|
+
// below already reads, so nothing past this block changes.
|
|
165
|
+
const sortLabel = (0, core_1.useCrispMessage)(sortLabelProp, "Sort");
|
|
166
|
+
const filterLabel = (0, core_1.useCrispMessage)(filterLabelProp, "Filter");
|
|
167
|
+
const reorderSortLabel = (0, core_1.useCrispMessage)(reorderSortLabelProp, "Reorder sort");
|
|
168
|
+
const noSortsLabel = (0, core_1.useCrispMessage)(noSortsLabelProp, "No sorts applied to this view.");
|
|
169
|
+
const addSortLabel = (0, core_1.useCrispMessage)(addSortLabelProp, "Add sort");
|
|
170
|
+
const learnSortingLabel = (0, core_1.useCrispMessage)(learnSortingLabelProp, "Learn about sorting");
|
|
171
|
+
const selectConditionLabel = (0, core_1.useCrispMessage)(selectConditionLabelProp, "Select condition");
|
|
172
|
+
const sortFieldLabel = (0, core_1.useCrispMessage)(sortFieldLabelProp, "Sort field");
|
|
173
|
+
const sortDirectionLabel = (0, core_1.useCrispMessage)(sortDirectionLabelProp, "Sort direction");
|
|
174
|
+
const removeSortLabel = (0, core_1.useCrispMessage)(removeSortLabelProp, "Remove sort");
|
|
175
|
+
const filterFieldLabel = (0, core_1.useCrispMessage)(filterFieldLabelProp, "Filter by field");
|
|
176
|
+
const filterFieldPlaceholder = (0, core_1.useCrispMessage)(filterFieldPlaceholderProp, "Filter by…");
|
|
177
|
+
const filterValuePlaceholder = (0, core_1.useCrispMessage)(filterValuePlaceholderProp, "Value…");
|
|
178
|
+
const ascendingLabel = (0, core_1.useCrispMessage)(ascendingLabelProp, "Ascending");
|
|
179
|
+
const descendingLabel = (0, core_1.useCrispMessage)(descendingLabelProp, "Descending");
|
|
180
|
+
// OP_LABELS' own words ("contains", "is", "is any of", …) have no prop of
|
|
181
|
+
// their own — a whole lookup table, not one string — so they translate
|
|
182
|
+
// straight from the provider's dictionary at the two spots they render
|
|
183
|
+
// (#628, #673 below), same fallback order as every resolved constant
|
|
184
|
+
// above.
|
|
185
|
+
const crispMessages = (0, core_1.useCrispMessages)();
|
|
160
186
|
// Each side falls back to the shared `columns` independently — a caller
|
|
161
187
|
// that only overrides one (Filter's own Record/Created by, say) still
|
|
162
188
|
// gets `columns` on the other, unchanged from before either prop existed.
|
|
@@ -245,12 +271,18 @@ function TableToolbar({ columns, sortColumns: sortColumnsProp, filterColumns: fi
|
|
|
245
271
|
};
|
|
246
272
|
const activeFilters = filters.length;
|
|
247
273
|
// The default phrase only works in English word order, so a caller can
|
|
248
|
-
// replace the whole thing rather than fill a template
|
|
274
|
+
// replace the whole thing rather than fill a template — crisp/#471's
|
|
275
|
+
// `sortTriggerLabel` formatter is exactly that escape hatch. Short of a
|
|
276
|
+
// full formatter, the "Sorted by" fragment itself still translates (the
|
|
277
|
+
// sort trigger reading "Sort" in Mongolian and "Sorted by X" in English is
|
|
278
|
+
// worse than an imperfect word order); `crispMessages` rather than a
|
|
279
|
+
// second `useCrispMessage` call, since this only fires on the branch that
|
|
280
|
+
// needs it.
|
|
249
281
|
const sortedLabel = sortTriggerLabel
|
|
250
282
|
? sortTriggerLabel(sorts, sortColumns)
|
|
251
283
|
: sorts.length
|
|
252
|
-
?
|
|
253
|
-
:
|
|
284
|
+
? `${crispMessages["Sorted by"] ?? "Sorted by"} ${sortColumns.find((c) => c.key === sorts[0].key)?.label}${sorts.length > 1 ? ` +${sorts.length - 1}` : ""}`
|
|
285
|
+
: sortLabel;
|
|
254
286
|
return ((0, jsx_runtime_1.jsx)("div", { className: (0, tabletoolbar_recipe_1.tabletoolbarRecipe)({ className }), ...rest, children: (0, jsx_runtime_1.jsxs)("div", { className: "crisp-tabletoolbar-toolbar", "data-inline": inline ? "" : undefined, children: [(0, jsx_runtime_1.jsxs)("span", { className: "crisp-tabletoolbar-toolgroup", children: [(0, jsx_runtime_1.jsxs)("button", { type: "button", className: `crisp-tabletoolbar-tool${sorts.length ? " crisp-tabletoolbar-tool--active" : ""}`, onClick: () => setOpenMenu(openMenu === "sort" ? null : "sort"), "aria-haspopup": "dialog", "aria-expanded": openMenu === "sort", "data-menu-trigger": true, children: [(0, jsx_runtime_1.jsx)(lucide_react_1.ArrowUpDown, { size: 14, strokeWidth: 1.75, "aria-hidden": "true" }), sortedLabel] }), openMenu === "sort" && ((0, jsx_runtime_1.jsxs)("div", { ref: sortListRef, className: "crisp-tabletoolbar-menu crisp-tabletoolbar-sortpop", role: "dialog", "aria-label": sortLabel, children: [sorts.length === 0 ? ((0, jsx_runtime_1.jsx)("div", { className: "crisp-tabletoolbar-sort-empty", children: noSortsLabel })) : (sorts.map((s, i) => ((0, jsx_runtime_1.jsxs)("div", { className: "crisp-tabletoolbar-sort-row", onDragOver: (e) => {
|
|
255
287
|
if (dragSort !== null)
|
|
256
288
|
e.preventDefault();
|
|
@@ -276,8 +308,8 @@ function TableToolbar({ columns, sortColumns: sortColumnsProp, filterColumns: fi
|
|
|
276
308
|
], "aria-label": sortDirectionLabel }), (0, jsx_runtime_1.jsx)("button", { type: "button", className: "crisp-tabletoolbar-sort-remove", onClick: () => removeSort(i), "aria-label": removeSortLabel, children: (0, jsx_runtime_1.jsx)(lucide_react_1.X, { size: 15, strokeWidth: 1.75 }) })] }, s.key)))), (0, jsx_runtime_1.jsx)("div", { className: "crisp-tabletoolbar-sort-addrow", children: (0, jsx_runtime_1.jsxs)("button", { ref: addSortRef, type: "button", className: "crisp-tabletoolbar-sort-add", onClick: addSort, children: [(0, jsx_runtime_1.jsx)(lucide_react_1.Plus, { size: 15, strokeWidth: 1.75 }), addSortLabel] }) }), helpHref && ((0, jsx_runtime_1.jsx)("div", { className: "crisp-tabletoolbar-sort-foot", children: (0, jsx_runtime_1.jsxs)("a", { className: "crisp-tabletoolbar-sort-learn", href: helpHref, target: "_blank", rel: "noreferrer", children: [(0, jsx_runtime_1.jsx)(lucide_react_1.CircleHelp, { size: 14, strokeWidth: 1.75 }), learnSortingLabel] }) }))] }))] }), (0, jsx_runtime_1.jsx)("span", { className: "crisp-tabletoolbar-divider", "aria-hidden": "true" }), (0, jsx_runtime_1.jsxs)("span", { className: "crisp-tabletoolbar-toolgroup", children: [(0, jsx_runtime_1.jsxs)("button", { type: "button", className: `crisp-tabletoolbar-tool crisp-tabletoolbar-tool--ghost${activeFilters ? " crisp-tabletoolbar-tool--active" : ""}`, onClick: () => setOpenMenu(openMenu === "filter" ? null : "filter"), "aria-haspopup": "dialog", "aria-expanded": openMenu === "filter", "data-menu-trigger": true, children: [(0, jsx_runtime_1.jsx)(lucide_react_1.ListFilter, { size: 14, strokeWidth: 1.75, "aria-hidden": "true" }), filterTriggerLabel
|
|
277
309
|
? filterTriggerLabel(activeFilters)
|
|
278
310
|
: activeFilters
|
|
279
|
-
?
|
|
280
|
-
:
|
|
311
|
+
? `${filterLabel} · ${activeFilters}`
|
|
312
|
+
: filterLabel] }), openMenu === "filter" && ((0, jsx_runtime_1.jsxs)("div", { className: "crisp-tabletoolbar-menu", role: "dialog", "aria-label": filterFieldLabel, children: [(0, jsx_runtime_1.jsx)("input", { className: "crisp-tabletoolbar-menu-input", placeholder: filterFieldPlaceholder, "aria-label": filterFieldLabel,
|
|
281
313
|
// biome-ignore lint/a11y/noAutofocus: focus the picker on open
|
|
282
314
|
autoFocus: true, value: filterQuery, onChange: (e) => setFilterQuery(e.target.value) }), filterColumns
|
|
283
315
|
.filter((col) => col.label
|
|
@@ -288,7 +320,8 @@ function TableToolbar({ columns, sortColumns: sortColumnsProp, filterColumns: fi
|
|
|
288
320
|
const open = openMenu === `fcond:${cond.id}`;
|
|
289
321
|
const hasValue = (!opNeedsValue(cond.op) || cond.value.trim()) &&
|
|
290
322
|
(!opNeedsValues(cond.op) || (cond.values?.length ?? 0) > 0);
|
|
291
|
-
return ((0, jsx_runtime_1.jsxs)("span", { className: "crisp-tabletoolbar-toolgroup", children: [(0, jsx_runtime_1.jsxs)("span", { className: "crisp-tabletoolbar-fchip", children: [(0, jsx_runtime_1.jsxs)("button", { type: "button", className: "crisp-tabletoolbar-fchip-main", "data-menu-trigger": true, "aria-haspopup": "dialog", onClick: () => setOpenMenu(open ? null : `fcond:${cond.id}`), children: [(0, jsx_runtime_1.jsx)("span", { className: "crisp-tabletoolbar-fchip-field", children: col?.label }), hasValue ? ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsx)("span", { className: "crisp-tabletoolbar-fchip-op", children: OP_LABELS[cond.op]
|
|
323
|
+
return ((0, jsx_runtime_1.jsxs)("span", { className: "crisp-tabletoolbar-toolgroup", children: [(0, jsx_runtime_1.jsxs)("span", { className: "crisp-tabletoolbar-fchip", children: [(0, jsx_runtime_1.jsxs)("button", { type: "button", className: "crisp-tabletoolbar-fchip-main", "data-menu-trigger": true, "aria-haspopup": "dialog", onClick: () => setOpenMenu(open ? null : `fcond:${cond.id}`), children: [(0, jsx_runtime_1.jsx)("span", { className: "crisp-tabletoolbar-fchip-field", children: col?.label }), hasValue ? ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsx)("span", { className: "crisp-tabletoolbar-fchip-op", children: crispMessages[OP_LABELS[cond.op]] ??
|
|
324
|
+
OP_LABELS[cond.op] }), opNeedsValue(cond.op) && ((0, jsx_runtime_1.jsx)("span", { className: "crisp-tabletoolbar-fchip-val", children: cond.value })), cond.op === "isanyof" && cond.values && ((0, jsx_runtime_1.jsx)("span", { className: "crisp-tabletoolbar-fchip-val", children: isAnyOfSummary(cond.values) }))] })) : ((0, jsx_runtime_1.jsx)("span", { className: "crisp-tabletoolbar-fchip-op", children: selectConditionLabel }))] }), (0, jsx_runtime_1.jsx)("button", { type: "button", className: "crisp-tabletoolbar-fchip-x", "aria-label": `${crispMessages.Remove ?? "Remove"} ${col?.label} ${crispMessages.filter ?? "filter"}`, onClick: () => removeFilter(cond.id), children: (0, jsx_runtime_1.jsx)(lucide_react_1.X, { size: 13, strokeWidth: 2 }) })] }), open && ((0, jsx_runtime_1.jsxs)("div", { className: "crisp-tabletoolbar-menu", role: "dialog", "aria-label": `${col?.label} ${crispMessages.condition ?? "condition"}`, children: [(0, jsx_runtime_1.jsxs)("div", { className: "crisp-tabletoolbar-menu-head", children: [col?.label, " ", crispMessages.condition ?? "condition"] }), opsFor(col).map((op) => ((0, jsx_runtime_1.jsxs)("button", { type: "button", className: "crisp-tabletoolbar-menu-item", "data-on": cond.op === op ? "" : undefined, onClick: () => patchFilter(cond.id, { op }), children: [crispMessages[OP_LABELS[op]] ?? OP_LABELS[op], cond.op === op ? ((0, jsx_runtime_1.jsx)(lucide_react_1.Check, { className: "crisp-tabletoolbar-menu-check", size: 15, strokeWidth: 1.75 })) : null] }, op))), opNeedsValue(cond.op) && ((0, jsx_runtime_1.jsx)("input", { className: "crisp-tabletoolbar-menu-input", placeholder: filterValuePlaceholder, "aria-label": `${col?.label} filter value`,
|
|
292
325
|
// biome-ignore lint/a11y/noAutofocus: focus the value field
|
|
293
326
|
autoFocus: true, value: cond.value, onChange: (e) => patchFilter(cond.id, { value: e.target.value }) })), cond.op === "isanyof" && col?.values && ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsx)("input", { className: "crisp-tabletoolbar-menu-input", placeholder: filterValuePlaceholder, "aria-label": `${col?.label} filter values`,
|
|
294
327
|
// biome-ignore lint/a11y/noAutofocus: focus the search field
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.CrispMessagesProvider = CrispMessagesProvider;
|
|
37
|
+
exports.useCrispMessage = useCrispMessage;
|
|
38
|
+
exports.useCrispMessages = useCrispMessages;
|
|
39
|
+
const jsx_runtime_1 = require("react/jsx-runtime");
|
|
40
|
+
const React = __importStar(require("react"));
|
|
41
|
+
const CrispMessagesContext = React.createContext({});
|
|
42
|
+
function CrispMessagesProvider({ messages, children, }) {
|
|
43
|
+
return ((0, jsx_runtime_1.jsx)(CrispMessagesContext.Provider, { value: messages, children: children }));
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Resolves one translatable string for a component: `explicit` is whatever
|
|
47
|
+
* the caller passed for that prop (`undefined` when they didn't), and
|
|
48
|
+
* `englishDefault` is the literal that shipped as that prop's default value
|
|
49
|
+
* before crisp/#471 — still what renders with no provider in the tree.
|
|
50
|
+
*
|
|
51
|
+
* A plain function, not a second context read inside a loop or a render
|
|
52
|
+
* helper — components call this once per string, the same shape
|
|
53
|
+
* `useLabelAssociation`/`useFormReset` and this codebase's other small
|
|
54
|
+
* `core/` hooks already take.
|
|
55
|
+
*/
|
|
56
|
+
function useCrispMessage(explicit, englishDefault) {
|
|
57
|
+
const messages = React.useContext(CrispMessagesContext);
|
|
58
|
+
if (explicit !== undefined)
|
|
59
|
+
return explicit;
|
|
60
|
+
return messages[englishDefault] ?? englishDefault;
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* The same resolution as `useCrispMessage`, for a component that builds its
|
|
64
|
+
* own default text internally (TableToolbar's "Sorted by {field}", a
|
|
65
|
+
* DatePicker's month/weekday names) rather than taking one string prop per
|
|
66
|
+
* piece — every literal fragment of that built-in text still needs its own
|
|
67
|
+
* translation lookup, and a component with many such fragments (twelve
|
|
68
|
+
* month names, seven weekday names) would otherwise call `useCrispMessage`
|
|
69
|
+
* in a loop, which breaks the rules of hooks. `useCrispMessages` returns the
|
|
70
|
+
* raw dictionary once; call sites do their own `dict[english] ?? english`
|
|
71
|
+
* per fragment, same fallback order, no hook-in-a-loop.
|
|
72
|
+
*/
|
|
73
|
+
function useCrispMessages() {
|
|
74
|
+
return React.useContext(CrispMessagesContext);
|
|
75
|
+
}
|
package/dist/cjs/core/index.js
CHANGED
|
@@ -14,7 +14,7 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
|
14
14
|
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
15
|
};
|
|
16
16
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
-
exports.usePrefersReducedMotion = exports.useMentionTrigger = exports.useLabelAssociation = exports.useFormReset = exports.useFocusAfterRemoval = exports.specToRecipe = exports.defineSpec = exports.resolveRecipe = exports.Primitive = exports.placeFloating = exports.matchMentionTrigger = exports.formValueStyle = exports.registerFloatingLayer = exports.isInsideLaterFloatingLayer = exports.hasOpenFloatingLayer = exports.FloatingPortal = exports.isRtlElement = exports.withDescribedBy = exports.defineRecipe = exports.cx = exports.parseCellDate = exports.formatCellDate = exports.cellDatePrecision = exports.caretRectInField = void 0;
|
|
17
|
+
exports.usePrefersReducedMotion = exports.useMentionTrigger = exports.useLabelAssociation = exports.useFormReset = exports.useFocusAfterRemoval = exports.specToRecipe = exports.defineSpec = exports.resolveRecipe = exports.Primitive = exports.placeFloating = exports.matchMentionTrigger = exports.useCrispMessages = exports.useCrispMessage = exports.CrispMessagesProvider = exports.formValueStyle = exports.registerFloatingLayer = exports.isInsideLaterFloatingLayer = exports.hasOpenFloatingLayer = exports.FloatingPortal = exports.isRtlElement = exports.withDescribedBy = exports.defineRecipe = exports.cx = exports.parseCellDate = exports.formatCellDate = exports.cellDatePrecision = exports.caretRectInField = void 0;
|
|
18
18
|
var caret_rect_1 = require("./caret-rect");
|
|
19
19
|
Object.defineProperty(exports, "caretRectInField", { enumerable: true, get: function () { return caret_rect_1.caretRectInField; } });
|
|
20
20
|
var cell_date_1 = require("./cell-date");
|
|
@@ -37,6 +37,10 @@ Object.defineProperty(exports, "isInsideLaterFloatingLayer", { enumerable: true,
|
|
|
37
37
|
Object.defineProperty(exports, "registerFloatingLayer", { enumerable: true, get: function () { return floating_stack_1.registerFloatingLayer; } });
|
|
38
38
|
var form_value_1 = require("./form-value");
|
|
39
39
|
Object.defineProperty(exports, "formValueStyle", { enumerable: true, get: function () { return form_value_1.formValueStyle; } });
|
|
40
|
+
var i18n_1 = require("./i18n");
|
|
41
|
+
Object.defineProperty(exports, "CrispMessagesProvider", { enumerable: true, get: function () { return i18n_1.CrispMessagesProvider; } });
|
|
42
|
+
Object.defineProperty(exports, "useCrispMessage", { enumerable: true, get: function () { return i18n_1.useCrispMessage; } });
|
|
43
|
+
Object.defineProperty(exports, "useCrispMessages", { enumerable: true, get: function () { return i18n_1.useCrispMessages; } });
|
|
40
44
|
var mention_1 = require("./mention");
|
|
41
45
|
Object.defineProperty(exports, "matchMentionTrigger", { enumerable: true, get: function () { return mention_1.matchMentionTrigger; } });
|
|
42
46
|
var place_floating_1 = require("./place-floating");
|
|
@@ -25,7 +25,12 @@ const spec = defineSpec({
|
|
|
25
25
|
decls: {
|
|
26
26
|
background: c.bgBrandSolid,
|
|
27
27
|
color: c.fgOnBrand,
|
|
28
|
-
|
|
28
|
+
// crisp#494: the inset ring is a black tint, not white -- live-
|
|
29
|
+
// measured on Attio's own "New Company" button as
|
|
30
|
+
// rgba(0,0,0,0.06), not the rgba(255,255,255,0.07) both solid
|
|
31
|
+
// intents shared. The other two shadow layers already matched
|
|
32
|
+
// exactly.
|
|
33
|
+
boxShadow: "inset 0 0 0 1px rgba(0,0,0,0.06), 0 2px 4px -2px rgba(38,109,240,0.12), 0 3px 6px -2px rgba(38,109,240,0.08)",
|
|
29
34
|
},
|
|
30
35
|
parts: [
|
|
31
36
|
{ selector: "&:hover", decls: { background: c.bgBrandSolidPressed } },
|
|
@@ -47,7 +52,10 @@ const spec = defineSpec({
|
|
|
47
52
|
decls: {
|
|
48
53
|
background: c.bgDangerSolid,
|
|
49
54
|
color: c.fgOnBrand,
|
|
50
|
-
|
|
55
|
+
// Same corrected inset ring as primary (crisp#494) -- shared
|
|
56
|
+
// literal, same solid-fill button family; the coloured layers
|
|
57
|
+
// themselves weren't independently re-measured this round.
|
|
58
|
+
boxShadow: "inset 0 0 0 1px rgba(0,0,0,0.06), 0 2px 4px -2px rgba(186,37,37,0.20), 0 3px 6px -2px rgba(186,37,37,0.12)",
|
|
51
59
|
},
|
|
52
60
|
parts: [
|
|
53
61
|
{ selector: "&:hover", decls: { filter: "brightness(0.96)" } },
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { ArrowDown, ArrowUp, Check, ChevronsUpDown, CircleUserRound, GripVertical, Plus, Search, SlidersHorizontal, } from "lucide-react";
|
|
3
3
|
import * as React from "react";
|
|
4
|
-
import { cellDatePrecision, formatCellDate, isRtlElement, parseCellDate, } from "../../core/index.js";
|
|
4
|
+
import { cellDatePrecision, formatCellDate, isRtlElement, parseCellDate, useCrispMessage, useCrispMessages, } from "../../core/index.js";
|
|
5
5
|
import { Avatar } from "../avatar/index.js";
|
|
6
6
|
import { Checkbox } from "../checkbox/index.js";
|
|
7
7
|
import { Menu, MenuItem } from "../menu/index.js";
|
|
@@ -294,9 +294,47 @@ function DataReferenceEditor({ col, value, isPending, onCommit, onCancel, }) {
|
|
|
294
294
|
onCommit(v);
|
|
295
295
|
} }) }));
|
|
296
296
|
}
|
|
297
|
-
export function DataTable({ columns: columnsProp, data, selectable = false, selectedIds, onSelectionChange, selectAllLabel
|
|
298
|
-
|
|
299
|
-
|
|
297
|
+
export function DataTable({ columns: columnsProp, data, selectable = false, selectedIds, onSelectionChange, selectAllLabel: selectAllLabelProp, selectRowLabel = (row) => `Select row ${row.id}`, selectionAnnouncement, columnMenu = false, onColumnsChange, groupBy, onRowsReorder, error, sort: sortProp, defaultSort, onSortChange, filterable = false, searchPlaceholder: searchPlaceholderProp, searchLabel: searchLabelProp, onEdit, stickyFirst = true, stickyLast = false, locale: localeProp, avatarTone = "brand", surface, interactive = false, onRowClick, onAddRow, addRowLabel: addRowLabelProp, empty: emptyProp, showCount = true, countLabel, toolbar, className, reorderRowLabel: reorderRowLabelProp, sortAscendingLabel: sortAscendingLabelProp, sortDescendingLabel: sortDescendingLabelProp, moveColumnLeftLabel: moveColumnLeftLabelProp, moveColumnRightLabel: moveColumnRightLabelProp, editColumnLabel: editColumnLabelProp, hideColumnLabel: hideColumnLabelProp, renameColumnLabel: renameColumnLabelProp, viewSettingsLabel: viewSettingsLabelProp, columnsGroupLabel: columnsGroupLabelProp, ...rest }) {
|
|
298
|
+
// crisp/#471: same resolution order as TableToolbar's own — an explicit
|
|
299
|
+
// prop wins, then the provider's translation of the built-in English
|
|
300
|
+
// default, then that default itself with no provider in the tree.
|
|
301
|
+
const crispMessages = useCrispMessages();
|
|
302
|
+
const selectAllLabel = useCrispMessage(selectAllLabelProp, "Select all rows");
|
|
303
|
+
const searchPlaceholder = useCrispMessage(searchPlaceholderProp, "Search…");
|
|
304
|
+
const searchLabel = useCrispMessage(searchLabelProp, "Search table");
|
|
305
|
+
// Also `React.ReactNode`-typed, same reasoning as `empty` below.
|
|
306
|
+
const addRowLabel = addRowLabelProp ?? crispMessages["New record"] ?? "New record";
|
|
307
|
+
// `empty` is typed `React.ReactNode`, not `string` — a consumer can pass a
|
|
308
|
+
// whole custom empty-state element, not just replacement text, so it
|
|
309
|
+
// can't go through `useCrispMessage` (string-only). A ReactNode override
|
|
310
|
+
// passes straight through unchanged (it's already whatever the consumer
|
|
311
|
+
// wants shown); only the built-in string default translates.
|
|
312
|
+
const empty = emptyProp ?? crispMessages["No results."] ?? "No results.";
|
|
313
|
+
const reorderRowLabel = useCrispMessage(reorderRowLabelProp, "Reorder row");
|
|
314
|
+
const sortAscendingLabel = useCrispMessage(sortAscendingLabelProp, "Sort ascending");
|
|
315
|
+
const sortDescendingLabel = useCrispMessage(sortDescendingLabelProp, "Sort descending");
|
|
316
|
+
const moveColumnLeftLabel = useCrispMessage(moveColumnLeftLabelProp, "Move left");
|
|
317
|
+
const moveColumnRightLabel = useCrispMessage(moveColumnRightLabelProp, "Move right");
|
|
318
|
+
const editColumnLabel = useCrispMessage(editColumnLabelProp, "Edit column label");
|
|
319
|
+
const hideColumnLabel = useCrispMessage(hideColumnLabelProp, "Hide from view");
|
|
320
|
+
const renameColumnLabel = useCrispMessage(renameColumnLabelProp, "Rename column");
|
|
321
|
+
const viewSettingsLabel = useCrispMessage(viewSettingsLabelProp, "View settings");
|
|
322
|
+
const columnsGroupLabel = useCrispMessage(columnsGroupLabelProp, "Columns");
|
|
323
|
+
// countLabel/selectionAnnouncement stay formatter props, same escape hatch
|
|
324
|
+
// as TableToolbar's sortTriggerLabel — word order and pluralisation rules
|
|
325
|
+
// differ too much across languages for a template. Their BUILT-IN default
|
|
326
|
+
// (when the consumer supplies neither) still translates its own static
|
|
327
|
+
// fragments through the provider, same as TableToolbar's "Sorted by".
|
|
328
|
+
const resolvedCountLabel = countLabel ??
|
|
329
|
+
((shown, total) => shown === total
|
|
330
|
+
? `${total} ${total === 1
|
|
331
|
+
? (crispMessages.row ?? "row")
|
|
332
|
+
: (crispMessages.rows ?? "rows")}`
|
|
333
|
+
: `${shown} ${crispMessages.of ?? "of"} ${total}`);
|
|
334
|
+
const resolvedSelectionAnnouncement = selectionAnnouncement ??
|
|
335
|
+
((count) => count === 0
|
|
336
|
+
? (crispMessages["Selection cleared"] ?? "Selection cleared")
|
|
337
|
+
: `${count} ${crispMessages.selected ?? "selected"}`);
|
|
300
338
|
// Two different defaults on purpose. FORMATTING falls back to "en-US", which
|
|
301
339
|
// is what the built-in date/number/currency renderers have always produced,
|
|
302
340
|
// so no existing output moves. SORTING falls back to `undefined`, which is
|
|
@@ -390,7 +428,7 @@ export function DataTable({ columns: columnsProp, data, selectable = false, sele
|
|
|
390
428
|
const [selectionText, setSelectionText] = React.useState("");
|
|
391
429
|
if (prevSelectedCount.current !== selected.size) {
|
|
392
430
|
prevSelectedCount.current = selected.size;
|
|
393
|
-
setSelectionText(
|
|
431
|
+
setSelectionText(resolvedSelectionAnnouncement(selected.size));
|
|
394
432
|
}
|
|
395
433
|
const [editing, setEditing] = React.useState(null);
|
|
396
434
|
// Set only while an async `onEdit` is in flight (#385) -- a sync `onEdit`
|
|
@@ -736,7 +774,7 @@ export function DataTable({ columns: columnsProp, data, selectable = false, sele
|
|
|
736
774
|
return dir === "asc" ? (_jsx(ArrowUp, { className: "crisp-datatable-chevron", size: 13, strokeWidth: 2 })) : (_jsx(ArrowDown, { className: "crisp-datatable-chevron", size: 13, strokeWidth: 2 }));
|
|
737
775
|
return (_jsx(ChevronsUpDown, { className: "crisp-datatable-chevron", size: 13, strokeWidth: 2 }));
|
|
738
776
|
}
|
|
739
|
-
return (_jsxs("div", { ref: gridRef, className: datatableRecipe({ className }), tabIndex: -1, onKeyDown: onGridKeyDown, ...rest, children: [_jsx("div", { className: "crisp-datatable-live", role: "status", "aria-live": "polite", children: selectionText }), (filterable || showCount || toolbar || columnMenu) && (_jsxs("div", { className: "crisp-datatable-toolbar", children: [columnMenu && (_jsxs(Menu, { align: "start", trigger: _jsxs("button", { type: "button", className: "crisp-datatable-viewsettings", children: [_jsx(SlidersHorizontal, { size: 14, strokeWidth: 1.75 }),
|
|
777
|
+
return (_jsxs("div", { ref: gridRef, className: datatableRecipe({ className }), tabIndex: -1, onKeyDown: onGridKeyDown, ...rest, children: [_jsx("div", { className: "crisp-datatable-live", role: "status", "aria-live": "polite", children: selectionText }), (filterable || showCount || toolbar || columnMenu) && (_jsxs("div", { className: "crisp-datatable-toolbar", children: [columnMenu && (_jsxs(Menu, { align: "start", trigger: _jsxs("button", { type: "button", className: "crisp-datatable-viewsettings", children: [_jsx(SlidersHorizontal, { size: 14, strokeWidth: 1.75 }), viewSettingsLabel] }), children: [_jsx("div", { className: "crisp-datatable-group-label", children: columnsGroupLabel }), allColumns.map((c) => (_jsxs(MenuItem, { onSelect: () => c.hidden ? showColumn(c.key) : hideColumn(c.key), children: [_jsx(Check, { size: 14, strokeWidth: 2, style: { opacity: c.hidden ? 0 : 1 } }), typeof c.header === "string" ? c.header : c.key] }, c.key)))] })), filterable && (_jsxs("span", { className: "crisp-datatable-search", children: [_jsx(Search, { size: 15, strokeWidth: 1.75, "aria-hidden": "true" }), _jsx("input", { type: "search", placeholder: searchPlaceholder, value: query, onChange: (e) => setQuery(e.target.value), "aria-label": searchLabel })] })), showCount && (_jsx("span", { className: "crisp-datatable-count", children: resolvedCountLabel(rows.length, data.length) })), toolbar] })), _jsxs(Table, { interactive: interactive, surface: surface, style: { minWidth }, scrollProps: {
|
|
740
778
|
ref: scrollRef,
|
|
741
779
|
onScroll: (e) => {
|
|
742
780
|
const el = e.currentTarget;
|
|
@@ -766,7 +804,7 @@ export function DataTable({ columns: columnsProp, data, selectable = false, sele
|
|
|
766
804
|
const inner = (_jsxs("span", { className: "crisp-datatable-th-inner", children: [Icon ? _jsx(Icon, { size: 16, strokeWidth: 1.75 }) : null, _jsx("span", { className: "crisp-datatable-th-label", children: col.header }), col.sortable ? _jsx(ChevronFor, { colKey: col.key }) : null] }));
|
|
767
805
|
return (_jsxs(TableHead, { align: alignFor(col), "aria-sort": ariaSort, ...dataStickyCol(i), children: [renaming === col.key ? (_jsx("input", {
|
|
768
806
|
// biome-ignore lint/a11y/noAutofocus: the rename item opens this field
|
|
769
|
-
autoFocus: true, className: "crisp-datatable-th-rename", defaultValue: typeof col.header === "string" ? col.header : "", "aria-label":
|
|
807
|
+
autoFocus: true, className: "crisp-datatable-th-rename", defaultValue: typeof col.header === "string" ? col.header : "", "aria-label": renameColumnLabel, onBlur: (e) => {
|
|
770
808
|
renameColumn(col.key, e.currentTarget.value);
|
|
771
809
|
setRenaming(null);
|
|
772
810
|
}, onKeyDown: (e) => {
|
|
@@ -778,7 +816,7 @@ export function DataTable({ columns: columnsProp, data, selectable = false, sele
|
|
|
778
816
|
setRenaming(null);
|
|
779
817
|
} })) : columnMenu ? (_jsxs(Menu, { align: "start", trigger: _jsx("button", { type: "button", className: "crisp-datatable-sort", "data-sorted": sortDirByKey.get(col.key), "aria-label": `${typeof col.header === "string"
|
|
780
818
|
? col.header
|
|
781
|
-
: col.key} column options`, children: inner }), children: [_jsx(MenuItem, { onSelect: () => setSort({ key: col.key, dir: "asc" }), children:
|
|
819
|
+
: col.key} ${crispMessages["column options"] ?? "column options"}`, children: inner }), children: [_jsx(MenuItem, { onSelect: () => setSort({ key: col.key, dir: "asc" }), children: sortAscendingLabel }), _jsx(MenuItem, { onSelect: () => setSort({ key: col.key, dir: "desc" }), children: sortDescendingLabel }), col.managed === false ? null : (_jsxs(_Fragment, { children: [_jsx(MenuItem, { onSelect: () => moveColumn(col.key, -1), children: moveColumnLeftLabel }), _jsx(MenuItem, { onSelect: () => moveColumn(col.key, 1), children: moveColumnRightLabel }), _jsx(MenuItem, { onSelect: () => setRenaming(col.key), children: editColumnLabel }), _jsx(MenuItem, { onSelect: () => hideColumn(col.key), children: hideColumnLabel })] }))] })) : col.sortable ? (_jsx("button", { type: "button", className: "crisp-datatable-sort", "data-sorted": sortDirByKey.get(col.key), onClick: () => cycleSort(col.key), children: inner })) : (inner), col.resizable !== false && (_jsx("span", { className: "crisp-datatable-resize", "data-resizing": resizingKey === col.key ? "" : undefined, onPointerDown: (e) => startResize(col, e), onPointerMove: moveResize, onPointerUp: endResize }))] }, col.key));
|
|
782
820
|
})] }) }), _jsx(TableBody, { children: error ? (_jsx(TableRow, { children: _jsx(TableCell, { colSpan: totalCols, children: _jsx("div", { className: "crisp-datatable-empty", children: error }) }) })) : rows.length === 0 ? (_jsx(TableRow, { children: _jsx(TableCell, { colSpan: totalCols, children: _jsx("div", { className: "crisp-datatable-empty", children: empty }) }) })) : (groupedRows.map(({ group, rows: groupRows }) => (_jsxs(React.Fragment, { children: [group !== null && (_jsx(TableRow, { children: _jsxs(TableCell, { colSpan: totalCols, className: "crisp-datatable-group", children: [_jsx("span", { className: "crisp-datatable-group-label", children: group }), _jsx("span", { className: "crisp-datatable-group-count", children: groupRows.length })] }) })), groupRows.map((row) => (_jsxs(TableRow, { "data-selected": selected.has(row.id) ? "" : undefined, "data-drop-target": showGrip &&
|
|
783
821
|
dragRowId &&
|
|
784
822
|
dragRowId !== row.id &&
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { ArrowDownWideNarrow, ArrowUpDown, ArrowUpNarrowWide, Check, CircleHelp, GripVertical, ListFilter, Plus, X, } from "lucide-react";
|
|
3
3
|
import * as React from "react";
|
|
4
|
-
import { isInsideLaterFloatingLayer, useFocusAfterRemoval } from "../../core/index.js";
|
|
4
|
+
import { isInsideLaterFloatingLayer, useCrispMessage, useCrispMessages, useFocusAfterRemoval, } from "../../core/index.js";
|
|
5
5
|
import { MenuCheckboxItem } from "../menu/index.js";
|
|
6
6
|
import { Select } from "../select/index.js";
|
|
7
7
|
import { tabletoolbarRecipe } from "./tabletoolbar.recipe.js";
|
|
@@ -117,7 +117,33 @@ export function applySorts(rows, sorts, get = (row, key) => row[key]) {
|
|
|
117
117
|
* this (Sort: Due date/Assignee/Creation date; Filter: Assignee/Record/
|
|
118
118
|
* Created by), which one shared list can't reproduce.
|
|
119
119
|
*/
|
|
120
|
-
export function TableToolbar({ columns, sortColumns: sortColumnsProp, filterColumns: filterColumnsProp, sorts: sortsProp, defaultSorts, onSortsChange, filters: filtersProp, defaultFilters, onFiltersChange, count, children, helpHref, className, sortLabel
|
|
120
|
+
export function TableToolbar({ columns, sortColumns: sortColumnsProp, filterColumns: filterColumnsProp, sorts: sortsProp, defaultSorts, onSortsChange, filters: filtersProp, defaultFilters, onFiltersChange, count, children, helpHref, className, sortLabel: sortLabelProp, filterLabel: filterLabelProp, reorderSortLabel: reorderSortLabelProp, noSortsLabel: noSortsLabelProp, addSortLabel: addSortLabelProp, learnSortingLabel: learnSortingLabelProp, selectConditionLabel: selectConditionLabelProp, sortFieldLabel: sortFieldLabelProp, sortDirectionLabel: sortDirectionLabelProp, removeSortLabel: removeSortLabelProp, filterFieldLabel: filterFieldLabelProp, filterFieldPlaceholder: filterFieldPlaceholderProp, filterValuePlaceholder: filterValuePlaceholderProp, ascendingLabel: ascendingLabelProp, descendingLabel: descendingLabelProp, sortTriggerLabel, filterTriggerLabel, inline, ...rest }) {
|
|
121
|
+
// crisp/#471: an explicit prop always wins; missing, it reads the nearest
|
|
122
|
+
// CrispMessagesProvider's translation of the English default below, or
|
|
123
|
+
// that default itself with no provider — unchanged from before this
|
|
124
|
+
// existed. One resolved constant per prop, same name every existing usage
|
|
125
|
+
// below already reads, so nothing past this block changes.
|
|
126
|
+
const sortLabel = useCrispMessage(sortLabelProp, "Sort");
|
|
127
|
+
const filterLabel = useCrispMessage(filterLabelProp, "Filter");
|
|
128
|
+
const reorderSortLabel = useCrispMessage(reorderSortLabelProp, "Reorder sort");
|
|
129
|
+
const noSortsLabel = useCrispMessage(noSortsLabelProp, "No sorts applied to this view.");
|
|
130
|
+
const addSortLabel = useCrispMessage(addSortLabelProp, "Add sort");
|
|
131
|
+
const learnSortingLabel = useCrispMessage(learnSortingLabelProp, "Learn about sorting");
|
|
132
|
+
const selectConditionLabel = useCrispMessage(selectConditionLabelProp, "Select condition");
|
|
133
|
+
const sortFieldLabel = useCrispMessage(sortFieldLabelProp, "Sort field");
|
|
134
|
+
const sortDirectionLabel = useCrispMessage(sortDirectionLabelProp, "Sort direction");
|
|
135
|
+
const removeSortLabel = useCrispMessage(removeSortLabelProp, "Remove sort");
|
|
136
|
+
const filterFieldLabel = useCrispMessage(filterFieldLabelProp, "Filter by field");
|
|
137
|
+
const filterFieldPlaceholder = useCrispMessage(filterFieldPlaceholderProp, "Filter by…");
|
|
138
|
+
const filterValuePlaceholder = useCrispMessage(filterValuePlaceholderProp, "Value…");
|
|
139
|
+
const ascendingLabel = useCrispMessage(ascendingLabelProp, "Ascending");
|
|
140
|
+
const descendingLabel = useCrispMessage(descendingLabelProp, "Descending");
|
|
141
|
+
// OP_LABELS' own words ("contains", "is", "is any of", …) have no prop of
|
|
142
|
+
// their own — a whole lookup table, not one string — so they translate
|
|
143
|
+
// straight from the provider's dictionary at the two spots they render
|
|
144
|
+
// (#628, #673 below), same fallback order as every resolved constant
|
|
145
|
+
// above.
|
|
146
|
+
const crispMessages = useCrispMessages();
|
|
121
147
|
// Each side falls back to the shared `columns` independently — a caller
|
|
122
148
|
// that only overrides one (Filter's own Record/Created by, say) still
|
|
123
149
|
// gets `columns` on the other, unchanged from before either prop existed.
|
|
@@ -206,12 +232,18 @@ export function TableToolbar({ columns, sortColumns: sortColumnsProp, filterColu
|
|
|
206
232
|
};
|
|
207
233
|
const activeFilters = filters.length;
|
|
208
234
|
// The default phrase only works in English word order, so a caller can
|
|
209
|
-
// replace the whole thing rather than fill a template
|
|
235
|
+
// replace the whole thing rather than fill a template — crisp/#471's
|
|
236
|
+
// `sortTriggerLabel` formatter is exactly that escape hatch. Short of a
|
|
237
|
+
// full formatter, the "Sorted by" fragment itself still translates (the
|
|
238
|
+
// sort trigger reading "Sort" in Mongolian and "Sorted by X" in English is
|
|
239
|
+
// worse than an imperfect word order); `crispMessages` rather than a
|
|
240
|
+
// second `useCrispMessage` call, since this only fires on the branch that
|
|
241
|
+
// needs it.
|
|
210
242
|
const sortedLabel = sortTriggerLabel
|
|
211
243
|
? sortTriggerLabel(sorts, sortColumns)
|
|
212
244
|
: sorts.length
|
|
213
|
-
?
|
|
214
|
-
:
|
|
245
|
+
? `${crispMessages["Sorted by"] ?? "Sorted by"} ${sortColumns.find((c) => c.key === sorts[0].key)?.label}${sorts.length > 1 ? ` +${sorts.length - 1}` : ""}`
|
|
246
|
+
: sortLabel;
|
|
215
247
|
return (_jsx("div", { className: tabletoolbarRecipe({ className }), ...rest, children: _jsxs("div", { className: "crisp-tabletoolbar-toolbar", "data-inline": inline ? "" : undefined, children: [_jsxs("span", { className: "crisp-tabletoolbar-toolgroup", children: [_jsxs("button", { type: "button", className: `crisp-tabletoolbar-tool${sorts.length ? " crisp-tabletoolbar-tool--active" : ""}`, onClick: () => setOpenMenu(openMenu === "sort" ? null : "sort"), "aria-haspopup": "dialog", "aria-expanded": openMenu === "sort", "data-menu-trigger": true, children: [_jsx(ArrowUpDown, { size: 14, strokeWidth: 1.75, "aria-hidden": "true" }), sortedLabel] }), openMenu === "sort" && (_jsxs("div", { ref: sortListRef, className: "crisp-tabletoolbar-menu crisp-tabletoolbar-sortpop", role: "dialog", "aria-label": sortLabel, children: [sorts.length === 0 ? (_jsx("div", { className: "crisp-tabletoolbar-sort-empty", children: noSortsLabel })) : (sorts.map((s, i) => (_jsxs("div", { className: "crisp-tabletoolbar-sort-row", onDragOver: (e) => {
|
|
216
248
|
if (dragSort !== null)
|
|
217
249
|
e.preventDefault();
|
|
@@ -237,8 +269,8 @@ export function TableToolbar({ columns, sortColumns: sortColumnsProp, filterColu
|
|
|
237
269
|
], "aria-label": sortDirectionLabel }), _jsx("button", { type: "button", className: "crisp-tabletoolbar-sort-remove", onClick: () => removeSort(i), "aria-label": removeSortLabel, children: _jsx(X, { size: 15, strokeWidth: 1.75 }) })] }, s.key)))), _jsx("div", { className: "crisp-tabletoolbar-sort-addrow", children: _jsxs("button", { ref: addSortRef, type: "button", className: "crisp-tabletoolbar-sort-add", onClick: addSort, children: [_jsx(Plus, { size: 15, strokeWidth: 1.75 }), addSortLabel] }) }), helpHref && (_jsx("div", { className: "crisp-tabletoolbar-sort-foot", children: _jsxs("a", { className: "crisp-tabletoolbar-sort-learn", href: helpHref, target: "_blank", rel: "noreferrer", children: [_jsx(CircleHelp, { size: 14, strokeWidth: 1.75 }), learnSortingLabel] }) }))] }))] }), _jsx("span", { className: "crisp-tabletoolbar-divider", "aria-hidden": "true" }), _jsxs("span", { className: "crisp-tabletoolbar-toolgroup", children: [_jsxs("button", { type: "button", className: `crisp-tabletoolbar-tool crisp-tabletoolbar-tool--ghost${activeFilters ? " crisp-tabletoolbar-tool--active" : ""}`, onClick: () => setOpenMenu(openMenu === "filter" ? null : "filter"), "aria-haspopup": "dialog", "aria-expanded": openMenu === "filter", "data-menu-trigger": true, children: [_jsx(ListFilter, { size: 14, strokeWidth: 1.75, "aria-hidden": "true" }), filterTriggerLabel
|
|
238
270
|
? filterTriggerLabel(activeFilters)
|
|
239
271
|
: activeFilters
|
|
240
|
-
?
|
|
241
|
-
:
|
|
272
|
+
? `${filterLabel} · ${activeFilters}`
|
|
273
|
+
: filterLabel] }), openMenu === "filter" && (_jsxs("div", { className: "crisp-tabletoolbar-menu", role: "dialog", "aria-label": filterFieldLabel, children: [_jsx("input", { className: "crisp-tabletoolbar-menu-input", placeholder: filterFieldPlaceholder, "aria-label": filterFieldLabel,
|
|
242
274
|
// biome-ignore lint/a11y/noAutofocus: focus the picker on open
|
|
243
275
|
autoFocus: true, value: filterQuery, onChange: (e) => setFilterQuery(e.target.value) }), filterColumns
|
|
244
276
|
.filter((col) => col.label
|
|
@@ -249,7 +281,8 @@ export function TableToolbar({ columns, sortColumns: sortColumnsProp, filterColu
|
|
|
249
281
|
const open = openMenu === `fcond:${cond.id}`;
|
|
250
282
|
const hasValue = (!opNeedsValue(cond.op) || cond.value.trim()) &&
|
|
251
283
|
(!opNeedsValues(cond.op) || (cond.values?.length ?? 0) > 0);
|
|
252
|
-
return (_jsxs("span", { className: "crisp-tabletoolbar-toolgroup", children: [_jsxs("span", { className: "crisp-tabletoolbar-fchip", children: [_jsxs("button", { type: "button", className: "crisp-tabletoolbar-fchip-main", "data-menu-trigger": true, "aria-haspopup": "dialog", onClick: () => setOpenMenu(open ? null : `fcond:${cond.id}`), children: [_jsx("span", { className: "crisp-tabletoolbar-fchip-field", children: col?.label }), hasValue ? (_jsxs(_Fragment, { children: [_jsx("span", { className: "crisp-tabletoolbar-fchip-op", children: OP_LABELS[cond.op]
|
|
284
|
+
return (_jsxs("span", { className: "crisp-tabletoolbar-toolgroup", children: [_jsxs("span", { className: "crisp-tabletoolbar-fchip", children: [_jsxs("button", { type: "button", className: "crisp-tabletoolbar-fchip-main", "data-menu-trigger": true, "aria-haspopup": "dialog", onClick: () => setOpenMenu(open ? null : `fcond:${cond.id}`), children: [_jsx("span", { className: "crisp-tabletoolbar-fchip-field", children: col?.label }), hasValue ? (_jsxs(_Fragment, { children: [_jsx("span", { className: "crisp-tabletoolbar-fchip-op", children: crispMessages[OP_LABELS[cond.op]] ??
|
|
285
|
+
OP_LABELS[cond.op] }), opNeedsValue(cond.op) && (_jsx("span", { className: "crisp-tabletoolbar-fchip-val", children: cond.value })), cond.op === "isanyof" && cond.values && (_jsx("span", { className: "crisp-tabletoolbar-fchip-val", children: isAnyOfSummary(cond.values) }))] })) : (_jsx("span", { className: "crisp-tabletoolbar-fchip-op", children: selectConditionLabel }))] }), _jsx("button", { type: "button", className: "crisp-tabletoolbar-fchip-x", "aria-label": `${crispMessages.Remove ?? "Remove"} ${col?.label} ${crispMessages.filter ?? "filter"}`, onClick: () => removeFilter(cond.id), children: _jsx(X, { size: 13, strokeWidth: 2 }) })] }), open && (_jsxs("div", { className: "crisp-tabletoolbar-menu", role: "dialog", "aria-label": `${col?.label} ${crispMessages.condition ?? "condition"}`, children: [_jsxs("div", { className: "crisp-tabletoolbar-menu-head", children: [col?.label, " ", crispMessages.condition ?? "condition"] }), opsFor(col).map((op) => (_jsxs("button", { type: "button", className: "crisp-tabletoolbar-menu-item", "data-on": cond.op === op ? "" : undefined, onClick: () => patchFilter(cond.id, { op }), children: [crispMessages[OP_LABELS[op]] ?? OP_LABELS[op], cond.op === op ? (_jsx(Check, { className: "crisp-tabletoolbar-menu-check", size: 15, strokeWidth: 1.75 })) : null] }, op))), opNeedsValue(cond.op) && (_jsx("input", { className: "crisp-tabletoolbar-menu-input", placeholder: filterValuePlaceholder, "aria-label": `${col?.label} filter value`,
|
|
253
286
|
// biome-ignore lint/a11y/noAutofocus: focus the value field
|
|
254
287
|
autoFocus: true, value: cond.value, onChange: (e) => patchFilter(cond.id, { value: e.target.value }) })), cond.op === "isanyof" && col?.values && (_jsxs(_Fragment, { children: [_jsx("input", { className: "crisp-tabletoolbar-menu-input", placeholder: filterValuePlaceholder, "aria-label": `${col?.label} filter values`,
|
|
255
288
|
// biome-ignore lint/a11y/noAutofocus: focus the search field
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import * as React from "react";
|
|
3
|
+
const CrispMessagesContext = React.createContext({});
|
|
4
|
+
export function CrispMessagesProvider({ messages, children, }) {
|
|
5
|
+
return (_jsx(CrispMessagesContext.Provider, { value: messages, children: children }));
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* Resolves one translatable string for a component: `explicit` is whatever
|
|
9
|
+
* the caller passed for that prop (`undefined` when they didn't), and
|
|
10
|
+
* `englishDefault` is the literal that shipped as that prop's default value
|
|
11
|
+
* before crisp/#471 — still what renders with no provider in the tree.
|
|
12
|
+
*
|
|
13
|
+
* A plain function, not a second context read inside a loop or a render
|
|
14
|
+
* helper — components call this once per string, the same shape
|
|
15
|
+
* `useLabelAssociation`/`useFormReset` and this codebase's other small
|
|
16
|
+
* `core/` hooks already take.
|
|
17
|
+
*/
|
|
18
|
+
export function useCrispMessage(explicit, englishDefault) {
|
|
19
|
+
const messages = React.useContext(CrispMessagesContext);
|
|
20
|
+
if (explicit !== undefined)
|
|
21
|
+
return explicit;
|
|
22
|
+
return messages[englishDefault] ?? englishDefault;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* The same resolution as `useCrispMessage`, for a component that builds its
|
|
26
|
+
* own default text internally (TableToolbar's "Sorted by {field}", a
|
|
27
|
+
* DatePicker's month/weekday names) rather than taking one string prop per
|
|
28
|
+
* piece — every literal fragment of that built-in text still needs its own
|
|
29
|
+
* translation lookup, and a component with many such fragments (twelve
|
|
30
|
+
* month names, seven weekday names) would otherwise call `useCrispMessage`
|
|
31
|
+
* in a loop, which breaks the rules of hooks. `useCrispMessages` returns the
|
|
32
|
+
* raw dictionary once; call sites do their own `dict[english] ?? english`
|
|
33
|
+
* per fragment, same fallback order, no hook-in-a-loop.
|
|
34
|
+
*/
|
|
35
|
+
export function useCrispMessages() {
|
|
36
|
+
return React.useContext(CrispMessagesContext);
|
|
37
|
+
}
|
package/dist/esm/core/index.js
CHANGED
|
@@ -7,6 +7,7 @@ export { isRtlElement } from "./direction.js";
|
|
|
7
7
|
export { FloatingPortal } from "./floating-portal.js";
|
|
8
8
|
export { hasOpenFloatingLayer, isInsideLaterFloatingLayer, registerFloatingLayer, } from "./floating-stack.js";
|
|
9
9
|
export { formValueStyle } from "./form-value.js";
|
|
10
|
+
export { CrispMessagesProvider, useCrispMessage, useCrispMessages, } from "./i18n.js";
|
|
10
11
|
export { matchMentionTrigger, } from "./mention.js";
|
|
11
12
|
export { placeFloating, } from "./place-floating.js";
|
|
12
13
|
export { Primitive } from "./primitive.js";
|
package/dist/styles.css
CHANGED
|
@@ -320,7 +320,7 @@
|
|
|
320
320
|
&:disabled { opacity: 0.5; cursor: not-allowed; }
|
|
321
321
|
}
|
|
322
322
|
.crisp-button--intent_primary {
|
|
323
|
-
background: var(--crisp-bg-brand-solid); color: var(--crisp-fg-on-brand); box-shadow: inset 0 0 0 1px rgba(
|
|
323
|
+
background: var(--crisp-bg-brand-solid); color: var(--crisp-fg-on-brand); box-shadow: inset 0 0 0 1px rgba(0,0,0,0.06), 0 2px 4px -2px rgba(38,109,240,0.12), 0 3px 6px -2px rgba(38,109,240,0.08);
|
|
324
324
|
&:hover { background: var(--crisp-bg-brand-solid-pressed); }
|
|
325
325
|
&:active { background: var(--crisp-bg-brand-solid-pressed); }
|
|
326
326
|
}
|
|
@@ -329,7 +329,7 @@
|
|
|
329
329
|
&:hover { background: var(--crisp-bg-raised); }
|
|
330
330
|
}
|
|
331
331
|
.crisp-button--intent_danger {
|
|
332
|
-
background: var(--crisp-bg-danger-solid); color: var(--crisp-fg-on-brand); box-shadow: inset 0 0 0 1px rgba(
|
|
332
|
+
background: var(--crisp-bg-danger-solid); color: var(--crisp-fg-on-brand); box-shadow: inset 0 0 0 1px rgba(0,0,0,0.06), 0 2px 4px -2px rgba(186,37,37,0.20), 0 3px 6px -2px rgba(186,37,37,0.12);
|
|
333
333
|
&:hover { filter: brightness(0.96); }
|
|
334
334
|
&:active { filter: brightness(0.92); }
|
|
335
335
|
}
|
package/dist/styles.layered.css
CHANGED
|
@@ -321,7 +321,7 @@
|
|
|
321
321
|
&:disabled { opacity: 0.5; cursor: not-allowed; }
|
|
322
322
|
}
|
|
323
323
|
.crisp-button--intent_primary {
|
|
324
|
-
background: var(--crisp-bg-brand-solid); color: var(--crisp-fg-on-brand); box-shadow: inset 0 0 0 1px rgba(
|
|
324
|
+
background: var(--crisp-bg-brand-solid); color: var(--crisp-fg-on-brand); box-shadow: inset 0 0 0 1px rgba(0,0,0,0.06), 0 2px 4px -2px rgba(38,109,240,0.12), 0 3px 6px -2px rgba(38,109,240,0.08);
|
|
325
325
|
&:hover { background: var(--crisp-bg-brand-solid-pressed); }
|
|
326
326
|
&:active { background: var(--crisp-bg-brand-solid-pressed); }
|
|
327
327
|
}
|
|
@@ -330,7 +330,7 @@
|
|
|
330
330
|
&:hover { background: var(--crisp-bg-raised); }
|
|
331
331
|
}
|
|
332
332
|
.crisp-button--intent_danger {
|
|
333
|
-
background: var(--crisp-bg-danger-solid); color: var(--crisp-fg-on-brand); box-shadow: inset 0 0 0 1px rgba(
|
|
333
|
+
background: var(--crisp-bg-danger-solid); color: var(--crisp-fg-on-brand); box-shadow: inset 0 0 0 1px rgba(0,0,0,0.06), 0 2px 4px -2px rgba(186,37,37,0.20), 0 3px 6px -2px rgba(186,37,37,0.12);
|
|
334
334
|
&:hover { filter: brightness(0.96); }
|
|
335
335
|
&:active { filter: brightness(0.92); }
|
|
336
336
|
}
|
|
@@ -27,7 +27,7 @@ declare const spec: {
|
|
|
27
27
|
readonly decls: {
|
|
28
28
|
readonly background: `var(--${string})`;
|
|
29
29
|
readonly color: `var(--${string})`;
|
|
30
|
-
readonly boxShadow: "inset 0 0 0 1px rgba(
|
|
30
|
+
readonly boxShadow: "inset 0 0 0 1px rgba(0,0,0,0.06), 0 2px 4px -2px rgba(38,109,240,0.12), 0 3px 6px -2px rgba(38,109,240,0.08)";
|
|
31
31
|
};
|
|
32
32
|
readonly parts: [{
|
|
33
33
|
readonly selector: "&:hover";
|
|
@@ -58,7 +58,7 @@ declare const spec: {
|
|
|
58
58
|
readonly decls: {
|
|
59
59
|
readonly background: `var(--${string})`;
|
|
60
60
|
readonly color: `var(--${string})`;
|
|
61
|
-
readonly boxShadow: "inset 0 0 0 1px rgba(
|
|
61
|
+
readonly boxShadow: "inset 0 0 0 1px rgba(0,0,0,0.06), 0 2px 4px -2px rgba(186,37,37,0.20), 0 3px 6px -2px rgba(186,37,37,0.12)";
|
|
62
62
|
};
|
|
63
63
|
readonly parts: [{
|
|
64
64
|
readonly selector: "&:hover";
|
|
@@ -144,6 +144,26 @@ export type DataTableProps<T extends {
|
|
|
144
144
|
* copy of the column list).
|
|
145
145
|
*/
|
|
146
146
|
columnMenu?: boolean;
|
|
147
|
+
/** crisp/#471: the column header menu's own six strings — no `labels`
|
|
148
|
+
* object for these (unlike RecordPage/RecordsTable) because each is
|
|
149
|
+
* independent, single-purpose text with an obvious name of its own, the
|
|
150
|
+
* same shape `selectAllLabel`/`reorderRowLabel` above already take. */
|
|
151
|
+
sortAscendingLabel?: string;
|
|
152
|
+
sortDescendingLabel?: string;
|
|
153
|
+
moveColumnLeftLabel?: string;
|
|
154
|
+
moveColumnRightLabel?: string;
|
|
155
|
+
/** The header menu's "Edit column label" item (opens the rename input). */
|
|
156
|
+
editColumnLabel?: string;
|
|
157
|
+
hideColumnLabel?: string;
|
|
158
|
+
/** aria-label on the rename input itself, once "Edit column label" opens
|
|
159
|
+
* it — separate from `editColumnLabel` above, which is the menu item
|
|
160
|
+
* that opens it, not the input. */
|
|
161
|
+
renameColumnLabel?: string;
|
|
162
|
+
/** The "View settings" trigger that opens the column-visibility menu
|
|
163
|
+
* (only rendered when `columnMenu` is set). */
|
|
164
|
+
viewSettingsLabel?: string;
|
|
165
|
+
/** That menu's own "Columns" section heading. */
|
|
166
|
+
columnsGroupLabel?: string;
|
|
147
167
|
/** Called with the next column list after a move, rename or hide. */
|
|
148
168
|
onColumnsChange?: (columns: DataTableColumn<T>[]) => void;
|
|
149
169
|
/** Group rows under a heading row, the way a grouped view does. */
|
|
@@ -224,7 +244,7 @@ export type DataTableProps<T extends {
|
|
|
224
244
|
};
|
|
225
245
|
export declare function DataTable<T extends {
|
|
226
246
|
id: string;
|
|
227
|
-
}>({ columns: columnsProp, data, selectable, selectedIds, onSelectionChange, selectAllLabel, selectRowLabel, selectionAnnouncement, columnMenu, onColumnsChange, groupBy, onRowsReorder, error, sort: sortProp, defaultSort, onSortChange, filterable, searchPlaceholder, searchLabel, onEdit, stickyFirst, stickyLast, locale: localeProp, avatarTone, surface, interactive, onRowClick, onAddRow, addRowLabel, empty, showCount, countLabel, toolbar, className, reorderRowLabel, ...rest }: DataTableProps<T>): React.JSX.Element;
|
|
247
|
+
}>({ columns: columnsProp, data, selectable, selectedIds, onSelectionChange, selectAllLabel: selectAllLabelProp, selectRowLabel, selectionAnnouncement, columnMenu, onColumnsChange, groupBy, onRowsReorder, error, sort: sortProp, defaultSort, onSortChange, filterable, searchPlaceholder: searchPlaceholderProp, searchLabel: searchLabelProp, onEdit, stickyFirst, stickyLast, locale: localeProp, avatarTone, surface, interactive, onRowClick, onAddRow, addRowLabel: addRowLabelProp, empty: emptyProp, showCount, countLabel, toolbar, className, reorderRowLabel: reorderRowLabelProp, sortAscendingLabel: sortAscendingLabelProp, sortDescendingLabel: sortDescendingLabelProp, moveColumnLeftLabel: moveColumnLeftLabelProp, moveColumnRightLabel: moveColumnRightLabelProp, editColumnLabel: editColumnLabelProp, hideColumnLabel: hideColumnLabelProp, renameColumnLabel: renameColumnLabelProp, viewSettingsLabel: viewSettingsLabelProp, columnsGroupLabel: columnsGroupLabelProp, ...rest }: DataTableProps<T>): React.JSX.Element;
|
|
228
248
|
export declare namespace DataTable {
|
|
229
249
|
var displayName: string;
|
|
230
250
|
}
|
|
@@ -74,6 +74,10 @@ export type TableToolbarProps = Omit<React.ComponentPropsWithoutRef<"div">, "chi
|
|
|
74
74
|
helpHref?: string;
|
|
75
75
|
/** aria-label on the Sort trigger (default "Sort"). */
|
|
76
76
|
sortLabel?: string;
|
|
77
|
+
/** Text on the Filter trigger when `filterTriggerLabel` isn't set and no
|
|
78
|
+
* filter is active (default "Filter") — crisp/#471: TableToolbar had no
|
|
79
|
+
* override for this at all before, unlike every other label here. */
|
|
80
|
+
filterLabel?: string;
|
|
77
81
|
/** aria-label on each sort row's drag grip (default "Reorder sort"). */
|
|
78
82
|
reorderSortLabel?: string;
|
|
79
83
|
/** aria-label on a sort row's field select (default "Sort field"). */
|
|
@@ -135,7 +139,7 @@ export declare function applySorts<T>(rows: T[], sorts: ToolbarSort[], get?: (ro
|
|
|
135
139
|
* this (Sort: Due date/Assignee/Creation date; Filter: Assignee/Record/
|
|
136
140
|
* Created by), which one shared list can't reproduce.
|
|
137
141
|
*/
|
|
138
|
-
export declare function TableToolbar({ columns, sortColumns: sortColumnsProp, filterColumns: filterColumnsProp, sorts: sortsProp, defaultSorts, onSortsChange, filters: filtersProp, defaultFilters, onFiltersChange, count, children, helpHref, className, sortLabel, reorderSortLabel, noSortsLabel, addSortLabel, learnSortingLabel, selectConditionLabel, sortFieldLabel, sortDirectionLabel, removeSortLabel, filterFieldLabel, filterFieldPlaceholder, filterValuePlaceholder, ascendingLabel, descendingLabel, sortTriggerLabel, filterTriggerLabel, inline, ...rest }: TableToolbarProps): React.JSX.Element;
|
|
142
|
+
export declare function TableToolbar({ columns, sortColumns: sortColumnsProp, filterColumns: filterColumnsProp, sorts: sortsProp, defaultSorts, onSortsChange, filters: filtersProp, defaultFilters, onFiltersChange, count, children, helpHref, className, sortLabel: sortLabelProp, filterLabel: filterLabelProp, reorderSortLabel: reorderSortLabelProp, noSortsLabel: noSortsLabelProp, addSortLabel: addSortLabelProp, learnSortingLabel: learnSortingLabelProp, selectConditionLabel: selectConditionLabelProp, sortFieldLabel: sortFieldLabelProp, sortDirectionLabel: sortDirectionLabelProp, removeSortLabel: removeSortLabelProp, filterFieldLabel: filterFieldLabelProp, filterFieldPlaceholder: filterFieldPlaceholderProp, filterValuePlaceholder: filterValuePlaceholderProp, ascendingLabel: ascendingLabelProp, descendingLabel: descendingLabelProp, sortTriggerLabel, filterTriggerLabel, inline, ...rest }: TableToolbarProps): React.JSX.Element;
|
|
139
143
|
export type ToolbarToolProps = React.ComponentPropsWithoutRef<"button"> & {
|
|
140
144
|
/** Drawn the way Sort/Filter are once a condition is set (#399). */
|
|
141
145
|
active?: boolean;
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
/**
|
|
3
|
+
* crisp/#471: a consumer app (internal/grc) ships a non-English UI and needs
|
|
4
|
+
* every string crisp itself renders — not just its own screens — to read in
|
|
5
|
+
* that language too. Every crisp component that shows its own text already
|
|
6
|
+
* takes that text as an optional prop with an English default (crisp/#74,
|
|
7
|
+
* crisp/#75, crisp/#204's own sweep: `sortLabel = "Sort"` and the like) — an
|
|
8
|
+
* explicit prop always wins, so nothing here changes what a consumer who
|
|
9
|
+
* passes one already gets. What's missing is a way to set every one of
|
|
10
|
+
* those defaults ONCE, at the app root, instead of threading a translated
|
|
11
|
+
* string into every call site of every component across a whole app.
|
|
12
|
+
*
|
|
13
|
+
* `CrispMessagesProvider` fills that gap. Its `messages` map is keyed by
|
|
14
|
+
* the component's own English default text — the exact literal already
|
|
15
|
+
* hardcoded at each call site — not a separate id vocabulary a consumer
|
|
16
|
+
* would have to learn or keep in sync. This is deliberate: internal/grc's
|
|
17
|
+
* own `lib/i18n.tsx` already built a flat `Record<string,string>` dictionary
|
|
18
|
+
* keyed the same way for its OWN UI chrome, so that dictionary is a valid
|
|
19
|
+
* `CrispMessages` value as-is — passed straight into this provider, with no
|
|
20
|
+
* remapping, it also translates every crisp-rendered string that happens to
|
|
21
|
+
* share an English key GRC already has an entry for.
|
|
22
|
+
*
|
|
23
|
+
* `useCrispMessage` is what a component calls for each of its own
|
|
24
|
+
* translatable strings: `useCrispMessage(sortLabelProp, "Sort")`. Resolution
|
|
25
|
+
* order is explicit prop, then the provider's translation of the built-in
|
|
26
|
+
* English default, then that default itself — so a consumer with no
|
|
27
|
+
* provider (or a provider missing that one key) sees exactly what shipped
|
|
28
|
+
* before this existed.
|
|
29
|
+
*/
|
|
30
|
+
export type CrispMessages = Record<string, string>;
|
|
31
|
+
export declare function CrispMessagesProvider({ messages, children, }: {
|
|
32
|
+
messages: CrispMessages;
|
|
33
|
+
children?: React.ReactNode;
|
|
34
|
+
}): React.ReactElement;
|
|
35
|
+
/**
|
|
36
|
+
* Resolves one translatable string for a component: `explicit` is whatever
|
|
37
|
+
* the caller passed for that prop (`undefined` when they didn't), and
|
|
38
|
+
* `englishDefault` is the literal that shipped as that prop's default value
|
|
39
|
+
* before crisp/#471 — still what renders with no provider in the tree.
|
|
40
|
+
*
|
|
41
|
+
* A plain function, not a second context read inside a loop or a render
|
|
42
|
+
* helper — components call this once per string, the same shape
|
|
43
|
+
* `useLabelAssociation`/`useFormReset` and this codebase's other small
|
|
44
|
+
* `core/` hooks already take.
|
|
45
|
+
*/
|
|
46
|
+
export declare function useCrispMessage(explicit: string | undefined, englishDefault: string): string;
|
|
47
|
+
/**
|
|
48
|
+
* The same resolution as `useCrispMessage`, for a component that builds its
|
|
49
|
+
* own default text internally (TableToolbar's "Sorted by {field}", a
|
|
50
|
+
* DatePicker's month/weekday names) rather than taking one string prop per
|
|
51
|
+
* piece — every literal fragment of that built-in text still needs its own
|
|
52
|
+
* translation lookup, and a component with many such fragments (twelve
|
|
53
|
+
* month names, seven weekday names) would otherwise call `useCrispMessage`
|
|
54
|
+
* in a loop, which breaks the rules of hooks. `useCrispMessages` returns the
|
|
55
|
+
* raw dictionary once; call sites do their own `dict[english] ?? english`
|
|
56
|
+
* per fragment, same fallback order, no hook-in-a-loop.
|
|
57
|
+
*/
|
|
58
|
+
export declare function useCrispMessages(): CrispMessages;
|
|
@@ -7,6 +7,7 @@ export { isRtlElement } from "./direction";
|
|
|
7
7
|
export { FloatingPortal } from "./floating-portal";
|
|
8
8
|
export { hasOpenFloatingLayer, isInsideLaterFloatingLayer, registerFloatingLayer, } from "./floating-stack";
|
|
9
9
|
export { formValueStyle } from "./form-value";
|
|
10
|
+
export { type CrispMessages, CrispMessagesProvider, useCrispMessage, useCrispMessages, } from "./i18n";
|
|
10
11
|
export { type MentionAnchor, type MentionMatch, matchMentionTrigger, } from "./mention";
|
|
11
12
|
export { type FloatingAlign, type PlaceFloatingOptions, placeFloating, } from "./place-floating";
|
|
12
13
|
export { Primitive } from "./primitive";
|