@crisp-ui-kit/crisp 0.54.7 → 0.54.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (130) hide show
  1. package/dist/authshell.css +5 -1
  2. package/dist/authshell.layered.css +5 -1
  3. package/dist/badge.css +5 -5
  4. package/dist/badge.layered.css +5 -5
  5. package/dist/board.css +36 -8
  6. package/dist/board.layered.css +36 -8
  7. package/dist/button.css +3 -1
  8. package/dist/button.layered.css +3 -1
  9. package/dist/cjs/components/badge/badge.recipe.js +28 -5
  10. package/dist/cjs/components/button/button.js +33 -2
  11. package/dist/cjs/components/button/button.recipe.js +31 -5
  12. package/dist/cjs/components/contextmenu/contextmenu.js +2 -2
  13. package/dist/cjs/components/contextmenu/contextmenu.recipe.js +24 -0
  14. package/dist/cjs/components/datatable/datatable.js +590 -41
  15. package/dist/cjs/components/datatable/datatable.recipe.js +118 -5
  16. package/dist/cjs/components/dialog/dialog.js +72 -5
  17. package/dist/cjs/components/dialog/dialog.recipe.js +15 -1
  18. package/dist/cjs/components/tabletoolbar/tabletoolbar.chrome.js +49 -5
  19. package/dist/cjs/components/tabletoolbar/tabletoolbar.js +365 -49
  20. package/dist/cjs/components/tabletoolbar/tabletoolbar.recipe.js +78 -0
  21. package/dist/cjs/components/tabs/tabs.recipe.js +11 -2
  22. package/dist/cjs/components/tooltip/tooltip.js +23 -9
  23. package/dist/cjs/components/tooltip/tooltip.recipe.js +10 -9
  24. package/dist/cjs/product/authshell/authshell.js +14 -1
  25. package/dist/cjs/product/authshell/authshell.recipe.js +13 -1
  26. package/dist/cjs/product/board/board.js +175 -46
  27. package/dist/cjs/product/board/board.recipe.js +56 -4
  28. package/dist/cjs/product/comments/comments.js +34 -12
  29. package/dist/cjs/product/comments/comments.recipe.js +31 -7
  30. package/dist/cjs/product/formdialog/formdialog.js +10 -4
  31. package/dist/cjs/product/navitem/navitem.js +7 -2
  32. package/dist/cjs/product/navitem/navitem.recipe.js +16 -0
  33. package/dist/cjs/product/objectsettings/index.js +1 -2
  34. package/dist/cjs/product/objectsettings/objectsettings.js +3 -13
  35. package/dist/cjs/product/recordstable/recordstable.js +1 -17
  36. package/dist/cjs/product/richtext/richtext.js +76 -1
  37. package/dist/cjs/product/savedviews/savedviews.js +55 -24
  38. package/dist/cjs/product/savedviews/savedviews.recipe.js +27 -0
  39. package/dist/cjs/product/sidebar/sidebar.js +68 -15
  40. package/dist/cjs/product/sidebar/sidebar.recipe.js +6 -1
  41. package/dist/cjs/tokens/palette.js +32 -1
  42. package/dist/cjs/tokens/semantic.js +11 -0
  43. package/dist/comments.css +50 -14
  44. package/dist/comments.layered.css +50 -14
  45. package/dist/contextmenu.css +24 -0
  46. package/dist/contextmenu.layered.css +24 -0
  47. package/dist/datatable.css +101 -37
  48. package/dist/datatable.layered.css +101 -37
  49. package/dist/dialog.css +13 -1
  50. package/dist/dialog.layered.css +13 -1
  51. package/dist/esm/components/badge/badge.recipe.js +28 -5
  52. package/dist/esm/components/button/button.js +34 -3
  53. package/dist/esm/components/button/button.recipe.js +31 -5
  54. package/dist/esm/components/contextmenu/contextmenu.js +2 -2
  55. package/dist/esm/components/contextmenu/contextmenu.recipe.js +24 -0
  56. package/dist/esm/components/datatable/datatable.js +590 -42
  57. package/dist/esm/components/datatable/datatable.recipe.js +119 -6
  58. package/dist/esm/components/dialog/dialog.js +72 -5
  59. package/dist/esm/components/dialog/dialog.recipe.js +15 -1
  60. package/dist/esm/components/tabletoolbar/tabletoolbar.chrome.js +49 -5
  61. package/dist/esm/components/tabletoolbar/tabletoolbar.js +366 -50
  62. package/dist/esm/components/tabletoolbar/tabletoolbar.recipe.js +80 -2
  63. package/dist/esm/components/tabs/tabs.recipe.js +11 -2
  64. package/dist/esm/components/tooltip/tooltip.js +23 -9
  65. package/dist/esm/components/tooltip/tooltip.recipe.js +10 -9
  66. package/dist/esm/product/authshell/authshell.js +15 -2
  67. package/dist/esm/product/authshell/authshell.recipe.js +13 -1
  68. package/dist/esm/product/authshell/index.js +1 -1
  69. package/dist/esm/product/board/board.js +176 -47
  70. package/dist/esm/product/board/board.recipe.js +56 -4
  71. package/dist/esm/product/comments/comments.js +35 -13
  72. package/dist/esm/product/comments/comments.recipe.js +31 -7
  73. package/dist/esm/product/formdialog/formdialog.js +10 -4
  74. package/dist/esm/product/navitem/index.js +1 -1
  75. package/dist/esm/product/navitem/navitem.js +7 -2
  76. package/dist/esm/product/navitem/navitem.recipe.js +16 -0
  77. package/dist/esm/product/objectsettings/index.js +1 -1
  78. package/dist/esm/product/objectsettings/objectsettings.js +2 -12
  79. package/dist/esm/product/recordstable/recordstable.js +2 -18
  80. package/dist/esm/product/richtext/richtext.js +76 -1
  81. package/dist/esm/product/savedviews/savedviews.js +57 -26
  82. package/dist/esm/product/savedviews/savedviews.recipe.js +27 -0
  83. package/dist/esm/product/sidebar/sidebar.js +69 -16
  84. package/dist/esm/product/sidebar/sidebar.recipe.js +6 -1
  85. package/dist/esm/tokens/palette.js +32 -1
  86. package/dist/esm/tokens/semantic.js +11 -0
  87. package/dist/native/tokens.d.ts +1 -1
  88. package/dist/native/tokens.js +16 -2
  89. package/dist/navitem.css +7 -0
  90. package/dist/navitem.layered.css +7 -0
  91. package/dist/recordstable.css +34 -32
  92. package/dist/recordstable.layered.css +34 -32
  93. package/dist/savedviews.css +27 -0
  94. package/dist/savedviews.layered.css +27 -0
  95. package/dist/sidebar.css +6 -1
  96. package/dist/sidebar.layered.css +6 -1
  97. package/dist/styles.css +369 -106
  98. package/dist/styles.layered.css +369 -106
  99. package/dist/tabletoolbar.css +47 -3
  100. package/dist/tabletoolbar.layered.css +47 -3
  101. package/dist/tabs.css +1 -1
  102. package/dist/tabs.layered.css +1 -1
  103. package/dist/tokens.css +9 -1
  104. package/dist/tokens.json +16 -2
  105. package/dist/tooltip.css +1 -1
  106. package/dist/tooltip.layered.css +1 -1
  107. package/dist/types/components/badge/badge.recipe.d.ts +5 -1
  108. package/dist/types/components/button/button.recipe.d.ts +18 -5
  109. package/dist/types/components/contextmenu/contextmenu.d.ts +8 -1
  110. package/dist/types/components/datatable/datatable.d.ts +98 -1
  111. package/dist/types/components/dialog/dialog.d.ts +25 -2
  112. package/dist/types/components/tabletoolbar/tabletoolbar.d.ts +113 -3
  113. package/dist/types/components/tooltip/tooltip.d.ts +12 -4
  114. package/dist/types/components/tooltip/tooltip.recipe.d.ts +1 -1
  115. package/dist/types/product/authshell/authshell.d.ts +26 -2
  116. package/dist/types/product/authshell/index.d.ts +1 -1
  117. package/dist/types/product/board/board.d.ts +53 -6
  118. package/dist/types/product/comments/comments.d.ts +16 -3
  119. package/dist/types/product/formdialog/formdialog.d.ts +2 -1
  120. package/dist/types/product/navitem/index.d.ts +1 -1
  121. package/dist/types/product/navitem/navitem.d.ts +23 -0
  122. package/dist/types/product/objectsettings/index.d.ts +1 -1
  123. package/dist/types/product/objectsettings/objectsettings.d.ts +0 -10
  124. package/dist/types/product/recordstable/recordstable.d.ts +0 -1
  125. package/dist/types/product/savedviews/savedviews.d.ts +45 -8
  126. package/dist/types/product/sidebar/sidebar.d.ts +33 -5
  127. package/dist/types/tokens/contract.d.ts +4 -0
  128. package/dist/types/tokens/palette.d.ts +13 -1
  129. package/dist/types/tokens/semantic.d.ts +11 -0
  130. package/package.json +1 -1
