@crisp-ui-kit/crisp 0.54.4 → 0.54.5

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/chip.css CHANGED
@@ -1,5 +1,5 @@
1
1
  .crisp-chip {
2
- display: inline-flex; align-items: center; gap: 3px; height: var(--crisp-size-chip); min-width: 0; padding-inline: var(--crisp-space-1); border-radius: var(--crisp-radius-chip); font-size: var(--crisp-text-lg); line-height: 20px; font-weight: var(--crisp-weight-medium); white-space: nowrap;
2
+ display: inline-flex; align-items: center; gap: 3px; height: var(--crisp-size-chip); min-width: 0; max-width: 100%; padding-inline: var(--crisp-space-1); border-radius: var(--crisp-radius-chip); font-size: var(--crisp-text-lg); line-height: 20px; font-weight: var(--crisp-weight-medium); white-space: nowrap;
3
3
  }
4
4
  .crisp-chip--truncate_true {
5
5
  & .crisp-chip-label { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; display: block; }
@@ -1,6 +1,6 @@
1
1
  @layer crisp-components {
2
2
  .crisp-chip {
3
- display: inline-flex; align-items: center; gap: 3px; height: var(--crisp-size-chip); min-width: 0; padding-inline: var(--crisp-space-1); border-radius: var(--crisp-radius-chip); font-size: var(--crisp-text-lg); line-height: 20px; font-weight: var(--crisp-weight-medium); white-space: nowrap;
3
+ display: inline-flex; align-items: center; gap: 3px; height: var(--crisp-size-chip); min-width: 0; max-width: 100%; padding-inline: var(--crisp-space-1); border-radius: var(--crisp-radius-chip); font-size: var(--crisp-text-lg); line-height: 20px; font-weight: var(--crisp-weight-medium); white-space: nowrap;
4
4
  }
5
5
  .crisp-chip--truncate_true {
6
6
  & .crisp-chip-label { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; display: block; }
@@ -30,6 +30,18 @@ const spec = (0, core_1.defineSpec)({
30
30
  // -- `inline-flex` items don't by default -- which `truncate` below
31
31
  // depends on and any consumer constraining a Chip's width already needs.
32
32
  minWidth: 0,
33
+ // Caps the chip at its containing block's width (#668) -- `min-width: 0`
34
+ // alone only lets it shrink inside a flex/grid row with `flex-shrink`;
35
+ // it does nothing in a plain block or table-cell ancestor (GRC's own
36
+ // `DataTable` cells, a render-uniform `<td>`, not a flex row), where the
37
+ // chip just grew to content width and overflowed instead. Checked
38
+ // Attio's own actor chip for a value to match instead of guessing one:
39
+ // `max-width: none` there too -- it has no pixel cap either, and stays
40
+ // contained only because its own ancestor chain is a real flex row the
41
+ // whole way down, which `Chip` alone can't assume for every consumer.
42
+ // `100%` is the honest equivalent: never wider than the container,
43
+ // whatever kind of container it is.
44
+ maxWidth: "100%",
33
45
  paddingInline: tokens_1.contract.space["1"],
34
46
  borderRadius: tokens_1.contract.radius.chip,
35
47
  fontSize: tokens_1.contract.text.lg,
@@ -45,6 +45,12 @@ function initials(name) {
45
45
  * fixed-size.
46
46
  */
47
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 })] }));
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),
49
+ // Explicit rather than relying on the flex default (a fixed-size,
50
+ // non-wrapping element's own min-content floor already resists
51
+ // shrinking in every browser tested) — #668 asked "does the avatar
52
+ // stay" as its own question, so it gets its own undeniable answer
53
+ // rather than an implicit one.
54
+ style: { flexShrink: 0 }, 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
55
  }
50
56
  PersonChip.displayName = "PersonChip";
@@ -177,7 +177,7 @@ function applySorts(rows, sorts, get = (row, key) => row[key]) {
177
177
  * this (Sort: Due date/Assignee/Creation date; Filter: Assignee/Record/
178
178
  * Created by), which one shared list can't reproduce.
179
179
  */
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 }) {
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, viewColumns: viewColumnsProp, shownKeys: shownKeysProp, defaultShownKeys, onShownKeysChange, viewSettingsLabel: viewSettingsLabelProp, reorderAttributeLabel: reorderAttributeLabelProp, hideAttributeLabel, addAttributeToViewLabel: addAttributeToViewLabelProp, searchAttributesPlaceholder: searchAttributesPlaceholderProp, ...rest }) {
181
181
  // crisp/#471: an explicit prop always wins; missing, it reads the nearest
182
182
  // CrispMessagesProvider's translation of the English default below, or
183
183
  // that default itself with no provider — unchanged from before this
@@ -200,6 +200,10 @@ function TableToolbar({ columns, sortColumns: sortColumnsProp, filterColumns: fi
200
200
  const deleteFilterLabel = (0, core_1.useCrispMessage)(deleteFilterLabelProp, "Delete filter");
201
201
  const ascendingLabel = (0, core_1.useCrispMessage)(ascendingLabelProp, "Ascending");
202
202
  const descendingLabel = (0, core_1.useCrispMessage)(descendingLabelProp, "Descending");
203
+ const viewSettingsLabel = (0, core_1.useCrispMessage)(viewSettingsLabelProp, "View settings");
204
+ const reorderAttributeLabel = (0, core_1.useCrispMessage)(reorderAttributeLabelProp, "Reorder attribute");
205
+ const addAttributeToViewLabel = (0, core_1.useCrispMessage)(addAttributeToViewLabelProp, "Add attribute to view");
206
+ const searchAttributesPlaceholder = (0, core_1.useCrispMessage)(searchAttributesPlaceholderProp, "Search attributes…");
203
207
  // OP_LABELS' own words ("contains", "is", "is any of", …) have no prop of
204
208
  // their own — a whole lookup table, not one string — so they translate
205
209
  // straight from the provider's dictionary at the two spots they render
@@ -211,10 +215,17 @@ function TableToolbar({ columns, sortColumns: sortColumnsProp, filterColumns: fi
211
215
  // gets `columns` on the other, unchanged from before either prop existed.
212
216
  const sortColumns = sortColumnsProp ?? columns ?? [];
213
217
  const filterColumns = filterColumnsProp ?? columns ?? [];
218
+ const viewColumns = viewColumnsProp ?? columns ?? [];
214
219
  const [sortsState, setSortsState] = React.useState(defaultSorts ?? []);
215
220
  const [filtersState, setFiltersState] = React.useState(defaultFilters ?? []);
221
+ // #590: "everything shown" unless told otherwise -- lazy initializer so
222
+ // this only reads `viewColumns` once, at mount, the same way `defaultSorts`/
223
+ // `defaultFilters` above seed once and are never re-derived from a prop
224
+ // that may keep changing identity across renders.
225
+ const [shownKeysState, setShownKeysState] = React.useState(() => defaultShownKeys ?? viewColumns.map((c) => c.key));
216
226
  const sorts = sortsProp ?? sortsState;
217
227
  const filters = filtersProp ?? filtersState;
228
+ const shownKeys = shownKeysProp ?? shownKeysState;
218
229
  const putSorts = (next) => {
219
230
  if (sortsProp === undefined)
220
231
  setSortsState(next);
@@ -225,11 +236,28 @@ function TableToolbar({ columns, sortColumns: sortColumnsProp, filterColumns: fi
225
236
  setFiltersState(next);
226
237
  onFiltersChange?.(next);
227
238
  };
239
+ const putShownKeys = (next) => {
240
+ if (shownKeysProp === undefined)
241
+ setShownKeysState(next);
242
+ onShownKeysChange?.(next);
243
+ };
228
244
  const [openMenu, setOpenMenu] = React.useState(null);
229
245
  const [dragSort, setDragSort] = React.useState(null);
230
246
  const [filterQuery, setFilterQuery] = React.useState("");
231
247
  // filters the "isanyof" checkbox list, same lifetime as filterQuery above
232
248
  const [anyOfQuery, setAnyOfQuery] = React.useState("");
249
+ const [dragAttr, setDragAttr] = React.useState(null);
250
+ // #590: the popover has two screens -- the draggable shown-attribute list
251
+ // (default) and, behind "Add attribute to view", a search list of the
252
+ // rest. Measured live, 2026-09-25: clicking "View settings" opens a
253
+ // `role="dialog"` of dnd-kit `aria-roledescription="sortable"` rows --
254
+ // grip, type icon, label -- with no search box, ending in a static
255
+ // "Add attribute to view" row; each row also carries a "View attribute
256
+ // menu" kebab whose own contents weren't explored (undefined scope, not
257
+ // built here -- hiding an attribute is a trailing control instead, see
258
+ // the hide button below).
259
+ const [addingAttribute, setAddingAttribute] = React.useState(false);
260
+ const [addAttrQuery, setAddAttrQuery] = React.useState("");
233
261
  // close any open menu on outside click (anything not inside a menu or its
234
262
  // trigger) or Escape
235
263
  React.useEffect(() => {
@@ -292,6 +320,27 @@ function TableToolbar({ columns, sortColumns: sortColumnsProp, filterColumns: fi
292
320
  putFilters(filters.filter((c) => c.id !== id));
293
321
  setOpenMenu(null);
294
322
  };
323
+ // #590: shown attributes, in `shownKeys`' order -- a key with no matching
324
+ // `viewColumns` entry (stale state after a column is removed upstream)
325
+ // drops out rather than rendering a blank row.
326
+ const shownAttrs = shownKeys
327
+ .map((k) => viewColumns.find((c) => c.key === k))
328
+ .filter((c) => c != null);
329
+ const hiddenAttrs = viewColumns.filter((c) => !shownKeys.includes(c.key));
330
+ const moveAttr = (from, to) => {
331
+ if (from === to)
332
+ return;
333
+ const next = [...shownKeys];
334
+ const [moved] = next.splice(from, 1);
335
+ next.splice(to, 0, moved);
336
+ putShownKeys(next);
337
+ };
338
+ const hideAttr = (key) => putShownKeys(shownKeys.filter((k) => k !== key));
339
+ const showAttr = (key) => {
340
+ putShownKeys([...shownKeys, key]);
341
+ setAddingAttribute(false);
342
+ setAddAttrQuery("");
343
+ };
295
344
  // #594: the filter chip's kebab is a real role="menu" (crisp/#188's shared
296
345
  // model — arrows, Home/End, typeahead, Escape), not a hand-rolled div like
297
346
  // the sort/condition popovers above (those are role="dialog", which makes
@@ -381,7 +430,31 @@ function TableToolbar({ columns, sortColumns: sortColumnsProp, filterColumns: fi
381
430
  });
382
431
  }, children: v }, v));
383
432
  })] }))] }))] }, cond.id));
384
- }), (0, jsx_runtime_1.jsx)("span", { className: "crisp-tabletoolbar-spacer" }), count != null && ((0, jsx_runtime_1.jsx)("span", { className: "crisp-tabletoolbar-count", children: count })), children] }) }));
433
+ }), (0, jsx_runtime_1.jsx)("span", { className: "crisp-tabletoolbar-divider", "aria-hidden": "true" }), (0, jsx_runtime_1.jsxs)("span", { className: "crisp-tabletoolbar-toolgroup", children: [(0, jsx_runtime_1.jsxs)("button", { type: "button", className: "crisp-tabletoolbar-tool crisp-tabletoolbar-tool--ghost", onClick: () => {
434
+ if (openMenu === "viewsettings") {
435
+ setOpenMenu(null);
436
+ }
437
+ else {
438
+ setAddingAttribute(false);
439
+ setAddAttrQuery("");
440
+ setOpenMenu("viewsettings");
441
+ }
442
+ }, "aria-haspopup": "dialog", "aria-expanded": openMenu === "viewsettings", "data-menu-trigger": true, children: [(0, jsx_runtime_1.jsx)(lucide_react_1.SlidersHorizontal, { size: 14, strokeWidth: 1.75, "aria-hidden": "true" }), viewSettingsLabel] }), openMenu === "viewsettings" &&
443
+ (addingAttribute ? ((0, jsx_runtime_1.jsxs)("div", { className: "crisp-tabletoolbar-menu", role: "dialog", "aria-label": addAttributeToViewLabel, children: [(0, jsx_runtime_1.jsx)("input", { className: "crisp-tabletoolbar-menu-input", placeholder: searchAttributesPlaceholder, "aria-label": searchAttributesPlaceholder,
444
+ // biome-ignore lint/a11y/noAutofocus: focus the search box on open, same as Filter's field picker
445
+ autoFocus: true, value: addAttrQuery, onChange: (e) => setAddAttrQuery(e.target.value) }), hiddenAttrs
446
+ .filter((col) => col.label
447
+ .toLowerCase()
448
+ .includes(addAttrQuery.trim().toLowerCase()))
449
+ .map((col) => ((0, jsx_runtime_1.jsxs)("button", { type: "button", className: "crisp-tabletoolbar-menu-item", onClick: () => showAttr(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)))] })) : ((0, jsx_runtime_1.jsxs)("div", { className: "crisp-tabletoolbar-menu crisp-tabletoolbar-sortpop", role: "dialog", "aria-label": viewSettingsLabel, children: [shownAttrs.map((col, i) => ((0, jsx_runtime_1.jsxs)("div", { className: "crisp-tabletoolbar-sort-row", onDragOver: (e) => {
450
+ if (dragAttr !== null)
451
+ e.preventDefault();
452
+ }, onDrop: () => {
453
+ if (dragAttr !== null)
454
+ moveAttr(dragAttr, i);
455
+ setDragAttr(null);
456
+ }, children: [(0, jsx_runtime_1.jsx)("button", { type: "button", className: "crisp-tabletoolbar-sort-grip", "aria-label": reorderAttributeLabel, draggable: true, onDragStart: () => setDragAttr(i), onDragEnd: () => setDragAttr(null), children: (0, jsx_runtime_1.jsx)(lucide_react_1.GripVertical, { size: 15, strokeWidth: 1.75 }) }), (0, jsx_runtime_1.jsxs)("span", { className: "crisp-tabletoolbar-sort-optlabel crisp-tabletoolbar-sort-field", children: [col.icon ? ((0, jsx_runtime_1.jsx)(col.icon, { size: 15, strokeWidth: 1.75 })) : null, col.label] }), (0, jsx_runtime_1.jsx)("button", { type: "button", className: "crisp-tabletoolbar-sort-remove", onClick: () => hideAttr(col.key), "aria-label": hideAttributeLabel?.(col.label) ??
457
+ `${crispMessages.Hide ?? "Hide"} ${col.label} ${crispMessages["from view"] ?? "from view"}`, children: (0, jsx_runtime_1.jsx)(lucide_react_1.X, { size: 15, strokeWidth: 1.75 }) })] }, col.key))), (0, jsx_runtime_1.jsx)("div", { className: "crisp-tabletoolbar-sort-addrow", children: (0, jsx_runtime_1.jsxs)("button", { type: "button", className: "crisp-tabletoolbar-sort-add", onClick: () => setAddingAttribute(true), children: [(0, jsx_runtime_1.jsx)(lucide_react_1.Plus, { size: 15, strokeWidth: 1.75 }), addAttributeToViewLabel] }) })] })))] }), (0, jsx_runtime_1.jsx)("span", { className: "crisp-tabletoolbar-spacer" }), count != null && ((0, jsx_runtime_1.jsx)("span", { className: "crisp-tabletoolbar-count", children: count })), children] }) }));
385
458
  }
