bref-ui 2.0.1 → 2.1.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 (109) hide show
  1. package/dist/button/button.svelte +67 -47
  2. package/dist/button/types.d.ts +1 -0
  3. package/dist/checkbox/checkbox.svelte +40 -35
  4. package/dist/combobox/combobox.svelte +2 -3
  5. package/dist/combobox/input.svelte +1 -1
  6. package/dist/combobox/list.svelte +5 -5
  7. package/dist/combobox/option.svelte +29 -9
  8. package/dist/dialog/dialog.svelte +15 -12
  9. package/dist/dialog/footer.svelte +2 -2
  10. package/dist/dialog/header.svelte +4 -4
  11. package/dist/icon/icon.svelte +6 -4
  12. package/dist/pill/content.svelte +11 -4
  13. package/dist/pill/content.svelte.d.ts +1 -1
  14. package/dist/pill/on-delete.svelte +42 -13
  15. package/dist/pill/pill.svelte +74 -94
  16. package/dist/pill/registry.json +1 -1
  17. package/dist/pill/types.d.ts +1 -0
  18. package/dist/pill-choice-group/pill-choice-group.svelte +19 -0
  19. package/dist/pill-choice-group/types.d.ts +1 -0
  20. package/dist/pill-group/pill-group.svelte +2 -0
  21. package/dist/pill-group/types.d.ts +1 -0
  22. package/dist/popover/popover.svelte +31 -6
  23. package/dist/popover/types.d.ts +1 -1
  24. package/dist/progress/progress.svelte +42 -55
  25. package/dist/select/empty.svelte +2 -3
  26. package/dist/select/list.svelte +2 -1
  27. package/dist/select/option.svelte +20 -6
  28. package/dist/select/select.svelte +7 -4
  29. package/dist/select/trigger.svelte +30 -7
  30. package/dist/spinner/spinner.svelte +8 -21
  31. package/dist/surface/surface.svelte +6 -1
  32. package/dist/surface/types.d.ts +2 -0
  33. package/dist/switch/switch.svelte +48 -51
  34. package/dist/text-area/registry.json +1 -1
  35. package/dist/text-area/text-area.svelte +33 -73
  36. package/dist/text-area/types.d.ts +1 -0
  37. package/dist/text-input/registry.json +1 -1
  38. package/dist/text-input/text-input.svelte +33 -73
  39. package/dist/text-input/types.d.ts +1 -0
  40. package/dist/theme/cli-theme.svelte +2 -0
  41. package/dist/theme/glass.svelte +60 -0
  42. package/dist/theme/glass.svelte.d.ts +26 -0
  43. package/dist/theme/registry.json +1 -0
  44. package/dist/theme/styles.svelte +6 -0
  45. package/dist/theme/surface.d.ts +1 -1
  46. package/dist/theme/surface.js +3 -2
  47. package/dist/theme/surfaces.svelte +23 -45
  48. package/dist/theme/theme.svelte +2 -0
  49. package/dist/tree-view/tree-node/chevron-button.svelte +9 -9
  50. package/dist/tree-view/tree-node/content.svelte +15 -12
  51. package/dist/tree-view/tree-node/delete-button.svelte +6 -5
  52. package/dist/tree-view/tree-node/tree-node.svelte +23 -24
  53. package/dist/tree-view/tree-view-section.svelte +9 -9
  54. package/dist/tree-view/tree-view.svelte +17 -9
  55. package/dist/tree-view/types.d.ts +2 -1
  56. package/package.json +1 -1
  57. package/readme.md +54 -13
  58. package/src/lib/button/button.svelte +67 -47
  59. package/src/lib/button/types.ts +1 -0
  60. package/src/lib/checkbox/checkbox.svelte +40 -35
  61. package/src/lib/combobox/combobox.svelte +2 -3
  62. package/src/lib/combobox/input.svelte +1 -1
  63. package/src/lib/combobox/list.svelte +5 -5
  64. package/src/lib/combobox/option.svelte +29 -9
  65. package/src/lib/dialog/dialog.svelte +15 -12
  66. package/src/lib/dialog/footer.svelte +2 -2
  67. package/src/lib/dialog/header.svelte +4 -4
  68. package/src/lib/icon/icon.svelte +6 -4
  69. package/src/lib/pill/content.svelte +11 -4
  70. package/src/lib/pill/on-delete.svelte +42 -13
  71. package/src/lib/pill/pill.svelte +74 -94
  72. package/src/lib/pill/registry.json +1 -1
  73. package/src/lib/pill/types.ts +1 -0
  74. package/src/lib/pill-choice-group/pill-choice-group.svelte +19 -0
  75. package/src/lib/pill-choice-group/types.ts +1 -0
  76. package/src/lib/pill-group/pill-group.svelte +2 -0
  77. package/src/lib/pill-group/types.ts +1 -0
  78. package/src/lib/popover/popover.svelte +31 -6
  79. package/src/lib/popover/types.ts +16 -1
  80. package/src/lib/progress/progress.svelte +42 -55
  81. package/src/lib/select/empty.svelte +2 -3
  82. package/src/lib/select/list.svelte +2 -1
  83. package/src/lib/select/option.svelte +20 -6
  84. package/src/lib/select/select.svelte +7 -4
  85. package/src/lib/select/trigger.svelte +30 -7
  86. package/src/lib/spinner/spinner.svelte +8 -21
  87. package/src/lib/surface/surface.svelte +6 -1
  88. package/src/lib/surface/types.ts +2 -0
  89. package/src/lib/switch/switch.svelte +48 -51
  90. package/src/lib/text-area/registry.json +1 -1
  91. package/src/lib/text-area/text-area.svelte +33 -73
  92. package/src/lib/text-area/types.ts +1 -0
  93. package/src/lib/text-input/registry.json +1 -1
  94. package/src/lib/text-input/text-input.svelte +33 -73
  95. package/src/lib/text-input/types.ts +1 -0
  96. package/src/lib/theme/cli-theme.svelte +2 -0
  97. package/src/lib/theme/glass.svelte +60 -0
  98. package/src/lib/theme/registry.json +1 -0
  99. package/src/lib/theme/styles.svelte +6 -0
  100. package/src/lib/theme/surface.ts +4 -2
  101. package/src/lib/theme/surfaces.svelte +23 -45
  102. package/src/lib/theme/theme.svelte +2 -0
  103. package/src/lib/tree-view/tree-node/chevron-button.svelte +9 -9
  104. package/src/lib/tree-view/tree-node/content.svelte +15 -12
  105. package/src/lib/tree-view/tree-node/delete-button.svelte +6 -5
  106. package/src/lib/tree-view/tree-node/tree-node.svelte +23 -24
  107. package/src/lib/tree-view/tree-view-section.svelte +9 -9
  108. package/src/lib/tree-view/tree-view.svelte +17 -9
  109. package/src/lib/tree-view/types.ts +2 -1
