srcdev-nuxt-forms 0.0.23 → 0.1.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/.prettierrc +2 -1
- package/assets/styles/forms/themes/_error.css +9 -0
- package/assets/styles/forms/themes/_ghost.css +11 -0
- package/assets/styles/forms/themes/_primary.css +9 -1
- package/assets/styles/forms/themes/_secondary.css +11 -0
- package/assets/styles/forms/themes/_success.css +12 -0
- package/assets/styles/forms/themes/_tertiary.css +11 -0
- package/assets/styles/forms/themes/_warning.css +11 -0
- package/assets/styles/forms/themes/index.css +5 -0
- package/assets/styles/forms/variables/_theme.css +60 -0
- package/assets/styles/variables/colors/_orange.css +1 -1
- package/assets/styles/variables/colors/_red.css +1 -1
- package/components/forms/c12/prop-validators/index.ts +25 -0
- package/components/forms/c12/validation-patterns/en.json +1 -1
- package/components/forms/input-button/InputButtonCore.vue +367 -0
- package/components/forms/input-button/variants/InputButtonConfirm.vue +78 -0
- package/components/forms/input-button/variants/InputButtonSubmit.vue +74 -0
- package/components/forms/input-text/InputTextCore.vue +72 -66
- package/components/forms/input-text/variants/material/InputEmailMaterial.vue +72 -0
- package/components/forms/input-text/variants/material/InputPasswordMaterial.vue +88 -0
- package/components/forms/input-text/variants/material/InputTextMaterial.vue +75 -0
- package/components/forms/input-text/variants/material/InputTextMaterialCore.vue +258 -0
- package/components/forms/ui/FormField.vue +7 -2
- package/components/forms/ui/FormWrapper.vue +2 -2
- package/composables/useErrorMessages.ts +4 -4
- package/composables/useFormControl.ts +36 -16
- package/layouts/default.vue +33 -2
- package/nuxt.config.ts +4 -3
- package/package.json +3 -1
- package/pages/forms/examples/buttons/index.vue +154 -0
- package/pages/forms/examples/material/text-fields-compact.vue +136 -0
- package/pages/forms/examples/material/text-fields.vue +136 -0
- package/pages/index.vue +2 -70
- package/types/types.forms.ts +6 -11
- package/components/forms/input-text/InputTextField.vue +0 -22
- package/components/forms/input-text/variants/InputTextMaterial.vue +0 -192
|
@@ -0,0 +1,258 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div class="input-text-material" :class="[`theme-${theme}`, { error: fieldHasError }, { compact: compact }]">
|
|
3
|
+
<label class="label" :class="[{ active: isFocused }, { error: fieldHasError }, { dirty: isDirty }, { compact: compact }]" :for="id">
|
|
4
|
+
<span>{{ labelText }}</span>
|
|
5
|
+
</label>
|
|
6
|
+
<div class="input-text-container" :class="[{ active: isFocused }, { error: fieldHasError }, { dirty: isDirty }, { compact: compact }]">
|
|
7
|
+
<slot name="input"></slot>
|
|
8
|
+
</div>
|
|
9
|
+
</div>
|
|
10
|
+
</template>
|
|
11
|
+
|
|
12
|
+
<script setup lang="ts">
|
|
13
|
+
import type { InpuTextC12, IFormData } from '@/types/types.forms';
|
|
14
|
+
|
|
15
|
+
import propValidators from '../../../c12/prop-validators';
|
|
16
|
+
|
|
17
|
+
const props = defineProps({
|
|
18
|
+
size: {
|
|
19
|
+
type: String as PropType<string>,
|
|
20
|
+
default: 'normal',
|
|
21
|
+
validator(value: string) {
|
|
22
|
+
return propValidators.size.includes(value);
|
|
23
|
+
},
|
|
24
|
+
},
|
|
25
|
+
weight: {
|
|
26
|
+
type: String as PropType<string>,
|
|
27
|
+
default: 'wght-400',
|
|
28
|
+
validator(value: string) {
|
|
29
|
+
return propValidators.weight.includes(value);
|
|
30
|
+
},
|
|
31
|
+
},
|
|
32
|
+
theme: {
|
|
33
|
+
type: String as PropType<string>,
|
|
34
|
+
default: 'primary',
|
|
35
|
+
validator(value: string) {
|
|
36
|
+
return propValidators.theme.includes(value);
|
|
37
|
+
},
|
|
38
|
+
},
|
|
39
|
+
type: {
|
|
40
|
+
type: String,
|
|
41
|
+
validator(value: string) {
|
|
42
|
+
return ['text', 'password', 'tel', 'number', 'email', 'url'].includes(value);
|
|
43
|
+
},
|
|
44
|
+
},
|
|
45
|
+
id: {
|
|
46
|
+
type: String,
|
|
47
|
+
required: true,
|
|
48
|
+
},
|
|
49
|
+
name: {
|
|
50
|
+
type: String,
|
|
51
|
+
default: null,
|
|
52
|
+
},
|
|
53
|
+
required: {
|
|
54
|
+
type: Boolean,
|
|
55
|
+
value: false,
|
|
56
|
+
},
|
|
57
|
+
c12: {
|
|
58
|
+
type: Object as PropType<InpuTextC12>,
|
|
59
|
+
required: true,
|
|
60
|
+
},
|
|
61
|
+
styleClassPassthrough: {
|
|
62
|
+
type: String,
|
|
63
|
+
default: '',
|
|
64
|
+
},
|
|
65
|
+
compact: {
|
|
66
|
+
type: Boolean,
|
|
67
|
+
value: false,
|
|
68
|
+
},
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
const labelText = computed(() => {
|
|
72
|
+
return fieldHasError.value ? errorMessage.value : props.c12.label;
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
const modelValue = defineModel() as Ref<IFormData>;
|
|
76
|
+
|
|
77
|
+
const isFocused = computed(() => {
|
|
78
|
+
return modelValue.value.focusedField == props.name;
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
const isDirty = computed(() => {
|
|
82
|
+
return modelValue.value.dirtyFields[props.name];
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
const { errorMessage, setDefaultError, fieldHasError } = useErrorMessage(props.name, modelValue);
|
|
86
|
+
setDefaultError(props.c12.errorMessage);
|
|
87
|
+
</script>
|
|
88
|
+
|
|
89
|
+
<style lang="css">
|
|
90
|
+
.input-text-material {
|
|
91
|
+
input {
|
|
92
|
+
background-color: transparent;
|
|
93
|
+
line-height: var(--line-height);
|
|
94
|
+
|
|
95
|
+
&:focus {
|
|
96
|
+
outline: none;
|
|
97
|
+
box-shadow: none;
|
|
98
|
+
border: none;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
&:-internal-autofill-selected,
|
|
102
|
+
&:autofill {
|
|
103
|
+
background-color: transparent !important;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
label {
|
|
108
|
+
margin: initial;
|
|
109
|
+
line-height: var(--line-height);
|
|
110
|
+
padding: initial;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
--_gutter: 12px;
|
|
114
|
+
--_form-theme: var(--theme-form-primary);
|
|
115
|
+
--_border-width: var(--input-border-width-default);
|
|
116
|
+
|
|
117
|
+
display: grid;
|
|
118
|
+
border-radius: 2px;
|
|
119
|
+
border: var(--_border-width) solid var(--_form-theme);
|
|
120
|
+
|
|
121
|
+
margin-bottom: 20px;
|
|
122
|
+
overflow: hidden;
|
|
123
|
+
|
|
124
|
+
&.theme-secondary {
|
|
125
|
+
--_form-theme: var(--theme-form-secondary);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
&:focus-within {
|
|
129
|
+
border: var(--_border-width) solid var(--_form-theme);
|
|
130
|
+
outline: var(--_border-width) solid hsl(from var(--_form-theme) h s 50%);
|
|
131
|
+
background-color: hsl(from var(--_form-theme) h s 95%);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
&.error {
|
|
135
|
+
/* outline: calc(var(--_border-width) * 2) solid var(--theme-error); */
|
|
136
|
+
|
|
137
|
+
border: var(--_border-width) solid var(--theme-error);
|
|
138
|
+
outline: var(--_border-width) solid hsl(from var(--theme-error) h s 75%);
|
|
139
|
+
|
|
140
|
+
background-color: hsl(from var(--theme-error) h s 95%);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
.label {
|
|
144
|
+
grid-row: 1;
|
|
145
|
+
grid-column: 1;
|
|
146
|
+
|
|
147
|
+
font-family: var(--font-family);
|
|
148
|
+
font-size: 20px;
|
|
149
|
+
font-weight: 700;
|
|
150
|
+
padding: var(--_gutter);
|
|
151
|
+
transform: translateY(12px);
|
|
152
|
+
transition: all linear 0.2s;
|
|
153
|
+
background-color: transparent;
|
|
154
|
+
z-index: 2;
|
|
155
|
+
|
|
156
|
+
&.active,
|
|
157
|
+
&.dirty,
|
|
158
|
+
&.error {
|
|
159
|
+
font-size: 16px;
|
|
160
|
+
transform: translateY(-2px);
|
|
161
|
+
z-index: auto;
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
.input-text-container {
|
|
166
|
+
display: grid;
|
|
167
|
+
grid-row: 1;
|
|
168
|
+
grid-column: 1;
|
|
169
|
+
margin-top: 2rem;
|
|
170
|
+
background-color: transparent;
|
|
171
|
+
opacity: 0;
|
|
172
|
+
transition: opacity linear 0.2s;
|
|
173
|
+
|
|
174
|
+
&.active,
|
|
175
|
+
&.dirty,
|
|
176
|
+
&.error {
|
|
177
|
+
opacity: 1;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
.input-text {
|
|
181
|
+
font-family: var(--font-family);
|
|
182
|
+
border: 0px solid green;
|
|
183
|
+
padding: calc(var(--_gutter) / 2) var(--_gutter);
|
|
184
|
+
font-size: var(--font-size);
|
|
185
|
+
margin: 0;
|
|
186
|
+
/* opacity: 0;
|
|
187
|
+
transition: opacity linear 0.2s;
|
|
188
|
+
|
|
189
|
+
&.active,
|
|
190
|
+
&.dirty {
|
|
191
|
+
opacity: 1;
|
|
192
|
+
} */
|
|
193
|
+
/*
|
|
194
|
+
&::placeholder,
|
|
195
|
+
&:-ms-input-placeholder,
|
|
196
|
+
&::-moz-placeholder, */
|
|
197
|
+
&::-webkit-input-placeholder {
|
|
198
|
+
font-family: var(--font-family);
|
|
199
|
+
/* color: var(--gray-5); */
|
|
200
|
+
color: hsl(from var(--_form-theme) h s 50%);
|
|
201
|
+
font-size: var(--font-size);
|
|
202
|
+
font-style: italic;
|
|
203
|
+
font-weight: 500;
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
/*
|
|
210
|
+
* Compact UI
|
|
211
|
+
**/
|
|
212
|
+
|
|
213
|
+
.input-text-material {
|
|
214
|
+
&.compact {
|
|
215
|
+
overflow: initial;
|
|
216
|
+
|
|
217
|
+
&:focus-within {
|
|
218
|
+
background-color: initial;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
&.error {
|
|
222
|
+
background-color: initial;
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
.label {
|
|
227
|
+
&.compact {
|
|
228
|
+
align-content: center;
|
|
229
|
+
font-size: 16px;
|
|
230
|
+
padding: 0 12px;
|
|
231
|
+
transform: translateY(0);
|
|
232
|
+
|
|
233
|
+
span {
|
|
234
|
+
padding: 0 8px;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
&.active,
|
|
238
|
+
&.dirty,
|
|
239
|
+
&.error {
|
|
240
|
+
font-size: 16px;
|
|
241
|
+
font-weight: 500;
|
|
242
|
+
transform: translateY(-26px);
|
|
243
|
+
z-index: auto;
|
|
244
|
+
|
|
245
|
+
span {
|
|
246
|
+
background-color: white;
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
.input-text-container {
|
|
253
|
+
&.compact {
|
|
254
|
+
margin: 10px 8px 6px 8px;
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
</style>
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<div class="form-field" :class="[width, { 'has-gutter': hasGutter }]">
|
|
3
|
-
<h1>FormField.vue</h1>
|
|
2
|
+
<div class="form-field" :class="[width, styleClassPassthrough, { 'has-gutter': hasGutter }]">
|
|
4
3
|
<slot name="default"></slot>
|
|
5
4
|
</div>
|
|
6
5
|
</template>
|
|
@@ -16,6 +15,10 @@ defineProps({
|
|
|
16
15
|
type: Boolean as PropType<boolean>,
|
|
17
16
|
default: true,
|
|
18
17
|
},
|
|
18
|
+
styleClassPassthrough: {
|
|
19
|
+
type: String,
|
|
20
|
+
default: '',
|
|
21
|
+
},
|
|
19
22
|
});
|
|
20
23
|
</script>
|
|
21
24
|
|
|
@@ -25,6 +28,8 @@ defineProps({
|
|
|
25
28
|
--_max-width: 400px;
|
|
26
29
|
|
|
27
30
|
margin-inline: auto;
|
|
31
|
+
margin-bottom: 16px;
|
|
32
|
+
|
|
28
33
|
width: min(100% - calc(2 * var(--_gutter-width)), var(--_max-width));
|
|
29
34
|
outline: 0px solid var(--gray-5);
|
|
30
35
|
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
<div class="form-wrapper" :class="width">
|
|
3
|
-
<h1>FormWrapper.vue</h1>
|
|
4
3
|
<slot name="default"></slot>
|
|
5
4
|
</div>
|
|
6
5
|
</template>
|
|
@@ -17,7 +16,8 @@ defineProps({
|
|
|
17
16
|
|
|
18
17
|
<style lang="css">
|
|
19
18
|
.form-wrapper {
|
|
20
|
-
outline:
|
|
19
|
+
outline: 0px solid var(--gray-12);
|
|
20
|
+
padding-bottom: 20px;
|
|
21
21
|
|
|
22
22
|
&.narrow {
|
|
23
23
|
max-width: 400px;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import type { IFormData } from
|
|
1
|
+
import type { IFormData } from '@/types/types.forms';
|
|
2
2
|
|
|
3
3
|
export function useErrorMessage(name: string, formData: Ref<IFormData>) {
|
|
4
|
-
const defaultError = ref(
|
|
4
|
+
const defaultError = ref('');
|
|
5
5
|
const customErrorMessages = ref(toRaw(formData.value.customErrorMessages));
|
|
6
6
|
|
|
7
7
|
const hasCustomError = () => {
|
|
@@ -21,7 +21,7 @@ export function useErrorMessage(name: string, formData: Ref<IFormData>) {
|
|
|
21
21
|
};
|
|
22
22
|
|
|
23
23
|
const fieldHasError = computed(() => {
|
|
24
|
-
if (formData.value.
|
|
24
|
+
if (formData.value.submitDisabled) {
|
|
25
25
|
if (hasCustomError()) {
|
|
26
26
|
return true;
|
|
27
27
|
} else if (Object.keys(formData.value.validityState).length > 0 && formData.value.validityState[name] !== undefined) {
|
|
@@ -42,6 +42,6 @@ export function useErrorMessage(name: string, formData: Ref<IFormData>) {
|
|
|
42
42
|
errorMessage,
|
|
43
43
|
setDefaultError,
|
|
44
44
|
fieldHasError,
|
|
45
|
-
removeCustomError
|
|
45
|
+
removeCustomError,
|
|
46
46
|
};
|
|
47
47
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { IFormData, IFieldsInitialState, ICustomErrorMessage, ICustomErrorMessagesArr } from
|
|
1
|
+
import type { IFormData, IFieldsInitialState, ICustomErrorMessage, ICustomErrorMessagesArr } from '@/types/types.forms';
|
|
2
2
|
|
|
3
3
|
export function useFormControl(fieldsInitialState: IFieldsInitialState | Ref<IFieldsInitialState | null>) {
|
|
4
4
|
let savedInitialState = {};
|
|
@@ -6,13 +6,15 @@ export function useFormControl(fieldsInitialState: IFieldsInitialState | Ref<IFi
|
|
|
6
6
|
const formData = ref<IFormData>({
|
|
7
7
|
data: {} as IFieldsInitialState,
|
|
8
8
|
validityState: {},
|
|
9
|
+
dirtyFields: {},
|
|
10
|
+
focusedField: '',
|
|
9
11
|
isPending: false,
|
|
10
12
|
errorCount: 0,
|
|
11
13
|
customErrorMessages: {},
|
|
12
14
|
hasCustomErrorMessages: false,
|
|
13
15
|
formIsValid: false,
|
|
14
|
-
|
|
15
|
-
|
|
16
|
+
submitSuccess: false,
|
|
17
|
+
submitDisabled: false,
|
|
16
18
|
});
|
|
17
19
|
|
|
18
20
|
const initValidationState = async () => {
|
|
@@ -34,12 +36,21 @@ export function useFormControl(fieldsInitialState: IFieldsInitialState | Ref<IFi
|
|
|
34
36
|
return;
|
|
35
37
|
};
|
|
36
38
|
|
|
37
|
-
const getErrorCount = async () => {
|
|
39
|
+
const getErrorCount = async (updateState: boolean = false) => {
|
|
38
40
|
await nextTick();
|
|
39
41
|
|
|
40
42
|
const errorCount = Object.values(formData.value.validityState).filter((value) => !value).length;
|
|
41
43
|
formData.value.errorCount = errorCount;
|
|
42
44
|
formData.value.formIsValid = errorCount === 0;
|
|
45
|
+
|
|
46
|
+
if (updateState) {
|
|
47
|
+
formData.value.submitDisabled = true;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
if (formData.value.submitDisabled) {
|
|
51
|
+
formData.value.submitDisabled = !formData.value.formIsValid;
|
|
52
|
+
}
|
|
53
|
+
|
|
43
54
|
return formData.value.errorCount;
|
|
44
55
|
};
|
|
45
56
|
|
|
@@ -73,7 +84,7 @@ export function useFormControl(fieldsInitialState: IFieldsInitialState | Ref<IFi
|
|
|
73
84
|
formData.value.validityState[name] = valid;
|
|
74
85
|
formData.value.customErrorMessages[name] = {
|
|
75
86
|
useCustomError: true,
|
|
76
|
-
message
|
|
87
|
+
message,
|
|
77
88
|
};
|
|
78
89
|
}
|
|
79
90
|
formData.value.hasCustomErrorMessages = countItemsWithCustomError(formData.value.customErrorMessages) > 0;
|
|
@@ -88,15 +99,20 @@ export function useFormControl(fieldsInitialState: IFieldsInitialState | Ref<IFi
|
|
|
88
99
|
formData.value.formIsValid = false;
|
|
89
100
|
};
|
|
90
101
|
|
|
91
|
-
const showErrors = computed(() => {
|
|
92
|
-
return formData.value.errorCount > 0 && formData.value.isPending;
|
|
93
|
-
});
|
|
94
|
-
|
|
95
102
|
const formIsValid = computed(() => {
|
|
96
103
|
return formData.value.errorCount === 0;
|
|
97
104
|
});
|
|
98
105
|
|
|
106
|
+
const submitDisabled = computed(() => {
|
|
107
|
+
return formData.value.submitDisabled;
|
|
108
|
+
});
|
|
109
|
+
|
|
99
110
|
// Keep an eye on this for performance issue
|
|
111
|
+
|
|
112
|
+
watchEffect(() => {
|
|
113
|
+
console.log('watchEffect: formData.value', formData.value.validityState);
|
|
114
|
+
});
|
|
115
|
+
|
|
100
116
|
watch(
|
|
101
117
|
() => formData.value.validityState,
|
|
102
118
|
() => {
|
|
@@ -105,12 +121,16 @@ export function useFormControl(fieldsInitialState: IFieldsInitialState | Ref<IFi
|
|
|
105
121
|
{ deep: true }
|
|
106
122
|
);
|
|
107
123
|
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
// console.log("savedInitialState UPDATED", savedInitialState);
|
|
112
|
-
// }
|
|
113
|
-
// );
|
|
124
|
+
onMounted(() => {
|
|
125
|
+
initFormData();
|
|
126
|
+
});
|
|
114
127
|
|
|
115
|
-
return {
|
|
128
|
+
return {
|
|
129
|
+
formData,
|
|
130
|
+
getErrorCount,
|
|
131
|
+
updateCustomErrors,
|
|
132
|
+
resetForm,
|
|
133
|
+
formIsValid,
|
|
134
|
+
submitDisabled,
|
|
135
|
+
};
|
|
116
136
|
}
|
package/layouts/default.vue
CHANGED
|
@@ -1,8 +1,26 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
<div class="page-layout">
|
|
3
3
|
<div>
|
|
4
|
-
<h1>
|
|
4
|
+
<h1><NuxtLink to="/">Home</NuxtLink></h1>
|
|
5
|
+
<ul class="flex-group">
|
|
6
|
+
<li>
|
|
7
|
+
<NuxtLink to="/forms/examples/material/text-fields"
|
|
8
|
+
>Material UI text fields</NuxtLink
|
|
9
|
+
>
|
|
10
|
+
</li>
|
|
11
|
+
<li>
|
|
12
|
+
<NuxtLink to="/forms/examples/material/text-fields-compact"
|
|
13
|
+
>Material UI text fields (compact)</NuxtLink
|
|
14
|
+
>
|
|
15
|
+
</li>
|
|
16
|
+
</ul>
|
|
5
17
|
</div>
|
|
18
|
+
<h2>Buttons</h2>
|
|
19
|
+
<ul class="flex-group">
|
|
20
|
+
<li>
|
|
21
|
+
<NuxtLink to="/forms/examples/buttons">Buttons</NuxtLink>
|
|
22
|
+
</li>
|
|
23
|
+
</ul>
|
|
6
24
|
|
|
7
25
|
<div>
|
|
8
26
|
<slot name="layout-content"></slot>
|
|
@@ -20,7 +38,7 @@ useHead({
|
|
|
20
38
|
class: 'body-default',
|
|
21
39
|
id: 'body',
|
|
22
40
|
},
|
|
23
|
-
})
|
|
41
|
+
});
|
|
24
42
|
</script>
|
|
25
43
|
|
|
26
44
|
<style lang="css">
|
|
@@ -28,4 +46,17 @@ useHead({
|
|
|
28
46
|
display: grid;
|
|
29
47
|
grid-template-rows: auto 1fr auto;
|
|
30
48
|
}
|
|
49
|
+
|
|
50
|
+
.flex-group {
|
|
51
|
+
align-items: flex-start;
|
|
52
|
+
display: flex;
|
|
53
|
+
flex-wrap: wrap;
|
|
54
|
+
gap: 24px;
|
|
55
|
+
margin-bottom: 32px;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
ul.flex-group {
|
|
59
|
+
list-style-type: none;
|
|
60
|
+
padding: 0;
|
|
61
|
+
}
|
|
31
62
|
</style>
|
package/nuxt.config.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// https://nuxt.com/docs/api/configuration/nuxt-config
|
|
2
|
-
import { createResolver } from '@nuxt/kit'
|
|
3
|
-
const { resolve } = createResolver(import.meta.url)
|
|
2
|
+
import { createResolver } from '@nuxt/kit';
|
|
3
|
+
const { resolve } = createResolver(import.meta.url);
|
|
4
4
|
|
|
5
5
|
export default defineNuxtConfig({
|
|
6
6
|
devtools: { enabled: true },
|
|
@@ -11,10 +11,11 @@ export default defineNuxtConfig({
|
|
|
11
11
|
},
|
|
12
12
|
},
|
|
13
13
|
|
|
14
|
+
modules: ['@nuxt/icon'],
|
|
14
15
|
components: [
|
|
15
16
|
{
|
|
16
17
|
path: './components',
|
|
17
18
|
pathPrefix: false,
|
|
18
19
|
},
|
|
19
20
|
],
|
|
20
|
-
})
|
|
21
|
+
});
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "srcdev-nuxt-forms",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "0.0
|
|
4
|
+
"version": "0.1.0",
|
|
5
5
|
"main": "./nuxt.config.ts",
|
|
6
6
|
"scripts": {
|
|
7
7
|
"reinstall": "rm -rf node_modules && npm install",
|
|
@@ -14,7 +14,9 @@
|
|
|
14
14
|
"release": "release-it"
|
|
15
15
|
},
|
|
16
16
|
"devDependencies": {
|
|
17
|
+
"@iconify-json/material-symbols": "^1.1.83",
|
|
17
18
|
"@nuxt/eslint-config": "^0.3.13",
|
|
19
|
+
"@nuxt/icon": "^1.0.0",
|
|
18
20
|
"eslint": "^9.5.0",
|
|
19
21
|
"nuxt": "^3.12.2",
|
|
20
22
|
"release-it": "^17.4.0",
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div>
|
|
3
|
+
<NuxtLayout name="default">
|
|
4
|
+
<template #layout-content>
|
|
5
|
+
<div>
|
|
6
|
+
<h1>Example buttons</h1>
|
|
7
|
+
<p>Primary submit</p>
|
|
8
|
+
|
|
9
|
+
<p>Themes switcher</p>
|
|
10
|
+
<ul class="flex-group">
|
|
11
|
+
<li>
|
|
12
|
+
<InputButtonSubmit @click.stop.prevent="swapTheme('primary')" :is-pending="false" button-text="Primary" theme="primary" size="normal" />
|
|
13
|
+
</li>
|
|
14
|
+
<li>
|
|
15
|
+
<InputButtonSubmit @click.stop.prevent="swapTheme('secondary')" :is-pending="false" button-text="Secondary" theme="secondary" size="normal" />
|
|
16
|
+
</li>
|
|
17
|
+
<li>
|
|
18
|
+
<InputButtonSubmit @click.stop.prevent="swapTheme('tertiary')" :is-pending="false" button-text="Tertiary" theme="tertiary" size="normal" />
|
|
19
|
+
</li>
|
|
20
|
+
<li>
|
|
21
|
+
<InputButtonSubmit @click.stop.prevent="swapTheme('warning')" :is-pending="false" button-text="Warning" theme="warning" size="normal" />
|
|
22
|
+
</li>
|
|
23
|
+
<li>
|
|
24
|
+
<InputButtonSubmit @click.stop.prevent="swapTheme('success')" :is-pending="false" button-text="Success" theme="success" size="normal" />
|
|
25
|
+
</li>
|
|
26
|
+
<li>
|
|
27
|
+
<InputButtonSubmit @click.stop.prevent="swapTheme('error')" :is-pending="false" button-text="Error" theme="error" size="normal" />
|
|
28
|
+
</li>
|
|
29
|
+
<li>
|
|
30
|
+
<InputButtonSubmit @click.stop.prevent="swapTheme('ghost')" :is-pending="false" button-text="Ghost" theme="ghost" size="normal" />
|
|
31
|
+
</li>
|
|
32
|
+
</ul>
|
|
33
|
+
|
|
34
|
+
<FormWrapper width="medium">
|
|
35
|
+
<template #default>
|
|
36
|
+
<form @submit.prevent="submitForm">
|
|
37
|
+
<div class="flex-group">
|
|
38
|
+
<InputButtonSubmit @click.stop.prevent="submitForm" :is-pending="false" button-text="Submit" :theme size="x-small" />
|
|
39
|
+
|
|
40
|
+
<InputButtonSubmit @click.stop.prevent="submitForm" :is-pending="false" button-text="Submit" :theme size="small" />
|
|
41
|
+
|
|
42
|
+
<InputButtonSubmit @click.stop.prevent="submitForm" :is-pending="false" button-text="Submit" :theme size="normal" />
|
|
43
|
+
<InputButtonSubmit @click.stop.prevent="submitForm" :is-pending="false" button-text="Submit" :theme size="medium" />
|
|
44
|
+
<InputButtonSubmit @click.stop.prevent="submitForm" :is-pending="false" button-text="Submit" :theme size="large" />
|
|
45
|
+
</div>
|
|
46
|
+
|
|
47
|
+
<div class="flex-group">
|
|
48
|
+
<InputButtonConfirm @click.stop.prevent="submitForm" :is-pending="false" button-text="Confirm" :theme size="x-small" />
|
|
49
|
+
<InputButtonConfirm @click.stop.prevent="submitForm" :is-pending="false" button-text="Confirm" :theme size="small" />
|
|
50
|
+
<InputButtonConfirm @click.stop.prevent="submitForm" :is-pending="false" button-text="Confirm" :theme size="normal" />
|
|
51
|
+
<InputButtonConfirm @click.stop.prevent="submitForm" :is-pending="false" button-text="Confirm" :theme size="medium" />
|
|
52
|
+
<InputButtonConfirm @click.stop.prevent="submitForm" :is-pending="false" button-text="Confirm" :theme size="large" />
|
|
53
|
+
</div>
|
|
54
|
+
<div class="flex-group">
|
|
55
|
+
<InputButtonCore @click.stop.prevent="submitForm" :is-pending="false" button-text="Submit" :theme size="x-small">
|
|
56
|
+
<template #iconOnly>
|
|
57
|
+
<Icon name="radix-icons:eye-none" class="icon" />
|
|
58
|
+
</template>
|
|
59
|
+
</InputButtonCore>
|
|
60
|
+
|
|
61
|
+
<InputButtonCore @click.stop.prevent="submitForm" :is-pending="false" button-text="Submit" :theme size="small">
|
|
62
|
+
<template #iconOnly>
|
|
63
|
+
<Icon name="radix-icons:eye-none" class="icon" />
|
|
64
|
+
</template>
|
|
65
|
+
</InputButtonCore>
|
|
66
|
+
|
|
67
|
+
<InputButtonCore @click.stop.prevent="submitForm" :is-pending="false" button-text="Submit" :theme size="normal">
|
|
68
|
+
<template #iconOnly>
|
|
69
|
+
<Icon name="radix-icons:eye-none" class="icon" />
|
|
70
|
+
</template>
|
|
71
|
+
</InputButtonCore>
|
|
72
|
+
|
|
73
|
+
<InputButtonCore @click.stop.prevent="submitForm" :is-pending="false" button-text="Submit" :theme size="medium">
|
|
74
|
+
<template #iconOnly>
|
|
75
|
+
<Icon name="radix-icons:eye-none" class="icon" />
|
|
76
|
+
</template>
|
|
77
|
+
</InputButtonCore>
|
|
78
|
+
|
|
79
|
+
<InputButtonCore @click.stop.prevent="submitForm" :is-pending="false" button-text="Submit" :theme size="large">
|
|
80
|
+
<template #iconOnly>
|
|
81
|
+
<Icon name="radix-icons:eye-none" class="icon" />
|
|
82
|
+
</template>
|
|
83
|
+
</InputButtonCore>
|
|
84
|
+
</div>
|
|
85
|
+
</form>
|
|
86
|
+
</template>
|
|
87
|
+
</FormWrapper>
|
|
88
|
+
</div>
|
|
89
|
+
</template>
|
|
90
|
+
</NuxtLayout>
|
|
91
|
+
</div>
|
|
92
|
+
</template>
|
|
93
|
+
|
|
94
|
+
<script setup lang="ts">
|
|
95
|
+
import type { IFieldsInitialState, IOptionsConfig } from '@/types/types.forms';
|
|
96
|
+
|
|
97
|
+
definePageMeta({
|
|
98
|
+
layout: false,
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
useHead({
|
|
102
|
+
title: 'Homepage',
|
|
103
|
+
meta: [{ name: 'description', content: 'Homepage' }],
|
|
104
|
+
bodyAttrs: {
|
|
105
|
+
class: '',
|
|
106
|
+
},
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
const theme = ref('primary');
|
|
110
|
+
|
|
111
|
+
const swapTheme = (newTheme: string) => {
|
|
112
|
+
theme.value = newTheme;
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
/*
|
|
116
|
+
* Setup forms
|
|
117
|
+
*/
|
|
118
|
+
const fieldsInitialState = ref<IFieldsInitialState>({
|
|
119
|
+
emailAddress: '',
|
|
120
|
+
username: '',
|
|
121
|
+
password: '',
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
// Setup formData
|
|
125
|
+
const { formData, getErrorCount, updateCustomErrors, resetForm, formIsValid, submitDisabled } = useFormControl(fieldsInitialState);
|
|
126
|
+
|
|
127
|
+
const submitForm = async () => {
|
|
128
|
+
await getErrorCount(true);
|
|
129
|
+
|
|
130
|
+
if (formIsValid.value) {
|
|
131
|
+
formData.value.isPending = true;
|
|
132
|
+
console.log('Form is good - post it!');
|
|
133
|
+
// await useSleep(2000);
|
|
134
|
+
// formData.value.isPending = false;
|
|
135
|
+
} else {
|
|
136
|
+
console.warn('Form has errors');
|
|
137
|
+
}
|
|
138
|
+
};
|
|
139
|
+
</script>
|
|
140
|
+
|
|
141
|
+
<style lang="css">
|
|
142
|
+
.flex-group {
|
|
143
|
+
align-items: flex-start;
|
|
144
|
+
display: flex;
|
|
145
|
+
flex-wrap: wrap;
|
|
146
|
+
gap: 24px;
|
|
147
|
+
margin-bottom: 32px;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
ul.flex-group {
|
|
151
|
+
list-style-type: none;
|
|
152
|
+
padding: 0;
|
|
153
|
+
}
|
|
154
|
+
</style>
|