@@ -13,11 +13,21 @@
13
13
 
14
14
  /* Row badge (CommentCountBadge) -- lives in its own class family so the
15
15
  grid lanes can drop it into a cell without pulling in the panel's
16
- CSS. Persistent state: a 6px grey dot. Hover state: a 12px comment
17
- icon + 12px count text, both at 55% ink, replacing the dot. */
18
- .crisp-comments-badge { box-sizing: border-box; font-family: var(--crisp-font-sans); -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; display: inline-flex; align-items: center; gap: var(--crisp-space-1); border: none; background: none; padding: 0; margin: 0; cursor: pointer; color: color-mix(in srgb, var(--crisp-fg-primary) 55%, transparent); }
16
+ CSS. Re-measured live on a real commented row (crisp/#601
17
+ supersession, right-aligned in a DataTable primary column): a 6px
18
+ grey dot at rest (unchanged, already matched); on hover, the trigger
19
+ button is auto-width with a 12px icon + 12px/500 count text,
20
+ "padding: 2px 6px 2px 4px", radius 6px -- distinct from the preview
21
+ button beside it, a fixed 20px square with a 16px icon, same radius.
22
+ Both share the 55%-ink idle colour and darken on their OWN hover
23
+ (color-mix 4%, the same formula DataTable's own #601 icons use). */
24
+ .crisp-comments-badge { box-sizing: border-box; font-family: var(--crisp-font-sans); -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; display: inline-flex; align-items: center; gap: var(--crisp-space-1-5); }
19
25
  .crisp-comments-badge-dot { box-sizing: border-box; width: 6px; height: 6px; border-radius: var(--crisp-radius-full); background: color-mix(in srgb, var(--crisp-fg-primary) 38%, transparent); }
20
- .crisp-comments-badge-count { font-size: 12px; line-height: 16px; }
26
+ .crisp-comments-badge-trigger { box-sizing: border-box; display: inline-flex; align-items: center; gap: var(--crisp-space-1); height: 20px; margin: 0; padding: 2px var(--crisp-space-1-5) 2px var(--crisp-space-1); border: none; border-radius: var(--crisp-radius-sm); background: none; font-family: inherit; color: color-mix(in srgb, var(--crisp-fg-primary) 55%, transparent); cursor: pointer; }
27
+ .crisp-comments-badge-trigger:hover { background: color-mix(in srgb, var(--crisp-fg-primary) 4%, transparent); color: var(--crisp-fg-primary); }
28
+ .crisp-comments-badge-preview { box-sizing: border-box; display: inline-flex; align-items: center; justify-content: center; width: 20px; height: 20px; margin: 0; padding: 0; border: none; border-radius: var(--crisp-radius-sm); background: none; font-family: inherit; color: color-mix(in srgb, var(--crisp-fg-primary) 55%, transparent); cursor: pointer; flex-shrink: 0; }
29
+ .crisp-comments-badge-preview:hover { background: color-mix(in srgb, var(--crisp-fg-primary) 4%, transparent); color: var(--crisp-fg-primary); }
30
+ .crisp-comments-badge-count { font-size: 12px; line-height: 16px; font-weight: var(--crisp-weight-medium); }
21
31
  [dir="rtl"] .crisp-comments-badge [data-rtl-flip] { transform: scaleX(-1); }
22
32
 
23
33
  .crisp-comments {
@@ -31,31 +41,50 @@
31
41
  & .crisp-comments-breadcrumb { display: flex; align-items: center; gap: var(--crisp-space-1); font-size: 12px; font-weight: var(--crisp-weight-medium); color: color-mix(in srgb, var(--crisp-fg-primary) 63%, transparent); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
32
42
  & .crisp-comments-body { flex: 1; min-height: 0; overflow-y: auto; padding: 0 var(--crisp-space-4); display: flex; flex-direction: column; gap: var(--crisp-space-4); }
33
43
  & .crisp-comments-body--pad-top { padding-top: var(--crisp-space-3); }
34
- /* One comment row -- avatar 16px (Avatar size="sm"), 6px gap to the name/time line, 14px/600 name + 12px/500 63%-muted time, 14px/400 body two lines below. Hover actions fade in flush with the name line. */
44
+ /* One comment row -- avatar 16px (Avatar size="sm"), 6px gap to the name/time line, 14px/600 name + 12px/500 63%-muted time, 14px/400 body two lines below. Hover actions fade in flush with the name line. .crisp-comments-row-head's own height: 20px (above) is a single
45
+ text-line's worth, on the same assumption .crisp-comments-title/ .crisp-comments-breadcrumb already act on higher up in this same recipe -- a name that doesn't fit wraps to two lines with nothing
46
+ reserving room for the second one, overflowing straight into the
47
+ body text underneath (found live: this app's own dev-bypass author name, an email long enough to wrap, sat on top of the comment's own
48
+ body text). .crisp-comments-author gets the identical nowrap-
49
+ ellipsis treatment those two already have, plus min-width: 0 so a
50
+ flex item is actually allowed to shrink below its own text's natural width in the first place; .crisp-comments-time gets flex-shrink: 0 so it's the name that gives, never the timestamp -- the same "one
51
+ side shrinks, the other one doesn't" split PersonChip's own name/
52
+ avatar pair already uses (crisp#668/#671). */
35
53
  & .crisp-comments-row { position: relative; display: flex; gap: var(--crisp-space-1-5); }
36
54
  & .crisp-comments-row-main { flex: 1; min-width: 0; }
37
55
  & .crisp-comments-row-head { display: flex; align-items: baseline; gap: var(--crisp-space-1-5); height: 20px; }
38
- & .crisp-comments-author { font-size: var(--crisp-text-lg); font-weight: var(--crisp-weight-semibold); color: var(--crisp-fg-primary); }
39
- & .crisp-comments-time { font-size: 12px; font-weight: var(--crisp-weight-medium); color: color-mix(in srgb, var(--crisp-fg-primary) 63%, transparent); white-space: nowrap; }
56
+ & .crisp-comments-author { flex: 1 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: var(--crisp-text-lg); font-weight: var(--crisp-weight-semibold); color: var(--crisp-fg-primary); }
57
+ & .crisp-comments-time { flex-shrink: 0; font-size: 12px; font-weight: var(--crisp-weight-medium); color: color-mix(in srgb, var(--crisp-fg-primary) 63%, transparent); white-space: nowrap; }
40
58
  & .crisp-comments-resolved-label { display: flex; align-items: center; gap: var(--crisp-space-1); margin-bottom: var(--crisp-space-1); font-size: 12px; font-weight: var(--crisp-weight-medium); color: color-mix(in srgb, var(--crisp-fg-primary) 63%, transparent); }
41
59
  & .crisp-comments-body-text { font-size: var(--crisp-text-lg); line-height: 20px; font-weight: var(--crisp-weight-regular); color: var(--crisp-fg-primary); white-space: pre-wrap; word-break: break-word; }
42
- /* Hover actions -- measured: 4 buttons, 28x28, radius 8, flush side-by-side (0 gap), transparent by default, 16px icon at 55% ink. */
60
+
61
+ /* Hover actions -- measured: 4 buttons, 28x28, radius 8, flush
62
+ side-by-side (0 gap), transparent by default, 16px icon at 55% ink. */
43
63
  & .crisp-comments-row-actions { position: absolute; top: 0; inset-inline-end: 0; display: flex; opacity: 0; pointer-events: none; background: var(--crisp-bg-surface); }
44
- & .crisp-comments-row:hover .crisp-comments-row-actions, & .crisp-comments-row-actions:focus-within { opacity: 1; pointer-events: auto; }
64
+ & .crisp-comments-row:hover .crisp-comments-row-actions,
65
+ & .crisp-comments-row-actions:focus-within { opacity: 1; pointer-events: auto; }
45
66
  & .crisp-comments-action { display: inline-flex; align-items: center; justify-content: center; width: 28px; height: 28px; margin: 0; padding: 0; border: none; border-radius: var(--crisp-radius-md); background: transparent; color: color-mix(in srgb, var(--crisp-fg-primary) 55%, transparent); font-family: inherit; cursor: pointer; transition: background-color .12s, color .12s; }
46
67
  & .crisp-comments-action:hover { background: var(--crisp-bg-raised); color: var(--crisp-fg-primary); }
47
68
  & .crisp-comments-action[data-active] { color: var(--crisp-bg-brand-solid); }
48
- /* Reactions -- measured: 28px pill, radius 8, subtle 4% ink fill, padding 4px 8px 4px 6px, gap 4px between the emoji glyph and count. */
69
+
70
+ /* Reactions -- measured: 28px pill, radius 8, subtle 4% ink fill,
71
+ padding 4px 8px 4px 6px, gap 4px between the emoji glyph and count. */
49
72
  & .crisp-comments-reactions { display: flex; flex-wrap: wrap; gap: var(--crisp-space-1); margin-top: var(--crisp-space-1-5); }
50
73
  & .crisp-comments-reaction { display: inline-flex; align-items: center; gap: var(--crisp-space-1); height: 28px; padding: 4px 8px 4px 6px; border: none; border-radius: var(--crisp-radius-md); background: color-mix(in srgb, var(--crisp-fg-primary) 4%, transparent); font-family: inherit; font-size: 12px; font-weight: var(--crisp-weight-medium); color: var(--crisp-fg-primary); cursor: pointer; }
51
74
  & .crisp-comments-reaction[data-reacted] { background: color-mix(in srgb, var(--crisp-bg-brand-solid) 12%, transparent); color: var(--crisp-bg-brand-solid); }
52
75
  & .crisp-comments-reaction-add { display: inline-flex; align-items: center; justify-content: center; width: 28px; height: 28px; border: none; border-radius: var(--crisp-radius-md); background: transparent; font-family: inherit; color: color-mix(in srgb, var(--crisp-fg-primary) 55%, transparent); cursor: pointer; opacity: 0; transition: opacity .12s, background-color .12s; }
53
76
  & .crisp-comments-row:hover .crisp-comments-reaction-add { opacity: 1; }
54
77
  & .crisp-comments-reaction-add:hover { background: var(--crisp-bg-raised); color: var(--crisp-fg-primary); }
78
+
55
79
  /* Reply-in-thread view -- an indented block of replies below the root. */
56
80
  & .crisp-comments-replies { display: flex; flex-direction: column; gap: var(--crisp-space-4); margin-top: var(--crisp-space-3); padding-inline-start: var(--crisp-space-4); border-inline-start: 1px solid var(--crisp-border-subtle); }
81
+
57
82
  & .crisp-comments-empty { flex: 1; display: flex; flex-direction: column; align-items: center; justify-content: center; gap: var(--crisp-space-1); padding: var(--crisp-space-6) var(--crisp-space-4); text-align: center; color: color-mix(in srgb, var(--crisp-fg-primary) 55%, transparent); font-size: var(--crisp-text-lg); }
58
- /* Composer -- measured: 14px/20px body text, 16px gap above the reply/mention icon row, Comment button 84x32 padding 6px 10px radius 9 (= radius.lg) brand-solid fill, 14px/500 white label, opacity .4 disabled / 1 enabled (no color/background change between the two). */
83
+
84
+ /* Composer -- measured: 14px/20px body text, 16px gap above the
85
+ reply/mention icon row, Comment button 84x32 padding 6px 10px radius
86
+ 9 (= radius.lg) brand-solid fill, 14px/500 white label, opacity .4
87
+ disabled / 1 enabled (no color/background change between the two). */
59
88
  & .crisp-comments-composer { flex-shrink: 0; padding: var(--crisp-space-3) var(--crisp-space-4) var(--crisp-space-4); border-top: 1px solid var(--crisp-border-subtle); }
60
89
  & .crisp-comments-field { min-height: 20px; max-height: 120px; overflow-y: auto; font-family: inherit; font-size: var(--crisp-text-lg); line-height: 20px; font-weight: var(--crisp-weight-regular); color: var(--crisp-fg-primary); outline: none; white-space: pre-wrap; word-break: break-word; }
61
90
  & .crisp-comments-field:focus-visible { outline: 2px solid var(--crisp-stroke-focus-ring); outline-offset: 2px; }
@@ -65,15 +94,22 @@
65
94
  & .crisp-comments-tool { display: inline-flex; align-items: center; justify-content: center; width: 20px; height: 20px; margin: 0; padding: 0; border: none; border-radius: var(--crisp-radius-sm); background: none; color: color-mix(in srgb, var(--crisp-fg-primary) 45%, transparent); font-family: inherit; cursor: pointer; }
66
95
  & .crisp-comments-tool:hover { color: var(--crisp-fg-primary); }
67
96
  /* Same fill/shadow as Button's own intent="primary" (crisp/button.recipe.ts) --
68
- confirmed byte-identical against the live "Comment" button's computed box-shadow, so this reuses that literal rather than re-deriving it. 32px/radius.lg/6px-10px padding has no matching Button `size` yet (24/28/40 are the three that exist), so this stays hand-rolled instead of stretching one of those. */
97
+ confirmed byte-identical against the live "Comment" button's computed
98
+ box-shadow, so this reuses that literal rather than re-deriving it.
99
+ 32px/radius.lg/6px-10px padding has no matching Button `size` yet
100
+ (24/28/40 are the three that exist), so this stays hand-rolled instead
101
+ of stretching one of those. */
69
102
  & .crisp-comments-post { display: inline-flex; align-items: center; justify-content: center; height: 32px; padding: 6px var(--crisp-space-2-5); border: none; border-radius: var(--crisp-radius-lg); background: var(--crisp-bg-brand-solid); box-shadow: inset 0 0 0 1px rgba(0,0,0,0.06), 0 2px 4px -2px rgba(38,109,240,0.12), 0 3px 6px -2px rgba(38,109,240,0.08); font-family: inherit; font-size: var(--crisp-text-lg); font-weight: var(--crisp-weight-semibold); color: var(--crisp-fg-on-brand); cursor: pointer; opacity: 1; transition: opacity .12s, background-color .12s; }
70
103
  & .crisp-comments-post:hover:not(:disabled) { background: var(--crisp-bg-brand-solid-pressed); }
71
104
  & .crisp-comments-post:disabled { opacity: .4; cursor: default; }
72
- /* Filter menu -- measured: 220px, radius 12 (radius.xl), 4px padding, elevation.floating -- a plain reuse of crisp's own Menu, so no rules
105
+
106
+ /* Filter menu -- measured: 220px, radius 12 (radius.xl), 4px padding,
107
+ elevation.floating -- a plain reuse of crisp's own Menu, so no rules
73
108
  live here; kept as a comment for the record. */
74
109
 
75
110
  /* An inserted "@mention" chip in the composer -- same shape as
76
- RichTextEditor's own `.crisp-rte-mention` (crisp#428), reused here rather than re-derived since it's the same atomic-chip affordance. */
111
+ RichTextEditor's own `.crisp-rte-mention` (crisp#428), reused here
112
+ rather than re-derived since it's the same atomic-chip affordance. */
77
113
  & .crisp-comments-mention { display: inline; padding: 0 2px; border-radius: 4px; background: color-mix(in srgb, var(--crisp-bg-brand-solid) 12%, transparent); color: var(--crisp-bg-brand-solid); font-weight: var(--crisp-weight-medium); user-select: none; }
78
114
  [dir="rtl"] & [data-rtl-flip] { transform: scaleX(-1); }
79
115
  }
@@ -13,6 +13,30 @@
13
13
  .crisp-contextmenu-item--danger:hover { background: color-mix(in srgb, var(--crisp-bg-danger) 10%, transparent); }
14
14
  .crisp-contextmenu-shortcut { margin-inline-start: auto; padding-inline-start: var(--crisp-space-3); color: var(--crisp-fg-muted); font-size: var(--crisp-text-sm); }
15
15
  .crisp-contextmenu-sep { height: 1px; margin: var(--crisp-space-1) 0; border: none; background: var(--crisp-border-subtle); }
16
+ /* crisp/#598: Attio's OWN cell-level context menu, measured live on
17
+ app.attio.com/techpartners/companies (right-click a data cell) --
18
+ theme-INDEPENDENT dark chrome, not this app's dark THEME (every
19
+ geometry value above -- 12px/8px radius, 4px/8px padding, 32px item
20
+ height, item gap included below -- already matched what was
21
+ measured here; only the colour pair differs, so only colours are
22
+ overridden). Same "#101112 / #eeeff1" dark-pill ink Tooltip
23
+ already hardcodes for the identical reason (that surface is ALSO
24
+ theme-independent). */
25
+ .crisp-contextmenu-content[data-variant="dark"] { background: #101112; box-shadow: none; }
26
+ .crisp-contextmenu-content[data-variant="dark"] .crisp-contextmenu-item { gap: var(--crisp-space-1-5); color: #eeeff1; }
27
+ .crisp-contextmenu-content[data-variant="dark"] .crisp-contextmenu-item svg { color: #898a8d; }
28
+ /* Plain-item hover re-measured live on the same cell menu (right-click
29
+ "Description" on app.attio.com/techpartners/companies, hover the
30
+ "Copy" row): getComputedStyle on the row is rgba(255,255,255,0.1),
31
+ not the 0.08 this used to guess at. The danger row's own hover
32
+ (hovering "Clear value") still didn't get re-measured -- that one
33
+ remains the derived red-on-black translation below, flagged on
34
+ crisp#598 for someone to confirm live. */
35
+ .crisp-contextmenu-content[data-variant="dark"] .crisp-contextmenu-item:hover { background: rgba(255,255,255,0.1); }
36
+ .crisp-contextmenu-content[data-variant="dark"] .crisp-contextmenu-item--danger { color: #ff5454; }
37
+ .crisp-contextmenu-content[data-variant="dark"] .crisp-contextmenu-item--danger svg { color: #ff5454; }
38
+ .crisp-contextmenu-content[data-variant="dark"] .crisp-contextmenu-item--danger:hover { background: rgba(255,84,84,0.12); }
39
+ .crisp-contextmenu-content[data-variant="dark"] .crisp-contextmenu-sep { background: rgba(255,255,255,0.1); }
16
40
 
17
41
  .crisp-contextmenu {
18
42
  display: contents; font-family: var(--crisp-font-sans);
@@ -14,6 +14,30 @@
14
14
  .crisp-contextmenu-item--danger:hover { background: color-mix(in srgb, var(--crisp-bg-danger) 10%, transparent); }
15
15
  .crisp-contextmenu-shortcut { margin-inline-start: auto; padding-inline-start: var(--crisp-space-3); color: var(--crisp-fg-muted); font-size: var(--crisp-text-sm); }
16
16
  .crisp-contextmenu-sep { height: 1px; margin: var(--crisp-space-1) 0; border: none; background: var(--crisp-border-subtle); }
17
+ /* crisp/#598: Attio's OWN cell-level context menu, measured live on
18
+ app.attio.com/techpartners/companies (right-click a data cell) --
19
+ theme-INDEPENDENT dark chrome, not this app's dark THEME (every
20
+ geometry value above -- 12px/8px radius, 4px/8px padding, 32px item
21
+ height, item gap included below -- already matched what was
22
+ measured here; only the colour pair differs, so only colours are
23
+ overridden). Same "#101112 / #eeeff1" dark-pill ink Tooltip
24
+ already hardcodes for the identical reason (that surface is ALSO
25
+ theme-independent). */
26
+ .crisp-contextmenu-content[data-variant="dark"] { background: #101112; box-shadow: none; }
27
+ .crisp-contextmenu-content[data-variant="dark"] .crisp-contextmenu-item { gap: var(--crisp-space-1-5); color: #eeeff1; }
28
+ .crisp-contextmenu-content[data-variant="dark"] .crisp-contextmenu-item svg { color: #898a8d; }
29
+ /* Plain-item hover re-measured live on the same cell menu (right-click
30
+ "Description" on app.attio.com/techpartners/companies, hover the
31
+ "Copy" row): getComputedStyle on the row is rgba(255,255,255,0.1),
32
+ not the 0.08 this used to guess at. The danger row's own hover
33
+ (hovering "Clear value") still didn't get re-measured -- that one
34
+ remains the derived red-on-black translation below, flagged on
35
+ crisp#598 for someone to confirm live. */
36
+ .crisp-contextmenu-content[data-variant="dark"] .crisp-contextmenu-item:hover { background: rgba(255,255,255,0.1); }
37
+ .crisp-contextmenu-content[data-variant="dark"] .crisp-contextmenu-item--danger { color: #ff5454; }
38
+ .crisp-contextmenu-content[data-variant="dark"] .crisp-contextmenu-item--danger svg { color: #ff5454; }
39
+ .crisp-contextmenu-content[data-variant="dark"] .crisp-contextmenu-item--danger:hover { background: rgba(255,84,84,0.12); }
40
+ .crisp-contextmenu-content[data-variant="dark"] .crisp-contextmenu-sep { background: rgba(255,255,255,0.1); }
17
41
 
18
42
  .crisp-contextmenu {
19
43
  display: contents; font-family: var(--crisp-font-sans);
@@ -9,45 +9,71 @@
9
9
  & .crisp-datatable-count { margin-inline-start: auto; font-size: var(--crisp-text-md); color: var(--crisp-fg-muted); }
10
10
  /* announced, never drawn */
11
11
  & .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; }
12
- /* 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. */
13
- & th { padding: 0; }
14
- /* Same wrapper trap as AUTHORING.md Lesson 40 (ColumnCalc's summary trigger):
15
- Menu wraps its trigger in a flex div that sizes to its content, so a
16
- long header grew past the column instead of filling it -- with nothing
17
- 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
18
- it with no "...". Fill the wrapper, scoped to th so Menu's default (correct everywhere else) is untouched. */
12
+ /* 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. */
13
+ & .crisp-datatable-clipboard-proxy {
14
+ position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
15
+ overflow: hidden; clip: rect(0, 0, 0, 0); white-space: nowrap; border: 0;
16
+ resize: none;
17
+ }
18
+ /* single active cell (crisp/#645 follow-up -- this had no styling of its
19
+ 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). */
20
+ & td[data-selected] { outline: 2px solid var(--crisp-bg-brand-solid); outline-offset: -2px; border-radius: var(--crisp-radius-sm); z-index: 3; }
21
+ /* 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
22
+ highlight (out of scope this slice -- flagged on crisp#645). */
23
+ & td[data-in-range] { background: color-mix(in srgb, var(--crisp-bg-brand-solid) 10%, var(--crisp-bg-surface));
24
+ } /* 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
25
+ own fix live): DataTable's root nests `Table`'s own wrapper, whose
26
+ `table.recipe.ts` carries `& th, & td { padding: 0 12px }
27
+ ` 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
28
+ 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
29
+ double it to 24px. Scoping through `.crisp-table` adds a second
30
+ class to the selector (0,2,1 beats 0,1,1) so this can never lose a
31
+ specificity tie to source order again. */
32
+ & .crisp-table th { padding: 0; }
33
+ /* 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
34
+ -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
35
+ (correct everywhere else) is untouched. */
19
36
  & th .crisp-menu { width: 100%; height: 100%; }
20
37
  & .crisp-datatable-th-inner { display: inline-flex; align-items: center; gap: var(--crisp-space-1-5); width: 100%; min-width: 0; height: 100%; }
21
38
  & .crisp-datatable-th-inner > svg { flex-shrink: 0; color: var(--crisp-fg-muted); }
22
- /* 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. */
39
+ /* crisp/#605 follow-up: a header with neither a Menu nor a sort button
40
+ (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
41
+ 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
42
+ padding+icon+gap combinations -- Attio does not leave the inert
43
+ primary header flush left either. The `>` combinator targets only
44
+ 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. */
45
+ & th > .crisp-datatable-th-inner { box-sizing: border-box; padding: 0 var(--crisp-space-3); }
46
+ /* The summary cell has NO padding of its own -- the calculation trigger
47
+ owns the whole 246x37 area so a click anywhere in the cell opens the
48
+ menu. Zero the cell's inset and let ColumnCalc supply the 12px. */
23
49
  & tfoot td { padding: 0; }
24
- /* Group heading row -- a view is grouped under a subtle label with the
25
- bucket's count beside it. */
26
- & .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; }
50
+ /* Group heading row -- a view is grouped under a subtle label with the bucket's count beside it. */
51
+ & .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; }
27
52
  & .crisp-datatable-viewsettings:hover { background: color-mix(in srgb, var(--crisp-fg-primary) 4%, transparent); color: var(--crisp-fg-primary); }
28
53
  & .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); }
29
- & .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); }
54
+ & .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); }
30
55
  & .crisp-datatable-group-count { margin-inline-start: var(--crisp-space-1-5); color: var(--crisp-fg-muted); font-variant-numeric: tabular-nums; }
31
56
  & .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; }
32
57
  & .crisp-datatable-th-rename:focus { box-shadow: inset 0 0 0 1px var(--crisp-bg-brand-solid); }
33
58
  & .crisp-datatable-th-label { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; min-width: 0; }
34
- /* #465: 12px per the comment above (and Attio's measured header cell
35
- padding: 0 12px) -- was still c.space["2"] (8px), drifted from its
36
- own documented value. */
59
+ /* #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. */
37
60
  & .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; }
38
- /* 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
39
- count") keeps the icon+label left and only the trailing column-menu
40
- icon sits pinned to the right edge, independent of the label's width (#406, reversing #393's own justify-content: flex-end here). The
41
- 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
42
- the trailing icon on Attio too. */
61
+ /* A right-aligned column's header keeps its field-icon + label packed at
62
+ the START, matching its own values only in that the VALUES right-align
63
+ -- Attio's own right-aligned header (measured live: "Twitter follower
64
+ 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
65
+ (#406, reversing #393's own justify-content: flex-end here). The chevron's own margin-inline-start: auto below already pins IT to the
66
+ end of a flex-start row with no override needed -- that's what pins the trailing icon on Attio too. */
43
67
  & .crisp-datatable-chevron { margin-inline-start: auto; flex-shrink: 0; color: var(--crisp-fg-muted); opacity: 0; transition: opacity .12s; }
44
68
  & .crisp-datatable-sort:hover .crisp-datatable-chevron { opacity: 0.6; }
45
69
  & .crisp-datatable-sort[data-sorted] .crisp-datatable-chevron { opacity: 1; color: var(--crisp-fg-primary); }
46
70
  /* sticky first column(s) + horizontal scroll shadow — RecordsTable's feel */
47
71
  & th[data-sticky], & td[data-sticky] { position: sticky; z-index: 1; background: var(--crisp-bg-surface); }
48
- & th[data-sticky] { z-index: 3; }
49
- /* #554: a footer sticky-column cell sits in the bottom-sticky row (table.recipe.ts's
50
- `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. */
72
+ & th[data-sticky] { z-index: 3;
73
+ } /* #554: a footer sticky-column cell sits in the bottom-sticky row (table.recipe.ts's
74
+ `tfoot td { z-index: 2 }
75
+ `), which `td[data-sticky]`'s own higher-specificity
76
+ `z-index: 1` would otherwise undercut -- keep it level with the row it's in. */
51
77
  & tfoot td[data-sticky] { z-index: 2; }
52
78
  & tbody tr[data-selected] td[data-sticky] { background: color-mix(in srgb, var(--crisp-bg-brand-solid) 10%, var(--crisp-bg-surface)); }
53
79
  & tbody tr:hover td[data-sticky] { background: color-mix(in srgb, var(--crisp-fg-primary) 4%, var(--crisp-bg-surface)); }
@@ -66,40 +92,77 @@
66
92
  & .crisp-datatable-editable:hover { background: color-mix(in srgb, var(--crisp-fg-primary) 4%, var(--crisp-bg-surface)); cursor: text; }
67
93
  & .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; }
68
94
  & .crisp-datatable-edit:focus { box-shadow: inset 0 0 0 1px var(--crisp-bg-brand-solid); }
69
- /* An async onEdit's in-flight state (#385) -- the field/picker dims and
70
- stops accepting input rather than disappearing, so the just-typed or
71
- just-chosen value stays visible while its commit is pending. Same
72
- treatment Input already gives its own disabled state. */
95
+ /* 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. */
73
96
  & .crisp-datatable-edit:disabled { opacity: 0.5; cursor: not-allowed; }
74
97
  & .crisp-datatable-edit-pending { flex-shrink: 0; margin-inline-start: var(--crisp-space-1); color: var(--crisp-fg-muted); vertical-align: middle; }
75
- /* The rejection's message, beside the cell's own content -- a sibling in
76
- 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
77
- it. Block-level: the row grows to fit it rather than floating over
78
- the row below, so it can never end up clipped by the scroll
79
- container's overflow the way a position:fixed escape would need guarding against. */
98
+ /* 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
99
+ 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
100
+ guarding against. */
80
101
  & .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); }
