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.
Files changed (94) hide show
  1. package/dist/modules/codeInjector.d.ts.map +1 -1
  2. package/dist/modules/codeInjector.js +20 -3
  3. package/dist/modules/codeInjector.js.map +1 -1
  4. package/dist/modules/styleGenerator.d.ts +13 -0
  5. package/dist/modules/styleGenerator.d.ts.map +1 -1
  6. package/dist/modules/styleGenerator.js +274 -1
  7. package/dist/modules/styleGenerator.js.map +1 -1
  8. package/dist/modules/styles.d.ts +24 -0
  9. package/dist/modules/styles.d.ts.map +1 -1
  10. package/dist/modules/styles.js +452 -427
  11. package/dist/modules/styles.js.map +1 -1
  12. package/dist/modules/utils.d.ts +1 -13
  13. package/dist/modules/utils.d.ts.map +1 -1
  14. package/dist/modules/utils.js +2 -275
  15. package/dist/modules/utils.js.map +1 -1
  16. package/dist/spa/index.html +2 -2
  17. package/dist/spa/package-lock.json +1373 -1136
  18. package/dist/spa/package.json +18 -15
  19. package/dist/spa/pnpm-lock.yaml +1067 -648
  20. package/dist/spa/src/App.vue +84 -92
  21. package/dist/spa/src/afcl/AreaChart.vue +8 -24
  22. package/dist/spa/src/afcl/BarChart.vue +8 -23
  23. package/dist/spa/src/afcl/Button.vue +8 -2
  24. package/dist/spa/src/afcl/ButtonGroup.vue +9 -9
  25. package/dist/spa/src/afcl/Card.vue +40 -19
  26. package/dist/spa/src/afcl/Checkbox.vue +6 -6
  27. package/dist/spa/src/afcl/CountryFlag.vue +17 -11
  28. package/dist/spa/src/afcl/DatePicker.vue +7 -7
  29. package/dist/spa/src/afcl/Dialog.vue +8 -9
  30. package/dist/spa/src/afcl/DropdownMenu.vue +210 -0
  31. package/dist/spa/src/afcl/DropdownMenuAccordion.vue +56 -0
  32. package/dist/spa/src/afcl/DropdownMenuItem.vue +86 -0
  33. package/dist/spa/src/afcl/Dropzone.vue +10 -26
  34. package/dist/spa/src/afcl/Input.vue +4 -4
  35. package/dist/spa/src/afcl/Link.vue +1 -1
  36. package/dist/spa/src/afcl/LinkButton.vue +15 -1
  37. package/dist/spa/src/afcl/MixedChart.vue +4 -18
  38. package/dist/spa/src/afcl/Modal.vue +9 -9
  39. package/dist/spa/src/afcl/PieChart.vue +8 -29
  40. package/dist/spa/src/afcl/ProgressBar.vue +2 -2
  41. package/dist/spa/src/afcl/Select.vue +20 -20
  42. package/dist/spa/src/afcl/Skeleton.vue +1 -1
  43. package/dist/spa/src/afcl/Spinner.vue +1 -1
  44. package/dist/spa/src/afcl/Table.vue +19 -26
  45. package/dist/spa/src/afcl/Textarea.vue +1 -1
  46. package/dist/spa/src/afcl/Toggle.vue +4 -4
  47. package/dist/spa/src/afcl/Tooltip.vue +1 -1
  48. package/dist/spa/src/afcl/TreeMapChart.vue +5 -20
  49. package/dist/spa/src/afcl/VerticalTabs.vue +4 -4
  50. package/dist/spa/src/afcl/buttonStyles.ts +73 -33
  51. package/dist/spa/src/afcl/chartUtils.ts +19 -0
  52. package/dist/spa/src/afcl/classMerge.ts +11 -0
  53. package/dist/spa/src/afcl/dropdownMenuContext.ts +10 -0
  54. package/dist/spa/src/afcl/fileSize.ts +12 -0
  55. package/dist/spa/src/afcl/index.ts +3 -0
  56. package/dist/spa/src/components/AcceptModal.vue +17 -19
  57. package/dist/spa/src/components/Breadcrumbs.vue +11 -13
  58. package/dist/spa/src/components/BreadcrumbsWithButtons.vue +2 -2
  59. package/dist/spa/src/components/CallActionWrapper.vue +3 -0
  60. package/dist/spa/src/components/ColumnValueInput.vue +5 -5
  61. package/dist/spa/src/components/ColumnValueInputWrapper.vue +7 -6
  62. package/dist/spa/src/components/CustomDateRangePicker.vue +8 -8
  63. package/dist/spa/src/components/CustomRangePicker.vue +2 -2
  64. package/dist/spa/src/components/ErrorMessage.vue +3 -4
  65. package/dist/spa/src/components/Filters.vue +13 -13
  66. package/dist/spa/src/components/GroupsTable.vue +12 -12
  67. package/dist/spa/src/components/ListActionsThreeDots.vue +89 -207
  68. package/dist/spa/src/components/MenuLink.vue +23 -20
  69. package/dist/spa/src/components/RangePicker.vue +9 -10
  70. package/dist/spa/src/components/ResourceListTable.vue +68 -65
  71. package/dist/spa/src/components/ShowTable.vue +8 -8
  72. package/dist/spa/src/components/Sidebar.vue +31 -38
  73. package/dist/spa/src/components/ThreeDotsMenu.vue +88 -136
  74. package/dist/spa/src/components/Toast.vue +16 -23
  75. package/dist/spa/src/components/UserMenuSettingsButton.vue +15 -36
  76. package/dist/spa/src/components/ValueRenderer.vue +13 -8
  77. package/dist/spa/src/components/icons/IconCalendar.vue +3 -3
  78. package/dist/spa/src/components/icons/IconTime.vue +3 -3
  79. package/dist/spa/src/index.scss +6 -2
  80. package/dist/spa/src/main.ts +2 -0
  81. package/dist/spa/src/renderers/CompactField.vue +3 -3
  82. package/dist/spa/src/renderers/CompactUUID.vue +3 -3
  83. package/dist/spa/src/renderers/SensitiveBlurCell.vue +9 -9
  84. package/dist/spa/src/utils/threeDotsMenu.ts +7 -0
  85. package/dist/spa/src/utils/userMenu.ts +5 -0
  86. package/dist/spa/src/utils/utils.ts +12 -39
  87. package/dist/spa/src/views/CreateEditSkeleton.vue +3 -3
  88. package/dist/spa/src/views/CreateView.vue +5 -6
  89. package/dist/spa/src/views/EditView.vue +5 -6
  90. package/dist/spa/src/views/ListView.vue +114 -115
  91. package/dist/spa/src/views/LoginView.vue +4 -4
  92. package/dist/spa/src/views/ShowView.vue +17 -18
  93. package/dist/spa/vite.config.ts +23 -16
  94. package/package.json +1 -2
