@crisp-ui-kit/crisp 0.57.3 → 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 (169) 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 +1 -1
  5. package/dist/button.layered.css +1 -1
  6. package/dist/cjs/components/button/button.recipe.js +15 -10
  7. package/dist/cjs/components/datatable/datatable.js +6 -3
  8. package/dist/cjs/components/datatable/datatable.recipe.js +35 -2
  9. package/dist/cjs/components/menu/menu.js +48 -8
  10. package/dist/cjs/components/multiselect/multiselect.js +3 -0
  11. package/dist/cjs/components/multiselect/multiselect.recipe.js +18 -0
  12. package/dist/cjs/components/select/select.js +2 -2
  13. package/dist/cjs/components/select/select.recipe.js +19 -0
  14. package/dist/cjs/core/index.js +2 -1
  15. package/dist/cjs/core/place-floating.js +38 -0
  16. package/dist/cjs/index.js +8 -0
  17. package/dist/cjs/product/assigneepicker/assigneepicker.js +127 -0
  18. package/dist/cjs/product/assigneepicker/assigneepicker.recipe.js +36 -0
  19. package/dist/cjs/product/assigneepicker/index.js +7 -0
  20. package/dist/cjs/product/notesgallery/index.js +7 -0
  21. package/dist/cjs/product/notesgallery/notesgallery.js +80 -0
  22. package/dist/cjs/product/notesgallery/notesgallery.recipe.js +46 -0
  23. package/dist/cjs/product/objectpagemenu/index.js +17 -0
  24. package/dist/cjs/product/objectpagemenu/objectpagemenu.js +26 -0
  25. package/dist/cjs/product/objectpagemenu/objectpagemenu.recipe.js +30 -0
  26. package/dist/cjs/product/objectsettings/objectsettings.js +107 -14
  27. package/dist/cjs/product/recordpage/recordpage.js +64 -5
  28. package/dist/cjs/product/recordpage/recordpage.recipe.js +38 -0
  29. package/dist/cjs/product/recordpicker/index.js +7 -0
  30. package/dist/cjs/product/recordpicker/recordpicker.js +100 -0
  31. package/dist/cjs/product/recordpicker/recordpicker.recipe.js +36 -0
  32. package/dist/cjs/product/savedviews/index.js +2 -1
  33. package/dist/cjs/product/savedviews/savedviews.js +22 -0
  34. package/dist/cjs/product/savedviews/savedviews.recipe.js +47 -0
  35. package/dist/cjs/product/share/index.js +7 -0
  36. package/dist/cjs/product/share/share.js +179 -0
  37. package/dist/cjs/product/share/share.recipe.js +114 -0
  38. package/dist/cjs/product/shortcutssheet/index.js +17 -0
  39. package/dist/cjs/product/shortcutssheet/shortcutssheet.js +125 -0
  40. package/dist/cjs/product/shortcutssheet/shortcutssheet.recipe.js +50 -0
  41. package/dist/cjs/product/taskeditpopover/index.js +7 -0
  42. package/dist/cjs/product/taskeditpopover/taskeditpopover.js +159 -0
  43. package/dist/cjs/product/taskeditpopover/taskeditpopover.recipe.js +68 -0
  44. package/dist/cjs/product/workspaceswitchermenu/index.js +17 -0
  45. package/dist/cjs/product/workspaceswitchermenu/workspaceswitchermenu.js +38 -0
  46. package/dist/cjs/product/workspaceswitchermenu/workspaceswitchermenu.recipe.js +34 -0
  47. package/dist/cjs/tokens/palette.js +27 -0
  48. package/dist/cjs/tokens/semantic.js +20 -0
  49. package/dist/datatable.css +40 -7
  50. package/dist/datatable.layered.css +40 -7
  51. package/dist/esm/components/button/button.recipe.js +15 -10
  52. package/dist/esm/components/datatable/datatable.js +6 -3
  53. package/dist/esm/components/datatable/datatable.recipe.js +35 -2
  54. package/dist/esm/components/menu/menu.js +47 -7
  55. package/dist/esm/components/multiselect/multiselect.js +3 -0
  56. package/dist/esm/components/multiselect/multiselect.recipe.js +18 -0
  57. package/dist/esm/components/select/select.js +2 -2
  58. package/dist/esm/components/select/select.recipe.js +19 -0
  59. package/dist/esm/core/index.js +1 -1
  60. package/dist/esm/core/place-floating.js +37 -0
  61. package/dist/esm/index.js +8 -0
  62. package/dist/esm/product/assigneepicker/assigneepicker.js +91 -0
  63. package/dist/esm/product/assigneepicker/assigneepicker.recipe.js +33 -0
  64. package/dist/esm/product/assigneepicker/index.js +2 -0
  65. package/dist/esm/product/notesgallery/index.js +2 -0
  66. package/dist/esm/product/notesgallery/notesgallery.js +44 -0
  67. package/dist/esm/product/notesgallery/notesgallery.recipe.js +43 -0
  68. package/dist/esm/product/objectpagemenu/index.js +1 -0
  69. package/dist/esm/product/objectpagemenu/objectpagemenu.js +23 -0
  70. package/dist/esm/product/objectpagemenu/objectpagemenu.recipe.js +27 -0
  71. package/dist/esm/product/objectsettings/objectsettings.js +106 -13
  72. package/dist/esm/product/recordpage/recordpage.js +64 -5
  73. package/dist/esm/product/recordpage/recordpage.recipe.js +38 -0
  74. package/dist/esm/product/recordpicker/index.js +2 -0
  75. package/dist/esm/product/recordpicker/recordpicker.js +64 -0
  76. package/dist/esm/product/recordpicker/recordpicker.recipe.js +33 -0
  77. package/dist/esm/product/savedviews/index.js +1 -1
  78. package/dist/esm/product/savedviews/savedviews.js +22 -1
  79. package/dist/esm/product/savedviews/savedviews.recipe.js +47 -0
  80. package/dist/esm/product/share/index.js +2 -0
  81. package/dist/esm/product/share/share.js +143 -0
  82. package/dist/esm/product/share/share.recipe.js +111 -0
  83. package/dist/esm/product/shortcutssheet/index.js +1 -0
  84. package/dist/esm/product/shortcutssheet/shortcutssheet.js +88 -0
  85. package/dist/esm/product/shortcutssheet/shortcutssheet.recipe.js +47 -0
  86. package/dist/esm/product/taskeditpopover/index.js +2 -0
  87. package/dist/esm/product/taskeditpopover/taskeditpopover.js +123 -0
  88. package/dist/esm/product/taskeditpopover/taskeditpopover.recipe.js +65 -0
  89. package/dist/esm/product/workspaceswitchermenu/index.js +1 -0
  90. package/dist/esm/product/workspaceswitchermenu/workspaceswitchermenu.js +35 -0
  91. package/dist/esm/product/workspaceswitchermenu/workspaceswitchermenu.recipe.js +31 -0
  92. package/dist/esm/tokens/palette.js +27 -0
  93. package/dist/esm/tokens/semantic.js +20 -0
  94. package/dist/multiselect.css +18 -0
  95. package/dist/multiselect.layered.css +18 -0
  96. package/dist/native/tokens.d.ts +1 -1
  97. package/dist/native/tokens.js +56 -4
  98. package/dist/notesgallery.css +15 -0
  99. package/dist/notesgallery.d.ts +7 -0
  100. package/dist/notesgallery.layered.css +17 -0
  101. package/dist/objectpagemenu.css +3 -0
  102. package/dist/objectpagemenu.d.ts +7 -0
  103. package/dist/objectpagemenu.layered.css +5 -0
  104. package/dist/recordpage.css +36 -0
  105. package/dist/recordpage.layered.css +36 -0
  106. package/dist/recordpicker.css +13 -0
  107. package/dist/recordpicker.d.ts +7 -0
  108. package/dist/recordpicker.layered.css +15 -0
  109. package/dist/savedviews.css +47 -0
  110. package/dist/savedviews.layered.css +47 -0
  111. package/dist/select.css +19 -0
  112. package/dist/select.layered.css +19 -0
  113. package/dist/share.css +84 -0
  114. package/dist/share.d.ts +7 -0
  115. package/dist/share.layered.css +86 -0
  116. package/dist/shortcutssheet.css +24 -0
  117. package/dist/shortcutssheet.d.ts +7 -0
  118. package/dist/shortcutssheet.layered.css +26 -0
  119. package/dist/styles.css +387 -8
  120. package/dist/styles.layered.css +387 -8
  121. package/dist/taskeditpopover.css +36 -0
  122. package/dist/taskeditpopover.d.ts +7 -0
  123. package/dist/taskeditpopover.layered.css +38 -0
  124. package/dist/tokens.css +26 -0
  125. package/dist/tokens.json +56 -4
  126. package/dist/types/components/button/button.recipe.d.ts +2 -3
  127. package/dist/types/components/datatable/datatable.d.ts +8 -0
  128. package/dist/types/components/menu/menu.d.ts +50 -7
  129. package/dist/types/components/multiselect/multiselect.d.ts +9 -0
  130. package/dist/types/components/select/select.d.ts +10 -0
  131. package/dist/types/core/index.d.ts +1 -1
  132. package/dist/types/core/place-floating.d.ts +37 -0
  133. package/dist/types/index.d.ts +8 -0
  134. package/dist/types/product/assigneepicker/assigneepicker.d.ts +36 -0
  135. package/dist/types/product/assigneepicker/assigneepicker.recipe.d.ts +3 -0
  136. package/dist/types/product/assigneepicker/index.d.ts +2 -0
  137. package/dist/types/product/notesgallery/index.d.ts +2 -0
  138. package/dist/types/product/notesgallery/notesgallery.d.ts +54 -0
  139. package/dist/types/product/notesgallery/notesgallery.recipe.d.ts +3 -0
  140. package/dist/types/product/objectpagemenu/index.d.ts +1 -0
  141. package/dist/types/product/objectpagemenu/objectpagemenu.d.ts +40 -0
  142. package/dist/types/product/objectpagemenu/objectpagemenu.recipe.d.ts +17 -0
  143. package/dist/types/product/objectsettings/objectsettings.d.ts +71 -15
  144. package/dist/types/product/recordpage/index.d.ts +1 -1
  145. package/dist/types/product/recordpage/recordpage.d.ts +96 -1
  146. package/dist/types/product/recordpicker/index.d.ts +2 -0
  147. package/dist/types/product/recordpicker/recordpicker.d.ts +38 -0
  148. package/dist/types/product/recordpicker/recordpicker.recipe.d.ts +3 -0
  149. package/dist/types/product/savedviews/index.d.ts +1 -1
  150. package/dist/types/product/savedviews/savedviews.d.ts +36 -0
  151. package/dist/types/product/share/index.d.ts +2 -0
  152. package/dist/types/product/share/share.d.ts +85 -0
  153. package/dist/types/product/share/share.recipe.d.ts +3 -0
  154. package/dist/types/product/shortcutssheet/index.d.ts +1 -0
  155. package/dist/types/product/shortcutssheet/shortcutssheet.d.ts +49 -0
  156. package/dist/types/product/shortcutssheet/shortcutssheet.recipe.d.ts +3 -0
  157. package/dist/types/product/taskeditpopover/index.d.ts +2 -0
  158. package/dist/types/product/taskeditpopover/taskeditpopover.d.ts +67 -0
  159. package/dist/types/product/taskeditpopover/taskeditpopover.recipe.d.ts +3 -0
  160. package/dist/types/product/workspaceswitchermenu/index.d.ts +1 -0
  161. package/dist/types/product/workspaceswitchermenu/workspaceswitchermenu.d.ts +54 -0
  162. package/dist/types/product/workspaceswitchermenu/workspaceswitchermenu.recipe.d.ts +19 -0
  163. package/dist/types/tokens/contract.d.ts +13 -0
  164. package/dist/types/tokens/palette.d.ts +52 -0
  165. package/dist/types/tokens/semantic.d.ts +20 -0
  166. package/dist/workspaceswitchermenu.css +4 -0
  167. package/dist/workspaceswitchermenu.d.ts +7 -0
  168. package/dist/workspaceswitchermenu.layered.css +6 -0
  169. 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
