webcake-ui-kit 1.2.16 → 1.2.17

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.
@@ -0,0 +1,262 @@
1
+ <template>
2
+ <!-- Card tile (picture-card / picture-circle) -->
3
+ <div
4
+ v-if="isCard"
5
+ class="ui-upload-item ui-upload-item--card"
6
+ :class="[`ui-upload-item--${listType}`, `ui-upload-item--${file.status}`, { 'ui-upload-item--has-thumb': !!thumb }]"
7
+ >
8
+ <div class="ui-upload-item__card-inner">
9
+ <img v-if="thumb" :src="thumb" :alt="file.name" class="ui-upload-item__card-img" />
10
+ <span v-else class="ui-upload-item__card-file">
11
+ <component :is="fileIcon" :size="24" />
12
+ </span>
13
+
14
+ <Button
15
+ v-if="showRemoveIcon && file.status !== 'uploading'"
16
+ type="button"
17
+ class="ui-upload-item__remove"
18
+ aria-label="Remove"
19
+ @click="onRemove"
20
+ size="mini"
21
+ variant="secondary"
22
+ >
23
+ <template #icon>
24
+ <WkiX :size="16" />
25
+ </template>
26
+ </Button>
27
+
28
+ <div v-if="file.status === 'uploading'" class="ui-upload-item__card-mask ui-upload-item__card-mask--loading">
29
+ <Spinner size="sm" type="mirrored" />
30
+ <span class="ui-upload-item__card-percent">{{ percentLabel }}</span>
31
+ </div>
32
+ <div v-else-if="file.status === 'error'" class="ui-upload-item__card-mask ui-upload-item__card-mask--error">
33
+ <WkiCircleAlert :size="18" />
34
+ </div>
35
+
36
+ <div v-if="file.status !== 'uploading'" class="ui-upload-item__card-actions">
37
+ <button
38
+ v-if="showPreviewIcon && canPreview"
39
+ type="button"
40
+ class="ui-upload-item__card-action"
41
+ aria-label="Preview"
42
+ @click="onPreview"
43
+ >
44
+ <WkiEye :size="16" />
45
+ </button>
46
+ <button
47
+ v-if="!multiple"
48
+ type="button"
49
+ class="ui-upload-item__card-action"
50
+ aria-label="Replace"
51
+ @click="onReplace"
52
+ >
53
+ <WkiPencil :size="16" />
54
+ </button>
55
+ <button
56
+ v-if="showDownloadIcon && file.url"
57
+ type="button"
58
+ class="ui-upload-item__card-action"
59
+ aria-label="Download"
60
+ @click="onDownload"
61
+ >
62
+ <WkiDownload :size="16" />
63
+ </button>
64
+ </div>
65
+ </div>
66
+ </div>
67
+
68
+ <!-- Row (default / picture) -->
69
+ <div
70
+ v-else
71
+ class="ui-upload-item ui-upload-item--row"
72
+ :class="[`ui-upload-item--${listType}`, `ui-upload-item--${file.status}`]"
73
+ >
74
+ <div class="ui-upload-item__row">
75
+ <span class="ui-upload-item__media" :class="{ 'ui-upload-item__media--thumb': !!thumb }">
76
+ <img v-if="thumb" :src="thumb" :alt="file.name" class="ui-upload-item__thumb" />
77
+ <component :is="fileIcon" v-else :size="24" class="ui-upload-item__file-icon" />
78
+ </span>
79
+
80
+ <div class="ui-upload-item__info">
81
+ <p class="ui-upload-item__name" :title="file.name">{{ file.name }}</p>
82
+ <p class="ui-upload-item__desc" :class="`ui-upload-item__desc--${file.status}`">{{ descText }}</p>
83
+ </div>
84
+
85
+ <div class="ui-upload-item__actions">
86
+ <template v-if="file.status === 'uploading'">
87
+ <Button variant="ghost" size="xs" @click="onCancel">
88
+ <template #icon-left><WkiX :size="14" /></template>
89
+ Cancel
90
+ </Button>
91
+ <Spinner size="sm" type="mirrored" />
92
+ </template>
93
+ <template v-else-if="file.status === 'error'">
94
+ <Button variant="ghost" size="xs" @click="onRetry">
95
+ <template #icon-left><WkiRotateCw :size="14" /></template>
96
+ Try again
97
+ </Button>
98
+ <span class="ui-upload-item__status-icon ui-upload-item__status-icon--error">
99
+ <WkiCircleAlert :size="16" />
100
+ </span>
101
+ <button
102
+ v-if="showRemoveIcon"
103
+ type="button"
104
+ class="ui-upload-item__icon-btn ui-upload-item__remove"
105
+ aria-label="Remove"
106
+ @click="onRemove"
107
+ >
108
+ <WkiX :size="16" />
109
+ </button>
110
+ </template>
111
+ <template v-else>
112
+ <button
113
+ v-if="showDownloadIcon && file.url"
114
+ type="button"
115
+ class="ui-upload-item__icon-btn"
116
+ aria-label="Download"
117
+ @click="onDownload"
118
+ >
119
+ <WkiDownload :size="16" />
120
+ </button>
121
+ <span class="ui-upload-item__status-icon ui-upload-item__status-icon--done">
122
+ <WkiCheck :size="12" />
123
+ </span>
124
+ <button
125
+ v-if="showRemoveIcon"
126
+ type="button"
127
+ class="ui-upload-item__icon-btn ui-upload-item__remove"
128
+ aria-label="Remove"
129
+ @click="onRemove"
130
+ >
131
+ <WkiX :size="16" />
132
+ </button>
133
+ </template>
134
+ </div>
135
+ </div>
136
+
137
+ <div v-if="file.status === 'uploading'" class="ui-upload-item__progress">
138
+ <Progress :value="file.percent || 0" />
139
+ <div class="ui-upload-item__progress-text">
140
+ <span>{{ progressLeft }}</span>
141
+ <span>{{ percentLabel }}</span>
142
+ </div>
143
+ </div>
144
+ </div>
145
+ </template>
146
+
147
+ <script>
148
+ import Progress from '../progress/Progress.vue'
149
+ import Spinner from '../spinner/Spinner.vue'
150
+ import Button from '../button/Button.vue'
151
+ import WkiFileText from '../../icons/FileText.vue'
152
+ import WkiFileImage from '../../icons/FileImage.vue'
153
+ import WkiX from '../../icons/X.vue'
154
+ import WkiCheck from '../../icons/Check.vue'
155
+ import WkiCircleAlert from '../../icons/CircleAlert.vue'
156
+ import WkiRotateCw from '../../icons/RotateCw.vue'
157
+ import WkiDownload from '../../icons/Download.vue'
158
+ import WkiEye from '../../icons/Eye.vue'
159
+ import WkiPencil from '../../icons/Pencil.vue'
160
+ import { formatSize, isImageFileType, isImageUrl } from './upload-utils.js'
161
+
162
+ export default {
163
+ name: 'UploadItem',
164
+
165
+ components: {
166
+ Progress,
167
+ Spinner,
168
+ Button,
169
+ WkiFileText,
170
+ WkiFileImage,
171
+ WkiX,
172
+ WkiCheck,
173
+ WkiCircleAlert,
174
+ WkiRotateCw,
175
+ WkiDownload,
176
+ WkiEye,
177
+ WkiPencil
178
+ },
179
+
180
+ inject: {
181
+ wkUpload: { default: null }
182
+ },
183
+
184
+ props: {
185
+ file: { type: Object, required: true },
186
+ listType: { type: String, default: 'default' },
187
+ showPreviewIcon: { type: Boolean, default: true },
188
+ showRemoveIcon: { type: Boolean, default: true },
189
+ showDownloadIcon: { type: Boolean, default: false }
190
+ },
191
+
192
+ emits: [],
193
+
194
+ computed: {
195
+ isCard() {
196
+ return this.listType === 'picture-card' || this.listType === 'picture-circle'
197
+ },
198
+ multiple() {
199
+ return this.wkUpload ? this.wkUpload.multiple : true
200
+ },
201
+ isImage() {
202
+ return isImageFileType(this.file.type) || this.canPreview
203
+ },
204
+ canPreview() {
205
+ if (this.wkUpload && typeof this.wkUpload.resolveIsImage === 'function') {
206
+ return this.wkUpload.resolveIsImage(this.file)
207
+ }
208
+ return isImageUrl(this.file)
209
+ },
210
+ thumb() {
211
+ return this.file.thumbUrl || (this.canPreview ? this.file.url : '') || ''
212
+ },
213
+ fileIcon() {
214
+ return this.isImage ? 'WkiFileImage' : 'WkiFileText'
215
+ },
216
+ percentLabel() {
217
+ return `${Math.round(this.file.percent || 0)}%`
218
+ },
219
+ progressLeft() {
220
+ if (this.file.total) {
221
+ return `${formatSize(this.file.loaded || 0)} of ${formatSize(this.file.total)}`
222
+ }
223
+ if (this.file.size) return formatSize(this.file.size)
224
+ return ''
225
+ },
226
+ descText() {
227
+ const s = this.file.status
228
+ if (s === 'error') {
229
+ return (this.file.error && this.file.error.message) || 'Upload failed, please try again'
230
+ }
231
+ if (s === 'uploading') {
232
+ const size = this.file.size ? `${formatSize(this.file.size)} · ` : ''
233
+ return `${size}${this.percentLabel} uploaded`
234
+ }
235
+ return 'File uploaded successfully'
236
+ }
237
+ },
238
+
239
+ methods: {
240
+ onPreview() {
241
+ if (this.wkUpload) this.wkUpload.handlePreview(this.file)
242
+ },
243
+ onRemove() {
244
+ if (this.wkUpload) this.wkUpload.handleRemove(this.file)
245
+ },
246
+ onCancel() {
247
+ if (this.wkUpload) this.wkUpload.handleRemove(this.file)
248
+ },
249
+ onRetry() {
250
+ if (this.wkUpload) this.wkUpload.handleRetry(this.file)
251
+ },
252
+ onDownload() {
253
+ if (this.wkUpload) this.wkUpload.handleDownload(this.file)
254
+ },
255
+ onReplace() {
256
+ if (this.wkUpload) this.wkUpload.openFileDialog()
257
+ }
258
+ }
259
+ }
260
+ </script>
261
+
262
+ <style src="./upload-item.css" scoped></style>
@@ -0,0 +1,51 @@
1
+ <template>
2
+ <div v-if="hasItems || hasTrigger()" class="ui-upload-list" :class="`ui-upload-list--${listType}`">
3
+ <template v-if="hasItems">
4
+ <UploadItem
5
+ v-for="file in items"
6
+ :key="file.uid"
7
+ :file="file"
8
+ :list-type="listType"
9
+ :show-preview-icon="showPreviewIcon"
10
+ :show-remove-icon="showRemoveIcon"
11
+ :show-download-icon="showDownloadIcon"
12
+ />
13
+ </template>
14
+ <slot name="trigger" />
15
+ </div>
16
+ </template>
17
+
18
+ <script>
19
+ import UploadItem from './UploadItem.vue'
20
+
21
+ export default {
22
+ name: 'UploadList',
23
+
24
+ components: { UploadItem },
25
+
26
+ props: {
27
+ items: { type: Array, default: () => [] },
28
+ listType: { type: String, default: 'default' },
29
+ showPreviewIcon: { type: Boolean, default: true },
30
+ showRemoveIcon: { type: Boolean, default: true },
31
+ showDownloadIcon: { type: Boolean, default: false },
32
+ hideList: { type: Boolean, default: false }
33
+ },
34
+
35
+ emits: [],
36
+
37
+ computed: {
38
+ hasItems() {
39
+ return !this.hideList && this.items.length > 0
40
+ }
41
+ },
42
+
43
+ methods: {
44
+ hasTrigger() {
45
+ return !!((this.$scopedSlots && this.$scopedSlots.trigger) || this.$slots.trigger)
46
+ }
47
+ }
48
+ }
49
+ </script>
50
+
51
+ <style src="./upload-list.css" scoped></style>
@@ -0,0 +1,250 @@
1
+ /* ============================ Row (default / picture) ============================ */
2
+ .ui-upload-item--row {
3
+ position: relative;
4
+ display: flex;
5
+ flex-direction: column;
6
+ gap: var(--spacing-xs);
7
+ width: 100%;
8
+ }
9
+
10
+ .ui-upload-item__row {
11
+ display: flex;
12
+ align-items: center;
13
+ gap: var(--spacing-sm);
14
+ width: 100%;
15
+ }
16
+
17
+ /* Base: used as absolute corner button inside the card tile. */
18
+ .ui-upload-item__remove {
19
+ position: absolute;
20
+ top: var(--spacing-2xs);
21
+ right: var(--spacing-2xs);
22
+ z-index: 1;
23
+ opacity: 0;
24
+ }
25
+
26
+ /* Row mode: remove button sits inline inside __actions — reset to normal flow. */
27
+ .ui-upload-item--row .ui-upload-item__remove {
28
+ position: static;
29
+ opacity: 1;
30
+ }
31
+
32
+ .ui-upload-item__media {
33
+ flex: none;
34
+ display: flex;
35
+ align-items: center;
36
+ justify-content: center;
37
+ width: 40px;
38
+ height: 40px;
39
+ border-radius: var(--rounded-lg);
40
+ color: var(--muted-fg);
41
+ }
42
+
43
+ .ui-upload-item__media--thumb {
44
+ overflow: hidden;
45
+ background: var(--sidebar);
46
+ border: 1px solid var(--border-primary);
47
+ }
48
+
49
+ .ui-upload-item__thumb {
50
+ width: 100%;
51
+ height: 100%;
52
+ object-fit: cover;
53
+ }
54
+
55
+ .ui-upload-item__file-icon {
56
+ color: var(--secondary-fg);
57
+ }
58
+
59
+ .ui-upload-item__info {
60
+ flex: 1 1 auto;
61
+ min-width: 0;
62
+ display: flex;
63
+ flex-direction: column;
64
+ gap: var(--spacing-2xs);
65
+ }
66
+
67
+ .ui-upload-item__name {
68
+ margin: 0;
69
+ font-size: var(--paragraph-small-font-size);
70
+ line-height: var(--paragraph-small-line-height);
71
+ color: var(--primary-fg);
72
+ white-space: nowrap;
73
+ overflow: hidden;
74
+ text-overflow: ellipsis;
75
+ }
76
+
77
+ .ui-upload-item__desc {
78
+ margin: 0;
79
+ font-size: var(--paragraph-mini-font-size);
80
+ line-height: var(--paragraph-mini-line-height);
81
+ color: var(--muted-fg);
82
+ white-space: nowrap;
83
+ overflow: hidden;
84
+ text-overflow: ellipsis;
85
+ }
86
+
87
+ .ui-upload-item__desc--error {
88
+ color: var(--destructive);
89
+ }
90
+
91
+ .ui-upload-item__actions {
92
+ flex: none;
93
+ display: flex;
94
+ align-items: center;
95
+ gap: var(--spacing-xs);
96
+ }
97
+
98
+ .ui-upload-item__icon-btn {
99
+ display: inline-flex;
100
+ align-items: center;
101
+ justify-content: center;
102
+ min-width: 28px;
103
+ min-height: 28px;
104
+ padding: var(--spacing-6);
105
+ color: var(--secondary-fg);
106
+ background: transparent;
107
+ border: none;
108
+ border-radius: var(--rounded-md);
109
+ cursor: pointer;
110
+ }
111
+
112
+ .ui-upload-item__icon-btn:hover {
113
+ background: var(--ghost-hover);
114
+ }
115
+
116
+ .ui-upload-item__icon-btn:focus-visible {
117
+ outline: none;
118
+ box-shadow: 0 0 0 3px var(--focus-ring);
119
+ }
120
+
121
+ .ui-upload-item__status-icon {
122
+ flex: none;
123
+ display: inline-flex;
124
+ align-items: center;
125
+ justify-content: center;
126
+ width: 16px;
127
+ height: 16px;
128
+ border-radius: var(--rounded-full);
129
+ }
130
+
131
+ .ui-upload-item__status-icon--done {
132
+ color: var(--inverse-fg);
133
+ background: var(--primary-brand-bg);
134
+ }
135
+
136
+ .ui-upload-item__status-icon--error {
137
+ color: var(--destructive);
138
+ }
139
+
140
+ .ui-upload-item__progress {
141
+ display: flex;
142
+ flex-direction: column;
143
+ gap: var(--spacing-xs);
144
+ width: 100%;
145
+ }
146
+
147
+ .ui-upload-item__progress-text {
148
+ display: flex;
149
+ justify-content: space-between;
150
+ gap: var(--spacing-sm);
151
+ font-size: var(--paragraph-mini-font-size);
152
+ line-height: var(--paragraph-mini-line-height);
153
+ color: var(--muted-fg);
154
+ }
155
+
156
+ /* ============================ Card tile (picture-card / picture-circle) ============================ */
157
+ .ui-upload-item--card {
158
+ flex: none;
159
+ }
160
+
161
+ .ui-upload-item__card-inner {
162
+ position: relative;
163
+ display: flex;
164
+ align-items: center;
165
+ justify-content: center;
166
+ width: 140px;
167
+ height: 140px;
168
+ overflow: hidden;
169
+ background: var(--sidebar);
170
+ border: 1px solid var(--border-primary);
171
+ border-radius: var(--rounded-xl);
172
+ }
173
+
174
+ .ui-upload-item--picture-circle .ui-upload-item__card-inner {
175
+ border-radius: var(--rounded-full);
176
+ }
177
+
178
+ .ui-upload-item__card-img {
179
+ display: block;
180
+ width: 100%;
181
+ height: 100%;
182
+ object-fit: cover;
183
+ }
184
+
185
+ .ui-upload-item__card-file {
186
+ color: var(--muted-fg);
187
+ }
188
+
189
+ .ui-upload-item__card-mask {
190
+ position: absolute;
191
+ inset: 0;
192
+ display: flex;
193
+ flex-direction: column;
194
+ align-items: center;
195
+ justify-content: center;
196
+ gap: var(--spacing-2xs);
197
+ color: var(--inverse-fg);
198
+ background: var(--backdrop);
199
+ }
200
+
201
+ .ui-upload-item__card-mask--error {
202
+ color: var(--destructive);
203
+ background: var(--destructive-subtle);
204
+ }
205
+
206
+ .ui-upload-item__card-percent {
207
+ font-size: var(--paragraph-mini-font-size);
208
+ line-height: var(--paragraph-mini-line-height);
209
+ }
210
+
211
+ .ui-upload-item__card-actions {
212
+ position: absolute;
213
+ inset: 0;
214
+ display: flex;
215
+ align-items: center;
216
+ justify-content: center;
217
+ gap: var(--spacing-xs);
218
+ opacity: 0;
219
+ background: var(--backdrop);
220
+ transition: opacity 0.15s ease;
221
+ }
222
+
223
+ .ui-upload-item__card-inner:hover .ui-upload-item__card-actions,
224
+ .ui-upload-item__card-inner:focus-within .ui-upload-item__card-actions,
225
+ .ui-upload-item__card-inner:hover .ui-upload-item__remove,
226
+ .ui-upload-item__card-inner:focus-within .ui-upload-item__remove {
227
+ opacity: 1;
228
+ }
229
+
230
+ .ui-upload-item__card-action {
231
+ display: inline-flex;
232
+ align-items: center;
233
+ justify-content: center;
234
+ width: 28px;
235
+ height: 28px;
236
+ color: var(--inverse-fg);
237
+ background: transparent;
238
+ border: none;
239
+ border-radius: var(--rounded-md);
240
+ cursor: pointer;
241
+ }
242
+
243
+ .ui-upload-item__card-action:hover {
244
+ background: var(--outline-hover);
245
+ }
246
+
247
+ .ui-upload-item__card-action:focus-visible {
248
+ outline: none;
249
+ box-shadow: 0 0 0 3px var(--focus-ring);
250
+ }
@@ -0,0 +1,17 @@
1
+ .ui-upload-list {
2
+ display: flex;
3
+ width: 100%;
4
+ }
5
+
6
+ .ui-upload-list--default,
7
+ .ui-upload-list--picture {
8
+ flex-direction: column;
9
+ gap: var(--spacing-sm);
10
+ }
11
+
12
+ .ui-upload-list--picture-card,
13
+ .ui-upload-list--picture-circle {
14
+ flex-flow: row wrap;
15
+ gap: var(--spacing-sm);
16
+ align-items: flex-start;
17
+ }