@crisp-ui-kit/crisp 0.54.8 → 0.54.10

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 (59) hide show
  1. package/dist/board.css +32 -8
  2. package/dist/board.layered.css +32 -8
  3. package/dist/cjs/components/contextmenu/contextmenu.js +2 -2
  4. package/dist/cjs/components/contextmenu/contextmenu.recipe.js +24 -0
  5. package/dist/cjs/components/datatable/datatable.js +593 -51
  6. package/dist/cjs/components/datatable/datatable.recipe.js +67 -3
  7. package/dist/cjs/components/dialog/dialog.js +65 -1
  8. package/dist/cjs/components/tabletoolbar/tabletoolbar.chrome.js +37 -2
  9. package/dist/cjs/components/tabletoolbar/tabletoolbar.js +218 -39
  10. package/dist/cjs/components/tabletoolbar/tabletoolbar.recipe.js +48 -1
  11. package/dist/cjs/components/text/text.recipe.js +17 -7
  12. package/dist/cjs/product/board/board.js +62 -28
  13. package/dist/cjs/product/board/board.recipe.js +42 -2
  14. package/dist/cjs/product/comments/comments.js +11 -2
  15. package/dist/cjs/product/comments/comments.recipe.js +14 -4
  16. package/dist/cjs/product/recordstable/recordstable.js +1 -17
  17. package/dist/cjs/product/savedviews/savedviews.js +55 -24
  18. package/dist/cjs/product/savedviews/savedviews.recipe.js +27 -0
  19. package/dist/comments.css +14 -4
  20. package/dist/comments.layered.css +14 -4
  21. package/dist/contextmenu.css +24 -0
  22. package/dist/contextmenu.layered.css +24 -0
  23. package/dist/datatable.css +106 -77
  24. package/dist/datatable.layered.css +106 -77
  25. package/dist/esm/components/contextmenu/contextmenu.js +2 -2
  26. package/dist/esm/components/contextmenu/contextmenu.recipe.js +24 -0
  27. package/dist/esm/components/datatable/datatable.js +593 -52
  28. package/dist/esm/components/datatable/datatable.recipe.js +68 -4
  29. package/dist/esm/components/dialog/dialog.js +65 -1
  30. package/dist/esm/components/tabletoolbar/tabletoolbar.chrome.js +37 -2
  31. package/dist/esm/components/tabletoolbar/tabletoolbar.js +219 -40
  32. package/dist/esm/components/tabletoolbar/tabletoolbar.recipe.js +50 -3
  33. package/dist/esm/components/text/text.recipe.js +17 -7
  34. package/dist/esm/product/board/board.js +63 -29
  35. package/dist/esm/product/board/board.recipe.js +42 -2
  36. package/dist/esm/product/comments/comments.js +12 -3
  37. package/dist/esm/product/comments/comments.recipe.js +14 -4
  38. package/dist/esm/product/recordstable/recordstable.js +2 -18
  39. package/dist/esm/product/savedviews/savedviews.js +57 -26
  40. package/dist/esm/product/savedviews/savedviews.recipe.js +27 -0
  41. package/dist/recordstable.css +33 -31
  42. package/dist/recordstable.layered.css +33 -31
  43. package/dist/savedviews.css +27 -0
  44. package/dist/savedviews.layered.css +27 -0
  45. package/dist/styles.css +282 -125
  46. package/dist/styles.layered.css +282 -125
  47. package/dist/tabletoolbar.css +45 -4
  48. package/dist/tabletoolbar.layered.css +45 -4
  49. package/dist/text.css +1 -1
  50. package/dist/text.layered.css +1 -1
  51. package/dist/types/components/contextmenu/contextmenu.d.ts +8 -1
  52. package/dist/types/components/datatable/datatable.d.ts +98 -2
  53. package/dist/types/components/tabletoolbar/tabletoolbar.d.ts +72 -2
  54. package/dist/types/components/text/text.recipe.d.ts +1 -1
  55. package/dist/types/product/board/board.d.ts +48 -2
  56. package/dist/types/product/comments/comments.d.ts +6 -1
  57. package/dist/types/product/recordstable/recordstable.d.ts +0 -1
  58. package/dist/types/product/savedviews/savedviews.d.ts +45 -8
  59. package/package.json +1 -1
