@sbc-connect/nuxt-forms 0.2.0 → 0.3.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/CHANGELOG.md +20 -0
- package/app/components/Connect/Fieldset.vue +1 -1
- package/app/components/Connect/Form/Address/PostalCode.vue +1 -1
- package/app/components/Connect/Form/FieldWrapper.vue +5 -5
- package/app/components/Connect/Form/Phone/CountryCode.vue +124 -0
- package/app/components/Connect/Form/Phone/CountryFlag.vue +14 -0
- package/app/components/Connect/Form/Phone/index.vue +2 -0
- package/app/interfaces/phone-country.ts +14 -0
- package/app/utils/formHelpers.ts +13 -0
- package/app/utils/zod-schemas/addresses.ts +8 -1
- package/i18n/locales/en-CA.ts +4 -2
- package/package.json +4 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,25 @@
|
|
|
1
1
|
# @sbc-connect/nuxt-forms
|
|
2
2
|
|
|
3
|
+
## 0.3.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- [#108](https://github.com/bcgov/connect-nuxt/pull/108) [`8571754`](https://github.com/bcgov/connect-nuxt/commit/8571754160242c3233838fc039fd1a01ff9d7a82) Thanks [@cameron-eyds](https://github.com/cameron-eyds)! - Implements account creation in Auth and Country Code/Country Flag components in Forms
|
|
8
|
+
|
|
9
|
+
### Patch Changes
|
|
10
|
+
|
|
11
|
+
- [#115](https://github.com/bcgov/connect-nuxt/pull/115) [`dd17349`](https://github.com/bcgov/connect-nuxt/commit/dd173497255f35415b468e17f7192722f604d3c8) Thanks [@deetz99](https://github.com/deetz99)! - Relax postal code mask, different validation message for incorrect format vs invalid postal code
|
|
12
|
+
|
|
13
|
+
- Updated dependencies [[`d9d6356`](https://github.com/bcgov/connect-nuxt/commit/d9d63560e27117c43b9e8e54d363c3fdbb620049)]:
|
|
14
|
+
- @sbc-connect/nuxt-base@0.5.0
|
|
15
|
+
|
|
16
|
+
## 0.2.1
|
|
17
|
+
|
|
18
|
+
### Patch Changes
|
|
19
|
+
|
|
20
|
+
- Updated dependencies [[`f5d34df`](https://github.com/bcgov/connect-nuxt/commit/f5d34df0bbb2b37cbeda8a9dd39c6bce098c564f)]:
|
|
21
|
+
- @sbc-connect/nuxt-base@0.4.0
|
|
22
|
+
|
|
3
23
|
## 0.2.0
|
|
4
24
|
|
|
5
25
|
### Minor Changes
|
|
@@ -34,7 +34,7 @@ const bodyClass = bodyClassMap[bodyVariant]
|
|
|
34
34
|
>
|
|
35
35
|
<div
|
|
36
36
|
class="flex flex-col gap-1"
|
|
37
|
-
:class="{ 'w-full sm:
|
|
37
|
+
:class="{ 'w-full sm:basis-1/4 pb-0': orientation === 'horizontal' }"
|
|
38
38
|
>
|
|
39
39
|
<legend
|
|
40
40
|
:id="legendId"
|
|
@@ -20,12 +20,12 @@ const {
|
|
|
20
20
|
<span
|
|
21
21
|
aria-hidden="true"
|
|
22
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
|
-
]"
|
|
23
|
+
:class="{ 'w-full sm:basis-1/4': orientation === 'horizontal' }"
|
|
27
24
|
>
|
|
28
|
-
<div
|
|
25
|
+
<div
|
|
26
|
+
class="flex flex-wrap gap-4"
|
|
27
|
+
:class="{ 'text-red-600': !!error }"
|
|
28
|
+
>
|
|
29
29
|
<span>{{ label }}</span>
|
|
30
30
|
<span
|
|
31
31
|
v-if="!!error && showErrorMsg"
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import countryList from 'country-codes-list'
|
|
3
|
+
import CountryFlag from '#forms/app/components/Connect/Form/Phone/CountryFlag.vue'
|
|
4
|
+
import type { PhoneCountry } from '#forms/app/interfaces/phone-country'
|
|
5
|
+
|
|
6
|
+
defineProps<{
|
|
7
|
+
isInvalid: boolean | undefined
|
|
8
|
+
}>()
|
|
9
|
+
|
|
10
|
+
const countryCallingCode = defineModel<string | undefined>('countryCallingCode', { required: false })
|
|
11
|
+
watch(countryCallingCode, (val) => {
|
|
12
|
+
// this is needed for when something outside this component changes the callingCode model value
|
|
13
|
+
if (!val) {
|
|
14
|
+
countryIso2.value = undefined
|
|
15
|
+
} else if (selectedCountry.value?.callingCode !== val) {
|
|
16
|
+
countryIso2.value = search(val)[0]?.iso2
|
|
17
|
+
}
|
|
18
|
+
})
|
|
19
|
+
|
|
20
|
+
const countryIso2 = defineModel<string | undefined>('countryIso2', { required: false })
|
|
21
|
+
watch(countryIso2, (val) => {
|
|
22
|
+
if (!val) {
|
|
23
|
+
selectedCountry.value = undefined
|
|
24
|
+
} else if (val && selectedCountry.value?.iso2 !== val) {
|
|
25
|
+
selectCountry(val)
|
|
26
|
+
}
|
|
27
|
+
})
|
|
28
|
+
|
|
29
|
+
const selectedCountry = ref<PhoneCountry | undefined>(undefined)
|
|
30
|
+
watch(selectedCountry, (newVal) => {
|
|
31
|
+
if (newVal?.callingCode !== countryCallingCode.value) {
|
|
32
|
+
countryCallingCode.value = newVal?.callingCode
|
|
33
|
+
}
|
|
34
|
+
if (newVal?.iso2 !== countryIso2.value) {
|
|
35
|
+
countryIso2.value = newVal?.iso2
|
|
36
|
+
}
|
|
37
|
+
})
|
|
38
|
+
|
|
39
|
+
const _countryListOptions = countryList.customList(
|
|
40
|
+
'countryCode', '{countryCallingCode},{countryNameEn},{countryNameLocal}')
|
|
41
|
+
|
|
42
|
+
const manualInput = (event: Event) => {
|
|
43
|
+
selectedCountry.value = {
|
|
44
|
+
callingCode: event.target.value
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const countryListOptions: Array<PhoneCountry> = Object.keys(_countryListOptions).map((key) => {
|
|
49
|
+
const [callingCode, nameEn, nameLocal] = _countryListOptions[key].split(',')
|
|
50
|
+
return {
|
|
51
|
+
iso2: key,
|
|
52
|
+
callingCode,
|
|
53
|
+
label: `+${callingCode}`,
|
|
54
|
+
nameLocal,
|
|
55
|
+
nameEn
|
|
56
|
+
}
|
|
57
|
+
}).sort((a, b) => a.callingCode.localeCompare(b.callingCode))
|
|
58
|
+
|
|
59
|
+
const search = (q: string) => countryListOptions.filter((lo) => {
|
|
60
|
+
return lo.callingCode.includes(q)
|
|
61
|
+
|| lo.iso2?.includes(q)
|
|
62
|
+
|| lo.nameLocal?.includes(q)
|
|
63
|
+
|| lo.nameEn?.includes(q)
|
|
64
|
+
|| lo.label?.includes(q)
|
|
65
|
+
})
|
|
66
|
+
|
|
67
|
+
const selectCountry = (iso2: string) => {
|
|
68
|
+
selectedCountry.value = countryListOptions.find(item => item.iso2 === iso2)
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
onMounted(() => {
|
|
72
|
+
if (countryIso2.value !== undefined) {
|
|
73
|
+
selectCountry(countryIso2.value)
|
|
74
|
+
} else if (countryCallingCode.value) {
|
|
75
|
+
// Set a country based on the calling code if available
|
|
76
|
+
const iso2 = search(countryCallingCode.value)[0]?.iso2
|
|
77
|
+
if (iso2) {
|
|
78
|
+
selectCountry(iso2)
|
|
79
|
+
} else {
|
|
80
|
+
selectedCountry.value = {
|
|
81
|
+
callingCode: countryCallingCode.value,
|
|
82
|
+
label: `+${countryCallingCode.value}`
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
})
|
|
87
|
+
</script>
|
|
88
|
+
|
|
89
|
+
<template>
|
|
90
|
+
<UInputMenu
|
|
91
|
+
v-model="selectedCountry"
|
|
92
|
+
:items="countryListOptions"
|
|
93
|
+
:color="selectedCountry?.callingCode ? 'primary' : ''"
|
|
94
|
+
:search="search"
|
|
95
|
+
:label-key="''"
|
|
96
|
+
:trailing-icon="'i-mdi-chevron-down'"
|
|
97
|
+
class="cursor-pointer"
|
|
98
|
+
:aria-required="true"
|
|
99
|
+
:aria-invalid="isInvalid"
|
|
100
|
+
:aria-label="$t('connect.label.countryCode')"
|
|
101
|
+
@input="manualInput($event)"
|
|
102
|
+
>
|
|
103
|
+
<template v-if="!!selectedCountry?.iso2" #leading="{ modelValue }">
|
|
104
|
+
<div class="flex">
|
|
105
|
+
<CountryFlag
|
|
106
|
+
:tooltip-text="selectedCountry?.nameEn"
|
|
107
|
+
:country-code-iso2letter="selectedCountry?.iso2"
|
|
108
|
+
/>
|
|
109
|
+
<span class="ml-2 mt-2 truncate">{{ modelValue.label }}</span>
|
|
110
|
+
</div>
|
|
111
|
+
</template>
|
|
112
|
+
|
|
113
|
+
<template #item="{ item }">
|
|
114
|
+
<div class="flex items-end cursor-pointer">
|
|
115
|
+
<CountryFlag
|
|
116
|
+
:tooltip-text="item.nameLocal"
|
|
117
|
+
:country-code-iso2letter="item.iso2"
|
|
118
|
+
/>
|
|
119
|
+
<span class="ml-2 truncate">{{ item.label }}</span>
|
|
120
|
+
<span class="sr-only">{{ item.nameLocal }}</span>
|
|
121
|
+
</div>
|
|
122
|
+
</template>
|
|
123
|
+
</UInputMenu>
|
|
124
|
+
</template>
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { FormErrorEvent } from '@nuxt/ui'
|
|
2
|
+
|
|
3
|
+
export function onFormSubmitError(event: FormErrorEvent) {
|
|
4
|
+
if (event?.errors?.[0]?.id) {
|
|
5
|
+
const element = document.getElementById(event.errors[0].id)
|
|
6
|
+
if (element) {
|
|
7
|
+
element.scrollIntoView({ behavior: 'smooth', block: 'center' })
|
|
8
|
+
setTimeout(() => {
|
|
9
|
+
element.focus({ preventScroll: true })
|
|
10
|
+
}, 0)
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
}
|
|
@@ -5,6 +5,7 @@ import { z } from 'zod'
|
|
|
5
5
|
* Ref: https://en.wikipedia.org/wiki/Postal_codes_in_Canada
|
|
6
6
|
*/
|
|
7
7
|
const CanadaPostalCodeRegex = /^[ABCEGHJ-NPRSTVXY][0-9][ABCEGHJ-NPRSTV-Z][ ]?[0-9][ABCEGHJ-NPRSTV-Z][0-9]$/i
|
|
8
|
+
const CanadaPostalCodeFormatOnlyRegex = /^[A-Z][0-9][A-Z][ ]?[0-9][A-Z][0-9]$/i
|
|
8
9
|
|
|
9
10
|
/**
|
|
10
11
|
* US ZIP code regex (eg, accepts 12345 or 12345-6789).
|
|
@@ -78,12 +79,18 @@ export function getRequiredAddressSchema() {
|
|
|
78
79
|
}
|
|
79
80
|
|
|
80
81
|
if (country === 'CA') {
|
|
81
|
-
if (!
|
|
82
|
+
if (!CanadaPostalCodeFormatOnlyRegex.test(postalCode)) {
|
|
82
83
|
ctx.addIssue({
|
|
83
84
|
code: 'custom',
|
|
84
85
|
message: t('connect.validation.invalidPostalCodeFormat'),
|
|
85
86
|
path: ['postalCode']
|
|
86
87
|
})
|
|
88
|
+
} else if (!CanadaPostalCodeRegex.test(postalCode)) {
|
|
89
|
+
ctx.addIssue({
|
|
90
|
+
code: 'custom',
|
|
91
|
+
message: t('connect.validation.invalidPostalCode'),
|
|
92
|
+
path: ['postalCode']
|
|
93
|
+
})
|
|
87
94
|
}
|
|
88
95
|
} else if (country === 'US') {
|
|
89
96
|
if (!USZipCodeRegex.test(postalCode)) {
|
package/i18n/locales/en-CA.ts
CHANGED
|
@@ -7,6 +7,7 @@ export default {
|
|
|
7
7
|
city: 'City',
|
|
8
8
|
code: 'Code',
|
|
9
9
|
country: 'Country',
|
|
10
|
+
countryCode: 'Country Code',
|
|
10
11
|
deliveryAddress: 'Delivery Address',
|
|
11
12
|
deliveryInstructions: 'Delivery Instructions',
|
|
12
13
|
deliveryInstructionsOpt: 'Delivery Instructions (Optional)',
|
|
@@ -39,8 +40,9 @@ export default {
|
|
|
39
40
|
maxChars: 'Maximum 0 characters | Maximum 1 character | Maximum {count} characters', // 0/1 most likely will never be used but required for pluralization
|
|
40
41
|
minChars: 'Minimum 0 characters | Minimum 1 character | Minimum {count} characters', // 0/1 most likely will never be used but required for pluralization
|
|
41
42
|
required: 'Required',
|
|
42
|
-
|
|
43
|
-
|
|
43
|
+
invalidPostalCode: 'Enter a valid postal code',
|
|
44
|
+
invalidPostalCodeFormat: 'Format must be A1A 1A1',
|
|
45
|
+
invalidZipCodeFormat: 'Format must be 12345 or 12345-6789'
|
|
44
46
|
}
|
|
45
47
|
}
|
|
46
48
|
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sbc-connect/nuxt-forms",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.3.0",
|
|
5
5
|
"repository": "github:bcgov/connect-nuxt",
|
|
6
6
|
"license": "BSD-3-Clause",
|
|
7
7
|
"main": "./nuxt.config.ts",
|
|
@@ -17,7 +17,9 @@
|
|
|
17
17
|
"@sbc-connect/vitest-config": "0.0.6"
|
|
18
18
|
},
|
|
19
19
|
"dependencies": {
|
|
20
|
-
"
|
|
20
|
+
"country-codes-list": "^1.6.11",
|
|
21
|
+
"vue-country-flag-next": "^2.3.2",
|
|
22
|
+
"@sbc-connect/nuxt-base": "0.5.0"
|
|
21
23
|
},
|
|
22
24
|
"scripts": {
|
|
23
25
|
"preinstall": "npx only-allow pnpm",
|