@crisp-ui-kit/crisp 0.57.1 → 0.57.2

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 (252) hide show
  1. package/dist/authshell.css +9 -4
  2. package/dist/authshell.layered.css +9 -4
  3. package/dist/badge.css +3 -3
  4. package/dist/badge.layered.css +3 -3
  5. package/dist/board.css +28 -8
  6. package/dist/board.layered.css +28 -8
  7. package/dist/bottomsheet.css +26 -0
  8. package/dist/bottomsheet.d.ts +7 -0
  9. package/dist/bottomsheet.layered.css +28 -0
  10. package/dist/button.css +7 -6
  11. package/dist/button.layered.css +7 -6
  12. package/dist/checkbox.css +7 -3
  13. package/dist/checkbox.layered.css +7 -3
  14. package/dist/chip.css +15 -0
  15. package/dist/chip.d.ts +3 -1
  16. package/dist/chip.layered.css +15 -0
  17. package/dist/chipgroup.css +5 -0
  18. package/dist/chipgroup.d.ts +7 -0
  19. package/dist/chipgroup.layered.css +7 -0
  20. package/dist/cjs/components/badge/badge.recipe.js +22 -8
  21. package/dist/cjs/components/bottomsheet/bottomsheet.js +90 -0
  22. package/dist/cjs/components/bottomsheet/bottomsheet.recipe.js +77 -0
  23. package/dist/cjs/components/bottomsheet/index.js +9 -0
  24. package/dist/cjs/components/button/button.recipe.js +39 -15
  25. package/dist/cjs/components/checkbox/checkbox.recipe.js +61 -9
  26. package/dist/cjs/components/chip/chip.js +14 -2
  27. package/dist/cjs/components/chip/chip.recipe.js +69 -1
  28. package/dist/cjs/components/chipgroup/chipgroup.js +51 -0
  29. package/dist/cjs/components/chipgroup/chipgroup.recipe.js +36 -0
  30. package/dist/cjs/components/chipgroup/index.js +7 -0
  31. package/dist/cjs/components/datatable/datatable.js +5 -1
  32. package/dist/cjs/components/datatable/datatable.recipe.js +11 -2
  33. package/dist/cjs/components/dialog/dialog.recipe.js +21 -10
  34. package/dist/cjs/components/emptystate/emptystate.recipe.js +7 -2
  35. package/dist/cjs/components/fileupload/fileupload.js +21 -4
  36. package/dist/cjs/components/input/index.js +2 -1
  37. package/dist/cjs/components/input/input.js +31 -1
  38. package/dist/cjs/components/input/input.recipe.js +16 -9
  39. package/dist/cjs/components/menu/menu.recipe.js +21 -7
  40. package/dist/cjs/components/menusheet/index.js +9 -0
  41. package/dist/cjs/components/menusheet/menusheet.js +133 -0
  42. package/dist/cjs/components/menusheet/menusheet.recipe.js +103 -0
  43. package/dist/cjs/components/multiselect/multiselect.js +1 -1
  44. package/dist/cjs/components/multiselect/multiselect.recipe.js +33 -9
  45. package/dist/cjs/components/select/select.js +1 -1
  46. package/dist/cjs/components/select/select.recipe.js +38 -12
  47. package/dist/cjs/components/snackbar/index.js +8 -0
  48. package/dist/cjs/components/snackbar/snackbar.js +121 -0
  49. package/dist/cjs/components/snackbar/snackbar.recipe.js +71 -0
  50. package/dist/cjs/components/tabletoolbar/tabletoolbar.chrome.js +7 -2
  51. package/dist/cjs/components/tabletoolbar/tabletoolbar.js +37 -12
  52. package/dist/cjs/components/tabs/tabs.js +10 -4
  53. package/dist/cjs/components/tabs/tabs.recipe.js +23 -6
  54. package/dist/cjs/components/textarea/textarea.recipe.js +11 -8
  55. package/dist/cjs/index.js +7 -0
  56. package/dist/cjs/product/authshell/authshell.recipe.js +5 -1
  57. package/dist/cjs/product/command/command.recipe.js +4 -1
  58. package/dist/cjs/product/comments/comments.js +7 -2
  59. package/dist/cjs/product/comments/comments.recipe.js +25 -7
  60. package/dist/cjs/product/memberssettings/index.js +6 -0
  61. package/dist/cjs/product/memberssettings/memberssettings.js +99 -0
  62. package/dist/cjs/product/memberssettings/memberssettings.recipe.js +83 -0
  63. package/dist/cjs/product/notificationssettings/index.js +5 -0
  64. package/dist/cjs/product/notificationssettings/notificationssettings.js +19 -0
  65. package/dist/cjs/product/notificationssettings/notificationssettings.recipe.js +44 -0
  66. package/dist/cjs/product/recordpage/recordpage.js +57 -6
  67. package/dist/cjs/product/recordpage/recordpage.recipe.js +43 -6
  68. package/dist/cjs/product/workspacegeneralsettings/index.js +5 -0
  69. package/dist/cjs/product/workspacegeneralsettings/workspacegeneralsettings.js +21 -0
  70. package/dist/cjs/product/workspacegeneralsettings/workspacegeneralsettings.recipe.js +51 -0
  71. package/dist/cjs/tokens/contract.js +1 -0
  72. package/dist/cjs/tokens/elevation.js +8 -5
  73. package/dist/cjs/tokens/palette.js +28 -8
  74. package/dist/cjs/tokens/scale.js +44 -2
  75. package/dist/cjs/tokens/semantic.js +2 -0
  76. package/dist/columncalc.css +15 -7
  77. package/dist/columncalc.layered.css +15 -7
  78. package/dist/command.css +23 -19
  79. package/dist/command.layered.css +23 -19
  80. package/dist/comments.css +28 -19
  81. package/dist/comments.d.ts +2 -2
  82. package/dist/comments.layered.css +28 -19
  83. package/dist/datatable.css +161 -67
  84. package/dist/datatable.layered.css +161 -67
  85. package/dist/datepicker.css +13 -41
  86. package/dist/datepicker.layered.css +13 -41
  87. package/dist/datepickerfield.css +4 -1
  88. package/dist/datepickerfield.layered.css +4 -1
  89. package/dist/dialog.css +12 -7
  90. package/dist/dialog.layered.css +12 -7
  91. package/dist/emptystate.css +5 -3
  92. package/dist/emptystate.layered.css +5 -3
  93. package/dist/esm/components/badge/badge.recipe.js +22 -8
  94. package/dist/esm/components/bottomsheet/bottomsheet.js +52 -0
  95. package/dist/esm/components/bottomsheet/bottomsheet.recipe.js +74 -0
  96. package/dist/esm/components/bottomsheet/index.js +2 -0
  97. package/dist/esm/components/button/button.recipe.js +40 -16
  98. package/dist/esm/components/checkbox/checkbox.recipe.js +61 -9
  99. package/dist/esm/components/chip/chip.js +14 -2
  100. package/dist/esm/components/chip/chip.recipe.js +69 -1
  101. package/dist/esm/components/chipgroup/chipgroup.js +15 -0
  102. package/dist/esm/components/chipgroup/chipgroup.recipe.js +33 -0
  103. package/dist/esm/components/chipgroup/index.js +2 -0
  104. package/dist/esm/components/datatable/datatable.js +5 -1
  105. package/dist/esm/components/datatable/datatable.recipe.js +11 -2
  106. package/dist/esm/components/dialog/dialog.recipe.js +22 -11
  107. package/dist/esm/components/emptystate/emptystate.recipe.js +7 -2
  108. package/dist/esm/components/fileupload/fileupload.js +20 -5
  109. package/dist/esm/components/input/index.js +1 -1
  110. package/dist/esm/components/input/input.js +30 -0
  111. package/dist/esm/components/input/input.recipe.js +17 -10
  112. package/dist/esm/components/menu/menu.recipe.js +21 -7
  113. package/dist/esm/components/menusheet/index.js +2 -0
  114. package/dist/esm/components/menusheet/menusheet.js +95 -0
  115. package/dist/esm/components/menusheet/menusheet.recipe.js +100 -0
  116. package/dist/esm/components/multiselect/multiselect.js +1 -1
  117. package/dist/esm/components/multiselect/multiselect.recipe.js +33 -9
  118. package/dist/esm/components/select/select.js +1 -1
  119. package/dist/esm/components/select/select.recipe.js +38 -12
  120. package/dist/esm/components/snackbar/index.js +2 -0
  121. package/dist/esm/components/snackbar/snackbar.js +84 -0
  122. package/dist/esm/components/snackbar/snackbar.recipe.js +68 -0
  123. package/dist/esm/components/tabletoolbar/tabletoolbar.chrome.js +7 -2
  124. package/dist/esm/components/tabletoolbar/tabletoolbar.js +37 -12
  125. package/dist/esm/components/tabs/tabs.js +10 -4
  126. package/dist/esm/components/tabs/tabs.recipe.js +23 -6
  127. package/dist/esm/components/textarea/textarea.recipe.js +12 -9
  128. package/dist/esm/index.js +7 -0
  129. package/dist/esm/product/authshell/authshell.recipe.js +5 -1
  130. package/dist/esm/product/command/command.recipe.js +4 -1
  131. package/dist/esm/product/comments/comments.js +7 -2
  132. package/dist/esm/product/comments/comments.recipe.js +25 -7
  133. package/dist/esm/product/memberssettings/index.js +1 -0
  134. package/dist/esm/product/memberssettings/memberssettings.js +62 -0
  135. package/dist/esm/product/memberssettings/memberssettings.recipe.js +80 -0
  136. package/dist/esm/product/notificationssettings/index.js +1 -0
  137. package/dist/esm/product/notificationssettings/notificationssettings.js +16 -0
  138. package/dist/esm/product/notificationssettings/notificationssettings.recipe.js +41 -0
  139. package/dist/esm/product/recordpage/recordpage.js +58 -7
  140. package/dist/esm/product/recordpage/recordpage.recipe.js +43 -6
  141. package/dist/esm/product/workspacegeneralsettings/index.js +1 -0
  142. package/dist/esm/product/workspacegeneralsettings/workspacegeneralsettings.js +18 -0
  143. package/dist/esm/product/workspacegeneralsettings/workspacegeneralsettings.recipe.js +48 -0
  144. package/dist/esm/tokens/contract.js +2 -1
  145. package/dist/esm/tokens/elevation.js +8 -5
  146. package/dist/esm/tokens/palette.js +28 -8
  147. package/dist/esm/tokens/scale.js +43 -1
  148. package/dist/esm/tokens/semantic.js +2 -0
  149. package/dist/fileupload.css +3 -1
  150. package/dist/fileupload.layered.css +3 -1
  151. package/dist/input.css +6 -4
  152. package/dist/input.layered.css +6 -4
  153. package/dist/listpage.css +3 -1
  154. package/dist/listpage.layered.css +3 -1
  155. package/dist/memberssettings.css +67 -0
  156. package/dist/memberssettings.d.ts +7 -0
  157. package/dist/memberssettings.layered.css +69 -0
  158. package/dist/menu.css +2 -2
  159. package/dist/menu.layered.css +2 -2
  160. package/dist/menusheet.css +36 -0
  161. package/dist/menusheet.d.ts +7 -0
  162. package/dist/menusheet.layered.css +38 -0
  163. package/dist/multiselect.css +13 -6
  164. package/dist/multiselect.layered.css +13 -6
  165. package/dist/native/tokens.d.ts +2 -1
  166. package/dist/native/tokens.js +20 -3
  167. package/dist/notificationssettings.css +22 -0
  168. package/dist/notificationssettings.d.ts +7 -0
  169. package/dist/notificationssettings.layered.css +24 -0
  170. package/dist/objectsettings.css +4 -4
  171. package/dist/objectsettings.layered.css +4 -4
  172. package/dist/recordpage.css +67 -23
  173. package/dist/recordpage.layered.css +67 -23
  174. package/dist/recordpanel.css +35 -16
  175. package/dist/recordpanel.layered.css +35 -16
  176. package/dist/resizablepanel.css +8 -5
  177. package/dist/resizablepanel.layered.css +8 -5
  178. package/dist/richtext.css +24 -8
  179. package/dist/richtext.layered.css +24 -8
  180. package/dist/savedviews.css +9 -2
  181. package/dist/savedviews.layered.css +9 -2
  182. package/dist/select.css +25 -5
  183. package/dist/select.layered.css +25 -5
  184. package/dist/settingsshell.css +2 -3
  185. package/dist/settingsshell.layered.css +2 -3
  186. package/dist/slider.css +6 -27
  187. package/dist/slider.layered.css +6 -27
  188. package/dist/snackbar.css +24 -0
  189. package/dist/snackbar.d.ts +7 -0
  190. package/dist/snackbar.layered.css +26 -0
  191. package/dist/statcard.css +3 -1
  192. package/dist/statcard.layered.css +3 -1
  193. package/dist/styles.css +859 -345
  194. package/dist/styles.layered.css +859 -345
  195. package/dist/table.css +28 -10
  196. package/dist/table.layered.css +28 -10
  197. package/dist/tabletoolbar.css +35 -24
  198. package/dist/tabletoolbar.layered.css +35 -24
  199. package/dist/tabs.css +18 -4
  200. package/dist/tabs.layered.css +18 -4
  201. package/dist/textarea.css +2 -6
  202. package/dist/textarea.layered.css +2 -6
  203. package/dist/themepicker.css +2 -1
  204. package/dist/themepicker.layered.css +2 -1
  205. package/dist/tokens.css +13 -2
  206. package/dist/tokens.json +20 -3
  207. package/dist/types/components/bottomsheet/bottomsheet.d.ts +35 -0
  208. package/dist/types/components/bottomsheet/bottomsheet.recipe.d.ts +3 -0
  209. package/dist/types/components/bottomsheet/index.d.ts +2 -0
  210. package/dist/types/components/button/button.recipe.d.ts +11 -6
  211. package/dist/types/components/checkbox/checkbox.recipe.d.ts +28 -5
  212. package/dist/types/components/chip/chip.d.ts +22 -0
  213. package/dist/types/components/chip/chip.recipe.d.ts +48 -0
  214. package/dist/types/components/chipgroup/chipgroup.d.ts +14 -0
  215. package/dist/types/components/chipgroup/chipgroup.recipe.d.ts +5 -0
  216. package/dist/types/components/chipgroup/index.d.ts +2 -0
  217. package/dist/types/components/datatable/datatable.d.ts +19 -0
  218. package/dist/types/components/fileupload/fileupload.d.ts +26 -0
  219. package/dist/types/components/input/index.d.ts +1 -1
  220. package/dist/types/components/input/input.d.ts +72 -0
  221. package/dist/types/components/menusheet/index.d.ts +2 -0
  222. package/dist/types/components/menusheet/menusheet.d.ts +42 -0
  223. package/dist/types/components/menusheet/menusheet.recipe.d.ts +3 -0
  224. package/dist/types/components/snackbar/index.d.ts +2 -0
  225. package/dist/types/components/snackbar/snackbar.d.ts +36 -0
  226. package/dist/types/components/snackbar/snackbar.recipe.d.ts +5 -0
  227. package/dist/types/components/tabletoolbar/tabletoolbar.d.ts +28 -1
  228. package/dist/types/components/tabs/tabs.d.ts +11 -1
  229. package/dist/types/index.d.ts +7 -0
  230. package/dist/types/product/comments/comments.d.ts +9 -3
  231. package/dist/types/product/comments/comments.recipe.d.ts +12 -2
  232. package/dist/types/product/memberssettings/index.d.ts +1 -0
  233. package/dist/types/product/memberssettings/memberssettings.d.ts +85 -0
  234. package/dist/types/product/memberssettings/memberssettings.recipe.d.ts +3 -0
  235. package/dist/types/product/notificationssettings/index.d.ts +1 -0
  236. package/dist/types/product/notificationssettings/notificationssettings.d.ts +37 -0
  237. package/dist/types/product/notificationssettings/notificationssettings.recipe.d.ts +3 -0
  238. package/dist/types/product/recordpage/recordpage.d.ts +53 -2
  239. package/dist/types/product/workspacegeneralsettings/index.d.ts +1 -0
  240. package/dist/types/product/workspacegeneralsettings/workspacegeneralsettings.d.ts +48 -0
  241. package/dist/types/product/workspacegeneralsettings/workspacegeneralsettings.recipe.d.ts +3 -0
  242. package/dist/types/tokens/contract.d.ts +4 -1
  243. package/dist/types/tokens/elevation.d.ts +8 -5
  244. package/dist/types/tokens/palette.d.ts +9 -1
  245. package/dist/types/tokens/scale.d.ts +11 -1
  246. package/dist/types/tokens/semantic.d.ts +2 -0
  247. package/dist/wheelpicker.css +4 -2
  248. package/dist/wheelpicker.layered.css +4 -2
  249. package/dist/workspacegeneralsettings.css +30 -0
  250. package/dist/workspacegeneralsettings.d.ts +7 -0
  251. package/dist/workspacegeneralsettings.layered.css +32 -0
  252. package/package.json +36 -1
