sprintify-ui 0.0.179 → 0.0.181

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 (31) hide show
  1. package/dist/sprintify-ui.es.js +6896 -6710
  2. package/dist/style.css +1 -1
  3. package/dist/types/src/components/BaseDataIterator.vue.d.ts +69 -14
  4. package/dist/types/src/components/BaseDataIteratorSectionBox.vue.d.ts +23 -0
  5. package/dist/types/src/components/BaseDataIteratorSectionButton.vue.d.ts +20 -0
  6. package/dist/types/src/components/{BaseDataTableToggleColumns.vue.d.ts → BaseDataIteratorSectionColumns.vue.d.ts} +10 -40
  7. package/dist/types/src/components/BaseDataIteratorSectionModal.vue.d.ts +29 -0
  8. package/dist/types/src/components/BaseDataTable.vue.d.ts +65 -11
  9. package/dist/types/src/components/BaseDataTableRowAction.vue.d.ts +18 -0
  10. package/dist/types/src/components/BaseMenu.vue.d.ts +9 -0
  11. package/dist/types/src/components/BaseMenuItem.vue.d.ts +9 -0
  12. package/dist/types/src/components/BaseTable.vue.d.ts +3 -17
  13. package/dist/types/src/index.d.ts +12 -0
  14. package/dist/types/src/types/index.d.ts +15 -0
  15. package/package.json +1 -1
  16. package/src/assets/main.css +23 -0
  17. package/src/components/BaseActionItem.vue +3 -1
  18. package/src/components/BaseDataIterator.vue +135 -81
  19. package/src/components/BaseDataIteratorSectionBox.vue +33 -0
  20. package/src/components/BaseDataIteratorSectionButton.vue +34 -0
  21. package/src/components/{BaseDataTableToggleColumns.vue → BaseDataIteratorSectionColumns.vue} +3 -3
  22. package/src/components/BaseDataIteratorSectionModal.vue +41 -0
  23. package/src/components/BaseDataTable.stories.js +45 -14
  24. package/src/components/BaseDataTable.vue +251 -78
  25. package/src/components/BaseDataTableRowAction.vue +28 -0
  26. package/src/components/BaseMenu.vue +7 -0
  27. package/src/components/BaseMenuItem.vue +17 -1
  28. package/src/components/BaseTable.vue +44 -85
  29. package/src/lang/en.json +3 -0
  30. package/src/lang/fr.json +3 -0
  31. package/src/types/index.ts +17 -0
@@ -8,7 +8,7 @@
8
8
  >
9
9
  <div
10
10
  class="min-w-0"
11
- :class="{ 'col-span-1': !mobileLayout, 'col-span-2': mobileLayout }"
11
+ :class="{ 'col-span-1': !compactLayout, 'col-span-2': compactLayout }"
12
12
  >
13
13
  <!-- Header -->
14
14
  <div class="mb-4 flex space-x-2 empty:mb-0">
@@ -52,19 +52,18 @@
52
52
  </div>
53
53
  </div>
54
54
 
55
- <!-- Filters (mobile) -->
56
- <button
57
- v-if="mobileLayout && hasFilters"
58
- class="btn flex h-11 items-center justify-center py-1 text-base shadow-sm"
59
- type="button"
60
- @click="showFilters = true"
61
- >
62
- <BaseIcon
63
- class="h-6 w-6 text-slate-500 xs:mr-2"
64
- icon="heroicons:adjustments-horizontal-solid"
65
- />
66
- <span class="hidden xs:block">{{ $t('sui.filters') }}</span>
67
- </button>
55
+ <template v-if="compactLayout">
56
+ <template
57
+ v-for="(section, i) in sectionsInternal"
58
+ :key="section.name"
59
+ >
60
+ <BaseDataIteratorSectionButton
61
+ :section="section"
62
+ @open="openSection(i)"
63
+ >
64
+ </BaseDataIteratorSectionButton>
65
+ </template>
66
+ </template>
68
67
 
69
68
  <!-- Menu -->
70
69
  <BaseMenu
@@ -99,8 +98,8 @@
99
98
 
100
99
  <!-- Pagination -->
101
100
 
