@adminforth/quick-filters 2.1.1 → 2.2.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/build.log CHANGED
@@ -4,11 +4,12 @@
4
4
 
5
5
  sending incremental file list
6
6
  custom/
7
+ custom/FilterDropdown.vue
7
8
  custom/FiltersArea.vue
8
9
  custom/QickFiltersSelect.vue
9
10
  custom/UniversalSearchInput.vue
10
11
  custom/tsconfig.json
11
12
  custom/types.ts
12
13
 
13
- sent 5,221 bytes received 115 bytes 10,672.00 bytes/sec
14
- total size is 4,788 speedup is 0.90
14
+ sent 10,679 bytes received 134 bytes 21,626.00 bytes/sec
15
+ total size is 10,173 speedup is 0.94
@@ -0,0 +1,148 @@
1
+ <template>
2
+ <div class="afcl-select afcl-select-wrapper relative inline-block af-button-shadow rounded" ref="internalSelect"
3
+ :class="{'opacity-50': readonly}"
4
+ >
5
+ <div class="relative w-fit">
6
+ <button
7
+ ref="dropdownFilterEl"
8
+ type="button"
9
+ @click="dropdownClick"
10
+ class="group h-[34px] inline-flex items-center justify-between min-w-max px-3 py-2 text-left cursor-pointer
11
+ text-sm font-medium transition-all rounded border outline-none gap-x-2
12
+ bg-lightListViewButtonBackground text-lightListViewButtonText border-lightListViewButtonBorder
13
+ dark:bg-darkListViewButtonBackground dark:text-darkListViewButtonText dark:border-darkListViewButtonBorder
14
+ hover:bg-lightListViewButtonBackgroundHover hover:text-lightListViewButtonTextHover"
15
+ >
16
+ <span v-if="displayLabel" class="whitespace-nowrap">
17
+ {{ displayLabel }}
18
+ </span>
19
+ <span
20
+ v-else
21
+ class="opacity-100 transition-colors whitespace-nowrap"
22
+ >
23
+ {{ filter?.name || placeholder || $t('Select...') }}
24
+ </span>
25
+
26
+ <IconCaretDownSolid class="h-4 w-4 text-lightPrimary dark:text-darkPrimary opacity-50 transition duration-150 ease-in flex-shrink-0"
27
+ :class="{ 'transform rotate-180': showDropdown }"
28
+ />
29
+ </button>
30
+ </div>
31
+
32
+ <teleport to="body" v-if="teleportToBody && showDropdown">
33
+ <div
34
+ ref="dropdownEl"
35
+ :style="getDropdownPosition"
36
+ class="fixed z-[1000] w-max min-w-fit bg-lightDropdownOptionsBackground shadow-lg dark:shadow-black dark:bg-darkDropdownOptionsBackground
37
+ dark:border-gray-600 rounded-md text-base ring-1 ring-black ring-opacity-5 overflow-hidden focus:outline-none sm:text-sm max-h-64 flex flex-col"
38
+ >
39
+ <div class="py-1 overflow-y-auto grow" @scroll="handleDropdownScroll">
40
+ <div
41
+ v-for="item in options"
42
+ :key="item.value"
43
+ class="px-4 py-2 cursor-pointer hover:bg-lightDropdownOptionsHoverBackground dark:hover:bg-darkDropdownOptionsHoverBackground text-lightDropdownOptionsText dark:text-darkDropdownOptionsText"
44
+ :class="{ 'bg-lightDropdownPicked dark:bg-darkDropdownPicked': isItemSelected(item) }"
45
+ @click="toggleItem(item)"
46
+ >
47
+ <slot name="item" :option="item">
48
+ {{ item.label }}
49
+ </slot>
50
+ </div>
51
+
52
+ <div v-if="!options?.length" class="px-4 py-2 text-gray-500 italic text-center">
53
+ {{ $t('No items here') }}
54
+ </div>
55
+
56
+ <div
57
+ v-if="modelValue !== null && modelValue !== undefined && modelValue !== ''"
58
+ class="px-4 py-2 cursor-pointer hover:bg-lightDropdownOptionsHoverBackground dark:hover:bg-darkDropdownOptionsHoverBackground text-lightDropdownOptionsText dark:text-darkDropdownOptionsText"
59
+ @click="clearSelection"
60
+ >
61
+ {{ $t('Clear selection') }}
62
+ </div>
63
+ </div>
64
+ </div>
65
+ </teleport>
66
+ </div>
67
+ </template>
68
+
69
+ <script setup lang="ts">
70
+ import { ref, computed, onMounted, onUnmounted, type PropType } from 'vue';
71
+ import { IconCaretDownSolid } from '@iconify-prerendered/vue-flowbite';
72
+
73
+ const props = defineProps({
74
+ filter: {
75
+ type: Object as PropType<{ name: string; enum: any[] }>,
76
+ default: null,
77
+ },
78
+ options: {
79
+ type: Array as PropType<{label: string, value: any}[]>,
80
+ default: () => [],
81
+ },
82
+ modelValue: [String, Number, Boolean, Array] as PropType<any>,
83
+ placeholder: String,
84
+ readonly: Boolean,
85
+ teleportToBody: Boolean,
86
+ });
87
+
88
+ const emit = defineEmits(['update:modelValue', 'scroll-near-end']);
89
+
90
+ const showDropdown = ref(false);
91
+ const dropdownFilterEl = ref<HTMLElement | null>(null);
92
+ const dropdownEl = ref<HTMLElement | null>(null);
93
+ const internalSelect = ref<HTMLElement | null>(null);
94
+
95
+ const displayLabel = computed(() => {
96
+ const selected = props.options.find(o => o.value === props.modelValue);
97
+ return selected ? selected.label : '';
98
+ });
99
+
100
+ const isItemSelected = (item: any) => props.modelValue === item.value;
101
+
102
+ const toggleItem = (item: any) => {
103
+ emit('update:modelValue', item.value);
104
+ showDropdown.value = false;
105
+ };
106
+
107
+ const clearSelection = () => {
108
+ emit('update:modelValue', null);
109
+ showDropdown.value = false;
110
+ };
111
+
112
+ const dropdownClick = () => {
113
+ if (!props.readonly) showDropdown.value = !showDropdown.value;
114
+ };
115
+
116
+ const getDropdownPosition = computed(() => {
117
+ if (!dropdownFilterEl.value) return {};
118
+ const rect = dropdownFilterEl.value.getBoundingClientRect();
119
+ return {
120
+ top: `${rect.bottom + window.scrollY + 4}px`,
121
+ left: `${rect.right + window.scrollX - (dropdownEl.value?.offsetWidth || rect.width)}px`,
122
+ minWidth: `${rect.width}px`
123
+ };
124
+ });
125
+
126
+ const handleDropdownScroll = (event: Event) => {
127
+ const target = event.target as HTMLElement;
128
+ if (target.scrollTop + target.clientHeight >= target.scrollHeight - 10) {
129
+ emit('scroll-near-end');
130
+ }
131
+ };
132
+
133
+ const handleClickOutside = (event: MouseEvent) => {
134
+ const target = event.target as HTMLElement;
135
+ if (internalSelect.value?.contains(target)) return;
136
+ if (dropdownEl.value?.contains(target)) return;
137
+
138
+ showDropdown.value = false;
139
+ };
140
+
141
+ onMounted(() => {
142
+ document.addEventListener('click', handleClickOutside);
143
+ });
144
+
145
+ onUnmounted(() => {
146
+ document.removeEventListener('click', handleClickOutside);
147
+ });
148
+ </script>
@@ -4,12 +4,12 @@
4
4
  <UniversalSearchInput
