sprintify-ui 0.0.12 → 0.0.14

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 (56) hide show
  1. package/README.md +8 -7
  2. package/dist/sprintify-ui.es.js +4429 -3588
  3. package/dist/style.css +1 -1
  4. package/dist/tailwindcss/index.js +13 -4
  5. package/dist/types/src/components/BaseAutocomplete.vue.d.ts +8 -5
  6. package/dist/types/src/components/BaseAutocompleteFetch.vue.d.ts +8 -5
  7. package/dist/types/src/components/BaseBelongsTo.vue.d.ts +8 -5
  8. package/dist/types/src/components/BaseCharacterCounter.vue.d.ts +4 -4
  9. package/dist/types/src/components/BaseDatePicker.vue.d.ts +8 -5
  10. package/dist/types/src/components/BaseHasMany.vue.d.ts +277 -0
  11. package/dist/types/src/components/BaseInput.vue.d.ts +39 -1
  12. package/dist/types/src/components/{BaseMediaLibraryItem.vue.d.ts → BaseMediaItem.vue.d.ts} +26 -4
  13. package/dist/types/src/components/BaseMediaLibrary.vue.d.ts +23 -15
  14. package/dist/types/src/components/BaseMediaPreview.vue.d.ts +97 -0
  15. package/dist/types/src/components/BaseSelect.vue.d.ts +7 -7
  16. package/dist/types/src/components/BaseSideNavigationItem.vue.d.ts +20 -1
  17. package/dist/types/src/components/BaseTabItem.vue.d.ts +20 -1
  18. package/dist/types/src/components/BaseTagAutocomplete.vue.d.ts +25 -17
  19. package/dist/types/src/components/BaseTagAutocompleteFetch.vue.d.ts +37 -21
  20. package/dist/types/src/components/BaseTextarea.vue.d.ts +8 -5
  21. package/dist/types/src/components/index.d.ts +10 -4
  22. package/package.json +1 -1
  23. package/src/components/BaseAppDialogs.vue +2 -2
  24. package/src/components/BaseAppNotifications.vue +1 -1
  25. package/src/components/BaseAutocomplete.vue +18 -20
  26. package/src/components/BaseAutocompleteFetch.vue +2 -2
  27. package/src/components/BaseBelongsTo.vue +3 -2
  28. package/src/components/BaseClipboard.vue +1 -1
  29. package/src/components/BaseDatePicker.vue +2 -2
  30. package/src/components/BaseHasMany.vue +92 -0
  31. package/src/components/BaseInput.stories.js +20 -1
  32. package/src/components/BaseInput.vue +42 -14
  33. package/src/components/BaseMediaItem.stories.js +41 -0
  34. package/src/components/BaseMediaItem.vue +71 -0
  35. package/src/components/BaseMediaLibrary.stories.js +80 -0
  36. package/src/components/BaseMediaLibrary.vue +67 -68
  37. package/src/components/BaseMediaPreview.stories.js +72 -0
  38. package/src/components/BaseMediaPreview.vue +90 -0
  39. package/src/components/BaseMenu.vue +1 -1
  40. package/src/components/BaseSelect.vue +1 -1
  41. package/src/components/BaseSideNavigationItem.vue +11 -3
  42. package/src/components/BaseSystemAlert.vue +1 -1
  43. package/src/components/BaseTabItem.vue +13 -3
  44. package/src/components/BaseTable.vue +2 -2
  45. package/src/components/BaseTagAutocomplete.stories.js +129 -0
  46. package/src/components/BaseTagAutocomplete.vue +155 -57
  47. package/src/components/BaseTagAutocompleteFetch.stories.js +130 -0
  48. package/src/components/BaseTagAutocompleteFetch.vue +36 -25
  49. package/src/components/BaseTextarea.vue +2 -2
  50. package/src/components/HasMany.stories.js +135 -0
  51. package/src/components/index.ts +18 -6
  52. package/src/lang/en.json +1 -1
  53. package/src/lang/fr.json +1 -1
  54. package/dist/types/src/components/BasePaginationSimple.vue.d.ts +0 -25
  55. package/src/components/BaseMediaLibraryItem.vue +0 -92
  56. package/src/components/BasePaginationSimple.vue +0 -60
