sprintify-ui 0.0.97 → 0.0.99

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 (26) hide show
  1. package/dist/sprintify-ui.es.js +8010 -7924
  2. package/dist/types/src/components/BaseAutocomplete.vue.d.ts +32 -10
  3. package/dist/types/src/components/BaseAutocompleteDropdown.vue.d.ts +88 -0
  4. package/dist/types/src/components/BaseAutocompleteFetch.vue.d.ts +20 -5
  5. package/dist/types/src/components/BaseBelongsTo.vue.d.ts +18 -3
  6. package/dist/types/src/components/BaseDatePicker.vue.d.ts +1 -1
  7. package/dist/types/src/components/BaseHasMany.vue.d.ts +17 -0
  8. package/dist/types/src/components/BaseLoadingCover.vue.d.ts +1 -1
  9. package/dist/types/src/components/BaseSwitch.vue.d.ts +1 -1
  10. package/dist/types/src/components/BaseTagAutocomplete.vue.d.ts +66 -6
  11. package/dist/types/src/components/BaseTagAutocompleteFetch.vue.d.ts +19 -2
  12. package/dist/types/src/composables/clickOutside.d.ts +1 -1
  13. package/dist/types/src/index.d.ts +2 -0
  14. package/package.json +10 -10
  15. package/src/components/BaseAutocomplete.vue +53 -183
  16. package/src/components/BaseAutocompleteDropdown.vue +344 -0
  17. package/src/components/BaseAutocompleteFetch.vue +8 -3
  18. package/src/components/BaseDropdown.vue +2 -2
  19. package/src/components/BaseModalSide.stories.js +1 -3
  20. package/src/components/BaseModalSide.vue +5 -5
  21. package/src/components/BaseTagAutocomplete.stories.js +46 -1
  22. package/src/components/BaseTagAutocomplete.vue +184 -253
  23. package/src/components/BaseTagAutocompleteFetch.stories.js +4 -4
  24. package/src/components/BaseTagAutocompleteFetch.vue +10 -5
  25. package/src/composables/clickOutside.ts +4 -2
  26. package/src/index.ts +3 -1
@@ -61,92 +61,34 @@
61
61
  <div class="relative">
62
62
  <div
63
63
  v-show="opened || dropdownShow == 'always'"
64
- class="min-h-[110px] w-full overflow-hidden"
64
+ class="w-full overflow-hidden"
65
65
  :class="[
66
66
  inline
67
- ? 'relative'
68
- : 'absolute top-1 z-menu rounded border border-slate-300 bg-white shadow-md',
67
+ ? 'relative mt-1'
68
+ : 'absolute top-1 z-menu min-h-[110px] w-full overflow-hidden rounded border border-slate-300 bg-white shadow-md',
69
69
  ]"
70
70
  >
71
- <div
72
- ref="dropdown"
73
- data-scroll-lock-scrollable
74
- class="max-h-[214px] min-h-[75px] w-full overflow-y-auto"
71
+ <BaseAutocompleteDropdown
72
+ :selected="normalizedModelValue"
73
+ :options="filteredNormalizedOptions"
74
+ :size="size"
75
+ :loading="loading"
76
+ :loading-bottom="loadingBottom"
77
+ :dropdown-class="inline ? '' : 'p-1'"
78
+ :keywords="keywords"
79
+ @select="onSelect"
80
+ @scroll-bottom="emit('scrollBottom')"
75
81
  >
