@finema/core 1.4.205 → 1.4.207

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 (64) hide show
  1. package/README.md +60 -60
  2. package/dist/module.json +1 -1
  3. package/dist/module.mjs +1 -1
  4. package/dist/runtime/components/Alert.vue +48 -48
  5. package/dist/runtime/components/Avatar.vue +27 -27
  6. package/dist/runtime/components/Badge.vue +11 -11
  7. package/dist/runtime/components/Breadcrumb.vue +44 -44
  8. package/dist/runtime/components/Button/Group.vue +37 -37
  9. package/dist/runtime/components/Button/index.vue +75 -75
  10. package/dist/runtime/components/Card.vue +38 -38
  11. package/dist/runtime/components/Core.vue +45 -45
  12. package/dist/runtime/components/Dialog/index.vue +108 -108
  13. package/dist/runtime/components/Dropdown/index.vue +70 -70
  14. package/dist/runtime/components/FlexDeck/Base.vue +152 -152
  15. package/dist/runtime/components/FlexDeck/index.vue +68 -68
  16. package/dist/runtime/components/Form/FieldWrapper.vue +23 -23
  17. package/dist/runtime/components/Form/Fields.vue +230 -230
  18. package/dist/runtime/components/Form/InputCheckbox/index.vue +28 -28
  19. package/dist/runtime/components/Form/InputDateTime/index.vue +61 -61
  20. package/dist/runtime/components/Form/InputDateTimeRange/index.vue +83 -83
  21. package/dist/runtime/components/Form/InputNumber/index.vue +27 -27
  22. package/dist/runtime/components/Form/InputRadio/index.vue +27 -27
  23. package/dist/runtime/components/Form/InputSelect/index.vue +54 -45
  24. package/dist/runtime/components/Form/InputSelect/types.d.ts +1 -0
  25. package/dist/runtime/components/Form/InputSelectMultiple/index.vue +54 -54
  26. package/dist/runtime/components/Form/InputStatic/index.vue +16 -16
  27. package/dist/runtime/components/Form/InputTags/index.vue +141 -141
  28. package/dist/runtime/components/Form/InputText/index.vue +68 -68
  29. package/dist/runtime/components/Form/InputTextarea/index.vue +25 -25
  30. package/dist/runtime/components/Form/InputToggle/index.vue +27 -27
  31. package/dist/runtime/components/Form/InputUploadDropzone/index.vue +206 -206
  32. package/dist/runtime/components/Form/InputUploadDropzoneAuto/index.vue +342 -342
  33. package/dist/runtime/components/Form/InputUploadDropzoneAutoMultiple/ItemUpload.vue +241 -241
  34. package/dist/runtime/components/Form/InputUploadDropzoneAutoMultiple/ItemView.vue +89 -89
  35. package/dist/runtime/components/Form/InputUploadDropzoneAutoMultiple/index.vue +170 -170
  36. package/dist/runtime/components/Form/InputUploadDropzoneImageAutoMultiple/ItemUpload.vue +161 -161
  37. package/dist/runtime/components/Form/InputUploadDropzoneImageAutoMultiple/ItemView.vue +64 -64
  38. package/dist/runtime/components/Form/InputUploadDropzoneImageAutoMultiple/index.vue +178 -178
  39. package/dist/runtime/components/Form/InputUploadFileClassic/index.vue +95 -95
  40. package/dist/runtime/components/Form/InputUploadFileClassicAuto/index.vue +151 -151
  41. package/dist/runtime/components/Form/InputUploadImageAuto/index.vue +219 -219
  42. package/dist/runtime/components/Form/InputWYSIWYG/UploadImageForm.vue +55 -55
  43. package/dist/runtime/components/Form/InputWYSIWYG/index.vue +228 -228
  44. package/dist/runtime/components/Form/index.vue +6 -6
  45. package/dist/runtime/components/Icon.vue +23 -23
  46. package/dist/runtime/components/Image.vue +36 -36
  47. package/dist/runtime/components/Loader.vue +27 -27
  48. package/dist/runtime/components/Modal/index.vue +146 -146
  49. package/dist/runtime/components/QRCode.vue +22 -22
  50. package/dist/runtime/components/SimplePagination.vue +96 -96
  51. package/dist/runtime/components/Slideover/index.vue +110 -110
  52. package/dist/runtime/components/Table/Base.vue +153 -153
  53. package/dist/runtime/components/Table/ColumnDate.vue +16 -16
  54. package/dist/runtime/components/Table/ColumnDateTime.vue +18 -18
  55. package/dist/runtime/components/Table/ColumnImage.vue +15 -15
  56. package/dist/runtime/components/Table/ColumnNumber.vue +14 -14
  57. package/dist/runtime/components/Table/ColumnText.vue +29 -29
  58. package/dist/runtime/components/Table/Simple.vue +69 -69
  59. package/dist/runtime/components/Table/index.vue +65 -65
  60. package/dist/runtime/components/Tabs/index.vue +64 -64
  61. package/dist/runtime/components/TeleportSafe.vue +40 -40
  62. package/package.json +103 -102
  63. package/dist/runtime/components/Form/InputDateTime/index.vue~ +0 -61
  64. package/dist/runtime/ui.config/table.ts~ +0 -48
