@youcan/celeste 0.6.54 → 0.6.56
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 +86 -560
- 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 +3 -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 +1 -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 +3 -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 +1 -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";
|
|
@@ -42,6 +39,7 @@ import dropdownMenuSubTrigger from "./dropdown-menu-sub-trigger.js";
|
|
|
42
39
|
import { _ as _sfc_main$5 } from "./chunks/dropdown-menu-sub.js";
|
|
43
40
|
import dropdownMenuTrigger from "./dropdown-menu-trigger.js";
|
|
44
41
|
import dropdownMenu from "./dropdown-menu.js";
|
|
42
|
+
import fileUploadArea from "./file-upload-area.js";
|
|
45
43
|
import hintText from "./hint-text.js";
|
|
46
44
|
import Label from "./label.js";
|
|
47
45
|
import modalBody from "./modal-body.js";
|
|
@@ -114,13 +112,10 @@ const components = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.definePr
|
|
|
114
112
|
ComboboxTrigger,
|
|
115
113
|
CompactButton,
|
|
116
114
|
ContentDivider: _sfc_main$3,
|
|
117
|
-
ContextMenuItem: contextMenuItem,
|
|
118
115
|
CounterInput: _sfc_main$4,
|
|
119
116
|
DigitInput: digitInput,
|
|
120
117
|
DigitInputGroup: digitInputGroup,
|
|
121
118
|
DigitInputSlot: digitInputSlot,
|
|
122
|
-
Dropdown: dropdown,
|
|
123
|
-
DropdownItem,
|
|
124
119
|
DropdownMenu: dropdownMenu,
|
|
125
120
|
DropdownMenuContent: dropdownMenuContent,
|
|
126
121
|
DropdownMenuGroup: dropdownMenuGroup,
|
|
@@ -132,6 +127,7 @@ const components = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.definePr
|
|
|
132
127
|
DropdownMenuSubTrigger: dropdownMenuSubTrigger,
|
|
133
128
|
DropdownMenuTrigger: dropdownMenuTrigger,
|
|
134
129
|
FancyButton: fancyButton,
|
|
130
|
+
FileUploadArea: fileUploadArea,
|
|
135
131
|
HintText: hintText,
|
|
136
132
|
HueSlider,
|
|
137
133
|
Label,
|
|
@@ -263,14 +259,11 @@ export {
|
|
|
263
259
|
ComboboxTrigger,
|
|
264
260
|
CompactButton,
|
|
265
261
|
_sfc_main$3 as ContentDivider,
|
|
266
|
-
contextMenuItem as ContextMenuItem,
|
|
267
262
|
_sfc_main$4 as CounterInput,
|
|
268
263
|
DEFAULT_SWATCH,
|
|
269
264
|
digitInput as DigitInput,
|
|
270
265
|
digitInputGroup as DigitInputGroup,
|
|
271
266
|
digitInputSlot as DigitInputSlot,
|
|
272
|
-
dropdown as Dropdown,
|
|
273
|
-
DropdownItem,
|
|
274
267
|
dropdownMenu as DropdownMenu,
|
|
275
268
|
dropdownMenuContent as DropdownMenuContent,
|
|
276
269
|
dropdownMenuGroup as DropdownMenuGroup,
|
|
@@ -282,6 +275,7 @@ export {
|
|
|
282
275
|
dropdownMenuSubTrigger as DropdownMenuSubTrigger,
|
|
283
276
|
dropdownMenuTrigger as DropdownMenuTrigger,
|
|
284
277
|
fancyButton as FancyButton,
|
|
278
|
+
fileUploadArea as FileUploadArea,
|
|
285
279
|
HEX_VALIDATION_PATTERN,
|
|
286
280
|
hintText as HintText,
|
|
287
281
|
HueSlider,
|
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.56",
|
|
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.56",
|
|
48
|
+
"@youcan/celeste-tokens": "0.6.56"
|
|
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>
|