@crisp-ui-kit/crisp 0.54.11 → 0.55.1

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 (63) hide show
  1. package/dist/badge.css +1 -1
  2. package/dist/badge.layered.css +1 -1
  3. package/dist/cjs/components/badge/badge.recipe.js +10 -5
  4. package/dist/cjs/components/datatable/countries.js +73 -0
  5. package/dist/cjs/components/datatable/datatable.js +318 -40
  6. package/dist/cjs/components/datatable/datatable.recipe.js +18 -1
  7. package/dist/cjs/components/resizablepanel/resizablepanel.js +10 -1
  8. package/dist/cjs/components/table/table.js +8 -1
  9. package/dist/cjs/components/tabletoolbar/tabletoolbar.js +110 -27
  10. package/dist/cjs/index.js +0 -2
  11. package/dist/cjs/product/recordpage/index.js +6 -1
  12. package/dist/cjs/product/recordpage/recordpage.js +20 -4
  13. package/dist/datatable.css +141 -102
  14. package/dist/datatable.layered.css +141 -102
  15. package/dist/esm/components/badge/badge.recipe.js +10 -5
  16. package/dist/esm/components/datatable/countries.js +68 -0
  17. package/dist/esm/components/datatable/datatable.js +319 -41
  18. package/dist/esm/components/datatable/datatable.recipe.js +18 -1
  19. package/dist/esm/components/resizablepanel/resizablepanel.js +10 -1
  20. package/dist/esm/components/table/table.js +8 -1
  21. package/dist/esm/components/tabletoolbar/tabletoolbar.js +111 -28
  22. package/dist/esm/index.js +0 -2
  23. package/dist/esm/product/recordpage/index.js +2 -0
  24. package/dist/esm/product/recordpage/recordpage.js +20 -4
  25. package/dist/styles.css +142 -375
  26. package/dist/styles.layered.css +142 -375
  27. package/dist/types/components/badge/badge.recipe.d.ts +3 -1
  28. package/dist/types/components/datatable/countries.d.ts +12 -0
  29. package/dist/types/components/datatable/datatable.d.ts +27 -3
  30. package/dist/types/components/table/table.d.ts +14 -0
  31. package/dist/types/index.d.ts +0 -2
  32. package/dist/types/product/recordpage/index.d.ts +2 -0
  33. package/package.json +2 -12
  34. package/dist/cjs/product/recordstable/company-logos.js +0 -29
  35. package/dist/cjs/product/recordstable/index.js +0 -13
  36. package/dist/cjs/product/recordstable/recordstable.js +0 -1362
  37. package/dist/cjs/product/recordstable/recordstable.recipe.js +0 -175
  38. package/dist/cjs/product/recordstable/recordstable.tag.js +0 -26
  39. package/dist/cjs/product/taskstable/index.js +0 -7
  40. package/dist/cjs/product/taskstable/taskstable.js +0 -32
  41. package/dist/cjs/product/taskstable/taskstable.recipe.js +0 -56
  42. package/dist/esm/product/recordstable/company-logos.js +0 -26
  43. package/dist/esm/product/recordstable/index.js +0 -3
  44. package/dist/esm/product/recordstable/recordstable.js +0 -1322
  45. package/dist/esm/product/recordstable/recordstable.recipe.js +0 -172
  46. package/dist/esm/product/recordstable/recordstable.tag.js +0 -23
  47. package/dist/esm/product/taskstable/index.js +0 -2
  48. package/dist/esm/product/taskstable/taskstable.js +0 -29
  49. package/dist/esm/product/taskstable/taskstable.recipe.js +0 -53
  50. package/dist/recordstable.css +0 -240
  51. package/dist/recordstable.d.ts +0 -7
  52. package/dist/recordstable.layered.css +0 -242
  53. package/dist/taskstable.css +0 -30
  54. package/dist/taskstable.d.ts +0 -7
  55. package/dist/taskstable.layered.css +0 -32
  56. package/dist/types/product/recordstable/company-logos.d.ts +0 -1
  57. package/dist/types/product/recordstable/index.d.ts +0 -3
  58. package/dist/types/product/recordstable/recordstable.d.ts +0 -294
  59. package/dist/types/product/recordstable/recordstable.recipe.d.ts +0 -5
  60. package/dist/types/product/recordstable/recordstable.tag.d.ts +0 -15
  61. package/dist/types/product/taskstable/index.d.ts +0 -2
  62. package/dist/types/product/taskstable/taskstable.d.ts +0 -59
  63. package/dist/types/product/taskstable/taskstable.recipe.d.ts +0 -3
package/dist/styles.css CHANGED
@@ -252,7 +252,7 @@
252
252
  height: 20px; padding-inline: var(--crisp-space-1); font-size: var(--crisp-text-sm); line-height: 16px; gap: 3px; border-radius: var(--crisp-radius-sm);
253
253
  }
254
254
  .crisp-badge--size_large {
255
- height: var(--crisp-size-chip); padding-inline: 9px; font-size: var(--crisp-text-lg); letter-spacing: -0.1px; border-radius: var(--crisp-radius-chip);
255
+ height: var(--crisp-size-chip); padding-block: var(--crisp-space-px); padding-inline-start: var(--crisp-space-1); padding-inline-end: var(--crisp-space-1-5); font-size: var(--crisp-text-lg); letter-spacing: -0.1px; border-radius: var(--crisp-radius-lg);
256
256
  }
257
257
  .crisp-badge--variant_soft {
258
258
 
@@ -598,8 +598,11 @@
598
598
  }
599
599
 
