@crisp-ui-kit/crisp 0.56.0 → 0.57.1

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 (139) hide show
  1. package/dist/accentpicker.css +5 -0
  2. package/dist/accentpicker.d.ts +7 -0
  3. package/dist/accentpicker.layered.css +7 -0
  4. package/dist/avatar.css +1 -1
  5. package/dist/avatar.layered.css +1 -1
  6. package/dist/badge.css +2 -2
  7. package/dist/badge.layered.css +2 -2
  8. package/dist/button.css +3 -3
  9. package/dist/button.layered.css +3 -3
  10. package/dist/callout.css +6 -0
  11. package/dist/callout.d.ts +2 -2
  12. package/dist/callout.layered.css +6 -0
  13. package/dist/cjs/components/accentpicker/accentpicker.js +66 -0
  14. package/dist/cjs/components/accentpicker/accentpicker.recipe.js +25 -0
  15. package/dist/cjs/components/accentpicker/index.js +7 -0
  16. package/dist/cjs/components/avatar/avatar.recipe.js +4 -1
  17. package/dist/cjs/components/badge/badge.recipe.js +16 -2
  18. package/dist/cjs/components/button/button.recipe.js +16 -6
  19. package/dist/cjs/components/callout/callout.js +2 -2
  20. package/dist/cjs/components/callout/callout.recipe.js +15 -2
  21. package/dist/cjs/components/input/input.js +2 -2
  22. package/dist/cjs/components/input/input.recipe.js +14 -0
  23. package/dist/cjs/components/menu/menu.js +2 -2
  24. package/dist/cjs/components/menu/menu.recipe.js +8 -0
  25. package/dist/cjs/components/multiselect/multiselect.js +23 -10
  26. package/dist/cjs/components/multiselect/multiselect.recipe.js +104 -65
  27. package/dist/cjs/components/progress/progress.js +21 -4
  28. package/dist/cjs/components/progress/progress.recipe.js +30 -0
  29. package/dist/cjs/components/select/listbox-keyboard.js +7 -1
  30. package/dist/cjs/components/select/select.js +24 -11
  31. package/dist/cjs/components/select/select.recipe.js +177 -78
  32. package/dist/cjs/components/tabletoolbar/tabletoolbar.js +3 -3
  33. package/dist/cjs/components/tabletoolbar/tabletoolbar.recipe.js +26 -1
  34. package/dist/cjs/components/tabs/tabs.js +63 -4
  35. package/dist/cjs/components/tabs/tabs.recipe.js +13 -0
  36. package/dist/cjs/components/text/text.recipe.js +8 -7
  37. package/dist/cjs/components/textarea/textarea.js +3 -2
  38. package/dist/cjs/components/textarea/textarea.recipe.js +2 -1
  39. package/dist/cjs/components/textlink/textlink.js +2 -2
  40. package/dist/cjs/components/textlink/textlink.recipe.js +24 -3
  41. package/dist/cjs/index.js +1 -0
  42. package/dist/cjs/product/recordpage/recordpage.js +61 -7
  43. package/dist/cjs/tokens/elevation.js +7 -0
  44. package/dist/cjs/tokens/palette.js +120 -25
  45. package/dist/cjs/tokens/scale.js +9 -0
  46. package/dist/cjs/tokens/semantic.js +42 -0
  47. package/dist/esm/components/accentpicker/accentpicker.js +30 -0
  48. package/dist/esm/components/accentpicker/accentpicker.recipe.js +22 -0
  49. package/dist/esm/components/accentpicker/index.js +2 -0
  50. package/dist/esm/components/avatar/avatar.recipe.js +4 -1
  51. package/dist/esm/components/badge/badge.recipe.js +16 -2
  52. package/dist/esm/components/button/button.recipe.js +16 -6
  53. package/dist/esm/components/callout/callout.js +2 -2
  54. package/dist/esm/components/callout/callout.recipe.js +15 -2
  55. package/dist/esm/components/input/input.js +3 -3
  56. package/dist/esm/components/input/input.recipe.js +14 -0
  57. package/dist/esm/components/menu/menu.js +2 -2
  58. package/dist/esm/components/menu/menu.recipe.js +8 -0
  59. package/dist/esm/components/multiselect/multiselect.js +23 -10
  60. package/dist/esm/components/multiselect/multiselect.recipe.js +104 -65
  61. package/dist/esm/components/progress/progress.js +22 -5
  62. package/dist/esm/components/progress/progress.recipe.js +30 -0
  63. package/dist/esm/components/select/listbox-keyboard.js +7 -1
  64. package/dist/esm/components/select/select.js +24 -11
  65. package/dist/esm/components/select/select.recipe.js +177 -78
  66. package/dist/esm/components/tabletoolbar/tabletoolbar.js +3 -3
  67. package/dist/esm/components/tabletoolbar/tabletoolbar.recipe.js +26 -1
  68. package/dist/esm/components/tabs/tabs.js +63 -4
  69. package/dist/esm/components/tabs/tabs.recipe.js +13 -0
  70. package/dist/esm/components/text/text.recipe.js +8 -7
  71. package/dist/esm/components/textarea/textarea.js +3 -2
  72. package/dist/esm/components/textarea/textarea.recipe.js +2 -1
  73. package/dist/esm/components/textlink/textlink.js +2 -2
  74. package/dist/esm/components/textlink/textlink.recipe.js +24 -3
  75. package/dist/esm/index.js +1 -0
  76. package/dist/esm/product/recordpage/recordpage.js +62 -8
  77. package/dist/esm/tokens/elevation.js +7 -0
  78. package/dist/esm/tokens/palette.js +120 -25
  79. package/dist/esm/tokens/scale.js +9 -0
  80. package/dist/esm/tokens/semantic.js +42 -0
  81. package/dist/input.css +11 -0
  82. package/dist/input.d.ts +2 -2
  83. package/dist/input.layered.css +11 -0
  84. package/dist/menu.css +8 -0
  85. package/dist/menu.layered.css +8 -0
  86. package/dist/multiselect.css +57 -29
  87. package/dist/multiselect.d.ts +3 -1
  88. package/dist/multiselect.layered.css +57 -29
  89. package/dist/native/tokens.d.ts +1 -1
  90. package/dist/native/tokens.js +82 -28
  91. package/dist/progress.css +3 -0
  92. package/dist/progress.layered.css +3 -0
  93. package/dist/select.css +100 -42
  94. package/dist/select.d.ts +3 -2
  95. package/dist/select.layered.css +100 -42
  96. package/dist/styles.css +268 -93
  97. package/dist/styles.layered.css +268 -93
  98. package/dist/tabletoolbar.css +8 -1
  99. package/dist/tabletoolbar.layered.css +8 -1
  100. package/dist/tabs.css +6 -0
  101. package/dist/tabs.layered.css +6 -0
  102. package/dist/text.css +1 -1
  103. package/dist/text.layered.css +1 -1
  104. package/dist/textarea.css +2 -1
  105. package/dist/textarea.layered.css +2 -1
  106. package/dist/textlink.css +11 -2
  107. package/dist/textlink.d.ts +2 -1
  108. package/dist/textlink.layered.css +11 -2
  109. package/dist/tokens.css +43 -11
  110. package/dist/tokens.json +82 -28
  111. package/dist/types/components/accentpicker/accentpicker.d.ts +23 -0
  112. package/dist/types/components/accentpicker/accentpicker.recipe.d.ts +3 -0
  113. package/dist/types/components/accentpicker/index.d.ts +2 -0
  114. package/dist/types/components/button/button.recipe.d.ts +2 -2
  115. package/dist/types/components/callout/callout.d.ts +9 -1
  116. package/dist/types/components/callout/callout.recipe.d.ts +12 -2
  117. package/dist/types/components/input/input.d.ts +14 -0
  118. package/dist/types/components/input/input.recipe.d.ts +3 -0
  119. package/dist/types/components/menu/menu.d.ts +7 -1
  120. package/dist/types/components/multiselect/multiselect.d.ts +25 -6
  121. package/dist/types/components/multiselect/multiselect.recipe.d.ts +27 -0
  122. package/dist/types/components/progress/progress.d.ts +16 -4
  123. package/dist/types/components/progress/progress.recipe.d.ts +22 -0
  124. package/dist/types/components/select/listbox-keyboard.d.ts +10 -1
  125. package/dist/types/components/select/select.d.ts +29 -6
  126. package/dist/types/components/select/select.recipe.d.ts +25 -10
  127. package/dist/types/components/tabletoolbar/tabletoolbar.d.ts +14 -0
  128. package/dist/types/components/tabs/tabs.d.ts +9 -1
  129. package/dist/types/components/textarea/textarea.d.ts +16 -0
  130. package/dist/types/components/textlink/textlink.d.ts +8 -1
  131. package/dist/types/components/textlink/textlink.recipe.d.ts +10 -0
  132. package/dist/types/index.d.ts +1 -0
  133. package/dist/types/product/recordpage/recordpage.d.ts +61 -1
  134. package/dist/types/tokens/contract.d.ts +16 -3
  135. package/dist/types/tokens/elevation.d.ts +7 -0
  136. package/dist/types/tokens/palette.d.ts +55 -11
  137. package/dist/types/tokens/scale.d.ts +6 -0
  138. package/dist/types/tokens/semantic.d.ts +33 -0
  139. package/package.json +6 -1
