@usssa/component-library 1.0.0-alpha.15 → 1.0.0-alpha.151

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