@crisp-ui-kit/crisp 0.54.8 → 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 (54) 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 +541 -51
  6. package/dist/cjs/components/datatable/datatable.recipe.js +61 -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 +148 -15
  10. package/dist/cjs/components/tabletoolbar/tabletoolbar.recipe.js +35 -1
  11. package/dist/cjs/product/board/board.js +62 -28
  12. package/dist/cjs/product/board/board.recipe.js +42 -2
  13. package/dist/cjs/product/comments/comments.js +11 -2
  14. package/dist/cjs/product/comments/comments.recipe.js +14 -4
  15. package/dist/cjs/product/recordstable/recordstable.js +1 -17
  16. package/dist/cjs/product/savedviews/savedviews.js +55 -24
  17. package/dist/cjs/product/savedviews/savedviews.recipe.js +27 -0
  18. package/dist/comments.css +14 -4
  19. package/dist/comments.layered.css +14 -4
  20. package/dist/contextmenu.css +24 -0
  21. package/dist/contextmenu.layered.css +24 -0
  22. package/dist/datatable.css +103 -77
  23. package/dist/datatable.layered.css +103 -77
  24. package/dist/esm/components/contextmenu/contextmenu.js +2 -2
  25. package/dist/esm/components/contextmenu/contextmenu.recipe.js +24 -0
  26. package/dist/esm/components/datatable/datatable.js +541 -52
  27. package/dist/esm/components/datatable/datatable.recipe.js +62 -4
  28. package/dist/esm/components/dialog/dialog.js +65 -1
  29. package/dist/esm/components/tabletoolbar/tabletoolbar.chrome.js +37 -2
  30. package/dist/esm/components/tabletoolbar/tabletoolbar.js +149 -16
  31. package/dist/esm/components/tabletoolbar/tabletoolbar.recipe.js +37 -3
  32. package/dist/esm/product/board/board.js +63 -29
  33. package/dist/esm/product/board/board.recipe.js +42 -2
  34. package/dist/esm/product/comments/comments.js +12 -3
  35. package/dist/esm/product/comments/comments.recipe.js +14 -4
  36. package/dist/esm/product/recordstable/recordstable.js +2 -18
  37. package/dist/esm/product/savedviews/savedviews.js +57 -26
  38. package/dist/esm/product/savedviews/savedviews.recipe.js +27 -0
  39. package/dist/recordstable.css +33 -31
  40. package/dist/recordstable.layered.css +33 -31
  41. package/dist/savedviews.css +27 -0
  42. package/dist/savedviews.layered.css +27 -0
  43. package/dist/styles.css +266 -124
  44. package/dist/styles.layered.css +266 -124
  45. package/dist/tabletoolbar.css +33 -4
  46. package/dist/tabletoolbar.layered.css +33 -4
  47. package/dist/types/components/contextmenu/contextmenu.d.ts +8 -1
  48. package/dist/types/components/datatable/datatable.d.ts +90 -2
  49. package/dist/types/components/tabletoolbar/tabletoolbar.d.ts +72 -2
  50. package/dist/types/product/board/board.d.ts +48 -2
  51. package/dist/types/product/comments/comments.d.ts +6 -1
  52. package/dist/types/product/recordstable/recordstable.d.ts +0 -1
  53. package/dist/types/product/savedviews/savedviews.d.ts +45 -8
  54. 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,12 +86,21 @@
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); }
@@ -83,6 +110,8 @@
83
110
  & .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
111
  & .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
112
  & .crisp-tabletoolbar-datepop-preset:hover { background: color-mix(in srgb, var(--crisp-fg-primary) 8%, transparent); }
113
+ & .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; }
114
+ [dir="rtl"] & [data-rtl-flip] { transform: scaleX(-1); }
86
115
  }
87
116
 
88
117
  @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,12 +87,21 @@
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); }
@@ -84,6 +111,8 @@
84
111
  & .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
112
  & .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
113
  & .crisp-tabletoolbar-datepop-preset:hover { background: color-mix(in srgb, var(--crisp-fg-primary) 8%, transparent); }
114
+ & .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; }
115
+ [dir="rtl"] & [data-rtl-flip] { transform: scaleX(-1); }
87
116
  }