600
600
  .crisp-datatable {
601
- display: flex; flex-direction: column; gap: var(--crisp-space-2-5); font-family: var(--crisp-font-sans);
602
- & .crisp-datatable-toolbar { display: flex; align-items: center; gap: var(--crisp-space-2); min-height: 49px; border-top: 1px solid var(--crisp-border-subtle); }
601
+ display: flex; flex-direction: column; gap: var(--crisp-space-2-5); font-family: var(--crisp-font-sans); /* The toolbar carried no horizontal inset of its own, so its content (search box, sort/filter chips) sat flush at the table's outer edge
602
+ (x=0) while the header row's own th/td padding (table.recipe.ts, #465) starts every column's text at 12px -- one row down, the SAME
603
+ grid's own header cell already begins at that inset. c.space["3"] here just matches the row directly below it, measured live against Attio (the sort/filter row's search chip lines up with the first
604
+ column header's text, not the grid's raw edge). */
605
+ & .crisp-datatable-toolbar { display: flex; align-items: center; gap: var(--crisp-space-2); min-height: 49px; border-top: 1px solid var(--crisp-border-subtle); padding: 0 var(--crisp-space-3); }
603
606
  & .crisp-datatable-search { position: relative; flex: 1; max-width: 280px; display: inline-flex; align-items: center; }
604
607
  & .crisp-datatable-search svg { position: absolute; inset-inline-start: var(--crisp-space-2); color: var(--crisp-fg-muted); pointer-events: none; }
605
608
  & .crisp-datatable-search input { width: 100%; height: 32px; padding: 0 var(--crisp-space-2) 0 var(--crisp-space-6); border: none; border-radius: var(--crisp-radius-md); background: var(--crisp-bg-surface); box-shadow: inset 0 0 0 1px var(--crisp-ring-neutral), 0 0 2px rgba(28,40,64,0.18), 0 1px 3px rgba(0,0,0,0.04); font-family: inherit; font-size: var(--crisp-text-lg); color: var(--crisp-fg-primary); outline: none; transition: box-shadow .15s; }
@@ -607,72 +610,83 @@
607
610
  & .crisp-datatable-search input:focus { box-shadow: inset 0 0 0 1px var(--crisp-bg-brand-solid); }
608
611
  & .crisp-datatable-count { margin-inline-start: auto; font-size: var(--crisp-text-md); color: var(--crisp-fg-muted); }
609
612
  /* announced, never drawn */
610
- & .crisp-datatable-live { position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px; overflow: hidden; clip: rect(0, 0, 0, 0); white-space: nowrap; border: 0; }
611
- /* crisp/#645: visually-hidden-but-focusable, not visually-hidden-and- inert like .crisp-datatable-live above -- same clip-rect shape, this one just needs to actually hold DOM focus so a native copy/paste event has something to fire on (see its own comment in datatable.tsx). resize: none keeps a stray native textarea handle from ever being drawable, even though it's clipped to 1px anyway. */
612
- & .crisp-datatable-clipboard-proxy {
613
+ & .crisp-datatable-live {
613
614
  position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
614
615
  overflow: hidden; clip: rect(0, 0, 0, 0); white-space: nowrap; border: 0;
615
- resize: none;
616
616
  }
617
- /* single active cell (crisp/#645 follow-up -- this had no styling of its
618
- own at all before, a gap against Golden Rule 0: RecordsTable's own measured value, .crisp-recordstable-td[data-selected], brought across unchanged now that a visible anchor actually matters for the Shift+Click range below). */
617
+ /* crisp/#645: visually-hidden-but-focusable, not visually-hidden-and-
618
+ inert like .crisp-datatable-live above -- same clip-rect shape,
619
+ this one just needs to actually hold DOM focus so a native
620
+ copy/paste event has something to fire on (see its own comment
621
+ in datatable.tsx). resize: none keeps a stray native textarea handle
622
+ from ever being drawable, even though it's clipped to 1px anyway. */
623
+ & .crisp-datatable-clipboard-proxy { position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px; overflow: hidden; clip: rect(0, 0, 0, 0); white-space: nowrap; border: 0; resize: none; }
624
+ /* single active cell (crisp/#645 follow-up -- this had no styling of its own at all before, a gap against Golden Rule 0: RecordsTable's own
625
+ measured value, .crisp-recordstable-td[data-selected], brought
626
+ across unchanged now that a visible anchor actually matters for the
627
+ Shift+Click range below). */
619
628
  & td[data-selected] { outline: 2px solid var(--crisp-bg-brand-solid); outline-offset: -2px; border-radius: var(--crisp-radius-sm); z-index: 3; }
620
- /* crisp/#645, G-101: a Shift+Click range -- reuses the row-selection tint formula already used twice below (tbody tr[data-selected] td) for the same reason a selected group of rows gets one: a selected GROUP of cells is the same kind of state, just cell-scoped instead of row-scoped. Not independently re-measured against Attio's own range
621
- highlight (out of scope this slice -- flagged on crisp#645). */
622
- & td[data-in-range] { background: color-mix(in srgb, var(--crisp-bg-brand-solid) 10%, var(--crisp-bg-surface));
623
- } /* The header cell has no padding of its own -- the button owns the full cell so the whole header is clickable (its own inset is 12px). Mirrors the summary-row fix. `& .crisp-table th`, not bare `& th` (found while re-checking #691's
629
+ /* crisp/#645, G-101: a Shift+Click range -- reuses the row-selection
630
+ tint formula already used twice below (tbody tr[data-selected] td)
631
+ for the same reason a selected group of rows gets one: a selected
632
+ GROUP of cells is the same kind of state, just cell-scoped instead of
633
+ row-scoped. Not independently re-measured against Attio's own range highlight (out of scope this slice -- flagged on crisp#645). */
634
+ & td[data-in-range] { background: color-mix(in srgb, var(--crisp-bg-brand-solid) 10%, var(--crisp-bg-surface)); }
635
+ /* The header cell has no padding of its own -- the button owns the full cell so the whole header is clickable (its own inset is 12px). Mirrors the summary-row fix. `& .crisp-table th`, not bare `& th` (found while re-checking #691's
624
636
  own fix live): DataTable's root nests `Table`'s own wrapper, whose
625
- `table.recipe.ts` carries `& th, & td { padding: 0 12px }
626
- ` at the SAME specificity (.crisp-table th vs .crisp-datatable th, both one class + one element) -- a bare `& th` here only won by source order, which flipped the moment table.recipe.ts's own CSS file
627
- happened to land after datatable's in the build (alphabetical: datatable < table), silently reinstating 12px on every header cell in the whole product and making every interactive column's button
628
- double it to 24px. Scoping through `.crisp-table` adds a second
629
- class to the selector (0,2,1 beats 0,1,1) so this can never lose a
630
- specificity tie to source order again. */
637
+ `table.recipe.ts` carries `& th, & td { padding: 0 12px }` at the
638
+ SAME specificity (.crisp-table th vs .crisp-datatable th, both one
639
+ class + one element) -- a bare `& th` here only won by source
640
+ order, which flipped the moment table.recipe.ts's own CSS file happened to land after datatable's in the build (alphabetical:
641
+ datatable < table), silently reinstating 12px on every header cell
642
+ in the whole product and making every interactive column's button double it to 24px. Scoping through `.crisp-table` adds a second class to the selector (0,2,1 beats 0,1,1) so this can never lose a specificity tie to source order again. */
631
643
  & .crisp-table th { padding: 0; }
632
- /* Same wrapper trap as AUTHORING.md Lesson 40 (ColumnCalc's summary trigger): Menu wraps its trigger in a flex div that sizes to its content, so a long header grew past the column instead of filling it -- with nothing left to squeeze, the label's own ellipsis CSS (below) had no properly
633
- -sized box to truncate against, and the th's own overflow hard-clipped it with no "...". Fill the wrapper, scoped to th so Menu's default
634
- (correct everywhere else) is untouched. */
644
+ /* Same wrapper trap as AUTHORING.md Lesson 40 (ColumnCalc's summary trigger):
645
+ Menu wraps its trigger in a flex div that sizes to its content, so a
646
+ long header grew past the column instead of filling it -- with nothing
647
+ left to squeeze, the label's own ellipsis CSS (below) had no properly -sized box to truncate against, and the th's own overflow hard-clipped
648
+ it with no "...". Fill the wrapper, scoped to th so Menu's default (correct everywhere else) is untouched. */
635
649
  & th .crisp-menu { width: 100%; height: 100%; }
636
650
  & .crisp-datatable-th-inner { display: inline-flex; align-items: center; gap: var(--crisp-space-1-5); width: 100%; min-width: 0; height: 100%; }
637
651
  & .crisp-datatable-th-inner > svg { flex-shrink: 0; color: var(--crisp-fg-muted); }
638
- /* crisp/#605 follow-up: a header with neither a Menu nor a sort button
639
- (the primary/entity column's now-inert path, or any column with no columnMenu/sortable at all) renders .crisp-datatable-th-inner as a DIRECT child of th instead of nested inside a button -- and loses the button's own 12px inset, drifting left of the td's 12px base inset. Measured live on Attio's own Companies grid: the header label
640
- and the cell's own text land at the exact same x (335px from the viewport, both 40px from the column's left edge) via two different
641
- padding+icon+gap combinations -- Attio does not leave the inert
642
- primary header flush left either. The `>` combinator targets only
643
- the button-less case: Menu's own wrapper (`.crisp-menu`) and the plain sort `button` both put >=1 more level between `th` and this span, so their own 12px (already on the button) isn't doubled. */
652
+ /* crisp/#605 follow-up: a header with neither a Menu nor a sort button (the primary/entity column's now-inert path, or any column with no
653
+ columnMenu/sortable at all) renders .crisp-datatable-th-inner as a
654
+ DIRECT child of th instead of nested inside a button -- and loses
655
+ the button's own 12px inset, drifting left of the td's 12px base
656
+ inset. Measured live on Attio's own Companies grid: the header label and the cell's own text land at the exact same x (335px from the
657
+ viewport, both 40px from the column's left edge) via two different padding+icon+gap combinations -- Attio does not leave the inert primary header flush left either. The `>` combinator targets only the button-less case: Menu's own wrapper (`.crisp-menu`) and the
658
+ plain sort `button` both put >=1 more level between `th` and this
659
+ span, so their own 12px (already on the button) isn't doubled. */
644
660
  & th > .crisp-datatable-th-inner { box-sizing: border-box; padding: 0 var(--crisp-space-3); }
645
- /* The summary cell has NO padding of its own -- the calculation trigger
646
- owns the whole 246x37 area so a click anywhere in the cell opens the
647
- menu. Zero the cell's inset and let ColumnCalc supply the 12px. */
661
+ /* The summary cell has NO padding of its own -- the calculation trigger owns the whole 246x37 area so a click anywhere in the cell opens the menu. Zero the cell's inset and let ColumnCalc supply the 12px. */
648
662
  & tfoot td { padding: 0; }
649
- /* Group heading row -- a view is grouped under a subtle label with the bucket's count beside it. */
650
- & .crisp-datatable-viewsettings { display: inline-flex; align-items: center; gap: var(--crisp-space-1-5); height: var(--crisp-size-row); margin: 0; padding: 0 var(--crisp-space-2); border: none; border-radius: var(--crisp-radius-md); background: transparent; color: var(--crisp-fg-muted); font-family: inherit; font-size: var(--crisp-text-md); font-weight: var(--crisp-weight-medium); white-space: nowrap; cursor: pointer; transition: background-color .12s, color .12s; }
663
+ /* Group heading row -- a view is grouped under a subtle label with the
664
+ bucket's count beside it. */
665
+ & .crisp-datatable-viewsettings { display: inline-flex; align-items: center; gap: var(--crisp-space-1-5); height: var(--crisp-size-row); margin: 0; padding: 0 var(--crisp-space-2); border: none; border-radius: var(--crisp-radius-md); background: transparent; color: var(--crisp-fg-muted); font-family: inherit; font-size: var(--crisp-text-md); font-weight: var(--crisp-weight-medium); white-space: nowrap; cursor: pointer; transition: background-color .12s, color .12s; }
651
666
  & .crisp-datatable-viewsettings:hover { background: color-mix(in srgb, var(--crisp-fg-primary) 4%, transparent); color: var(--crisp-fg-primary); }
652
667
  & .crisp-datatable-group-label { padding: var(--crisp-space-1-5) var(--crisp-space-2) var(--crisp-space-1); font-size: var(--crisp-text-sm); font-weight: var(--crisp-weight-medium); color: var(--crisp-fg-muted); }
653
- & .crisp-datatable-group { height: var(--crisp-size-menuRow); padding: 0 var(--crisp-space-3); background: var(--crisp-bg-subtle); border-bottom: 1px solid var(--crisp-border-subtle); font-size: var(--crisp-text-md); font-weight: var(--crisp-weight-medium); color: color-mix(in srgb, var(--crisp-fg-primary) 63%, transparent); }
668
+ & .crisp-datatable-group { height: var(--crisp-size-menuRow); padding: 0 var(--crisp-space-3); background: var(--crisp-bg-subtle); border-bottom: 1px solid var(--crisp-border-subtle); font-size: var(--crisp-text-md); font-weight: var(--crisp-weight-medium); color: color-mix(in srgb, var(--crisp-fg-primary) 63%, transparent); }
654
669
  & .crisp-datatable-group-count { margin-inline-start: var(--crisp-space-1-5); color: var(--crisp-fg-muted); font-variant-numeric: tabular-nums; }
655
670
  & .crisp-datatable-th-rename { width: 100%; min-width: 0; height: var(--crisp-size-row); margin: 0; padding: 0 var(--crisp-space-1); border: none; border-radius: var(--crisp-radius-md); background: var(--crisp-bg-surface); box-shadow: inset 0 0 0 1px var(--crisp-bg-brand-solid); font-family: inherit; font-size: var(--crisp-text-lg); font-weight: var(--crisp-weight-medium); color: var(--crisp-fg-primary); outline: none; }
656
671
  & .crisp-datatable-th-rename:focus { box-shadow: inset 0 0 0 1px var(--crisp-bg-brand-solid); }
657
672
  & .crisp-datatable-th-label { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; min-width: 0; }
658
- /* #465: 12px per the comment above (and Attio's measured header cell padding: 0 12px) -- was still c.space["2"] (8px), drifted from its own documented value. */
673
+ /* #465: 12px per the comment above (and Attio's measured header cell
674
+ padding: 0 12px) -- was still c.space["2"] (8px), drifted from its
675
+ own documented value. */
659
676
  & .crisp-datatable-sort { display: flex; align-items: center; gap: var(--crisp-space-1-5); width: 100%; min-width: 0; height: 100%; margin: 0; padding: 0 var(--crisp-space-3); border: none; background: none; font: inherit; letter-spacing: inherit; color: inherit; text-align: start; cursor: pointer; }
660
- /* A right-aligned column's header keeps its field-icon + label packed at
661
- the START, matching its own values only in that the VALUES right-align
662
- -- Attio's own right-aligned header (measured live: "Twitter follower
663
- count") keeps the icon+label left and only the trailing column-menu icon sits pinned to the right edge, independent of the label's width
664
- (#406, reversing #393's own justify-content: flex-end here). The chevron's own margin-inline-start: auto below already pins IT to the
665
- end of a flex-start row with no override needed -- that's what pins the trailing icon on Attio too. */
677
+ /* A right-aligned column's header keeps its field-icon + label packed at the START, matching its own values only in that the VALUES right-align -- Attio's own right-aligned header (measured live: "Twitter follower
678
+ count") keeps the icon+label left and only the trailing column-menu
679
+ icon sits pinned to the right edge, independent of the label's width (#406, reversing #393's own justify-content: flex-end here). The
680
+ chevron's own margin-inline-start: auto below already pins IT to the end of a flex-start row with no override needed -- that's what pins
681
+ the trailing icon on Attio too. */
666
682
  & .crisp-datatable-chevron { margin-inline-start: auto; flex-shrink: 0; color: var(--crisp-fg-muted); opacity: 0; transition: opacity .12s; }
667
683
  & .crisp-datatable-sort:hover .crisp-datatable-chevron { opacity: 0.6; }
668
684
  & .crisp-datatable-sort[data-sorted] .crisp-datatable-chevron { opacity: 1; color: var(--crisp-fg-primary); }
669
685
  /* sticky first column(s) + horizontal scroll shadow — RecordsTable's feel */
670
686
  & th[data-sticky], & td[data-sticky] { position: sticky; z-index: 1; background: var(--crisp-bg-surface); }
671
- & th[data-sticky] { z-index: 3;
672
- } /* #554: a footer sticky-column cell sits in the bottom-sticky row (table.recipe.ts's
673
- `tfoot td { z-index: 2 }
674
- `), which `td[data-sticky]`'s own higher-specificity
675
- `z-index: 1` would otherwise undercut -- keep it level with the row it's in. */
687
+ & th[data-sticky] { z-index: 3; }
688
+ /* #554: a footer sticky-column cell sits in the bottom-sticky row (table.recipe.ts's
689
+ `tfoot td { z-index: 2 }`), which `td[data-sticky]`'s own higher-specificity `z-index: 1` would otherwise undercut -- keep it level with the row it's in. */
676
690
  & tfoot td[data-sticky] { z-index: 2; }
677
691
  & tbody tr[data-selected] td[data-sticky] { background: color-mix(in srgb, var(--crisp-bg-brand-solid) 10%, var(--crisp-bg-surface)); }
678
692
  & tbody tr:hover td[data-sticky] { background: color-mix(in srgb, var(--crisp-fg-primary) 4%, var(--crisp-bg-surface)); }
@@ -689,88 +703,98 @@
689
703
  & tbody tr[data-selected] td { background: color-mix(in srgb, var(--crisp-bg-brand-solid) 10%, var(--crisp-bg-surface)); }
690
704
  & tbody tr[data-selected]:hover td { background: color-mix(in srgb, var(--crisp-fg-primary) 4%, color-mix(in srgb, var(--crisp-bg-brand-solid) 10%, var(--crisp-bg-surface))); }
691
705
  & .crisp-datatable-editable:hover { background: color-mix(in srgb, var(--crisp-fg-primary) 4%, var(--crisp-bg-surface)); cursor: text; }
692
- /* crisp/#758: AI-enriched column marker, ported from RecordsTable's own already-measured value -- a full-bleed #9b69ff (bgPurple) wash at 4%, composited OVER whatever row-state background the cell already has (hover, selected), which is why it's an ::after overlay and not a
693
- plain background. */
706
+ /* crisp/#758: AI-enriched column marker, ported from RecordsTable's own
707
+ already-measured value -- a full-bleed #9b69ff (bgPurple) wash at 4%,
708
+ composited OVER whatever row-state background the cell already has
709
+ (hover, selected), which is why it's an ::after overlay and not a plain background. */
694
710
  & td[data-enriched]::after { content: ""; position: absolute; inset: 0; pointer-events: none; background: color-mix(in srgb, var(--crisp-bg-purple) 4%, transparent); }
695
711
  & .crisp-datatable-edit { width: 100%; height: 28px; margin: 0; padding: 0 var(--crisp-space-1-5); border: none; border-radius: var(--crisp-radius-sm); background: var(--crisp-bg-surface); box-shadow: inset 0 0 0 1px var(--crisp-bg-brand-solid); font-family: inherit; font-size: var(--crisp-text-lg); color: var(--crisp-fg-primary); outline: none; vertical-align: middle; }
696
712
  & .crisp-datatable-edit:focus { box-shadow: inset 0 0 0 1px var(--crisp-bg-brand-solid); }
697
- /* An async onEdit's in-flight state (#385) -- the field/picker dims and stops accepting input rather than disappearing, so the just-typed or just-chosen value stays visible while its commit is pending. Same treatment Input already gives its own disabled state. */
713
+ /* An async onEdit's in-flight state (#385) -- the field/picker dims and
714
+ stops accepting input rather than disappearing, so the just-typed or
715
+ just-chosen value stays visible while its commit is pending. Same
716
+ treatment Input already gives its own disabled state. */
698
717
  & .crisp-datatable-edit:disabled { opacity: 0.5; cursor: not-allowed; }
699
718
  & .crisp-datatable-edit-pending { flex-shrink: 0; margin-inline-start: var(--crisp-space-1); color: var(--crisp-fg-muted); vertical-align: middle; }
700
- /* The rejection's message, beside the cell's own content -- a sibling in the TableCell, never nested inside a column's own render output, so a
701
- hover-only wrapper (a Tooltip, say) around a custom render can't hide it. Block-level: the row grows to fit it rather than floating over the row below, so it can never end up clipped by the scroll container's overflow the way a position:fixed escape would need
702
- guarding against. */
719
+ /* The rejection's message, beside the cell's own content -- a sibling in
720
+ the TableCell, never nested inside a column's own render output, so a hover-only wrapper (a Tooltip, say) around a custom render can't hide
721
+ it. Block-level: the row grows to fit it rather than floating over
722
+ the row below, so it can never end up clipped by the scroll
723
+ container's overflow the way a position:fixed escape would need guarding against. */
703
724
  & .crisp-datatable-edit-error { display: block; padding: var(--crisp-space-0-5) 0 0; font-size: var(--crisp-text-sm); color: var(--crisp-bg-danger); }
704
- /* editor: "select"/"switch" -- always live, no separate edit mode, so
705
- the cell IS the picker: same chromeless-fill treatment as
706
- RecordsTable's own --picker cells (measured there already; DataTable shares RecordsTable's grid, Golden Rule 0). */
725
+ /* editor: "select"/"switch" -- always live, no separate edit mode, so the cell IS the picker: same chromeless-fill treatment as RecordsTable's own --picker cells (measured there already; DataTable
726
+ shares RecordsTable's grid, Golden Rule 0). */
707
727
  & .crisp-datatable-td--picker { padding: 0; }
708
728
  & .crisp-datatable-edit-picker { display: flex; align-items: center; gap: var(--crisp-space-1); height: var(--crisp-size-tableRow); padding: 0 var(--crisp-space-2); }
709
729
  & .crisp-datatable-edit-picker .crisp-select-trigger { flex: 1; min-width: 0; height: 100%; min-height: 0; border-radius: 0; box-shadow: none; padding: 0; }
710
730
  & .crisp-datatable-td--picker .crisp-datatable-edit-error { padding-inline-start: var(--crisp-space-2); }
711
- /* reference picker: a leading 16px avatar per option, plus a trailing
712
- "Create X" row when the consumer supplies one (#449) -- same measured
713
- values as RecordsTable's own .crisp-recordstable-refoption*. */
731
+ /* reference picker: a leading 16px avatar per option, plus a trailing "Create X" row when the consumer supplies one (#449) -- same measured values as RecordsTable's own .crisp-recordstable-refoption*. */
714
732
  & .crisp-datatable-refoption { display: flex; align-items: center; gap: var(--crisp-space-2); min-width: 0; overflow: hidden; text-overflow: ellipsis; }
715
733
  & .crisp-datatable-refoption-avatar { flex-shrink: 0; }
716
734
  & .crisp-datatable-refoption--create { color: var(--crisp-fg-muted); }
717
735
  & .crisp-datatable-empty { padding: var(--crisp-space-8) var(--crisp-space-3); text-align: center; color: var(--crisp-fg-muted); font-size: var(--crisp-text-lg); }
718
- /* #579: click-to-expand overlay for a truncated cell -- a read-only, full-value card, settled live as click (not Attio's usual
719
- hover-expand) and measured position: absolute, unlike this file's floating chrome (Menu/Select popovers) which portals and escapes the scroll container. Deliberately NOT the edit-error pattern above (grow the row, block-level, never clipped): a virtualized grid assumes a fixed row height for its scroll math (owned by the grid core, not this cell), so growing a row here would fight it. Anchored to the SAME column width the cell itself renders at (measured: Attio's own overlay came out only slightly wider than the
720
- triggering cell's clientWidth -- the overshoot is this padding), wrapping onto as many lines as the value needs; it CAN be clipped by `.crisp-table-scroll`'s own `overflow: auto` if it grows past the
721
- visible viewport, same as the edit-error comment above already
722
- notes for any position:absolute escape -- unmeasured against Attio
723
- for this exact case, flagged rather than guessed. */
736
+ /* #579: click-to-expand overlay for a truncated cell -- a read-only,
737
+ full-value card, settled live as click (not Attio's usual hover-expand) and measured position: absolute, unlike this file's
738
+ floating chrome (Menu/Select popovers) which portals and escapes the
739
+ scroll container. Deliberately NOT the edit-error pattern above
740
+ (grow the row, block-level, never clipped): a virtualized grid
741
+ assumes a fixed row height for its scroll math (owned by the grid
742
+ core, not this cell), so growing a row here would fight it. Anchored
743
+ to the SAME column width the cell itself renders at (measured:
744
+ Attio's own overlay came out only slightly wider than the triggering cell's clientWidth -- the overshoot is this padding),
745
+ wrapping onto as many lines as the value needs; it CAN be clipped by
746
+ `.crisp-table-scroll`'s own `overflow: auto` if it grows past the visible viewport, same as the edit-error comment above already notes for any position:absolute escape -- unmeasured against Attio for this exact case, flagged rather than guessed. */
724
747
  & td[data-cell-expanded] { overflow: visible; z-index: 7; }
725
- & .crisp-datatable-cell-overlay {
726
- position: absolute; inset-inline-start: 0; top: 0; width: 100%; box-sizing: border-box;
727
- padding: var(--crisp-space-2) var(--crisp-space-3); background: var(--crisp-bg-surface);
728
- border: 1px solid var(--crisp-bg-brand-solid); border-radius: 4px;
729
- font-family: var(--crisp-font-sans); font-size: var(--crisp-text-lg); line-height: 20px; font-weight: var(--crisp-weight-medium);
730
- color: var(--crisp-fg-primary); white-space: normal; word-break: break-word; cursor: default;
731
- }
748
+ & .crisp-datatable-cell-overlay { position: absolute; inset-inline-start: 0; top: 0; width: 100%; box-sizing: border-box; padding: var(--crisp-space-2) var(--crisp-space-3); background: var(--crisp-bg-surface); border: 1px solid var(--crisp-bg-brand-solid); border-radius: 4px; font-family: var(--crisp-font-sans); font-size: var(--crisp-text-lg); line-height: 20px; font-weight: var(--crisp-weight-medium); color: var(--crisp-fg-primary); white-space: normal; word-break: break-word; cursor: default; }
732
749
  /* fixed layout so column widths (and resize) are exact — like RecordsTable */
733
750
  & table { table-layout: fixed; }
734
- /* read-only/computed column marker (#596) -- sits just inside the
735
- resize handle, same absolute-positioned-against-the-th approach the
736
- handle itself uses, since th has zero padding and the button/inner
737
- row fills the whole cell with no room left for a trailing sibling. */
751
+ /* read-only/computed column marker (#596) -- sits just inside the resize handle, same absolute-positioned-against-the-th approach the handle itself uses, since th has zero padding and the button/inner row fills the whole cell with no room left for a trailing sibling. */
738
752
  & .crisp-datatable-readonly { position: absolute; top: 50%; inset-inline-end: 11px; transform: translateY(-50%); color: var(--crisp-fg-muted); pointer-events: none; z-index: 5; }
739
753
  /* column resize grip */
740
754
  & .crisp-datatable-resize { position: absolute; top: 0; inset-inline-end: -3px; width: 9px; height: 100%; cursor: col-resize; z-index: 6; touch-action: none; }
741
755
  & .crisp-datatable-resize::after { content: ""; position: absolute; top: 0; inset-inline-start: 3px; width: 2px; height: 100%; background: var(--crisp-bg-brand-solid); opacity: 0; transition: opacity .12s; }
742
756
  & .crisp-datatable-resize:hover::after, & .crisp-datatable-resize[data-resizing]::after { opacity: 1; }
743
- /* crisp/#731 -- column drag-to-reorder. Measured live against Attio's own grid (the issue's own comment): the dragged header keeps full
744
- opacity and the plain `pointer` cursor throughout (Attio draws no
745
- ghost and no "grabbing" cursor of its own), so this adds nothing to
746
- `[data-dragging]` itself -- the ONLY new paint is the drop-indicator
747
- line, a 3px bar in the same measured brand blue the resize handle's own hover state (above) already uses, positioned on whichever edge of the hovered header the pointer is closer to. */
757
+ /* crisp/#731 -- column drag-to-reorder. Measured live against Attio's
758
+ own grid (the issue's own comment): the dragged header keeps full opacity and the plain `pointer` cursor throughout (Attio draws no ghost and no "grabbing" cursor of its own), so this adds nothing to `[data-dragging]` itself -- the ONLY new paint is the drop-indicator line, a 3px bar in the same measured brand blue the resize handle's
759
+ own hover state (above) already uses, positioned on whichever edge of
760
+ the hovered header the pointer is closer to. */
748
761
  & th[data-dragging] { cursor: pointer; }
749
- & th[data-drop-before]::before, & th[data-drop-after]::after { content: ""; position: absolute; top: 0; width: 3px; height: 100%; background: var(--crisp-bg-brand-solid); z-index: 6; pointer-events: none; }
762
+ & th[data-drop-before]::before, & th[data-drop-after]::after {
763
+ content: ""; position: absolute; top: 0; width: 3px; height: 100%;
764
+ background: var(--crisp-bg-brand-solid); z-index: 6; pointer-events: none;
765
+ }
750
766
  & th[data-drop-before]::before { inset-inline-start: -1.5px; }
751
767
  & th[data-drop-after]::after { inset-inline-end: -1.5px; }
752
768
  /* typed cells — entity (avatar + name), colour-coded tags, numbers, dates */
753
769
  & .crisp-datatable-entity { display: flex; align-items: center; gap: var(--crisp-space-1-5); min-width: 0; color: var(--crisp-fg-primary); font-weight: var(--crisp-weight-medium); }
754
- /* crisp/#601 supersession follow-up -- the same flex-row shape .crisp-datatable-entity gives its own avatar+name+actions, but structural only (no entity-specific colour/weight): lets onOpenComments/onOpenPreview compose alongside ANY column's own
755
- content (a custom render, or a non-entity built-in type), not just
756
- type: "entity"'s own inline rendering. */
770
+ /* crisp/#601 supersession follow-up -- the same flex-row shape
771
+ .crisp-datatable-entity gives its own avatar+name+actions, but
772
+ structural only (no entity-specific colour/weight): lets
773
+ onOpenComments/onOpenPreview compose alongside ANY column's own content (a custom render, or a non-entity built-in type), not just type: "entity"'s own inline rendering. */
757
774
  & .crisp-datatable-cell-actions-row { display: flex; align-items: center; gap: var(--crisp-space-1-5); min-width: 0; width: 100%; }
758
- /* crisp/#601, G-050: measured LIVE (getComputedStyle on the name text, plain hover vs rest) that Attio's underline is unconditional, not a
759
- hover reveal -- identical at rest and on hover: text-decoration-color
760
- is rgba(0,0,0,0.09), a 9% tint, not the name's own full-contrast text colour. The previous :hover-gated, full-contrast rule here was itself unmeasured and wrong in two ways at once. */
775
+ /* crisp/#601, G-050: measured LIVE (getComputedStyle on the name text,
776
+ plain hover vs rest) that Attio's underline is unconditional, not a hover reveal -- identical at rest and on hover: text-decoration-color is rgba(0,0,0,0.09), a 9% tint, not the name's own full-contrast text
777
+ colour. The previous :hover-gated, full-contrast rule here was itself
778
+ unmeasured and wrong in two ways at once. */
761
779
  & .crisp-datatable-entity-name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; text-decoration: underline; text-decoration-color: color-mix(in srgb, var(--crisp-fg-primary) 9%, transparent); }
762
780
  & 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%; }
