sprintify-ui 0.1.14 → 0.1.16

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 (66) hide show
  1. package/README.md +1 -26
  2. package/dist/sprintify-ui.es.js +11391 -11329
  3. package/dist/style.css +1 -1
  4. package/dist/types/src/components/BaseAutocomplete.vue.d.ts +1 -1
  5. package/dist/types/src/components/BaseAutocompleteFetch.vue.d.ts +2 -2
  6. package/dist/types/src/components/BaseBelongsTo.vue.d.ts +1 -1
  7. package/dist/types/src/components/BaseCounter.vue.d.ts +4 -3
  8. package/dist/types/src/components/BaseDatePicker.vue.d.ts +1 -1
  9. package/dist/types/src/components/BaseFileUploader.vue.d.ts +1 -1
  10. package/dist/types/src/components/BaseHeader.vue.d.ts +6 -1
  11. package/dist/types/src/components/BaseMediaLibrary.vue.d.ts +1 -1
  12. package/dist/types/src/components/BaseMediaListItem.vue.d.ts +1 -1
  13. package/dist/types/src/components/BaseMenuItem.vue.d.ts +4 -3
  14. package/dist/types/src/components/BaseNumber.vue.d.ts +3 -3
  15. package/dist/types/src/components/BaseSwitch.vue.d.ts +0 -27
  16. package/dist/types/src/components/BaseTableColumn.vue.d.ts +1 -1
  17. package/dist/types/src/components/BaseTagAutocomplete.vue.d.ts +1 -1
  18. package/dist/types/src/composables/mediaQuery.d.ts +2 -2
  19. package/dist/types/src/i18n/index.d.ts +1 -0
  20. package/dist/types/src/index.d.ts +0 -178
  21. package/dist/types/src/stores/i18n.d.ts +5 -0
  22. package/dist/types/src/types/index.d.ts +3 -2
  23. package/package.json +7 -6
  24. package/src/components/BaseActionItemButton.vue +2 -0
  25. package/src/components/BaseAddressForm.vue +8 -7
  26. package/src/components/BaseAutocomplete.vue +3 -3
  27. package/src/components/BaseAutocompleteDrawer.vue +2 -1
  28. package/src/components/BaseAutocompleteFetch.vue +2 -1
  29. package/src/components/BaseCharacterCounter.vue +2 -1
  30. package/src/components/BaseClipboard.vue +4 -2
  31. package/src/components/BaseCounter.stories.js +1 -1
  32. package/src/components/BaseCounter.vue +5 -3
  33. package/src/components/BaseCropper.vue +2 -5
  34. package/src/components/BaseCropperModal.vue +3 -2
  35. package/src/components/BaseDataIterator.vue +6 -7
  36. package/src/components/BaseDataTable.vue +14 -14
  37. package/src/components/BaseDatePicker.vue +4 -4
  38. package/src/components/BaseDateSelect.vue +10 -10
  39. package/src/components/BaseDialog.vue +5 -6
  40. package/src/components/BaseDisplayRelativeTime.vue +6 -6
  41. package/src/components/BaseFilePicker.vue +7 -8
  42. package/src/components/BaseFileUploader.vue +3 -3
  43. package/src/components/BaseForm.vue +3 -3
  44. package/src/components/BaseHeader.stories.js +12 -5
  45. package/src/components/BaseHeader.vue +73 -74
  46. package/src/components/BaseIconPicker.vue +2 -1
  47. package/src/components/BaseLayoutNotificationDropdown.vue +3 -2
  48. package/src/components/BaseMediaLibrary.vue +8 -11
  49. package/src/components/BaseMediaListItem.vue +4 -3
  50. package/src/components/BaseMenuItem.vue +4 -3
  51. package/src/components/BaseNumber.vue +25 -5
  52. package/src/components/BasePagination.vue +4 -3
  53. package/src/components/BaseReadMore.vue +2 -1
  54. package/src/components/BaseSelect.vue +3 -2
  55. package/src/components/BaseSwitch.stories.js +5 -2
  56. package/src/components/BaseSwitch.vue +9 -36
  57. package/src/components/BaseTagAutocomplete.vue +4 -4
  58. package/src/components/BaseTagAutocompleteFetch.vue +3 -2
  59. package/src/composables/mediaQuery.ts +2 -2
  60. package/src/i18n/index.ts +60 -0
  61. package/src/index.ts +11 -11
  62. package/src/stores/dialogs.ts +3 -3
  63. package/src/stores/i18n.ts +14 -0
  64. package/src/types/index.ts +11 -2
  65. package/src/utils/fileSizeFormat.ts +6 -6
  66. package/src/utils/toHumanList.ts +2 -2
