@usssa/component-library 1.0.0-alpha.147 → 1.0.0-alpha.149

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