@crisp-ui-kit/crisp 0.35.0 → 0.37.0

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 (69) hide show
  1. package/dist/bulkbar.css +3 -0
  2. package/dist/bulkbar.layered.css +3 -0
  3. package/dist/cjs/components/bulkbar/bulkbar.js +1 -1
  4. package/dist/cjs/components/bulkbar/bulkbar.recipe.js +6 -0
  5. package/dist/cjs/components/contextmenu/contextmenu.js +25 -8
  6. package/dist/cjs/components/contextmenu/contextmenu.recipe.js +5 -2
  7. package/dist/cjs/components/dialog/dialog.js +1 -1
  8. package/dist/cjs/components/dialog/dialog.recipe.js +1 -1
  9. package/dist/cjs/components/menu/menu.js +20 -3
  10. package/dist/cjs/components/menu/menu.recipe.js +4 -3
  11. package/dist/cjs/components/multiselect/multiselect.js +26 -9
  12. package/dist/cjs/components/multiselect/multiselect.recipe.js +4 -3
  13. package/dist/cjs/components/popover/popover.js +25 -8
  14. package/dist/cjs/components/popover/popover.recipe.js +5 -3
  15. package/dist/cjs/components/select/select.js +30 -9
  16. package/dist/cjs/components/select/select.recipe.js +5 -3
  17. package/dist/cjs/components/tabletoolbar/tabletoolbar.js +50 -6
  18. package/dist/cjs/core/floating-portal.js +28 -0
  19. package/dist/cjs/core/floating-stack.js +54 -0
  20. package/dist/cjs/core/index.js +6 -1
  21. package/dist/cjs/product/recordpanel/recordpanel.js +4 -2
  22. package/dist/cjs/product/recordpanel/recordpanel.recipe.js +8 -0
  23. package/dist/cjs/product/recordstable/recordstable.js +15 -4
  24. package/dist/cjs/product/sidebar/sidebar.js +16 -3
  25. package/dist/contextmenu.css +1 -1
  26. package/dist/contextmenu.layered.css +1 -1
  27. package/dist/dialog.css +1 -1
  28. package/dist/dialog.layered.css +1 -1
  29. package/dist/esm/components/bulkbar/bulkbar.js +1 -1
  30. package/dist/esm/components/bulkbar/bulkbar.recipe.js +6 -0
  31. package/dist/esm/components/contextmenu/contextmenu.js +25 -8
  32. package/dist/esm/components/contextmenu/contextmenu.recipe.js +5 -2
  33. package/dist/esm/components/dialog/dialog.js +1 -1
  34. package/dist/esm/components/dialog/dialog.recipe.js +1 -1
  35. package/dist/esm/components/menu/menu.js +21 -4
  36. package/dist/esm/components/menu/menu.recipe.js +4 -3
  37. package/dist/esm/components/multiselect/multiselect.js +27 -10
  38. package/dist/esm/components/multiselect/multiselect.recipe.js +4 -3
  39. package/dist/esm/components/popover/popover.js +26 -9
  40. package/dist/esm/components/popover/popover.recipe.js +5 -3
  41. package/dist/esm/components/select/select.js +31 -10
  42. package/dist/esm/components/select/select.recipe.js +5 -3
  43. package/dist/esm/components/tabletoolbar/tabletoolbar.js +51 -7
  44. package/dist/esm/core/floating-portal.js +25 -0
  45. package/dist/esm/core/floating-stack.js +50 -0
  46. package/dist/esm/core/index.js +2 -0
  47. package/dist/esm/product/recordpanel/recordpanel.js +4 -2
  48. package/dist/esm/product/recordpanel/recordpanel.recipe.js +8 -0
  49. package/dist/esm/product/recordstable/recordstable.js +16 -5
  50. package/dist/esm/product/sidebar/sidebar.js +17 -4
  51. package/dist/menu.css +1 -1
  52. package/dist/menu.layered.css +1 -1
  53. package/dist/multiselect.css +1 -1
  54. package/dist/multiselect.layered.css +1 -1
  55. package/dist/popover.css +1 -1
  56. package/dist/popover.layered.css +1 -1
  57. package/dist/recordpanel.css +6 -0
  58. package/dist/recordpanel.layered.css +6 -0
  59. package/dist/select.css +1 -1
  60. package/dist/select.layered.css +1 -1
  61. package/dist/styles.css +15 -6
  62. package/dist/styles.layered.css +15 -6
  63. package/dist/types/components/tabletoolbar/tabletoolbar.d.ts +10 -1
  64. package/dist/types/core/floating-portal.d.ts +25 -0
  65. package/dist/types/core/floating-stack.d.ts +13 -0
  66. package/dist/types/core/index.d.ts +2 -0
  67. package/dist/types/product/recordpanel/recordpanel.d.ts +11 -1
  68. package/dist/types/product/sidebar/sidebar.d.ts +56 -0
  69. package/package.json +1 -1
@@ -25,6 +25,12 @@
25
25
  & .crisp-recordstable-tag[data-tone="orange"] { background: color-mix(in srgb, var(--crisp-bg-orange) 16%, transparent); color: color-mix(in srgb, var(--crisp-bg-orange) 82%, var(--crisp-fg-primary)); box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--crisp-bg-orange) 30%, transparent); }
26
26
  & .crisp-recordpanel-link { color: var(--crisp-bg-brand-solid); text-decoration: none; overflow: hidden; text-overflow: ellipsis; }
27
27
  & .crisp-recordpanel-link:hover { text-decoration: underline; }
28
+ /* #329: an optional slot for record actions (e.g. Edit). Reuses the header's own measured values rather than inventing new ones -- the
29
+ same horizontal inset (space.3), the same hairline border token
30
+ (borderSubtle, flipped to border-top since this sits at the opposite
31
+ edge), and the header/body gap (space.2) for its own vertical padding
32
+ and for spacing between whatever it's given. */
33
+ & .crisp-recordpanel-footer { flex-shrink: 0; display: flex; align-items: center; gap: var(--crisp-space-2); padding: var(--crisp-space-2) var(--crisp-space-3); border-top: 1px solid var(--crisp-border-subtle); }
28
34
  }
29
35
 
30
36
  @media (prefers-reduced-motion: reduce) {
@@ -26,6 +26,12 @@
26
26
  & .crisp-recordstable-tag[data-tone="orange"] { background: color-mix(in srgb, var(--crisp-bg-orange) 16%, transparent); color: color-mix(in srgb, var(--crisp-bg-orange) 82%, var(--crisp-fg-primary)); box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--crisp-bg-orange) 30%, transparent); }
27
27
  & .crisp-recordpanel-link { color: var(--crisp-bg-brand-solid); text-decoration: none; overflow: hidden; text-overflow: ellipsis; }
28
28
  & .crisp-recordpanel-link:hover { text-decoration: underline; }
