@crisp-ui-kit/crisp 0.54.6 → 0.54.8

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.
Files changed (113) hide show
  1. package/dist/authshell.css +5 -1
  2. package/dist/authshell.layered.css +5 -1
  3. package/dist/badge.css +5 -5
  4. package/dist/badge.layered.css +5 -5
  5. package/dist/board.css +13 -9
  6. package/dist/board.layered.css +13 -9
  7. package/dist/button.css +3 -1
  8. package/dist/button.layered.css +3 -1
  9. package/dist/cjs/components/badge/badge.recipe.js +28 -5
  10. package/dist/cjs/components/button/button.js +33 -2
  11. package/dist/cjs/components/button/button.recipe.js +31 -5
  12. package/dist/cjs/components/datatable/datatable.js +66 -7
  13. package/dist/cjs/components/datatable/datatable.recipe.js +57 -2
  14. package/dist/cjs/components/dialog/dialog.js +8 -5
  15. package/dist/cjs/components/dialog/dialog.recipe.js +15 -1
  16. package/dist/cjs/components/tabletoolbar/tabletoolbar.chrome.js +19 -1
  17. package/dist/cjs/components/tabletoolbar/tabletoolbar.js +239 -50
  18. package/dist/cjs/components/tabletoolbar/tabletoolbar.recipe.js +44 -0
  19. package/dist/cjs/components/tabs/tabs.recipe.js +11 -2
  20. package/dist/cjs/components/tooltip/tooltip.js +23 -9
  21. package/dist/cjs/components/tooltip/tooltip.recipe.js +10 -9
  22. package/dist/cjs/product/authshell/authshell.js +14 -1
  23. package/dist/cjs/product/authshell/authshell.recipe.js +13 -1
  24. package/dist/cjs/product/board/board.js +122 -27
  25. package/dist/cjs/product/board/board.recipe.js +14 -2
  26. package/dist/cjs/product/comments/comments.js +23 -10
  27. package/dist/cjs/product/comments/comments.recipe.js +17 -3
  28. package/dist/cjs/product/formdialog/formdialog.js +10 -4
  29. package/dist/cjs/product/navitem/navitem.js +7 -2
  30. package/dist/cjs/product/navitem/navitem.recipe.js +16 -0
  31. package/dist/cjs/product/objectsettings/index.js +1 -2
  32. package/dist/cjs/product/objectsettings/objectsettings.js +3 -13
  33. package/dist/cjs/product/richtext/richtext.js +76 -1
  34. package/dist/cjs/product/sidebar/sidebar.js +68 -15
  35. package/dist/cjs/product/sidebar/sidebar.recipe.js +6 -1
  36. package/dist/cjs/tokens/palette.js +32 -1
  37. package/dist/cjs/tokens/semantic.js +11 -0
  38. package/dist/comments.css +36 -10
  39. package/dist/comments.layered.css +36 -10
  40. package/dist/datatable.css +47 -9
  41. package/dist/datatable.layered.css +47 -9
  42. package/dist/dialog.css +13 -1
  43. package/dist/dialog.layered.css +13 -1
  44. package/dist/esm/components/badge/badge.recipe.js +28 -5
  45. package/dist/esm/components/button/button.js +34 -3
  46. package/dist/esm/components/button/button.recipe.js +31 -5
  47. package/dist/esm/components/datatable/datatable.js +67 -8
  48. package/dist/esm/components/datatable/datatable.recipe.js +57 -2
  49. package/dist/esm/components/dialog/dialog.js +8 -5
  50. package/dist/esm/components/dialog/dialog.recipe.js +15 -1
  51. package/dist/esm/components/tabletoolbar/tabletoolbar.chrome.js +19 -1
  52. package/dist/esm/components/tabletoolbar/tabletoolbar.js +240 -51
  53. package/dist/esm/components/tabletoolbar/tabletoolbar.recipe.js +44 -0
  54. package/dist/esm/components/tabs/tabs.recipe.js +11 -2
  55. package/dist/esm/components/tooltip/tooltip.js +23 -9
  56. package/dist/esm/components/tooltip/tooltip.recipe.js +10 -9
  57. package/dist/esm/product/authshell/authshell.js +15 -2
  58. package/dist/esm/product/authshell/authshell.recipe.js +13 -1
  59. package/dist/esm/product/authshell/index.js +1 -1
  60. package/dist/esm/product/board/board.js +122 -27
  61. package/dist/esm/product/board/board.recipe.js +14 -2
  62. package/dist/esm/product/comments/comments.js +23 -10
  63. package/dist/esm/product/comments/comments.recipe.js +17 -3
  64. package/dist/esm/product/formdialog/formdialog.js +10 -4
  65. package/dist/esm/product/navitem/index.js +1 -1
  66. package/dist/esm/product/navitem/navitem.js +7 -2
  67. package/dist/esm/product/navitem/navitem.recipe.js +16 -0
  68. package/dist/esm/product/objectsettings/index.js +1 -1
  69. package/dist/esm/product/objectsettings/objectsettings.js +2 -12
  70. package/dist/esm/product/richtext/richtext.js +76 -1
  71. package/dist/esm/product/sidebar/sidebar.js +69 -16
  72. package/dist/esm/product/sidebar/sidebar.recipe.js +6 -1
  73. package/dist/esm/tokens/palette.js +32 -1
  74. package/dist/esm/tokens/semantic.js +11 -0
  75. package/dist/native/tokens.d.ts +1 -1
  76. package/dist/native/tokens.js +16 -2
  77. package/dist/navitem.css +7 -0
  78. package/dist/navitem.layered.css +7 -0
  79. package/dist/recordstable.css +2 -1
  80. package/dist/recordstable.layered.css +2 -1
  81. package/dist/sidebar.css +6 -1
  82. package/dist/sidebar.layered.css +6 -1
  83. package/dist/styles.css +165 -42
  84. package/dist/styles.layered.css +165 -42
  85. package/dist/tabletoolbar.css +17 -1
  86. package/dist/tabletoolbar.layered.css +17 -1
  87. package/dist/tabs.css +1 -1
  88. package/dist/tabs.layered.css +1 -1
  89. package/dist/tokens.css +9 -1
  90. package/dist/tokens.json +16 -2
  91. package/dist/tooltip.css +1 -1
  92. package/dist/tooltip.layered.css +1 -1
  93. package/dist/types/components/badge/badge.recipe.d.ts +5 -1
  94. package/dist/types/components/button/button.recipe.d.ts +18 -5
  95. package/dist/types/components/datatable/datatable.d.ts +9 -0
  96. package/dist/types/components/dialog/dialog.d.ts +25 -2
  97. package/dist/types/components/tabletoolbar/tabletoolbar.d.ts +47 -5
  98. package/dist/types/components/tooltip/tooltip.d.ts +12 -4
  99. package/dist/types/components/tooltip/tooltip.recipe.d.ts +1 -1
  100. package/dist/types/product/authshell/authshell.d.ts +26 -2
  101. package/dist/types/product/authshell/index.d.ts +1 -1
  102. package/dist/types/product/board/board.d.ts +5 -4
  103. package/dist/types/product/comments/comments.d.ts +10 -2
  104. package/dist/types/product/formdialog/formdialog.d.ts +2 -1
  105. package/dist/types/product/navitem/index.d.ts +1 -1
  106. package/dist/types/product/navitem/navitem.d.ts +23 -0
  107. package/dist/types/product/objectsettings/index.d.ts +1 -1
  108. package/dist/types/product/objectsettings/objectsettings.d.ts +0 -10
  109. package/dist/types/product/sidebar/sidebar.d.ts +33 -5
  110. package/dist/types/tokens/contract.d.ts +4 -0
  111. package/dist/types/tokens/palette.d.ts +13 -1
  112. package/dist/types/tokens/semantic.d.ts +11 -0
  113. package/package.json +1 -1
@@ -2,7 +2,20 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.AuthShell = AuthShell;
4
4
  const jsx_runtime_1 = require("react/jsx-runtime");
5
+ const react_1 = require("react");
5
6
  const authshell_recipe_1 = require("./authshell.recipe");
