@wovenui/components 0.0.0-stage → 1.0.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 (130) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +284 -2
  3. package/dist/components.json +4204 -0
  4. package/dist/llms-full.txt +2839 -0
  5. package/dist/wovenui-kit.css +5002 -0
  6. package/dist/wovenui-kit.css.map +1 -0
  7. package/dist/wovenui-kit.iife.js +532 -0
  8. package/dist/wovenui-kit.js +529 -0
  9. package/dist/wovenui-kit.min.css +2 -0
  10. package/dist/wovenui-kit.min.css.map +1 -0
  11. package/package.json +268 -4
  12. package/src/_close.scss +45 -0
  13. package/src/_helpers.scss +11 -0
  14. package/src/_kit.scss +124 -0
  15. package/src/_shared.scss +10 -0
  16. package/src/components/_accordion.scss +132 -0
  17. package/src/components/_alert.scss +161 -0
  18. package/src/components/_autocomplete.scss +129 -0
  19. package/src/components/_avatar-group.scss +80 -0
  20. package/src/components/_avatar.scss +134 -0
  21. package/src/components/_badge.scss +91 -0
  22. package/src/components/_breadcrumb.scss +115 -0
  23. package/src/components/_btn-group.scss +113 -0
  24. package/src/components/_button.scss +301 -0
  25. package/src/components/_card.scss +120 -0
  26. package/src/components/_checkbox.scss +168 -0
  27. package/src/components/_code-block.scss +78 -0
  28. package/src/components/_confirm-modal.scss +75 -0
  29. package/src/components/_data-table.scss +79 -0
  30. package/src/components/_desc-list.scss +180 -0
  31. package/src/components/_divider.scss +66 -0
  32. package/src/components/_dot.scss +115 -0
  33. package/src/components/_drawer.scss +200 -0
  34. package/src/components/_dropdown.scss +182 -0
  35. package/src/components/_empty-state.scss +146 -0
  36. package/src/components/_field-group.scss +68 -0
  37. package/src/components/_field-message.scss +75 -0
  38. package/src/components/_field.scss +127 -0
  39. package/src/components/_form-actions.scss +58 -0
  40. package/src/components/_info-row.scss +69 -0
  41. package/src/components/_input-group.scss +87 -0
  42. package/src/components/_input.scss +278 -0
  43. package/src/components/_list.scss +76 -0
  44. package/src/components/_modal.scss +168 -0
  45. package/src/components/_page-header.scss +85 -0
  46. package/src/components/_pagination.scss +138 -0
  47. package/src/components/_panel.scss +69 -0
  48. package/src/components/_popover.scss +154 -0
  49. package/src/components/_progress-circle.scss +123 -0
  50. package/src/components/_progress.scss +122 -0
  51. package/src/components/_radio.scss +162 -0
  52. package/src/components/_segmented.scss +183 -0
  53. package/src/components/_select.scss +154 -0
  54. package/src/components/_sidebar.scss +300 -0
  55. package/src/components/_skeleton.scss +90 -0
  56. package/src/components/_spinner.scss +84 -0
  57. package/src/components/_stat.scss +92 -0
  58. package/src/components/_table.scss +460 -0
  59. package/src/components/_tabs.scss +327 -0
  60. package/src/components/_tag.scss +143 -0
  61. package/src/components/_text.scss +42 -0
  62. package/src/components/_textarea.scss +57 -0
  63. package/src/components/_timeline.scss +196 -0
  64. package/src/components/_title.scss +41 -0
  65. package/src/components/_toast.scss +221 -0
  66. package/src/components/_toggle.scss +171 -0
  67. package/src/components/_toolbar.scss +74 -0
  68. package/src/components/_topbar.scss +204 -0
  69. package/src/entries/accordion.scss +4 -0
  70. package/src/entries/alert.scss +4 -0
  71. package/src/entries/autocomplete.scss +4 -0
  72. package/src/entries/avatar-group.scss +4 -0
  73. package/src/entries/avatar.scss +4 -0
  74. package/src/entries/badge.scss +4 -0
  75. package/src/entries/breadcrumb.scss +4 -0
  76. package/src/entries/btn-group.scss +4 -0
  77. package/src/entries/button.scss +4 -0
  78. package/src/entries/card.scss +4 -0
  79. package/src/entries/checkbox.scss +4 -0
  80. package/src/entries/code-block.scss +4 -0
  81. package/src/entries/confirm-modal.scss +4 -0
  82. package/src/entries/data-table.scss +4 -0
  83. package/src/entries/desc-list.scss +4 -0
  84. package/src/entries/divider.scss +4 -0
  85. package/src/entries/dot.scss +4 -0
  86. package/src/entries/drawer.scss +4 -0
  87. package/src/entries/dropdown.scss +4 -0
  88. package/src/entries/empty-state.scss +4 -0
  89. package/src/entries/field-group.scss +4 -0
  90. package/src/entries/field-message.scss +4 -0
  91. package/src/entries/field.scss +4 -0
  92. package/src/entries/form-actions.scss +4 -0
  93. package/src/entries/info-row.scss +4 -0
  94. package/src/entries/input-group.scss +4 -0
  95. package/src/entries/input.scss +4 -0
  96. package/src/entries/list.scss +4 -0
  97. package/src/entries/modal.scss +4 -0
  98. package/src/entries/page-header.scss +4 -0
  99. package/src/entries/pagination.scss +4 -0
  100. package/src/entries/panel.scss +4 -0
  101. package/src/entries/popover.scss +4 -0
  102. package/src/entries/progress-circle.scss +4 -0
  103. package/src/entries/progress.scss +4 -0
  104. package/src/entries/radio.scss +4 -0
  105. package/src/entries/segmented.scss +4 -0
  106. package/src/entries/select.scss +4 -0
  107. package/src/entries/sidebar.scss +4 -0
  108. package/src/entries/skeleton.scss +4 -0
  109. package/src/entries/spinner.scss +4 -0
  110. package/src/entries/stat.scss +4 -0
  111. package/src/entries/table.scss +4 -0
  112. package/src/entries/tabs.scss +4 -0
  113. package/src/entries/tag.scss +4 -0
  114. package/src/entries/text.scss +4 -0
  115. package/src/entries/textarea.scss +4 -0
  116. package/src/entries/timeline.scss +4 -0
  117. package/src/entries/title.scss +4 -0
  118. package/src/entries/toast.scss +4 -0
  119. package/src/entries/toggle.scss +4 -0
  120. package/src/entries/toolbar.scss +4 -0
  121. package/src/entries/topbar.scss +4 -0
  122. package/src/index.scss +7 -0
  123. package/src/js/autocomplete.js +86 -0
  124. package/src/js/data-table.js +141 -0
  125. package/src/js/index.js +34 -0
  126. package/src/js/input-clear.js +25 -0
  127. package/src/js/package.json +3 -0
  128. package/src/js/tabs.js +73 -0
  129. package/src/js/toast.js +161 -0
  130. package/src/js/util.js +21 -0
