@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.
Files changed (140) hide show
  1. package/.prettierrc +27 -1
  2. package/.vscode/extensions.json +1 -1
  3. package/README.md +64 -2
  4. package/artwork/image.png +0 -0
  5. package/backend/api.js +26 -24
  6. package/backend/auth.js +2 -2
  7. package/backend/batching.js +1 -1
  8. package/backend/endpoints.js +8 -11
  9. package/backend/index.js +2 -2
  10. package/backend/mock-data.js +27 -36
  11. package/backend/mock-src/classes/logger.js +5 -7
  12. package/backend/mock-src/classes/utils.js +3 -2
  13. package/backend/mock-src/ticketmaster.js +4 -4
  14. package/backend/validator.js +2 -2
  15. package/config/configs.json +0 -1
  16. package/dev-server.js +134 -0
  17. package/exit +209 -0
  18. package/index.html +78 -8
  19. package/index.js +1 -1
  20. package/jsconfig.json +16 -0
  21. package/package.json +39 -25
  22. package/postcss.config.js +1 -1
  23. package/postinstall.js +124 -20
  24. package/public/android-chrome-192x192.png +0 -0
  25. package/public/android-chrome-512x512.png +0 -0
  26. package/public/apple-touch-icon.png +0 -0
  27. package/public/favicon-16x16.png +0 -0
  28. package/public/favicon-32x32.png +0 -0
  29. package/public/favicon.ico +0 -0
  30. package/public/img/logo_trans.png +0 -0
  31. package/public/img/necro_logo.png +0 -0
  32. package/public/manifest.json +16 -10
  33. package/run +176 -9
  34. package/src/App.vue +498 -85
  35. package/src/assets/css/base/reset.scss +43 -0
  36. package/src/assets/css/base/scroll.scss +114 -0
  37. package/src/assets/css/base/typography.scss +37 -0
  38. package/src/assets/css/components/buttons.scss +216 -0
  39. package/src/assets/css/components/forms.scss +221 -0
  40. package/src/assets/css/components/modals.scss +13 -0
  41. package/src/assets/css/components/tables.scss +27 -0
  42. package/src/assets/css/components/toasts.scss +100 -0
  43. package/src/assets/css/main.scss +201 -122
  44. package/src/assets/img/background.svg +2 -2
  45. package/src/assets/img/background.svg.backup +11 -0
  46. package/src/assets/img/logo_trans.png +0 -0
  47. package/src/components/Auth/LoginForm.vue +62 -11
  48. package/src/components/Editors/Account/Account.vue +116 -40
  49. package/src/components/Editors/Account/AccountCreator.vue +88 -39
  50. package/src/components/Editors/Account/AccountView.vue +102 -34
  51. package/src/components/Editors/Account/CreateAccount.vue +80 -32
  52. package/src/components/Editors/Profile/CreateProfile.vue +269 -83
  53. package/src/components/Editors/Profile/Profile.vue +132 -47
  54. package/src/components/Editors/Profile/ProfileCountryChooser.vue +82 -20
  55. package/src/components/Editors/Profile/ProfileView.vue +89 -32
  56. package/src/components/Editors/TagLabel.vue +67 -6
  57. package/src/components/Editors/TagToggle.vue +7 -2
  58. package/src/components/Filter/Filter.vue +288 -71
  59. package/src/components/Filter/FilterPreview.vue +202 -31
  60. package/src/components/Filter/PriceSortToggle.vue +76 -6
  61. package/src/components/Table/Header.vue +1 -1
  62. package/src/components/Table/Row.vue +1 -1
  63. package/src/components/Table/Table.vue +19 -2
  64. package/src/components/Tasks/CheckStock.vue +6 -8
  65. package/src/components/Tasks/Controls/DesktopControls.vue +27 -17
  66. package/src/components/Tasks/Controls/MobileControls.vue +8 -45
  67. package/src/components/Tasks/CreateTaskAXS.vue +80 -72
  68. package/src/components/Tasks/CreateTaskTM.vue +95 -141
  69. package/src/components/Tasks/MassEdit.vue +4 -6
  70. package/src/components/Tasks/QuickSettings.vue +199 -30
  71. package/src/components/Tasks/ScrapeVenue.vue +5 -6
  72. package/src/components/Tasks/Stats.vue +50 -24
  73. package/src/components/Tasks/Task.vue +384 -179
  74. package/src/components/Tasks/TaskLabel.vue +2 -2
  75. package/src/components/Tasks/TaskView.vue +136 -48
  76. package/src/components/Tasks/Utilities.vue +25 -10
  77. package/src/components/Tasks/ViewTask.vue +321 -0
  78. package/src/components/icons/Bag.vue +1 -1
  79. package/src/components/icons/Check.vue +5 -0
  80. package/src/components/icons/Close.vue +21 -0
  81. package/src/components/icons/CloseX.vue +5 -0
  82. package/src/components/icons/Eye.vue +6 -0
  83. package/src/components/icons/Key.vue +21 -0
  84. package/src/components/icons/Loyalty.vue +1 -1
  85. package/src/components/icons/Mail.vue +2 -2
  86. package/src/components/icons/Pencil.vue +21 -0
  87. package/src/components/icons/Play.vue +2 -2
  88. package/src/components/icons/Profile.vue +18 -0
  89. package/src/components/icons/Reload.vue +4 -5
  90. package/src/components/icons/Sandclock.vue +2 -2
  91. package/src/components/icons/Sell.vue +21 -0
  92. package/src/components/icons/Spinner.vue +42 -0
  93. package/src/components/icons/SquareCheck.vue +18 -0
  94. package/src/components/icons/SquareUncheck.vue +18 -0
  95. package/src/components/icons/Stadium.vue +1 -1
  96. package/src/components/icons/Wildcard.vue +18 -0
  97. package/src/components/icons/index.js +26 -1
  98. package/src/components/ui/Modal.vue +107 -13
  99. package/src/components/ui/Navbar.vue +175 -40
  100. package/src/components/ui/ReconnectIndicator.vue +351 -55
  101. package/src/components/ui/Splash.vue +5 -35
  102. package/src/components/ui/controls/CountryChooser.vue +200 -62
  103. package/src/components/ui/controls/atomic/Checkbox.vue +119 -10
  104. package/src/components/ui/controls/atomic/Dropdown.vue +216 -39
  105. package/src/components/ui/controls/atomic/LoadingButton.vue +45 -0
  106. package/src/components/ui/controls/atomic/MultiDropdown.vue +300 -37
  107. package/src/components/ui/controls/atomic/Switch.vue +53 -25
  108. package/src/composables/useClickOutside.js +21 -0
  109. package/src/composables/useDropdownPosition.js +174 -0
  110. package/src/libs/Filter.js +60 -24
  111. package/src/registerServiceWorker.js +1 -1
  112. package/src/stores/connection.js +4 -4
  113. package/src/stores/sampleData.js +172 -199
  114. package/src/stores/ui.js +55 -20
  115. package/src/stores/utils.js +30 -4
  116. package/src/types/index.js +41 -0
  117. package/src/utils/debug.js +1 -0
  118. package/src/views/Accounts.vue +116 -50
  119. package/src/views/Console.vue +394 -79
  120. package/src/views/Editor.vue +1176 -123
  121. package/src/views/FilterBuilder.vue +528 -250
  122. package/src/views/Login.vue +76 -14
  123. package/src/views/Profiles.vue +119 -34
  124. package/src/views/Tasks.vue +266 -98
  125. package/static/offline.html +192 -50
  126. package/switch-branch.sh +41 -0
  127. package/tailwind.config.js +119 -27
  128. package/vite.config.js +73 -16
  129. package/workbox-config.cjs +63 -0
  130. package/ICONS.md +0 -21
  131. package/public/img/background.svg +0 -14
  132. package/public/img/logo.png +0 -0
  133. package/public/img/logo_icon.png +0 -0
  134. package/public/img/logo_icon_2.png +0 -0
  135. package/src/assets/css/_input.scss +0 -143
  136. package/src/assets/img/logo.png +0 -0
  137. package/src/assets/img/logo_icon.png +0 -0
  138. package/src/assets/img/logo_icon_2.png +0 -0
  139. package/vue.config.js +0 -32
  140. package/workbox-config.js +0 -7