29
+ /* #329: an optional slot for record actions (e.g. Edit). Reuses the header's own measured values rather than inventing new ones -- the
30
+ same horizontal inset (space.3), the same hairline border token
31
+ (borderSubtle, flipped to border-top since this sits at the opposite
32
+ edge), and the header/body gap (space.2) for its own vertical padding
33
+ and for spacing between whatever it's given. */
34
+ & .crisp-recordpanel-footer { flex-shrink: 0; display: flex; align-items: center; gap: var(--crisp-space-2); padding: var(--crisp-space-2) var(--crisp-space-3); border-top: 1px solid var(--crisp-border-subtle); }
29
35
  }
30
36
 
31
37
  @media (prefers-reduced-motion: reduce) {
package/dist/select.css CHANGED
@@ -8,7 +8,7 @@
8
8
  & .crisp-select-placeholder { color: var(--crisp-fg-muted); }
9
9
  & .crisp-select-chevron { flex-shrink: 0; color: var(--crisp-fg-muted); transition: transform .15s ease; }
10
10
  & .crisp-select-trigger[data-open] .crisp-select-chevron { transform: rotate(180deg); }
11
- & .crisp-select-listbox { position: fixed; z-index: 60; box-sizing: border-box; display: flex; flex-direction: column; gap: var(--crisp-space-px); max-height: 280px; overflow-y: auto; padding: var(--crisp-space-1); border-radius: var(--crisp-radius-xl); background: var(--crisp-bg-popover); box-shadow: inset 0 0 0 1px var(--crisp-ring-popover), 0 0 0 1px var(--crisp-shadow-popover1), 0 4px 8px -4px var(--crisp-shadow-popover2), 0 4px 12px -2px var(--crisp-shadow-popover3); transform-origin: top left; opacity: 1; transform: none; transition: opacity .08s ease, transform .08s ease; }
11
+ & .crisp-select-listbox { position: fixed; z-index: 70; box-sizing: border-box; display: flex; flex-direction: column; gap: var(--crisp-space-px); max-height: 280px; overflow-y: auto; padding: var(--crisp-space-1); border-radius: var(--crisp-radius-xl); background: var(--crisp-bg-popover); box-shadow: inset 0 0 0 1px var(--crisp-ring-popover), 0 0 0 1px var(--crisp-shadow-popover1), 0 4px 8px -4px var(--crisp-shadow-popover2), 0 4px 12px -2px var(--crisp-shadow-popover3); transform-origin: top left; opacity: 1; transform: none; transition: opacity .08s ease, transform .08s ease; }
12
12
  & .crisp-select-listbox[data-up] { transform-origin: bottom; }
13
13
  & .crisp-select-listbox { @starting-style { opacity: 0; transform: scale(0.97) translateY(-1px); } }
14
14
  & .crisp-select-search { display: flex; align-items: center; gap: var(--crisp-space-2); height: 34px; flex-shrink: 0; margin: calc(-1 * var(--crisp-space-1)) calc(-1 * var(--crisp-space-1)) var(--crisp-space-1); padding: 0 var(--crisp-space-2-5); border-bottom: 1px solid var(--crisp-border-subtle); color: var(--crisp-fg-muted); }
@@ -9,7 +9,7 @@
9
9
  & .crisp-select-placeholder { color: var(--crisp-fg-muted); }
10
10
  & .crisp-select-chevron { flex-shrink: 0; color: var(--crisp-fg-muted); transition: transform .15s ease; }
11
11
  & .crisp-select-trigger[data-open] .crisp-select-chevron { transform: rotate(180deg); }
12
- & .crisp-select-listbox { position: fixed; z-index: 60; box-sizing: border-box; display: flex; flex-direction: column; gap: var(--crisp-space-px); max-height: 280px; overflow-y: auto; padding: var(--crisp-space-1); border-radius: var(--crisp-radius-xl); background: var(--crisp-bg-popover); box-shadow: inset 0 0 0 1px var(--crisp-ring-popover), 0 0 0 1px var(--crisp-shadow-popover1), 0 4px 8px -4px var(--crisp-shadow-popover2), 0 4px 12px -2px var(--crisp-shadow-popover3); transform-origin: top left; opacity: 1; transform: none; transition: opacity .08s ease, transform .08s ease; }
12
+ & .crisp-select-listbox { position: fixed; z-index: 70; box-sizing: border-box; display: flex; flex-direction: column; gap: var(--crisp-space-px); max-height: 280px; overflow-y: auto; padding: var(--crisp-space-1); border-radius: var(--crisp-radius-xl); background: var(--crisp-bg-popover); box-shadow: inset 0 0 0 1px var(--crisp-ring-popover), 0 0 0 1px var(--crisp-shadow-popover1), 0 4px 8px -4px var(--crisp-shadow-popover2), 0 4px 12px -2px var(--crisp-shadow-popover3); transform-origin: top left; opacity: 1; transform: none; transition: opacity .08s ease, transform .08s ease; }
13
13
  & .crisp-select-listbox[data-up] { transform-origin: bottom; }
14
14
  & .crisp-select-listbox { @starting-style { opacity: 0; transform: scale(0.97) translateY(-1px); } }
15
15
  & .crisp-select-search { display: flex; align-items: center; gap: var(--crisp-space-2); height: 34px; flex-shrink: 0; margin: calc(-1 * var(--crisp-space-1)) calc(-1 * var(--crisp-space-1)) var(--crisp-space-1); padding: 0 var(--crisp-space-2-5); border-bottom: 1px solid var(--crisp-border-subtle); color: var(--crisp-fg-muted); }
package/dist/styles.css CHANGED
@@ -283,6 +283,9 @@
283
283
  & .crisp-bulkbar-count { display: inline-flex; align-items: center; gap: var(--crisp-space-1); height: var(--crisp-size-menuRow); margin: 0; padding: 0 var(--crisp-space-2); border: none; border-radius: var(--crisp-radius-md); background: color-mix(in srgb, var(--crisp-fg-primary) 4%, transparent); font-family: inherit; font-size: var(--crisp-text-md); font-weight: var(--crisp-weight-medium); color: var(--crisp-fg-muted); white-space: nowrap; cursor: pointer; transition: background-color .12s; }
284
284
  & .crisp-bulkbar-count:hover { background: color-mix(in srgb, var(--crisp-fg-primary) 8%, transparent); color: var(--crisp-fg-primary); }
285
285
  & .crisp-bulkbar-count svg { flex-shrink: 0; }
286
+ /* the ", Clear selection" ARIA concatenates onto the pill's name (#324)
287
+ -- read, never drawn. Same technique as Slider's thumblabel. */
288
+ & .crisp-bulkbar-clearlabel { position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px; overflow: hidden; clip: rect(0, 0, 0, 0); white-space: nowrap; border: 0; }
286
289
  & .crisp-bulkbar-btn { display: inline-flex; align-items: center; gap: var(--crisp-space-1); height: var(--crisp-size-menuRow); padding: 0 var(--crisp-space-2); border: none; border-radius: var(--crisp-radius-md); background: transparent; color: var(--crisp-fg-primary); font-family: inherit; font-size: var(--crisp-text-md); font-weight: var(--crisp-weight-medium); white-space: nowrap; cursor: pointer; transition: background-color .12s; }
287
290
  & .crisp-bulkbar-btn:hover { background: var(--crisp-bg-raised); }
288
291
  & .crisp-bulkbar-btn--danger { color: var(--crisp-bg-danger); }
@@ -447,7 +450,7 @@
447
450
 
448
451
  .crisp-contextmenu {
449
452
  display: contents; font-family: var(--crisp-font-sans);
450
- & .crisp-contextmenu-content { position: fixed; z-index: 60; min-width: 200px; box-sizing: border-box; padding: var(--crisp-space-1); border-radius: var(--crisp-radius-xl); background: var(--crisp-bg-popover); box-shadow: inset 0 0 0 1px var(--crisp-ring-popover), 0 0 0 1px var(--crisp-shadow-popover1), 0 4px 8px -4px var(--crisp-shadow-popover2), 0 4px 12px -2px var(--crisp-shadow-popover3); opacity: 1; transform: none; transition: opacity .08s ease, transform .08s ease; transform-origin: top left; @starting-style { opacity: 0; transform: scale(0.97) translateY(-1px); } }
453
+ & .crisp-contextmenu-content { position: fixed; z-index: 70; min-width: 200px; box-sizing: border-box; padding: var(--crisp-space-1); border-radius: var(--crisp-radius-xl); background: var(--crisp-bg-popover); box-shadow: inset 0 0 0 1px var(--crisp-ring-popover), 0 0 0 1px var(--crisp-shadow-popover1), 0 4px 8px -4px var(--crisp-shadow-popover2), 0 4px 12px -2px var(--crisp-shadow-popover3); opacity: 1; transform: none; transition: opacity .08s ease, transform .08s ease; transform-origin: top left; @starting-style { opacity: 0; transform: scale(0.97) translateY(-1px); } }
451
454
  & .crisp-contextmenu-item { display: flex; align-items: center; gap: var(--crisp-space-2); width: 100%; height: var(--crisp-size-menuRow); box-sizing: border-box; padding: 0 var(--crisp-space-2); margin: 0; border: none; border-radius: var(--crisp-radius-md); background: none; color: var(--crisp-fg-primary); font-family: inherit; font-size: var(--crisp-text-lg); text-align: start; white-space: nowrap; cursor: pointer; }
452
455
  & .crisp-contextmenu-item svg { flex-shrink: 0; color: var(--crisp-fg-muted); }
453
456
  & .crisp-contextmenu-item:hover { background: color-mix(in srgb, var(--crisp-fg-primary) 6%, transparent); }
@@ -707,7 +710,7 @@
707
710
  & .crisp-dialog-card { display: flex; flex-direction: column; flex: 1; min-height: 0; box-sizing: border-box; border-radius: var(--crisp-radius-xl); background: var(--crisp-bg-surface); box-shadow: 0 0 0 1px color-mix(in srgb, var(--crisp-fg-primary) 8%, transparent); overflow: hidden; }
708
711
  & .crisp-dialog-header { display: flex; align-items: center; gap: var(--crisp-space-2); flex-shrink: 0; height: 52px; padding: 0 var(--crisp-space-2) 0 var(--crisp-space-4); border-bottom: 1px solid var(--crisp-border-subtle); }
709
712
  & .crisp-dialog-header svg { flex-shrink: 0; color: var(--crisp-fg-muted); }
710
- & .crisp-dialog-title { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: var(--crisp-text-lg); font-weight: var(--crisp-weight-medium); color: var(--crisp-fg-primary); }
713
+ & .crisp-dialog-title { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: var(--crisp-text-lg); font-weight: var(--crisp-weight-medium); color: var(--crisp-fg-primary); margin: 0; }
711
714
  & .crisp-dialog-close { display: inline-flex; align-items: center; justify-content: center; flex-shrink: 0; width: 28px; height: 28px; margin: 0; padding: 0; border: none; border-radius: var(--crisp-radius-md); background: none; color: var(--crisp-fg-muted); font-family: inherit; cursor: pointer; }
712
715
  & .crisp-dialog-close:hover { background: color-mix(in srgb, var(--crisp-fg-primary) 6%, transparent); color: var(--crisp-fg-primary); }
713
716
  & .crisp-dialog-body { flex: 1; min-height: 0; overflow-y: auto; display: flex; flex-direction: column; gap: var(--crisp-space-4); padding: var(--crisp-space-5) var(--crisp-space-4); }
@@ -970,7 +973,7 @@
970
973
 
971
974
  .crisp-menu {
972
975
  position: relative; display: inline-flex; font-family: var(--crisp-font-sans); -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale;
973
- & .crisp-menu-content { position: fixed; z-index: 60; box-sizing: border-box; display: flex; flex-direction: column; gap: var(--crisp-space-px); min-width: 200px; max-height: 360px; overflow-y: auto; padding: var(--crisp-space-1); border-radius: var(--crisp-radius-xl); background: var(--crisp-bg-popover); box-shadow: inset 0 0 0 1px var(--crisp-ring-popover), 0 0 0 1px var(--crisp-shadow-popover1), 0 4px 8px -4px var(--crisp-shadow-popover2), 0 4px 12px -2px var(--crisp-shadow-popover3); transform-origin: top left; opacity: 1; transform: none; transition: opacity .08s ease, transform .08s ease; }
976
+ & .crisp-menu-content { position: fixed; z-index: 70; box-sizing: border-box; display: flex; flex-direction: column; gap: var(--crisp-space-px); min-width: 200px; max-height: 360px; overflow-y: auto; padding: var(--crisp-space-1); border-radius: var(--crisp-radius-xl); background: var(--crisp-bg-popover); box-shadow: inset 0 0 0 1px var(--crisp-ring-popover), 0 0 0 1px var(--crisp-shadow-popover1), 0 4px 8px -4px var(--crisp-shadow-popover2), 0 4px 12px -2px var(--crisp-shadow-popover3); transform-origin: top left; opacity: 1; transform: none; transition: opacity .08s ease, transform .08s ease; }
974
977
  & .crisp-menu-content[data-align="end"] { transform-origin: top right; }
975
978
  & .crisp-menu-content[data-up] { transform-origin: bottom left; }
976
979
  & .crisp-menu-content[data-up][data-align="end"] { transform-origin: bottom right; }
@@ -1009,7 +1012,7 @@
1009
1012
  & .crisp-multiselect-placeholder { color: var(--crisp-fg-muted); }
1010
1013
  & .crisp-multiselect-chevron { flex-shrink: 0; color: var(--crisp-fg-muted); transition: transform .15s ease; }
1011
1014
  & .crisp-multiselect-trigger[data-open] .crisp-multiselect-chevron { transform: rotate(180deg); }
1012
- & .crisp-multiselect-listbox { position: fixed; z-index: 60; box-sizing: border-box; display: flex; flex-direction: column; gap: var(--crisp-space-px); max-height: 300px; overflow-y: auto; padding: var(--crisp-space-1); border-radius: var(--crisp-radius-xl); background: var(--crisp-bg-popover); box-shadow: inset 0 0 0 1px var(--crisp-ring-popover), 0 0 0 1px var(--crisp-shadow-popover1), 0 4px 8px -4px var(--crisp-shadow-popover2), 0 4px 12px -2px var(--crisp-shadow-popover3); transform-origin: top left; opacity: 1; transform: none; transition: opacity .08s ease, transform .08s ease; }
1015
+ & .crisp-multiselect-listbox { position: fixed; z-index: 70; box-sizing: border-box; display: flex; flex-direction: column; gap: var(--crisp-space-px); max-height: 300px; overflow-y: auto; padding: var(--crisp-space-1); border-radius: var(--crisp-radius-xl); background: var(--crisp-bg-popover); box-shadow: inset 0 0 0 1px var(--crisp-ring-popover), 0 0 0 1px var(--crisp-shadow-popover1), 0 4px 8px -4px var(--crisp-shadow-popover2), 0 4px 12px -2px var(--crisp-shadow-popover3); transform-origin: top left; opacity: 1; transform: none; transition: opacity .08s ease, transform .08s ease; }
1013
1016
  & .crisp-multiselect-listbox[data-up] { transform-origin: bottom left; }
1014
1017
  & .crisp-multiselect-listbox { @starting-style { opacity: 0; transform: scale(0.97) translateY(-1px); } }
1015
1018
  & .crisp-multiselect-search { display: flex; align-items: center; gap: var(--crisp-space-2); height: 34px; flex-shrink: 0; margin: calc(-1 * var(--crisp-space-1)) calc(-1 * var(--crisp-space-1)) var(--crisp-space-1); padding: 0 var(--crisp-space-2-5); border-bottom: 1px solid var(--crisp-border-subtle); color: var(--crisp-fg-muted); }
@@ -1042,7 +1045,7 @@
1042
1045
 
1043
1046
  .crisp-popover {
1044
1047
  display: contents; font-family: var(--crisp-font-sans);
1045
- & .crisp-popover-content { position: fixed; z-index: 60; box-sizing: border-box; min-width: 200px; max-width: min(360px, 92vw); padding: var(--crisp-space-1); border-radius: var(--crisp-radius-xl); background: var(--crisp-bg-popover); color: var(--crisp-fg-primary); box-shadow: inset 0 0 0 1px var(--crisp-ring-popover), 0 0 0 1px var(--crisp-shadow-popover1), 0 4px 8px -4px var(--crisp-shadow-popover2), 0 4px 12px -2px var(--crisp-shadow-popover3); opacity: 1; transform: none; transition: opacity 0.08s ease, transform 0.08s ease; transform-origin: top left; @starting-style { opacity: 0; transform: scale(0.97) translateY(-1px); } }
1048
+ & .crisp-popover-content { position: fixed; z-index: 70; box-sizing: border-box; min-width: 200px; max-width: min(360px, 92vw); padding: var(--crisp-space-1); border-radius: var(--crisp-radius-xl); background: var(--crisp-bg-popover); color: var(--crisp-fg-primary); box-shadow: inset 0 0 0 1px var(--crisp-ring-popover), 0 0 0 1px var(--crisp-shadow-popover1), 0 4px 8px -4px var(--crisp-shadow-popover2), 0 4px 12px -2px var(--crisp-shadow-popover3); opacity: 1; transform: none; transition: opacity 0.08s ease, transform 0.08s ease; transform-origin: top left; @starting-style { opacity: 0; transform: scale(0.97) translateY(-1px); } }
1046
1049
  & .crisp-popover-content[data-align="end"] { transform-origin: top right; }
1047
1050
  & .crisp-popover-content[data-up] { transform-origin: bottom left; }
1048
1051
  }
@@ -1214,7 +1217,7 @@
1214
1217
  & .crisp-select-placeholder { color: var(--crisp-fg-muted); }
1215
1218
  & .crisp-select-chevron { flex-shrink: 0; color: var(--crisp-fg-muted); transition: transform .15s ease; }
1216
1219
  & .crisp-select-trigger[data-open] .crisp-select-chevron { transform: rotate(180deg); }
1217
- & .crisp-select-listbox { position: fixed; z-index: 60; box-sizing: border-box; display: flex; flex-direction: column; gap: var(--crisp-space-px); max-height: 280px; overflow-y: auto; padding: var(--crisp-space-1); border-radius: var(--crisp-radius-xl); background: var(--crisp-bg-popover); box-shadow: inset 0 0 0 1px var(--crisp-ring-popover), 0 0 0 1px var(--crisp-shadow-popover1), 0 4px 8px -4px var(--crisp-shadow-popover2), 0 4px 12px -2px var(--crisp-shadow-popover3); transform-origin: top left; opacity: 1; transform: none; transition: opacity .08s ease, transform .08s ease; }
1220
+ & .crisp-select-listbox { position: fixed; z-index: 70; box-sizing: border-box; display: flex; flex-direction: column; gap: var(--crisp-space-px); max-height: 280px; overflow-y: auto; padding: var(--crisp-space-1); border-radius: var(--crisp-radius-xl); background: var(--crisp-bg-popover); box-shadow: inset 0 0 0 1px var(--crisp-ring-popover), 0 0 0 1px var(--crisp-shadow-popover1), 0 4px 8px -4px var(--crisp-shadow-popover2), 0 4px 12px -2px var(--crisp-shadow-popover3); transform-origin: top left; opacity: 1; transform: none; transition: opacity .08s ease, transform .08s ease; }
1218
1221
  & .crisp-select-listbox[data-up] { transform-origin: bottom; }
1219
1222
  & .crisp-select-listbox { @starting-style { opacity: 0; transform: scale(0.97) translateY(-1px); } }
1220
1223
  & .crisp-select-search { display: flex; align-items: center; gap: var(--crisp-space-2); height: 34px; flex-shrink: 0; margin: calc(-1 * var(--crisp-space-1)) calc(-1 * var(--crisp-space-1)) var(--crisp-space-1); padding: 0 var(--crisp-space-2-5); border-bottom: 1px solid var(--crisp-border-subtle); color: var(--crisp-fg-muted); }
@@ -2062,6 +2065,12 @@
2062
2065
  & .crisp-recordstable-tag[data-tone="orange"] { background: color-mix(in srgb, var(--crisp-bg-orange) 16%, transparent); color: color-mix(in srgb, var(--crisp-bg-orange) 82%, var(--crisp-fg-primary)); box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--crisp-bg-orange) 30%, transparent); }
2063
2066
  & .crisp-recordpanel-link { color: var(--crisp-bg-brand-solid); text-decoration: none; overflow: hidden; text-overflow: ellipsis; }
2064
2067
  & .crisp-recordpanel-link:hover { text-decoration: underline; }
2068
+ /* #329: an optional slot for record actions (e.g. Edit). Reuses the header's own measured values rather than inventing new ones -- the
2069
+ same horizontal inset (space.3), the same hairline border token
2070
+ (borderSubtle, flipped to border-top since this sits at the opposite
2071
+ edge), and the header/body gap (space.2) for its own vertical padding
2072
+ and for spacing between whatever it's given. */
2073
+ & .crisp-recordpanel-footer { flex-shrink: 0; display: flex; align-items: center; gap: var(--crisp-space-2); padding: var(--crisp-space-2) var(--crisp-space-3); border-top: 1px solid var(--crisp-border-subtle); }
2065
2074
  }