@@ -1,8 +1,10 @@
1
1
  <script lang="ts">
2
- import { fly } from 'svelte/transition';
2
+ import { cubicOut } from 'svelte/easing';
3
3
  import { prefersReducedMotion } from 'svelte/motion';
4
+ import type { TransitionConfig } from 'svelte/transition';
4
5
  import Surface from '../surface/surface.svelte';
5
6
  import type { PopoverProps } from './types.js';
7
+ import type { SurfaceProps } from '../surface/types.ts';
6
8
 
7
9
  let {
8
10
  trigger,
@@ -15,11 +17,27 @@
15
17
  let anchor: HTMLDivElement;
16
18
  let returnFocus: HTMLElement | null = null;
17
19
 
20
+ const flyGlass: (node: Element) => TransitionConfig = () => ({
21
+ duration: prefersReducedMotion.current ? 0 : 250,
22
+ easing: cubicOut,
23
+ css: (t: number, u: number) =>
24
+ `--surface-motion-offset: ${u * 10}px; --surface-motion-opacity: ${t}`
25
+ });
26
+
18
27
  const showPopover = (node: HTMLDivElement) => {
19
28
  if (node.matches(':popover-open')) return;
20
29
  returnFocus = document.activeElement instanceof HTMLElement ? document.activeElement : null;
21
30
  node.showPopover({ source: anchor.firstElementChild as HTMLElement });
22
31
  };
32
+
33
+ const surfacePropsWithDefaults: SurfaceProps = $derived({
34
+ variant: 'soft',
35
+ spacing: 'medium',
36
+ radius: 'small',
37
+ shadow: 'medium',
38
+ ...surfaceProps,
39
+ glass: true
40
+ });
23
41
  </script>
24
42
 
25
43
  <div data-trigger bind:this={anchor}>
@@ -30,7 +48,8 @@
30
48
  use:showPopover
31
49
  {id}
32
50
  popover="auto"
33
- transition:fly={{ y: 10, duration: prefersReducedMotion.current ? 0 : 250 }}
51
+ data-width={surfaceProps.width}
52
+ transition:flyGlass
34
53
  onintrostart={(event) => showPopover(event.currentTarget)}
35
54
  onoutrostart={(event) => {
36
55
  if (!event.currentTarget.matches(':popover-open')) return;
@@ -42,7 +61,7 @@
42
61
  open = false;
43
62
  }}
44
63
  >
45
- <Surface {...surfaceProps}>
64
+ <Surface {...surfacePropsWithDefaults} data-surface-motion>
46
65
  <div data-content>{@render children()}</div>
47
66
  </Surface>
48
67
  </div>
@@ -59,17 +78,23 @@
59
78
  inset: auto;
60
79
  position-area: block-end;
61
80
  position-try-fallbacks: flip-block;
62
- width: anchor-size(width);
81
+ width: fit-content;
82
+ min-width: min(anchor-size(width), calc(100vw - 1rem));
63
83
  max-width: calc(100vw - 1rem);
64
84
  max-height: min(24rem, calc(100% - 0.75rem));
65
85
  margin: 0.375rem 0;
66
86
  padding: 0;
67
87
  overflow: visible;
68
- overflow-wrap: anywhere;
88
+ overflow-wrap: break-word;
69
89
  border: 0;
70
90
  background: transparent;
71
91
  color: var(--color-foreground, black);
72
- transition: all var(--internal-duration) allow-discrete;
92
+ transition:
93
+ display var(--internal-duration) allow-discrete,
94
+ overlay var(--internal-duration) allow-discrete;
95
+ }
96
+ div[popover][data-width='fill'] {
97
+ width: anchor-size(width);
73
98
  }
