adminforth 2.4.0-next.31 → 2.4.0-next.310

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 (176) 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 +69 -17
  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 +12 -2
  40. package/dist/index.d.ts.map +1 -1
  41. package/dist/index.js +45 -22
  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 +202 -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 +172 -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 +59 -174
  71. package/dist/spa/src/adminforth.ts +42 -18
  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 +6 -6
  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 +127 -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 +315 -73
  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 +16 -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 +90 -23
  106. package/dist/spa/src/components/ResourceForm.vue +94 -51
  107. package/dist/spa/src/components/ResourceListTable.vue +115 -85
  108. package/dist/spa/src/components/ResourceListTableVirtual.vue +114 -80
  109. package/dist/spa/src/components/ShowTable.vue +21 -15
  110. package/dist/spa/src/components/Sidebar.vue +470 -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 +13 -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 +163 -23
  130. package/dist/spa/src/types/Common.ts +91 -32
  131. package/dist/spa/src/types/FrontendAPI.ts +31 -5
  132. package/dist/spa/src/types/adapters/CaptchaAdapter.ts +34 -0
  133. package/dist/spa/src/types/adapters/EmailAdapter.ts +2 -2
  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/index.ts +8 -0
  137. package/dist/spa/src/utils.ts +291 -11
  138. package/dist/spa/src/views/CreateView.vue +63 -21
  139. package/dist/spa/src/views/EditView.vue +55 -22
  140. package/dist/spa/src/views/ListView.vue +144 -87
  141. package/dist/spa/src/views/LoginView.vue +26 -35
  142. package/dist/spa/src/views/ResourceParent.vue +2 -2
  143. package/dist/spa/src/views/SettingsView.vue +121 -0
  144. package/dist/spa/src/views/ShowView.vue +83 -53
  145. package/dist/spa/src/websocket.ts +6 -1
  146. package/dist/spa/tsconfig.app.json +1 -1
  147. package/dist/spa/vite.config.ts +45 -2
  148. package/dist/types/Back.d.ts +146 -14
  149. package/dist/types/Back.d.ts.map +1 -1
  150. package/dist/types/Back.js +15 -0
  151. package/dist/types/Back.js.map +1 -1
  152. package/dist/types/Common.d.ts +106 -29
  153. package/dist/types/Common.d.ts.map +1 -1
  154. package/dist/types/Common.js.map +1 -1
  155. package/dist/types/FrontendAPI.d.ts +31 -3
  156. package/dist/types/FrontendAPI.d.ts.map +1 -1
  157. package/dist/types/FrontendAPI.js.map +1 -1
  158. package/dist/types/adapters/CaptchaAdapter.d.ts +30 -0
  159. package/dist/types/adapters/CaptchaAdapter.d.ts.map +1 -0
  160. package/dist/types/adapters/CaptchaAdapter.js +5 -0
  161. package/dist/types/adapters/CaptchaAdapter.js.map +1 -0
  162. package/dist/types/adapters/EmailAdapter.d.ts +1 -1
  163. package/dist/types/adapters/ImageVisionAdapter.d.ts +25 -0
  164. package/dist/types/adapters/ImageVisionAdapter.d.ts.map +1 -0
  165. package/dist/types/adapters/ImageVisionAdapter.js +2 -0
  166. package/dist/types/adapters/ImageVisionAdapter.js.map +1 -0
  167. package/dist/types/adapters/KeyValueAdapter.d.ts +10 -0
  168. package/dist/types/adapters/KeyValueAdapter.d.ts.map +1 -0
  169. package/dist/types/adapters/KeyValueAdapter.js +2 -0
  170. package/dist/types/adapters/KeyValueAdapter.js.map +1 -0
  171. package/dist/types/adapters/index.d.ts +9 -0
  172. package/dist/types/adapters/index.d.ts.map +1 -0
  173. package/dist/types/adapters/index.js +2 -0
  174. package/dist/types/adapters/index.js.map +1 -0
  175. package/package.json +4 -2
  176. package/dist/spa/src/types/adapters/index.js +0 -5