@@ -25,7 +25,7 @@
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
27
  .crisp-button--size_xs {
28
- height: 20px; padding-inline: var(--crisp-space-1-5); font-size: 10px; font-weight: var(--crisp-weight-regular); line-height: 15px; letter-spacing: normal; border-radius: var(--crisp-radius-sm); gap: var(--crisp-space-1);
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
29
  }
30
30
  .crisp-button--size_md {
31
31
  height: 28px; padding-inline: var(--crisp-space-2); font-size: var(--crisp-text-lg); border-radius: var(--crisp-radius-md);
@@ -26,7 +26,7 @@
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
28
  .crisp-button--size_xs {
29
- height: 20px; padding-inline: var(--crisp-space-1-5); font-size: 10px; font-weight: var(--crisp-weight-regular); line-height: 15px; letter-spacing: normal; border-radius: var(--crisp-radius-sm); gap: var(--crisp-space-1);
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
30
  }
31
31
  .crisp-button--size_md {
32
32
  height: 28px; padding-inline: var(--crisp-space-2); font-size: var(--crisp-text-lg); border-radius: var(--crisp-radius-md);
@@ -135,19 +135,24 @@ const spec = (0, core_1.defineSpec)({
135
135
  // shape="icon" already does for every other size, so it needs no
136
136
  // extra rule), 4px gap between multiple children.
137
137
  //
138
- // The label itself is NOT 14px/#682's tracking -- getComputedStyle on
139
- // the live button (fonts loaded, DPR 2, zero zoom/transform anywhere
140
- // in the ancestor chain) reads 10px/regular/15px line-height/normal
141
- // tracking, independently matching `recordpanel.recipe.ts`'s own
142
- // "View all" measurement (10px/400) elsewhere in this codebase. 10px
143
- // sits below the text scale's 11px floor, so it's a literal here, the
144
- // same as recordpanel's.
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.
145
151
  xs: {
146
152
  height: "20px",
147
153
  paddingInline: tokens_1.contract.space["1.5"],
148
- fontSize: "10px",
149
- fontWeight: tokens_1.contract.weight.regular,
150
- lineHeight: "15px",
154
+ fontSize: tokens_1.contract.text.sm,
155
+ lineHeight: "16px",
151
156
  letterSpacing: "normal",
152
157
  borderRadius: tokens_1.contract.radius.sm,
153
158
  gap: tokens_1.contract.space["1"],
@@ -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": {
@@ -318,9 +318,24 @@ const config = (0, core_1.defineRecipe)({
318
318
  Avatar's own already-shipped shape="rounded" value, not a new number.
319
319
  shape="circle" reuses Avatar's own shape="circle" value (radius.full)
320
320
  the same way, measured live on a real Attio person avatar. */
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.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}; }
322
322
  & .crisp-datatable-avatar[data-shape="circle"] { border-radius: ${tokens_1.contract.radius.full}; }
323
- & .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}; }
324
339
  & .crisp-datatable-avatar img { width: 100%; height: 100%; border-radius: inherit; object-fit: cover; }
325
340
  & .crisp-datatable-tags { display: flex; align-items: center; gap: ${tokens_1.contract.space["1"]}; overflow: hidden; }
326
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); }
@@ -331,6 +346,24 @@ const config = (0, core_1.defineRecipe)({
331
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}); }
332
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}); }
333
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}; }
334
367
  & .crisp-datatable-num { font-variant-numeric: tabular-nums; }