@@ -1,99 +1,82 @@
1
- <template >
2
- <div class="relative" v-if="threeDotsDropdownItems?.length || customActions?.length || (bulkActions?.some((action: AdminForthBulkActionFront) => action.showInThreeDotsDropdown))">
3
- <Button
4
- ref="buttonTriggerRef"
5
- @click="toggleDropdownVisibility"
6
- class="py-2.5 px-2.5 text-lightThreeDotsMenuIconDots hover:text-lightThreeDotsMenuIconDotsHover dark:text-darkThreeDotsMenuIconDots dark:hover:text-darkThreeDotsMenuIconDotsHover"
7
- variant="secondary"
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
- <svg class="w-3 h-3" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="currentColor" viewBox="0 0 4 15">
10
- <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"/>
11
- </svg>
12
- </Button>
13
-
14
- <!-- Dropdown menu -->
15
- <div
16
- ref="dropdownRef"
17
- :class="{
18
- 'block': showDropdown,
19
- 'hidden': !showDropdown,
20
- 'left-0': openToRight,
21
- 'right-0': !openToRight
22
- }"
23
- class="absolute z-50 mt-3 bg-lightThreeDotsMenuBodyBackground divide-y divide-gray-100 rounded-lg shadow w-max max-w-64 dark:bg-darkThreeDotsMenuBodyBackground dark:divide-gray-600">
24
- <ul class="py-2 text-sm text-lightThreeDotsMenuBodyText dark:text-darkThreeDotsMenuBodyText" aria-labelledby="dropdownMenuIconButton">
25
- <li v-for="(item, i) in threeDotsDropdownItems" :key="`dropdown-item-${i}`">
26
- <div
27
- class="block hover:bg-lightThreeDotsMenuBodyBackgroundHover hover:text-lightThreeDotsMenuBodyTextHover dark:hover:bg-darkThreeDotsMenuBodyBackgroundHover dark:hover:text-darkThreeDotsMenuBodyTextHover"
28
- :class="{
29
- 'pointer-events-none': checkboxes && checkboxes.length === 0 && item.meta?.disabledWhenNoCheckboxes,
30
- 'opacity-50': checkboxes && checkboxes.length === 0 && item.meta?.disabledWhenNoCheckboxes,
31
- 'cursor-not-allowed': checkboxes && checkboxes.length === 0 && item.meta?.disabledWhenNoCheckboxes,
32
- }"
33
- @click="injectedComponentClick(i, $event)"
34
- >
35
- <div class="wrapper" v-if="getCustomComponent(item)">
36
- <component
37
- :ref="(el: any) => setComponentRef(el, i)" :is="getCustomComponent(item)!"
38
- :meta="item.meta"
39
- :resource="coreStore.resource"
40
- :adminUser="coreStore.adminUser"
41
- :checkboxes="checkboxes"
42
- :updateList="props.updateList"
43
- :clearCheckboxes="clearCheckboxes"
44
- />
45
- </div>
46
- </div>
47
- </li>
48
- <li v-for="(action, i) in customActions" :key="action.id">
49
- <div
50
- class="wrapper"
51
- @click="injectedComponentClick(threeDotsDropdownItems ? threeDotsDropdownItems.length + i : i, $event)"
52
- >
53
- <component
54
- :ref="(el: any) => setComponentRef(el, threeDotsDropdownItems ? threeDotsDropdownItems.length + i : i)"
55
- :is="(action.customComponent && getCustomComponent(formatComponent(action.customComponent))) || CallActionWrapper"
56
- :meta="formatComponent(action.customComponent).meta"
57
- @callAction="(payload? : Object) => handleActionClick(action, payload)"
58
- >
59
- <a @click.prevent class="block">
60
- <div class="flex items-center gap-2 hover:text-lightThreeDotsMenuBodyTextHover hover:bg-lightThreeDotsMenuBodyBackgroundHover dark:hover:bg-darkThreeDotsMenuBodyBackgroundHover dark:hover:text-darkThreeDotsMenuBodyTextHover">
61
- <component
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, nextTick, type ComponentPublicInstance, onMounted, onUnmounted } from 'vue';
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?: MouseEvent) {
182
- if (event?.defaultPrevented) {
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
- document.addEventListener('mousedown', handleClickOutside)
215
- })
216
-
217
- onUnmounted(() => {
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-lg dark:text-darkToastText dark:bg-darkToastBackground bg-lightToastBackground text-lightToastText border-l-4"
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-5 h-5" aria-hidden="true" />
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-lg focus:ring-2 focus: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:ring-darkToastCloseIconFocusRing" >
44
- <svg class="w-3 h-3" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 14 14">
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 { IconInfoCircleSolid, IconCloseCircleSolid, IconExclamationCircleSolid, IconCheckCircleSolid } from '@iconify-prerendered/vue-flowbite';
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: IconInfoCircleSolid,
74
- borderClass: 'border-lightPrimary dark:border-darkPrimary',
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: IconCloseCircleSolid,
80
- borderClass: 'border-red-500 dark:border-red-800',
81
- iconClass: 'text-red-500 bg-red-100 dark:bg-red-800 dark:text-red-200',
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: IconExclamationCircleSolid,
86
- borderClass: 'border-orange-500 dark:border-orange-700',
87
- iconClass: 'text-orange-500 bg-orange-100 dark:bg-orange-700 dark:text-orange-200',
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: IconCheckCircleSolid,
92
- borderClass: 'border-green-500 dark:border-green-800',
93
- iconClass: 'text-green-500 bg-green-100 dark:bg-green-800 dark:text-green-200',
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
- <div v-if="options && options.length" class="min-w-40">
3
- <div class="cursor-pointer flex items-center justify-between gap-1 block px-4 py-2 text-sm
4
- bg-lightUserMenuItemBackground hover:bg-lightUserMenuItemBackgroundHover text-lightUserMenuItemText
5
- hover:text-lightUserMenuItemText dark:bg-darkUserMenuItemBackground dark:hover:bg-darkUserMenuItemBackgroundHover
6
- dark:text-darkUserMenuItemText dark:hover:darkUserMenuItemTextHover
7
- w-full select-none "
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
- <span>{{ $t('Settings') }}</span>
11
- <IconCaretDownSolid class="h-5 w-5 text-lightPrimary dark:text-gray-400 opacity-50 transition duration-150 ease-in"
12
- :class="{ 'transform rotate-180': showDropdown }"
13
- />
14
- </div>
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 { IconCaretDownSolid } from '@iconify-prerendered/vue-flowbite';
40
- import { computed, ref, onMounted, watch } from 'vue';
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-md m-0.5 bg-lightAnnouncementBG dark:bg-darkAnnouncementBG text-lightAnnouncementText dark:text-darkAnnouncementText py-0.5 px-2.5 text-sm"
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-gray-400">-</span>
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 bg-green-100 whitespace-nowrap text-green-800 text-xs font-medium me-2 px-2.5 py-0.5 rounded dark:bg-gray-700 dark:text-green-400 border border-green-400">{{ $t('Yes') }}</span>
62
- <span v-else-if="record[column.name] === false" class="af-false-value-icon bg-red-100 whitespace-nowrap text-red-800 text-xs font-medium me-2 px-2.5 py-0.5 rounded dark:bg-gray-700 dark:text-red-400 border border-red-400">{{ $t('No') }}</span>
63
- <span v-else class="bg-gray-100 whitespace-nowrap text-gray-800 text-xs font-medium me-2 px-2.5 py-0.5 rounded dark:bg-gray-700 dark:text-gray-400 border border-gray-400">{{ $t('Unset') }}</span>
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 bg-green-100 whitespace-nowrap text-green-800 text-xs font-medium me-2 px-2.5 py-0.5 rounded dark:bg-gray-700 dark:text-green-400 border border-green-400">
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 bg-red-100 whitespace-nowrap text-red-800 text-xs font-medium me-2 px-2.5 py-0.5 rounded dark:bg-gray-700 dark:text-red-400 border border-red-400">
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-md m-0.5 bg-lightAnnouncementBG dark:bg-darkAnnouncementBG text-lightAnnouncementText dark:text-darkAnnouncementText py-0.5 px-2.5 text-sm"
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 20 20">
3
- <path d="M20 4a2 2 0 0 0-2-2h-2V1a1 1 0 0 0-2 0v1h-3V1a1 1 0 0 0-2 0v1H6V1a1 1 0 0 0-2 0v1H2a2 2 0 0 0-2 2v2h20V4ZM0 18a2 2 0 0 0 2 2h16a2 2 0 0 0 2-2V8H0v10Zm5-8h10a1 1 0 0 1 0 2H5a1 1 0 0 1 0-2Z"/>
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
- <path fill-rule="evenodd" d="M2 12C2 6.477 6.477 2 12 2s10 4.477 10 10-4.477 10-10 10S2 17.523 2 12Zm11-4a1 1 0 1 0-2 0v4a1 1 0 0 0 .293.707l3 3a1 1 0 0 0 1.414-1.414L13 11.586V8Z" clip-rule="evenodd"></path>
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>
@@ -25,11 +25,15 @@
25
25
  // }
26
26
  // }
27
27
 
28
- body {
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: -0px 6px 6px rgb(0, 0, 0, 0.1);
36
+ box-shadow: 0 1px 1px rgb(24 24 27 / 0.05);
33
37
  }
34
38
 
35
39
  *{
@@ -1,5 +1,7 @@
1
1
  import { createApp } from 'vue'
2
2
  import { createPinia } from 'pinia'
3
+ import '@fontsource-variable/geist'
4
+ import '@fontsource-variable/geist-mono'
3
5
  /* IMPORTANT:ADMINFORTH IMPORTS */
4
6
 
5
7
 
@@ -1,7 +1,7 @@
1
1
  <template>
2
2
  <Tooltip>
3
- <span class="flex items-center">
4
- {{ visualValue }} <IconFileCopyAltSolid @click.stop="copyToCB" class="min-w-5 min-h-5 text-lightPrimary dark:text-darkPrimary" v-if="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 { IconFileCopyAltSolid } from '@iconify-prerendered/vue-flowbite';
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 }} <IconFileCopyAltSolid @click.stop="copyToCB" class="min-w-5 min-h-5 text-lightPrimary dark:text-darkPrimary" v-if="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 { IconFileCopyAltSolid } from '@iconify-prerendered/vue-flowbite';
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-5 h-5">
55
- <IconEyeSolid
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-5 h-5 cursor-pointer text-lightPrimary dark:text-darkPrimary"
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
- <IconEyeSlashSolid
60
+ <IconEyeOff
61
61
  v-else
62
62
  @click.stop="toggle"
63
- class="w-5 h-5 cursor-pointer text-lightPrimary dark:text-darkPrimary"
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-5 h-5">
68
- <IconFileCopyAltSolid
67
+ <span v-if="showCopy && rawValue" class="shrink-0 w-4 h-4">
68
+ <IconCopy
69
69
  @click.stop="copyToCB"
70
- class="w-5 h-5 cursor-pointer text-lightPrimary dark:text-darkPrimary"
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 { IconFileCopyAltSolid, IconEyeSolid, IconEyeSlashSolid } from '@iconify-prerendered/vue-flowbite';
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`;