sprintify-ui 0.0.183 → 0.0.184

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 (49) hide show
  1. package/dist/sprintify-ui.es.js +16888 -14992
  2. package/dist/style.css +1 -1
  3. package/dist/types/src/components/BaseCropper.vue.d.ts +57 -0
  4. package/dist/types/src/components/BaseCropperModal.vue.d.ts +27 -0
  5. package/dist/types/src/components/BaseDisplayRelativeTime.vue.d.ts +3 -3
  6. package/dist/types/src/components/BaseFilePicker.vue.d.ts +52 -37
  7. package/dist/types/src/components/BaseFilePickerCrop.vue.d.ts +57 -0
  8. package/dist/types/src/components/BaseFileUploader.vue.d.ts +65 -81
  9. package/dist/types/src/components/BaseMediaLibrary.vue.d.ts +20 -10
  10. package/dist/types/src/components/BaseTableColumn.vue.d.ts +1 -1
  11. package/dist/types/src/components/index.d.ts +4 -1
  12. package/dist/types/src/index.d.ts +24 -4
  13. package/dist/types/src/svg/BaseEmptyState.vue.d.ts +1 -1
  14. package/dist/types/src/types/ImagePickerResult.d.ts +5 -0
  15. package/dist/types/src/types/index.d.ts +28 -0
  16. package/dist/types/src/utils/blob.d.ts +3 -0
  17. package/dist/types/src/utils/cropper/avatar.d.ts +5 -0
  18. package/dist/types/src/utils/cropper/cover.d.ts +5 -0
  19. package/dist/types/src/utils/cropper/presetInterface.d.ts +7 -0
  20. package/dist/types/src/utils/cropper/presets.d.ts +6 -0
  21. package/dist/types/src/utils/fileValidations.d.ts +2 -0
  22. package/dist/types/src/utils/index.d.ts +3 -1
  23. package/dist/types/src/utils/resizeImageFromURI.d.ts +1 -0
  24. package/package.json +35 -32
  25. package/src/components/BaseCropper.stories.js +113 -0
  26. package/src/components/BaseCropper.vue +451 -0
  27. package/src/components/BaseCropperModal.stories.js +54 -0
  28. package/src/components/BaseCropperModal.vue +139 -0
  29. package/src/components/BaseFilePicker.stories.js +30 -3
  30. package/src/components/BaseFilePicker.vue +107 -75
  31. package/src/components/BaseFilePickerCrop.stories.js +134 -0
  32. package/src/components/BaseFilePickerCrop.vue +116 -0
  33. package/src/components/BaseFileUploader.stories.js +11 -7
  34. package/src/components/BaseFileUploader.vue +57 -86
  35. package/src/components/BaseMediaLibrary.stories.js +24 -5
  36. package/src/components/BaseMediaLibrary.vue +17 -2
  37. package/src/components/index.ts +6 -0
  38. package/src/lang/en.json +6 -1
  39. package/src/lang/fr.json +6 -1
  40. package/src/types/ImagePickerResult.ts +5 -0
  41. package/src/types/index.ts +31 -0
  42. package/src/utils/blob.ts +30 -0
  43. package/src/utils/cropper/avatar.ts +33 -0
  44. package/src/utils/cropper/cover.ts +41 -0
  45. package/src/utils/cropper/presetInterface.ts +16 -0
  46. package/src/utils/cropper/presets.ts +7 -0
  47. package/src/utils/fileValidations.ts +26 -0
  48. package/src/utils/index.ts +12 -1
  49. package/src/utils/resizeImageFromURI.ts +118 -0
