@crisp-ui-kit/crisp 0.54.8 → 0.54.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (59) hide show
  1. package/dist/board.css +32 -8
  2. package/dist/board.layered.css +32 -8
  3. package/dist/cjs/components/contextmenu/contextmenu.js +2 -2
  4. package/dist/cjs/components/contextmenu/contextmenu.recipe.js +24 -0
  5. package/dist/cjs/components/datatable/datatable.js +593 -51
  6. package/dist/cjs/components/datatable/datatable.recipe.js +67 -3
  7. package/dist/cjs/components/dialog/dialog.js +65 -1
  8. package/dist/cjs/components/tabletoolbar/tabletoolbar.chrome.js +37 -2
  9. package/dist/cjs/components/tabletoolbar/tabletoolbar.js +218 -39
  10. package/dist/cjs/components/tabletoolbar/tabletoolbar.recipe.js +48 -1
  11. package/dist/cjs/components/text/text.recipe.js +17 -7
  12. package/dist/cjs/product/board/board.js +62 -28
  13. package/dist/cjs/product/board/board.recipe.js +42 -2
  14. package/dist/cjs/product/comments/comments.js +11 -2
  15. package/dist/cjs/product/comments/comments.recipe.js +14 -4
  16. package/dist/cjs/product/recordstable/recordstable.js +1 -17
  17. package/dist/cjs/product/savedviews/savedviews.js +55 -24
  18. package/dist/cjs/product/savedviews/savedviews.recipe.js +27 -0
  19. package/dist/comments.css +14 -4
  20. package/dist/comments.layered.css +14 -4
  21. package/dist/contextmenu.css +24 -0
  22. package/dist/contextmenu.layered.css +24 -0
  23. package/dist/datatable.css +106 -77
  24. package/dist/datatable.layered.css +106 -77
  25. package/dist/esm/components/contextmenu/contextmenu.js +2 -2
  26. package/dist/esm/components/contextmenu/contextmenu.recipe.js +24 -0
  27. package/dist/esm/components/datatable/datatable.js +593 -52
  28. package/dist/esm/components/datatable/datatable.recipe.js +68 -4
  29. package/dist/esm/components/dialog/dialog.js +65 -1
  30. package/dist/esm/components/tabletoolbar/tabletoolbar.chrome.js +37 -2
  31. package/dist/esm/components/tabletoolbar/tabletoolbar.js +219 -40
  32. package/dist/esm/components/tabletoolbar/tabletoolbar.recipe.js +50 -3
  33. package/dist/esm/components/text/text.recipe.js +17 -7
  34. package/dist/esm/product/board/board.js +63 -29
  35. package/dist/esm/product/board/board.recipe.js +42 -2
  36. package/dist/esm/product/comments/comments.js +12 -3
  37. package/dist/esm/product/comments/comments.recipe.js +14 -4
  38. package/dist/esm/product/recordstable/recordstable.js +2 -18
  39. package/dist/esm/product/savedviews/savedviews.js +57 -26
  40. package/dist/esm/product/savedviews/savedviews.recipe.js +27 -0
  41. package/dist/recordstable.css +33 -31
  42. package/dist/recordstable.layered.css +33 -31
  43. package/dist/savedviews.css +27 -0
  44. package/dist/savedviews.layered.css +27 -0
  45. package/dist/styles.css +282 -125
  46. package/dist/styles.layered.css +282 -125
  47. package/dist/tabletoolbar.css +45 -4
  48. package/dist/tabletoolbar.layered.css +45 -4
  49. package/dist/text.css +1 -1
  50. package/dist/text.layered.css +1 -1
  51. package/dist/types/components/contextmenu/contextmenu.d.ts +8 -1
  52. package/dist/types/components/datatable/datatable.d.ts +98 -2
  53. package/dist/types/components/tabletoolbar/tabletoolbar.d.ts +72 -2
  54. package/dist/types/components/text/text.recipe.d.ts +1 -1
  55. package/dist/types/product/board/board.d.ts +48 -2
  56. package/dist/types/product/comments/comments.d.ts +6 -1
  57. package/dist/types/product/recordstable/recordstable.d.ts +0 -1
  58. package/dist/types/product/savedviews/savedviews.d.ts +45 -8
  59. package/package.json +1 -1
@@ -39,7 +39,7 @@
39
39
  & .crisp-tabletoolbar-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; }
40
40
  & .crisp-tabletoolbar-sort-learn svg { flex-shrink: 0; }
41
41
  & .crisp-tabletoolbar-sort-learn:hover { color: var(--crisp-fg-primary); }
42
- & .crisp-tabletoolbar-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; cursor: pointer; }
42
+ & .crisp-tabletoolbar-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; }
43
43
  & .crisp-tabletoolbar-menu-item:hover { background: var(--crisp-bg-raised); }
44
44
  & .crisp-tabletoolbar-menu-item:disabled { opacity: 0.4; cursor: default; }
45
45
  & .crisp-tabletoolbar-menu-item:disabled:hover { background: none; }
@@ -47,7 +47,25 @@
47
47
  & .crisp-tabletoolbar-menu-item[data-on] svg { color: var(--crisp-bg-brand-solid); }
48
48
  & .crisp-tabletoolbar-menu-item .crisp-tabletoolbar-menu-check { margin-left: auto; }
49
49
  & .crisp-tabletoolbar-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; }
50
- & .crisp-tabletoolbar-menu-sep { height: 1px; background: var(--crisp-border-subtle); margin: var(--crisp-space-0-5) 0; }
50
+ & .crisp-tabletoolbar-menu-sep { height: 1px; border: none; background: var(--crisp-border-subtle); margin: var(--crisp-space-0-5) 0; }
51
+ /* #592: the Filter field picker's nested-scope header (back control +
52
+ search, once drilled into a nestedColumns column) and a search
53
+ 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
54
+ colours ARE measured live: the parent segment muted at the same
55
+ exact color-mix 63% sort-learn above already uses, the
56
+ separator chevron and leaf segment both full fgPrimary (the
57
+ generic menu-item svg rule below defaults every icon to
58
+ fgMuted, so the separator needs its own override to read
59
+ correctly here). */
60
+ & .crisp-tabletoolbar-menu-nesthead { display: flex; align-items: center; gap: var(--crisp-space-1); margin: var(--crisp-space-0-5); }
61
+ & .crisp-tabletoolbar-menu-nesthead .crisp-tabletoolbar-menu-input { flex: 1; margin: 0; }
62
+ & .crisp-tabletoolbar-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; }
63
+ & .crisp-tabletoolbar-menu-back:hover { background: var(--crisp-bg-raised); color: var(--crisp-fg-primary); }
64
+ & .crisp-tabletoolbar-menu-item--expand { justify-content: space-between; }
65
+ & .crisp-tabletoolbar-menu-item-path { display: inline-flex; align-items: center; gap: 2px; min-width: 0; overflow: hidden; }
66
+ & .crisp-tabletoolbar-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); }
67
+ & .crisp-tabletoolbar-menu-item-path-sep { flex-shrink: 0; color: var(--crisp-fg-primary); }
68
+ & .crisp-tabletoolbar-menu-item-path > span:last-child { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
51
69
  & .crisp-tabletoolbar-menu--up { top: auto; bottom: calc(100% + var(--crisp-space-1)); transform-origin: bottom; }
52
70
  & .crisp-tabletoolbar-menu--right { left: auto; right: 0; }
53
71
  & .crisp-tabletoolbar-menu--fixed { position: fixed; top: auto; left: auto; z-index: 40; }
@@ -68,21 +86,44 @@
68
86
  & .crisp-tabletoolbar-count { font-size: var(--crisp-text-md); color: var(--crisp-fg-muted); }
69
87
  & .crisp-tabletoolbar-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; }