@@ -3,11 +3,19 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.dialogRecipe = void 0;
4
4
  const core_1 = require("../../core");
5
5
  const tokens_1 = require("../../tokens");
6
- // A modal dialog surface — measured from a create-record modal: a radius-16
7
- // frosted-glass card (60% tinted background + 15px backdrop blur, both re-measured
8
- // per theme — light uses a blue-tinted 3-layer shadow, dark drops to a plain
9
- // 2-layer black shadow) with a bordered header (title + close), a scrollable
10
- // body, and a bordered footer for the primary action.
6
+ // your-moon/crisp#934: was a radius-16 frosted-glass card (a
7
+ // 60%-tinted, 15px-backdrop-blurred outer shell wrapping a solid radius-12
8
+ // inner card) -- owner-directed: Dialog's identity now follows seed-design
9
+ // (daangn), whose own Dialog spec has no shadow/blur/tint property at all;
10
+ // elevation there is conveyed purely by a darker backdrop scrim plus the
11
+ // existing scale+fade entrance animation (which crisp already had). Flat
12
+ // single surface now: `.crisp-dialog-content` is a bare, invisible sizing
13
+ // wrapper (no background/radius/shadow of its own); `.crisp-dialog-card` is
14
+ // the one real surface, radius bumped from `xl` (12px) to `3xl` (20px,
15
+ // seed's own shared floating-surface radius, `$radius.r5`), no shadow.
16
+ // `bgScrim` itself also moved toward seed's own darker backdrop value
17
+ // (`tokens/palette.ts`). The alert variant no longer needs its own override
18
+ // of either rule -- both variants now render the identical flat surface.
11
19
  //