@@ -0,0 +1,470 @@
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" :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="{'my-4 ': isSidebarIconOnly && !isSidebarHovering, 'm-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 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(16.5rem - 0.75rem*2 - 0.875rem*2 - 1.25rem - 0.75rem)',
110
+ width: 'calc(16.5rem - 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: 16.5rem; /* 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
+ box-shadow: 12px 0px 18px -8px rgba(0, 0, 0, 0.15);
200
+ }
201
+
202
+ .sidebar-expanded {
203
+ width: 16.5rem; /* Expanded width (w-64) */
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
+ function handleBreakpointChange(e: MediaQueryListEvent) {
317
+ isMobile.value = !e.matches;
318
+ if (isMobile.value) {
319
+ isSidebarIconOnly.value = false;
320
+ } else {
321
+ if (props.forceIconOnly === true) {
322
+ isSidebarIconOnly.value = true;
323
+ } else if (iconOnlySidebarEnabled.value && localStorage.getItem('afIconOnlySidebar') === 'true') {
324
+ isSidebarIconOnly.value = true;
325
+ } else {
326
+ isSidebarIconOnly.value = false;
327
+ }
328
+ }
329
+ }
330
+
331
+ smQuery.addEventListener('change', handleBreakpointChange);
332
+
333
+
334
+ const isSidebarHovering = ref(false);
335
+ const isTogglingSidebar = ref(false);
336
+
337
+ function toggleSidebar() {
338
+ if (props.forceIconOnly) {
339
+ return;
340
+ }
341
+ if (!iconOnlySidebarEnabled.value) {
342
+ return;
343
+ }
344
+ if (isMobile.value) {
345
+ return;
346
+ }
347
+ isTogglingSidebar.value = true;
348
+ isSidebarIconOnly.value = !isSidebarIconOnly.value;
349
+ if (isSidebarIconOnly.value) {
350
+ isSidebarHovering.value = false;
351
+ }
352
+ setTimeout(() => {
353
+ isTogglingSidebar.value = false;
354
+ }, 100);
355
+ }
356
+
357
+ function clickOnMenuItem(label: string | number) {
358
+ if (opened.value.includes(label)) {
359
+ opened.value = opened.value.filter((item) => item !== label);
360
+ } else {
361
+ opened.value.push(label);
362
+ }
363
+ }
364
+
365
+ watch(()=>coreStore.menu, () => {
366
+ coreStore.menu.forEach((item, i) => {
367
+ if (item.open) {
368
+ opened.value.push(i);
369
+ };
370
+ });
371
+ })
372
+
373
+
374
+
375
+ watch(isSidebarIconOnly, (isIconOnly) => {
376
+ if (!isMobile.value && iconOnlySidebarEnabled.value && !props.forceIconOnly) {
377
+ localStorage.setItem('afIconOnlySidebar', isIconOnly.toString());
378
+ }
379
+ emit('sidebarStateChange', { isSidebarIconOnly: isIconOnly, isSidebarHovering: isSidebarHovering.value });
380
+ })
381
+
382
+ watch(isSidebarHovering, (hovering) => {
383
+ emit('sidebarStateChange', { isSidebarIconOnly: isSidebarIconOnly.value, isSidebarHovering: hovering });
384
+ })
385
+
386
+ watch(sidebarAside, (sidebarAside) => {
387
+ if (sidebarAside) {
388
+ coreStore.fetchMenuBadges();
389
+ }
390
+ })
391
+
392
+ const ctaBadge: Ref<(AnnouncementBadgeResponse & { hash: string; }) | null> = computed(() => {
393
+ const badge = coreStore.config?.announcementBadge;
394
+ if (!badge) {
395
+ return null;
396
+ }
397
+ const hash = badge.closable ? verySimpleHash(JSON.stringify(badge)) : '';
398
+ if (badge.closable && window.localStorage.getItem(`ctaBadge-${hash}`)) {
399
+ return null;
400
+ }
401
+ return {...badge, hash};
402
+ });
403
+
404
+ function closeCTA() {
405
+ if (!ctaBadge.value) {
406
+ return;
407
+ }
408
+ const hash = ctaBadge.value.hash;
409
+ window.localStorage.setItem(`ctaBadge-${hash}`, '1');
410
+ nextTick( async() => {
411
+ emit('loadMenu');
412
+ await coreStore.fetchMenuBadges();
413
+ adminforth.menu.refreshMenuBadges();
414
+ })
415
+ }
416
+
417
+ onMounted(() => {
418
+ if (!iconOnlySidebarEnabled.value) {
419
+ isSidebarIconOnly.value = false;
420
+ }
421
+
422
+ coreStore.menu.forEach((item, i) => {
423
+ if (item.open) {
424
+ opened.value.push(i);
425
+ };
426
+ });
427
+ // Emit initial state
428
+ emit('sidebarStateChange', { isSidebarIconOnly: isSidebarIconOnly.value, isSidebarHovering: isSidebarHovering.value });
429
+ })
430
+
431
+ onUnmounted(() => {
432
+ smQuery.removeEventListener('change', handleBreakpointChange);
433
+ if (isMobile.value && props.sideBarOpen) {
434
+ document.body.style.overflow = '';
435
+ document.body.style.position = '';
436
+ document.body.style.width = '';
437
+ }
438
+ })
439
+
440
+ watch(() => props.forceIconOnly, (force) => {
441
+ if (isMobile.value) {
442
+ isSidebarIconOnly.value = false;
443
+ return;
444
+ }
445
+ if (props.forceIconOnly === true) {
446
+ isSidebarIconOnly.value = true;
447
+ } else if (iconOnlySidebarEnabled.value && localStorage.getItem('afIconOnlySidebar') === 'true') {
448
+ isSidebarIconOnly.value = true;
449
+ } else {
450
+ isSidebarIconOnly.value = false;
451
+ }
452
+ }, { immediate: true })
453
+
454
+ watch(() => props.sideBarOpen, (isOpen) => {
455
+ if (isMobile.value) {
456
+ if (isOpen) {
457
+ // Lock body scroll
458
+ document.body.style.overflow = 'hidden';
459
+ document.body.style.position = 'fixed';
460
+ document.body.style.width = '100%';
461
+ } else {
462
+ // Unlock body scroll
463
+ document.body.style.overflow = '';
464
+ document.body.style.position = '';
465
+ document.body.style.width = '';
466
+ }
467
+ }
468
+ }, { immediate: true })
469
+
470
+ </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>
@@ -1,8 +1,8 @@
1
1
  <template >
2
- <template v-if="threeDotsDropdownItems?.length || customActions?.length">
2
+ <template v-if="threeDotsDropdownItems?.length || customActions?.length || (bulkActions?.some((action: AdminForthBulkActionCommon) => action.showInThreeDotsDropdown))">
3
3
  <button
4
4
  data-dropdown-toggle="listThreeDotsDropdown"
5
- class="flex items-center py-2 px-2 text-sm font-medium text-gray-900 focus:outline-none bg-white rounded border border-gray-300 hover:bg-gray-100 hover:text-lightPrimary focus:z-10 focus:ring-4 focus:ring-gray-100 dark:focus:ring-gray-700 dark:bg-gray-800 dark:text-gray-400 dark:border-gray-600 dark:hover:text-white dark:hover:bg-gray-700 rounded-default"
5
+ class="flex items-center py-2 px-2 text-sm font-medium text-lightThreeDotsMenuIconDots focus:outline-none bg-lightThreeDotsMenuIconBackground rounded border border-lightThreeDotsMenuIconBackgroundBorder hover:bg-lightThreeDotsMenuIconBackgroundHover hover:text-lightThreeDotsMenuIconDotsHover focus:z-10 focus:ring-4 focus:ring-lightThreeDotsMenuIconFocus dark:focus:ring-darkThreeDotsMenuIconFocus dark:bg-darkThreeDotsMenuIconBackground dark:text-darkThreeDotsMenuIconDots dark:border-darkThreeDotsMenuIconBackgroundBorder dark:hover:text-darkThreeDotsMenuIconDotsHover dark:hover:bg-darkThreeDotsMenuIconBackgroundHover rounded-default"
6
6
  >
7
7
  <svg class="w-3 h-3" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="currentColor" viewBox="0 0 4 15">
8
8
  <path d="M3.5 1.5a1.5 1.5 0 1 1-3 0 1.5 1.5 0 0 1 3 0Zm0 6.041a1.5 1.5 0 1 1-3 0 1.5 1.5 0 0 1 3 0Zm0 5.959a1.5 1.5 0 1 1-3 0 1.5 1.5 0 0 1 3 0Z"/>
@@ -12,26 +12,60 @@
12
12
  <!-- Dropdown menu -->
13
13
  <div
14
14
  id="listThreeDotsDropdown"
15
- class="z-20 hidden bg-white divide-y divide-gray-100 rounded-lg shadow w-44 dark:bg-gray-700 dark:divide-gray-600">
16
- <ul class="py-2 text-sm text-gray-700 dark:text-gray-200" aria-labelledby="dropdownMenuIconButton">
17
- <li v-for="item in threeDotsDropdownItems" :key="`dropdown-item-${item.label}`">
18
- <a href="#" class="block px-4 py-2 hover:bg-gray-100 dark:hover:bg-gray-600 dark:hover:text-white">
19
- <component :is="getCustomComponent(item)"
15
+ class="z-30 hidden bg-lightThreeDotsMenuBodyBackground divide-y divide-gray-100 rounded-lg shadow w-44 dark:bg-darkThreeDotsMenuBodyBackground dark:divide-gray-600">
16
+ <ul class="py-2 text-sm text-lightThreeDotsMenuBodyText dark:text-darkThreeDotsMenuBodyText" aria-labelledby="dropdownMenuIconButton">
17
+ <li v-for="(item, i) in threeDotsDropdownItems" :key="`dropdown-item-${i}`">
18
+ <a href="#"
19
+ class="block px-4 py-2 hover:bg-lightThreeDotsMenuBodyBackgroundHover hover:text-lightThreeDotsMenuBodyTextHover dark:hover:bg-darkThreeDotsMenuBodyBackgroundHover dark:hover:text-darkThreeDotsMenuBodyTextHover"
20
+ :class="{
21
+ 'pointer-events-none': checkboxes && checkboxes.length === 0 && item.meta?.disabledWhenNoCheckboxes,
22
+ 'opacity-50': checkboxes && checkboxes.length === 0 && item.meta?.disabledWhenNoCheckboxes,
23
+ 'cursor-not-allowed': checkboxes && checkboxes.length === 0 && item.meta?.disabledWhenNoCheckboxes,
24
+ }"
25
+ @click="injectedComponentClick(i)">
26
+ <component :ref="(el: any) => setComponentRef(el, i)" :is="getCustomComponent(item)"
20
27
  :meta="item.meta"