5
5
  v-if="filter.hasSearchInput"
6
6
  :meta="{
7
- placeholder: `Search ${filter.name}`,
7
+ placeholder: `${filter.name}`,
8
8
  filterField: filter.name
9
9
  }"
10
10
  />
11
11
 
12
- <div class="w-64" v-else>
12
+ <div v-else>
13
13
  <QickFiltersSelect
14
14
  :filter="filter"
15
15
  />
@@ -1,18 +1,18 @@
1
1
  <template>
2
- <Select
3
- class="w-full text-sm"
2
+ <FilterDropdown
3
+ class="text-sm"
4
+ :filter="filter as { name: string; enum: any[] }"
4
5
  :options="selectOptions"
5
6
  v-model="selected"
6
- classesForInput="py-[6px] !text-sm bg-white rounded"
7
7
  teleportToBody
8
- ></Select>
8
+ ></FilterDropdown>
9
9
  </template>
10
10
 
11
11
 
12
12
 
13
13
  <script lang="ts" setup>
14
14
  import { ref, onMounted, watch } from 'vue';
15
- import { Select } from '@/afcl'
15
+ import FilterDropdown from './FilterDropdown.vue'
16
16
  import type { Filter } from './types';
17
17
  import { useAdminforth } from '@/adminforth';
18
18
  import { AdminForthFilterOperators } from '@/types/Common';
