@necrolab/dashboard 0.4.220 → 0.5.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/.prettierrc +27 -1
- package/.vscode/extensions.json +1 -1
- package/README.md +64 -2
- package/artwork/image.png +0 -0
- package/backend/api.js +26 -24
- package/backend/auth.js +2 -2
- package/backend/batching.js +1 -1
- package/backend/endpoints.js +8 -11
- package/backend/index.js +2 -2
- package/backend/mock-data.js +27 -36
- package/backend/mock-src/classes/logger.js +5 -7
- package/backend/mock-src/classes/utils.js +3 -2
- package/backend/mock-src/ticketmaster.js +4 -4
- package/backend/validator.js +2 -2
- package/config/configs.json +0 -1
- package/dev-server.js +134 -0
- package/exit +209 -0
- package/index.html +78 -8
- package/index.js +1 -1
- package/jsconfig.json +16 -0
- package/package.json +39 -25
- package/postcss.config.js +1 -1
- package/postinstall.js +124 -20
- package/public/android-chrome-192x192.png +0 -0
- package/public/android-chrome-512x512.png +0 -0
- package/public/apple-touch-icon.png +0 -0
- package/public/favicon-16x16.png +0 -0
- package/public/favicon-32x32.png +0 -0
- package/public/favicon.ico +0 -0
- package/public/img/logo_trans.png +0 -0
- package/public/img/necro_logo.png +0 -0
- package/public/manifest.json +16 -10
- package/run +176 -9
- package/src/App.vue +498 -85
- package/src/assets/css/base/reset.scss +43 -0
- package/src/assets/css/base/scroll.scss +114 -0
- package/src/assets/css/base/typography.scss +37 -0
- package/src/assets/css/components/buttons.scss +216 -0
- package/src/assets/css/components/forms.scss +221 -0
- package/src/assets/css/components/modals.scss +13 -0
- package/src/assets/css/components/tables.scss +27 -0
- package/src/assets/css/components/toasts.scss +100 -0
- package/src/assets/css/main.scss +201 -122
- package/src/assets/img/background.svg +2 -2
- package/src/assets/img/background.svg.backup +11 -0
- package/src/assets/img/logo_trans.png +0 -0
- package/src/components/Auth/LoginForm.vue +62 -11
- package/src/components/Editors/Account/Account.vue +116 -40
- package/src/components/Editors/Account/AccountCreator.vue +88 -39
- package/src/components/Editors/Account/AccountView.vue +102 -34
- package/src/components/Editors/Account/CreateAccount.vue +80 -32
- package/src/components/Editors/Profile/CreateProfile.vue +269 -83
- package/src/components/Editors/Profile/Profile.vue +132 -47
- package/src/components/Editors/Profile/ProfileCountryChooser.vue +82 -20
- package/src/components/Editors/Profile/ProfileView.vue +89 -32
- package/src/components/Editors/TagLabel.vue +67 -6
- package/src/components/Editors/TagToggle.vue +7 -2
- package/src/components/Filter/Filter.vue +288 -71
- package/src/components/Filter/FilterPreview.vue +202 -31
- package/src/components/Filter/PriceSortToggle.vue +76 -6
- package/src/components/Table/Header.vue +1 -1
- package/src/components/Table/Row.vue +1 -1
- package/src/components/Table/Table.vue +19 -2
- package/src/components/Tasks/CheckStock.vue +6 -8
- package/src/components/Tasks/Controls/DesktopControls.vue +27 -17
- package/src/components/Tasks/Controls/MobileControls.vue +8 -45
- package/src/components/Tasks/CreateTaskAXS.vue +80 -72
- package/src/components/Tasks/CreateTaskTM.vue +95 -141
- package/src/components/Tasks/MassEdit.vue +4 -6
- package/src/components/Tasks/QuickSettings.vue +199 -30
- package/src/components/Tasks/ScrapeVenue.vue +5 -6
- package/src/components/Tasks/Stats.vue +50 -24
- package/src/components/Tasks/Task.vue +384 -179
- package/src/components/Tasks/TaskLabel.vue +2 -2
- package/src/components/Tasks/TaskView.vue +136 -48
- package/src/components/Tasks/Utilities.vue +25 -10
- package/src/components/Tasks/ViewTask.vue +321 -0
- package/src/components/icons/Bag.vue +1 -1
- package/src/components/icons/Check.vue +5 -0
- package/src/components/icons/Close.vue +21 -0
- package/src/components/icons/CloseX.vue +5 -0
- package/src/components/icons/Eye.vue +6 -0
- package/src/components/icons/Key.vue +21 -0
- package/src/components/icons/Loyalty.vue +1 -1
- package/src/components/icons/Mail.vue +2 -2
- package/src/components/icons/Pencil.vue +21 -0
- package/src/components/icons/Play.vue +2 -2
- package/src/components/icons/Profile.vue +18 -0
- package/src/components/icons/Reload.vue +4 -5
- package/src/components/icons/Sandclock.vue +2 -2
- package/src/components/icons/Sell.vue +21 -0
- package/src/components/icons/Spinner.vue +42 -0
- package/src/components/icons/SquareCheck.vue +18 -0
- package/src/components/icons/SquareUncheck.vue +18 -0
- package/src/components/icons/Stadium.vue +1 -1
- package/src/components/icons/Wildcard.vue +18 -0
- package/src/components/icons/index.js +26 -1
- package/src/components/ui/Modal.vue +107 -13
- package/src/components/ui/Navbar.vue +175 -40
- package/src/components/ui/ReconnectIndicator.vue +351 -55
- package/src/components/ui/Splash.vue +5 -35
- package/src/components/ui/controls/CountryChooser.vue +200 -62
- package/src/components/ui/controls/atomic/Checkbox.vue +119 -10
- package/src/components/ui/controls/atomic/Dropdown.vue +216 -39
- package/src/components/ui/controls/atomic/LoadingButton.vue +45 -0
- package/src/components/ui/controls/atomic/MultiDropdown.vue +300 -37
- package/src/components/ui/controls/atomic/Switch.vue +53 -25
- package/src/composables/useClickOutside.js +21 -0
- package/src/composables/useDropdownPosition.js +174 -0
- package/src/libs/Filter.js +60 -24
- package/src/registerServiceWorker.js +1 -1
- package/src/stores/connection.js +4 -4
- package/src/stores/sampleData.js +172 -199
- package/src/stores/ui.js +55 -20
- package/src/stores/utils.js +30 -4
- package/src/types/index.js +41 -0
- package/src/utils/debug.js +1 -0
- package/src/views/Accounts.vue +116 -50
- package/src/views/Console.vue +394 -79
- package/src/views/Editor.vue +1176 -123
- package/src/views/FilterBuilder.vue +528 -250
- package/src/views/Login.vue +76 -14
- package/src/views/Profiles.vue +119 -34
- package/src/views/Tasks.vue +266 -98
- package/static/offline.html +192 -50
- package/switch-branch.sh +41 -0
- package/tailwind.config.js +119 -27
- package/vite.config.js +73 -16
- package/workbox-config.cjs +63 -0
- package/ICONS.md +0 -21
- package/public/img/background.svg +0 -14
- package/public/img/logo.png +0 -0
- package/public/img/logo_icon.png +0 -0
- package/public/img/logo_icon_2.png +0 -0
- package/src/assets/css/_input.scss +0 -143
- package/src/assets/img/logo.png +0 -0
- package/src/assets/img/logo_icon.png +0 -0
- package/src/assets/img/logo_icon_2.png +0 -0
- package/vue.config.js +0 -32
- package/workbox-config.js +0 -7
|
@@ -1,31 +1,55 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<div @click="toggleOpened" class="
|
|
3
|
-
<span class="
|
|
4
|
-
<span
|
|
2
|
+
<div @click="toggleOpened" class="dropdown" ref="dropdownRef">
|
|
3
|
+
<span class="dropdown-display">
|
|
4
|
+
<span class="dropdown-value" :class="capitalize ? 'capitalize' : ''">
|
|
5
5
|
{{ displayValue }}
|
|
6
6
|
</span>
|
|
7
|
-
<
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
>
|
|
13
|
-
<div class="grid grid-rows-1">
|
|
14
|
-
<button
|
|
15
|
-
v-for="(option, i) in props.options"
|
|
16
|
-
:key="option.value"
|
|
17
|
-
:class="`cursor-pointer text-left w-full ${
|
|
18
|
-
i !== 0 ? `border-t ${props.topPadding || 'pt-3 mt-3'}` : ''
|
|
19
|
-
} border-light-300 flex justify-between items-center`"
|
|
20
|
-
@click.stop="toggleOption(option.value)"
|
|
21
|
-
>
|
|
22
|
-
<span :class="`overflow-hidden smooth-hover ${capitalize ? 'capitalize' : ''}`">
|
|
23
|
-
{{ option.label }}
|
|
24
|
-
</span>
|
|
25
|
-
<span v-if="selectedOptions.includes(option.value)" class="ml-2 scale-125"><CheckmarkIcon /></span>
|
|
26
|
-
</button>
|
|
7
|
+
<div class="dropdown-counter">
|
|
8
|
+
<span v-if="selectedOptions.length > 1" class="counter-badge">
|
|
9
|
+
{{ selectedOptions.length }}
|
|
10
|
+
</span>
|
|
11
|
+
<DownIcon class="dropdown-arrow" :class="opened ? 'rotate-180' : ''" />
|
|
27
12
|
</div>
|
|
28
|
-
</
|
|
13
|
+
</span>
|
|
14
|
+
<Teleport to="body">
|
|
15
|
+
<transition name="dropdown-fade">
|
|
16
|
+
<div
|
|
17
|
+
v-if="opened"
|
|
18
|
+
class="dropdown-menu-portal multi scrollable"
|
|
19
|
+
:style="menuStyle"
|
|
20
|
+
@click.stop
|
|
21
|
+
@wheel.stop
|
|
22
|
+
@touchmove.stop>
|
|
23
|
+
<div class="option-list scrollable">
|
|
24
|
+
<button
|
|
25
|
+
v-for="(option, i) in props.options"
|
|
26
|
+
:key="option.value"
|
|
27
|
+
class="dropdown-item"
|
|
28
|
+
:class="i !== 0 ? 'border-t border-dark-650' : ''"
|
|
29
|
+
@click.stop="toggleOption(option.value)">
|
|
30
|
+
<span class="dropdown-item-text" :class="capitalize ? 'capitalize' : ''">
|
|
31
|
+
{{ option.label }}
|
|
32
|
+
</span>
|
|
33
|
+
<CheckmarkIcon v-if="selectedOptions.includes(option.value)" class="ml-2" />
|
|
34
|
+
</button>
|
|
35
|
+
</div>
|
|
36
|
+
|
|
37
|
+
<div v-if="selectedOptions.length > 0" class="selected-summary">
|
|
38
|
+
<div class="flex items-center justify-between">
|
|
39
|
+
<div class="selected-count">
|
|
40
|
+
<span class="count-badge">
|
|
41
|
+
{{ selectedOptions.length }}
|
|
42
|
+
</span>
|
|
43
|
+
<span class="count-label">
|
|
44
|
+
item{{ selectedOptions.length === 1 ? "" : "s" }} selected
|
|
45
|
+
</span>
|
|
46
|
+
</div>
|
|
47
|
+
<button class="clear-button" @click.stop="clearAll">Clear All</button>
|
|
48
|
+
</div>
|
|
49
|
+
</div>
|
|
50
|
+
</div>
|
|
51
|
+
</transition>
|
|
52
|
+
</Teleport>
|
|
29
53
|
</div>
|
|
30
54
|
</template>
|
|
31
55
|
|
|
@@ -33,6 +57,8 @@
|
|
|
33
57
|
import { ref, computed } from "vue";
|
|
34
58
|
import { DownIcon, CheckmarkIcon } from "@/components/icons";
|
|
35
59
|
import { useUIStore } from "@/stores/ui";
|
|
60
|
+
import { useDropdownPosition } from "@/composables/useDropdownPosition";
|
|
61
|
+
import { useClickOutside } from "@/composables/useClickOutside";
|
|
36
62
|
|
|
37
63
|
const ui = useUIStore();
|
|
38
64
|
|
|
@@ -42,23 +68,64 @@ const props = defineProps({
|
|
|
42
68
|
options: { type: Array, required: true },
|
|
43
69
|
rightAmount: { type: String },
|
|
44
70
|
topPadding: { type: String },
|
|
45
|
-
capitalize: { type: Boolean }
|
|
71
|
+
capitalize: { type: Boolean },
|
|
72
|
+
includeAdjacentButtons: { type: Boolean, default: false }
|
|
46
73
|
});
|
|
47
74
|
|
|
48
75
|
const selectedOptions = ref([]);
|
|
76
|
+
const dropdownRef = ref(null);
|
|
49
77
|
const id = Math.random();
|
|
50
78
|
const opened = computed(() => ui.currentDropdown === id);
|
|
51
79
|
|
|
52
80
|
const displayValue = computed(() => {
|
|
53
81
|
if (selectedOptions.value.length === 0) {
|
|
54
|
-
return props.default;
|
|
82
|
+
return props.default || "Select options...";
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
if (selectedOptions.value.length === 1) {
|
|
86
|
+
const option = props.options.find((opt) => opt.value === selectedOptions.value[0]);
|
|
87
|
+
return option ? option.label : selectedOptions.value[0];
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
if (selectedOptions.value.length <= 2) {
|
|
91
|
+
const labels = selectedOptions.value.map((val) => {
|
|
92
|
+
const option = props.options.find((opt) => opt.value === val);
|
|
93
|
+
return option ? option.label : val;
|
|
94
|
+
});
|
|
95
|
+
return labels.join(", ");
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
const firstOption = props.options.find((opt) => opt.value === selectedOptions.value[0]);
|
|
99
|
+
const firstName = firstOption ? firstOption.label : selectedOptions.value[0];
|
|
100
|
+
return `${firstName} +${selectedOptions.value.length - 1} more`;
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
// Use composables for positioning and click outside
|
|
104
|
+
const { menuStyle, updatePosition } = useDropdownPosition(dropdownRef, {
|
|
105
|
+
maxHeight: 280,
|
|
106
|
+
includeAdjacentButtons: props.includeAdjacentButtons,
|
|
107
|
+
estimateHeight: () => {
|
|
108
|
+
const optionsCount = props.options?.length || 0;
|
|
109
|
+
const summaryHeight = selectedOptions.value.length > 0 ? 70 : 0;
|
|
110
|
+
const baseMaxHeight = selectedOptions.value.length > 0 ? 280 : 200;
|
|
111
|
+
const optionListHeight = Math.min(optionsCount * 44, 200);
|
|
112
|
+
return Math.min(optionListHeight + summaryHeight, baseMaxHeight);
|
|
113
|
+
}
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
useClickOutside(dropdownRef, () => {
|
|
117
|
+
if (opened.value) {
|
|
118
|
+
ui.setCurrentDropdown("");
|
|
55
119
|
}
|
|
56
|
-
return selectedOptions.value.join(", ");
|
|
57
120
|
});
|
|
58
121
|
|
|
59
122
|
const toggleOpened = () => {
|
|
60
|
-
if (opened.value)
|
|
61
|
-
|
|
123
|
+
if (opened.value) {
|
|
124
|
+
ui.setCurrentDropdown("");
|
|
125
|
+
} else {
|
|
126
|
+
ui.setCurrentDropdown(id);
|
|
127
|
+
updatePosition();
|
|
128
|
+
}
|
|
62
129
|
};
|
|
63
130
|
|
|
64
131
|
const toggleOption = (option) => {
|
|
@@ -68,13 +135,46 @@ const toggleOption = (option) => {
|
|
|
68
135
|
} else {
|
|
69
136
|
selectedOptions.value.splice(index, 1);
|
|
70
137
|
}
|
|
138
|
+
|
|
139
|
+
// Handle default logic
|
|
71
140
|
if (selectedOptions.value.length === 0 && props.default) {
|
|
72
141
|
selectedOptions.value = [props.default];
|
|
73
142
|
}
|
|
74
|
-
if (selectedOptions.value.length > 1 && selectedOptions.value.includes(props.default) && option !== props.default)
|
|
143
|
+
if (selectedOptions.value.length > 1 && selectedOptions.value.includes(props.default) && option !== props.default) {
|
|
75
144
|
selectedOptions.value = selectedOptions.value.filter((e) => e !== props.default);
|
|
76
|
-
else if (option
|
|
77
|
-
|
|
145
|
+
} else if (option === props.default) {
|
|
146
|
+
selectedOptions.value = [props.default];
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
if (typeof props.onSelect === "function") {
|
|
150
|
+
props.onSelect(selectedOptions.value);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// Recalculate position after selection changes
|
|
154
|
+
updatePosition();
|
|
155
|
+
};
|
|
156
|
+
|
|
157
|
+
const clearAll = () => {
|
|
158
|
+
// Default to first option instead of empty
|
|
159
|
+
if (props.options && props.options.length > 0) {
|
|
160
|
+
selectedOptions.value = [props.options[0].value];
|
|
161
|
+
if (typeof props.onSelect === "function") {
|
|
162
|
+
props.onSelect([props.options[0].value]);
|
|
163
|
+
}
|
|
164
|
+
} else if (props.default) {
|
|
165
|
+
selectedOptions.value = [props.default];
|
|
166
|
+
if (typeof props.onSelect === "function") {
|
|
167
|
+
props.onSelect([props.default]);
|
|
168
|
+
}
|
|
169
|
+
} else {
|
|
170
|
+
selectedOptions.value = [];
|
|
171
|
+
if (typeof props.onSelect === "function") {
|
|
172
|
+
props.onSelect([]);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// Recalculate position after clearing
|
|
177
|
+
updatePosition();
|
|
78
178
|
};
|
|
79
179
|
|
|
80
180
|
// Initialize with default option if provided
|
|
@@ -84,10 +184,173 @@ if (props.default && !selectedOptions.value.includes(props.default)) {
|
|
|
84
184
|
</script>
|
|
85
185
|
|
|
86
186
|
<style scoped>
|
|
87
|
-
.
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
187
|
+
.dropdown {
|
|
188
|
+
@apply relative w-full h-10 text-white ml-auto rounded-lg ring-0;
|
|
189
|
+
background: linear-gradient(135deg, oklch(0.18 0 0) 0%, oklch(0.20 0 0) 100%);
|
|
190
|
+
border: 1px solid oklch(0.26 0 0);
|
|
191
|
+
padding: 0.75rem;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
.dropdown:hover {
|
|
195
|
+
@apply border-dark-400;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
.dropdown:focus-within {
|
|
199
|
+
@apply border-blue-500;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
@media (max-width: 810px) {
|
|
203
|
+
.dropdown {
|
|
204
|
+
@apply h-10;
|
|
205
|
+
padding: 0.625rem;
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
.dropdown-display {
|
|
210
|
+
@apply flex items-center justify-between z-10;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
.dropdown-value {
|
|
214
|
+
@apply w-full overflow-hidden block truncate pr-2 text-sm;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
.dropdown-counter {
|
|
218
|
+
@apply flex items-center gap-2 absolute right-2;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
.counter-badge {
|
|
222
|
+
@apply bg-green-500 text-white text-xs font-semibold px-1.5 py-0.5 rounded-full text-center min-w-[18px] shadow-sm;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
.dropdown-arrow {
|
|
226
|
+
@apply min-w-4 min-h-4 transition-all duration-300;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
.dropdown-menu-portal {
|
|
230
|
+
@apply rounded-xl shadow-2xl overflow-hidden;
|
|
231
|
+
background: linear-gradient(135deg, oklch(0.18 0 0) 0%, oklch(0.20 0 0) 100%);
|
|
232
|
+
border: 1px solid oklch(0.26 0 0);
|
|
233
|
+
backdrop-filter: blur(12px);
|
|
234
|
+
box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.4), 0 10px 10px -5px rgba(0, 0, 0, 0.2),
|
|
235
|
+
0 0 0 1px rgba(255, 255, 255, 0.05);
|
|
236
|
+
overscroll-behavior: contain !important;
|
|
237
|
+
touch-action: pan-y !important;
|
|
238
|
+
-webkit-overflow-scrolling: touch !important;
|
|
239
|
+
scrollbar-width: thin;
|
|
240
|
+
scrollbar-color: oklch(0.31 0 0) transparent;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
.dropdown-menu-portal::-webkit-scrollbar {
|
|
244
|
+
width: 6px;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
.dropdown-menu-portal::-webkit-scrollbar-track {
|
|
248
|
+
background: transparent;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
.dropdown-menu-portal::-webkit-scrollbar-thumb {
|
|
252
|
+
background: oklch(0.31 0 0);
|
|
253
|
+
border-radius: 3px;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
.dropdown-menu-portal::-webkit-scrollbar-thumb:hover {
|
|
257
|
+
background: oklch(0.45 0 0);
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
.dropdown-menu-portal.multi .option-list {
|
|
261
|
+
@apply max-h-48 overflow-y-auto;
|
|
262
|
+
overscroll-behavior: contain !important;
|
|
263
|
+
touch-action: pan-y !important;
|
|
264
|
+
-webkit-overflow-scrolling: touch !important;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
.dropdown-item {
|
|
268
|
+
@apply cursor-pointer text-left w-full text-white transition-all duration-200 flex justify-between items-center;
|
|
269
|
+
padding: 0.75rem 1rem;
|
|
270
|
+
font-size: 0.875rem;
|
|
271
|
+
font-weight: 500;
|
|
272
|
+
border-bottom: 1px solid rgba(61, 62, 68, 0.3);
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
.dropdown-item:last-child {
|
|
276
|
+
border-bottom: none;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
.dropdown-item:hover {
|
|
280
|
+
@apply bg-dark-600;
|
|
281
|
+
color: oklch(1 0 0);
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
.dropdown-item:active {
|
|
285
|
+
@apply bg-dark-650;
|
|
286
|
+
box-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.4);
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
.dropdown-item:first-child {
|
|
290
|
+
@apply rounded-t-xl;
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
.dropdown-item:last-child {
|
|
294
|
+
@apply rounded-b-xl;
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
.dropdown-item-text {
|
|
298
|
+
@apply overflow-hidden;
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
/* Checkmark styling */
|
|
302
|
+
.dropdown-item svg {
|
|
303
|
+
@apply w-4 h-4;
|
|
304
|
+
color: oklch(0.72 0.15 145);
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
.selected-summary {
|
|
308
|
+
@apply border-t bg-dark-550 w-full px-4 py-3;
|
|
309
|
+
border-top: 1px solid rgba(61, 62, 68, 0.5);
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
.selected-count {
|
|
313
|
+
@apply flex items-center gap-2;
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
.count-badge {
|
|
317
|
+
@apply bg-green-500 text-white text-xs font-semibold px-2 py-1 rounded-full shadow-sm;
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
.count-label {
|
|
321
|
+
@apply text-xs font-medium text-light-400;
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
.clear-button {
|
|
325
|
+
@apply text-xs bg-red-500 text-white transition-colors duration-200 font-medium px-3 py-1.5 rounded-lg shadow-sm;
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
.clear-button:hover {
|
|
329
|
+
@apply bg-red-400;
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
/* Transition animations */
|
|
333
|
+
.dropdown-fade-enter-active {
|
|
334
|
+
@apply transition-all duration-300;
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
.dropdown-fade-leave-active {
|
|
338
|
+
@apply transition-all duration-200;
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
.dropdown-fade-enter-from {
|
|
342
|
+
@apply opacity-0;
|
|
343
|
+
transform: translateY(-8px) scale(0.95);
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
.dropdown-fade-leave-to {
|
|
347
|
+
@apply opacity-0;
|
|
348
|
+
transform: translateY(-4px) scale(0.98);
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
.dropdown-fade-enter-to,
|
|
352
|
+
.dropdown-fade-leave-from {
|
|
353
|
+
@apply opacity-100;
|
|
354
|
+
transform: translateY(0) scale(1);
|
|
92
355
|
}
|
|
93
356
|
</style>
|
|
@@ -1,21 +1,27 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<label class="switch">
|
|
3
|
-
<input type="checkbox" v-model="value" />
|
|
2
|
+
<label class="switch" :class="{ 'disabled': disabled }">
|
|
3
|
+
<input type="checkbox" v-model="value" :disabled="disabled" />
|
|
4
4
|
<span class="slider round"></span>
|
|
5
5
|
</label>
|
|
6
6
|
</template>
|
|
7
7
|
|
|
8
8
|
<script setup>
|
|
9
9
|
const value = defineModel();
|
|
10
|
+
const props = defineProps({
|
|
11
|
+
disabled: {
|
|
12
|
+
type: Boolean,
|
|
13
|
+
default: false
|
|
14
|
+
}
|
|
15
|
+
});
|
|
10
16
|
</script>
|
|
11
17
|
|
|
12
18
|
<style lang="scss" scoped>
|
|
13
|
-
/*
|
|
19
|
+
/* iOS-style switch */
|
|
14
20
|
.switch {
|
|
15
21
|
position: relative;
|
|
16
22
|
display: inline-block;
|
|
17
|
-
width:
|
|
18
|
-
height:
|
|
23
|
+
width: 51px;
|
|
24
|
+
height: 31px;
|
|
19
25
|
}
|
|
20
26
|
|
|
21
27
|
/* Hide default HTML checkbox */
|
|
@@ -33,35 +39,52 @@ const value = defineModel();
|
|
|
33
39
|
left: 0;
|
|
34
40
|
right: 0;
|
|
35
41
|
bottom: 0;
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
42
|
+
background-color: oklch(0.26 0 0);
|
|
43
|
+
border: 2px solid oklch(0.35 0 0);
|
|
44
|
+
transition: 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
|
39
45
|
}
|
|
40
46
|
|
|
41
47
|
.slider:before {
|
|
42
48
|
position: absolute;
|
|
43
49
|
content: "";
|
|
44
|
-
height:
|
|
45
|
-
width:
|
|
46
|
-
left:
|
|
47
|
-
bottom:
|
|
48
|
-
background-color:
|
|
49
|
-
transition: 0.
|
|
50
|
+
height: 23px;
|
|
51
|
+
width: 23px;
|
|
52
|
+
left: 2px;
|
|
53
|
+
bottom: 2px;
|
|
54
|
+
background-color: oklch(0.50 0 0);
|
|
55
|
+
transition: 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
|
56
|
+
box-shadow: 0 1px 3px oklch(0 0 0 / 0.3);
|
|
50
57
|
}
|
|
51
58
|
|
|
52
59
|
input:checked + .slider {
|
|
53
|
-
|
|
60
|
+
background-color: oklch(0.72 0.15 145);
|
|
61
|
+
border-color: oklch(0.72 0.15 145);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
input:checked + .slider:before {
|
|
65
|
+
background-color: oklch(1 0 0);
|
|
54
66
|
}
|
|
55
67
|
|
|
56
68
|
input:checked + .slider:before {
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
69
|
+
transform: translateX(20px);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
.switch.disabled {
|
|
73
|
+
pointer-events: none;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
input:disabled + .slider {
|
|
77
|
+
opacity: 0.5;
|
|
78
|
+
cursor: not-allowed;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
input:disabled + .slider:before {
|
|
82
|
+
opacity: 0.7;
|
|
60
83
|
}
|
|
61
84
|
|
|
62
85
|
/* Rounded sliders */
|
|
63
86
|
.slider.round {
|
|
64
|
-
border-radius:
|
|
87
|
+
border-radius: 31px;
|
|
65
88
|
}
|
|
66
89
|
|
|
67
90
|
.slider.round:before {
|
|
@@ -70,15 +93,20 @@ input:checked + .slider:before {
|
|
|
70
93
|
|
|
71
94
|
@media (max-width: 810px) {
|
|
72
95
|
.switch {
|
|
73
|
-
width:
|
|
74
|
-
min-width:
|
|
75
|
-
height:
|
|
96
|
+
width: 44px;
|
|
97
|
+
min-width: 44px;
|
|
98
|
+
height: 26px;
|
|
76
99
|
|
|
77
100
|
.slider:before {
|
|
78
|
-
width:
|
|
79
|
-
|
|
80
|
-
|
|
101
|
+
width: 22px;
|
|
102
|
+
height: 22px;
|
|
103
|
+
left: 2px;
|
|
104
|
+
bottom: 2px;
|
|
81
105
|
}
|
|
82
106
|
}
|
|
107
|
+
|
|
108
|
+
input:checked + .slider:before {
|
|
109
|
+
transform: translateX(18px);
|
|
110
|
+
}
|
|
83
111
|
}
|
|
84
112
|
</style>
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { onMounted, onUnmounted } from "vue";
|
|
2
|
+
|
|
3
|
+
export function useClickOutside(elementRef, callback) {
|
|
4
|
+
const handleClickOutside = (event) => {
|
|
5
|
+
if (elementRef.value && !elementRef.value.contains(event.target)) {
|
|
6
|
+
callback();
|
|
7
|
+
}
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
onMounted(() => {
|
|
11
|
+
document.addEventListener("click", handleClickOutside);
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
onUnmounted(() => {
|
|
15
|
+
document.removeEventListener("click", handleClickOutside);
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
return {
|
|
19
|
+
handleClickOutside
|
|
20
|
+
};
|
|
21
|
+
}
|