21
28
  :resource="coreStore.resource"
22
29
  :adminUser="coreStore.adminUser"
30
+ :checkboxes="checkboxes"
31
+ :updateList="props.updateList"
32
+ :clearCheckboxes="clearCheckboxes"
23
33
  />
24
34
  </a>
25
35
  </li>
26
36
  <li v-for="action in customActions" :key="action.id">
27
- <a href="#" @click.prevent="handleActionClick(action)" class="block px-4 py-2 hover:bg-gray-100 dark:hover:bg-gray-600 dark:hover:text-white">
37
+ <component
38
+ :is="(action.customComponent && getCustomComponent(action.customComponent)) || CallActionWrapper"
39
+ :meta="action.customComponent?.meta"
40
+ @callAction="(payload? : Object) => handleActionClick(action, payload)"
41
+ >
42
+ <a href="#" @click.prevent class="block px-4 py-2 hover:text-lightThreeDotsMenuBodyTextHover hover:bg-lightThreeDotsMenuBodyBackgroundHover dark:hover:bg-darkThreeDotsMenuBodyBackgroundHover dark:hover:text-darkThreeDotsMenuBodyTextHover">
43
+ <div class="flex items-center gap-2">
44
+ <component
45
+ v-if="action.icon"
46
+ :is="getIcon(action.icon)"
47
+ class="w-4 h-4 text-lightPrimary dark:text-darkPrimary"
48
+ />
49
+ {{ action.name }}
50
+ </div>
51
+ </a>
52
+ </component>
53
+ </li>
54
+ <li v-for="action in (bulkActions ?? []).filter(a => a.showInThreeDotsDropdown)" :key="action.id">
55
+ <a href="#" @click.prevent="startBulkAction(action.id)"
56
+ class="block px-4 py-2 hover:text-lightThreeDotsMenuBodyTextHover hover:bg-lightThreeDotsMenuBodyBackgroundHover dark:hover:bg-darkThreeDotsMenuBodyBackgroundHover dark:hover:text-darkThreeDotsMenuBodyTextHover"
57
+ :class="{
58
+ 'pointer-events-none': checkboxes && checkboxes.length === 0,
59
+ 'opacity-50': checkboxes && checkboxes.length === 0,
60
+ 'cursor-not-allowed': checkboxes && checkboxes.length === 0
61
+ }">
28
62
  <div class="flex items-center gap-2">
