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.
- package/README.md +8 -7
- package/dist/sprintify-ui.es.js +4429 -3588
- package/dist/style.css +1 -1
- package/dist/tailwindcss/index.js +13 -4
- package/dist/types/src/components/BaseAutocomplete.vue.d.ts +8 -5
- package/dist/types/src/components/BaseAutocompleteFetch.vue.d.ts +8 -5
- package/dist/types/src/components/BaseBelongsTo.vue.d.ts +8 -5
- package/dist/types/src/components/BaseCharacterCounter.vue.d.ts +4 -4
- package/dist/types/src/components/BaseDatePicker.vue.d.ts +8 -5
- package/dist/types/src/components/BaseHasMany.vue.d.ts +277 -0
- package/dist/types/src/components/BaseInput.vue.d.ts +39 -1
- package/dist/types/src/components/{BaseMediaLibraryItem.vue.d.ts → BaseMediaItem.vue.d.ts} +26 -4
- package/dist/types/src/components/BaseMediaLibrary.vue.d.ts +23 -15
- package/dist/types/src/components/BaseMediaPreview.vue.d.ts +97 -0
- package/dist/types/src/components/BaseSelect.vue.d.ts +7 -7
- package/dist/types/src/components/BaseSideNavigationItem.vue.d.ts +20 -1
- package/dist/types/src/components/BaseTabItem.vue.d.ts +20 -1
- package/dist/types/src/components/BaseTagAutocomplete.vue.d.ts +25 -17
- package/dist/types/src/components/BaseTagAutocompleteFetch.vue.d.ts +37 -21
- package/dist/types/src/components/BaseTextarea.vue.d.ts +8 -5
- package/dist/types/src/components/index.d.ts +10 -4
- package/package.json +1 -1
- package/src/components/BaseAppDialogs.vue +2 -2
- package/src/components/BaseAppNotifications.vue +1 -1
- package/src/components/BaseAutocomplete.vue +18 -20
- package/src/components/BaseAutocompleteFetch.vue +2 -2
- package/src/components/BaseBelongsTo.vue +3 -2
- package/src/components/BaseClipboard.vue +1 -1
- package/src/components/BaseDatePicker.vue +2 -2
- package/src/components/BaseHasMany.vue +92 -0
- package/src/components/BaseInput.stories.js +20 -1
- package/src/components/BaseInput.vue +42 -14
- package/src/components/BaseMediaItem.stories.js +41 -0
- package/src/components/BaseMediaItem.vue +71 -0
- package/src/components/BaseMediaLibrary.stories.js +80 -0
- package/src/components/BaseMediaLibrary.vue +67 -68
- package/src/components/BaseMediaPreview.stories.js +72 -0
- package/src/components/BaseMediaPreview.vue +90 -0
- package/src/components/BaseMenu.vue +1 -1
- package/src/components/BaseSelect.vue +1 -1
- package/src/components/BaseSideNavigationItem.vue +11 -3
- package/src/components/BaseSystemAlert.vue +1 -1
- package/src/components/BaseTabItem.vue +13 -3
- package/src/components/BaseTable.vue +2 -2
- package/src/components/BaseTagAutocomplete.stories.js +129 -0
- package/src/components/BaseTagAutocomplete.vue +155 -57
- package/src/components/BaseTagAutocompleteFetch.stories.js +130 -0
- package/src/components/BaseTagAutocompleteFetch.vue +36 -25
- package/src/components/BaseTextarea.vue +2 -2
- package/src/components/HasMany.stories.js +135 -0
- package/src/components/index.ts +18 -6
- package/src/lang/en.json +1 -1
- package/src/lang/fr.json +1 -1
- package/dist/types/src/components/BasePaginationSimple.vue.d.ts +0 -25
- package/src/components/BaseMediaLibraryItem.vue +0 -92
- package/src/components/BasePaginationSimple.vue +0 -60
|
@@ -1,23 +1,43 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
2
|
+
<div class="inline-flex rounded border border-slate-300">
|
|
3
|
+
<div
|
|
4
|
+
v-if="icon && iconPosition == 'left'"
|
|
5
|
+
class="flex shrink-0 items-center justify-center rounded-l border-r border-slate-300 bg-slate-100 px-3 text-slate-600"
|
|
6
|
+
>
|
|
7
|
+
<BaseIcon icon="heroicons:phone-20-solid" />
|
|
8
|
+
</div>
|
|
9
|
+
<input
|
|
10
|
+
ref="input"
|
|
11
|
+
:value="modelValue"
|
|
12
|
+
:type="type"
|
|
13
|
+
:name="name"
|
|
14
|
+
:step="step"
|
|
15
|
+
:disabled="disabled"
|
|
16
|
+
:placeholder="placeholder"
|
|
17
|
+
:required="required"
|
|
18
|
+
class="border-none bg-transparent outline-none focus:z-[1] focus:ring-2 focus:ring-primary-600 focus:ring-offset-1 disabled:cursor-not-allowed disabled:text-slate-300"
|
|
19
|
+
:class="{
|
|
20
|
+
'rounded-r': icon && iconPosition == 'left',
|
|
21
|
+
'rounded-l': icon && iconPosition == 'right',
|
|
22
|
+
rounded: !icon,
|
|
23
|
+
}"
|
|
24
|
+
:autocomplete="autocomplete ? 'on' : 'off'"
|
|
25
|
+
@keydown.enter="onEnter"
|
|
26
|
+
@input="$emit('update:modelValue', transformInputValue($event))"
|
|
27
|
+
/>
|
|
28
|
+
<div
|
|
29
|
+
v-if="icon && iconPosition == 'right'"
|
|
30
|
+
class="flex shrink-0 items-center justify-center rounded-r border-l border-slate-300 bg-slate-100 px-3 text-slate-600"
|
|
31
|
+
>
|
|
32
|
+
<BaseIcon icon="heroicons:phone-20-solid" />
|
|
33
|
+
</div>
|
|
34
|
+
</div>
|
|
16
35
|
</template>
|
|
17
36
|
|
|
18
37
|
<script lang="ts" setup>
|
|
19
38
|
import { get, isNumber, isString, trim } from 'lodash';
|
|
20
39
|
import { PropType } from 'vue';
|
|
40
|
+
import { BaseIcon } from './index';
|
|
21
41
|
|
|
22
42
|
const props = defineProps({
|
|
23
43
|
modelValue: {
|
|
@@ -59,6 +79,14 @@ const props = defineProps({
|
|
|
59
79
|
default: false,
|
|
60
80
|
type: Boolean,
|
|
61
81
|
},
|
|
82
|
+
icon: {
|
|
83
|
+
default: null,
|
|
84
|
+
type: String,
|
|
85
|
+
},
|
|
86
|
+
iconPosition: {
|
|
87
|
+
default: 'left',
|
|
88
|
+
type: String as PropType<'left' | 'right'>,
|
|
89
|
+
},
|
|
62
90
|
});
|
|
63
91
|
|
|
64
92
|
defineEmits(['update:modelValue']);
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import BaseMediaItem from './BaseMediaItem.vue';
|
|
2
|
+
|
|
3
|
+
export default {
|
|
4
|
+
title: 'Components/BaseMediaItem',
|
|
5
|
+
component: BaseMediaItem,
|
|
6
|
+
args: {},
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
const Template = (args) => ({
|
|
10
|
+
components: {
|
|
11
|
+
BaseMediaItem,
|
|
12
|
+
},
|
|
13
|
+
setup() {
|
|
14
|
+
return { args };
|
|
15
|
+
},
|
|
16
|
+
template: `
|
|
17
|
+
<BaseMediaItem v-bind="args" />
|
|
18
|
+
`,
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
export const Demo = Template.bind({});
|
|
22
|
+
Demo.args = {
|
|
23
|
+
media: {
|
|
24
|
+
id: 'xxxxxxxxxxx',
|
|
25
|
+
file_name: 'picture.jpg',
|
|
26
|
+
url: 'https://images.unsplash.com/photo-1670139018938-af8bf748a1bc?auto=format&fit=crop&w=800&h=800&q=80',
|
|
27
|
+
mime_type: 'image',
|
|
28
|
+
size: 430 * 1024,
|
|
29
|
+
},
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
export const PDF = Template.bind({});
|
|
33
|
+
PDF.args = {
|
|
34
|
+
media: {
|
|
35
|
+
id: 'xxxxxxxxxxx',
|
|
36
|
+
file_name: 'document.pdf',
|
|
37
|
+
url: 'https://images.unsplash.com/photo-1670139018938-af8bf748a1bc?auto=format&fit=crop&w=800&h=800&q=80',
|
|
38
|
+
mime_type: 'application/pdf',
|
|
39
|
+
size: 430 * 1024,
|
|
40
|
+
},
|
|
41
|
+
};
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div class="rounded border border-slate-300 bg-white shadow">
|
|
3
|
+
<div class="relative flex">
|
|
4
|
+
<div class="shrink-0">
|
|
5
|
+
<BaseMediaPreview class="h-14 w-14 rounded-l" :media="media" />
|
|
6
|
+
</div>
|
|
7
|
+
<component
|
|
8
|
+
:is="url ? 'a' : 'p'"
|
|
9
|
+
:href="url"
|
|
10
|
+
target="_blank"
|
|
11
|
+
class="grow overflow-hidden px-3 pt-3"
|
|
12
|
+
>
|
|
13
|
+
<div class="text-left leading-tight">
|
|
14
|
+
<p class="grow truncate text-sm font-medium">
|
|
15
|
+
{{ name }}
|
|
16
|
+
</p>
|
|
17
|
+
<p class="shrink-0 text-[10px] font-medium text-slate-400">
|
|
18
|
+
{{ size }}
|
|
19
|
+
</p>
|
|
20
|
+
</div>
|
|
21
|
+
</component>
|
|
22
|
+
<div v-if="showRemove" class="shrink-0 p-2">
|
|
23
|
+
<button
|
|
24
|
+
type="button"
|
|
25
|
+
class="rounded-full bg-slate-400 p-1 text-white hover:bg-slate-500"
|
|
26
|
+
@click="$emit('delete')"
|
|
27
|
+
>
|
|
28
|
+
<BaseIcon icon="heroicons:x-mark-20-solid" class="h-4 w-4"></BaseIcon>
|
|
29
|
+
</button>
|
|
30
|
+
</div>
|
|
31
|
+
</div>
|
|
32
|
+
</div>
|
|
33
|
+
</template>
|
|
34
|
+
|
|
35
|
+
<script lang="ts" setup>
|
|
36
|
+
import { Media } from '@/types/Media';
|
|
37
|
+
import { UploadedFile } from '@/types/UploadedFile';
|
|
38
|
+
import { PropType } from 'vue';
|
|
39
|
+
import { fileSizeFormat } from '@/utils';
|
|
40
|
+
import BaseMediaPreview from './BaseMediaPreview.vue';
|
|
41
|
+
import { BaseIcon } from '.';
|
|
42
|
+
|
|
43
|
+
defineEmits(['delete']);
|
|
44
|
+
|
|
45
|
+
const props = defineProps({
|
|
46
|
+
media: {
|
|
47
|
+
required: true,
|
|
48
|
+
type: Object as PropType<Media | UploadedFile>,
|
|
49
|
+
},
|
|
50
|
+
showRemove: {
|
|
51
|
+
default: true,
|
|
52
|
+
type: Boolean,
|
|
53
|
+
},
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
const name = computed(() => {
|
|
57
|
+
return props.media.file_name;
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
const size = computed(() => {
|
|
61
|
+
return fileSizeFormat(props.media.size);
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
const url = computed(() => {
|
|
65
|
+
if ('url' in props.media) {
|
|
66
|
+
return props.media.url;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
return null;
|
|
70
|
+
});
|
|
71
|
+
</script>
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import BaseApp from './BaseApp.vue';
|
|
2
|
+
import BaseMediaLibrary from './BaseMediaLibrary.vue';
|
|
3
|
+
|
|
4
|
+
const mediaModel = {
|
|
5
|
+
id: 'xxxxx',
|
|
6
|
+
file_name: 'picture0-1-2dfjjje-23refg-45t.jpg',
|
|
7
|
+
mime_type: 'image/jpg',
|
|
8
|
+
url: 'https://images.unsplash.com/photo-1670139018938-af8bf748a1bc?auto=format&fit=crop&w=1200&h=800&q=80',
|
|
9
|
+
size: 430 * 1024,
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
export default {
|
|
13
|
+
title: 'Form/BaseMediaLibrary',
|
|
14
|
+
component: BaseMediaLibrary,
|
|
15
|
+
args: {
|
|
16
|
+
name: 'media',
|
|
17
|
+
max: 2,
|
|
18
|
+
min: 2,
|
|
19
|
+
acceptedExtensions: ['jpg', 'png'],
|
|
20
|
+
maxSize: 500 * 1024,
|
|
21
|
+
currentMedia: [
|
|
22
|
+
mediaModel,
|
|
23
|
+
{
|
|
24
|
+
id: '1',
|
|
25
|
+
url: '',
|
|
26
|
+
mime_type: 'application/pdf',
|
|
27
|
+
file_name: 'document.pdf',
|
|
28
|
+
size: 40012,
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
id: '2',
|
|
32
|
+
url: '',
|
|
33
|
+
mime_type: 'application/excel',
|
|
34
|
+
file_name: 'finance-2022.xlsx',
|
|
35
|
+
size: 5461,
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
id: '3',
|
|
39
|
+
url: '',
|
|
40
|
+
mime_type: 'image/png',
|
|
41
|
+
file_name: '34345-1.png',
|
|
42
|
+
size: 40012,
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
id: '4',
|
|
46
|
+
url: '',
|
|
47
|
+
mime_type: 'audio/mp3',
|
|
48
|
+
file_name: 'test.mp3',
|
|
49
|
+
size: 792834,
|
|
50
|
+
},
|
|
51
|
+
],
|
|
52
|
+
},
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
const Template = (args) => ({
|
|
56
|
+
components: {
|
|
57
|
+
BaseApp,
|
|
58
|
+
BaseMediaLibrary,
|
|
59
|
+
},
|
|
60
|
+
setup() {
|
|
61
|
+
return { args };
|
|
62
|
+
},
|
|
63
|
+
template: `
|
|
64
|
+
<BaseMediaLibrary v-bind="args" />
|
|
65
|
+
<BaseApp></BaseApp>
|
|
66
|
+
`,
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
export const Demo = Template.bind({});
|
|
70
|
+
Demo.args = {};
|
|
71
|
+
|
|
72
|
+
export const Disabled = Template.bind({});
|
|
73
|
+
Disabled.args = {
|
|
74
|
+
disabled: true,
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
export const Errors = Template.bind({});
|
|
78
|
+
Errors.args = {
|
|
79
|
+
errors: ['Whoops, you forgot the name of your mother!'],
|
|
80
|
+
};
|
|
@@ -1,29 +1,5 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
<div>
|
|
3
|
-
<div
|
|
4
|
-
v-if="currentMediaInternal.length + normalizedModelValue.to_add.length"
|
|
5
|
-
class="mb-5"
|
|
6
|
-
>
|
|
7
|
-
<div class="flex flex-wrap">
|
|
8
|
-
<BaseMediaLibraryItem
|
|
9
|
-
v-for="(media, index) in currentMediaInternal"
|
|
10
|
-
:key="media.id"
|
|
11
|
-
:media="media"
|
|
12
|
-
@delete="promptRemoveMedia(index)"
|
|
13
|
-
>
|
|
14
|
-
{{ media.file_name }}
|
|
15
|
-
</BaseMediaLibraryItem>
|
|
16
|
-
<BaseMediaLibraryItem
|
|
17
|
-
v-for="(file, index) in normalizedModelValue.to_add"
|
|
18
|
-
:key="file.id"
|
|
19
|
-
:media="file"
|
|
20
|
-
@delete="promptRemoveUploadedFile(index)"
|
|
21
|
-
>
|
|
22
|
-
{{ file.file_name }}
|
|
23
|
-
</BaseMediaLibraryItem>
|
|
24
|
-
</div>
|
|
25
|
-
</div>
|
|
26
|
-
|
|
27
3
|
<BaseFileUploader
|
|
28
4
|
:max-size="maxSize"
|
|
29
5
|
:disabled="disabled"
|
|
@@ -44,34 +20,28 @@
|
|
|
44
20
|
:max="max"
|
|
45
21
|
>
|
|
46
22
|
<div
|
|
47
|
-
class="
|
|
23
|
+
class="rounded border border-dashed p-6 duration-150"
|
|
48
24
|
:class="[
|
|
49
|
-
baseFileUploaderProps.dragging ? 'bg-
|
|
50
|
-
baseFileUploaderProps.disabled
|
|
25
|
+
baseFileUploaderProps.dragging ? 'bg-blue-100' : 'bg-slate-100',
|
|
26
|
+
baseFileUploaderProps.disabled
|
|
27
|
+
? 'cursor-not-allowed border-slate-300'
|
|
28
|
+
: 'border-slate-400 hover:bg-slate-50',
|
|
51
29
|
]"
|
|
52
30
|
>
|
|
53
|
-
<div class="
|
|
31
|
+
<div :class="[baseFileUploaderProps.disabled ? 'opacity-30' : '']">
|
|
54
32
|
<BaseIcon
|
|
55
33
|
icon="heroicons:arrow-up-on-square"
|
|
56
|
-
class="h-6 w-6 text-slate-500"
|
|
34
|
+
class="mx-auto mb-3 h-6 w-6 text-slate-500"
|
|
57
35
|
/>
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
{{ $t('sui.drop_or_click_to_upload') }}
|
|
62
|
-
</p>
|
|
63
|
-
|
|
64
|
-
<div class="mt-1 text-sm leading-tight text-slate-500">
|
|
65
|
-
<p v-if="max > 1">
|
|
66
|
-
{{ $t('sui.you_can_upload_up_to_n_files', { count: max }) }}
|
|
67
|
-
</p>
|
|
68
|
-
<p>
|
|
69
|
-
{{
|
|
70
|
-
capitalize(
|
|
71
|
-
$t('sui.up_to_x', { x: fileSizeFormat(maxSize) })
|
|
72
|
-
)
|
|
73
|
-
}}
|
|
36
|
+
<div class="text-center">
|
|
37
|
+
<p class="mb-0 text-sm font-medium leading-tight">
|
|
38
|
+
{{ $t('sui.drop_or_click_to_upload') }}
|
|
74
39
|
</p>
|
|
40
|
+
|
|
41
|
+
<div class="mt-1 text-xs leading-tight text-slate-500">
|
|
42
|
+
<p v-if="maxFileText">{{ maxFileText }}</p>
|
|
43
|
+
<p>{{ maxFileSize }}</p>
|
|
44
|
+
</div>
|
|
75
45
|
</div>
|
|
76
46
|
</div>
|
|
77
47
|
</div>
|
|
@@ -79,22 +49,54 @@
|
|
|
79
49
|
</template>
|
|
80
50
|
</BaseFileUploader>
|
|
81
51
|
|
|
82
|
-
<
|
|
52
|
+
<BaseAlert v-if="globalErrorMessage" class="mt-5" color="danger" bordered>
|
|
83
53
|
{{ globalErrorMessage }}
|
|
84
|
-
</
|
|
54
|
+
</BaseAlert>
|
|
55
|
+
|
|
56
|
+
<div
|
|
57
|
+
v-if="currentMediaInternal.length + normalizedModelValue.to_add.length"
|
|
58
|
+
class="mt-5"
|
|
59
|
+
>
|
|
60
|
+
<div class="grid gap-2 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4">
|
|
61
|
+
<div v-for="(media, index) in currentMediaInternal" :key="media.id">
|
|
62
|
+
<BaseMediaItem
|
|
63
|
+
:media="media"
|
|
64
|
+
:show-remove="!disabled"
|
|
65
|
+
@delete="promptRemoveMedia(index)"
|
|
66
|
+
>
|
|
67
|
+
{{ media.file_name }}
|
|
68
|
+
</BaseMediaItem>
|
|
69
|
+
</div>
|
|
70
|
+
<div
|
|
71
|
+
v-for="(file, index) in normalizedModelValue.to_add"
|
|
72
|
+
:key="file.id"
|
|
73
|
+
>
|
|
74
|
+
<BaseMediaItem
|
|
75
|
+
:media="file"
|
|
76
|
+
:show-remove="!disabled"
|
|
77
|
+
@delete="promptRemoveUploadedFile(index)"
|
|
78
|
+
>
|
|
79
|
+
{{ file.file_name }}
|
|
80
|
+
</BaseMediaItem>
|
|
81
|
+
</div>
|
|
82
|
+
</div>
|
|
83
|
+
</div>
|
|
85
84
|
</div>
|
|
86
85
|
</template>
|
|
87
86
|
|
|
88
87
|
<script lang="ts" setup>
|
|
89
88
|
import { UploadedFile } from '@/types/UploadedFile';
|
|
90
89
|
import { Media } from '@/types/Media';
|
|
91
|
-
import
|
|
90
|
+
import { cloneDeep, isArray, isObject } from 'lodash';
|
|
92
91
|
import { PropType } from 'vue';
|
|
93
92
|
import { MediaLibraryPayload } from '@/types/types';
|
|
94
93
|
import { useDialogsStore } from '../stores/dialogs';
|
|
95
94
|
import { useNotificationsStore } from '../stores/notifications';
|
|
96
95
|
import { capitalize } from 'lodash';
|
|
97
|
-
import
|
|
96
|
+
import BaseFileUploader from './BaseFileUploader.vue';
|
|
97
|
+
import BaseMediaItem from './BaseMediaItem.vue';
|
|
98
|
+
import { fileSizeFormat } from '@/utils';
|
|
99
|
+
import BaseAlert from './BaseAlert.vue';
|
|
98
100
|
|
|
99
101
|
const i18n = useI18n();
|
|
100
102
|
|
|
@@ -146,7 +148,7 @@ const props = defineProps({
|
|
|
146
148
|
},
|
|
147
149
|
errors: {
|
|
148
150
|
default: undefined,
|
|
149
|
-
type:
|
|
151
|
+
type: [Array] as PropType<string[]>,
|
|
150
152
|
},
|
|
151
153
|
disabled: {
|
|
152
154
|
default: false,
|
|
@@ -159,9 +161,9 @@ const currentMediaInternal = ref(cloneDeep(props.currentMedia));
|
|
|
159
161
|
const normalizedModelValue = computed(() => {
|
|
160
162
|
if (
|
|
161
163
|
props.modelValue &&
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
164
|
+
isObject(props.modelValue) &&
|
|
165
|
+
isArray(props.modelValue.to_add) &&
|
|
166
|
+
isArray(props.modelValue.to_remove)
|
|
165
167
|
) {
|
|
166
168
|
return props.modelValue;
|
|
167
169
|
}
|
|
@@ -260,23 +262,20 @@ function sync(modelValue: MediaLibraryPayload) {
|
|
|
260
262
|
emit('update', modelValue);
|
|
261
263
|
}
|
|
262
264
|
|
|
263
|
-
function errorMessage(name: string): string {
|
|
264
|
-
const errors = get(props.errors, name, []);
|
|
265
|
-
|
|
266
|
-
if (errors.length == 0) {
|
|
267
|
-
return '';
|
|
268
|
-
}
|
|
269
|
-
|
|
270
|
-
return errors[0];
|
|
271
|
-
}
|
|
272
|
-
|
|
273
265
|
const globalErrorMessage = computed(() => {
|
|
274
|
-
if (
|
|
275
|
-
return
|
|
276
|
-
}
|
|
277
|
-
if (errorMessage(props.name + '.to_remove')) {
|
|
278
|
-
return errorMessage(props.name + '.to_remove');
|
|
266
|
+
if (props.errors && props.errors.length) {
|
|
267
|
+
return props.errors[0];
|
|
279
268
|
}
|
|
280
269
|
return '';
|
|
281
270
|
});
|
|
271
|
+
|
|
272
|
+
const maxFileText = computed(() => {
|
|
273
|
+
return i18n.t('sui.you_can_upload_up_to_n_files', { count: props.max });
|
|
274
|
+
});
|
|
275
|
+
|
|
276
|
+
const maxFileSize = computed(() => {
|
|
277
|
+
return capitalize(
|
|
278
|
+
i18n.t('sui.up_to_x', { x: fileSizeFormat(props.maxSize) })
|
|
279
|
+
);
|
|
280
|
+
});
|
|
282
281
|
</script>
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import BaseMediaPreview from './BaseMediaPreview.vue';
|
|
2
|
+
|
|
3
|
+
export default {
|
|
4
|
+
title: 'Components/BaseMediaPreview',
|
|
5
|
+
component: BaseMediaPreview,
|
|
6
|
+
};
|
|
7
|
+
|
|
8
|
+
const Template = (args) => ({
|
|
9
|
+
components: {
|
|
10
|
+
BaseMediaPreview,
|
|
11
|
+
},
|
|
12
|
+
setup() {
|
|
13
|
+
return { args };
|
|
14
|
+
},
|
|
15
|
+
template: `
|
|
16
|
+
<BaseMediaPreview v-bind="args" class="w-20 h-20" />
|
|
17
|
+
`,
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
export const Image = Template.bind({});
|
|
21
|
+
Image.args = {
|
|
22
|
+
media: {
|
|
23
|
+
id: 'xxxxxxxxxxx',
|
|
24
|
+
file_name: 'picture.jpg',
|
|
25
|
+
mime_type: 'image/jpg',
|
|
26
|
+
size: 430 * 1024,
|
|
27
|
+
},
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
export const ImageUrl = Template.bind({});
|
|
31
|
+
ImageUrl.args = {
|
|
32
|
+
media: {
|
|
33
|
+
id: 'xxxxxxxxxxx',
|
|
34
|
+
file_name: 'picture.jpg',
|
|
35
|
+
mime_type: 'image/jpg',
|
|
36
|
+
url: 'https://images.unsplash.com/photo-1670139018938-af8bf748a1bc?auto=format&fit=crop&w=800&h=800&q=80',
|
|
37
|
+
size: 430 * 1024,
|
|
38
|
+
},
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
export const PDF = Template.bind({});
|
|
42
|
+
PDF.args = {
|
|
43
|
+
media: {
|
|
44
|
+
id: 'xxxxxxxxxxx',
|
|
45
|
+
file_name: 'picture.pdf',
|
|
46
|
+
mime_type: 'application/pdf',
|
|
47
|
+
url: 'https://images.unsplash.com/photo-1670139018938-af8bf748a1bc?auto=format&fit=crop&w=800&h=800&q=80',
|
|
48
|
+
size: 430 * 1024,
|
|
49
|
+
},
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
export const Music = Template.bind({});
|
|
53
|
+
Music.args = {
|
|
54
|
+
media: {
|
|
55
|
+
id: 'xxxxxxxxxxx',
|
|
56
|
+
file_name: 'picture.mp3',
|
|
57
|
+
mime_type: 'audio/mpeg',
|
|
58
|
+
url: 'https://images.unsplash.com/photo-1670139018938-af8bf748a1bc?auto=format&fit=crop&w=800&h=800&q=80',
|
|
59
|
+
size: 430 * 1024,
|
|
60
|
+
},
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
export const Other = Template.bind({});
|
|
64
|
+
Other.args = {
|
|
65
|
+
media: {
|
|
66
|
+
id: 'xxxxxxxxxxx',
|
|
67
|
+
file_name: 'picture.mp3',
|
|
68
|
+
mime_type: 'application/excel',
|
|
69
|
+
url: 'https://images.unsplash.com/photo-1670139018938-af8bf748a1bc?auto=format&fit=crop&w=800&h=800&q=80',
|
|
70
|
+
size: 430 * 1024,
|
|
71
|
+
},
|
|
72
|
+
};
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<component
|
|
3
|
+
:is="url ? 'a' : 'div'"
|
|
4
|
+
:href="url"
|
|
5
|
+
target="_blank"
|
|
6
|
+
class="relative flex items-center justify-center overflow-hidden"
|
|
7
|
+
:class="[url ? 'duration-100 hover:bg-slate-100' : 'bg-white']"
|
|
8
|
+
>
|
|
9
|
+
<img
|
|
10
|
+
v-if="type == 'image' && url"
|
|
11
|
+
:src="url"
|
|
12
|
+
class="h-full w-full bg-black object-contain object-center"
|
|
13
|
+
:alt="name"
|
|
14
|
+
/>
|
|
15
|
+
<img
|
|
16
|
+
v-else-if="type == 'image' && 'data_url' in media"
|
|
17
|
+
:src="media.data_url"
|
|
18
|
+
class="h-full w-full bg-black object-contain object-center"
|
|
19
|
+
:alt="name"
|
|
20
|
+
/>
|
|
21
|
+
<div
|
|
22
|
+
v-else
|
|
23
|
+
class="flex h-full w-full items-center justify-center bg-slate-200"
|
|
24
|
+
>
|
|
25
|
+
<BaseIcon
|
|
26
|
+
v-if="extension == 'pdf'"
|
|
27
|
+
class="max-w-8 h-1/2 max-h-8 w-1/2 text-slate-600"
|
|
28
|
+
icon="mdi:file-pdf-box"
|
|
29
|
+
/>
|
|
30
|
+
<BaseIcon
|
|
31
|
+
v-else-if="type == 'image'"
|
|
32
|
+
class="max-w-8 h-1/2 max-h-8 w-1/2 text-slate-600"
|
|
33
|
+
icon="mdi:camera"
|
|
34
|
+
/>
|
|
35
|
+
<BaseIcon
|
|
36
|
+
v-else-if="type == 'audio'"
|
|
37
|
+
class="max-w-8 h-1/2 max-h-8 w-1/2 text-slate-600"
|
|
38
|
+
icon="mdi:music"
|
|
39
|
+
/>
|
|
40
|
+
<span
|
|
41
|
+
v-else
|
|
42
|
+
class="text-xs font-semibold uppercase leading-tight text-slate-600"
|
|
43
|
+
>
|
|
44
|
+
{{ extension }}
|
|
45
|
+
</span>
|
|
46
|
+
</div>
|
|
47
|
+
</component>
|
|
48
|
+
</template>
|
|
49
|
+
|
|
50
|
+
<script lang="ts" setup>
|
|
51
|
+
import { Media } from '@/types/Media';
|
|
52
|
+
import { UploadedFile } from '@/types/UploadedFile';
|
|
53
|
+
import { PropType } from 'vue';
|
|
54
|
+
import { BaseIcon } from './index';
|
|
55
|
+
|
|
56
|
+
defineEmits(['delete']);
|
|
57
|
+
|
|
58
|
+
const props = defineProps({
|
|
59
|
+
media: {
|
|
60
|
+
required: true,
|
|
61
|
+
type: Object as PropType<Media | UploadedFile>,
|
|
62
|
+
},
|
|
63
|
+
showRemove: {
|
|
64
|
+
default: true,
|
|
65
|
+
type: Boolean,
|
|
66
|
+
},
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
const name = computed(() => {
|
|
70
|
+
return props.media.file_name;
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
const type = computed(() => {
|
|
74
|
+
const parts = props.media.mime_type.split('/');
|
|
75
|
+
return parts[0];
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
const extension = computed(() => {
|
|
79
|
+
const parts = props.media.mime_type.split('/');
|
|
80
|
+
return parts[parts.length - 1];
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
const url = computed(() => {
|
|
84
|
+
if ('url' in props.media) {
|
|
85
|
+
return props.media.url;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
return null;
|
|
89
|
+
});
|
|
90
|
+
</script>
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
>
|
|
17
17
|
<MenuItems
|
|
18
18
|
:class="[menuClass, menuPositionClass]"
|
|
19
|
-
class="absolute z-
|
|
19
|
+
class="absolute z-menu mt-2 rounded-md bg-white p-1 shadow-lg ring-1 ring-black ring-opacity-10 focus:outline-none"
|
|
20
20
|
>
|
|
21
21
|
<slot name="items">
|
|
22
22
|
<template v-for="item in items" :key="item.label + 'link'">
|
|
@@ -36,7 +36,7 @@ const EMPTY_VALUE_EXTERNAL = null;
|
|
|
36
36
|
const props = defineProps({
|
|
37
37
|
modelValue: {
|
|
38
38
|
default: undefined,
|
|
39
|
-
type: [String, Number, null
|
|
39
|
+
type: [String, Number, null] as PropType<Option | undefined>,
|
|
40
40
|
},
|
|
41
41
|
name: {
|
|
42
42
|
default: undefined,
|
|
@@ -1,11 +1,15 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<router-link
|
|
2
|
+
<router-link
|
|
3
|
+
v-slot="{ href, navigate, isActive, isExactActive }"
|
|
4
|
+
:to="to"
|
|
5
|
+
custom
|
|
6
|
+
>
|
|
3
7
|
<a
|
|
4
8
|
:href="disabled ? undefined : href"
|
|
5
9
|
:disabled="disabled"
|
|
6
10
|
class="group relative flex items-center px-3 py-1"
|
|
7
11
|
:class="[
|
|
8
|
-
isActive
|
|
12
|
+
(activeStrategy == 'default' ? isActive : isExactActive)
|
|
9
13
|
? 'font-semibold text-blue-600'
|
|
10
14
|
: 'text-slate-600 hover:text-slate-900',
|
|
11
15
|
disabled ? 'cursor-not-allowed opacity-60' : '',
|
|
@@ -15,7 +19,7 @@
|
|
|
15
19
|
<div
|
|
16
20
|
class="absolute left-0 top-0 h-full"
|
|
17
21
|
:class="[
|
|
18
|
-
isActive
|
|
22
|
+
(activeStrategy == 'default' ? isActive : isExactActive)
|
|
19
23
|
? 'w-[2px] bg-blue-600'
|
|
20
24
|
: 'group-hover:w-px group-hover:bg-slate-700',
|
|
21
25
|
]"
|
|
@@ -38,6 +42,10 @@ const props = defineProps({
|
|
|
38
42
|
default: false,
|
|
39
43
|
type: Boolean,
|
|
40
44
|
},
|
|
45
|
+
activeStrategy: {
|
|
46
|
+
default: 'default',
|
|
47
|
+
type: String as PropType<'default' | 'exact'>,
|
|
48
|
+
},
|
|
41
49
|
});
|
|
42
50
|
|
|
43
51
|
const onClick = (navigate: () => void) => {
|
|
@@ -36,7 +36,7 @@ import { RouteLocationRaw } from 'vue-router';
|
|
|
36
36
|
const props = defineProps({
|
|
37
37
|
to: {
|
|
38
38
|
default: undefined,
|
|
39
|
-
type: [Object, String
|
|
39
|
+
type: [Object, String] as PropType<RouteLocationRaw | undefined>,
|
|
40
40
|
},
|
|
41
41
|
action: {
|
|
42
42
|
default: undefined,
|