@mptw/skylens-ui 1.2.14 → 1.2.16

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.
@@ -1,409 +1,254 @@
1
1
  <template lang="pug">
2
- div(ref="el")
3
- .table(
4
- :class="{ 'table-responsive': responsive }"
5
- @mousemove="onMousemoved"
6
- )
7
- table
8
- thead
9
- slot(name="thead")
2
+ div
3
+ .table(
4
+ :class="{ 'table-responsive': responsive }"
5
+ @mousemove="onMousemoved"
6
+ )
7
+ table
8
+ thead
10
9
  tr
11
- template(v-for="field, index in fields")
10
+ template(v-for="field, index in fields" :key="field.key")
12
11
  slot(
13
- v-if="$slots[`${field.key}-header`]"
14
- :name="`${field.key}-header`"
15
- :field="field"
16
- :index="index"
17
- )
12
+ v-if="$slots[`${field.key}-header`]"
13
+ :name="`${field.key}-header`"
14
+ :field="field"
15
+ :index="index"
16
+ )
18
17
  th(
19
18
  v-else
20
19
  :style="field._style"
21
- :class="[field._class, { sortable: field.sortable, sorting: field.key === sortBy, filterable: field.filterable, filtering: filterBy !== '' }]"
20
+ :class="[field._class, { sortable: field.sortable, sorting: field.key === sortBy, 'sort-desc': sortDesc}]"
22
21
  :colspan="headerColumnSpans[index]"
23
- @click.prevent="onClickedHeaderCol(field.key)"
24
22
  :key="field.key"
25
- :name="`${field.key}-header`"
26
23
  )