@@ -0,0 +1,162 @@
1
+ ////
2
+ //// @component wu-radio
3
+ //// Radio button with a label, drawn from the native input.
4
+ ////
5
+ //// The circle is `1em`, so it follows the root type size. The `wu-radio-input` part also works alone with an `aria-label`.
6
+ ////
7
+ //// Inside a `wu-field-group sm` or `lg`, a radio without its own size takes the group size.
8
+ ////
9
+ //// @since 1.0.0
10
+ //// @needs none
11
+ //// @js none
12
+ //// @example html
13
+ //// <label class="wu-radio">
14
+ //// <input type="radio" class="wu-radio-input" name="plan" value="free" checked>
15
+ //// <span class="wu-radio-label">Free</span>
16
+ //// </label>
17
+ //// <label class="wu-radio">
18
+ //// <input type="radio" class="wu-radio-input" name="plan" value="pro">
19
+ //// <span class="wu-radio-label">Pro</span>
20
+ //// </label>
21
+ //// @example html
22
+ //// <input type="radio" class="wu-radio-input" name="size" value="m" aria-label="Medium">
23
+ //// @modifier sm | lg - Smaller or larger text and circle @since 1.0.0
24
+ //// @part wu-radio-input - The native radio input, drawn as a circle with a dot @since 1.0.0
25
+ //// @part wu-radio-label - Label text beside the circle @since 1.0.0
26
+ //// @state disabled - Half opacity, no pointer events @since 1.0.0
27
+ //// @state aria-invalid="true" | :user-invalid - Two pixel danger edge that stays on hover and when checked @since 1.0.0
28
+ //// @state :checked - Brand fill with a dot @since 1.0.0
29
+ ////
30
+
31
+ @use "@wovenui/core/mixins" as woven;
32
+
33
+ .wu-radio {
34
+ display: inline-flex;
35
+ align-items: flex-start;
36
+ gap: woven.token(space-2);
37
+ color: woven.token(text-regular);
38
+ font-size: woven.token(font-size-md);
39
+ line-height: woven.token(line-height-normal);
40
+ cursor: pointer;
41
+
42
+ &:has(.wu-radio-input:disabled) {
43
+ opacity: woven.token(opacity-50);
44
+ cursor: not-allowed;
45
+ }
46
+ }
47
+
48
+ .wu-radio-input {
49
+ display: inline-grid;
50
+ flex: none;
51
+ place-content: center;
52
+ width: 1em;
53
+ height: 1em;
54
+ margin: 0;
55
+ margin-block-start: calc((1lh - 1em) / 2);
56
+ border: woven.token(border-width-1) solid woven.token(border-control);
57
+ border-radius: woven.token(radius-full);
58
+ background: woven.token(bg-surface);
59
+ font-size: inherit;
60
+ line-height: inherit;
61
+ vertical-align: middle;
62
+ appearance: none;
63
+ cursor: pointer;
64
+ transition-property: background-color, border-color;
65
+ transition-duration: woven.token(duration-150);
66
+ transition-timing-function: woven.token(ease-out);
67
+
68
+ // Dot
69
+ &::before {
70
+ content: "";
71
+ width: 0.375em;
72
+ height: 0.375em;
73
+ border-radius: woven.token(radius-full);
74
+ background: woven.token(text-on-brand);
75
+ visibility: hidden;
76
+ }
77
+
78
+ &:hover:not(:disabled, :checked) {
79
+ border-color: woven.token(border-control-hover);
80
+ }
81
+
82
+ &:focus-visible {
83
+ outline: woven.token(border-focus-width) solid woven.token(border-focus);
84
+ outline-offset: woven.token(border-focus-offset);
85
+ }
86
+
87
+ &:checked {
88
+ border-color: woven.token(brand);
89
+ background: woven.token(brand);
90
+
91
+ &::before {
92
+ visibility: visible;
93
+ }
94
+
95
+ &:hover:not(:disabled) {
96
+ border-color: woven.token(brand-hover);
97
+ background: woven.token(brand-hover);
98
+ }
99
+ }
100
+
101
+ // Invalid: a 2px danger edge that stays on hover and when checked
102
+ &:is([aria-invalid="true"], :user-invalid),
103
+ &:is([aria-invalid="true"], :user-invalid):is(:hover, :checked) {
104
+ border-color: woven.token(danger);
105
+ box-shadow: 0 0 0 woven.token(border-width-1) woven.token(danger);
106
+ }
107
+
108
+ &:disabled {
109
+ opacity: woven.token(opacity-50);
110
+ cursor: not-allowed;
111
+ }
112
+
113
+ .wu-radio > &:disabled {
114
+ opacity: 1;
115
+ }
116
+ }
117
+
118
+ .wu-radio-label {
119
+ min-width: 0;
120
+ }
121
+
122
+ .wu-radio.sm {
123
+ font-size: woven.token(font-size-sm);
124
+ }
125
+
126
+ .wu-radio.lg {
127
+ font-size: woven.token(font-size-lg);
128
+ }
129
+
130
+ @media (forced-colors: active) {
131
+ .wu-radio-input {
132
+ border-color: CanvasText;
133
+ background: Canvas;
134
+ forced-color-adjust: none;
135
+
136
+ &::before {
137
+ background: HighlightText;
138
+ }
139
+
140
+ &:checked {
141
+ border-color: Highlight;
142
+ background: Highlight;
143
+ }
144
+
145
+ &:disabled {
146
+ border-color: GrayText;
147
+ }
148
+
149
+ &:disabled:checked {
150
+ background: GrayText;
151
+ }
152
+ }
153
+ }
154
+
155
+ // Size from a wu-field-group sm or lg
156
+ :where(.wu-field-group.sm) .wu-radio:not(.sm, .lg) {
157
+ font-size: woven.token(font-size-sm);
158
+ }
159
+
160
+ :where(.wu-field-group.lg) .wu-radio:not(.sm, .lg) {
161
+ font-size: woven.token(font-size-lg);
162
+ }
@@ -0,0 +1,183 @@
1
+ ////
2
+ //// @component wu-segmented
3
+ //// Radio group shown as connected segments, one selected at a time.
4
+ ////
5
+ //// Arrow keys move the choice natively, and segments share the width equally. A `div` with `role="radiogroup"` and an `aria-label` works instead of a `fieldset`.
6
+ ////
7
+ //// Type follows the button: `xs`, `sm` and `md` for `sm`, default and `lg`. The animated thumb supports up to six label segments.
8
+ ////
9
+ //// @since 1.0.0
10
+ //// @needs none
11
+ //// @js none
12
+ //// @example html
13
+ //// <fieldset class="wu-segmented">
14
+ //// <legend class="sr-only">View</legend>
15
+ //// <label class="wu-segmented-item"><input type="radio" name="view" class="wu-segmented-input" value="day" checked> Day</label>
16
+ //// <label class="wu-segmented-item"><input type="radio" name="view" class="wu-segmented-input" value="week"> Week</label>
17
+ //// <label class="wu-segmented-item"><input type="radio" name="view" class="wu-segmented-input" value="month"> Month</label>
18
+ //// </fieldset>
19
+ //// @modifier sm | lg - Smaller or larger control height and font size @since 1.0.0
20
+ //// @modifier animated - One thumb slides to the checked segment with a spring when the user allows motion; `wu-animated` on an ancestor does the same @since 1.0.0
21
+ //// @part wu-segmented-item - One segment: the label that wraps a radio input @since 1.0.0
22
+ //// @part wu-segmented-input - The radio input, visually hidden but focusable and announced @since 1.0.0
23
+ //// @state :checked - The selected segment has a raised surface, an edge and a shadow @since 1.0.0
24
+ //// @state disabled - Half opacity on that segment @since 1.0.0
25
+ ////
26
+
27
+ @use "@wovenui/core/mixins" as woven;
28
+
29
+ $-inset: woven.token(space-1);
30
+ $-max-animated: 6;
31
+
32
+ .wu-segmented {
33
+ position: relative;
34
+ display: inline-grid;
35
+ grid-auto-columns: 1fr;
36
+ grid-auto-flow: column;
37
+ min-inline-size: 0;
38
+ min-block-size: 0;
39
+ block-size: woven.token(control-height-md);
40
+ margin: 0;
41
+ padding: $-inset;
42
+ border: 0;
43
+ border-radius: woven.token(radius-md);
44
+ background: woven.token(bg-alternate);
45
+ font-size: woven.token(font-size-sm);
46
+ vertical-align: middle;
47
+ }
48
+
49
+ .wu-segmented-item {
50
+ position: relative;
51
+ display: flex;
52
+ align-items: center;
53
+ justify-content: center;
54
+ padding-inline: woven.token(control-padding-inline-sm);
55
+ border-radius: woven.token(radius-sm);
56
+ color: woven.token(text-muted);
57
+ font-weight: woven.token(font-weight-medium);
58
+ line-height: 1;
59
+ white-space: nowrap;
60
+ cursor: pointer;
61
+ transition-property: background-color, box-shadow, color;
62
+ transition-duration: woven.token(duration-150);
63
+ transition-timing-function: woven.token(ease-out);
64
+
65
+ &:hover:not(:has(:checked, :disabled)) {
66
+ color: woven.token(text-regular);
67
+ }
68
+
69
+ &:has(:focus-visible) {
70
+ outline: woven.token(border-focus-width) solid woven.token(border-focus);
71
+ outline-offset: woven.token(border-focus-offset);
72
+ }
73
+
74
+ // Selected: raised surface with an edge and a shadow, so color is not the only signal
75
+ &:has(:checked) {
76
+ background: woven.token(bg-surface);
77
+ box-shadow:
78
+ inset 0 0 0 woven.token(border-width-1) woven.token(border-control),
79
+ woven.token(shadow-sm);
80
+ color: woven.token(text-regular);
81
+ }
82
+
83
+ &:has(:disabled) {
84
+ opacity: woven.token(opacity-50);
85
+ cursor: not-allowed;
86
+ }
87
+ }
88
+
89
+ // Visually hidden, still focusable and announced
90
+ .wu-segmented-input {
91
+ position: absolute;
92
+ width: 1px;
93
+ height: 1px;
94
+ margin: 0;
95
+ opacity: 0;
96
+ clip-path: inset(50%);
97
+ pointer-events: none;
98
+ }
99
+
100
+ .wu-segmented.sm {
101
+ block-size: woven.token(control-height-sm);
102
+ font-size: woven.token(font-size-xs);
103
+
104
+ > .wu-segmented-item {
105
+ padding-inline: woven.token(space-2);
106
+ }
107
+ }
108
+
109
+ .wu-segmented.lg {
110
+ block-size: woven.token(control-height-lg);
111
+ font-size: woven.token(font-size-md);
112
+
113
+ > .wu-segmented-item {
114
+ padding-inline: woven.token(control-padding-inline-md);
115
+ }
116
+ }
117
+
118
+ // Opt-in motion: one thumb slides to the checked segment with a spring (up to six label segments)
119
+ @media (prefers-reduced-motion: no-preference) {
120
+ .wu-segmented.animated:not(:has(> .wu-segmented-item:nth-of-type(#{$-max-animated + 1}))),
121
+ .wu-animated .wu-segmented:not(:has(> .wu-segmented-item:nth-of-type(#{$-max-animated + 1}))) {
122
+ &:has(:checked)::before {
123
+ content: "";
124
+ position: absolute;
125
+ inset-block: $-inset;
126
+ inset-inline-start: $-inset;
127
+ border-radius: woven.token(radius-sm);
128
+ background: woven.token(bg-surface);
129
+ box-shadow:
130
+ inset 0 0 0 woven.token(border-width-1) woven.token(border-control),
131
+ woven.token(shadow-sm);
132
+ transition: translate woven.token(duration-500) var(--wu-kit-ease-spring);
133
+ }
134
+
135
+ @for $n from 1 through $-max-animated {
136
+ &:has(> .wu-segmented-item:nth-of-type(#{$n}):last-of-type)::before {
137
+ width: calc((100% - 2 * #{$-inset}) / #{$n});
138
+ }
139
+ }
140
+
141
+ @for $i from 2 through $-max-animated {
142
+ &:has(> .wu-segmented-item:nth-of-type(#{$i}) :checked)::before {
143
+ translate: ($i - 1) * 100% 0;
144
+ }
145
+
146
+ &:dir(rtl):has(> .wu-segmented-item:nth-of-type(#{$i}) :checked)::before {
147
+ translate: ($i - 1) * -100% 0;
148
+ }
149
+ }
150
+
151
+ > .wu-segmented-item:has(:checked) {
152
+ background: transparent;
153
+ box-shadow: none;
154
+ }
155
+ }
156
+ }
157
+
158
+ @media (forced-colors: active) {
159
+ .wu-segmented {
160
+ border: woven.token(border-width-1) solid ButtonBorder;
161
+ }
162
+
163
+ .wu-segmented-item:has(:checked),
164
+ .wu-segmented::before {
165
+ background: Highlight;
166
+ color: HighlightText;
167
+ forced-color-adjust: none;
168
+ }
169
+ }
170
+
171
+ // Size from a wu-field-group sm or lg
172
+ @each $size, $font in ("sm": "font-size-xs", "lg": "font-size-md") {
173
+ :where(.wu-field-group.#{$size}) .wu-segmented:not(.sm, .lg) {
174
+ block-size: woven.token(control-height-#{$size});
175
+ font-size: woven.token(#{$font});
176
+ }
177
+ }
178
+
179
+ .wu-segmented-item > svg {
180
+ flex-shrink: 0;
181
+ width: 1em;
182
+ height: 1em;
183
+ }
@@ -0,0 +1,154 @@
1
+ ////
2
+ //// @component wu-select
3
+ //// Native `select` with the kit input look and a drawn arrow.
4
+ ////
5
+ //// Core base already sets the border, background, hover, focus ring, invalid border, disabled look, padding and the default height. List boxes (`multiple` or `size`) keep their natural height and have no arrow.
6
+ ////
7
+ //// Where `appearance: base-select` is supported, the picker matches the kit; other browsers keep the native list.
8
+ ////
9
+ //// Inside a `wu-field-group sm` or `lg`, a select without its own size takes the group size.
10
+ ////
11
+ //// @since 1.0.0
12
+ //// @needs none
13
+ //// @js none
14
+ //// @example html
15
+ //// <select class="wu-select" name="country" aria-label="Country">
16
+ //// <option>Pakistan</option>
17
+ //// <option>United Arab Emirates</option>
18
+ //// </select>
19
+ //// @example html
20
+ //// <select class="wu-select sm" name="sort" aria-label="Sort by" aria-invalid="true">
21
+ //// <option>Newest</option>
22
+ //// <option>Oldest</option>
23
+ //// </select>
24
+ //// @modifier sm | lg - Smaller or larger control height and padding @since 1.0.0
25
+ //// @variable --wu-select--ArrowColor [var(--wu-text-muted)] - Color of the arrow @since 1.0.0
26
+ //// @state disabled - Not-allowed cursor on top of core disabled look @since 1.0.0
27
+ //// @state aria-invalid="true" | :user-invalid - Danger edge doubled to two pixels, so the change is not color alone @since 1.0.0
28
+ ////
29
+
30
+ @use "@wovenui/core/mixins" as woven;
31
+
32
+ $-arrow: 0.3125rem;
33
+ $-arrow-color: var(--wu-select--ArrowColor, #{woven.token(text-muted)});
34
+
35
+ .wu-select {
36
+ border-radius: woven.token(radius-md);
37
+ font-size: woven.token(font-size-md);
38
+ line-height: woven.token(line-height-normal);
39
+
40
+ &:disabled {
41
+ cursor: not-allowed;
42
+ }
43
+
44
+ // Invalid: core's danger border doubled to 2px, so the change is not color alone
45
+ &:is([aria-invalid="true"], :user-invalid) {
46
+ box-shadow: inset 0 0 0 woven.token(border-width-1) woven.token(danger);
47
+ }
48
+ }
49
+
50
+ // Single-line select: CSS triangle arrow from two gradient halves at the inline end
51
+ .wu-select:not([multiple], [size]:not([size="1"])) {
52
+ padding-inline-end: calc(#{woven.token(control-padding-inline-md)} + 2 * #{$-arrow} + #{woven.token(space-2)});
53
+ background-image: linear-gradient(45deg, transparent 50%, #{$-arrow-color} 50%), linear-gradient(135deg, #{$-arrow-color} 50%, transparent 50%);
54
+ background-position:
55
+ calc(100% - #{woven.token(control-padding-inline-md)} - #{$-arrow}) 50%,
56
+ calc(100% - #{woven.token(control-padding-inline-md)}) 50%;
57
+ background-size: $-arrow $-arrow;
58
+ background-repeat: no-repeat;
59
+ appearance: none;
60
+ cursor: pointer;
61
+
62
+ &:dir(rtl) {
63
+ background-position:
64
+ woven.token(control-padding-inline-md) 50%,
65
+ calc(#{woven.token(control-padding-inline-md)} + #{$-arrow}) 50%;
66
+ }
67
+
68
+ &:disabled {
69
+ cursor: not-allowed;
70
+ }
71
+ }
72
+
73
+ .wu-select.sm {
74
+ padding-inline: woven.token(control-padding-inline-sm);
75
+ font-size: woven.token(font-size-sm);
76
+ }
77
+
78
+ .wu-select.lg {
79
+ padding-inline: woven.token(control-padding-inline-lg);
80
+ }
81
+
82
+ // Height and centered text for the class and the field-group cascade, one rule
83
+ @each $size, $font in ("sm": "font-size-sm", "lg": "font-size-md") {
84
+ .wu-select.#{$size}:not([multiple], [size]:not([size="1"])),
85
+ :where(.wu-field-group.#{$size}) .wu-select:not(.sm, .lg, .wu-input-affix > *, [multiple], [size]:not([size="1"])) {
86
+ height: woven.token(control-height-#{$size});
87
+ padding-block: round(down, calc((#{woven.token(control-height-#{$size})} - 2 * #{woven.token(border-width-1)} - #{woven.token(#{$font})} * #{woven.token(line-height-normal)}) / 2), 1px);
88
+ }
89
+ }
90
+
91
+ // The wrapper sets no padding on its select, so center the text in the box
92
+ .wu-input-affix > .wu-select:not([multiple], [size]:not([size="1"])) {
93
+ align-items: center;
94
+ }
95
+
96
+ // Customizable select where supported: a picker that matches the kit; other browsers keep the native list
97
+ @supports (appearance: base-select) {
98
+ .wu-select:not([multiple], [size]:not([size="1"])) {
99
+ appearance: base-select;
100
+
101
+ &::picker-icon {
102
+ display: none;
103
+ }
104
+
105
+ &::picker(select) {
106
+ margin-block: woven.token(space-1);
107
+ padding: woven.token(space-1);
108
+ border: woven.token(border-width-1) solid woven.token(border-regular);
109
+ border-radius: woven.token(radius-md);
110
+ background: woven.token(bg-raised);
111
+ box-shadow: woven.token(shadow-lg);
112
+ color: woven.token(text-regular);
113
+ appearance: base-select;
114
+ }
115
+
116
+ option {
117
+ padding-block: woven.token(space-2);
118
+ padding-inline: woven.token(control-padding-inline-sm);
119
+ border-radius: woven.token(radius-sm);
120
+ cursor: pointer;
121
+ }
122
+
123
+ option:is(:hover, :focus-visible) {
124
+ background: woven.token(bg-hover);
125
+ }
126
+
127
+ option:checked {
128
+ background: woven.token(bg-selected);
129
+ color: woven.token(text-brand);
130
+ }
131
+
132
+ option:disabled {
133
+ opacity: woven.token(opacity-50);
134
+ cursor: not-allowed;
135
+ }
136
+ }
137
+ }
138
+
139
+ // Forced colors: the native control and its own arrow, also over the customizable select
140
+ @media (forced-colors: active) {
141
+ .wu-select:not([multiple], [size]:not([size="1"])) {
142
+ padding-inline-end: woven.token(control-padding-inline-md);
143
+ background-image: none;
144
+ appearance: auto;
145
+ }
146
+ }
147
+
148
+ // Size from a wu-field-group sm or lg; a select with its own sm or lg keeps it
149
+ @each $size, $font in ("sm": "font-size-sm", "lg": "font-size-md") {
150
+ :where(.wu-field-group.#{$size}) .wu-select:not(.sm, .lg, .wu-input-affix > *, [multiple], [size]:not([size="1"])) {
151
+ padding-inline-start: woven.token(control-padding-inline-#{$size});
152
+ font-size: woven.token(#{$font});
153
+ }
154
+ }