12
20
  // #414: the footer's submit-key caps (⌘/Enter) used to be one span drawing a
13
21
  // visible pill (18px, 22%-white background). Attio's own are two separate
@@ -21,10 +29,8 @@ const config = (0, core_1.defineRecipe)({
21
29
  & .crisp-dialog-backdrop { position: fixed; inset: 0; z-index: 60; background: ${tokens_1.contract.bgScrim}; }
22
30
  & .crisp-dialog-positioner { position: fixed; inset: 0; z-index: 61; display: flex; align-items: flex-start; justify-content: center; padding: 15vh ${tokens_1.contract.space["4"]} ${tokens_1.contract.space["4"]}; }
23
31
  & .crisp-dialog-positioner[data-variant="alert"] { align-items: center; padding: ${tokens_1.contract.space["4"]}; }
24
- & .crisp-dialog-content { -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; position: relative; display: flex; flex-direction: column; box-sizing: border-box; width: 90vw; max-height: 74vh; padding: ${tokens_1.contract.space["1"]}; border-radius: ${tokens_1.contract.radius["2xl"]}; background: color-mix(in srgb, ${tokens_1.contract.bgOverlayFrost} 60%, transparent); backdrop-filter: blur(15px); -webkit-backdrop-filter: blur(15px); box-shadow: ${tokens_1.elevation.modal}; opacity: 1; transform: scale(1); transition: opacity 0.2s ease, transform 0.2s ease; @starting-style { opacity: 0; transform: scale(0.97); } }
25
- & .crisp-dialog-content[data-variant="alert"] { padding: 0; background: transparent; backdrop-filter: none; -webkit-backdrop-filter: none; box-shadow: none; }
26
- & .crisp-dialog-card { display: flex; flex-direction: column; flex: 1; min-height: 0; box-sizing: border-box; border-radius: ${tokens_1.contract.radius.xl}; background: ${tokens_1.contract.bgSurface}; box-shadow: 0 0 0 1px color-mix(in srgb, ${tokens_1.contract.fgPrimary} 8%, transparent); overflow: hidden; }
27
- & .crisp-dialog-content[data-variant="alert"] .crisp-dialog-card { border-radius: ${tokens_1.contract.radius.xl}; background: ${tokens_1.contract.bgPopover}; box-shadow: ${tokens_1.elevation.floating}; }
32
+ & .crisp-dialog-content { -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; position: relative; display: flex; flex-direction: column; box-sizing: border-box; width: 90vw; max-height: 74vh; opacity: 1; transform: scale(1); transition: opacity 0.2s ease, transform 0.2s ease; @starting-style { opacity: 0; transform: scale(0.97); } }
33
+ & .crisp-dialog-card { display: flex; flex-direction: column; flex: 1; min-height: 0; box-sizing: border-box; border-radius: ${tokens_1.contract.radius["3xl"]}; background: ${tokens_1.contract.bgSurface}; overflow: hidden; }
28
34
  & .crisp-dialog-header { display: flex; align-items: center; gap: ${tokens_1.contract.space["2"]}; flex-shrink: 0; height: 52px; padding: 0 ${tokens_1.contract.space["2"]} 0 ${tokens_1.contract.space["4"]}; border-bottom: 1px solid ${tokens_1.contract.borderSubtle}; }
29
35
  & .crisp-dialog-header svg { flex-shrink: 0; color: ${tokens_1.contract.fgMuted}; }
30
36
  & .crisp-dialog-title { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: ${tokens_1.contract.text.lg}; font-weight: ${tokens_1.contract.weight.medium}; color: ${tokens_1.contract.fgPrimary}; margin: 0; }
@@ -46,7 +52,12 @@ const config = (0, core_1.defineRecipe)({
46
52
  & .crisp-dialog-field { display: flex; flex-direction: column; gap: ${tokens_1.contract.space["1.5"]}; }
47
53
  & .crisp-dialog-field-label { font-size: ${tokens_1.contract.text.sm}; font-weight: ${tokens_1.contract.weight.medium}; color: color-mix(in srgb, ${tokens_1.contract.fgPrimary} 50%, transparent); }
48
54
  & .crisp-dialog-more { display: inline-flex; align-items: center; gap: ${tokens_1.contract.space["2"]}; margin-inline-end: auto; border: none; background: none; padding: 0; font-family: inherit; font-size: ${tokens_1.contract.text.md}; color: ${tokens_1.contract.fgMuted}; cursor: pointer; }
49
- & .crisp-dialog-submit { display: inline-flex; align-items: center; gap: ${tokens_1.contract.space["2"]}; height: 28px; padding: 0 ${tokens_1.contract.space["1"]} 0 ${tokens_1.contract.space["2.5"]}; border: none; border-radius: ${tokens_1.contract.radius.md}; background: ${tokens_1.contract.bgBrandSolid}; color: ${tokens_1.contract.fgOnBrand}; font-family: inherit; font-size: ${tokens_1.contract.text.md}; font-weight: ${tokens_1.contract.weight.medium}; cursor: pointer; }
55
+ /* your-moon/crisp#953: bgBrandContrast, not bgBrandSolid --
56
+ the accent (#0298d9) only clears 3.23:1 with white; the darker
57
+ sibling clears 4.5:1+, same fix already applied to Button/Badge/
58
+ Avatar (#897). Scoped to this one fill only -- Dialog's own frosted-
59
+ surface identity (#934) is untouched here. */
60
+ & .crisp-dialog-submit { display: inline-flex; align-items: center; gap: ${tokens_1.contract.space["2"]}; height: 28px; padding: 0 ${tokens_1.contract.space["1"]} 0 ${tokens_1.contract.space["2.5"]}; border: none; border-radius: ${tokens_1.contract.radius.md}; background: ${tokens_1.contract.bgBrandContrast}; color: ${tokens_1.contract.fgOnBrand}; font-family: inherit; font-size: ${tokens_1.contract.text.md}; font-weight: ${tokens_1.contract.weight.medium}; cursor: pointer; }
50
61
  & .crisp-dialog-submit-key { display: inline-flex; align-items: center; gap: ${tokens_1.contract.space["1"]}; }
51
62
  & .crisp-dialog-submit-key kbd { box-sizing: border-box; display: inline-flex; align-items: center; justify-content: center; width: 16px; height: 16px; padding: 0 3px; border-radius: 4px; background: transparent; color: ${tokens_1.contract.fgOnBrand}; font-family: inherit; font-style: normal; font-size: 10px; }`,
52
63
  variants: {},
@@ -31,9 +31,14 @@ const config = (0, core_1.defineRecipe)({
31
31
  // A full-view empty state (measured from a filtered-to-zero records table):
32
32
  // a 45px ringed circle icon, a 20/24 semibold heading, a 14/20 medium muted body,
33
33
  // then a primary action. Gaps are 16 / 8 / 16 (icon→title→desc→action).
34
+ // *Correction (crisp#933):* the original pass above didn't check letter-spacing.
35
+ // `records` below is the exact same title component (confirmed live at
36
+ // /techpartners/users and /techpartners/workspaces, both "No X records" at
37
+ // 20px/600/24px) and measures `-0.4px` -- exactly `c.tracking["20"]` (#909),
38
+ // not a coincidence, just a value nobody read off this element until now.
34
39
  page: `gap: ${tokens_1.contract.space["2"]};
35
40
  & .crisp-emptystate-icon { width: 45px; height: 45px; margin-bottom: ${tokens_1.contract.space["2"]}; border-radius: ${tokens_1.contract.radius.full}; border: 1px solid ${tokens_1.contract.borderSubtle}; background: none; color: ${tokens_1.contract.fgMuted}; }
36
- & .crisp-emptystate-title { font-size: 20px; line-height: 24px; }
41
+ & .crisp-emptystate-title { font-size: 20px; line-height: 24px; letter-spacing: ${tokens_1.contract.tracking["20"]}; }
37
42
  & .crisp-emptystate-desc { max-width: 266px; font-size: ${tokens_1.contract.text.lg}; font-weight: ${tokens_1.contract.weight.medium}; line-height: 20px; }
38
43
  & .crisp-emptystate-action { margin-top: ${tokens_1.contract.space["2"]}; }`,
39
44
  // #603: Attio's zero-*records* table state (Users/Workspaces with
@@ -48,7 +53,7 @@ const config = (0, core_1.defineRecipe)({
48
53
  // by the consumer via the existing `icon` prop, same as every
49
54
  // other variant.
50
55
  records: `& .crisp-emptystate-icon { width: 138px; height: 138px; margin-bottom: ${tokens_1.contract.space["2"]}; border-radius: 0; color: color-mix(in srgb, ${tokens_1.contract.fgPrimary} 22%, transparent); background: radial-gradient(circle, color-mix(in srgb, ${tokens_1.contract.fgPrimary} 12%, transparent) 1.2px, transparent 1.2px) 0 0 / 16px 16px; }
51
- & .crisp-emptystate-title { font-size: 20px; line-height: 24px; }
56
+ & .crisp-emptystate-title { font-size: 20px; line-height: 24px; letter-spacing: ${tokens_1.contract.tracking["20"]}; }
52
57
  & .crisp-emptystate-desc { max-width: 266px; font-size: ${tokens_1.contract.text.lg}; font-weight: ${tokens_1.contract.weight.medium}; line-height: 20px; }`,
53
58
  },
54
59
  },
@@ -35,6 +35,8 @@ var __importStar = (this && this.__importStar) || (function () {
35
35
  })();
36
36
  Object.defineProperty(exports, "__esModule", { value: true });
37
37
  exports.fileUploadLabels = void 0;
38
+ exports.FileUploadItem = FileUploadItem;
39
+ exports.formatSize = formatSize;
38
40
  exports.FileUpload = FileUpload;
39
41
  const jsx_runtime_1 = require("react/jsx-runtime");
40
42
  const react_file_upload_1 = require("@seed-design/react-file-upload");
@@ -54,6 +56,21 @@ exports.fileUploadLabels = {
54
56
  INVALID_TYPE: "That file type is not accepted.",
55
57
  },
56
58
  };
59
+ /**
60
+ * One row of `FileUpload`'s own accepted-file list (crisp#965) — icon-free
61
+ * name + trailing size + remove, `min-height: size.tableRow` on `bgRaised`
62
+ * (measured from a data-grid row, see `fileupload.recipe.ts`). Exported on
63
+ * its own because `FileUpload` internally only lists `File` objects from the
64
+ * current browser session; a caller with already-persisted attachments
65
+ * (fetched from a server, never a real `File`) has no `acceptedFileEntries`
66
+ * to hand it and needs the identical row for its own list instead of
67
+ * hand-rolling one.
68
+ */
69
+ function FileUploadItem({ name, size, status, progress, onRemove, removeLabel, className, }) {
70
+ return ((0, jsx_runtime_1.jsxs)("li", { className: className
71
+ ? `crisp-fileupload-item ${className}`
72
+ : "crisp-fileupload-item", "data-status": status, children: [(0, jsx_runtime_1.jsx)("span", { className: "crisp-fileupload-item-name", children: name }), status === "uploading" && progress != null && ((0, jsx_runtime_1.jsx)("span", { className: "crisp-fileupload-progress", role: "progressbar", "aria-valuenow": Math.round(progress), "aria-valuemin": 0, "aria-valuemax": 100, "aria-label": name, children: (0, jsx_runtime_1.jsx)("span", { className: "crisp-fileupload-progress-fill", style: { width: `${progress}%` } }) })), size != null && ((0, jsx_runtime_1.jsx)("span", { className: "crisp-fileupload-item-size", children: formatSize(size) })), onRemove && ((0, jsx_runtime_1.jsx)("button", { type: "button", className: "crisp-fileupload-remove", "aria-label": removeLabel, onClick: onRemove, children: (0, jsx_runtime_1.jsx)(lucide_react_1.X, { size: 13, strokeWidth: 2 }) }))] }));
73
+ }
57
74
  /** 1000-based, matching how every file manager reports size. */
58
75
  function formatSize(bytes) {
59
76
  if (bytes < 1000)
@@ -140,8 +157,8 @@ function FileUpload({ className, labels, "aria-label": ariaLabel, id, accept, di
140
157
  // focus should land next.
141
158
  const rootRef = React.useRef(null);
142
159
  const focusAfterRemoval = (0, core_1.useFocusAfterRemoval)(rootRef, ".crisp-fileupload-remove", dropzoneRef);
143
- return ((0, jsx_runtime_1.jsxs)("div", { ref: rootRef, ...rest, className: (0, fileupload_recipe_1.fileuploadRecipe)({ className }), children: [(0, jsx_runtime_1.jsxs)("button", { ...dropzoneProps, ref: dropzoneRef, type: "button", className: "crisp-fileupload-zone", tabIndex: disabled ? -1 : 0, "aria-label": ariaLabel ?? t.hint, disabled: disabled, "data-dragging": dragging ? "" : undefined, "data-disabled": disabled ? "" : undefined, "data-invalid": invalid ? "" : undefined, onClick: () => openFilePicker(), children: [(0, jsx_runtime_1.jsx)("span", { className: "crisp-fileupload-title", children: t.title }), (0, jsx_runtime_1.jsx)("span", { className: "crisp-fileupload-hint", children: t.hint })] }), (0, jsx_runtime_1.jsx)("input", { ...hiddenInputProps, id: id ?? hiddenInputProps.id, ref: inputRef, className: "crisp-fileupload-input" }), acceptedFileEntries.length > 0 && ((0, jsx_runtime_1.jsx)("ul", { className: "crisp-fileupload-list", children: acceptedFileEntries.map((entry, i) => ((0, jsx_runtime_1.jsxs)("li", { className: "crisp-fileupload-item", "data-status": entry.status, children: [(0, jsx_runtime_1.jsx)("span", { className: "crisp-fileupload-item-name", children: entry.file.name }), entry.status === "uploading" && entry.progress != null && ((0, jsx_runtime_1.jsx)("span", { className: "crisp-fileupload-progress", role: "progressbar", "aria-valuenow": Math.round(entry.progress), "aria-valuemin": 0, "aria-valuemax": 100, "aria-label": entry.file.name, children: (0, jsx_runtime_1.jsx)("span", { className: "crisp-fileupload-progress-fill", style: { width: `${entry.progress}%` } }) })), (0, jsx_runtime_1.jsx)("span", { className: "crisp-fileupload-item-size", children: formatSize(entry.file.size) }), (0, jsx_runtime_1.jsx)("button", { type: "button", className: "crisp-fileupload-remove", "aria-label": `${t.remove}: ${entry.file.name}`, onClick: () => {
144
- focusAfterRemoval(i);
145
- removeFileEntry(entry.id);
146
- }, children: (0, jsx_runtime_1.jsx)(lucide_react_1.X, { size: 13, strokeWidth: 2 }) })] }, entry.id))) })), (0, jsx_runtime_1.jsx)("ul", { className: "crisp-fileupload-errors", "aria-live": "polite", children: rejections.map((r) => ((0, jsx_runtime_1.jsx)("li", { className: "crisp-fileupload-error", children: `${r.file.name}: ${r.errors.map((e) => t.errors[e] ?? e).join(" ")}` }, r.file.name))) })] }));
160
+ return ((0, jsx_runtime_1.jsxs)("div", { ref: rootRef, ...rest, className: (0, fileupload_recipe_1.fileuploadRecipe)({ className }), children: [(0, jsx_runtime_1.jsxs)("button", { ...dropzoneProps, ref: dropzoneRef, type: "button", className: "crisp-fileupload-zone", tabIndex: disabled ? -1 : 0, "aria-label": ariaLabel ?? t.hint, disabled: disabled, "data-dragging": dragging ? "" : undefined, "data-disabled": disabled ? "" : undefined, "data-invalid": invalid ? "" : undefined, onClick: () => openFilePicker(), children: [(0, jsx_runtime_1.jsx)("span", { className: "crisp-fileupload-title", children: t.title }), (0, jsx_runtime_1.jsx)("span", { className: "crisp-fileupload-hint", children: t.hint })] }), (0, jsx_runtime_1.jsx)("input", { ...hiddenInputProps, id: id ?? hiddenInputProps.id, ref: inputRef, className: "crisp-fileupload-input" }), acceptedFileEntries.length > 0 && ((0, jsx_runtime_1.jsx)("ul", { className: "crisp-fileupload-list", children: acceptedFileEntries.map((entry, i) => ((0, jsx_runtime_1.jsx)(FileUploadItem, { name: entry.file.name, size: entry.file.size, status: entry.status, progress: entry.status === "uploading" ? entry.progress : undefined, removeLabel: `${t.remove}: ${entry.file.name}`, onRemove: () => {
161
+ focusAfterRemoval(i);
162
+ removeFileEntry(entry.id);
163
+ } }, entry.id))) })), (0, jsx_runtime_1.jsx)("ul", { className: "crisp-fileupload-errors", "aria-live": "polite", children: rejections.map((r) => ((0, jsx_runtime_1.jsx)("li", { className: "crisp-fileupload-error", children: `${r.file.name}: ${r.errors.map((e) => t.errors[e] ?? e).join(" ")}` }, r.file.name))) })] }));
147
164
  }
@@ -1,7 +1,8 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.inputRecipe = exports.Input = void 0;
3
+ exports.inputRecipe = exports.InputShell = exports.Input = void 0;
4
4
  var input_1 = require("./input");
5
5
  Object.defineProperty(exports, "Input", { enumerable: true, get: function () { return input_1.Input; } });
6
+ Object.defineProperty(exports, "InputShell", { enumerable: true, get: function () { return input_1.InputShell; } });
6
7
  var input_recipe_1 = require("./input.recipe");
7
8
  Object.defineProperty(exports, "inputRecipe", { enumerable: true, get: function () { return input_recipe_1.inputRecipe; } });
@@ -33,7 +33,7 @@ var __importStar = (this && this.__importStar) || (function () {
33
33
  };
34
34
  })();
35
35
  Object.defineProperty(exports, "__esModule", { value: true });
36
- exports.Input = void 0;
36
+ exports.InputShell = exports.Input = void 0;
37
37
  const jsx_runtime_1 = require("react/jsx-runtime");
38
38
  const React = __importStar(require("react"));
39
39
  const input_recipe_1 = require("./input.recipe");
@@ -45,3 +45,33 @@ exports.Input = React.forwardRef(({ size, invalid, className, style, suffix, ...
45
45
  return ((0, jsx_runtime_1.jsxs)("span", { className: (0, input_recipe_1.inputRecipe)({ size, invalid, className }), style: style, "data-readonly": props.readOnly ? "" : undefined, children: [(0, jsx_runtime_1.jsx)("input", { ref: ref, className: "crisp-input-value", "aria-invalid": invalid === true || undefined, ...props }), suffix != null && (0, jsx_runtime_1.jsx)("span", { className: "crisp-input-suffix", children: suffix })] }));
46
46
  });
47
47
  exports.Input.displayName = "Input";
48
+ /**
49
+ * crisp#917: the same field chrome `Input` draws (border, focus ring,
50
+ * radius, size, invalid/read-only states) around arbitrary children instead
51
+ * of a hardcoded `<input>` — for a third-party editor (CodeMirror, a
52
+ * rich-text widget, a masked-input library) that needs to mount into a
53
+ * plain element rather than a native `<input>`.
54
+ *
55
+ * Deliberately NOT `asChild` (crisp's usual `Primitive`/`Slot` pattern, used
56
+ * by `Button`/`Chip`/`Avatar`/etc. elsewhere): those merge the shell's own
57
+ * props onto ONE child, replacing it outright — correct when the shell
58
+ * and the interactive surface are the same element. `Input`'s real shape is
59
+ * TWO parts, an outer shell hosting the `::after` focus ring plus an inner
60
+ * value that carries the actual text/interaction; a rich editor mounts its
61
+ * own DOM subtree inside a container div, which is exactly what `children`
62
+ * gives it, not what replacing a single node would.
63
+ *
64
+ * Only draws the OUTER shell (`inputRecipe`'s own class — border, ring,
65
+ * radius, size-driven height, background, `data-readonly` tint) around
66
+ * `children` unchanged, the same granularity GRC's own pre-existing
67
+ * workaround (internal/grc#159, reaching into `.crisp-input`/
68
+ * `.crisp-input--size_medium` by hand) already used — NOT `.crisp-input-
69
+ * value`'s own text-field resets (`border: none`, `padding-inline`, `font:
70
+ * inherit`, …), which assume a real `<input>` and would fight a rich
71
+ * editor's own internal layout. A child that wants to fill the shell the
72
+ * way `Input`'s own `<input>` does should set its own sizing (e.g. `flex: 1
73
+ * 1 auto; min-width: 0; width: 100%; height: 100%`), same as any block
74
+ * content dropped into an `inline-flex` container.
75
+ */
76
+ exports.InputShell = React.forwardRef(({ size, invalid, className, style, suffix, readOnly, children, ...props }, ref) => ((0, jsx_runtime_1.jsxs)("div", { ref: ref, className: (0, input_recipe_1.inputRecipe)({ size, invalid, className }), style: style, "data-readonly": readOnly ? "" : undefined, ...props, children: [children, suffix != null && (0, jsx_runtime_1.jsx)("span", { className: "crisp-input-suffix", children: suffix })] })));
77
+ exports.InputShell.displayName = "InputShell";
@@ -4,11 +4,13 @@ exports.inputRecipe = void 0;
4
4
  const core_1 = require("../../core");
5
5
  const tokens_1 = require("../../tokens");
6
6
  // Geometry is measured: 32/36px tall, r8/r10, 10/12px side padding.
7
- // The shadow is `elevation.textField` (#466), not `elevation.field` — a real
8
- // typed field on Attio's own Settings page carries one 1px ring, not
9
- // `field`'s button-chrome glow/drop-shadow layers too; `field`'s own doc
10
- // comment had claimed "inputs, textareas" among its consumers, which this
11
- // corrects.
7
+ // your-moon/crisp#910: the rest-state edge used to be `elevation.textField`,
8
+ // a `box-shadow` ring (#466's fix for a real typed field's 1px ring, not
9
+ // `field`'s button-chrome glow/drop-shadow layers) -- now a real `border`,
10
+ // matching seed-design's own `text-input.yaml` (an actual border that
11
+ // THICKENS on focus/invalid, not a fixed-width ring that only recolors).
12
+ // `borderDefault` (#dcdee3) is seed's own `stroke.neutral-weak`, already the
13
+ // crisp token's exact value.
12
14
  // The FOCUS TREATMENT is seed-design's, measured from @seed-design/css: a 2px
13
15
  // ring on an ::after overlay that is present-but-transparent at rest, so
14
16
  // focusing cannot shift layout the way a width change would, and animates
@@ -23,7 +25,7 @@ const config = (0, core_1.defineRecipe)({
23
25
  name: "input",
24
26
  base: `
25
27
  position: relative; box-sizing: border-box; display: inline-flex; align-items: center;
26
- width: 100%; background: ${tokens_1.contract.bgSurface}; box-shadow: ${tokens_1.elevation.textField};
28
+ width: 100%; background: ${tokens_1.contract.bgSurface}; border: 1px solid ${tokens_1.contract.borderDefault};
27
29
  font-family: ${tokens_1.contract.font.sans}; font-size: ${tokens_1.contract.text.lg}; line-height: 20px;
28
30
 
29
31
  &::after {
@@ -46,8 +48,10 @@ const config = (0, core_1.defineRecipe)({
46
48
  primary-email field -- a tinted field, not just muted text, driven off
47
49
  the native \`readOnly\` attribute. \`ringField\` (rgba(0,0,0,.06)) is an
48
50
  exact match for the measured ring; the 2%-fgPrimary tint reuses the
49
- same color-mix idiom every other soft crisp surface already uses. */
50
- &[data-readonly] { background: color-mix(in srgb, ${tokens_1.contract.fgPrimary} 2%, transparent); box-shadow: inset 0 0 0 1px ${tokens_1.contract.ringField}; }
51
+ same color-mix idiom every other soft crisp surface already uses.
52
+ \`border-color: transparent\` neutralises #910's new real rest border
53
+ so this measured ring isn't drawn twice. */
54
+ &[data-readonly] { background: color-mix(in srgb, ${tokens_1.contract.fgPrimary} 2%, transparent); border-color: transparent; box-shadow: inset 0 0 0 1px ${tokens_1.contract.ringField}; }
51
55
  & .crisp-input-suffix { flex-shrink: 0; margin-inline-start: ${tokens_1.contract.space["1"]}; }
52
56
  `,
53
57
  variants: {
@@ -59,7 +63,10 @@ const config = (0, core_1.defineRecipe)({
59
63
  // padding `medium` already uses, just 4px shorter.
60
64
  sm: `height: ${tokens_1.contract.size.row}; border-radius: ${tokens_1.contract.radius.md}; & .crisp-input-value { padding-inline: ${tokens_1.contract.space["2.5"]}; }`,
61
65
  medium: `height: 32px; border-radius: ${tokens_1.contract.radius.md}; & .crisp-input-value { padding-inline: ${tokens_1.contract.space["2.5"]}; }`,
62
- large: `height: 36px; border-radius: 10px; & .crisp-input-value { padding-inline: ${tokens_1.contract.space["3"]}; }`,
66
+ // your-moon/crisp#910: was a stray literal \`10px\` -- seed's own
67
+ // large-size field radius is \`$radius.r3\` = 12px (\`text-input.yaml\`),
68
+ // matching Button's own \`lg\` fix in the same PR.
69
+ large: `height: 36px; border-radius: ${tokens_1.contract.radius.xl}; & .crisp-input-value { padding-inline: ${tokens_1.contract.space["3"]}; }`,
63
70
  },
64
71
  invalid: {
65
72
  // critical outranks focus: a field that is wrong stays visibly wrong
@@ -4,11 +4,25 @@ exports.menuRecipe = void 0;
4
4
  const core_1 = require("../../core");
5
5
  const tokens_1 = require("../../tokens");
6
6
  // A dropdown menu — same popover surface used for ⋯ / sort / filter
7
- // menus (measured from listboxes): radius 12, 4px padding, a layered
8
- // soft shadow, over 32px items (radius 8, icon + label + optional shortcut /
9
- // check). The content portals to document.body (#339) so no ancestor's
10
- // transform/backdrop-filter can capture its position: fixed box; z-index 70
11
- // keeps it above Dialog/Drawer's 60/61.
7
+ // menus (measured from listboxes): 4px padding, over 32px items (radius 8,
8
+ // icon + label + optional shortcut / check). The content portals to
9
+ // document.body (#339) so no ancestor's transform/backdrop-filter can
10
+ // capture its position: fixed box; z-index 70 keeps it above Dialog/
11
+ // Drawer's 60/61.
12
+ //
13
+ // your-moon/crisp#934: radius and shadow are seed-design's,
14
+ // not Attio's -- Menu is the ONE component the seed raw dump confirms
15
+ // actually uses a shadow token at all (every other floating surface in
16
+ // seed's own spec has none). Radius moved from `radius.xl` (12px, this
17
+ // component's own private value) to `radius["3xl"]` (20px) -- seed treats
18
+ // "floating surface radius" as one shared value across Dialog/Menu/Select,
19
+ // not a per-component one; `radius["3xl"]` is already that shared token
20
+ // (Select's listbox, crisp/#867; Dialog, this same PR). Shadow moved from
21
+ // `elevation.floating` (crisp's own 4-layer blue-tinted glow, still used by
22
+ // Popover/ContextMenu/Toast/etc -- NOT touched here, out of scope) to
23
+ // `elevation.selectFloating` -- already seed's exact flat `$shadow.s3`
24
+ // value, already in this file (Select's own listbox, #867), reused rather
25
+ // than duplicated.
12
26
  //
13
27
  // The menu's floating content and everything inside it live in `globalCss`
14
28
  // below, not nested under `&` (#357): nesting compiles to a real descendant
@@ -35,8 +49,8 @@ const config = (0, core_1.defineRecipe)({
35
49
  font-family: ${tokens_1.contract.font.sans}; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale;
36
50
  position: fixed; z-index: 70; pointer-events: auto; box-sizing: border-box; display: flex; flex-direction: column; gap: ${tokens_1.contract.space.px};
37
51
  min-width: 200px; max-height: 360px; overflow-y: auto; padding: ${tokens_1.contract.space["1"]};
38
- border-radius: ${tokens_1.contract.radius.xl}; background: ${tokens_1.contract.bgPopover};
39
- box-shadow: ${tokens_1.elevation.floating};
52
+ border-radius: ${tokens_1.contract.radius["3xl"]}; background: ${tokens_1.contract.bgPopover};
53
+ box-shadow: ${tokens_1.elevation.selectFloating};
40
54
  transform-origin: top left; opacity: 1; transform: none; transition: opacity .08s ease, transform .08s ease;
41
55
  }
42
56
  .crisp-menu-content[data-align="end"] { transform-origin: top right; }
@@ -0,0 +1,9 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.menusheetRecipe = exports.MenuSheetItem = exports.MenuSheetGroup = exports.MenuSheet = void 0;
4
+ var menusheet_1 = require("./menusheet");
5
+ Object.defineProperty(exports, "MenuSheet", { enumerable: true, get: function () { return menusheet_1.MenuSheet; } });
6
+ Object.defineProperty(exports, "MenuSheetGroup", { enumerable: true, get: function () { return menusheet_1.MenuSheetGroup; } });
7
+ Object.defineProperty(exports, "MenuSheetItem", { enumerable: true, get: function () { return menusheet_1.MenuSheetItem; } });
8
+ var menusheet_recipe_1 = require("./menusheet.recipe");
9
+ Object.defineProperty(exports, "menusheetRecipe", { enumerable: true, get: function () { return menusheet_recipe_1.menusheetRecipe; } });
@@ -0,0 +1,133 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.MenuSheet = MenuSheet;
37
+ exports.MenuSheetGroup = MenuSheetGroup;
38
+ exports.MenuSheetItem = MenuSheetItem;
39
+ const jsx_runtime_1 = require("react/jsx-runtime");
40
+ const dialog = __importStar(require("@zag-js/dialog"));
41
+ const react_1 = require("@zag-js/react");
42
+ const React = __importStar(require("react"));
43
+ const core_1 = require("../../core");
44
+ const menu_keyboard_1 = require("../menu/menu-keyboard");
45
+ const menusheet_recipe_1 = require("./menusheet.recipe");
46
+ const MenuSheetContext = React.createContext(null);
47
+ let instanceCounter = 0;
48
+ function useMenuSheetInstanceId() {
49
+ const idRef = React.useRef(null);
50
+ if (idRef.current == null) {
51
+ instanceCounter += 1;
52
+ idRef.current = `crisp-menusheet-${instanceCounter}`;
53
+ }
54
+ return idRef.current;
55
+ }
56
+ /**
57
+ * A mobile menu sheet -- a focus-trapped bottom sheet (same Zag dialog
58
+ * machine as BottomSheet) whose body is a role=menu list, with the identical
59
+ * keyboard model Menu already uses: arrow keys move, Home/End jump,
60
+ * typeahead, Enter/Space select, Escape closes. Compose items with
61
+ * MenuSheetItem, and group a run of them in one rounded card with
62
+ * MenuSheetGroup.
63
+ */
64
+ function MenuSheet({ open, onOpenChange, title, description, footer, "aria-label": ariaLabel, children, className, "aria-describedby": describedBy, ...rest }) {
65
+ const id = useMenuSheetInstanceId();
66
+ const listRef = React.useRef(null);
67
+ const service = (0, react_1.useMachine)(dialog.machine, {
68
+ id,
69
+ open,
70
+ onOpenChange(details) {
71
+ onOpenChange(details.open);
72
+ },
73
+ "aria-label": typeof title === "string" ? title : ariaLabel,
74
+ onEscapeKeyDown(event) {
75
+ if ((0, core_1.hasOpenFloatingLayer)())
76
+ event.preventDefault();
77
+ },
78
+ // Without this, Zag's own focus trap parks initial focus on the dialog
79
+ // surface itself, which wins the race against useMenuKeyboard's rAF-
80
+ // scheduled "focus the first item" effect below -- the two compete for
81
+ // the same job and Zag's own dialog-level default was landing last.
82
+ // Pointing it at the first menu item directly makes Zag do that job
83
+ // itself instead of fighting useMenuKeyboard for it.
84
+ initialFocusEl() {
85
+ return (listRef.current?.querySelector(menu_keyboard_1.MENU_ITEM_SELECTOR) ?? null);
86
+ },
87
+ });
88
+ const api = dialog.connect(service, react_1.normalizeProps);
89
+ const close = React.useCallback(() => onOpenChange(false), [onOpenChange]);
90
+ const { onKeyDown: menuKeyDown, items } = (0, menu_keyboard_1.useMenuKeyboard)({
91
+ open: api.open,
92
+ close,
93
+ contentRef: listRef,
94
+ });
95
+ // Roving tabindex: exactly one item is a real Tab stop at a time (#237 --
96
+ // "selected, or first when none" -- is right in every state but the one
97
+ // that matters, so it is enforced imperatively here rather than trusted to
98
+ // a per-render default). useMenuKeyboard moves FOCUS with arrow keys; this
99
+ // keeps the tabindex in sync with wherever that focus actually lands,
100
+ // including the initial auto-focus Zag's own initialFocusEl performs.
101
+ const onFocus = React.useCallback((e) => {
102
+ for (const el of items())
103
+ el.tabIndex = -1;
104
+ e.target.tabIndex = 0;
105
+ }, [items]);
106
+ React.useEffect(() => {
107
+ if (!api.open)
108
+ return;
109
+ const [first] = items();
110
+ if (first)
111
+ first.tabIndex = 0;
112
+ }, [api.open, items]);
113
+ const ctx = React.useMemo(() => ({ close }), [close]);
114
+ if (!api.open)
115
+ return null;
116
+ return ((0, jsx_runtime_1.jsx)(react_1.Portal, { children: (0, jsx_runtime_1.jsxs)("div", { className: (0, menusheet_recipe_1.menusheetRecipe)(), children: [(0, jsx_runtime_1.jsx)("div", { ...api.getBackdropProps(), className: "crisp-menusheet-backdrop" }), (0, jsx_runtime_1.jsx)("div", { ...api.getPositionerProps(), className: "crisp-menusheet-positioner", children: (0, jsx_runtime_1.jsxs)("div", { ...rest, ...(0, core_1.withDescribedBy)(api.getContentProps(), describedBy), className: className
117
+ ? `crisp-menusheet-content ${className}`
118
+ : "crisp-menusheet-content", children: [(title != null || description != null) && ((0, jsx_runtime_1.jsxs)("div", { className: "crisp-menusheet-header", children: [title != null && ((0, jsx_runtime_1.jsx)("span", { ...api.getTitleProps(), className: "crisp-menusheet-title", children: title })), description != null && ((0, jsx_runtime_1.jsx)("span", { className: "crisp-menusheet-description", children: description }))] })), (0, jsx_runtime_1.jsx)(MenuSheetContext.Provider, { value: ctx, children: (0, jsx_runtime_1.jsx)("div", { ref: listRef, role: "menu", className: "crisp-menusheet-list", onKeyDown: menuKeyDown, onFocus: onFocus, children: children }) }), footer != null && ((0, jsx_runtime_1.jsx)("div", { className: "crisp-menusheet-footer", children: footer }))] }) })] }) }));
119
+ }
120
+ MenuSheet.displayName = "MenuSheet";
121
+ function MenuSheetGroup({ children }) {
122
+ return (0, jsx_runtime_1.jsx)("div", { className: "crisp-menusheet-group", children: children });
123
+ }
124
+ MenuSheetGroup.displayName = "MenuSheetGroup";
125
+ function MenuSheetItem({ children, icon, description, tone = "neutral", disabled, onSelect, closeOnSelect = true, }) {
126
+ const ctx = React.useContext(MenuSheetContext);
127
+ return ((0, jsx_runtime_1.jsxs)("button", { type: "button", role: "menuitem", tabIndex: -1, disabled: disabled, "data-disabled": disabled ? "" : undefined, "data-tone": tone, className: "crisp-menusheet-item", onClick: () => {
128
+ onSelect?.();
129
+ if (closeOnSelect)
130
+ ctx?.close();
131
+ }, children: [icon != null && ((0, jsx_runtime_1.jsx)("span", { className: "crisp-menusheet-item-icon", children: icon })), (0, jsx_runtime_1.jsxs)("span", { className: "crisp-menusheet-item-content", children: [(0, jsx_runtime_1.jsx)("span", { className: "crisp-menusheet-item-label", children: children }), description != null && ((0, jsx_runtime_1.jsx)("span", { className: "crisp-menusheet-item-description", children: description }))] })] }));
132
+ }
133
+ MenuSheetItem.displayName = "MenuSheetItem";
@@ -0,0 +1,103 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.menusheetRecipe = void 0;
4
+ const core_1 = require("../../core");
5
+ const tokens_1 = require("../../tokens");
6
+ // A mobile menu sheet -- daangn/seed-design components menu-sheet.yaml and
7
+ // menu-sheet-item.yaml (dev at e1c486b904297f7b6a8f17b8523f732c37015d60), not
8
+ // Attio (no such component there; crisp#911). Same @zag-js/dialog machine as
9
+ // BottomSheet, and the identical role=menu keyboard model Menu already uses
10
+ // (useMenuKeyboard, shared rather than reimplemented). Differs from
11
+ // BottomSheet in every slot value: r5 radius (not r6), content-level padding
12
+ // (not per-slot), no header alignment / close-button matrix at all -- menu
13
+ // sheet has neither in its own schema. Every numeric value cites its yaml
14
+ // line. NOTE: no apostrophes in this file -- one broke the recipe compiler
15
+ // silently on BottomSheet, see AUTHORING.md Lesson 64.
16
+ const config = (0, core_1.defineRecipe)({
17
+ name: "menusheet",
18
+ base: `display: contents; font-family: ${tokens_1.contract.font.sans};
19
+ & .crisp-menusheet-backdrop {
20
+ position: fixed; inset: 0; z-index: 60;
21
+ background: ${tokens_1.contract.bgOverlayStrong}; /* menu-sheet.yaml:95 backdrop.color = $color.bg.overlay */
22
+ opacity: 1; transition: opacity 100ms cubic-bezier(0,0,.15,1); /* menu-sheet.yaml:96-98 enterDuration/enterTimingFunction = $duration.d2/$timing-function.enter */
23
+ @starting-style { opacity: 0; }
24
+ }
25
+ & .crisp-menusheet-positioner {
26
+ position: fixed; inset: 0; z-index: 61;
27
+ display: flex; align-items: flex-end; justify-content: center;
28
+ }
29
+ & .crisp-menusheet-content {
30
+ -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale;
31
+ position: relative; box-sizing: border-box; display: flex; flex-direction: column;
32
+ overflow: hidden; width: 100%; max-width: 480px; /* menu-sheet.yaml:104 content.maxWidth */
33
+ max-height: 85vh;
34
+ background: ${tokens_1.contract.bgSurface}; /* menu-sheet.yaml:103 content.color = $color.bg.layer-floating */
35
+ padding: ${tokens_1.contract.space["6"]} ${tokens_1.contract.space["4"]} ${tokens_1.contract.space["4"]}; /* menu-sheet.yaml:105-107 content.paddingX/paddingTop/paddingBottom = global-gutter/x6/x4 (16px/24px/16px) */
36
+ border-radius: ${tokens_1.contract.radius["3xl"]} ${tokens_1.contract.radius["3xl"]} 0 0; /* menu-sheet.yaml:108 content.topCornerRadius = $radius.r5 (20px) */
37
+ transform: translateY(0);
38
+ transition: transform 300ms cubic-bezier(.03,.4,.1,1); /* menu-sheet.yaml:109-110 content.enterDuration/enterTimingFunction (enter-expressive) */
39
+ @starting-style { transform: translateY(100%); }
40
+ }
41
+ & .crisp-menusheet-header {
42
+ display: flex; flex-direction: column;
43
+ gap: ${tokens_1.contract.space["1"]}; /* menu-sheet.yaml:115 header.gap = $dimension.x1 (4px) */
44
+ padding-bottom: ${tokens_1.contract.space["4"]}; /* menu-sheet.yaml:116 header.paddingBottom = $dimension.x4 (16px) */
45
+ flex-shrink: 0;
46
+ }
47
+ & .crisp-menusheet-title {
48
+ color: ${tokens_1.contract.fgPrimary}; /* menu-sheet.yaml:121 title.color = $color.fg.neutral */
49
+ font-size: 18px; line-height: 24px; font-weight: ${tokens_1.contract.weight.bold}; /* menu-sheet.yaml:118-120 title.fontSize/lineHeight/fontWeight = $font-size.t6/$line-height.t6/bold */
50
+ }
51
+ & .crisp-menusheet-description {
52
+ color: ${tokens_1.contract.fgMuted}; /* menu-sheet.yaml:126 description.color = $color.fg.neutral-muted */
53
+ font-size: ${tokens_1.contract.text.lg}; line-height: 19px; font-weight: ${tokens_1.contract.weight.regular}; /* menu-sheet.yaml:123-125 description.fontSize/lineHeight/fontWeight = $font-size.t4/$line-height.t4/regular */
54
+ }
55
+ & .crisp-menusheet-list {
56
+ display: flex; flex-direction: column;
57
+ gap: ${tokens_1.contract.space["2.5"]}; /* menu-sheet.yaml:129 list.gap = $dimension.x2_5 (10px) */
58
+ overflow-y: auto; min-height: 0;
59
+ }
60
+ & .crisp-menusheet-group {
61
+ display: flex; flex-direction: column;
62
+ border-radius: ${tokens_1.contract.radius["2xl"]}; overflow: hidden; /* menu-sheet.yaml:131 group.cornerRadius = $radius.r4 (16px) */
63
+ }
64
+ & .crisp-menusheet-group > * + * { border-top: 1px solid ${tokens_1.contract.strokeGroupDivider}; } /* menu-sheet.yaml:132-134 divider.strokeBottomWidth/strokeColor = 1px/$color.stroke.neutral-muted */
65
+ & .crisp-menusheet-footer {
66
+ flex-shrink: 0;
67
+ padding-top: ${tokens_1.contract.space["2.5"]}; /* menu-sheet.yaml:136 footer.paddingTop = $dimension.x2_5 (10px) */
68
+ }
69
+ & .crisp-menusheet-item {
70
+ position: relative; box-sizing: border-box; display: flex; align-items: center;
71
+ width: 100%; min-height: 52px; /* menu-sheet-item.yaml:72 root.minHeight */
72
+ padding: ${tokens_1.contract.space["3.5"]} ${tokens_1.contract.space["4"]}; gap: ${tokens_1.contract.space["3.5"]}; /* menu-sheet-item.yaml:73-75 root.paddingX/paddingY/gap = x4/x3_5/x3_5 (16px/14px/14px) */
73
+ border: none; margin: 0; cursor: pointer; text-align: start;
74
+ background: ${tokens_1.contract.bgNeutralWeak}; /* menu-sheet-item.yaml:71 root.color = $color.bg.neutral-weak */
75
+ font-family: inherit;
76
+ }
77
+ & .crisp-menusheet-item:hover,
78
+ & .crisp-menusheet-item:active { background: color-mix(in srgb, ${tokens_1.contract.fgPrimary} 6%, ${tokens_1.contract.bgNeutralWeak}); } /* menu-sheet-item.yaml:90-91 pressed.root.color = $color.bg.neutral-weak-pressed, approximated as a tint over the base fill */
79
+ & .crisp-menusheet-item:active .crisp-menusheet-item-content { transform: scale(0.98); } /* menu-sheet-item.yaml:92 pressed.root.scaleScope = content */
80
+ & .crisp-menusheet-item-icon { flex-shrink: 0; display: inline-flex; width: 22px; height: 22px; } /* menu-sheet-item.yaml:77 prefixIcon.size */
81
+ & .crisp-menusheet-item-content {
82
+ flex: 1 1 auto; min-width: 0; display: flex; flex-direction: column;
83
+ gap: ${tokens_1.contract.space["0.5"]}; /* menu-sheet-item.yaml:79 content.gap = $dimension.x0_5 (2px) */
84
+ transition: transform 150ms cubic-bezier(0,0,.15,1);
85
+ }
86
+ & .crisp-menusheet-item-label {
87
+ font-size: ${tokens_1.contract.text.xl}; line-height: 22px; font-weight: ${tokens_1.contract.weight.regular}; /* menu-sheet-item.yaml:81-83 label.fontSize/lineHeight/fontWeight = $font-size.t5/$line-height.t5/regular */
88
+ }
89
+ & .crisp-menusheet-item-description {
90
+ color: ${tokens_1.contract.fgNeutralSubtle}; /* menu-sheet-item.yaml:88 description.color = $color.fg.neutral-subtle */
91
+ font-size: ${tokens_1.contract.text.md}; line-height: 18px; font-weight: ${tokens_1.contract.weight.medium}; /* menu-sheet-item.yaml:85-87 description.fontSize/lineHeight/fontWeight = $font-size.t3/$line-height.t3/medium */
92
+ }
93
+ & .crisp-menusheet-item[data-tone="neutral"] .crisp-menusheet-item-icon,
94
+ & .crisp-menusheet-item[data-tone="neutral"] .crisp-menusheet-item-label { color: ${tokens_1.contract.fgPrimary}; } /* menu-sheet-item.yaml:93-98 tone=neutral */
95
+ & .crisp-menusheet-item[data-tone="critical"] .crisp-menusheet-item-icon,
96
+ & .crisp-menusheet-item[data-tone="critical"] .crisp-menusheet-item-label { color: ${tokens_1.contract.fgDanger}; } /* menu-sheet-item.yaml:99-104 tone=critical */
97
+ `,
98
+ variants: {},
99
+ defaultVariants: {},
100
+ compoundVariants: [],
101
+ });
102
+ exports.menusheetRecipe = (0, core_1.resolveRecipe)(config);
103
+ exports.default = config;
@@ -222,7 +222,7 @@ id, placeholder: placeholderProp, searchable = true, searchPlaceholder: searchPl
222
222
  }, children: [(0, jsx_runtime_1.jsx)("span", { className: "crisp-multiselect-chips", children: selectedOptions.length === 0 ? ((0, jsx_runtime_1.jsx)("span", { className: "crisp-multiselect-placeholder", children: placeholder })) : (selectedOptions.map((o) => (0, jsx_runtime_1.jsx)(Chip, { opt: o }, o.value))) }), (0, jsx_runtime_1.jsx)(lucide_react_1.ChevronDown, { className: "crisp-multiselect-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-multiselect-listbox", role: "listbox", "aria-multiselectable": "true", "aria-label": ariaLabel, "data-up": up ? "" : undefined, style: pos, children: [searchable && ((0, jsx_runtime_1.jsxs)("div", { className: "crisp-multiselect-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) => {
223
223
  setQuery(e.target.value);
224
224
  setHighlight(0);
225
- }, onKeyDown: onNavKeys })] })), filtered.length === 0 ? ((0, jsx_runtime_1.jsx)("div", { className: "crisp-multiselect-empty", children: emptyLabel })) : (filtered.map((opt, i) => {
225
+ }, onKeyDown: onNavKeys })] })), searchable && (0, jsx_runtime_1.jsx)("div", { className: "crisp-multiselect-divider" }), filtered.length === 0 ? ((0, jsx_runtime_1.jsx)("div", { className: "crisp-multiselect-empty", children: emptyLabel })) : (filtered.map((opt, i) => {
226
226
  const sel = selectedSet.has(opt.value);
227
227
  // #343: a header renders once per run of same-`group`
228
228
  // options, so consecutive rows sharing a group render one