@finema/core 1.4.106 → 1.4.108

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/dist/module.d.mts CHANGED
@@ -28,6 +28,7 @@ interface ModuleOptions {
28
28
  * @default false
29
29
  */
30
30
  global?: boolean;
31
+ securityDisabled?: boolean;
31
32
  }
32
33
  type CORE = {
33
34
  strategy?: 'merge' | 'override';
package/dist/module.d.ts CHANGED
@@ -28,6 +28,7 @@ interface ModuleOptions {
28
28
  * @default false
29
29
  */
30
30
  global?: boolean;
31
+ securityDisabled?: boolean;
31
32
  }
32
33
  type CORE = {
33
34
  strategy?: 'merge' | 'override';
package/dist/module.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@finema/core",
3
- "version": "1.4.106",
3
+ "version": "1.4.108",
4
4
  "configKey": "core",
5
5
  "compatibility": {
6
6
  "nuxt": "^3.7.4"
package/dist/module.mjs CHANGED
@@ -1,7 +1,7 @@
1
1
  import { defineNuxtModule, createResolver, installModule, addPlugin, addComponentsDir, addImportsDir } from '@nuxt/kit';
2
2
 
3
3
  const name = "@finema/core";
4
- const version = "1.4.106";
4
+ const version = "1.4.108";
5
5
 
6
6
  const colors = {
7
7
  black: "#20243E",
@@ -398,7 +398,9 @@ const module = defineNuxtModule({
398
398
  };
399
399
  await installModule("@pinia/nuxt");
400
400
  await installModule("@vee-validate/nuxt", veeValidateNuxtOptions);
401
- await installModule("nuxt-security", nuxtSecurityOptions);
401
+ if (!options.securityDisabled) {
402
+ await installModule("nuxt-security", nuxtSecurityOptions);
403
+ }
402
404
  addPlugin({
403
405
  src: resolve(runtimeDir, "plugin")
404
406
  });
@@ -13,7 +13,6 @@ import type { ButtonColor, ButtonSize, ButtonVariant } from '#ui/types/button'
13
13
  import type { PropType } from 'vue'
14
14
 
15
15
  defineProps({
16
- ...UButton.props,
17
16
  label: {
18
17
  type: String,
19
18
  },
@@ -123,6 +123,14 @@
123
123
  v-bind="getFieldBinding(option)"
124
124
  v-on="option.on ?? {}"
125
125
  />
126
+ <FormInputUploadImageAuto
127
+ v-else-if="option.type === INPUT_TYPES.UPLOAD_IMAGE_AUTO"
128
+ :class="option.class"
129
+ :form="form"
130
+ :request-options="option.props.requestOptions"
131
+ v-bind="getFieldBinding(option)"
132
+ v-on="option.on ?? {}"
133
+ />
126
134
  <FormInputUploadDropzone
127
135
  v-else-if="option.type === INPUT_TYPES.UPLOAD_DROPZONE"
128
136
  :class="option.class"
@@ -161,6 +169,13 @@
161
169
  v-bind="getFieldBinding(option)"
162
170
  v-on="option.on ?? {}"
163
171
  />
172
+ <FormInputTags
173
+ v-else-if="option.type === INPUT_TYPES.TAGS"
174
+ :class="option.class"
175
+ :form="form"
176
+ v-bind="getFieldBinding(option)"
177
+ v-on="option.on ?? {}"
178
+ />
164
179
  <component
165
180
  :is="option.component"
166
181
  v-else-if="option.type === INPUT_TYPES.COMPONENT"
@@ -0,0 +1,134 @@
1
+ <template>
2
+ <FieldWrapper v-bind="wrapperProps">
3
+ <div
4
+ :class="[
5
+ ui.base,
6
+ ui.border,
7
+ ui.shadow,
8
+ ui.padding,
9
+ {
10
+ [ui.stages.default]: !wrapperProps.errorMessage,
11
+ [ui.stages.error]: wrapperProps.errorMessage,
12
+ },
13
+ ]"
14
+ >
15
+ <p
16
+ v-if="wrapperProps.placeholder && !innerValue && (!value || value.length === 0)"
17
+ :class="[ui.placeholder]"
18
+ @click="focusInput"
19
+ >
20
+ {{ wrapperProps.placeholder }}
21
+ </p>
22
+ <div :class="[ui.innerWrapper]">
23
+ <Badge
24
+ v-for="(text, index) in value"
25
+ :key="index"
26
+ :size="ui.tag.size || (ui.default.tag.size as any)"
27
+ :color="ui.tag.color || (ui.default.tag.color as any)"
28
+ :variant="ui.tag.variant || (ui.default.tag.variant as any)"
29
+ :ui="badgeUi"
30
+ >
31
+ {{ tagPrefix + text }}
32
+ </Badge>
33
+ <input
34
+ ref="inputRef"
35
+ v-model="innerValue"
36
+ :class="[ui.input]"
37
+ :disabled="wrapperProps.isDisabled"
38
+ :name="name"
39
+ :readonly="isReadonly"
40
+ :autofocus="!!autoFocus"
41
+ @blur="onBlur"
42
+ @keyup.delete="onDelete"
43
+ @keyup.enter="onEnter"
44
+ @keyup.space="onSpace"
45
+ />
46
+ </div>
47
+ </div>
48
+ </FieldWrapper>
49
+ </template>
50
+
51
+ <script lang="ts" setup>
52
+ import { ref, toRef, useUI, useUiConfig } from '#imports'
53
+ import { type ITagsFieldProps } from '#core/components/Form/InputTags/types'
54
+ import { tags } from '#core/ui.config'
55
+ import { useFieldHOC } from '#core/composables/useForm'
56
+ import FieldWrapper from '#core/components/Form/FieldWrapper.vue'
57
+
58
+ const config = useUiConfig<typeof tags>(tags, 'tags')
59
+
60
+ const props = withDefaults(defineProps<ITagsFieldProps>(), {
61
+ tagPrefix: '#',
62
+ })
63
+
64
+ const emits = defineEmits(['change', 'blur'])
65
+
66
+ const { value, wrapperProps } = useFieldHOC<string[]>(props)
67
+
68
+ const { ui } = useUI('tags', toRef(props, 'ui'), config)
69
+
70
+ const inputRef = ref<HTMLInputElement | null>(null)
71
+ const innerValue = ref<string>('')
72
+ const backspaceCount = ref<number>(0)
73
+
74
+ const operateValue = (input: string) => {
75
+ const tagSet = splitBySpace(input)
76
+
77
+ if (value.value && value.value.length > 0) {
78
+ value.value = [...value.value, ...tagSet]
79
+ } else {
80
+ value.value = tagSet
81
+ }
82
+
83
+ innerValue.value = ''
84
+
85
+ emits('change', value.value)
86
+ }
87
+
88
+ const onBlur = (e: FocusEvent) => {
89
+ const eventValue = e.target as HTMLInputElement
90
+
91
+ if (!eventValue.value || eventValue.value === '') return
92
+
93
+ operateValue(eventValue.value)
94
+
95
+ emits('blur', eventValue.value)
96
+ }
97
+
98
+ const onDelete = (e: KeyboardEvent) => {
99
+ if (e.key === 'Backspace') {
100
+ if (innerValue.value === '') {
101
+ backspaceCount.value += 1
102
+ } else {
103
+ backspaceCount.value = 0
104
+ }
105
+
106
+ if (backspaceCount.value >= 2 && value.value.length > 0) {
107
+ value.value.pop()
108
+ backspaceCount.value = 0
109
+ }
110
+ }
111
+ }
112
+
113
+ const onEnter = (e: KeyboardEvent) => {
114
+ if (e.key === 'Enter') {
115
+ operateValue(innerValue.value)
116
+ }
117
+ }
118
+
119
+ const onSpace = (e: KeyboardEvent) => {
120
+ if (e.key === ' ') {
121
+ operateValue(innerValue.value)
122
+ }
123
+ }
124
+
125
+ const splitBySpace = (input: string): string[] => {
126
+ const spited = input.split(' ')
127
+
128
+ return spited.filter((item) => item !== '')
129
+ }
130
+
131
+ const focusInput = () => {
132
+ inputRef.value?.focus()
133
+ }
134
+ </script>
@@ -0,0 +1,11 @@
1
+ import { type IFieldProps, type IFormFieldBase, type INPUT_TYPES } from '#core/components/Form/types';
2
+ export interface ITagsFieldProps extends IFieldProps {
3
+ leadingIcon?: string;
4
+ trailingIcon?: string;
5
+ tagPrefix?: string;
6
+ badgeUi?: object | any;
7
+ }
8
+ export type ITagsField = IFormFieldBase<INPUT_TYPES.TAGS, ITagsFieldProps, {
9
+ change?: (value: string[]) => void;
10
+ blur?: (value: string) => void;
11
+ }>;
@@ -20,7 +20,7 @@
20
20
  <div :class="[ui.imageItem.onPreview.wrapper]">
21
21
  <img
22
22
  :class="[ui.imageItem.onPreview.previewImgClass]"
23
- :src="upload.data.value[responseKey]"
23
+ :src="upload.data.value[responseKey || 'url']"
24
24
  alt="img"
25
25
  />
26
26
  <div :class="[ui.imageItem.onPreview.previewActionWrapper]">
@@ -46,7 +46,7 @@
46
46
  />
47
47
  </div>
48
48
  <img
49
- :src="upload.data.value[responseKey]"
49
+ :src="upload.data.value[responseKey || 'url']"
50
50
  alt="image-preview"
51
51
  class="w-full rounded-lg"
52
52
  />
@@ -0,0 +1,222 @@
1
+ <template>
2
+ <FieldWrapper v-bind="wrapperProps">
3
+ <div :class="[ui.base]">
4
+ <input
5
+ ref="fileInputRef"
6
+ type="file"
7
+ class="hidden"
8
+ :name="name"
9
+ :accept="acceptFile"
10
+ :disabled="isDisabled"
11
+ @change="handleChange"
12
+ />
13
+
14
+ <div :class="[ui.imageItem.wrapper]">
15
+ <div v-if="!selectedFile" class="w-full">
16
+ <div :class="[ui.action.addingWrapper]" @click="handleOpenFile">
17
+ <Icon :name="ui.action.addingIcon" :class="[ui.action.addingBtnClass]" />
18
+ <p :class="[ui.action.addingTextClass]">{{ uploadAddLabel }}</p>
19
+ </div>
20
+ </div>
21
+
22
+ <!-- Loading State -->
23
+ <div v-if="selectedFile && upload.status.value.isLoading" class="w-full">
24
+ <div :class="[ui.imageItem.onLoading.wrapper]">
25
+ <div :class="[ui.imageItem.onLoading.percentClass]">{{ percent }}%</div>
26
+ <UProgress :value="percent" />
27
+ </div>
28
+ </div>
29
+
30
+ <!-- Success State -->
31
+ <div v-if="selectedFile && upload.status.value.isSuccess" class="w-full">
32
+ <div :class="[ui.imageItem.onPreview.wrapper]">
33
+ <img
34
+ :class="[ui.imageItem.onPreview.previewImgClass]"
35
+ :src="upload.data.value[responseKey || 'url']"
36
+ alt="img"
37
+ />
38
+ <div :class="[ui.imageItem.onPreview.previewActionWrapper]">
39
+ <Icon
40
+ title="ดูตัวอย่าง"
41
+ :name="ui.action.previewIcon"
42
+ :class="[ui.imageItem.onPreview.actionBtnClass]"
43
+ @click="() => (isPreviewOpen = true)"
44
+ />
45
+ <Icon
46
+ title="ลบไฟล์"
47
+ :name="ui.action.deleteIcon"
48
+ :class="[ui.imageItem.onPreview.actionBtnClass]"
49
+ @click="handleDeleteFile"
50
+ />
51
+ <Modal v-model="isPreviewOpen" size="xl">
52
+ <div class="absolute -top-8 left-0 flex w-full justify-between text-gray-600">
53
+ <p>{{ selectedFile?.name }}</p>
54
+ <Icon
55
+ name="i-heroicons-x-mark"
56
+ class="size-6 cursor-pointer hover:text-gray-400"
57
+ @click="() => (isPreviewOpen = false)"
58
+ />
59
+ </div>
60
+ <img
61
+ :src="upload.data.value[responseKey || 'url']"
62
+ alt="img-preview"
63
+ class="w-full rounded-lg"
64
+ />
65
+ </Modal>
66
+ </div>
67
+ </div>
68
+
69
+ <div :class="[ui.imageItem.onPreview.previewTextWrapper]">
70
+ <p :class="[ui.imageItem.onPreview.previewText]">{{ selectedFile.name }}</p>
71
+ </div>
72
+ </div>
73
+
74
+ <!-- Failed State -->
75
+ <div v-if="selectedFile && upload.status.value.isError" class="w-full">
76
+ <div :class="[ui.imageItem.onFailed.wrapper]">
77
+ <img
78
+ :class="[ui.imageItem.onFailed.failedImgClass]"
79
+ :src="generateURL(selectedFile)"
80
+ alt="img"
81
+ />
82
+ <div :class="[ui.imageItem.onFailed.failedActionWrapper]">
83
+ <Icon
84
+ title="ลบไฟล์"
85
+ :name="ui.action.deleteIcon"
86
+ :class="[ui.imageItem.onFailed.actionBtnClass]"
87
+ @click="handleDeleteFile"
88
+ />
89
+ </div>
90
+ </div>
91
+ </div>
92
+ </div>
93
+ </div>
94
+ </FieldWrapper>
95
+ </template>
96
+
97
+ <script lang="ts" setup>
98
+ import { type IUploadImageAutoProps } from './types'
99
+ import FieldWrapper from '#core/components/Form/FieldWrapper.vue'
100
+ import { useFieldHOC } from '#core/composables/useForm'
101
+ import {
102
+ computed,
103
+ ref,
104
+ toRef,
105
+ useUI,
106
+ useUiConfig,
107
+ useWatchTrue,
108
+ useUploadLoader,
109
+ _get,
110
+ type IUploadRequest,
111
+ } from '#imports'
112
+ import { uploadImage } from '#core/ui.config'
113
+ import {
114
+ checkMaxSize,
115
+ generateURL,
116
+ useFileAllocate,
117
+ useFileProgress,
118
+ } from '#core/helpers/componentHelper'
119
+ import i18next from 'i18next'
120
+
121
+ const config = useUiConfig<typeof uploadImage>(uploadImage, 'uploadImage')
122
+
123
+ const props = withDefaults(defineProps<IUploadImageAutoProps>(), {
124
+ accept: 'image/*',
125
+ bodyKey: 'file',
126
+ responseKey: 'url',
127
+ uploadAddLabel: 'อัพโหลด',
128
+ })
129
+
130
+ const emits = defineEmits(['change', 'success', 'delete', 'error'])
131
+
132
+ const request: IUploadRequest = {
133
+ pathURL: props.uploadPathURL,
134
+ requestOptions: props.requestOptions,
135
+ }
136
+
137
+ const selectedFile = ref<File>()
138
+ const isPreviewOpen = ref<boolean>(false)
139
+
140
+ const { wrapperProps, value, errorMessage } = useFieldHOC<string | undefined>(props)
141
+
142
+ const upload = useUploadLoader(request)
143
+ const { ui } = useUI('uploadImage', toRef(props, 'ui'), config)
144
+
145
+ const fileInputRef = ref<HTMLInputElement | null>()
146
+
147
+ const fileAllocate = useFileAllocate(toRef(selectedFile), props)
148
+ const { onUploadProgress, onDownloadProgress, percent } = useFileProgress()
149
+ const acceptFile = computed(() =>
150
+ typeof props.accept === 'string' ? props.accept : props.accept?.join(',')
151
+ )
152
+
153
+ const handleChange = (e: Event) => {
154
+ if (props.isDisabled) return
155
+
156
+ const file = (e.target as HTMLInputElement).files?.[0]
157
+ const result = handleCheckFileCondition(file)
158
+
159
+ if (result && file) {
160
+ selectedFile.value = file
161
+ emits('change', selectedFile.value)
162
+ const formData = new FormData()
163
+
164
+ formData.append(props.bodyKey, file)
165
+ upload.run(formData, { data: { onUploadProgress, onDownloadProgress } })
166
+ }
167
+ }
168
+
169
+ const handleCheckFileCondition = (file: File | undefined): boolean => {
170
+ if (!file) return false
171
+ const maxSize = checkMaxSize(file, fileAllocate.acceptFileSizeKb.value)
172
+
173
+ if (!maxSize) {
174
+ if (fileAllocate.isAcceptFileUseMb.value) {
175
+ errorMessage.value = i18next.t('custom:invalid_file_size_mb', {
176
+ size: fileAllocate.acceptFileSizeMb.value,
177
+ })
178
+ } else {
179
+ errorMessage.value = i18next.t('custom:invalid_file_size_kb', {
180
+ size: fileAllocate.acceptFileSizeKb.value,
181
+ })
182
+ }
183
+
184
+ return false
185
+ }
186
+
187
+ errorMessage.value = ''
188
+
189
+ return true
190
+ }
191
+
192
+ const handleOpenFile = () => {
193
+ fileInputRef.value?.click()
194
+ }
195
+
196
+ const handleDeleteFile = () => {
197
+ fileInputRef.value!.value = ''
198
+ selectedFile.value = undefined
199
+ value.value = undefined
200
+ emits('change', undefined)
201
+
202
+ emits('delete')
203
+ }
204
+
205
+ useWatchTrue(
206
+ () => upload.status.value.isSuccess,
207
+ () => {
208
+ const response = _get(upload.data.value, props.responseKey, '')
209
+
210
+ value.value = response
211
+
212
+ emits('success', response)
213
+ }
214
+ )
215
+
216
+ useWatchTrue(
217
+ () => upload.status.value.isError,
218
+ () => {
219
+ emits('error', upload.status.value.errorData)
220
+ }
221
+ )
222
+ </script>
@@ -0,0 +1,19 @@
1
+ import { type AxiosRequestConfig } from 'axios';
2
+ import { type IFieldProps, type IFormFieldBase, type INPUT_TYPES } from '../types';
3
+ export interface IUploadImageAutoProps extends IFieldProps {
4
+ requestOptions: Omit<AxiosRequestConfig, 'baseURL'> & {
5
+ baseURL: string;
6
+ };
7
+ uploadPathURL?: string;
8
+ uploadAddLabel?: string;
9
+ accept?: string[] | string;
10
+ bodyKey?: string;
11
+ responseKey?: string;
12
+ maxSize?: number;
13
+ }
14
+ export type IUploadImageAutoField = IFormFieldBase<INPUT_TYPES.UPLOAD_IMAGE_AUTO, IUploadImageAutoProps, {
15
+ change: (value: File | undefined) => void;
16
+ success: (res: any) => void;
17
+ delete: () => void;
18
+ error: (err: any) => void;
19
+ }>;
@@ -11,12 +11,14 @@ import { type IDateTimeField } from '#core/components/Form/InputDateTime/date_ti
11
11
  import { type IDateTimeRangeField } from '#core/components/Form/InputDateTimeRange/date_range_time_field.types';
12
12
  import { type IUploadFileClassicField } from '#core/components/Form/InputUploadFileClassic/types';
13
13
  import { type IUploadFileField } from '#core/components/Form/InputUploadFileClassicAuto/types';
14
+ import { type IUploadImageAutoField } from '#core/components/Form/InputUploadImageAuto/types';
14
15
  import { type IUploadDropzoneField } from '#core/components/Form/InputUploadDropzone/types';
15
16
  import { type IUploadDropzoneAutoField } from '#core/components/Form/InputUploadDropzoneAuto/types';
16
17
  import type { INumberField } from '#core/components/Form/InputNumber/types';
17
18
  import type { IUploadDropzoneAutoMultipleField } from '#core/components/Form/InputUploadDropzoneAutoMultiple/types';
18
19
  import type { IUploadDropzoneImageAutoMultipleField } from '#core/components/Form/InputUploadDropzoneImageAutoMultiple/types';
19
20
  import type { IWYSIWYGField } from '#core/components/Form/InputWYSIWYG/types';
21
+ import type { ITagsField } from '#core/components/Form/InputTags/types';
20
22
  export declare const enum INPUT_TYPES {
21
23
  TEXT = "TEXT",
22
24
  NUMBER = "NUMBER",
@@ -34,11 +36,13 @@ export declare const enum INPUT_TYPES {
34
36
  DATE_TIME_RANGE = "DATE_TIME_RANGE",
35
37
  UPLOAD_FILE_CLASSIC = "UPLOAD_FILE_CLASSIC",
36
38
  UPLOAD_FILE_CLASSIC_AUTO = "UPLOAD_FILE_CLASSIC_AUTO",
39
+ UPLOAD_IMAGE_AUTO = "UPLOAD_IMAGE_AUTO",
37
40
  UPLOAD_DROPZONE = "UPLOAD_DROPZONE",
38
41
  UPLOAD_DROPZONE_AUTO = "UPLOAD_DROPZONE_AUTO",
39
42
  UPLOAD_DROPZONE_AUTO_MULTIPLE = "UPLOAD_DROPZONE_AUTO_MULTIPLE",
40
43
  UPLOAD_DROPZONE_IMAGE_AUTO_MULTIPLE = "UPLOAD_DROPZONE_IMAGE_AUTO_MULTIPLE",
41
44
  WYSIWYG = "WYSIWYG",
45
+ TAGS = "TAGS",
42
46
  COMPONENT = "COMPONENT"
43
47
  }
44
48
  export interface IFieldProps {
@@ -68,4 +72,4 @@ export interface IFormFieldBase<I extends INPUT_TYPES, P extends IFieldProps, O>
68
72
  props: P;
69
73
  on?: O;
70
74
  }
71
- export type IFormField = ITextField | INumberField | IStaticField | ICheckboxField | IRadioField | ISelectField | IToggleField | ITextareaField | IDateTimeField | IDateTimeRangeField | IUploadFileClassicField | IUploadFileField | IUploadDropzoneField | IUploadDropzoneAutoField | IUploadDropzoneAutoMultipleField | IUploadDropzoneImageAutoMultipleField | IWYSIWYGField | IFormFieldBase<INPUT_TYPES.COMPONENT, any, any>;
75
+ export type IFormField = ITextField | INumberField | IStaticField | ICheckboxField | IRadioField | ISelectField | IToggleField | ITextareaField | IDateTimeField | IDateTimeRangeField | IUploadFileClassicField | IUploadFileField | IUploadImageAutoField | IUploadDropzoneField | IUploadDropzoneAutoField | IUploadDropzoneAutoMultipleField | IUploadDropzoneImageAutoMultipleField | IWYSIWYGField | ITagsField | IFormFieldBase<INPUT_TYPES.COMPONENT, any, any>;
@@ -15,11 +15,13 @@ export var INPUT_TYPES = /* @__PURE__ */ ((INPUT_TYPES2) => {
15
15
  INPUT_TYPES2["DATE_TIME_RANGE"] = "DATE_TIME_RANGE";
16
16
  INPUT_TYPES2["UPLOAD_FILE_CLASSIC"] = "UPLOAD_FILE_CLASSIC";
17
17
  INPUT_TYPES2["UPLOAD_FILE_CLASSIC_AUTO"] = "UPLOAD_FILE_CLASSIC_AUTO";
18
+ INPUT_TYPES2["UPLOAD_IMAGE_AUTO"] = "UPLOAD_IMAGE_AUTO";
18
19
  INPUT_TYPES2["UPLOAD_DROPZONE"] = "UPLOAD_DROPZONE";
19
20
  INPUT_TYPES2["UPLOAD_DROPZONE_AUTO"] = "UPLOAD_DROPZONE_AUTO";
20
21
  INPUT_TYPES2["UPLOAD_DROPZONE_AUTO_MULTIPLE"] = "UPLOAD_DROPZONE_AUTO_MULTIPLE";
21
22
  INPUT_TYPES2["UPLOAD_DROPZONE_IMAGE_AUTO_MULTIPLE"] = "UPLOAD_DROPZONE_IMAGE_AUTO_MULTIPLE";
22
23
  INPUT_TYPES2["WYSIWYG"] = "WYSIWYG";
24
+ INPUT_TYPES2["TAGS"] = "TAGS";
23
25
  INPUT_TYPES2["COMPONENT"] = "COMPONENT";
24
26
  return INPUT_TYPES2;
25
27
  })(INPUT_TYPES || {});
@@ -2,7 +2,7 @@
2
2
  <div
3
3
  v-if="isLoading"
4
4
  :class="[
5
- 'flex items-center justify-center',
5
+ 'flex w-full items-center justify-center',
6
6
  $attrs.class,
7
7
  {
8
8
  'min-h-[200px]': !$attrs.class,
@@ -1,21 +1,22 @@
1
- <template>
2
- <QrcodeVue :value="value" :level="level" :render-as="renderAs" class="size-[350px]" />
3
- </template>
4
- <script lang="ts" setup>
5
- import QrcodeVue from 'qrcode.vue'
6
-
7
- defineProps({
8
- value: {
9
- type: String,
10
- required: true,
11
- },
12
- level: {
13
- type: String,
14
- default: 'M',
15
- },
16
- renderAs: {
17
- type: String,
18
- default: 'svg',
19
- },
20
- })
21
- </script>
1
+ <template>
2
+ <QrcodeVue :value="value" :level="level" :render-as="renderAs" class="size-[350px]" />
3
+ </template>
4
+ <script lang="ts" setup>
5
+ import QrcodeVue, { type RenderAs } from 'qrcode.vue'
6
+ import { type PropType } from 'vue'
7
+
8
+ defineProps({
9
+ value: {
10
+ type: String,
11
+ required: true,
12
+ },
13
+ level: {
14
+ type: String,
15
+ default: 'M',
16
+ },
17
+ renderAs: {
18
+ type: String as PropType<RenderAs>,
19
+ default: 'svg',
20
+ },
21
+ })
22
+ </script>
@@ -1 +1 @@
1
- export type UIComponentList = 'modal' | 'slideover' | 'dropdown' | 'icon' | 'button' | 'buttonGroup' | 'tabs' | 'card' | 'breadcrumb' | 'badge' | 'input' | 'pagination' | 'notification' | 'uploadFileInputClassicAuto' | 'uploadFileDropzone' | 'uploadFileDropzoneImage';
1
+ export type UIComponentList = 'modal' | 'slideover' | 'dropdown' | 'icon' | 'button' | 'buttonGroup' | 'tabs' | 'card' | 'breadcrumb' | 'badge' | 'input' | 'pagination' | 'notification' | 'uploadFileInputClassicAuto' | 'uploadFileDropzone' | 'uploadFileDropzoneImage' | 'uploadImage' | 'tags';
@@ -12,3 +12,5 @@ export { breadcrumb } from './breadcrumb';
12
12
  export { uploadFileInputClassicAuto } from './uploadFileInputClassicAuto';
13
13
  export { uploadFileDropzone } from './uploadFileDropzone';
14
14
  export { uploadFileDropzoneImage } from './uploadDropzoneImage';
15
+ export { uploadImage } from './uploadImage';
16
+ export { tags } from './tags';
@@ -12,3 +12,5 @@ export { breadcrumb } from "./breadcrumb.mjs";
12
12
  export { uploadFileInputClassicAuto } from "./uploadFileInputClassicAuto.mjs";
13
13
  export { uploadFileDropzone } from "./uploadFileDropzone.mjs";
14
14
  export { uploadFileDropzoneImage } from "./uploadDropzoneImage.mjs";
15
+ export { uploadImage } from "./uploadImage.mjs";
16
+ export { tags } from "./tags.mjs";
@@ -0,0 +1,25 @@
1
+ export declare const tags: {
2
+ base: string;
3
+ border: string;
4
+ shadow: string;
5
+ padding: string;
6
+ stages: {
7
+ default: string;
8
+ error: string;
9
+ };
10
+ placeholder: string;
11
+ innerWrapper: string;
12
+ input: string;
13
+ tag: {
14
+ size: string;
15
+ color: string;
16
+ variant: string;
17
+ };
18
+ default: {
19
+ tag: {
20
+ size: string;
21
+ color: string;
22
+ variant: string;
23
+ };
24
+ };
25
+ };
@@ -0,0 +1,25 @@
1
+ export const tags = {
2
+ base: "relative flex w-full flex-wrap items-center bg-white ring-1 ring-inset",
3
+ border: "rounded-md border-0",
4
+ shadow: "shadow-sm",
5
+ padding: "px-3.5 py-2.5",
6
+ stages: {
7
+ default: "has-[:focus]:ring-primary-500 ring-gray-300 has-[:focus]:ring-2",
8
+ error: "has-[:focus]:ring-danger-500 ring-danger-500 ring-danger-300 has-[:focus]:ring-2"
9
+ },
10
+ placeholder: "absolute left-3.5 top-2.5 text-sm text-gray-400",
11
+ innerWrapper: "mr-2 flex w-full flex-wrap items-center gap-2",
12
+ input: "m-0 grow p-0 text-sm text-gray-900 outline-none",
13
+ tag: {
14
+ size: "",
15
+ color: "",
16
+ variant: ""
17
+ },
18
+ default: {
19
+ tag: {
20
+ size: "sm",
21
+ color: "primary",
22
+ variant: "solid"
23
+ }
24
+ }
25
+ };
@@ -9,7 +9,7 @@ export const uploadFileDropzoneImage = {
9
9
  percentClass: "text-primary"
10
10
  },
11
11
  onPreview: {
12
- wrapper: "relative size-24 overflow-hidden rounded-lg ring-[1px] ring-gray-100",
12
+ wrapper: "relative size-24 overflow-hidden rounded-lg border-[1px] border-gray-100",
13
13
  previewImgClass: "size-full object-cover",
14
14
  previewActionWrapper: "absolute inset-0 z-10 flex items-center justify-center space-x-2 opacity-0 transition hover:bg-black/50 hover:opacity-100",
15
15
  actionBtnClass: "size-7 cursor-pointer text-white",
@@ -17,7 +17,7 @@ export const uploadFileDropzoneImage = {
17
17
  previewText: "truncate text-center text-sm"
18
18
  },
19
19
  onFailed: {
20
- wrapper: "ring-danger relative size-24 overflow-hidden rounded-lg ring-[1px]",
20
+ wrapper: "border-danger border-dashed relative size-24 overflow-hidden rounded-lg border-[1px]",
21
21
  failedImgClass: "size-full object-cover",
22
22
  failedActionWrapper: "absolute inset-0 z-10 flex items-center justify-center space-x-2 bg-white/50",
23
23
  actionBtnClass: "text-danger size-7 cursor-pointer"
@@ -0,0 +1,34 @@
1
+ export declare const uploadImage: {
2
+ base: string;
3
+ imageItemWrapper: string;
4
+ imageItem: {
5
+ wrapper: string;
6
+ onLoading: {
7
+ wrapper: string;
8
+ percentClass: string;
9
+ };
10
+ onPreview: {
11
+ wrapper: string;
12
+ previewImgClass: string;
13
+ previewActionWrapper: string;
14
+ actionBtnClass: string;
15
+ previewTextWrapper: string;
16
+ previewText: string;
17
+ };
18
+ onFailed: {
19
+ wrapper: string;
20
+ failedImgClass: string;
21
+ failedActionWrapper: string;
22
+ actionBtnClass: string;
23
+ };
24
+ };
25
+ action: {
26
+ addingWrapper: string;
27
+ deleteIconClass: string;
28
+ addingBtnClass: string;
29
+ addingTextClass: string;
30
+ previewIcon: string;
31
+ deleteIcon: string;
32
+ addingIcon: string;
33
+ };
34
+ };
@@ -0,0 +1,36 @@
1
+ export const uploadImage = {
2
+ base: "w-full overflow-hidden",
3
+ imageItemWrapper: "flex w-full flex-wrap gap-4",
4
+ imageItem: {
5
+ wrapper: "max-w-[96px]",
6
+ onLoading: {
7
+ wrapper: "flex size-24 flex-col items-center justify-center overflow-hidden rounded-lg border-[1px] border-dashed p-2",
8
+ percentClass: "text-primary"
9
+ },
10
+ onPreview: {
11
+ wrapper: "relative size-24 overflow-hidden rounded-lg border-[1px] border-gray-100",
12
+ previewImgClass: "size-full object-cover",
13
+ previewActionWrapper: "absolute inset-0 z-10 flex items-center justify-center space-x-2 opacity-0 transition hover:bg-black/50 hover:opacity-100",
14
+ actionBtnClass: "size-7 cursor-pointer text-white",
15
+ previewTextWrapper: "mt-2 truncate",
16
+ previewText: "truncate text-center text-sm"
17
+ },
18
+ onFailed: {
19
+ wrapper: "relative size-24 overflow-hidden rounded-lg border-[1px] border-dashed border-danger",
20
+ failedImgClass: "size-full object-cover",
21
+ failedActionWrapper: "absolute inset-0 z-10 flex items-center justify-center space-x-2 bg-white/50",
22
+ actionBtnClass: "text-danger size-7 cursor-pointer"
23
+ }
24
+ },
25
+ action: {
26
+ // wrapper: 'flex items-center space-x-4',
27
+ addingWrapper: "flex size-24 flex-col items-center justify-center overflow-hidden rounded-lg border-[1px] border-dashed p-2 cursor-pointer transition hover:bg-gray-100",
28
+ // iconClass: 'size-6 text-gray-400 cursor-pointer',
29
+ deleteIconClass: "size-6 text-danger cursor-pointer",
30
+ addingBtnClass: "size-7 text-gray-500",
31
+ addingTextClass: "text-center text-sm text-gray-500",
32
+ previewIcon: "i-ic:outline-remove-red-eye",
33
+ deleteIcon: "ph:trash",
34
+ addingIcon: "i-material-symbols:add"
35
+ }
36
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@finema/core",
3
- "version": "1.4.106",
3
+ "version": "1.4.108",
4
4
  "repository": "https://gitlab.finema.co/finema/ui-kit",
5
5
  "license": "MIT",
6
6
  "author": "Finema Dev Core Team",
@@ -23,8 +23,10 @@
23
23
  "scripts": {
24
24
  "prepack": "nuxt-module-build build",
25
25
  "dev": "nuxi dev playground",
26
+ "docs": "nuxi dev docs",
26
27
  "dev:build": "nuxi build playground",
27
- "dev:prepare": "nuxt-module-build build --stub && nuxt-module-build prepare && nuxi prepare playground",
28
+ "dev:prepare": "nuxt-module-build build --stub && nuxt-module-build prepare && nuxi prepare playground && nuxi prepare docs",
29
+ "docs:prepare": "nuxt-module-build build --stub && nuxt-module-build prepare && nuxi prepare docs",
28
30
  "lint": "eslint . --cache",
29
31
  "lint:fix": "eslint . --fix --cache",
30
32
  "test": "vitest run",
@@ -34,7 +36,7 @@
34
36
  },
35
37
  "dependencies": {
36
38
  "@nuxt/kit": "^3.11.1",
37
- "@nuxt/ui": "^2.15.0",
39
+ "@nuxt/ui": "^2.15.1",
38
40
  "@pinia/nuxt": "^0.5.1",
39
41
  "@vee-validate/nuxt": "^4.12.6",
40
42
  "@vee-validate/zod": "^4.12.6",