@@ -0,0 +1,148 @@
1
+ <template>
2
+ <div class="afcl-select afcl-select-wrapper relative inline-block af-button-shadow rounded" ref="internalSelect"
3
+ :class="{'opacity-50': readonly}"
4
+ >
5
+ <div class="relative w-fit">
6
+ <button
7
+ ref="dropdownFilterEl"
8
+ type="button"
9
+ @click="dropdownClick"
10
+ class="group h-[34px] inline-flex items-center justify-between min-w-max px-3 py-2 text-left cursor-pointer
11
+ text-sm font-medium transition-all rounded border outline-none gap-x-2
12
+ bg-lightListViewButtonBackground text-lightListViewButtonText border-lightListViewButtonBorder
13
+ dark:bg-darkListViewButtonBackground dark:text-darkListViewButtonText dark:border-darkListViewButtonBorder
14
+ hover:bg-lightListViewButtonBackgroundHover hover:text-lightListViewButtonTextHover"
15
+ >
16
+ <span v-if="displayLabel" class="whitespace-nowrap">
17
+ {{ displayLabel }}
18
+ </span>
19
+ <span
20
+ v-else
21
+ class="opacity-100 transition-colors whitespace-nowrap"
22
+ >
23
+ {{ filter?.name || placeholder || $t('Select...') }}
24
+ </span>
25
+
26
+ <IconCaretDownSolid class="h-4 w-4 text-lightPrimary dark:text-darkPrimary opacity-50 transition duration-150 ease-in flex-shrink-0"
27
+ :class="{ 'transform rotate-180': showDropdown }"
28
+ />
29
+ </button>
30
+ </div>
31
+
32
+ <teleport to="body" v-if="teleportToBody && showDropdown">
33
+ <div
34
+ ref="dropdownEl"
35
+ :style="getDropdownPosition"
36
+ class="fixed z-[1000] w-max min-w-fit bg-lightDropdownOptionsBackground shadow-lg dark:shadow-black dark:bg-darkDropdownOptionsBackground
37
+ dark:border-gray-600 rounded-md text-base ring-1 ring-black ring-opacity-5 overflow-hidden focus:outline-none sm:text-sm max-h-64 flex flex-col"
38
+ >
39
+ <div class="py-1 overflow-y-auto grow" @scroll="handleDropdownScroll">
40
+ <div
41
+ v-for="item in options"
42
+ :key="item.value"
43
+ class="px-4 py-2 cursor-pointer hover:bg-lightDropdownOptionsHoverBackground dark:hover:bg-darkDropdownOptionsHoverBackground text-lightDropdownOptionsText dark:text-darkDropdownOptionsText"
44
+ :class="{ 'bg-lightDropdownPicked dark:bg-darkDropdownPicked': isItemSelected(item) }"
45
+ @click="toggleItem(item)"
46
+ >
47
+ <slot name="item" :option="item">
48
+ {{ item.label }}
49
+ </slot>
50
+ </div>
51
+
52
+ <div v-if="!options?.length" class="px-4 py-2 text-gray-500 italic text-center">
53
+ {{ $t('No items here') }}
54
+ </div>
55
+
56
+ <div
57
+ v-if="modelValue !== null && modelValue !== undefined && modelValue !== ''"
58
+ class="px-4 py-2 cursor-pointer hover:bg-lightDropdownOptionsHoverBackground dark:hover:bg-darkDropdownOptionsHoverBackground text-lightDropdownOptionsText dark:text-darkDropdownOptionsText"
59
+ @click="clearSelection"
60
+ >
61
+ {{ $t('Clear selection') }}
62
+ </div>
63
+ </div>
64
+ </div>
65
+ </teleport>
66
+ </div>
67
+ </template>
68
+
69
+ <script setup lang="ts">
70
+ import { ref, computed, onMounted, onUnmounted, type PropType } from 'vue';
71
+ import { IconCaretDownSolid } from '@iconify-prerendered/vue-flowbite';
72
+
73
+ const props = defineProps({
74
+ filter: {
75
+ type: Object as PropType<{ name: string; enum: any[] }>,
76
+ default: null,
77
+ },
78
+ options: {
79
+ type: Array as PropType<{label: string, value: any}[]>,
80
+ default: () => [],
81
+ },
82
+ modelValue: [String, Number, Boolean, Array] as PropType<any>,
83
+ placeholder: String,
84
+ readonly: Boolean,
85
+ teleportToBody: Boolean,
86
+ });
87
+
88
+ const emit = defineEmits(['update:modelValue', 'scroll-near-end']);
89
+
90
+ const showDropdown = ref(false);
91
+ const dropdownFilterEl = ref<HTMLElement | null>(null);
92
+ const dropdownEl = ref<HTMLElement | null>(null);
93
+ const internalSelect = ref<HTMLElement | null>(null);
94
+
95
+ const displayLabel = computed(() => {
96
+ const selected = props.options.find(o => o.value === props.modelValue);
97
+ return selected ? selected.label : '';
98
+ });
99
+
100
+ const isItemSelected = (item: any) => props.modelValue === item.value;
101
+
102
+ const toggleItem = (item: any) => {
103
+ emit('update:modelValue', item.value);
104
+ showDropdown.value = false;
105
+ };
106
+
107
+ const clearSelection = () => {
108
+ emit('update:modelValue', null);
109
+ showDropdown.value = false;
110
+ };
111
+
112
+ const dropdownClick = () => {
113
+ if (!props.readonly) showDropdown.value = !showDropdown.value;
114
+ };
115
+
116
+ const getDropdownPosition = computed(() => {
117
+ if (!dropdownFilterEl.value) return {};
118
+ const rect = dropdownFilterEl.value.getBoundingClientRect();
119
+ return {
120
+ top: `${rect.bottom + window.scrollY + 4}px`,
121
+ left: `${rect.right + window.scrollX - (dropdownEl.value?.offsetWidth || rect.width)}px`,
122
+ minWidth: `${rect.width}px`
123
+ };
124
+ });
125
+
126
+ const handleDropdownScroll = (event: Event) => {
127
+ const target = event.target as HTMLElement;
128
+ if (target.scrollTop + target.clientHeight >= target.scrollHeight - 10) {
129
+ emit('scroll-near-end');
130
+ }
131
+ };
132
+
133
+ const handleClickOutside = (event: MouseEvent) => {
134
+ const target = event.target as HTMLElement;
135
+ if (internalSelect.value?.contains(target)) return;
136
+ if (dropdownEl.value?.contains(target)) return;
137
+
138
+ showDropdown.value = false;
139
+ };
140
+
141
+ onMounted(() => {
142
+ document.addEventListener('click', handleClickOutside);
143
+ });
144
+
145
+ onUnmounted(() => {
146
+ document.removeEventListener('click', handleClickOutside);
147
+ });
148
+ </script>
@@ -4,12 +4,12 @@
4
4
  <UniversalSearchInput