27
- .inline-flex.items-center
28
- SLElementWithTitle(:title="field.tooltip")
29
- span {{ field.label ? field.label : '&nbsp;' }}
30
- SLIcon.sort-icon(
31
- v-if="field.sortable"
32
- class="ml-0.5"
33
- :icon="sortDesc ? 'descending' : 'ascending'"
34
- )
35
- SLIcon.filter-icon(
36
- v-if="field.filterable"
37
- class="ml-0.5"
38
- :icon="filterBy !== '' ? 'filter-on' : 'filter'"
39
- )
40
- tbody(:class="{ 'hoverable': hoverableRows, 'clickable': clickableRows }")
41
- template(v-for="(item, itemIndex) in items" :key="itemIndex")
42
- tr(
43
- @click="rowClicked(item, itemIndex, $event)"
44
- @mouseenter="rowMouseEntered(item, itemIndex, $event)"
45
- @mouseleave="rowMouseLeaved(item, itemIndex, $event)"
46
- :tabindex="clickableRows ? 0 : undefined"
47
- :class="[hoverableRows ? `hover:${tdHoverColor}` : '', `${hoverRowIndex === itemIndex ? tdHoverColor : ''}`, { hover: hoverRowIndex === itemIndex }]"
48
- )
49
- template(v-for="(field, index) in fields")
50
- slot(
51
- v-if="$slots[field.key]"
52
- :name="field.key"
53
- :item="item"
54
- :index="itemIndex"
55
- ).border-border
56
- td(
57
- v-else
58
- :colspan="columnSpans[index]"
59
- :key="`${itemIndex}-${field.key}`"
60
- :class='tdBorderColor'
61
- ) {{ item[field.key] }}
62
- tr(v-if="items.length === 0")
63
- td(:colspan="fields.length")
64
- div(v-if="loading", style="min-height: 200px")
65
- .flex.flex-col.items-center.py-8.px-4.space-y-3(v-else)
66
- img(src="images/commons/empty-box.png")
67
- .text-lg.text-primary-light {{ noItems }}
68
- .text-sm.text-gray-3(v-if="reason") 可能的原因:{{ reason }}
69
- SLLoading(
70
- v-if="loading"
71
- :boundaries="[{ sides: ['top'], query: 'td' }, { sides: ['bottom'], query: 'tbody' }]"
72
- )
73
- .table-header-filter-popup(ref="popupEl")
74
- .table-header-filter-popup-content
75
- a.table-header-filter-item(
76
- v-for="option in filterOptions"
77
- href="javascript:;"
78
- :class="{ active: filterBy === option.key }"
79
- :key="option.key"
80
- @click="onClickedFilter(option.key)"
81
- )
82
- .inline-flex.items-center.space-x-1
83
- SLIcon(
84
- v-if="option.icon"
85
- :icon="option.icon"
86
- :class="option._iconClass"
87
- )
88
- span(v-if="option.label") {{ option.label }}
89
- </template>
90
-
91
- <script lang="ts">
92
- import type { PropType } from "vue";
93
- import { fromEvent, Subscription } from "rxjs";
94
- import { createPopper } from "@popperjs/core";
95
- import type { Instance as PopperInstance } from "@popperjs/core";
96
- import type ITableField from "~ui/interface/ITableField";
97
-
98
- export default defineComponent({
99
- name: "SLTable",
100
- components: {},
101
- props: {
102
- items: {
103
- type: Array,
104
- default: () => [],
105
- },
106
- fields: {
107
- type: Array as PropType<Array<ITableField>>,
108
- default: () => [],
109
- },
110
- sortBy: {
111
- type: String,
112
- default: "",
113
- },
114
- sortDesc: {
115
- type: Boolean,
116
- default: false,
117
- },
118
- filterBy: {
119
- type: String,
120
- default: "",
121
- },
122
- hoverableRows: {
123
- type: Boolean,
124
- default: false,
125
- },
126
- clickableRows: {
127
- type: Boolean,
128
- default: false,
129
- },
130
- loading: {
131
- type: Boolean,
132
- default: false,
133
- },
134
- responsive: {
135
- type: Boolean,
136
- default: true,
137
- },
138
- tdBorderColor: {
139
- type: String,
140
- default: "border-border",
141
- },
142
- tdHoverColor: {
143
- type: String,
144
- default: "bg-bg",
145
- },
146
- noItems: {
147
- type: String,
148
- default: "無資料",
24
+ .inline-flex.items-center.space-x-1
25
+ SLElementWithTitle(v-if='field.info', :title="field.info")
26
+ SLIcon.text-primary(icon='info-circle')
27
+ component(:is='field.sortable ? "a" : "div"', href='javascript:;' class='inline-flex items-center', @click="field.sortable && sort(field.key)")
28
+ span {{ field.label }}
29
+ SLIcon.sort-icon.ml-2(v-if="field.sortable" :icon="sortDesc ? 'descending' : 'ascending'")
30
+ tbody(:class="{ 'hoverable': hoverableRows, 'clickable': clickableRows }")
31
+ template(v-for="(item, itemIndex) in bodyHeaderItems" :key="itemIndex")
32
+ tr.body-header-row(
33
+ @click="rowClicked(item, itemIndex, $event)"
34
+ @mouseenter="rowMouseEntered(item, itemIndex, $event)"
35
+ @mouseleave="rowMouseLeaved(item, itemIndex, $event)"
36
+ :tabindex="clickableRows ? 0 : undefined"
37
+ :class="[hoverableRows ? `hover:${tdHoverColor}` : '', `${hoverRowIndex === itemIndex ? tdHoverColor : ''}`, { hover: hoverRowIndex === itemIndex }]"
38
+ )
39
+ template(v-for="(field, index) in fields" :key="field.key")
40
+ slot(
41
+ v-if="$slots[`body-header-${field.key}`]"
42
+ :name="field.key"
43
+ :item="item"
44
+ :index="itemIndex"
45
+ ).border-border
46
+ td(
47
+ v-else
48
+ :colspan="columnSpans[index]"
49
+ :key="`${itemIndex}-${field.key}`"
50
+ :class='[tdBorderColor, field._class, { sortable: field.sortable }]'
51
+ ) {{ item[field.key] }}
52
+ template(v-for="(item, itemIndex) in items" :key="itemIndex")
53
+ tr(
54
+ @click="rowClicked(item, itemIndex, $event)"
55
+ @mouseenter="rowMouseEntered(item, itemIndex, $event)"
56
+ @mouseleave="rowMouseLeaved(item, itemIndex, $event)"
57
+ :tabindex="clickableRows ? 0 : undefined"
58
+ :class="[hoverableRows ? `hover:${tdHoverColor}` : '', `${hoverRowIndex === itemIndex ? tdHoverColor : ''}`, { hover: hoverRowIndex === itemIndex }]"
59
+ )
60
+ template(v-for="(field, index) in fields" :key="field.key")
61
+ slot(
62
+ v-if="$slots[field.key]"
63
+ :name="field.key"
64
+ :item="item"
65
+ :index="itemIndex"
66
+ ).border-border
67
+ td(
68
+ v-else
69
+ :colspan="columnSpans[index]"
70
+ :key="`${itemIndex}-${field.key}`"
71
+ :class='[tdBorderColor, field._class, { sortable: field.sortable }]'
72
+ ) {{ item[field.key] }}
73
+ tr(v-if="items.length === 0")
74
+ td(:colspan="fields.length")
75
+ .my-28.text-base.text-gray-2.text-center {{ noItems }}
76
+ SLLoading(
77
+ v-if="loading"
78
+ :boundaries="[{ sides: ['top'], query: 'td' }, { sides: ['bottom'], query: 'tbody' }]"
79
+ )
80
+ </template>
81
+
82
+ <script lang="ts">
83
+ import type { PropType } from 'vue';
84
+ import type ITableField from '@/interface/ITableField';
85
+
86
+ export default defineComponent({
87
+ name: 'SLTable',
88
+ components: {
149
89
  },
150
- reason: {
151
- type: String,
152
- default: "",
90
+ props: {
91
+ items: {
92
+ type: Array,
93
+ default: () => [],
94
+ },
95
+ bodyHeaderItems: {
96
+ type: Array,
97
+ default: () => [],
98
+ },
99
+ fields: {
100
+ type: Array as PropType<Array<ITableField>>,
101
+ default: () => [],
102
+ },
103
+ sortBy: {
104
+ type: String,
105
+ default: '',
106
+ },
107
+ sortDesc: {
108
+ type: Boolean,
109
+ default: false,
110
+ },
111
+ hoverableRows: {
112
+ type: Boolean,
113
+ default: false,
114
+ },
115
+ clickableRows: {
116
+ type: Boolean,
117
+ default: false,
118
+ },
119
+ loading: {
120
+ type: Boolean,
121
+ default: false,
122
+ },
123
+ responsive: {
124
+ type: Boolean,
125
+ default: true,
126
+ },
127
+ tdBorderColor: {
128
+ type: String,
129
+ default: 'border-border',
130
+ },
131
+ tdHoverColor: {
132
+ type: String,
133
+ default: 'bg-bg',
134
+ },
135
+ noItems: {
136
+ type: String,
137
+ default: 'No Items',
138
+ },
153
139
  },
154
- },
155
- emits: [
156
- "update:sortDesc",
157
- "update:sortBy",
158
- "update:filterBy",
159
- "row-clicked",
160
- "row-mouseentered",
161
- "row-mouseleaved",
162
- "table-mousemoved",
163
- ],
164
- setup(props, { emit }) {
165
- const { fields, sortBy, sortDesc, clickableRows, hoverableRows } =
166
- toRefs(props);
167
-
168
- const el = shallowRef<HTMLElement | null>(null);
169
- const popupEl = shallowRef<HTMLElement | null>(null);
170
- const hoverRowIndex = ref(-1);
171
- const filterOptions = ref<
172
- {
173
- key: string;
174
- label?: string;
175
- icon?: string;
176
- }[]
177
- >([]);
178
-
179
- let popperInstance: PopperInstance | null = null;
180
- let clickSubscriber: Subscription | null = null;
181
- let filterHeaderCol: HTMLElement | null = null;
182
-
183
- const headerColumnSpans = computed(() => {
184
- return fields.value.map((field) => {
185
- if (!field.colspan) return 1;
186
- return field.colspan;
140
+ setup(props, { emit }) {
141
+ const { fields, sortBy, sortDesc, clickableRows, hoverableRows } =
142
+ toRefs(props);
143
+
144
+ const hoverRowIndex = ref(-1);
145
+
146
+ const headerColumnSpans = computed(() => {
147
+ return fields.value.map((field) => {
148
+ if (!field.colspan) return 1;
149
+ return field.colspan;
150
+ });
187
151
  });
188
- });
189
- const columnSpans = computed(() => {
190
- return fields.value.map((field) => {
191
- if (!field.col) return 1;
192
- if (!field.col.span) return 1;
193
- return field.col.span;
152
+ const columnSpans = computed(() => {
153
+ return fields.value.map((field) => {
154
+ if (!field.col) return 1;
155
+ if (!field.col.span) return 1;
156
+ return field.col.span;
157
+ });
194
158
  });
195
- });
196
-
197
- function sort(fieldKey: string) {
198
- if (fieldKey === sortBy.value) {
199
- emit("update:sortDesc", !sortDesc.value);
200
- } else {
201
- emit("update:sortBy", fieldKey);
202
- }
203
- }
204
-
205
- function onClickedHeaderCol(fieldKey: string) {
206
- const field = fields.value.find((f) => f.key === fieldKey);
207
- if (!field || (!field.sortable && !field.filterable)) return;
208
-
209
- if (field.sortable) {
210
- sort(field.key);
211
- }
212
-
213
- if (field.filterable) {
214
- filterOptions.value = [...field.filterOptions];
215
- filterHeaderCol = el.value.querySelector(`th[name=${field.key}-header]`)
216
- .children[0] as HTMLElement;
217
- show();
218
- }
219
- }
220
-
221
- function rowClicked(item: any, index: number, e: Event) {
222
- if (!clickableRows.value) return;
223
-
224
- emit("row-clicked", item, index, e);
225
- e.preventDefault();
226
- }
227
-
228
- function rowMouseEntered(item: any, index: number, e: Event) {
229
- if (!hoverableRows.value) return;
230
-
231
- hoverRowIndex.value = index;
232
- emit("row-mouseentered", item, index, e);
233
- }
234
-
235
- function rowMouseLeaved(item: any, index: number, e: Event) {
236
- if (!hoverableRows.value) return;
237
-
238
- hoverRowIndex.value = -1;
239
- emit("row-mouseleaved", item, index, e);
240
- }
241
-
242
- function onMousemoved(e: Event) {
243
- if (!hoverableRows.value) return;
244
-
245
- emit("table-mousemoved", e);
246
- }
247
-
248
- function subscribeClick() {
249
- clickSubscriber = fromEvent(window, "click").subscribe((e) => {
250
- const { target } = e;
251
- if (
252
- filterHeaderCol &&
253
- popupEl.value &&
254
- !filterHeaderCol.contains(target as HTMLElement) &&
255
- !popupEl.value.contains(target as HTMLElement)
256
- ) {
257
- hide();
159
+
160
+ function sort(fieldKey: string) {
161
+ if (fieldKey === sortBy.value) {
162
+ emit('update:sortDesc', !sortDesc.value);
163
+ } else {
164
+ emit('update:sortBy', fieldKey);
258
165
  }
259
- });
260
- }
261
-
262
- function unsubscribeClick() {
263
- if (clickSubscriber) {
264
- clickSubscriber.unsubscribe();
265
166
  }
266
- clickSubscriber = null;
267
- }
268
-
269
- function show() {
270
- if (!popupEl.value || !filterHeaderCol || !popperInstance) return;
271
-
272
- popupEl.value.setAttribute("data-show", "");
273
-
274
- subscribeClick();
275
- popperInstance.state.elements.reference = filterHeaderCol;
276
- popperInstance.setOptions({
277
- modifiers: [
278
- {
279
- name: "offset",
280
- options: {
281
- offset: [0, 4],
282
- },
283
- },
284
- { name: "eventListeners", enabled: true },
285
- ],
286
- });
287
- popperInstance.update();
288
- }
289
-
290
- function hide() {
291
- if (
292
- !popupEl.value ||
293
- popupEl.value.getAttribute("data-show") === null ||
294
- !popperInstance
295
- )
296
- return;
297
-
298
- unsubscribeClick();
299
-
300
- if (popupEl.value) {
301
- popupEl.value.removeAttribute("data-show");
167
+
168
+ function rowClicked(item: any, index: number, e: Event) {
169
+ if (!clickableRows.value) return;
170
+
171
+ emit('row-clicked', item, index, e);
302
172
  }
303
-
304
- popperInstance.setOptions({
305
- modifiers: [{ name: "eventListeners", enabled: false }],
306
- });
307
- }
308
-
309
- function onClickedFilter(filterKey: string) {
310
- emit("update:filterBy", filterKey);
311
- hide();
312
- }
313
-
314
- onMounted(() => {
315
- if (el.value && popupEl.value) {
316
- popperInstance = createPopper(el.value, popupEl.value, {
317
- placement: "bottom-end",
318
- modifiers: [{ name: "eventListeners", enabled: false }],
319
- });
173
+
174
+ function rowMouseEntered(item: any, index: number, e: Event) {
175
+ if (!hoverableRows.value) return;
176
+
177
+ hoverRowIndex.value = index;
178
+ emit('row-mouseentered', item, index, e);
320
179
  }
321
- });
322
-
323
- onBeforeUnmount(() => {
324
- if (popperInstance) {
325
- popperInstance.destroy();
180
+
181
+ function rowMouseLeaved(item: any, index: number, e: Event) {
182
+ if (!hoverableRows.value) return;
183
+
184
+ hoverRowIndex.value = -1;
185
+ emit('row-mouseleaved', item, index, e);
326
186
  }
327
- popperInstance = null;
328
- });
329
-
330
- return {
331
- el,
332
- popupEl,
333
- hoverRowIndex,
334
- filterOptions,
335
- headerColumnSpans,
336
- columnSpans,
337
- onClickedHeaderCol,
338
- rowClicked,
339
- rowMouseEntered,
340
- rowMouseLeaved,
341
- onMousemoved,
342
- onClickedFilter,
343
- };
344
- },
345
- });
346
- </script>
347
-
348
- <style lang="stylus">
349
- .table
350
- @apply block relative w-full overflow-hidden
351
-
352
- &.table-responsive
353
- @apply overflow-auto
354
-
187
+
188
+ function onMousemoved(e: Event) {
189
+ if (!hoverableRows.value) return;
190
+
191
+ emit('table-mousemoved', e);
192
+ }
193
+
194
+ return {
195
+ hoverRowIndex,
196
+ headerColumnSpans,
197
+ columnSpans,
198
+ sort,
199
+ rowClicked,
200
+ rowMouseEntered,
201
+ rowMouseLeaved,
202
+ onMousemoved,
203
+ };
204
+ },
205
+ });
206
+ </script>
207
+
208
+ <style lang="stylus">
209
+ .table
210
+ @apply block relative w-full overflow-hidden
211
+
212
+ &.table-responsive
213
+ @apply overflow-auto
214
+
215
+ table
216
+ @apply table-auto
217
+
355
218
  table
356
- @apply table-auto
357
-
358
- table
359
- @apply w-full table-fixed
360
-
361
- thead
362
- tr
363
- @apply border-b border-primary-light bg-white
364
-
365
- th
366
- @apply px-2 pb-2 text-sm font-normal text-primary whitespace-nowrap
367
- &.sortable, &.filterable
368
- @apply cursor-pointer
369
- .sort-icon, .filter-icon
370
- @apply opacity-0
371
- &:hover,
372
- &.sorting
219
+ @apply w-full table-fixed
220
+
221
+ thead
222
+ tr
223
+ @apply border-b border-primary-light bg-white
224
+
225
+ th
226
+ @apply px-2 pb-2 text-base font-normal text-primary whitespace-nowrap
227
+
228
+ &.sortable
229
+ cursor pointer
373
230
  .sort-icon
374
- @apply opacity-100
375
- &.filterable
376
- .filter-icon
377
- @apply opacity-100
378
-
379
- tbody
380
- &.clickable
231
+ opacity 0
232
+ transition transform 0.2s ease, opacity 0.1s ease
233
+ &:hover,
234
+ &.sorting
235
+ .sort-icon
236
+ opacity 1
237
+
238
+ tbody
239
+ &.clickable
240
+ tr
241
+ @apply cursor-pointer
242
+
381
243
  tr
382
- @apply cursor-pointer
383
-
384
- tr
385
- td
386
- @apply p-2 border-b text-sm text-gray-2
387
-
388
- .table-header-filter-popup
389
- @apply z-10 flex border border-primary-light rounded overflow-hidden invisible pointer-events-none
390
-
391
- &[data-show]
392
- @apply visible pointer-events-auto
393
-
394
- &-content
395
- @apply flex flex-col
396
-
397
- .table-header-filter-item
398
- @apply inline-flex justify-center p-2 bg-white text-sm text-gray-3
399
- @apply s('hover:bg-bg-middle')
400
-
401
- &.active
402
- @apply bg-bg text-primary
244
+ &.body-header-row
245
+ td
246
+ @apply bg-bg-middle text-lg font-bold shadow-inline
403
247
 
404
- .icon
405
- @apply text-primary
248
+ td
249
+ @apply p-2 border-b border-opacity-50 text-base text-gray-2
406
250
 
407
- .icon
408
- @apply text-base leading-none
409
- </style>
251
+ &.text-right.sortable
252
+ @apply pr-8
253
+ </style>
254
+
@@ -1,13 +1,15 @@
1
+ import type { StyleValue } from "vue";
2
+
1
3
  export default interface ITableField {
2
4
  key: string;
3
5
  label: string;
4
- _style?: string | Object;
6
+ _style?: StyleValue;
5
7
  _class?: string | Object | Array<string | Object>;
6
8
  sortable?: boolean;
7
9
  filterable?: boolean;
8
10
  colspan?: number;
9
11
  col?: {
10
- span?:number,
12
+ span?: number,
11
13
  };
12
14
  filterOptions?: {
13
15
  key: string;
@@ -16,4 +18,5 @@ export default interface ITableField {
16
18
  _iconClass?: string;
17
19
  }[];
18
20
  tooltip?: string;
21
+ info?: string;
19
22
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@mptw/skylens-ui",
3
3
  "type": "module",
4
- "version": "1.2.14",
4
+ "version": "1.2.16",
5
5
  "main": "./nuxt.config.ts",
6
6
  "scripts": {
7
7
  "dev": "nuxi dev .playground",