classcard-ui 0.2.1509 → 0.2.1510

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 (217) hide show
  1. package/README.md +24 -24
  2. package/dist/classcard-ui.common.js +90 -89
  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 +90 -89
  6. package/dist/classcard-ui.umd.js.map +1 -1
  7. package/dist/classcard-ui.umd.min.js +1 -1
  8. package/dist/classcard-ui.umd.min.js.map +1 -1
  9. package/package.json +84 -84
  10. package/src/.DS_Store +0 -0
  11. package/src/App.vue +16 -16
  12. package/src/colorConfig.js +52 -52
  13. package/src/components/CAlertModal/CAlertModal.vue +181 -181
  14. package/src/components/CAlertModal/index.js +3 -3
  15. package/src/components/CAlerts/CAlerts.vue +114 -114
  16. package/src/components/CAlerts/index.js +2 -2
  17. package/src/components/CAnchorTabs/CAnchorTabs.vue +100 -100
  18. package/src/components/CAnchorTabs/index.js +2 -2
  19. package/src/components/CAnchorTag/CAnchorTag.vue +84 -84
  20. package/src/components/CAnchorTag/index.js +2 -2
  21. package/src/components/CAvatar/CAvatar.vue +231 -231
  22. package/src/components/CAvatar/index.js +2 -2
  23. package/src/components/CAvatarGroup/CAvatarGroup.vue +213 -213
  24. package/src/components/CAvatarGroup/index.js +2 -2
  25. package/src/components/CBasicTable/CBasicTable.vue +184 -184
  26. package/src/components/CBasicTable/index.js +2 -2
  27. package/src/components/CBreadcrumbs/CBreadcrumbs.vue +38 -38
  28. package/src/components/CBreadcrumbs/index.js +2 -2
  29. package/src/components/CButton/CButton.vue +239 -239
  30. package/src/components/CButton/index.js +2 -2
  31. package/src/components/CButtonGroup/CButtonGroup.vue +155 -155
  32. package/src/components/CButtonGroup/index.js +2 -2
  33. package/src/components/CButtonIcon/CButtonIcon.vue +166 -166
  34. package/src/components/CButtonIcon/index.js +2 -2
  35. package/src/components/CButtonLink/CButtonLink.vue +43 -43
  36. package/src/components/CButtonLink/index.js +2 -2
  37. package/src/components/CButtonSelect/CButtonSelect.vue +186 -186
  38. package/src/components/CButtonSelect/index.js +2 -2
  39. package/src/components/CButtonSelectBorder/CButtonSelectBorder.vue +265 -265
  40. package/src/components/CButtonSelectBorder/index.js +3 -3
  41. package/src/components/CButtonWithDropdown/CButtonWithDropdown.vue +208 -208
  42. package/src/components/CButtonWithDropdown/index.js +2 -2
  43. package/src/components/CCalendar/CCalendar.vue +443 -443
  44. package/src/components/CCalendar/index.js +3 -3
  45. package/src/components/CCard/CCard.vue +53 -53
  46. package/src/components/CCard/index.js +2 -2
  47. package/src/components/CCheckbox/CCheckbox.vue +200 -200
  48. package/src/components/CCheckbox/index.js +2 -2
  49. package/src/components/CCircularButton/CCircularButton.vue +57 -57
  50. package/src/components/CCircularButton/index.js +2 -2
  51. package/src/components/CCollapsibleSection/CCollapsibleSection.vue +121 -121
  52. package/src/components/CCollapsibleSection/index.js +2 -2
  53. package/src/components/CColorDots/CColorDots.vue +52 -52
  54. package/src/components/CColorDots/index.js +3 -3
  55. package/src/components/CConfirmActionModal/CConfirmActionModal.vue +290 -290
  56. package/src/components/CConfirmActionModal/index.js +3 -3
  57. package/src/components/CDatepicker/CDatepicker.vue +235 -235
  58. package/src/components/CDatepicker/index.js +2 -2
  59. package/src/components/CDualSelect/CDualSelect.vue +193 -193
  60. package/src/components/CDualSelect/index.js +2 -2
  61. package/src/components/CEditor/CEditor.vue +137 -137
  62. package/src/components/CEditor/index.js +6 -6
  63. package/src/components/CEditor/quill-font-setup.js +133 -133
  64. package/src/components/CFormSectionHeading/CFormSectionHeading.vue +76 -76
  65. package/src/components/CFormSectionHeading/index.js +2 -2
  66. package/src/components/CGroupedFilterDropdown/CGroupedFilterDropdown.vue +331 -331
  67. package/src/components/CGroupedFilterDropdown/index.js +2 -2
  68. package/src/components/CGroupedSelect/CGroupedSelect.vue +393 -393
  69. package/src/components/CGroupedSelect/index.js +3 -3
  70. package/src/components/CIcon/CIcon.vue +112 -112
  71. package/src/components/CIcon/index.js +2 -2
  72. package/src/components/CIconDropdown/CIconDropdown.vue +206 -206
  73. package/src/components/CIconDropdown/index.js +2 -2
  74. package/src/components/CIconSelect/CIconSelect.vue +182 -182
  75. package/src/components/CIconSelect/index.js +3 -3
  76. package/src/components/CInput/CInput.vue +227 -227
  77. package/src/components/CInput/index.js +2 -2
  78. package/src/components/CInputAddon/CInputAddon.vue +297 -297
  79. package/src/components/CInputAddon/index.js +2 -2
  80. package/src/components/CInputEmail/CInputEmail.vue +107 -107
  81. package/src/components/CInputEmail/index.js +2 -2
  82. package/src/components/CInsetTabs/CInsetTabs.vue +222 -222
  83. package/src/components/CInsetTabs/index.js +3 -3
  84. package/src/components/CLinearProgressBar/CLinearProgressBar.vue +35 -35
  85. package/src/components/CLinearProgressBar/index.js +2 -2
  86. package/src/components/CModalHeading/CModalHeading.vue +22 -22
  87. package/src/components/CModalHeading/index.js +2 -2
  88. package/src/components/CModuleHelpLinks/CModuleHelpLinks.vue +88 -88
  89. package/src/components/CModuleHelpLinks/index.js +3 -3
  90. package/src/components/CMultiselect/CMultiselect.vue +1314 -1314
  91. package/src/components/CMultiselect/index.js +2 -2
  92. package/src/components/CMultiselectr/CMultiselectr.vue +44 -44
  93. package/src/components/CMultiselectr/index.js +2 -2
  94. package/src/components/CPageHeading/CPageHeading.vue +83 -83
  95. package/src/components/CPageHeading/index.js +2 -2
  96. package/src/components/CPagination/CPagination.vue +239 -239
  97. package/src/components/CPagination/index.js +2 -2
  98. package/src/components/CPhoneNumber/CPhoneNumber.vue +213 -213
  99. package/src/components/CPhoneNumber/index.js +2 -2
  100. package/src/components/CProgress/CProgress.vue +95 -95
  101. package/src/components/CProgress/index.js +2 -2
  102. package/src/components/CRadio/CRadio.vue +198 -198
  103. package/src/components/CRadio/index.js +2 -2
  104. package/src/components/CRadioGroup/CRadioGroup.vue +96 -96
  105. package/src/components/CRadioGroup/index.js +2 -2
  106. package/src/components/CRangeSlider/CRangeSlider.vue +55 -55
  107. package/src/components/CRangeSlider/index.js +2 -2
  108. package/src/components/CReorderableStackedList/CReorderableStackedList.vue +94 -94
  109. package/src/components/CReorderableStackedList/index.js +2 -2
  110. package/src/components/CSelect/CSelect.vue +1225 -1225
  111. package/src/components/CSelect/index.js +2 -2
  112. package/src/components/CSmallTimeline/CSmallTimeline.vue +40 -40
  113. package/src/components/CSmallTimeline/index.js +2 -2
  114. package/src/components/CStackedList/CStackedList.vue +162 -162
  115. package/src/components/CStackedList/index.js +2 -2
  116. package/src/components/CStats/CStats.vue +157 -157
  117. package/src/components/CStats/index.js +2 -2
  118. package/src/components/CSwitch/CSwitch.vue +200 -200
  119. package/src/components/CSwitch/index.js +2 -2
  120. package/src/components/CTabLazy/CTabLazy.vue +83 -83
  121. package/src/components/CTabLazy/index.js +2 -2
  122. package/src/components/CTable/CTable.vue +1114 -1114
  123. package/src/components/CTable/index.js +2 -2
  124. package/src/components/CTabs/CTabs.vue +250 -250
  125. package/src/components/CTabs/index.js +2 -2
  126. package/src/components/CTag/CTag.vue +109 -109
  127. package/src/components/CTag/index.js +2 -2
  128. package/src/components/CTextarea/CTextarea.vue +118 -118
  129. package/src/components/CTextarea/index.js +2 -2
  130. package/src/components/CTimeline/CTimeline.vue +237 -237
  131. package/src/components/CTimeline/index.js +2 -2
  132. package/src/components/CTokenEditor/CTokenEditor.vue +804 -804
  133. package/src/components/CTokenEditor/index.js +3 -3
  134. package/src/components/CToolTip/CToolTip.vue +108 -108
  135. package/src/components/CToolTip/index.js +3 -3
  136. package/src/components/CUpload/CUpload.vue +332 -332
  137. package/src/components/CUpload/index.js +2 -2
  138. package/src/components/NumberAnimator.vue +112 -112
  139. package/src/components/index.js +60 -60
  140. package/src/helper.js +8 -8
  141. package/src/icons.js +870 -859
  142. package/src/main.js +22 -22
  143. package/src/stories/CAlertModal.stories.js +30 -30
  144. package/src/stories/CAlerts.stories.js +39 -39
  145. package/src/stories/CAnchorTabs.stories.js +29 -29
  146. package/src/stories/CAnchorTag.stories.js +38 -38
  147. package/src/stories/CAvatar.stories.js +38 -38
  148. package/src/stories/CAvatarGroup.stories.js +136 -136
  149. package/src/stories/CBasicTable.stories.js +316 -316
  150. package/src/stories/CBreadcrumbs.stories.js +24 -24
  151. package/src/stories/CButton.stories.js +49 -49
  152. package/src/stories/CButtonGroup.stories.js +43 -43
  153. package/src/stories/CButtonIcon.stories.js +27 -27
  154. package/src/stories/CButtonLink.stories.js +24 -24
  155. package/src/stories/CButtonSelect.stories.js +44 -44
  156. package/src/stories/CButtonSelectBorder.stories.js +56 -56
  157. package/src/stories/CButtonWithDropdown.stories.js +41 -41
  158. package/src/stories/CCalendar.stories.js +16 -16
  159. package/src/stories/CCard.stories.js +30 -30
  160. package/src/stories/CCheckbox.stories.js +38 -38
  161. package/src/stories/CCircularButton.stories.js +29 -29
  162. package/src/stories/CCollapsibleSection.stories.js +29 -29
  163. package/src/stories/CColorDots.stories.js +37 -37
  164. package/src/stories/CConfirmActionModal.stories.js +67 -67
  165. package/src/stories/CDatepicker.stories.js +31 -31
  166. package/src/stories/CDualSelect.stories.js +29 -29
  167. package/src/stories/CEditor.stories.js +36 -36
  168. package/src/stories/CFormSectionHeading.stories.js +37 -37
  169. package/src/stories/CGroupedFilterDropdown.stories.js +131 -131
  170. package/src/stories/CGroupedSelect.stories.js +103 -103
  171. package/src/stories/CIcon.stories.js +31 -31
  172. package/src/stories/CIconDropdown.stories.js +52 -52
  173. package/src/stories/CIconSelect.stories.js +45 -45
  174. package/src/stories/CInput.stories.js +50 -50
  175. package/src/stories/CInputAddon.stories.js +37 -37
  176. package/src/stories/CInputEmail.stories.js +27 -27
  177. package/src/stories/CInsetTabs.stories.js +50 -50
  178. package/src/stories/CLinearProgressBar.stories.js +22 -22
  179. package/src/stories/CModalHeading.stories.js +25 -25
  180. package/src/stories/CModuleHelpLinks.stories.js +25 -25
  181. package/src/stories/CMultiselect.stories.js +146 -146
  182. package/src/stories/CMultiselectr.stories.js +23 -23
  183. package/src/stories/CPageHeading.stories.js +32 -32
  184. package/src/stories/CPagination.stories.js +30 -30
  185. package/src/stories/CPhoneNumber.stories.js +37 -37
  186. package/src/stories/CProgress.stories.js +23 -23
  187. package/src/stories/CRadio.stories.js +44 -44
  188. package/src/stories/CRadioGroup.stories.js +51 -51
  189. package/src/stories/CRangeSlider.stories.js +23 -23
  190. package/src/stories/CReorderableStackedList.stories.js +23 -23
  191. package/src/stories/CSelect.stories.js +157 -157
  192. package/src/stories/CSmallTimeline.stories.js +26 -26
  193. package/src/stories/CStackedList.stories.js +37 -37
  194. package/src/stories/CStats.stories.js +53 -53
  195. package/src/stories/CSwitch.stories.js +28 -28
  196. package/src/stories/CTabLazy.stories.js +42 -42
  197. package/src/stories/CTable.stories.js +203 -203
  198. package/src/stories/CTabs.stories.js +36 -36
  199. package/src/stories/CTag.stories.js +37 -37
  200. package/src/stories/CTextarea.stories.js +32 -32
  201. package/src/stories/CTimeline.stories.js +26 -26
  202. package/src/stories/CTokenEditor.stories.js +136 -136
  203. package/src/stories/CToolTip.stories.js +27 -27
  204. package/src/stories/CUpload.stories.js +36 -36
  205. package/src/stories/Introduction.stories.mdx +207 -207
  206. package/src/stories/Page.vue +88 -88
  207. package/src/stories/assets/code-brackets.svg +0 -0
  208. package/src/stories/assets/colors.svg +0 -0
  209. package/src/stories/assets/comments.svg +0 -0
  210. package/src/stories/assets/direction.svg +0 -0
  211. package/src/stories/assets/flow.svg +0 -0
  212. package/src/stories/assets/plugin.svg +0 -0
  213. package/src/stories/assets/repo.svg +0 -0
  214. package/src/stories/assets/stackalt.svg +0 -0
  215. package/src/stories/header.css +26 -26
  216. package/src/stories/page.css +69 -69
  217. package/src/stories/utils.css +32 -32