@@ -28,7 +28,7 @@
28
28
  v-model="searchQuery"
29
29
  type="text"
30
30
  class="h-11 w-full overflow-hidden rounded-md border border-slate-300 bg-white pl-10 pr-9 shadow-sm"
31
- :placeholder="$t('sui.autocomplete_placeholder')"
31
+ :placeholder="t('sui.autocomplete_placeholder')"
32
32
  @input="onSearch"
33
33
  />
34
34
  <div
@@ -102,11 +102,11 @@
102
102
  <p
103
103
  class="text-center text-sm text-slate-500 sm:text-right [&>b]:font-medium [&>b]:text-slate-600"
104
104
  >
105
- {{ $t('sui.pagination_detail_1') }}
105
+ {{ t('sui.pagination_detail_1') }}
106
106
 
107
107
  <b>{{ paginationStart }}</b> - <b>{{ paginationEnd }}</b>
108
108
 
109
- {{ $t('sui.pagination_detail_2') }}
109
+ {{ t('sui.pagination_detail_2') }}
110
110
 
111
111
  <b>{{ paginationMetadata.total }}</b>
112
112
  </p>
@@ -164,7 +164,7 @@
164
164
 
165
165
  <script lang="ts">
166
166
  /* eslint-disable @typescript-eslint/no-explicit-any */
167
-
167
+ import { t } from '@/i18n';
168
168
  type Direction = 'asc' | 'desc';
169
169
 
170
170
  const DEFAULT_QUERY = {
@@ -276,7 +276,6 @@ const props = defineProps({
276
276
  },
277
277
  });
278
278
 
279
- const i18n = useI18n();
280
279
  const http = config.http;
281
280
 
