@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 +1 -1
- package/dist/chip.layered.css +1 -1
- package/dist/cjs/components/chip/chip.recipe.js +12 -0
- package/dist/cjs/components/personchip/personchip.js +7 -1
- package/dist/cjs/components/tabletoolbar/tabletoolbar.js +75 -2
- package/dist/cjs/product/comments/comments.js +13 -21
- package/dist/cjs/product/comments/comments.recipe.js +29 -14
- package/dist/comments.css +21 -15
- package/dist/comments.layered.css +21 -15
- package/dist/esm/components/chip/chip.recipe.js +12 -0
- package/dist/esm/components/personchip/personchip.js +7 -1
- package/dist/esm/components/tabletoolbar/tabletoolbar.js +76 -3
- package/dist/esm/product/comments/comments.js +13 -21
- package/dist/esm/product/comments/comments.recipe.js +29 -14
- package/dist/styles.css +22 -16
- package/dist/styles.layered.css +22 -16
- package/dist/types/components/chip/chip.recipe.d.ts +1 -0
- package/dist/types/components/tabletoolbar/tabletoolbar.d.ts +32 -1
- package/dist/types/product/comments/comments.d.ts +1 -2
- package/package.json +1 -1
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; }
|
package/dist/chip.layered.css
CHANGED
|
@@ -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),
|
|
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-
|
|
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,
|
|
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.
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
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)(
|
|
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,
|
|
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,
|
|
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
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
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
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
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),
|
|
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-
|
|
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,
|
|
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: [
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
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(
|
|
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,
|
|
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,
|
|
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
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
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
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
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
|
|
package/dist/styles.layered.css
CHANGED
|
@@ -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,
|
|
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
|
}
|