@dolanske/vui 1.4.1 → 1.4.2

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 (140) hide show
  1. package/package.json +1 -2
  2. package/src/App.vue +0 -103
  3. package/src/components/Accordion/Accordion.vue +0 -98
  4. package/src/components/Accordion/AccordionGroup.vue +0 -49
  5. package/src/components/Accordion/accordion.scss +0 -97
  6. package/src/components/Alert/Alert.vue +0 -59
  7. package/src/components/Alert/alert.scss +0 -162
  8. package/src/components/Avatar/Avatar.vue +0 -53
  9. package/src/components/Avatar/avatar.scss +0 -52
  10. package/src/components/Badge/Badge.vue +0 -21
  11. package/src/components/Badge/badge.scss +0 -210
  12. package/src/components/Breadcrumbs/BreadcrumbItem.vue +0 -26
  13. package/src/components/Breadcrumbs/Breadcrumbs.vue +0 -29
  14. package/src/components/Breadcrumbs/breadcrumbs.scss +0 -31
  15. package/src/components/Button/Button.vue +0 -86
  16. package/src/components/Button/button.scss +0 -292
  17. package/src/components/ButtonGroup/ButtonGroup.vue +0 -28
  18. package/src/components/ButtonGroup/button-group.scss +0 -51
  19. package/src/components/Calendar/Calendar.vue +0 -66
  20. package/src/components/Calendar/calendar.scss +0 -88
  21. package/src/components/Card/Card.vue +0 -48
  22. package/src/components/Card/card.scss +0 -55
  23. package/src/components/Checkbox/Checkbox.vue +0 -54
  24. package/src/components/Checkbox/checkbox.scss +0 -80
  25. package/src/components/CopyClipboard/CopyClipboard.vue +0 -98
  26. package/src/components/CopyClipboard/copy-clipboard.scss +0 -25
  27. package/src/components/Divider/Divider.vue +0 -38
  28. package/src/components/Divider/divider.scss +0 -37
  29. package/src/components/Drawer/Drawer.vue +0 -102
  30. package/src/components/Drawer/drawer.scss +0 -37
  31. package/src/components/Dropdown/Dropdown.vue +0 -120
  32. package/src/components/Dropdown/DropdownItem.vue +0 -33
  33. package/src/components/Dropdown/DropdownTitle.vue +0 -14
  34. package/src/components/Dropdown/dropdown-item.scss +0 -84
  35. package/src/components/Dropdown/dropdown.scss +0 -53
  36. package/src/components/Flex/Flex.vue +0 -113
  37. package/src/components/Grid/Grid.vue +0 -87
  38. package/src/components/Input/Color.vue +0 -26
  39. package/src/components/Input/Counter.vue +0 -66
  40. package/src/components/Input/Dropzone.vue +0 -65
  41. package/src/components/Input/File.vue +0 -15
  42. package/src/components/Input/Input.vue +0 -123
  43. package/src/components/Input/Password.vue +0 -35
  44. package/src/components/Input/Textarea.vue +0 -78
  45. package/src/components/Input/input.scss +0 -302
  46. package/src/components/Kbd/Kbd.vue +0 -48
  47. package/src/components/Kbd/KbdGroup.vue +0 -28
  48. package/src/components/Kbd/kbd.scss +0 -19
  49. package/src/components/Modal/Confirm.vue +0 -56
  50. package/src/components/Modal/Modal.vue +0 -103
  51. package/src/components/Modal/modal.scss +0 -54
  52. package/src/components/OTP/OTP.vue +0 -133
  53. package/src/components/OTP/OTPItem.vue +0 -37
  54. package/src/components/OTP/otp.scss +0 -84
  55. package/src/components/Pagination/Pagination.vue +0 -92
  56. package/src/components/Pagination/pagination.ts +0 -78
  57. package/src/components/Popout/Popout.vue +0 -73
  58. package/src/components/Popout/popout.scss +0 -16
  59. package/src/components/Progress/Progress.vue +0 -103
  60. package/src/components/Progress/progress.scss +0 -47
  61. package/src/components/Radio/Radio.vue +0 -38
  62. package/src/components/Radio/RadioGroup.vue +0 -34
  63. package/src/components/Radio/radio.scss +0 -78
  64. package/src/components/Select/Select.vue +0 -212
  65. package/src/components/Select/select.scss +0 -82
  66. package/src/components/Sheet/Sheet.vue +0 -106
  67. package/src/components/Sheet/sheet.scss +0 -71
  68. package/src/components/Sidebar/Sidebar.vue +0 -116
  69. package/src/components/Sidebar/sidebar.scss +0 -124
  70. package/src/components/Skeleton/Skeleton.vue +0 -43
  71. package/src/components/Skeleton/skeleton.scss +0 -14
  72. package/src/components/Spinner/Spinner.vue +0 -42
  73. package/src/components/Spinner/spinner.scss +0 -47
  74. package/src/components/Switch/Switch.vue +0 -31
  75. package/src/components/Switch/switch.scss +0 -93
  76. package/src/components/Table/Cell.vue +0 -23
  77. package/src/components/Table/Head.vue +0 -66
  78. package/src/components/Table/Root.vue +0 -66
  79. package/src/components/Table/SelectAll.vue +0 -23
  80. package/src/components/Table/SelectRow.vue +0 -30
  81. package/src/components/Table/index.ts +0 -7
  82. package/src/components/Table/table.scss +0 -155
  83. package/src/components/Table/table.ts +0 -248
  84. package/src/components/Tabs/Tab.vue +0 -25
  85. package/src/components/Tabs/Tabs.vue +0 -90
  86. package/src/components/Tabs/tabs.scss +0 -87
  87. package/src/components/Toast/Toasts.vue +0 -52
  88. package/src/components/Toast/toast.scss +0 -45
  89. package/src/components/Toast/toast.ts +0 -75
  90. package/src/components/Tooltip/Tooltip.vue +0 -78
  91. package/src/components/Tooltip/tooltip.scss +0 -5
  92. package/src/examples/ExampleAccordions.vue +0 -69
  93. package/src/examples/ExampleAlerts.vue +0 -78
  94. package/src/examples/ExampleAvatars.vue +0 -44
  95. package/src/examples/ExampleBadges.vue +0 -48
  96. package/src/examples/ExampleBreadcrumbs.vue +0 -46
  97. package/src/examples/ExampleButtons.vue +0 -148
  98. package/src/examples/ExampleCalendars.vue +0 -40
  99. package/src/examples/ExampleCards.vue +0 -94
  100. package/src/examples/ExampleCheckboxes.vue +0 -123
  101. package/src/examples/ExampleCopyClipboard.vue +0 -47
  102. package/src/examples/ExampleDividers.vue +0 -39
  103. package/src/examples/ExampleDrawers.vue +0 -67
  104. package/src/examples/ExampleDropdowns.vue +0 -114
  105. package/src/examples/ExampleFlexGrid.vue +0 -124
  106. package/src/examples/ExampleInputs.vue +0 -236
  107. package/src/examples/ExampleKBD.vue +0 -65
  108. package/src/examples/ExampleModals.vue +0 -143
  109. package/src/examples/ExamplePalette.vue +0 -165
  110. package/src/examples/ExamplePopouts.vue +0 -41
  111. package/src/examples/ExampleSheets.vue +0 -77
  112. package/src/examples/ExampleSidebars.vue +0 -276
  113. package/src/examples/ExampleSkeletons.vue +0 -26
  114. package/src/examples/ExampleSpinners.vue +0 -80
  115. package/src/examples/ExampleTables.vue +0 -359
  116. package/src/examples/ExampleTabs.vue +0 -142
  117. package/src/examples/ExampleToasts.vue +0 -96
  118. package/src/examples/ExampleTooltips.vue +0 -70
  119. package/src/examples/shared/ExampleColor.vue +0 -28
  120. package/src/index.ts +0 -116
  121. package/src/internal/Backdrop/Backdrop.vue +0 -22
  122. package/src/internal/Backdrop/backdrop.scss +0 -34
  123. package/src/main.ts +0 -5
  124. package/src/shared/helpers.ts +0 -124
  125. package/src/shared/slots.ts +0 -61
  126. package/src/shared/theme.ts +0 -22
  127. package/src/shared/types.ts +0 -29
  128. package/src/style/animation.scss +0 -50
  129. package/src/style/core.scss +0 -133
  130. package/src/style/fonts.scss +0 -73
  131. package/src/style/layout.scss +0 -179
  132. package/src/style/media-query.scss +0 -29
  133. package/src/style/reset.scss +0 -135
  134. package/src/style/text.scss +0 -137
  135. package/src/style/theme.scss +0 -195
  136. package/src/style/tooltip.scss +0 -146
  137. package/src/style/typography.scss +0 -435
  138. package/src/style/utils.scss +0 -36
  139. package/src/style.scss +0 -1
  140. package/src/vite-env.d.ts +0 -1