@@ -1,230 +1,230 @@
1
- <template>
2
- <FieldWrapper v-bind="wrapperProps">
3
- <Modal v-model="isShowUploadImageModal" title="อัพโหลดรูปภาพ">
4
- <UploadImageForm :options="image" @submit="onImageSubmit" />
5
- </Modal>
6
- <ClientOnly>
7
- <div
8
- class="form-textarea focus:ring-primary-500 dark:focus:ring-primary-400 relative block w-full resize-none rounded-md border-0 bg-white p-0 pb-3 text-sm text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 placeholder:text-gray-400 focus:outline-none focus:ring-2 disabled:cursor-not-allowed disabled:opacity-75 dark:bg-gray-900 dark:text-white dark:ring-gray-700 dark:placeholder:text-gray-500"
9
- >
10
- <div class="tiptap-menu-bar">
11
- <div
12
- v-for="(items, index) in menuItems"
13
- :key="index"
14
- class="flex items-center border-r border-gray-200 pr-2"
15
- >
16
- <button
17
- v-for="item in items"
18
- :key="item.name"
19
- :class="{ 'is-active': item.isActive?.() }"
20
- class="menu-item"
21
- type="button"
22
- :title="item.title"
23
- @click="item.action"
24
- >
25
- <Icon :name="item.icon" class="size-5" />
26
- </button>
27
- </div>
28
- </div>
29
- <EditorContent
30
- :editor="editor"
31
- :placeholder="placeholder ?? label"
32
- :autofocus="autoFocus"
33
- :disabled="isDisabled || isReadonly"
34
- :name="name"
35
- />
36
- </div>
37
- </ClientOnly>
38
- </FieldWrapper>
39
- </template>
40
- <script lang="ts" setup>
41
- import { useFieldHOC } from '#core/composables/useForm'
42
- import FieldWrapper from '#core/components/Form/FieldWrapper.vue'
43
- import { EditorContent, useEditor } from '@tiptap/vue-3'
44
- import type { IWYSIWYGFieldProps } from '#core/components/Form/InputWYSIWYG/types'
45
- import StarterKit from '@tiptap/starter-kit'
46
- import Underline from '@tiptap/extension-underline'
47
- import TextAlign from '@tiptap/extension-text-align'
48
- import Link from '@tiptap/extension-link'
49
- import Image from '@tiptap/extension-image'
50
- import Youtube from '@tiptap/extension-youtube'
51
- import { computed, watch, ref } from 'vue'
52
- import UploadImageForm from '#core/components/Form/InputWYSIWYG/UploadImageForm.vue'
53
-
54
- const props = withDefaults(defineProps<IWYSIWYGFieldProps>(), {})
55
- const { value, wrapperProps } = useFieldHOC<string>(props)
56
- const isShowUploadImageModal = ref(false)
57
- const editor = useEditor({
58
- content: value.value,
59
- extensions: [
60
- StarterKit,
61
- Underline,
62
- TextAlign.configure({
63
- types: ['heading', 'paragraph'],
64
- }),
65
- Link.configure({
66
- openOnClick: false,
67
- }),
68
- Image,
69
- Youtube,
70
- ],
71
- editorProps: {
72
- attributes: {
73
- class: 'prose m-4 focus:outline-none',
74
- },
75
- },
76
- onUpdate: ({ editor }) => {
77
- value.value = editor.getHTML()
78
- },
79
- })
80
-
81
- watch(value, (newValue) => {
82
- if (editor.value && newValue !== editor.value.getHTML()) {
83
- editor.value.commands.setContent(newValue)
84
- }
85
- })
86
-
87
- const menuItems = computed(() => [
88
- [
89
- {
90
- name: 'bold',
91
- icon: 'ph:text-b-bold',
92
- action: () => editor.value!.chain().focus().toggleBold().run(),
93
- isActive: () => editor.value!.isActive('bold'),
94
- title: 'Bold',
95
- },
96
- {
97
- name: 'italic',
98
- icon: 'ph:text-italic',
99
- action: () => editor.value!.chain().focus().toggleItalic().run(),
100
- isActive: () => editor.value!.isActive('italic'),
101
- title: 'Italic',
102
- },
103
- {
104
- name: 'underline',
105
- icon: 'ph:text-underline',
106
- action: () => editor.value!.chain().focus().toggleUnderline().run(),
107
- isActive: () => editor.value!.isActive('underline'),
108
- title: 'Underline',
109
- },
110
- ],
111
- [
112
- {
113
- name: 'bullet-list',
114
- icon: 'ph:list-bullets',
115
- action: () => editor.value!.chain().focus().toggleBulletList().run(),
116
- isActive: () => editor.value!.isActive('bulletList'),
117
- title: 'Bullet List',
118
- },
119
- {
120
- name: 'ordered-list',
121
- icon: 'ph:list-numbers',
122
- action: () => editor.value!.chain().focus().toggleOrderedList().run(),
123
- isActive: () => editor.value!.isActive('orderedList'),
124
- title: 'Ordered List',
125
- },
126
- ],
127
- [
128
- {
129
- name: 'align-left',
130
- icon: 'ph:text-align-left',
131
- action: () => editor.value!.chain().focus().setTextAlign('left').run(),
132
- isActive: () => editor.value!.isActive({ textAlign: 'left' }),
133
- title: 'Align Left',
134
- },
135
- {
136
- name: 'align-center',
137
- icon: 'ph:text-align-center',
138
- action: () => editor.value!.chain().focus().setTextAlign('center').run(),
139
- isActive: () => editor.value!.isActive({ textAlign: 'center' }),
140
- title: 'Align Center',
141
- },
142
- {
143
- name: 'align-right',
144
- icon: 'ph:text-align-right',
145
- action: () => editor.value!.chain().focus().setTextAlign('right').run(),
146
- isActive: () => editor.value!.isActive({ textAlign: 'right' }),
147
- title: 'Align Right',
148
- },
149
- ],
150
- [
151
- {
152
- name: 'link',
153
- icon: 'ph:link-simple',
154
- action: () => {
155
- const url = window.prompt('URL')
156
-
157
- if (url) {
158
- editor.value!.chain().focus().setLink({ href: url }).run()
159
- }
160
- },
161
- isActive: () => editor.value!.isActive('link'),
162
- title: 'Insert Link',
163
- },
164
- {
165
- name: 'image',
166
- icon: 'ph:image',
167
- action: () => {
168
- if (props.image?.requestOptions) {
169
- isShowUploadImageModal.value = true
170
- } else {
171
- const url = window.prompt('Image URL')
172
-
173
- if (url) {
174
- editor.value!.chain().focus().setImage({ src: url }).run()
175
- }
176
- }
177
- },
178
- title: 'Insert Image',
179
- },
180
- {
181
- name: 'video',
182
- icon: 'ph:video-camera',
183
- action: () => {
184
- const url = window.prompt('Video URL')
185
-
186
- if (url) {
187
- editor.value!.chain().focus().setYoutubeVideo({ src: url }).run()
188
- }
189
- },
190
- title: 'Insert Video',
191
- },
192
- ],
193
- [
194
- {
195
- name: 'hardBreak',
196
- icon: 'ri:text-wrap',
197
- action: () => editor.value!.chain().focus().setHardBreak().run(),
198
- title: 'Hard Break',
199
- },
200
- {
201
- name: 'clear',
202
- icon: 'ri:format-clear',
203
- action: () => editor.value!.chain().focus().clearNodes().unsetAllMarks().run(),
204
- title: 'Clear',
205
- },
206
- ],
207
- [
208
- {
209
- name: 'undo',
210
- icon: 'ph:arrow-counter-clockwise',
211
- action: () => editor.value!.chain().focus().undo().run(),
212
- title: 'Undo',
213
- },
214
- {
215
- name: 'redo',
216
- icon: 'ph:arrow-clockwise',
217
- action: () => editor.value!.chain().focus().redo().run(),
218
- title: 'Redo',
219
- },
220
- ],
221
- ])
222
-
223
- const onImageSubmit = (url: string) => {
224
- editor.value!.chain().focus().setImage({ src: url }).run()
225
- isShowUploadImageModal.value = false
226
- }
227
- </script>
1
+ <template>
2
+ <FieldWrapper v-bind="wrapperProps">
3
+ <Modal v-model="isShowUploadImageModal" title="อัพโหลดรูปภาพ">
4
+ <UploadImageForm :options="image" @submit="onImageSubmit" />
5
+ </Modal>
6
+ <ClientOnly>
7
+ <div
8
+ class="form-textarea focus:ring-primary-500 dark:focus:ring-primary-400 relative block w-full resize-none rounded-md border-0 bg-white p-0 pb-3 text-sm text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 placeholder:text-gray-400 focus:outline-none focus:ring-2 disabled:cursor-not-allowed disabled:opacity-75 dark:bg-gray-900 dark:text-white dark:ring-gray-700 dark:placeholder:text-gray-500"
9
+ >
10
+ <div class="tiptap-menu-bar">
11
+ <div
12
+ v-for="(items, index) in menuItems"
13
+ :key="index"
14
+ class="flex items-center border-r border-gray-200 pr-2"
15
+ >
16
+ <button
17
+ v-for="item in items"
18
+ :key="item.name"
19
+ :class="{ 'is-active': item.isActive?.() }"
20
+ class="menu-item"
21
+ type="button"
22
+ :title="item.title"
23
+ @click="item.action"
24
+ >
25
+ <Icon :name="item.icon" class="size-5" />
26
+ </button>
27
+ </div>
28
+ </div>
29
+ <EditorContent
30
+ :editor="editor"
31
+ :placeholder="placeholder ?? label"
32
+ :autofocus="autoFocus"
33
+ :disabled="isDisabled || isReadonly"
34
+ :name="name"
35
+ />
36
+ </div>
37
+ </ClientOnly>
38
+ </FieldWrapper>
39
+ </template>
40
+ <script lang="ts" setup>
41
+ import { useFieldHOC } from '#core/composables/useForm'
42
+ import FieldWrapper from '#core/components/Form/FieldWrapper.vue'
43
+ import { EditorContent, useEditor } from '@tiptap/vue-3'
44
+ import type { IWYSIWYGFieldProps } from '#core/components/Form/InputWYSIWYG/types'
45
+ import StarterKit from '@tiptap/starter-kit'
46
+ import Underline from '@tiptap/extension-underline'
47
+ import TextAlign from '@tiptap/extension-text-align'
48
+ import Link from '@tiptap/extension-link'
49
+ import Image from '@tiptap/extension-image'
50
+ import Youtube from '@tiptap/extension-youtube'
51
+ import { computed, watch, ref } from 'vue'
52
+ import UploadImageForm from '#core/components/Form/InputWYSIWYG/UploadImageForm.vue'
53
+
54
+ const props = withDefaults(defineProps<IWYSIWYGFieldProps>(), {})
55
+ const { value, wrapperProps } = useFieldHOC<string>(props)
56
+ const isShowUploadImageModal = ref(false)
57
+ const editor = useEditor({
58
+ content: value.value,
59
+ extensions: [
60
+ StarterKit,
61
+ Underline,
62
+ TextAlign.configure({
63
+ types: ['heading', 'paragraph'],
64
+ }),
65
+ Link.configure({
66
+ openOnClick: false,
67
+ }),
68
+ Image,
69
+ Youtube,
70
+ ],
71
+ editorProps: {
72
+ attributes: {
73
+ class: 'prose m-4 focus:outline-none',
74
+ },
75
+ },
76
+ onUpdate: ({ editor }) => {
77
+ value.value = editor.getHTML()
78
+ },
79
+ })
80
+
81
+ watch(value, (newValue) => {
82
+ if (editor.value && newValue !== editor.value.getHTML()) {
83
+ editor.value.commands.setContent(newValue)
84
+ }
85
+ })
86
+
87
+ const menuItems = computed(() => [
88
+ [
89
+ {
90
+ name: 'bold',
91
+ icon: 'ph:text-b-bold',
92
+ action: () => editor.value!.chain().focus().toggleBold().run(),
93
+ isActive: () => editor.value!.isActive('bold'),
94
+ title: 'Bold',
95
+ },
96
+ {
97
+ name: 'italic',
98
+ icon: 'ph:text-italic',
99
+ action: () => editor.value!.chain().focus().toggleItalic().run(),
100
+ isActive: () => editor.value!.isActive('italic'),
101
+ title: 'Italic',
102
+ },
103
+ {
104
+ name: 'underline',
105
+ icon: 'ph:text-underline',
106
+ action: () => editor.value!.chain().focus().toggleUnderline().run(),
107
+ isActive: () => editor.value!.isActive('underline'),
108
+ title: 'Underline',
109
+ },
110
+ ],
111
+ [
112
+ {
113
+ name: 'bullet-list',
114
+ icon: 'ph:list-bullets',
115
+ action: () => editor.value!.chain().focus().toggleBulletList().run(),
116
+ isActive: () => editor.value!.isActive('bulletList'),
117
+ title: 'Bullet List',
118
+ },
119
+ {
120
+ name: 'ordered-list',
121
+ icon: 'ph:list-numbers',
122
+ action: () => editor.value!.chain().focus().toggleOrderedList().run(),
123
+ isActive: () => editor.value!.isActive('orderedList'),
124
+ title: 'Ordered List',
125
+ },
126
+ ],
127
+ [
128
+ {
129
+ name: 'align-left',
130
+ icon: 'ph:text-align-left',
131
+ action: () => editor.value!.chain().focus().setTextAlign('left').run(),
132
+ isActive: () => editor.value!.isActive({ textAlign: 'left' }),
133
+ title: 'Align Left',
134
+ },
135
+ {
136
+ name: 'align-center',
137
+ icon: 'ph:text-align-center',
138
+ action: () => editor.value!.chain().focus().setTextAlign('center').run(),
139
+ isActive: () => editor.value!.isActive({ textAlign: 'center' }),
140
+ title: 'Align Center',
141
+ },
142
+ {
143
+ name: 'align-right',
144
+ icon: 'ph:text-align-right',
145
+ action: () => editor.value!.chain().focus().setTextAlign('right').run(),
146
+ isActive: () => editor.value!.isActive({ textAlign: 'right' }),
147
+ title: 'Align Right',
148
+ },
149
+ ],
150
+ [
151
+ {
152
+ name: 'link',
153
+ icon: 'ph:link-simple',
154
+ action: () => {
155
+ const url = window.prompt('URL')
156
+
157
+ if (url) {
158
+ editor.value!.chain().focus().setLink({ href: url }).run()
159
+ }
160
+ },
161
+ isActive: () => editor.value!.isActive('link'),
162
+ title: 'Insert Link',
163
+ },
164
+ {
165
+ name: 'image',
166
+ icon: 'ph:image',
167
+ action: () => {
168
+ if (props.image?.requestOptions) {
169
+ isShowUploadImageModal.value = true
170
+ } else {
171
+ const url = window.prompt('Image URL')
172
+
173
+ if (url) {
174
+ editor.value!.chain().focus().setImage({ src: url }).run()
175
+ }
176
+ }
177
+ },
178
+ title: 'Insert Image',
179
+ },
180
+ {
181
+ name: 'video',
182
+ icon: 'ph:video-camera',
183
+ action: () => {
184
+ const url = window.prompt('Video URL')
185
+
186
+ if (url) {
187
+ editor.value!.chain().focus().setYoutubeVideo({ src: url }).run()
188
+ }
189
+ },
190
+ title: 'Insert Video',
191
+ },
192
+ ],
193
+ [
194
+ {
195
+ name: 'hardBreak',
196
+ icon: 'ri:text-wrap',
197
+ action: () => editor.value!.chain().focus().setHardBreak().run(),
198
+ title: 'Hard Break',
199
+ },
200
+ {
201
+ name: 'clear',
202
+ icon: 'ri:format-clear',
203
+ action: () => editor.value!.chain().focus().clearNodes().unsetAllMarks().run(),
204
+ title: 'Clear',
205
+ },
206
+ ],
207
+ [
208
+ {
209
+ name: 'undo',
210
+ icon: 'ph:arrow-counter-clockwise',
211
+ action: () => editor.value!.chain().focus().undo().run(),
212
+ title: 'Undo',
213
+ },
214
+ {
215
+ name: 'redo',
216
+ icon: 'ph:arrow-clockwise',
217
+ action: () => editor.value!.chain().focus().redo().run(),
218
+ title: 'Redo',
219
+ },
220
+ ],
221
+ ])
222
+
223
+ const onImageSubmit = (url: string) => {
224
+ editor.value!.chain().focus().setImage({ src: url }).run()
225
+ isShowUploadImageModal.value = false
226
+ }
227
+ </script>
228
228
  <style>
