@sbc-connect/nuxt-forms 0.1.11 → 0.1.13
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 +24 -0
- package/README.md +16 -3
- package/app/components/Connect/Fieldset.vue +15 -9
- 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 -3
- package/app/components/Connect/Form/Textarea.vue +1 -2
- 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,29 @@
|
|
|
1
1
|
# @sbc-connect/nuxt-forms
|
|
2
2
|
|
|
3
|
+
## 0.1.13
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- [#50](https://github.com/bcgov/connect-nuxt/pull/50) [`19e3a00`](https://github.com/bcgov/connect-nuxt/commit/19e3a00a8350f8f02f674d10e1d572c909ad90c5) Thanks [@deetz99](https://github.com/deetz99)! - Minor CSS updates
|
|
8
|
+
|
|
9
|
+
- update footer link divider colour
|
|
10
|
+
- update formfield help/error placeholder spacer
|
|
11
|
+
- fix fieldset orientation
|
|
12
|
+
|
|
13
|
+
issue: bcgov/entity#30434
|
|
14
|
+
|
|
15
|
+
- Updated dependencies [[`19e3a00`](https://github.com/bcgov/connect-nuxt/commit/19e3a00a8350f8f02f674d10e1d572c909ad90c5)]:
|
|
16
|
+
- @sbc-connect/nuxt-base@0.1.12
|
|
17
|
+
|
|
18
|
+
## 0.1.12
|
|
19
|
+
|
|
20
|
+
### Patch Changes
|
|
21
|
+
|
|
22
|
+
- [#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
|
|
23
|
+
|
|
24
|
+
- Updated dependencies [[`9edf5ab`](https://github.com/bcgov/connect-nuxt/commit/9edf5ab6c4c7598f661e8ea713e6a60b390105c1)]:
|
|
25
|
+
- @sbc-connect/nuxt-base@0.1.11
|
|
26
|
+
|
|
3
27
|
## 0.1.11
|
|
4
28
|
|
|
5
29
|
### Patch Changes
|
package/README.md
CHANGED
|
@@ -15,7 +15,7 @@ This package provides a library of reusable form components, integrated validati
|
|
|
15
15
|
- Pre-built components for common inputs like addresses, postal codes, names, emails and phone numbers.
|
|
16
16
|
- Reusable Zod schemas to manage the input validations.
|
|
17
17
|
|
|
18
|
-
For detailed usage and API documentation, please see the [Forms Layer Docs](../../../docs/packages/layers/forms/
|
|
18
|
+
For detailed usage and API documentation, please see the [Forms Layer Docs](../../../docs/packages/layers/forms/overview.md).
|
|
19
19
|
|
|
20
20
|
## Usage
|
|
21
21
|
|
|
@@ -38,7 +38,20 @@ defineNuxtConfig({
|
|
|
38
38
|
```
|
|
39
39
|
|
|
40
40
|
## Environment Variables
|
|
41
|
-
This
|
|
41
|
+
This project requires certain environment variables to be set to run correctly.
|
|
42
|
+
|
|
43
|
+
Create a file named .env in the root of the project.
|
|
44
|
+
|
|
45
|
+
Copy the contents of the .env.example file into your new .env file.
|
|
46
|
+
|
|
47
|
+
### Local Development
|
|
48
|
+
Copy the contents of the **.env.example** file into your new .env file.
|
|
49
|
+
|
|
50
|
+
### Production Environments
|
|
51
|
+
> [!IMPORTANT]
|
|
52
|
+
> The values for staging and production environments are managed securely and should not be stored in this file.
|
|
53
|
+
|
|
54
|
+
To obtain the correct values for a production build or deployment, please contact the Connect Platform Team.
|
|
42
55
|
|
|
43
56
|
## Contributing
|
|
44
|
-
We welcome contributions to this package! Please see the main [
|
|
57
|
+
We welcome contributions to this package! Please see the main [Contribution Guidelines](../../../CONTRIBUTING.md) for information on our branching strategy, commit conventions, and pull request process.
|
|
@@ -10,17 +10,23 @@ const {
|
|
|
10
10
|
orientation?: 'vertical' | 'horizontal'
|
|
11
11
|
}>()
|
|
12
12
|
// TODO: figure out why text-error doesnt work on the legend text
|
|
13
|
+
|
|
14
|
+
const legendId = useId()
|
|
13
15
|
</script>
|
|
14
16
|
|
|
15
17
|
<template>
|
|
16
|
-
<fieldset
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
18
|
+
<fieldset :aria-labelledby="legendId">
|
|
19
|
+
<div
|
|
20
|
+
class="flex"
|
|
21
|
+
:class="[orientation === 'horizontal' ? 'flex-col gap-6 sm:flex-row sm:gap-4' : 'flex-col gap-6']"
|
|
22
|
+
>
|
|
21
23
|
<legend
|
|
24
|
+
:id="legendId"
|
|
22
25
|
class="text-base text-neutral-highlighted font-bold"
|
|
23
|
-
:class="
|
|
26
|
+
:class="[
|
|
27
|
+
{ 'text-red-600': !!error },
|
|
28
|
+
{ 'w-full sm:w-1/4 pb-0': orientation === 'horizontal' },
|
|
29
|
+
]"
|
|
24
30
|
>
|
|
25
31
|
<div class="flex flex-wrap gap-4">
|
|
26
32
|
<span>{{ label }}</span>
|
|
@@ -32,10 +38,10 @@ const {
|
|
|
32
38
|
</span>
|
|
33
39
|
</div>
|
|
34
40
|
</legend>
|
|
35
|
-
</div>
|
|
36
41
|
|
|
37
|
-
|
|
38
|
-
|
|
42
|
+
<div class="flex-1">
|
|
43
|
+
<slot />
|
|
44
|
+
</div>
|
|
39
45
|
</div>
|
|
40
46
|
</fieldset>
|
|
41
47
|
</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 + '-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,12 +2,12 @@
|
|
|
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
|
|
9
8
|
mask?: string
|
|
10
9
|
disabled?: boolean
|
|
10
|
+
help?: string
|
|
11
11
|
}>()
|
|
12
12
|
|
|
13
13
|
const model = defineModel<string>({ required: true })
|
|
@@ -24,8 +24,7 @@ const model = defineModel<string>({ required: true })
|
|
|
24
24
|
:id="inputId"
|
|
25
25
|
v-model="model"
|
|
26
26
|
:mask
|
|
27
|
-
:required
|
|
28
|
-
:invalid="!!error"
|
|
27
|
+
:aria-required="required"
|
|
29
28
|
:label
|
|
30
29
|
:autofocus
|
|
31
30
|
:disabled
|
|
@@ -2,11 +2,11 @@
|
|
|
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
|
|
9
8
|
disabled?: boolean
|
|
9
|
+
help?: string
|
|
10
10
|
}>()
|
|
11
11
|
|
|
12
12
|
const model = defineModel<string>({ required: true })
|
|
@@ -24,7 +24,6 @@ const model = defineModel<string>({ required: true })
|
|
|
24
24
|
v-model="model"
|
|
25
25
|
:data-testid="inputId"
|
|
26
26
|
:required
|
|
27
|
-
:invalid="!!error"
|
|
28
27
|
:label
|
|
29
28
|
:autofocus
|
|
30
29
|
:disabled
|
|
@@ -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.13",
|
|
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.12"
|
|
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
|
}
|