7
+ /** A plain `{ icon, title }` object (not a React element) — the shape
8
+ * `logo` renders as `AuthShell`'s own icon-badge + wordmark layout, as
9
+ * opposed to a caller-supplied node it just centers as-is. Same test
10
+ * `sidebar.tsx`'s `isSidebarFooterCopy` already uses for its own "plain
11
+ * shape vs. any node" prop. */
12
+ function isAuthShellBrand(value) {
13
+ return (typeof value === "object" &&
14
+ value !== null &&
15
+ !(0, react_1.isValidElement)(value) &&
16
+ "icon" in value &&
17
+ "title" in value);
18
+ }
6
19
  /**
7
20
  * The welcome/auth layout — measured live.
8
21
  * A centered page frame: an optional top progress bar, a logo, a two-pane
@@ -16,6 +29,6 @@ function AuthShell({ progress, logo, title, children, preview, footer, className
16
29
  const pct = hasProgress
17
30
  ? Math.round(Math.max(0, Math.min(1, progress)) * 100)
18
31
  : 0;
19
- return ((0, jsx_runtime_1.jsxs)("div", { className: (0, authshell_recipe_1.authshellRecipe)({ className }), "data-single-pane": preview == null ? "" : undefined, ...rest, children: [hasProgress && ((0, jsx_runtime_1.jsx)("div", { className: "crisp-authshell-progress", role: "progressbar", "aria-valuenow": pct, "aria-valuemin": 0, "aria-valuemax": 100, children: (0, jsx_runtime_1.jsx)("div", { className: "crisp-authshell-progress-fill", style: { width: `${pct}%` } }) })), logo != null && (0, jsx_runtime_1.jsx)("div", { className: "crisp-authshell-logo", children: logo }), (0, jsx_runtime_1.jsxs)("div", { className: "crisp-authshell-card", children: [(0, jsx_runtime_1.jsx)("div", { className: "crisp-authshell-pane", children: (0, jsx_runtime_1.jsxs)("div", { className: "crisp-authshell-body", children: [title != null && ((0, jsx_runtime_1.jsx)("div", { className: "crisp-authshell-title", children: title })), (0, jsx_runtime_1.jsx)("div", { className: "crisp-authshell-form", children: children })] }) }), preview != null && ((0, jsx_runtime_1.jsx)("div", { className: "crisp-authshell-preview", children: preview }))] }), footer != null && (0, jsx_runtime_1.jsx)("div", { className: "crisp-authshell-footer", children: footer })] }));
32
+ return ((0, jsx_runtime_1.jsxs)("div", { className: (0, authshell_recipe_1.authshellRecipe)({ className }), "data-single-pane": preview == null ? "" : undefined, ...rest, children: [hasProgress && ((0, jsx_runtime_1.jsx)("div", { className: "crisp-authshell-progress", role: "progressbar", "aria-valuenow": pct, "aria-valuemin": 0, "aria-valuemax": 100, children: (0, jsx_runtime_1.jsx)("div", { className: "crisp-authshell-progress-fill", style: { width: `${pct}%` } }) })), logo != null && ((0, jsx_runtime_1.jsx)("div", { className: "crisp-authshell-logo", children: isAuthShellBrand(logo) ? ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsx)("div", { className: "crisp-authshell-logo-badge", children: logo.icon }), (0, jsx_runtime_1.jsxs)("div", { className: "crisp-authshell-logo-text", children: [(0, jsx_runtime_1.jsx)("div", { className: "crisp-authshell-logo-title", children: logo.title }), logo.subtitle != null && ((0, jsx_runtime_1.jsx)("div", { className: "crisp-authshell-logo-subtitle", children: logo.subtitle }))] })] })) : (logo) })), (0, jsx_runtime_1.jsxs)("div", { className: "crisp-authshell-card", children: [(0, jsx_runtime_1.jsx)("div", { className: "crisp-authshell-pane", children: (0, jsx_runtime_1.jsxs)("div", { className: "crisp-authshell-body", children: [title != null && ((0, jsx_runtime_1.jsx)("div", { className: "crisp-authshell-title", children: title })), (0, jsx_runtime_1.jsx)("div", { className: "crisp-authshell-form", children: children })] }) }), preview != null && ((0, jsx_runtime_1.jsx)("div", { className: "crisp-authshell-preview", children: preview }))] }), footer != null && (0, jsx_runtime_1.jsx)("div", { className: "crisp-authshell-footer", children: footer })] }));
20
33
  }
21
34
  AuthShell.displayName = "AuthShell";
@@ -23,6 +23,14 @@ const tokens_1 = require("../../tokens");
23
23
  // roughly centred in the viewport rather than pinned near the top.
24
24
  // `data-single-pane` now drops the card chrome and narrows the body to
25
25
  // match; the two-pane card is unchanged.
26
+ //
27
+ // your-moon/crisp#722: `.crisp-authshell-logo` is `min-height: 24px`, not
28
+ // `height` -- Attio's own mark (a single flat 96x24 SVG, measured live on
29
+ // the logged-out `/login` page) fits exactly at 24px, but a fixed height
30
+ // clipped any consumer logo taller than that. Found via internal/grc's real
31
+ // lockup (an icon badge beside a two-line TECH/tagline wordmark, ~36px
32
+ // tall), silently cut down to a bare one-line span rather than reported as
33
+ // not fitting.
26
34
  const config = (0, core_1.defineRecipe)({
27
35
  name: "authshell",
28
36
  base: `min-height: 100%; box-sizing: border-box; display: flex; flex-direction: column; align-items: center; position: relative;
@@ -34,7 +42,11 @@ const config = (0, core_1.defineRecipe)({
34
42
  & .crisp-authshell-progress { position: absolute; top: 0; left: 0; width: 100%; height: 2px; background: ${tokens_1.contract.borderDefault}; }
35
43
  & .crisp-authshell-progress-fill { height: 2px; background: ${tokens_1.contract.fgPrimary}; border-radius: 4px; transition: width .2s; }
36
44
 
37
- & .crisp-authshell-logo { margin-top: ${tokens_1.contract.space["8"]}; height: 24px; display: flex; align-items: center; }
45
+ & .crisp-authshell-logo { margin-top: ${tokens_1.contract.space["8"]}; min-height: 24px; display: flex; align-items: center; gap: ${tokens_1.contract.space["2.5"]}; }
46
+ & .crisp-authshell-logo-badge { flex-shrink: 0; box-sizing: border-box; width: 32px; height: 32px; border-radius: ${tokens_1.contract.radius.md}; background: ${tokens_1.contract.bgBrandSolid}; color: #fff; display: flex; align-items: center; justify-content: center; }
47
+ & .crisp-authshell-logo-text { display: flex; flex-direction: column; line-height: 1.2; }
48
+ & .crisp-authshell-logo-title { font-size: ${tokens_1.contract.text.xl}; font-weight: ${tokens_1.contract.weight.bold}; letter-spacing: -0.2px; color: ${tokens_1.contract.fgPrimary}; }
49
+ & .crisp-authshell-logo-subtitle { font-size: ${tokens_1.contract.text.sm}; color: ${tokens_1.contract.fgMuted}; }
38
50
 
39
51
  & .crisp-authshell-card { margin-top: ${tokens_1.contract.space["6"]}; display: flex; height: 692px; border-radius: 20px; border: 1px solid ${tokens_1.contract.borderSubtle}; overflow: hidden; }
40
52
  &[data-single-pane] .crisp-authshell-card { height: auto; border: none; border-radius: 0; overflow: visible; }
@@ -91,10 +91,11 @@ function computeBoardAgg(agg, values) {
91
91
  }
92
92
  }
93
93
  /**
94
- * A kanban board with native drag-and-drop between columns. Cards are
95
- * `draggable`; dropping one on a column reparents it via `onCardsChange`
96
- * (the parent owns the cards, so the move is a controlled state update).
97
- * Measured from a deals pipeline.
94
+ * A kanban board with pointer-events drag-and-drop between columns (#674 —
95
+ * Attio's own mechanism, confirmed live: mousedown, several mousemoves,
96
+ * mouseup, no `[draggable]` anywhere). Dropping a card on a column reparents
97
+ * it via `onCardsChange` (the parent owns the cards, so the move is a
98
+ * controlled state update). Measured from a deals pipeline.
98
99
  */
