sprintify-ui 0.2.23 → 0.2.25

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.
@@ -20,6 +20,7 @@ declare const messages: {
20
20
  apply_filters: string;
21
21
  authentication_code: string;
22
22
  autocomplete_placeholder: string;
23
+ bulk_actions: string;
23
24
  cancel: string;
24
25
  city: string;
25
26
  clear: string;
@@ -108,6 +109,7 @@ declare const messages: {
108
109
  apply_filters: string;
109
110
  authentication_code: string;
110
111
  autocomplete_placeholder: string;
112
+ bulk_actions: string;
111
113
  cancel: string;
112
114
  city: string;
113
115
  clear: string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sprintify-ui",
3
- "version": "0.2.23",
3
+ "version": "0.2.25",
4
4
  "scripts": {
5
5
  "build": "rimraf dist && vue-tsc && vite build",
6
6
  "build-fast": "rimraf dist && vite build",
@@ -25,6 +25,7 @@ const props = withDefaults(
25
25
  size?: 'sm' | 'base' | 'lg';
26
26
  icon?: string;
27
27
  wrap?: boolean;
28
+ bordered?: boolean;
28
29
  }>(),
29
30
  {
30
31
  contrast: 'high',
@@ -37,10 +38,18 @@ const props = withDefaults(
37
38
  const colorStyle = computed((): Record<string, string> => {
38
39
  const config = getColorConfig(props.color, props.contrast == 'high');
39
40
 
40
- return {
41
+ const styles: Record<string, string> = {
41
42
  backgroundColor: config.backgroundColor,
42
43
  color: config.textColor,
43
44
  };
45
+
46
+ if (props.bordered) {
47
+ styles.borderColor = config.borderColor;
48
+ styles.borderWidth = '1px';
49
+ styles.borderStyle = 'solid';
50
+ }
51
+
52
+ return styles;
44
53
  });
45
54
 
46
55
  const wrapClasses = computed(() => {
@@ -11,18 +11,34 @@
11
11
  :class="{ 'col-span-1': !compactLayout, 'col-span-2': compactLayout }"
12
12
  >
13
13
  <!-- Header -->
14
- <div class="mb-4 flex space-x-2 empty:mb-0">
14
+ <div
15
+ class="flex space-x-2 empty:mb-0"
16
+ :class="{
17
+ 'mb-2.5': size == 'sm',
18
+ 'mb-4': size == 'md',
19
+ }"
20
+ >
15
21
  <!-- Search bar -->
16
22
  <div
17
23
  v-if="searchable"
18
24
  class="grow"
19
25
  >
20
- <div class="relative h-11">
26
+ <div
27
+ class="relative"
28
+ :class="{
29
+ 'h-9': size == 'sm',
30
+ 'h-11': size == 'md',
31
+ }"
32
+ >
21
33
  <div
22
34
  class="pointer-events-none absolute left-0 top-0 flex h-full items-center justify-center pl-2.5"
23
35
  >
24
36
  <BaseIcon
25
- class="h-6 w-6 text-slate-400"
37
+ class="text-slate-400"
38
+ :class="{
39
+ 'h-5 w-5': size == 'sm',
40
+ 'h-6 w-6': size == 'md',
41
+ }"
26
42
  icon="heroicons:magnifying-glass"
27
43
  />
28
44
  </div>
@@ -30,7 +46,11 @@
30
46
  ref="searchInput"
31
47
  v-model="searchQuery"
32
48
  type="text"
33
- class="h-11 w-full overflow-hidden rounded-md border border-slate-300 bg-white pl-10 pr-9 shadow-sm"
49
+ class="w-full h-full py-0 overflow-hidden rounded-md border border-slate-300 bg-white shadow-sm"
50
+ :class="{
51
+ 'pl-9 pr-8': size == 'sm',
52
+ 'pl-10 pr-9': size == 'md',
53
+ }"
34
54
  :placeholder="t('sui.autocomplete_placeholder')"
35
55
  @input="onSearch"
36
56
  >
@@ -41,13 +61,14 @@
41
61
  <button
42
62
  type="button"
43
63
  class="flex appearance-none items-center rounded p-1 hover:bg-slate-100"
44
- @click="
45
- searchQuery = '';
46
- onSearch('');
47
- "
64
+ @click="searchQuery = ''; onSearch('');"
48
65
  >
49
66
  <BaseIcon
50
- class="h-6 w-6 text-slate-500"
67
+ class="text-slate-500"
68
+ :class="{
69
+ 'h-5 w-5': size == 'sm',
70
+ 'h-6 w-6': size == 'md',
71
+ }"
51
72
  icon="heroicons:x-mark"
52
73
  />
53
74
  </button>
@@ -62,6 +83,7 @@
62
83
  >
63
84
  <BaseDataIteratorSectionButton
64
85
  :section="section"
86
+ :size="size"
65
87
  @open="openSection(i)"
66
88
  />
67
89
  </template>
@@ -71,13 +93,18 @@
71
93
  <BaseMenu
72
94
  v-if="actions && actions.length"
73
95
  tw-menu="w-52"
96
+ :size="size == 'sm' ? 'xs' : 'sm'"
74
97
  :items="actions"
75
98
  >
76
99
  <template #button="{ open }">
77
100
  <div
78
- class="flex h-11 w-11 items-center justify-center rounded-md border border-slate-300 bg-white shadow-sm duration-150 hover:bg-slate-50"
101
+ class="flex items-center justify-center rounded-md border border-slate-300 bg-white shadow-sm duration-150 hover:bg-slate-50"
79
102
  :class="[
80
103
  open ? 'ring-2 ring-primary-500 ring-offset-2' : false,
104
+ {
105
+ 'h-9 w-9': size == 'sm',
106
+ 'h-11 w-11': size == 'md',
107
+ }
81
108
  ]"
82
109
  >
83
110
  <BaseIcon icon="heroicons-solid:dots-vertical" />
@@ -277,6 +304,14 @@ const props = defineProps({
277
304
  type: String as PropType<'default' | 'compact'>,
278
305
  },
279
306
 
307
+ /**
308
+ * Overall size and spacing of the component
309
+ */
310
+ size: {
311
+ default: 'md',
312
+ type: String as PropType<'sm' | 'md'>,
313
+ },
314
+
280
315
  /**
281
316
  * Sections
282
317
  */
@@ -334,6 +369,8 @@ const searchQuery = ref('');
334
369
 
335
370
  let lastUrl = '';
336
371
  let lastQueryHash = '';
372
+
373
+ // eslint-disable-next-line vue/no-setup-props-destructure
337
374
  const query = ref<DataTableQuery>(cloneDeep(props.defaultQuery));
338
375
  const slots = useSlots();
339
376
 
@@ -705,7 +742,7 @@ const paginationStart = computed(() => {
705
742
 
706
743
  return (
707
744
  paginationMetadata.value.per_page *
708
- (paginationMetadata.value.current_page - 1) +
745
+ (paginationMetadata.value.current_page - 1) +
709
746
  1
710
747
  );
711
748
  });
@@ -756,7 +793,7 @@ const sectionsInternal = computed<DataIteratorSection[]>(() => {
756
793
  name: 'filters',
757
794
  title: t('sui.filters'),
758
795
  closeText: t('sui.apply_filters'),
759
- icon: 'heroicons:adjustments-horizontal-solid',
796
+ icon: 'heroicons:adjustments-horizontal',
760
797
  opened: true,
761
798
  },
762
799
  ...sections,
@@ -1,7 +1,12 @@
1
1
  <template>
2
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']"
3
+ class="btn flex items-center justify-center text-base shadow-sm"
4
+ :class="[
5
+ width > 600 ? 'px-4' : 'px-3.5',
6
+ {
7
+ 'h-9': size == 'sm',
8
+ 'h-11': size == 'md',
9
+ }]"
5
10
  type="button"
6
11
  @click="open()"
7
12
  >
@@ -12,12 +17,17 @@
12
17
  <span
13
18
  v-if="section.title && width > 600"
14
19
  class="ml-2 whitespace-pre text-sm"
15
- >{{ section.title }}</span>
20
+ >
21
+ {{ section.title }}
22
+ </span>
16
23
  <BaseBadge
17
24
  v-if="section.count"
18
25
  class="ml-2"
19
- :label="section.count"
20
- />
26
+ color="blue"
27
+ :size="size == 'sm' ? 'sm' : 'base'"
28
+ >
29
+ {{ section.count }}
30
+ </BaseBadge>
21
31
  </button>
22
32
  </template>
23
33
 
@@ -28,6 +38,7 @@ import BaseBadge from './BaseBadge.vue';
28
38
 
29
39
  defineProps<{
30
40
  section: DataIteratorSection;
41
+ size: 'sm' | 'md';
31
42
  }>();
32
43
 
33
44
  const emit = defineEmits<{
@@ -6,17 +6,20 @@ import BaseBadge from './BaseBadge.vue';
6
6
  import BaseAppNotifications from './BaseAppNotifications.vue';
7
7
  import BaseAppDialogs from './BaseAppDialogs.vue';
8
8
  import { onBeforeUnmount } from 'vue';
9
+ import { DateTime } from 'luxon';
9
10
 
10
11
  export default {
11
12
  title: 'Data/BaseDataTable',
12
13
  component: BaseDataTable,
13
14
  argTypes: {
14
15
  layout: {
15
- control: {
16
- type: 'select',
17
- options: ['default', 'compact'],
18
- },
16
+ control: { type: 'select' },
17
+ options: ['default', 'compact'],
19
18
  },
19
+ size: {
20
+ control: { type: 'select' },
21
+ options: ['sm', 'md'],
22
+ }
20
23
  },
21
24
  args: {
22
25
  url: 'https://effettandem.com/api/content/articles',
@@ -48,23 +51,36 @@ const template = `
48
51
  field="title->en"
49
52
  :toggle="false"
50
53
  >
51
- <div class="max-w-[200px]">
52
- <div class="font-medium text-slate-900 leading-tight truncate">
53
- {{ row.title }}
54
- </div>
55
- <p class="text-xs leading-tight mt-1 text-slate-500 line-clamp-2">
56
- {{ row.subtitle }}
57
- </p>
54
+ <div class="font-medium text-slate-900 leading-tight truncate">
55
+ {{ row.title.slice(0, 30) }}...
58
56
  </div>
59
57
  </BaseTableColumn>
60
58
 
61
59
  <BaseTableColumn
62
60
  v-slot="{ row }"
63
- label="VIP"
61
+ label="URL"
62
+ field="website_url"
63
+ sortable
64
+ >
65
+ <a :href="row.website_url" target="_blank" class="btn btn-xs whitespace-pre space-x-1.5 shadow-sm">
66
+ <span class="whitespace-pre">Open website</span>
67
+ <BaseIcon icon="heroicons:arrow-top-right-on-square-20-solid" class="w-4 h-4 inline-block" />
68
+ </a>
69
+ </BaseTableColumn>
70
+
71
+ <BaseTableColumn
72
+ v-slot="{ row }"
73
+ label="Access Level"
64
74
  field="access_level"
65
75
  sortable
66
76
  >
67
- <BaseBoolean :model-value="row.access_level == 'vip'" />
77
+ <BaseBadge
78
+ contrast="low"
79
+ :color="row.access_level == 'public' ? 'green' : 'gray'"
80
+ bordered
81
+ >
82
+ {{ row.access_level }}
83
+ </BaseBadge>
68
84
  </BaseTableColumn>
69
85
 
70
86
  <BaseTableColumn
@@ -76,9 +92,9 @@ const template = `
76
92
  :toggle="true"
77
93
  :toggle-default="false"
78
94
  >
79
- <div class="whitespace-pre">
95
+ <BaseBadge color="blue" contrast="low" bordered>
80
96
  {{ Math.round(row.votes_avg_score) }} / 5
81
- </div>
97
+ </BaseBadge>
82
98
  </BaseTableColumn>
83
99
 
84
100
  <BaseTableColumn
@@ -90,37 +106,34 @@ const template = `
90
106
  :toggle="true"
91
107
  sortable
92
108
  >
93
- <BaseBadge>
94
- <span class="whitespace-pre">{{ row.owner?.name }}</span>
95
- </BaseBadge>
109
+ <span class="text-slate-600 whitespace-pre">{{ row.owner?.name }}</span>
96
110
  </BaseTableColumn>
97
111
 
98
112
  <BaseTableColumn
99
113
  v-slot="{ row }"
100
- label="Select"
101
- :clickable="false"
102
- padding="0px"
114
+ label="Created at"
115
+ field="created_at"
116
+ sortable
103
117
  >
104
- <select class="select border border-slate-300 rounded text-xs">
105
- <option>Option 1</option>
106
- </select>
118
+ <p class="text-slate-600 text-sm">{{ DateTime.fromISO(row.created_at).toLocaleString() }}</p>
107
119
  </BaseTableColumn>
108
120
 
109
121
  <BaseTableColumn
110
122
  v-slot="{ row }"
111
- label="Access level"
112
- field="access_level"
113
- sortable
123
+ label="Select"
124
+ :clickable="false"
125
+ padding="0px"
114
126
  >
115
- <BaseBadge>
116
- {{ row.access_level }}
117
- </BaseBadge>
127
+ <select class="select border-none text-sm rounded text-slate-600 focus:z-10 focus:ring-2 bg-transparent">
128
+ <option>Option 1</option>
129
+ <option>Option 2</option>
130
+ </select>
118
131
  </BaseTableColumn>
119
132
 
120
133
  <template #detail="{ row }">
121
- <div class="p-4 text-sm">
122
- <p>ID : {{ row.id }}</p>
123
- <p>Created at : {{ row.created_at }}</p>
134
+ <div class="p-4 text-xs">
135
+ <p>ID : <span class="font-mono">{{ row.id }}</span></p>
136
+ <p>Created at : <span class="font-mono">{{ row.created_at }}</span></p>
124
137
  </div>
125
138
  </template>
126
139
 
@@ -189,7 +202,7 @@ const templateSetup = (args) => {
189
202
  lastFetch.value = new Date();
190
203
  }
191
204
 
192
- return { args, onCellClick, ownerIsVisible: true, onFetch, lastFetch };
205
+ return { args, onCellClick, ownerIsVisible: true, onFetch, lastFetch, DateTime };
193
206
  };
194
207
 
195
208
  const Template = (args) => ({
@@ -219,12 +232,12 @@ Demo.args = {
219
232
  actions: [
220
233
  {
221
234
  label: 'Open Google',
222
- icon: 'heroicons:link',
235
+ icon: 'heroicons:link-20-solid',
223
236
  href: 'https://google.com',
224
237
  },
225
238
  {
226
239
  label: 'Export',
227
- icon: 'heroicons:table-cells',
240
+ icon: 'heroicons:arrow-down-tray',
228
241
  action: () => alert('export!'),
229
242
  },
230
243
  { line: true },
@@ -262,6 +275,21 @@ Demo.args = {
262
275
  ],
263
276
  };
264
277
 
278
+ export const Small = Template.bind({});
279
+ Small.args = {
280
+ editUrl() {
281
+ return '/';
282
+ },
283
+ deleteUrl() {
284
+ return '/';
285
+ },
286
+ size: 'sm',
287
+ searchable: true,
288
+ toggleable: true,
289
+ actions: [],
290
+ };
291
+
292
+
265
293
  const VIfOnBaseTableColumn = (args) => ({
266
294
  components: templateComponents,
267
295
  setup() {
@@ -338,4 +366,4 @@ Simple.args = {
338
366
  searchable: false,
339
367
  toggleable: false,
340
368
  actions: [],
341
- };
369
+ };
@@ -8,6 +8,7 @@
8
8
  :actions="actions"
9
9
  :history-mode="historyMode"
10
10
  :layout="layout"
11
+ :size="size"
11
12
  :sections="sectionsInternal"
12
13
  :scroll-top-on-fetch="maxHeight ? false : scrollTopOnFetch"
13
14
  @fetch="onFetch"
@@ -40,7 +41,7 @@
40
41
  }}.</span>
41
42
  <button
42
43
  type="button"
43
- class="mr-3 inline text-slate-700 underline"
44
+ class="mr-3 inline text-slate-800 underline underline-offset-1 decoration-slate-400 decoration-2 decoration-dashed"
44
45
  @click="uncheckAll()"
45
46
  >
46
47
  {{ t('sui.deselect_all') }}
@@ -53,12 +54,19 @@
53
54
  >
54
55
  <template #button="{ open }">
55
56
  <div
56
- class="flex h-10 w-10 items-center justify-center rounded-full border border-slate-300 bg-white duration-150 hover:bg-slate-50"
57
+ class="flex h-10 px-4 items-center justify-center btn pl-3"
57
58
  :class="[
58
59
  open ? 'ring-2 ring-primary-500 ring-offset-2' : false,
59
60
  ]"
60
61
  >
61
- <BaseIcon icon="heroicons-solid:dots-vertical" />
62
+ <BaseIcon
63
+ icon="heroicons-solid:dots-vertical"
64
+ class="mr-2"
65
+ />
66
+
67
+ <span class="font-semibold">
68
+ {{ t('sui.bulk_actions') }}
69
+ </span>
62
70
  </div>
63
71
  </template>
64
72
  </BaseMenu>
@@ -79,6 +87,7 @@
79
87
  :sort-direction="sortDirection"
80
88
  :max-height="maxHeight"
81
89
  :visible-columns="visibleColumns"
90
+ :size="size"
82
91
  @update:checked-rows="onCheckedRowsUpdate"
83
92
  @sort="onSortChange"
84
93
  @cell-click="onCellClick"
@@ -103,16 +112,28 @@
103
112
  <BaseDataTableRowAction
104
113
  :row="row"
105
114
  :row-action="rowAction"
115
+ :size="size"
106
116
  />
107
117
  </template>
108
118
  <BaseMenu
109
119
  v-if="showRowActionMenu"
110
120
  :items="rowActionMenuItems(row)"
111
121
  size="sm"
112
- button-class="btn flex h-8 w-8 p-0 items-center justify-center"
122
+ :tw-button="[
123
+ 'btn flex p-0 items-center justify-center',
124
+ {
125
+ 'py-1.5 px-2': size == 'sm',
126
+ 'p-2': size == 'md'
127
+ }
128
+ ]"
113
129
  >
114
130
  <template #button>
115
- <BaseIcon icon="heroicons-solid:dots-vertical" />
131
+ <BaseIcon
132
+ icon="heroicons-solid:dots-vertical"
133
+ :class="{
134
+ 'h-3 w-3': size == 'sm'
135
+ }"
136
+ />
116
137
  </template>
117
138
  </BaseMenu>
118
139
  </div>
@@ -416,12 +437,23 @@ const props = defineProps({
416
437
 
417
438
  /**
418
439
  * Layout type
440
+ *
441
+ * default: Layout with a sidebar
442
+ * compact: Layout without a sidebar, this is the default layout for mobile, even if you don't specify it
419
443
  */
420
444
  layout: {
421
445
  default: 'default',
422
446
  type: String as PropType<'default' | 'compact'>,
423
447
  },
424
448
 
449
+ /**
450
+ * Overall size and spacing of the component
451
+ */
452
+ size: {
453
+ default: 'md',
454
+ type: String as PropType<'sm' | 'md'>,
455
+ },
456
+
425
457
  sections: {
426
458
  default: undefined,
427
459
  type: Array as PropType<DataIteratorSection[]>,
@@ -665,7 +697,7 @@ const sectionsInternal = computed<DataIteratorSection[]>(() => {
665
697
  ...sections,
666
698
  {
667
699
  name: 'columns',
668
- icon: 'heroicons:table-cells-20-solid',
700
+ icon: 'heroicons:view-columns',
669
701
  title: t('sui.columns'),
670
702
  closeText: t('sui.apply'),
671
703
  opened: false,
@@ -2,33 +2,52 @@
2
2
  <button
3
3
  v-if="rowAction.action"
4
4
  type="button"
5
- class="btn btn-white border border-slate-300 p-2 shadow-sm"
5
+ class="btn btn-white border border-slate-300 shadow-sm"
6
+ :class="{
7
+ 'py-1.5 px-2': size === 'sm',
8
+ 'p-2': size === 'md',
9
+ }"
6
10
  :disabled="rowAction.disabled && rowAction.disabled(row)"
7
11
  @click="rowAction.action ? rowAction.action(row) : null"
8
12
  >
9
13
  <BaseIcon
10
14
  :icon="rowAction.icon"
11
15
  class="text-slate-500"
16
+ :class="{
17
+ 'h-3 w-3': size == 'sm'
18
+ }"
12
19
  />
13
20
  </button>
14
21
  <RouterLink
15
22
  v-else-if="rowAction.to"
16
23
  :to="rowAction.to(row)"
17
24
  :disabled="rowAction.disabled && rowAction.disabled(row)"
18
- class="btn btn-white border border-slate-300 p-2 shadow-sm hover:bg-slate-100"
25
+ class="btn btn-white border border-slate-300 shadow-sm hover:bg-slate-100"
26
+ :class="{
27
+ 'py-1.5 px-2': size === 'sm',
28
+ 'p-2': size === 'md',
29
+ }"
19
30
  >
20
31
  <BaseIcon
21
32
  :icon="rowAction.icon"
22
33
  class="text-slate-500"
34
+ :class="{
35
+ 'h-3 w-3': size == 'sm'
36
+ }"
23
37
  />
24
38
  </RouterLink>
25
39
  </template>
26
40
 
27
41
  <script lang="ts" setup>
28
42
  import { CollectionItem, RowAction } from '@/types';
43
+ import { BaseIcon } from '.';
29
44
 
30
- defineProps<{
45
+ withDefaults(defineProps<{
31
46
  row: CollectionItem;
32
47
  rowAction: RowAction;
33
- }>();
48
+ size?: 'sm' | 'md';
49
+ }>(), {
50
+ size: 'md',
51
+ });
52
+
34
53
  </script>
@@ -13,6 +13,10 @@ export default {
13
13
  control: { type: 'select' },
14
14
  options: ['bottom-left', 'bottom-right'],
15
15
  },
16
+ size: {
17
+ control: { type: 'select' },
18
+ options: ['xs', 'sm', 'md'],
19
+ }
16
20
  },
17
21
  };
18
22