@@ -1,31 +1,55 @@
1
1
  <template>
2
- <div @click="toggleOpened" class="relative dropdown w-full p-2 h-10 text-white ml-auto rounded ring-0 px-3.5">
3
- <span class="gap-3 justify-between items-center z-inf">
4
- <span style="width: full" :class="`overflow-hidden block ${capitalize ? 'capitalize' : ''}`">
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
- <DownIcon :class="`absolute ${props.rightAmount || 'right-3'} top-3.5`" />
8
- </span>
9
- <div
10
- class="dropdown-content z-inf custom-dropdown-content max-h-40 overflow-y-auto hidden-scrollbars"
11
- v-if="opened"
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
- </div>
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) ui.setCurrentDropdown("");
61
- else ui.setCurrentDropdown(id);
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 == props.default) selectedOptions.value = [props.default];
77
- if (typeof props.onSelect === "function") props.onSelect(selectedOptions.value);
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
- .custom-dropdown-content {
88
- top: 2.5rem !important;
89
- @apply border border-light-300 px-3.5;
90
- left: -1px !important;
91
- width: calc(100% + 2px);
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
- /* The switch - the box around the slider */
19
+ /* iOS-style switch */
14
20
  .switch {
15
21
  position: relative;
16
22
  display: inline-block;
17
- width: 60px;
18
- height: 30px;
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
- transition: 0.2s;
37
- opacity: 0.4;
38
- @apply border border-white;
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: 20px;
45
- width: 20px;
46
- left: 4px;
47
- bottom: 4px;
48
- background-color: white;
49
- transition: 0.2s;
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
- opacity: 1;
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
- -webkit-transform: translateX(26px);
58
- -ms-transform: translateX(26px);
59
- transform: translateX(26px);
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: 34px;
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: 50px;
74
- min-width: 50px;
75
- height: 22px;
96
+ width: 44px;
97
+ min-width: 44px;
98
+ height: 26px;
76
99
 
77
100
  .slider:before {
78
- width: 15px;
79
- top: 3px;
80
- height: 15px;
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
+ }