@crisp-ui-kit/crisp 0.55.6 → 0.57.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (112) hide show
  1. package/dist/callout.css +6 -0
  2. package/dist/callout.d.ts +2 -2
  3. package/dist/callout.layered.css +6 -0
  4. package/dist/cjs/components/callout/callout.js +2 -2
  5. package/dist/cjs/components/callout/callout.recipe.js +15 -2
  6. package/dist/cjs/components/input/input.recipe.js +6 -0
  7. package/dist/cjs/components/menu/menu.js +2 -2
  8. package/dist/cjs/components/menu/menu.recipe.js +8 -0
  9. package/dist/cjs/components/multiselect/multiselect.js +23 -10
  10. package/dist/cjs/components/multiselect/multiselect.recipe.js +95 -65
  11. package/dist/cjs/components/select/listbox-keyboard.js +7 -1
  12. package/dist/cjs/components/select/select.js +24 -11
  13. package/dist/cjs/components/select/select.recipe.js +150 -78
  14. package/dist/cjs/components/statusdot/index.js +7 -0
  15. package/dist/cjs/components/statusdot/statusdot.js +15 -0
  16. package/dist/cjs/components/statusdot/statusdot.recipe.js +34 -0
  17. package/dist/cjs/components/tabletoolbar/tabletoolbar.js +3 -3
  18. package/dist/cjs/components/tabletoolbar/tabletoolbar.recipe.js +26 -1
  19. package/dist/cjs/components/tabs/tabs.js +63 -4
  20. package/dist/cjs/components/tabs/tabs.recipe.js +13 -0
  21. package/dist/cjs/components/text/text.recipe.js +10 -0
  22. package/dist/cjs/components/textlink/textlink.js +2 -2
  23. package/dist/cjs/components/textlink/textlink.recipe.js +18 -0
  24. package/dist/cjs/index.js +1 -0
  25. package/dist/cjs/product/recordpage/recordpage.js +17 -4
  26. package/dist/cjs/tokens/elevation.js +7 -0
  27. package/dist/cjs/tokens/palette.js +41 -0
  28. package/dist/cjs/tokens/scale.js +16 -1
  29. package/dist/cjs/tokens/semantic.js +18 -0
  30. package/dist/esm/components/callout/callout.js +2 -2
  31. package/dist/esm/components/callout/callout.recipe.js +15 -2
  32. package/dist/esm/components/input/input.recipe.js +6 -0
  33. package/dist/esm/components/menu/menu.js +2 -2
  34. package/dist/esm/components/menu/menu.recipe.js +8 -0
  35. package/dist/esm/components/multiselect/multiselect.js +23 -10
  36. package/dist/esm/components/multiselect/multiselect.recipe.js +95 -65
  37. package/dist/esm/components/select/listbox-keyboard.js +7 -1
  38. package/dist/esm/components/select/select.js +24 -11
  39. package/dist/esm/components/select/select.recipe.js +150 -78
  40. package/dist/esm/components/statusdot/index.js +2 -0
  41. package/dist/esm/components/statusdot/statusdot.js +12 -0
  42. package/dist/esm/components/statusdot/statusdot.recipe.js +31 -0
  43. package/dist/esm/components/tabletoolbar/tabletoolbar.js +3 -3
  44. package/dist/esm/components/tabletoolbar/tabletoolbar.recipe.js +26 -1
  45. package/dist/esm/components/tabs/tabs.js +63 -4
  46. package/dist/esm/components/tabs/tabs.recipe.js +13 -0
  47. package/dist/esm/components/text/text.recipe.js +10 -0
  48. package/dist/esm/components/textlink/textlink.js +2 -2
  49. package/dist/esm/components/textlink/textlink.recipe.js +18 -0
  50. package/dist/esm/index.js +1 -0
  51. package/dist/esm/product/recordpage/recordpage.js +18 -5
  52. package/dist/esm/tokens/elevation.js +7 -0
  53. package/dist/esm/tokens/palette.js +41 -0
  54. package/dist/esm/tokens/scale.js +16 -1
  55. package/dist/esm/tokens/semantic.js +18 -0
  56. package/dist/input.css +4 -0
  57. package/dist/input.d.ts +2 -2
  58. package/dist/input.layered.css +4 -0
  59. package/dist/menu.css +8 -0
  60. package/dist/menu.layered.css +8 -0
  61. package/dist/multiselect.css +53 -29
  62. package/dist/multiselect.d.ts +3 -1
  63. package/dist/multiselect.layered.css +53 -29
  64. package/dist/native/tokens.d.ts +1 -1
  65. package/dist/native/tokens.js +50 -7
  66. package/dist/select.css +80 -42
  67. package/dist/select.d.ts +3 -2
  68. package/dist/select.layered.css +80 -42
  69. package/dist/statusdot.css +3 -0
  70. package/dist/statusdot.d.ts +7 -0
  71. package/dist/statusdot.layered.css +5 -0
  72. package/dist/styles.css +238 -73
  73. package/dist/styles.layered.css +238 -73
  74. package/dist/tabletoolbar.css +8 -1
  75. package/dist/tabletoolbar.layered.css +8 -1
  76. package/dist/tabs.css +6 -0
  77. package/dist/tabs.layered.css +6 -0
  78. package/dist/text.css +6 -0
  79. package/dist/text.d.ts +2 -2
  80. package/dist/text.layered.css +6 -0
  81. package/dist/textlink.css +9 -0
  82. package/dist/textlink.d.ts +2 -1
  83. package/dist/textlink.layered.css +9 -0
  84. package/dist/tokens.css +54 -1
  85. package/dist/tokens.json +50 -7
  86. package/dist/types/components/callout/callout.d.ts +9 -1
  87. package/dist/types/components/callout/callout.recipe.d.ts +12 -2
  88. package/dist/types/components/input/input.d.ts +2 -0
  89. package/dist/types/components/input/input.recipe.d.ts +3 -0
  90. package/dist/types/components/menu/menu.d.ts +7 -1
  91. package/dist/types/components/multiselect/multiselect.d.ts +24 -6
  92. package/dist/types/components/multiselect/multiselect.recipe.d.ts +24 -0
  93. package/dist/types/components/select/listbox-keyboard.d.ts +10 -1
  94. package/dist/types/components/select/select.d.ts +28 -6
  95. package/dist/types/components/select/select.recipe.d.ts +22 -10
  96. package/dist/types/components/statusdot/index.d.ts +2 -0
  97. package/dist/types/components/statusdot/statusdot.d.ts +21 -0
  98. package/dist/types/components/statusdot/statusdot.recipe.d.ts +15 -0
  99. package/dist/types/components/tabletoolbar/tabletoolbar.d.ts +14 -0
  100. package/dist/types/components/tabs/tabs.d.ts +9 -1
  101. package/dist/types/components/text/text.d.ts +4 -0
  102. package/dist/types/components/text/text.recipe.d.ts +12 -0
  103. package/dist/types/components/textlink/textlink.d.ts +8 -1
  104. package/dist/types/components/textlink/textlink.recipe.d.ts +10 -0
  105. package/dist/types/index.d.ts +1 -0
  106. package/dist/types/product/recordpage/recordpage.d.ts +25 -1
  107. package/dist/types/tokens/contract.d.ts +13 -3
  108. package/dist/types/tokens/elevation.d.ts +7 -0
  109. package/dist/types/tokens/palette.d.ts +36 -0
  110. package/dist/types/tokens/scale.d.ts +6 -1
  111. package/dist/types/tokens/semantic.d.ts +18 -0
  112. package/package.json +6 -1