763
- /* crisp/#601, G-048: two 20px ghost icons, right-aligned in the primary column, revealed on ROW hover (the issue's own title says "row
764
- hover", and a live check confirmed hovering well past the name --
765
- not just the icons' own hit target -- still reveals them) or keyboard focus. Measured live: border-radius 6px, icon colour rgba(0,0,0,0.55) at rest, darkening to the full text colour with a rgba(0,0,0,0.04) background on the icon's OWN hover -- both
766
- expressed as color-mix against fgPrimary so they stay correct in
767
- dark mode instead of a literal black that would invert. */
781
+ /* crisp/#601, G-048: two 20px ghost icons, right-aligned in the primary
782
+ column, revealed on ROW hover (the issue's own title says "row
783
+ hover", and a live check confirmed hovering well past the name -- not just the icons' own hit target -- still reveals them) or
784
+ keyboard focus. Measured live: border-radius 6px, icon colour
785
+ rgba(0,0,0,0.55) at rest, darkening to the full text colour with a
786
+ rgba(0,0,0,0.04) background on the icon's OWN hover -- both expressed as color-mix against fgPrimary so they stay correct in dark mode instead of a literal black that would invert. */
768
787
  & .crisp-datatable-entity-actions { display: flex; align-items: center; gap: var(--crisp-space-1-5); margin-inline-start: auto; flex-shrink: 0; opacity: 0; }
