@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,64 @@
1
+ <template lang="pug">
2
+ div(v-if="show")
3
+ .page-modal-backdrop
4
+ .page-modal.custom-scrollbar-y(
5
+ ref="modalEl"
6
+ )
7
+ .modal-dialog
8
+ slot
9
+ </template>
10
+
11
+ <script lang="ts">
12
+ export default defineComponent({
13
+ name: "SLPageModal",
14
+ components: {},
15
+ props: {
16
+ show: {
17
+ type: Boolean,
18
+ default: false,
19
+ },
20
+ },
21
+ setup(props) {
22
+ const { show } = toRefs(props);
23
+
24
+ const modalEl = shallowRef<HTMLElement | null>(null);
25
+ const modalScrollbarEl = shallowRef<HTMLElement | null>(null);
26
+
27
+ watch(show, () => {
28
+ const body = document.querySelector("body");
29
+ if (!body) return;
30
+
31
+ if (show.value) {
32
+ body.classList.add("popup");
33
+ } else {
34
+ body.classList.remove("popup");
35
+ }
36
+ });
37
+
38
+ onBeforeUnmount(() => {
39
+ const body = document.querySelector("body");
40
+ if (body) {
41
+ body.classList.remove("popup");
42
+ }
43
+ });
44
+
45
+ return {
46
+ modalEl,
47
+ modalScrollbarEl,
48
+ };
49
+ },
50
+ });
51
+ </script>
52
+
53
+ <style lang="stylus">
54
+ .page-modal-backdrop
55
+ @apply fixed top-0 left-0 w-full h-full s('bg-white/70') backdrop-blur-8
56
+
57
+ .page-modal
58
+ @apply fixed top-0 left-0 w-full h-full overflow-auto
59
+
60
+ .modal-dialog
61
+ @apply flex items-center justify-center relative m-8 rounded-lg bg-white shadow-popup-lg overflow-hidden
62
+ width calc(100% - theme('spacing.8') * 2)
63
+ height calc(100% - theme('spacing.8') * 2)
64
+ </style>
@@ -0,0 +1,192 @@
1
+ <template lang="pug">
2
+ .pagination
3
+ .pagination-info(v-if="showInfo")
4
+ span 每頁
5
+ SLSelect(
6
+ size='sm'
7
+ placeholder=''
8
+ :modelValue='perPage'
9
+ :options='perPageOptions'
10
+ placement='bottom'
11
+ @update:modelValue='onChangedPerPage'
12
+ )
13
+ span 筆, 共 {{ total }} 筆
14
+ .pagination-items
15
+ a(
16
+ v-if="page > 1 && total > 0 && totalPages > 5"
17
+ href="javascript:;"
18
+ @click="gotoPage(1)"
19
+ ).pagination-item.pagination-item--icon
20
+ SLIcon(icon="angle-double-left")
21
+ a(
22
+ v-if="page > 1 && total > 0"
23
+ href="javascript:;"
24
+ @click="backwardPage"
25
+ ).pagination-item.pagination-item--icon
26
+ SLIcon(icon="chevron-left")
27
+ a(
28
+ v-for="item, i in pageItems"
29
+ href="javascript:;"
30
+ :class="{ active: page === item, more: item === '...' }"
31
+ :key="`${item}${i + 1}`"
32
+ @click="gotoPage(item)"
33
+ ).pagination-item {{ item }}
34
+ a(
35
+ v-if="page < totalPages && total > 0"
36
+ href="javascript:;"
37
+ @click="forwardPage"
38
+ ).pagination-item.pagination-item--icon
39
+ SLIcon(icon="chevron-right")
40
+ a(
41
+ v-if="page < totalPages && total > 0 && totalPages > 5"
42
+ href="javascript:;"
43
+ @click="gotoPage(totalPages)"
44
+ ).pagination-item.pagination-item--icon
45
+ SLIcon(icon="angle-double-right")
46
+ </template>
47
+
48
+ <script lang="ts">
49
+ export default defineComponent({
50
+ name: "SLPagination",
51
+ components: {},
52
+ props: {
53
+ total: {
54
+ type: Number,
55
+ default: 0,
56
+ },
57
+ perPage: {
58
+ type: Number,
59
+ default: 8,
60
+ },
61
+ page: {
62
+ type: Number,
63
+ default: 1,
64
+ },
65
+ showInfo: {
66
+ type: Boolean,
67
+ default: false,
68
+ },
69
+ },
70
+ emits: ["update:page", "update:perPage"],
71
+ setup(props, { emit }) {
72
+ const { total, perPage, page } = toRefs(props);
73
+
74
+ const basePerPage = perPage.value;
75
+
76
+ const totalPages = computed(() => {
77
+ return Math.ceil(total.value / perPage.value);
78
+ });
79
+ const pageItems = computed(() => {
80
+ const items = [];
81
+ let from = 0;
82
+ let to = 0;
83
+ if (page.value >= 3) {
84
+ items.push("...");
85
+ }
86
+
87
+ if (page.value - 1 <= 0) {
88
+ from = 1;
89
+ to = Math.min(3, totalPages.value);
90
+ } else if (page.value + 1 > totalPages.value) {
91
+ from = Math.max(1, page.value - 2);
92
+ to = page.value;
93
+ } else {
94
+ from = Math.max(1, page.value - 1);
95
+ to = Math.min(totalPages.value, page.value + 1);
96
+ }
97
+
98
+ for (let i = from; i <= to; i += 1) {
99
+ items.push(i);
100
+ }
101
+
102
+ if (totalPages.value - page.value >= 2) {
103
+ items.push("...");
104
+ }
105
+ return items;
106
+ });
107
+ const perPageOptions = computed(() => [
108
+ { label: basePerPage, value: basePerPage },
109
+ { label: basePerPage * 2, value: basePerPage * 2 },
110
+ { label: basePerPage * 4, value: basePerPage * 4 },
111
+ ]);
112
+
113
+ function gotoPage(newPage: number | string) {
114
+ if (typeof newPage !== "number") {
115
+ return;
116
+ }
117
+
118
+ emit("update:page", newPage);
119
+ }
120
+
121
+ function backwardPage() {
122
+ gotoPage(page.value - 1);
123
+ }
124
+
125
+ function forwardPage() {
126
+ gotoPage(page.value + 1);
127
+ }
128
+
129
+ function onChangedPerPage(newPerPage: number) {
130
+ emit("update:perPage", newPerPage);
131
+ }
132
+
133
+ return {
134
+ totalPages,
135
+ pageItems,
136
+ perPageOptions,
137
+ gotoPage,
138
+ backwardPage,
139
+ forwardPage,
140
+ onChangedPerPage,
141
+ };
142
+ },
143
+ });
144
+ </script>
145
+
146
+ <style lang="stylus">
147
+ .pagination
148
+ @apply flex items-center justify-end
149
+
150
+ &-info
151
+ @apply inline-flex items-center mr-4 s('space-x-0.5') text-base text-gray-2
152
+
153
+ .custom-select
154
+ @apply s('min-w-[2.3125rem]')
155
+
156
+ .form-control
157
+ label
158
+ @apply text-center
159
+
160
+ .icon
161
+ @apply hidden
162
+
163
+ .select-options
164
+ .option-list
165
+ .option-button
166
+ @apply s('min-w-0') text-center
167
+
168
+ &-items
169
+ @apply inline-flex items-center
170
+
171
+ &-item + &-item
172
+ @apply border-l
173
+
174
+ &-item
175
+ @apply inline-flex items-center justify-center s('min-w-[1.5rem]') s('min-h-[1.5rem]') p-1 border-t border-b border-primary bg-white text-xs text-primary
176
+ @apply s('hover:text-primary-hover') s('focus:text-primary')
177
+
178
+ &--icon
179
+ @apply s('py-1.5')
180
+
181
+ &:first-child
182
+ @apply border-l rounded-l-sm
183
+
184
+ &:last-child
185
+ @apply border-r rounded-r-sm
186
+
187
+ &.active
188
+ @apply bg-primary text-white
189
+
190
+ &.more
191
+ @apply cursor-default s('hover:text-primary-hover')
192
+ </style>
@@ -0,0 +1,207 @@
1
+ <template lang="pug">
2
+ .pill-checkbox(:class="pillClass")
3
+ input(
4
+ type="checkbox"
5
+ :id="uid"
6
+ :checked="isChecked"
7
+ :value="value"
8
+ :disabled="disabled"
9
+ @change="onChanged"
10
+ )
11
+ label(:for="uid")
12
+ span {{ label }}
13
+ SLIcon(v-if="icon" :icon="icon")
14
+ </template>
15
+
16
+ <script lang="ts">
17
+ import type { PropType } from "vue";
18
+ import { isEqual } from "lodash-es";
19
+ import { getUID } from "~ui/utils";
20
+
21
+ export default defineComponent({
22
+ name: "SLPillCheckbox",
23
+ components: {},
24
+ props: {
25
+ label: {
26
+ type: String,
27
+ default: "",
28
+ },
29
+ value: {
30
+ type: [Number, String, Boolean, Object] as PropType<
31
+ number | string | boolean | object
32
+ >,
33
+ default: true,
34
+ },
35
+ modelValue: {
36
+ type: [Number, String, Boolean, Array] as PropType<
37
+ number | string | boolean | Array<any>
38
+ >,
39
+ default: false,
40
+ },
41
+ trueValue: {
42
+ type: [Number, String, Boolean] as PropType<number | string | boolean>,
43
+ default: true,
44
+ },
45
+ falseValue: {
46
+ type: [Number, String, Boolean] as PropType<number | string | boolean>,
47
+ default: false,
48
+ },
49
+ disabled: {
50
+ type: Boolean,
51
+ default: false,
52
+ },
53
+ icon: {
54
+ type: String,
55
+ default: null,
56
+ },
57
+ size: {
58
+ type: String,
59
+ default: "",
60
+ validator: (value: string) => ["", "xs"].includes(value),
61
+ },
62
+ color: {
63
+ type: String,
64
+ default: "",
65
+ validator: (value: string) =>
66
+ [
67
+ "",
68
+ "third",
69
+ "third-7",
70
+ "primary",
71
+ "primary-hover",
72
+ "secondary-line",
73
+ "blue-1",
74
+ "blue-2",
75
+ "secondary-new-300",
76
+ ].includes(value),
77
+ },
78
+ },
79
+ emits: ["update:modelValue"],
80
+ setup(props, { emit }) {
81
+ const { size, color, modelValue, value, trueValue, falseValue, disabled } =
82
+ toRefs(props);
83
+
84
+ const uid = ref<string | undefined>(undefined);
85
+
86
+ const pillClass = computed(() => {
87
+ const classes = [];
88
+ if (size.value && size.value !== "") {
89
+ classes.push(`pill-checkbox-${size.value}`);
90
+ }
91
+
92
+ if (color.value && color.value !== "") {
93
+ classes.push(`pill-checkbox-${color.value}`);
94
+ }
95
+ return classes.join(" ");
96
+ });
97
+ const isChecked = computed(() => {
98
+ if (Array.isArray(modelValue.value)) {
99
+ let found = false;
100
+ modelValue.value.forEach((v) => {
101
+ if (isEqual(v, value.value)) {
102
+ found = true;
103
+ }
104
+ });
105
+ return found;
106
+ }
107
+ return modelValue.value === trueValue.value;
108
+ });
109
+
110
+ function onChanged(e: Event) {
111
+ if (disabled.value) {
112
+ return;
113
+ }
114
+
115
+ const isChecked = (e.target as HTMLInputElement).checked;
116
+
117
+ if (Array.isArray(modelValue.value)) {
118
+ let newValue = [...modelValue.value];
119
+
120
+ if (isChecked) {
121
+ newValue.push(value.value);
122
+ } else {
123
+ newValue = newValue.filter((v) => v !== value.value);
124
+ }
125
+
126
+ emit("update:modelValue", newValue);
127
+ } else {
128
+ emit(
129
+ "update:modelValue",
130
+ isChecked ? trueValue.value : falseValue.value
131
+ );
132
+ }
133
+ }
134
+
135
+ onMounted(() => {
136
+ uid.value = getUID();
137
+ });
138
+
139
+ return {
140
+ uid,
141
+ pillClass,
142
+ isChecked,
143
+ onChanged,
144
+ };
145
+ },
146
+ });
147
+ </script>
148
+
149
+ <style lang="stylus">
150
+ .pill-checkbox
151
+ @apply inline-block relative
152
+
153
+ &.pill-checkbox-xs
154
+ label
155
+ @apply py-px text-xs
156
+
157
+ &.pill-checkbox-third
158
+ input:checked + label
159
+ @apply bg-third border-third
160
+
161
+ &.pill-checkbox-third-7
162
+ input:checked + label
163
+ @apply bg-third-7 border-third-7
164
+
165
+ &.pill-checkbox-primary
166
+ input:checked + label
167
+ @apply bg-primary border-primary
168
+
169
+ &.pill-checkbox-primary-hover
170
+ input:checked + label
171
+ @apply bg-primary-hover border-primary-hover
172
+
173
+ &.pill-checkbox-secondary-line
174
+ input:checked + label
175
+ @apply bg-secondary-line border-secondary-line
176
+
177
+ &.pill-checkbox-blue-1
178
+ input:checked + label
179
+ @apply bg-blue-1 border-blue-1
180
+
181
+ &.pill-checkbox-blue-2
182
+ input:checked + label
183
+ @apply bg-blue-2 border-blue-2
184
+
185
+ &.pill-checkbox-secondary-new-300
186
+ input:checked + label
187
+ @apply bg-secondary-new-300 border-secondary-new-300
188
+
189
+ input
190
+ @apply absolute s('left-1/2') s('top-1/2') transform s('-translate-x-1/2') s('-translate-y-1/2')
191
+
192
+ label
193
+ @apply inline-flex justify-center items-center relative s('px-1.75') s('py-0.75')
194
+ @apply bg-white border border-primary-lighter rounded-full text-sm s('leading-4.75') text-gray-2 align-top cursor-pointer
195
+
196
+ .icon
197
+ @apply hidden ml-2
198
+
199
+ input:checked + label
200
+ @apply bg-white border-white text-white font-bold
201
+
202
+ .icon
203
+ @apply inline-block
204
+
205
+ input:disabled + label
206
+ @apply bg-white border-cancel text-gray-4 font-normal cursor-default
207
+ </style>
@@ -0,0 +1,101 @@
1
+ <template lang="pug">
2
+ label.pill-label(:class="pillClass")
3
+ span {{ label }}
4
+ SLIcon(v-if="icon" :icon="icon")
5
+ </template>
6
+
7
+ <script lang="ts">
8
+ export default defineComponent({
9
+ name: "SLPillLabel",
10
+ components: {},
11
+ props: {
12
+ label: {
13
+ type: String,
14
+ default: "",
15
+ },
16
+ icon: {
17
+ type: String,
18
+ default: null,
19
+ },
20
+ size: {
21
+ type: String,
22
+ default: "",
23
+ validator: (value: string) => ["", "sm", "xs"].includes(value),
24
+ },
25
+ color: {
26
+ type: String,
27
+ default: "",
28
+ validator: (value: string) =>
29
+ [
30
+ "",
31
+ "third",
32
+ "third-7",
33
+ "primary",
34
+ "primary-hover",
35
+ "secondary-line",
36
+ "blue-1",
37
+ "blue-2",
38
+ "white",
39
+ ].includes(value),
40
+ },
41
+ },
42
+ setup(props) {
43
+ const { size, color } = toRefs(props);
44
+
45
+ const pillClass = computed(() => {
46
+ const classes = [];
47
+ if (size.value && size.value !== "") {
48
+ classes.push(`pill-label-${size.value}`);
49
+ }
50
+
51
+ if (color.value && color.value !== "") {
52
+ classes.push(`pill-label-${color.value}`);
53
+ }
54
+ return classes.join(" ");
55
+ });
56
+
57
+ return {
58
+ pillClass,
59
+ };
60
+ },
61
+ });
62
+ </script>
63
+
64
+ <style lang="stylus">
65
+ .pill-label
66
+ @apply inline-flex justify-center items-center relative s('px-1.75') s('py-0.75')
67
+ @apply bg-white border border-white rounded-full text-sm s('leading-4.75') text-white font-bold align-top whitespace-nowrap
68
+
69
+ &.pill-label-xs
70
+ @apply py-px text-xs
71
+
72
+ &.pill-label-sm
73
+ @apply py-px text-sm
74
+
75
+ &.pill-label-white
76
+ @apply text-gray-2
77
+
78
+ &.pill-label-third
79
+ @apply bg-third border-third
80
+
81
+ &.pill-label-third-7
82
+ @apply bg-third-7 border-third-7
83
+
84
+ &.pill-label-primary
85
+ @apply bg-primary border-primary
86
+
87
+ &.pill-label-primary-hover
88
+ @apply bg-primary-hover border-primary-hover
89
+
90
+ &.pill-label-secondary-line
91
+ @apply bg-secondary-line border-secondary-line
92
+
93
+ &.pill-label-blue-1
94
+ @apply bg-blue-1 border-blue-1
95
+
96
+ &.pill-label-blue-2
97
+ @apply bg-blue-2 border-blue-2
98
+
99
+ .icon
100
+ @apply inline-block ml-2
101
+ </style>
@@ -0,0 +1,158 @@
1
+ <template lang="pug">
2
+ a.popup-menu-button(
3
+ href='javascript:;'
4
+ @click='onClickedToggle'
5
+ ref='toggleEl'
6
+ )
7
+ SLIcon(icon='three-dot-v')
8
+ ClientOnly
9
+ Teleport(to="body")
10
+ .buttons-popup(ref='popupEl')
11
+ template(
12
+ v-for='button, i in buttons'
13
+ )
14
+ a.menu-button(
15
+ href='javascript:;'
16
+ @click='onClickedMenuButton(button.event)'
17
+ )
18
+ SLIcon(:icon='button.icon')
19
+ span {{ button.name }}
20
+ .menu-button-splitter(v-if='(i + 1) < buttons.length')
21
+ </template>
22
+
23
+ <script lang="ts">
24
+ import type { PropType } from "vue";
25
+ import { fromEvent } from "rxjs";
26
+ import { createPopper } from "@popperjs/core";
27
+ import type { Options as PopperOptions } from "@popperjs/core";
28
+ import type IPopupMenuButton from "~ui/interface/IPopupMenuButton";
29
+
30
+ export default defineComponent({
31
+ name: "SLPopupMenuButton",
32
+ components: {},
33
+ props: {
34
+ buttons: {
35
+ type: Array as PropType<Array<IPopupMenuButton>>,
36
+ default: () => [],
37
+ },
38
+ },
39
+ setup(_, { emit }) {
40
+ const toggleEl = shallowRef<HTMLElement | null>(null);
41
+ const popupEl = shallowRef<HTMLElement | null>(null);
42
+ const isShow = ref(false);
43
+
44
+ let popperInstance: any = null;
45
+ let clickSubscriber: any = null;
46
+
47
+ function unsubscribeClick() {
48
+ if (clickSubscriber) {
49
+ clickSubscriber.unsubscribe();
50
+ }
51
+ clickSubscriber = null;
52
+ }
53
+
54
+ function show() {
55
+ isShow.value = true;
56
+ if (popupEl.value) {
57
+ popupEl.value.setAttribute("data-show", "");
58
+ }
59
+ popperInstance.setOptions((options: PopperOptions) => ({
60
+ modifiers: [
61
+ {
62
+ name: "offset",
63
+ options: {
64
+ offset: [0, 2],
65
+ },
66
+ },
67
+ { name: "eventListeners", enabled: true },
68
+ ],
69
+ }));
70
+ popperInstance.update();
71
+
72
+ clickSubscriber = fromEvent(window, "click").subscribe((e) => {
73
+ const { target } = e;
74
+ if (toggleEl.value && !toggleEl.value.contains(target as HTMLElement)) {
75
+ hide();
76
+ }
77
+ });
78
+ }
79
+
80
+ function hide() {
81
+ isShow.value = false;
82
+ unsubscribeClick();
83
+ if (popupEl.value) {
84
+ popupEl.value.removeAttribute("data-show");
85
+ }
86
+
87
+ if (popperInstance) {
88
+ popperInstance.setOptions((options: PopperOptions) => ({
89
+ modifiers: [{ name: "eventListeners", enabled: false }],
90
+ }));
91
+ }
92
+ }
93
+
94
+ function onClickedToggle() {
95
+ if (!isShow.value) {
96
+ show();
97
+ } else {
98
+ hide();
99
+ }
100
+ }
101
+
102
+ function onClickedMenuButton(eventType: string) {
103
+ emit(eventType);
104
+ }
105
+
106
+ onMounted(() => {
107
+ nextTick(() => {
108
+ if (toggleEl.value && popupEl.value) {
109
+ popperInstance = createPopper(toggleEl.value, popupEl.value, {
110
+ placement: "bottom-end",
111
+ modifiers: [{ name: "eventListeners", enabled: false }],
112
+ });
113
+ }
114
+ });
115
+ });
116
+
117
+ onBeforeUnmount(() => {
118
+ unsubscribeClick();
119
+ if (popperInstance) {
120
+ popperInstance.destroy();
121
+ }
122
+
123
+ popperInstance = null;
124
+ });
125
+
126
+ return {
127
+ toggleEl,
128
+ popupEl,
129
+ onClickedToggle,
130
+ onClickedMenuButton,
131
+ };
132
+ },
133
+ });
134
+ </script>
135
+
136
+ <style lang="stylus">
137
+ .popup-menu-button
138
+ @apply inline-block relative
139
+
140
+ .buttons-popup
141
+ @apply z-60 flex-col items-stretch border border-primary-lighter rounded bg-white shadow-md hidden
142
+
143
+ &[data-show]
144
+ @apply flex
145
+
146
+ .menu-button
147
+ @apply inline-flex flex-row flex-nowrap items-center justify-center px-2 py-1 space-x-2 text-base text-primary whitespace-nowrap
148
+
149
+ &:first-child
150
+ @apply pt-2
151
+
152
+ &:last-child
153
+ @apply pb-2
154
+
155
+ .menu-button-splitter
156
+ @apply h-px mx-auto my-1 bg-bg
157
+ width calc(100% - theme("spacing.4"))
158
+ </style>