@sbc-connect/nuxt-forms 0.1.11 → 0.1.12
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/CHANGELOG.md +9 -0
- package/app/components/Connect/Fieldset.vue +17 -16
- package/app/components/Connect/Form/Address/City.vue +4 -5
- package/app/components/Connect/Form/Address/Country.vue +10 -8
- package/app/components/Connect/Form/Address/DeliveryInstructions.vue +4 -5
- package/app/components/Connect/Form/Address/PostalCode.vue +4 -5
- package/app/components/Connect/Form/Address/Region.vue +15 -15
- package/app/components/Connect/Form/Address/Street.vue +5 -7
- package/app/components/Connect/Form/Address/StreetAdditional.vue +4 -5
- package/app/components/Connect/Form/Address/index.vue +19 -15
- package/app/components/Connect/Form/FieldWrapper.vue +42 -0
- package/app/components/Connect/Form/Input.vue +2 -5
- package/app/components/Connect/Form/Textarea.vue +1 -4
- package/app/utils/index.ts +1 -0
- package/app/utils/isoCountriesList.ts +1 -1
- package/app/utils/zod-schemas/addresses.ts +111 -0
- package/app/utils/zod-schemas/index.ts +1 -0
- package/i18n/locales/en-CA.ts +6 -0
- package/package.json +6 -3
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,14 @@
|
|
|
1
1
|
# @sbc-connect/nuxt-forms
|
|
2
2
|
|
|
3
|
+
## 0.1.12
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- [#47](https://github.com/bcgov/connect-nuxt/pull/47) [`9edf5ab`](https://github.com/bcgov/connect-nuxt/commit/9edf5ab6c4c7598f661e8ea713e6a60b390105c1) Thanks [@deetz99](https://github.com/deetz99)! - Add zod and address validations. issue: bcgov/entity#29338
|
|
8
|
+
|
|
9
|
+
- Updated dependencies [[`9edf5ab`](https://github.com/bcgov/connect-nuxt/commit/9edf5ab6c4c7598f661e8ea713e6a60b390105c1)]:
|
|
10
|
+
- @sbc-connect/nuxt-base@0.1.11
|
|
11
|
+
|
|
3
12
|
## 0.1.11
|
|
4
13
|
|
|
5
14
|
### Patch Changes
|
|
@@ -17,22 +17,23 @@ const {
|
|
|
17
17
|
class="flex"
|
|
18
18
|
:class="[orientation === 'horizontal' ? 'flex-col gap-6 sm:flex-row sm:gap-4' : 'flex-col gap-6']"
|
|
19
19
|
>
|
|
20
|
-
<
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
20
|
+
<legend
|
|
21
|
+
class="text-base text-neutral-highlighted font-bold pb-6"
|
|
22
|
+
:class="[
|
|
23
|
+
{ 'text-red-600': !!error },
|
|
24
|
+
{ 'w-full sm:w-1/4 pb-0': orientation === 'horizontal' },
|
|
25
|
+
]"
|
|
26
|
+
>
|
|
27
|
+
<div class="flex flex-wrap gap-4">
|
|
28
|
+
<span>{{ label }}</span>
|
|
29
|
+
<span
|
|
30
|
+
v-if="!!error && showErrorMsg"
|
|
31
|
+
class="font-normal"
|
|
32
|
+
>
|
|
33
|
+
{{ error.message }}
|
|
34
|
+
</span>
|
|
35
|
+
</div>
|
|
36
|
+
</legend>
|
|
36
37
|
|
|
37
38
|
<div class="flex-1">
|
|
38
39
|
<slot />
|
|
@@ -1,19 +1,18 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
|
-
|
|
3
|
-
|
|
2
|
+
defineProps<{
|
|
3
|
+
parentId: string
|
|
4
4
|
schemaPrefix: string
|
|
5
5
|
}>()
|
|
6
6
|
|
|
7
7
|
const model = defineModel<string>({ default: '' })
|
|
8
|
-
|
|
9
|
-
const inputId = props.id + '-city'
|
|
10
8
|
</script>
|
|
11
9
|
|
|
12
10
|
<template>
|
|
13
11
|
<ConnectFormInput
|
|
14
12
|
v-model="model"
|
|
13
|
+
:data-testid="`${parentId}-field-city`"
|
|
14
|
+
:input-id="`${parentId}-input-city`"
|
|
15
15
|
:name="schemaPrefix + '.city'"
|
|
16
|
-
:input-id="inputId"
|
|
17
16
|
required
|
|
18
17
|
:label="$t('connect.label.city')"
|
|
19
18
|
/>
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
|
-
import {
|
|
2
|
+
import { isoCountriesListSortedByName as countries } from '#forms/app/utils/isoCountriesList'
|
|
3
3
|
|
|
4
|
-
|
|
5
|
-
|
|
4
|
+
defineProps<{
|
|
5
|
+
parentId: string
|
|
6
6
|
schemaPrefix: string
|
|
7
7
|
disabled?: boolean
|
|
8
8
|
}>()
|
|
@@ -13,8 +13,6 @@ defineEmits<{
|
|
|
13
13
|
|
|
14
14
|
const model = defineModel<string>({ default: '' })
|
|
15
15
|
|
|
16
|
-
const inputId = props.id + '-country'
|
|
17
|
-
|
|
18
16
|
const displayedCountryName = computed(() => {
|
|
19
17
|
if (model.value) {
|
|
20
18
|
const found = countries.find(c => c.alpha_2 === model.value)?.name
|
|
@@ -32,14 +30,15 @@ const options = [
|
|
|
32
30
|
|
|
33
31
|
<template>
|
|
34
32
|
<UFormField
|
|
33
|
+
:data-testid="`${parentId}-field-country`"
|
|
35
34
|
:name="schemaPrefix + '.country'"
|
|
36
35
|
class="grow"
|
|
37
36
|
>
|
|
38
37
|
<template #default="{ error }">
|
|
39
38
|
<USelect
|
|
40
|
-
:id="
|
|
39
|
+
:id="`${parentId}-input-country`"
|
|
41
40
|
v-model="model"
|
|
42
|
-
:data-testid="
|
|
41
|
+
:data-testid="`${parentId}-input-country`"
|
|
43
42
|
:items="options"
|
|
44
43
|
value-key="alpha_2"
|
|
45
44
|
label-key="name"
|
|
@@ -52,6 +51,9 @@ const options = [
|
|
|
52
51
|
base: error
|
|
53
52
|
? 'shadow-input-error focus:shadow-input-error data-[state=open]:shadow-input-error'
|
|
54
53
|
: '',
|
|
54
|
+
trailingIcon: error
|
|
55
|
+
? 'text-error group-data-[state=open]:text-error group-focus:text-error'
|
|
56
|
+
: '',
|
|
55
57
|
item: 'nth-2:border-b nth-2:border-gray-200',
|
|
56
58
|
itemTrailingIcon: 'hidden',
|
|
57
59
|
}"
|
|
@@ -66,7 +68,7 @@ const options = [
|
|
|
66
68
|
? 'top-1/2 -translate-y-1/2'
|
|
67
69
|
: 'top-1 -translate-y-none text-xs',
|
|
68
70
|
error
|
|
69
|
-
? 'text-red-600'
|
|
71
|
+
? 'text-red-600 group-data-[state=open]:text-red-600 group-focus:text-red-600'
|
|
70
72
|
: '',
|
|
71
73
|
'absolute left-0 px-2.5 text-sm transition-all',
|
|
72
74
|
'group-data-[state=open]:text-primary group-focus:text-primary',
|
|
@@ -1,19 +1,18 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
|
-
|
|
3
|
-
|
|
2
|
+
defineProps<{
|
|
3
|
+
parentId: string
|
|
4
4
|
schemaPrefix: string
|
|
5
5
|
disabled?: boolean
|
|
6
6
|
}>()
|
|
7
7
|
|
|
8
8
|
const model = defineModel<string>({ default: '' })
|
|
9
|
-
|
|
10
|
-
const inputId = props.id + '-locationDescription'
|
|
11
9
|
</script>
|
|
12
10
|
|
|
13
11
|
<template>
|
|
14
12
|
<ConnectFormTextarea
|
|
15
13
|
v-model="model"
|
|
16
|
-
:
|
|
14
|
+
:data-testid="`${parentId}-field-locationDescription`"
|
|
15
|
+
:input-id="`${parentId}-input-locationDescription`"
|
|
17
16
|
:name="schemaPrefix + '.locationDescription'"
|
|
18
17
|
:disabled
|
|
19
18
|
:label="$t('connect.label.deliveryInstructionsOpt')"
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
|
-
|
|
3
|
-
|
|
2
|
+
defineProps<{
|
|
3
|
+
parentId: string
|
|
4
4
|
schemaPrefix: string
|
|
5
5
|
disabled?: boolean
|
|
6
6
|
country?: string
|
|
@@ -13,15 +13,14 @@ const model = defineModel({
|
|
|
13
13
|
type: String,
|
|
14
14
|
default: ''
|
|
15
15
|
})
|
|
16
|
-
|
|
17
|
-
const inputId = props.id + '-postalCode'
|
|
18
16
|
</script>
|
|
19
17
|
|
|
20
18
|
<template>
|
|
21
19
|
<ConnectFormInput
|
|
22
20
|
v-model="model"
|
|
23
21
|
:name="schemaPrefix + '.postalCode'"
|
|
24
|
-
:
|
|
22
|
+
:data-testid="`${parentId}-field-postalCode`"
|
|
23
|
+
:input-id="`${parentId}-input-postalCode`"
|
|
25
24
|
:disabled
|
|
26
25
|
required
|
|
27
26
|
:label="country === 'US'
|
|
@@ -1,15 +1,13 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
2
|
const {
|
|
3
3
|
maxlength = '1000',
|
|
4
|
-
country
|
|
5
|
-
id
|
|
4
|
+
country
|
|
6
5
|
} = defineProps<{
|
|
7
|
-
|
|
6
|
+
parentId: string
|
|
8
7
|
country?: string
|
|
9
8
|
schemaPrefix: string
|
|
10
9
|
disabled?: boolean
|
|
11
10
|
maxlength?: string
|
|
12
|
-
help?: string
|
|
13
11
|
}>()
|
|
14
12
|
|
|
15
13
|
const model = defineModel<string>({ default: '' })
|
|
@@ -32,32 +30,34 @@ const displayedRegionName = computed(() => {
|
|
|
32
30
|
}
|
|
33
31
|
return ''
|
|
34
32
|
})
|
|
35
|
-
|
|
36
|
-
const inputId = id + '-region'
|
|
37
33
|
</script>
|
|
38
34
|
|
|
39
35
|
<template>
|
|
40
36
|
<UFormField
|
|
41
37
|
:name="schemaPrefix + '.region'"
|
|
42
38
|
class="grow flex-1"
|
|
39
|
+
:data-testid="`${parentId}-field-region`"
|
|
43
40
|
>
|
|
44
41
|
<template #default="{ error }">
|
|
45
42
|
<USelect
|
|
46
43
|
v-if="country === 'US' || country === 'CA'"
|
|
47
|
-
:id="
|
|
44
|
+
:id="`${parentId}-input-region`"
|
|
48
45
|
v-model="model"
|
|
49
|
-
:data-testid="
|
|
46
|
+
:data-testid="`${parentId}-input-region`"
|
|
50
47
|
:items="regions"
|
|
51
48
|
:aria-label="country === 'CA' ? $t('connect.label.province') : $t('connect.label.state')"
|
|
52
49
|
value-key="code"
|
|
53
50
|
label-key="name"
|
|
54
|
-
:aria-required="
|
|
51
|
+
:aria-required="country === 'US' || country === 'CA'"
|
|
55
52
|
:disabled
|
|
56
53
|
class="w-full grow ring-0"
|
|
57
54
|
:ui="{
|
|
58
55
|
base: error
|
|
59
56
|
? 'shadow-input-error focus:shadow-input-error data-[state=open]:shadow-input-error'
|
|
60
57
|
: '',
|
|
58
|
+
trailingIcon: error
|
|
59
|
+
? 'text-error group-data-[state=open]:text-error group-focus:text-error'
|
|
60
|
+
: '',
|
|
61
61
|
}"
|
|
62
62
|
>
|
|
63
63
|
<template #default="{ modelValue }">
|
|
@@ -70,10 +70,10 @@ const inputId = id + '-region'
|
|
|
70
70
|
? 'top-1/2 -translate-y-1/2'
|
|
71
71
|
: 'top-1 -translate-y-none text-xs',
|
|
72
72
|
error
|
|
73
|
-
? 'text-error'
|
|
74
|
-
: '',
|
|
73
|
+
? 'text-error group-data-[state=open]:text-error group-focus:text-error'
|
|
74
|
+
: 'group-data-[state=open]:text-primary group-focus:text-primary',
|
|
75
75
|
'absolute left-0 px-2.5 text-sm transition-all',
|
|
76
|
-
'
|
|
76
|
+
'',
|
|
77
77
|
]"
|
|
78
78
|
>
|
|
79
79
|
{{ country === 'CA' ? $t('connect.label.province') : $t('connect.label.state') }}
|
|
@@ -91,15 +91,15 @@ const inputId = id + '-region'
|
|
|
91
91
|
</USelect>
|
|
92
92
|
<ConnectInput
|
|
93
93
|
v-else
|
|
94
|
-
:id="
|
|
94
|
+
:id="`${parentId}-input-region`"
|
|
95
95
|
v-model="model"
|
|
96
|
-
:
|
|
96
|
+
:data-testid="`${parentId}-input-region`"
|
|
97
97
|
:disabled
|
|
98
98
|
:label="$t('connect.label.regionOpt')"
|
|
99
99
|
:maxlength
|
|
100
100
|
/>
|
|
101
101
|
<div
|
|
102
|
-
v-if="
|
|
102
|
+
v-if="!$slots.help && !error"
|
|
103
103
|
class="h-4 mt-1"
|
|
104
104
|
/>
|
|
105
105
|
</template>
|
|
@@ -1,12 +1,11 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
2
|
const {
|
|
3
|
-
id,
|
|
4
3
|
schemaPrefix,
|
|
5
4
|
helpText = 'none',
|
|
6
5
|
country,
|
|
7
6
|
disableAddressComplete
|
|
8
7
|
} = defineProps<{
|
|
9
|
-
|
|
8
|
+
parentId: string
|
|
10
9
|
country?: string
|
|
11
10
|
schemaPrefix: string
|
|
12
11
|
disableAddressComplete?: boolean
|
|
@@ -19,8 +18,6 @@ const emit = defineEmits<{
|
|
|
19
18
|
addressComplete: [value: ConnectAddress]
|
|
20
19
|
}>()
|
|
21
20
|
|
|
22
|
-
const inputId = id + '-street'
|
|
23
|
-
|
|
24
21
|
const { address: canadaPostAddress, enableAddressComplete } = useCanadaPost()
|
|
25
22
|
|
|
26
23
|
const addressComplete = (id: string) => {
|
|
@@ -37,7 +34,8 @@ watch(canadaPostAddress, (newAddress) => {
|
|
|
37
34
|
<template>
|
|
38
35
|
<ConnectFormInput
|
|
39
36
|
v-model="model"
|
|
40
|
-
:
|
|
37
|
+
:data-testid="`${parentId}-field-street`"
|
|
38
|
+
:input-id="`${parentId}-input-street`"
|
|
41
39
|
:name="schemaPrefix + '.street'"
|
|
42
40
|
:help="helpText === 'none'
|
|
43
41
|
? undefined
|
|
@@ -47,7 +45,7 @@ watch(canadaPostAddress, (newAddress) => {
|
|
|
47
45
|
"
|
|
48
46
|
:label="$t('connect.label.street')"
|
|
49
47
|
required
|
|
50
|
-
@keypress.once="addressComplete(
|
|
51
|
-
@click="addressComplete(
|
|
48
|
+
@keypress.once="addressComplete(`${parentId}-input-street`)"
|
|
49
|
+
@click="addressComplete(`${parentId}-input-street`)"
|
|
52
50
|
/>
|
|
53
51
|
</template>
|
|
@@ -1,19 +1,18 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
|
-
|
|
3
|
-
|
|
2
|
+
defineProps<{
|
|
3
|
+
parentId: string
|
|
4
4
|
schemaPrefix: string
|
|
5
5
|
}>()
|
|
6
6
|
|
|
7
7
|
const model = defineModel<string>({ default: '' })
|
|
8
|
-
|
|
9
|
-
const inputId = props.id + '-streetAdditional'
|
|
10
8
|
</script>
|
|
11
9
|
|
|
12
10
|
<template>
|
|
13
11
|
<ConnectFormInput
|
|
14
12
|
v-model="model"
|
|
15
13
|
:name="schemaPrefix + '.streetAdditional'"
|
|
16
|
-
:
|
|
14
|
+
:data-testid="`${parentId}-field-streetAdditional`"
|
|
15
|
+
:input-id="`${parentId}-input-streetAdditional`"
|
|
17
16
|
:label="$t('connect.label.streetAdditional')"
|
|
18
17
|
/>
|
|
19
18
|
</template>
|
|
@@ -1,22 +1,23 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
|
+
/* eslint-disable @typescript-eslint/no-explicit-any */ // allow any for form ref type
|
|
3
|
+
import type { Form } from '@nuxt/ui'
|
|
4
|
+
|
|
2
5
|
const {
|
|
3
6
|
disabledFields,
|
|
4
7
|
excludedFields = ['streetName', 'streetNumber', 'unitNumber'],
|
|
5
8
|
schemaPrefix,
|
|
6
|
-
streetHelpText = 'none'
|
|
9
|
+
streetHelpText = 'none',
|
|
10
|
+
formRef
|
|
7
11
|
} = defineProps<{
|
|
8
12
|
id: string
|
|
9
13
|
schemaPrefix: string
|
|
14
|
+
formRef?: Form<any> | null
|
|
10
15
|
disabledFields?: Array<keyof ConnectAddress>
|
|
11
16
|
excludedFields?: Array<keyof ConnectAddress>
|
|
12
17
|
disableAddressComplete?: boolean
|
|
13
18
|
streetHelpText?: 'allow-po' | 'no-po' | 'none'
|
|
14
19
|
}>()
|
|
15
20
|
|
|
16
|
-
const emit = defineEmits<{
|
|
17
|
-
shouldValidate: [keys: Array<keyof ConnectAddress>]
|
|
18
|
-
}>()
|
|
19
|
-
|
|
20
21
|
const state = defineModel<Partial<ConnectAddress>>({ required: true })
|
|
21
22
|
|
|
22
23
|
async function populateAddressComplete(e: ConnectAddress) {
|
|
@@ -37,17 +38,20 @@ async function populateAddressComplete(e: ConnectAddress) {
|
|
|
37
38
|
// wait for dom to populate inputs
|
|
38
39
|
await nextTick()
|
|
39
40
|
|
|
40
|
-
//
|
|
41
|
-
|
|
41
|
+
// validate populated fields if formRef provided
|
|
42
|
+
if (formRef) {
|
|
43
|
+
const fields = validKeys.map(k => `${schemaPrefix}.${k}`)
|
|
44
|
+
await formRef.validate({ name: fields, silent: true })
|
|
45
|
+
}
|
|
42
46
|
}
|
|
43
47
|
</script>
|
|
44
48
|
|
|
45
49
|
<template>
|
|
46
|
-
<div class="space-y-2" :data-testid="id">
|
|
50
|
+
<div class="space-y-2" :data-testid="id + '-container'">
|
|
47
51
|
<ConnectFormAddressCountry
|
|
48
52
|
v-if="!excludedFields.includes('country')"
|
|
49
|
-
:id
|
|
50
53
|
v-model="state.country"
|
|
54
|
+
:parent-id="id"
|
|
51
55
|
:schema-prefix="schemaPrefix"
|
|
52
56
|
:disabled="disabledFields?.includes('country')"
|
|
53
57
|
@change="state.region = ''"
|
|
@@ -55,8 +59,8 @@ async function populateAddressComplete(e: ConnectAddress) {
|
|
|
55
59
|
|
|
56
60
|
<ConnectFormAddressStreet
|
|
57
61
|
v-if="!excludedFields.includes('street')"
|
|
58
|
-
:id
|
|
59
62
|
v-model="state.street"
|
|
63
|
+
:parent-id="id"
|
|
60
64
|
:schema-prefix="schemaPrefix"
|
|
61
65
|
:country="state.country"
|
|
62
66
|
:disable-address-complete="disableAddressComplete"
|
|
@@ -67,8 +71,8 @@ async function populateAddressComplete(e: ConnectAddress) {
|
|
|
67
71
|
|
|
68
72
|
<ConnectFormAddressStreetAdditional
|
|
69
73
|
v-if="!excludedFields.includes('streetAdditional')"
|
|
70
|
-
:id
|
|
71
74
|
v-model="state.streetAdditional"
|
|
75
|
+
:parent-id="id"
|
|
72
76
|
:schema-prefix="schemaPrefix"
|
|
73
77
|
:disabled="disabledFields?.includes('streetAdditional')"
|
|
74
78
|
/>
|
|
@@ -76,16 +80,16 @@ async function populateAddressComplete(e: ConnectAddress) {
|
|
|
76
80
|
<div class="flex flex-col gap-2 sm:gap-4 sm:flex-row">
|
|
77
81
|
<ConnectFormAddressCity
|
|
78
82
|
v-if="!excludedFields.includes('city')"
|
|
79
|
-
:id
|
|
80
83
|
v-model="state.city"
|
|
84
|
+
:parent-id="id"
|
|
81
85
|
:schema-prefix="schemaPrefix"
|
|
82
86
|
:disabled="disabledFields?.includes('city')"
|
|
83
87
|
/>
|
|
84
88
|
|
|
85
89
|
<ConnectFormAddressRegion
|
|
86
90
|
v-if="!excludedFields.includes('region')"
|
|
87
|
-
:id
|
|
88
91
|
v-model="state.region"
|
|
92
|
+
:parent-id="id"
|
|
89
93
|
:schema-prefix="schemaPrefix"
|
|
90
94
|
:country="state.country"
|
|
91
95
|
:disabled="disabledFields?.includes('region')"
|
|
@@ -93,8 +97,8 @@ async function populateAddressComplete(e: ConnectAddress) {
|
|
|
93
97
|
|
|
94
98
|
<ConnectFormAddressPostalCode
|
|
95
99
|
v-if="!excludedFields.includes('postalCode')"
|
|
96
|
-
:id
|
|
97
100
|
v-model="state.postalCode"
|
|
101
|
+
:parent-id="id"
|
|
98
102
|
:schema-prefix="schemaPrefix"
|
|
99
103
|
:country="state.country"
|
|
100
104
|
:disabled="disabledFields?.includes('postalCode')"
|
|
@@ -103,8 +107,8 @@ async function populateAddressComplete(e: ConnectAddress) {
|
|
|
103
107
|
|
|
104
108
|
<ConnectFormAddressDeliveryInstructions
|
|
105
109
|
v-if="!excludedFields.includes('locationDescription')"
|
|
106
|
-
:id
|
|
107
110
|
v-model="state.locationDescription"
|
|
111
|
+
:parent-id="id"
|
|
108
112
|
:schema-prefix="schemaPrefix"
|
|
109
113
|
:disabled="disabledFields?.includes('locationDescription')"
|
|
110
114
|
/>
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import type { FormError } from '@nuxt/ui'
|
|
3
|
+
|
|
4
|
+
const {
|
|
5
|
+
orientation = 'vertical'
|
|
6
|
+
} = defineProps<{
|
|
7
|
+
label: string
|
|
8
|
+
error?: FormError
|
|
9
|
+
showErrorMsg?: boolean
|
|
10
|
+
orientation?: 'vertical' | 'horizontal'
|
|
11
|
+
}>()
|
|
12
|
+
// TODO: figure out why text-error doesnt work on the legend text
|
|
13
|
+
</script>
|
|
14
|
+
|
|
15
|
+
<template>
|
|
16
|
+
<div
|
|
17
|
+
class="flex"
|
|
18
|
+
:class="[orientation === 'horizontal' ? 'flex-col gap-6 sm:flex-row sm:gap-4' : 'flex-col gap-6']"
|
|
19
|
+
>
|
|
20
|
+
<span
|
|
21
|
+
aria-hidden="true"
|
|
22
|
+
class="text-base text-neutral-highlighted font-bold"
|
|
23
|
+
:class="[
|
|
24
|
+
{ 'text-red-600': !!error },
|
|
25
|
+
{ 'w-full sm:w-1/4': orientation === 'horizontal' },
|
|
26
|
+
]"
|
|
27
|
+
>
|
|
28
|
+
<div class="flex flex-wrap gap-4">
|
|
29
|
+
<span>{{ label }}</span>
|
|
30
|
+
<span
|
|
31
|
+
v-if="!!error && showErrorMsg"
|
|
32
|
+
class="font-normal"
|
|
33
|
+
>
|
|
34
|
+
{{ error.message }}
|
|
35
|
+
</span>
|
|
36
|
+
</div>
|
|
37
|
+
</span>
|
|
38
|
+
<div class="flex-1">
|
|
39
|
+
<slot />
|
|
40
|
+
</div>
|
|
41
|
+
</div>
|
|
42
|
+
</template>
|
|
@@ -2,7 +2,6 @@
|
|
|
2
2
|
defineProps<{
|
|
3
3
|
name: string
|
|
4
4
|
label: string
|
|
5
|
-
help?: string
|
|
6
5
|
inputId: string
|
|
7
6
|
required?: boolean
|
|
8
7
|
autofocus?: boolean
|
|
@@ -16,7 +15,6 @@ const model = defineModel<string>({ required: true })
|
|
|
16
15
|
<template>
|
|
17
16
|
<UFormField
|
|
18
17
|
:name
|
|
19
|
-
:help
|
|
20
18
|
class="grow flex-1"
|
|
21
19
|
>
|
|
22
20
|
<template #default="{ error }">
|
|
@@ -24,14 +22,13 @@ const model = defineModel<string>({ required: true })
|
|
|
24
22
|
:id="inputId"
|
|
25
23
|
v-model="model"
|
|
26
24
|
:mask
|
|
27
|
-
:required
|
|
28
|
-
:invalid="!!error"
|
|
25
|
+
:aria-required="required"
|
|
29
26
|
:label
|
|
30
27
|
:autofocus
|
|
31
28
|
:disabled
|
|
32
29
|
/>
|
|
33
30
|
<div
|
|
34
|
-
v-if="
|
|
31
|
+
v-if="!$slots.help && !error"
|
|
35
32
|
class="h-4 mt-1"
|
|
36
33
|
/>
|
|
37
34
|
</template>
|
|
@@ -2,7 +2,6 @@
|
|
|
2
2
|
defineProps<{
|
|
3
3
|
name: string
|
|
4
4
|
label: string
|
|
5
|
-
help?: string
|
|
6
5
|
inputId: string
|
|
7
6
|
required?: boolean
|
|
8
7
|
autofocus?: boolean
|
|
@@ -15,7 +14,6 @@ const model = defineModel<string>({ required: true })
|
|
|
15
14
|
<template>
|
|
16
15
|
<UFormField
|
|
17
16
|
:name
|
|
18
|
-
:help
|
|
19
17
|
class="grow flex-1"
|
|
20
18
|
>
|
|
21
19
|
<template #default="{ error }">
|
|
@@ -24,13 +22,12 @@ const model = defineModel<string>({ required: true })
|
|
|
24
22
|
v-model="model"
|
|
25
23
|
:data-testid="inputId"
|
|
26
24
|
:required
|
|
27
|
-
:invalid="!!error"
|
|
28
25
|
:label
|
|
29
26
|
:autofocus
|
|
30
27
|
:disabled
|
|
31
28
|
/>
|
|
32
29
|
<div
|
|
33
|
-
v-if="
|
|
30
|
+
v-if="!$slots.help && !error"
|
|
34
31
|
class="h-4 mt-1"
|
|
35
32
|
/>
|
|
36
33
|
</template>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './zod-schemas'
|
|
@@ -2023,7 +2023,7 @@ export const countrySubdivisions = {
|
|
|
2023
2023
|
ca: subdivisionsCa,
|
|
2024
2024
|
us: subdivisionsUs
|
|
2025
2025
|
}
|
|
2026
|
-
export const
|
|
2026
|
+
export const isoCountriesListSortedByName = isoCountriesList.toSorted(
|
|
2027
2027
|
(c1, c2) => {
|
|
2028
2028
|
if (c1.name < c2.name) {
|
|
2029
2029
|
return -1
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import { z } from 'zod'
|
|
2
|
+
|
|
3
|
+
export function getRequiredAddressSchema() {
|
|
4
|
+
const t = useNuxtApp().$i18n.t
|
|
5
|
+
|
|
6
|
+
return z.object({
|
|
7
|
+
street: z
|
|
8
|
+
.string()
|
|
9
|
+
.min(1, t('connect.validation.fieldRequired'))
|
|
10
|
+
.max(50, t('connect.validation.maxChars', { count: 50 })),
|
|
11
|
+
streetAdditional: z
|
|
12
|
+
.string()
|
|
13
|
+
.max(50, t('connect.validation.maxChars', { count: 50 }))
|
|
14
|
+
.optional(),
|
|
15
|
+
city: z
|
|
16
|
+
.string()
|
|
17
|
+
.min(1, t('connect.validation.fieldRequired'))
|
|
18
|
+
.max(40, t('connect.validation.maxChars', { count: 40 })),
|
|
19
|
+
region: z
|
|
20
|
+
.string()
|
|
21
|
+
.optional(),
|
|
22
|
+
postalCode: z
|
|
23
|
+
.string()
|
|
24
|
+
.min(1, t('connect.validation.fieldRequired'))
|
|
25
|
+
.max(15, t('connect.validation.maxChars', { count: 15 })),
|
|
26
|
+
country: z
|
|
27
|
+
.string()
|
|
28
|
+
.min(1, t('connect.validation.fieldRequired')),
|
|
29
|
+
locationDescription: z
|
|
30
|
+
.string()
|
|
31
|
+
.max(80, t('connect.validation.maxChars', { count: 80 }))
|
|
32
|
+
.optional()
|
|
33
|
+
}).superRefine((data, ctx) => {
|
|
34
|
+
// validate region based on country
|
|
35
|
+
// required if country is US or CA
|
|
36
|
+
// optional and max 2 characters if not US or CA
|
|
37
|
+
const country = data.country
|
|
38
|
+
const region = data.region
|
|
39
|
+
|
|
40
|
+
if (region && region.length > 2) {
|
|
41
|
+
ctx.addIssue({
|
|
42
|
+
code: 'custom',
|
|
43
|
+
message: t('connect.validation.maxChars', { count: 2 }),
|
|
44
|
+
path: ['region']
|
|
45
|
+
})
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
if ((country === 'US' || country === 'CA') && region?.length === 0) {
|
|
49
|
+
ctx.addIssue({
|
|
50
|
+
code: 'custom',
|
|
51
|
+
message: t('connect.validation.fieldRequired'),
|
|
52
|
+
path: ['region']
|
|
53
|
+
})
|
|
54
|
+
}
|
|
55
|
+
})
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export function getNonRequiredAddressSchema() {
|
|
59
|
+
const t = useNuxtApp().$i18n.t
|
|
60
|
+
|
|
61
|
+
return z.object({
|
|
62
|
+
street: z
|
|
63
|
+
.string()
|
|
64
|
+
.min(1, t('connect.validation.fieldRequired'))
|
|
65
|
+
.max(50, t('connect.validation.maxChars', { count: 50 }))
|
|
66
|
+
.optional()
|
|
67
|
+
.or(z.literal('')),
|
|
68
|
+
streetAdditional: z
|
|
69
|
+
.string()
|
|
70
|
+
.max(50, t('connect.validation.maxChars', { count: 50 }))
|
|
71
|
+
.optional(),
|
|
72
|
+
city: z
|
|
73
|
+
.string()
|
|
74
|
+
.min(1, t('connect.validation.fieldRequired'))
|
|
75
|
+
.max(40, t('connect.validation.maxChars', { count: 40 }))
|
|
76
|
+
.optional()
|
|
77
|
+
.or(z.literal('')),
|
|
78
|
+
region: z
|
|
79
|
+
.string()
|
|
80
|
+
.optional(),
|
|
81
|
+
postalCode: z
|
|
82
|
+
.string()
|
|
83
|
+
.min(1, t('connect.validation.fieldRequired'))
|
|
84
|
+
.max(15, t('connect.validation.maxChars', { count: 15 }))
|
|
85
|
+
.optional()
|
|
86
|
+
.or(z.literal('')),
|
|
87
|
+
country: z
|
|
88
|
+
.string()
|
|
89
|
+
.min(1, t('connect.validation.fieldRequired'))
|
|
90
|
+
.optional()
|
|
91
|
+
.or(z.literal('')),
|
|
92
|
+
locationDescription: z
|
|
93
|
+
.string()
|
|
94
|
+
.max(80, t('connect.validation.maxChars', { count: 80 }))
|
|
95
|
+
.optional()
|
|
96
|
+
}).superRefine((data, ctx) => {
|
|
97
|
+
// validate region based on country
|
|
98
|
+
// required if country is US or CA
|
|
99
|
+
// optional and max 2 characters if not US or CA
|
|
100
|
+
// const country = data.country
|
|
101
|
+
const region = data.region
|
|
102
|
+
|
|
103
|
+
if (region && region.length > 2) {
|
|
104
|
+
ctx.addIssue({
|
|
105
|
+
code: 'custom',
|
|
106
|
+
message: t('connect.validation.maxChars', { count: 2 }),
|
|
107
|
+
path: ['region']
|
|
108
|
+
})
|
|
109
|
+
}
|
|
110
|
+
})
|
|
111
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './addresses'
|
package/i18n/locales/en-CA.ts
CHANGED
|
@@ -33,6 +33,12 @@ export default {
|
|
|
33
33
|
text: {
|
|
34
34
|
addressCanBePOBox: 'Street address, PO box, rural route, or general delivery address.',
|
|
35
35
|
addressCannotBePOBox: 'Address cannot be a PO Box.'
|
|
36
|
+
},
|
|
37
|
+
validation: {
|
|
38
|
+
fieldRequired: 'This field is required',
|
|
39
|
+
maxChars: 'Maximum 0 characters | Maximum 1 character | Maximum {count} characters', // 0/1 most likely will never be used but required for pluralization
|
|
40
|
+
minChars: 'Minimum 0 characters | Minimum 1 character | Minimum {count} characters', // 0/1 most likely will never be used but required for pluralization
|
|
41
|
+
required: 'Required'
|
|
36
42
|
}
|
|
37
43
|
}
|
|
38
44
|
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sbc-connect/nuxt-forms",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "0.1.
|
|
4
|
+
"version": "0.1.12",
|
|
5
5
|
"repository": "github:bcgov/connect-nuxt",
|
|
6
6
|
"license": "BSD-3-Clause",
|
|
7
7
|
"main": "./nuxt.config.ts",
|
|
@@ -17,7 +17,8 @@
|
|
|
17
17
|
"@sbc-connect/vitest-config": "0.0.6"
|
|
18
18
|
},
|
|
19
19
|
"dependencies": {
|
|
20
|
-
"
|
|
20
|
+
"zod": "^4.0.17",
|
|
21
|
+
"@sbc-connect/nuxt-base": "0.1.11"
|
|
21
22
|
},
|
|
22
23
|
"scripts": {
|
|
23
24
|
"preinstall": "npx only-allow pnpm",
|
|
@@ -29,8 +30,10 @@
|
|
|
29
30
|
"lint": "eslint .",
|
|
30
31
|
"lint:fix": "eslint --fix .",
|
|
31
32
|
"test": "pnpm run test:unit; pnpm run test:e2e",
|
|
32
|
-
"test:unit": "vitest run",
|
|
33
|
+
"test:unit": "vitest run --passWithNoTests",
|
|
34
|
+
"test:unit:watch": "vitest",
|
|
33
35
|
"test:e2e": "npx playwright test",
|
|
36
|
+
"test:e2e:ui": "npx playwright test --ui",
|
|
34
37
|
"typecheck": "npx nuxt typecheck"
|
|
35
38
|
}
|
|
36
39
|
}
|