5
5
  v-if="filter.hasSearchInput"
6
6
  :meta="{
7
- placeholder: `Search ${filter.name}`,
7
+ placeholder: `${filter.name}`,
8
8
  filterField: filter.name
9
9
  }"
10
10
  />
11
11
 
12
- <div class="w-64" v-else>
12
+ <div v-else>
13
13
  <QickFiltersSelect
14
14
  :filter="filter"
15
15
  />
@@ -1,18 +1,18 @@
1
1
  <template>
2
- <Select
3
- class="w-full text-sm"
2
+ <FilterDropdown
3
+ class="text-sm"
4
+ :filter="filter as { name: string; enum: any[] }"
4
5
  :options="selectOptions"
5
6
  v-model="selected"
6
- classesForInput="py-[6px] !text-sm bg-white rounded"
7
7
  teleportToBody
8
- ></Select>
8
+ ></FilterDropdown>
9
9
  </template>
10
10
 
11
11
 
12
12
 
13
13
  <script lang="ts" setup>
14
14
  import { ref, onMounted, watch } from 'vue';
15
- import { Select } from '@/afcl'
15
+ import FilterDropdown from './FilterDropdown.vue'
16
16
  import type { Filter } from './types';
17
17
  import { useAdminforth } from '@/adminforth';
18
18
  import { AdminForthFilterOperators } from '@/types/Common';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adminforth/quick-filters",
3
- "version": "2.1.1",
3
+ "version": "2.2.1",
4
4
  "main": "dist/index.js",
5
5
  "types": "dist/index.d.ts",
6
6
  "type": "module",
@@ -17,13 +17,13 @@
17
17
  "description": "",
18
18
  "devDependencies": {
19
19
  "@types/node": "latest",
20
- "adminforth": "^2.42.0",
20
+ "adminforth": "^2.50.0",
21
21
  "semantic-release": "^24.2.1",
22
22
  "semantic-release-slack-bot": "^4.0.2",
23
23
  "typescript": "^5.7.3"
24
24
  },
25
25
  "peerDependencies": {
26
- "adminforth": "^2.42.0"
26
+ "adminforth": "^2.50.0"
27
27
  },
28
28
  "release": {
29
29
  "plugins": [