@necrolab/dashboard 0.4.48 → 0.4.49

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 (38) hide show
  1. package/.claude/settings.local.json +2 -1
  2. package/exit +209 -0
  3. package/index.html +1 -1
  4. package/package.json +1 -1
  5. package/public/manifest.json +8 -3
  6. package/src/assets/css/_input.scss +104 -111
  7. package/src/assets/css/_utilities.scss +441 -0
  8. package/src/assets/css/main.scss +228 -154
  9. package/src/components/Auth/LoginForm.vue +8 -8
  10. package/src/components/Editors/Account/Account.vue +156 -146
  11. package/src/components/Editors/Account/AccountCreator.vue +1 -1
  12. package/src/components/Editors/Account/AccountView.vue +13 -13
  13. package/src/components/Editors/Account/CreateAccount.vue +25 -16
  14. package/src/components/Editors/Profile/CreateProfile.vue +1 -1
  15. package/src/components/Editors/Profile/Profile.vue +1 -1
  16. package/src/components/Editors/Profile/ProfileCountryChooser.vue +83 -19
  17. package/src/components/Editors/Profile/ProfileView.vue +11 -11
  18. package/src/components/Tasks/CreateTaskAXS.vue +3 -3
  19. package/src/components/Tasks/CreateTaskTM.vue +7 -35
  20. package/src/components/Tasks/QuickSettings.vue +112 -9
  21. package/src/components/Tasks/Stats.vue +29 -25
  22. package/src/components/Tasks/Task.vue +489 -365
  23. package/src/components/Tasks/TaskView.vue +21 -23
  24. package/src/components/icons/Sandclock.vue +2 -2
  25. package/src/components/icons/Stadium.vue +1 -1
  26. package/src/components/ui/Modal.vue +37 -35
  27. package/src/components/ui/controls/CountryChooser.vue +200 -62
  28. package/src/components/ui/controls/atomic/Dropdown.vue +177 -91
  29. package/src/components/ui/controls/atomic/MultiDropdown.vue +247 -168
  30. package/src/composables/useClickOutside.js +21 -0
  31. package/src/composables/useDropdownPosition.js +174 -0
  32. package/src/stores/ui.js +5 -4
  33. package/src/views/Accounts.vue +2 -2
  34. package/src/views/Console.vue +25 -45
  35. package/src/views/Editor.vue +1194 -730
  36. package/src/views/Profiles.vue +2 -2
  37. package/src/views/Tasks.vue +170 -137
  38. package/tailwind.config.js +47 -21
@@ -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>