@crisp-ui-kit/crisp 0.54.7 → 0.54.9
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/authshell.css +5 -1
- package/dist/authshell.layered.css +5 -1
- package/dist/badge.css +5 -5
- package/dist/badge.layered.css +5 -5
- package/dist/board.css +36 -8
- package/dist/board.layered.css +36 -8
- package/dist/button.css +3 -1
- package/dist/button.layered.css +3 -1
- package/dist/cjs/components/badge/badge.recipe.js +28 -5
- package/dist/cjs/components/button/button.js +33 -2
- package/dist/cjs/components/button/button.recipe.js +31 -5
- package/dist/cjs/components/contextmenu/contextmenu.js +2 -2
- package/dist/cjs/components/contextmenu/contextmenu.recipe.js +24 -0
- package/dist/cjs/components/datatable/datatable.js +590 -41
- package/dist/cjs/components/datatable/datatable.recipe.js +118 -5
- package/dist/cjs/components/dialog/dialog.js +72 -5
- package/dist/cjs/components/dialog/dialog.recipe.js +15 -1
- package/dist/cjs/components/tabletoolbar/tabletoolbar.chrome.js +49 -5
- package/dist/cjs/components/tabletoolbar/tabletoolbar.js +365 -49
- package/dist/cjs/components/tabletoolbar/tabletoolbar.recipe.js +78 -0
- package/dist/cjs/components/tabs/tabs.recipe.js +11 -2
- package/dist/cjs/components/tooltip/tooltip.js +23 -9
- package/dist/cjs/components/tooltip/tooltip.recipe.js +10 -9
- package/dist/cjs/product/authshell/authshell.js +14 -1
- package/dist/cjs/product/authshell/authshell.recipe.js +13 -1
- package/dist/cjs/product/board/board.js +175 -46
- package/dist/cjs/product/board/board.recipe.js +56 -4
- package/dist/cjs/product/comments/comments.js +34 -12
- package/dist/cjs/product/comments/comments.recipe.js +31 -7
- package/dist/cjs/product/formdialog/formdialog.js +10 -4
- package/dist/cjs/product/navitem/navitem.js +7 -2
- package/dist/cjs/product/navitem/navitem.recipe.js +16 -0
- package/dist/cjs/product/objectsettings/index.js +1 -2
- package/dist/cjs/product/objectsettings/objectsettings.js +3 -13
- package/dist/cjs/product/recordstable/recordstable.js +1 -17
- package/dist/cjs/product/richtext/richtext.js +76 -1
- package/dist/cjs/product/savedviews/savedviews.js +55 -24
- package/dist/cjs/product/savedviews/savedviews.recipe.js +27 -0
- package/dist/cjs/product/sidebar/sidebar.js +68 -15
- package/dist/cjs/product/sidebar/sidebar.recipe.js +6 -1
- package/dist/cjs/tokens/palette.js +32 -1
- package/dist/cjs/tokens/semantic.js +11 -0
- package/dist/comments.css +50 -14
- package/dist/comments.layered.css +50 -14
- package/dist/contextmenu.css +24 -0
- package/dist/contextmenu.layered.css +24 -0
- package/dist/datatable.css +101 -37
- package/dist/datatable.layered.css +101 -37
- package/dist/dialog.css +13 -1
- package/dist/dialog.layered.css +13 -1
- package/dist/esm/components/badge/badge.recipe.js +28 -5
- package/dist/esm/components/button/button.js +34 -3
- package/dist/esm/components/button/button.recipe.js +31 -5
- package/dist/esm/components/contextmenu/contextmenu.js +2 -2
- package/dist/esm/components/contextmenu/contextmenu.recipe.js +24 -0
- package/dist/esm/components/datatable/datatable.js +590 -42
- package/dist/esm/components/datatable/datatable.recipe.js +119 -6
- package/dist/esm/components/dialog/dialog.js +72 -5
- package/dist/esm/components/dialog/dialog.recipe.js +15 -1
- package/dist/esm/components/tabletoolbar/tabletoolbar.chrome.js +49 -5
- package/dist/esm/components/tabletoolbar/tabletoolbar.js +366 -50
- package/dist/esm/components/tabletoolbar/tabletoolbar.recipe.js +80 -2
- package/dist/esm/components/tabs/tabs.recipe.js +11 -2
- package/dist/esm/components/tooltip/tooltip.js +23 -9
- package/dist/esm/components/tooltip/tooltip.recipe.js +10 -9
- package/dist/esm/product/authshell/authshell.js +15 -2
- package/dist/esm/product/authshell/authshell.recipe.js +13 -1
- package/dist/esm/product/authshell/index.js +1 -1
- package/dist/esm/product/board/board.js +176 -47
- package/dist/esm/product/board/board.recipe.js +56 -4
- package/dist/esm/product/comments/comments.js +35 -13
- package/dist/esm/product/comments/comments.recipe.js +31 -7
- package/dist/esm/product/formdialog/formdialog.js +10 -4
- package/dist/esm/product/navitem/index.js +1 -1
- package/dist/esm/product/navitem/navitem.js +7 -2
- package/dist/esm/product/navitem/navitem.recipe.js +16 -0
- package/dist/esm/product/objectsettings/index.js +1 -1
- package/dist/esm/product/objectsettings/objectsettings.js +2 -12
- package/dist/esm/product/recordstable/recordstable.js +2 -18
- package/dist/esm/product/richtext/richtext.js +76 -1
- package/dist/esm/product/savedviews/savedviews.js +57 -26
- package/dist/esm/product/savedviews/savedviews.recipe.js +27 -0
- package/dist/esm/product/sidebar/sidebar.js +69 -16
- package/dist/esm/product/sidebar/sidebar.recipe.js +6 -1
- package/dist/esm/tokens/palette.js +32 -1
- package/dist/esm/tokens/semantic.js +11 -0
- package/dist/native/tokens.d.ts +1 -1
- package/dist/native/tokens.js +16 -2
- package/dist/navitem.css +7 -0
- package/dist/navitem.layered.css +7 -0
- package/dist/recordstable.css +34 -32
- package/dist/recordstable.layered.css +34 -32
- package/dist/savedviews.css +27 -0
- package/dist/savedviews.layered.css +27 -0
- package/dist/sidebar.css +6 -1
- package/dist/sidebar.layered.css +6 -1
- package/dist/styles.css +369 -106
- package/dist/styles.layered.css +369 -106
- package/dist/tabletoolbar.css +47 -3
- package/dist/tabletoolbar.layered.css +47 -3
- package/dist/tabs.css +1 -1
- package/dist/tabs.layered.css +1 -1
- package/dist/tokens.css +9 -1
- package/dist/tokens.json +16 -2
- package/dist/tooltip.css +1 -1
- package/dist/tooltip.layered.css +1 -1
- package/dist/types/components/badge/badge.recipe.d.ts +5 -1
- package/dist/types/components/button/button.recipe.d.ts +18 -5
- package/dist/types/components/contextmenu/contextmenu.d.ts +8 -1
- package/dist/types/components/datatable/datatable.d.ts +98 -1
- package/dist/types/components/dialog/dialog.d.ts +25 -2
- package/dist/types/components/tabletoolbar/tabletoolbar.d.ts +113 -3
- package/dist/types/components/tooltip/tooltip.d.ts +12 -4
- package/dist/types/components/tooltip/tooltip.recipe.d.ts +1 -1
- package/dist/types/product/authshell/authshell.d.ts +26 -2
- package/dist/types/product/authshell/index.d.ts +1 -1
- package/dist/types/product/board/board.d.ts +53 -6
- package/dist/types/product/comments/comments.d.ts +16 -3
- package/dist/types/product/formdialog/formdialog.d.ts +2 -1
- package/dist/types/product/navitem/index.d.ts +1 -1
- package/dist/types/product/navitem/navitem.d.ts +23 -0
- package/dist/types/product/objectsettings/index.d.ts +1 -1
- package/dist/types/product/objectsettings/objectsettings.d.ts +0 -10
- package/dist/types/product/recordstable/recordstable.d.ts +0 -1
- package/dist/types/product/savedviews/savedviews.d.ts +45 -8
- package/dist/types/product/sidebar/sidebar.d.ts +33 -5
- package/dist/types/tokens/contract.d.ts +4 -0
- package/dist/types/tokens/palette.d.ts +13 -1
- package/dist/types/tokens/semantic.d.ts +11 -0
- package/package.json +1 -1
|
@@ -41,28 +41,43 @@ const menu_1 = require("../../components/menu");
|
|
|
41
41
|
const select_1 = require("../../components/select");
|
|
42
42
|
const core_1 = require("../../core");
|
|
43
43
|
const savedviews_recipe_1 = require("./savedviews.recipe");
|
|
44
|
+
// #618: the same fallback Select's own `optionText` gives a non-string
|
|
45
|
+
// `label` — `keywords` first, then the plain string itself, then (rather
|
|
46
|
+
// than becoming permanently unmatchable) the view's own `id`.
|
|
47
|
+
function viewText(v) {
|
|
48
|
+
if (v.keywords)
|
|
49
|
+
return v.keywords;
|
|
50
|
+
return typeof v.name === "string" ? v.name : v.id;
|
|
51
|
+
}
|
|
44
52
|
/**
|
|
45
53
|
* A saved-view switcher — measured from Attio's "All Companies ▾" menu. A
|
|
46
54
|
* chip trigger opens a popover listing every saved view as a `role="option"`
|
|
47
55
|
* row (leading coloured icon, name, a hover-revealed kebab of
|
|
48
56
|
* rename/duplicate/delete), plus a trailing "Create new view" row. The
|
|
49
57
|
* active view is marked `aria-selected`/`aria-current="page"` and mirrored
|
|
50
|
-
* onto the trigger's own icon/label.
|
|
58
|
+
* onto the trigger's own icon/label. The panel opens on a "Search views..."
|
|
59
|
+
* box (#618, P-007) — real DOM focus moves there (`requestAnimationFrame`,
|
|
60
|
+
* same race `Select`'s own `searchable` variant already guards against),
|
|
61
|
+
* not virtual focus on the trigger.
|
|
51
62
|
*
|
|
52
|
-
* Keyboard: while the
|
|
53
|
-
* virtual highlight (`aria-activedescendant
|
|
54
|
-
*
|
|
55
|
-
*
|
|
56
|
-
*
|
|
57
|
-
*
|
|
63
|
+
* Keyboard: while the panel is open, focus lives on the search input — ↑/↓
|
|
64
|
+
* move a virtual highlight (`aria-activedescendant`) over the (possibly
|
|
65
|
+
* filtered) view list, Enter chooses the highlighted row, Escape closes,
|
|
66
|
+
* deferring to a still-open kebab submenu first (`hasOpenFloatingLayer`),
|
|
67
|
+
* so Escape closes one layer at a time — the same split `Select`'s own
|
|
68
|
+
* `searchable` variant already uses between its trigger and its own search
|
|
69
|
+
* input.
|
|
58
70
|
*/
|
|
59
|
-
function SavedViews({ label, icon, views, activeId, onSelect, onCreate, onRename, onDuplicate, onDelete, onToggleFavorite, createLabel: createLabelProp, renameLabel: renameLabelProp, duplicateLabel: duplicateLabelProp, deleteLabel: deleteLabelProp, favoriteLabel: favoriteLabelProp, viewOptionsLabel: viewOptionsLabelProp, className, "aria-label": ariaLabel, ...rest }) {
|
|
71
|
+
function SavedViews({ label, icon, views, activeId, onSelect, onCreate, onRename, onDuplicate, onDelete, onToggleFavorite, createLabel: createLabelProp, renameLabel: renameLabelProp, duplicateLabel: duplicateLabelProp, deleteLabel: deleteLabelProp, favoriteLabel: favoriteLabelProp, viewOptionsLabel: viewOptionsLabelProp, searchPlaceholder: searchPlaceholderProp, searchLabel: searchLabelProp, emptyLabel: emptyLabelProp, className, "aria-label": ariaLabel, ...rest }) {
|
|
60
72
|
const createLabel = (0, core_1.useCrispMessage)(createLabelProp, "Create new view");
|
|
61
73
|
const renameLabel = (0, core_1.useCrispMessage)(renameLabelProp, "Rename");
|
|
62
74
|
const duplicateLabel = (0, core_1.useCrispMessage)(duplicateLabelProp, "Duplicate");
|
|
63
75
|
const deleteLabel = (0, core_1.useCrispMessage)(deleteLabelProp, "Delete");
|
|
64
76
|
const favoriteLabel = (0, core_1.useCrispMessage)(favoriteLabelProp, "Add to favorites");
|
|
65
77
|
const viewOptionsLabel = (0, core_1.useCrispMessage)(viewOptionsLabelProp, "View options");
|
|
78
|
+
const searchPlaceholder = (0, core_1.useCrispMessage)(searchPlaceholderProp, "Search views...");
|
|
79
|
+
const searchLabel = (0, core_1.useCrispMessage)(searchLabelProp, "Filter views");
|
|
80
|
+
const emptyLabel = (0, core_1.useCrispMessage)(emptyLabelProp, "No results");
|
|
66
81
|
const autoId = React.useId();
|
|
67
82
|
const triggerId = `${autoId}-trigger`;
|
|
68
83
|
// role="combobox" (below) does not take its name from content — unlike a
|
|
@@ -81,13 +96,22 @@ function SavedViews({ label, icon, views, activeId, onSelect, onCreate, onRename
|
|
|
81
96
|
const [up, setUp] = React.useState(false);
|
|
82
97
|
const [pos, setPos] = React.useState({});
|
|
83
98
|
const [highlight, setHighlight] = React.useState(-1);
|
|
99
|
+
// #618: the panel's own "Search views..." box — real DOM focus lives here
|
|
100
|
+
// while open, unlike the virtual-focus-on-trigger model this component
|
|
101
|
+
// used before the search box existed.
|
|
102
|
+
const [query, setQuery] = React.useState("");
|
|
84
103
|
const rootRef = React.useRef(null);
|
|
85
104
|
const triggerRef = React.useRef(null);
|
|
86
105
|
const panelRef = React.useRef(null);
|
|
106
|
+
const searchRef = React.useRef(null);
|
|
87
107
|
const activeView = views.find((v) => v.id === activeId) ?? null;
|
|
108
|
+
const q = query.trim().toLowerCase();
|
|
109
|
+
const filteredViews = q
|
|
110
|
+
? views.filter((v) => viewText(v).toLowerCase().includes(q))
|
|
111
|
+
: views;
|
|
88
112
|
// the trailing "Create new view" row is one more enabled index, past every
|
|
89
|
-
//
|
|
90
|
-
const createIndex =
|
|
113
|
+
// (possibly filtered) view -- it never itself filters out
|
|
114
|
+
const createIndex = filteredViews.length;
|
|
91
115
|
const openPanel = () => {
|
|
92
116
|
const rect = triggerRef.current?.getBoundingClientRect();
|
|
93
117
|
if (rect) {
|
|
@@ -100,8 +124,14 @@ function SavedViews({ label, icon, views, activeId, onSelect, onCreate, onRename
|
|
|
100
124
|
setUp(flipUp);
|
|
101
125
|
setPos(style);
|
|
102
126
|
}
|
|
127
|
+
setQuery("");
|
|
103
128
|
setHighlight(-1);
|
|
104
129
|
setOpen(true);
|
|
130
|
+
// Same race Select's own `searchable` variant already guards against
|
|
131
|
+
// (its own comment: calling focus synchronously here loses to the
|
|
132
|
+
// portal's own mount timing) -- a `requestAnimationFrame` after the
|
|
133
|
+
// state update that renders the input wins it.
|
|
134
|
+
requestAnimationFrame(() => searchRef.current?.focus({ preventScroll: true }));
|
|
105
135
|
};
|
|
106
136
|
const close = React.useCallback(() => {
|
|
107
137
|
setOpen(false);
|
|
@@ -113,7 +143,7 @@ function SavedViews({ label, icon, views, activeId, onSelect, onCreate, onRename
|
|
|
113
143
|
close();
|
|
114
144
|
}
|
|
115
145
|
else {
|
|
116
|
-
const v =
|
|
146
|
+
const v = filteredViews[highlight];
|
|
117
147
|
if (v) {
|
|
118
148
|
onSelect(v.id);
|
|
119
149
|
close();
|
|
@@ -121,7 +151,7 @@ function SavedViews({ label, icon, views, activeId, onSelect, onCreate, onRename
|
|
|
121
151
|
}
|
|
122
152
|
};
|
|
123
153
|
const { onKeyDown: onNavKeys } = (0, select_1.useListboxKeyboard)({
|
|
124
|
-
enabledIndexes: Array.from({ length:
|
|
154
|
+
enabledIndexes: Array.from({ length: filteredViews.length + 1 }, (_, i) => i),
|
|
125
155
|
highlight,
|
|
126
156
|
setHighlight,
|
|
127
157
|
commit,
|
|
@@ -180,25 +210,26 @@ function SavedViews({ label, icon, views, activeId, onSelect, onCreate, onRename
|
|
|
180
210
|
const triggerIcon = activeView ? activeView.icon : icon;
|
|
181
211
|
const triggerLabel = activeView ? activeView.name : label;
|
|
182
212
|
return ((0, jsx_runtime_1.jsxs)("div", { ref: rootRef, className: (0, savedviews_recipe_1.savedviewsRecipe)({ className }), ...rest, children: [(0, jsx_runtime_1.jsxs)("button", { ref: triggerRef, id: triggerId, type: "button",
|
|
183
|
-
// combobox
|
|
184
|
-
//
|
|
185
|
-
//
|
|
186
|
-
//
|
|
187
|
-
//
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
if (open)
|
|
191
|
-
onNavKeys(e);
|
|
192
|
-
else if (e.key === "ArrowDown" || e.key === "Enter") {
|
|
213
|
+
// combobox: aria-haspopup explicitly allows "dialog" — the value
|
|
214
|
+
// Attio's own trigger measures — alongside the more common
|
|
215
|
+
// "listbox". aria-activedescendant lives on the search input (#618)
|
|
216
|
+
// now that real focus moves there while open, not here — the same
|
|
217
|
+
// split Select's own `searchable` variant already uses.
|
|
218
|
+
role: "combobox", "aria-haspopup": "dialog", "aria-expanded": open, "aria-controls": open ? panelId : undefined, "aria-label": ariaLabel, "aria-labelledby": ariaLabel ? undefined : triggerLabelId, "data-open": open ? "" : undefined, className: "crisp-savedviews-trigger", onClick: () => (open ? setOpen(false) : openPanel()), onKeyDown: (e) => {
|
|
219
|
+
// navigation is handled on the search input while open (#618)
|
|
220
|
+
if (!open && (e.key === "ArrowDown" || e.key === "Enter")) {
|
|
193
221
|
e.preventDefault();
|
|
194
222
|
openPanel();
|
|
195
223
|
}
|
|
196
|
-
}, children: [triggerIcon != null && ((0, jsx_runtime_1.jsx)("span", { className: "crisp-savedviews-trigger-icon", children: triggerIcon })), (0, jsx_runtime_1.jsx)("span", { id: triggerLabelId, className: "crisp-savedviews-trigger-label", children: triggerLabel }), (0, jsx_runtime_1.jsx)(lucide_react_1.ChevronDown, { className: "crisp-savedviews-trigger-chevron", size: 16, strokeWidth: 1.75 })] }), open && ((0, jsx_runtime_1.jsx)(core_1.FloatingPortal, { sourceRef: rootRef, children: (0, jsx_runtime_1.jsxs)("div", { ref: panelRef, id: panelId, role: "listbox", "aria-label": ariaLabel, "aria-labelledby": ariaLabel ? undefined : triggerLabelId, className: "crisp-savedviews-panel", "data-up": up ? "" : undefined, style: pos, children: [
|
|
224
|
+
}, children: [triggerIcon != null && ((0, jsx_runtime_1.jsx)("span", { className: "crisp-savedviews-trigger-icon", children: triggerIcon })), (0, jsx_runtime_1.jsx)("span", { id: triggerLabelId, className: "crisp-savedviews-trigger-label", children: triggerLabel }), (0, jsx_runtime_1.jsx)(lucide_react_1.ChevronDown, { className: "crisp-savedviews-trigger-chevron", size: 16, strokeWidth: 1.75 })] }), open && ((0, jsx_runtime_1.jsx)(core_1.FloatingPortal, { sourceRef: rootRef, children: (0, jsx_runtime_1.jsxs)("div", { ref: panelRef, id: panelId, role: "listbox", "aria-label": ariaLabel, "aria-labelledby": ariaLabel ? undefined : triggerLabelId, className: "crisp-savedviews-panel", "data-up": up ? "" : undefined, style: pos, children: [(0, jsx_runtime_1.jsx)("input", { ref: searchRef, type: "text", placeholder: searchPlaceholder, "aria-label": searchLabel, "aria-controls": panelId, "aria-activedescendant": highlight >= 0 ? optionId(highlight) : undefined, className: "crisp-savedviews-search", value: query, onChange: (e) => {
|
|
225
|
+
setQuery(e.target.value);
|
|
226
|
+
setHighlight(-1);
|
|
227
|
+
}, onKeyDown: onNavKeys }), (0, jsx_runtime_1.jsx)("div", { className: "crisp-savedviews-search-sep", "aria-hidden": "true" }), filteredViews.length === 0 && ((0, jsx_runtime_1.jsx)("div", { className: "crisp-savedviews-empty", children: emptyLabel })), filteredViews.map((v, i) => {
|
|
197
228
|
const selected = v.id === activeId;
|
|
198
229
|
return ((0, jsx_runtime_1.jsxs)("div", { id: optionId(i), role: "option", "aria-selected": selected, "aria-current": selected ? "page" : undefined, "data-highlighted": i === highlight ? "" : undefined, className: "crisp-savedviews-row", onMouseEnter: () => setHighlight(i), onClick: () => {
|
|
199
230
|
onSelect(v.id);
|
|
200
231
|
close();
|
|
201
|
-
}, children: [v.icon != null && ((0, jsx_runtime_1.jsx)("span", { className: "crisp-savedviews-row-icon", children: v.icon })), (0, jsx_runtime_1.jsx)("span", { className: "crisp-savedviews-row-label", children: v.name }), v.shared && ((0, jsx_runtime_1.jsx)("span", { className: "crisp-savedviews-row-shared", "aria-hidden": "true", children: (0, jsx_runtime_1.jsx)(lucide_react_1.Users2, { size: 12, strokeWidth: 1.75 }) })), (0, jsx_runtime_1.jsx)("div", { className: "crisp-savedviews-kebab-slot", onClick: (e) => e.stopPropagation(), children: (0, jsx_runtime_1.jsxs)(menu_1.Menu, { align: "end", trigger: (0, jsx_runtime_1.jsx)("button", { type: "button", className: "crisp-savedviews-kebab", "aria-label": viewOptionsLabel, children: (0, jsx_runtime_1.jsx)(lucide_react_1.MoreVertical, { size: 14, strokeWidth: 1.75 }) }), children: [onToggleFavorite && ((0, jsx_runtime_1.jsx)(menu_1.MenuItem, { onSelect: () => onToggleFavorite(v.id), children: favoriteLabel })), (0, jsx_runtime_1.jsx)(menu_1.MenuItem, { onSelect: () => onRename(v.id), children: renameLabel }), (0, jsx_runtime_1.jsx)(menu_1.MenuItem, { onSelect: () => onDuplicate(v.id), children: duplicateLabel }), (0, jsx_runtime_1.jsx)(menu_1.MenuItem, { danger: true, onSelect: () => onDelete(v.id), children: deleteLabel })] }) })] }, v.id));
|
|
232
|
+
}, children: [v.icon != null && ((0, jsx_runtime_1.jsx)("span", { className: "crisp-savedviews-row-icon", children: v.icon })), (0, jsx_runtime_1.jsx)("span", { className: "crisp-savedviews-row-label", children: v.name }), v.shared && ((0, jsx_runtime_1.jsx)("span", { className: "crisp-savedviews-row-shared", "aria-hidden": "true", children: (0, jsx_runtime_1.jsx)(lucide_react_1.Users2, { size: 12, strokeWidth: 1.75 }) })), (0, jsx_runtime_1.jsx)("div", { className: "crisp-savedviews-kebab-slot", onClick: (e) => e.stopPropagation(), children: (0, jsx_runtime_1.jsxs)(menu_1.Menu, { align: "end", trigger: (0, jsx_runtime_1.jsx)("button", { type: "button", className: "crisp-savedviews-kebab", "aria-label": viewOptionsLabel, children: (0, jsx_runtime_1.jsx)(lucide_react_1.MoreVertical, { size: 14, strokeWidth: 1.75 }) }), children: [onToggleFavorite && ((0, jsx_runtime_1.jsx)(menu_1.MenuItem, { onSelect: () => onToggleFavorite(v.id), children: favoriteLabel })), (0, jsx_runtime_1.jsx)(menu_1.MenuItem, { onSelect: () => onRename(v.id), children: renameLabel }), (0, jsx_runtime_1.jsx)(menu_1.MenuItem, { onSelect: () => onDuplicate(v.id), children: duplicateLabel }), (0, jsx_runtime_1.jsx)(menu_1.MenuSeparator, {}), (0, jsx_runtime_1.jsx)(menu_1.MenuItem, { danger: true, onSelect: () => onDelete(v.id), children: deleteLabel }), (v.createdBy != null || v.createdAt != null) && ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsx)(menu_1.MenuSeparator, {}), (0, jsx_runtime_1.jsxs)("div", { className: "crisp-savedviews-kebab-footer", children: [v.createdBy != null && ((0, jsx_runtime_1.jsx)("div", { className: "crisp-savedviews-kebab-footer-line", children: v.createdBy })), v.createdAt != null && ((0, jsx_runtime_1.jsx)("div", { className: "crisp-savedviews-kebab-footer-line", children: v.createdAt }))] })] }))] }) })] }, v.id));
|
|
202
233
|
}), (0, jsx_runtime_1.jsxs)("div", { id: optionId(createIndex), role: "option", "aria-selected": false, "data-highlighted": highlight === createIndex ? "" : undefined, className: "crisp-savedviews-row", onMouseEnter: () => setHighlight(createIndex), onClick: () => {
|
|
203
234
|
onCreate();
|
|
204
235
|
close();
|
|
@@ -63,6 +63,21 @@ const config = (0, core_1.defineRecipe)({
|
|
|
63
63
|
.crisp-savedviews-panel[data-up] { transform-origin: bottom left; }
|
|
64
64
|
.crisp-savedviews-panel { @starting-style { opacity: 0; transform: scale(0.97) translateY(-1px); } }
|
|
65
65
|
|
|
66
|
+
/* #618 (P-007): the panel's own "Search views..." box — measured live:
|
|
67
|
+
32px tall, 6px/8px padding, NO border/background/leading icon of its
|
|
68
|
+
own (unlike Select's own bordered searchable-input pill), 14px text.
|
|
69
|
+
A hairline divider (borderSubtle, 1px, 3px top margin) separates it
|
|
70
|
+
from the view list, matching the divider Attio's own kebab menu uses
|
|
71
|
+
between its action items and the "Created by" footer below. */
|
|
72
|
+
.crisp-savedviews-search {
|
|
73
|
+
box-sizing: border-box; display: block; width: 100%; height: 32px; flex-shrink: 0;
|
|
74
|
+
margin: 0; padding: ${tokens_1.contract.space["1.5"]} ${tokens_1.contract.space["2"]}; border: none; background: none;
|
|
75
|
+
font-family: inherit; font-size: ${tokens_1.contract.text.lg}; color: ${tokens_1.contract.fgPrimary}; outline: none;
|
|
76
|
+
}
|
|
77
|
+
.crisp-savedviews-search::placeholder { color: ${tokens_1.contract.fgMuted}; }
|
|
78
|
+
.crisp-savedviews-search-sep { height: 1px; flex-shrink: 0; margin: 3px 0 0; background: ${tokens_1.contract.borderSubtle}; }
|
|
79
|
+
.crisp-savedviews-empty { padding: ${tokens_1.contract.space["1.5"]} ${tokens_1.contract.space["2"]}; font-size: ${tokens_1.contract.text.lg}; color: ${tokens_1.contract.fgMuted}; }
|
|
80
|
+
|
|
66
81
|
.crisp-savedviews-row {
|
|
67
82
|
box-sizing: border-box; position: relative; display: flex; align-items: center; gap: ${tokens_1.contract.space["1.5"]};
|
|
68
83
|
height: ${tokens_1.contract.size.menuRow}; flex-shrink: 0; padding: ${tokens_1.contract.space["1.5"]} ${tokens_1.contract.space["2"]};
|
|
@@ -94,6 +109,18 @@ const config = (0, core_1.defineRecipe)({
|
|
|
94
109
|
.crisp-savedviews-row:focus-within .crisp-savedviews-kebab,
|
|
95
110
|
.crisp-savedviews-kebab[data-open] { opacity: 1; }
|
|
96
111
|
.crisp-savedviews-kebab:hover, .crisp-savedviews-kebab[data-open] { background: ${tokens_1.contract.bgSurface}; color: ${tokens_1.contract.fgPrimary}; }
|
|
112
|
+
|
|
113
|
+
/* #618 (P-008): the kebab menu's own "Created by X" / date footer —
|
|
114
|
+
measured live: 2 stacked lines, 6px/8px padding, 2px gap between
|
|
115
|
+
them, 12px/500 text, BOTH lines the same color-mix 50% muted (a
|
|
116
|
+
distinct value from the 63% mix #592's chip breadcrumb uses —
|
|
117
|
+
independently measured, not assumed to match). */
|
|
118
|
+
.crisp-savedviews-kebab-footer {
|
|
119
|
+
display: flex; flex-direction: column; gap: 2px;
|
|
120
|
+
padding: ${tokens_1.contract.space["1.5"]} ${tokens_1.contract.space["2"]};
|
|
121
|
+
font-size: ${tokens_1.contract.text.sm}; font-weight: ${tokens_1.contract.weight.medium};
|
|
122
|
+
color: color-mix(in srgb, ${tokens_1.contract.fgPrimary} 50%, transparent);
|
|
123
|
+
}
|
|
97
124
|
`,
|
|
98
125
|
variants: {},
|
|
99
126
|
defaultVariants: {},
|
|
@@ -80,42 +80,83 @@ exports.ATTIO_SIDEBAR_WORKSPACE = {
|
|
|
80
80
|
avatarLabel: "T",
|
|
81
81
|
avatarTone: "cyan",
|
|
82
82
|
};
|
|
83
|
-
/** The measured main nav — the default `items` prop.
|
|
83
|
+
/** The measured main nav — the default `items` prop.
|
|
84
|
+
*
|
|
85
|
+
* `shortcut` is set only where a live hover produced Attio's "G then
|
|
86
|
+
* <letter>" tooltip (crisp#633, P-108) -- Calls/Sequences/Workflows showed
|
|
87
|
+
* no tooltip at all after a 1.2s hover, tried repeatedly, so they're left
|
|
88
|
+
* without one rather than guessed. */
|
|
84
89
|
exports.ATTIO_SIDEBAR_ITEMS = [
|
|
85
|
-
{
|
|
90
|
+
{
|
|
91
|
+
label: "Home",
|
|
92
|
+
icon: IconHome,
|
|
93
|
+
shortcut: { description: "Go to home page", keys: ["g", "h"] },
|
|
94
|
+
},
|
|
86
95
|
{
|
|
87
96
|
label: "Tasks",
|
|
88
97
|
icon: IconTasks,
|
|
89
98
|
active: true,
|
|
90
99
|
trailing: ((0, jsx_runtime_1.jsx)(badge_1.Badge, { tone: "brand", variant: "solid", size: "small", children: "1" })),
|
|
100
|
+
shortcut: { description: "Go to tasks page", keys: ["g", "t"] },
|
|
101
|
+
},
|
|
102
|
+
{
|
|
103
|
+
label: "Notes",
|
|
104
|
+
icon: IconNotes,
|
|
105
|
+
shortcut: { description: "Go to all notes page", keys: ["g", "n"] },
|
|
106
|
+
},
|
|
107
|
+
{
|
|
108
|
+
label: "Emails",
|
|
109
|
+
icon: IconEmails,
|
|
110
|
+
shortcut: { description: "Go to email drafts page", keys: ["g", "e"] },
|
|
91
111
|
},
|
|
92
|
-
{ label: "Notes", icon: IconNotes },
|
|
93
|
-
{ label: "Emails", icon: IconEmails },
|
|
94
112
|
{ label: "Calls", icon: IconCalls },
|
|
95
|
-
{
|
|
113
|
+
{
|
|
114
|
+
label: "Reports",
|
|
115
|
+
icon: IconReports,
|
|
116
|
+
shortcut: { description: "Go to reports page", keys: ["g", "d"] },
|
|
117
|
+
},
|
|
96
118
|
{ label: "Sequences", icon: IconSequences },
|
|
97
119
|
{ label: "Workflows", icon: IconWorkflows },
|
|
98
120
|
];
|
|
99
|
-
/** The measured Records section — the default `recordsSection` prop.
|
|
121
|
+
/** The measured Records section — the default `recordsSection` prop. Same
|
|
122
|
+
* `shortcut` caveat as `ATTIO_SIDEBAR_ITEMS`: Deals/Users/Workspaces showed
|
|
123
|
+
* no hover tooltip. `actions` is Attio's own gear + add glyph (P-109),
|
|
124
|
+
* measured aria-labels "Record options" / "Create new objects or enable
|
|
125
|
+
* standard objects". */
|
|
100
126
|
exports.ATTIO_SIDEBAR_RECORDS_SECTION = {
|
|
101
127
|
label: "Records",
|
|
102
128
|
items: [
|
|
103
|
-
{
|
|
104
|
-
|
|
129
|
+
{
|
|
130
|
+
label: "Companies",
|
|
131
|
+
tone: "brand",
|
|
132
|
+
glyph: GlyphCompanies,
|
|
133
|
+
shortcut: { description: "Go to all companies page", keys: ["g", "c"] },
|
|
134
|
+
},
|
|
135
|
+
{
|
|
136
|
+
label: "People",
|
|
137
|
+
tone: "brand",
|
|
138
|
+
glyph: GlyphPeople,
|
|
139
|
+
shortcut: { description: "Go to all people page", keys: ["g", "p"] },
|
|
140
|
+
},
|
|
105
141
|
{ label: "Deals", tone: "orange", glyph: GlyphDeals },
|
|
106
142
|
{ label: "Users", tone: "cyan", glyph: GlyphUsers },
|
|
107
143
|
{ label: "Workspaces", tone: "purple", glyph: GlyphWorkspaces },
|
|
108
144
|
],
|
|
145
|
+
actions: defaultSectionActions("Record options", "Create new objects or enable standard objects"),
|
|
109
146
|
};
|
|
110
|
-
/** The measured Lists section — the default `listsSection` prop.
|
|
147
|
+
/** The measured Lists section — the default `listsSection` prop. `actions`
|
|
148
|
+
* per P-109, measured aria-labels "List options" / "Add list". */
|
|
111
149
|
exports.ATTIO_SIDEBAR_LISTS_SECTION = {
|
|
112
150
|
label: "Lists",
|
|
113
151
|
newListLabel: "+ New list",
|
|
152
|
+
actions: defaultSectionActions("List options", "Add list"),
|
|
114
153
|
};
|
|
115
|
-
/** The measured Chats section — the default `chatsSection` prop.
|
|
154
|
+
/** The measured Chats section — the default `chatsSection` prop. `actions`
|
|
155
|
+
* per P-109, measured aria-labels "Chat options" / "New chat". */
|
|
116
156
|
exports.ATTIO_SIDEBAR_CHATS_SECTION = {
|
|
117
157
|
label: "Chats",
|
|
118
158
|
items: [{ label: "In-app notification for deal wins", icon: IconChat }],
|
|
159
|
+
actions: defaultSectionActions("Chat options", "New chat"),
|
|
119
160
|
};
|
|
120
161
|
/** The measured footer copy — the default `footer` prop. */
|
|
121
162
|
exports.ATTIO_SIDEBAR_FOOTER = {
|
|
@@ -124,8 +165,20 @@ exports.ATTIO_SIDEBAR_FOOTER = {
|
|
|
124
165
|
trialText: "13 days left on trial!",
|
|
125
166
|
trialActionLabel: "Keep Pro",
|
|
126
167
|
};
|
|
127
|
-
function SectionHeader({ label, open, onToggle, }) {
|
|
128
|
-
return ((0, jsx_runtime_1.jsxs)("button", { type: "button", className: "crisp-sidebar-section-header", "data-collapsed": open ? undefined : "", "aria-expanded": open, onClick: onToggle, children: [(0, jsx_runtime_1.jsx)(IconChevronMini, {}), (0, jsx_runtime_1.jsx)("span", { children: label })] }));
|
|
168
|
+
function SectionHeader({ label, open, onToggle, actions, }) {
|
|
169
|
+
return ((0, jsx_runtime_1.jsxs)("div", { className: "crisp-sidebar-section-header-row", children: [(0, jsx_runtime_1.jsxs)("button", { type: "button", className: "crisp-sidebar-section-header", "data-collapsed": open ? undefined : "", "aria-expanded": open, onClick: onToggle, children: [(0, jsx_runtime_1.jsx)(IconChevronMini, {}), (0, jsx_runtime_1.jsx)("span", { children: label })] }), actions != null && ((0, jsx_runtime_1.jsx)("div", { className: "crisp-sidebar-section-actions", children: actions }))] }));
|
|
170
|
+
}
|
|
171
|
+
/** Attio's own two hover-only header actions (crisp#633, P-109), measured on
|
|
172
|
+
* the Records/Lists/Chats section headers: a gear ("<Section> options") and
|
|
173
|
+
* an add glyph -- re-measured on all three, the add icon is the SAME glyph
|
|
174
|
+
* (`icon-defs/cab3218f7a08cd13.svg`) reused everywhere, not a per-section
|
|
175
|
+
* one, so `PackagePlus` covers Lists/Chats too, not just Records' own
|
|
176
|
+
* "Create new objects" link. Both are decorative defaults with no handler,
|
|
177
|
+
* the same trade a consumer already makes with `footer`'s default
|
|
178
|
+
* "Getting started"/"Keep Pro" buttons — a consumer wanting the real
|
|
179
|
+
* behaviour passes its own `actions` node instead. */
|
|
180
|
+
function defaultSectionActions(optionsLabel, addLabel) {
|
|
181
|
+
return ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsx)("button", { type: "button", className: "crisp-sidebar-section-action", "aria-label": optionsLabel, children: (0, jsx_runtime_1.jsx)(lucide_react_1.Settings, { size: 16, strokeWidth: 1.75, "aria-hidden": "true" }) }), (0, jsx_runtime_1.jsx)("button", { type: "button", className: "crisp-sidebar-section-action", "aria-label": addLabel, children: (0, jsx_runtime_1.jsx)(lucide_react_1.PackagePlus, { size: 16, strokeWidth: 1.75, "aria-hidden": "true" }) })] }));
|
|
129
182
|
}
|
|
130
183
|
exports.Sidebar = React.forwardRef(({ className, asChild, onToggleCollapse, collapseLabel = "Collapse sidebar", inboxLabel = "Inbox", searchLabel = "Search", workspace = exports.ATTIO_SIDEBAR_WORKSPACE, workspaceMenu, items = exports.ATTIO_SIDEBAR_ITEMS, recordsSection = exports.ATTIO_SIDEBAR_RECORDS_SECTION, listsSection = exports.ATTIO_SIDEBAR_LISTS_SECTION, chatsSection = exports.ATTIO_SIDEBAR_CHATS_SECTION, groups, footer = exports.ATTIO_SIDEBAR_FOOTER, commandItems, commandOpen: commandOpenProp, defaultCommandOpen, onCommandOpenChange, commandShortcut = true, "aria-label": ariaLabel = "Main", ...props }, ref) => {
|
|
131
184
|
// Seeded once, on mount, from whatever groups start collapsed — not
|
|
@@ -174,14 +227,14 @@ exports.Sidebar = React.forwardRef(({ className, asChild, onToggleCollapse, coll
|
|
|
174
227
|
});
|
|
175
228
|
}, []);
|
|
176
229
|
const workspaceTrigger = ((0, jsx_runtime_1.jsxs)("button", { type: "button", className: "crisp-sidebar-ws", children: [(0, jsx_runtime_1.jsx)(avatar_1.Avatar, { size: "lg", shape: "rounded", tone: workspace.avatarTone, children: workspace.avatarLabel }), (0, jsx_runtime_1.jsx)("span", { className: "crisp-sidebar-ws-name", children: workspace.name }), (0, jsx_runtime_1.jsx)(IconChevronDown, {})] }));
|
|
177
|
-
return ((0, jsx_runtime_1.jsxs)(core_1.Primitive.nav, { ref: ref, asChild: asChild, "aria-label": ariaLabel, className: (0, sidebar_recipe_1.sidebarRecipe)({ className }), ...props, children: [(0, jsx_runtime_1.jsxs)("div", { className: "crisp-sidebar-ws-row", children: [workspaceMenu ? ((0, jsx_runtime_1.jsx)(menu_1.Menu, { trigger: workspaceTrigger, className: "crisp-sidebar-ws-menuroot", contentClassName: "crisp-sidebar-ws-menu-content", children: workspaceMenu })) : (workspaceTrigger), (0, jsx_runtime_1.jsx)(button_1.Button, { intent: "ghost", size: "sm", shape: "icon", className: "crisp-sidebar-ws-collapse", "aria-label": collapseLabel, onClick: onToggleCollapse, children: (0, jsx_runtime_1.jsx)(IconCollapsePanel, {}) })] }), (0, jsx_runtime_1.jsxs)("div", { className: "crisp-sidebar-searchrow", children: [(0, jsx_runtime_1.jsxs)("button", { type: "button", className: "crisp-sidebar-search", onClick: () => setCommandOpen(true), children: [(0, jsx_runtime_1.jsx)(IconSearch, {}), (0, jsx_runtime_1.jsx)("span", { className: "crisp-sidebar-search-label", children: searchLabel }), (0, jsx_runtime_1.jsx)("span", { className: "crisp-sidebar-search-kbd", children: "\u2318K" })] }), (0, jsx_runtime_1.jsx)(button_1.Button, { intent: "neutral", size: "sm", shape: "icon", "aria-label": inboxLabel, children: (0, jsx_runtime_1.jsx)(IconInbox, {}) })] }), (0, jsx_runtime_1.jsx)("div", { className: "crisp-sidebar-divider" }), (0, jsx_runtime_1.jsx)("div", { className: "crisp-sidebar-section", children: items.map(({ label, icon: ItemIcon, active, trailing, href, onSelect }) => ((0, jsx_runtime_1.jsx)(navitem_1.NavItem, { href: href ?? "#", active: active, icon: (0, jsx_runtime_1.jsx)(ItemIcon, {}), trailing: trailing, onClick: onSelect, children: label }, href ?? label))) }), groups ? (
|
|
230
|
+
return ((0, jsx_runtime_1.jsxs)(core_1.Primitive.nav, { ref: ref, asChild: asChild, "aria-label": ariaLabel, className: (0, sidebar_recipe_1.sidebarRecipe)({ className }), ...props, children: [(0, jsx_runtime_1.jsxs)("div", { className: "crisp-sidebar-ws-row", children: [workspaceMenu ? ((0, jsx_runtime_1.jsx)(menu_1.Menu, { trigger: workspaceTrigger, className: "crisp-sidebar-ws-menuroot", contentClassName: "crisp-sidebar-ws-menu-content", children: workspaceMenu })) : (workspaceTrigger), (0, jsx_runtime_1.jsx)(button_1.Button, { intent: "ghost", size: "sm", shape: "icon", className: "crisp-sidebar-ws-collapse", "aria-label": collapseLabel, onClick: onToggleCollapse, children: (0, jsx_runtime_1.jsx)(IconCollapsePanel, {}) })] }), (0, jsx_runtime_1.jsxs)("div", { className: "crisp-sidebar-searchrow", children: [(0, jsx_runtime_1.jsxs)("button", { type: "button", className: "crisp-sidebar-search", onClick: () => setCommandOpen(true), children: [(0, jsx_runtime_1.jsx)(IconSearch, {}), (0, jsx_runtime_1.jsx)("span", { className: "crisp-sidebar-search-label", children: searchLabel }), (0, jsx_runtime_1.jsx)("span", { className: "crisp-sidebar-search-kbd", children: "\u2318K" })] }), (0, jsx_runtime_1.jsx)(button_1.Button, { intent: "neutral", size: "sm", shape: "icon", "aria-label": inboxLabel, children: (0, jsx_runtime_1.jsx)(IconInbox, {}) })] }), (0, jsx_runtime_1.jsx)("div", { className: "crisp-sidebar-divider" }), (0, jsx_runtime_1.jsx)("div", { className: "crisp-sidebar-section", children: items.map(({ label, icon: ItemIcon, active, trailing, href, onSelect, shortcut, }) => ((0, jsx_runtime_1.jsx)(navitem_1.NavItem, { href: href ?? "#", active: active, icon: (0, jsx_runtime_1.jsx)(ItemIcon, {}), trailing: trailing, onClick: onSelect, shortcut: shortcut, children: label }, href ?? label))) }), groups ? (
|
|
178
231
|
// #325: navigation as data, reusing the exact section markup/
|
|
179
232
|
// recipe above — SectionHeader + .crisp-sidebar-section — instead
|
|
180
233
|
// of the three fixed Attio sections.
|
|
181
234
|
groups.map((group) => {
|
|
182
235
|
const groupKey = group.key ?? group.label;
|
|
183
|
-
return ((0, jsx_runtime_1.jsxs)(React.Fragment, { children: [(0, jsx_runtime_1.jsx)(SectionHeader, { label: group.label, open: !collapsedSections.has(groupKey), onToggle: () => toggleSection(groupKey) }), (0, jsx_runtime_1.jsx)("div", { className: "crisp-sidebar-section", "data-collapsed": collapsedSections.has(groupKey) ? "" : undefined, children: group.items.map((item) => ((0, jsx_runtime_1.jsx)(navitem_1.NavItem, { href: item.href ?? "#", active: item.active, icon: item.icon ? (0, jsx_runtime_1.jsx)(item.icon, {}) : undefined, trailing: item.count, onClick: item.onSelect, children: item.label }, item.href ?? item.label))) })] }, groupKey));
|
|
184
|
-
})) : ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsx)(SectionHeader, { label: recordsSection.label, open: !collapsedSections.has("records"), onToggle: () => toggleSection("records") }), (0, jsx_runtime_1.jsx)("div", { className: "crisp-sidebar-section", "data-collapsed": collapsedSections.has("records") ? "" : undefined, children: recordsSection.items.map(({ label, tone, glyph: Glyph }) => ((0, jsx_runtime_1.jsx)(navitem_1.NavItem, { href: "#", icon: (0, jsx_runtime_1.jsx)(avatar_1.Avatar, { size: "sm", shape: "rounded", tone: tone, children: (0, jsx_runtime_1.jsx)(Glyph, {}) }), children: label }, label))) }), (0, jsx_runtime_1.jsx)(SectionHeader, { label: listsSection.label, open: !collapsedSections.has("lists"), onToggle: () => toggleSection("lists") }), (0, jsx_runtime_1.jsx)("div", { className: "crisp-sidebar-section", "data-collapsed": collapsedSections.has("lists") ? "" : undefined, children: (0, jsx_runtime_1.jsx)("button", { type: "button", className: "crisp-sidebar-newlist", children: listsSection.newListLabel }) }), (0, jsx_runtime_1.jsx)(SectionHeader, { label: chatsSection.label, open: !collapsedSections.has("chats"), onToggle: () => toggleSection("chats") }), (0, jsx_runtime_1.jsx)("div", { className: "crisp-sidebar-section", "data-collapsed": collapsedSections.has("chats") ? "" : undefined, children: chatsSection.items.map(({ label, icon: ItemIcon, active, trailing, href, onSelect, }) => ((0, jsx_runtime_1.jsx)(navitem_1.NavItem, { href: href ?? "#", active: active, icon: (0, jsx_runtime_1.jsx)(ItemIcon, {}), trailing: trailing, onClick: onSelect, children: label }, href ?? label))) })] })), footer !== false &&
|
|
236
|
+
return ((0, jsx_runtime_1.jsxs)(React.Fragment, { children: [(0, jsx_runtime_1.jsx)(SectionHeader, { label: group.label, open: !collapsedSections.has(groupKey), onToggle: () => toggleSection(groupKey), actions: group.actions }), (0, jsx_runtime_1.jsx)("div", { className: "crisp-sidebar-section", "data-collapsed": collapsedSections.has(groupKey) ? "" : undefined, children: group.items.map((item) => ((0, jsx_runtime_1.jsx)(navitem_1.NavItem, { href: item.href ?? "#", active: item.active, icon: item.icon ? (0, jsx_runtime_1.jsx)(item.icon, {}) : undefined, trailing: item.count, onClick: item.onSelect, shortcut: item.shortcut, children: item.label }, item.href ?? item.label))) })] }, groupKey));
|
|
237
|
+
})) : ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsx)(SectionHeader, { label: recordsSection.label, open: !collapsedSections.has("records"), onToggle: () => toggleSection("records"), actions: recordsSection.actions }), (0, jsx_runtime_1.jsx)("div", { className: "crisp-sidebar-section", "data-collapsed": collapsedSections.has("records") ? "" : undefined, children: recordsSection.items.map(({ label, tone, glyph: Glyph, shortcut }) => ((0, jsx_runtime_1.jsx)(navitem_1.NavItem, { href: "#", icon: (0, jsx_runtime_1.jsx)(avatar_1.Avatar, { size: "sm", shape: "rounded", tone: tone, children: (0, jsx_runtime_1.jsx)(Glyph, {}) }), shortcut: shortcut, children: label }, label))) }), (0, jsx_runtime_1.jsx)(SectionHeader, { label: listsSection.label, open: !collapsedSections.has("lists"), onToggle: () => toggleSection("lists"), actions: listsSection.actions }), (0, jsx_runtime_1.jsx)("div", { className: "crisp-sidebar-section", "data-collapsed": collapsedSections.has("lists") ? "" : undefined, children: (0, jsx_runtime_1.jsx)("button", { type: "button", className: "crisp-sidebar-newlist", children: listsSection.newListLabel }) }), (0, jsx_runtime_1.jsx)(SectionHeader, { label: chatsSection.label, open: !collapsedSections.has("chats"), onToggle: () => toggleSection("chats"), actions: chatsSection.actions }), (0, jsx_runtime_1.jsx)("div", { className: "crisp-sidebar-section", "data-collapsed": collapsedSections.has("chats") ? "" : undefined, children: chatsSection.items.map(({ label, icon: ItemIcon, active, trailing, href, onSelect, shortcut, }) => ((0, jsx_runtime_1.jsx)(navitem_1.NavItem, { href: href ?? "#", active: active, icon: (0, jsx_runtime_1.jsx)(ItemIcon, {}), trailing: trailing, onClick: onSelect, shortcut: shortcut, children: label }, href ?? label))) })] })), footer !== false &&
|
|
185
238
|
(isSidebarFooterCopy(footer) ? ((0, jsx_runtime_1.jsxs)("div", { className: "crisp-sidebar-footer", children: [(0, jsx_runtime_1.jsx)(button_1.Button, { intent: "neutral", size: "sm", className: "crisp-sidebar-pill", children: footer.gettingStartedLabel }), (0, jsx_runtime_1.jsx)(navitem_1.NavItem, { href: "#", icon: (0, jsx_runtime_1.jsx)(IconInvite, {}), children: footer.inviteLabel }), (0, jsx_runtime_1.jsx)("div", { className: "crisp-sidebar-divider" }), (0, jsx_runtime_1.jsxs)("div", { className: "crisp-sidebar-trial", children: [(0, jsx_runtime_1.jsx)("span", { children: footer.trialText }), (0, jsx_runtime_1.jsx)(button_1.Button, { intent: "neutral", size: "sm", children: footer.trialActionLabel })] })] })) : ((0, jsx_runtime_1.jsx)("div", { className: "crisp-sidebar-footer", children: footer }))), (0, jsx_runtime_1.jsx)(command_1.Command, { open: commandOpen, onOpenChange: setCommandOpen, items: commandItems })] }));
|
|
186
239
|
});
|
|
187
240
|
exports.Sidebar.displayName = "Sidebar";
|
|
@@ -19,9 +19,14 @@ const config = (0, core_1.defineRecipe)({
|
|
|
19
19
|
; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; overflow-y: auto; & .crisp-sidebar-section { display: flex; flex-direction: column; gap: ${tokens_1.contract.space.px}; margin-top: ${tokens_1.contract.space["2"]}; }
|
|
20
20
|
& .crisp-sidebar-section[data-collapsed] { display: none; }
|
|
21
21
|
& .crisp-sidebar-label { font-size: ${tokens_1.contract.text.sm}; font-weight: ${tokens_1.contract.weight.medium}; color: ${tokens_1.contract.fgMuted}; padding: ${tokens_1.contract.space["1.5"]} ${tokens_1.contract.space["2"]} ${tokens_1.contract.space["0.5"]}; }
|
|
22
|
-
& .crisp-sidebar-section-header { display: flex; align-items: center;
|
|
22
|
+
& .crisp-sidebar-section-header-row { display: flex; align-items: center; }
|
|
23
|
+
& .crisp-sidebar-section-header { display: flex; align-items: center; gap: ${tokens_1.contract.space["1"]}; flex: 1; min-width: 0; margin: 0; border: none; background: none; padding: ${tokens_1.contract.space["1.5"]} ${tokens_1.contract.space["2"]} ${tokens_1.contract.space["0.5"]}; font-family: ${tokens_1.contract.font.sans}; font-size: ${tokens_1.contract.text.sm}; font-weight: ${tokens_1.contract.weight.medium}; color: ${tokens_1.contract.fgMuted}; cursor: pointer; }
|
|
23
24
|
& .crisp-sidebar-section-header svg { flex-shrink: 0; transition: transform .15s ease; }
|
|
24
25
|
& .crisp-sidebar-section-header[data-collapsed] svg { transform: rotate(-90deg); }
|
|
26
|
+
& .crisp-sidebar-section-actions { display: flex; align-items: center; gap: ${tokens_1.contract.space["1"]}; flex-shrink: 0; padding-inline-end: ${tokens_1.contract.space["1"]}; opacity: 0; transition: opacity .12s ease; }
|
|
27
|
+
& .crisp-sidebar-section-header-row:hover .crisp-sidebar-section-actions, & .crisp-sidebar-section-header-row:focus-within .crisp-sidebar-section-actions { opacity: 1; }
|
|
28
|
+
& .crisp-sidebar-section-action { box-sizing: border-box; display: inline-flex; align-items: center; justify-content: center; width: ${tokens_1.contract.space["5"]}; height: ${tokens_1.contract.space["5"]}; margin: 0; padding: 0; border: none; border-radius: ${tokens_1.contract.radius.sm}; background: transparent; color: color-mix(in srgb, ${tokens_1.contract.fgPrimary} 63%, transparent); font-family: inherit; cursor: pointer; text-decoration: none; transition: background-color 0.14s, color 0.14s; }
|
|
29
|
+
& .crisp-sidebar-section-action:hover { background: ${tokens_1.contract.bgRaised}; color: ${tokens_1.contract.fgPrimary}; }
|
|
25
30
|
& .crisp-sidebar-ws-row { display: flex; align-items: center; gap: ${tokens_1.contract.space["2"]}; height: ${tokens_1.contract.size.header}; padding: 0 ${tokens_1.contract.space["2"]}; }
|
|
26
31
|
& .crisp-sidebar-ws-menuroot { display: flex; flex: 1; min-width: 0; }
|
|
27
32
|
& .crisp-sidebar-ws { box-sizing: border-box; display: flex; align-items: center; gap: ${tokens_1.contract.space["2"]}; flex: 1; min-width: 0; height: 100%; margin: 0; padding: 0; border: none; background: none; border-radius: ${tokens_1.contract.radius.lg}; font-family: inherit; font-size: inherit; color: inherit; text-align: start; cursor: pointer; transition: background-color 0.16s; }
|
|
@@ -20,6 +20,15 @@ exports.palette = {
|
|
|
20
20
|
// off-limits to toggle, same restriction as `ringField`/`ringSwitch`) —
|
|
21
21
|
// reuses `line200`'s dark line colour rather than inventing a new one.
|
|
22
22
|
line150: { light: "#e6e7ea", dark: "#212224" },
|
|
23
|
+
// The filter chip's field/value segments while the condition is
|
|
24
|
+
// incomplete (no value typed yet) — measured live on Attio's own filter
|
|
25
|
+
// chip (`rgb(246,247,247)`), lighter than `gray50` (#f4f4f5, close but not
|
|
26
|
+
// equal). The chip's operator/kebab segments stay `gray00` in the same
|
|
27
|
+
// state; only the two text-content segments carry this muted fill. Dark
|
|
28
|
+
// value not independently re-measured (Attio's dark theme is off-limits to
|
|
29
|
+
// toggle) — reuses `gray50`'s dark value, the nearest existing "muted
|
|
30
|
+
// surface" tone, rather than inventing a third dark gray.
|
|
31
|
+
gray25: { light: "#f6f7f7", dark: "#101112" },
|
|
23
32
|
ringNeutral: { light: "transparent", dark: "#212224" },
|
|
24
33
|
// Text-entry fields (Input, Textarea) — measured live on app.attio.com's
|
|
25
34
|
// Settings page, a real typed field: `inset 0 0 0 1px rgba(0,0,0,.06)`,
|
|
@@ -40,6 +49,22 @@ exports.palette = {
|
|
|
40
49
|
ringSwitch: { light: "#d9dade", dark: "#212224" },
|
|
41
50
|
tileActive: { light: "#ffffff", dark: "#101112" },
|
|
42
51
|
tileActiveShadow: { light: "rgba(0,0,0,0.04)", dark: "rgba(0,0,0,0)" },
|
|
52
|
+
// Dialog's page-dimming backdrop (crisp#677) — measured live behind the
|
|
53
|
+
// Create-attribute FormDialog, both themes: `rgba(0,0,0,.25)` light,
|
|
54
|
+
// `rgba(0,0,0,.55)` dark. `Dialog`'s own backdrop had shipped fully
|
|
55
|
+
// `transparent` (AUTHORING's Lesson 14 measured the command palette has
|
|
56
|
+
// NO dark backdrop and that got generalized to every `Dialog`, which this
|
|
57
|
+
// corrects — the palette is a different surface from a plain content
|
|
58
|
+
// dialog and really does dim the page).
|
|
59
|
+
bgScrim: { light: "rgba(0,0,0,0.25)", dark: "rgba(0,0,0,0.55)" },
|
|
60
|
+
// The discard-confirm's description line (crisp#677, `Dialog`'s new
|
|
61
|
+
// `variant="alert"`) — measured live, both themes, over `popoverBg`: a
|
|
62
|
+
// theme-relative alpha rather than a flat gray, so it stays legible if
|
|
63
|
+
// `popoverBg` itself ever shifts.
|
|
64
|
+
fgAlertDescription: {
|
|
65
|
+
light: "rgba(0,0,0,0.63)",
|
|
66
|
+
dark: "rgba(255,255,255,0.6)",
|
|
67
|
+
},
|
|
43
68
|
overlayFrost: { light: "#ffffff", dark: "#1c1d1f" },
|
|
44
69
|
shadowOverlay1: { light: "rgba(28,40,64,0.12)", dark: "rgba(0,0,0,0.48)" },
|
|
45
70
|
shadowOverlay2: { light: "rgba(28,40,64,0.16)", dark: "rgba(0,0,0,0.64)" },
|
|
@@ -116,7 +141,13 @@ exports.palette = {
|
|
|
116
141
|
// fg-muted/bg-raised themselves (system-wide, measured from Attio, kept
|
|
117
142
|
// as-is per build/spec-check.mjs's own note), this token exists only for
|
|
118
143
|
// Badge's soft-neutral text, so darkening it doesn't move any other surface.
|
|
119
|
-
|
|
144
|
+
// crisp#696: re-measured live (Standard/System pills, object settings) at
|
|
145
|
+
// rgb(80,81,85) -- darker than the #686870 above, which only ever cleared
|
|
146
|
+
// AA, never matched the reference. Only the light side moves: dark
|
|
147
|
+
// (#9a9aa2) already passed and Attio's own dark theme stays off-limits to
|
|
148
|
+
// toggle, the same restriction this file notes throughout (ringField,
|
|
149
|
+
// ringSwitch, line150).
|
|
150
|
+
softNeutralText: { light: "#505155", dark: "#9a9aa2" },
|
|
120
151
|
// Badge's purple/role-pill tone (crisp#506) -- measured live off Attio's
|
|
121
152
|
// own role pill ("Admin", app.attio.com/<ws>/settings/members-and-teams):
|
|
122
153
|
// background rgb(245,240,255), text rgb(98,56,181), ring rgb(232,221,254)
|
|
@@ -11,15 +11,26 @@ exports.semantic = {
|
|
|
11
11
|
fgOnTone: "ink",
|
|
12
12
|
bgSurface: "gray00",
|
|
13
13
|
bgRaised: "gray50",
|
|
14
|
+
/** A surface lighter than `bgRaised` for content that isn't actionable
|
|
15
|
+
* yet — the filter chip's field/value segments before the condition has
|
|
16
|
+
* a value (see palette.ts's `gray25`). */
|
|
17
|
+
bgSurfaceMuted: "gray25",
|
|
14
18
|
borderDefault: "line200",
|
|
15
19
|
borderSubtle: "line100",
|
|
16
20
|
/** ImportDialog's dashed dropzone border — see palette.ts's `line150`. */
|
|
17
21
|
borderDropzone: "line150",
|
|
22
|
+
/** A hairline between adjacent same-height segments (the filter chip's
|
|
23
|
+
* field/operator/value/kebab boxes) — same measured hex as
|
|
24
|
+
* `borderDropzone` (`line150`), aliased under its own name because the
|
|
25
|
+
* two consumers share nothing but the colour. */
|
|
26
|
+
borderDivider: "line150",
|
|
18
27
|
ringNeutral: "ringNeutral",
|
|
19
28
|
ringField: "ringField",
|
|
20
29
|
ringSwitch: "ringSwitch",
|
|
21
30
|
bgTileActive: "tileActive",
|
|
22
31
|
shadowTileActive: "tileActiveShadow",
|
|
32
|
+
bgScrim: "bgScrim",
|
|
33
|
+
fgAlertDescription: "fgAlertDescription",
|
|
23
34
|
bgOverlayFrost: "overlayFrost",
|
|
24
35
|
shadowOverlay1: "shadowOverlay1",
|
|
25
36
|
shadowOverlay2: "shadowOverlay2",
|
package/dist/comments.css
CHANGED
|
@@ -12,11 +12,21 @@
|
|
|
12
12
|
|
|
13
13
|
/* Row badge (CommentCountBadge) -- lives in its own class family so the
|
|
14
14
|
grid lanes can drop it into a cell without pulling in the panel's
|
|
15
|
-
CSS.
|
|
16
|
-
|
|
17
|
-
|
|
15
|
+
CSS. Re-measured live on a real commented row (crisp/#601
|
|
16
|
+
supersession, right-aligned in a DataTable primary column): a 6px
|
|
17
|
+
grey dot at rest (unchanged, already matched); on hover, the trigger
|
|
18
|
+
button is auto-width with a 12px icon + 12px/500 count text,
|
|
19
|
+
"padding: 2px 6px 2px 4px", radius 6px -- distinct from the preview
|
|
20
|
+
button beside it, a fixed 20px square with a 16px icon, same radius.
|
|
21
|
+
Both share the 55%-ink idle colour and darken on their OWN hover
|
|
22
|
+
(color-mix 4%, the same formula DataTable's own #601 icons use). */
|
|
23
|
+
.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-5); }
|
|
18
24
|
.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-
|
|
25
|
+
.crisp-comments-badge-trigger { box-sizing: border-box; display: inline-flex; align-items: center; gap: var(--crisp-space-1); height: 20px; margin: 0; padding: 2px var(--crisp-space-1-5) 2px var(--crisp-space-1); border: none; border-radius: var(--crisp-radius-sm); background: none; font-family: inherit; color: color-mix(in srgb, var(--crisp-fg-primary) 55%, transparent); cursor: pointer; }
|
|
26
|
+
.crisp-comments-badge-trigger:hover { background: color-mix(in srgb, var(--crisp-fg-primary) 4%, transparent); color: var(--crisp-fg-primary); }
|
|
27
|
+
.crisp-comments-badge-preview { box-sizing: border-box; display: inline-flex; align-items: center; justify-content: center; width: 20px; height: 20px; margin: 0; padding: 0; border: none; border-radius: var(--crisp-radius-sm); background: none; font-family: inherit; color: color-mix(in srgb, var(--crisp-fg-primary) 55%, transparent); cursor: pointer; flex-shrink: 0; }
|
|
28
|
+
.crisp-comments-badge-preview:hover { background: color-mix(in srgb, var(--crisp-fg-primary) 4%, transparent); color: var(--crisp-fg-primary); }
|
|
29
|
+
.crisp-comments-badge-count { font-size: 12px; line-height: 16px; font-weight: var(--crisp-weight-medium); }
|
|
20
30
|
[dir="rtl"] .crisp-comments-badge [data-rtl-flip] { transform: scaleX(-1); }
|
|
21
31
|
|
|
22
32
|
.crisp-comments {
|
|
@@ -30,31 +40,50 @@
|
|
|
30
40
|
& .crisp-comments-breadcrumb { display: flex; align-items: center; gap: var(--crisp-space-1); font-size: 12px; font-weight: var(--crisp-weight-medium); color: color-mix(in srgb, var(--crisp-fg-primary) 63%, transparent); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
31
41
|
& .crisp-comments-body { flex: 1; min-height: 0; overflow-y: auto; padding: 0 var(--crisp-space-4); display: flex; flex-direction: column; gap: var(--crisp-space-4); }
|
|
32
42
|
& .crisp-comments-body--pad-top { padding-top: var(--crisp-space-3); }
|
|
33
|
-
/* One comment row -- avatar 16px (Avatar size="sm"), 6px gap to the name/time line, 14px/600 name + 12px/500 63%-muted time, 14px/400 body two lines below. Hover actions fade in flush with the name line.
|
|
43
|
+
/* One comment row -- avatar 16px (Avatar size="sm"), 6px gap to the name/time line, 14px/600 name + 12px/500 63%-muted time, 14px/400 body two lines below. Hover actions fade in flush with the name line. .crisp-comments-row-head's own height: 20px (above) is a single
|
|
44
|
+
text-line's worth, on the same assumption .crisp-comments-title/ .crisp-comments-breadcrumb already act on higher up in this same recipe -- a name that doesn't fit wraps to two lines with nothing
|
|
45
|
+
reserving room for the second one, overflowing straight into the
|
|
46
|
+
body text underneath (found live: this app's own dev-bypass author name, an email long enough to wrap, sat on top of the comment's own
|
|
47
|
+
body text). .crisp-comments-author gets the identical nowrap-
|
|
48
|
+
ellipsis treatment those two already have, plus min-width: 0 so a
|
|
49
|
+
flex item is actually allowed to shrink below its own text's natural width in the first place; .crisp-comments-time gets flex-shrink: 0 so it's the name that gives, never the timestamp -- the same "one
|
|
50
|
+
side shrinks, the other one doesn't" split PersonChip's own name/
|
|
51
|
+
avatar pair already uses (crisp#668/#671). */
|
|
34
52
|
& .crisp-comments-row { position: relative; display: flex; gap: var(--crisp-space-1-5); }
|
|
35
53
|
& .crisp-comments-row-main { flex: 1; min-width: 0; }
|
|
36
54
|
& .crisp-comments-row-head { display: flex; align-items: baseline; gap: var(--crisp-space-1-5); height: 20px; }
|
|
37
|
-
& .crisp-comments-author { font-size: var(--crisp-text-lg); font-weight: var(--crisp-weight-semibold); color: var(--crisp-fg-primary); }
|
|
38
|
-
& .crisp-comments-time { font-size: 12px; font-weight: var(--crisp-weight-medium); color: color-mix(in srgb, var(--crisp-fg-primary) 63%, transparent); white-space: nowrap; }
|
|
55
|
+
& .crisp-comments-author { flex: 1 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: var(--crisp-text-lg); font-weight: var(--crisp-weight-semibold); color: var(--crisp-fg-primary); }
|
|
56
|
+
& .crisp-comments-time { flex-shrink: 0; font-size: 12px; font-weight: var(--crisp-weight-medium); color: color-mix(in srgb, var(--crisp-fg-primary) 63%, transparent); white-space: nowrap; }
|
|
39
57
|
& .crisp-comments-resolved-label { display: flex; align-items: center; gap: var(--crisp-space-1); margin-bottom: var(--crisp-space-1); font-size: 12px; font-weight: var(--crisp-weight-medium); color: color-mix(in srgb, var(--crisp-fg-primary) 63%, transparent); }
|
|
40
58
|
& .crisp-comments-body-text { font-size: var(--crisp-text-lg); line-height: 20px; font-weight: var(--crisp-weight-regular); color: var(--crisp-fg-primary); white-space: pre-wrap; word-break: break-word; }
|
|
41
|
-
|
|
59
|
+
|
|
60
|
+
/* Hover actions -- measured: 4 buttons, 28x28, radius 8, flush
|
|
61
|
+
side-by-side (0 gap), transparent by default, 16px icon at 55% ink. */
|
|
42
62
|
& .crisp-comments-row-actions { position: absolute; top: 0; inset-inline-end: 0; display: flex; opacity: 0; pointer-events: none; background: var(--crisp-bg-surface); }
|
|
43
|
-
& .crisp-comments-row:hover .crisp-comments-row-actions,
|
|
63
|
+
& .crisp-comments-row:hover .crisp-comments-row-actions,
|
|
64
|
+
& .crisp-comments-row-actions:focus-within { opacity: 1; pointer-events: auto; }
|
|
44
65
|
& .crisp-comments-action { display: inline-flex; align-items: center; justify-content: center; width: 28px; height: 28px; margin: 0; padding: 0; border: none; border-radius: var(--crisp-radius-md); background: transparent; color: color-mix(in srgb, var(--crisp-fg-primary) 55%, transparent); font-family: inherit; cursor: pointer; transition: background-color .12s, color .12s; }
|
|
45
66
|
& .crisp-comments-action:hover { background: var(--crisp-bg-raised); color: var(--crisp-fg-primary); }
|
|
46
67
|
& .crisp-comments-action[data-active] { color: var(--crisp-bg-brand-solid); }
|
|
47
|
-
|
|
68
|
+
|
|
69
|
+
/* Reactions -- measured: 28px pill, radius 8, subtle 4% ink fill,
|
|
70
|
+
padding 4px 8px 4px 6px, gap 4px between the emoji glyph and count. */
|
|
48
71
|
& .crisp-comments-reactions { display: flex; flex-wrap: wrap; gap: var(--crisp-space-1); margin-top: var(--crisp-space-1-5); }
|
|
49
72
|
& .crisp-comments-reaction { display: inline-flex; align-items: center; gap: var(--crisp-space-1); height: 28px; padding: 4px 8px 4px 6px; border: none; border-radius: var(--crisp-radius-md); background: color-mix(in srgb, var(--crisp-fg-primary) 4%, transparent); font-family: inherit; font-size: 12px; font-weight: var(--crisp-weight-medium); color: var(--crisp-fg-primary); cursor: pointer; }
|
|
50
73
|
& .crisp-comments-reaction[data-reacted] { background: color-mix(in srgb, var(--crisp-bg-brand-solid) 12%, transparent); color: var(--crisp-bg-brand-solid); }
|
|
51
74
|
& .crisp-comments-reaction-add { display: inline-flex; align-items: center; justify-content: center; width: 28px; height: 28px; border: none; border-radius: var(--crisp-radius-md); background: transparent; font-family: inherit; color: color-mix(in srgb, var(--crisp-fg-primary) 55%, transparent); cursor: pointer; opacity: 0; transition: opacity .12s, background-color .12s; }
|
|
52
75
|
& .crisp-comments-row:hover .crisp-comments-reaction-add { opacity: 1; }
|
|
53
76
|
& .crisp-comments-reaction-add:hover { background: var(--crisp-bg-raised); color: var(--crisp-fg-primary); }
|
|
77
|
+
|
|
54
78
|
/* Reply-in-thread view -- an indented block of replies below the root. */
|
|
55
79
|
& .crisp-comments-replies { display: flex; flex-direction: column; gap: var(--crisp-space-4); margin-top: var(--crisp-space-3); padding-inline-start: var(--crisp-space-4); border-inline-start: 1px solid var(--crisp-border-subtle); }
|
|
80
|
+
|
|
56
81
|
& .crisp-comments-empty { flex: 1; display: flex; flex-direction: column; align-items: center; justify-content: center; gap: var(--crisp-space-1); padding: var(--crisp-space-6) var(--crisp-space-4); text-align: center; color: color-mix(in srgb, var(--crisp-fg-primary) 55%, transparent); font-size: var(--crisp-text-lg); }
|
|
57
|
-
|
|
82
|
+
|
|
83
|
+
/* Composer -- measured: 14px/20px body text, 16px gap above the
|
|
84
|
+
reply/mention icon row, Comment button 84x32 padding 6px 10px radius
|
|
85
|
+
9 (= radius.lg) brand-solid fill, 14px/500 white label, opacity .4
|
|
86
|
+
disabled / 1 enabled (no color/background change between the two). */
|
|
58
87
|
& .crisp-comments-composer { flex-shrink: 0; padding: var(--crisp-space-3) var(--crisp-space-4) var(--crisp-space-4); border-top: 1px solid var(--crisp-border-subtle); }
|
|
59
88
|
& .crisp-comments-field { min-height: 20px; max-height: 120px; overflow-y: auto; font-family: inherit; font-size: var(--crisp-text-lg); line-height: 20px; font-weight: var(--crisp-weight-regular); color: var(--crisp-fg-primary); outline: none; white-space: pre-wrap; word-break: break-word; }
|
|
60
89
|
& .crisp-comments-field:focus-visible { outline: 2px solid var(--crisp-stroke-focus-ring); outline-offset: 2px; }
|
|
@@ -64,15 +93,22 @@
|
|
|
64
93
|
& .crisp-comments-tool { display: inline-flex; align-items: center; justify-content: center; width: 20px; height: 20px; margin: 0; padding: 0; border: none; border-radius: var(--crisp-radius-sm); background: none; color: color-mix(in srgb, var(--crisp-fg-primary) 45%, transparent); font-family: inherit; cursor: pointer; }
|
|
65
94
|
& .crisp-comments-tool:hover { color: var(--crisp-fg-primary); }
|
|
66
95
|
/* Same fill/shadow as Button's own intent="primary" (crisp/button.recipe.ts) --
|
|
67
|
-
confirmed byte-identical against the live "Comment" button's computed
|
|
96
|
+
confirmed byte-identical against the live "Comment" button's computed
|
|
97
|
+
box-shadow, so this reuses that literal rather than re-deriving it.
|
|
98
|
+
32px/radius.lg/6px-10px padding has no matching Button `size` yet
|
|
99
|
+
(24/28/40 are the three that exist), so this stays hand-rolled instead
|
|
100
|
+
of stretching one of those. */
|
|
68
101
|
& .crisp-comments-post { display: inline-flex; align-items: center; justify-content: center; height: 32px; padding: 6px var(--crisp-space-2-5); border: none; border-radius: var(--crisp-radius-lg); background: var(--crisp-bg-brand-solid); box-shadow: inset 0 0 0 1px rgba(0,0,0,0.06), 0 2px 4px -2px rgba(38,109,240,0.12), 0 3px 6px -2px rgba(38,109,240,0.08); font-family: inherit; font-size: var(--crisp-text-lg); font-weight: var(--crisp-weight-semibold); color: var(--crisp-fg-on-brand); cursor: pointer; opacity: 1; transition: opacity .12s, background-color .12s; }
|
|
69
102
|
& .crisp-comments-post:hover:not(:disabled) { background: var(--crisp-bg-brand-solid-pressed); }
|
|
70
103
|
& .crisp-comments-post:disabled { opacity: .4; cursor: default; }
|
|
71
|
-
|
|
104
|
+
|
|
105
|
+
/* Filter menu -- measured: 220px, radius 12 (radius.xl), 4px padding,
|
|
106
|
+
elevation.floating -- a plain reuse of crisp's own Menu, so no rules
|
|
72
107
|
live here; kept as a comment for the record. */
|
|
73
108
|
|
|
74
109
|
/* An inserted "@mention" chip in the composer -- same shape as
|
|
75
|
-
RichTextEditor's own `.crisp-rte-mention` (crisp#428), reused here
|
|
110
|
+
RichTextEditor's own `.crisp-rte-mention` (crisp#428), reused here
|
|
111
|
+
rather than re-derived since it's the same atomic-chip affordance. */
|
|
76
112
|
& .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; }
|
|
77
113
|
[dir="rtl"] & [data-rtl-flip] { transform: scaleX(-1); }
|
|
78
114
|
}
|