package/dist/select.css CHANGED
@@ -1,57 +1,78 @@
1
1
 
2
2
  /* pointer-events: auto (#375): a modal Dialog sets pointer-events: none
3
- on document.body while open (Zag re-enables it only on layers IT
4
- tracks), and the portaled listbox is outside Zag's knowledge -- it
5
- inherited the lock, so every option was unclickable, hit-testing
6
- right through to whatever real element sat behind it. */
3
+ on document.body while open, and the portaled listbox is outside its
4
+ knowledge -- it inherited the lock without this. */
7
5
  .crisp-select-listbox {
8
6
  font-family: var(--crisp-font-sans); -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale;
9
- position: fixed; z-index: 70; pointer-events: auto; box-sizing: border-box; display: flex; flex-direction: column; gap: var(--crisp-space-px);
10
- max-height: 280px; overflow-y: auto; padding: var(--crisp-space-1);
11
- border-radius: var(--crisp-radius-xl); background: var(--crisp-bg-popover);
12
- box-shadow: inset 0 0 0 1px var(--crisp-ring-popover), 0 0 0 1px var(--crisp-shadow-popover1), 0 4px 8px -4px var(--crisp-shadow-popover2), 0 4px 12px -2px var(--crisp-shadow-popover3);
13
- transform-origin: top left; opacity: 1; transform: none; transition: opacity .08s ease, transform .08s ease;
7
+ position: fixed; z-index: 70; pointer-events: auto; box-sizing: border-box; display: flex; flex-direction: column; gap: var(--crisp-space-2);
8
+ max-height: 480px; overflow-y: auto; padding: var(--crisp-space-2);
9
+ border-radius: var(--crisp-radius-3xl); background: var(--crisp-bg-select-listbox);
10
+ box-shadow: 0 4px 16px var(--crisp-shadow-select);
11
+ transform-origin: top left; opacity: 1; transform: none;
12
+ transition: opacity .15s cubic-bezier(0,0,.15,1), transform .15s cubic-bezier(0,0,.15,1);
14
13
  }
