bref-ui 2.0.0 → 2.1.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 (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 +30 -3
  57. package/readme.md +3 -1
  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
@@ -11,9 +11,9 @@
11
11
 
12
12
  <style>
13
13
  [data-switch] {
14
- --track-width: 3.25rem;
15
- --track-height: 2rem;
16
- --thumb-size: 1.5rem;
14
+ --track-width: 2.75rem;
15
+ --track-height: 1.625rem;
16
+ --thumb-size: 1.125rem;
17
17
  --inset: 0.25rem;
18
18
  position: relative;
19
19
  display: inline-flex;
@@ -35,43 +35,24 @@
35
35
 
36
36
  [data-switch] > span {
37
37
  --internal-tint: var(--color-foreground, black);
38
- --internal-strength-light: 4%;
39
- --internal-strength-dark: 16%;
40
- --internal-highlight: color-mix(in srgb, var(--color-foreground, black) 16%, transparent);
38
+ --internal-strength: 48%;
39
+ --internal-fill: color-mix(
40
+ in srgb,
41
+ var(--internal-tint) var(--internal-strength),
42
+ var(--color-background, white)
43
+ );
44
+ --internal-border: var(--internal-fill);
41
45
  box-sizing: border-box;
42
46
  position: relative;
43
47
  width: 100%;
44
48
  height: 100%;
45
- border: 1px solid
46
- light-dark(
47
- color-mix(in srgb, var(--color-foreground, black) 12%, transparent),
48
- color-mix(in srgb, var(--color-foreground, black) 30%, transparent)
49
- );
49
+ border: 1px solid var(--internal-border);
50
50
  border-radius: 999px;
51
- background: color-mix(
52
- in srgb,
53
- light-dark(
54
- color-mix(
55
- in srgb,
56
- var(--internal-tint) var(--internal-strength-light),
57
- var(--color-background, white)
58
- ),
59
- color-mix(
60
- in srgb,
61
- var(--internal-tint) var(--internal-strength-dark),
62
- var(--color-background, white)
63
- )
64
- )
65
- 85%,
66
- transparent
67
- );
68
- box-shadow:
69
- inset 0 1px 0 var(--internal-highlight),
70
- 0 2px 6px color-mix(in srgb, black 8%, transparent);
71
- -webkit-backdrop-filter: blur(0.5rem) saturate(120%);
72
- backdrop-filter: blur(0.5rem) saturate(120%);
51
+ background: var(--internal-fill);
73
52
  pointer-events: none;
74
- transition: all 150ms ease;
53
+ transition:
54
+ background-color 150ms ease,
55
+ border-color 150ms ease;
75
56
  }
76
57
 
77
58
  [data-switch] > span > span {
@@ -84,27 +65,42 @@
84
65
  width: var(--thumb-size);
85
66
  height: var(--thumb-size);
86
67
  border-radius: 50%;
87
- background: var(--color-foreground);
88
- color: var(--color-background);
68
+ background: var(--color-background, white);
69
+ color: var(--color-foreground, black);
70
+ box-shadow: 0 1px 2px color-mix(in srgb, var(--color-background, white) 12%, transparent);
89
71
  font-size: calc(var(--thumb-size) * 0.7);
90
72
  transform: translateY(-50%);
91
- transition:
92
- left 150ms ease,
93
- background 150ms ease;
73
+ transition: left 150ms ease;
74
+ }
75
+
76
+ input:hover:not(:disabled) + span {
77
+ --internal-strength: 52%;
78
+ }
79
+
80
+ input:active:not(:disabled) + span {
81
+ --internal-strength: 56%;
94
82
  }
95
83
 
96
84
  input:checked + span {
97
85
  --internal-tint: var(--color-primary, blue);
98
- --internal-strength-light: 88%;
99
- --internal-strength-dark: 88%;
100
- --internal-highlight: color-mix(in srgb, var(--color-foreground, black) 28%, transparent);
101
- border-color: color-mix(in srgb, var(--internal-tint) 68%, var(--color-foreground, black));
86
+ --internal-strength: 88%;
87
+ }
88
+
89
+ input:checked:hover:not(:disabled) + span {
90
+ --internal-strength: 92%;
91
+ }
92
+
93
+ input:checked:active:not(:disabled) + span {
94
+ --internal-strength: 96%;
102
95
  }
103
96
 
104
97
  input:checked + span > span {
105
98
  left: calc(100% - var(--thumb-size) - var(--inset));
106
- background: var(--color-background);
107
- color: var(--color-foreground);
99
+ }
100
+
101
+ input:user-invalid + span,
102
+ input[aria-invalid='true'] + span {
103
+ --internal-border: var(--color-error, #bc2436);
108
104
  }
109
105
 
110
106
  input:focus-visible + span {
@@ -121,15 +117,15 @@
121
117
  }
122
118
 
123
119
  [data-size='small'] {
124
- --track-width: 2.75rem;
125
- --track-height: 1.75rem;
126
- --thumb-size: 1.25rem;
120
+ --track-width: 2.25rem;
121
+ --track-height: 1.375rem;
122
+ --thumb-size: 0.875rem;
127
123
  }
128
124
 
129
125
  [data-size='large'] {
130
- --track-width: 3.75rem;
131
- --track-height: 2.25rem;
132
- --thumb-size: 1.625rem;
126
+ --track-width: 3.25rem;
127
+ --track-height: 1.875rem;
128
+ --thumb-size: 1.375rem;
133
129
  }
134
130
 
135
131
  @media (prefers-reduced-motion: reduce) {
@@ -150,6 +146,7 @@
150
146
  [data-switch] > span > span {
151
147
  background: ButtonText;
152
148
  color: Canvas;
149
+ box-shadow: none;
153
150
  }
154
151
  input:checked + span {
155
152
  background: Highlight;
@@ -4,5 +4,5 @@
4
4
  "exports": {
5
5
  "TextArea": "text-area.svelte"
6
6
  },
7
- "dependencies": ["icon"]
7
+ "dependencies": ["icon", "theme"]
8
8
  }
@@ -1,6 +1,6 @@
1
1
  <script lang="ts">
2
- /* eslint-disable max-lines -- Keep the native control and its scoped style recipe together. */
3
2
  import Icon from '../icon/icon.svelte';
3
+ import { surface } from '../theme/surface.js';
4
4
  import type { TextAreaProps } from './types.js';
5
5
 
6
6
  let {
@@ -9,7 +9,8 @@
9
9
  icon,
10
10
  size = 'medium',
11
11
  wide = false,
12
- variant = 'neutral',
12
+ variant = 'soft',
13
+ glass = false,
13
14
  resizable = true,
14
15
  oninput,
15
16
  defaultValue,
@@ -23,7 +24,17 @@
23
24
  };
24
25
  </script>
25
26
 
26
- <div data-size={size} data-variant={variant} data-wide={wide || undefined}>
27
+ <div
28
+ class={surface({
29
+ color: 'background',
30
+ variant,
31
+ glass,
32
+ hover: variant === 'soft' && !attributes.disabled ? 'small' : undefined
33
+ })}
34
+ data-size={size}
35
+ data-variant={variant}
36
+ data-wide={wide || undefined}
37
+ >
27
38
  {#if icon}
28
39
  <span><Icon {...icon} label={undefined} /></span>
29
40
  {/if}
@@ -38,76 +49,39 @@
38
49
  <style>
39
50
  div {
40
51
  --internal-height: 2.5rem;
41
- --internal-padding: 0.85rem;
52
+ --internal-padding: 1rem;
42
53
  --internal-icon-size: 1.25rem;
43
- --internal-tint: var(--color-foreground, black);
44
- --internal-strength-light: 3%;
45
- --internal-strength-dark: 3%;
46
- --internal-opacity: 15%;
47
- --internal-border: transparent;
48
- --internal-highlight: color-mix(in srgb, var(--color-foreground, black) 12%, transparent);
54
+ --internal-placeholder: color-mix(in srgb, var(--color-muted,) 35%, var(--color-foreground));
49
55
  box-sizing: border-box;
50
56
  display: inline-flex;
51
57
  align-items: flex-start;
52
58
  align-self: start;
53
- gap: 0.6rem;
59
+ gap: 0.625rem;
54
60
  width: min(100%, 18rem);
55
61
  min-width: 0;
56
62
  max-width: 100%;
57
63
  min-height: var(--internal-height);
58
64
  padding-inline: var(--internal-padding);
59
- padding-block: 0.65rem;
60
- border: 1px solid var(--internal-border);
61
- border-radius: 0.5rem;
62
- background: color-mix(
63
- in srgb,
64
- light-dark(
65
- color-mix(
66
- in srgb,
67
- var(--internal-tint) var(--internal-strength-light),
68
- var(--color-background, white)
69
- ),
70
- color-mix(
71
- in srgb,
72
- var(--internal-tint) var(--internal-strength-dark),
73
- var(--color-background, white)
74
- )
75
- )
76
- var(--internal-opacity),
77
- transparent
78
- );
79
- color: var(--color-foreground, black);
80
- transition: all 150ms ease;
81
- -webkit-backdrop-filter: blur(0.5rem) saturate(120%);
82
- backdrop-filter: blur(0.5rem) saturate(120%);
65
+ padding-block: 0.75rem;
66
+ border-radius: 0.625rem;
83
67
  }
84
68
  div[data-wide] {
85
69
  width: 100%;
86
70
  }
87
71
  div[data-size='small'] {
88
72
  --internal-height: 2rem;
89
- --internal-padding: 0.65rem;
73
+ --internal-padding: 0.75rem;
90
74
  --internal-icon-size: 1rem;
91
75
  }
92
76
  div[data-size='large'] {
93
77
  --internal-height: 3rem;
94
- --internal-padding: 1rem;
78
+ --internal-padding: 1.125rem;
95
79
  --internal-icon-size: 1.5rem;
96
80
  }
97
- div[data-variant='soft'] {
98
- --internal-strength-light: 4%;
99
- --internal-strength-dark: 16%;
100
- --internal-opacity: 85%;
101
- --internal-border: light-dark(
102
- color-mix(in srgb, var(--color-foreground, black) 12%, transparent),
103
- color-mix(in srgb, var(--color-foreground, black) 30%, transparent)
104
- );
105
- box-shadow: inset 0 1px 0 var(--internal-highlight);
106
- }
107
81
  span {
108
82
  display: inline-flex;
109
83
  flex: 0 0 auto;
110
- color: var(--color-muted, #666);
84
+ color: var(--color-muted);
111
85
  font-size: var(--internal-icon-size);
112
86
  padding-top: 0.1rem;
113
87
  pointer-events: none;
@@ -124,7 +98,7 @@
124
98
  font: inherit;
125
99
  font-size: 1rem;
126
100
  font-weight: 200;
127
- line-height: 1.4;
101
+ line-height: 1.5;
128
102
  }
129
103
  textarea {
130
104
  resize: vertical;
@@ -133,41 +107,27 @@
133
107
  resize: none;
134
108
  }
135
109
  textarea::placeholder {
136
- color: var(--color-muted, #666);
137
- }
138
- div[data-variant='soft']:hover:not(:has(:disabled)) {
139
- --internal-border: light-dark(
140
- color-mix(in srgb, var(--color-foreground, black) 18%, transparent),
141
- color-mix(in srgb, var(--color-foreground, black) 38%, transparent)
142
- );
143
- --internal-strength-light: 6%;
144
- --internal-strength-dark: 20%;
110
+ color: var(--internal-placeholder);
111
+ opacity: 1;
145
112
  }
146
113
  div:has(textarea:focus) {
147
- --internal-tint: var(--color-primary, blue);
148
- --internal-strength-light: 6%;
149
- --internal-strength-dark: 12%;
150
- --internal-border: color-mix(in srgb, var(--internal-tint) 55%, transparent);
151
- box-shadow:
152
- inset 0 1px 0 var(--internal-highlight),
153
- 0 0 0 3px color-mix(in srgb, var(--internal-tint) 16%, transparent);
114
+ outline: 2px solid var(--color-primary);
115
+ outline-offset: 2px;
154
116
  }
155
- div:has(textarea:invalid, [aria-invalid='true']) {
156
- --internal-tint: var(--color-error, #bc2436);
157
- --internal-border: color-mix(in srgb, var(--internal-tint) 65%, transparent);
117
+ div:has(textarea:user-invalid, textarea[aria-invalid='true']) {
118
+ --surface-border: var(--color-error);
119
+ outline-color: var(--color-error);
158
120
  }
159
121
  div:has(:disabled) {
160
122
  opacity: 0.5;
161
123
  cursor: not-allowed;
162
124
  }
125
+ div[data-variant='soft']:has(textarea:disabled):hover {
126
+ --surface-strength: var(--surface-soft-strength);
127
+ }
163
128
  textarea:disabled {
164
129
  cursor: inherit;
165
130
  }
166
- @media (prefers-reduced-motion: reduce) {
167
- div {
168
- transition: none;
169
- }
170
- }
171
131
  @media (forced-colors: active) {
172
132
  div {
173
133
  border-color: ButtonText;
@@ -9,5 +9,6 @@ export type TextAreaProps = Omit<HTMLTextareaAttributes, 'value'> & {
9
9
  size?: BaseSize;
10
10
  wide?: boolean;
11
11
  variant?: Exclude<Variant, 'filled'>;
12
+ glass?: boolean;
12
13
  resizable?: boolean;
13
14
  };
@@ -4,5 +4,5 @@
4
4
  "exports": {
5
5
  "TextInput": "text-input.svelte"
6
6
  },
7
- "dependencies": ["icon"]
7
+ "dependencies": ["icon", "theme"]
8
8
  }
@@ -1,6 +1,6 @@
1
1
  <script lang="ts">
2
- /* eslint-disable max-lines -- Keep the native control and its scoped style recipe together. */
3
2
  import Icon from '../icon/icon.svelte';
3
+ import { surface } from '../theme/surface.js';
4
4
  import type { TextInputProps } from './types.js';
5
5
 
6
6
  let {
@@ -9,7 +9,8 @@
9
9
  icon,
10
10
  size = 'medium',
11
11
  wide = false,
12
- variant = 'neutral',
12
+ variant = 'soft',
13
+ glass = false,
13
14
  oninput,
14
15
  defaultValue,
15
16
  ...attributes
@@ -22,7 +23,17 @@
22
23
  };
23
24
  </script>
24
25
 
25
- <div data-size={size} data-variant={variant} data-wide={wide || undefined}>
26
+ <div
27
+ class={surface({
28
+ color: 'background',
29
+ variant,
30
+ glass,
31
+ hover: variant === 'soft' && !attributes.disabled ? 'small' : undefined
32
+ })}
33
+ data-size={size}
34
+ data-variant={variant}
35
+ data-wide={wide || undefined}
36
+ >
26
37
  {#if icon}
27
38
  <span><Icon {...icon} label={undefined} /></span>
28
39
  {/if}
@@ -37,75 +48,38 @@
37
48
  <style>
38
49
  div {
39
50
  --internal-height: 2.5rem;
40
- --internal-padding: 0.85rem;
51
+ --internal-padding: 1rem;
41
52
  --internal-icon-size: 1.25rem;
42
- --internal-tint: var(--color-foreground, black);
43
- --internal-strength-light: 3%;
44
- --internal-strength-dark: 3%;
45
- --internal-opacity: 15%;
46
- --internal-border: transparent;
47
- --internal-highlight: color-mix(in srgb, var(--color-foreground, black) 12%, transparent);
53
+ --internal-placeholder: color-mix(in srgb, var(--color-muted,) 35%, var(--color-foreground));
48
54
  box-sizing: border-box;
49
55
  display: inline-flex;
50
56
  align-items: center;
51
57
  align-self: start;
52
- gap: 0.6rem;
58
+ gap: 0.625rem;
53
59
  width: min(100%, 18rem);
54
60
  min-width: 0;
55
61
  max-width: 100%;
56
62
  min-height: var(--internal-height);
57
63
  padding-inline: var(--internal-padding);
58
- border: 1px solid var(--internal-border);
59
- border-radius: 0.5rem;
60
- background: color-mix(
61
- in srgb,
62
- light-dark(
63
- color-mix(
64
- in srgb,
65
- var(--internal-tint) var(--internal-strength-light),
66
- var(--color-background, white)
67
- ),
68
- color-mix(
69
- in srgb,
70
- var(--internal-tint) var(--internal-strength-dark),
71
- var(--color-background, white)
72
- )
73
- )
74
- var(--internal-opacity),
75
- transparent
76
- );
77
- color: var(--color-foreground, black);
78
- transition: all 150ms ease;
79
- -webkit-backdrop-filter: blur(0.5rem) saturate(120%);
80
- backdrop-filter: blur(0.5rem) saturate(120%);
64
+ border-radius: 0.625rem;
81
65
  }
82
66
  div[data-wide] {
83
67
  width: 100%;
84
68
  }
85
69
  div[data-size='small'] {
86
70
  --internal-height: 2rem;
87
- --internal-padding: 0.65rem;
71
+ --internal-padding: 0.75rem;
88
72
  --internal-icon-size: 1rem;
89
73
  }
90
74
  div[data-size='large'] {
91
75
  --internal-height: 3rem;
92
- --internal-padding: 1rem;
76
+ --internal-padding: 1.125rem;
93
77
  --internal-icon-size: 1.5rem;
94
78
  }
95
- div[data-variant='soft'] {
96
- --internal-strength-light: 4%;
97
- --internal-strength-dark: 16%;
98
- --internal-opacity: 85%;
99
- --internal-border: light-dark(
100
- color-mix(in srgb, var(--color-foreground, black) 12%, transparent),
101
- color-mix(in srgb, var(--color-foreground, black) 30%, transparent)
102
- );
103
- box-shadow: inset 0 1px 0 var(--internal-highlight);
104
- }
105
79
  span {
106
80
  display: inline-flex;
107
81
  flex: 0 0 auto;
108
- color: var(--color-muted, #666);
82
+ color: var(--color-muted);
109
83
  font-size: var(--internal-icon-size);
110
84
  pointer-events: none;
111
85
  }
@@ -121,7 +95,7 @@
121
95
  font: inherit;
122
96
  font-size: 1rem;
123
97
  font-weight: 200;
124
- line-height: 1.4;
98
+ line-height: 1.5;
125
99
  }
126
100
  input {
127
101
  height: calc(var(--internal-height) - 2px);
@@ -136,41 +110,27 @@
136
110
  margin: 0;
137
111
  }
138
112
  input::placeholder {
139
- color: var(--color-muted, #666);
140
- }
141
- div[data-variant='soft']:hover:not(:has(:disabled)) {
142
- --internal-border: light-dark(
143
- color-mix(in srgb, var(--color-foreground, black) 18%, transparent),
144
- color-mix(in srgb, var(--color-foreground, black) 38%, transparent)
145
- );
146
- --internal-strength-light: 6%;
147
- --internal-strength-dark: 20%;
113
+ color: var(--internal-placeholder);
114
+ opacity: 1;
148
115
  }
149
116
  div:has(input:focus) {
150
- --internal-tint: var(--color-primary, blue);
151
- --internal-strength-light: 6%;
152
- --internal-strength-dark: 12%;
153
- --internal-border: color-mix(in srgb, var(--internal-tint) 55%, transparent);
154
- box-shadow:
155
- inset 0 1px 0 var(--internal-highlight),
156
- 0 0 0 3px color-mix(in srgb, var(--internal-tint) 16%, transparent);
157
- }
158
- div:has(input:invalid, [aria-invalid='true']) {
159
- --internal-tint: var(--color-error, #bc2436);
160
- --internal-border: color-mix(in srgb, var(--internal-tint) 65%, transparent);
117
+ outline: 2px solid var(--color-primary);
118
+ outline-offset: 2px;
119
+ }
120
+ div:has(input:user-invalid, input[aria-invalid='true']) {
121
+ --surface-border: var(--color-error);
122
+ outline-color: var(--color-error);
161
123
  }
162
124
  div:has(:disabled) {
163
125
  opacity: 0.5;
164
126
  cursor: not-allowed;
165
127
  }
128
+ div[data-variant='soft']:has(input:disabled):hover {
129
+ --surface-strength: var(--surface-soft-strength);
130
+ }
166
131
  input:disabled {
167
132
  cursor: inherit;
168
133
  }
169
- @media (prefers-reduced-motion: reduce) {
170
- div {
171
- transition: none;
172
- }
173
- }
174
134
  @media (forced-colors: active) {
175
135
  div {
176
136
  border-color: ButtonText;
@@ -9,4 +9,5 @@ export type TextInputProps = Omit<HTMLInputAttributes, 'value' | 'size'> & {
9
9
  size?: BaseSize;
10
10
  wide?: boolean;
11
11
  variant?: Exclude<Variant, 'filled'>;
12
+ glass?: boolean;
12
13
  };
@@ -2,6 +2,7 @@
2
2
  import type { Snippet } from 'svelte';
3
3
  import Colors from './colors.svelte';
4
4
  import Surfaces from './surfaces.svelte';
5
+ import Glass from './glass.svelte';
5
6
  import Fonts from './fonts.svelte';
6
7
  import Styles from './styles.svelte';
7
8
 
@@ -10,6 +11,7 @@
10
11
 
11
12
  <Colors />
12
13
  <Surfaces />
14
+ <Glass />
13
15
  <Fonts />
14
16
  <Styles />
15
17
 
@@ -0,0 +1,60 @@
1
+ <style>
2
+ :global(.glass) {
3
+ --glass-paint: var(--surface-fill, var(--color-background, white));
4
+ --glass-opacity: 20%;
5
+ --glass-blur: blur(0.75rem) saturate(120%);
6
+ }
7
+ /* Three class selectors keep opt-in material above ordinary scoped component paint.
8
+ Inline styles and explicit selectors can still override it. */
9
+ :global(.glass.glass.glass) {
10
+ background: var(--glass-paint);
11
+ color: var(--surface-content, var(--color-foreground, black));
12
+ box-shadow:
13
+ inset 0 1px 0 color-mix(in srgb, var(--color-foreground, black) 8%, transparent),
14
+ var(--surface-shadow, 0 0.5rem 1.5rem color-mix(in srgb, black 12%, transparent));
15
+ }
16
+ :global(.glass:where(.surface-neutral)) {
17
+ --glass-paint: color-mix(
18
+ in srgb,
19
+ var(--surface-tint) var(--surface-strength),
20
+ var(--surface-base)
21
+ );
22
+ }
23
+ :global(.glass:where(.surface-soft)) {
24
+ --glass-opacity: 40%;
25
+ }
26
+ :global(.glass:where(.surface-filled)) {
27
+ --glass-opacity: 65%;
28
+ }
29
+ @supports ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
30
+ :global(.glass.glass.glass) {
31
+ background: color-mix(in srgb, var(--glass-paint) var(--glass-opacity), transparent);
32
+ -webkit-backdrop-filter: var(--glass-blur);
33
+ backdrop-filter: var(--glass-blur);
34
+ }
35
+ }
36
+ @media (prefers-reduced-transparency: reduce), (prefers-contrast: more) {
37
+ :global(.glass.glass.glass) {
38
+ background: var(--glass-paint);
39
+ box-shadow: var(--surface-shadow, 0 0 0 transparent);
40
+ -webkit-backdrop-filter: none;
41
+ backdrop-filter: none;
42
+ }
43
+ }
44
+ @media (forced-colors: active) {
45
+ :global(.glass.glass.glass) {
46
+ border-color: CanvasText;
47
+ background: Canvas;
48
+ color: CanvasText;
49
+ box-shadow: none;
50
+ -webkit-backdrop-filter: none;
51
+ backdrop-filter: none;
52
+ }
53
+ :global(
54
+ .glass.glass.glass:is(:disabled, [aria-disabled='true'], [data-surface-disabled='true'])
55
+ ) {
56
+ color: GrayText;
57
+ opacity: 1;
58
+ }
59
+ }
60
+ </style>
@@ -4,6 +4,7 @@
4
4
  "cli-theme.svelte",
5
5
  "colors.svelte",
6
6
  "surfaces.svelte",
7
+ "glass.svelte",
7
8
  "fonts.svelte",
8
9
  "styles.svelte",
9
10
  "surface.ts"
@@ -1,7 +1,13 @@
1
1
  <style>
2
+ :global(:root) {
3
+ --font-sans:
4
+ ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
5
+ }
6
+
2
7
  :global(body) {
3
8
  background: var(--color-background);
4
9
  color: var(--color-foreground);
10
+ font-family: var(--font-text, var(--font-sans));
5
11
  }
6
12
 
7
13
  :global(*) {
@@ -5,14 +5,16 @@ export const surface = ({
5
5
  variant = 'neutral',
6
6
  color = 'foreground',
7
7
  shadow,
8
- hover
8
+ hover,
9
+ glass = false
9
10
  }: SurfaceBaseProps = {}): string =>
10
11
  [
11
12
  'surface',
12
13
  `surface-${variant}`,
13
14
  `surface-${color}`,
14
15
  shadow && `surface-shadow-${shadow === true ? 'medium' : shadow}`,
15
- hover && `surface-hover-${hover}`
16
+ hover && `surface-hover-${hover}`,
17
+ glass && 'glass'
16
18
  ]
17
19
  .filter(Boolean)
18
20
  .join(' ');