@@ -1,37 +1,41 @@
1
1
  @layer crisp-components {
2
2
 
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. */
3
+ /* pointer-events: auto (#375) -- see Select's matching comment. */
8
4
  .crisp-multiselect-listbox {
9
5
  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: 300px; 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;
6
+ position: fixed; z-index: 70; pointer-events: auto; box-sizing: border-box; display: flex; flex-direction: column; gap: var(--crisp-space-2);
7
+ max-height: 480px; overflow-y: auto; padding: var(--crisp-space-2);
8
+ border-radius: var(--crisp-radius-3xl); background: var(--crisp-bg-select-listbox);
9
+ box-shadow: 0 4px 16px var(--crisp-shadow-select);
10
+ transform-origin: top left; opacity: 1; transform: none;
11
+ transition: opacity .15s cubic-bezier(0,0,.15,1), transform .15s cubic-bezier(0,0,.15,1);
15
12
  }
16
13
  .crisp-multiselect-listbox[data-up] { transform-origin: bottom left; }
17
- .crisp-multiselect-listbox { @starting-style { opacity: 0; transform: scale(0.97) translateY(-1px); } }
18
- .crisp-multiselect-search { display: flex; align-items: center; gap: var(--crisp-space-1-5); height: 36px; flex-shrink: 0; margin: calc(-1 * var(--crisp-space-1)) calc(-1 * var(--crisp-space-1)) var(--crisp-space-1); padding: var(--crisp-space-2) var(--crisp-space-3); color: var(--crisp-fg-muted); }
14
+ .crisp-multiselect-listbox { @starting-style { opacity: 0; transform: scale(0.95); } }
15
+ /* LOCKED geometry (#481, guarded by build/spec-check.mjs): height 36px,
16
+ gap space["1.5"] (6px), no border-bottom -- an Attio measurement, not
17
+ part of this seed-parity pass. Only colours below follow Select. */
18
+ .crisp-multiselect-search { display: flex; align-items: center; gap: var(--crisp-space-1-5); height: 36px; flex-shrink: 0; margin: calc(-1 * var(--crisp-space-2)) calc(-1 * var(--crisp-space-2)) var(--crisp-space-2); padding: var(--crisp-space-2) var(--crisp-space-3); color: var(--crisp-fg-neutral-muted); }
19
19
  .crisp-multiselect-search svg { flex-shrink: 0; }
20
- .crisp-multiselect-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-multiselect-search input::placeholder { 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-multiselect-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); }
20
+ .crisp-multiselect-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-select-neutral); }
21
+ .crisp-multiselect-search input::placeholder { color: var(--crisp-fg-select-placeholder); }
22
+ /* Same groupLabel treatment as Select's (13px/regular/neutral-subtle). */
23
+ .crisp-multiselect-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
24
  .crisp-multiselect-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); font-family: inherit; cursor: pointer; text-align: start;
25
+ box-sizing: border-box; display: flex; align-items: center; gap: var(--crisp-space-2); flex-shrink: 0;
26
+ padding: var(--crisp-space-2-5) var(--crisp-space-4); margin: 0; border: none; background: none; border-radius: var(--crisp-radius-xl);
27
+ font-family: inherit; text-align: start; cursor: pointer;
28
+ transition: background-color .15s cubic-bezier(.35,0,.35,1);
29
29
  }
