@crisp-ui-kit/crisp 0.7.6 → 0.9.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.
@@ -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);
@@ -0,0 +1,17 @@
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("./timepicker"), exports);
@@ -0,0 +1,54 @@
1
+ "use strict";
2
+ "use client";
3
+ Object.defineProperty(exports, "__esModule", { value: true });
4
+ exports.TimePicker = TimePicker;
5
+ const jsx_runtime_1 = require("react/jsx-runtime");
6
+ const react_time_picker_1 = require("@seed-design/react-time-picker");
7
+ const wheelpicker_1 = require("../wheelpicker");
8
+ const defaultLabels = {
9
+ period: "AM/PM",
10
+ hour: "Hour",
11
+ minute: "Minute",
12
+ };
13
+ /**
14
+ * An hour/minute wheel, driven by seed-design's headless engine so the column
15
+ * order, the 12- vs 24-hour split and the minute stepping are locale-correct by
16
+ * construction, and rendered with crisp's own `WheelPicker` so the pixels are
17
+ * the ones already measured for it.
18
+ */
19
+ function TimePicker({ value, defaultValue, onValueChange, minuteStep, locale, disabled, labels, size = "medium", className, ...rest }) {
20
+ // The engine defaults `locale` to "ko-KR" — it is a Korean design system.
21
+ // crisp's other components are English-default, so the default is overridden
22
+ // here rather than inherited; a consumer's own `locale` still wins.
23
+ const api = (0, react_time_picker_1.useTimePicker)({
24
+ value,
25
+ defaultValue,
26
+ onValueChange,
27
+ minuteStep,
28
+ locale: locale ?? "en-US",
29
+ disabled,
30
+ });
31
+ const t = { ...defaultLabels, ...labels };
32
+ // `columnOrder` is locale-derived: en-US puts the period last ("3:30 PM"),
33
+ // ko-KR first, and a 24-hour locale omits it entirely.
34
+ const columns = api.columnOrder.map((type) => ({
35
+ key: type,
36
+ label: t[type],
37
+ options: api.columns[type].options.map((o) => ({
38
+ value: o.value,
39
+ label: o.label,
40
+ })),
41
+ }));
42
+ const selection = Object.fromEntries(api.columnOrder.map((type) => [type, api.columns[type].value]));
43
+ return ((0, jsx_runtime_1.jsx)("div", { ...rest, className: className, children: (0, jsx_runtime_1.jsx)(wheelpicker_1.WheelPicker, { columns: columns, value: selection, size: size, onValueChange: (next) => {
44
+ // Route each wheel back to its OWN column, so the engine keeps
45
+ // ownership of rounding, looping and the 12-to-24-hour mapping.
46
+ // Sending them all to one column silently makes the period wheel
47
+ // set the hour.
48
+ for (const type of api.columnOrder) {
49
+ const v = next[type];
50
+ if (v != null)
51
+ api.columns[type].onValueChange(v);
52
+ }
53
+ } }) }));
54
+ }
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);
@@ -60,6 +61,7 @@ __exportStar(require("./components/table"), exports);
60
61
  __exportStar(require("./components/tabletoolbar"), exports);
61
62
  __exportStar(require("./components/tabs"), exports);
62
63
  __exportStar(require("./components/textarea"), exports);
64
+ __exportStar(require("./components/timepicker"), exports);
63
65
  __exportStar(require("./components/toast"), exports);
64
66
  __exportStar(require("./components/tooltip"), exports);
