sprintify-ui 0.6.32 → 0.6.34

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 (93) hide show
  1. package/dist/sprintify-ui.es.js +13449 -13183
  2. package/dist/style.css +1 -1
  3. package/dist/tailwindcss/button.js +261 -0
  4. package/dist/tailwindcss/index.js +16 -301
  5. package/dist/tailwindcss/input.js +24 -0
  6. package/dist/tailwindcss/overlay.js +13 -0
  7. package/dist/tailwindcss/theme.js +46 -0
  8. package/dist/types/src/components/BaseActionItemButton.vue.d.ts +2 -2
  9. package/dist/types/src/components/BaseAddressForm.vue.d.ts +10 -0
  10. package/dist/types/src/components/BaseAutocomplete.vue.d.ts +10 -10
  11. package/dist/types/src/components/BaseAutocompleteDrawer.vue.d.ts +3 -3
  12. package/dist/types/src/components/BaseAutocompleteFetch.vue.d.ts +6 -6
  13. package/dist/types/src/components/BaseAvatarGroup.vue.d.ts +1 -1
  14. package/dist/types/src/components/BaseBelongsTo.vue.d.ts +6 -6
  15. package/dist/types/src/components/BaseBelongsToFetch.vue.d.ts +6 -6
  16. package/dist/types/src/components/BaseButton.vue.d.ts +12 -12
  17. package/dist/types/src/components/BaseButtonGroup.vue.d.ts +24 -24
  18. package/dist/types/src/components/BaseCalendar.vue.d.ts +1 -1
  19. package/dist/types/src/components/BaseColor.vue.d.ts +19 -0
  20. package/dist/types/src/components/BaseDataIterator.vue.d.ts +1 -1
  21. package/dist/types/src/components/BaseDataIteratorSectionColumns.vue.d.ts +4 -4
  22. package/dist/types/src/components/BaseDataTable.vue.d.ts +1 -1
  23. package/dist/types/src/components/BaseDropdownAutocomplete.vue.d.ts +1 -1
  24. package/dist/types/src/components/BaseField.vue.d.ts +13 -3
  25. package/dist/types/src/components/BaseInput.vue.d.ts +53 -4
  26. package/dist/types/src/components/BaseInputError.vue.d.ts +14 -1
  27. package/dist/types/src/components/BaseInputLabel.vue.d.ts +15 -5
  28. package/dist/types/src/components/BaseLayoutSidebarConfigurable.vue.d.ts +1 -1
  29. package/dist/types/src/components/BaseLayoutStackedConfigurable.vue.d.ts +1 -1
  30. package/dist/types/src/components/BaseLoadingCover.vue.d.ts +1 -1
  31. package/dist/types/src/components/BaseMediaGallery.vue.d.ts +1 -1
  32. package/dist/types/src/components/BaseMediaListItem.vue.d.ts +1 -1
  33. package/dist/types/src/components/BaseMediaPictures.vue.d.ts +1 -1
  34. package/dist/types/src/components/BaseMediaPicturesItem.vue.d.ts +1 -1
  35. package/dist/types/src/components/BaseMenu.vue.d.ts +1 -1
  36. package/dist/types/src/components/BaseMenuItem.vue.d.ts +1 -1
  37. package/dist/types/src/components/BaseNavbar.vue.d.ts +1 -1
  38. package/dist/types/src/components/BaseNavbarItemContent.vue.d.ts +1 -1
  39. package/dist/types/src/components/BaseNavbarSideItemContent.vue.d.ts +1 -1
  40. package/dist/types/src/components/BasePassword.vue.d.ts +13 -0
  41. package/dist/types/src/components/BaseSelect.vue.d.ts +27 -0
  42. package/dist/types/src/components/BaseSideNavigation.vue.d.ts +3 -3
  43. package/dist/types/src/components/BaseSwitch.vue.d.ts +1 -1
  44. package/dist/types/src/components/BaseTable.vue.d.ts +1 -1
  45. package/dist/types/src/components/BaseTabs.vue.d.ts +3 -3
  46. package/dist/types/src/components/BaseTagAutocomplete.vue.d.ts +3 -3
  47. package/dist/types/src/components/BaseTextarea.vue.d.ts +18 -0
  48. package/dist/types/src/components/BaseTextareaAutoresize.vue.d.ts +9 -0
  49. package/dist/types/src/composables/field.d.ts +3 -0
  50. package/dist/types/src/composables/inputSize.d.ts +6 -0
  51. package/dist/types/src/utils/sizes.d.ts +19 -0
  52. package/package.json +1 -1
  53. package/src/assets/form.css +1 -1
  54. package/src/components/BaseAddressForm.stories.js +7 -2
  55. package/src/components/BaseAddressForm.vue +64 -37
  56. package/src/components/BaseAutocomplete.stories.js +1 -1
  57. package/src/components/BaseAutocomplete.vue +86 -96
  58. package/src/components/BaseAutocompleteDrawer.vue +3 -2
  59. package/src/components/BaseAutocompleteFetch.stories.js +1 -1
  60. package/src/components/BaseAutocompleteFetch.vue +3 -2
  61. package/src/components/BaseBelongsTo.stories.js +1 -1
  62. package/src/components/BaseBelongsTo.vue +3 -2
  63. package/src/components/BaseBelongsToFetch.vue +3 -2
  64. package/src/components/BaseButton.stories.js +21 -0
  65. package/src/components/BaseButton.vue +42 -6
  66. package/src/components/BaseButtonGroup.stories.js +31 -1
  67. package/src/components/BaseButtonGroup.vue +46 -33
  68. package/src/components/BaseColor.stories.js +22 -0
  69. package/src/components/BaseColor.vue +28 -25
  70. package/src/components/BaseDataTable.vue +5 -5
  71. package/src/components/BaseDataTableRowAction.vue +6 -6
  72. package/src/components/BaseDatePicker.vue +6 -3
  73. package/src/components/BaseDraggable.vue +5 -1
  74. package/src/components/BaseDropdown.stories.js +2 -3
  75. package/src/components/BaseDropdownAutocomplete.vue +2 -2
  76. package/src/components/BaseField.vue +19 -8
  77. package/src/components/BaseInput.stories.js +36 -2
  78. package/src/components/BaseInput.vue +199 -74
  79. package/src/components/BaseInputError.vue +32 -2
  80. package/src/components/BaseInputLabel.vue +36 -9
  81. package/src/components/BasePassword.stories.js +25 -0
  82. package/src/components/BasePassword.vue +35 -55
  83. package/src/components/BaseSelect.stories.js +34 -0
  84. package/src/components/BaseSelect.vue +57 -8
  85. package/src/components/BaseTagAutocomplete.vue +3 -2
  86. package/src/components/BaseTextarea.stories.js +25 -0
  87. package/src/components/BaseTextarea.vue +34 -3
  88. package/src/components/BaseTextareaAutoresize.stories.js +27 -2
  89. package/src/components/BaseTextareaAutoresize.vue +27 -8
  90. package/src/components/BaseTimelineItem.stories.js +1 -3
  91. package/src/composables/field.ts +20 -0
  92. package/src/composables/inputSize.ts +29 -0
  93. package/src/utils/sizes.ts +21 -0