81
- /* 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
82
- shares RecordsTable's grid, Golden Rule 0). */
102
+ /* editor: "select"/"switch" -- always live, no separate edit mode, so
103
+ the cell IS the picker: same chromeless-fill treatment as
104
+ RecordsTable's own --picker cells (measured there already; DataTable shares RecordsTable's grid, Golden Rule 0). */
83
105
  & .crisp-datatable-td--picker { padding: 0; }
84
106
  & .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); }
85
107
  & .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; }
86
108
  & .crisp-datatable-td--picker .crisp-datatable-edit-error { padding-inline-start: var(--crisp-space-2); }
87
- /* 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*. */
109
+ /* reference picker: a leading 16px avatar per option, plus a trailing
110
+ "Create X" row when the consumer supplies one (#449) -- same measured
111
+ values as RecordsTable's own .crisp-recordstable-refoption*. */
88
112
  & .crisp-datatable-refoption { display: flex; align-items: center; gap: var(--crisp-space-2); min-width: 0; overflow: hidden; text-overflow: ellipsis; }
89
113
  & .crisp-datatable-refoption-avatar { flex-shrink: 0; }
90
114
  & .crisp-datatable-refoption--create { color: var(--crisp-fg-muted); }
91
115
  & .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); }
116
+ /* #579: click-to-expand overlay for a truncated cell -- a read-only, full-value card, settled live as click (not Attio's usual
117
+ 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
118
+ 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
119
+ visible viewport, same as the edit-error comment above already
120
+ notes for any position:absolute escape -- unmeasured against Attio
121
+ for this exact case, flagged rather than guessed. */
122
+ & td[data-cell-expanded] { overflow: visible; z-index: 7; }
123
+ & .crisp-datatable-cell-overlay {
124
+ position: absolute; inset-inline-start: 0; top: 0; width: 100%; box-sizing: border-box;
125
+ padding: var(--crisp-space-2) var(--crisp-space-3); background: var(--crisp-bg-surface);
126
+ border: 1px solid var(--crisp-bg-brand-solid); border-radius: 4px;
127
+ font-family: var(--crisp-font-sans); font-size: var(--crisp-text-lg); line-height: 20px; font-weight: var(--crisp-weight-medium);
128
+ color: var(--crisp-fg-primary); white-space: normal; word-break: break-word; cursor: default;
129
+ }
92
130
  /* fixed layout so column widths (and resize) are exact — like RecordsTable */
