@crisp-ui-kit/crisp 0.54.2 → 0.54.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/board.css +13 -1
- package/dist/board.layered.css +13 -1
- package/dist/chip.css +7 -1
- package/dist/chip.d.ts +2 -1
- package/dist/chip.layered.css +7 -1
- package/dist/cjs/components/chip/chip.js +3 -2
- package/dist/cjs/components/chip/chip.recipe.js +37 -1
- package/dist/cjs/components/datatable/datatable.js +489 -111
- package/dist/cjs/components/emptystate/emptystate.js +3 -2
- package/dist/cjs/components/emptystate/emptystate.recipe.js +26 -1
- package/dist/cjs/components/personchip/index.js +17 -0
- package/dist/cjs/components/personchip/personchip.js +50 -0
- package/dist/cjs/components/table/table.js +2 -2
- package/dist/cjs/components/tabletoolbar/tabletoolbar.js +41 -4
- package/dist/cjs/components/tabs/tabs.js +2 -2
- package/dist/cjs/components/tabs/tabs.recipe.js +30 -3
- package/dist/cjs/components/text/index.js +7 -0
- package/dist/cjs/components/text/text.js +55 -0
- package/dist/cjs/components/text/text.recipe.js +34 -0
- package/dist/cjs/index.js +4 -0
- package/dist/cjs/product/board/board.js +60 -2
- package/dist/cjs/product/board/board.recipe.js +10 -0
- package/dist/cjs/product/comments/comments.js +426 -0
- package/dist/cjs/product/comments/comments.recipe.js +126 -0
- package/dist/cjs/product/comments/index.js +8 -0
- package/dist/cjs/product/headerbar/headerbar.recipe.js +9 -1
- package/dist/cjs/product/listpage/listpage.recipe.js +10 -1
- package/dist/cjs/product/objectsettings/index.js +16 -0
- package/dist/cjs/product/objectsettings/objectsettings.js +365 -0
- package/dist/cjs/product/objectsettings/objectsettings.recipe.js +65 -0
- package/dist/cjs/product/recordpage/recordpage.recipe.js +10 -1
- package/dist/cjs/product/recordstable/recordstable.js +89 -6
- package/dist/cjs/product/richtext/richtext.recipe.js +26 -1
- package/dist/cjs/product/taskstable/taskstable.recipe.js +10 -1
- package/dist/comments.css +80 -0
- package/dist/comments.d.ts +7 -0
- package/dist/comments.layered.css +82 -0
- package/dist/emptystate.css +15 -1
- package/dist/emptystate.d.ts +2 -2
- package/dist/emptystate.layered.css +15 -1
- package/dist/esm/components/chip/chip.js +3 -2
- package/dist/esm/components/chip/chip.recipe.js +37 -1
- package/dist/esm/components/datatable/datatable.js +488 -111
- package/dist/esm/components/emptystate/emptystate.js +4 -3
- package/dist/esm/components/emptystate/emptystate.recipe.js +26 -1
- package/dist/esm/components/emptystate/index.js +1 -1
- package/dist/esm/components/personchip/index.js +1 -0
- package/dist/esm/components/personchip/personchip.js +47 -0
- package/dist/esm/components/table/table.js +2 -2
- package/dist/esm/components/tabletoolbar/tabletoolbar.js +43 -6
- package/dist/esm/components/tabs/tabs.js +2 -2
- package/dist/esm/components/tabs/tabs.recipe.js +30 -3
- package/dist/esm/components/text/index.js +2 -0
- package/dist/esm/components/text/text.js +19 -0
- package/dist/esm/components/text/text.recipe.js +31 -0
- package/dist/esm/index.js +4 -0
- package/dist/esm/product/board/board.js +61 -3
- package/dist/esm/product/board/board.recipe.js +10 -0
- package/dist/esm/product/comments/comments.js +388 -0
- package/dist/esm/product/comments/comments.recipe.js +123 -0
- package/dist/esm/product/comments/index.js +2 -0
- package/dist/esm/product/headerbar/headerbar.recipe.js +9 -1
- package/dist/esm/product/listpage/listpage.recipe.js +10 -1
- package/dist/esm/product/objectsettings/index.js +2 -0
- package/dist/esm/product/objectsettings/objectsettings.js +322 -0
- package/dist/esm/product/objectsettings/objectsettings.recipe.js +62 -0
- package/dist/esm/product/recordpage/recordpage.recipe.js +10 -1
- package/dist/esm/product/recordstable/recordstable.js +90 -8
- package/dist/esm/product/richtext/richtext.recipe.js +26 -1
- package/dist/esm/product/taskstable/taskstable.recipe.js +10 -1
- package/dist/headerbar.css +1 -1
- package/dist/headerbar.layered.css +1 -1
- package/dist/listpage.css +8 -1
- package/dist/listpage.layered.css +8 -1
- package/dist/objectsettings.css +49 -0
- package/dist/objectsettings.d.ts +7 -0
- package/dist/objectsettings.layered.css +51 -0
- package/dist/recordpage.css +13 -2
- package/dist/recordpage.layered.css +13 -2
- package/dist/richtext.css +11 -1
- package/dist/richtext.layered.css +11 -1
- package/dist/styles.css +224 -10
- package/dist/styles.layered.css +224 -10
- package/dist/tabs.css +7 -1
- package/dist/tabs.d.ts +2 -2
- package/dist/tabs.layered.css +7 -1
- package/dist/taskstable.css +8 -1
- package/dist/taskstable.layered.css +8 -1
- package/dist/text.css +9 -0
- package/dist/text.d.ts +7 -0
- package/dist/text.layered.css +11 -0
- package/dist/types/components/chip/chip.d.ts +32 -0
- package/dist/types/components/chip/chip.recipe.d.ts +31 -0
- package/dist/types/components/datatable/datatable.d.ts +28 -4
- package/dist/types/components/emptystate/emptystate.d.ts +29 -4
- package/dist/types/components/emptystate/emptystate.recipe.d.ts +2 -0
- package/dist/types/components/emptystate/index.d.ts +1 -1
- package/dist/types/components/personchip/index.d.ts +1 -0
- package/dist/types/components/personchip/personchip.d.ts +37 -0
- package/dist/types/components/table/table.d.ts +36 -0
- package/dist/types/components/tabletoolbar/tabletoolbar.d.ts +8 -2
- package/dist/types/components/tabs/tabs.d.ts +4 -2
- package/dist/types/components/tabs/tabs.recipe.d.ts +18 -3
- package/dist/types/components/text/index.d.ts +2 -0
- package/dist/types/components/text/text.d.ts +32 -0
- package/dist/types/components/text/text.recipe.d.ts +40 -0
- package/dist/types/index.d.ts +4 -0
- package/dist/types/product/board/board.d.ts +20 -1
- package/dist/types/product/comments/comments.d.ts +192 -0
- package/dist/types/product/comments/comments.recipe.d.ts +3 -0
- package/dist/types/product/comments/index.d.ts +2 -0
- package/dist/types/product/objectsettings/index.d.ts +2 -0
- package/dist/types/product/objectsettings/objectsettings.d.ts +261 -0
- package/dist/types/product/objectsettings/objectsettings.recipe.d.ts +3 -0
- package/dist/types/product/recordstable/recordstable.d.ts +24 -2
- package/package.json +21 -1
|
@@ -4,7 +4,8 @@ exports.EmptyState = EmptyState;
|
|
|
4
4
|
const jsx_runtime_1 = require("react/jsx-runtime");
|
|
5
5
|
const emptystate_recipe_1 = require("./emptystate.recipe");
|
|
6
6
|
/** A centred empty-state panel — icon, title, description, optional action. */
|
|
7
|
-
function EmptyState({ icon, title, description, action, variant, className, ...rest }) {
|
|
8
|
-
|
|
7
|
+
function EmptyState({ icon, title, description, action, learnMore, variant, className, ...rest }) {
|
|
8
|
+
const learnMoreInner = learnMore && ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsx)("span", { className: "crisp-emptystate-learnmore-icon", children: learnMore.icon }), (0, jsx_runtime_1.jsx)("span", { className: "crisp-emptystate-learnmore-title", children: learnMore.title })] }));
|
|
9
|
+
return ((0, jsx_runtime_1.jsxs)("div", { className: (0, emptystate_recipe_1.emptystateRecipe)({ variant, className }), ...rest, children: [icon && (0, jsx_runtime_1.jsx)("span", { className: "crisp-emptystate-icon", children: icon }), (0, jsx_runtime_1.jsx)("div", { className: "crisp-emptystate-title", children: title }), description != null && ((0, jsx_runtime_1.jsx)("div", { className: "crisp-emptystate-desc", children: description })), action && (0, jsx_runtime_1.jsx)("div", { className: "crisp-emptystate-action", children: action }), learnMore && ((0, jsx_runtime_1.jsxs)("div", { className: "crisp-emptystate-learnmore", children: [(0, jsx_runtime_1.jsx)("div", { className: "crisp-emptystate-learnmore-label", children: learnMore.label ?? "Learn more" }), learnMore.href ? ((0, jsx_runtime_1.jsx)("a", { className: "crisp-emptystate-learnmore-card", href: learnMore.href, onClick: learnMore.onClick, children: learnMoreInner })) : ((0, jsx_runtime_1.jsx)("button", { type: "button", className: "crisp-emptystate-learnmore-card", onClick: learnMore.onClick, children: learnMoreInner }))] }))] }));
|
|
9
10
|
}
|
|
10
11
|
EmptyState.displayName = "EmptyState";
|
|
@@ -10,7 +10,18 @@ const config = (0, core_1.defineRecipe)({
|
|
|
10
10
|
& .crisp-emptystate-icon { display: inline-flex; align-items: center; justify-content: center; width: 40px; height: 40px; margin-bottom: ${tokens_1.contract.space["1"]}; border-radius: ${tokens_1.contract.radius.xl}; background: color-mix(in srgb, ${tokens_1.contract.fgPrimary} 5%, transparent); color: ${tokens_1.contract.fgMuted}; }
|
|
11
11
|
& .crisp-emptystate-title { font-size: ${tokens_1.contract.text.lg}; font-weight: ${tokens_1.contract.weight.semibold}; color: ${tokens_1.contract.fgPrimary}; }
|
|
12
12
|
& .crisp-emptystate-desc { max-width: 320px; font-size: ${tokens_1.contract.text.md}; color: ${tokens_1.contract.fgMuted}; }
|
|
13
|
-
|
|
13
|
+
/* #603: measured 8px gap between "Import CSV"/"Connect data source" --
|
|
14
|
+
inline-flex so one action (the common case) or several lay out the
|
|
15
|
+
same way, wrapping if the container is narrow. */
|
|
16
|
+
& .crisp-emptystate-action { display: inline-flex; align-items: center; flex-wrap: wrap; justify-content: center; gap: ${tokens_1.contract.space["2"]}; margin-top: ${tokens_1.contract.space["2"]}; }
|
|
17
|
+
/* #603: Attio's zero-records "Learn more" card -- a label above a
|
|
18
|
+
clickable icon+title tile. Left/start-aligned, unlike the rest of
|
|
19
|
+
this centred panel, so it overrides the base text-align. */
|
|
20
|
+
& .crisp-emptystate-learnmore { display: flex; flex-direction: column; align-items: stretch; gap: ${tokens_1.contract.space["2"]}; margin-top: ${tokens_1.contract.space["6"]}; text-align: start; }
|
|
21
|
+
& .crisp-emptystate-learnmore-label { font-size: ${tokens_1.contract.text.sm}; font-weight: ${tokens_1.contract.weight.medium}; color: color-mix(in srgb, ${tokens_1.contract.fgPrimary} 50%, transparent); }
|
|
22
|
+
& .crisp-emptystate-learnmore-card { display: flex; align-items: center; gap: ${tokens_1.contract.space["2"]}; padding: ${tokens_1.contract.space["2"]} ${tokens_1.contract.space["3"]} ${tokens_1.contract.space["2"]} ${tokens_1.contract.space["2"]}; margin: 0; border: 1px solid ${tokens_1.contract.borderSubtle}; border-radius: 18px; background: ${tokens_1.contract.bgSurface}; font: inherit; text-align: start; text-decoration: none; color: inherit; cursor: pointer; }
|
|
23
|
+
& .crisp-emptystate-learnmore-icon { display: inline-flex; align-items: center; justify-content: center; width: 56px; height: 56px; flex-shrink: 0; border-radius: ${tokens_1.contract.radius.xl}; background: color-mix(in srgb, ${tokens_1.contract.fgPrimary} 2%, transparent); color: ${tokens_1.contract.fgMuted}; }
|
|
24
|
+
& .crisp-emptystate-learnmore-title { font-size: ${tokens_1.contract.text.lg}; font-weight: ${tokens_1.contract.weight.medium}; color: ${tokens_1.contract.fgPrimary}; }`,
|
|
14
25
|
variants: {
|
|
15
26
|
variant: {
|
|
16
27
|
// compact icon tile (default)
|
|
@@ -25,6 +36,20 @@ const config = (0, core_1.defineRecipe)({
|
|
|
25
36
|
& .crisp-emptystate-title { font-size: 20px; line-height: 24px; }
|
|
26
37
|
& .crisp-emptystate-desc { max-width: 266px; font-size: ${tokens_1.contract.text.lg}; font-weight: ${tokens_1.contract.weight.medium}; line-height: 20px; }
|
|
27
38
|
& .crisp-emptystate-action { margin-top: ${tokens_1.contract.space["2"]}; }`,
|
|
39
|
+
// #603: Attio's zero-*records* table state (Users/Workspaces with
|
|
40
|
+
// nothing imported) -- the same dotted-grid backdrop `illustrated`
|
|
41
|
+
// uses, at its own measured 138x138 square (illustrated's is a
|
|
42
|
+
// 132x96 rectangle; different surface, not the same number).
|
|
43
|
+
// Attio's own illustration also draws four small "+" tick marks at
|
|
44
|
+
// the tile's corners -- a single proprietary SVG asset (confirmed
|
|
45
|
+
// by inspecting the live DOM: one <svg> with no separately
|
|
46
|
+
// measurable sub-elements), so not reproduced here per AUTHORING's
|
|
47
|
+
// "never copy Attio's icon set" rule; the icon itself is supplied
|
|
48
|
+
// by the consumer via the existing `icon` prop, same as every
|
|
49
|
+
// other variant.
|
|
50
|
+
records: `& .crisp-emptystate-icon { width: 138px; height: 138px; margin-bottom: ${tokens_1.contract.space["2"]}; border-radius: 0; color: color-mix(in srgb, ${tokens_1.contract.fgPrimary} 22%, transparent); background: radial-gradient(circle, color-mix(in srgb, ${tokens_1.contract.fgPrimary} 12%, transparent) 1.2px, transparent 1.2px) 0 0 / 16px 16px; }
|
|
51
|
+
& .crisp-emptystate-title { font-size: 20px; line-height: 24px; }
|
|
52
|
+
& .crisp-emptystate-desc { max-width: 266px; font-size: ${tokens_1.contract.text.lg}; font-weight: ${tokens_1.contract.weight.medium}; line-height: 20px; }`,
|
|
28
53
|
},
|
|
29
54
|
},
|
|
30
55
|
defaultVariants: { variant: "default" },
|
|
@@ -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("./personchip"), exports);
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.PersonChip = PersonChip;
|
|
4
|
+
const jsx_runtime_1 = require("react/jsx-runtime");
|
|
5
|
+
const tokens_1 = require("../../tokens");
|
|
6
|
+
const avatar_1 = require("../avatar");
|
|
7
|
+
const chip_1 = require("../chip");
|
|
8
|
+
// The same five hashed avatar tones DataTable's own `avatarTone="hashed"`
|
|
9
|
+
// picks from (datatable.tsx's private `TAG_TONES`, transcribed rather than
|
|
10
|
+
// imported — DataTable owns its own file, and this is a one-line pure
|
|
11
|
+
// function, not a visual value that could drift). `neutral`/`brand` are
|
|
12
|
+
// left out: `neutral` is Avatar's own no-photo default (wrong here, every
|
|
13
|
+
// name needs a colour to tell people apart) and `brand` is reserved for an
|
|
14
|
+
// entity that IS the record a cell points at (`RecordChip`'s own tone),
|
|
15
|
+
// which a person mention never is.
|
|
16
|
+
const HASH_TONES = ["brand", "orange", "cyan", "purple", "green"];
|
|
17
|
+
function hashTone(name) {
|
|
18
|
+
let h = 0;
|
|
19
|
+
for (let i = 0; i < name.length; i++)
|
|
20
|
+
h = (h * 31 + name.charCodeAt(i)) >>> 0;
|
|
21
|
+
return HASH_TONES[h % HASH_TONES.length];
|
|
22
|
+
}
|
|
23
|
+
function initials(name) {
|
|
24
|
+
const parts = name.trim().split(/\s+/).filter(Boolean);
|
|
25
|
+
if (parts.length === 0)
|
|
26
|
+
return "?";
|
|
27
|
+
return (parts[0][0] + (parts[1]?.[0] ?? "")).toUpperCase();
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* A person, as a chip: a small avatar (a photo, or hashed-colour initials)
|
|
31
|
+
* plus their name, truncating with an ellipsis when it doesn't fit — Attio's
|
|
32
|
+
* own actor chip (measured live on `techpartners`, Tasks page, "Assigned to"
|
|
33
|
+
* column): a 16px circular avatar in a neutral `Chip`, `gap: 3px` between
|
|
34
|
+
* avatar and name (now `Chip`'s own base — every multi-child `Chip` gets
|
|
35
|
+
* it). Every other value — the pill's height, padding, radius, ring and text
|
|
36
|
+
* — is already `Chip`'s, unchanged; `PersonChip` adds no recipe of its own.
|
|
37
|
+
*
|
|
38
|
+
* Ellipsis only, no `Tooltip` on hover (#623 — no Attio reference for one;
|
|
39
|
+
* a truncated cell there is revealed by its own click-to-expand, #579, a
|
|
40
|
+
* different, DataTable-specific interaction).
|
|
41
|
+
*
|
|
42
|
+
* Composes `Chip`'s `truncate` CSS directly rather than its string-only
|
|
43
|
+
* auto-wrap (children here is an avatar plus a name, not a single string),
|
|
44
|
+
* so the name gets its own `.crisp-chip-label` span while the avatar stays
|
|
45
|
+
* fixed-size.
|
|
46
|
+
*/
|
|
47
|
+
function PersonChip({ name, avatar, muted, className, ...props }) {
|
|
48
|
+
return ((0, jsx_runtime_1.jsxs)(chip_1.Chip, { tone: "neutral", truncate: true, className: className, ...props, children: [(0, jsx_runtime_1.jsx)(avatar_1.Avatar, { size: "sm", shape: "circle", tone: avatar ? "neutral" : hashTone(name), children: avatar ? (0, jsx_runtime_1.jsx)("img", { src: avatar, alt: "" }) : initials(name) }), (0, jsx_runtime_1.jsx)("span", { className: "crisp-chip-label", style: muted ? { color: tokens_1.contract.fgNeutralMuted } : undefined, children: name })] }));
|
|
49
|
+
}
|
|
50
|
+
PersonChip.displayName = "PersonChip";
|
|
@@ -48,11 +48,11 @@ exports.Table = React.forwardRef(({ interactive, surface, className, wrapperClas
|
|
|
48
48
|
// on the root for the same reason as Textarea: the wrapper is the box a
|
|
49
49
|
// consumer sees and the scroll container they want to size. Native
|
|
50
50
|
// table attributes still reach the <table> below.
|
|
51
|
-
style, ...props }, ref) => ((0, jsx_runtime_1.jsx)("div", { className: (0, table_recipe_1.tableRecipe)({
|
|
51
|
+
style, tableStyle, ...props }, ref) => ((0, jsx_runtime_1.jsx)("div", { className: (0, table_recipe_1.tableRecipe)({
|
|
52
52
|
interactive,
|
|
53
53
|
surface,
|
|
54
54
|
className: wrapperClassName,
|
|
55
|
-
}), style: style, children: (0, jsx_runtime_1.jsx)("div", { ...scrollProps, className: `crisp-table-scroll${scrollProps?.className ? ` ${scrollProps.className}` : ""}`, children: (0, jsx_runtime_1.jsx)("table", { ref: ref, className: className, ...props, children: children }) }) })));
|
|
55
|
+
}), style: style, children: (0, jsx_runtime_1.jsx)("div", { ...scrollProps, className: `crisp-table-scroll${scrollProps?.className ? ` ${scrollProps.className}` : ""}`, children: (0, jsx_runtime_1.jsx)("table", { ref: ref, className: className, style: tableStyle, ...props, children: children }) }) })));
|
|
56
56
|
exports.Table.displayName = "Table";
|
|
57
57
|
exports.TableHeader = React.forwardRef((props, ref) => (0, jsx_runtime_1.jsx)("thead", { ref: ref, ...props }));
|
|
58
58
|
exports.TableHeader.displayName = "TableHeader";
|
|
@@ -46,14 +46,24 @@ const select_1 = require("../select");
|
|
|
46
46
|
const tabletoolbar_recipe_1 = require("./tabletoolbar.recipe");
|
|
47
47
|
const OP_LABELS = {
|
|
48
48
|
contains: "contains",
|
|
49
|
+
doesnotcontain: "does not contain",
|
|
50
|
+
startswith: "starts with",
|
|
51
|
+
endswith: "ends with",
|
|
49
52
|
is: "is",
|
|
50
53
|
isnot: "is not",
|
|
51
54
|
isanyof: "is any of",
|
|
52
55
|
empty: "is empty",
|
|
53
56
|
notempty: "is not empty",
|
|
54
57
|
};
|
|
58
|
+
// #619: order and label set match Attio's own text-filter operator picker
|
|
59
|
+
// verbatim (measured live, per RecordsTable's own #578 port of the same
|
|
60
|
+
// set: contains · does not contain · starts with · ends with · is · is not ·
|
|
61
|
+
// empty · not empty).
|
|
55
62
|
const OP_ORDER = [
|
|
56
63
|
"contains",
|
|
64
|
+
"doesnotcontain",
|
|
65
|
+
"startswith",
|
|
66
|
+
"endswith",
|
|
57
67
|
"is",
|
|
58
68
|
"isnot",
|
|
59
69
|
"empty",
|
|
@@ -62,7 +72,12 @@ const OP_ORDER = [
|
|
|
62
72
|
// "isanyof" is appended only for a column that supplies `values` — there is
|
|
63
73
|
// nothing to check off otherwise.
|
|
64
74
|
const opsFor = (col) => col?.values?.length ? [...OP_ORDER, "isanyof"] : OP_ORDER;
|
|
65
|
-
const opNeedsValue = (op) => op === "contains" ||
|
|
75
|
+
const opNeedsValue = (op) => op === "contains" ||
|
|
76
|
+
op === "doesnotcontain" ||
|
|
77
|
+
op === "startswith" ||
|
|
78
|
+
op === "endswith" ||
|
|
79
|
+
op === "is" ||
|
|
80
|
+
op === "isnot";
|
|
66
81
|
const opNeedsValues = (op) => op === "isanyof";
|
|
67
82
|
// "Department is any of IT, Finance" past two values becomes "… +N": the
|
|
68
83
|
// chip is one line, and a field can have far more than two facet values.
|
|
@@ -110,9 +125,15 @@ function applyFilters(rows, filters, get = (row, key) => row[key]) {
|
|
|
110
125
|
const v = f.value.trim().toLowerCase();
|
|
111
126
|
if (f.op === "contains")
|
|
112
127
|
return t.includes(v);
|
|
128
|
+
if (f.op === "doesnotcontain")
|
|
129
|
+
return !t.includes(v);
|
|
130
|
+
if (f.op === "startswith")
|
|
131
|
+
return t.startsWith(v);
|
|
132
|
+
if (f.op === "endswith")
|
|
133
|
+
return t.endsWith(v);
|
|
113
134
|
if (f.op === "is")
|
|
114
135
|
return t === v;
|
|
115
|
-
return t !== v;
|
|
136
|
+
return t !== v; // isnot
|
|
116
137
|
}));
|
|
117
138
|
}
|
|
118
139
|
/**
|
|
@@ -156,7 +177,7 @@ function applySorts(rows, sorts, get = (row, key) => row[key]) {
|
|
|
156
177
|
* this (Sort: Due date/Assignee/Creation date; Filter: Assignee/Record/
|
|
157
178
|
* Created by), which one shared list can't reproduce.
|
|
158
179
|
*/
|
|
159
|
-
function TableToolbar({ columns, sortColumns: sortColumnsProp, filterColumns: filterColumnsProp, sorts: sortsProp, defaultSorts, onSortsChange, filters: filtersProp, defaultFilters, onFiltersChange, count, children, helpHref, className, sortLabel: sortLabelProp, filterLabel: filterLabelProp, reorderSortLabel: reorderSortLabelProp, noSortsLabel: noSortsLabelProp, addSortLabel: addSortLabelProp, learnSortingLabel: learnSortingLabelProp, selectConditionLabel: selectConditionLabelProp, sortFieldLabel: sortFieldLabelProp, sortDirectionLabel: sortDirectionLabelProp, removeSortLabel: removeSortLabelProp, filterFieldLabel: filterFieldLabelProp, filterFieldPlaceholder: filterFieldPlaceholderProp, filterValuePlaceholder: filterValuePlaceholderProp, ascendingLabel: ascendingLabelProp, descendingLabel: descendingLabelProp, sortTriggerLabel, filterTriggerLabel, inline, ...rest }) {
|
|
180
|
+
function TableToolbar({ columns, sortColumns: sortColumnsProp, filterColumns: filterColumnsProp, sorts: sortsProp, defaultSorts, onSortsChange, filters: filtersProp, defaultFilters, onFiltersChange, count, children, helpHref, className, sortLabel: sortLabelProp, filterLabel: filterLabelProp, reorderSortLabel: reorderSortLabelProp, noSortsLabel: noSortsLabelProp, addSortLabel: addSortLabelProp, learnSortingLabel: learnSortingLabelProp, selectConditionLabel: selectConditionLabelProp, sortFieldLabel: sortFieldLabelProp, sortDirectionLabel: sortDirectionLabelProp, removeSortLabel: removeSortLabelProp, filterFieldLabel: filterFieldLabelProp, filterFieldPlaceholder: filterFieldPlaceholderProp, filterValuePlaceholder: filterValuePlaceholderProp, filterOptionsLabel: filterOptionsLabelProp, deleteFilterLabel: deleteFilterLabelProp, ascendingLabel: ascendingLabelProp, descendingLabel: descendingLabelProp, sortTriggerLabel, filterTriggerLabel, inline, ...rest }) {
|
|
160
181
|
// crisp/#471: an explicit prop always wins; missing, it reads the nearest
|
|
161
182
|
// CrispMessagesProvider's translation of the English default below, or
|
|
162
183
|
// that default itself with no provider — unchanged from before this
|
|
@@ -175,6 +196,8 @@ function TableToolbar({ columns, sortColumns: sortColumnsProp, filterColumns: fi
|
|
|
175
196
|
const filterFieldLabel = (0, core_1.useCrispMessage)(filterFieldLabelProp, "Filter by field");
|
|
176
197
|
const filterFieldPlaceholder = (0, core_1.useCrispMessage)(filterFieldPlaceholderProp, "Filter by…");
|
|
177
198
|
const filterValuePlaceholder = (0, core_1.useCrispMessage)(filterValuePlaceholderProp, "Value…");
|
|
199
|
+
const filterOptionsLabel = (0, core_1.useCrispMessage)(filterOptionsLabelProp, "Filter options");
|
|
200
|
+
const deleteFilterLabel = (0, core_1.useCrispMessage)(deleteFilterLabelProp, "Delete filter");
|
|
178
201
|
const ascendingLabel = (0, core_1.useCrispMessage)(ascendingLabelProp, "Ascending");
|
|
179
202
|
const descendingLabel = (0, core_1.useCrispMessage)(descendingLabelProp, "Descending");
|
|
180
203
|
// OP_LABELS' own words ("contains", "is", "is any of", …) have no prop of
|
|
@@ -269,6 +292,19 @@ function TableToolbar({ columns, sortColumns: sortColumnsProp, filterColumns: fi
|
|
|
269
292
|
putFilters(filters.filter((c) => c.id !== id));
|
|
270
293
|
setOpenMenu(null);
|
|
271
294
|
};
|
|
295
|
+
// #594: the filter chip's kebab is a real role="menu" (crisp/#188's shared
|
|
296
|
+
// model — arrows, Home/End, typeahead, Escape), not a hand-rolled div like
|
|
297
|
+
// the sort/condition popovers above (those are role="dialog", which makes
|
|
298
|
+
// no such promise). `openMenu` already only ever names ONE open surface at
|
|
299
|
+
// a time, so one hook call at the top level — not one per filter row,
|
|
300
|
+
// which would break the Rules of Hooks inside `filters.map` below — tracks
|
|
301
|
+
// whichever chip's kebab is currently open via its content ref.
|
|
302
|
+
const kebabMenuRef = React.useRef(null);
|
|
303
|
+
const { onKeyDown: onKebabKeyDown } = (0, menu_1.useMenuKeyboard)({
|
|
304
|
+
open: typeof openMenu === "string" && openMenu.startsWith("fkebab:"),
|
|
305
|
+
close: () => setOpenMenu(null),
|
|
306
|
+
contentRef: kebabMenuRef,
|
|
307
|
+
});
|
|
272
308
|
const activeFilters = filters.length;
|
|
273
309
|
// The default phrase only works in English word order, so a caller can
|
|
274
310
|
// replace the whole thing rather than fill a template — crisp/#471's
|
|
@@ -318,10 +354,11 @@ function TableToolbar({ columns, sortColumns: sortColumnsProp, filterColumns: fi
|
|
|
318
354
|
.map((col) => ((0, jsx_runtime_1.jsxs)("button", { type: "button", className: "crisp-tabletoolbar-menu-item", onClick: () => addFilter(col.key), children: [col.icon ? ((0, jsx_runtime_1.jsx)(col.icon, { size: 15, strokeWidth: 1.75 })) : ((0, jsx_runtime_1.jsx)("span", { style: { width: 15 } })), col.label] }, col.key)))] }))] }), filters.map((cond) => {
|
|
319
355
|
const col = filterColumns.find((c) => c.key === cond.key);
|
|
320
356
|
const open = openMenu === `fcond:${cond.id}`;
|
|
357
|
+
const kebabOpen = openMenu === `fkebab:${cond.id}`;
|
|
321
358
|
const hasValue = (!opNeedsValue(cond.op) || cond.value.trim()) &&
|
|
322
359
|
(!opNeedsValues(cond.op) || (cond.values?.length ?? 0) > 0);
|
|
323
360
|
return ((0, jsx_runtime_1.jsxs)("span", { className: "crisp-tabletoolbar-toolgroup", children: [(0, jsx_runtime_1.jsxs)("span", { className: "crisp-tabletoolbar-fchip", children: [(0, jsx_runtime_1.jsxs)("button", { type: "button", className: "crisp-tabletoolbar-fchip-main", "data-menu-trigger": true, "aria-haspopup": "dialog", onClick: () => setOpenMenu(open ? null : `fcond:${cond.id}`), children: [(0, jsx_runtime_1.jsx)("span", { className: "crisp-tabletoolbar-fchip-field", children: col?.label }), hasValue ? ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsx)("span", { className: "crisp-tabletoolbar-fchip-op", children: crispMessages[OP_LABELS[cond.op]] ??
|
|
324
|
-
OP_LABELS[cond.op] }), opNeedsValue(cond.op) && ((0, jsx_runtime_1.jsx)("span", { className: "crisp-tabletoolbar-fchip-val", children: cond.value })), cond.op === "isanyof" && cond.values && ((0, jsx_runtime_1.jsx)("span", { className: "crisp-tabletoolbar-fchip-val", children: isAnyOfSummary(cond.values) }))] })) : ((0, jsx_runtime_1.jsx)("span", { className: "crisp-tabletoolbar-fchip-op", children: selectConditionLabel }))] }), (0, jsx_runtime_1.jsx)("button", { type: "button", className: "crisp-tabletoolbar-fchip-x", "aria-
|
|
361
|
+
OP_LABELS[cond.op] }), opNeedsValue(cond.op) && ((0, jsx_runtime_1.jsx)("span", { className: "crisp-tabletoolbar-fchip-val", children: cond.value })), cond.op === "isanyof" && cond.values && ((0, jsx_runtime_1.jsx)("span", { className: "crisp-tabletoolbar-fchip-val", children: isAnyOfSummary(cond.values) }))] })) : ((0, jsx_runtime_1.jsx)("span", { className: "crisp-tabletoolbar-fchip-op", children: selectConditionLabel }))] }), (0, jsx_runtime_1.jsx)("button", { type: "button", className: "crisp-tabletoolbar-fchip-x", "data-menu-trigger": true, "aria-haspopup": "menu", "aria-expanded": kebabOpen, "aria-label": `${col?.label} ${filterOptionsLabel}`, onClick: () => setOpenMenu(kebabOpen ? null : `fkebab:${cond.id}`), children: (0, jsx_runtime_1.jsx)(lucide_react_1.MoreVertical, { size: 13, strokeWidth: 2 }) })] }), kebabOpen && ((0, jsx_runtime_1.jsx)("div", { ref: kebabMenuRef, className: "crisp-tabletoolbar-menu crisp-tabletoolbar-menu--right crisp-tabletoolbar-menu--hdr", role: "menu", "aria-label": `${col?.label} ${filterOptionsLabel}`, onKeyDown: onKebabKeyDown, children: (0, jsx_runtime_1.jsx)("button", { type: "button", role: "menuitem", className: "crisp-tabletoolbar-menu-item", onClick: () => removeFilter(cond.id), children: deleteFilterLabel }) })), open && ((0, jsx_runtime_1.jsxs)("div", { className: "crisp-tabletoolbar-menu", role: "dialog", "aria-label": `${col?.label} ${crispMessages.condition ?? "condition"}`, children: [(0, jsx_runtime_1.jsxs)("div", { className: "crisp-tabletoolbar-menu-head", children: [col?.label, " ", crispMessages.condition ?? "condition"] }), opsFor(col).map((op) => ((0, jsx_runtime_1.jsxs)("button", { type: "button", className: "crisp-tabletoolbar-menu-item", "data-on": cond.op === op ? "" : undefined, onClick: () => patchFilter(cond.id, { op }), children: [crispMessages[OP_LABELS[op]] ?? OP_LABELS[op], cond.op === op ? ((0, jsx_runtime_1.jsx)(lucide_react_1.Check, { className: "crisp-tabletoolbar-menu-check", size: 15, strokeWidth: 1.75 })) : null] }, op))), opNeedsValue(cond.op) && ((0, jsx_runtime_1.jsx)("input", { className: "crisp-tabletoolbar-menu-input", placeholder: filterValuePlaceholder, "aria-label": `${col?.label} filter value`,
|
|
325
362
|
// biome-ignore lint/a11y/noAutofocus: focus the value field
|
|
326
363
|
autoFocus: true, value: cond.value, onChange: (e) => patchFilter(cond.id, { value: e.target.value }) })), cond.op === "isanyof" && col?.values && ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsx)("input", { className: "crisp-tabletoolbar-menu-input", placeholder: filterValuePlaceholder, "aria-label": `${col?.label} filter values`,
|
|
327
364
|
// biome-ignore lint/a11y/noAutofocus: focus the search field
|
|
@@ -44,7 +44,7 @@ const tabs_keyboard_1 = require("./tabs-keyboard");
|
|
|
44
44
|
* Home/End jump); activation follows focus. Controlled via
|
|
45
45
|
* `value`/`onValueChange`, or uncontrolled from `defaultValue`.
|
|
46
46
|
*/
|
|
47
|
-
function Tabs({ tabs, value, defaultValue, onValueChange, leading, children, className, "aria-label": ariaLabel, ...rest }) {
|
|
47
|
+
function Tabs({ tabs, value, defaultValue, onValueChange, leading, children, className, fill, "aria-label": ariaLabel, ...rest }) {
|
|
48
48
|
// Scoped to this instance: `tab-${value}` and `panel-${value}` are global,
|
|
49
49
|
// so two tab bars sharing a value mint the same id twice and every
|
|
50
50
|
// aria-labelledby resolves to the first one (#227).
|
|
@@ -83,7 +83,7 @@ function Tabs({ tabs, value, defaultValue, onValueChange, leading, children, cla
|
|
|
83
83
|
select,
|
|
84
84
|
listRef,
|
|
85
85
|
});
|
|
86
|
-
return ((0, jsx_runtime_1.jsxs)("div", { className: (0, tabs_recipe_1.tabsRecipe)({ className }), ...rest, children: [(0, jsx_runtime_1.jsxs)("div", { ref: listRef, className: "crisp-tabs-list", role: "tablist", "aria-label": ariaLabel, onKeyDown: onKeyDown, children: [leading && (0, jsx_runtime_1.jsx)("span", { className: "crisp-tabs-leading", children: leading }), tabs.map((t) => {
|
|
86
|
+
return ((0, jsx_runtime_1.jsxs)("div", { className: (0, tabs_recipe_1.tabsRecipe)({ fill, className }), ...rest, children: [(0, jsx_runtime_1.jsxs)("div", { ref: listRef, className: "crisp-tabs-list", role: "tablist", "aria-label": ariaLabel, onKeyDown: onKeyDown, children: [leading && (0, jsx_runtime_1.jsx)("span", { className: "crisp-tabs-leading", children: leading }), tabs.map((t) => {
|
|
87
87
|
const selected = t.value === active;
|
|
88
88
|
return ((0, jsx_runtime_1.jsxs)("button", { type: "button", role: "tab", id: tabId(t.value), "aria-selected": selected, "aria-controls": children && selected ? panelId(t.value) : undefined, tabIndex: tabIndexFor(t.value), "data-value": t.value, "data-selected": selected ? "" : undefined, disabled: t.disabled, className: "crisp-tabs-tab", onClick: () => select(t.value), children: [t.icon, (0, jsx_runtime_1.jsx)("span", { className: "tabs-tab-label", children: t.label }), t.badge != null && ((0, jsx_runtime_1.jsx)("span", { className: "crisp-tabs-tab-badge", children: t.badge }))] }, t.value));
|
|
89
89
|
}), indicator && ((0, jsx_runtime_1.jsx)("span", { className: "crisp-tabs-indicator", style: {
|
|
@@ -15,6 +15,22 @@ const tokens_1 = require("../../tokens");
|
|
|
15
15
|
// a 16px leading icon and an optional count badge. Measured from the live
|
|
16
16
|
// app: bar `padding: 0 12px 8px`, 28px pills, 4/8 padding, 6px icon-label
|
|
17
17
|
// gap, 14/500 labels, 10px badges (rgba(0,0,0,.04)).
|
|
18
|
+
//
|
|
19
|
+
// The panel's own `padding-top` (`c.space["8"]`, 32px) is measured
|
|
20
|
+
// separately, live, on a company record page (Overview tab): the gap from
|
|
21
|
+
// the tab row's own bottom to the content below it (the "Highlights"
|
|
22
|
+
// heading) is a clean 32px with zero margin/padding contributed by either
|
|
23
|
+
// boundary — not the 12px a GRC consumer had been carrying locally, which
|
|
24
|
+
// turned out (checking its own history) to have been measured against that
|
|
25
|
+
// app's own demo rendering, never against Attio itself.
|
|
26
|
+
//
|
|
27
|
+
// The panel is also `display: flex; flex-direction: column` (structural,
|
|
28
|
+
// not measured): a consumer's own content — a table sizing itself to "the
|
|
29
|
+
// rest of the space" — needs a flex-column ancestor with a real height to
|
|
30
|
+
// grow into. A GRC consumer used to supply that itself, wrapping every
|
|
31
|
+
// `children(active)` in its own `<div className="flex h-full min-h-0
|
|
32
|
+
// flex-col">`; dropping that wrapper in favour of `fill` without this would
|
|
33
|
+
// have silently broken every such table the moment the wrapper went away.
|
|
18
34
|
const config = (0, core_1.defineRecipe)({
|
|
19
35
|
name: "tabs",
|
|
20
36
|
base: `
|
|
@@ -32,11 +48,22 @@ const config = (0, core_1.defineRecipe)({
|
|
|
32
48
|
& .crisp-tabs-tab-badge { display: inline-flex; align-items: center; justify-content: center; min-width: 16px; height: 16px; padding: 0 ${tokens_1.contract.space["1"]}; border-radius: ${tokens_1.contract.radius.sm}; background: color-mix(in srgb, ${tokens_1.contract.fgPrimary} 6%, transparent); font-size: ${tokens_1.contract.text.xs}; font-weight: ${tokens_1.contract.weight.medium}; color: ${tokens_1.contract.fgMuted}; }
|
|
33
49
|
& .crisp-tabs-tab[data-selected] .crisp-tabs-tab-badge { color: ${tokens_1.contract.fgPrimary}; }
|
|
34
50
|
& .crisp-tabs-indicator { position: absolute; bottom: 0; left: 0; z-index: 1; height: 1px; background: ${tokens_1.contract.fgPrimary}; border-radius: 2px 2px 0 0; transition: transform .2s cubic-bezier(.4,0,.2,1), width .2s cubic-bezier(.4,0,.2,1); pointer-events: none; }
|
|
35
|
-
& .crisp-tabs-panel { flex: 1; min-height: 0; }
|
|
51
|
+
& .crisp-tabs-panel { display: flex; flex-direction: column; flex: 1; min-height: 0; padding-top: ${tokens_1.contract.space["8"]}; }
|
|
36
52
|
& .crisp-tabs-panel:focus-visible { outline: none; }
|
|
37
53
|
`,
|
|
38
|
-
variants: {
|
|
39
|
-
|
|
54
|
+
variants: {
|
|
55
|
+
// Opt-in: a `Tabs` dropped into a document-flow layout (a docs page, a
|
|
56
|
+
// demo) should size to its content like anything else. `fill` is for a
|
|
57
|
+
// consumer embedding it in a fixed-height shell — a record page, a full
|
|
58
|
+
// screen — matching the record page's own tab area, which visibly
|
|
59
|
+
// fills all available height (the same live check this file's panel
|
|
60
|
+
// measurement came from).
|
|
61
|
+
fill: {
|
|
62
|
+
true: `height: 100%;`,
|
|
63
|
+
false: "",
|
|
64
|
+
},
|
|
65
|
+
},
|
|
66
|
+
defaultVariants: { fill: false },
|
|
40
67
|
compoundVariants: [],
|
|
41
68
|
});
|
|
42
69
|
exports.tabsRecipe = Object.assign((0, core_1.resolveRecipe)(config), { config });
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.textRecipe = exports.Text = void 0;
|
|
4
|
+
var text_1 = require("./text");
|
|
5
|
+
Object.defineProperty(exports, "Text", { enumerable: true, get: function () { return text_1.Text; } });
|
|
6
|
+
var text_recipe_1 = require("./text.recipe");
|
|
7
|
+
Object.defineProperty(exports, "textRecipe", { enumerable: true, get: function () { return text_recipe_1.textRecipe; } });
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.Text = void 0;
|
|
37
|
+
const jsx_runtime_1 = require("react/jsx-runtime");
|
|
38
|
+
const React = __importStar(require("react"));
|
|
39
|
+
const core_1 = require("../../core");
|
|
40
|
+
const text_recipe_1 = require("./text.recipe");
|
|
41
|
+
/**
|
|
42
|
+
* Plain coloured text — `tone="default"` (the app's primary text colour) or
|
|
43
|
+
* `"muted"` — for the runs of text that aren't a Badge, a Chip, a link, or
|
|
44
|
+
* any of crisp's other typed things: a cell's own yes/no, an empty-state
|
|
45
|
+
* caption, a secondary line under a heading. One component so every such
|
|
46
|
+
* spot reaches for crisp's own tokens instead of each hand-writing its own
|
|
47
|
+
* colour utility for the same two values.
|
|
48
|
+
*
|
|
49
|
+
* Renders a `<span>` (inherits its size/weight/line-height from context —
|
|
50
|
+
* see `text.recipe.ts`'s doc for why `Text` sets none of those itself).
|
|
51
|
+
*/
|
|
52
|
+
exports.Text = React.forwardRef(({ tone, className, asChild, children, ...props }, ref) => {
|
|
53
|
+
return ((0, jsx_runtime_1.jsx)(core_1.Primitive.span, { ref: ref, asChild: asChild, className: (0, text_recipe_1.textRecipe)({ tone, className }), ...props, children: children }));
|
|
54
|
+
});
|
|
55
|
+
exports.Text.displayName = "Text";
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.spec = exports.textRecipe = void 0;
|
|
4
|
+
const core_1 = require("../../core");
|
|
5
|
+
const tokens_1 = require("../../tokens");
|
|
6
|
+
// No new measurement — `tone` just names two colour tokens every other
|
|
7
|
+
// component already carries (`fgPrimary`/`fgNeutralMuted`), for the plain
|
|
8
|
+
// runs of text that aren't any of crisp's other typed things (a Badge, a
|
|
9
|
+
// Chip, a link). Font size/weight/line-height are deliberately NOT set here:
|
|
10
|
+
// unlike Badge or Chip, plain text has no single measured size across the
|
|
11
|
+
// app (a table cell, a label, a caption are all different sizes) — `Text`
|
|
12
|
+
// inherits type from its context, the same choice `MiddleTruncate` makes for
|
|
13
|
+
// the same reason (see its own doc).
|
|
14
|
+
const spec = (0, core_1.defineSpec)({
|
|
15
|
+
name: "text",
|
|
16
|
+
// `display: inline` -- a span's own default, stated rather than left
|
|
17
|
+
// implicit: an EMPTY base compiles to an empty string, and
|
|
18
|
+
// `core/resolve.ts`'s `if (base) classes.push(prefixedName)` treats that
|
|
19
|
+
// falsy string as "no base rule", which drops the `crisp-text` class
|
|
20
|
+
// itself, silently, from every instance (caught by this file's own
|
|
21
|
+
// tests going red on that exact class).
|
|
22
|
+
base: { display: "inline" },
|
|
23
|
+
variants: {
|
|
24
|
+
tone: {
|
|
25
|
+
default: { color: tokens_1.contract.fgPrimary },
|
|
26
|
+
muted: { color: tokens_1.contract.fgNeutralMuted },
|
|
27
|
+
},
|
|
28
|
+
},
|
|
29
|
+
defaultVariants: { tone: "default" },
|
|
30
|
+
});
|
|
31
|
+
exports.spec = spec;
|
|
32
|
+
const config = (0, core_1.specToRecipe)(spec);
|
|
33
|
+
exports.textRecipe = Object.assign((0, core_1.resolveRecipe)(config), { config });
|
|
34
|
+
exports.default = config;
|
package/dist/cjs/index.js
CHANGED
|
@@ -53,6 +53,7 @@ __exportStar(require("./components/mentionchip"), exports);
|
|
|
53
53
|
__exportStar(require("./components/menu"), exports);
|
|
54
54
|
__exportStar(require("./components/middletruncate"), exports);
|
|
55
55
|
__exportStar(require("./components/multiselect"), exports);
|
|
56
|
+
__exportStar(require("./components/personchip"), exports);
|
|
56
57
|
__exportStar(require("./components/popover"), exports);
|
|
57
58
|
__exportStar(require("./components/progress"), exports);
|
|
58
59
|
__exportStar(require("./components/quantitypicker"), exports);
|
|
@@ -70,6 +71,7 @@ __exportStar(require("./components/switch"), exports);
|
|
|
70
71
|
__exportStar(require("./components/table"), exports);
|
|
71
72
|
__exportStar(require("./components/tabletoolbar"), exports);
|
|
72
73
|
__exportStar(require("./components/tabs"), exports);
|
|
74
|
+
__exportStar(require("./components/text"), exports);
|
|
73
75
|
__exportStar(require("./components/textarea"), exports);
|
|
74
76
|
__exportStar(require("./components/textlink"), exports);
|
|
75
77
|
__exportStar(require("./components/timepicker"), exports);
|
|
@@ -87,6 +89,7 @@ __exportStar(require("./product/appshell"), exports);
|
|
|
87
89
|
__exportStar(require("./product/authshell"), exports);
|
|
88
90
|
__exportStar(require("./product/board"), exports);
|
|
89
91
|
__exportStar(require("./product/command"), exports);
|
|
92
|
+
__exportStar(require("./product/comments"), exports);
|
|
90
93
|
__exportStar(require("./product/entitycard"), exports);
|
|
91
94
|
__exportStar(require("./product/flowcanvas"), exports);
|
|
92
95
|
__exportStar(require("./product/formdialog"), exports);
|
|
@@ -95,6 +98,7 @@ __exportStar(require("./product/importdialog"), exports);
|
|
|
95
98
|
__exportStar(require("./product/inbox"), exports);
|
|
96
99
|
__exportStar(require("./product/listpage"), exports);
|
|
97
100
|
__exportStar(require("./product/navitem"), exports);
|
|
101
|
+
__exportStar(require("./product/objectsettings"), exports);
|
|
98
102
|
__exportStar(require("./product/onboarding"), exports);
|
|
99
103
|
__exportStar(require("./product/recordpage"), exports);
|
|
100
104
|
__exportStar(require("./product/recordpanel"), exports);
|
|
@@ -35,20 +35,76 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
35
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
36
|
exports.Board = Board;
|
|
37
37
|
const jsx_runtime_1 = require("react/jsx-runtime");
|
|
38
|
+
const lucide_react_1 = require("lucide-react");
|
|
38
39
|
const React = __importStar(require("react"));
|
|
40
|
+
const menu_1 = require("../../components/menu");
|
|
39
41
|
const core_1 = require("../../core");
|
|
40
42
|
const board_recipe_1 = require("./board.recipe");
|
|
43
|
+
const BOARD_AGG_ORDER = [
|
|
44
|
+
"countEmpty",
|
|
45
|
+
"countFilled",
|
|
46
|
+
"percentEmpty",
|
|
47
|
+
"percentFilled",
|
|
48
|
+
"sum",
|
|
49
|
+
"average",
|
|
50
|
+
"max",
|
|
51
|
+
"min",
|
|
52
|
+
];
|
|
53
|
+
// English default text, looked up through `useCrispMessages()` (crisp/#471)
|
|
54
|
+
// the same way Board's other built-in fragments already are.
|
|
55
|
+
const AGG_LABEL = {
|
|
56
|
+
countEmpty: "Count empty",
|
|
57
|
+
countFilled: "Count filled",
|
|
58
|
+
percentEmpty: "Percent empty",
|
|
59
|
+
percentFilled: "Percent filled",
|
|
60
|
+
sum: "Sum",
|
|
61
|
+
average: "Average",
|
|
62
|
+
max: "Max",
|
|
63
|
+
min: "Min",
|
|
64
|
+
};
|
|
65
|
+
function computeBoardAgg(agg, values) {
|
|
66
|
+
const filled = values.filter((v) => v != null);
|
|
67
|
+
const total = values.length;
|
|
68
|
+
switch (agg) {
|
|
69
|
+
case "countEmpty":
|
|
70
|
+
return String(total - filled.length);
|
|
71
|
+
case "countFilled":
|
|
72
|
+
return String(filled.length);
|
|
73
|
+
case "percentEmpty":
|
|
74
|
+
return total === 0
|
|
75
|
+
? "0%"
|
|
76
|
+
: `${Math.round(((total - filled.length) / total) * 100)}%`;
|
|
77
|
+
case "percentFilled":
|
|
78
|
+
return total === 0
|
|
79
|
+
? "0%"
|
|
80
|
+
: `${Math.round((filled.length / total) * 100)}%`;
|
|
81
|
+
case "sum":
|
|
82
|
+
return filled.reduce((a, b) => a + b, 0).toLocaleString();
|
|
83
|
+
case "average":
|
|
84
|
+
return filled.length === 0
|
|
85
|
+
? "0"
|
|
86
|
+
: (filled.reduce((a, b) => a + b, 0) / filled.length).toLocaleString(undefined, { maximumFractionDigits: 2 });
|
|
87
|
+
case "max":
|
|
88
|
+
return filled.length === 0 ? "—" : Math.max(...filled).toLocaleString();
|
|
89
|
+
case "min":
|
|
90
|
+
return filled.length === 0 ? "—" : Math.min(...filled).toLocaleString();
|
|
91
|
+
}
|
|
92
|
+
}
|
|
41
93
|
/**
|
|
42
94
|
* A kanban board with native drag-and-drop between columns. Cards are
|
|
43
95
|
* `draggable`; dropping one on a column reparents it via `onCardsChange`
|
|
44
96
|
* (the parent owns the cards, so the move is a controlled state update).
|
|
45
97
|
* Measured from a deals pipeline.
|
|
46
98
|
*/
|
|
47
|
-
function Board({ columns, cards, onCardsChange, renderCard, onNewCard, newCardLabel: newCardLabelProp, getCardLabel, announceMove: announceMoveProp, className, ...rest }) {
|
|
99
|
+
function Board({ columns, cards, onCardsChange, renderCard, onNewCard, newCardLabel: newCardLabelProp, getCardLabel, announceMove: announceMoveProp, calc, className, ...rest }) {
|
|
48
100
|
// crisp/#471: same resolution order as every other component's own —
|
|
49
101
|
// explicit prop, then a CrispMessagesProvider's translation of the English
|
|
50
102
|
// default, then that default itself.
|
|
51
103
|
const newCardLabel = (0, core_1.useCrispMessage)(newCardLabelProp, "New");
|
|
104
|
+
// #604: which BoardAgg each column's footer shows, keyed by column id.
|
|
105
|
+
// Unset (undefined) means the column still shows the "+ Add calculation"
|
|
106
|
+
// trigger, exactly like the table footer's own per-column state.
|
|
107
|
+
const [colAgg, setColAgg] = React.useState({});
|
|
52
108
|
const crispMessages = (0, core_1.useCrispMessages)();
|
|
53
109
|
const announceMove = announceMoveProp ??
|
|
54
110
|
((column, card) => card
|
|
@@ -128,7 +184,9 @@ function Board({ columns, cards, onCardsChange, renderCard, onNewCard, newCardLa
|
|
|
128
184
|
// Ctrl+Right at the last column still reaches the browser
|
|
129
185
|
if (delta && moveByKey(card, delta))
|
|
130
186
|
e.preventDefault();
|
|
131
|
-
}, children: renderCard(card) }, card.id)))] })]
|
|
187
|
+
}, children: renderCard(card) }, card.id)))] }), calc && ((0, jsx_runtime_1.jsx)("div", { className: "crisp-board-col-footer", children: (0, jsx_runtime_1.jsx)(menu_1.Menu, { align: "start", trigger: (0, jsx_runtime_1.jsx)("button", { type: "button", className: "crisp-board-col-calc", "data-empty": colAgg[col.id] ? undefined : "", children: colAgg[col.id] ? ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsx)("span", { className: "crisp-board-col-calc-label", children: crispMessages[AGG_LABEL[colAgg[col.id]]] ??
|
|
188
|
+
AGG_LABEL[colAgg[col.id]] }), (0, jsx_runtime_1.jsx)("span", { className: "crisp-board-col-calc-value", children: computeBoardAgg(colAgg[col.id], colCards.map((c) => calc.getValue(c))) })] })) : ((crispMessages["+ Add calculation"] ??
|
|
189
|
+
"+ Add calculation")) }), children: BOARD_AGG_ORDER.map((a) => ((0, jsx_runtime_1.jsxs)(menu_1.MenuItem, { onSelect: () => setColAgg((prev) => ({ ...prev, [col.id]: a })), children: [crispMessages[AGG_LABEL[a]] ?? AGG_LABEL[a], colAgg[col.id] === a ? ((0, jsx_runtime_1.jsx)(lucide_react_1.Check, { size: 15, strokeWidth: 1.75 })) : null] }, a))) }) }))] }, col.id));
|
|
132
190
|
}), (0, jsx_runtime_1.jsx)("div", { className: "crisp-board-live", role: "status", "aria-live": "polite", children: announcement })] }));
|
|
133
191
|
}
|
|
134
192
|
Board.displayName = "Board";
|
|
@@ -35,6 +35,16 @@ const config = (0, core_1.defineRecipe)({
|
|
|
35
35
|
& .crisp-board-card[data-dragging] { opacity: 0.4; }
|
|
36
36
|
& .crisp-board-card:focus { outline: none; }
|
|
37
37
|
& .crisp-board-card:focus-visible { outline: 2px solid ${tokens_1.contract.bgBrandSolid}; outline-offset: 1px; }
|
|
38
|
+
/* #604: a per-column calc footer, mirroring the table's own (measured
|
|
39
|
+
live, Deals pipeline). Sits below the scrollable card body, not
|
|
40
|
+
inside it or pinned to it -- Attio's own is position: static, not
|
|
41
|
+
sticky, so it's a plain flex-shrink:0 sibling here too. */
|
|
42
|
+
& .crisp-board-col-footer { flex-shrink: 0; padding: ${tokens_1.contract.space["1"]} 0; }
|
|
43
|
+
& .crisp-board-col-calc { display: flex; align-items: center; justify-content: center; gap: ${tokens_1.contract.space["1"]}; width: 100%; height: 28px; padding: ${tokens_1.contract.space["1"]} ${tokens_1.contract.space["2"]}; margin: 0; border: none; border-radius: ${tokens_1.contract.radius.md}; background: none; font-family: inherit; font-size: ${tokens_1.contract.text.lg}; font-weight: ${tokens_1.contract.weight.medium}; color: color-mix(in srgb, ${tokens_1.contract.fgPrimary} 63%, transparent); cursor: pointer; }
|
|
44
|
+
& .crisp-board-col-calc:hover { background: color-mix(in srgb, ${tokens_1.contract.fgPrimary} 4%, transparent); }
|
|
45
|
+
& .crisp-board-col-calc[data-empty] { opacity: 0.7; }
|
|
46
|
+
& .crisp-board-col-calc-label { text-transform: capitalize; }
|
|
47
|
+
& .crisp-board-col-calc-value { color: ${tokens_1.contract.fgPrimary}; font-variant-numeric: tabular-nums; }
|
|
38
48
|
/* announced, never drawn */
|
|
39
49
|
& .crisp-board-live {
|
|
40
50
|
position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
|