74
99
  div:popover-open {
75
100
  display: flex;
@@ -1,7 +1,22 @@
1
1
  import type { SurfaceProps } from '../surface/types.ts';
2
2
  import type { Snippet } from 'svelte';
3
3
 
4
- export interface PopoverProps extends SurfaceProps {
4
+ export interface PopoverProps extends Pick<
5
+ SurfaceProps,
6
+ | 'as'
7
+ | 'variant'
8
+ | 'color'
9
+ | 'shadow'
10
+ | 'hover'
11
+ | 'spacing'
12
+ | 'orientation'
13
+ | 'width'
14
+ | 'height'
15
+ | 'radius'
16
+ | 'scroll'
17
+ | 'class'
18
+ | 'style'
19
+ > {
5
20
  trigger: Snippet;
6
21
  children: Snippet;
7
22
  open?: boolean;
@@ -75,24 +75,15 @@
75
75
  onpointercancel?.(event);
76
76
  }}
77
77
  />
78
- <span style:inset-inline-start={`${percentage}%`} aria-hidden="true"></span>
78
+ <span class={['glass']} style:inset-inline-start={`${percentage}%`} aria-hidden="true"></span>
79
79
  {/if}
80
80
  </div>
81
81
 
82
82
  <style>
83
83
  div {
84
84
  --progress-height: 0.375rem;
85
- --progress-track: color-mix(
86
- in srgb,
87
- color-mix(in srgb, var(--color-foreground) 16%, var(--color-background)) 85%,
88
- transparent
89
- );
90
- --progress-fill: color-mix(
91
- in srgb,
92
- color-mix(in srgb, var(--color-primary) 88%, var(--color-background)) 85%,
93
- transparent
94
- );
95
- --progress-highlight: color-mix(in srgb, var(--color-foreground) 28%, transparent);
85
+ --progress-track: color-mix(in srgb, var(--color-foreground) 22%, var(--color-background));
86
+ --progress-fill: var(--color-primary);
96
87
  position: relative;
97
88
  display: block;
98
89
  width: 100%;
@@ -112,11 +103,6 @@
112
103
  border-radius: 999px;
113
104
  appearance: none;
114
105
  background: var(--progress-track);
115
- box-shadow:
116
- inset 0 1px 0 color-mix(in srgb, var(--color-foreground) 16%, transparent),
117
- 0 2px 6px color-mix(in srgb, var(--color-foreground) 8%, transparent);
118
- -webkit-backdrop-filter: blur(0.5rem) saturate(120%);
119
- backdrop-filter: blur(0.5rem) saturate(120%);
120
106
  overflow: hidden;
121
107
  }
122
108
  progress::-webkit-progress-bar {
@@ -125,12 +111,10 @@
125
111
  progress::-webkit-progress-value {
126
112
  background: var(--progress-fill);
127
113
  border-radius: 999px;
128
- box-shadow: inset 0 1px 0 var(--progress-highlight);
129
114
  }
130
115
  progress::-moz-progress-bar {
131
116
  background: var(--progress-fill);
132
117
  border-radius: 999px;
133
- box-shadow: inset 0 1px 0 var(--progress-highlight);
134
118
  }
135
119
  div[data-unknown='true'] progress::-webkit-progress-value {
136
120
  background: transparent;
@@ -142,14 +126,13 @@
142
126
  content: '';
143
127
  position: absolute;
144
128
  top: calc(50% - var(--progress-height) / 2);
145
- left: 0;
146
- width: 100%;
129
+ inset-inline: 0 100%;
147
130
  height: var(--progress-height);
148
131
  border-radius: 999px;
149
132
  background: var(--progress-fill);
150
- box-shadow: inset 0 1px 0 var(--progress-highlight);
151
- transform-origin: left center;
152
- animation: sweep 1.8s cubic-bezier(0.4, 0, 0.2, 1) infinite;
133
+ animation:
134
+ sweep-leading 2s cubic-bezier(0.15, 0, 0.45, 1) infinite,
135
+ sweep-trailing 2s cubic-bezier(0.55, 0, 0.85, 1) infinite;
153
136
  pointer-events: none;
154
137
  }
155
138
  div[data-seekable='true'] {
@@ -160,6 +143,10 @@
160
143
  div[data-seekable='true'] progress {
161
144
  pointer-events: none;
162
145
  }
146
+ div:has(input:disabled) {
147
+ --progress-fill: var(--color-muted);
148
+ filter: grayscale(1);
149
+ }
163
150
  input {
164
151
  position: absolute;
165
152
  inset: 0;
@@ -174,19 +161,15 @@
174
161
  cursor: not-allowed;
175
162
  }
176
163
  span {
164
+ --glass-paint: color-mix(in srgb, var(--color-background) 92%, var(--progress-fill));
165
+ --surface-shadow: 0 2px 6px color-mix(in srgb, var(--color-foreground) 18%, transparent);
177
166
  box-sizing: border-box;
178
167
  position: absolute;
179
168
  top: 50%;
180
169
  width: 1.25rem;
181
170
  height: 1.25rem;
171
+ border: 3px solid var(--progress-fill);
182
172
  border-radius: 50%;
183
- background: color-mix(in srgb, var(--color-background) 25%, transparent);
184
- border: 1px solid color-mix(in srgb, var(--color-foreground) 30%, transparent);
185
- box-shadow:
186
- inset 0 1px 0 var(--progress-highlight),
187
- 0 2px 6px color-mix(in srgb, var(--color-foreground) 12%, transparent);
188
- -webkit-backdrop-filter: blur(0.125rem) saturate(120%);
189
- backdrop-filter: blur(0.125rem) saturate(120%);
190
173
  transform: translate(var(--internal-thumb-offset, -50%), -50%);
191
174
  transition:
192
175
  transform 150ms ease,
@@ -196,37 +179,30 @@
196
179
  @media (hover: hover) {
197
180
  input:not(:disabled):hover + span {
198
181
  transform: translate(var(--internal-thumb-offset, -50%), -50%) scale(1.3);
199
- box-shadow:
200
- inset 0 1px 0 var(--progress-highlight),
201
- 0 3px 10px color-mix(in srgb, var(--color-foreground) 20%, transparent);
182
+ --surface-shadow: 0 3px 10px color-mix(in srgb, var(--color-foreground) 22%, transparent);
202
183
  }
203
184
  }
204
185
  input:not(:disabled):active + span {
205
186
  transform: translate(var(--internal-thumb-offset, -50%), -50%) scale(1.5);
206
- box-shadow:
207
- inset 0 1px 0 var(--progress-highlight),
187
+ --surface-shadow:
208
188
  0 4px 14px color-mix(in srgb, var(--color-foreground) 24%, transparent),
209
- 0 0 0 4px color-mix(in srgb, var(--color-foreground) 12%, transparent);
189
+ 0 0 0 4px color-mix(in srgb, var(--progress-fill) 18%, transparent);
210
190
  }
211
191
  span:dir(rtl) {
212
192
  --internal-thumb-offset: 50%;
213
193
  }
214
194
  input:focus-visible + span {
215
- outline: 2px solid var(--color-foreground);
195
+ outline: 2px solid var(--color-primary);
216
196
  outline-offset: 3px;
217
197
  }
218
- input:disabled + span {
219
- opacity: 0.5;
220
- }
221
- @keyframes sweep {
222
- 0% {
223
- transform: translateX(0) scaleX(0);
198
+ @keyframes sweep-leading {
199
+ to {
200
+ inset-inline-end: 0;
224
201
  }
225
- 50% {
226
- transform: translateX(25%) scaleX(0.5);
227
- }
228
- 100% {
229
- transform: translateX(100%) scaleX(0);
202
+ }
203
+ @keyframes sweep-trailing {
204
+ to {
205
+ inset-inline-start: 100%;
230
206
  }
231
207
  }
232
208
  @media (prefers-reduced-motion: reduce) {
@@ -235,22 +211,33 @@
235
211
  }
236
212
  div[data-unknown='true']::after {
237
213
  animation: none;
238
- transform: translateX(32.5%) scaleX(0.35);
214
+ inset-inline: 35%;
239
215
  }
240
216
  }
241
217
  @media (forced-colors: active) {
218
+ div {
219
+ --progress-track: Canvas;
220
+ --progress-fill: Highlight;
221
+ }
222
+ div:has(input:disabled) {
223
+ --progress-fill: GrayText;
224
+ filter: none;
225
+ }
226
+ div[data-unknown='true']::after {
227
+ forced-color-adjust: none;
228
+ }
229
+ input:focus-visible + span {
230
+ outline-color: Highlight;
231
+ }
242
232
  progress {
233
+ forced-color-adjust: none;
243
234
  border: 1px solid CanvasText;
244
235
  box-shadow: none;
245
- -webkit-backdrop-filter: none;
246
- backdrop-filter: none;
247
236
  }
248
237
  span {
249
238
  background: Highlight;
250
- border-color: HighlightText;
239
+ border: 1px solid HighlightText;
251
240
  box-shadow: none;
252
- -webkit-backdrop-filter: none;
253
- backdrop-filter: none;
254
241
  }
255
242
  }
256
243
  </style>
@@ -14,9 +14,8 @@
14
14
  p {
15
15
  display: flex;
16
16
  align-items: center;
17
- flex-direction: column;
18
- justify-content: center;
19
- gap: 0.5rem;
17
+ justify-content: flex-start;
18
+ gap: 0.6rem;
20
19
  min-height: fit-content;
21
20
  margin: 0;
22
21
  padding: 0.5rem;
@@ -21,7 +21,8 @@
21
21
  <style>
22
22
  div {
23
23
  display: grid;
24
- gap: 0.375rem;
24
+ grid-template-columns: minmax(0, 1fr);
25
+ gap: 0.125rem;
25
26
  min-width: 0;
26
27
  }
27
28
  </style>
@@ -8,7 +8,7 @@
8
8
  <button type="button" aria-pressed={selected} {disabled} onclick={onSelect}>
9
9
  {#if icon}<span data-icon><Icon {...icon} /></span>{/if}
10
10
  <span data-label>{label}</span>
11
- {#if selected}<span data-icon><Icon name="check" /></span>{/if}
11
+ {#if selected}<span data-icon data-check><Icon name="check" /></span>{/if}
12
12
  </button>
13
13
 
14
14
  <style>
@@ -18,15 +18,17 @@
18
18
  align-items: center;
19
19
  gap: 0.6rem;
20
20
  width: 100%;
21
- min-height: var(--internal-height);
21
+ min-width: 0;
22
+ min-height: var(--internal-row-height);
22
23
  padding: 0.375rem 0.65rem;
23
24
  border: 0;
24
- border-radius: 0.4rem;
25
+ border-radius: 0.5rem;
25
26
  background: transparent;
26
27
  color: var(--color-foreground);
27
28
  font: inherit;
28
29
  text-align: left;
29
30
  cursor: pointer;
31
+ transition: background-color 150ms ease;
30
32
  }
31
33
  [data-label] {
32
34
  flex: 1;
@@ -40,11 +42,18 @@
40
42
  flex: 0 0 auto;
41
43
  font-size: var(--internal-icon-size);
42
44
  }
43
- button:hover:not(:disabled),
44
- button[aria-pressed='true'] {
45
- background: color-mix(in srgb, var(--color-primary) 12%, transparent);
45
+ [data-check] {
46
46
  color: var(--color-primary);
47
47
  }
48
+ button[aria-pressed='true'] {
49
+ background: color-mix(in srgb, var(--color-primary) 8%, transparent);
50
+ }
51
+ button:hover:not(:disabled) {
52
+ background: color-mix(in srgb, var(--color-foreground) 6%, transparent);
53
+ }
54
+ button[aria-pressed='true']:hover:not(:disabled) {
55
+ background: color-mix(in srgb, var(--color-primary) 14%, transparent);
56
+ }
48
57
  button:focus-visible {
49
58
  outline: 2px solid var(--color-primary);
50
59
  outline-offset: -2px;
@@ -53,6 +62,11 @@
53
62
  opacity: 0.5;
54
63
  cursor: not-allowed;
55
64
  }
65
+ @media (prefers-reduced-motion: reduce) {
66
+ button {
67
+ transition: none;
68
+ }
69
+ }
56
70
  @media (forced-colors: active) {
57
71
  button:focus-visible {
58
72
  outline-color: Highlight;
@@ -32,7 +32,7 @@
32
32
  </script>
33
33
 
34
34
  <div data-size={size}>
35
- <Popover bind:open variant="filled" spacing="small" radius="small" shadow scroll {disabled}>
35
+ <Popover bind:open spacing="x-small" radius="0.75rem" width="fill" shadow scroll {disabled}>
36
36
  {#snippet trigger()}
37
37
  <Trigger
38
38
  {id}
@@ -51,7 +51,8 @@
51
51
  <style>
52
52
  div {
53
53
  --internal-height: 2.5rem;
54
- --internal-padding: 0.85rem;
54
+ --internal-row-height: 2.125rem;
55
+ --internal-padding: 1rem;
55
56
  --internal-icon-size: 1.25rem;
56
57
  position: relative;
57
58
  width: 100%;
@@ -59,12 +60,14 @@
59
60
  }
60
61
  div[data-size='small'] {
61
62
  --internal-height: 2rem;
62
- --internal-padding: 0.65rem;
63
+ --internal-row-height: 1.875rem;
64
+ --internal-padding: 0.75rem;
63
65
  --internal-icon-size: 1rem;
64
66
  }
65
67
  div[data-size='large'] {
66
68
  --internal-height: 3rem;
67
- --internal-padding: 1rem;
69
+ --internal-row-height: 2.375rem;
70
+ --internal-padding: 1.125rem;
68
71
  --internal-icon-size: 1.5rem;
69
72
  }
70
73
  </style>
@@ -9,13 +9,12 @@
9
9
  <button {id} type="button" data-size={size} {disabled} aria-expanded={open} onclick={onToggle}>
10
10
  <Surface
11
11
  as="span"
12
- variant="soft"
12
+ variant="neutral"
13
13
  color="foreground"
14
14
  orientation="horizontal"
15
- radius="small"
15
+ radius="0.625rem"
16
16
  width="fill"
17
17
  height="fill"
18
- hover={disabled ? undefined : 'small'}
19
18
  >
20
19
  {#if selected.length === 1 && selected[0].icon}
21
20
  <span data-icon><Icon {...selected[0].icon} /></span>
@@ -23,7 +22,7 @@
23
22
  <span data-value data-placeholder={selected.length === 0 || undefined}>
24
23
  {selected.length ? selected.map((item) => item.label).join(', ') : placeholder}
25
24
  </span>
26
- <span data-icon><Icon name="arrow_drop_down" /></span>
25
+ <span data-icon data-chevron><Icon name="keyboard_arrow_down" /></span>
27
26
  </Surface>
28
27
  </button>
29
28
 
@@ -35,12 +34,16 @@
35
34
  height: var(--internal-height);
36
35
  padding: 0;
37
36
  border: 0;
38
- border-radius: 0.5rem;
39
- background: transparent;
37
+ border-radius: 0.625rem;
38
+ background: light-dark(
39
+ color-mix(in srgb, var(--color-foreground) 6%, var(--color-background)),
40
+ color-mix(in srgb, var(--color-foreground) 10%, var(--color-background))
41
+ );
40
42
  color: var(--color-foreground);
41
43
  font: inherit;
42
44
  text-align: left;
43
45
  cursor: pointer;
46
+ transition: background-color 150ms ease;
44
47
  }
45
48
  span:first-child {
46
49
  margin-inline-start: var(--internal-padding);
@@ -61,7 +64,9 @@
61
64
  font-weight: 200;
62
65
  line-height: 1.4;
63
66
  }
64
- [data-placeholder],
67
+ [data-placeholder] {
68
+ color: color-mix(in srgb, var(--color-muted) 90%, var(--color-foreground));
69
+ }
65
70
  [data-icon] {
66
71
  color: var(--color-muted);
67
72
  }
@@ -70,6 +75,18 @@
70
75
  flex: 0 0 auto;
71
76
  font-size: var(--internal-icon-size);
72
77
  }
78
+ button:hover:not(:disabled) {
79
+ background: light-dark(
80
+ color-mix(in srgb, var(--color-foreground) 8%, var(--color-background)),
81
+ color-mix(in srgb, var(--color-foreground) 12%, var(--color-background))
82
+ );
83
+ }
84
+ [data-chevron] {
85
+ transition: transform 150ms ease;
86
+ }
87
+ button[aria-expanded='true'] [data-chevron] {
88
+ transform: rotate(180deg);
89
+ }
73
90
  button:focus-visible {
74
91
  outline: 2px solid var(--color-primary);
75
92
  outline-offset: 2px;
@@ -78,6 +95,12 @@
78
95
  opacity: 0.5;
79
96
  cursor: not-allowed;
80
97
  }
98
+ @media (prefers-reduced-motion: reduce) {
99
+ button,
100
+ [data-chevron] {
101
+ transition: none;
102
+ }
103
+ }
81
104
  @media (forced-colors: active) {
82
105
  button:focus-visible {
83
106
  outline-color: Highlight;
@@ -16,23 +16,15 @@
16
16
  initial-value: 90deg;
17
17
  }
18
18
  div {
19
- --spinner-track: color-mix(
20
- in srgb,
21
- color-mix(in srgb, var(--color-foreground) 16%, var(--color-background)) 85%,
22
- transparent
23
- );
24
- --spinner-fill: color-mix(
25
- in srgb,
26
- color-mix(in srgb, var(--color-primary) 88%, var(--color-background)) 85%,
27
- transparent
28
- );
19
+ --spinner-track: color-mix(in srgb, currentColor 18%, transparent);
20
+ --spinner-fill: currentColor;
29
21
  display: inline-flex;
30
22
  align-items: center;
31
23
  justify-content: center;
32
24
  width: 1em;
33
25
  height: 1em;
34
26
  font-size: inherit;
35
- color: var(--color-primary);
27
+ color: inherit;
36
28
  }
37
29
  div[data-size='x-small'] {
38
30
  font-size: 0.875rem;
@@ -53,14 +45,11 @@
53
45
  box-sizing: border-box;
54
46
  width: 80%;
55
47
  height: 80%;
56
- padding: max(2px, 0.12em);
48
+ padding: max(1px, 0.055em);
57
49
  background: conic-gradient(
58
50
  var(--spinner-fill) 0deg var(--spinner-sweep),
59
51
  var(--spinner-track) var(--spinner-sweep) 360deg
60
52
  );
61
- box-shadow: inset 0 1px 0 color-mix(in srgb, var(--color-foreground) 28%, transparent);
62
- -webkit-backdrop-filter: blur(0.5rem) saturate(120%);
63
- backdrop-filter: blur(0.5rem) saturate(120%);
64
53
  mask:
65
54
  linear-gradient(black, black) content-box,
66
55
  linear-gradient(black, black);
@@ -85,8 +74,8 @@
85
74
  transform: rotate(0deg);
86
75
  }
87
76
  50% {
88
- --spinner-sweep: 270deg;
89
- transform: rotate(180deg);
77
+ --spinner-sweep: 210deg;
78
+ transform: rotate(90deg);
90
79
  }
91
80
  100% {
92
81
  --spinner-sweep: 30deg;
@@ -95,6 +84,7 @@
95
84
  }
96
85
  @media (prefers-reduced-motion: reduce) {
97
86
  span {
87
+ --spinner-sweep: 90deg;
98
88
  animation: none;
99
89
  }
100
90
  }
@@ -102,13 +92,10 @@
102
92
  span {
103
93
  padding: 0;
104
94
  mask: none;
105
- border: max(2px, 0.12em) solid CanvasText;
95
+ border: max(1px, 0.055em) solid CanvasText;
106
96
  border-color: CanvasText;
107
97
  border-top-color: Highlight;
108
98
  background: none;
109
- box-shadow: none;
110
- -webkit-backdrop-filter: none;
111
- backdrop-filter: none;
112
99
  }
113
100
  }
114
101
  </style>
@@ -7,6 +7,7 @@
7
7
  children,
8
8
  color = 'background',
9
9
  variant = 'neutral',
10
+ glass = false,
10
11
  spacing,
11
12
  orientation = 'vertical',
12
13
  width = 'fill',
@@ -35,7 +36,7 @@
35
36
  this={as}
36
37
  {...attributes}
37
38
  data-surface
38
- class={[surface({ variant, color, shadow, hover }), className]}
39
+ class={[surface({ variant, color, glass, shadow, hover }), className]}
39
40
  data-variant={variant}
40
41
  data-spacing={spacing}
41
42
  data-orientation={orientation}
@@ -64,6 +65,10 @@
64
65
  border-radius: var(--surface-radius, 0);
65
66
  overflow: visible;
66
67
  }
68
+ [data-surface][data-surface-motion] {
69
+ opacity: var(--surface-motion-opacity, 1);
70
+ translate: 0 var(--surface-motion-offset, 0px);
71
+ }
67
72
  [data-orientation='horizontal'] {
68
73
  flex-direction: row;
69
74
  align-items: center;
@@ -11,6 +11,8 @@ export interface SurfaceBaseProps {
11
11
  shadow?: BaseSize | boolean;
12
12
  /** Optional hover treatment strength. */
13
13
  hover?: BaseSize;
14
+ /** Enable translucent paint and backdrop blur. */
15
+ glass?: boolean;
14
16
  }
15
17
 
16
18
  export interface SurfaceProps extends SurfaceBaseProps, Omit<HTMLAttributes<HTMLElement>, 'color'> {