769
788
  & tr:hover .crisp-datatable-entity-actions, & .crisp-datatable-entity-actions:focus-within { opacity: 1; }
770
789
  & .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: 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; }
771
790
  & .crisp-datatable-entity-action:hover { background: color-mix(in srgb, var(--crisp-fg-primary) 4%, transparent); color: var(--crisp-fg-primary); }
772
791
  & .crisp-datatable-avatar { width: var(--crisp-size-icon); height: var(--crisp-size-icon); border-radius: var(--crisp-radius-sm); flex-shrink: 0; display: inline-flex; align-items: center; justify-content: center; font-size: 10px; line-height: 1; font-weight: var(--crisp-weight-semibold); text-transform: uppercase; color: var(--crisp-fg-on-brand); background: var(--crisp-bg-brand-solid); }
773
- & .crisp-datatable-avatar[data-tone="success"] { background: var(--crisp-bg-success); } & .crisp-datatable-avatar[data-tone="warning"] { background: var(--crisp-bg-warning); } & .crisp-datatable-avatar[data-tone="danger"] { background: var(--crisp-bg-danger); } & .crisp-datatable-avatar[data-tone="purple"] { background: var(--crisp-bg-purple); } & .crisp-datatable-avatar[data-tone="cyan"] { background: var(--crisp-bg-cyan); } & .crisp-datatable-avatar[data-tone="orange"] { background: var(--crisp-bg-orange); }
792
+ & .crisp-datatable-avatar[data-tone="success"] { background: var(--crisp-bg-success); }
793
+ & .crisp-datatable-avatar[data-tone="warning"] { background: var(--crisp-bg-warning); }
794
+ & .crisp-datatable-avatar[data-tone="danger"] { background: var(--crisp-bg-danger); }
795
+ & .crisp-datatable-avatar[data-tone="purple"] { background: var(--crisp-bg-purple); }
796
+ & .crisp-datatable-avatar[data-tone="cyan"] { background: var(--crisp-bg-cyan); }
797
+ & .crisp-datatable-avatar[data-tone="orange"] { background: var(--crisp-bg-orange); }
774
798
  & .crisp-datatable-avatar img { width: 100%; height: 100%; border-radius: inherit; object-fit: cover; }
775
799
  & .crisp-datatable-tags { display: flex; align-items: center; gap: var(--crisp-space-1); overflow: hidden; }
776
800
  & .crisp-datatable-tag { display: inline-flex; align-items: center; height: var(--crisp-size-chip); padding: 0 var(--crisp-space-1); border-radius: var(--crisp-radius-chip); font-size: var(--crisp-text-lg); line-height: 20px; font-weight: var(--crisp-weight-medium); white-space: nowrap; background: color-mix(in srgb, var(--crisp-fg-primary) 6%, transparent); color: color-mix(in srgb, var(--crisp-fg-primary) 82%, transparent); }
@@ -783,24 +807,39 @@
783
807
  & .crisp-datatable-tag[data-tone="orange"] { background: color-mix(in srgb, var(--crisp-bg-orange) 16%, transparent); color: color-mix(in srgb, var(--crisp-bg-orange) 82%, var(--crisp-fg-primary)); }
784
808
  & .crisp-datatable-num { font-variant-numeric: tabular-nums; }
785
809
  & .crisp-datatable-muted { color: var(--crisp-fg-muted); }
786
- /* url/email cell types (#654) -- same measured values as RecordsTable's own .crisp-recordstable-link, which this mirrors: no underline at rest, underline on hover, own-colour text rather than a browser-blue link. */
810
+ /* url/email cell types (#654) -- same measured values as RecordsTable's
811
+ own .crisp-recordstable-link, which this mirrors: no underline at
812
+ rest, underline on hover, own-colour text rather than a browser-blue
813
+ link. */
787
814
  & .crisp-datatable-link { color: var(--crisp-fg-primary); text-decoration: none; overflow: hidden; text-overflow: ellipsis; }
788
815
  & .crisp-datatable-link:hover { text-decoration: underline; }
789
- /* rating cell type/editor (#760) -- measured live: 16px stars on an 18px pitch (a 2px gap), an empty star at rgba(0,0,0,0.1), a committed/filled star at brand600 (--crisp-bg-brand-solid, an exact match to the measured #266df0), and a distinct hover-preview colour -- rgb(33,91,196) -- that's close to but NOT brand700/bgBrandSolidPressed
790
- (#1f5fd6); kept as its own literal rather than forced onto that token,
791
- since the two were measured stable and different. The read-only svg
792
- and the editable button's own inner svg both carry the crisp-datatable-rating-star class on the svg itself (not the button), so one set of fill rules covers both -- the button only adds its own reset + cursor via a separate -btn class. */
816
+ /* phone cell type/editor (#765) -- measured live: the dial-code prefix
817
+ is a muted rgba(0,0,0,0.63) at 14px, matching every other muted-text
818
+ value already measured elsewhere in this file. */
819
+ & .crisp-datatable-phone { display: inline-flex; align-items: center; gap: var(--crisp-space-1); }
820
+ & .crisp-datatable-phone-dial { color: color-mix(in srgb, var(--crisp-fg-primary) 63%, transparent); }
821
+ & .crisp-datatable-phone-editor { display: flex; align-items: center; gap: var(--crisp-space-1); height: var(--crisp-size-tableRow); padding: 0 var(--crisp-space-2); }
822
+ & .crisp-datatable-phone-editor input { flex: 1; min-width: 0; }
823
+ /* datetime cell editor (#762) -- DatePickerField + TimePicker side by side. */
824
+ & .crisp-datatable-datetime-editor { display: flex; align-items: center; gap: var(--crisp-space-1); height: var(--crisp-size-tableRow); padding: 0 var(--crisp-space-2); }
825
+ /* rating cell type/editor (#760) -- measured live: 16px stars on an
826
+ 18px pitch (a 2px gap), an empty star at rgba(0,0,0,0.1), a
827
+ committed/filled star at brand600 (--crisp-bg-brand-solid, an exact
828
+ match to the measured #266df0), and a distinct hover-preview colour
829
+ -- rgb(33,91,196) -- that's close to but NOT brand700/bgBrandSolidPressed (#1f5fd6); kept as its own literal rather than forced onto that token, since the two were measured stable and different. The read-only svg and the editable button's own inner svg both carry the
830
+ crisp-datatable-rating-star class on the svg itself (not the
831
+ button), so one set of fill rules covers both -- the button only
832
+ adds its own reset + cursor via a separate -btn class. */
793
833
  & .crisp-datatable-rating { display: inline-flex; align-items: center; gap: 2px; }