30
+ /* Keyboard/hover highlight keeps crisp's own visible neutral fill, same
31
+ reasoning as Select's matching rule (seed's own pressed tint is too
32
+ faint for a keyboard-navigation indicator). */
30
33
  .crisp-multiselect-option[data-highlighted] { background: var(--crisp-bg-raised); }
34
+ .crisp-multiselect-option:active { background: var(--crisp-bg-select-pressed); }
31
35
  .crisp-multiselect-option-chip { flex: 0 1 auto; min-width: 0; overflow: hidden; }
32
36
  .crisp-multiselect-check { flex-shrink: 0; margin-inline-start: auto; display: inline-flex; align-items: center; justify-content: center; width: 16px; height: 16px; border-radius: var(--crisp-radius-sm); box-shadow: inset 0 0 0 1.5px var(--crisp-border-default); color: transparent; }
33
37
  .crisp-multiselect-option[aria-selected="true"] .crisp-multiselect-check { background: var(--crisp-bg-brand-solid); box-shadow: none; color: #fff; }
34
- .crisp-multiselect-empty { padding: var(--crisp-space-3) var(--crisp-space-2); text-align: center; font-size: var(--crisp-text-md); color: var(--crisp-fg-muted); }
38
+ .crisp-multiselect-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); }
35
39
 
36
40
  .crisp-recordstable-tag { display: inline-flex; align-items: center; height: var(--crisp-size-chip); padding: 0 var(--crisp-space-1); border-radius: var(--crisp-radius-chip); font-size: var(--crisp-text-lg); line-height: 20px; font-weight: var(--crisp-weight-medium); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; background: color-mix(in srgb, var(--crisp-fg-primary) 6%, transparent); color: color-mix(in srgb, var(--crisp-fg-primary) 82%, transparent); box-shadow: inset 0 0 0 1px var(--crisp-border-subtle); }
37
41
  .crisp-recordstable-tag[data-tone="brand"] { background: color-mix(in srgb, var(--crisp-bg-brand-solid) 14%, transparent); color: color-mix(in srgb, var(--crisp-bg-brand-solid) 82%, var(--crisp-fg-primary)); box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--crisp-bg-brand-solid) 30%, transparent); }
@@ -44,21 +48,41 @@
44
48
 
45
49
  .crisp-multiselect {
46
50
  position: relative; display: inline-flex; width: 100%; font-family: var(--crisp-font-sans); -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale;
47
- & .crisp-multiselect-trigger { box-sizing: border-box; display: flex; align-items: center; gap: var(--crisp-space-1); width: 100%; min-width: 0; min-height: var(--crisp-size-menuRow); margin: 0; padding: var(--crisp-space-0-5) var(--crisp-space-1-5) var(--crisp-space-0-5) 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; text-align: start; transition: background-color .12s ease, box-shadow .12s ease; }
48
- & .crisp-multiselect-trigger:hover { background: var(--crisp-bg-raised); }
49
- & .crisp-multiselect-trigger[data-open], & .crisp-multiselect-trigger:focus-visible { outline: none; box-shadow: inset 0 0 0 1px var(--crisp-bg-brand-solid); background: var(--crisp-bg-surface); }
50
- & .crisp-multiselect-trigger:disabled { opacity: 0.5; cursor: not-allowed; }
51
+ & .crisp-multiselect-trigger { box-sizing: border-box; display: flex; align-items: center; width: 100%; min-width: 0; margin: 0; border: 1px solid var(--crisp-stroke-select-trigger); background: transparent; cursor: pointer; text-align: start; 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); }
52
+ /* Same pressed/disabled/readonly/focus model as Select's trigger -- see
53
+ that recipe's comment for why hover and an open/focus ring are gone. */
54
+ & .crisp-multiselect-trigger:active:not(:disabled):not([aria-readonly="true"]) { background: var(--crisp-bg-select-pressed); }
55
+ & .crisp-multiselect-trigger:focus-visible { outline: 2px solid var(--crisp-stroke-focus-ring); outline-offset: -2px; }
56
+ & .crisp-multiselect-trigger:disabled { background: var(--crisp-bg-select-disabled); cursor: not-allowed; }
57
+ & .crisp-multiselect-trigger:disabled .crisp-multiselect-placeholder, & .crisp-multiselect-trigger:disabled .crisp-multiselect-chevron { color: var(--crisp-fg-disabled); }
58
+ & .crisp-multiselect-trigger[aria-readonly="true"] { background: var(--crisp-bg-select-disabled); cursor: default; }
51
59
  & .crisp-multiselect-chips { flex: 1; min-width: 0; display: flex; flex-wrap: wrap; align-items: center; gap: var(--crisp-space-1); }
