@sbc-connect/nuxt-forms 0.1.10 → 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/.env.example CHANGED
@@ -8,4 +8,7 @@ NUXT_PUBLIC_STATUS_API_URL="https://status-api-dev.apps.gold.devops.gov.bc.ca"
8
8
  NUXT_PUBLIC_STATUS_API_VERSION="/api/v1"
9
9
 
10
10
  # LaunchDarkly
11
- NUXT_PUBLIC_LD_CLIENT_ID=""
11
+ NUXT_PUBLIC_LD_CLIENT_ID=""
12
+
13
+ # Canada Post
14
+ NUXT_PUBLIC_ADDRESS_COMPLETE_KEY=""
package/CHANGELOG.md CHANGED
@@ -1,5 +1,23 @@
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
+
12
+ ## 0.1.11
13
+
14
+ ### Patch Changes
15
+
16
+ - [#45](https://github.com/bcgov/connect-nuxt/pull/45) [`8eb83d3`](https://github.com/bcgov/connect-nuxt/commit/8eb83d3e4d0af392f3e7ca8661020e6ae3f4cc7d) Thanks [@deetz99](https://github.com/deetz99)! - Add a11y check, fix base a11y, add tests for forms - bcgov/entity#29335
17
+
18
+ - Updated dependencies [[`8eb83d3`](https://github.com/bcgov/connect-nuxt/commit/8eb83d3e4d0af392f3e7ca8661020e6ae3f4cc7d)]:
19
+ - @sbc-connect/nuxt-base@0.1.10
20
+
3
21
  ## 0.1.10
4
22
 
5
23
  ### 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
- <div :class="{ 'w-full sm:w-1/4': orientation === 'horizontal' }">
21
- <legend
22
- class="text-base text-neutral-highlighted font-bold"
23
- :class="{ 'text-red-600': !!error }"
24
- >
25
- <div class="flex flex-wrap gap-4">
26
- <span>{{ label }}</span>
27
- <span
28
- v-if="!!error && showErrorMsg"
29
- class="font-normal"
30
- >
31
- {{ error.message }}
32
- </span>
33
- </div>
34
- </legend>
35
- </div>
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
- const props = defineProps<{
3
- id: string
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 { iscCountriesListSortedByName as countries } from '#forms/app/utils/isoCountriesList'
2
+ import { isoCountriesListSortedByName as countries } from '#forms/app/utils/isoCountriesList'
3
3
 
4
- const props = defineProps<{
5
- id: string
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="inputId"
39
+ :id="`${parentId}-input-country`"
41
40
  v-model="model"
42
- :data-testid="inputId"
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
- const props = defineProps<{
3
- id: string
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
- :input-id="inputId"
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
- const props = defineProps<{
3
- id: string
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
- :input-id="inputId"
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
- id: string
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="inputId"
44
+ :id="`${parentId}-input-region`"
48
45
  v-model="model"
49
- :data-testid="inputId"
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="true"
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
- 'group-data-[state=open]:text-primary group-focus:text-primary',
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="inputId"
94
+ :id="`${parentId}-input-region`"
95
95
  v-model="model"
96
- :invalid="!!error"
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="!help && !error"
102
+ v-if="!$slots.help && !error"
103
103
  class="h-4 mt-1"
104
104
  />
105
105
  </template>
@@ -1,42 +1,41 @@
1
1
  <script setup lang="ts">
2
2
  const {
3
- id,
4
3
  schemaPrefix,
5
- helpText = 'none'
4
+ helpText = 'none',
5
+ country,
6
+ disableAddressComplete
6
7
  } = defineProps<{
7
- id: string
8
- // country?: string
8
+ parentId: string
9
+ country?: string
9
10
  schemaPrefix: string
10
- // disableAddressComplete?: boolean
11
+ disableAddressComplete?: boolean
11
12
  helpText?: 'allow-po' | 'no-po' | 'none'
12
13
  }>()
13
14
 
14
15
  const model = defineModel<string>({ default: '' })
15
16
 
16
- // TODO: add address complete, and address complete disable options
17
- // const emit = defineEmits<{
18
- // addressComplete: [value: ConnectAddress]
19
- // }>()
20
-
21
- const inputId = id + '-street'
17
+ const emit = defineEmits<{
18
+ addressComplete: [value: ConnectAddress]
19
+ }>()
22
20
 
23
- // const { address: canadaPostAddress, enableAddressComplete } = useCanadaPost()
21
+ const { address: canadaPostAddress, enableAddressComplete } = useCanadaPost()
24
22
 
25
- // const addressComplete = (id: string) => {
26
- // if (props.country && props.country.trim() !== '') {
27
- // enableAddressComplete(id, props.country, props.disableAddressComplete)
28
- // }
29
- // }
23
+ const addressComplete = (id: string) => {
24
+ if (!disableAddressComplete && country && country.trim() !== '') {
25
+ enableAddressComplete(id, country, false)
26
+ }
27
+ }
30
28
 
31
- // watch(canadaPostAddress, (newAddress) => {
32
- // emit('addressComplete', newAddress)
33
- // })
29
+ watch(canadaPostAddress, (newAddress) => {
30
+ emit('addressComplete', newAddress)
31
+ })
34
32
  </script>
35
33
 
36
34
  <template>
37
35
  <ConnectFormInput
38
36
  v-model="model"
39
- :input-id="inputId"
37
+ :data-testid="`${parentId}-field-street`"
38
+ :input-id="`${parentId}-input-street`"
40
39
  :name="schemaPrefix + '.street'"
41
40
  :help="helpText === 'none'
42
41
  ? undefined
@@ -46,7 +45,7 @@ const inputId = id + '-street'
46
45
  "
47
46
  :label="$t('connect.label.street')"
48
47
  required
48
+ @keypress.once="addressComplete(`${parentId}-input-street`)"
49
+ @click="addressComplete(`${parentId}-input-street`)"
49
50
  />
50
- <!-- @keypress.once="addressComplete(inputId)"
51
- @click="addressComplete(inputId)" -->
52
51
  </template>
@@ -1,19 +1,18 @@
1
1
  <script setup lang="ts">
2
- const props = defineProps<{
3
- id: string
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
- :input-id="inputId"
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
- // emit valid keys to be validated by parent UForm
41
- emit('shouldValidate', validKeys.map(k => schemaPrefix + k) as Array<keyof ConnectAddress>)
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="!help && !error"
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="!help && !error"
30
+ v-if="!$slots.help && !error"
34
31
  class="h-4 mt-1"
35
32
  />
36
33
  </template>