@@ -1,4 +1,4 @@
1
- import { defineRecipe, resolveRecipe } from "../../core/index.js";
1
+ import { defineRecipe, resolveRecipe, rtlFlipCss } from "../../core/index.js";
2
2
  import { contract as c, elevation as e } from "../../tokens/index.js";
3
3
  // The feature-rich, config-driven data grid (sort / filter / selection /
4
4
  // inline edit) built on the composable `Table` primitives. This recipe adds the
@@ -19,6 +19,30 @@ const config = defineRecipe({
19
19
  position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
20
20
  overflow: hidden; clip: rect(0, 0, 0, 0); white-space: nowrap; border: 0;
21
21
  }
22
+ /* crisp/#645: visually-hidden-but-focusable, not visually-hidden-and-
23
+ inert like .crisp-datatable-live above -- same clip-rect shape,
24
+ this one just needs to actually hold DOM focus so a native
25
+ copy/paste event has something to fire on (see its own comment
26
+ in datatable.tsx). resize: none keeps a stray native textarea handle
27
+ from ever being drawable, even though it's clipped to 1px anyway. */
28
+ & .crisp-datatable-clipboard-proxy {
29
+ position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
30
+ overflow: hidden; clip: rect(0, 0, 0, 0); white-space: nowrap; border: 0;
31
+ resize: none;
32
+ }
33
+ /* single active cell (crisp/#645 follow-up -- this had no styling of its
34
+ own at all before, a gap against Golden Rule 0: RecordsTable's own
35
+ measured value, .crisp-recordstable-td[data-selected], brought
36
+ across unchanged now that a visible anchor actually matters for the
37
+ Shift+Click range below). */
38
+ & td[data-selected] { outline: 2px solid ${c.bgBrandSolid}; outline-offset: -2px; border-radius: ${c.radius.sm}; z-index: 3; }
39
+ /* crisp/#645, G-101: a Shift+Click range -- reuses the row-selection
40
+ tint formula already used twice below (tbody tr[data-selected] td)
41
+ for the same reason a selected group of rows gets one: a selected
42
+ GROUP of cells is the same kind of state, just cell-scoped instead of
43
+ row-scoped. Not independently re-measured against Attio's own range
44
+ highlight (out of scope this slice -- flagged on crisp#645). */
45
+ & td[data-in-range] { background: color-mix(in srgb, ${c.bgBrandSolid} 10%, ${c.bgSurface}); }
22
46
  /* The header cell has no padding of its own -- the button owns the
23
47
  full cell so the whole header is clickable (its own inset is 12px).
24
48
  Mirrors the summary-row fix.
@@ -111,6 +135,12 @@ const config = defineRecipe({
111
135
  & tbody tr[data-selected] td { background: color-mix(in srgb, ${c.bgBrandSolid} 10%, ${c.bgSurface}); }
112
136
  & tbody tr[data-selected]:hover td { background: color-mix(in srgb, ${c.fgPrimary} 4%, color-mix(in srgb, ${c.bgBrandSolid} 10%, ${c.bgSurface})); }
113
137
  & .crisp-datatable-editable:hover { background: color-mix(in srgb, ${c.fgPrimary} 4%, ${c.bgSurface}); cursor: text; }
138
+ /* crisp/#758: AI-enriched column marker, ported from RecordsTable's own
139
+ already-measured value -- a full-bleed #9b69ff (bgPurple) wash at 4%,
140
+ composited OVER whatever row-state background the cell already has
141
+ (hover, selected), which is why it's an ::after overlay and not a
142
+ plain background. */
143
+ & td[data-enriched]::after { content: ""; position: absolute; inset: 0; pointer-events: none; background: color-mix(in srgb, ${c.bgPurple} 4%, transparent); }
114
144
  & .crisp-datatable-edit { width: 100%; height: 28px; margin: 0; padding: 0 ${c.space["1.5"]}; border: none; border-radius: ${c.radius.sm}; background: ${c.bgSurface}; box-shadow: inset 0 0 0 1px ${c.bgBrandSolid}; font-family: inherit; font-size: ${c.text.lg}; color: ${c.fgPrimary}; outline: none; vertical-align: middle; }
115
145
  & .crisp-datatable-edit:focus { box-shadow: inset 0 0 0 1px ${c.bgBrandSolid}; }
116
146
  /* An async onEdit's in-flight state (#385) -- the field/picker dims and
@@ -177,11 +207,44 @@ const config = defineRecipe({
177
207
  & .crisp-datatable-resize { position: absolute; top: 0; inset-inline-end: -3px; width: 9px; height: 100%; cursor: col-resize; z-index: 6; touch-action: none; }
178
208
  & .crisp-datatable-resize::after { content: ""; position: absolute; top: 0; inset-inline-start: 3px; width: 2px; height: 100%; background: ${c.bgBrandSolid}; opacity: 0; transition: opacity .12s; }
179
209
  & .crisp-datatable-resize:hover::after, & .crisp-datatable-resize[data-resizing]::after { opacity: 1; }
210
+ /* crisp/#731 -- column drag-to-reorder. Measured live against Attio's
211
+ own grid (the issue's own comment): the dragged header keeps full
212
+ opacity and the plain \`pointer\` cursor throughout (Attio draws no
213
+ ghost and no "grabbing" cursor of its own), so this adds nothing to
214
+ \`[data-dragging]\` itself -- the ONLY new paint is the drop-indicator
215
+ line, a 3px bar in the same measured brand blue the resize handle's
216
+ own hover state (above) already uses, positioned on whichever edge of
217
+ the hovered header the pointer is closer to. */
218
+ & th[data-dragging] { cursor: pointer; }
219
+ & th[data-drop-before]::before, & th[data-drop-after]::after {
220
+ content: ""; position: absolute; top: 0; width: 3px; height: 100%;
221
+ background: ${c.bgBrandSolid}; z-index: 6; pointer-events: none;
222
+ }
223
+ & th[data-drop-before]::before { inset-inline-start: -1.5px; }
224
+ & th[data-drop-after]::after { inset-inline-end: -1.5px; }
180
225
  /* typed cells — entity (avatar + name), colour-coded tags, numbers, dates */
181
226
  & .crisp-datatable-entity { display: flex; align-items: center; gap: ${c.space["1.5"]}; min-width: 0; color: ${c.fgPrimary}; font-weight: ${c.weight.medium}; }
182
- & .crisp-datatable-entity-name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
183
- & .crisp-datatable-entity:hover .crisp-datatable-entity-name { text-decoration: underline; }
227
+ /* crisp/#601, G-050: measured LIVE (getComputedStyle on the name text,
228
+ plain hover vs rest) that Attio's underline is unconditional, not a
229
+ hover reveal -- identical at rest and on hover: text-decoration-color
230
+ is rgba(0,0,0,0.09), a 9% tint, not the name's own full-contrast text
231
+ colour. The previous :hover-gated, full-contrast rule here was itself
232
+ unmeasured and wrong in two ways at once. */
233
+ & .crisp-datatable-entity-name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; text-decoration: underline; text-decoration-color: color-mix(in srgb, ${c.fgPrimary} 9%, transparent); }
184
234
  & button.crisp-datatable-entity-name--link { margin: 0; padding: 0; border: none; background: none; font: inherit; color: inherit; letter-spacing: inherit; text-align: start; cursor: pointer; max-width: 100%; }