52
- & .crisp-multiselect-placeholder { color: var(--crisp-fg-muted); }
53
- & .crisp-multiselect-chevron { flex-shrink: 0; color: var(--crisp-fg-muted); transition: transform .15s ease; }
54
- & .crisp-multiselect-trigger[data-open] .crisp-multiselect-chevron { transform: rotate(180deg); }
60
+ & .crisp-multiselect-placeholder { color: var(--crisp-fg-select-placeholder); }
61
+ /* closeRotateDuration/openRotateDuration, same asymmetric timing as Select's own chevron (see that recipe's comment). */
62
+ & .crisp-multiselect-chevron { flex-shrink: 0; color: var(--crisp-fg-neutral-muted); transition: transform .1s cubic-bezier(.35,0,.35,1); }
63
+ & .crisp-multiselect-trigger[data-open] .crisp-multiselect-chevron { transform: rotate(180deg); transition-duration: .15s; }
64
+ }
65
+ .crisp-multiselect--size_medium {
66
+ & .crisp-multiselect-trigger { min-height: var(--crisp-size-selectTriggerMd); border-radius: var(--crisp-radius-md); gap: var(--crisp-space-2); padding: var(--crisp-space-1) var(--crisp-space-3-5); }
67
+ & .crisp-multiselect-chevron { width: var(--crisp-size-icon); height: var(--crisp-size-icon); }
68
+ }
69
+ .crisp-multiselect--size_large {
70
+ & .crisp-multiselect-trigger { min-height: var(--crisp-size-selectTriggerLg); border-radius: var(--crisp-radius-xl); gap: var(--crisp-space-2-5); padding: var(--crisp-space-1-5) var(--crisp-space-4); }
71
+ & .crisp-multiselect-chevron { width: var(--crisp-size-selectIconLg); height: var(--crisp-size-selectIconLg); }
55
72
  }
56
73
  .crisp-multiselect--invalid_true {
57
- & .crisp-multiselect-trigger, & .crisp-multiselect-trigger:hover, & .crisp-multiselect-trigger[data-open], & .crisp-multiselect-trigger:focus-visible { box-shadow: inset 0 0 0 1px var(--crisp-bg-danger); }
74
+ & .crisp-multiselect-trigger { border-width: 2px; border-color: var(--crisp-stroke-select-invalid); }
58
75
  }
59
76
  .crisp-multiselect--invalid_false {
60
77
 
61
78
  }
79
+ .crisp-multiselect--variant_default {
80
+
81
+ }
82
+ .crisp-multiselect--variant_ghost {
83
+ & .crisp-multiselect-trigger { min-height: 0; height: 100%; border-color: transparent; }
84
+ & .crisp-multiselect-trigger:hover:not(:disabled) { background: var(--crisp-bg-select-pressed); }
85
+ }
62
86
 