2066
2075
 
2067
2076
  @media (prefers-reduced-motion: reduce) {
@@ -284,6 +284,9 @@
284
284
  & .crisp-bulkbar-count { display: inline-flex; align-items: center; gap: var(--crisp-space-1); height: var(--crisp-size-menuRow); margin: 0; padding: 0 var(--crisp-space-2); border: none; border-radius: var(--crisp-radius-md); background: color-mix(in srgb, var(--crisp-fg-primary) 4%, transparent); font-family: inherit; font-size: var(--crisp-text-md); font-weight: var(--crisp-weight-medium); color: var(--crisp-fg-muted); white-space: nowrap; cursor: pointer; transition: background-color .12s; }
285
285
  & .crisp-bulkbar-count:hover { background: color-mix(in srgb, var(--crisp-fg-primary) 8%, transparent); color: var(--crisp-fg-primary); }
286
286
  & .crisp-bulkbar-count svg { flex-shrink: 0; }
287
+ /* the ", Clear selection" ARIA concatenates onto the pill's name (#324)
288
+ -- read, never drawn. Same technique as Slider's thumblabel. */
289
+ & .crisp-bulkbar-clearlabel { position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px; overflow: hidden; clip: rect(0, 0, 0, 0); white-space: nowrap; border: 0; }
287
290
  & .crisp-bulkbar-btn { display: inline-flex; align-items: center; gap: var(--crisp-space-1); height: var(--crisp-size-menuRow); padding: 0 var(--crisp-space-2); border: none; border-radius: var(--crisp-radius-md); background: transparent; color: var(--crisp-fg-primary); font-family: inherit; font-size: var(--crisp-text-md); font-weight: var(--crisp-weight-medium); white-space: nowrap; cursor: pointer; transition: background-color .12s; }
288
291
  & .crisp-bulkbar-btn:hover { background: var(--crisp-bg-raised); }
289
292
  & .crisp-bulkbar-btn--danger { color: var(--crisp-bg-danger); }
@@ -448,7 +451,7 @@
448
451
 
449
452
  .crisp-contextmenu {
450
453
  display: contents; font-family: var(--crisp-font-sans);
451
- & .crisp-contextmenu-content { position: fixed; z-index: 60; min-width: 200px; box-sizing: border-box; padding: var(--crisp-space-1); border-radius: var(--crisp-radius-xl); background: var(--crisp-bg-popover); box-shadow: inset 0 0 0 1px var(--crisp-ring-popover), 0 0 0 1px var(--crisp-shadow-popover1), 0 4px 8px -4px var(--crisp-shadow-popover2), 0 4px 12px -2px var(--crisp-shadow-popover3); opacity: 1; transform: none; transition: opacity .08s ease, transform .08s ease; transform-origin: top left; @starting-style { opacity: 0; transform: scale(0.97) translateY(-1px); } }
454
+ & .crisp-contextmenu-content { position: fixed; z-index: 70; min-width: 200px; box-sizing: border-box; padding: var(--crisp-space-1); border-radius: var(--crisp-radius-xl); background: var(--crisp-bg-popover); box-shadow: inset 0 0 0 1px var(--crisp-ring-popover), 0 0 0 1px var(--crisp-shadow-popover1), 0 4px 8px -4px var(--crisp-shadow-popover2), 0 4px 12px -2px var(--crisp-shadow-popover3); opacity: 1; transform: none; transition: opacity .08s ease, transform .08s ease; transform-origin: top left; @starting-style { opacity: 0; transform: scale(0.97) translateY(-1px); } }
452
455
  & .crisp-contextmenu-item { display: flex; align-items: center; gap: var(--crisp-space-2); width: 100%; height: var(--crisp-size-menuRow); box-sizing: border-box; padding: 0 var(--crisp-space-2); margin: 0; border: none; border-radius: var(--crisp-radius-md); background: none; color: var(--crisp-fg-primary); font-family: inherit; font-size: var(--crisp-text-lg); text-align: start; white-space: nowrap; cursor: pointer; }
453
456
  & .crisp-contextmenu-item svg { flex-shrink: 0; color: var(--crisp-fg-muted); }
454
457
  & .crisp-contextmenu-item:hover { background: color-mix(in srgb, var(--crisp-fg-primary) 6%, transparent); }
@@ -708,7 +711,7 @@
708
711
  & .crisp-dialog-card { display: flex; flex-direction: column; flex: 1; min-height: 0; box-sizing: border-box; border-radius: var(--crisp-radius-xl); background: var(--crisp-bg-surface); box-shadow: 0 0 0 1px color-mix(in srgb, var(--crisp-fg-primary) 8%, transparent); overflow: hidden; }
709
712
  & .crisp-dialog-header { display: flex; align-items: center; gap: var(--crisp-space-2); flex-shrink: 0; height: 52px; padding: 0 var(--crisp-space-2) 0 var(--crisp-space-4); border-bottom: 1px solid var(--crisp-border-subtle); }
710
713
  & .crisp-dialog-header svg { flex-shrink: 0; color: var(--crisp-fg-muted); }
711
- & .crisp-dialog-title { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: var(--crisp-text-lg); font-weight: var(--crisp-weight-medium); color: var(--crisp-fg-primary); }
714
+ & .crisp-dialog-title { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: var(--crisp-text-lg); font-weight: var(--crisp-weight-medium); color: var(--crisp-fg-primary); margin: 0; }
712
715
  & .crisp-dialog-close { display: inline-flex; align-items: center; justify-content: center; flex-shrink: 0; width: 28px; height: 28px; margin: 0; padding: 0; border: none; border-radius: var(--crisp-radius-md); background: none; color: var(--crisp-fg-muted); font-family: inherit; cursor: pointer; }
713
716
  & .crisp-dialog-close:hover { background: color-mix(in srgb, var(--crisp-fg-primary) 6%, transparent); color: var(--crisp-fg-primary); }
714
717
  & .crisp-dialog-body { flex: 1; min-height: 0; overflow-y: auto; display: flex; flex-direction: column; gap: var(--crisp-space-4); padding: var(--crisp-space-5) var(--crisp-space-4); }
@@ -971,7 +974,7 @@
971
974
 
972
975
  .crisp-menu {
973
976
  position: relative; display: inline-flex; font-family: var(--crisp-font-sans); -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale;
974
- & .crisp-menu-content { position: fixed; z-index: 60; box-sizing: border-box; display: flex; flex-direction: column; gap: var(--crisp-space-px); min-width: 200px; max-height: 360px; overflow-y: auto; padding: var(--crisp-space-1); border-radius: var(--crisp-radius-xl); background: var(--crisp-bg-popover); box-shadow: inset 0 0 0 1px var(--crisp-ring-popover), 0 0 0 1px var(--crisp-shadow-popover1), 0 4px 8px -4px var(--crisp-shadow-popover2), 0 4px 12px -2px var(--crisp-shadow-popover3); transform-origin: top left; opacity: 1; transform: none; transition: opacity .08s ease, transform .08s ease; }
977
+ & .crisp-menu-content { position: fixed; z-index: 70; box-sizing: border-box; display: flex; flex-direction: column; gap: var(--crisp-space-px); min-width: 200px; max-height: 360px; overflow-y: auto; padding: var(--crisp-space-1); border-radius: var(--crisp-radius-xl); background: var(--crisp-bg-popover); box-shadow: inset 0 0 0 1px var(--crisp-ring-popover), 0 0 0 1px var(--crisp-shadow-popover1), 0 4px 8px -4px var(--crisp-shadow-popover2), 0 4px 12px -2px var(--crisp-shadow-popover3); transform-origin: top left; opacity: 1; transform: none; transition: opacity .08s ease, transform .08s ease; }
975
978
  & .crisp-menu-content[data-align="end"] { transform-origin: top right; }
976
979
  & .crisp-menu-content[data-up] { transform-origin: bottom left; }
977
980
  & .crisp-menu-content[data-up][data-align="end"] { transform-origin: bottom right; }
@@ -1010,7 +1013,7 @@
1010
1013
  & .crisp-multiselect-placeholder { color: var(--crisp-fg-muted); }
1011
1014
  & .crisp-multiselect-chevron { flex-shrink: 0; color: var(--crisp-fg-muted); transition: transform .15s ease; }
1012
1015
  & .crisp-multiselect-trigger[data-open] .crisp-multiselect-chevron { transform: rotate(180deg); }
1013
- & .crisp-multiselect-listbox { position: fixed; z-index: 60; box-sizing: border-box; display: flex; flex-direction: column; gap: var(--crisp-space-px); max-height: 300px; overflow-y: auto; padding: var(--crisp-space-1); border-radius: var(--crisp-radius-xl); background: var(--crisp-bg-popover); box-shadow: inset 0 0 0 1px var(--crisp-ring-popover), 0 0 0 1px var(--crisp-shadow-popover1), 0 4px 8px -4px var(--crisp-shadow-popover2), 0 4px 12px -2px var(--crisp-shadow-popover3); transform-origin: top left; opacity: 1; transform: none; transition: opacity .08s ease, transform .08s ease; }
1016
+ & .crisp-multiselect-listbox { position: fixed; z-index: 70; box-sizing: border-box; display: flex; flex-direction: column; gap: var(--crisp-space-px); max-height: 300px; overflow-y: auto; padding: var(--crisp-space-1); border-radius: var(--crisp-radius-xl); background: var(--crisp-bg-popover); box-shadow: inset 0 0 0 1px var(--crisp-ring-popover), 0 0 0 1px var(--crisp-shadow-popover1), 0 4px 8px -4px var(--crisp-shadow-popover2), 0 4px 12px -2px var(--crisp-shadow-popover3); transform-origin: top left; opacity: 1; transform: none; transition: opacity .08s ease, transform .08s ease; }
1014
1017
  & .crisp-multiselect-listbox[data-up] { transform-origin: bottom left; }
1015
1018
  & .crisp-multiselect-listbox { @starting-style { opacity: 0; transform: scale(0.97) translateY(-1px); } }
1016
1019
  & .crisp-multiselect-search { display: flex; align-items: center; gap: var(--crisp-space-2); height: 34px; flex-shrink: 0; margin: calc(-1 * var(--crisp-space-1)) calc(-1 * var(--crisp-space-1)) var(--crisp-space-1); padding: 0 var(--crisp-space-2-5); border-bottom: 1px solid var(--crisp-border-subtle); color: var(--crisp-fg-muted); }
@@ -1043,7 +1046,7 @@
1043
1046
 
1044
1047
  .crisp-popover {
1045
1048
  display: contents; font-family: var(--crisp-font-sans);
1046
- & .crisp-popover-content { position: fixed; z-index: 60; box-sizing: border-box; min-width: 200px; max-width: min(360px, 92vw); padding: var(--crisp-space-1); border-radius: var(--crisp-radius-xl); background: var(--crisp-bg-popover); color: var(--crisp-fg-primary); box-shadow: inset 0 0 0 1px var(--crisp-ring-popover), 0 0 0 1px var(--crisp-shadow-popover1), 0 4px 8px -4px var(--crisp-shadow-popover2), 0 4px 12px -2px var(--crisp-shadow-popover3); opacity: 1; transform: none; transition: opacity 0.08s ease, transform 0.08s ease; transform-origin: top left; @starting-style { opacity: 0; transform: scale(0.97) translateY(-1px); } }
1049
+ & .crisp-popover-content { position: fixed; z-index: 70; box-sizing: border-box; min-width: 200px; max-width: min(360px, 92vw); padding: var(--crisp-space-1); border-radius: var(--crisp-radius-xl); background: var(--crisp-bg-popover); color: var(--crisp-fg-primary); box-shadow: inset 0 0 0 1px var(--crisp-ring-popover), 0 0 0 1px var(--crisp-shadow-popover1), 0 4px 8px -4px var(--crisp-shadow-popover2), 0 4px 12px -2px var(--crisp-shadow-popover3); opacity: 1; transform: none; transition: opacity 0.08s ease, transform 0.08s ease; transform-origin: top left; @starting-style { opacity: 0; transform: scale(0.97) translateY(-1px); } }
1047
1050
  & .crisp-popover-content[data-align="end"] { transform-origin: top right; }
1048
1051
  & .crisp-popover-content[data-up] { transform-origin: bottom left; }
1049
1052
  }
@@ -1215,7 +1218,7 @@
1215
1218
  & .crisp-select-placeholder { color: var(--crisp-fg-muted); }
1216
1219
  & .crisp-select-chevron { flex-shrink: 0; color: var(--crisp-fg-muted); transition: transform .15s ease; }
1217
1220
  & .crisp-select-trigger[data-open] .crisp-select-chevron { transform: rotate(180deg); }
1218
- & .crisp-select-listbox { position: fixed; z-index: 60; box-sizing: border-box; display: flex; flex-direction: column; gap: var(--crisp-space-px); max-height: 280px; overflow-y: auto; padding: var(--crisp-space-1); border-radius: var(--crisp-radius-xl); background: var(--crisp-bg-popover); box-shadow: inset 0 0 0 1px var(--crisp-ring-popover), 0 0 0 1px var(--crisp-shadow-popover1), 0 4px 8px -4px var(--crisp-shadow-popover2), 0 4px 12px -2px var(--crisp-shadow-popover3); transform-origin: top left; opacity: 1; transform: none; transition: opacity .08s ease, transform .08s ease; }
1221
+ & .crisp-select-listbox { position: fixed; z-index: 70; box-sizing: border-box; display: flex; flex-direction: column; gap: var(--crisp-space-px); max-height: 280px; overflow-y: auto; padding: var(--crisp-space-1); border-radius: var(--crisp-radius-xl); background: var(--crisp-bg-popover); box-shadow: inset 0 0 0 1px var(--crisp-ring-popover), 0 0 0 1px var(--crisp-shadow-popover1), 0 4px 8px -4px var(--crisp-shadow-popover2), 0 4px 12px -2px var(--crisp-shadow-popover3); transform-origin: top left; opacity: 1; transform: none; transition: opacity .08s ease, transform .08s ease; }
1219
1222
  & .crisp-select-listbox[data-up] { transform-origin: bottom; }
1220
1223
  & .crisp-select-listbox { @starting-style { opacity: 0; transform: scale(0.97) translateY(-1px); } }
1221
1224
  & .crisp-select-search { display: flex; align-items: center; gap: var(--crisp-space-2); height: 34px; flex-shrink: 0; margin: calc(-1 * var(--crisp-space-1)) calc(-1 * var(--crisp-space-1)) var(--crisp-space-1); padding: 0 var(--crisp-space-2-5); border-bottom: 1px solid var(--crisp-border-subtle); color: var(--crisp-fg-muted); }
@@ -2063,6 +2066,12 @@
2063
2066
  & .crisp-recordstable-tag[data-tone="orange"] { background: color-mix(in srgb, var(--crisp-bg-orange) 16%, transparent); color: color-mix(in srgb, var(--crisp-bg-orange) 82%, var(--crisp-fg-primary)); box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--crisp-bg-orange) 30%, transparent); }
2064
2067
  & .crisp-recordpanel-link { color: var(--crisp-bg-brand-solid); text-decoration: none; overflow: hidden; text-overflow: ellipsis; }
2065
2068
  & .crisp-recordpanel-link:hover { text-decoration: underline; }
2069
+ /* #329: an optional slot for record actions (e.g. Edit). Reuses the header's own measured values rather than inventing new ones -- the
2070
+ same horizontal inset (space.3), the same hairline border token
2071
+ (borderSubtle, flipped to border-top since this sits at the opposite
2072
+ edge), and the header/body gap (space.2) for its own vertical padding
2073
+ and for spacing between whatever it's given. */
2074
+ & .crisp-recordpanel-footer { flex-shrink: 0; display: flex; align-items: center; gap: var(--crisp-space-2); padding: var(--crisp-space-2) var(--crisp-space-3); border-top: 1px solid var(--crisp-border-subtle); }
2066
2075
  }