@@ -1,17 +1,19 @@
1
1
  <template>
2
2
  <div ref="autocomplete">
3
3
  <div class="relative z-[1]">
4
- <div class="flex">
5
- <select
4
+ <div
5
+ class="flex"
6
+ :class="[select ? '-space-x-px' : '']"
7
+ >
8
+ <BaseSelect
6
9
  v-if="select"
7
10
  v-model="selection"
8
11
  :disabled="disabled"
9
- class="rounded-l border-r-transparent focus:z-[1] focus:border-r-blue-500 disabled:cursor-not-allowed disabled:text-slate-300 disabled:opacity-100"
10
- :class="[
11
- hasErrorInternal ? 'border-red-600' : 'border-slate-300',
12
- inputSelectClass,
13
- ]"
14
- @change="onSelectChange"
12
+ class="rounded-r-none border-r-transparent min-w-0"
13
+ :size="sizeInternal"
14
+ :has-error="hasErrorInternal"
15
+ required
16
+ @update:model-value="onSelectChange"
15
17
  >
16
18
  <option
17
19
  v-for="option in select.options"
@@ -20,60 +22,38 @@
20
22
  >
21
23
  {{ option.label }}
22
24
  </option>
23
- </select>
24
- <div class="relative grow">
25
- <input
26
- ref="inputElement"
27
- :value="keywords"
28
- type="text"
29
- :placeholder="placeholder ? placeholder : t('sui.autocomplete_placeholder')"
30
- class="relative w-full placeholder:text-slate-400 disabled:cursor-not-allowed disabled:text-slate-300"
31
- :class="[
32
- showDeleteButton ? 'pr-9' : '',
33
- select ? '-left-px rounded-r' : 'rounded',
34
- hasErrorInternal ? 'border-red-600' : 'border-slate-300',
35
- inputSizeClass,
36
- !visibleFocus
37
- ? [
38
- 'focus:ring-0',
39
- hasErrorInternal
40
- ? 'focus:border-red-600'
41
- : 'focus:border-slate-300',
42
- ]
43
- : '',
44
- ]"
45
- autocomplete="off"
46
- :disabled="disabled"
47
- @click="open"
48
- @input="onTextInput"
49
- @keydown="onTextKeydown"
50
- >
51
- <div
52
- class="pointer-events-none absolute left-0 top-0 flex h-full items-center justify-center"
53
- :class="[iconWrapClass]"
54
- >
55
- <BaseIcon
56
- class="text-slate-400"
57
- :class="[iconClass]"
58
- icon="heroicons:magnifying-glass-solid"
59
- />
60
- </div>
61
- </div>
25
+ </BaseSelect>
26
+ <BaseInput
27
+ ref="inputElement"
28
+ :model-value="keywords"
29
+ type="text"
30
+ :visible-focus="visibleFocus"
31
+ :placeholder="placeholder ? placeholder : t('sui.autocomplete_placeholder')"
32
+ class="w-full grow min-w-0"
33
+ :class="inputClasses"
34
+ :has-error="hasErrorInternal"
35
+ :size="sizeInternal"
36
+ :autocomplete="false"
37
+ :disabled="disabled"
38
+ icon-left="heroicons:magnifying-glass-solid"
39
+ @focus="open"
40
+ @update:model-value="onTextInput"
41
+ @keydown="onTextKeydown"
42
+ />
62
43
  </div>
