@necrolab/dashboard 0.4.48 → 0.4.50

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 (42) hide show
  1. package/.claude/settings.local.json +2 -1
  2. package/.prettierrc +12 -1
  3. package/exit +209 -0
  4. package/index.html +1 -1
  5. package/package.json +1 -1
  6. package/public/manifest.json +8 -3
  7. package/src/assets/css/_input.scss +104 -111
  8. package/src/assets/css/_utilities.scss +441 -0
  9. package/src/assets/css/main.scss +228 -154
  10. package/src/components/Auth/LoginForm.vue +49 -6
  11. package/src/components/Editors/Account/Account.vue +156 -146
  12. package/src/components/Editors/Account/AccountCreator.vue +1 -1
  13. package/src/components/Editors/Account/AccountView.vue +13 -13
  14. package/src/components/Editors/Account/CreateAccount.vue +25 -16
  15. package/src/components/Editors/Profile/CreateProfile.vue +1 -1
  16. package/src/components/Editors/Profile/Profile.vue +1 -1
  17. package/src/components/Editors/Profile/ProfileCountryChooser.vue +83 -19
  18. package/src/components/Editors/Profile/ProfileView.vue +11 -11
  19. package/src/components/Tasks/CreateTaskAXS.vue +3 -3
  20. package/src/components/Tasks/CreateTaskTM.vue +7 -35
  21. package/src/components/Tasks/QuickSettings.vue +112 -9
  22. package/src/components/Tasks/Stats.vue +29 -26
  23. package/src/components/Tasks/Task.vue +499 -365
  24. package/src/components/Tasks/TaskView.vue +187 -127
  25. package/src/components/icons/Sandclock.vue +2 -2
  26. package/src/components/icons/Stadium.vue +1 -1
  27. package/src/components/ui/Modal.vue +37 -35
  28. package/src/components/ui/controls/CountryChooser.vue +200 -62
  29. package/src/components/ui/controls/atomic/Dropdown.vue +177 -91
  30. package/src/components/ui/controls/atomic/MultiDropdown.vue +247 -168
  31. package/src/composables/useClickOutside.js +21 -0
  32. package/src/composables/useDropdownPosition.js +174 -0
  33. package/src/stores/sampleData.js +4 -2
  34. package/src/stores/ui.js +8 -6
  35. package/src/views/Accounts.vue +12 -19
  36. package/src/views/Console.vue +34 -47
  37. package/src/views/Editor.vue +1194 -730
  38. package/src/views/Login.vue +65 -119
  39. package/src/views/Profiles.vue +2 -2
  40. package/src/views/Tasks.vue +170 -137
  41. package/static/offline.html +192 -50
  42. package/tailwind.config.js +47 -21
@@ -1,87 +1,225 @@
1
1
  <template>
2
- <div @click="open = !open">
3
- <div class="dropdown input-default p-4 w-16 bg-dark-550 small-dropdown">
4
- <span class="flex justify-between items-center z-inf text-white">
5
- <div class="flex gap-3 justify-center">
6
- <img class="w-5" :src="`/flags/${ui.currentCountry.siteId.split('_')[1].toLowerCase()}.svg`" />
7
- </div>
8
- </span>
9
- <transition name="fade">
10
- <div
11
- v-if="open"
12
- class="dropdown-content special-dropdown mt-2 snap-mandatory snap-y z-inf overflow-scroll hidden-scrollbars"
13
- >
14
- <div class="snap-start pt-2 text-sm font-bold text-center">TM</div>
15
- <div
16
- v-bind:key="country.id"
17
- v-for="(country, i) in countries.TM"
18
- :class="`cursor-pointer snap-start w-12 ${i === 0 ? 'pt-2' : 'my-2'}`"
19
- @click="ui.setCurrentCountry(country, true, 'TM')"
20
- >
21
- <div class="flex justify-between smooth-hover">
22
- <span class="text-sm">{{ country.siteId.split("_")[1] }} </span>
23
- <img class="w-5" :src="`/flags/${country.siteId.split('_')[1].toLowerCase()}.svg`" />
24
- </div>
25
- </div>
26
- <div class="snap-start pt-2 text-sm font-bold text-center">AXS</div>
27
- <div
28
- v-bind:key="country.id"
29
- v-for="(country, i) in countries.AXS"
30
- :class="`cursor-pointer snap-start w-12 ${i === 0 ? 'pt-2' : 'my-2'}`"
31
- @click="ui.setCurrentCountry(country, true, 'AXS')"
32
- >
33
- <div class="flex gap-3 justify-between smooth-hover">
34
- <span class="text-sm">{{ country.siteId.split("_")[1] }} </span>
35
- <img class="w-5" :src="`/flags/${country.siteId.split('_')[1].toLowerCase()}.svg`" />
36
- </div>
37
- </div>
38
- </div>
39
- </transition>
2
+ <div>
3
+ <div
4
+ class="dropdown input-default p-4 w-16 bg-dark-550 small-dropdown"
5
+ ref="dropdownRef"
6
+ @click="toggleOpen"
7
+ >
8
+ <span class="flex justify-between items-center z-50 text-white">
9
+ <div class="flex gap-3 justify-center">
10
+ <img
11
+ class="w-5"
12
+ :src="`/flags/${ui.currentCountry.siteId.split('_')[1].toLowerCase()}.svg`"
13
+ />
40
14
  </div>