29
63
  <component
30
64
  v-if="action.icon"
31
65
  :is="getIcon(action.icon)"
32
66
  class="w-4 h-4 text-lightPrimary dark:text-darkPrimary"
33
67
  />
34
- {{ action.name }}
68
+ {{ action.label }}
35
69
  </div>
36
70
  </a>
37
71
  </li>
@@ -47,17 +81,39 @@ import { useCoreStore } from '@/stores/core';
47
81
  import adminforth from '@/adminforth';
48
82
  import { callAdminForthApi } from '@/utils';
49
83
  import { useRoute, useRouter } from 'vue-router';
84
+ import CallActionWrapper from '@/components/CallActionWrapper.vue'
85
+ import { ref, type ComponentPublicInstance } from 'vue';
86
+ import type { AdminForthBulkActionCommon, AdminForthComponentDeclarationFull } from '@/types/Common';
87
+ import type { AdminForthActionInput } from '@/types/Back';
88
+
50
89
 
51
90
  const route = useRoute();
52
91
  const coreStore = useCoreStore();
53
92
  const router = useRouter();
93
+ const threeDotsDropdownItemsRefs = ref<Array<ComponentPublicInstance | null>>([]);
54
94
 
55
95
  const props = defineProps({
56
- threeDotsDropdownItems: Array,
57
- customActions: Array
96
+ threeDotsDropdownItems: Array<AdminForthComponentDeclarationFull>,
97
+ customActions: Array<AdminForthActionInput>,
98
+ bulkActions: Array<AdminForthBulkActionCommon>,
99
+ checkboxes: Array,
100
+ updateList: {
101
+ type: Function,
102
+ },
103
+ clearCheckboxes: {
104
+ type: Function
105
+ }
58
106
  });
