srcdev-nuxt-forms 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/assets/styles/forms/index.css +1 -0
- package/assets/styles/forms/themes/_primary.css +2 -0
- package/assets/styles/forms/themes/_secondary.css +2 -0
- package/assets/styles/forms/utils/_a11y.css +5 -0
- package/assets/styles/forms/utils/index.css +1 -0
- package/assets/styles/forms/variables/_theme.css +11 -17
- package/assets/styles/variables/colors/_gray.css +1 -0
- package/components/forms/c12/prop-validators/index.ts +8 -20
- package/components/forms/c12/utils.ts +14 -0
- package/components/forms/c12/validation-patterns/en.json +12 -0
- package/components/forms/form-errors/InputError.vue +132 -0
- package/components/forms/input-button/InputButtonCore.vue +11 -8
- package/components/forms/input-button/variants/InputButtonConfirm.vue +1 -1
- package/components/forms/input-button/variants/InputButtonSubmit.vue +1 -1
- package/components/forms/input-checkbox/InputCheckboxCore.vue +407 -0
- package/components/forms/input-checkbox/InputCheckboxWithLabel.vue +125 -0
- package/components/forms/input-checkbox/variants/MultipleCheckboxes.vue +194 -0
- package/components/forms/input-checkbox/variants/SingleCheckbox.vue +157 -0
- package/components/forms/input-radio/InputRadioCore.vue +226 -0
- package/components/forms/input-radio/InputRadioWithLabel.vue +118 -0
- package/components/forms/input-radio/variants/MultipleRadio.vue +183 -0
- package/components/forms/input-radio/variants/SingleRadio.vue +131 -0
- package/components/forms/input-range/InputRangeCore.vue +171 -0
- package/components/forms/input-range/variants/InputRangeDefault.vue +131 -0
- package/components/forms/input-text/InputTextCore.vue +61 -31
- package/components/forms/input-text/variants/material/InputPasswordMaterial.vue +27 -1
- package/components/forms/input-text/variants/material/InputTextMaterial.vue +1 -8
- package/components/forms/input-text/variants/material/InputTextMaterialCore.vue +83 -28
- package/components/forms/input-textarea/InputTextareaCore.vue +170 -0
- package/components/forms/input-textarea/variants/material/InputTextareaMaterial.vue +75 -0
- package/components/forms/input-textarea/variants/material/InputTextareaMaterialCore.vue +290 -0
- package/components/ui/content-grid/ContentGrid.vue +85 -0
- package/composables/useErrorMessages.ts +17 -5
- package/composables/useFormControl.ts +147 -37
- package/layouts/default.vue +7 -13
- package/nuxt.config.ts +22 -0
- package/package.json +9 -8
- package/pages/forms/examples/buttons/index.vue +14 -13
- package/pages/forms/examples/material/text-fields.vue +320 -84
- package/pages/limit-text.vue +43 -0
- package/server/api/places/list.get.ts +23 -0
- package/server/api/textFields.post.ts +37 -0
- package/server/api/utils/index.get.ts +20 -0
- package/server/data/places/cities.json +37 -0
- package/server/data/places/countries.json +55 -0
- package/server/data/utils/title.json +49 -0
- package/types/types.forms.ts +33 -3
- package/types/types.places.ts +8 -0
- package/pages/forms/examples/material/text-fields-compact.vue +0 -136
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import cities from '../../data/places/cities.json';
|
|
2
|
+
import countries from '../../data/places/countries.json';
|
|
3
|
+
|
|
4
|
+
export default defineEventHandler(async (event) => {
|
|
5
|
+
const sleep = async (ms: number) => {
|
|
6
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
const query = getQuery(event);
|
|
10
|
+
|
|
11
|
+
let timeout = 0;
|
|
12
|
+
if (typeof query.delay !== 'undefined') {
|
|
13
|
+
timeout = Number(query.delay);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
await sleep(timeout);
|
|
17
|
+
|
|
18
|
+
if (query.category === 'cities') {
|
|
19
|
+
return cities;
|
|
20
|
+
} else if (query.category === 'countries') {
|
|
21
|
+
return countries;
|
|
22
|
+
}
|
|
23
|
+
});
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { createError } from 'h3';
|
|
2
|
+
|
|
3
|
+
export default defineEventHandler(async (event: any) => {
|
|
4
|
+
const body = await readBody<{ emailAddress: string; password: string; username: string }>(event);
|
|
5
|
+
|
|
6
|
+
const { emailAddress, password, username } = body;
|
|
7
|
+
|
|
8
|
+
let throwError = false;
|
|
9
|
+
|
|
10
|
+
if (emailAddress === 'test@test.com') {
|
|
11
|
+
throwError = true;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const response = {
|
|
15
|
+
status: 200,
|
|
16
|
+
statusText: 'success',
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
// set some other response status cde
|
|
20
|
+
// setResponseStatus(event, 202)
|
|
21
|
+
|
|
22
|
+
// Throw some server side errors
|
|
23
|
+
if (throwError) {
|
|
24
|
+
throw createError({
|
|
25
|
+
statusCode: 400,
|
|
26
|
+
statusMessage: 'error',
|
|
27
|
+
data: {
|
|
28
|
+
errors: {
|
|
29
|
+
emailAddress: 'Email address already registered',
|
|
30
|
+
username: 'Username already registered',
|
|
31
|
+
password: ['Password is too weak', 'Password is too short'],
|
|
32
|
+
},
|
|
33
|
+
},
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
return response;
|
|
37
|
+
});
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import title from '../../data/utils/title.json';
|
|
2
|
+
|
|
3
|
+
export default defineEventHandler(async (event) => {
|
|
4
|
+
const sleep = async (ms: number) => {
|
|
5
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
6
|
+
};
|
|
7
|
+
|
|
8
|
+
const query = getQuery(event);
|
|
9
|
+
|
|
10
|
+
let timeout = 0;
|
|
11
|
+
if (typeof query.delay !== 'undefined') {
|
|
12
|
+
timeout = Number(query.delay);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
await sleep(timeout);
|
|
16
|
+
|
|
17
|
+
if (query.category === 'title') {
|
|
18
|
+
return title;
|
|
19
|
+
}
|
|
20
|
+
});
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
{
|
|
2
|
+
"data": [
|
|
3
|
+
{
|
|
4
|
+
"id": "bath",
|
|
5
|
+
"name": "cities",
|
|
6
|
+
"value": "cities-12",
|
|
7
|
+
"label": "Bath"
|
|
8
|
+
},
|
|
9
|
+
{
|
|
10
|
+
"id": "bristol",
|
|
11
|
+
"name": "cities",
|
|
12
|
+
"value": "cities-23",
|
|
13
|
+
"label": "Bristol"
|
|
14
|
+
},
|
|
15
|
+
{
|
|
16
|
+
"id": "london",
|
|
17
|
+
"name": "cities",
|
|
18
|
+
"value": "cities-42",
|
|
19
|
+
"label": "London"
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
"id": "sunderland",
|
|
23
|
+
"name": "cities",
|
|
24
|
+
"value": "cities-56",
|
|
25
|
+
"label": "Sunderland"
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
"id": "penzance",
|
|
29
|
+
"name": "cities",
|
|
30
|
+
"value": "cities-09",
|
|
31
|
+
"label": "Penzance"
|
|
32
|
+
}
|
|
33
|
+
],
|
|
34
|
+
"total": 5,
|
|
35
|
+
"skip": 0,
|
|
36
|
+
"limit": 10
|
|
37
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
{
|
|
2
|
+
"data": [
|
|
3
|
+
{
|
|
4
|
+
"id": "uk",
|
|
5
|
+
"name": "countries",
|
|
6
|
+
"value": "countries-12",
|
|
7
|
+
"label": "United Kingdom"
|
|
8
|
+
},
|
|
9
|
+
{
|
|
10
|
+
"id": "france",
|
|
11
|
+
"name": "countries",
|
|
12
|
+
"value": "countries-23",
|
|
13
|
+
"label": "France"
|
|
14
|
+
},
|
|
15
|
+
{
|
|
16
|
+
"id": "spain",
|
|
17
|
+
"name": "countries",
|
|
18
|
+
"value": "countries-42",
|
|
19
|
+
"label": "Spain"
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
"id": "italy",
|
|
23
|
+
"name": "countries",
|
|
24
|
+
"value": "countries-56",
|
|
25
|
+
"label": "Italy"
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
"id": "greece",
|
|
29
|
+
"name": "countries",
|
|
30
|
+
"value": "countries-09",
|
|
31
|
+
"label": "Greece"
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
"id": "turkey",
|
|
35
|
+
"name": "countries",
|
|
36
|
+
"value": "countries-13",
|
|
37
|
+
"label": "Turkey"
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
"id": "germany",
|
|
41
|
+
"name": "countries",
|
|
42
|
+
"value": "countries-76",
|
|
43
|
+
"label": "Germany"
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
"id": "portugal",
|
|
47
|
+
"name": "countries",
|
|
48
|
+
"value": "countries-34",
|
|
49
|
+
"label": "Portugal"
|
|
50
|
+
}
|
|
51
|
+
],
|
|
52
|
+
"total": 5,
|
|
53
|
+
"skip": 0,
|
|
54
|
+
"limit": 10
|
|
55
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
{
|
|
2
|
+
"data": [
|
|
3
|
+
{
|
|
4
|
+
"id": "mr",
|
|
5
|
+
"name": "title",
|
|
6
|
+
"value": "title-12",
|
|
7
|
+
"label": "Mr"
|
|
8
|
+
},
|
|
9
|
+
{
|
|
10
|
+
"id": "Mrs",
|
|
11
|
+
"name": "title",
|
|
12
|
+
"value": "title-23",
|
|
13
|
+
"label": "Mrs"
|
|
14
|
+
},
|
|
15
|
+
{
|
|
16
|
+
"id": "ms",
|
|
17
|
+
"name": "title",
|
|
18
|
+
"value": "title-42",
|
|
19
|
+
"label": "Ms"
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
"id": "madam",
|
|
23
|
+
"name": "title",
|
|
24
|
+
"value": "title-56",
|
|
25
|
+
"label": "Madam"
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
"id": "prof",
|
|
29
|
+
"name": "title",
|
|
30
|
+
"value": "title-09",
|
|
31
|
+
"label": "Professor"
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
"id": "sir",
|
|
35
|
+
"name": "title",
|
|
36
|
+
"value": "title-11",
|
|
37
|
+
"label": "Sir"
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
"id": "lady",
|
|
41
|
+
"name": "title",
|
|
42
|
+
"value": "title-22",
|
|
43
|
+
"label": "Lady"
|
|
44
|
+
}
|
|
45
|
+
],
|
|
46
|
+
"total": 5,
|
|
47
|
+
"skip": 0,
|
|
48
|
+
"limit": 10
|
|
49
|
+
}
|
package/types/types.forms.ts
CHANGED
|
@@ -12,6 +12,13 @@ export interface IOptionsConfig {
|
|
|
12
12
|
label: string;
|
|
13
13
|
}
|
|
14
14
|
|
|
15
|
+
export interface IFormMultipleOptions {
|
|
16
|
+
data: IOptionsConfig[];
|
|
17
|
+
total: number;
|
|
18
|
+
skip: number;
|
|
19
|
+
limit: number;
|
|
20
|
+
}
|
|
21
|
+
|
|
15
22
|
export interface IOptionsValueArr {
|
|
16
23
|
[key: string]: string | boolean | number | URL | object;
|
|
17
24
|
}
|
|
@@ -65,10 +72,26 @@ export interface InpuTextC12 {
|
|
|
65
72
|
errorMessage: string;
|
|
66
73
|
}
|
|
67
74
|
|
|
68
|
-
export interface
|
|
75
|
+
export interface IErrorMessagesArr {
|
|
69
76
|
[x: string]: ICustomErrorMessage;
|
|
70
77
|
}
|
|
71
78
|
|
|
79
|
+
export interface IFormFieldC12 {
|
|
80
|
+
label: string;
|
|
81
|
+
placeholder: string;
|
|
82
|
+
errorMessage: string;
|
|
83
|
+
useCustomError: boolean;
|
|
84
|
+
customErrors: null | string | string[];
|
|
85
|
+
isValid: boolean;
|
|
86
|
+
isDirty: boolean;
|
|
87
|
+
type: string;
|
|
88
|
+
previousValue: null | string | boolean | number | URL | object;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export interface IFormFieldsC12 {
|
|
92
|
+
[x: string]: IFormFieldC12;
|
|
93
|
+
}
|
|
94
|
+
|
|
72
95
|
export interface IFormData {
|
|
73
96
|
[x: string]: string | boolean | number | URL | object;
|
|
74
97
|
data: IFieldsInitialState;
|
|
@@ -77,8 +100,15 @@ export interface IFormData {
|
|
|
77
100
|
focusedField: string;
|
|
78
101
|
isPending: boolean;
|
|
79
102
|
errorCount: number;
|
|
80
|
-
|
|
103
|
+
errorMessages: IErrorMessagesArr;
|
|
104
|
+
formFieldsC12: IFormFieldsC12;
|
|
81
105
|
formIsValid: boolean;
|
|
82
|
-
|
|
106
|
+
submitAttempted: boolean;
|
|
83
107
|
submitDisabled: boolean;
|
|
108
|
+
submitSuccess: boolean;
|
|
109
|
+
displayErrorMessages: boolean;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
export interface IApiErrorMessages {
|
|
113
|
+
[x: string]: string;
|
|
84
114
|
}
|
|
@@ -1,136 +0,0 @@
|
|
|
1
|
-
<template>
|
|
2
|
-
<div>
|
|
3
|
-
<NuxtLayout name="default">
|
|
4
|
-
<template #layout-content>
|
|
5
|
-
<div>
|
|
6
|
-
<h1>Material UI text fields (compact)</h1>
|
|
7
|
-
<p>Example test fields in "compact" material UI</p>
|
|
8
|
-
|
|
9
|
-
<FormWrapper width="medium">
|
|
10
|
-
<template #default>
|
|
11
|
-
<form @submit.prevent="submitForm">
|
|
12
|
-
<FormField width="wide" :has-gutter="true">
|
|
13
|
-
<template #default>
|
|
14
|
-
<InputEmailMaterial
|
|
15
|
-
id="emailAddress"
|
|
16
|
-
name="emailAddress"
|
|
17
|
-
validation="emailaddress"
|
|
18
|
-
:required="true"
|
|
19
|
-
:c12="{
|
|
20
|
-
label: 'Your Email Address',
|
|
21
|
-
placeholder: 'eg. joe@example.com',
|
|
22
|
-
errorMessage: 'Please enter a valid email address',
|
|
23
|
-
}"
|
|
24
|
-
v-model="formData"
|
|
25
|
-
theme="secondary"
|
|
26
|
-
:compact
|
|
27
|
-
/>
|
|
28
|
-
</template>
|
|
29
|
-
</FormField>
|
|
30
|
-
|
|
31
|
-
<FormField width="wide" :has-gutter="true">
|
|
32
|
-
<template #default>
|
|
33
|
-
<InputTextMaterial
|
|
34
|
-
id="username"
|
|
35
|
-
name="username"
|
|
36
|
-
validation="username"
|
|
37
|
-
:required="true"
|
|
38
|
-
:c12="{
|
|
39
|
-
label: 'Your Username',
|
|
40
|
-
placeholder: 'eg. YourUserName',
|
|
41
|
-
errorMessage: 'Please enter a valid username',
|
|
42
|
-
}"
|
|
43
|
-
v-model="formData"
|
|
44
|
-
theme="secondary"
|
|
45
|
-
:compact
|
|
46
|
-
/>
|
|
47
|
-
</template>
|
|
48
|
-
</FormField>
|
|
49
|
-
|
|
50
|
-
<FormField width="wide" :has-gutter="true">
|
|
51
|
-
<template #default>
|
|
52
|
-
<InputPasswordMaterial
|
|
53
|
-
id="password"
|
|
54
|
-
name="password"
|
|
55
|
-
validation="password"
|
|
56
|
-
:required="true"
|
|
57
|
-
:c12="{
|
|
58
|
-
label: 'Password',
|
|
59
|
-
placeholder: 'eg. Your5illYPa55w0rd',
|
|
60
|
-
errorMessage: 'Please enter a valid password',
|
|
61
|
-
}"
|
|
62
|
-
v-model="formData"
|
|
63
|
-
theme="secondary"
|
|
64
|
-
:compact
|
|
65
|
-
/>
|
|
66
|
-
</template>
|
|
67
|
-
</FormField>
|
|
68
|
-
|
|
69
|
-
<FormField width="wide" :has-gutter="true">
|
|
70
|
-
<template #default>
|
|
71
|
-
<InputButtonSubmit @click.stop.prevent="submitForm" :is-pending="false" :readonly="submitDisabled" button-text="Submit" theme="secondary" size="medium" />
|
|
72
|
-
</template>
|
|
73
|
-
</FormField>
|
|
74
|
-
</form>
|
|
75
|
-
</template>
|
|
76
|
-
</FormWrapper>
|
|
77
|
-
</div>
|
|
78
|
-
<ClientOnly>
|
|
79
|
-
<p>Client only content</p>
|
|
80
|
-
<pre>
|
|
81
|
-
{{ formData }}
|
|
82
|
-
</pre>
|
|
83
|
-
</ClientOnly>
|
|
84
|
-
</template>
|
|
85
|
-
</NuxtLayout>
|
|
86
|
-
</div>
|
|
87
|
-
</template>
|
|
88
|
-
|
|
89
|
-
<script setup lang="ts">
|
|
90
|
-
import type { IFieldsInitialState, IOptionsConfig } from '@/types/types.forms';
|
|
91
|
-
|
|
92
|
-
definePageMeta({
|
|
93
|
-
layout: false,
|
|
94
|
-
});
|
|
95
|
-
|
|
96
|
-
useHead({
|
|
97
|
-
title: 'Homepage',
|
|
98
|
-
meta: [{ name: 'description', content: 'Homepage' }],
|
|
99
|
-
bodyAttrs: {
|
|
100
|
-
class: '',
|
|
101
|
-
},
|
|
102
|
-
});
|
|
103
|
-
|
|
104
|
-
const compact = ref(true);
|
|
105
|
-
|
|
106
|
-
/*
|
|
107
|
-
* Setup forms
|
|
108
|
-
*/
|
|
109
|
-
const fieldsInitialState = ref<IFieldsInitialState>({
|
|
110
|
-
emailAddress: '',
|
|
111
|
-
username: '',
|
|
112
|
-
password: '',
|
|
113
|
-
});
|
|
114
|
-
|
|
115
|
-
// Setup formData
|
|
116
|
-
const { formData, getErrorCount, updateCustomErrors, resetForm, formIsValid, submitDisabled } = useFormControl(fieldsInitialState);
|
|
117
|
-
|
|
118
|
-
const submitForm = async () => {
|
|
119
|
-
await getErrorCount(true);
|
|
120
|
-
|
|
121
|
-
if (formIsValid.value) {
|
|
122
|
-
formData.value.isPending = true;
|
|
123
|
-
console.log('Form is good - post it!');
|
|
124
|
-
// await useSleep(2000);
|
|
125
|
-
// formData.value.isPending = false;
|
|
126
|
-
} else {
|
|
127
|
-
console.warn('Form has errors');
|
|
128
|
-
}
|
|
129
|
-
};
|
|
130
|
-
</script>
|
|
131
|
-
|
|
132
|
-
<style lang="css">
|
|
133
|
-
p {
|
|
134
|
-
color: initial;
|
|
135
|
-
}
|
|
136
|
-
</style>
|