386
459
  /**
387
460
  * The Sort/Filter chip's own chrome (`.crisp-tabletoolbar-tool`), standalone
@@ -46,13 +46,6 @@ const menu_1 = require("../../components/menu");
46
46
  const popover_1 = require("../../components/popover");
47
47
  const core_1 = require("../../core");
48
48
  const comments_recipe_1 = require("./comments.recipe");
49
- /** A single literal JSX occurrence for the reply glyph, used at two sizes --
50
- * so the RTL-mirroring sweep (which counts the flip-marker attribute against
51
- * source-text occurrences, not runtime renders) sees one mark for one
52
- * direction-bearing icon, not two. */
53
- function ReplyGlyph({ size }) {
54
- return ((0, jsx_runtime_1.jsx)(lucide_react_1.CornerUpLeft, { "data-rtl-flip": "", size: size, strokeWidth: 2, "aria-hidden": "true" }));
55
- }
56
49
  const DEFAULT_EMOJI = [
57
50
  "👍",
58
51
  "👎",
@@ -159,7 +152,7 @@ function serializeComposerField(field) {
159
152
  * disabled until the field holds text. Uncontrolled — reads its own DOM on
160
153
  * submit, the same shape `RichTextEditor` uses for its body.
161
154
  */
162
- const Composer = React.forwardRef(function Composer({ placeholder, postLabel, mentionTrigger, renderMentionPicker, replyToolLabel, mentionToolLabel, onSubmit, }, ref) {
155
+ const Composer = React.forwardRef(function Composer({ placeholder, postLabel, mentionTrigger, renderMentionPicker, mentionToolLabel, onSubmit, }, ref) {
163
156
  const fieldRef = React.useRef(null);
164
157
  const [empty, setEmpty] = React.useState(true);
165
158
  const [mention, setMention] = React.useState(null);
@@ -295,15 +288,15 @@ const Composer = React.forwardRef(function Composer({ placeholder, postLabel, me
295
288
  e.preventDefault();
296
289
  submit();
297
290
  }
298
- } }), (0, jsx_runtime_1.jsxs)("div", { className: "crisp-comments-composer-foot", children: [(0, jsx_runtime_1.jsxs)("div", { className: "crisp-comments-composer-tools", children: [(0, jsx_runtime_1.jsx)("button", { type: "button", className: "crisp-comments-tool", "aria-label": replyToolLabel, children: (0, jsx_runtime_1.jsx)(ReplyGlyph, { size: 14 }) }), (0, jsx_runtime_1.jsx)("button", { type: "button", className: "crisp-comments-tool", "aria-label": mentionToolLabel, onClick: () => {
299
- const field = fieldRef.current;
300
- if (!field)
301
- return;
302
- field.focus();
303
- document.execCommand("insertText", false, mentionTrigger);
304
- syncEmpty();
305
- updateMention();
306
- }, children: "@" })] }), (0, jsx_runtime_1.jsx)("button", { type: "button", className: "crisp-comments-post", disabled: empty, onClick: submit, children: postLabel })] }), mention &&
291
+ } }), (0, jsx_runtime_1.jsxs)("div", { className: "crisp-comments-composer-foot", children: [(0, jsx_runtime_1.jsx)("div", { className: "crisp-comments-composer-tools", children: (0, jsx_runtime_1.jsx)("button", { type: "button", className: "crisp-comments-tool", "aria-label": mentionToolLabel, onClick: () => {
292
+ const field = fieldRef.current;
293
+ if (!field)
294
+ return;
295
+ field.focus();
296
+ document.execCommand("insertText", false, mentionTrigger);
297
+ syncEmpty();
298
+ updateMention();
299
+ }, children: "@" }) }), (0, jsx_runtime_1.jsx)("button", { type: "button", className: "crisp-comments-post", disabled: empty, onClick: submit, children: postLabel })] }), mention &&
307
300
  renderMentionPicker?.({
308
301
  query: mention.query,
309
302
  anchor: mention.anchor,
@@ -312,7 +305,7 @@ const Composer = React.forwardRef(function Composer({ placeholder, postLabel, me
312
305
  })] }));
313
306
  });
314
307
  function CommentRow({ comment, replyLabel, reactLabel, resolveLabel, moreLabel, copyLinkLabel, deleteLabel, addReactionLabel, reactionSearchLabel, emojiOptions, resolvable, resolved, resolvedLabel, onReply, onReact, onResolve, onCopyLink, onDeleteRequest, }) {
315
- 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)(ReplyGlyph, { size: 16 }) })), (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 })] })] })] }));
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 })] })] })] }));
316
309
  }
317
310
  /**
318
311
  * Per-record comment threads (crisp#625) — measured live on app.attio.com's
@@ -323,7 +316,7 @@ function CommentRow({ comment, replyLabel, reactLabel, resolveLabel, moreLabel,
323
316
  * separate row affordance (P-051) — deliberately NOT part of this
324
317
  * component, since it lives in a table cell this component doesn't own.
325
318
  */
