classcard-ui 0.2.1475 → 0.2.1476
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/README.md +24 -24
- package/dist/classcard-ui.common.js +86 -85
- package/dist/classcard-ui.common.js.map +1 -1
- package/dist/classcard-ui.css +1 -1
- package/dist/classcard-ui.umd.js +86 -85
- package/dist/classcard-ui.umd.js.map +1 -1
- package/dist/classcard-ui.umd.min.js +1 -1
- package/dist/classcard-ui.umd.min.js.map +1 -1
- package/package.json +83 -83
- package/src/App.vue +16 -16
- package/src/colorConfig.js +52 -52
- package/src/components/CAlertModal/CAlertModal.vue +179 -179
- package/src/components/CAlertModal/index.js +3 -3
- package/src/components/CAlerts/CAlerts.vue +114 -114
- package/src/components/CAlerts/index.js +2 -2
- package/src/components/CAnchorTabs/CAnchorTabs.vue +100 -100
- package/src/components/CAnchorTabs/index.js +2 -2
- package/src/components/CAnchorTag/CAnchorTag.vue +84 -84
- package/src/components/CAnchorTag/index.js +2 -2
- package/src/components/CAvatar/CAvatar.vue +230 -230
- package/src/components/CAvatar/index.js +2 -2
- package/src/components/CAvatarGroup/CAvatarGroup.vue +213 -213
- package/src/components/CAvatarGroup/index.js +2 -2
- package/src/components/CBasicTable/CBasicTable.vue +184 -184
- package/src/components/CBasicTable/index.js +2 -2
- package/src/components/CBreadcrumbs/CBreadcrumbs.vue +38 -38
- package/src/components/CBreadcrumbs/index.js +2 -2
- package/src/components/CButton/CButton.vue +239 -239
- package/src/components/CButton/index.js +2 -2
- package/src/components/CButtonGroup/CButtonGroup.vue +155 -155
- package/src/components/CButtonGroup/index.js +2 -2
- package/src/components/CButtonIcon/CButtonIcon.vue +166 -166
- package/src/components/CButtonIcon/index.js +2 -2
- package/src/components/CButtonLink/CButtonLink.vue +43 -43
- package/src/components/CButtonLink/index.js +2 -2
- package/src/components/CButtonSelect/CButtonSelect.vue +186 -186
- package/src/components/CButtonSelect/index.js +2 -2
- package/src/components/CButtonSelectBorder/CButtonSelectBorder.vue +265 -265
- package/src/components/CButtonSelectBorder/index.js +3 -3
- package/src/components/CButtonWithDropdown/CButtonWithDropdown.vue +152 -152
- package/src/components/CButtonWithDropdown/index.js +2 -2
- package/src/components/CCalendar/CCalendar.vue +443 -443
- package/src/components/CCalendar/index.js +3 -3
- package/src/components/CCard/CCard.vue +53 -53
- package/src/components/CCard/index.js +2 -2
- package/src/components/CCheckbox/CCheckbox.vue +200 -200
- package/src/components/CCheckbox/index.js +2 -2
- package/src/components/CCircularButton/CCircularButton.vue +57 -57
- package/src/components/CCircularButton/index.js +2 -2
- package/src/components/CCollapsibleSection/CCollapsibleSection.vue +121 -121
- package/src/components/CCollapsibleSection/index.js +2 -2
- package/src/components/CColorDots/CColorDots.vue +52 -52
- package/src/components/CColorDots/index.js +3 -3
- package/src/components/CConfirmActionModal/CConfirmActionModal.vue +221 -221
- package/src/components/CConfirmActionModal/index.js +3 -3
- package/src/components/CDatepicker/CDatepicker.vue +235 -235
- package/src/components/CDatepicker/index.js +2 -2
- package/src/components/CDualSelect/CDualSelect.vue +193 -193
- package/src/components/CDualSelect/index.js +2 -2
- package/src/components/CEditor/CEditor.vue +114 -114
- package/src/components/CEditor/index.js +2 -2
- package/src/components/CFormSectionHeading/CFormSectionHeading.vue +76 -76
- package/src/components/CFormSectionHeading/index.js +2 -2
- package/src/components/CGroupedFilterDropdown/CGroupedFilterDropdown.vue +263 -263
- package/src/components/CGroupedFilterDropdown/index.js +2 -2
- package/src/components/CGroupedSelect/CGroupedSelect.vue +366 -366
- package/src/components/CGroupedSelect/index.js +3 -3
- package/src/components/CIcon/CIcon.vue +112 -112
- package/src/components/CIcon/index.js +2 -2
- package/src/components/CIconDropdown/CIconDropdown.vue +206 -206
- package/src/components/CIconDropdown/index.js +2 -2
- package/src/components/CIconSelect/CIconSelect.vue +182 -182
- package/src/components/CIconSelect/index.js +3 -3
- package/src/components/CInput/CInput.vue +173 -173
- package/src/components/CInput/index.js +2 -2
- package/src/components/CInputAddon/CInputAddon.vue +297 -297
- package/src/components/CInputAddon/index.js +2 -2
- package/src/components/CInputEmail/CInputEmail.vue +107 -107
- package/src/components/CInputEmail/index.js +2 -2
- package/src/components/CInsetTabs/CInsetTabs.vue +134 -134
- package/src/components/CInsetTabs/index.js +3 -3
- package/src/components/CModalHeading/CModalHeading.vue +22 -22
- package/src/components/CModalHeading/index.js +2 -2
- package/src/components/CModuleHelpLinks/CModuleHelpLinks.vue +88 -88
- package/src/components/CModuleHelpLinks/index.js +3 -3
- package/src/components/CMultiselect/CMultiselect.vue +1170 -1170
- package/src/components/CMultiselect/index.js +2 -2
- package/src/components/CMultiselectr/CMultiselectr.vue +44 -44
- package/src/components/CMultiselectr/index.js +2 -2
- package/src/components/CPageHeading/CPageHeading.vue +83 -83
- package/src/components/CPageHeading/index.js +2 -2
- package/src/components/CPagination/CPagination.vue +239 -239
- package/src/components/CPagination/index.js +2 -2
- package/src/components/CPhoneNumber/CPhoneNumber.vue +213 -213
- package/src/components/CPhoneNumber/index.js +2 -2
- package/src/components/CProgress/CProgress.vue +91 -91
- package/src/components/CProgress/index.js +2 -2
- package/src/components/CRadio/CRadio.vue +197 -197
- package/src/components/CRadio/index.js +2 -2
- package/src/components/CRadioGroup/CRadioGroup.vue +96 -96
- package/src/components/CRadioGroup/index.js +2 -2
- package/src/components/CRangeSlider/CRangeSlider.vue +55 -55
- package/src/components/CRangeSlider/index.js +2 -2
- package/src/components/CReorderableStackedList/CReorderableStackedList.vue +94 -94
- package/src/components/CReorderableStackedList/index.js +2 -2
- package/src/components/CSelect/CSelect.vue +1210 -1210
- package/src/components/CSelect/index.js +2 -2
- package/src/components/CSmallTimeline/CSmallTimeline.vue +40 -40
- package/src/components/CSmallTimeline/index.js +2 -2
- package/src/components/CStackedList/CStackedList.vue +162 -162
- package/src/components/CStackedList/index.js +2 -2
- package/src/components/CStats/CStats.vue +157 -157
- package/src/components/CStats/index.js +2 -2
- package/src/components/CSwitch/CSwitch.vue +200 -200
- package/src/components/CSwitch/index.js +2 -2
- package/src/components/CTabLazy/CTabLazy.vue +83 -83
- package/src/components/CTabLazy/index.js +2 -2
- package/src/components/CTable/CTable.vue +1114 -1114
- package/src/components/CTable/index.js +2 -2
- package/src/components/CTabs/CTabs.vue +250 -250
- package/src/components/CTabs/index.js +2 -2
- package/src/components/CTag/CTag.vue +109 -109
- package/src/components/CTag/index.js +2 -2
- package/src/components/CTextarea/CTextarea.vue +118 -118
- package/src/components/CTextarea/index.js +2 -2
- package/src/components/CTimeline/CTimeline.vue +237 -237
- package/src/components/CTimeline/index.js +2 -2
- package/src/components/CToolTip/CToolTip.vue +108 -108
- package/src/components/CToolTip/index.js +3 -3
- package/src/components/CUpload/CUpload.vue +331 -331
- package/src/components/CUpload/index.js +2 -2
- package/src/components/NumberAnimator.vue +112 -112
- package/src/components/index.js +57 -57
- package/src/helper.js +8 -8
- package/src/icons.js +831 -830
- package/src/main.js +22 -22
- package/src/stories/CAlertModal.stories.js +30 -30
- package/src/stories/CAlerts.stories.js +39 -39
- package/src/stories/CAnchorTabs.stories.js +29 -29
- package/src/stories/CAnchorTag.stories.js +38 -38
- package/src/stories/CAvatar.stories.js +38 -38
- package/src/stories/CAvatarGroup.stories.js +136 -136
- package/src/stories/CBasicTable.stories.js +316 -316
- package/src/stories/CBreadcrumbs.stories.js +24 -24
- package/src/stories/CButton.stories.js +49 -49
- package/src/stories/CButtonGroup.stories.js +43 -43
- package/src/stories/CButtonIcon.stories.js +27 -27
- package/src/stories/CButtonLink.stories.js +24 -24
- package/src/stories/CButtonSelect.stories.js +44 -44
- package/src/stories/CButtonSelectBorder.stories.js +56 -56
- package/src/stories/CButtonWithDropdown.stories.js +41 -41
- package/src/stories/CCalendar.stories.js +16 -16
- package/src/stories/CCard.stories.js +30 -30
- package/src/stories/CCheckbox.stories.js +38 -38
- package/src/stories/CCircularButton.stories.js +29 -29
- package/src/stories/CCollapsibleSection.stories.js +29 -29
- package/src/stories/CColorDots.stories.js +37 -37
- package/src/stories/CConfirmActionModal.stories.js +60 -60
- package/src/stories/CDatepicker.stories.js +31 -31
- package/src/stories/CDualSelect.stories.js +29 -29
- package/src/stories/CEditor.stories.js +30 -30
- package/src/stories/CFormSectionHeading.stories.js +37 -37
- package/src/stories/CGroupedFilterDropdown.stories.js +176 -176
- package/src/stories/CGroupedSelect.stories.js +103 -103
- package/src/stories/CIcon.stories.js +31 -31
- package/src/stories/CIconDropdown.stories.js +52 -52
- package/src/stories/CIconSelect.stories.js +45 -45
- package/src/stories/CInput.stories.js +36 -36
- package/src/stories/CInputAddon.stories.js +37 -37
- package/src/stories/CInputEmail.stories.js +27 -27
- package/src/stories/CInsetTabs.stories.js +48 -48
- package/src/stories/CModalHeading.stories.js +25 -25
- package/src/stories/CModuleHelpLinks.stories.js +25 -25
- package/src/stories/CMultiselect.stories.js +136 -136
- package/src/stories/CMultiselectr.stories.js +23 -23
- package/src/stories/CPageHeading.stories.js +32 -32
- package/src/stories/CPagination.stories.js +30 -30
- package/src/stories/CPhoneNumber.stories.js +37 -37
- package/src/stories/CProgress.stories.js +23 -23
- package/src/stories/CRadio.stories.js +44 -44
- package/src/stories/CRadioGroup.stories.js +51 -51
- package/src/stories/CRangeSlider.stories.js +23 -23
- package/src/stories/CReorderableStackedList.stories.js +23 -23
- package/src/stories/CSelect.stories.js +157 -157
- package/src/stories/CSmallTimeline.stories.js +26 -26
- package/src/stories/CStackedList.stories.js +37 -37
- package/src/stories/CStats.stories.js +53 -53
- package/src/stories/CSwitch.stories.js +28 -28
- package/src/stories/CTabLazy.stories.js +42 -42
- package/src/stories/CTable.stories.js +203 -203
- package/src/stories/CTabs.stories.js +36 -36
- package/src/stories/CTag.stories.js +37 -37
- package/src/stories/CTextarea.stories.js +32 -32
- package/src/stories/CTimeline.stories.js +26 -26
- package/src/stories/CToolTip.stories.js +27 -27
- package/src/stories/CUpload.stories.js +36 -36
- package/src/stories/Introduction.stories.mdx +207 -207
- package/src/stories/Page.vue +88 -88
- package/src/stories/assets/code-brackets.svg +0 -0
- package/src/stories/assets/colors.svg +0 -0
- package/src/stories/assets/comments.svg +0 -0
- package/src/stories/assets/direction.svg +0 -0
- package/src/stories/assets/flow.svg +0 -0
- package/src/stories/assets/plugin.svg +0 -0
- package/src/stories/assets/repo.svg +0 -0
- package/src/stories/assets/stackalt.svg +0 -0
- package/src/stories/header.css +26 -26
- package/src/stories/page.css +69 -69
- package/src/stories/utils.css +32 -32
|
@@ -1,1210 +1,1210 @@
|
|
|
1
|
-
<template>
|
|
2
|
-
<div>
|
|
3
|
-
<div
|
|
4
|
-
:class="`flex w-full items-center ${
|
|
5
|
-
label ? 'justify-between' : 'justify-end'
|
|
6
|
-
}`"
|
|
7
|
-
>
|
|
8
|
-
<div class="flex items-center" v-if="label">
|
|
9
|
-
<!-- label of select field -->
|
|
10
|
-
<label class="block text-sm font-medium text-gray-900">
|
|
11
|
-
{{ label }}
|
|
12
|
-
</label>
|
|
13
|
-
<!-- asterisk sign to render if field is required -->
|
|
14
|
-
<p v-if="isRequired" class="ml-1 text-red-600">*</p>
|
|
15
|
-
</div>
|
|
16
|
-
<button
|
|
17
|
-
v-if="actionBtn"
|
|
18
|
-
:id="getActionIDFn(actionBtn)"
|
|
19
|
-
class="block text-sm font-medium text-indigo-800 hover:underline"
|
|
20
|
-
@click="actionBtnEvent($event)"
|
|
21
|
-
>
|
|
22
|
-
{{ actionBtn }}
|
|
23
|
-
</button>
|
|
24
|
-
<span v-if="hint" class="text-sm text-gray-500">{{ hint }}</span>
|
|
25
|
-
</div>
|
|
26
|
-
<div :class="['relative', inputContainerClass]" ref="inputContainer">
|
|
27
|
-
<div :class="label || actionBtn ? 'mt-1' : ''">
|
|
28
|
-
<input
|
|
29
|
-
ref="c-select-input"
|
|
30
|
-
type="text"
|
|
31
|
-
v-model="selectSearch"
|
|
32
|
-
@click="handleInputClick"
|
|
33
|
-
@focus="handleInputFocus"
|
|
34
|
-
@input="search()"
|
|
35
|
-
@keydown="handleKeydown"
|
|
36
|
-
@keydown.enter.prevent="handleKeydown"
|
|
37
|
-
aria-haspopup="listbox"
|
|
38
|
-
aria-expanded="true"
|
|
39
|
-
aria-labelledby="listbox-label"
|
|
40
|
-
class="h-9 w-full cursor-pointer border py-2 pr-10 text-left text-sm"
|
|
41
|
-
:class="[
|
|
42
|
-
classes,
|
|
43
|
-
!isValidate && 'border-red-300',
|
|
44
|
-
inputClasses,
|
|
45
|
-
customBorderRadius,
|
|
46
|
-
toggleDropdown ? 'pl-10' : 'pl-3',
|
|
47
|
-
]"
|
|
48
|
-
:disabled="isDisabled"
|
|
49
|
-
autocomplete="off"
|
|
50
|
-
@blur="handleElementBlur"
|
|
51
|
-
:id="id"
|
|
52
|
-
:style="inputStyle"
|
|
53
|
-
/>
|
|
54
|
-
<button
|
|
55
|
-
class="absolute top-2.5 right-10 z-100 cursor-pointer"
|
|
56
|
-
:id="id + '_close_button'"
|
|
57
|
-
v-if="showCloseButton && (value == null || value.length == 0)"
|
|
58
|
-
@click.stop="handleCrossClick"
|
|
59
|
-
>
|
|
60
|
-
<c-icon
|
|
61
|
-
name="close"
|
|
62
|
-
type="outline-v2"
|
|
63
|
-
class="h-4 w-4 text-gray-400"
|
|
64
|
-
/>
|
|
65
|
-
</button>
|
|
66
|
-
<div
|
|
67
|
-
v-if="!toggleDropdown"
|
|
68
|
-
class="pointer-events-none absolute inset-0 left-3 flex h-9 items-center overflow-hidden pr-10"
|
|
69
|
-
>
|
|
70
|
-
<div
|
|
71
|
-
:class="['flex items-center gap-2 truncate', selectedOptionStyles]"
|
|
72
|
-
>
|
|
73
|
-
<div
|
|
74
|
-
class="flex shrink-0"
|
|
75
|
-
v-if="!addCheckBox && showImage && !selectSearch && value"
|
|
76
|
-
>
|
|
77
|
-
<c-avatar
|
|
78
|
-
v-if="value.photo"
|
|
79
|
-
size="extraextraextrasmall"
|
|
80
|
-
:image="value.photo"
|
|
81
|
-
:rounded="true"
|
|
82
|
-
></c-avatar>
|
|
83
|
-
<c-avatar
|
|
84
|
-
v-else
|
|
85
|
-
size="extraextraextrasmall"
|
|
86
|
-
:nameInitials="value.initials"
|
|
87
|
-
:rounded="true"
|
|
88
|
-
:isDynamicallyColored="coloredAvatars"
|
|
89
|
-
></c-avatar>
|
|
90
|
-
</div>
|
|
91
|
-
<div
|
|
92
|
-
class="flex shrink-0"
|
|
93
|
-
v-if="
|
|
94
|
-
addCheckBox &&
|
|
95
|
-
showImage &&
|
|
96
|
-
!selectSearch &&
|
|
97
|
-
selectedValuesArray &&
|
|
98
|
-
selectedValuesArray.length &&
|
|
99
|
-
selectedValuesArray[0].id
|
|
100
|
-
"
|
|
101
|
-
>
|
|
102
|
-
<c-avatar
|
|
103
|
-
v-if="selectedValuesArray[0].photo"
|
|
104
|
-
size="extraextraextrasmall"
|
|
105
|
-
:image="selectedValuesArray[0].photo"
|
|
106
|
-
:rounded="true"
|
|
107
|
-
></c-avatar>
|
|
108
|
-
<c-avatar
|
|
109
|
-
v-else
|
|
110
|
-
size="extraextraextrasmall"
|
|
111
|
-
:nameInitials="selectedValuesArray[0].initials"
|
|
112
|
-
:rounded="true"
|
|
113
|
-
:isDynamicallyColored="coloredAvatars"
|
|
114
|
-
></c-avatar>
|
|
115
|
-
</div>
|
|
116
|
-
<c-icon
|
|
117
|
-
v-if="icon && !showImage && !selectSearch"
|
|
118
|
-
:class="icon.class"
|
|
119
|
-
:name="icon.name"
|
|
120
|
-
:type="icon.type"
|
|
121
|
-
:viewBox="icon.viewBox"
|
|
122
|
-
>
|
|
123
|
-
</c-icon>
|
|
124
|
-
<div
|
|
125
|
-
v-if="addCheckBox"
|
|
126
|
-
class="flex flex-1 items-center overflow-hidden text-sm"
|
|
127
|
-
>
|
|
128
|
-
<p
|
|
129
|
-
class="block truncate"
|
|
130
|
-
v-if="
|
|
131
|
-
selectedValuesArray.length > 0 &&
|
|
132
|
-
selectedValuesArray[0].id &&
|
|
133
|
-
(!selectSearch || selectSearch == '')
|
|
134
|
-
"
|
|
135
|
-
>
|
|
136
|
-
{{ selectedValuesArray[0][renderOptionName] }}
|
|
137
|
-
</p>
|
|
138
|
-
<p v-else>
|
|
139
|
-
{{ !selectSearch || selectSearch == "" ? placeholder : null }}
|
|
140
|
-
</p>
|
|
141
|
-
<p
|
|
142
|
-
v-if="
|
|
143
|
-
selectedValuesArray.length > 1 &&
|
|
144
|
-
(!selectSearch || selectSearch == '') &&
|
|
145
|
-
selectedValuesArray[0].showExtra
|
|
146
|
-
"
|
|
147
|
-
class="ml-1 block"
|
|
148
|
-
>
|
|
149
|
-
+{{ selectedValuesArray.length - 1 }}
|
|
150
|
-
</p>
|
|
151
|
-
</div>
|
|
152
|
-
<p
|
|
153
|
-
v-else
|
|
154
|
-
:class="[
|
|
155
|
-
'flex items-center gap-2 truncate text-sm',
|
|
156
|
-
showImage ? 'ml-3' : '',
|
|
157
|
-
selectedValueClass,
|
|
158
|
-
]"
|
|
159
|
-
v-bind:style="{
|
|
160
|
-
...(shouldShowCustomFonts ? { fontFamily: selectedValue } : {}),
|
|
161
|
-
}"
|
|
162
|
-
>
|
|
163
|
-
{{
|
|
164
|
-
hasSelectedValue
|
|
165
|
-
? selectedValue
|
|
166
|
-
: !selectSearch || selectSearch == ""
|
|
167
|
-
? placeholder
|
|
168
|
-
: null
|
|
169
|
-
}}
|
|
170
|
-
<c-tag
|
|
171
|
-
v-if="value && value.badge"
|
|
172
|
-
:label="value.badge.label"
|
|
173
|
-
:color="value.badge.color"
|
|
174
|
-
class="shrink-0"
|
|
175
|
-
></c-tag>
|
|
176
|
-
</p>
|
|
177
|
-
</div>
|
|
178
|
-
</div>
|
|
179
|
-
<div
|
|
180
|
-
v-if="toggleDropdown"
|
|
181
|
-
class="pointer-events-none absolute inset-0 left-3 flex h-9 items-center overflow-hidden pr-10"
|
|
182
|
-
>
|
|
183
|
-
<div class="flex items-center gap-2">
|
|
184
|
-
<c-icon
|
|
185
|
-
name="search"
|
|
186
|
-
type="outline"
|
|
187
|
-
class="h-5 w-5 text-gray-400"
|
|
188
|
-
></c-icon>
|
|
189
|
-
<p v-if="!selectSearch" class="text-sm text-gray-500">Search</p>
|
|
190
|
-
</div>
|
|
191
|
-
</div>
|
|
192
|
-
<div class="pointer-events-none absolute top-2 right-3 flex">
|
|
193
|
-
<div
|
|
194
|
-
v-if="type == 'tertiary' ? showFocus : true"
|
|
195
|
-
class="pointer-events-none right-0 flex items-center"
|
|
196
|
-
>
|
|
197
|
-
<c-icon
|
|
198
|
-
name="chevron-down"
|
|
199
|
-
type="solid"
|
|
200
|
-
:class="[
|
|
201
|
-
'h-5 w-5 transition-transform duration-300',
|
|
202
|
-
toggleDropdown ? 'rotate-180' : '',
|
|
203
|
-
dropdownSelectorIconClass,
|
|
204
|
-
]"
|
|
205
|
-
></c-icon>
|
|
206
|
-
</div>
|
|
207
|
-
</div>
|
|
208
|
-
</div>
|
|
209
|
-
<transition
|
|
210
|
-
enter-active-class="transition ease-out duration-100"
|
|
211
|
-
enter-class="transform opacity-0 scale-95"
|
|
212
|
-
enter-to-class="transform opacity-100 scale-100"
|
|
213
|
-
leave-active-class="transition ease-in duration-75"
|
|
214
|
-
leave-class="transform opacity-100 scale-100"
|
|
215
|
-
leave-to-class="transform opacity-0 scale-95"
|
|
216
|
-
>
|
|
217
|
-
<div
|
|
218
|
-
v-if="toggleDropdown && !isDisabled"
|
|
219
|
-
:class="`${getDropdownPosition()} ${
|
|
220
|
-
useSticky ? 'sticky sm:absolute' : 'absolute'
|
|
221
|
-
} z-10 ${
|
|
222
|
-
isFooter && shouldOpenAbove
|
|
223
|
-
? 'mb-12 max-h-60'
|
|
224
|
-
: !isFooter && shouldOpenAbove
|
|
225
|
-
? 'mb-1 max-h-60'
|
|
226
|
-
: 'mt-1 max-h-80 pb-20'
|
|
227
|
-
} ${customWidth ? customWidth : 'w-full'}`"
|
|
228
|
-
>
|
|
229
|
-
<ul
|
|
230
|
-
tabindex="-1"
|
|
231
|
-
role="listbox"
|
|
232
|
-
aria-labelledby="listbox-label"
|
|
233
|
-
ref="optionsList"
|
|
234
|
-
:class="`max-h-60 overflow-auto overscroll-contain bg-white py-1 text-sm ring-1 ring-gray-900 ring-opacity-5 focus:outline-none ${getDropdownPosition2()} ${getDropdownShadow()} ${
|
|
235
|
-
isFooter ||
|
|
236
|
-
(showCreateOption &&
|
|
237
|
-
showCreateOptionAfterSearch &&
|
|
238
|
-
selectSearch &&
|
|
239
|
-
selectSearch.trim())
|
|
240
|
-
? 'rounded-t-md'
|
|
241
|
-
: 'rounded-md'
|
|
242
|
-
}`"
|
|
243
|
-
@mousedown="handlePreventBlur"
|
|
244
|
-
>
|
|
245
|
-
<li
|
|
246
|
-
v-if="addAction"
|
|
247
|
-
@mousedown="actionEvent($event)"
|
|
248
|
-
class="relative flex min-h-[36px] cursor-pointer select-none py-2 pl-3 pr-9 text-indigo-500 hover:bg-indigo-100 hover:text-indigo-700"
|
|
249
|
-
>
|
|
250
|
-
<c-icon
|
|
251
|
-
type="outline"
|
|
252
|
-
class="mr-1 h-5 w-5 text-indigo-400 group-hover:text-indigo-500"
|
|
253
|
-
name="plus"
|
|
254
|
-
></c-icon>
|
|
255
|
-
{{ addAction.label }}
|
|
256
|
-
</li>
|
|
257
|
-
<li v-if="headerSwitch" class="min-h-[36px] p-2 hover:bg-gray-100">
|
|
258
|
-
<c-switch
|
|
259
|
-
:label="headerSwitch.headerText"
|
|
260
|
-
class="text-sm"
|
|
261
|
-
direction="left"
|
|
262
|
-
size="small"
|
|
263
|
-
:value="headerSwitch.headerSwitchValue ? 1 : 0"
|
|
264
|
-
@returnToggleValue="handleHeaderSwitch"
|
|
265
|
-
:disabled="headerSwitch.headerSwitchDisable"
|
|
266
|
-
></c-switch>
|
|
267
|
-
</li>
|
|
268
|
-
<hr v-if="headerSwitch" class="my-1" />
|
|
269
|
-
<!-- Select All Option -->
|
|
270
|
-
<template
|
|
271
|
-
v-if="
|
|
272
|
-
(selectCheckboxes || addCheckBox) &&
|
|
273
|
-
(organizedOptions.groups.length > 0 ||
|
|
274
|
-
organizedOptions.selectedValues.length > 0 ||
|
|
275
|
-
organizedOptions.unselectedValues.length > 0)
|
|
276
|
-
"
|
|
277
|
-
>
|
|
278
|
-
<li
|
|
279
|
-
@mousedown="handleSelect($event, allOption)"
|
|
280
|
-
class="relative flex min-h-[36px] cursor-pointer select-none items-center gap-3 py-2 pl-3 pr-9 text-gray-700 hover:bg-gray-100"
|
|
281
|
-
>
|
|
282
|
-
<input
|
|
283
|
-
type="checkbox"
|
|
284
|
-
name="select-all"
|
|
285
|
-
class="pointer-events-none h-4 w-4 cursor-pointer rounded border-gray-300 text-indigo-600 focus:ring-indigo-500 disabled:opacity-50"
|
|
286
|
-
:checked="selectAll"
|
|
287
|
-
/>
|
|
288
|
-
|
|
289
|
-
<div class="flex items-center gap-2">
|
|
290
|
-
<c-icon
|
|
291
|
-
v-if="allOption && allOption.icon"
|
|
292
|
-
:class="allOption.icon.class"
|
|
293
|
-
:name="allOption.icon.name"
|
|
294
|
-
:type="allOption.icon.type"
|
|
295
|
-
:viewBox="allOption.icon.viewBox"
|
|
296
|
-
></c-icon>
|
|
297
|
-
<span class="list-options block break-words font-normal">
|
|
298
|
-
Select all
|
|
299
|
-
</span>
|
|
300
|
-
</div>
|
|
301
|
-
</li>
|
|
302
|
-
<hr class="my-1" />
|
|
303
|
-
</template>
|
|
304
|
-
<!-- Groups Section -->
|
|
305
|
-
<li
|
|
306
|
-
v-for="(option, index) in organizedOptions.groups"
|
|
307
|
-
:key="`group-${option.id || index}`"
|
|
308
|
-
id="listbox-option-group"
|
|
309
|
-
role="option"
|
|
310
|
-
:ref="`option-group-${String(index)}`"
|
|
311
|
-
@mousedown="handleSelect($event, option)"
|
|
312
|
-
:class="option.isDisabled ? 'pointer-events-none' : ''"
|
|
313
|
-
>
|
|
314
|
-
<span
|
|
315
|
-
class="flex min-h-[36px] w-full items-center gap-3 px-3 py-2"
|
|
316
|
-
:class="`group ${
|
|
317
|
-
option.isDisabled ? 'custom-disabled-state' : ''
|
|
318
|
-
} ${optionClasses} relative cursor-pointer select-none text-gray-700 hover:bg-gray-100`"
|
|
319
|
-
>
|
|
320
|
-
<div v-if="addCheckBox" class="flex h-5 shrink-0 items-center">
|
|
321
|
-
<input
|
|
322
|
-
type="checkbox"
|
|
323
|
-
name="group"
|
|
324
|
-
class="pointer-events-none h-4 w-4 cursor-pointer rounded border-gray-300 text-indigo-600 focus:ring-indigo-500 disabled:opacity-50"
|
|
325
|
-
:checked="isChecked(option)"
|
|
326
|
-
/>
|
|
327
|
-
</div>
|
|
328
|
-
<div class="flex min-w-0 flex-1 items-start gap-2">
|
|
329
|
-
<div v-if="option.showImage" class="shrink-0 pt-0.5">
|
|
330
|
-
<c-avatar
|
|
331
|
-
v-if="option.photo"
|
|
332
|
-
size="extraextraextrasmall"
|
|
333
|
-
:image="option.photo"
|
|
334
|
-
:rounded="true"
|
|
335
|
-
></c-avatar>
|
|
336
|
-
<c-avatar
|
|
337
|
-
v-else
|
|
338
|
-
size="extraextraextrasmall"
|
|
339
|
-
:nameInitials="option.initials"
|
|
340
|
-
:rounded="true"
|
|
341
|
-
:isDynamicallyColored="coloredAvatars"
|
|
342
|
-
></c-avatar>
|
|
343
|
-
</div>
|
|
344
|
-
<div
|
|
345
|
-
class="flex shrink-0 items-center justify-center rounded-full bg-gray-100 p-1"
|
|
346
|
-
v-if="option.showIcon && option.icon"
|
|
347
|
-
>
|
|
348
|
-
<c-icon
|
|
349
|
-
:class="option.icon.class"
|
|
350
|
-
:name="option.icon.name"
|
|
351
|
-
:type="option.icon.type"
|
|
352
|
-
:viewBox="option.icon.viewBox"
|
|
353
|
-
>
|
|
354
|
-
</c-icon>
|
|
355
|
-
</div>
|
|
356
|
-
<span
|
|
357
|
-
:class="[
|
|
358
|
-
option.photo || option.initials ? '' : 'text-left',
|
|
359
|
-
addCheckBox ? 'overflow-hidden' : '',
|
|
360
|
-
]"
|
|
361
|
-
class="list-options min-w-0 flex-1 break-words font-normal"
|
|
362
|
-
v-bind:style="{
|
|
363
|
-
...(shouldShowCustomFonts
|
|
364
|
-
? { fontFamily: option[renderOptionName] }
|
|
365
|
-
: {}),
|
|
366
|
-
}"
|
|
367
|
-
>{{ option[renderOptionName] }}
|
|
368
|
-
</span>
|
|
369
|
-
</div>
|
|
370
|
-
<c-icon
|
|
371
|
-
v-if="!addCheckBox && isOptionSelected(option)"
|
|
372
|
-
name="check-outline-v2"
|
|
373
|
-
type="outline-v2"
|
|
374
|
-
class="h-4 w-4 shrink-0 self-center text-indigo-700"
|
|
375
|
-
/>
|
|
376
|
-
</span>
|
|
377
|
-
</li>
|
|
378
|
-
<hr
|
|
379
|
-
v-if="
|
|
380
|
-
organizedOptions.groups.length > 0 &&
|
|
381
|
-
(organizedOptions.selectedValues.length > 0 ||
|
|
382
|
-
organizedOptions.unselectedValues.length > 0)
|
|
383
|
-
"
|
|
384
|
-
class="my-1"
|
|
385
|
-
/>
|
|
386
|
-
<!-- Selected values section (non-groups) -->
|
|
387
|
-
<li
|
|
388
|
-
v-for="(option, index) in organizedOptions.selectedValues"
|
|
389
|
-
:key="`selected-value-${option.id || index}`"
|
|
390
|
-
:id="`listbox-option-selected-value-${String(index)}`"
|
|
391
|
-
role="option"
|
|
392
|
-
:ref="`option-selected-value-${String(index)}`"
|
|
393
|
-
@mousedown="handleSelect($event, option)"
|
|
394
|
-
:class="option.isDisabled ? 'pointer-events-none' : ''"
|
|
395
|
-
>
|
|
396
|
-
<span
|
|
397
|
-
class="flex min-h-[36px] w-full items-center gap-3 px-3 py-2"
|
|
398
|
-
:class="`group ${
|
|
399
|
-
option.isDisabled ? 'custom-disabled-state' : ''
|
|
400
|
-
} ${optionClasses} relative cursor-pointer select-none text-gray-700 hover:bg-gray-100`"
|
|
401
|
-
>
|
|
402
|
-
<div v-if="addCheckBox" class="flex h-5 shrink-0 items-center">
|
|
403
|
-
<input
|
|
404
|
-
type="checkbox"
|
|
405
|
-
name="value"
|
|
406
|
-
class="pointer-events-none h-4 w-4 cursor-pointer rounded border-gray-300 text-indigo-600 focus:ring-indigo-500 disabled:opacity-50"
|
|
407
|
-
:checked="preservedOrder ? isChecked(option) : true"
|
|
408
|
-
/>
|
|
409
|
-
</div>
|
|
410
|
-
<div class="flex min-w-0 flex-1 items-start gap-2">
|
|
411
|
-
<div v-if="option.showImage" class="shrink-0 pt-0.5">
|
|
412
|
-
<c-avatar
|
|
413
|
-
v-if="option.photo"
|
|
414
|
-
size="extraextraextrasmall"
|
|
415
|
-
:image="option.photo"
|
|
416
|
-
:rounded="true"
|
|
417
|
-
></c-avatar>
|
|
418
|
-
<c-avatar
|
|
419
|
-
v-else
|
|
420
|
-
size="extraextraextrasmall"
|
|
421
|
-
:nameInitials="option.initials"
|
|
422
|
-
:rounded="true"
|
|
423
|
-
:isDynamicallyColored="coloredAvatars"
|
|
424
|
-
></c-avatar>
|
|
425
|
-
</div>
|
|
426
|
-
<div
|
|
427
|
-
class="flex shrink-0 items-center justify-center rounded-full bg-gray-100 p-1"
|
|
428
|
-
v-if="option.showIcon && option.icon"
|
|
429
|
-
>
|
|
430
|
-
<c-icon
|
|
431
|
-
:class="option.icon.class"
|
|
432
|
-
:name="option.icon.name"
|
|
433
|
-
:type="option.icon.type"
|
|
434
|
-
:viewBox="option.icon.viewBox"
|
|
435
|
-
>
|
|
436
|
-
</c-icon>
|
|
437
|
-
</div>
|
|
438
|
-
<span
|
|
439
|
-
:class="[
|
|
440
|
-
option.photo || option.initials ? '' : 'text-left',
|
|
441
|
-
addCheckBox ? 'overflow-hidden' : '',
|
|
442
|
-
]"
|
|
443
|
-
class="list-options min-w-0 flex-1 break-words font-normal"
|
|
444
|
-
v-bind:style="{
|
|
445
|
-
...(shouldShowCustomFonts
|
|
446
|
-
? { fontFamily: option[renderOptionName] }
|
|
447
|
-
: {}),
|
|
448
|
-
}"
|
|
449
|
-
>{{ option[renderOptionName] }}
|
|
450
|
-
</span>
|
|
451
|
-
</div>
|
|
452
|
-
</span>
|
|
453
|
-
<hr v-if="option.isBorder" class="my-1" />
|
|
454
|
-
</li>
|
|
455
|
-
<hr
|
|
456
|
-
v-if="
|
|
457
|
-
organizedOptions.selectedValues.length > 0 &&
|
|
458
|
-
organizedOptions.unselectedValues.length > 0
|
|
459
|
-
"
|
|
460
|
-
class="my-1"
|
|
461
|
-
/>
|
|
462
|
-
<!-- Unselected values section -->
|
|
463
|
-
<li
|
|
464
|
-
v-for="(option, index) in organizedOptions.unselectedValues"
|
|
465
|
-
:key="`unselected-value-${option.id || index}`"
|
|
466
|
-
:id="`listbox-option-unselected-value-${String(index)}`"
|
|
467
|
-
role="option"
|
|
468
|
-
:ref="`option-unselected-value-${String(index)}`"
|
|
469
|
-
@mousedown="handleSelect($event, option)"
|
|
470
|
-
:class="option.isDisabled ? 'pointer-events-none' : ''"
|
|
471
|
-
>
|
|
472
|
-
<span
|
|
473
|
-
class="flex min-h-[36px] w-full items-center gap-3 px-3 py-2"
|
|
474
|
-
:class="`group ${
|
|
475
|
-
option.isDisabled ? 'custom-disabled-state' : ''
|
|
476
|
-
} ${optionClasses} relative cursor-pointer select-none text-gray-700 hover:bg-gray-100`"
|
|
477
|
-
>
|
|
478
|
-
<div v-if="addCheckBox" class="flex h-5 shrink-0 items-center">
|
|
479
|
-
<input
|
|
480
|
-
type="checkbox"
|
|
481
|
-
name="value"
|
|
482
|
-
class="pointer-events-none h-4 w-4 cursor-pointer rounded border-gray-300 text-indigo-600 focus:ring-indigo-500 disabled:opacity-50"
|
|
483
|
-
:checked="selectAll || isChecked(option)"
|
|
484
|
-
/>
|
|
485
|
-
</div>
|
|
486
|
-
<div class="flex min-w-0 flex-1 items-start gap-2">
|
|
487
|
-
<div v-if="option.showImage" class="shrink-0 pt-0.5">
|
|
488
|
-
<c-avatar
|
|
489
|
-
v-if="option.photo"
|
|
490
|
-
size="extraextraextrasmall"
|
|
491
|
-
:image="option.photo"
|
|
492
|
-
:rounded="true"
|
|
493
|
-
></c-avatar>
|
|
494
|
-
<c-avatar
|
|
495
|
-
v-else
|
|
496
|
-
size="extraextraextrasmall"
|
|
497
|
-
:nameInitials="option.initials"
|
|
498
|
-
:rounded="true"
|
|
499
|
-
:isDynamicallyColored="coloredAvatars"
|
|
500
|
-
></c-avatar>
|
|
501
|
-
</div>
|
|
502
|
-
<div
|
|
503
|
-
class="flex shrink-0 items-center justify-center rounded-full bg-gray-100 p-1"
|
|
504
|
-
v-if="option.showIcon && option.icon"
|
|
505
|
-
>
|
|
506
|
-
<c-icon
|
|
507
|
-
:class="option.icon.class"
|
|
508
|
-
:name="option.icon.name"
|
|
509
|
-
:type="option.icon.type"
|
|
510
|
-
:viewBox="option.icon.viewBox"
|
|
511
|
-
>
|
|
512
|
-
</c-icon>
|
|
513
|
-
</div>
|
|
514
|
-
<div
|
|
515
|
-
class="list-options flex min-w-0 flex-1 flex-wrap items-center gap-2 font-normal"
|
|
516
|
-
>
|
|
517
|
-
<span
|
|
518
|
-
:class="[
|
|
519
|
-
option.photo || option.initials ? '' : 'text-left',
|
|
520
|
-
addCheckBox ? 'overflow-hidden' : '',
|
|
521
|
-
]"
|
|
522
|
-
class="min-w-0 flex-1 break-words"
|
|
523
|
-
v-bind:style="{
|
|
524
|
-
...(shouldShowCustomFonts
|
|
525
|
-
? { fontFamily: option[renderOptionName] }
|
|
526
|
-
: {}),
|
|
527
|
-
}"
|
|
528
|
-
>{{ option[renderOptionName] }}</span
|
|
529
|
-
>
|
|
530
|
-
<c-tag
|
|
531
|
-
v-if="option.badge"
|
|
532
|
-
:label="option.badge.label"
|
|
533
|
-
:color="option.badge.color"
|
|
534
|
-
class="shrink-0"
|
|
535
|
-
></c-tag>
|
|
536
|
-
</div>
|
|
537
|
-
</div>
|
|
538
|
-
<c-icon
|
|
539
|
-
v-if="!addCheckBox && isOptionSelected(option)"
|
|
540
|
-
name="check-outline-v2"
|
|
541
|
-
type="outline-v2"
|
|
542
|
-
class="h-4 w-4 shrink-0 self-center text-indigo-700"
|
|
543
|
-
/>
|
|
544
|
-
</span>
|
|
545
|
-
<hr v-if="option.isBorder" class="my-1" />
|
|
546
|
-
</li>
|
|
547
|
-
<li
|
|
548
|
-
v-if="
|
|
549
|
-
organizedOptions.groups.length === 0 &&
|
|
550
|
-
organizedOptions.selectedValues.length === 0 &&
|
|
551
|
-
organizedOptions.unselectedValues.length === 0 &&
|
|
552
|
-
selectSearch &&
|
|
553
|
-
selectSearch.length > 0
|
|
554
|
-
"
|
|
555
|
-
class="px-3 py-2 text-sm text-gray-500"
|
|
556
|
-
>
|
|
557
|
-
No options found, try searching something else.
|
|
558
|
-
</li>
|
|
559
|
-
</ul>
|
|
560
|
-
<div
|
|
561
|
-
v-if="isFooter"
|
|
562
|
-
class="group cursor-pointer rounded-b-md bg-gray-50 p-3 ring-1 ring-gray-900 ring-opacity-5"
|
|
563
|
-
@mousedown="handleFooter"
|
|
564
|
-
>
|
|
565
|
-
<p class="text-sm text-indigo-700">
|
|
566
|
-
{{ footerText }}
|
|
567
|
-
</p>
|
|
568
|
-
</div>
|
|
569
|
-
<div
|
|
570
|
-
v-if="
|
|
571
|
-
showCreateOption &&
|
|
572
|
-
showCreateOptionAfterSearch &&
|
|
573
|
-
selectSearch &&
|
|
574
|
-
selectSearch.trim()
|
|
575
|
-
"
|
|
576
|
-
class="sticky bottom-0 z-10 flex min-h-[44px] items-start gap-3 rounded-b-md bg-white px-3 py-2 shadow-lg ring-1 ring-gray-900 ring-opacity-5"
|
|
577
|
-
>
|
|
578
|
-
<span class="word-break flex-1 text-sm text-gray-700">{{
|
|
579
|
-
selectSearch
|
|
580
|
-
}}</span>
|
|
581
|
-
|
|
582
|
-
<button
|
|
583
|
-
type="button"
|
|
584
|
-
:id="id + '_create_button'"
|
|
585
|
-
@mousedown="handleCreateOption($event)"
|
|
586
|
-
class="flex items-center gap-1 text-sm text-indigo-700"
|
|
587
|
-
>
|
|
588
|
-
<c-icon
|
|
589
|
-
type="solid"
|
|
590
|
-
name="plus-circle-solid-v2"
|
|
591
|
-
class="h-5 w-5 text-indigo-600"
|
|
592
|
-
viewBox="0 0 24 24"
|
|
593
|
-
></c-icon>
|
|
594
|
-
Create
|
|
595
|
-
</button>
|
|
596
|
-
</div>
|
|
597
|
-
</div>
|
|
598
|
-
</transition>
|
|
599
|
-
</div>
|
|
600
|
-
<p
|
|
601
|
-
v-if="helpText && isValidate == true"
|
|
602
|
-
class="mt-2 text-left text-sm text-gray-500"
|
|
603
|
-
>
|
|
604
|
-
{{ helpText }}
|
|
605
|
-
</p>
|
|
606
|
-
<!-- validation error message -->
|
|
607
|
-
<p
|
|
608
|
-
v-if="!isValidate && errorMessage"
|
|
609
|
-
class="mt-2 text-left text-sm text-red-600"
|
|
610
|
-
>
|
|
611
|
-
{{ errorMessage }}
|
|
612
|
-
</p>
|
|
613
|
-
</div>
|
|
614
|
-
</template>
|
|
615
|
-
<script>
|
|
616
|
-
import { isNumber, isEmpty } from "lodash-es";
|
|
617
|
-
import CIcon from "../CIcon/CIcon.vue";
|
|
618
|
-
import { getActionID } from "../../helper";
|
|
619
|
-
import CTag from '../CTag/CTag.vue';
|
|
620
|
-
import CAvatar from '../CAvatar/CAvatar.vue';
|
|
621
|
-
export default {
|
|
622
|
-
name: "CSelect",
|
|
623
|
-
components: {
|
|
624
|
-
CIcon,
|
|
625
|
-
CTag,
|
|
626
|
-
CAvatar,
|
|
627
|
-
},
|
|
628
|
-
props: {
|
|
629
|
-
// id of selectpicker
|
|
630
|
-
id: {
|
|
631
|
-
type: String,
|
|
632
|
-
},
|
|
633
|
-
// label of selectpicker
|
|
634
|
-
label: {
|
|
635
|
-
type: String,
|
|
636
|
-
},
|
|
637
|
-
// action button of selectpicker
|
|
638
|
-
actionBtn: {
|
|
639
|
-
type: String,
|
|
640
|
-
default: null,
|
|
641
|
-
},
|
|
642
|
-
// placeholder in selectpicker
|
|
643
|
-
placeholder: {
|
|
644
|
-
type: String,
|
|
645
|
-
},
|
|
646
|
-
// list to render in dropdown
|
|
647
|
-
options: {
|
|
648
|
-
type: Array,
|
|
649
|
-
required: true,
|
|
650
|
-
},
|
|
651
|
-
// show the avatars with different colors
|
|
652
|
-
coloredAvatars: {
|
|
653
|
-
type: Boolean,
|
|
654
|
-
},
|
|
655
|
-
// text below dropdown to describe more about the field
|
|
656
|
-
helpText: {
|
|
657
|
-
type: String,
|
|
658
|
-
},
|
|
659
|
-
// implement a fixed btn at the bottom of dropdown
|
|
660
|
-
isFooter: {
|
|
661
|
-
type: Boolean,
|
|
662
|
-
},
|
|
663
|
-
// style input field
|
|
664
|
-
inputStyle: {
|
|
665
|
-
type: String,
|
|
666
|
-
},
|
|
667
|
-
// footer btn text
|
|
668
|
-
footerText: {
|
|
669
|
-
type: String,
|
|
670
|
-
},
|
|
671
|
-
// text adjacent to label of dropdown to provide hint about field
|
|
672
|
-
hint: { type: String },
|
|
673
|
-
// to show if image is present along with dropdown option
|
|
674
|
-
showImage: { type: Boolean },
|
|
675
|
-
// whether the select field is mandatory or not
|
|
676
|
-
isRequired: {
|
|
677
|
-
type: Boolean,
|
|
678
|
-
},
|
|
679
|
-
// validation is passed or not
|
|
680
|
-
isValidate: { type: Boolean, default: true },
|
|
681
|
-
// will truncate the text in input field
|
|
682
|
-
selectedOptionStyles: {
|
|
683
|
-
type: String,
|
|
684
|
-
},
|
|
685
|
-
// validation error message
|
|
686
|
-
errorMessage: {
|
|
687
|
-
type: String,
|
|
688
|
-
},
|
|
689
|
-
// perform action on first option in dropdown
|
|
690
|
-
addAction: { type: Object },
|
|
691
|
-
// icons in dropdown list
|
|
692
|
-
icon: { type: Object },
|
|
693
|
-
// value to set as default option in dropdown
|
|
694
|
-
value: { type: [Object, String, Array], default: null },
|
|
695
|
-
// type of dropdown - gray,tertiary, blue or default
|
|
696
|
-
type: {
|
|
697
|
-
type: String,
|
|
698
|
-
},
|
|
699
|
-
// name of the field in array to that contains text to render in dropdown
|
|
700
|
-
renderOptionName: {
|
|
701
|
-
type: String,
|
|
702
|
-
default: "option",
|
|
703
|
-
},
|
|
704
|
-
// whether to disable the selectpicker
|
|
705
|
-
isDisabled: {
|
|
706
|
-
type: Boolean,
|
|
707
|
-
default: false,
|
|
708
|
-
},
|
|
709
|
-
inputClasses: {
|
|
710
|
-
type: String,
|
|
711
|
-
default: "",
|
|
712
|
-
},
|
|
713
|
-
optionClasses: {
|
|
714
|
-
type: String,
|
|
715
|
-
default: "",
|
|
716
|
-
},
|
|
717
|
-
// customised width for the dropdown
|
|
718
|
-
customWidth: {
|
|
719
|
-
type: String,
|
|
720
|
-
default: null,
|
|
721
|
-
},
|
|
722
|
-
customBorderRadius: {
|
|
723
|
-
type: String,
|
|
724
|
-
default: "rounded-md",
|
|
725
|
-
},
|
|
726
|
-
addCheckBox: {
|
|
727
|
-
type: Boolean,
|
|
728
|
-
default: false,
|
|
729
|
-
},
|
|
730
|
-
selectAll: {
|
|
731
|
-
type: Boolean,
|
|
732
|
-
default: false,
|
|
733
|
-
},
|
|
734
|
-
selectCheckboxes: {
|
|
735
|
-
type: Boolean,
|
|
736
|
-
default: false,
|
|
737
|
-
},
|
|
738
|
-
headerSwitch: {
|
|
739
|
-
type: Object,
|
|
740
|
-
default: null,
|
|
741
|
-
},
|
|
742
|
-
useSticky: {
|
|
743
|
-
type: Boolean,
|
|
744
|
-
default: false,
|
|
745
|
-
},
|
|
746
|
-
showCloseButton: {
|
|
747
|
-
type: Boolean,
|
|
748
|
-
default: false,
|
|
749
|
-
},
|
|
750
|
-
showCreateOption: {
|
|
751
|
-
type: Boolean,
|
|
752
|
-
default: false,
|
|
753
|
-
},
|
|
754
|
-
shouldShowCustomFonts: {
|
|
755
|
-
type: Boolean,
|
|
756
|
-
default: false,
|
|
757
|
-
},
|
|
758
|
-
customFontNames: {
|
|
759
|
-
type: Array,
|
|
760
|
-
default: () => [],
|
|
761
|
-
},
|
|
762
|
-
inputContainerClass: {
|
|
763
|
-
type: String,
|
|
764
|
-
default: "",
|
|
765
|
-
},
|
|
766
|
-
},
|
|
767
|
-
data() {
|
|
768
|
-
return {
|
|
769
|
-
toggleDropdown: false,
|
|
770
|
-
selectedValue: null,
|
|
771
|
-
showFocus: false,
|
|
772
|
-
selectSearch: null,
|
|
773
|
-
renderOptions: this.options,
|
|
774
|
-
selectedValuesArray: [],
|
|
775
|
-
isToggle: 0,
|
|
776
|
-
shouldOpenAbove: false,
|
|
777
|
-
// Frozen order while dropdown is open - prevents UI shift on selection
|
|
778
|
-
preservedOrder: null,
|
|
779
|
-
};
|
|
780
|
-
},
|
|
781
|
-
computed: {
|
|
782
|
-
classes() {
|
|
783
|
-
return {
|
|
784
|
-
"text-gray-900 border border-gray-300 hover:bg-gray-200 bg-gray-100 focus:ring-2 focus:border-gray-200 focus:ring-gray-200 focus:ring-offset-2 shadow-sm":
|
|
785
|
-
this.type == "gray",
|
|
786
|
-
"bg-white border border-gray-300 hover:bg-indigo-100 text-indigo-600 hover:text-indigo-800 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-200":
|
|
787
|
-
this.type == "tertiary",
|
|
788
|
-
"bg-white text-indigo-700 border-none focus:outline-none":
|
|
789
|
-
this.type == "indigo",
|
|
790
|
-
"border border-gray-300 bg-white shadow-sm focus:outline-none focus:border-gray-300 focus:ring-indigo-600 focus:ring-2 focus:ring-offset-2":
|
|
791
|
-
this.type == null,
|
|
792
|
-
};
|
|
793
|
-
},
|
|
794
|
-
allOption() {
|
|
795
|
-
if (
|
|
796
|
-
this.options &&
|
|
797
|
-
this.options.length > 0 &&
|
|
798
|
-
this.options[0] &&
|
|
799
|
-
this.options[0].id === "all"
|
|
800
|
-
) {
|
|
801
|
-
return this.options[0];
|
|
802
|
-
}
|
|
803
|
-
return null;
|
|
804
|
-
},
|
|
805
|
-
organizedOptions() {
|
|
806
|
-
if (!this.addCheckBox) {
|
|
807
|
-
const filteredOptions = this.renderOptions.filter(
|
|
808
|
-
(option) => option.id !== "all"
|
|
809
|
-
);
|
|
810
|
-
return {
|
|
811
|
-
groups: [],
|
|
812
|
-
selectedValues: [],
|
|
813
|
-
unselectedValues: filteredOptions,
|
|
814
|
-
};
|
|
815
|
-
}
|
|
816
|
-
|
|
817
|
-
// While dropdown is open and not searching, preserve order to prevent UI shift on selection
|
|
818
|
-
if (
|
|
819
|
-
this.preservedOrder &&
|
|
820
|
-
(!this.selectSearch || this.selectSearch.trim() === "")
|
|
821
|
-
) {
|
|
822
|
-
return this.preservedOrder;
|
|
823
|
-
}
|
|
824
|
-
|
|
825
|
-
return this.computeOrganizedOptions();
|
|
826
|
-
},
|
|
827
|
-
showCreateOptionAfterSearch() {
|
|
828
|
-
if (!this.showCreateOption) return false;
|
|
829
|
-
|
|
830
|
-
const term = (this.selectSearch || "").trim().toLowerCase();
|
|
831
|
-
if (!term) return false;
|
|
832
|
-
|
|
833
|
-
const hasExactMatch = this.options.some((option) => {
|
|
834
|
-
const label = option[this.renderOptionName];
|
|
835
|
-
return label && String(label).toLowerCase() === term;
|
|
836
|
-
});
|
|
837
|
-
|
|
838
|
-
return !hasExactMatch;
|
|
839
|
-
},
|
|
840
|
-
dropdownSelectorIconClass() {
|
|
841
|
-
return this.type === "indigo" ? "text-indigo-600" : "text-gray-400";
|
|
842
|
-
},
|
|
843
|
-
hasSelectedValue() {
|
|
844
|
-
const val = this.selectedValue;
|
|
845
|
-
if (val === null || val === undefined || val === "") {
|
|
846
|
-
return false;
|
|
847
|
-
}
|
|
848
|
-
if (typeof val === "object") {
|
|
849
|
-
return !isEmpty(val);
|
|
850
|
-
}
|
|
851
|
-
return true;
|
|
852
|
-
},
|
|
853
|
-
selectedValueClass() {
|
|
854
|
-
if (this.hasSelectedValue) {
|
|
855
|
-
if (this.isDisabled) {
|
|
856
|
-
return "text-gray-500";
|
|
857
|
-
}
|
|
858
|
-
|
|
859
|
-
if (this.type === "indigo") {
|
|
860
|
-
return "text-indigo-600";
|
|
861
|
-
}
|
|
862
|
-
return "text-gray-700";
|
|
863
|
-
}
|
|
864
|
-
return "text-gray-500";
|
|
865
|
-
},
|
|
866
|
-
},
|
|
867
|
-
methods: {
|
|
868
|
-
isEmpty,
|
|
869
|
-
getActionIDFn(name) {
|
|
870
|
-
return getActionID(name, this.id);
|
|
871
|
-
},
|
|
872
|
-
checkAvailableSpace() {
|
|
873
|
-
if (!this.$refs.inputContainer) return false;
|
|
874
|
-
|
|
875
|
-
const container = this.$refs.inputContainer;
|
|
876
|
-
const rect = container.getBoundingClientRect();
|
|
877
|
-
const viewportHeight = window.innerHeight;
|
|
878
|
-
const bottomSpace = viewportHeight - rect.bottom;
|
|
879
|
-
const componentHeight = 320;
|
|
880
|
-
|
|
881
|
-
// Check if bottom space is less than 80% the component height if there is no footer
|
|
882
|
-
return (
|
|
883
|
-
bottomSpace <
|
|
884
|
-
(this.isFooter ? componentHeight : (componentHeight * 4) / 5)
|
|
885
|
-
);
|
|
886
|
-
},
|
|
887
|
-
getDropdownPosition() {
|
|
888
|
-
this.shouldOpenAbove = this.checkAvailableSpace();
|
|
889
|
-
return this.shouldOpenAbove ? "bottom-full" : "top-full";
|
|
890
|
-
},
|
|
891
|
-
getDropdownPosition2() {
|
|
892
|
-
return this.shouldOpenAbove ? "bottom-full" : "";
|
|
893
|
-
},
|
|
894
|
-
getDropdownShadow() {
|
|
895
|
-
if (this.shouldOpenAbove) {
|
|
896
|
-
return "shadow-[0_-10px_15px_-3px_rgba(0,0,0,0.1),0_-4px_6px_-4px_rgba(0,0,0,0.1)]";
|
|
897
|
-
} else {
|
|
898
|
-
return "shadow-lg";
|
|
899
|
-
}
|
|
900
|
-
},
|
|
901
|
-
handleCrossClick(event) {
|
|
902
|
-
event.preventDefault();
|
|
903
|
-
this.$emit("hide-single-select-dropdown");
|
|
904
|
-
},
|
|
905
|
-
handleInputClick() {
|
|
906
|
-
this.toggleDropdown = !this.toggleDropdown;
|
|
907
|
-
if (!this.toggleDropdown) {
|
|
908
|
-
this.selectSearch = "";
|
|
909
|
-
this.$nextTick(() => {
|
|
910
|
-
if (this.$refs["c-select-input"]) {
|
|
911
|
-
this.$refs["c-select-input"].focus();
|
|
912
|
-
}
|
|
913
|
-
});
|
|
914
|
-
}
|
|
915
|
-
},
|
|
916
|
-
handleInputFocus() {
|
|
917
|
-
if (this.type == "tertiary") {
|
|
918
|
-
this.showFocus = true;
|
|
919
|
-
}
|
|
920
|
-
},
|
|
921
|
-
handleSelect(event, option) {
|
|
922
|
-
this.selectSearch = null;
|
|
923
|
-
this.selectedValue = option[this.renderOptionName];
|
|
924
|
-
if (this.addCheckBox) {
|
|
925
|
-
// Freeze current display order BEFORE updating selection to prevent UI shift
|
|
926
|
-
const currentDisplay = this.organizedOptions;
|
|
927
|
-
this.preservedOrder = {
|
|
928
|
-
groups: [...currentDisplay.groups],
|
|
929
|
-
selectedValues: [...currentDisplay.selectedValues],
|
|
930
|
-
unselectedValues: [...currentDisplay.unselectedValues],
|
|
931
|
-
};
|
|
932
|
-
if (this.getIndex(option) == -1) {
|
|
933
|
-
this.selectedValuesArray = [...this.selectedValuesArray, option];
|
|
934
|
-
} else {
|
|
935
|
-
this.selectedValuesArray = this.selectedValuesArray.filter(
|
|
936
|
-
(item) => option.id !== item.id
|
|
937
|
-
);
|
|
938
|
-
}
|
|
939
|
-
this.$emit("onChangeChecked", this.selectedValuesArray, option);
|
|
940
|
-
}
|
|
941
|
-
|
|
942
|
-
this.$emit("onChangeValue", option);
|
|
943
|
-
if (!this.addCheckBox) {
|
|
944
|
-
this.toggleDropdown = false;
|
|
945
|
-
}
|
|
946
|
-
this.type === "tertiary" ? (this.showFocus = false) : "";
|
|
947
|
-
this.renderOptions = this.options;
|
|
948
|
-
},
|
|
949
|
-
handleFooter() {
|
|
950
|
-
this.$emit("handleFooter");
|
|
951
|
-
},
|
|
952
|
-
actionEvent(event) {
|
|
953
|
-
this.$emit("listAction", event);
|
|
954
|
-
},
|
|
955
|
-
actionBtnEvent(event) {
|
|
956
|
-
this.$emit("listAction", event);
|
|
957
|
-
},
|
|
958
|
-
handleElementBlur() {
|
|
959
|
-
this.toggleDropdown = false;
|
|
960
|
-
if (this.addCheckBox) {
|
|
961
|
-
this.$emit("onClickOutside", this.selectedValuesArray);
|
|
962
|
-
}
|
|
963
|
-
this.selectSearch = null;
|
|
964
|
-
this.renderOptions = this.options;
|
|
965
|
-
},
|
|
966
|
-
handleHeaderSwitch(value) {
|
|
967
|
-
this.$emit("handleHeaderSwitch", value);
|
|
968
|
-
},
|
|
969
|
-
//this prevents the blur to be called when the user clicks on the scrollbar
|
|
970
|
-
handlePreventBlur(event) {
|
|
971
|
-
event.preventDefault();
|
|
972
|
-
},
|
|
973
|
-
search() {
|
|
974
|
-
this.selectedValue = null;
|
|
975
|
-
// if the search term is empty space, then set the render options to empty array
|
|
976
|
-
if (
|
|
977
|
-
this.selectSearch !== null &&
|
|
978
|
-
this.selectSearch.length > 0 &&
|
|
979
|
-
this.selectSearch.trim() === ""
|
|
980
|
-
) {
|
|
981
|
-
this.renderOptions = [];
|
|
982
|
-
this.$emit("search-term", this.selectSearch);
|
|
983
|
-
return;
|
|
984
|
-
}
|
|
985
|
-
if (
|
|
986
|
-
!this.selectSearch ||
|
|
987
|
-
this.selectSearch == "" ||
|
|
988
|
-
this.selectSearch.trim() == ""
|
|
989
|
-
) {
|
|
990
|
-
this.renderOptions = this.options;
|
|
991
|
-
this.$emit("search-term", this.selectSearch);
|
|
992
|
-
return;
|
|
993
|
-
}
|
|
994
|
-
let options = [...this.options];
|
|
995
|
-
this.renderOptions = options.filter((option) => {
|
|
996
|
-
isNumber(option[this.renderOptionName])
|
|
997
|
-
? (option[this.renderOptionName] =
|
|
998
|
-
option[this.renderOptionName].toString())
|
|
999
|
-
: "";
|
|
1000
|
-
const optionName = String(
|
|
1001
|
-
option[this.renderOptionName] || ""
|
|
1002
|
-
).toLowerCase();
|
|
1003
|
-
const searchTerm = this.selectSearch.trim().toLowerCase();
|
|
1004
|
-
return optionName.includes(searchTerm);
|
|
1005
|
-
});
|
|
1006
|
-
|
|
1007
|
-
if (this.renderOptions.length === 0) {
|
|
1008
|
-
this.$emit("search-term", this.selectSearch.trim());
|
|
1009
|
-
}
|
|
1010
|
-
},
|
|
1011
|
-
async scrollOptionIntoView(refName) {
|
|
1012
|
-
await this.$nextTick();
|
|
1013
|
-
const list = this.$refs.optionsList;
|
|
1014
|
-
const optionEl = (this.$refs[refName] || [])[0];
|
|
1015
|
-
if (!list || !optionEl) return;
|
|
1016
|
-
|
|
1017
|
-
const top = optionEl.offsetTop;
|
|
1018
|
-
const bottom = top + optionEl.offsetHeight;
|
|
1019
|
-
const viewTop = list.scrollTop;
|
|
1020
|
-
const viewBottom = viewTop + list.clientHeight;
|
|
1021
|
-
|
|
1022
|
-
const target =
|
|
1023
|
-
top < viewTop
|
|
1024
|
-
? top
|
|
1025
|
-
: bottom > viewBottom
|
|
1026
|
-
? bottom - list.clientHeight
|
|
1027
|
-
: viewTop;
|
|
1028
|
-
|
|
1029
|
-
if (target !== viewTop) {
|
|
1030
|
-
list.scrollTo({ top: target, behavior: "smooth" });
|
|
1031
|
-
}
|
|
1032
|
-
},
|
|
1033
|
-
isOptionSelected(option) {
|
|
1034
|
-
/*
|
|
1035
|
-
* Compares value to option by id when both exist,
|
|
1036
|
-
* otherwise by renderOptionName / string value.
|
|
1037
|
-
*/
|
|
1038
|
-
if (option == null) {
|
|
1039
|
-
return false;
|
|
1040
|
-
}
|
|
1041
|
-
|
|
1042
|
-
const v = this.value;
|
|
1043
|
-
if (v === null) {
|
|
1044
|
-
return false;
|
|
1045
|
-
}
|
|
1046
|
-
|
|
1047
|
-
if (typeof v === "object" && v !== null) {
|
|
1048
|
-
if (v.id !== undefined && option.id !== undefined) {
|
|
1049
|
-
return v.id === option.id;
|
|
1050
|
-
}
|
|
1051
|
-
|
|
1052
|
-
const vLabel = v[this.renderOptionName];
|
|
1053
|
-
if (vLabel !== undefined && vLabel !== null) {
|
|
1054
|
-
return String(vLabel) === String(option[this.renderOptionName]);
|
|
1055
|
-
}
|
|
1056
|
-
}
|
|
1057
|
-
|
|
1058
|
-
return String(v) === String(option[this.renderOptionName]);
|
|
1059
|
-
},
|
|
1060
|
-
getIndex(option) {
|
|
1061
|
-
return this.selectedValuesArray.findIndex((item) => item.id == option.id);
|
|
1062
|
-
},
|
|
1063
|
-
isChecked(option) {
|
|
1064
|
-
return this.selectedValuesArray.findIndex(
|
|
1065
|
-
(item) => item.id == option.id
|
|
1066
|
-
) == -1
|
|
1067
|
-
? false
|
|
1068
|
-
: true;
|
|
1069
|
-
},
|
|
1070
|
-
handleCreateOption(event) {
|
|
1071
|
-
event.preventDefault();
|
|
1072
|
-
this.toggleDropdown = false;
|
|
1073
|
-
this.$emit("handleCreateOption", {
|
|
1074
|
-
event,
|
|
1075
|
-
searchText: this.selectSearch.trim(),
|
|
1076
|
-
});
|
|
1077
|
-
this.selectSearch = null;
|
|
1078
|
-
this.renderOptions = this.options;
|
|
1079
|
-
|
|
1080
|
-
const cSelectInputRef = this.$refs["c-select-input"];
|
|
1081
|
-
this.$nextTick(() => {
|
|
1082
|
-
cSelectInputRef && cSelectInputRef.blur();
|
|
1083
|
-
});
|
|
1084
|
-
},
|
|
1085
|
-
handleKeydown(event) {
|
|
1086
|
-
if (event.key === "Enter") {
|
|
1087
|
-
event.preventDefault();
|
|
1088
|
-
event.stopPropagation();
|
|
1089
|
-
|
|
1090
|
-
if (
|
|
1091
|
-
this.showCreateOption &&
|
|
1092
|
-
this.showCreateOptionAfterSearch &&
|
|
1093
|
-
this.selectSearch &&
|
|
1094
|
-
this.selectSearch.trim()
|
|
1095
|
-
) {
|
|
1096
|
-
this.handleCreateOption(event);
|
|
1097
|
-
}
|
|
1098
|
-
}
|
|
1099
|
-
},
|
|
1100
|
-
async handleLoadFont() {
|
|
1101
|
-
const customFontNames = this.customFontNames;
|
|
1102
|
-
const googleParam = customFontNames
|
|
1103
|
-
.map((f) => `family=${f.replace(/ /g, "+")}`)
|
|
1104
|
-
.join("&");
|
|
1105
|
-
|
|
1106
|
-
const link = document.createElement("link");
|
|
1107
|
-
link.rel = "stylesheet";
|
|
1108
|
-
link.href = `https://fonts.googleapis.com/css2?${googleParam}&display=swap`;
|
|
1109
|
-
link.setAttribute("data-fonts", customFontNames.join(","));
|
|
1110
|
-
document.head.appendChild(link);
|
|
1111
|
-
|
|
1112
|
-
await document.fonts.ready;
|
|
1113
|
-
},
|
|
1114
|
-
isGroup(option) {
|
|
1115
|
-
return option.type && option.type.includes("group");
|
|
1116
|
-
},
|
|
1117
|
-
computeOrganizedOptions() {
|
|
1118
|
-
const selectedIds = new Set(
|
|
1119
|
-
this.selectedValuesArray.map((item) => item.id)
|
|
1120
|
-
);
|
|
1121
|
-
|
|
1122
|
-
const groups = [];
|
|
1123
|
-
const selectedValues = [];
|
|
1124
|
-
const unselectedValues = [];
|
|
1125
|
-
|
|
1126
|
-
// Filter out the "all" option from renderOptions
|
|
1127
|
-
this.renderOptions
|
|
1128
|
-
.filter((option) => option.id !== "all")
|
|
1129
|
-
.forEach((option) => {
|
|
1130
|
-
if (this.isGroup(option)) {
|
|
1131
|
-
// Groups stay in their own section regardless of selection
|
|
1132
|
-
groups.push(option);
|
|
1133
|
-
} else {
|
|
1134
|
-
// Values are separated by selection state
|
|
1135
|
-
if (selectedIds.has(option.id)) {
|
|
1136
|
-
selectedValues.push(option);
|
|
1137
|
-
} else {
|
|
1138
|
-
unselectedValues.push(option);
|
|
1139
|
-
}
|
|
1140
|
-
}
|
|
1141
|
-
});
|
|
1142
|
-
|
|
1143
|
-
// Sort by renderOptionName (guard against undefined a/b or missing renderOptionName)
|
|
1144
|
-
const getSortKey = (item) =>
|
|
1145
|
-
String((item && item[this.renderOptionName]) || "").toLowerCase();
|
|
1146
|
-
|
|
1147
|
-
selectedValues.sort((a, b) => getSortKey(a).localeCompare(getSortKey(b)));
|
|
1148
|
-
unselectedValues.sort((a, b) =>
|
|
1149
|
-
getSortKey(a).localeCompare(getSortKey(b))
|
|
1150
|
-
);
|
|
1151
|
-
|
|
1152
|
-
return {
|
|
1153
|
-
groups,
|
|
1154
|
-
selectedValues,
|
|
1155
|
-
unselectedValues,
|
|
1156
|
-
};
|
|
1157
|
-
},
|
|
1158
|
-
},
|
|
1159
|
-
watch: {
|
|
1160
|
-
value() {
|
|
1161
|
-
this.selectedValue =
|
|
1162
|
-
this.value !== null && this.value[this.renderOptionName]
|
|
1163
|
-
? this.value[this.renderOptionName]
|
|
1164
|
-
: this.value;
|
|
1165
|
-
if (this.addCheckBox) {
|
|
1166
|
-
this.selectedValuesArray = this.value ? this.value : [];
|
|
1167
|
-
}
|
|
1168
|
-
},
|
|
1169
|
-
options() {
|
|
1170
|
-
this.renderOptions = this.options;
|
|
1171
|
-
},
|
|
1172
|
-
toggleDropdown(newValue) {
|
|
1173
|
-
if (newValue) {
|
|
1174
|
-
this.checkAvailableSpace();
|
|
1175
|
-
|
|
1176
|
-
this.preservedOrder = null;
|
|
1177
|
-
|
|
1178
|
-
const index = this.renderOptions.findIndex(
|
|
1179
|
-
(option) => option[this.renderOptionName] === this.selectedValue
|
|
1180
|
-
);
|
|
1181
|
-
if (index !== -1) {
|
|
1182
|
-
const refName = `option-${String(index)}`;
|
|
1183
|
-
this.scrollOptionIntoView(refName);
|
|
1184
|
-
}
|
|
1185
|
-
} else {
|
|
1186
|
-
this.preservedOrder = null;
|
|
1187
|
-
}
|
|
1188
|
-
},
|
|
1189
|
-
},
|
|
1190
|
-
mounted() {
|
|
1191
|
-
this.selectedValue =
|
|
1192
|
-
this.value !== null && this.value[this.renderOptionName]
|
|
1193
|
-
? this.value[this.renderOptionName]
|
|
1194
|
-
: this.value;
|
|
1195
|
-
if (this.addCheckBox) {
|
|
1196
|
-
this.selectedValuesArray = this.value ? this.value : [];
|
|
1197
|
-
}
|
|
1198
|
-
|
|
1199
|
-
if (this.shouldShowCustomFonts && this.customFontNames.length > 0) {
|
|
1200
|
-
this.handleLoadFont();
|
|
1201
|
-
}
|
|
1202
|
-
},
|
|
1203
|
-
};
|
|
1204
|
-
</script>
|
|
1205
|
-
|
|
1206
|
-
<style scoped>
|
|
1207
|
-
.word-break {
|
|
1208
|
-
word-break: break-word;
|
|
1209
|
-
}
|
|
1210
|
-
</style>
|
|
1
|
+
<template>
|
|
2
|
+
<div>
|
|
3
|
+
<div
|
|
4
|
+
:class="`flex w-full items-center ${
|
|
5
|
+
label ? 'justify-between' : 'justify-end'
|
|
6
|
+
}`"
|
|
7
|
+
>
|
|
8
|
+
<div class="flex items-center" v-if="label">
|
|
9
|
+
<!-- label of select field -->
|
|
10
|
+
<label class="block text-sm font-medium text-gray-900">
|
|
11
|
+
{{ label }}
|
|
12
|
+
</label>
|
|
13
|
+
<!-- asterisk sign to render if field is required -->
|
|
14
|
+
<p v-if="isRequired" class="ml-1 text-red-600">*</p>
|
|
15
|
+
</div>
|
|
16
|
+
<button
|
|
17
|
+
v-if="actionBtn"
|
|
18
|
+
:id="getActionIDFn(actionBtn)"
|
|
19
|
+
class="block text-sm font-medium text-indigo-800 hover:underline"
|
|
20
|
+
@click="actionBtnEvent($event)"
|
|
21
|
+
>
|
|
22
|
+
{{ actionBtn }}
|
|
23
|
+
</button>
|
|
24
|
+
<span v-if="hint" class="text-sm text-gray-500">{{ hint }}</span>
|
|
25
|
+
</div>
|
|
26
|
+
<div :class="['relative', inputContainerClass]" ref="inputContainer">
|
|
27
|
+
<div :class="label || actionBtn ? 'mt-1' : ''">
|
|
28
|
+
<input
|
|
29
|
+
ref="c-select-input"
|
|
30
|
+
type="text"
|
|
31
|
+
v-model="selectSearch"
|
|
32
|
+
@click="handleInputClick"
|
|
33
|
+
@focus="handleInputFocus"
|
|
34
|
+
@input="search()"
|
|
35
|
+
@keydown="handleKeydown"
|
|
36
|
+
@keydown.enter.prevent="handleKeydown"
|
|
37
|
+
aria-haspopup="listbox"
|
|
38
|
+
aria-expanded="true"
|
|
39
|
+
aria-labelledby="listbox-label"
|
|
40
|
+
class="h-9 w-full cursor-pointer border py-2 pr-10 text-left text-sm"
|
|
41
|
+
:class="[
|
|
42
|
+
classes,
|
|
43
|
+
!isValidate && 'border-red-300',
|
|
44
|
+
inputClasses,
|
|
45
|
+
customBorderRadius,
|
|
46
|
+
toggleDropdown ? 'pl-10' : 'pl-3',
|
|
47
|
+
]"
|
|
48
|
+
:disabled="isDisabled"
|
|
49
|
+
autocomplete="off"
|
|
50
|
+
@blur="handleElementBlur"
|
|
51
|
+
:id="id"
|
|
52
|
+
:style="inputStyle"
|
|
53
|
+
/>
|
|
54
|
+
<button
|
|
55
|
+
class="absolute top-2.5 right-10 z-100 cursor-pointer"
|
|
56
|
+
:id="id + '_close_button'"
|
|
57
|
+
v-if="showCloseButton && (value == null || value.length == 0)"
|
|
58
|
+
@click.stop="handleCrossClick"
|
|
59
|
+
>
|
|
60
|
+
<c-icon
|
|
61
|
+
name="close"
|
|
62
|
+
type="outline-v2"
|
|
63
|
+
class="h-4 w-4 text-gray-400"
|
|
64
|
+
/>
|
|
65
|
+
</button>
|
|
66
|
+
<div
|
|
67
|
+
v-if="!toggleDropdown"
|
|
68
|
+
class="pointer-events-none absolute inset-0 left-3 flex h-9 items-center overflow-hidden pr-10"
|
|
69
|
+
>
|
|
70
|
+
<div
|
|
71
|
+
:class="['flex items-center gap-2 truncate', selectedOptionStyles]"
|
|
72
|
+
>
|
|
73
|
+
<div
|
|
74
|
+
class="flex shrink-0"
|
|
75
|
+
v-if="!addCheckBox && showImage && !selectSearch && value"
|
|
76
|
+
>
|
|
77
|
+
<c-avatar
|
|
78
|
+
v-if="value.photo"
|
|
79
|
+
size="extraextraextrasmall"
|
|
80
|
+
:image="value.photo"
|
|
81
|
+
:rounded="true"
|
|
82
|
+
></c-avatar>
|
|
83
|
+
<c-avatar
|
|
84
|
+
v-else
|
|
85
|
+
size="extraextraextrasmall"
|
|
86
|
+
:nameInitials="value.initials"
|
|
87
|
+
:rounded="true"
|
|
88
|
+
:isDynamicallyColored="coloredAvatars"
|
|
89
|
+
></c-avatar>
|
|
90
|
+
</div>
|
|
91
|
+
<div
|
|
92
|
+
class="flex shrink-0"
|
|
93
|
+
v-if="
|
|
94
|
+
addCheckBox &&
|
|
95
|
+
showImage &&
|
|
96
|
+
!selectSearch &&
|
|
97
|
+
selectedValuesArray &&
|
|
98
|
+
selectedValuesArray.length &&
|
|
99
|
+
selectedValuesArray[0].id
|
|
100
|
+
"
|
|
101
|
+
>
|
|
102
|
+
<c-avatar
|
|
103
|
+
v-if="selectedValuesArray[0].photo"
|
|
104
|
+
size="extraextraextrasmall"
|
|
105
|
+
:image="selectedValuesArray[0].photo"
|
|
106
|
+
:rounded="true"
|
|
107
|
+
></c-avatar>
|
|
108
|
+
<c-avatar
|
|
109
|
+
v-else
|
|
110
|
+
size="extraextraextrasmall"
|
|
111
|
+
:nameInitials="selectedValuesArray[0].initials"
|
|
112
|
+
:rounded="true"
|
|
113
|
+
:isDynamicallyColored="coloredAvatars"
|
|
114
|
+
></c-avatar>
|
|
115
|
+
</div>
|
|
116
|
+
<c-icon
|
|
117
|
+
v-if="icon && !showImage && !selectSearch"
|
|
118
|
+
:class="icon.class"
|
|
119
|
+
:name="icon.name"
|
|
120
|
+
:type="icon.type"
|
|
121
|
+
:viewBox="icon.viewBox"
|
|
122
|
+
>
|
|
123
|
+
</c-icon>
|
|
124
|
+
<div
|
|
125
|
+
v-if="addCheckBox"
|
|
126
|
+
class="flex flex-1 items-center overflow-hidden text-sm"
|
|
127
|
+
>
|
|
128
|
+
<p
|
|
129
|
+
class="block truncate"
|
|
130
|
+
v-if="
|
|
131
|
+
selectedValuesArray.length > 0 &&
|
|
132
|
+
selectedValuesArray[0].id &&
|
|
133
|
+
(!selectSearch || selectSearch == '')
|
|
134
|
+
"
|
|
135
|
+
>
|
|
136
|
+
{{ selectedValuesArray[0][renderOptionName] }}
|
|
137
|
+
</p>
|
|
138
|
+
<p v-else>
|
|
139
|
+
{{ !selectSearch || selectSearch == "" ? placeholder : null }}
|
|
140
|
+
</p>
|
|
141
|
+
<p
|
|
142
|
+
v-if="
|
|
143
|
+
selectedValuesArray.length > 1 &&
|
|
144
|
+
(!selectSearch || selectSearch == '') &&
|
|
145
|
+
selectedValuesArray[0].showExtra
|
|
146
|
+
"
|
|
147
|
+
class="ml-1 block"
|
|
148
|
+
>
|
|
149
|
+
+{{ selectedValuesArray.length - 1 }}
|
|
150
|
+
</p>
|
|
151
|
+
</div>
|
|
152
|
+
<p
|
|
153
|
+
v-else
|
|
154
|
+
:class="[
|
|
155
|
+
'flex items-center gap-2 truncate text-sm',
|
|
156
|
+
showImage ? 'ml-3' : '',
|
|
157
|
+
selectedValueClass,
|
|
158
|
+
]"
|
|
159
|
+
v-bind:style="{
|
|
160
|
+
...(shouldShowCustomFonts ? { fontFamily: selectedValue } : {}),
|
|
161
|
+
}"
|
|
162
|
+
>
|
|
163
|
+
{{
|
|
164
|
+
hasSelectedValue
|
|
165
|
+
? selectedValue
|
|
166
|
+
: !selectSearch || selectSearch == ""
|
|
167
|
+
? placeholder
|
|
168
|
+
: null
|
|
169
|
+
}}
|
|
170
|
+
<c-tag
|
|
171
|
+
v-if="value && value.badge"
|
|
172
|
+
:label="value.badge.label"
|
|
173
|
+
:color="value.badge.color"
|
|
174
|
+
class="shrink-0"
|
|
175
|
+
></c-tag>
|
|
176
|
+
</p>
|
|
177
|
+
</div>
|
|
178
|
+
</div>
|
|
179
|
+
<div
|
|
180
|
+
v-if="toggleDropdown"
|
|
181
|
+
class="pointer-events-none absolute inset-0 left-3 flex h-9 items-center overflow-hidden pr-10"
|
|
182
|
+
>
|
|
183
|
+
<div class="flex items-center gap-2">
|
|
184
|
+
<c-icon
|
|
185
|
+
name="search"
|
|
186
|
+
type="outline"
|
|
187
|
+
class="h-5 w-5 text-gray-400"
|
|
188
|
+
></c-icon>
|
|
189
|
+
<p v-if="!selectSearch" class="text-sm text-gray-500">Search</p>
|
|
190
|
+
</div>
|
|
191
|
+
</div>
|
|
192
|
+
<div class="pointer-events-none absolute top-2 right-3 flex">
|
|
193
|
+
<div
|
|
194
|
+
v-if="type == 'tertiary' ? showFocus : true"
|
|
195
|
+
class="pointer-events-none right-0 flex items-center"
|
|
196
|
+
>
|
|
197
|
+
<c-icon
|
|
198
|
+
name="chevron-down"
|
|
199
|
+
type="solid"
|
|
200
|
+
:class="[
|
|
201
|
+
'h-5 w-5 transition-transform duration-300',
|
|
202
|
+
toggleDropdown ? 'rotate-180' : '',
|
|
203
|
+
dropdownSelectorIconClass,
|
|
204
|
+
]"
|
|
205
|
+
></c-icon>
|
|
206
|
+
</div>
|
|
207
|
+
</div>
|
|
208
|
+
</div>
|
|
209
|
+
<transition
|
|
210
|
+
enter-active-class="transition ease-out duration-100"
|
|
211
|
+
enter-class="transform opacity-0 scale-95"
|
|
212
|
+
enter-to-class="transform opacity-100 scale-100"
|
|
213
|
+
leave-active-class="transition ease-in duration-75"
|
|
214
|
+
leave-class="transform opacity-100 scale-100"
|
|
215
|
+
leave-to-class="transform opacity-0 scale-95"
|
|
216
|
+
>
|
|
217
|
+
<div
|
|
218
|
+
v-if="toggleDropdown && !isDisabled"
|
|
219
|
+
:class="`${getDropdownPosition()} ${
|
|
220
|
+
useSticky ? 'sticky sm:absolute' : 'absolute'
|
|
221
|
+
} z-10 ${
|
|
222
|
+
isFooter && shouldOpenAbove
|
|
223
|
+
? 'mb-12 max-h-60'
|
|
224
|
+
: !isFooter && shouldOpenAbove
|
|
225
|
+
? 'mb-1 max-h-60'
|
|
226
|
+
: 'mt-1 max-h-80 pb-20'
|
|
227
|
+
} ${customWidth ? customWidth : 'w-full'}`"
|
|
228
|
+
>
|
|
229
|
+
<ul
|
|
230
|
+
tabindex="-1"
|
|
231
|
+
role="listbox"
|
|
232
|
+
aria-labelledby="listbox-label"
|
|
233
|
+
ref="optionsList"
|
|
234
|
+
:class="`max-h-60 overflow-auto overscroll-contain bg-white py-1 text-sm ring-1 ring-gray-900 ring-opacity-5 focus:outline-none ${getDropdownPosition2()} ${getDropdownShadow()} ${
|
|
235
|
+
isFooter ||
|
|
236
|
+
(showCreateOption &&
|
|
237
|
+
showCreateOptionAfterSearch &&
|
|
238
|
+
selectSearch &&
|
|
239
|
+
selectSearch.trim())
|
|
240
|
+
? 'rounded-t-md'
|
|
241
|
+
: 'rounded-md'
|
|
242
|
+
}`"
|
|
243
|
+
@mousedown="handlePreventBlur"
|
|
244
|
+
>
|
|
245
|
+
<li
|
|
246
|
+
v-if="addAction"
|
|
247
|
+
@mousedown="actionEvent($event)"
|
|
248
|
+
class="relative flex min-h-[36px] cursor-pointer select-none py-2 pl-3 pr-9 text-indigo-500 hover:bg-indigo-100 hover:text-indigo-700"
|
|
249
|
+
>
|
|
250
|
+
<c-icon
|
|
251
|
+
type="outline"
|
|
252
|
+
class="mr-1 h-5 w-5 text-indigo-400 group-hover:text-indigo-500"
|
|
253
|
+
name="plus"
|
|
254
|
+
></c-icon>
|
|
255
|
+
{{ addAction.label }}
|
|
256
|
+
</li>
|
|
257
|
+
<li v-if="headerSwitch" class="min-h-[36px] p-2 hover:bg-gray-100">
|
|
258
|
+
<c-switch
|
|
259
|
+
:label="headerSwitch.headerText"
|
|
260
|
+
class="text-sm"
|
|
261
|
+
direction="left"
|
|
262
|
+
size="small"
|
|
263
|
+
:value="headerSwitch.headerSwitchValue ? 1 : 0"
|
|
264
|
+
@returnToggleValue="handleHeaderSwitch"
|
|
265
|
+
:disabled="headerSwitch.headerSwitchDisable"
|
|
266
|
+
></c-switch>
|
|
267
|
+
</li>
|
|
268
|
+
<hr v-if="headerSwitch" class="my-1" />
|
|
269
|
+
<!-- Select All Option -->
|
|
270
|
+
<template
|
|
271
|
+
v-if="
|
|
272
|
+
(selectCheckboxes || addCheckBox) &&
|
|
273
|
+
(organizedOptions.groups.length > 0 ||
|
|
274
|
+
organizedOptions.selectedValues.length > 0 ||
|
|
275
|
+
organizedOptions.unselectedValues.length > 0)
|
|
276
|
+
"
|
|
277
|
+
>
|
|
278
|
+
<li
|
|
279
|
+
@mousedown="handleSelect($event, allOption)"
|
|
280
|
+
class="relative flex min-h-[36px] cursor-pointer select-none items-center gap-3 py-2 pl-3 pr-9 text-gray-700 hover:bg-gray-100"
|
|
281
|
+
>
|
|
282
|
+
<input
|
|
283
|
+
type="checkbox"
|
|
284
|
+
name="select-all"
|
|
285
|
+
class="pointer-events-none h-4 w-4 cursor-pointer rounded border-gray-300 text-indigo-600 focus:ring-indigo-500 disabled:opacity-50"
|
|
286
|
+
:checked="selectAll"
|
|
287
|
+
/>
|
|
288
|
+
|
|
289
|
+
<div class="flex items-center gap-2">
|
|
290
|
+
<c-icon
|
|
291
|
+
v-if="allOption && allOption.icon"
|
|
292
|
+
:class="allOption.icon.class"
|
|
293
|
+
:name="allOption.icon.name"
|
|
294
|
+
:type="allOption.icon.type"
|
|
295
|
+
:viewBox="allOption.icon.viewBox"
|
|
296
|
+
></c-icon>
|
|
297
|
+
<span class="list-options block break-words font-normal">
|
|
298
|
+
Select all
|
|
299
|
+
</span>
|
|
300
|
+
</div>
|
|
301
|
+
</li>
|
|
302
|
+
<hr class="my-1" />
|
|
303
|
+
</template>
|
|
304
|
+
<!-- Groups Section -->
|
|
305
|
+
<li
|
|
306
|
+
v-for="(option, index) in organizedOptions.groups"
|
|
307
|
+
:key="`group-${option.id || index}`"
|
|
308
|
+
id="listbox-option-group"
|
|
309
|
+
role="option"
|
|
310
|
+
:ref="`option-group-${String(index)}`"
|
|
311
|
+
@mousedown="handleSelect($event, option)"
|
|
312
|
+
:class="option.isDisabled ? 'pointer-events-none' : ''"
|
|
313
|
+
>
|
|
314
|
+
<span
|
|
315
|
+
class="flex min-h-[36px] w-full items-center gap-3 px-3 py-2"
|
|
316
|
+
:class="`group ${
|
|
317
|
+
option.isDisabled ? 'custom-disabled-state' : ''
|
|
318
|
+
} ${optionClasses} relative cursor-pointer select-none text-gray-700 hover:bg-gray-100`"
|
|
319
|
+
>
|
|
320
|
+
<div v-if="addCheckBox" class="flex h-5 shrink-0 items-center">
|
|
321
|
+
<input
|
|
322
|
+
type="checkbox"
|
|
323
|
+
name="group"
|
|
324
|
+
class="pointer-events-none h-4 w-4 cursor-pointer rounded border-gray-300 text-indigo-600 focus:ring-indigo-500 disabled:opacity-50"
|
|
325
|
+
:checked="isChecked(option)"
|
|
326
|
+
/>
|
|
327
|
+
</div>
|
|
328
|
+
<div class="flex min-w-0 flex-1 items-start gap-2">
|
|
329
|
+
<div v-if="option.showImage" class="shrink-0 pt-0.5">
|
|
330
|
+
<c-avatar
|
|
331
|
+
v-if="option.photo"
|
|
332
|
+
size="extraextraextrasmall"
|
|
333
|
+
:image="option.photo"
|
|
334
|
+
:rounded="true"
|
|
335
|
+
></c-avatar>
|
|
336
|
+
<c-avatar
|
|
337
|
+
v-else
|
|
338
|
+
size="extraextraextrasmall"
|
|
339
|
+
:nameInitials="option.initials"
|
|
340
|
+
:rounded="true"
|
|
341
|
+
:isDynamicallyColored="coloredAvatars"
|
|
342
|
+
></c-avatar>
|
|
343
|
+
</div>
|
|
344
|
+
<div
|
|
345
|
+
class="flex shrink-0 items-center justify-center rounded-full bg-gray-100 p-1"
|
|
346
|
+
v-if="option.showIcon && option.icon"
|
|
347
|
+
>
|
|
348
|
+
<c-icon
|
|
349
|
+
:class="option.icon.class"
|
|
350
|
+
:name="option.icon.name"
|
|
351
|
+
:type="option.icon.type"
|
|
352
|
+
:viewBox="option.icon.viewBox"
|
|
353
|
+
>
|
|
354
|
+
</c-icon>
|
|
355
|
+
</div>
|
|
356
|
+
<span
|
|
357
|
+
:class="[
|
|
358
|
+
option.photo || option.initials ? '' : 'text-left',
|
|
359
|
+
addCheckBox ? 'overflow-hidden' : '',
|
|
360
|
+
]"
|
|
361
|
+
class="list-options min-w-0 flex-1 break-words font-normal"
|
|
362
|
+
v-bind:style="{
|
|
363
|
+
...(shouldShowCustomFonts
|
|
364
|
+
? { fontFamily: option[renderOptionName] }
|
|
365
|
+
: {}),
|
|
366
|
+
}"
|
|
367
|
+
>{{ option[renderOptionName] }}
|
|
368
|
+
</span>
|
|
369
|
+
</div>
|
|
370
|
+
<c-icon
|
|
371
|
+
v-if="!addCheckBox && isOptionSelected(option)"
|
|
372
|
+
name="check-outline-v2"
|
|
373
|
+
type="outline-v2"
|
|
374
|
+
class="h-4 w-4 shrink-0 self-center text-indigo-700"
|
|
375
|
+
/>
|
|
376
|
+
</span>
|
|
377
|
+
</li>
|
|
378
|
+
<hr
|
|
379
|
+
v-if="
|
|
380
|
+
organizedOptions.groups.length > 0 &&
|
|
381
|
+
(organizedOptions.selectedValues.length > 0 ||
|
|
382
|
+
organizedOptions.unselectedValues.length > 0)
|
|
383
|
+
"
|
|
384
|
+
class="my-1"
|
|
385
|
+
/>
|
|
386
|
+
<!-- Selected values section (non-groups) -->
|
|
387
|
+
<li
|
|
388
|
+
v-for="(option, index) in organizedOptions.selectedValues"
|
|
389
|
+
:key="`selected-value-${option.id || index}`"
|
|
390
|
+
:id="`listbox-option-selected-value-${String(index)}`"
|
|
391
|
+
role="option"
|
|
392
|
+
:ref="`option-selected-value-${String(index)}`"
|
|
393
|
+
@mousedown="handleSelect($event, option)"
|
|
394
|
+
:class="option.isDisabled ? 'pointer-events-none' : ''"
|
|
395
|
+
>
|
|
396
|
+
<span
|
|
397
|
+
class="flex min-h-[36px] w-full items-center gap-3 px-3 py-2"
|
|
398
|
+
:class="`group ${
|
|
399
|
+
option.isDisabled ? 'custom-disabled-state' : ''
|
|
400
|
+
} ${optionClasses} relative cursor-pointer select-none text-gray-700 hover:bg-gray-100`"
|
|
401
|
+
>
|
|
402
|
+
<div v-if="addCheckBox" class="flex h-5 shrink-0 items-center">
|
|
403
|
+
<input
|
|
404
|
+
type="checkbox"
|
|
405
|
+
name="value"
|
|
406
|
+
class="pointer-events-none h-4 w-4 cursor-pointer rounded border-gray-300 text-indigo-600 focus:ring-indigo-500 disabled:opacity-50"
|
|
407
|
+
:checked="preservedOrder ? isChecked(option) : true"
|
|
408
|
+
/>
|
|
409
|
+
</div>
|
|
410
|
+
<div class="flex min-w-0 flex-1 items-start gap-2">
|
|
411
|
+
<div v-if="option.showImage" class="shrink-0 pt-0.5">
|
|
412
|
+
<c-avatar
|
|
413
|
+
v-if="option.photo"
|
|
414
|
+
size="extraextraextrasmall"
|
|
415
|
+
:image="option.photo"
|
|
416
|
+
:rounded="true"
|
|
417
|
+
></c-avatar>
|
|
418
|
+
<c-avatar
|
|
419
|
+
v-else
|
|
420
|
+
size="extraextraextrasmall"
|
|
421
|
+
:nameInitials="option.initials"
|
|
422
|
+
:rounded="true"
|
|
423
|
+
:isDynamicallyColored="coloredAvatars"
|
|
424
|
+
></c-avatar>
|
|
425
|
+
</div>
|
|
426
|
+
<div
|
|
427
|
+
class="flex shrink-0 items-center justify-center rounded-full bg-gray-100 p-1"
|
|
428
|
+
v-if="option.showIcon && option.icon"
|
|
429
|
+
>
|
|
430
|
+
<c-icon
|
|
431
|
+
:class="option.icon.class"
|
|
432
|
+
:name="option.icon.name"
|
|
433
|
+
:type="option.icon.type"
|
|
434
|
+
:viewBox="option.icon.viewBox"
|
|
435
|
+
>
|
|
436
|
+
</c-icon>
|
|
437
|
+
</div>
|
|
438
|
+
<span
|
|
439
|
+
:class="[
|
|
440
|
+
option.photo || option.initials ? '' : 'text-left',
|
|
441
|
+
addCheckBox ? 'overflow-hidden' : '',
|
|
442
|
+
]"
|
|
443
|
+
class="list-options min-w-0 flex-1 break-words font-normal"
|
|
444
|
+
v-bind:style="{
|
|
445
|
+
...(shouldShowCustomFonts
|
|
446
|
+
? { fontFamily: option[renderOptionName] }
|
|
447
|
+
: {}),
|
|
448
|
+
}"
|
|
449
|
+
>{{ option[renderOptionName] }}
|
|
450
|
+
</span>
|
|
451
|
+
</div>
|
|
452
|
+
</span>
|
|
453
|
+
<hr v-if="option.isBorder" class="my-1" />
|
|
454
|
+
</li>
|
|
455
|
+
<hr
|
|
456
|
+
v-if="
|
|
457
|
+
organizedOptions.selectedValues.length > 0 &&
|
|
458
|
+
organizedOptions.unselectedValues.length > 0
|
|
459
|
+
"
|
|
460
|
+
class="my-1"
|
|
461
|
+
/>
|
|
462
|
+
<!-- Unselected values section -->
|
|
463
|
+
<li
|
|
464
|
+
v-for="(option, index) in organizedOptions.unselectedValues"
|
|
465
|
+
:key="`unselected-value-${option.id || index}`"
|
|
466
|
+
:id="`listbox-option-unselected-value-${String(index)}`"
|
|
467
|
+
role="option"
|
|
468
|
+
:ref="`option-unselected-value-${String(index)}`"
|
|
469
|
+
@mousedown="handleSelect($event, option)"
|
|
470
|
+
:class="option.isDisabled ? 'pointer-events-none' : ''"
|
|
471
|
+
>
|
|
472
|
+
<span
|
|
473
|
+
class="flex min-h-[36px] w-full items-center gap-3 px-3 py-2"
|
|
474
|
+
:class="`group ${
|
|
475
|
+
option.isDisabled ? 'custom-disabled-state' : ''
|
|
476
|
+
} ${optionClasses} relative cursor-pointer select-none text-gray-700 hover:bg-gray-100`"
|
|
477
|
+
>
|
|
478
|
+
<div v-if="addCheckBox" class="flex h-5 shrink-0 items-center">
|
|
479
|
+
<input
|
|
480
|
+
type="checkbox"
|
|
481
|
+
name="value"
|
|
482
|
+
class="pointer-events-none h-4 w-4 cursor-pointer rounded border-gray-300 text-indigo-600 focus:ring-indigo-500 disabled:opacity-50"
|
|
483
|
+
:checked="selectAll || isChecked(option)"
|
|
484
|
+
/>
|
|
485
|
+
</div>
|
|
486
|
+
<div class="flex min-w-0 flex-1 items-start gap-2">
|
|
487
|
+
<div v-if="option.showImage" class="shrink-0 pt-0.5">
|
|
488
|
+
<c-avatar
|
|
489
|
+
v-if="option.photo"
|
|
490
|
+
size="extraextraextrasmall"
|
|
491
|
+
:image="option.photo"
|
|
492
|
+
:rounded="true"
|
|
493
|
+
></c-avatar>
|
|
494
|
+
<c-avatar
|
|
495
|
+
v-else
|
|
496
|
+
size="extraextraextrasmall"
|
|
497
|
+
:nameInitials="option.initials"
|
|
498
|
+
:rounded="true"
|
|
499
|
+
:isDynamicallyColored="coloredAvatars"
|
|
500
|
+
></c-avatar>
|
|
501
|
+
</div>
|
|
502
|
+
<div
|
|
503
|
+
class="flex shrink-0 items-center justify-center rounded-full bg-gray-100 p-1"
|
|
504
|
+
v-if="option.showIcon && option.icon"
|
|
505
|
+
>
|
|
506
|
+
<c-icon
|
|
507
|
+
:class="option.icon.class"
|
|
508
|
+
:name="option.icon.name"
|
|
509
|
+
:type="option.icon.type"
|
|
510
|
+
:viewBox="option.icon.viewBox"
|
|
511
|
+
>
|
|
512
|
+
</c-icon>
|
|
513
|
+
</div>
|
|
514
|
+
<div
|
|
515
|
+
class="list-options flex min-w-0 flex-1 flex-wrap items-center gap-2 font-normal"
|
|
516
|
+
>
|
|
517
|
+
<span
|
|
518
|
+
:class="[
|
|
519
|
+
option.photo || option.initials ? '' : 'text-left',
|
|
520
|
+
addCheckBox ? 'overflow-hidden' : '',
|
|
521
|
+
]"
|
|
522
|
+
class="min-w-0 flex-1 break-words"
|
|
523
|
+
v-bind:style="{
|
|
524
|
+
...(shouldShowCustomFonts
|
|
525
|
+
? { fontFamily: option[renderOptionName] }
|
|
526
|
+
: {}),
|
|
527
|
+
}"
|
|
528
|
+
>{{ option[renderOptionName] }}</span
|
|
529
|
+
>
|
|
530
|
+
<c-tag
|
|
531
|
+
v-if="option.badge"
|
|
532
|
+
:label="option.badge.label"
|
|
533
|
+
:color="option.badge.color"
|
|
534
|
+
class="shrink-0"
|
|
535
|
+
></c-tag>
|
|
536
|
+
</div>
|
|
537
|
+
</div>
|
|
538
|
+
<c-icon
|
|
539
|
+
v-if="!addCheckBox && isOptionSelected(option)"
|
|
540
|
+
name="check-outline-v2"
|
|
541
|
+
type="outline-v2"
|
|
542
|
+
class="h-4 w-4 shrink-0 self-center text-indigo-700"
|
|
543
|
+
/>
|
|
544
|
+
</span>
|
|
545
|
+
<hr v-if="option.isBorder" class="my-1" />
|
|
546
|
+
</li>
|
|
547
|
+
<li
|
|
548
|
+
v-if="
|
|
549
|
+
organizedOptions.groups.length === 0 &&
|
|
550
|
+
organizedOptions.selectedValues.length === 0 &&
|
|
551
|
+
organizedOptions.unselectedValues.length === 0 &&
|
|
552
|
+
selectSearch &&
|
|
553
|
+
selectSearch.length > 0
|
|
554
|
+
"
|
|
555
|
+
class="px-3 py-2 text-sm text-gray-500"
|
|
556
|
+
>
|
|
557
|
+
No options found, try searching something else.
|
|
558
|
+
</li>
|
|
559
|
+
</ul>
|
|
560
|
+
<div
|
|
561
|
+
v-if="isFooter"
|
|
562
|
+
class="group cursor-pointer rounded-b-md bg-gray-50 p-3 ring-1 ring-gray-900 ring-opacity-5"
|
|
563
|
+
@mousedown="handleFooter"
|
|
564
|
+
>
|
|
565
|
+
<p class="text-sm text-indigo-700">
|
|
566
|
+
{{ footerText }}
|
|
567
|
+
</p>
|
|
568
|
+
</div>
|
|
569
|
+
<div
|
|
570
|
+
v-if="
|
|
571
|
+
showCreateOption &&
|
|
572
|
+
showCreateOptionAfterSearch &&
|
|
573
|
+
selectSearch &&
|
|
574
|
+
selectSearch.trim()
|
|
575
|
+
"
|
|
576
|
+
class="sticky bottom-0 z-10 flex min-h-[44px] items-start gap-3 rounded-b-md bg-white px-3 py-2 shadow-lg ring-1 ring-gray-900 ring-opacity-5"
|
|
577
|
+
>
|
|
578
|
+
<span class="word-break flex-1 text-sm text-gray-700">{{
|
|
579
|
+
selectSearch
|
|
580
|
+
}}</span>
|
|
581
|
+
|
|
582
|
+
<button
|
|
583
|
+
type="button"
|
|
584
|
+
:id="id + '_create_button'"
|
|
585
|
+
@mousedown="handleCreateOption($event)"
|
|
586
|
+
class="flex items-center gap-1 text-sm text-indigo-700"
|
|
587
|
+
>
|
|
588
|
+
<c-icon
|
|
589
|
+
type="solid"
|
|
590
|
+
name="plus-circle-solid-v2"
|
|
591
|
+
class="h-5 w-5 text-indigo-600"
|
|
592
|
+
viewBox="0 0 24 24"
|
|
593
|
+
></c-icon>
|
|
594
|
+
Create
|
|
595
|
+
</button>
|
|
596
|
+
</div>
|
|
597
|
+
</div>
|
|
598
|
+
</transition>
|
|
599
|
+
</div>
|
|
600
|
+
<p
|
|
601
|
+
v-if="helpText && isValidate == true"
|
|
602
|
+
class="mt-2 text-left text-sm text-gray-500"
|
|
603
|
+
>
|
|
604
|
+
{{ helpText }}
|
|
605
|
+
</p>
|
|
606
|
+
<!-- validation error message -->
|
|
607
|
+
<p
|
|
608
|
+
v-if="!isValidate && errorMessage"
|
|
609
|
+
class="mt-2 text-left text-sm text-red-600"
|
|
610
|
+
>
|
|
611
|
+
{{ errorMessage }}
|
|
612
|
+
</p>
|
|
613
|
+
</div>
|
|
614
|
+
</template>
|
|
615
|
+
<script>
|
|
616
|
+
import { isNumber, isEmpty } from "lodash-es";
|
|
617
|
+
import CIcon from "../CIcon/CIcon.vue";
|
|
618
|
+
import { getActionID } from "../../helper";
|
|
619
|
+
import CTag from '../CTag/CTag.vue';
|
|
620
|
+
import CAvatar from '../CAvatar/CAvatar.vue';
|
|
621
|
+
export default {
|
|
622
|
+
name: "CSelect",
|
|
623
|
+
components: {
|
|
624
|
+
CIcon,
|
|
625
|
+
CTag,
|
|
626
|
+
CAvatar,
|
|
627
|
+
},
|
|
628
|
+
props: {
|
|
629
|
+
// id of selectpicker
|
|
630
|
+
id: {
|
|
631
|
+
type: String,
|
|
632
|
+
},
|
|
633
|
+
// label of selectpicker
|
|
634
|
+
label: {
|
|
635
|
+
type: String,
|
|
636
|
+
},
|
|
637
|
+
// action button of selectpicker
|
|
638
|
+
actionBtn: {
|
|
639
|
+
type: String,
|
|
640
|
+
default: null,
|
|
641
|
+
},
|
|
642
|
+
// placeholder in selectpicker
|
|
643
|
+
placeholder: {
|
|
644
|
+
type: String,
|
|
645
|
+
},
|
|
646
|
+
// list to render in dropdown
|
|
647
|
+
options: {
|
|
648
|
+
type: Array,
|
|
649
|
+
required: true,
|
|
650
|
+
},
|
|
651
|
+
// show the avatars with different colors
|
|
652
|
+
coloredAvatars: {
|
|
653
|
+
type: Boolean,
|
|
654
|
+
},
|
|
655
|
+
// text below dropdown to describe more about the field
|
|
656
|
+
helpText: {
|
|
657
|
+
type: String,
|
|
658
|
+
},
|
|
659
|
+
// implement a fixed btn at the bottom of dropdown
|
|
660
|
+
isFooter: {
|
|
661
|
+
type: Boolean,
|
|
662
|
+
},
|
|
663
|
+
// style input field
|
|
664
|
+
inputStyle: {
|
|
665
|
+
type: String,
|
|
666
|
+
},
|
|
667
|
+
// footer btn text
|
|
668
|
+
footerText: {
|
|
669
|
+
type: String,
|
|
670
|
+
},
|
|
671
|
+
// text adjacent to label of dropdown to provide hint about field
|
|
672
|
+
hint: { type: String },
|
|
673
|
+
// to show if image is present along with dropdown option
|
|
674
|
+
showImage: { type: Boolean },
|
|
675
|
+
// whether the select field is mandatory or not
|
|
676
|
+
isRequired: {
|
|
677
|
+
type: Boolean,
|
|
678
|
+
},
|
|
679
|
+
// validation is passed or not
|
|
680
|
+
isValidate: { type: Boolean, default: true },
|
|
681
|
+
// will truncate the text in input field
|
|
682
|
+
selectedOptionStyles: {
|
|
683
|
+
type: String,
|
|
684
|
+
},
|
|
685
|
+
// validation error message
|
|
686
|
+
errorMessage: {
|
|
687
|
+
type: String,
|
|
688
|
+
},
|
|
689
|
+
// perform action on first option in dropdown
|
|
690
|
+
addAction: { type: Object },
|
|
691
|
+
// icons in dropdown list
|
|
692
|
+
icon: { type: Object },
|
|
693
|
+
// value to set as default option in dropdown
|
|
694
|
+
value: { type: [Object, String, Array], default: null },
|
|
695
|
+
// type of dropdown - gray,tertiary, blue or default
|
|
696
|
+
type: {
|
|
697
|
+
type: String,
|
|
698
|
+
},
|
|
699
|
+
// name of the field in array to that contains text to render in dropdown
|
|
700
|
+
renderOptionName: {
|
|
701
|
+
type: String,
|
|
702
|
+
default: "option",
|
|
703
|
+
},
|
|
704
|
+
// whether to disable the selectpicker
|
|
705
|
+
isDisabled: {
|
|
706
|
+
type: Boolean,
|
|
707
|
+
default: false,
|
|
708
|
+
},
|
|
709
|
+
inputClasses: {
|
|
710
|
+
type: String,
|
|
711
|
+
default: "",
|
|
712
|
+
},
|
|
713
|
+
optionClasses: {
|
|
714
|
+
type: String,
|
|
715
|
+
default: "",
|
|
716
|
+
},
|
|
717
|
+
// customised width for the dropdown
|
|
718
|
+
customWidth: {
|
|
719
|
+
type: String,
|
|
720
|
+
default: null,
|
|
721
|
+
},
|
|
722
|
+
customBorderRadius: {
|
|
723
|
+
type: String,
|
|
724
|
+
default: "rounded-md",
|
|
725
|
+
},
|
|
726
|
+
addCheckBox: {
|
|
727
|
+
type: Boolean,
|
|
728
|
+
default: false,
|
|
729
|
+
},
|
|
730
|
+
selectAll: {
|
|
731
|
+
type: Boolean,
|
|
732
|
+
default: false,
|
|
733
|
+
},
|
|
734
|
+
selectCheckboxes: {
|
|
735
|
+
type: Boolean,
|
|
736
|
+
default: false,
|
|
737
|
+
},
|
|
738
|
+
headerSwitch: {
|
|
739
|
+
type: Object,
|
|
740
|
+
default: null,
|
|
741
|
+
},
|
|
742
|
+
useSticky: {
|
|
743
|
+
type: Boolean,
|
|
744
|
+
default: false,
|
|
745
|
+
},
|
|
746
|
+
showCloseButton: {
|
|
747
|
+
type: Boolean,
|
|
748
|
+
default: false,
|
|
749
|
+
},
|
|
750
|
+
showCreateOption: {
|
|
751
|
+
type: Boolean,
|
|
752
|
+
default: false,
|
|
753
|
+
},
|
|
754
|
+
shouldShowCustomFonts: {
|
|
755
|
+
type: Boolean,
|
|
756
|
+
default: false,
|
|
757
|
+
},
|
|
758
|
+
customFontNames: {
|
|
759
|
+
type: Array,
|
|
760
|
+
default: () => [],
|
|
761
|
+
},
|
|
762
|
+
inputContainerClass: {
|
|
763
|
+
type: String,
|
|
764
|
+
default: "",
|
|
765
|
+
},
|
|
766
|
+
},
|
|
767
|
+
data() {
|
|
768
|
+
return {
|
|
769
|
+
toggleDropdown: false,
|
|
770
|
+
selectedValue: null,
|
|
771
|
+
showFocus: false,
|
|
772
|
+
selectSearch: null,
|
|
773
|
+
renderOptions: this.options,
|
|
774
|
+
selectedValuesArray: [],
|
|
775
|
+
isToggle: 0,
|
|
776
|
+
shouldOpenAbove: false,
|
|
777
|
+
// Frozen order while dropdown is open - prevents UI shift on selection
|
|
778
|
+
preservedOrder: null,
|
|
779
|
+
};
|
|
780
|
+
},
|
|
781
|
+
computed: {
|
|
782
|
+
classes() {
|
|
783
|
+
return {
|
|
784
|
+
"text-gray-900 border border-gray-300 hover:bg-gray-200 bg-gray-100 focus:ring-2 focus:border-gray-200 focus:ring-gray-200 focus:ring-offset-2 shadow-sm":
|
|
785
|
+
this.type == "gray",
|
|
786
|
+
"bg-white border border-gray-300 hover:bg-indigo-100 text-indigo-600 hover:text-indigo-800 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-200":
|
|
787
|
+
this.type == "tertiary",
|
|
788
|
+
"bg-white text-indigo-700 border-none focus:outline-none":
|
|
789
|
+
this.type == "indigo",
|
|
790
|
+
"border border-gray-300 bg-white shadow-sm focus:outline-none focus:border-gray-300 focus:ring-indigo-600 focus:ring-2 focus:ring-offset-2":
|
|
791
|
+
this.type == null,
|
|
792
|
+
};
|
|
793
|
+
},
|
|
794
|
+
allOption() {
|
|
795
|
+
if (
|
|
796
|
+
this.options &&
|
|
797
|
+
this.options.length > 0 &&
|
|
798
|
+
this.options[0] &&
|
|
799
|
+
this.options[0].id === "all"
|
|
800
|
+
) {
|
|
801
|
+
return this.options[0];
|
|
802
|
+
}
|
|
803
|
+
return null;
|
|
804
|
+
},
|
|
805
|
+
organizedOptions() {
|
|
806
|
+
if (!this.addCheckBox) {
|
|
807
|
+
const filteredOptions = this.renderOptions.filter(
|
|
808
|
+
(option) => option.id !== "all"
|
|
809
|
+
);
|
|
810
|
+
return {
|
|
811
|
+
groups: [],
|
|
812
|
+
selectedValues: [],
|
|
813
|
+
unselectedValues: filteredOptions,
|
|
814
|
+
};
|
|
815
|
+
}
|
|
816
|
+
|
|
817
|
+
// While dropdown is open and not searching, preserve order to prevent UI shift on selection
|
|
818
|
+
if (
|
|
819
|
+
this.preservedOrder &&
|
|
820
|
+
(!this.selectSearch || this.selectSearch.trim() === "")
|
|
821
|
+
) {
|
|
822
|
+
return this.preservedOrder;
|
|
823
|
+
}
|
|
824
|
+
|
|
825
|
+
return this.computeOrganizedOptions();
|
|
826
|
+
},
|
|
827
|
+
showCreateOptionAfterSearch() {
|
|
828
|
+
if (!this.showCreateOption) return false;
|
|
829
|
+
|
|
830
|
+
const term = (this.selectSearch || "").trim().toLowerCase();
|
|
831
|
+
if (!term) return false;
|
|
832
|
+
|
|
833
|
+
const hasExactMatch = this.options.some((option) => {
|
|
834
|
+
const label = option[this.renderOptionName];
|
|
835
|
+
return label && String(label).toLowerCase() === term;
|
|
836
|
+
});
|
|
837
|
+
|
|
838
|
+
return !hasExactMatch;
|
|
839
|
+
},
|
|
840
|
+
dropdownSelectorIconClass() {
|
|
841
|
+
return this.type === "indigo" ? "text-indigo-600" : "text-gray-400";
|
|
842
|
+
},
|
|
843
|
+
hasSelectedValue() {
|
|
844
|
+
const val = this.selectedValue;
|
|
845
|
+
if (val === null || val === undefined || val === "") {
|
|
846
|
+
return false;
|
|
847
|
+
}
|
|
848
|
+
if (typeof val === "object") {
|
|
849
|
+
return !isEmpty(val);
|
|
850
|
+
}
|
|
851
|
+
return true;
|
|
852
|
+
},
|
|
853
|
+
selectedValueClass() {
|
|
854
|
+
if (this.hasSelectedValue) {
|
|
855
|
+
if (this.isDisabled) {
|
|
856
|
+
return "text-gray-500";
|
|
857
|
+
}
|
|
858
|
+
|
|
859
|
+
if (this.type === "indigo") {
|
|
860
|
+
return "text-indigo-600";
|
|
861
|
+
}
|
|
862
|
+
return "text-gray-700";
|
|
863
|
+
}
|
|
864
|
+
return "text-gray-500";
|
|
865
|
+
},
|
|
866
|
+
},
|
|
867
|
+
methods: {
|
|
868
|
+
isEmpty,
|
|
869
|
+
getActionIDFn(name) {
|
|
870
|
+
return getActionID(name, this.id);
|
|
871
|
+
},
|
|
872
|
+
checkAvailableSpace() {
|
|
873
|
+
if (!this.$refs.inputContainer) return false;
|
|
874
|
+
|
|
875
|
+
const container = this.$refs.inputContainer;
|
|
876
|
+
const rect = container.getBoundingClientRect();
|
|
877
|
+
const viewportHeight = window.innerHeight;
|
|
878
|
+
const bottomSpace = viewportHeight - rect.bottom;
|
|
879
|
+
const componentHeight = 320;
|
|
880
|
+
|
|
881
|
+
// Check if bottom space is less than 80% the component height if there is no footer
|
|
882
|
+
return (
|
|
883
|
+
bottomSpace <
|
|
884
|
+
(this.isFooter ? componentHeight : (componentHeight * 4) / 5)
|
|
885
|
+
);
|
|
886
|
+
},
|
|
887
|
+
getDropdownPosition() {
|
|
888
|
+
this.shouldOpenAbove = this.checkAvailableSpace();
|
|
889
|
+
return this.shouldOpenAbove ? "bottom-full" : "top-full";
|
|
890
|
+
},
|
|
891
|
+
getDropdownPosition2() {
|
|
892
|
+
return this.shouldOpenAbove ? "bottom-full" : "";
|
|
893
|
+
},
|
|
894
|
+
getDropdownShadow() {
|
|
895
|
+
if (this.shouldOpenAbove) {
|
|
896
|
+
return "shadow-[0_-10px_15px_-3px_rgba(0,0,0,0.1),0_-4px_6px_-4px_rgba(0,0,0,0.1)]";
|
|
897
|
+
} else {
|
|
898
|
+
return "shadow-lg";
|
|
899
|
+
}
|
|
900
|
+
},
|
|
901
|
+
handleCrossClick(event) {
|
|
902
|
+
event.preventDefault();
|
|
903
|
+
this.$emit("hide-single-select-dropdown");
|
|
904
|
+
},
|
|
905
|
+
handleInputClick() {
|
|
906
|
+
this.toggleDropdown = !this.toggleDropdown;
|
|
907
|
+
if (!this.toggleDropdown) {
|
|
908
|
+
this.selectSearch = "";
|
|
909
|
+
this.$nextTick(() => {
|
|
910
|
+
if (this.$refs["c-select-input"]) {
|
|
911
|
+
this.$refs["c-select-input"].focus();
|
|
912
|
+
}
|
|
913
|
+
});
|
|
914
|
+
}
|
|
915
|
+
},
|
|
916
|
+
handleInputFocus() {
|
|
917
|
+
if (this.type == "tertiary") {
|
|
918
|
+
this.showFocus = true;
|
|
919
|
+
}
|
|
920
|
+
},
|
|
921
|
+
handleSelect(event, option) {
|
|
922
|
+
this.selectSearch = null;
|
|
923
|
+
this.selectedValue = option[this.renderOptionName];
|
|
924
|
+
if (this.addCheckBox) {
|
|
925
|
+
// Freeze current display order BEFORE updating selection to prevent UI shift
|
|
926
|
+
const currentDisplay = this.organizedOptions;
|
|
927
|
+
this.preservedOrder = {
|
|
928
|
+
groups: [...currentDisplay.groups],
|
|
929
|
+
selectedValues: [...currentDisplay.selectedValues],
|
|
930
|
+
unselectedValues: [...currentDisplay.unselectedValues],
|
|
931
|
+
};
|
|
932
|
+
if (this.getIndex(option) == -1) {
|
|
933
|
+
this.selectedValuesArray = [...this.selectedValuesArray, option];
|
|
934
|
+
} else {
|
|
935
|
+
this.selectedValuesArray = this.selectedValuesArray.filter(
|
|
936
|
+
(item) => option.id !== item.id
|
|
937
|
+
);
|
|
938
|
+
}
|
|
939
|
+
this.$emit("onChangeChecked", this.selectedValuesArray, option);
|
|
940
|
+
}
|
|
941
|
+
|
|
942
|
+
this.$emit("onChangeValue", option);
|
|
943
|
+
if (!this.addCheckBox) {
|
|
944
|
+
this.toggleDropdown = false;
|
|
945
|
+
}
|
|
946
|
+
this.type === "tertiary" ? (this.showFocus = false) : "";
|
|
947
|
+
this.renderOptions = this.options;
|
|
948
|
+
},
|
|
949
|
+
handleFooter() {
|
|
950
|
+
this.$emit("handleFooter");
|
|
951
|
+
},
|
|
952
|
+
actionEvent(event) {
|
|
953
|
+
this.$emit("listAction", event);
|
|
954
|
+
},
|
|
955
|
+
actionBtnEvent(event) {
|
|
956
|
+
this.$emit("listAction", event);
|
|
957
|
+
},
|
|
958
|
+
handleElementBlur() {
|
|
959
|
+
this.toggleDropdown = false;
|
|
960
|
+
if (this.addCheckBox) {
|
|
961
|
+
this.$emit("onClickOutside", this.selectedValuesArray);
|
|
962
|
+
}
|
|
963
|
+
this.selectSearch = null;
|
|
964
|
+
this.renderOptions = this.options;
|
|
965
|
+
},
|
|
966
|
+
handleHeaderSwitch(value) {
|
|
967
|
+
this.$emit("handleHeaderSwitch", value);
|
|
968
|
+
},
|
|
969
|
+
//this prevents the blur to be called when the user clicks on the scrollbar
|
|
970
|
+
handlePreventBlur(event) {
|
|
971
|
+
event.preventDefault();
|
|
972
|
+
},
|
|
973
|
+
search() {
|
|
974
|
+
this.selectedValue = null;
|
|
975
|
+
// if the search term is empty space, then set the render options to empty array
|
|
976
|
+
if (
|
|
977
|
+
this.selectSearch !== null &&
|
|
978
|
+
this.selectSearch.length > 0 &&
|
|
979
|
+
this.selectSearch.trim() === ""
|
|
980
|
+
) {
|
|
981
|
+
this.renderOptions = [];
|
|
982
|
+
this.$emit("search-term", this.selectSearch);
|
|
983
|
+
return;
|
|
984
|
+
}
|
|
985
|
+
if (
|
|
986
|
+
!this.selectSearch ||
|
|
987
|
+
this.selectSearch == "" ||
|
|
988
|
+
this.selectSearch.trim() == ""
|
|
989
|
+
) {
|
|
990
|
+
this.renderOptions = this.options;
|
|
991
|
+
this.$emit("search-term", this.selectSearch);
|
|
992
|
+
return;
|
|
993
|
+
}
|
|
994
|
+
let options = [...this.options];
|
|
995
|
+
this.renderOptions = options.filter((option) => {
|
|
996
|
+
isNumber(option[this.renderOptionName])
|
|
997
|
+
? (option[this.renderOptionName] =
|
|
998
|
+
option[this.renderOptionName].toString())
|
|
999
|
+
: "";
|
|
1000
|
+
const optionName = String(
|
|
1001
|
+
option[this.renderOptionName] || ""
|
|
1002
|
+
).toLowerCase();
|
|
1003
|
+
const searchTerm = this.selectSearch.trim().toLowerCase();
|
|
1004
|
+
return optionName.includes(searchTerm);
|
|
1005
|
+
});
|
|
1006
|
+
|
|
1007
|
+
if (this.renderOptions.length === 0) {
|
|
1008
|
+
this.$emit("search-term", this.selectSearch.trim());
|
|
1009
|
+
}
|
|
1010
|
+
},
|
|
1011
|
+
async scrollOptionIntoView(refName) {
|
|
1012
|
+
await this.$nextTick();
|
|
1013
|
+
const list = this.$refs.optionsList;
|
|
1014
|
+
const optionEl = (this.$refs[refName] || [])[0];
|
|
1015
|
+
if (!list || !optionEl) return;
|
|
1016
|
+
|
|
1017
|
+
const top = optionEl.offsetTop;
|
|
1018
|
+
const bottom = top + optionEl.offsetHeight;
|
|
1019
|
+
const viewTop = list.scrollTop;
|
|
1020
|
+
const viewBottom = viewTop + list.clientHeight;
|
|
1021
|
+
|
|
1022
|
+
const target =
|
|
1023
|
+
top < viewTop
|
|
1024
|
+
? top
|
|
1025
|
+
: bottom > viewBottom
|
|
1026
|
+
? bottom - list.clientHeight
|
|
1027
|
+
: viewTop;
|
|
1028
|
+
|
|
1029
|
+
if (target !== viewTop) {
|
|
1030
|
+
list.scrollTo({ top: target, behavior: "smooth" });
|
|
1031
|
+
}
|
|
1032
|
+
},
|
|
1033
|
+
isOptionSelected(option) {
|
|
1034
|
+
/*
|
|
1035
|
+
* Compares value to option by id when both exist,
|
|
1036
|
+
* otherwise by renderOptionName / string value.
|
|
1037
|
+
*/
|
|
1038
|
+
if (option == null) {
|
|
1039
|
+
return false;
|
|
1040
|
+
}
|
|
1041
|
+
|
|
1042
|
+
const v = this.value;
|
|
1043
|
+
if (v === null) {
|
|
1044
|
+
return false;
|
|
1045
|
+
}
|
|
1046
|
+
|
|
1047
|
+
if (typeof v === "object" && v !== null) {
|
|
1048
|
+
if (v.id !== undefined && option.id !== undefined) {
|
|
1049
|
+
return v.id === option.id;
|
|
1050
|
+
}
|
|
1051
|
+
|
|
1052
|
+
const vLabel = v[this.renderOptionName];
|
|
1053
|
+
if (vLabel !== undefined && vLabel !== null) {
|
|
1054
|
+
return String(vLabel) === String(option[this.renderOptionName]);
|
|
1055
|
+
}
|
|
1056
|
+
}
|
|
1057
|
+
|
|
1058
|
+
return String(v) === String(option[this.renderOptionName]);
|
|
1059
|
+
},
|
|
1060
|
+
getIndex(option) {
|
|
1061
|
+
return this.selectedValuesArray.findIndex((item) => item.id == option.id);
|
|
1062
|
+
},
|
|
1063
|
+
isChecked(option) {
|
|
1064
|
+
return this.selectedValuesArray.findIndex(
|
|
1065
|
+
(item) => item.id == option.id
|
|
1066
|
+
) == -1
|
|
1067
|
+
? false
|
|
1068
|
+
: true;
|
|
1069
|
+
},
|
|
1070
|
+
handleCreateOption(event) {
|
|
1071
|
+
event.preventDefault();
|
|
1072
|
+
this.toggleDropdown = false;
|
|
1073
|
+
this.$emit("handleCreateOption", {
|
|
1074
|
+
event,
|
|
1075
|
+
searchText: this.selectSearch.trim(),
|
|
1076
|
+
});
|
|
1077
|
+
this.selectSearch = null;
|
|
1078
|
+
this.renderOptions = this.options;
|
|
1079
|
+
|
|
1080
|
+
const cSelectInputRef = this.$refs["c-select-input"];
|
|
1081
|
+
this.$nextTick(() => {
|
|
1082
|
+
cSelectInputRef && cSelectInputRef.blur();
|
|
1083
|
+
});
|
|
1084
|
+
},
|
|
1085
|
+
handleKeydown(event) {
|
|
1086
|
+
if (event.key === "Enter") {
|
|
1087
|
+
event.preventDefault();
|
|
1088
|
+
event.stopPropagation();
|
|
1089
|
+
|
|
1090
|
+
if (
|
|
1091
|
+
this.showCreateOption &&
|
|
1092
|
+
this.showCreateOptionAfterSearch &&
|
|
1093
|
+
this.selectSearch &&
|
|
1094
|
+
this.selectSearch.trim()
|
|
1095
|
+
) {
|
|
1096
|
+
this.handleCreateOption(event);
|
|
1097
|
+
}
|
|
1098
|
+
}
|
|
1099
|
+
},
|
|
1100
|
+
async handleLoadFont() {
|
|
1101
|
+
const customFontNames = this.customFontNames;
|
|
1102
|
+
const googleParam = customFontNames
|
|
1103
|
+
.map((f) => `family=${f.replace(/ /g, "+")}`)
|
|
1104
|
+
.join("&");
|
|
1105
|
+
|
|
1106
|
+
const link = document.createElement("link");
|
|
1107
|
+
link.rel = "stylesheet";
|
|
1108
|
+
link.href = `https://fonts.googleapis.com/css2?${googleParam}&display=swap`;
|
|
1109
|
+
link.setAttribute("data-fonts", customFontNames.join(","));
|
|
1110
|
+
document.head.appendChild(link);
|
|
1111
|
+
|
|
1112
|
+
await document.fonts.ready;
|
|
1113
|
+
},
|
|
1114
|
+
isGroup(option) {
|
|
1115
|
+
return option.type && option.type.includes("group");
|
|
1116
|
+
},
|
|
1117
|
+
computeOrganizedOptions() {
|
|
1118
|
+
const selectedIds = new Set(
|
|
1119
|
+
this.selectedValuesArray.map((item) => item.id)
|
|
1120
|
+
);
|
|
1121
|
+
|
|
1122
|
+
const groups = [];
|
|
1123
|
+
const selectedValues = [];
|
|
1124
|
+
const unselectedValues = [];
|
|
1125
|
+
|
|
1126
|
+
// Filter out the "all" option from renderOptions
|
|
1127
|
+
this.renderOptions
|
|
1128
|
+
.filter((option) => option.id !== "all")
|
|
1129
|
+
.forEach((option) => {
|
|
1130
|
+
if (this.isGroup(option)) {
|
|
1131
|
+
// Groups stay in their own section regardless of selection
|
|
1132
|
+
groups.push(option);
|
|
1133
|
+
} else {
|
|
1134
|
+
// Values are separated by selection state
|
|
1135
|
+
if (selectedIds.has(option.id)) {
|
|
1136
|
+
selectedValues.push(option);
|
|
1137
|
+
} else {
|
|
1138
|
+
unselectedValues.push(option);
|
|
1139
|
+
}
|
|
1140
|
+
}
|
|
1141
|
+
});
|
|
1142
|
+
|
|
1143
|
+
// Sort by renderOptionName (guard against undefined a/b or missing renderOptionName)
|
|
1144
|
+
const getSortKey = (item) =>
|
|
1145
|
+
String((item && item[this.renderOptionName]) || "").toLowerCase();
|
|
1146
|
+
|
|
1147
|
+
selectedValues.sort((a, b) => getSortKey(a).localeCompare(getSortKey(b)));
|
|
1148
|
+
unselectedValues.sort((a, b) =>
|
|
1149
|
+
getSortKey(a).localeCompare(getSortKey(b))
|
|
1150
|
+
);
|
|
1151
|
+
|
|
1152
|
+
return {
|
|
1153
|
+
groups,
|
|
1154
|
+
selectedValues,
|
|
1155
|
+
unselectedValues,
|
|
1156
|
+
};
|
|
1157
|
+
},
|
|
1158
|
+
},
|
|
1159
|
+
watch: {
|
|
1160
|
+
value() {
|
|
1161
|
+
this.selectedValue =
|
|
1162
|
+
this.value !== null && this.value[this.renderOptionName]
|
|
1163
|
+
? this.value[this.renderOptionName]
|
|
1164
|
+
: this.value;
|
|
1165
|
+
if (this.addCheckBox) {
|
|
1166
|
+
this.selectedValuesArray = this.value ? this.value : [];
|
|
1167
|
+
}
|
|
1168
|
+
},
|
|
1169
|
+
options() {
|
|
1170
|
+
this.renderOptions = this.options;
|
|
1171
|
+
},
|
|
1172
|
+
toggleDropdown(newValue) {
|
|
1173
|
+
if (newValue) {
|
|
1174
|
+
this.checkAvailableSpace();
|
|
1175
|
+
|
|
1176
|
+
this.preservedOrder = null;
|
|
1177
|
+
|
|
1178
|
+
const index = this.renderOptions.findIndex(
|
|
1179
|
+
(option) => option[this.renderOptionName] === this.selectedValue
|
|
1180
|
+
);
|
|
1181
|
+
if (index !== -1) {
|
|
1182
|
+
const refName = `option-${String(index)}`;
|
|
1183
|
+
this.scrollOptionIntoView(refName);
|
|
1184
|
+
}
|
|
1185
|
+
} else {
|
|
1186
|
+
this.preservedOrder = null;
|
|
1187
|
+
}
|
|
1188
|
+
},
|
|
1189
|
+
},
|
|
1190
|
+
mounted() {
|
|
1191
|
+
this.selectedValue =
|
|
1192
|
+
this.value !== null && this.value[this.renderOptionName]
|
|
1193
|
+
? this.value[this.renderOptionName]
|
|
1194
|
+
: this.value;
|
|
1195
|
+
if (this.addCheckBox) {
|
|
1196
|
+
this.selectedValuesArray = this.value ? this.value : [];
|
|
1197
|
+
}
|
|
1198
|
+
|
|
1199
|
+
if (this.shouldShowCustomFonts && this.customFontNames.length > 0) {
|
|
1200
|
+
this.handleLoadFont();
|
|
1201
|
+
}
|
|
1202
|
+
},
|
|
1203
|
+
};
|
|
1204
|
+
</script>
|
|
1205
|
+
|
|
1206
|
+
<style scoped>
|
|
1207
|
+
.word-break {
|
|
1208
|
+
word-break: break-word;
|
|
1209
|
+
}
|
|
1210
|
+
</style>
|