@crisp-ui-kit/crisp 0.52.6 → 0.53.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/breadcrumb.css +2 -1
- package/dist/breadcrumb.layered.css +2 -1
- package/dist/button.css +2 -2
- package/dist/button.layered.css +2 -2
- package/dist/cjs/components/breadcrumb/breadcrumb.js +5 -2
- package/dist/cjs/components/breadcrumb/breadcrumb.recipe.js +5 -2
- package/dist/cjs/components/button/button.recipe.js +10 -2
- package/dist/cjs/components/datatable/datatable.js +55 -10
- package/dist/cjs/components/datatable/datatable.recipe.js +2 -0
- package/dist/cjs/components/datepickerfield/datepickerfield.js +5 -1
- package/dist/cjs/components/dialog/dialog.js +2 -1
- package/dist/cjs/components/drawer/drawer.js +2 -1
- package/dist/cjs/components/multiselect/multiselect.js +5 -1
- package/dist/cjs/components/quantitypicker/quantitypicker.js +4 -1
- package/dist/cjs/components/select/select.js +7 -1
- package/dist/cjs/components/statcard/statcard.js +3 -2
- package/dist/cjs/components/statcard/statcard.recipe.js +10 -0
- package/dist/cjs/components/tabletoolbar/tabletoolbar.js +40 -7
- package/dist/cjs/components/toast/toast.js +3 -1
- package/dist/cjs/core/i18n.js +75 -0
- package/dist/cjs/core/index.js +5 -1
- package/dist/datatable.css +2 -0
- package/dist/datatable.layered.css +2 -0
- package/dist/esm/components/breadcrumb/breadcrumb.js +5 -2
- package/dist/esm/components/breadcrumb/breadcrumb.recipe.js +5 -2
- package/dist/esm/components/button/button.recipe.js +10 -2
- package/dist/esm/components/datatable/datatable.js +56 -11
- package/dist/esm/components/datatable/datatable.recipe.js +2 -0
- package/dist/esm/components/datepickerfield/datepickerfield.js +5 -1
- package/dist/esm/components/dialog/dialog.js +3 -2
- package/dist/esm/components/drawer/drawer.js +3 -2
- package/dist/esm/components/multiselect/multiselect.js +6 -2
- package/dist/esm/components/quantitypicker/quantitypicker.js +5 -2
- package/dist/esm/components/select/select.js +8 -2
- package/dist/esm/components/statcard/statcard.js +3 -2
- package/dist/esm/components/statcard/statcard.recipe.js +10 -0
- package/dist/esm/components/tabletoolbar/tabletoolbar.js +41 -8
- package/dist/esm/components/toast/toast.js +4 -2
- package/dist/esm/core/i18n.js +37 -0
- package/dist/esm/core/index.js +1 -0
- package/dist/statcard.css +8 -0
- package/dist/statcard.layered.css +8 -0
- package/dist/styles.css +14 -3
- package/dist/styles.layered.css +14 -3
- package/dist/types/components/breadcrumb/breadcrumb.d.ts +1 -1
- package/dist/types/components/button/button.recipe.d.ts +2 -2
- package/dist/types/components/datatable/datatable.d.ts +31 -1
- package/dist/types/components/datepickerfield/datepickerfield.d.ts +1 -1
- package/dist/types/components/dialog/dialog.d.ts +1 -1
- package/dist/types/components/drawer/drawer.d.ts +1 -1
- package/dist/types/components/multiselect/multiselect.d.ts +1 -1
- package/dist/types/components/quantitypicker/quantitypicker.d.ts +1 -1
- package/dist/types/components/select/select.d.ts +1 -1
- package/dist/types/components/statcard/statcard.d.ts +5 -1
- package/dist/types/components/tabletoolbar/tabletoolbar.d.ts +5 -1
- package/dist/types/components/toast/toast.d.ts +1 -1
- package/dist/types/core/i18n.d.ts +58 -0
- package/dist/types/core/index.d.ts +1 -0
- package/package.json +1 -1
|
@@ -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
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { CircleAlert, CircleCheck, Info, TriangleAlert, X } from "lucide-react";
|
|
3
3
|
import * as React from "react";
|
|
4
|
-
import { useFocusAfterRemoval } from "../../core/index.js";
|
|
4
|
+
import { useCrispMessage, useFocusAfterRemoval } from "../../core/index.js";
|
|
5
5
|
import { toastRecipe } from "./toast.recipe.js";
|
|
6
6
|
const ToastContext = React.createContext(null);
|
|
7
7
|
const ICONS = {
|
|
@@ -22,7 +22,9 @@ function ToastCard({ item, onClose, dismissLabel, }) {
|
|
|
22
22
|
* anywhere to push a notification. Toasts stack bottom-right and auto-dismiss
|
|
23
23
|
* after `duration` (default 4s; 0 keeps them until dismissed).
|
|
24
24
|
*/
|
|
25
|
-
export function ToastProvider({ children, duration = 4000, dismissLabel
|
|
25
|
+
export function ToastProvider({ children, duration = 4000, dismissLabel: dismissLabelProp, regionLabel: regionLabelProp, }) {
|
|
26
|
+
const dismissLabel = useCrispMessage(dismissLabelProp, "Dismiss");
|
|
27
|
+
const regionLabel = useCrispMessage(regionLabelProp, "Notifications");
|
|
26
28
|
const [items, setItems] = React.useState([]);
|
|
27
29
|
const idRef = React.useRef(0);
|
|
28
30
|
const timers = React.useRef(new Map());
|
|
@@ -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/statcard.css
CHANGED
|
@@ -4,6 +4,14 @@
|
|
|
4
4
|
& .crisp-statcard-label svg { color: var(--crisp-fg-muted); }
|
|
5
5
|
& .crisp-statcard-value { font-size: 28px; line-height: 32px; font-weight: var(--crisp-weight-semibold); letter-spacing: -0.4px; color: var(--crisp-fg-primary); }
|
|
6
6
|
& .crisp-statcard-foot { display: inline-flex; align-items: center; gap: var(--crisp-space-1-5); font-size: var(--crisp-text-sm); color: var(--crisp-fg-muted); }
|
|
7
|
+
/* caption's own status colour, opt-in via data-tone: not a new measured
|
|
8
|
+
value — the same fg*Soft tokens Badge's tone already uses (and Badge's
|
|
9
|
+
own doc comment records why: a dedicated soft-text token, not the
|
|
10
|
+
tone's accent colour, is the one that clears AA). No "ok" reading on Badge itself; fgSuccessSoft is Badge's own "success" tone's text colour, reused here under the name a caption's own good-news state
|
|
11
|
+
reads as. */
|
|
12
|
+
& .crisp-statcard-caption[data-tone="ok"] { color: var(--crisp-fg-success-soft); }
|
|
13
|
+
& .crisp-statcard-caption[data-tone="warning"] { color: var(--crisp-fg-warning-soft); }
|
|
14
|
+
& .crisp-statcard-caption[data-tone="critical"] { color: var(--crisp-fg-danger-soft); }
|
|
7
15
|
& .crisp-statcard-delta { display: inline-flex; align-items: center; gap: 2px; height: 20px; padding: 0 var(--crisp-space-1); border-radius: var(--crisp-radius-sm); font-size: var(--crisp-text-sm); font-weight: var(--crisp-weight-medium); }
|
|
8
16
|
& .crisp-statcard-delta svg { flex-shrink: 0; }
|
|
9
17
|
& .crisp-statcard-delta[data-trend="up"] { background: color-mix(in srgb, var(--crisp-bg-success) 16%, transparent); color: color-mix(in srgb, var(--crisp-bg-success) 80%, var(--crisp-fg-primary)); }
|
|
@@ -5,6 +5,14 @@
|
|
|
5
5
|
& .crisp-statcard-label svg { color: var(--crisp-fg-muted); }
|
|
6
6
|
& .crisp-statcard-value { font-size: 28px; line-height: 32px; font-weight: var(--crisp-weight-semibold); letter-spacing: -0.4px; color: var(--crisp-fg-primary); }
|
|
7
7
|
& .crisp-statcard-foot { display: inline-flex; align-items: center; gap: var(--crisp-space-1-5); font-size: var(--crisp-text-sm); color: var(--crisp-fg-muted); }
|
|
8
|
+
/* caption's own status colour, opt-in via data-tone: not a new measured
|
|
9
|
+
value — the same fg*Soft tokens Badge's tone already uses (and Badge's
|
|
10
|
+
own doc comment records why: a dedicated soft-text token, not the
|
|
11
|
+
tone's accent colour, is the one that clears AA). No "ok" reading on Badge itself; fgSuccessSoft is Badge's own "success" tone's text colour, reused here under the name a caption's own good-news state
|
|
12
|
+
reads as. */
|
|
13
|
+
& .crisp-statcard-caption[data-tone="ok"] { color: var(--crisp-fg-success-soft); }
|
|
14
|
+
& .crisp-statcard-caption[data-tone="warning"] { color: var(--crisp-fg-warning-soft); }
|
|
15
|
+
& .crisp-statcard-caption[data-tone="critical"] { color: var(--crisp-fg-danger-soft); }
|
|
8
16
|
& .crisp-statcard-delta { display: inline-flex; align-items: center; gap: 2px; height: 20px; padding: 0 var(--crisp-space-1); border-radius: var(--crisp-radius-sm); font-size: var(--crisp-text-sm); font-weight: var(--crisp-weight-medium); }
|
|
9
17
|
& .crisp-statcard-delta svg { flex-shrink: 0; }
|
|
10
18
|
& .crisp-statcard-delta[data-trend="up"] { background: color-mix(in srgb, var(--crisp-bg-success) 16%, transparent); color: color-mix(in srgb, var(--crisp-bg-success) 80%, var(--crisp-fg-primary)); }
|
package/dist/styles.css
CHANGED
|
@@ -286,7 +286,8 @@
|
|
|
286
286
|
& a.crisp-breadcrumb-item:hover, & button.crisp-breadcrumb-item:hover { color: var(--crisp-fg-primary); background: color-mix(in srgb, var(--crisp-fg-primary) 5%, transparent); }
|
|
287
287
|
& button.crisp-breadcrumb-item { border: none; background: none; font: inherit; cursor: pointer; }
|
|
288
288
|
& .crisp-breadcrumb-item[aria-current] { color: var(--crisp-fg-primary); font-weight: var(--crisp-weight-medium); }
|
|
289
|
-
& .crisp-breadcrumb-sep { flex-shrink: 0;
|
|
289
|
+
& .crisp-breadcrumb-sep { flex-shrink: 0; display: inline-flex; }
|
|
290
|
+
& .crisp-breadcrumb-sep-dot { flex-shrink: 0; width: 3px; height: 10px; border-radius: var(--crisp-radius-full); background: color-mix(in srgb, var(--crisp-fg-primary) 28%, transparent); }
|
|
290
291
|
[dir="rtl"] & [data-rtl-flip] { transform: scaleX(-1); }
|
|
291
292
|
}
|
|
292
293
|
|
|
@@ -320,7 +321,7 @@
|
|
|
320
321
|
&:disabled { opacity: 0.5; cursor: not-allowed; }
|
|
321
322
|
}
|
|
322
323
|
.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(
|
|
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);
|
|
324
325
|
&:hover { background: var(--crisp-bg-brand-solid-pressed); }
|
|
325
326
|
&:active { background: var(--crisp-bg-brand-solid-pressed); }
|
|
326
327
|
}
|
|
@@ -329,7 +330,7 @@
|
|
|
329
330
|
&:hover { background: var(--crisp-bg-raised); }
|
|
330
331
|
}
|
|
331
332
|
.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(
|
|
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);
|
|
333
334
|
&:hover { filter: brightness(0.96); }
|
|
334
335
|
&:active { filter: brightness(0.92); }
|
|
335
336
|
}
|
|
@@ -638,6 +639,8 @@
|
|
|
638
639
|
/* typed cells — entity (avatar + name), colour-coded tags, numbers, dates */
|
|
639
640
|
& .crisp-datatable-entity { display: flex; align-items: center; gap: var(--crisp-space-1-5); min-width: 0; color: var(--crisp-fg-primary); font-weight: var(--crisp-weight-medium); }
|
|
640
641
|
& .crisp-datatable-entity-name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
642
|
+
& .crisp-datatable-entity:hover .crisp-datatable-entity-name { text-decoration: underline; }
|
|
643
|
+
& button.crisp-datatable-entity-name--link { margin: 0; padding: 0; border: none; background: none; font: inherit; color: inherit; letter-spacing: inherit; text-align: start; cursor: pointer; max-width: 100%; }
|
|
641
644
|
& .crisp-datatable-avatar { width: var(--crisp-size-icon); height: var(--crisp-size-icon); border-radius: var(--crisp-radius-sm); flex-shrink: 0; display: inline-flex; align-items: center; justify-content: center; font-size: 10px; line-height: 1; font-weight: var(--crisp-weight-semibold); text-transform: uppercase; color: var(--crisp-fg-on-brand); background: var(--crisp-bg-brand-solid); }
|
|
642
645
|
& .crisp-datatable-avatar[data-tone="success"] { background: var(--crisp-bg-success); }
|
|
643
646
|
& .crisp-datatable-avatar[data-tone="warning"] { background: var(--crisp-bg-warning); }
|
|
@@ -1541,6 +1544,14 @@
|
|
|
1541
1544
|
& .crisp-statcard-label svg { color: var(--crisp-fg-muted); }
|
|
1542
1545
|
& .crisp-statcard-value { font-size: 28px; line-height: 32px; font-weight: var(--crisp-weight-semibold); letter-spacing: -0.4px; color: var(--crisp-fg-primary); }
|
|
1543
1546
|
& .crisp-statcard-foot { display: inline-flex; align-items: center; gap: var(--crisp-space-1-5); font-size: var(--crisp-text-sm); color: var(--crisp-fg-muted); }
|
|
1547
|
+
/* caption's own status colour, opt-in via data-tone: not a new measured
|
|
1548
|
+
value — the same fg*Soft tokens Badge's tone already uses (and Badge's
|
|
1549
|
+
own doc comment records why: a dedicated soft-text token, not the
|
|
1550
|
+
tone's accent colour, is the one that clears AA). No "ok" reading on Badge itself; fgSuccessSoft is Badge's own "success" tone's text colour, reused here under the name a caption's own good-news state
|
|
1551
|
+
reads as. */
|
|
1552
|
+
& .crisp-statcard-caption[data-tone="ok"] { color: var(--crisp-fg-success-soft); }
|
|
1553
|
+
& .crisp-statcard-caption[data-tone="warning"] { color: var(--crisp-fg-warning-soft); }
|
|
1554
|
+
& .crisp-statcard-caption[data-tone="critical"] { color: var(--crisp-fg-danger-soft); }
|
|
1544
1555
|
& .crisp-statcard-delta { display: inline-flex; align-items: center; gap: 2px; height: 20px; padding: 0 var(--crisp-space-1); border-radius: var(--crisp-radius-sm); font-size: var(--crisp-text-sm); font-weight: var(--crisp-weight-medium); }
|
|
1545
1556
|
& .crisp-statcard-delta svg { flex-shrink: 0; }
|
|
1546
1557
|
& .crisp-statcard-delta[data-trend="up"] { background: color-mix(in srgb, var(--crisp-bg-success) 16%, transparent); color: color-mix(in srgb, var(--crisp-bg-success) 80%, var(--crisp-fg-primary)); }
|
package/dist/styles.layered.css
CHANGED
|
@@ -287,7 +287,8 @@
|
|
|
287
287
|
& a.crisp-breadcrumb-item:hover, & button.crisp-breadcrumb-item:hover { color: var(--crisp-fg-primary); background: color-mix(in srgb, var(--crisp-fg-primary) 5%, transparent); }
|
|
288
288
|
& button.crisp-breadcrumb-item { border: none; background: none; font: inherit; cursor: pointer; }
|
|
289
289
|
& .crisp-breadcrumb-item[aria-current] { color: var(--crisp-fg-primary); font-weight: var(--crisp-weight-medium); }
|
|
290
|
-
& .crisp-breadcrumb-sep { flex-shrink: 0;
|
|
290
|
+
& .crisp-breadcrumb-sep { flex-shrink: 0; display: inline-flex; }
|
|
291
|
+
& .crisp-breadcrumb-sep-dot { flex-shrink: 0; width: 3px; height: 10px; border-radius: var(--crisp-radius-full); background: color-mix(in srgb, var(--crisp-fg-primary) 28%, transparent); }
|
|
291
292
|
[dir="rtl"] & [data-rtl-flip] { transform: scaleX(-1); }
|
|
292
293
|
}
|
|
293
294
|
|
|
@@ -321,7 +322,7 @@
|
|
|
321
322
|
&:disabled { opacity: 0.5; cursor: not-allowed; }
|
|
322
323
|
}
|
|
323
324
|
.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(
|
|
325
|
+
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
326
|
&:hover { background: var(--crisp-bg-brand-solid-pressed); }
|
|
326
327
|
&:active { background: var(--crisp-bg-brand-solid-pressed); }
|
|
327
328
|
}
|
|
@@ -330,7 +331,7 @@
|
|
|
330
331
|
&:hover { background: var(--crisp-bg-raised); }
|
|
331
332
|
}
|
|
332
333
|
.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(
|
|
334
|
+
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
335
|
&:hover { filter: brightness(0.96); }
|
|
335
336
|
&:active { filter: brightness(0.92); }
|
|
336
337
|
}
|
|
@@ -639,6 +640,8 @@
|
|
|
639
640
|
/* typed cells — entity (avatar + name), colour-coded tags, numbers, dates */
|
|
640
641
|
& .crisp-datatable-entity { display: flex; align-items: center; gap: var(--crisp-space-1-5); min-width: 0; color: var(--crisp-fg-primary); font-weight: var(--crisp-weight-medium); }
|
|
641
642
|
& .crisp-datatable-entity-name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
643
|
+
& .crisp-datatable-entity:hover .crisp-datatable-entity-name { text-decoration: underline; }
|
|
644
|
+
& button.crisp-datatable-entity-name--link { margin: 0; padding: 0; border: none; background: none; font: inherit; color: inherit; letter-spacing: inherit; text-align: start; cursor: pointer; max-width: 100%; }
|
|
642
645
|
& .crisp-datatable-avatar { width: var(--crisp-size-icon); height: var(--crisp-size-icon); border-radius: var(--crisp-radius-sm); flex-shrink: 0; display: inline-flex; align-items: center; justify-content: center; font-size: 10px; line-height: 1; font-weight: var(--crisp-weight-semibold); text-transform: uppercase; color: var(--crisp-fg-on-brand); background: var(--crisp-bg-brand-solid); }
|
|
643
646
|
& .crisp-datatable-avatar[data-tone="success"] { background: var(--crisp-bg-success); }
|
|
644
647
|
& .crisp-datatable-avatar[data-tone="warning"] { background: var(--crisp-bg-warning); }
|
|
@@ -1542,6 +1545,14 @@
|
|
|
1542
1545
|
& .crisp-statcard-label svg { color: var(--crisp-fg-muted); }
|
|
1543
1546
|
& .crisp-statcard-value { font-size: 28px; line-height: 32px; font-weight: var(--crisp-weight-semibold); letter-spacing: -0.4px; color: var(--crisp-fg-primary); }
|
|
1544
1547
|
& .crisp-statcard-foot { display: inline-flex; align-items: center; gap: var(--crisp-space-1-5); font-size: var(--crisp-text-sm); color: var(--crisp-fg-muted); }
|
|
1548
|
+
/* caption's own status colour, opt-in via data-tone: not a new measured
|
|
1549
|
+
value — the same fg*Soft tokens Badge's tone already uses (and Badge's
|
|
1550
|
+
own doc comment records why: a dedicated soft-text token, not the
|
|
1551
|
+
tone's accent colour, is the one that clears AA). No "ok" reading on Badge itself; fgSuccessSoft is Badge's own "success" tone's text colour, reused here under the name a caption's own good-news state
|
|
1552
|
+
reads as. */
|
|
1553
|
+
& .crisp-statcard-caption[data-tone="ok"] { color: var(--crisp-fg-success-soft); }
|
|
1554
|
+
& .crisp-statcard-caption[data-tone="warning"] { color: var(--crisp-fg-warning-soft); }
|
|
1555
|
+
& .crisp-statcard-caption[data-tone="critical"] { color: var(--crisp-fg-danger-soft); }
|
|
1545
1556
|
& .crisp-statcard-delta { display: inline-flex; align-items: center; gap: 2px; height: 20px; padding: 0 var(--crisp-space-1); border-radius: var(--crisp-radius-sm); font-size: var(--crisp-text-sm); font-weight: var(--crisp-weight-medium); }
|
|
1546
1557
|
& .crisp-statcard-delta svg { flex-shrink: 0; }
|
|
1547
1558
|
& .crisp-statcard-delta[data-trend="up"] { background: color-mix(in srgb, var(--crisp-bg-success) 16%, transparent); color: color-mix(in srgb, var(--crisp-bg-success) 80%, var(--crisp-fg-primary)); }
|
|
@@ -7,7 +7,7 @@ export type BreadcrumbItem = {
|
|
|
7
7
|
};
|
|
8
8
|
export type BreadcrumbProps = React.ComponentPropsWithoutRef<"nav"> & {
|
|
9
9
|
items: BreadcrumbItem[];
|
|
10
|
-
/** Separator between items (default a
|
|
10
|
+
/** Separator between items (default a small rounded divider — see below). */
|
|
11
11
|
separator?: React.ReactNode;
|
|
12
12
|
"aria-label"?: string;
|
|
13
13
|
className?: string;
|
|
@@ -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";
|
|
@@ -100,6 +100,16 @@ export type DataTableColumn<T> = {
|
|
|
100
100
|
managed?: boolean;
|
|
101
101
|
/** Hidden from the grid but still listed in the column manager. */
|
|
102
102
|
hidden?: boolean;
|
|
103
|
+
/** `type: "entity"` only (#501) — makes the cell's name its own click
|
|
104
|
+
* target, separate from the row's own `onRowClick`: the name renders as a
|
|
105
|
+
* `stopPropagation`-guarded button instead of a plain span, the same
|
|
106
|
+
* `crisp-datatable-entity-name--link` pattern RecordsTable's own
|
|
107
|
+
* `EntityCell.onOpenPage` already uses (`crisp-recordstable-entity-name
|
|
108
|
+
* --link`). For a cell whose entity points at a DIFFERENT record than the
|
|
109
|
+
* row itself (a reference/owner/approver column), not for navigating the
|
|
110
|
+
* row's own record — that's what `onRowClick` is for. Ignored on any
|
|
111
|
+
* other `type`. */
|
|
112
|
+
onEntityClick?: (row: T) => void;
|
|
103
113
|
};
|
|
104
114
|
export type SortState = {
|
|
105
115
|
key: string;
|
|
@@ -144,6 +154,26 @@ export type DataTableProps<T extends {
|
|
|
144
154
|
* copy of the column list).
|
|
145
155
|
*/
|
|
146
156
|
columnMenu?: boolean;
|
|
157
|
+
/** crisp/#471: the column header menu's own six strings — no `labels`
|
|
158
|
+
* object for these (unlike RecordPage/RecordsTable) because each is
|
|
159
|
+
* independent, single-purpose text with an obvious name of its own, the
|
|
160
|
+
* same shape `selectAllLabel`/`reorderRowLabel` above already take. */
|
|
161
|
+
sortAscendingLabel?: string;
|
|
162
|
+
sortDescendingLabel?: string;
|
|
163
|
+
moveColumnLeftLabel?: string;
|
|
164
|
+
moveColumnRightLabel?: string;
|
|
165
|
+
/** The header menu's "Edit column label" item (opens the rename input). */
|
|
166
|
+
editColumnLabel?: string;
|
|
167
|
+
hideColumnLabel?: string;
|
|
168
|
+
/** aria-label on the rename input itself, once "Edit column label" opens
|
|
169
|
+
* it — separate from `editColumnLabel` above, which is the menu item
|
|
170
|
+
* that opens it, not the input. */
|
|
171
|
+
renameColumnLabel?: string;
|
|
172
|
+
/** The "View settings" trigger that opens the column-visibility menu
|
|
173
|
+
* (only rendered when `columnMenu` is set). */
|
|
174
|
+
viewSettingsLabel?: string;
|
|
175
|
+
/** That menu's own "Columns" section heading. */
|
|
176
|
+
columnsGroupLabel?: string;
|
|
147
177
|
/** Called with the next column list after a move, rename or hide. */
|
|
148
178
|
onColumnsChange?: (columns: DataTableColumn<T>[]) => void;
|
|
149
179
|
/** Group rows under a heading row, the way a grouped view does. */
|
|
@@ -224,7 +254,7 @@ export type DataTableProps<T extends {
|
|
|
224
254
|
};
|
|
225
255
|
export declare function DataTable<T extends {
|
|
226
256
|
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;
|
|
257
|
+
}>({ 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
258
|
export declare namespace DataTable {
|
|
229
259
|
var displayName: string;
|
|
230
260
|
}
|
|
@@ -51,7 +51,7 @@ export type DatePickerFieldProps = Omit<React.ComponentPropsWithoutRef<"div">, "
|
|
|
51
51
|
* popover. This is what `DatePicker` used to be; `DatePicker` is now
|
|
52
52
|
* seed-design's bare calendar, and this composes it.
|
|
53
53
|
*/
|
|
54
|
-
export declare function DatePickerField({ value, defaultValue, onValueChange, onChange, placeholder, variant, format, clearable, clearLabel, presets, calendarProps, "aria-label": ariaLabel, className, ...rest }: DatePickerFieldProps): React.JSX.Element;
|
|
54
|
+
export declare function DatePickerField({ value, defaultValue, onValueChange, onChange, placeholder: placeholderProp, variant, format, clearable, clearLabel: clearLabelProp, presets, calendarProps, "aria-label": ariaLabel, className, ...rest }: DatePickerFieldProps): React.JSX.Element;
|
|
55
55
|
export declare namespace DatePickerField {
|
|
56
56
|
var displayName: string;
|
|
57
57
|
}
|
|
@@ -29,7 +29,7 @@ export type DialogProps = Omit<React.ComponentPropsWithoutRef<"div">, "title" |
|
|
|
29
29
|
* Escape / outside-click dismissal) with the measured modal surface.
|
|
30
30
|
* Compose the body with `DialogBody` and actions with `DialogFooter`.
|
|
31
31
|
*/
|
|
32
|
-
export declare function Dialog({ open, onOpenChange, title, icon, width, "aria-label": ariaLabel, closeLabel, children, className, style, "aria-describedby": describedBy, ...rest }: DialogProps): React.JSX.Element | null;
|
|
32
|
+
export declare function Dialog({ open, onOpenChange, title, icon, width, "aria-label": ariaLabel, closeLabel: closeLabelProp, children, className, style, "aria-describedby": describedBy, ...rest }: DialogProps): React.JSX.Element | null;
|
|
33
33
|
export declare namespace Dialog {
|
|
34
34
|
var displayName: string;
|
|
35
35
|
}
|
|
@@ -20,7 +20,7 @@ export type DrawerProps = Omit<React.ComponentPropsWithoutRef<"div">, "title" |
|
|
|
20
20
|
* Escape / outside-click dismissal) with a panel that slides in from the edge.
|
|
21
21
|
* Compose the body with `DrawerBody` and actions with `DrawerFooter`.
|
|
22
22
|
*/
|
|
23
|
-
export declare function Drawer({ open, onOpenChange, side, title, icon, "aria-label": ariaLabel, closeLabel, children, className, "aria-describedby": describedBy, ...rest }: DrawerProps): React.JSX.Element | null;
|
|
23
|
+
export declare function Drawer({ open, onOpenChange, side, title, icon, "aria-label": ariaLabel, closeLabel: closeLabelProp, children, className, "aria-describedby": describedBy, ...rest }: DrawerProps): React.JSX.Element | null;
|
|
24
24
|
export declare namespace Drawer {
|
|
25
25
|
var displayName: string;
|
|
26
26
|
}
|
|
@@ -56,7 +56,7 @@ export type MultiSelectProps = Omit<React.ComponentPropsWithoutRef<"div">, "valu
|
|
|
56
56
|
* popover with a search field over rows of "[chip] … [checkbox]" that toggle on
|
|
57
57
|
* click (the menu stays open). Keyboard: ↑/↓ move, Enter toggles, Esc closes.
|
|
58
58
|
*/
|
|
59
|
-
export declare function MultiSelect({ options, value: valueProp, defaultValue, onValueChange, name, required, id, placeholder, searchable, searchPlaceholder, emptyLabel, disabled, invalid, className, "aria-label": ariaLabel, ...rest }: MultiSelectProps): React.JSX.Element;
|
|
59
|
+
export declare function MultiSelect({ options, value: valueProp, defaultValue, onValueChange, name, required, id, placeholder: placeholderProp, searchable, searchPlaceholder: searchPlaceholderProp, emptyLabel: emptyLabelProp, disabled, invalid, className, "aria-label": ariaLabel, ...rest }: MultiSelectProps): React.JSX.Element;
|
|
60
60
|
export declare namespace MultiSelect {
|
|
61
61
|
var displayName: string;
|
|
62
62
|
}
|
|
@@ -37,7 +37,7 @@ export type QuantityPickerProps = QuantityPickerRootProps & {
|
|
|
37
37
|
* Slider documents. Here it also decides child ORDER, since the engine
|
|
38
38
|
* reverses its visible children rather than relying on CSS.
|
|
39
39
|
*/
|
|
40
|
-
export declare function QuantityPicker({ size, fill, className, name, decrementLabel, incrementLabel, ...props }: QuantityPickerProps): React.JSX.Element;
|
|
40
|
+
export declare function QuantityPicker({ size, fill, className, name, decrementLabel: decrementLabelProp, incrementLabel: incrementLabelProp, ...props }: QuantityPickerProps): React.JSX.Element;
|
|
41
41
|
export declare namespace QuantityPicker {
|
|
42
42
|
var displayName: string;
|
|
43
43
|
}
|
|
@@ -69,7 +69,7 @@ export type SelectProps = Omit<React.ComponentPropsWithoutRef<"div">, "value" |
|
|
|
69
69
|
* "No results" empty state). Full keyboard model — ↑/↓ move, Enter/Space pick,
|
|
70
70
|
* Esc close, Home/End jump, opens upward when tight. Controlled or uncontrolled.
|
|
71
71
|
*/
|
|
72
|
-
export declare function Select({ options, value, defaultValue, onValueChange, name, required, placeholder, variant, width, searchable, searchPlaceholder, onSearchChange, emptyLabel, disabled, invalid, className, "aria-label": ariaLabel, id, ...rest }: SelectProps): React.JSX.Element;
|
|
72
|
+
export declare function Select({ options, value, defaultValue, onValueChange, name, required, placeholder: placeholderProp, variant, width, searchable, searchPlaceholder: searchPlaceholderProp, onSearchChange, emptyLabel: emptyLabelProp, disabled, invalid, className, "aria-label": ariaLabel, id, ...rest }: SelectProps): React.JSX.Element;
|
|
73
73
|
export declare namespace Select {
|
|
74
74
|
var displayName: string;
|
|
75
75
|
}
|
|
@@ -12,6 +12,10 @@ export type StatCardProps = React.ComponentPropsWithoutRef<"div"> & {
|
|
|
12
12
|
deltaSuffix?: string;
|
|
13
13
|
/** Caption after the delta (e.g. "vs last month"). */
|
|
14
14
|
caption?: React.ReactNode;
|
|
15
|
+
/** Status colour for `caption` (e.g. "3 without a granter" in red) —
|
|
16
|
+
* independent of `delta`'s own up/down colouring, for a caption that
|
|
17
|
+
* reads a state rather than a trend. Unset keeps the caption muted. */
|
|
18
|
+
captionTone?: "ok" | "warning" | "critical";
|
|
15
19
|
/** Opt-in bordered card chrome (padding + border + radius), for use
|
|
16
20
|
* standalone instead of hand-wrapping in `<Card><CardBody>`. Default
|
|
17
21
|
* `false` — StatCard renders bare, as measured (pair with `Card` yourself
|
|
@@ -23,7 +27,7 @@ export type StatCardProps = React.ComponentPropsWithoutRef<"div"> & {
|
|
|
23
27
|
* A single-metric KPI tile — measured from a reports number/insight card.
|
|
24
28
|
* Label, a large value, and an optional up/down delta pill with a caption.
|
|
25
29
|
*/
|
|
26
|
-
export declare function StatCard({ label, icon, value, delta, deltaSuffix, caption, card, className, ...rest }: StatCardProps): React.JSX.Element;
|
|
30
|
+
export declare function StatCard({ label, icon, value, delta, deltaSuffix, caption, captionTone, card, className, ...rest }: StatCardProps): React.JSX.Element;
|
|
27
31
|
export declare namespace StatCard {
|
|
28
32
|
var displayName: string;
|
|
29
33
|
}
|
|
@@ -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;
|
|
@@ -20,7 +20,7 @@ type ToastApi = {
|
|
|
20
20
|
* anywhere to push a notification. Toasts stack bottom-right and auto-dismiss
|
|
21
21
|
* after `duration` (default 4s; 0 keeps them until dismissed).
|
|
22
22
|
*/
|
|
23
|
-
export declare function ToastProvider({ children, duration, dismissLabel, regionLabel, }: {
|
|
23
|
+
export declare function ToastProvider({ children, duration, dismissLabel: dismissLabelProp, regionLabel: regionLabelProp, }: {
|
|
24
24
|
children: React.ReactNode;
|
|
25
25
|
duration?: number;
|
|
26
26
|
/** aria-label on each toast's dismiss button (default "Dismiss"). */
|
|
@@ -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";
|