adminforth 2.4.0-next.33 → 2.4.0-next.331

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 (177) hide show
  1. package/commands/callTsProxy.js +14 -4
  2. package/commands/createApp/templates/api.ts.hbs +10 -0
  3. package/commands/createApp/templates/custom/tsconfig.json.hbs +2 -3
  4. package/commands/createApp/templates/index.ts.hbs +12 -1
  5. package/commands/createApp/templates/package.json.hbs +1 -1
  6. package/commands/createApp/templates/prisma.config.ts.hbs +8 -0
  7. package/commands/createApp/templates/schema.prisma.hbs +0 -1
  8. package/commands/createApp/utils.js +10 -0
  9. package/commands/createCustomComponent/configLoader.js +17 -4
  10. package/commands/createCustomComponent/main.js +13 -7
  11. package/commands/createCustomComponent/templates/customCrud/beforeActionButtons.vue.hbs +38 -0
  12. package/commands/createCustomComponent/templates/customCrud/saveButton.vue.hbs +28 -0
  13. package/commands/createPlugin/templates/custom/tsconfig.json.hbs +2 -5
  14. package/commands/createPlugin/templates/package.json.hbs +1 -1
  15. package/commands/generateModels.js +30 -22
  16. package/dist/auth.d.ts +9 -1
  17. package/dist/auth.d.ts.map +1 -1
  18. package/dist/auth.js +21 -2
  19. package/dist/auth.js.map +1 -1
  20. package/dist/dataConnectors/baseConnector.d.ts +1 -1
  21. package/dist/dataConnectors/baseConnector.d.ts.map +1 -1
  22. package/dist/dataConnectors/baseConnector.js +70 -18
  23. package/dist/dataConnectors/baseConnector.js.map +1 -1
  24. package/dist/dataConnectors/clickhouse.d.ts.map +1 -1
  25. package/dist/dataConnectors/clickhouse.js +15 -0
  26. package/dist/dataConnectors/clickhouse.js.map +1 -1
  27. package/dist/dataConnectors/mongo.d.ts.map +1 -1
  28. package/dist/dataConnectors/mongo.js +50 -15
  29. package/dist/dataConnectors/mongo.js.map +1 -1
  30. package/dist/dataConnectors/mysql.d.ts.map +1 -1
  31. package/dist/dataConnectors/mysql.js +11 -0
  32. package/dist/dataConnectors/mysql.js.map +1 -1
  33. package/dist/dataConnectors/postgres.d.ts.map +1 -1
  34. package/dist/dataConnectors/postgres.js +43 -14
  35. package/dist/dataConnectors/postgres.js.map +1 -1
  36. package/dist/dataConnectors/sqlite.d.ts.map +1 -1
  37. package/dist/dataConnectors/sqlite.js +11 -0
  38. package/dist/dataConnectors/sqlite.js.map +1 -1
  39. package/dist/index.d.ts +11 -1
  40. package/dist/index.d.ts.map +1 -1
  41. package/dist/index.js +44 -21
  42. package/dist/index.js.map +1 -1
  43. package/dist/modules/codeInjector.d.ts +2 -0
  44. package/dist/modules/codeInjector.d.ts.map +1 -1
  45. package/dist/modules/codeInjector.js +62 -6
  46. package/dist/modules/codeInjector.js.map +1 -1
  47. package/dist/modules/configValidator.d.ts +6 -0
  48. package/dist/modules/configValidator.d.ts.map +1 -1
  49. package/dist/modules/configValidator.js +209 -25
  50. package/dist/modules/configValidator.js.map +1 -1
  51. package/dist/modules/restApi.d.ts +1 -1
  52. package/dist/modules/restApi.d.ts.map +1 -1
  53. package/dist/modules/restApi.js +199 -31
  54. package/dist/modules/restApi.js.map +1 -1
  55. package/dist/modules/styles.d.ts +499 -13
  56. package/dist/modules/styles.d.ts.map +1 -1
  57. package/dist/modules/styles.js +555 -31
  58. package/dist/modules/styles.js.map +1 -1
  59. package/dist/modules/utils.d.ts +7 -15
  60. package/dist/modules/utils.d.ts.map +1 -1
  61. package/dist/modules/utils.js +45 -68
  62. package/dist/modules/utils.js.map +1 -1
  63. package/dist/servers/express.d.ts +5 -0
  64. package/dist/servers/express.d.ts.map +1 -1
  65. package/dist/servers/express.js +40 -1
  66. package/dist/servers/express.js.map +1 -1
  67. package/dist/spa/index.html +1 -1
  68. package/dist/spa/package-lock.json +1208 -708
  69. package/dist/spa/package.json +34 -34
  70. package/dist/spa/src/App.vue +132 -174
  71. package/dist/spa/src/adminforth.ts +41 -17
  72. package/dist/spa/src/afcl/AreaChart.vue +0 -1
  73. package/dist/spa/src/afcl/BarChart.vue +2 -2
  74. package/dist/spa/src/afcl/Button.vue +3 -3
  75. package/dist/spa/src/afcl/ButtonGroup.vue +91 -0
  76. package/dist/spa/src/afcl/Card.vue +25 -0
  77. package/dist/spa/src/afcl/Checkbox.vue +21 -13
  78. package/dist/spa/src/afcl/CountryFlag.vue +4 -1
  79. package/dist/spa/src/{components/CustomDatePicker.vue → afcl/DatePicker.vue} +95 -9
  80. package/dist/spa/src/afcl/Dialog.vue +47 -27
  81. package/dist/spa/src/afcl/Dropzone.vue +145 -48
  82. package/dist/spa/src/afcl/Input.vue +14 -6
  83. package/dist/spa/src/afcl/JsonViewer.vue +25 -0
  84. package/dist/spa/src/afcl/LinkButton.vue +3 -3
  85. package/dist/spa/src/afcl/PieChart.vue +5 -5
  86. package/dist/spa/src/afcl/ProgressBar.vue +7 -7
  87. package/dist/spa/src/afcl/Select.vue +82 -34
  88. package/dist/spa/src/afcl/Skeleton.vue +6 -6
  89. package/dist/spa/src/afcl/Table.vue +313 -75
  90. package/dist/spa/src/afcl/Textarea.vue +31 -0
  91. package/dist/spa/src/afcl/Toggle.vue +32 -0
  92. package/dist/spa/src/afcl/Tooltip.vue +28 -18
  93. package/dist/spa/src/afcl/VerticalTabs.vue +21 -7
  94. package/dist/spa/src/afcl/index.ts +6 -3
  95. package/dist/spa/src/components/AcceptModal.vue +48 -14
  96. package/dist/spa/src/components/Breadcrumbs.vue +5 -5
  97. package/dist/spa/src/components/CallActionWrapper.vue +15 -0
  98. package/dist/spa/src/components/ColumnValueInput.vue +38 -18
  99. package/dist/spa/src/components/ColumnValueInputWrapper.vue +4 -3
  100. package/dist/spa/src/components/CustomDateRangePicker.vue +9 -8
  101. package/dist/spa/src/components/CustomRangePicker.vue +37 -21
  102. package/dist/spa/src/components/ErrorMessage.vue +21 -0
  103. package/dist/spa/src/components/Filters.vue +195 -132
  104. package/dist/spa/src/components/GroupsTable.vue +9 -8
  105. package/dist/spa/src/components/MenuLink.vue +95 -23
  106. package/dist/spa/src/components/ResourceForm.vue +99 -51
  107. package/dist/spa/src/components/ResourceListTable.vue +121 -95
  108. package/dist/spa/src/components/ResourceListTableVirtual.vue +119 -88
  109. package/dist/spa/src/components/ShowTable.vue +21 -15
  110. package/dist/spa/src/components/Sidebar.vue +472 -0
  111. package/dist/spa/src/components/SingleSkeletLoader.vue +6 -6
  112. package/dist/spa/src/components/SkeleteLoader.vue +3 -3
  113. package/dist/spa/src/components/ThreeDotsMenu.vue +84 -15
  114. package/dist/spa/src/components/Toast.vue +40 -29
  115. package/dist/spa/src/components/UserMenuSettingsButton.vue +69 -0
  116. package/dist/spa/src/components/ValueRenderer.vue +44 -17
  117. package/dist/spa/src/controls/BoolToggle.vue +34 -0
  118. package/dist/spa/src/i18n.ts +5 -3
  119. package/dist/spa/src/main.ts +1 -1
  120. package/dist/spa/src/renderers/CompactField.vue +1 -1
  121. package/dist/spa/src/renderers/CompactUUID.vue +1 -1
  122. package/dist/spa/src/router/index.ts +8 -0
  123. package/dist/spa/src/shims-vue.d.ts +5 -0
  124. package/dist/spa/src/spa_types/core.ts +13 -1
  125. package/dist/spa/src/stores/core.ts +15 -1
  126. package/dist/spa/src/stores/filters.ts +33 -2
  127. package/dist/spa/src/stores/modal.ts +6 -1
  128. package/dist/spa/src/stores/toast.ts +22 -3
  129. package/dist/spa/src/types/Back.ts +168 -23
  130. package/dist/spa/src/types/Common.ts +109 -32
  131. package/dist/spa/src/types/FrontendAPI.ts +32 -23
  132. package/dist/spa/src/types/adapters/CaptchaAdapter.ts +34 -0
  133. package/dist/spa/src/types/adapters/EmailAdapter.ts +2 -4
  134. package/dist/spa/src/types/adapters/ImageVisionAdapter.ts +30 -0
  135. package/dist/spa/src/types/adapters/KeyValueAdapter.ts +16 -0
  136. package/dist/spa/src/types/adapters/StorageAdapter.ts +4 -2
  137. package/dist/spa/src/types/adapters/index.ts +3 -0
  138. package/dist/spa/src/utils.ts +291 -11
  139. package/dist/spa/src/views/CreateView.vue +88 -22
  140. package/dist/spa/src/views/EditView.vue +55 -22
  141. package/dist/spa/src/views/ListView.vue +144 -87
  142. package/dist/spa/src/views/LoginView.vue +26 -35
  143. package/dist/spa/src/views/ResourceParent.vue +2 -2
  144. package/dist/spa/src/views/SettingsView.vue +121 -0
  145. package/dist/spa/src/views/ShowView.vue +83 -53
  146. package/dist/spa/src/websocket.ts +6 -1
  147. package/dist/spa/tsconfig.app.json +1 -1
  148. package/dist/spa/vite.config.ts +45 -2
  149. package/dist/types/Back.d.ts +151 -14
  150. package/dist/types/Back.d.ts.map +1 -1
  151. package/dist/types/Back.js +15 -0
  152. package/dist/types/Back.js.map +1 -1
  153. package/dist/types/Common.d.ts +123 -29
  154. package/dist/types/Common.d.ts.map +1 -1
  155. package/dist/types/Common.js.map +1 -1
  156. package/dist/types/FrontendAPI.d.ts +32 -18
  157. package/dist/types/FrontendAPI.d.ts.map +1 -1
  158. package/dist/types/FrontendAPI.js.map +1 -1
  159. package/dist/types/adapters/CaptchaAdapter.d.ts +30 -0
  160. package/dist/types/adapters/CaptchaAdapter.d.ts.map +1 -0
  161. package/dist/types/adapters/CaptchaAdapter.js +5 -0
  162. package/dist/types/adapters/CaptchaAdapter.js.map +1 -0
  163. package/dist/types/adapters/EmailAdapter.d.ts +2 -3
  164. package/dist/types/adapters/EmailAdapter.d.ts.map +1 -1
  165. package/dist/types/adapters/ImageVisionAdapter.d.ts +25 -0
  166. package/dist/types/adapters/ImageVisionAdapter.d.ts.map +1 -0
  167. package/dist/types/adapters/ImageVisionAdapter.js +2 -0
  168. package/dist/types/adapters/ImageVisionAdapter.js.map +1 -0
  169. package/dist/types/adapters/KeyValueAdapter.d.ts +10 -0
  170. package/dist/types/adapters/KeyValueAdapter.d.ts.map +1 -0
  171. package/dist/types/adapters/KeyValueAdapter.js +2 -0
  172. package/dist/types/adapters/KeyValueAdapter.js.map +1 -0
  173. package/dist/types/adapters/StorageAdapter.d.ts +2 -0
  174. package/dist/types/adapters/StorageAdapter.d.ts.map +1 -1
  175. package/dist/types/adapters/index.d.ts +3 -0
  176. package/dist/types/adapters/index.d.ts.map +1 -1
  177. package/package.json +4 -2
