@crisp-ui-kit/crisp 0.54.7 → 0.54.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (113) hide show
  1. package/dist/authshell.css +5 -1
  2. package/dist/authshell.layered.css +5 -1
  3. package/dist/badge.css +5 -5
  4. package/dist/badge.layered.css +5 -5
  5. package/dist/board.css +13 -9
  6. package/dist/board.layered.css +13 -9
  7. package/dist/button.css +3 -1
  8. package/dist/button.layered.css +3 -1
  9. package/dist/cjs/components/badge/badge.recipe.js +28 -5
  10. package/dist/cjs/components/button/button.js +33 -2
  11. package/dist/cjs/components/button/button.recipe.js +31 -5
  12. package/dist/cjs/components/datatable/datatable.js +66 -7
  13. package/dist/cjs/components/datatable/datatable.recipe.js +57 -2
  14. package/dist/cjs/components/dialog/dialog.js +8 -5
  15. package/dist/cjs/components/dialog/dialog.recipe.js +15 -1
  16. package/dist/cjs/components/tabletoolbar/tabletoolbar.chrome.js +12 -3
  17. package/dist/cjs/components/tabletoolbar/tabletoolbar.js +231 -48
  18. package/dist/cjs/components/tabletoolbar/tabletoolbar.recipe.js +44 -0
  19. package/dist/cjs/components/tabs/tabs.recipe.js +11 -2
  20. package/dist/cjs/components/tooltip/tooltip.js +23 -9
  21. package/dist/cjs/components/tooltip/tooltip.recipe.js +10 -9
  22. package/dist/cjs/product/authshell/authshell.js +14 -1
  23. package/dist/cjs/product/authshell/authshell.recipe.js +13 -1
  24. package/dist/cjs/product/board/board.js +122 -27
  25. package/dist/cjs/product/board/board.recipe.js +14 -2
  26. package/dist/cjs/product/comments/comments.js +23 -10
  27. package/dist/cjs/product/comments/comments.recipe.js +17 -3
  28. package/dist/cjs/product/formdialog/formdialog.js +10 -4
  29. package/dist/cjs/product/navitem/navitem.js +7 -2
  30. package/dist/cjs/product/navitem/navitem.recipe.js +16 -0
  31. package/dist/cjs/product/objectsettings/index.js +1 -2
  32. package/dist/cjs/product/objectsettings/objectsettings.js +3 -13
  33. package/dist/cjs/product/richtext/richtext.js +76 -1
  34. package/dist/cjs/product/sidebar/sidebar.js +68 -15
  35. package/dist/cjs/product/sidebar/sidebar.recipe.js +6 -1
  36. package/dist/cjs/tokens/palette.js +32 -1
  37. package/dist/cjs/tokens/semantic.js +11 -0
  38. package/dist/comments.css +36 -10
  39. package/dist/comments.layered.css +36 -10
  40. package/dist/datatable.css +47 -9
  41. package/dist/datatable.layered.css +47 -9
  42. package/dist/dialog.css +13 -1
  43. package/dist/dialog.layered.css +13 -1
  44. package/dist/esm/components/badge/badge.recipe.js +28 -5
  45. package/dist/esm/components/button/button.js +34 -3
  46. package/dist/esm/components/button/button.recipe.js +31 -5
  47. package/dist/esm/components/datatable/datatable.js +67 -8
  48. package/dist/esm/components/datatable/datatable.recipe.js +57 -2
  49. package/dist/esm/components/dialog/dialog.js +8 -5
  50. package/dist/esm/components/dialog/dialog.recipe.js +15 -1
  51. package/dist/esm/components/tabletoolbar/tabletoolbar.chrome.js +12 -3
  52. package/dist/esm/components/tabletoolbar/tabletoolbar.js +231 -48
  53. package/dist/esm/components/tabletoolbar/tabletoolbar.recipe.js +44 -0
  54. package/dist/esm/components/tabs/tabs.recipe.js +11 -2
  55. package/dist/esm/components/tooltip/tooltip.js +23 -9
  56. package/dist/esm/components/tooltip/tooltip.recipe.js +10 -9
  57. package/dist/esm/product/authshell/authshell.js +15 -2
  58. package/dist/esm/product/authshell/authshell.recipe.js +13 -1
  59. package/dist/esm/product/authshell/index.js +1 -1
  60. package/dist/esm/product/board/board.js +122 -27
  61. package/dist/esm/product/board/board.recipe.js +14 -2
  62. package/dist/esm/product/comments/comments.js +23 -10
  63. package/dist/esm/product/comments/comments.recipe.js +17 -3
  64. package/dist/esm/product/formdialog/formdialog.js +10 -4
  65. package/dist/esm/product/navitem/index.js +1 -1
  66. package/dist/esm/product/navitem/navitem.js +7 -2
  67. package/dist/esm/product/navitem/navitem.recipe.js +16 -0
  68. package/dist/esm/product/objectsettings/index.js +1 -1
  69. package/dist/esm/product/objectsettings/objectsettings.js +2 -12
  70. package/dist/esm/product/richtext/richtext.js +76 -1
  71. package/dist/esm/product/sidebar/sidebar.js +69 -16
  72. package/dist/esm/product/sidebar/sidebar.recipe.js +6 -1
  73. package/dist/esm/tokens/palette.js +32 -1
  74. package/dist/esm/tokens/semantic.js +11 -0
  75. package/dist/native/tokens.d.ts +1 -1
  76. package/dist/native/tokens.js +16 -2
  77. package/dist/navitem.css +7 -0
  78. package/dist/navitem.layered.css +7 -0
  79. package/dist/recordstable.css +1 -1
  80. package/dist/recordstable.layered.css +1 -1
  81. package/dist/sidebar.css +6 -1
  82. package/dist/sidebar.layered.css +6 -1
  83. package/dist/styles.css +163 -42
  84. package/dist/styles.layered.css +163 -42
  85. package/dist/tabletoolbar.css +16 -1
  86. package/dist/tabletoolbar.layered.css +16 -1
  87. package/dist/tabs.css +1 -1
  88. package/dist/tabs.layered.css +1 -1
  89. package/dist/tokens.css +9 -1
  90. package/dist/tokens.json +16 -2
  91. package/dist/tooltip.css +1 -1
  92. package/dist/tooltip.layered.css +1 -1
  93. package/dist/types/components/badge/badge.recipe.d.ts +5 -1
  94. package/dist/types/components/button/button.recipe.d.ts +18 -5
  95. package/dist/types/components/datatable/datatable.d.ts +9 -0
  96. package/dist/types/components/dialog/dialog.d.ts +25 -2
  97. package/dist/types/components/tabletoolbar/tabletoolbar.d.ts +43 -3
  98. package/dist/types/components/tooltip/tooltip.d.ts +12 -4
  99. package/dist/types/components/tooltip/tooltip.recipe.d.ts +1 -1
  100. package/dist/types/product/authshell/authshell.d.ts +26 -2
  101. package/dist/types/product/authshell/index.d.ts +1 -1
  102. package/dist/types/product/board/board.d.ts +5 -4
  103. package/dist/types/product/comments/comments.d.ts +10 -2
  104. package/dist/types/product/formdialog/formdialog.d.ts +2 -1
  105. package/dist/types/product/navitem/index.d.ts +1 -1
  106. package/dist/types/product/navitem/navitem.d.ts +23 -0
  107. package/dist/types/product/objectsettings/index.d.ts +1 -1
  108. package/dist/types/product/objectsettings/objectsettings.d.ts +0 -10
  109. package/dist/types/product/sidebar/sidebar.d.ts +33 -5
  110. package/dist/types/tokens/contract.d.ts +4 -0
  111. package/dist/types/tokens/palette.d.ts +13 -1
  112. package/dist/types/tokens/semantic.d.ts +11 -0
  113. package/package.json +1 -1