326
- function CommentsPanel({ threads, currentUserId, subject, subjectIcon, showResolved = false, onShowResolvedChange, onlyMine = false, onOnlyMineChange, activeThreadId: activeThreadIdProp, onActiveThreadChange, onPost, onReact, onResolve, onDelete, onCopyLink, emojiOptions = DEFAULT_EMOJI, mentionTrigger = "@", renderMentionPicker, onClose, closeLabel: closeLabelProp, backLabel: backLabelProp, title: titleProp, threadTitle: threadTitleProp, breadcrumbPrefix: breadcrumbPrefixProp, composerPlaceholder: composerPlaceholderProp, replyPlaceholder: replyPlaceholderProp, postLabel: postLabelProp, replyPostLabel: replyPostLabelProp, emptyLabel, filterLabel: filterLabelProp, showResolvedLabel: showResolvedLabelProp, onlyMineLabel: onlyMineLabelProp, replyActionLabel: replyActionLabelProp, reactActionLabel: reactActionLabelProp, resolveActionLabel: resolveActionLabelProp, resolvedByYouLabel: resolvedByYouLabelProp, resolvedByLabel, moreActionsLabel: moreActionsLabelProp, copyLinkLabel: copyLinkLabelProp, deleteLabel: deleteLabelProp, addReactionLabel: addReactionLabelProp, reactionSearchLabel: reactionSearchLabelProp, deleteConfirmTitle: deleteConfirmTitleProp, deleteConfirmBody: deleteConfirmBodyProp, deleteConfirmActionLabel: deleteConfirmActionLabelProp, cancelLabel: cancelLabelProp, composerReplyToolLabel: composerReplyToolLabelProp, composerMentionToolLabel: composerMentionToolLabelProp, className, ...rest }) {
319
+ function CommentsPanel({ threads, currentUserId, subject, subjectIcon, showResolved = false, onShowResolvedChange, onlyMine = false, onOnlyMineChange, activeThreadId: activeThreadIdProp, onActiveThreadChange, onPost, onReact, onResolve, onDelete, onCopyLink, emojiOptions = DEFAULT_EMOJI, mentionTrigger = "@", renderMentionPicker, onClose, closeLabel: closeLabelProp, backLabel: backLabelProp, title: titleProp, threadTitle: threadTitleProp, breadcrumbPrefix: breadcrumbPrefixProp, composerPlaceholder: composerPlaceholderProp, replyPlaceholder: replyPlaceholderProp, postLabel: postLabelProp, replyPostLabel: replyPostLabelProp, emptyLabel, filterLabel: filterLabelProp, showResolvedLabel: showResolvedLabelProp, onlyMineLabel: onlyMineLabelProp, replyActionLabel: replyActionLabelProp, reactActionLabel: reactActionLabelProp, resolveActionLabel: resolveActionLabelProp, resolvedByYouLabel: resolvedByYouLabelProp, resolvedByLabel, moreActionsLabel: moreActionsLabelProp, copyLinkLabel: copyLinkLabelProp, deleteLabel: deleteLabelProp, addReactionLabel: addReactionLabelProp, reactionSearchLabel: reactionSearchLabelProp, deleteConfirmTitle: deleteConfirmTitleProp, deleteConfirmBody: deleteConfirmBodyProp, deleteConfirmActionLabel: deleteConfirmActionLabelProp, cancelLabel: cancelLabelProp, composerMentionToolLabel: composerMentionToolLabelProp, className, ...rest }) {
327
320
  const closeLabel = (0, core_1.useCrispMessage)(closeLabelProp, "Close");
328
321
  const backLabel = (0, core_1.useCrispMessage)(backLabelProp, "Back");
329
322
  const title = (0, core_1.useCrispMessage)(titleProp, "All comments");
@@ -349,7 +342,6 @@ function CommentsPanel({ threads, currentUserId, subject, subjectIcon, showResol
349
342
  const deleteConfirmBody = (0, core_1.useCrispMessage)(deleteConfirmBodyProp, "You're about to delete this comment. Once deleted it cannot be restored.");
350
343
  const deleteConfirmActionLabel = (0, core_1.useCrispMessage)(deleteConfirmActionLabelProp, "Delete");
351
344
  const cancelLabel = (0, core_1.useCrispMessage)(cancelLabelProp, "Cancel");
352
- const composerReplyToolLabel = (0, core_1.useCrispMessage)(composerReplyToolLabelProp, "Reply");
353
345
  const composerMentionToolLabel = (0, core_1.useCrispMessage)(composerMentionToolLabelProp, "Mention someone");
354
346
  const [internalActiveId, setInternalActiveId] = React.useState(null);
355
347
  const activeThreadId = activeThreadIdProp ?? internalActiveId;
@@ -394,7 +386,7 @@ function CommentsPanel({ threads, currentUserId, subject, subjectIcon, showResol
394
386
  ? resolvedByYouLabel
395
387
  : (resolvedByLabel?.(t.resolvedBy.name) ??
396
388
  `Resolved by ${t.resolvedBy.name}`)
397
- : 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, replyToolLabel: composerReplyToolLabel, 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) => {
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) => {
398
390
  if (!open)
399
391
  setDeleteTarget(null);
400
392
  }, 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: () => {
@@ -98,29 +98,44 @@ const config = (0, core_1.defineRecipe)({
98
98
  RichTextEditor's own \`.crisp-rte-mention\` (crisp#428), reused here
99
99
  rather than re-derived since it's the same atomic-chip affordance. */
100
100
  & .crisp-comments-mention { display: inline; padding: 0 2px; border-radius: 4px; background: color-mix(in srgb, ${tokens_1.contract.bgBrandSolid} 12%, transparent); color: ${tokens_1.contract.bgBrandSolid}; font-weight: ${tokens_1.contract.weight.medium}; user-select: none; }
101
-
101
+ ${core_1.rtlFlipCss}`,
102
+ variants: {},
103
+ defaultVariants: {},
104
+ compoundVariants: [],
105
+ // Two pieces below intentionally live OUTSIDE the nested \`base\` above,
106
+ // the same reason Menu's own \`.crisp-menu-content\` does (menu.recipe.ts's
107
+ // own comment, crisp#357): nesting compiles to a descendant combinator
108
+ // scoped to \`.crisp-comments\`, which needs real DOM containment. Both of
109
+ // these escape it -- \`ReactionPicker\` because \`Popover\` portals its
110
+ // content to \`document.body\`, and \`CommentCountBadge\` because it's built
111
+ // to be dropped into a table cell that has no \`.crisp-comments\` ancestor
112
+ // at all (its own module doc comment). Nested, this shipped invisible:
113
+ // \`CommentCountBadge\`'s dot rendered at 0×0 and the reaction picker fell
114
+ // back to \`Popover\`'s own generic (unstyled-by-this-recipe) surface --
115
+ // caught taking this component's own screenshots, not by any gate, since
116
+ // \`spec-check\` reads recipe source text and both classes were right there
117
+ // in it.
118
+ globalCss: `
102
119
  /* Reaction picker -- see \`ReactionPicker\`'s own doc comment: the exact
103
120
  chrome wasn't independently pixel-measured this pass (screenshot
104
121
  capture broke mid-session), so this reuses Menu's already-measured
105
122
  floating-popover shell rather than a new value. */
106
- & .crisp-comments-reactionpicker { width: 240px; padding: ${tokens_1.contract.space["2"]}; border-radius: ${tokens_1.contract.radius.xl}; background: ${tokens_1.contract.bgSurface}; box-shadow: ${tokens_1.elevation.floating}; }
107
- & .crisp-comments-reactionpicker-search { box-sizing: border-box; width: 100%; height: 28px; margin-bottom: ${tokens_1.contract.space["2"]}; padding: 0 ${tokens_1.contract.space["2"]}; border: none; border-radius: ${tokens_1.contract.radius.md}; background: ${tokens_1.contract.bgRaised}; font-family: inherit; font-size: ${tokens_1.contract.text.md}; color: ${tokens_1.contract.fgPrimary}; }
108
- & .crisp-comments-reactionpicker-search::placeholder { color: color-mix(in srgb, ${tokens_1.contract.fgPrimary} 40%, transparent); }
109
- & .crisp-comments-reactionpicker-grid { display: grid; grid-template-columns: repeat(8, 1fr); gap: ${tokens_1.contract.space["0.5"]}; max-height: 160px; overflow-y: auto; }
110
- & .crisp-comments-reactionpicker-item { display: inline-flex; align-items: center; justify-content: center; width: 26px; height: 26px; border: none; border-radius: ${tokens_1.contract.radius.sm}; background: none; font-family: inherit; font-size: 16px; line-height: 1; cursor: pointer; }
111
- & .crisp-comments-reactionpicker-item:hover { background: ${tokens_1.contract.bgRaised}; }
123
+ .crisp-comments-reactionpicker { font-family: ${tokens_1.contract.font.sans}; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; box-sizing: border-box; width: 240px; padding: ${tokens_1.contract.space["2"]}; border-radius: ${tokens_1.contract.radius.xl}; background: ${tokens_1.contract.bgSurface}; box-shadow: ${tokens_1.elevation.floating}; }
124
+ .crisp-comments-reactionpicker-search { box-sizing: border-box; width: 100%; height: 28px; margin-bottom: ${tokens_1.contract.space["2"]}; padding: 0 ${tokens_1.contract.space["2"]}; border: none; border-radius: ${tokens_1.contract.radius.md}; background: ${tokens_1.contract.bgRaised}; font-family: inherit; font-size: ${tokens_1.contract.text.md}; color: ${tokens_1.contract.fgPrimary}; }
125
+ .crisp-comments-reactionpicker-search::placeholder { color: color-mix(in srgb, ${tokens_1.contract.fgPrimary} 40%, transparent); }
126
+ .crisp-comments-reactionpicker-grid { display: grid; grid-template-columns: repeat(8, 1fr); gap: ${tokens_1.contract.space["0.5"]}; max-height: 160px; overflow-y: auto; }
127
+ .crisp-comments-reactionpicker-item { display: inline-flex; align-items: center; justify-content: center; width: 26px; height: 26px; border: none; border-radius: ${tokens_1.contract.radius.sm}; background: none; font-family: inherit; font-size: 16px; line-height: 1; cursor: pointer; }
128
+ .crisp-comments-reactionpicker-item:hover { background: ${tokens_1.contract.bgRaised}; }
112
129
 
113
130
  /* Row badge (CommentCountBadge) -- lives in its own class family so the
114
131
  grid lanes can drop it into a cell without pulling in the panel's
115
132
  CSS. Persistent state: a 6px grey dot. Hover state: a 12px comment
116
133
  icon + 12px count text, both at 55% ink, replacing the dot. */
117
- & .crisp-comments-badge { display: inline-flex; align-items: center; gap: ${tokens_1.contract.space["1"]}; border: none; background: none; padding: 0; margin: 0; font-family: inherit; cursor: pointer; color: color-mix(in srgb, ${tokens_1.contract.fgPrimary} 55%, transparent); }
118
- & .crisp-comments-badge-dot { width: 6px; height: 6px; border-radius: ${tokens_1.contract.radius.full}; background: color-mix(in srgb, ${tokens_1.contract.fgPrimary} 38%, transparent); }
119
- & .crisp-comments-badge-count { font-size: 12px; line-height: 16px; }
120
- ${core_1.rtlFlipCss}`,
121
- variants: {},
122
- defaultVariants: {},
123
- compoundVariants: [],
134
+ .crisp-comments-badge { box-sizing: border-box; font-family: ${tokens_1.contract.font.sans}; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; display: inline-flex; align-items: center; gap: ${tokens_1.contract.space["1"]}; border: none; background: none; padding: 0; margin: 0; cursor: pointer; color: color-mix(in srgb, ${tokens_1.contract.fgPrimary} 55%, transparent); }
135
+ .crisp-comments-badge-dot { box-sizing: border-box; width: 6px; height: 6px; border-radius: ${tokens_1.contract.radius.full}; background: color-mix(in srgb, ${tokens_1.contract.fgPrimary} 38%, transparent); }
136
+ .crisp-comments-badge-count { font-size: 12px; line-height: 16px; }
137
+ [dir="rtl"] .crisp-comments-badge [data-rtl-flip] { transform: scaleX(-1); }
138
+ `,
124
139
  });
125
140
  exports.commentsRecipe = (0, core_1.resolveRecipe)(config);
126
141
  exports.default = config;
package/dist/comments.css CHANGED
@@ -1,3 +1,24 @@
1
+
2
+ /* Reaction picker -- see `ReactionPicker`'s own doc comment: the exact
3
+ chrome wasn't independently pixel-measured this pass (screenshot
4
+ capture broke mid-session), so this reuses Menu's already-measured
5
+ floating-popover shell rather than a new value. */
6
+ .crisp-comments-reactionpicker { font-family: var(--crisp-font-sans); -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; box-sizing: border-box; width: 240px; padding: var(--crisp-space-2); border-radius: var(--crisp-radius-xl); background: var(--crisp-bg-surface); box-shadow: inset 0 0 0 1px var(--crisp-ring-popover), 0 0 0 1px var(--crisp-shadow-popover1), 0 4px 8px -4px var(--crisp-shadow-popover2), 0 4px 12px -2px var(--crisp-shadow-popover3); }
7
+ .crisp-comments-reactionpicker-search { box-sizing: border-box; width: 100%; height: 28px; margin-bottom: var(--crisp-space-2); padding: 0 var(--crisp-space-2); border: none; border-radius: var(--crisp-radius-md); background: var(--crisp-bg-raised); font-family: inherit; font-size: var(--crisp-text-md); color: var(--crisp-fg-primary); }
8
+ .crisp-comments-reactionpicker-search::placeholder { color: color-mix(in srgb, var(--crisp-fg-primary) 40%, transparent); }
9
+ .crisp-comments-reactionpicker-grid { display: grid; grid-template-columns: repeat(8, 1fr); gap: var(--crisp-space-0-5); max-height: 160px; overflow-y: auto; }
10
+ .crisp-comments-reactionpicker-item { display: inline-flex; align-items: center; justify-content: center; width: 26px; height: 26px; border: none; border-radius: var(--crisp-radius-sm); background: none; font-family: inherit; font-size: 16px; line-height: 1; cursor: pointer; }
11
+ .crisp-comments-reactionpicker-item:hover { background: var(--crisp-bg-raised); }
12
+
13
+ /* Row badge (CommentCountBadge) -- lives in its own class family so the
14
+ grid lanes can drop it into a cell without pulling in the panel's
15
+ CSS. Persistent state: a 6px grey dot. Hover state: a 12px comment
16
+ icon + 12px count text, both at 55% ink, replacing the dot. */
17
+ .crisp-comments-badge { box-sizing: border-box; font-family: var(--crisp-font-sans); -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; display: inline-flex; align-items: center; gap: var(--crisp-space-1); border: none; background: none; padding: 0; margin: 0; cursor: pointer; color: color-mix(in srgb, var(--crisp-fg-primary) 55%, transparent); }
18
+ .crisp-comments-badge-dot { box-sizing: border-box; width: 6px; height: 6px; border-radius: var(--crisp-radius-full); background: color-mix(in srgb, var(--crisp-fg-primary) 38%, transparent); }
19
+ .crisp-comments-badge-count { font-size: 12px; line-height: 16px; }
20
+ [dir="rtl"] .crisp-comments-badge [data-rtl-flip] { transform: scaleX(-1); }
21
+
1
22
  .crisp-comments {
2
23
  display: flex; flex-direction: column; box-sizing: border-box; width: 320px; height: 100%; background: var(--crisp-bg-surface); border-inline-start: 1px solid var(--crisp-border-subtle); font-family: var(--crisp-font-sans); -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale;
3
24
  & * { box-sizing: border-box; }
@@ -53,21 +74,6 @@
53
74
  /* An inserted "@mention" chip in the composer -- same shape as
54
75
  RichTextEditor's own `.crisp-rte-mention` (crisp#428), reused here rather than re-derived since it's the same atomic-chip affordance. */
55
76
  & .crisp-comments-mention { display: inline; padding: 0 2px; border-radius: 4px; background: color-mix(in srgb, var(--crisp-bg-brand-solid) 12%, transparent); color: var(--crisp-bg-brand-solid); font-weight: var(--crisp-weight-medium); user-select: none; }
56
-
57
- /* Reaction picker -- see `ReactionPicker`'s own doc comment: the exact chrome wasn't independently pixel-measured this pass (screenshot
58
- capture broke mid-session), so this reuses Menu's already-measured floating-popover shell rather than a new value. */
59
- & .crisp-comments-reactionpicker { width: 240px; padding: var(--crisp-space-2); border-radius: var(--crisp-radius-xl); background: var(--crisp-bg-surface); box-shadow: inset 0 0 0 1px var(--crisp-ring-popover), 0 0 0 1px var(--crisp-shadow-popover1), 0 4px 8px -4px var(--crisp-shadow-popover2), 0 4px 12px -2px var(--crisp-shadow-popover3); }
60
- & .crisp-comments-reactionpicker-search { box-sizing: border-box; width: 100%; height: 28px; margin-bottom: var(--crisp-space-2); padding: 0 var(--crisp-space-2); border: none; border-radius: var(--crisp-radius-md); background: var(--crisp-bg-raised); font-family: inherit; font-size: var(--crisp-text-md); color: var(--crisp-fg-primary); }
61
- & .crisp-comments-reactionpicker-search::placeholder { color: color-mix(in srgb, var(--crisp-fg-primary) 40%, transparent); }
62
- & .crisp-comments-reactionpicker-grid { display: grid; grid-template-columns: repeat(8, 1fr); gap: var(--crisp-space-0-5); max-height: 160px; overflow-y: auto; }
63
- & .crisp-comments-reactionpicker-item { display: inline-flex; align-items: center; justify-content: center; width: 26px; height: 26px; border: none; border-radius: var(--crisp-radius-sm); background: none; font-family: inherit; font-size: 16px; line-height: 1; cursor: pointer; }
64
- & .crisp-comments-reactionpicker-item:hover { background: var(--crisp-bg-raised); }
65
- /* Row badge (CommentCountBadge) -- lives in its own class family so the grid lanes can drop it into a cell without pulling in the panel's
66
- CSS. Persistent state: a 6px grey dot. Hover state: a 12px comment
67
- icon + 12px count text, both at 55% ink, replacing the dot. */
68
- & .crisp-comments-badge { display: inline-flex; align-items: center; gap: var(--crisp-space-1); border: none; background: none; padding: 0; margin: 0; font-family: inherit; cursor: pointer; color: color-mix(in srgb, var(--crisp-fg-primary) 55%, transparent); }
69
- & .crisp-comments-badge-dot { width: 6px; height: 6px; border-radius: var(--crisp-radius-full); background: color-mix(in srgb, var(--crisp-fg-primary) 38%, transparent); }
70
- & .crisp-comments-badge-count { font-size: 12px; line-height: 16px; }
71
77
  [dir="rtl"] & [data-rtl-flip] { transform: scaleX(-1); }
72
78
  }
73
79
 
@@ -1,4 +1,25 @@
1
1
  @layer crisp-components {
2
+
3
+ /* Reaction picker -- see `ReactionPicker`'s own doc comment: the exact
4
+ chrome wasn't independently pixel-measured this pass (screenshot
5
+ capture broke mid-session), so this reuses Menu's already-measured
6
+ floating-popover shell rather than a new value. */
7
+ .crisp-comments-reactionpicker { font-family: var(--crisp-font-sans); -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; box-sizing: border-box; width: 240px; padding: var(--crisp-space-2); border-radius: var(--crisp-radius-xl); background: var(--crisp-bg-surface); box-shadow: inset 0 0 0 1px var(--crisp-ring-popover), 0 0 0 1px var(--crisp-shadow-popover1), 0 4px 8px -4px var(--crisp-shadow-popover2), 0 4px 12px -2px var(--crisp-shadow-popover3); }
8
+ .crisp-comments-reactionpicker-search { box-sizing: border-box; width: 100%; height: 28px; margin-bottom: var(--crisp-space-2); padding: 0 var(--crisp-space-2); border: none; border-radius: var(--crisp-radius-md); background: var(--crisp-bg-raised); font-family: inherit; font-size: var(--crisp-text-md); color: var(--crisp-fg-primary); }
9
+ .crisp-comments-reactionpicker-search::placeholder { color: color-mix(in srgb, var(--crisp-fg-primary) 40%, transparent); }
10
+ .crisp-comments-reactionpicker-grid { display: grid; grid-template-columns: repeat(8, 1fr); gap: var(--crisp-space-0-5); max-height: 160px; overflow-y: auto; }
11
+ .crisp-comments-reactionpicker-item { display: inline-flex; align-items: center; justify-content: center; width: 26px; height: 26px; border: none; border-radius: var(--crisp-radius-sm); background: none; font-family: inherit; font-size: 16px; line-height: 1; cursor: pointer; }
12
+ .crisp-comments-reactionpicker-item:hover { background: var(--crisp-bg-raised); }
13
+
14
+ /* Row badge (CommentCountBadge) -- lives in its own class family so the
15
+ grid lanes can drop it into a cell without pulling in the panel's
16
+ CSS. Persistent state: a 6px grey dot. Hover state: a 12px comment
17
+ icon + 12px count text, both at 55% ink, replacing the dot. */
18
+ .crisp-comments-badge { box-sizing: border-box; font-family: var(--crisp-font-sans); -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; display: inline-flex; align-items: center; gap: var(--crisp-space-1); border: none; background: none; padding: 0; margin: 0; cursor: pointer; color: color-mix(in srgb, var(--crisp-fg-primary) 55%, transparent); }
19
+ .crisp-comments-badge-dot { box-sizing: border-box; width: 6px; height: 6px; border-radius: var(--crisp-radius-full); background: color-mix(in srgb, var(--crisp-fg-primary) 38%, transparent); }
20
+ .crisp-comments-badge-count { font-size: 12px; line-height: 16px; }
21
+ [dir="rtl"] .crisp-comments-badge [data-rtl-flip] { transform: scaleX(-1); }
22
+
2
23
  .crisp-comments {
3
24
  display: flex; flex-direction: column; box-sizing: border-box; width: 320px; height: 100%; background: var(--crisp-bg-surface); border-inline-start: 1px solid var(--crisp-border-subtle); font-family: var(--crisp-font-sans); -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale;
4
25
  & * { box-sizing: border-box; }
@@ -54,21 +75,6 @@
54
75
  /* An inserted "@mention" chip in the composer -- same shape as
55
76
  RichTextEditor's own `.crisp-rte-mention` (crisp#428), reused here rather than re-derived since it's the same atomic-chip affordance. */
56
77
  & .crisp-comments-mention { display: inline; padding: 0 2px; border-radius: 4px; background: color-mix(in srgb, var(--crisp-bg-brand-solid) 12%, transparent); color: var(--crisp-bg-brand-solid); font-weight: var(--crisp-weight-medium); user-select: none; }
57
-
58
- /* Reaction picker -- see `ReactionPicker`'s own doc comment: the exact chrome wasn't independently pixel-measured this pass (screenshot
59
- capture broke mid-session), so this reuses Menu's already-measured floating-popover shell rather than a new value. */
60
- & .crisp-comments-reactionpicker { width: 240px; padding: var(--crisp-space-2); border-radius: var(--crisp-radius-xl); background: var(--crisp-bg-surface); box-shadow: inset 0 0 0 1px var(--crisp-ring-popover), 0 0 0 1px var(--crisp-shadow-popover1), 0 4px 8px -4px var(--crisp-shadow-popover2), 0 4px 12px -2px var(--crisp-shadow-popover3); }
61
- & .crisp-comments-reactionpicker-search { box-sizing: border-box; width: 100%; height: 28px; margin-bottom: var(--crisp-space-2); padding: 0 var(--crisp-space-2); border: none; border-radius: var(--crisp-radius-md); background: var(--crisp-bg-raised); font-family: inherit; font-size: var(--crisp-text-md); color: var(--crisp-fg-primary); }
62
- & .crisp-comments-reactionpicker-search::placeholder { color: color-mix(in srgb, var(--crisp-fg-primary) 40%, transparent); }
63
- & .crisp-comments-reactionpicker-grid { display: grid; grid-template-columns: repeat(8, 1fr); gap: var(--crisp-space-0-5); max-height: 160px; overflow-y: auto; }
64
- & .crisp-comments-reactionpicker-item { display: inline-flex; align-items: center; justify-content: center; width: 26px; height: 26px; border: none; border-radius: var(--crisp-radius-sm); background: none; font-family: inherit; font-size: 16px; line-height: 1; cursor: pointer; }
65
- & .crisp-comments-reactionpicker-item:hover { background: var(--crisp-bg-raised); }
66
- /* Row badge (CommentCountBadge) -- lives in its own class family so the grid lanes can drop it into a cell without pulling in the panel's
67
- CSS. Persistent state: a 6px grey dot. Hover state: a 12px comment
68
- icon + 12px count text, both at 55% ink, replacing the dot. */
69
- & .crisp-comments-badge { display: inline-flex; align-items: center; gap: var(--crisp-space-1); border: none; background: none; padding: 0; margin: 0; font-family: inherit; cursor: pointer; color: color-mix(in srgb, var(--crisp-fg-primary) 55%, transparent); }
70
- & .crisp-comments-badge-dot { width: 6px; height: 6px; border-radius: var(--crisp-radius-full); background: color-mix(in srgb, var(--crisp-fg-primary) 38%, transparent); }
71
- & .crisp-comments-badge-count { font-size: 12px; line-height: 16px; }
72
78
  [dir="rtl"] & [data-rtl-flip] { transform: scaleX(-1); }
73
79
  }
74
80
 
@@ -27,6 +27,18 @@ const spec = defineSpec({
27
27
  // -- `inline-flex` items don't by default -- which `truncate` below
28
28
  // depends on and any consumer constraining a Chip's width already needs.
29
29
  minWidth: 0,
30
+ // Caps the chip at its containing block's width (#668) -- `min-width: 0`
31
+ // alone only lets it shrink inside a flex/grid row with `flex-shrink`;
32
+ // it does nothing in a plain block or table-cell ancestor (GRC's own
33
+ // `DataTable` cells, a render-uniform `<td>`, not a flex row), where the
34
+ // chip just grew to content width and overflowed instead. Checked
35
+ // Attio's own actor chip for a value to match instead of guessing one:
36
+ // `max-width: none` there too -- it has no pixel cap either, and stays
37
+ // contained only because its own ancestor chain is a real flex row the
38
+ // whole way down, which `Chip` alone can't assume for every consumer.
39
+ // `100%` is the honest equivalent: never wider than the container,
40
+ // whatever kind of container it is.
41
+ maxWidth: "100%",
30
42
  paddingInline: c.space["1"],
31
43
  borderRadius: c.radius.chip,
32
44
  fontSize: c.text.lg,
@@ -42,6 +42,12 @@ function initials(name) {
42
42
  * fixed-size.
43
43
  */
44
44
  export function PersonChip({ name, avatar, muted, className, ...props }) {
45
- return (_jsxs(Chip, { tone: "neutral", truncate: true, className: className, ...props, children: [_jsx(Avatar, { size: "sm", shape: "circle", tone: avatar ? "neutral" : hashTone(name), children: avatar ? _jsx("img", { src: avatar, alt: "" }) : initials(name) }), _jsx("span", { className: "crisp-chip-label", style: muted ? { color: c.fgNeutralMuted } : undefined, children: name })] }));
45
+ return (_jsxs(Chip, { tone: "neutral", truncate: true, className: className, ...props, children: [_jsx(Avatar, { size: "sm", shape: "circle", tone: avatar ? "neutral" : hashTone(name),
46
+ // Explicit rather than relying on the flex default (a fixed-size,
47
+ // non-wrapping element's own min-content floor already resists
48
+ // shrinking in every browser tested) — #668 asked "does the avatar
49
+ // stay" as its own question, so it gets its own undeniable answer
50
+ // rather than an implicit one.
51
+ style: { flexShrink: 0 }, children: avatar ? _jsx("img", { src: avatar, alt: "" }) : initials(name) }), _jsx("span", { className: "crisp-chip-label", style: muted ? { color: c.fgNeutralMuted } : undefined, children: name })] }));
46
52
  }
47
53
  PersonChip.displayName = "PersonChip";
@@ -1,5 +1,5 @@
1
1
  import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { ArrowDownWideNarrow, ArrowUpDown, ArrowUpNarrowWide, Check, CircleHelp, GripVertical, ListFilter, MoreVertical, Plus, X, } from "lucide-react";
2
+ import { ArrowDownWideNarrow, ArrowUpDown, ArrowUpNarrowWide, Check, CircleHelp, GripVertical, ListFilter, MoreVertical, Plus, SlidersHorizontal, X, } from "lucide-react";
3
3
  import * as React from "react";
4
4
  import { isInsideLaterFloatingLayer, useCrispMessage, useCrispMessages, useFocusAfterRemoval, } from "../../core/index.js";
5
5
  import { MenuCheckboxItem, useMenuKeyboard } from "../menu/index.js";
@@ -138,7 +138,7 @@ export function applySorts(rows, sorts, get = (row, key) => row[key]) {
138
138
  * this (Sort: Due date/Assignee/Creation date; Filter: Assignee/Record/
139
139
  * Created by), which one shared list can't reproduce.
140
140
  */
141
- export function TableToolbar({ columns, sortColumns: sortColumnsProp, filterColumns: filterColumnsProp, sorts: sortsProp, defaultSorts, onSortsChange, filters: filtersProp, defaultFilters, onFiltersChange, count, children, helpHref, className, sortLabel: sortLabelProp, filterLabel: filterLabelProp, reorderSortLabel: reorderSortLabelProp, noSortsLabel: noSortsLabelProp, addSortLabel: addSortLabelProp, learnSortingLabel: learnSortingLabelProp, selectConditionLabel: selectConditionLabelProp, sortFieldLabel: sortFieldLabelProp, sortDirectionLabel: sortDirectionLabelProp, removeSortLabel: removeSortLabelProp, filterFieldLabel: filterFieldLabelProp, filterFieldPlaceholder: filterFieldPlaceholderProp, filterValuePlaceholder: filterValuePlaceholderProp, filterOptionsLabel: filterOptionsLabelProp, deleteFilterLabel: deleteFilterLabelProp, ascendingLabel: ascendingLabelProp, descendingLabel: descendingLabelProp, sortTriggerLabel, filterTriggerLabel, inline, ...rest }) {
141
+ export function TableToolbar({ columns, sortColumns: sortColumnsProp, filterColumns: filterColumnsProp, sorts: sortsProp, defaultSorts, onSortsChange, filters: filtersProp, defaultFilters, onFiltersChange, count, children, helpHref, className, sortLabel: sortLabelProp, filterLabel: filterLabelProp, reorderSortLabel: reorderSortLabelProp, noSortsLabel: noSortsLabelProp, addSortLabel: addSortLabelProp, learnSortingLabel: learnSortingLabelProp, selectConditionLabel: selectConditionLabelProp, sortFieldLabel: sortFieldLabelProp, sortDirectionLabel: sortDirectionLabelProp, removeSortLabel: removeSortLabelProp, filterFieldLabel: filterFieldLabelProp, filterFieldPlaceholder: filterFieldPlaceholderProp, filterValuePlaceholder: filterValuePlaceholderProp, filterOptionsLabel: filterOptionsLabelProp, deleteFilterLabel: deleteFilterLabelProp, ascendingLabel: ascendingLabelProp, descendingLabel: descendingLabelProp, sortTriggerLabel, filterTriggerLabel, inline, viewColumns: viewColumnsProp, shownKeys: shownKeysProp, defaultShownKeys, onShownKeysChange, viewSettingsLabel: viewSettingsLabelProp, reorderAttributeLabel: reorderAttributeLabelProp, hideAttributeLabel, addAttributeToViewLabel: addAttributeToViewLabelProp, searchAttributesPlaceholder: searchAttributesPlaceholderProp, ...rest }) {
142
142
  // crisp/#471: an explicit prop always wins; missing, it reads the nearest
143
143
  // CrispMessagesProvider's translation of the English default below, or
144
144
  // that default itself with no provider — unchanged from before this
@@ -161,6 +161,10 @@ export function TableToolbar({ columns, sortColumns: sortColumnsProp, filterColu
161
161
  const deleteFilterLabel = useCrispMessage(deleteFilterLabelProp, "Delete filter");
162
162
  const ascendingLabel = useCrispMessage(ascendingLabelProp, "Ascending");
163
163
  const descendingLabel = useCrispMessage(descendingLabelProp, "Descending");
164
+ const viewSettingsLabel = useCrispMessage(viewSettingsLabelProp, "View settings");
165
+ const reorderAttributeLabel = useCrispMessage(reorderAttributeLabelProp, "Reorder attribute");
166
+ const addAttributeToViewLabel = useCrispMessage(addAttributeToViewLabelProp, "Add attribute to view");
167
+ const searchAttributesPlaceholder = useCrispMessage(searchAttributesPlaceholderProp, "Search attributes…");
164
168
  // OP_LABELS' own words ("contains", "is", "is any of", …) have no prop of
165
169
  // their own — a whole lookup table, not one string — so they translate
166
170
  // straight from the provider's dictionary at the two spots they render
@@ -172,10 +176,17 @@ export function TableToolbar({ columns, sortColumns: sortColumnsProp, filterColu
172
176
  // gets `columns` on the other, unchanged from before either prop existed.
173
177
  const sortColumns = sortColumnsProp ?? columns ?? [];
174
178
  const filterColumns = filterColumnsProp ?? columns ?? [];
179
+ const viewColumns = viewColumnsProp ?? columns ?? [];
175
180
  const [sortsState, setSortsState] = React.useState(defaultSorts ?? []);
176
181
  const [filtersState, setFiltersState] = React.useState(defaultFilters ?? []);
182
+ // #590: "everything shown" unless told otherwise -- lazy initializer so
183
+ // this only reads `viewColumns` once, at mount, the same way `defaultSorts`/
184
+ // `defaultFilters` above seed once and are never re-derived from a prop
185
+ // that may keep changing identity across renders.
186
+ const [shownKeysState, setShownKeysState] = React.useState(() => defaultShownKeys ?? viewColumns.map((c) => c.key));
177
187
  const sorts = sortsProp ?? sortsState;
178
188
  const filters = filtersProp ?? filtersState;
189
+ const shownKeys = shownKeysProp ?? shownKeysState;
179
190
  const putSorts = (next) => {
180
191
  if (sortsProp === undefined)
181
192
  setSortsState(next);
@@ -186,11 +197,28 @@ export function TableToolbar({ columns, sortColumns: sortColumnsProp, filterColu
186
197
  setFiltersState(next);
187
198
  onFiltersChange?.(next);
188
199
  };
200
+ const putShownKeys = (next) => {
201
+ if (shownKeysProp === undefined)
202
+ setShownKeysState(next);
203
+ onShownKeysChange?.(next);
204
+ };
189
205
  const [openMenu, setOpenMenu] = React.useState(null);
190
206
  const [dragSort, setDragSort] = React.useState(null);
191
207
  const [filterQuery, setFilterQuery] = React.useState("");
192
208
  // filters the "isanyof" checkbox list, same lifetime as filterQuery above
193
209
  const [anyOfQuery, setAnyOfQuery] = React.useState("");
210
+ const [dragAttr, setDragAttr] = React.useState(null);
211
+ // #590: the popover has two screens -- the draggable shown-attribute list
212
+ // (default) and, behind "Add attribute to view", a search list of the
213
+ // rest. Measured live, 2026-09-25: clicking "View settings" opens a
214
+ // `role="dialog"` of dnd-kit `aria-roledescription="sortable"` rows --
215
+ // grip, type icon, label -- with no search box, ending in a static
216
+ // "Add attribute to view" row; each row also carries a "View attribute
217
+ // menu" kebab whose own contents weren't explored (undefined scope, not
218
+ // built here -- hiding an attribute is a trailing control instead, see
219
+ // the hide button below).
220
+ const [addingAttribute, setAddingAttribute] = React.useState(false);
221
+ const [addAttrQuery, setAddAttrQuery] = React.useState("");
194
222
  // close any open menu on outside click (anything not inside a menu or its
195
223
  // trigger) or Escape
196
224
  React.useEffect(() => {
@@ -253,6 +281,27 @@ export function TableToolbar({ columns, sortColumns: sortColumnsProp, filterColu
253
281
  putFilters(filters.filter((c) => c.id !== id));
254
282
  setOpenMenu(null);
255
283
  };
284
+ // #590: shown attributes, in `shownKeys`' order -- a key with no matching
285
+ // `viewColumns` entry (stale state after a column is removed upstream)
286
+ // drops out rather than rendering a blank row.
287
+ const shownAttrs = shownKeys
288
+ .map((k) => viewColumns.find((c) => c.key === k))
289
+ .filter((c) => c != null);
290
+ const hiddenAttrs = viewColumns.filter((c) => !shownKeys.includes(c.key));
291
+ const moveAttr = (from, to) => {
292
+ if (from === to)
293
+ return;
294
+ const next = [...shownKeys];
295
+ const [moved] = next.splice(from, 1);
296
+ next.splice(to, 0, moved);
297
+ putShownKeys(next);
298
+ };
299
+ const hideAttr = (key) => putShownKeys(shownKeys.filter((k) => k !== key));
300
+ const showAttr = (key) => {
301
+ putShownKeys([...shownKeys, key]);
302
+ setAddingAttribute(false);
303
+ setAddAttrQuery("");
304
+ };
256
305
  // #594: the filter chip's kebab is a real role="menu" (crisp/#188's shared
257
306
  // model — arrows, Home/End, typeahead, Escape), not a hand-rolled div like
258
307
  // the sort/condition popovers above (those are role="dialog", which makes
@@ -342,7 +391,31 @@ export function TableToolbar({ columns, sortColumns: sortColumnsProp, filterColu
342
391
  });
343
392
  }, children: v }, v));
344
393
  })] }))] }))] }, cond.id));
345
- }), _jsx("span", { className: "crisp-tabletoolbar-spacer" }), count != null && (_jsx("span", { className: "crisp-tabletoolbar-count", children: count })), children] }) }));
394
+ }), _jsx("span", { className: "crisp-tabletoolbar-divider", "aria-hidden": "true" }), _jsxs("span", { className: "crisp-tabletoolbar-toolgroup", children: [_jsxs("button", { type: "button", className: "crisp-tabletoolbar-tool crisp-tabletoolbar-tool--ghost", onClick: () => {
395
+ if (openMenu === "viewsettings") {
396
+ setOpenMenu(null);
397
+ }
398
+ else {
399
+ setAddingAttribute(false);
400
+ setAddAttrQuery("");
401
+ setOpenMenu("viewsettings");
402
+ }
403
+ }, "aria-haspopup": "dialog", "aria-expanded": openMenu === "viewsettings", "data-menu-trigger": true, children: [_jsx(SlidersHorizontal, { size: 14, strokeWidth: 1.75, "aria-hidden": "true" }), viewSettingsLabel] }), openMenu === "viewsettings" &&
404
+ (addingAttribute ? (_jsxs("div", { className: "crisp-tabletoolbar-menu", role: "dialog", "aria-label": addAttributeToViewLabel, children: [_jsx("input", { className: "crisp-tabletoolbar-menu-input", placeholder: searchAttributesPlaceholder, "aria-label": searchAttributesPlaceholder,
405
+ // biome-ignore lint/a11y/noAutofocus: focus the search box on open, same as Filter's field picker
406
+ autoFocus: true, value: addAttrQuery, onChange: (e) => setAddAttrQuery(e.target.value) }), hiddenAttrs
407
+ .filter((col) => col.label
408
+ .toLowerCase()
409
+ .includes(addAttrQuery.trim().toLowerCase()))
410
+ .map((col) => (_jsxs("button", { type: "button", className: "crisp-tabletoolbar-menu-item", onClick: () => showAttr(col.key), children: [col.icon ? (_jsx(col.icon, { size: 15, strokeWidth: 1.75 })) : (_jsx("span", { style: { width: 15 } })), col.label] }, col.key)))] })) : (_jsxs("div", { className: "crisp-tabletoolbar-menu crisp-tabletoolbar-sortpop", role: "dialog", "aria-label": viewSettingsLabel, children: [shownAttrs.map((col, i) => (_jsxs("div", { className: "crisp-tabletoolbar-sort-row", onDragOver: (e) => {
411
+ if (dragAttr !== null)
412
+ e.preventDefault();
413
+ }, onDrop: () => {
414
+ if (dragAttr !== null)
415
+ moveAttr(dragAttr, i);
416
+ setDragAttr(null);
417
+ }, children: [_jsx("button", { type: "button", className: "crisp-tabletoolbar-sort-grip", "aria-label": reorderAttributeLabel, draggable: true, onDragStart: () => setDragAttr(i), onDragEnd: () => setDragAttr(null), children: _jsx(GripVertical, { size: 15, strokeWidth: 1.75 }) }), _jsxs("span", { className: "crisp-tabletoolbar-sort-optlabel crisp-tabletoolbar-sort-field", children: [col.icon ? (_jsx(col.icon, { size: 15, strokeWidth: 1.75 })) : null, col.label] }), _jsx("button", { type: "button", className: "crisp-tabletoolbar-sort-remove", onClick: () => hideAttr(col.key), "aria-label": hideAttributeLabel?.(col.label) ??
418
+ `${crispMessages.Hide ?? "Hide"} ${col.label} ${crispMessages["from view"] ?? "from view"}`, children: _jsx(X, { size: 15, strokeWidth: 1.75 }) })] }, col.key))), _jsx("div", { className: "crisp-tabletoolbar-sort-addrow", children: _jsxs("button", { type: "button", className: "crisp-tabletoolbar-sort-add", onClick: () => setAddingAttribute(true), children: [_jsx(Plus, { size: 15, strokeWidth: 1.75 }), addAttributeToViewLabel] }) })] })))] }), _jsx("span", { className: "crisp-tabletoolbar-spacer" }), count != null && (_jsx("span", { className: "crisp-tabletoolbar-count", children: count })), children] }) }));
346
419
  }
