adminforth 3.35.1 → 4.0.1
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/dist/modules/codeInjector.d.ts.map +1 -1
- package/dist/modules/codeInjector.js +20 -3
- package/dist/modules/codeInjector.js.map +1 -1
- package/dist/modules/styleGenerator.d.ts +13 -0
- package/dist/modules/styleGenerator.d.ts.map +1 -1
- package/dist/modules/styleGenerator.js +274 -1
- package/dist/modules/styleGenerator.js.map +1 -1
- package/dist/modules/styles.d.ts +24 -0
- package/dist/modules/styles.d.ts.map +1 -1
- package/dist/modules/styles.js +452 -427
- package/dist/modules/styles.js.map +1 -1
- package/dist/modules/utils.d.ts +1 -13
- package/dist/modules/utils.d.ts.map +1 -1
- package/dist/modules/utils.js +2 -275
- package/dist/modules/utils.js.map +1 -1
- package/dist/spa/index.html +2 -2
- package/dist/spa/package-lock.json +1373 -1136
- package/dist/spa/package.json +18 -15
- package/dist/spa/pnpm-lock.yaml +1067 -648
- package/dist/spa/src/App.vue +84 -92
- package/dist/spa/src/afcl/AreaChart.vue +8 -24
- package/dist/spa/src/afcl/BarChart.vue +8 -23
- package/dist/spa/src/afcl/Button.vue +8 -2
- package/dist/spa/src/afcl/ButtonGroup.vue +9 -9
- package/dist/spa/src/afcl/Card.vue +40 -19
- package/dist/spa/src/afcl/Checkbox.vue +6 -6
- package/dist/spa/src/afcl/CountryFlag.vue +17 -11
- package/dist/spa/src/afcl/DatePicker.vue +7 -7
- package/dist/spa/src/afcl/Dialog.vue +8 -9
- package/dist/spa/src/afcl/DropdownMenu.vue +210 -0
- package/dist/spa/src/afcl/DropdownMenuAccordion.vue +56 -0
- package/dist/spa/src/afcl/DropdownMenuItem.vue +86 -0
- package/dist/spa/src/afcl/Dropzone.vue +10 -26
- package/dist/spa/src/afcl/Input.vue +4 -4
- package/dist/spa/src/afcl/Link.vue +1 -1
- package/dist/spa/src/afcl/LinkButton.vue +15 -1
- package/dist/spa/src/afcl/MixedChart.vue +4 -18
- package/dist/spa/src/afcl/Modal.vue +9 -9
- package/dist/spa/src/afcl/PieChart.vue +8 -29
- package/dist/spa/src/afcl/ProgressBar.vue +2 -2
- package/dist/spa/src/afcl/Select.vue +20 -20
- package/dist/spa/src/afcl/Skeleton.vue +1 -1
- package/dist/spa/src/afcl/Spinner.vue +1 -1
- package/dist/spa/src/afcl/Table.vue +19 -26
- package/dist/spa/src/afcl/Textarea.vue +1 -1
- package/dist/spa/src/afcl/Toggle.vue +4 -4
- package/dist/spa/src/afcl/Tooltip.vue +1 -1
- package/dist/spa/src/afcl/TreeMapChart.vue +5 -20
- package/dist/spa/src/afcl/VerticalTabs.vue +4 -4
- package/dist/spa/src/afcl/buttonStyles.ts +73 -33
- package/dist/spa/src/afcl/chartUtils.ts +19 -0
- package/dist/spa/src/afcl/classMerge.ts +11 -0
- package/dist/spa/src/afcl/dropdownMenuContext.ts +10 -0
- package/dist/spa/src/afcl/fileSize.ts +12 -0
- package/dist/spa/src/afcl/index.ts +3 -0
- package/dist/spa/src/components/AcceptModal.vue +17 -19
- package/dist/spa/src/components/Breadcrumbs.vue +11 -13
- package/dist/spa/src/components/BreadcrumbsWithButtons.vue +2 -2
- package/dist/spa/src/components/CallActionWrapper.vue +3 -0
- package/dist/spa/src/components/ColumnValueInput.vue +5 -5
- package/dist/spa/src/components/ColumnValueInputWrapper.vue +7 -6
- package/dist/spa/src/components/CustomDateRangePicker.vue +8 -8
- package/dist/spa/src/components/CustomRangePicker.vue +2 -2
- package/dist/spa/src/components/ErrorMessage.vue +3 -4
- package/dist/spa/src/components/Filters.vue +13 -13
- package/dist/spa/src/components/GroupsTable.vue +12 -12
- package/dist/spa/src/components/ListActionsThreeDots.vue +89 -207
- package/dist/spa/src/components/MenuLink.vue +23 -20
- package/dist/spa/src/components/RangePicker.vue +9 -10
- package/dist/spa/src/components/ResourceListTable.vue +68 -65
- package/dist/spa/src/components/ShowTable.vue +8 -8
- package/dist/spa/src/components/Sidebar.vue +31 -38
- package/dist/spa/src/components/ThreeDotsMenu.vue +88 -136
- package/dist/spa/src/components/Toast.vue +16 -23
- package/dist/spa/src/components/UserMenuSettingsButton.vue +15 -36
- package/dist/spa/src/components/ValueRenderer.vue +13 -8
- package/dist/spa/src/components/icons/IconCalendar.vue +3 -3
- package/dist/spa/src/components/icons/IconTime.vue +3 -3
- package/dist/spa/src/index.scss +6 -2
- package/dist/spa/src/main.ts +2 -0
- package/dist/spa/src/renderers/CompactField.vue +3 -3
- package/dist/spa/src/renderers/CompactUUID.vue +3 -3
- package/dist/spa/src/renderers/SensitiveBlurCell.vue +9 -9
- package/dist/spa/src/utils/threeDotsMenu.ts +7 -0
- package/dist/spa/src/utils/userMenu.ts +5 -0
- package/dist/spa/src/utils/utils.ts +12 -39
- package/dist/spa/src/views/CreateEditSkeleton.vue +3 -3
- package/dist/spa/src/views/CreateView.vue +5 -6
- package/dist/spa/src/views/EditView.vue +5 -6
- package/dist/spa/src/views/ListView.vue +114 -115
- package/dist/spa/src/views/LoginView.vue +4 -4
- package/dist/spa/src/views/ShowView.vue +17 -18
- package/dist/spa/vite.config.ts +23 -16
- package/package.json +1 -2
|
@@ -1,99 +1,82 @@
|
|
|
1
|
-
<template
|
|
2
|
-
<
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
1
|
+
<template>
|
|
2
|
+
<DropdownMenu
|
|
3
|
+
v-if="threeDotsDropdownItems?.length || customActions?.length || (bulkActions?.some((action: AdminForthBulkActionFront) => action.showInThreeDotsDropdown))"
|
|
4
|
+
v-model:open="showDropdown"
|
|
5
|
+
placement="bottom-start"
|
|
6
|
+
:closeOnSelect="false"
|
|
7
|
+
keepMounted
|
|
8
|
+
:contentClass="['max-w-64', THREE_DOTS_MENU_CONTENT_CLASSES]"
|
|
9
|
+
>
|
|
10
|
+
<template #trigger>
|
|
11
|
+
<Button
|
|
12
|
+
iconOnly
|
|
13
|
+
class="text-lightThreeDotsMenuIconDots hover:text-lightThreeDotsMenuIconDotsHover dark:text-darkThreeDotsMenuIconDots dark:hover:text-darkThreeDotsMenuIconDotsHover"
|
|
14
|
+
variant="secondary"
|
|
15
|
+
>
|
|
16
|
+
<IconEllipsis aria-hidden="true" />
|
|
17
|
+
</Button>
|
|
18
|
+
</template>
|
|
19
|
+
|
|
20
|
+
<DropdownMenuItem
|
|
21
|
+
v-for="(item, i) in threeDotsDropdownItems"
|
|
22
|
+
:key="`dropdown-item-${i}`"
|
|
23
|
+
:class="THREE_DOTS_MENU_ITEM_CLASSES"
|
|
24
|
+
:disabled="!!(checkboxes && checkboxes.length === 0 && item.meta?.disabledWhenNoCheckboxes)"
|
|
25
|
+
@click="injectedComponentClick(i, $event)"
|
|
8
26
|
>
|
|
9
|
-
<
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
v-if="action.icon && !actionLoadingStates[action.id!]"
|
|
63
|
-
:is="getIcon(action.icon)"
|
|
64
|
-
class="w-4 h-4 text-lightPrimary dark:text-darkPrimary dark:brightness-200"
|
|
65
|
-
/>
|
|
66
|
-
<Spinner
|
|
67
|
-
v-if="actionLoadingStates[action.id!]"
|
|
68
|
-
class="w-5 h-5 text-gray-200 dark:text-gray-500 fill-gray-500 dark:fill-gray-300"
|
|
69
|
-
/>
|
|
70
|
-
{{ action.name }}
|
|
71
|
-
</div>
|
|
72
|
-
</a>
|
|
73
|
-
</component>
|
|
74
|
-
</div>
|
|
75
|
-
</li>
|
|
76
|
-
<li v-for="action in (bulkActions ?? []).filter(a => a.showInThreeDotsDropdown)" :key="action.id">
|
|
77
|
-
<a @click.prevent="startBulkAction(action.id)"
|
|
78
|
-
class="block px-4 py-2 hover:text-lightThreeDotsMenuBodyTextHover hover:bg-lightThreeDotsMenuBodyBackgroundHover dark:hover:bg-darkThreeDotsMenuBodyBackgroundHover dark:hover:text-darkThreeDotsMenuBodyTextHover"
|
|
79
|
-
:class="{
|
|
80
|
-
'pointer-events-none': checkboxes && checkboxes.length === 0,
|
|
81
|
-
'opacity-50': checkboxes && checkboxes.length === 0,
|
|
82
|
-
'cursor-not-allowed': checkboxes && checkboxes.length === 0
|
|
83
|
-
}">
|
|
84
|
-
<div class="flex items-center gap-2">
|
|
85
|
-
<component
|
|
86
|
-
v-if="action.icon"
|
|
87
|
-
:is="getIcon(action.icon)"
|
|
88
|
-
class="w-4 h-4 text-lightPrimary dark:text-darkPrimary dark:brightness-200"
|
|
89
|
-
/>
|
|
90
|
-
{{ action.label }}
|
|
91
|
-
</div>
|
|
92
|
-
</a>
|
|
93
|
-
</li>
|
|
94
|
-
</ul>
|
|
95
|
-
</div>
|
|
96
|
-
</div>
|
|
27
|
+
<component
|
|
28
|
+
v-if="getCustomComponent(item)"
|
|
29
|
+
:ref="(el: any) => setComponentRef(el, i)" :is="getCustomComponent(item)!"
|
|
30
|
+
:meta="item.meta"
|
|
31
|
+
:resource="coreStore.resource"
|
|
32
|
+
:adminUser="coreStore.adminUser"
|
|
33
|
+
:checkboxes="checkboxes"
|
|
34
|
+
:updateList="props.updateList"
|
|
35
|
+
:clearCheckboxes="clearCheckboxes"
|
|
36
|
+
/>
|
|
37
|
+
</DropdownMenuItem>
|
|
38
|
+
|
|
39
|
+
<DropdownMenuItem
|
|
40
|
+
v-for="(action, i) in customActions"
|
|
41
|
+
:key="action.id"
|
|
42
|
+
:class="THREE_DOTS_MENU_ITEM_CLASSES"
|
|
43
|
+
@click="injectedComponentClick(threeDotsDropdownItems ? threeDotsDropdownItems.length + i : i, $event)"
|
|
44
|
+
>
|
|
45
|
+
<component
|
|
46
|
+
:ref="(el: any) => setComponentRef(el, threeDotsDropdownItems ? threeDotsDropdownItems.length + i : i)"
|
|
47
|
+
:is="(action.customComponent && getCustomComponent(formatComponent(action.customComponent))) || CallActionWrapper"
|
|
48
|
+
:meta="formatComponent(action.customComponent).meta"
|
|
49
|
+
@callAction="(payload? : Object) => handleActionClick(action, payload)"
|
|
50
|
+
>
|
|
51
|
+
<!-- preventDefault marks the click as handled by the action component -->
|
|
52
|
+
<span @click.prevent class="flex flex-1 items-center gap-2">
|
|
53
|
+
<component
|
|
54
|
+
v-if="action.icon && !actionLoadingStates[action.id!]"
|
|
55
|
+
:is="getIcon(action.icon)"
|
|
56
|
+
class="w-4 h-4 text-lightInputIconColor dark:text-darkInputIconColor"
|
|
57
|
+
/>
|
|
58
|
+
<Spinner
|
|
59
|
+
v-if="actionLoadingStates[action.id!]"
|
|
60
|
+
class="w-4 h-4 text-lightSkeletonBackgroundColor dark:text-darkSkeletonBackgroundColor fill-lightListTableText dark:fill-darkListTableText"
|
|
61
|
+
/>
|
|
62
|
+
{{ action.name }}
|
|
63
|
+
</span>
|
|
64
|
+
</component>
|
|
65
|
+
</DropdownMenuItem>
|
|
66
|
+
|
|
67
|
+
<DropdownMenuItem
|
|
68
|
+
v-for="action in (bulkActions ?? []).filter(a => a.showInThreeDotsDropdown)"
|
|
69
|
+
:key="action.id"
|
|
70
|
+
:class="THREE_DOTS_MENU_ITEM_CLASSES"
|
|
71
|
+
:disabled="!!(checkboxes && checkboxes.length === 0)"
|
|
72
|
+
@click="startBulkAction(action.id!)"
|
|
73
|
+
>
|
|
74
|
+
<template v-if="action.icon" #icon>
|
|
75
|
+
<component :is="getIcon(action.icon)" />
|
|
76
|
+
</template>
|
|
77
|
+
{{ action.label }}
|
|
78
|
+
</DropdownMenuItem>
|
|
79
|
+
</DropdownMenu>
|
|
97
80
|
</template>
|
|
98
81
|
|
|
99
82
|
|
|
@@ -103,9 +86,11 @@ import { useCoreStore } from '@/stores/core';
|
|
|
103
86
|
import { useAdminforth } from '@/adminforth';
|
|
104
87
|
import { useRoute, useRouter } from 'vue-router';
|
|
105
88
|
import CallActionWrapper from '@/components/CallActionWrapper.vue'
|
|
106
|
-
import { ref,
|
|
89
|
+
import { ref, type ComponentPublicInstance, onMounted } from 'vue';
|
|
107
90
|
import type { AdminForthActionFront, AdminForthBulkActionFront, AdminForthComponentDeclarationFull } from '@/types/Common';
|
|
108
|
-
import { Spinner, Button } from '@/afcl';
|
|
91
|
+
import { Spinner, Button, DropdownMenu, DropdownMenuItem } from '@/afcl';
|
|
92
|
+
import { IconEllipsis } from '@iconify-prerendered/vue-lucide';
|
|
93
|
+
import { THREE_DOTS_MENU_CONTENT_CLASSES, THREE_DOTS_MENU_ITEM_CLASSES } from '@/utils/threeDotsMenu';
|
|
109
94
|
|
|
110
95
|
const { list, alert} = useAdminforth();
|
|
111
96
|
const route = useRoute();
|
|
@@ -113,10 +98,7 @@ const coreStore = useCoreStore();
|
|
|
113
98
|
const router = useRouter();
|
|
114
99
|
const threeDotsDropdownItemsRefs = ref<Array<ComponentPublicInstance | null>>([]);
|
|
115
100
|
const showDropdown = ref(false);
|
|
116
|
-
const openToRight = ref(true);
|
|
117
101
|
const actionLoadingStates = ref<Record<string, boolean>>({});
|
|
118
|
-
const dropdownRef = ref<HTMLElement | null>(null);
|
|
119
|
-
const buttonTriggerRef = ref<ComponentPublicInstance | null>(null);
|
|
120
102
|
|
|
121
103
|
const props = defineProps({
|
|
122
104
|
threeDotsDropdownItems: Array<AdminForthComponentDeclarationFull>,
|
|
@@ -178,8 +160,8 @@ function startBulkAction(actionId: string) {
|
|
|
178
160
|
showDropdown.value = false;
|
|
179
161
|
}
|
|
180
162
|
|
|
181
|
-
async function injectedComponentClick(index: number, event
|
|
182
|
-
if (event
|
|
163
|
+
async function injectedComponentClick(index: number, event: MouseEvent) {
|
|
164
|
+
if (event.defaultPrevented) {
|
|
183
165
|
showDropdown.value = false;
|
|
184
166
|
return;
|
|
185
167
|
}
|
|
@@ -190,42 +172,12 @@ async function injectedComponentClick(index: number, event?: MouseEvent) {
|
|
|
190
172
|
showDropdown.value = false;
|
|
191
173
|
}
|
|
192
174
|
|
|
193
|
-
async function toggleDropdownVisibility() {
|
|
194
|
-
showDropdown.value = !showDropdown.value;
|
|
195
|
-
if (!showDropdown.value) return;
|
|
196
|
-
await nextTick();
|
|
197
|
-
const triggerRect = (buttonTriggerRef.value!.$el as HTMLElement).getBoundingClientRect();
|
|
198
|
-
const appEl = document.getElementById('app')!;
|
|
199
|
-
const appContentRight = appEl.getBoundingClientRect().right - parseFloat(getComputedStyle(appEl).paddingRight);
|
|
200
|
-
openToRight.value = triggerRect.left + dropdownRef.value!.offsetWidth <= appContentRight;
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
function handleClickOutside(e: MouseEvent) {
|
|
204
|
-
if (!dropdownRef.value) return
|
|
205
|
-
|
|
206
|
-
const triggerEl = buttonTriggerRef.value?.$el as HTMLElement | undefined;
|
|
207
|
-
|
|
208
|
-
if (!dropdownRef.value.contains(e.target as Node) && !triggerEl?.contains(e.target as Node)) {
|
|
209
|
-
showDropdown.value = false;
|
|
210
|
-
}
|
|
211
|
-
}
|
|
212
|
-
|
|
213
175
|
onMounted(() => {
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
document.removeEventListener('mousedown', handleClickOutside)
|
|
219
|
-
})
|
|
176
|
+
list.closeThreeDotsDropdown = () => {
|
|
177
|
+
showDropdown.value = false;
|
|
178
|
+
};
|
|
179
|
+
});
|
|
220
180
|
|
|
221
181
|
</script>
|
|
222
182
|
|
|
223
|
-
<style lang="scss" scoped>
|
|
224
|
-
.wrapper {
|
|
225
|
-
@apply px-4 py-2
|
|
226
|
-
hover:text-lightThreeDotsMenuBodyTextHover hover:bg-lightThreeDotsMenuBodyBackgroundHover
|
|
227
|
-
dark:hover:bg-darkThreeDotsMenuBodyBackgroundHover dark:hover:text-darkThreeDotsMenuBodyTextHover
|
|
228
|
-
cursor-pointer;
|
|
229
|
-
}
|
|
230
|
-
</style>
|
|
231
183
|
|
|
@@ -1,15 +1,14 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
|
|
3
3
|
|
|
4
|
-
<div class="afcl-toast flex items-center w-full p-4 rounded-lg shadow-
|
|
5
|
-
:class="variantConfig.borderClass"
|
|
4
|
+
<div class="afcl-toast flex items-center w-full p-3 pe-4 rounded-lg shadow-popover dark:shadow-darkPopover dark:text-darkToastText dark:bg-darkToastBackground bg-lightToastBackground text-lightToastText"
|
|
6
5
|
role="alert"
|
|
7
6
|
@mouseenter="pauseTimer"
|
|
8
7
|
@mouseleave="resumeTimer"
|
|
9
8
|
>
|
|
10
9
|
<div class="relative w-8 h-8 flex items-center justify-center">
|
|
11
10
|
<div class="absolute af-toast-icon inline-flex items-center justify-center flex-shrink-0 rounded-lg w-full h-full" :class="variantConfig.iconClass">
|
|
12
|
-
<component :is="variantConfig.icon" class="w-
|
|
11
|
+
<component :is="variantConfig.icon" class="w-4 h-4" aria-hidden="true" />
|
|
13
12
|
</div>
|
|
14
13
|
<svg v-if="hasTimer" class="absolute inset-0 w-full h-full rotate-180">
|
|
15
14
|
<rect
|
|
@@ -40,10 +39,8 @@
|
|
|
40
39
|
</div>
|
|
41
40
|
</div>
|
|
42
41
|
</div>
|
|
43
|
-
<button @click="closeToast" type="button" class="ms-auto -mx-1.5 -my-1.5 bg-lightToastCloseIconBackground text-lightToastCloseIcon hover:text-lightToastCloseIconHover rounded-
|
|
44
|
-
<
|
|
45
|
-
<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"/>
|
|
46
|
-
</svg>
|
|
42
|
+
<button @click="closeToast" type="button" class="ms-auto -mx-1.5 -my-1.5 bg-lightToastCloseIconBackground text-lightToastCloseIcon hover:text-lightToastCloseIconHover rounded-default focus:outline-none focus-visible:ring-[3px] focus-visible:ring-lightToastCloseIconFocusRing p-1.5 hover:bg-lightToastCloseIconBackgroundHover inline-flex items-center justify-center h-8 w-8 dark:text-darkToastCloseIcon dark:hover:text-darkToastCloseIconHover dark:bg-darkToastCloseIconBackground dark:hover:bg-darkToastCloseIconBackgroundHover dark:focus-visible:ring-darkToastCloseIconFocusRing" >
|
|
43
|
+
<IconX class="w-3.5 h-3.5" />
|
|
47
44
|
</button>
|
|
48
45
|
</div>
|
|
49
46
|
|
|
@@ -53,7 +50,7 @@
|
|
|
53
50
|
<script setup lang="ts">
|
|
54
51
|
import { onMounted, onUnmounted, computed, ref } from 'vue';
|
|
55
52
|
import { useToastStore } from '@/stores/toast';
|
|
56
|
-
import {
|
|
53
|
+
import { IconInfo, IconCircleX, IconTriangleAlert, IconCircleCheck, IconX } from '@iconify-prerendered/vue-lucide';
|
|
57
54
|
|
|
58
55
|
const toastStore = useToastStore();
|
|
59
56
|
const emit = defineEmits(['close']);
|
|
@@ -70,28 +67,24 @@ const props = defineProps<{
|
|
|
70
67
|
|
|
71
68
|
const VARIANT_CONFIGS = {
|
|
72
69
|
info: {
|
|
73
|
-
icon:
|
|
74
|
-
|
|
75
|
-
iconClass: 'text-lightPrimary dark:text-darkPrimary bg-lightPrimaryOpacity dark:bg-darkPrimary/50 dark:!text-blue-100',
|
|
70
|
+
icon: IconInfo,
|
|
71
|
+
iconClass: 'text-lightPrimary bg-lightPrimaryOpacity dark:text-darkPrimary dark:bg-darkPrimaryOpacity',
|
|
76
72
|
strokeClass: 'stroke-lightPrimary/50 dark:stroke-darkPrimary/30',
|
|
77
73
|
},
|
|
78
74
|
danger: {
|
|
79
|
-
icon:
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
strokeClass: 'stroke-red-500 dark:stroke-red-700',
|
|
75
|
+
icon: IconCircleX,
|
|
76
|
+
iconClass: 'text-lightDanger bg-lightDangerSoft dark:text-darkDanger dark:bg-darkDangerSoft',
|
|
77
|
+
strokeClass: 'stroke-lightDanger/50 dark:stroke-darkDanger/30',
|
|
83
78
|
},
|
|
84
79
|
warning: {
|
|
85
|
-
icon:
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
strokeClass: 'stroke-orange-500 dark:stroke-orange-600',
|
|
80
|
+
icon: IconTriangleAlert,
|
|
81
|
+
iconClass: 'text-lightWarning bg-lightWarningSoft dark:text-darkWarning dark:bg-darkWarningSoft',
|
|
82
|
+
strokeClass: 'stroke-lightWarning/50 dark:stroke-darkWarning/30',
|
|
89
83
|
},
|
|
90
84
|
success: {
|
|
91
|
-
icon:
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
strokeClass: 'stroke-green-500 dark:stroke-green-600',
|
|
85
|
+
icon: IconCircleCheck,
|
|
86
|
+
iconClass: 'text-lightSuccess bg-lightSuccessSoft dark:text-darkSuccess dark:bg-darkSuccessSoft',
|
|
87
|
+
strokeClass: 'stroke-lightSuccess/50 dark:stroke-darkSuccess/30',
|
|
95
88
|
},
|
|
96
89
|
} as const;
|
|
97
90
|
|
|
@@ -1,43 +1,23 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<
|
|
3
|
-
<
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
@click="showDropdown = !showDropdown"
|
|
2
|
+
<DropdownMenuAccordion v-if="options && options.length" :label="$t('Settings')" :triggerClass="USER_MENU_ITEM_CLASSES">
|
|
3
|
+
<DropdownMenuItem
|
|
4
|
+
v-for="option in options"
|
|
5
|
+
:key="option.slug ?? option.pageLabel"
|
|
6
|
+
:to="getRoute(option)"
|
|
7
|
+
:class="USER_MENU_ITEM_CLASSES"
|
|
9
8
|
>
|
|
10
|
-
|
|
11
|
-
<
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
</
|
|
15
|
-
|
|
16
|
-
<!-- collapsed submenu stays in layout with zero height, so popup width already fits its widest item and does not jump on expand -->
|
|
17
|
-
<div :class="{ 'h-0 overflow-hidden invisible': !showDropdown }">
|
|
18
|
-
|
|
19
|
-
<router-link class="cursor-pointer flex items-center gap-1 block px-4 py-1 text-sm
|
|
20
|
-
bg-lightUserMenuItemBackground hover:bg-lightUserMenuItemBackgroundHover text-lightUserMenuItemText
|
|
21
|
-
hover:text-lightUserMenuItemText dark:bg-darkUserMenuItemBackground dark:hover:bg-darkUserMenuItemBackgroundHover
|
|
22
|
-
dark:text-darkUserMenuItemText dark:hover:darkUserMenuItemTextHover
|
|
23
|
-
w-full text-select-none pl-5 select-none"
|
|
24
|
-
v-for="option in options"
|
|
25
|
-
:to="getRoute(option)"
|
|
26
|
-
>
|
|
27
|
-
<span class="mr-1">
|
|
28
|
-
<component v-if="option.icon" :is="getIcon(option.icon)" class="w-5 h-5 transition duration-75" ></component>
|
|
29
|
-
</span>
|
|
30
|
-
<span>{{ option.pageLabel }}</span>
|
|
31
|
-
</router-link>
|
|
32
|
-
</div>
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
</div>
|
|
9
|
+
<template v-if="option.icon" #icon>
|
|
10
|
+
<component :is="getIcon(option.icon)" />
|
|
11
|
+
</template>
|
|
12
|
+
{{ option.pageLabel }}
|
|
13
|
+
</DropdownMenuItem>
|
|
14
|
+
</DropdownMenuAccordion>
|
|
36
15
|
</template>
|
|
37
16
|
|
|
38
17
|
<script setup lang="ts">
|
|
39
|
-
import {
|
|
40
|
-
import {
|
|
18
|
+
import { computed } from 'vue';
|
|
19
|
+
import { DropdownMenuAccordion, DropdownMenuItem } from '@/afcl';
|
|
20
|
+
import { USER_MENU_ITEM_CLASSES } from '@/utils/userMenu';
|
|
41
21
|
import { useCoreStore } from '@/stores/core';
|
|
42
22
|
import { getIcon } from '@/utils';
|
|
43
23
|
import { useRouter } from 'vue-router';
|
|
@@ -47,7 +27,6 @@ const router = useRouter();
|
|
|
47
27
|
const coreStore = useCoreStore();
|
|
48
28
|
const { t } = useI18n();
|
|
49
29
|
|
|
50
|
-
const showDropdown = ref(false);
|
|
51
30
|
const props = defineProps(['meta', 'resource']);
|
|
52
31
|
|
|
53
32
|
const options = computed(() => {
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
<span
|
|
9
9
|
v-if="record[column.name] && column.isArray?.enabled"
|
|
10
10
|
v-for="foreignResource in record[column.name]"
|
|
11
|
-
class="rounded-
|
|
11
|
+
class="inline-flex items-center rounded-full m-0.5 bg-lightButtonGroupActiveBackground dark:bg-darkButtonGroupActiveBackground text-lightButtonGroupActiveText dark:text-darkButtonGroupActiveText h-5 px-2 text-xs font-medium"
|
|
12
12
|
>
|
|
13
13
|
<RouterLink
|
|
14
14
|
v-if="foreignResource && foreignResource?.pk"
|
|
@@ -53,14 +53,14 @@
|
|
|
53
53
|
{{ record[column.name].label }}
|
|
54
54
|
</p>
|
|
55
55
|
<div v-else>
|
|
56
|
-
<span class="text-
|
|
56
|
+
<span class="text-lightInputPlaceholderText dark:text-darkInputPlaceholderText">-</span>
|
|
57
57
|
</div>
|
|
58
58
|
</span>
|
|
59
59
|
|
|
60
60
|
<span v-else-if="column.type === 'boolean'">
|
|
61
|
-
<span v-if="record[column.name] === true" class="af-true-value-icon
|
|
62
|
-
<span v-else-if="record[column.name] === false" class="af-false-value-icon
|
|
63
|
-
<span v-else class="
|
|
61
|
+
<span v-if="record[column.name] === true" class="af-true-value-icon gap-1" :class="[BADGE_CLASSES, TRUE_BADGE_CLASSES]"><IconCheck class="w-3 h-3" aria-hidden="true" />{{ $t('Yes') }}</span>
|
|
62
|
+
<span v-else-if="record[column.name] === false" class="af-false-value-icon" :class="[BADGE_CLASSES, FALSE_BADGE_CLASSES]">{{ $t('No') }}</span>
|
|
63
|
+
<span v-else class="border-dashed text-lightInputIconColor border-lightInputBorder dark:text-darkInputIconColor dark:border-darkInputBorder" :class="BADGE_CLASSES">{{ $t('Unset') }}</span>
|
|
64
64
|
</span>
|
|
65
65
|
<span
|
|
66
66
|
v-else-if="column.type === 'json' && column.isArray?.enabled"
|
|
@@ -70,19 +70,19 @@
|
|
|
70
70
|
<span
|
|
71
71
|
v-if="column.isArray.itemType === 'boolean' && arrayItem"
|
|
72
72
|
:key="`${column.name}-${arrayItemIndex}-true`"
|
|
73
|
-
class="af-true-value-icon
|
|
73
|
+
class="af-true-value-icon gap-1" :class="[BADGE_CLASSES, TRUE_BADGE_CLASSES]">
|
|
74
74
|
{{ $t('Yes') }}
|
|
75
75
|
</span>
|
|
76
76
|
<span
|
|
77
77
|
v-else-if="column.isArray.itemType === 'boolean'"
|
|
78
78
|
:key="`${column.name}-${arrayItemIndex}-false`"
|
|
79
|
-
class="af-false-value-icon
|
|
79
|
+
class="af-false-value-icon" :class="[BADGE_CLASSES, FALSE_BADGE_CLASSES]">
|
|
80
80
|
{{ $t('No') }}
|
|
81
81
|
</span>
|
|
82
82
|
<span
|
|
83
83
|
v-else
|
|
84
84
|
:key="`${column.name}-${arrayItemIndex}`"
|
|
85
|
-
class="rounded-
|
|
85
|
+
class="inline-flex items-center rounded-full m-0.5 bg-lightButtonGroupActiveBackground dark:bg-darkButtonGroupActiveBackground text-lightButtonGroupActiveText dark:text-darkButtonGroupActiveText h-5 px-2 text-xs font-medium"
|
|
86
86
|
>
|
|
87
87
|
{{ checkEmptyValues(getArrayItemDisplayValue(arrayItem, column), route.meta.type as "show" | "list") }}
|
|
88
88
|
</span>
|
|
@@ -119,12 +119,17 @@ import {checkEmptyValues, formatDate, formatDateTime, formatTime} from '@/utils'
|
|
|
119
119
|
import { useRoute, useRouter } from 'vue-router';
|
|
120
120
|
import "vue3-json-viewer/dist/vue3-json-viewer.css";
|
|
121
121
|
import { computed, defineAsyncComponent } from 'vue';
|
|
122
|
+
import { IconCheck } from '@iconify-prerendered/vue-lucide';
|
|
122
123
|
import type { AdminForthResourceColumnCommon } from '@/types/Common';
|
|
123
124
|
const JsonViewer = defineAsyncComponent(() => import('vue3-json-viewer').then(module => module.JsonViewer))
|
|
124
125
|
|
|
125
126
|
import { useCoreStore } from '@/stores/core';
|
|
126
127
|
import type { any } from 'zod';
|
|
127
128
|
|
|
129
|
+
const BADGE_CLASSES = 'inline-flex items-center whitespace-nowrap me-2 px-2 h-5 rounded-full border text-xs font-medium';
|
|
130
|
+
const TRUE_BADGE_CLASSES = 'bg-lightSuccessSoft text-lightSuccess border-lightSuccessBorder dark:bg-darkSuccessSoft dark:text-darkSuccess dark:border-darkSuccessBorder';
|
|
131
|
+
const FALSE_BADGE_CLASSES = 'bg-lightSecondaryDarken text-lightInputIconColor border-transparent dark:bg-darkSecondaryLighten dark:text-darkInputIconColor';
|
|
132
|
+
|
|
128
133
|
const coreStore = useCoreStore();
|
|
129
134
|
const route = useRoute();
|
|
130
135
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<svg aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="currentColor" viewBox="0 0
|
|
3
|
-
<path d="
|
|
2
|
+
<svg aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" viewBox="0 0 24 24">
|
|
3
|
+
<path d="M8 2v4"/><path d="M16 2v4"/><rect width="18" height="18" x="3" y="4" rx="2"/><path d="M3 10h18"/>
|
|
4
4
|
</svg>
|
|
5
|
-
</template>
|
|
5
|
+
</template>
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<svg aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="currentColor" viewBox="0 0 24 24">
|
|
3
|
-
<
|
|
2
|
+
<svg aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" viewBox="0 0 24 24">
|
|
3
|
+
<circle cx="12" cy="12" r="10"/><path d="M12 6v6l4 2"/>
|
|
4
4
|
</svg>
|
|
5
|
-
</template>
|
|
5
|
+
</template>
|
package/dist/spa/src/index.scss
CHANGED
|
@@ -25,11 +25,15 @@
|
|
|
25
25
|
// }
|
|
26
26
|
// }
|
|
27
27
|
|
|
28
|
-
|
|
28
|
+
html {
|
|
29
29
|
scrollbar-gutter: stable;
|
|
30
30
|
}
|
|
31
|
+
body {
|
|
32
|
+
-webkit-font-smoothing: antialiased;
|
|
33
|
+
-moz-osx-font-smoothing: grayscale;
|
|
34
|
+
}
|
|
31
35
|
.af-button-shadow {
|
|
32
|
-
box-shadow:
|
|
36
|
+
box-shadow: 0 1px 1px rgb(24 24 27 / 0.05);
|
|
33
37
|
}
|
|
34
38
|
|
|
35
39
|
*{
|
package/dist/spa/src/main.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
<Tooltip>
|
|
3
|
-
<span class="flex items-center">
|
|
4
|
-
{{ visualValue }} <
|
|
3
|
+
<span class="flex items-center gap-1.5 font-mono text-[12.5px]">
|
|
4
|
+
{{ visualValue }} <IconCopy @click.stop="copyToCB" class="min-w-4 w-4 h-4 cursor-pointer text-lightInputIconColor hover:text-lightInputText dark:text-darkInputIconColor dark:hover:text-darkInputText transition-colors" v-if="visualValue"/>
|
|
5
5
|
</span>
|
|
6
6
|
<template #tooltip v-if="visualValue">
|
|
7
7
|
{{ props.record[props.column.name] }}
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
<script setup lang="ts">
|
|
14
14
|
|
|
15
15
|
import { computed, ref, onMounted, nextTick } from 'vue';
|
|
16
|
-
import {
|
|
16
|
+
import { IconCopy } from '@iconify-prerendered/vue-lucide';
|
|
17
17
|
import Tooltip from '@/afcl/Tooltip.vue';
|
|
18
18
|
import { useAdminforth } from '@/adminforth';
|
|
19
19
|
import { useI18n } from 'vue-i18n';
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
<Tooltip>
|
|
3
|
-
<span class="flex items-center">
|
|
4
|
-
{{ visualValue }} <
|
|
3
|
+
<span class="flex items-center gap-1.5 font-mono text-[12.5px]">
|
|
4
|
+
{{ visualValue }} <IconCopy @click.stop="copyToCB" class="min-w-4 w-4 h-4 cursor-pointer text-lightInputIconColor hover:text-lightInputText dark:text-darkInputIconColor dark:hover:text-darkInputText transition-colors" v-if="visualValue"/>
|
|
5
5
|
</span>
|
|
6
6
|
<template #tooltip v-if="visualValue">
|
|
7
7
|
{{ props.record[props.column.name] }}
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
<script setup lang="ts">
|
|
14
14
|
|
|
15
15
|
import { computed, ref, onMounted, nextTick } from 'vue';
|
|
16
|
-
import {
|
|
16
|
+
import { IconCopy } from '@iconify-prerendered/vue-lucide';
|
|
17
17
|
import Tooltip from '@/afcl/Tooltip.vue';
|
|
18
18
|
import { useAdminforth } from '@/adminforth';
|
|
19
19
|
import { useI18n } from 'vue-i18n';
|
|
@@ -51,23 +51,23 @@
|
|
|
51
51
|
</span>
|
|
52
52
|
</div>
|
|
53
53
|
|
|
54
|
-
<span v-if="eyeButton" class="shrink-0 w-
|
|
55
|
-
<
|
|
54
|
+
<span v-if="eyeButton" class="shrink-0 w-4 h-4">
|
|
55
|
+
<IconEye
|
|
56
56
|
v-if="!show"
|
|
57
57
|
@click.stop="toggle"
|
|
58
|
-
class="w-
|
|
58
|
+
class="w-4 h-4 cursor-pointer text-lightInputIconColor hover:text-lightInputText dark:text-darkInputIconColor dark:hover:text-darkInputText transition-colors"
|
|
59
59
|
/>
|
|
60
|
-
<
|
|
60
|
+
<IconEyeOff
|
|
61
61
|
v-else
|
|
62
62
|
@click.stop="toggle"
|
|
63
|
-
class="w-
|
|
63
|
+
class="w-4 h-4 cursor-pointer text-lightInputIconColor hover:text-lightInputText dark:text-darkInputIconColor dark:hover:text-darkInputText transition-colors"
|
|
64
64
|
/>
|
|
65
65
|
</span>
|
|
66
66
|
|
|
67
|
-
<span v-if="showCopy && rawValue" class="shrink-0 w-
|
|
68
|
-
<
|
|
67
|
+
<span v-if="showCopy && rawValue" class="shrink-0 w-4 h-4">
|
|
68
|
+
<IconCopy
|
|
69
69
|
@click.stop="copyToCB"
|
|
70
|
-
class="w-
|
|
70
|
+
class="w-4 h-4 cursor-pointer text-lightInputIconColor hover:text-lightInputText dark:text-darkInputIconColor dark:hover:text-darkInputText transition-colors"
|
|
71
71
|
/>
|
|
72
72
|
</span>
|
|
73
73
|
</div>
|
|
@@ -76,7 +76,7 @@
|
|
|
76
76
|
<script setup lang="ts">
|
|
77
77
|
import { ref, computed } from 'vue';
|
|
78
78
|
import { useI18n } from 'vue-i18n';
|
|
79
|
-
import {
|
|
79
|
+
import { IconCopy, IconEye, IconEyeOff } from '@iconify-prerendered/vue-lucide';
|
|
80
80
|
import ValueRenderer from '@/components/ValueRenderer.vue';
|
|
81
81
|
import Tooltip from '@/afcl/Tooltip.vue';
|
|
82
82
|
import { useAdminforth } from '@/adminforth';
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
// colors of the three dots menus (list header and list rows), themable with the ThreeDotsMenu tokens
|
|
2
|
+
export const THREE_DOTS_MENU_CONTENT_CLASSES = `bg-lightThreeDotsMenuBodyBackground text-lightThreeDotsMenuBodyText
|
|
3
|
+
dark:bg-darkThreeDotsMenuBodyBackground dark:text-darkThreeDotsMenuBodyText`;
|
|
4
|
+
|
|
5
|
+
export const THREE_DOTS_MENU_ITEM_CLASSES = `hover:bg-lightThreeDotsMenuBodyBackgroundHover hover:text-lightThreeDotsMenuBodyTextHover
|
|
6
|
+
focus-visible:bg-lightThreeDotsMenuBodyBackgroundHover dark:focus-visible:bg-darkThreeDotsMenuBodyBackgroundHover
|
|
7
|
+
dark:hover:bg-darkThreeDotsMenuBodyBackgroundHover dark:hover:text-darkThreeDotsMenuBodyTextHover`;
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
// colors of the user menu items, themable with the UserMenu tokens
|
|
2
|
+
export const USER_MENU_ITEM_CLASSES = `bg-lightUserMenuItemBackground hover:bg-lightUserMenuItemBackgroundHover
|
|
3
|
+
focus-visible:bg-lightUserMenuItemBackgroundHover aria-expanded:bg-lightUserMenuItemBackgroundHover
|
|
4
|
+
dark:bg-darkUserMenuItemBackground dark:hover:bg-darkUserMenuItemBackgroundHover
|
|
5
|
+
dark:focus-visible:bg-darkUserMenuItemBackgroundHover dark:aria-expanded:bg-darkUserMenuItemBackgroundHover`;
|