@@ -0,0 +1,472 @@
1
+ <template>
2
+ <aside
3
+ ref="sidebarAside"
4
+ @mouseover="!isTogglingSidebar && (isSidebarHovering = true)"
5
+ @mouseleave="!isTogglingSidebar && (isSidebarHovering = false)"
6
+ id="logo-lightSidebar"
7
+ class="sidebar-container fixed border-none top-0 left-0 z-40 h-screen transition-all duration-300 ease-in-out bg-lightSidebar dark:bg-darkSidebar border-r border-lightSidebarBorder sm:translate-x-0 dark:border-darkSidebarBorder"
8
+ :class="{
9
+ '-translate-x-full': !sideBarOpen,
10
+ 'transform-none': sideBarOpen,
11
+ 'sidebar-collapsed': iconOnlySidebarEnabled && isSidebarIconOnly && !isSidebarHovering,
12
+ 'sidebar-expanded': !iconOnlySidebarEnabled || !isSidebarIconOnly || (isSidebarIconOnly && isSidebarHovering)
13
+ }"
14
+ aria-label="Sidebar"
15
+ >
16
+ <div class="h-full px-3 pb-20 md:pb-4 bg-lightSidebar dark:bg-darkSidebar border-r border-lightSidebarBorder dark:border-darkSidebarBorder pt-4" :class="{'sidebar-scroll':!isSidebarIconOnly || (isSidebarIconOnly && isSidebarHovering)}">
17
+ <div class="af-logo-title-wrapper flex relative transition-all duration-300 ease-in-out h-8 items-center" :class="{'mb-4': isSidebarIconOnly && !isSidebarHovering, 'mx-4 mb-4': !isSidebarIconOnly || (isSidebarIconOnly && isSidebarHovering)}">
18
+ <img :src="loadFile(coreStore.config?.brandLogo || '@/assets/logo.svg')" :alt="`${ coreStore.config?.brandName } Logo`" class="af-logo h-8 me-3" :class="{ 'hidden': !(coreStore.config?.showBrandLogoInSidebar !== false && (!iconOnlySidebarEnabled || !isSidebarIconOnly || (isSidebarIconOnly && isSidebarHovering))) }" />
19
+ <img :src="loadFile(coreStore.config?.iconOnlySidebar?.logo || '')" :alt="`${ coreStore.config?.brandName } Logo`" class="af-sidebar-icon-only-logo h-8 me-3" :class="{ 'hidden': !(coreStore.config?.showBrandLogoInSidebar !== false && coreStore.config?.iconOnlySidebar?.logo && iconOnlySidebarEnabled && isSidebarIconOnly && !isSidebarHovering) }" />
20
+ <span
21
+ v-if="coreStore.config?.showBrandNameInSidebar && (!iconOnlySidebarEnabled || !isSidebarIconOnly || (isSidebarIconOnly && isSidebarHovering))"
22
+ class="af-title self-center text-lightNavbarText-size font-semibold sm:text-lightNavbarText-size whitespace-nowrap dark:text-darkSidebarText text-lightSidebarText"
23
+ >
24
+ {{ coreStore.config?.brandName }}
25
+ </span>
26
+ <div v-if="!isSidebarIconOnly || (isSidebarIconOnly && isSidebarHovering)" class="flex items-center gap-2 w-auto" :class="{'w-full justify-end': coreStore.config?.showBrandLogoInSidebar === false}">
27
+ <component
28
+ v-for="c in coreStore?.config?.globalInjections?.sidebarTop || []"
29
+ :is="getCustomComponent(c)"
30
+ :meta="c.meta"
31
+ :adminUser="coreStore.adminUser"
32
+ />
33
+ </div>
34
+ <div class="absolute top-1.5 -right-4 z-10 hidden sm:block" v-if="!forceIconOnly && iconOnlySidebarEnabled && (!isSidebarIconOnly || (isSidebarIconOnly && isSidebarHovering))">
35
+ <button class="text-sm text-lightSidebarIcons group-hover:text-lightSidebarIconsHover dark:group-hover:text-darkSidebarIconsHover dark:text-darkSidebarIcons" @click="toggleSidebar">
36
+ <IconCloseSidebarSolid v-if="!isSidebarIconOnly" class="w-5 h-5 active:scale-95 transition-all duration-200 hover:text-lightSidebarIconsHover dark:hover:text-darkSidebarIconsHover" />
37
+ <IconOpenSidebarSolid v-else class="w-5 h-5 active:scale-95 transition-all duration-200 hover:text-lightSidebarIconsHover dark:hover:text-darkSidebarIconsHover" />
38
+ </button>
39
+ </div>
40
+ </div>
41
+
42
+ <ul class="af-sidebar-container space-y-2 font-medium" >
43
+ <template v-if="!iconOnlySidebarEnabled || !isSidebarIconOnly" v-for="(item, i) in coreStore.menu" :key="`menu-${i}`">
44
+ <div v-if="item.type === 'divider'" class="border-t border-lightSidebarDevider dark:border-darkSidebarDevider"></div>
45
+ <div v-else-if="item.type === 'gap'" class="flex items-center justify-center h-8"></div>
46
+ <div v-else-if="item.type === 'heading'" class="flex items-center justify-left pl-2 h-8 text-lightSidebarHeading dark:text-darkSidebarHeading
47
+ ">{{ item.label }}</div>
48
+ <li v-else-if="item.children" class="af-sidebar-expand-container">
49
+ <button @click="clickOnMenuItem(i)" type="button" class="af-sidebar-expand-button flex items-center w-full px-3.5 py-2 text-base text-lightSidebarText rounded-default transition duration-75 group hover:bg-lightSidebarItemHover hover:text-lightSidebarTextHover dark:text-darkSidebarText dark:hover:bg-darkSidebarHover dark:hover:text-darkSidebarTextHover"
50
+ :class="opened.includes(i) ? 'af-sidebar-dropdown-expanded' : 'af-sidebar-dropdown-collapsed'"
51
+ :aria-controls="`dropdown-example${i}`"
52
+ :data-collapse-toggle="`dropdown-example${i}`"
53
+ >
54
+ <component v-if="item.icon" :is="getIcon(item.icon)" class="w-5 h-5 text-lightSidebarIcons group-hover:text-lightSidebarIconsHover transition duration-75 dark:group-hover:text-darkSidebarIconsHover dark:text-darkSidebarIcons" ></component>
55
+ <span class="overflow-hidden flex-1 ms-3 text-left rtl:text-right whitespace-nowrap">{{ item.label }}
56
+ <span v-if="item.badge" class="inline-flex items-center justify-center w-3 h-3 p-3 ms-3 text-sm font-medium rounded-full bg-lightAnnouncementBG dark:bg-darkAnnouncementBG
57
+ fill-lightAnnouncementText dark:fill-darkAccent text-lightAnnouncementText dark:text-darkAccent">
58
+ <Tooltip v-if="item.badgeTooltip">
59
+ {{ item.badge }}
60
+ <template #tooltip>
61
+ {{ item.badgeTooltip }}
62
+ </template>
63
+ </Tooltip>
64
+ <template v-else>
65
+ {{ item.badge }}
66
+ </template>
67
+ </span>
68
+ </span>
69
+
70
+ <svg :class="{'rotate-180': opened.includes(i) }" class="w-3 h-3" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 10 6">
71
+ <path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="m1 1 4 4 4-4"/>
72
+ </svg>
73
+ </button>
74
+
75
+ <ul :id="`dropdown-example${i}`" role="none" class="af-sidebar-dropdown pt-1 space-y-1" :class="{ 'hidden': !opened.includes(i) }">
76
+ <template v-for="(child, j) in item.children" :key="`menu-${i}-${j}`">
77
+ <li class="af-sidebar-menu-link">
78
+ <MenuLink :item="child" isChild="true" @click="$emit('hideSidebar')"/>
79
+ </li>
80
+ </template>
81
+ </ul>
82
+ </li>
83
+ <li v-else class="af-sidebar-menu-link">
84
+ <MenuLink :item="item" @click="$emit('hideSidebar')"/>
85
+ </li>
86
+ </template>
87
+ <template v-if="iconOnlySidebarEnabled && isSidebarIconOnly" v-for="(item, i) in coreStore.menu" :key="`menu-${i}`">
88
+ <div v-if="item.type === 'divider'" class="border-t border-lightSidebarDevider dark:border-darkSidebarDevider"></div>
89
+ <div v-else-if="item.type === 'gap'" class="flex items-center justify-center h-8"></div>
90
+ <div v-else-if="item.type === 'heading' && isSidebarHovering" class="flex items-center justify-left pl-2 h-8 text-lightSidebarHeading dark:text-darkSidebarHeading
91
+ ">{{ item.label }}</div>
92
+ <div v-else-if="item.type === 'heading' && !isSidebarHovering" class="opacity-0 w-1 h-8">{{ item.label }}</div>
93
+ <li v-else-if="item.children" class="af-sidebar-expand-container">
94
+ <button @click="clickOnMenuItem(i)" type="button" class="af-sidebar-expand-button relative flex items-center h-10 w-full px-3.5 py-2 text-base text-lightSidebarText rounded-default group hover:bg-lightSidebarItemHover hover:text-lightSidebarTextHover dark:text-darkSidebarText dark:hover:bg-darkSidebarHover dark:hover:text-darkSidebarTextHover"
95
+ :class="opened.includes(i) ? 'af-sidebar-dropdown-expanded' : 'af-sidebar-dropdown-collapsed'"
96
+ :aria-controls="`dropdown-example${i}`"
97
+ :data-collapse-toggle="`dropdown-example${i}`"
98
+ >
99
+ <component v-if="item.icon" :is="getIcon(item.icon)" class="min-w-5 min-h-5 text-lightSidebarIcons group-hover:text-lightSidebarIconsHover transition duration-75 dark:group-hover:text-darkSidebarIconsHover dark:text-darkSidebarIcons" ></component>
100
+
101
+ <span
102
+ class="overflow-hidden block ms-3 text-left rtl:text-right transition-all duration-200 ease-in-out"
103
+ :class="{
104
+ 'opacity-0 ms-0 translate-x-4 flex-none': isSidebarIconOnly && !isSidebarHovering,
105
+ 'opacity-100 ms-3 translate-x-0 flex-none': isSidebarIconOnly && isSidebarHovering,
106
+ 'opacity-100 ms-3 translate-x-0 flex-1': !isSidebarIconOnly
107
+ }"
108
+ :style="isSidebarIconOnly ? {
109
+ minWidth: `calc(${expandedWidth} - 0.75rem*2 - 0.875rem*2 - 1.25rem - 0.75rem)`,
110
+ width: `calc(${expandedWidth} - 0.75rem*2 - 0.875rem*2 - 1.25rem - 0.75rem)`
111
+ } : {}"
112
+ >{{ item.label }}
113
+
114
+ <span v-if="item.badge" class="inline-flex items-center justify-center w-3 h-3 p-3 ms-3 text-sm font-medium rounded-full bg-lightAnnouncementBG dark:bg-darkAnnouncementBG
115
+ fill-lightAnnouncementText dark:fill-darkAccent text-lightAnnouncementText dark:text-darkAccent">
116
+ <Tooltip v-if="item.badgeTooltip">
117
+ {{ item.badge }}
118
+ <template #tooltip>
119
+ {{ item.badgeTooltip }}
120
+ </template>
121
+ </Tooltip>
122
+ <template v-else>
123
+ {{ item.badge }}
124
+ </template>
125
+ </span>
126
+ </span>
127
+
128
+ <svg :class="{'rotate-180': opened.includes(i) }" class="w-3 h-3 ml-2 absolute right-3.5" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 10 6">
129
+ <path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="m1 1 4 4 4-4"/>
130
+ </svg>
131
+ </button>
132
+
133
+ <ul :id="`dropdown-example${i}`" role="none" class="af-sidebar-dropdown pt-1 space-y-1" :class="{ 'hidden': !opened.includes(i), 'relative after:absolute after:-left-3 after:inset-y-0 after:w-0.5 after:bg-lightSidebarIcons/50 dark:after:bg-darkSidebarIcons/50 after:rounded-full': isSidebarIconOnly && !isSidebarHovering && opened.includes(i) }">
134
+ <template v-for="(child, j) in item.children" :key="`menu-${i}-${j}`">
135
+ <li class="af-sidebar-menu-link">
136
+ <MenuLink :item="child" isChild="true" @click="$emit('hideSidebar')" :isSidebarIconOnly="isSidebarIconOnly" :isSidebarHovering="isSidebarHovering"/>
137
+ </li>
138
+ </template>
139
+ </ul>
140
+ </li>
141
+ <li v-else class="af-sidebar-menu-link">
142
+ <MenuLink :item="item" @click="$emit('hideSidebar')" :isSidebarIconOnly="isSidebarIconOnly" :isSidebarHovering="isSidebarHovering"/>
143
+ </li>
144
+ </template>
145
+ </ul>
146
+
147
+
148
+ <div id="dropdown-cta" class="p-4 mt-6 w-[230px] rounded-lg bg-lightAnnouncementBG dark:bg-darkAnnouncementBG
149
+ fill-lightAnnouncementText dark:fill-darkAccent text-lightAnnouncementText dark:text-darkAccent text-sm" role="alert"
150
+ v-if="(ctaBadge && !isSidebarIconOnly) || (ctaBadge && isSidebarIconOnly && isSidebarHovering)"
151
+ >
152
+ <div class="flex items-center mb-3" :class="!ctaBadge.title ? 'float-right' : ''">
153
+ <!-- <span class="bg-lightPrimaryOpacity dark:bg-darkPrimaryOpacity text-sm font-semibold me-2 px-2.5 py-0.5 rounded "
154
+ v-if="ctaBadge.title"
155
+ > -->
156
+ <span>
157
+ {{ctaBadge.title}}
158
+ </span>
159
+ <button type="button"
160
+ class="ms-auto -mx-1.5 -my-1.5 bg-lightPrimaryOpacity dark:bg-darkPrimaryOpacity inline-flex justify-center items-center w-6 h-6 rounded-lg p-1 hover:brightness-110"
161
+
162
+ data-dismiss-target="#dropdown-cta" aria-label="Close"
163
+ v-if="ctaBadge?.closable" @click="closeCTA"
164
+ >
165
+ <span class="sr-only">Close</span>
166
+ <svg class="w-2.5 h-2.5" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 14 14">
167
+ <path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="m1 1 6 6m0 0 6 6M7 7l6-6M7 7l-6 6"/>
168
+ </svg>
169
+ </button>
170
+ </div>
171
+ <p class="mb-3 text-sm " v-if="ctaBadge.html" v-html="ctaBadge.html"></p>
172
+ <p class="mb-3 text-sm fill-lightNavbarText dark:fill-darkPrimary text-lightNavbarText dark:text-darkNavbarPrimary" v-else>
173
+ {{ ctaBadge.text }}
174
+ </p>
175
+ <!-- <a class="text-sm text-lightPrimary underline font-medium hover:text-blue-900 dark:text-blue-400 dark:hover:text-blue-300" href="#">Turn new navigation off</a> -->
176
+ </div>
177
+
178
+ <component
179
+ v-for="c in coreStore?.config?.globalInjections?.sidebar || []"
180
+ :is="getCustomComponent(c)"
181
+ :meta="c.meta"
182
+ :adminUser="coreStore.adminUser"
183
+ />
184
+ </div>
185
+ </aside>
186
+ </template>
187
+
188
+ <style lang="scss" scoped>
189
+ /* Sidebar width animations */
190
+ .sidebar-container {
191
+ width: v-bind(expandedWidth); /* Default expanded width (w-64) */
192
+ transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
193
+ overflow: hidden; /* Prevent content from showing during animation */
194
+ will-change: width, transform;
195
+ }
196
+
197
+ .sidebar-collapsed {
198
+ width: 4.5rem; /* Collapsed width (w-18) */
199
+ }
200
+
201
+ .sidebar-expanded {
202
+ width: v-bind(expandedWidth); /* Expanded width (w-64) */
203
+ box-shadow: 3px 0px 12px -2px rgba(0, 0, 0, 0.15);
204
+ }
205
+
206
+ :deep(.dark) .sidebar-collapsed {
207
+ box-shadow: 12px 0px 18px -8px rgba(0, 0, 0, 0.45);
208
+ }
209
+
210
+ /* Text visibility transitions */
211
+ .sidebar-collapsed .af-title {
212
+ opacity: 0;
213
+ transform: translateX(-12px);
214
+ transition: opacity 0.2s ease-out, transform 0.25s cubic-bezier(0.4, 0, 0.2, 1);
215
+ }
216
+ .sidebar-collapsed svg.w-3 {
217
+ opacity: 0;
218
+ transition: opacity 0.2s ease-out;
219
+ }
220
+
221
+ .sidebar-expanded .af-title {
222
+ opacity: 1;
223
+ transform: translateX(0);
224
+ transition: opacity 0.25s ease-in 0.1s, transform 0.3s cubic-bezier(0.4, 0, 0.2, 1) 0.05s;
225
+ }
226
+ .sidebar-expanded svg.w-3 {
227
+ opacity: 1;
228
+ transition: opacity 0.25s ease-in 0.1s;
229
+ }
230
+
231
+ /* Overlay scrollbar styling */
232
+ .sidebar-scroll {
233
+ overflow-y: auto;
234
+ overflow-x: hidden;
235
+ scrollbar-width: thin;
236
+ scrollbar-color: transparent transparent;
237
+ }
238
+
239
+ /* Webkit overlay scrollbar */
240
+ .sidebar-scroll::-webkit-scrollbar {
241
+ width: 8px;
242
+ }
243
+
244
+ .sidebar-scroll::-webkit-scrollbar-track {
245
+ background: transparent;
246
+ }
247
+
248
+ .sidebar-scroll::-webkit-scrollbar-thumb {
249
+ background-color: transparent;
250
+ border-radius: 4px;
251
+ transition: background-color 0.2s ease;
252
+ }
253
+
254
+ /* Show scrollbar on hover/scroll */
255
+ .sidebar-scroll:hover::-webkit-scrollbar-thumb,
256
+ .sidebar-scroll:active::-webkit-scrollbar-thumb {
257
+ background-color: rgba(156, 163, 175, 0.4);
258
+ }
259
+
260
+ .sidebar-scroll:hover {
261
+ scrollbar-color: rgba(156, 163, 175, 0.4) transparent;
262
+ }
263
+
264
+ /* Dark mode scrollbar */
265
+ .dark .sidebar-scroll:hover {
266
+ scrollbar-color: rgba(75, 85, 99, 0.4) transparent;
267
+ }
268
+
269
+ .dark .sidebar-scroll:hover::-webkit-scrollbar-thumb,
270
+ .dark .sidebar-scroll:active::-webkit-scrollbar-thumb {
271
+ background-color: rgba(75, 85, 99, 0.4);
272
+ }
273
+
274
+ /* For browsers that support overlay scrollbars natively */
275
+ @supports (overflow: overlay) {
276
+ .sidebar-scroll {
277
+ overflow-y: overlay;
278
+ }
279
+ }
280
+ </style>
281
+
282
+ <script setup lang="ts">
283
+ import { computed, ref, watch, nextTick, onMounted, onUnmounted, type Ref } from 'vue';
284
+ import { useCoreStore } from '@/stores/core';
285
+ import MenuLink from './MenuLink.vue';
286
+ import { IconCloseSidebarSolid, IconOpenSidebarSolid } from '@iconify-prerendered/vue-flowbite';
287
+ import { getIcon, verySimpleHash, loadFile, getCustomComponent } from '@/utils';
288
+ import { Tooltip } from '@/afcl';
289
+ import type { AnnouncementBadgeResponse } from '@/types/Common';
290
+ import adminforth from '@/adminforth';
291
+
292
+ interface Props {
293
+ sideBarOpen: boolean;
294
+ forceIconOnly?: boolean;
295
+ }
296
+
297
+ const props = defineProps<Props>();
298
+
299
+ const emit = defineEmits<{
300
+ hideSidebar: [];
301
+ loadMenu: [];
302
+ sidebarStateChange: [{ isSidebarIconOnly: boolean; isSidebarHovering: boolean }];
303
+ }>();
304
+
305
+ const coreStore = useCoreStore();
306
+
307
+ //create a ref to store the opened menu items with ts type;
308
+ const opened = ref<(string|number)[]>([]);
309
+ const sidebarAside = ref(null);
310
+
311
+ const smQuery = window.matchMedia('(min-width: 640px)');
312
+ const isMobile = ref(!smQuery.matches);
313
+ const iconOnlySidebarEnabled = computed(() => props.forceIconOnly === true || coreStore.config?.iconOnlySidebar?.enabled !== false);
314
+ const isSidebarIconOnly = ref(false);
315
+
316
+ const expandedWidth = computed(() => coreStore.config?.iconOnlySidebar?.expandedSidebarWidth || '16.5rem');
317
+
318
+ function handleBreakpointChange(e: MediaQueryListEvent) {
319
+ isMobile.value = !e.matches;
320
+ if (isMobile.value) {
321
+ isSidebarIconOnly.value = false;
322
+ } else {
323
+ if (props.forceIconOnly === true) {
324
+ isSidebarIconOnly.value = true;
325
+ } else if (iconOnlySidebarEnabled.value && localStorage.getItem('afIconOnlySidebar') === 'true') {
326
+ isSidebarIconOnly.value = true;
327
+ } else {
328
+ isSidebarIconOnly.value = false;
329
+ }
330
+ }
331
+ }
332
+
333
+ smQuery.addEventListener('change', handleBreakpointChange);
334
+
335
+
336
+ const isSidebarHovering = ref(false);
337
+ const isTogglingSidebar = ref(false);
338
+
339
+ function toggleSidebar() {
340
+ if (props.forceIconOnly) {
341
+ return;
342
+ }
343
+ if (!iconOnlySidebarEnabled.value) {
344
+ return;
345
+ }
346
+ if (isMobile.value) {
347
+ return;
348
+ }
349
+ isTogglingSidebar.value = true;
350
+ isSidebarIconOnly.value = !isSidebarIconOnly.value;
351
+ if (isSidebarIconOnly.value) {
352
+ isSidebarHovering.value = false;
353
+ }
354
+ setTimeout(() => {
355
+ isTogglingSidebar.value = false;
356
+ }, 100);
357
+ }
358
+
359
+ function clickOnMenuItem(label: string | number) {
360
+ if (opened.value.includes(label)) {
361
+ opened.value = opened.value.filter((item) => item !== label);
362
+ } else {
363
+ opened.value.push(label);
364
+ }
365
+ }
366
+
367
+ watch(()=>coreStore.menu, () => {
368
+ coreStore.menu.forEach((item, i) => {
369
+ if (item.open) {
370
+ opened.value.push(i);
371
+ };
372
+ });
373
+ })
374
+
375
+
376
+
377
+ watch(isSidebarIconOnly, (isIconOnly) => {
378
+ if (!isMobile.value && iconOnlySidebarEnabled.value && !props.forceIconOnly) {
379
+ localStorage.setItem('afIconOnlySidebar', isIconOnly.toString());
380
+ }
381
+ emit('sidebarStateChange', { isSidebarIconOnly: isIconOnly, isSidebarHovering: isSidebarHovering.value });
382
+ })
383
+
384
+ watch(isSidebarHovering, (hovering) => {
385
+ emit('sidebarStateChange', { isSidebarIconOnly: isSidebarIconOnly.value, isSidebarHovering: hovering });
386
+ })
387
+
388
+ watch(sidebarAside, (sidebarAside) => {
389
+ if (sidebarAside) {
390
+ coreStore.fetchMenuBadges();
391
+ }
392
+ })
393
+
394
+ const ctaBadge: Ref<(AnnouncementBadgeResponse & { hash: string; }) | null> = computed(() => {
395
+ const badge = coreStore.config?.announcementBadge;
396
+ if (!badge) {
397
+ return null;
398
+ }
399
+ const hash = badge.closable ? verySimpleHash(JSON.stringify(badge)) : '';
400
+ if (badge.closable && window.localStorage.getItem(`ctaBadge-${hash}`)) {
401
+ return null;
402
+ }
403
+ return {...badge, hash};
404
+ });
405
+
406
+ function closeCTA() {
407
+ if (!ctaBadge.value) {
408
+ return;
409
+ }
410
+ const hash = ctaBadge.value.hash;
411
+ window.localStorage.setItem(`ctaBadge-${hash}`, '1');
412
+ nextTick( async() => {
413
+ emit('loadMenu');
414
+ await coreStore.fetchMenuBadges();
415
+ adminforth.menu.refreshMenuBadges();
416
+ })
417
+ }
418
+
419
+ onMounted(() => {
420
+ if (!iconOnlySidebarEnabled.value) {
421
+ isSidebarIconOnly.value = false;
422
+ }
423
+
424
+ coreStore.menu.forEach((item, i) => {
425
+ if (item.open) {
426
+ opened.value.push(i);
427
+ };
428
+ });
429
+ // Emit initial state
430
+ emit('sidebarStateChange', { isSidebarIconOnly: isSidebarIconOnly.value, isSidebarHovering: isSidebarHovering.value });
431
+ })
432
+
433
+ onUnmounted(() => {
434
+ smQuery.removeEventListener('change', handleBreakpointChange);
435
+ if (isMobile.value && props.sideBarOpen) {
436
+ document.body.style.overflow = '';
437
+ document.body.style.position = '';
438
+ document.body.style.width = '';
439
+ }
440
+ })
441
+
442
+ watch(() => props.forceIconOnly, (force) => {
443
+ if (isMobile.value) {
444
+ isSidebarIconOnly.value = false;
445
+ return;
446
+ }
447
+ if (props.forceIconOnly === true) {
448
+ isSidebarIconOnly.value = true;
449
+ } else if (iconOnlySidebarEnabled.value && localStorage.getItem('afIconOnlySidebar') === 'true') {
450
+ isSidebarIconOnly.value = true;
451
+ } else {
452
+ isSidebarIconOnly.value = false;
453
+ }
454
+ }, { immediate: true })
455
+
456
+ watch(() => props.sideBarOpen, (isOpen) => {
457
+ if (isMobile.value) {
458
+ if (isOpen) {
459
+ // Lock body scroll
460
+ document.body.style.overflow = 'hidden';
461
+ document.body.style.position = 'fixed';
462
+ document.body.style.width = '100%';
463
+ } else {
464
+ // Unlock body scroll
465
+ document.body.style.overflow = '';
466
+ document.body.style.position = '';
467
+ document.body.style.width = '';
468
+ }
469
+ }
470
+ }, { immediate: true })
471
+
472
+ </script>
@@ -2,12 +2,12 @@
2
2
  <div
