@crisp-ui-kit/crisp 0.57.2 → 0.57.4

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 (200) hide show
  1. package/dist/assigneepicker.css +13 -0
  2. package/dist/assigneepicker.d.ts +7 -0
  3. package/dist/assigneepicker.layered.css +15 -0
  4. package/dist/button.css +3 -0
  5. package/dist/button.d.ts +2 -2
  6. package/dist/button.layered.css +3 -0
  7. package/dist/chip.css +1 -1
  8. package/dist/chip.layered.css +1 -1
  9. package/dist/cjs/components/breadcrumb/breadcrumb.js +1 -1
  10. package/dist/cjs/components/button/button.recipe.js +33 -0
  11. package/dist/cjs/components/chip/chip.recipe.js +3 -1
  12. package/dist/cjs/components/datatable/datatable.js +6 -3
  13. package/dist/cjs/components/datatable/datatable.recipe.js +45 -6
  14. package/dist/cjs/components/datepickerfield/datepickerfield.js +2 -2
  15. package/dist/cjs/components/datepickerfield/datepickerfield.recipe.js +16 -3
  16. package/dist/cjs/components/menu/menu.js +65 -9
  17. package/dist/cjs/components/multiselect/multiselect.js +3 -0
  18. package/dist/cjs/components/multiselect/multiselect.recipe.js +18 -0
  19. package/dist/cjs/components/select/select.js +2 -2
  20. package/dist/cjs/components/select/select.recipe.js +19 -0
  21. package/dist/cjs/components/table/table.recipe.js +7 -1
  22. package/dist/cjs/core/floating-portal.js +7 -4
  23. package/dist/cjs/core/index.js +2 -1
  24. package/dist/cjs/core/place-floating.js +38 -0
  25. package/dist/cjs/index.js +8 -0
  26. package/dist/cjs/product/assigneepicker/assigneepicker.js +127 -0
  27. package/dist/cjs/product/assigneepicker/assigneepicker.recipe.js +36 -0
  28. package/dist/cjs/product/assigneepicker/index.js +7 -0
  29. package/dist/cjs/product/inbox/inbox.js +11 -3
  30. package/dist/cjs/product/notesgallery/index.js +7 -0
  31. package/dist/cjs/product/notesgallery/notesgallery.js +80 -0
  32. package/dist/cjs/product/notesgallery/notesgallery.recipe.js +46 -0
  33. package/dist/cjs/product/objectpagemenu/index.js +17 -0
  34. package/dist/cjs/product/objectpagemenu/objectpagemenu.js +26 -0
  35. package/dist/cjs/product/objectpagemenu/objectpagemenu.recipe.js +30 -0
  36. package/dist/cjs/product/objectsettings/objectsettings.js +107 -14
  37. package/dist/cjs/product/recordpage/index.js +2 -1
  38. package/dist/cjs/product/recordpage/recordpage.js +112 -7
  39. package/dist/cjs/product/recordpage/recordpage.recipe.js +63 -0
  40. package/dist/cjs/product/recordpicker/index.js +7 -0
  41. package/dist/cjs/product/recordpicker/recordpicker.js +100 -0
  42. package/dist/cjs/product/recordpicker/recordpicker.recipe.js +36 -0
  43. package/dist/cjs/product/savedviews/index.js +2 -1
  44. package/dist/cjs/product/savedviews/savedviews.js +22 -0
  45. package/dist/cjs/product/savedviews/savedviews.recipe.js +47 -0
  46. package/dist/cjs/product/share/index.js +7 -0
  47. package/dist/cjs/product/share/share.js +179 -0
  48. package/dist/cjs/product/share/share.recipe.js +114 -0
  49. package/dist/cjs/product/shortcutssheet/index.js +17 -0
  50. package/dist/cjs/product/shortcutssheet/shortcutssheet.js +125 -0
  51. package/dist/cjs/product/shortcutssheet/shortcutssheet.recipe.js +50 -0
  52. package/dist/cjs/product/taskeditpopover/index.js +7 -0
  53. package/dist/cjs/product/taskeditpopover/taskeditpopover.js +159 -0
  54. package/dist/cjs/product/taskeditpopover/taskeditpopover.recipe.js +68 -0
  55. package/dist/cjs/product/workspaceswitchermenu/index.js +17 -0
  56. package/dist/cjs/product/workspaceswitchermenu/workspaceswitchermenu.js +38 -0
  57. package/dist/cjs/product/workspaceswitchermenu/workspaceswitchermenu.recipe.js +34 -0
  58. package/dist/cjs/tokens/palette.js +27 -0
  59. package/dist/cjs/tokens/semantic.js +20 -0
  60. package/dist/datatable.css +50 -11
  61. package/dist/datatable.layered.css +50 -11
  62. package/dist/datepickerfield.css +8 -2
  63. package/dist/datepickerfield.d.ts +2 -2
  64. package/dist/datepickerfield.layered.css +8 -2
  65. package/dist/esm/components/breadcrumb/breadcrumb.js +1 -1
  66. package/dist/esm/components/button/button.recipe.js +33 -0
  67. package/dist/esm/components/chip/chip.recipe.js +3 -1
  68. package/dist/esm/components/datatable/datatable.js +6 -3
  69. package/dist/esm/components/datatable/datatable.recipe.js +45 -6
  70. package/dist/esm/components/datepickerfield/datepickerfield.js +2 -2
  71. package/dist/esm/components/datepickerfield/datepickerfield.recipe.js +16 -3
  72. package/dist/esm/components/menu/menu.js +64 -8
  73. package/dist/esm/components/multiselect/multiselect.js +3 -0
  74. package/dist/esm/components/multiselect/multiselect.recipe.js +18 -0
  75. package/dist/esm/components/select/select.js +2 -2
  76. package/dist/esm/components/select/select.recipe.js +19 -0
  77. package/dist/esm/components/table/table.recipe.js +7 -1
  78. package/dist/esm/core/floating-portal.js +7 -4
  79. package/dist/esm/core/index.js +1 -1
  80. package/dist/esm/core/place-floating.js +37 -0
  81. package/dist/esm/index.js +8 -0
  82. package/dist/esm/product/assigneepicker/assigneepicker.js +91 -0
  83. package/dist/esm/product/assigneepicker/assigneepicker.recipe.js +33 -0
  84. package/dist/esm/product/assigneepicker/index.js +2 -0
  85. package/dist/esm/product/inbox/inbox.js +11 -3
  86. package/dist/esm/product/notesgallery/index.js +2 -0
  87. package/dist/esm/product/notesgallery/notesgallery.js +44 -0
  88. package/dist/esm/product/notesgallery/notesgallery.recipe.js +43 -0
  89. package/dist/esm/product/objectpagemenu/index.js +1 -0
  90. package/dist/esm/product/objectpagemenu/objectpagemenu.js +23 -0
  91. package/dist/esm/product/objectpagemenu/objectpagemenu.recipe.js +27 -0
  92. package/dist/esm/product/objectsettings/objectsettings.js +106 -13
  93. package/dist/esm/product/recordpage/index.js +1 -1
  94. package/dist/esm/product/recordpage/recordpage.js +112 -8
  95. package/dist/esm/product/recordpage/recordpage.recipe.js +63 -0
  96. package/dist/esm/product/recordpicker/index.js +2 -0
  97. package/dist/esm/product/recordpicker/recordpicker.js +64 -0
  98. package/dist/esm/product/recordpicker/recordpicker.recipe.js +33 -0
  99. package/dist/esm/product/savedviews/index.js +1 -1
  100. package/dist/esm/product/savedviews/savedviews.js +22 -1
  101. package/dist/esm/product/savedviews/savedviews.recipe.js +47 -0
  102. package/dist/esm/product/share/index.js +2 -0
  103. package/dist/esm/product/share/share.js +143 -0
  104. package/dist/esm/product/share/share.recipe.js +111 -0
  105. package/dist/esm/product/shortcutssheet/index.js +1 -0
  106. package/dist/esm/product/shortcutssheet/shortcutssheet.js +88 -0
  107. package/dist/esm/product/shortcutssheet/shortcutssheet.recipe.js +47 -0
  108. package/dist/esm/product/taskeditpopover/index.js +2 -0
  109. package/dist/esm/product/taskeditpopover/taskeditpopover.js +123 -0
  110. package/dist/esm/product/taskeditpopover/taskeditpopover.recipe.js +65 -0
  111. package/dist/esm/product/workspaceswitchermenu/index.js +1 -0
  112. package/dist/esm/product/workspaceswitchermenu/workspaceswitchermenu.js +35 -0
  113. package/dist/esm/product/workspaceswitchermenu/workspaceswitchermenu.recipe.js +31 -0
  114. package/dist/esm/tokens/palette.js +27 -0
  115. package/dist/esm/tokens/semantic.js +20 -0
  116. package/dist/multiselect.css +18 -0
  117. package/dist/multiselect.layered.css +18 -0
  118. package/dist/native/tokens.d.ts +1 -1
  119. package/dist/native/tokens.js +56 -4
  120. package/dist/notesgallery.css +15 -0
  121. package/dist/notesgallery.d.ts +7 -0
  122. package/dist/notesgallery.layered.css +17 -0
  123. package/dist/objectpagemenu.css +3 -0
  124. package/dist/objectpagemenu.d.ts +7 -0
  125. package/dist/objectpagemenu.layered.css +5 -0
  126. package/dist/recordpage.css +59 -0
  127. package/dist/recordpage.layered.css +59 -0
  128. package/dist/recordpicker.css +13 -0
  129. package/dist/recordpicker.d.ts +7 -0
  130. package/dist/recordpicker.layered.css +15 -0
  131. package/dist/savedviews.css +47 -0
  132. package/dist/savedviews.layered.css +47 -0
  133. package/dist/select.css +19 -0
  134. package/dist/select.layered.css +19 -0
  135. package/dist/share.css +84 -0
  136. package/dist/share.d.ts +7 -0
  137. package/dist/share.layered.css +86 -0
  138. package/dist/shortcutssheet.css +24 -0
  139. package/dist/shortcutssheet.d.ts +7 -0
  140. package/dist/shortcutssheet.layered.css +26 -0
  141. package/dist/styles.css +438 -16
  142. package/dist/styles.layered.css +438 -16
  143. package/dist/table.css +7 -2
  144. package/dist/table.layered.css +7 -2
  145. package/dist/taskeditpopover.css +36 -0
  146. package/dist/taskeditpopover.d.ts +7 -0
  147. package/dist/taskeditpopover.layered.css +38 -0
  148. package/dist/tokens.css +26 -0
  149. package/dist/tokens.json +56 -4
  150. package/dist/types/components/breadcrumb/breadcrumb.d.ts +9 -1
  151. package/dist/types/components/button/button.d.ts +2 -0
  152. package/dist/types/components/button/button.recipe.d.ts +12 -0
  153. package/dist/types/components/chip/chip.recipe.d.ts +1 -1
  154. package/dist/types/components/datatable/datatable.d.ts +8 -0
  155. package/dist/types/components/datepickerfield/datepickerfield.d.ts +4 -1
  156. package/dist/types/components/datepickerfield/datepickerfield.recipe.d.ts +18 -3
  157. package/dist/types/components/menu/menu.d.ts +50 -7
  158. package/dist/types/components/multiselect/multiselect.d.ts +9 -0
  159. package/dist/types/components/select/select.d.ts +10 -0
  160. package/dist/types/core/floating-portal.d.ts +6 -5
  161. package/dist/types/core/index.d.ts +1 -1
  162. package/dist/types/core/place-floating.d.ts +37 -0
  163. package/dist/types/index.d.ts +8 -0
  164. package/dist/types/product/assigneepicker/assigneepicker.d.ts +36 -0
  165. package/dist/types/product/assigneepicker/assigneepicker.recipe.d.ts +3 -0
  166. package/dist/types/product/assigneepicker/index.d.ts +2 -0
  167. package/dist/types/product/inbox/inbox.d.ts +13 -1
  168. package/dist/types/product/notesgallery/index.d.ts +2 -0
  169. package/dist/types/product/notesgallery/notesgallery.d.ts +54 -0
  170. package/dist/types/product/notesgallery/notesgallery.recipe.d.ts +3 -0
  171. package/dist/types/product/objectpagemenu/index.d.ts +1 -0
  172. package/dist/types/product/objectpagemenu/objectpagemenu.d.ts +40 -0
  173. package/dist/types/product/objectpagemenu/objectpagemenu.recipe.d.ts +17 -0
  174. package/dist/types/product/objectsettings/objectsettings.d.ts +71 -15
  175. package/dist/types/product/recordpage/index.d.ts +1 -1
  176. package/dist/types/product/recordpage/recordpage.d.ts +142 -1
  177. package/dist/types/product/recordpicker/index.d.ts +2 -0
  178. package/dist/types/product/recordpicker/recordpicker.d.ts +38 -0
  179. package/dist/types/product/recordpicker/recordpicker.recipe.d.ts +3 -0
  180. package/dist/types/product/savedviews/index.d.ts +1 -1
  181. package/dist/types/product/savedviews/savedviews.d.ts +36 -0
  182. package/dist/types/product/share/index.d.ts +2 -0
  183. package/dist/types/product/share/share.d.ts +85 -0
  184. package/dist/types/product/share/share.recipe.d.ts +3 -0
  185. package/dist/types/product/shortcutssheet/index.d.ts +1 -0
  186. package/dist/types/product/shortcutssheet/shortcutssheet.d.ts +49 -0
  187. package/dist/types/product/shortcutssheet/shortcutssheet.recipe.d.ts +3 -0
  188. package/dist/types/product/taskeditpopover/index.d.ts +2 -0
  189. package/dist/types/product/taskeditpopover/taskeditpopover.d.ts +67 -0
  190. package/dist/types/product/taskeditpopover/taskeditpopover.recipe.d.ts +3 -0
  191. package/dist/types/product/workspaceswitchermenu/index.d.ts +1 -0
  192. package/dist/types/product/workspaceswitchermenu/workspaceswitchermenu.d.ts +54 -0
  193. package/dist/types/product/workspaceswitchermenu/workspaceswitchermenu.recipe.d.ts +19 -0
  194. package/dist/types/tokens/contract.d.ts +13 -0
  195. package/dist/types/tokens/palette.d.ts +52 -0
  196. package/dist/types/tokens/semantic.d.ts +20 -0
  197. package/dist/workspaceswitchermenu.css +4 -0
  198. package/dist/workspaceswitchermenu.d.ts +7 -0
  199. package/dist/workspaceswitchermenu.layered.css +6 -0
  200. package/package.json +41 -1
