@usssa/component-library 1.0.0-alpha.21 → 1.0.0-alpha.211

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 (97) hide show
  1. package/README.md +8 -5
  2. package/package.json +25 -6
  3. package/src/assets/fonts/CorneroRegular.woff +0 -0
  4. package/src/assets/fonts/CorneroRegular.woff2 +0 -0
  5. package/src/assets/logo.svg +19 -0
  6. package/src/assets/no-result.svg +25 -0
  7. package/src/assets/upload-illustration.svg +48 -0
  8. package/src/components/core/UAccordionSelect.vue +237 -0
  9. package/src/components/core/UAvatar.vue +90 -26
  10. package/src/components/core/UAvatarGroup.vue +62 -52
  11. package/src/components/core/UBadgeStd.vue +6 -1
  12. package/src/components/core/UBannerStd.vue +100 -31
  13. package/src/components/core/UBreadCrumbs.vue +171 -0
  14. package/src/components/core/UBtnIcon.vue +57 -52
  15. package/src/components/core/UBtnStd.vue +38 -31
  16. package/src/components/core/UBtnToggle.vue +11 -6
  17. package/src/components/core/UCheckboxStd.vue +26 -20
  18. package/src/components/core/UChip.vue +91 -43
  19. package/src/components/core/UDate.vue +581 -0
  20. package/src/components/core/UDialogStd.vue +452 -58
  21. package/src/components/core/UDrawer/UDrawer.vue +471 -0
  22. package/src/components/core/UDrawer/UDrawerMenuItem.vue +124 -0
  23. package/src/components/core/UEventCard.vue +419 -0
  24. package/src/components/core/UExpansionStd.vue +274 -0
  25. package/src/components/core/UExpansionTableStd.vue +297 -0
  26. package/src/components/core/UFilter.vue +89 -0
  27. package/src/components/core/UGameObject.vue +441 -0
  28. package/src/components/core/UInnerLoader.vue +69 -0
  29. package/src/components/core/UInputAddressLookup.vue +484 -0
  30. package/src/components/core/UInputPhoneStd.vue +74 -68
  31. package/src/components/core/UInputTextStd.vue +137 -116
  32. package/src/components/core/UInputTypeahead.vue +44 -0
  33. package/src/components/core/UInputTypeaheadAdvanceSearch.vue +134 -0
  34. package/src/components/core/UMenuButtonStd.vue +280 -0
  35. package/src/components/core/UMenuDropdown.vue +82 -0
  36. package/src/components/core/UMenuDropdownAdvancedSearch.vue +306 -0
  37. package/src/components/core/UMenuItem.vue +221 -0
  38. package/src/components/core/UMenuSearch.vue +73 -0
  39. package/src/components/core/UModal.vue +660 -0
  40. package/src/components/core/UMultiSelectStd.vue +501 -61
  41. package/src/components/core/UPagination.vue +84 -25
  42. package/src/components/core/URadioBtn.vue +66 -43
  43. package/src/components/core/URadioStd.vue +23 -14
  44. package/src/components/core/USelectStd.vue +415 -84
  45. package/src/components/core/USheet.vue +349 -0
  46. package/src/components/core/UStepper/UProgress.vue +157 -0
  47. package/src/components/core/UStepper/UStepper.vue +211 -0
  48. package/src/components/core/UTabBtnStd.vue +36 -22
  49. package/src/components/core/UTable/UTable.vue +2061 -57
  50. package/src/components/core/UTableStd.vue +1311 -273
  51. package/src/components/core/UTabsStd.vue +52 -23
  52. package/src/components/core/UToggleStd.vue +18 -13
  53. package/src/components/core/UToolbar/UCustomMenuIcon.vue +58 -0
  54. package/src/components/core/UToolbar/UToolbar.vue +226 -0
  55. package/src/components/core/UTooltip.vue +31 -10
  56. package/src/components/core/UTypeahead.vue +890 -0
  57. package/src/components/core/UUploader.vue +644 -0
  58. package/src/components/index.js +77 -26
  59. package/src/composables/useNotify.js +16 -16
  60. package/src/composables/useOverlayLoader.js +23 -0
  61. package/src/composables/useScreenType.js +30 -0
  62. package/src/css/app.sass +42 -25
  63. package/src/css/colors.sass +2 -0
  64. package/src/css/quasar.variables.sass +99 -68
  65. package/src/css/vars/colors.variables.sass +28 -41
  66. package/src/utils/data.ts +177 -0
  67. package/src/App.vue +0 -9
  68. package/src/boot/.gitkeep +0 -0
  69. package/src/layouts/MainLayout.vue +0 -137
  70. package/src/pages/Avatar.vue +0 -77
  71. package/src/pages/AvatarGroup.vue +0 -139
  72. package/src/pages/BadgeStd.vue +0 -83
  73. package/src/pages/BannerPage.vue +0 -76
  74. package/src/pages/BtnIcon.vue +0 -120
  75. package/src/pages/BtnStd.vue +0 -126
  76. package/src/pages/BtnToggle.vue +0 -131
  77. package/src/pages/CheckBox.vue +0 -62
  78. package/src/pages/Chip.vue +0 -92
  79. package/src/pages/ComponentBase.vue +0 -54
  80. package/src/pages/Dialog.vue +0 -206
  81. package/src/pages/ErrorNotFound.vue +0 -11
  82. package/src/pages/IndexPage.vue +0 -11
  83. package/src/pages/InputPhone.vue +0 -152
  84. package/src/pages/InputText.vue +0 -139
  85. package/src/pages/MultiSelectStd.vue +0 -174
  86. package/src/pages/NotifyPage.vue +0 -109
  87. package/src/pages/Pagination.vue +0 -71
  88. package/src/pages/Radio.vue +0 -80
  89. package/src/pages/RadioBtn.vue +0 -104
  90. package/src/pages/SelectStd.vue +0 -160
  91. package/src/pages/TabButtonPage.vue +0 -126
  92. package/src/pages/TablePage.vue +0 -371
  93. package/src/pages/TabsPage.vue +0 -261
  94. package/src/pages/TogglePage.vue +0 -58
  95. package/src/pages/TooltipPage.vue +0 -125
  96. package/src/router/index.js +0 -34
  97. package/src/router/routes.js +0 -113