794
834
  & .crisp-datatable-rating-star { display: block; }
795
835
  & .crisp-datatable-rating-star path { fill: rgba(0, 0, 0, 0.1); }
796
836
  & .crisp-datatable-rating-star[data-filled] path { fill: var(--crisp-bg-brand-solid); }
797
837
  & .crisp-datatable-rating-star[data-hovering] path { fill: #215bc4; }
798
838
  & .crisp-datatable-rating-star-btn { display: inline-flex; padding: 0; margin: 0; border: none; background: none; font-family: inherit; cursor: pointer; }
799
- /* row drag-to-reorder -- a Settings > Objects > Attributes screen measures a leading 20px grip column, a 16px grip icon, and the row's own
800
- hairline doubling as the drop indicator (it tints toward the brand
801
- colour as the active drop target). The affordance + geometry are
802
- measured; the drag MECHANISM is crisp's existing house idiom -- HTML5 draggable + dataTransfer, the same one column reorder already uses (RecordsTable's reorderColumn) -- not a proprietary
803
- pointer-events implementation. */
839
+ /* row drag-to-reorder -- a Settings > Objects > Attributes screen measures
840
+ a leading 20px grip column, a 16px grip icon, and the row's own hairline doubling as the drop indicator (it tints toward the brand colour as the active drop target). The affordance + geometry are measured; the drag MECHANISM is crisp's existing house idiom -- HTML5
841
+ draggable + dataTransfer, the same one column reorder already uses
842
+ (RecordsTable's reorderColumn) -- not a proprietary pointer-events implementation. */
804
843
  & .crisp-datatable-col-grip { width: 20px; padding: 0; text-align: center; }
805
844
  & .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: var(--crisp-fg-muted); cursor: grab; }
806
845
  & .crisp-datatable-grip:active { cursor: grabbing; }
@@ -2987,247 +3026,6 @@
2987
3026
  }
2988
3027
  }
2989
3028
 
