@finema/finework-layer 0.1.1 → 0.1.3

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.
@@ -1,13 +1,9 @@
1
1
  <template>
2
- <LayoutAdmin
3
- label="Test Layout"
4
- :sidebar-items="sidebarItems"
5
- :is-sidebar-group="true"
6
- >
2
+ <LayoutUser>
7
3
  <p>
8
4
  dasdasd
9
5
  </p>
10
- </LayoutAdmin>
6
+ </LayoutUser>
11
7
  </template>
12
8
 
13
9
  <script lang="ts" setup>
package/CHANGELOG.md CHANGED
@@ -1,5 +1,13 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.1.3](https://gitlab.finema.co/finema/finework/finework-frontend-layer/compare/0.1.2...0.1.3) (2025-10-15)
4
+
5
+ ## [0.1.2](https://gitlab.finema.co/finema/finework/finework-frontend-layer/compare/0.1.1...0.1.2) (2025-10-14)
6
+
7
+ ### Features
8
+
9
+ * layout user ([6bf83a1](https://gitlab.finema.co/finema/finework/finework-frontend-layer/commit/6bf83a15db0747323e6fd02555eab0b0c4e3e64e))
10
+
3
11
  ## [0.1.1](https://gitlab.finema.co/finema/finework/finework-frontend-layer/compare/0.0.16...0.1.1) (2025-10-14)
4
12
 
5
13
  ### Bug Fixes
package/app/app.config.ts CHANGED
@@ -195,7 +195,8 @@ export default defineAppConfig({
195
195
  variants: {
196
196
  size: {
197
197
  xl: {
198
- base: 'py-2.5 disabled:bg-[#F5F5F5] disabled:text-[#737373] disabled:cursor-not-allowed w-full',
198
+ base: 'py-2.5 min-h-[100px] items-start disabled:bg-[#F5F5F5] disabled:text-[#737373] disabled:cursor-not-allowed w-full',
199
+ item: 'bg-white',
199
200
  },
200
201
  },
201
202
  },
@@ -1,15 +1,20 @@
1
1
  <template>
2
2
  <aside
3
- :class="[`
3
+ :class="[
4
+ `
4
5
  flex h-full flex-col overflow-y-auto bg-white
5
6
  transition-all duration-300
6
- `]"
7
+ `,
8
+ ]"
7
9
  >
8
10
  <div
9
- :class="['flex h-[72px] items-center max-sm:pr-5', {
10
- 'justify-center': isCollapsed,
11
- 'w-[260px] justify-between pl-5': !isCollapsed,
12
- }]"
11
+ :class="[
12
+ 'flex h-[72px] items-center max-sm:pr-5',
13
+ {
14
+ 'justify-center': isCollapsed,
15
+ 'w-[260px] justify-between pl-5': !isCollapsed,
16
+ },
17
+ ]"
13
18
  >
14
19
  <NuxtLink :to="routes.home.to">
15
20
  <img
@@ -22,8 +27,10 @@
22
27
 
23
28
  <Navbar />
24
29
  </div>
25
- <div class="bg-primary flex items-center justify-between px-7 py-4 font-bold text-white">
26
- {{ isCollapsed ? '' : label }}
30
+ <div
31
+ class="bg-primary flex items-center justify-between px-7 py-4 font-bold text-white"
32
+ >
33
+ {{ isCollapsed ? "" : label }}
27
34
 
28
35
  <Icon
29
36
  v-if="!isCollapsed"
@@ -40,15 +47,8 @@
40
47
  </div>
41
48
 
42
49
  <div class="flex-1 overflow-y-auto border-r border-gray-200 p-4">
43
- <div
44
- v-if="isGroup"
45
- class="space-y-4 divide-y-1 divide-[#d9d9d9]"
46
- >
47
- <div
48
- v-for="group in navigationItems"
49
- :key="group.label"
50
- class="pb-4"
51
- >
50
+ <div v-if="isGroup" class="space-y-4 divide-y-1 divide-[#d9d9d9]">
51
+ <div v-for="group in navigationItems" :key="group.label" class="pb-4">
52
52
  <div class="mt-2 mb-2 text-[10px] font-semibold text-gray-400">
53
53
  {{ group.label }}
54
54
  </div>
@@ -69,8 +69,10 @@
69
69
  'hover:text-primary',
70
70
  'data-active:before:bg-white data-active:before:rounded-lg data-active:text-primary font-semibold',
71
71
  ],
72
- linkLeadingIcon: 'group-data-[state=open]:text-current text-current size-[24px] group-hover:text-primary',
73
- childList: 'border-none ms-0 pl-8 bg-gray-100 mt-2 py-1 rounded-lg',
72
+ linkLeadingIcon:
73
+ 'group-data-[state=open]:text-current text-current size-[24px] group-hover:text-primary',
74
+ childList:
75
+ 'border-none ms-0 pl-8 bg-gray-100 mt-2 py-1 rounded-lg',
74
76
  childLink: 'ps-0',
75
77
  childItem: 'ps-0',
76
78
  }"
@@ -96,7 +98,8 @@
96
98
  'hover:text-primary',
97
99
  'data-active:before:bg-white data-active:before:rounded-lg data-active:text-primary font-semibold',
98
100
  ],
99
- linkLeadingIcon: 'group-data-[state=open]:text-current text-current size-[24px] group-hover:text-primary ',
101
+ linkLeadingIcon:
102
+ 'group-data-[state=open]:text-current text-current size-[24px] group-hover:text-primary ',
100
103
  childList: 'border-none ms-0 pl-8 bg-gray-100 mt-2 py-1 rounded-lg',
101
104
  childLink: 'ps-0',
102
105
  childItem: 'ps-0',
@@ -104,10 +107,7 @@
104
107
  class="w-full justify-center"
105
108
  />
106
109
  </div>
107
- <div
108
- v-if="isMobile"
109
- class="border-t border-gray-100 p-3"
110
- >
110
+ <div v-if="isMobile" class="border-t border-gray-100 p-3">
111
111
  <div class="flex items-center justify-between gap-2">
112
112
  <div class="flex min-w-0 flex-1 items-center gap-3">
113
113
  <Avatar
@@ -120,7 +120,7 @@
120
120
  {{ auth.me.value?.display_name || auth.me.value?.full_name }}
121
121
  </p>
122
122
  <p class="text-muted truncate text-xs">
123
- {{ auth.me.value?.email || '' }}
123
+ {{ auth.me.value?.email || "" }}
124
124
  </p>
125
125
  </div>
126
126
  </div>
@@ -149,7 +149,7 @@ import type { NavigationMenuItem } from '@nuxt/ui'
149
149
  import { computed } from 'vue'
150
150
  import { useRoute } from 'vue-router'
151
151
  import type { Permission, UserModule } from '#imports'
152
- import Navbar from './Apps.vue'
152
+ import Navbar from '../Apps.vue'
153
153
 
154
154
  defineEmits<{
155
155
  'toggle-collapsed': []
@@ -166,7 +166,6 @@ const props = defineProps<{
166
166
  const route = useRoute()
167
167
  const auth = useAuth()
168
168
  const userMenuItems = [
169
-
170
169
  {
171
170
  label: 'View profile',
172
171
  icon: 'i-lucide-user',
@@ -193,13 +192,16 @@ const mappedItems = (items: NavigationMenuItem[]): NavigationMenuItem[] => {
193
192
 
194
193
  return {
195
194
  active: isChildCurrentlyActive,
196
- class: 'hover:bg-transparent hover:text-gray-700 hover:font-bold py-2 data-active:before:bg-transparent data-active:text-gray-700 data-active:font-bold',
195
+ class:
196
+ 'hover:bg-transparent hover:text-gray-700 hover:font-bold py-2 data-active:before:bg-transparent data-active:text-gray-700 data-active:font-bold',
197
197
  icon: '',
198
198
  ...child,
199
199
  }
200
200
  })
201
201
 
202
- const selfIsActive = item.to ? route.path.startsWith(String(item.to)) : false
202
+ const selfIsActive = item.to
203
+ ? route.path.startsWith(String(item.to))
204
+ : false
203
205
 
204
206
  const itemIsActive = selfIsActive || isAnyChildActive // A root item is active if its own link matches OR if any child is active
205
207
 
@@ -221,46 +223,52 @@ const mappedItems = (items: NavigationMenuItem[]): NavigationMenuItem[] => {
221
223
 
222
224
  // filter items base on auth.hasPermission and nested children , permissions: ['pmo:USER', 'pmo:ADMIN', 'pmo:SUPER']
223
225
  const filterItems = (items: NavigationMenuItem[]): NavigationMenuItem[] => {
224
- return items.filter((item) => {
225
- if (item.permissions && Array.isArray(item.permissions)) {
226
- // permissions: ['pmo:USER', 'pmo:ADMIN', 'pmo:SUPER'] => module: 'pmo', permissions: ['USER', 'ADMIN', 'SUPER']
227
- const permissionStrings = item.permissions as string[]
226
+ return items
227
+ .filter((item) => {
228
+ if (item.permissions && Array.isArray(item.permissions)) {
229
+ // permissions: ['pmo:USER', 'pmo:ADMIN', 'pmo:SUPER'] => module: 'pmo', permissions: ['USER', 'ADMIN', 'SUPER']
230
+ const permissionStrings = item.permissions as string[]
228
231
 
229
- // Extract module from first permission (all should have same module)
230
- const firstPermission = permissionStrings[0]
232
+ // Extract module from first permission (all should have same module)
233
+ const firstPermission = permissionStrings[0]
231
234
 
232
- if (!firstPermission) {
233
- return true
234
- }
235
+ if (!firstPermission) {
236
+ return true
237
+ }
235
238
 
236
- const [moduleStr] = firstPermission.split(':')
239
+ const [moduleStr] = firstPermission.split(':')
237
240
 
238
- // Extract all permission levels
239
- const permissionLevels = permissionStrings.map((p) => p.split(':')[1]) as Permission[]
241
+ // Extract all permission levels
242
+ const permissionLevels = permissionStrings.map(
243
+ (p) => p.split(':')[1],
244
+ ) as Permission[]
240
245
 
241
- return auth.hasPermission(moduleStr as UserModule, ...permissionLevels)
242
- }
246
+ return auth.hasPermission(moduleStr as UserModule, ...permissionLevels)
247
+ }
243
248
 
244
- // Recursively filter children
245
- if (item.children) {
246
- const filteredChildren = filterItems(item.children as NavigationMenuItem[])
249
+ // Recursively filter children
250
+ if (item.children) {
251
+ const filteredChildren = filterItems(
252
+ item.children as NavigationMenuItem[],
253
+ )
247
254
 
248
- // Only include parent if it has visible children or no permission requirement
249
- return filteredChildren.length > 0
250
- }
255
+ // Only include parent if it has visible children or no permission requirement
256
+ return filteredChildren.length > 0
257
+ }
251
258
 
252
- return true
253
- }).map((item) => {
254
- // Apply filtering to children
255
- if (item.children) {
256
- return {
257
- ...item,
258
- children: filterItems(item.children as NavigationMenuItem[]),
259
+ return true
260
+ })
261
+ .map((item) => {
262
+ // Apply filtering to children
263
+ if (item.children) {
264
+ return {
265
+ ...item,
266
+ children: filterItems(item.children as NavigationMenuItem[]),
267
+ }
259
268
  }
260
- }
261
269
 
262
- return item
263
- })
270
+ return item
271
+ })
264
272
  }
265
273
 
266
274
  const navigationItems = computed<NavigationMenuItem[]>(() => {
@@ -1,13 +1,16 @@
1
1
  <template>
2
2
  <div class="relative flex min-h-screen flex-1">
3
3
  <div
4
- :class="[`
4
+ :class="[
5
+ `
5
6
  fixed inset-0 z-30 hidden w-auto
6
7
  lg:block
7
- `, {
8
- 'max-w-[88px]': isCollapsed,
9
- 'max-w-[260px]': !isCollapsed,
10
- }]"
8
+ `,
9
+ {
10
+ 'max-w-[88px]': isCollapsed,
11
+ 'max-w-[260px]': !isCollapsed,
12
+ },
13
+ ]"
11
14
  >
12
15
  <Sidebar
13
16
  :label="label"
@@ -18,18 +21,17 @@
18
21
  />
19
22
  </div>
20
23
  <div
21
- :class="['w-full bg-gray-50', {
22
- 'lg:pl-[88px]': isCollapsed,
23
- 'lg:pl-[260px]': !isCollapsed,
24
- }]"
24
+ :class="[
25
+ 'w-full bg-gray-50',
26
+ {
27
+ 'lg:pl-[88px]': isCollapsed,
28
+ 'lg:pl-[260px]': !isCollapsed,
29
+ },
30
+ ]"
25
31
  >
26
32
  <nav
27
- class="
28
- fixed top-0 left-0 z-20 flex min-h-[64px] w-screen items-center justify-between
29
- gap-4 bg-white px-5 lg:min-h-[72px]
30
- lg:justify-end
31
- "
32
- style="box-shadow: 0px 4px 4px 0px rgba(0, 0, 0, 0.04);"
33
+ class="fixed top-0 left-0 z-20 flex min-h-[64px] w-screen items-center justify-between gap-4 bg-white px-5 lg:min-h-[72px] lg:justify-end"
34
+ style="box-shadow: 0px 4px 4px 0px rgba(0, 0, 0, 0.04)"
33
35
  >
34
36
  <div class="flex items-center gap-4">
35
37
  <Slideover
@@ -41,10 +43,7 @@
41
43
  side="left"
42
44
  >
43
45
  <svg
44
- class="
45
- cursor-pointer
46
- lg:hidden
47
- "
46
+ class="cursor-pointer lg:hidden"
48
47
  width="19"
49
48
  height="18"
50
49
  viewBox="0 0 19 18"
@@ -74,10 +73,7 @@
74
73
  <img
75
74
  src="/logo-mini.png"
76
75
  alt="logo_mini_color"
77
- class="
78
- w-[126px] min-w-[126px] lg:hidden
79
- lg:w-[172px] lg:min-w-[172px]
80
- "
76
+ class="w-[126px] min-w-[126px] lg:hidden lg:w-[172px] lg:min-w-[172px]"
81
77
  />
82
78
  </NuxtLink>
83
79
  <Apps class="lg:hidden" />
@@ -96,14 +92,12 @@
96
92
  }"
97
93
  >
98
94
  <div class="relative flex cursor-pointer items-center gap-2">
99
- <div
100
- class="hidden flex-col justify-end text-right lg:flex"
101
- >
95
+ <div class="hidden flex-col justify-end text-right lg:flex">
102
96
  <p class="font-bold">
103
97
  {{ auth.me.value?.display_name || auth.me.value?.full_name }}
104
98
  </p>
105
99
  <p class="text-muted text-sm">
106
- {{ auth.me.value?.email || '' }}
100
+ {{ auth.me.value?.email || "" }}
107
101
  </p>
108
102
  </div>
109
103
  <Avatar
@@ -111,22 +105,17 @@
111
105
  icon="ri:user-line"
112
106
  :src="auth.me.value?.avatar_url || ''"
113
107
  />
114
- <Icon
115
- name="i-ph:caret-down-light"
116
- class="size-5"
117
- />
108
+ <Icon name="i-ph:caret-down-light" class="size-5" />
118
109
  </div>
119
110
  </DropdownMenu>
120
111
  </div>
121
112
  </nav>
122
113
  <div class="w-full bg-gray-50 pt-[64px] lg:pt-[72px]">
123
- <main
124
- class="
125
- mx-auto min-h-full w-full flex-1 px-6 py-10 lg:px-8
126
- "
127
- >
114
+ <main class="mx-auto min-h-full w-full flex-1 px-6 py-10 lg:px-8">
128
115
  <Breadcrumb
129
- v-if="!app.pageMeta.isHideBreadcrumbs && breadcrumbsItems.length > 1"
116
+ v-if="
117
+ !app.pageMeta.isHideBreadcrumbs && breadcrumbsItems.length > 1
118
+ "
130
119
  :items="breadcrumbsItems"
131
120
  class="mb-6"
132
121
  :ui="{
@@ -136,17 +125,11 @@
136
125
  />
137
126
  <div
138
127
  v-if="app.pageMeta.title"
139
- class="
140
- mb-4 flex flex-col justify-between gap-1 md:mb-6 md:gap-4
141
- lg:flex-row lg:items-start
142
- "
128
+ class="mb-4 flex flex-col justify-between gap-1 md:mb-6 md:gap-4 lg:flex-row lg:items-start"
143
129
  >
144
130
  <div class="flex flex-1 flex-col">
145
131
  <h1
146
- class="
147
- text-3xl font-bold wrap-break-word
148
- lg:max-w-2/3
149
- "
132
+ class="text-3xl font-bold wrap-break-word lg:max-w-2/3"
150
133
  :title="app.pageMeta.title"
151
134
  >
152
135
  {{ app.pageMeta.title }}
@@ -154,10 +137,7 @@
154
137
  </h1>
155
138
 
156
139
  <div id="page-subtitle" />
157
- <p
158
- v-if="app.pageMeta.sub_title"
159
- class="text-[#475467]"
160
- >
140
+ <p v-if="app.pageMeta.sub_title" class="text-[#475467]">
161
141
  {{ app.pageMeta.sub_title }}
162
142
  </p>
163
143
  </div>
@@ -174,7 +154,7 @@
174
154
  import type { DropdownMenuItem, NavigationMenuItem } from '@nuxt/ui'
175
155
  import { computed } from 'vue'
176
156
  import Sidebar from './Sidebar.vue'
177
- import Apps from './Apps.vue'
157
+ import Apps from '../Apps.vue'
178
158
 
179
159
  defineProps<{
180
160
  label: string
@@ -0,0 +1,99 @@
1
+ <template>
2
+ <div class="overflow-hidden pt-[80px]">
3
+ <div class="flex min-h-[calc(100vh-80px)] flex-col">
4
+ <nav
5
+ class="fixed top-0 left-0 z-20 flex min-h-[80px] w-screen items-center justify-center bg-white"
6
+ style="box-shadow: 0px 4px 4px 0px rgba(0, 0, 0, 0.04)"
7
+ >
8
+ <div class="mx-auto flex w-full px-4 md:px-8">
9
+ <div class="flex items-center gap-4">
10
+ <NuxtLink :to="routes.home.to">
11
+ <img
12
+ src="/logo-mini.png"
13
+ alt="logo_mini"
14
+ class="h-[27px] w-[172px] cursor-pointer"
15
+ />
16
+ </NuxtLink>
17
+ <Apps />
18
+ </div>
19
+
20
+ <div class="flex flex-1 items-center justify-end gap-2">
21
+ <DropdownMenu
22
+ :items="userMenuItems"
23
+ size="xl"
24
+ :content="{
25
+ align: 'end',
26
+ side: 'bottom',
27
+ }"
28
+ :ui="{
29
+ content: 'w-48',
30
+ }"
31
+ >
32
+ <div class="relative flex cursor-pointer items-center gap-2">
33
+ <div class="hidden flex-col justify-end text-right lg:flex">
34
+ <p class="text-sm font-bold">
35
+ {{
36
+ auth.me.value?.display_name || auth.me.value?.full_name
37
+ }}
38
+ </p>
39
+ <p class="text-muted text-xs">
40
+ {{ auth.me.value?.email }}
41
+ </p>
42
+ </div>
43
+ <Avatar
44
+ class="border-muted size-[36px] border text-2xl"
45
+ icon="ri:user-line"
46
+ :src="auth.me.value?.avatar_url"
47
+ />
48
+ <Icon name="i-ph:caret-down-light" class="size-5" />
49
+ </div>
50
+ </DropdownMenu>
51
+ </div>
52
+ </div>
53
+ </nav>
54
+ <main class="mx-auto w-full max-w-7xl flex-1 px-4 pb-20 2xl:max-w-7/10">
55
+ <div
56
+ v-if="app.pageMeta.title"
57
+ class="mb-4 flex flex-col justify-between gap-1 md:mb-6 md:gap-4 lg:flex-row lg:items-center"
58
+ >
59
+ <div class="flex flex-1 flex-col">
60
+ <h1
61
+ class="text-3xl font-bold wrap-break-word lg:max-w-2/3"
62
+ :title="app.pageMeta.title"
63
+ >
64
+ {{ app.pageMeta.title }}
65
+ <span id="page-title-extra" />
66
+ </h1>
67
+
68
+ <div id="page-subtitle" />
69
+ </div>
70
+ <div id="page-header" />
71
+ </div>
72
+ <slot />
73
+ </main>
74
+ <div id="page-footer" />
75
+ </div>
76
+ </div>
77
+ </template>
78
+
79
+ <script lang="ts" setup>
80
+ import type { DropdownMenuItem } from '@nuxt/ui'
81
+ import Apps from '../Apps.vue'
82
+
83
+ const app = useApp()
84
+ const auth = useAuth()
85
+ const userMenuItems: DropdownMenuItem[] = [
86
+ {
87
+ label: 'View profile',
88
+ icon: 'i-lucide-user',
89
+ to: routes.account.profile.to,
90
+ },
91
+
92
+ {
93
+ label: routes.logout.label,
94
+ icon: 'i-lucide-log-out',
95
+ to: routes.logout.to,
96
+ external: true,
97
+ },
98
+ ]
99
+ </script>
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@finema/finework-layer",
3
3
  "type": "module",
4
- "version": "0.1.1",
4
+ "version": "0.1.3",
5
5
  "main": "./nuxt.config.ts",
6
6
  "scripts": {
7
7
  "dev": "nuxi dev .playground -o",