classcard-ui 0.2.1478 → 0.2.1481

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