@@ -0,0 +1,890 @@
1
+ <script setup>
2
+ import { computed, ref, toRaw, watch } from 'vue'
3
+ import UAvatar from './UAvatar.vue'
4
+ import UBtnStd from './UBtnStd.vue'
5
+ import UMenuDropdownAdvancedSearch from './UMenuDropdownAdvancedSearch.vue'
6
+ import UTabBtnStd from './UTabBtnStd.vue'
7
+ import UTooltip from './UTooltip.vue'
8
+ import { categorizeObjects, deepEqual, parseInitials } from '../../utils/data'
9
+ import { useNotify } from '../../composables/useNotify'
10
+ import { useScreenType } from '../../composables/useScreenType'
11
+
12
+ const emit = defineEmits([
13
+ 'onInvitePerson',
14
+ 'onItemActionClick',
15
+ 'updateCountry',
16
+ 'updateModelVal',
17
+ 'updateTab',
18
+ ])
19
+
20
+ const loading = defineModel('loading')
21
+ const props = defineProps({
22
+ // Label for button to select a record
23
+ actionButtonLabel: {
24
+ type: String,
25
+ },
26
+ actionButtonLoading: {
27
+ type: Boolean,
28
+ },
29
+ addedBtnIcon: {
30
+ type: String,
31
+ },
32
+ addedBtnLabel: {
33
+ type: String,
34
+ default: 'Added',
35
+ },
36
+ // Set default tab
37
+ advancedSearchSelectedTab: {
38
+ type: String,
39
+ },
40
+ algoliaIndex: {
41
+ type: Object,
42
+ },
43
+ alreadyAddedIds: {
44
+ type: Array,
45
+ },
46
+ // Set context for component for special handling
47
+ // context.action (e.g. addContact)
48
+ // context.data (e.g. array of existing contacts)
49
+ applyLabel: {
50
+ type: String,
51
+ default: 'Apply',
52
+ },
53
+ categoryLabels: {
54
+ type: Object,
55
+ default: () => ({
56
+ users: 'People',
57
+ teams: 'Teams',
58
+ events: 'Events',
59
+ venues: 'Venues',
60
+ other: 'Other',
61
+ }),
62
+ },
63
+ clearAllFieldsLabel: {
64
+ type: String,
65
+ default: 'Clear All Fields',
66
+ },
67
+ context: {
68
+ type: Array,
69
+ },
70
+ disabledButtonColor: {
71
+ type: String,
72
+ },
73
+ exceedLimitBody: {
74
+ type: String,
75
+ default:
76
+ 'Please select advanced search to provide more details for your search.',
77
+ },
78
+ exceedLimitCaption: {
79
+ type: String,
80
+ default: 'Results exceeds limit.',
81
+ },
82
+ exceedLimitCount: {
83
+ type: Number,
84
+ default: 50,
85
+ },
86
+ fields: {
87
+ type: Array,
88
+ },
89
+ headerLabel: {
90
+ type: String,
91
+ default: "I'm looking for",
92
+ },
93
+ inviteLabel: {
94
+ type: String,
95
+ default: 'Invite',
96
+ },
97
+ labelIcon: {
98
+ type: String,
99
+ },
100
+ model: {
101
+ type: Object,
102
+ },
103
+ noDataCaption: {
104
+ type: String,
105
+ default: 'No results found',
106
+ },
107
+ noDataBody: {
108
+ type: String,
109
+ default: 'Invite user to join USSSA.',
110
+ },
111
+ options: {
112
+ type: Array,
113
+ },
114
+ recentlySelected: {
115
+ type: String,
116
+ default: 'Recently Selected',
117
+ },
118
+ recentSearches: {
119
+ type: Array,
120
+ },
121
+ recentSearchesLoading: {
122
+ type: Boolean,
123
+ },
124
+ searchBrokeLabel: {
125
+ type: String,
126
+ default: 'Search broke',
127
+ },
128
+ searchMenuRef: {
129
+ type: Object,
130
+ },
131
+ searchResultsLabel: {
132
+ type: String,
133
+ default: 'Search results',
134
+ },
135
+ searchText: {
136
+ type: String,
137
+ },
138
+ // Show/hide advanced search
139
+ showAdvancedSearch: {
140
+ type: Boolean,
141
+ default: true,
142
+ },
143
+ // Show/hide entity tabs in advanced search
144
+ showAdvancedSearchTabs: {
145
+ type: Boolean,
146
+ default: true,
147
+ },
148
+ showCustomMenu: {
149
+ type: Boolean,
150
+ default: false,
151
+ },
152
+ showDescriptionTooltip: {
153
+ type: Boolean,
154
+ },
155
+ showInviteBtn: {
156
+ type: Boolean,
157
+ default: false,
158
+ },
159
+ // Show/hide recent searches
160
+ showRecentSelected: {
161
+ type: Boolean,
162
+ default: true,
163
+ },
164
+ size: {
165
+ type: String,
166
+ default: 'md',
167
+ },
168
+ //Advance search title
169
+ title: {
170
+ type: String,
171
+ default: 'Advanced search',
172
+ },
173
+ toggleAdvancedSearchAriaLabel: {
174
+ type: String,
175
+ default: 'Toggle advanced search',
176
+ },
177
+ toolTipDisabledButton: {
178
+ type: String,
179
+ },
180
+ })
181
+
182
+ const { notify } = useNotify()
183
+ const $screen = useScreenType()
184
+
185
+ const advancedSearchApplyFilter = ref(false)
186
+ const advancedSearchFilterDirty = ref(false)
187
+ const advancedSearchFilterModelDefault = ref({})
188
+ const advancedSearchSelectedTab = ref(props.advancedSearchSelectedTab ?? null)
189
+ const exceedLimit = ref(false)
190
+ const searchMenuShowing = ref(false)
191
+ const searchResults = ref(null)
192
+ const selectedItem = ref({})
193
+ const toggle = ref(false)
194
+
195
+ const isMobile = computed(() => $screen.value.isMobile)
196
+
197
+ const recentSearches = computed({
198
+ get() {
199
+ let arr = props.recentSearches
200
+ if (props.context) {
201
+ arr = specialContextHandler(arr)
202
+ }
203
+ return arr
204
+ },
205
+ set(value) {
206
+ return value
207
+ },
208
+ })
209
+
210
+ const screenSize = computed(() => {
211
+ if (isMobile.value) {
212
+ return 'sm'
213
+ } else {
214
+ return props.size
215
+ }
216
+ })
217
+
218
+ const convertDate = (inputDate) => {
219
+ const [month, day, year] = inputDate.split('/')
220
+ return `${year}-${month}-${day}`
221
+ }
222
+
223
+ const handleTabChange = (val) => {
224
+ if (advancedSearchSelectedTab.value === val) {
225
+ advancedSearchSelectedTab.value = null
226
+ } else {
227
+ advancedSearchSelectedTab.value = val
228
+ }
229
+ Object.assign(
230
+ props.model,
231
+ structuredClone(toRaw(advancedSearchFilterModelDefault.value))
232
+ )
233
+ emit('updateTab', advancedSearchSelectedTab.value)
234
+ search()
235
+ }
236
+
237
+ const onApplyAdvancedSearchFilter = () => {
238
+ advancedSearchApplyFilter.value = true
239
+ advancedSearchFilterDirty.value = false
240
+ search()
241
+ updateAdvanceSearchToggle()
242
+ }
243
+
244
+ const onClearAdvancedSearchFilter = () => {
245
+ Object.assign(
246
+ props.model,
247
+ structuredClone(toRaw(advancedSearchFilterModelDefault.value))
248
+ )
249
+ advancedSearchApplyFilter.value = false
250
+ advancedSearchFilterDirty.value = false
251
+ search()
252
+ }
253
+
254
+ const onInvitePerson = (item) => {
255
+ emit('onInvitePerson', item, props.searchMenuRef)
256
+ }
257
+
258
+ const onItemActionClick = (item, isRecentSearchAction) => {
259
+ selectedItem.value = item
260
+ return emit(
261
+ 'onItemActionClick',
262
+ item,
263
+ isRecentSearchAction,
264
+ props.searchMenuRef
265
+ )
266
+ }
267
+
268
+ const search = async () => {
269
+ try {
270
+ if (props.searchText.length < 2) {
271
+ searchResults.value = null
272
+ return void 0
273
+ }
274
+
275
+ loading.value = true
276
+ const configPayload = {}
277
+ const selectedTab = advancedSearchSelectedTab.value
278
+ if (selectedTab) {
279
+ configPayload.facetFilters = [`collection:${selectedTab}`]
280
+ } else {
281
+ let advancedSearchTabOptions = props.options.map((option) => {
282
+ return `collection:${option.id}`
283
+ })
284
+ configPayload.facetFilters = [advancedSearchTabOptions]
285
+ }
286
+
287
+ if (advancedSearchApplyFilter.value) {
288
+ const filters = props.model
289
+ let algoliaFilter = ''
290
+
291
+ const phoneNumber = filters?.selectedCountry?.code + filters?.phoneNumber
292
+
293
+ Object.keys(filters).map((key, index) => {
294
+ let filterValue = filters[key]
295
+
296
+ if (key === 'phoneNumber' && filterValue) {
297
+ filterValue = phoneNumber
298
+ } else if (key === 'startDate' && filterValue) {
299
+ filterValue = convertDate(filters.startDate)
300
+ } else if (key === 'endDate' && filterValue) {
301
+ filterValue = convertDate(filters.endDate)
302
+ }
303
+
304
+ switch (key) {
305
+ case 'city':
306
+ if (selectedTab === 'events' || selectedTab === 'venues') {
307
+ key = 'city'
308
+ } else {
309
+ key = 'address.city'
310
+ }
311
+ break
312
+ case 'state':
313
+ if (selectedTab === 'events' || selectedTab === 'venues') {
314
+ key = 'state'
315
+ } else {
316
+ key = 'address.state'
317
+ }
318
+ break
319
+ case 'postalCode':
320
+ if (selectedTab === 'venues') {
321
+ key = 'postalCode'
322
+ } else {
323
+ key = 'address.postalCode'
324
+ }
325
+ break
326
+ case 'selectedCountry':
327
+ key = 'address.country'
328
+ break
329
+ case 'emailAddress':
330
+ key = 'email'
331
+ break
332
+ case 'teamName':
333
+ key = 'name'
334
+ break
335
+ case 'phoneNumber':
336
+ key = 'mobileNumber'
337
+ break
338
+ case 'venueName':
339
+ key = 'name'
340
+ break
341
+ case 'eventName':
342
+ key = 'name'
343
+ break
344
+ case 'startDate':
345
+ key = 'startsOn'
346
+ break
347
+ case 'endDate':
348
+ key = 'endsOn'
349
+ break
350
+ case 'venueAddress':
351
+ key = 'streetAddress'
352
+ break
353
+ default:
354
+ key = key
355
+ }
356
+
357
+ if (filterValue && filterValue.length) {
358
+ algoliaFilter.length
359
+ ? (algoliaFilter += ` AND ${key}:${filterValue}`)
360
+ : (algoliaFilter += `${key}:"${filterValue}"`)
361
+ }
362
+ })
363
+
364
+ if (algoliaFilter.length) {
365
+ configPayload.filters = algoliaFilter
366
+ }
367
+ }
368
+
369
+ const res = await props.algoliaIndex.search(props.searchText, configPayload)
370
+
371
+ if (res.hits.length >= props.exceedLimitCount) {
372
+ exceedLimit.value = true
373
+ } else {
374
+ exceedLimit.value = false
375
+ }
376
+
377
+ let results = res.hits
378
+
379
+ if (props.context) {
380
+ results = specialContextHandler(results)
381
+ }
382
+
383
+ results = categorizeObjects(results, 'collection')
384
+
385
+ if (Object.keys(results).length) {
386
+ searchResults.value = results
387
+ } else {
388
+ searchResults.value = null
389
+ }
390
+ return void 0
391
+ } catch (error) {
392
+ notify({
393
+ type: 'accent',
394
+ label: props.searchBrokeLabel,
395
+ position: 'top',
396
+ })
397
+ } finally {
398
+ loading.value = false
399
+ }
400
+ }
401
+
402
+ const specialContextHandler = (results) => {
403
+ // Mark result records that are already exits
404
+ results = results.map((result) => {
405
+ const isAlreadyAddedSearch = props.context.filter(
406
+ (data) =>
407
+ data.id === result.id ||
408
+ data.id === result.docId ||
409
+ result.id === data.docId
410
+ )
411
+
412
+ isAlreadyAddedSearch.every((data) => {
413
+ if (
414
+ data.id === result.id ||
415
+ data.id === result.docId ||
416
+ result.id === data.docId
417
+ ) {
418
+ if (data?.tooltip) {
419
+ result.disableTooltip = data?.tooltip
420
+ }
421
+ if (data?.label) {
422
+ result.label = data?.label
423
+ }
424
+ result.color = data?.color ?? props.disabledButtonColor
425
+ }
426
+ })
427
+
428
+ if (isAlreadyAddedSearch && isAlreadyAddedSearch.length > 0) {
429
+ return {
430
+ ...result,
431
+ disable: true,
432
+ btnProps: {
433
+ ...result.btnProps,
434
+ color: result.btnProps?.color ?? result?.color,
435
+ },
436
+ }
437
+ }
438
+ if (props.alreadyAddedIds?.length) {
439
+ if (props.alreadyAddedIds.includes(result.docId || result.id)) {
440
+ return {
441
+ ...result,
442
+ disable: true,
443
+ label: props.addedBtnLabel,
444
+ icon: props.addedBtnIcon,
445
+ btnProps: {
446
+ ...result.btnProps,
447
+ color: result.btnProps?.color ?? 'positive',
448
+ outline: false,
449
+ },
450
+ }
451
+ }
452
+ }
453
+ return {
454
+ ...result,
455
+ }
456
+ })
457
+
458
+ return results
459
+ }
460
+
461
+ const transformCategory = (category) => {
462
+ return props.categoryLabels[category] || categoryLabels.other
463
+ }
464
+
465
+ const updateAdvanceSearchToggle = (val) => {
466
+ toggle.value = !toggle.value
467
+ }
468
+
469
+ const updateCountry = (val, label) => {
470
+ emit('updateCountry', val, label)
471
+ }
472
+
473
+ const updateModelVal = (event, label) => {
474
+ emit('updateModelVal', event, label)
475
+ }
476
+
477
+ //Assign default model value on tab change
478
+ watch(
479
+ () => props.model,
480
+ (value) => {
481
+ advancedSearchFilterModelDefault.value = Object.assign(
482
+ {},
483
+ structuredClone(toRaw(value))
484
+ )
485
+ },
486
+ { immediate: true }
487
+ )
488
+
489
+ watch(
490
+ () => props.model,
491
+ (newValue) => {
492
+ if (deepEqual(newValue, advancedSearchFilterModelDefault.value)) {
493
+ advancedSearchFilterDirty.value = false
494
+ } else {
495
+ advancedSearchFilterDirty.value = true
496
+ }
497
+ },
498
+ { deep: true }
499
+ )
500
+
501
+ watch(
502
+ () => props.searchText,
503
+ (value) => {
504
+ if (value.length >= 2) {
505
+ if (!searchMenuShowing.value) {
506
+ props.searchMenuRef?.show()
507
+ search()
508
+ }
509
+ } else {
510
+ searchResults.value = null
511
+ }
512
+ },
513
+ { immediate: true }
514
+ )
515
+ </script>
516
+
517
+ <template>
518
+ <q-list
519
+ :class="[
520
+ `u-typeahead-menu size-${screenSize}`,
521
+ { 'q-px-xs q-pt-ba': !isMobile },
522
+ { 'q-pt-xs': isMobile },
523
+ ]"
524
+ role="menuitem"
525
+ >
526
+ <div
527
+ v-if="showAdvancedSearchTabs"
528
+ :class="[`q-pb-xs`, { 'q-px-xs': !isMobile, 'q-pl-ba': isMobile }]"
529
+ >
530
+ <div class="text-overline-xs">{{ headerLabel }}</div>
531
+
532
+ <!-- Tab options -->
533
+
534
+ <div
535
+ :class="`tabs-option ${screenSize === 'sm' ? 'q-mt-sm' : 'q-mt-xs'}`"
536
+ >
537
+ <UTabBtnStd
538
+ :buttonTabsOptions="options"
539
+ :modelValue="advancedSearchSelectedTab"
540
+ size="sm"
541
+ :standard="false"
542
+ @onTabClick="handleTabChange"
543
+ />
544
+ </div>
545
+ </div>
546
+ <div
547
+ :class="[
548
+ 'custom-menuDropdown bg-neutral-2 q-ma-xs',
549
+ { 'q-mx-ba': isMobile },
550
+ ]"
551
+ >
552
+ <!-- Menu dropdown -->
553
+ <UMenuDropdownAdvancedSearch
554
+ v-if="showAdvancedSearch"
555
+ :apply-label="applyLabel"
556
+ :clear-all-fields-label="clearAllFieldsLabel"
557
+ :disbaleApplyFilter="!advancedSearchFilterDirty"
558
+ :fields="fields"
559
+ :labelIcon="labelIcon"
560
+ :model="model"
561
+ :showCustomMenu="showCustomMenu"
562
+ :size="screenSize"
563
+ :title="title"
564
+ :toggle="toggle"
565
+ :toggle-advanced-search-aria-label="toggleAdvancedSearchAriaLabel"
566
+ @onApplyAdvancedSearchFilter="onApplyAdvancedSearchFilter"
567
+ @onClearAdvancedSearchFilter="onClearAdvancedSearchFilter"
568
+ @updateAdvanceSearchToggle="(val) => updateAdvanceSearchToggle(val)"
569
+ @updateCountry="(event, label) => updateCountry(event, label)"
570
+ @updateModelVal="(event, label) => updateModelVal(event, label)"
571
+ >
572
+ </UMenuDropdownAdvancedSearch>
573
+ </div>
574
+
575
+ <!-- Search Result list -->
576
+
577
+ <q-list
578
+ v-if="searchResults && !exceedLimit && searchText.length"
579
+ :class="[`search-list q-mt-xs`, { 'q-px-xxs q-pt-xs': isMobile }]"
580
+ >
581
+ <span class="text-overline-xs q-px-sm">
582
+ {{ props.searchResultsLabel }}
583
+ </span>
584
+ <template v-for="category of Object.keys(searchResults)" :key="category">
585
+ <q-item-label caption>
586
+ <span class="text-caption-sm q-py-xxs q-px-sm">
587
+ {{ transformCategory(category) }}
588
+ </span>
589
+ </q-item-label>
590
+ <q-item
591
+ v-for="item of searchResults[category]"
592
+ class="list-item q-py-xs q-px-sm"
593
+ clickable
594
+ :key="item.id"
595
+ >
596
+ <q-item-section side>
597
+ <UAvatar
598
+ v-if="
599
+ !item.profilePictureUrl ||
600
+ item.profilePictureUrl?.nullValue === 0
601
+ "
602
+ :aria-label="item.name"
603
+ :name="parseInitials(item.name)"
604
+ size="md"
605
+ />
606
+ <UAvatar
607
+ v-else
608
+ :aria-label="item.name"
609
+ :image="item.profilePictureUrl"
610
+ :name="item.name"
611
+ :round="true"
612
+ :showIndicator="false"
613
+ size="md"
614
+ />
615
+ </q-item-section>
616
+
617
+ <q-item-section>
618
+ <q-item-label class="text-caption-md wrapped-text">
619
+ {{ item?.name }}
620
+ </q-item-label>
621
+ <q-item-label class="text-body-xs text-neutral-9 wrapped-text">
622
+ {{ item?.description }}
623
+ </q-item-label>
624
+
625
+ <UTooltip
626
+ v-if="showDescriptionTooltip"
627
+ class="description-tooltip"
628
+ anchor="top middle"
629
+ :description="item.description"
630
+ self="bottom middle"
631
+ />
632
+ </q-item-section>
633
+
634
+ <q-item-section side>
635
+ <UBtnStd
636
+ v-bind="item.btnProps"
637
+ :color="item.btnProps?.color ?? 'primary'"
638
+ :disable="
639
+ item.disable ||
640
+ (selectedItem?.id !== item.id && actionButtonLoading)
641
+ "
642
+ :label="item.label ? item.label : actionButtonLabel"
643
+ :leftIcon="item.icon"
644
+ :loading="
645
+ selectedItem?.id === item.id ? actionButtonLoading : false
646
+ "
647
+ :outline="item.btnProps?.outline ?? true"
648
+ size="sm"
649
+ @click.stop="onItemActionClick(item)"
650
+ >
651
+ <template
652
+ v-if="
653
+ (item.disableTooltip && item.disable) ||
654
+ (item.disable && toolTipDisabledButton)
655
+ "
656
+ #tooltip
657
+ >
658
+ <UTooltip
659
+ anchor="top middle"
660
+ :description="
661
+ item.disableTooltip
662
+ ? item.disableTooltip
663
+ : toolTipDisabledButton
664
+ "
665
+ :offset="[0, 4]"
666
+ self="bottom middle"
667
+ />
668
+ </template>
669
+ </UBtnStd>
670
+ </q-item-section>
671
+ </q-item>
672
+ </template>
673
+ </q-list>
674
+
675
+ <!-- No data section -->
676
+
677
+ <div
678
+ v-if="searchText.length && !searchResults && !exceedLimit && !loading"
679
+ :class="[{ 'q-px-xs': !isMobile }, { 'q-px-xxs': isMobile }]"
680
+ >
681
+ <div class="items-center column q-py-ba q-mt-xs">
682
+ <img
683
+ :alt="props.noDataCaption"
684
+ :aria-label="props.noDataCaption"
685
+ src="../../assets/no-result.svg"
686
+ />
687
+ <span class="text-caption-lg q-mt-xs">{{ props.noDataCaption }}</span>
688
+ <span class="text-body-sm text-neutral-9 q-mt-xxs q-mb-ba">
689
+ {{ props.noDataBody }}
690
+ </span>
691
+ </div>
692
+
693
+ <div
694
+ v-if="showInviteBtn"
695
+ :class="[
696
+ 'row items-center full-width',
697
+ { 'q-px-ba q-mt-xs': isMobile },
698
+ ]"
699
+ >
700
+ <span
701
+ :class="`searchText-${screenSize} text-caption-md q-mr-xs col wrapped-text`"
702
+ >
703
+ {{ searchText }}
704
+ </span>
705
+ <UBtnStd
706
+ color="primary"
707
+ data-test-id="invite-btn-action"
708
+ :label="props.inviteLabel"
709
+ outline
710
+ size="sm"
711
+ @click.stop="onInvitePerson(item, false)"
712
+ />
713
+ </div>
714
+ </div>
715
+
716
+ <!-- Exceed limit section -->
717
+
718
+ <div v-if="searchText.length && exceedLimit && searchResults">
719
+ <div class="column q-my-ba items-center">
720
+ <img
721
+ :alt="props.noDataCaption"
722
+ :aria-label="props.noDataCaption"
723
+ src="../../assets/no-result.svg"
724
+ />
725
+ <span class="text-caption-lg q-mt-xs">{{
726
+ props.exceedLimitCaption
727
+ }}</span>
728
+ <span class="text-body-sm text-neutral-9 q-mt-xxs text-center">
729
+ {{ props.exceedLimitBody }}
730
+ </span>
731
+ </div>
732
+ </div>
733
+
734
+ <!-- Recently selected list -->
735
+
736
+ <q-list :class="[`search-list q-mt-xs`, { 'q-px-xxs q-pt-xs': isMobile }]">
737
+ <div
738
+ v-if="showRecentSelected && recentSearchesLoading"
739
+ :class="[`row justify-between items-center`, { 'q-px-sm': isMobile }]"
740
+ >
741
+ <span class="text-overline-xs q-px-sm">
742
+ {{ props.recentlySelected }}
743
+ </span>
744
+
745
+ <q-spinner v-if="recentSearchesLoading" color="grey-14" size="1rem" />
746
+ </div>
747
+ <div
748
+ v-if="
749
+ showRecentSelected &&
750
+ recentSearches &&
751
+ recentSearches.length &&
752
+ !recentSearchesLoading
753
+ "
754
+ >
755
+ <span class="text-overline-xs q-px-sm">
756
+ {{ props.recentlySelected }}
757
+ </span>
758
+
759
+ <template v-for="item in recentSearches" :key="item.id">
760
+ <q-item class="list-item q-py-xs q-px-sm" clickable>
761
+ <q-item-section side>
762
+ <UAvatar
763
+ v-if="
764
+ !item.profilePictureUrl ||
765
+ item.profilePictureUrl?.nullValue === 0
766
+ "
767
+ :aria-label="item.name"
768
+ :name="parseInitials(item.name)"
769
+ size="md"
770
+ />
771
+ <UAvatar
772
+ v-else
773
+ :aria-label="item.name"
774
+ :image="item.profilePictureUrl"
775
+ :round="true"
776
+ :showIndicator="false"
777
+ size="md"
778
+ />
779
+ </q-item-section>
780
+
781
+ <q-item-section>
782
+ <q-item-label class="text-caption-md wrapped-text">
783
+ {{ item?.name }}
784
+ </q-item-label>
785
+ <q-item-label
786
+ class="text-body-xs text-neutral-9 label wrapped-text"
787
+ >
788
+ {{ item?.description }}
789
+ </q-item-label>
790
+ <UTooltip
791
+ v-if="showDescriptionTooltip"
792
+ class="description-tooltip"
793
+ anchor="top middle"
794
+ :description="item.description"
795
+ self="bottom middle"
796
+ />
797
+ </q-item-section>
798
+
799
+ <q-item-section side>
800
+ <UBtnStd
801
+ v-bind="item.btnProps"
802
+ :color="item.btnProps?.color ?? 'primary'"
803
+ :disable="
804
+ item.disable ||
805
+ (selectedItem?.id !== item.id && actionButtonLoading)
806
+ "
807
+ :label="item.label ? item.label : actionButtonLabel"
808
+ :leftIcon="item.icon"
809
+ :loading="
810
+ selectedItem?.id === item.id ? actionButtonLoading : false
811
+ "
812
+ :outline="item.btnProps?.outline ?? true"
813
+ size="sm"
814
+ @click.stop="onItemActionClick(item, true)"
815
+ >
816
+ <template
817
+ v-if="
818
+ (item.disableTooltip && item.disable) ||
819
+ (item.disable && toolTipDisabledButton)
820
+ "
821
+ #tooltip
822
+ >
823
+ <UTooltip
824
+ anchor="top middle"
825
+ :description="
826
+ item.disableTooltip
827
+ ? item.disableTooltip
828
+ : toolTipDisabledButton
829
+ "
830
+ :offset="[0, 4]"
831
+ self="bottom middle"
832
+ />
833
+ </template>
834
+ </UBtnStd>
835
+ </q-item-section>
836
+ </q-item>
837
+ </template>
838
+ </div>
839
+ </q-list>
840
+ </q-list>
841
+ </template>
842
+
843
+ <style lang="sass">
844
+ .u-typeahead-menu
845
+ .u-tabs-outer .u-tab-button
846
+ gap: 0px
847
+ .u-tabs-outer .u-tab-button .truncated-label
848
+ overflow-wrap: unset
849
+ .u-tabs-outer .u-tab-sm
850
+ margin: 0 $xxs
851
+ padding: 0 $ba
852
+ .u-tabs-outer .u-tab-sm:first-child
853
+ margin-left: 0px
854
+ .u-tabs-outer .u-tab-sm:last-child
855
+ margin-right: 0px
856
+ .u-tabs-outer .q-tab__content .q-icon
857
+ font-size: 0.75rem
858
+ .tabs-option
859
+ display: flex
860
+ overflow-y: auto
861
+ scrollbar-width: none
862
+ .custom-menuDropdown
863
+ border-radius: $xs
864
+ flex-direction: column
865
+ align-items: flex-start
866
+ border-radius: $xs
867
+ background: $neutral-1
868
+ .search-list
869
+ display: flex
870
+ flex-direction: column
871
+ gap: $xxs
872
+ .list-item
873
+ border-radius: $xs
874
+ align-items: center
875
+ display: flex
876
+ cursor: default !important
877
+ gap: $xs
878
+ .q-item
879
+ margin-bottom: 0px !important
880
+ .q-item__section--avatar
881
+ min-width: 0px
882
+ .q-item__section--side
883
+ padding: 0px
884
+ .wrapped-text
885
+ white-space: normal
886
+ overflow: hidden
887
+ word-wrap: break-word
888
+ .description-tooltip
889
+ transform: translateX(20px)
890
+ </style>