@mptw/skylens-ui 1.0.0 → 1.1.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.
package/app.vue CHANGED
@@ -1,3 +1,2 @@
1
1
  <template lang="pug">
2
- SLMap
3
2
  </template>
@@ -0,0 +1,719 @@
1
+ <template lang="pug">
2
+ .multi-email-input(:class='{ collapsable, collapsed }')
3
+ a.multi-email-input-header(href='javascript:;', @click='onClickedToggleCollapse')
4
+ .left-block
5
+ span 與其他使用者共用
6
+ SLInfoTip(placement="top", :pinnable='false')
7
+ .space-y-3
8
+ .flex.items-center.text-sm.text-gray-2
9
+ SLIcon.text-secondary(icon="check-circle")
10
+ span.whitespace-nowrap :確認帳號存在 SKYLENS
11
+ .flex.items-center.text-sm.text-gray-2
12
+ SLIcon.text-danger(icon='exclamation-circle')
13
+ span.whitespace-nowrap :帳號可能不存在 SKYLENS , 需請帳號管理者新增
14
+ .right-block
15
+ SLIcon(icon='chevron-down')
16
+ .multi-email-input-body
17
+ .multi-email-input-body-wrapper
18
+ .emails-input(ref='inputEl')
19
+ .emails-input-wrapper
20
+ .emails-list.custom-scrollbar-y(
21
+ v-if='modelValue.length > 0'
22
+ ref='listEl'
23
+ )
24
+ .emails-list-wrapper(ref='listWrapperEl')
25
+ .email-item(
26
+ v-for='inputEmail in modelValue'
27
+ :key='inputEmail.email'
28
+ )
29
+ .email-item-status
30
+ SLSpinIcon(
31
+ v-if='inputEmail.status === "loading"'
32
+ icon='loading-circle'
33
+ ).icon-loading
34
+ SLIcon(
35
+ v-else-if='inputEmail.status === "success"'
36
+ icon='check-circle'
37
+ ).icon-success
38
+ SLIcon(
39
+ v-else-if='inputEmail.status === "warn"'
40
+ icon='exclamation-circle'
41
+ ).icon-warn
42
+ .email-item-email {{ inputEmail.email }}
43
+ a.email-item-delete-button(
44
+ href='javascript:;'
45
+ @click='onClickedRemoveInputEmail(inputEmail.email)'
46
+ )
47
+ SLIcon(icon='times')
48
+ textarea(
49
+ v-model.trim="email"
50
+ rows="1"
51
+ placeholder='輸入使用者Email'
52
+ ref='textareaEl'
53
+ @paste.prevent="onPastedEmails"
54
+ @keydown.enter.prevent="onEnteredEmail"
55
+ @focus='onFocusTextarea'
56
+ @blur='onBlurTextarea'
57
+ )
58
+ .multi-email-input-role(v-if='isShowRoleOptions')
59
+ SLDropdown.multi-email-input-role-dropdown(
60
+ v-model:show-dropdown="isShowRoleDropdown"
61
+ :title='roleDropdownTitle'
62
+ toggle-icon='chevron-down'
63
+ placement="bottom"
64
+ menu-class='multi-email-input-role-dropdown-menu'
65
+ )
66
+ SLDropdownItem(
67
+ title='檢視'
68
+ :active='role === "normal"'
69
+ @click='onClickedRole("normal")'
70
+ )
71
+ SLDropdownItem(
72
+ title='編輯'
73
+ :active='role === "editor"'
74
+ @click='onClickedRole("editor")'
75
+ )
76
+ .multi-email-input-actions
77
+ SLButton(
78
+ :title="addButtonTitle"
79
+ color="primary"
80
+ size="md"
81
+ :disabled="!isAddEnable"
82
+ :loading='loading'
83
+ @click="onClickedAdd"
84
+ )
85
+ ClientOnly
86
+ Teleport(to='body')
87
+ .multi-email-input-autocomplete(
88
+ :style='`width: ${autoCompleteWidth}px;`'
89
+ ref='autoCompleteEl'
90
+ )
91
+ .swiper(ref='swiperEl')
92
+ .swiper-wrapper
93
+ .swiper-slide
94
+ a.autotcomplete-item(
95
+ v-for='member, i in autoCompletedMembers'
96
+ href='javascript:;'
97
+ tabindex='-1'
98
+ :class='{ focused: focusedAutoCompleteIndex === i }'
99
+ :key='member'
100
+ @click='onClickedMember(member)'
101
+ ref='autoCompleteItemEls'
102
+ ) {{ member }}
103
+ .swiper-scrollbar
104
+ </template>
105
+
106
+ <script lang="ts">
107
+ import type { PropType } from "vue";
108
+ import { createPopper } from "@popperjs/core";
109
+ import type { Instance as PopperInstance } from "@popperjs/core";
110
+ import { fromEvent, Subscription } from "rxjs";
111
+ import Swiper from "swiper";
112
+ import { FreeMode, Scrollbar, Mousewheel } from "swiper/modules";
113
+ import "swiper/css";
114
+ import "swiper/css/scrollbar";
115
+ import { OrgUserRole } from "~ui/interface/IOrganization";
116
+
117
+ export default defineComponent({
118
+ name: "SLMultiEmailInput",
119
+ components: {},
120
+ props: {
121
+ collapsable: {
122
+ type: Boolean,
123
+ default: false,
124
+ },
125
+ collapsed: {
126
+ type: Boolean,
127
+ default: true,
128
+ },
129
+ loading: {
130
+ type: Boolean,
131
+ default: false,
132
+ },
133
+ orgMembers: {
134
+ type: Array as PropType<string[]>,
135
+ default: () => [],
136
+ },
137
+ label: {
138
+ type: String,
139
+ default: "與其他使用者共用",
140
+ },
141
+ modelValue: {
142
+ type: Array as PropType<
143
+ {
144
+ email: string;
145
+ status: "loading" | "success" | "warn";
146
+ }[]
147
+ >,
148
+ default: () => [
149
+ { email: "aaa@example.com", status: "loading" },
150
+ { email: "bbb@example.com", status: "success" },
151
+ { email: "ccc@example.com", status: "warn" },
152
+ ],
153
+ },
154
+ role: {
155
+ type: String as PropType<OrgUserRole>,
156
+ default: OrgUserRole.Normal,
157
+ },
158
+ isShowRoleOptions: {
159
+ type: Boolean,
160
+ default: true,
161
+ },
162
+ addButtonTitle: {
163
+ type: String,
164
+ default: "加入",
165
+ },
166
+ recommendEmails: {
167
+ type: Array as PropType<string[]>,
168
+ default: () => [],
169
+ },
170
+ },
171
+ emits: [
172
+ "remove:email",
173
+ "paste:emails",
174
+ "enter:email",
175
+ "update:role",
176
+ "add",
177
+ "toggle:collapse",
178
+ ],
179
+ setup(props, { emit }) {
180
+ const { collapsable, orgMembers, modelValue, role, recommendEmails } =
181
+ toRefs(props);
182
+
183
+ const inputEl = shallowRef<HTMLDivElement | null>(null);
184
+ const listEl = shallowRef<HTMLDivElement | null>(null);
185
+ const listWrapperEl = shallowRef<HTMLDivElement | null>(null);
186
+ const textareaEl = shallowRef<HTMLTextAreaElement | null>(null);
187
+ const autoCompleteEl = shallowRef<HTMLDivElement | null>(null);
188
+ const swiperEl = shallowRef<HTMLDivElement | null>(null);
189
+ const email = ref("");
190
+ const isShowRoleDropdown = ref(false);
191
+ const isShowAutoComplete = ref(false);
192
+ const autoCompleteWidth = ref(0);
193
+ const isScrollToEnd = ref(false);
194
+ const focusedAutoCompleteIndex = ref(-1);
195
+ const focusedTextarea = ref(false);
196
+
197
+ let popperInstance: PopperInstance | null = null;
198
+ let clickSubscription: Subscription | null = null;
199
+ let keydownSubscription: Subscription | null = null;
200
+ let swiper: Swiper | null = null;
201
+
202
+ const autoCompletedMembers = computed(() => {
203
+ if (!email.value) return recommendEmails.value;
204
+
205
+ const regexp = new RegExp(`.*${email.value}.*`, "i");
206
+ return orgMembers.value.filter((member) => regexp.test(member));
207
+ });
208
+ const roleDropdownTitle = computed(() => {
209
+ switch (role.value) {
210
+ case OrgUserRole.Editor:
211
+ return "編輯";
212
+ default:
213
+ return "檢視";
214
+ }
215
+ });
216
+ const isAddEnable = computed(() => {
217
+ let isEnable =
218
+ modelValue.value.filter((inputEmail) => inputEmail.status === "success")
219
+ .length > 0;
220
+
221
+ modelValue.value.forEach((inputEmail) => {
222
+ if (inputEmail.status === "loading") {
223
+ isEnable = false;
224
+ }
225
+ });
226
+
227
+ return isEnable;
228
+ });
229
+
230
+ function onClickedRemoveInputEmail(email: string) {
231
+ emit("remove:email", email);
232
+ }
233
+
234
+ function onPastedEmails(e: ClipboardEvent) {
235
+ if (!e.clipboardData) return;
236
+
237
+ const pastedText = e.clipboardData.getData("text");
238
+ const pastedEmails = pastedText.replaceAll(/\r\n|\r/g, "\n").split("\n");
239
+ const pastedEmailSet: Set<string> = new Set();
240
+ pastedEmails.forEach((email) => {
241
+ if (
242
+ !modelValue.value.find((inputEmail) => inputEmail.email === email)
243
+ ) {
244
+ pastedEmailSet.add(email);
245
+ }
246
+ });
247
+ isScrollToEnd.value = true;
248
+ emit("paste:emails", Array.from(pastedEmailSet));
249
+ }
250
+
251
+ function onEnteredEmail() {
252
+ if (
253
+ !modelValue.value.find((inputEmail) => inputEmail.email === email.value)
254
+ ) {
255
+ isScrollToEnd.value = true;
256
+ emit("enter:email", email.value);
257
+ }
258
+ email.value = "";
259
+ }
260
+
261
+ function onClickedMember(member: string) {
262
+ if (!modelValue.value.find((inputEmail) => inputEmail.email === member)) {
263
+ emit("enter:email", member);
264
+ }
265
+ email.value = "";
266
+ focusedAutoCompleteIndex.value = -1;
267
+ }
268
+
269
+ function onClickedRole(changeRole: "normal" | "editor") {
270
+ emit("update:role", changeRole);
271
+ isShowRoleDropdown.value = false;
272
+ }
273
+
274
+ function onClickedAdd() {
275
+ emit("add");
276
+ }
277
+
278
+ function updateSwiper() {
279
+ nextTick(() => {
280
+ if (!swiper) return;
281
+
282
+ swiper.update();
283
+ });
284
+ }
285
+
286
+ function setupSwiper() {
287
+ nextTick(() => {
288
+ if (!swiperEl.value) return;
289
+
290
+ if (swiper) {
291
+ updateSwiper();
292
+ return;
293
+ }
294
+
295
+ swiper = new Swiper(swiperEl.value, {
296
+ modules: [FreeMode, Scrollbar, Mousewheel],
297
+ slidesPerView: "auto",
298
+ direction: "vertical",
299
+ freeMode: true,
300
+ mousewheel: true,
301
+ scrollbar: {
302
+ el: ".swiper-scrollbar",
303
+ draggable: true,
304
+ snapOnRelease: false,
305
+ },
306
+ });
307
+ });
308
+ }
309
+
310
+ function destroySwiper() {
311
+ if (swiper) {
312
+ swiper.destroy();
313
+ }
314
+ swiper = null;
315
+ }
316
+
317
+ function subscribeClick() {
318
+ clickSubscription = fromEvent(window, "click").subscribe((e) => {
319
+ const { target } = e;
320
+ if (
321
+ (!textareaEl.value || !textareaEl.value.contains(target as Node)) &&
322
+ (!autoCompleteEl.value ||
323
+ !autoCompleteEl.value.contains(target as Node))
324
+ ) {
325
+ hideAutoComplete();
326
+ }
327
+ });
328
+ }
329
+
330
+ function unsubscribeClick() {
331
+ if (clickSubscription) {
332
+ clickSubscription.unsubscribe();
333
+ }
334
+ clickSubscription = null;
335
+ }
336
+
337
+ function setupPopper() {
338
+ nextTick(() => {
339
+ if (!inputEl.value || !autoCompleteEl.value) return;
340
+ popperInstance = createPopper(inputEl.value, autoCompleteEl.value, {
341
+ placement: "bottom",
342
+ modifiers: [
343
+ {
344
+ name: "offset",
345
+ options: {
346
+ offset: [0, 4],
347
+ },
348
+ },
349
+ { name: "eventListeners", enabled: false },
350
+ ],
351
+ });
352
+ });
353
+ }
354
+
355
+ function destroyPopper() {
356
+ destroySwiper();
357
+ unsubscribeClick();
358
+
359
+ if (popperInstance) {
360
+ popperInstance.destroy();
361
+ }
362
+ popperInstance = null;
363
+ }
364
+
365
+ function showAutoComplete() {
366
+ if (
367
+ !autoCompleteEl.value ||
368
+ !inputEl.value ||
369
+ autoCompleteEl.value.hasAttribute("data-show") ||
370
+ !popperInstance ||
371
+ !autoCompletedMembers.value.length
372
+ )
373
+ return;
374
+
375
+ isShowAutoComplete.value = true;
376
+ subscribeClick();
377
+ const inputElRect = inputEl.value.getBoundingClientRect();
378
+ autoCompleteWidth.value = inputElRect.width;
379
+
380
+ autoCompleteEl.value.setAttribute("data-show", "");
381
+ popperInstance.setOptions((options) => ({
382
+ ...options,
383
+ modifiers: [
384
+ ...(options.modifiers || []),
385
+ { name: "eventListeners", enabled: true },
386
+ ],
387
+ }));
388
+
389
+ setupSwiper();
390
+ }
391
+
392
+ function hideAutoComplete() {
393
+ if (!autoCompleteEl.value || !popperInstance) return;
394
+
395
+ isShowAutoComplete.value = false;
396
+ unsubscribeClick();
397
+ destroySwiper();
398
+ autoCompleteEl.value.removeAttribute("data-show");
399
+ popperInstance.setOptions((options) => ({
400
+ ...options,
401
+ modifiers: [
402
+ ...(options.modifiers || []),
403
+ { name: "eventListeners", enabled: false },
404
+ ],
405
+ }));
406
+ }
407
+
408
+ function onFocusTextarea() {
409
+ focusedTextarea.value = true;
410
+ showAutoComplete();
411
+ }
412
+
413
+ function onBlurTextarea() {
414
+ focusedTextarea.value = false;
415
+ }
416
+
417
+ function listScrollToEnd() {
418
+ if (!isScrollToEnd.value) return;
419
+
420
+ nextTick(() => {
421
+ isScrollToEnd.value = false;
422
+ if (!listEl.value || !listWrapperEl.value) return;
423
+
424
+ const listElRect = listEl.value.getBoundingClientRect();
425
+ const listWrapperElRect = listWrapperEl.value.getBoundingClientRect();
426
+ const diff = listWrapperElRect.height - listElRect.height;
427
+
428
+ listEl.value.scrollTo({
429
+ top: diff > 0 ? diff : 0,
430
+ behavior: "smooth",
431
+ });
432
+ });
433
+ }
434
+
435
+ function onWindowKeydown(e: Event) {
436
+ if (!isShowAutoComplete.value) return;
437
+
438
+ const keyboardEvent = e as KeyboardEvent;
439
+ if (
440
+ keyboardEvent.key === "ArrowDown" ||
441
+ keyboardEvent.key === "ArrowUp" ||
442
+ keyboardEvent.key === "Enter"
443
+ ) {
444
+ switch (keyboardEvent.key) {
445
+ case "ArrowDown":
446
+ focusedAutoCompleteIndex.value = Math.min(
447
+ autoCompletedMembers.value.length - 1,
448
+ focusedAutoCompleteIndex.value + 1
449
+ );
450
+ break;
451
+ case "ArrowUp":
452
+ focusedAutoCompleteIndex.value = Math.max(
453
+ -1,
454
+ focusedAutoCompleteIndex.value - 1
455
+ );
456
+ break;
457
+ default:
458
+ const member =
459
+ autoCompletedMembers.value[focusedAutoCompleteIndex.value];
460
+ onClickedMember(member);
461
+ hideAutoComplete();
462
+ }
463
+
464
+ if (textareaEl.value && keyboardEvent.key !== "Enter") {
465
+ if (focusedAutoCompleteIndex.value > -1) {
466
+ textareaEl.value.blur();
467
+ } else {
468
+ textareaEl.value.focus();
469
+ }
470
+ }
471
+
472
+ e.preventDefault();
473
+ e.stopPropagation();
474
+ return;
475
+ }
476
+ }
477
+
478
+ function setupAutoCompleteItemsTabindex() {
479
+ nextTick(() => {
480
+ const slideEl = swiperEl.value?.querySelector(".swiper-slide");
481
+ if (!slideEl) return;
482
+
483
+ for (let i = 0; i < slideEl.children.length; i += 1) {
484
+ const autoCompleteItemEl = slideEl.children[i];
485
+
486
+ if (i === focusedAutoCompleteIndex.value) {
487
+ autoCompleteItemEl.setAttribute("tabindex", "0");
488
+ } else {
489
+ autoCompleteItemEl.setAttribute("tabindex", "-1");
490
+ }
491
+ }
492
+ });
493
+ }
494
+
495
+ function scrollToAutoCompleteItem() {
496
+ nextTick(() => {
497
+ if (!swiperEl.value || !swiper) return;
498
+
499
+ if (focusedAutoCompleteIndex.value <= -1) {
500
+ swiper.translateTo(0, 0, false, false);
501
+ return;
502
+ }
503
+
504
+ const swiperWrapperEl = swiperEl.value.querySelector(".swiper-wrapper");
505
+ const swiperSlideEl = swiperEl.value.querySelector(".swiper-slide");
506
+ const itemEl = swiperSlideEl?.children[focusedAutoCompleteIndex.value];
507
+
508
+ if (!swiperWrapperEl || !swiperSlideEl || !itemEl) return;
509
+
510
+ const swiperRect = swiperEl.value.getBoundingClientRect();
511
+ const wrapperRect = swiperWrapperEl.getBoundingClientRect();
512
+ const slideRect = swiperSlideEl.getBoundingClientRect();
513
+ const itemRect = itemEl.getBoundingClientRect();
514
+ const scrollTop = wrapperRect.y - swiperRect.y;
515
+ const scrollBotttom = scrollTop - swiperRect.height;
516
+ const itemTop = slideRect.y - itemRect.y;
517
+ const itemBottom = itemTop - itemRect.height;
518
+
519
+ if (itemTop > scrollTop) {
520
+ swiper.translateTo(itemTop, 300, false, false);
521
+ return;
522
+ }
523
+
524
+ if (itemBottom < scrollBotttom) {
525
+ swiper.translateTo(
526
+ itemBottom - scrollBotttom + scrollTop,
527
+ 300,
528
+ false,
529
+ false
530
+ );
531
+ }
532
+ });
533
+ }
534
+
535
+ function onClickedToggleCollapse() {
536
+ if (!collapsable.value) return;
537
+
538
+ emit("toggle:collapse");
539
+ }
540
+
541
+ watch(autoCompletedMembers, () => {
542
+ focusedAutoCompleteIndex.value = -1;
543
+
544
+ if (autoCompletedMembers.value.length) {
545
+ if (focusedTextarea.value) {
546
+ showAutoComplete();
547
+ }
548
+ updateSwiper();
549
+ } else {
550
+ hideAutoComplete();
551
+ }
552
+ });
553
+
554
+ watch(modelValue, () => {
555
+ listScrollToEnd();
556
+ });
557
+
558
+ watch(focusedAutoCompleteIndex, () => {
559
+ setupAutoCompleteItemsTabindex();
560
+ scrollToAutoCompleteItem();
561
+ });
562
+
563
+ onMounted(() => {
564
+ setupPopper();
565
+
566
+ keydownSubscription = fromEvent(window, "keydown").subscribe(
567
+ onWindowKeydown
568
+ );
569
+ });
570
+
571
+ onBeforeUnmount(() => {
572
+ destroyPopper();
573
+
574
+ if (keydownSubscription) {
575
+ keydownSubscription.unsubscribe();
576
+ }
577
+ keydownSubscription = null;
578
+ });
579
+
580
+ return {
581
+ inputEl,
582
+ listEl,
583
+ listWrapperEl,
584
+ textareaEl,
585
+ autoCompleteEl,
586
+ swiperEl,
587
+ email,
588
+ isShowRoleDropdown,
589
+ autoCompleteWidth,
590
+ focusedAutoCompleteIndex,
591
+ autoCompletedMembers,
592
+ roleDropdownTitle,
593
+ isAddEnable,
594
+ onClickedRemoveInputEmail,
595
+ onPastedEmails,
596
+ onEnteredEmail,
597
+ onClickedMember,
598
+ onClickedRole,
599
+ onClickedAdd,
600
+ onFocusTextarea,
601
+ onBlurTextarea,
602
+ onClickedToggleCollapse,
603
+ };
604
+ },
605
+ });
606
+ </script>
607
+
608
+ <style lang="stylus" scoped>
609
+ .multi-email-input
610
+ @apply flex flex-col
611
+
612
+ &.collapsable
613
+ .multi-email-input-header
614
+ @apply cursor-pointer
615
+
616
+ .right-block
617
+ @apply flex
618
+
619
+ &.collapsed
620
+ .multi-email-input-header
621
+ .right-block
622
+ .icon
623
+ @apply rotate-0
624
+
625
+ &-header
626
+ @apply flex items-center justify-between mb-2 cursor-default
627
+
628
+ .left-block
629
+ @apply flex-grow-0 flex-shrink-0 flex items-center text-base text-gray-2
630
+
631
+ .right-block
632
+ @apply flex-grow-0 flex-shrink-0 text-xs hidden
633
+ .icon
634
+ @apply transition-transform rotate-180
635
+
636
+ &-body
637
+
638
+ &-wrapper
639
+ @apply flex items-start space-x-4
640
+
641
+ .emails-input
642
+ @apply flex-grow flex items-center s('p-1.75') space-x-4 bg-white border border-primary-light rounded overflow-hidden
643
+
644
+ &-wrapper
645
+ @apply flex-grow space-y-2 overflow-hidden
646
+
647
+ .emails-list
648
+ @apply s('max-h-[97px]')
649
+
650
+ &-wrapper
651
+ @apply flex flex-col items-start space-y-2 overflow-hidden
652
+
653
+ .email-item
654
+ @apply inline-flex items-center justify-start max-w-full s('px-1.75') s('py-0.5') space-x-2 bg-white border border-primary-lighter rounded-full text-base text-gray-2
655
+
656
+ &-email
657
+ @apply flex-grow truncate
658
+
659
+ &-status
660
+ @apply flex-grow-0 flex-shrink-0
661
+
662
+ .icon-loading
663
+ @apply text-third
664
+
665
+ .icon-success
666
+ @apply text-secondary
667
+
668
+ .icon-warn
669
+ @apply text-danger
670
+
671
+ &-delete-button
672
+ @apply text-xs
673
+
674
+ textarea
675
+ @apply flex-grow-0 flex-shrink-0 block w-full p-0 border-0 bg-white text-sm text-gray-2 resize-none s('focus:outline-none')
676
+
677
+ .multi-email-input-role
678
+ @apply flex-grow-0 flex-shrink-0 flex
679
+
680
+ :deep(.dropdown-toggle)
681
+ @apply text-sm
682
+
683
+ .multi-email-input-actions
684
+ @apply flex-grow-0 flex-shrink-0
685
+
686
+ .btn
687
+ @apply text-sm
688
+
689
+ .multi-email-input-autocomplete
690
+ @apply z-50 s('max-h-[116px]') p-1 bg-white rounded shadow hidden
691
+
692
+ &[data-show]
693
+ @apply flex
694
+
695
+ .swiper
696
+ @apply w-full
697
+
698
+ .swiper-slide
699
+ @apply h-auto
700
+ .swiper-scrollbar
701
+ @apply top-0 right-0 h-full bg-transparent
702
+
703
+ :deep(.swiper-scrollbar-drag)
704
+ @apply bg-primary-lighter
705
+ .autotcomplete-item
706
+ @apply block w-full py-1 px-2 bg-white text-sm text-gray-2
707
+ &:hover, &.focused
708
+ @apply bg-bg
709
+ </style>
710
+
711
+ <style lang="stylus">
712
+ .dropdown-menu.multi-email-input-role-dropdown-menu
713
+ @apply border-primary
714
+
715
+ .dropdown-list-wrapper
716
+ .dropdown-list
717
+ .dropdown-item
718
+ @apply text-center
719
+ </style>
@@ -0,0 +1,94 @@
1
+ <template lang="pug">
2
+ .project-share-item
3
+ .item-info
4
+ .item-info-email {{ email }}
5
+ .item-actions
6
+ SLDropdown(
7
+ v-model:showDropdown="showDropdown"
8
+ :title="`${modelValue === 'normal' ? '檢視者' : '編輯者'}`"
9
+ toggle-icon='chevron-down'
10
+ placement='bottom'
11
+ )
12
+ SLDrowdownItem(
13
+ title="檢視者"
14
+ :class="{ active: modelValue === 'normal' }"
15
+ @click="onClickedViewer"
16
+ )
17
+ SLDrowdownItem(
18
+ title="編輯者"
19
+ :class="{ active: modelValue === 'editor' }"
20
+ @click="onClickedEditor"
21
+ )
22
+ SLDrowdownItem(title="刪除" @click="onClickedDelete")
23
+ </template>
24
+
25
+ <script lang="ts">
26
+ import type { PropType } from "vue";
27
+ import { OrgUserRole } from "~ui/interface/IOrganization";
28
+
29
+ export default defineComponent({
30
+ name: "SLProjectShareItem",
31
+ components: {},
32
+ props: {
33
+ email: {
34
+ type: String,
35
+ default: "",
36
+ },
37
+ modelValue: {
38
+ type: String as PropType<OrgUserRole>,
39
+ default: OrgUserRole.Normal,
40
+ },
41
+ },
42
+ emits: ["update:modelValue", "delete"],
43
+ setup(props, { emit }) {
44
+ const { email } = toRefs(props);
45
+
46
+ const showDropdown = ref(false);
47
+
48
+ function onClickedViewer() {
49
+ emit("update:modelValue", email.value, OrgUserRole.Normal);
50
+ showDropdown.value = false;
51
+ }
52
+
53
+ function onClickedEditor() {
54
+ emit("update:modelValue", email.value, OrgUserRole.Editor);
55
+ showDropdown.value = false;
56
+ }
57
+
58
+ function onClickedDelete() {
59
+ emit("delete", email.value);
60
+ showDropdown.value = false;
61
+ }
62
+
63
+ return {
64
+ showDropdown,
65
+ onClickedViewer,
66
+ onClickedEditor,
67
+ onClickedDelete,
68
+ };
69
+ },
70
+ });
71
+ </script>
72
+
73
+ <style lang="stylus" scoped>
74
+ .project-share-item
75
+ @apply flex items-center justify-between space-x-4
76
+
77
+ .item-info
78
+ @apply flex-grow text-sm text-gray-2 truncate
79
+
80
+ .item-actions
81
+ @apply flex-grow-0 flex-shrink-0
82
+
83
+ :deep(.dropdown-menu)
84
+ @apply border-primary-light
85
+ .dropdown-list-wrapper
86
+ .dropdown-list
87
+ @apply s('min-w-[74px]') divide-y divide-bg
88
+
89
+ :deep(.dropdown-toggle)
90
+ @apply s('py-0.75') border-primary-light bg-white text-sm
91
+
92
+ .dropdown-item
93
+ @apply text-center
94
+ </style>
@@ -2,6 +2,7 @@
2
2
  li(
3
3
  v-if="distributed && distributed.header"
4
4
  :class="tabNavItemClass"
5
+ :style="distributed && distributed.style"
5
6
  @click="navClick"
6
7
  ref='tabPaneEl'
7
8
  )
@@ -68,6 +69,7 @@ export default defineComponent({
68
69
 
69
70
  const distributed = inject<{
70
71
  header: boolean;
72
+ style: string;
71
73
  changeTabTo: (newTab: number) => void;
72
74
  }>("distributed");
73
75
  const activeTab = inject<Ref<number>>("activeTab");
@@ -0,0 +1,6 @@
1
+ export enum OrgUserRole {
2
+ Personal = 'personal',
3
+ Admin = 'admin',
4
+ Editor = 'editor',
5
+ Normal = 'normal',
6
+ }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@mptw/skylens-ui",
3
3
  "type": "module",
4
- "version": "1.0.0",
4
+ "version": "1.1.0",
5
5
  "main": "./nuxt.config.ts",
6
6
  "scripts": {
7
7
  "dev": "nuxi dev .playground",
@@ -43,6 +43,7 @@
43
43
  "rxjs": "^7.8.1",
44
44
  "sortablejs": "^1.15.2",
45
45
  "stylus": "^0.63.0",
46
+ "swiper": "^11.1.1",
46
47
  "tailwindcss-textshadow": "^2.1.3",
47
48
  "v-calendar": "^3.1.2",
48
49
  "vue": "^3.4.22",