@@ -70,6 +70,71 @@ function matchSlashTrigger(textBeforeCaret, trigger = "/") {
70
70
  return { query, start };
71
71
  }
72
72
  const exec = (cmd, value) => document.execCommand(cmd, false, value);
73
+ /**
74
+ * #685: `execCommand("insertUnorderedList"/"insertOrderedList")` is built
75
+ * through DOM APIs, not the HTML parser, so it can leave the live DOM in a
76
+ * shape the parser itself would never produce: a `<ul>`/`<ol>` as a literal
77
+ * child of the `<p>` it converted, e.g. `<p><ul><li>text</li></ul></p>`.
78
+ * That's invalid content for a `<p>` — reading `innerHTML` back and later
79
+ * re-parsing it (a fresh mount, GRC reloading a stored note) auto-closes the
80
+ * `<p>` the instant the parser meets the `<ul>`, which leaves a now-empty
81
+ * `<p></p>` sitting next to the list instead. Two different malformed
82
+ * shapes, one root cause, so one function fixes both: unwrap a `<ul>`/`<ol>`
83
+ * that's still literally nested inside a `<p>` (the freshly-converted case),
84
+ * and drop an empty `<p>` that ended up glued to a list (the reloaded-old-
85
+ * content case) — matching Attio's own structure, where the list is always
86
+ * a sibling of the surrounding `<p>` blocks, never nested in or padded by
87
+ * one.
88
+ */
89
+ const normalizeListStructure = (root) => {
90
+ for (const list of root.querySelectorAll("p > ul, p > ol")) {
91
+ const p = list.parentElement;
92
+ if (!p)
93
+ continue;
94
+ const before = [];
95
+ const after = [];
96
+ let seenList = false;
97
+ for (const child of Array.from(p.childNodes)) {
98
+ if (child === list) {
99
+ seenList = true;
100
+ }
101
+ else if (seenList) {
102
+ after.push(child);
103
+ }
104
+ else {
105
+ before.push(child);
106
+ }
107
+ }
108
+ const isBlank = (nodes) => nodes.every((n) => (n.textContent ?? "").trim() === "");
109
+ const parent = p.parentElement;
110
+ if (!parent)
111
+ continue;
112
+ if (!isBlank(before)) {
113
+ const leading = document.createElement("p");
114
+ for (const n of before)
115
+ leading.appendChild(n);
116
+ parent.insertBefore(leading, p);
117
+ }
118
+ parent.insertBefore(list, p);
119
+ if (!isBlank(after)) {
120
+ const trailing = document.createElement("p");
121
+ for (const n of after)
122
+ trailing.appendChild(n);
123
+ parent.insertBefore(trailing, p);
124
+ }
125
+ parent.removeChild(p);
126
+ }
127
+ for (const list of root.querySelectorAll("ul, ol")) {
128
+ const isEmptyP = (el) => !!el &&
129
+ el.tagName === "P" &&
130
+ el.children.length === 0 &&
131
+ (el.textContent ?? "").trim() === "";
132
+ if (isEmptyP(list.previousElementSibling))
133
+ list.previousElementSibling?.remove();
134
+ if (isEmptyP(list.nextElementSibling))
135
+ list.nextElementSibling?.remove();
136
+ }
137
+ };
73
138
  /**
74
139
  * A block rich-text editor — measured from Attio's own notes editor. A large
75
140
  * title, a prose body (h1/h2/h3, bulleted/numbered lists, an image block,
@@ -181,8 +246,14 @@ export const RichTextEditor = React.forwardRef(function RichTextEditor({ default
181
246
  React.useEffect(() => {
182
247
  if (titleRef.current)
183
248
  titleRef.current.textContent = defaultTitle;
184
- if (bodyRef.current)
249
+ if (bodyRef.current) {
185
250
  bodyRef.current.innerHTML = defaultHtml;
251
+ // #685: normalize any pre-existing `<p><ul>` nesting (or its
252
+ // reloaded shape, an empty `<p>` glued to the list) from content
253
+ // stored before the fix, so it renders correctly instead of staying
254
+ // broken forever.
255
+ normalizeListStructure(bodyRef.current);
256
+ }
186
257
  syncEmpty(titleRef.current);
187
258
  syncEmpty(bodyRef.current);
188
259
  }, []);
@@ -371,6 +442,10 @@ export const RichTextEditor = React.forwardRef(function RichTextEditor({ default
371
442
  const run = (cmd, value) => {
372
443
  bodyRef.current?.focus();
373
444
  exec(cmd, value);
445
+ // #685: insertUnorderedList/insertOrderedList can leave the new list
446
+ // nested inside the `<p>` it converted instead of replacing it.
447
+ if (bodyRef.current)
448
+ normalizeListStructure(bodyRef.current);
374
449
  updateBar();
375
450
  syncEmpty(bodyRef.current);
376
451
  emit();
@@ -1,5 +1,5 @@
1
1
  import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { Building2, ChartNoAxesColumn, ChevronDown, CircleDollarSign, FileText, House, Inbox, LayoutGrid, Mail, MessageSquare, PanelLeft, Phone, Search, Send, SquareCheck, User, UserPlus, Users, Waypoints, } from "lucide-react";
2
+ import { Building2, ChartNoAxesColumn, ChevronDown, CircleDollarSign, FileText, House, Inbox, LayoutGrid, Mail, MessageSquare, PackagePlus, PanelLeft, Phone, Search, Send, Settings, SquareCheck, User, UserPlus, Users, Waypoints, } from "lucide-react";
3
3
  import * as React from "react";
4
4
  import { Avatar } from "../../components/avatar/index.js";
5
5
  import { Badge } from "../../components/badge/index.js";
@@ -44,42 +44,83 @@ export const ATTIO_SIDEBAR_WORKSPACE = {
44
44
  avatarLabel: "T",
45
45
  avatarTone: "cyan",
46
46
  };
47
- /** The measured main nav — the default `items` prop. */
47
+ /** The measured main nav — the default `items` prop.
48
+ *
49
+ * `shortcut` is set only where a live hover produced Attio's "G then
50
+ * <letter>" tooltip (crisp#633, P-108) -- Calls/Sequences/Workflows showed
51
+ * no tooltip at all after a 1.2s hover, tried repeatedly, so they're left
52
+ * without one rather than guessed. */
48
53
  export const ATTIO_SIDEBAR_ITEMS = [
49
- { label: "Home", icon: IconHome },
54
+ {
55
+ label: "Home",
56
+ icon: IconHome,
57
+ shortcut: { description: "Go to home page", keys: ["g", "h"] },
58
+ },
50
59
  {
51
60
  label: "Tasks",
52
61
  icon: IconTasks,
53
62
  active: true,
54
63
  trailing: (_jsx(Badge, { tone: "brand", variant: "solid", size: "small", children: "1" })),
64
+ shortcut: { description: "Go to tasks page", keys: ["g", "t"] },
65
+ },
66
+ {
67
+ label: "Notes",
68
+ icon: IconNotes,
69
+ shortcut: { description: "Go to all notes page", keys: ["g", "n"] },
70
+ },
71
+ {
72
+ label: "Emails",
73
+ icon: IconEmails,
74
+ shortcut: { description: "Go to email drafts page", keys: ["g", "e"] },
55
75
  },
56
- { label: "Notes", icon: IconNotes },
57
- { label: "Emails", icon: IconEmails },
58
76
  { label: "Calls", icon: IconCalls },
59
- { label: "Reports", icon: IconReports },
77
+ {
78
+ label: "Reports",
79
+ icon: IconReports,
80
+ shortcut: { description: "Go to reports page", keys: ["g", "d"] },
81
+ },
60
82
  { label: "Sequences", icon: IconSequences },
61
83
  { label: "Workflows", icon: IconWorkflows },
62
84
  ];
