@mptw/skylens-ui 1.0.0

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 (143) hide show
  1. package/.editorconfig +12 -0
  2. package/.eslintrc.cjs +4 -0
  3. package/.nuxtrc +1 -0
  4. package/.playground/app.config.ts +5 -0
  5. package/.playground/nuxt.config.ts +3 -0
  6. package/README.md +73 -0
  7. package/app.config.ts +5 -0
  8. package/app.vue +3 -0
  9. package/assets/css/fonts/MEShPlus-font-icon.eot +0 -0
  10. package/assets/css/fonts/MEShPlus-font-icon.svg +186 -0
  11. package/assets/css/fonts/MEShPlus-font-icon.ttf +0 -0
  12. package/assets/css/fonts/MEShPlus-font-icon.woff +0 -0
  13. package/assets/css/icons.css +555 -0
  14. package/assets/css/layout.styl +98 -0
  15. package/assets/css/tailwind.css +3 -0
  16. package/assets/images/commons/app-logo-white.png +0 -0
  17. package/assets/images/commons/app-logo.png +0 -0
  18. package/assets/images/commons/empty-box.png +0 -0
  19. package/assets/images/commons/watermark.svg +15 -0
  20. package/components/SLAlertModal.vue +99 -0
  21. package/components/SLBreadcrumbs.vue +42 -0
  22. package/components/SLButton.vue +301 -0
  23. package/components/SLCalendarButton.vue +259 -0
  24. package/components/SLCalendarPopup.vue +832 -0
  25. package/components/SLCard.vue +118 -0
  26. package/components/SLChart.vue +181 -0
  27. package/components/SLCheckbox.vue +179 -0
  28. package/components/SLCircleButton.vue +95 -0
  29. package/components/SLCollapsableBlock.vue +116 -0
  30. package/components/SLCollapsableMulitPillSelect.vue +92 -0
  31. package/components/SLConfirmModal.vue +122 -0
  32. package/components/SLDateInput.vue +460 -0
  33. package/components/SLDatePicker.vue +143 -0
  34. package/components/SLDistributor.vue +30 -0
  35. package/components/SLDownloadButton.vue +410 -0
  36. package/components/SLDraggable.vue +151 -0
  37. package/components/SLDropdown.vue +277 -0
  38. package/components/SLDropdownItem.vue +31 -0
  39. package/components/SLElementWithTitle.vue +181 -0
  40. package/components/SLEyeCheckbox.vue +162 -0
  41. package/components/SLFileInput.vue +101 -0
  42. package/components/SLGenderAgeSelect.vue +279 -0
  43. package/components/SLHeatMap.vue +151 -0
  44. package/components/SLHourRangeInput.vue +211 -0
  45. package/components/SLIOSSwitch.vue +235 -0
  46. package/components/SLIcon.vue +35 -0
  47. package/components/SLIconButton.vue +130 -0
  48. package/components/SLInfoTip.vue +221 -0
  49. package/components/SLInsightSitePage.vue +27 -0
  50. package/components/SLLegend.vue +76 -0
  51. package/components/SLLinearProgressBar.vue +35 -0
  52. package/components/SLLink.vue +162 -0
  53. package/components/SLLoading.vue +127 -0
  54. package/components/SLMapChart.vue +151 -0
  55. package/components/SLModal.vue +377 -0
  56. package/components/SLMonthCalendarButton.vue +378 -0
  57. package/components/SLMonthPicker.vue +151 -0
  58. package/components/SLMultiSelect.vue +121 -0
  59. package/components/SLNoData.vue +43 -0
  60. package/components/SLPageModal.vue +64 -0
  61. package/components/SLPagination.vue +192 -0
  62. package/components/SLPillCheckbox.vue +207 -0
  63. package/components/SLPillLabel.vue +101 -0
  64. package/components/SLPopupMenuButton.vue +158 -0
  65. package/components/SLProfileButton.vue +186 -0
  66. package/components/SLRadioButton.vue +97 -0
  67. package/components/SLRadioButtonGroup.vue +113 -0
  68. package/components/SLRadioGroup.vue +201 -0
  69. package/components/SLRangeInput.vue +209 -0
  70. package/components/SLRegionsMapChart.vue +151 -0
  71. package/components/SLRightSider.vue +242 -0
  72. package/components/SLSearchInput.vue +87 -0
  73. package/components/SLSelect.vue +545 -0
  74. package/components/SLSelectAllToggle.vue +118 -0
  75. package/components/SLSiderbarNav.vue +394 -0
  76. package/components/SLSiderbarNavItem.vue +204 -0
  77. package/components/SLSitePage.vue +96 -0
  78. package/components/SLSkyInsightSitePage.vue +27 -0
  79. package/components/SLSortByDropdown.vue +189 -0
  80. package/components/SLSpinIcon.vue +24 -0
  81. package/components/SLStayRangeInput.vue +306 -0
  82. package/components/SLTab.vue +159 -0
  83. package/components/SLTable.vue +409 -0
  84. package/components/SLTableTooltip.vue +111 -0
  85. package/components/SLTabs.vue +388 -0
  86. package/components/SLTextInput.vue +362 -0
  87. package/components/SLTextarea.vue +134 -0
  88. package/components/SLToast.vue +71 -0
  89. package/components/SLToggleButton.vue +78 -0
  90. package/components/SLTwoLayerMultiSelect.vue +428 -0
  91. package/components/SLTwoLayerSelect.vue +265 -0
  92. package/components/SLWarnModal.vue +75 -0
  93. package/interface/IDateRange.ts +5 -0
  94. package/interface/IHourRange.ts +4 -0
  95. package/interface/IInputOption.ts +6 -0
  96. package/interface/ILegendItem.ts +7 -0
  97. package/interface/IPopupMenuButton.ts +5 -0
  98. package/interface/IStayRange.ts +15 -0
  99. package/interface/ITableField.ts +19 -0
  100. package/interface/IWeb.ts +5 -0
  101. package/interface/NavConfigType.ts +14 -0
  102. package/interface/commons.ts +7 -0
  103. package/models/DateRange.ts +21 -0
  104. package/nuxt.config.ts +22 -0
  105. package/package.json +52 -0
  106. package/plugins/googlemaps.ts +17 -0
  107. package/plugins/v-calendar.ts +5 -0
  108. package/public/assets/files/taiwan-regions.json +7 -0
  109. package/public/assets/files/taiwan.json +24 -0
  110. package/public/assets/images/commons/default-favico.png +0 -0
  111. package/public/assets/images/dashboard/closure-button-icon.png +0 -0
  112. package/public/assets/images/dashboard/dashboard-widget-bar-chart-icon-disabled.png +0 -0
  113. package/public/assets/images/dashboard/dashboard-widget-bar-chart-icon-poi.png +0 -0
  114. package/public/assets/images/dashboard/dashboard-widget-bar-chart-icon-web.png +0 -0
  115. package/public/assets/images/dashboard/dashboard-widget-country-heatmap-chart-icon-disabled.png +0 -0
  116. package/public/assets/images/dashboard/dashboard-widget-country-heatmap-chart-icon.png +0 -0
  117. package/public/assets/images/dashboard/dashboard-widget-geo-list-icon-disabled.png +0 -0
  118. package/public/assets/images/dashboard/dashboard-widget-geo-list-icon-poi.png +0 -0
  119. package/public/assets/images/dashboard/dashboard-widget-geo-list-icon-web.png +0 -0
  120. package/public/assets/images/dashboard/dashboard-widget-heatmap-chart-icon-disabled.png +0 -0
  121. package/public/assets/images/dashboard/dashboard-widget-heatmap-chart-icon-web.png +0 -0
  122. package/public/assets/images/dashboard/dashboard-widget-line-chart-icon-disabled.png +0 -0
  123. package/public/assets/images/dashboard/dashboard-widget-line-chart-icon-poi.png +0 -0
  124. package/public/assets/images/dashboard/dashboard-widget-list-icon-disabled.png +0 -0
  125. package/public/assets/images/dashboard/dashboard-widget-list-icon-poi.png +0 -0
  126. package/public/assets/images/dashboard/dashboard-widget-list-icon-web.png +0 -0
  127. package/public/assets/images/dashboard/dashboard-widget-pie-chart-icon-disabled.png +0 -0
  128. package/public/assets/images/dashboard/dashboard-widget-pie-chart-icon-poi.png +0 -0
  129. package/public/assets/images/dashboard/dashboard-widget-pie-chart-icon-web.png +0 -0
  130. package/public/assets/images/dashboard/drag-hint-icon.svg +3 -0
  131. package/public/assets/images/dashboard/extend-button-icon.png +0 -0
  132. package/public/assets/images/dashboard/map-list-chart-icon.svg +17 -0
  133. package/public/assets/images/dashboard/map-marker-chart-icon.svg +32 -0
  134. package/public/assets/images/dashboard/pie-chart-icon.svg +4 -0
  135. package/public/assets/images/dashboard/poi-gender-age-chart-icon.svg +20 -0
  136. package/public/assets/images/dashboard/poi-nav-icon.png +0 -0
  137. package/public/assets/images/dashboard/web-gender-age-chart-icon.svg +20 -0
  138. package/public/assets/images/dashboard/web-nav-icon.png +0 -0
  139. package/tailwind.config.js +300 -0
  140. package/tsconfig.json +8 -0
  141. package/utils/constants.ts +160 -0
  142. package/utils/generateSheet.ts +36 -0
  143. package/utils/index.ts +539 -0