93
131
  & table { table-layout: fixed; }
132
+ /* read-only/computed column marker (#596) -- sits just inside the
133
+ resize handle, same absolute-positioned-against-the-th approach the
134
+ handle itself uses, since th has zero padding and the button/inner
135
+ row fills the whole cell with no room left for a trailing sibling. */
136
+ & .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; }
94
137
  /* column resize grip */
95
138
  & .crisp-datatable-resize { position: absolute; top: 0; inset-inline-end: -3px; width: 9px; height: 100%; cursor: col-resize; z-index: 6; touch-action: none; }
96
139
  & .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; }
97
140
  & .crisp-datatable-resize:hover::after, & .crisp-datatable-resize[data-resizing]::after { opacity: 1; }
141
+ /* crisp/#731 -- column drag-to-reorder. Measured live against Attio's own grid (the issue's own comment): the dragged header keeps full
142
+ opacity and the plain `pointer` cursor throughout (Attio draws no
143
+ ghost and no "grabbing" cursor of its own), so this adds nothing to
144
+ `[data-dragging]` itself -- the ONLY new paint is the drop-indicator
145
+ 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. */
146
+ & th[data-dragging] { cursor: pointer; }
147
+ & 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; }
148
+ & th[data-drop-before]::before { inset-inline-start: -1.5px; }
149
+ & th[data-drop-after]::after { inset-inline-end: -1.5px; }
98
150
  /* typed cells — entity (avatar + name), colour-coded tags, numbers, dates */