63
87
  @media (prefers-reduced-motion: reduce) {
64
88
  .crisp-multiselect, .crisp-multiselect * {
@@ -2,7 +2,7 @@
2
2
  // The same tokens the CSS custom properties carry, resolved per theme, with
3
3
  // dimensions as numbers for React Native and Lynx style objects.
4
4
  export type Theme = "light" | "dark";
5
- export type ColorTokens = Record<"bgBrandSolid" | "bgBrandSolidPressed" | "fgOnBrand" | "fgOnTone" | "bgSurface" | "bgRaised" | "bgSurfaceMuted" | "borderDefault" | "borderSubtle" | "borderDropzone" | "borderDivider" | "ringNeutral" | "ringField" | "ringSwitch" | "bgTileActive" | "shadowTileActive" | "bgScrim" | "fgAlertDescription" | "bgOverlayFrost" | "shadowOverlay1" | "shadowOverlay2" | "shadowOverlay3" | "bgPopover" | "ringPopover" | "shadowPopover1" | "shadowPopover2" | "shadowPopover3" | "fgPrimary" | "fgMuted" | "bgDanger" | "bgDangerSolid" | "fgDanger" | "strokeFieldFocus" | "fgDisabled" | "bgNeutralWeak" | "bgNeutralInverted" | "fgOnNeutralInverted" | "fgNeutralSubtle" | "fgNeutralMuted" | "strokeFocusRing" | "bgSuccess" | "bgWarning" | "bgOrange" | "bgCyan" | "bgPurple" | "bgSidebar" | "bgSubtle" | "fgBrandSoft" | "fgSuccessSoft" | "fgWarningSoft" | "fgDangerSoft" | "fgNeutralSoft" | "bgPurpleSoft" | "fgPurpleSoft" | "ringPurpleSoft", string>;
5
+ export type ColorTokens = Record<"bgBrandSolid" | "bgBrandSolidPressed" | "fgOnBrand" | "fgOnTone" | "bgSurface" | "bgRaised" | "bgSurfaceMuted" | "borderDefault" | "borderSubtle" | "borderDropzone" | "borderDivider" | "ringNeutral" | "ringField" | "ringSwitch" | "bgTileActive" | "shadowTileActive" | "bgScrim" | "fgAlertDescription" | "bgOverlayFrost" | "shadowOverlay1" | "shadowOverlay2" | "shadowOverlay3" | "bgPopover" | "ringPopover" | "shadowPopover1" | "shadowPopover2" | "shadowPopover3" | "fgPrimary" | "fgMuted" | "bgDanger" | "bgDangerSolid" | "fgDanger" | "strokeFieldFocus" | "fgDisabled" | "bgNeutralWeak" | "bgNeutralInverted" | "fgOnNeutralInverted" | "fgNeutralSubtle" | "fgNeutralMuted" | "strokeFocusRing" | "bgSuccess" | "bgWarning" | "bgOrange" | "bgCyan" | "bgPurple" | "bgSidebar" | "bgSubtle" | "fgBrandSoft" | "fgSuccessSoft" | "fgWarningSoft" | "fgDangerSoft" | "fgNeutralSoft" | "bgPurpleSoft" | "fgPurpleSoft" | "ringPurpleSoft" | "ringBrandSoft" | "fgSelectNeutral" | "bgSelectListbox" | "strokeSelectTrigger" | "bgSelectPressed" | "bgSelectDisabled" | "strokeSelectInvalid" | "strokeSelectDivider" | "fgSelectPlaceholder" | "shadowSelect", string>;
6
6
  export declare const tokens: {
7
7
  color: Record<Theme, ColorTokens>;
8
8
  palette: Record<Theme, Record<string, string>>;
@@ -58,7 +58,17 @@ export const tokens = {
58
58
  "fgNeutralSoft": "#505155",
59
59
  "bgPurpleSoft": "#f5f0ff",
60
60
  "fgPurpleSoft": "#6238b5",
61
- "ringPurpleSoft": "#e8ddfe"
61
+ "ringPurpleSoft": "#e8ddfe",
62
+ "ringBrandSoft": "#d6e5ff",
63
+ "fgSelectNeutral": "#1a1c20",
64
+ "bgSelectListbox": "#ffffff",
65
+ "strokeSelectTrigger": "#dcdee3",
66
+ "bgSelectPressed": "#00000007",
67
+ "bgSelectDisabled": "#f3f4f5",
68
+ "strokeSelectInvalid": "#fa342c",
69
+ "strokeSelectDivider": "#00000010",
70
+ "fgSelectPlaceholder": "#b0b3ba",
71
+ "shadowSelect": "#0000001f"
62
72
  },
63
73
  "dark": {
64
74
  "bgBrandSolid": "#266df0",
@@ -115,7 +125,17 @@ export const tokens = {
115
125
  "fgNeutralSoft": "#9a9aa2",
116
126
  "bgPurpleSoft": "#2f1e5a",
117
127
  "fgPurpleSoft": "#d8c4ff",
118
- "ringPurpleSoft": "#45297d"
128
+ "ringPurpleSoft": "#45297d",
129
+ "ringBrandSoft": "#212224",
130
+ "fgSelectNeutral": "#f3f4f5",
131
+ "bgSelectListbox": "#1d2025",
132
+ "strokeSelectTrigger": "#393d46",
133
+ "bgSelectPressed": "#ffffff0d",
134
+ "bgSelectDisabled": "#2b2e35",
135
+ "strokeSelectInvalid": "#ff6e60",
136
+ "strokeSelectDivider": "#ffffff17",
137
+ "fgSelectPlaceholder": "#868b94",
138
+ "shadowSelect": "#000000cc"
119
139
  }
120
140
  },
121
141
  "palette": {
@@ -132,6 +152,7 @@ export const tokens = {
132
152
  "ringNeutral": "transparent",
133
153
  "ringField": "rgba(0,0,0,0.06)",
134
154
  "ringSwitch": "#d9dade",
155
+ "ringBrandSoft": "#d6e5ff",
135
156
  "tileActive": "#ffffff",
136
157
  "tileActiveShadow": "rgba(0,0,0,0.04)",
137
158
  "bgScrim": "rgba(0,0,0,0.25)",
@@ -172,7 +193,15 @@ export const tokens = {
172
193
  "softNeutralText": "#505155",
173
194
  "purpleTint": "#f5f0ff",
174
195
  "softPurpleText": "#6238b5",
175
- "purpleRing": "#e8ddfe"
196
+ "purpleRing": "#e8ddfe",
197
+ "selectListboxBg": "#ffffff",
198
+ "selectTriggerStroke": "#dcdee3",
199
+ "selectPressedTint": "#00000007",
200
+ "selectDisabledBg": "#f3f4f5",
201
+ "selectInvalidStroke": "#fa342c",
202
+ "selectDivider": "#00000010",
203
+ "selectPlaceholder": "#b0b3ba",
204
+ "selectShadow": "#0000001f"
176
205
  },
177
206
  "dark": {
178
207
  "gray00": "#0a0a0a",
@@ -187,6 +216,7 @@ export const tokens = {
187
216
  "ringNeutral": "#212224",
188
217
  "ringField": "#212224",
189
218
  "ringSwitch": "#212224",
219
+ "ringBrandSoft": "#212224",
190
220
  "tileActive": "#101112",
191
221
  "tileActiveShadow": "rgba(0,0,0,0)",
192
222
  "bgScrim": "rgba(0,0,0,0.55)",
@@ -227,7 +257,15 @@ export const tokens = {
227
257
  "softNeutralText": "#9a9aa2",
228
258
  "purpleTint": "#2f1e5a",
229
259
  "softPurpleText": "#d8c4ff",
230
- "purpleRing": "#45297d"
260
+ "purpleRing": "#45297d",
261
+ "selectListboxBg": "#1d2025",
262
+ "selectTriggerStroke": "#393d46",
263
+ "selectPressedTint": "#ffffff0d",
264
+ "selectDisabledBg": "#2b2e35",
265
+ "selectInvalidStroke": "#ff6e60",
266
+ "selectDivider": "#ffffff17",
267
+ "selectPlaceholder": "#868b94",
268
+ "selectShadow": "#000000cc"
231
269
  }
232
270
  },
233
271
  "space": {
@@ -241,7 +279,8 @@ export const tokens = {
241
279
  "px": 1,
242
280
  "0.5": 2,
243
281
  "1.5": 6,
244
- "2.5": 10
282
+ "2.5": 10,
283
+ "3.5": 14
245
284
  },
246
285
  "radius": {
247
286
  "chip": 7,
@@ -250,6 +289,7 @@ export const tokens = {
250
289
  "lg": 9,
251
290
  "xl": 12,
252
291
  "2xl": 16,
292
+ "3xl": 20,
253
293
  "full": 999
254
294
  },
255
295
  "size": {
@@ -261,7 +301,10 @@ export const tokens = {
261
301
  "tableHeader": 40,
262
302
  "header": 48,
263
303
  "calendarCell": 34,
264
- "calendarPill": 32
304
+ "calendarPill": 32,
305
+ "selectTriggerMd": 40,
306
+ "selectTriggerLg": 52,
307
+ "selectIconLg": 20
265
308
  },
266
309
  "text": {
267
310
  "xs": 11,
@@ -271,7 +314,7 @@ export const tokens = {
271
314
  "xl": 16
272
315
  },
273
316
  "font": {
274
- "sans": "\"Inter\", system-ui, -apple-system, \"Segoe UI\", sans-serif"
317
+ "sans": "\"VK Sans Display\", \"Inter\", system-ui, -apple-system, \"Segoe UI\", sans-serif"
275
318
  },
276
319
  "weight": {
277
320
  "regular": "400",
package/dist/select.css CHANGED
@@ -1,57 +1,68 @@
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
+ .crisp-select-search { display: flex; align-items: center; gap: var(--crisp-space-2); height: 34px; flex-shrink: 0; margin: calc(-1 * var(--crisp-space-2)) calc(-1 * var(--crisp-space-2)) var(--crisp-space-2); padding: 0 var(--crisp-space-2-5); border-bottom: 1px solid var(--crisp-border-subtle); color: var(--crisp-fg-neutral-muted); }
18
17
  .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); }
18
+ .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-select-neutral); }
19
+ .crisp-select-search input::placeholder { color: var(--crisp-fg-select-placeholder); }
20
+ .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); }
21
+ /* select.yaml's groupLabel -- font-size/line-height/weight are the same
22
+ across sizes, only paddingY differs (medium 8px, large 10px). */
23
+ .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
24
  .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;
25
+ box-sizing: border-box; display: flex; align-items: center; flex-shrink: 0;
26
+ padding: var(--crisp-space-2-5) var(--crisp-space-4); margin-inline: 0; border: none; background: none; border-radius: var(--crisp-radius-xl);
27
+ font-family: inherit; color: var(--crisp-fg-select-neutral); text-align: start; cursor: pointer; white-space: nowrap;
28
+ transition: background-color .15s cubic-bezier(.35,0,.35,1), margin-inline .15s cubic-bezier(.35,0,.35,1);
30
29
  }
30
+ /* Keyboard/hover highlight keeps crisp's own visible neutral fill --
31
+ seed's spec has no "highlighted" state at all (only pressed/selected/
32
+ disabled), and its actual pressed tint (below) is a ~3% black hint,
33
+ too faint to serve as a keyboard-navigation indicator. */
31
34
  .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); }
35
+ .crisp-select-option:active:not([data-disabled]) { background: var(--crisp-bg-select-pressed); margin-inline: var(--crisp-space-2); }
36
+ .crisp-select-option[data-disabled] { cursor: not-allowed; }
37
+ .crisp-select-option[data-disabled] .crisp-select-option-label,
38
+ .crisp-select-option[data-disabled] .crisp-select-option-description,
39
+ .crisp-select-option[data-disabled] .crisp-select-option-check { color: var(--crisp-fg-disabled); }
40
+ .crisp-select-option-dot { flex-shrink: 0; width: 8px; height: 8px; border-radius: var(--crisp-radius-full); margin-inline-end: var(--crisp-space-2); }
41
+ .crisp-select-option-body { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: var(--crisp-space-0-5); }
34
42
  .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); }
