@usetheo/ui 0.19.0 → 0.21.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/components/composites/stepper/index.d.ts +1 -0
- package/dist/components/composites/stepper/stepper.d.ts +47 -0
- package/dist/components/primitives/file-dropzone/file-dropzone.d.ts +26 -0
- package/dist/components/primitives/file-dropzone/index.d.ts +1 -0
- package/dist/components/primitives/file-dropzone/validate.d.ts +40 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +308 -2
- package/package.json +1 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { Stepper, deriveSteps, type StepStatus, type StepperProps, type StepperStepData, } from "./stepper.js";
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import type { HTMLAttributes, ReactNode } from "react";
|
|
2
|
+
/**
|
|
3
|
+
* Stepper — multi-step pipeline progress, promoted from the theo-cloud
|
|
4
|
+
* dashboard build-timeline (production). Read-only by design: steps are NOT
|
|
5
|
+
* clickable (pipeline visualization, not a wizard). 100% controlled — live
|
|
6
|
+
* updates (SSE/polling) belong to the consumer.
|
|
7
|
+
*
|
|
8
|
+
* Contract: step `id`s MUST be unique (React keys); at most ONE step should
|
|
9
|
+
* be `active` (`aria-current="step"` is emitted per active step as-is).
|
|
10
|
+
* A `label` is required — it names the pipeline for screen readers.
|
|
11
|
+
*
|
|
12
|
+
* <Stepper label="Build pipeline" steps={[
|
|
13
|
+
* { id: "queued", label: "Queued", status: "done" },
|
|
14
|
+
* { id: "build", label: "Building", status: "active" },
|
|
15
|
+
* ]} />
|
|
16
|
+
*
|
|
17
|
+
* For the simple "wizard position" case, derive the array from an index:
|
|
18
|
+
* <Stepper label="Onboarding" steps={deriveSteps(defs, activeIndex)} />
|
|
19
|
+
*/
|
|
20
|
+
export type StepStatus = "pending" | "active" | "done" | "failed";
|
|
21
|
+
export interface StepperStepData {
|
|
22
|
+
/** Unique key for the step (consumer contract: no duplicates). */
|
|
23
|
+
id: string;
|
|
24
|
+
label: string;
|
|
25
|
+
/** For failed steps, pass the failure cause here — it is read by screen readers inside the same item (F-dom-3). */
|
|
26
|
+
description?: string;
|
|
27
|
+
status: StepStatus;
|
|
28
|
+
/** Optional slot — prefer `<Timestamp value={...} />` from this library. */
|
|
29
|
+
timestamp?: ReactNode;
|
|
30
|
+
/** Optional slot rendered ONLY when status === "failed" (the action belongs to the consumer). */
|
|
31
|
+
retry?: ReactNode;
|
|
32
|
+
}
|
|
33
|
+
export interface StepperProps extends HTMLAttributes<HTMLOListElement> {
|
|
34
|
+
/** Accessible name for the pipeline (aria-label of the list). */
|
|
35
|
+
label: string;
|
|
36
|
+
steps: StepperStepData[];
|
|
37
|
+
/** @default "vertical" — both production cases (build/ingest) are vertical. */
|
|
38
|
+
orientation?: "vertical" | "horizontal";
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Pure helper for the simple index-driven case: steps before `activeIndex`
|
|
42
|
+
* are done, the step at it is active, later ones are pending. Out-of-range
|
|
43
|
+
* indexes are safe: past the end → all done; negative → all pending.
|
|
44
|
+
*/
|
|
45
|
+
export declare function deriveSteps(defs: Array<Pick<StepperStepData, "id" | "label" | "description">>, activeIndex: number): StepperStepData[];
|
|
46
|
+
declare const Stepper: import("react").ForwardRefExoticComponent<StepperProps & import("react").RefAttributes<HTMLOListElement>>;
|
|
47
|
+
export { Stepper };
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* FileDropzone — drag-drop + file-picker primitive, dependency-free (HTML5
|
|
3
|
+
* DnD). The drag mechanics, the typed-rejection shape and the browser quirks
|
|
4
|
+
* are ported from the react-dropzone reference study (blueprint M5): nested
|
|
5
|
+
* dragenter/dragleave target counting, empty MIME type during drag (Chrome),
|
|
6
|
+
* and the all-or-nothing maxFiles rule.
|
|
7
|
+
*
|
|
8
|
+
* Boundary: this component SELECTS and VALIDATES files. Uploading (network,
|
|
9
|
+
* progress) belongs to the consumer — compose with `Progress` for that.
|
|
10
|
+
* Directory drops are out of scope (entries without a real File are skipped).
|
|
11
|
+
*/
|
|
12
|
+
import type { HTMLAttributes, ReactNode } from "react";
|
|
13
|
+
import type { FileRejection, ValidateFilesOptions } from "./validate.js";
|
|
14
|
+
export { matchesAccept, validateFiles, type FileDropzoneError, type FileDropzoneErrorCode, type FileRejection, type ValidateFilesOptions, } from "./validate.js";
|
|
15
|
+
export type FileDropzoneState = "idle" | "drag-over" | "drag-reject" | "disabled";
|
|
16
|
+
export interface FileDropzoneProps extends Omit<HTMLAttributes<HTMLDivElement>, "onDrop" | "children">, ValidateFilesOptions {
|
|
17
|
+
/** Accessible name for the dropzone (aria-label). */
|
|
18
|
+
label: string;
|
|
19
|
+
disabled?: boolean;
|
|
20
|
+
onFilesAccepted?: (files: File[]) => void;
|
|
21
|
+
onFilesRejected?: (rejections: FileRejection[]) => void;
|
|
22
|
+
/** Custom instructions content; defaults to a drag-or-click hint. */
|
|
23
|
+
instructions?: ReactNode;
|
|
24
|
+
}
|
|
25
|
+
declare const FileDropzone: import("react").ForwardRefExoticComponent<FileDropzoneProps & import("react").RefAttributes<HTMLDivElement>>;
|
|
26
|
+
export { FileDropzone };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { FileDropzone, matchesAccept, validateFiles, type FileDropzoneError, type FileDropzoneErrorCode, type FileDropzoneProps, type FileDropzoneState, type FileRejection, type ValidateFilesOptions, } from "./file-dropzone.js";
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pure validation core of FileDropzone — typed rejections, accept matching
|
|
3
|
+
* (MIME exact / wildcard / extension), size and collective count rules.
|
|
4
|
+
* Ported behaviors from the react-dropzone reference study (blueprint M5).
|
|
5
|
+
*/
|
|
6
|
+
export type FileDropzoneErrorCode = "file-invalid-type" | "file-too-large" | "file-too-small" | "too-many-files";
|
|
7
|
+
export interface FileDropzoneError {
|
|
8
|
+
/** One of the four built-in codes, or a custom code from `validator`. */
|
|
9
|
+
code: FileDropzoneErrorCode | string;
|
|
10
|
+
message: string;
|
|
11
|
+
}
|
|
12
|
+
export interface FileRejection {
|
|
13
|
+
file: File;
|
|
14
|
+
errors: FileDropzoneError[];
|
|
15
|
+
}
|
|
16
|
+
export interface ValidateFilesOptions {
|
|
17
|
+
accept?: Record<string, string[]>;
|
|
18
|
+
maxSize?: number;
|
|
19
|
+
minSize?: number;
|
|
20
|
+
maxFiles?: number;
|
|
21
|
+
multiple?: boolean;
|
|
22
|
+
validator?: (file: File) => FileDropzoneError | FileDropzoneError[] | null;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Matches a file against an `accept` map ({mime: [ext...]}): exact MIME,
|
|
26
|
+
* `type/*` wildcard, or extension (case-insensitive). No accept → accepts
|
|
27
|
+
* everything. An empty `file.type` is accepted (Chrome reports "" for some
|
|
28
|
+
* types during drag) — real validation happens again on drop.
|
|
29
|
+
*/
|
|
30
|
+
export declare function matchesAccept(file: File, accept?: Record<string, string[]>): boolean;
|
|
31
|
+
/**
|
|
32
|
+
* Validates files against accept/size/count rules plus an optional custom
|
|
33
|
+
* validator. Per-file errors accumulate; the maxFiles/multiple rule is
|
|
34
|
+
* COLLECTIVE and all-or-nothing: exceeding it rejects EVERY file with
|
|
35
|
+
* `too-many-files` (ported from the reference behavior).
|
|
36
|
+
*/
|
|
37
|
+
export declare function validateFiles(files: File[], options?: ValidateFilesOptions): {
|
|
38
|
+
accepted: File[];
|
|
39
|
+
rejections: FileRejection[];
|
|
40
|
+
};
|
package/dist/index.d.ts
CHANGED
|
@@ -48,6 +48,8 @@ export { PinInput, type PinInputProps } from "./components/primitives/pin-input/
|
|
|
48
48
|
export { DataTable, type DataTableColumn, type DataTableProps, type DataTableSort, } from "./components/composites/data-table/index.js";
|
|
49
49
|
export { PageShell, type PageShellProps } from "./components/composites/page-shell/index.js";
|
|
50
50
|
export { linScale, niceMax, seriesPath, TrendChart, type TrendChartProps, type TrendPoint, type TrendSeries, } from "./components/composites/trend-chart/index.js";
|
|
51
|
+
export { Stepper, deriveSteps, type StepStatus, type StepperProps, type StepperStepData, } from "./components/composites/stepper/index.js";
|
|
52
|
+
export { FileDropzone, matchesAccept, validateFiles, type FileDropzoneError, type FileDropzoneErrorCode, type FileDropzoneProps, type FileDropzoneState, type FileRejection, type ValidateFilesOptions, } from "./components/primitives/file-dropzone/index.js";
|
|
51
53
|
export { MetricsPanel, type Metric } from "./components/primitives/metrics-panel/index.js";
|
|
52
54
|
export { SocialAuthRow, type SocialProvider, } from "./components/primitives/social-auth-row/index.js";
|
|
53
55
|
export { LoginSplit } from "./components/primitives/login-split/index.js";
|
package/dist/index.js
CHANGED
|
@@ -4,7 +4,7 @@ import { Slot } from '@radix-ui/react-slot';
|
|
|
4
4
|
import { cva } from 'class-variance-authority';
|
|
5
5
|
import { forwardRef, useState, useRef, useEffect, useCallback, createContext, useId, useMemo, Children, isValidElement, cloneElement, Fragment as Fragment$1, useContext, createElement } from 'react';
|
|
6
6
|
import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
|
|
7
|
-
import { Check, X, Copy, Loader2, ArrowUpCircle, AlertCircle, ChevronDown, ChevronUp, Minus, Circle, ChevronRight, ChevronsUpDown, TrendingDown, TrendingUp, ArrowUpRight, TriangleAlert, CheckCircle2, Info, ChevronsLeft, ChevronLeft, ChevronsRight, Search, Filter, GitCommit, GitBranch, Activity, Plus, Lock, EyeOff, Eye, Trash2, GitPullRequest, Server, ExternalLink, Globe, ShieldCheck, ShieldX, Clock, RotateCcw, ArrowDownLeft, MoreHorizontal } from 'lucide-react';
|
|
7
|
+
import { Check, X, Copy, Loader2, ArrowUpCircle, AlertCircle, ChevronDown, ChevronUp, Minus, Circle, ChevronRight, ChevronsUpDown, TrendingDown, TrendingUp, ArrowUpRight, TriangleAlert, CheckCircle2, Info, ChevronsLeft, ChevronLeft, ChevronsRight, Search, Filter, CircleDashed, GitCommit, GitBranch, Activity, Plus, Lock, EyeOff, Eye, Trash2, GitPullRequest, Server, ExternalLink, Globe, ShieldCheck, ShieldX, Clock, RotateCcw, ArrowDownLeft, MoreHorizontal } from 'lucide-react';
|
|
8
8
|
import { Command, useCommandState } from 'cmdk';
|
|
9
9
|
import * as SliderPrimitive from '@radix-ui/react-slider';
|
|
10
10
|
import * as DialogPrimitive from '@radix-ui/react-dialog';
|
|
@@ -4539,6 +4539,312 @@ var TrendChart = forwardRef(
|
|
|
4539
4539
|
}
|
|
4540
4540
|
);
|
|
4541
4541
|
TrendChart.displayName = "TrendChart";
|
|
4542
|
+
function deriveSteps(defs, activeIndex) {
|
|
4543
|
+
return defs.map((def, i) => ({
|
|
4544
|
+
...def,
|
|
4545
|
+
status: i < activeIndex ? "done" : i === activeIndex ? "active" : "pending"
|
|
4546
|
+
}));
|
|
4547
|
+
}
|
|
4548
|
+
var ICON = {
|
|
4549
|
+
done: Check,
|
|
4550
|
+
active: Loader2,
|
|
4551
|
+
failed: X,
|
|
4552
|
+
pending: CircleDashed
|
|
4553
|
+
};
|
|
4554
|
+
var ICON_TONE = {
|
|
4555
|
+
done: "text-success",
|
|
4556
|
+
active: "animate-spin text-primary",
|
|
4557
|
+
failed: "text-destructive",
|
|
4558
|
+
pending: "text-muted-foreground"
|
|
4559
|
+
};
|
|
4560
|
+
var LABEL_TONE = {
|
|
4561
|
+
done: "font-medium text-foreground",
|
|
4562
|
+
active: "font-semibold text-primary",
|
|
4563
|
+
failed: "font-semibold text-destructive",
|
|
4564
|
+
pending: "font-medium text-muted-foreground"
|
|
4565
|
+
};
|
|
4566
|
+
var SR_STATE_TEXT = {
|
|
4567
|
+
done: " \u2014 completed",
|
|
4568
|
+
active: null,
|
|
4569
|
+
failed: " \u2014 failed",
|
|
4570
|
+
pending: " \u2014 not started"
|
|
4571
|
+
};
|
|
4572
|
+
var Stepper = forwardRef(
|
|
4573
|
+
({ label, steps, orientation = "vertical", className, ...props }, ref) => /* @__PURE__ */ jsx(
|
|
4574
|
+
"ol",
|
|
4575
|
+
{
|
|
4576
|
+
"data-slot": "stepper",
|
|
4577
|
+
"data-orientation": orientation,
|
|
4578
|
+
ref,
|
|
4579
|
+
"aria-label": label,
|
|
4580
|
+
className: cn(
|
|
4581
|
+
orientation === "horizontal" ? "flex flex-wrap gap-x-6 gap-y-3" : "space-y-3",
|
|
4582
|
+
className
|
|
4583
|
+
),
|
|
4584
|
+
...props,
|
|
4585
|
+
children: steps.map((s) => {
|
|
4586
|
+
const status = Object.hasOwn(ICON, s.status) ? s.status : "pending";
|
|
4587
|
+
const Icon2 = ICON[status];
|
|
4588
|
+
return /* @__PURE__ */ jsxs(
|
|
4589
|
+
"li",
|
|
4590
|
+
{
|
|
4591
|
+
"data-slot": "stepper-step",
|
|
4592
|
+
"data-state": status,
|
|
4593
|
+
"aria-current": status === "active" ? "step" : void 0,
|
|
4594
|
+
className: "flex items-start gap-3",
|
|
4595
|
+
children: [
|
|
4596
|
+
/* @__PURE__ */ jsx(
|
|
4597
|
+
"span",
|
|
4598
|
+
{
|
|
4599
|
+
"aria-hidden": "true",
|
|
4600
|
+
className: "mt-0.5 flex size-5 shrink-0 items-center justify-center rounded-full border border-border",
|
|
4601
|
+
"data-slot": "stepper-icon",
|
|
4602
|
+
children: /* @__PURE__ */ jsx(Icon2, { className: cn("size-3", ICON_TONE[status]) })
|
|
4603
|
+
}
|
|
4604
|
+
),
|
|
4605
|
+
/* @__PURE__ */ jsxs("div", { className: "min-w-0 flex-1", children: [
|
|
4606
|
+
/* @__PURE__ */ jsxs(
|
|
4607
|
+
"p",
|
|
4608
|
+
{
|
|
4609
|
+
"data-slot": "stepper-label",
|
|
4610
|
+
title: s.label,
|
|
4611
|
+
className: cn("truncate text-body-sm", LABEL_TONE[status]),
|
|
4612
|
+
children: [
|
|
4613
|
+
s.label,
|
|
4614
|
+
SR_STATE_TEXT[status] && /* @__PURE__ */ jsx("span", { className: "sr-only", children: SR_STATE_TEXT[status] })
|
|
4615
|
+
]
|
|
4616
|
+
}
|
|
4617
|
+
),
|
|
4618
|
+
s.description && /* @__PURE__ */ jsx("p", { className: "text-label text-muted-foreground", children: s.description }),
|
|
4619
|
+
s.timestamp && /* @__PURE__ */ jsx("p", { "data-slot": "stepper-timestamp", className: "text-label text-muted-foreground", children: s.timestamp }),
|
|
4620
|
+
status === "failed" && s.retry && /* @__PURE__ */ jsx("div", { "data-slot": "stepper-retry", className: "mt-1", children: s.retry })
|
|
4621
|
+
] })
|
|
4622
|
+
]
|
|
4623
|
+
},
|
|
4624
|
+
s.id
|
|
4625
|
+
);
|
|
4626
|
+
})
|
|
4627
|
+
}
|
|
4628
|
+
)
|
|
4629
|
+
);
|
|
4630
|
+
Stepper.displayName = "Stepper";
|
|
4631
|
+
|
|
4632
|
+
// src/components/primitives/file-dropzone/validate.ts
|
|
4633
|
+
function matchesAccept(file, accept) {
|
|
4634
|
+
const entries = Object.entries(accept ?? {});
|
|
4635
|
+
if (entries.length === 0) return true;
|
|
4636
|
+
if (file.type === "") {
|
|
4637
|
+
const exts = entries.flatMap(([, e]) => e);
|
|
4638
|
+
if (exts.length === 0) return true;
|
|
4639
|
+
return exts.some((ext) => file.name.toLowerCase().endsWith(ext.toLowerCase()));
|
|
4640
|
+
}
|
|
4641
|
+
return entries.some(([mime, exts]) => {
|
|
4642
|
+
if (mime === file.type) return true;
|
|
4643
|
+
if (mime.endsWith("/*") && file.type.startsWith(mime.slice(0, -1))) return true;
|
|
4644
|
+
return exts.some((ext) => file.name.toLowerCase().endsWith(ext.toLowerCase()));
|
|
4645
|
+
});
|
|
4646
|
+
}
|
|
4647
|
+
function acceptedExtensions(accept) {
|
|
4648
|
+
const exts = Object.values(accept).flat();
|
|
4649
|
+
return exts.length > 0 ? exts.join(", ") : Object.keys(accept).join(", ");
|
|
4650
|
+
}
|
|
4651
|
+
function validateFiles(files, options = {}) {
|
|
4652
|
+
const { accept, maxSize, minSize, maxFiles, multiple = true, validator } = options;
|
|
4653
|
+
const accepted = [];
|
|
4654
|
+
const rejections = [];
|
|
4655
|
+
for (const file of files) {
|
|
4656
|
+
const errors = [];
|
|
4657
|
+
if (accept && !matchesAccept(file, accept)) {
|
|
4658
|
+
errors.push({
|
|
4659
|
+
code: "file-invalid-type",
|
|
4660
|
+
message: `File type must be one of ${acceptedExtensions(accept)}`
|
|
4661
|
+
});
|
|
4662
|
+
}
|
|
4663
|
+
if (maxSize !== void 0 && file.size > maxSize) {
|
|
4664
|
+
errors.push({ code: "file-too-large", message: `File is larger than ${maxSize} bytes` });
|
|
4665
|
+
}
|
|
4666
|
+
if (minSize !== void 0 && file.size < minSize) {
|
|
4667
|
+
errors.push({ code: "file-too-small", message: `File is smaller than ${minSize} bytes` });
|
|
4668
|
+
}
|
|
4669
|
+
const custom = validator?.(file);
|
|
4670
|
+
if (custom) errors.push(...Array.isArray(custom) ? custom : [custom]);
|
|
4671
|
+
if (errors.length > 0) {
|
|
4672
|
+
rejections.push({ file, errors });
|
|
4673
|
+
} else {
|
|
4674
|
+
accepted.push(file);
|
|
4675
|
+
}
|
|
4676
|
+
}
|
|
4677
|
+
const tooMany = !multiple && accepted.length > 1 || maxFiles !== void 0 && maxFiles >= 1 && accepted.length > maxFiles;
|
|
4678
|
+
if (tooMany) {
|
|
4679
|
+
const limit = !multiple ? 1 : maxFiles;
|
|
4680
|
+
for (const file of accepted) {
|
|
4681
|
+
rejections.push({
|
|
4682
|
+
file,
|
|
4683
|
+
errors: [{ code: "too-many-files", message: `Too many files \u2014 at most ${limit} allowed` }]
|
|
4684
|
+
});
|
|
4685
|
+
}
|
|
4686
|
+
accepted.length = 0;
|
|
4687
|
+
}
|
|
4688
|
+
return { accepted, rejections };
|
|
4689
|
+
}
|
|
4690
|
+
function hasFiles(event) {
|
|
4691
|
+
return Array.from(event.dataTransfer?.types ?? []).includes("Files");
|
|
4692
|
+
}
|
|
4693
|
+
var FileDropzone = forwardRef(
|
|
4694
|
+
({
|
|
4695
|
+
label,
|
|
4696
|
+
disabled = false,
|
|
4697
|
+
accept,
|
|
4698
|
+
maxSize,
|
|
4699
|
+
minSize,
|
|
4700
|
+
maxFiles,
|
|
4701
|
+
multiple = true,
|
|
4702
|
+
validator,
|
|
4703
|
+
onFilesAccepted,
|
|
4704
|
+
onFilesRejected,
|
|
4705
|
+
instructions,
|
|
4706
|
+
className,
|
|
4707
|
+
...props
|
|
4708
|
+
}, ref) => {
|
|
4709
|
+
const dragTargetsRef = useRef([]);
|
|
4710
|
+
const inputRef = useRef(null);
|
|
4711
|
+
const [dragState, setDragState] = useState("idle");
|
|
4712
|
+
const [rejections, setRejections] = useState([]);
|
|
4713
|
+
const validateOptions = {
|
|
4714
|
+
accept,
|
|
4715
|
+
maxSize,
|
|
4716
|
+
minSize,
|
|
4717
|
+
maxFiles,
|
|
4718
|
+
multiple,
|
|
4719
|
+
validator
|
|
4720
|
+
};
|
|
4721
|
+
const processFiles = (files) => {
|
|
4722
|
+
if (files.length === 0) return;
|
|
4723
|
+
const result = validateFiles(files, validateOptions);
|
|
4724
|
+
setRejections(result.rejections);
|
|
4725
|
+
if (result.accepted.length > 0) onFilesAccepted?.(result.accepted);
|
|
4726
|
+
if (result.rejections.length > 0) onFilesRejected?.(result.rejections);
|
|
4727
|
+
};
|
|
4728
|
+
const openPicker = () => {
|
|
4729
|
+
const input = inputRef.current;
|
|
4730
|
+
if (!input || disabled) return;
|
|
4731
|
+
input.value = "";
|
|
4732
|
+
input.click();
|
|
4733
|
+
};
|
|
4734
|
+
const onKeyDown = (event) => {
|
|
4735
|
+
if (event.target !== event.currentTarget) return;
|
|
4736
|
+
if (event.key === "Enter" || event.key === " ") {
|
|
4737
|
+
event.preventDefault();
|
|
4738
|
+
openPicker();
|
|
4739
|
+
}
|
|
4740
|
+
};
|
|
4741
|
+
const onDragEnter = (event) => {
|
|
4742
|
+
event.preventDefault();
|
|
4743
|
+
if (disabled || !hasFiles(event)) return;
|
|
4744
|
+
dragTargetsRef.current = [...dragTargetsRef.current, event.target];
|
|
4745
|
+
const items = Array.from(event.dataTransfer?.items ?? []).filter(
|
|
4746
|
+
(item) => item.kind === "file"
|
|
4747
|
+
);
|
|
4748
|
+
const reject = accept !== void 0 && items.length > 0 && items.some(
|
|
4749
|
+
(item) => item.type !== "" && !matchesAccept(
|
|
4750
|
+
new File([], item.getAsFile?.()?.name ?? "", { type: item.type }),
|
|
4751
|
+
accept
|
|
4752
|
+
)
|
|
4753
|
+
);
|
|
4754
|
+
setDragState(reject ? "drag-reject" : "drag-over");
|
|
4755
|
+
};
|
|
4756
|
+
const onDragOver = (event) => {
|
|
4757
|
+
event.preventDefault();
|
|
4758
|
+
if (event.dataTransfer) {
|
|
4759
|
+
try {
|
|
4760
|
+
event.dataTransfer.dropEffect = "copy";
|
|
4761
|
+
} catch {
|
|
4762
|
+
}
|
|
4763
|
+
}
|
|
4764
|
+
};
|
|
4765
|
+
const onDragLeave = (event) => {
|
|
4766
|
+
event.preventDefault();
|
|
4767
|
+
const root = event.currentTarget;
|
|
4768
|
+
const targets = dragTargetsRef.current.filter((t) => root.contains(t));
|
|
4769
|
+
const idx = targets.indexOf(event.target);
|
|
4770
|
+
if (idx !== -1) targets.splice(idx, 1);
|
|
4771
|
+
dragTargetsRef.current = targets;
|
|
4772
|
+
if (targets.length === 0) setDragState("idle");
|
|
4773
|
+
};
|
|
4774
|
+
const onDrop = (event) => {
|
|
4775
|
+
event.preventDefault();
|
|
4776
|
+
dragTargetsRef.current = [];
|
|
4777
|
+
setDragState("idle");
|
|
4778
|
+
if (disabled || !hasFiles(event)) return;
|
|
4779
|
+
processFiles(Array.from(event.dataTransfer?.files ?? []));
|
|
4780
|
+
};
|
|
4781
|
+
const state = disabled ? "disabled" : dragState;
|
|
4782
|
+
return /* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
|
|
4783
|
+
/* @__PURE__ */ jsx(
|
|
4784
|
+
"div",
|
|
4785
|
+
{
|
|
4786
|
+
"data-slot": "file-dropzone",
|
|
4787
|
+
"data-state": state,
|
|
4788
|
+
ref,
|
|
4789
|
+
role: "button",
|
|
4790
|
+
"aria-label": label,
|
|
4791
|
+
"aria-disabled": disabled || void 0,
|
|
4792
|
+
tabIndex: disabled ? void 0 : 0,
|
|
4793
|
+
onClick: () => openPicker(),
|
|
4794
|
+
onKeyDown,
|
|
4795
|
+
onDragEnter,
|
|
4796
|
+
onDragOver,
|
|
4797
|
+
onDragLeave,
|
|
4798
|
+
onDrop,
|
|
4799
|
+
className: cn(
|
|
4800
|
+
"flex min-h-24 cursor-pointer flex-col items-center justify-center gap-1 rounded-md border border-border border-dashed p-4 text-center",
|
|
4801
|
+
state === "drag-over" && "border-primary bg-primary/5",
|
|
4802
|
+
state === "drag-reject" && "border-destructive bg-destructive/5",
|
|
4803
|
+
state === "disabled" && "cursor-not-allowed opacity-50",
|
|
4804
|
+
className
|
|
4805
|
+
),
|
|
4806
|
+
...props,
|
|
4807
|
+
children: /* @__PURE__ */ jsx(
|
|
4808
|
+
"div",
|
|
4809
|
+
{
|
|
4810
|
+
"data-slot": "file-dropzone-instructions",
|
|
4811
|
+
className: "text-body-sm text-muted-foreground",
|
|
4812
|
+
children: instructions ?? "Drag files here or click to browse."
|
|
4813
|
+
}
|
|
4814
|
+
)
|
|
4815
|
+
}
|
|
4816
|
+
),
|
|
4817
|
+
/* @__PURE__ */ jsx(
|
|
4818
|
+
"input",
|
|
4819
|
+
{
|
|
4820
|
+
"data-slot": "file-dropzone-input",
|
|
4821
|
+
ref: inputRef,
|
|
4822
|
+
type: "file",
|
|
4823
|
+
"aria-hidden": "true",
|
|
4824
|
+
tabIndex: -1,
|
|
4825
|
+
multiple,
|
|
4826
|
+
accept: accept ? Object.entries(accept).flatMap(([m, e]) => [m, ...e]).join(",") : void 0,
|
|
4827
|
+
disabled,
|
|
4828
|
+
className: "sr-only",
|
|
4829
|
+
onChange: (event) => processFiles(Array.from(event.target.files ?? []))
|
|
4830
|
+
}
|
|
4831
|
+
),
|
|
4832
|
+
rejections.length > 0 && /* @__PURE__ */ jsx(
|
|
4833
|
+
"ul",
|
|
4834
|
+
{
|
|
4835
|
+
"data-slot": "file-dropzone-rejections",
|
|
4836
|
+
className: "space-y-0.5 text-destructive text-label",
|
|
4837
|
+
children: rejections.map((r) => /* @__PURE__ */ jsxs("li", { children: [
|
|
4838
|
+
r.file.name,
|
|
4839
|
+
" \u2014 ",
|
|
4840
|
+
r.errors.map((e) => e.message).join("; ")
|
|
4841
|
+
] }, r.file.name))
|
|
4842
|
+
}
|
|
4843
|
+
)
|
|
4844
|
+
] });
|
|
4845
|
+
}
|
|
4846
|
+
);
|
|
4847
|
+
FileDropzone.displayName = "FileDropzone";
|
|
4542
4848
|
var MetricsPanel = forwardRef(
|
|
4543
4849
|
({ className, title, description, metrics, columns, ...props }, ref) => /* @__PURE__ */ jsxs(
|
|
4544
4850
|
"div",
|
|
@@ -5469,4 +5775,4 @@ function CommandPalette({
|
|
|
5469
5775
|
] }) });
|
|
5470
5776
|
}
|
|
5471
5777
|
|
|
5472
|
-
export { AccountMenu, ActionBar, Alert, Avatar, BadgeWithDot as Badge, Breadcrumb, Button, Card, Checkbox, CodeBlock, Combobox, CommandPalette, ConfirmDialog, CopyButton, DangerZone, DataTable, DeploymentRow, DescriptionList, Dialog, DomainConfig, DropdownMenu, EmptyState, EnvVarEditor, FormField, Input, JsonViewer, Label, LoginSplit, MetricCard, MetricsPanel, PageShell, Pagination, PinInput, PlanBadge, PreviewEnvCard, Progress, ProjectCard, RadioGroup, RollbackUI, ScrollArea, Select, Sheet, Sidebar, Skeleton, Slider, SocialAuthRow, StatTile, StatusDot, StatusIndicator, Switch, Table, Tabs, TaskHeader, Textarea, Timestamp, Toast, Toaster, TooltipWithStatics as Tooltip, TopNav, TrendChart, UpdateBanner, avatarVariants, badgeVariants, buttonVariants, cn, computePageRange, isNearBottom, linScale, niceMax, seriesPath, sheetVariants, useStickToBottom, useToast };
|
|
5778
|
+
export { AccountMenu, ActionBar, Alert, Avatar, BadgeWithDot as Badge, Breadcrumb, Button, Card, Checkbox, CodeBlock, Combobox, CommandPalette, ConfirmDialog, CopyButton, DangerZone, DataTable, DeploymentRow, DescriptionList, Dialog, DomainConfig, DropdownMenu, EmptyState, EnvVarEditor, FileDropzone, FormField, Input, JsonViewer, Label, LoginSplit, MetricCard, MetricsPanel, PageShell, Pagination, PinInput, PlanBadge, PreviewEnvCard, Progress, ProjectCard, RadioGroup, RollbackUI, ScrollArea, Select, Sheet, Sidebar, Skeleton, Slider, SocialAuthRow, StatTile, StatusDot, StatusIndicator, Stepper, Switch, Table, Tabs, TaskHeader, Textarea, Timestamp, Toast, Toaster, TooltipWithStatics as Tooltip, TopNav, TrendChart, UpdateBanner, avatarVariants, badgeVariants, buttonVariants, cn, computePageRange, deriveSteps, isNearBottom, linScale, matchesAccept, niceMax, seriesPath, sheetVariants, useStickToBottom, useToast, validateFiles };
|
package/package.json
CHANGED