59
107
 
60
- async function handleActionClick(action) {
108
+ const emit = defineEmits(['startBulkAction']);
109
+
110
+ function setComponentRef(el: ComponentPublicInstance | null, index: number) {
111
+ if (el) {
112
+ threeDotsDropdownItemsRefs.value[index] = el;
113
+ }
114
+ }
115
+
116
+ async function handleActionClick(action: AdminForthActionInput, payload: any) {
61
117
  adminforth.list.closeThreeDotsDropdown();
62
118
 
63
119
  const actionId = action.id;
@@ -67,7 +123,8 @@ async function handleActionClick(action) {
67
123
  body: {
68
124
  resourceId: route.params.resourceId,
69
125
  actionId: actionId,
70
- recordId: route.params.primaryKey
126
+ recordId: route.params.primaryKey,
127
+ extra: payload || {},
71
128
  }
72
129
  });
73
130
 
@@ -88,8 +145,8 @@ async function handleActionClick(action) {
88
145
 
89
146
  if (data?.ok) {
90
147
  await coreStore.fetchRecord({
91
- resourceId: route.params.resourceId,
92
- primaryKey: route.params.primaryKey,
148
+ resourceId: route.params.resourceId as string,
149
+ primaryKey: route.params.primaryKey as string,
93
150
  source: 'show',
94
151
  });
95
152
 
@@ -108,4 +165,16 @@ async function handleActionClick(action) {
108
165
  });
109
166
  }
110
167
  }
168
+
169
+ function startBulkAction(actionId: string) {
170
+ adminforth.list.closeThreeDotsDropdown();
171
+ emit('startBulkAction', actionId);
172
+ }
173
+
174
+ async function injectedComponentClick(index: number) {
175
+ const componentRef = threeDotsDropdownItemsRefs.value[index];
176
+ if (componentRef && 'click' in componentRef) {
177
+ (componentRef as any).click?.();
178
+ }
179
+ }
111
180
  </script>