@@ -1,6 +1,6 @@
1
1
  <template>
2
2
  <BaseTagAutocomplete
3
- :loading="loading && page == 1"
3
+ :loading="showLoading && page == 1"
4
4
  :model-value="modelValue"
5
5
  :disabled="disabled"
6
6
  :placeholder="placeholder"
@@ -8,22 +8,30 @@
8
8
  :value-key="valueKey"
9
9
  :label-key="labelKey"
10
10
  :input-class="inputClass"
11
- :min="min"
12
11
  :max="max"
12
+ :filter="() => true"
13
13
  @focus="onFocus"
14
14
  @typing="onTyping"
15
15
  @scroll-bottom="scrollBottom"
16
16
  @update:model-value="$emit('update:modelValue', $event)"
17
17
  >
18
- <template #empty>
19
- <div class="p-5 text-center text-slate-600">
20
- <span v-if="firstSearch">
18
+ <template #option="optionProps">
19
+ <slot name="option" v-bind="optionProps" />
20
+ </template>
21
+
22
+ <template #footer="footerProps">
23
+ <slot name="footer" v-bind="footerProps" :keywords="keywords"> </slot>
24
+ </template>
25
+
26
+ <template #empty="emptyProps">
27
+ <slot name="empty" v-bind="emptyProps" :first-search="firstSearch">
28
+ <div
29
+ v-if="firstSearch"
30
+ class="flex h-[80px] items-center justify-center px-3 text-center text-base leading-tight text-slate-600"
31
+ >
21
32
  {{ $t('sui.nothing_found') }}
22
- </span>
23
- <span v-else>
24
- {{ $t('sui.autocomplete_placeholder') }}
25
- </span>
26
- </div>
33
+ </div>
34
+ </slot>
27
35
  </template>
28
36
  </BaseTagAutocomplete>
29
37
  </template>
@@ -33,6 +41,7 @@ import { debounce } from 'lodash';
33
41
  import { config } from '@/index';
34
42
  import { PropType, Ref } from 'vue';
35
43
  import { Option } from '@/types/types';
44
+ import BaseTagAutocomplete from './BaseTagAutocomplete.vue';
36
45
 