235
+ /* crisp/#601, G-048: two 20px ghost icons, right-aligned in the primary
236
+ column, revealed on ROW hover (the issue's own title says "row
237
+ hover", and a live check confirmed hovering well past the name --
238
+ not just the icons' own hit target -- still reveals them) or
239
+ keyboard focus. Measured live: border-radius 6px, icon colour
240
+ rgba(0,0,0,0.55) at rest, darkening to the full text colour with a
241
+ rgba(0,0,0,0.04) background on the icon's OWN hover -- both
242
+ expressed as color-mix against fgPrimary so they stay correct in
243
+ dark mode instead of a literal black that would invert. */
244
+ & .crisp-datatable-entity-actions { display: flex; align-items: center; gap: ${c.space["1.5"]}; margin-inline-start: auto; flex-shrink: 0; opacity: 0; }
245
+ & tr:hover .crisp-datatable-entity-actions, & .crisp-datatable-entity-actions:focus-within { opacity: 1; }
246
+ & .crisp-datatable-entity-action { display: inline-flex; align-items: center; justify-content: center; width: 20px; height: 20px; margin: 0; padding: 0; border: none; border-radius: ${c.radius.sm}; background: none; font-family: inherit; color: color-mix(in srgb, ${c.fgPrimary} 55%, transparent); cursor: pointer; flex-shrink: 0; }
247
+ & .crisp-datatable-entity-action:hover { background: color-mix(in srgb, ${c.fgPrimary} 4%, transparent); color: ${c.fgPrimary}; }
185
248
  & .crisp-datatable-avatar { width: ${c.size.icon}; height: ${c.size.icon}; border-radius: ${c.radius.sm}; flex-shrink: 0; display: inline-flex; align-items: center; justify-content: center; font-size: 10px; line-height: 1; font-weight: ${c.weight.semibold}; text-transform: uppercase; color: ${c.fgOnBrand}; background: ${c.bgBrandSolid}; }