@@ -0,0 +1,428 @@
1
+ <template lang="pug">
2
+ .two-layer-multi-select(:class="[sizeClass, { bordered: withBorder }]" ref="selectEl")
3
+ a(
4
+ href="javascript:;"
5
+ :class="{ selected: modelValue.length > 0 }"
6
+ @click="onClickedToggle"
7
+ ref="toggleEl"
8
+ data-cy="project-button-toggle"
9
+ ).toggle-button
10
+ span.button-title {{ placeholder }}
11
+ SLIcon(icon="caret-down")
12
+ .select-menu-popup(ref="popupEl")
13
+ .select-header
14
+ SLSearchInput(
15
+ v-model='query',
16
+ :placeholder='searchPlaceholder',
17
+ :lazy='500',
18
+ :isSearch='query !== ""',
19
+ @clear="onClearQuery"
20
+ )
21
+ .select-body
22
+ .menu.menu-1
23
+ .menu-header {{ firstOptionTitle }}
24
+ .menu-body.custom-scrollbar-y
25
+ .list
26
+ .item(
27
+ v-for='option in firstOptions',
28
+ :class='{ selected: selectedFirstOption === option.key, checked: option.isCheckAll || option.isCheckSome }',
29
+ :key='option.key',
30
+ )
31
+ SLCheckbox(
32
+ label=""
33
+ size="sm"
34
+ :modelValue='option.isCheckAll'
35
+ :isCheckSome="option.isCheckSome"
36
+ @update:modelValue='(checked) => onInputedFirstOption(option.key, checked)'
37
+ )
38
+ a.item-name(
39
+ href="javascript:;"
40
+ @click="onClickedFirstOption(option.key)"
41
+ ) {{ option.name }}
42
+ .select-body-divider
43
+ .menu.menu-2
44
+ .menu-header {{ secondOptionTitle }}
45
+ .menu-body.custom-scrollbar-y
46
+ .list
47
+ .item(
48
+ v-for='option in secondOptions',
49
+ :class="{ checked: modelValue.includes(option.key) }"
50
+ :key='option.key',
51
+ )
52
+ SLCheckbox(
53
+ size="sm"
54
+ :label="option.name"
55
+ :value="option.key"
56
+ :modelValue="modelValue"
57
+ @update:modelValue='onInputedSecondOption'
58
+ )
59
+ </template>
60
+
61
+ <script lang="ts">
62
+ import type { PropType } from "vue";
63
+ import { fromEvent } from "rxjs";
64
+ import { createPopper } from "@popperjs/core";
65
+ import type { Placement } from "@popperjs/core";
66
+
67
+ export default defineComponent({
68
+ name: "SLTwoLayerMultiSelect",
69
+ components: {},
70
+ props: {
71
+ size: {
72
+ type: String,
73
+ default: "",
74
+ validator: (val: string) => ["sm", ""].includes(val),
75
+ },
76
+ withBorder: {
77
+ type: Boolean,
78
+ default: false,
79
+ },
80
+ placeholder: {
81
+ type: String,
82
+ default: "",
83
+ },
84
+ searchPlaceholder: {
85
+ type: String,
86
+ default: "",
87
+ },
88
+ placement: {
89
+ type: String as PropType<Placement>,
90
+ default: "bottom-start",
91
+ },
92
+ firstOptionTitle: {
93
+ type: String,
94
+ default: "",
95
+ },
96
+ secondOptionTitle: {
97
+ type: String,
98
+ default: "",
99
+ },
100
+ modelValue: {
101
+ type: Array as PropType<string[]>,
102
+ default: () => [],
103
+ },
104
+ options: {
105
+ type: Array as PropType<
106
+ {
107
+ key: string;
108
+ name: string;
109
+ subOptions: { key: string; name: string }[];
110
+ }[]
111
+ >,
112
+ default: () => [],
113
+ },
114
+ },
115
+ emits: ["update:modelValue", "update:selecting"],
116
+ setup(props, { emit }) {
117
+ const { size, modelValue, options, placement } = toRefs(props);
118
+
119
+ const selectEl = shallowRef<HTMLElement | null>(null);
120
+ const toggleEl = shallowRef<HTMLElement | null>(null);
121
+ const popupEl = shallowRef<HTMLElement | null>(null);
122
+ const query = ref("");
123
+ const selectedFirstOption = ref<string | null>(null);
124
+ const isSelecting = ref(false);
125
+
126
+ let popperInstance: any = null;
127
+ let clickSubscriber: any = null;
128
+
129
+ const sizeClass = computed(() => {
130
+ if (size.value === "") return "";
131
+ return `two-layer-multi-select-${size.value}`;
132
+ });
133
+ const filteredOptions = computed(() => {
134
+ if (query.value === "") {
135
+ return options.value;
136
+ }
137
+
138
+ const regexp = new RegExp(`.*${query.value}.*`, "i");
139
+ return options.value
140
+ .map((option) => ({
141
+ ...option,
142
+ subOptions: option.subOptions.filter((o) => regexp.test(o.name)),
143
+ }))
144
+ .filter((option) => option.subOptions.length > 0);
145
+ });
146
+ const firstOptions = computed(() => {
147
+ return options.value
148
+ .map((option) => {
149
+ let count = 0;
150
+
151
+ option.subOptions.forEach((o) => {
152
+ if (modelValue.value.includes(o.key)) {
153
+ count += 1;
154
+ }
155
+ });
156
+
157
+ return {
158
+ ...option,
159
+ isCheckAll: count === option.subOptions.length,
160
+ isCheckSome: count > 0 && count < option.subOptions.length,
161
+ };
162
+ })
163
+ .filter((option) => {
164
+ const found = filteredOptions.value.find((o) => o.key === option.key);
165
+ return !!found;
166
+ });
167
+ });
168
+ const secondOptions = computed(() => {
169
+ const found = filteredOptions.value.find(
170
+ (option) =>
171
+ selectedFirstOption.value && option.key === selectedFirstOption.value
172
+ );
173
+ return found?.subOptions || [];
174
+ });
175
+
176
+ function onClickedFirstOption(key: string) {
177
+ selectedFirstOption.value = key;
178
+ }
179
+
180
+ function onInputedFirstOption(key: string, checked: boolean) {
181
+ const checkedOptionSet = new Set([...modelValue.value]);
182
+ const foundOption = options.value.find((o) => o.key === key);
183
+
184
+ if (checked) {
185
+ (foundOption?.subOptions || []).forEach((o) => {
186
+ checkedOptionSet.add(o.key);
187
+ });
188
+ } else {
189
+ (foundOption?.subOptions || []).forEach((o) => {
190
+ checkedOptionSet.delete(o.key);
191
+ });
192
+ }
193
+
194
+ selectedFirstOption.value = key;
195
+
196
+ emit("update:modelValue", Array.from(checkedOptionSet.values()));
197
+ }
198
+
199
+ function onInputedSecondOption(newValue: string[]) {
200
+ emit("update:modelValue", newValue);
201
+ }
202
+
203
+ function onClearQuery() {
204
+ query.value = "";
205
+ }
206
+
207
+ function subscribeClick() {
208
+ clickSubscriber = fromEvent(window, "click").subscribe((e) => {
209
+ const { target } = e;
210
+ if (selectEl.value && !selectEl.value.contains(target as HTMLElement)) {
211
+ hide();
212
+ }
213
+ });
214
+ }
215
+
216
+ function unsubscribeClick() {
217
+ if (clickSubscriber) {
218
+ clickSubscriber.unsubscribe();
219
+ }
220
+ clickSubscriber = null;
221
+ }
222
+
223
+ function show() {
224
+ if (!popupEl.value) return;
225
+
226
+ isSelecting.value = true;
227
+
228
+ const body = document.querySelector("body");
229
+ if (body) {
230
+ body.classList.add("popup");
231
+ }
232
+
233
+ if (popupEl.value) {
234
+ popupEl.value.setAttribute("data-show", "");
235
+ }
236
+ subscribeClick();
237
+ popperInstance.setOptions({
238
+ modifiers: [
239
+ {
240
+ name: "offset",
241
+ options: {
242
+ offset: [0, 8],
243
+ },
244
+ },
245
+ { name: "eventListeners", enabled: true },
246
+ ],
247
+ });
248
+ }
249
+
250
+ function hide() {
251
+ const body = document.querySelector("body");
252
+ if (body) {
253
+ body.classList.remove("popup");
254
+ }
255
+
256
+ if (!popupEl.value || popupEl.value.getAttribute("data-show") === null)
257
+ return;
258
+
259
+ isSelecting.value = false;
260
+
261
+ unsubscribeClick();
262
+
263
+ if (popupEl.value) {
264
+ popupEl.value.removeAttribute("data-show");
265
+ }
266
+ popperInstance.setOptions({
267
+ modifiers: [{ name: "eventListeners", enabled: false }],
268
+ });
269
+
270
+ query.value = "";
271
+ }
272
+
273
+ function onClickedToggle() {
274
+ if (!popupEl.value || popupEl.value.getAttribute("data-show") !== null) {
275
+ hide();
276
+ return;
277
+ }
278
+
279
+ show();
280
+ }
281
+
282
+ watch(isSelecting, () => {
283
+ emit("update:selecting", isSelecting.value);
284
+ });
285
+
286
+ onMounted(() => {
287
+ if (!popupEl.value) return;
288
+
289
+ if (selectEl.value && popupEl.value) {
290
+ popperInstance = createPopper(selectEl.value, popupEl.value, {
291
+ placement: placement.value as Placement,
292
+ modifiers: [{ name: "eventListeners", enabled: false }],
293
+ });
294
+ }
295
+ });
296
+ onBeforeUnmount(() => {
297
+ const body = document.querySelector("body");
298
+ if (body) {
299
+ body.classList.remove("popup");
300
+ }
301
+
302
+ if (popperInstance) {
303
+ popperInstance.destroy();
304
+ }
305
+ popperInstance = null;
306
+ });
307
+
308
+ return {
309
+ selectEl,
310
+ toggleEl,
311
+ popupEl,
312
+ query,
313
+ sizeClass,
314
+ selectedFirstOption,
315
+ firstOptions,
316
+ secondOptions,
317
+ onClickedFirstOption,
318
+ onInputedFirstOption,
319
+ onInputedSecondOption,
320
+ onClearQuery,
321
+ onClickedToggle,
322
+ };
323
+ },
324
+ });
325
+ </script>
326
+
327
+ <style lang="stylus">
328
+ .two-layer-multi-select
329
+ @apply relative w-full
330
+
331
+ &.two-layer-multi-select-sm
332
+ .toggle-button
333
+ @apply text-sm
334
+
335
+ &.bordered
336
+ .select-menu-popup
337
+ @apply border-primary-light
338
+
339
+ .toggle-button
340
+ @apply relative flex items-center w-full s('p-1.75') s('space-x-1.75') border border-primary-light rounded text-base text-gray-4
341
+
342
+ &.selected
343
+ @apply text-gray-2
344
+
345
+ .button-title
346
+ @apply flex-grow
347
+
348
+ .icon
349
+ @apply flex-grow-0 flex-shrink-0 inline-flex text-xs text-gray-2 leading-none
350
+
351
+ .select-menu-popup
352
+ @apply z-10 flex flex-col s("h-67.75") s('min-w-[552px]') w-full s("pt-2.75") s("px-2.75") bg-white border border-bg rounded shadow invisible pointer-events-none
353
+
354
+ &[data-show]
355
+ @apply visible pointer-events-auto
356
+
357
+ .select-header
358
+ @apply flex-grow-0 flex-shrink-0 mb-3
359
+
360
+ .search-input
361
+ .button-group
362
+ @apply s('py-0.75')
363
+
364
+ .text-input
365
+ .input-group
366
+ .input
367
+ @apply s('py-1.25')
368
+
369
+ input
370
+ @apply text-sm
371
+
372
+ .select-body
373
+ @apply flex-grow flex overflow-hidden
374
+
375
+ .menu
376
+ @apply flex-grow flex flex-col text-base leading-normal overflow-hidden
377
+ flex 1
378
+
379
+ &:hover
380
+ flex 1.5
381
+
382
+ &-2
383
+ .menu-body
384
+ .list
385
+ .item
386
+ .checkbox
387
+ @apply flex-grow
388
+
389
+ &-header
390
+ @apply flex-grow-0 flex-shrink-0 w-full pb-2 text-primary border-b border-primary
391
+
392
+ &-body
393
+ @apply relative flex-grow
394
+
395
+ .list
396
+ .item
397
+ @apply relative flex items-center justify-start flex-nowrap s('py-1.5') px-2 s('space-x-2.5') text-sm text-gray-3
398
+ @apply s('hover:bg-bg')
399
+
400
+ &.checked
401
+ @apply text-primary
402
+
403
+ .checkbox
404
+ input:checked + label
405
+ span
406
+ @apply text-primary
407
+
408
+ &.selected
409
+ @apply bg-bg
410
+
411
+ .checkbox
412
+ @apply flex-grow-0 flex-shrink-0 inline-flex
413
+
414
+ label
415
+ @apply flex-grow flex-nowrap flex items-center
416
+
417
+ .icon
418
+ @apply flex-grow-0 flex-shrink-0
419
+
420
+ span
421
+ @apply flex-grow block truncate
422
+
423
+ &-name
424
+ @apply flex-grow block truncate
425
+
426
+ &-divider
427
+ @apply flex-grow-0 flex-shrink-0 w-px h-auto mb-3 s("mr-2.75") ml-3 bg-gray-4
428
+ </style>
@@ -0,0 +1,265 @@
1
+ <template lang="pug">
2
+ .tw-layer-select
3
+ .select-header
4
+ SLSearchInput(
5
+ :modelValue='query',
6
+ :placeholder='searchPlaceholder',
7
+ :lazy='500',
8
+ :isSearch='query !== ""',
9
+ @update:modelValue='onInputedQuery'
10
+ @clear="onClearQuery"
11
+ )
12
+ .select-body(v-if='firstLayers.length > 0')
13
+ .menu(
14
+ v-if="firstLayers.length > 1"
15
+ ref='leftMenuEl'
16
+ )
17
+ .menu-header {{ firstLayerTitle }}
18
+ .menu-body.custom-scrollbar-y
19
+ .list
20
+ a.item(
21
+ v-for='layer in firstLayers',
22
+ href='javascript:;',
23
+ :class='{ selected: selectedFirstLayerKey === layer.key }',
24
+ :key='layer.key',
25
+ @click='onClickedCustomer(layer.key)'
26
+ )
27
+ span {{ layer.name }}
28
+ //- .highlight-dot
29
+ .select-body-divider(v-if="firstLayers.length > 1")
30
+ .menu(ref='rightMenuEl')
31
+ .menu-header {{ secondLayerTitle }}
32
+ .menu-body.custom-scrollbar-y
33
+ .list
34
+ a.item(
35
+ v-for='layer in secondLayers',
36
+ href='javascript:;',
37
+ :class='{ selected: !!modelValue && modelValue.secondLayer === layer.key }',
38
+ :key='layer.key',
39
+ @click='onClickedProject(layer.key)'
40
+ )
41
+ span {{ layer.name }}
42
+ //- .highlight-dot
43
+ .select-body-empty.flex.items-center.justify-center.h-full.pb-3.text-sm.text-gray-3(v-else-if='query !== "" && firstLayers.length === 0') {{ emptyMessage }}
44
+ </template>
45
+
46
+ <script lang="ts">
47
+ import type { PropType } from "vue";
48
+ import { gsap } from "gsap";
49
+ import { ScrollToPlugin } from "gsap/ScrollToPlugin";
50
+
51
+ gsap.registerPlugin(ScrollToPlugin);
52
+
53
+ export default defineComponent({
54
+ name: "SLTwoLayerSelect",
55
+ components: {},
56
+ props: {
57
+ searchPlaceholder: {
58
+ type: String,
59
+ default: "",
60
+ },
61
+ firstLayerTitle: {
62
+ type: String,
63
+ default: "",
64
+ },
65
+ secondLayerTitle: {
66
+ type: String,
67
+ default: "",
68
+ },
69
+ modelValue: {
70
+ type: Object as PropType<{
71
+ firstLayer: string;
72
+ secondLayer: string;
73
+ } | null>,
74
+ default: null,
75
+ },
76
+ selectedFirstLayerKey: {
77
+ type: String as PropType<string | null>,
78
+ default: null,
79
+ },
80
+ query: {
81
+ type: String,
82
+ default: "",
83
+ },
84
+ firstLayers: {
85
+ type: Array as PropType<Array<{ key: string; name: string }>>,
86
+ default: () => [],
87
+ },
88
+ secondLayers: {
89
+ type: Array as PropType<Array<{ key: string; name: string }>>,
90
+ default: () => [],
91
+ },
92
+ emptyMessage: {
93
+ type: String,
94
+ default: "",
95
+ },
96
+ },
97
+ emits: ["search", "selectFirstLayer", "update:modelValue"],
98
+ setup(props, { emit }) {
99
+ const { modelValue, firstLayers, secondLayers, selectedFirstLayerKey } =
100
+ toRefs(props);
101
+
102
+ const leftMenuEl = shallowRef<HTMLElement | null>(null);
103
+ const rightMenuEl = shallowRef<HTMLElement | null>(null);
104
+
105
+ function onInputedQuery(value: string) {
106
+ emit("search", value);
107
+ }
108
+
109
+ function onClearQuery() {
110
+ emit("search", "");
111
+ }
112
+
113
+ function onClickedCustomer(firstLayerKey: string) {
114
+ emit("selectFirstLayer", firstLayerKey);
115
+ }
116
+
117
+ function onClickedProject(secondLayerKey: string) {
118
+ emit("update:modelValue", {
119
+ firstLayer: selectedFirstLayerKey.value,
120
+ secondLayer: secondLayerKey,
121
+ });
122
+ }
123
+
124
+ function scrollToValue() {
125
+ const leftMenuBody = leftMenuEl.value?.querySelector(".menu-body");
126
+ const rightMenuBody = rightMenuEl.value?.querySelector(".menu-body");
127
+
128
+ if (!modelValue.value) {
129
+ if (leftMenuBody) {
130
+ gsap.to(leftMenuBody, { duration: 0.5, scrollTo: 0 });
131
+ }
132
+
133
+ if (rightMenuBody) {
134
+ gsap.to(rightMenuBody, { duration: 0.5, scrollTo: 0 });
135
+ }
136
+ return;
137
+ }
138
+
139
+ const { firstLayer, secondLayer } = modelValue.value;
140
+ const firstLayerIndex = firstLayers.value.findIndex(
141
+ (l) => l.key === firstLayer
142
+ );
143
+ const secondLayerKey = secondLayers.value.findIndex(
144
+ (l) => l.key === secondLayer
145
+ );
146
+ const firstLayerItems = leftMenuEl.value?.querySelectorAll(".item");
147
+ const secondLayerItems = rightMenuEl.value?.querySelectorAll(".item");
148
+
149
+ if (!firstLayerItems || !secondLayerItems) {
150
+ return;
151
+ }
152
+
153
+ const selectedFirstLayerItem = firstLayerItems[firstLayerIndex];
154
+ const selectedSecondLayerItem = secondLayerItems[secondLayerKey];
155
+
156
+ if (selectedFirstLayerItem && leftMenuBody) {
157
+ gsap.to(leftMenuBody, {
158
+ duration: 0.5,
159
+ scrollTo: selectedFirstLayerItem,
160
+ });
161
+ }
162
+
163
+ if (selectedSecondLayerItem && rightMenuBody) {
164
+ gsap.to(rightMenuBody, {
165
+ duration: 0.5,
166
+ scrollTo: selectedSecondLayerItem,
167
+ });
168
+ }
169
+ }
170
+
171
+ watch(modelValue, () => {
172
+ scrollToValue();
173
+ });
174
+
175
+ watch(firstLayers, () => {
176
+ if (firstLayers.value.length === 1) {
177
+ const [firstLayer] = firstLayers.value;
178
+ onClickedCustomer(firstLayer.key);
179
+ } else {
180
+ onClickedCustomer(modelValue.value?.firstLayer || "");
181
+ }
182
+ nextTick(() => {
183
+ const leftMenuBody = leftMenuEl.value?.querySelector(".menu-body");
184
+ if (leftMenuBody) {
185
+ gsap.to(leftMenuBody, { duration: 0.5, scrollTo: 0 });
186
+ }
187
+ });
188
+ });
189
+ watch(secondLayers, () => {
190
+ nextTick(() => {
191
+ const rightMenuBody = rightMenuEl.value?.querySelector(".menu-body");
192
+ if (rightMenuBody) {
193
+ gsap.to(rightMenuBody, { duration: 0.5, scrollTo: 0 });
194
+ }
195
+ });
196
+ });
197
+
198
+ onMounted(() => {
199
+ nextTick(() => {
200
+ scrollToValue();
201
+ });
202
+ });
203
+
204
+ return {
205
+ leftMenuEl,
206
+ rightMenuEl,
207
+ onInputedQuery,
208
+ onClearQuery,
209
+ onClickedCustomer,
210
+ onClickedProject,
211
+ scrollToValue,
212
+ };
213
+ },
214
+ });
215
+ </script>
216
+
217
+ <style lang="stylus">
218
+ .tw-layer-select
219
+ @apply flex flex-col s("h-67.75") s("pt-2.75") s("px-2.75") bg-white border border-bg rounded shadow
220
+ width 552px
221
+
222
+ .select-header
223
+ @apply flex-grow-0 flex-shrink-0 mb-3
224
+
225
+ .search-input
226
+ .button-group
227
+ @apply s('py-0.75')
228
+
229
+ .text-input
230
+ .input-group
231
+ .input
232
+ @apply s('py-1.25')
233
+
234
+ input
235
+ @apply text-sm
236
+
237
+ .select-body
238
+ @apply flex-grow flex overflow-hidden
239
+
240
+ .menu
241
+ @apply flex-grow flex flex-col text-base leading-normal overflow-hidden
242
+ flex 1
243
+
244
+ &:hover
245
+ flex 1.5
246
+
247
+ &-header
248
+ @apply flex-grow-0 flex-shrink-0 w-full pb-2 text-primary border-b border-primary
249
+
250
+ &-body
251
+ @apply relative flex-grow
252
+
253
+ .list
254
+ .item
255
+ @apply relative block s('py-1.5') px-2 s("hover:bg-bg-middle") text-sm text-gray-3 truncate
256
+
257
+ &.selected
258
+ @apply bg-bg text-primary
259
+
260
+ .highlight-dot
261
+ @apply absolute top-0 left-0 s('w-1.5') s('h-1.5') mt-2 ml-1 bg-red rounded-full
262
+
263
+ &-divider
264
+ @apply flex-grow-0 flex-shrink-0 w-px h-auto mb-3 s("mr-2.75") ml-3 bg-gray-4
265
+ </style>