@sbc-connect/nuxt-base 0.1.22 → 0.1.24

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 CHANGED
@@ -1,5 +1,21 @@
1
1
  # @sbc-connect/nuxt-base
2
2
 
3
+ ## 0.1.24
4
+
5
+ ### Patch Changes
6
+
7
+ - [#80](https://github.com/bcgov/connect-nuxt/pull/80) [`0615542`](https://github.com/bcgov/connect-nuxt/commit/06155422d0a8d61ee862bb3483330610b4a9434d) Thanks [@deetz99](https://github.com/deetz99)! - feat: Move button control to base and handle loading state internally
8
+
9
+ ## 0.1.23
10
+
11
+ ### Patch Changes
12
+
13
+ - [#77](https://github.com/bcgov/connect-nuxt/pull/77) [`86ec3d0`](https://github.com/bcgov/connect-nuxt/commit/86ec3d03baa7dae3137f6c4c96a328f13eb46ce5) Thanks [@deetz99](https://github.com/deetz99)! - feat: Allow different element in ConnectI18nHelper and sanitize html.
14
+
15
+ - [#77](https://github.com/bcgov/connect-nuxt/pull/77) [`86ec3d0`](https://github.com/bcgov/connect-nuxt/commit/86ec3d03baa7dae3137f6c4c96a328f13eb46ce5) Thanks [@deetz99](https://github.com/deetz99)! - Update dependencies
16
+
17
+ - [#77](https://github.com/bcgov/connect-nuxt/pull/77) [`86ec3d0`](https://github.com/bcgov/connect-nuxt/commit/86ec3d03baa7dae3137f6c4c96a328f13eb46ce5) Thanks [@deetz99](https://github.com/deetz99)! - fix: reactivity issue in ConnectAddressDisplay component.
18
+
3
19
  ## 0.1.22
4
20
 
5
21
  ### Patch Changes
package/app/app.config.ts CHANGED
@@ -63,9 +63,6 @@ export default defineAppConfig({
63
63
  ]
64
64
  },
65
65
  button: {
66
- slots: {
67
- base: 'cursor-pointer'
68
- },
69
66
  compoundVariants: [
70
67
  {
71
68
  color: 'primary',
@@ -54,7 +54,9 @@ const getAddressDisplayParts = (
54
54
  ].filter(val => !!val && val !== ',')
55
55
  }
56
56
 
57
- const addressDisplay = getAddressDisplayParts(props.address, props.separateCity, props.textDecor, props.omitCountry)
57
+ const addressDisplay = computed<string[]>(() =>
58
+ getAddressDisplayParts(props.address, props.separateCity, props.textDecor, props.omitCountry)
59
+ )
58
60
  </script>
59
61
 
60
62
  <template>
@@ -0,0 +1,27 @@
1
+ <script setup lang="ts">
2
+ defineProps<{ buttons: ConnectButton[], position: 'left' | 'right' }>()
3
+
4
+ // remove alertText and removeAlertSpacing from being added to button html attrs
5
+ const removeButtonAttrs = (button: ConnectButton) => {
6
+ const newButton = { ...button }
7
+ delete newButton['alertText']
8
+ delete newButton['removeAlertSpacing']
9
+ return newButton
10
+ }
11
+ </script>
12
+
13
+ <template>
14
+ <div
15
+ v-for="(button, i) in buttons"
16
+ :key="`${position}-button-${i}`"
17
+ data-testid="button-wrapper"
18
+ >
19
+ <UButton
20
+ class="max-w-fit px-7 py-3 mb-1"
21
+ v-bind="removeButtonAttrs(button)"
22
+ />
23
+ <p :class="['text-center', !button.removeAlertSpacing ? 'h-[1.25rem]' : '']">
24
+ <span v-if="button.alertText" class="text-error">{{ button.alertText }}</span>
25
+ </p>
26
+ </div>
27
+ </template>
@@ -0,0 +1,66 @@
1
+ <script setup lang="ts">
2
+ const {
3
+ stateKey = 'default-button-control'
4
+ } = defineProps<{
5
+ stateKey?: string
6
+ }>()
7
+
8
+ const { buttonControl: meta } = useConnectButtonControl(stateKey)
9
+ const buttonControl = computed(() => meta.value)
10
+ const leftGroup = computed(() => buttonControl.value?.leftGroup || [])
11
+ const rightGroup = computed(() => buttonControl.value?.rightGroup || [])
12
+ const leftAlertText = computed(() => buttonControl.value?.leftGroup.alertText || undefined)
13
+ const rightAlertText = computed(() => buttonControl.value?.rightGroup.alertText || undefined)
14
+ </script>
15
+
16
+ <template>
17
+ <div
18
+ class="@container"
19
+ data-testid="connect-button-control"
20
+ >
21
+ <div class="grid grid-cols-1 gap-4 @3xl:grid-cols-2">
22
+ <div>
23
+ <div
24
+ class="flex justify-center @3xl:justify-start"
25
+ data-testid="left-buttons"
26
+ >
27
+ <div
28
+ class="relative flex flex-col flex-wrap gap-4 justify-center"
29
+ :class="leftGroup.stacked ? '' : 'flex-row'"
30
+ >
31
+ <div
32
+ v-if="leftAlertText"
33
+ class="absolute flex flex-wrap justify-start bottom-1/3 right-[-13rem] w-[12rem]"
34
+ >
35
+ <p class="text-error" role="alert">
36
+ {{ leftAlertText }}
37
+ </p>
38
+ </div>
39
+ <ConnectButtonControlButtons :buttons="leftGroup.buttons" position="left" />
40
+ </div>
41
+ </div>
42
+ </div>
43
+ <div>
44
+ <div
45
+ class="flex justify-center @3xl:justify-end"
46
+ data-testid="right-buttons"
47
+ >
48
+ <div
49
+ class="relative flex flex-col flex-wrap gap-4 justify-center"
50
+ :class="rightGroup.stacked ? '' : 'flex-row'"
51
+ >
52
+ <div
53
+ v-if="rightAlertText"
54
+ class="absolute flex flex-wrap justify-end bottom-1/3 left-[-13rem] w-[12rem]"
55
+ >
56
+ <p class="text-error" role="alert">
57
+ {{ rightAlertText }}
58
+ </p>
59
+ </div>
60
+ <ConnectButtonControlButtons :buttons="rightGroup.buttons" position="right" />
61
+ </div>
62
+ </div>
63
+ </div>
64
+ </div>
65
+ </div>
66
+ </template>
@@ -0,0 +1,18 @@
1
+ <script setup lang="ts">
2
+ const {
3
+ stateKey = 'default-button-control'
4
+ } = defineProps<{
5
+ stateKey?: string
6
+ }>()
7
+ </script>
8
+
9
+ <template>
10
+ <div
11
+ class="bg-shade-inverted pb-6 pt-10"
12
+ data-testid="connect-button-control-wrapper"
13
+ >
14
+ <div class="app-inner-container app-body">
15
+ <ConnectButtonControl :state-key="stateKey" />
16
+ </div>
17
+ </div>
18
+ </template>
@@ -1,6 +1,26 @@
1
1
  <script setup lang="ts">
2
- const props = defineProps<{
2
+ /* eslint-disable vue/no-v-html */
3
+ /* eslint-disable vue/no-v-text-v-html-on-component */
4
+
5
+ const {
6
+ translationPath,
7
+ as = 'span'
8
+ } = defineProps<{
3
9
  translationPath: string
10
+ as?: 'h1'
11
+ | 'h2'
12
+ | 'h3'
13
+ | 'h4'
14
+ | 'h5'
15
+ | 'h6'
16
+ | 'label'
17
+ | 'span'
18
+ | 'p'
19
+ | 'div'
20
+ | 'li'
21
+ | 'legend'
22
+ | 'strong'
23
+ | 'em'
4
24
  }>()
5
25
 
6
26
  const attrs = useAttrs()
@@ -14,11 +34,10 @@ const textToDisplay = computed(() => {
14
34
  italicStart: '<em>',
15
35
  italicEnd: '</em>'
16
36
  }
17
- return t(props.translationPath, translationProps)
37
+ return t(translationPath, translationProps)
18
38
  })
19
39
  </script>
20
40
 
21
41
  <template>
22
- <!-- eslint-disable-next-line vue/no-v-html -->
23
- <span v-html="textToDisplay" />
42
+ <component :is="as" v-html="$sanitize(textToDisplay)" />
24
43
  </template>
@@ -0,0 +1,146 @@
1
+ const defaultOptions: ConnectButtonControlOptions = {
2
+ handleLoading: true,
3
+ clearAlertTextOnClick: true
4
+ }
5
+
6
+ export const useConnectButtonControl = (
7
+ stateKey: string = 'default-button-control',
8
+ opts: Partial<ConnectButtonControlOptions> = {}
9
+ ) => {
10
+ const options = {
11
+ ...defaultOptions,
12
+ ...opts
13
+ }
14
+
15
+ const buttonControl = useState<ConnectButtonControl>(stateKey, () => ({
16
+ leftGroup: { buttons: [] },
17
+ rightGroup: { buttons: [] }
18
+ }))
19
+
20
+ const forEachButton = (update: (button: ConnectButton, group: 'left' | 'right', index: number) => void) => {
21
+ buttonControl.value.leftGroup?.buttons?.forEach((btn, index) => update(btn, 'left', index))
22
+ buttonControl.value.rightGroup?.buttons?.forEach((btn, index) => update(btn, 'right', index))
23
+ }
24
+
25
+ const forEachGroup = (update: (group: ConnectButtonGroup) => void) => {
26
+ if (buttonControl.value.leftGroup) {
27
+ update(buttonControl.value.leftGroup)
28
+ }
29
+ if (buttonControl.value.rightGroup) {
30
+ update(buttonControl.value.rightGroup)
31
+ }
32
+ }
33
+
34
+ async function setAlertText(text?: string, group?: 'left' | 'right', index?: number) {
35
+ forEachGroup(group => group.alertText = undefined)
36
+ forEachButton(btn => btn.alertText = undefined)
37
+
38
+ if (text && group !== undefined) {
39
+ await nextTick()
40
+
41
+ const buttonGroup = group === 'left' ? buttonControl.value.leftGroup : buttonControl.value.rightGroup
42
+
43
+ if (index !== undefined) {
44
+ const button = buttonGroup.buttons[index]
45
+ if (button) {
46
+ button.alertText = text
47
+ }
48
+ } else {
49
+ buttonGroup.alertText = text
50
+ }
51
+ }
52
+ }
53
+
54
+ const handleClick = (
55
+ originalClick: ConnectButton['onClick'],
56
+ group: 'left' | 'right',
57
+ index: number
58
+ ) => {
59
+ if (!originalClick || typeof originalClick !== 'function') {
60
+ return undefined
61
+ }
62
+
63
+ return async () => {
64
+ try {
65
+ if (options.clearAlertTextOnClick) {
66
+ await setAlertText(undefined)
67
+ }
68
+
69
+ if (options.handleLoading) {
70
+ const clickedButton = buttonControl.value[group === 'left' ? 'leftGroup' : 'rightGroup']?.buttons[index]
71
+ if (!clickedButton) {
72
+ return
73
+ }
74
+
75
+ clickedButton.loading = true
76
+ forEachButton((button, btnGroup, btnIndex) => {
77
+ if (btnGroup !== group || btnIndex !== index) {
78
+ button.disabled = true
79
+ }
80
+ })
81
+ }
82
+
83
+ await (originalClick as () => Promise<void> | void)()
84
+ } catch (error) {
85
+ if (import.meta.env.DEV) {
86
+ console.error(`Error on button click. group: ${group} index: ${index} error: ${error}`)
87
+ }
88
+ } finally {
89
+ if (options.handleLoading) {
90
+ forEachButton((button) => {
91
+ button.loading = false
92
+ button.disabled = false
93
+ })
94
+ }
95
+ }
96
+ }
97
+ }
98
+
99
+ function setButtonControl(config: ConnectButtonControl) {
100
+ const updateGroup = (config: { buttons: ConnectButton[] }, group: 'left' | 'right') => {
101
+ if (!config || !config.buttons.length) {
102
+ return { ...config, buttons: [] }
103
+ }
104
+ return {
105
+ ...config,
106
+ buttons: config.buttons.map((button, index) => ({
107
+ ...button,
108
+ onClick: handleClick(button.onClick, group, index)
109
+ }))
110
+ }
111
+ }
112
+
113
+ buttonControl.value = {
114
+ leftGroup: updateGroup(config.leftGroup, 'left'),
115
+ rightGroup: updateGroup(config.rightGroup, 'right')
116
+ }
117
+ }
118
+
119
+ function handleButtonLoading(
120
+ loading: boolean,
121
+ group?: 'left' | 'right',
122
+ index?: number
123
+ ) {
124
+ if (!loading) {
125
+ forEachButton((button) => {
126
+ button.loading = false
127
+ button.disabled = false
128
+ })
129
+ } else if (group !== undefined && index !== undefined) {
130
+ forEachButton((button, btnGroup, btnIndex) => {
131
+ if (btnGroup === group && btnIndex === index) {
132
+ button.loading = true
133
+ } else {
134
+ button.disabled = true
135
+ }
136
+ })
137
+ }
138
+ }
139
+
140
+ return {
141
+ setButtonControl,
142
+ setAlertText,
143
+ handleButtonLoading,
144
+ buttonControl
145
+ }
146
+ }
@@ -0,0 +1,22 @@
1
+ import type { ButtonProps } from '@nuxt/ui'
2
+
3
+ export interface ConnectButton extends ButtonProps {
4
+ alertText?: string
5
+ removeAlertSpacing?: boolean
6
+ }
7
+
8
+ export interface ConnectButtonGroup {
9
+ alertText?: string
10
+ buttons: ConnectButton[]
11
+ stacked?: boolean
12
+ }
13
+
14
+ export interface ConnectButtonControl {
15
+ leftGroup: ConnectButtonGroup
16
+ rightGroup: ConnectButtonGroup
17
+ }
18
+
19
+ export interface ConnectButtonControlOptions {
20
+ handleLoading: boolean
21
+ clearAlertTextOnClick: boolean
22
+ }
@@ -60,7 +60,10 @@ export default {
60
60
  strong: 'This should have {boldStart} bold {boldEnd} text',
61
61
  strongWithProps: 'This should have {boldStart} bold {boldEnd} text and allow a {prop}',
62
62
  italic: 'Italic test {italicStart} goes here {italicEnd}.',
63
- computed: 'This text will be updated. Updated {boldStart}{count}{boldEnd} times.'
63
+ computed: 'This text will be updated. Updated {boldStart}{count}{boldEnd} times.',
64
+ sanitized: () => {
65
+ return "This is <strong>safe bold text</strong>, with a sanitized <script>alert('XSS')</script> script element."
66
+ }
64
67
  }
65
68
  }
66
69
  }
@@ -276,14 +276,3 @@ body {
276
276
  @apply pointer-events-none absolute left-0 top-1 text-neutral text-xs font-medium px-2.5 transition-all duration-200 peer-focus:top-1 peer-focus:text-xs peer-placeholder-shown:text-sm peer-placeholder-shown:top-2.5 peer-focus:translate-none peer-focus:text-primary peer-aria-invalid:text-error;
277
277
  }
278
278
  }
279
-
280
- /* TODO: maybe delete this */
281
- /* Extra Utilities */
282
- /* This should be working out-of-the-box from Nuxt UI but isnt for some reason */
283
- /* https://ui.nuxt.com/getting-started/theme#neutral */
284
- /* .text-highlighted {
285
- color: var(--color-gray-900);
286
- @variant dark {
287
- color: var(--color-gray-900);
288
- }
289
- } */
package/nuxt.config.ts CHANGED
@@ -120,10 +120,9 @@ export default defineNuxtConfig({
120
120
  }
121
121
  },
122
122
 
123
- // removing this for now as its causing more issues
124
- // typescript: {
125
- // includeWorkspace: true // fixes auto imports in tests
126
- // },
123
+ typescript: {
124
+ includeWorkspace: true // allow auto imports in tests
125
+ },
127
126
 
128
127
  runtimeConfig: {
129
128
  public: {
package/package.json CHANGED
@@ -1,29 +1,30 @@
1
1
  {
2
2
  "name": "@sbc-connect/nuxt-base",
3
3
  "type": "module",
4
- "version": "0.1.22",
4
+ "version": "0.1.24",
5
5
  "repository": "github:bcgov/connect-nuxt",
6
6
  "license": "BSD-3-Clause",
7
7
  "main": "./nuxt.config.ts",
8
8
  "dependencies": {
9
9
  "@iconify-json/mdi": "^1.2.3",
10
- "@nuxt/ui": "^3.3.0",
11
- "@nuxtjs/i18n": "^10.0.3",
12
- "@vueuse/core": "^13.6.0",
13
- "@vueuse/nuxt": "^13.6.0",
14
- "dompurify": "^3.2.6",
15
- "es-toolkit": "^1.39.8",
10
+ "@nuxt/ui": "^4.1.0",
11
+ "@nuxtjs/i18n": "^10.1.2",
12
+ "@vueuse/core": "14.0.0",
13
+ "@vueuse/nuxt": "14.0.0",
14
+ "dompurify": "^3.3.0",
15
+ "es-toolkit": "^1.41.0",
16
16
  "launchdarkly-vue-client-sdk": "^2.4.1",
17
- "maska": "^3.2.0"
17
+ "maska": "^3.2.0",
18
+ "zod": "^4.1.12"
18
19
  },
19
20
  "devDependencies": {
20
- "@axe-core/playwright": "^4.10.2",
21
- "@vitest/coverage-v8": "^3.2.4",
22
- "dotenv": "^17.2.1",
23
- "nuxt": "^4.0.2",
24
- "typescript": "^5.8.3",
25
- "vue-tsc": "^3.0.4",
26
- "@sbc-connect/eslint-config": "0.0.7",
21
+ "@axe-core/playwright": "4.11.0",
22
+ "@vitest/coverage-v8": "3.2.4",
23
+ "dotenv": "17.2.3",
24
+ "nuxt": "4.1.3",
25
+ "typescript": "5.9.3",
26
+ "vue-tsc": "3.1.2",
27
+ "@sbc-connect/eslint-config": "0.0.8",
27
28
  "@sbc-connect/playwright-config": "0.0.7",
28
29
  "@sbc-connect/vitest-config": "0.0.6"
29
30
  },
@@ -39,6 +40,7 @@
39
40
  "test": "pnpm run test:unit; pnpm run test:e2e",
40
41
  "test:unit": "vitest run --passWithNoTests",
41
42
  "test:unit:watch": "vitest",
43
+ "test:unit:cov": "vitest --dir tests/unit --coverage",
42
44
  "test:e2e": "npx playwright test",
43
45
  "test:e2e:ui": "npx playwright test --ui",
44
46
  "typecheck": "npx nuxt typecheck"