@finema/core 1.4.201 → 1.4.203

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 (72) hide show
  1. package/README.md +60 -60
  2. package/dist/module.d.mts +1 -0
  3. package/dist/module.d.ts +1 -0
  4. package/dist/module.json +1 -1
  5. package/dist/module.mjs +2 -1
  6. package/dist/runtime/components/Alert.vue +48 -48
  7. package/dist/runtime/components/Avatar.vue +27 -27
  8. package/dist/runtime/components/Badge.vue +11 -11
  9. package/dist/runtime/components/Breadcrumb.vue +44 -44
  10. package/dist/runtime/components/Button/Group.vue +37 -37
  11. package/dist/runtime/components/Button/index.vue +75 -75
  12. package/dist/runtime/components/Card.vue +38 -38
  13. package/dist/runtime/components/Core.vue +45 -45
  14. package/dist/runtime/components/Dialog/index.vue +108 -108
  15. package/dist/runtime/components/Dropdown/index.vue +70 -70
  16. package/dist/runtime/components/FlexDeck/Base.vue +152 -152
  17. package/dist/runtime/components/FlexDeck/index.vue +68 -68
  18. package/dist/runtime/components/Form/FieldWrapper.vue +23 -23
  19. package/dist/runtime/components/Form/Fields.vue +230 -230
  20. package/dist/runtime/components/Form/InputCheckbox/index.vue +28 -28
  21. package/dist/runtime/components/Form/InputDateTime/index.vue +61 -61
  22. package/dist/runtime/components/Form/InputDateTimeRange/index.vue +83 -83
  23. package/dist/runtime/components/Form/InputNumber/index.vue +27 -27
  24. package/dist/runtime/components/Form/InputRadio/index.vue +27 -27
  25. package/dist/runtime/components/Form/InputSelect/index.vue +45 -45
  26. package/dist/runtime/components/Form/InputSelectMultiple/index.vue +54 -54
  27. package/dist/runtime/components/Form/InputStatic/index.vue +16 -16
  28. package/dist/runtime/components/Form/InputTags/index.vue +141 -141
  29. package/dist/runtime/components/Form/InputText/index.vue +68 -68
  30. package/dist/runtime/components/Form/InputTextarea/index.vue +25 -25
  31. package/dist/runtime/components/Form/InputToggle/index.vue +27 -27
  32. package/dist/runtime/components/Form/InputUploadDropzone/index.vue +206 -206
  33. package/dist/runtime/components/Form/InputUploadDropzoneAuto/index.vue +342 -342
  34. package/dist/runtime/components/Form/InputUploadDropzoneAutoMultiple/ItemUpload.vue +241 -241
  35. package/dist/runtime/components/Form/InputUploadDropzoneAutoMultiple/ItemView.vue +89 -89
  36. package/dist/runtime/components/Form/InputUploadDropzoneAutoMultiple/index.vue +170 -170
  37. package/dist/runtime/components/Form/InputUploadDropzoneImageAutoMultiple/ItemUpload.vue +161 -161
  38. package/dist/runtime/components/Form/InputUploadDropzoneImageAutoMultiple/ItemView.vue +64 -64
  39. package/dist/runtime/components/Form/InputUploadDropzoneImageAutoMultiple/index.vue +178 -178
  40. package/dist/runtime/components/Form/InputUploadFileClassic/index.vue +95 -95
  41. package/dist/runtime/components/Form/InputUploadFileClassicAuto/index.vue +151 -151
  42. package/dist/runtime/components/Form/InputUploadImageAuto/index.vue +219 -219
  43. package/dist/runtime/components/Form/InputWYSIWYG/UploadImageForm.vue +55 -55
  44. package/dist/runtime/components/Form/InputWYSIWYG/index.vue +228 -228
  45. package/dist/runtime/components/Form/index.vue +6 -6
  46. package/dist/runtime/components/Icon.vue +23 -23
  47. package/dist/runtime/components/Image.vue +36 -36
  48. package/dist/runtime/components/Loader.vue +27 -27
  49. package/dist/runtime/components/Modal/index.vue +146 -146
  50. package/dist/runtime/components/QRCode.vue +22 -22
  51. package/dist/runtime/components/SimplePagination.vue +96 -96
  52. package/dist/runtime/components/Slideover/index.vue +110 -110
  53. package/dist/runtime/components/Table/Base.vue +153 -153
  54. package/dist/runtime/components/Table/ColumnDate.vue +16 -16
  55. package/dist/runtime/components/Table/ColumnDateTime.vue +18 -18
  56. package/dist/runtime/components/Table/ColumnImage.vue +15 -15
  57. package/dist/runtime/components/Table/ColumnNumber.vue +14 -14
  58. package/dist/runtime/components/Table/ColumnText.vue +29 -29
  59. package/dist/runtime/components/Table/Simple.vue +69 -69
  60. package/dist/runtime/components/Table/index.vue +65 -65
  61. package/dist/runtime/components/Tabs/index.vue +64 -64
  62. package/dist/runtime/components/TeleportSafe.vue +40 -40
  63. package/dist/runtime/core.config.d.ts +1 -0
  64. package/dist/runtime/core.config.mjs +2 -1
  65. package/dist/runtime/ui.config/notifications.d.ts +3 -0
  66. package/dist/runtime/ui.config/notifications.mjs +3 -0
  67. package/dist/runtime/utils/TimeHelper.mjs +15 -10
  68. package/dist/runtime/utils/TimeHelper.spec.mjs +11 -4
  69. package/dist/runtime/utils/TimeHelper.thai.spec.mjs +2 -1
  70. package/package.json +102 -101
  71. package/dist/runtime/components/Form/InputDateTime/index.vue~ +0 -61
  72. 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>