@sbc-connect/nuxt-forms 0.1.9 → 0.1.11

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,28 @@
1
1
  # @sbc-connect/nuxt-forms
2
2
 
3
+ ## 0.1.11
4
+
5
+ ### Patch Changes
6
+
7
+ - [#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
8
+
9
+ - Updated dependencies [[`8eb83d3`](https://github.com/bcgov/connect-nuxt/commit/8eb83d3e4d0af392f3e7ca8661020e6ae3f4cc7d)]:
10
+ - @sbc-connect/nuxt-base@0.1.10
11
+
12
+ ## 0.1.10
13
+
14
+ ### Patch Changes
15
+
16
+ - [#43](https://github.com/bcgov/connect-nuxt/pull/43) [`3b521b7`](https://github.com/bcgov/connect-nuxt/commit/3b521b70bf1a0cc5a0feea63a825ef5544347aa8) Thanks [@deetz99](https://github.com/deetz99)! - Form components and new lint rules
17
+
18
+ - Forms layer address components
19
+ - Lint rules and fixes to prevent rule collision and promote best practices
20
+
21
+ issue: bcgov/entity#29338
22
+
23
+ - Updated dependencies [[`3b521b7`](https://github.com/bcgov/connect-nuxt/commit/3b521b70bf1a0cc5a0feea63a825ef5544347aa8)]:
24
+ - @sbc-connect/nuxt-base@0.1.9
25
+
3
26
  ## 0.1.9
4
27
 
5
28
  ### Patch Changes
@@ -0,0 +1,41 @@
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
+ <fieldset
17
+ class="flex"
18
+ :class="[orientation === 'horizontal' ? 'flex-col gap-6 sm:flex-row sm:gap-4' : 'flex-col gap-6']"
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>
36
+
37
+ <div class="flex-1">
38
+ <slot />
39
+ </div>
40
+ </fieldset>
41
+ </template>
@@ -0,0 +1,20 @@
1
+ <script setup lang="ts">
2
+ const props = defineProps<{
3
+ id: string
4
+ schemaPrefix: string
5
+ }>()
6
+
7
+ const model = defineModel<string>({ default: '' })
8
+
9
+ const inputId = props.id + '-city'
10
+ </script>
11
+
12
+ <template>
13
+ <ConnectFormInput
14
+ v-model="model"
15
+ :name="schemaPrefix + '.city'"
16
+ :input-id="inputId"
17
+ required
18
+ :label="$t('connect.label.city')"
19
+ />
20
+ </template>
@@ -0,0 +1,94 @@
1
+ <script setup lang="ts">
2
+ import { iscCountriesListSortedByName as countries } from '#forms/app/utils/isoCountriesList'
3
+
4
+ const props = defineProps<{
5
+ id: string
6
+ schemaPrefix: string
7
+ disabled?: boolean
8
+ }>()
9
+
10
+ defineEmits<{
11
+ change: []
12
+ }>()
13
+
14
+ const model = defineModel<string>({ default: '' })
15
+
16
+ const inputId = props.id + '-country'
17
+
18
+ const displayedCountryName = computed(() => {
19
+ if (model.value) {
20
+ const found = countries.find(c => c.alpha_2 === model.value)?.name
21
+ return found ?? ''
22
+ }
23
+ return ''
24
+ })
25
+
26
+ const options = [
27
+ countries.find(c => c.alpha_2 === 'CA')!,
28
+ countries.find(c => c.alpha_2 === 'US')!,
29
+ ...countries
30
+ ]
31
+ </script>
32
+
33
+ <template>
34
+ <UFormField
35
+ :name="schemaPrefix + '.country'"
36
+ class="grow"
37
+ >
38
+ <template #default="{ error }">
39
+ <USelect
40
+ :id="inputId"
41
+ v-model="model"
42
+ :data-testid="inputId"
43
+ :items="options"
44
+ value-key="alpha_2"
45
+ label-key="name"
46
+ class="w-full"
47
+ :class="error ? 'shadow-input-error ring-0' : ''"
48
+ :disabled
49
+ :aria-label="$t('connect.label.country')"
50
+ :aria-required="true"
51
+ :ui="{
52
+ base: error
53
+ ? 'shadow-input-error focus:shadow-input-error data-[state=open]:shadow-input-error'
54
+ : '',
55
+ item: 'nth-2:border-b nth-2:border-gray-200',
56
+ itemTrailingIcon: 'hidden',
57
+ }"
58
+ @change="$emit('change')"
59
+ >
60
+ <template #default="{ modelValue }">
61
+ <div class="relative px-2.5 pb-2 pt-6 w-full">
62
+ <span
63
+ aria-hidden="true"
64
+ :class="[
65
+ !modelValue
66
+ ? 'top-1/2 -translate-y-1/2'
67
+ : 'top-1 -translate-y-none text-xs',
68
+ error
69
+ ? 'text-red-600'
70
+ : '',
71
+ 'absolute left-0 px-2.5 text-sm transition-all',
72
+ 'group-data-[state=open]:text-primary group-focus:text-primary',
73
+ ]"
74
+ >
75
+ {{ $t('connect.label.country') }}
76
+ </span>
77
+ <div class="h-6">
78
+ <span
79
+ v-if="modelValue"
80
+ class="line-clamp-1 text-left"
81
+ >
82
+ {{ displayedCountryName }}
83
+ </span>
84
+ </div>
85
+ </div>
86
+ </template>
87
+ </USelect>
88
+ <div
89
+ v-if="!$slots.help && !error"
90
+ class="h-4 mt-1"
91
+ />
92
+ </template>
93
+ </UFormField>
94
+ </template>
@@ -0,0 +1,21 @@
1
+ <script setup lang="ts">
2
+ const props = defineProps<{
3
+ id: string
4
+ schemaPrefix: string
5
+ disabled?: boolean
6
+ }>()
7
+
8
+ const model = defineModel<string>({ default: '' })
9
+
10
+ const inputId = props.id + '-locationDescription'
11
+ </script>
12
+
13
+ <template>
14
+ <ConnectFormTextarea
15
+ v-model="model"
16
+ :input-id="inputId"
17
+ :name="schemaPrefix + '.locationDescription'"
18
+ :disabled
19
+ :label="$t('connect.label.deliveryInstructionsOpt')"
20
+ />
21
+ </template>
@@ -0,0 +1,37 @@
1
+ <script setup lang="ts">
2
+ const props = defineProps<{
3
+ id: string
4
+ schemaPrefix: string
5
+ disabled?: boolean
6
+ country?: string
7
+ }>()
8
+
9
+ const model = defineModel({
10
+ set(value) {
11
+ return value.toUpperCase()
12
+ },
13
+ type: String,
14
+ default: ''
15
+ })
16
+
17
+ const inputId = props.id + '-postalCode'
18
+ </script>
19
+
20
+ <template>
21
+ <ConnectFormInput
22
+ v-model="model"
23
+ :name="schemaPrefix + '.postalCode'"
24
+ :input-id="inputId"
25
+ :disabled
26
+ required
27
+ :label="country === 'US'
28
+ ? $t('connect.label.zipCode')
29
+ : $t('connect.label.postalCode')
30
+ "
31
+ :mask="country === 'CA'
32
+ ? 'V#@ #@#'
33
+ : country === 'US'
34
+ ? '#####-####'
35
+ : undefined"
36
+ />
37
+ </template>
@@ -0,0 +1,107 @@
1
+ <script setup lang="ts">
2
+ const {
3
+ maxlength = '1000',
4
+ country,
5
+ id
6
+ } = defineProps<{
7
+ id: string
8
+ country?: string
9
+ schemaPrefix: string
10
+ disabled?: boolean
11
+ maxlength?: string
12
+ help?: string
13
+ }>()
14
+
15
+ const model = defineModel<string>({ default: '' })
16
+
17
+ const regions = computed(() => {
18
+ switch (country) {
19
+ case 'US':
20
+ return countrySubdivisions.us
21
+ case 'CA':
22
+ return countrySubdivisions.ca
23
+ default:
24
+ return []
25
+ }
26
+ })
27
+
28
+ const displayedRegionName = computed(() => {
29
+ if (model.value) {
30
+ const found = regions.value.find(r => r.code === model.value)?.name
31
+ return found ?? ''
32
+ }
33
+ return ''
34
+ })
35
+
36
+ const inputId = id + '-region'
37
+ </script>
38
+
39
+ <template>
40
+ <UFormField
41
+ :name="schemaPrefix + '.region'"
42
+ class="grow flex-1"
43
+ >
44
+ <template #default="{ error }">
45
+ <USelect
46
+ v-if="country === 'US' || country === 'CA'"
47
+ :id="inputId"
48
+ v-model="model"
49
+ :data-testid="inputId"
50
+ :items="regions"
51
+ :aria-label="country === 'CA' ? $t('connect.label.province') : $t('connect.label.state')"
52
+ value-key="code"
53
+ label-key="name"
54
+ :aria-required="true"
55
+ :disabled
56
+ class="w-full grow ring-0"
57
+ :ui="{
58
+ base: error
59
+ ? 'shadow-input-error focus:shadow-input-error data-[state=open]:shadow-input-error'
60
+ : '',
61
+ }"
62
+ >
63
+ <template #default="{ modelValue }">
64
+ <div class="relative px-2.5 pb-2 pt-6 w-full">
65
+ <span
66
+ aria-hidden="true"
67
+ class="absolute left-0 px-2.5 text-sm transition-all"
68
+ :class="[
69
+ !modelValue
70
+ ? 'top-1/2 -translate-y-1/2'
71
+ : 'top-1 -translate-y-none text-xs',
72
+ error
73
+ ? 'text-error'
74
+ : '',
75
+ 'absolute left-0 px-2.5 text-sm transition-all',
76
+ 'group-data-[state=open]:text-primary group-focus:text-primary',
77
+ ]"
78
+ >
79
+ {{ country === 'CA' ? $t('connect.label.province') : $t('connect.label.state') }}
80
+ </span>
81
+ <div class="h-6">
82
+ <span
83
+ v-if="modelValue"
84
+ class="line-clamp-1 text-left"
85
+ >
86
+ {{ displayedRegionName }}
87
+ </span>
88
+ </div>
89
+ </div>
90
+ </template>
91
+ </USelect>
92
+ <ConnectInput
93
+ v-else
94
+ :id="inputId"
95
+ v-model="model"
96
+ :invalid="!!error"
97
+ :disabled
98
+ :label="$t('connect.label.regionOpt')"
99
+ :maxlength
100
+ />
101
+ <div
102
+ v-if="!help && !error"
103
+ class="h-4 mt-1"
104
+ />
105
+ </template>
106
+ </UFormField>
107
+ </template>
@@ -0,0 +1,53 @@
1
+ <script setup lang="ts">
2
+ const {
3
+ id,
4
+ schemaPrefix,
5
+ helpText = 'none',
6
+ country,
7
+ disableAddressComplete
8
+ } = defineProps<{
9
+ id: string
10
+ country?: string
11
+ schemaPrefix: string
12
+ disableAddressComplete?: boolean
13
+ helpText?: 'allow-po' | 'no-po' | 'none'
14
+ }>()
15
+
16
+ const model = defineModel<string>({ default: '' })
17
+
18
+ const emit = defineEmits<{
19
+ addressComplete: [value: ConnectAddress]
20
+ }>()
21
+
22
+ const inputId = id + '-street'
23
+
24
+ const { address: canadaPostAddress, enableAddressComplete } = useCanadaPost()
25
+
26
+ const addressComplete = (id: string) => {
27
+ if (!disableAddressComplete && country && country.trim() !== '') {
28
+ enableAddressComplete(id, country, false)
29
+ }
30
+ }
31
+
32
+ watch(canadaPostAddress, (newAddress) => {
33
+ emit('addressComplete', newAddress)
34
+ })
35
+ </script>
36
+
37
+ <template>
38
+ <ConnectFormInput
39
+ v-model="model"
40
+ :input-id="inputId"
41
+ :name="schemaPrefix + '.street'"
42
+ :help="helpText === 'none'
43
+ ? undefined
44
+ : helpText === 'no-po'
45
+ ? $t('connect.text.addressCannotBePOBox')
46
+ : $t('connect.text.addressCanBePOBox')
47
+ "
48
+ :label="$t('connect.label.street')"
49
+ required
50
+ @keypress.once="addressComplete(inputId)"
51
+ @click="addressComplete(inputId)"
52
+ />
53
+ </template>
@@ -0,0 +1,19 @@
1
+ <script setup lang="ts">
2
+ const props = defineProps<{
3
+ id: string
4
+ schemaPrefix: string
5
+ }>()
6
+
7
+ const model = defineModel<string>({ default: '' })
8
+
9
+ const inputId = props.id + '-streetAdditional'
10
+ </script>
11
+
12
+ <template>
13
+ <ConnectFormInput
14
+ v-model="model"
15
+ :name="schemaPrefix + '.streetAdditional'"
16
+ :input-id="inputId"
17
+ :label="$t('connect.label.streetAdditional')"
18
+ />
19
+ </template>
@@ -0,0 +1,112 @@
1
+ <script setup lang="ts">
2
+ const {
3
+ disabledFields,
4
+ excludedFields = ['streetName', 'streetNumber', 'unitNumber'],
5
+ schemaPrefix,
6
+ streetHelpText = 'none'
7
+ } = defineProps<{
8
+ id: string
9
+ schemaPrefix: string
10
+ disabledFields?: Array<keyof ConnectAddress>
11
+ excludedFields?: Array<keyof ConnectAddress>
12
+ disableAddressComplete?: boolean
13
+ streetHelpText?: 'allow-po' | 'no-po' | 'none'
14
+ }>()
15
+
16
+ const emit = defineEmits<{
17
+ shouldValidate: [keys: Array<keyof ConnectAddress>]
18
+ }>()
19
+
20
+ const state = defineModel<Partial<ConnectAddress>>({ required: true })
21
+
22
+ async function populateAddressComplete(e: ConnectAddress) {
23
+ // get disabled/excluded fields
24
+ const disabledSet = new Set(disabledFields)
25
+ const excludedSet = new Set(excludedFields)
26
+
27
+ // get array of valid keys
28
+ const validKeys = (Object.keys(e) as Array<keyof ConnectAddress>).filter(key =>
29
+ !disabledSet.has(key) && !excludedSet.has(key)
30
+ )
31
+
32
+ // set state for each valid key
33
+ validKeys.forEach((key) => {
34
+ state.value[key] = e[key]
35
+ })
36
+
37
+ // wait for dom to populate inputs
38
+ await nextTick()
39
+
40
+ // emit valid keys to be validated by parent UForm
41
+ emit('shouldValidate', validKeys.map(k => schemaPrefix + k) as Array<keyof ConnectAddress>)
42
+ }
43
+ </script>
44
+
45
+ <template>
46
+ <div class="space-y-2" :data-testid="id">
47
+ <ConnectFormAddressCountry
48
+ v-if="!excludedFields.includes('country')"
49
+ :id
50
+ v-model="state.country"
51
+ :schema-prefix="schemaPrefix"
52
+ :disabled="disabledFields?.includes('country')"
53
+ @change="state.region = ''"
54
+ />
55
+
56
+ <ConnectFormAddressStreet
57
+ v-if="!excludedFields.includes('street')"
58
+ :id
59
+ v-model="state.street"
60
+ :schema-prefix="schemaPrefix"
61
+ :country="state.country"
62
+ :disable-address-complete="disableAddressComplete"
63
+ :disabled="disabledFields?.includes('street')"
64
+ :help-text="streetHelpText"
65
+ @address-complete="populateAddressComplete"
66
+ />
67
+
68
+ <ConnectFormAddressStreetAdditional
69
+ v-if="!excludedFields.includes('streetAdditional')"
70
+ :id
71
+ v-model="state.streetAdditional"
72
+ :schema-prefix="schemaPrefix"
73
+ :disabled="disabledFields?.includes('streetAdditional')"
74
+ />
75
+
76
+ <div class="flex flex-col gap-2 sm:gap-4 sm:flex-row">
77
+ <ConnectFormAddressCity
78
+ v-if="!excludedFields.includes('city')"
79
+ :id
80
+ v-model="state.city"
81
+ :schema-prefix="schemaPrefix"
82
+ :disabled="disabledFields?.includes('city')"
83
+ />
84
+
85
+ <ConnectFormAddressRegion
86
+ v-if="!excludedFields.includes('region')"
87
+ :id
88
+ v-model="state.region"
89
+ :schema-prefix="schemaPrefix"
90
+ :country="state.country"
91
+ :disabled="disabledFields?.includes('region')"
92
+ />
93
+
94
+ <ConnectFormAddressPostalCode
95
+ v-if="!excludedFields.includes('postalCode')"
96
+ :id
97
+ v-model="state.postalCode"
98
+ :schema-prefix="schemaPrefix"
99
+ :country="state.country"
100
+ :disabled="disabledFields?.includes('postalCode')"
101
+ />
102
+ </div>
103
+
104
+ <ConnectFormAddressDeliveryInstructions
105
+ v-if="!excludedFields.includes('locationDescription')"
106
+ :id
107
+ v-model="state.locationDescription"
108
+ :schema-prefix="schemaPrefix"
109
+ :disabled="disabledFields?.includes('locationDescription')"
110
+ />
111
+ </div>
112
+ </template>
@@ -0,0 +1,39 @@
1
+ <script setup lang="ts">
2
+ defineProps<{
3
+ name: string
4
+ label: string
5
+ help?: string
6
+ inputId: string
7
+ required?: boolean
8
+ autofocus?: boolean
9
+ mask?: string
10
+ disabled?: boolean
11
+ }>()
12
+
13
+ const model = defineModel<string>({ required: true })
14
+ </script>
15
+
16
+ <template>
17
+ <UFormField
18
+ :name
19
+ :help
20
+ class="grow flex-1"
21
+ >
22
+ <template #default="{ error }">
23
+ <ConnectInput
24
+ :id="inputId"
25
+ v-model="model"
26
+ :mask
27
+ :required
28
+ :invalid="!!error"
29
+ :label
30
+ :autofocus
31
+ :disabled
32
+ />
33
+ <div
34
+ v-if="!help && !error"
35
+ class="h-4 mt-1"
36
+ />
37
+ </template>
38
+ </UFormField>
39
+ </template>
@@ -0,0 +1,38 @@
1
+ <script setup lang="ts">
2
+ defineProps<{
3
+ name: string
4
+ label: string
5
+ help?: string
6
+ inputId: string
7
+ required?: boolean
8
+ autofocus?: boolean
9
+ disabled?: boolean
10
+ }>()
11
+
12
+ const model = defineModel<string>({ required: true })
13
+ </script>
14
+
15
+ <template>
16
+ <UFormField
17
+ :name
18
+ :help
19
+ class="grow flex-1"
20
+ >
21
+ <template #default="{ error }">
22
+ <ConnectTextarea
23
+ :id="inputId"
24
+ v-model="model"
25
+ :data-testid="inputId"
26
+ :required
27
+ :invalid="!!error"
28
+ :label
29
+ :autofocus
30
+ :disabled
31
+ />
32
+ <div
33
+ v-if="!help && !error"
34
+ class="h-4 mt-1"
35
+ />
36
+ </template>
37
+ </UFormField>
38
+ </template>