@asnewyla/select 0.2.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.
- package/LICENSE +21 -0
- package/README.md +110 -0
- package/dist/index.css +259 -0
- package/dist/index.css.map +1 -0
- package/dist/index.d.mts +11 -0
- package/dist/index.d.ts +11 -0
- package/dist/index.js +66 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +64 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +65 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 XD
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
# @asnewyla/select
|
|
2
|
+
|
|
3
|
+
Styled select/combobox built on `@asnewyla/unstyled-select`. Label
|
|
4
|
+
association, inline validation error, client-side search/filter, and
|
|
5
|
+
removable chips for multi-select — full keyboard/focus accessibility.
|
|
6
|
+
|
|
7
|
+
## Install
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npm install @asnewyla/select
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
Import the stylesheet once, anywhere in your app:
|
|
14
|
+
|
|
15
|
+
```ts
|
|
16
|
+
import '@asnewyla/select/styles.css';
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## Usage
|
|
20
|
+
|
|
21
|
+
```tsx
|
|
22
|
+
import { Select } from '@asnewyla/select';
|
|
23
|
+
|
|
24
|
+
const fruitOptions = [
|
|
25
|
+
{ value: 'apple', label: 'Apple' },
|
|
26
|
+
{ value: 'banana', label: 'Banana' },
|
|
27
|
+
{ value: 'cherry', label: 'Cherry', disabled: true },
|
|
28
|
+
];
|
|
29
|
+
|
|
30
|
+
<Select
|
|
31
|
+
label="Fruit"
|
|
32
|
+
options={fruitOptions}
|
|
33
|
+
placeholder="Choose a fruit"
|
|
34
|
+
/>
|
|
35
|
+
|
|
36
|
+
<Select
|
|
37
|
+
label="Fruit"
|
|
38
|
+
options={fruitOptions}
|
|
39
|
+
error="Pick a fruit to continue"
|
|
40
|
+
/>
|
|
41
|
+
|
|
42
|
+
// Controlled
|
|
43
|
+
<Select
|
|
44
|
+
label="Fruit"
|
|
45
|
+
options={fruitOptions}
|
|
46
|
+
value={selected}
|
|
47
|
+
onChange={setSelected}
|
|
48
|
+
/>
|
|
49
|
+
|
|
50
|
+
// Multi-select — selected options render as removable chips
|
|
51
|
+
<Select
|
|
52
|
+
label="Fruit"
|
|
53
|
+
options={fruitOptions}
|
|
54
|
+
multiple
|
|
55
|
+
defaultValue={['apple', 'banana']}
|
|
56
|
+
onChange={(values) => console.log(values)}
|
|
57
|
+
/>
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
### Props
|
|
61
|
+
|
|
62
|
+
Everything `@asnewyla/unstyled-select` accepts, plus:
|
|
63
|
+
|
|
64
|
+
| Prop | Type | Default |
|
|
65
|
+
|---|---|---|
|
|
66
|
+
| `label` | `string` (**required**) | — |
|
|
67
|
+
| `error` | `string` | — |
|
|
68
|
+
|
|
69
|
+
`error` doubles as the invalid-state flag — there's no separate `invalid`
|
|
70
|
+
prop, and `renderValue` isn't exposed either (its multi-select chip
|
|
71
|
+
rendering is what this package provides). Setting `error` sets
|
|
72
|
+
`aria-invalid`/`data-invalid` on the trigger and links it via
|
|
73
|
+
`aria-describedby` to the rendered error message. Same convention
|
|
74
|
+
`@asnewyla/input` and `@asnewyla/checkbox` use.
|
|
75
|
+
|
|
76
|
+
### Multi-select chips
|
|
77
|
+
|
|
78
|
+
Each selected option renders as a chip with its own remove button
|
|
79
|
+
(`aria-label="Remove <label>"`). Removing a chip calls `onChange` with
|
|
80
|
+
that option deselected — the same toggle clicking it again in the listbox
|
|
81
|
+
would trigger. The listbox is still there for adding more options; chips
|
|
82
|
+
are just an alternate way to see and remove what's already selected.
|
|
83
|
+
|
|
84
|
+
Single-select shows the selected label as plain text, unchanged from
|
|
85
|
+
`@asnewyla/unstyled-select`'s own default — chips are a multi-select-only
|
|
86
|
+
affordance.
|
|
87
|
+
|
|
88
|
+
### Theming
|
|
89
|
+
|
|
90
|
+
Override the CSS custom properties — defaults adapt automatically to
|
|
91
|
+
`prefers-color-scheme` via `@asnewyla/tokens`:
|
|
92
|
+
|
|
93
|
+
```css
|
|
94
|
+
:root {
|
|
95
|
+
--xd-color-primary: #0d9488;
|
|
96
|
+
--xd-color-destructive: #dc2626;
|
|
97
|
+
--xd-color-surface: #ffffff;
|
|
98
|
+
}
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
### Not yet supported
|
|
102
|
+
|
|
103
|
+
Inherits `@asnewyla/unstyled-select`'s own limitations — see its README:
|
|
104
|
+
no native `FormData` form participation, and the listbox popup renders
|
|
105
|
+
inline (no portal), so it can be visually clipped by an `overflow: hidden`
|
|
106
|
+
or scrolling ancestor.
|
|
107
|
+
|
|
108
|
+
## License
|
|
109
|
+
|
|
110
|
+
MIT
|
package/dist/index.css
ADDED
|
@@ -0,0 +1,259 @@
|
|
|
1
|
+
/* src/components/Select.css */
|
|
2
|
+
.xd-select,
|
|
3
|
+
.xd-select *,
|
|
4
|
+
.xd-select *::before,
|
|
5
|
+
.xd-select *::after {
|
|
6
|
+
box-sizing: border-box;
|
|
7
|
+
}
|
|
8
|
+
.xd-select {
|
|
9
|
+
--xd-select-gap: var(--xd-space-xs, 0.25rem);
|
|
10
|
+
--xd-select-max-h: 17rem;
|
|
11
|
+
display: flex;
|
|
12
|
+
flex-direction: column;
|
|
13
|
+
gap: var(--xd-space-xs, 0.25rem);
|
|
14
|
+
min-width: 14rem;
|
|
15
|
+
color: var(--xd-color-text, #0f172a);
|
|
16
|
+
}
|
|
17
|
+
.xd-select > div:has(> .xd-select-trigger) {
|
|
18
|
+
position: relative;
|
|
19
|
+
}
|
|
20
|
+
.xd-select-label {
|
|
21
|
+
font-size: var(--xd-font-size-sm, 0.8125rem);
|
|
22
|
+
font-weight: var(--xd-font-weight-semibold, 600);
|
|
23
|
+
line-height: var(--xd-line-height-normal, 1.25);
|
|
24
|
+
color: var(--xd-color-text, #0f172a);
|
|
25
|
+
}
|
|
26
|
+
.xd-select-trigger {
|
|
27
|
+
display: flex;
|
|
28
|
+
align-items: center;
|
|
29
|
+
flex-wrap: wrap;
|
|
30
|
+
gap: var(--xd-space-xs, 0.25rem);
|
|
31
|
+
min-height: 2.25rem;
|
|
32
|
+
border: var(--xd-border-width-thin, 1px) solid var(--xd-color-border-strong, #6b7688);
|
|
33
|
+
border-radius: var(--xd-radius-md, 0.375rem);
|
|
34
|
+
padding: var(--xd-space-sm, 0.375rem) var(--xd-space-md, 0.5rem);
|
|
35
|
+
font-family: inherit;
|
|
36
|
+
font-size: var(--xd-font-size-md, 0.875rem);
|
|
37
|
+
line-height: var(--xd-line-height-normal, 1.25);
|
|
38
|
+
background-color: var(--xd-color-surface, #ffffff);
|
|
39
|
+
color: var(--xd-color-text, #0f172a);
|
|
40
|
+
cursor: pointer;
|
|
41
|
+
box-shadow: var(--xd-shadow-sm, 0 1px 2px rgb(15 23 42 / 8%));
|
|
42
|
+
transition:
|
|
43
|
+
border-color var(--xd-motion-fast, 120ms ease-out),
|
|
44
|
+
background-color var(--xd-motion-fast, 120ms ease-out),
|
|
45
|
+
box-shadow var(--xd-motion-fast, 120ms ease-out);
|
|
46
|
+
}
|
|
47
|
+
.xd-select-trigger:hover:not([data-disabled=true]) {
|
|
48
|
+
background-color: var(--xd-color-surface-hover, #f1f5f9);
|
|
49
|
+
}
|
|
50
|
+
.xd-select-trigger:focus-visible {
|
|
51
|
+
outline: var(--xd-focus-ring-width, 2px) solid var(--xd-color-focus, #0f766e);
|
|
52
|
+
outline-offset: var(--xd-focus-ring-offset, 2px);
|
|
53
|
+
}
|
|
54
|
+
.xd-select-trigger[data-open=true] {
|
|
55
|
+
border-color: var(--xd-color-focus, #0f766e);
|
|
56
|
+
background-color: var(--xd-color-surface-hover, #f1f5f9);
|
|
57
|
+
}
|
|
58
|
+
.xd-select-trigger::after {
|
|
59
|
+
content: "";
|
|
60
|
+
flex-shrink: 0;
|
|
61
|
+
margin-left: auto;
|
|
62
|
+
width: 0.45em;
|
|
63
|
+
height: 0.45em;
|
|
64
|
+
border-right: var(--xd-border-width-thin, 1px) solid currentColor;
|
|
65
|
+
border-bottom: var(--xd-border-width-thin, 1px) solid currentColor;
|
|
66
|
+
color: var(--xd-color-text-muted, #475569);
|
|
67
|
+
transform: translateY(-0.15em) rotate(45deg);
|
|
68
|
+
transition: transform var(--xd-motion-fast, 120ms ease-out), color var(--xd-motion-fast, 120ms ease-out);
|
|
69
|
+
}
|
|
70
|
+
.xd-select-trigger[data-open=true]::after {
|
|
71
|
+
color: var(--xd-color-text, #0f172a);
|
|
72
|
+
transform: translateY(0.1em) rotate(-135deg);
|
|
73
|
+
}
|
|
74
|
+
.xd-select-value {
|
|
75
|
+
overflow: hidden;
|
|
76
|
+
text-overflow: ellipsis;
|
|
77
|
+
white-space: nowrap;
|
|
78
|
+
min-width: 0;
|
|
79
|
+
}
|
|
80
|
+
.xd-select-value[data-placeholder=true] {
|
|
81
|
+
color: var(--xd-color-text-muted, #475569);
|
|
82
|
+
}
|
|
83
|
+
.xd-select-trigger[data-invalid=true] {
|
|
84
|
+
border-color: var(--xd-color-destructive, #dc2626);
|
|
85
|
+
}
|
|
86
|
+
.xd-select-trigger[data-invalid=true]:focus-visible {
|
|
87
|
+
outline-color: var(--xd-color-destructive, #dc2626);
|
|
88
|
+
}
|
|
89
|
+
.xd-select-error {
|
|
90
|
+
font-size: var(--xd-font-size-sm, 0.8125rem);
|
|
91
|
+
line-height: var(--xd-line-height-normal, 1.25);
|
|
92
|
+
color: var(--xd-color-destructive, #dc2626);
|
|
93
|
+
}
|
|
94
|
+
.xd-select-trigger[data-disabled=true] {
|
|
95
|
+
opacity: var(--xd-opacity-disabled, 0.5);
|
|
96
|
+
cursor: not-allowed;
|
|
97
|
+
box-shadow: none;
|
|
98
|
+
}
|
|
99
|
+
.xd-select-chips {
|
|
100
|
+
display: inline-flex;
|
|
101
|
+
flex-wrap: wrap;
|
|
102
|
+
gap: var(--xd-space-xs, 0.25rem);
|
|
103
|
+
min-width: 0;
|
|
104
|
+
}
|
|
105
|
+
.xd-select-chip {
|
|
106
|
+
display: inline-flex;
|
|
107
|
+
align-items: center;
|
|
108
|
+
gap: var(--xd-space-xs, 0.25rem);
|
|
109
|
+
border: var(--xd-border-width-thin, 1px) solid var(--xd-color-border, #cbd5e1);
|
|
110
|
+
border-radius: var(--xd-radius-sm, 0.25rem);
|
|
111
|
+
padding: 0.125rem var(--xd-space-xs, 0.25rem) 0.125rem var(--xd-space-sm, 0.375rem);
|
|
112
|
+
background-color: var(--xd-color-surface-hover, #f1f5f9);
|
|
113
|
+
color: var(--xd-color-text, #0f172a);
|
|
114
|
+
font-size: var(--xd-font-size-sm, 0.8125rem);
|
|
115
|
+
line-height: var(--xd-line-height-normal, 1.25);
|
|
116
|
+
white-space: nowrap;
|
|
117
|
+
}
|
|
118
|
+
.xd-select-chip-remove {
|
|
119
|
+
display: inline-flex;
|
|
120
|
+
align-items: center;
|
|
121
|
+
justify-content: center;
|
|
122
|
+
border: none;
|
|
123
|
+
border-radius: var(--xd-radius-sm, 0.25rem);
|
|
124
|
+
width: 1rem;
|
|
125
|
+
height: 1rem;
|
|
126
|
+
padding: 0;
|
|
127
|
+
background: transparent;
|
|
128
|
+
color: var(--xd-color-text-muted, #475569);
|
|
129
|
+
font-size: 0.875rem;
|
|
130
|
+
line-height: 1;
|
|
131
|
+
cursor: pointer;
|
|
132
|
+
transition: background-color var(--xd-motion-fast, 120ms ease-out), color var(--xd-motion-fast, 120ms ease-out);
|
|
133
|
+
}
|
|
134
|
+
.xd-select-chip-remove:hover {
|
|
135
|
+
background-color: var(--xd-color-destructive, #dc2626);
|
|
136
|
+
color: var(--xd-on-destructive, #ffffff);
|
|
137
|
+
}
|
|
138
|
+
.xd-select-chip-remove:focus-visible {
|
|
139
|
+
outline: var(--xd-focus-ring-width, 2px) solid var(--xd-color-focus, #0f766e);
|
|
140
|
+
outline-offset: 1px;
|
|
141
|
+
}
|
|
142
|
+
.xd-select-trigger + [role=listbox] {
|
|
143
|
+
position: absolute;
|
|
144
|
+
top: calc(100% + var(--xd-select-gap));
|
|
145
|
+
left: 0;
|
|
146
|
+
right: 0;
|
|
147
|
+
z-index: 50;
|
|
148
|
+
max-height: var(--xd-select-max-h);
|
|
149
|
+
overflow-y: auto;
|
|
150
|
+
overflow-x: hidden;
|
|
151
|
+
overscroll-behavior: contain;
|
|
152
|
+
border: var(--xd-border-width-thin, 1px) solid var(--xd-color-border, #cbd5e1);
|
|
153
|
+
border-radius: var(--xd-radius-md, 0.375rem);
|
|
154
|
+
padding: var(--xd-space-xs, 0.25rem);
|
|
155
|
+
background-color: var(--xd-color-surface, #ffffff);
|
|
156
|
+
box-shadow: var(--xd-shadow-md, 0 4px 12px rgb(15 23 42 / 12%));
|
|
157
|
+
scrollbar-width: thin;
|
|
158
|
+
scrollbar-color: var(--xd-color-border-strong, #6b7688) transparent;
|
|
159
|
+
transform-origin: top center;
|
|
160
|
+
animation: xd-select-open var(--xd-motion-fast, 120ms ease-out);
|
|
161
|
+
}
|
|
162
|
+
@keyframes xd-select-open {
|
|
163
|
+
from {
|
|
164
|
+
opacity: 0;
|
|
165
|
+
transform: translateY(-4px) scale(0.98);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
.xd-select-trigger + [role=listbox] > div:first-child {
|
|
169
|
+
position: sticky;
|
|
170
|
+
top: calc(var(--xd-space-xs, 0.25rem) * -1);
|
|
171
|
+
z-index: 1;
|
|
172
|
+
margin: calc(var(--xd-space-xs, 0.25rem) * -1) calc(var(--xd-space-xs, 0.25rem) * -1) var(--xd-space-xs, 0.25rem);
|
|
173
|
+
padding: var(--xd-space-xs, 0.25rem);
|
|
174
|
+
background-color: var(--xd-color-surface, #ffffff);
|
|
175
|
+
}
|
|
176
|
+
.xd-select-trigger + [role=listbox] input {
|
|
177
|
+
width: 100%;
|
|
178
|
+
border: var(--xd-border-width-thin, 1px) solid var(--xd-color-border, #cbd5e1);
|
|
179
|
+
border-radius: var(--xd-radius-sm, 0.25rem);
|
|
180
|
+
padding: var(--xd-space-sm, 0.375rem) var(--xd-space-md, 0.5rem);
|
|
181
|
+
font-family: inherit;
|
|
182
|
+
font-size: var(--xd-font-size-md, 0.875rem);
|
|
183
|
+
line-height: var(--xd-line-height-normal, 1.25);
|
|
184
|
+
background-color: var(--xd-color-surface-hover, #f1f5f9);
|
|
185
|
+
color: var(--xd-color-text, #0f172a);
|
|
186
|
+
transition: border-color var(--xd-motion-fast, 120ms ease-out);
|
|
187
|
+
}
|
|
188
|
+
.xd-select-trigger + [role=listbox] input::placeholder {
|
|
189
|
+
color: var(--xd-color-text-muted, #475569);
|
|
190
|
+
}
|
|
191
|
+
.xd-select-trigger + [role=listbox] input:focus-visible {
|
|
192
|
+
outline: var(--xd-focus-ring-width, 2px) solid var(--xd-color-focus, #0f766e);
|
|
193
|
+
outline-offset: -1px;
|
|
194
|
+
border-color: transparent;
|
|
195
|
+
}
|
|
196
|
+
.xd-select-trigger + [role=listbox] [role=option] {
|
|
197
|
+
display: flex;
|
|
198
|
+
align-items: center;
|
|
199
|
+
gap: var(--xd-space-xs, 0.25rem);
|
|
200
|
+
width: 100%;
|
|
201
|
+
border: none;
|
|
202
|
+
border-radius: var(--xd-radius-sm, 0.25rem);
|
|
203
|
+
padding: var(--xd-space-sm, 0.375rem) var(--xd-space-md, 0.5rem);
|
|
204
|
+
text-align: left;
|
|
205
|
+
font-family: inherit;
|
|
206
|
+
font-size: var(--xd-font-size-md, 0.875rem);
|
|
207
|
+
font-weight: var(--xd-font-weight-regular, 400);
|
|
208
|
+
line-height: var(--xd-line-height-normal, 1.25);
|
|
209
|
+
background-color: transparent;
|
|
210
|
+
color: var(--xd-color-text, #0f172a);
|
|
211
|
+
cursor: pointer;
|
|
212
|
+
scroll-margin: var(--xd-space-xs, 0.25rem);
|
|
213
|
+
transition: background-color var(--xd-motion-fast, 120ms ease-out), color var(--xd-motion-fast, 120ms ease-out);
|
|
214
|
+
}
|
|
215
|
+
.xd-select-trigger + [role=listbox] [role=option]:hover:not([data-disabled=true]),
|
|
216
|
+
.xd-select-trigger + [role=listbox] [role=option]:focus-visible:not([data-disabled=true]) {
|
|
217
|
+
background-color: var(--xd-color-surface-hover, #f1f5f9);
|
|
218
|
+
}
|
|
219
|
+
.xd-select-trigger + [role=listbox] [role=option]:focus-visible {
|
|
220
|
+
outline: var(--xd-focus-ring-width, 2px) solid var(--xd-color-focus, #0f766e);
|
|
221
|
+
outline-offset: -2px;
|
|
222
|
+
}
|
|
223
|
+
.xd-select-trigger + [role=listbox] [role=option][data-selected=true] {
|
|
224
|
+
color: var(--xd-color-focus, #0f766e);
|
|
225
|
+
font-weight: var(--xd-font-weight-semibold, 600);
|
|
226
|
+
}
|
|
227
|
+
.xd-select-trigger + [role=listbox] [role=option]::before {
|
|
228
|
+
content: "";
|
|
229
|
+
flex-shrink: 0;
|
|
230
|
+
width: 0.35em;
|
|
231
|
+
height: 0.65em;
|
|
232
|
+
margin: 0 0.2em 0.15em 0.1em;
|
|
233
|
+
border-right: 2px solid currentColor;
|
|
234
|
+
border-bottom: 2px solid currentColor;
|
|
235
|
+
transform: rotate(45deg);
|
|
236
|
+
opacity: 0;
|
|
237
|
+
transition: opacity var(--xd-motion-fast, 120ms ease-out);
|
|
238
|
+
}
|
|
239
|
+
.xd-select-trigger + [role=listbox] [role=option][data-selected=true]::before {
|
|
240
|
+
opacity: 1;
|
|
241
|
+
}
|
|
242
|
+
.xd-select-trigger + [role=listbox] [role=option][data-disabled=true] {
|
|
243
|
+
opacity: var(--xd-opacity-disabled, 0.5);
|
|
244
|
+
cursor: not-allowed;
|
|
245
|
+
}
|
|
246
|
+
@media (prefers-reduced-motion: reduce) {
|
|
247
|
+
.xd-select-trigger,
|
|
248
|
+
.xd-select-trigger::after,
|
|
249
|
+
.xd-select-chip-remove,
|
|
250
|
+
.xd-select-trigger + [role=listbox] input,
|
|
251
|
+
.xd-select-trigger + [role=listbox] [role=option],
|
|
252
|
+
.xd-select-trigger + [role=listbox] [role=option]::before {
|
|
253
|
+
transition-duration: 1ms;
|
|
254
|
+
}
|
|
255
|
+
.xd-select-trigger + [role=listbox] {
|
|
256
|
+
animation: none;
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
/*# sourceMappingURL=index.css.map */
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/components/Select.css"],"sourcesContent":["/*\n * asnewyla/select -- Select.css\n *\n * DOM produced by Select.tsx + UnstyledSelect.tsx:\n *\n * div.xd-select Select.tsx wrapper\n * |- label.xd-select-label\n * |- div UnstyledSelect containerRef,\n * | | unclassed -- reached via :has()\n * | |- div.xd-select-trigger role=\"combobox\"\n * | | data-open / data-invalid / data-disabled\n * | | holds span.xd-select-value, or\n * | | span.xd-select-chips when multiple\n * | '- div[role=\"listbox\"] present only while open\n * | |- div > input search field\n * | '- button[role=\"option\"] data-selected / data-disabled\n * '- div.xd-select-error\n *\n * Options take REAL focus (useRovingFocus calls .focus()), so\n * :focus-visible is the keyboard indicator -- there is no data-active\n * attribute to style. :hover and :focus-visible therefore share one fill.\n *\n * ─── Changes in this revision ───\n *\n * 1. The previous version referenced five tokens that do not exist:\n * --xd-color-surface, --xd-color-surface-hover, --xd-shadow-md,\n * --xd-color-focus and --xd-color-text. Every one silently fell back\n * to its hardcoded literal, so the component ignored the active theme\n * entirely and the popover rendered WHITE in dark mode. Those tokens\n * now exist in tokens.css; this file reads them.\n *\n * 2. The popover was in normal flow with a margin-top, so opening the\n * Select pushed everything below it down the page. It is now\n * absolutely positioned against the containerRef.\n *\n * 3. A selected option was filled with --xd-color-primary while focused\n * options also draw a ring, so the selected row swallowed the focus\n * indicator. Selected is now carried by colour plus a check mark;\n * the fill is reserved for hover/focus, which is the transient state\n * the user needs to track.\n *\n * 4. The listbox had no max-height, so a long option list grew without\n * bound. Now caps and scrolls, with a sticky search field.\n *\n * 5. Interactive edges use --xd-color-border-strong. --xd-color-border\n * is ~1.3:1 against its surface: correct for the popover's own outline\n * (decorative), wrong for a control the user has to find (WCAG 1.4.11).\n *\n * 6. The single-select value and the empty-multi-select placeholder are\n * now real elements (span.xd-select-value, from Select.tsx's own\n * renderValue), not a bare text node -- so both can be dimmed and\n * truncated instead of only the (already-wrapped) chips case.\n *\n * Fallbacks below mirror tokens.css's default light palette, per the\n * soft-dependency model -- a consumer importing no token file still gets\n * a working component.\n *\n * Not fixable here, needs measurement in UnstyledSelect.tsx: the popover\n * never flips upward near the viewport bottom.\n */\n\n/* This package has no global reset to rely on, and `width: 100%` +\n padding/border below (the search input, options) needs border-box to\n not overflow its container — content-box (the browser default) adds\n padding/border on top of that 100%, which is exactly what caused the\n horizontal scrollbar on the popup. Scoped to this component's own\n subtree rather than a real global reset, since nothing here should\n reach outside `.xd-select`. */\n.xd-select,\n.xd-select *,\n.xd-select *::before,\n.xd-select *::after {\n box-sizing: border-box;\n}\n\n.xd-select {\n /* Local, component-scoped values. Not tokens: nothing else in the\n library needs a popover offset or a listbox height cap. */\n --xd-select-gap: var(--xd-space-xs, 0.25rem);\n --xd-select-max-h: 17rem;\n\n display: flex;\n flex-direction: column;\n gap: var(--xd-space-xs, 0.25rem);\n /* Without this, a short label/placeholder (\"Fruit\" / \"Choose a fruit\")\n lets the whole field shrink to fit that text, reading as an\n undersized control rather than a normal-width form field. Still just\n a floor — a longer value or a wider parent can grow it past this. */\n min-width: 14rem;\n color: var(--xd-color-text, #0f172a);\n}\n\n/* UnstyledSelect's container has no class of its own; it is the\n positioning context for the popover. :has() is Baseline since 2023.\n To avoid depending on it, add className=\"xd-select-field\" to the\n containerRef div in UnstyledSelect.tsx and swap this one selector. */\n.xd-select > div:has(> .xd-select-trigger) {\n position: relative;\n}\n\n/* --- Label --- */\n\n.xd-select-label {\n font-size: var(--xd-font-size-sm, 0.8125rem);\n font-weight: var(--xd-font-weight-semibold, 600);\n line-height: var(--xd-line-height-normal, 1.25);\n color: var(--xd-color-text, #0f172a);\n}\n\n/* --- Trigger --- */\n\n.xd-select-trigger {\n display: flex;\n align-items: center;\n flex-wrap: wrap;\n gap: var(--xd-space-xs, 0.25rem);\n min-height: 2.25rem;\n\n border: var(--xd-border-width-thin, 1px) solid\n var(--xd-color-border-strong, #6b7688);\n border-radius: var(--xd-radius-md, 0.375rem);\n padding: var(--xd-space-sm, 0.375rem) var(--xd-space-md, 0.5rem);\n\n font-family: inherit;\n font-size: var(--xd-font-size-md, 0.875rem);\n line-height: var(--xd-line-height-normal, 1.25);\n\n background-color: var(--xd-color-surface, #ffffff);\n color: var(--xd-color-text, #0f172a);\n cursor: pointer;\n box-shadow: var(--xd-shadow-sm, 0 1px 2px rgb(15 23 42 / 8%));\n\n transition:\n border-color var(--xd-motion-fast, 120ms ease-out),\n background-color var(--xd-motion-fast, 120ms ease-out),\n box-shadow var(--xd-motion-fast, 120ms ease-out);\n}\n\n.xd-select-trigger:hover:not([data-disabled=\"true\"]) {\n background-color: var(--xd-color-surface-hover, #f1f5f9);\n}\n\n.xd-select-trigger:focus-visible {\n outline: var(--xd-focus-ring-width, 2px) solid var(--xd-color-focus, #0f766e);\n outline-offset: var(--xd-focus-ring-offset, 2px);\n}\n\n/* Open: hold the focus colour on the border so the trigger reads as\n connected to the popover, which is a visually separate box. */\n.xd-select-trigger[data-open=\"true\"] {\n border-color: var(--xd-color-focus, #0f766e);\n background-color: var(--xd-color-surface-hover, #f1f5f9);\n}\n\n/* --- Chevron --- */\n\n.xd-select-trigger::after {\n content: \"\";\n flex-shrink: 0;\n margin-left: auto;\n width: 0.45em;\n height: 0.45em;\n border-right: var(--xd-border-width-thin, 1px) solid currentColor;\n border-bottom: var(--xd-border-width-thin, 1px) solid currentColor;\n color: var(--xd-color-text-muted, #475569);\n transform: translateY(-0.15em) rotate(45deg);\n transition:\n transform var(--xd-motion-fast, 120ms ease-out),\n color var(--xd-motion-fast, 120ms ease-out);\n}\n\n.xd-select-trigger[data-open=\"true\"]::after {\n color: var(--xd-color-text, #0f172a);\n transform: translateY(0.1em) rotate(-135deg);\n}\n\n/* --- Value / placeholder --- */\n\n.xd-select-value {\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n min-width: 0;\n}\n\n.xd-select-value[data-placeholder=\"true\"] {\n color: var(--xd-color-text-muted, #475569);\n}\n\n/* --- Invalid --- */\n\n.xd-select-trigger[data-invalid=\"true\"] {\n border-color: var(--xd-color-destructive, #dc2626);\n}\n\n.xd-select-trigger[data-invalid=\"true\"]:focus-visible {\n outline-color: var(--xd-color-destructive, #dc2626);\n}\n\n.xd-select-error {\n font-size: var(--xd-font-size-sm, 0.8125rem);\n line-height: var(--xd-line-height-normal, 1.25);\n color: var(--xd-color-destructive, #dc2626);\n}\n\n/* --- Disabled --- */\n\n.xd-select-trigger[data-disabled=\"true\"] {\n opacity: var(--xd-opacity-disabled, 0.5);\n cursor: not-allowed;\n box-shadow: none;\n}\n\n/* --- Multi-select chips ---\n Neutral fill rather than --xd-color-primary: a row of six saturated\n chips overwhelms the trigger in every theme, and a chip is a value\n already chosen, not a call to action. The remove button carries the\n destructive colour on hover, where the warning is actually useful. */\n\n.xd-select-chips {\n display: inline-flex;\n flex-wrap: wrap;\n gap: var(--xd-space-xs, 0.25rem);\n min-width: 0;\n}\n\n.xd-select-chip {\n display: inline-flex;\n align-items: center;\n gap: var(--xd-space-xs, 0.25rem);\n\n border: var(--xd-border-width-thin, 1px) solid var(--xd-color-border, #cbd5e1);\n border-radius: var(--xd-radius-sm, 0.25rem);\n padding: 0.125rem var(--xd-space-xs, 0.25rem) 0.125rem\n var(--xd-space-sm, 0.375rem);\n\n background-color: var(--xd-color-surface-hover, #f1f5f9);\n color: var(--xd-color-text, #0f172a);\n font-size: var(--xd-font-size-sm, 0.8125rem);\n line-height: var(--xd-line-height-normal, 1.25);\n white-space: nowrap;\n}\n\n.xd-select-chip-remove {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n border: none;\n border-radius: var(--xd-radius-sm, 0.25rem);\n width: 1rem;\n height: 1rem;\n padding: 0;\n\n background: transparent;\n color: var(--xd-color-text-muted, #475569);\n font-size: 0.875rem;\n line-height: 1;\n cursor: pointer;\n\n transition:\n background-color var(--xd-motion-fast, 120ms ease-out),\n color var(--xd-motion-fast, 120ms ease-out);\n}\n\n.xd-select-chip-remove:hover {\n background-color: var(--xd-color-destructive, #dc2626);\n color: var(--xd-on-destructive, #ffffff);\n}\n\n.xd-select-chip-remove:focus-visible {\n outline: var(--xd-focus-ring-width, 2px) solid var(--xd-color-focus, #0f766e);\n outline-offset: 1px;\n}\n\n/* --- Popup (rendered by UnstyledSelect as the trigger's next sibling) ---\n Absolutely positioned so opening does not reflow the page.\n Border AND shadow, both always: themes set --xd-shadow-md to none in\n dark mode, so the border is what keeps the popover legible there. */\n\n.xd-select-trigger + [role=\"listbox\"] {\n position: absolute;\n top: calc(100% + var(--xd-select-gap));\n left: 0;\n right: 0;\n z-index: 50;\n\n max-height: var(--xd-select-max-h);\n overflow-y: auto;\n /* Explicit, not just \"not set\": a non-visible overflow-y with overflow-x\n left at its visible default computes overflow-x to auto too (CSS\n Overflow spec) — so any future horizontal overflow in here would\n silently reappear as a scrollbar instead of just being invisible. */\n overflow-x: hidden;\n overscroll-behavior: contain;\n\n border: var(--xd-border-width-thin, 1px) solid var(--xd-color-border, #cbd5e1);\n border-radius: var(--xd-radius-md, 0.375rem);\n padding: var(--xd-space-xs, 0.25rem);\n background-color: var(--xd-color-surface, #ffffff);\n box-shadow: var(--xd-shadow-md, 0 4px 12px rgb(15 23 42 / 12%));\n\n scrollbar-width: thin;\n scrollbar-color: var(--xd-color-border-strong, #6b7688) transparent;\n\n transform-origin: top center;\n animation: xd-select-open var(--xd-motion-fast, 120ms ease-out);\n}\n\n@keyframes xd-select-open {\n from {\n opacity: 0;\n transform: translateY(-4px) scale(0.98);\n }\n}\n\n/* Search field, kept sticky so it stays reachable once the list scrolls.\n Negative margins let its background bleed to the popover's padding\n edge, so options do not appear to slide out from under it. */\n\n.xd-select-trigger + [role=\"listbox\"] > div:first-child {\n position: sticky;\n top: calc(var(--xd-space-xs, 0.25rem) * -1);\n z-index: 1;\n margin: calc(var(--xd-space-xs, 0.25rem) * -1)\n calc(var(--xd-space-xs, 0.25rem) * -1) var(--xd-space-xs, 0.25rem);\n padding: var(--xd-space-xs, 0.25rem);\n background-color: var(--xd-color-surface, #ffffff);\n}\n\n.xd-select-trigger + [role=\"listbox\"] input {\n width: 100%;\n border: var(--xd-border-width-thin, 1px) solid var(--xd-color-border, #cbd5e1);\n border-radius: var(--xd-radius-sm, 0.25rem);\n padding: var(--xd-space-sm, 0.375rem) var(--xd-space-md, 0.5rem);\n\n font-family: inherit;\n font-size: var(--xd-font-size-md, 0.875rem);\n line-height: var(--xd-line-height-normal, 1.25);\n\n background-color: var(--xd-color-surface-hover, #f1f5f9);\n color: var(--xd-color-text, #0f172a);\n\n transition: border-color var(--xd-motion-fast, 120ms ease-out);\n}\n\n.xd-select-trigger + [role=\"listbox\"] input::placeholder {\n color: var(--xd-color-text-muted, #475569);\n}\n\n.xd-select-trigger + [role=\"listbox\"] input:focus-visible {\n outline: var(--xd-focus-ring-width, 2px) solid var(--xd-color-focus, #0f766e);\n outline-offset: -1px;\n border-color: transparent;\n}\n\n/* --- Options ---\n :hover and :focus-visible share one fill. Two different highlights in\n an open listbox is a real usability bug: the user cannot tell which\n row Enter will commit. */\n\n.xd-select-trigger + [role=\"listbox\"] [role=\"option\"] {\n display: flex;\n align-items: center;\n gap: var(--xd-space-xs, 0.25rem);\n width: 100%;\n\n border: none;\n border-radius: var(--xd-radius-sm, 0.25rem);\n padding: var(--xd-space-sm, 0.375rem) var(--xd-space-md, 0.5rem);\n\n text-align: left;\n font-family: inherit;\n font-size: var(--xd-font-size-md, 0.875rem);\n font-weight: var(--xd-font-weight-regular, 400);\n line-height: var(--xd-line-height-normal, 1.25);\n\n background-color: transparent;\n color: var(--xd-color-text, #0f172a);\n cursor: pointer;\n scroll-margin: var(--xd-space-xs, 0.25rem);\n\n transition:\n background-color var(--xd-motion-fast, 120ms ease-out),\n color var(--xd-motion-fast, 120ms ease-out);\n}\n\n.xd-select-trigger\n + [role=\"listbox\"]\n [role=\"option\"]:hover:not([data-disabled=\"true\"]),\n.xd-select-trigger\n + [role=\"listbox\"]\n [role=\"option\"]:focus-visible:not([data-disabled=\"true\"]) {\n background-color: var(--xd-color-surface-hover, #f1f5f9);\n}\n\n.xd-select-trigger + [role=\"listbox\"] [role=\"option\"]:focus-visible {\n outline: var(--xd-focus-ring-width, 2px) solid var(--xd-color-focus, #0f766e);\n outline-offset: -2px;\n}\n\n/* Selected: colour and a check, never a fill -- a filled selected row\n plus a filled focused row makes the focused row unidentifiable. */\n.xd-select-trigger + [role=\"listbox\"] [role=\"option\"][data-selected=\"true\"] {\n color: var(--xd-color-focus, #0f766e);\n font-weight: var(--xd-font-weight-semibold, 600);\n}\n\n/* Check mark, drawn from two borders. The pseudo-element occupies its\n width on every row so labels stay aligned whether selected or not. */\n.xd-select-trigger + [role=\"listbox\"] [role=\"option\"]::before {\n content: \"\";\n flex-shrink: 0;\n width: 0.35em;\n height: 0.65em;\n margin: 0 0.2em 0.15em 0.1em;\n border-right: 2px solid currentColor;\n border-bottom: 2px solid currentColor;\n transform: rotate(45deg);\n opacity: 0;\n transition: opacity var(--xd-motion-fast, 120ms ease-out);\n}\n\n.xd-select-trigger\n + [role=\"listbox\"]\n [role=\"option\"][data-selected=\"true\"]::before {\n opacity: 1;\n}\n\n.xd-select-trigger + [role=\"listbox\"] [role=\"option\"][data-disabled=\"true\"] {\n opacity: var(--xd-opacity-disabled, 0.5);\n cursor: not-allowed;\n}\n\n/* --- Motion preference --- */\n\n@media (prefers-reduced-motion: reduce) {\n .xd-select-trigger,\n .xd-select-trigger::after,\n .xd-select-chip-remove,\n .xd-select-trigger + [role=\"listbox\"] input,\n .xd-select-trigger + [role=\"listbox\"] [role=\"option\"],\n .xd-select-trigger + [role=\"listbox\"] [role=\"option\"]::before {\n transition-duration: 1ms;\n }\n\n .xd-select-trigger + [role=\"listbox\"] {\n animation: none;\n }\n}\n"],"mappings":"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","names":[]}
|
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import * as react from 'react';
|
|
2
|
+
import { UnstyledSelectProps } from '@asnewyla/unstyled-select';
|
|
3
|
+
|
|
4
|
+
type DistributiveOmit<T, K extends PropertyKey> = T extends unknown ? Omit<T, K> : never;
|
|
5
|
+
type SelectProps = DistributiveOmit<UnstyledSelectProps, 'invalid' | 'renderValue'> & {
|
|
6
|
+
label: string;
|
|
7
|
+
error?: string;
|
|
8
|
+
};
|
|
9
|
+
declare const Select: react.ForwardRefExoticComponent<SelectProps & react.RefAttributes<HTMLDivElement>>;
|
|
10
|
+
|
|
11
|
+
export { Select, type SelectProps };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import * as react from 'react';
|
|
2
|
+
import { UnstyledSelectProps } from '@asnewyla/unstyled-select';
|
|
3
|
+
|
|
4
|
+
type DistributiveOmit<T, K extends PropertyKey> = T extends unknown ? Omit<T, K> : never;
|
|
5
|
+
type SelectProps = DistributiveOmit<UnstyledSelectProps, 'invalid' | 'renderValue'> & {
|
|
6
|
+
label: string;
|
|
7
|
+
error?: string;
|
|
8
|
+
};
|
|
9
|
+
declare const Select: react.ForwardRefExoticComponent<SelectProps & react.RefAttributes<HTMLDivElement>>;
|
|
10
|
+
|
|
11
|
+
export { Select, type SelectProps };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var unstyledSelect = require('@asnewyla/unstyled-select');
|
|
4
|
+
var react = require('react');
|
|
5
|
+
var jsxRuntime = require('react/jsx-runtime');
|
|
6
|
+
|
|
7
|
+
// src/components/Select.tsx
|
|
8
|
+
function SelectInner({ label, id, error, disabled, className, placeholder, ...rest }, ref) {
|
|
9
|
+
const generatedId = react.useId();
|
|
10
|
+
const selectId = id ?? generatedId;
|
|
11
|
+
const errorId = `${selectId}-error`;
|
|
12
|
+
const labelId = `${selectId}-label`;
|
|
13
|
+
const renderValue = (selectedOptions, { removeOption }) => {
|
|
14
|
+
if (rest.multiple) {
|
|
15
|
+
return selectedOptions.length > 0 ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: "xd-select-chips", children: selectedOptions.map((option) => /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "xd-select-chip", children: [
|
|
16
|
+
option.label,
|
|
17
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
18
|
+
"button",
|
|
19
|
+
{
|
|
20
|
+
type: "button",
|
|
21
|
+
className: "xd-select-chip-remove",
|
|
22
|
+
"aria-label": `Remove ${option.label}`,
|
|
23
|
+
onClick: (e) => {
|
|
24
|
+
e.stopPropagation();
|
|
25
|
+
removeOption(option);
|
|
26
|
+
},
|
|
27
|
+
children: "\xD7"
|
|
28
|
+
}
|
|
29
|
+
)
|
|
30
|
+
] }, option.value)) }) : /* @__PURE__ */ jsxRuntime.jsx("span", { className: "xd-select-value", "data-placeholder": "true", children: placeholder });
|
|
31
|
+
}
|
|
32
|
+
const selected = selectedOptions[0];
|
|
33
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
34
|
+
"span",
|
|
35
|
+
{
|
|
36
|
+
className: "xd-select-value",
|
|
37
|
+
"data-placeholder": selected ? void 0 : "true",
|
|
38
|
+
children: selected ? selected.label : placeholder
|
|
39
|
+
}
|
|
40
|
+
);
|
|
41
|
+
};
|
|
42
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "xd-select", children: [
|
|
43
|
+
/* @__PURE__ */ jsxRuntime.jsx("label", { className: "xd-select-label", id: labelId, htmlFor: selectId, children: label }),
|
|
44
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
45
|
+
unstyledSelect.UnstyledSelect,
|
|
46
|
+
{
|
|
47
|
+
id: selectId,
|
|
48
|
+
placeholder,
|
|
49
|
+
disabled,
|
|
50
|
+
...rest,
|
|
51
|
+
invalid: !!error,
|
|
52
|
+
renderValue,
|
|
53
|
+
"aria-labelledby": labelId,
|
|
54
|
+
"aria-describedby": error ? errorId : void 0,
|
|
55
|
+
className: className ? `xd-select-trigger ${className}` : "xd-select-trigger",
|
|
56
|
+
ref
|
|
57
|
+
}
|
|
58
|
+
),
|
|
59
|
+
error && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "xd-select-error", id: errorId, children: error })
|
|
60
|
+
] });
|
|
61
|
+
}
|
|
62
|
+
var Select = react.forwardRef(SelectInner);
|
|
63
|
+
|
|
64
|
+
exports.Select = Select;
|
|
65
|
+
//# sourceMappingURL=index.js.map
|
|
66
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/components/Select.tsx"],"names":["useId","jsx","jsxs","UnstyledSelect","forwardRef"],"mappings":";;;;;;;AA0BA,SAAS,WAAA,CACP,EAAE,KAAA,EAAO,EAAA,EAAI,KAAA,EAAO,QAAA,EAAU,SAAA,EAAW,WAAA,EAAa,GAAG,IAAA,EAAK,EAC9D,GAAA,EACA;AACA,EAAA,MAAM,cAAcA,WAAA,EAAM;AAC1B,EAAA,MAAM,WAAW,EAAA,IAAM,WAAA;AACvB,EAAA,MAAM,OAAA,GAAU,GAAG,QAAQ,CAAA,MAAA,CAAA;AAC3B,EAAA,MAAM,OAAA,GAAU,GAAG,QAAQ,CAAA,MAAA,CAAA;AAM3B,EAAA,MAAM,WAAA,GAAc,CAClB,eAAA,EACA,EAAE,cAAa,KACZ;AACH,IAAA,IAAI,KAAK,QAAA,EAAU;AACjB,MAAA,OAAO,eAAA,CAAgB,MAAA,GAAS,CAAA,mBAC9BC,cAAA,CAAC,UAAK,SAAA,EAAU,iBAAA,EACb,QAAA,EAAA,eAAA,CAAgB,GAAA,CAAI,CAAC,MAAA,qBACpBC,eAAA,CAAC,MAAA,EAAA,EAAwB,WAAU,gBAAA,EAChC,QAAA,EAAA;AAAA,QAAA,MAAA,CAAO,KAAA;AAAA,wBACRD,cAAA;AAAA,UAAC,QAAA;AAAA,UAAA;AAAA,YACC,IAAA,EAAK,QAAA;AAAA,YACL,SAAA,EAAU,uBAAA;AAAA,YACV,YAAA,EAAY,CAAA,OAAA,EAAU,MAAA,CAAO,KAAK,CAAA,CAAA;AAAA,YAClC,OAAA,EAAS,CAAC,CAAA,KAAM;AACd,cAAA,CAAA,CAAE,eAAA,EAAgB;AAClB,cAAA,YAAA,CAAa,MAAM,CAAA;AAAA,YACrB,CAAA;AAAA,YACD,QAAA,EAAA;AAAA;AAAA;AAED,OAAA,EAAA,EAZS,MAAA,CAAO,KAalB,CACD,CAAA,EACH,CAAA,mBAEAA,cAAA,CAAC,MAAA,EAAA,EAAK,SAAA,EAAU,iBAAA,EAAkB,kBAAA,EAAiB,MAAA,EAChD,QAAA,EAAA,WAAA,EACH,CAAA;AAAA,IAEJ;AAEA,IAAA,MAAM,QAAA,GAAW,gBAAgB,CAAC,CAAA;AAClC,IAAA,uBACEA,cAAA;AAAA,MAAC,MAAA;AAAA,MAAA;AAAA,QACC,SAAA,EAAU,iBAAA;AAAA,QACV,kBAAA,EAAkB,WAAW,MAAA,GAAY,MAAA;AAAA,QAExC,QAAA,EAAA,QAAA,GAAW,SAAS,KAAA,GAAQ;AAAA;AAAA,KAC/B;AAAA,EAEJ,CAAA;AAEA,EAAA,uBACEC,eAAA,CAAC,KAAA,EAAA,EAAI,SAAA,EAAU,WAAA,EAKb,QAAA,EAAA;AAAA,oBAAAD,cAAA,CAAC,WAAM,SAAA,EAAU,iBAAA,EAAkB,IAAI,OAAA,EAAS,OAAA,EAAS,UACtD,QAAA,EAAA,KAAA,EACH,CAAA;AAAA,oBACAA,cAAA;AAAA,MAACE,6BAAA;AAAA,MAAA;AAAA,QACC,EAAA,EAAI,QAAA;AAAA,QACJ,WAAA;AAAA,QACA,QAAA;AAAA,QACC,GAAG,IAAA;AAAA,QACJ,OAAA,EAAS,CAAC,CAAC,KAAA;AAAA,QACX,WAAA;AAAA,QACA,iBAAA,EAAiB,OAAA;AAAA,QACjB,kBAAA,EAAkB,QAAQ,OAAA,GAAU,MAAA;AAAA,QACpC,SAAA,EACE,SAAA,GAAY,CAAA,kBAAA,EAAqB,SAAS,CAAA,CAAA,GAAK,mBAAA;AAAA,QAEjD;AAAA;AAAA,KACF;AAAA,IACC,yBACCF,cAAA,CAAC,KAAA,EAAA,EAAI,WAAU,iBAAA,EAAkB,EAAA,EAAI,SAClC,QAAA,EAAA,KAAA,EACH;AAAA,GAAA,EAEJ,CAAA;AAEJ;AAEO,IAAM,MAAA,GAASG,iBAAwC,WAAW","file":"index.js","sourcesContent":["import {\n type SelectOption,\n UnstyledSelect,\n type UnstyledSelectProps,\n type UnstyledSelectRenderValueHelpers,\n} from '@asnewyla/unstyled-select';\nimport { type ForwardedRef, forwardRef, useId } from 'react';\nimport './Select.css';\n\n// `invalid`/`renderValue` live entirely on the common (non-discriminated)\n// part of `UnstyledSelectProps`, but `Omit` on a discriminated union still\n// risks collapsing it (see unstyled-select's own test-utils for the same\n// problem). `T` has to stay a naked type parameter for the conditional to\n// distribute over the union member-by-member instead of collapsing it.\ntype DistributiveOmit<T, K extends PropertyKey> = T extends unknown\n ? Omit<T, K>\n : never;\n\nexport type SelectProps = DistributiveOmit<\n UnstyledSelectProps,\n 'invalid' | 'renderValue'\n> & {\n label: string;\n error?: string;\n};\n\nfunction SelectInner(\n { label, id, error, disabled, className, placeholder, ...rest }: SelectProps,\n ref: ForwardedRef<HTMLDivElement>,\n) {\n const generatedId = useId();\n const selectId = id ?? generatedId;\n const errorId = `${selectId}-error`;\n const labelId = `${selectId}-label`;\n\n // Always supplied (not just for `multiple`) so the single-select value\n // and the empty-multi-select placeholder are real elements\n // (span.xd-select-value), not a bare text node — Select.css can then\n // dim/truncate them the same way it already does the chips.\n const renderValue = (\n selectedOptions: SelectOption[],\n { removeOption }: UnstyledSelectRenderValueHelpers,\n ) => {\n if (rest.multiple) {\n return selectedOptions.length > 0 ? (\n <span className=\"xd-select-chips\">\n {selectedOptions.map((option) => (\n <span key={option.value} className=\"xd-select-chip\">\n {option.label}\n <button\n type=\"button\"\n className=\"xd-select-chip-remove\"\n aria-label={`Remove ${option.label}`}\n onClick={(e) => {\n e.stopPropagation();\n removeOption(option);\n }}\n >\n ×\n </button>\n </span>\n ))}\n </span>\n ) : (\n <span className=\"xd-select-value\" data-placeholder=\"true\">\n {placeholder}\n </span>\n );\n }\n\n const selected = selectedOptions[0];\n return (\n <span\n className=\"xd-select-value\"\n data-placeholder={selected ? undefined : 'true'}\n >\n {selected ? selected.label : placeholder}\n </span>\n );\n };\n\n return (\n <div className=\"xd-select\">\n {/* `htmlFor` only auto-associates with labelable elements (input,\n button, select, ...) — the trigger is a plain `role=\"combobox\"` div, so\n it's kept here (harmless, satisfies the a11y/noLabelWithoutControl\n lint) but the real accessible-name link is `aria-labelledby` below. */}\n <label className=\"xd-select-label\" id={labelId} htmlFor={selectId}>\n {label}\n </label>\n <UnstyledSelect\n id={selectId}\n placeholder={placeholder}\n disabled={disabled}\n {...rest}\n invalid={!!error}\n renderValue={renderValue}\n aria-labelledby={labelId}\n aria-describedby={error ? errorId : undefined}\n className={\n className ? `xd-select-trigger ${className}` : 'xd-select-trigger'\n }\n ref={ref}\n />\n {error && (\n <div className=\"xd-select-error\" id={errorId}>\n {error}\n </div>\n )}\n </div>\n );\n}\n\nexport const Select = forwardRef<HTMLDivElement, SelectProps>(SelectInner);\n"]}
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { UnstyledSelect } from '@asnewyla/unstyled-select';
|
|
2
|
+
import { forwardRef, useId } from 'react';
|
|
3
|
+
import { jsxs, jsx } from 'react/jsx-runtime';
|
|
4
|
+
|
|
5
|
+
// src/components/Select.tsx
|
|
6
|
+
function SelectInner({ label, id, error, disabled, className, placeholder, ...rest }, ref) {
|
|
7
|
+
const generatedId = useId();
|
|
8
|
+
const selectId = id ?? generatedId;
|
|
9
|
+
const errorId = `${selectId}-error`;
|
|
10
|
+
const labelId = `${selectId}-label`;
|
|
11
|
+
const renderValue = (selectedOptions, { removeOption }) => {
|
|
12
|
+
if (rest.multiple) {
|
|
13
|
+
return selectedOptions.length > 0 ? /* @__PURE__ */ jsx("span", { className: "xd-select-chips", children: selectedOptions.map((option) => /* @__PURE__ */ jsxs("span", { className: "xd-select-chip", children: [
|
|
14
|
+
option.label,
|
|
15
|
+
/* @__PURE__ */ jsx(
|
|
16
|
+
"button",
|
|
17
|
+
{
|
|
18
|
+
type: "button",
|
|
19
|
+
className: "xd-select-chip-remove",
|
|
20
|
+
"aria-label": `Remove ${option.label}`,
|
|
21
|
+
onClick: (e) => {
|
|
22
|
+
e.stopPropagation();
|
|
23
|
+
removeOption(option);
|
|
24
|
+
},
|
|
25
|
+
children: "\xD7"
|
|
26
|
+
}
|
|
27
|
+
)
|
|
28
|
+
] }, option.value)) }) : /* @__PURE__ */ jsx("span", { className: "xd-select-value", "data-placeholder": "true", children: placeholder });
|
|
29
|
+
}
|
|
30
|
+
const selected = selectedOptions[0];
|
|
31
|
+
return /* @__PURE__ */ jsx(
|
|
32
|
+
"span",
|
|
33
|
+
{
|
|
34
|
+
className: "xd-select-value",
|
|
35
|
+
"data-placeholder": selected ? void 0 : "true",
|
|
36
|
+
children: selected ? selected.label : placeholder
|
|
37
|
+
}
|
|
38
|
+
);
|
|
39
|
+
};
|
|
40
|
+
return /* @__PURE__ */ jsxs("div", { className: "xd-select", children: [
|
|
41
|
+
/* @__PURE__ */ jsx("label", { className: "xd-select-label", id: labelId, htmlFor: selectId, children: label }),
|
|
42
|
+
/* @__PURE__ */ jsx(
|
|
43
|
+
UnstyledSelect,
|
|
44
|
+
{
|
|
45
|
+
id: selectId,
|
|
46
|
+
placeholder,
|
|
47
|
+
disabled,
|
|
48
|
+
...rest,
|
|
49
|
+
invalid: !!error,
|
|
50
|
+
renderValue,
|
|
51
|
+
"aria-labelledby": labelId,
|
|
52
|
+
"aria-describedby": error ? errorId : void 0,
|
|
53
|
+
className: className ? `xd-select-trigger ${className}` : "xd-select-trigger",
|
|
54
|
+
ref
|
|
55
|
+
}
|
|
56
|
+
),
|
|
57
|
+
error && /* @__PURE__ */ jsx("div", { className: "xd-select-error", id: errorId, children: error })
|
|
58
|
+
] });
|
|
59
|
+
}
|
|
60
|
+
var Select = forwardRef(SelectInner);
|
|
61
|
+
|
|
62
|
+
export { Select };
|
|
63
|
+
//# sourceMappingURL=index.mjs.map
|
|
64
|
+
//# sourceMappingURL=index.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/components/Select.tsx"],"names":[],"mappings":";;;;;AA0BA,SAAS,WAAA,CACP,EAAE,KAAA,EAAO,EAAA,EAAI,KAAA,EAAO,QAAA,EAAU,SAAA,EAAW,WAAA,EAAa,GAAG,IAAA,EAAK,EAC9D,GAAA,EACA;AACA,EAAA,MAAM,cAAc,KAAA,EAAM;AAC1B,EAAA,MAAM,WAAW,EAAA,IAAM,WAAA;AACvB,EAAA,MAAM,OAAA,GAAU,GAAG,QAAQ,CAAA,MAAA,CAAA;AAC3B,EAAA,MAAM,OAAA,GAAU,GAAG,QAAQ,CAAA,MAAA,CAAA;AAM3B,EAAA,MAAM,WAAA,GAAc,CAClB,eAAA,EACA,EAAE,cAAa,KACZ;AACH,IAAA,IAAI,KAAK,QAAA,EAAU;AACjB,MAAA,OAAO,eAAA,CAAgB,MAAA,GAAS,CAAA,mBAC9B,GAAA,CAAC,UAAK,SAAA,EAAU,iBAAA,EACb,QAAA,EAAA,eAAA,CAAgB,GAAA,CAAI,CAAC,MAAA,qBACpB,IAAA,CAAC,MAAA,EAAA,EAAwB,WAAU,gBAAA,EAChC,QAAA,EAAA;AAAA,QAAA,MAAA,CAAO,KAAA;AAAA,wBACR,GAAA;AAAA,UAAC,QAAA;AAAA,UAAA;AAAA,YACC,IAAA,EAAK,QAAA;AAAA,YACL,SAAA,EAAU,uBAAA;AAAA,YACV,YAAA,EAAY,CAAA,OAAA,EAAU,MAAA,CAAO,KAAK,CAAA,CAAA;AAAA,YAClC,OAAA,EAAS,CAAC,CAAA,KAAM;AACd,cAAA,CAAA,CAAE,eAAA,EAAgB;AAClB,cAAA,YAAA,CAAa,MAAM,CAAA;AAAA,YACrB,CAAA;AAAA,YACD,QAAA,EAAA;AAAA;AAAA;AAED,OAAA,EAAA,EAZS,MAAA,CAAO,KAalB,CACD,CAAA,EACH,CAAA,mBAEA,GAAA,CAAC,MAAA,EAAA,EAAK,SAAA,EAAU,iBAAA,EAAkB,kBAAA,EAAiB,MAAA,EAChD,QAAA,EAAA,WAAA,EACH,CAAA;AAAA,IAEJ;AAEA,IAAA,MAAM,QAAA,GAAW,gBAAgB,CAAC,CAAA;AAClC,IAAA,uBACE,GAAA;AAAA,MAAC,MAAA;AAAA,MAAA;AAAA,QACC,SAAA,EAAU,iBAAA;AAAA,QACV,kBAAA,EAAkB,WAAW,MAAA,GAAY,MAAA;AAAA,QAExC,QAAA,EAAA,QAAA,GAAW,SAAS,KAAA,GAAQ;AAAA;AAAA,KAC/B;AAAA,EAEJ,CAAA;AAEA,EAAA,uBACE,IAAA,CAAC,KAAA,EAAA,EAAI,SAAA,EAAU,WAAA,EAKb,QAAA,EAAA;AAAA,oBAAA,GAAA,CAAC,WAAM,SAAA,EAAU,iBAAA,EAAkB,IAAI,OAAA,EAAS,OAAA,EAAS,UACtD,QAAA,EAAA,KAAA,EACH,CAAA;AAAA,oBACA,GAAA;AAAA,MAAC,cAAA;AAAA,MAAA;AAAA,QACC,EAAA,EAAI,QAAA;AAAA,QACJ,WAAA;AAAA,QACA,QAAA;AAAA,QACC,GAAG,IAAA;AAAA,QACJ,OAAA,EAAS,CAAC,CAAC,KAAA;AAAA,QACX,WAAA;AAAA,QACA,iBAAA,EAAiB,OAAA;AAAA,QACjB,kBAAA,EAAkB,QAAQ,OAAA,GAAU,MAAA;AAAA,QACpC,SAAA,EACE,SAAA,GAAY,CAAA,kBAAA,EAAqB,SAAS,CAAA,CAAA,GAAK,mBAAA;AAAA,QAEjD;AAAA;AAAA,KACF;AAAA,IACC,yBACC,GAAA,CAAC,KAAA,EAAA,EAAI,WAAU,iBAAA,EAAkB,EAAA,EAAI,SAClC,QAAA,EAAA,KAAA,EACH;AAAA,GAAA,EAEJ,CAAA;AAEJ;AAEO,IAAM,MAAA,GAAS,WAAwC,WAAW","file":"index.mjs","sourcesContent":["import {\n type SelectOption,\n UnstyledSelect,\n type UnstyledSelectProps,\n type UnstyledSelectRenderValueHelpers,\n} from '@asnewyla/unstyled-select';\nimport { type ForwardedRef, forwardRef, useId } from 'react';\nimport './Select.css';\n\n// `invalid`/`renderValue` live entirely on the common (non-discriminated)\n// part of `UnstyledSelectProps`, but `Omit` on a discriminated union still\n// risks collapsing it (see unstyled-select's own test-utils for the same\n// problem). `T` has to stay a naked type parameter for the conditional to\n// distribute over the union member-by-member instead of collapsing it.\ntype DistributiveOmit<T, K extends PropertyKey> = T extends unknown\n ? Omit<T, K>\n : never;\n\nexport type SelectProps = DistributiveOmit<\n UnstyledSelectProps,\n 'invalid' | 'renderValue'\n> & {\n label: string;\n error?: string;\n};\n\nfunction SelectInner(\n { label, id, error, disabled, className, placeholder, ...rest }: SelectProps,\n ref: ForwardedRef<HTMLDivElement>,\n) {\n const generatedId = useId();\n const selectId = id ?? generatedId;\n const errorId = `${selectId}-error`;\n const labelId = `${selectId}-label`;\n\n // Always supplied (not just for `multiple`) so the single-select value\n // and the empty-multi-select placeholder are real elements\n // (span.xd-select-value), not a bare text node — Select.css can then\n // dim/truncate them the same way it already does the chips.\n const renderValue = (\n selectedOptions: SelectOption[],\n { removeOption }: UnstyledSelectRenderValueHelpers,\n ) => {\n if (rest.multiple) {\n return selectedOptions.length > 0 ? (\n <span className=\"xd-select-chips\">\n {selectedOptions.map((option) => (\n <span key={option.value} className=\"xd-select-chip\">\n {option.label}\n <button\n type=\"button\"\n className=\"xd-select-chip-remove\"\n aria-label={`Remove ${option.label}`}\n onClick={(e) => {\n e.stopPropagation();\n removeOption(option);\n }}\n >\n ×\n </button>\n </span>\n ))}\n </span>\n ) : (\n <span className=\"xd-select-value\" data-placeholder=\"true\">\n {placeholder}\n </span>\n );\n }\n\n const selected = selectedOptions[0];\n return (\n <span\n className=\"xd-select-value\"\n data-placeholder={selected ? undefined : 'true'}\n >\n {selected ? selected.label : placeholder}\n </span>\n );\n };\n\n return (\n <div className=\"xd-select\">\n {/* `htmlFor` only auto-associates with labelable elements (input,\n button, select, ...) — the trigger is a plain `role=\"combobox\"` div, so\n it's kept here (harmless, satisfies the a11y/noLabelWithoutControl\n lint) but the real accessible-name link is `aria-labelledby` below. */}\n <label className=\"xd-select-label\" id={labelId} htmlFor={selectId}>\n {label}\n </label>\n <UnstyledSelect\n id={selectId}\n placeholder={placeholder}\n disabled={disabled}\n {...rest}\n invalid={!!error}\n renderValue={renderValue}\n aria-labelledby={labelId}\n aria-describedby={error ? errorId : undefined}\n className={\n className ? `xd-select-trigger ${className}` : 'xd-select-trigger'\n }\n ref={ref}\n />\n {error && (\n <div className=\"xd-select-error\" id={errorId}>\n {error}\n </div>\n )}\n </div>\n );\n}\n\nexport const Select = forwardRef<HTMLDivElement, SelectProps>(SelectInner);\n"]}
|
package/package.json
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@asnewyla/select",
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"description": "Styled select/combobox with label, validation error display, and accessibility",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"repository": {
|
|
7
|
+
"type": "git",
|
|
8
|
+
"url": "git+https://github.com/xavierDelaFuente/xd-components.git",
|
|
9
|
+
"directory": "packages/select"
|
|
10
|
+
},
|
|
11
|
+
"publishConfig": {
|
|
12
|
+
"access": "public"
|
|
13
|
+
},
|
|
14
|
+
"main": "dist/index.js",
|
|
15
|
+
"module": "dist/index.mjs",
|
|
16
|
+
"types": "dist/index.d.ts",
|
|
17
|
+
"exports": {
|
|
18
|
+
".": {
|
|
19
|
+
"types": "./dist/index.d.ts",
|
|
20
|
+
"import": "./dist/index.mjs",
|
|
21
|
+
"require": "./dist/index.js"
|
|
22
|
+
},
|
|
23
|
+
"./styles.css": "./dist/index.css"
|
|
24
|
+
},
|
|
25
|
+
"files": [
|
|
26
|
+
"dist"
|
|
27
|
+
],
|
|
28
|
+
"sideEffects": [
|
|
29
|
+
"*.css"
|
|
30
|
+
],
|
|
31
|
+
"keywords": [
|
|
32
|
+
"react",
|
|
33
|
+
"select",
|
|
34
|
+
"combobox",
|
|
35
|
+
"form",
|
|
36
|
+
"styled",
|
|
37
|
+
"accessible"
|
|
38
|
+
],
|
|
39
|
+
"peerDependencies": {
|
|
40
|
+
"react": "^18.0.0 || ^19.0.0",
|
|
41
|
+
"react-dom": "^18.0.0 || ^19.0.0"
|
|
42
|
+
},
|
|
43
|
+
"dependencies": {
|
|
44
|
+
"@asnewyla/unstyled-select": "0.2.0"
|
|
45
|
+
},
|
|
46
|
+
"devDependencies": {
|
|
47
|
+
"@testing-library/jest-dom": "^6.4.0",
|
|
48
|
+
"@testing-library/react": "^16.0.0",
|
|
49
|
+
"@testing-library/user-event": "^14.5.0",
|
|
50
|
+
"@vitest/coverage-v8": "^2.0.0",
|
|
51
|
+
"jsdom": "^24.0.0",
|
|
52
|
+
"react": "^18.3.0",
|
|
53
|
+
"react-dom": "^18.3.0",
|
|
54
|
+
"tsup": "^8.1.0",
|
|
55
|
+
"typescript": "^5.5.0",
|
|
56
|
+
"vitest": "^2.0.0"
|
|
57
|
+
},
|
|
58
|
+
"scripts": {
|
|
59
|
+
"build": "tsup",
|
|
60
|
+
"test": "vitest run",
|
|
61
|
+
"test:watch": "vitest",
|
|
62
|
+
"test:coverage": "vitest run --coverage",
|
|
63
|
+
"type-check": "tsc --noEmit"
|
|
64
|
+
}
|
|
65
|
+
}
|