347
420
  /**
348
421
  * The Sort/Filter chip's own chrome (`.crisp-tabletoolbar-tool`), standalone
@@ -8,13 +8,6 @@ import { Menu, MenuCheckboxItem, MenuItem } from "../../components/menu/index.js
8
8
  import { Popover } from "../../components/popover/index.js";
9
9
  import { matchMentionTrigger, useCrispMessage, } from "../../core/index.js";
10
10
  import { commentsRecipe } from "./comments.recipe.js";
11
- /** A single literal JSX occurrence for the reply glyph, used at two sizes --
12
- * so the RTL-mirroring sweep (which counts the flip-marker attribute against
13
- * source-text occurrences, not runtime renders) sees one mark for one
14
- * direction-bearing icon, not two. */
15
- function ReplyGlyph({ size }) {
16
- return (_jsx(CornerUpLeft, { "data-rtl-flip": "", size: size, strokeWidth: 2, "aria-hidden": "true" }));
17
- }
18
11
  const DEFAULT_EMOJI = [
19
12
  "👍",
20
13
  "👎",
@@ -121,7 +114,7 @@ export function serializeComposerField(field) {
121
114
  * disabled until the field holds text. Uncontrolled — reads its own DOM on
122
115
  * submit, the same shape `RichTextEditor` uses for its body.
123
116
  */
124
- const Composer = React.forwardRef(function Composer({ placeholder, postLabel, mentionTrigger, renderMentionPicker, replyToolLabel, mentionToolLabel, onSubmit, }, ref) {
117
+ const Composer = React.forwardRef(function Composer({ placeholder, postLabel, mentionTrigger, renderMentionPicker, mentionToolLabel, onSubmit, }, ref) {
125
118
  const fieldRef = React.useRef(null);
126
119
  const [empty, setEmpty] = React.useState(true);
127
120
  const [mention, setMention] = React.useState(null);
@@ -257,15 +250,15 @@ const Composer = React.forwardRef(function Composer({ placeholder, postLabel, me
257
250
  e.preventDefault();
258
251
  submit();
259
252
  }
260
- } }), _jsxs("div", { className: "crisp-comments-composer-foot", children: [_jsxs("div", { className: "crisp-comments-composer-tools", children: [_jsx("button", { type: "button", className: "crisp-comments-tool", "aria-label": replyToolLabel, children: _jsx(ReplyGlyph, { size: 14 }) }), _jsx("button", { type: "button", className: "crisp-comments-tool", "aria-label": mentionToolLabel, onClick: () => {
261
- const field = fieldRef.current;
262
- if (!field)
263
- return;
264
- field.focus();
265
- document.execCommand("insertText", false, mentionTrigger);
266
- syncEmpty();
267
- updateMention();
268
- }, children: "@" })] }), _jsx("button", { type: "button", className: "crisp-comments-post", disabled: empty, onClick: submit, children: postLabel })] }), mention &&
253
+ } }), _jsxs("div", { className: "crisp-comments-composer-foot", children: [_jsx("div", { className: "crisp-comments-composer-tools", children: _jsx("button", { type: "button", className: "crisp-comments-tool", "aria-label": mentionToolLabel, onClick: () => {
254
+ const field = fieldRef.current;
255
+ if (!field)
256
+ return;
257
+ field.focus();
258
+ document.execCommand("insertText", false, mentionTrigger);
259
+ syncEmpty();
260
+ updateMention();
261
+ }, children: "@" }) }), _jsx("button", { type: "button", className: "crisp-comments-post", disabled: empty, onClick: submit, children: postLabel })] }), mention &&
269
262
  renderMentionPicker?.({
270
263
  query: mention.query,
271
264
  anchor: mention.anchor,
@@ -274,7 +267,7 @@ const Composer = React.forwardRef(function Composer({ placeholder, postLabel, me
274
267
  })] }));
275
268
  });