2067
2076
 
2068
2077
  @media (prefers-reduced-motion: reduce) {
@@ -8,18 +8,27 @@ export type ToolbarSort = {
8
8
  key: string;
9
9
  dir: SortDir;
10
10
  };
11
- export type ToolbarFilterOp = "contains" | "is" | "isnot" | "empty" | "notempty";
11
+ export type ToolbarFilterOp = "contains" | "is" | "isnot" | "isanyof" | "empty" | "notempty";
12
12
  export type ToolbarFilter = {
13
13
  id: string;
14
14
  key: string;
15
15
  op: ToolbarFilterOp;
16
16
  value: string;
17
+ /** Selected values for `"isanyof"` — ignored by every other op. */
18
+ values?: string[];
17
19
  };
18
20
  export type TableToolbarColumn = {
19
21
  key: string;
20
22
  label: string;
21
23
  /** Leading field-type icon, shown in the field pickers. */
22
24
  icon?: IconType;
25
+ /**
26
+ * This column's distinct values (a status/select field's option set, e.g.).
27
+ * Supplying it adds `"isanyof"` — a searchable checkbox list, Attio's
28
+ * faceted multi-select filter — to the column's condition picker. Omit it
29
+ * for a free-text column; there is nothing to check off.
30
+ */
31
+ values?: string[];
23
32
  };
24
33
  export type TableToolbarProps = Omit<React.ComponentPropsWithoutRef<"div">, "children"> & {
25
34
  /** The fields that can be sorted and filtered on. */
@@ -0,0 +1,25 @@
1
+ import type * as React from "react";
2
+ /**
3
+ * Portals a floating layer (a `position: fixed` listbox/menu/popover
4
+ * content) to `document.body` — exactly what Tooltip already did with a
5
+ * bare `createPortal` call, pulled out so `Select`, `MultiSelect`, `Menu`,
6
+ * `Popover` and `ContextMenu` share the one implementation (#339).
7
+ *
8
+ * Needed because a `position: fixed` box resolves against the nearest
9
+ * ancestor that establishes a containing block for it — not necessarily the
10
+ * viewport. `transform` (even the identity `scale(1)`), `filter` and
11
+ * `backdrop-filter` all qualify, and `Dialog`'s own measured surface sets
12
+ * both a resting `transform: scale(1)` (so its `@starting-style` entrance
13
+ * has something to animate from) and `backdrop-filter: blur(15px)` (Attio's
14
+ * frosted-glass modal). A floating layer opened from a trigger inside a
15
+ * `Dialog` was rendering offset by the dialog's own box instead of anchored
16
+ * to its trigger.
17
+ *
18
+ * Every caller renders this conditionally on its own `open` state, which
19
+ * starts `false` and is driven only by client interaction — never a prop a
20
+ * fixture sets true at mount (unlike Dialog/Drawer/Command) — so this never
21
+ * touches `document` during SSR, the same safety Tooltip already had.
22
+ */
23
+ export declare function FloatingPortal({ children, }: {
24
+ children: React.ReactNode;
25
+ }): React.ReactPortal;
@@ -0,0 +1,13 @@
1
+ /** Call when a layer opens; call the returned cleanup when it closes. */
2
+ export declare function registerFloatingLayer(el: HTMLElement): () => void;
3
+ /**
4
+ * True when `target` sits inside a layer that was registered strictly after
5
+ * `self` — a layer that could only have been spawned from inside `self`,
6
+ * directly or through further nesting.
7
+ *
8
+ * Pass `null` for `self` when the caller is a hand-rolled dropdown that
9
+ * doesn't register itself on this stack (RecordsTable's and TableToolbar's
10
+ * own popovers, e.g.) — every registered layer then counts, which is the
11
+ * right query for "was this click inside some floating child I opened".
12
+ */
13
+ export declare function isInsideLaterFloatingLayer(target: Node, self: HTMLElement | null): boolean;
@@ -3,6 +3,8 @@ export { cx } from "./cx";
3
3
  export { defineRecipe } from "./define";
4
4
  export { withDescribedBy } from "./dialog-description";
5
5
  export { isRtlElement } from "./direction";
6
+ export { FloatingPortal } from "./floating-portal";
7
+ export { isInsideLaterFloatingLayer, registerFloatingLayer, } from "./floating-stack";
6
8
  export { formValueStyle } from "./form-value";
7
9
  export { type FloatingAlign, type PlaceFloatingOptions, placeFloating, } from "./place-floating";
8
10
  export { Primitive } from "./primitive";
@@ -4,10 +4,20 @@ export type RecordPanelProps = Omit<React.ComponentPropsWithoutRef<"div">, "chil
4
4
  /** The record to show; render nothing when null. */
5
5
  /** Label for the close control. */
6
6
  closeLabel?: string;
7
+ /**
8
+ * The panel's heading, and so its accessible name (via `aria-labelledby`,
9
+ * not a stringified `aria-label` — it also drives the visible title, which
10
+ * can be any node). Defaults to the entity column's name, same as before
11
+ * this prop existed; pass it to title the panel by something else, e.g.
12
+ * "{Collection} record".
13
+ */
14
+ title?: React.ReactNode;
7
15
  /** Heading above the field list. */
8
16
  detailsLabel?: React.ReactNode;
9
17
  /** Shown in place of a field that has no value. */
10
18
  emptyLabel?: React.ReactNode;
19
+ /** Rendered below the field list, e.g. an Edit button. Omit for no footer. */
20
+ footer?: React.ReactNode;
11
21
  record: Row | null;
12
22
  columns: Column[];
13
23
  onClose: () => void;
@@ -19,7 +29,7 @@ export type RecordPanelProps = Omit<React.ComponentPropsWithoutRef<"div">, "chil
19
29
  * relative` container (e.g. the app main area); it slides in from the right and
20
30
  * overlays the content.
21
31
  */
22
- export declare function RecordPanel({ record, columns, onClose, className, closeLabel, detailsLabel, emptyLabel, ...rest }: RecordPanelProps): React.JSX.Element | null;
32
+ export declare function RecordPanel({ record, columns, onClose, className, closeLabel, title, detailsLabel, emptyLabel, footer, ...rest }: RecordPanelProps): React.JSX.Element | null;
23
33
  export declare namespace RecordPanel {
24
34
  var displayName: string;
25
35
  }
@@ -40,6 +40,42 @@ export type SidebarListsSection = {
40
40
  label: string;
41
41
  newListLabel: string;
42
42
  };
43
+ /** A row in a `groups` section (#325). */
44
+ export type SidebarNavGroupItem = {
45
+ label: string;
46
+ /** 16px icon component, rendered `<Icon />`. Optional — not every group's
47
+ * rows carry one (unlike the main nav's `SidebarNavItem`, where it's
48
+ * required). */
49
+ icon?: React.ComponentType<IconProps>;
50
+ /** Either this or `onSelect` — a plain link, or a handler for anything
51
+ * that isn't real navigation (opening a panel, selecting a view). Both
52
+ * may be set; neither is required to make the row *something* clickable
53
+ * (it still renders as a link, defaulting `href` to "#"). */
54
+ href?: string;
55
+ onSelect?: () => void;
56
+ active?: boolean;
57
+ /** Trailing content — a count, a badge, anything `NavItem`'s own
58
+ * `trailing` slot already takes. */
59
+ count?: React.ReactNode;
60
+ };
61
+ /**
62
+ * A named, collapsible group of `SidebarNavGroupItem` rows — navigation as
63
+ * data, for a consumer whose sections aren't Attio's Records/Lists/Chats
64
+ * (#325). Pass `groups` to `Sidebar` and it replaces those three sections
65
+ * entirely; a group's `items` can come from runtime data (a re-render with a
66
+ * new array is all a group needs to pick up new rows).
67
+ */
68
+ export type SidebarNavGroup = {
69
+ /** Identity for this group's own collapse state across renders. Falls
70
+ * back to `label` — set it explicitly if two groups could ever share a
71
+ * label, or if a group's label itself changes at runtime. */
72
+ key?: string;
73
+ label: string;
74
+ items: SidebarNavGroupItem[];
75
+ /** Starts this group collapsed (default: expanded, matching every
76
+ * existing section). Read once, on mount. */
77
+ defaultCollapsed?: boolean;
78
+ };
43
79
  /** Footer copy: the onboarding pill, the invite row, and the trial banner. */
44
80
  export type SidebarFooter = {
45
81
  gettingStartedLabel: string;
@@ -67,6 +103,16 @@ export type SidebarProps = React.ComponentPropsWithoutRef<"nav"> & {
67
103
  listsSection?: SidebarListsSection;
68
104
  /** The Chats section (label + nav-item rows). */
69
105
  chatsSection?: SidebarSection;
106
+ /**
107
+ * Navigation as data (#325): any number of named, collapsible groups,
108
+ * reusing the same section markup/recipe as Records/Lists/Chats. When
109
+ * given, it REPLACES those three sections entirely — a consumer whose
110
+ * sections aren't Attio's (internal/grc's Activity, Governance, Identity,
111
+ * Organization, System Administration, plus user-built registers) passes
112
+ * `groups` instead of the three fixed props. Omit it and `<Sidebar />`
113
+ * renders exactly as before this prop existed.
114
+ */
115
+ groups?: SidebarNavGroup[];
70
116
  /** Footer copy (onboarding pill, invite row, trial banner). */
71
117
  footer?: SidebarFooter;
72
118
  };
@@ -102,6 +148,16 @@ export declare const Sidebar: React.ForwardRefExoticComponent<Omit<React.Detaile
102
148
  listsSection?: SidebarListsSection;
103
149
  /** The Chats section (label + nav-item rows). */
104
150
  chatsSection?: SidebarSection;
151
+ /**
152
+ * Navigation as data (#325): any number of named, collapsible groups,
153
+ * reusing the same section markup/recipe as Records/Lists/Chats. When
154
+ * given, it REPLACES those three sections entirely — a consumer whose
155
+ * sections aren't Attio's (internal/grc's Activity, Governance, Identity,
156
+ * Organization, System Administration, plus user-built registers) passes
157
+ * `groups` instead of the three fixed props. Omit it and `<Sidebar />`
158
+ * renders exactly as before this prop existed.
159
+ */
160
+ groups?: SidebarNavGroup[];
105
161
  /** Footer copy (onboarding pill, invite row, trial banner). */
106
162
  footer?: SidebarFooter;
107
163
  } & React.RefAttributes<HTMLDivElement>>;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@crisp-ui-kit/crisp",
3
- "version": "0.35.0",
3
+ "version": "0.37.0",
4
4
  "description": "A lean React design system, measured pixel-for-pixel from Attio.",
5
5
  "license": "MIT",
6
6
  "type": "module",