3
3
  role="status" class="max-w-sm animate-pulse"
4
4
  >
5
- <div class="h-2.5 bg-gray-200 rounded-full dark:bg-gray-700 w-48 mb-4"></div>
6
- <div class="h-2 bg-gray-200 rounded-full dark:bg-gray-700 max-w-[360px] mb-2.5"></div>
7
- <div class="h-2 bg-gray-200 rounded-full dark:bg-gray-700 mb-2.5"></div>
8
- <div class="h-2 bg-gray-200 rounded-full dark:bg-gray-700 max-w-[330px] mb-2.5"></div>
9
- <div class="h-2 bg-gray-200 rounded-full dark:bg-gray-700 max-w-[300px] mb-2.5"></div>
10
- <div class="h-2 bg-gray-200 rounded-full dark:bg-gray-700 max-w-[360px]"></div>
5
+ <div class="h-2.5 bg-lightSkeletonBackgroundColor rounded-full dark:bg-darkSkeletonBackgroundColor w-48 mb-4"></div>
6
+ <div class="h-2 bg-lightSkeletonBackgroundColor rounded-full dark:bg-darkSkeletonBackgroundColor max-w-[360px] mb-2.5"></div>
7
+ <div class="h-2 bg-lightSkeletonBackgroundColor rounded-full dark:bg-darkSkeletonBackgroundColor mb-2.5"></div>
8
+ <div class="h-2 bg-lightSkeletonBackgroundColor rounded-full dark:bg-darkSkeletonBackgroundColor max-w-[330px] mb-2.5"></div>
9
+ <div class="h-2 bg-lightSkeletonBackgroundColor rounded-full dark:bg-darkSkeletonBackgroundColor max-w-[300px] mb-2.5"></div>
10
+ <div class="h-2 bg-lightSkeletonBackgroundColor rounded-full dark:bg-darkSkeletonBackgroundColor max-w-[360px]"></div>
11
11
  <span class="sr-only">{{ $t('Loading...') }}</span>
12
12
  </div>
13
13
  </template>
@@ -12,7 +12,7 @@
12
12
  : '']"
13
13
  >
14
14
  <div role="status" class="max-w-sm animate-pulse">
15
- <div class="h-2 bg-gray-200 rounded-full dark:bg-gray-700 max-w-[360px]"></div>
15
+ <div class="h-2 bg-lightSkeletonBackgroundColor rounded-full dark:bg-darkSkeletonBackgroundColor max-w-[360px]"></div>
16
16
  </div>
17
17
  </td>
18
18
  </tr>
@@ -26,8 +26,8 @@ const props = withDefaults(defineProps<{
26
26
  rowHeights?: number[];
27
27
  columnWidths?: number[];
28
28
  }>(), {
29
- rowHeights: [],
30
- columnWidths: [],
29
+ rowHeights: () => [],
30
+ columnWidths: () => [],
31
31
  });
32
32
 
33
33
  </script>