276
269
  function CommentRow({ comment, replyLabel, reactLabel, resolveLabel, moreLabel, copyLinkLabel, deleteLabel, addReactionLabel, reactionSearchLabel, emojiOptions, resolvable, resolved, resolvedLabel, onReply, onReact, onResolve, onCopyLink, onDeleteRequest, }) {
277
- return (_jsxs("div", { className: "crisp-comments-row", children: [_jsx(AuthorAvatar, { author: comment.author }), _jsxs("div", { className: "crisp-comments-row-main", children: [resolvedLabel != null && (_jsxs("div", { className: "crisp-comments-resolved-label", children: [_jsx(Check, { size: 12, strokeWidth: 2.5, "aria-hidden": "true" }), resolvedLabel] })), _jsxs("div", { className: "crisp-comments-row-head", children: [_jsx("span", { className: "crisp-comments-author", children: comment.author.name }), _jsx("span", { className: "crisp-comments-time", children: comment.time })] }), _jsx("div", { className: "crisp-comments-body-text", children: comment.body }), comment.reactions && comment.reactions.length > 0 && (_jsxs("div", { className: "crisp-comments-reactions", children: [comment.reactions.map((r) => (_jsxs("button", { type: "button", className: "crisp-comments-reaction", "data-reacted": r.reactedByMe ? "" : undefined, onClick: () => onReact(r.emoji), children: [_jsx("span", { "aria-hidden": "true", children: r.emoji }), r.count] }, r.emoji))), _jsx(Popover, { align: "start", trigger: _jsx("button", { type: "button", className: "crisp-comments-reaction-add", "aria-label": addReactionLabel, children: _jsx(SmilePlus, { size: 14, strokeWidth: 1.75, "aria-hidden": "true" }) }), children: _jsx(ReactionPicker, { options: emojiOptions, searchLabel: reactionSearchLabel, onPick: onReact }) })] }))] }), _jsxs("div", { className: "crisp-comments-row-actions", children: [onReply && (_jsx("button", { type: "button", className: "crisp-comments-action", "aria-label": replyLabel, onClick: onReply, children: _jsx(ReplyGlyph, { size: 16 }) })), _jsx(Popover, { align: "end", trigger: _jsx("button", { type: "button", className: "crisp-comments-action", "aria-label": reactLabel, children: _jsx(SmilePlus, { size: 16, strokeWidth: 1.75, "aria-hidden": "true" }) }), children: _jsx(ReactionPicker, { options: emojiOptions, searchLabel: reactionSearchLabel, onPick: onReact }) }), resolvable && (_jsx("button", { type: "button", className: "crisp-comments-action", "aria-label": resolveLabel, "aria-pressed": !!resolved, "data-active": resolved ? "" : undefined, onClick: onResolve, children: _jsx(Check, { size: 16, strokeWidth: 1.75, "aria-hidden": "true" }) })), _jsxs(Menu, { align: "end", trigger: _jsx("button", { type: "button", className: "crisp-comments-action", "aria-label": moreLabel, children: _jsx(MoreHorizontal, { size: 16, strokeWidth: 1.75, "aria-hidden": "true" }) }), children: [_jsx(MenuItem, { onSelect: onCopyLink, children: copyLinkLabel }), _jsx(MenuItem, { danger: true, onSelect: onDeleteRequest, children: deleteLabel })] })] })] }));
270
+ return (_jsxs("div", { className: "crisp-comments-row", children: [_jsx(AuthorAvatar, { author: comment.author }), _jsxs("div", { className: "crisp-comments-row-main", children: [resolvedLabel != null && (_jsxs("div", { className: "crisp-comments-resolved-label", children: [_jsx(Check, { size: 12, strokeWidth: 2.5, "aria-hidden": "true" }), resolvedLabel] })), _jsxs("div", { className: "crisp-comments-row-head", children: [_jsx("span", { className: "crisp-comments-author", children: comment.author.name }), _jsx("span", { className: "crisp-comments-time", children: comment.time })] }), _jsx("div", { className: "crisp-comments-body-text", children: comment.body }), comment.reactions && comment.reactions.length > 0 && (_jsxs("div", { className: "crisp-comments-reactions", children: [comment.reactions.map((r) => (_jsxs("button", { type: "button", className: "crisp-comments-reaction", "data-reacted": r.reactedByMe ? "" : undefined, onClick: () => onReact(r.emoji), children: [_jsx("span", { "aria-hidden": "true", children: r.emoji }), r.count] }, r.emoji))), _jsx(Popover, { align: "start", trigger: _jsx("button", { type: "button", className: "crisp-comments-reaction-add", "aria-label": addReactionLabel, children: _jsx(SmilePlus, { size: 14, strokeWidth: 1.75, "aria-hidden": "true" }) }), children: _jsx(ReactionPicker, { options: emojiOptions, searchLabel: reactionSearchLabel, onPick: onReact }) })] }))] }), _jsxs("div", { className: "crisp-comments-row-actions", children: [onReply && (_jsx("button", { type: "button", className: "crisp-comments-action", "aria-label": replyLabel, onClick: onReply, children: _jsx(CornerUpLeft, { "data-rtl-flip": "", size: 16, strokeWidth: 2, "aria-hidden": "true" }) })), _jsx(Popover, { align: "end", trigger: _jsx("button", { type: "button", className: "crisp-comments-action", "aria-label": reactLabel, children: _jsx(SmilePlus, { size: 16, strokeWidth: 1.75, "aria-hidden": "true" }) }), children: _jsx(ReactionPicker, { options: emojiOptions, searchLabel: reactionSearchLabel, onPick: onReact }) }), resolvable && (_jsx("button", { type: "button", className: "crisp-comments-action", "aria-label": resolveLabel, "aria-pressed": !!resolved, "data-active": resolved ? "" : undefined, onClick: onResolve, children: _jsx(Check, { size: 16, strokeWidth: 1.75, "aria-hidden": "true" }) })), _jsxs(Menu, { align: "end", trigger: _jsx("button", { type: "button", className: "crisp-comments-action", "aria-label": moreLabel, children: _jsx(MoreHorizontal, { size: 16, strokeWidth: 1.75, "aria-hidden": "true" }) }), children: [_jsx(MenuItem, { onSelect: onCopyLink, children: copyLinkLabel }), _jsx(MenuItem, { danger: true, onSelect: onDeleteRequest, children: deleteLabel })] })] })] }));
278
271
  }