43
+ .crisp-select-option-description { min-width: 0; overflow: hidden; text-overflow: ellipsis; color: var(--crisp-fg-neutral-subtle); }
44
+ .crisp-select-option-check { flex-shrink: 0; color: var(--crisp-fg-select-neutral); margin-inline-start: var(--crisp-space-2); }
36
45
 
37
46
  .crisp-select {
38
47
  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); }
48
+ & .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); }
49
+ /* 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
50
+ never moves. */
51
+ /* 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). */
52
+ & .crisp-select-trigger:active:not(:disabled):not([aria-readonly="true"]) { background: var(--crisp-bg-select-pressed); }
53
+ & .crisp-select-trigger:active:not(:disabled):not([aria-readonly="true"]) .crisp-select-value { transform: scale(0.98); }
54
+ & .crisp-select-trigger:focus-visible { outline: 2px solid var(--crisp-stroke-focus-ring); outline-offset: -2px; }
55
+ & .crisp-select-trigger:disabled { background: var(--crisp-bg-select-disabled); cursor: not-allowed; }
56
+ & .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); }
57
+ /* readonly: same fill as disabled, but the value/placeholder text stays normal ink -- it reads as "answered and locked", not "answered and
58
+ greyed out" (select-trigger.yaml's readonly state only overrides
59
+ root.color; value/placeholder keep their enabled colours). */
60
+ & .crisp-select-trigger[aria-readonly="true"] { background: var(--crisp-bg-select-disabled); cursor: default; }
61
+ & .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); }
62
+ & .crisp-select-placeholder { color: var(--crisp-fg-select-placeholder); }
63
+ /* closeRotateDuration/closeRotateTimingFunction: 100ms, seed's "easing" curve. Overridden to 150ms on open (openRotateDuration) below -- same curve both ways, only the duration differs. */
64
+ & .crisp-select-chevron { flex-shrink: 0; color: var(--crisp-fg-neutral-muted); transition: transform .1s cubic-bezier(.35,0,.35,1); }
65
+ & .crisp-select-trigger[data-open] .crisp-select-chevron { transform: rotate(180deg); transition-duration: .15s; }
55
66
  }