@@ -0,0 +1,139 @@
1
+ <template>
2
+ <BaseModalCenter
3
+ :model-value="modelValue"
4
+ clipped
5
+ :max-width="cropperSize + 'px'"
6
+ @update:model-value="$emit('update:modelValue', $event)"
7
+ >
8
+ <div v-if="ready" class="flex items-center justify-center">
9
+ <BaseCropper
10
+ v-if="cropperInternal.source"
11
+ ref="baseCropperRef"
12
+ :disabled="loading"
13
+ v-bind="cropperInternal"
14
+ >
15
+ <template #footer="{ initializing }">
16
+ <div class="mt-5 px-4 pb-5">
17
+ <div class="flex justify-center space-x-2">
18
+ <button
19
+ type="button"
20
+ class="btn btn-lg btn-white"
21
+ :disabled="initializing"
22
+ @click="close()"
23
+ >
24
+ {{ $t('cancel') }}
25
+ </button>
26
+ <BaseButton
27
+ type="button"
28
+ class="btn btn-lg btn-primary"
29
+ :loading="loading"
30
+ :disabled="initializing"
31
+ @click="save()"
32
+ >
33
+ {{ $t('save') }}
34
+ </BaseButton>
35
+ </div>
36
+ </div>
37
+ </template>
38
+ </BaseCropper>
39
+ </div>
40
+ </BaseModalCenter>
41
+ </template>
42
+
43
+ <script lang="ts" setup>
44
+ import BaseModalCenter from './BaseModalCenter.vue';
45
+ import BaseCropper from './BaseCropper.vue';
46
+ import { BaseCropperConfig } from '@/types';
47
+ import { debounce } from 'lodash';
48
+ import { BaseButton } from '.';
49
+
50
+ const props = defineProps<{
51
+ modelValue?: boolean;
52
+ cropper: BaseCropperConfig;
53
+ }>();
54
+
55
+ const emits = defineEmits<{
56
+ (event: 'update:modelValue', value: boolean): void;
57
+ (event: 'cropped', value: HTMLCanvasElement | string | Blob): void;
58
+ }>();
59
+
60
+ const ready = ref(false);
61
+ const loading = ref(false);
62
+ const baseCropperRef = ref<InstanceType<typeof BaseCropper> | null>(null);
63
+
64
+ const maxWidth = computed(() => {
65
+ if (props.cropper.config?.maxWidth) {
66
+ return props.cropper.config.maxWidth;
67
+ }
68
+ return 384;
69
+ });
70
+
71
+ const cropperSize = ref(maxWidth.value);
72
+
73
+ const onResizeDebounced = debounce(() => {
74
+ onResize();
75
+ }, 100);
76
+
77
+ function onResize() {
78
+ cropperSize.value = Math.min(window.innerWidth, maxWidth.value);
79
+ }
80
+
81
+ const cropperInternal = computed(() => {
82
+ return {
83
+ ...props.cropper,
84
+ config: {
85
+ ...props.cropper.config,
86
+ maxWidth: cropperSize.value,
87
+ },
88
+ };
89
+ });
90
+
91
+ onMounted(() => {
92
+ onResize();
93
+
94
+ // To avoid the cropper to be initialized with a wrong size
95
+ ready.value = true;
96
+
97
+ window.addEventListener('resize', onResizeDebounced);
98
+ });
99
+
100
+ onBeforeUnmount(() => {
101
+ window.removeEventListener('resize', onResizeDebounced);
102
+ });
103
+
104
+ async function save() {
105
+ if (!baseCropperRef.value) {
106
+ return null;
107
+ }
108
+
109
+ loading.value = true;
110
+
111
+ return new Promise((resolve, reject) => {
112
+ setTimeout(() => {
113
+ baseCropperRef.value
114
+ ?.save()
115
+ .then((result) => {
116
+ resolve(result);
117
+ if (result) {
118
+ emits('cropped', result);
119
+ }
120
+ close();
121
+ })
122
+ .catch((error) => {
123
+ reject(error);
124
+ })
125
+ .finally(() => {
126
+ loading.value = false;
127
+ });
128
+ }, 50);
129
+ });
130
+ }
131
+
132
+ function close() {
133
+ emits('update:modelValue', false);
134
+ }
135
+
136
+ defineExpose({
137
+ save,
138
+ });
139
+ </script>
@@ -1,18 +1,27 @@
1
1
  import BaseFilePicker from './BaseFilePicker.vue';
2
2
  import { Icon as BaseIcon } from '@iconify/vue';
3
+ import ShowValue from '../../.storybook/components/ShowValue.vue';
4
+ import BaseAppNotifications from '@/components/BaseAppNotifications.vue';
3
5
 
4
6
  export default {
5
7
  title: 'Form/BaseFilePicker',
6
8
  component: BaseFilePicker,
9
+ args: {},
7
10
  };
8
11
 
