@crisp-ui-kit/crisp 0.7.6 → 0.8.0
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/cjs/components/fileupload/fileupload.js +118 -0
- package/dist/cjs/components/fileupload/fileupload.recipe.js +104 -0
- package/dist/cjs/components/fileupload/index.js +18 -0
- package/dist/cjs/index.js +1 -0
- package/dist/esm/components/fileupload/fileupload.js +81 -0
- package/dist/esm/components/fileupload/fileupload.recipe.js +101 -0
- package/dist/esm/components/fileupload/index.js +2 -0
- package/dist/esm/index.js +1 -0
- package/dist/fileupload.css +85 -0
- package/dist/fileupload.d.ts +7 -0
- package/dist/styles.css +86 -0
- package/dist/types/components/fileupload/fileupload.d.ts +24 -0
- package/dist/types/components/fileupload/fileupload.recipe.d.ts +5 -0
- package/dist/types/components/fileupload/index.d.ts +2 -0
- package/dist/types/index.d.ts +1 -0
- package/package.json +7 -1
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
"use client";
|
|
3
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
4
|
+
if (k2 === undefined) k2 = k;
|
|
5
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
6
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
7
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
8
|
+
}
|
|
9
|
+
Object.defineProperty(o, k2, desc);
|
|
10
|
+
}) : (function(o, m, k, k2) {
|
|
11
|
+
if (k2 === undefined) k2 = k;
|
|
12
|
+
o[k2] = m[k];
|
|
13
|
+
}));
|
|
14
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
15
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
16
|
+
}) : function(o, v) {
|
|
17
|
+
o["default"] = v;
|
|
18
|
+
});
|
|
19
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
20
|
+
var ownKeys = function(o) {
|
|
21
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
22
|
+
var ar = [];
|
|
23
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
24
|
+
return ar;
|
|
25
|
+
};
|
|
26
|
+
return ownKeys(o);
|
|
27
|
+
};
|
|
28
|
+
return function (mod) {
|
|
29
|
+
if (mod && mod.__esModule) return mod;
|
|
30
|
+
var result = {};
|
|
31
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
32
|
+
__setModuleDefault(result, mod);
|
|
33
|
+
return result;
|
|
34
|
+
};
|
|
35
|
+
})();
|
|
36
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
37
|
+
exports.fileUploadLabels = void 0;
|
|
38
|
+
exports.FileUpload = FileUpload;
|
|
39
|
+
const jsx_runtime_1 = require("react/jsx-runtime");
|
|
40
|
+
const react_file_upload_1 = require("@seed-design/react-file-upload");
|
|
41
|
+
const lucide_react_1 = require("lucide-react");
|
|
42
|
+
const React = __importStar(require("react"));
|
|
43
|
+
const fileupload_recipe_1 = require("./fileupload.recipe");
|
|
44
|
+
exports.fileUploadLabels = {
|
|
45
|
+
// Attio's own copy on the record Files pane.
|
|
46
|
+
title: "No files",
|
|
47
|
+
hint: "Drag a file here or choose one from your computer.",
|
|
48
|
+
remove: "Remove file",
|
|
49
|
+
errors: {
|
|
50
|
+
FILE_TOO_LARGE: "That file is too large.",
|
|
51
|
+
FILE_TOO_SMALL: "That file is too small.",
|
|
52
|
+
TOO_MANY_FILES: "Too many files.",
|
|
53
|
+
INVALID_TYPE: "That file type is not accepted.",
|
|
54
|
+
},
|
|
55
|
+
};
|
|
56
|
+
/** 1000-based, matching how every file manager reports size. */
|
|
57
|
+
function formatSize(bytes) {
|
|
58
|
+
if (bytes < 1000)
|
|
59
|
+
return `${bytes} B`;
|
|
60
|
+
const units = ["kB", "MB", "GB", "TB"];
|
|
61
|
+
let n = bytes;
|
|
62
|
+
let i = -1;
|
|
63
|
+
while (n >= 1000 && i < units.length - 1) {
|
|
64
|
+
n /= 1000;
|
|
65
|
+
i++;
|
|
66
|
+
}
|
|
67
|
+
return `${n < 10 ? n.toFixed(1) : Math.round(n)} ${units[i]}`;
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* A file dropzone with an accepted-file list, driven by seed-design's headless
|
|
71
|
+
* engine so the behaviour — drag state, accept/reject, per-file upload status —
|
|
72
|
+
* is theirs by construction. Controlled or uncontrolled, the same way every
|
|
73
|
+
* other crisp component is.
|
|
74
|
+
*/
|
|
75
|
+
function FileUpload({ className, labels, "aria-label": ariaLabel, accept, disabled: disabledProp, required, invalid: invalidProp, readOnly, maxFiles, maxFileSize, minFileSize, name, validate, preventDocumentDrop, onFileReject, onFileAccept, acceptedFileEntries: acceptedFileEntriesProp, defaultAcceptedFileEntries, onAcceptedFileEntriesChange, ...rest }) {
|
|
76
|
+
const props = {
|
|
77
|
+
accept,
|
|
78
|
+
disabled: disabledProp,
|
|
79
|
+
required,
|
|
80
|
+
invalid: invalidProp,
|
|
81
|
+
readOnly,
|
|
82
|
+
maxFiles,
|
|
83
|
+
maxFileSize,
|
|
84
|
+
minFileSize,
|
|
85
|
+
name,
|
|
86
|
+
validate,
|
|
87
|
+
preventDocumentDrop,
|
|
88
|
+
onFileReject,
|
|
89
|
+
onFileAccept,
|
|
90
|
+
acceptedFileEntries: acceptedFileEntriesProp,
|
|
91
|
+
defaultAcceptedFileEntries,
|
|
92
|
+
onAcceptedFileEntriesChange,
|
|
93
|
+
};
|
|
94
|
+
const [rejections, setRejections] = React.useState([]);
|
|
95
|
+
const t = {
|
|
96
|
+
...exports.fileUploadLabels,
|
|
97
|
+
...labels,
|
|
98
|
+
errors: { ...exports.fileUploadLabels.errors, ...labels?.errors },
|
|
99
|
+
};
|
|
100
|
+
const api = (0, react_file_upload_1.useFileUpload)({
|
|
101
|
+
...props,
|
|
102
|
+
onFileReject: (r) => {
|
|
103
|
+
setRejections(r);
|
|
104
|
+
props.onFileReject?.(r);
|
|
105
|
+
},
|
|
106
|
+
onFileAccept: (entries, helpers) => {
|
|
107
|
+
setRejections([]);
|
|
108
|
+
props.onFileAccept?.(entries, helpers);
|
|
109
|
+
},
|
|
110
|
+
});
|
|
111
|
+
const { acceptedFileEntries, dragging, disabled, invalid, openFilePicker, removeFileEntry, dropzoneProps, hiddenInputProps,
|
|
112
|
+
// The prop getters deliberately exclude the refs -- the engine's own
|
|
113
|
+
// FileUploadDropzone/HiddenInput compose them. Rendering crisp's markup
|
|
114
|
+
// instead means attaching them here; without inputRef, `openFilePicker`
|
|
115
|
+
// finds no input and returns silently, so the dropzone opens nothing.
|
|
116
|
+
inputRef, dropzoneRef, } = api;
|
|
117
|
+
return ((0, jsx_runtime_1.jsxs)("div", { ...rest, className: (0, fileupload_recipe_1.fileuploadRecipe)({ className }), children: [(0, jsx_runtime_1.jsxs)("button", { ...dropzoneProps, ref: dropzoneRef, type: "button", className: "fileupload-zone", tabIndex: disabled ? -1 : 0, "aria-label": ariaLabel ?? t.hint, disabled: disabled, "data-dragging": dragging ? "" : undefined, "data-disabled": disabled ? "" : undefined, "data-invalid": invalid ? "" : undefined, onClick: () => openFilePicker(), children: [(0, jsx_runtime_1.jsx)("span", { className: "fileupload-title", children: t.title }), (0, jsx_runtime_1.jsx)("span", { className: "fileupload-hint", children: t.hint })] }), (0, jsx_runtime_1.jsx)("input", { ...hiddenInputProps, ref: inputRef, className: "fileupload-input" }), acceptedFileEntries.length > 0 && ((0, jsx_runtime_1.jsx)("ul", { className: "fileupload-list", children: acceptedFileEntries.map((entry) => ((0, jsx_runtime_1.jsxs)("li", { className: "fileupload-item", "data-status": entry.status, children: [(0, jsx_runtime_1.jsx)("span", { className: "fileupload-item-name", children: entry.file.name }), entry.status === "uploading" && entry.progress != null && ((0, jsx_runtime_1.jsx)("span", { className: "fileupload-progress", role: "progressbar", "aria-valuenow": Math.round(entry.progress), "aria-valuemin": 0, "aria-valuemax": 100, "aria-label": entry.file.name, children: (0, jsx_runtime_1.jsx)("span", { className: "fileupload-progress-fill", style: { width: `${entry.progress}%` } }) })), (0, jsx_runtime_1.jsx)("span", { className: "fileupload-item-size", children: formatSize(entry.file.size) }), (0, jsx_runtime_1.jsx)("button", { type: "button", className: "fileupload-remove", "aria-label": `${t.remove}: ${entry.file.name}`, onClick: () => removeFileEntry(entry.id), children: (0, jsx_runtime_1.jsx)(lucide_react_1.X, { size: 13, strokeWidth: 2 }) })] }, entry.id))) })), rejections.length > 0 && ((0, jsx_runtime_1.jsx)("ul", { className: "fileupload-errors", "aria-live": "polite", children: rejections.map((r) => ((0, jsx_runtime_1.jsx)("li", { className: "fileupload-error", children: `${r.file.name}: ${r.errors.map((e) => t.errors[e] ?? e).join(" ")}` }, r.file.name))) }))] }));
|
|
118
|
+
}
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.fileuploadRecipe = void 0;
|
|
4
|
+
const core_1 = require("../../core");
|
|
5
|
+
const tokens_1 = require("../../tokens");
|
|
6
|
+
// seed-design's file-upload behaviour on Attio's measured scale.
|
|
7
|
+
//
|
|
8
|
+
// Measured from Attio's record Files pane (Deal record → Files, empty state):
|
|
9
|
+
// the title is 16/20 at weight 600 in `rgb(16,17,18)` (= gray900 = fgPrimary),
|
|
10
|
+
// the hint 14/20 at weight 500 in `rgba(0,0,0,0.63)`, and the action button is
|
|
11
|
+
// 28px tall with an 8px corner.
|
|
12
|
+
//
|
|
13
|
+
// Attio draws NO boundary around that pane -- the whole surface is the drop
|
|
14
|
+
// target -- but a reusable dropzone needs an edge, so the dashed affordance is
|
|
15
|
+
// crisp's own measured one from `sidebar-newlist` (1px dashed borderDefault,
|
|
16
|
+
// r8, bgRaised on hover) rather than a new invention. Attio's drag-OVER
|
|
17
|
+
// treatment could not be measured: synthetic DragEvents do not trigger its
|
|
18
|
+
// handlers. The active state therefore reuses the brand emphasis crisp already
|
|
19
|
+
// uses for a live drag affordance (`appshell-resize[data-dragging]`).
|
|
20
|
+
const config = (0, core_1.defineRecipe)({
|
|
21
|
+
name: "fileupload",
|
|
22
|
+
base: `
|
|
23
|
+
display: flex; flex-direction: column; gap: ${tokens_1.contract.space["2"]};
|
|
24
|
+
font-family: ${tokens_1.contract.font.sans}; min-width: 0;
|
|
25
|
+
|
|
26
|
+
& .fileupload-zone {
|
|
27
|
+
box-sizing: border-box; position: relative;
|
|
28
|
+
display: flex; flex-direction: column; align-items: center;
|
|
29
|
+
justify-content: center; gap: ${tokens_1.contract.space["1"]};
|
|
30
|
+
width: 100%; padding: ${tokens_1.contract.space["6"]} ${tokens_1.contract.space["4"]};
|
|
31
|
+
border: 1px dashed ${tokens_1.contract.borderDefault}; border-radius: ${tokens_1.contract.radius.md};
|
|
32
|
+
background: transparent; cursor: pointer; text-align: center;
|
|
33
|
+
font-family: inherit;
|
|
34
|
+
transition: background-color .12s, border-color .12s;
|
|
35
|
+
outline: 2px solid transparent; outline-offset: -2px;
|
|
36
|
+
}
|
|
37
|
+
& .fileupload-zone:hover { background: ${tokens_1.contract.bgRaised}; }
|
|
38
|
+
& .fileupload-zone:focus-visible { outline-color: ${tokens_1.contract.strokeFocusRing}; }
|
|
39
|
+
& .fileupload-zone[data-dragging] {
|
|
40
|
+
border-color: ${tokens_1.contract.bgBrandSolid};
|
|
41
|
+
background: color-mix(in srgb, ${tokens_1.contract.bgBrandSolid} 6%, transparent);
|
|
42
|
+
}
|
|
43
|
+
& .fileupload-zone[data-disabled] { opacity: .5; cursor: not-allowed; }
|
|
44
|
+
& .fileupload-zone[data-invalid] { border-color: ${tokens_1.contract.bgDanger}; }
|
|
45
|
+
|
|
46
|
+
/* Attio: "No files" 16/20 weight 600 */
|
|
47
|
+
& .fileupload-title {
|
|
48
|
+
color: ${tokens_1.contract.fgPrimary}; font-size: ${tokens_1.contract.text.xl}; line-height: 20px;
|
|
49
|
+
font-weight: ${tokens_1.contract.weight.semibold};
|
|
50
|
+
}
|
|
51
|
+
/* Attio: the hint line, 14/20 weight 500 at 63% of the foreground */
|
|
52
|
+
& .fileupload-hint {
|
|
53
|
+
color: color-mix(in srgb, ${tokens_1.contract.fgPrimary} 63%, transparent);
|
|
54
|
+
font-size: ${tokens_1.contract.text.lg}; line-height: 20px;
|
|
55
|
+
font-weight: ${tokens_1.contract.weight.medium};
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
& .fileupload-input {
|
|
59
|
+
position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
|
|
60
|
+
overflow: hidden; clip: rect(0, 0, 0, 0); white-space: nowrap; border: 0;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
& .fileupload-list { display: flex; flex-direction: column; gap: ${tokens_1.contract.space["1"]}; margin: 0; padding: 0; list-style: none; }
|
|
64
|
+
/* the row height crisp measured for a data-grid row */
|
|
65
|
+
& .fileupload-item {
|
|
66
|
+
display: flex; align-items: center; gap: ${tokens_1.contract.space["2"]};
|
|
67
|
+
min-height: ${tokens_1.contract.size.tableRow}; padding: 0 ${tokens_1.contract.space["2"]};
|
|
68
|
+
border-radius: ${tokens_1.contract.radius.md}; background: ${tokens_1.contract.bgRaised};
|
|
69
|
+
font-size: ${tokens_1.contract.text.lg}; line-height: 20px; color: ${tokens_1.contract.fgPrimary};
|
|
70
|
+
}
|
|
71
|
+
& .fileupload-item-name { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
72
|
+
& .fileupload-item-size { flex-shrink: 0; color: ${tokens_1.contract.fgMuted}; font-size: ${tokens_1.contract.text.sm}; font-variant-numeric: tabular-nums; }
|
|
73
|
+
& .fileupload-item[data-status="error"] { color: ${tokens_1.contract.bgDanger}; }
|
|
74
|
+
& .fileupload-item[data-status="error"] .fileupload-item-size { color: ${tokens_1.contract.bgDanger}; }
|
|
75
|
+
|
|
76
|
+
/* progress track, reusing the measured Progress geometry */
|
|
77
|
+
& .fileupload-progress {
|
|
78
|
+
flex-shrink: 0; width: 64px; height: 4px; border-radius: ${tokens_1.contract.radius.full};
|
|
79
|
+
background: ${tokens_1.contract.bgNeutralWeak}; overflow: hidden;
|
|
80
|
+
}
|
|
81
|
+
& .fileupload-progress-fill { height: 100%; background: ${tokens_1.contract.bgBrandSolid}; transition: width .2s ease; }
|
|
82
|
+
|
|
83
|
+
& .fileupload-remove {
|
|
84
|
+
display: inline-flex; align-items: center; justify-content: center;
|
|
85
|
+
flex-shrink: 0; width: ${tokens_1.contract.size.icon}; height: ${tokens_1.contract.size.icon};
|
|
86
|
+
padding: 0; border: none; border-radius: ${tokens_1.contract.radius.sm};
|
|
87
|
+
background: transparent; color: ${tokens_1.contract.fgMuted}; cursor: pointer;
|
|
88
|
+
font-family: inherit;
|
|
89
|
+
outline: 2px solid transparent; outline-offset: -2px;
|
|
90
|
+
}
|
|
91
|
+
& .fileupload-remove:hover { color: ${tokens_1.contract.fgPrimary}; }
|
|
92
|
+
& .fileupload-remove:focus-visible { outline-color: ${tokens_1.contract.strokeFocusRing}; }
|
|
93
|
+
|
|
94
|
+
& .fileupload-errors { display: flex; flex-direction: column; gap: ${tokens_1.contract.space["0.5"]}; margin: 0; padding: 0; list-style: none; }
|
|
95
|
+
& .fileupload-error { color: ${tokens_1.contract.bgDanger}; font-size: ${tokens_1.contract.text.md}; line-height: 18px; }
|
|
96
|
+
`,
|
|
97
|
+
variants: {},
|
|
98
|
+
defaultVariants: {},
|
|
99
|
+
compoundVariants: [],
|
|
100
|
+
});
|
|
101
|
+
exports.fileuploadRecipe = Object.assign((0, core_1.resolveRecipe)(config), {
|
|
102
|
+
config,
|
|
103
|
+
});
|
|
104
|
+
exports.default = config;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
14
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
|
+
};
|
|
16
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
+
__exportStar(require("./fileupload"), exports);
|
|
18
|
+
__exportStar(require("./fileupload.recipe"), exports);
|
package/dist/cjs/index.js
CHANGED
|
@@ -39,6 +39,7 @@ __exportStar(require("./components/divider"), exports);
|
|
|
39
39
|
__exportStar(require("./components/donutchart"), exports);
|
|
40
40
|
__exportStar(require("./components/drawer"), exports);
|
|
41
41
|
__exportStar(require("./components/emptystate"), exports);
|
|
42
|
+
__exportStar(require("./components/fileupload"), exports);
|
|
42
43
|
__exportStar(require("./components/formfield"), exports);
|
|
43
44
|
__exportStar(require("./components/input"), exports);
|
|
44
45
|
__exportStar(require("./components/kbd"), exports);
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { useFileUpload, } from "@seed-design/react-file-upload";
|
|
4
|
+
import { X } from "lucide-react";
|
|
5
|
+
import * as React from "react";
|
|
6
|
+
import { fileuploadRecipe } from "./fileupload.recipe.js";
|
|
7
|
+
export const fileUploadLabels = {
|
|
8
|
+
// Attio's own copy on the record Files pane.
|
|
9
|
+
title: "No files",
|
|
10
|
+
hint: "Drag a file here or choose one from your computer.",
|
|
11
|
+
remove: "Remove file",
|
|
12
|
+
errors: {
|
|
13
|
+
FILE_TOO_LARGE: "That file is too large.",
|
|
14
|
+
FILE_TOO_SMALL: "That file is too small.",
|
|
15
|
+
TOO_MANY_FILES: "Too many files.",
|
|
16
|
+
INVALID_TYPE: "That file type is not accepted.",
|
|
17
|
+
},
|
|
18
|
+
};
|
|
19
|
+
/** 1000-based, matching how every file manager reports size. */
|
|
20
|
+
function formatSize(bytes) {
|
|
21
|
+
if (bytes < 1000)
|
|
22
|
+
return `${bytes} B`;
|
|
23
|
+
const units = ["kB", "MB", "GB", "TB"];
|
|
24
|
+
let n = bytes;
|
|
25
|
+
let i = -1;
|
|
26
|
+
while (n >= 1000 && i < units.length - 1) {
|
|
27
|
+
n /= 1000;
|
|
28
|
+
i++;
|
|
29
|
+
}
|
|
30
|
+
return `${n < 10 ? n.toFixed(1) : Math.round(n)} ${units[i]}`;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* A file dropzone with an accepted-file list, driven by seed-design's headless
|
|
34
|
+
* engine so the behaviour — drag state, accept/reject, per-file upload status —
|
|
35
|
+
* is theirs by construction. Controlled or uncontrolled, the same way every
|
|
36
|
+
* other crisp component is.
|
|
37
|
+
*/
|
|
38
|
+
export function FileUpload({ className, labels, "aria-label": ariaLabel, accept, disabled: disabledProp, required, invalid: invalidProp, readOnly, maxFiles, maxFileSize, minFileSize, name, validate, preventDocumentDrop, onFileReject, onFileAccept, acceptedFileEntries: acceptedFileEntriesProp, defaultAcceptedFileEntries, onAcceptedFileEntriesChange, ...rest }) {
|
|
39
|
+
const props = {
|
|
40
|
+
accept,
|
|
41
|
+
disabled: disabledProp,
|
|
42
|
+
required,
|
|
43
|
+
invalid: invalidProp,
|
|
44
|
+
readOnly,
|
|
45
|
+
maxFiles,
|
|
46
|
+
maxFileSize,
|
|
47
|
+
minFileSize,
|
|
48
|
+
name,
|
|
49
|
+
validate,
|
|
50
|
+
preventDocumentDrop,
|
|
51
|
+
onFileReject,
|
|
52
|
+
onFileAccept,
|
|
53
|
+
acceptedFileEntries: acceptedFileEntriesProp,
|
|
54
|
+
defaultAcceptedFileEntries,
|
|
55
|
+
onAcceptedFileEntriesChange,
|
|
56
|
+
};
|
|
57
|
+
const [rejections, setRejections] = React.useState([]);
|
|
58
|
+
const t = {
|
|
59
|
+
...fileUploadLabels,
|
|
60
|
+
...labels,
|
|
61
|
+
errors: { ...fileUploadLabels.errors, ...labels?.errors },
|
|
62
|
+
};
|
|
63
|
+
const api = useFileUpload({
|
|
64
|
+
...props,
|
|
65
|
+
onFileReject: (r) => {
|
|
66
|
+
setRejections(r);
|
|
67
|
+
props.onFileReject?.(r);
|
|
68
|
+
},
|
|
69
|
+
onFileAccept: (entries, helpers) => {
|
|
70
|
+
setRejections([]);
|
|
71
|
+
props.onFileAccept?.(entries, helpers);
|
|
72
|
+
},
|
|
73
|
+
});
|
|
74
|
+
const { acceptedFileEntries, dragging, disabled, invalid, openFilePicker, removeFileEntry, dropzoneProps, hiddenInputProps,
|
|
75
|
+
// The prop getters deliberately exclude the refs -- the engine's own
|
|
76
|
+
// FileUploadDropzone/HiddenInput compose them. Rendering crisp's markup
|
|
77
|
+
// instead means attaching them here; without inputRef, `openFilePicker`
|
|
78
|
+
// finds no input and returns silently, so the dropzone opens nothing.
|
|
79
|
+
inputRef, dropzoneRef, } = api;
|
|
80
|
+
return (_jsxs("div", { ...rest, className: fileuploadRecipe({ className }), children: [_jsxs("button", { ...dropzoneProps, ref: dropzoneRef, type: "button", className: "fileupload-zone", tabIndex: disabled ? -1 : 0, "aria-label": ariaLabel ?? t.hint, disabled: disabled, "data-dragging": dragging ? "" : undefined, "data-disabled": disabled ? "" : undefined, "data-invalid": invalid ? "" : undefined, onClick: () => openFilePicker(), children: [_jsx("span", { className: "fileupload-title", children: t.title }), _jsx("span", { className: "fileupload-hint", children: t.hint })] }), _jsx("input", { ...hiddenInputProps, ref: inputRef, className: "fileupload-input" }), acceptedFileEntries.length > 0 && (_jsx("ul", { className: "fileupload-list", children: acceptedFileEntries.map((entry) => (_jsxs("li", { className: "fileupload-item", "data-status": entry.status, children: [_jsx("span", { className: "fileupload-item-name", children: entry.file.name }), entry.status === "uploading" && entry.progress != null && (_jsx("span", { className: "fileupload-progress", role: "progressbar", "aria-valuenow": Math.round(entry.progress), "aria-valuemin": 0, "aria-valuemax": 100, "aria-label": entry.file.name, children: _jsx("span", { className: "fileupload-progress-fill", style: { width: `${entry.progress}%` } }) })), _jsx("span", { className: "fileupload-item-size", children: formatSize(entry.file.size) }), _jsx("button", { type: "button", className: "fileupload-remove", "aria-label": `${t.remove}: ${entry.file.name}`, onClick: () => removeFileEntry(entry.id), children: _jsx(X, { size: 13, strokeWidth: 2 }) })] }, entry.id))) })), rejections.length > 0 && (_jsx("ul", { className: "fileupload-errors", "aria-live": "polite", children: rejections.map((r) => (_jsx("li", { className: "fileupload-error", children: `${r.file.name}: ${r.errors.map((e) => t.errors[e] ?? e).join(" ")}` }, r.file.name))) }))] }));
|
|
81
|
+
}
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import { defineRecipe, resolveRecipe } from "../../core/index.js";
|
|
2
|
+
import { contract as c } from "../../tokens/index.js";
|
|
3
|
+
// seed-design's file-upload behaviour on Attio's measured scale.
|
|
4
|
+
//
|
|
5
|
+
// Measured from Attio's record Files pane (Deal record → Files, empty state):
|
|
6
|
+
// the title is 16/20 at weight 600 in `rgb(16,17,18)` (= gray900 = fgPrimary),
|
|
7
|
+
// the hint 14/20 at weight 500 in `rgba(0,0,0,0.63)`, and the action button is
|
|
8
|
+
// 28px tall with an 8px corner.
|
|
9
|
+
//
|
|
10
|
+
// Attio draws NO boundary around that pane -- the whole surface is the drop
|
|
11
|
+
// target -- but a reusable dropzone needs an edge, so the dashed affordance is
|
|
12
|
+
// crisp's own measured one from `sidebar-newlist` (1px dashed borderDefault,
|
|
13
|
+
// r8, bgRaised on hover) rather than a new invention. Attio's drag-OVER
|
|
14
|
+
// treatment could not be measured: synthetic DragEvents do not trigger its
|
|
15
|
+
// handlers. The active state therefore reuses the brand emphasis crisp already
|
|
16
|
+
// uses for a live drag affordance (`appshell-resize[data-dragging]`).
|
|
17
|
+
const config = defineRecipe({
|
|
18
|
+
name: "fileupload",
|
|
19
|
+
base: `
|
|
20
|
+
display: flex; flex-direction: column; gap: ${c.space["2"]};
|
|
21
|
+
font-family: ${c.font.sans}; min-width: 0;
|
|
22
|
+
|
|
23
|
+
& .fileupload-zone {
|
|
24
|
+
box-sizing: border-box; position: relative;
|
|
25
|
+
display: flex; flex-direction: column; align-items: center;
|
|
26
|
+
justify-content: center; gap: ${c.space["1"]};
|
|
27
|
+
width: 100%; padding: ${c.space["6"]} ${c.space["4"]};
|
|
28
|
+
border: 1px dashed ${c.borderDefault}; border-radius: ${c.radius.md};
|
|
29
|
+
background: transparent; cursor: pointer; text-align: center;
|
|
30
|
+
font-family: inherit;
|
|
31
|
+
transition: background-color .12s, border-color .12s;
|
|
32
|
+
outline: 2px solid transparent; outline-offset: -2px;
|
|
33
|
+
}
|
|
34
|
+
& .fileupload-zone:hover { background: ${c.bgRaised}; }
|
|
35
|
+
& .fileupload-zone:focus-visible { outline-color: ${c.strokeFocusRing}; }
|
|
36
|
+
& .fileupload-zone[data-dragging] {
|
|
37
|
+
border-color: ${c.bgBrandSolid};
|
|
38
|
+
background: color-mix(in srgb, ${c.bgBrandSolid} 6%, transparent);
|
|
39
|
+
}
|
|
40
|
+
& .fileupload-zone[data-disabled] { opacity: .5; cursor: not-allowed; }
|
|
41
|
+
& .fileupload-zone[data-invalid] { border-color: ${c.bgDanger}; }
|
|
42
|
+
|
|
43
|
+
/* Attio: "No files" 16/20 weight 600 */
|
|
44
|
+
& .fileupload-title {
|
|
45
|
+
color: ${c.fgPrimary}; font-size: ${c.text.xl}; line-height: 20px;
|
|
46
|
+
font-weight: ${c.weight.semibold};
|
|
47
|
+
}
|
|
48
|
+
/* Attio: the hint line, 14/20 weight 500 at 63% of the foreground */
|
|
49
|
+
& .fileupload-hint {
|
|
50
|
+
color: color-mix(in srgb, ${c.fgPrimary} 63%, transparent);
|
|
51
|
+
font-size: ${c.text.lg}; line-height: 20px;
|
|
52
|
+
font-weight: ${c.weight.medium};
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
& .fileupload-input {
|
|
56
|
+
position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
|
|
57
|
+
overflow: hidden; clip: rect(0, 0, 0, 0); white-space: nowrap; border: 0;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
& .fileupload-list { display: flex; flex-direction: column; gap: ${c.space["1"]}; margin: 0; padding: 0; list-style: none; }
|
|
61
|
+
/* the row height crisp measured for a data-grid row */
|
|
62
|
+
& .fileupload-item {
|
|
63
|
+
display: flex; align-items: center; gap: ${c.space["2"]};
|
|
64
|
+
min-height: ${c.size.tableRow}; padding: 0 ${c.space["2"]};
|
|
65
|
+
border-radius: ${c.radius.md}; background: ${c.bgRaised};
|
|
66
|
+
font-size: ${c.text.lg}; line-height: 20px; color: ${c.fgPrimary};
|
|
67
|
+
}
|
|
68
|
+
& .fileupload-item-name { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
69
|
+
& .fileupload-item-size { flex-shrink: 0; color: ${c.fgMuted}; font-size: ${c.text.sm}; font-variant-numeric: tabular-nums; }
|
|
70
|
+
& .fileupload-item[data-status="error"] { color: ${c.bgDanger}; }
|
|
71
|
+
& .fileupload-item[data-status="error"] .fileupload-item-size { color: ${c.bgDanger}; }
|
|
72
|
+
|
|
73
|
+
/* progress track, reusing the measured Progress geometry */
|
|
74
|
+
& .fileupload-progress {
|
|
75
|
+
flex-shrink: 0; width: 64px; height: 4px; border-radius: ${c.radius.full};
|
|
76
|
+
background: ${c.bgNeutralWeak}; overflow: hidden;
|
|
77
|
+
}
|
|
78
|
+
& .fileupload-progress-fill { height: 100%; background: ${c.bgBrandSolid}; transition: width .2s ease; }
|
|
79
|
+
|
|
80
|
+
& .fileupload-remove {
|
|
81
|
+
display: inline-flex; align-items: center; justify-content: center;
|
|
82
|
+
flex-shrink: 0; width: ${c.size.icon}; height: ${c.size.icon};
|
|
83
|
+
padding: 0; border: none; border-radius: ${c.radius.sm};
|
|
84
|
+
background: transparent; color: ${c.fgMuted}; cursor: pointer;
|
|
85
|
+
font-family: inherit;
|
|
86
|
+
outline: 2px solid transparent; outline-offset: -2px;
|
|
87
|
+
}
|
|
88
|
+
& .fileupload-remove:hover { color: ${c.fgPrimary}; }
|
|
89
|
+
& .fileupload-remove:focus-visible { outline-color: ${c.strokeFocusRing}; }
|
|
90
|
+
|
|
91
|
+
& .fileupload-errors { display: flex; flex-direction: column; gap: ${c.space["0.5"]}; margin: 0; padding: 0; list-style: none; }
|
|
92
|
+
& .fileupload-error { color: ${c.bgDanger}; font-size: ${c.text.md}; line-height: 18px; }
|
|
93
|
+
`,
|
|
94
|
+
variants: {},
|
|
95
|
+
defaultVariants: {},
|
|
96
|
+
compoundVariants: [],
|
|
97
|
+
});
|
|
98
|
+
export const fileuploadRecipe = Object.assign(resolveRecipe(config), {
|
|
99
|
+
config,
|
|
100
|
+
});
|
|
101
|
+
export default config;
|
package/dist/esm/index.js
CHANGED
|
@@ -23,6 +23,7 @@ export * from "./components/divider/index.js";
|
|
|
23
23
|
export * from "./components/donutchart/index.js";
|
|
24
24
|
export * from "./components/drawer/index.js";
|
|
25
25
|
export * from "./components/emptystate/index.js";
|
|
26
|
+
export * from "./components/fileupload/index.js";
|
|
26
27
|
export * from "./components/formfield/index.js";
|
|
27
28
|
export * from "./components/input/index.js";
|
|
28
29
|
export * from "./components/kbd/index.js";
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
.fileupload {
|
|
2
|
+
|
|
3
|
+
display: flex; flex-direction: column; gap: var(--space-2);
|
|
4
|
+
font-family: var(--font-sans); min-width: 0;
|
|
5
|
+
|
|
6
|
+
& .fileupload-zone {
|
|
7
|
+
box-sizing: border-box; position: relative;
|
|
8
|
+
display: flex; flex-direction: column; align-items: center;
|
|
9
|
+
justify-content: center; gap: var(--space-1);
|
|
10
|
+
width: 100%; padding: var(--space-6) var(--space-4);
|
|
11
|
+
border: 1px dashed var(--border-default); border-radius: var(--radius-md);
|
|
12
|
+
background: transparent; cursor: pointer; text-align: center;
|
|
13
|
+
font-family: inherit;
|
|
14
|
+
transition: background-color .12s, border-color .12s;
|
|
15
|
+
outline: 2px solid transparent; outline-offset: -2px;
|
|
16
|
+
}
|
|
17
|
+
& .fileupload-zone:hover { background: var(--bg-raised); }
|
|
18
|
+
& .fileupload-zone:focus-visible { outline-color: var(--stroke-focus-ring); }
|
|
19
|
+
& .fileupload-zone[data-dragging] {
|
|
20
|
+
border-color: var(--bg-brand-solid);
|
|
21
|
+
background: color-mix(in srgb, var(--bg-brand-solid) 6%, transparent);
|
|
22
|
+
}
|
|
23
|
+
& .fileupload-zone[data-disabled] { opacity: .5; cursor: not-allowed; }
|
|
24
|
+
& .fileupload-zone[data-invalid] { border-color: var(--bg-danger); }
|
|
25
|
+
|
|
26
|
+
/* Attio: "No files" 16/20 weight 600 */
|
|
27
|
+
& .fileupload-title {
|
|
28
|
+
color: var(--fg-primary); font-size: var(--text-xl); line-height: 20px;
|
|
29
|
+
font-weight: var(--weight-semibold);
|
|
30
|
+
}
|
|
31
|
+
/* Attio: the hint line, 14/20 weight 500 at 63% of the foreground */
|
|
32
|
+
& .fileupload-hint {
|
|
33
|
+
color: color-mix(in srgb, var(--fg-primary) 63%, transparent);
|
|
34
|
+
font-size: var(--text-lg); line-height: 20px;
|
|
35
|
+
font-weight: var(--weight-medium);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
& .fileupload-input {
|
|
39
|
+
position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
|
|
40
|
+
overflow: hidden; clip: rect(0, 0, 0, 0); white-space: nowrap; border: 0;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
& .fileupload-list { display: flex; flex-direction: column; gap: var(--space-1); margin: 0; padding: 0; list-style: none; }
|
|
44
|
+
/* the row height crisp measured for a data-grid row */
|
|
45
|
+
& .fileupload-item {
|
|
46
|
+
display: flex; align-items: center; gap: var(--space-2);
|
|
47
|
+
min-height: var(--size-tableRow); padding: 0 var(--space-2);
|
|
48
|
+
border-radius: var(--radius-md); background: var(--bg-raised);
|
|
49
|
+
font-size: var(--text-lg); line-height: 20px; color: var(--fg-primary);
|
|
50
|
+
}
|
|
51
|
+
& .fileupload-item-name { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
52
|
+
& .fileupload-item-size { flex-shrink: 0; color: var(--fg-muted); font-size: var(--text-sm); font-variant-numeric: tabular-nums; }
|
|
53
|
+
& .fileupload-item[data-status="error"] { color: var(--bg-danger); }
|
|
54
|
+
& .fileupload-item[data-status="error"] .fileupload-item-size { color: var(--bg-danger); }
|
|
55
|
+
|
|
56
|
+
/* progress track, reusing the measured Progress geometry */
|
|
57
|
+
& .fileupload-progress {
|
|
58
|
+
flex-shrink: 0; width: 64px; height: 4px; border-radius: var(--radius-full);
|
|
59
|
+
background: var(--bg-neutral-weak); overflow: hidden;
|
|
60
|
+
}
|
|
61
|
+
& .fileupload-progress-fill { height: 100%; background: var(--bg-brand-solid); transition: width .2s ease; }
|
|
62
|
+
|
|
63
|
+
& .fileupload-remove {
|
|
64
|
+
display: inline-flex; align-items: center; justify-content: center;
|
|
65
|
+
flex-shrink: 0; width: var(--size-icon); height: var(--size-icon);
|
|
66
|
+
padding: 0; border: none; border-radius: var(--radius-sm);
|
|
67
|
+
background: transparent; color: var(--fg-muted); cursor: pointer;
|
|
68
|
+
font-family: inherit;
|
|
69
|
+
outline: 2px solid transparent; outline-offset: -2px;
|
|
70
|
+
}
|
|
71
|
+
& .fileupload-remove:hover { color: var(--fg-primary); }
|
|
72
|
+
& .fileupload-remove:focus-visible { outline-color: var(--stroke-focus-ring); }
|
|
73
|
+
|
|
74
|
+
& .fileupload-errors { display: flex; flex-direction: column; gap: var(--space-0-5); margin: 0; padding: 0; list-style: none; }
|
|
75
|
+
& .fileupload-error { color: var(--bg-danger); font-size: var(--text-md); line-height: 18px; }
|
|
76
|
+
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
@media (prefers-reduced-motion: reduce) {
|
|
80
|
+
.fileupload, .fileupload * {
|
|
81
|
+
animation-duration: 0.01ms !important;
|
|
82
|
+
animation-iteration-count: 1 !important;
|
|
83
|
+
transition-duration: 0.01ms !important;
|
|
84
|
+
}
|
|
85
|
+
}
|
package/dist/styles.css
CHANGED
|
@@ -874,6 +874,92 @@
|
|
|
874
874
|
& .emptystate-action { margin-top: var(--space-2); }
|
|
875
875
|
}
|
|
876
876
|
|
|
877
|
+
.fileupload {
|
|
878
|
+
|
|
879
|
+
display: flex; flex-direction: column; gap: var(--space-2);
|
|
880
|
+
font-family: var(--font-sans); min-width: 0;
|
|
881
|
+
|
|
882
|
+
& .fileupload-zone {
|
|
883
|
+
box-sizing: border-box; position: relative;
|
|
884
|
+
display: flex; flex-direction: column; align-items: center;
|
|
885
|
+
justify-content: center; gap: var(--space-1);
|
|
886
|
+
width: 100%; padding: var(--space-6) var(--space-4);
|
|
887
|
+
border: 1px dashed var(--border-default); border-radius: var(--radius-md);
|
|
888
|
+
background: transparent; cursor: pointer; text-align: center;
|
|
889
|
+
font-family: inherit;
|
|
890
|
+
transition: background-color .12s, border-color .12s;
|
|
891
|
+
outline: 2px solid transparent; outline-offset: -2px;
|
|
892
|
+
}
|
|
893
|
+
& .fileupload-zone:hover { background: var(--bg-raised); }
|
|
894
|
+
& .fileupload-zone:focus-visible { outline-color: var(--stroke-focus-ring); }
|
|
895
|
+
& .fileupload-zone[data-dragging] {
|
|
896
|
+
border-color: var(--bg-brand-solid);
|
|
897
|
+
background: color-mix(in srgb, var(--bg-brand-solid) 6%, transparent);
|
|
898
|
+
}
|
|
899
|
+
& .fileupload-zone[data-disabled] { opacity: .5; cursor: not-allowed; }
|
|
900
|
+
& .fileupload-zone[data-invalid] { border-color: var(--bg-danger); }
|
|
901
|
+
|
|
902
|
+
/* Attio: "No files" 16/20 weight 600 */
|
|
903
|
+
& .fileupload-title {
|
|
904
|
+
color: var(--fg-primary); font-size: var(--text-xl); line-height: 20px;
|
|
905
|
+
font-weight: var(--weight-semibold);
|
|
906
|
+
}
|
|
907
|
+
/* Attio: the hint line, 14/20 weight 500 at 63% of the foreground */
|
|
908
|
+
& .fileupload-hint {
|
|
909
|
+
color: color-mix(in srgb, var(--fg-primary) 63%, transparent);
|
|
910
|
+
font-size: var(--text-lg); line-height: 20px;
|
|
911
|
+
font-weight: var(--weight-medium);
|
|
912
|
+
}
|
|
913
|
+
|
|
914
|
+
& .fileupload-input {
|
|
915
|
+
position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
|
|
916
|
+
overflow: hidden; clip: rect(0, 0, 0, 0); white-space: nowrap; border: 0;
|
|
917
|
+
}
|
|
918
|
+
|
|
919
|
+
& .fileupload-list { display: flex; flex-direction: column; gap: var(--space-1); margin: 0; padding: 0; list-style: none; }
|
|
920
|
+
/* the row height crisp measured for a data-grid row */
|
|
921
|
+
& .fileupload-item {
|
|
922
|
+
display: flex; align-items: center; gap: var(--space-2);
|
|
923
|
+
min-height: var(--size-tableRow); padding: 0 var(--space-2);
|
|
924
|
+
border-radius: var(--radius-md); background: var(--bg-raised);
|
|
925
|
+
font-size: var(--text-lg); line-height: 20px; color: var(--fg-primary);
|
|
926
|
+
}
|
|
927
|
+
& .fileupload-item-name { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
928
|
+
& .fileupload-item-size { flex-shrink: 0; color: var(--fg-muted); font-size: var(--text-sm); font-variant-numeric: tabular-nums; }
|
|
929
|
+
& .fileupload-item[data-status="error"] { color: var(--bg-danger); }
|
|
930
|
+
& .fileupload-item[data-status="error"] .fileupload-item-size { color: var(--bg-danger); }
|
|
931
|
+
|
|
932
|
+
/* progress track, reusing the measured Progress geometry */
|
|
933
|
+
& .fileupload-progress {
|
|
934
|
+
flex-shrink: 0; width: 64px; height: 4px; border-radius: var(--radius-full);
|
|
935
|
+
background: var(--bg-neutral-weak); overflow: hidden;
|
|
936
|
+
}
|
|
937
|
+
& .fileupload-progress-fill { height: 100%; background: var(--bg-brand-solid); transition: width .2s ease; }
|
|
938
|
+
|
|
939
|
+
& .fileupload-remove {
|
|
940
|
+
display: inline-flex; align-items: center; justify-content: center;
|
|
941
|
+
flex-shrink: 0; width: var(--size-icon); height: var(--size-icon);
|
|
942
|
+
padding: 0; border: none; border-radius: var(--radius-sm);
|
|
943
|
+
background: transparent; color: var(--fg-muted); cursor: pointer;
|
|
944
|
+
font-family: inherit;
|
|
945
|
+
outline: 2px solid transparent; outline-offset: -2px;
|
|
946
|
+
}
|
|
947
|
+
& .fileupload-remove:hover { color: var(--fg-primary); }
|
|
948
|
+
& .fileupload-remove:focus-visible { outline-color: var(--stroke-focus-ring); }
|
|
949
|
+
|
|
950
|
+
& .fileupload-errors { display: flex; flex-direction: column; gap: var(--space-0-5); margin: 0; padding: 0; list-style: none; }
|
|
951
|
+
& .fileupload-error { color: var(--bg-danger); font-size: var(--text-md); line-height: 18px; }
|
|
952
|
+
|
|
953
|
+
}
|
|
954
|
+
|
|
955
|
+
@media (prefers-reduced-motion: reduce) {
|
|
956
|
+
.fileupload, .fileupload * {
|
|
957
|
+
animation-duration: 0.01ms !important;
|
|
958
|
+
animation-iteration-count: 1 !important;
|
|
959
|
+
transition-duration: 0.01ms !important;
|
|
960
|
+
}
|
|
961
|
+
}
|
|
962
|
+
|
|
877
963
|
.formfield {
|
|
878
964
|
display: flex; flex-direction: column; gap: var(--space-1-5); box-sizing: border-box; font-family: var(--font-sans);
|
|
879
965
|
& .formfield-label { display: inline-flex; align-items: center; gap: var(--space-1); font-size: var(--text-sm); font-weight: var(--weight-medium); color: var(--fg-muted); }
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { type FileEntry, type FileError, type FileRejection, type UseFileUploadProps } from "@seed-design/react-file-upload";
|
|
2
|
+
import * as React from "react";
|
|
3
|
+
export type { FileEntry, FileError, FileRejection };
|
|
4
|
+
/** Every user-facing string, so a consumer can translate without restating the rest. */
|
|
5
|
+
export type FileUploadLabels = {
|
|
6
|
+
title: string;
|
|
7
|
+
hint: string;
|
|
8
|
+
remove: string;
|
|
9
|
+
errors: Record<string, string>;
|
|
10
|
+
};
|
|
11
|
+
export declare const fileUploadLabels: FileUploadLabels;
|
|
12
|
+
export type FileUploadProps = UseFileUploadProps & Omit<React.ComponentPropsWithoutRef<"div">, keyof UseFileUploadProps | "children"> & {
|
|
13
|
+
/** PARTIAL override of the chrome strings; anything omitted keeps its default. */
|
|
14
|
+
labels?: Partial<Omit<FileUploadLabels, "errors">> & {
|
|
15
|
+
errors?: Record<string, string>;
|
|
16
|
+
};
|
|
17
|
+
};
|
|
18
|
+
/**
|
|
19
|
+
* A file dropzone with an accepted-file list, driven by seed-design's headless
|
|
20
|
+
* engine so the behaviour — drag state, accept/reject, per-file upload status —
|
|
21
|
+
* is theirs by construction. Controlled or uncontrolled, the same way every
|
|
22
|
+
* other crisp component is.
|
|
23
|
+
*/
|
|
24
|
+
export declare function FileUpload({ className, labels, "aria-label": ariaLabel, accept, disabled: disabledProp, required, invalid: invalidProp, readOnly, maxFiles, maxFileSize, minFileSize, name, validate, preventDocumentDrop, onFileReject, onFileAccept, acceptedFileEntries: acceptedFileEntriesProp, defaultAcceptedFileEntries, onAcceptedFileEntriesChange, ...rest }: FileUploadProps): React.JSX.Element;
|
package/dist/types/index.d.ts
CHANGED
|
@@ -19,6 +19,7 @@ export * from "./components/divider";
|
|
|
19
19
|
export * from "./components/donutchart";
|
|
20
20
|
export * from "./components/drawer";
|
|
21
21
|
export * from "./components/emptystate";
|
|
22
|
+
export * from "./components/fileupload";
|
|
22
23
|
export * from "./components/formfield";
|
|
23
24
|
export * from "./components/input";
|
|
24
25
|
export * from "./components/kbd";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@crisp-ui-kit/crisp",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.8.0",
|
|
4
4
|
"description": "A lean React design system, measured pixel-for-pixel from Attio.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -137,6 +137,11 @@
|
|
|
137
137
|
"import": "./dist/esm/components/emptystate/index.js",
|
|
138
138
|
"require": "./dist/cjs/components/emptystate/index.js"
|
|
139
139
|
},
|
|
140
|
+
"./fileupload": {
|
|
141
|
+
"types": "./dist/types/components/fileupload/index.d.ts",
|
|
142
|
+
"import": "./dist/esm/components/fileupload/index.js",
|
|
143
|
+
"require": "./dist/cjs/components/fileupload/index.js"
|
|
144
|
+
},
|
|
140
145
|
"./formfield": {
|
|
141
146
|
"types": "./dist/types/components/formfield/index.d.ts",
|
|
142
147
|
"import": "./dist/esm/components/formfield/index.js",
|
|
@@ -407,6 +412,7 @@
|
|
|
407
412
|
"@radix-ui/react-slot": "^1.3.3",
|
|
408
413
|
"@seed-design/dom-utils": "^2.1.0",
|
|
409
414
|
"@seed-design/react-date-picker": "1.2.0",
|
|
415
|
+
"@seed-design/react-file-upload": "1.0.1",
|
|
410
416
|
"@zag-js/combobox": "^1.43.3",
|
|
411
417
|
"@zag-js/dialog": "^1.43.3",
|
|
412
418
|
"@zag-js/react": "^1.43.3",
|