37
46
  const props = defineProps({
38
47
  modelValue: {
@@ -67,21 +76,22 @@ const props = defineProps({
67
76
  default: false,
68
77
  type: Boolean,
69
78
  },
70
- min: {
71
- default: undefined,
72
- type: Number,
73
- },
74
79
  max: {
75
80
  default: undefined,
76
81
  type: Number,
77
82
  },
83
+ queryKey: {
84
+ default: 'search',
85
+ type: String,
86
+ },
78
87
  });
79
88
 
80
89
  defineEmits(['update:modelValue', 'typing', 'focus', 'scrollBottom']);
81
90
 
82
91
  const http = config.http;
83
92
 
84
- const loading = ref(false);
93
+ const showLoading = ref(false);
94
+ const fetching = ref(false);
85
95
  const firstSearch = ref(false);
86
96
  const reachedEnd = ref(false);
87
97
  const keywords = ref('');
@@ -94,18 +104,15 @@ const onTyping = (query: string) => {
94
104
 
95
105
  if (keywords.value != query) {
96
106
  keywords.value = query;
107
+ showLoading.value = true;
97
108
  debouncedSearch();
98
109
  }
99
110
  };
100
111
 
101
112
  const onFocus = () => {
102
- if (firstSearch.value) {
103
- return;
113
+ if (!firstSearch.value) {
114
+ search();
104
115
  }
105
-
106
- search();
107
-
108
- firstSearch.value = true;
109
116
  };
110
117
 
111
118
  const scrollBottom = () => {
@@ -116,17 +123,18 @@ const scrollBottom = () => {
116
123
  };
117
124
 
118
125
  const search = () => {
119
- if (loading.value) {
126
+ if (fetching.value) {
120
127
  return;
121
128
  }
122
129
 
123
- loading.value = true;
130
+ fetching.value = true;
131
+ showLoading.value = true;
124
132
  firstSearch.value = true;
125
133
 
126
134
  http
127
135
  .get(props.url, {
128
136
  params: {
129
- search: keywords.value,
137
+ [props.queryKey]: keywords.value,
130
138
  page: page.value,
131
139
  },
132
140
  })
@@ -142,9 +150,12 @@ const search = () => {
142
150
  } else {
143
151
  options.value.push(...data);
144
152
  }
153
+
154
+ firstSearch.value = true;
145
155
  })
146
156
  .finally(() => {
147
- loading.value = false;
157
+ fetching.value = false;
158
+ showLoading.value = false;
148
159
  });
149
160
  };
150
161
 
@@ -18,8 +18,8 @@ import { PropType } from 'vue';
18
18
 
19
19
  defineProps({
20
20
  modelValue: {
21
- required: true,
22
- type: [String, undefined] as PropType<string | undefined>,
21
+ default: undefined,
22
+ type: String as PropType<string | undefined>,
23
23
  },
24
24
  type: {
25
25
  type: String,
@@ -0,0 +1,135 @@
1
+ import BaseHasMany from './BaseHasMany.vue';
2
+
3
+ export default {
4
+ title: 'Form/BaseHasMany',
5
+ component: BaseHasMany,
6
+ argTypes: {},
7
+ args: {
8
+ url: 'https://effettandem.com/api/content/articles',
9
+ field: 'title',
10
+ foreignKey: 'id',
11
+ },
12
+ decorators: [() => ({ template: '<div class="mb-36"><story/></div>' })],
13
+ };
14
+
15
+ const Template = (args) => {
16
+ return {
17
+ components: { BaseHasMany },
18
+ setup() {
19
+ const value = ref([]);
20
+ return { args, value };
21
+ },
22
+ template: `
23
+ <BaseHasMany
24
+ v-model="value"
25
+ v-bind="args"
26
+ ></BaseHasMany>
27
+ <p class="mt-5 text-sm">Value: <span class="bg-slate-200 font-mono px-1 py-px rounded">{{ value ?? '[]' }}</span></p>
28
+ `,
29
+ };
30
+ };
31
+
32
+ export const Demo = Template.bind({});
33
+ Demo.args = {};
34
+
35
+ export const Disabled = (args) => {
36
+ return {
37
+ components: { BaseHasMany },
38
+ setup() {
39
+ const value = ref([]);
40
+ return { args, value };
41
+ },
42
+ template: `<BaseHasMany
43
+ v-bind="args"
44
+ v-model="value"
45
+ :current-models="[{title: 'Dark Vader', id: 1}]"
46
+ :disabled="true"
47
+ ></BaseHasMany>`,
48
+ };
49
+ };
50
+
51
+ export const Maximum = Template.bind({});
52
+ Maximum.args = {
53
+ max: 3,
54
+ };
55
+
56
+ export const SlotOption = (args) => {
57
+ return {
58
+ components: { BaseHasMany },
59
+ setup() {
60
+ const value = ref([]);
61
+ return { args, value };
62
+ },
63
+ template: `
64
+ <div class="mb-20">
65
+ <BaseHasMany
66
+ v-model="value"
67
+ v-bind="args"
68
+ >
69
+ <template #option="{ option, active, selected }">
70
+ <div
71
+ class="rounded px-2 py-1"
72
+ :class="{
73
+ 'hover:bg-slate-100': !active && !selected,
74
+ 'bg-slate-200 hover:bg-slate-300': active && !selected,
75
+ 'bg-blue-500 text-white hover:bg-blue-600': !active && selected,
76
+ 'bg-blue-600 text-white hover:bg-blue-700': active && selected,
77
+ }"
78
+ >
79
+ <p class="text-sm font-medium">{{ option.title }}</p>
80
+ <p class="opacity-60 text-xs">{{ option.owner?.name }}</p>
81
+ </div>
82
+ </template>
83
+ </BaseHasMany>
84
+ </div>
85
+ `,
86
+ };
87
+ };
88
+
89
+ export const SlotFooter = (args) => {
90
+ return {
91
+ components: { BaseHasMany },
92
+ setup() {
93
+ const value = ref([]);
94
+ function onClick() {
95
+ alert(1);
96
+ }
97
+ return { args, value, onClick };
98
+ },
99
+ template: `
100
+ <BaseHasMany
101
+ v-model="value"
102
+ v-bind="args"
103
+ >
104
+ <template #footer>
105
+ <div class="text-center p-2 border-t">
106
+ <button @click=onClick class="btn btn-sm w-full btn-slate-200-outline">This is the footer 💯</button>
107
+ </div>
108
+ </template>
109
+ </BaseHasMany>
110
+ `,
111
+ };
112
+ };
113
+
114
+ export const SlotEmpty = (args) => {
115
+ return {
116
+ components: { BaseHasMany },
117
+ setup() {
118
+ const value = ref([]);
119
+ return { args, value };
120
+ },
121
+ template: `
122
+ <BaseHasMany
123
+ v-model="value"
124
+ v-bind="args"
125
+ >
126
+ <template #empty="props">
127
+ <div>
128
+ <div v-if="props.firstSearch" class="text-center py-10 p-6">🤓🤓🤓</div>
129
+ <div v-else class="text-center px-6 py-20">Start your search... 🔎</div>
130
+ </div>
131
+ </template>
132
+ </BaseHasMany>
133
+ `,
134
+ };
135
+ };
@@ -12,6 +12,7 @@ import BaseBreadcrumbs from './BaseBreadcrumbs.vue';
12
12
  import BaseButton from './BaseButton.vue';
13
13
  import BaseCard from './BaseCard.vue';
14
14
  import BaseCardRow from './BaseCardRow.vue';
15
+ import BaseCharacterCounter from './BaseCharacterCounter.vue';
15
16
  import BaseClipboard from './BaseClipboard.vue';
16
17
  import BaseContainer from './BaseContainer.vue';
17
18
  import BaseCounter from './BaseCounter.vue';
@@ -24,10 +25,14 @@ import BaseDescriptionListItem from './BaseDescriptionListItem.vue';
24
25
  import BaseDialog from './BaseDialog.vue';
25
26
  import BaseFilePicker from './BaseFilePicker.vue';
26
27
  import BaseFileUploader from './BaseFileUploader.vue';
28
+ import BaseHasMany from './BaseHasMany.vue';
27
29
  import { Icon as BaseIcon } from '@iconify/vue';
28
30
  import BaseInput from './BaseInput.vue';
29
31
  import BaseInputLabel from './BaseInputLabel.vue';
30
32
  import BaseLoadingCover from './BaseLoadingCover.vue';
33
+ import BaseMediaItem from './BaseMediaItem.vue';
34
+ import BaseMediaLibrary from './BaseMediaLibrary.vue';
35
+ import BaseMediaPreview from './BaseMediaPreview.vue';
31
36
  import BaseMenu from './BaseMenu.vue';
32
37
  import BaseMenuItem from './BaseMenuItem.vue';
33
38
  import BaseModalCenter from './BaseModalCenter.vue';
@@ -46,13 +51,14 @@ import BaseSideNavigationItem from './BaseSideNavigationItem.vue';
46
51
  import BaseSkeleton from './BaseSkeleton.vue';
47
52
  import BaseSwitch from './BaseSwitch.vue';
48
53
  import BaseSystemAlert from './BaseSystemAlert.vue';
49
- import BaseTextarea from './BaseTextarea.vue';
50
- import BaseTextareaAutoresize from './BaseTextareaAutoresize.vue';
51
- import BaseCharacterCounter from './BaseCharacterCounter.vue';
52
54
  import BaseTabs from './BaseTabs.vue';
53
55
  import BaseTabItem from './BaseTabItem.vue';
56
+ import BaseTagAutocomplete from './BaseTagAutocomplete.vue';
57
+ import BaseTagAutocompleteFetch from './BaseTagAutocompleteFetch.vue';
54
58
  import BaseTable from './BaseTable.vue';
55
59
  import BaseTableColumn from './BaseTableColumn.vue';
60
+ import BaseTextarea from './BaseTextarea.vue';
61
+ import BaseTextareaAutoresize from './BaseTextareaAutoresize.vue';
56
62
 
57
63
  export {
58
64
  BaseAlert,
@@ -69,6 +75,7 @@ export {
69
75
  BaseButton,
70
76
  BaseCard,
71
77
  BaseCardRow,
78
+ BaseCharacterCounter,
72
79
  BaseClipboard,
73
80
  BaseContainer,
74
81
  BaseCounter,
@@ -81,10 +88,14 @@ export {
81
88
  BaseDialog,
82
89
  BaseFilePicker,
83
90
  BaseFileUploader,
91
+ BaseHasMany,
84
92
  BaseIcon,
85
93
  BaseInput,
86
94
  BaseInputLabel,
87
95
  BaseLoadingCover,
96
+ BaseMediaItem,
97
+ BaseMediaLibrary,
98
+ BaseMediaPreview,
88
99
  BaseMenu,
89
100
  BaseMenuItem,
90
101
  BaseModalCenter,
@@ -103,11 +114,12 @@ export {
103
114
  BaseSkeleton,
104
115
  BaseSwitch,
105
116
  BaseSystemAlert,
106
- BaseTextarea,
107
- BaseTextareaAutoresize,
108
- BaseCharacterCounter,
109
117
  BaseTabs,
110
118
  BaseTabItem,
119
+ BaseTagAutocomplete,
120
+ BaseTagAutocompleteFetch,
111
121
  BaseTable,
112
122
  BaseTableColumn,
123
+ BaseTextarea,
124
+ BaseTextareaAutoresize,
113
125
  };
package/src/lang/en.json CHANGED
@@ -53,6 +53,6 @@
53
53
  "year": "Year",
54
54
  "yes_delete": "Yes, delete",
55
55
  "you_can_upload_up_to_n_files": "You can upload one file at most|You can upload up to {count} files",
56
- "you_cannot_select_more_than_x_items": "You can't select more than one item|You can't select more than {x} items"
56
+ "you_cannot_select_more_than_x_items": "You can't select more than one item|You can't select more than {count} items"
57
57
  }
58
58
  }
package/src/lang/fr.json CHANGED
@@ -53,6 +53,6 @@
53
53
  "year": "An",
54
54
  "yes_delete": "Oui, supprimer",
55
55
  "you_can_upload_up_to_n_files": "Vous pouvez télécharger un fichier au maximum|Vous pouvez télécharger jusqu'à {count} fichiers",
56
- "you_cannot_select_more_than_x_items": "Vous ne pouvez pas sélectionner plus de un élément|Vous ne pouvez pas sélectionner plus de {x} éléments"
56
+ "you_cannot_select_more_than_x_items": "Vous ne pouvez pas sélectionner plus de un élément|Vous ne pouvez pas sélectionner plus de {count} éléments"
57
57
  }
58
58
  }
@@ -1,25 +0,0 @@
1
- declare const _default: import("vue").DefineComponent<{
2
- modelValue: {
3
- required: true;
4
- type: NumberConstructor;
5
- };
6
- lastPage: {
7
- required: true;
8
- type: NumberConstructor;
9
- };
10
- }, unknown, unknown, {}, {
11
- next(): void;
12
- previous(): void;
13
- }, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, "update:model-value"[], "update:model-value", import("vue").VNodeProps & import("vue").AllowedComponentProps & import("vue").ComponentCustomProps, Readonly<import("vue").ExtractPropTypes<{
14
- modelValue: {
15
- required: true;
16
- type: NumberConstructor;
17
- };
18
- lastPage: {
19
- required: true;
20
- type: NumberConstructor;
21
- };
22
- }>> & {
23
- "onUpdate:model-value"?: ((...args: any[]) => any) | undefined;
24
- }, {}>;
25
- export default _default;
@@ -1,92 +0,0 @@
1
- <template>
2
- <div class="w-full rounded-lg border border-slate-200">
3
- <div class="flex space-x-5 p-4">
4
- <div
5
- class="h-20 w-20 shrink-0 rounded border border-slate-200 bg-slate-900"
6
- >
7
- <component
8
- :is="url ? 'a' : 'div'"
9
- :href="url"
10
- target="_blank"
11
- class="flex h-full w-full items-center justify-center overflow-hidden"
12
- >
13
- <img
14
- v-if="type == 'image' && url"
15
- :src="url"
16
- class="h-full w-full object-contain object-center"
17
- :alt="name"
18
- />
19
- <img
20
- v-else-if="type == 'image' && 'data_url' in media"
21
- :src="media.data_url"
22
- class="h-full w-full object-contain object-center"
23
- :alt="name"
24
- />
25
- <span v-else class="leading-tight text-slate-600">{{
26
- extension
27
- }}</span>
28
- </component>
29
- </div>
30
- <div class="grow overflow-hidden">
31
- <div class="leading-normal">
32
- <p class="mb-1 truncate text-sm font-medium leading-tight">
33
- {{ name }}
34
- </p>
35
- <p class="text-sm leading-tight text-slate-500">
36
- {{ size }}
37
- </p>
38
- <button
39
- type="button"
40
- class="mt-2 appearance-none text-sm underline"
41
- @click="$emit('delete')"
42
- >
43
- {{ capitalize($t('sui.remove')) }}
44
- </button>
45
- </div>
46
- </div>
47
- </div>
48
- </div>
49
- </template>
50
-
51
- <script lang="ts" setup>
52
- import { Media } from '@/types/Media';
53
- import { UploadedFile } from '@/types/UploadedFile';
54
- import { PropType } from 'vue';
55
- import { capitalize } from 'lodash';
56
- import { fileSizeFormat } from 'src/utils';
57
-
58
- defineEmits(['delete']);
59
-
60
- const props = defineProps({
61
- media: {
62
- required: true,
63
- type: Object as PropType<Media | UploadedFile>,
64
- },
65
- });
66
-
67
- const name = computed(() => {
68
- return props.media.file_name;
69
- });
70
-
71
- const size = computed(() => {
72
- return fileSizeFormat(props.media.size);
73
- });
74
-
75
- const type = computed(() => {
76
- const parts = props.media.mime_type.split('/');
77
- return parts[0];
78
- });
79
-
80
- const extension = computed(() => {
81
- const parts = props.media.mime_type.split('/');
82
- return parts[parts.length - 1];
83
- });
84
-
85
- const url = computed(() => {
86
- if ('url' in props.media) {
87
- return props.media.url;
88
- }
89
-
90
- return null;
91
- });
92
- </script>
@@ -1,60 +0,0 @@
1
- <template>
2
- <nav
3
- v-if="lastPage > 1 || lastPage < modelValue"
4
- class="flex items-center space-x-1 sm:px-0"
5
- >
6
- <button
7
- type="button"
8
- :disabled="modelValue == 1"
9
- class="flex h-8 w-8 items-center justify-center rounded-md border border-slate-300 text-sm font-medium text-slate-400 outline-none hover:enabled:border-slate-400 hover:enabled:text-slate-700 disabled:cursor-not-allowed disabled:border-transparent disabled:opacity-60"
10
- @click="previous()"
11
- >
12
- <BaseIcon
13
- class="h-5 w-5 text-slate-400"
14
- icon="heroicons-solid:chevron-left"
15
- />
16
- </button>
17
- <button
18
- :disabled="modelValue >= lastPage"
19
- class="flex h-8 w-8 items-center justify-center rounded-md border border-slate-300 text-sm font-medium text-slate-400 outline-none hover:enabled:border-slate-400 hover:enabled:text-slate-700 disabled:cursor-not-allowed disabled:border-transparent disabled:opacity-60"
20
- @click="next()"
21
- >
22
- <BaseIcon
23
- class="h-5 w-5 text-slate-400"
24
- icon="heroicons-solid:chevron-right"
25
- />
26
- </button>
27
- </nav>
28
- </template>
29
-
30
- <script lang="ts">
31
- import { defineComponent } from 'vue';
32
-
33
- export default defineComponent({
34
- props: {
35
- modelValue: {
36
- required: true,
37
- type: Number,
38
- },
39
- lastPage: {
40
- required: true,
41
- type: Number,
42
- },
43
- },
44
- emits: ['update:model-value'],
45
- methods: {
46
- next() {
47
- if (this.modelValue >= this.lastPage) {
48
- return;
49
- }
50
- this.$emit('update:model-value', this.modelValue + 1);
51
- },
52
- previous() {
53
- if (this.modelValue == 1) {
54
- return;
55
- }
56
- this.$emit('update:model-value', this.modelValue - 1);
57
- },
58
- },
59
- });
60
- </script>