15
+ </span>
16
+ <Teleport to="body">
17
+ <transition name="fade">
18
+ <div
19
+ v-if="open"
20
+ class="dropdown-content-portal special-dropdown"
21
+ :style="menuStyle"
22
+ @click.stop
23
+ @wheel.stop
24
+ @touchmove.stop
25
+ >
26
+ <div class="header-item">TM</div>
27
+ <div
28
+ v-bind:key="country.id"
29
+ v-for="country in countries.TM"
30
+ class="country-item"
31
+ @click="selectCountry(country, 'TM')"
32
+ >
33
+ <div class="flex items-center justify-center gap-2">
34
+ <span class="text-xs font-medium">{{
35
+ country.siteId.split("_")[1]
36
+ }}</span>
37
+ <img
38
+ class="w-5 h-4"
39
+ :src="`/flags/${country.siteId.split('_')[1].toLowerCase()}.svg`"
40
+ />
41
+ </div>
42
+ </div>
43
+ <div class="header-item">AXS</div>
44
+ <div
45
+ v-bind:key="country.id"
46
+ v-for="country in countries.AXS"
47
+ class="country-item"
48
+ @click="selectCountry(country, 'AXS')"
49
+ >
50
+ <div class="flex items-center justify-center gap-2">
51
+ <span class="text-xs font-medium">{{
52
+ country.siteId.split("_")[1]
53
+ }}</span>
54
+ <img
55
+ class="w-5 h-4"
56
+ :src="`/flags/${country.siteId.split('_')[1].toLowerCase()}.svg`"
57
+ />
58
+ </div>
59
+ </div>
60
+ </div>
61
+ </transition>
62
+ </Teleport>
41
63
  </div>
64
+ </div>
42
65
  </template>
43
66
 
44
67
  <script setup>
45
68
  import { ref } from "vue";
46
69
  import { useUIStore } from "@/stores/ui";
47
70
  import { countries } from "@/stores/countries";
71
+ import { useDropdownPosition } from "@/composables/useDropdownPosition";
72
+ import { useClickOutside } from "@/composables/useClickOutside";
73
+
48
74
  const ui = useUIStore();
49
75
  const open = ref(false);
76
+ const dropdownRef = ref(null);
77
+
78
+ // Use composables for positioning and click outside
79
+ const { menuStyle, updatePosition } = useDropdownPosition(dropdownRef, {
80
+ offset: { x: -26, y: 4 },
81
+ minWidth: 100,
82
+ maxHeight: 208,
83
+ estimateHeight: () => {
84
+ const tmCount = countries.TM?.length || 0;
85
+ const axsCount = countries.AXS?.length || 0;
86
+ const totalItems = tmCount + axsCount + 2; // +2 for headers
87
+ return Math.min(totalItems * 32, 208);
88
+ },
89
+ });
90
+
91
+ useClickOutside(dropdownRef, () => {
92
+ if (open.value) {
93
+ open.value = false;
94
+ }
95
+ });
96
+
97
+ const toggleOpen = () => {
98
+ open.value = !open.value;
99
+ if (open.value) {
100
+ updatePosition();
101
+ }
102
+ };
103
+
104
+ const selectCountry = (country, module) => {
105
+ ui.setCurrentCountry(country, true, module);
106
+ open.value = false;
107
+ };
50
108
  </script>
51
109
 
52
110
  <style scoped>
53
111
  .special-dropdown {
54
- @apply min-w-20;
112
+ @apply min-w-20;
55
113
  }
56
114
 