@@ -1,393 +1,393 @@
1
- <template>
2
- <div>
3
- <div
4
- v-if="showDropDown && options.length > 0"
5
- class="fixed inset-0"
6
- @click="closeDropDown(false)"
7
- aria-hidden="true"
8
- ></div>
9
- <div class="flex items-center" v-if="label">
10
- <!-- label of select field -->
11
- <label class="block text-sm font-medium text-gray-900">
12
- {{ label }}
13
- </label>
14
- <!-- asterisk sign to render if field is required -->
15
- <p v-if="isRequired" class="ml-1 text-red-600">*</p>
16
- </div>
17
- <div class="relative mt-1" ref="inputContainer">
18
- <div class="relative">
19
- <input
20
- type="text"
21
- class="relative w-full cursor-default rounded-md border border-gray-300 bg-white py-2 pl-3 pr-10 text-left shadow-sm focus:border-indigo-500 focus:outline-none focus:ring-1 focus:ring-indigo-500 disabled:pointer-events-none disabled:opacity-50 sm:text-sm"
22
- aria-haspopup="listbox"
23
- aria-expanded="true"
24
- aria-labelledby="listbox-label"
25
- :value="inputValue"
26
- :placeholder="placeholder"
27
- autocomplete="off"
28
- @focus="openDropDown()"
29
- @input="onInput"
30
- :disabled="disabled"
31
- :id="id + '_input_search'"
32
- />
33
- <span
34
- class="pointer-events-none absolute inset-y-0 right-0 flex items-center pr-2"
35
- >
36
- <svg
37
- class="h-5 w-5 animate-spin text-gray-400"
38
- xmlns="http://www.w3.org/2000/svg"
39
- fill="none"
40
- viewBox="0 0 24 24"
41
- v-if="isSearching"
42
- >
43
- <circle
44
- class="opacity-25"
45
- cx="12"
46
- cy="12"
47
- r="10"
48
- stroke="currentColor"
49
- stroke-width="4"
50
- ></circle>
51
- <path
52
- class="opacity-75"
53
- fill="currentColor"
54
- d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"
55
- ></path>
56
- </svg>
57
- <!-- Heroicon name: solid/selector -->
58
- <svg
59
- class="h-5 w-5 text-gray-400"
60
- xmlns="http://www.w3.org/2000/svg"
61
- viewBox="0 0 20 20"
62
- fill="currentColor"
63
- aria-hidden="true"
64
- v-if="!isSearching"
65
- >
66
- <path
67
- fill-rule="evenodd"
68
- d="M10 3a1 1 0 01.707.293l3 3a1 1 0 01-1.414 1.414L10 5.414 7.707 7.707a1 1 0 01-1.414-1.414l3-3A1 1 0 0110 3zm-3.707 9.293a1 1 0 011.414 0L10 14.586l2.293-2.293a1 1 0 011.414 1.414l-3 3a1 1 0 01-1.414 0l-3-3a1 1 0 010-1.414z"
69
- clip-rule="evenodd"
70
- />
71
- </svg>
72
- </span>
73
- <button
74
- class="z-100 absolute top-2.5 right-10"
75
- v-if="
76
- showCloseButton && (inputValue == null || inputValue.length == 0)
77
- "
78
- @click.stop="handleCrossClick"
79
- :id="id + '_button_close_dropdown'"
80
- >
81
- <c-icon
82
- name="close"
83
- type="outline-v2"
84
- class="h-4 w-4 text-gray-400"
85
- cursorType="cursor-pointer"
86
- />
87
- </button>
88
- </div>
89
- <!--
90
- Select popover, show/hide based on select state.
91
-
92
- Entering: ""
93
- From: ""
94
- To: ""
95
- Leaving: "transition ease-in duration-100"
96
- From: "opacity-100"
97
- To: "opacity-0"
98
- -->
99
- <div
100
- v-if="showDropDown"
101
- :class="[
102
- shouldOpenAbove ? 'bottom-full mb-1' : 'top-full mt-1',
103
- 'absolute z-10 max-h-60 w-full',
104
- ]"
105
- >
106
- <ul
107
- tabindex="-1"
108
- role="listbox"
109
- aria-labelledby="listbox-label"
110
- aria-activedescendant="listbox-option-3"
111
- v-if="options.length > 0"
112
- class="max-h-60 overflow-auto overscroll-contain rounded-md bg-white text-base shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none sm:text-sm"
113
- >
114
- <!--
115
- Select option, manage highlight styles based on mouseenter/mouseleave and keyboard navigation.
116
-
117
- Highlighted: "text-white bg-indigo-600", Not Highlighted: "text-gray-900"
118
- -->
119
-
120
- <template v-for="option in options">
121
- <li
122
- :key="option.id"
123
- class="sticky top-0 z-20 cursor-default select-none bg-gray-100 px-3 py-2 text-left text-sm text-gray-700"
124
- role="option"
125
- >
126
- <span class="block truncate"> {{ option.label }} </span>
127
- </li>
128
- <li
129
- v-for="subOption in option.options"
130
- :class="[
131
- 'group relative flex select-none justify-between gap-3 px-3 py-2 text-left text-gray-900',
132
- customiseCSS ? 'flex' : '',
133
- subOption.isDisabled
134
- ? 'cursor-not-allowed opacity-50'
135
- : 'cursor-default hover:bg-gray-100 hover:text-gray-900',
136
- ]"
137
- :key="subOption.id"
138
- role="option"
139
- @click="handleOptionClick(subOption)"
140
- >
141
- <!-- custom slot for option ui -->
142
- <slot
143
- v-if="$scopedSlots['custom-option']"
144
- name="custom-option"
145
- :subOption="subOption"
146
- :option="option"
147
- :keyField="keyField"
148
- ></slot>
149
-
150
- <!-- default option ui -->
151
- <template v-else>
152
- <div class="flex w-full items-center">
153
- <img
154
- v-if="subOption.image_url"
155
- :src="subOption.image_url"
156
- :class="[
157
- 'mr-3 h-10 w-10 flex-shrink-0 object-cover',
158
- subOption.type === 'user' ? 'rounded-full' : 'rounded-md',
159
- ]"
160
- loading="lazy"
161
- :alt="subOption.label"
162
- />
163
- <div
164
- v-if="
165
- !subOption.image_url &&
166
- subOption.type === 'user' &&
167
- subOption.initials
168
- "
169
- class="mr-3 flex h-10 w-10 items-center justify-center rounded-full bg-gray-500 text-white"
170
- >
171
- <span class="text-base text-white">{{
172
- subOption.initials
173
- }}</span>
174
- </div>
175
- <div
176
- v-if="!subOption.image_url && subOption.icon"
177
- class="mr-3 flex h-10 w-10 items-center justify-center rounded-md bg-gray-100"
178
- >
179
- <c-icon
180
- :name="subOption.icon.name"
181
- :type="subOption.icon.type"
182
- :class="subOption.icon.class"
183
- :viewBox="subOption.icon.viewBox"
184
- ></c-icon>
185
- </div>
186
- <span class="flex-1">
187
- <div class="flex items-center justify-between">
188
- <span
189
- class="flex"
190
- :class="[
191
- subOption[keyField] === value[keyField]
192
- ? 'font-semibold'
193
- : 'font-normal',
194
- ' block break-words',
195
- ]"
196
- >
197
- {{ subOption.label }}
198
- <span
199
- v-if="subOption.labelExtra"
200
- class="ml-1 text-sm text-gray-400 group-hover:text-gray-400"
201
- >{{ subOption.labelExtra }}</span
202
- >
203
- <div
204
- v-if="
205
- subOption.stock_level ||
206
- (subOption.stock_level <= 0 &&
207
- subOption.stock_level !== null)
208
- "
209
- >
210
- <p
211
- class="ml-2 text-sm"
212
- :class="[quantityLabelColor(subOption)]"
213
- >
214
- {{ subOption.stock_level }} units
215
- </p>
216
- </div>
217
- </span>
218
- <slot name="custom-tag" :subOption="subOption"></slot>
219
- </div>
220
- <span
221
- class="mt-1 text-sm font-normal text-gray-500 group-hover:text-gray-500"
222
- v-if="subOption.description"
223
- >
224
- {{ subOption.description }}
225
- </span>
226
- </span>
227
- </div>
228
- <p
229
- v-if="subOption.price"
230
- class="relative flex items-center whitespace-nowrap text-sm text-gray-900"
231
- >
232
- {{ subOption.price }}
233
- </p>
234
- <span
235
- class="absolute inset-y-0 right-0 flex items-center pr-4 text-indigo-600 group-hover:text-white"
236
- v-if="subOption[keyField] === value[keyField]"
237
- >
238
- <svg
239
- class="h-5 w-5"
240
- xmlns="http://www.w3.org/2000/svg"
241
- viewBox="0 0 20 20"
242
- fill="currentColor"
243
- aria-hidden="true"
244
- >
245
- <path
246
- fill-rule="evenodd"
247
- d="M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z"
248
- clip-rule="evenodd"
249
- />
250
- </svg>
251
- </span>
252
- </template>
253
- </li>
254
- </template>
255
- </ul>
256
- <p
257
- v-else
258
- class="rounded-md bg-white py-2 text-center text-sm shadow-lg ring-1 ring-black ring-opacity-5"
259
- >
260
- No match found. Try a different search.
261
- </p>
262
- </div>
263
- </div>
264
- <p v-if="isError" class="mt-2 text-left text-sm text-red-600">
265
- {{ errorMessage }}
266
- </p>
267
- </div>
268
- </template>
269
-
270
- <script>
271
- import { debounce } from "lodash-es";
272
- import CIcon from "../CIcon/CIcon.vue";
273
-
274
- export default {
275
- name: "CGroupedSelect",
276
- components: {
277
- CIcon,
278
- },
279
- props: {
280
- label: String,
281
- options: Array,
282
- value: Object,
283
- placeholder: String,
284
- isSearching: Boolean,
285
- disabled: { type: Boolean, default: false },
286
- isError: { type: Boolean, default: false },
287
- errorMessage: {
288
- type: String,
289
- default: "This field cannot be empty",
290
- },
291
- keyField: { type: String, default: "label" },
292
- // whether the select field is mandatory or not
293
- isRequired: {
294
- type: Boolean,
295
- },
296
- customiseCSS: {
297
- type: Boolean,
298
- default: false,
299
- },
300
- showCloseButton: {
301
- type: Boolean,
302
- default: false,
303
- },
304
- // whether to search when the select field is focused
305
- searchOnFocus: {
306
- type: Boolean,
307
- default: true,
308
- },
309
- },
310
- data() {
311
- return {
312
- showDropDown: false,
313
- inputValue: "",
314
- shouldOpenAbove: false,
315
- };
316
- },
317
- methods: {
318
- updateDropdownPlacement() {
319
- if (!this.$refs.inputContainer) {
320
- this.shouldOpenAbove = false;
321
- return;
322
- }
323
-
324
- const rect = this.$refs.inputContainer.getBoundingClientRect();
325
- const dropdownMaxHeight = 240; // max height of the dropdown in pixels - max-h-60
326
- const bottomSpace = window.innerHeight - rect.bottom;
327
- const topSpace = rect.top;
328
-
329
- this.shouldOpenAbove =
330
- bottomSpace < dropdownMaxHeight && topSpace > bottomSpace;
331
- },
332
- handleCrossClick(event) {
333
- event.preventDefault();
334
- this.$emit("hide-grouped-select-dropdown");
335
- },
336
- onInput(e) {
337
- this.inputValue = e.target.value;
338
- this.emitSearch(e.target.value);
339
- },
340
- emitSearch: debounce(function (value) {
341
- if (value !== this.value.label) {
342
- this.$emit("search", value);
343
- } else {
344
- this.$emit("search", "");
345
- }
346
- }, 500),
347
- handleOptionClick(option) {
348
- this.inputValue = option.label;
349
- this.closeDropDown(true);
350
- this.$emit("input", option);
351
- },
352
- closeDropDown(hasData) {
353
- this.inputValue = this.value.label;
354
- this.showDropDown = false;
355
- this.$emit("blur", { hasData: hasData });
356
- },
357
- openDropDown() {
358
- this.showDropDown = true;
359
- if (this.searchOnFocus) {
360
- this.emitSearch("");
361
- }
362
- },
363
- quantityLabelColor(data) {
364
- if (data.stock_level <= 0) {
365
- return "text-red-300";
366
- } else if (data.stock_level < data.alert_level) {
367
- return "text-orange-300";
368
- } else {
369
- return "text-gray-300";
370
- }
371
- },
372
- },
373
- watch: {
374
- value(newValue) {
375
- this.inputValue = newValue.label;
376
- },
377
- showDropDown(newValue) {
378
- if (newValue) {
379
- this.$nextTick(this.updateDropdownPlacement);
380
- }
381
- },
382
- },
383
- mounted() {
384
- this.inputValue = this.value.label;
385
- },
386
- };
387
- </script>
388
-
389
- <style scoped>
390
- input::placeholder {
391
- @apply text-gray-500;
392
- }
393
- </style>
1
+ <template>
2
+ <div>
3
+ <div
4
+ v-if="showDropDown && options.length > 0"
5
+ class="fixed inset-0"
6
+ @click="closeDropDown(false)"
7
+ aria-hidden="true"
8
+ ></div>
9
+ <div class="flex items-center" v-if="label">
10
+ <!-- label of select field -->
11
+ <label class="block text-sm font-medium text-gray-900">
12
+ {{ label }}
13
+ </label>
14
+ <!-- asterisk sign to render if field is required -->
15
+ <p v-if="isRequired" class="ml-1 text-red-600">*</p>
16
+ </div>
17
+ <div class="relative mt-1" ref="inputContainer">
18
+ <div class="relative">
19
+ <input
20
+ type="text"
21
+ class="relative w-full cursor-default rounded-md border border-gray-300 bg-white py-2 pl-3 pr-10 text-left shadow-sm focus:border-indigo-500 focus:outline-none focus:ring-1 focus:ring-indigo-500 disabled:pointer-events-none disabled:opacity-50 sm:text-sm"
22
+ aria-haspopup="listbox"
23
+ aria-expanded="true"
24
+ aria-labelledby="listbox-label"
25
+ :value="inputValue"
26
+ :placeholder="placeholder"
27
+ autocomplete="off"
28
+ @focus="openDropDown()"
29
+ @input="onInput"
30
+ :disabled="disabled"
31
+ :id="id + '_input_search'"
32
+ />
33
+ <span
34
+ class="pointer-events-none absolute inset-y-0 right-0 flex items-center pr-2"
35
+ >
36
+ <svg
37
+ class="h-5 w-5 animate-spin text-gray-400"
38
+ xmlns="http://www.w3.org/2000/svg"
39
+ fill="none"
40
+ viewBox="0 0 24 24"
41
+ v-if="isSearching"
42
+ >
43
+ <circle
44
+ class="opacity-25"
45
+ cx="12"
46
+ cy="12"
47
+ r="10"
48
+ stroke="currentColor"
49
+ stroke-width="4"
50
+ ></circle>
51
+ <path
52
+ class="opacity-75"
53
+ fill="currentColor"
54
+ d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"
55
+ ></path>
56
+ </svg>
57
+ <!-- Heroicon name: solid/selector -->
58
+ <svg
59
+ class="h-5 w-5 text-gray-400"
60
+ xmlns="http://www.w3.org/2000/svg"
61
+ viewBox="0 0 20 20"
62
+ fill="currentColor"
63
+ aria-hidden="true"
64
+ v-if="!isSearching"
65
+ >
66
+ <path
67
+ fill-rule="evenodd"
68
+ d="M10 3a1 1 0 01.707.293l3 3a1 1 0 01-1.414 1.414L10 5.414 7.707 7.707a1 1 0 01-1.414-1.414l3-3A1 1 0 0110 3zm-3.707 9.293a1 1 0 011.414 0L10 14.586l2.293-2.293a1 1 0 011.414 1.414l-3 3a1 1 0 01-1.414 0l-3-3a1 1 0 010-1.414z"
69
+ clip-rule="evenodd"
70
+ />
71
+ </svg>
72
+ </span>
73
+ <button
74
+ class="z-100 absolute top-2.5 right-10"
75
+ v-if="
76
+ showCloseButton && (inputValue == null || inputValue.length == 0)
77
+ "
78
+ @click.stop="handleCrossClick"
79
+ :id="id + '_button_close_dropdown'"
80
+ >
81
+ <c-icon
82
+ name="close"
83
+ type="outline-v2"
84
+ class="h-4 w-4 text-gray-400"
85
+ cursorType="cursor-pointer"
86
+ />
87
+ </button>
88
+ </div>
89
+ <!--
90
+ Select popover, show/hide based on select state.
91
+
92
+ Entering: ""
93
+ From: ""
94
+ To: ""
95
+ Leaving: "transition ease-in duration-100"
96
+ From: "opacity-100"
97
+ To: "opacity-0"
98
+ -->
99
+ <div
100
+ v-if="showDropDown"
101
+ :class="[
102
+ shouldOpenAbove ? 'bottom-full mb-1' : 'top-full mt-1',
103
+ 'absolute z-10 max-h-60 w-full',
104
+ ]"
105
+ >
106
+ <ul
107
+ tabindex="-1"
108
+ role="listbox"
109
+ aria-labelledby="listbox-label"
110
+ aria-activedescendant="listbox-option-3"
111
+ v-if="options.length > 0"
112
+ class="max-h-60 overflow-auto overscroll-contain rounded-md bg-white text-base shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none sm:text-sm"
113
+ >
114
+ <!--
115
+ Select option, manage highlight styles based on mouseenter/mouseleave and keyboard navigation.
116
+
117
+ Highlighted: "text-white bg-indigo-600", Not Highlighted: "text-gray-900"
118
+ -->
119
+
120
+ <template v-for="option in options">
121
+ <li
122
+ :key="option.id"
123
+ class="sticky top-0 z-20 cursor-default select-none bg-gray-100 px-3 py-2 text-left text-sm text-gray-700"
124
+ role="option"
125
+ >
126
+ <span class="block truncate"> {{ option.label }} </span>
127
+ </li>
128
+ <li
129
+ v-for="subOption in option.options"
130
+ :class="[
131
+ 'group relative flex select-none justify-between gap-3 px-3 py-2 text-left text-gray-900',
132
+ customiseCSS ? 'flex' : '',
133
+ subOption.isDisabled
134
+ ? 'cursor-not-allowed opacity-50'
135
+ : 'cursor-default hover:bg-gray-100 hover:text-gray-900',
136
+ ]"
137
+ :key="subOption.id"
138
+ role="option"
139
+ @click="handleOptionClick(subOption)"
140
+ >
141
+ <!-- custom slot for option ui -->
142
+ <slot
143
+ v-if="$scopedSlots['custom-option']"
144
+ name="custom-option"
145
+ :subOption="subOption"
146
+ :option="option"
147
+ :keyField="keyField"
148
+ ></slot>
149
+
150
+ <!-- default option ui -->
151
+ <template v-else>
152
+ <div class="flex w-full items-center">
153
+ <img
154
+ v-if="subOption.image_url"
155
+ :src="subOption.image_url"
156
+ :class="[
157
+ 'mr-3 h-10 w-10 flex-shrink-0 object-cover',
158
+ subOption.type === 'user' ? 'rounded-full' : 'rounded-md',
159
+ ]"
160
+ loading="lazy"
161
+ :alt="subOption.label"
162
+ />
163
+ <div
164
+ v-if="
165
+ !subOption.image_url &&
166
+ subOption.type === 'user' &&
167
+ subOption.initials
168
+ "
169
+ class="mr-3 flex h-10 w-10 items-center justify-center rounded-full bg-gray-500 text-white"
170
+ >
171
+ <span class="text-base text-white">{{
172
+ subOption.initials
173
+ }}</span>
174
+ </div>
175
+ <div
176
+ v-if="!subOption.image_url && subOption.icon"
177
+ class="mr-3 flex h-10 w-10 items-center justify-center rounded-md bg-gray-100"
178
+ >
179
+ <c-icon
180
+ :name="subOption.icon.name"
181
+ :type="subOption.icon.type"
182
+ :class="subOption.icon.class"
183
+ :viewBox="subOption.icon.viewBox"
184
+ ></c-icon>
185
+ </div>
186
+ <span class="flex-1">
187
+ <div class="flex items-center justify-between">
188
+ <span
189
+ class="flex"
190
+ :class="[
191
+ subOption[keyField] === value[keyField]
192
+ ? 'font-semibold'
193
+ : 'font-normal',
194
+ ' block break-words',
195
+ ]"
196
+ >
197
+ {{ subOption.label }}
198
+ <span
199
+ v-if="subOption.labelExtra"
200
+ class="ml-1 text-sm text-gray-400 group-hover:text-gray-400"
201
+ >{{ subOption.labelExtra }}</span
202
+ >
203
+ <div
204
+ v-if="
205
+ subOption.stock_level ||
206
+ (subOption.stock_level <= 0 &&
207
+ subOption.stock_level !== null)
208
+ "
209
+ >
210
+ <p
211
+ class="ml-2 text-sm"
212
+ :class="[quantityLabelColor(subOption)]"
213
+ >
214
+ {{ subOption.stock_level }} units
215
+ </p>
216
+ </div>
217
+ </span>
218
+ <slot name="custom-tag" :subOption="subOption"></slot>
219
+ </div>
220
+ <span
221
+ class="mt-1 text-sm font-normal text-gray-500 group-hover:text-gray-500"
222
+ v-if="subOption.description"
223
+ >
224
+ {{ subOption.description }}
225
+ </span>
226
+ </span>
227
+ </div>
228
+ <p
229
+ v-if="subOption.price"
230
+ class="relative flex items-center whitespace-nowrap text-sm text-gray-900"
231
+ >
232
+ {{ subOption.price }}
233
+ </p>
234
+ <span
235
+ class="absolute inset-y-0 right-0 flex items-center pr-4 text-indigo-600 group-hover:text-white"
236
+ v-if="subOption[keyField] === value[keyField]"
237
+ >
238
+ <svg
239
+ class="h-5 w-5"
240
+ xmlns="http://www.w3.org/2000/svg"
241
+ viewBox="0 0 20 20"
242
+ fill="currentColor"
243
+ aria-hidden="true"
244
+ >
245
+ <path
246
+ fill-rule="evenodd"
247
+ d="M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z"
248
+ clip-rule="evenodd"
249
+ />
250
+ </svg>
251
+ </span>
252
+ </template>
253
+ </li>
254
+ </template>
255
+ </ul>
256
+ <p
257
+ v-else
258
+ class="rounded-md bg-white py-2 text-center text-sm shadow-lg ring-1 ring-black ring-opacity-5"
259
+ >
260
+ No match found. Try a different search.
261
+ </p>
262
+ </div>
263
+ </div>
264
+ <p v-if="isError" class="mt-2 text-left text-sm text-red-600">
265
+ {{ errorMessage }}
266
+ </p>
267
+ </div>
268
+ </template>
269
+
270
+ <script>
271
+ import { debounce } from "lodash-es";
272
+ import CIcon from "../CIcon/CIcon.vue";
273
+
274
+ export default {
275
+ name: "CGroupedSelect",
276
+ components: {
277
+ CIcon,
278
+ },
279
+ props: {
280
+ label: String,
281
+ options: Array,
282
+ value: Object,
283
+ placeholder: String,
284
+ isSearching: Boolean,
285
+ disabled: { type: Boolean, default: false },
286
+ isError: { type: Boolean, default: false },
287
+ errorMessage: {
288
+ type: String,
289
+ default: "This field cannot be empty",
290
+ },
291
+ keyField: { type: String, default: "label" },
292
+ // whether the select field is mandatory or not
293
+ isRequired: {
294
+ type: Boolean,
295
+ },
296
+ customiseCSS: {
297
+ type: Boolean,
298
+ default: false,
299
+ },
300
+ showCloseButton: {
301
+ type: Boolean,
302
+ default: false,
303
+ },
304
+ // whether to search when the select field is focused
305
+ searchOnFocus: {
306
+ type: Boolean,
307
+ default: true,
308
+ },
309
+ },
310
+ data() {
311
+ return {
312
+ showDropDown: false,
313
+ inputValue: "",
314
+ shouldOpenAbove: false,
315
+ };
316
+ },
317
+ methods: {
318
+ updateDropdownPlacement() {
319
+ if (!this.$refs.inputContainer) {
320
+ this.shouldOpenAbove = false;
321
+ return;
322
+ }
323
+
324
+ const rect = this.$refs.inputContainer.getBoundingClientRect();
325
+ const dropdownMaxHeight = 240; // max height of the dropdown in pixels - max-h-60
326
+ const bottomSpace = window.innerHeight - rect.bottom;
327
+ const topSpace = rect.top;
328
+
329
+ this.shouldOpenAbove =
330
+ bottomSpace < dropdownMaxHeight && topSpace > bottomSpace;
331
+ },
332
+ handleCrossClick(event) {
333
+ event.preventDefault();
334
+ this.$emit("hide-grouped-select-dropdown");
335
+ },
336
+ onInput(e) {
337
+ this.inputValue = e.target.value;
338
+ this.emitSearch(e.target.value);
339
+ },
340
+ emitSearch: debounce(function (value) {
341
+ if (value !== this.value.label) {
342
+ this.$emit("search", value);
343
+ } else {
344
+ this.$emit("search", "");
345
+ }
346
+ }, 500),
347
+ handleOptionClick(option) {
348
+ this.inputValue = option.label;
349
+ this.closeDropDown(true);
350
+ this.$emit("input", option);
351
+ },
352
+ closeDropDown(hasData) {
353
+ this.inputValue = this.value.label;
354
+ this.showDropDown = false;
355
+ this.$emit("blur", { hasData: hasData });
356
+ },
357
+ openDropDown() {
358
+ this.showDropDown = true;
359
+ if (this.searchOnFocus) {
360
+ this.emitSearch("");
361
+ }
362
+ },
363
+ quantityLabelColor(data) {
364
+ if (data.stock_level <= 0) {
365
+ return "text-red-300";
366
+ } else if (data.stock_level < data.alert_level) {
367
+ return "text-orange-300";
368
+ } else {
369
+ return "text-gray-300";
370
+ }
371
+ },
372
+ },
373
+ watch: {
374
+ value(newValue) {
375
+ this.inputValue = newValue.label;
376
+ },
377
+ showDropDown(newValue) {
378
+ if (newValue) {
379
+ this.$nextTick(this.updateDropdownPlacement);
380
+ }
381
+ },
382
+ },
383
+ mounted() {
384
+ this.inputValue = this.value.label;
385
+ },
386
+ };
387
+ </script>
388
+
389
+ <style scoped>
390
+ input::placeholder {
391
+ @apply text-gray-500;
392
+ }
393
+ </style>