63
44
 
64
45
  <div
65
46
  v-if="showDeleteButton"
66
- class="absolute right-0 top-0 flex h-full items-center p-1"
47
+ class="absolute right-0 top-0 flex h-full p-1"
67
48
  >
68
49
  <button
69
50
  type="button"
70
- class="group flex h-full items-center rounded p-1.5 enabled:hover:bg-slate-100"
51
+ class="group flex rounded items-center justify-center aspect-1 enabled:hover:bg-slate-200"
71
52
  @click="clear()"
72
53
  >
73
54
  <BaseIcon
74
55
  icon="heroicons:x-mark"
75
- class="text-slate-500 group-hover:text-slate-700"
76
- :class="[iconClass]"
56
+ :class="deleteButtonIconClasses"
77
57
  />
78
58
  </button>
79
59
  </div>
@@ -86,7 +66,7 @@
86
66
  :class="[
87
67
  inline
88
68
  ? 'relative'
89
- : 'absolute top-1 z-menu min-h-[110px] w-full overflow-hidden rounded border border-slate-300 bg-white shadow-md',
69
+ : 'absolute top-0.5 z-menu min-h-[110px] w-full overflow-hidden input-rounded border border-slate-300 bg-white shadow-2xl',
90
70
  ]"
91
71
  >
92
72
  <BaseAutocompleteDrawer
@@ -136,6 +116,8 @@ import { useClickOutside } from '@/composables/clickOutside';
136
116
  import BaseAutocompleteDrawer from './BaseAutocompleteDrawer.vue';
137
117
  import { SelectConfiguration } from '@/types';
138
118
  import { t } from '@/i18n';