@@ -0,0 +1,13 @@
1
+ .crisp-assigneepicker {
2
+ display: flex; flex-direction: column; box-sizing: border-box; width: 350px; font-family: var(--crisp-font-sans);
3
+ & .crisp-assigneepicker-search { display: flex; align-items: center; gap: var(--crisp-space-2); height: 36px; padding: 0 var(--crisp-space-3); color: var(--crisp-fg-muted); }
4
+ & .crisp-assigneepicker-search svg { flex-shrink: 0; }
5
+ & .crisp-assigneepicker-search-input { flex: 1; min-width: 0; border: none; outline: none; background: none; font-family: inherit; font-size: var(--crisp-text-lg); font-weight: var(--crisp-weight-medium); line-height: 20px; color: var(--crisp-fg-primary); }
6
+ & .crisp-assigneepicker-search-input::placeholder { color: var(--crisp-fg-muted); }
7
+ & .crisp-assigneepicker-pill { display: flex; padding: 0 var(--crisp-space-3) var(--crisp-space-2); }
8
+ & .crisp-assigneepicker-remove { display: inline-flex; align-items: center; justify-content: center; flex-shrink: 0; width: 14px; height: 14px; margin: 0; padding: 0; border: none; background: none; color: inherit; font-family: inherit; cursor: pointer; }
9
+ & .crisp-assigneepicker-empty { padding: var(--crisp-space-4) var(--crisp-space-3); text-align: center; font-size: var(--crisp-text-lg); font-weight: var(--crisp-weight-medium); color: color-mix(in srgb, var(--crisp-fg-primary) 63%, transparent); }
10
+ & .crisp-assigneepicker-list { display: flex; flex-direction: column; margin: 0; padding: var(--crisp-space-1); list-style: none; }
11
+ & .crisp-assigneepicker-option { display: flex; align-items: center; width: 100%; box-sizing: border-box; height: 32px; padding: 0 var(--crisp-space-2); border: none; border-radius: var(--crisp-radius-md); background: none; font-family: inherit; text-align: start; cursor: pointer; }
12
+ & .crisp-assigneepicker-option:hover, & .crisp-assigneepicker-option[data-highlighted] { background: var(--crisp-bg-raised); }
13
+ }
@@ -0,0 +1,7 @@
1
+ import type { RecipeFn } from "../core/types";
2
+
3
+ export interface AssigneepickerVariants {
4
+
5
+ }
6
+
7
+ export declare const assigneepickerRecipe: RecipeFn<{ }>;
@@ -0,0 +1,15 @@
1
+ @layer crisp-components {
2
+ .crisp-assigneepicker {
3
+ display: flex; flex-direction: column; box-sizing: border-box; width: 350px; font-family: var(--crisp-font-sans);
4
+ & .crisp-assigneepicker-search { display: flex; align-items: center; gap: var(--crisp-space-2); height: 36px; padding: 0 var(--crisp-space-3); color: var(--crisp-fg-muted); }
5
+ & .crisp-assigneepicker-search svg { flex-shrink: 0; }
6
+ & .crisp-assigneepicker-search-input { flex: 1; min-width: 0; border: none; outline: none; background: none; font-family: inherit; font-size: var(--crisp-text-lg); font-weight: var(--crisp-weight-medium); line-height: 20px; color: var(--crisp-fg-primary); }
7
+ & .crisp-assigneepicker-search-input::placeholder { color: var(--crisp-fg-muted); }
8
+ & .crisp-assigneepicker-pill { display: flex; padding: 0 var(--crisp-space-3) var(--crisp-space-2); }
9
+ & .crisp-assigneepicker-remove { display: inline-flex; align-items: center; justify-content: center; flex-shrink: 0; width: 14px; height: 14px; margin: 0; padding: 0; border: none; background: none; color: inherit; font-family: inherit; cursor: pointer; }
10
+ & .crisp-assigneepicker-empty { padding: var(--crisp-space-4) var(--crisp-space-3); text-align: center; font-size: var(--crisp-text-lg); font-weight: var(--crisp-weight-medium); color: color-mix(in srgb, var(--crisp-fg-primary) 63%, transparent); }
11
+ & .crisp-assigneepicker-list { display: flex; flex-direction: column; margin: 0; padding: var(--crisp-space-1); list-style: none; }
12
+ & .crisp-assigneepicker-option { display: flex; align-items: center; width: 100%; box-sizing: border-box; height: 32px; padding: 0 var(--crisp-space-2); border: none; border-radius: var(--crisp-radius-md); background: none; font-family: inherit; text-align: start; cursor: pointer; }
13
+ & .crisp-assigneepicker-option:hover, & .crisp-assigneepicker-option[data-highlighted] { background: var(--crisp-bg-raised); }
14
+ }
15
+ }
package/dist/button.css CHANGED
@@ -24,6 +24,9 @@
24
24
  .crisp-button--size_sm {
25
25
  height: 24px; padding-inline: var(--crisp-space-1-5); font-size: var(--crisp-text-md); border-radius: var(--crisp-radius-md);
26
26
  }