2990
- .crisp-recordstable {
2991
- box-sizing: border-box; position: relative; display: flex; flex-direction: column; min-height: 0; height: 100%; width: 100%; font-family: var(--crisp-font-sans); -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale;
2992
- & * { box-sizing: border-box; }
2993
- /* toolbar */
2994
- & .crisp-recordstable-toolbar { display: flex; align-items: center; gap: var(--crisp-space-2); padding: 0 var(--crisp-space-3) var(--crisp-space-2); }
2995
- & .crisp-recordstable-toolbar[data-inline] { padding-bottom: 0; }
2996
- & .crisp-recordstable-search { display: flex; align-items: center; gap: var(--crisp-space-1-5); height: var(--crisp-size-row); padding: 0 var(--crisp-space-2-5); border: none; border-radius: var(--crisp-radius-md); background: var(--crisp-bg-surface); box-shadow: inset 0 0 0 1px var(--crisp-border-subtle); color: var(--crisp-fg-muted); }
2997
- & .crisp-recordstable-search svg { flex-shrink: 0; }
2998
- & .crisp-recordstable-search input { border: none; outline: none; background: transparent; font-family: inherit; font-size: var(--crisp-text-md); color: var(--crisp-fg-primary); width: 180px; }
2999
- & .crisp-recordstable-search input::placeholder { color: var(--crisp-fg-muted); }
3000
- & .crisp-recordstable-tool { display: inline-flex; align-items: center; gap: var(--crisp-space-1); height: var(--crisp-size-row); margin: 0; padding: 0 var(--crisp-space-2); border: none; border-radius: var(--crisp-radius-md); background: var(--crisp-bg-surface); box-shadow: inset 0 0 0 1px var(--crisp-border-divider); color: var(--crisp-fg-primary); font-family: inherit; font-size: var(--crisp-text-md); font-weight: var(--crisp-weight-medium); white-space: nowrap; cursor: pointer; transition: background-color .12s; }
3001
- & .crisp-recordstable-tool:hover { background: var(--crisp-bg-raised); }
3002
- & .crisp-recordstable-tool svg { flex-shrink: 0; color: var(--crisp-fg-muted); }
3003
- /* crisp/#772: re-measured live on Sort with an actual sort applied -- an active trigger's own fill is white (bgSurface), the same as the
3004
- base .tool rule above now already sets; this override existed to
3005
- distinguish active from a base state that used to be transparent.
3006
- Kept explicit (not just deleted) so a future change to the base
3007
- rule's background can't silently change this one too. */
3008
- & .crisp-recordstable-tool--active { background: var(--crisp-bg-surface); }
3009
- & .crisp-recordstable-tool--ghost { box-shadow: none; background: color-mix(in srgb, var(--crisp-fg-primary) 3%, transparent); color: var(--crisp-fg-muted); }
3010
- & .crisp-recordstable-tool--ghost:hover { background: color-mix(in srgb, var(--crisp-fg-primary) 6%, transparent); }
3011
- /* crisp/#772 follow-up, re-measured live 2026-09-25: an EMPTY Sort/Filter
3012
- trigger (no sort/condition set) isn't a solid-ring chip at all -- it's
3013
- a dashed ::before (1px dashed, border-radius: inherit, absolutely
3014
- positioned inset: 0) over a barely-there fill, confirmed on Attio's own empty Filter chip (its ::before border computes to exactly "1px dashed rgba(0, 0, 0, 0.06)"). Once a value IS set, the trigger drops the dashed pseudo and picks up the base tool's own solid ring
3015
- instead (the --active modifier, unchanged) -- confirmed on Sort with
3016
- an actual sort applied. color-mix (not the raw rgba Attio computes
3017
- to) so both the border and the fill flip correctly in dark mode, the
3018
- same reasoning --ghost above already uses. Only wired into
3019
- TableToolbar itself (tabletoolbar.tsx) this pass -- RecordsTable's own Sort/Filter triggers share this same chrome function but are deprecated, not touched here. */
3020
- & .crisp-recordstable-tool--dashed { position: relative; box-shadow: none; background: color-mix(in srgb, var(--crisp-fg-primary) 2%, transparent); }
3021
- & .crisp-recordstable-tool--dashed::before { content: ""; position: absolute; inset: 0; border-radius: inherit; border: 1px dashed color-mix(in srgb, var(--crisp-fg-primary) 6%, transparent); pointer-events: none; }
3022
- & .crisp-recordstable-tool--dashed:hover { background: color-mix(in srgb, var(--crisp-fg-primary) 4%, transparent); }
3023
- & .crisp-recordstable-toolgroup { position: relative; display: inline-flex; }
3024
- & .crisp-recordstable-divider { flex-shrink: 0; width: 1px; height: 20px; background: var(--crisp-border-subtle); }
3025
- & .crisp-recordstable-menu { position: absolute; top: calc(100% + var(--crisp-space-1)); left: 0; z-index: 30; min-width: 232px; padding: var(--crisp-space-1); border-radius: var(--crisp-radius-xl); background: var(--crisp-bg-popover); box-shadow: inset 0 0 0 1px var(--crisp-ring-popover), 0 0 0 1px var(--crisp-shadow-popover1), 0 4px 8px -4px var(--crisp-shadow-popover2), 0 4px 12px -2px var(--crisp-shadow-popover3); display: flex; flex-direction: column; gap: var(--crisp-space-px); transform-origin: top; opacity: 1; transform: none; transition: opacity .13s ease, transform .13s ease; }
3026
- & .crisp-recordstable-menu { @starting-style { opacity: 0; transform: scale(0.96); } }
3027
- & .crisp-recordstable-ctx { z-index: 60; }
3028
- & .crisp-recordstable-th[data-drop] { box-shadow: inset 2px 0 0 var(--crisp-bg-brand-solid); }
3029
- & .crisp-recordstable-th-inner[draggable="true"]:active { cursor: grabbing; }
3030
- & .crisp-recordstable-menu-head { padding: var(--crisp-space-1) var(--crisp-space-2) var(--crisp-space-0-5); font-size: var(--crisp-text-sm); font-weight: var(--crisp-weight-medium); color: var(--crisp-fg-muted); }
3031
- & .crisp-recordstable-sortpop { min-width: 480px; overflow: visible; padding: var(--crisp-space-3) var(--crisp-space-1-5); gap: var(--crisp-space-2); }
3032
- & .crisp-recordstable-sort-empty { padding: var(--crisp-space-1) var(--crisp-space-1) var(--crisp-space-2); font-size: var(--crisp-text-md); color: var(--crisp-fg-muted); }
3033
- & .crisp-recordstable-sort-row { display: flex; align-items: center; gap: var(--crisp-space-1-5); }
3034
- & .crisp-recordstable-sort-grip { display: inline-flex; align-items: center; justify-content: center; width: 16px; height: 28px; flex-shrink: 0; margin-right: calc(-1 * var(--crisp-space-1)); padding: 0; border: none; background: none; color: var(--crisp-fg-muted); cursor: grab; }
3035
- & .crisp-recordstable-sort-grip:active { cursor: grabbing; }
3036
- & .crisp-recordstable-sort-grip:hover { color: var(--crisp-fg-primary); }
3037
- & .crisp-recordstable-sort-optlabel { display: inline-flex; align-items: center; gap: var(--crisp-space-1-5); min-width: 0; }
3038
- & .crisp-recordstable-sort-optlabel svg { flex-shrink: 0; color: var(--crisp-fg-muted); }
3039
- & .crisp-recordstable-sort-field { flex: 1; min-width: 0; }
3040
- & .crisp-recordstable-sort-dir { width: 136px; flex-shrink: 0; }
3041
- & .crisp-recordstable-sort-field .crisp-select-trigger, & .crisp-recordstable-sort-dir .crisp-select-trigger { height: 28px; padding-right: var(--crisp-space-1); }
3042
- & .crisp-recordstable-sort-remove { display: inline-flex; align-items: center; justify-content: center; width: 28px; height: 28px; flex-shrink: 0; padding: 0; margin: 0; border: none; border-radius: var(--crisp-radius-md); background: none; color: var(--crisp-fg-muted); cursor: pointer; transition: background-color .12s, color .12s; }
3043
- & .crisp-recordstable-sort-remove:hover { background: var(--crisp-bg-raised); color: var(--crisp-fg-primary); }
3044
- & .crisp-recordstable-sort-addrow { display: flex; padding: var(--crisp-space-0-5) 0; }
3045
- & .crisp-recordstable-sort-add { display: inline-flex; align-items: center; gap: var(--crisp-space-1-5); height: 28px; padding: 0 var(--crisp-space-2) 0 var(--crisp-space-1-5); margin: 0; border: none; border-radius: var(--crisp-radius-md); background: var(--crisp-bg-surface); box-shadow: inset 0 0 0 1px var(--crisp-ring-neutral), 0 0 2px rgba(28,40,64,0.18), 0 1px 3px rgba(0,0,0,0.04); font-family: inherit; font-size: var(--crisp-text-lg); font-weight: var(--crisp-weight-regular); color: var(--crisp-fg-primary); cursor: pointer; transition: background-color .12s; }
3046
- & .crisp-recordstable-sort-add:hover { background: var(--crisp-bg-raised); }
3047
- & .crisp-recordstable-sort-add svg { flex-shrink: 0; color: var(--crisp-fg-muted); }
3048
- & .crisp-recordstable-sort-foot { display: flex; padding: var(--crisp-space-1-5) var(--crisp-space-1) var(--crisp-space-0-5); }
3049
- & .crisp-recordstable-sort-learn { display: inline-flex; align-items: center; gap: var(--crisp-space-1-5); font-size: var(--crisp-text-lg); color: color-mix(in srgb, var(--crisp-fg-primary) 63%, transparent); text-decoration: none; }
3050
- & .crisp-recordstable-sort-learn svg { flex-shrink: 0; }
3051
- & .crisp-recordstable-sort-learn:hover { color: var(--crisp-fg-primary); }
3052
- & .crisp-recordstable-menu-item { display: flex; align-items: center; gap: var(--crisp-space-1-5); width: 100%; height: var(--crisp-size-menuRow); margin: 0; padding: 0 var(--crisp-space-2); border: none; background: none; border-radius: var(--crisp-radius-md); font-family: inherit; font-size: var(--crisp-text-lg); line-height: 20px; font-weight: var(--crisp-weight-medium); color: var(--crisp-fg-primary); text-align: left; text-decoration: none; cursor: pointer; }
3053
- & .crisp-recordstable-menu-item:hover { background: var(--crisp-bg-raised); }
3054
- & .crisp-recordstable-menu-item:disabled { opacity: 0.4; cursor: default; }
3055
- & .crisp-recordstable-menu-item:disabled:hover { background: none; }
3056
- & .crisp-recordstable-menu-item svg { flex-shrink: 0; color: var(--crisp-fg-muted); }
3057
- & .crisp-recordstable-menu-item[data-on] svg { color: var(--crisp-bg-brand-solid); }
3058
- & .crisp-recordstable-menu-item .crisp-recordstable-menu-check { margin-left: auto; }
3059
- & .crisp-recordstable-menu-input { height: var(--crisp-size-row); margin: var(--crisp-space-0-5); padding: 0 var(--crisp-space-2); border: none; border-radius: var(--crisp-radius-md); box-shadow: inset 0 0 0 1px var(--crisp-border-subtle); background: var(--crisp-bg-surface); font-family: inherit; font-size: var(--crisp-text-lg); color: var(--crisp-fg-primary); outline: none; }
3060
- & .crisp-recordstable-menu-sep { height: 1px; border: none; background: var(--crisp-border-subtle); margin: var(--crisp-space-0-5) 0; }
3061
- /* #592: the Filter field picker's nested-scope header (back control +
3062
- search, once drilled into a nestedColumns column) and a search
3063
- result's own breadcrumb row ("Team › Twitter follower count", surfaced by the SAME query matching a nested column). menu-back reuses the established 28px icon-only control pattern this file already uses twice (sort-remove, the kebab segment) rather than an independent measurement of Attio's own back arrow. The breadcrumb
3064
- colours ARE measured live: the parent segment muted at the same
3065
- exact color-mix 63% sort-learn above already uses, the
3066
- separator chevron and leaf segment both full fgPrimary (the
3067
- generic menu-item svg rule below defaults every icon to
3068
- fgMuted, so the separator needs its own override to read
3069
- correctly here). */
3070
- & .crisp-recordstable-menu-nesthead { display: flex; align-items: center; gap: var(--crisp-space-1); margin: var(--crisp-space-0-5); }
3071
- & .crisp-recordstable-menu-nesthead .crisp-recordstable-menu-input { flex: 1; margin: 0; }
3072
- & .crisp-recordstable-menu-back { display: inline-flex; align-items: center; justify-content: center; width: 28px; height: 28px; flex-shrink: 0; padding: 0; border: none; border-radius: var(--crisp-radius-md); background: none; color: var(--crisp-fg-muted); cursor: pointer; }
3073
- & .crisp-recordstable-menu-back:hover { background: var(--crisp-bg-raised); color: var(--crisp-fg-primary); }
3074
- & .crisp-recordstable-menu-item--expand { justify-content: space-between; }
3075
- & .crisp-recordstable-menu-item-path { display: inline-flex; align-items: center; gap: 2px; min-width: 0; overflow: hidden; }
3076
- & .crisp-recordstable-menu-item-path-parent { flex-shrink: 0; overflow: hidden; text-overflow: ellipsis; max-width: 100px; color: color-mix(in srgb, var(--crisp-fg-primary) 63%, transparent); }
3077
- & .crisp-recordstable-menu-item-path-sep { flex-shrink: 0; color: var(--crisp-fg-primary); }
3078
- & .crisp-recordstable-menu-item-path > span:last-child { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
3079
- & .crisp-recordstable-menu--up { top: auto; bottom: calc(100% + var(--crisp-space-1)); transform-origin: bottom; }
3080
- & .crisp-recordstable-menu--right { left: auto; right: 0; }
3081
- & .crisp-recordstable-menu--fixed { position: fixed; top: auto; left: auto; z-index: 40; }
3082
- & .crisp-recordstable-menu--hdr { min-width: 200px; }
3083
- & .crisp-recordstable-menu--op { min-width: 0; }
3084
- & .crisp-recordstable-menu-item--danger { color: var(--crisp-bg-danger); }
3085
- & .crisp-recordstable-menu-item--danger svg { color: var(--crisp-bg-danger); }
3086
- & .crisp-recordstable-menu-item--danger:hover { background: color-mix(in srgb, var(--crisp-bg-danger) 10%, transparent); }
3087
- & .crisp-recordstable-menu-check { margin-left: auto; color: var(--crisp-bg-brand-solid); }
3088
- & .crisp-recordstable-fchip { display: inline-flex; align-items: center; height: var(--crisp-size-row); padding-right: var(--crisp-space-1); border-radius: var(--crisp-radius-md); box-shadow: inset 0 0 0 1px var(--crisp-border-default); background: var(--crisp-bg-surface); font-size: var(--crisp-text-md); color: var(--crisp-fg-primary); white-space: nowrap; }
3089
- & .crisp-recordstable-fchip-main { display: inline-flex; align-items: center; gap: var(--crisp-space-1-5); height: 100%; margin: 0; padding: 0 var(--crisp-space-1) 0 var(--crisp-space-2); border: none; background: none; font-family: inherit; font-size: var(--crisp-text-md); color: var(--crisp-fg-primary); cursor: pointer; }
3090
- & .crisp-recordstable-fchip-field { font-weight: var(--crisp-weight-medium); }
3091
- & .crisp-recordstable-fchip-op { color: var(--crisp-fg-muted); }
3092
- & .crisp-recordstable-fchip-val { font-weight: var(--crisp-weight-medium); max-width: 140px; overflow: hidden; text-overflow: ellipsis; }
3093
- & .crisp-recordstable-fchip-x { display: inline-flex; align-items: center; justify-content: center; width: 20px; height: 20px; padding: 0; border: none; border-radius: var(--crisp-radius-sm); background: none; color: var(--crisp-fg-muted); cursor: pointer; }
3094
- & .crisp-recordstable-fchip-x:hover { background: color-mix(in srgb, var(--crisp-fg-primary) 10%, transparent); color: var(--crisp-fg-primary); }
3095
- & .crisp-recordstable-spacer { flex: 1; }
3096
- & .crisp-recordstable-count { font-size: var(--crisp-text-md); color: var(--crisp-fg-muted); }
3097
- & .crisp-recordstable-new { display: inline-flex; align-items: center; gap: var(--crisp-space-1); height: var(--crisp-size-row); padding: 0 var(--crisp-space-2-5); border: none; border-radius: var(--crisp-radius-md); background: var(--crisp-bg-brand-solid); color: var(--crisp-fg-on-brand); font-family: inherit; font-size: var(--crisp-text-md); font-weight: var(--crisp-weight-medium); cursor: pointer; transition: background-color .12s; }
3098
- & .crisp-recordstable-new:hover { background: var(--crisp-bg-brand-solid-pressed); }
3099
-
3100
- /* scroll frame + table */
3101
- & .crisp-recordstable-scroll { flex: 1; min-height: 0; overflow: auto; background: var(--crisp-bg-surface); border-top: 1px solid var(--crisp-border-subtle); scrollbar-width: thin; scrollbar-color: color-mix(in srgb, var(--crisp-fg-primary) 22%, transparent) transparent; }
3102
- & .crisp-recordstable-scroll::-webkit-scrollbar { width: 10px; height: 10px; }
3103
- & .crisp-recordstable-scroll::-webkit-scrollbar-thumb { background: color-mix(in srgb, var(--crisp-fg-primary) 20%, transparent); border-radius: var(--crisp-radius-full); border: 2px solid var(--crisp-bg-surface); background-clip: padding-box; }
3104
- & .crisp-recordstable-scroll::-webkit-scrollbar-thumb:hover { background: color-mix(in srgb, var(--crisp-fg-primary) 34%, transparent); background-clip: padding-box; }
3105
- & .crisp-recordstable-scroll::-webkit-scrollbar-corner { background: transparent; }
3106
- & .crisp-recordstable-table { table-layout: fixed; border-collapse: separate; border-spacing: 0; }
3107
- & .crisp-recordstable-th, & .crisp-recordstable-td { position: relative; height: var(--crisp-size-tableRow); padding: 0 var(--crisp-space-2); border-bottom: 1px solid var(--crisp-border-subtle); border-inline-end: 1px solid var(--crisp-border-subtle); text-align: start; white-space: nowrap; overflow: hidden; }
3108
- & .crisp-recordstable-th { position: sticky; top: 0; z-index: 2; padding: 0; height: var(--crisp-size-tableHeader); background: var(--crisp-bg-surface); font-size: var(--crisp-text-lg); font-weight: var(--crisp-weight-medium); letter-spacing: -0.14px; color: color-mix(in srgb, var(--crisp-fg-primary) 63%, transparent); user-select: none; }
3109
- & .crisp-recordstable-th-inner { display: flex; align-items: center; gap: var(--crisp-space-1-5); width: 100%; height: 100%; margin: 0; padding: 0 var(--crisp-space-2); border: none; background: none; color: inherit; font: inherit; letter-spacing: inherit; text-align: start; cursor: pointer; }
3110
- & .crisp-recordstable-th-inner svg { flex-shrink: 0; color: color-mix(in srgb, var(--crisp-fg-primary) 45%, transparent); }
3111
- & .crisp-recordstable-th-label { flex: 1; overflow: hidden; text-overflow: ellipsis; }
3112
- & .crisp-recordstable-th-rename { width: 100%; min-width: 0; height: 100%; margin: 0; padding: 0; border: none; outline: none; background: none; color: inherit; font: inherit; letter-spacing: inherit; }
3113
- & .crisp-recordstable-th-rename:focus { box-shadow: inset 0 0 0 1px var(--crisp-bg-brand-solid); }
3114
- & .crisp-recordstable-sort { flex-shrink: 0; opacity: 0.4; transition: opacity .12s; color: color-mix(in srgb, var(--crisp-fg-primary) 45%, transparent); }
3115
- & .crisp-recordstable-th:hover .crisp-recordstable-sort, & .crisp-recordstable-th[data-sorted] .crisp-recordstable-sort { opacity: 1; }
3116
- & .crisp-recordstable-th[data-sorted="desc"] .crisp-recordstable-sort svg { transform: rotate(180deg); }
3117
-
3118
- /* column resize handle */
3119
- & .crisp-recordstable-resize { position: absolute; top: 0; inset-inline-end: -3px; width: 9px; height: 100%; cursor: col-resize; z-index: 6; touch-action: none; }
3120
- & .crisp-recordstable-resize::after { content: ""; position: absolute; top: 0; inset-inline-start: 3px; width: 2px; height: 100%; background: var(--crisp-bg-brand-solid); opacity: 0; transition: opacity .12s; }
3121
- & .crisp-recordstable-resize:hover::after, & .crisp-recordstable-resize[data-resizing]::after { opacity: 1; }
3122
-
3123
- /* add-column header */
3124
- & .crisp-recordstable-addcol { min-width: 44px; }
3125
- & .crisp-recordstable-addcol button { display: flex; align-items: center; justify-content: center; width: 44px; height: 100%; border: none; background: none; color: var(--crisp-fg-muted); cursor: pointer; }
3126
- & .crisp-recordstable-addcol button:hover { background: var(--crisp-bg-raised); color: var(--crisp-fg-primary); }
3127
-
3128
- /* cells */
3129
- /* font-weight is explicit because the first cell of each row is a
3130
- th[scope=row] — the record's name is what the row IS — and a th defaults to bold. Measured before the change and unchanged after: 400. */
3131
- & .crisp-recordstable-td { font-size: var(--crisp-text-lg); font-weight: var(--crisp-weight-regular); color: var(--crisp-fg-primary); background: var(--crisp-bg-surface); cursor: default; }
3132
- & .crisp-recordstable-td--editable { cursor: text; }
3133
- /* keyboard/click cell selection (spreadsheet-style) */
3134
- & .crisp-recordstable-td[data-selected] { outline: 2px solid var(--crisp-bg-brand-solid); outline-offset: -2px; border-radius: var(--crisp-radius-sm); z-index: 3; }
3135
- & .crisp-recordstable-scroll:focus { outline: none; }
3136
- /* picker cells (status / date / reference) host a ghost control edge-to-edge */
3137
- & .crisp-recordstable-td--picker { padding: 0; }
3138
- & .crisp-recordstable-td--picker .crisp-select-trigger, & .crisp-recordstable-td--picker .crisp-datepicker-trigger, & .crisp-recordstable-td--picker .crisp-multiselect-trigger { width: 100%; height: 100%; min-height: 0; border-radius: 0; box-shadow: none; padding: 0 var(--crisp-space-2); }
3139
- & .crisp-recordstable-td--picker .crisp-multiselect-trigger:hover { background: none; }
3140
- & .crisp-recordstable-td--picker .crisp-multiselect-trigger[data-open] { box-shadow: inset 0 0 0 1.5px var(--crisp-bg-brand-solid); }
3141
- /* tag cell: one compact clipped line of full chips, no chevron (opens the picker on click) */
3142
- & .crisp-recordstable-td--picker .crisp-multiselect-chips { flex-wrap: nowrap; overflow: hidden; }
3143
- & .crisp-recordstable-td--picker .crisp-multiselect-chips .crisp-recordstable-tag { flex-shrink: 0; max-width: none; }
3144
- & .crisp-recordstable-td--picker .crisp-multiselect-chevron { display: none; }
3145
- & .crisp-recordstable-td--picker .crisp-multiselect-placeholder { color: var(--crisp-fg-muted); }
3146
- /* reference picker: a leading 16px avatar per option, plus a trailing "Create X" row when the consumer supplies one (#449) -- measured live against Attio's own picker (16px circular avatar, 16px plain icon for
3147
- the create row, both sitting in the option's existing 8px gap, no new gap value invented). */
3148
- & .crisp-recordstable-refoption { display: flex; align-items: center; gap: var(--crisp-space-2); min-width: 0; overflow: hidden; text-overflow: ellipsis; }
3149
- & .crisp-recordstable-refoption-avatar { flex-shrink: 0; }
3150
- & .crisp-recordstable-refoption--create { color: var(--crisp-fg-muted); }
3151
- & .crisp-recordstable-status { display: inline-flex; align-items: center; gap: var(--crisp-space-1-5); min-width: 0; }
3152
- & .crisp-recordstable-status-dot { flex-shrink: 0; width: 8px; height: 8px; border-radius: var(--crisp-radius-full); }
3153
- & .crisp-recordstable-cell { display: flex; align-items: center; gap: var(--crisp-space-2); height: 100%; min-width: 0; }
3154
- & .crisp-recordstable-cell--num { justify-content: flex-end; font-variant-numeric: tabular-nums; }
3155
- & .crisp-recordstable-cell--tags { gap: var(--crisp-space-1); }
3156
- & .crisp-recordstable-tag { display: inline-flex; align-items: center; height: var(--crisp-size-chip); padding: 0 var(--crisp-space-1); border-radius: var(--crisp-radius-chip); font-size: var(--crisp-text-lg); line-height: 20px; font-weight: var(--crisp-weight-medium); white-space: nowrap; background: color-mix(in srgb, var(--crisp-fg-primary) 6%, transparent); color: color-mix(in srgb, var(--crisp-fg-primary) 82%, transparent); box-shadow: inset 0 0 0 1px var(--crisp-border-subtle); }
3157
- & .crisp-recordstable-tag[data-tone="brand"] { background: color-mix(in srgb, var(--crisp-bg-brand-solid) 14%, transparent); color: color-mix(in srgb, var(--crisp-bg-brand-solid) 82%, var(--crisp-fg-primary)); box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--crisp-bg-brand-solid) 30%, transparent); }
3158
- & .crisp-recordstable-tag[data-tone="success"] { background: color-mix(in srgb, var(--crisp-bg-success) 16%, transparent); color: color-mix(in srgb, var(--crisp-bg-success) 78%, var(--crisp-fg-primary)); box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--crisp-bg-success) 30%, transparent); }
3159
- & .crisp-recordstable-tag[data-tone="warning"] { background: color-mix(in srgb, var(--crisp-bg-warning) 18%, transparent); color: color-mix(in srgb, var(--crisp-bg-warning) 82%, var(--crisp-fg-primary)); box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--crisp-bg-warning) 30%, transparent); }
3160
- & .crisp-recordstable-tag[data-tone="danger"] { background: color-mix(in srgb, var(--crisp-bg-danger) 15%, transparent); color: color-mix(in srgb, var(--crisp-bg-danger) 80%, var(--crisp-fg-primary)); box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--crisp-bg-danger) 30%, transparent); }
3161
- & .crisp-recordstable-tag[data-tone="purple"] { background: color-mix(in srgb, var(--crisp-bg-purple) 16%, transparent); color: color-mix(in srgb, var(--crisp-bg-purple) 80%, var(--crisp-fg-primary)); box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--crisp-bg-purple) 30%, transparent); }
3162
- & .crisp-recordstable-tag[data-tone="cyan"] { background: color-mix(in srgb, var(--crisp-bg-cyan) 18%, transparent); color: color-mix(in srgb, var(--crisp-bg-cyan) 82%, var(--crisp-fg-primary)); box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--crisp-bg-cyan) 30%, transparent); }
3163
- & .crisp-recordstable-tag[data-tone="orange"] { background: color-mix(in srgb, var(--crisp-bg-orange) 16%, transparent); color: color-mix(in srgb, var(--crisp-bg-orange) 82%, var(--crisp-fg-primary)); box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--crisp-bg-orange) 30%, transparent); }
3164
- & .crisp-recordstable-muted { color: var(--crisp-fg-muted); }
3165
- & .crisp-recordstable-entity { display: flex; align-items: center; gap: var(--crisp-space-1-5); min-width: 0; color: var(--crisp-fg-primary); font-weight: var(--crisp-weight-medium); text-decoration: none; }
3166
- & .crisp-recordstable-entity:hover .crisp-recordstable-entity-name { text-decoration: underline; }
3167
- & .crisp-recordstable-entity-name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
3168
- & button.crisp-recordstable-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%; }
3169
- & .crisp-recordstable-fav { width: var(--crisp-size-icon); height: var(--crisp-size-icon); border-radius: var(--crisp-radius-sm); flex-shrink: 0; object-fit: cover; background: var(--crisp-bg-raised); }
3170
- & .crisp-recordstable-open { position: absolute; inset-inline-end: var(--crisp-space-1); top: 50%; transform: translateY(-50%); display: inline-flex; align-items: center; justify-content: center; width: 24px; height: 24px; padding: 0; border: none; border-radius: var(--crisp-radius-sm); background: var(--crisp-bg-surface); box-shadow: inset 0 0 0 1px var(--crisp-border-default); color: var(--crisp-fg-muted); font-family: inherit; cursor: pointer; opacity: 0; transition: opacity .12s, background-color .12s; z-index: 3; }
3171
- & .crisp-recordstable-open:hover { background: var(--crisp-bg-raised); color: var(--crisp-fg-primary); }
3172
- & tbody tr:hover .crisp-recordstable-open { opacity: 1; }
3173
- & .crisp-recordstable-link { color: var(--crisp-fg-primary); text-decoration: none; overflow: hidden; text-overflow: ellipsis; }
3174
- & .crisp-recordstable-link:hover { text-decoration: underline; }
3175
- /* inline edit input */
3176
- & .crisp-recordstable-edit { position: absolute; inset: 0; width: 100%; height: 100%; margin: 0; padding: 0 var(--crisp-space-2); border: none; outline: 2px solid var(--crisp-bg-brand-solid); outline-offset: -2px; border-radius: var(--crisp-radius-sm); background: var(--crisp-bg-surface); font-family: inherit; font-size: var(--crisp-text-lg); color: var(--crisp-fg-primary); z-index: 5; }
3177
- & .crisp-recordstable-edit--num { text-align: end; font-variant-numeric: tabular-nums; }
3178
- /* selection / hover / sticky */
3179
- & .crisp-recordstable-col-check { width: 40px; min-width: 40px; padding: 0; }
3180
- & .crisp-recordstable-check-inner { display: flex; align-items: center; justify-content: center; height: 100%; }
3181
- & .crisp-recordstable-col-check, & .crisp-recordstable-col-entity { position: sticky; z-index: 3; background: var(--crisp-bg-surface); }
3182
- /* The sticky pin is a LOGICAL offset: with physical `left` these columns were not pinned at all under RTL -- scrolled, they left the viewport entirely (measured: viewport 13..773, check column at x=1333). Nothing in JS measures these, so converting them is safe; the two properties below that have no logical form get an explicit RTL rule rather than being left to point the wrong way. */
3183
- & .crisp-recordstable-col-check { inset-inline-start: 0; }
3184
- & .crisp-recordstable-col-entity { inset-inline-start: 40px; clip-path: inset(0 -38px 0 0); transition: box-shadow .2s ease; }
3185
- [dir="rtl"] & .crisp-recordstable-col-entity { clip-path: inset(0 0 0 -38px); }
3186
- & thead .crisp-recordstable-col-check, & thead .crisp-recordstable-col-entity { z-index: 4; }
3187
- /* soft shadow on the sticky column edge once the grid is scrolled horizontally */
3188
- & .crisp-recordstable-scroll[data-scrolled] .crisp-recordstable-col-entity { box-shadow: 6px 0 16px 4px rgba(0,0,0,0.12); }
3189
- /* box-shadow has no logical form either: the bleed follows the scrolled content, which is the opposite side under RTL. */
3190
- [dir="rtl"] & .crisp-recordstable-scroll[data-scrolled] .crisp-recordstable-col-entity { box-shadow: -6px 0 16px 4px rgba(0,0,0,0.12); }
3191
- /* AI-enriched values carry a purple wash. Measured live: a full-bleed overlay at #9b69ff 4% (== bgPurple), composited OVER whatever row-state background the cell already has, which is why it is an ::after and not a background. */
3192
- & .crisp-recordstable-td[data-enriched]::after { content: ""; position: absolute; inset: 0; pointer-events: none; background: color-mix(in srgb, var(--crisp-bg-purple) 4%, transparent); }
3193
- & tbody tr:hover .crisp-recordstable-td { background: color-mix(in srgb, var(--crisp-fg-primary) 4%, var(--crisp-bg-surface)); }
3194
- & tbody tr[data-selected] .crisp-recordstable-td { background: color-mix(in srgb, var(--crisp-bg-brand-solid) 10%, var(--crisp-bg-surface)); }
3195
- & tbody tr[data-selected]:hover .crisp-recordstable-td { background: color-mix(in srgb, var(--crisp-fg-primary) 4%, color-mix(in srgb, var(--crisp-bg-brand-solid) 10%, var(--crisp-bg-surface))); }
3196
- & .crisp-recordstable-th:last-child, & .crisp-recordstable-td:last-child { border-inline-end: none; }
3197
- /* add-row footer */
3198
- & .crisp-recordstable-addrow { display: flex; align-items: center; gap: var(--crisp-space-2); width: 100%; height: var(--crisp-size-tableRow); padding: 0 var(--crisp-space-2); border: none; border-top: 1px solid var(--crisp-border-subtle); background: var(--crisp-bg-surface); color: var(--crisp-fg-muted); font-family: inherit; font-size: var(--crisp-text-lg); cursor: pointer; }
3199
- & .crisp-recordstable-addrow:hover { background: var(--crisp-bg-raised); color: var(--crisp-fg-primary); }
3200
- /* zero-row empty state — centred in the body region (headers + summary stay) */
3201
- & .crisp-recordstable-empty { display: flex; align-items: center; justify-content: center; min-height: 320px; padding: var(--crisp-space-8) var(--crisp-space-4); }
3202
- /* summary footer — per-column aggregation, sticky to the bottom */
3203
- & .crisp-recordstable-sum { position: sticky; bottom: 0; z-index: 2; height: var(--crisp-size-tableRow); padding: 0; border-top: 1px solid var(--crisp-border-subtle); border-bottom: 1px solid var(--crisp-border-subtle); border-inline-end: 1px solid var(--crisp-border-subtle); background: var(--crisp-bg-surface); }
3204
- & tfoot .crisp-recordstable-col-check, & tfoot .crisp-recordstable-col-entity { position: sticky; bottom: 0; z-index: 3; }
3205
- & .crisp-recordstable-sum:last-child { border-inline-end: none; }
3206
- & .crisp-recordstable-sum-btn { display: flex; align-items: center; justify-content: flex-end; gap: var(--crisp-space-1-5); width: 100%; height: 100%; margin: 0; padding: 0 var(--crisp-space-3); border: none; background: none; color: color-mix(in srgb, var(--crisp-fg-primary) 63%, transparent); font-family: inherit; font-size: var(--crisp-text-lg); font-weight: var(--crisp-weight-medium); cursor: pointer; white-space: nowrap; overflow: hidden; }
3207
- & .crisp-recordstable-sum-btn:hover { background: var(--crisp-bg-raised); }
3208
- & .crisp-recordstable-sum-label { color: color-mix(in srgb, var(--crisp-fg-primary) 45%, transparent); text-transform: capitalize; }
3209
- & .crisp-recordstable-sum-value { color: var(--crisp-fg-primary); font-variant-numeric: tabular-nums; overflow: hidden; text-overflow: ellipsis; }
3210
- & .crisp-recordstable-sumgroup { width: 100%; height: 100%; }
3211
- & .crisp-recordstable-pin { flex-shrink: 0; margin-inline-start: var(--crisp-space-1); color: var(--crisp-bg-warning); fill: color-mix(in srgb, var(--crisp-bg-warning) 35%, transparent); }
3212
- & .crisp-recordstable-sum-btn[data-empty] { opacity: 0; justify-content: flex-start; }
3213
- & .crisp-recordstable-sum-btn[data-empty]:hover { opacity: 1; }
3214
- /* bulk action bar lives in its own component now — see components/bulkbar */
3215
- & .crisp-recordstable-bulk-sep { width: 1px; height: 20px; margin: 0 var(--crisp-space-0-5); background: var(--crisp-border-subtle); }
3216
- & .crisp-recordstable-bulk-close { display: inline-flex; align-items: center; justify-content: center; width: 32px; height: 32px; padding: 0; border: none; border-radius: var(--crisp-radius-md); background: transparent; color: var(--crisp-fg-muted); cursor: pointer; }
3217
- & .crisp-recordstable-bulk-close:hover { background: var(--crisp-bg-raised); color: var(--crisp-fg-primary); }
3218
- /* announced, never drawn */
3219
- & .crisp-recordstable-live { position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px; overflow: hidden; clip: rect(0, 0, 0, 0); white-space: nowrap; border: 0; }
3220
- [dir="rtl"] & [data-rtl-flip] { transform: scaleX(-1); }
3221
- }
3222
-
3223
- @media (prefers-reduced-motion: reduce) {
3224
- .crisp-recordstable, .crisp-recordstable * {
3225
- animation-duration: 0.01ms !important;
3226
- animation-iteration-count: 1 !important;
3227
- transition-duration: 0.01ms !important;
3228
- }
3229
- }
3230
-
3231
3029
  .crisp-richtext {
3232
3030
  display: flex; flex-direction: column; box-sizing: border-box; font-family: var(--crisp-font-sans); color: var(--crisp-fg-primary); -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale;
3233
3031
  & .crisp-rte-title { font-size: 32px; line-height: 36px; font-weight: var(--crisp-weight-semibold); letter-spacing: -0.6px; color: var(--crisp-fg-primary); outline: none; }
@@ -3518,37 +3316,6 @@
3518
3316
  }
