@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
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
3
|
import * as React from "react";
|
|
4
|
-
import { cx } from "../../core/index.js";
|
|
4
|
+
import { cx, useCrispMessage } from "../../core/index.js";
|
|
5
5
|
import { FillSheet } from "../fillsheet/index.js";
|
|
6
6
|
import { filldocRecipe } from "./filldoc.recipe.js";
|
|
7
7
|
/** Size a textarea to its content; see FillSheet's autoGrow for the reset. */
|
|
@@ -36,7 +36,12 @@ const emptyTable = () => ({
|
|
|
36
36
|
* page covered in handles and grips stops looking like a page, which is the
|
|
37
37
|
* whole thing this is for.
|
|
38
38
|
*/
|
|
39
|
-
export const FillDoc = React.forwardRef(function FillDoc({ blocks, onBlocksChange, mode = "edit", page = "a4", label, addTextLabel
|
|
39
|
+
export const FillDoc = React.forwardRef(function FillDoc({ blocks, onBlocksChange, mode = "edit", page = "a4", label, addTextLabel: addTextLabelProp, addHeadingLabel: addHeadingLabelProp, addTableLabel: addTableLabelProp, removeLabel: removeLabelProp, className, style, ...rest }, forwardedRef) {
|
|
40
|
+
// crisp/#471: same resolution order as Breadcrumb's own.
|
|
41
|
+
const addTextLabel = useCrispMessage(addTextLabelProp, "Text");
|
|
42
|
+
const addHeadingLabel = useCrispMessage(addHeadingLabelProp, "Heading");
|
|
43
|
+
const addTableLabel = useCrispMessage(addTableLabelProp, "Table");
|
|
44
|
+
const removeLabel = useCrispMessage(removeLabelProp, "Remove");
|
|
40
45
|
const editable = mode === "edit";
|
|
41
46
|
const replace = (i, block) => {
|
|
42
47
|
const next = blocks.slice();
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { heatmapchartRecipe } from "./heatmapchart.recipe.js";
|
|
3
|
+
/**
|
|
4
|
+
* An NxM cell-matrix chart — GRC's own risk heatmap (impact x likelihood)
|
|
5
|
+
* is the first consumer. Severity/intensity mapping (`tone`, `ring`) is
|
|
6
|
+
* entirely the caller's own data, not a scale this component invents; see
|
|
7
|
+
* the recipe's own doc comment for why (and for its measurement — or lack
|
|
8
|
+
* of one — against Attio).
|
|
9
|
+
*/
|
|
10
|
+
export 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 }) {
|
|
11
|
+
const cols = data[0]?.length ?? 0;
|
|
12
|
+
const summary = ariaLabel ?? `Heatmap: ${data.length} x ${cols} grid`;
|
|
13
|
+
return (_jsx("div", { className: heatmapchartRecipe({ className }), ...rest, role: "img", "aria-label": summary, children: empty ? (_jsx("div", { className: "crisp-heatmapchart-empty", children: emptyLabel })) : (_jsxs(_Fragment, { children: [_jsxs("div", { className: "crisp-heatmapchart-rowaxis", children: [_jsx("span", { children: rowAxis.high }), rowAxis.title != null && (_jsx("span", { className: "crisp-heatmapchart-rowaxis-title", children: rowAxis.title })), _jsx("span", { children: rowAxis.low })] }), _jsxs("div", { className: "crisp-heatmapchart-body", children: [_jsx("div", { className: "crisp-heatmapchart-grid", style: { gridTemplateColumns: `repeat(${cols}, 1fr)` }, children: data.map((row, r) => row.map((cell, cc) => {
|
|
14
|
+
const clickable = onCellClick != null && isCellClickable(cell, r, cc);
|
|
15
|
+
const title = cellTitle?.(cell, r, cc);
|
|
16
|
+
const key = `${r}-${cc}`;
|
|
17
|
+
const tone = cell.tone ?? "neutral";
|
|
18
|
+
return clickable ? (_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)) : (_jsx("div", { className: "crisp-heatmapchart-cell", "data-tone": tone, "data-ring": cell.ring ? "" : undefined, title: title, children: format(cell, r, cc) }, key));
|
|
19
|
+
})) }), _jsxs("div", { className: "crisp-heatmapchart-colaxis", children: [_jsx("span", { children: colAxis.low }), colAxis.title != null && _jsx("span", { children: colAxis.title }), _jsx("span", { children: colAxis.high })] })] })] })) }));
|
|
20
|
+
}
|
|
21
|
+
HeatmapChart.displayName = "HeatmapChart";
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { defineRecipe, resolveRecipe } from "../../core/index.js";
|
|
2
|
+
import { contract as c } from "../../tokens/index.js";
|
|
3
|
+
const soft = (v, pct) => `color-mix(in srgb, ${v} ${pct}%, transparent)`;
|
|
4
|
+
// An NxM cell-matrix chart, alongside BarChart/LineChart/DonutChart — GRC's
|
|
5
|
+
// own risk heatmap (impact x likelihood) is the first consumer. No Attio
|
|
6
|
+
// widget in the workspace this was checked against (Reports/Dashboards:
|
|
7
|
+
// bar/line/number report tiles) has a matching matrix chart to measure a
|
|
8
|
+
// pixel value from, so this is composed entirely from already-measured
|
|
9
|
+
// crisp values: the same `color-mix` soft-tint recipe Badge's own `soft()`
|
|
10
|
+
// helper and StatCard's delta pill already use, at the same 14-16%,
|
|
11
|
+
// against the token contract's own success/warning/orange/danger roles.
|
|
12
|
+
// A cell's text stays `fgPrimary` at every tone — not tone-matched — the
|
|
13
|
+
// same "the background already carries the severity, tone-on-tone measures
|
|
14
|
+
// illegible" reasoning GRC's own hand-built heatmap already recorded.
|
|
15
|
+
const config = defineRecipe({
|
|
16
|
+
name: "heatmapchart",
|
|
17
|
+
base: `display: flex; gap: ${c.space["2"]}; box-sizing: border-box; font-family: ${c.font.sans};
|
|
18
|
+
-webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale;
|
|
19
|
+
|
|
20
|
+
& .crisp-heatmapchart-rowaxis { display: flex; flex-direction: column; justify-content: space-between; padding: ${c.space["1"]} 0; font-size: ${c.text.xs}; font-weight: ${c.weight.medium}; text-transform: uppercase; letter-spacing: 0.02em; color: ${c.fgNeutralSubtle}; }
|
|
21
|
+
& .crisp-heatmapchart-rowaxis-title { writing-mode: vertical-rl; transform: rotate(180deg); align-self: center; }
|
|
22
|
+
|
|
23
|
+
& .crisp-heatmapchart-body { flex: 1; }
|
|
24
|
+
& .crisp-heatmapchart-grid { display: grid; gap: ${c.space["1.5"]}; }
|
|
25
|
+
|
|
26
|
+
& .crisp-heatmapchart-cell { box-sizing: border-box; display: flex; align-items: center; justify-content: center; aspect-ratio: 2 / 1; border-radius: ${c.radius.md}; border: none; background: transparent; margin: 0; padding: 0; font-family: inherit; font-size: ${c.text.md}; font-weight: ${c.weight.semibold}; font-variant-numeric: tabular-nums; color: ${c.fgPrimary}; }
|
|
27
|
+
& button.crisp-heatmapchart-cell { cursor: pointer; transition: filter 0.12s ease; }
|
|
28
|
+
& button.crisp-heatmapchart-cell:hover { filter: brightness(0.97); }
|
|
29
|
+
& button.crisp-heatmapchart-cell:focus-visible { outline: 2px solid ${c.bgBrandSolid}; outline-offset: 1px; }
|
|
30
|
+
|
|
31
|
+
& .crisp-heatmapchart-cell[data-tone="neutral"] { background: ${c.bgRaised}; color: ${c.fgMuted}; }
|
|
32
|
+
& .crisp-heatmapchart-cell[data-tone="success"] { background: ${soft(c.bgSuccess, 16)}; }
|
|
33
|
+
& .crisp-heatmapchart-cell[data-tone="warning"] { background: ${soft(c.bgWarning, 16)}; }
|
|
34
|
+
& .crisp-heatmapchart-cell[data-tone="orange"] { background: ${soft(c.bgOrange, 18)}; }
|
|
35
|
+
& .crisp-heatmapchart-cell[data-tone="danger"] { background: ${soft(c.bgDanger, 16)}; }
|
|
36
|
+
& .crisp-heatmapchart-cell[data-ring] { box-shadow: inset 0 0 0 2px ${c.bgDangerSolid}; }
|
|
37
|
+
|
|
38
|
+
& .crisp-heatmapchart-colaxis { display: flex; justify-content: space-between; margin-top: ${c.space["1.5"]}; padding: 0 ${c.space["0.5"]}; font-size: ${c.text.xs}; font-weight: ${c.weight.medium}; text-transform: uppercase; letter-spacing: 0.02em; color: ${c.fgNeutralSubtle}; }
|
|
39
|
+
|
|
40
|
+
& .crisp-heatmapchart-empty { padding: ${c.space["8"]} 0; text-align: center; font-size: ${c.text.md}; color: ${c.fgMuted}; }`,
|
|
41
|
+
variants: {},
|
|
42
|
+
defaultVariants: {},
|
|
43
|
+
compoundVariants: [],
|
|
44
|
+
});
|
|
45
|
+
export const heatmapchartRecipe = resolveRecipe(config);
|
|
46
|
+
export default config;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./heatmapchart.js";
|
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { useCrispMessage } from "../../core/index.js";
|
|
2
3
|
import { spinnerRecipe } from "./spinner.recipe.js";
|
|
3
4
|
/** A spinning ring indicator. Reports `role="status"` for assistive tech. */
|
|
4
|
-
export function Spinner({ size = 16, "aria-label":
|
|
5
|
+
export function Spinner({ size = 16, "aria-label": ariaLabelProp, className, ...rest }) {
|
|
6
|
+
// crisp/#471: same resolution order as Breadcrumb's own.
|
|
7
|
+
const ariaLabel = useCrispMessage(ariaLabelProp, "Loading");
|
|
5
8
|
return (_jsx("span", { role: "status", "aria-label": ariaLabel, className: spinnerRecipe({ className }), ...rest, style: {
|
|
6
9
|
width: size,
|
|
7
10
|
height: size,
|
|
@@ -1,13 +1,17 @@
|
|
|
1
1
|
import { defineRecipe, resolveRecipe } from "../../core/index.js";
|
|
2
|
-
import { contract as c } from "../../tokens/index.js";
|
|
3
|
-
// Toast notifications — a fixed
|
|
2
|
+
import { contract as c, elevation as e } from "../../tokens/index.js";
|
|
3
|
+
// Toast notifications — a fixed TOP-right stack of cards on the measured
|
|
4
4
|
// popover surface, each with a tone icon, title/description, optional action,
|
|
5
|
-
// and a close button.
|
|
5
|
+
// and a close button. Position, radius, padding, surface and shadow measured
|
|
6
|
+
// live off Attio's own "Copied to clipboard!" toast (crisp#515): the stack
|
|
7
|
+
// anchors top-right (not bottom-right), and the card shares Menu/Popover's
|
|
8
|
+
// `bgPopover` surface + `elevation.floating` shadow (Lesson 31 — reference
|
|
9
|
+
// the shared token, never a hand-typed near-duplicate).
|
|
6
10
|
const config = defineRecipe({
|
|
7
11
|
name: "toast",
|
|
8
12
|
base: `display: contents; font-family: ${c.font.sans};
|
|
9
|
-
& .crisp-toast-viewport { position: fixed; z-index: 80; inset-inline-end: ${c.space["4"]};
|
|
10
|
-
& .crisp-toast { pointer-events: auto; display: flex; align-items: flex-start; gap: ${c.space["2.5"]}; box-sizing: border-box; padding: ${c.space["
|
|
13
|
+
& .crisp-toast-viewport { position: fixed; z-index: 80; inset-inline-end: ${c.space["4"]}; top: ${c.space["4"]}; display: flex; flex-direction: column; gap: ${c.space["2"]}; width: 360px; max-width: calc(100vw - ${c.space["8"]}); pointer-events: none; }
|
|
14
|
+
& .crisp-toast { pointer-events: auto; display: flex; align-items: flex-start; gap: ${c.space["2.5"]}; box-sizing: border-box; padding: ${c.space["2.5"]}; border-radius: ${c.radius.md}; background: ${c.bgPopover}; box-shadow: ${e.floating}; opacity: 1; transform: translateY(0); transition: opacity 0.15s ease, transform 0.15s ease; @starting-style { opacity: 0; transform: translateY(8px); } }
|
|
11
15
|
& .crisp-toast-icon { flex-shrink: 0; margin-top: 1px; display: inline-flex; color: ${c.fgMuted}; }
|
|
12
16
|
& .crisp-toast[data-tone="info"] .crisp-toast-icon { color: ${c.bgBrandSolid}; }
|
|
13
17
|
& .crisp-toast[data-tone="success"] .crisp-toast-icon { color: ${c.bgSuccess}; }
|
package/dist/esm/core/i18n.js
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
2
|
import * as React from "react";
|
|
3
3
|
const CrispMessagesContext = React.createContext({});
|
|
4
|
-
|
|
5
|
-
|
|
4
|
+
const CrispComponentDefaultsContext = React.createContext({});
|
|
5
|
+
export function CrispMessagesProvider({ messages, componentDefaults, children, }) {
|
|
6
|
+
return (_jsx(CrispMessagesContext.Provider, { value: messages, children: _jsx(CrispComponentDefaultsContext.Provider, { value: componentDefaults ?? {}, children: children }) }));
|
|
6
7
|
}
|
|
7
8
|
/**
|
|
8
9
|
* Resolves one translatable string for a component: `explicit` is whatever
|
|
@@ -35,3 +36,16 @@ export function useCrispMessage(explicit, englishDefault) {
|
|
|
35
36
|
export function useCrispMessages() {
|
|
36
37
|
return React.useContext(CrispMessagesContext);
|
|
37
38
|
}
|
|
39
|
+
/**
|
|
40
|
+
* Reads one component's own whole-object defaults override, set via
|
|
41
|
+
* `CrispMessagesProvider`'s `componentDefaults` prop (see its own doc for
|
|
42
|
+
* why this exists separately from `useCrispMessage`). `T` is the
|
|
43
|
+
* component's own labels TYPE (e.g. `Partial<RecordPageLabels>`) — this
|
|
44
|
+
* hook itself only knows `name`, a plain string key; the caller supplies
|
|
45
|
+
* and trusts the real shape, the same way `useContext` always has for an
|
|
46
|
+
* untyped-at-the-provider value.
|
|
47
|
+
*/
|
|
48
|
+
export function useCrispComponentDefaults(name) {
|
|
49
|
+
const all = React.useContext(CrispComponentDefaultsContext);
|
|
50
|
+
return all[name];
|
|
51
|
+
}
|
package/dist/esm/core/index.js
CHANGED
|
@@ -7,7 +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
|
+
export { CrispMessagesProvider, useCrispComponentDefaults, useCrispMessage, useCrispMessages, } from "./i18n.js";
|
|
11
11
|
export { matchMentionTrigger, } from "./mention.js";
|
|
12
12
|
export { placeFloating, } from "./place-floating.js";
|
|
13
13
|
export { Primitive } from "./primitive.js";
|
package/dist/esm/index.js
CHANGED
|
@@ -29,6 +29,7 @@ export * from "./components/fileupload/index.js";
|
|
|
29
29
|
export * from "./components/filldoc/index.js";
|
|
30
30
|
export * from "./components/fillsheet/index.js";
|
|
31
31
|
export * from "./components/formfield/index.js";
|
|
32
|
+
export * from "./components/heatmapchart/index.js";
|
|
32
33
|
export * from "./components/input/index.js";
|
|
33
34
|
export * from "./components/kbd/index.js";
|
|
34
35
|
export * from "./components/linechart/index.js";
|
|
@@ -78,6 +79,7 @@ export * from "./product/richtext/index.js";
|
|
|
78
79
|
export * from "./product/settingssection/index.js";
|
|
79
80
|
export * from "./product/settingsshell/index.js";
|
|
80
81
|
export * from "./product/sidebar/index.js";
|
|
82
|
+
export * from "./product/statlist/index.js";
|
|
81
83
|
export * from "./product/tasklist/index.js";
|
|
82
84
|
export * from "./product/trialbanner/index.js";
|
|
83
85
|
export * from "./product/viewtoggle/index.js";
|
|
@@ -8,7 +8,7 @@ import { Select } from "../../components/select/index.js";
|
|
|
8
8
|
import { Spinner } from "../../components/spinner/index.js";
|
|
9
9
|
import { Switch } from "../../components/switch/index.js";
|
|
10
10
|
import { Tabs } from "../../components/tabs/index.js";
|
|
11
|
-
import { isRtlElement } from "../../core/index.js";
|
|
11
|
+
import { isRtlElement, useCrispComponentDefaults } from "../../core/index.js";
|
|
12
12
|
import { editableInputType, tagTone, useEditableField, } from "../recordstable/index.js";
|
|
13
13
|
import { recordpageRecipe } from "./recordpage.recipe.js";
|
|
14
14
|
function valueEmpty(v) {
|
|
@@ -220,19 +220,34 @@ function EmptyCard({ icon, children, }) {
|
|
|
220
220
|
*/
|
|
221
221
|
export function RecordPage({ record, columns, collection = "Records", labels, onBack, onToggleFull, full, className, onEdit, tabs: tabsProp, defaultTab, notes: notesProp, onAddNote, tasks: tasksProp, onAddTask, onToggleTask, ...rest }) {
|
|
222
222
|
const { editingKey, pendingKey, error, openEditing, closeEditing, commit } = useEditableField(onEdit);
|
|
223
|
+
// crisp/#471: a CrispMessagesProvider's `componentDefaults.recordPage` sits
|
|
224
|
+
// BETWEEN the built-in English defaults and this instance's own explicit
|
|
225
|
+
// `labels` prop — a provider translates every RecordPage on the page at
|
|
226
|
+
// once, and a caller who still passes `labels` for one instance keeps
|
|
227
|
+
// overriding it there, same as before this existed. Not `useCrispMessage`:
|
|
228
|
+
// several of these entries (`domainSetTo`, `sortedBy`, …) are formatter
|
|
229
|
+
// FUNCTIONS, which a flat string dictionary can't hold.
|
|
230
|
+
const providerLabels = useCrispComponentDefaults("recordPage");
|
|
223
231
|
const t = React.useMemo(() => ({
|
|
224
232
|
...recordPageLabels,
|
|
233
|
+
...providerLabels,
|
|
225
234
|
...labels,
|
|
226
|
-
tabs: {
|
|
235
|
+
tabs: {
|
|
236
|
+
...recordPageLabels.tabs,
|
|
237
|
+
...providerLabels?.tabs,
|
|
238
|
+
...labels?.tabs,
|
|
239
|
+
},
|
|
227
240
|
highlightLabels: {
|
|
228
241
|
...recordPageLabels.highlightLabels,
|
|
242
|
+
...providerLabels?.highlightLabels,
|
|
229
243
|
...labels?.highlightLabels,
|
|
230
244
|
},
|
|
231
245
|
highlightEmpty: {
|
|
232
246
|
...recordPageLabels.highlightEmpty,
|
|
247
|
+
...providerLabels?.highlightEmpty,
|
|
233
248
|
...labels?.highlightEmpty,
|
|
234
249
|
},
|
|
235
|
-
}), [labels]);
|
|
250
|
+
}), [labels, providerLabels]);
|
|
236
251
|
const [favorite, setFavorite] = React.useState(false);
|
|
237
252
|
// Notes/Tasks are controlled when the caller supplies `notes`/`tasks` (even
|
|
238
253
|
// `[]`) -- otherwise RecordPage keeps its own self-contained state, as
|
|
@@ -14,7 +14,7 @@ import { Menu, MenuItem, useMenuKeyboard } from "../../components/menu/index.js"
|
|
|
14
14
|
import { MultiSelect } from "../../components/multiselect/index.js";
|
|
15
15
|
import { Select } from "../../components/select/index.js";
|
|
16
16
|
import { Switch } from "../../components/switch/index.js";
|
|
17
|
-
import { isInsideLaterFloatingLayer, isRtlElement, usePrefersReducedMotion, } from "../../core/index.js";
|
|
17
|
+
import { isInsideLaterFloatingLayer, isRtlElement, useCrispComponentDefaults, usePrefersReducedMotion, } from "../../core/index.js";
|
|
18
18
|
import { companyLogos } from "./company-logos.js";
|
|
19
19
|
import { recordstableRecipe } from "./recordstable.recipe.js";
|
|
20
20
|
const EDITABLE = new Set(["text", "number", "url", "currency"]);
|
|
@@ -456,12 +456,23 @@ export function RecordsTable({ labels, columns: columnsProp = defaultColumns, ro
|
|
|
456
456
|
const [filterConds, setFilterConds] = React.useState([]);
|
|
457
457
|
const [filterQuery, setFilterQuery] = React.useState("");
|
|
458
458
|
// one popover open at a time: "sort" | "filter" | "more" | `sum:<key>`
|
|
459
|
+
// crisp/#471: same "provider sits between the built-in default and this
|
|
460
|
+
// instance's own explicit labels prop" order as RecordPage's own — see
|
|
461
|
+
// its comment. Several entries here (`selectionAnnouncement`, `sortedBy`,
|
|
462
|
+
// `recordCount`, …) are formatter functions, which is why this goes
|
|
463
|
+
// through `useCrispComponentDefaults`, not `useCrispMessage`.
|
|
464
|
+
const providerLabels = useCrispComponentDefaults("recordsTable");
|
|
459
465
|
const t = React.useMemo(() => ({
|
|
460
466
|
...recordsTableLabels,
|
|
467
|
+
...providerLabels,
|
|
461
468
|
...labels,
|
|
462
|
-
agg: {
|
|
463
|
-
|
|
464
|
-
|
|
469
|
+
agg: {
|
|
470
|
+
...recordsTableLabels.agg,
|
|
471
|
+
...providerLabels?.agg,
|
|
472
|
+
...labels?.agg,
|
|
473
|
+
},
|
|
474
|
+
op: { ...recordsTableLabels.op, ...providerLabels?.op, ...labels?.op },
|
|
475
|
+
}), [labels, providerLabels]);
|
|
465
476
|
const [openMenu, setOpenMenu] = React.useState(null);
|
|
466
477
|
// the element that opened the current menu, so Esc/select can hand focus back
|
|
467
478
|
const menuReturn = React.useRef(null);
|
|
@@ -134,6 +134,19 @@ export const RichTextEditor = React.forwardRef(function RichTextEditor({ default
|
|
|
134
134
|
// entirely", the same distinction `updateBar` already makes for the block
|
|
135
135
|
// menu's search input below.
|
|
136
136
|
const mentionPickerRef = React.useRef(null);
|
|
137
|
+
// Whether focus currently sits somewhere inside whatever
|
|
138
|
+
// `renderMentionPicker` returned (#514) — tracked through React's own
|
|
139
|
+
// onFocus/onBlur, not `mentionPickerRef.current.contains(activeElement)`.
|
|
140
|
+
// A picker built from crisp's own `Command` (the shape #372's own doc
|
|
141
|
+
// recommends) renders through a `Portal`, so its DOM parent is nowhere
|
|
142
|
+
// near `mentionPickerRef` even though it's still that ref's REACT child;
|
|
143
|
+
// `.contains()` sees a stranger the instant the picker's search input
|
|
144
|
+
// takes focus and updateMention closes the picker it was just asked to
|
|
145
|
+
// protect. React's synthetic focus/blur events bubble through the
|
|
146
|
+
// component tree a portal renders into, not the DOM tree it renders to
|
|
147
|
+
// (documented React behaviour), so a plain onFocus/onBlur pair on this
|
|
148
|
+
// same wrapper sees the portal's contents correctly.
|
|
149
|
+
const mentionPickerHasFocusRef = React.useRef(false);
|
|
137
150
|
const syncEmpty = (el) => {
|
|
138
151
|
if (el)
|
|
139
152
|
el.toggleAttribute("data-empty", (el.textContent ?? "") === "");
|
|
@@ -255,6 +268,11 @@ export const RichTextEditor = React.forwardRef(function RichTextEditor({ default
|
|
|
255
268
|
}, [readOnly, blockMenu, closeBlockMenu]);
|
|
256
269
|
const closeMention = React.useCallback(() => {
|
|
257
270
|
mentionRangeRef.current = null;
|
|
271
|
+
// Defensive, not load-bearing: the wrapper's onBlur already clears this
|
|
272
|
+
// when the picker's own focused element leaves the DOM on unmount, but
|
|
273
|
+
// resetting it directly here means a stale `true` can never survive
|
|
274
|
+
// past whatever actually closed the picker, blur-ordering included.
|
|
275
|
+
mentionPickerHasFocusRef.current = false;
|
|
258
276
|
setMention(null);
|
|
259
277
|
}, []);
|
|
260
278
|
/**
|
|
@@ -266,16 +284,23 @@ export const RichTextEditor = React.forwardRef(function RichTextEditor({ default
|
|
|
266
284
|
* no trigger.
|
|
267
285
|
*
|
|
268
286
|
* Does NOT auto-close while focus sits inside the consumer's own picker
|
|
269
|
-
* (`
|
|
287
|
+
* (`mentionPickerHasFocusRef`) — a picker with its own search field would
|
|
270
288
|
* otherwise close itself the instant that field took focus, since focus
|
|
271
289
|
* moving there also moves the document selection out of `body`. This is
|
|
272
290
|
* the same distinction `updateBar` already draws for the block menu's own
|
|
273
|
-
* search input, made while merging the two features together
|
|
291
|
+
* search input, made while merging the two features together — except a
|
|
292
|
+
* consumer's picker, unlike the block menu's own inline search, is free
|
|
293
|
+
* to render through a Portal (crisp's own `Command`, the shape #372's own
|
|
294
|
+
* doc recommends building one from, does exactly that), so this reads a
|
|
295
|
+
* ref React's focus/blur bubbling keeps current instead of asking
|
|
296
|
+
* `mentionPickerRef`'s DOM node whether it contains `activeElement` (#514
|
|
297
|
+
* — a Portal's content is a DOM stranger to its React parent, so that
|
|
298
|
+
* check silently stopped protecting the one case it exists for).
|
|
274
299
|
*/
|
|
275
300
|
const updateMention = React.useCallback(() => {
|
|
276
301
|
if (readOnly)
|
|
277
302
|
return;
|
|
278
|
-
if (
|
|
303
|
+
if (mentionPickerHasFocusRef.current)
|
|
279
304
|
return;
|
|
280
305
|
const body = bodyRef.current;
|
|
281
306
|
const sel = window.getSelection();
|
|
@@ -528,7 +553,11 @@ export const RichTextEditor = React.forwardRef(function RichTextEditor({ default
|
|
|
528
553
|
select: insertMention,
|
|
529
554
|
close: closeMention,
|
|
530
555
|
});
|
|
531
|
-
return rendered == null ? null : (_jsx("div", { ref: mentionPickerRef, style: { display: "contents" },
|
|
556
|
+
return rendered == null ? null : (_jsx("div", { ref: mentionPickerRef, style: { display: "contents" }, onFocus: () => {
|
|
557
|
+
mentionPickerHasFocusRef.current = true;
|
|
558
|
+
}, onBlur: () => {
|
|
559
|
+
mentionPickerHasFocusRef.current = false;
|
|
560
|
+
}, children: rendered }));
|
|
532
561
|
})()] }));
|
|
533
562
|
});
|
|
534
563
|
RichTextEditor.displayName = "RichTextEditor";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./statlist.js";
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { Badge } from "../../components/badge/badge.js";
|
|
3
|
+
import { statlistRecipe } from "./statlist.recipe.js";
|
|
4
|
+
/**
|
|
5
|
+
* A list of label + trailing-count rows — a dashboard's own findings
|
|
6
|
+
* summary ("Open risks with no owner: 3") or an audit-readiness check list,
|
|
7
|
+
* each row optionally a link. Not measured against one specific Attio
|
|
8
|
+
* widget (see the recipe's own doc comment for why); composed from
|
|
9
|
+
* already-measured crisp values throughout.
|
|
10
|
+
*/
|
|
11
|
+
export function StatList({ items, emptyLabel = "Nothing to show", className, ...rest }) {
|
|
12
|
+
return (_jsx("div", { className: statlistRecipe({ className }), ...rest, children: items.length === 0 ? (_jsx("div", { className: "crisp-statlist-empty", children: emptyLabel })) : (items.map((item) => {
|
|
13
|
+
const content = (_jsxs(_Fragment, { children: [_jsx("span", { className: "crisp-statlist-label", children: item.label }), _jsx(Badge, { tone: item.count === 0 ? "neutral" : (item.tone ?? "neutral"), children: item.count })] }));
|
|
14
|
+
return item.href ? (_jsx("a", { href: item.href, onClick: item.onClick, className: "crisp-statlist-row", children: content }, item.id)) : (_jsx("div", { className: "crisp-statlist-row", children: content }, item.id));
|
|
15
|
+
})) }));
|
|
16
|
+
}
|
|
17
|
+
StatList.displayName = "StatList";
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { defineRecipe, resolveRecipe } from "../../core/index.js";
|
|
2
|
+
import { contract as c } from "../../tokens/index.js";
|
|
3
|
+
// A list of label + trailing-count rows, each optionally a link — the
|
|
4
|
+
// "N findings of this kind" shape a dashboard's own status summary and an
|
|
5
|
+
// audit-readiness check list both need. No direct Attio widget measures
|
|
6
|
+
// this exact row (Attio's own Reports/Dashboards surface in this workspace
|
|
7
|
+
// has bar/line/number report tiles, not a findings list), so this reuses
|
|
8
|
+
// already-measured crisp values throughout rather than a fresh guess:
|
|
9
|
+
// TaskList's own row-divider structure (border-top, no border on the first
|
|
10
|
+
// row), the type scale's own `md`/`sm` sizes, and Badge's own tone colours
|
|
11
|
+
// for the trailing count — no new pixel or colour value is introduced here.
|
|
12
|
+
const config = defineRecipe({
|
|
13
|
+
name: "statlist",
|
|
14
|
+
base: `display: flex; flex-direction: column; box-sizing: border-box; font-family: ${c.font.sans};
|
|
15
|
+
-webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale;
|
|
16
|
+
|
|
17
|
+
& .crisp-statlist-row { display: flex; align-items: center; justify-content: space-between; gap: ${c.space["3"]}; padding: ${c.space["2.5"]} 0; border-top: 1px solid ${c.borderSubtle}; font-size: ${c.text.md}; color: inherit; text-decoration: none; }
|
|
18
|
+
& .crisp-statlist-row:first-child { border-top: none; }
|
|
19
|
+
& a.crisp-statlist-row:hover .crisp-statlist-label { color: ${c.fgBrandSoft}; }
|
|
20
|
+
|
|
21
|
+
& .crisp-statlist-label { color: ${c.fgMuted}; }
|
|
22
|
+
|
|
23
|
+
& .crisp-statlist-empty { padding: ${c.space["4"]} 0; text-align: center; font-size: ${c.text.md}; color: ${c.fgMuted}; }`,
|
|
24
|
+
variants: {},
|
|
25
|
+
defaultVariants: {},
|
|
26
|
+
compoundVariants: [],
|
|
27
|
+
});
|
|
28
|
+
export const statlistRecipe = resolveRecipe(config);
|
|
29
|
+
export default config;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
.crisp-heatmapchart {
|
|
2
|
+
display: flex; gap: var(--crisp-space-2); box-sizing: border-box; font-family: var(--crisp-font-sans); -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale;
|
|
3
|
+
& .crisp-heatmapchart-rowaxis { display: flex; flex-direction: column; justify-content: space-between; padding: var(--crisp-space-1) 0; font-size: var(--crisp-text-xs); font-weight: var(--crisp-weight-medium); text-transform: uppercase; letter-spacing: 0.02em; color: var(--crisp-fg-neutral-subtle); }
|
|
4
|
+
& .crisp-heatmapchart-rowaxis-title { writing-mode: vertical-rl; transform: rotate(180deg); align-self: center; }
|
|
5
|
+
& .crisp-heatmapchart-body { flex: 1; }
|
|
6
|
+
& .crisp-heatmapchart-grid { display: grid; gap: var(--crisp-space-1-5); }
|
|
7
|
+
& .crisp-heatmapchart-cell { box-sizing: border-box; display: flex; align-items: center; justify-content: center; aspect-ratio: 2 / 1; border-radius: var(--crisp-radius-md); border: none; background: transparent; margin: 0; padding: 0; font-family: inherit; font-size: var(--crisp-text-md); font-weight: var(--crisp-weight-semibold); font-variant-numeric: tabular-nums; color: var(--crisp-fg-primary); }
|
|
8
|
+
& button.crisp-heatmapchart-cell { cursor: pointer; transition: filter 0.12s ease; }
|
|
9
|
+
& button.crisp-heatmapchart-cell:hover { filter: brightness(0.97); }
|
|
10
|
+
& button.crisp-heatmapchart-cell:focus-visible { outline: 2px solid var(--crisp-bg-brand-solid); outline-offset: 1px; }
|
|
11
|
+
& .crisp-heatmapchart-cell[data-tone="neutral"] { background: var(--crisp-bg-raised); color: var(--crisp-fg-muted); }
|
|
12
|
+
& .crisp-heatmapchart-cell[data-tone="success"] { background: color-mix(in srgb, var(--crisp-bg-success) 16%, transparent); }
|
|
13
|
+
& .crisp-heatmapchart-cell[data-tone="warning"] { background: color-mix(in srgb, var(--crisp-bg-warning) 16%, transparent); }
|
|
14
|
+
& .crisp-heatmapchart-cell[data-tone="orange"] { background: color-mix(in srgb, var(--crisp-bg-orange) 18%, transparent); }
|
|
15
|
+
& .crisp-heatmapchart-cell[data-tone="danger"] { background: color-mix(in srgb, var(--crisp-bg-danger) 16%, transparent); }
|
|
16
|
+
& .crisp-heatmapchart-cell[data-ring] { box-shadow: inset 0 0 0 2px var(--crisp-bg-danger-solid); }
|
|
17
|
+
& .crisp-heatmapchart-colaxis { display: flex; justify-content: space-between; margin-top: var(--crisp-space-1-5); padding: 0 var(--crisp-space-0-5); font-size: var(--crisp-text-xs); font-weight: var(--crisp-weight-medium); text-transform: uppercase; letter-spacing: 0.02em; color: var(--crisp-fg-neutral-subtle); }
|
|
18
|
+
& .crisp-heatmapchart-empty { padding: var(--crisp-space-8) 0; text-align: center; font-size: var(--crisp-text-md); color: var(--crisp-fg-muted); }
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
@media (prefers-reduced-motion: reduce) {
|
|
22
|
+
.crisp-heatmapchart, .crisp-heatmapchart * {
|
|
23
|
+
animation-duration: 0.01ms !important;
|
|
24
|
+
animation-iteration-count: 1 !important;
|
|
25
|
+
transition-duration: 0.01ms !important;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
@layer crisp-components {
|
|
2
|
+
.crisp-heatmapchart {
|
|
3
|
+
display: flex; gap: var(--crisp-space-2); box-sizing: border-box; font-family: var(--crisp-font-sans); -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale;
|
|
4
|
+
& .crisp-heatmapchart-rowaxis { display: flex; flex-direction: column; justify-content: space-between; padding: var(--crisp-space-1) 0; font-size: var(--crisp-text-xs); font-weight: var(--crisp-weight-medium); text-transform: uppercase; letter-spacing: 0.02em; color: var(--crisp-fg-neutral-subtle); }
|
|
5
|
+
& .crisp-heatmapchart-rowaxis-title { writing-mode: vertical-rl; transform: rotate(180deg); align-self: center; }
|
|
6
|
+
& .crisp-heatmapchart-body { flex: 1; }
|
|
7
|
+
& .crisp-heatmapchart-grid { display: grid; gap: var(--crisp-space-1-5); }
|
|
8
|
+
& .crisp-heatmapchart-cell { box-sizing: border-box; display: flex; align-items: center; justify-content: center; aspect-ratio: 2 / 1; border-radius: var(--crisp-radius-md); border: none; background: transparent; margin: 0; padding: 0; font-family: inherit; font-size: var(--crisp-text-md); font-weight: var(--crisp-weight-semibold); font-variant-numeric: tabular-nums; color: var(--crisp-fg-primary); }
|
|
9
|
+
& button.crisp-heatmapchart-cell { cursor: pointer; transition: filter 0.12s ease; }
|
|
10
|
+
& button.crisp-heatmapchart-cell:hover { filter: brightness(0.97); }
|
|
11
|
+
& button.crisp-heatmapchart-cell:focus-visible { outline: 2px solid var(--crisp-bg-brand-solid); outline-offset: 1px; }
|
|
12
|
+
& .crisp-heatmapchart-cell[data-tone="neutral"] { background: var(--crisp-bg-raised); color: var(--crisp-fg-muted); }
|
|
13
|
+
& .crisp-heatmapchart-cell[data-tone="success"] { background: color-mix(in srgb, var(--crisp-bg-success) 16%, transparent); }
|
|
14
|
+
& .crisp-heatmapchart-cell[data-tone="warning"] { background: color-mix(in srgb, var(--crisp-bg-warning) 16%, transparent); }
|
|
15
|
+
& .crisp-heatmapchart-cell[data-tone="orange"] { background: color-mix(in srgb, var(--crisp-bg-orange) 18%, transparent); }
|
|
16
|
+
& .crisp-heatmapchart-cell[data-tone="danger"] { background: color-mix(in srgb, var(--crisp-bg-danger) 16%, transparent); }
|
|
17
|
+
& .crisp-heatmapchart-cell[data-ring] { box-shadow: inset 0 0 0 2px var(--crisp-bg-danger-solid); }
|
|
18
|
+
& .crisp-heatmapchart-colaxis { display: flex; justify-content: space-between; margin-top: var(--crisp-space-1-5); padding: 0 var(--crisp-space-0-5); font-size: var(--crisp-text-xs); font-weight: var(--crisp-weight-medium); text-transform: uppercase; letter-spacing: 0.02em; color: var(--crisp-fg-neutral-subtle); }
|
|
19
|
+
& .crisp-heatmapchart-empty { padding: var(--crisp-space-8) 0; text-align: center; font-size: var(--crisp-text-md); color: var(--crisp-fg-muted); }
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
@media (prefers-reduced-motion: reduce) {
|
|
23
|
+
.crisp-heatmapchart, .crisp-heatmapchart * {
|
|
24
|
+
animation-duration: 0.01ms !important;
|
|
25
|
+
animation-iteration-count: 1 !important;
|
|
26
|
+
transition-duration: 0.01ms !important;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
.crisp-statlist {
|
|
2
|
+
display: flex; flex-direction: column; box-sizing: border-box; font-family: var(--crisp-font-sans); -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale;
|
|
3
|
+
& .crisp-statlist-row { display: flex; align-items: center; justify-content: space-between; gap: var(--crisp-space-3); padding: var(--crisp-space-2-5) 0; border-top: 1px solid var(--crisp-border-subtle); font-size: var(--crisp-text-md); color: inherit; text-decoration: none; }
|
|
4
|
+
& .crisp-statlist-row:first-child { border-top: none; }
|
|
5
|
+
& a.crisp-statlist-row:hover .crisp-statlist-label { color: var(--crisp-fg-brand-soft); }
|
|
6
|
+
& .crisp-statlist-label { color: var(--crisp-fg-muted); }
|
|
7
|
+
& .crisp-statlist-empty { padding: var(--crisp-space-4) 0; text-align: center; font-size: var(--crisp-text-md); color: var(--crisp-fg-muted); }
|
|
8
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
@layer crisp-components {
|
|
2
|
+
.crisp-statlist {
|
|
3
|
+
display: flex; flex-direction: column; box-sizing: border-box; font-family: var(--crisp-font-sans); -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale;
|
|
4
|
+
& .crisp-statlist-row { display: flex; align-items: center; justify-content: space-between; gap: var(--crisp-space-3); padding: var(--crisp-space-2-5) 0; border-top: 1px solid var(--crisp-border-subtle); font-size: var(--crisp-text-md); color: inherit; text-decoration: none; }
|
|
5
|
+
& .crisp-statlist-row:first-child { border-top: none; }
|
|
6
|
+
& a.crisp-statlist-row:hover .crisp-statlist-label { color: var(--crisp-fg-brand-soft); }
|
|
7
|
+
& .crisp-statlist-label { color: var(--crisp-fg-muted); }
|
|
8
|
+
& .crisp-statlist-empty { padding: var(--crisp-space-4) 0; text-align: center; font-size: var(--crisp-text-md); color: var(--crisp-fg-muted); }
|
|
9
|
+
}
|
|
10
|
+
}
|
package/dist/styles.css
CHANGED
|
@@ -1028,6 +1028,34 @@
|
|
|
1028
1028
|
& .crisp-formfield-error { font-size: var(--crisp-text-sm); line-height: 16px; color: var(--crisp-bg-danger); }
|
|
1029
1029
|
}
|
|
1030
1030
|
|
|
1031
|
+
.crisp-heatmapchart {
|
|
1032
|
+
display: flex; gap: var(--crisp-space-2); box-sizing: border-box; font-family: var(--crisp-font-sans); -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale;
|
|
1033
|
+
& .crisp-heatmapchart-rowaxis { display: flex; flex-direction: column; justify-content: space-between; padding: var(--crisp-space-1) 0; font-size: var(--crisp-text-xs); font-weight: var(--crisp-weight-medium); text-transform: uppercase; letter-spacing: 0.02em; color: var(--crisp-fg-neutral-subtle); }
|
|
1034
|
+
& .crisp-heatmapchart-rowaxis-title { writing-mode: vertical-rl; transform: rotate(180deg); align-self: center; }
|
|
1035
|
+
& .crisp-heatmapchart-body { flex: 1; }
|
|
1036
|
+
& .crisp-heatmapchart-grid { display: grid; gap: var(--crisp-space-1-5); }
|
|
1037
|
+
& .crisp-heatmapchart-cell { box-sizing: border-box; display: flex; align-items: center; justify-content: center; aspect-ratio: 2 / 1; border-radius: var(--crisp-radius-md); border: none; background: transparent; margin: 0; padding: 0; font-family: inherit; font-size: var(--crisp-text-md); font-weight: var(--crisp-weight-semibold); font-variant-numeric: tabular-nums; color: var(--crisp-fg-primary); }
|
|
1038
|
+
& button.crisp-heatmapchart-cell { cursor: pointer; transition: filter 0.12s ease; }
|
|
1039
|
+
& button.crisp-heatmapchart-cell:hover { filter: brightness(0.97); }
|
|
1040
|
+
& button.crisp-heatmapchart-cell:focus-visible { outline: 2px solid var(--crisp-bg-brand-solid); outline-offset: 1px; }
|
|
1041
|
+
& .crisp-heatmapchart-cell[data-tone="neutral"] { background: var(--crisp-bg-raised); color: var(--crisp-fg-muted); }
|
|
1042
|
+
& .crisp-heatmapchart-cell[data-tone="success"] { background: color-mix(in srgb, var(--crisp-bg-success) 16%, transparent); }
|
|
1043
|
+
& .crisp-heatmapchart-cell[data-tone="warning"] { background: color-mix(in srgb, var(--crisp-bg-warning) 16%, transparent); }
|
|
1044
|
+
& .crisp-heatmapchart-cell[data-tone="orange"] { background: color-mix(in srgb, var(--crisp-bg-orange) 18%, transparent); }
|
|
1045
|
+
& .crisp-heatmapchart-cell[data-tone="danger"] { background: color-mix(in srgb, var(--crisp-bg-danger) 16%, transparent); }
|
|
1046
|
+
& .crisp-heatmapchart-cell[data-ring] { box-shadow: inset 0 0 0 2px var(--crisp-bg-danger-solid); }
|
|
1047
|
+
& .crisp-heatmapchart-colaxis { display: flex; justify-content: space-between; margin-top: var(--crisp-space-1-5); padding: 0 var(--crisp-space-0-5); font-size: var(--crisp-text-xs); font-weight: var(--crisp-weight-medium); text-transform: uppercase; letter-spacing: 0.02em; color: var(--crisp-fg-neutral-subtle); }
|
|
1048
|
+
& .crisp-heatmapchart-empty { padding: var(--crisp-space-8) 0; text-align: center; font-size: var(--crisp-text-md); color: var(--crisp-fg-muted); }
|
|
1049
|
+
}
|
|
1050
|
+
|
|
1051
|
+
@media (prefers-reduced-motion: reduce) {
|
|
1052
|
+
.crisp-heatmapchart, .crisp-heatmapchart * {
|
|
1053
|
+
animation-duration: 0.01ms !important;
|
|
1054
|
+
animation-iteration-count: 1 !important;
|
|
1055
|
+
transition-duration: 0.01ms !important;
|
|
1056
|
+
}
|
|
1057
|
+
}
|
|
1058
|
+
|
|
1031
1059
|
.crisp-input {
|
|
1032
1060
|
position: relative; box-sizing: border-box; display: inline-flex; align-items: center; width: 100%; background: var(--crisp-bg-surface); box-shadow: inset 0 0 0 1px var(--crisp-ring-field); font-family: var(--crisp-font-sans); font-size: var(--crisp-text-lg); line-height: 20px;
|
|
1033
1061
|
&::after { content: ""; position: absolute; inset: 0; border-radius: inherit; border: 2px solid transparent; transition: border-color .1s ease; pointer-events: none; }
|
|
@@ -1758,8 +1786,8 @@
|
|
|
1758
1786
|
|
|
1759
1787
|
.crisp-toast {
|
|
1760
1788
|
display: contents; font-family: var(--crisp-font-sans);
|
|
1761
|
-
& .crisp-toast-viewport { position: fixed; z-index: 80; inset-inline-end: var(--crisp-space-4);
|
|
1762
|
-
& .crisp-toast { pointer-events: auto; display: flex; align-items: flex-start; gap: var(--crisp-space-2-5); box-sizing: border-box; padding: var(--crisp-space-
|
|
1789
|
+
& .crisp-toast-viewport { position: fixed; z-index: 80; inset-inline-end: var(--crisp-space-4); top: var(--crisp-space-4); display: flex; flex-direction: column; gap: var(--crisp-space-2); width: 360px; max-width: calc(100vw - var(--crisp-space-8)); pointer-events: none; }
|
|
1790
|
+
& .crisp-toast { pointer-events: auto; display: flex; align-items: flex-start; gap: var(--crisp-space-2-5); box-sizing: border-box; padding: var(--crisp-space-2-5); border-radius: var(--crisp-radius-md); background: var(--crisp-bg-popover); box-shadow: inset 0 0 0 1px var(--crisp-ring-popover), 0 0 0 1px var(--crisp-shadow-popover1), 0 4px 8px -4px var(--crisp-shadow-popover2), 0 4px 12px -2px var(--crisp-shadow-popover3); opacity: 1; transform: translateY(0); transition: opacity 0.15s ease, transform 0.15s ease; @starting-style { opacity: 0; transform: translateY(8px); } }
|
|
1763
1791
|
& .crisp-toast-icon { flex-shrink: 0; margin-top: 1px; display: inline-flex; color: var(--crisp-fg-muted); }
|
|
1764
1792
|
& .crisp-toast[data-tone="info"] .crisp-toast-icon { color: var(--crisp-bg-brand-solid); }
|
|
1765
1793
|
& .crisp-toast[data-tone="success"] .crisp-toast-icon { color: var(--crisp-bg-success); }
|
|
@@ -2729,6 +2757,15 @@
|
|
|
2729
2757
|
}
|
|
2730
2758
|
}
|
|
2731
2759
|
|
|
2760
|
+
.crisp-statlist {
|
|
2761
|
+
display: flex; flex-direction: column; box-sizing: border-box; font-family: var(--crisp-font-sans); -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale;
|
|
2762
|
+
& .crisp-statlist-row { display: flex; align-items: center; justify-content: space-between; gap: var(--crisp-space-3); padding: var(--crisp-space-2-5) 0; border-top: 1px solid var(--crisp-border-subtle); font-size: var(--crisp-text-md); color: inherit; text-decoration: none; }
|
|
2763
|
+
& .crisp-statlist-row:first-child { border-top: none; }
|
|
2764
|
+
& a.crisp-statlist-row:hover .crisp-statlist-label { color: var(--crisp-fg-brand-soft); }
|
|
2765
|
+
& .crisp-statlist-label { color: var(--crisp-fg-muted); }
|
|
2766
|
+
& .crisp-statlist-empty { padding: var(--crisp-space-4) 0; text-align: center; font-size: var(--crisp-text-md); color: var(--crisp-fg-muted); }
|
|
2767
|
+
}
|
|
2768
|
+
|
|
2732
2769
|
.crisp-tasklist {
|
|
2733
2770
|
display: flex; flex-direction: column; box-sizing: border-box; font-family: var(--crisp-font-sans); -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale;
|
|
2734
2771
|
& .crisp-tasklist-group { display: flex; align-items: center; gap: var(--crisp-space-1-5); height: 36px; padding: 0 var(--crisp-space-2); font-size: var(--crisp-text-md); font-weight: var(--crisp-weight-medium); color: var(--crisp-fg-muted); }
|