88
117
 
89
118
  @media (prefers-reduced-motion: reduce) {
@@ -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,7 +171,12 @@ 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;
156
181
  /** `type: "entity"` only (#501) — makes the cell's name its own click
157
182
  * target, separate from the row's own `onRowClick`: the name renders as a
@@ -163,6 +188,30 @@ export type DataTableColumn<T> = {
163
188
  * row's own record — that's what `onRowClick` is for. Ignored on any
164
189
  * other `type`. */
165
190
  onEntityClick?: (row: T) => void;
191
+ /** `type: "entity"` only (crisp/#601, checklist G-048) — a right-aligned
192
+ * "Open preview" icon that appears on row hover (or keyboard focus),
193
+ * alongside `onOpenComments` below. Matches Attio's own measured pair
194
+ * exactly: a 20px ghost button, `radius.sm`, muted at rest, darkening on
195
+ * its own hover. Omit to render neither icon (default). Ignored on any
196
+ * other `type`, same as `onEntityClick`. */
197
+ onOpenPreview?: (row: T) => void;
198
+ /** `type: "entity"` only (crisp/#601, checklist G-048) — the OTHER
199
+ * hover icon next to `onOpenPreview`. Measured live: its button carries
200
+ * `aria-controls="comment-drawer"`, not the "possibly drag-to-reorder"
201
+ * the issue's own first-pass guess landed on — it opens a comment
202
+ * drawer for the row. Once set, renders via `CommentCountBadge`
203
+ * (product/comments, crisp/#625) rather than a plain icon — a persistent
204
+ * dot once the row has ≥1 comment, replaced on hover by the count (see
205
+ * `commentCount`); `onOpenPreview` above rides along as that badge's own
206
+ * second icon instead of rendering separately. Setting `onOpenPreview`
207
+ * with NO `onOpenComments` keeps the old plain-icon-only rendering (a
208
+ * column with a preview affordance but no comments concept at all). */
209
+ onOpenComments?: (row: T) => void;
210
+ /** The row's own comment count, read by `CommentCountBadge` when
211
+ * `onOpenComments` is set. Omit to always pass `0` — the badge still
212
+ * renders (a plain icon, no dot, no digit) once `onOpenComments` is set;
213
+ * it just can't show a real count without this. */
214
+ commentCount?: (row: T) => number;
166
215
  };