3519
3317
  }
3520
3318
 
3521
- .crisp-taskstable {
3522
- display: flex; flex-direction: column; min-height: 0; box-sizing: border-box; font-family: var(--crisp-font-sans); color: var(--crisp-fg-primary); -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale;
3523
- & .crisp-taskstable-colhead { display: flex; align-items: center; gap: var(--crisp-space-1-5); min-height: 49px; max-height: 49px; padding: var(--crisp-space-2-5) var(--crisp-space-3); box-shadow: inset 0 -1px 0 var(--crisp-border-subtle); font-size: var(--crisp-text-sm); color: var(--crisp-fg-muted); }
3524
- & .crisp-taskstable-colhead-cell { display: inline-flex; align-items: center; gap: var(--crisp-space-1-5); min-width: 0; }
3525
- & .crisp-taskstable-row { display: flex; align-items: center; gap: var(--crisp-space-1-5); }
3526
- /* #642: measured live -- title, due date and assignee name are ALL 14px/500 on Attio's own Tasks page. Only -title/-due set their own
3527
- font-size (and only -title its own weight), so an unstyled consumer
3528
- slot (a plain PersonChip/RecordChip name span with no font-size of
3529
- its own) fell through to the browser/body default, 16px/400. Set
3530
- the ambient font here so it cascades to any slot content that
3531
- doesn't override it -- -title/-due's own declarations still apply,
3532
- unchanged, they're just now redundant with this baseline rather than the only source of it. */
3533
- & .crisp-taskstable-cell { display: flex; align-items: center; min-width: 0; font-size: var(--crisp-text-lg); font-weight: var(--crisp-weight-medium); }
3534
- & .crisp-taskstable-check { appearance: none; -webkit-appearance: none; box-sizing: border-box; flex-shrink: 0; margin: 0; width: 16px; height: 16px; border: none; border-radius: var(--crisp-radius-full); background: var(--crisp-bg-surface); box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--crisp-fg-primary) 9%, transparent); cursor: pointer; transition: background-color .12s, box-shadow .12s; }
3535
- & .crisp-taskstable-check:hover { box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--crisp-fg-primary) 30%, transparent); }
3536
- & .crisp-taskstable-check:checked { background: var(--crisp-bg-brand-solid) center / 10px no-repeat; box-shadow: none; background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='10' viewBox='0 0 12 12' fill='none' stroke='white' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M2.5 6.5 5 9l4.5-5'/%3E%3C/svg%3E"); }
3537
- & .crisp-taskstable-check:focus-visible { outline: 2px solid var(--crisp-bg-brand-solid); outline-offset: 1px; }
3538
- & .crisp-taskstable-title { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: var(--crisp-text-lg); font-weight: var(--crisp-weight-medium); color: var(--crisp-fg-primary); }
3539
- & .crisp-taskstable-due { font-size: var(--crisp-text-lg); color: var(--crisp-fg-muted); white-space: nowrap; }
3540
- & .crisp-taskstable-due[data-overdue] { color: var(--crisp-fg-danger); }
3541
- & .crisp-taskstable-empty { padding: var(--crisp-space-8) var(--crisp-space-4); text-align: center; font-size: var(--crisp-text-md); color: var(--crisp-fg-muted); }
3542
- }
3543
-
3544
- @media (prefers-reduced-motion: reduce) {
3545
- .crisp-taskstable, .crisp-taskstable * {
3546
- animation-duration: 0.01ms !important;
3547
- animation-iteration-count: 1 !important;
3548
- transition-duration: 0.01ms !important;
3549
- }
3550
- }
3551
-
3552
3319
  .crisp-themepicker {
3553
3320
  display: flex; gap: var(--crisp-space-2); font-family: var(--crisp-font-sans); -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale;
3554
3321
  & .crisp-themepicker-option { box-sizing: border-box; display: flex; flex-direction: column; align-items: center; gap: var(--crisp-space-1-5); width: 224px; padding: var(--crisp-space-1); border: 1px solid var(--crisp-border-subtle); border-radius: 16px; background: var(--crisp-bg-surface); cursor: pointer; font-family: inherit; }