229
229
  .tiptap-menu-bar{@apply flex flex-wrap border-b py-2 px-2 gap-1}.menu-item{@apply px-1 py-1 rounded hover:bg-gray-100 transition-colors flex justify-center items-center}.menu-item.is-active{@apply bg-primary-100 text-primary-600}
230
- </style>
230
+ </style>
@@ -1,6 +1,6 @@
1
- <template>
2
- <form class="form">
3
- <slot />
4
- </form>
5
- </template>
6
- <script lang="ts" setup></script>
1
+ <template>
2
+ <form class="form">
3
+ <slot />
4
+ </form>
5
+ </template>
6
+ <script lang="ts" setup></script>
@@ -1,23 +1,23 @@
1
- <template>
2
- <UIcon :name="name" :dynamic="dynamicValue" />
3
- </template>
4
-
5
- <script lang="ts" setup>
6
- import { computed, useUiConfig } from '#imports'
7
- import { icon } from '#core/ui.config'
8
-
9
- const props = defineProps({
10
- name: {
11
- type: String,
12
- required: true,
13
- },
14
- dynamic: {
15
- type: Boolean,
16
- default: false,
17
- },
18
- })
19
-
20
- const config = useUiConfig<typeof icon>(icon, 'icon')
21
-
22
- const dynamicValue = computed(() => props.dynamic || config.dynamic)
23
- </script>
1
+ <template>
2
+ <UIcon :name="name" :dynamic="dynamicValue" />
3
+ </template>
4
+
5
+ <script lang="ts" setup>
6
+ import { computed, useUiConfig } from '#imports'
7
+ import { icon } from '#core/ui.config'
8
+
9
+ const props = defineProps({
10
+ name: {
11
+ type: String,
12
+ required: true,
13
+ },
14
+ dynamic: {
15
+ type: Boolean,
16
+ default: false,
17
+ },
18
+ })
19
+
20
+ const config = useUiConfig<typeof icon>(icon, 'icon')
21
+
22
+ const dynamicValue = computed(() => props.dynamic || config.dynamic)
23
+ </script>
@@ -1,36 +1,36 @@
1
- <template>
2
- <img :src="getSrc" />
3
- </template>
4
- <script lang="ts" setup>
5
- import { useImage } from '@vueuse/core'
6
- import { computed } from 'vue'
7
-
8
- const props = defineProps({
9
- src: {
10
- type: String,
11
- required: true,
12
- },
13
- loadingSrc: {
14
- type: String,
15
- default: '',
16
- },
17
- errorSrc: {
18
- type: String,
19
- default: '',
20
- },
21
- })
22
-
23
- const { isLoading, error } = useImage({ src: props.src })
24
-
25
- const getSrc = computed(() => {
26
- if (isLoading.value) {
27
- return props.loadingSrc
28
- }
29
-
30
- if (error.value) {
31
- return props.errorSrc
32
- }
33
-
34
- return props.src
35
- })
36
- </script>
1
+ <template>
2
+ <img :src="getSrc" />
3
+ </template>
4
+ <script lang="ts" setup>
5
+ import { useImage } from '@vueuse/core'
6
+ import { computed } from 'vue'
7
+
8
+ const props = defineProps({
9
+ src: {
10
+ type: String,
11
+ required: true,
12
+ },
13
+ loadingSrc: {
14
+ type: String,
15
+ default: '',
16
+ },
17
+ errorSrc: {
18
+ type: String,
19
+ default: '',
20
+ },
21
+ })
22
+
23
+ const { isLoading, error } = useImage({ src: props.src })
24
+
25
+ const getSrc = computed(() => {
26
+ if (isLoading.value) {
27
+ return props.loadingSrc
28
+ }
29
+
30
+ if (error.value) {
31
+ return props.errorSrc
32
+ }
33
+
34
+ return props.src
35
+ })
36
+ </script>
@@ -1,27 +1,27 @@
1
- <template>
2
- <div
3
- v-if="isLoading"
4
- :class="[
5
- 'flex w-full items-center justify-center',
6
- $attrs.class,
7
- {
8
- 'min-h-[200px]': !$attrs.class,
9
- },
10
- ]"
11
- >
12
- <UIcon name="i-svg-spinners:180-ring-with-bg" class="text-primary text-4xl" dynamic />
13
- </div>
14
- <slot v-else />
15
- </template>
16
- <script lang="ts" setup>
17
- defineOptions({
18
- inheritAttrs: false,
19
- })
20
-
21
- defineProps({
22
- isLoading: {
23
- type: Boolean,
24
- default: true,
25
- },
26
- })
27
- </script>
1
+ <template>
2
+ <div
3
+ v-if="isLoading"
4
+ :class="[
5
+ 'flex w-full items-center justify-center',
6
+ $attrs.class,
7
+ {
8
+ 'min-h-[200px]': !$attrs.class,
9
+ },
10
+ ]"
11
+ >
12
+ <UIcon name="i-svg-spinners:180-ring-with-bg" class="text-primary text-4xl" dynamic />
13
+ </div>
14
+ <slot v-else />
15
+ </template>
16
+ <script lang="ts" setup>
17
+ defineOptions({
18
+ inheritAttrs: false,
19
+ })
20
+
21
+ defineProps({
22
+ isLoading: {
23
+ type: Boolean,
24
+ default: true,
25
+ },
26
+ })
27
+ </script>