70
88
  & .crisp-tabletoolbar-new:hover { background: var(--crisp-bg-brand-solid-pressed); }
89
+
71
90
  & .crisp-tabletoolbar-fcond { display: inline-flex; align-items: center; height: var(--crisp-size-row); font-size: var(--crisp-text-md); color: var(--crisp-fg-primary); white-space: nowrap; }
72
91
  & .crisp-tabletoolbar-fcond-seg { display: inline-flex; align-items: center; gap: var(--crisp-space-1-5); height: 100%; margin: 0; padding: 0 var(--crisp-space-2); border: none; background: var(--crisp-bg-surface-muted); box-shadow: inset 0 0 0 1px var(--crisp-border-divider); font-family: inherit; font-size: inherit; color: inherit; cursor: pointer; }
73
92
  & .crisp-tabletoolbar-fcond-seg svg { flex-shrink: 0; }
74
- & .crisp-tabletoolbar-fcond[data-complete] .crisp-tabletoolbar-fcond-seg--field, & .crisp-tabletoolbar-fcond[data-complete] .crisp-tabletoolbar-fcond-seg--val { background: var(--crisp-bg-surface); }
75
- & .crisp-tabletoolbar-fcond-seg--field { border-radius: var(--crisp-radius-md) 0 0 var(--crisp-radius-md); font-weight: var(--crisp-weight-medium); }
93
+ & .crisp-tabletoolbar-fcond[data-complete] .crisp-tabletoolbar-fcond-seg--field,
94
+ & .crisp-tabletoolbar-fcond[data-complete] .crisp-tabletoolbar-fcond-seg--val { background: var(--crisp-bg-surface); }
95
+ & .crisp-tabletoolbar-fcond-seg--field { border-radius: var(--crisp-radius-md) 0 0 var(--crisp-radius-md); font-weight: var(--crisp-weight-medium); max-width: 200px; }
76
96
  & .crisp-tabletoolbar-fcond-seg--field svg { color: var(--crisp-fg-muted); }
97
+ /* #592: a nested pick's field segment ("Team › Twitter follower
98
+ count") — measured live: BOTH path segments the same colour (chip context only; the picker's own menu rows keep the parent muted, see
99
+ tabletoolbar.chrome.ts), each independently truncated. Attio's own layout gives the leaf segment priority (it stays fully readable longer, the parent truncates first) via a two-part min-content measurement this reproduces qualitatively with a flex-shrink differential rather than byte-for-byte (the exact px split Attio computes is content-length-dependent, not a fixed value) — not independently re-verified at other path-segment lengths. */
100
+ & .crisp-tabletoolbar-fcond-path { display: inline-flex; align-items: center; gap: 2px; min-width: 0; max-width: 178px; }
101
+ & .crisp-tabletoolbar-fcond-path-seg { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; min-width: 0; flex: 1 1 auto; }
102
+ & .crisp-tabletoolbar-fcond-path-seg:first-child { flex: 0 1 auto; }
103
+ & .crisp-tabletoolbar-fcond-path-sep { flex-shrink: 0; color: inherit; }
77
104
  & .crisp-tabletoolbar-fcond-seg--op { background: var(--crisp-bg-surface); color: var(--crisp-fg-muted); }
78
105
  & .crisp-tabletoolbar-fcond-seg--val { font-weight: var(--crisp-weight-medium); max-width: 140px; overflow: hidden; text-overflow: ellipsis; }
79
106
  & .crisp-tabletoolbar-fcond-seg--kebab { background: var(--crisp-bg-surface); justify-content: center; width: 28px; padding: 0; border-radius: 0 var(--crisp-radius-md) var(--crisp-radius-md) 0; color: var(--crisp-fg-muted); }
80
107
  & .crisp-tabletoolbar-fcond-seg--kebab:hover { color: var(--crisp-fg-primary); }
108
+ /* #754: the sort row's own field trigger, replacing a plain Select
109
+ with the same nested-drill-down popover Filter's field picker already has — reuses Select's own already-measured trigger chrome
110
+ verbatim (its class is scoped under Select's own root class, so it can't be reused as a bare class name outside an actual Select tree;
111
+ these values are copied, not re-measured). */
112
+ & .crisp-tabletoolbar-sort-field-picker { position: relative; display: inline-flex; flex: 1; min-width: 0; }
113
+ & .crisp-tabletoolbar-sort-field-trigger { box-sizing: border-box; display: inline-flex; align-items: center; gap: var(--crisp-space-1-5); height: var(--crisp-size-menuRow); width: 100%; min-width: 0; margin: 0; padding: 0 var(--crisp-space-1-5) 0 var(--crisp-space-2); border: none; border-radius: var(--crisp-radius-md); background: transparent; box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--crisp-fg-primary) 7%, transparent); font-family: inherit; font-size: var(--crisp-text-lg); color: var(--crisp-fg-primary); cursor: pointer; transition: background-color .12s ease, box-shadow .12s ease; }
114
+ & .crisp-tabletoolbar-sort-field-trigger:hover { background: var(--crisp-bg-raised); }
115
+ & .crisp-tabletoolbar-sort-field-trigger[aria-expanded="true"],
116
+ & .crisp-tabletoolbar-sort-field-trigger:focus-visible { outline: none; box-shadow: inset 0 0 0 1px var(--crisp-bg-brand-solid); background: var(--crisp-bg-surface); }
117
+ & .crisp-tabletoolbar-sort-field-label { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; text-align: start; }
118
+ & .crisp-tabletoolbar-sort-field-picker .crisp-tabletoolbar-menu { min-width: 200px; }
81
119
  & .crisp-tabletoolbar-menu--val { min-width: 134px; }
82
120
  & .crisp-tabletoolbar-datepop { width: 300px; }
83
121
  & .crisp-tabletoolbar-datepop-presets { display: flex; flex-wrap: wrap; gap: var(--crisp-space-1); margin-top: var(--crisp-space-2); padding: 0 var(--crisp-space-1); }
84
122
  & .crisp-tabletoolbar-datepop-preset { height: var(--crisp-size-chip); padding: 0 var(--crisp-space-2); border: none; border-radius: var(--crisp-radius-chip); background: var(--crisp-bg-raised); color: var(--crisp-fg-primary); font-family: inherit; font-size: var(--crisp-text-sm); cursor: pointer; }