56
67
  .crisp-select--width_full {
57
68
  width: 100%;
@@ -59,12 +70,39 @@
59
70
  .crisp-select--width_auto {
60
71
  width: auto;
61
72
  }
73
+ .crisp-select--size_medium {
74
+ & .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); }
75
+ & .crisp-select-value, & .crisp-select-placeholder { font-size: var(--crisp-text-lg); line-height: 19px; }
76
+ & .crisp-select-chevron { width: var(--crisp-size-icon); height: var(--crisp-size-icon); }
77
+ .crisp-select-option { gap: var(--crisp-space-2); }
78
+ .crisp-select-option-label { font-size: var(--crisp-text-lg); line-height: 19px; }
79
+ .crisp-select-option-description { font-size: var(--crisp-text-sm); line-height: 16px; }
80
+ .crisp-select-option-check { width: var(--crisp-space-3); height: var(--crisp-space-3); }
81
+ .crisp-select-label { padding-top: var(--crisp-space-2); }
82
+ }
83
+ .crisp-select--size_large {
84
+ & .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); }
85
+ & .crisp-select-value, & .crisp-select-placeholder { font-size: var(--crisp-text-xl); line-height: 22px; }
86
+ & .crisp-select-chevron { width: var(--crisp-size-selectIconLg); height: var(--crisp-size-selectIconLg); }
87
+ .crisp-select-option { gap: var(--crisp-space-3); }
88
+ .crisp-select-option-label { font-size: var(--crisp-text-xl); line-height: 22px; }
89
+ .crisp-select-option-description { font-size: var(--crisp-text-md); line-height: 18px; }
90
+ .crisp-select-option-check { width: var(--crisp-space-3-5); height: var(--crisp-space-3-5); }
91
+ .crisp-select-label { padding-top: var(--crisp-space-2-5); }
92
+ }
62
93
  .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); }
94
+ & .crisp-select-trigger { border-width: 2px; border-color: var(--crisp-stroke-select-invalid); }
64
95
  }
65
96
  .crisp-select--invalid_false {
66
97
 
67
98
  }
99
+ .crisp-select--variant_default {
100
+
101
+ }
102
+ .crisp-select--variant_ghost {
103
+ & .crisp-select-trigger { height: 100%; border-color: transparent; }
104
+ & .crisp-select-trigger:hover:not(:disabled) { background: var(--crisp-bg-select-pressed); }
105
+ }
68
106
 
69
107
  @media (prefers-reduced-motion: reduce) {
70
108
  .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?: "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: { medium: string; large: string }; invalid: { true: string; false: string }; variant: { default: string; ghost: string } }>;
@@ -1,58 +1,69 @@
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
+ .crisp-select-search { display: flex; align-items: center; gap: var(--crisp-space-2); height: 34px; flex-shrink: 0; margin: calc(-1 * var(--crisp-space-2)) calc(-1 * var(--crisp-space-2)) var(--crisp-space-2); padding: 0 var(--crisp-space-2-5); border-bottom: 1px solid var(--crisp-border-subtle); color: var(--crisp-fg-neutral-muted); }
19
18
  .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); }
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-select-neutral); }
20
+ .crisp-select-search input::placeholder { color: var(--crisp-fg-select-placeholder); }
21
+ .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); }
22
+ /* select.yaml's groupLabel -- font-size/line-height/weight are the same
23
+ across sizes, only paddingY differs (medium 8px, large 10px). */
24
+ .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
25
  .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;
26
+ box-sizing: border-box; display: flex; align-items: center; flex-shrink: 0;
27
+ padding: var(--crisp-space-2-5) var(--crisp-space-4); margin-inline: 0; border: none; background: none; border-radius: var(--crisp-radius-xl);
28
+ font-family: inherit; color: var(--crisp-fg-select-neutral); text-align: start; cursor: pointer; white-space: nowrap;
29
+ transition: background-color .15s cubic-bezier(.35,0,.35,1), margin-inline .15s cubic-bezier(.35,0,.35,1);
31
30
  }
31
+ /* Keyboard/hover highlight keeps crisp's own visible neutral fill --
32
+ seed's spec has no "highlighted" state at all (only pressed/selected/
33
+ disabled), and its actual pressed tint (below) is a ~3% black hint,
34
+ too faint to serve as a keyboard-navigation indicator. */
32
35
  .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); }
