@indielayer/ui 1.9.2 → 1.10.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 (98) hide show
  1. package/docs/components/menu/DocsMenu.vue +1 -0
  2. package/docs/pages/component/form/usage.vue +2 -0
  3. package/docs/pages/component/menu/usage.vue +2 -0
  4. package/docs/pages/component/select/index.vue +7 -0
  5. package/docs/pages/component/select/multiple.vue +42 -0
  6. package/docs/pages/component/select/usage.vue +8 -12
  7. package/docs/pages/component/table/virtual.vue +19 -6
  8. package/docs/pages/component/toggle/index.vue +1 -1
  9. package/docs/pages/component/tooltip/index.vue +1 -1
  10. package/docs/pages/component/upload/index.vue +29 -0
  11. package/docs/pages/component/upload/usage.vue +115 -0
  12. package/docs/search/components.json +1 -1
  13. package/lib/common/icons.d.ts +2 -0
  14. package/lib/common/icons.js +17 -15
  15. package/lib/components/checkbox/Checkbox.vue2.js +9 -9
  16. package/lib/components/datepicker/Datepicker.vue.d.ts +4 -4
  17. package/lib/components/datepicker/Datepicker.vue.js +1 -1
  18. package/lib/components/drawer/Drawer.vue.js +1 -17
  19. package/lib/components/form/Form.vue.d.ts +4 -4
  20. package/lib/components/form/Form.vue.js +34 -34
  21. package/lib/components/formGroup/FormGroup.vue.d.ts +1 -1
  22. package/lib/components/formGroup/FormGroup.vue.js +39 -37
  23. package/lib/components/index.d.ts +1 -0
  24. package/lib/components/index.js +68 -66
  25. package/lib/components/label/theme/Label.base.theme.js +7 -7
  26. package/lib/components/menu/Menu.vue.d.ts +2 -0
  27. package/lib/components/menu/MenuItem.vue.d.ts +15 -3
  28. package/lib/components/menu/MenuItem.vue.js +1 -1
  29. package/lib/components/menu/MenuItem.vue2.js +43 -37
  30. package/lib/components/modal/Modal.vue.d.ts +4 -4
  31. package/lib/components/modal/Modal.vue.js +38 -34
  32. package/lib/components/notifications/Notifications.vue.d.ts +15 -0
  33. package/lib/components/notifications/Notifications.vue.js +149 -127
  34. package/lib/components/progress/Progress.vue.d.ts +4 -4
  35. package/lib/components/progress/Progress.vue.js +7 -7
  36. package/lib/components/scroll/Scroll.vue2.js +1 -1
  37. package/lib/components/select/Select.vue.d.ts +43 -1
  38. package/lib/components/select/Select.vue.js +358 -258
  39. package/lib/components/select/theme/Select.base.theme.js +1 -0
  40. package/lib/components/tab/Tab.vue.js +1 -1
  41. package/lib/components/tab/TabGroup.vue.js +2 -2
  42. package/lib/components/table/Table.vue.d.ts +4 -4
  43. package/lib/components/table/Table.vue.js +37 -37
  44. package/lib/components/table/TableCell.vue.d.ts +1 -1
  45. package/lib/components/tag/Tag.vue.js +23 -21
  46. package/lib/components/textarea/Textarea.vue.js +1 -1
  47. package/lib/components/upload/Upload.vue.d.ts +195 -0
  48. package/lib/components/upload/Upload.vue.js +264 -0
  49. package/lib/components/upload/Upload.vue2.js +4 -0
  50. package/lib/components/upload/__tests__/Upload.spec.d.ts +1 -0
  51. package/lib/components/upload/index.d.ts +2 -0
  52. package/lib/components/upload/theme/Upload.base.theme.d.ts +3 -0
  53. package/lib/components/upload/theme/Upload.base.theme.js +8 -0
  54. package/lib/components/upload/theme/Upload.carbon.theme.d.ts +3 -0
  55. package/lib/components/upload/theme/Upload.carbon.theme.js +5 -0
  56. package/lib/composables/useVirtualList.js +56 -53
  57. package/lib/index.js +43 -41
  58. package/lib/index.umd.js +4 -4
  59. package/lib/node_modules/.pnpm/@vueuse_core@11.1.0_vue@3.5.10_typescript@5.2.2_/node_modules/@vueuse/core/index.js +501 -0
  60. package/lib/node_modules/.pnpm/@vueuse_shared@11.1.0_vue@3.5.10_typescript@5.2.2_/node_modules/@vueuse/shared/index.js +96 -0
  61. package/lib/theme.d.ts +2 -1
  62. package/lib/themes/base/components.d.ts +1 -0
  63. package/lib/themes/base/components.js +23 -21
  64. package/lib/themes/carbon/components.d.ts +1 -0
  65. package/lib/themes/carbon/components.js +23 -21
  66. package/lib/version.d.ts +1 -1
  67. package/lib/version.js +1 -1
  68. package/package.json +3 -3
  69. package/src/common/icons.ts +2 -0
  70. package/src/components/checkbox/Checkbox.vue +5 -5
  71. package/src/components/drawer/Drawer.vue +0 -16
  72. package/src/components/form/Form.vue +10 -4
  73. package/src/components/formGroup/FormGroup.vue +2 -0
  74. package/src/components/index.ts +1 -0
  75. package/src/components/label/theme/Label.base.theme.ts +7 -5
  76. package/src/components/menu/Menu.vue +2 -0
  77. package/src/components/menu/MenuItem.vue +8 -6
  78. package/src/components/modal/Modal.vue +6 -1
  79. package/src/components/notifications/Notifications.vue +34 -4
  80. package/src/components/progress/Progress.vue +2 -2
  81. package/src/components/select/Select.vue +165 -67
  82. package/src/components/select/theme/Select.base.theme.ts +2 -0
  83. package/src/components/table/Table.vue +2 -3
  84. package/src/components/tag/Tag.vue +11 -9
  85. package/src/components/upload/Upload.vue +365 -0
  86. package/src/components/upload/__tests__/Upload.spec.ts +11 -0
  87. package/src/components/upload/index.ts +2 -0
  88. package/src/components/upload/theme/Upload.base.theme.ts +9 -0
  89. package/src/components/upload/theme/Upload.carbon.theme.ts +7 -0
  90. package/src/composables/useInputtable.ts +1 -1
  91. package/src/composables/useVirtualList.ts +8 -5
  92. package/src/theme.ts +2 -0
  93. package/src/themes/base/components.ts +1 -0
  94. package/src/themes/carbon/components.ts +1 -0
  95. package/src/version.ts +1 -1
  96. package/volar.d.ts +1 -0
  97. package/lib/node_modules/.pnpm/@vueuse_core@10.2.0_vue@3.5.10_typescript@5.2.2_/node_modules/@vueuse/core/index.js +0 -412
  98. package/lib/node_modules/.pnpm/@vueuse_shared@10.2.0_vue@3.5.10_typescript@5.2.2_/node_modules/@vueuse/shared/index.js +0 -90