335
368
  & .crisp-datatable-muted { color: ${tokens_1.contract.fgMuted}; }
336
369
  /* crisp#822 -- a date/datetime cell's danger/overdue tone: same token as
@@ -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,
@@ -181,18 +214,25 @@ function Menu({ trigger, children, align = "start", open: openProp, defaultOpen,
181
214
  });
182
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
183
216
  ? `crisp-menu-content ${contentClassName}`
184
- : "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, onFocus: onContentFocus, 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 }) }) }))] }));
185
218
  }
186
219
  Menu.displayName = "Menu";
187
- 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) {
188
227
  const ctx = React.useContext(MenuContext);
189
- 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);
190
230
  onSelect?.();
191
231
  if (closeOnSelect)
192
232
  ctx?.close();
193
- }, 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 })] }));
194
- }
195
- 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";
196
236
  function MenuCheckboxItem({ children, checked, onCheckedChange, disabled, }) {
197
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 })] }));
198
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}; }
@@ -14,7 +14,7 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
14
  for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
15
  };
16
16
  Object.defineProperty(exports, "__esModule", { value: true });
17
- exports.usePrefersReducedMotion = exports.useMentionTrigger = exports.useLabelAssociation = exports.useFormReset = exports.useFocusAfterRemoval = exports.specToRecipe = exports.defineSpec = exports.resolveRecipe = exports.Primitive = exports.placeFloating = exports.matchMentionTrigger = exports.useCrispMessages = exports.useCrispMessage = exports.useCrispComponentDefaults = exports.CrispMessagesProvider = exports.formValueStyle = exports.registerFloatingLayer = exports.isInsideLaterFloatingLayer = exports.hasOpenFloatingLayer = exports.hasNestedFloatingLayer = exports.FloatingPortal = exports.isRtlElement = exports.withDescribedBy = exports.defineRecipe = exports.cx = exports.parseCellDate = exports.formatCellDate = exports.cellDatePrecision = exports.caretRectInField = void 0;
17
+ exports.usePrefersReducedMotion = exports.useMentionTrigger = exports.useLabelAssociation = exports.useFormReset = exports.useFocusAfterRemoval = exports.specToRecipe = exports.defineSpec = exports.resolveRecipe = exports.Primitive = exports.placeSubmenu = exports.placeFloating = exports.matchMentionTrigger = exports.useCrispMessages = exports.useCrispMessage = exports.useCrispComponentDefaults = exports.CrispMessagesProvider = exports.formValueStyle = exports.registerFloatingLayer = exports.isInsideLaterFloatingLayer = exports.hasOpenFloatingLayer = exports.hasNestedFloatingLayer = exports.FloatingPortal = exports.isRtlElement = exports.withDescribedBy = exports.defineRecipe = exports.cx = exports.parseCellDate = exports.formatCellDate = exports.cellDatePrecision = exports.caretRectInField = void 0;
18
18
  var caret_rect_1 = require("./caret-rect");
19
19
  Object.defineProperty(exports, "caretRectInField", { enumerable: true, get: function () { return caret_rect_1.caretRectInField; } });
20
20
  var cell_date_1 = require("./cell-date");
@@ -47,6 +47,7 @@ var mention_1 = require("./mention");
47
47
  Object.defineProperty(exports, "matchMentionTrigger", { enumerable: true, get: function () { return mention_1.matchMentionTrigger; } });
48
48
  var place_floating_1 = require("./place-floating");
49
49
  Object.defineProperty(exports, "placeFloating", { enumerable: true, get: function () { return place_floating_1.placeFloating; } });
50
+ Object.defineProperty(exports, "placeSubmenu", { enumerable: true, get: function () { return place_floating_1.placeSubmenu; } });
50
51
  var primitive_1 = require("./primitive");
51
52
  Object.defineProperty(exports, "Primitive", { enumerable: true, get: function () { return primitive_1.Primitive; } });
52
53
  var resolve_1 = require("./resolve");
@@ -1,6 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.placeFloating = placeFloating;
4
+ exports.placeSubmenu = placeSubmenu;
4
5
  const direction_1 = require("./direction");
5
6
  /**
6
7
  * Where a floating layer goes, in logical terms.
@@ -36,3 +37,40 @@ function placeFloating({ rect, align = "start", gap, flipBelow, anchor, }) {
36
37
  style.left = rect.left;
37
38
  return { style, up };
38
39
  }
40
+ /**
41
+ * Where a cascading submenu goes, BESIDE the item that opens it — not below
42
+ * it, the way `placeFloating`'s own dropdown does. Opens toward the inline
43
+ * end by default (further into the reading direction) and flips to the
44
+ * inline start when the surface's measured width doesn't fit there.
45
+ *
46
+ * Live-measured on Attio's own object-page "…" menu (crisp#616): the
47
+ * Notifications submenu sits with NO gap against the item's opposite edge
48
+ * (its own edge lines up exactly with the item's), so callers pass `gap: 0`
49
+ * for this specific cascade rather than the 4px a dropdown uses against its
50
+ * trigger.
51
+ */
52
+ function placeSubmenu({ rect, size, gap, anchor, }) {
53
+ const rtl = (0, direction_1.isRtlElement)(anchor);
54
+ // "end" here is the inline-end direction FROM the item, e.g. further
55
+ // right in LTR — the room available there is measured from whichever
56
+ // physical edge that is.
57
+ const roomTowardEnd = rtl ? rect.left : window.innerWidth - rect.right;
58
+ const opensEnd = roomTowardEnd >= size.width + gap;
59
+ const align = opensEnd ? "start" : "end";
60
+ const style = {};
61
+ // Same formula as `placeFloating`'s own `toRight`, applied to the opposite
62
+ // geometry: there `align` picks which of the TRIGGER's edges the layer's
63
+ // matching edge lines up with; here it picks which side of the ITEM the
64
+ // layer cascades toward.
65
+ const anchorRight = align === "end" ? !rtl : rtl;
66
+ if (anchorRight)
67
+ style.right = window.innerWidth - rect.left + gap;
68
+ else
69
+ style.left = rect.right + gap;
70
+ const up = rect.top + size.height > window.innerHeight;
71
+ if (up)
72
+ style.bottom = Math.max(0, window.innerHeight - rect.bottom);
73
+ else
74
+ style.top = rect.top;
75
+ return { style, align, up };
76
+ }
package/dist/cjs/index.js CHANGED
@@ -92,6 +92,7 @@ __exportStar(require("./core"), exports);
92
92
  // the generic primitives (Table, DataTable, Button, …) to build your own UI from.