76
- <slot v-if="filteredNormalizedOptions.length == 0" name="empty">
77
- <div
78
- class="flex items-center justify-center px-5 py-10 text-center text-slate-600"
79
- >
80
- {{ $t('sui.nothing_found') }}
81
- </div>
82
- </slot>
83
-
84
- <ul v-else :class="[inline ? 'p-0 pt-1' : 'p-1']">
85
- <li
86
- v-for="(option, index) in filteredNormalizedOptions"
87
- :key="option.value"
88
- class="block"
89
- >
90
- <button
91
- class="block w-full cursor-pointer appearance-none border-none text-left focus:outline-none"
92
- type="button"
93
- tabindex="-1"
94
- @click="onSelect(option)"
95
- @mouseenter="selectionIndex = index"
96
- >
97
- <slot
98
- name="option"
99
- :option="option.option"
100
- :selected="isSelected(option)"
101
- :active="optionActive && optionActive.value == option.value"
102
- >
103
- <div
104
- class="flex items-center rounded px-2 py-1 text-sm"
105
- :class="[optionClass(option), optionSizeClass]"
106
- >
107
- <div class="grow">
108
- {{ option.label }}
109
- </div>
110
- <div class="shrink-0">
111
- <BaseIcon
112
- v-if="isSelected(option)"
113
- icon="heroicons:check-20-solid"
114
- :class="iconClass"
115
- ></BaseIcon>
116
- </div>
117
- </div>
118
- </slot>
119
- </button>
120
- </li>
121
- </ul>
122
- </div>
123
-
124
- <div>
125
- <div v-if="$slots.footer" class="bg-white">
126
- <slot
127
- :options="filteredNormalizedOptions"
128
- :keywords="keywords"
129
- name="footer"
130
- />
131
- </div>
132
- </div>
133
-
134
- <div
135
- v-if="loading"
136
- class="absolute inset-0 h-full w-full space-y-1 bg-white p-2"
137
- >
138
- <div class="space-y-1">
139
- <BaseSkeleton class="h-7 w-full" delay="0ms"></BaseSkeleton>
140
- <BaseSkeleton
141
- class="h-7 w-full opacity-70"
142
- delay="50ms"
143
- ></BaseSkeleton>
144
- <BaseSkeleton
145
- class="h-7 w-full opacity-30"
146
- delay="100ms"
147
- ></BaseSkeleton>
148
- </div>
149
- </div>
82
+ <template #empty="emptyProps">
83
+ <slot name="empty" v-bind="{ ...emptyProps, ...slotProps }" />
84
+ </template>
85
+ <template #option="optionProps">
86
+ <slot name="option" v-bind="{ ...optionProps, ...slotProps }" />
87
+ </template>
88
+ <template #footer="footerProps">
89
+ <slot name="footer" v-bind="{ ...footerProps, ...slotProps }" />
90
+ </template>
91
+ </BaseAutocompleteDropdown>
150
92
  </div>
151
93
  </div>
152
94
  </div>
@@ -156,12 +98,11 @@
156
98
  import { get } from 'lodash';
157
99
  import { PropType, Ref, ComputedRef } from 'vue';
158
100
  import { NormalizedOption, Option } from '@/types';
159
- import { useInfiniteScroll } from '@vueuse/core';
160
- import BaseSkeleton from '@/components/BaseSkeleton.vue';
161
101
  import { useHasOptions } from '@/composables/hasOptions';
162
102
  import { useField } from '@/composables/field';
163
103
  import { BaseIcon } from './index';
164
104
  import { useClickOutside } from '@/composables/clickOutside';
105
+ import BaseAutocompleteDropdown from './BaseAutocompleteDropdown.vue';
165
106
 