15
- .crisp-select-listbox[data-up] { transform-origin: bottom; }
16
- .crisp-select-listbox { @starting-style { opacity: 0; transform: scale(0.97) translateY(-1px); } }
17
- .crisp-select-search { display: flex; align-items: center; gap: var(--crisp-space-2); height: 34px; flex-shrink: 0; margin: calc(-1 * var(--crisp-space-1)) calc(-1 * var(--crisp-space-1)) var(--crisp-space-1); padding: 0 var(--crisp-space-2-5); border-bottom: 1px solid var(--crisp-border-subtle); color: var(--crisp-fg-muted); }
14
+ .crisp-select-listbox[data-up] { transform-origin: bottom left; }
15
+ .crisp-select-listbox { @starting-style { opacity: 0; transform: scale(0.95); } }
16
+ /* Composed from @seed-design/rootage's text-input.yaml
17
+ (variant=outline,size=medium) -- seed's Select has no search field of
18
+ its own to resolve this from (see the file header), so this reuses
19
+ the SAME tokens #867 already resolved for the trigger at this size:
20
+ minHeight x10 (var(--crisp-size-selectTriggerMd)), paddingX x3_5
21
+ (var(--crisp-space-3-5)), gap x2 (var(--crisp-space-2)), cornerRadius r2
22
+ (var(--crisp-radius-md)), strokeColor stroke.neutral-weak
23
+ (var(--crisp-stroke-select-trigger)) -- a real bordered field sitting flush
24
+ inside the listbox's own 8px padding, replacing the old bare
25
+ 34px/no-border/bleeds-to-the-edge strip (crisp/#904). */
26
+ .crisp-select-search { display: flex; align-items: center; gap: var(--crisp-space-2); height: var(--crisp-size-selectTriggerMd); flex-shrink: 0; padding: 0 var(--crisp-space-3-5); border: 1px solid var(--crisp-stroke-select-trigger); border-radius: var(--crisp-radius-md); color: var(--crisp-fg-neutral-muted); }
18
27
  .crisp-select-search svg { flex-shrink: 0; }
19
- .crisp-select-search input { flex: 1; min-width: 0; border: none; outline: none; background: none; font-family: inherit; font-size: var(--crisp-text-lg); color: var(--crisp-fg-primary); }
20
- .crisp-select-search input::placeholder { color: var(--crisp-fg-muted); }
21
- .crisp-select-empty { padding: var(--crisp-space-3) var(--crisp-space-2); text-align: center; font-size: var(--crisp-text-md); color: var(--crisp-fg-muted); }
22
- /* #343: same measured values as Menu's own group heading
23
- (crisp-menu-label) -- reused rather than re-measured, since it's the
24
- same "small muted heading above a run of rows" treatment. */
25
- .crisp-select-label { padding: var(--crisp-space-1-5) var(--crisp-space-2) var(--crisp-space-0-5); font-size: var(--crisp-text-sm); font-weight: var(--crisp-weight-medium); color: var(--crisp-fg-muted); }
28
+ .crisp-select-search input { flex: 1; min-width: 0; border: none; outline: none; background: none; font-family: inherit; font-size: var(--crisp-text-lg); line-height: 19px; color: var(--crisp-fg-select-neutral); }
29
+ .crisp-select-search input::placeholder { color: var(--crisp-fg-select-placeholder); }
30
+ .crisp-select-empty { padding: var(--crisp-space-3) var(--crisp-space-4); text-align: center; font-size: var(--crisp-text-md); color: var(--crisp-fg-neutral-subtle); }
31
+ /* select.yaml's groupLabel -- font-size/line-height/weight are the same
32
+ across sizes, only paddingY differs (medium 8px, large 10px). */
33
+ .crisp-select-label { padding: var(--crisp-space-2) var(--crisp-space-4) 0; font-size: var(--crisp-text-md); line-height: 18px; font-weight: var(--crisp-weight-regular); color: var(--crisp-fg-neutral-subtle); }
26
34
  .crisp-select-option {
27
- box-sizing: border-box; display: flex; align-items: center; gap: var(--crisp-space-2); height: var(--crisp-size-menuRow); flex-shrink: 0;
28
- padding: 0 var(--crisp-space-2); margin: 0; border: none; background: none; border-radius: var(--crisp-radius-md);
29
- font-family: inherit; font-size: var(--crisp-text-lg); color: var(--crisp-fg-primary); text-align: start; cursor: pointer; white-space: nowrap;
35
+ box-sizing: border-box; display: flex; align-items: center; flex-shrink: 0;
36
+ padding: var(--crisp-space-2-5) var(--crisp-space-4); margin-inline: 0; border: none; background: none; border-radius: var(--crisp-radius-xl);
37
+ font-family: inherit; color: var(--crisp-fg-select-neutral); text-align: start; cursor: pointer; white-space: nowrap;
38
+ transition: background-color .15s cubic-bezier(.35,0,.35,1), margin-inline .15s cubic-bezier(.35,0,.35,1);
30
39
  }
40
+ /* Keyboard/hover highlight keeps crisp's own visible neutral fill --
41
+ seed's spec has no "highlighted" state at all (only pressed/selected/
42
+ disabled), and its actual pressed tint (below) is a ~3% black hint,
43
+ too faint to serve as a keyboard-navigation indicator. */
31
44
  .crisp-select-option[data-highlighted] { background: var(--crisp-bg-raised); }
32
- .crisp-select-option[data-disabled] { opacity: 0.5; cursor: not-allowed; }
33
- .crisp-select-option-dot { flex-shrink: 0; width: 8px; height: 8px; border-radius: var(--crisp-radius-full); }
45
+ .crisp-select-option:active:not([data-disabled]) { background: var(--crisp-bg-select-pressed); margin-inline: var(--crisp-space-2); }
46
+ .crisp-select-option[data-disabled] { cursor: not-allowed; }
47
+ .crisp-select-option[data-disabled] .crisp-select-option-label,
48
+ .crisp-select-option[data-disabled] .crisp-select-option-description,
49
+ .crisp-select-option[data-disabled] .crisp-select-option-check { color: var(--crisp-fg-disabled); }
50
+ .crisp-select-option-dot { flex-shrink: 0; width: 8px; height: 8px; border-radius: var(--crisp-radius-full); margin-inline-end: var(--crisp-space-2); }
51
+ .crisp-select-option-body { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: var(--crisp-space-0-5); }
34
52
  .crisp-select-option-label { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; }