85
123
  & .crisp-tabletoolbar-datepop-preset:hover { background: color-mix(in srgb, var(--crisp-fg-primary) 8%, transparent); }
124
+
125
+ & .crisp-tabletoolbar-tool--raised { border: none; background: var(--crisp-bg-surface-muted); 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; cursor: pointer; }
126
+ [dir="rtl"] & [data-rtl-flip] { transform: scaleX(-1); }
86
127
  }
87
128
 
88
129
  @media (prefers-reduced-motion: reduce) {
@@ -40,7 +40,7 @@
40
40
  & .crisp-tabletoolbar-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; }
41
41
  & .crisp-tabletoolbar-sort-learn svg { flex-shrink: 0; }
42
42
  & .crisp-tabletoolbar-sort-learn:hover { color: var(--crisp-fg-primary); }
43
- & .crisp-tabletoolbar-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; cursor: pointer; }
43
+ & .crisp-tabletoolbar-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; }
44
44
  & .crisp-tabletoolbar-menu-item:hover { background: var(--crisp-bg-raised); }
45
45
  & .crisp-tabletoolbar-menu-item:disabled { opacity: 0.4; cursor: default; }
46
46
  & .crisp-tabletoolbar-menu-item:disabled:hover { background: none; }
@@ -48,7 +48,25 @@
48
48
  & .crisp-tabletoolbar-menu-item[data-on] svg { color: var(--crisp-bg-brand-solid); }
49
49
  & .crisp-tabletoolbar-menu-item .crisp-tabletoolbar-menu-check { margin-left: auto; }
50
50
  & .crisp-tabletoolbar-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; }
51
- & .crisp-tabletoolbar-menu-sep { height: 1px; background: var(--crisp-border-subtle); margin: var(--crisp-space-0-5) 0; }
51
+ & .crisp-tabletoolbar-menu-sep { height: 1px; border: none; background: var(--crisp-border-subtle); margin: var(--crisp-space-0-5) 0; }
52
+ /* #592: the Filter field picker's nested-scope header (back control +
53
+ search, once drilled into a nestedColumns column) and a search
54
+ 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
55
+ colours ARE measured live: the parent segment muted at the same
56
+ exact color-mix 63% sort-learn above already uses, the
57
+ separator chevron and leaf segment both full fgPrimary (the
58
+ generic menu-item svg rule below defaults every icon to
59
+ fgMuted, so the separator needs its own override to read
60
+ correctly here). */
61
+ & .crisp-tabletoolbar-menu-nesthead { display: flex; align-items: center; gap: var(--crisp-space-1); margin: var(--crisp-space-0-5); }
62
+ & .crisp-tabletoolbar-menu-nesthead .crisp-tabletoolbar-menu-input { flex: 1; margin: 0; }
63
+ & .crisp-tabletoolbar-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; }
64
+ & .crisp-tabletoolbar-menu-back:hover { background: var(--crisp-bg-raised); color: var(--crisp-fg-primary); }
65
+ & .crisp-tabletoolbar-menu-item--expand { justify-content: space-between; }
66
+ & .crisp-tabletoolbar-menu-item-path { display: inline-flex; align-items: center; gap: 2px; min-width: 0; overflow: hidden; }
67
+ & .crisp-tabletoolbar-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); }
68
+ & .crisp-tabletoolbar-menu-item-path-sep { flex-shrink: 0; color: var(--crisp-fg-primary); }
69
+ & .crisp-tabletoolbar-menu-item-path > span:last-child { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
52
70
  & .crisp-tabletoolbar-menu--up { top: auto; bottom: calc(100% + var(--crisp-space-1)); transform-origin: bottom; }
53
71
  & .crisp-tabletoolbar-menu--right { left: auto; right: 0; }
54
72
  & .crisp-tabletoolbar-menu--fixed { position: fixed; top: auto; left: auto; z-index: 40; }
@@ -69,21 +87,44 @@
69
87
  & .crisp-tabletoolbar-count { font-size: var(--crisp-text-md); color: var(--crisp-fg-muted); }
70
88
  & .crisp-tabletoolbar-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; }
71
89
  & .crisp-tabletoolbar-new:hover { background: var(--crisp-bg-brand-solid-pressed); }
90
+
72
91
  & .crisp-tabletoolbar-fcond { display: inline-flex; align-items: center; height: var(--crisp-size-row); font-size: var(--crisp-text-md); color: var(--crisp-fg-primary); white-space: nowrap; }
73
92
  & .crisp-tabletoolbar-fcond-seg { display: inline-flex; align-items: center; gap: var(--crisp-space-1-5); height: 100%; margin: 0; padding: 0 var(--crisp-space-2); border: none; background: var(--crisp-bg-surface-muted); box-shadow: inset 0 0 0 1px var(--crisp-border-divider); font-family: inherit; font-size: inherit; color: inherit; cursor: pointer; }
74
93
  & .crisp-tabletoolbar-fcond-seg svg { flex-shrink: 0; }
75
- & .crisp-tabletoolbar-fcond[data-complete] .crisp-tabletoolbar-fcond-seg--field, & .crisp-tabletoolbar-fcond[data-complete] .crisp-tabletoolbar-fcond-seg--val { background: var(--crisp-bg-surface); }
76
- & .crisp-tabletoolbar-fcond-seg--field { border-radius: var(--crisp-radius-md) 0 0 var(--crisp-radius-md); font-weight: var(--crisp-weight-medium); }
94
+ & .crisp-tabletoolbar-fcond[data-complete] .crisp-tabletoolbar-fcond-seg--field,
95
+ & .crisp-tabletoolbar-fcond[data-complete] .crisp-tabletoolbar-fcond-seg--val { background: var(--crisp-bg-surface); }
96
+ & .crisp-tabletoolbar-fcond-seg--field { border-radius: var(--crisp-radius-md) 0 0 var(--crisp-radius-md); font-weight: var(--crisp-weight-medium); max-width: 200px; }
77
97
  & .crisp-tabletoolbar-fcond-seg--field svg { color: var(--crisp-fg-muted); }
98
+ /* #592: a nested pick's field segment ("Team › Twitter follower
99
+ count") — measured live: BOTH path segments the same colour (chip context only; the picker's own menu rows keep the parent muted, see
100
+ tabletoolbar.chrome.ts), each independently truncated. Attio's own layout gives the leaf segment priority (it stays fully readable longer, the parent truncates first) via a two-part min-content measurement this reproduces qualitatively with a flex-shrink differential rather than byte-for-byte (the exact px split Attio computes is content-length-dependent, not a fixed value) — not independently re-verified at other path-segment lengths. */
101
+ & .crisp-tabletoolbar-fcond-path { display: inline-flex; align-items: center; gap: 2px; min-width: 0; max-width: 178px; }
102
+ & .crisp-tabletoolbar-fcond-path-seg { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; min-width: 0; flex: 1 1 auto; }
103
+ & .crisp-tabletoolbar-fcond-path-seg:first-child { flex: 0 1 auto; }
104
+ & .crisp-tabletoolbar-fcond-path-sep { flex-shrink: 0; color: inherit; }
78
105
  & .crisp-tabletoolbar-fcond-seg--op { background: var(--crisp-bg-surface); color: var(--crisp-fg-muted); }
