@crisp-ui-kit/crisp 0.53.2 → 0.53.4
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/cjs/components/breadcrumb/breadcrumb.js +6 -1
- package/dist/cjs/components/columncalc/columncalc.js +13 -4
- package/dist/cjs/components/composewindow/composewindow.js +5 -1
- package/dist/cjs/components/contextmenu/contextmenu.js +3 -1
- package/dist/cjs/components/filldoc/filldoc.js +6 -1
- package/dist/cjs/components/heatmapchart/heatmapchart.js +24 -0
- package/dist/cjs/components/heatmapchart/heatmapchart.recipe.js +49 -0
- package/dist/cjs/components/heatmapchart/index.js +17 -0
- package/dist/cjs/components/spinner/spinner.js +4 -1
- package/dist/cjs/components/toast/toast.recipe.js +8 -4
- package/dist/cjs/core/i18n.js +17 -2
- package/dist/cjs/core/index.js +2 -1
- package/dist/cjs/index.js +2 -0
- package/dist/cjs/product/recordpage/recordpage.js +17 -2
- package/dist/cjs/product/recordstable/recordstable.js +14 -3
- package/dist/cjs/product/richtext/richtext.js +33 -4
- package/dist/cjs/product/statlist/index.js +17 -0
- package/dist/cjs/product/statlist/statlist.js +20 -0
- package/dist/cjs/product/statlist/statlist.recipe.js +32 -0
- package/dist/esm/components/breadcrumb/breadcrumb.js +6 -1
- package/dist/esm/components/columncalc/columncalc.js +13 -4
- package/dist/esm/components/composewindow/composewindow.js +6 -2
- package/dist/esm/components/contextmenu/contextmenu.js +4 -2
- package/dist/esm/components/filldoc/filldoc.js +7 -2
- package/dist/esm/components/heatmapchart/heatmapchart.js +21 -0
- package/dist/esm/components/heatmapchart/heatmapchart.recipe.js +46 -0
- package/dist/esm/components/heatmapchart/index.js +1 -0
- package/dist/esm/components/spinner/spinner.js +4 -1
- package/dist/esm/components/toast/toast.recipe.js +9 -5
- package/dist/esm/core/i18n.js +16 -2
- package/dist/esm/core/index.js +1 -1
- package/dist/esm/index.js +2 -0
- package/dist/esm/product/recordpage/recordpage.js +18 -3
- package/dist/esm/product/recordstable/recordstable.js +15 -4
- package/dist/esm/product/richtext/richtext.js +33 -4
- package/dist/esm/product/statlist/index.js +1 -0
- package/dist/esm/product/statlist/statlist.js +17 -0
- package/dist/esm/product/statlist/statlist.recipe.js +29 -0
- package/dist/heatmapchart.css +27 -0
- package/dist/heatmapchart.d.ts +7 -0
- package/dist/heatmapchart.layered.css +29 -0
- package/dist/statlist.css +8 -0
- package/dist/statlist.d.ts +7 -0
- package/dist/statlist.layered.css +10 -0
- package/dist/styles.css +39 -2
- package/dist/styles.layered.css +39 -2
- package/dist/toast.css +2 -2
- package/dist/toast.layered.css +2 -2
- package/dist/types/components/breadcrumb/breadcrumb.d.ts +1 -1
- package/dist/types/components/columncalc/columncalc.d.ts +1 -1
- package/dist/types/components/composewindow/composewindow.d.ts +1 -1
- package/dist/types/components/contextmenu/contextmenu.d.ts +1 -1
- package/dist/types/components/heatmapchart/heatmapchart.d.ts +51 -0
- package/dist/types/components/heatmapchart/heatmapchart.recipe.d.ts +3 -0
- package/dist/types/components/heatmapchart/index.d.ts +1 -0
- package/dist/types/components/spinner/spinner.d.ts +1 -1
- package/dist/types/core/i18n.d.ts +30 -1
- package/dist/types/core/index.d.ts +1 -1
- package/dist/types/index.d.ts +2 -0
- package/dist/types/product/statlist/index.d.ts +1 -0
- package/dist/types/product/statlist/statlist.d.ts +34 -0
- package/dist/types/product/statlist/statlist.recipe.d.ts +3 -0
- package/package.json +11 -1
|
@@ -36,12 +36,17 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
36
36
|
exports.Breadcrumb = Breadcrumb;
|
|
37
37
|
const jsx_runtime_1 = require("react/jsx-runtime");
|
|
38
38
|
const React = __importStar(require("react"));
|
|
39
|
+
const core_1 = require("../../core");
|
|
39
40
|
const breadcrumb_recipe_1 = require("./breadcrumb.recipe");
|
|
40
41
|
/**
|
|
41
42
|
* A breadcrumb trail — measured from a record-page nav. The last item is
|
|
42
43
|
* marked `aria-current="page"`; earlier items render as links or buttons.
|
|
43
44
|
*/
|
|
44
|
-
function Breadcrumb({ items, separator, "aria-label":
|
|
45
|
+
function Breadcrumb({ items, separator, "aria-label": ariaLabelProp, className, ...rest }) {
|
|
46
|
+
// crisp/#471: an explicit prop always wins; missing, it reads the nearest
|
|
47
|
+
// CrispMessagesProvider's translation of the English default, or that
|
|
48
|
+
// default itself with no provider — unchanged from before this existed.
|
|
49
|
+
const ariaLabel = (0, core_1.useCrispMessage)(ariaLabelProp, "Breadcrumb");
|
|
45
50
|
// Live-measured (app.attio.com/<ws>/home, /companies,
|
|
46
51
|
// /company/<id>/overview): a plain rounded divider, not a chevron -- a
|
|
47
52
|
// symmetric shape needs no RTL mirroring, unlike the ChevronRight this
|
|
@@ -40,6 +40,7 @@ exports.ColumnCalc = ColumnCalc;
|
|
|
40
40
|
const jsx_runtime_1 = require("react/jsx-runtime");
|
|
41
41
|
const lucide_react_1 = require("lucide-react");
|
|
42
42
|
const React = __importStar(require("react"));
|
|
43
|
+
const core_1 = require("../../core");
|
|
43
44
|
const menu_1 = require("../menu");
|
|
44
45
|
const columncalc_recipe_1 = require("./columncalc.recipe");
|
|
45
46
|
/** The four calcs offered on every column, in their order. */
|
|
@@ -117,10 +118,18 @@ function computeCalc(values, op) {
|
|
|
117
118
|
const defaultFormat = (n, op) => op === "percentEmpty" || op === "percentFilled"
|
|
118
119
|
? `${Math.round(n)}%`
|
|
119
120
|
: n.toLocaleString(undefined, { maximumFractionDigits: 2 });
|
|
120
|
-
function ColumnCalc({ rows, value, numeric = false, defaultOp = "none", op: opProp, onOpChange, addLabel
|
|
121
|
-
//
|
|
122
|
-
//
|
|
123
|
-
|
|
121
|
+
function ColumnCalc({ rows, value, numeric = false, defaultOp = "none", op: opProp, onOpChange, addLabel: addLabelProp, sectionLabel: sectionLabelProp, format = defaultFormat, align, opLabels, className, ...rest }) {
|
|
122
|
+
// crisp/#471: an explicit prop always wins; missing, it reads the nearest
|
|
123
|
+
// CrispMessagesProvider's translation of the English default, or that
|
|
124
|
+
// default itself with no provider — unchanged from before this existed.
|
|
125
|
+
const addLabel = (0, core_1.useCrispMessage)(addLabelProp, "Add calculation");
|
|
126
|
+
const sectionLabel = (0, core_1.useCrispMessage)(sectionLabelProp, "Aggregations");
|
|
127
|
+
const crispMessages = (0, core_1.useCrispMessages)();
|
|
128
|
+
// opLabels (a per-op partial override) wins, then the provider's own
|
|
129
|
+
// translation of CALC_LABEL's English default, then that default itself —
|
|
130
|
+
// a consumer can translate one op via opLabels without restating all
|
|
131
|
+
// nine, AND get the rest from a provider with no per-instance prop at all.
|
|
132
|
+
const labelFor = (o) => opLabels?.[o] ?? crispMessages[exports.CALC_LABEL[o]] ?? exports.CALC_LABEL[o];
|
|
124
133
|
const [internal, setInternal] = React.useState(defaultOp);
|
|
125
134
|
const op = opProp ?? internal;
|
|
126
135
|
const setOp = (next) => {
|
|
@@ -93,7 +93,11 @@ function useComposeWindowInstanceId() {
|
|
|
93
93
|
* measured screenshot. `trapFocus` follows the same call — a picture-in-
|
|
94
94
|
* picture window, not a modal, so Tab is not caught inside it.
|
|
95
95
|
*/
|
|
96
|
-
function ComposeWindow({ open, onOpenChange, breadcrumb, minimized: minimizedProp, defaultMinimized, onMinimizedChange, pinned: pinnedProp, defaultPinned, onPinnedChange, width = 968, "aria-label": ariaLabel, minimizeLabel
|
|
96
|
+
function ComposeWindow({ open, onOpenChange, breadcrumb, minimized: minimizedProp, defaultMinimized, onMinimizedChange, pinned: pinnedProp, defaultPinned, onPinnedChange, width = 968, "aria-label": ariaLabel, minimizeLabel: minimizeLabelProp, pinLabel: pinLabelProp, closeLabel: closeLabelProp, children, className, style, "aria-describedby": describedBy, ...rest }) {
|
|
97
|
+
// crisp/#471: same resolution order as Breadcrumb's own.
|
|
98
|
+
const minimizeLabel = (0, core_1.useCrispMessage)(minimizeLabelProp, "Minimize");
|
|
99
|
+
const pinLabel = (0, core_1.useCrispMessage)(pinLabelProp, "Pin to screen");
|
|
100
|
+
const closeLabel = (0, core_1.useCrispMessage)(closeLabelProp, "Close");
|
|
97
101
|
const id = useComposeWindowInstanceId();
|
|
98
102
|
const [internalMinimized, setInternalMinimized] = React.useState(defaultMinimized ?? false);
|
|
99
103
|
const minimized = minimizedProp ?? internalMinimized;
|
|
@@ -45,7 +45,9 @@ const contextmenu_recipe_1 = require("./contextmenu.recipe");
|
|
|
45
45
|
* Wrap the target; right-clicking it opens the menu (flipping at the viewport
|
|
46
46
|
* edges), closing on select, outside pointer-down, and Escape.
|
|
47
47
|
*/
|
|
48
|
-
function ContextMenu({ children, items, menuLabel
|
|
48
|
+
function ContextMenu({ children, items, menuLabel: menuLabelProp, className, asChild = false, }) {
|
|
49
|
+
// crisp/#471: same resolution order as Breadcrumb's own.
|
|
50
|
+
const menuLabel = (0, core_1.useCrispMessage)(menuLabelProp, "Context menu");
|
|
49
51
|
const rootRef = React.useRef(null);
|
|
50
52
|
const menuRef = React.useRef(null);
|
|
51
53
|
const returnFocusTo = React.useRef(null);
|
|
@@ -72,7 +72,12 @@ const emptyTable = () => ({
|
|
|
72
72
|
* page covered in handles and grips stops looking like a page, which is the
|
|
73
73
|
* whole thing this is for.
|
|
74
74
|
*/
|
|
75
|
-
exports.FillDoc = React.forwardRef(function FillDoc({ blocks, onBlocksChange, mode = "edit", page = "a4", label, addTextLabel
|
|
75
|
+
exports.FillDoc = React.forwardRef(function FillDoc({ blocks, onBlocksChange, mode = "edit", page = "a4", label, addTextLabel: addTextLabelProp, addHeadingLabel: addHeadingLabelProp, addTableLabel: addTableLabelProp, removeLabel: removeLabelProp, className, style, ...rest }, forwardedRef) {
|
|
76
|
+
// crisp/#471: same resolution order as Breadcrumb's own.
|
|
77
|
+
const addTextLabel = (0, core_1.useCrispMessage)(addTextLabelProp, "Text");
|
|
78
|
+
const addHeadingLabel = (0, core_1.useCrispMessage)(addHeadingLabelProp, "Heading");
|
|
79
|
+
const addTableLabel = (0, core_1.useCrispMessage)(addTableLabelProp, "Table");
|
|
80
|
+
const removeLabel = (0, core_1.useCrispMessage)(removeLabelProp, "Remove");
|
|
76
81
|
const editable = mode === "edit";
|
|
77
82
|
const replace = (i, block) => {
|
|
78
83
|
const next = blocks.slice();
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.HeatmapChart = HeatmapChart;
|
|
4
|
+
const jsx_runtime_1 = require("react/jsx-runtime");
|
|
5
|
+
const heatmapchart_recipe_1 = require("./heatmapchart.recipe");
|
|
6
|
+
/**
|
|
7
|
+
* An NxM cell-matrix chart — GRC's own risk heatmap (impact x likelihood)
|
|
8
|
+
* is the first consumer. Severity/intensity mapping (`tone`, `ring`) is
|
|
9
|
+
* entirely the caller's own data, not a scale this component invents; see
|
|
10
|
+
* the recipe's own doc comment for why (and for its measurement — or lack
|
|
11
|
+
* of one — against Attio).
|
|
12
|
+
*/
|
|
13
|
+
function HeatmapChart({ data, rowAxis, colAxis, format = (cell) => (cell.value ? cell.value : ""), cellTitle, onCellClick, isCellClickable = (cell) => cell.value > 0, empty, emptyLabel = "Nothing to plot yet.", "aria-label": ariaLabel, className, ...rest }) {
|
|
14
|
+
const cols = data[0]?.length ?? 0;
|
|
15
|
+
const summary = ariaLabel ?? `Heatmap: ${data.length} x ${cols} grid`;
|
|
16
|
+
return ((0, jsx_runtime_1.jsx)("div", { className: (0, heatmapchart_recipe_1.heatmapchartRecipe)({ className }), ...rest, role: "img", "aria-label": summary, children: empty ? ((0, jsx_runtime_1.jsx)("div", { className: "crisp-heatmapchart-empty", children: emptyLabel })) : ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsxs)("div", { className: "crisp-heatmapchart-rowaxis", children: [(0, jsx_runtime_1.jsx)("span", { children: rowAxis.high }), rowAxis.title != null && ((0, jsx_runtime_1.jsx)("span", { className: "crisp-heatmapchart-rowaxis-title", children: rowAxis.title })), (0, jsx_runtime_1.jsx)("span", { children: rowAxis.low })] }), (0, jsx_runtime_1.jsxs)("div", { className: "crisp-heatmapchart-body", children: [(0, jsx_runtime_1.jsx)("div", { className: "crisp-heatmapchart-grid", style: { gridTemplateColumns: `repeat(${cols}, 1fr)` }, children: data.map((row, r) => row.map((cell, cc) => {
|
|
17
|
+
const clickable = onCellClick != null && isCellClickable(cell, r, cc);
|
|
18
|
+
const title = cellTitle?.(cell, r, cc);
|
|
19
|
+
const key = `${r}-${cc}`;
|
|
20
|
+
const tone = cell.tone ?? "neutral";
|
|
21
|
+
return clickable ? ((0, jsx_runtime_1.jsx)("button", { type: "button", className: "crisp-heatmapchart-cell", "data-tone": tone, "data-ring": cell.ring ? "" : undefined, title: title, onClick: () => onCellClick?.(r, cc, cell), children: format(cell, r, cc) }, key)) : ((0, jsx_runtime_1.jsx)("div", { className: "crisp-heatmapchart-cell", "data-tone": tone, "data-ring": cell.ring ? "" : undefined, title: title, children: format(cell, r, cc) }, key));
|
|
22
|
+
})) }), (0, jsx_runtime_1.jsxs)("div", { className: "crisp-heatmapchart-colaxis", children: [(0, jsx_runtime_1.jsx)("span", { children: colAxis.low }), colAxis.title != null && (0, jsx_runtime_1.jsx)("span", { children: colAxis.title }), (0, jsx_runtime_1.jsx)("span", { children: colAxis.high })] })] })] })) }));
|
|
23
|
+
}
|
|
24
|
+
HeatmapChart.displayName = "HeatmapChart";
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.heatmapchartRecipe = void 0;
|
|
4
|
+
const core_1 = require("../../core");
|
|
5
|
+
const tokens_1 = require("../../tokens");
|
|
6
|
+
const soft = (v, pct) => `color-mix(in srgb, ${v} ${pct}%, transparent)`;
|
|
7
|
+
// An NxM cell-matrix chart, alongside BarChart/LineChart/DonutChart — GRC's
|
|
8
|
+
// own risk heatmap (impact x likelihood) is the first consumer. No Attio
|
|
9
|
+
// widget in the workspace this was checked against (Reports/Dashboards:
|
|
10
|
+
// bar/line/number report tiles) has a matching matrix chart to measure a
|
|
11
|
+
// pixel value from, so this is composed entirely from already-measured
|
|
12
|
+
// crisp values: the same `color-mix` soft-tint recipe Badge's own `soft()`
|
|
13
|
+
// helper and StatCard's delta pill already use, at the same 14-16%,
|
|
14
|
+
// against the token contract's own success/warning/orange/danger roles.
|
|
15
|
+
// A cell's text stays `fgPrimary` at every tone — not tone-matched — the
|
|
16
|
+
// same "the background already carries the severity, tone-on-tone measures
|
|
17
|
+
// illegible" reasoning GRC's own hand-built heatmap already recorded.
|
|
18
|
+
const config = (0, core_1.defineRecipe)({
|
|
19
|
+
name: "heatmapchart",
|
|
20
|
+
base: `display: flex; gap: ${tokens_1.contract.space["2"]}; box-sizing: border-box; font-family: ${tokens_1.contract.font.sans};
|
|
21
|
+
-webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale;
|
|
22
|
+
|
|
23
|
+
& .crisp-heatmapchart-rowaxis { display: flex; flex-direction: column; justify-content: space-between; padding: ${tokens_1.contract.space["1"]} 0; font-size: ${tokens_1.contract.text.xs}; font-weight: ${tokens_1.contract.weight.medium}; text-transform: uppercase; letter-spacing: 0.02em; color: ${tokens_1.contract.fgNeutralSubtle}; }
|
|
24
|
+
& .crisp-heatmapchart-rowaxis-title { writing-mode: vertical-rl; transform: rotate(180deg); align-self: center; }
|
|
25
|
+
|
|
26
|
+
& .crisp-heatmapchart-body { flex: 1; }
|
|
27
|
+
& .crisp-heatmapchart-grid { display: grid; gap: ${tokens_1.contract.space["1.5"]}; }
|
|
28
|
+
|
|
29
|
+
& .crisp-heatmapchart-cell { box-sizing: border-box; display: flex; align-items: center; justify-content: center; aspect-ratio: 2 / 1; border-radius: ${tokens_1.contract.radius.md}; border: none; background: transparent; margin: 0; padding: 0; font-family: inherit; font-size: ${tokens_1.contract.text.md}; font-weight: ${tokens_1.contract.weight.semibold}; font-variant-numeric: tabular-nums; color: ${tokens_1.contract.fgPrimary}; }
|
|
30
|
+
& button.crisp-heatmapchart-cell { cursor: pointer; transition: filter 0.12s ease; }
|
|
31
|
+
& button.crisp-heatmapchart-cell:hover { filter: brightness(0.97); }
|
|
32
|
+
& button.crisp-heatmapchart-cell:focus-visible { outline: 2px solid ${tokens_1.contract.bgBrandSolid}; outline-offset: 1px; }
|
|
33
|
+
|
|
34
|
+
& .crisp-heatmapchart-cell[data-tone="neutral"] { background: ${tokens_1.contract.bgRaised}; color: ${tokens_1.contract.fgMuted}; }
|
|
35
|
+
& .crisp-heatmapchart-cell[data-tone="success"] { background: ${soft(tokens_1.contract.bgSuccess, 16)}; }
|
|
36
|
+
& .crisp-heatmapchart-cell[data-tone="warning"] { background: ${soft(tokens_1.contract.bgWarning, 16)}; }
|
|
37
|
+
& .crisp-heatmapchart-cell[data-tone="orange"] { background: ${soft(tokens_1.contract.bgOrange, 18)}; }
|
|
38
|
+
& .crisp-heatmapchart-cell[data-tone="danger"] { background: ${soft(tokens_1.contract.bgDanger, 16)}; }
|
|
39
|
+
& .crisp-heatmapchart-cell[data-ring] { box-shadow: inset 0 0 0 2px ${tokens_1.contract.bgDangerSolid}; }
|
|
40
|
+
|
|
41
|
+
& .crisp-heatmapchart-colaxis { display: flex; justify-content: space-between; margin-top: ${tokens_1.contract.space["1.5"]}; padding: 0 ${tokens_1.contract.space["0.5"]}; font-size: ${tokens_1.contract.text.xs}; font-weight: ${tokens_1.contract.weight.medium}; text-transform: uppercase; letter-spacing: 0.02em; color: ${tokens_1.contract.fgNeutralSubtle}; }
|
|
42
|
+
|
|
43
|
+
& .crisp-heatmapchart-empty { padding: ${tokens_1.contract.space["8"]} 0; text-align: center; font-size: ${tokens_1.contract.text.md}; color: ${tokens_1.contract.fgMuted}; }`,
|
|
44
|
+
variants: {},
|
|
45
|
+
defaultVariants: {},
|
|
46
|
+
compoundVariants: [],
|
|
47
|
+
});
|
|
48
|
+
exports.heatmapchartRecipe = (0, core_1.resolveRecipe)(config);
|
|
49
|
+
exports.default = config;
|
|
@@ -0,0 +1,17 @@
|
|
|
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 __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
14
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
|
+
};
|
|
16
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
+
__exportStar(require("./heatmapchart"), exports);
|
|
@@ -2,9 +2,12 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.Spinner = Spinner;
|
|
4
4
|
const jsx_runtime_1 = require("react/jsx-runtime");
|
|
5
|
+
const core_1 = require("../../core");
|
|
5
6
|
const spinner_recipe_1 = require("./spinner.recipe");
|
|
6
7
|
/** A spinning ring indicator. Reports `role="status"` for assistive tech. */
|
|
7
|
-
function Spinner({ size = 16, "aria-label":
|
|
8
|
+
function Spinner({ size = 16, "aria-label": ariaLabelProp, className, ...rest }) {
|
|
9
|
+
// crisp/#471: same resolution order as Breadcrumb's own.
|
|
10
|
+
const ariaLabel = (0, core_1.useCrispMessage)(ariaLabelProp, "Loading");
|
|
8
11
|
return ((0, jsx_runtime_1.jsx)("span", { role: "status", "aria-label": ariaLabel, className: (0, spinner_recipe_1.spinnerRecipe)({ className }), ...rest, style: {
|
|
9
12
|
width: size,
|
|
10
13
|
height: size,
|
|
@@ -3,14 +3,18 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.toastRecipe = void 0;
|
|
4
4
|
const core_1 = require("../../core");
|
|
5
5
|
const tokens_1 = require("../../tokens");
|
|
6
|
-
// Toast notifications — a fixed
|
|
6
|
+
// Toast notifications — a fixed TOP-right stack of cards on the measured
|
|
7
7
|
// popover surface, each with a tone icon, title/description, optional action,
|
|
8
|
-
// and a close button.
|
|
8
|
+
// and a close button. Position, radius, padding, surface and shadow measured
|
|
9
|
+
// live off Attio's own "Copied to clipboard!" toast (crisp#515): the stack
|
|
10
|
+
// anchors top-right (not bottom-right), and the card shares Menu/Popover's
|
|
11
|
+
// `bgPopover` surface + `elevation.floating` shadow (Lesson 31 — reference
|
|
12
|
+
// the shared token, never a hand-typed near-duplicate).
|
|
9
13
|
const config = (0, core_1.defineRecipe)({
|
|
10
14
|
name: "toast",
|
|
11
15
|
base: `display: contents; font-family: ${tokens_1.contract.font.sans};
|
|
12
|
-
& .crisp-toast-viewport { position: fixed; z-index: 80; inset-inline-end: ${tokens_1.contract.space["4"]};
|
|
13
|
-
& .crisp-toast { pointer-events: auto; display: flex; align-items: flex-start; gap: ${tokens_1.contract.space["2.5"]}; box-sizing: border-box; padding: ${tokens_1.contract.space["
|
|
16
|
+
& .crisp-toast-viewport { position: fixed; z-index: 80; inset-inline-end: ${tokens_1.contract.space["4"]}; top: ${tokens_1.contract.space["4"]}; display: flex; flex-direction: column; gap: ${tokens_1.contract.space["2"]}; width: 360px; max-width: calc(100vw - ${tokens_1.contract.space["8"]}); pointer-events: none; }
|
|
17
|
+
& .crisp-toast { pointer-events: auto; display: flex; align-items: flex-start; gap: ${tokens_1.contract.space["2.5"]}; box-sizing: border-box; padding: ${tokens_1.contract.space["2.5"]}; border-radius: ${tokens_1.contract.radius.md}; background: ${tokens_1.contract.bgPopover}; box-shadow: ${tokens_1.elevation.floating}; opacity: 1; transform: translateY(0); transition: opacity 0.15s ease, transform 0.15s ease; @starting-style { opacity: 0; transform: translateY(8px); } }
|
|
14
18
|
& .crisp-toast-icon { flex-shrink: 0; margin-top: 1px; display: inline-flex; color: ${tokens_1.contract.fgMuted}; }
|
|
15
19
|
& .crisp-toast[data-tone="info"] .crisp-toast-icon { color: ${tokens_1.contract.bgBrandSolid}; }
|
|
16
20
|
& .crisp-toast[data-tone="success"] .crisp-toast-icon { color: ${tokens_1.contract.bgSuccess}; }
|
package/dist/cjs/core/i18n.js
CHANGED
|
@@ -36,11 +36,13 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
36
36
|
exports.CrispMessagesProvider = CrispMessagesProvider;
|
|
37
37
|
exports.useCrispMessage = useCrispMessage;
|
|
38
38
|
exports.useCrispMessages = useCrispMessages;
|
|
39
|
+
exports.useCrispComponentDefaults = useCrispComponentDefaults;
|
|
39
40
|
const jsx_runtime_1 = require("react/jsx-runtime");
|
|
40
41
|
const React = __importStar(require("react"));
|
|
41
42
|
const CrispMessagesContext = React.createContext({});
|
|
42
|
-
|
|
43
|
-
|
|
43
|
+
const CrispComponentDefaultsContext = React.createContext({});
|
|
44
|
+
function CrispMessagesProvider({ messages, componentDefaults, children, }) {
|
|
45
|
+
return ((0, jsx_runtime_1.jsx)(CrispMessagesContext.Provider, { value: messages, children: (0, jsx_runtime_1.jsx)(CrispComponentDefaultsContext.Provider, { value: componentDefaults ?? {}, children: children }) }));
|
|
44
46
|
}
|
|
45
47
|
/**
|
|
46
48
|
* Resolves one translatable string for a component: `explicit` is whatever
|
|
@@ -73,3 +75,16 @@ function useCrispMessage(explicit, englishDefault) {
|
|
|
73
75
|
function useCrispMessages() {
|
|
74
76
|
return React.useContext(CrispMessagesContext);
|
|
75
77
|
}
|
|
78
|
+
/**
|
|
79
|
+
* Reads one component's own whole-object defaults override, set via
|
|
80
|
+
* `CrispMessagesProvider`'s `componentDefaults` prop (see its own doc for
|
|
81
|
+
* why this exists separately from `useCrispMessage`). `T` is the
|
|
82
|
+
* component's own labels TYPE (e.g. `Partial<RecordPageLabels>`) — this
|
|
83
|
+
* hook itself only knows `name`, a plain string key; the caller supplies
|
|
84
|
+
* and trusts the real shape, the same way `useContext` always has for an
|
|
85
|
+
* untyped-at-the-provider value.
|
|
86
|
+
*/
|
|
87
|
+
function useCrispComponentDefaults(name) {
|
|
88
|
+
const all = React.useContext(CrispComponentDefaultsContext);
|
|
89
|
+
return all[name];
|
|
90
|
+
}
|
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.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;
|
|
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.useCrispComponentDefaults = 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");
|
|
@@ -39,6 +39,7 @@ var form_value_1 = require("./form-value");
|
|
|
39
39
|
Object.defineProperty(exports, "formValueStyle", { enumerable: true, get: function () { return form_value_1.formValueStyle; } });
|
|
40
40
|
var i18n_1 = require("./i18n");
|
|
41
41
|
Object.defineProperty(exports, "CrispMessagesProvider", { enumerable: true, get: function () { return i18n_1.CrispMessagesProvider; } });
|
|
42
|
+
Object.defineProperty(exports, "useCrispComponentDefaults", { enumerable: true, get: function () { return i18n_1.useCrispComponentDefaults; } });
|
|
42
43
|
Object.defineProperty(exports, "useCrispMessage", { enumerable: true, get: function () { return i18n_1.useCrispMessage; } });
|
|
43
44
|
Object.defineProperty(exports, "useCrispMessages", { enumerable: true, get: function () { return i18n_1.useCrispMessages; } });
|
|
44
45
|
var mention_1 = require("./mention");
|
package/dist/cjs/index.js
CHANGED
|
@@ -45,6 +45,7 @@ __exportStar(require("./components/fileupload"), exports);
|
|
|
45
45
|
__exportStar(require("./components/filldoc"), exports);
|
|
46
46
|
__exportStar(require("./components/fillsheet"), exports);
|
|
47
47
|
__exportStar(require("./components/formfield"), exports);
|
|
48
|
+
__exportStar(require("./components/heatmapchart"), exports);
|
|
48
49
|
__exportStar(require("./components/input"), exports);
|
|
49
50
|
__exportStar(require("./components/kbd"), exports);
|
|
50
51
|
__exportStar(require("./components/linechart"), exports);
|
|
@@ -94,6 +95,7 @@ __exportStar(require("./product/richtext"), exports);
|
|
|
94
95
|
__exportStar(require("./product/settingssection"), exports);
|
|
95
96
|
__exportStar(require("./product/settingsshell"), exports);
|
|
96
97
|
__exportStar(require("./product/sidebar"), exports);
|
|
98
|
+
__exportStar(require("./product/statlist"), exports);
|
|
97
99
|
__exportStar(require("./product/tasklist"), exports);
|
|
98
100
|
__exportStar(require("./product/trialbanner"), exports);
|
|
99
101
|
__exportStar(require("./product/viewtoggle"), exports);
|
|
@@ -257,19 +257,34 @@ function EmptyCard({ icon, children, }) {
|
|
|
257
257
|
*/
|
|
258
258
|
function RecordPage({ record, columns, collection = "Records", labels, onBack, onToggleFull, full, className, onEdit, tabs: tabsProp, defaultTab, notes: notesProp, onAddNote, tasks: tasksProp, onAddTask, onToggleTask, ...rest }) {
|
|
259
259
|
const { editingKey, pendingKey, error, openEditing, closeEditing, commit } = (0, recordstable_1.useEditableField)(onEdit);
|
|
260
|
+
// crisp/#471: a CrispMessagesProvider's `componentDefaults.recordPage` sits
|
|
261
|
+
// BETWEEN the built-in English defaults and this instance's own explicit
|
|
262
|
+
// `labels` prop — a provider translates every RecordPage on the page at
|
|
263
|
+
// once, and a caller who still passes `labels` for one instance keeps
|
|
264
|
+
// overriding it there, same as before this existed. Not `useCrispMessage`:
|
|
265
|
+
// several of these entries (`domainSetTo`, `sortedBy`, …) are formatter
|
|
266
|
+
// FUNCTIONS, which a flat string dictionary can't hold.
|
|
267
|
+
const providerLabels = (0, core_1.useCrispComponentDefaults)("recordPage");
|
|
260
268
|
const t = React.useMemo(() => ({
|
|
261
269
|
...exports.recordPageLabels,
|
|
270
|
+
...providerLabels,
|
|
262
271
|
...labels,
|
|
263
|
-
tabs: {
|
|
272
|
+
tabs: {
|
|
273
|
+
...exports.recordPageLabels.tabs,
|
|
274
|
+
...providerLabels?.tabs,
|
|
275
|
+
...labels?.tabs,
|
|
276
|
+
},
|
|
264
277
|
highlightLabels: {
|
|
265
278
|
...exports.recordPageLabels.highlightLabels,
|
|
279
|
+
...providerLabels?.highlightLabels,
|
|
266
280
|
...labels?.highlightLabels,
|
|
267
281
|
},
|
|
268
282
|
highlightEmpty: {
|
|
269
283
|
...exports.recordPageLabels.highlightEmpty,
|
|
284
|
+
...providerLabels?.highlightEmpty,
|
|
270
285
|
...labels?.highlightEmpty,
|
|
271
286
|
},
|
|
272
|
-
}), [labels]);
|
|
287
|
+
}), [labels, providerLabels]);
|
|
273
288
|
const [favorite, setFavorite] = React.useState(false);
|
|
274
289
|
// Notes/Tasks are controlled when the caller supplies `notes`/`tasks` (even
|
|
275
290
|
// `[]`) -- otherwise RecordPage keeps its own self-contained state, as
|
|
@@ -495,12 +495,23 @@ function RecordsTable({ labels, columns: columnsProp = exports.defaultColumns, r
|
|
|
495
495
|
const [filterConds, setFilterConds] = React.useState([]);
|
|
496
496
|
const [filterQuery, setFilterQuery] = React.useState("");
|
|
497
497
|
// one popover open at a time: "sort" | "filter" | "more" | `sum:<key>`
|
|
498
|
+
// crisp/#471: same "provider sits between the built-in default and this
|
|
499
|
+
// instance's own explicit labels prop" order as RecordPage's own — see
|
|
500
|
+
// its comment. Several entries here (`selectionAnnouncement`, `sortedBy`,
|
|
501
|
+
// `recordCount`, …) are formatter functions, which is why this goes
|
|
502
|
+
// through `useCrispComponentDefaults`, not `useCrispMessage`.
|
|
503
|
+
const providerLabels = (0, core_1.useCrispComponentDefaults)("recordsTable");
|
|
498
504
|
const t = React.useMemo(() => ({
|
|
499
505
|
...exports.recordsTableLabels,
|
|
506
|
+
...providerLabels,
|
|
500
507
|
...labels,
|
|
501
|
-
agg: {
|
|
502
|
-
|
|
503
|
-
|
|
508
|
+
agg: {
|
|
509
|
+
...exports.recordsTableLabels.agg,
|
|
510
|
+
...providerLabels?.agg,
|
|
511
|
+
...labels?.agg,
|
|
512
|
+
},
|
|
513
|
+
op: { ...exports.recordsTableLabels.op, ...providerLabels?.op, ...labels?.op },
|
|
514
|
+
}), [labels, providerLabels]);
|
|
504
515
|
const [openMenu, setOpenMenu] = React.useState(null);
|
|
505
516
|
// the element that opened the current menu, so Esc/select can hand focus back
|
|
506
517
|
const menuReturn = React.useRef(null);
|
|
@@ -170,6 +170,19 @@ exports.RichTextEditor = React.forwardRef(function RichTextEditor({ defaultTitle
|
|
|
170
170
|
// entirely", the same distinction `updateBar` already makes for the block
|
|
171
171
|
// menu's search input below.
|
|
172
172
|
const mentionPickerRef = React.useRef(null);
|
|
173
|
+
// Whether focus currently sits somewhere inside whatever
|
|
174
|
+
// `renderMentionPicker` returned (#514) — tracked through React's own
|
|
175
|
+
// onFocus/onBlur, not `mentionPickerRef.current.contains(activeElement)`.
|
|
176
|
+
// A picker built from crisp's own `Command` (the shape #372's own doc
|
|
177
|
+
// recommends) renders through a `Portal`, so its DOM parent is nowhere
|
|
178
|
+
// near `mentionPickerRef` even though it's still that ref's REACT child;
|
|
179
|
+
// `.contains()` sees a stranger the instant the picker's search input
|
|
180
|
+
// takes focus and updateMention closes the picker it was just asked to
|
|
181
|
+
// protect. React's synthetic focus/blur events bubble through the
|
|
182
|
+
// component tree a portal renders into, not the DOM tree it renders to
|
|
183
|
+
// (documented React behaviour), so a plain onFocus/onBlur pair on this
|
|
184
|
+
// same wrapper sees the portal's contents correctly.
|
|
185
|
+
const mentionPickerHasFocusRef = React.useRef(false);
|
|
173
186
|
const syncEmpty = (el) => {
|
|
174
187
|
if (el)
|
|
175
188
|
el.toggleAttribute("data-empty", (el.textContent ?? "") === "");
|
|
@@ -291,6 +304,11 @@ exports.RichTextEditor = React.forwardRef(function RichTextEditor({ defaultTitle
|
|
|
291
304
|
}, [readOnly, blockMenu, closeBlockMenu]);
|
|
292
305
|
const closeMention = React.useCallback(() => {
|
|
293
306
|
mentionRangeRef.current = null;
|
|
307
|
+
// Defensive, not load-bearing: the wrapper's onBlur already clears this
|
|
308
|
+
// when the picker's own focused element leaves the DOM on unmount, but
|
|
309
|
+
// resetting it directly here means a stale `true` can never survive
|
|
310
|
+
// past whatever actually closed the picker, blur-ordering included.
|
|
311
|
+
mentionPickerHasFocusRef.current = false;
|
|
294
312
|
setMention(null);
|
|
295
313
|
}, []);
|
|
296
314
|
/**
|
|
@@ -302,16 +320,23 @@ exports.RichTextEditor = React.forwardRef(function RichTextEditor({ defaultTitle
|
|
|
302
320
|
* no trigger.
|
|
303
321
|
*
|
|
304
322
|
* Does NOT auto-close while focus sits inside the consumer's own picker
|
|
305
|
-
* (`
|
|
323
|
+
* (`mentionPickerHasFocusRef`) — a picker with its own search field would
|
|
306
324
|
* otherwise close itself the instant that field took focus, since focus
|
|
307
325
|
* moving there also moves the document selection out of `body`. This is
|
|
308
326
|
* the same distinction `updateBar` already draws for the block menu's own
|
|
309
|
-
* search input, made while merging the two features together
|
|
327
|
+
* search input, made while merging the two features together — except a
|
|
328
|
+
* consumer's picker, unlike the block menu's own inline search, is free
|
|
329
|
+
* to render through a Portal (crisp's own `Command`, the shape #372's own
|
|
330
|
+
* doc recommends building one from, does exactly that), so this reads a
|
|
331
|
+
* ref React's focus/blur bubbling keeps current instead of asking
|
|
332
|
+
* `mentionPickerRef`'s DOM node whether it contains `activeElement` (#514
|
|
333
|
+
* — a Portal's content is a DOM stranger to its React parent, so that
|
|
334
|
+
* check silently stopped protecting the one case it exists for).
|
|
310
335
|
*/
|
|
311
336
|
const updateMention = React.useCallback(() => {
|
|
312
337
|
if (readOnly)
|
|
313
338
|
return;
|
|
314
|
-
if (
|
|
339
|
+
if (mentionPickerHasFocusRef.current)
|
|
315
340
|
return;
|
|
316
341
|
const body = bodyRef.current;
|
|
317
342
|
const sel = window.getSelection();
|
|
@@ -564,7 +589,11 @@ exports.RichTextEditor = React.forwardRef(function RichTextEditor({ defaultTitle
|
|
|
564
589
|
select: insertMention,
|
|
565
590
|
close: closeMention,
|
|
566
591
|
});
|
|
567
|
-
return rendered == null ? null : ((0, jsx_runtime_1.jsx)("div", { ref: mentionPickerRef, style: { display: "contents" },
|
|
592
|
+
return rendered == null ? null : ((0, jsx_runtime_1.jsx)("div", { ref: mentionPickerRef, style: { display: "contents" }, onFocus: () => {
|
|
593
|
+
mentionPickerHasFocusRef.current = true;
|
|
594
|
+
}, onBlur: () => {
|
|
595
|
+
mentionPickerHasFocusRef.current = false;
|
|
596
|
+
}, children: rendered }));
|
|
568
597
|
})()] }));
|
|
569
598
|
});
|
|
570
599
|
exports.RichTextEditor.displayName = "RichTextEditor";
|
|
@@ -0,0 +1,17 @@
|
|
|
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 __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
14
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
|
+
};
|
|
16
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
+
__exportStar(require("./statlist"), exports);
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.StatList = StatList;
|
|
4
|
+
const jsx_runtime_1 = require("react/jsx-runtime");
|
|
5
|
+
const badge_1 = require("../../components/badge/badge");
|
|
6
|
+
const statlist_recipe_1 = require("./statlist.recipe");
|
|
7
|
+
/**
|
|
8
|
+
* A list of label + trailing-count rows — a dashboard's own findings
|
|
9
|
+
* summary ("Open risks with no owner: 3") or an audit-readiness check list,
|
|
10
|
+
* each row optionally a link. Not measured against one specific Attio
|
|
11
|
+
* widget (see the recipe's own doc comment for why); composed from
|
|
12
|
+
* already-measured crisp values throughout.
|
|
13
|
+
*/
|
|
14
|
+
function StatList({ items, emptyLabel = "Nothing to show", className, ...rest }) {
|
|
15
|
+
return ((0, jsx_runtime_1.jsx)("div", { className: (0, statlist_recipe_1.statlistRecipe)({ className }), ...rest, children: items.length === 0 ? ((0, jsx_runtime_1.jsx)("div", { className: "crisp-statlist-empty", children: emptyLabel })) : (items.map((item) => {
|
|
16
|
+
const content = ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsx)("span", { className: "crisp-statlist-label", children: item.label }), (0, jsx_runtime_1.jsx)(badge_1.Badge, { tone: item.count === 0 ? "neutral" : (item.tone ?? "neutral"), children: item.count })] }));
|
|
17
|
+
return item.href ? ((0, jsx_runtime_1.jsx)("a", { href: item.href, onClick: item.onClick, className: "crisp-statlist-row", children: content }, item.id)) : ((0, jsx_runtime_1.jsx)("div", { className: "crisp-statlist-row", children: content }, item.id));
|
|
18
|
+
})) }));
|
|
19
|
+
}
|
|
20
|
+
StatList.displayName = "StatList";
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.statlistRecipe = void 0;
|
|
4
|
+
const core_1 = require("../../core");
|
|
5
|
+
const tokens_1 = require("../../tokens");
|
|
6
|
+
// A list of label + trailing-count rows, each optionally a link — the
|
|
7
|
+
// "N findings of this kind" shape a dashboard's own status summary and an
|
|
8
|
+
// audit-readiness check list both need. No direct Attio widget measures
|
|
9
|
+
// this exact row (Attio's own Reports/Dashboards surface in this workspace
|
|
10
|
+
// has bar/line/number report tiles, not a findings list), so this reuses
|
|
11
|
+
// already-measured crisp values throughout rather than a fresh guess:
|
|
12
|
+
// TaskList's own row-divider structure (border-top, no border on the first
|
|
13
|
+
// row), the type scale's own `md`/`sm` sizes, and Badge's own tone colours
|
|
14
|
+
// for the trailing count — no new pixel or colour value is introduced here.
|
|
15
|
+
const config = (0, core_1.defineRecipe)({
|
|
16
|
+
name: "statlist",
|
|
17
|
+
base: `display: flex; flex-direction: column; box-sizing: border-box; font-family: ${tokens_1.contract.font.sans};
|
|
18
|
+
-webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale;
|
|
19
|
+
|
|
20
|
+
& .crisp-statlist-row { display: flex; align-items: center; justify-content: space-between; gap: ${tokens_1.contract.space["3"]}; padding: ${tokens_1.contract.space["2.5"]} 0; border-top: 1px solid ${tokens_1.contract.borderSubtle}; font-size: ${tokens_1.contract.text.md}; color: inherit; text-decoration: none; }
|
|
21
|
+
& .crisp-statlist-row:first-child { border-top: none; }
|
|
22
|
+
& a.crisp-statlist-row:hover .crisp-statlist-label { color: ${tokens_1.contract.fgBrandSoft}; }
|
|
23
|
+
|
|
24
|
+
& .crisp-statlist-label { color: ${tokens_1.contract.fgMuted}; }
|
|
25
|
+
|
|
26
|
+
& .crisp-statlist-empty { padding: ${tokens_1.contract.space["4"]} 0; text-align: center; font-size: ${tokens_1.contract.text.md}; color: ${tokens_1.contract.fgMuted}; }`,
|
|
27
|
+
variants: {},
|
|
28
|
+
defaultVariants: {},
|
|
29
|
+
compoundVariants: [],
|
|
30
|
+
});
|
|
31
|
+
exports.statlistRecipe = (0, core_1.resolveRecipe)(config);
|
|
32
|
+
exports.default = config;
|
|
@@ -1,11 +1,16 @@
|
|
|
1
1
|
import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import * as React from "react";
|
|
3
|
+
import { useCrispMessage } from "../../core/index.js";
|
|
3
4
|
import { breadcrumbRecipe } from "./breadcrumb.recipe.js";
|
|
4
5
|
/**
|
|
5
6
|
* A breadcrumb trail — measured from a record-page nav. The last item is
|
|
6
7
|
* marked `aria-current="page"`; earlier items render as links or buttons.
|
|
7
8
|
*/
|
|
8
|
-
export function Breadcrumb({ items, separator, "aria-label":
|
|
9
|
+
export function Breadcrumb({ items, separator, "aria-label": ariaLabelProp, className, ...rest }) {
|
|
10
|
+
// crisp/#471: an explicit prop always wins; missing, it reads the nearest
|
|
11
|
+
// CrispMessagesProvider's translation of the English default, or that
|
|
12
|
+
// default itself with no provider — unchanged from before this existed.
|
|
13
|
+
const ariaLabel = useCrispMessage(ariaLabelProp, "Breadcrumb");
|
|
9
14
|
// Live-measured (app.attio.com/<ws>/home, /companies,
|
|
10
15
|
// /company/<id>/overview): a plain rounded divider, not a chevron -- a
|
|
11
16
|
// symmetric shape needs no RTL mirroring, unlike the ChevronRight this
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { ChevronDown, Plus } from "lucide-react";
|
|
3
3
|
import * as React from "react";
|
|
4
|
+
import { useCrispMessage, useCrispMessages } from "../../core/index.js";
|
|
4
5
|
import { Menu, MenuItem } from "../menu/index.js";
|
|
5
6
|
import { columncalcRecipe } from "./columncalc.recipe.js";
|
|
6
7
|
/** The four calcs offered on every column, in their order. */
|
|
@@ -78,10 +79,18 @@ export function computeCalc(values, op) {
|
|
|
78
79
|
const defaultFormat = (n, op) => op === "percentEmpty" || op === "percentFilled"
|
|
79
80
|
? `${Math.round(n)}%`
|
|
80
81
|
: n.toLocaleString(undefined, { maximumFractionDigits: 2 });
|
|
81
|
-
export function ColumnCalc({ rows, value, numeric = false, defaultOp = "none", op: opProp, onOpChange, addLabel
|
|
82
|
-
//
|
|
83
|
-
//
|
|
84
|
-
|
|
82
|
+
export function ColumnCalc({ rows, value, numeric = false, defaultOp = "none", op: opProp, onOpChange, addLabel: addLabelProp, sectionLabel: sectionLabelProp, format = defaultFormat, align, opLabels, className, ...rest }) {
|
|
83
|
+
// crisp/#471: an explicit prop always wins; missing, it reads the nearest
|
|
84
|
+
// CrispMessagesProvider's translation of the English default, or that
|
|
85
|
+
// default itself with no provider — unchanged from before this existed.
|
|
86
|
+
const addLabel = useCrispMessage(addLabelProp, "Add calculation");
|
|
87
|
+
const sectionLabel = useCrispMessage(sectionLabelProp, "Aggregations");
|
|
88
|
+
const crispMessages = useCrispMessages();
|
|
89
|
+
// opLabels (a per-op partial override) wins, then the provider's own
|
|
90
|
+
// translation of CALC_LABEL's English default, then that default itself —
|
|
91
|
+
// a consumer can translate one op via opLabels without restating all
|
|
92
|
+
// nine, AND get the rest from a provider with no per-instance prop at all.
|
|
93
|
+
const labelFor = (o) => opLabels?.[o] ?? crispMessages[CALC_LABEL[o]] ?? CALC_LABEL[o];
|
|
85
94
|
const [internal, setInternal] = React.useState(defaultOp);
|
|
86
95
|
const op = opProp ?? internal;
|
|
87
96
|
const setOp = (next) => {
|
|
@@ -3,7 +3,7 @@ import * as dialog from "@zag-js/dialog";
|
|
|
3
3
|
import { normalizeProps, Portal, useMachine } from "@zag-js/react";
|
|
4
4
|
import { Minus, Pin, X } from "lucide-react";
|
|
5
5
|
import * as React from "react";
|
|
6
|
-
import { cx, withDescribedBy } from "../../core/index.js";
|
|
6
|
+
import { cx, useCrispMessage, withDescribedBy } from "../../core/index.js";
|
|
7
7
|
import { composewindowRecipe } from "./composewindow.recipe.js";
|
|
8
8
|
// A globally unique id per mounted instance — see command.tsx/dialog.tsx for
|
|
9
9
|
// the rationale (the machine defers its dismissal listeners a frame, so a
|
|
@@ -56,7 +56,11 @@ function useComposeWindowInstanceId() {
|
|
|
56
56
|
* measured screenshot. `trapFocus` follows the same call — a picture-in-
|
|
57
57
|
* picture window, not a modal, so Tab is not caught inside it.
|
|
58
58
|
*/
|
|
59
|
-
export function ComposeWindow({ open, onOpenChange, breadcrumb, minimized: minimizedProp, defaultMinimized, onMinimizedChange, pinned: pinnedProp, defaultPinned, onPinnedChange, width = 968, "aria-label": ariaLabel, minimizeLabel
|
|
59
|
+
export function ComposeWindow({ open, onOpenChange, breadcrumb, minimized: minimizedProp, defaultMinimized, onMinimizedChange, pinned: pinnedProp, defaultPinned, onPinnedChange, width = 968, "aria-label": ariaLabel, minimizeLabel: minimizeLabelProp, pinLabel: pinLabelProp, closeLabel: closeLabelProp, children, className, style, "aria-describedby": describedBy, ...rest }) {
|
|
60
|
+
// crisp/#471: same resolution order as Breadcrumb's own.
|
|
61
|
+
const minimizeLabel = useCrispMessage(minimizeLabelProp, "Minimize");
|
|
62
|
+
const pinLabel = useCrispMessage(pinLabelProp, "Pin to screen");
|
|
63
|
+
const closeLabel = useCrispMessage(closeLabelProp, "Close");
|
|
60
64
|
const id = useComposeWindowInstanceId();
|
|
61
65
|
const [internalMinimized, setInternalMinimized] = React.useState(defaultMinimized ?? false);
|
|
62
66
|
const minimized = minimizedProp ?? internalMinimized;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { Slot } from "@radix-ui/react-slot";
|
|
3
3
|
import * as React from "react";
|
|
4
|
-
import { FloatingPortal, isInsideLaterFloatingLayer, registerFloatingLayer, } from "../../core/index.js";
|
|
4
|
+
import { FloatingPortal, isInsideLaterFloatingLayer, registerFloatingLayer, useCrispMessage, } from "../../core/index.js";
|
|
5
5
|
import { useMenuKeyboard } from "../menu/index.js";
|
|
6
6
|
import { contextmenuRecipe } from "./contextmenu.recipe.js";
|
|
7
7
|
/**
|
|
@@ -9,7 +9,9 @@ import { contextmenuRecipe } from "./contextmenu.recipe.js";
|
|
|
9
9
|
* Wrap the target; right-clicking it opens the menu (flipping at the viewport
|
|
10
10
|
* edges), closing on select, outside pointer-down, and Escape.
|
|
11
11
|
*/
|
|
12
|
-
export function ContextMenu({ children, items, menuLabel
|
|
12
|
+
export function ContextMenu({ children, items, menuLabel: menuLabelProp, className, asChild = false, }) {
|
|
13
|
+
// crisp/#471: same resolution order as Breadcrumb's own.
|
|
14
|
+
const menuLabel = useCrispMessage(menuLabelProp, "Context menu");
|
|
13
15
|
const rootRef = React.useRef(null);
|
|
14
16
|
const menuRef = React.useRef(null);
|
|
15
17
|
const returnFocusTo = React.useRef(null);
|