279
272
  /**
280
273
  * Per-record comment threads (crisp#625) — measured live on app.attio.com's
@@ -285,7 +278,7 @@ function CommentRow({ comment, replyLabel, reactLabel, resolveLabel, moreLabel,
285
278
  * separate row affordance (P-051) — deliberately NOT part of this
286
279
  * component, since it lives in a table cell this component doesn't own.
287
280
  */
288
- export function CommentsPanel({ threads, currentUserId, subject, subjectIcon, showResolved = false, onShowResolvedChange, onlyMine = false, onOnlyMineChange, activeThreadId: activeThreadIdProp, onActiveThreadChange, onPost, onReact, onResolve, onDelete, onCopyLink, emojiOptions = DEFAULT_EMOJI, mentionTrigger = "@", renderMentionPicker, onClose, closeLabel: closeLabelProp, backLabel: backLabelProp, title: titleProp, threadTitle: threadTitleProp, breadcrumbPrefix: breadcrumbPrefixProp, composerPlaceholder: composerPlaceholderProp, replyPlaceholder: replyPlaceholderProp, postLabel: postLabelProp, replyPostLabel: replyPostLabelProp, emptyLabel, filterLabel: filterLabelProp, showResolvedLabel: showResolvedLabelProp, onlyMineLabel: onlyMineLabelProp, replyActionLabel: replyActionLabelProp, reactActionLabel: reactActionLabelProp, resolveActionLabel: resolveActionLabelProp, resolvedByYouLabel: resolvedByYouLabelProp, resolvedByLabel, moreActionsLabel: moreActionsLabelProp, copyLinkLabel: copyLinkLabelProp, deleteLabel: deleteLabelProp, addReactionLabel: addReactionLabelProp, reactionSearchLabel: reactionSearchLabelProp, deleteConfirmTitle: deleteConfirmTitleProp, deleteConfirmBody: deleteConfirmBodyProp, deleteConfirmActionLabel: deleteConfirmActionLabelProp, cancelLabel: cancelLabelProp, composerReplyToolLabel: composerReplyToolLabelProp, composerMentionToolLabel: composerMentionToolLabelProp, className, ...rest }) {
281
+ export function CommentsPanel({ threads, currentUserId, subject, subjectIcon, showResolved = false, onShowResolvedChange, onlyMine = false, onOnlyMineChange, activeThreadId: activeThreadIdProp, onActiveThreadChange, onPost, onReact, onResolve, onDelete, onCopyLink, emojiOptions = DEFAULT_EMOJI, mentionTrigger = "@", renderMentionPicker, onClose, closeLabel: closeLabelProp, backLabel: backLabelProp, title: titleProp, threadTitle: threadTitleProp, breadcrumbPrefix: breadcrumbPrefixProp, composerPlaceholder: composerPlaceholderProp, replyPlaceholder: replyPlaceholderProp, postLabel: postLabelProp, replyPostLabel: replyPostLabelProp, emptyLabel, filterLabel: filterLabelProp, showResolvedLabel: showResolvedLabelProp, onlyMineLabel: onlyMineLabelProp, replyActionLabel: replyActionLabelProp, reactActionLabel: reactActionLabelProp, resolveActionLabel: resolveActionLabelProp, resolvedByYouLabel: resolvedByYouLabelProp, resolvedByLabel, moreActionsLabel: moreActionsLabelProp, copyLinkLabel: copyLinkLabelProp, deleteLabel: deleteLabelProp, addReactionLabel: addReactionLabelProp, reactionSearchLabel: reactionSearchLabelProp, deleteConfirmTitle: deleteConfirmTitleProp, deleteConfirmBody: deleteConfirmBodyProp, deleteConfirmActionLabel: deleteConfirmActionLabelProp, cancelLabel: cancelLabelProp, composerMentionToolLabel: composerMentionToolLabelProp, className, ...rest }) {
289
282
  const closeLabel = useCrispMessage(closeLabelProp, "Close");
290
283
  const backLabel = useCrispMessage(backLabelProp, "Back");
291
284
  const title = useCrispMessage(titleProp, "All comments");
@@ -311,7 +304,6 @@ export function CommentsPanel({ threads, currentUserId, subject, subjectIcon, sh
311
304
  const deleteConfirmBody = useCrispMessage(deleteConfirmBodyProp, "You're about to delete this comment. Once deleted it cannot be restored.");
312
305
  const deleteConfirmActionLabel = useCrispMessage(deleteConfirmActionLabelProp, "Delete");
313
306
  const cancelLabel = useCrispMessage(cancelLabelProp, "Cancel");
314
- const composerReplyToolLabel = useCrispMessage(composerReplyToolLabelProp, "Reply");
315
307
  const composerMentionToolLabel = useCrispMessage(composerMentionToolLabelProp, "Mention someone");
316
308
  const [internalActiveId, setInternalActiveId] = React.useState(null);
317
309
  const activeThreadId = activeThreadIdProp ?? internalActiveId;
@@ -356,7 +348,7 @@ export function CommentsPanel({ threads, currentUserId, subject, subjectIcon, sh
356
348
  ? resolvedByYouLabel
357
349
  : (resolvedByLabel?.(t.resolvedBy.name) ??
358
350
  `Resolved by ${t.resolvedBy.name}`)
359
- : 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)))) }), _jsx(Composer, { placeholder: activeThread ? replyPlaceholder : composerPlaceholder, postLabel: activeThread ? replyPostLabel : postLabel, mentionTrigger: mentionTrigger, renderMentionPicker: renderMentionPicker, replyToolLabel: composerReplyToolLabel, mentionToolLabel: composerMentionToolLabel, onSubmit: (text) => onPost(text, activeThread ? activeThread.id : null) }, activeThread ? activeThread.id : "root"), _jsxs(Dialog, { open: deleteTarget != null, onOpenChange: (open) => {
351
+ : 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)))) }), _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"), _jsxs(Dialog, { open: deleteTarget != null, onOpenChange: (open) => {
360
352
  if (!open)
361
353
  setDeleteTarget(null);