79
106
  & .crisp-tabletoolbar-fcond-seg--val { font-weight: var(--crisp-weight-medium); max-width: 140px; overflow: hidden; text-overflow: ellipsis; }
80
107
  & .crisp-tabletoolbar-fcond-seg--kebab { background: var(--crisp-bg-surface); justify-content: center; width: 28px; padding: 0; border-radius: 0 var(--crisp-radius-md) var(--crisp-radius-md) 0; color: var(--crisp-fg-muted); }
81
108
  & .crisp-tabletoolbar-fcond-seg--kebab:hover { color: var(--crisp-fg-primary); }
109
+ /* #754: the sort row's own field trigger, replacing a plain Select
110
+ with the same nested-drill-down popover Filter's field picker already has — reuses Select's own already-measured trigger chrome
111
+ verbatim (its class is scoped under Select's own root class, so it can't be reused as a bare class name outside an actual Select tree;
112
+ these values are copied, not re-measured). */
113
+ & .crisp-tabletoolbar-sort-field-picker { position: relative; display: inline-flex; flex: 1; min-width: 0; }
114
+ & .crisp-tabletoolbar-sort-field-trigger { box-sizing: border-box; display: inline-flex; align-items: center; gap: var(--crisp-space-1-5); height: var(--crisp-size-menuRow); width: 100%; min-width: 0; margin: 0; padding: 0 var(--crisp-space-1-5) 0 var(--crisp-space-2); border: none; border-radius: var(--crisp-radius-md); background: transparent; box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--crisp-fg-primary) 7%, transparent); font-family: inherit; font-size: var(--crisp-text-lg); color: var(--crisp-fg-primary); cursor: pointer; transition: background-color .12s ease, box-shadow .12s ease; }
115
+ & .crisp-tabletoolbar-sort-field-trigger:hover { background: var(--crisp-bg-raised); }
116
+ & .crisp-tabletoolbar-sort-field-trigger[aria-expanded="true"],
117
+ & .crisp-tabletoolbar-sort-field-trigger:focus-visible { outline: none; box-shadow: inset 0 0 0 1px var(--crisp-bg-brand-solid); background: var(--crisp-bg-surface); }
118
+ & .crisp-tabletoolbar-sort-field-label { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; text-align: start; }
119
+ & .crisp-tabletoolbar-sort-field-picker .crisp-tabletoolbar-menu { min-width: 200px; }
82
120
  & .crisp-tabletoolbar-menu--val { min-width: 134px; }
83
121
  & .crisp-tabletoolbar-datepop { width: 300px; }
84
122
  & .crisp-tabletoolbar-datepop-presets { display: flex; flex-wrap: wrap; gap: var(--crisp-space-1); margin-top: var(--crisp-space-2); padding: 0 var(--crisp-space-1); }
85
123
  & .crisp-tabletoolbar-datepop-preset { height: var(--crisp-size-chip); padding: 0 var(--crisp-space-2); border: none; border-radius: var(--crisp-radius-chip); background: var(--crisp-bg-raised); color: var(--crisp-fg-primary); font-family: inherit; font-size: var(--crisp-text-sm); cursor: pointer; }
86
124
  & .crisp-tabletoolbar-datepop-preset:hover { background: color-mix(in srgb, var(--crisp-fg-primary) 8%, transparent); }
125
+
126
+ & .crisp-tabletoolbar-tool--raised { border: none; background: var(--crisp-bg-surface-muted); 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; cursor: pointer; }
127
+ [dir="rtl"] & [data-rtl-flip] { transform: scaleX(-1); }
87
128
  }
88
129
 