35
- .crisp-select-option-check { flex-shrink: 0; color: var(--crisp-bg-brand-solid); }
53
+ .crisp-select-option-description { min-width: 0; overflow: hidden; text-overflow: ellipsis; color: var(--crisp-fg-neutral-subtle); }
54
+ .crisp-select-option-check { flex-shrink: 0; color: var(--crisp-fg-select-neutral); margin-inline-start: var(--crisp-space-2); }
36
55
 
37
56
  .crisp-select {
38
57
  position: relative; display: inline-flex; font-family: var(--crisp-font-sans); -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale;
39
- & .crisp-select-trigger { box-sizing: border-box; display: inline-flex; align-items: center; gap: var(--crisp-space-1-5); height: var(--crisp-size-menuRow); width: 100%; min-width: 0; margin: 0; padding: 0 var(--crisp-space-1-5) 0 var(--crisp-space-2); border: none; border-radius: var(--crisp-radius-md); background: transparent; box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--crisp-fg-primary) 7%, transparent); font-family: inherit; font-size: var(--crisp-text-lg); color: var(--crisp-fg-primary); cursor: pointer; transition: background-color .12s ease, box-shadow .12s ease; }
40
- & .crisp-select-trigger:hover { background: var(--crisp-bg-raised); }
41
- & .crisp-select-trigger[data-open], & .crisp-select-trigger:focus-visible { outline: none; box-shadow: inset 0 0 0 1px var(--crisp-bg-brand-solid); background: var(--crisp-bg-surface); }
42
- & .crisp-select-trigger:disabled { opacity: 0.5; cursor: not-allowed; }
43
- & .crisp-select-value { flex: 1; min-width: 0; display: inline-flex; align-items: center; gap: var(--crisp-space-1-5); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; text-align: start; }
44
- & .crisp-select-placeholder { color: var(--crisp-fg-muted); }
45
- & .crisp-select-chevron { flex-shrink: 0; color: var(--crisp-fg-muted); transition: transform .15s ease; }
46
- & .crisp-select-trigger[data-open] .crisp-select-chevron { transform: rotate(180deg); }
47
- }
48
- .crisp-select--variant_default {
49
-
50
- }
51
- .crisp-select--variant_ghost {
52
- & .crisp-select-trigger { background: transparent; box-shadow: none; }
53
- & .crisp-select-trigger:hover { background: var(--crisp-bg-raised); box-shadow: none; }
54
- & .crisp-select-trigger[data-open], & .crisp-select-trigger:focus-visible { background: transparent; box-shadow: inset 0 0 0 1px var(--crisp-bg-brand-solid); }
58
+ & .crisp-select-trigger { box-sizing: border-box; display: inline-flex; align-items: center; width: 100%; min-width: 0; margin: 0; border: 1px solid var(--crisp-stroke-select-trigger); background: transparent; cursor: pointer; font-family: inherit; color: var(--crisp-fg-select-neutral); transition: background-color .15s cubic-bezier(.35,0,.35,1), border-color .1s cubic-bezier(.35,0,.35,1), border-width .1s cubic-bezier(.35,0,.35,1); }
59
+ /* seed defines no hover state for this trigger -- only pressed, invalid, disabled and readonly. The scale-down is scoped to the CONTENT (seed's scaleScope: content), not the button itself, so the border
60
+ never moves. */
61
+ /* Only the value scales, not the chevron: the chevron's own transform is its open/close rotation, and a scale would fight it for one shared CSS property when both are true at once (pressing while open). */
62
+ & .crisp-select-trigger:active:not(:disabled):not([aria-readonly="true"]) { background: var(--crisp-bg-select-pressed); }
63
+ & .crisp-select-trigger:active:not(:disabled):not([aria-readonly="true"]) .crisp-select-value { transform: scale(0.98); }
64
+ & .crisp-select-trigger:focus-visible { outline: 2px solid var(--crisp-stroke-focus-ring); outline-offset: -2px; }
65
+ & .crisp-select-trigger:disabled { background: var(--crisp-bg-select-disabled); cursor: not-allowed; }
66
+ & .crisp-select-trigger:disabled .crisp-select-value, & .crisp-select-trigger:disabled .crisp-select-placeholder, & .crisp-select-trigger:disabled .crisp-select-chevron { color: var(--crisp-fg-disabled); }
67
+ /* readonly: same fill as disabled, but the value/placeholder text stays normal ink -- it reads as "answered and locked", not "answered and
68
+ greyed out" (select-trigger.yaml's readonly state only overrides
69
+ root.color; value/placeholder keep their enabled colours). */
70
+ & .crisp-select-trigger[aria-readonly="true"] { background: var(--crisp-bg-select-disabled); cursor: default; }
71
+ & .crisp-select-value { flex: 1; min-width: 0; display: inline-flex; align-items: center; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; text-align: start; transition: transform .15s cubic-bezier(0,0,.15,1); }
72
+ & .crisp-select-placeholder { color: var(--crisp-fg-select-placeholder); }
73
+ /* closeRotateDuration/closeRotateTimingFunction: 100ms, seed's "easing" curve. Overridden to 150ms on open (openRotateDuration) below -- same curve both ways, only the duration differs. */
74
+ & .crisp-select-chevron { flex-shrink: 0; color: var(--crisp-fg-neutral-muted); transition: transform .1s cubic-bezier(.35,0,.35,1); }
75
+ & .crisp-select-trigger[data-open] .crisp-select-chevron { transform: rotate(180deg); transition-duration: .15s; }
55
76
  }
