@sbc-connect/nuxt-base 0.1.4 → 0.1.6

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.
Files changed (48) hide show
  1. package/CHANGELOG.md +38 -0
  2. package/README.md +1 -1
  3. package/app/app.config.ts +167 -16
  4. package/app/components/Connect/Address/Display.vue +77 -0
  5. package/app/components/Connect/Breadcrumb.vue +2 -2
  6. package/app/components/Connect/Footer.vue +3 -3
  7. package/app/components/Connect/Header/Wrapper.vue +2 -2
  8. package/app/components/Connect/Header/index.vue +4 -5
  9. package/app/components/Connect/I18n/Helper.vue +24 -0
  10. package/app/components/Connect/Input.vue +32 -0
  11. package/app/components/Connect/Layout/Index.vue +23 -0
  12. package/app/components/Connect/Modal/index.vue +51 -0
  13. package/app/components/Connect/PageSection.vue +57 -0
  14. package/app/components/Connect/Slideover/WhatsNew.vue +4 -4
  15. package/app/components/Connect/Spinner.vue +94 -0
  16. package/app/components/Connect/SystemBanner.vue +8 -8
  17. package/app/components/Connect/Textarea.vue +30 -0
  18. package/app/components/Connect/Transition/Collapse.vue +39 -0
  19. package/app/composables/useConnectModal.ts +11 -0
  20. package/app/interfaces/connect-address.ts +12 -0
  21. package/app/interfaces/connect-modal.ts +5 -0
  22. package/app/layouts/ConnectBase.vue +3 -13
  23. package/app/plugins/maska.client.ts +10 -0
  24. package/app/plugins/whats-new.client.ts +1 -1
  25. package/app/spa-loading-template.html +60 -38
  26. package/app/types/base-app-config.d.ts +15 -0
  27. package/app/types/base-page-meta.d.ts +8 -0
  28. package/app/utils/logFetchError.ts +36 -0
  29. package/app/utils/parseSpecialCharacters.ts +25 -0
  30. package/app/utils/setBreadcrumbs.ts +5 -0
  31. package/i18n/locales/en-CA.ts +3 -2
  32. package/modules/base-assets/index.ts +30 -0
  33. package/modules/{assets/runtime/assets/core-tw.css → base-assets/runtime/assets/connect-base-tw.css} +48 -46
  34. package/nuxt.config.ts +43 -17
  35. package/package.json +9 -9
  36. package/app/plugins/launchdarkly.client.ts +0 -9
  37. package/modules/assets/index.ts +0 -24
  38. package/modules/assets/runtime/assets/core-main.css +0 -6
  39. /package/modules/{assets/runtime/assets/core-layout.css → base-assets/runtime/assets/connect-base-layout.css} +0 -0
  40. /package/{public → modules/base-assets/runtime/public}/fonts/BCSans/BCSans-Bold.woff +0 -0
  41. /package/{public → modules/base-assets/runtime/public}/fonts/BCSans/BCSans-Bold.woff2 +0 -0
  42. /package/{public → modules/base-assets/runtime/public}/fonts/BCSans/BCSans-BoldItalic.woff +0 -0
  43. /package/{public → modules/base-assets/runtime/public}/fonts/BCSans/BCSans-BoldItalic.woff2 +0 -0
  44. /package/{public → modules/base-assets/runtime/public}/fonts/BCSans/BCSans-Italic.woff +0 -0
  45. /package/{public → modules/base-assets/runtime/public}/fonts/BCSans/BCSans-Italic.woff2 +0 -0
  46. /package/{public → modules/base-assets/runtime/public}/fonts/BCSans/BCSans-Regular.woff +0 -0
  47. /package/{public → modules/base-assets/runtime/public}/fonts/BCSans/BCSans-Regular.woff2 +0 -0
  48. /package/{public → modules/base-assets/runtime/public}/fonts/BCSans/LICENSE_OFL.txt +0 -0