99
151
  & .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); }
100
- & .crisp-datatable-entity-name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
101
- & .crisp-datatable-entity:hover .crisp-datatable-entity-name { text-decoration: underline; }
152
+ /* crisp/#601, G-050: measured LIVE (getComputedStyle on the name text, plain hover vs rest) that Attio's underline is unconditional, not a
153
+ hover reveal -- identical at rest and on hover: text-decoration-color
154
+ 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. */
155
+ & .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); }
102
156
  & 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%; }
157
+ /* 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
158
+ hover", and a live check confirmed hovering well past the name --
159
+ 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
160
+ expressed as color-mix against fgPrimary so they stay correct in
161
+ dark mode instead of a literal black that would invert. */
162
+ & .crisp-datatable-entity-actions { display: flex; align-items: center; gap: var(--crisp-space-1-5); margin-inline-start: auto; flex-shrink: 0; opacity: 0; }
163
+ & tr:hover .crisp-datatable-entity-actions, & .crisp-datatable-entity-actions:focus-within { opacity: 1; }
164
+ & .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; }
165
+ & .crisp-datatable-entity-action:hover { background: color-mix(in srgb, var(--crisp-fg-primary) 4%, transparent); color: var(--crisp-fg-primary); }
103
166
  & .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); }
104
167
  & .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); }
105
168
  & .crisp-datatable-avatar img { width: 100%; height: 100%; border-radius: inherit; object-fit: cover; }
@@ -126,6 +189,7 @@
126
189
  & .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; }
127
190
  & .crisp-datatable-grip:active { cursor: grabbing; }
128
191
  & tbody tr[data-drop-target] td, & tbody tr[data-drop-target] th { box-shadow: inset 0 2px 0 0 var(--crisp-bg-brand-solid); transition: box-shadow .12s; }
192
+ [dir="rtl"] & [data-rtl-flip] { transform: scaleX(-1); }
129
193
  }
130
194
 
131
195
  @media (prefers-reduced-motion: reduce) {