362
354
  }, title: deleteConfirmTitle, children: [_jsx(DialogBody, { children: deleteConfirmBody }), _jsxs(DialogFooter, { children: [_jsx(Button, { type: "button", intent: "neutral", size: "md", onClick: () => setDeleteTarget(null), children: cancelLabel }), _jsx(Button, { type: "button", intent: "danger", size: "md", onClick: () => {
@@ -95,29 +95,44 @@ const config = defineRecipe({
95
95
  RichTextEditor's own \`.crisp-rte-mention\` (crisp#428), reused here
96
96
  rather than re-derived since it's the same atomic-chip affordance. */
97
97
  & .crisp-comments-mention { display: inline; padding: 0 2px; border-radius: 4px; background: color-mix(in srgb, ${c.bgBrandSolid} 12%, transparent); color: ${c.bgBrandSolid}; font-weight: ${c.weight.medium}; user-select: none; }
98
-
98
+ ${rtlFlipCss}`,
99
+ variants: {},
100
+ defaultVariants: {},
101
+ compoundVariants: [],
102
+ // Two pieces below intentionally live OUTSIDE the nested \`base\` above,
103
+ // the same reason Menu's own \`.crisp-menu-content\` does (menu.recipe.ts's
104
+ // own comment, crisp#357): nesting compiles to a descendant combinator
105
+ // scoped to \`.crisp-comments\`, which needs real DOM containment. Both of
106
+ // these escape it -- \`ReactionPicker\` because \`Popover\` portals its
107
+ // content to \`document.body\`, and \`CommentCountBadge\` because it's built
108
+ // to be dropped into a table cell that has no \`.crisp-comments\` ancestor
109
+ // at all (its own module doc comment). Nested, this shipped invisible:
110
+ // \`CommentCountBadge\`'s dot rendered at 0×0 and the reaction picker fell
111
+ // back to \`Popover\`'s own generic (unstyled-by-this-recipe) surface --
112
+ // caught taking this component's own screenshots, not by any gate, since
113
+ // \`spec-check\` reads recipe source text and both classes were right there
114
+ // in it.
115
+ globalCss: `
99
116
  /* Reaction picker -- see \`ReactionPicker\`'s own doc comment: the exact
100
117
  chrome wasn't independently pixel-measured this pass (screenshot
101
118
  capture broke mid-session), so this reuses Menu's already-measured
102
119
  floating-popover shell rather than a new value. */
103
- & .crisp-comments-reactionpicker { width: 240px; padding: ${c.space["2"]}; border-radius: ${c.radius.xl}; background: ${c.bgSurface}; box-shadow: ${e.floating}; }
104
- & .crisp-comments-reactionpicker-search { box-sizing: border-box; width: 100%; height: 28px; margin-bottom: ${c.space["2"]}; padding: 0 ${c.space["2"]}; border: none; border-radius: ${c.radius.md}; background: ${c.bgRaised}; font-family: inherit; font-size: ${c.text.md}; color: ${c.fgPrimary}; }
105
- & .crisp-comments-reactionpicker-search::placeholder { color: color-mix(in srgb, ${c.fgPrimary} 40%, transparent); }
106
- & .crisp-comments-reactionpicker-grid { display: grid; grid-template-columns: repeat(8, 1fr); gap: ${c.space["0.5"]}; max-height: 160px; overflow-y: auto; }
107
- & .crisp-comments-reactionpicker-item { display: inline-flex; align-items: center; justify-content: center; width: 26px; height: 26px; border: none; border-radius: ${c.radius.sm}; background: none; font-family: inherit; font-size: 16px; line-height: 1; cursor: pointer; }
108
- & .crisp-comments-reactionpicker-item:hover { background: ${c.bgRaised}; }
120
+ .crisp-comments-reactionpicker { font-family: ${c.font.sans}; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; box-sizing: border-box; width: 240px; padding: ${c.space["2"]}; border-radius: ${c.radius.xl}; background: ${c.bgSurface}; box-shadow: ${e.floating}; }
121
+ .crisp-comments-reactionpicker-search { box-sizing: border-box; width: 100%; height: 28px; margin-bottom: ${c.space["2"]}; padding: 0 ${c.space["2"]}; border: none; border-radius: ${c.radius.md}; background: ${c.bgRaised}; font-family: inherit; font-size: ${c.text.md}; color: ${c.fgPrimary}; }
122
+ .crisp-comments-reactionpicker-search::placeholder { color: color-mix(in srgb, ${c.fgPrimary} 40%, transparent); }
123
+ .crisp-comments-reactionpicker-grid { display: grid; grid-template-columns: repeat(8, 1fr); gap: ${c.space["0.5"]}; max-height: 160px; overflow-y: auto; }
124
+ .crisp-comments-reactionpicker-item { display: inline-flex; align-items: center; justify-content: center; width: 26px; height: 26px; border: none; border-radius: ${c.radius.sm}; background: none; font-family: inherit; font-size: 16px; line-height: 1; cursor: pointer; }
125
+ .crisp-comments-reactionpicker-item:hover { background: ${c.bgRaised}; }
109
126
 
110
127
  /* Row badge (CommentCountBadge) -- lives in its own class family so the
111
128
  grid lanes can drop it into a cell without pulling in the panel's
112
129
  CSS. Persistent state: a 6px grey dot. Hover state: a 12px comment
113
130
  icon + 12px count text, both at 55% ink, replacing the dot. */
114
- & .crisp-comments-badge { display: inline-flex; align-items: center; gap: ${c.space["1"]}; border: none; background: none; padding: 0; margin: 0; font-family: inherit; cursor: pointer; color: color-mix(in srgb, ${c.fgPrimary} 55%, transparent); }
115
- & .crisp-comments-badge-dot { width: 6px; height: 6px; border-radius: ${c.radius.full}; background: color-mix(in srgb, ${c.fgPrimary} 38%, transparent); }
116
- & .crisp-comments-badge-count { font-size: 12px; line-height: 16px; }
117
- ${rtlFlipCss}`,
118
- variants: {},
119
- defaultVariants: {},
120
- compoundVariants: [],
131
+ .crisp-comments-badge { box-sizing: border-box; font-family: ${c.font.sans}; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; display: inline-flex; align-items: center; gap: ${c.space["1"]}; border: none; background: none; padding: 0; margin: 0; cursor: pointer; color: color-mix(in srgb, ${c.fgPrimary} 55%, transparent); }
132
+ .crisp-comments-badge-dot { box-sizing: border-box; width: 6px; height: 6px; border-radius: ${c.radius.full}; background: color-mix(in srgb, ${c.fgPrimary} 38%, transparent); }
133
+ .crisp-comments-badge-count { font-size: 12px; line-height: 16px; }
134
+ [dir="rtl"] .crisp-comments-badge [data-rtl-flip] { transform: scaleX(-1); }
135
+ `,
121
136
  });
122
137
  export const commentsRecipe = resolveRecipe(config);
123
138
  export default config;
package/dist/styles.css CHANGED
@@ -447,7 +447,7 @@
447
447
  }
448
448
 
449
449
  .crisp-chip {
450
- display: inline-flex; align-items: center; gap: 3px; height: var(--crisp-size-chip); min-width: 0; padding-inline: var(--crisp-space-1); border-radius: var(--crisp-radius-chip); font-size: var(--crisp-text-lg); line-height: 20px; font-weight: var(--crisp-weight-medium); white-space: nowrap;
450
+ display: inline-flex; align-items: center; gap: 3px; height: var(--crisp-size-chip); min-width: 0; max-width: 100%; padding-inline: var(--crisp-space-1); border-radius: var(--crisp-radius-chip); font-size: var(--crisp-text-lg); line-height: 20px; font-weight: var(--crisp-weight-medium); white-space: nowrap;
451
451
  }
452
452
  .crisp-chip--truncate_true {
453
453
  & .crisp-chip-label { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; display: block; }
@@ -2090,6 +2090,27 @@
2090
2090
  }
2091
2091
  }
2092
2092
 
2093
+
2094
+ /* Reaction picker -- see `ReactionPicker`'s own doc comment: the exact
2095
+ chrome wasn't independently pixel-measured this pass (screenshot
2096
+ capture broke mid-session), so this reuses Menu's already-measured
2097
+ floating-popover shell rather than a new value. */
2098
+ .crisp-comments-reactionpicker { font-family: var(--crisp-font-sans); -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; box-sizing: border-box; width: 240px; padding: var(--crisp-space-2); border-radius: var(--crisp-radius-xl); background: var(--crisp-bg-surface); box-shadow: inset 0 0 0 1px var(--crisp-ring-popover), 0 0 0 1px var(--crisp-shadow-popover1), 0 4px 8px -4px var(--crisp-shadow-popover2), 0 4px 12px -2px var(--crisp-shadow-popover3); }
2099
+ .crisp-comments-reactionpicker-search { box-sizing: border-box; width: 100%; height: 28px; margin-bottom: var(--crisp-space-2); padding: 0 var(--crisp-space-2); border: none; border-radius: var(--crisp-radius-md); background: var(--crisp-bg-raised); font-family: inherit; font-size: var(--crisp-text-md); color: var(--crisp-fg-primary); }
2100
+ .crisp-comments-reactionpicker-search::placeholder { color: color-mix(in srgb, var(--crisp-fg-primary) 40%, transparent); }
2101
+ .crisp-comments-reactionpicker-grid { display: grid; grid-template-columns: repeat(8, 1fr); gap: var(--crisp-space-0-5); max-height: 160px; overflow-y: auto; }
2102
+ .crisp-comments-reactionpicker-item { display: inline-flex; align-items: center; justify-content: center; width: 26px; height: 26px; border: none; border-radius: var(--crisp-radius-sm); background: none; font-family: inherit; font-size: 16px; line-height: 1; cursor: pointer; }
2103
+ .crisp-comments-reactionpicker-item:hover { background: var(--crisp-bg-raised); }
2104
+
2105
+ /* Row badge (CommentCountBadge) -- lives in its own class family so the
2106
+ grid lanes can drop it into a cell without pulling in the panel's
2107
+ CSS. Persistent state: a 6px grey dot. Hover state: a 12px comment
2108
+ icon + 12px count text, both at 55% ink, replacing the dot. */
2109
+ .crisp-comments-badge { box-sizing: border-box; font-family: var(--crisp-font-sans); -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; display: inline-flex; align-items: center; gap: var(--crisp-space-1); border: none; background: none; padding: 0; margin: 0; cursor: pointer; color: color-mix(in srgb, var(--crisp-fg-primary) 55%, transparent); }
2110
+ .crisp-comments-badge-dot { box-sizing: border-box; width: 6px; height: 6px; border-radius: var(--crisp-radius-full); background: color-mix(in srgb, var(--crisp-fg-primary) 38%, transparent); }
2111
+ .crisp-comments-badge-count { font-size: 12px; line-height: 16px; }
2112
+ [dir="rtl"] .crisp-comments-badge [data-rtl-flip] { transform: scaleX(-1); }
2113
+
2093
2114
  .crisp-comments {
2094
2115
  display: flex; flex-direction: column; box-sizing: border-box; width: 320px; height: 100%; background: var(--crisp-bg-surface); border-inline-start: 1px solid var(--crisp-border-subtle); font-family: var(--crisp-font-sans); -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale;
2095
2116
  & * { box-sizing: border-box; }
@@ -2145,21 +2166,6 @@
2145
2166
  /* An inserted "@mention" chip in the composer -- same shape as
2146
2167
  RichTextEditor's own `.crisp-rte-mention` (crisp#428), reused here rather than re-derived since it's the same atomic-chip affordance. */
2147
2168
  & .crisp-comments-mention { display: inline; padding: 0 2px; border-radius: 4px; background: color-mix(in srgb, var(--crisp-bg-brand-solid) 12%, transparent); color: var(--crisp-bg-brand-solid); font-weight: var(--crisp-weight-medium); user-select: none; }
2148
-
2149
- /* Reaction picker -- see `ReactionPicker`'s own doc comment: the exact chrome wasn't independently pixel-measured this pass (screenshot
2150
- capture broke mid-session), so this reuses Menu's already-measured floating-popover shell rather than a new value. */
2151
- & .crisp-comments-reactionpicker { width: 240px; padding: var(--crisp-space-2); border-radius: var(--crisp-radius-xl); background: var(--crisp-bg-surface); box-shadow: inset 0 0 0 1px var(--crisp-ring-popover), 0 0 0 1px var(--crisp-shadow-popover1), 0 4px 8px -4px var(--crisp-shadow-popover2), 0 4px 12px -2px var(--crisp-shadow-popover3); }
2152
- & .crisp-comments-reactionpicker-search { box-sizing: border-box; width: 100%; height: 28px; margin-bottom: var(--crisp-space-2); padding: 0 var(--crisp-space-2); border: none; border-radius: var(--crisp-radius-md); background: var(--crisp-bg-raised); font-family: inherit; font-size: var(--crisp-text-md); color: var(--crisp-fg-primary); }
2153
- & .crisp-comments-reactionpicker-search::placeholder { color: color-mix(in srgb, var(--crisp-fg-primary) 40%, transparent); }
2154
- & .crisp-comments-reactionpicker-grid { display: grid; grid-template-columns: repeat(8, 1fr); gap: var(--crisp-space-0-5); max-height: 160px; overflow-y: auto; }
2155
- & .crisp-comments-reactionpicker-item { display: inline-flex; align-items: center; justify-content: center; width: 26px; height: 26px; border: none; border-radius: var(--crisp-radius-sm); background: none; font-family: inherit; font-size: 16px; line-height: 1; cursor: pointer; }
2156
- & .crisp-comments-reactionpicker-item:hover { background: var(--crisp-bg-raised); }
2157
- /* Row badge (CommentCountBadge) -- lives in its own class family so the grid lanes can drop it into a cell without pulling in the panel's
2158
- CSS. Persistent state: a 6px grey dot. Hover state: a 12px comment
2159
- icon + 12px count text, both at 55% ink, replacing the dot. */
2160
- & .crisp-comments-badge { display: inline-flex; align-items: center; gap: var(--crisp-space-1); border: none; background: none; padding: 0; margin: 0; font-family: inherit; cursor: pointer; color: color-mix(in srgb, var(--crisp-fg-primary) 55%, transparent); }
2161
- & .crisp-comments-badge-dot { width: 6px; height: 6px; border-radius: var(--crisp-radius-full); background: color-mix(in srgb, var(--crisp-fg-primary) 38%, transparent); }
2162
- & .crisp-comments-badge-count { font-size: 12px; line-height: 16px; }
2163
2169
  [dir="rtl"] & [data-rtl-flip] { transform: scaleX(-1); }
2164
2170
  }
2165
2171
 
@@ -448,7 +448,7 @@
448
448
  }
449
449
 
450
450
  .crisp-chip {
451
- display: inline-flex; align-items: center; gap: 3px; height: var(--crisp-size-chip); min-width: 0; padding-inline: var(--crisp-space-1); border-radius: var(--crisp-radius-chip); font-size: var(--crisp-text-lg); line-height: 20px; font-weight: var(--crisp-weight-medium); white-space: nowrap;
451
+ display: inline-flex; align-items: center; gap: 3px; height: var(--crisp-size-chip); min-width: 0; max-width: 100%; padding-inline: var(--crisp-space-1); border-radius: var(--crisp-radius-chip); font-size: var(--crisp-text-lg); line-height: 20px; font-weight: var(--crisp-weight-medium); white-space: nowrap;
452
452
  }
453
453
  .crisp-chip--truncate_true {
454
454
  & .crisp-chip-label { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; display: block; }
@@ -2091,6 +2091,27 @@
2091
2091
  }
2092
2092
  }
2093
2093
 
2094
+
2095
+ /* Reaction picker -- see `ReactionPicker`'s own doc comment: the exact
2096
+ chrome wasn't independently pixel-measured this pass (screenshot
2097
+ capture broke mid-session), so this reuses Menu's already-measured
2098
+ floating-popover shell rather than a new value. */
2099
+ .crisp-comments-reactionpicker { font-family: var(--crisp-font-sans); -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; box-sizing: border-box; width: 240px; padding: var(--crisp-space-2); border-radius: var(--crisp-radius-xl); background: var(--crisp-bg-surface); box-shadow: inset 0 0 0 1px var(--crisp-ring-popover), 0 0 0 1px var(--crisp-shadow-popover1), 0 4px 8px -4px var(--crisp-shadow-popover2), 0 4px 12px -2px var(--crisp-shadow-popover3); }
2100
+ .crisp-comments-reactionpicker-search { box-sizing: border-box; width: 100%; height: 28px; margin-bottom: var(--crisp-space-2); padding: 0 var(--crisp-space-2); border: none; border-radius: var(--crisp-radius-md); background: var(--crisp-bg-raised); font-family: inherit; font-size: var(--crisp-text-md); color: var(--crisp-fg-primary); }
2101
+ .crisp-comments-reactionpicker-search::placeholder { color: color-mix(in srgb, var(--crisp-fg-primary) 40%, transparent); }
2102
+ .crisp-comments-reactionpicker-grid { display: grid; grid-template-columns: repeat(8, 1fr); gap: var(--crisp-space-0-5); max-height: 160px; overflow-y: auto; }
2103
+ .crisp-comments-reactionpicker-item { display: inline-flex; align-items: center; justify-content: center; width: 26px; height: 26px; border: none; border-radius: var(--crisp-radius-sm); background: none; font-family: inherit; font-size: 16px; line-height: 1; cursor: pointer; }
2104
+ .crisp-comments-reactionpicker-item:hover { background: var(--crisp-bg-raised); }
2105
+
2106
+ /* Row badge (CommentCountBadge) -- lives in its own class family so the
2107
+ grid lanes can drop it into a cell without pulling in the panel's
2108
+ CSS. Persistent state: a 6px grey dot. Hover state: a 12px comment
2109
+ icon + 12px count text, both at 55% ink, replacing the dot. */
2110
+ .crisp-comments-badge { box-sizing: border-box; font-family: var(--crisp-font-sans); -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; display: inline-flex; align-items: center; gap: var(--crisp-space-1); border: none; background: none; padding: 0; margin: 0; cursor: pointer; color: color-mix(in srgb, var(--crisp-fg-primary) 55%, transparent); }
2111
+ .crisp-comments-badge-dot { box-sizing: border-box; width: 6px; height: 6px; border-radius: var(--crisp-radius-full); background: color-mix(in srgb, var(--crisp-fg-primary) 38%, transparent); }
2112
+ .crisp-comments-badge-count { font-size: 12px; line-height: 16px; }
2113
+ [dir="rtl"] .crisp-comments-badge [data-rtl-flip] { transform: scaleX(-1); }
2114
+
2094
2115
  .crisp-comments {
2095
2116
  display: flex; flex-direction: column; box-sizing: border-box; width: 320px; height: 100%; background: var(--crisp-bg-surface); border-inline-start: 1px solid var(--crisp-border-subtle); font-family: var(--crisp-font-sans); -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale;
2096
2117
  & * { box-sizing: border-box; }
@@ -2146,21 +2167,6 @@
2146
2167
  /* An inserted "@mention" chip in the composer -- same shape as
2147
2168
  RichTextEditor's own `.crisp-rte-mention` (crisp#428), reused here rather than re-derived since it's the same atomic-chip affordance. */
2148
2169
  & .crisp-comments-mention { display: inline; padding: 0 2px; border-radius: 4px; background: color-mix(in srgb, var(--crisp-bg-brand-solid) 12%, transparent); color: var(--crisp-bg-brand-solid); font-weight: var(--crisp-weight-medium); user-select: none; }
2149
-
2150
- /* Reaction picker -- see `ReactionPicker`'s own doc comment: the exact chrome wasn't independently pixel-measured this pass (screenshot
2151
- capture broke mid-session), so this reuses Menu's already-measured floating-popover shell rather than a new value. */
2152
- & .crisp-comments-reactionpicker { width: 240px; padding: var(--crisp-space-2); border-radius: var(--crisp-radius-xl); background: var(--crisp-bg-surface); box-shadow: inset 0 0 0 1px var(--crisp-ring-popover), 0 0 0 1px var(--crisp-shadow-popover1), 0 4px 8px -4px var(--crisp-shadow-popover2), 0 4px 12px -2px var(--crisp-shadow-popover3); }
2153
- & .crisp-comments-reactionpicker-search { box-sizing: border-box; width: 100%; height: 28px; margin-bottom: var(--crisp-space-2); padding: 0 var(--crisp-space-2); border: none; border-radius: var(--crisp-radius-md); background: var(--crisp-bg-raised); font-family: inherit; font-size: var(--crisp-text-md); color: var(--crisp-fg-primary); }
2154
- & .crisp-comments-reactionpicker-search::placeholder { color: color-mix(in srgb, var(--crisp-fg-primary) 40%, transparent); }
2155
- & .crisp-comments-reactionpicker-grid { display: grid; grid-template-columns: repeat(8, 1fr); gap: var(--crisp-space-0-5); max-height: 160px; overflow-y: auto; }
2156
- & .crisp-comments-reactionpicker-item { display: inline-flex; align-items: center; justify-content: center; width: 26px; height: 26px; border: none; border-radius: var(--crisp-radius-sm); background: none; font-family: inherit; font-size: 16px; line-height: 1; cursor: pointer; }
2157
- & .crisp-comments-reactionpicker-item:hover { background: var(--crisp-bg-raised); }
2158
- /* Row badge (CommentCountBadge) -- lives in its own class family so the grid lanes can drop it into a cell without pulling in the panel's
2159
- CSS. Persistent state: a 6px grey dot. Hover state: a 12px comment
2160
- icon + 12px count text, both at 55% ink, replacing the dot. */
2161
- & .crisp-comments-badge { display: inline-flex; align-items: center; gap: var(--crisp-space-1); border: none; background: none; padding: 0; margin: 0; font-family: inherit; cursor: pointer; color: color-mix(in srgb, var(--crisp-fg-primary) 55%, transparent); }
2162
- & .crisp-comments-badge-dot { width: 6px; height: 6px; border-radius: var(--crisp-radius-full); background: color-mix(in srgb, var(--crisp-fg-primary) 38%, transparent); }
2163
- & .crisp-comments-badge-count { font-size: 12px; line-height: 16px; }
2164
2170
  [dir="rtl"] & [data-rtl-flip] { transform: scaleX(-1); }
2165
2171
  }
2166
2172
 
@@ -6,6 +6,7 @@ declare const spec: {
6
6
  readonly gap: "3px";
7
7
  readonly height: `var(--${string})`;
8
8
  readonly minWidth: 0;
9
+ readonly maxWidth: "100%";
9
10
  readonly paddingInline: `var(--${string})`;
10
11
  readonly borderRadius: `var(--${string})`;
11
12
  readonly fontSize: `var(--${string})`;
@@ -119,6 +119,37 @@ export type TableToolbarProps = Omit<React.ComponentPropsWithoutRef<"div">, "chi
119
119
  */
120
120
  inline?: boolean;
121
121
  className?: string;
122
+ /**
123
+ * The full set of attributes View settings offers — every attribute on
124
+ * the object, shown or not. Defaults to `columns`. Omit both, along with
125
+ * `shownKeys`, and the trigger still renders but the popover has nothing
126
+ * to list, the same "still renders, empty popover" contract `columns`
127
+ * already documents for Sort/Filter.
128
+ */
129
+ viewColumns?: TableToolbarColumn[];
130
+ /**
131
+ * Ordered keys of the attributes currently shown in the view — what
132
+ * the popover's draggable list reflects and reorders. Omit for internal
133
+ * state, seeded from `defaultShownKeys` or (failing that) every key in
134
+ * `viewColumns`/`columns`, in their given order — i.e. "everything shown"
135
+ * unless told otherwise.
136
+ */
137
+ shownKeys?: string[];
138
+ defaultShownKeys?: string[];
139
+ onShownKeysChange?: (keys: string[]) => void;
140
+ /** Text on the View settings trigger (default "View settings"). */
141
+ viewSettingsLabel?: string;
142
+ /** aria-label on a shown attribute's drag grip (default "Reorder attribute"). */
143
+ reorderAttributeLabel?: string;
144
+ /** aria-label on a shown attribute's hide control — receives the
145
+ * attribute's label (default `Hide ${label} from view`). */
146
+ hideAttributeLabel?: (label: string) => string;
147
+ /** Label on the popover's footer action that opens the add-attribute
148
+ * search screen (default "Add attribute to view"), and the heading of
149
+ * that screen once open. */
150
+ addAttributeToViewLabel?: string;
151
+ /** Placeholder in the add-attribute search box (default "Search attributes…"). */
152
+ searchAttributesPlaceholder?: string;
122
153
  };
123
154
  /**
124
155
  * Applies a toolbar's filter conditions to any row array. Reads the raw value
@@ -145,7 +176,7 @@ export declare function applySorts<T>(rows: T[], sorts: ToolbarSort[], get?: (ro
145
176
  * this (Sort: Due date/Assignee/Creation date; Filter: Assignee/Record/
146
177
  * Created by), which one shared list can't reproduce.
147
178
  */
148
- export declare function TableToolbar({ columns, sortColumns: sortColumnsProp, filterColumns: filterColumnsProp, sorts: sortsProp, defaultSorts, onSortsChange, filters: filtersProp, defaultFilters, onFiltersChange, count, children, helpHref, className, sortLabel: sortLabelProp, filterLabel: filterLabelProp, reorderSortLabel: reorderSortLabelProp, noSortsLabel: noSortsLabelProp, addSortLabel: addSortLabelProp, learnSortingLabel: learnSortingLabelProp, selectConditionLabel: selectConditionLabelProp, sortFieldLabel: sortFieldLabelProp, sortDirectionLabel: sortDirectionLabelProp, removeSortLabel: removeSortLabelProp, filterFieldLabel: filterFieldLabelProp, filterFieldPlaceholder: filterFieldPlaceholderProp, filterValuePlaceholder: filterValuePlaceholderProp, filterOptionsLabel: filterOptionsLabelProp, deleteFilterLabel: deleteFilterLabelProp, ascendingLabel: ascendingLabelProp, descendingLabel: descendingLabelProp, sortTriggerLabel, filterTriggerLabel, inline, ...rest }: TableToolbarProps): React.JSX.Element;
179
+ export declare function TableToolbar({ columns, sortColumns: sortColumnsProp, filterColumns: filterColumnsProp, sorts: sortsProp, defaultSorts, onSortsChange, filters: filtersProp, defaultFilters, onFiltersChange, count, children, helpHref, className, sortLabel: sortLabelProp, filterLabel: filterLabelProp, reorderSortLabel: reorderSortLabelProp, noSortsLabel: noSortsLabelProp, addSortLabel: addSortLabelProp, learnSortingLabel: learnSortingLabelProp, selectConditionLabel: selectConditionLabelProp, sortFieldLabel: sortFieldLabelProp, sortDirectionLabel: sortDirectionLabelProp, removeSortLabel: removeSortLabelProp, filterFieldLabel: filterFieldLabelProp, filterFieldPlaceholder: filterFieldPlaceholderProp, filterValuePlaceholder: filterValuePlaceholderProp, filterOptionsLabel: filterOptionsLabelProp, deleteFilterLabel: deleteFilterLabelProp, ascendingLabel: ascendingLabelProp, descendingLabel: descendingLabelProp, sortTriggerLabel, filterTriggerLabel, inline, viewColumns: viewColumnsProp, shownKeys: shownKeysProp, defaultShownKeys, onShownKeysChange, viewSettingsLabel: viewSettingsLabelProp, reorderAttributeLabel: reorderAttributeLabelProp, hideAttributeLabel, addAttributeToViewLabel: addAttributeToViewLabelProp, searchAttributesPlaceholder: searchAttributesPlaceholderProp, ...rest }: TableToolbarProps): React.JSX.Element;
149
180
  export type ToolbarToolProps = React.ComponentPropsWithoutRef<"button"> & {
150
181
  /** Drawn the way Sort/Filter are once a condition is set (#399). */
151
182
  active?: boolean;
@@ -144,7 +144,6 @@ export type CommentsPanelProps = Omit<React.ComponentPropsWithoutRef<"div">, "ch
144
144
  deleteConfirmBody?: string;
145
145
  deleteConfirmActionLabel?: string;
146
146
  cancelLabel?: string;
147
- composerReplyToolLabel?: string;
148
147
  composerMentionToolLabel?: string;
149
148
  className?: string;
150
149
  };
@@ -157,7 +156,7 @@ export type CommentsPanelProps = Omit<React.ComponentPropsWithoutRef<"div">, "ch
157
156
  * separate row affordance (P-051) — deliberately NOT part of this
158
157
  * component, since it lives in a table cell this component doesn't own.
159
158
  */
160
- export declare function CommentsPanel({ threads, currentUserId, subject, subjectIcon, showResolved, onShowResolvedChange, onlyMine, onOnlyMineChange, activeThreadId: activeThreadIdProp, onActiveThreadChange, onPost, onReact, onResolve, onDelete, onCopyLink, emojiOptions, mentionTrigger, renderMentionPicker, onClose, closeLabel: closeLabelProp, backLabel: backLabelProp, title: titleProp, threadTitle: threadTitleProp, breadcrumbPrefix: breadcrumbPrefixProp, composerPlaceholder: composerPlaceholderProp, replyPlaceholder: replyPlaceholderProp, postLabel: postLabelProp, replyPostLabel: replyPostLabelProp, emptyLabel, filterLabel: filterLabelProp, showResolvedLabel: showResolvedLabelProp, onlyMineLabel: onlyMineLabelProp, replyActionLabel: replyActionLabelProp, reactActionLabel: reactActionLabelProp, resolveActionLabel: resolveActionLabelProp, resolvedByYouLabel: resolvedByYouLabelProp, resolvedByLabel, moreActionsLabel: moreActionsLabelProp, copyLinkLabel: copyLinkLabelProp, deleteLabel: deleteLabelProp, addReactionLabel: addReactionLabelProp, reactionSearchLabel: reactionSearchLabelProp, deleteConfirmTitle: deleteConfirmTitleProp, deleteConfirmBody: deleteConfirmBodyProp, deleteConfirmActionLabel: deleteConfirmActionLabelProp, cancelLabel: cancelLabelProp, composerReplyToolLabel: composerReplyToolLabelProp, composerMentionToolLabel: composerMentionToolLabelProp, className, ...rest }: CommentsPanelProps): React.JSX.Element;
159
+ export declare function CommentsPanel({ threads, currentUserId, subject, subjectIcon, showResolved, onShowResolvedChange, onlyMine, onOnlyMineChange, activeThreadId: activeThreadIdProp, onActiveThreadChange, onPost, onReact, onResolve, onDelete, onCopyLink, emojiOptions, mentionTrigger, renderMentionPicker, onClose, closeLabel: closeLabelProp, backLabel: backLabelProp, title: titleProp, threadTitle: threadTitleProp, breadcrumbPrefix: breadcrumbPrefixProp, composerPlaceholder: composerPlaceholderProp, replyPlaceholder: replyPlaceholderProp, postLabel: postLabelProp, replyPostLabel: replyPostLabelProp, emptyLabel, filterLabel: filterLabelProp, showResolvedLabel: showResolvedLabelProp, onlyMineLabel: onlyMineLabelProp, replyActionLabel: replyActionLabelProp, reactActionLabel: reactActionLabelProp, resolveActionLabel: resolveActionLabelProp, resolvedByYouLabel: resolvedByYouLabelProp, resolvedByLabel, moreActionsLabel: moreActionsLabelProp, copyLinkLabel: copyLinkLabelProp, deleteLabel: deleteLabelProp, addReactionLabel: addReactionLabelProp, reactionSearchLabel: reactionSearchLabelProp, deleteConfirmTitle: deleteConfirmTitleProp, deleteConfirmBody: deleteConfirmBodyProp, deleteConfirmActionLabel: deleteConfirmActionLabelProp, cancelLabel: cancelLabelProp, composerMentionToolLabel: composerMentionToolLabelProp, className, ...rest }: CommentsPanelProps): React.JSX.Element;
161
160
  export declare namespace CommentsPanel {
162
161
  var displayName: string;
163
162
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@crisp-ui-kit/crisp",
3
- "version": "0.54.4",
3
+ "version": "0.54.5",
4
4
  "description": "A lean React design system, measured pixel-for-pixel from Attio.",
5
5
  "license": "MIT",
6
6
  "type": "module",