@@ -1,78 +0,0 @@
1
- import { createArray } from '../../shared/helpers'
2
-
3
- export interface Pagination {
4
- totalItems: number
5
- currentPage: number
6
- perPage: number
7
- totalPages: number
8
- startPage: number
9
- endPage: number
10
- startIndex: number
11
- endIndex: number
12
- pages: number[]
13
- }
14
-
15
- export function paginate(
16
- totalItems: number,
17
- currentPage: number = 1,
18
- perPage: number = 15,
19
- maxPages: number = 5,
20
- ): Pagination {
21
- // calculate total pages
22
- const totalPages = Math.ceil(totalItems / perPage)
23
-
24
- // ensure current page isn't out of range
25
- if (currentPage < 1) {
26
- currentPage = 1
27
- }
28
- else if (currentPage > totalPages) {
29
- currentPage = totalPages
30
- }
31
-
32
- let startPage: number, endPage: number
33
- if (totalPages <= maxPages) {
34
- // total pages less than max so show all pages
35
- startPage = 1
36
- endPage = totalPages
37
- }
38
- else {
39
- // total pages more than max so calculate start and end pages
40
- const maxPagesBeforeCurrentPage = Math.floor(maxPages / 2)
41
- const maxPagesAfterCurrentPage = Math.ceil(maxPages / 2) - 1
42
- if (currentPage <= maxPagesBeforeCurrentPage) {
43
- // current page near the start
44
- startPage = 1
45
- endPage = maxPages
46
- }
47
- else if (currentPage + maxPagesAfterCurrentPage >= totalPages) {
48
- // current page near the end
49
- startPage = totalPages - maxPages + 1
50
- endPage = totalPages
51
- }
52
- else {
53
- // current page somewhere in the middle
54
- startPage = currentPage - maxPagesBeforeCurrentPage
55
- endPage = currentPage + maxPagesAfterCurrentPage
56
- }
57
- }
58
-
59
- // calculate start and end item indexes
60
- const startIndex = (currentPage - 1) * perPage
61
- const endIndex = Math.min(startIndex + perPage - 1, totalItems - 1)
62
-
63
- // create an array of pages to ng-repeat in the pager control
64
- const pages = createArray((endPage + 1) - startPage).map(i => startPage + i)
65
-
66
- // return object with all pager properties required by the view
67
- return {
68
- totalItems,
69
- currentPage,
70
- perPage,
71
- totalPages,
72
- startPage,
73
- endPage,
74
- startIndex,
75
- endIndex,
76
- pages,
77
- }
78
- }
@@ -1,73 +0,0 @@
1
- <script setup lang='ts'>
2
- import type { Placement, PopoutMaybeElement } from '../../shared/types'
3
- import { autoUpdate, flip, offset, shift, useFloating } from '@floating-ui/vue'
4
- import { onClickOutside } from '@vueuse/core'
5
- import { toRef, useTemplateRef, watch } from 'vue'
6
- import { getPlacementAnimationName } from '../../shared/helpers'
7
- import './popout.scss'
8
-
9
- export interface Props {
10
- /**
11
- * Reference to the HTML element the Popout is anchored to
12
- */
13
- anchor: PopoutMaybeElement<HTMLElement>
14
- /**
15
- * Override the autoPlacement option
16
- */
17
- placement?: Placement
18
- /**
19
- * Distance between the anchor and the rendered tooltip
20
- */
21
- offset?: number
22
- /**
23
- * Set the visibility of the dropdown
24
- */
25
- visible: boolean
26
- }
27
-
28
- const props = withDefaults(defineProps<Props>(), {
29
- offset: 8,
30
- placement: 'top',
31
- })
32
-
33
- const emit = defineEmits<{
34
- clickOutside: []
35
- }>()
36
- const popoutRef = useTemplateRef('popout')
37
- const anchorRef = toRef(props.anchor)
38
-
39
- const { floatingStyles, update } = useFloating(anchorRef, popoutRef, {
40
- whileElementsMounted: autoUpdate,
41
- strategy: 'fixed',
42
- transform: false,
43
- placement: props.placement,
44
- middleware: [
45
- shift({ padding: 8 }),
46
- flip(),
47
- offset(props.offset),
48
- ],
49
- })
50
-
51
- // Make sure to update the popout when the anchor is mounted
52
- watch(() => props.anchor, (value) => {
53
- if (value) {
54
- anchorRef.value = value
55
- update()
56
- }
57
- })
58
-
59
- onClickOutside(popoutRef, () => emit('clickOutside'))
60
- </script>
61
-
62
- <template>
63
- <Transition :name="getPlacementAnimationName(props.placement)">
64
- <div
65
- v-if="props.visible"
66
- ref="popout"
67
- :style="floatingStyles"
68
- class="vui-popout"
69
- >
70
- <slot />
71
- </div>
72
- </Transition>
73
- </template>
@@ -1,16 +0,0 @@
1
- .vui-popout {
2
- border-radius: var(--border-radius);
3
- box-shadow: var(--box-shadow);
4
- min-width: 80px;
5
- background-color: var(--color-bg-medium);
6
- border-radius: var(--border-radius-m);
7
- z-index: var(--z-popout);
8
- position: fixed;
9
- }
10
-
11
- :root.light {
12
- .vui-popout {
13
- background-color: var(--color-bg);
14
- box-shadow: var(--box-shadow-strong);
15
- }
16
- }
@@ -1,103 +0,0 @@
1
- <script setup lang='ts'>
2
- import { whenever } from '@vueuse/core'
3
- import { computed, onMounted, useTemplateRef, watch, watchEffect } from 'vue'
4
- import { clamp, delay, formatUnitValue, isNil, randomMinMax } from '../../shared/helpers'
5
- import './progress.scss'
6
-
7
- interface Props {
8
- /**
9
- * Will randomly increment but never actually reach the end
10
- */
11
- fake?: boolean
12
- /**
13
- * Indicator color. Use CSS color values or variables
14
- */
15
- color?: string
16
- /**
17
- * Displays loader at the top of the page. It is only displayed when the
18
- * progress is between 0 and 100 (exclusive).
19
- */
20
- fixed?: boolean
21
- /**
22
- * Height
23
- */
24
- height?: number | string
25
- }
26
-
27
- const {
28
- fake,
29
- color = 'var(--color-accent)',
30
- fixed,
31
- height,
32
- } = defineProps<Props>()
33
-
34
- const emit = defineEmits<{
35
- done: []
36
- }>()
37
-
38
- const progressAmount = defineModel<number>({
39
- default: 0,
40
- set(value) {
41
- return clamp(0, 100, value)
42
- },
43
- })
44
-
45
- // Set height programatically
46
- const progressRef = useTemplateRef('progress')
47
-
48
- watchEffect(() => {
49
- if (progressRef.value && !isNil(height)) {
50
- progressRef.value.style.setProperty(
51
- '--vui-progress-height',
52
- formatUnitValue(height),
53
- )
54
- }
55
- })
56
-
57
- whenever(() => fake, fakeIncrement)
58
-
59
- // Automatically / randomly increment but never reach 100% until
60
- async function fakeIncrement() {
61
- if (fake && progressAmount.value < 100) {
62
- if (progressAmount.value > 90) {
63
- // Only in crement by the fraction of the remaining amount
64
- progressAmount.value += (100 - progressAmount.value) * 0.05
65
- await delay(randomMinMax(500, 3000))
66
- }
67
- else {
68
- progressAmount.value += randomMinMax(1, 10)
69
- await delay(randomMinMax(200, 12000))
70
- }
71
- fakeIncrement()
72
- }
73
- }
74
-
75
- watch(progressAmount, (v) => {
76
- if (v >= 100) {
77
- emit('done')
78
- }
79
- })
80
-
81
- onMounted(fakeIncrement)
82
-
83
- const w = computed(() => `${clamp(0, 100, progressAmount.value)}%`)
84
- const bC = computed(() => color)
85
- </script>
86
-
87
- <template>
88
- <div
89
- ref="progress"
90
- class="vui-progress"
91
- :class="{
92
- fixed,
93
- 'fixed-active': progressAmount > 0 && progressAmount < 100,
94
- }"
95
- >
96
- <div
97
- class="vui-progress-indicator" :style="{
98
- width: w,
99
- backgroundColor: bC,
100
- }"
101
- />
102
- </div>
103
- </template>
@@ -1,47 +0,0 @@
1
- .vui-progress {
2
- --vui-progress-height: 3px;
3
-
4
- display: block;
5
- width: 100%;
6
- position: relative;
7
- border-radius: 999px;
8
- background-color: var(--color-bg-raised);
9
- overflow: hidden;
10
- height: var(--vui-progress-height);
11
-
12
- &.fixed {
13
- transition: var(--transition-slow);
14
- transition-property: height;
15
- position: fixed;
16
- inset: 0;
17
- bottom: unset;
18
- height: 0;
19
- border-radius: none !important;
20
-
21
- &.fixed-active {
22
- height: var(--vui-progress-height);
23
- }
24
-
25
- .vui-progress-indicator {
26
- border-top-left-radius: unset !important;
27
- border-bottom-left-radius: unset !important;
28
- }
29
- }
30
-
31
- .vui-progress-indicator {
32
- position: absolute;
33
- z-index: var(--z-active);
34
- inset: 0;
35
- right: unset;
36
- width: 0;
37
- border-radius: 999px;
38
- background-color: var(--color-text-lighter);
39
- transition: var(--transition-slow);
40
- }
41
- }
42
-
43
- :root.light {
44
- .vui-progress-indicator {
45
- background-color: var(--);
46
- }
47
- }
@@ -1,38 +0,0 @@
1
- <script setup lang='ts'>
2
- import { Icon } from '@iconify/vue'
3
- import { computed, useId } from 'vue'
4
- import './radio.scss'
5
-
6
- export interface RadioProps {
7
- label?: string
8
- disabled?: boolean
9
- value: any
10
- accent?: boolean
11
- }
12
-
13
- const {
14
- label,
15
- disabled,
16
- value,
17
- accent,
18
- } = defineProps<RadioProps>()
19
- const slots = defineSlots()
20
- const checked = defineModel()
21
- const id = useId()
22
- const isChecked = computed(() => value === checked.value)
23
- </script>
24
-
25
- <template>
26
- <div class="vui-radio" :class="{ disabled: !!disabled, checked: isChecked, accent }">
27
- <input :id v-model="checked" type="radio" :value :checked="isChecked" :disabled>
28
- <label :for="id">
29
- <span class="vui-radio-icon">
30
- <Icon :icon="isChecked ? 'ph:radio-button-fill' : 'ph:circle'" />
31
- </span>
32
- <p v-if="!slots.default" class="vui-radio-content">{{ label || value }}</p>
33
- <div v-else class="vui-radio-content">
34
- <slot />
35
- </div>
36
- </label>
37
- </div>
38
- </template>
@@ -1,34 +0,0 @@
1
- <script setup lang='ts'>
2
- import type { FlexProps } from '../Flex/Flex.vue'
3
- import { enforceSlotType, useFlattenedSlot } from '../../shared/slots'
4
- import Flex from '../Flex/Flex.vue'
5
-
6
- interface Props extends FlexProps {
7
- disabled?: boolean
8
- }
9
-
10
- const {
11
- disabled,
12
- ...flexProps
13
- } = defineProps<Props>()
14
-
15
- const slots = defineSlots()
16
-
17
- const checked = defineModel()
18
-
19
- const flattened = useFlattenedSlot(slots.default)
20
- enforceSlotType(flattened, 'Radio')
21
- </script>
22
-
23
- <template>
24
- <Flex v-bind="flexProps">
25
- <Component
26
- :is="vnode"
27
- v-for="vnode of flattened"
28
- :key="vnode.props.value"
29
- v-bind="vnode.props"
30
- v-model="checked"
31
- :class="{ disabled: disabled || vnode.props.disabled }"
32
- />
33
- </Flex>
34
- </template>
@@ -1,78 +0,0 @@
1
- .vui-radio {
2
- --radio-size: 24px;
3
-
4
- &.checked {
5
- .vui-radio-icon svg {
6
- color: var(--color-text);
7
- }
8
-
9
- &.accent .vui-radio-icon svg {
10
- color: var(--color-accent);
11
- }
12
- }
13
-
14
- &.disabled {
15
- .vui-radio-icon {
16
- opacity: 0.25;
17
- cursor: not-allowed;
18
- }
19
-
20
- input + label .vui-radio-content {
21
- opacity: 0.4;
22
- cursor: not-allowed;
23
- }
24
- }
25
-
26
- .vui-radio-icon {
27
- cursor: pointer;
28
- width: var(--radio-size);
29
- height: var(--radio-size);
30
-
31
- svg {
32
- width: 100%;
33
- height: 100%;
34
- color: var(--color-text);
35
- }
36
- }
37
-
38
- input {
39
- width: 1px;
40
- height: 1px;
41
- position: absolute;
42
- overflow: hidden;
43
- outline: none !important;
44
- opacity: 0;
45
-
46
- &:focus-visible + label .vui-radio-icon {
47
- outline: 2px solid var(--color-text);
48
- border-radius: 999px;
49
- }
50
-
51
- & + label {
52
- display: grid;
53
- grid-template-columns: 20px 1fr;
54
- gap: 10px;
55
- font-size: var(--font-size-s);
56
- color: var(--color-text);
57
- user-select: none;
58
-
59
- .vui-radio-content {
60
- display: block;
61
-
62
- &:is(p) {
63
- display: flex;
64
- align-items: center;
65
- min-height: var(--radio-size);
66
- font-size: var(--font-size-m);
67
- // line-height: var(--radio-size);
68
- }
69
- }
70
- }
71
- }
72
- }
73
-
74
- :root.light {
75
- .vui-radio.checked.accent .vui-radio-icon svg {
76
- color: var(--color-accent);
77
- }
78
- }
@@ -1,212 +0,0 @@
1
- <!-- eslint-disable ts/consistent-type-definitions -->
2
- <script setup lang='ts' generic="T">
3
- import { Icon } from '@iconify/vue'
4
- import { computed, onMounted, ref, useId, useTemplateRef } from 'vue'
5
- import { searchString } from '../../shared/helpers'
6
- import Button from '../Button/Button.vue'
7
- import Dropdown from '../Dropdown/Dropdown.vue'
8
- import DropdownItem from '../Dropdown/DropdownItem.vue'
9
- import DropdownTitle from '../Dropdown/DropdownTitle.vue'
10
- import Input from '../Input/Input.vue'
11
- import '../Input/input.scss'
12
- import './select.scss'
13
-
14
- export type SelectOption = {
15
- value: any
16
- label: string
17
- }
18
-
19
- type Props = {
20
- single?: boolean
21
- readonly?: boolean
22
- options: SelectOption[]
23
- label?: string
24
- placeholder?: string
25
- required?: boolean
26
- expand?: boolean
27
- hint?: string
28
- search?: boolean
29
- maxActiveOptions?: number
30
- showClear?: boolean
31
- disabled?: boolean
32
- errors?: string[]
33
- }
34
-
35
- const {
36
- expand,
37
- readonly,
38
- required,
39
- hint,
40
- placeholder,
41
- options,
42
- single = true,
43
- search,
44
- maxActiveOptions,
45
- showClear,
46
- disabled,
47
- errors = [] as string[],
48
- } = defineProps<Props>()
49
-
50
- const selected = defineModel<SelectOption[] | undefined>()
51
- const triggerRef = useTemplateRef('trigger')
52
-
53
- //
54
- function setValue(option: SelectOption) {
55
- if (single) {
56
- // Single
57
- if (selected.value?.some(o => o.value === option.value) && !required) {
58
- selected.value = undefined
59
- }
60
- else {
61
- selected.value = [option]
62
- }
63
- }
64
- else {
65
- if (selected.value && selected.value?.some(o => o.value === option.value)) {
66
- const values = selected.value.filter(o => o.value !== option.value)
67
- // Cant remove the last value if it's required
68
- if ((required && values.length > 0) || !required) {
69
- selected.value = values.length > 0 ? values : undefined
70
- }
71
- }
72
- else {
73
- if (!selected.value) {
74
- selected.value = [option]
75
- }
76
- else if (!maxActiveOptions || (selected.value.length < maxActiveOptions)) {
77
- selected.value?.push(option)
78
- }
79
- }
80
- }
81
- }
82
-
83
- // Search
84
- const searchStr = ref()
85
- const filteredOptions = computed(() => {
86
- if (!searchStr.value || searchStr.value.length === 0)
87
- return options
88
-
89
- return options.filter((option) => {
90
- return searchString(option.label, searchStr.value)
91
- })
92
- })
93
-
94
- // Render text inside the button
95
- const renderPlaceholder = computed(() => {
96
- // Render placeholder or a default select option
97
- if (!selected.value || selected.value.length === 0)
98
- return placeholder ?? 'Select an option'
99
-
100
- // Selected values
101
- if (single)
102
- return selected.value[0].label
103
-
104
- // If amount of selected exceeds the active capacity
105
- if (selected.value.length > 3) {
106
- return `${selected.value.length} selected`
107
- }
108
-
109
- // Just list ALL selected optionsx
110
- return selected.value.map(o => o.label).join(', ')
111
- })
112
-
113
- onMounted(() => {
114
- if (readonly && showClear) {
115
- console.warn('[Select] readonly and showClear are mutually exclusive props. The clear button will not show up if required is present.')
116
- }
117
- })
118
-
119
- const dropdownRef = useTemplateRef('dropdown')
120
-
121
- function clearValue() {
122
- selected.value = undefined
123
- dropdownRef.value?.close()
124
- }
125
-
126
- const id = useId()
127
- </script>
128
-
129
- <template>
130
- <div class="vui-input-container vui-select" :class="{ expand, required, readonly, disabled, 'has-errors': errors.length > 0 }">
131
- <Dropdown ref="dropdown" :expand @close="triggerRef?.focus({ preventScroll: true })">
132
- <template #trigger="{ toggle, isOpen }">
133
- <div class="vui-input vui-select-trigger-content">
134
- <label v-if="label" :for="id">{{ label }}</label>
135
- <p v-if="hint" class="vui-input-hint">
136
- {{ hint }}
137
- </p>
138
-
139
- <button
140
- :id
141
- ref="trigger"
142
- class="vui-input-style vui-select-trigger-container"
143
- :class="{ 'has-value': selected && selected.length > 0 }"
144
- :disabled
145
- @click="toggle"
146
- >
147
- <span>
148
- {{ renderPlaceholder }}
149
- </span>
150
- <template v-if="showClear && !required && (selected && selected.length > 0)">
151
- <div class="flex-1" />
152
- <Button
153
- plain
154
- icon="ph:x"
155
- square
156
- size="s"
157
- @click.stop="clearValue"
158
- />
159
- </template>
160
- <Icon :icon="isOpen ? 'ph:caret-up' : 'ph:caret-down'" />
161
- </button>
162
- </div>
163
- </template>
164
-
165
- <template #default="{ close, isOpen }">
166
- <DropdownTitle v-if="search" sticky>
167
- <Input
168
- v-model="searchStr"
169
- placeholder="Search..."
170
- :focus="isOpen"
171
- expand
172
- >
173
- <template #start>
174
- <Icon icon="ph:magnifying-glass" />
175
- </template>
176
- </Input>
177
- </DropdownTitle>
178
-
179
- <p v-if="filteredOptions.length === 0" class="vue-select-no-results">
180
- No results...
181
- </p>
182
-
183
- <DropdownItem
184
- v-for="option in filteredOptions"
185
- :key="option.value"
186
- :class="{ selected: selected?.find(v => v.value === option.value) }"
187
- :icon="selected?.find(v => v.value === option.value) ? 'ph:check-bold' : ''"
188
- @click="() => {
189
- if (readonly) return
190
-
191
- setValue(option)
192
- // In single mode, close modal after clicking
193
- if (single && !(required && selected && selected[0].value === option.value)) {
194
- close()
195
- }
196
- }"
197
- >
198
- {{ option.label }}
199
- </DropdownItem>
200
- </template>
201
- </Dropdown>
202
-
203
- <p v-if="maxActiveOptions && !single" class="vui-input-limit">
204
- {{ `${selected ? selected.length : 0}/${maxActiveOptions}` }}
205
- </p>
206
- <ul v-if="errors.length > 0" class="vui-input-errors">
207
- <li v-for="err in errors" :key="err">
208
- {{ err }}
209
- </li>
210
- </ul>
211
- </div>
212
- </template>