@@ -0,0 +1,94 @@
1
+ <script setup lang="ts">
2
+ const props = defineProps({
3
+ fullscreen: {
4
+ type: Boolean,
5
+ default: false
6
+ },
7
+ size: {
8
+ type: Number,
9
+ default: 75
10
+ }
11
+ })
12
+
13
+ const spinnerStyle = computed(() => ({
14
+ '--spinner-size': `${props.size}px`,
15
+ '--spinner-margin': `${props.size / -2}px`
16
+ }))
17
+ </script>
18
+
19
+ <template>
20
+ <!-- eslint-disable tailwindcss/no-custom-classname -->
21
+ <Teleport v-if="fullscreen" to="body">
22
+ <div class="fixed inset-0 z-[999] h-screen w-screen bg-gray-100 opacity-50" />
23
+ <svg
24
+ class="spinner fixed"
25
+ viewBox="0 0 50 50"
26
+ :style="spinnerStyle"
27
+ >
28
+ <circle
29
+ class="path"
30
+ cx="25"
31
+ cy="25"
32
+ r="20"
33
+ fill="none"
34
+ stroke-width="5"
35
+ />
36
+ </svg>
37
+ </Teleport>
38
+ <svg
39
+ v-else
40
+ class="spinner absolute"
41
+ viewBox="0 0 50 50"
42
+ :style="spinnerStyle"
43
+ >
44
+ <circle
45
+ class="path"
46
+ cx="25"
47
+ cy="25"
48
+ r="20"
49
+ fill="none"
50
+ stroke-width="5"
51
+ />
52
+ </svg>
53
+ </template>
54
+
55
+ <style scoped>
56
+ .spinner {
57
+ animation: rotate 1.5s linear infinite;
58
+ z-index: 10000;
59
+ top: 50%;
60
+ left: 50%;
61
+ margin-top: var(--spinner-margin, -37.5px);
62
+ margin-left: var(--spinner-margin, -37.5px);
63
+ width: var(--spinner-size, 75px);
64
+ height: var(--spinner-size, 75px);
65
+ transform-origin: 50% 50%;
66
+ }
67
+
68
+ .spinner .path {
69
+ stroke: #1669bb;
70
+ stroke-linecap: round;
71
+ animation: dash 1.25s ease-in-out infinite;
72
+ }
73
+
74
+ @keyframes rotate {
75
+ 100% {
76
+ transform: rotate(360deg);
77
+ }
78
+ }
79
+
80
+ @keyframes dash {
81
+ 0% {
82
+ stroke-dasharray: 1, 150;
83
+ stroke-dashoffset: 0;
84
+ }
85
+ 50% {
86
+ stroke-dasharray: 90, 150;
87
+ stroke-dashoffset: -35;
88
+ }
89
+ 100% {
90
+ stroke-dasharray: 90, 150;
91
+ stroke-dashoffset: -124;
92
+ }
93
+ }
94
+ </style>
@@ -15,7 +15,7 @@ onMounted(async () => {
15
15
  </script>
16
16
 
17
17
  <template>
18
- <div class="bg-brandLight px-2">
18
+ <div class="bg-brand-inverted">
19
19
  <UAlert
20
20
  v-show="!!message && !close"
21
21
  class="py-0"
@@ -23,18 +23,18 @@ onMounted(async () => {
23
23
  :close="dismissible"
24
24
  close-icon="i-mdi-close"
25
25
  :ui="{
26
- root: 'rounded-none bg-brandLight p-0 app-inner-container',
27
- wrapper: 'bg-brandLight',
28
- close: 'mt-2 text-neutralDark',
26
+ root: 'rounded-none bg-brand-inverted p-2 sm:px-4 app-inner-container',
27
+ wrapper: 'bg-brand-inverted',
28
+ close: 'mt-2 text-neutral-highlighted',
29
29
  }"
30
30
  @update:open="close = true"
31
31
  >
32
- <!-- dismissible ? { class: 'pr-2 text-neutralDark' } : null -->
32
+ <!-- dismissible ? { class: 'pr-2 text-neutral-highlighted' } : null -->
33
33
  <template #description>
34
34
  <div class="flex gap-2 items-center py-2">
35
- <UIcon class="size-7 shrink-0 text-neutralDark self-start" :name="icon" />
35
+ <UIcon class="size-7 shrink-0 text-neutral-highlighted self-start" :name="icon" />
36
36
  <!-- eslint-disable vue/no-v-html tailwindcss/no-custom-classname -->
37
- <p class="vhtml text-neutralDark" v-html="message" />
37
+ <p class="vhtml text-neutral-highlighted" v-html="message" />
38
38
  </div>
39
39
  </template>
40
40
  </UAlert>
@@ -44,7 +44,7 @@ onMounted(async () => {
44
44
  <!-- must style globally for vhtml style to work -->
45
45
  <style>
46
46
  .vhtml > a {
47
- color: var(--ui-neutralDark);
47
+ color: var(--ui-neutral-highlighted);
48
48
  text-decoration: underline;
49
49
  }
50
50
  </style>
@@ -0,0 +1,30 @@
1
+ <script setup lang="ts">
2
+ const model = defineModel<string>({ required: true })
3
+
4
+ defineProps<{
5
+ id: string
6
+ label: string
7
+ invalid?: boolean
8
+ }>()
9
+ </script>
10
+
11
+ <template>
12
+ <UTextarea
13
+ :id
14
+ v-model.trim="model"
15
+ :data-testid="id"
16
+ placeholder="&nbsp;"
17
+ class="w-full grow"
18
+ :ui="{
19
+ base: invalid ? 'ring-0 shadow-input-error focus:shadow-input-error resize-none' : 'resize-none',
20
+ }"
21
+ >
22
+ <label
23
+ class="floating-label-textarea bg-shade w-[96%] flex-1 pt-1 -mt-1"
24
+ :class="invalid ? 'text-error' : ''"
25
+ :for="id"
26
+ >
27
+ {{ label }}
28
+ </label>
29
+ </UTextarea>
30
+ </template>
@@ -0,0 +1,39 @@
1
+ <template>
2
+ <Transition name="collapse">
3
+ <slot />
4
+ </Transition>
5
+ </template>
6
+
7
+ <style>
8
+ .collapse-enter-active,
9
+ .collapse-leave-active {
10
+ transition: max-height 0.3s ease-in-out, opacity 0.3s ease-in-out;
11
+ }
12
+ .collapse-enter-from,
13
+ .collapse-leave-to {
14
+ max-height: 0;
15
+ opacity: 0;
16
+ }
17
+ .collapse-enter-to,
18
+ .collapse-leave-from {
19
+ max-height: 1000px;
20
+ opacity: 1;
21
+ }
22
+
23
+ @media (prefers-reduced-motion) {
24
+ .collapse-enter-active,
25
+ .collapse-leave-active {
26
+ transition: max-height 0s ease-out, opacity 0s ease-out;
27
+ }
28
+ .collapse-enter-from,
29
+ .collapse-leave-to {
30
+ max-height: 0;
31
+ opacity: 0;
32
+ }
33
+ .collapse-enter-to,
34
+ .collapse-leave-from {
35
+ max-height: 1000px;
36
+ opacity: 1;
37
+ }
38
+ }
39
+ </style>
@@ -0,0 +1,11 @@
1
+ import { ConnectModal } from '#components'
2
+
3
+ export const useConnectModal = () => {
4
+ const overlay = useOverlay()
5
+
6
+ const baseModal = overlay.create(ConnectModal)
7
+
8
+ return {
9
+ baseModal
10
+ }
11
+ }
@@ -0,0 +1,12 @@
1
+ export interface ConnectAddress {
2
+ street: string
3
+ streetAdditional: string
4
+ city: string
5
+ region: string
6
+ postalCode: string
7
+ country: string
8
+ locationDescription: string
9
+ streetName?: string
10
+ streetNumber?: string
11
+ unitNumber?: string
12
+ }
@@ -0,0 +1,5 @@
1
+ import type { ButtonProps } from '@nuxt/ui'
2
+
3
+ export interface ConnectModalButton extends ButtonProps {
4
+ shouldClose?: boolean
5
+ }
@@ -1,15 +1,5 @@
1
1
  <template>
2
- <div class="app-container">
3
- <ConnectHeader />
4
- <ConnectSystemBanner />
5
- <ConnectBreadcrumb />
6
- <div class="app-inner-container app-body">
7
- <div class="flex flex-col lg:flex-row lg:gap-6 grow">
8
- <div class="grow max-w-full overflow-hidden">
9
- <slot />
10
- </div>
11
- </div>
12
- </div>
13
- <ConnectFooter />
14
- </div>
2
+ <ConnectLayout>
3
+ <slot />
4
+ </ConnectLayout>
15
5
  </template>
@@ -0,0 +1,10 @@
1
+ // https://beholdr.github.io/maska/v3/#/vue
2
+ import { vMaska } from 'maska/vue'
3
+
4
+ export default defineNuxtPlugin({
5
+ name: 'core-maska-plugin',
6
+ parallel: true,
7
+ setup(nuxtApp) {
8
+ nuxtApp.vueApp.directive('maska', vMaska)
9
+ }
10
+ })
@@ -1,5 +1,5 @@
1
1
  import { useStorage } from '@vueuse/core'
2
- import isEqual from 'lodash-es/isEqual'
2
+ import { isEqual } from 'es-toolkit'
3
3
 
4
4
  export default defineNuxtPlugin({
5
5
  name: 'connect-whats-new-plugin',
@@ -1,39 +1,61 @@
1
- <div class="loader"></div>
2
- <style>
3
- .loader {
4
- display: block;
5
- position: fixed;
6
- z-index: 1031;
7
- top: 50%;
8
- left: 50%;
9
- transform: translate(-50%, -50%);
10
- width: 50px;
11
- height: 50px;
12
- box-sizing: border-box;
13
- border: solid 5px transparent;
14
- border-top-color: #1669bb;
15
- border-left-color: #1669bb;
16
- border-bottom-color: #efefef;
17
- border-right-color: #efefef;
18
- border-radius: 50%;
19
- -webkit-animation: loader 400ms linear infinite;
20
- animation: loader 400ms linear infinite;
21
- }
1
+ <head>
2
+ <title>Loading...</title>
3
+ <style>
4
+ .loading-container {
5
+ position: fixed;
6
+ inset: 0;
7
+ z-index: 9999;
8
+ background-color: rgba(241, 243, 245, 0.8);
9
+ display: flex;
10
+ justify-content: center;
11
+ align-items: center;
12
+ }
22
13
 
23
- @-webkit-keyframes loader {
24
- 0% {
25
- -webkit-transform: translate(-50%, -50%) rotate(0deg);
26
- }
27
- 100% {
28
- -webkit-transform: translate(-50%, -50%) rotate(360deg);
29
- }
30
- }
31
- @keyframes loader {
32
- 0% {
33
- transform: translate(-50%, -50%) rotate(0deg);
34
- }
35
- 100% {
36
- transform: translate(-50%, -50%) rotate(360deg);
37
- }
38
- }
39
- </style>
14
+ .loader {
15
+ display: block;
16
+ width: 75px;
17
+ height: 75px;
18
+ animation: rotate 1.5s linear infinite;
19
+ transform-origin: 50% 50%;
20
+ }
21
+
22
+ .loader .path {
23
+ stroke: #1669bb;
24
+ stroke-linecap: round;
25
+ animation: dash 1.25s ease-in-out infinite;
26
+ }
27
+
28
+ @keyframes rotate {
29
+ 100% {
30
+ transform: rotate(360deg);
31
+ }
32
+ }
33
+
34
+ @keyframes dash {
35
+ 0% {
36
+ stroke-dasharray: 1, 150;
37
+ stroke-dashoffset: 0;
38
+ }
39
+ 50% {
40
+ stroke-dasharray: 90, 150;
41
+ stroke-dashoffset: -35;
42
+ }
43
+ 100% {
44
+ stroke-dasharray: 90, 150;
45
+ stroke-dashoffset: -124;
46
+ }
47
+ }
48
+ </style>
49
+ </head>
50
+ <div class="loading-container">
51
+ <svg class="loader" viewBox="0 0 50 50">
52
+ <circle
53
+ class="path"
54
+ cx="25"
55
+ cy="25"
56
+ r="20"
57
+ fill="none"
58
+ stroke-width="5"
59
+ />
60
+ </svg>
61
+ </div>
@@ -0,0 +1,15 @@
1
+ declare module '@nuxt/schema' {
2
+ interface AppConfigInput {
3
+ connect?: {
4
+ header?: {
5
+ localeSelect?: boolean
6
+ whatsNew?: boolean
7
+ }
8
+ footer?: {
9
+ versions?: string[]
10
+ }
11
+ }
12
+ }
13
+ }
14
+
15
+ export {}
@@ -0,0 +1,8 @@
1
+ declare module '#app' {
2
+ interface PageMeta {
3
+ breadcrumbs?: ConnectBreadcrumb[]
4
+ hideBreadcrumbs?: boolean
5
+ }
6
+ }
7
+
8
+ export {}
@@ -0,0 +1,36 @@
1
+ /* eslint-disable @typescript-eslint/no-explicit-any */
2
+ export function logFetchError(error: unknown, message: string) {
3
+ let status = 'Unknown Status'
4
+ let statusText = 'Unknown Status Text'
5
+ let hasMessage = false
6
+
7
+ if (error && typeof error === 'object' && 'response' in error && typeof (error as any).response === 'object') {
8
+ const response = (error as any).response
9
+
10
+ if ('status' in response && typeof response.status === 'number') {
11
+ status = response.status
12
+ }
13
+
14
+ if ('statusText' in response && typeof response.statusText === 'string') {
15
+ statusText = response.statusText
16
+ }
17
+
18
+ if (
19
+ 'data' in error
20
+ && (error as any).data
21
+ && typeof (error as any).data === 'object'
22
+ && 'message' in (error as any).data
23
+ ) {
24
+ statusText = statusText || (error as any).data.message
25
+ hasMessage = (error as any).data.message !== undefined
26
+ }
27
+
28
+ console.error(
29
+ `${message}: ${status} - ${statusText} ${
30
+ hasMessage ? `--- ${JSON.stringify((error as any).data)}` : ''
31
+ }`
32
+ )
33
+ } else {
34
+ console.error(message)
35
+ }
36
+ }
@@ -0,0 +1,25 @@
1
+ /**
2
+ * Parses a string to decode HTML entities and remove HTML tags.
3
+ *
4
+ * @param text The string to parse.
5
+ * @param fallback The fallback text to return if the input is null or undefined.
6
+ * @returns The sanitized string.
7
+ */
8
+ export function parseSpecialCharacters(text: string | undefined | null, fallback = 'N/A'): string {
9
+ // Return fallback text if the input is null or undefined.
10
+ if (text === null || text === undefined) {
11
+ return fallback
12
+ }
13
+
14
+ const inputText = String(text)
15
+
16
+ try {
17
+ const parser = new DOMParser()
18
+ const doc = parser.parseFromString(inputText, 'text/html')
19
+
20
+ return doc.body.textContent || fallback
21
+ } catch (error) {
22
+ console.error(`Error parsing special characters in: ${inputText}`, error)
23
+ return fallback
24
+ }
25
+ }
@@ -0,0 +1,5 @@
1
+ export function setBreadcrumbs(breadcrumbs: ConnectBreadcrumb[]) {
2
+ const route = useRoute()
3
+ // @ts-expect-error - Type instantiation is excessively deep and possibly infinite.
4
+ route.meta.breadcrumbs = breadcrumbs
5
+ }
@@ -54,10 +54,11 @@ export default {
54
54
  }
55
55
  },
56
56
  test: {
57
- i18nBold: {
57
+ i18n: {
58
58
  strong: 'This should have {boldStart} bold {boldEnd} text',
59
59
  strongWithProps: 'This should have {boldStart} bold {boldEnd} text and allow a {prop}',
60
- italic: 'Italic test {italicStart} goes here {italicEnd}.'
60
+ italic: 'Italic test {italicStart} goes here {italicEnd}.',
61
+ computed: 'This text will be updated. Updated {boldStart}{count}{boldEnd} times.'
61
62
  }
62
63
  }
63
64
  }
@@ -0,0 +1,30 @@
1
+ import { defineNuxtModule, createResolver, installModule } from 'nuxt/kit'
2
+
3
+ export default defineNuxtModule({
4
+ meta: {
5
+ name: 'base-assets',
6
+ configKey: 'baseAssets'
7
+ },
8
+ defaults: {},
9
+ async setup(_options, _nuxt) {
10
+ console.info('Setting up **base** assets module')
11
+ const resolver = createResolver(import.meta.url)
12
+
13
+ _nuxt.hook('nitro:config', async (nitroConfig) => {
14
+ nitroConfig.publicAssets ||= []
15
+ nitroConfig.publicAssets.push({
16
+ dir: resolver.resolve('./runtime/public'),
17
+ maxAge: 60 * 60 * 24 * 365
18
+ })
19
+ })
20
+
21
+ _nuxt.options.css.push(resolver.resolve('./runtime/assets/connect-base-tw.css'))
22
+ _nuxt.options.css.push(resolver.resolve('./runtime/assets/connect-base-layout.css'))
23
+ await installModule('@nuxt/ui')
24
+
25
+ _nuxt.options.alias = {
26
+ ..._nuxt.options.alias,
27
+ '#connect-theme': resolver.resolve('./runtime/assets/connect-base-tw.css')
28
+ }
29
+ }
30
+ })