99
100
  function Board({ columns, cards, onCardsChange, renderCard, onNewCard, newCardLabel: newCardLabelProp, getCardLabel, announceMove: announceMoveProp, calc, className, ...rest }) {
100
101
  // crisp/#471: same resolution order as every other component's own —
@@ -112,11 +113,37 @@ function Board({ columns, cards, onCardsChange, renderCard, onNewCard, newCardLa
112
113
  : `${crispMessages["Moved to"] ?? "Moved to"} ${column}`);
113
114
  const [dragId, setDragId] = React.useState(null);
114
115
  const [overCol, setOverCol] = React.useState(null);
116
+ const [ghost, setGhost] = React.useState(null);
115
117
  const [announcement, setAnnouncement] = React.useState("");
116
118
  // the card a keyboard move just sent to another column; React unmounts and
117
119
  // remounts it under the new parent, so focus has to be put back by hand
118
120
  const [refocusId, setRefocusId] = React.useState(null);
119
121
  const rootRef = React.useRef(null);
122
+ const colRefs = React.useRef(new Map());
123
+ // #674: which column a client point is over, read off each column's own
124
+ // rect at drag time rather than `elementFromPoint` -- the fixed-position
125
+ // ghost sits on top of everything (deliberately, so it never itself
126
+ // becomes the hit) and this is the one MEASURED interaction a test has a
127
+ // real handle on (`elementFromPoint` needs an actual layout engine bun
128
+ // test's happy-dom doesn't run).
129
+ //
130
+ // X only, not a full box: `.crisp-board`'s columns are a single
131
+ // non-wrapping row (`display: flex`, no `flex-wrap`), so X alone already
132
+ // identifies the column unambiguously. Checking Y against each column's
133
+ // OWN rect looked more precise but was actually a bug caught driving this
134
+ // in a real headed browser (Playwright), not by the tests passing --
135
+ // `align-items: flex-start` on the row means a short/empty column's own
136
+ // box ends where its content does, well short of a taller neighbour's, so
137
+ // a drop below that point silently found no column at all even while
138
+ // visually still inside the board.
139
+ const columnAt = (x) => {
140
+ for (const [id, el] of colRefs.current) {
141
+ const r = el.getBoundingClientRect();
142
+ if (x >= r.left && x <= r.right)
143
+ return id;
144
+ }
145
+ return null;
146
+ };
120
147
  const move = (cardId, toColumn) => {
121
148
  const card = cards.find((c) => c.id === cardId);
122
149
  if (!card || card.columnId === toColumn)
@@ -132,8 +159,77 @@ function Board({ columns, cards, onCardsChange, renderCard, onNewCard, newCardLa
132
159
  setRefocusId(null);
133
160
  }, [refocusId]);
134
161
  /**
135
- * Native HTML5 drag-and-drop is pointer-only by specification, so the board's
136
- * primary action needs its own key model or it has none at all.
162
+ * #674: Attio's own board drag is driven by pointer events (mousedown ->
163
+ * several mousemoves -> mouseup) -- confirmed live, no `[draggable]`
164
+ * anywhere -- not native HTML5 `draggable`/`onDragStart`, which is
165
+ * pointer-only by specification (no touch/pen support) and fires its own
166
+ * separate, browser-chrome-owned drag session crisp never controlled: no
167
+ * custom drag image, no way to redraw the dragged card while it moves.
168
+ * Pointer events give the board its own tracked drag state instead, the
169
+ * same primitive `resizeHandle`/`recordstable`'s own column-resize already
170
+ * use elsewhere in crisp.
171
+ *
172
+ * A drag doesn't "start" the instant the pointer goes down on a card --
173
+ * only once it has moved past a small threshold (4px). Below that, the
174
+ * gesture reads as a click/tap that never happened here (the card carries
175
+ * no click handler of its own today, but a future one would otherwise
176
+ * fire through a phantom near-zero-distance "drag").
177
+ */
178
+ const dragStateRef = React.useRef(null);
179
+ const DRAG_THRESHOLD = 4;
180
+ const onCardPointerDown = (e, card) => {
181
+ if (!e.isPrimary)
182
+ return;
183
+ const r = e.currentTarget.getBoundingClientRect();
184
+ dragStateRef.current = {
185
+ id: card.id,
186
+ pointerId: e.pointerId,
187
+ startX: e.clientX,
188
+ startY: e.clientY,
189
+ active: false,
190
+ grabDx: e.clientX - r.left,
191
+ grabDy: e.clientY - r.top,
192
+ width: r.width,
193
+ height: r.height,
194
+ };
195
+ };
196
+ const onCardPointerMove = (e) => {
197
+ const st = dragStateRef.current;
198
+ if (!st || st.pointerId !== e.pointerId)
199
+ return;
200
+ if (!st.active) {
201
+ const dx = e.clientX - st.startX;
202
+ const dy = e.clientY - st.startY;
203
+ if (Math.hypot(dx, dy) < DRAG_THRESHOLD)
204
+ return;
205
+ st.active = true;
206
+ setDragId(st.id);
207
+ e.currentTarget.setPointerCapture(e.pointerId);
208
+ }
209
+ setGhost({
210
+ x: e.clientX - st.grabDx,
211
+ y: e.clientY - st.grabDy,
212
+ width: st.width,
213
+ height: st.height,
214
+ });
215
+ setOverCol(columnAt(e.clientX));
216
+ };
217
+ const endCardDrag = (e, commit) => {
218
+ const st = dragStateRef.current;
219
+ if (!st || st.pointerId !== e.pointerId)
220
+ return;
221
+ if (commit && st.active && overCol)
222
+ move(st.id, overCol);
223
+ dragStateRef.current = null;
224
+ setDragId(null);
225
+ setOverCol(null);
226
+ setGhost(null);
227
+ };
228
+ /**
229
+ * Neither drag mechanism the board has used (the old native HTML5 kind,
230
+ * this file's own pointer-events one now) is reachable from a keyboard, so
231
+ * the board's primary action needs its own key model or it has none at
232
+ * all.
137
233
  *
138
234
  * Ctrl/Cmd + Left/Right rather than plain arrows: a screen reader owns the
139
235
  * bare arrow keys in browse mode, and a board scrolls horizontally, so plain
@@ -151,27 +247,14 @@ function Board({ columns, cards, onCardsChange, renderCard, onNewCard, newCardLa
151
247
  };
152
248
  return ((0, jsx_runtime_1.jsxs)("div", { ref: rootRef, className: (0, board_recipe_1.boardRecipe)({ className }), ...rest, children: [columns.map((col) => {
153
249
  const colCards = cards.filter((c) => c.columnId === col.id);
154
- return ((0, jsx_runtime_1.jsxs)("div", { className: "crisp-board-col", "data-over": overCol === col.id ? "" : undefined, onDragOver: (e) => {
155
- if (!dragId)
156
- return;
157
- e.preventDefault();
158
- setOverCol(col.id);
159
- }, onDragLeave: (e) => {
160
- if (!e.currentTarget.contains(e.relatedTarget)) {
161
- setOverCol((v) => (v === col.id ? null : v));
162
- }
163
- }, onDrop: (e) => {
164
- e.preventDefault();
165
- if (dragId)
166
- move(dragId, col.id);
167
- setDragId(null);
168
- setOverCol(null);
169
- }, children: [(0, jsx_runtime_1.jsxs)("div", { className: "crisp-board-col-header", children: [col.accent && ((0, jsx_runtime_1.jsx)("span", { className: "crisp-board-col-dot", style: { background: col.accent } })), (0, jsx_runtime_1.jsx)("span", { className: "crisp-board-col-title", children: col.title }), (0, jsx_runtime_1.jsx)("span", { className: "crisp-board-col-count", children: colCards.length }), onNewCard && ((0, jsx_runtime_1.jsx)("button", { type: "button", className: "crisp-board-col-add", "aria-label": `${crispMessages["Add to"] ?? "Add to"} ${col.title}`, onClick: () => onNewCard(col.id), children: "+" }))] }), (0, jsx_runtime_1.jsxs)("div", { className: "crisp-board-col-body", role: "list", "aria-label": col.title, children: [onNewCard && ((0, jsx_runtime_1.jsxs)("button", { type: "button", className: "crisp-board-newcard", onClick: () => onNewCard(col.id), children: ["+ ", newCardLabel] })), colCards.map((card) => ((0, jsx_runtime_1.jsx)("div", { className: "crisp-board-card", role: "listitem",
250
+ return ((0, jsx_runtime_1.jsxs)("div", { ref: (el) => {
251
+ if (el)
252
+ colRefs.current.set(col.id, el);
253
+ else
254
+ colRefs.current.delete(col.id);
255
+ }, className: "crisp-board-col", "data-over": overCol === col.id ? "" : undefined, children: [(0, jsx_runtime_1.jsxs)("div", { className: "crisp-board-col-header", children: [col.accent && ((0, jsx_runtime_1.jsx)("span", { className: "crisp-board-col-dot", style: { background: col.accent } })), (0, jsx_runtime_1.jsx)("span", { className: "crisp-board-col-title", children: col.title }), (0, jsx_runtime_1.jsx)("span", { className: "crisp-board-col-count", children: colCards.length }), onNewCard && ((0, jsx_runtime_1.jsx)("button", { type: "button", className: "crisp-board-col-add", "aria-label": `${crispMessages["Add to"] ?? "Add to"} ${col.title}`, onClick: () => onNewCard(col.id), children: "+" }))] }), (0, jsx_runtime_1.jsxs)("div", { className: "crisp-board-col-body", role: "list", "aria-label": col.title, children: [onNewCard && ((0, jsx_runtime_1.jsxs)("button", { type: "button", className: "crisp-board-newcard", onClick: () => onNewCard(col.id), children: ["+ ", newCardLabel] })), colCards.map((card) => ((0, jsx_runtime_1.jsx)("div", { className: "crisp-board-card", role: "listitem",
170
256
  // biome-ignore lint/a11y/noNoninteractiveTabindex: the rule assumes a listitem cannot be operated. This one carries the board's only keyboard path to move a card, so it has to be reachable by Tab
171
- tabIndex: 0, "aria-label": getCardLabel?.(card), "data-card-id": card.id, draggable: true, "data-dragging": dragId === card.id ? "" : undefined, onDragStart: () => setDragId(card.id), onDragEnd: () => {
172
- setDragId(null);
173
- setOverCol(null);
174
- }, onKeyDown: (e) => {
257
+ tabIndex: 0, "aria-label": getCardLabel?.(card), "data-card-id": card.id, "data-dragging": dragId === card.id ? "" : undefined, onPointerDown: (e) => onCardPointerDown(e, card), onPointerMove: onCardPointerMove, onPointerUp: (e) => endCardDrag(e, true), onPointerCancel: (e) => endCardDrag(e, false), onKeyDown: (e) => {
175
258
  if (!(e.ctrlKey || e.metaKey))
176
259
  return;
177
260
  // the columns are a flex row, so under RTL the next one
@@ -187,6 +270,18 @@ function Board({ columns, cards, onCardsChange, renderCard, onNewCard, newCardLa
187
270
  }, 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
271
  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
272
  "+ 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));
190
- }), (0, jsx_runtime_1.jsx)("div", { className: "crisp-board-live", role: "status", "aria-live": "polite", children: announcement })] }));
273
+ }), dragId &&
274
+ ghost &&
275
+ (() => {
276
+ const draggedCard = cards.find((c) => c.id === dragId);
277
+ if (!draggedCard)
278
+ return null;
279
+ return ((0, jsx_runtime_1.jsx)("div", { className: "crisp-board-card crisp-board-ghost", style: {
280
+ left: ghost.x,
281
+ top: ghost.y,
282
+ width: ghost.width,
283
+ height: ghost.height,
284
+ }, children: renderCard(draggedCard) }));
285
+ })(), (0, jsx_runtime_1.jsx)("div", { className: "crisp-board-live", role: "status", "aria-live": "assertive", children: announcement })] }));
191
286
  }
192
287
  Board.displayName = "Board";
@@ -23,18 +23,30 @@ const config = (0, core_1.defineRecipe)({
23
23
  & .crisp-board-col-header { display: flex; align-items: center; gap: ${tokens_1.contract.space["2"]}; height: 36px; padding: 0 ${tokens_1.contract.space["1"]}; flex-shrink: 0; }
24
24
  & .crisp-board-col-dot { width: 12px; height: 12px; border-radius: ${tokens_1.contract.radius.full}; flex-shrink: 0; }
25
25
  & .crisp-board-col-title { font-size: ${tokens_1.contract.text.lg}; font-weight: ${tokens_1.contract.weight.semibold}; color: ${tokens_1.contract.fgPrimary}; }
26
- & .crisp-board-col-count { 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} 4%, transparent); font-size: ${tokens_1.contract.text.sm}; font-weight: ${tokens_1.contract.weight.medium}; color: color-mix(in srgb, ${tokens_1.contract.fgPrimary} 63%, transparent); }
26
+ /* crisp#697: HANDOFF's #472 note claimed the count pill's
27
+ color-mix(fgPrimary, 63%, transparent) reproduced the measured
28
+ rgb(80,81,85) -- it doesn't; 63% of fgPrimary composited on white
29
+ paints rgb(104,105,105), visibly lighter. Text now reads
30
+ fgNeutralSoft directly (the same rgb(80,81,85) token Badge's neutral
31
+ tone uses, #696), and the ring the pill was also missing
32
+ (inset 0 0 0 1px borderSubtle, measured rgb(238,239,241)) is added. */
33
+ & .crisp-board-col-count { 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} 4%, transparent); box-shadow: inset 0 0 0 1px ${tokens_1.contract.borderSubtle}; font-size: ${tokens_1.contract.text.sm}; line-height: 16px; font-weight: ${tokens_1.contract.weight.medium}; color: ${tokens_1.contract.fgNeutralSoft}; }
27
34
  & .crisp-board-col-add { display: inline-flex; align-items: center; justify-content: center; width: 20px; height: 20px; margin-inline-start: auto; padding: 0; border: none; border-radius: ${tokens_1.contract.radius.sm}; background: none; color: ${tokens_1.contract.fgMuted}; font-family: inherit; cursor: pointer; }
28
35
  & .crisp-board-col-add:hover { background: color-mix(in srgb, ${tokens_1.contract.fgPrimary} 6%, transparent); color: ${tokens_1.contract.fgPrimary}; }
29
36
  & .crisp-board-col-body { display: flex; flex-direction: column; gap: ${tokens_1.contract.space["2"]}; flex: 1; min-height: 48px; padding: ${tokens_1.contract.space["1"]}; border-radius: ${tokens_1.contract.radius.lg}; overflow-y: auto; transition: background 0.12s; }
30
37
  & .crisp-board-col[data-over] .crisp-board-col-body { background: color-mix(in srgb, ${tokens_1.contract.bgBrandSolid} 7%, transparent); box-shadow: inset 0 0 0 1px color-mix(in srgb, ${tokens_1.contract.bgBrandSolid} 30%, transparent); }
31
38
  & .crisp-board-newcard { display: flex; align-items: center; justify-content: center; gap: ${tokens_1.contract.space["1.5"]}; height: 32px; flex-shrink: 0; border: none; border-radius: ${tokens_1.contract.radius.md}; background: color-mix(in srgb, ${tokens_1.contract.fgPrimary} 4%, transparent); color: ${tokens_1.contract.fgMuted}; font-family: inherit; font-size: ${tokens_1.contract.text.md}; font-weight: ${tokens_1.contract.weight.medium}; cursor: pointer; }
32
39
  & .crisp-board-newcard:hover { background: color-mix(in srgb, ${tokens_1.contract.fgPrimary} 7%, transparent); color: ${tokens_1.contract.fgPrimary}; }
33
- & .crisp-board-card { display: flex; flex-direction: column; gap: ${tokens_1.contract.space["2"]}; box-sizing: border-box; padding: ${tokens_1.contract.space["2.5"]} ${tokens_1.contract.space["3"]}; border-radius: ${tokens_1.contract.radius.xl}; background: ${tokens_1.contract.bgSurface}; box-shadow: inset 0 0 0 1px ${tokens_1.contract.borderSubtle}; cursor: grab; }
40
+ & .crisp-board-card { display: flex; flex-direction: column; gap: ${tokens_1.contract.space["2"]}; box-sizing: border-box; padding: ${tokens_1.contract.space["2.5"]} ${tokens_1.contract.space["3"]}; border-radius: ${tokens_1.contract.radius.xl}; background: ${tokens_1.contract.bgSurface}; box-shadow: inset 0 0 0 1px ${tokens_1.contract.borderSubtle}; cursor: grab; touch-action: none; }
34
41
  & .crisp-board-card:active { cursor: grabbing; }
35
42
  & .crisp-board-card[data-dragging] { opacity: 0.4; }
36
43
  & .crisp-board-card:focus { outline: none; }
37
44
  & .crisp-board-card:focus-visible { outline: 2px solid ${tokens_1.contract.bgBrandSolid}; outline-offset: 1px; }
45
+ /* #674: the pointer-events drag's own drag image -- the same card
46
+ surface above, just pinned to the pointer instead of the flex flow.
47
+ No shadow lift or scale: neither was measured against Attio, and this
48
+ is the same card, momentarily somewhere else, not a new look. */
49
+ & .crisp-board-ghost { position: fixed; z-index: 1; pointer-events: none; cursor: grabbing; }
38
50
  /* #604: a per-column calc footer, mirroring the table's own (measured
39
51
  live, Deals pipeline). Sits below the scrollable card body, not
40
52
  inside it or pinned to it -- Attio's own is position: static, not
@@ -304,8 +304,8 @@ const Composer = React.forwardRef(function Composer({ placeholder, postLabel, me
304
304
  close: closeMention,
305
305
  })] }));
306
306
  });
307
- function CommentRow({ comment, replyLabel, reactLabel, resolveLabel, moreLabel, copyLinkLabel, deleteLabel, addReactionLabel, reactionSearchLabel, emojiOptions, resolvable, resolved, resolvedLabel, onReply, onReact, onResolve, onCopyLink, onDeleteRequest, }) {
308
- return ((0, jsx_runtime_1.jsxs)("div", { className: "crisp-comments-row", children: [(0, jsx_runtime_1.jsx)(AuthorAvatar, { author: comment.author }), (0, jsx_runtime_1.jsxs)("div", { className: "crisp-comments-row-main", children: [resolvedLabel != null && ((0, jsx_runtime_1.jsxs)("div", { className: "crisp-comments-resolved-label", children: [(0, jsx_runtime_1.jsx)(lucide_react_1.Check, { size: 12, strokeWidth: 2.5, "aria-hidden": "true" }), resolvedLabel] })), (0, jsx_runtime_1.jsxs)("div", { className: "crisp-comments-row-head", children: [(0, jsx_runtime_1.jsx)("span", { className: "crisp-comments-author", children: comment.author.name }), (0, jsx_runtime_1.jsx)("span", { className: "crisp-comments-time", children: comment.time })] }), (0, jsx_runtime_1.jsx)("div", { className: "crisp-comments-body-text", children: comment.body }), comment.reactions && comment.reactions.length > 0 && ((0, jsx_runtime_1.jsxs)("div", { className: "crisp-comments-reactions", children: [comment.reactions.map((r) => ((0, jsx_runtime_1.jsxs)("button", { type: "button", className: "crisp-comments-reaction", "data-reacted": r.reactedByMe ? "" : undefined, onClick: () => onReact(r.emoji), children: [(0, jsx_runtime_1.jsx)("span", { "aria-hidden": "true", children: r.emoji }), r.count] }, r.emoji))), (0, jsx_runtime_1.jsx)(popover_1.Popover, { align: "start", trigger: (0, jsx_runtime_1.jsx)("button", { type: "button", className: "crisp-comments-reaction-add", "aria-label": addReactionLabel, children: (0, jsx_runtime_1.jsx)(lucide_react_1.SmilePlus, { size: 14, strokeWidth: 1.75, "aria-hidden": "true" }) }), children: (0, jsx_runtime_1.jsx)(ReactionPicker, { options: emojiOptions, searchLabel: reactionSearchLabel, onPick: onReact }) })] }))] }), (0, jsx_runtime_1.jsxs)("div", { className: "crisp-comments-row-actions", children: [onReply && ((0, jsx_runtime_1.jsx)("button", { type: "button", className: "crisp-comments-action", "aria-label": replyLabel, onClick: onReply, children: (0, jsx_runtime_1.jsx)(lucide_react_1.CornerUpLeft, { "data-rtl-flip": "", size: 16, strokeWidth: 2, "aria-hidden": "true" }) })), (0, jsx_runtime_1.jsx)(popover_1.Popover, { align: "end", trigger: (0, jsx_runtime_1.jsx)("button", { type: "button", className: "crisp-comments-action", "aria-label": reactLabel, children: (0, jsx_runtime_1.jsx)(lucide_react_1.SmilePlus, { size: 16, strokeWidth: 1.75, "aria-hidden": "true" }) }), children: (0, jsx_runtime_1.jsx)(ReactionPicker, { options: emojiOptions, searchLabel: reactionSearchLabel, onPick: onReact }) }), resolvable && ((0, jsx_runtime_1.jsx)("button", { type: "button", className: "crisp-comments-action", "aria-label": resolveLabel, "aria-pressed": !!resolved, "data-active": resolved ? "" : undefined, onClick: onResolve, children: (0, jsx_runtime_1.jsx)(lucide_react_1.Check, { size: 16, strokeWidth: 1.75, "aria-hidden": "true" }) })), (0, jsx_runtime_1.jsxs)(menu_1.Menu, { align: "end", trigger: (0, jsx_runtime_1.jsx)("button", { type: "button", className: "crisp-comments-action", "aria-label": moreLabel, children: (0, jsx_runtime_1.jsx)(lucide_react_1.MoreHorizontal, { size: 16, strokeWidth: 1.75, "aria-hidden": "true" }) }), children: [(0, jsx_runtime_1.jsx)(menu_1.MenuItem, { onSelect: onCopyLink, children: copyLinkLabel }), (0, jsx_runtime_1.jsx)(menu_1.MenuItem, { danger: true, onSelect: onDeleteRequest, children: deleteLabel })] })] })] }));
307
+ function CommentRow({ comment, replyLabel, reactLabel, resolveLabel, moreLabel, copyLinkLabel, deleteLabel, addReactionLabel, reactionSearchLabel, emojiOptions, resolvable, resolved, resolvedLabel, reactable, onReply, onReact, onResolve, onCopyLink, onDeleteRequest, }) {
308
+ return ((0, jsx_runtime_1.jsxs)("div", { className: "crisp-comments-row", children: [(0, jsx_runtime_1.jsx)(AuthorAvatar, { author: comment.author }), (0, jsx_runtime_1.jsxs)("div", { className: "crisp-comments-row-main", children: [resolvedLabel != null && ((0, jsx_runtime_1.jsxs)("div", { className: "crisp-comments-resolved-label", children: [(0, jsx_runtime_1.jsx)(lucide_react_1.Check, { size: 12, strokeWidth: 2.5, "aria-hidden": "true" }), resolvedLabel] })), (0, jsx_runtime_1.jsxs)("div", { className: "crisp-comments-row-head", children: [(0, jsx_runtime_1.jsx)("span", { className: "crisp-comments-author", children: comment.author.name }), (0, jsx_runtime_1.jsx)("span", { className: "crisp-comments-time", children: comment.time })] }), (0, jsx_runtime_1.jsx)("div", { className: "crisp-comments-body-text", children: comment.body }), comment.reactions && comment.reactions.length > 0 && ((0, jsx_runtime_1.jsxs)("div", { className: "crisp-comments-reactions", children: [comment.reactions.map((r) => ((0, jsx_runtime_1.jsxs)("button", { type: "button", className: "crisp-comments-reaction", "data-reacted": r.reactedByMe ? "" : undefined, onClick: () => onReact?.(r.emoji), children: [(0, jsx_runtime_1.jsx)("span", { "aria-hidden": "true", children: r.emoji }), r.count] }, r.emoji))), (0, jsx_runtime_1.jsx)(popover_1.Popover, { align: "start", trigger: (0, jsx_runtime_1.jsx)("button", { type: "button", className: "crisp-comments-reaction-add", "aria-label": addReactionLabel, children: (0, jsx_runtime_1.jsx)(lucide_react_1.SmilePlus, { size: 14, strokeWidth: 1.75, "aria-hidden": "true" }) }), children: (0, jsx_runtime_1.jsx)(ReactionPicker, { options: emojiOptions, searchLabel: reactionSearchLabel, onPick: (emoji) => onReact?.(emoji) }) })] }))] }), (0, jsx_runtime_1.jsxs)("div", { className: "crisp-comments-row-actions", children: [onReply && ((0, jsx_runtime_1.jsx)("button", { type: "button", className: "crisp-comments-action", "aria-label": replyLabel, onClick: onReply, children: (0, jsx_runtime_1.jsx)(lucide_react_1.CornerUpLeft, { "data-rtl-flip": "", size: 16, strokeWidth: 2, "aria-hidden": "true" }) })), reactable && ((0, jsx_runtime_1.jsx)(popover_1.Popover, { align: "end", trigger: (0, jsx_runtime_1.jsx)("button", { type: "button", className: "crisp-comments-action", "aria-label": reactLabel, children: (0, jsx_runtime_1.jsx)(lucide_react_1.SmilePlus, { size: 16, strokeWidth: 1.75, "aria-hidden": "true" }) }), children: (0, jsx_runtime_1.jsx)(ReactionPicker, { options: emojiOptions, searchLabel: reactionSearchLabel, onPick: (emoji) => onReact?.(emoji) }) })), resolvable && ((0, jsx_runtime_1.jsx)("button", { type: "button", className: "crisp-comments-action", "aria-label": resolveLabel, "aria-pressed": !!resolved, "data-active": resolved ? "" : undefined, onClick: onResolve, children: (0, jsx_runtime_1.jsx)(lucide_react_1.Check, { size: 16, strokeWidth: 1.75, "aria-hidden": "true" }) })), (0, jsx_runtime_1.jsxs)(menu_1.Menu, { align: "end", trigger: (0, jsx_runtime_1.jsx)("button", { type: "button", className: "crisp-comments-action", "aria-label": moreLabel, children: (0, jsx_runtime_1.jsx)(lucide_react_1.MoreHorizontal, { size: 16, strokeWidth: 1.75, "aria-hidden": "true" }) }), children: [(0, jsx_runtime_1.jsx)(menu_1.MenuItem, { onSelect: onCopyLink, children: copyLinkLabel }), onDeleteRequest && ((0, jsx_runtime_1.jsx)(menu_1.MenuItem, { danger: true, onSelect: onDeleteRequest, children: deleteLabel }))] })] })] }));
309
309
  }
310
310
  /**
311
311
  * Per-record comment threads (crisp#625) — measured live on app.attio.com's
@@ -375,23 +375,36 @@ function CommentsPanel({ threads, currentUserId, subject, subjectIcon, showResol
375
375
  ? resolvedByYouLabel
376
376
  : (resolvedByLabel?.(activeThread.resolvedBy.name) ??
377
377
  `Resolved by ${activeThread.resolvedBy.name}`)
378
- : undefined, onReact: (emoji) => onReact(activeThread.id, activeThread.root.id, emoji), onResolve: onResolve ? () => onResolve(activeThread.id) : undefined, onCopyLink: () => copyLink(activeThread.id, activeThread.root.id), onDeleteRequest: () => setDeleteTarget({
379
- threadId: activeThread.id,
380
- commentId: activeThread.root.id,
381
- }) }), activeThread.replies && activeThread.replies.length > 0 && ((0, jsx_runtime_1.jsx)("div", { className: "crisp-comments-replies", children: activeThread.replies.map((reply) => ((0, jsx_runtime_1.jsx)(CommentRow, { comment: reply, currentUserId: currentUserId, replyLabel: replyActionLabel, reactLabel: reactActionLabel, resolveLabel: resolveActionLabel, moreLabel: moreActionsLabel, copyLinkLabel: copyLinkLabel, deleteLabel: deleteLabel, addReactionLabel: addReactionLabel, reactionSearchLabel: reactionSearchLabel, emojiOptions: emojiOptions, onReact: (emoji) => onReact(activeThread.id, reply.id, emoji), onCopyLink: () => copyLink(activeThread.id, reply.id), onDeleteRequest: () => setDeleteTarget({
378
+ : undefined, reactable: !!onReact, onReact: onReact
379
+ ? (emoji) => onReact(activeThread.id, activeThread.root.id, emoji)
380
+ : undefined, onResolve: onResolve ? () => onResolve(activeThread.id) : undefined, onCopyLink: () => copyLink(activeThread.id, activeThread.root.id), onDeleteRequest: onDelete
381
+ ? () => setDeleteTarget({
382
382
  threadId: activeThread.id,
383
- commentId: reply.id,
384
- }) }, reply.id))) }))] })) : visibleThreads.length === 0 && emptyLabel != null ? ((0, jsx_runtime_1.jsx)("div", { className: "crisp-comments-empty", children: emptyLabel })) : (visibleThreads.map((t) => ((0, jsx_runtime_1.jsx)(CommentRow, { comment: t.root, currentUserId: currentUserId, replyLabel: replyActionLabel, reactLabel: reactActionLabel, resolveLabel: resolveActionLabel, moreLabel: moreActionsLabel, copyLinkLabel: copyLinkLabel, deleteLabel: deleteLabel, addReactionLabel: addReactionLabel, reactionSearchLabel: reactionSearchLabel, emojiOptions: emojiOptions, resolvable: !!onResolve, resolved: t.resolved, resolvedLabel: t.resolved
383
+ commentId: activeThread.root.id,
384
+ })
385
+ : undefined }), activeThread.replies && activeThread.replies.length > 0 && ((0, jsx_runtime_1.jsx)("div", { className: "crisp-comments-replies", children: activeThread.replies.map((reply) => ((0, jsx_runtime_1.jsx)(CommentRow, { comment: reply, currentUserId: currentUserId, replyLabel: replyActionLabel, reactLabel: reactActionLabel, resolveLabel: resolveActionLabel, moreLabel: moreActionsLabel, copyLinkLabel: copyLinkLabel, deleteLabel: deleteLabel, addReactionLabel: addReactionLabel, reactionSearchLabel: reactionSearchLabel, emojiOptions: emojiOptions, reactable: !!onReact, onReact: onReact
386
+ ? (emoji) => onReact(activeThread.id, reply.id, emoji)
387
+ : undefined, onCopyLink: () => copyLink(activeThread.id, reply.id), onDeleteRequest: onDelete
388
+ ? () => setDeleteTarget({
389
+ threadId: activeThread.id,
390
+ commentId: reply.id,
391
+ })
392
+ : undefined }, reply.id))) }))] })) : visibleThreads.length === 0 && emptyLabel != null ? ((0, jsx_runtime_1.jsx)("div", { className: "crisp-comments-empty", children: emptyLabel })) : (visibleThreads.map((t) => ((0, jsx_runtime_1.jsx)(CommentRow, { comment: t.root, currentUserId: currentUserId, replyLabel: replyActionLabel, reactLabel: reactActionLabel, resolveLabel: resolveActionLabel, moreLabel: moreActionsLabel, copyLinkLabel: copyLinkLabel, deleteLabel: deleteLabel, addReactionLabel: addReactionLabel, reactionSearchLabel: reactionSearchLabel, emojiOptions: emojiOptions, resolvable: !!onResolve, resolved: t.resolved, resolvedLabel: t.resolved
385
393
  ? t.resolvedBy?.id === currentUserId || !t.resolvedBy
386
394
  ? resolvedByYouLabel
387
395
  : (resolvedByLabel?.(t.resolvedBy.name) ??
388
396
  `Resolved by ${t.resolvedBy.name}`)
389
- : undefined, onReply: () => setActiveThreadId(t.id), onReact: (emoji) => onReact(t.id, t.root.id, emoji), onResolve: onResolve ? () => onResolve(t.id) : undefined, onCopyLink: () => copyLink(t.id, t.root.id), onDeleteRequest: () => setDeleteTarget({ threadId: t.id, commentId: t.root.id }) }, t.id)))) }), (0, jsx_runtime_1.jsx)(Composer, { placeholder: activeThread ? replyPlaceholder : composerPlaceholder, postLabel: activeThread ? replyPostLabel : postLabel, mentionTrigger: mentionTrigger, renderMentionPicker: renderMentionPicker, mentionToolLabel: composerMentionToolLabel, onSubmit: (text) => onPost(text, activeThread ? activeThread.id : null) }, activeThread ? activeThread.id : "root"), (0, jsx_runtime_1.jsxs)(dialog_1.Dialog, { open: deleteTarget != null, onOpenChange: (open) => {
397
+ : undefined, onReply: () => setActiveThreadId(t.id), reactable: !!onReact, onReact: onReact ? (emoji) => onReact(t.id, t.root.id, emoji) : undefined, onResolve: onResolve ? () => onResolve(t.id) : undefined, onCopyLink: () => copyLink(t.id, t.root.id), onDeleteRequest: onDelete
398
+ ? () => setDeleteTarget({ threadId: t.id, commentId: t.root.id })
399
+ : undefined }, t.id)))) }), (0, jsx_runtime_1.jsx)(Composer, { placeholder: activeThread ? replyPlaceholder : composerPlaceholder, postLabel: activeThread ? replyPostLabel : postLabel, mentionTrigger: mentionTrigger, renderMentionPicker: renderMentionPicker, mentionToolLabel: composerMentionToolLabel, onSubmit: (text) => onPost(text, activeThread ? activeThread.id : null) }, activeThread ? activeThread.id : "root"), (0, jsx_runtime_1.jsxs)(dialog_1.Dialog, { open: deleteTarget != null, onOpenChange: (open) => {
390
400
  if (!open)
391
401
  setDeleteTarget(null);
392
402
  }, title: deleteConfirmTitle, children: [(0, jsx_runtime_1.jsx)(dialog_1.DialogBody, { children: deleteConfirmBody }), (0, jsx_runtime_1.jsxs)(dialog_1.DialogFooter, { children: [(0, jsx_runtime_1.jsx)(button_1.Button, { type: "button", intent: "neutral", size: "md", onClick: () => setDeleteTarget(null), children: cancelLabel }), (0, jsx_runtime_1.jsx)(button_1.Button, { type: "button", intent: "danger", size: "md", onClick: () => {
403
+ // `deleteTarget` can only ever be set via `onDeleteRequest`
404
+ // (above), which itself only exists when `onDelete` was
405
+ // given -- this call is never reached otherwise.
393
406
  if (deleteTarget)
394
- onDelete(deleteTarget.threadId, deleteTarget.commentId);
407
+ onDelete?.(deleteTarget.threadId, deleteTarget.commentId);
395
408
  setDeleteTarget(null);
396
409
  }, children: deleteConfirmActionLabel })] })] })] }));
397
410
  }
@@ -36,12 +36,26 @@ const config = (0, core_1.defineRecipe)({
36
36
 
37
37
  /* One comment row -- avatar 16px (Avatar size="sm"), 6px gap to the
38
38
  name/time line, 14px/600 name + 12px/500 63%-muted time, 14px/400 body
39
- two lines below. Hover actions fade in flush with the name line. */
39
+ two lines below. Hover actions fade in flush with the name line.
40
+ .crisp-comments-row-head's own height: 20px (above) is a single
41
+ text-line's worth, on the same assumption .crisp-comments-title/
42
+ .crisp-comments-breadcrumb already act on higher up in this same
43
+ recipe -- a name that doesn't fit wraps to two lines with nothing
44
+ reserving room for the second one, overflowing straight into the
45
+ body text underneath (found live: this app's own dev-bypass author
46
+ name, an email long enough to wrap, sat on top of the comment's own
47
+ body text). .crisp-comments-author gets the identical nowrap-
48
+ ellipsis treatment those two already have, plus min-width: 0 so a
49
+ flex item is actually allowed to shrink below its own text's natural
50
+ width in the first place; .crisp-comments-time gets flex-shrink: 0
51
+ so it's the name that gives, never the timestamp -- the same "one
52
+ side shrinks, the other one doesn't" split PersonChip's own name/
53
+ avatar pair already uses (crisp#668/#671). */
40
54
  & .crisp-comments-row { position: relative; display: flex; gap: ${tokens_1.contract.space["1.5"]}; }
41
55
  & .crisp-comments-row-main { flex: 1; min-width: 0; }
42
56
  & .crisp-comments-row-head { display: flex; align-items: baseline; gap: ${tokens_1.contract.space["1.5"]}; height: 20px; }
43
- & .crisp-comments-author { font-size: ${tokens_1.contract.text.lg}; font-weight: ${tokens_1.contract.weight.semibold}; color: ${tokens_1.contract.fgPrimary}; }
44
- & .crisp-comments-time { font-size: 12px; font-weight: ${tokens_1.contract.weight.medium}; color: color-mix(in srgb, ${tokens_1.contract.fgPrimary} 63%, transparent); white-space: nowrap; }
57
+ & .crisp-comments-author { flex: 1 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: ${tokens_1.contract.text.lg}; font-weight: ${tokens_1.contract.weight.semibold}; color: ${tokens_1.contract.fgPrimary}; }
58
+ & .crisp-comments-time { flex-shrink: 0; font-size: 12px; font-weight: ${tokens_1.contract.weight.medium}; color: color-mix(in srgb, ${tokens_1.contract.fgPrimary} 63%, transparent); white-space: nowrap; }
45
59
  & .crisp-comments-resolved-label { display: flex; align-items: center; gap: ${tokens_1.contract.space["1"]}; margin-bottom: ${tokens_1.contract.space["1"]}; font-size: 12px; font-weight: ${tokens_1.contract.weight.medium}; color: color-mix(in srgb, ${tokens_1.contract.fgPrimary} 63%, transparent); }
46
60
  & .crisp-comments-body-text { font-size: ${tokens_1.contract.text.lg}; line-height: 20px; font-weight: ${tokens_1.contract.weight.regular}; color: ${tokens_1.contract.fgPrimary}; white-space: pre-wrap; word-break: break-word; }
47
61
 
@@ -69,13 +69,19 @@ function useFormDialogOptional(required) {
69
69
  * per-field wiring). A record needs several fields at once to be valid,
70
70
  * which is why records are not created by inline editing.
71
71
  */
72
- function FormDialog({ title, submitLabel, onSubmit, onClose, busy, error, children, wide = false, size, createMode = false, createMoreLabel: createMoreLabelProp, cancelLabel: cancelLabelProp, discardTitle: discardTitleProp, discardConfirmLabel: discardConfirmLabelProp, discardCancelLabel: discardCancelLabelProp, className, ...rest }) {
72
+ function FormDialog({ title, submitLabel, onSubmit, onClose, busy, error, children, wide = false, size, createMode = false, createMoreLabel: createMoreLabelProp, cancelLabel: cancelLabelProp, discardTitle: discardTitleProp, discardDescription: discardDescriptionProp, discardConfirmLabel: discardConfirmLabelProp, discardCancelLabel: discardCancelLabelProp, className, ...rest }) {
73
73
  const createMoreLabel = (0, core_1.useCrispMessage)(createMoreLabelProp, "Create more");
74
74
  const createRecordLabel = (0, core_1.useCrispMessage)(undefined, "Create record");
75
75
  const cancelLabel = (0, core_1.useCrispMessage)(cancelLabelProp, "Cancel");
76
- const discardTitle = (0, core_1.useCrispMessage)(discardTitleProp, "Discard changes? Your changes will be lost.");
76
+ // crisp#677: Attio's own confirm is two lines (a title, then a separate
77
+ // sentence), not one combined string — measured live, both quoted
78
+ // verbatim off app.attio.com's create-attribute dialog's own discard
79
+ // confirm. The buttons read "Cancel"/"Discard" there too, not crisp's
80
+ // prior "Keep editing" (a paraphrase, never independently measured).
81
+ const discardTitle = (0, core_1.useCrispMessage)(discardTitleProp, "Discard changes");
82
+ const discardDescription = (0, core_1.useCrispMessage)(discardDescriptionProp, "Are you sure you want to discard these changes?");
77
83
  const discardConfirmLabel = (0, core_1.useCrispMessage)(discardConfirmLabelProp, "Discard");
78
- const discardCancelLabel = (0, core_1.useCrispMessage)(discardCancelLabelProp, "Keep editing");
84
+ const discardCancelLabel = (0, core_1.useCrispMessage)(discardCancelLabelProp, "Cancel");
79
85
  const formId = React.useId();
80
86
  const formRef = React.useRef(null);
81
87
  const switchId = React.useId();
@@ -163,7 +169,7 @@ function FormDialog({ title, submitLabel, onSubmit, onClose, busy, error, childr
163
169
  return ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsxs)(dialog_1.Dialog, { open: true, onOpenChange: (open) => !open && requestClose(), title: title, width: size ?? (wide ? 760 : 640), className: (0, formdialog_recipe_1.formdialogRecipe)({ className }), ...rest, children: [(0, jsx_runtime_1.jsxs)(dialog_1.DialogBody, { children: [(0, jsx_runtime_1.jsx)(OptionalLabelsContext.Provider, { value: createMode, children: (0, jsx_runtime_1.jsx)("form", { ref: formRef, id: formId, className: "crisp-formdialog-form", onSubmit: (e) => {
164
170
  e.preventDefault();
165
171
  onSubmit(createMore);
166
- }, onChange: markDirty, onInput: markDirty, onClick: markDirty, children: children }) }), error != null && (0, jsx_runtime_1.jsx)("p", { className: "crisp-formdialog-error", children: error })] }), (0, jsx_runtime_1.jsx)(dialog_1.DialogFooter, { children: createMode ? ((0, jsx_runtime_1.jsxs)("div", { className: "crisp-formdialog-createfoot", children: [(0, jsx_runtime_1.jsxs)("span", { className: "crisp-dialog-more", children: [(0, jsx_runtime_1.jsx)(switch_1.Switch, { id: switchId, checked: createMore, onCheckedChange: setCreateMore, "aria-label": createMoreLabel }), (0, jsx_runtime_1.jsx)("label", { className: "crisp-formdialog-more-label", htmlFor: switchId, children: createMoreLabel })] }), (0, jsx_runtime_1.jsxs)("button", { type: "submit", form: formId, className: "crisp-dialog-submit", disabled: busy, children: [createRecordLabel, (0, jsx_runtime_1.jsxs)("span", { className: "crisp-dialog-submit-key", "aria-hidden": "true", children: [(0, jsx_runtime_1.jsx)("kbd", { children: "\u2318" }), (0, jsx_runtime_1.jsx)("kbd", { children: "\u21B5" })] })] })] })) : ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsx)(button_1.Button, { type: "button", intent: "neutral", onClick: requestClose, children: cancelLabel }), (0, jsx_runtime_1.jsx)(button_1.Button, { type: "submit", form: formId, intent: "primary", loading: busy, children: submitLabel })] })) })] }), (0, jsx_runtime_1.jsx)(dialog_1.Dialog, { open: confirmingDiscard, onOpenChange: (open) => !open && setConfirmingDiscard(false), title: discardTitle, width: 400, children: (0, jsx_runtime_1.jsxs)(dialog_1.DialogFooter, { children: [(0, jsx_runtime_1.jsx)(button_1.Button, { type: "button", intent: "neutral", onClick: () => setConfirmingDiscard(false), children: discardCancelLabel }), (0, jsx_runtime_1.jsx)(button_1.Button, { type: "button", intent: "danger", onClick: () => {
172
+ }, onChange: markDirty, onInput: markDirty, onClick: markDirty, children: children }) }), error != null && (0, jsx_runtime_1.jsx)("p", { className: "crisp-formdialog-error", children: error })] }), (0, jsx_runtime_1.jsx)(dialog_1.DialogFooter, { children: createMode ? ((0, jsx_runtime_1.jsxs)("div", { className: "crisp-formdialog-createfoot", children: [(0, jsx_runtime_1.jsxs)("span", { className: "crisp-dialog-more", children: [(0, jsx_runtime_1.jsx)(switch_1.Switch, { id: switchId, checked: createMore, onCheckedChange: setCreateMore, "aria-label": createMoreLabel }), (0, jsx_runtime_1.jsx)("label", { className: "crisp-formdialog-more-label", htmlFor: switchId, children: createMoreLabel })] }), (0, jsx_runtime_1.jsxs)("button", { type: "submit", form: formId, className: "crisp-dialog-submit", disabled: busy, children: [createRecordLabel, (0, jsx_runtime_1.jsxs)("span", { className: "crisp-dialog-submit-key", "aria-hidden": "true", children: [(0, jsx_runtime_1.jsx)("kbd", { children: "\u2318" }), (0, jsx_runtime_1.jsx)("kbd", { children: "\u21B5" })] })] })] })) : ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsx)(button_1.Button, { type: "button", intent: "neutral", onClick: requestClose, children: cancelLabel }), (0, jsx_runtime_1.jsx)(button_1.Button, { type: "submit", form: formId, intent: "primary", loading: busy, children: submitLabel })] })) })] }), (0, jsx_runtime_1.jsx)(dialog_1.Dialog, { open: confirmingDiscard, onOpenChange: (open) => !open && setConfirmingDiscard(false), variant: "alert", title: discardTitle, description: discardDescription, width: 440, children: (0, jsx_runtime_1.jsxs)(dialog_1.DialogFooter, { variant: "alert", children: [(0, jsx_runtime_1.jsx)(button_1.Button, { type: "button", intent: "neutral", onClick: () => setConfirmingDiscard(false), children: discardCancelLabel }), (0, jsx_runtime_1.jsx)(button_1.Button, { type: "button", intent: "danger", onClick: () => {
167
173
  setConfirmingDiscard(false);
168
174
  onClose();
169
175
  }, children: discardConfirmLabel })] }) })] }));