63
- /** The measured Records section — the default `recordsSection` prop. */
85
+ /** The measured Records section — the default `recordsSection` prop. Same
86
+ * `shortcut` caveat as `ATTIO_SIDEBAR_ITEMS`: Deals/Users/Workspaces showed
87
+ * no hover tooltip. `actions` is Attio's own gear + add glyph (P-109),
88
+ * measured aria-labels "Record options" / "Create new objects or enable
89
+ * standard objects". */
64
90
  export const ATTIO_SIDEBAR_RECORDS_SECTION = {
65
91
  label: "Records",
66
92
  items: [
67
- { label: "Companies", tone: "brand", glyph: GlyphCompanies },
68
- { label: "People", tone: "brand", glyph: GlyphPeople },
93
+ {
94
+ label: "Companies",
95
+ tone: "brand",
96
+ glyph: GlyphCompanies,
97
+ shortcut: { description: "Go to all companies page", keys: ["g", "c"] },
98
+ },
99
+ {
100
+ label: "People",
101
+ tone: "brand",
102
+ glyph: GlyphPeople,
103
+ shortcut: { description: "Go to all people page", keys: ["g", "p"] },
104
+ },
69
105
  { label: "Deals", tone: "orange", glyph: GlyphDeals },
70
106
  { label: "Users", tone: "cyan", glyph: GlyphUsers },
71
107
  { label: "Workspaces", tone: "purple", glyph: GlyphWorkspaces },
72
108
  ],
109
+ actions: defaultSectionActions("Record options", "Create new objects or enable standard objects"),
73
110
  };
74
- /** The measured Lists section — the default `listsSection` prop. */
111
+ /** The measured Lists section — the default `listsSection` prop. `actions`
112
+ * per P-109, measured aria-labels "List options" / "Add list". */
75
113
  export const ATTIO_SIDEBAR_LISTS_SECTION = {
76
114
  label: "Lists",
77
115
  newListLabel: "+ New list",
116
+ actions: defaultSectionActions("List options", "Add list"),
78
117
  };
79
- /** The measured Chats section — the default `chatsSection` prop. */
118
+ /** The measured Chats section — the default `chatsSection` prop. `actions`
119
+ * per P-109, measured aria-labels "Chat options" / "New chat". */
80
120
  export const ATTIO_SIDEBAR_CHATS_SECTION = {
81
121
  label: "Chats",
82
122
  items: [{ label: "In-app notification for deal wins", icon: IconChat }],
123
+ actions: defaultSectionActions("Chat options", "New chat"),
83
124
  };
84
125
  /** The measured footer copy — the default `footer` prop. */