119
+ import { Size, sizes } from '@/utils/sizes';
120
+ import { BaseInput, BaseSelect } from '.';
139
121
 
140
122
  const props = defineProps({
141
123
  modelValue: {
@@ -158,6 +140,10 @@ const props = defineProps({
158
140
  default: undefined,
159
141
  type: String,
160
142
  },
143
+ size: {
144
+ default: undefined,
145
+ type: String as PropType<Size>,
146
+ },
161
147
  placeholder: {
162
148
  default: undefined,
163
149
  type: String,
@@ -190,10 +176,6 @@ const props = defineProps({
190
176
  default: false,
191
177
  type: Boolean,
192
178
  },
193
- size: {
194
- default: 'base',
195
- type: String as PropType<'xs' | 'sm' | 'base'>,
196
- },
197
179
  dropdownShow: {
198
180
  default: 'focus',
199
181
  type: String as PropType<'focus' | 'always'>,
@@ -237,10 +219,11 @@ const emit = defineEmits([
237
219
  'select',
238
220
  ]);
239
221
 
240
- const { hasErrorInternal, emitUpdate } = useField({
222
+ const { hasErrorInternal, emitUpdate, sizeInternal } = useField({
241
223
  name: computed(() => props.name),
242
224
  required: computed(() => props.required),
243
225
  hasError: computed(() => props.hasError),
226
+ size: computed(() => props.size),
244
227
  emit: emit,
245
228
  });
246
229
 
@@ -335,6 +318,31 @@ useClickOutside(autocomplete, () => {
335
318
  close();
336
319
  });
337
320
 
321
+ onMounted(() => {
322
+ window.addEventListener('keydown', onWindowKeydown);
323
+ });
324
+
325
+ onBeforeUnmount(() => {
326
+ window.removeEventListener('keydown', onWindowKeydown);
327
+ });
328
+
329
+ function onWindowKeydown(event: KeyboardEvent) {
330
+
331
+ if (!opened.value) {
332
+ return;
333
+ }
334
+
335
+ const key = event.key;
336
+
337
+ if (key === 'Tab') {
338
+ close();
339
+ }
340
+
341
+ if (key === 'Escape') {
342
+ close();
343
+ }
344
+ }
345
+
338
346
  function open() {
339
347
  clearInterval(timerId);
340
348
  // Always focus as a safety
@@ -368,10 +376,10 @@ function close() {
368
376
  emit('close');
369
377
  }
370
378
 
371
- const onTextInput = (event: Event) => {
379
+ const onTextInput = (text: string) => {
372
380
  open();
373
381
  shouldFilter.value = true;
374
- setKeywords(get(event, 'target.value') + '');
382
+ setKeywords(text);
375
383
  emit('typing', keywords.value);
376
384
 
377
385
  // If keywords is empty, emit null
@@ -463,8 +471,7 @@ watch(
463
471
  { immediate: true }
464
472
  );
465
473
 
466
- function onSelectChange(event: Event) {
467
- const value = get(event, 'target.value', null);
474
+ function onSelectChange(value: string | number | null | undefined) {
468
475
  const option = props.select?.options.find((option) => option.value == value);
469
476
 
470
477
  if (props.select && props.select.onChange) {
@@ -491,45 +498,28 @@ const slotProps = {
491
498
 
492
499
  // Element Classes
493
500
 
494
- const inputSizeClass = computed(() => {
495
- if (props.size == 'xs') {
496
- return 'xs:text-xs xs:pl-7 text-base pl-9';
497
- }
498
- if (props.size == 'sm') {
499
- return 'xs:text-sm xs:pl-8 text-base pl-9';
500
- }
501
- return 'text-base pl-9';
502
- });
501
+ const deleteButtonIconClasses = computed(() => {
502
+ const base = 'text-slate-500 group-hover:text-slate-700';
503
503
 
504
- const inputSelectClass = computed(() => {
505
- if (props.size == 'xs') {
506
- return 'xs:text-xs text-base';
507
- }
508
- if (props.size == 'sm') {
509
- return 'xs:text-sm text-base';
510
- }
511
- return 'text-base';
512
- });
504
+ const sizeConfig = sizes[sizeInternal.value];
513
505
 
514
- const iconClass = computed(() => {
515
- if (props.size == 'xs') {
516
- return 'xs:h-4 xs:w-4 h-5 w-5';
517
- }
518
- if (props.size == 'sm') {
519
- return 'xs:h-5 xs:w-5 h-5 w-5';
520
- }
521
- return 'h-5 w-5';
506
+ return [
507
+ base,
508
+ sizeConfig.iconSize,
509
+ ];
522
510
  });
523
511
 
524
- const iconWrapClass = computed(() => {
525
- if (props.size == 'xs') {
526
- return 'xs:pl-2 pl-2.5';
527
- }
528
- if (props.size == 'sm') {
529
- return 'xs:pl-2 pl-2.5';
530
- }
531
- return 'pl-2.5';
532
- });
512
+ const inputClasses = computed(() => {
513
+ const base = 'w-full';
514
+ const paddingRight = showDeleteButton.value ? 'pr-5' : '';
515
+ const withSelect = props.select ? 'rounded-l-none' : '';
516
+
517
+ return [
518
+ base,
519
+ withSelect,
520
+ paddingRight
521
+ ];
522
+ })
533
523
 
534
524
  const showDeleteButton = computed(() => {
535
525
  return normalizedModelValue.value && !props.disabled && props.showModelValue;
@@ -121,6 +121,7 @@ import BaseSkeleton from './BaseSkeleton.vue';
121
121
  import { Icon as BaseIcon } from '@iconify/vue';
122
122
  import BaseSpinnerSmall from '../svg/BaseSpinnerSmall.vue';
123
123
  import { t } from '@/i18n';
124
+ import { Size } from '@/utils/sizes';
124
125
 
125
126
  const props = defineProps({
126
127
  selected: {
@@ -148,8 +149,8 @@ const props = defineProps({
148
149
  default: false,
149
150
  },
150
151
  size: {
151
- type: String as PropType<'xs' | 'sm' | 'base'>,
152
- default: 'base',
152
+ type: String as PropType<Size>,
153
+ default: 'md',
153
154
  },
154
155
  twDrawer: {
155
156
  type: String,
@@ -3,7 +3,7 @@ import ShowValue from '@/../.storybook/components/ShowValue.vue';
3
3
  import { options } from '@/../.storybook/utils';
4
4
  import { createFieldStory } from '../../.storybook/utils';
5
5
 
6
- const sizes = ['xs', 'sm', 'base'];
6
+ const sizes = ['xs', 'sm', 'md'];
7
7
 
8
8
  export default {
9
9
  title: 'Form/BaseAutocompleteFetch',
@@ -74,6 +74,7 @@ import {
74
74
  import BaseAutocomplete from './BaseAutocomplete.vue';
75
75
  import { useHasPaginatedData } from '@/composables/paginatedData';
76
76
  import { t } from '@/i18n';
77
+ import { Size } from '@/utils/sizes';
77
78
 
78
79
  /**
79
80
  * Behavior notes
@@ -130,8 +131,8 @@ const props = defineProps({
130
131
  type: Boolean,
131
132
  },
132
133
  size: {
133
- default: 'base',
134
- type: String as PropType<'xs' | 'sm' | 'base'>,
134
+ default: undefined,
135
+ type: String as PropType<Size>,
135
136
  },
136
137
  dropdownShow: {
137
138
  default: 'focus',
@@ -3,7 +3,7 @@ import ShowValue from '@/../.storybook/components/ShowValue.vue';
3
3
  import { options } from '@/../.storybook/utils';
4
4
  import { createFieldStory } from '../../.storybook/utils';
5
5
 
6
- const sizes = ['xs', 'sm', 'base'];
6
+ const sizes = ['xs', 'sm', 'md'];
7
7
 
8
8
  export default {
9
9
  title: 'Form/BaseBelongsTo',
@@ -46,6 +46,7 @@
46
46
  import { PropType } from 'vue';
47
47
  import BaseAutocomplete from './BaseAutocomplete.vue';
48
48
  import { Option, SelectConfiguration } from '@/types';
49
+ import { Size } from '@/utils/sizes';
49
50
 
50
51
  const props = defineProps({
51
52
  modelValue: {
@@ -91,8 +92,8 @@ const props = defineProps({
91
92
  type: Boolean,
92
93
  },
93
94
  size: {
94
- default: 'base',
95
- type: String as PropType<'xs' | 'sm' | 'base'>,
95
+ default: undefined,
96
+ type: String as PropType<Size>,
96
97
  },
97
98
  dropdownShow: {
98
99
  default: 'focus',
@@ -48,6 +48,7 @@ import { AxiosResponse } from 'axios';
48
48
  import { config } from '@/index';
49
49
  import BaseAutocompleteFetch from './BaseAutocompleteFetch.vue';
50
50
  import { Option, SelectConfiguration } from '@/types';
51
+ import { Size } from '@/utils/sizes';
51
52
 
52
53
  const props = defineProps({
53
54
  modelValue: {
@@ -101,8 +102,8 @@ const props = defineProps({
101
102
  type: Boolean,
102
103
  },
103
104
  size: {
104
- default: 'base',
105
- type: String as PropType<'xs' | 'sm' | 'base'>,
105
+ default: undefined,
106
+ type: String as PropType<Size>,
106
107
  },
107
108
  dropdownShow: {
108
109
  default: 'focus',
@@ -1,6 +1,10 @@
1
1
  import BaseButton from './BaseButton.vue';
2
2
 
3
3
  const colors = [
4
+ '#6fc3fc',
5
+ '#ff00ff',
6
+ '#111111',
7
+ '#f1f1f1',
4
8
  'primary',
5
9
  'secondary',
6
10
  'secondary-outline',
@@ -109,3 +113,20 @@ export const Sizes = (args) => ({
109
113
  </div>
110
114
  `,
111
115
  });
116
+
117
+ const IconTemplate = (args) => ({
118
+ components: { BaseButton },
119
+ setup() {
120
+ return { args };
121
+ },
122
+ template: `
123
+ <BaseButton v-bind="args">
124
+ </BaseButton>
125
+ `,
126
+ });
127
+
128
+ export const Icon = IconTemplate.bind({});
129
+ Icon.args = {
130
+ icon: 'mdi:plus',
131
+ color: 'primary',
132
+ };
@@ -3,6 +3,7 @@
3
3
  :is="as"
4
4
  ref="button"
5
5
  :class="classes"
6
+ :style="styles"
6
7
  :to="to"
7
8
  :disabled="disabled"
8
9
  :type="type"
@@ -46,11 +47,14 @@
46
47
  import { ClassNameValue, twMerge } from 'tailwind-merge';
47
48
  import { RouteLocationRaw } from 'vue-router';
48
49
  import { BaseIcon } from '.';
50
+ import { getColorConfig } from '@/utils/colors';
49
51
 
50
52
  defineOptions({
51
53
  inheritAttrs: false,
52
54
  });
53
55
 
56
+ const slots = useSlots();
57
+
54
58
  const sizes = {
55
59
  'xs': 'btn-xs',
56
60
  'sm': 'btn-sm',
@@ -89,7 +93,7 @@ const props = withDefaults(defineProps<{
89
93
  type?: 'button' | 'submit' | 'reset';
90
94
  iconPosition?: 'start' | 'end';
91
95
  size?: keyof typeof sizes;
92
- color?: keyof typeof colors;
96
+ color?: keyof typeof colors | string;
93
97
  to?: RouteLocationRaw;
94
98
  }>(), {
95
99
  as: 'button',
@@ -106,20 +110,50 @@ const props = withDefaults(defineProps<{
106
110
 
107
111
  defineEmits(['click']);
108
112
 
113
+ const iconOnly = computed(() => {
114
+ return props.icon && !slots.default;
115
+ });
116
+
109
117
  const classes = computed(() => {
110
118
  const classes = ['btn'];
111
119
 
112
120
  if (props.color) {
113
- classes.push(colors[props.color]);
121
+
122
+ if (props.color in colors) {
123
+ classes.push(colors[props.color as keyof typeof colors]);
124
+ } else {
125
+ classes.push('hover:opacity-80');
126
+ }
127
+
114
128
  }
115
129
 
116
130
  if (props.size) {
117
131
  classes.push(sizes[props.size]);
118
132
  }
119
133
 
134
+ if (iconOnly.value) {
135
+ classes.push('p-0 aspect-1 justify-center');
136
+ }
137
+
120
138
  return twMerge(classes, props.class);
121
139
  });
122
140
 
141
+ const styles = computed(() => {
142
+ if (props.color && !(props.color in colors)) {
143
+ const config = getColorConfig(props.color);
144
+
145
+ return {
146
+ backgroundColor: config.backgroundColor,
147
+ color: config.textColor,
148
+ borderColor: config.borderColor,
149
+ borderWidth: '1px',
150
+ borderStyle: 'solid',
151
+ };
152
+ }
153
+
154
+ return {};
155
+ });
156
+
123
157
  const containerClass = computed(() => {
124
158
  const classes = ['flex items-center justify-center'];
125
159
 
@@ -147,10 +181,12 @@ const containerClass = computed(() => {
147
181
  const iconClass = computed(() => {
148
182
  const classes = [''];
149
183
 
150
- if (props.iconPosition === 'start') {
151
- classes.push('-translate-x-[20%]');
152
- } else if (props.iconPosition === 'end') {
153
- classes.push('translate-x-[20%]');
184
+ if (!iconOnly.value) {
185
+ if (props.iconPosition === 'start') {
186
+ classes.push('-translate-x-[20%]');
187
+ } else if (props.iconPosition === 'end') {
188
+ classes.push('translate-x-[20%]');
189
+ }
154
190
  }
155
191
 
156
192
  if (props.size === 'xs') {
@@ -2,10 +2,21 @@ import { createFieldStory, options } from '../../.storybook/utils';
2
2
  import BaseButtonGroup from './BaseButtonGroup.vue';
3
3
  import ShowValue from '../../.storybook/components/ShowValue.vue';
4
4
 
5
+ const sizes = ['xs', 'sm', 'md'];
6
+
5
7
  export default {
6
8
  title: 'Form/BaseButtonGroup',
7
9
  component: BaseButtonGroup,
8
- argTypes: {},
10
+ argTypes: {
11
+ spacing: {
12
+ control: { type: 'select' },
13
+ options: ['none', 'default'],
14
+ },
15
+ size: {
16
+ control: { type: 'select' },
17
+ options: sizes,
18
+ },
19
+ },
9
20
  args: {
10
21
  labelKey: 'label',
11
22
  valueKey: 'value',
@@ -45,6 +56,11 @@ Disabled.args = {
45
56
  modelValue: { label: 'Dark Maul', value: 'darth_maul' },
46
57
  };
47
58
 
59
+ export const NoSpace = Template.bind({});
60
+ NoSpace.args = {
61
+ spacing: 'none',
62
+ };
63
+
48
64
  export const SlotOption = (args) => ({
49
65
  components: { BaseButtonGroup },
50
66
  setup() {
@@ -79,6 +95,20 @@ export const SlotOption = (args) => ({
79
95
  `,
80
96
  });
81
97
 
98
+ export const Sizes = (args) => ({
99
+ components: { BaseButtonGroup },
100
+ setup() {
101
+ const value = ref(null);
102
+ return { value, args, sizes };
103
+ },
104
+ template: `
105
+ <div v-for="size in sizes" :key="size" class="mb-4">
106
+ <p class="text-xs text-slate-600 leading-tight mb-1"> {{ size }}</p>
107
+ <BaseButtonGroup v-bind="args" v-model="value" :size="size"></BaseButtonGroup>
108
+ </div>
109
+ `,
110
+ });
111
+
82
112
  export const Field = createFieldStory({
83
113
  component: BaseButtonGroup,
84
114
  componentName: 'BaseButtonGroup',
@@ -1,32 +1,27 @@
1
1
  <template>
2
2
  <div
3
- class="flex flex-wrap"
4
- :style="{ margin: '-' + spacing }"
3
+ class="flex items-center flex-wrap"
4
+ :class="wrapperClasses"
5
5
  >
6
- <div
6
+ <BaseButton
7
7
  v-for="option in normalizedOptions"
8
8
  :key="option.value ? option.value : 'null'"
9
- :style="{ padding: spacing }"
9
+ :type="buttonType"
10
+ :disabled="disabled"
11
+ :size="sizeInternal"
12
+ :class="twButton"
13
+ :color="buttonColor(option, isSelected(option))"
14
+ @click="onSelect(option)"
10
15
  >
11
- <button
12
- :type="buttonType"
16
+ <slot
17
+ name="option"
18
+ :selected="computed(() => isSelected(option))"
19
+ :option="option"
13
20
  :disabled="disabled"
14
- :class="[
15
- twButton,
16
- isSelected(option) ? twButtonSelected : twButtonUnselected,
17
- ]"
18
- @click="onSelect(option)"
19
21
  >
20
- <slot
21
- name="option"
22
- :selected="computed(() => isSelected(option))"
23
- :option="option"
24
- :disabled="disabled"
25
- >
26
- {{ option.label }}
27
- </slot>
28
- </button>
29
- </div>
22
+ {{ option.label }}
23
+ </slot>
24
+ </BaseButton>
30
25
  </div>
31
26
  </template>
32
27
 
@@ -36,6 +31,8 @@ import { NormalizedOption, Option } from '@/types';
36
31
  import { cloneDeep, isArray } from 'lodash';
37
32
  import { useHasOptions } from '@/composables/hasOptions';
38
33
  import { useField } from '@/composables/field';
34
+ import BaseButton from '@/components/BaseButton.vue';
35
+ import { Size } from '@/utils/sizes';
39
36
 
40
37
  const props = defineProps({
41
38
  modelValue: {
@@ -52,25 +49,27 @@ const props = defineProps({
52
49
  default: false,
53
50
  type: Boolean,
54
51
  },
52
+ size: {
53
+ default: undefined,
54
+ type: String as PropType<Size>,
55
+ },
55
56
  buttonType: {
56
57
  default: 'button',
57
58
  type: String as PropType<'button' | 'submit'>,
58
59
  },
59
- twButton: {
60
- default: 'btn btn-sm',
61
- type: String,
60
+ buttonColor: {
61
+ default(option: Option, selected: boolean) {
62
+ return selected ? 'primary' : ''
63
+ },
64
+ type: Function as PropType<(option: Option, selected: boolean) => string>,
62
65
  },
63
- twButtonSelected: {
64
- default: 'btn-primary',
65
- type: String,
66
- },
67
- twButtonUnselected: {
66
+ twButton: {
68
67
  default: '',
69
- type: String,
68
+ type: [String, Array] as PropType<string | string[]>,
70
69
  },
71
70
  spacing: {
72
- default: '0.15rem',
73
- type: String,
71
+ default: 'default',
72
+ type: String as PropType<'none' | 'default'>,
74
73
  },
75
74
  options: {
76
75
  required: true,
@@ -100,9 +99,10 @@ const props = defineProps({
100
99
 
101
100
  const emit = defineEmits(['update:modelValue']);
102
101
 
103
- const { requiredInternal, emitUpdate } = useField({
102
+ const { requiredInternal, emitUpdate, sizeInternal } = useField({
104
103
  name: computed(() => props.name),
105
104
  required: computed(() => props.required),
105
+ size: computed(() => props.size),
106
106
  hasError: computed(() => props.hasError),
107
107
  emit: emit,
108
108
  });
@@ -147,4 +147,17 @@ function onSelect(option: NormalizedOption) {
147
147
  emitUpdate(newOption);
148
148
  }
149
149
  }
150
+
151
+ const wrapperClasses = computed(() => {
152
+
153
+ if (props.spacing == 'none') {
154
+ return 'btn-group';
155
+ }
156
+
157
+ return {
158
+ "xs": "gap-0.5",
159
+ "sm": "gap-1",
160
+ "md": "gap-1.5",
161
+ }[sizeInternal.value];
162
+ });
150
163
  </script>