@baklavue/ui 1.0.0-preview.3 → 1.0.0-preview.4

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/CHANGELOG.md CHANGED
@@ -1,3 +1,11 @@
1
+ # [@baklavue/ui-v1.0.0-preview.4](https://github.com/erbilnas/baklavue/compare/@baklavue/ui-v1.0.0-preview.3...@baklavue/ui-v1.0.0-preview.4) (2026-02-11)
2
+
3
+
4
+ ### Features
5
+
6
+ * add file upload component ([7aa41b1](https://github.com/erbilnas/baklavue/commit/7aa41b1766c72572be97c84bddc2ca5dccac9be8))
7
+ * add image component ([609eefe](https://github.com/erbilnas/baklavue/commit/609eefe9a4a9f74fc19c7e2a8aa2ab0d7529686f))
8
+
1
9
  # [@baklavue/ui-v1.0.0-preview.3](https://github.com/erbilnas/baklavue/compare/@baklavue/ui-v1.0.0-preview.2...@baklavue/ui-v1.0.0-preview.3) (2026-02-11)
2
10
 
3
11
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@baklavue/ui",
3
- "version": "1.0.0-preview.3",
3
+ "version": "1.0.0-preview.4",
4
4
  "description": "Vue 3 UI kit for Trendyol Baklava Design System",
5
5
  "author": "erbilnas",
6
6
  "license": "MIT",
@@ -0,0 +1,440 @@
1
+ <script setup lang="ts">
2
+ /**
3
+ * FileUpload Component
4
+ *
5
+ * A custom file upload component with drag-and-drop zone, click-to-browse,
6
+ * file list with remove, validation (size/type), and optional preview.
7
+ *
8
+ * @component
9
+ * @example
10
+ * ```vue
11
+ * <!-- Basic usage -->
12
+ * <template>
13
+ * <BvFileUpload v-model="file" label="Upload document" />
14
+ * </template>
15
+ * ```
16
+ *
17
+ * @example
18
+ * ```vue
19
+ * <!-- Multiple with validation -->
20
+ * <template>
21
+ * <BvFileUpload
22
+ * v-model="files"
23
+ * multiple
24
+ * accept="image/*"
25
+ * :max-size="1024 * 1024"
26
+ * @invalid="handleInvalid"
27
+ * />
28
+ * </template>
29
+ * ```
30
+ */
31
+ import { computed, onBeforeUnmount, onMounted, ref, watch } from "vue";
32
+ import BvIcon from "../icon/Icon.vue";
33
+ import BvTag from "../tag/Tag.vue";
34
+ import { loadBaklavaResources } from "../utils/loadBaklavaResources";
35
+ import type {
36
+ FileUploadInvalidEntry,
37
+ FileUploadProps,
38
+ } from "./file-upload.types";
39
+
40
+ const props = withDefaults(defineProps<FileUploadProps>(), {
41
+ modelValue: undefined,
42
+ multiple: false,
43
+ accept: undefined,
44
+ maxSize: undefined,
45
+ minSize: undefined,
46
+ maxFiles: undefined,
47
+ disabled: false,
48
+ label: undefined,
49
+ helpText: undefined,
50
+ invalidText: undefined,
51
+ showPreview: false,
52
+ size: "medium",
53
+ });
54
+
55
+ const emit = defineEmits<{
56
+ "update:modelValue": [value: File | File[] | null];
57
+ invalid: [entries: FileUploadInvalidEntry[]];
58
+ change: [files: File[]];
59
+ }>();
60
+
61
+ const inputRef = ref<HTMLInputElement | null>(null);
62
+ const isDragging = ref(false);
63
+ const previewUrls = ref<Map<File, string>>(new Map());
64
+
65
+ /** Normalize modelValue to File[] */
66
+ const filesList = computed<File[]>(() => {
67
+ const v = props.modelValue;
68
+ if (!v) return [];
69
+ return Array.isArray(v) ? [...v] : [v];
70
+ });
71
+
72
+ /** Check if file passes accept filter */
73
+ function matchesAccept(file: File): boolean {
74
+ if (!props.accept) return true;
75
+ const rules = props.accept.split(",").map((r) => r.trim());
76
+ const ext = file.name.split(".").pop()?.toLowerCase() ?? "";
77
+
78
+ for (const rule of rules) {
79
+ if (rule.startsWith(".")) {
80
+ if (ext === rule.slice(1).toLowerCase()) return true;
81
+ } else {
82
+ const mime = file.type;
83
+ if (rule.endsWith("/*")) {
84
+ const base = rule.slice(0, -1);
85
+ if (mime.startsWith(base)) return true;
86
+ } else if (mime === rule) {
87
+ return true;
88
+ }
89
+ }
90
+ }
91
+ return false;
92
+ }
93
+
94
+ /** Validate files and return invalid entries */
95
+ function validateFiles(
96
+ incoming: File[],
97
+ existingCount: number,
98
+ ): FileUploadInvalidEntry[] {
99
+ const invalid: FileUploadInvalidEntry[] = [];
100
+ const maxFiles = props.maxFiles ?? Infinity;
101
+ let validAdded = 0;
102
+
103
+ for (const file of incoming) {
104
+ if (!matchesAccept(file)) {
105
+ invalid.push({ file, reason: "type" });
106
+ continue;
107
+ }
108
+ if (props.maxSize !== undefined && file.size > props.maxSize) {
109
+ invalid.push({ file, reason: "size" });
110
+ continue;
111
+ }
112
+ if (props.minSize !== undefined && file.size < props.minSize) {
113
+ invalid.push({ file, reason: "size" });
114
+ continue;
115
+ }
116
+ if (props.multiple && existingCount + validAdded >= maxFiles) {
117
+ invalid.push({ file, reason: "count" });
118
+ continue;
119
+ }
120
+ validAdded++;
121
+ }
122
+
123
+ return invalid;
124
+ }
125
+
126
+ /** Process and emit validated files */
127
+ function processFiles(newFiles: File[]) {
128
+ const valid: File[] = [];
129
+ const invalid = validateFiles(newFiles, filesList.value.length);
130
+
131
+ for (const f of newFiles) {
132
+ const entry = invalid.find((e) => e.file === f);
133
+ if (!entry) valid.push(f);
134
+ }
135
+
136
+ if (invalid.length > 0) {
137
+ emit("invalid", invalid);
138
+ }
139
+
140
+ if (valid.length > 0) {
141
+ const combined = props.multiple
142
+ ? [...filesList.value, ...valid]
143
+ : [valid[0]];
144
+ const out = props.multiple ? combined : combined[0];
145
+ emit("update:modelValue", out);
146
+ emit("change", combined);
147
+ }
148
+ }
149
+
150
+ function handleInputChange(e: Event) {
151
+ const input = e.target as HTMLInputElement;
152
+ const files = input.files ? Array.from(input.files) : [];
153
+ processFiles(files);
154
+ input.value = "";
155
+ }
156
+
157
+ function handleDrop(e: DragEvent) {
158
+ e.preventDefault();
159
+ isDragging.value = false;
160
+ if (props.disabled) return;
161
+ const files = e.dataTransfer?.files ? Array.from(e.dataTransfer.files) : [];
162
+ processFiles(files);
163
+ }
164
+
165
+ function handleDragOver(e: DragEvent) {
166
+ e.preventDefault();
167
+ e.stopPropagation();
168
+ if (props.disabled) return;
169
+ isDragging.value = true;
170
+ }
171
+
172
+ function handleDragLeave() {
173
+ isDragging.value = false;
174
+ }
175
+
176
+ function openFilePicker() {
177
+ if (props.disabled) return;
178
+ inputRef.value?.click();
179
+ }
180
+
181
+ function removeFile(index: number) {
182
+ const list = [...filesList.value];
183
+ const removed = list[index];
184
+ list.splice(index, 1);
185
+ if (props.showPreview && removed && removed.type.startsWith("image/")) {
186
+ const url = previewUrls.value.get(removed);
187
+ if (url) URL.revokeObjectURL(url);
188
+ previewUrls.value.delete(removed);
189
+ }
190
+ const out = props.multiple ? list : (list[0] ?? null);
191
+ emit("update:modelValue", out);
192
+ emit("change", list);
193
+ }
194
+
195
+ function getPreviewUrl(file: File): string {
196
+ if (!file.type.startsWith("image/")) return "";
197
+ let url = previewUrls.value.get(file);
198
+ if (!url) {
199
+ url = URL.createObjectURL(file);
200
+ previewUrls.value.set(file, url);
201
+ }
202
+ return url;
203
+ }
204
+
205
+ watch(
206
+ filesList,
207
+ (files) => {
208
+ const toRevoke = new Set(previewUrls.value.keys());
209
+ for (const f of files) {
210
+ toRevoke.delete(f);
211
+ }
212
+ for (const f of toRevoke) {
213
+ const url = previewUrls.value.get(f);
214
+ if (url) URL.revokeObjectURL(url);
215
+ previewUrls.value.delete(f);
216
+ }
217
+ },
218
+ { deep: true },
219
+ );
220
+
221
+ onMounted(() => {
222
+ loadBaklavaResources();
223
+ });
224
+
225
+ onBeforeUnmount(() => {
226
+ for (const url of previewUrls.value.values()) {
227
+ URL.revokeObjectURL(url);
228
+ }
229
+ previewUrls.value.clear();
230
+ });
231
+
232
+ const zoneSizeClass = computed(() => `file-upload-zone--${props.size}`);
233
+ const hasError = computed(() => !!props.invalidText);
234
+ </script>
235
+
236
+ <template>
237
+ <div class="file-upload">
238
+ <label v-if="label" class="file-upload-label">{{ label }}</label>
239
+
240
+ <div
241
+ class="file-upload-zone"
242
+ :class="[
243
+ zoneSizeClass,
244
+ {
245
+ 'file-upload-zone--dragging': isDragging,
246
+ 'file-upload-zone--disabled': disabled,
247
+ 'file-upload-zone--invalid': hasError,
248
+ },
249
+ ]"
250
+ @click="openFilePicker"
251
+ @drop="handleDrop"
252
+ @dragover="handleDragOver"
253
+ @dragleave="handleDragLeave"
254
+ >
255
+ <input
256
+ ref="inputRef"
257
+ type="file"
258
+ class="file-upload-input"
259
+ :accept="accept"
260
+ :multiple="multiple"
261
+ :disabled="disabled"
262
+ @change="handleInputChange"
263
+ />
264
+ <div class="file-upload-content">
265
+ <BvIcon name="upload" size="24px" class="file-upload-icon" />
266
+ <span class="file-upload-text">
267
+ <slot name="hint"> </slot>
268
+ </span>
269
+ </div>
270
+ </div>
271
+
272
+ <p v-if="helpText && !hasError" class="file-upload-help">{{ helpText }}</p>
273
+ <p v-if="invalidText" class="file-upload-invalid">{{ invalidText }}</p>
274
+
275
+ <div v-if="filesList.length > 0" class="file-upload-list">
276
+ <div
277
+ v-for="(file, index) in filesList"
278
+ :key="`${file.name}-${file.size}-${index}`"
279
+ class="file-upload-item"
280
+ >
281
+ <div
282
+ v-if="showPreview && file.type.startsWith('image/')"
283
+ class="file-upload-preview"
284
+ >
285
+ <img
286
+ :src="getPreviewUrl(file)"
287
+ :alt="file.name"
288
+ class="file-upload-thumb"
289
+ />
290
+ </div>
291
+ <BvTag
292
+ closable
293
+ size="small"
294
+ class="file-upload-tag"
295
+ @close="removeFile(index)"
296
+ >
297
+ {{ file.name }} ({{ (file.size / 1024).toFixed(1) }} KB)
298
+ </BvTag>
299
+ </div>
300
+ </div>
301
+ </div>
302
+ </template>
303
+
304
+ <style scoped>
305
+ .file-upload {
306
+ display: flex;
307
+ flex-direction: column;
308
+ gap: var(--bl-spacing-2, 0.5rem);
309
+ }
310
+
311
+ .file-upload-label {
312
+ font: var(--bl-font-body-2-medium, 0.875rem 500);
313
+ color: var(--bl-color-neutral-darker, #374151);
314
+ }
315
+
316
+ .file-upload-zone {
317
+ display: flex;
318
+ align-items: center;
319
+ justify-content: center;
320
+ border: 2px dashed var(--bl-color-neutral-light, #e5e7eb);
321
+ border-radius: var(--bl-radius-m, 8px);
322
+ background: var(--bl-color-neutral-background, #f9fafb);
323
+ cursor: pointer;
324
+ transition:
325
+ border-color 0.2s,
326
+ background 0.2s;
327
+ }
328
+
329
+ .file-upload-zone:hover:not(.file-upload-zone--disabled) {
330
+ border-color: var(--bl-color-primary, #ff6000);
331
+ background: var(--bl-color-primary-background, #fff5f0);
332
+ }
333
+
334
+ .file-upload-zone--dragging {
335
+ border-color: var(--bl-color-primary, #ff6000);
336
+ background: var(--bl-color-primary-background, #fff5f0);
337
+ }
338
+
339
+ .file-upload-zone--disabled {
340
+ cursor: not-allowed;
341
+ opacity: 0.6;
342
+ }
343
+
344
+ .file-upload-zone--invalid {
345
+ border-color: var(--bl-color-danger, #dc2626);
346
+ }
347
+
348
+ .file-upload-zone--small {
349
+ min-height: 80px;
350
+ padding: var(--bl-spacing-3, 0.75rem);
351
+ }
352
+
353
+ .file-upload-zone--medium {
354
+ min-height: 120px;
355
+ padding: var(--bl-spacing-4, 1rem);
356
+ }
357
+
358
+ .file-upload-zone--large {
359
+ min-height: 160px;
360
+ padding: var(--bl-spacing-5, 1.25rem);
361
+ }
362
+
363
+ .file-upload-input {
364
+ position: absolute;
365
+ width: 0;
366
+ height: 0;
367
+ opacity: 0;
368
+ overflow: hidden;
369
+ pointer-events: none;
370
+ }
371
+
372
+ .file-upload-content {
373
+ display: flex;
374
+ flex-direction: column;
375
+ align-items: center;
376
+ gap: var(--bl-spacing-2, 0.5rem);
377
+ }
378
+
379
+ .file-upload-icon {
380
+ color: var(--bl-color-neutral-subtle, #9ca3af);
381
+ }
382
+
383
+ .file-upload-text {
384
+ font: var(--bl-font-body-2-regular, 0.875rem 400);
385
+ color: var(--bl-color-neutral-subtle, #6b7280);
386
+ }
387
+
388
+ .file-upload-browse {
389
+ color: var(--bl-color-primary, #ff6000);
390
+ text-decoration: underline;
391
+ }
392
+
393
+ .file-upload-help,
394
+ .file-upload-invalid {
395
+ font: var(--bl-font-body-3-regular, 0.75rem 400);
396
+ margin: 0;
397
+ }
398
+
399
+ .file-upload-help {
400
+ color: var(--bl-color-neutral-subtle, #6b7280);
401
+ }
402
+
403
+ .file-upload-invalid {
404
+ color: var(--bl-color-danger, #dc2626);
405
+ }
406
+
407
+ .file-upload-list {
408
+ display: flex;
409
+ flex-wrap: wrap;
410
+ gap: var(--bl-spacing-2, 0.5rem);
411
+ }
412
+
413
+ .file-upload-item {
414
+ display: flex;
415
+ flex-direction: column;
416
+ align-items: flex-start;
417
+ gap: var(--bl-spacing-1, 0.25rem);
418
+ }
419
+
420
+ .file-upload-preview {
421
+ width: 48px;
422
+ height: 48px;
423
+ border-radius: var(--bl-radius-s, 4px);
424
+ overflow: hidden;
425
+ background: var(--bl-color-neutral-light, #e5e7eb);
426
+ }
427
+
428
+ .file-upload-thumb {
429
+ width: 100%;
430
+ height: 100%;
431
+ object-fit: cover;
432
+ }
433
+
434
+ .file-upload-tag {
435
+ max-width: 240px;
436
+ overflow: hidden;
437
+ text-overflow: ellipsis;
438
+ white-space: nowrap;
439
+ }
440
+ </style>
@@ -0,0 +1,89 @@
1
+ /**
2
+ * File upload size.
3
+ */
4
+ export type FileUploadSize = "small" | "medium" | "large";
5
+
6
+ /**
7
+ * Validation failure reason.
8
+ */
9
+ export type FileUploadInvalidReason = "type" | "size" | "count";
10
+
11
+ /**
12
+ * Invalid file entry emitted on validation failure.
13
+ */
14
+ export interface FileUploadInvalidEntry {
15
+ file: File;
16
+ reason: FileUploadInvalidReason;
17
+ }
18
+
19
+ /**
20
+ * Props for the FileUpload component.
21
+ *
22
+ * A custom file upload component with drag-and-drop, validation,
23
+ * file list with remove, and optional preview.
24
+ *
25
+ * @interface FileUploadProps
26
+ */
27
+ export interface FileUploadProps {
28
+ /**
29
+ * Bound files (v-model). Single file or array when multiple.
30
+ */
31
+ modelValue?: File | File[] | null;
32
+
33
+ /**
34
+ * Allow multiple files.
35
+ */
36
+ multiple?: boolean;
37
+
38
+ /**
39
+ * Accepted MIME types or extensions (e.g. `image/*`, `.pdf`, `application/pdf`).
40
+ */
41
+ accept?: string;
42
+
43
+ /**
44
+ * Maximum file size in bytes.
45
+ */
46
+ maxSize?: number;
47
+
48
+ /**
49
+ * Minimum file size in bytes.
50
+ */
51
+ minSize?: number;
52
+
53
+ /**
54
+ * Maximum number of files when multiple is true.
55
+ */
56
+ maxFiles?: number;
57
+
58
+ /**
59
+ * Disabled state.
60
+ */
61
+ disabled?: boolean;
62
+
63
+ /**
64
+ * Label for the upload area.
65
+ */
66
+ label?: string;
67
+
68
+ /**
69
+ * Helper text below the upload area.
70
+ */
71
+ helpText?: string;
72
+
73
+ /**
74
+ * Error message when validation fails.
75
+ */
76
+ invalidText?: string;
77
+
78
+ /**
79
+ * Show image previews for image files.
80
+ */
81
+ showPreview?: boolean;
82
+
83
+ /**
84
+ * Drop zone size (small, medium, large).
85
+ *
86
+ * @default "medium"
87
+ */
88
+ size?: FileUploadSize;
89
+ }
@@ -0,0 +1,7 @@
1
+ export { default as BvFileUpload } from "./FileUpload.vue";
2
+ export type {
3
+ FileUploadProps,
4
+ FileUploadSize,
5
+ FileUploadInvalidReason,
6
+ FileUploadInvalidEntry,
7
+ } from "./file-upload.types";
@@ -0,0 +1,144 @@
1
+ <script setup lang="ts">
2
+ /**
3
+ * Image Component
4
+ *
5
+ * Performance-focused image wrapper with lazy loading,
6
+ * skeleton placeholder, and error handling.
7
+ *
8
+ * @component
9
+ * @example
10
+ * ```vue
11
+ * <template>
12
+ * <BvImage src="/photo.jpg" alt="Photo" width="200px" height="120px" />
13
+ * </template>
14
+ * ```
15
+ */
16
+ import { ref, computed, onMounted } from "vue";
17
+ import { loadBaklavaResources } from "../utils/loadBaklavaResources";
18
+ import BvSkeleton from "../skeleton/Skeleton.vue";
19
+ import type { ImageProps } from "./image.types";
20
+
21
+ const props = withDefaults(defineProps<ImageProps>(), {
22
+ loading: "lazy",
23
+ placeholder: "skeleton",
24
+ objectFit: "cover",
25
+ });
26
+
27
+ const emit = defineEmits<{
28
+ load: [event: Event];
29
+ error: [event: Event];
30
+ }>();
31
+
32
+ const isLoading = ref(true);
33
+ const hasError = ref(false);
34
+
35
+ const showPlaceholder = computed(
36
+ () => isLoading.value && props.placeholder === "skeleton" && !hasError.value,
37
+ );
38
+
39
+ const showImage = computed(() => !hasError.value);
40
+
41
+ const wrapperStyle = computed(() => ({
42
+ width: props.width ?? "100%",
43
+ height: props.height ?? "auto",
44
+ aspectRatio: !props.height && props.width ? "16 / 9" : undefined,
45
+ }));
46
+
47
+ function onLoad(event: Event) {
48
+ isLoading.value = false;
49
+ emit("load", event);
50
+ }
51
+
52
+ function onError(event: Event) {
53
+ isLoading.value = false;
54
+ hasError.value = true;
55
+ emit("error", event);
56
+ }
57
+
58
+ onMounted(() => {
59
+ loadBaklavaResources();
60
+ });
61
+ </script>
62
+
63
+ <template>
64
+ <div class="image-wrapper" :style="wrapperStyle">
65
+ <!-- Placeholder (skeleton or custom slot) -->
66
+ <div v-if="showPlaceholder" class="image-placeholder">
67
+ <slot name="placeholder">
68
+ <BvSkeleton
69
+ class="image-skeleton"
70
+ width="100%"
71
+ height="100%"
72
+ variant="rectangle"
73
+ />
74
+ </slot>
75
+ </div>
76
+
77
+ <!-- Loaded image -->
78
+ <img
79
+ v-show="showImage"
80
+ :src="src"
81
+ :alt="alt"
82
+ :loading="loading"
83
+ :srcset="srcset"
84
+ :sizes="sizes"
85
+ class="image-img"
86
+ :style="{ objectFit }"
87
+ @load="onLoad"
88
+ @error="onError"
89
+ />
90
+
91
+ <!-- Error fallback -->
92
+ <div v-if="hasError" class="image-fallback">
93
+ <slot name="fallback">
94
+ <div class="image-fallback-default" role="img" :aria-label="alt">
95
+ Failed to load image
96
+ </div>
97
+ </slot>
98
+ </div>
99
+ </div>
100
+ </template>
101
+
102
+ <style scoped>
103
+ .image-wrapper {
104
+ position: relative;
105
+ overflow: hidden;
106
+ display: block;
107
+ }
108
+
109
+ .image-placeholder {
110
+ position: absolute;
111
+ inset: 0;
112
+ display: flex;
113
+ align-items: center;
114
+ justify-content: center;
115
+ }
116
+
117
+ .image-skeleton {
118
+ width: 100% !important;
119
+ height: 100% !important;
120
+ }
121
+
122
+ .image-img {
123
+ display: block;
124
+ width: 100%;
125
+ height: 100%;
126
+ vertical-align: middle;
127
+ }
128
+
129
+ .image-fallback {
130
+ position: absolute;
131
+ inset: 0;
132
+ display: flex;
133
+ align-items: center;
134
+ justify-content: center;
135
+ }
136
+
137
+ .image-fallback-default {
138
+ background: var(--bl-color-neutral-light, #e5e7eb);
139
+ color: var(--bl-color-neutral-darker, #6b7280);
140
+ font-size: 0.875rem;
141
+ padding: 1rem;
142
+ text-align: center;
143
+ }
144
+ </style>
@@ -0,0 +1,57 @@
1
+ /** Native image loading behavior */
2
+ export type ImageLoading = "lazy" | "eager";
3
+
4
+ /** Placeholder type while image loads */
5
+ export type ImagePlaceholder = "skeleton" | "none";
6
+
7
+ export interface ImageProps {
8
+ /**
9
+ * Image URL (required).
10
+ */
11
+ src: string;
12
+
13
+ /**
14
+ * Accessible description (required).
15
+ */
16
+ alt: string;
17
+
18
+ /**
19
+ * CSS width (e.g. "200px", "100%").
20
+ * Recommended to prevent CLS.
21
+ */
22
+ width?: string;
23
+
24
+ /**
25
+ * CSS height (e.g. "120px", "auto").
26
+ * Recommended to prevent CLS.
27
+ */
28
+ height?: string;
29
+
30
+ /**
31
+ * Native loading behavior.
32
+ * @default "lazy"
33
+ */
34
+ loading?: ImageLoading;
35
+
36
+ /**
37
+ * Placeholder when lazy and not yet loaded.
38
+ * @default "skeleton"
39
+ */
40
+ placeholder?: ImagePlaceholder;
41
+
42
+ /**
43
+ * CSS object-fit.
44
+ * @default "cover"
45
+ */
46
+ objectFit?: string;
47
+
48
+ /**
49
+ * Responsive image sources (srcset attribute).
50
+ */
51
+ srcset?: string;
52
+
53
+ /**
54
+ * Sizes attribute for srcset.
55
+ */
56
+ sizes?: string;
57
+ }
@@ -0,0 +1,3 @@
1
+ // Component exports must use the "Bv-" prefix
2
+ export { default as BvImage } from "./Image.vue";
3
+ export type { ImageProps, ImageLoading, ImagePlaceholder } from "./image.types";
package/src/index.ts CHANGED
@@ -17,7 +17,9 @@ export * from "./datepicker";
17
17
  export * from "./dialog";
18
18
  export * from "./drawer";
19
19
  export * from "./dropdown";
20
+ export * from "./file-upload";
20
21
  export * from "./icon";
22
+ export * from "./image";
21
23
  export * from "./input";
22
24
  export * from "./link";
23
25
  export * from "./notification";