@@ -7,6 +7,7 @@ const selectProps = {
7
7
  placeholder: String,
8
8
  options: Array as PropType<SelectOption[]>,
9
9
  multiple: Boolean,
10
+ truncate: Boolean,
10
11
  flat: Boolean,
11
12
  native: Boolean,
12
13
  filterable: Boolean,
@@ -30,12 +31,14 @@ const selectProps = {
30
31
  export type SelectOption = {
31
32
  value: number | string;
32
33
  label: string;
34
+ prefix?: string;
35
+ suffix?: string;
33
36
  disabled?: boolean;
34
37
  }
35
38
 
36
39
  export type SelectProps = ExtractPublicPropTypes<typeof selectProps>
37
40
 
38
- type InternalClasses = 'wrapper' | 'box' | 'content' | 'search' | 'contentBody' | 'iconWrapper' | 'icon'
41
+ type InternalClasses = 'wrapper' | 'box' | 'truncateCounter' | 'content' | 'search' | 'contentBody' | 'iconWrapper' | 'icon'
39
42
  type InternalExtraData = { errorInternal: Ref<boolean>; }
40
43
  export interface SelectTheme extends ThemeComponent<SelectProps, InternalClasses, InternalExtraData> {}
41
44
 
@@ -49,7 +52,7 @@ export default {
49
52
 
50
53
  <script setup lang="ts">
51
54
  import { computed, ref, watch, type PropType, type ExtractPublicPropTypes, type Ref, nextTick, unref, onUnmounted } from 'vue'
52
- import { useEventListener } from '@vueuse/core'
55
+ import { useEventListener, useResizeObserver, useThrottleFn } from '@vueuse/core'
53
56
  import { useColors } from '../../composables/useColors'
54
57
  import { useCommon } from '../../composables/useCommon'
55
58
  import { useInputtable } from '../../composables/useInputtable'
@@ -66,7 +69,7 @@ import XSpinner from '../spinner/Spinner.vue'
66
69
  import XPopover from '../popover/Popover.vue'
67
70
  import XPopoverContainer from '../popover/PopoverContainer.vue'
68
71
  import XInputFooter from '../inputFooter/InputFooter.vue'
69
- import type { XInput } from '../input'
72
+ import XInput from '../input/Input.vue'
70
73
 
71
74
  const props = defineProps(selectProps)
72
75
 
@@ -116,6 +119,8 @@ const internalOptions = computed(() => {
116
119
  value: option.value,
117
120
  label: option.label,
118
121
  active: isActive,
122
+ prefix: option.prefix,
123
+ suffix: option.suffix,
119
124
  disabled: option.disabled,
120
125
  iconRight: isActive ? checkIcon : undefined,
121
126
  onClick: () => handleOptionClick(option.value),
@@ -372,6 +377,59 @@ function handleKeyNavigation(e: KeyboardEvent) {
372
377
  }
373
378
  }
374
379
 
380
+ const tagsRef = ref<HTMLElement | null>(null)
381
+ const multipleHiddenRef = ref<InstanceType<typeof XPopover> | null>(null)
382
+ const showCountTag = ref(false)
383
+ const hiddenTags = ref(0)
384
+
385
+ const handleTruncate = useThrottleFn(() => {
386
+ if (props.multiple && props.truncate) {
387
+ nextTick(() => {
388
+ const maxTags = calcMaxTags()
389
+
390
+ if (maxTags < selected.value.length) {
391
+ showCountTag.value = true
392
+ hiddenTags.value = selected.value.length - maxTags
393
+ } else {
394
+ showCountTag.value = false
395
+ hiddenTags.value = 0
396
+ }
397
+ })
398
+ }
399
+ }, 100, true)
400
+
401
+ useResizeObserver(tagsRef, () => { handleTruncate() })
402
+
403
+ // Calculate max tags that can be displayed, and display: none the rest
404
+ function calcMaxTags() {
405
+ if (!tagsRef.value) return 0
406
+
407
+ const tags = tagsRef.value.querySelectorAll('.x-tag')
408
+ const tagsArray = Array.from(tags)
409
+
410
+ let totalWidth = 0
411
+ let tagsCount = 0
412
+
413
+ const maxWidth = tagsRef.value.offsetWidth - 30
414
+
415
+ for (let i = 0; i < tagsArray.length; i++) {
416
+ const tag = tagsArray[i] as HTMLElement
417
+
418
+ tag.style.display = 'flex'
419
+
420
+ totalWidth += tag.offsetWidth
421
+
422
+ if (totalWidth < maxWidth) tagsCount++
423
+ else tag.style.display = 'none'
424
+ }
425
+
426
+ return tagsCount
427
+ }
428
+
429
+ watch(selected, (val) => {
430
+ handleTruncate()
431
+ }, { immediate: true, deep: true })
432
+
375
433
  const { styles, classes, className } = useTheme('Select', {}, props, { errorInternal })
376
434
 
377
435
  defineExpose({ focus, blur, reset, validate, setError })
@@ -406,18 +464,109 @@ defineExpose({ focus, blur, reset, validate, setError })
406
464
  <div v-else>&nbsp;</div>
407
465
  </template>
408
466
  </div>
409
- <x-popover
410
- v-else
411
- ref="popoverRef"
412
- :disabled="isDisabled"
413
- >
414
- <div
415
- :class="[classes.box]"
467
+ <template v-else>
468
+ <x-popover
469
+ ref="popoverRef"
470
+ :disabled="isDisabled"
471
+ >
472
+ <div
473
+ :class="[classes.box]"
474
+ >
475
+ <template v-if="multiple && Array.isArray(selected) && selected.length > 0">
476
+ <div
477
+ ref="tagsRef"
478
+ class="flex gap-1 relative"
479
+ :class="{
480
+ 'flex-wrap': !truncate,
481
+ 'overflow-hidden': truncate,
482
+ }"
483
+ >
484
+ <x-tag
485
+ v-for="value in selected"
486
+ :key="value"
487
+ size="xs"
488
+ removable
489
+ :outlined="!(isDisabled || options?.find((i) => i.value === value)?.disabled)"
490
+ :disabled="isDisabled || options?.find((i) => i.value === value)?.disabled"
491
+ @remove="(e: Event) => { handleRemove(e, value) }"
492
+ >{{ getLabel(value) }}</x-tag>
493
+
494
+ <div
495
+ v-if="showCountTag"
496
+ :class="classes.truncateCounter"
497
+ @click.stop="multipleHiddenRef?.toggle()"
498
+ >+{{ hiddenTags }}</div>
499
+ </div>
500
+ </template>
501
+ <template v-else-if="!multiple && !isEmpty(selected)">
502
+ {{ getLabel(selected) }}
503
+ </template>
504
+
505
+ <template v-else>
506
+ <div
507
+ v-if="placeholder"
508
+ class="text-secondary-400 dark:text-secondary-500"
509
+ >
510
+ {{ placeholder }}
511
+ </div>
512
+ <div v-else>&nbsp;</div>
513
+ </template>
514
+ </div>
515
+
516
+ <template #content>
517
+ <x-popover-container
518
+ :class="classes.content"
519
+ >
520
+ <slot name="content-header">
521
+ <div v-if="filterable" :class="classes.search">
522
+ <x-input
523
+ ref="filterRef"
524
+ v-model="filter"
525
+ :placeholder="filterPlaceholder"
526
+ skip-form-registry
527
+ size="sm"
528
+ />
529
+ </div>
530
+ </slot>
531
+ <div v-bind="containerProps" :class="classes.contentBody">
532
+ <div v-bind="wrapperProps">
533
+ <x-menu-item
534
+ v-for="item in list"
535
+ :key="item.index"
536
+ ref="itemsRef"
537
+ :item="item.data"
538
+ :size="size"
539
+ :disabled="item.data.disabled"
540
+ :selected="item.index === selectedIndex"
541
+ :color="color"
542
+ filled
543
+ @click="() => !multiple && popoverRef?.hide()"
544
+ >
545
+ <template #prefix><slot name="prefix" :item="item.data">{{ item.data.prefix }}</slot></template>
546
+ <slot name="label" :item="item.data"></slot>
547
+ <template #suffix><slot name="suffix" :item="item.data">{{ item.data.suffix }}</slot></template>
548
+ </x-menu-item>
549
+ </div>
550
+ <div v-if="list.length === 0" class="p-2 text-center text-secondary-400">
551
+ No options
552
+ </div>
553
+ </div>
554
+ <slot name="content-footer"></slot>
555
+ </x-popover-container>
556
+ </template>
557
+ </x-popover>
558
+ <x-popover
559
+ v-if="multiple && truncate && showCountTag"
560
+ ref="multipleHiddenRef"
561
+ :popper-show-triggers="[]"
562
+ :popper-hide-triggers="[]"
563
+ class="inline-block !absolute right-0"
564
+ placement="auto-start"
416
565
  >
417
- <template v-if="multiple && Array.isArray(selected) && selected.length > 0">
418
- <div class="flex gap-1 flex-wrap">
566
+ <template #content>
567
+ <x-popover-container class="p-2 flex gap-2 flex-wrap">
419
568
  <x-tag
420
- v-for="value in selected"
569
+ v-for="value in selected?.slice(selected.length - hiddenTags)"
421
570
  :key="value"
422
571
  size="xs"
423
572
  removable
@@ -425,61 +574,10 @@ defineExpose({ focus, blur, reset, validate, setError })
425
574
  :disabled="isDisabled || options?.find((i) => i.value === value)?.disabled"
426
575
  @remove="(e: Event) => { handleRemove(e, value) }"
427
576
  >{{ getLabel(value) }}</x-tag>
428
- </div>
577
+ </x-popover-container>
429
578
  </template>
430
- <template v-else-if="!multiple && !isEmpty(selected)">
431
- {{ getLabel(selected) }}
432
- </template>
433
-
434
- <template v-else>
435
- <div
436
- v-if="placeholder"
437
- class="text-secondary-400 dark:text-secondary-500"
438
- >
439
- {{ placeholder }}
440
- </div>
441
- <div v-else>&nbsp;</div>
442
- </template>
443
- </div>
444
-
445
- <template #content>
446
- <x-popover-container
447
- :class="classes.content"
448
- >
449
- <slot name="content-header">
450
- <div v-if="filterable" :class="classes.search">
451
- <x-input
452
- ref="filterRef"
453
- v-model="filter"
454
- :placeholder="filterPlaceholder"
455
- skip-form-registry
456
- size="sm"
457
- />
458
- </div>
459
- </slot>
460
- <div v-bind="containerProps" :class="classes.contentBody">
461
- <div v-bind="wrapperProps">
462
- <x-menu-item
463
- v-for="item in list"
464
- :key="item.index"
465
- ref="itemsRef"
466
- :item="item.data"
467
- :size="size"
468
- :disabled="item.data.disabled"
469
- :selected="item.index === selectedIndex"
470
- :color="color"
471
- filled
472
- @click="() => !multiple && popoverRef?.hide()"
473
- />
474
- </div>
475
- <div v-if="list.length === 0" class="p-2 text-center text-secondary-400">
476
- No options
477
- </div>
478
- </div>
479
- <slot name="content-footer"></slot>
480
- </x-popover-container>
481
- </template>
482
- </x-popover>
579
+ </x-popover>
580
+ </template>
483
581
 
484
582
  <select
485
583
  :id="id"
@@ -27,6 +27,8 @@ const theme: SelectTheme = {
27
27
  return classes
28
28
  },
29
29
 
30
+ truncateCounter: 'absolute right-0 cursor-pointer hover:bg-secondary-200 bg-secondary-100 text-secondary-800 rounded px-1 py-0.5 text-xs',
31
+
30
32
  content: 'p-1',
31
33
 
32
34
  search: 'p-1 mb-0.5',
@@ -91,9 +91,8 @@ const props = defineProps({
91
91
  },
92
92
  })
93
93
 
94
- const selected = defineModel<number | number[]>('selected')
95
-
96
- const hasSelected = computed(() => typeof selected.value === 'number')
94
+ const selected = defineModel<number | string>('selected')
95
+ const hasSelected = computed(() => typeof selected.value !== 'undefined')
97
96
 
98
97
  type internalT = T & {
99
98
  __expanded?: boolean;
@@ -53,7 +53,7 @@ const { styles, classes, className } = useTheme('Tag', {}, props)
53
53
  <template>
54
54
  <component
55
55
  :is="tag"
56
- class="text-[color:var(--x-tag-text)] dark:text-[color:var(--x-tag-dark-text)] border"
56
+ class="text-[color:var(--x-tag-text)] dark:text-[color:var(--x-tag-dark-text)] border relative"
57
57
  :style="styles"
58
58
  :class="
59
59
  [
@@ -67,16 +67,18 @@ const { styles, classes, className } = useTheme('Tag', {}, props)
67
67
  >
68
68
  <span
69
69
  v-if="removable"
70
- class="max-w-full truncate"
70
+ class="max-w-full truncate pr-4"
71
71
  >
72
72
  <slot></slot>
73
- <x-icon
74
- :size="closeIconSize"
75
- :icon="closeIcon"
76
- class="ml-1.5 -mt-0.5 cursor-pointer transition-colors duration-150"
77
- :class="[disabled ? 'text-secondary-400' : 'hover:text-secondary-500']"
78
- @click="(e: Event) => !disabled && $emit('remove', e)"
79
- />
73
+ <div class="absolute right-1.5 top-0 h-full flex items-center">
74
+ <x-icon
75
+ :size="closeIconSize"
76
+ :icon="closeIcon"
77
+ class="cursor-pointer transition-colors duration-150"
78
+ :class="[disabled ? 'text-secondary-400' : 'hover:text-secondary-500']"
79
+ @click="(e: Event) => !disabled && $emit('remove', e)"
80
+ />
81
+ </div>
80
82
  </span>
81
83
 
82
84
  <slot v-else></slot>