167
216
  export type SortState = {
168
217
  key: string;
@@ -227,8 +276,35 @@ export type DataTableProps<T extends {
227
276
  viewSettingsLabel?: string;
228
277
  /** That menu's own "Columns" section heading. */
229
278
  columnsGroupLabel?: string;
279
+ /** Accessible name for the cell context menu (crisp/#598) and the hidden
280
+ * clipboard-proxy textarea its Copy/Cmd+C share (crisp/#645). */
281
+ cellMenuLabel?: string;
282
+ /** The cell context menu's four items, matching Attio's own exactly
283
+ * (checklist G-043 — "identical menu regardless of field type"). */
284
+ copyLabel?: string;
285
+ pasteLabel?: string;
286
+ viewEditHistoryLabel?: string;
287
+ /** "Clear value" commits an empty string via the existing `onEdit`, same
288
+ * as clearing a plain text editor by hand -- no separate callback. */
289
+ clearValueLabel?: string;
230
290
  /** Called with the next column list after a move, rename or hide. */
231
291
  onColumnsChange?: (columns: DataTableColumn<T>[]) => void;
292
+ /**
293
+ * Controlled column order — a plain array of `key`s (crisp/#731), lighter
294
+ * than `onColumnsChange`'s full column definitions (which can't round-trip
295
+ * through JSON: `render`/`accessor`/etc. are functions) and so the shape a
296
+ * saved view actually persists. Omit for an uncontrolled default seeded
297
+ * from `columns`' own order and updated internally by a header drag or the
298
+ * column menu's Move left/right. A key this doesn't mention (a column
299
+ * added since the order was last saved) renders after every key it DOES
300
+ * mention, in `columns`' own relative order — see `reorderColumns`. Column
301
+ * drag-to-reorder itself is always available (like resize); this prop only
302
+ * changes who owns the resulting order.
303
+ */
304
+ columnOrder?: string[];
305
+ /** Called with the next `columnOrder` after a header drag or the column
306
+ * menu's Move left/right — whichever changed the order. */
307
+ onColumnOrderChange?: (order: string[]) => void;
232
308
  /** Group rows under a heading row, the way a grouped view does. */
233
309
  groupBy?: (row: T) => string;
234
310
  /**
@@ -241,6 +317,11 @@ export type DataTableProps<T extends {
241
317
  onRowsReorder?: (rows: T[]) => void;
242
318
  /** aria-label on each row's drag grip (default "Reorder row"). */
243
319
  reorderRowLabel?: string;
320
+ /** aria-label on the entity column's "Open preview" hover icon (crisp/#601;
321
+ * only rendered when a column sets `onOpenPreview`). Also passed through
322
+ * to `CommentCountBadge`'s own `previewLabel` when a column sets
323
+ * `onOpenComments` too (crisp/#601 supersession). */
324
+ openPreviewLabel?: string;
244
325
  /**
245
326
  * Render an error state in place of the rows. Uses the same measured
246
327
  * `EmptyState variant="page"` surface as the zero-row state.
@@ -265,6 +346,13 @@ export type DataTableProps<T extends {
265
346
  * outside `render`'s own output so a hover-only wrapper (a `Tooltip`, say)
266
347
  * can't swallow it. */
267
348
  onEdit?: (id: string, key: string, value: string) => Promise<void> | void;
349
+ /** The cell context menu's "View edit history" item (crisp/#598, checklist
350
+ * G-043 -- Attio's own item carries a "Pro" pill crisp has no billing
351
+ * concept to gate on, so the item itself always renders; see the
352
+ * component's own comment). Omitted: the item still renders (matching
353
+ * Attio, which shows it regardless of field type) but disabled, since
354
+ * there is nothing for it to open. */
355
+ onViewEditHistory?: (id: string, key: string) => void;
268
356
  /** Sticky the first column(s) with a horizontal scroll shadow (default true). */
269
357
  stickyFirst?: boolean;
270
358
  /** Sticky the LAST column with a horizontal scroll shadow, mirroring
@@ -337,7 +425,7 @@ export type DataTableProps<T extends {
337
425
  };
338
426
  export declare function DataTable<T extends {
339
427
  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;
428
+ }>({ 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
429
  export declare namespace DataTable {
342
430
  var displayName: string;
343
431
  }
@@ -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,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;
@@ -6,6 +6,27 @@ export type SavedView = {
6
6
  icon?: React.ReactNode;
7
7
  /** Renders a small shared-view indicator next to the name. */
8
8
  shared?: boolean;
9
+ /**
10
+ * Text matched against the switcher's own search box (#618) when `name`
11
+ * isn't a plain string — same fallback `Select`'s own `keywords` already
12
+ * gives a non-string `label`. Falls back to `id` when `name` is also
13
+ * non-string and this is omitted, the same way `Select`'s own
14
+ * `optionText` falls back to `value` — a view with a rich, non-string
15
+ * name and no `keywords` still matches SOME query (its own id) rather
16
+ * than becoming permanently unfindable by search.
17
+ */
18
+ keywords?: string;
19
+ /**
20
+ * "Created by …" — the first line of the kebab menu's own footer row
21
+ * (#618, measured live: "Created by Attio system" / "26th Aug 2026
22
+ * 12:20 PM", two stacked lines). Renders only the line(s) actually
23
+ * given — omit both for a view with no author/date metadata and the
24
+ * footer (and its divider) don't render at all.
25
+ */
26
+ createdBy?: React.ReactNode;
27
+ /** The footer's second line — a pre-formatted date/time, since crisp
28
+ * doesn't own locale formatting. See `createdBy`. */
29
+ createdAt?: React.ReactNode;
9
30
  };
10
31
  export type SavedViewsProps = Omit<React.ComponentPropsWithoutRef<"div">, "children" | "onSelect"> & {
11
32
  /** The trigger's label when no view is active (Attio's "All Companies"). */
@@ -32,6 +53,18 @@ export type SavedViewsProps = Omit<React.ComponentPropsWithoutRef<"div">, "child
32
53
  viewOptionsLabel?: string;
33
54
  /** aria-label for the panel itself (defaults to the resolved trigger label). */
34
55
  "aria-label"?: string;
56
+ /** Placeholder on the panel's own search box (default "Search views..." —
57
+ * Attio's own text, measured live: 3 literal dots, not an ellipsis
58
+ * glyph). */
59
+ searchPlaceholder?: string;
60
+ /** aria-label on the search box (default "Filter views" — Attio's own
61
+ * measured aria-label, distinct from its placeholder text). */
62
+ searchLabel?: string;
63
+ /** Shown in place of the view list when a search matches nothing (default
64
+ * "No results" — the same text and contract `Select`'s own `searchable`
65
+ * empty state already uses). The "Create new view" row still renders
66
+ * below it either way. */
67
+ emptyLabel?: string;
35
68
  className?: string;
36
69
  };
37
70
  /**
@@ -40,16 +73,20 @@ export type SavedViewsProps = Omit<React.ComponentPropsWithoutRef<"div">, "child
40
73
  * row (leading coloured icon, name, a hover-revealed kebab of
41
74
  * rename/duplicate/delete), plus a trailing "Create new view" row. The
42
75
  * active view is marked `aria-selected`/`aria-current="page"` and mirrored
43
- * onto the trigger's own icon/label.
76
+ * onto the trigger's own icon/label. The panel opens on a "Search views..."
77
+ * box (#618, P-007) — real DOM focus moves there (`requestAnimationFrame`,
78
+ * same race `Select`'s own `searchable` variant already guards against),
79
+ * not virtual focus on the trigger.
44
80
  *
45
- * Keyboard: while the trigger has focus and the panel is open, ↑/↓ move a
46
- * virtual highlight (`aria-activedescendant`, the same "focus stays on the
47
- * trigger" model `Select`/`MultiSelect` use without a search field), Enter
48
- * chooses the highlighted row, Escape closes — deferring to a still-open
49
- * kebab submenu first (`hasOpenFloatingLayer`), so Escape closes one layer
50
- * at a time.
81
+ * Keyboard: while the panel is open, focus lives on the search input — ↑/↓
82
+ * move a virtual highlight (`aria-activedescendant`) over the (possibly
83
+ * filtered) view list, Enter chooses the highlighted row, Escape closes,
84
+ * deferring to a still-open kebab submenu first (`hasOpenFloatingLayer`),
85
+ * so Escape closes one layer at a time — the same split `Select`'s own
86
+ * `searchable` variant already uses between its trigger and its own search
87
+ * input.
51
88
  */
52
- export declare function SavedViews({ label, icon, views, activeId, onSelect, onCreate, onRename, onDuplicate, onDelete, onToggleFavorite, createLabel: createLabelProp, renameLabel: renameLabelProp, duplicateLabel: duplicateLabelProp, deleteLabel: deleteLabelProp, favoriteLabel: favoriteLabelProp, viewOptionsLabel: viewOptionsLabelProp, className, "aria-label": ariaLabel, ...rest }: SavedViewsProps): React.JSX.Element;
89
+ export declare function SavedViews({ label, icon, views, activeId, onSelect, onCreate, onRename, onDuplicate, onDelete, onToggleFavorite, createLabel: createLabelProp, renameLabel: renameLabelProp, duplicateLabel: duplicateLabelProp, deleteLabel: deleteLabelProp, favoriteLabel: favoriteLabelProp, viewOptionsLabel: viewOptionsLabelProp, searchPlaceholder: searchPlaceholderProp, searchLabel: searchLabelProp, emptyLabel: emptyLabelProp, className, "aria-label": ariaLabel, ...rest }: SavedViewsProps): React.JSX.Element;
53
90
  export declare namespace SavedViews {
54
91
  var displayName: string;
55
92
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@crisp-ui-kit/crisp",
3
- "version": "0.54.8",
3
+ "version": "0.54.9",
4
4
  "description": "A lean React design system, measured pixel-for-pixel from Attio.",
5
5
  "license": "MIT",
6
6
  "type": "module",