27
+ .crisp-button--size_xs {
28
+ height: 20px; padding-inline: var(--crisp-space-1-5); font-size: var(--crisp-text-sm); line-height: 16px; letter-spacing: normal; border-radius: var(--crisp-radius-sm); gap: var(--crisp-space-1);
29
+ }
27
30
  .crisp-button--size_md {
28
31
  height: 28px; padding-inline: var(--crisp-space-2); font-size: var(--crisp-text-lg); border-radius: var(--crisp-radius-md);
29
32
  &[data-icon-start] { padding-inline-start: var(--crisp-space-1-5); }
package/dist/button.d.ts CHANGED
@@ -2,9 +2,9 @@ import type { RecipeFn } from "../core/types";
2
2
 
3
3
  export interface ButtonVariants {
4
4
  intent?: "primary" | "neutral" | "danger" | "ghost";
5
- size?: "sm" | "md" | "lg";
5
+ size?: "sm" | "xs" | "md" | "lg";
6
6
  shape?: "text" | "icon";
7
7
  touchTarget?: boolean;
8
8
  }
9
9
 
10
- export declare const buttonRecipe: RecipeFn<{ intent: { primary: string; neutral: string; danger: string; ghost: string }; size: { sm: string; md: string; lg: string }; shape: { text: string; icon: string }; touchTarget: { true: string; false: string } }>;
10
+ export declare const buttonRecipe: RecipeFn<{ intent: { primary: string; neutral: string; danger: string; ghost: string }; size: { sm: string; xs: string; md: string; lg: string }; shape: { text: string; icon: string }; touchTarget: { true: string; false: string } }>;
@@ -25,6 +25,9 @@
25
25
  .crisp-button--size_sm {
26
26
  height: 24px; padding-inline: var(--crisp-space-1-5); font-size: var(--crisp-text-md); border-radius: var(--crisp-radius-md);
27
27
  }
28
+ .crisp-button--size_xs {
29
+ height: 20px; padding-inline: var(--crisp-space-1-5); font-size: var(--crisp-text-sm); line-height: 16px; letter-spacing: normal; border-radius: var(--crisp-radius-sm); gap: var(--crisp-space-1);
30
+ }
28
31
  .crisp-button--size_md {
29
32
  height: 28px; padding-inline: var(--crisp-space-2); font-size: var(--crisp-text-lg); border-radius: var(--crisp-radius-md);
30
33
  &[data-icon-start] { padding-inline-start: var(--crisp-space-1-5); }
package/dist/chip.css CHANGED
@@ -2,7 +2,7 @@
2
2
  display: inline-flex; align-items: center; gap: 3px; height: var(--crisp-size-chip); min-width: 0; max-width: 100%; padding-inline: var(--crisp-space-1); border-radius: var(--crisp-radius-chip); font-size: var(--crisp-text-lg); line-height: 20px; font-weight: var(--crisp-weight-medium); white-space: nowrap;
3
3
  }
4
4
  .crisp-chip--truncate_true {
5
- & .crisp-chip-label { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; display: block; }
5
+ & .crisp-chip-label { min-width: 0; overflow: clip; text-overflow: ellipsis; white-space: nowrap; display: block; }
6
6
  }
7
7
  .crisp-chip--truncate_false {
8
8
 
@@ -3,7 +3,7 @@
3
3
  display: inline-flex; align-items: center; gap: 3px; height: var(--crisp-size-chip); min-width: 0; max-width: 100%; padding-inline: var(--crisp-space-1); border-radius: var(--crisp-radius-chip); font-size: var(--crisp-text-lg); line-height: 20px; font-weight: var(--crisp-weight-medium); white-space: nowrap;
4
4
  }
5
5
  .crisp-chip--truncate_true {
6
- & .crisp-chip-label { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; display: block; }
6
+ & .crisp-chip-label { min-width: 0; overflow: clip; text-overflow: ellipsis; white-space: nowrap; display: block; }
7
7
  }
8
8
  .crisp-chip--truncate_false {
9
9
 
@@ -55,7 +55,7 @@ function Breadcrumb({ items, separator, "aria-label": ariaLabelProp, className,
55
55
  return ((0, jsx_runtime_1.jsx)("nav", { "aria-label": ariaLabel, className: (0, breadcrumb_recipe_1.breadcrumbRecipe)({ className }), ...rest, children: items.map((item, i) => {
56
56
  const last = i === items.length - 1;
57
57
  const content = ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [item.icon, item.label] }));
58
- return ((0, jsx_runtime_1.jsxs)(React.Fragment, { children: [last ? ((0, jsx_runtime_1.jsx)("span", { className: "crisp-breadcrumb-item", "aria-current": "page", children: content })) : item.href ? ((0, jsx_runtime_1.jsx)("a", { className: "crisp-breadcrumb-item", href: item.href, children: content })) : ((0, jsx_runtime_1.jsx)("button", { type: "button", className: "crisp-breadcrumb-item", onClick: item.onClick, children: content })), !last && (0, jsx_runtime_1.jsx)("span", { className: "crisp-breadcrumb-sep", children: sep })] }, i));
58
+ return ((0, jsx_runtime_1.jsxs)(React.Fragment, { children: [last ? ((0, jsx_runtime_1.jsx)("span", { className: "crisp-breadcrumb-item", "aria-current": "page", children: content })) : item.href ? ((0, jsx_runtime_1.jsx)("a", { className: "crisp-breadcrumb-item", href: item.href, onClick: item.onClick, children: content })) : ((0, jsx_runtime_1.jsx)("button", { type: "button", className: "crisp-breadcrumb-item", onClick: item.onClick, children: content })), !last && (0, jsx_runtime_1.jsx)("span", { className: "crisp-breadcrumb-sep", children: sep })] }, i));
59
59
  }) }));
60
60
  }
61
61
  Breadcrumb.displayName = "Breadcrumb";