65
67
  __exportStar(require("./components/wheelpicker"), 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;
@@ -0,0 +1,2 @@
1
+ export * from "./fileupload.js";
2
+ export * from "./fileupload.recipe.js";
@@ -0,0 +1 @@
1
+ export * from "./timepicker.js";
@@ -0,0 +1,51 @@
1
+ "use client";
2
+ import { jsx as _jsx } from "react/jsx-runtime";
3
+ import { useTimePicker, } from "@seed-design/react-time-picker";
4
+ import { WheelPicker } from "../wheelpicker/index.js";
5
+ const defaultLabels = {
6
+ period: "AM/PM",
7
+ hour: "Hour",
8
+ minute: "Minute",
9
+ };
10
+ /**
11
+ * An hour/minute wheel, driven by seed-design's headless engine so the column
12
+ * order, the 12- vs 24-hour split and the minute stepping are locale-correct by
13
+ * construction, and rendered with crisp's own `WheelPicker` so the pixels are
14
+ * the ones already measured for it.
15
+ */
16
+ export function TimePicker({ value, defaultValue, onValueChange, minuteStep, locale, disabled, labels, size = "medium", className, ...rest }) {
17
+ // The engine defaults `locale` to "ko-KR" — it is a Korean design system.
18
+ // crisp's other components are English-default, so the default is overridden
19
+ // here rather than inherited; a consumer's own `locale` still wins.
20
+ const api = useTimePicker({
21
+ value,
22
+ defaultValue,
23
+ onValueChange,
24
+ minuteStep,
25
+ locale: locale ?? "en-US",
26
+ disabled,
27
+ });
28
+ const t = { ...defaultLabels, ...labels };
29
+ // `columnOrder` is locale-derived: en-US puts the period last ("3:30 PM"),
30
+ // ko-KR first, and a 24-hour locale omits it entirely.
31
+ const columns = api.columnOrder.map((type) => ({
32
+ key: type,
33
+ label: t[type],
34
+ options: api.columns[type].options.map((o) => ({
35
+ value: o.value,
36
+ label: o.label,
37
+ })),
38
+ }));
39
+ const selection = Object.fromEntries(api.columnOrder.map((type) => [type, api.columns[type].value]));
40
+ return (_jsx("div", { ...rest, className: className, children: _jsx(WheelPicker, { columns: columns, value: selection, size: size, onValueChange: (next) => {
41
+ // Route each wheel back to its OWN column, so the engine keeps
42
+ // ownership of rounding, looping and the 12-to-24-hour mapping.
43
+ // Sending them all to one column silently makes the period wheel
44
+ // set the hour.
45
+ for (const type of api.columnOrder) {
46
+ const v = next[type];
47
+ if (v != null)
48
+ api.columns[type].onValueChange(v);
49
+ }
50
+ } }) }));
51
+ }
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";
@@ -44,6 +45,7 @@ export * from "./components/table/index.js";
44
45
  export * from "./components/tabletoolbar/index.js";
45
46
  export * from "./components/tabs/index.js";
46
47
  export * from "./components/textarea/index.js";
48
+ export * from "./components/timepicker/index.js";
47
49
  export * from "./components/toast/index.js";
48
50
  export * from "./components/tooltip/index.js";
49
51
  export * from "./components/wheelpicker/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
+ }
@@ -0,0 +1,7 @@
1
+ import type { RecipeFn } from "../core/types";
2
+
3
+ export interface FileuploadVariants {
4
+
5
+ }
6
+
7
+ export declare const fileuploadRecipe: RecipeFn<{ }>;
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;
@@ -0,0 +1,5 @@
1
+ declare const config: import("../..").RecipeConfig<{}>;
2
+ export declare const fileuploadRecipe: import("../..").RecipeFn<{}> & {
3
+ config: import("../..").RecipeConfig<{}>;
4
+ };
5
+ export default config;
@@ -0,0 +1,2 @@
1
+ export * from "./fileupload";
2
+ export * from "./fileupload.recipe";
@@ -0,0 +1 @@
1
+ export * from "./timepicker";
@@ -0,0 +1,15 @@
1
+ import { type MinuteStep, type TimePickerValue, type UseTimePickerProps } from "@seed-design/react-time-picker";
2
+ import type * as React from "react";
3
+ export type { MinuteStep, TimePickerValue };
4
+ export type TimePickerProps = UseTimePickerProps & Omit<React.ComponentPropsWithoutRef<"div">, keyof UseTimePickerProps | "children"> & {
5
+ /** Accessible names for the wheels, so they can be translated. */
6
+ labels?: Partial<Record<"period" | "hour" | "minute", string>>;
7
+ size?: "small" | "medium";
8
+ };
9
+ /**
10
+ * An hour/minute wheel, driven by seed-design's headless engine so the column
11
+ * order, the 12- vs 24-hour split and the minute stepping are locale-correct by
12
+ * construction, and rendered with crisp's own `WheelPicker` so the pixels are
13
+ * the ones already measured for it.
14
+ */
15
+ export declare function TimePicker({ value, defaultValue, onValueChange, minuteStep, locale, disabled, labels, size, className, ...rest }: TimePickerProps): React.JSX.Element;
@@ -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";
@@ -40,6 +41,7 @@ export * from "./components/table";
40
41
  export * from "./components/tabletoolbar";
41
42
  export * from "./components/tabs";
42
43
  export * from "./components/textarea";
44
+ export * from "./components/timepicker";
43
45
  export * from "./components/toast";
44
46
  export * from "./components/tooltip";
45
47
  export * from "./components/wheelpicker";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@crisp-ui-kit/crisp",
3
- "version": "0.7.6",
3
+ "version": "0.9.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",
@@ -242,6 +247,11 @@
242
247
  "import": "./dist/esm/components/textarea/index.js",
243
248
  "require": "./dist/cjs/components/textarea/index.js"
244
249
  },
250
+ "./timepicker": {
251
+ "types": "./dist/types/components/timepicker/index.d.ts",
252
+ "import": "./dist/esm/components/timepicker/index.js",
253
+ "require": "./dist/cjs/components/timepicker/index.js"
254
+ },
245
255
  "./toast": {
246
256
  "types": "./dist/types/components/toast/index.d.ts",
247
257
  "import": "./dist/esm/components/toast/index.js",
@@ -407,6 +417,8 @@
407
417
  "@radix-ui/react-slot": "^1.3.3",
408
418
  "@seed-design/dom-utils": "^2.1.0",
409
419
  "@seed-design/react-date-picker": "1.2.0",
420
+ "@seed-design/react-file-upload": "1.0.1",
421
+ "@seed-design/react-time-picker": "1.0.0",
410
422
  "@zag-js/combobox": "^1.43.3",
411
423
  "@zag-js/dialog": "^1.43.3",
412
424
  "@zag-js/react": "^1.43.3",