56
77
  .crisp-select--width_full {
57
78
  width: 100%;
@@ -59,12 +80,49 @@
59
80
  .crisp-select--width_auto {
60
81
  width: auto;
61
82
  }
83
+ .crisp-select--size_small {
84
+ & .crisp-select-trigger { height: var(--crisp-size-menuRow); border-radius: var(--crisp-radius-md); gap: var(--crisp-space-1-5); padding-inline: var(--crisp-space-2-5); }
85
+ & .crisp-select-value, & .crisp-select-placeholder { font-size: var(--crisp-text-lg); line-height: 20px; }
86
+ & .crisp-select-chevron { width: var(--crisp-size-icon); height: var(--crisp-size-icon); }
87
+ .crisp-select-option { gap: var(--crisp-space-2); }
88
+ .crisp-select-option-label { font-size: var(--crisp-text-lg); line-height: 19px; }
89
+ .crisp-select-option-description { font-size: var(--crisp-text-sm); line-height: 16px; }
90
+ .crisp-select-option-check { width: var(--crisp-space-3); height: var(--crisp-space-3); }
91
+ .crisp-select-label { padding-top: var(--crisp-space-2); }
92
+ }
93
+ .crisp-select--size_medium {
94
+ & .crisp-select-trigger { height: var(--crisp-size-selectTriggerMd); border-radius: var(--crisp-radius-md); gap: var(--crisp-space-2); padding-inline: var(--crisp-space-3-5); }
95
+ & .crisp-select-value, & .crisp-select-placeholder { font-size: var(--crisp-text-lg); line-height: 19px; }
96
+ & .crisp-select-chevron { width: var(--crisp-size-icon); height: var(--crisp-size-icon); }
97
+ .crisp-select-option { gap: var(--crisp-space-2); }
98
+ .crisp-select-option-label { font-size: var(--crisp-text-lg); line-height: 19px; }
99
+ .crisp-select-option-description { font-size: var(--crisp-text-sm); line-height: 16px; }
100
+ .crisp-select-option-check { width: var(--crisp-space-3); height: var(--crisp-space-3); }
101
+ .crisp-select-label { padding-top: var(--crisp-space-2); }
102
+ }
103
+ .crisp-select--size_large {
104
+ & .crisp-select-trigger { height: var(--crisp-size-selectTriggerLg); border-radius: var(--crisp-radius-xl); gap: var(--crisp-space-2-5); padding-inline: var(--crisp-space-4); }
105
+ & .crisp-select-value, & .crisp-select-placeholder { font-size: var(--crisp-text-xl); line-height: 22px; }
106
+ & .crisp-select-chevron { width: var(--crisp-size-selectIconLg); height: var(--crisp-size-selectIconLg); }
107
+ .crisp-select-option { gap: var(--crisp-space-3); }
108
+ .crisp-select-option-label { font-size: var(--crisp-text-xl); line-height: 22px; }
109
+ .crisp-select-option-description { font-size: var(--crisp-text-md); line-height: 18px; }
110
+ .crisp-select-option-check { width: var(--crisp-space-3-5); height: var(--crisp-space-3-5); }
111
+ .crisp-select-label { padding-top: var(--crisp-space-2-5); }
112
+ }
62
113
  .crisp-select--invalid_true {
63
- & .crisp-select-trigger, & .crisp-select-trigger:hover, & .crisp-select-trigger[data-open], & .crisp-select-trigger:focus-visible { box-shadow: inset 0 0 0 1px var(--crisp-bg-danger); }
114
+ & .crisp-select-trigger { border-width: 2px; border-color: var(--crisp-stroke-select-invalid); }
64
115
  }
65
116
  .crisp-select--invalid_false {
66
117
 
67
118
  }
119
+ .crisp-select--variant_default {
120
+
121
+ }
122
+ .crisp-select--variant_ghost {
123
+ & .crisp-select-trigger { height: 100%; border-color: transparent; }
124
+ & .crisp-select-trigger:hover:not(:disabled) { background: var(--crisp-bg-select-pressed); }
125
+ }
68
126
 
69
127
  @media (prefers-reduced-motion: reduce) {
70
128
  .crisp-select, .crisp-select * {
package/dist/select.d.ts CHANGED
@@ -1,9 +1,10 @@
1
1
  import type { RecipeFn } from "../core/types";
2
2
 
3
3
  export interface SelectVariants {
4
- variant?: "default" | "ghost";
5
4
  width?: "full" | "auto";
5
+ size?: "small" | "medium" | "large";
6
6
  invalid?: boolean;
7
+ variant?: "default" | "ghost";
7
8
  }
8
9
 
9
- export declare const selectRecipe: RecipeFn<{ variant: { default: string; ghost: string }; width: { full: string; auto: string }; invalid: { true: string; false: string } }>;
10
+ export declare const selectRecipe: RecipeFn<{ width: { full: string; auto: string }; size: { small: string; medium: string; large: string }; invalid: { true: string; false: string }; variant: { default: string; ghost: string } }>;
@@ -1,58 +1,79 @@
1
1
  @layer crisp-components {
2
2
 
3
3
  /* pointer-events: auto (#375): a modal Dialog sets pointer-events: none
4
- on document.body while open (Zag re-enables it only on layers IT
5
- tracks), and the portaled listbox is outside Zag's knowledge -- it
6
- inherited the lock, so every option was unclickable, hit-testing
7
- right through to whatever real element sat behind it. */
4
+ on document.body while open, and the portaled listbox is outside its
5
+ knowledge -- it inherited the lock without this. */
8
6
  .crisp-select-listbox {
9
7
  font-family: var(--crisp-font-sans); -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale;
10
- position: fixed; z-index: 70; pointer-events: auto; box-sizing: border-box; display: flex; flex-direction: column; gap: var(--crisp-space-px);
11
- max-height: 280px; overflow-y: auto; padding: var(--crisp-space-1);
12
- border-radius: var(--crisp-radius-xl); background: var(--crisp-bg-popover);
13
- box-shadow: inset 0 0 0 1px var(--crisp-ring-popover), 0 0 0 1px var(--crisp-shadow-popover1), 0 4px 8px -4px var(--crisp-shadow-popover2), 0 4px 12px -2px var(--crisp-shadow-popover3);
14
- transform-origin: top left; opacity: 1; transform: none; transition: opacity .08s ease, transform .08s ease;
8
+ position: fixed; z-index: 70; pointer-events: auto; box-sizing: border-box; display: flex; flex-direction: column; gap: var(--crisp-space-2);
9
+ max-height: 480px; overflow-y: auto; padding: var(--crisp-space-2);
10
+ border-radius: var(--crisp-radius-3xl); background: var(--crisp-bg-select-listbox);
11
+ box-shadow: 0 4px 16px var(--crisp-shadow-select);
12
+ transform-origin: top left; opacity: 1; transform: none;
13
+ transition: opacity .15s cubic-bezier(0,0,.15,1), transform .15s cubic-bezier(0,0,.15,1);
15
14
  }
16
- .crisp-select-listbox[data-up] { transform-origin: bottom; }
17
- .crisp-select-listbox { @starting-style { opacity: 0; transform: scale(0.97) translateY(-1px); } }
18
- .crisp-select-search { display: flex; align-items: center; gap: var(--crisp-space-2); height: 34px; flex-shrink: 0; margin: calc(-1 * var(--crisp-space-1)) calc(-1 * var(--crisp-space-1)) var(--crisp-space-1); padding: 0 var(--crisp-space-2-5); border-bottom: 1px solid var(--crisp-border-subtle); color: var(--crisp-fg-muted); }
15
+ .crisp-select-listbox[data-up] { transform-origin: bottom left; }
16
+ .crisp-select-listbox { @starting-style { opacity: 0; transform: scale(0.95); } }
17
+ /* Composed from @seed-design/rootage's text-input.yaml
18
+ (variant=outline,size=medium) -- seed's Select has no search field of
19
+ its own to resolve this from (see the file header), so this reuses
20
+ the SAME tokens #867 already resolved for the trigger at this size:
21
+ minHeight x10 (var(--crisp-size-selectTriggerMd)), paddingX x3_5
22
+ (var(--crisp-space-3-5)), gap x2 (var(--crisp-space-2)), cornerRadius r2
23
+ (var(--crisp-radius-md)), strokeColor stroke.neutral-weak
24
+ (var(--crisp-stroke-select-trigger)) -- a real bordered field sitting flush
25
+ inside the listbox's own 8px padding, replacing the old bare
26
+ 34px/no-border/bleeds-to-the-edge strip (crisp/#904). */
27
+ .crisp-select-search { display: flex; align-items: center; gap: var(--crisp-space-2); height: var(--crisp-size-selectTriggerMd); flex-shrink: 0; padding: 0 var(--crisp-space-3-5); border: 1px solid var(--crisp-stroke-select-trigger); border-radius: var(--crisp-radius-md); color: var(--crisp-fg-neutral-muted); }
19
28
  .crisp-select-search svg { flex-shrink: 0; }
20
- .crisp-select-search input { flex: 1; min-width: 0; border: none; outline: none; background: none; font-family: inherit; font-size: var(--crisp-text-lg); color: var(--crisp-fg-primary); }
21
- .crisp-select-search input::placeholder { color: var(--crisp-fg-muted); }
22
- .crisp-select-empty { padding: var(--crisp-space-3) var(--crisp-space-2); text-align: center; font-size: var(--crisp-text-md); color: var(--crisp-fg-muted); }
23
- /* #343: same measured values as Menu's own group heading
24
- (crisp-menu-label) -- reused rather than re-measured, since it's the
25
- same "small muted heading above a run of rows" treatment. */
26
- .crisp-select-label { padding: var(--crisp-space-1-5) var(--crisp-space-2) var(--crisp-space-0-5); font-size: var(--crisp-text-sm); font-weight: var(--crisp-weight-medium); color: var(--crisp-fg-muted); }
29
+ .crisp-select-search input { flex: 1; min-width: 0; border: none; outline: none; background: none; font-family: inherit; font-size: var(--crisp-text-lg); line-height: 19px; color: var(--crisp-fg-select-neutral); }
30
+ .crisp-select-search input::placeholder { color: var(--crisp-fg-select-placeholder); }
31
+ .crisp-select-empty { padding: var(--crisp-space-3) var(--crisp-space-4); text-align: center; font-size: var(--crisp-text-md); color: var(--crisp-fg-neutral-subtle); }
32
+ /* select.yaml's groupLabel -- font-size/line-height/weight are the same
33
+ across sizes, only paddingY differs (medium 8px, large 10px). */
34
+ .crisp-select-label { padding: var(--crisp-space-2) var(--crisp-space-4) 0; font-size: var(--crisp-text-md); line-height: 18px; font-weight: var(--crisp-weight-regular); color: var(--crisp-fg-neutral-subtle); }
27
35
  .crisp-select-option {
28
- box-sizing: border-box; display: flex; align-items: center; gap: var(--crisp-space-2); height: var(--crisp-size-menuRow); flex-shrink: 0;
29
- padding: 0 var(--crisp-space-2); margin: 0; border: none; background: none; border-radius: var(--crisp-radius-md);
30
- font-family: inherit; font-size: var(--crisp-text-lg); color: var(--crisp-fg-primary); text-align: start; cursor: pointer; white-space: nowrap;
36
+ box-sizing: border-box; display: flex; align-items: center; flex-shrink: 0;
37
+ padding: var(--crisp-space-2-5) var(--crisp-space-4); margin-inline: 0; border: none; background: none; border-radius: var(--crisp-radius-xl);
38
+ font-family: inherit; color: var(--crisp-fg-select-neutral); text-align: start; cursor: pointer; white-space: nowrap;
39
+ transition: background-color .15s cubic-bezier(.35,0,.35,1), margin-inline .15s cubic-bezier(.35,0,.35,1);
31
40
  }
41
+ /* Keyboard/hover highlight keeps crisp's own visible neutral fill --
42
+ seed's spec has no "highlighted" state at all (only pressed/selected/
43
+ disabled), and its actual pressed tint (below) is a ~3% black hint,
44
+ too faint to serve as a keyboard-navigation indicator. */
32
45
  .crisp-select-option[data-highlighted] { background: var(--crisp-bg-raised); }
33
- .crisp-select-option[data-disabled] { opacity: 0.5; cursor: not-allowed; }
34
- .crisp-select-option-dot { flex-shrink: 0; width: 8px; height: 8px; border-radius: var(--crisp-radius-full); }
46
+ .crisp-select-option:active:not([data-disabled]) { background: var(--crisp-bg-select-pressed); margin-inline: var(--crisp-space-2); }
47
+ .crisp-select-option[data-disabled] { cursor: not-allowed; }
48
+ .crisp-select-option[data-disabled] .crisp-select-option-label,
49
+ .crisp-select-option[data-disabled] .crisp-select-option-description,
50
+ .crisp-select-option[data-disabled] .crisp-select-option-check { color: var(--crisp-fg-disabled); }
51
+ .crisp-select-option-dot { flex-shrink: 0; width: 8px; height: 8px; border-radius: var(--crisp-radius-full); margin-inline-end: var(--crisp-space-2); }
52
+ .crisp-select-option-body { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: var(--crisp-space-0-5); }
35
53
  .crisp-select-option-label { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; }
36
- .crisp-select-option-check { flex-shrink: 0; color: var(--crisp-bg-brand-solid); }
54
+ .crisp-select-option-description { min-width: 0; overflow: hidden; text-overflow: ellipsis; color: var(--crisp-fg-neutral-subtle); }
55
+ .crisp-select-option-check { flex-shrink: 0; color: var(--crisp-fg-select-neutral); margin-inline-start: var(--crisp-space-2); }
37
56
 
38
57
  .crisp-select {
39
58
  position: relative; display: inline-flex; font-family: var(--crisp-font-sans); -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale;
40
- & .crisp-select-trigger { box-sizing: border-box; display: inline-flex; align-items: center; gap: var(--crisp-space-1-5); height: var(--crisp-size-menuRow); width: 100%; min-width: 0; margin: 0; padding: 0 var(--crisp-space-1-5) 0 var(--crisp-space-2); border: none; border-radius: var(--crisp-radius-md); background: transparent; box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--crisp-fg-primary) 7%, transparent); font-family: inherit; font-size: var(--crisp-text-lg); color: var(--crisp-fg-primary); cursor: pointer; transition: background-color .12s ease, box-shadow .12s ease; }
41
- & .crisp-select-trigger:hover { background: var(--crisp-bg-raised); }
42
- & .crisp-select-trigger[data-open], & .crisp-select-trigger:focus-visible { outline: none; box-shadow: inset 0 0 0 1px var(--crisp-bg-brand-solid); background: var(--crisp-bg-surface); }
43
- & .crisp-select-trigger:disabled { opacity: 0.5; cursor: not-allowed; }
44
- & .crisp-select-value { flex: 1; min-width: 0; display: inline-flex; align-items: center; gap: var(--crisp-space-1-5); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; text-align: start; }
45
- & .crisp-select-placeholder { color: var(--crisp-fg-muted); }
46
- & .crisp-select-chevron { flex-shrink: 0; color: var(--crisp-fg-muted); transition: transform .15s ease; }
47
- & .crisp-select-trigger[data-open] .crisp-select-chevron { transform: rotate(180deg); }
48
- }
49
- .crisp-select--variant_default {
50
-
51
- }
52
- .crisp-select--variant_ghost {
53
- & .crisp-select-trigger { background: transparent; box-shadow: none; }
54
- & .crisp-select-trigger:hover { background: var(--crisp-bg-raised); box-shadow: none; }
55
- & .crisp-select-trigger[data-open], & .crisp-select-trigger:focus-visible { background: transparent; box-shadow: inset 0 0 0 1px var(--crisp-bg-brand-solid); }
59
+ & .crisp-select-trigger { box-sizing: border-box; display: inline-flex; align-items: center; width: 100%; min-width: 0; margin: 0; border: 1px solid var(--crisp-stroke-select-trigger); background: transparent; cursor: pointer; font-family: inherit; color: var(--crisp-fg-select-neutral); transition: background-color .15s cubic-bezier(.35,0,.35,1), border-color .1s cubic-bezier(.35,0,.35,1), border-width .1s cubic-bezier(.35,0,.35,1); }
60
+ /* seed defines no hover state for this trigger -- only pressed, invalid, disabled and readonly. The scale-down is scoped to the CONTENT (seed's scaleScope: content), not the button itself, so the border
61
+ never moves. */
62
+ /* Only the value scales, not the chevron: the chevron's own transform is its open/close rotation, and a scale would fight it for one shared CSS property when both are true at once (pressing while open). */
63
+ & .crisp-select-trigger:active:not(:disabled):not([aria-readonly="true"]) { background: var(--crisp-bg-select-pressed); }
64
+ & .crisp-select-trigger:active:not(:disabled):not([aria-readonly="true"]) .crisp-select-value { transform: scale(0.98); }
65
+ & .crisp-select-trigger:focus-visible { outline: 2px solid var(--crisp-stroke-focus-ring); outline-offset: -2px; }
66
+ & .crisp-select-trigger:disabled { background: var(--crisp-bg-select-disabled); cursor: not-allowed; }
67
+ & .crisp-select-trigger:disabled .crisp-select-value, & .crisp-select-trigger:disabled .crisp-select-placeholder, & .crisp-select-trigger:disabled .crisp-select-chevron { color: var(--crisp-fg-disabled); }
68
+ /* readonly: same fill as disabled, but the value/placeholder text stays normal ink -- it reads as "answered and locked", not "answered and
69
+ greyed out" (select-trigger.yaml's readonly state only overrides
70
+ root.color; value/placeholder keep their enabled colours). */
71
+ & .crisp-select-trigger[aria-readonly="true"] { background: var(--crisp-bg-select-disabled); cursor: default; }
72
+ & .crisp-select-value { flex: 1; min-width: 0; display: inline-flex; align-items: center; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; text-align: start; transition: transform .15s cubic-bezier(0,0,.15,1); }
73
+ & .crisp-select-placeholder { color: var(--crisp-fg-select-placeholder); }
74
+ /* closeRotateDuration/closeRotateTimingFunction: 100ms, seed's "easing" curve. Overridden to 150ms on open (openRotateDuration) below -- same curve both ways, only the duration differs. */
75
+ & .crisp-select-chevron { flex-shrink: 0; color: var(--crisp-fg-neutral-muted); transition: transform .1s cubic-bezier(.35,0,.35,1); }
76
+ & .crisp-select-trigger[data-open] .crisp-select-chevron { transform: rotate(180deg); transition-duration: .15s; }
56
77
  }
57
78
  .crisp-select--width_full {
58
79
  width: 100%;
@@ -60,12 +81,49 @@
60
81
  .crisp-select--width_auto {
61
82
  width: auto;
62
83
  }
84
+ .crisp-select--size_small {
85
+ & .crisp-select-trigger { height: var(--crisp-size-menuRow); border-radius: var(--crisp-radius-md); gap: var(--crisp-space-1-5); padding-inline: var(--crisp-space-2-5); }
86
+ & .crisp-select-value, & .crisp-select-placeholder { font-size: var(--crisp-text-lg); line-height: 20px; }
87
+ & .crisp-select-chevron { width: var(--crisp-size-icon); height: var(--crisp-size-icon); }
88
+ .crisp-select-option { gap: var(--crisp-space-2); }
89
+ .crisp-select-option-label { font-size: var(--crisp-text-lg); line-height: 19px; }
90
+ .crisp-select-option-description { font-size: var(--crisp-text-sm); line-height: 16px; }
91
+ .crisp-select-option-check { width: var(--crisp-space-3); height: var(--crisp-space-3); }
92
+ .crisp-select-label { padding-top: var(--crisp-space-2); }
93
+ }
94
+ .crisp-select--size_medium {
95
+ & .crisp-select-trigger { height: var(--crisp-size-selectTriggerMd); border-radius: var(--crisp-radius-md); gap: var(--crisp-space-2); padding-inline: var(--crisp-space-3-5); }
96
+ & .crisp-select-value, & .crisp-select-placeholder { font-size: var(--crisp-text-lg); line-height: 19px; }
97
+ & .crisp-select-chevron { width: var(--crisp-size-icon); height: var(--crisp-size-icon); }
98
+ .crisp-select-option { gap: var(--crisp-space-2); }
99
+ .crisp-select-option-label { font-size: var(--crisp-text-lg); line-height: 19px; }
100
+ .crisp-select-option-description { font-size: var(--crisp-text-sm); line-height: 16px; }
101
+ .crisp-select-option-check { width: var(--crisp-space-3); height: var(--crisp-space-3); }
102
+ .crisp-select-label { padding-top: var(--crisp-space-2); }
103
+ }
104
+ .crisp-select--size_large {
105
+ & .crisp-select-trigger { height: var(--crisp-size-selectTriggerLg); border-radius: var(--crisp-radius-xl); gap: var(--crisp-space-2-5); padding-inline: var(--crisp-space-4); }
106
+ & .crisp-select-value, & .crisp-select-placeholder { font-size: var(--crisp-text-xl); line-height: 22px; }
107
+ & .crisp-select-chevron { width: var(--crisp-size-selectIconLg); height: var(--crisp-size-selectIconLg); }
108
+ .crisp-select-option { gap: var(--crisp-space-3); }
109
+ .crisp-select-option-label { font-size: var(--crisp-text-xl); line-height: 22px; }
110
+ .crisp-select-option-description { font-size: var(--crisp-text-md); line-height: 18px; }
111
+ .crisp-select-option-check { width: var(--crisp-space-3-5); height: var(--crisp-space-3-5); }
112
+ .crisp-select-label { padding-top: var(--crisp-space-2-5); }
113
+ }
63
114
  .crisp-select--invalid_true {
64
- & .crisp-select-trigger, & .crisp-select-trigger:hover, & .crisp-select-trigger[data-open], & .crisp-select-trigger:focus-visible { box-shadow: inset 0 0 0 1px var(--crisp-bg-danger); }
115
+ & .crisp-select-trigger { border-width: 2px; border-color: var(--crisp-stroke-select-invalid); }
65
116
  }
66
117
  .crisp-select--invalid_false {
67
118
 
68
119
  }
120
+ .crisp-select--variant_default {
121
+
122
+ }
123
+ .crisp-select--variant_ghost {
124
+ & .crisp-select-trigger { height: 100%; border-color: transparent; }
125
+ & .crisp-select-trigger:hover:not(:disabled) { background: var(--crisp-bg-select-pressed); }
126
+ }
69
127
 
70
128
  @media (prefers-reduced-motion: reduce) {
71
129
  .crisp-select, .crisp-select * {