@@ -124,6 +124,39 @@ const spec = (0, core_1.defineSpec)({
124
124
  fontSize: tokens_1.contract.text.md,
125
125
  borderRadius: tokens_1.contract.radius.md,
126
126
  },
127
+ // crisp#688: Attio's own 20px small button, split out of #682 rather
128
+ // than redefining `sm` -- 72 existing `size="sm"` call sites (sidebar,
129
+ // comments, recordpage, workspacegeneralsettings, personchip,
130
+ // datatable) already rely on sm's 24px, unverified against `xs`'s own
131
+ // context. Box measured live on app.attio.com Home/Tasks ("View all",
132
+ // "Today, <date>", the date-stepper's Previous/Next-day icon
133
+ // buttons): 20px height, 6px radius, 6px inline padding on text
134
+ // buttons (a 20x20 icon-only button centers a 16px icon the same way
135
+ // shape="icon" already does for every other size, so it needs no
136
+ // extra rule), 4px gap between multiple children.
137
+ //
138
+ // Follow-up correction (still #688): the label is 12px/500(medium)/
139
+ // 16px line-height/normal tracking -- NOT #682's 14px button tracking,
140
+ // and NOT the 10px/regular/15px this file shipped with first. That
141
+ // first pass read getComputedStyle on the `<button>` element itself;
142
+ // Attio's actual label lives in an inner `<div>` that overrides
143
+ // font-size/weight/line-height on top of it (confirmed by walking the
144
+ // full child tree, on "View all" AND "Today, <date>", both Home and
145
+ // the savedviews toolbar's own "Save for everyone") -- the outer
146
+ // button's own 10px/400/15px is real but never what's on screen.
147
+ // 12px is already `c.text.sm` and 500 is already the base's own
148
+ // default `fontWeight` (no override needed for either); only
149
+ // line-height and tracking still diverge from the base's 20px/
150
+ // -0.07px and need their own values here.
151
+ xs: {
152
+ height: "20px",
153
+ paddingInline: tokens_1.contract.space["1.5"],
154
+ fontSize: tokens_1.contract.text.sm,
155
+ lineHeight: "16px",
156
+ letterSpacing: "normal",
157
+ borderRadius: tokens_1.contract.radius.sm,
158
+ gap: tokens_1.contract.space["1"],
159
+ },
127
160
  md: {
128
161
  decls: {
129
162
  height: "28px",
@@ -76,7 +76,9 @@ const spec = (0, core_1.defineSpec)({
76
76
  selector: "& .crisp-chip-label",
77
77
  decls: {
78
78
  minWidth: 0,
79
- overflow: "hidden",
79
+ // #719: `clip`, not `hidden` -- immune to a programmatic
80
+ // `scrollLeft` assignment; renders the ellipsis identically.
81
+ overflow: "clip",
80
82
  textOverflow: "ellipsis",
81
83
  whiteSpace: "nowrap",
82
84
  display: "block",
@@ -428,14 +428,17 @@ function renderTyped(col, value, avatarTone, row, display, locale = "en-US", ope
428
428
  const tags = Array.isArray(value) ? value : [];
429
429
  if (!tags.length)
430
430
  return empty;
431
- return ((0, jsx_runtime_1.jsx)("span", { className: "crisp-datatable-tags", children: tags.map((t) => ((0, jsx_runtime_1.jsx)("span", { className: "crisp-datatable-tag", "data-tone": col.tones?.[t] ?? toneFor(t), children: t }, t))) }));
431
+ return ((0, jsx_runtime_1.jsx)("span", { className: "crisp-datatable-tags", children: tags.map((t) => col.swatches?.[t] ? ((0, jsx_runtime_1.jsx)("span", { className: "crisp-datatable-swatch", "data-swatch": col.swatches[t], children: t }, t)) : ((0, jsx_runtime_1.jsx)("span", { className: "crisp-datatable-tag", "data-tone": col.tones?.[t] ?? toneFor(t), children: t }, t))) }));
432
432
  }
433
433
  case "status": {
434
434
  const s = asText(value);
435
435
  if (!s)
436
436
  return empty;
437
- // tone is looked up by the RAW status, so formatting the label (casing,
438
- // translation) can never silently repaint the chip
437
+ // tone/swatch is looked up by the RAW status, so formatting the label
438
+ // (casing, translation) can never silently repaint the chip
439
+ if (col.swatches?.[s]) {
440
+ return ((0, jsx_runtime_1.jsx)("span", { className: "crisp-datatable-swatch", "data-swatch": col.swatches[s], children: display || s }));
441
+ }
439
442
  return ((0, jsx_runtime_1.jsx)("span", { className: "crisp-datatable-tag", "data-tone": col.tones?.[s] ?? toneFor(s), children: display || s }));
440
443
  }
441
444
  case "rating": {
@@ -116,7 +116,13 @@ const config = (0, core_1.defineRecipe)({
116
116
  & .crisp-datatable-group-count { margin-inline-start: ${tokens_1.contract.space["1.5"]}; color: ${tokens_1.contract.fgMuted}; font-variant-numeric: tabular-nums; }
117
117
  & .crisp-datatable-th-rename { width: 100%; min-width: 0; height: ${tokens_1.contract.size.row}; margin: 0; padding: 0 ${tokens_1.contract.space["1"]}; border: none; border-radius: ${tokens_1.contract.radius.md}; background: ${tokens_1.contract.bgSurface}; box-shadow: inset 0 0 0 1px ${tokens_1.contract.bgBrandSolid}; font-family: inherit; font-size: ${tokens_1.contract.text.lg}; font-weight: ${tokens_1.contract.weight.medium}; color: ${tokens_1.contract.fgPrimary}; outline: none; }
118
118
  & .crisp-datatable-th-rename:focus { box-shadow: inset 0 0 0 1px ${tokens_1.contract.bgBrandSolid}; }
119
- & .crisp-datatable-th-label { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; min-width: 0; }
119
+ /* #719: overflow: clip, not hidden, on every truncation rule below --
120
+ hidden still honours a programmatic scrollLeft assignment (found via
121
+ a screenshot tool that pans anything with scrollWidth > clientWidth),
122
+ blanking or fragmenting the visible text even though real user
123
+ scrolling was already blocked. clip renders the ellipsis identically
124
+ and leaves no scroll position to move. */
125
+ & .crisp-datatable-th-label { overflow: clip; text-overflow: ellipsis; white-space: nowrap; min-width: 0; }
120
126
  /* #465: 12px per the comment above (and Attio's measured header cell
121
127
  padding: 0 12px) -- was still c.space["2"] (8px), drifted from its
122
128
  own documented value. */
@@ -203,7 +209,7 @@ const config = (0, core_1.defineRecipe)({
203
209
  /* reference picker: a leading 16px avatar per option, plus a trailing
204
210
  "Create X" row when the consumer supplies one (#449) -- same measured
205
211
  values as RecordsTable's own .crisp-recordstable-refoption*. */
206
- & .crisp-datatable-refoption { display: flex; align-items: center; gap: ${tokens_1.contract.space["2"]}; min-width: 0; overflow: hidden; text-overflow: ellipsis; }
212
+ & .crisp-datatable-refoption { display: flex; align-items: center; gap: ${tokens_1.contract.space["2"]}; min-width: 0; overflow: clip; text-overflow: ellipsis; }
207
213
  & .crisp-datatable-refoption-avatar { flex-shrink: 0; }
208
214
  & .crisp-datatable-refoption--create { color: ${tokens_1.contract.fgMuted}; }
209
215
  & .crisp-datatable-empty { padding: ${tokens_1.contract.space["8"]} ${tokens_1.contract.space["3"]}; text-align: center; color: ${tokens_1.contract.fgMuted}; font-size: ${tokens_1.contract.text.lg}; }
@@ -289,7 +295,7 @@ const config = (0, core_1.defineRecipe)({
289
295
  (#833/#835/#848) that happened to apply the same 500 to every
290
296
  primary-column body cell regardless of type. Made explicit here so
291
297
  fixing that leak doesn't also (wrongly) drop entity names to 400. */
292
- & .crisp-datatable-entity-name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-weight: ${tokens_1.contract.weight.medium}; text-decoration: underline; text-decoration-color: color-mix(in srgb, ${tokens_1.contract.fgPrimary} 9%, transparent); }
298
+ & .crisp-datatable-entity-name { overflow: clip; text-overflow: ellipsis; white-space: nowrap; font-weight: ${tokens_1.contract.weight.medium}; text-decoration: underline; text-decoration-color: color-mix(in srgb, ${tokens_1.contract.fgPrimary} 9%, transparent); }
293
299
  /* #893: shared by the button (onEntityClick, a different record) and
294
300
  the anchor (href, the row's own record) -- was button-only, which
295
301
  silently left a real <a> unstyled the day #893 added one. */
@@ -312,9 +318,24 @@ const config = (0, core_1.defineRecipe)({
312
318
  Avatar's own already-shipped shape="rounded" value, not a new number.
313
319
  shape="circle" reuses Avatar's own shape="circle" value (radius.full)
314
320
  the same way, measured live on a real Attio person avatar. */
315
- & .crisp-datatable-avatar { width: ${tokens_1.contract.size.icon}; height: ${tokens_1.contract.size.icon}; border-radius: 30%; flex-shrink: 0; display: inline-flex; align-items: center; justify-content: center; font-size: 10px; line-height: 1; font-weight: ${tokens_1.contract.weight.semibold}; text-transform: uppercase; color: ${tokens_1.contract.fgOnBrand}; background: ${tokens_1.contract.bgBrandSolid}; }
321
+ & .crisp-datatable-avatar { width: ${tokens_1.contract.size.icon}; height: ${tokens_1.contract.size.icon}; border-radius: 30%; flex-shrink: 0; display: inline-flex; align-items: center; justify-content: center; font-size: 10px; line-height: 1; font-weight: ${tokens_1.contract.weight.semibold}; text-transform: uppercase; color: ${tokens_1.contract.fgOnTone}; background: ${tokens_1.contract.bgBrandSolid}; }
316
322
  & .crisp-datatable-avatar[data-shape="circle"] { border-radius: ${tokens_1.contract.radius.full}; }
317
- & .crisp-datatable-avatar[data-tone="success"] { background: ${tokens_1.contract.bgSuccess}; } & .crisp-datatable-avatar[data-tone="warning"] { background: ${tokens_1.contract.bgWarning}; } & .crisp-datatable-avatar[data-tone="danger"] { background: ${tokens_1.contract.bgDanger}; } & .crisp-datatable-avatar[data-tone="purple"] { background: ${tokens_1.contract.bgPurple}; } & .crisp-datatable-avatar[data-tone="cyan"] { background: ${tokens_1.contract.bgCyan}; } & .crisp-datatable-avatar[data-tone="orange"] { background: ${tokens_1.contract.bgOrange}; }
323
+ /* your-moon/crisp#891: every data-tone override painted ONLY a
324
+ background, inheriting the base rule's white fgOnBrand -- white
325
+ clears AA against none of these seven fills (brand 3.23:1, success
326
+ 3.96, warning 4.12, danger 3.76, purple 3.58, cyan 2.30, orange
327
+ 2.77). fgOnTone (ink, #101112 -- semantic.ts's own comment: "dark ink
328
+ clears AA on every tone in both themes") clears 4.5:1+ on every fill
329
+ here with a comfortable margin (brand 5.85, success 4.77/6.29,
330
+ danger 5.03/6.19, purple 5.27, cyan 8.23, orange 6.83, light/dark),
331
+ so the base rule's colour moves there and every override keeps it --
332
+ EXCEPT warning, whose raw fill (bgWarning) only clears 4.59:1 in
333
+ light, too thin a margin to rely on: it takes bgWarningSolid instead,
334
+ the same solid-warning-plus-ink pair Badge's own solid variant
335
+ already established (13.98/7.83), for the same reason (see
336
+ tokens/palette.ts's amberSolid comment). No background changes
337
+ otherwise -- the fills themselves are unaffected, only the text. */
338
+ & .crisp-datatable-avatar[data-tone="success"] { background: ${tokens_1.contract.bgSuccess}; color: ${tokens_1.contract.fgOnTone}; } & .crisp-datatable-avatar[data-tone="warning"] { background: ${tokens_1.contract.bgWarningSolid}; color: ${tokens_1.contract.fgOnTone}; } & .crisp-datatable-avatar[data-tone="danger"] { background: ${tokens_1.contract.bgDanger}; color: ${tokens_1.contract.fgOnTone}; } & .crisp-datatable-avatar[data-tone="purple"] { background: ${tokens_1.contract.bgPurple}; color: ${tokens_1.contract.fgOnTone}; } & .crisp-datatable-avatar[data-tone="cyan"] { background: ${tokens_1.contract.bgCyan}; color: ${tokens_1.contract.fgOnTone}; } & .crisp-datatable-avatar[data-tone="orange"] { background: ${tokens_1.contract.bgOrange}; color: ${tokens_1.contract.fgOnTone}; }
318
339
  & .crisp-datatable-avatar img { width: 100%; height: 100%; border-radius: inherit; object-fit: cover; }
319
340
  & .crisp-datatable-tags { display: flex; align-items: center; gap: ${tokens_1.contract.space["1"]}; overflow: hidden; }
320
341
  & .crisp-datatable-tag { display: inline-flex; align-items: center; height: ${tokens_1.contract.size.chip}; padding: 0 ${tokens_1.contract.space["1"]}; border-radius: ${tokens_1.contract.radius.chip}; font-size: ${tokens_1.contract.text.lg}; line-height: 20px; font-weight: ${tokens_1.contract.weight.medium}; white-space: nowrap; background: color-mix(in srgb, ${tokens_1.contract.fgPrimary} 6%, transparent); color: color-mix(in srgb, ${tokens_1.contract.fgPrimary} 82%, transparent); }
@@ -325,6 +346,24 @@ const config = (0, core_1.defineRecipe)({
325
346
  & .crisp-datatable-tag[data-tone="purple"] { background: color-mix(in srgb, ${tokens_1.contract.bgPurple} 16%, transparent); color: color-mix(in srgb, ${tokens_1.contract.bgPurple} 80%, ${tokens_1.contract.fgPrimary}); }
326
347
  & .crisp-datatable-tag[data-tone="cyan"] { background: color-mix(in srgb, ${tokens_1.contract.bgCyan} 18%, transparent); color: color-mix(in srgb, ${tokens_1.contract.bgCyan} 82%, ${tokens_1.contract.fgPrimary}); }
327
348
  & .crisp-datatable-tag[data-tone="orange"] { background: color-mix(in srgb, ${tokens_1.contract.bgOrange} 16%, transparent); color: color-mix(in srgb, ${tokens_1.contract.bgOrange} 82%, ${tokens_1.contract.fgPrimary}); }
349
+ /* your-moon/crisp#998: Attio's own 12-colour tag palette (measured
350
+ live, see tokens/palette.ts's tagSwatch1 comment) -- flat black
351
+ text, no ring, unlike the tone family above. 7px radius/10px text
352
+ are measured literals (no matching step in the shared scale, same
353
+ as Select's own .crisp-select-swatch). */
354
+ & .crisp-datatable-swatch { display: inline-flex; align-items: center; height: ${tokens_1.contract.size.chip}; padding: 0 ${tokens_1.contract.space["1"]}; border-radius: 7px; font-size: 10px; font-weight: ${tokens_1.contract.weight.regular}; white-space: nowrap; color: ${tokens_1.contract.fgTagSwatch}; }
355
+ & .crisp-datatable-swatch[data-swatch="1"] { background: ${tokens_1.contract.bgTagSwatch1}; }
356
+ & .crisp-datatable-swatch[data-swatch="2"] { background: ${tokens_1.contract.bgTagSwatch2}; }
357
+ & .crisp-datatable-swatch[data-swatch="3"] { background: ${tokens_1.contract.bgTagSwatch3}; }
358
+ & .crisp-datatable-swatch[data-swatch="4"] { background: ${tokens_1.contract.bgTagSwatch4}; }
359
+ & .crisp-datatable-swatch[data-swatch="5"] { background: ${tokens_1.contract.bgTagSwatch5}; }
360
+ & .crisp-datatable-swatch[data-swatch="6"] { background: ${tokens_1.contract.bgTagSwatch6}; }
361
+ & .crisp-datatable-swatch[data-swatch="7"] { background: ${tokens_1.contract.bgTagSwatch7}; }
362
+ & .crisp-datatable-swatch[data-swatch="8"] { background: ${tokens_1.contract.bgTagSwatch8}; }
363
+ & .crisp-datatable-swatch[data-swatch="9"] { background: ${tokens_1.contract.bgTagSwatch9}; }
364
+ & .crisp-datatable-swatch[data-swatch="10"] { background: ${tokens_1.contract.bgTagSwatch10}; }
365
+ & .crisp-datatable-swatch[data-swatch="11"] { background: ${tokens_1.contract.bgTagSwatch11}; }
366
+ & .crisp-datatable-swatch[data-swatch="12"] { background: ${tokens_1.contract.bgTagSwatch12}; }
328
367
  & .crisp-datatable-num { font-variant-numeric: tabular-nums; }
329
368
  & .crisp-datatable-muted { color: ${tokens_1.contract.fgMuted}; }
330
369
  /* crisp#822 -- a date/datetime cell's danger/overdue tone: same token as
@@ -335,7 +374,7 @@ const config = (0, core_1.defineRecipe)({
335
374
  own .crisp-recordstable-link, which this mirrors: no underline at
336
375
  rest, underline on hover, own-colour text rather than a browser-blue
337
376
  link. */
338
- & .crisp-datatable-link { color: ${tokens_1.contract.fgPrimary}; text-decoration: none; overflow: hidden; text-overflow: ellipsis; }
377
+ & .crisp-datatable-link { color: ${tokens_1.contract.fgPrimary}; text-decoration: none; overflow: clip; text-overflow: ellipsis; }
339
378
  & .crisp-datatable-link:hover { text-decoration: underline; }
340
379
  /* phone cell type/editor (#765) -- measured live: the dial-code prefix
341
380
  is a muted rgba(0,0,0,0.63) at 14px, matching every other muted-text
@@ -58,7 +58,7 @@ const fromDate = (d) => ({
58
58
  * popover. This is what `DatePicker` used to be; `DatePicker` is now
59
59
  * seed-design's bare calendar, and this composes it.
60
60
  */
61
- function DatePickerField({ value, defaultValue, onValueChange, onChange, placeholder: placeholderProp, variant = "default", format = defaultFormat, clearable = true, clearLabel: clearLabelProp, presets, calendarProps, "aria-label": ariaLabel, className, ...rest }) {
61
+ function DatePickerField({ value, defaultValue, onValueChange, onChange, placeholder: placeholderProp, variant = "default", width = "full", format = defaultFormat, clearable = true, clearLabel: clearLabelProp, presets, calendarProps, "aria-label": ariaLabel, className, ...rest }) {
62
62
  // crisp/#471: same resolution order as Select's own.
63
63
  const placeholder = (0, core_1.useCrispMessage)(placeholderProp, "Set date…");
64
64
  const clearLabel = (0, core_1.useCrispMessage)(clearLabelProp, "Clear date");
@@ -79,7 +79,7 @@ function DatePickerField({ value, defaultValue, onValueChange, onChange, placeho
79
79
  onValueChange?.(null);
80
80
  onChange?.(null);
81
81
  };
82
- return ((0, jsx_runtime_1.jsx)("div", { className: (0, datepickerfield_recipe_1.datepickerfieldRecipe)({ className }), ...rest, children: (0, jsx_runtime_1.jsxs)("div", { className: "crisp-datepickerfield-box", "data-ghost": variant === "ghost" ? "" : undefined, "data-placeholder": selected ? undefined : "", children: [(0, jsx_runtime_1.jsx)(popover_1.Popover, { open: open, onOpenChange: setOpen, "aria-label": ariaLabel, trigger: (0, jsx_runtime_1.jsxs)("button", { type: "button", className: "crisp-datepickerfield-trigger", "aria-label": ariaLabel, children: [(0, jsx_runtime_1.jsx)(lucide_react_1.CalendarDays, { size: 15, strokeWidth: 1.75, "aria-hidden": "true" }), selected ? format(selected) : placeholder] }), children: (0, jsx_runtime_1.jsxs)("div", { className: "crisp-datepickerfield-panel", children: [(0, jsx_runtime_1.jsx)(datepicker_1.DatePicker
82
+ return ((0, jsx_runtime_1.jsx)("div", { className: (0, datepickerfield_recipe_1.datepickerfieldRecipe)({ width, className }), ...rest, children: (0, jsx_runtime_1.jsxs)("div", { className: "crisp-datepickerfield-box", "data-ghost": variant === "ghost" ? "" : undefined, "data-placeholder": selected ? undefined : "", children: [(0, jsx_runtime_1.jsx)(popover_1.Popover, { open: open, onOpenChange: setOpen, "aria-label": ariaLabel, trigger: (0, jsx_runtime_1.jsxs)("button", { type: "button", className: "crisp-datepickerfield-trigger", "aria-label": ariaLabel, children: [(0, jsx_runtime_1.jsx)(lucide_react_1.CalendarDays, { size: 15, strokeWidth: 1.75, "aria-hidden": "true" }), selected ? format(selected) : placeholder] }), children: (0, jsx_runtime_1.jsxs)("div", { className: "crisp-datepickerfield-panel", children: [(0, jsx_runtime_1.jsx)(datepicker_1.DatePicker
83
83
  // DatePickerProps is a union over selectionMode, and TypeScript
84
84
  // cannot narrow it through a spread: it tries every branch and
85
85
  // reports the range one as missing props. The field is always
@@ -13,7 +13,7 @@ const config = (0, core_1.defineRecipe)({
13
13
  display: inline-flex; font-family: ${tokens_1.contract.font.sans};
14
14
 
15
15
  & .crisp-datepickerfield-box {
16
- display: inline-flex; align-items: center;
16
+ display: inline-flex; align-items: center; width: 100%;
17
17
  box-sizing: border-box; height: 32px; margin: 0;
18
18
  border-radius: ${tokens_1.contract.radius.md}; background: transparent;
19
19
  box-shadow: inset 0 0 0 1px color-mix(in srgb, ${tokens_1.contract.fgPrimary} 7%, transparent);
@@ -22,6 +22,7 @@ const config = (0, core_1.defineRecipe)({
22
22
  & .crisp-datepickerfield-box[data-ghost] { background: transparent; box-shadow: none; }
23
23
  & .crisp-datepickerfield-box[data-ghost]:hover { background: color-mix(in srgb, ${tokens_1.contract.fgPrimary} 4%, transparent); }
24
24
  & .crisp-datepickerfield-trigger {
25
+ flex: 1; min-width: 0;
25
26
  display: inline-flex; align-items: center; gap: ${tokens_1.contract.space["2"]};
26
27
  height: 100%; padding: 0 ${tokens_1.contract.space["2.5"]}; margin: 0;
27
28
  border: none; background: none; color: inherit; font-family: inherit;
@@ -51,8 +52,20 @@ const config = (0, core_1.defineRecipe)({
51
52
  }
52
53
  & .crisp-datepickerfield-preset:hover { background: color-mix(in srgb, ${tokens_1.contract.fgPrimary} 8%, transparent); }
53
54
  `,
54
- variants: {},
55
- defaultVariants: {},
55
+ variants: {
56
+ // crisp#980: full (default) fills its container, matching Select's own
57
+ // width variant/default -- so a form composing DatePickerField alongside
58
+ // Input/Select via FormField gets one consistent column width across
59
+ // every field type, not just some of them. auto sizes to content, the
60
+ // behaviour every consumer got unconditionally before this.
61
+ width: {
62
+ full: `width: 100%;`,
63
+ auto: `width: auto;`,
64
+ },
65
+ },
66
+ defaultVariants: {
67
+ width: "full",
68
+ },
56
69
  compoundVariants: [],
57
70
  });
58
71
  exports.datepickerfieldRecipe = Object.assign((0, core_1.resolveRecipe)(config), {
@@ -33,8 +33,8 @@ var __importStar = (this && this.__importStar) || (function () {
33
33
  };
34
34
  })();
35
35
  Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.MenuItem = void 0;
36
37
  exports.Menu = Menu;
37
- exports.MenuItem = MenuItem;
38
38
  exports.MenuCheckboxItem = MenuCheckboxItem;
39
39
  exports.MenuSeparator = MenuSeparator;
40
40
  exports.MenuLabel = MenuLabel;
@@ -52,7 +52,7 @@ const MenuContext = React.createContext(null);
52
52
  * typeahead, Enter/Space select, Esc closes and returns focus to the trigger);
53
53
  * closes on outside click. Flips up / right-aligns when space is tight.
54
54
  */
55
- function Menu({ trigger, children, align = "start", open: openProp, defaultOpen, onOpenChange, className, contentClassName, "aria-label": ariaLabel, }) {
55
+ function Menu({ trigger, children, align = "start", placement = "menu", open: openProp, defaultOpen, onOpenChange, className, contentClassName, "aria-label": ariaLabel, }) {
56
56
  // The surface and the button that opens it were two unconnected nodes: the
57
57
  // menu/dialog had no id, so nothing could point at it, and no accessible
58
58
  // name, so a screen reader announced a bare "menu"/"dialog". Labelling the
@@ -68,6 +68,7 @@ function Menu({ trigger, children, align = "start", open: openProp, defaultOpen,
68
68
  onOpenChange?.(next);
69
69
  }, [openProp, onOpenChange]);
70
70
  const [up, setUp] = React.useState(false);
71
+ const [submenuAlign, setSubmenuAlign] = React.useState("start");
71
72
  const [pos, setPos] = React.useState({});
72
73
  const rootRef = React.useRef(null);
73
74
  const triggerRef = React.useRef(null);
@@ -79,6 +80,38 @@ function Menu({ trigger, children, align = "start", open: openProp, defaultOpen,
79
80
  const rect = triggerRef.current?.getBoundingClientRect();
80
81
  if (!rect)
81
82
  return;
83
+ if (placement === "submenu") {
84
+ // The content div is already in the DOM by this point (it's part of
85
+ // the same render this effect runs after, just not yet painted) so its
86
+ // real rendered size/padding are measurable, not guessed.
87
+ const content = contentRef.current;
88
+ const size = content?.getBoundingClientRect() ?? {
89
+ width: 200,
90
+ height: 0,
91
+ };
92
+ const padTop = content
93
+ ? Number.parseFloat(getComputedStyle(content).paddingTop) || 0
94
+ : 0;
95
+ const { style, align: side, up: flipUp, } = (0, core_1.placeSubmenu)({
96
+ // Not a `{...rect}` spread: DOMRect's numeric fields are accessors
97
+ // on its prototype, not own enumerable properties, so a spread
98
+ // silently drops everything except the two overridden below.
99
+ rect: {
100
+ left: rect.left,
101
+ right: rect.right,
102
+ top: rect.top - padTop,
103
+ bottom: rect.bottom - padTop,
104
+ },
105
+ size,
106
+ // Measured flush against the item, no gap (crisp#616).
107
+ gap: 0,
108
+ anchor: triggerRef.current,
109
+ });
110
+ setSubmenuAlign(side);
111
+ setUp(flipUp);
112
+ setPos(style);
113
+ return;
114
+ }
82
115
  const { style, up: flipUp } = (0, core_1.placeFloating)({
83
116
  rect,
84
117
  align,
@@ -144,12 +177,28 @@ function Menu({ trigger, children, align = "start", open: openProp, defaultOpen,
144
177
  document.addEventListener("keydown", onKey);
145
178
  return () => document.removeEventListener("keydown", onKey);
146
179
  }, [open, close]);
147
- const { onKeyDown: onContentKeyDown } = (0, menu_keyboard_1.useMenuKeyboard)({
180
+ const { onKeyDown: onContentKeyDown, items } = (0, menu_keyboard_1.useMenuKeyboard)({
148
181
  open,
149
182
  close,
150
183
  dismiss: () => setOpen(false),
151
184
  contentRef,
152
185
  });
186
+ // Roving tabindex (crisp#964): every item hardcodes tabIndex={-1} for the
187
+ // arrow-key model above, which means none of them is ever a real Tab stop
188
+ // on its own -- kept in sync with wherever focus actually lands, same as
189
+ // MenuSheet's identical fix (#966).
190
+ const onContentFocus = React.useCallback((e) => {
191
+ for (const el of items())
192
+ el.tabIndex = -1;
193
+ e.target.tabIndex = 0;
194
+ }, [items]);
195
+ React.useEffect(() => {
196
+ if (!open)
197
+ return;
198
+ const [first] = items();
199
+ if (first)
200
+ first.tabIndex = 0;
201
+ }, [open, items]);
153
202
  const triggerEl = React.cloneElement(trigger, {
154
203
  ref: triggerRef,
155
204
  id: triggerId,
@@ -165,18 +214,25 @@ function Menu({ trigger, children, align = "start", open: openProp, defaultOpen,
165
214
  });
166
215
  return ((0, jsx_runtime_1.jsxs)("div", { ref: rootRef, className: (0, menu_recipe_1.menuRecipe)({ className }), children: [triggerEl, open && ((0, jsx_runtime_1.jsx)(core_1.FloatingPortal, { sourceRef: rootRef, children: (0, jsx_runtime_1.jsx)("div", { ref: contentRef, className: contentClassName
167
216
  ? `crisp-menu-content ${contentClassName}`
168
- : "crisp-menu-content", role: "menu", id: surfaceId, "aria-label": ariaLabel, "aria-labelledby": ariaLabel ? undefined : triggerId, "data-align": align, "data-up": up ? "" : undefined, style: pos, onKeyDown: onContentKeyDown, children: (0, jsx_runtime_1.jsx)(MenuContext.Provider, { value: { close }, children: children }) }) }))] }));
217
+ : "crisp-menu-content", role: "menu", id: surfaceId, "aria-label": ariaLabel, "aria-labelledby": ariaLabel ? undefined : triggerId, "data-align": placement === "submenu" ? submenuAlign : align, "data-up": up ? "" : undefined, style: pos, onKeyDown: onContentKeyDown, onFocus: onContentFocus, children: (0, jsx_runtime_1.jsx)(MenuContext.Provider, { value: { close }, children: children }) }) }))] }));
169
218
  }
170
219
  Menu.displayName = "Menu";
171
- function MenuItem({ children, icon, shortcut, count, danger, disabled, onSelect, closeOnSelect = true, }) {
220
+ /**
221
+ * A row inside a `Menu`. Forwards its ref and spreads unrecognised button
222
+ * props (crisp#616) so it can double as another `Menu`'s own `trigger` —
223
+ * `Menu` clones its trigger with `ref`/`onClick`/`aria-*`, which a plain
224
+ * function component would silently drop.
225
+ */
226
+ exports.MenuItem = React.forwardRef(function MenuItem({ children, icon, shortcut, count, trailingIcon, danger, disabled, onSelect, closeOnSelect = true, onClick, ...rest }, ref) {
172
227
  const ctx = React.useContext(MenuContext);
173
- return ((0, jsx_runtime_1.jsxs)("button", { type: "button", role: "menuitem", tabIndex: -1, disabled: disabled, "data-disabled": disabled ? "" : undefined, className: `crisp-menu-item${danger ? " crisp-menu-item--danger" : ""}`, onClick: () => {
228
+ return ((0, jsx_runtime_1.jsxs)("button", { ref: ref, type: "button", role: "menuitem", tabIndex: -1, disabled: disabled, "data-disabled": disabled ? "" : undefined, className: `crisp-menu-item${danger ? " crisp-menu-item--danger" : ""}`, onClick: (e) => {
229
+ onClick?.(e);
174
230
  onSelect?.();
175
231
  if (closeOnSelect)
176
232
  ctx?.close();
177
- }, children: [icon, (0, jsx_runtime_1.jsx)("span", { className: "crisp-menu-item-label", children: children }), count != null && (0, jsx_runtime_1.jsx)("span", { className: "crisp-menu-item-count", children: count }), shortcut && (0, jsx_runtime_1.jsx)("span", { className: "crisp-menu-item-shortcut", children: shortcut })] }));
178
- }
179
- MenuItem.displayName = "MenuItem";
233
+ }, ...rest, children: [icon, (0, jsx_runtime_1.jsx)("span", { className: "crisp-menu-item-label", children: children }), count != null && ((0, jsx_runtime_1.jsx)("span", { className: "crisp-menu-item-count", children: count })), shortcut && ((0, jsx_runtime_1.jsx)("span", { className: "crisp-menu-item-shortcut", children: shortcut })), trailingIcon] }));
234
+ });
235
+ exports.MenuItem.displayName = "MenuItem";
180
236
  function MenuCheckboxItem({ children, checked, onCheckedChange, disabled, }) {
181
237
  return ((0, jsx_runtime_1.jsxs)("button", { type: "button", role: "menuitemcheckbox", "aria-checked": checked, tabIndex: -1, disabled: disabled, "data-disabled": disabled ? "" : undefined, className: "crisp-menu-item", onClick: () => onCheckedChange?.(!checked), children: [checked ? ((0, jsx_runtime_1.jsx)(lucide_react_1.Check, { className: "crisp-menu-item-check", size: 15, strokeWidth: 2 })) : ((0, jsx_runtime_1.jsx)("span", { className: "crisp-menu-item-check--placeholder" })), (0, jsx_runtime_1.jsx)("span", { className: "crisp-menu-item-label", children: children })] }));
182
238
  }
@@ -57,6 +57,9 @@ function toneFor(t) {
57
57
  return TONES[h % TONES.length];
58
58
  }
59
59
  function Chip({ opt }) {
60
+ if (opt.swatch) {
61
+ return ((0, jsx_runtime_1.jsx)("span", { className: "crisp-multiselect-swatch", "data-swatch": opt.swatch, children: opt.label }));
62
+ }
60
63
  return ((0, jsx_runtime_1.jsx)("span", { className: "crisp-recordstable-tag", "data-tone": opt.tone ?? toneFor(opt.value), children: opt.label }));
61
64
  }
62
65
  /**
@@ -107,6 +107,24 @@ const config = (0, core_1.defineRecipe)({
107
107
  .crisp-recordstable-tag[data-tone="purple"] { background: color-mix(in srgb, ${tokens_1.contract.bgPurple} 16%, transparent); color: color-mix(in srgb, ${tokens_1.contract.bgPurple} 80%, ${tokens_1.contract.fgPrimary}); box-shadow: inset 0 0 0 1px color-mix(in srgb, ${tokens_1.contract.bgPurple} 30%, transparent); }
108
108
  .crisp-recordstable-tag[data-tone="cyan"] { background: color-mix(in srgb, ${tokens_1.contract.bgCyan} 18%, transparent); color: color-mix(in srgb, ${tokens_1.contract.bgCyan} 82%, ${tokens_1.contract.fgPrimary}); box-shadow: inset 0 0 0 1px color-mix(in srgb, ${tokens_1.contract.bgCyan} 30%, transparent); }
109
109
  .crisp-recordstable-tag[data-tone="orange"] { background: color-mix(in srgb, ${tokens_1.contract.bgOrange} 16%, transparent); color: color-mix(in srgb, ${tokens_1.contract.bgOrange} 82%, ${tokens_1.contract.fgPrimary}); box-shadow: inset 0 0 0 1px color-mix(in srgb, ${tokens_1.contract.bgOrange} 30%, transparent); }
110
+ /* your-moon/crisp#998: Attio's own 12-colour tag palette (measured live,
111
+ see tokens/palette.ts's tagSwatch1 comment) -- flat black text, no
112
+ ring/border, unlike the tone family above. 7px radius/10px text are
113
+ measured literals, no matching step in the shared scale (same as
114
+ Select's own .crisp-select-swatch). */
115
+ .crisp-multiselect-swatch { display: inline-flex; align-items: center; height: ${tokens_1.contract.size.chip}; padding: 0 ${tokens_1.contract.space["1"]}; border-radius: 7px; font-size: 10px; font-weight: ${tokens_1.contract.weight.regular}; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; color: ${tokens_1.contract.fgTagSwatch}; }
116
+ .crisp-multiselect-swatch[data-swatch="1"] { background: ${tokens_1.contract.bgTagSwatch1}; }
117
+ .crisp-multiselect-swatch[data-swatch="2"] { background: ${tokens_1.contract.bgTagSwatch2}; }
118
+ .crisp-multiselect-swatch[data-swatch="3"] { background: ${tokens_1.contract.bgTagSwatch3}; }
119
+ .crisp-multiselect-swatch[data-swatch="4"] { background: ${tokens_1.contract.bgTagSwatch4}; }
120
+ .crisp-multiselect-swatch[data-swatch="5"] { background: ${tokens_1.contract.bgTagSwatch5}; }
121
+ .crisp-multiselect-swatch[data-swatch="6"] { background: ${tokens_1.contract.bgTagSwatch6}; }
122
+ .crisp-multiselect-swatch[data-swatch="7"] { background: ${tokens_1.contract.bgTagSwatch7}; }
123
+ .crisp-multiselect-swatch[data-swatch="8"] { background: ${tokens_1.contract.bgTagSwatch8}; }
124
+ .crisp-multiselect-swatch[data-swatch="9"] { background: ${tokens_1.contract.bgTagSwatch9}; }
125
+ .crisp-multiselect-swatch[data-swatch="10"] { background: ${tokens_1.contract.bgTagSwatch10}; }
126
+ .crisp-multiselect-swatch[data-swatch="11"] { background: ${tokens_1.contract.bgTagSwatch11}; }
127
+ .crisp-multiselect-swatch[data-swatch="12"] { background: ${tokens_1.contract.bgTagSwatch12}; }
110
128
  `,
111
129
  variants: {
112
130
  // Owner's follow-up after #867/#879/#882 (crisp/#889): SIZE is Attio's
@@ -217,7 +217,7 @@ function Select({ options, value, defaultValue, onValueChange, name, required, p
217
217
  return ((0, jsx_runtime_1.jsxs)("div", { ref: rootRef, className: (0, select_recipe_1.selectRecipe)({ variant, width, size, invalid, className }), ...rest, children: [name && (selected || required) && ((0, jsx_runtime_1.jsx)("input", { className: "crisp-formvalue", style: core_1.formValueStyle, name: name, value: selected ?? "", required: required, tabIndex: -1, "aria-hidden": "true",
218
218
  // controlled by the listbox above; `readOnly` would bar it from
219
219
  // validation, which is the only reason this input is not hidden
220
- onChange: () => { } })), (0, jsx_runtime_1.jsxs)("button", { ref: triggerRef, type: "button", id: id, role: "combobox", "aria-haspopup": "listbox", "aria-expanded": open, "aria-controls": open ? listboxId : undefined, "aria-activedescendant": searchable ? undefined : activeId, "aria-label": ariaLabel, "aria-invalid": invalid === true || undefined, "aria-readonly": readOnly ? "true" : undefined, disabled: disabled, "data-open": open ? "" : undefined, className: "crisp-select-trigger", onClick: () => (open ? setOpen(false) : openMenu()), onKeyDown: onTriggerKeyDown, children: [(0, jsx_runtime_1.jsx)("span", { className: "crisp-select-value", children: selectedOption ? ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [selectedOption.color && ((0, jsx_runtime_1.jsx)("span", { className: "crisp-select-option-dot", style: { background: selectedOption.color } })), selectedOption.triggerLabel ?? selectedOption.label] })) : ((0, jsx_runtime_1.jsx)("span", { className: "crisp-select-placeholder", children: placeholder })) }), (0, jsx_runtime_1.jsx)(lucide_react_1.ChevronDown, { className: "crisp-select-chevron", size: 16, strokeWidth: 1.75 })] }), open && ((0, jsx_runtime_1.jsx)(core_1.FloatingPortal, { sourceRef: rootRef, children: (0, jsx_runtime_1.jsxs)("div", { ref: listboxRef, id: listboxId, className: "crisp-select-listbox", role: "listbox", "aria-label": ariaLabel, "data-up": up ? "" : undefined, style: pos, children: [searchable && ((0, jsx_runtime_1.jsxs)("div", { className: "crisp-select-search", children: [(0, jsx_runtime_1.jsx)(lucide_react_1.Search, { size: 16, strokeWidth: 1.75 }), (0, jsx_runtime_1.jsx)("input", { ref: searchRef, type: "text", placeholder: searchPlaceholder, "aria-label": searchPlaceholder, "aria-controls": listboxId, "aria-activedescendant": activeId, value: query, onChange: (e) => {
220
+ onChange: () => { } })), (0, jsx_runtime_1.jsxs)("button", { ref: triggerRef, type: "button", id: id, role: "combobox", "aria-haspopup": "listbox", "aria-expanded": open, "aria-controls": open ? listboxId : undefined, "aria-activedescendant": searchable ? undefined : activeId, "aria-label": ariaLabel, "aria-invalid": invalid === true || undefined, "aria-readonly": readOnly ? "true" : undefined, disabled: disabled, "data-open": open ? "" : undefined, className: "crisp-select-trigger", onClick: () => (open ? setOpen(false) : openMenu()), onKeyDown: onTriggerKeyDown, children: [(0, jsx_runtime_1.jsx)("span", { className: "crisp-select-value", children: selectedOption ? (selectedOption.swatch ? ((0, jsx_runtime_1.jsx)("span", { className: "crisp-select-swatch", "data-swatch": selectedOption.swatch, children: selectedOption.triggerLabel ?? selectedOption.label })) : ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [selectedOption.color && ((0, jsx_runtime_1.jsx)("span", { className: "crisp-select-option-dot", style: { background: selectedOption.color } })), selectedOption.triggerLabel ?? selectedOption.label] }))) : ((0, jsx_runtime_1.jsx)("span", { className: "crisp-select-placeholder", children: placeholder })) }), (0, jsx_runtime_1.jsx)(lucide_react_1.ChevronDown, { className: "crisp-select-chevron", size: 16, strokeWidth: 1.75 })] }), open && ((0, jsx_runtime_1.jsx)(core_1.FloatingPortal, { sourceRef: rootRef, children: (0, jsx_runtime_1.jsxs)("div", { ref: listboxRef, id: listboxId, className: "crisp-select-listbox", role: "listbox", "aria-label": ariaLabel, "data-up": up ? "" : undefined, style: pos, children: [searchable && ((0, jsx_runtime_1.jsxs)("div", { className: "crisp-select-search", children: [(0, jsx_runtime_1.jsx)(lucide_react_1.Search, { size: 16, strokeWidth: 1.75 }), (0, jsx_runtime_1.jsx)("input", { ref: searchRef, type: "text", placeholder: searchPlaceholder, "aria-label": searchPlaceholder, "aria-controls": listboxId, "aria-activedescendant": activeId, value: query, onChange: (e) => {
221
221
  setQuery(e.target.value);
222
222
  setHighlight(0);
223
223
  onSearchChange?.(e.target.value);
@@ -228,7 +228,7 @@ function Select({ options, value, defaultValue, onValueChange, name, required, p
228
228
  // heading, not one each.
229
229
  const showGroupLabel = opt.group !== undefined &&
230
230
  opt.group !== filtered[i - 1]?.group;
231
- return ((0, jsx_runtime_1.jsxs)(React.Fragment, { children: [showGroupLabel && ((0, jsx_runtime_1.jsx)("div", { className: "crisp-select-label", children: opt.group })), (0, jsx_runtime_1.jsxs)("button", { type: "button", id: optionId(i), role: "option", "aria-selected": isSel, tabIndex: -1, disabled: opt.disabled, "data-disabled": opt.disabled ? "" : undefined, "data-highlighted": i === highlight ? "" : undefined, className: "crisp-select-option", onClick: () => pick(opt), onMouseEnter: () => setHighlight(i), children: [opt.color && ((0, jsx_runtime_1.jsx)("span", { className: "crisp-select-option-dot", style: { background: opt.color } })), opt.description !== undefined ? ((0, jsx_runtime_1.jsxs)("span", { className: "crisp-select-option-body", children: [(0, jsx_runtime_1.jsx)("span", { className: "crisp-select-option-label", children: opt.label }), (0, jsx_runtime_1.jsx)("span", { className: "crisp-select-option-description", children: opt.description })] })) : ((0, jsx_runtime_1.jsx)("span", { className: "crisp-select-option-label", children: opt.label })), isSel && ((0, jsx_runtime_1.jsx)(lucide_react_1.Check, { className: "crisp-select-option-check", size: 15, strokeWidth: 2 }))] })] }, opt.value));
231
+ return ((0, jsx_runtime_1.jsxs)(React.Fragment, { children: [showGroupLabel && ((0, jsx_runtime_1.jsx)("div", { className: "crisp-select-label", children: opt.group })), (0, jsx_runtime_1.jsxs)("button", { type: "button", id: optionId(i), role: "option", "aria-selected": isSel, tabIndex: -1, disabled: opt.disabled, "data-disabled": opt.disabled ? "" : undefined, "data-highlighted": i === highlight ? "" : undefined, className: "crisp-select-option", onClick: () => pick(opt), onMouseEnter: () => setHighlight(i), children: [!opt.swatch && opt.color && ((0, jsx_runtime_1.jsx)("span", { className: "crisp-select-option-dot", style: { background: opt.color } })), opt.description !== undefined ? ((0, jsx_runtime_1.jsxs)("span", { className: "crisp-select-option-body", children: [(0, jsx_runtime_1.jsx)("span", { className: "crisp-select-option-label", children: opt.swatch ? ((0, jsx_runtime_1.jsx)("span", { className: "crisp-select-swatch", "data-swatch": opt.swatch, children: opt.label })) : (opt.label) }), (0, jsx_runtime_1.jsx)("span", { className: "crisp-select-option-description", children: opt.description })] })) : ((0, jsx_runtime_1.jsx)("span", { className: "crisp-select-option-label", children: opt.swatch ? ((0, jsx_runtime_1.jsx)("span", { className: "crisp-select-swatch", "data-swatch": opt.swatch, children: opt.label })) : (opt.label) })), isSel && ((0, jsx_runtime_1.jsx)(lucide_react_1.Check, { className: "crisp-select-option-check", size: 15, strokeWidth: 2 }))] })] }, opt.value));
232
232
  }))] }) }))] }));
233
233
  }
234
234
  Select.displayName = "Select";
@@ -137,6 +137,25 @@ const config = (0, core_1.defineRecipe)({
137
137
  .crisp-select-option[data-disabled] .crisp-select-option-description,
138
138
  .crisp-select-option[data-disabled] .crisp-select-option-check { color: ${tokens_1.contract.fgDisabled}; }
139
139
  .crisp-select-option-dot { flex-shrink: 0; width: 8px; height: 8px; border-radius: ${tokens_1.contract.radius.full}; margin-inline-end: ${tokens_1.contract.space["2"]}; }
140
+ /* your-moon/crisp#998: Attio's own 12-colour tag palette (measured live,
141
+ see tokens/palette.ts's tagSwatch1 comment) -- the label itself as a
142
+ filled pill, not a dot beside it. 7px radius/10px text have no
143
+ matching step in the shared scale (radius.xs is 4px, .sm is 6px;
144
+ text.xs is 11px), so both are the measured literals, same as
145
+ DataTable's own avatar font-size (#891). */
146
+ .crisp-select-swatch { display: inline-flex; align-items: center; height: ${tokens_1.contract.size.chip}; padding: 0 ${tokens_1.contract.space["1"]}; border-radius: 7px; font-size: 10px; font-weight: ${tokens_1.contract.weight.regular}; color: ${tokens_1.contract.fgTagSwatch}; }
147
+ .crisp-select-swatch[data-swatch="1"] { background: ${tokens_1.contract.bgTagSwatch1}; }
148
+ .crisp-select-swatch[data-swatch="2"] { background: ${tokens_1.contract.bgTagSwatch2}; }
149
+ .crisp-select-swatch[data-swatch="3"] { background: ${tokens_1.contract.bgTagSwatch3}; }
150
+ .crisp-select-swatch[data-swatch="4"] { background: ${tokens_1.contract.bgTagSwatch4}; }
151
+ .crisp-select-swatch[data-swatch="5"] { background: ${tokens_1.contract.bgTagSwatch5}; }
152
+ .crisp-select-swatch[data-swatch="6"] { background: ${tokens_1.contract.bgTagSwatch6}; }
153
+ .crisp-select-swatch[data-swatch="7"] { background: ${tokens_1.contract.bgTagSwatch7}; }
154
+ .crisp-select-swatch[data-swatch="8"] { background: ${tokens_1.contract.bgTagSwatch8}; }
155
+ .crisp-select-swatch[data-swatch="9"] { background: ${tokens_1.contract.bgTagSwatch9}; }
156
+ .crisp-select-swatch[data-swatch="10"] { background: ${tokens_1.contract.bgTagSwatch10}; }
157
+ .crisp-select-swatch[data-swatch="11"] { background: ${tokens_1.contract.bgTagSwatch11}; }
158
+ .crisp-select-swatch[data-swatch="12"] { background: ${tokens_1.contract.bgTagSwatch12}; }
140
159
  .crisp-select-option-body { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: ${tokens_1.contract.space["0.5"]}; }
141
160
  .crisp-select-option-label { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; }
142
161
  .crisp-select-option-description { min-width: 0; overflow: hidden; text-overflow: ellipsis; color: ${tokens_1.contract.fgNeutralSubtle}; }
@@ -24,7 +24,13 @@ const config = (0, core_1.defineRecipe)({
24
24
  (padding: 0 12px) and independently on a body cell's text inset
25
25
  (getBoundingClientRect delta from the cell edge). Matches
26
26
  ColumnCalc's own already-correct 12px trigger inset. */
27
- & th, & td { position: relative; height: ${tokens_1.contract.size.tableRow}; padding: 0 ${tokens_1.contract.space["3"]}; border-bottom: 1px solid ${tokens_1.contract.borderSubtle}; border-inline-end: 1px solid ${tokens_1.contract.borderSubtle}; text-align: start; vertical-align: middle; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
27
+ /* #719: overflow: clip, not hidden -- both render the ellipsis
28
+ identically, but hidden still honours a programmatic scrollLeft
29
+ assignment (a screenshot tool, a browser extension) even though it
30
+ already blocks user-driven wheel/scrollbar input; clip leaves no
31
+ scroll position for anything to move, so the truncated text can
32
+ never be panned out of view. */
33
+ & th, & td { position: relative; height: ${tokens_1.contract.size.tableRow}; padding: 0 ${tokens_1.contract.space["3"]}; border-bottom: 1px solid ${tokens_1.contract.borderSubtle}; border-inline-end: 1px solid ${tokens_1.contract.borderSubtle}; text-align: start; vertical-align: middle; white-space: nowrap; overflow: clip; text-overflow: ellipsis; }
28
34
  /* #554: sticky on the vertical axis, mirroring RecordsTable's own
29
35
  \`.crisp-recordstable-th\`. z-index 2 sits above a plain body cell (auto)
30
36
  and below a sticky-column corner cell (DataTable's \`th[data-sticky]\`,