89
130
  @media (prefers-reduced-motion: reduce) {
package/dist/text.css CHANGED
@@ -1,5 +1,5 @@
1
1
  .crisp-text {
2
- display: inline;
2
+ vertical-align: baseline;
3
3
  }
4
4
  .crisp-text--tone_default {
5
5
  color: var(--crisp-fg-primary);
@@ -1,6 +1,6 @@
1
1
  @layer crisp-components {
2
2
  .crisp-text {
3
- display: inline;
3
+ vertical-align: baseline;
4
4
  }
5
5
  .crisp-text--tone_default {
6
6
  color: var(--crisp-fg-primary);
@@ -27,13 +27,20 @@ export type ContextMenuProps = {
27
27
  * element, same as any other `asChild` in crisp (`core/primitive.tsx`).
28
28
  */
29
29
  asChild?: boolean;
30
+ /** `"light"` (default) is this component's own original popover surface.
31
+ * `"dark"` is Attio's OWN cell-level context menu (`DataTable`'s #598) —
32
+ * measured live as a fixed, theme-INDEPENDENT dark chrome (background
33
+ * `#101112`, text `#eeeff1`), the same "dark pill" ink pair `Tooltip`
34
+ * already hardcodes for the same reason: this surface doesn't follow the
35
+ * app's light/dark theme, Attio always renders it dark. */
36
+ variant?: "light" | "dark";
30
37
  };
31
38
  /**
32
39
  * A right-click context menu — the popover surface positioned at the cursor.
33
40
  * Wrap the target; right-clicking it opens the menu (flipping at the viewport
34
41
  * edges), closing on select, outside pointer-down, and Escape.
35
42
  */
36
- export declare function ContextMenu({ children, items, menuLabel: menuLabelProp, className, asChild, }: ContextMenuProps): React.JSX.Element;
43
+ export declare function ContextMenu({ children, items, menuLabel: menuLabelProp, className, asChild, variant, }: ContextMenuProps): React.JSX.Element;
37
44
  export declare namespace ContextMenu {
38
45
  var displayName: string;
39
46
  }
@@ -1,4 +1,5 @@
1
1
  import * as React from "react";
2
+ import { type CalcOp } from "../columncalc";
2
3
  type IconType = React.ComponentType<{
3
4
  size?: number;
4
5
  strokeWidth?: number;
@@ -7,6 +8,7 @@ export declare function isLabelTruncated(el: {
7
8
  scrollWidth: number;
8
9
  clientWidth: number;
9
10
  }): boolean;
11
+ export declare function reorderColumns<T>(cols: DataTableColumn<T>[], order: string[] | undefined): DataTableColumn<T>[];
10
12
  export type EntityValue = {
11
13
  name: string;
12
14
  avatar?: string;
@@ -140,6 +142,24 @@ export type DataTableColumn<T> = {
140
142
  format?: (value: unknown, row: T) => string;
141
143
  /** Footer summary for this column (shown when any column supplies one). */
142
144
  summary?: (rows: T[]) => React.ReactNode;
145
+ /**
146
+ * Give this column an aggregation footer via `ColumnCalc`, matching
147
+ * Attio's own per-type set exactly (crisp/#602, checklist G-073;
148
+ * confirmed live against `app.attio.com`'s "Add calculation" menu on
149
+ * both a text and a numeric column): Count empty / Count filled /
150
+ * Percent empty / Percent filled on every column, plus Sum / Average /
151
+ * Min / Max on `"number"`/`"currency"` columns, plus an explicit "None"
152
+ * once one is active. RecordsTable's own OLDER, separate calc footer
153
+ * predates `ColumnCalc` and gives a flat, per-type-WRONG set (no
154
+ * Percent empty/filled at all, "Count all" instead of "Count empty" +
155
+ * "Count filled" as two ops) — that duplicate implementation is not
156
+ * touched here; this is DataTable's own footer only. Wins over
157
+ * `summary` when both are set on the same column. */
158
+ calc?: {
159
+ defaultOp?: CalcOp;
160
+ op?: CalcOp;
161
+ onOpChange?: (op: CalcOp) => void;
162
+ };
143
163
  /** Opt this column out of the header menu's Hide/Move/Rename items. */
144
164
  managed?: boolean;
145
165
  /** Hidden from the grid but still listed in the column manager. */
@@ -151,8 +171,21 @@ export type DataTableColumn<T> = {
151
171
  * icon set); this uses a Lucide `Lock`, the same substitution every
152
172
  * other crisp icon makes. Purely a visual marker — it does NOT disable
153
173
  * editing on its own; a column that shouldn't be editable simply
154
- * doesn't set `editable: true`, the same as any other column. */
174
+ * doesn't set `editable: true`, the same as any other column.
175
+ * Also used by the cell context menu (crisp/#598, #645): matching
176
+ * Attio's own "no copy handler at all" for a computed field (checklist
177
+ * G-100), a `readOnly` cell's Copy item is disabled and its keyboard/
178
+ * native-copy path writes nothing to the clipboard, and its Clear-value
179
+ * item is disabled (there is nothing editable to clear). */
155
180
  readOnly?: boolean;
181
+ /** Marks the column's values as AI-enriched (crisp/#758) — every cell
182
+ * gets a full-bleed purple wash, composited OVER whatever row-state
183
+ * background the cell already has (hover, selected), which is why it's
184
+ * an overlay rather than a plain `background`. Ported from
185
+ * `RecordsTable`'s own already-measured value (`#9b69ff` at 4%, same as
186
+ * `bgPurple`) — no new measurement needed, `RecordsTable`'s own
187
+ * `Column.enriched` predates this. */
188
+ enriched?: boolean;
156
189
  /** `type: "entity"` only (#501) — makes the cell's name its own click
157
190
  * target, separate from the row's own `onRowClick`: the name renders as a
158
191
  * `stopPropagation`-guarded button instead of a plain span, the same
@@ -163,6 +196,30 @@ export type DataTableColumn<T> = {
163
196
  * row's own record — that's what `onRowClick` is for. Ignored on any
164
197
  * other `type`. */
165
198
  onEntityClick?: (row: T) => void;
199
+ /** `type: "entity"` only (crisp/#601, checklist G-048) — a right-aligned
200
+ * "Open preview" icon that appears on row hover (or keyboard focus),
201
+ * alongside `onOpenComments` below. Matches Attio's own measured pair
202
+ * exactly: a 20px ghost button, `radius.sm`, muted at rest, darkening on
203
+ * its own hover. Omit to render neither icon (default). Ignored on any
204
+ * other `type`, same as `onEntityClick`. */
205
+ onOpenPreview?: (row: T) => void;
206
+ /** `type: "entity"` only (crisp/#601, checklist G-048) — the OTHER
207
+ * hover icon next to `onOpenPreview`. Measured live: its button carries
208
+ * `aria-controls="comment-drawer"`, not the "possibly drag-to-reorder"
209
+ * the issue's own first-pass guess landed on — it opens a comment
210
+ * drawer for the row. Once set, renders via `CommentCountBadge`
211
+ * (product/comments, crisp/#625) rather than a plain icon — a persistent
212
+ * dot once the row has ≥1 comment, replaced on hover by the count (see
213
+ * `commentCount`); `onOpenPreview` above rides along as that badge's own
214
+ * second icon instead of rendering separately. Setting `onOpenPreview`
215
+ * with NO `onOpenComments` keeps the old plain-icon-only rendering (a
216
+ * column with a preview affordance but no comments concept at all). */
217
+ onOpenComments?: (row: T) => void;
218
+ /** The row's own comment count, read by `CommentCountBadge` when
219
+ * `onOpenComments` is set. Omit to always pass `0` — the badge still
220
+ * renders (a plain icon, no dot, no digit) once `onOpenComments` is set;
221
+ * it just can't show a real count without this. */
222
+ commentCount?: (row: T) => number;
166
223
  };
167
224
  export type SortState = {
168
225
  key: string;
@@ -227,8 +284,35 @@ export type DataTableProps<T extends {
227
284
  viewSettingsLabel?: string;
228
285
  /** That menu's own "Columns" section heading. */
229
286
  columnsGroupLabel?: string;
287
+ /** Accessible name for the cell context menu (crisp/#598) and the hidden
288
+ * clipboard-proxy textarea its Copy/Cmd+C share (crisp/#645). */
289
+ cellMenuLabel?: string;
290
+ /** The cell context menu's four items, matching Attio's own exactly
291
+ * (checklist G-043 — "identical menu regardless of field type"). */
292
+ copyLabel?: string;
293
+ pasteLabel?: string;
294
+ viewEditHistoryLabel?: string;
295
+ /** "Clear value" commits an empty string via the existing `onEdit`, same
296
+ * as clearing a plain text editor by hand -- no separate callback. */
297
+ clearValueLabel?: string;
230
298
  /** Called with the next column list after a move, rename or hide. */
231
299
  onColumnsChange?: (columns: DataTableColumn<T>[]) => void;
300
+ /**
301
+ * Controlled column order — a plain array of `key`s (crisp/#731), lighter
302
+ * than `onColumnsChange`'s full column definitions (which can't round-trip
303
+ * through JSON: `render`/`accessor`/etc. are functions) and so the shape a
304
+ * saved view actually persists. Omit for an uncontrolled default seeded
305
+ * from `columns`' own order and updated internally by a header drag or the
306
+ * column menu's Move left/right. A key this doesn't mention (a column
307
+ * added since the order was last saved) renders after every key it DOES
308
+ * mention, in `columns`' own relative order — see `reorderColumns`. Column
309
+ * drag-to-reorder itself is always available (like resize); this prop only
310
+ * changes who owns the resulting order.
311
+ */
312
+ columnOrder?: string[];
313
+ /** Called with the next `columnOrder` after a header drag or the column
314
+ * menu's Move left/right — whichever changed the order. */
315
+ onColumnOrderChange?: (order: string[]) => void;
232
316
  /** Group rows under a heading row, the way a grouped view does. */
233
317
  groupBy?: (row: T) => string;
234
318
  /**
@@ -241,6 +325,11 @@ export type DataTableProps<T extends {
241
325
  onRowsReorder?: (rows: T[]) => void;
242
326
  /** aria-label on each row's drag grip (default "Reorder row"). */
243
327
  reorderRowLabel?: string;
328
+ /** aria-label on the entity column's "Open preview" hover icon (crisp/#601;
329
+ * only rendered when a column sets `onOpenPreview`). Also passed through
330
+ * to `CommentCountBadge`'s own `previewLabel` when a column sets
331
+ * `onOpenComments` too (crisp/#601 supersession). */
332
+ openPreviewLabel?: string;
244
333
  /**
245
334
  * Render an error state in place of the rows. Uses the same measured
246
335
  * `EmptyState variant="page"` surface as the zero-row state.
@@ -265,6 +354,13 @@ export type DataTableProps<T extends {
265
354
  * outside `render`'s own output so a hover-only wrapper (a `Tooltip`, say)
266
355
  * can't swallow it. */
267
356
  onEdit?: (id: string, key: string, value: string) => Promise<void> | void;
357
+ /** The cell context menu's "View edit history" item (crisp/#598, checklist
358
+ * G-043 -- Attio's own item carries a "Pro" pill crisp has no billing
359
+ * concept to gate on, so the item itself always renders; see the
360
+ * component's own comment). Omitted: the item still renders (matching
361
+ * Attio, which shows it regardless of field type) but disabled, since
362
+ * there is nothing for it to open. */
363
+ onViewEditHistory?: (id: string, key: string) => void;
268
364
  /** Sticky the first column(s) with a horizontal scroll shadow (default true). */
269
365
  stickyFirst?: boolean;
270
366
  /** Sticky the LAST column with a horizontal scroll shadow, mirroring
@@ -337,7 +433,7 @@ export type DataTableProps<T extends {
337
433
  };
338
434
  export declare function DataTable<T extends {
339
435
  id: string;
340
- }>({ columns: columnsProp, data, selectable, selectedIds, onSelectionChange, selectAllLabel: selectAllLabelProp, selectRowLabel, selectionAnnouncement, columnMenu, onColumnsChange, groupBy, onRowsReorder, error, sort: sortProp, defaultSort, onSortChange, filterable, searchPlaceholder: searchPlaceholderProp, searchLabel: searchLabelProp, onEdit, stickyFirst, stickyLast, scrollProps, locale: localeProp, avatarTone, surface, interactive, onRowClick, onAddRow, addRowLabel: addRowLabelProp, empty: emptyProp, showCount, countLabel, toolbar, height, className, reorderRowLabel: reorderRowLabelProp, sortAscendingLabel: sortAscendingLabelProp, sortDescendingLabel: sortDescendingLabelProp, moveColumnLeftLabel: moveColumnLeftLabelProp, moveColumnRightLabel: moveColumnRightLabelProp, editColumnLabel: editColumnLabelProp, hideColumnLabel: hideColumnLabelProp, renameColumnLabel: renameColumnLabelProp, viewSettingsLabel: viewSettingsLabelProp, columnsGroupLabel: columnsGroupLabelProp, ...rest }: DataTableProps<T>): React.JSX.Element;
436
+ }>({ columns: columnsProp, data, selectable, selectedIds, onSelectionChange, selectAllLabel: selectAllLabelProp, selectRowLabel, selectionAnnouncement, columnMenu, onColumnsChange, columnOrder, onColumnOrderChange, groupBy, onRowsReorder, error, sort: sortProp, defaultSort, onSortChange, filterable, searchPlaceholder: searchPlaceholderProp, searchLabel: searchLabelProp, onEdit, onViewEditHistory, stickyFirst, stickyLast, scrollProps, locale: localeProp, avatarTone, surface, interactive, onRowClick, onAddRow, addRowLabel: addRowLabelProp, empty: emptyProp, showCount, countLabel, toolbar, height, className, reorderRowLabel: reorderRowLabelProp, openPreviewLabel: openPreviewLabelProp, sortAscendingLabel: sortAscendingLabelProp, sortDescendingLabel: sortDescendingLabelProp, moveColumnLeftLabel: moveColumnLeftLabelProp, moveColumnRightLabel: moveColumnRightLabelProp, editColumnLabel: editColumnLabelProp, hideColumnLabel: hideColumnLabelProp, renameColumnLabel: renameColumnLabelProp, viewSettingsLabel: viewSettingsLabelProp, columnsGroupLabel: columnsGroupLabelProp, cellMenuLabel: cellMenuLabelProp, copyLabel: copyLabelProp, pasteLabel: pasteLabelProp, viewEditHistoryLabel: viewEditHistoryLabelProp, clearValueLabel: clearValueLabelProp, ...rest }: DataTableProps<T>): React.JSX.Element;
341
437
  export declare namespace DataTable {
342
438
  var displayName: string;
343
439
  }
@@ -48,8 +48,39 @@ export type TableToolbarColumn = {
48
48
  * `is`/`is not`/`before`/`after`/`empty`/`not empty` with a calendar +
49
49
  * Today/Tomorrow/Next week quick-picks — both sets, and the number
50
50
  * spinner, measured live against Attio's own column-typed filter menus.
51
+ * `"select"` (#715) swaps in `is`/`is not`/`empty`/`not empty` — a
52
+ * closed-vocabulary column's text-search operators (`contains`/`starts
53
+ * with`/…) mean nothing against a fixed value set. Pair it with `values`
54
+ * to also offer `"is any of"`, the same as every other type.
51
55
  */
52
- type?: "text" | "number" | "date";
56
+ type?: "text" | "number" | "date" | "select";
57
+ /**
58
+ * This column's own related object's attributes, reachable as a
59
+ * "Parent › Child" path in the Filter field picker (#592) — a
60
+ * relationship field (Attio's own "Team 26", a company's linked people,
61
+ * e.g.) expands into a nested picker over these instead of only
62
+ * offering the relationship field itself. Recurses: a nested column can
63
+ * carry its own `nestedColumns` too (Team → Company → …, confirmed
64
+ * reachable live) with no depth limit built in here. The relationship
65
+ * field itself stays independently selectable (its own operators, e.g.
66
+ * `contains`/`does not contain`/`empty`/`not empty` for a person
67
+ * relationship, measured live) — drilling in doesn't remove that option,
68
+ * it adds the nested one alongside it, matching Attio's own picker
69
+ * exactly (its nested screen lists the relationship field itself first,
70
+ * checked when it's the chip's current field, then its children).
71
+ */
72
+ nestedColumns?: TableToolbarColumn[];
73
+ /**
74
+ * The related object's own singular noun, used once a consumer drills
75
+ * into this column: the nested picker's placeholder reads `Search
76
+ * ${nestedObjectLabel} attributes…` — measured live, e.g. "person" for a
77
+ * Team/People relationship, "company" for a Company relationship. Only
78
+ * meaningful alongside `nestedColumns`. Falls back to this column's own
79
+ * `label`, lowercased, when omitted — a plain fallback, since Attio's
80
+ * own noun is the *related object's* name, not usually the field's own
81
+ * label lowercased ("Team" the field vs. "person" the related object).
82
+ */
83
+ nestedObjectLabel?: string;
53
84
  };
54
85
  export type TableToolbarProps = Omit<React.ComponentPropsWithoutRef<"div">, "children"> & {
55
86
  /**
@@ -192,6 +223,45 @@ export type TableToolbarProps = Omit<React.ComponentPropsWithoutRef<"div">, "chi
192
223
  addAttributeToViewLabel?: string;
193
224
  /** Placeholder in the add-attribute search box (default "Search attributes…"). */
194
225
  searchAttributesPlaceholder?: string;
226
+ /**
227
+ * Text on the Import/Export trigger (default "Import / Export"). The
228
+ * trigger only renders when at least one of `onImportCsv`/`onExportCsv`/
229
+ * `onExportExcel`/`importExportHelpHref` is given — an unwired menu has
230
+ * nothing to do, the same "still renders empty" contract other triggers
231
+ * here make doesn't fit a menu that would just be blank.
232
+ */
233
+ importExportLabel?: string;
234
+ /** The menu's "Import CSV" item — omit to leave it out. */
235
+ onImportCsv?: () => void;
236
+ /** The menu's "Export view as CSV" item — omit to leave it out. */
237
+ onExportCsv?: () => void;
238
+ /** The menu's "Export view as Excel" item — omit to leave it out. */
239
+ onExportExcel?: () => void;
240
+ /** Shows a "Learn about imports and exports" link in the menu when set —
241
+ * same pattern as Sort's own `helpHref`. */
242
+ importExportHelpHref?: string;
243
+ /** Label on the "Import CSV" item (default "Import CSV"). */
244
+ importCsvLabel?: string;
245
+ /** Label on the "Export view as CSV" item (default "Export view as CSV"). */
246
+ exportCsvLabel?: string;
247
+ /** Label on the "Export view as Excel" item (default "Export view as Excel"). */
248
+ exportExcelLabel?: string;
249
+ /** Text of the Import/Export menu's help link (default "Learn about
250
+ * imports and exports"). */
251
+ learnImportExportLabel?: string;
252
+ /** aria-label on the Filter field picker's back control, shown once
253
+ * drilled into a `nestedColumns` column (default "Back"). */
254
+ backLabel?: string;
255
+ /**
256
+ * Placeholder in the field picker's own search box once drilled into a
257
+ * `nestedColumns` column — receives that column's `nestedObjectLabel`
258
+ * (or its `label`, lowercased). Defaults to `Search ${noun} attributes…`,
259
+ * matching Attio's own measured wording ("Search person attributes…",
260
+ * drilled into a Team/People relationship) — a formatter, like
261
+ * `sortTriggerLabel` above, since the noun sits mid-sentence and a fixed
262
+ * template doesn't survive every language's word order.
263
+ */
264
+ nestedFieldPlaceholder?: (noun: string) => string;
195
265
  };
196
266
  /**
197
267
  * Applies a toolbar's filter conditions to any row array. Reads the raw value
@@ -218,7 +288,7 @@ export declare function applySorts<T>(rows: T[], sorts: ToolbarSort[], get?: (ro
218
288
  * this (Sort: Due date/Assignee/Creation date; Filter: Assignee/Record/
219
289
  * Created by), which one shared list can't reproduce.
220
290
  */
221
- export declare function TableToolbar({ columns, sortColumns: sortColumnsProp, filterColumns: filterColumnsProp, sorts: sortsProp, defaultSorts, onSortsChange, filters: filtersProp, defaultFilters, onFiltersChange, count, children, helpHref, className, sortLabel: sortLabelProp, filterLabel: filterLabelProp, reorderSortLabel: reorderSortLabelProp, noSortsLabel: noSortsLabelProp, addSortLabel: addSortLabelProp, learnSortingLabel: learnSortingLabelProp, selectConditionLabel: selectConditionLabelProp, sortFieldLabel: sortFieldLabelProp, sortDirectionLabel: sortDirectionLabelProp, removeSortLabel: removeSortLabelProp, filterFieldLabel: filterFieldLabelProp, filterFieldPlaceholder: filterFieldPlaceholderProp, filterValuePlaceholder: filterValuePlaceholderProp, filterOptionsLabel: filterOptionsLabelProp, deleteFilterLabel: deleteFilterLabelProp, ascendingLabel: ascendingLabelProp, descendingLabel: descendingLabelProp, sortTriggerLabel, filterTriggerLabel, inline, showSort, showViewSettings, viewColumns: viewColumnsProp, shownKeys: shownKeysProp, defaultShownKeys, onShownKeysChange, viewSettingsLabel: viewSettingsLabelProp, reorderAttributeLabel: reorderAttributeLabelProp, hideAttributeLabel, addAttributeToViewLabel: addAttributeToViewLabelProp, searchAttributesPlaceholder: searchAttributesPlaceholderProp, ...rest }: TableToolbarProps): React.JSX.Element;
291
+ export declare function TableToolbar({ columns, sortColumns: sortColumnsProp, filterColumns: filterColumnsProp, sorts: sortsProp, defaultSorts, onSortsChange, filters: filtersProp, defaultFilters, onFiltersChange, count, children, helpHref, className, sortLabel: sortLabelProp, filterLabel: filterLabelProp, reorderSortLabel: reorderSortLabelProp, noSortsLabel: noSortsLabelProp, addSortLabel: addSortLabelProp, learnSortingLabel: learnSortingLabelProp, selectConditionLabel: selectConditionLabelProp, sortFieldLabel: sortFieldLabelProp, sortDirectionLabel: sortDirectionLabelProp, removeSortLabel: removeSortLabelProp, filterFieldLabel: filterFieldLabelProp, filterFieldPlaceholder: filterFieldPlaceholderProp, filterValuePlaceholder: filterValuePlaceholderProp, filterOptionsLabel: filterOptionsLabelProp, deleteFilterLabel: deleteFilterLabelProp, ascendingLabel: ascendingLabelProp, descendingLabel: descendingLabelProp, sortTriggerLabel, filterTriggerLabel, inline, showSort, showViewSettings, viewColumns: viewColumnsProp, shownKeys: shownKeysProp, defaultShownKeys, onShownKeysChange, viewSettingsLabel: viewSettingsLabelProp, reorderAttributeLabel: reorderAttributeLabelProp, hideAttributeLabel, addAttributeToViewLabel: addAttributeToViewLabelProp, searchAttributesPlaceholder: searchAttributesPlaceholderProp, importExportLabel: importExportLabelProp, onImportCsv, onExportCsv, onExportExcel, importExportHelpHref, importCsvLabel: importCsvLabelProp, exportCsvLabel: exportCsvLabelProp, exportExcelLabel: exportExcelLabelProp, learnImportExportLabel: learnImportExportLabelProp, backLabel: backLabelProp, nestedFieldPlaceholder, ...rest }: TableToolbarProps): React.JSX.Element;
222
292
  export type ToolbarToolProps = React.ComponentPropsWithoutRef<"button"> & {
223
293
  /** Drawn the way Sort/Filter are once a condition is set (#399). */
224
294
  active?: boolean;
@@ -1,7 +1,7 @@
1
1
  declare const spec: {
2
2
  readonly name: "text";
3
3
  readonly base: {
4
- readonly display: "inline";
4
+ readonly verticalAlign: "baseline";
5
5
  };
6
6
  readonly variants: {
7
7
  tone: {
@@ -1,4 +1,9 @@
1
1
  import * as React from "react";
2
+ /** crisp/#718 — Attio's own two independent density toggles: "collapse"
3
+ * (columns narrow to a 52px vertical rail; cards shrink to a 40px row) or
4
+ * "expand" (the normal width/height), measured live on the Deals
5
+ * pipeline. */
6
+ export type BoardDensity = "expand" | "collapse";
2
7
  export type BoardColumn = {
3
8
  id: string;
4
9
  title: string;
@@ -24,10 +29,51 @@ export type BoardProps<T extends BoardCard> = Omit<React.ComponentPropsWithoutRe
24
29
  cards: T[];
25
30
  /** Called with the next cards after a drag moves one between columns. */
26
31
  onCardsChange?: (cards: T[]) => void;
27
- renderCard: (card: T) => React.ReactNode;
32
+ /**
33
+ * The second argument is the board's CURRENT card density (crisp/#718) —
34
+ * `renderCard` decides what a collapsed (40px row) card actually shows,
35
+ * since Board only knows the card is a `T`, never its display shape.
36
+ * Ignoring the argument (a plain `(card) => ...`) keeps every existing
37
+ * call site valid; `cardDensity` then just resolves to `"expand"` always,
38
+ * exactly as before this prop existed.
39
+ */
40
+ renderCard: (card: T, cardDensity: BoardDensity) => React.ReactNode;
28
41
  /** When set, each column shows a "+ {newCardLabel}" button and its header +. */
29
42
  onNewCard?: (columnId: string) => void;
30
43
  newCardLabel?: string;
44
+ /**
45
+ * Shows the density toolbar above the board — Attio's own two
46
+ * independent "Collapse/Expand columns" and "Collapse/Expand cards"
47
+ * segmented toggles (crisp/#718, measured live). Default `false`: an
48
+ * existing `Board` renders no toolbar and no extra chrome, exactly as
49
+ * before this prop existed.
50
+ */
51
+ density?: boolean;
52
+ /** Controlled column density (omit for uncontrolled, seeded "expand"). */
53
+ columnDensity?: BoardDensity;
54
+ onColumnDensityChange?: (density: BoardDensity) => void;
55
+ /** Controlled card density (omit for uncontrolled, seeded "expand") —
56
+ * also the value `renderCard`'s second argument resolves to. */
57
+ cardDensity?: BoardDensity;
58
+ onCardDensityChange?: (density: BoardDensity) => void;
59
+ collapseColumnsLabel?: string;
60
+ expandColumnsLabel?: string;
61
+ collapseCardsLabel?: string;
62
+ expandCardsLabel?: string;
63
+ /** aria-label for the column-density toggle's own `role="radiogroup"`
64
+ * wrapper (Attio's own markup uses a plain `role="group"` with no name —
65
+ * crisp names both groups, the more correct ARIA shape for a set of
66
+ * mutually-exclusive `role="radio"` buttons). */
67
+ columnDensityGroupLabel?: string;
68
+ cardDensityGroupLabel?: string;
69
+ /**
70
+ * Renders a trailing 52×52px dashed "+" slot after the last column —
71
+ * Attio's own "Add stage" (crisp/#718, measured live). Omit to skip it
72
+ * entirely, matching every other opt-in affordance here (`onNewCard`,
73
+ * `calc`).
74
+ */
75
+ onAddStage?: () => void;
76
+ addStageLabel?: string;
31
77
  /**
32
78
  * Accessible name for a card. Also used in the move announcement. Without it
33
79
  * a card is announced by whatever `renderCard` puts in it, which is usually
@@ -54,7 +100,7 @@ export type BoardProps<T extends BoardCard> = Omit<React.ComponentPropsWithoutRe
54
100
  * it via `onCardsChange` (the parent owns the cards, so the move is a
55
101
  * controlled state update). Measured from a deals pipeline.
56
102
  */
57
- export declare function Board<T extends BoardCard>({ columns, cards, onCardsChange, renderCard, onNewCard, newCardLabel: newCardLabelProp, getCardLabel, announceMove: announceMoveProp, calc, className, ...rest }: BoardProps<T>): React.JSX.Element;
103
+ export declare function Board<T extends BoardCard>({ columns, cards, onCardsChange, renderCard, onNewCard, newCardLabel: newCardLabelProp, getCardLabel, announceMove: announceMoveProp, calc, density, columnDensity: columnDensityProp, onColumnDensityChange, cardDensity: cardDensityProp, onCardDensityChange, collapseColumnsLabel: collapseColumnsLabelProp, expandColumnsLabel: expandColumnsLabelProp, collapseCardsLabel: collapseCardsLabelProp, expandCardsLabel: expandCardsLabelProp, columnDensityGroupLabel: columnDensityGroupLabelProp, cardDensityGroupLabel: cardDensityGroupLabelProp, onAddStage, addStageLabel: addStageLabelProp, className, ...rest }: BoardProps<T>): React.JSX.Element;
58
104
  export declare namespace Board {
59
105
  var displayName: string;
60
106
  }
@@ -120,7 +120,12 @@ export type CommentsPanelProps = Omit<React.ComponentPropsWithoutRef<"div">, "ch
120
120
  emojiOptions?: string[];
121
121
  mentionTrigger?: string;
122
122
  renderMentionPicker?: (state: CommentMentionTriggerState) => React.ReactNode;
123
- onClose: () => void;
123
+ /** Omit for a consumer with no separate open/close state of its own —
124
+ * embedded in a tab pane rather than a true slide-over/drawer, e.g.
125
+ * (crisp#738, the same reasoning #721 already applied to
126
+ * `onReact`/`onDelete`) — the header's close "✕" drops out rather than
127
+ * sitting there as a button that visibly does nothing. */
128
+ onClose?: () => void;
124
129
  closeLabel?: string;
125
130
  backLabel?: string;
126
131
  title?: string;
@@ -228,7 +228,6 @@ export type RecordsTableLabels = {
228
228
  selectionAnnouncement: (count: number) => string;
229
229
  delete: string;
230
230
  sendEmail: string;
231
- exportCsv: string;
232
231
  runWorkflow: string;
233
232
  calculate: string;
234
233
  emptyPlaceholder: string;