57
115
  .small-dropdown {
58
- background-clip: border-box !important;
59
- border-radius: 100% !important;
60
- padding: 0;
61
- width: 3em !important;
62
- height: 3em !important;
63
- display: flex;
64
- justify-items: center;
65
- justify-content: center;
116
+ background-clip: border-box !important;
117
+ border-radius: 100% !important;
118
+ padding: 0;
119
+ width: 3em !important;
120
+ height: 3em !important;
121
+ display: flex;
122
+ justify-items: center;
123
+ justify-content: center;
124
+ }
125
+
126
+ .dropdown-content-portal {
127
+ @apply bg-dark-400 border border-dark-650 rounded-lg shadow-2xl z-50;
128
+ padding: 0.5rem;
129
+ max-height: 208px !important;
130
+ overflow-y: auto !important;
131
+ overscroll-behavior: contain !important;
132
+ touch-action: pan-y !important;
133
+ -webkit-overflow-scrolling: touch !important;
134
+ scrollbar-width: none;
135
+ -ms-overflow-style: none;
136
+ min-width: 100px;
137
+ width: 100px;
138
+ }
139
+
140
+ .dropdown-content-portal::-webkit-scrollbar {
141
+ display: none;
66
142
  }
67
143
 
68
- .dropdown-content {
69
- left: -1.25rem;
70
- background-clip: border-box !important;
71
- @apply border border-dark-650;
72
- border-width: 2px;
73
- --tw-border-opacity: 1;
74
- border-color: rgb(61 62 68 / var(--tw-border-opacity));
75
- max-height: 13rem;
144
+ .header-item {
145
+ text-align: center !important;
146
+ font-size: 0.75rem;
147
+ font-weight: 700;
148
+ color: white;
149
+ padding: 8px 4px;
150
+ cursor: default;
151
+ pointer-events: none;
152
+ margin: 4px 2px;
153
+ position: relative;
154
+ background: linear-gradient(
155
+ 135deg,
156
+ rgba(255, 255, 255, 0.08) 0%,
157
+ rgba(255, 255, 255, 0.04) 100%
158
+ );
159
+ border: 1px solid rgba(255, 255, 255, 0.1);
160
+ border-radius: 6px;
161
+ letter-spacing: 0.5px;
162
+ text-shadow: 0 1px 2px rgba(0, 0, 0, 0.3);
163
+ box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
164
+ display: flex;
165
+ align-items: center;
166
+ justify-content: center;
76
167
  }
77
168
 
78
- @media (max-width: 1024px) {
79
- .small-dropdown {
80
- @apply bg-dark-550 !important;
81
- }
169
+ .header-item:first-child {
170
+ margin-top: 2px;
171
+ }
172
+
173
+ .header-item::before {
174
+ content: "";
175
+ position: absolute;
176
+ top: 0;
177
+ left: 0;
178
+ right: 0;
179
+ height: 1px;
180
+ background: linear-gradient(
181
+ 90deg,
182
+ transparent 0%,
183
+ rgba(255, 255, 255, 0.2) 50%,
184
+ transparent 100%
185
+ );
186
+ border-radius: 6px 6px 0 0;
187
+ }
188
+
189
+ .country-item {
190
+ padding: 8px 4px;
191
+ font-size: 0.875rem;
192
+ color: white;
193
+ cursor: pointer;
194
+ border-radius: 6px;
195
+ margin: 1px 2px;
196
+ transition: all 0.15s ease;
197
+ display: flex;
198
+ align-items: center;
199
+ justify-content: center;
200
+ min-height: 32px;
201
+ }
202
+
203
+ .country-item .flex {
204
+ display: flex;
205
+ align-items: center;
206
+ justify-content: center;
207
+ gap: 6px;
208
+ width: 100%;
209
+ flex-wrap: nowrap;
210
+ }
211
+
212
+ .country-item .flex span {
213
+ font-size: 0.75rem;
214
+ font-weight: 500;
215
+ white-space: nowrap;
216
+ flex-shrink: 0;
217
+ text-align: center;
218
+ }
82
219
 
83
- .dropdown-content {
84
- @apply bg-dark-550 !important;
85
- }
220
+ .country-item .flex img {
221
+ width: 18px;
222
+ height: 14px;
223
+ flex-shrink: 0;
86
224
  }
87
225
  </style>
@@ -1,164 +1,250 @@
1
1
  <template>
2
- <div @click="toggleOpened" class="dropdown">
3
- <span class="dropdown-display">
4
- <span class="dropdown-value" :class="capitalize ? 'capitalize' : ''">
5
- {{ currentValue ? currentValue : props.default }}
2
+ <div @click="toggleOpened" class="dropdown" :class="props.variant" ref="dropdownRef">
3
+ <span class="dropdown-display">
4
+ <span class="dropdown-value" :class="capitalize ? 'capitalize' : ''">
5
+ {{ currentValue ? currentValue : props.default }}
6
+ </span>
7
+ <DownIcon class="dropdown-arrow" :class="opened ? 'rotate-180' : ''" />
8
+ </span>
9
+ <Teleport to="body">
10
+ <transition name="dropdown-fade">
11
+ <div
12
+ v-if="opened"
13
+ class="dropdown-menu-portal scrollable"
14
+ :style="menuStyle"
15
+ @click.stop
16
+ @wheel.stop
17
+ @touchmove.stop
18
+ >
19
+ <button
20
+ v-bind:key="f"
21
+ class="dropdown-item"
22
+ :class="i !== 0 ? 'border-t border-dark-650' : ''"
23
+ v-for="(f, i) in !allowDefault ? props.options : ['', ...props.options]"
24
+ @click.stop="chose(f)"
25
+ >
26
+ <span class="dropdown-item-text" :class="capitalize ? 'capitalize' : ''">
27
+ {{ f ? f : props.default }}
6
28
  </span>
7
- <DownIcon class="dropdown-arrow" :class="opened ? 'rotate-180' : ''" />
8
- </span>
9
- <transition name="dropdown-fade">
10
- <div
11
- class="dropdown-menu scrollable"
12
- v-if="opened"
13
- @click.stop
14
- @wheel.stop
15
- @touchmove.stop
16
- >
17
- <button
18
- v-bind:key="f"
19
- class="dropdown-item"
20
- :class="i !== 0 ? 'border-t border-dark-650' : ''"
21
- v-for="(f, i) in !allowDefault ? props.options : ['', ...props.options]"
22
- @click.stop="chose(f)"
23
- >
24
- <span class="dropdown-item-text" :class="capitalize ? 'capitalize' : ''">
25
- {{f ? f : props.default}}
26
- </span>
27
- <CheckmarkIcon v-if="(f || props.default) === currentValue" class="ml-2" />
28
- </button>
29
- </div>
30
- </transition>
31
- </div>
29
+ <CheckmarkIcon v-if="(f || props.default) === currentValue" class="ml-2" />
30
+ </button>
31
+ </div>
32
+ </transition>
33
+ </Teleport>
34
+ </div>
32
35
  </template>
33
36
 
34
37
  <script setup>
35
38
  import { ref, computed, watch } from "vue";
36
39
  import { DownIcon, CheckmarkIcon } from "@/components/icons";
37
40
  import { useUIStore } from "@/stores/ui";
41
+ import { useDropdownPosition } from "@/composables/useDropdownPosition";
42
+ import { useClickOutside } from "@/composables/useClickOutside";
43
+
38
44
  const ui = useUIStore();
39
45
 
40
46
  const props = defineProps({
41
- onClick: { type: Function },
42
- default: { type: String },
43
- value: { type: String },
44
- options: { type: Object },
45
- allowDefault: { type: Boolean },
46
- rightAmount: { type: String },
47
- topPadding: { type: String },
48
- capitalize: { type: Boolean }
47
+ onClick: { type: Function },
48
+ default: { type: String },
49
+ value: { type: String },
50
+ options: { type: Object },
51
+ allowDefault: { type: Boolean },
52
+ rightAmount: { type: String },
53
+ topPadding: { type: String },
54
+ capitalize: { type: Boolean },
55
+ includeAdjacentButtons: { type: Boolean, default: false },
56
+ variant: { type: String, default: 'default' }
49
57
  });
50
58
 
51
59
  const currentValue = ref(props.value);
60
+ const dropdownRef = ref(null);
52
61
 
53
62
  // Watch for changes to the value prop and update currentValue
54
- watch(() => props.value, (newValue) => {
63
+ watch(
64
+ () => props.value,
65
+ (newValue) => {
55
66
  currentValue.value = newValue;
56
- });
67
+ }
68
+ );
69
+
57
70
  const id = Math.random();
58
71
  const opened = computed(() => ui.currentDropdown === id);
59
72
 
73
+ // Use composables for positioning and click outside
74
+ const { menuStyle, updatePosition } = useDropdownPosition(dropdownRef, {
75
+ maxHeight: 200,
76
+ includeAdjacentButtons: props.includeAdjacentButtons,
77
+ estimateHeight: () => {
78
+ const optionsCount = !props.allowDefault
79
+ ? props.options?.length || 0
80
+ : (props.options?.length || 0) + 1;
81
+ return Math.min(optionsCount * 44, 200);
82
+ },
83
+ });
84
+
85
+ useClickOutside(dropdownRef, () => {
86
+ if (opened.value) {
87
+ ui.setCurrentDropdown("");
88
+ }
89
+ });
90
+
60
91
  const toggleOpened = () => {
61
- if (opened.value) ui.setCurrentDropdown("");
62
- else ui.setCurrentDropdown(id);
92
+ if (opened.value) {
93
+ ui.setCurrentDropdown("");
94
+ } else {
95
+ ui.setCurrentDropdown(id);
96
+ updatePosition();
97
+ }
63
98
  };
64
99
 
65
100
  const chose = (f) => {
66
- ui.logger.Info("Dropdown: chosen", f, "hiding...");
67
- currentValue.value = f;
101
+ ui.logger.Info("Dropdown: chosen", f, "hiding...");
102
+ currentValue.value = f;
103
+ ui.setCurrentDropdown("");
104
+ if (props.onClick) props.onClick(f);
105
+ // Ensure dropdown closes
106
+ setTimeout(() => {
68
107
  ui.setCurrentDropdown("");
69
- if (props.onClick) props.onClick(f);
70
- // Ensure dropdown closes
71
- setTimeout(() => {
72
- ui.setCurrentDropdown("");
73
- }, 50);
108
+ }, 50);
74
109
  };
75
-
76
110
  </script>
77
111
 
78
112
  <style scoped>
79
113
  .dropdown {
80
- @apply relative w-full p-2 h-12 text-white ml-auto rounded-lg ring-0;
114
+ @apply relative w-full text-white ml-auto rounded-lg ring-0;
115
+ background: linear-gradient(135deg, #2a2b30 0%, #2e2f34 100%);
116
+ border: 1px solid #3d3e44;
117
+ padding: 0.75rem;
118
+ height: 3.45em;
119
+ }
120
+
121
+ .dropdown.transparent {
122
+ background: transparent !important;
123
+ border: none !important;
124
+ box-shadow: none !important;
125
+ padding: 0 !important;
126
+ height: 40px !important;
81
127
  }
82
128
 
83
- @media (max-width: 810px) {
84
- .dropdown {
85
- @apply h-10;
86
- }
129
+ .dropdown.transparent:hover,
130
+ .dropdown.transparent:focus-within {
131
+ border: none !important;
132
+ background: transparent !important;
133
+ box-shadow: none !important;
134
+ }
135
+
136
+ .dropdown:not(.transparent):hover {
137
+ @apply border-dark-400;
138
+ }
139
+
140
+ .dropdown:not(.transparent):focus-within {
141
+ @apply border-blue-500;
142
+ }
143
+
144
+ @media (min-width: 768px) {
145
+ .dropdown {
146
+ height: 40px;
147
+ padding: 0.625rem;
148
+ }
87
149
  }
88
150
 
89
151
  .dropdown-display {
90
- @apply flex items-center justify-between z-10 w-full;
152
+ @apply flex items-center justify-between z-10 w-full h-full;
91
153
  }
92
154
 
93
155
  .dropdown-value {
94
- @apply overflow-hidden truncate min-w-0 flex-1 mr-2;
156
+ @apply overflow-hidden truncate min-w-0 flex-1 mr-2 text-sm;
157
+ }
158
+
159
+ @media (min-width: 768px) {
160
+ .dropdown-value {
161
+ font-size: 12px;
162
+ }
95
163
  }
96
164
 
97
165
  .dropdown-arrow {
98
- @apply w-4 h-4 transition-transform duration-200 flex-shrink-0;
166
+ @apply w-4 h-4 transition-all duration-300 flex-shrink-0;
99
167
  }
100
168
 
101
- .dropdown-menu {
102
- @apply absolute border border-dark-650 rounded-lg shadow-lg max-h-40 overflow-y-auto;
103
- top: 2.5rem;
104
- left: -1px;
105
- width: calc(100% + 2px);
106
- z-index: 10000;
107
- box-shadow: 0 8px 25px -5px rgba(0, 0, 0, 0.3), 0 4px 6px -2px rgba(0, 0, 0, 0.1);
108
- background-color: #2e2f34;
109
- overscroll-behavior: contain !important;
110
- touch-action: pan-y !important;
111
- -webkit-overflow-scrolling: touch !important;
112
- scrollbar-width: thin; /* Firefox - show thin scrollbar for testing */
113
- scrollbar-color: #555 #2e2f34; /* Firefox scrollbar colors */
169
+ .dropdown-menu-portal {
170
+ @apply rounded-xl shadow-2xl overflow-hidden;
171
+ background: linear-gradient(135deg, #2a2b30 0%, #2e2f34 100%);
172
+ border: 1px solid #3d3e44;
173
+ backdrop-filter: blur(12px);
174
+ box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.4), 0 10px 10px -5px rgba(0, 0, 0, 0.2),
175
+ 0 0 0 1px rgba(255, 255, 255, 0.05);
176
+ overflow-y: auto !important;
177
+ overscroll-behavior: contain !important;
178
+ touch-action: pan-y !important;
179
+ -webkit-overflow-scrolling: touch !important;
180
+ scrollbar-width: none;
181
+ -ms-overflow-style: none;
114
182
  }
115
183
 
116
- .dropdown-menu::-webkit-scrollbar {
117
- width: 8px; /* Chrome, Safari, Edge - show scrollbar for testing */
184
+ .dropdown-menu-portal::-webkit-scrollbar {
185
+ display: none;
118
186
  }
119
187
 
120
- .dropdown-menu::-webkit-scrollbar-track {
121
- background: #2e2f34;
188
+ .dropdown-item {
189
+ @apply cursor-pointer text-left w-full text-white transition-all duration-200 flex items-center justify-between;
190
+ padding: 0.75rem 1rem;
191
+ font-size: 0.875rem;
192
+ font-weight: 500;
193
+ border-bottom: 1px solid rgba(61, 62, 68, 0.3);
122
194
  }
123
195
 
124
- .dropdown-menu::-webkit-scrollbar-thumb {
125
- background: #555;
126
- border-radius: 4px;
196
+ .dropdown-item:last-child {
197
+ border-bottom: none;
127
198
  }
128
199
 
129
- .dropdown-menu::-webkit-scrollbar-thumb:hover {
130
- background: #777;
200
+ .dropdown-item:hover {
201
+ @apply bg-dark-600;
202
+ color: #ffffff;
131
203
  }
132
204
 
133
- .dropdown-item {
134
- @apply cursor-pointer text-left w-full py-2 px-3 text-white hover:bg-dark-400 transition-all duration-150 flex items-center justify-between;
205
+ .dropdown-item:active {
206
+ @apply bg-dark-650;
207
+ box-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.4);
135
208
  }
136
209
 
137
210
  .dropdown-item:first-child {
138
- @apply rounded-t-lg;
211
+ @apply rounded-t-xl;
139
212
  }
140
213
 
141
214
  .dropdown-item:last-child {
142
- @apply rounded-b-lg;
215
+ @apply rounded-b-xl;
143
216
  }
144
217
 
145
218
  .dropdown-item-text {
146
- @apply overflow-hidden truncate pr-2;
219
+ @apply overflow-hidden truncate pr-2;
220
+ }
221
+
222
+ .dropdown-item svg {
223
+ @apply w-4 h-4;
224
+ color: #10b981;
225
+ }
226
+
227
+ .dropdown-fade-enter-active {
228
+ @apply transition-all duration-300;
147
229
  }
148
230
 
149
- /* Transition animations */
150
- .dropdown-fade-enter-active,
151
231
  .dropdown-fade-leave-active {
152
- @apply transition-all duration-200;
232
+ @apply transition-all duration-200;
233
+ }
234
+
235
+ .dropdown-fade-enter-from {
236
+ @apply opacity-0;
237
+ transform: translateY(-8px) scale(0.95);
153
238
  }
154
239
 
155
- .dropdown-fade-enter-from,
156
240
  .dropdown-fade-leave-to {
157
- @apply opacity-0 transform scale-95 -translate-y-1;
241
+ @apply opacity-0;
242
+ transform: translateY(-4px) scale(0.98);
158
243
  }
159
244
 
160
245
  .dropdown-fade-enter-to,
161
246
  .dropdown-fade-leave-from {
162
- @apply opacity-100 transform scale-100 translate-y-0;
247
+ @apply opacity-100;
248
+ transform: translateY(0) scale(1);
163
249
  }
164
- </style>
250
+ </style>