@antify/ui-module 1.1.5 → 1.2.1
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 +8 -1
- 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/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 +6 -1
- 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/AntTagInput.vue +306 -0
- package/src/runtime/components/form/Elements/AntDropDown.vue +53 -23
- package/src/runtime/components/form/Elements/AntField.vue +2 -2
package/dist/module.d.mts
CHANGED
|
@@ -48,6 +48,7 @@ declare enum CollapseStrategy {
|
|
|
48
48
|
}
|
|
49
49
|
|
|
50
50
|
declare enum IconSize {
|
|
51
|
+
xs = "xs",
|
|
51
52
|
sm = "sm",
|
|
52
53
|
md = "md",
|
|
53
54
|
xl3 = "3xl"
|
|
@@ -111,6 +112,7 @@ declare enum ButtonType {
|
|
|
111
112
|
declare enum BaseInputType {
|
|
112
113
|
date = "date",
|
|
113
114
|
datetime = "datetime",
|
|
115
|
+
datetimeLocal = "datetime-local",
|
|
114
116
|
email = "email",
|
|
115
117
|
hidden = "hidden",
|
|
116
118
|
month = "month",
|
package/dist/module.d.ts
CHANGED
|
@@ -48,6 +48,7 @@ declare enum CollapseStrategy {
|
|
|
48
48
|
}
|
|
49
49
|
|
|
50
50
|
declare enum IconSize {
|
|
51
|
+
xs = "xs",
|
|
51
52
|
sm = "sm",
|
|
52
53
|
md = "md",
|
|
53
54
|
xl3 = "3xl"
|
|
@@ -111,6 +112,7 @@ declare enum ButtonType {
|
|
|
111
112
|
declare enum BaseInputType {
|
|
112
113
|
date = "date",
|
|
113
114
|
datetime = "datetime",
|
|
115
|
+
datetimeLocal = "datetime-local",
|
|
114
116
|
email = "email",
|
|
115
117
|
hidden = "hidden",
|
|
116
118
|
month = "month",
|
package/dist/module.json
CHANGED
package/dist/module.mjs
CHANGED
|
@@ -2,6 +2,7 @@ import defaultColors from 'tailwindcss/colors.js';
|
|
|
2
2
|
import { defineNuxtModule, createResolver, addPlugin, addImportsDir, addComponentsDir } from '@nuxt/kit';
|
|
3
3
|
|
|
4
4
|
const colors = {
|
|
5
|
+
"transparent": defaultColors.transparent,
|
|
5
6
|
"white": defaultColors.white,
|
|
6
7
|
"black": defaultColors.black,
|
|
7
8
|
"neutral-50": defaultColors.slate["50"],
|
|
@@ -168,12 +169,16 @@ const tailwindcss = {
|
|
|
168
169
|
// For module dev
|
|
169
170
|
"./src/runtime/**/*.{vue,js,ts,jsx,tsx}",
|
|
170
171
|
"./src/runtime/**/*.stories.ts",
|
|
171
|
-
"./playground
|
|
172
|
+
"./playground/app.vue",
|
|
173
|
+
"./playground/components/**/*.{vue,js,ts,jsx,tsx}",
|
|
174
|
+
"./playground/pages/**/*.{vue,js,ts,jsx,tsx}",
|
|
175
|
+
"./playground/layouts/**/*.{vue,js,ts,jsx,tsx}",
|
|
172
176
|
"../ui-module/src/**/*.{vue,js,ts,jsx,tsx}",
|
|
173
177
|
// For project dev
|
|
174
178
|
"./app.vue",
|
|
175
179
|
"./components/**/*.{vue,js,ts,jsx,tsx}",
|
|
176
180
|
"./pages/**/*.{vue,js,ts,jsx,tsx}",
|
|
181
|
+
"./layouts/**/*.{vue,js,ts,jsx,tsx}",
|
|
177
182
|
// If this config is used in a project
|
|
178
183
|
"./node_modules/@antify/*/dist/**/*.{js,vue,ts}",
|
|
179
184
|
"./node_modules/@antify/*/src/**/*.{js,vue,ts}"
|
|
@@ -252,6 +257,7 @@ var CollapseStrategy = /* @__PURE__ */ ((CollapseStrategy2) => {
|
|
|
252
257
|
})(CollapseStrategy || {});
|
|
253
258
|
|
|
254
259
|
var IconSize = /* @__PURE__ */ ((IconSize2) => {
|
|
260
|
+
IconSize2["xs"] = "xs";
|
|
255
261
|
IconSize2["sm"] = "sm";
|
|
256
262
|
IconSize2["md"] = "md";
|
|
257
263
|
IconSize2["xl3"] = "3xl";
|
|
@@ -296,6 +302,7 @@ var ButtonType = /* @__PURE__ */ ((ButtonType2) => {
|
|
|
296
302
|
var BaseInputType = /* @__PURE__ */ ((BaseInputType2) => {
|
|
297
303
|
BaseInputType2["date"] = "date";
|
|
298
304
|
BaseInputType2["datetime"] = "datetime";
|
|
305
|
+
BaseInputType2["datetimeLocal"] = "datetime-local";
|
|
299
306
|
BaseInputType2["email"] = "email";
|
|
300
307
|
BaseInputType2["hidden"] = "hidden";
|
|
301
308
|
BaseInputType2["month"] = "month";
|
|
@@ -62,5 +62,5 @@ function onClick() {
|
|
|
62
62
|
</template>
|
|
63
63
|
|
|
64
64
|
<style scoped>
|
|
65
|
-
.bounce-enter-active{animation:bounce .6s}.bounce-leave-active{animation:bounce .6s reverse}@keyframes bounce{0%{opacity:0;transform:translateY(-100%)}50%{opacity:1;transform:translateY(5%)}to{opacity:1;transform:translateY(0)}}.slide-enter-active{animation:slide 1s}.slide-leave-active{animation:slide 1s reverse}
|
|
65
|
+
.bounce-enter-active{animation:bounce .6s}.bounce-leave-active{animation:bounce .6s reverse}@keyframes bounce{0%{opacity:0;transform:translateY(-100%)}50%{opacity:1;transform:translateY(5%)}to{opacity:1;transform:translateY(0)}}.slide-enter-active{animation:slide 1s}.slide-leave-active{animation:slide 1s reverse}
|
|
66
66
|
</style>
|
|
@@ -6,6 +6,7 @@ import {handleEnumValidation} from '../handler';
|
|
|
6
6
|
import {type IconDefinition} from '@fortawesome/free-solid-svg-icons';
|
|
7
7
|
import {faCircleXmark} from '@fortawesome/free-solid-svg-icons';
|
|
8
8
|
|
|
9
|
+
defineEmits(['close']);
|
|
9
10
|
const props = withDefaults(defineProps<{
|
|
10
11
|
colorType?: TagColorType,
|
|
11
12
|
size?: Size;
|
|
@@ -13,7 +14,7 @@ const props = withDefaults(defineProps<{
|
|
|
13
14
|
dismiss?: boolean
|
|
14
15
|
}>(), {
|
|
15
16
|
size: Size.md,
|
|
16
|
-
colorType: TagColorType.
|
|
17
|
+
colorType: TagColorType.base,
|
|
17
18
|
dismiss: false
|
|
18
19
|
});
|
|
19
20
|
|
|
@@ -21,8 +22,7 @@ const classes = computed(() => {
|
|
|
21
22
|
const variants: Record<TagColorType, string> = {
|
|
22
23
|
[TagColorType.danger]: 'bg-danger-500 text-danger-500-font',
|
|
23
24
|
[TagColorType.info]: 'bg-info-500 text-info-500-font',
|
|
24
|
-
[TagColorType.
|
|
25
|
-
[TagColorType.neutral50]: 'bg-neutral-50 text-neutral-50-font',
|
|
25
|
+
[TagColorType.base]: 'bg-neutral-300 text-neutral-300-font',
|
|
26
26
|
[TagColorType.success]: 'bg-success-500 text-success-500-font',
|
|
27
27
|
[TagColorType.warning]: 'bg-warning-500 text-warning-500-font',
|
|
28
28
|
};
|
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import { Canvas, Meta, Story } from '@storybook/
|
|
1
|
+
import { Canvas, Meta, Story } from '@storybook/blocks';
|
|
2
|
+
import * as MainStories from './Main.stories';
|
|
2
3
|
|
|
3
|
-
<Meta
|
|
4
|
+
<Meta of={MainStories} />
|
|
4
5
|
|
|
5
6
|
<h1>This is the storybook for Antify UI.</h1>
|
|
6
7
|
|
|
@@ -15,15 +15,18 @@ const meta = {
|
|
|
15
15
|
};
|
|
16
16
|
export default meta;
|
|
17
17
|
export const Docs = {
|
|
18
|
+
parameters: {
|
|
19
|
+
chromatic: { disableSnapshot: false }
|
|
20
|
+
},
|
|
18
21
|
render: (args) => ({
|
|
19
22
|
components: { AntAccordion, AntAccordionItem },
|
|
20
23
|
setup() {
|
|
21
24
|
return { args };
|
|
22
25
|
},
|
|
23
26
|
template: `
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
+
<div class="p-4">
|
|
28
|
+
<AntAccordion v-bind="args"/>
|
|
29
|
+
</div>`
|
|
27
30
|
}),
|
|
28
31
|
args: {
|
|
29
32
|
items: [
|
|
@@ -53,13 +56,13 @@ export const CustomContent = {
|
|
|
53
56
|
return { args };
|
|
54
57
|
},
|
|
55
58
|
template: `
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
59
|
+
<div class="p-4">
|
|
60
|
+
<AntAccordion v-bind="args">
|
|
61
|
+
<template #item-content="{item, index}">
|
|
62
|
+
<div class="text-danger-500">{{ item.content }}</div>
|
|
63
|
+
</template>
|
|
64
|
+
</AntAccordion>
|
|
65
|
+
</div>`
|
|
63
66
|
}),
|
|
64
67
|
args: {
|
|
65
68
|
...Docs.args
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import AntKeycap from '../AntKeycap.vue';
|
|
2
|
-
import { type Meta, type StoryObj } from
|
|
2
|
+
import { type Meta, type StoryObj } from '@storybook/vue3';
|
|
3
3
|
declare const meta: Meta<typeof AntKeycap>;
|
|
4
4
|
export default meta;
|
|
5
5
|
type Story = StoryObj<typeof AntKeycap>;
|
|
@@ -36,24 +36,27 @@ export const Icon = {
|
|
|
36
36
|
}
|
|
37
37
|
};
|
|
38
38
|
export const Summary = {
|
|
39
|
+
parameters: {
|
|
40
|
+
chromatic: { disableSnapshot: false }
|
|
41
|
+
},
|
|
39
42
|
render: (args) => ({
|
|
40
43
|
components: { AntKeycap, faChevronUp, faChevronDown },
|
|
41
44
|
setup() {
|
|
42
45
|
return { args, faChevronUp, faChevronDown };
|
|
43
46
|
},
|
|
44
47
|
template: `
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
48
|
+
<div class="p-4">
|
|
49
|
+
Press
|
|
50
|
+
<AntKeycap v-bind="args" :icon="faChevronUp"/>
|
|
51
|
+
or
|
|
52
|
+
<AntKeycap v-bind="args" :icon="faChevronDown"/>
|
|
53
|
+
to navigate. Press
|
|
54
|
+
<AntKeycap v-bind="args">ctl</AntKeycap>
|
|
55
|
+
+
|
|
56
|
+
<AntKeycap v-bind="args">K</AntKeycap>
|
|
57
|
+
to search.
|
|
58
|
+
</div>
|
|
59
|
+
`
|
|
57
60
|
}),
|
|
58
61
|
args: {}
|
|
59
62
|
};
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
*/
|
|
15
15
|
import AntField from './Elements/AntField.vue';
|
|
16
16
|
import {type SelectOption} from './__types/AntSelect.type';
|
|
17
|
-
import {computed, onMounted, ref
|
|
17
|
+
import {computed, onMounted, ref} from 'vue';
|
|
18
18
|
import {Size} from '../../enums/Size.enum';
|
|
19
19
|
import {FieldValidator} from '@antify/validate';
|
|
20
20
|
import {handleEnumValidation} from '../../handler';
|
|
@@ -164,13 +164,6 @@ onMounted(() => {
|
|
|
164
164
|
|
|
165
165
|
focusedDropDownItem.value = _modelValue.value;
|
|
166
166
|
});
|
|
167
|
-
watch(isOpen, (val) => {
|
|
168
|
-
nextTick(() => {
|
|
169
|
-
if (val) {
|
|
170
|
-
dropDownRef.value?.focus();
|
|
171
|
-
}
|
|
172
|
-
});
|
|
173
|
-
});
|
|
174
167
|
|
|
175
168
|
function onClickOutside() {
|
|
176
169
|
if (!isOpen.value) {
|
|
@@ -181,47 +174,6 @@ function onClickOutside() {
|
|
|
181
174
|
inputRef.value?.focus();
|
|
182
175
|
}
|
|
183
176
|
|
|
184
|
-
function onKeyDownInput(e: KeyboardEvent) {
|
|
185
|
-
if (e.key === 'Enter') {
|
|
186
|
-
isOpen.value = true;
|
|
187
|
-
inputRef.value?.blur();
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
if (e.key === 'Escape') {
|
|
191
|
-
isOpen.value = false;
|
|
192
|
-
inputRef.value?.focus();
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
if (e.key === 'ArrowDown' || e.key === 'ArrowRight') {
|
|
196
|
-
const index = props.options.findIndex(option => option.value === _modelValue.value);
|
|
197
|
-
const option = props.options[index + 1];
|
|
198
|
-
|
|
199
|
-
if (index === -1) {
|
|
200
|
-
_modelValue.value = props.options[0].value;
|
|
201
|
-
} else if (option !== undefined) {
|
|
202
|
-
_modelValue.value = option.value;
|
|
203
|
-
}
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
if (e.key === 'ArrowUp' || e.key === 'ArrowLeft') {
|
|
207
|
-
const index = props.options.findIndex(option => option.value === _modelValue.value);
|
|
208
|
-
const option = props.options[index - 1];
|
|
209
|
-
|
|
210
|
-
if (option !== undefined) {
|
|
211
|
-
_modelValue.value = option.value;
|
|
212
|
-
}
|
|
213
|
-
}
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
function onFocusInInput() {
|
|
217
|
-
inputRef.value?.addEventListener('keydown', onKeyDownInput);
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
function onFocusOutInput(e: FocusEvent) {
|
|
221
|
-
e.preventDefault();
|
|
222
|
-
inputRef.value?.removeEventListener('keydown', onKeyDownInput);
|
|
223
|
-
}
|
|
224
|
-
|
|
225
177
|
function onClickSelectInput(e: MouseEvent) {
|
|
226
178
|
e.preventDefault();
|
|
227
179
|
|
|
@@ -280,9 +232,8 @@ function onClickRemoveButton() {
|
|
|
280
232
|
ref="inputRef"
|
|
281
233
|
:tabindex="disabled ? undefined : 0"
|
|
282
234
|
@mousedown="onClickSelectInput"
|
|
283
|
-
@focusin="onFocusInInput"
|
|
284
|
-
@focusout="onFocusOutInput"
|
|
285
235
|
v-bind="$attrs"
|
|
236
|
+
@click="inputRef?.focus()"
|
|
286
237
|
>
|
|
287
238
|
<div
|
|
288
239
|
v-if="_modelValue === null && placeholder !== undefined"
|
|
@@ -329,6 +280,8 @@ function onClickRemoveButton() {
|
|
|
329
280
|
:input-ref="inputRef"
|
|
330
281
|
:size="size"
|
|
331
282
|
:color-type="_colorType"
|
|
283
|
+
@select-element="(e) => _modelValue = e"
|
|
284
|
+
close-on-enter
|
|
332
285
|
/>
|
|
333
286
|
</div>
|
|
334
287
|
|
|
@@ -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>
|