@milaboratories/uikit 2.2.66 → 2.2.68
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/CHANGELOG.md +12 -0
- package/dist/pl-uikit.js +4852 -4475
- package/dist/pl-uikit.js.map +1 -1
- package/dist/pl-uikit.umd.cjs +10 -10
- package/dist/pl-uikit.umd.cjs.map +1 -1
- package/dist/src/components/PlAutocomplete/PlAutocomplete.vue.d.ts +85 -0
- package/dist/src/components/PlAutocomplete/PlAutocomplete.vue.d.ts.map +1 -0
- package/dist/src/components/PlAutocomplete/__tests__/PlAutocomplete.spec.d.ts +2 -0
- package/dist/src/components/PlAutocomplete/__tests__/PlAutocomplete.spec.d.ts.map +1 -0
- package/dist/src/components/PlAutocomplete/index.d.ts +2 -0
- package/dist/src/components/PlAutocomplete/index.d.ts.map +1 -0
- package/dist/src/components/PlDropdown/PlDropdown.vue.d.ts.map +1 -1
- package/dist/src/components/PlDropdownMulti/PlDropdownMulti.vue.d.ts.map +1 -1
- package/dist/src/components/PlRadio/PlRadio.vue.d.ts +33 -0
- package/dist/src/components/PlRadio/PlRadio.vue.d.ts.map +1 -0
- package/dist/src/components/PlRadio/PlRadioGroup.vue.d.ts +50 -0
- package/dist/src/components/PlRadio/PlRadioGroup.vue.d.ts.map +1 -0
- package/dist/src/components/PlRadio/__tests__/PlRadioGroup.spec.d.ts +2 -0
- package/dist/src/components/PlRadio/__tests__/PlRadioGroup.spec.d.ts.map +1 -0
- package/dist/src/components/PlRadio/index.d.ts +3 -0
- package/dist/src/components/PlRadio/index.d.ts.map +1 -0
- package/dist/src/components/PlRadio/keys.d.ts +4 -0
- package/dist/src/components/PlRadio/keys.d.ts.map +1 -0
- package/dist/src/index.d.ts +2 -0
- package/dist/src/index.d.ts.map +1 -1
- package/dist/style.css +1 -1
- package/dist/tsconfig.lib.tsbuildinfo +1 -1
- package/package.json +4 -4
- package/src/assets/base.scss +0 -2
- package/src/assets/variables.scss +3 -3
- package/src/components/PlAutocomplete/PlAutocomplete.vue +413 -0
- package/src/components/PlAutocomplete/__tests__/PlAutocomplete.spec.ts +41 -0
- package/src/components/PlAutocomplete/index.ts +1 -0
- package/src/components/PlAutocomplete/pl-autocomplete.scss +277 -0
- package/src/components/PlDropdown/PlDropdown.vue +13 -6
- package/src/components/PlDropdown/pl-dropdown.scss +13 -3
- package/src/components/PlDropdownMulti/PlDropdownMulti.vue +14 -6
- package/src/components/PlDropdownMulti/pl-dropdown-multi.scss +39 -25
- package/src/components/PlRadio/PlRadio.vue +92 -0
- package/src/components/PlRadio/PlRadioGroup.vue +74 -0
- package/src/components/PlRadio/__tests__/PlRadioGroup.spec.ts +168 -0
- package/src/components/PlRadio/index.ts +2 -0
- package/src/components/PlRadio/keys.ts +4 -0
- package/src/index.ts +3 -0
|
@@ -0,0 +1,277 @@
|
|
|
1
|
+
@use "@/assets/mixins" as *;
|
|
2
|
+
|
|
3
|
+
.pl-autocomplete__options {
|
|
4
|
+
--option-hover-bg: var(--btn-sec-hover-grey);
|
|
5
|
+
|
|
6
|
+
z-index: var(--z-dropdown-options);
|
|
7
|
+
border: 1px solid var(--border-color-div-grey);
|
|
8
|
+
position: absolute;
|
|
9
|
+
background-color: var(--pl-dropdown-options-bg);
|
|
10
|
+
border-radius: 6px;
|
|
11
|
+
max-height: 244px;
|
|
12
|
+
box-shadow: 0px 4px 12px -2px rgba(15, 36, 77, 0.08), 0px 6px 24px -2px rgba(15, 36, 77, 0.08);
|
|
13
|
+
|
|
14
|
+
@include scrollbar;
|
|
15
|
+
|
|
16
|
+
.nothing-found {
|
|
17
|
+
padding: 0 10px;
|
|
18
|
+
height: var(--control-height);
|
|
19
|
+
line-height: var(--control-height);
|
|
20
|
+
background-color: #fff;
|
|
21
|
+
opacity: 0.5;
|
|
22
|
+
font-style: italic;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
.option {
|
|
26
|
+
position: relative;
|
|
27
|
+
padding: 0 30px 0 10px;
|
|
28
|
+
height: var(--control-height);
|
|
29
|
+
line-height: var(--control-height);
|
|
30
|
+
cursor: pointer;
|
|
31
|
+
user-select: none;
|
|
32
|
+
|
|
33
|
+
.checkmark {
|
|
34
|
+
position: absolute;
|
|
35
|
+
display: none;
|
|
36
|
+
right: 10px;
|
|
37
|
+
@include abs-center-y();
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
>span {
|
|
41
|
+
display: block;
|
|
42
|
+
overflow: hidden;
|
|
43
|
+
white-space: nowrap;
|
|
44
|
+
max-width: 100%;
|
|
45
|
+
text-overflow: ellipsis;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
&.selected {
|
|
49
|
+
background-color: var(--color-active-select);
|
|
50
|
+
|
|
51
|
+
.checkmark {
|
|
52
|
+
display: block;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
&.active:not(.selected) {
|
|
57
|
+
background-color: var(--option-hover-bg);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
&:hover {
|
|
61
|
+
background-color: var(--option-hover-bg);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
.pl-autocomplete {
|
|
67
|
+
$root: &;
|
|
68
|
+
|
|
69
|
+
--contour-color: var(--txt-01);
|
|
70
|
+
--contour-border-width: 1px;
|
|
71
|
+
|
|
72
|
+
--label-offset-left-x: 8px;
|
|
73
|
+
--label-offset-right-x: 8px;
|
|
74
|
+
--label-color: var(--txt-01);
|
|
75
|
+
|
|
76
|
+
position: relative;
|
|
77
|
+
outline: none;
|
|
78
|
+
min-height: var(--control-height);
|
|
79
|
+
border-radius: 6px;
|
|
80
|
+
font-family: var(--font-family-base);
|
|
81
|
+
font-size: var(--font-size-base);
|
|
82
|
+
font-weight: var(--font-weigh-base);
|
|
83
|
+
|
|
84
|
+
&__envelope {
|
|
85
|
+
font-family: var(--control-font-family);
|
|
86
|
+
min-width: 160px;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
label {
|
|
90
|
+
@include outlined-control-label();
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
&__container {
|
|
94
|
+
position: absolute;
|
|
95
|
+
top: 0;
|
|
96
|
+
left: 0;
|
|
97
|
+
right: 0;
|
|
98
|
+
border-radius: 6px;
|
|
99
|
+
min-height: var(--control-height);
|
|
100
|
+
color: var(--txt-01);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
&__contour {
|
|
104
|
+
border-radius: var(--border-radius-control);
|
|
105
|
+
border: var(--contour-border-width) solid var(--contour-color);
|
|
106
|
+
box-shadow: var(--contour-box-shadow);
|
|
107
|
+
z-index: 0;
|
|
108
|
+
pointer-events: none;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
&__field {
|
|
112
|
+
position: relative;
|
|
113
|
+
border-radius: 6px;
|
|
114
|
+
overflow: hidden;
|
|
115
|
+
background: transparent;
|
|
116
|
+
padding-left: 11px;
|
|
117
|
+
|
|
118
|
+
min-height: var(--control-height);
|
|
119
|
+
line-height: var(--control-height);
|
|
120
|
+
|
|
121
|
+
display: flex;
|
|
122
|
+
flex-direction: row;
|
|
123
|
+
align-items: center;
|
|
124
|
+
cursor: pointer;
|
|
125
|
+
|
|
126
|
+
.input-value {
|
|
127
|
+
position: absolute;
|
|
128
|
+
top: 0;
|
|
129
|
+
left: 0;
|
|
130
|
+
bottom: 0;
|
|
131
|
+
right: 0;
|
|
132
|
+
display: flex;
|
|
133
|
+
flex-direction: row;
|
|
134
|
+
align-items: center;
|
|
135
|
+
padding: 0 60px 0 11px; // @TODO padding-right based on controls width
|
|
136
|
+
pointer-events: none;
|
|
137
|
+
line-height: 20px;
|
|
138
|
+
color: var(--txt-01);
|
|
139
|
+
overflow: hidden;
|
|
140
|
+
white-space: pre;
|
|
141
|
+
text-overflow: ellipsis;
|
|
142
|
+
cursor: inherit;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
input {
|
|
146
|
+
min-height: calc(var(--control-height) - 2px);
|
|
147
|
+
line-height: 20px;
|
|
148
|
+
font-family: inherit;
|
|
149
|
+
font-size: inherit;
|
|
150
|
+
background-color: transparent;
|
|
151
|
+
border: none;
|
|
152
|
+
padding: 0;
|
|
153
|
+
width: calc(100% - 40px);
|
|
154
|
+
color: var(--txt-01);
|
|
155
|
+
caret-color: var(--border-color-focus);
|
|
156
|
+
|
|
157
|
+
&:focus {
|
|
158
|
+
outline: none;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
&:placeholder-shown {
|
|
162
|
+
text-overflow: ellipsis;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
&::placeholder {
|
|
166
|
+
color: var(--color-placeholder);
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
&__helper {
|
|
172
|
+
@include field-helper();
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
&__error {
|
|
176
|
+
@include field-error();
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
&__controls {
|
|
180
|
+
display: flex;
|
|
181
|
+
flex-direction: row;
|
|
182
|
+
align-items: center;
|
|
183
|
+
min-height: var(--control-height);
|
|
184
|
+
gap: 6px;
|
|
185
|
+
|
|
186
|
+
margin-left: auto;
|
|
187
|
+
|
|
188
|
+
.icon-delete-clear {
|
|
189
|
+
cursor: pointer;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
.mask-16,
|
|
193
|
+
.mask-24 {
|
|
194
|
+
background-color: var(--control-mask-fill);
|
|
195
|
+
cursor: pointer;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
.mask-loading {
|
|
199
|
+
animation: spin 2.5s linear infinite;
|
|
200
|
+
background-color: #07AD3E;
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
&__arrow-wrapper {
|
|
205
|
+
display: flex;
|
|
206
|
+
align-items: center;
|
|
207
|
+
min-height: var(--control-height);
|
|
208
|
+
padding-right: 11px;
|
|
209
|
+
}
|
|
210
|
+
.arrow-icon {
|
|
211
|
+
cursor: pointer;
|
|
212
|
+
|
|
213
|
+
// Default "arrow" icon (16x16)
|
|
214
|
+
&.arrow-icon-default {
|
|
215
|
+
transition: transform .2s;
|
|
216
|
+
background-color: var(--control-mask-fill);
|
|
217
|
+
@include mask(url('@/assets/images/16_chevron-down.svg'), 16px);
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
&.open,
|
|
222
|
+
&:focus-within {
|
|
223
|
+
z-index: 1;
|
|
224
|
+
--label-color: var(--txt-focus);
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
&.open {
|
|
228
|
+
#{$root}__container {
|
|
229
|
+
z-index: 1000;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
#{$root}__field {
|
|
233
|
+
border-radius: 6px 6px 0 0;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
.arrow-icon {
|
|
237
|
+
&.arrow-icon-default {
|
|
238
|
+
background-color: var(--control-mask-fill);
|
|
239
|
+
transform: rotate(-180deg);
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
&:hover {
|
|
245
|
+
--contour-color: var(--control-hover-color);
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
&:focus-within:not(.error) {
|
|
249
|
+
--label-color: var(--txt-focus);
|
|
250
|
+
--contour-color: var(--border-color-focus);
|
|
251
|
+
--contour-border-width: 2px;
|
|
252
|
+
--contour-box-shadow: 0 0 0 4px var(--border-color-focus-shadow);
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
&:focus-within.error {
|
|
256
|
+
--contour-border-width: 2px;
|
|
257
|
+
--contour-box-shadow: 0 0 0 4px var(--color-error-shadow);
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
&.error {
|
|
261
|
+
--contour-color: var(--txt-error);
|
|
262
|
+
--label-color: var(--txt-error);
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
&.disabled {
|
|
266
|
+
--contour-color: var(--color-dis-01);
|
|
267
|
+
--control-mask-fill: var(--color-dis-01);
|
|
268
|
+
--label-color: var(--color-dis-01);
|
|
269
|
+
cursor: not-allowed;
|
|
270
|
+
pointer-events: none;
|
|
271
|
+
user-select: none;
|
|
272
|
+
|
|
273
|
+
.input-value {
|
|
274
|
+
color: var(--dis-01);
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
}
|
|
@@ -223,7 +223,12 @@ const clear = () => emit('update:modelValue', undefined);
|
|
|
223
223
|
|
|
224
224
|
const setFocusOnInput = () => input.value?.focus();
|
|
225
225
|
|
|
226
|
-
const toggleOpen = () =>
|
|
226
|
+
const toggleOpen = () => {
|
|
227
|
+
data.open = !data.open;
|
|
228
|
+
if (!data.open) {
|
|
229
|
+
data.search = '';
|
|
230
|
+
}
|
|
231
|
+
};
|
|
227
232
|
|
|
228
233
|
const onInputFocus = () => (data.open = true);
|
|
229
234
|
|
|
@@ -298,7 +303,7 @@ watchPostEffect(() => {
|
|
|
298
303
|
</script>
|
|
299
304
|
|
|
300
305
|
<template>
|
|
301
|
-
<div class="pl-dropdown__envelope">
|
|
306
|
+
<div class="pl-dropdown__envelope" @click="setFocusOnInput">
|
|
302
307
|
<div
|
|
303
308
|
ref="rootRef"
|
|
304
309
|
:tabindex="tabindex"
|
|
@@ -321,7 +326,7 @@ watchPostEffect(() => {
|
|
|
321
326
|
@focus="onInputFocus"
|
|
322
327
|
/>
|
|
323
328
|
|
|
324
|
-
<div v-if="!data.open" class="input-value"
|
|
329
|
+
<div v-if="!data.open" class="input-value">
|
|
325
330
|
<LongText> {{ textValue }} </LongText>
|
|
326
331
|
</div>
|
|
327
332
|
|
|
@@ -329,9 +334,11 @@ watchPostEffect(() => {
|
|
|
329
334
|
<PlMaskIcon24 v-if="isLoadingOptions" name="loading" />
|
|
330
335
|
<PlIcon16 v-if="clearable && hasValue" name="delete-clear" @click.stop="clear" />
|
|
331
336
|
<slot name="append" />
|
|
332
|
-
<div
|
|
333
|
-
|
|
334
|
-
|
|
337
|
+
<div class="pl-dropdown__arrow-wrapper" @click.stop="toggleOpen">
|
|
338
|
+
<div v-if="arrowIconLarge" class="arrow-icon" :class="[`icon-24 ${arrowIconLarge}`]" />
|
|
339
|
+
<div v-else-if="arrowIcon" class="arrow-icon" :class="[`icon-16 ${arrowIcon}`]" />
|
|
340
|
+
<div v-else class="arrow-icon arrow-icon-default" />
|
|
341
|
+
</div>
|
|
335
342
|
</div>
|
|
336
343
|
</div>
|
|
337
344
|
<label v-if="label">
|
|
@@ -113,7 +113,7 @@
|
|
|
113
113
|
border-radius: 6px;
|
|
114
114
|
overflow: hidden;
|
|
115
115
|
background: transparent;
|
|
116
|
-
padding:
|
|
116
|
+
padding-left: 11px;
|
|
117
117
|
|
|
118
118
|
min-height: var(--control-height);
|
|
119
119
|
line-height: var(--control-height);
|
|
@@ -199,11 +199,19 @@
|
|
|
199
199
|
}
|
|
200
200
|
}
|
|
201
201
|
|
|
202
|
+
&__arrow-wrapper {
|
|
203
|
+
display: flex;
|
|
204
|
+
align-items: center;
|
|
205
|
+
min-height: var(--control-height);
|
|
206
|
+
padding-right: 11px;
|
|
207
|
+
}
|
|
208
|
+
|
|
202
209
|
.arrow-icon {
|
|
203
210
|
cursor: pointer;
|
|
204
211
|
|
|
205
212
|
// Default "arrow" icon (16x16)
|
|
206
213
|
&.arrow-icon-default {
|
|
214
|
+
transition: transform .2s;
|
|
207
215
|
background-color: var(--control-mask-fill);
|
|
208
216
|
@include mask(url('@/assets/images/16_chevron-down.svg'), 16px);
|
|
209
217
|
}
|
|
@@ -225,8 +233,10 @@
|
|
|
225
233
|
}
|
|
226
234
|
|
|
227
235
|
.arrow-icon {
|
|
228
|
-
|
|
229
|
-
|
|
236
|
+
&.arrow-icon-default {
|
|
237
|
+
background-color: var(--control-mask-fill);
|
|
238
|
+
transform: rotate(-180deg);
|
|
239
|
+
}
|
|
230
240
|
}
|
|
231
241
|
}
|
|
232
242
|
|
|
@@ -164,7 +164,12 @@ const unselectOption = (d: M) => emitModel(unref(selectedValuesRef).filter((v) =
|
|
|
164
164
|
|
|
165
165
|
const setFocusOnInput = () => input.value?.focus();
|
|
166
166
|
|
|
167
|
-
const toggleModel = () =>
|
|
167
|
+
const toggleModel = () => {
|
|
168
|
+
data.open = !data.open;
|
|
169
|
+
if (!data.open) {
|
|
170
|
+
data.search = '';
|
|
171
|
+
}
|
|
172
|
+
};
|
|
168
173
|
|
|
169
174
|
const onFocusOut = (event: FocusEvent) => {
|
|
170
175
|
const relatedTarget = event.relatedTarget as Node | null;
|
|
@@ -232,7 +237,7 @@ watchPostEffect(() => {
|
|
|
232
237
|
</script>
|
|
233
238
|
|
|
234
239
|
<template>
|
|
235
|
-
<div class="pl-dropdown-multi__envelope">
|
|
240
|
+
<div class="pl-dropdown-multi__envelope" @click="setFocusOnInput">
|
|
236
241
|
<div
|
|
237
242
|
ref="rootRef"
|
|
238
243
|
:tabindex="tabindex"
|
|
@@ -254,15 +259,18 @@ watchPostEffect(() => {
|
|
|
254
259
|
autocomplete="chrome-off"
|
|
255
260
|
@focus="data.open = true"
|
|
256
261
|
/>
|
|
257
|
-
<div v-if="!data.open" class="chips-container"
|
|
262
|
+
<div v-if="!data.open" class="chips-container">
|
|
258
263
|
<PlChip v-for="(opt, i) in selectedOptionsRef" :key="i" closeable small @click.stop="data.open = true" @close="unselectOption(opt.value)">
|
|
259
264
|
{{ opt.label || opt.value }}
|
|
260
265
|
</PlChip>
|
|
261
266
|
</div>
|
|
262
|
-
|
|
263
|
-
<div
|
|
264
|
-
|
|
267
|
+
|
|
268
|
+
<div class="pl-dropdown-multi__controls">
|
|
269
|
+
<PlMaskIcon24 v-if="isLoadingOptions" name="loading" />
|
|
265
270
|
<slot name="append" />
|
|
271
|
+
<div class="pl-dropdown-multi__arrow-wrapper" @click.stop="toggleModel">
|
|
272
|
+
<div class="arrow-icon arrow-icon-default" />
|
|
273
|
+
</div>
|
|
266
274
|
</div>
|
|
267
275
|
</div>
|
|
268
276
|
<label v-if="label">
|
|
@@ -129,7 +129,7 @@
|
|
|
129
129
|
border-radius: 6px;
|
|
130
130
|
overflow: hidden;
|
|
131
131
|
background: transparent;
|
|
132
|
-
padding:
|
|
132
|
+
padding-left: 11px;
|
|
133
133
|
|
|
134
134
|
min-height: var(--control-height);
|
|
135
135
|
line-height: 20px;
|
|
@@ -146,7 +146,7 @@
|
|
|
146
146
|
bottom: 0;
|
|
147
147
|
right: 30px;
|
|
148
148
|
overflow: hidden;
|
|
149
|
-
padding: 0 11px;
|
|
149
|
+
padding: 0 60px 0 11px;
|
|
150
150
|
line-height: 20px;
|
|
151
151
|
color: var(--contour-color);
|
|
152
152
|
display: flex;
|
|
@@ -186,43 +186,57 @@
|
|
|
186
186
|
}
|
|
187
187
|
}
|
|
188
188
|
|
|
189
|
-
&
|
|
190
|
-
@include field-helper();
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
&__error {
|
|
194
|
-
@include field-error();
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
&__append {
|
|
198
|
-
padding-right: 24px;
|
|
189
|
+
&__controls {
|
|
199
190
|
display: flex;
|
|
200
191
|
flex-direction: row;
|
|
201
192
|
align-items: center;
|
|
202
|
-
gap:
|
|
193
|
+
gap: 6px;
|
|
194
|
+
margin-left: auto;
|
|
203
195
|
|
|
204
|
-
.
|
|
196
|
+
.mask-16,
|
|
197
|
+
.mask-24 {
|
|
198
|
+
background-color: var(--control-mask-fill);
|
|
205
199
|
cursor: pointer;
|
|
206
200
|
}
|
|
207
201
|
|
|
208
|
-
.mask {
|
|
202
|
+
.mask-loading {
|
|
203
|
+
animation: spin 2.5s linear infinite;
|
|
204
|
+
background-color: #07AD3E;
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
&__arrow-wrapper {
|
|
209
|
+
display: flex;
|
|
210
|
+
align-items: center;
|
|
211
|
+
min-height: var(--control-height);
|
|
212
|
+
padding-right: 11px;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
.arrow-icon {
|
|
216
|
+
cursor: pointer;
|
|
217
|
+
|
|
218
|
+
// Default "arrow" icon (16x16)
|
|
219
|
+
&.arrow-icon-default {
|
|
220
|
+
transition: transform .2s;
|
|
209
221
|
background-color: var(--control-mask-fill);
|
|
210
|
-
|
|
222
|
+
@include mask(url('@/assets/images/16_chevron-down.svg'), 16px);
|
|
211
223
|
}
|
|
212
224
|
}
|
|
213
225
|
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
@include
|
|
226
|
+
&__helper {
|
|
227
|
+
@include field-helper();
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
&__error {
|
|
231
|
+
@include field-error();
|
|
220
232
|
}
|
|
221
233
|
|
|
222
234
|
&.open {
|
|
223
|
-
.arrow {
|
|
224
|
-
|
|
225
|
-
|
|
235
|
+
.arrow-icon {
|
|
236
|
+
&.arrow-icon-default {
|
|
237
|
+
background-color: var(--control-mask-fill);
|
|
238
|
+
transform: rotate(-180deg);
|
|
239
|
+
}
|
|
226
240
|
}
|
|
227
241
|
}
|
|
228
242
|
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
<script setup lang="ts" generic="M">
|
|
2
|
+
import { inject } from 'vue';
|
|
3
|
+
import { radioGroupModelKey, radioGroupNameKey } from './keys';
|
|
4
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars -- used by the props documentation
|
|
5
|
+
import type PlRadioGroup from './PlRadioGroup.vue';
|
|
6
|
+
|
|
7
|
+
const standaloneModel = defineModel<M>();
|
|
8
|
+
|
|
9
|
+
const { name: standaloneName, ...props } = defineProps<{
|
|
10
|
+
/** Used to group multiple radio controls. Will be ignored if this component is a descendant of a {@link PlRadioGroup}. */
|
|
11
|
+
name?: string;
|
|
12
|
+
/** Value that goes into `v-model`. */
|
|
13
|
+
value?: M;
|
|
14
|
+
/** Whether the radio control is disabled. */
|
|
15
|
+
disabled?: boolean;
|
|
16
|
+
}>();
|
|
17
|
+
|
|
18
|
+
defineSlots<{
|
|
19
|
+
/** Label of the radio control. */
|
|
20
|
+
default?(): unknown;
|
|
21
|
+
}>();
|
|
22
|
+
|
|
23
|
+
const name = inject(radioGroupNameKey, standaloneName);
|
|
24
|
+
const model = inject<typeof standaloneModel>(radioGroupModelKey, standaloneModel);
|
|
25
|
+
</script>
|
|
26
|
+
|
|
27
|
+
<template>
|
|
28
|
+
<label :class="$style.container">
|
|
29
|
+
<input v-model="model" :class="$style.input" type="radio" :name v-bind="props" />
|
|
30
|
+
<span :class="$style.label"><slot /></span>
|
|
31
|
+
</label>
|
|
32
|
+
</template>
|
|
33
|
+
|
|
34
|
+
<style module>
|
|
35
|
+
.container {
|
|
36
|
+
display: flex;
|
|
37
|
+
align-items: center;
|
|
38
|
+
gap: 4px;
|
|
39
|
+
padding: 4px;
|
|
40
|
+
border-radius: 6px;
|
|
41
|
+
transition: all 200ms ease-in-out;
|
|
42
|
+
color: var(--txt-01);
|
|
43
|
+
user-select: none;
|
|
44
|
+
&:hover:not(:has(:disabled)) {
|
|
45
|
+
background: var(--btn-sec-hover-grey);
|
|
46
|
+
}
|
|
47
|
+
&:has(:disabled) {
|
|
48
|
+
color: var(--dis-01);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
.input {
|
|
53
|
+
appearance: none;
|
|
54
|
+
position: relative;
|
|
55
|
+
block-size: 24px;
|
|
56
|
+
aspect-ratio: 1;
|
|
57
|
+
margin: 0;
|
|
58
|
+
border-radius: 50%;
|
|
59
|
+
outline: 2px solid transparent;
|
|
60
|
+
color: inherit;
|
|
61
|
+
transition: inherit;
|
|
62
|
+
&:focus {
|
|
63
|
+
outline-color: var(--border-color-focus);
|
|
64
|
+
}
|
|
65
|
+
&::before {
|
|
66
|
+
content: "";
|
|
67
|
+
position: absolute;
|
|
68
|
+
inset: 2px;
|
|
69
|
+
border-radius: 50%;
|
|
70
|
+
border: 2px solid;
|
|
71
|
+
}
|
|
72
|
+
&::after {
|
|
73
|
+
content: "";
|
|
74
|
+
position: absolute;
|
|
75
|
+
inset: 7.5px;
|
|
76
|
+
border-radius: 50%;
|
|
77
|
+
background-color: currentColor;
|
|
78
|
+
scale: 0;
|
|
79
|
+
transition: inherit;
|
|
80
|
+
}
|
|
81
|
+
&:checked::after {
|
|
82
|
+
scale: 1;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
.label {
|
|
87
|
+
padding-inline: 4px;
|
|
88
|
+
line-height: calc(20 / 14);
|
|
89
|
+
font-weight: 500;
|
|
90
|
+
text-box: trim-both cap alphabetic;
|
|
91
|
+
}
|
|
92
|
+
</style>
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
<script setup lang="ts" generic="M">
|
|
2
|
+
import { provide } from 'vue';
|
|
3
|
+
import { radioGroupModelKey, radioGroupNameKey } from './keys';
|
|
4
|
+
import PlRadio from './PlRadio.vue';
|
|
5
|
+
|
|
6
|
+
type RadioGroupOption = {
|
|
7
|
+
label: string;
|
|
8
|
+
value: M;
|
|
9
|
+
disabled?: boolean;
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
const model = defineModel<M>();
|
|
13
|
+
|
|
14
|
+
const props = defineProps<{
|
|
15
|
+
/** Name of the radio group. */
|
|
16
|
+
name?: string;
|
|
17
|
+
/**
|
|
18
|
+
* List of available options.
|
|
19
|
+
* Renders a list of {@link PlRadio} components before the {@link slots.default | default} slot.
|
|
20
|
+
*/
|
|
21
|
+
options?: Readonly<RadioGroupOption[]>;
|
|
22
|
+
/** Function to get option's unique key. Use if default mechanism (key = index) is unstable. */
|
|
23
|
+
keyExtractor?: (value: M, index: number) => PropertyKey;
|
|
24
|
+
}>();
|
|
25
|
+
|
|
26
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars -- used by the props documentation
|
|
27
|
+
const slots = defineSlots<{
|
|
28
|
+
/**
|
|
29
|
+
* Can be anything, but usually an array of {@link PlRadio} components.
|
|
30
|
+
* If {@link props.options|options} are provided, they will be rendered before this slot.
|
|
31
|
+
*/
|
|
32
|
+
default?(): unknown;
|
|
33
|
+
/** Label of the radio group. */
|
|
34
|
+
label?(): unknown;
|
|
35
|
+
}>();
|
|
36
|
+
|
|
37
|
+
const keyExtractor = props.keyExtractor ?? ((_, i) => i);
|
|
38
|
+
|
|
39
|
+
provide(radioGroupNameKey, props.name);
|
|
40
|
+
provide(radioGroupModelKey, model);
|
|
41
|
+
</script>
|
|
42
|
+
|
|
43
|
+
<template>
|
|
44
|
+
<fieldset :class="$style.container">
|
|
45
|
+
<legend :class="$style.label">
|
|
46
|
+
<slot name="label" />
|
|
47
|
+
</legend>
|
|
48
|
+
<PlRadio
|
|
49
|
+
v-for="(option, i) in options"
|
|
50
|
+
:key="keyExtractor(option.value, i)"
|
|
51
|
+
:value="option.value"
|
|
52
|
+
:disabled="option.disabled"
|
|
53
|
+
>
|
|
54
|
+
{{ option.label }}
|
|
55
|
+
</PlRadio>
|
|
56
|
+
<slot />
|
|
57
|
+
</fieldset>
|
|
58
|
+
</template>
|
|
59
|
+
|
|
60
|
+
<style module>
|
|
61
|
+
.container {
|
|
62
|
+
margin: 0;
|
|
63
|
+
padding: 0;
|
|
64
|
+
border: none;
|
|
65
|
+
}
|
|
66
|
+
.label {
|
|
67
|
+
margin-block-end: 12px;
|
|
68
|
+
padding: 0;
|
|
69
|
+
color: var(--txt-01);
|
|
70
|
+
line-height: calc(20 / 14);
|
|
71
|
+
font-weight: 500;
|
|
72
|
+
text-box: trim-both cap alphabetic;
|
|
73
|
+
}
|
|
74
|
+
</style>
|