186
249
  & .crisp-datatable-avatar[data-tone="success"] { background: ${c.bgSuccess}; } & .crisp-datatable-avatar[data-tone="warning"] { background: ${c.bgWarning}; } & .crisp-datatable-avatar[data-tone="danger"] { background: ${c.bgDanger}; } & .crisp-datatable-avatar[data-tone="purple"] { background: ${c.bgPurple}; } & .crisp-datatable-avatar[data-tone="cyan"] { background: ${c.bgCyan}; } & .crisp-datatable-avatar[data-tone="orange"] { background: ${c.bgOrange}; }
187
250
  & .crisp-datatable-avatar img { width: 100%; height: 100%; border-radius: inherit; object-fit: cover; }
@@ -213,7 +276,8 @@ const config = defineRecipe({
213
276
  & .crisp-datatable-col-grip { width: 20px; padding: 0; text-align: center; }
214
277
  & .crisp-datatable-grip { display: inline-flex; align-items: center; justify-content: center; width: 100%; height: 100%; margin: 0; border: none; border-radius: 0; background: none; font-family: inherit; color: ${c.fgMuted}; cursor: grab; }
215
278
  & .crisp-datatable-grip:active { cursor: grabbing; }
216
- & tbody tr[data-drop-target] td, & tbody tr[data-drop-target] th { box-shadow: inset 0 2px 0 0 ${c.bgBrandSolid}; transition: box-shadow .12s; }`,
279
+ & tbody tr[data-drop-target] td, & tbody tr[data-drop-target] th { box-shadow: inset 0 2px 0 0 ${c.bgBrandSolid}; transition: box-shadow .12s; }
280
+ ${rtlFlipCss}`,
217
281
  variants: {},
218
282
  defaultVariants: {},
219
283
  compoundVariants: [],
@@ -25,10 +25,74 @@ function useDialogInstanceId() {
25
25
  export function Dialog({ open, onOpenChange, title, icon, width = 640, variant = "panel", description, "aria-label": ariaLabel, closeLabel: closeLabelProp, children, className, style, "aria-describedby": describedBy, ...rest }) {
26
26
  const closeLabel = useCrispMessage(closeLabelProp, "Close");
27
27
  const id = useDialogInstanceId();
28
+ const isAlert = variant === "alert";
28
29
  const service = useMachine(dialog.machine, {
29
30
  id,
30
31
  open,
31
- role: variant === "alert" ? "alertdialog" : "dialog",
32
+ role: isAlert ? "alertdialog" : "dialog",
33
+ // #704: a stacked (second) Dialog can get marked `aria-hidden="true"`
34
+ // on its own outer wrapper a frame after it opens. Traced into
35
+ // `@zag-js/dialog`'s `hideContentBelow` effect and `@zag-js/aria-hidden`'s
36
+ // own source: a modal Dialog's `hideContentBelow` calls
37
+ // `hideOthers([itself])` (deferred one `requestAnimationFrame`), which
38
+ // walks `document.body`'s children and `aria-hidden`s every one that
39
+ // ISN'T an ancestor of its own content. Each open `Dialog` schedules
40
+ // this independently, with no shared registry of who else is open —
41
+ // when the effect re-runs for the FIRST (panel) Dialog (Zag re-runs an
42
+ // effect whenever any prop it reads is considered changed, which a
43
+ // parent re-render can trigger even when the boolean VALUE didn't),
44
+ // its walk runs again with the SECOND (alert) Dialog now mounted as a
45
+ // sibling body child it has never seen before, and marks that whole
46
+ // sibling subtree — including the alert's own wrapper, the thing
47
+ // actually on top and interactive — as hidden. A real upstream
48
+ // limitation (no per-instance awareness of other open Dialogs), not
49
+ // something crisp can fix by calling the library differently.
50
+ //
51
+ // `hideContentBelow` is gated purely on `modal` (no separate flag), so
52
+ // `modal: false` for the alert variant skips it — `trapFocus`/
53
+ // `preventScroll` are passed explicitly right below so they stay on
54
+ // regardless (their own defaults are ALSO `= modal`, so leaving them
55
+ // out here would silently turn them off too).
56
+ //
57
+ // A DOM-level fix was tried first — correcting the wrongly-set
58
+ // attribute after the fact instead of touching `modal` at all, so
59
+ // every other zag-managed behavior would stay untouched by
60
+ // construction. It broke `formdialog.test.tsx`'s own
61
+ // "the confirm's own Cancel keeps the dirtied form open" test (a real
62
+ // regression, reproduced with the correction reduced to literally one
63
+ // `removeAttribute` call, and reproduced again unmodified on the prior
64
+ // WIP commit — that version was never actually run against the full
65
+ // suite): `waitFor(() => expect(queryByRole("alertdialog")).toBeNull())`
66
+ // hung for the full test timeout rather than the confirm's real,
67
+ // sub-100ms unmount, in a way that traces to `aria-hidden` specifically
68
+ // (swapping the correction for a harmless, differently-named attribute
69
+ // did not reproduce it) — a fragile interaction this DOM-mutation
70
+ // approach could not be made to reliably avoid.
71
+ //
72
+ // `modal: false` also turns off `@zag-js/dismissable`'s own
73
+ // `pointerBlocking` for the alert. Verified live (a real
74
+ // headed-then-headless Chromium session against a built `FormDialog`,
75
+ // not just reasoning from the source) that this does NOT regress
76
+ // outside-click safety in practice — but for a DIFFERENT reason than
77
+ // `pointerBlocking` itself: the thing actually keeping a background
78
+ // click from reaching anything is `@zag-js/remove-scroll`'s
79
+ // `preventBodyScroll`, gated on `preventScroll` (kept `true` above,
80
+ // independent of `modal`) — it sets `pointer-events: none` directly on
81
+ // `<body>` while any modal-ish Dialog is open, and `getContentProps()`
82
+ // re-enables `pointer-events: auto` only on each Dialog's OWN content
83
+ // element. Confirmed live: the alert's content had an explicit
84
+ // `pointer-events: auto`; its OWN backdrop/positioner did not, and
85
+ // inherited body's `none`; clicking anywhere in that dead zone (the
86
+ // alert's backdrop, or the panel's, now both non-interactive) dismissed
87
+ // neither dialog, because the click lands on nothing that listens for
88
+ // it — a stronger guarantee than "click closes the wrong layer", not
89
+ // just an absence of that one failure mode. `pointerBlocking` is a
90
+ // second, redundant layer over the same problem `preventScroll` already
91
+ // solves here; the alert's own `closeOnInteractOutside` is already
92
+ // `false` for any `alertdialog` role regardless of `modal`.
93
+ modal: !isAlert,
94
+ trapFocus: true,
95
+ preventScroll: true,
32
96
  onOpenChange(details) {
33
97
  onOpenChange(details.open);
34
98
  },
@@ -31,6 +31,19 @@ import { contract as c, elevation as e } from "../../tokens/index.js";
31
31
  // content width: a floor sized for the text set overshoots the other two.
32
32
  // Dropped to `min-width: 0` so every op set sizes to its own content, the
33
33
  // same as any other menu here without an explicit floor.
34
+ //
35
+ // `.menu-item` also picked up `text-decoration: none` for #595's Import/
36
+ // Export menu — Attio's own "Import CSV" and "Learn about imports and
37
+ // exports" rows are real `<a>` tags (the middle two, pure JS actions, are
38
+ // plain `<div role="menuitem">`), and an anchor needs the underline reset
39
+ // every button-based `.menu-item` never needed. No-op for every existing
40
+ // button consumer of this class. Same menu's 2 dividers use `.menu-sep` on
41
+ // a real `<hr>` (implicit `role="separator"`, no extra ARIA plumbing needed)
42
+ // rather than a `<div role="separator">` — biome's a11y rules want a
43
+ // separator either genuinely interactive (`tabIndex`, `aria-valuenow`, a
44
+ // resizable-splitter kind of thing this isn't) or the semantic element
45
+ // instead; `.menu-sep` picked up `border: none` for it, a no-op for the
46
+ // existing plain-`<div>` consumers of this class.
34
47
  export const toolbarChromeHead = (p) => ` & .${CLASS_PREFIX}${p}-toolbar { display: flex; align-items: center; gap: ${c.space["2"]}; padding: 0 ${c.space["3"]} ${c.space["2"]}; }
35
48
  & .${CLASS_PREFIX}${p}-toolbar[data-inline] { padding-bottom: 0; }
36
49
  & .${CLASS_PREFIX}${p}-search { display: flex; align-items: center; gap: ${c.space["1.5"]}; height: ${c.size.row}; padding: 0 ${c.space["2.5"]}; border: none; border-radius: ${c.radius.md}; background: ${c.bgSurface}; box-shadow: inset 0 0 0 1px ${c.borderSubtle}; color: ${c.fgMuted}; }
@@ -69,7 +82,7 @@ export const toolbarChromeBody = (p) => ` & .${CLASS_PREFIX}${p}-menu-head {
69
82
  & .${CLASS_PREFIX}${p}-sort-learn { display: inline-flex; align-items: center; gap: ${c.space["1.5"]}; font-size: ${c.text.lg}; color: color-mix(in srgb, ${c.fgPrimary} 63%, transparent); text-decoration: none; }
70
83
  & .${CLASS_PREFIX}${p}-sort-learn svg { flex-shrink: 0; }
71
84
  & .${CLASS_PREFIX}${p}-sort-learn:hover { color: ${c.fgPrimary}; }
72
- & .${CLASS_PREFIX}${p}-menu-item { display: flex; align-items: center; gap: ${c.space["1.5"]}; width: 100%; height: ${c.size.menuRow}; margin: 0; padding: 0 ${c.space["2"]}; border: none; background: none; border-radius: ${c.radius.md}; font-family: inherit; font-size: ${c.text.lg}; line-height: 20px; font-weight: ${c.weight.medium}; color: ${c.fgPrimary}; text-align: left; cursor: pointer; }
85
+ & .${CLASS_PREFIX}${p}-menu-item { display: flex; align-items: center; gap: ${c.space["1.5"]}; width: 100%; height: ${c.size.menuRow}; margin: 0; padding: 0 ${c.space["2"]}; border: none; background: none; border-radius: ${c.radius.md}; font-family: inherit; font-size: ${c.text.lg}; line-height: 20px; font-weight: ${c.weight.medium}; color: ${c.fgPrimary}; text-align: left; text-decoration: none; cursor: pointer; }
73
86
  & .${CLASS_PREFIX}${p}-menu-item:hover { background: ${c.bgRaised}; }
74
87
  & .${CLASS_PREFIX}${p}-menu-item:disabled { opacity: 0.4; cursor: default; }
75
88
  & .${CLASS_PREFIX}${p}-menu-item:disabled:hover { background: none; }
@@ -77,7 +90,29 @@ export const toolbarChromeBody = (p) => ` & .${CLASS_PREFIX}${p}-menu-head {
77
90
  & .${CLASS_PREFIX}${p}-menu-item[data-on] svg { color: ${c.bgBrandSolid}; }
78
91
  & .${CLASS_PREFIX}${p}-menu-item .${CLASS_PREFIX}${p}-menu-check { margin-left: auto; }
79
92
  & .${CLASS_PREFIX}${p}-menu-input { height: ${c.size.row}; margin: ${c.space["0.5"]}; padding: 0 ${c.space["2"]}; border: none; border-radius: ${c.radius.md}; box-shadow: inset 0 0 0 1px ${c.borderSubtle}; background: ${c.bgSurface}; font-family: inherit; font-size: ${c.text.lg}; color: ${c.fgPrimary}; outline: none; }
80
- & .${CLASS_PREFIX}${p}-menu-sep { height: 1px; background: ${c.borderSubtle}; margin: ${c.space["0.5"]} 0; }
93
+ & .${CLASS_PREFIX}${p}-menu-sep { height: 1px; border: none; background: ${c.borderSubtle}; margin: ${c.space["0.5"]} 0; }
94
+ /* #592: the Filter field picker's nested-scope header (back control +
95
+ search, once drilled into a nestedColumns column) and a search
96
+ result's own breadcrumb row ("Team › Twitter follower count",
97
+ surfaced by the SAME query matching a nested column). menu-back
98
+ reuses the established 28px icon-only control pattern this file
99
+ already uses twice (sort-remove, the kebab segment) rather than
100
+ an independent measurement of Attio's own back arrow. The breadcrumb
101
+ colours ARE measured live: the parent segment muted at the same
102
+ exact color-mix 63% sort-learn above already uses, the
103
+ separator chevron and leaf segment both full fgPrimary (the
104
+ generic menu-item svg rule below defaults every icon to
105
+ fgMuted, so the separator needs its own override to read
106
+ correctly here). */
107
+ & .${CLASS_PREFIX}${p}-menu-nesthead { display: flex; align-items: center; gap: ${c.space["1"]}; margin: ${c.space["0.5"]}; }
108
+ & .${CLASS_PREFIX}${p}-menu-nesthead .${CLASS_PREFIX}${p}-menu-input { flex: 1; margin: 0; }
109
+ & .${CLASS_PREFIX}${p}-menu-back { display: inline-flex; align-items: center; justify-content: center; width: 28px; height: 28px; flex-shrink: 0; padding: 0; border: none; border-radius: ${c.radius.md}; background: none; color: ${c.fgMuted}; cursor: pointer; }
110
+ & .${CLASS_PREFIX}${p}-menu-back:hover { background: ${c.bgRaised}; color: ${c.fgPrimary}; }
111
+ & .${CLASS_PREFIX}${p}-menu-item--expand { justify-content: space-between; }
112
+ & .${CLASS_PREFIX}${p}-menu-item-path { display: inline-flex; align-items: center; gap: 2px; min-width: 0; overflow: hidden; }
113
+ & .${CLASS_PREFIX}${p}-menu-item-path-parent { flex-shrink: 0; overflow: hidden; text-overflow: ellipsis; max-width: 100px; color: color-mix(in srgb, ${c.fgPrimary} 63%, transparent); }
114
+ & .${CLASS_PREFIX}${p}-menu-item-path-sep { flex-shrink: 0; color: ${c.fgPrimary}; }
115
+ & .${CLASS_PREFIX}${p}-menu-item-path > span:last-child { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
81
116
  & .${CLASS_PREFIX}${p}-menu--up { top: auto; bottom: calc(100% + ${c.space["1"]}); transform-origin: bottom; }
82
117
  & .${CLASS_PREFIX}${p}-menu--right { left: auto; right: 0; }
83
118
  & .${CLASS_PREFIX}${p}-menu--fixed { position: fixed; top: auto; left: auto; z-index: 40; }