36
+ .crisp-select-option:active:not([data-disabled]) { background: var(--crisp-bg-select-pressed); margin-inline: var(--crisp-space-2); }
37
+ .crisp-select-option[data-disabled] { cursor: not-allowed; }
38
+ .crisp-select-option[data-disabled] .crisp-select-option-label,
39
+ .crisp-select-option[data-disabled] .crisp-select-option-description,
40
+ .crisp-select-option[data-disabled] .crisp-select-option-check { color: var(--crisp-fg-disabled); }
41
+ .crisp-select-option-dot { flex-shrink: 0; width: 8px; height: 8px; border-radius: var(--crisp-radius-full); margin-inline-end: var(--crisp-space-2); }
42
+ .crisp-select-option-body { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: var(--crisp-space-0-5); }
35
43
  .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); }
44
+ .crisp-select-option-description { min-width: 0; overflow: hidden; text-overflow: ellipsis; color: var(--crisp-fg-neutral-subtle); }
45
+ .crisp-select-option-check { flex-shrink: 0; color: var(--crisp-fg-select-neutral); margin-inline-start: var(--crisp-space-2); }
37
46
 
38
47
  .crisp-select {
39
48
  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); }
49
+ & .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); }
50
+ /* 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
51
+ never moves. */
52
+ /* 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). */
53
+ & .crisp-select-trigger:active:not(:disabled):not([aria-readonly="true"]) { background: var(--crisp-bg-select-pressed); }
54
+ & .crisp-select-trigger:active:not(:disabled):not([aria-readonly="true"]) .crisp-select-value { transform: scale(0.98); }
55
+ & .crisp-select-trigger:focus-visible { outline: 2px solid var(--crisp-stroke-focus-ring); outline-offset: -2px; }
56
+ & .crisp-select-trigger:disabled { background: var(--crisp-bg-select-disabled); cursor: not-allowed; }
57
+ & .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); }
58
+ /* readonly: same fill as disabled, but the value/placeholder text stays normal ink -- it reads as "answered and locked", not "answered and
59
+ greyed out" (select-trigger.yaml's readonly state only overrides
60
+ root.color; value/placeholder keep their enabled colours). */
61
+ & .crisp-select-trigger[aria-readonly="true"] { background: var(--crisp-bg-select-disabled); cursor: default; }
62
+ & .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); }
63
+ & .crisp-select-placeholder { color: var(--crisp-fg-select-placeholder); }
64
+ /* closeRotateDuration/closeRotateTimingFunction: 100ms, seed's "easing" curve. Overridden to 150ms on open (openRotateDuration) below -- same curve both ways, only the duration differs. */
65
+ & .crisp-select-chevron { flex-shrink: 0; color: var(--crisp-fg-neutral-muted); transition: transform .1s cubic-bezier(.35,0,.35,1); }
66
+ & .crisp-select-trigger[data-open] .crisp-select-chevron { transform: rotate(180deg); transition-duration: .15s; }
56
67
  }
57
68
  .crisp-select--width_full {
58
69
  width: 100%;
@@ -60,12 +71,39 @@
60
71
  .crisp-select--width_auto {
61
72
  width: auto;
62
73
  }
74
+ .crisp-select--size_medium {
75
+ & .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); }
76
+ & .crisp-select-value, & .crisp-select-placeholder { font-size: var(--crisp-text-lg); line-height: 19px; }
77
+ & .crisp-select-chevron { width: var(--crisp-size-icon); height: var(--crisp-size-icon); }
78
+ .crisp-select-option { gap: var(--crisp-space-2); }
79
+ .crisp-select-option-label { font-size: var(--crisp-text-lg); line-height: 19px; }
80
+ .crisp-select-option-description { font-size: var(--crisp-text-sm); line-height: 16px; }
81
+ .crisp-select-option-check { width: var(--crisp-space-3); height: var(--crisp-space-3); }
82
+ .crisp-select-label { padding-top: var(--crisp-space-2); }
83
+ }
84
+ .crisp-select--size_large {
85
+ & .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); }
86
+ & .crisp-select-value, & .crisp-select-placeholder { font-size: var(--crisp-text-xl); line-height: 22px; }
87
+ & .crisp-select-chevron { width: var(--crisp-size-selectIconLg); height: var(--crisp-size-selectIconLg); }
88
+ .crisp-select-option { gap: var(--crisp-space-3); }
89
+ .crisp-select-option-label { font-size: var(--crisp-text-xl); line-height: 22px; }
90
+ .crisp-select-option-description { font-size: var(--crisp-text-md); line-height: 18px; }
91
+ .crisp-select-option-check { width: var(--crisp-space-3-5); height: var(--crisp-space-3-5); }
92
+ .crisp-select-label { padding-top: var(--crisp-space-2-5); }
93
+ }
63
94
  .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); }
95
+ & .crisp-select-trigger { border-width: 2px; border-color: var(--crisp-stroke-select-invalid); }
65
96
  }
66
97
  .crisp-select--invalid_false {
67
98
 
68
99
  }
100
+ .crisp-select--variant_default {
101
+
102
+ }
103
+ .crisp-select--variant_ghost {
104
+ & .crisp-select-trigger { height: 100%; border-color: transparent; }
105
+ & .crisp-select-trigger:hover:not(:disabled) { background: var(--crisp-bg-select-pressed); }
106
+ }
69
107
 
70
108
  @media (prefers-reduced-motion: reduce) {
71
109
  .crisp-select, .crisp-select * {
@@ -0,0 +1,3 @@
1
+ .crisp-statusdot {
2
+ display: inline-block; box-sizing: border-box; flex-shrink: 0; width: 12px; height: 12px; border-radius: var(--crisp-radius-full);
3
+ }