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
|
@@ -32,12 +32,13 @@ import { PropType } from 'vue';
|
|
|
32
32
|
import { NormalizedOption, Option } from '@/types';
|
|
33
33
|
import { cloneDeep, isArray } from 'lodash';
|
|
34
34
|
import { useHasOptions } from '@/composables/hasOptions';
|
|
35
|
+
import { useField } from '@/composables/field';
|
|
35
36
|
|
|
36
37
|
const props = defineProps({
|
|
37
38
|
modelValue: {
|
|
38
39
|
default: undefined,
|
|
39
|
-
type: [
|
|
40
|
-
Option[] | Option | undefined
|
|
40
|
+
type: [Object, Array, null, undefined] as PropType<
|
|
41
|
+
Option[] | Option | null | undefined
|
|
41
42
|
>,
|
|
42
43
|
},
|
|
43
44
|
required: {
|
|
@@ -84,10 +85,25 @@ const props = defineProps({
|
|
|
84
85
|
default: false,
|
|
85
86
|
type: Boolean,
|
|
86
87
|
},
|
|
88
|
+
name: {
|
|
89
|
+
default: undefined,
|
|
90
|
+
type: String,
|
|
91
|
+
},
|
|
92
|
+
hasError: {
|
|
93
|
+
default: false,
|
|
94
|
+
type: Boolean,
|
|
95
|
+
},
|
|
87
96
|
});
|
|
88
97
|
|
|
89
98
|
const emit = defineEmits(['update:modelValue']);
|
|
90
99
|
|
|
100
|
+
const { requiredInternal, emitUpdate } = useField({
|
|
101
|
+
name: computed(() => props.name),
|
|
102
|
+
required: computed(() => props.required),
|
|
103
|
+
hasError: computed(() => props.hasError),
|
|
104
|
+
emit: emit,
|
|
105
|
+
});
|
|
106
|
+
|
|
91
107
|
const { normalizedOptions, normalizedModelValue, isSelected } = useHasOptions(
|
|
92
108
|
computed(() => props.modelValue),
|
|
93
109
|
computed(() => props.options),
|
|
@@ -112,23 +128,20 @@ function onSelect(option: NormalizedOption) {
|
|
|
112
128
|
newModalValue.push(option);
|
|
113
129
|
}
|
|
114
130
|
|
|
115
|
-
|
|
116
|
-
'update:modelValue',
|
|
117
|
-
newModalValue.map((o) => o.option)
|
|
118
|
-
);
|
|
131
|
+
emitUpdate(newModalValue.map((o) => o.option));
|
|
119
132
|
} else {
|
|
120
|
-
if (!
|
|
133
|
+
if (!requiredInternal.value) {
|
|
121
134
|
if (
|
|
122
135
|
!isArray(normalizedModelValue.value) &&
|
|
123
136
|
option.value == normalizedModelValue.value?.value
|
|
124
137
|
) {
|
|
125
|
-
|
|
138
|
+
emitUpdate(null);
|
|
126
139
|
return;
|
|
127
140
|
}
|
|
128
141
|
}
|
|
129
142
|
|
|
130
143
|
const newOption = option.option;
|
|
131
|
-
|
|
144
|
+
emitUpdate(newOption);
|
|
132
145
|
}
|
|
133
146
|
}
|
|
134
147
|
</script>
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import BaseDatePicker from './BaseDatePicker.vue';
|
|
2
|
+
import ShowValue from '@/../.storybook/components/ShowValue.vue';
|
|
2
3
|
import { DateTime } from 'luxon';
|
|
4
|
+
import { createFieldStory } from '../../.storybook/utils';
|
|
3
5
|
|
|
4
6
|
export default {
|
|
5
7
|
title: 'Form/BaseDatePicker',
|
|
@@ -8,36 +10,32 @@ export default {
|
|
|
8
10
|
};
|
|
9
11
|
|
|
10
12
|
const Template = (args) => ({
|
|
11
|
-
components: { BaseDatePicker },
|
|
13
|
+
components: { BaseDatePicker, ShowValue },
|
|
12
14
|
setup() {
|
|
13
|
-
|
|
15
|
+
const value = ref(null);
|
|
16
|
+
return { value, args };
|
|
14
17
|
},
|
|
15
18
|
template: `
|
|
16
|
-
<BaseDatePicker v-bind="args">
|
|
19
|
+
<BaseDatePicker v-model="value" v-bind="args">
|
|
17
20
|
</BaseDatePicker>
|
|
21
|
+
<ShowValue :value="value" />
|
|
18
22
|
`,
|
|
19
23
|
});
|
|
20
24
|
|
|
21
25
|
export const Demo = Template.bind({});
|
|
22
|
-
Demo.args = {
|
|
23
|
-
modelValue: '2023-01-01',
|
|
24
|
-
};
|
|
25
26
|
|
|
26
27
|
export const YearRange = Template.bind({});
|
|
27
28
|
YearRange.args = {
|
|
28
|
-
modelValue: '1980-11-16',
|
|
29
29
|
yearRange: [1920, 2020],
|
|
30
30
|
};
|
|
31
31
|
|
|
32
32
|
export const MinDate = Template.bind({});
|
|
33
33
|
MinDate.args = {
|
|
34
|
-
modelValue: '2022-11-16',
|
|
35
34
|
minDate: DateTime.fromISO('2022-11-10').toJSDate(),
|
|
36
35
|
};
|
|
37
36
|
|
|
38
37
|
export const MaxDate = Template.bind({});
|
|
39
38
|
MaxDate.args = {
|
|
40
|
-
modelValue: '2022-11-16',
|
|
41
39
|
maxDate: DateTime.fromISO('2022-11-20').toJSDate(),
|
|
42
40
|
};
|
|
43
41
|
|
|
@@ -46,3 +44,9 @@ Disabled.args = {
|
|
|
46
44
|
modelValue: '2022-11-16',
|
|
47
45
|
disabled: true,
|
|
48
46
|
};
|
|
47
|
+
|
|
48
|
+
export const Field = createFieldStory({
|
|
49
|
+
component: BaseDatePicker,
|
|
50
|
+
componentName: 'BaseDatePicker',
|
|
51
|
+
label: 'Date',
|
|
52
|
+
});
|
|
@@ -14,8 +14,8 @@
|
|
|
14
14
|
type="text"
|
|
15
15
|
readonly
|
|
16
16
|
:disabled="disabled"
|
|
17
|
-
class="w-full rounded pl-10 pr-16 disabled:cursor-not-allowed disabled:text-
|
|
18
|
-
:class="
|
|
17
|
+
class="w-full rounded pl-10 pr-16 disabled:cursor-not-allowed disabled:text-slate-300"
|
|
18
|
+
:class="[hasErrorInternal ? 'border-red-500' : 'border-slate-300']"
|
|
19
19
|
:placeholder="$t('sui.click_or_select_date')"
|
|
20
20
|
/>
|
|
21
21
|
<div
|
|
@@ -38,6 +38,8 @@ import { PropType, Ref } from 'vue';
|
|
|
38
38
|
import Pikaday from 'pikaday';
|
|
39
39
|
import { capitalize, padStart } from 'lodash';
|
|
40
40
|
import { DateTime, Info } from 'luxon';
|
|
41
|
+
import { BaseIcon } from '.';
|
|
42
|
+
import { useField } from '@/composables/field';
|
|
41
43
|
|
|
42
44
|
const props = defineProps({
|
|
43
45
|
modelValue: {
|
|
@@ -52,10 +54,6 @@ const props = defineProps({
|
|
|
52
54
|
default: false,
|
|
53
55
|
type: Boolean,
|
|
54
56
|
},
|
|
55
|
-
inputClass: {
|
|
56
|
-
default: 'border-slate-300',
|
|
57
|
-
type: String,
|
|
58
|
-
},
|
|
59
57
|
minDate: {
|
|
60
58
|
default: undefined,
|
|
61
59
|
type: Date,
|
|
@@ -68,10 +66,25 @@ const props = defineProps({
|
|
|
68
66
|
default: undefined,
|
|
69
67
|
type: [Number, Array] as PropType<number | [number, number]>,
|
|
70
68
|
},
|
|
69
|
+
hasError: {
|
|
70
|
+
default: false,
|
|
71
|
+
type: Boolean,
|
|
72
|
+
},
|
|
73
|
+
name: {
|
|
74
|
+
default: undefined,
|
|
75
|
+
type: String,
|
|
76
|
+
},
|
|
71
77
|
});
|
|
72
78
|
|
|
73
79
|
const emit = defineEmits(['update:modelValue']);
|
|
74
80
|
|
|
81
|
+
const { hasErrorInternal, emitUpdate } = useField({
|
|
82
|
+
name: computed(() => props.name),
|
|
83
|
+
required: computed(() => props.required),
|
|
84
|
+
hasError: computed(() => props.hasError),
|
|
85
|
+
emit: emit,
|
|
86
|
+
});
|
|
87
|
+
|
|
75
88
|
const i18n = useI18n();
|
|
76
89
|
|
|
77
90
|
const datepicker = ref(null) as Ref<HTMLInputElement | null>;
|
|
@@ -118,6 +131,10 @@ onMounted(() => {
|
|
|
118
131
|
return `${year}-${month}-${day}`;
|
|
119
132
|
},
|
|
120
133
|
parse(dateString) {
|
|
134
|
+
if (!dateString) {
|
|
135
|
+
return null;
|
|
136
|
+
}
|
|
137
|
+
|
|
121
138
|
const datetime = DateTime.fromISO(dateString);
|
|
122
139
|
const year = datetime.year;
|
|
123
140
|
const month = datetime.month - 1;
|
|
@@ -140,7 +157,7 @@ onMounted(() => {
|
|
|
140
157
|
return;
|
|
141
158
|
}
|
|
142
159
|
|
|
143
|
-
|
|
160
|
+
emitUpdate(datetime.toISODate());
|
|
144
161
|
},
|
|
145
162
|
});
|
|
146
163
|
});
|
|
@@ -153,7 +170,7 @@ onBeforeUnmount(() => {
|
|
|
153
170
|
|
|
154
171
|
function clear() {
|
|
155
172
|
picker?.clear();
|
|
156
|
-
|
|
173
|
+
emitUpdate(null);
|
|
157
174
|
}
|
|
158
175
|
|
|
159
176
|
// https://stackoverflow.com/a/23368052
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import BaseDateSelect from './BaseDateSelect.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/BaseDateSelect',
|
|
@@ -7,30 +9,28 @@ export default {
|
|
|
7
9
|
};
|
|
8
10
|
|
|
9
11
|
const Template = (args) => ({
|
|
10
|
-
components: { BaseDateSelect },
|
|
12
|
+
components: { BaseDateSelect, ShowValue },
|
|
11
13
|
setup() {
|
|
12
|
-
|
|
14
|
+
const value = ref(null);
|
|
15
|
+
return { value, args };
|
|
13
16
|
},
|
|
14
17
|
template: `
|
|
15
|
-
|
|
16
|
-
|
|
18
|
+
<BaseDateSelect v-model="value" v-bind="args">
|
|
19
|
+
</BaseDateSelect>
|
|
20
|
+
<ShowValue :value="value" />
|
|
17
21
|
`,
|
|
18
22
|
});
|
|
19
23
|
|
|
20
24
|
export const Demo = Template.bind({});
|
|
21
|
-
Demo.args = {
|
|
22
|
-
modelValue: '2021-01-01',
|
|
23
|
-
};
|
|
25
|
+
Demo.args = {};
|
|
24
26
|
|
|
25
27
|
export const MinYear = Template.bind({});
|
|
26
28
|
MinYear.args = {
|
|
27
|
-
modelValue: '2022-11-16',
|
|
28
29
|
minYear: 1980,
|
|
29
30
|
};
|
|
30
31
|
|
|
31
32
|
export const MaxYear = Template.bind({});
|
|
32
33
|
MaxYear.args = {
|
|
33
|
-
modelValue: '2002-11-16',
|
|
34
34
|
maxYear: 2010,
|
|
35
35
|
};
|
|
36
36
|
|
|
@@ -39,3 +39,9 @@ Disabled.args = {
|
|
|
39
39
|
modelValue: '2022-11-16',
|
|
40
40
|
disabled: true,
|
|
41
41
|
};
|
|
42
|
+
|
|
43
|
+
export const Field = createFieldStory({
|
|
44
|
+
component: BaseDateSelect,
|
|
45
|
+
componentName: 'BaseDateSelect',
|
|
46
|
+
label: 'Date',
|
|
47
|
+
});
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
{
|
|
13
13
|
'cursor-not-allowed bg-slate-100 text-slate-500': disabled,
|
|
14
14
|
},
|
|
15
|
-
|
|
15
|
+
[hasErrorInternal ? 'border-red-500' : 'border-slate-300'],
|
|
16
16
|
]"
|
|
17
17
|
:placeholder="$t('sui.year')"
|
|
18
18
|
@change="update()"
|
|
@@ -37,7 +37,7 @@
|
|
|
37
37
|
{
|
|
38
38
|
'cursor-not-allowed bg-slate-100 text-slate-500': disabled,
|
|
39
39
|
},
|
|
40
|
-
|
|
40
|
+
[hasErrorInternal ? 'border-red-500' : 'border-slate-300'],
|
|
41
41
|
]"
|
|
42
42
|
:placeholder="$t('sui.month')"
|
|
43
43
|
@change="update()"
|
|
@@ -62,7 +62,7 @@
|
|
|
62
62
|
{
|
|
63
63
|
'cursor-not-allowed bg-slate-100 text-slate-500': dayDisabled,
|
|
64
64
|
},
|
|
65
|
-
|
|
65
|
+
[hasErrorInternal ? 'border-red-500' : 'border-slate-300'],
|
|
66
66
|
]"
|
|
67
67
|
:placeholder="$t('sui.day')"
|
|
68
68
|
@change="update()"
|
|
@@ -92,6 +92,7 @@
|
|
|
92
92
|
import { PropType } from 'vue';
|
|
93
93
|
import { range, padStart } from 'lodash';
|
|
94
94
|
import { DateTime, Info } from 'luxon';
|
|
95
|
+
import { useField } from '@/composables/field';
|
|
95
96
|
|
|
96
97
|
const props = defineProps({
|
|
97
98
|
modelValue: {
|
|
@@ -114,14 +115,25 @@ const props = defineProps({
|
|
|
114
115
|
default: DateTime.now().year,
|
|
115
116
|
type: Number,
|
|
116
117
|
},
|
|
117
|
-
|
|
118
|
-
default:
|
|
118
|
+
name: {
|
|
119
|
+
default: undefined,
|
|
119
120
|
type: String,
|
|
120
121
|
},
|
|
122
|
+
hasError: {
|
|
123
|
+
default: false,
|
|
124
|
+
type: Boolean,
|
|
125
|
+
},
|
|
121
126
|
});
|
|
122
127
|
|
|
123
128
|
const emit = defineEmits(['update:modelValue']);
|
|
124
129
|
|
|
130
|
+
const { hasErrorInternal, emitUpdate } = useField({
|
|
131
|
+
name: computed(() => props.name),
|
|
132
|
+
required: computed(() => props.required),
|
|
133
|
+
hasError: computed(() => props.hasError),
|
|
134
|
+
emit: emit,
|
|
135
|
+
});
|
|
136
|
+
|
|
125
137
|
const i18n = useI18n();
|
|
126
138
|
|
|
127
139
|
const years = range(props.maxYear, props.minYear) as number[];
|
|
@@ -162,9 +174,9 @@ function update() {
|
|
|
162
174
|
const dateTime = getDateTime();
|
|
163
175
|
|
|
164
176
|
if (dateTime) {
|
|
165
|
-
|
|
177
|
+
emitUpdate(dateTime.toISODate());
|
|
166
178
|
} else {
|
|
167
|
-
|
|
179
|
+
emitUpdate(null);
|
|
168
180
|
}
|
|
169
181
|
}
|
|
170
182
|
|
|
@@ -173,7 +185,7 @@ function clear() {
|
|
|
173
185
|
date.value.month = null;
|
|
174
186
|
date.value.year = null;
|
|
175
187
|
|
|
176
|
-
|
|
188
|
+
emitUpdate(null);
|
|
177
189
|
}
|
|
178
190
|
|
|
179
191
|
function getDateTime(): DateTime | null {
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div :data-name="name">
|
|
3
|
+
<BaseInputLabel
|
|
4
|
+
v-if="labelNormalized"
|
|
5
|
+
:label="labelNormalized"
|
|
6
|
+
:required="required"
|
|
7
|
+
:class="labelClassInternal"
|
|
8
|
+
/>
|
|
9
|
+
<slot></slot>
|
|
10
|
+
<template v-if="errorMessage">
|
|
11
|
+
<BaseInputError v-if="errorTypeInternal == 'default'" class="mt-1">
|
|
12
|
+
{{ errorMessage }}
|
|
13
|
+
</BaseInputError>
|
|
14
|
+
<BaseAlert
|
|
15
|
+
v-else-if="errorTypeInternal == 'alert'"
|
|
16
|
+
bordered
|
|
17
|
+
color="danger"
|
|
18
|
+
class="mt-4"
|
|
19
|
+
>
|
|
20
|
+
{{ errorMessage }}
|
|
21
|
+
</BaseAlert>
|
|
22
|
+
</template>
|
|
23
|
+
</div>
|
|
24
|
+
</template>
|
|
25
|
+
|
|
26
|
+
<script lang="ts" setup>
|
|
27
|
+
import { PropType } from 'vue';
|
|
28
|
+
import BaseAlert from './BaseAlert.vue';
|
|
29
|
+
import BaseInputError from './BaseInputError.vue';
|
|
30
|
+
import BaseInputLabel from './BaseInputLabel.vue';
|
|
31
|
+
|
|
32
|
+
const props = defineProps({
|
|
33
|
+
name: {
|
|
34
|
+
default: '',
|
|
35
|
+
type: String,
|
|
36
|
+
},
|
|
37
|
+
label: {
|
|
38
|
+
type: String,
|
|
39
|
+
default: '',
|
|
40
|
+
},
|
|
41
|
+
required: {
|
|
42
|
+
type: Boolean,
|
|
43
|
+
default: false,
|
|
44
|
+
},
|
|
45
|
+
errorType: {
|
|
46
|
+
type: String as PropType<'default' | 'alert'>,
|
|
47
|
+
default: 'default',
|
|
48
|
+
},
|
|
49
|
+
labelClass: {
|
|
50
|
+
default: '',
|
|
51
|
+
type: [String, Array, Object] as PropType<
|
|
52
|
+
string | string[] | Record<string, boolean>
|
|
53
|
+
>,
|
|
54
|
+
},
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
const errorTypeInternal = ref(props.errorType);
|
|
58
|
+
|
|
59
|
+
function setErrorType(errorType: 'default' | 'alert' | null) {
|
|
60
|
+
if (errorType != null) {
|
|
61
|
+
errorTypeInternal.value = errorType;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const labelClassInternal = ref(props.labelClass);
|
|
66
|
+
|
|
67
|
+
function setLabelClass(
|
|
68
|
+
labelClass: string | string[] | Record<string, boolean> | null
|
|
69
|
+
) {
|
|
70
|
+
if (labelClass != null) {
|
|
71
|
+
labelClassInternal.value = labelClass;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const getErrorMessageByName = inject(
|
|
76
|
+
'form:getErrorMessageByName',
|
|
77
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
78
|
+
(name: string) => {
|
|
79
|
+
return null;
|
|
80
|
+
}
|
|
81
|
+
) as (name: string) => string | null | undefined;
|
|
82
|
+
|
|
83
|
+
const clearErrors = inject('form:clearErrors', () => {
|
|
84
|
+
return;
|
|
85
|
+
}) as () => void;
|
|
86
|
+
|
|
87
|
+
function fieldOnUpdate() {
|
|
88
|
+
clearErrors();
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const labelNormalized = computed((): string | null => {
|
|
92
|
+
if (props.label) {
|
|
93
|
+
return props.label;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
return null;
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
const errorMessage = computed((): string | null | undefined => {
|
|
100
|
+
return getErrorMessageByName(props.name);
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
provide('field:name', readonly(ref(props.name)));
|
|
104
|
+
provide('field:required', readonly(ref(props.required)));
|
|
105
|
+
provide('field:onUpdate', fieldOnUpdate);
|
|
106
|
+
provide('field:errorMessage', errorMessage);
|
|
107
|
+
provide('field:setErrorType', setErrorType);
|
|
108
|
+
provide('field:setLabelClass', setLabelClass);
|
|
109
|
+
</script>
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import BaseFieldI18n from './BaseFieldI18n.vue';
|
|
2
|
+
import BaseForm from './BaseForm.vue';
|
|
3
|
+
import ShowValue from '@/../.storybook/components/ShowValue.vue';
|
|
4
|
+
|
|
5
|
+
export default {
|
|
6
|
+
title: 'Form/BaseFieldI18n',
|
|
7
|
+
component: BaseFieldI18n,
|
|
8
|
+
decorators: [
|
|
9
|
+
(story) => ({
|
|
10
|
+
components: { story, BaseForm },
|
|
11
|
+
template: `
|
|
12
|
+
<BaseForm method="post" url="https://api.com/todos/422" :data="{}">
|
|
13
|
+
<story/>
|
|
14
|
+
<button type="submit" class="btn btn-primary mt-5">Submit</button>
|
|
15
|
+
</BaseForm>`,
|
|
16
|
+
}),
|
|
17
|
+
],
|
|
18
|
+
args: {
|
|
19
|
+
component: 'BaseInput',
|
|
20
|
+
required: true,
|
|
21
|
+
name: 'name',
|
|
22
|
+
label: 'Name',
|
|
23
|
+
},
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
const Template = (args) => ({
|
|
27
|
+
components: { BaseFieldI18n, ShowValue },
|
|
28
|
+
setup() {
|
|
29
|
+
const value = ref(null);
|
|
30
|
+
return { args, value };
|
|
31
|
+
},
|
|
32
|
+
template: `
|
|
33
|
+
<BaseFieldI18n v-model="value" v-bind="args" class="w-full"></BaseFieldI18n>
|
|
34
|
+
<ShowValue :value="value" />
|
|
35
|
+
`,
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
export const Demo = Template.bind({});
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div :data-name="nameInternal">
|
|
3
|
+
<div class="space-y-2">
|
|
4
|
+
<div v-for="(locale, key) in localesInternal" :key="key">
|
|
5
|
+
<BaseField
|
|
6
|
+
:name="`${nameInternal}.${key}`"
|
|
7
|
+
:required="requiredInternal"
|
|
8
|
+
:label="getLabel(locale)"
|
|
9
|
+
>
|
|
10
|
+
<BaseInput
|
|
11
|
+
v-if="component == 'BaseInput'"
|
|
12
|
+
:model-value="formattedValue[key] + ''"
|
|
13
|
+
class="w-full bg-white"
|
|
14
|
+
v-bind="componentProps"
|
|
15
|
+
@update:model-value="onInput($event, key + '')"
|
|
16
|
+
/>
|
|
17
|
+
<BaseTextarea
|
|
18
|
+
v-else-if="component == 'BaseTextarea'"
|
|
19
|
+
:model-value="formattedValue[key] + ''"
|
|
20
|
+
:required="requiredInternal"
|
|
21
|
+
:name="`${nameInternal}.${key}`"
|
|
22
|
+
class="w-full bg-white"
|
|
23
|
+
v-bind="componentProps"
|
|
24
|
+
@update:model-value="onInput($event, key + '')"
|
|
25
|
+
/>
|
|
26
|
+
</BaseField>
|
|
27
|
+
</div>
|
|
28
|
+
</div>
|
|
29
|
+
<BaseAlert v-if="globalErrorMessage" class="mt-3" bordered color="danger">
|
|
30
|
+
{{ globalErrorMessage }}
|
|
31
|
+
</BaseAlert>
|
|
32
|
+
</div>
|
|
33
|
+
</template>
|
|
34
|
+
|
|
35
|
+
<script lang="ts" setup>
|
|
36
|
+
import { Locales } from '@/types';
|
|
37
|
+
import { get, isPlainObject } from 'lodash';
|
|
38
|
+
import objectHash from 'object-hash';
|
|
39
|
+
import { PropType } from 'vue';
|
|
40
|
+
import { config } from '..';
|
|
41
|
+
import { useField } from '@/composables/field';
|
|
42
|
+
import BaseInput from './BaseInput.vue';
|
|
43
|
+
import BaseTextarea from './BaseTextarea.vue';
|
|
44
|
+
import BaseField from './BaseField.vue';
|
|
45
|
+
import BaseAlert from './BaseAlert.vue';
|
|
46
|
+
|
|
47
|
+
defineComponent({
|
|
48
|
+
components: {
|
|
49
|
+
BaseInput,
|
|
50
|
+
BaseTextarea,
|
|
51
|
+
},
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
type Value = { [locale: string]: string | number | boolean };
|
|
55
|
+
|
|
56
|
+
const props = defineProps({
|
|
57
|
+
modelValue: {
|
|
58
|
+
required: true,
|
|
59
|
+
type: [Object, null, undefined] as PropType<Value | null | undefined>,
|
|
60
|
+
},
|
|
61
|
+
locales: {
|
|
62
|
+
default: undefined,
|
|
63
|
+
type: Object as PropType<Locales>,
|
|
64
|
+
},
|
|
65
|
+
component: {
|
|
66
|
+
default: 'BaseInput',
|
|
67
|
+
type: String as PropType<'BaseInput' | 'BaseTextarea'>,
|
|
68
|
+
},
|
|
69
|
+
componentProps: {
|
|
70
|
+
default: undefined,
|
|
71
|
+
type: Object,
|
|
72
|
+
},
|
|
73
|
+
defaultValue: {
|
|
74
|
+
default: '',
|
|
75
|
+
type: [String, Boolean, Number] as PropType<string | boolean | number>,
|
|
76
|
+
},
|
|
77
|
+
name: {
|
|
78
|
+
default: undefined,
|
|
79
|
+
type: String,
|
|
80
|
+
},
|
|
81
|
+
required: {
|
|
82
|
+
default: false,
|
|
83
|
+
type: Boolean,
|
|
84
|
+
},
|
|
85
|
+
hasError: {
|
|
86
|
+
default: false,
|
|
87
|
+
type: Boolean,
|
|
88
|
+
},
|
|
89
|
+
label: {
|
|
90
|
+
default: undefined,
|
|
91
|
+
type: String,
|
|
92
|
+
},
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
const emit = defineEmits(['update:modelValue']);
|
|
96
|
+
|
|
97
|
+
const { nameInternal, requiredInternal } = useField({
|
|
98
|
+
name: computed(() => props.name),
|
|
99
|
+
required: computed(() => props.required),
|
|
100
|
+
hasError: computed(() => props.hasError),
|
|
101
|
+
emit: emit,
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
const getErrorMessageByName = inject(
|
|
105
|
+
'form:getErrorMessageByName',
|
|
106
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
107
|
+
(name: string) => {
|
|
108
|
+
return null;
|
|
109
|
+
}
|
|
110
|
+
) as (name: string) => string | null | undefined;
|
|
111
|
+
|
|
112
|
+
const colors = ref(['#10b981', '#06b6d4', '#a855f7']);
|
|
113
|
+
|
|
114
|
+
const localesInternal = computed((): Locales => {
|
|
115
|
+
if (props.locales) {
|
|
116
|
+
return props.locales;
|
|
117
|
+
}
|
|
118
|
+
return config.locales;
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
const formattedValue = computed((): Value => {
|
|
122
|
+
// Get current value
|
|
123
|
+
let value = {} as Value;
|
|
124
|
+
if (props.modelValue && isPlainObject(props.modelValue)) {
|
|
125
|
+
value = props.modelValue as Value;
|
|
126
|
+
}
|
|
127
|
+
// Fill missing locales
|
|
128
|
+
Object.keys(localesInternal.value).forEach((locale) => {
|
|
129
|
+
const currentValue = get(props.modelValue, locale);
|
|
130
|
+
if (!currentValue) {
|
|
131
|
+
value[locale] = props.defaultValue;
|
|
132
|
+
}
|
|
133
|
+
});
|
|
134
|
+
return value;
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
// If formatted value is different, send event to parent
|
|
138
|
+
if (
|
|
139
|
+
!props.modelValue ||
|
|
140
|
+
objectHash(formattedValue.value) !== objectHash(props.modelValue)
|
|
141
|
+
) {
|
|
142
|
+
const newFormattedValue = formattedValue.value;
|
|
143
|
+
emit('update:modelValue', newFormattedValue);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
function onInput(value: string, locale: string) {
|
|
147
|
+
const newFormattedValue = formattedValue.value;
|
|
148
|
+
newFormattedValue[locale] = value;
|
|
149
|
+
emit('update:modelValue', newFormattedValue);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
function getLabel(locale: string): string | undefined {
|
|
153
|
+
if (!props.label) {
|
|
154
|
+
return undefined;
|
|
155
|
+
}
|
|
156
|
+
return `${props.label} (${locale})`;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
const globalErrorMessage = computed(() => {
|
|
160
|
+
return getErrorMessageByName(nameInternal.value);
|
|
161
|
+
});
|
|
162
|
+
</script>
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import BaseFileUploader from '
|
|
2
|
-
import BaseLoadingCover from '
|
|
3
|
-
import BaseAppNotifications from '
|
|
1
|
+
import BaseFileUploader from '@/components/BaseFileUploader.vue';
|
|
2
|
+
import BaseLoadingCover from '@/components/BaseLoadingCover.vue';
|
|
3
|
+
import BaseAppNotifications from '@/components/BaseAppNotifications.vue';
|
|
4
4
|
import { Icon as BaseIcon } from '@iconify/vue';
|
|
5
5
|
|
|
6
6
|
export default {
|
|
@@ -36,9 +36,9 @@
|
|
|
36
36
|
import { config } from '@/index';
|
|
37
37
|
import { PropType } from 'vue';
|
|
38
38
|
import { UploadedFile } from '@/types/UploadedFile';
|
|
39
|
-
import { toHumanList, fileSizeFormat } from '
|
|
40
|
-
import { useNotificationsStore } from '
|
|
41
|
-
import BaseLoadingCover from '
|
|
39
|
+
import { toHumanList, fileSizeFormat } from '@/utils';
|
|
40
|
+
import { useNotificationsStore } from '@/stores/notifications';
|
|
41
|
+
import BaseLoadingCover from '@/components/BaseLoadingCover.vue';
|
|
42
42
|
import BaseFilePicker from './BaseFilePicker.vue';
|
|
43
43
|
|
|
44
44
|
const http = config.http;
|