85
126
  export const ATTIO_SIDEBAR_FOOTER = {
@@ -88,8 +129,20 @@ export const ATTIO_SIDEBAR_FOOTER = {
88
129
  trialText: "13 days left on trial!",
89
130
  trialActionLabel: "Keep Pro",
90
131
  };
91
- function SectionHeader({ label, open, onToggle, }) {
92
- return (_jsxs("button", { type: "button", className: "crisp-sidebar-section-header", "data-collapsed": open ? undefined : "", "aria-expanded": open, onClick: onToggle, children: [_jsx(IconChevronMini, {}), _jsx("span", { children: label })] }));
132
+ function SectionHeader({ label, open, onToggle, actions, }) {
133
+ return (_jsxs("div", { className: "crisp-sidebar-section-header-row", children: [_jsxs("button", { type: "button", className: "crisp-sidebar-section-header", "data-collapsed": open ? undefined : "", "aria-expanded": open, onClick: onToggle, children: [_jsx(IconChevronMini, {}), _jsx("span", { children: label })] }), actions != null && (_jsx("div", { className: "crisp-sidebar-section-actions", children: actions }))] }));
134
+ }
135
+ /** Attio's own two hover-only header actions (crisp#633, P-109), measured on
136
+ * the Records/Lists/Chats section headers: a gear ("<Section> options") and
137
+ * an add glyph -- re-measured on all three, the add icon is the SAME glyph
138
+ * (`icon-defs/cab3218f7a08cd13.svg`) reused everywhere, not a per-section
139
+ * one, so `PackagePlus` covers Lists/Chats too, not just Records' own
140
+ * "Create new objects" link. Both are decorative defaults with no handler,
141
+ * the same trade a consumer already makes with `footer`'s default
142
+ * "Getting started"/"Keep Pro" buttons — a consumer wanting the real
143
+ * behaviour passes its own `actions` node instead. */
144
+ function defaultSectionActions(optionsLabel, addLabel) {
145
+ return (_jsxs(_Fragment, { children: [_jsx("button", { type: "button", className: "crisp-sidebar-section-action", "aria-label": optionsLabel, children: _jsx(Settings, { size: 16, strokeWidth: 1.75, "aria-hidden": "true" }) }), _jsx("button", { type: "button", className: "crisp-sidebar-section-action", "aria-label": addLabel, children: _jsx(PackagePlus, { size: 16, strokeWidth: 1.75, "aria-hidden": "true" }) })] }));
93
146
  }
94
147
  export const Sidebar = React.forwardRef(({ className, asChild, onToggleCollapse, collapseLabel = "Collapse sidebar", inboxLabel = "Inbox", searchLabel = "Search", workspace = ATTIO_SIDEBAR_WORKSPACE, workspaceMenu, items = ATTIO_SIDEBAR_ITEMS, recordsSection = ATTIO_SIDEBAR_RECORDS_SECTION, listsSection = ATTIO_SIDEBAR_LISTS_SECTION, chatsSection = ATTIO_SIDEBAR_CHATS_SECTION, groups, footer = ATTIO_SIDEBAR_FOOTER, commandItems, commandOpen: commandOpenProp, defaultCommandOpen, onCommandOpenChange, commandShortcut = true, "aria-label": ariaLabel = "Main", ...props }, ref) => {
95
148
  // Seeded once, on mount, from whatever groups start collapsed — not
@@ -138,14 +191,14 @@ export const Sidebar = React.forwardRef(({ className, asChild, onToggleCollapse,
138
191
  });
139
192
  }, []);
140
193
  const workspaceTrigger = (_jsxs("button", { type: "button", className: "crisp-sidebar-ws", children: [_jsx(Avatar, { size: "lg", shape: "rounded", tone: workspace.avatarTone, children: workspace.avatarLabel }), _jsx("span", { className: "crisp-sidebar-ws-name", children: workspace.name }), _jsx(IconChevronDown, {})] }));
141
- return (_jsxs(Primitive.nav, { ref: ref, asChild: asChild, "aria-label": ariaLabel, className: sidebarRecipe({ className }), ...props, children: [_jsxs("div", { className: "crisp-sidebar-ws-row", children: [workspaceMenu ? (_jsx(Menu, { trigger: workspaceTrigger, className: "crisp-sidebar-ws-menuroot", contentClassName: "crisp-sidebar-ws-menu-content", children: workspaceMenu })) : (workspaceTrigger), _jsx(Button, { intent: "ghost", size: "sm", shape: "icon", className: "crisp-sidebar-ws-collapse", "aria-label": collapseLabel, onClick: onToggleCollapse, children: _jsx(IconCollapsePanel, {}) })] }), _jsxs("div", { className: "crisp-sidebar-searchrow", children: [_jsxs("button", { type: "button", className: "crisp-sidebar-search", onClick: () => setCommandOpen(true), children: [_jsx(IconSearch, {}), _jsx("span", { className: "crisp-sidebar-search-label", children: searchLabel }), _jsx("span", { className: "crisp-sidebar-search-kbd", children: "\u2318K" })] }), _jsx(Button, { intent: "neutral", size: "sm", shape: "icon", "aria-label": inboxLabel, children: _jsx(IconInbox, {}) })] }), _jsx("div", { className: "crisp-sidebar-divider" }), _jsx("div", { className: "crisp-sidebar-section", children: items.map(({ label, icon: ItemIcon, active, trailing, href, onSelect }) => (_jsx(NavItem, { href: href ?? "#", active: active, icon: _jsx(ItemIcon, {}), trailing: trailing, onClick: onSelect, children: label }, href ?? label))) }), groups ? (
194
+ return (_jsxs(Primitive.nav, { ref: ref, asChild: asChild, "aria-label": ariaLabel, className: sidebarRecipe({ className }), ...props, children: [_jsxs("div", { className: "crisp-sidebar-ws-row", children: [workspaceMenu ? (_jsx(Menu, { trigger: workspaceTrigger, className: "crisp-sidebar-ws-menuroot", contentClassName: "crisp-sidebar-ws-menu-content", children: workspaceMenu })) : (workspaceTrigger), _jsx(Button, { intent: "ghost", size: "sm", shape: "icon", className: "crisp-sidebar-ws-collapse", "aria-label": collapseLabel, onClick: onToggleCollapse, children: _jsx(IconCollapsePanel, {}) })] }), _jsxs("div", { className: "crisp-sidebar-searchrow", children: [_jsxs("button", { type: "button", className: "crisp-sidebar-search", onClick: () => setCommandOpen(true), children: [_jsx(IconSearch, {}), _jsx("span", { className: "crisp-sidebar-search-label", children: searchLabel }), _jsx("span", { className: "crisp-sidebar-search-kbd", children: "\u2318K" })] }), _jsx(Button, { intent: "neutral", size: "sm", shape: "icon", "aria-label": inboxLabel, children: _jsx(IconInbox, {}) })] }), _jsx("div", { className: "crisp-sidebar-divider" }), _jsx("div", { className: "crisp-sidebar-section", children: items.map(({ label, icon: ItemIcon, active, trailing, href, onSelect, shortcut, }) => (_jsx(NavItem, { href: href ?? "#", active: active, icon: _jsx(ItemIcon, {}), trailing: trailing, onClick: onSelect, shortcut: shortcut, children: label }, href ?? label))) }), groups ? (
142
195
  // #325: navigation as data, reusing the exact section markup/
143
196
  // recipe above — SectionHeader + .crisp-sidebar-section — instead
144
197
  // of the three fixed Attio sections.
145
198
  groups.map((group) => {
146
199
  const groupKey = group.key ?? group.label;
147
- return (_jsxs(React.Fragment, { children: [_jsx(SectionHeader, { label: group.label, open: !collapsedSections.has(groupKey), onToggle: () => toggleSection(groupKey) }), _jsx("div", { className: "crisp-sidebar-section", "data-collapsed": collapsedSections.has(groupKey) ? "" : undefined, children: group.items.map((item) => (_jsx(NavItem, { href: item.href ?? "#", active: item.active, icon: item.icon ? _jsx(item.icon, {}) : undefined, trailing: item.count, onClick: item.onSelect, children: item.label }, item.href ?? item.label))) })] }, groupKey));
148
- })) : (_jsxs(_Fragment, { children: [_jsx(SectionHeader, { label: recordsSection.label, open: !collapsedSections.has("records"), onToggle: () => toggleSection("records") }), _jsx("div", { className: "crisp-sidebar-section", "data-collapsed": collapsedSections.has("records") ? "" : undefined, children: recordsSection.items.map(({ label, tone, glyph: Glyph }) => (_jsx(NavItem, { href: "#", icon: _jsx(Avatar, { size: "sm", shape: "rounded", tone: tone, children: _jsx(Glyph, {}) }), children: label }, label))) }), _jsx(SectionHeader, { label: listsSection.label, open: !collapsedSections.has("lists"), onToggle: () => toggleSection("lists") }), _jsx("div", { className: "crisp-sidebar-section", "data-collapsed": collapsedSections.has("lists") ? "" : undefined, children: _jsx("button", { type: "button", className: "crisp-sidebar-newlist", children: listsSection.newListLabel }) }), _jsx(SectionHeader, { label: chatsSection.label, open: !collapsedSections.has("chats"), onToggle: () => toggleSection("chats") }), _jsx("div", { className: "crisp-sidebar-section", "data-collapsed": collapsedSections.has("chats") ? "" : undefined, children: chatsSection.items.map(({ label, icon: ItemIcon, active, trailing, href, onSelect, }) => (_jsx(NavItem, { href: href ?? "#", active: active, icon: _jsx(ItemIcon, {}), trailing: trailing, onClick: onSelect, children: label }, href ?? label))) })] })), footer !== false &&
200
+ return (_jsxs(React.Fragment, { children: [_jsx(SectionHeader, { label: group.label, open: !collapsedSections.has(groupKey), onToggle: () => toggleSection(groupKey), actions: group.actions }), _jsx("div", { className: "crisp-sidebar-section", "data-collapsed": collapsedSections.has(groupKey) ? "" : undefined, children: group.items.map((item) => (_jsx(NavItem, { href: item.href ?? "#", active: item.active, icon: item.icon ? _jsx(item.icon, {}) : undefined, trailing: item.count, onClick: item.onSelect, shortcut: item.shortcut, children: item.label }, item.href ?? item.label))) })] }, groupKey));
201
+ })) : (_jsxs(_Fragment, { children: [_jsx(SectionHeader, { label: recordsSection.label, open: !collapsedSections.has("records"), onToggle: () => toggleSection("records"), actions: recordsSection.actions }), _jsx("div", { className: "crisp-sidebar-section", "data-collapsed": collapsedSections.has("records") ? "" : undefined, children: recordsSection.items.map(({ label, tone, glyph: Glyph, shortcut }) => (_jsx(NavItem, { href: "#", icon: _jsx(Avatar, { size: "sm", shape: "rounded", tone: tone, children: _jsx(Glyph, {}) }), shortcut: shortcut, children: label }, label))) }), _jsx(SectionHeader, { label: listsSection.label, open: !collapsedSections.has("lists"), onToggle: () => toggleSection("lists"), actions: listsSection.actions }), _jsx("div", { className: "crisp-sidebar-section", "data-collapsed": collapsedSections.has("lists") ? "" : undefined, children: _jsx("button", { type: "button", className: "crisp-sidebar-newlist", children: listsSection.newListLabel }) }), _jsx(SectionHeader, { label: chatsSection.label, open: !collapsedSections.has("chats"), onToggle: () => toggleSection("chats"), actions: chatsSection.actions }), _jsx("div", { className: "crisp-sidebar-section", "data-collapsed": collapsedSections.has("chats") ? "" : undefined, children: chatsSection.items.map(({ label, icon: ItemIcon, active, trailing, href, onSelect, shortcut, }) => (_jsx(NavItem, { href: href ?? "#", active: active, icon: _jsx(ItemIcon, {}), trailing: trailing, onClick: onSelect, shortcut: shortcut, children: label }, href ?? label))) })] })), footer !== false &&
149
202
  (isSidebarFooterCopy(footer) ? (_jsxs("div", { className: "crisp-sidebar-footer", children: [_jsx(Button, { intent: "neutral", size: "sm", className: "crisp-sidebar-pill", children: footer.gettingStartedLabel }), _jsx(NavItem, { href: "#", icon: _jsx(IconInvite, {}), children: footer.inviteLabel }), _jsx("div", { className: "crisp-sidebar-divider" }), _jsxs("div", { className: "crisp-sidebar-trial", children: [_jsx("span", { children: footer.trialText }), _jsx(Button, { intent: "neutral", size: "sm", children: footer.trialActionLabel })] })] })) : (_jsx("div", { className: "crisp-sidebar-footer", children: footer }))), _jsx(Command, { open: commandOpen, onOpenChange: setCommandOpen, items: commandItems })] }));
150
203
  });
151
204
  Sidebar.displayName = "Sidebar";
@@ -16,9 +16,14 @@ const config = defineRecipe({
16
16
  ; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; overflow-y: auto; & .crisp-sidebar-section { display: flex; flex-direction: column; gap: ${c.space.px}; margin-top: ${c.space["2"]}; }
17
17
  & .crisp-sidebar-section[data-collapsed] { display: none; }
18
18
  & .crisp-sidebar-label { font-size: ${c.text.sm}; font-weight: ${c.weight.medium}; color: ${c.fgMuted}; padding: ${c.space["1.5"]} ${c.space["2"]} ${c.space["0.5"]}; }
19
- & .crisp-sidebar-section-header { display: flex; align-items: center; gap: ${c.space["1"]}; width: 100%; margin: 0; border: none; background: none; padding: ${c.space["1.5"]} ${c.space["2"]} ${c.space["0.5"]}; font-family: ${c.font.sans}; font-size: ${c.text.sm}; font-weight: ${c.weight.medium}; color: ${c.fgMuted}; cursor: pointer; }
19
+ & .crisp-sidebar-section-header-row { display: flex; align-items: center; }
20
+ & .crisp-sidebar-section-header { display: flex; align-items: center; gap: ${c.space["1"]}; flex: 1; min-width: 0; margin: 0; border: none; background: none; padding: ${c.space["1.5"]} ${c.space["2"]} ${c.space["0.5"]}; font-family: ${c.font.sans}; font-size: ${c.text.sm}; font-weight: ${c.weight.medium}; color: ${c.fgMuted}; cursor: pointer; }
20
21
  & .crisp-sidebar-section-header svg { flex-shrink: 0; transition: transform .15s ease; }
21
22
  & .crisp-sidebar-section-header[data-collapsed] svg { transform: rotate(-90deg); }
23
+ & .crisp-sidebar-section-actions { display: flex; align-items: center; gap: ${c.space["1"]}; flex-shrink: 0; padding-inline-end: ${c.space["1"]}; opacity: 0; transition: opacity .12s ease; }
24
+ & .crisp-sidebar-section-header-row:hover .crisp-sidebar-section-actions, & .crisp-sidebar-section-header-row:focus-within .crisp-sidebar-section-actions { opacity: 1; }
25
+ & .crisp-sidebar-section-action { box-sizing: border-box; display: inline-flex; align-items: center; justify-content: center; width: ${c.space["5"]}; height: ${c.space["5"]}; margin: 0; padding: 0; border: none; border-radius: ${c.radius.sm}; background: transparent; color: color-mix(in srgb, ${c.fgPrimary} 63%, transparent); font-family: inherit; cursor: pointer; text-decoration: none; transition: background-color 0.14s, color 0.14s; }
26
+ & .crisp-sidebar-section-action:hover { background: ${c.bgRaised}; color: ${c.fgPrimary}; }
22
27
  & .crisp-sidebar-ws-row { display: flex; align-items: center; gap: ${c.space["2"]}; height: ${c.size.header}; padding: 0 ${c.space["2"]}; }
23
28
  & .crisp-sidebar-ws-menuroot { display: flex; flex: 1; min-width: 0; }
24
29
  & .crisp-sidebar-ws { box-sizing: border-box; display: flex; align-items: center; gap: ${c.space["2"]}; flex: 1; min-width: 0; height: 100%; margin: 0; padding: 0; border: none; background: none; border-radius: ${c.radius.lg}; font-family: inherit; font-size: inherit; color: inherit; text-align: start; cursor: pointer; transition: background-color 0.16s; }
@@ -17,6 +17,15 @@ export const palette = {
17
17
  // off-limits to toggle, same restriction as `ringField`/`ringSwitch`) —
18
18
  // reuses `line200`'s dark line colour rather than inventing a new one.
19
19
  line150: { light: "#e6e7ea", dark: "#212224" },
20
+ // The filter chip's field/value segments while the condition is
21
+ // incomplete (no value typed yet) — measured live on Attio's own filter
22
+ // chip (`rgb(246,247,247)`), lighter than `gray50` (#f4f4f5, close but not
23
+ // equal). The chip's operator/kebab segments stay `gray00` in the same
24
+ // state; only the two text-content segments carry this muted fill. Dark
25
+ // value not independently re-measured (Attio's dark theme is off-limits to
26
+ // toggle) — reuses `gray50`'s dark value, the nearest existing "muted
27
+ // surface" tone, rather than inventing a third dark gray.
28
+ gray25: { light: "#f6f7f7", dark: "#101112" },
20
29
  ringNeutral: { light: "transparent", dark: "#212224" },
21
30
  // Text-entry fields (Input, Textarea) — measured live on app.attio.com's
22
31
  // Settings page, a real typed field: `inset 0 0 0 1px rgba(0,0,0,.06)`,
@@ -37,6 +46,22 @@ export const palette = {
37
46
  ringSwitch: { light: "#d9dade", dark: "#212224" },
38
47
  tileActive: { light: "#ffffff", dark: "#101112" },
39
48
  tileActiveShadow: { light: "rgba(0,0,0,0.04)", dark: "rgba(0,0,0,0)" },
49
+ // Dialog's page-dimming backdrop (crisp#677) — measured live behind the
50
+ // Create-attribute FormDialog, both themes: `rgba(0,0,0,.25)` light,
51
+ // `rgba(0,0,0,.55)` dark. `Dialog`'s own backdrop had shipped fully
52
+ // `transparent` (AUTHORING's Lesson 14 measured the command palette has
53
+ // NO dark backdrop and that got generalized to every `Dialog`, which this
54
+ // corrects — the palette is a different surface from a plain content
55
+ // dialog and really does dim the page).
56
+ bgScrim: { light: "rgba(0,0,0,0.25)", dark: "rgba(0,0,0,0.55)" },
57
+ // The discard-confirm's description line (crisp#677, `Dialog`'s new
58
+ // `variant="alert"`) — measured live, both themes, over `popoverBg`: a
59
+ // theme-relative alpha rather than a flat gray, so it stays legible if
60
+ // `popoverBg` itself ever shifts.
61
+ fgAlertDescription: {
62
+ light: "rgba(0,0,0,0.63)",
63
+ dark: "rgba(255,255,255,0.6)",
64
+ },
40
65
  overlayFrost: { light: "#ffffff", dark: "#1c1d1f" },
41
66
  shadowOverlay1: { light: "rgba(28,40,64,0.12)", dark: "rgba(0,0,0,0.48)" },
42
67
  shadowOverlay2: { light: "rgba(28,40,64,0.16)", dark: "rgba(0,0,0,0.64)" },
@@ -113,7 +138,13 @@ export const palette = {
113
138
  // fg-muted/bg-raised themselves (system-wide, measured from Attio, kept
114
139
  // as-is per build/spec-check.mjs's own note), this token exists only for
115
140
  // Badge's soft-neutral text, so darkening it doesn't move any other surface.
116
- softNeutralText: { light: "#686870", dark: "#9a9aa2" },
141
+ // crisp#696: re-measured live (Standard/System pills, object settings) at
142
+ // rgb(80,81,85) -- darker than the #686870 above, which only ever cleared
143
+ // AA, never matched the reference. Only the light side moves: dark
144
+ // (#9a9aa2) already passed and Attio's own dark theme stays off-limits to
145
+ // toggle, the same restriction this file notes throughout (ringField,
146
+ // ringSwitch, line150).
147
+ softNeutralText: { light: "#505155", dark: "#9a9aa2" },
117
148
  // Badge's purple/role-pill tone (crisp#506) -- measured live off Attio's
118
149
  // own role pill ("Admin", app.attio.com/<ws>/settings/members-and-teams):
119
150
  // background rgb(245,240,255), text rgb(98,56,181), ring rgb(232,221,254)
@@ -8,15 +8,26 @@ export const semantic = {
8
8
  fgOnTone: "ink",
9
9
  bgSurface: "gray00",
10
10
  bgRaised: "gray50",
11
+ /** A surface lighter than `bgRaised` for content that isn't actionable
12
+ * yet — the filter chip's field/value segments before the condition has
13
+ * a value (see palette.ts's `gray25`). */
14
+ bgSurfaceMuted: "gray25",
11
15
  borderDefault: "line200",
12
16
  borderSubtle: "line100",
13
17
  /** ImportDialog's dashed dropzone border — see palette.ts's `line150`. */
14
18
  borderDropzone: "line150",
19
+ /** A hairline between adjacent same-height segments (the filter chip's
20
+ * field/operator/value/kebab boxes) — same measured hex as
21
+ * `borderDropzone` (`line150`), aliased under its own name because the
22
+ * two consumers share nothing but the colour. */
23
+ borderDivider: "line150",
15
24
  ringNeutral: "ringNeutral",
16
25
  ringField: "ringField",
17
26
  ringSwitch: "ringSwitch",
18
27
  bgTileActive: "tileActive",
19
28
  shadowTileActive: "tileActiveShadow",
29
+ bgScrim: "bgScrim",
30
+ fgAlertDescription: "fgAlertDescription",
20
31
  bgOverlayFrost: "overlayFrost",
21
32
  shadowOverlay1: "shadowOverlay1",
22
33
  shadowOverlay2: "shadowOverlay2",
@@ -2,7 +2,7 @@
2
2
  // The same tokens the CSS custom properties carry, resolved per theme, with
3
3
  // dimensions as numbers for React Native and Lynx style objects.
4
4
  export type Theme = "light" | "dark";
5
- export type ColorTokens = Record<"bgBrandSolid" | "bgBrandSolidPressed" | "fgOnBrand" | "fgOnTone" | "bgSurface" | "bgRaised" | "borderDefault" | "borderSubtle" | "borderDropzone" | "ringNeutral" | "ringField" | "ringSwitch" | "bgTileActive" | "shadowTileActive" | "bgOverlayFrost" | "shadowOverlay1" | "shadowOverlay2" | "shadowOverlay3" | "bgPopover" | "ringPopover" | "shadowPopover1" | "shadowPopover2" | "shadowPopover3" | "fgPrimary" | "fgMuted" | "bgDanger" | "bgDangerSolid" | "fgDanger" | "strokeFieldFocus" | "fgDisabled" | "bgNeutralWeak" | "bgNeutralInverted" | "fgOnNeutralInverted" | "fgNeutralSubtle" | "fgNeutralMuted" | "strokeFocusRing" | "bgSuccess" | "bgWarning" | "bgOrange" | "bgCyan" | "bgPurple" | "bgSidebar" | "bgSubtle" | "fgBrandSoft" | "fgSuccessSoft" | "fgWarningSoft" | "fgDangerSoft" | "fgNeutralSoft" | "bgPurpleSoft" | "fgPurpleSoft" | "ringPurpleSoft", string>;
5
+ export type ColorTokens = Record<"bgBrandSolid" | "bgBrandSolidPressed" | "fgOnBrand" | "fgOnTone" | "bgSurface" | "bgRaised" | "bgSurfaceMuted" | "borderDefault" | "borderSubtle" | "borderDropzone" | "borderDivider" | "ringNeutral" | "ringField" | "ringSwitch" | "bgTileActive" | "shadowTileActive" | "bgScrim" | "fgAlertDescription" | "bgOverlayFrost" | "shadowOverlay1" | "shadowOverlay2" | "shadowOverlay3" | "bgPopover" | "ringPopover" | "shadowPopover1" | "shadowPopover2" | "shadowPopover3" | "fgPrimary" | "fgMuted" | "bgDanger" | "bgDangerSolid" | "fgDanger" | "strokeFieldFocus" | "fgDisabled" | "bgNeutralWeak" | "bgNeutralInverted" | "fgOnNeutralInverted" | "fgNeutralSubtle" | "fgNeutralMuted" | "strokeFocusRing" | "bgSuccess" | "bgWarning" | "bgOrange" | "bgCyan" | "bgPurple" | "bgSidebar" | "bgSubtle" | "fgBrandSoft" | "fgSuccessSoft" | "fgWarningSoft" | "fgDangerSoft" | "fgNeutralSoft" | "bgPurpleSoft" | "fgPurpleSoft" | "ringPurpleSoft", string>;
6
6
  export declare const tokens: {
7
7
  color: Record<Theme, ColorTokens>;
8
8
  palette: Record<Theme, Record<string, string>>;
@@ -10,14 +10,18 @@ export const tokens = {
10
10
  "fgOnTone": "#101112",
11
11
  "bgSurface": "#ffffff",
12
12
  "bgRaised": "#f4f4f5",
13
+ "bgSurfaceMuted": "#f6f7f7",
13
14
  "borderDefault": "#e4e4e7",
14
15
  "borderSubtle": "#eeeff1",
15
16
  "borderDropzone": "#e6e7ea",
17
+ "borderDivider": "#e6e7ea",
16
18
  "ringNeutral": "transparent",
17
19
  "ringField": "rgba(0,0,0,0.06)",
18
20
  "ringSwitch": "#d9dade",
19
21
  "bgTileActive": "#ffffff",
20
22
  "shadowTileActive": "rgba(0,0,0,0.04)",
23
+ "bgScrim": "rgba(0,0,0,0.25)",
24
+ "fgAlertDescription": "rgba(0,0,0,0.63)",
21
25
  "bgOverlayFrost": "#ffffff",
22
26
  "shadowOverlay1": "rgba(28,40,64,0.12)",
23
27
  "shadowOverlay2": "rgba(28,40,64,0.16)",
@@ -51,7 +55,7 @@ export const tokens = {
51
55
  "fgSuccessSoft": "#186b3d",
52
56
  "fgWarningSoft": "#80520d",
53
57
  "fgDangerSoft": "#a33539",
54
- "fgNeutralSoft": "#686870",
58
+ "fgNeutralSoft": "#505155",
55
59
  "bgPurpleSoft": "#f5f0ff",
56
60
  "fgPurpleSoft": "#6238b5",
57
61
  "ringPurpleSoft": "#e8ddfe"
@@ -63,14 +67,18 @@ export const tokens = {
63
67
  "fgOnTone": "#101112",
64
68
  "bgSurface": "#0a0a0a",
65
69
  "bgRaised": "#101112",
70
+ "bgSurfaceMuted": "#101112",
66
71
  "borderDefault": "#212224",
67
72
  "borderSubtle": "#1c1d1f",
68
73
  "borderDropzone": "#212224",
74
+ "borderDivider": "#212224",
69
75
  "ringNeutral": "#212224",
70
76
  "ringField": "#212224",
71
77
  "ringSwitch": "#212224",
72
78
  "bgTileActive": "#101112",
73
79
  "shadowTileActive": "rgba(0,0,0,0)",
80
+ "bgScrim": "rgba(0,0,0,0.55)",
81
+ "fgAlertDescription": "rgba(255,255,255,0.6)",
74
82
  "bgOverlayFrost": "#1c1d1f",
75
83
  "shadowOverlay1": "rgba(0,0,0,0.48)",
76
84
  "shadowOverlay2": "rgba(0,0,0,0.64)",
@@ -120,11 +128,14 @@ export const tokens = {
120
128
  "line200": "#e4e4e7",
121
129
  "line100": "#eeeff1",
122
130
  "line150": "#e6e7ea",
131
+ "gray25": "#f6f7f7",
123
132
  "ringNeutral": "transparent",
124
133
  "ringField": "rgba(0,0,0,0.06)",
125
134
  "ringSwitch": "#d9dade",
126
135
  "tileActive": "#ffffff",
127
136
  "tileActiveShadow": "rgba(0,0,0,0.04)",
137
+ "bgScrim": "rgba(0,0,0,0.25)",
138
+ "fgAlertDescription": "rgba(0,0,0,0.63)",
128
139
  "overlayFrost": "#ffffff",
129
140
  "shadowOverlay1": "rgba(28,40,64,0.12)",
130
141
  "shadowOverlay2": "rgba(28,40,64,0.16)",
@@ -158,7 +169,7 @@ export const tokens = {
158
169
  "softSuccessText": "#186b3d",
159
170
  "softWarningText": "#80520d",
160
171
  "softDangerText": "#a33539",
161
- "softNeutralText": "#686870",
172
+ "softNeutralText": "#505155",
162
173
  "purpleTint": "#f5f0ff",
163
174
  "softPurpleText": "#6238b5",
164
175
  "purpleRing": "#e8ddfe"
@@ -172,11 +183,14 @@ export const tokens = {
172
183
  "line200": "#212224",
173
184
  "line100": "#1c1d1f",
174
185
  "line150": "#212224",
186
+ "gray25": "#101112",
175
187
  "ringNeutral": "#212224",
176
188
  "ringField": "#212224",
177
189
  "ringSwitch": "#212224",
178
190
  "tileActive": "#101112",
179
191
  "tileActiveShadow": "rgba(0,0,0,0)",
192
+ "bgScrim": "rgba(0,0,0,0.55)",
193
+ "fgAlertDescription": "rgba(255,255,255,0.6)",
180
194
  "overlayFrost": "#1c1d1f",
181
195
  "shadowOverlay1": "rgba(0,0,0,0.48)",
182
196
  "shadowOverlay2": "rgba(0,0,0,0.64)",
package/dist/navitem.css CHANGED
@@ -1,3 +1,10 @@
1
+
2
+ .crisp-navitem-shortcut { display: flex; align-items: baseline; justify-content: space-between; gap: var(--crisp-space-3); }
3
+ .crisp-navitem-shortcut-desc { font-size: var(--crisp-text-sm); font-weight: var(--crisp-weight-medium); line-height: 16px; white-space: nowrap; }
4
+ .crisp-navitem-shortcut-chord { display: inline-flex; align-items: center; gap: var(--crisp-space-1); flex-shrink: 0; }
5
+ .crisp-navitem-shortcut-then { font-size: var(--crisp-text-xs); font-weight: var(--crisp-weight-medium); color: #898a8d; }
6
+ .crisp-navitem-shortcut-chord .crisp-kbd { text-transform: uppercase; font-weight: var(--crisp-weight-regular); color: #a2a4a7; box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.2); }
7
+
1
8
  .crisp-navitem {
2
9
  box-sizing: border-box; display: flex; align-items: center; gap: var(--crisp-space-1-5); height: 28px; padding-inline: var(--crisp-space-2); padding-inline-end: var(--crisp-space-1); border-radius: var(--crisp-radius-lg); font-family: var(--crisp-font-sans); font-size: var(--crisp-text-lg); font-weight: var(--crisp-weight-medium); letter-spacing: -0.14px; line-height: 20px; color: var(--crisp-fg-primary); text-decoration: none; cursor: pointer; user-select: none; width: 100%; transition: background-color 0.16s;
3
10
  & .crisp-navitem-label { flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
@@ -1,4 +1,11 @@
1
1
  @layer crisp-components {
2
+
3
+ .crisp-navitem-shortcut { display: flex; align-items: baseline; justify-content: space-between; gap: var(--crisp-space-3); }
4
+ .crisp-navitem-shortcut-desc { font-size: var(--crisp-text-sm); font-weight: var(--crisp-weight-medium); line-height: 16px; white-space: nowrap; }
5
+ .crisp-navitem-shortcut-chord { display: inline-flex; align-items: center; gap: var(--crisp-space-1); flex-shrink: 0; }
6
+ .crisp-navitem-shortcut-then { font-size: var(--crisp-text-xs); font-weight: var(--crisp-weight-medium); color: #898a8d; }
7
+ .crisp-navitem-shortcut-chord .crisp-kbd { text-transform: uppercase; font-weight: var(--crisp-weight-regular); color: #a2a4a7; box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.2); }
8
+
2
9
  .crisp-navitem {
3
10
  box-sizing: border-box; display: flex; align-items: center; gap: var(--crisp-space-1-5); height: 28px; padding-inline: var(--crisp-space-2); padding-inline-end: var(--crisp-space-1); border-radius: var(--crisp-radius-lg); font-family: var(--crisp-font-sans); font-size: var(--crisp-text-lg); font-weight: var(--crisp-weight-medium); letter-spacing: -0.14px; line-height: 20px; color: var(--crisp-fg-primary); text-decoration: none; cursor: pointer; user-select: none; width: 100%; transition: background-color 0.16s;
4
11
  & .crisp-navitem-label { flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
@@ -56,7 +56,7 @@
56
56
  & .crisp-recordstable-menu--right { left: auto; right: 0; }
57
57
  & .crisp-recordstable-menu--fixed { position: fixed; top: auto; left: auto; z-index: 40; }
58
58
  & .crisp-recordstable-menu--hdr { min-width: 200px; }
59
- & .crisp-recordstable-menu--op { min-width: 134px; }
59
+ & .crisp-recordstable-menu--op { min-width: 0; }
60
60
  & .crisp-recordstable-menu-item--danger { color: var(--crisp-bg-danger); }
61
61
  & .crisp-recordstable-menu-item--danger svg { color: var(--crisp-bg-danger); }
62
62
  & .crisp-recordstable-menu-item--danger:hover { background: color-mix(in srgb, var(--crisp-bg-danger) 10%, transparent); }
@@ -57,7 +57,7 @@
57
57
  & .crisp-recordstable-menu--right { left: auto; right: 0; }
58
58
  & .crisp-recordstable-menu--fixed { position: fixed; top: auto; left: auto; z-index: 40; }
59
59
  & .crisp-recordstable-menu--hdr { min-width: 200px; }
60
- & .crisp-recordstable-menu--op { min-width: 134px; }
60
+ & .crisp-recordstable-menu--op { min-width: 0; }
61
61
  & .crisp-recordstable-menu-item--danger { color: var(--crisp-bg-danger); }
62
62
  & .crisp-recordstable-menu-item--danger svg { color: var(--crisp-bg-danger); }
63
63
  & .crisp-recordstable-menu-item--danger:hover { background: color-mix(in srgb, var(--crisp-bg-danger) 10%, transparent); }
package/dist/sidebar.css CHANGED
@@ -6,9 +6,14 @@
6
6
  & .crisp-sidebar-section { display: flex; flex-direction: column; gap: var(--crisp-space-px); margin-top: var(--crisp-space-2); }
7
7
  & .crisp-sidebar-section[data-collapsed] { display: none; }
8
8
  & .crisp-sidebar-label { font-size: var(--crisp-text-sm); font-weight: var(--crisp-weight-medium); color: var(--crisp-fg-muted); padding: var(--crisp-space-1-5) var(--crisp-space-2) var(--crisp-space-0-5); }
9
- & .crisp-sidebar-section-header { display: flex; align-items: center; gap: var(--crisp-space-1); width: 100%; margin: 0; border: none; background: none; padding: var(--crisp-space-1-5) var(--crisp-space-2) var(--crisp-space-0-5); font-family: var(--crisp-font-sans); font-size: var(--crisp-text-sm); font-weight: var(--crisp-weight-medium); color: var(--crisp-fg-muted); cursor: pointer; }
9
+ & .crisp-sidebar-section-header-row { display: flex; align-items: center; }
10
+ & .crisp-sidebar-section-header { display: flex; align-items: center; gap: var(--crisp-space-1); flex: 1; min-width: 0; margin: 0; border: none; background: none; padding: var(--crisp-space-1-5) var(--crisp-space-2) var(--crisp-space-0-5); font-family: var(--crisp-font-sans); font-size: var(--crisp-text-sm); font-weight: var(--crisp-weight-medium); color: var(--crisp-fg-muted); cursor: pointer; }
10
11
  & .crisp-sidebar-section-header svg { flex-shrink: 0; transition: transform .15s ease; }
11
12
  & .crisp-sidebar-section-header[data-collapsed] svg { transform: rotate(-90deg); }
13
+ & .crisp-sidebar-section-actions { display: flex; align-items: center; gap: var(--crisp-space-1); flex-shrink: 0; padding-inline-end: var(--crisp-space-1); opacity: 0; transition: opacity .12s ease; }
14
+ & .crisp-sidebar-section-header-row:hover .crisp-sidebar-section-actions, & .crisp-sidebar-section-header-row:focus-within .crisp-sidebar-section-actions { opacity: 1; }
15
+ & .crisp-sidebar-section-action { box-sizing: border-box; display: inline-flex; align-items: center; justify-content: center; width: var(--crisp-space-5); height: var(--crisp-space-5); margin: 0; padding: 0; border: none; border-radius: var(--crisp-radius-sm); background: transparent; color: color-mix(in srgb, var(--crisp-fg-primary) 63%, transparent); font-family: inherit; cursor: pointer; text-decoration: none; transition: background-color 0.14s, color 0.14s; }
16
+ & .crisp-sidebar-section-action:hover { background: var(--crisp-bg-raised); color: var(--crisp-fg-primary); }
12
17
  & .crisp-sidebar-ws-row { display: flex; align-items: center; gap: var(--crisp-space-2); height: var(--crisp-size-header); padding: 0 var(--crisp-space-2); }
13
18
  & .crisp-sidebar-ws-menuroot { display: flex; flex: 1; min-width: 0; }
14
19
  & .crisp-sidebar-ws { box-sizing: border-box; display: flex; align-items: center; gap: var(--crisp-space-2); flex: 1; min-width: 0; height: 100%; margin: 0; padding: 0; border: none; background: none; border-radius: var(--crisp-radius-lg); font-family: inherit; font-size: inherit; color: inherit; text-align: start; cursor: pointer; transition: background-color 0.16s; }
@@ -7,9 +7,14 @@
7
7
  & .crisp-sidebar-section { display: flex; flex-direction: column; gap: var(--crisp-space-px); margin-top: var(--crisp-space-2); }
8
8
  & .crisp-sidebar-section[data-collapsed] { display: none; }
9
9
  & .crisp-sidebar-label { font-size: var(--crisp-text-sm); font-weight: var(--crisp-weight-medium); color: var(--crisp-fg-muted); padding: var(--crisp-space-1-5) var(--crisp-space-2) var(--crisp-space-0-5); }
10
- & .crisp-sidebar-section-header { display: flex; align-items: center; gap: var(--crisp-space-1); width: 100%; margin: 0; border: none; background: none; padding: var(--crisp-space-1-5) var(--crisp-space-2) var(--crisp-space-0-5); font-family: var(--crisp-font-sans); font-size: var(--crisp-text-sm); font-weight: var(--crisp-weight-medium); color: var(--crisp-fg-muted); cursor: pointer; }
10
+ & .crisp-sidebar-section-header-row { display: flex; align-items: center; }
11
+ & .crisp-sidebar-section-header { display: flex; align-items: center; gap: var(--crisp-space-1); flex: 1; min-width: 0; margin: 0; border: none; background: none; padding: var(--crisp-space-1-5) var(--crisp-space-2) var(--crisp-space-0-5); font-family: var(--crisp-font-sans); font-size: var(--crisp-text-sm); font-weight: var(--crisp-weight-medium); color: var(--crisp-fg-muted); cursor: pointer; }
11
12
  & .crisp-sidebar-section-header svg { flex-shrink: 0; transition: transform .15s ease; }
12
13
  & .crisp-sidebar-section-header[data-collapsed] svg { transform: rotate(-90deg); }
14
+ & .crisp-sidebar-section-actions { display: flex; align-items: center; gap: var(--crisp-space-1); flex-shrink: 0; padding-inline-end: var(--crisp-space-1); opacity: 0; transition: opacity .12s ease; }
15
+ & .crisp-sidebar-section-header-row:hover .crisp-sidebar-section-actions, & .crisp-sidebar-section-header-row:focus-within .crisp-sidebar-section-actions { opacity: 1; }
16
+ & .crisp-sidebar-section-action { box-sizing: border-box; display: inline-flex; align-items: center; justify-content: center; width: var(--crisp-space-5); height: var(--crisp-space-5); margin: 0; padding: 0; border: none; border-radius: var(--crisp-radius-sm); background: transparent; color: color-mix(in srgb, var(--crisp-fg-primary) 63%, transparent); font-family: inherit; cursor: pointer; text-decoration: none; transition: background-color 0.14s, color 0.14s; }
17
+ & .crisp-sidebar-section-action:hover { background: var(--crisp-bg-raised); color: var(--crisp-fg-primary); }
13
18
  & .crisp-sidebar-ws-row { display: flex; align-items: center; gap: var(--crisp-space-2); height: var(--crisp-size-header); padding: 0 var(--crisp-space-2); }
14
19
  & .crisp-sidebar-ws-menuroot { display: flex; flex: 1; min-width: 0; }
15
20
  & .crisp-sidebar-ws { box-sizing: border-box; display: flex; align-items: center; gap: var(--crisp-space-2); flex: 1; min-width: 0; height: 100%; margin: 0; padding: 0; border: none; background: none; border-radius: var(--crisp-radius-lg); font-family: inherit; font-size: inherit; color: inherit; text-align: start; cursor: pointer; transition: background-color 0.16s; }