@@ -36,9 +36,14 @@ Object.defineProperty(exports, "__esModule", { value: true });
36
36
  exports.NavItem = void 0;
37
37
  const jsx_runtime_1 = require("react/jsx-runtime");
38
38
  const React = __importStar(require("react"));
39
+ const kbd_1 = require("../../components/kbd");
40
+ const tooltip_1 = require("../../components/tooltip");
39
41
  const core_1 = require("../../core");
40
42
  const navitem_recipe_1 = require("./navitem.recipe");
41
- exports.NavItem = React.forwardRef(({ active, icon, trailing, className, children, ...props }, ref) => {
42
- return ((0, jsx_runtime_1.jsxs)(core_1.Primitive.a, { ref: ref, "data-active": active || undefined, className: (0, navitem_recipe_1.navitemRecipe)({ className }), ...props, children: [icon, (0, jsx_runtime_1.jsx)("span", { className: "crisp-navitem-label", children: children }), trailing != null && ((0, jsx_runtime_1.jsx)("span", { className: "crisp-navitem-trailing", children: trailing }))] }));
43
+ exports.NavItem = React.forwardRef(({ active, icon, trailing, shortcut, className, children, ...props }, ref) => {
44
+ const row = ((0, jsx_runtime_1.jsxs)(core_1.Primitive.a, { ref: ref, "data-active": active || undefined, className: (0, navitem_recipe_1.navitemRecipe)({ className }), ...props, children: [icon, (0, jsx_runtime_1.jsx)("span", { className: "crisp-navitem-label", children: children }), trailing != null && ((0, jsx_runtime_1.jsx)("span", { className: "crisp-navitem-trailing", children: trailing }))] }));
45
+ if (!shortcut)
46
+ return row;
47
+ return ((0, jsx_runtime_1.jsx)(tooltip_1.Tooltip, { asChild: true, side: "right", delay: 500, content: (0, jsx_runtime_1.jsxs)("span", { className: "crisp-navitem-shortcut", children: [(0, jsx_runtime_1.jsx)("span", { className: "crisp-navitem-shortcut-desc", children: shortcut.description }), (0, jsx_runtime_1.jsx)("span", { className: "crisp-navitem-shortcut-chord", children: shortcut.keys.map((key, i) => ((0, jsx_runtime_1.jsxs)(React.Fragment, { children: [i > 0 && ((0, jsx_runtime_1.jsx)("span", { className: "crisp-navitem-shortcut-then", children: "then" })), (0, jsx_runtime_1.jsx)(kbd_1.Kbd, { children: key })] }, i))) })] }), children: row }));
43
48
  });
44
49
  exports.NavItem.displayName = "NavItem";
@@ -21,6 +21,22 @@ const config = (0, core_1.defineRecipe)({
21
21
  variants: {},
22
22
  defaultVariants: {},
23
23
  compoundVariants: [],
24
+ // Tooltip portals its content to document.body (same reason sidebar.recipe.ts's
25
+ // menu content lives in globalCss, not a `&`-nested rule): a shortcut hint
26
+ // never sits inside `.crisp-navitem` in the live DOM, so it needs an
27
+ // unscoped rule to be reached at all. Values measured live on
28
+ // app.attio.com's own "G then <letter>" hint (crisp#633): the description
29
+ // inherits Tooltip's own #eeeff1 text colour for free (it's the same hex),
30
+ // but "then" and the Kbd chip are their own greys, hardcoded raw rather
31
+ // than through a token -- same call tooltip.recipe.ts already made for
32
+ // this exact bubble, which is always dark regardless of the app's theme.
33
+ globalCss: `
34
+ .crisp-navitem-shortcut { display: flex; align-items: baseline; justify-content: space-between; gap: ${tokens_1.contract.space["3"]}; }
35
+ .crisp-navitem-shortcut-desc { font-size: ${tokens_1.contract.text.sm}; font-weight: ${tokens_1.contract.weight.medium}; line-height: 16px; white-space: nowrap; }
36
+ .crisp-navitem-shortcut-chord { display: inline-flex; align-items: center; gap: ${tokens_1.contract.space["1"]}; flex-shrink: 0; }
37
+ .crisp-navitem-shortcut-then { font-size: ${tokens_1.contract.text.xs}; font-weight: ${tokens_1.contract.weight.medium}; color: #898a8d; }
38
+ .crisp-navitem-shortcut-chord .crisp-kbd { text-transform: uppercase; font-weight: ${tokens_1.contract.weight.regular}; color: #a2a4a7; box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.2); }
39
+ `,
24
40
  });
25
41
  exports.navitemRecipe = Object.assign((0, core_1.resolveRecipe)(config), {
26
42
  config,
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.objectsettingsRecipe = exports.ObjectsList = exports.ObjectSettings = exports.ObjectGeneralSection = exports.ObjectDetailPage = exports.ObjectAppearanceSection = exports.CreateAttributeDialog = exports.AttributeTypeSelect = exports.AttributesTable = exports.ATTRIBUTE_TYPE_OPTIONS = exports.ATTRIBUTE_AI_AUTOFILL_OPTIONS = void 0;
3
+ exports.objectsettingsRecipe = exports.ObjectsList = exports.ObjectGeneralSection = exports.ObjectDetailPage = exports.ObjectAppearanceSection = exports.CreateAttributeDialog = exports.AttributeTypeSelect = exports.AttributesTable = exports.ATTRIBUTE_TYPE_OPTIONS = exports.ATTRIBUTE_AI_AUTOFILL_OPTIONS = void 0;
4
4
  var objectsettings_1 = require("./objectsettings");
5
5
  Object.defineProperty(exports, "ATTRIBUTE_AI_AUTOFILL_OPTIONS", { enumerable: true, get: function () { return objectsettings_1.ATTRIBUTE_AI_AUTOFILL_OPTIONS; } });
6
6
  Object.defineProperty(exports, "ATTRIBUTE_TYPE_OPTIONS", { enumerable: true, get: function () { return objectsettings_1.ATTRIBUTE_TYPE_OPTIONS; } });
@@ -10,7 +10,6 @@ Object.defineProperty(exports, "CreateAttributeDialog", { enumerable: true, get:
10
10
  Object.defineProperty(exports, "ObjectAppearanceSection", { enumerable: true, get: function () { return objectsettings_1.ObjectAppearanceSection; } });
11
11
  Object.defineProperty(exports, "ObjectDetailPage", { enumerable: true, get: function () { return objectsettings_1.ObjectDetailPage; } });
12
12
  Object.defineProperty(exports, "ObjectGeneralSection", { enumerable: true, get: function () { return objectsettings_1.ObjectGeneralSection; } });
13
- Object.defineProperty(exports, "ObjectSettings", { enumerable: true, get: function () { return objectsettings_1.ObjectSettings; } });
14
13
  Object.defineProperty(exports, "ObjectsList", { enumerable: true, get: function () { return objectsettings_1.ObjectsList; } });
15
14
  var objectsettings_recipe_1 = require("./objectsettings.recipe");
16
15
  Object.defineProperty(exports, "objectsettingsRecipe", { enumerable: true, get: function () { return objectsettings_recipe_1.objectsettingsRecipe; } });
@@ -33,7 +33,7 @@ var __importStar = (this && this.__importStar) || (function () {
33
33
  };
34
34
  })();
35
35
  Object.defineProperty(exports, "__esModule", { value: true });
36
- exports.ObjectSettings = exports.ATTRIBUTE_AI_AUTOFILL_OPTIONS = exports.ATTRIBUTE_TYPE_OPTIONS = void 0;
36
+ exports.ATTRIBUTE_AI_AUTOFILL_OPTIONS = exports.ATTRIBUTE_TYPE_OPTIONS = void 0;
37
37
  exports.ObjectsList = ObjectsList;
38
38
  exports.ObjectDetailPage = ObjectDetailPage;
39
39
  exports.ObjectGeneralSection = ObjectGeneralSection;
@@ -157,7 +157,7 @@ function ObjectDetailPage({ icon, iconColor, name, typeLabel, description, tab,
157
157
  setInternalTab(next);
158
158
  onTabChange?.(next);
159
159
  };
160
- return ((0, jsx_runtime_1.jsx)("div", { className: (0, objectsettings_recipe_1.objectsettingsRecipe)(), children: (0, jsx_runtime_1.jsxs)("div", { className: "crisp-objectsettings-page", children: [(0, jsx_runtime_1.jsx)("div", { className: "crisp-objectsettings-head", children: (0, jsx_runtime_1.jsxs)("div", { style: { display: "flex", alignItems: "flex-start", gap: 12 }, children: [(0, jsx_runtime_1.jsx)("span", { className: "crisp-objectsettings-icon crisp-objectsettings-icon--lg", style: { background: iconColor ?? "currentColor" }, children: icon }), (0, jsx_runtime_1.jsxs)("div", { className: "crisp-objectsettings-heading", children: [(0, jsx_runtime_1.jsxs)("h1", { className: "crisp-objectsettings-title", children: [name, typeLabel != null && ((0, jsx_runtime_1.jsx)(badge_1.Badge, { tone: "neutral", size: "small", children: typeLabel }))] }), description != null && ((0, jsx_runtime_1.jsx)("p", { className: "crisp-objectsettings-subtitle", children: description }))] })] }) }), (0, jsx_runtime_1.jsx)(tabs_1.Tabs, { value: active, onValueChange: setTab, tabs: [
160
+ return ((0, jsx_runtime_1.jsx)("div", { className: (0, objectsettings_recipe_1.objectsettingsRecipe)(), children: (0, jsx_runtime_1.jsxs)("div", { className: "crisp-objectsettings-page", children: [(0, jsx_runtime_1.jsx)("div", { className: "crisp-objectsettings-head", children: (0, jsx_runtime_1.jsxs)("div", { style: { display: "flex", alignItems: "flex-start", gap: 12 }, children: [(0, jsx_runtime_1.jsx)("span", { className: "crisp-objectsettings-icon crisp-objectsettings-icon--lg", style: { background: iconColor ?? "currentColor" }, children: icon }), (0, jsx_runtime_1.jsxs)("div", { className: "crisp-objectsettings-heading", children: [(0, jsx_runtime_1.jsxs)("h1", { className: "crisp-objectsettings-title", children: [name, typeLabel != null && ((0, jsx_runtime_1.jsx)(badge_1.Badge, { tone: "neutral", size: "medium", children: typeLabel }))] }), description != null && ((0, jsx_runtime_1.jsx)("p", { className: "crisp-objectsettings-subtitle", children: description }))] })] }) }), (0, jsx_runtime_1.jsx)(tabs_1.Tabs, { value: active, onValueChange: setTab, tabs: [
161
161
  { value: "configuration", label: configurationLabel },
162
162
  {
163
163
  value: "attributes",
@@ -245,7 +245,7 @@ function AttributesTable({ attributes, onOpenAttribute, onEditAttribute, onArchi
245
245
  {
246
246
  key: "properties",
247
247
  header: propertiesColumnLabel,
248
- render: (_v, row) => ((0, jsx_runtime_1.jsxs)("span", { style: { display: "flex", gap: 6 }, children: [row.system && ((0, jsx_runtime_1.jsx)(badge_1.Badge, { tone: "neutral", size: "small", children: systemLabel })), row.enriched && ((0, jsx_runtime_1.jsx)(badge_1.Badge, { tone: "purple", size: "small", children: enrichedLabel }))] })),
248
+ render: (_v, row) => ((0, jsx_runtime_1.jsxs)("span", { style: { display: "flex", gap: 6 }, children: [row.system && ((0, jsx_runtime_1.jsx)(badge_1.Badge, { tone: "neutral", size: "medium", children: systemLabel })), row.enriched && ((0, jsx_runtime_1.jsx)(badge_1.Badge, { tone: "purple", size: "medium", children: enrichedLabel }))] })),
249
249
  },
250
250
  {
251
251
  key: "actions",
@@ -353,13 +353,3 @@ function CreateAttributeDialog({ type, onTypeChange, typeOptions, aiAutofillType
353
353
  return ((0, jsx_runtime_1.jsxs)(formdialog_1.FormDialog, { title: title, submitLabel: (0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [createLabel, (0, jsx_runtime_1.jsxs)("span", { className: "crisp-objectsettings-kbd", "aria-hidden": "true", children: [(0, jsx_runtime_1.jsx)("kbd", { children: "\u2318" }), (0, jsx_runtime_1.jsx)("kbd", { children: "\u21B5" })] })] }), onSubmit: onSubmit, onClose: onClose, busy: busy, error: error, size: 520, className: (0, objectsettings_recipe_1.objectsettingsRecipe)(), children: [(0, jsx_runtime_1.jsx)(formfield_1.FormField, { label: attributeTypeLabel, children: (0, jsx_runtime_1.jsx)(AttributeTypeSelect, { value: type, onValueChange: onTypeChange, options: typeOptions, aiAutofillOptions: aiAutofillTypeOptions }) }), (0, jsx_runtime_1.jsx)(formfield_1.FormField, { label: nameLabel, htmlFor: nameId, children: (0, jsx_runtime_1.jsx)(input_1.Input, { id: nameId, autoFocus: true, value: name, onChange: (e) => onNameChange(e.target.value) }) }), (0, jsx_runtime_1.jsx)(formfield_1.FormField, { label: descriptionLabel, optional: true, children: (0, jsx_runtime_1.jsx)(textarea_1.Textarea, { placeholder: descriptionPlaceholder, value: description, onChange: (e) => onDescriptionChange(e.target.value) }) }), (0, jsx_runtime_1.jsxs)("div", { className: "crisp-objectsettings-aiautofill", children: [(0, jsx_runtime_1.jsx)("span", { className: "crisp-objectsettings-aiautofill-label", children: aiAutofillLabel }), (0, jsx_runtime_1.jsxs)("label", { className: "crisp-objectsettings-aiautofill-row", htmlFor: switchId, children: [(0, jsx_runtime_1.jsx)(lucide_react_1.Sparkles, { size: 14 }), (0, jsx_runtime_1.jsx)("span", { children: setUpAiAutofillLabel }), (0, jsx_runtime_1.jsx)(switch_1.Switch, { id: switchId, checked: aiAutofillEnabled, onCheckedChange: onAiAutofillChange })] })] })] }));
354
354
  }
355
355
  CreateAttributeDialog.displayName = "CreateAttributeDialog";
356
- /**
357
- * This folder holds several components (`ObjectsList`, `ObjectDetailPage`,
358
- * `AttributesTable`, `CreateAttributeDialog`, …), not one named after the
359
- * slug — unlike every other product dir, whose docs `<ManualInstallation>`
360
- * resolves an import binding from the folder slug ("avatargroup" →
361
- * `AvatarGroup`) by reading this file's exports. This alias exists only so
362
- * that resolver finds a match for "objectsettings"; consumers should import
363
- * the real names above, never this one.
364
- */
365
- exports.ObjectSettings = ObjectsList;