102
- <div v-if="paginationMetadata" class="mt-5">
103
- <p class="text-center text-xs text-slate-400 sm:text-right">
101
+ <div v-if="paginationMetadata" class="mt-4">
102
+ <p class="text-center text-sm text-slate-400 sm:text-right">
104
103
  {{
105
104
  (paginationMetadata.current_page - 1) *
106
105
  paginationMetadata.per_page +
@@ -128,44 +127,22 @@
128
127
  </div>
129
128
  </div>
130
129
 
131
- <div v-if="!mobileLayout" ref="sidebar">
130
+ <div v-if="!compactLayout" ref="sidebar" class="space-y-3">
132
131
  <slot
133
132
  name="sidebarTop"
134
133
  :pagination-metadata="paginationMetadata"
135
134
  ></slot>
136
135
 
137
- <div v-if="hasFilters" class="mb-4">
138
- <BaseCard>
139
- <BaseCardRow size="sm">
140
- <button
141
- type="button"
142
- class="flex w-full items-center justify-between"
143
- @click="showFilterDesktop = !showFilterDesktop"
144
- >
145
- <h2 class="font-semibold">
146
- {{ $t('sui.filters') }}
147
- </h2>
148
-
149
- <BaseIcon
150
- :icon="
151
- showFilterDesktop
152
- ? 'heroicons:chevron-down'
153
- : 'heroicons:chevron-up'
154
- "
155
- ></BaseIcon>
156
- </button>
157
-
158
- <div v-show="showFilterDesktop" class="mt-4 space-y-3">
159
- <slot
160
- name="filters"
161
- :query="query"
162
- :update-query="updateFilterQuery"
163
- :update-query-value="updateFilterQueryValue"
164
- />
165
- </div>
166
- </BaseCardRow>
167
- </BaseCard>
168
- </div>
136
+ <template v-for="section in sectionsInternal" :key="section.name">
137
+ <BaseDataIteratorSectionBox :section="section">
138
+ <slot
139
+ :name="section.name"
140
+ :query="query"
141
+ :update-query="updateFilterQuery"
142
+ :update-query-value="updateFilterQueryValue"
143
+ />
144
+ </BaseDataIteratorSectionBox>
145
+ </template>
169
146
 
170
147
  <slot
171
148
  name="sidebarBottom"
@@ -174,28 +151,20 @@
174
151
  </div>
175
152
  </div>
176
153
 
177
- <BaseModalSide v-if="hasFilters" v-model="showFilters">
178
- <div class="px-4 py-5">
179
- <h2 class="mb-4 font-semibold">
180
- {{ $t('sui.filters') }}
181
- </h2>
182
-
183
- <div class="space-y-3">
184
- <slot
185
- name="filters"
186
- :query="query"
187
- :update-query="updateFilterQuery"
188
- :update-query-value="updateFilterQueryValue"
189
- />
190
- </div>
191
-
192
- <div>
193
- <button class="btn btn-primary mt-4" @click="showFilters = false">
194
- {{ $t('sui.apply_filters') }}
195
- </button>
196
- </div>
197
- </div>
198
- </BaseModalSide>
154
+ <template v-for="(section, i) in sectionsInternal" :key="section.name">
155
+ <BaseDataIteratorSectionModal
156
+ :section="section"
157
+ :model-value="sectionModalActive == i"
158
+ @update:model-value="closeSection"
159
+ >
160
+ <slot
161
+ :name="section.name"
162
+ :query="query"
163
+ :update-query="updateFilterQuery"
164
+ :update-query-value="updateFilterQueryValue"
165
+ />
166
+ </BaseDataIteratorSectionModal>
167
+ </template>
199
168
  </div>
200
169
  </template>
201
170
 
@@ -218,6 +187,7 @@ import hash from 'object-hash';
218
187
  import { PropType } from 'vue';
219
188
  import {
220
189
  Collection,
190
+ DataIteratorSection,
221
191
  DataTableQuery,
222
192
  MenuItemInterface,
223
193
  PaginatedCollection,
@@ -225,13 +195,13 @@ import {
225
195
  } from '@/types';
226
196
 
227
197
  import BaseMenu from './BaseMenu.vue';
228
- import BaseCard from './BaseCard.vue';
229
- import BaseCardRow from './BaseCardRow.vue';
230
198
  import BasePagination from './BasePagination.vue';
231
- import BaseModalSide from './BaseModalSide.vue';
232
199
  import { config } from '@/index';
233
200
  import { useMutationObserver, useResizeObserver } from '@vueuse/core';
234
201
  import { useHasPaginatedData } from '@/composables/paginatedData';
202
+ import BaseDataIteratorSectionButton from './BaseDataIteratorSectionButton.vue';
203
+ import BaseDataIteratorSectionModal from './BaseDataIteratorSectionModal.vue';
204
+ import BaseDataIteratorSectionBox from './BaseDataIteratorSectionBox.vue';
235
205
 
236
206
  const props = defineProps({
237
207
  /**
@@ -286,17 +256,43 @@ const props = defineProps({
286
256
  default: false,
287
257
  type: Boolean,
288
258
  },
259
+
260
+ /**
261
+ * Layout type
262
+ */
263
+ layout: {
264
+ default: 'default',
265
+ type: String as PropType<'default' | 'compact'>,
266
+ },
267
+
268
+ /**
269
+ * Sections
270
+ */
271
+ sections: {
272
+ default: undefined,
273
+ type: Array as PropType<DataIteratorSection[]>,
274
+ },
275
+
276
+ /**
277
+ * Scroll to top when fetching new data
278
+ */
279
+ scrollTopOnFetch: {
280
+ default: true,
281
+ type: Boolean,
282
+ },
289
283
  });
290
284
 
285
+ const i18n = useI18n();
291
286
  const http = config.http;
292
287
 
293
- defineEmits([
288
+ const emit = defineEmits([
294
289
  'click',
295
290
  'delete',
296
291
  'checkAll',
297
292
  'update:checked-rows',
298
293
  'check',
299
294
  'update:query',
295
+ 'will-scroll-top',
300
296
  ]);
301
297
 
302
298
  const dataIteratorNode = ref<null | HTMLElement>(null);
@@ -322,7 +318,6 @@ useResizeObserver(dataIteratorNode, () => {
322
318
  const firstLoad = ref(false);
323
319
  const loading = ref(true);
324
320
  const error = ref(false);
325
- const showFilters = ref(false);
326
321
  const searchQuery = ref('');
327
322
 
328
323
  let lastUrl = '';
@@ -330,7 +325,10 @@ let lastQueryHash = '';
330
325
  const query = ref<DataTableQuery>(cloneDeep(props.defaultQuery));
331
326
  const slots = useSlots();
332
327
 
333
- const mobileLayout = computed(() => {
328
+ const compactLayout = computed(() => {
329
+ if (props.layout === 'compact') {
330
+ return true;
331
+ }
334
332
  return width.value < 1024;
335
333
  });
336
334
 
@@ -364,7 +362,7 @@ onMounted(() => {
364
362
  });
365
363
 
366
364
  watch(
367
- () => mobileLayout.value,
365
+ () => compactLayout.value,
368
366
  () => {
369
367
  // After the sidebar appears...
370
368
  nextTick(() => {
@@ -373,8 +371,6 @@ watch(
373
371
  }
374
372
  );
375
373
 
376
- const showFilterDesktop = ref(true);
377
-
378
374
  /*
379
375
  |--------------------------------------------------------------------------
380
376
  | Query params
@@ -655,9 +651,20 @@ const searchKeywords = computed((): string => {
655
651
  /** Scroll into view */
656
652
 
657
653
  const scrollIntoView = () => {
654
+ emit('will-scroll-top');
655
+
656
+ if (!props.scrollTopOnFetch) {
657
+ return;
658
+ }
659
+
660
+ scrollIntoViewAction();
661
+ };
662
+
663
+ const scrollIntoViewAction = () => {
658
664
  if (dataIteratorNode.value == null) {
659
665
  return;
660
666
  }
667
+
661
668
  dataIteratorNode.value.scrollIntoView({
662
669
  behavior: 'smooth',
663
670
  });
@@ -685,6 +692,53 @@ onMounted(() => {
685
692
  updateSearchInput();
686
693
  });
687
694
 
695
+ /*
696
+ |--------------------------------------------------------------------------
697
+ | Sections
698
+ |--------------------------------------------------------------------------
699
+ */
700
+
701
+ const sectionModalActive = ref<null | number>(null);
702
+
703
+ const sectionsInternal = computed<DataIteratorSection[]>(() => {
704
+ const sections = props.sections ?? [];
705
+
706
+ if (hasFilters.value) {
707
+ return [
708
+ {
709
+ name: 'filters',
710
+ title: i18n.t('sui.filters'),
711
+ closeText: i18n.t('sui.apply_filters'),
712
+ icon: 'heroicons:adjustments-horizontal-solid',
713
+ opened: true,
714
+ },
715
+ ...sections,
716
+ ];
717
+ }
718
+
719
+ return sections;
720
+ });
721
+
722
+ function openSection(index: number) {
723
+ if (sectionsInternal.value[index]) {
724
+ sectionModalActive.value = index;
725
+ } else {
726
+ sectionModalActive.value = null;
727
+ }
728
+ }
729
+
730
+ function closeSection() {
731
+ sectionModalActive.value = null;
732
+ }
733
+
734
+ /*
735
+ |--------------------------------------------------------------------------
736
+ | Provide
737
+ |--------------------------------------------------------------------------
738
+ */
739
+
740
+ provide('dataIterator:width', width);
741
+
688
742
  /*
689
743
  |--------------------------------------------------------------------------
690
744
  | Exposed API
@@ -695,7 +749,7 @@ defineExpose({
695
749
  fetch: () => fetch(true),
696
750
  fetchWithLoading: () => fetchWithLoading(true),
697
751
  fetchWithoutLoading: () => fetchWithoutLoading(true),
698
- scrollIntoView,
752
+ scrollIntoView: scrollIntoViewAction,
699
753
  query,
700
754
  });
701
755
  </script>
@@ -0,0 +1,33 @@
1
+ <template>
2
+ <BaseCard>
3
+ <button
4
+ type="button"
5
+ class="flex w-full items-center justify-between px-4 py-3"
6
+ @click="active = !active"
7
+ >
8
+ <h2 class="text-sm font-semibold">
9
+ {{ section.title }}
10
+ </h2>
11
+
12
+ <BaseIcon
13
+ class="h-5 w-5 text-slate-500 duration-300"
14
+ :class="active ? 'rotate-90 transform' : ''"
15
+ icon="heroicons:chevron-right-20-solid"
16
+ ></BaseIcon>
17
+ </button>
18
+ <div v-show="active" class="border-t border-slate-300 py-5 px-4">
19
+ <slot />
20
+ </div>
21
+ </BaseCard>
22
+ </template>
23
+
24
+ <script lang="ts" setup>
25
+ import { DataIteratorSection } from '@/types';
26
+ import { BaseCard, BaseIcon } from '.';
27
+
28
+ const props = defineProps<{
29
+ section: DataIteratorSection;
30
+ }>();
31
+
32
+ const active = ref<boolean>(props.section.opened ?? false);
33
+ </script>
@@ -0,0 +1,34 @@
1
+ <template>
2
+ <button
3
+ class="btn flex h-11 items-center justify-center py-1 text-base shadow-sm"
4
+ :class="[width > 600 ? 'px-4' : 'px-3.5']"
5
+ type="button"
6
+ @click="open()"
7
+ >
8
+ <BaseIcon class="h-5 w-5 text-slate-500" :icon="section.icon" />
9
+ <span
10
+ v-if="section.title && width > 600"
11
+ class="ml-2 whitespace-pre text-sm"
12
+ >{{ section.title }}</span
13
+ >
14
+ </button>
15
+ </template>
16
+
17
+ <script lang="ts" setup>
18
+ import { DataIteratorSection } from '@/types';
19
+ import { BaseIcon } from '.';
20
+
21
+ defineProps<{
22
+ section: DataIteratorSection;
23
+ }>();
24
+
25
+ const emit = defineEmits<{
26
+ (event: 'open'): void;
27
+ }>();
28
+
29
+ const width = inject('dataIterator:width', ref(0));
30
+
31
+ function open() {
32
+ emit('open');
33
+ }
34
+ </script>
@@ -1,15 +1,15 @@
1
1
  <template>
2
2
  <ul>
3
3
  <li v-for="col in toggleableColumns" :key="col.newKey">
4
- <label>
4
+ <label class="cursor-pointer">
5
5
  <input
6
6
  :checked="visibleColumns.includes(col.newKey)"
7
7
  type="checkbox"
8
- class="mr-1.5 h-3.5 w-3.5 rounded focus:ring-1 focus:ring-primary-300 focus:ring-offset-1"
8
+ class="mr-2 h-3.5 w-3.5 cursor-pointer rounded focus:ring-1 focus:ring-primary-300 focus:ring-offset-1"
9
9
  :value="col.newKey"
10
10
  @change="onVisibleColumnChange($event, col.newKey)"
11
11
  />
12
- <span class="text-xs text-slate-600">
12
+ <span class="text-xs text-slate-700">
13
13
  {{ col.label }}
14
14
  </span>
15
15
  </label>
@@ -0,0 +1,41 @@
1
+ <template>
2
+ <BaseModalSide
3
+ :model-value="modelValue"
4
+ @update:model-value="emit('update:modelValue', $event)"
5
+ >
6
+ <div>
7
+ <div class="border-b border-slate-300 p-4">
8
+ <h2 class="text-base font-semibold">
9
+ {{ section.title }}
10
+ </h2>
11
+ </div>
12
+
13
+ <div class="py-5 px-4">
14
+ <slot />
15
+ </div>
16
+
17
+ <div class="border-t border-slate-300 p-4">
18
+ <button
19
+ class="btn btn-primary w-full sm:w-auto"
20
+ @click="emit('update:modelValue', false)"
21
+ >
22
+ {{ section.closeText }}
23
+ </button>
24
+ </div>
25
+ </div>
26
+ </BaseModalSide>
27
+ </template>
28
+
29
+ <script lang="ts" setup>
30
+ import { DataIteratorSection } from '@/types';
31
+ import BaseModalSide from './BaseModalSide.vue';
32
+
33
+ defineProps<{
34
+ modelValue: boolean;
35
+ section: DataIteratorSection;
36
+ }>();
37
+
38
+ const emit = defineEmits<{
39
+ (event: 'update:modelValue', value: boolean): void;
40
+ }>();
41
+ </script>
@@ -9,7 +9,14 @@ import BaseAppDialogs from './BaseAppDialogs.vue';
9
9
  export default {
10
10
  title: 'Data/BaseDataTable',
11
11
  component: BaseDataTable,
12
- argTypes: {},
12
+ argTypes: {
13
+ layout: {
14
+ control: {
15
+ type: 'select',
16
+ options: ['default', 'compact'],
17
+ },
18
+ },
19
+ },
13
20
  args: {
14
21
  url: 'https://effettandem.com/api/content/articles',
15
22
  urlQuery: {
@@ -99,6 +106,12 @@ const template = `
99
106
  </div>
100
107
  </template>
101
108
 
109
+ <template #test>
110
+ <div>
111
+ Section Test
112
+ </div>
113
+ </template>
114
+
102
115
  <template #filters="{ query, updateQueryValue }">
103
116
  <div class="space-y-3">
104
117
  <div>
@@ -107,7 +120,7 @@ const template = `
107
120
  </p>
108
121
  <BaseSelect
109
122
  :model-value="query.type ?? null"
110
- class="w-full rounded border-slate-300"
123
+ class="w-full rounded border-slate-300 text-base sm:text-sm"
111
124
  placeholder="-"
112
125
  @update:model-value="updateQueryValue('type', $event)"
113
126
  >
@@ -125,7 +138,7 @@ const template = `
125
138
  </p>
126
139
  <BaseSelect
127
140
  :model-value="query.access_level ?? null"
128
- class="w-full rounded border-slate-300"
141
+ class="w-full rounded border-slate-300 text-base sm:text-sm"
129
142
  placeholder="-"
130
143
  @update:model-value="updateQueryValue('access_level', $event)"
131
144
  >
@@ -170,7 +183,19 @@ const Template = (args) => ({
170
183
  export const Demo = Template.bind({});
171
184
 
172
185
  Demo.args = {
186
+ showUrl() {
187
+ return '/';
188
+ },
189
+ editUrl() {
190
+ return '/';
191
+ },
192
+ deleteUrl() {
193
+ return '/';
194
+ },
195
+ detailed: true,
173
196
  searchable: true,
197
+ maxHeight: 300,
198
+ checkable: true,
174
199
  actions: [
175
200
  {
176
201
  label: 'Open Google',
@@ -190,7 +215,6 @@ Demo.args = {
190
215
  color: 'danger',
191
216
  },
192
217
  ],
193
- checkable: true,
194
218
  checkableActions: [
195
219
  {
196
220
  label: 'Delete all',
@@ -199,16 +223,23 @@ Demo.args = {
199
223
  },
200
224
  },
201
225
  ],
202
- detailed: true,
203
- showUrl() {
204
- return '/';
205
- },
206
- editUrl() {
207
- return '/';
208
- },
209
- deleteUrl() {
210
- return '/';
211
- },
226
+ rowActions: [
227
+ {
228
+ label: 'Open Google',
229
+ icon: 'heroicons:link',
230
+ action() {
231
+ alert('hit');
232
+ },
233
+ },
234
+ ],
235
+ sections: [
236
+ {
237
+ name: 'test',
238
+ title: 'Section Test',
239
+ icon: 'heroicons:link',
240
+ closeText: 'Close',
241
+ },
242
+ ],
212
243
  };
213
244
 
214
245
  const SimpleTemplate = (args) => ({