282
281
  const emit = defineEmits([
@@ -730,8 +729,8 @@ const sectionsInternal = computed<DataIteratorSection[]>(() => {
730
729
  return [
731
730
  {
732
731
  name: 'filters',
733
- title: i18n.t('sui.filters'),
734
- closeText: i18n.t('sui.apply_filters'),
732
+ title: t('sui.filters'),
733
+ closeText: t('sui.apply_filters'),
735
734
  icon: 'heroicons:adjustments-horizontal-solid',
736
735
  opened: true,
737
736
  },
@@ -32,7 +32,7 @@
32
32
  <div>
33
33
  <span class="mr-3 text-slate-500"
34
34
  >{{
35
- $t('sui.x_rows_selected', {
35
+ t('sui.x_rows_selected', {
36
36
  count: newCheckedRows.length,
37
37
  })
38
38
  }}.</span
@@ -42,7 +42,7 @@
42
42
  class="mr-3 inline text-slate-700 underline"
43
43
  @click="uncheckAll()"
44
44
  >
45
- {{ $t('sui.deselect_all') }}
45
+ {{ t('sui.deselect_all') }}
46
46
  </button>
47
47
  </div>
48
48
  <BaseMenu
@@ -135,7 +135,7 @@
135
135
  class="h-10 w-10 text-red-600"
136
136
  />
137
137
  <p class="mt-3 text-center text-sm text-slate-600">
138
- {{ $t('sui.whoops') }}
138
+ {{ t('sui.whoops') }}
139
139
  </p>
140
140
  </div>
141
141
  </div>
@@ -147,7 +147,7 @@
147
147
  <BaseEmptyState class="w-32"></BaseEmptyState>
148
148
 
149
149
  <p class="mt-3 text-center text-sm text-slate-600">
150
- {{ $t('sui.nothing_found') }}
150
+ {{ t('sui.nothing_found') }}
151
151
  </p>
152
152
  </div>
153
153
  </div>
@@ -192,6 +192,7 @@
192
192
 
193
193
  <script lang="ts" setup>
194
194
  import { PropType } from 'vue';
195
+ import { t } from '@/i18n';
195
196
  import {
196
197
  Collection,
197
198
  CollectionItem,
@@ -217,7 +218,6 @@ import { RouteLocationRaw } from 'vue-router';
217
218
  import BaseMenu from './BaseMenu.vue';
218
219
  import BaseDataTableRowAction from './BaseDataTableRowAction.vue';
219
220
 
220
- const i18n = useI18n();
221
221
  const router = useRouter();
222
222
 
223
223
  const http = config.http;
@@ -468,11 +468,11 @@ const canDelete = (row: CollectionItem): boolean => {
468
468
 
469
469
  function onDeleteClick(row: CollectionItem) {
470
470
  dialogs.push({
471
- title: i18n.t('sui.delete_record') + '?',
472
- message: i18n.t('sui.delete_record_description'),
471
+ title: t('sui.delete_record') + '?',
472
+ message: t('sui.delete_record_description'),
473
473
  color: 'danger',
474
474
  closeOnOutsideClick: true,
475
- confirmText: i18n.t('sui.yes_delete'),
475
+ confirmText: t('sui.yes_delete'),
476
476
  onConfirm: () => onDelete(row),
477
477
  });
478
478
  }
@@ -487,7 +487,7 @@ const onDelete = (row: CollectionItem) => {
487
487
  .then((response) => {
488
488
  if (response.data && response.data.message) {
489
489
  notifications.push({
490
- title: i18n.t('sui.success'),
490
+ title: t('sui.success'),
491
491
  text: response.data.message,
492
492
  color: 'success',
493
493
  });
@@ -499,7 +499,7 @@ const onDelete = (row: CollectionItem) => {
499
499
  })
500
500
  .catch((error) => {
501
501
  notifications.push({
502
- title: i18n.t('sui.error'),
502
+ title: t('sui.error'),
503
503
  text: error.response.data?.message ?? 'Unknown error',
504
504
  color: 'danger',
505
505
  });
@@ -572,7 +572,7 @@ const rowActionsInternal = computed<RowAction[]>(() => {
572
572
 
573
573
  if (props.editUrl && props.editButton) {
574
574
  actions.push({
575
- label: i18n.t('sui.edit'),
575
+ label: t('sui.edit'),
576
576
  icon: 'heroicons:cog-6-tooth-solid',
577
577
  to: (row: CollectionItem) => (props.editUrl ? props.editUrl(row) : ''),
578
578
  disabled: (row: CollectionItem) => !canUpdate(row),
@@ -581,7 +581,7 @@ const rowActionsInternal = computed<RowAction[]>(() => {
581
581
 
582
582
  if (props.deleteUrl && props.deleteButton) {
583
583
  actions.push({
584
- label: i18n.t('sui.delete'),
584
+ label: t('sui.delete'),
585
585
  icon: 'heroicons:trash-20-solid',
586
586
  action: onDeleteClick,
587
587
  disabled: (row: CollectionItem) => !canDelete(row),
@@ -643,8 +643,8 @@ const sectionsInternal = computed<DataIteratorSection[]>(() => {
643
643
  {
644
644
  name: 'columns',
645
645
  icon: 'heroicons:table-cells-20-solid',
646
- title: i18n.t('sui.columns'),
647
- closeText: i18n.t('sui.apply'),
646
+ title: t('sui.columns'),
647
+ closeText: t('sui.apply'),
648
648
  opened: false,
649
649
  },
650
650
  ];
@@ -15,7 +15,7 @@
15
15
  :disabled="disabled"
16
16
  class="flatpickr w-full rounded pl-10 pr-16 disabled:cursor-not-allowed disabled:text-slate-300"
17
17
  :class="[hasErrorInternal ? 'border-red-500' : 'border-slate-300']"
18
- :placeholder="$t('sui.click_or_select_date')"
18
+ :placeholder="t('sui.click_or_select_date')"
19
19
  />
20
20
  <div
21
21
  v-if="modelValueFormatted && !disabled"
@@ -41,9 +41,11 @@ import { useField } from '@/composables/field';
41
41
  import flatpickr from 'flatpickr';
42
42
  import 'flatpickr/dist/flatpickr.css';
43
43
 
44
+ import { t } from '@/i18n';
44
45
  import { French } from 'flatpickr/dist/l10n/fr';
45
46
  import { english } from 'flatpickr/dist/l10n/default';
46
47
  import { Instance } from 'flatpickr/dist/types/instance';
48
+ import { useI18nStore } from '@/stores/i18n';
47
49
 
48
50
  const props = withDefaults(
49
51
  defineProps<{
@@ -78,8 +80,6 @@ const props = withDefaults(
78
80
  }
79
81
  );
80
82
 
81
- const i18n = useI18n();
82
-
83
83
  const emit = defineEmits(['update:modelValue']);
84
84
 
85
85
  const formatInternal = computed(() => {
@@ -140,7 +140,7 @@ function parseDate(date: string): string {
140
140
  }
141
141
 
142
142
  const locale = computed(() => {
143
- if (i18n.locale.value == 'fr') {
143
+ if (useI18nStore().locale == 'fr') {
144
144
  return French;
145
145
  }
146
146
 
@@ -14,11 +14,11 @@
14
14
  },
15
15
  [hasErrorInternal ? 'border-red-500' : 'border-slate-300'],
16
16
  ]"
17
- :placeholder="$t('sui.year')"
17
+ :placeholder="t('sui.year')"
18
18
  @change="update()"
19
19
  >
20
20
  <option disabled selected hidden :value="null">
21
- {{ $t('sui.year') }}
21
+ {{ t('sui.year') }}
22
22
  </option>
23
23
  <option v-for="year in years" :key="year" :value="year">
24
24
  {{ year }}
@@ -39,11 +39,11 @@
39
39
  },
40
40
  [hasErrorInternal ? 'border-red-500' : 'border-slate-300'],
41
41
  ]"
42
- :placeholder="$t('sui.month')"
42
+ :placeholder="t('sui.month')"
43
43
  @change="update()"
44
44
  >
45
45
  <option disabled selected hidden :value="null">
46
- {{ $t('sui.month') }}
46
+ {{ t('sui.month') }}
47
47
  </option>
48
48
  <option v-for="(month, i) in months" :key="month" :value="i + 1">
49
49
  {{ month }}
@@ -64,11 +64,11 @@
64
64
  },
65
65
  [hasErrorInternal ? 'border-red-500' : 'border-slate-300'],
66
66
  ]"
67
- :placeholder="$t('sui.day')"
67
+ :placeholder="t('sui.day')"
68
68
  @change="update()"
69
69
  >
70
70
  <option disabled selected hidden :value="null">
71
- {{ $t('sui.day') }}
71
+ {{ t('sui.day') }}
72
72
  </option>
73
73
  <option v-for="day in days" :key="day" :value="day">
74
74
  {{ day }}
@@ -83,7 +83,7 @@
83
83
  class="mt-1 appearance-none border-transparent bg-transparent text-sm text-slate-700 underline outline-none disabled:cursor-not-allowed disabled:opacity-50"
84
84
  @click="clear()"
85
85
  >
86
- <span>{{ $t('sui.clear') }}</span>
86
+ <span>{{ t('sui.clear') }}</span>
87
87
  </button>
88
88
  </div>
89
89
  </template>
@@ -93,6 +93,8 @@ import { PropType } from 'vue';
93
93
  import { range, padStart } from 'lodash';
94
94
  import { DateTime, Info } from 'luxon';
95
95
  import { useField } from '@/composables/field';
96
+ import { t } from '@/i18n';
97
+ import { useI18nStore } from '@/stores/i18n';
96
98
 
97
99
  const props = defineProps({
98
100
  modelValue: {
@@ -134,10 +136,8 @@ const { hasErrorInternal, emitUpdate } = useField({
134
136
  emit: emit,
135
137
  });
136
138
 
137
- const i18n = useI18n();
138
-
139
139
  const years = range(props.maxYear, props.minYear) as number[];
140
- const months = Info.months('short', { locale: i18n.locale.value });
140
+ const months = Info.months('short', { locale: useI18nStore().locale });
141
141
  const days = computed(() => {
142
142
  if (!date.value.year) {
143
143
  return [];
@@ -62,14 +62,14 @@
62
62
  }"
63
63
  @click="$emit('confirm')"
64
64
  >
65
- {{ confirmText ?? $t('sui.confirm') }}
65
+ {{ confirmText ?? t('sui.confirm') }}
66
66
  </button>
67
67
  <button
68
68
  type="button"
69
69
  class="btn w-full sm:mr-2 sm:w-auto"
70
70
  @click="$emit('cancel')"
71
71
  >
72
- {{ cancelText ?? $t('sui.cancel') }}
72
+ {{ cancelText ?? t('sui.cancel') }}
73
73
  </button>
74
74
  </div>
75
75
  </div>
@@ -78,6 +78,7 @@
78
78
  <script lang="ts" setup>
79
79
  import { PropType } from 'vue';
80
80
  import { Icon as BaseIcon } from '@iconify/vue';
81
+ import { t } from '@/i18n';
81
82
 
82
83
  defineProps({
83
84
  color: {
@@ -94,15 +95,13 @@ defineProps({
94
95
  },
95
96
  confirmText: {
96
97
  default() {
97
- const i18n = useI18n();
98
- return i18n.t('sui.confirm');
98
+ return t('sui.confirm');
99
99
  },
100
100
  type: String,
101
101
  },
102
102
  cancelText: {
103
103
  default() {
104
- const i18n = useI18n();
105
- return i18n.t('sui.cancel');
104
+ return t('sui.cancel');
106
105
  },
107
106
  type: String,
108
107
  },
@@ -15,6 +15,8 @@
15
15
  </template>
16
16
 
17
17
  <script lang="ts" setup>
18
+ import { t } from '@/i18n';
19
+ import { useI18nStore } from '@/stores/i18n';
18
20
  import humanizeDuration from 'humanize-duration';
19
21
  import { DateTime } from 'luxon';
20
22
  import { PropType } from 'vue';
@@ -89,12 +91,10 @@ const intervalId = setInterval(() => {
89
91
  now.value = DateTime.now().toSeconds();
90
92
  }, intervalValue);
91
93
 
92
- const i18n = useI18n();
93
-
94
94
  const readableDate = computed(() => {
95
95
  const duration = getDuration();
96
96
  const durationHuman = humanizeDuration(duration, {
97
- language: i18n.locale.value,
97
+ language: useI18nStore().locale,
98
98
  round: true,
99
99
  largest: 1,
100
100
  });
@@ -102,15 +102,15 @@ const readableDate = computed(() => {
102
102
  const minutes = getMinutes(duration);
103
103
 
104
104
  if (minutes < 1) {
105
- return i18n.t('sui.just_now');
105
+ return t('sui.just_now');
106
106
  }
107
107
 
108
- return i18n.t('sui.x_ago', { duration: durationHuman });
108
+ return t('sui.x_ago', { duration: durationHuman });
109
109
  });
110
110
 
111
111
  const tooltip = computed(() => {
112
112
  return DateTime.fromISO(props.value)
113
- .setLocale(i18n.locale.value)
113
+ .setLocale(useI18nStore().locale)
114
114
  .toLocaleString(DateTime.DATETIME_FULL);
115
115
  });
116
116
 
@@ -24,6 +24,7 @@
24
24
  </template>
25
25
 
26
26
  <script lang="ts" setup>
27
+ import { t } from '@/i18n';
27
28
  import { useNotificationsStore } from '@/stores/notifications';
28
29
  import { fileSizeFormat, toHumanList } from '@/utils';
29
30
  import { maxSize, validExtension } from '@/utils/fileValidations';
@@ -49,8 +50,6 @@ const emit = defineEmits(['select']);
49
50
 
50
51
  const notifications = useNotificationsStore();
51
52
 
52
- const i18n = useI18n();
53
-
54
53
  const selecting = ref(false);
55
54
  const dragging = ref(false);
56
55
  const input = ref<HTMLInputElement | undefined>();
@@ -95,8 +94,8 @@ async function select(files: File[]) {
95
94
  if (!maxSize(file, props.maxSize)) {
96
95
  notifications.push({
97
96
  color: 'danger',
98
- title: i18n.t('sui.error'),
99
- text: i18n.t('sui.the_file_size_must_not_exceed_x', {
97
+ title: t('sui.error'),
98
+ text: t('sui.the_file_size_must_not_exceed_x', {
100
99
  x: fileSizeFormat(props.maxSize),
101
100
  }),
102
101
  });
@@ -108,13 +107,13 @@ async function select(files: File[]) {
108
107
  if (!validExtension(file, props.acceptedExtensions)) {
109
108
  notifications.push({
110
109
  color: 'danger',
111
- title: i18n.t('sui.error'),
110
+ title: t('sui.error'),
112
111
  text:
113
- i18n.t('sui.the_file_type_is_invalid') +
112
+ t('sui.the_file_type_is_invalid') +
114
113
  ' ' +
115
- i18n.t('sui.file_must_be_of_type') +
114
+ t('sui.file_must_be_of_type') +
116
115
  ' ' +
117
- toHumanList(props.acceptedExtensions as string[], i18n.t('sui.or')) +
116
+ toHumanList(props.acceptedExtensions as string[], t('sui.or')) +
118
117
  '.',
119
118
  });
120
119
 
@@ -37,9 +37,9 @@ import BaseLoadingCover from '@/components/BaseLoadingCover.vue';
37
37
  import { BaseCropperConfig } from '@/types';
38
38
  import BaseFilePicker from './BaseFilePicker.vue';
39
39
  import BaseFilePickerCrop from './BaseFilePickerCrop.vue';
40
+ import { t } from '@/i18n';
40
41
 
41
42
  const http = config.http;
42
- const i18n = useI18n();
43
43
  const notifications = useNotificationsStore();
44
44
 
45
45
  const props = withDefaults(
@@ -148,8 +148,8 @@ async function onFileSelect(file: File) {
148
148
  emit('upload:fail');
149
149
  notifications.push({
150
150
  color: 'danger',
151
- title: i18n.t('sui.error'),
152
- text: i18n.t('sui.upload_failed'),
151
+ title: t('sui.error'),
152
+ text: t('sui.upload_failed'),
153
153
  });
154
154
  } finally {
155
155
  emit('upload:end');
@@ -45,6 +45,7 @@ import { Method, DataFormat } from '@/types';
45
45
  import { AxiosError, AxiosInstance, AxiosResponse } from 'axios';
46
46
  import { config, useNotificationsStore } from '@/index';
47
47
  import { get, isArray } from 'lodash';
48
+ import { t } from '@/i18n';
48
49
 
49
50
  const notifications = useNotificationsStore();
50
51
 
@@ -107,7 +108,6 @@ const props = defineProps({
107
108
  },
108
109
  });
109
110
 
110
- const i18n = useI18n();
111
111
  const emit = defineEmits(['error', 'success']);
112
112
 
113
113
  const form = ref<null | HTMLFormElement>(null);
@@ -226,7 +226,7 @@ function query() {
226
226
  if (props.showNotificationOnError && errorMessage) {
227
227
  notifications.push({
228
228
  color: 'danger',
229
- title: i18n.t('sui.error'),
229
+ title: t('sui.error'),
230
230
  text: errorMessage,
231
231
  });
232
232
  }
@@ -248,7 +248,7 @@ function successHandler(response: AxiosResponse<any, any>) {
248
248
  if (props.showNotificationOnSuccess) {
249
249
  notifications.push({
250
250
  color: 'success',
251
- title: i18n.t('sui.success'),
251
+ title: t('sui.success'),
252
252
  text: message,
253
253
  });
254
254
  }
@@ -35,11 +35,6 @@ const attributes = [
35
35
  ];
36
36
 
37
37
  const actions = [
38
- {
39
- label: 'Edit',
40
- to: '/articles',
41
- icon: 'heroicons:pencil-solid',
42
- },
43
38
  {
44
39
  label: 'View',
45
40
  icon: 'heroicons:link-solid',
@@ -47,6 +42,18 @@ const actions = [
47
42
  alert('View');
48
43
  },
49
44
  },
45
+ {
46
+ label: 'Edit',
47
+ to: '/articles',
48
+ icon: 'heroicons:pencil-solid',
49
+ },
50
+ {
51
+ label: 'Publish 2',
52
+ to: '/articles',
53
+ color: 'secondary',
54
+ icon: 'heroicons:check-solid',
55
+ },
56
+
50
57
  {
51
58
  label: 'Publish',
52
59
  to: '/articles',
@@ -61,46 +61,38 @@
61
61
  </div>
62
62
  </div>
63
63
 
64
- <div v-if="compactLayout" class="mt-5 flex gap-2">
65
- <BaseActionItemButton
66
- v-if="primaryAction"
67
- :action="primaryAction"
68
- size="sm"
69
- />
70
-
71
- <BaseMenu
72
- v-if="secondaryActions.length > 1"
73
- :items="secondaryActions"
74
- size="sm"
75
- position="bottom-right"
64
+ <div class="mt-5">
65
+ <div
66
+ class="flex gap-2"
67
+ :class="{
68
+ 'lg:mt-0 lg:ml-4': !compactLayout,
69
+ }"
76
70
  >
77
- <template #button="{ open }">
78
- <div
79
- class="btn btn-sm flex items-center gap-1 hover:border-slate-400"
80
- :class="[open ? 'bg-slate-100' : '']"
81
- >
82
- <span>{{ $t('sui.more') }}</span>
83
- <BaseIcon
84
- icon="heroicons:chevron-down-solid"
85
- class="h-5 w-5 text-slate-500"
86
- />
87
- </div>
88
- </template>
89
- </BaseMenu>
90
- <BaseActionItemButton
91
- v-else-if="secondaryActions.length === 1"
92
- :action="secondaryActions[0]"
93
- size="sm"
94
- />
95
- </div>
96
-
97
- <div v-else class="mt-5 flex gap-2 lg:mt-0 lg:ml-4">
98
- <BaseActionItemButton
99
- v-for="action in actions"
100
- :key="action.label"
101
- :action="action"
102
- size="sm"
103
- />
71
+ <BaseActionItemButton
72
+ v-for="(primaryAction, i) in primaryActions"
73
+ :key="i"
74
+ :action="primaryAction"
75
+ size="sm"
76
+ />
77
+ <BaseMenu
78
+ v-if="secondaryActions.length"
79
+ :items="secondaryActions"
80
+ size="sm"
81
+ :position="compactLayout ? 'bottom-right' : 'bottom-left'"
82
+ >
83
+ <template #button="{ open }">
84
+ <div
85
+ class="btn btn-sm flex items-center rounded-full p-2 hover:border-slate-400"
86
+ :class="[open ? 'bg-slate-100' : '']"
87
+ >
88
+ <BaseIcon
89
+ icon="heroicons-outline:dots-horizontal"
90
+ class="h-4 w-4 text-slate-500"
91
+ />
92
+ </div>
93
+ </template>
94
+ </BaseMenu>
95
+ </div>
104
96
  </div>
105
97
  </div>
106
98
  </div>
@@ -113,6 +105,7 @@ import { BaseBreadcrumbs, BaseIcon } from '..';
113
105
  import BaseActionItemButton from './BaseActionItemButton.vue';
114
106
  import BaseBadge from './BaseBadge.vue';
115
107
  import BaseMenu from './BaseMenu.vue';
108
+ import { cloneDeep } from 'lodash';
116
109
 
117
110
  const props = withDefaults(
118
111
  defineProps<{
@@ -123,6 +116,7 @@ const props = withDefaults(
123
116
  badge?: { icon: string; label: string; color: string };
124
117
  layout?: 'default' | 'compact';
125
118
  breadcrumbs?: Breadcrumb[];
119
+ maxActions?: number;
126
120
  }>(),
127
121
  {
128
122
  subtitle: undefined,
@@ -131,59 +125,64 @@ const props = withDefaults(
131
125
  badge: undefined,
132
126
  layout: 'default',
133
127
  breadcrumbs: undefined,
128
+ maxActions: 3,
134
129
  }
135
130
  );
136
131
 
137
- const primaryActionIndex = computed(() => {
138
- if (!props.actions || props.actions.length === 0) {
139
- return undefined;
140
- }
141
-
142
- if (props.actions?.length === 1) {
143
- return 0;
144
- }
132
+ const baseHeaderRef = ref<HTMLElement | null>(null);
145
133
 
146
- const primaryIndex = props.actions?.findIndex((a) => a.color == 'primary');
134
+ const width = ref(800);
135
+ useResizeObserver(baseHeaderRef, () => {
136
+ width.value = baseHeaderRef.value?.clientWidth ?? 800;
137
+ });
147
138
 
148
- if (primaryIndex !== -1) {
149
- return primaryIndex;
139
+ const compactLayout = computed(() => {
140
+ if (props.layout === 'compact') {
141
+ return true;
150
142
  }
151
-
152
- return 0;
143
+ return width.value < 500;
153
144
  });
154
145
 
155
- const primaryAction = computed(() => {
156
- if (!props.actions) {
157
- return undefined;
146
+ const maxActionsInternal = computed(() => {
147
+ if (compactLayout.value) {
148
+ return 1;
158
149
  }
150
+ return Math.max(1, props.maxActions);
151
+ });
159
152
 
160
- const index = primaryActionIndex.value;
161
-
162
- if (index !== undefined) {
163
- return props.actions[index];
153
+ const primaryActions = computed(() => {
154
+ if (!props.actions) {
155
+ return [];
164
156
  }
165
157
 
166
- return null;
158
+ return cloneDeep(props.actions)
159
+ .sort(sortByColor(false))
160
+ .slice(0, maxActionsInternal.value)
161
+ .sort(sortByColor(true));
167
162
  });
168
163
 
164
+ function sortByColor(reverse = false) {
165
+ const sortingArr = ['secondary', 'primary'];
166
+
167
+ return (a: ActionItem, b: ActionItem) => {
168
+ if (!reverse) {
169
+ return (
170
+ sortingArr.indexOf(b.color ?? '') - sortingArr.indexOf(a.color ?? '')
171
+ );
172
+ }
173
+ return (
174
+ sortingArr.indexOf(a.color ?? '') - sortingArr.indexOf(b.color ?? '')
175
+ );
176
+ };
177
+ }
178
+
169
179
  const secondaryActions = computed(() => {
170
180
  if (!props.actions) {
171
181
  return [];
172
182
  }
173
- return props.actions?.filter((a, i) => i !== primaryActionIndex.value);
174
- });
175
183
 
176
- const baseHeaderRef = ref<HTMLElement | null>(null);
177
-
178
- const width = ref(800);
179
- useResizeObserver(baseHeaderRef, () => {
180
- width.value = baseHeaderRef.value?.clientWidth ?? 800;
181
- });
182
-
183
- const compactLayout = computed(() => {
184
- if (props.layout === 'compact') {
185
- return true;
186
- }
187
- return width.value < 500;
184
+ return props.actions.filter(
185
+ (a) => !primaryActions.value.map((a) => a.label).includes(a.label)
186
+ );
188
187
  });
189
188
  </script>