93
93
  __exportStar(require("./product/activityfeed"), exports);
94
94
  __exportStar(require("./product/appshell"), exports);
95
+ __exportStar(require("./product/assigneepicker"), exports);
95
96
  __exportStar(require("./product/authshell"), exports);
96
97
  __exportStar(require("./product/board"), exports);
97
98
  __exportStar(require("./product/command"), exports);
@@ -105,21 +106,28 @@ __exportStar(require("./product/inbox"), exports);
105
106
  __exportStar(require("./product/listpage"), exports);
106
107
  __exportStar(require("./product/memberssettings"), exports);
107
108
  __exportStar(require("./product/navitem"), exports);
109
+ __exportStar(require("./product/notesgallery"), exports);
108
110
  __exportStar(require("./product/notificationssettings"), exports);
111
+ __exportStar(require("./product/objectpagemenu"), exports);
109
112
  __exportStar(require("./product/objectsettings"), exports);
110
113
  __exportStar(require("./product/onboarding"), exports);
111
114
  __exportStar(require("./product/recordpage"), exports);
112
115
  __exportStar(require("./product/recordpanel"), exports);
116
+ __exportStar(require("./product/recordpicker"), exports);
113
117
  __exportStar(require("./product/richtext"), exports);
114
118
  __exportStar(require("./product/savedviews"), exports);
115
119
  __exportStar(require("./product/settingssection"), exports);
116
120
  __exportStar(require("./product/settingsshell"), exports);
121
+ __exportStar(require("./product/share"), exports);
122
+ __exportStar(require("./product/shortcutssheet"), exports);
117
123
  __exportStar(require("./product/sidebar"), exports);
118
124
  __exportStar(require("./product/statlist"), exports);
125
+ __exportStar(require("./product/taskeditpopover"), exports);
119
126
  __exportStar(require("./product/tasklist"), exports);
120
127
  __exportStar(require("./product/themepicker"), exports);
121
128
  __exportStar(require("./product/trialbanner"), exports);
122
129
  __exportStar(require("./product/viewtoggle"), exports);
123
130
  __exportStar(require("./product/workspacegeneralsettings"), exports);
131
+ __exportStar(require("./product/workspaceswitchermenu"), exports);
124
132
  // design tokens + recipe core (for building custom components)
125
133
  __exportStar(require("./tokens"), exports);