@nuxt-customer-portal/ui 0.3.2 → 0.4.0

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,30 @@
1
1
  # @nuxt-customer-portal/ui
2
2
 
3
+ ## 0.4.0
4
+
5
+ ### Minor Changes
6
+
7
+ - 59c8921: Add reusable appearance presets, validated typography and surface overrides, a light/dark live preview and optional full header logos. Apply saved branding reactively and preserve existing portals through backward-compatible defaults.
8
+ - 59c8921: Configure supported store content and SaaS interface languages using the shared bundled language list. Preserve disabled translations and switch unavailable UI locales to an enabled language.
9
+
10
+ ### Patch Changes
11
+
12
+ - Updated dependencies [59c8921]
13
+ - Updated dependencies [59c8921]
14
+ - Updated dependencies [59c8921]
15
+ - Updated dependencies [59c8921]
16
+ - Updated dependencies [59c8921]
17
+ - Updated dependencies [59c8921]
18
+ - Updated dependencies [59c8921]
19
+ - Updated dependencies [59c8921]
20
+ - @nuxt-customer-portal/core@0.4.0
21
+
22
+ ## 0.3.3
23
+
24
+ ### Patch Changes
25
+
26
+ - @nuxt-customer-portal/core@0.3.3
27
+
3
28
  ## 0.3.2
4
29
 
5
30
  ### Patch Changes
package/app/app.config.ts CHANGED
@@ -24,6 +24,11 @@ export default defineAppConfig({
24
24
  content: dropdownContent,
25
25
  item: 'data-highlighted:before:bg-accented data-[state=open]:before:bg-accented'
26
26
  }
27
+ },
28
+ tabs: {
29
+ slots: {
30
+ trigger: 'hover:bg-elevated'
31
+ }
27
32
  }
28
33
  }
29
34
  })
