sprintify-ui 0.0.41 → 0.0.42
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/sprintify-ui.es.js +6033 -5518
- package/dist/types/src/components/BaseAutocomplete.vue.d.ts +32 -12
- package/dist/types/src/components/BaseAutocompleteFetch.vue.d.ts +28 -28
- package/dist/types/src/components/BaseBelongsTo.vue.d.ts +35 -35
- package/dist/types/src/components/BaseButtonGroup.vue.d.ts +46 -8
- package/dist/types/src/components/BaseDatePicker.vue.d.ts +18 -9
- package/dist/types/src/components/BaseDateSelect.vue.d.ts +14 -5
- package/dist/types/src/components/BaseField.vue.d.ts +151 -0
- package/dist/types/src/components/BaseFieldI18n.vue.d.ts +93 -0
- package/dist/types/src/components/BaseForm.vue.d.ts +267 -0
- package/dist/types/src/components/BaseFormField.d.ts +81 -0
- package/dist/types/src/components/BaseHasMany.vue.d.ts +31 -31
- package/dist/types/src/components/BaseInput.vue.d.ts +1 -1
- package/dist/types/src/components/BaseInputError.vue.d.ts +48 -0
- package/dist/types/src/components/BaseInputPercent.vue.d.ts +1 -1
- package/dist/types/src/components/BaseLocaleForm.vue.d.ts +420 -0
- package/dist/types/src/components/BaseMediaLibrary.vue.d.ts +46 -24
- package/dist/types/src/components/BaseNumberForm.vue.d.ts +382 -0
- package/dist/types/src/components/BasePassword.vue.d.ts +10 -14
- package/dist/types/src/components/BasePasswordForm.vue.d.ts +365 -0
- package/dist/types/src/components/BaseRadioGroup.vue.d.ts +23 -4
- package/dist/types/src/components/BaseSelect.vue.d.ts +20 -1
- package/dist/types/src/components/BaseSwitch.vue.d.ts +155 -23
- package/dist/types/src/components/BaseTagAutocomplete.vue.d.ts +31 -12
- package/dist/types/src/components/BaseTagAutocompleteFetch.vue.d.ts +20 -20
- package/dist/types/src/components/BaseTextarea.vue.d.ts +9 -0
- package/dist/types/src/components/BaseTextareaAutoresize.vue.d.ts +18 -0
- package/dist/types/src/components/BaseTextareaForm.vue.d.ts +394 -0
- package/dist/types/src/components/index.d.ts +4 -1
- package/dist/types/src/composables/field.d.ts +17 -0
- package/dist/types/src/index.d.ts +3 -0
- package/dist/types/src/types/index.d.ts +11 -0
- package/package.json +4 -1
- package/src/components/BaseAutocomplete.stories.js +56 -51
- package/src/components/BaseAutocomplete.vue +25 -8
- package/src/components/BaseAutocompleteFetch.stories.js +67 -65
- package/src/components/BaseAutocompleteFetch.vue +9 -29
- package/src/components/BaseBelongsTo.stories.js +72 -82
- package/src/components/BaseBelongsTo.vue +10 -11
- package/src/components/BaseButtonGroup.stories.js +11 -10
- package/src/components/BaseButtonGroup.vue +22 -9
- package/src/components/BaseCharacterCounter.stories.js +1 -1
- package/src/components/BaseDatePicker.stories.js +13 -9
- package/src/components/BaseDatePicker.vue +25 -8
- package/src/components/BaseDateSelect.stories.js +15 -9
- package/src/components/BaseDateSelect.vue +20 -8
- package/src/components/BaseField.vue +109 -0
- package/src/components/BaseFieldI18n.stories.js +38 -0
- package/src/components/BaseFieldI18n.vue +162 -0
- package/src/components/BaseFileUploader.stories.js +3 -3
- package/src/components/BaseFileUploader.vue +3 -3
- package/src/components/BaseForm.vue +298 -0
- package/src/components/BaseFormField.ts +117 -0
- package/src/components/BaseHasMany.stories.js +25 -10
- package/src/components/BaseHasMany.vue +9 -9
- package/src/components/BaseInput.stories.js +27 -14
- package/src/components/BaseInput.vue +17 -8
- package/src/components/BaseInputError.vue +7 -0
- package/src/components/BaseInputPercent.stories.js +10 -3
- package/src/components/BaseInputPercent.vue +2 -1
- package/src/components/BaseLocaleForm.vue +142 -0
- package/src/components/BaseMediaLibrary.stories.js +7 -6
- package/src/components/BaseMediaLibrary.vue +32 -31
- package/src/components/BaseMenu.vue +1 -1
- package/src/components/BaseNumberForm.vue +67 -0
- package/src/components/BasePassword.stories.js +9 -4
- package/src/components/BasePassword.vue +49 -44
- package/src/components/BasePasswordForm.vue +59 -0
- package/src/components/BaseRadioGroup.stories.js +9 -8
- package/src/components/BaseRadioGroup.vue +17 -3
- package/src/components/BaseSelect.stories.js +15 -2
- package/src/components/BaseSelect.vue +26 -10
- package/src/components/BaseSwitch.stories.js +7 -0
- package/src/components/BaseSwitch.vue +134 -124
- package/src/components/BaseTagAutocomplete.stories.js +21 -14
- package/src/components/BaseTagAutocomplete.vue +25 -14
- package/src/components/BaseTagAutocompleteFetch.stories.js +37 -21
- package/src/components/BaseTagAutocompleteFetch.vue +5 -5
- package/src/components/BaseTextarea.stories.js +11 -3
- package/src/components/BaseTextarea.vue +20 -6
- package/src/components/BaseTextareaAutoresize.stories.js +11 -2
- package/src/components/BaseTextareaAutoresize.vue +28 -4
- package/src/components/BaseTextareaForm.vue +101 -0
- package/src/components/BaseTimeline.vue +1 -1
- package/src/components/BaseTimelineItem.vue +4 -4
- package/src/components/index.ts +6 -0
- package/src/composables/field.ts +100 -0
- package/src/index.ts +11 -1
- package/src/types/index.ts +12 -0
|
@@ -0,0 +1,298 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<form ref="form" class="relative" @submit.prevent="submit()">
|
|
3
|
+
<slot
|
|
4
|
+
:errors="errors"
|
|
5
|
+
:loading="loading"
|
|
6
|
+
:disabled="disabled"
|
|
7
|
+
:submit="submit"
|
|
8
|
+
/>
|
|
9
|
+
|
|
10
|
+
<transition
|
|
11
|
+
enter-active-class="transition duration-100 ease-out"
|
|
12
|
+
enter-from-class="opacity-0"
|
|
13
|
+
enter-to-class="opacity-100"
|
|
14
|
+
leave-active-class="transition duration-200 ease-in"
|
|
15
|
+
leave-from-class="opacity-100"
|
|
16
|
+
leave-to-class="opacity-0"
|
|
17
|
+
>
|
|
18
|
+
<slot v-if="loading" name="loading">
|
|
19
|
+
<div
|
|
20
|
+
class="absolute inset-0 flex h-full w-full items-center justify-center"
|
|
21
|
+
>
|
|
22
|
+
<div
|
|
23
|
+
class="absolute inset-0 h-full w-full opacity-80"
|
|
24
|
+
:class="loadingMaskClass"
|
|
25
|
+
/>
|
|
26
|
+
<svg
|
|
27
|
+
class="relative h-6 w-6 animate-spin text-blue-600"
|
|
28
|
+
viewBox="0 0 24 24"
|
|
29
|
+
>
|
|
30
|
+
<path
|
|
31
|
+
fill="currentColor"
|
|
32
|
+
d="M12,4V2A10,10 0 0,0 2,12H4A8,8 0 0,1 12,4Z"
|
|
33
|
+
/>
|
|
34
|
+
</svg>
|
|
35
|
+
</div>
|
|
36
|
+
</slot>
|
|
37
|
+
</transition>
|
|
38
|
+
</form>
|
|
39
|
+
</template>
|
|
40
|
+
|
|
41
|
+
<script lang="ts" setup>
|
|
42
|
+
import { PropType, Ref } from 'vue';
|
|
43
|
+
import { serialize } from 'object-to-formdata';
|
|
44
|
+
import { Method, DataFormat } from '@/types';
|
|
45
|
+
import { AxiosError, AxiosInstance, AxiosResponse } from 'axios';
|
|
46
|
+
import { config, useNotificationsStore } from '@/index';
|
|
47
|
+
import { isArray } from 'lodash';
|
|
48
|
+
|
|
49
|
+
const notifications = useNotificationsStore();
|
|
50
|
+
|
|
51
|
+
type NextFunction = () => void;
|
|
52
|
+
|
|
53
|
+
const props = defineProps({
|
|
54
|
+
url: {
|
|
55
|
+
required: true,
|
|
56
|
+
type: String,
|
|
57
|
+
},
|
|
58
|
+
method: {
|
|
59
|
+
required: true,
|
|
60
|
+
type: String as PropType<Method>,
|
|
61
|
+
validator: (value: string) => {
|
|
62
|
+
return Object.values(Method).includes(value as Method);
|
|
63
|
+
},
|
|
64
|
+
},
|
|
65
|
+
data: {
|
|
66
|
+
required: true,
|
|
67
|
+
type: Object as PropType<Record<string, any>>,
|
|
68
|
+
},
|
|
69
|
+
axiosInstance: {
|
|
70
|
+
default: null,
|
|
71
|
+
type: Object as PropType<AxiosInstance | null>,
|
|
72
|
+
},
|
|
73
|
+
format: {
|
|
74
|
+
type: String as PropType<DataFormat>,
|
|
75
|
+
default: DataFormat.json,
|
|
76
|
+
validator: (value: string) => {
|
|
77
|
+
return Object.values(DataFormat).includes(value as DataFormat);
|
|
78
|
+
},
|
|
79
|
+
},
|
|
80
|
+
beforeSubmit: {
|
|
81
|
+
default: (next: NextFunction) => {
|
|
82
|
+
next();
|
|
83
|
+
},
|
|
84
|
+
type: Function as PropType<(next: NextFunction) => void>,
|
|
85
|
+
},
|
|
86
|
+
successHandler: {
|
|
87
|
+
default: undefined,
|
|
88
|
+
type: Function as PropType<(response: any) => void>,
|
|
89
|
+
},
|
|
90
|
+
errorHandler: {
|
|
91
|
+
default: (error: AxiosError) => {
|
|
92
|
+
error;
|
|
93
|
+
},
|
|
94
|
+
type: Function as PropType<(error: AxiosError) => void>,
|
|
95
|
+
},
|
|
96
|
+
loadingMaskClass: {
|
|
97
|
+
default: 'bg-white',
|
|
98
|
+
type: String,
|
|
99
|
+
},
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
const i18n = useI18n();
|
|
103
|
+
const emit = defineEmits(['error', 'success']);
|
|
104
|
+
|
|
105
|
+
const form = ref(null) as Ref<null | HTMLFormElement>;
|
|
106
|
+
const loading = ref(false);
|
|
107
|
+
const disabled = ref(false);
|
|
108
|
+
const errors = ref({}) as Ref<Record<string, string[]>>;
|
|
109
|
+
|
|
110
|
+
const httpClient = computed((): AxiosInstance => {
|
|
111
|
+
if (props.axiosInstance) {
|
|
112
|
+
return props.axiosInstance;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
return config.http;
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
const htmlFormElement = computed((): HTMLFormElement | null => {
|
|
119
|
+
return form.value;
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
const hasErrors = computed((): boolean => {
|
|
123
|
+
return Object.keys(errors.value).length > 0;
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
const elementWithError = computed((): HTMLElement | null => {
|
|
127
|
+
if (!hasErrors.value) {
|
|
128
|
+
return null;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
const keys = Object.keys(errors.value);
|
|
132
|
+
|
|
133
|
+
for (let i = 0; i < keys.length; i++) {
|
|
134
|
+
const name = keys[i];
|
|
135
|
+
const element = findElementByName(name);
|
|
136
|
+
if (element) {
|
|
137
|
+
return element;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
return null;
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
function findElementByName(name: string): HTMLElement | null {
|
|
145
|
+
let el = htmlFormElement.value?.querySelector(`[name='${name}']`) as
|
|
146
|
+
| HTMLElement
|
|
147
|
+
| undefined;
|
|
148
|
+
if (el) {
|
|
149
|
+
return el;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
el = htmlFormElement.value?.querySelector(`[data-name='${name}']`) as
|
|
153
|
+
| HTMLElement
|
|
154
|
+
| undefined;
|
|
155
|
+
|
|
156
|
+
if (el) {
|
|
157
|
+
return el;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
return null;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
function submit() {
|
|
164
|
+
props.beforeSubmit(query);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
function query() {
|
|
168
|
+
if (loading.value) {
|
|
169
|
+
return;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
loading.value = true;
|
|
173
|
+
|
|
174
|
+
let method = props.method as Method;
|
|
175
|
+
let data = props.data;
|
|
176
|
+
let headers = { 'Content-Type': 'application/json' };
|
|
177
|
+
|
|
178
|
+
if (props.format == 'formData') {
|
|
179
|
+
method = Method.post;
|
|
180
|
+
|
|
181
|
+
data = serialize(props.data, {
|
|
182
|
+
nullsAsUndefineds: false,
|
|
183
|
+
booleansAsIntegers: true,
|
|
184
|
+
allowEmptyArrays: true,
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
if (props.method !== Method.post) {
|
|
188
|
+
data.append('_method', props.method);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
headers = {
|
|
192
|
+
'Content-Type': 'multipart/form-data',
|
|
193
|
+
};
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
httpClient.value[method](props.url, data, { headers: headers })
|
|
197
|
+
.then((response) => {
|
|
198
|
+
loading.value = false;
|
|
199
|
+
|
|
200
|
+
errors.value = {};
|
|
201
|
+
|
|
202
|
+
successHandler(response);
|
|
203
|
+
|
|
204
|
+
emit('success', response);
|
|
205
|
+
})
|
|
206
|
+
.catch((error: AxiosError<AxiosResponse<any>>) => {
|
|
207
|
+
console.error(error);
|
|
208
|
+
|
|
209
|
+
loading.value = false;
|
|
210
|
+
|
|
211
|
+
if (error.response && error.response.status == 422) {
|
|
212
|
+
loadErrors(error);
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
props.errorHandler(error);
|
|
216
|
+
|
|
217
|
+
emit('error', error);
|
|
218
|
+
});
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
function successHandler(response: AxiosResponse<any, any>) {
|
|
222
|
+
if (props.successHandler) {
|
|
223
|
+
props.successHandler(response);
|
|
224
|
+
} else {
|
|
225
|
+
const message = response.data.message ?? ('' as string);
|
|
226
|
+
|
|
227
|
+
if (!message) {
|
|
228
|
+
return;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
notifications.push({
|
|
232
|
+
color: 'success',
|
|
233
|
+
title: i18n.t('sui.success'),
|
|
234
|
+
text: message,
|
|
235
|
+
});
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
function loadErrors(error: AxiosError): void {
|
|
240
|
+
errors.value = error?.response?.data.errors ?? {};
|
|
241
|
+
|
|
242
|
+
if (elementWithError.value) {
|
|
243
|
+
elementWithError.value.scrollIntoView({
|
|
244
|
+
behavior: 'smooth',
|
|
245
|
+
block: 'center',
|
|
246
|
+
});
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
function getErrorMessageByName(name: string): string | null {
|
|
251
|
+
if (!errors.value[name]) {
|
|
252
|
+
return null;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
if (!isArray(errors.value[name])) {
|
|
256
|
+
return null;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
if (errors.value[name].length == 0) {
|
|
260
|
+
return null;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
return errors.value[name][0];
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
function clearErrors(name = null): void {
|
|
267
|
+
if (name == null) {
|
|
268
|
+
errors.value = {};
|
|
269
|
+
} else {
|
|
270
|
+
delete errors.value[name];
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
function disabledForm() {
|
|
275
|
+
disabled.value = true;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
function enableForm() {
|
|
279
|
+
disabled.value = false;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
provide('form:errors', readonly(errors));
|
|
283
|
+
provide('form:getErrorMessageByName', getErrorMessageByName);
|
|
284
|
+
provide('form:clearErrors', clearErrors);
|
|
285
|
+
|
|
286
|
+
provide('form:disabled', readonly(disabled));
|
|
287
|
+
provide('form:enable', enableForm);
|
|
288
|
+
provide('form:disable', disabledForm);
|
|
289
|
+
|
|
290
|
+
defineExpose({
|
|
291
|
+
submit,
|
|
292
|
+
errors,
|
|
293
|
+
hasErrors,
|
|
294
|
+
clearErrors,
|
|
295
|
+
disabled,
|
|
296
|
+
loading,
|
|
297
|
+
});
|
|
298
|
+
</script>
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import { defineComponent } from 'vue';
|
|
2
|
+
import BaseForm from './BaseForm.vue';
|
|
3
|
+
|
|
4
|
+
export default defineComponent({
|
|
5
|
+
props: {
|
|
6
|
+
name: {
|
|
7
|
+
required: true,
|
|
8
|
+
type: String,
|
|
9
|
+
},
|
|
10
|
+
placeholder: {
|
|
11
|
+
default: '',
|
|
12
|
+
type: String,
|
|
13
|
+
},
|
|
14
|
+
label: {
|
|
15
|
+
default: '',
|
|
16
|
+
type: String,
|
|
17
|
+
},
|
|
18
|
+
disabled: {
|
|
19
|
+
type: Boolean,
|
|
20
|
+
default: false,
|
|
21
|
+
},
|
|
22
|
+
required: {
|
|
23
|
+
default: false,
|
|
24
|
+
type: Boolean,
|
|
25
|
+
},
|
|
26
|
+
autofocus: {
|
|
27
|
+
default: false,
|
|
28
|
+
type: Boolean,
|
|
29
|
+
},
|
|
30
|
+
preventSubmit: {
|
|
31
|
+
default: false,
|
|
32
|
+
type: Boolean,
|
|
33
|
+
},
|
|
34
|
+
},
|
|
35
|
+
emits: ['update:modelValue'],
|
|
36
|
+
computed: {
|
|
37
|
+
form(): typeof BaseForm | null {
|
|
38
|
+
let parent = this.$parent;
|
|
39
|
+
for (let i = 0; i < 20; i++) {
|
|
40
|
+
if (parent == null) {
|
|
41
|
+
throw new Error(
|
|
42
|
+
'A BaseForm field component must be in a BaseForm component'
|
|
43
|
+
);
|
|
44
|
+
}
|
|
45
|
+
if (parent.$options.__name == 'BaseForm') {
|
|
46
|
+
return parent as unknown as typeof BaseForm;
|
|
47
|
+
}
|
|
48
|
+
parent = parent.$parent;
|
|
49
|
+
}
|
|
50
|
+
return null;
|
|
51
|
+
},
|
|
52
|
+
errors() {
|
|
53
|
+
if (!this.form) {
|
|
54
|
+
return {};
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
return this.form.errors;
|
|
58
|
+
},
|
|
59
|
+
labelValue(): string {
|
|
60
|
+
if (this.label != '' && this.label != null && this.label != undefined) {
|
|
61
|
+
return this.label;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
if (this.label === '') {
|
|
65
|
+
return '';
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
if (this.$te(this.name)) {
|
|
69
|
+
return this.$t(this.name);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
return this.name;
|
|
73
|
+
},
|
|
74
|
+
},
|
|
75
|
+
methods: {
|
|
76
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
77
|
+
inputListener(payload: any) {
|
|
78
|
+
this.clearErrors();
|
|
79
|
+
this.$emit('update:modelValue', payload);
|
|
80
|
+
},
|
|
81
|
+
errorMessage(name: string | null = null) {
|
|
82
|
+
if (name == null) {
|
|
83
|
+
name = this.name;
|
|
84
|
+
}
|
|
85
|
+
if (this.hasError(name)) {
|
|
86
|
+
return this.errors[name][0];
|
|
87
|
+
}
|
|
88
|
+
return null;
|
|
89
|
+
},
|
|
90
|
+
hasError(name: string | null = null) {
|
|
91
|
+
if (name == null) {
|
|
92
|
+
name = this.name;
|
|
93
|
+
}
|
|
94
|
+
return (
|
|
95
|
+
Object.keys(this.errors).length > 0 &&
|
|
96
|
+
Object.prototype.hasOwnProperty.call(this.errors, name) &&
|
|
97
|
+
this.errors[name].length > 0
|
|
98
|
+
);
|
|
99
|
+
},
|
|
100
|
+
clearErrors(name: string | null = null) {
|
|
101
|
+
if (name == null) {
|
|
102
|
+
name = this.name;
|
|
103
|
+
}
|
|
104
|
+
this.form?.clearErrors(name);
|
|
105
|
+
},
|
|
106
|
+
disableForm() {
|
|
107
|
+
if (this.form) {
|
|
108
|
+
this.form.disabled = true;
|
|
109
|
+
}
|
|
110
|
+
},
|
|
111
|
+
enableForm() {
|
|
112
|
+
if (this.form) {
|
|
113
|
+
this.form.disabled = false;
|
|
114
|
+
}
|
|
115
|
+
},
|
|
116
|
+
},
|
|
117
|
+
});
|
|
@@ -1,4 +1,7 @@
|
|
|
1
1
|
import BaseHasMany from './BaseHasMany.vue';
|
|
2
|
+
import ShowValue from '@/../.storybook/components/ShowValue.vue';
|
|
3
|
+
import { createFieldStory, options } from '../../.storybook/utils';
|
|
4
|
+
import BaseAppNotifications from './BaseAppNotifications.vue';
|
|
2
5
|
|
|
3
6
|
export default {
|
|
4
7
|
title: 'Form/BaseHasMany',
|
|
@@ -7,14 +10,14 @@ export default {
|
|
|
7
10
|
args: {
|
|
8
11
|
url: 'https://effettandem.com/api/content/articles',
|
|
9
12
|
field: 'title',
|
|
10
|
-
|
|
13
|
+
primaryKey: 'id',
|
|
11
14
|
},
|
|
12
15
|
decorators: [() => ({ template: '<div class="mb-36"><story/></div>' })],
|
|
13
16
|
};
|
|
14
17
|
|
|
15
18
|
const Template = (args) => {
|
|
16
19
|
return {
|
|
17
|
-
components: { BaseHasMany },
|
|
20
|
+
components: { BaseHasMany, ShowValue, BaseAppNotifications },
|
|
18
21
|
setup() {
|
|
19
22
|
const value = ref([]);
|
|
20
23
|
return { args, value };
|
|
@@ -24,7 +27,8 @@ const Template = (args) => {
|
|
|
24
27
|
v-model="value"
|
|
25
28
|
v-bind="args"
|
|
26
29
|
></BaseHasMany>
|
|
27
|
-
<
|
|
30
|
+
<ShowValue :value="value" />
|
|
31
|
+
<BaseAppNotifications />
|
|
28
32
|
`,
|
|
29
33
|
};
|
|
30
34
|
};
|
|
@@ -37,13 +41,16 @@ export const Disabled = (args) => {
|
|
|
37
41
|
components: { BaseHasMany },
|
|
38
42
|
setup() {
|
|
39
43
|
const value = ref([]);
|
|
40
|
-
|
|
44
|
+
const currentModel = options[0];
|
|
45
|
+
return { args, value, currentModel };
|
|
41
46
|
},
|
|
42
47
|
template: `<BaseHasMany
|
|
43
48
|
v-bind="args"
|
|
44
49
|
v-model="value"
|
|
45
|
-
:current-models="[
|
|
50
|
+
:current-models="[currentModel]"
|
|
46
51
|
:disabled="true"
|
|
52
|
+
primaryKey="value"
|
|
53
|
+
field="label"
|
|
47
54
|
></BaseHasMany>`,
|
|
48
55
|
};
|
|
49
56
|
};
|
|
@@ -55,7 +62,7 @@ Maximum.args = {
|
|
|
55
62
|
|
|
56
63
|
export const SlotOption = (args) => {
|
|
57
64
|
return {
|
|
58
|
-
components: {
|
|
65
|
+
components: {},
|
|
59
66
|
setup() {
|
|
60
67
|
const value = ref([]);
|
|
61
68
|
return { args, value };
|
|
@@ -88,11 +95,13 @@ export const SlotOption = (args) => {
|
|
|
88
95
|
|
|
89
96
|
export const SlotFooter = (args) => {
|
|
90
97
|
return {
|
|
91
|
-
components: {
|
|
98
|
+
components: {},
|
|
92
99
|
setup() {
|
|
93
100
|
const value = ref([]);
|
|
94
101
|
function onClick() {
|
|
95
|
-
|
|
102
|
+
setTimeout(() => {
|
|
103
|
+
alert(1);
|
|
104
|
+
}, 300);
|
|
96
105
|
}
|
|
97
106
|
return { args, value, onClick };
|
|
98
107
|
},
|
|
@@ -103,7 +112,7 @@ export const SlotFooter = (args) => {
|
|
|
103
112
|
>
|
|
104
113
|
<template #footer>
|
|
105
114
|
<div class="text-center p-2 border-t">
|
|
106
|
-
<button @click=onClick class="btn btn-sm w-full btn-slate-200-outline">This is the footer 💯</button>
|
|
115
|
+
<button type="button" @click=onClick class="btn btn-sm w-full btn-slate-200-outline">This is the footer 💯</button>
|
|
107
116
|
</div>
|
|
108
117
|
</template>
|
|
109
118
|
</BaseHasMany>
|
|
@@ -113,7 +122,7 @@ export const SlotFooter = (args) => {
|
|
|
113
122
|
|
|
114
123
|
export const SlotEmpty = (args) => {
|
|
115
124
|
return {
|
|
116
|
-
components: {
|
|
125
|
+
components: {},
|
|
117
126
|
setup() {
|
|
118
127
|
const value = ref([]);
|
|
119
128
|
return { args, value };
|
|
@@ -133,3 +142,9 @@ export const SlotEmpty = (args) => {
|
|
|
133
142
|
`,
|
|
134
143
|
};
|
|
135
144
|
};
|
|
145
|
+
|
|
146
|
+
export const Field = createFieldStory({
|
|
147
|
+
component: BaseHasMany,
|
|
148
|
+
componentName: 'BaseHasMany',
|
|
149
|
+
label: 'Name',
|
|
150
|
+
});
|
|
@@ -5,9 +5,9 @@
|
|
|
5
5
|
:disabled="disabled"
|
|
6
6
|
:placeholder="placeholder"
|
|
7
7
|
:required="required"
|
|
8
|
-
:value-key="
|
|
8
|
+
:value-key="primaryKey"
|
|
9
9
|
:label-key="field"
|
|
10
|
-
:
|
|
10
|
+
:has-error="hasError"
|
|
11
11
|
:query-key="queryKey"
|
|
12
12
|
:max="max"
|
|
13
13
|
@update:model-value="onUpdate"
|
|
@@ -32,13 +32,13 @@ import BaseTagAutocompleteFetch from './BaseTagAutocompleteFetch.vue';
|
|
|
32
32
|
const props = defineProps({
|
|
33
33
|
modelValue: {
|
|
34
34
|
required: true,
|
|
35
|
-
type: Array as PropType<Option[]>,
|
|
35
|
+
type: [Array, null] as PropType<Option[] | null>,
|
|
36
36
|
},
|
|
37
37
|
url: {
|
|
38
38
|
required: true,
|
|
39
39
|
type: String,
|
|
40
40
|
},
|
|
41
|
-
|
|
41
|
+
primaryKey: {
|
|
42
42
|
default: 'id',
|
|
43
43
|
type: String,
|
|
44
44
|
},
|
|
@@ -58,10 +58,6 @@ const props = defineProps({
|
|
|
58
58
|
default: undefined,
|
|
59
59
|
type: String,
|
|
60
60
|
},
|
|
61
|
-
inputClass: {
|
|
62
|
-
default: undefined,
|
|
63
|
-
type: String,
|
|
64
|
-
},
|
|
65
61
|
max: {
|
|
66
62
|
default: undefined,
|
|
67
63
|
type: Number,
|
|
@@ -76,6 +72,10 @@ const props = defineProps({
|
|
|
76
72
|
},
|
|
77
73
|
type: Array as PropType<Option[]>,
|
|
78
74
|
},
|
|
75
|
+
hasError: {
|
|
76
|
+
default: false,
|
|
77
|
+
type: Boolean,
|
|
78
|
+
},
|
|
79
79
|
});
|
|
80
80
|
|
|
81
81
|
const emit = defineEmits(['update:modelValue']);
|
|
@@ -94,7 +94,7 @@ function onUpdate(newModels: Option[]) {
|
|
|
94
94
|
models.value = newModels;
|
|
95
95
|
emit(
|
|
96
96
|
'update:modelValue',
|
|
97
|
-
newModels.map((m) => m[props.
|
|
97
|
+
newModels.map((m) => m[props.primaryKey])
|
|
98
98
|
);
|
|
99
99
|
}
|
|
100
100
|
</script>
|
|
@@ -1,34 +1,38 @@
|
|
|
1
1
|
import BaseInput from './BaseInput.vue';
|
|
2
|
+
import ShowValue from '@/../.storybook/components/ShowValue.vue';
|
|
3
|
+
import { createFieldStory } from '@/../.storybook/utils';
|
|
2
4
|
|
|
3
5
|
export default {
|
|
4
6
|
title: 'Form/BaseInput',
|
|
5
7
|
component: BaseInput,
|
|
8
|
+
decorators: [
|
|
9
|
+
(story) => ({
|
|
10
|
+
components: { story },
|
|
11
|
+
template: `
|
|
12
|
+
<form @submit.prevent="" class="border-none">
|
|
13
|
+
<story/>
|
|
14
|
+
</form>`,
|
|
15
|
+
}),
|
|
16
|
+
],
|
|
6
17
|
args: {
|
|
7
|
-
required: true,
|
|
8
18
|
type: 'text',
|
|
9
|
-
|
|
19
|
+
placeholder: 'Enter your name',
|
|
10
20
|
},
|
|
11
21
|
};
|
|
12
22
|
|
|
13
23
|
const Template = (args) => ({
|
|
14
|
-
components: {
|
|
15
|
-
BaseInput,
|
|
16
|
-
},
|
|
24
|
+
components: { BaseInput, ShowValue },
|
|
17
25
|
setup() {
|
|
18
|
-
const value = ref(
|
|
26
|
+
const value = ref(null);
|
|
19
27
|
return { args, value };
|
|
20
28
|
},
|
|
21
29
|
template: `
|
|
22
|
-
|
|
23
|
-
<
|
|
24
|
-
</form>
|
|
30
|
+
<BaseInput v-model="value" v-bind="args" class="w-full"></BaseInput>
|
|
31
|
+
<ShowValue :value="value" />
|
|
25
32
|
`,
|
|
26
33
|
});
|
|
27
34
|
|
|
28
35
|
export const Demo = Template.bind({});
|
|
29
|
-
Demo.args = {
|
|
30
|
-
placeholder: 'Enter your name',
|
|
31
|
-
};
|
|
32
36
|
|
|
33
37
|
export const IconLeft = Template.bind({});
|
|
34
38
|
IconLeft.args = {
|
|
@@ -74,7 +78,11 @@ export const Disabled = Template.bind({});
|
|
|
74
78
|
Disabled.args = {
|
|
75
79
|
modelValue: 'Disabled input!',
|
|
76
80
|
disabled: true,
|
|
77
|
-
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
export const Required = Template.bind({});
|
|
84
|
+
Required.args = {
|
|
85
|
+
required: true,
|
|
78
86
|
};
|
|
79
87
|
|
|
80
88
|
export const Error = Template.bind({});
|
|
@@ -82,5 +90,10 @@ Error.args = {
|
|
|
82
90
|
hasError: true,
|
|
83
91
|
prefix: 'Price',
|
|
84
92
|
iconRight: 'heroicons:currency-dollar',
|
|
85
|
-
placeholder: 'Enter your name',
|
|
86
93
|
};
|
|
94
|
+
|
|
95
|
+
export const Field = createFieldStory({
|
|
96
|
+
component: BaseInput,
|
|
97
|
+
componentName: 'BaseInput',
|
|
98
|
+
label: 'Name',
|
|
99
|
+
});
|