@antify/ui-module 1.1.4 → 1.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/dist/module.d.mts +2 -0
- package/dist/module.d.ts +2 -0
- package/dist/module.json +1 -1
- package/dist/module.mjs +3 -0
- package/dist/runtime/components/AntAccordionItem.vue +1 -1
- package/dist/runtime/components/AntTag.vue +3 -3
- package/dist/runtime/components/AntToast.vue +1 -0
- package/dist/runtime/components/{Main.stories.mdx → Main.mdx} +3 -2
- package/dist/runtime/components/Main.stories.d.ts +7 -0
- package/dist/runtime/components/Main.stories.mjs +8 -0
- package/dist/runtime/components/__stories/AntAccordion.stories.mjs +13 -10
- package/dist/runtime/components/__stories/AntKeycap.stories.d.ts +1 -1
- package/dist/runtime/components/__stories/AntKeycap.stories.mjs +15 -12
- package/dist/runtime/components/__types/AntIcon.types.d.ts +1 -0
- package/dist/runtime/components/__types/AntIcon.types.mjs +1 -0
- package/dist/runtime/components/buttons/__stories/AntButton.stories.mjs +3 -0
- package/dist/runtime/components/form/AntCheckboxWidget/__stories/AntCheckbox.stories.mjs +3 -0
- package/dist/runtime/components/form/AntSelect.vue +4 -51
- package/dist/runtime/components/form/AntSwitch.vue +8 -3
- package/dist/runtime/components/form/AntTagInput.vue +306 -0
- package/dist/runtime/components/form/Elements/AntDropDown.vue +53 -23
- package/dist/runtime/components/form/Elements/AntField.vue +2 -2
- package/dist/runtime/components/form/Elements/__stories/AntBaseInput.stories.mjs +3 -0
- package/dist/runtime/components/form/Elements/__types/AntBaseInput.type.d.ts +1 -0
- package/dist/runtime/components/form/Elements/__types/AntBaseInput.type.mjs +1 -0
- package/dist/runtime/components/form/__stories/AntSelect.stories.mjs +3 -0
- package/dist/runtime/components/form/__stories/AntSwitch.stories.mjs +3 -0
- package/dist/runtime/components/form/__stories/AntSwitcher.stories.mjs +3 -0
- package/dist/runtime/components/form/__stories/AntTagInput.stories.d.ts +9 -0
- package/dist/runtime/components/form/__stories/AntTagInput.stories.mjs +106 -0
- package/dist/runtime/components/form/__stories/AntTextarea.stories.mjs +3 -0
- package/dist/runtime/components/form/index.d.ts +2 -1
- package/dist/runtime/components/form/index.mjs +2 -0
- package/dist/runtime/tailwind.config.d.ts +1 -0
- package/dist/runtime/tailwind.config.mjs +1 -0
- package/dist/runtime/types/AntTag.type.d.ts +1 -2
- package/dist/runtime/types/AntTag.type.mjs +1 -2
- package/package.json +20 -21
- package/src/runtime/components/AntAccordionItem.vue +2 -2
- package/src/runtime/components/AntTag.vue +3 -3
- package/src/runtime/components/AntToast.vue +1 -0
- package/src/runtime/components/form/AntSelect.vue +4 -51
- package/src/runtime/components/form/AntSwitch.vue +8 -3
- package/src/runtime/components/form/AntTagInput.vue +306 -0
- package/src/runtime/components/form/Elements/AntDropDown.vue +53 -23
- package/src/runtime/components/form/Elements/AntField.vue +2 -2
|
@@ -0,0 +1,306 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
|
|
3
|
+
import { AntField } from './Elements';
|
|
4
|
+
import type { SelectOption } from './__types';
|
|
5
|
+
import { Grouped, InputColorType, Size } from '../../enums';
|
|
6
|
+
import type { FieldValidator } from '@antify/validate';
|
|
7
|
+
import { useVModel } from '@vueuse/core';
|
|
8
|
+
import {
|
|
9
|
+
faChevronRight,
|
|
10
|
+
type IconDefinition
|
|
11
|
+
} from '@fortawesome/free-solid-svg-icons';
|
|
12
|
+
import { computed, type Ref, ref } from 'vue';
|
|
13
|
+
import AntTag from '../AntTag.vue';
|
|
14
|
+
import AntIcon from '../AntIcon.vue';
|
|
15
|
+
import { IconSize } from '../__types';
|
|
16
|
+
import AntDropDown from './Elements/AntDropDown.vue';
|
|
17
|
+
import AntSkeleton from '../AntSkeleton.vue';
|
|
18
|
+
import { vOnClickOutside } from '@vueuse/components'
|
|
19
|
+
|
|
20
|
+
const emit = defineEmits([ 'update:modelValue' ]);
|
|
21
|
+
const props = withDefaults(
|
|
22
|
+
defineProps<{
|
|
23
|
+
modelValue: (string | number)[] | null;
|
|
24
|
+
options: SelectOption[];
|
|
25
|
+
label?: string;
|
|
26
|
+
description?: string;
|
|
27
|
+
placeholder?: string;
|
|
28
|
+
size?: Size;
|
|
29
|
+
colorType?: InputColorType;
|
|
30
|
+
disabled?: boolean;
|
|
31
|
+
skeleton?: boolean;
|
|
32
|
+
validator?: FieldValidator;
|
|
33
|
+
name?: string;
|
|
34
|
+
showMessageOnError?: boolean;
|
|
35
|
+
expanded?: boolean;
|
|
36
|
+
icon?: IconDefinition;
|
|
37
|
+
grouped?: Grouped;
|
|
38
|
+
nullable?: boolean;
|
|
39
|
+
|
|
40
|
+
allowCreate?: boolean;
|
|
41
|
+
allowDuplicates?: boolean;
|
|
42
|
+
openOnFocus?: boolean;
|
|
43
|
+
autoCloseAfterSelection?: boolean;
|
|
44
|
+
|
|
45
|
+
createCallback?: (item: string) => Promise<SelectOption>;
|
|
46
|
+
}>(), {
|
|
47
|
+
size: Size.md,
|
|
48
|
+
colorType: InputColorType.base,
|
|
49
|
+
icon: () => faChevronRight,
|
|
50
|
+
grouped: Grouped.none,
|
|
51
|
+
|
|
52
|
+
allowCreate: false,
|
|
53
|
+
allowDuplicates: false,
|
|
54
|
+
openOnFocus: true,
|
|
55
|
+
autoCloseAfterSelection: false,
|
|
56
|
+
showMessageOnError: true,
|
|
57
|
+
|
|
58
|
+
placeholder: 'Add new tag'
|
|
59
|
+
}
|
|
60
|
+
);
|
|
61
|
+
|
|
62
|
+
const _modelValue: Ref<(string | number)[] | null> = useVModel(props, 'modelValue', emit);
|
|
63
|
+
const _skeleton = useVModel(props, 'skeleton', emit);
|
|
64
|
+
|
|
65
|
+
const dropDownOpen = ref(false);
|
|
66
|
+
const focusedDropDownItem: Ref<string | number | null> = ref(null);
|
|
67
|
+
const tagInput = ref('');
|
|
68
|
+
const filteredOptions = ref(props.options);
|
|
69
|
+
|
|
70
|
+
const inputRef: Ref<HTMLElement | null> = ref(null);
|
|
71
|
+
|
|
72
|
+
const _colorType = computed(() => props.validator?.hasErrors() ? InputColorType.danger : props.colorType);
|
|
73
|
+
|
|
74
|
+
const inputContainerClasses = computed(() => {
|
|
75
|
+
const variants: Record<InputColorType, string> = {
|
|
76
|
+
[InputColorType.base]: 'outline-neutral-300 focus-within:outline-primary-500 focus-within:ring-primary-200 bg-white',
|
|
77
|
+
[InputColorType.danger]: 'outline-danger-500 focus-within:outline-danger-500 focus-within:ring-danger-200 bg-danger-100',
|
|
78
|
+
[InputColorType.info]: 'outline-info-500 focus-within:outline-info-500 focus-within:ring-info-200 bg-info-100',
|
|
79
|
+
[InputColorType.success]: 'outline-success-500 focus-within:outline-success-500 focus-within:ring-success-200 bg-success-100',
|
|
80
|
+
[InputColorType.warning]: 'outline-warning-500 focus-within:outline-warning-500 focus-within:ring-warning-200 bg-warning-100',
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
return {
|
|
84
|
+
'flex gap-1 items-center flex-wrap': true,
|
|
85
|
+
'transition-colors relative border-none outline w-full focus-within:z-10': true,
|
|
86
|
+
'outline-offset-[-1px] outline-1 focus-within:outline-offset-[-1px] focus-within:outline-1': true,
|
|
87
|
+
'opacity-50 cursor-not-allowed': props.disabled,
|
|
88
|
+
[variants[_colorType.value]]: true,
|
|
89
|
+
// Size
|
|
90
|
+
'focus-within:ring-2 px-1.5 py-1.5 text-xs': props.size === Size.sm,
|
|
91
|
+
'focus-within:ring-4 px-2.5 py-1.5 text-sm': props.size === Size.md,
|
|
92
|
+
// Grouping
|
|
93
|
+
'rounded-tl-md rounded-bl-md rounded-tr-none rounded-br-none': props.grouped === Grouped.left,
|
|
94
|
+
'rounded-none': props.grouped === Grouped.center,
|
|
95
|
+
'rounded-tl-none rounded-bl-none rounded-tr-md rounded-br-md': props.grouped === Grouped.right,
|
|
96
|
+
'rounded-md': props.grouped === Grouped.none,
|
|
97
|
+
'rounded-bl-none rounded-br-none': dropDownOpen.value && (!props.options || props.options.length > 0),
|
|
98
|
+
'invisible': props.skeleton,
|
|
99
|
+
};
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
const inputClasses = computed(() => {
|
|
103
|
+
const variants: Record<InputColorType, string> = {
|
|
104
|
+
[InputColorType.base]: 'placeholder:text-neutral-500',
|
|
105
|
+
[InputColorType.danger]: 'placeholder:text-danger-700',
|
|
106
|
+
[InputColorType.info]: 'placeholder:text-info-700',
|
|
107
|
+
[InputColorType.success]: 'placeholder:text-success-700',
|
|
108
|
+
[InputColorType.warning]: 'placeholder:text-warning-700',
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
return {
|
|
112
|
+
'outline-0 border:none ring:none bg-transparent w-full py-1': true,
|
|
113
|
+
'opacity-50 cursor-not-allowed': props.disabled,
|
|
114
|
+
[variants[_colorType.value]]: true,
|
|
115
|
+
}
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
const skeletonGrouped = computed(() => {
|
|
119
|
+
if (!props.nullable || (props.nullable && _modelValue.value === null)) {
|
|
120
|
+
return props.grouped;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
if (props.grouped === Grouped.right || props.grouped === Grouped.center) {
|
|
124
|
+
return Grouped.center;
|
|
125
|
+
} else {
|
|
126
|
+
return Grouped.left;
|
|
127
|
+
}
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
function onClickOutside() {
|
|
131
|
+
if (!dropDownOpen.value) {
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
dropDownOpen.value = false;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
async function checkCreateTag(item: string): Promise<void> {
|
|
139
|
+
if (props.allowCreate && focusedDropDownItem.value) {
|
|
140
|
+
// If allowCreate is active but a item is focused inside the dropdown do nothing here.
|
|
141
|
+
return;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
if (item && props.allowCreate && props.createCallback) {
|
|
145
|
+
const newOption: SelectOption = await props.createCallback(item);
|
|
146
|
+
|
|
147
|
+
addTag(newOption.value);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
function addTagFromOptions(item: string | number) {
|
|
152
|
+
if (props.allowCreate && !focusedDropDownItem.value) {
|
|
153
|
+
// If allowCreate is active we don't need to add it here.
|
|
154
|
+
return;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
const option = props.options?.find(option => option.value === item);
|
|
158
|
+
|
|
159
|
+
if (option) {
|
|
160
|
+
addTag(item);
|
|
161
|
+
|
|
162
|
+
if (props.autoCloseAfterSelection) {
|
|
163
|
+
dropDownOpen.value = false;
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
function addTag(tagValue: string | number): void {
|
|
169
|
+
_modelValue.value = _modelValue.value || [];
|
|
170
|
+
|
|
171
|
+
if (!props.allowDuplicates && _modelValue.value.includes(tagValue) || !tagValue) {
|
|
172
|
+
return;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
_modelValue.value.push(tagValue);
|
|
176
|
+
|
|
177
|
+
tagInput.value = '';
|
|
178
|
+
|
|
179
|
+
filterDropDown();
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
function removeLastTag() {
|
|
183
|
+
if (tagInput.value === '' && _modelValue.value && _modelValue.value.length > 0) {
|
|
184
|
+
_modelValue.value.splice(-1, 1);
|
|
185
|
+
|
|
186
|
+
filterDropDown();
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
function removeTag(tag: string | number) {
|
|
191
|
+
if (_modelValue.value && !props.disabled && !props.skeleton) {
|
|
192
|
+
_modelValue.value.splice(_modelValue.value.findIndex((_value) => _value === tag), 1);
|
|
193
|
+
|
|
194
|
+
filterDropDown();
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
function changeFocus() {
|
|
199
|
+
if (props.openOnFocus) {
|
|
200
|
+
dropDownOpen.value = true;
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
function filterDropDown() {
|
|
205
|
+
if (!props.options) {
|
|
206
|
+
return;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
if (props.allowCreate) {
|
|
210
|
+
focusedDropDownItem.value = null;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
dropDownOpen.value = true;
|
|
214
|
+
|
|
215
|
+
filteredOptions.value = props.options.filter(option => option.label.toLowerCase().includes(tagInput.value.toLowerCase()));
|
|
216
|
+
|
|
217
|
+
// Remove all elements that are in modelValue from the filtered options
|
|
218
|
+
if (_modelValue.value && !props.allowDuplicates) {
|
|
219
|
+
filteredOptions.value = filteredOptions.value.filter(option => !_modelValue.value?.includes(option.value));
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
if (!props.allowCreate && filteredOptions.value.length > 0) {
|
|
223
|
+
focusedDropDownItem.value = filteredOptions.value[0]?.value;
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
</script>
|
|
227
|
+
|
|
228
|
+
<template>
|
|
229
|
+
<AntField
|
|
230
|
+
:label="label"
|
|
231
|
+
:size="size"
|
|
232
|
+
:skeleton="_skeleton"
|
|
233
|
+
:description="description"
|
|
234
|
+
:color-type="colorType"
|
|
235
|
+
:validator="validator"
|
|
236
|
+
:show-message-on-error="showMessageOnError"
|
|
237
|
+
:expanded="expanded"
|
|
238
|
+
>
|
|
239
|
+
<div
|
|
240
|
+
class="relative w-full"
|
|
241
|
+
v-on-click-outside="onClickOutside"
|
|
242
|
+
>
|
|
243
|
+
<AntSkeleton
|
|
244
|
+
v-if="skeleton"
|
|
245
|
+
absolute
|
|
246
|
+
rounded
|
|
247
|
+
:grouped="skeletonGrouped"
|
|
248
|
+
/>
|
|
249
|
+
|
|
250
|
+
<div
|
|
251
|
+
:class="inputContainerClasses"
|
|
252
|
+
class="w-full flex gap-2.5 items-center"
|
|
253
|
+
>
|
|
254
|
+
<AntTag
|
|
255
|
+
v-for="(tag, index) in _modelValue"
|
|
256
|
+
:key="`tag-input-tag-${index}`"
|
|
257
|
+
:size="size"
|
|
258
|
+
:color-type="_colorType"
|
|
259
|
+
dismiss
|
|
260
|
+
@close="removeTag(tag)"
|
|
261
|
+
>
|
|
262
|
+
{{ options.find((option: SelectOption) => option.value === tag)?.label }}
|
|
263
|
+
</AntTag>
|
|
264
|
+
|
|
265
|
+
<!-- Input -->
|
|
266
|
+
<div class="flex items-center gap-1 w-32 shrink grow">
|
|
267
|
+
<AntIcon :icon="icon" :size="size === Size.sm ? IconSize.xs : IconSize.sm"/>
|
|
268
|
+
|
|
269
|
+
<input
|
|
270
|
+
v-model="tagInput"
|
|
271
|
+
type="text"
|
|
272
|
+
ref="inputRef"
|
|
273
|
+
:placeholder="placeholder"
|
|
274
|
+
:class="inputClasses"
|
|
275
|
+
:disabled="disabled"
|
|
276
|
+
@focus="changeFocus"
|
|
277
|
+
@input="filterDropDown"
|
|
278
|
+
@keydown.delete="removeLastTag"
|
|
279
|
+
@keydown.enter.prevent="checkCreateTag(tagInput)"
|
|
280
|
+
/>
|
|
281
|
+
</div>
|
|
282
|
+
</div>
|
|
283
|
+
|
|
284
|
+
<AntDropDown
|
|
285
|
+
v-if="filteredOptions && !disabled"
|
|
286
|
+
v-model:focused="focusedDropDownItem"
|
|
287
|
+
v-model:open="dropDownOpen"
|
|
288
|
+
ref="dropDownRef"
|
|
289
|
+
:model-value="null"
|
|
290
|
+
:auto-select-first-on-open="!allowCreate"
|
|
291
|
+
:options="filteredOptions"
|
|
292
|
+
:input-ref="inputRef"
|
|
293
|
+
:size="size"
|
|
294
|
+
:color-type="_colorType"
|
|
295
|
+
:focus-on-open="false"
|
|
296
|
+
@select-element="addTagFromOptions"
|
|
297
|
+
>
|
|
298
|
+
<template #empty>
|
|
299
|
+
<span v-if="allowCreate">
|
|
300
|
+
No tag found, create now
|
|
301
|
+
</span>
|
|
302
|
+
</template>
|
|
303
|
+
</AntDropDown>
|
|
304
|
+
</div>
|
|
305
|
+
</AntField>
|
|
306
|
+
</template>
|
|
@@ -7,30 +7,34 @@
|
|
|
7
7
|
* TODO:: if the dropdown is open and the user types something, the element with a matching value should be focused.
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
|
-
import { computed, nextTick, ref, watch } from 'vue';
|
|
10
|
+
import { computed, nextTick, onMounted, onUnmounted, ref, watch } from 'vue';
|
|
11
11
|
import { InputColorType, Size } from '../../../enums';
|
|
12
12
|
import type { SelectOption } from '../__types';
|
|
13
13
|
import { useVModel } from '@vueuse/core';
|
|
14
14
|
import type { Validator } from '@antify/validate';
|
|
15
15
|
|
|
16
|
-
const emit = defineEmits(['update:open', 'update:modelValue']);
|
|
16
|
+
const emit = defineEmits([ 'update:open', 'update:modelValue', 'update:focused', 'selectElement' ]);
|
|
17
17
|
const props = withDefaults(defineProps<{
|
|
18
|
-
modelValue: string | number | null;
|
|
18
|
+
modelValue: string | string[] | number | number[] | null;
|
|
19
|
+
focused: string | number | null;
|
|
19
20
|
open: boolean;
|
|
20
21
|
options: SelectOption[];
|
|
21
22
|
colorType?: InputColorType;
|
|
22
23
|
validator?: Validator;
|
|
23
24
|
size?: Size;
|
|
24
|
-
inputRef?: HTMLElement | null
|
|
25
|
+
inputRef?: HTMLElement | null;
|
|
26
|
+
closeOnEnter?: boolean;
|
|
27
|
+
autoSelectFirstOnOpen?: boolean;
|
|
25
28
|
}>(), {
|
|
26
29
|
colorType: InputColorType.base,
|
|
30
|
+
focusOnOpen: true,
|
|
31
|
+
closeOnEnter: false,
|
|
32
|
+
autoSelectFirstOnOpen: true
|
|
27
33
|
});
|
|
28
34
|
|
|
29
35
|
const _modelValue = useVModel(props, 'modelValue', emit);
|
|
30
36
|
const isOpen = useVModel(props, 'open', emit);
|
|
31
|
-
|
|
32
|
-
const focusedDropDownItem = ref<string | number | null>(null);
|
|
33
|
-
const dropDownRef = ref<HTMLElement | null>(null);
|
|
37
|
+
const focusedDropDownItem = useVModel(props, 'focused', emit);
|
|
34
38
|
|
|
35
39
|
const dropdownClasses = computed(() => {
|
|
36
40
|
const variants: Record<InputColorType, string> = {
|
|
@@ -68,28 +72,45 @@ const dropDownItemClasses = computed(() => {
|
|
|
68
72
|
};
|
|
69
73
|
});
|
|
70
74
|
|
|
71
|
-
watch(isOpen, (
|
|
75
|
+
watch(isOpen, () => {
|
|
72
76
|
nextTick(() => {
|
|
73
|
-
if (
|
|
74
|
-
|
|
75
|
-
|
|
77
|
+
if (props.autoSelectFirstOnOpen) {
|
|
78
|
+
focusedDropDownItem.value =
|
|
79
|
+
typeof _modelValue.value === 'string' || typeof _modelValue.value === 'number' ? _modelValue.value :
|
|
80
|
+
Array.isArray(_modelValue.value) ? _modelValue.value[0] :
|
|
81
|
+
props.options[0].value;
|
|
82
|
+
} else {
|
|
83
|
+
focusedDropDownItem.value = null;
|
|
76
84
|
}
|
|
77
85
|
});
|
|
78
86
|
});
|
|
79
87
|
|
|
88
|
+
onMounted(() => {
|
|
89
|
+
nextTick(() => {
|
|
90
|
+
props.inputRef?.addEventListener('keydown', onKeyDownDropDown);
|
|
91
|
+
});
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
onUnmounted(() => {
|
|
95
|
+
props.inputRef?.removeEventListener('keydown', onKeyDownDropDown);
|
|
96
|
+
});
|
|
97
|
+
|
|
80
98
|
function onKeyDownDropDown(e: KeyboardEvent) {
|
|
81
99
|
if (e.key === 'Enter') {
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
100
|
+
if (props.closeOnEnter) {
|
|
101
|
+
isOpen.value = false;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
emit('selectElement', focusedDropDownItem.value);
|
|
85
105
|
}
|
|
86
106
|
|
|
87
107
|
if (e.key === 'Escape') {
|
|
88
108
|
isOpen.value = false;
|
|
89
|
-
props.inputRef?.focus();
|
|
90
109
|
}
|
|
91
110
|
|
|
92
111
|
if (e.key === 'ArrowDown' || e.key === 'ArrowRight') {
|
|
112
|
+
isOpen.value = true;
|
|
113
|
+
|
|
93
114
|
const index = props.options.findIndex(option => option.value === focusedDropDownItem.value);
|
|
94
115
|
const option = props.options[index + 1];
|
|
95
116
|
|
|
@@ -101,6 +122,8 @@ function onKeyDownDropDown(e: KeyboardEvent) {
|
|
|
101
122
|
}
|
|
102
123
|
|
|
103
124
|
if (e.key === 'ArrowUp' || e.key === 'ArrowLeft') {
|
|
125
|
+
isOpen.value = true;
|
|
126
|
+
|
|
104
127
|
const index = props.options.findIndex(option => option.value === focusedDropDownItem.value);
|
|
105
128
|
const option = props.options[index - 1];
|
|
106
129
|
|
|
@@ -123,7 +146,7 @@ function getActiveDropDownItemClasses(option: SelectOption) {
|
|
|
123
146
|
[InputColorType.danger]: 'bg-danger-100/25',
|
|
124
147
|
};
|
|
125
148
|
|
|
126
|
-
return option.value === focusedDropDownItem.value ? {[variants[props.colorType]]: true} : {};
|
|
149
|
+
return option.value === focusedDropDownItem.value ? { [variants[props.colorType]]: true } : {};
|
|
127
150
|
}
|
|
128
151
|
|
|
129
152
|
function onClickDropDownItem(e: MouseEvent, value: string | number | null) {
|
|
@@ -131,17 +154,19 @@ function onClickDropDownItem(e: MouseEvent, value: string | number | null) {
|
|
|
131
154
|
props.inputRef?.focus();
|
|
132
155
|
|
|
133
156
|
isOpen.value = false;
|
|
157
|
+
emit('selectElement', value);
|
|
134
158
|
_modelValue.value = value;
|
|
135
159
|
}
|
|
160
|
+
|
|
161
|
+
watch(_modelValue, (val) => {
|
|
162
|
+
focusedDropDownItem.value = Array.isArray(val) ? val[0] : val;
|
|
163
|
+
});
|
|
136
164
|
</script>
|
|
137
165
|
|
|
138
166
|
<template>
|
|
139
167
|
<div
|
|
140
168
|
v-if="isOpen"
|
|
141
169
|
:class="dropdownClasses"
|
|
142
|
-
@keydown="onKeyDownDropDown"
|
|
143
|
-
ref="dropDownRef"
|
|
144
|
-
tabindex="0"
|
|
145
170
|
>
|
|
146
171
|
<div
|
|
147
172
|
v-for="(option, index) in options"
|
|
@@ -152,9 +177,14 @@ function onClickDropDownItem(e: MouseEvent, value: string | number | null) {
|
|
|
152
177
|
>
|
|
153
178
|
{{ option.label }}
|
|
154
179
|
</div>
|
|
180
|
+
|
|
181
|
+
<div
|
|
182
|
+
v-if="options.length === 0"
|
|
183
|
+
:class="{...dropDownItemClasses}"
|
|
184
|
+
>
|
|
185
|
+
<slot name="empty">
|
|
186
|
+
No options available
|
|
187
|
+
</slot>
|
|
188
|
+
</div>
|
|
155
189
|
</div>
|
|
156
190
|
</template>
|
|
157
|
-
|
|
158
|
-
<style scoped>
|
|
159
|
-
|
|
160
|
-
</style>
|
|
@@ -7,7 +7,7 @@ import {handleEnumValidation} from '../../../handler';
|
|
|
7
7
|
import AntInputLimiter from './AntInputLimiter.vue';
|
|
8
8
|
import {InputColorType} from '../../../enums';
|
|
9
9
|
|
|
10
|
-
|
|
10
|
+
defineEmits(['clickLabel', 'validate']);
|
|
11
11
|
const props = withDefaults(defineProps<{
|
|
12
12
|
label?: string;
|
|
13
13
|
description?: string;
|
|
@@ -25,7 +25,7 @@ const props = withDefaults(defineProps<{
|
|
|
25
25
|
skeleton: false,
|
|
26
26
|
size: Size.md,
|
|
27
27
|
showMessageOnError: true,
|
|
28
|
-
errors: [],
|
|
28
|
+
errors: () => [],
|
|
29
29
|
expanded: true
|
|
30
30
|
});
|
|
31
31
|
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
export var BaseInputType = /* @__PURE__ */ ((BaseInputType2) => {
|
|
2
2
|
BaseInputType2["date"] = "date";
|
|
3
3
|
BaseInputType2["datetime"] = "datetime";
|
|
4
|
+
BaseInputType2["datetimeLocal"] = "datetime-local";
|
|
4
5
|
BaseInputType2["email"] = "email";
|
|
5
6
|
BaseInputType2["hidden"] = "hidden";
|
|
6
7
|
BaseInputType2["month"] = "month";
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { AntTagInput } from '../index';
|
|
2
|
+
import type { Meta, StoryObj } from '@storybook/vue3';
|
|
3
|
+
declare const meta: Meta<typeof AntTagInput>;
|
|
4
|
+
export default meta;
|
|
5
|
+
type Story = StoryObj<typeof AntTagInput>;
|
|
6
|
+
export declare const Docs: Story;
|
|
7
|
+
export declare const AllowCreate: Story;
|
|
8
|
+
export declare const Disabled: Story;
|
|
9
|
+
export declare const Skeleton: Story;
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import { AntTagInput } from "../index.mjs";
|
|
2
|
+
import { ref } from "vue";
|
|
3
|
+
import { InputColorType, Size } from "../../../enums/index.mjs";
|
|
4
|
+
const meta = {
|
|
5
|
+
title: "Components/Forms/Tag input",
|
|
6
|
+
component: AntTagInput,
|
|
7
|
+
argTypes: {
|
|
8
|
+
modelValue: {
|
|
9
|
+
control: "text",
|
|
10
|
+
table: { type: { summary: "string|null" } }
|
|
11
|
+
},
|
|
12
|
+
colorType: {
|
|
13
|
+
control: { type: "select" },
|
|
14
|
+
options: Object.values(InputColorType)
|
|
15
|
+
},
|
|
16
|
+
size: {
|
|
17
|
+
control: { type: "radio" },
|
|
18
|
+
options: Object.values(Size),
|
|
19
|
+
table: { defaultValue: { summary: Size.md } }
|
|
20
|
+
},
|
|
21
|
+
placeholder: {
|
|
22
|
+
table: { defaultValue: { summary: "this.label" } }
|
|
23
|
+
},
|
|
24
|
+
createCallback: {
|
|
25
|
+
control: "none",
|
|
26
|
+
description: "If allowCreate is true the createCallback needs to be specified. It will be called when the user creates a new tag. It should return a promise that resolves to a SelectOption.",
|
|
27
|
+
table: {
|
|
28
|
+
type: {
|
|
29
|
+
summary: "(item: string) => Promise<SelectOption>",
|
|
30
|
+
detail: `
|
|
31
|
+
Params:
|
|
32
|
+
item: string - the label of the new tag
|
|
33
|
+
|
|
34
|
+
Returns:
|
|
35
|
+
Promise<SelectOption> - the new tag as a SelectOption
|
|
36
|
+
`
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
};
|
|
42
|
+
export default meta;
|
|
43
|
+
const options = [
|
|
44
|
+
{
|
|
45
|
+
label: "Tag",
|
|
46
|
+
value: "1"
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
label: "Cat",
|
|
50
|
+
value: "2"
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
label: "Dog",
|
|
54
|
+
value: "3"
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
label: "Chicken",
|
|
58
|
+
value: "4"
|
|
59
|
+
}
|
|
60
|
+
];
|
|
61
|
+
export const Docs = {
|
|
62
|
+
render: (args) => ({
|
|
63
|
+
components: { AntTagInput },
|
|
64
|
+
setup() {
|
|
65
|
+
const value = ref([]);
|
|
66
|
+
return {
|
|
67
|
+
args,
|
|
68
|
+
value
|
|
69
|
+
};
|
|
70
|
+
},
|
|
71
|
+
template: `
|
|
72
|
+
<div style="width: 360px">
|
|
73
|
+
<AntTagInput v-model="value" v-bind="args"/>
|
|
74
|
+
</div>
|
|
75
|
+
`
|
|
76
|
+
}),
|
|
77
|
+
args: {
|
|
78
|
+
options
|
|
79
|
+
}
|
|
80
|
+
};
|
|
81
|
+
export const AllowCreate = {
|
|
82
|
+
render: Docs.render,
|
|
83
|
+
args: {
|
|
84
|
+
options,
|
|
85
|
+
allowCreate: true,
|
|
86
|
+
createCallback(item) {
|
|
87
|
+
return new Promise((resolve) => {
|
|
88
|
+
resolve({ label: item, value: `${Math.random()}-${item}` });
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
};
|
|
93
|
+
export const Disabled = {
|
|
94
|
+
render: Docs.render,
|
|
95
|
+
args: {
|
|
96
|
+
options,
|
|
97
|
+
disabled: true
|
|
98
|
+
}
|
|
99
|
+
};
|
|
100
|
+
export const Skeleton = {
|
|
101
|
+
render: Docs.render,
|
|
102
|
+
args: {
|
|
103
|
+
options,
|
|
104
|
+
skeleton: true
|
|
105
|
+
}
|
|
106
|
+
};
|
|
@@ -5,7 +5,8 @@ import AntSwitcher from './AntSwitcher.vue';
|
|
|
5
5
|
import AntSearch from './AntSearch.vue';
|
|
6
6
|
import AntSelect from './AntSelect.vue';
|
|
7
7
|
import AntSwitch from './AntSwitch.vue';
|
|
8
|
+
import AntTagInput from './AntTagInput.vue';
|
|
8
9
|
import AntTextarea from './AntTextarea.vue';
|
|
9
10
|
import AntTextInput from './AntTextInput.vue';
|
|
10
11
|
import AntUnitInput from './AntUnitInput.vue';
|
|
11
|
-
export { AntButton, AntNumberInput, AntRangeSlider, AntSwitcher, AntSearch, AntSelect, AntSwitch, AntTextarea, AntTextInput, AntUnitInput, };
|
|
12
|
+
export { AntButton, AntNumberInput, AntRangeSlider, AntSwitcher, AntSearch, AntSelect, AntSwitch, AntTagInput, AntTextarea, AntTextInput, AntUnitInput, };
|
|
@@ -5,6 +5,7 @@ import AntSwitcher from "./AntSwitcher.vue";
|
|
|
5
5
|
import AntSearch from "./AntSearch.vue";
|
|
6
6
|
import AntSelect from "./AntSelect.vue";
|
|
7
7
|
import AntSwitch from "./AntSwitch.vue";
|
|
8
|
+
import AntTagInput from "./AntTagInput.vue";
|
|
8
9
|
import AntTextarea from "./AntTextarea.vue";
|
|
9
10
|
import AntTextInput from "./AntTextInput.vue";
|
|
10
11
|
import AntUnitInput from "./AntUnitInput.vue";
|
|
@@ -16,6 +17,7 @@ export {
|
|
|
16
17
|
AntSearch,
|
|
17
18
|
AntSelect,
|
|
18
19
|
AntSwitch,
|
|
20
|
+
AntTagInput,
|
|
19
21
|
AntTextarea,
|
|
20
22
|
AntTextInput,
|
|
21
23
|
AntUnitInput
|