166
107
  const props = defineProps({
167
108
  modelValue: {
@@ -192,6 +133,10 @@ const props = defineProps({
192
133
  default: false,
193
134
  type: Boolean,
194
135
  },
136
+ loadingBottom: {
137
+ default: false,
138
+ type: Boolean,
139
+ },
195
140
  required: {
196
141
  default: false,
197
142
  type: Boolean,
@@ -233,12 +178,10 @@ const props = defineProps({
233
178
  const emit = defineEmits([
234
179
  'update:modelValue',
235
180
  'typing',
236
- 'blur',
237
- 'focus',
238
- 'scrollBottom',
239
181
  'clear',
240
182
  'open',
241
183
  'close',
184
+ 'scrollBottom',
242
185
  ]);
243
186
 
244
187
  const { hasErrorInternal, emitUpdate } = useField({
@@ -248,15 +191,6 @@ const { hasErrorInternal, emitUpdate } = useField({
248
191
  emit: emit,
249
192
  });
250
193
 
251
- let timerId = 0;
252
- const keywords = ref('');
253
- const selectionIndex = ref(0);
254
- const autocomplete = ref(null) as Ref<HTMLElement | null>;
255
- const inputElement = ref(null) as Ref<HTMLInputElement | null>;
256
- const dropdown = ref(null) as Ref<HTMLElement | null>;
257
- const shouldFilter = ref(false);
258
- const opened = ref(false);
259
-
260
194
  const hasOptions = useHasOptions(
261
195
  computed(() => props.modelValue),
262
196
  computed(() => props.options),
@@ -265,7 +199,13 @@ const hasOptions = useHasOptions(
265
199
  computed(() => false)
266
200
  );
267
201
 
268
- const isSelected = hasOptions.isSelected;
202
+ let timerId = 0;
203
+ const keywords = ref('');
204
+ const autocomplete = ref(null) as Ref<HTMLElement | null>;
205
+ const inputElement = ref(null) as Ref<HTMLInputElement | null>;
206
+ const shouldFilter = ref(false);
207
+ const opened = ref(false);
208
+
269
209
  const normalizedOptions = hasOptions.normalizedOptions;
270
210
  const normalizedModelValue =
271
211
  hasOptions.normalizedModelValue as ComputedRef<NormalizedOption | null>;
@@ -285,30 +225,15 @@ const filteredNormalizedOptions = computed((): NormalizedOption[] => {
285
225
  });
286
226
  });
287
227
 
288
- const optionActive = computed(() => {
289
- return (
290
- filteredNormalizedOptions.value[
291
- Math.min(selectionIndex.value, filteredNormalizedOptions.value.length - 1)
292
- ] ?? null
293
- );
294
- });
295
-
296
- // Update the keywords + selectionIndex when the model value changes
228
+ // Update the keywords when the model value changes
297
229
  watch(
298
230
  () => normalizedModelValue.value,
299
231
  () => {
300
232
  if (normalizedModelValue.value) {
301
- const index = filteredNormalizedOptions.value.findIndex(
302
- (option) => option.value === normalizedModelValue.value?.value
303
- );
304
- if (index >= 0) {
305
- selectionIndex.value = index;
306
- }
307
233
  if (props.modelValueShow) {
308
234
  setKeywords(normalizedModelValue.value?.label);
309
235
  }
310
236
  } else {
311
- selectionIndex.value = 0;
312
237
  if (props.modelValueShow) {
313
238
  setKeywords('');
314
239
  }
@@ -317,10 +242,8 @@ watch(
317
242
  { immediate: true }
318
243
  );
319
244
 
320
- useClickOutside(autocomplete, (outside: boolean) => {
321
- if (outside) {
322
- close();
323
- }
245
+ useClickOutside(autocomplete, () => {
246
+ close();
324
247
  });
325
248
 
326
249
  function open() {
@@ -335,6 +258,7 @@ function open() {
335
258
  }
336
259
 
337
260
  function close() {
261
+ shouldFilter.value = false;
338
262
  opened.value = false;
339
263
  blur();
340
264
  timerId = setTimeout(() => {
@@ -349,13 +273,10 @@ function close() {
349
273
  const onTextInput = (event: Event) => {
350
274
  open();
351
275
  shouldFilter.value = true;
352
- selectionIndex.value = 0;
353
276
  setKeywords(get(event, 'target.value') + '');
354
277
  emit('typing', keywords.value);
355
- dropdown.value?.scrollTo({
356
- top: 0,
357
- });
358
278
 
279
+ // If keywords is empty, emit null
359
280
  if (keywords.value == '') {
360
281
  update(null);
361
282
  }
@@ -364,38 +285,15 @@ const onTextInput = (event: Event) => {
364
285
  const onTextKeydown = (event: KeyboardEvent) => {
365
286
  const key = event.key;
366
287
 
367
- if (props.loading) {
368
- return;
369
- }
370
-
371
- if (key === 'ArrowDown') {
372
- event.preventDefault();
373
- if (selectionIndex.value < filteredNormalizedOptions.value.length - 1) {
374
- selectionIndex.value++;
375
- } else {
376
- selectionIndex.value = 0;
377
- }
378
- return;
379
- }
288
+ // Prevent default behavior for up/down arrows
380
289
 
381
290
  if (key === 'ArrowUp') {
382
291
  event.preventDefault();
383
- if (selectionIndex.value > 0) {
384
- selectionIndex.value--;
385
- } else {
386
- selectionIndex.value = Math.max(
387
- 0,
388
- filteredNormalizedOptions.value.length - 1
389
- );
390
- }
391
292
  return;
392
293
  }
393
294
 
394
- if (key === 'Enter') {
295
+ if (key === 'ArrowDown') {
395
296
  event.preventDefault();
396
- if (optionActive.value) {
397
- onSelect(optionActive.value);
398
- }
399
297
  return;
400
298
  }
401
299
  };
@@ -414,14 +312,10 @@ function onSelect(normalizedModelValue: Option | null | undefined) {
414
312
  }
415
313
 
416
314
  function update(normalizedSelection: Option | null | undefined) {
417
- const selection = normalizedSelection ? normalizedSelection.option : null;
418
- if (selection) {
419
- const index = filteredNormalizedOptions.value.findIndex(
420
- (option) => option.value == selection.value
421
- );
422
- selectionIndex.value = index;
423
- }
315
+ // Re-activate filter
424
316
  shouldFilter.value = false;
317
+ // Emit update
318
+ const selection = normalizedSelection ? normalizedSelection.option : null;
425
319
  emitUpdate(selection);
426
320
  }
427
321
 
@@ -437,27 +331,15 @@ function blur() {
437
331
  inputElement.value?.blur();
438
332
  }
439
333
 
440
- // Element Classes
441
-
442
- const optionClass = (option: NormalizedOption) => {
443
- const active = optionActive.value && optionActive.value.value == option.value;
444
-
445
- if (active) {
446
- return 'bg-slate-200';
447
- }
448
-
449
- return 'bg-white';
334
+ const slotProps = {
335
+ focus,
336
+ blur,
337
+ open,
338
+ close,
339
+ keywords: computed(() => keywords.value),
450
340
  };
451
341
 
452
- const optionSizeClass = computed(() => {
453
- if (props.size == 'xs') {
454
- return 'text-xs';
455
- }
456
- if (props.size == 'sm') {
457
- return 'text-sm';
458
- }
459
- return 'text-sm';
460
- });
342
+ // Element Classes
461
343
 
462
344
  const inputClass = computed(() => {
463
345
  if (props.size == 'xs') {
@@ -489,18 +371,6 @@ const iconWrapClass = computed(() => {
489
371
  return 'pl-2.5';
490
372
  });
491
373
 
492
- // Infinite scroll
493
-
494
- onMounted(() => {
495
- useInfiniteScroll(
496
- dropdown.value,
497
- () => {
498
- emit('scrollBottom');
499
- },
500
- { distance: 60 }
501
- );
502
- });
503
-
504
374
  defineExpose({
505
375
  focus,
506
376
  blur,
@@ -0,0 +1,344 @@
1
+ <template>
2
+ <div class="relative w-full overflow-hidden">
3
+ <div
4
+ ref="dropdown"
5
+ data-scroll-lock-scrollable
6
+ class="max-h-[214px] w-full overflow-y-auto"
7
+ >
8
+ <!-- Slot empty -->
9
+ <slot v-if="options.length == 0" name="empty">
10
+ <div
11
+ class="flex items-center justify-center px-5 py-10 text-center text-slate-600"
12
+ >
13
+ {{ $t('sui.nothing_found') }}
14
+ </div>
15
+ </slot>
16
+
17
+ <!-- Option list -->
18
+ <ul v-else :class="dropdownClass">
19
+ <li
20
+ v-for="(option, index) in options"
21
+ :key="option.value"
22
+ ref="optionRefs"
23
+ class="block"
24
+ >
25
+ <button
26
+ class="block w-full cursor-pointer select-none appearance-none border-none text-left focus:outline-none"
27
+ type="button"
28
+ tabindex="-1"
29
+ @click="onSelect(option)"
30
+ @mouseenter="onOptionMouseEnter(index)"
31
+ @mousemove="onOptionMouseMove(index)"
32
+ >
33
+ <slot
34
+ name="option"
35
+ :option="option.option"
36
+ :selected="isSelected(option)"
37
+ :active="focusOption && focusOption.value == option.value"
38
+ >
39
+ <div
40
+ class="flex items-center rounded px-[0.75em] py-[0.5em]"
41
+ :class="[optionClass(option), optionSizeClass]"
42
+ >
43
+ <div class="grow">
44
+ {{ option.label }}
45
+ </div>
46
+ <div class="shrink-0">
47
+ <BaseIcon
48
+ v-if="isSelected(option)"
49
+ icon="heroicons:check-20-solid"
50
+ :class="optionIconClass"
51
+ ></BaseIcon>
52
+ </div>
53
+ </div>
54
+ </slot>
55
+ </button>
56
+ </li>
57
+ </ul>
58
+
59
+ <!-- Loading bottom -->
60
+ <!-- This component should always take the same amount of space whether it's visible or not -->
61
+ <!-- This is to prevent infinite scroll jumps -->
62
+ <div
63
+ :class="[loadingBottom ? 'opacity-100' : 'opacity-0']"
64
+ class="relative py-3 text-center"
65
+ >
66
+ <div
67
+ class="absolute inset-0 h-full w-full bg-gradient-to-b from-transparent to-white"
68
+ ></div>
69
+ <BaseSpinnerSmall
70
+ class="mx-auto h-7 w-7 text-slate-300"
71
+ ></BaseSpinnerSmall>
72
+ </div>
73
+ </div>
74
+
75
+ <div ref="footer">
76
+ <div v-if="$slots.footer" class="bg-white">
77
+ <slot :options="options" name="footer" />
78
+ </div>
79
+ </div>
80
+
81
+ <div
82
+ v-if="loading"
83
+ class="absolute inset-0 h-full w-full space-y-1 bg-white p-2"
84
+ >
85
+ <div class="space-y-1">
86
+ <BaseSkeleton class="h-7 w-full" delay="0ms"></BaseSkeleton>
87
+ <BaseSkeleton class="h-7 w-full opacity-70" delay="50ms"></BaseSkeleton>
88
+ <BaseSkeleton
89
+ class="h-7 w-full opacity-30"
90
+ delay="100ms"
91
+ ></BaseSkeleton>
92
+ </div>
93
+ </div>
94
+ </div>
95
+ </template>
96
+
97
+ <script lang="ts" setup>
98
+ import { Ref, PropType } from 'vue';
99
+ import { useInfiniteScroll } from '@vueuse/core';
100
+ import { NormalizedOption } from '@/types';
101
+ import { isArray, isObject, throttle } from 'lodash';
102
+ import BaseSkeleton from './BaseSkeleton.vue';
103
+ import { BaseIcon } from '.';
104
+ import BaseSpinnerSmall from '../svg/BaseSpinnerSmall.vue';
105
+
106
+ const props = defineProps({
107
+ selected: {
108
+ type: [Array, Object, null, undefined] as PropType<
109
+ NormalizedOption[] | NormalizedOption | null | undefined
110
+ >,
111
+ default: undefined,
112
+ },
113
+ options: {
114
+ type: Array as PropType<NormalizedOption[]>,
115
+ default() {
116
+ return [];
117
+ },
118
+ },
119
+ keywords: {
120
+ type: String,
121
+ default: '',
122
+ },
123
+ loading: {
124
+ type: Boolean,
125
+ default: false,
126
+ },
127
+ loadingBottom: {
128
+ type: Boolean,
129
+ default: false,
130
+ },
131
+ size: {
132
+ type: String as PropType<'xs' | 'sm' | 'base'>,
133
+ default: 'base',
134
+ },
135
+ dropdownClass: {
136
+ type: String,
137
+ default: '',
138
+ },
139
+ });
140
+
141
+ const emit = defineEmits(['scrollBottom', 'select']);
142
+
143
+ const dropdown = ref(null) as Ref<HTMLDivElement | null>;
144
+ const optionRefs = ref([]) as Ref<HTMLElement[]>;
145
+
146
+ let mouseIsMoving = false;
147
+
148
+ // Focus
149
+
150
+ const focusIndex = ref(0);
151
+
152
+ const focusOption = computed(() => {
153
+ return (
154
+ props.options[Math.min(focusIndex.value, props.options.length - 1)] ?? null
155
+ );
156
+ });
157
+
158
+ // Selecting
159
+
160
+ function onSelect(option: NormalizedOption) {
161
+ emit('select', option);
162
+ }
163
+
164
+ function isSelected(option: NormalizedOption): boolean {
165
+ if (isArray(props.selected)) {
166
+ return props.selected.some((modelValue) => {
167
+ return modelValue.value === option.value;
168
+ });
169
+ }
170
+
171
+ if (isObject(props.selected)) {
172
+ return props.selected.value == option.value;
173
+ }
174
+
175
+ return false;
176
+ }
177
+
178
+ // Arrow actions
179
+
180
+ window.addEventListener('keydown', onKeyDown);
181
+
182
+ onBeforeUnmount(() => {
183
+ window.removeEventListener('keydown', onKeyDown);
184
+ });
185
+
186
+ function onKeyDown(event: KeyboardEvent) {
187
+ const key = event.key;
188
+
189
+ mouseIsMoving = false;
190
+
191
+ if (props.loading) {
192
+ return;
193
+ }
194
+
195
+ if (key === 'ArrowDown') {
196
+ if (focusIndex.value < props.options.length - 1) {
197
+ focusIndex.value++;
198
+ } else {
199
+ focusIndex.value = 0;
200
+ }
201
+ scrollToFocus();
202
+ return;
203
+ }
204
+
205
+ if (key === 'ArrowUp') {
206
+ if (focusIndex.value > 0) {
207
+ focusIndex.value--;
208
+ } else {
209
+ focusIndex.value = Math.max(0, props.options.length - 1);
210
+ }
211
+ scrollToFocus();
212
+ return;
213
+ }
214
+
215
+ if (key === 'Enter') {
216
+ event.preventDefault();
217
+ if (focusOption.value) {
218
+ onSelect(focusOption.value);
219
+ }
220
+ return;
221
+ }
222
+ }
223
+
224
+ // Infinite scroll
225
+
226
+ onMounted(() => {
227
+ useInfiniteScroll(
228
+ dropdown.value,
229
+ () => {
230
+ emit('scrollBottom');
231
+ },
232
+ { distance: 60 }
233
+ );
234
+ });
235
+
236
+ function scrollToFocus() {
237
+ if (!optionRefs.value) {
238
+ return;
239
+ }
240
+
241
+ const option = optionRefs.value[focusIndex.value] ?? null;
242
+
243
+ if (!option) {
244
+ return;
245
+ }
246
+
247
+ option.scrollIntoView({
248
+ block: 'center',
249
+ inline: 'center',
250
+ });
251
+ }
252
+
253
+ // Validate focus index
254
+
255
+ watch(
256
+ () => props.options,
257
+ () => {
258
+ focusIndex.value = Math.max(
259
+ 0,
260
+ Math.min(focusIndex.value, props.options.length - 1)
261
+ );
262
+ },
263
+ { immediate: true }
264
+ );
265
+
266
+ // Update the focusIndex when the selected value changes
267
+
268
+ watch(
269
+ () => props.selected,
270
+ () => {
271
+ if (
272
+ props.selected &&
273
+ isObject(props.selected) &&
274
+ !isArray(props.selected)
275
+ ) {
276
+ const selected = props.selected as NormalizedOption;
277
+ const index = props.options.findIndex(
278
+ (option) => option.value === selected.value
279
+ );
280
+ if (index >= 0) {
281
+ focusIndex.value = index;
282
+ }
283
+ } else {
284
+ focusIndex.value = 0;
285
+ }
286
+ },
287
+ { immediate: true }
288
+ );
289
+
290
+ // Scroll top when the keywords change
291
+
292
+ watch(
293
+ () => props.keywords,
294
+ () => {
295
+ if (dropdown.value) {
296
+ dropdown.value.scrollTop = 0;
297
+ }
298
+ // Reset the focusIndex
299
+ focusIndex.value = 0;
300
+ }
301
+ );
302
+
303
+ const onOptionMouseEnter = (index: number) => {
304
+ if (mouseIsMoving) {
305
+ focusIndex.value = index;
306
+ }
307
+ };
308
+
309
+ const onOptionMouseMove = throttle((index: number) => {
310
+ mouseIsMoving = true;
311
+ }, 10);
312
+
313
+ // Classes
314
+
315
+ const optionClass = (option: NormalizedOption) => {
316
+ const focus = focusOption.value && focusOption.value.value == option.value;
317
+
318
+ if (focus) {
319
+ return 'bg-slate-200';
320
+ }
321
+
322
+ return 'bg-white';
323
+ };
324
+
325
+ const optionSizeClass = computed(() => {
326
+ if (props.size == 'xs') {
327
+ return 'text-xs';
328
+ }
329
+ if (props.size == 'sm') {
330
+ return 'text-sm';
331
+ }
332
+ return 'text-sm';
333
+ });
334
+
335
+ const optionIconClass = computed(() => {
336
+ if (props.size == 'xs') {
337
+ return 'w-4 h-4';
338
+ }
339
+ if (props.size == 'sm') {
340
+ return 'w-5 h-5';
341
+ }
342
+ return 'w-5 h-5';
343
+ });
344
+ </script>