@@ -18,9 +18,23 @@ const props = withDefaults(
18
18
  const { t, locale, setLocale } = useI18n()
19
19
  const toast = useToast()
20
20
  const portalRuntimeSettings = useState<{
21
- branding?: { portalName?: string; tagline?: string; markLight?: string; markDark?: string }
22
- appearance?: { colorMode?: string }
21
+ branding?: {
22
+ portalName?: string
23
+ tagline?: string
24
+ markLight?: string
25
+ markDark?: string
26
+ logoLight?: string
27
+ logoDark?: string
28
+ }
29
+ appearance?: { colorMode?: string; headerBranding?: string }
30
+ languages?: string[]
23
31
  } | null>('portal-runtime-settings', () => null)
32
+ const availableLanguages = computed(() =>
33
+ [en, nl].filter(
34
+ (language) =>
35
+ !portalRuntimeSettings.value?.languages || portalRuntimeSettings.value.languages.includes(language.code)
36
+ )
37
+ )
24
38
  const colorMode = useColorMode()
25
39
  const runtimeBrandName = computed(() => portalRuntimeSettings.value?.branding?.portalName || props.brandName)
26
40
  const runtimeTagline = computed(() => portalRuntimeSettings.value?.branding?.tagline || props.brandTagline)
@@ -33,6 +47,15 @@ const runtimeMark = computed(() => {
33
47
  ? branding.markDark || branding.markLight || ''
34
48
  : branding.markLight || branding.markDark || ''
35
49
  })
50
+ const runtimeFullLogo = computed(() => {
51
+ if (portalRuntimeSettings.value?.appearance?.headerBranding !== 'full-logo') {
52
+ return ''
53
+ }
54
+ const branding = portalRuntimeSettings.value.branding
55
+ return colorMode.value === 'dark'
56
+ ? branding?.logoDark || branding?.logoLight || ''
57
+ : branding?.logoLight || branding?.logoDark || ''
58
+ })
36
59
  const showColorModeControl = computed(
37
60
  () => !portalRuntimeSettings.value || portalRuntimeSettings.value.appearance?.colorMode === 'user-choice'
38
61
  )
@@ -173,7 +196,13 @@ const stopImpersonating = async () => {
173
196
  class="flex items-center gap-3 rounded-lg focus-visible:outline-2 focus-visible:outline-offset-4 focus-visible:outline-primary"
174
197
  :aria-label="runtimeBrandName"
175
198
  >
176
- <div class="relative">
199
+ <img
200
+ v-if="runtimeFullLogo"
201
+ :src="runtimeFullLogo"
202
+ :alt="runtimeBrandName"
203
+ class="h-12 w-auto max-w-44 object-contain"
204
+ />
205
+ <div v-else class="relative">
177
206
  <img v-if="runtimeMark" :src="runtimeMark" alt="" class="size-10 rounded-lg object-contain" />
178
207
  <svg
179
208
  v-else
@@ -199,7 +228,7 @@ const stopImpersonating = async () => {
199
228
  </div>
200
229
 
201
230
  <!-- Neutral fallback wordmark; host shells can replace the layout entirely. -->
202
- <div class="hidden flex-col sm:flex">
231
+ <div v-if="!runtimeFullLogo" class="hidden flex-col sm:flex">
203
232
  <span class="portal-wordmark text-2xl font-bold text-gray-900 dark:text-white leading-tight">
204
233
  {{ runtimeBrandName }}
205
234
  </span>
@@ -245,8 +274,9 @@ const stopImpersonating = async () => {
245
274
  <div class="hidden lg:flex items-center gap-3 ml-auto">
246
275
  <UButton icon="i-lucide-search" color="neutral" variant="ghost" size="sm" square @click="searchOpen = true" />
247
276
  <ULocaleSelect
277
+ v-if="availableLanguages.length > 1"
248
278
  v-model="currentLocale"
249
- :locales="[en, nl]"
279
+ :locales="availableLanguages"
250
280
  :ui="{ content: 'w-max min-w-40', itemLabel: 'whitespace-nowrap' }"
251
281
  />
252
282
  <UColorModeButton v-if="showColorModeControl" />
@@ -256,7 +286,12 @@ const stopImpersonating = async () => {
256
286
  </div>
257
287
 
258
288
  <!-- Organization Switcher Modal -->
259
- <UModal v-model:open="showOrgSwitcherModal" :title="t('menu.switchOrganization')" :ui="{ footer: 'justify-end' }">
289
+ <UModal
290
+ v-if="showOrgSwitcherModal"
291
+ v-model:open="showOrgSwitcherModal"
292
+ :title="t('menu.switchOrganization')"
293
+ :ui="{ footer: 'justify-end' }"
294
+ >
260
295
  <template #body>
261
296
  <OrganizationSwitcher
262
297
  v-if="isAuthenticated"
@@ -283,8 +318,9 @@ const stopImpersonating = async () => {
283
318
  @click="searchOpen = true"
284
319
  />
285
320
  <ULocaleSelect
321
+ v-if="availableLanguages.length > 1"
286
322
  v-model="currentLocale"
287
- :locales="[en, nl]"
323
+ :locales="availableLanguages"
288
324
  class="w-32"
289
325
  :ui="{ content: 'w-max min-w-40', itemLabel: 'whitespace-nowrap' }"
290
326
  />
@@ -53,7 +53,7 @@ const getText = (key: string | undefined, fallback: string): string => {
53
53
  </script>
54
54
 
55
55
  <template>
56
- <UModal v-model:open="open" :title="getText(title, title)" :ui="{ footer: 'justify-end' }">
56
+ <UModal v-if="open" v-model:open="open" :title="getText(title, title)" :ui="{ footer: 'justify-end' }">
57
57
  <template #body>
58
58
  <div class="space-y-4">
59
59
  <p class="text-sm text-gray-600 dark:text-gray-400">
@@ -6,6 +6,7 @@ const props = defineProps<{
6
6
  email: string
7
7
  role: string | null
8
8
  canEdit?: boolean
9
+ canResend?: boolean
9
10
  canRevoke?: boolean
10
11
  }>()
11
12
  const emit = defineEmits<{ refresh: [] }>()
@@ -46,6 +47,18 @@ const revoke = async () => {
46
47
  busy.value = false
47
48
  }
48
49
  }
50
+ const resend = async () => {
51
+ busy.value = true
52
+ try {
53
+ await api.resend(props.endpoint)
54
+ toast.add({ title: t('invitationManagement.resent'), color: 'success' })
55
+ emit('refresh')
56
+ } catch {
57
+ toast.add({ title: t('invitationManagement.resendFailed'), color: 'error' })
58
+ } finally {
59
+ busy.value = false
60
+ }
61
+ }
49
62
  </script>
50
63
 
51
64
  <template>
@@ -54,23 +67,36 @@ const revoke = async () => {
54
67
  v-if="canEdit"
55
68
  icon="i-lucide-pencil"
56
69
  color="neutral"
57
- variant="ghost"
58
- size="sm"
70
+ variant="outline"
71
+ size="xs"
59
72
  :disabled="busy"
60
73
  :aria-label="t('invitationManagement.edit')"
61
74
  @click="openEditor"
62
- />
75
+ >{{ t('invitationManagement.edit') }}</UButton
76
+ >
77
+ <UButton
78
+ v-if="canResend"
79
+ icon="i-lucide-send"
80
+ color="neutral"
81
+ variant="outline"
82
+ size="xs"
83
+ :loading="busy"
84
+ :aria-label="t('invitationManagement.resend')"
85
+ @click="resend"
86
+ >{{ t('invitationManagement.resend') }}</UButton
87
+ >
63
88
  <UButton
64
89
  v-if="canRevoke"
65
90
  icon="i-lucide-x"
66
91
  color="error"
67
- variant="ghost"
68
- size="sm"
92
+ variant="outline"
93
+ size="xs"
69
94
  :disabled="busy"
70
95
  :aria-label="t('invitationManagement.revoke')"
71
96
  @click="revoking = true"
72
- />
73
- <UModal v-model:open="editing" :title="t('invitationManagement.edit')">
97
+ >{{ t('invitationManagement.revoke') }}</UButton
98
+ >
99
+ <UModal v-if="editing" v-model:open="editing" :title="t('invitationManagement.edit')">
74
100
  <template #body>
75
101
  <UForm :state="state" :schema="invitationRoleSchema" novalidate class="space-y-4" @submit="save">
76
102
  <p>{{ email }}</p>
@@ -79,12 +105,15 @@ const revoke = async () => {
79
105
  </UFormField>
80
106
  <div class="flex justify-end gap-2">
81
107
  <UButton color="neutral" variant="outline" @click="editing = false">{{ t('common.cancel') }}</UButton>
82
- <UButton type="submit" :loading="busy">{{ t('invitationManagement.save') }}</UButton>
108
+ <UButton type="submit" icon="i-lucide-save" class="ml-auto flex min-w-28 justify-center" :loading="busy">{{
109
+ t('invitationManagement.save')
110
+ }}</UButton>
83
111
  </div>
84
112
  </UForm>
85
113
  </template>
86
114
  </UModal>
87
115
  <ConfirmationModal
116
+ v-if="revoking"
88
117
  v-model:open="revoking"
89
118
  title="invitationManagement.revoke"
90
119
  message="invitationManagement.confirmRevoke"
@@ -19,7 +19,7 @@ const showFilters = ref(false)
19
19
  const showSort = ref(false)
20
20
  const toolbar = ref<HTMLElement | null>(null)
21
21
  const { width } = useElementSize(toolbar)
22
- const isMobile = computed(() => width.value < Math.max(660, 160 + props.filters.length * 120 + 232))
22
+ const isMobile = computed(() => width.value < Math.max(660, 160 + props.filters.length * 208 + 232))
23
23
  </script>
24
24
 
25
25
  <template>
@@ -40,7 +40,11 @@ const isMobile = computed(() => width.value < Math.max(660, 160 + props.filters.
40
40
  value-key="value"
41
41
  :placeholder="filter.placeholder"
42
42
  :aria-label="filter.placeholder"
43
- class="w-44 min-w-28 shrink"
43
+ class="w-52 shrink-0"
44
+ :ui="{
45
+ content: 'w-max min-w-[var(--reka-select-trigger-width)]',
46
+ itemLabel: 'whitespace-nowrap'
47
+ }"
44
48
  @update:model-value="emit('filter', filter.key, $event)"
45
49
  />
46
50
  <div class="ml-auto flex shrink-0 items-center gap-2">
@@ -80,7 +84,7 @@ const isMobile = computed(() => width.value < Math.max(660, 160 + props.filters.
80
84
  />
81
85
  </template>
82
86
  </div>
83
- <UModal v-model:open="showFilters" :title="t('common.filters')">
87
+ <UModal v-if="showFilters" v-model:open="showFilters" :title="t('common.filters')">
84
88
  <template #body>
85
89
  <div class="space-y-4">
86
90
  <UFormField v-for="filter in filters" :key="filter.key" :label="filter.placeholder">
@@ -95,7 +99,7 @@ const isMobile = computed(() => width.value < Math.max(660, 160 + props.filters.
95
99
  </div>
96
100
  </template>
97
101
  </UModal>
98
- <UModal v-model:open="showSort" :title="t('common.sort')">
102
+ <UModal v-if="showSort" v-model:open="showSort" :title="t('common.sort')">
99
103
  <template #body>
100
104
  <div class="space-y-4">
101
105
  <USelect
@@ -1,4 +1,5 @@
1
1
  export const useInvitationManagement = () => ({
2
2
  changeRole: (endpoint: string, role: string) => $fetch(endpoint, { method: 'PATCH', body: { role } }),
3
+ resend: (endpoint: string) => $fetch(`${endpoint}/resend`, { method: 'POST' }),
3
4
  revoke: (endpoint: string) => $fetch(endpoint, { method: 'PATCH', body: { status: 'canceled' } })
4
5
  })
@@ -6,7 +6,8 @@ const hasAudience = (
6
6
  isAuthenticated: boolean,
7
7
  isAdmin: boolean,
8
8
  organizationRole: string | null,
9
- organizationType: 'PROVIDER' | 'CLIENT' | null
9
+ organizationType: 'PROVIDER' | 'CLIENT' | null,
10
+ isPersonalClient: boolean
10
11
  ) =>
11
12
  audiences.some((audience) => {
12
13
  if (audience === 'public') {
@@ -30,6 +31,13 @@ const hasAudience = (
30
31
  if (audience === 'clientAdmin') {
31
32
  return organizationType === 'CLIENT' && (organizationRole === 'owner' || organizationRole === 'admin')
32
33
  }
34
+ if (audience === 'clientOrganizationAdmin') {
35
+ return (
36
+ organizationType === 'CLIENT' &&
37
+ !isPersonalClient &&
38
+ (organizationRole === 'owner' || organizationRole === 'admin')
39
+ )
40
+ }
33
41
  return organizationType === 'PROVIDER' && (organizationRole === 'owner' || organizationRole === 'admin')
34
42
  })
35
43
 
@@ -37,9 +45,15 @@ export const useModuleNavigation = (sidebarOpen?: Ref<boolean>) => {
37
45
  const { t } = useI18n()
38
46
  const route = useRoute()
39
47
  const { modules: registeredModules } = usePortalFeatures()
40
- const { isAuthenticated, isSystemAdmin, activeOrganizationRole, activeOrganizationType } = usePortalSession()
48
+ const {
49
+ isAuthenticated,
50
+ isSystemAdmin,
51
+ activeOrganizationRole,
52
+ activeOrganizationType,
53
+ activeOrganizationIsPersonal
54
+ } = usePortalSession()
41
55
 
42
- const modules = computed(() =>
56
+ const accessibleModules = computed(() =>
43
57
  registeredModules.value
44
58
  .filter((module) =>
45
59
  hasAudience(
@@ -47,15 +61,28 @@ export const useModuleNavigation = (sidebarOpen?: Ref<boolean>) => {
47
61
  isAuthenticated.value,
48
62
  isSystemAdmin.value,
49
63
  activeOrganizationRole.value,
50
- activeOrganizationType.value
64
+ activeOrganizationType.value,
65
+ activeOrganizationIsPersonal.value
51
66
  )
52
67
  )
53
68
  .map((module) => ({ ...module, label: t(module.labelKey) }))
54
69
  )
70
+ const modules = computed(() =>
71
+ accessibleModules.value.filter((module) =>
72
+ hasAudience(
73
+ module.navigationAudiences ?? module.audiences,
74
+ isAuthenticated.value,
75
+ isSystemAdmin.value,
76
+ activeOrganizationRole.value,
77
+ activeOrganizationType.value,
78
+ activeOrganizationIsPersonal.value
79
+ )
80
+ )
81
+ )
55
82
 
56
83
  const activeModule = computed(
57
84
  () =>
58
- modules.value
85
+ accessibleModules.value
59
86
  .map((module) => ({
60
87
  module,
61
88
  matchLength: Math.max(
@@ -78,7 +105,8 @@ export const useModuleNavigation = (sidebarOpen?: Ref<boolean>) => {
78
105
  isAuthenticated.value,
79
106
  isSystemAdmin.value,
80
107
  activeOrganizationRole.value,
81
- activeOrganizationType.value
108
+ activeOrganizationType.value,
109
+ activeOrganizationIsPersonal.value
82
110
  )
83
111
  )
84
112
  .sort((a, b) => (a.order ?? 100) - (b.order ?? 100))
@@ -104,7 +132,8 @@ export const useModuleNavigation = (sidebarOpen?: Ref<boolean>) => {
104
132
  isAuthenticated.value,
105
133
  isSystemAdmin.value,
106
134
  activeOrganizationRole.value,
107
- activeOrganizationType.value
135
+ activeOrganizationType.value,
136
+ activeOrganizationIsPersonal.value
108
137
  )
109
138
  )
110
139
  .sort((a, b) => (a.order ?? 100) - (b.order ?? 100))
@@ -6,7 +6,8 @@ const hasAudience = (
6
6
  isAuthenticated: boolean,
7
7
  isAdmin: boolean,
8
8
  organizationRole: string | null,
9
- organizationType: 'PROVIDER' | 'CLIENT' | null
9
+ organizationType: 'PROVIDER' | 'CLIENT' | null,
10
+ isPersonalClient: boolean
10
11
  ) =>
11
12
  audiences.some((audience) => {
12
13
  if (audience === 'public') {
@@ -30,6 +31,13 @@ const hasAudience = (
30
31
  if (audience === 'clientAdmin') {
31
32
  return organizationType === 'CLIENT' && (organizationRole === 'owner' || organizationRole === 'admin')
32
33
  }
34
+ if (audience === 'clientOrganizationAdmin') {
35
+ return (
36
+ organizationType === 'CLIENT' &&
37
+ !isPersonalClient &&
38
+ (organizationRole === 'owner' || organizationRole === 'admin')
39
+ )
40
+ }
33
41
  return organizationType === 'PROVIDER' && (organizationRole === 'owner' || organizationRole === 'admin')
34
42
  })
35
43
 
@@ -37,7 +45,13 @@ export const useNavigationLinks = (sidebarOpen: Ref<boolean>) => {
37
45
  const { t } = useI18n()
38
46
  const route = useRoute()
39
47
  const { navigation } = usePortalFeatures()
40
- const { isAuthenticated, isSystemAdmin, activeOrganizationRole, activeOrganizationType } = usePortalSession()
48
+ const {
49
+ isAuthenticated,
50
+ isSystemAdmin,
51
+ activeOrganizationRole,
52
+ activeOrganizationType,
53
+ activeOrganizationIsPersonal
54
+ } = usePortalSession()
41
55
 
42
56
  const visibleItems = computed(() =>
43
57
  navigation.value.filter(
@@ -47,7 +61,8 @@ export const useNavigationLinks = (sidebarOpen: Ref<boolean>) => {
47
61
  isAuthenticated.value,
48
62
  isSystemAdmin.value,
49
63
  activeOrganizationRole.value,
50
- activeOrganizationType.value
64
+ activeOrganizationType.value,
65
+ activeOrganizationIsPersonal.value
51
66
  ) &&
52
67
  (route.path === '/' || !item.audiences.every((audience) => audience === 'public'))
53
68
  )
@@ -2,7 +2,9 @@
2
2
  <script setup lang="ts">
3
3
  const route = useRoute()
4
4
  const toast = useToast()
5
+ const { t } = useI18n()
5
6
  const open = ref(false)
7
+ const sidebarCollapsed = ref(false)
6
8
  const showFooter = computed(() => route.meta?.public === true)
7
9
 
8
10
  const { activeModule, activeModuleMenuItems } = useModuleNavigation(open)
@@ -39,7 +41,7 @@ onMounted(async () => {
39
41
  </script>
40
42
 
41
43
  <template>
42
- <div class="dashboard-layout relative min-h-screen">
44
+ <div class="dashboard-layout relative min-h-screen" :data-sidebar-collapsed="sidebarCollapsed">
43
45
  <!-- AppHeader - fixed at top -->
44
46
  <div class="fixed left-0 right-0 z-50" style="top: var(--portal-top-bar-height, 0px)">
45
47
  <div class="mx-auto w-full max-w-[1600px] px-4">
@@ -49,30 +51,43 @@ onMounted(async () => {
49
51
 
50
52
  <UDashboardGroup
51
53
  unit="rem"
54
+ storage-key="portal-dashboard-v2"
52
55
  class="px-4 mx-auto max-w-[1600px]! w-full!"
53
56
  style="top: var(--portal-top-bar-height, 0px)"
54
57
  >
55
58
  <UDashboardSidebar
56
59
  id="default"
57
60
  v-model:open="open"
61
+ v-model:collapsed="sidebarCollapsed"
58
62
  collapsible
59
63
  resizable
64
+ :collapsed-size="4"
60
65
  :default-size="18"
61
66
  :min-size="18"
62
67
  :max-size="24"
63
- class="min-w-72 bg-elevated/25 lg:pb-12 data-[collapsed=true]:min-w-0"
68
+ class="min-w-72 bg-elevated/25 lg:pb-12 data-[collapsed=true]:min-w-16"
64
69
  :ui="{ footer: 'lg:border-t lg:border-default' }"
65
70
  >
66
- <template #header>
67
- <div v-if="activeModule" class="flex min-w-0 items-center gap-2 px-2 text-sm font-semibold">
71
+ <template #header="{ collapsed }">
72
+ <div v-if="activeModule && !collapsed" class="flex min-w-0 items-center gap-2 px-2 text-sm font-semibold">
68
73
  <UIcon v-if="activeModule.icon" :name="activeModule.icon" class="size-4 shrink-0 text-primary" />
69
74
  <span class="truncate">{{ activeModule.label }}</span>
70
75
  </div>
71
76
  </template>
72
- <template #default="{ collapsed }">
73
- <UDashboardSearchButton :collapsed="collapsed" class="mt-2 bg-transparent ring-default" />
74
- <div v-if="!collapsed && activeModule" class="px-2.5 pt-3 pb-1 text-xs font-semibold uppercase text-muted">
75
- {{ activeModule.label }}
77
+ <template #default="{ collapsed, collapse }">
78
+ <div class="flex items-center pt-3 pb-1" :class="collapsed ? 'justify-center' : 'justify-between px-2.5'">
79
+ <span v-if="!collapsed && activeModule" class="text-xs font-semibold uppercase text-muted">
80
+ {{ activeModule.label }}
81
+ </span>
82
+ <UButton
83
+ :icon="collapsed ? 'i-lucide-panel-left-open' : 'i-lucide-panel-left-close'"
84
+ color="neutral"
85
+ variant="ghost"
86
+ size="xs"
87
+ :aria-label="t(collapsed ? 'common.expandSidebar' : 'common.collapseSidebar')"
88
+ :title="t(collapsed ? 'common.expandSidebar' : 'common.collapseSidebar')"
89
+ @click="collapse(!collapsed)"
90
+ />
76
91
  </div>
77
92
  <UNavigationMenu
78
93
  :collapsed="collapsed"
@@ -93,3 +108,9 @@ onMounted(async () => {
93
108
  <AppFooter v-if="showFooter" />
94
109
  </div>
95
110
  </template>
111
+
112
+ <style>
113
+ .dashboard-layout[data-sidebar-collapsed='true'] main [class~='max-w-(--ui-container)'] {
114
+ max-width: none;
115
+ }
116
+ </style>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nuxt-customer-portal/ui",
3
- "version": "0.3.2",
3
+ "version": "0.4.0",
4
4
  "license": "MIT",
5
5
  "homepage": "https://nuxt-customer-portal.com",
6
6
  "repository": {
@@ -29,7 +29,7 @@
29
29
  "@nuxtjs/i18n": "^10.6.0",
30
30
  "@pinia/nuxt": "^1.0.1",
31
31
  "pinia": "^4.0.2",
32
- "@nuxt-customer-portal/core": "^0.3.2"
32
+ "@nuxt-customer-portal/core": "^0.4.0"
33
33
  },
34
34
  "peerDependencies": {
35
35
  "nuxt": "^4.5.1",
@@ -1,6 +1,6 @@
1
1
  export default {
2
2
  id: 'ui',
3
- version: '0.3.2',
3
+ version: '0.4.0',
4
4
  source: '@nuxt-customer-portal/ui',
5
5
  dependsOn: ['core']
6
6
  }