9
12
  const Template = (args) => ({
10
- components: { BaseFilePicker, BaseIcon },
13
+ components: { BaseFilePicker, BaseIcon, ShowValue, BaseAppNotifications },
11
14
  setup() {
12
- return { args };
15
+ const file = ref(null);
16
+
17
+ function onSelect(f) {
18
+ file.value = f;
19
+ }
20
+
21
+ return { args, file, onSelect };
13
22
  },
14
23
  template: `
15
- <BaseFilePicker v-bind="args">
24
+ <BaseFilePicker v-bind="args" @select="onSelect">
16
25
  <template #default="{ dragging, disabled }">
17
26
  <div
18
27
  class="flex w-full items-center space-x-4 rounded-lg border-2 border-dashed border-slate-200 p-5 duration-100"
@@ -35,16 +44,34 @@ const Template = (args) => ({
35
44
  >
36
45
  {{ $t("sui.drop_or_click_to_upload") }}
37
46
  </p>
47
+ <div class="mt-1">
48
+ <p v-if="args.maxSize" class="text-sm text-slate-500">Max {{ args.maxSize }} bytes</p>
49
+ <p v-if="args.acceptedExtensions" class="text-sm text-slate-500">{{ args.acceptedExtensions?.join(', ') }}</p>
50
+ </div>
38
51
  </div>
39
52
  </div>
40
53
  </template>
41
54
  </BaseFilePicker>
55
+
56
+ <ShowValue :value="file" />
57
+
58
+ <BaseAppNotifications></BaseAppNotifications>
42
59
  `,
43
60
  });
44
61
 
45
62
  export const Demo = Template.bind({});
46
63
  Demo.args = {};
47
64
 
65
+ export const MaxSize = Template.bind({});
66
+ MaxSize.args = {
67
+ maxSize: 1024 * 10, // 10kb
68
+ };
69
+
70
+ export const FileExtension = Template.bind({});
71
+ FileExtension.args = {
72
+ acceptedExtensions: ['xlsx', 'xls'],
73
+ };
74
+
48
75
  export const Disabled = Template.bind({});
