@youcan/celeste 0.6.54 → 0.6.55
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/celeste.css +85 -559
- package/dist/cjs/chunks/index2.js +28 -0
- package/dist/cjs/color-picker.js +6 -4
- package/dist/cjs/color-swatch.js +3 -3
- package/dist/cjs/file-upload-area.js +280 -0
- package/dist/cjs/index.js +0 -9
- package/dist/components/color-picker/color-picker.d.ts +2 -0
- package/dist/components/color-picker/color-swatch.d.ts +1 -2
- package/dist/components/file-upload/file-upload-area.d.ts +43 -0
- package/dist/components/file-upload/index.d.ts +2 -0
- package/dist/components/index.d.ts +0 -1
- package/dist/es/chunks/index2.js +30 -2
- package/dist/es/color-picker.js +6 -4
- package/dist/es/color-swatch.js +3 -3
- package/dist/es/file-upload-area.js +281 -0
- package/dist/es/index.js +0 -9
- package/package.json +9 -9
- package/src/components/color-picker/color-picker.vue +4 -0
- package/src/components/color-picker/color-swatch.vue +1 -2
- package/src/components/file-upload/file-upload-area.vue +229 -0
- package/src/components/file-upload/index.ts +2 -0
- package/src/components/file-upload/stories/file-upload-area.stories.ts +76 -0
- package/src/components/index.ts +0 -1
- package/dist/cjs/context-menu-item.js +0 -134
- package/dist/cjs/dropdown-item.js +0 -99
- package/dist/cjs/dropdown.js +0 -290
- package/dist/components/dropdown/context-menu-item.d.ts +0 -39
- package/dist/components/dropdown/dropdown-item.d.ts +0 -30
- package/dist/components/dropdown/dropdown.d.ts +0 -55
- package/dist/components/dropdown/index.d.ts +0 -6
- package/dist/es/context-menu-item.js +0 -135
- package/dist/es/dropdown-item.js +0 -100
- package/dist/es/dropdown.js +0 -291
- package/src/components/dropdown/context-menu-item.vue +0 -317
- package/src/components/dropdown/dropdown-item.vue +0 -184
- package/src/components/dropdown/dropdown.vue +0 -650
- package/src/components/dropdown/index.ts +0 -8
- package/src/components/dropdown/stories/context-menu-item.stories.ts +0 -51
- package/src/components/dropdown/stories/dropdown-item.stories.ts +0 -33
- package/src/components/dropdown/stories/dropdown.stories.ts +0 -53
|
@@ -0,0 +1,281 @@
|
|
|
1
|
+
import { shallowRef, computed, toValue, unref, defineComponent, mergeModels, useModel, useTemplateRef, openBlock, createElementBlock, normalizeClass, createElementVNode, toDisplayString, createCommentVNode, createBlock, withCtx, mergeProps } from "vue";
|
|
2
|
+
import Button from "./button.js";
|
|
3
|
+
import { i as isClient, t as toArray, w as watchImmediate, a as tryOnScopeDispose, b as isObject } from "./chunks/index2.js";
|
|
4
|
+
import { c as clsx } from "./chunks/clsx.js";
|
|
5
|
+
import { _ as _export_sfc } from "./chunks/_plugin-vue_export-helper.js";
|
|
6
|
+
const defaultWindow = isClient ? window : void 0;
|
|
7
|
+
function unrefElement(elRef) {
|
|
8
|
+
var _a;
|
|
9
|
+
const plain = toValue(elRef);
|
|
10
|
+
return (_a = plain == null ? void 0 : plain.$el) != null ? _a : plain;
|
|
11
|
+
}
|
|
12
|
+
function useEventListener(...args) {
|
|
13
|
+
const cleanups = [];
|
|
14
|
+
const cleanup = () => {
|
|
15
|
+
cleanups.forEach((fn) => fn());
|
|
16
|
+
cleanups.length = 0;
|
|
17
|
+
};
|
|
18
|
+
const register = (el, event, listener, options) => {
|
|
19
|
+
el.addEventListener(event, listener, options);
|
|
20
|
+
return () => el.removeEventListener(event, listener, options);
|
|
21
|
+
};
|
|
22
|
+
const firstParamTargets = computed(() => {
|
|
23
|
+
const test = toArray(toValue(args[0])).filter((e) => e != null);
|
|
24
|
+
return test.every((e) => typeof e !== "string") ? test : void 0;
|
|
25
|
+
});
|
|
26
|
+
const stopWatch = watchImmediate(
|
|
27
|
+
() => {
|
|
28
|
+
var _a, _b;
|
|
29
|
+
return [
|
|
30
|
+
(_b = (_a = firstParamTargets.value) == null ? void 0 : _a.map((e) => unrefElement(e))) != null ? _b : [defaultWindow].filter((e) => e != null),
|
|
31
|
+
toArray(toValue(firstParamTargets.value ? args[1] : args[0])),
|
|
32
|
+
toArray(unref(firstParamTargets.value ? args[2] : args[1])),
|
|
33
|
+
// @ts-expect-error - TypeScript gets the correct types, but somehow still complains
|
|
34
|
+
toValue(firstParamTargets.value ? args[3] : args[2])
|
|
35
|
+
];
|
|
36
|
+
},
|
|
37
|
+
([raw_targets, raw_events, raw_listeners, raw_options]) => {
|
|
38
|
+
cleanup();
|
|
39
|
+
if (!(raw_targets == null ? void 0 : raw_targets.length) || !(raw_events == null ? void 0 : raw_events.length) || !(raw_listeners == null ? void 0 : raw_listeners.length))
|
|
40
|
+
return;
|
|
41
|
+
const optionsClone = isObject(raw_options) ? { ...raw_options } : raw_options;
|
|
42
|
+
cleanups.push(
|
|
43
|
+
...raw_targets.flatMap(
|
|
44
|
+
(el) => raw_events.flatMap(
|
|
45
|
+
(event) => raw_listeners.map((listener) => register(el, event, listener, optionsClone))
|
|
46
|
+
)
|
|
47
|
+
)
|
|
48
|
+
);
|
|
49
|
+
},
|
|
50
|
+
{ flush: "post" }
|
|
51
|
+
);
|
|
52
|
+
const stop = () => {
|
|
53
|
+
stopWatch();
|
|
54
|
+
cleanup();
|
|
55
|
+
};
|
|
56
|
+
tryOnScopeDispose(cleanup);
|
|
57
|
+
return stop;
|
|
58
|
+
}
|
|
59
|
+
function useDropZone(target, options = {}) {
|
|
60
|
+
var _a, _b;
|
|
61
|
+
const isOverDropZone = shallowRef(false);
|
|
62
|
+
const files = shallowRef(null);
|
|
63
|
+
let counter = 0;
|
|
64
|
+
let isValid = true;
|
|
65
|
+
if (isClient) {
|
|
66
|
+
const _options = typeof options === "function" ? { onDrop: options } : options;
|
|
67
|
+
const multiple = (_a = _options.multiple) != null ? _a : true;
|
|
68
|
+
const preventDefaultForUnhandled = (_b = _options.preventDefaultForUnhandled) != null ? _b : false;
|
|
69
|
+
const getFiles = (event) => {
|
|
70
|
+
var _a2, _b2;
|
|
71
|
+
const list = Array.from((_b2 = (_a2 = event.dataTransfer) == null ? void 0 : _a2.files) != null ? _b2 : []);
|
|
72
|
+
return list.length === 0 ? null : multiple ? list : [list[0]];
|
|
73
|
+
};
|
|
74
|
+
const checkDataTypes = (types) => {
|
|
75
|
+
const dataTypes = unref(_options.dataTypes);
|
|
76
|
+
if (typeof dataTypes === "function")
|
|
77
|
+
return dataTypes(types);
|
|
78
|
+
if (!(dataTypes == null ? void 0 : dataTypes.length))
|
|
79
|
+
return true;
|
|
80
|
+
if (types.length === 0)
|
|
81
|
+
return false;
|
|
82
|
+
return types.every(
|
|
83
|
+
(type) => dataTypes.some((allowedType) => type.includes(allowedType))
|
|
84
|
+
);
|
|
85
|
+
};
|
|
86
|
+
const checkValidity = (items) => {
|
|
87
|
+
const types = Array.from(items != null ? items : []).map((item) => item.type);
|
|
88
|
+
const dataTypesValid = checkDataTypes(types);
|
|
89
|
+
const multipleFilesValid = multiple || items.length <= 1;
|
|
90
|
+
return dataTypesValid && multipleFilesValid;
|
|
91
|
+
};
|
|
92
|
+
const isSafari = () => /^(?:(?!chrome|android).)*safari/i.test(navigator.userAgent) && !("chrome" in window);
|
|
93
|
+
const handleDragEvent = (event, eventType) => {
|
|
94
|
+
var _a2, _b2, _c, _d, _e, _f;
|
|
95
|
+
const dataTransferItemList = (_a2 = event.dataTransfer) == null ? void 0 : _a2.items;
|
|
96
|
+
isValid = (_b2 = dataTransferItemList && checkValidity(dataTransferItemList)) != null ? _b2 : false;
|
|
97
|
+
if (preventDefaultForUnhandled) {
|
|
98
|
+
event.preventDefault();
|
|
99
|
+
}
|
|
100
|
+
if (!isSafari() && !isValid) {
|
|
101
|
+
if (event.dataTransfer) {
|
|
102
|
+
event.dataTransfer.dropEffect = "none";
|
|
103
|
+
}
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
event.preventDefault();
|
|
107
|
+
if (event.dataTransfer) {
|
|
108
|
+
event.dataTransfer.dropEffect = "copy";
|
|
109
|
+
}
|
|
110
|
+
const currentFiles = getFiles(event);
|
|
111
|
+
switch (eventType) {
|
|
112
|
+
case "enter":
|
|
113
|
+
counter += 1;
|
|
114
|
+
isOverDropZone.value = true;
|
|
115
|
+
(_c = _options.onEnter) == null ? void 0 : _c.call(_options, null, event);
|
|
116
|
+
break;
|
|
117
|
+
case "over":
|
|
118
|
+
(_d = _options.onOver) == null ? void 0 : _d.call(_options, null, event);
|
|
119
|
+
break;
|
|
120
|
+
case "leave":
|
|
121
|
+
counter -= 1;
|
|
122
|
+
if (counter === 0)
|
|
123
|
+
isOverDropZone.value = false;
|
|
124
|
+
(_e = _options.onLeave) == null ? void 0 : _e.call(_options, null, event);
|
|
125
|
+
break;
|
|
126
|
+
case "drop":
|
|
127
|
+
counter = 0;
|
|
128
|
+
isOverDropZone.value = false;
|
|
129
|
+
if (isValid) {
|
|
130
|
+
files.value = currentFiles;
|
|
131
|
+
(_f = _options.onDrop) == null ? void 0 : _f.call(_options, currentFiles, event);
|
|
132
|
+
}
|
|
133
|
+
break;
|
|
134
|
+
}
|
|
135
|
+
};
|
|
136
|
+
useEventListener(target, "dragenter", (event) => handleDragEvent(event, "enter"));
|
|
137
|
+
useEventListener(target, "dragover", (event) => handleDragEvent(event, "over"));
|
|
138
|
+
useEventListener(target, "dragleave", (event) => handleDragEvent(event, "leave"));
|
|
139
|
+
useEventListener(target, "drop", (event) => handleDragEvent(event, "drop"));
|
|
140
|
+
}
|
|
141
|
+
return {
|
|
142
|
+
files,
|
|
143
|
+
isOverDropZone
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
const _hoisted_1 = ["data-over-drop-zone"];
|
|
147
|
+
const _hoisted_2 = { class: "celeste-file-upload-area-content" };
|
|
148
|
+
const _hoisted_3 = { class: "celeste-file-upload-area-label" };
|
|
149
|
+
const _hoisted_4 = {
|
|
150
|
+
key: 0,
|
|
151
|
+
class: "celeste-file-upload-area-description"
|
|
152
|
+
};
|
|
153
|
+
const _hoisted_5 = ["multiple", "accept"];
|
|
154
|
+
const _sfc_main = /* @__PURE__ */ defineComponent({
|
|
155
|
+
__name: "file-upload-area",
|
|
156
|
+
props: /* @__PURE__ */ mergeModels({
|
|
157
|
+
label: { default: "Choose a file or drag & drop it here." },
|
|
158
|
+
description: { default: "JPEG, PNG, PDF, and MP4 formats, up to 50 MB." },
|
|
159
|
+
labelOnDrop: { default: "Drop file to upload" },
|
|
160
|
+
triggerLabel: { default: "Browse Files" },
|
|
161
|
+
maxSize: { default: 2e3 },
|
|
162
|
+
maxFiles: { default: 5 },
|
|
163
|
+
multiple: { type: Boolean, default: false },
|
|
164
|
+
icon: { default: "i-celeste-upload-cloud-2-line" },
|
|
165
|
+
accept: {},
|
|
166
|
+
onTriggerClick: {},
|
|
167
|
+
class: {}
|
|
168
|
+
}, {
|
|
169
|
+
"modelValue": { default: [] },
|
|
170
|
+
"modelModifiers": {}
|
|
171
|
+
}),
|
|
172
|
+
emits: /* @__PURE__ */ mergeModels(["error"], ["update:modelValue"]),
|
|
173
|
+
setup(__props, { emit: __emit }) {
|
|
174
|
+
const props = __props;
|
|
175
|
+
const emit = __emit;
|
|
176
|
+
const files = useModel(__props, "modelValue");
|
|
177
|
+
const dropZoneRef = useTemplateRef("dropZoneRef");
|
|
178
|
+
const { isOverDropZone } = useDropZone(dropZoneRef, { onDrop });
|
|
179
|
+
function handleUpload(e) {
|
|
180
|
+
const input = e.target;
|
|
181
|
+
const newFiles = Array.from(input.files ?? []);
|
|
182
|
+
const filesToProcess = newFiles.slice(0, props.maxFiles);
|
|
183
|
+
const rejectedFiles = newFiles.slice(props.maxFiles);
|
|
184
|
+
if (rejectedFiles.length > 0) {
|
|
185
|
+
emit("error", {
|
|
186
|
+
message: "Files exceeded upload limit",
|
|
187
|
+
type: "count",
|
|
188
|
+
files: rejectedFiles
|
|
189
|
+
});
|
|
190
|
+
}
|
|
191
|
+
const validFiles = filesToProcess.filter(isValidFile);
|
|
192
|
+
files.value = [...validFiles];
|
|
193
|
+
}
|
|
194
|
+
function onDrop(droppedFiles) {
|
|
195
|
+
if (!(droppedFiles == null ? void 0 : droppedFiles.length))
|
|
196
|
+
return;
|
|
197
|
+
const filesToProcess = droppedFiles.slice(0, props.maxFiles);
|
|
198
|
+
const rejectedFiles = droppedFiles.slice(props.maxFiles);
|
|
199
|
+
if (rejectedFiles.length > 0) {
|
|
200
|
+
emit("error", {
|
|
201
|
+
message: "Files exceeded upload limit",
|
|
202
|
+
type: "count",
|
|
203
|
+
files: rejectedFiles
|
|
204
|
+
});
|
|
205
|
+
}
|
|
206
|
+
const validFiles = filesToProcess.filter(isValidFile);
|
|
207
|
+
files.value = [...validFiles];
|
|
208
|
+
}
|
|
209
|
+
function isValidFile(file) {
|
|
210
|
+
var _a;
|
|
211
|
+
const maxBytes = props.maxSize * 1024;
|
|
212
|
+
if (file.size > maxBytes) {
|
|
213
|
+
emit("error", {
|
|
214
|
+
message: `File too large: ${file.name}`,
|
|
215
|
+
type: "size",
|
|
216
|
+
files: [file]
|
|
217
|
+
});
|
|
218
|
+
return false;
|
|
219
|
+
}
|
|
220
|
+
const acceptableTypes = ((_a = props.accept) == null ? void 0 : _a.split(",")) ?? [];
|
|
221
|
+
if (acceptableTypes.length > 0 && !acceptableTypes.includes(file.type)) {
|
|
222
|
+
emit("error", {
|
|
223
|
+
message: "Unsupported type",
|
|
224
|
+
type: "type",
|
|
225
|
+
files: [file]
|
|
226
|
+
});
|
|
227
|
+
return false;
|
|
228
|
+
}
|
|
229
|
+
return true;
|
|
230
|
+
}
|
|
231
|
+
function handleTriggerClick(e) {
|
|
232
|
+
if (props.onTriggerClick) {
|
|
233
|
+
e.preventDefault();
|
|
234
|
+
props.onTriggerClick();
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
return (_ctx, _cache) => {
|
|
238
|
+
return openBlock(), createElementBlock("label", {
|
|
239
|
+
ref_key: "dropZoneRef",
|
|
240
|
+
ref: dropZoneRef,
|
|
241
|
+
class: normalizeClass(unref(clsx)(
|
|
242
|
+
"celeste-file-upload-area",
|
|
243
|
+
props.class
|
|
244
|
+
)),
|
|
245
|
+
"data-over-drop-zone": unref(isOverDropZone)
|
|
246
|
+
}, [
|
|
247
|
+
createElementVNode("i", {
|
|
248
|
+
class: normalizeClass(_ctx.icon)
|
|
249
|
+
}, null, 2),
|
|
250
|
+
createElementVNode("div", _hoisted_2, [
|
|
251
|
+
createElementVNode("span", _hoisted_3, toDisplayString(unref(isOverDropZone) ? _ctx.labelOnDrop : _ctx.label), 1),
|
|
252
|
+
!unref(isOverDropZone) ? (openBlock(), createElementBlock("span", _hoisted_4, toDisplayString(_ctx.description), 1)) : createCommentVNode("", true)
|
|
253
|
+
]),
|
|
254
|
+
!unref(isOverDropZone) ? (openBlock(), createBlock(Button, {
|
|
255
|
+
key: 0,
|
|
256
|
+
"as-child": "",
|
|
257
|
+
intent: "neutral",
|
|
258
|
+
variant: "stroke",
|
|
259
|
+
size: "xs",
|
|
260
|
+
class: "celeste-file-upload-area-trigger",
|
|
261
|
+
onClick: handleTriggerClick
|
|
262
|
+
}, {
|
|
263
|
+
default: withCtx(() => [
|
|
264
|
+
createElementVNode("span", null, toDisplayString(_ctx.triggerLabel), 1)
|
|
265
|
+
]),
|
|
266
|
+
_: 1
|
|
267
|
+
})) : createCommentVNode("", true),
|
|
268
|
+
createElementVNode("input", mergeProps(_ctx.$attrs, {
|
|
269
|
+
type: "file",
|
|
270
|
+
multiple: _ctx.multiple,
|
|
271
|
+
accept: _ctx.accept,
|
|
272
|
+
onChange: handleUpload
|
|
273
|
+
}), null, 16, _hoisted_5)
|
|
274
|
+
], 10, _hoisted_1);
|
|
275
|
+
};
|
|
276
|
+
}
|
|
277
|
+
});
|
|
278
|
+
const fileUploadArea = /* @__PURE__ */ _export_sfc(_sfc_main, [["__scopeId", "data-v-6e8a873e"]]);
|
|
279
|
+
export {
|
|
280
|
+
fileUploadArea as default
|
|
281
|
+
};
|
package/dist/es/index.js
CHANGED
|
@@ -29,9 +29,6 @@ import { _ as _sfc_main$4 } from "./chunks/counter-input.vue_vue_type_style_inde
|
|
|
29
29
|
import digitInputGroup from "./digit-input-group.js";
|
|
30
30
|
import digitInputSlot from "./digit-input-slot.js";
|
|
31
31
|
import digitInput from "./digit-input.js";
|
|
32
|
-
import contextMenuItem from "./context-menu-item.js";
|
|
33
|
-
import DropdownItem from "./dropdown-item.js";
|
|
34
|
-
import dropdown from "./dropdown.js";
|
|
35
32
|
import dropdownMenuContent from "./dropdown-menu-content.js";
|
|
36
33
|
import dropdownMenuGroup from "./dropdown-menu-group.js";
|
|
37
34
|
import dropdownMenuItem from "./dropdown-menu-item.js";
|
|
@@ -114,13 +111,10 @@ const components = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.definePr
|
|
|
114
111
|
ComboboxTrigger,
|
|
115
112
|
CompactButton,
|
|
116
113
|
ContentDivider: _sfc_main$3,
|
|
117
|
-
ContextMenuItem: contextMenuItem,
|
|
118
114
|
CounterInput: _sfc_main$4,
|
|
119
115
|
DigitInput: digitInput,
|
|
120
116
|
DigitInputGroup: digitInputGroup,
|
|
121
117
|
DigitInputSlot: digitInputSlot,
|
|
122
|
-
Dropdown: dropdown,
|
|
123
|
-
DropdownItem,
|
|
124
118
|
DropdownMenu: dropdownMenu,
|
|
125
119
|
DropdownMenuContent: dropdownMenuContent,
|
|
126
120
|
DropdownMenuGroup: dropdownMenuGroup,
|
|
@@ -263,14 +257,11 @@ export {
|
|
|
263
257
|
ComboboxTrigger,
|
|
264
258
|
CompactButton,
|
|
265
259
|
_sfc_main$3 as ContentDivider,
|
|
266
|
-
contextMenuItem as ContextMenuItem,
|
|
267
260
|
_sfc_main$4 as CounterInput,
|
|
268
261
|
DEFAULT_SWATCH,
|
|
269
262
|
digitInput as DigitInput,
|
|
270
263
|
digitInputGroup as DigitInputGroup,
|
|
271
264
|
digitInputSlot as DigitInputSlot,
|
|
272
|
-
dropdown as Dropdown,
|
|
273
|
-
DropdownItem,
|
|
274
265
|
dropdownMenu as DropdownMenu,
|
|
275
266
|
dropdownMenuContent as DropdownMenuContent,
|
|
276
267
|
dropdownMenuGroup as DropdownMenuGroup,
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@youcan/celeste",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "0.6.
|
|
4
|
+
"version": "0.6.55",
|
|
5
5
|
"author": "YouCan <tech@youcan.shop>",
|
|
6
6
|
"keywords": [
|
|
7
7
|
"youcan",
|
|
@@ -44,15 +44,15 @@
|
|
|
44
44
|
"radix-vue": "^1.9.12",
|
|
45
45
|
"tinycolor2": "^1.6.0",
|
|
46
46
|
"vue-sonner": "^2.0.2",
|
|
47
|
-
"@youcan/celeste-icons": "0.6.
|
|
48
|
-
"@youcan/celeste-tokens": "0.6.
|
|
47
|
+
"@youcan/celeste-icons": "0.6.55",
|
|
48
|
+
"@youcan/celeste-tokens": "0.6.55"
|
|
49
49
|
},
|
|
50
50
|
"devDependencies": {
|
|
51
|
-
"@chromatic-com/storybook": "^4.
|
|
52
|
-
"@storybook/addon-docs": "^9.
|
|
51
|
+
"@chromatic-com/storybook": "^4.1.1",
|
|
52
|
+
"@storybook/addon-docs": "^9.1.10",
|
|
53
53
|
"@storybook/addon-knobs": "^8.0.1",
|
|
54
|
-
"@storybook/addon-onboarding": "^9.
|
|
55
|
-
"@storybook/vue3-vite": "^9.
|
|
54
|
+
"@storybook/addon-onboarding": "^9.1.10",
|
|
55
|
+
"@storybook/vue3-vite": "^9.1.10",
|
|
56
56
|
"@tsconfig/node20": "^20.1.4",
|
|
57
57
|
"@types/jsdom": "^21.1.7",
|
|
58
58
|
"@types/tinycolor2": "^1.4.6",
|
|
@@ -64,14 +64,14 @@
|
|
|
64
64
|
"@vue/tsconfig": "^0.7.0",
|
|
65
65
|
"autoprefixer": "^10.4.21",
|
|
66
66
|
"chromatic": "^11.22.1",
|
|
67
|
-
"eslint-plugin-storybook": "9.
|
|
67
|
+
"eslint-plugin-storybook": "9.1.10",
|
|
68
68
|
"glob": "^11.0.3",
|
|
69
69
|
"jsdom": "^25.0.1",
|
|
70
70
|
"postcss": "^8.5.6",
|
|
71
71
|
"postcss-nesting": "^13.0.2",
|
|
72
72
|
"rimraf": "^6.0.1",
|
|
73
73
|
"sass": "^1.83.1",
|
|
74
|
-
"storybook": "^9.
|
|
74
|
+
"storybook": "^9.1.10",
|
|
75
75
|
"unocss": "^66.3.3",
|
|
76
76
|
"vite": "^6.0.7",
|
|
77
77
|
"vite-plugin-dts": "^4.4.0",
|
|
@@ -17,6 +17,7 @@ const props = withDefaults(defineProps<ColorPickerProps>(), {
|
|
|
17
17
|
modelValue: 'hsl(240, 100%, 50%)',
|
|
18
18
|
formats: () => ['hex', 'rgb', 'hsl', 'hsb'],
|
|
19
19
|
label: 'Pick Color',
|
|
20
|
+
colorSwatchLabel: 'Recommended Colors',
|
|
20
21
|
});
|
|
21
22
|
|
|
22
23
|
const emit = defineEmits<ColorPickerEmits>();
|
|
@@ -246,6 +247,7 @@ export interface ColorPickerProps {
|
|
|
246
247
|
class?: HTMLAttributes['class'];
|
|
247
248
|
formats?: ColorFormat[];
|
|
248
249
|
defaultFormat?: ColorFormat;
|
|
250
|
+
colorSwatchLabel?: string;
|
|
249
251
|
}
|
|
250
252
|
|
|
251
253
|
export interface ColorPickerEmits {
|
|
@@ -489,6 +491,7 @@ declare global {
|
|
|
489
491
|
<ColorSwatch
|
|
490
492
|
v-else
|
|
491
493
|
v-model="tinyColorRef"
|
|
494
|
+
:label="colorSwatchLabel"
|
|
492
495
|
:hue="hueRef"
|
|
493
496
|
/>
|
|
494
497
|
</div>
|
|
@@ -507,6 +510,7 @@ declare global {
|
|
|
507
510
|
display: block;
|
|
508
511
|
width: var(--color-tile-size);
|
|
509
512
|
height: var(--color-tile-size);
|
|
513
|
+
border: 1px solid var(--color-bg-soft-200);
|
|
510
514
|
border-radius: var(--radius-4);
|
|
511
515
|
background-color: v-bind(modelValue);
|
|
512
516
|
}
|
|
@@ -5,7 +5,6 @@ import { computed } from 'vue';
|
|
|
5
5
|
import { defineColorModel } from './composable/use-color-model';
|
|
6
6
|
|
|
7
7
|
const props = withDefaults(defineProps<ColorSwatchProps>(), {
|
|
8
|
-
label: 'Recommended Colors',
|
|
9
8
|
swatch: () => DEFAULT_SWATCH,
|
|
10
9
|
});
|
|
11
10
|
const emit = defineEmits<ColorSwatchEmits>();
|
|
@@ -28,7 +27,7 @@ function isSwatchColorSelected(color: string) {
|
|
|
28
27
|
<script lang="ts">
|
|
29
28
|
export interface ColorSwatchProps {
|
|
30
29
|
modelValue: tinycolor.Instance;
|
|
31
|
-
label
|
|
30
|
+
label: string;
|
|
32
31
|
swatch?: string[];
|
|
33
32
|
defaultColor?: string;
|
|
34
33
|
}
|
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import Button from '@/components/button/button.vue';
|
|
3
|
+
import { useDropZone } from '@vueuse/core';
|
|
4
|
+
import clsx from 'clsx';
|
|
5
|
+
import { type InputHTMLAttributes, useTemplateRef } from 'vue';
|
|
6
|
+
|
|
7
|
+
const props = withDefaults(defineProps<FileUploadAreaProps>(), {
|
|
8
|
+
icon: 'i-celeste-upload-cloud-2-line',
|
|
9
|
+
label: 'Choose a file or drag & drop it here.',
|
|
10
|
+
description: 'JPEG, PNG, PDF, and MP4 formats, up to 50 MB.',
|
|
11
|
+
triggerLabel: 'Browse Files',
|
|
12
|
+
labelOnDrop: 'Drop file to upload',
|
|
13
|
+
multiple: false,
|
|
14
|
+
maxSize: 2000,
|
|
15
|
+
maxFiles: 5,
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
const emit = defineEmits<FileUploadAreaEmit>();
|
|
19
|
+
|
|
20
|
+
const files = defineModel<File[]>({ default: [] });
|
|
21
|
+
|
|
22
|
+
const dropZoneRef = useTemplateRef<HTMLElement>('dropZoneRef');
|
|
23
|
+
const { isOverDropZone } = useDropZone(dropZoneRef, { onDrop });
|
|
24
|
+
|
|
25
|
+
function handleUpload(e: Event) {
|
|
26
|
+
const input = e.target as HTMLInputElement;
|
|
27
|
+
const newFiles = Array.from(input.files ?? []);
|
|
28
|
+
|
|
29
|
+
const filesToProcess = newFiles.slice(0, props.maxFiles);
|
|
30
|
+
const rejectedFiles = newFiles.slice(props.maxFiles);
|
|
31
|
+
|
|
32
|
+
if (rejectedFiles.length > 0) {
|
|
33
|
+
emit('error', {
|
|
34
|
+
message: 'Files exceeded upload limit',
|
|
35
|
+
type: 'count',
|
|
36
|
+
files: rejectedFiles,
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const validFiles = filesToProcess.filter(isValidFile);
|
|
41
|
+
|
|
42
|
+
files.value = [...validFiles];
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function onDrop(droppedFiles: File[] | null) {
|
|
46
|
+
if (!droppedFiles?.length)
|
|
47
|
+
return;
|
|
48
|
+
|
|
49
|
+
const filesToProcess = droppedFiles.slice(0, props.maxFiles);
|
|
50
|
+
const rejectedFiles = droppedFiles.slice(props.maxFiles);
|
|
51
|
+
|
|
52
|
+
if (rejectedFiles.length > 0) {
|
|
53
|
+
emit('error', {
|
|
54
|
+
message: 'Files exceeded upload limit',
|
|
55
|
+
type: 'count',
|
|
56
|
+
files: rejectedFiles,
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const validFiles = filesToProcess.filter(isValidFile);
|
|
61
|
+
files.value = [...validFiles];
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function isValidFile(file: File): boolean {
|
|
65
|
+
const maxBytes = props.maxSize * 1024;
|
|
66
|
+
|
|
67
|
+
if (file.size > maxBytes) {
|
|
68
|
+
emit('error', {
|
|
69
|
+
message: `File too large: ${file.name}`,
|
|
70
|
+
type: 'size',
|
|
71
|
+
files: [file],
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
return false;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const acceptableTypes = props.accept?.split(',') ?? [];
|
|
78
|
+
|
|
79
|
+
if (acceptableTypes.length > 0 && !acceptableTypes.includes(file.type)) {
|
|
80
|
+
emit('error', {
|
|
81
|
+
message: 'Unsupported type',
|
|
82
|
+
type: 'type',
|
|
83
|
+
files: [file],
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
return false;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
return true;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function handleTriggerClick(e: Event) {
|
|
93
|
+
if (props.onTriggerClick) {
|
|
94
|
+
e.preventDefault();
|
|
95
|
+
props.onTriggerClick();
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
</script>
|
|
99
|
+
|
|
100
|
+
<script lang="ts">
|
|
101
|
+
export interface FileUploadAreaProps {
|
|
102
|
+
label?: string;
|
|
103
|
+
description?: string;
|
|
104
|
+
labelOnDrop?: string;
|
|
105
|
+
triggerLabel?: string;
|
|
106
|
+
maxSize?: number;
|
|
107
|
+
maxFiles?: number;
|
|
108
|
+
multiple?: boolean;
|
|
109
|
+
icon?: string;
|
|
110
|
+
accept?: InputHTMLAttributes['accept'];
|
|
111
|
+
onTriggerClick?: () => void;
|
|
112
|
+
class?: InputHTMLAttributes['class'];
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
export interface FileError {
|
|
116
|
+
message: string;
|
|
117
|
+
type: 'size' | 'type' | 'count';
|
|
118
|
+
files: File[];
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
export interface FileUploadAreaEmit {
|
|
122
|
+
error: [error: FileError];
|
|
123
|
+
}
|
|
124
|
+
</script>
|
|
125
|
+
|
|
126
|
+
<template>
|
|
127
|
+
<label
|
|
128
|
+
ref="dropZoneRef"
|
|
129
|
+
:class="clsx(
|
|
130
|
+
'celeste-file-upload-area',
|
|
131
|
+
props.class,
|
|
132
|
+
)"
|
|
133
|
+
:data-over-drop-zone="isOverDropZone"
|
|
134
|
+
>
|
|
135
|
+
<i :class="icon" />
|
|
136
|
+
<div class="celeste-file-upload-area-content">
|
|
137
|
+
<span class="celeste-file-upload-area-label">
|
|
138
|
+
{{ isOverDropZone ? labelOnDrop : label }}
|
|
139
|
+
</span>
|
|
140
|
+
<span
|
|
141
|
+
v-if="!isOverDropZone"
|
|
142
|
+
class="celeste-file-upload-area-description"
|
|
143
|
+
>
|
|
144
|
+
{{ description }}
|
|
145
|
+
</span>
|
|
146
|
+
</div>
|
|
147
|
+
<Button
|
|
148
|
+
v-if="!isOverDropZone"
|
|
149
|
+
as-child
|
|
150
|
+
intent="neutral"
|
|
151
|
+
variant="stroke"
|
|
152
|
+
size="xs"
|
|
153
|
+
class="celeste-file-upload-area-trigger"
|
|
154
|
+
@click="handleTriggerClick"
|
|
155
|
+
>
|
|
156
|
+
<span>{{ triggerLabel }}</span>
|
|
157
|
+
</Button>
|
|
158
|
+
<input
|
|
159
|
+
v-bind="$attrs"
|
|
160
|
+
type="file"
|
|
161
|
+
:multiple="multiple"
|
|
162
|
+
:accept="accept"
|
|
163
|
+
@change="handleUpload"
|
|
164
|
+
>
|
|
165
|
+
</label>
|
|
166
|
+
</template>
|
|
167
|
+
|
|
168
|
+
<style scoped lang="scss">
|
|
169
|
+
.celeste-file-upload-area {
|
|
170
|
+
display: flex;
|
|
171
|
+
box-sizing: border-box;
|
|
172
|
+
flex-direction: column;
|
|
173
|
+
align-items: center;
|
|
174
|
+
justify-content: center;
|
|
175
|
+
min-height: 204px;
|
|
176
|
+
gap: calc(var(--spacing-10) * 2);
|
|
177
|
+
padding: calc(var(--spacing-16) * 2);
|
|
178
|
+
transition: background-color var(--animation-normal) ease-out;
|
|
179
|
+
border: 1px dashed var(--color-stroke-sub-300);
|
|
180
|
+
border-radius: var(--radius-12);
|
|
181
|
+
background-color: var(--color-bg-white-0);
|
|
182
|
+
cursor: pointer;
|
|
183
|
+
|
|
184
|
+
&:hover {
|
|
185
|
+
background-color: var(--color-bg-weak-50);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
& > i {
|
|
189
|
+
width: 24px;
|
|
190
|
+
height: 24px;
|
|
191
|
+
color: var(--color-icon-sub-600);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
.celeste-file-upload-area-content {
|
|
195
|
+
display: grid;
|
|
196
|
+
gap: var(--spacing-6);
|
|
197
|
+
text-align: center;
|
|
198
|
+
|
|
199
|
+
.celeste-file-upload-area-label {
|
|
200
|
+
color: var(--color-text-strong-950);
|
|
201
|
+
font: var(--label-sm);
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
.celeste-file-upload-area-description {
|
|
205
|
+
color: var(--color-text-sub-600);
|
|
206
|
+
font: var(--label-xs);
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
.celeste-file-upload-area-trigger {
|
|
211
|
+
background-color: var(--color-bg-white-0) !important;
|
|
212
|
+
|
|
213
|
+
&:hover {
|
|
214
|
+
background-color: transparent !important;
|
|
215
|
+
box-shadow: none !important;
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
& > input[type='file'] {
|
|
220
|
+
position: absolute;
|
|
221
|
+
opacity: 0;
|
|
222
|
+
scale: 0;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
&[data-over-drop-zone='true'] {
|
|
226
|
+
background-color: var(--color-bg-weak-50);
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
</style>
|