49
76
  Disabled.args = {
50
77
  disabled: true,
@@ -23,79 +23,111 @@
23
23
  />
24
24
  </template>
25
25
 
26
- <script lang="ts">
27
- import { defineComponent } from 'vue';
28
-
29
- export default defineComponent({
30
- props: {
31
- disabled: {
32
- default: false,
33
- type: Boolean,
34
- },
35
- buttonClass: {
36
- default: '',
37
- type: String,
38
- },
39
- accept: {
40
- default: undefined,
41
- type: String,
42
- },
43
- },
44
- emits: ['select'],
45
- data() {
46
- return {
47
- selecting: false,
48
- dragging: false,
49
- };
50
- },
51
- computed: {
52
- inputElement(): HTMLInputElement | undefined {
53
- return this.$refs.input as HTMLInputElement | undefined;
54
- },
55
- },
56
- methods: {
57
- async pickFile() {
58
- if (this.disabled) {
59
- return;
60
- }
61
-
62
- (this.$refs.input as HTMLElement | undefined)?.click();
63
- },
64
- onInputChange() {
65
- const files = (this.inputElement?.files ?? []) as File[];
66
- this.select(files);
67
- },
68
- handleDrop(e: any) {
69
- if (this.disabled) {
70
- return;
71
- }
72
-
73
- const files = e?.dataTransfer?.files ?? [];
74
-
75
- this.select(files);
76
- },
77
- async select(files: File[]) {
78
- if (this.disabled) {
79
- return;
80
- }
81
-
82
- if (!files || files.length == 0 || !(files[0] instanceof File)) {
83
- return;
84
- }
85
-
86
- this.selecting = true;
87
-
88
- try {
89
- const file = files[0];
90
-
91
- this.$emit('select', file);
92
- } finally {
93
- if (this.inputElement) {
94
- this.inputElement.value = '';
95
- }
96
- this.selecting = false;
97
- }
98
- },
99
- },
100
- });
26
+ <script lang="ts" setup>
27
+ import { useNotificationsStore } from '@/stores/notifications';
28
+ import { fileSizeFormat, toHumanList } from '@/utils';
29
+ import { maxSize, validExtension } from '@/utils/fileValidations';
30
+
31
+ const props = withDefaults(
32
+ defineProps<{
33
+ disabled?: boolean;
34
+ buttonClass?: string;
35
+ maxSize?: number;
36
+ accept?: string;
37
+ acceptedExtensions?: string[];
38
+ }>(),
39
+ {
40
+ disabled: false,
41
+ buttonClass: '',
42
+ maxSize: 1024 * 1024 * 20, // 20 MB,
43
+ accept: undefined,
44
+ acceptedExtensions: undefined,
45
+ }
46
+ );
47
+
48
+ const emit = defineEmits(['select']);
49
+
50
+ const notifications = useNotificationsStore();
51
+
52
+ const i18n = useI18n();
53
+
54
+ const selecting = ref(false);
55
+ const dragging = ref(false);
56
+ const input = ref<HTMLInputElement | undefined>();
57
+
58
+ async function pickFile() {
59
+ if (props.disabled) {
60
+ return;
61
+ }
62
+
63
+ input.value?.click();
64
+ }
65
+
66
+ function onInputChange() {
67
+ const files = (input.value?.files ?? []) as File[];
68
+ select(files);
69
+ }
70
+
71
+ function handleDrop(e: any) {
72
+ if (props.disabled) {
73
+ return;
74
+ }
75
+
76
+ const files = e?.dataTransfer?.files ?? [];
77
+
78
+ select(files);
79
+ }
80
+
81
+ async function select(files: File[]) {
82
+ if (props.disabled) {
83
+ return;
84
+ }
85
+
86
+ if (!files || files.length == 0 || !(files[0] instanceof File)) {
87
+ return;
88
+ }
89
+
90
+ selecting.value = true;
91
+
92
+ try {
93
+ const file = files[0];
94
+
95
+ if (!maxSize(file, props.maxSize)) {
96
+ notifications.push({
97
+ color: 'danger',
98
+ title: i18n.t('sui.error'),
99
+ text: i18n.t('sui.the_file_size_must_not_exceed_x', {
100
+ x: fileSizeFormat(props.maxSize),
101
+ }),
102
+ });
103
+
104
+ selecting.value = false;
105
+ return;
106
+ }
107
+
108
+ if (!validExtension(file, props.acceptedExtensions)) {
109
+ notifications.push({
110
+ color: 'danger',
111
+ title: i18n.t('sui.error'),
112
+ text:
113
+ i18n.t('sui.the_file_type_is_invalid') +
114
+ ' ' +
115
+ i18n.t('sui.file_must_be_of_type') +
116
+ ' ' +
117
+ toHumanList(props.acceptedExtensions as string[], i18n.t('sui.or')) +
118
+ '.',
119
+ });
120
+
121
+ selecting.value = false;
122
+ return;
123
+ }
124
+
125
+ emit('select', file);
126
+ } finally {
127
+ if (input.value) {
128
+ input.value.value = '';
129
+ }
130
+ selecting.value = false;
131
+ }
132
+ }
101
133
  </script>
@@ -0,0 +1,134 @@
1
+ import BaseFilePickerCrop from '@/components/BaseFilePickerCrop.vue';
2
+ import BaseLoadingCover from '@/components/BaseLoadingCover.vue';
3
+ import BaseAppNotifications from '@/components/BaseAppNotifications.vue';
4
+ import BaseCropper from '@/components/BaseCropper.vue';
5
+ import BaseModalCenter from '@/components/BaseModalCenter.vue';
6
+ import BaseButton from '@/components/BaseButton.vue';
7
+ import { Icon as BaseIcon } from '@iconify/vue';
8
+ import ShowValue from '../../.storybook/components/ShowValue.vue';
9
+
10
+ export default {
11
+ title: 'Form/BaseFilePickerCrop',
12
+ component: BaseFilePickerCrop,
13
+ args: {
14
+ buttonClass: 'w-full',
15
+ acceptedExtensions: ['jpg', 'jpeg', 'png'],
16
+ },
17
+ };
18
+
19
+ const Template = (args) => ({
20
+ components: {
21
+ BaseFilePickerCrop,
22
+ BaseIcon,
23
+ BaseLoadingCover,
24
+ BaseAppNotifications,
25
+ BaseCropper,
26
+ BaseModalCenter,
27
+ BaseButton,
28
+ ShowValue,
29
+ },
30
+ setup() {
31
+ const file = ref(null);
32
+
33
+ function onSelect(f) {
34
+ file.value = f;
35
+ }
36
+
37
+ return { args, file, onSelect };
38
+ },
39
+ template: `
40
+ <BaseFilePickerCrop v-bind="args" @select="onSelect">
41
+ <template #default="{ dragging, disabled, uploading, selecting }">
42
+ <div
43
+ class="flex w-full items-center space-x-4 rounded-lg border-2 border-dashed border-slate-200 p-5 duration-100"
44
+ :class="[
45
+ dragging ? 'bg-slate-100' : 'bg-white',
46
+ disabled ? 'bg-slate-100 cursor-not-allowed' : 'hover:bg-slate-50',
47
+ ]"
48
+ >
49
+ <div class="rounded-full bg-slate-200 p-2">
50
+ <BaseIcon
51
+ icon="heroicons:arrow-up-on-square"
52
+ class="h-6 w-6"
53
+ :class="[disabled ? 'text-slate-400' : 'text-slate-500']"
54
+ />
55
+ </div>
56
+ <div class="text-left" :class="[disabled ? 'opacity-50' : '']">
57
+ <p class="mb-0 text-sm font-medium leading-tight">
58
+ {{ $t("sui.drop_or_click_to_upload") }}
59
+ </p>
60
+ <div class="mt-1">
61
+ <p v-if="args.maxSize" class="text-sm text-slate-500">Max {{ args.maxSize }} bytes</p>
62
+ <p v-if="args.acceptedExtensions" class="text-sm text-slate-500">{{ args.acceptedExtensions?.join(', ') }}</p>
63
+ </div>
64
+ </div>
65
+ </div>
66
+ </template>
67
+ <template #loading="{ dragging, disabled, uploading, selecting }">
68
+ <BaseLoadingCover
69
+ :model-value="args.loading || uploading || selecting"
70
+ :delay="0"
71
+ icon-class="text-primary-600 w-6 h-6"
72
+ backdrop-class="bg-white opacity-60"
73
+ />
74
+ </template>
75
+ </BaseFilePickerCrop>
76
+
77
+ <ShowValue :value="file" />
78
+
79
+ <BaseAppNotifications></BaseAppNotifications>
80
+ `,
81
+ });
82
+
83
+ export const Demo = Template.bind({});
84
+ Demo.args = {};
85
+
86
+ export const Avatar = Template.bind({});
87
+ Avatar.args = {
88
+ cropper: {
89
+ preset: 'avatar',
90
+ presetOptions: {
91
+ size: 300,
92
+ },
93
+ config: {
94
+ maxWidth: 300,
95
+ },
96
+ },
97
+ };
98
+
99
+ export const Cover = Template.bind({});
100
+ Cover.args = {
101
+ cropper: {
102
+ preset: 'cover',
103
+ presetOptions: {
104
+ size: 600,
105
+ },
106
+ config: {
107
+ maxWidth: 600,
108
+ },
109
+ },
110
+ };
111
+
112
+ export const Resize = Template.bind({});
113
+ Resize.args = {
114
+ cropper: {
115
+ maxSize: 600,
116
+ config: {
117
+ initialResize: 100,
118
+ },
119
+ preset: 'cover',
120
+ presetOptions: {
121
+ size: 600,
122
+ },
123
+ },
124
+ };
125
+
126
+ export const MaxSize = Template.bind({});
127
+ MaxSize.args = {
128
+ maxSize: 10 * 1024,
129
+ };
130
+
131
+ export const Disabled = Template.bind({});
132
+ Disabled.args = {
133
+ disabled: true,
134
+ };
@@ -0,0 +1,116 @@
1
+ <template>
2
+ <BaseFilePicker
3
+ :button-class="buttonClass"
4
+ :disabled="disabled"
5
+ accept="image/*"
6
+ :max-size="maxSize"
7
+ :accepted-extensions="acceptedExtensions"
8
+ @select="launchCropper"
9
+ >
10
+ <template #default="slotProps">
11
+ <slot name="default" v-bind="slotProps" />
12
+
13
+ <BaseCropperModal
14
+ v-if="cropperInternal"
15
+ ref="baseCropperModalRef"
16
+ v-model="showCropperModal"
17
+ :cropper="cropperInternal"
18
+ @cropped="onCropped"
19
+ />
20
+ </template>
21
+ </BaseFilePicker>
22
+ </template>
23
+
24
+ <script lang="ts" setup>
25
+ import BaseFilePicker from '@/components/BaseFilePicker.vue';
26
+ import BaseCropperModal from '@/components/BaseCropperModal.vue';
27
+ import { blobToBase64 } from '@/utils';
28
+ import { BaseCropperConfig } from '@/types';
29
+ import { isObject, reject } from 'lodash';
30
+
31
+ const props = withDefaults(
32
+ defineProps<{
33
+ disabled?: boolean;
34
+ buttonClass?: string;
35
+ maxSize?: number;
36
+ acceptedExtensions?: string[];
37
+ cropper: BaseCropperConfig | null;
38
+ }>(),
39
+ {
40
+ disabled: false,
41
+ buttonClass: '',
42
+ maxSize: 1024 * 1024 * 20, // 20 MB
43
+ acceptedExtensions: undefined,
44
+ }
45
+ );
46
+
47
+ const baseCropperModalRef = ref<InstanceType<typeof BaseCropperModal> | null>(
48
+ null
49
+ );
50
+
51
+ const emit = defineEmits(['select']);
52
+
53
+ const showCropperModal = ref(false);
54
+ const cropperSource = ref('');
55
+
56
+ const cropperInternal = computed<BaseCropperConfig | null>(() => {
57
+ if (!cropperSource.value) {
58
+ return null;
59
+ }
60
+
61
+ if (isObject(props.cropper)) {
62
+ return {
63
+ ...props.cropper,
64
+ source: cropperSource.value,
65
+ };
66
+ }
67
+
68
+ return {
69
+ source: cropperSource.value,
70
+ };
71
+ });
72
+
73
+ async function launchCropper(file: File) {
74
+ if (!file) {
75
+ return;
76
+ }
77
+
78
+ showCropperModal.value = false;
79
+ cropperSource.value = await blobToBase64(file);
80
+
81
+ if (!cropperSource.value) {
82
+ return;
83
+ }
84
+
85
+ showCropperModal.value = true;
86
+ }
87
+
88
+ async function onCropped(cropped: HTMLCanvasElement | string | Blob) {
89
+ if (cropped instanceof Blob) {
90
+ emit('select', cropped);
91
+ return;
92
+ }
93
+
94
+ if (cropped instanceof HTMLCanvasElement) {
95
+ const blob = await new Promise<Blob>((resolve) => {
96
+ cropped.toBlob((blob) => {
97
+ if (blob) {
98
+ resolve(blob);
99
+ } else {
100
+ reject(new Error('Failed to convert canvas to blob'));
101
+ }
102
+ });
103
+ });
104
+
105
+ emit('select', blob);
106
+
107
+ return;
108
+ }
109
+
110
+ if (typeof cropped === 'string') {
111
+ const blob = await fetch(cropped).then((r) => r.blob());
112
+ emit('select', blob);
113
+ return;
114
+ }
115
+ }
116
+ </script>
@@ -31,21 +31,17 @@ const Template = (args) => ({
31
31
  class="flex w-full items-center space-x-4 rounded-lg border-2 border-dashed border-slate-200 p-5 duration-100"
32
32
  :class="[
33
33
  dragging ? 'bg-slate-100' : 'bg-white',
34
- disabled ? 'bg-slate-100 cursor-not-allowed' : 'hover:bg-slate-50',
34
+ disabled ? 'bg-slate-100 cursor-not-allowed opacity-50' : 'hover:bg-slate-50',
35
35
  ]"
36
36
  >
37
37
  <div class="rounded-full bg-slate-200 p-2">
38
38
  <BaseIcon
39
39
  icon="heroicons:arrow-up-on-square"
40
40
  class="h-6 w-6"
41
- :class="[disabled ? 'text-slate-400' : 'text-slate-500']"
42
41
  />
43
42
  </div>
44
43
  <div class="text-left">
45
- <p
46
- class="mb-0 text-sm font-medium leading-tight"
47
- :class="[disabled ? 'text-slate-400' : 'text-slate-900']"
48
- >
44
+ <p class="mb-0 text-sm font-medium leading-tight">
49
45
  {{ $t("sui.drop_or_click_to_upload") }}
50
46
  </p>
51
47
  <p class="text-sm text-slate-500 mt-1">Max 200kb</p>
@@ -57,7 +53,7 @@ const Template = (args) => ({
57
53
  <BaseLoadingCover
58
54
  :model-value="args.loading || uploading || selecting"
59
55
  :delay="0"
60
- icon-class="text-primary-600 w-6 h-6"
56
+ icon-class="text-red-600 w-6 h-6"
61
57
  backdrop-class="bg-white opacity-60"
62
58
  />
63
59
  </template>
@@ -69,6 +65,14 @@ const Template = (args) => ({
69
65
  export const Demo = Template.bind({});
70
66
  Demo.args = {};
71
67
 
68
+ export const ImagePicker = Template.bind({});
69
+ ImagePicker.args = {
70
+ component: 'BaseFilePickerCrop',
71
+ cropper: {
72
+ preset: 'avatar',
73
+ },
74
+ };
75
+
72
76
  export const Disabled = Template.bind({});
73
77
  Disabled.args = {
74
78
  disabled: true,