@deftui/react 0.1.2 → 0.1.4

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/README.md ADDED
@@ -0,0 +1,43 @@
1
+ # @deftui/react
2
+
3
+ A React component library built on Base UI, shadcn/ui, and their open-source registry ecosystem, offering easy-to-use compound component APIs.
4
+
5
+ [Upstream documentation](./dist/vendor/README.md): To look up component usage, check the local exports first, then consult the upstream.
6
+
7
+ > Most basic components expose exactly the same APIs as their shadcn/ui counterparts backed by Base UI.
8
+
9
+ ## Installation
10
+
11
+ ```sh
12
+ bun add @deftui/react
13
+ ```
14
+
15
+ ## Usage
16
+
17
+ Install Tailwind CSS 4 in your project first, then import the styles in your application's global CSS:
18
+
19
+ ```css
20
+ @import "tailwindcss";
21
+ @import "@deftui/react/styles";
22
+ ```
23
+
24
+ Component Example:
25
+
26
+ ```tsx
27
+ import { Button, Card } from "@deftui/react";
28
+
29
+ export function Example() {
30
+ return (
31
+ <Card>
32
+ <Card.Header>
33
+ <Card.Title>Welcome to Deft UI</Card.Title>
34
+ <Card.Description>Compose your UI using Compound Component API</Card.Description>
35
+ </Card.Header>
36
+
37
+ <Card.Content>
38
+ <Button>Get started</Button>
39
+ </Card.Content>
40
+ </Card>
41
+ );
42
+ }
43
+ ```
package/dist/index.d.mts CHANGED
@@ -16,6 +16,7 @@ import { Dropzone, formatSize } from "./widgets/dropzone.mjs";
16
16
  import { LightboxActions, LightboxBackdrop, LightboxCaptions, LightboxChangeReason, LightboxClose, LightboxContent, LightboxControl, LightboxControlContext, LightboxCounter, LightboxDownload, LightboxImageChangeEventDetails, LightboxImageItem, LightboxItem, LightboxMore, LightboxNext, LightboxOpenEventDetails, LightboxPrevious, LightboxProps, LightboxSlide, LightboxSlides, LightboxThumbnail, LightboxThumbnails, LightboxToolbar, LightboxTrigger, LightboxTriggerProps, LightboxVideoItem, LightboxViewport, LightboxZoom, LightboxZoomButton, useLightbox } from "./vendor/tailored/lightbox.mjs";
17
17
  import { Carousel, Image, ImageProps, Lightbox } from "./widgets/multimedia.mjs";
18
18
  import { Pagination } from "./widgets/pagination.mjs";
19
+ import { Form } from "./vendor/coss/form.mjs";
19
20
  import { Badge } from "./vendor/shadcn/badge.mjs";
20
21
  import { Switch } from "./vendor/shadcn/switch.mjs";
21
22
  import { Spinner } from "./vendor/shadcn/spinner.mjs";
@@ -23,12 +24,13 @@ import { Skeleton } from "./vendor/shadcn/skeleton.mjs";
23
24
  import { Checkbox } from "./vendor/shadcn/checkbox.mjs";
24
25
  import { Textarea } from "./vendor/shadcn/textarea.mjs";
25
26
  import { ScrollArea } from "./vendor/coss/scroll-area.mjs";
26
- import { Form } from "./vendor/coss/form.mjs";
27
27
  import { IconSwap } from "./vendor/tailored/icon-swap.mjs";
28
+ import { JsonViewer } from "./vendor/tailored/json-viewer.mjs";
29
+ import { FileTree, FileTreeNode } from "./vendor/tailored/file-tree.mjs";
28
30
  import { AlertDialog, Breadcrumb, ButtonGroup, Calendar, Card, Combobox, Dialog, Drawer, Dropdown, Empty, Field, InputGroup, Kbd, Popover, Progress, Radio, Select, Table, Tabs, Tooltip } from "./widgets/index.mjs";
29
31
  import { cn } from "cn";
30
32
  import { cva } from "class-variance-authority";
31
33
  import { mergeProps } from "@base-ui/react/merge-props";
32
34
  import { useDropzone } from "react-dropzone";
33
35
  export type * from "react-dropzone";
34
- export { AlertDialog, type AlertDialogHandle, Badge, Breadcrumb, Button, ButtonGroup, Calendar, Card, Carousel, Checkbox, Combobox, type ComboboxItemCollection, type ComboboxPrimitiveValue, type CreateComboboxItemsOptions, DatePicker, type DateRange, DateRangePicker, Dialog, type DialogHandle, Drawer, type DrawerHandle, Dropdown, Dropzone, Empty, Field, Form, IconSwap, Image, ImageProps, Input, InputGroup, Kbd, Label, Lightbox, type LightboxActions, type LightboxBackdrop, type LightboxCaptions, type LightboxChangeReason, type LightboxClose, type LightboxContent, type LightboxControl, type LightboxControlContext, type LightboxCounter, type LightboxDownload, type LightboxImageChangeEventDetails, type LightboxImageItem, type LightboxItem, type LightboxMore, type LightboxNext, type LightboxOpenEventDetails, type LightboxPrevious, type LightboxProps, type LightboxSlide, type LightboxSlides, type LightboxThumbnail, type LightboxThumbnails, type LightboxToolbar, type LightboxTrigger, type LightboxTriggerProps, type LightboxVideoItem, type LightboxViewport, type LightboxZoom, type LightboxZoomButton, Pagination, Popover, Progress, Radio, ScrollArea, Select, Separator, Sidebar, Skeleton, Spinner, Switch, Table, Tabs, Textarea, TimePicker, Toaster as ToastProvider, Tooltip, cn, createAlertDialogHandle, createComboboxItems, createDialogHandle, createDrawerHandle, cva, formatSize, mergeProps, toast, useCopyToClipboard, useDropzone, useIsMobile, type useLightbox, useMediaQuery, useSidebar };
36
+ export { AlertDialog, type AlertDialogHandle, Badge, Breadcrumb, Button, ButtonGroup, Calendar, Card, Carousel, Checkbox, Combobox, type ComboboxItemCollection, type ComboboxPrimitiveValue, type CreateComboboxItemsOptions, DatePicker, type DateRange, DateRangePicker, Dialog, type DialogHandle, Drawer, type DrawerHandle, Dropdown, Dropzone, Empty, Field, FileTree, type FileTreeNode, Form, IconSwap, Image, ImageProps, Input, InputGroup, JsonViewer, Kbd, Label, Lightbox, type LightboxActions, type LightboxBackdrop, type LightboxCaptions, type LightboxChangeReason, type LightboxClose, type LightboxContent, type LightboxControl, type LightboxControlContext, type LightboxCounter, type LightboxDownload, type LightboxImageChangeEventDetails, type LightboxImageItem, type LightboxItem, type LightboxMore, type LightboxNext, type LightboxOpenEventDetails, type LightboxPrevious, type LightboxProps, type LightboxSlide, type LightboxSlides, type LightboxThumbnail, type LightboxThumbnails, type LightboxToolbar, type LightboxTrigger, type LightboxTriggerProps, type LightboxVideoItem, type LightboxViewport, type LightboxZoom, type LightboxZoomButton, Pagination, Popover, Progress, Radio, ScrollArea, Select, Separator, Sidebar, Skeleton, Spinner, Switch, Table, Tabs, Textarea, TimePicker, Toaster as ToastProvider, Tooltip, cn, createAlertDialogHandle, createComboboxItems, createDialogHandle, createDrawerHandle, cva, formatSize, mergeProps, toast, useCopyToClipboard, useDropzone, useIsMobile, type useLightbox, useMediaQuery, useSidebar };
package/dist/index.mjs CHANGED
@@ -18,15 +18,17 @@ import { DatePicker, DateRangePicker, TimePicker } from "./widgets/datetime.mjs"
18
18
  import { Dropzone, formatSize } from "./widgets/dropzone.mjs";
19
19
  import { Carousel, Image, Lightbox } from "./widgets/multimedia.mjs";
20
20
  import { Pagination } from "./widgets/pagination.mjs";
21
+ import { Form } from "./vendor/coss/form.mjs";
21
22
  import { Badge } from "./vendor/shadcn/badge.mjs";
22
23
  import { Switch } from "./vendor/shadcn/switch.mjs";
23
24
  import { Checkbox } from "./vendor/shadcn/checkbox.mjs";
24
25
  import { ScrollArea } from "./vendor/coss/scroll-area.mjs";
25
- import { Form } from "./vendor/coss/form.mjs";
26
26
  import { IconSwap } from "./vendor/tailored/icon-swap.mjs";
27
+ import { JsonViewer } from "./vendor/tailored/json-viewer.mjs";
28
+ import { FileTree } from "./vendor/tailored/file-tree.mjs";
27
29
  import { AlertDialog, Breadcrumb, ButtonGroup, Calendar, Card, Combobox, Dialog, Drawer, Dropdown, Empty, Field, InputGroup, Kbd, Popover, Progress, Radio, Select, Table, Tabs, Tooltip } from "./widgets/index.mjs";
28
30
  import { cn } from "cn";
29
31
  import { cva } from "class-variance-authority";
30
32
  import { mergeProps } from "@base-ui/react/merge-props";
31
33
  import { useDropzone } from "react-dropzone";
32
- export { AlertDialog, Badge, Breadcrumb, Button, ButtonGroup, Calendar, Card, Carousel, Checkbox, Combobox, DatePicker, DateRangePicker, Dialog, Drawer, Dropdown, Dropzone, Empty, Field, Form, IconSwap, Image, Input, InputGroup, Kbd, Label, Lightbox, Pagination, Popover, Progress, Radio, ScrollArea, Select, Separator, Sidebar, Skeleton, Spinner, Switch, Table, Tabs, Textarea, TimePicker, Toaster as ToastProvider, Tooltip, cn, createAlertDialogHandle, createComboboxItems, createDialogHandle, createDrawerHandle, cva, formatSize, mergeProps, toast, useCopyToClipboard, useDropzone, useIsMobile, useMediaQuery, useSidebar };
34
+ export { AlertDialog, Badge, Breadcrumb, Button, ButtonGroup, Calendar, Card, Carousel, Checkbox, Combobox, DatePicker, DateRangePicker, Dialog, Drawer, Dropdown, Dropzone, Empty, Field, FileTree, Form, IconSwap, Image, Input, InputGroup, JsonViewer, Kbd, Label, Lightbox, Pagination, Popover, Progress, Radio, ScrollArea, Select, Separator, Sidebar, Skeleton, Spinner, Switch, Table, Tabs, Textarea, TimePicker, Toaster as ToastProvider, Tooltip, cn, createAlertDialogHandle, createComboboxItems, createDialogHandle, createDrawerHandle, cva, formatSize, mergeProps, toast, useCopyToClipboard, useDropzone, useIsMobile, useMediaQuery, useSidebar };
@@ -1,4 +1,4 @@
1
- /* Ejected from shadcn v4.22.2 — cSpell: disable */
1
+ /* Ejected from shadcn v4.23.0 — cSpell: disable */
2
2
 
3
3
  @theme inline {
4
4
  @keyframes accordion-down {
@@ -54,14 +54,15 @@ shadcn 组件使用 `components.json` 中的 `base-vega` 风格和 Lucide 图标
54
54
 
55
55
  ## Tailored
56
56
 
57
- `tailored/` 存放基于上游、需要较大结构或行为调整的单体组件。仅需导入路径、图标或少量样式适配的组件,仍放在对应的来源目录。
57
+ `tailored/` 存放:仅少量引入的上游组件 或 需要较大调整的单体组件
58
58
 
59
59
  - 按本地需求维护,对比后选择性吸收上游修复,避免直接覆盖本地实现。
60
60
  - 优先复用已有基础组件;仅供该组件使用的依赖可内联,分别记录其来源。
61
- - 下表记录主组件及内联依赖的 registry ID、文档和主要修改;对外的组合封装放在 `widgets/`。
62
61
 
63
62
  | ID | File | Docs | Modifications |
64
63
  | --- | --- | --- | --- |
65
64
  | `https://ui.iandefined.com/r/icon-swap.json` | [tailored/icon-swap.tsx](tailored/icon-swap.tsx) | [Icon Swap](https://ui.iandefined.com/docs/utilities/icon-swap/) | 进出场动画减弱模糊为 2px,缩短为 0.2s 时长 |
66
65
  | `https://ui.iandefined.com/r/lightbox.json` | [tailored/lightbox.tsx](tailored/lightbox.tsx) | [Lightbox](https://ui.iandefined.com/docs/components/lightbox/) | 适配本地组件和尺寸;修正嵌套弹层层级与点击隔离;补充加载失败反馈 |
67
66
  | `https://ui.iandefined.com/r/scroll-area.json` | [tailored/lightbox.tsx](tailored/lightbox.tsx) | [Scroll Area](https://ui.iandefined.com/docs/components/scroll-area/) | 作为 Lightbox 的私有依赖内联 |
67
+ | `Tinkerers-Labs/mischief-ui/file-tree` | [tailored/file-tree.tsx](tailored/file-tree.tsx) | [File Tree](https://ui.tinkererslabs.com/docs/components/file-tree/) | — |
68
+ | `Tinkerers-Labs/mischief-ui/json-viewer` | [tailored/json-viewer.tsx](tailored/json-viewer.tsx) | [JSON Viewer](https://ui.tinkererslabs.com/docs/components/json-viewer/) | — |
@@ -0,0 +1,20 @@
1
+ import * as React$1 from "react";
2
+ export type FileTreeNode = {
3
+ id: string;
4
+ name: string;
5
+ kind?: "file" | "folder";
6
+ children?: FileTreeNode[];
7
+ meta?: React$1.ReactNode;
8
+ icon?: React$1.ReactNode;
9
+ };
10
+ export type FileTreeProps = Omit<React$1.HTMLAttributes<HTMLDivElement>, "children" | "onSelect"> & {
11
+ nodes: FileTreeNode[];
12
+ expandedIds?: string[];
13
+ defaultExpandedIds?: string[];
14
+ onExpandedChange?: (ids: string[]) => void;
15
+ selectedId?: string | null;
16
+ defaultSelectedId?: string | null;
17
+ onSelect?: (node: FileTreeNode) => void;
18
+ label?: string;
19
+ };
20
+ export declare function FileTree({ nodes, expandedIds, defaultExpandedIds, onExpandedChange, selectedId, defaultSelectedId, onSelect, label, className, ...rootProps }: FileTreeProps): React$1.JSX.Element;
@@ -0,0 +1,145 @@
1
+ "use client";
2
+ import { cn } from "../shadcn/lib/utils.mjs";
3
+ import * as React$1 from "react";
4
+ import { jsx, jsxs } from "react/jsx-runtime";
5
+ import { ChevronRight, File, Folder, FolderOpen } from "lucide-react";
6
+ // src/vendor/tailored/file-tree.tsx
7
+ function isFolder(node) {
8
+ return node.kind === "folder" || Array.isArray(node.children);
9
+ }
10
+ function flatten(nodes, expanded, level = 1) {
11
+ return nodes.flatMap((node) => {
12
+ const folder = isFolder(node);
13
+ const open = folder && expanded.has(node.id);
14
+ const self = {
15
+ node,
16
+ level,
17
+ isFolder: folder,
18
+ isExpanded: open
19
+ };
20
+ if (!open || !node.children) return [self];
21
+ return [self, ...flatten(node.children, expanded, level + 1)];
22
+ });
23
+ }
24
+ function FileTree({ nodes, expandedIds, defaultExpandedIds = [], onExpandedChange, selectedId, defaultSelectedId = null, onSelect, label = "Files", className, ...rootProps }) {
25
+ const [uncontrolledExpanded, setUncontrolledExpanded] = React$1.useState(defaultExpandedIds);
26
+ const [uncontrolledSelected, setUncontrolledSelected] = React$1.useState(defaultSelectedId);
27
+ const expandedList = expandedIds ?? uncontrolledExpanded;
28
+ const expanded = React$1.useMemo(() => new Set(expandedList), [expandedList]);
29
+ const selected = selectedId === void 0 ? uncontrolledSelected : selectedId;
30
+ const rows = React$1.useMemo(() => flatten(nodes, expanded), [nodes, expanded]);
31
+ const containerRef = React$1.useRef(null);
32
+ const setExpanded = (ids) => {
33
+ if (expandedIds === void 0) setUncontrolledExpanded(ids);
34
+ onExpandedChange?.(ids);
35
+ };
36
+ const toggle = (id, open) => {
37
+ const shouldOpen = open ?? !expanded.has(id);
38
+ if (shouldOpen === expanded.has(id)) return;
39
+ setExpanded(shouldOpen ? [...expandedList, id] : expandedList.filter((entry) => entry !== id));
40
+ };
41
+ const select = (node) => {
42
+ if (selectedId === void 0) setUncontrolledSelected(node.id);
43
+ onSelect?.(node);
44
+ };
45
+ const focusRow = (index) => {
46
+ const target = rows[index];
47
+ if (!target) return;
48
+ containerRef.current?.querySelector(`[data-node="${CSS.escape(target.node.id)}"]`)?.focus();
49
+ };
50
+ const onKeyDown = (event, row, index) => {
51
+ switch (event.key) {
52
+ case "ArrowDown":
53
+ event.preventDefault();
54
+ focusRow(Math.min(index + 1, rows.length - 1));
55
+ break;
56
+ case "ArrowUp":
57
+ event.preventDefault();
58
+ focusRow(Math.max(index - 1, 0));
59
+ break;
60
+ case "ArrowRight":
61
+ event.preventDefault();
62
+ if (row.isFolder && !row.isExpanded) toggle(row.node.id, true);
63
+ else if (row.isFolder) focusRow(index + 1);
64
+ break;
65
+ case "ArrowLeft": {
66
+ event.preventDefault();
67
+ if (row.isFolder && row.isExpanded) {
68
+ toggle(row.node.id, false);
69
+ break;
70
+ }
71
+ const parent = rows.slice(0, index).reverse().findIndex((entry) => entry.level < row.level);
72
+ if (parent >= 0) focusRow(index - 1 - parent);
73
+ break;
74
+ }
75
+ case "Home":
76
+ event.preventDefault();
77
+ focusRow(0);
78
+ break;
79
+ case "End":
80
+ event.preventDefault();
81
+ focusRow(rows.length - 1);
82
+ break;
83
+ case "Enter":
84
+ case " ":
85
+ event.preventDefault();
86
+ if (row.isFolder) toggle(row.node.id);
87
+ select(row.node);
88
+ }
89
+ };
90
+ const activeIndex = Math.max(rows.findIndex((row) => row.node.id === selected), 0);
91
+ return /* @__PURE__ */ jsx("div", {
92
+ ref: containerRef,
93
+ "data-slot": "file-tree",
94
+ className: cn("border-border bg-card text-card-foreground rounded-[var(--radius)] border p-1.5", className),
95
+ ...rootProps,
96
+ children: /* @__PURE__ */ jsx("div", {
97
+ role: "tree",
98
+ "aria-label": label,
99
+ children: rows.map((row, index) => {
100
+ const isSelected = row.node.id === selected;
101
+ const Icon = row.isFolder ? row.isExpanded ? FolderOpen : Folder : File;
102
+ return /* @__PURE__ */ jsxs("div", {
103
+ role: "treeitem",
104
+ "data-node": row.node.id,
105
+ "data-slot": "file-tree-item",
106
+ "aria-level": row.level,
107
+ "aria-selected": isSelected,
108
+ "aria-expanded": row.isFolder ? row.isExpanded : void 0,
109
+ tabIndex: index === activeIndex ? 0 : -1,
110
+ className: cn("focus-visible:ring-ring flex min-h-9 cursor-default items-center gap-1.5 rounded-[calc(var(--radius)-0.35rem)] pr-2 text-sm focus-visible:ring-2 focus-visible:outline-none", isSelected ? "bg-muted font-semibold" : "hover:bg-muted/60 transition-colors duration-150 motion-reduce:transition-none"),
111
+ style: { paddingLeft: `${(row.level - 1) * .85 + .35}rem` },
112
+ onClick: () => {
113
+ if (row.isFolder) toggle(row.node.id);
114
+ select(row.node);
115
+ },
116
+ onKeyDown: (event) => onKeyDown(event, row, index),
117
+ children: [
118
+ /* @__PURE__ */ jsx("span", {
119
+ className: "flex size-4 shrink-0 items-center justify-center",
120
+ children: row.isFolder ? /* @__PURE__ */ jsx(ChevronRight, {
121
+ "aria-hidden": "true",
122
+ size: 13,
123
+ className: cn("text-muted-foreground transition-transform duration-150 motion-reduce:transition-none", row.isExpanded && "rotate-90")
124
+ }) : null
125
+ }),
126
+ /* @__PURE__ */ jsx("span", {
127
+ "aria-hidden": "true",
128
+ className: "text-muted-foreground shrink-0",
129
+ children: row.node.icon ?? /* @__PURE__ */ jsx(Icon, { size: 15 })
130
+ }),
131
+ /* @__PURE__ */ jsx("span", {
132
+ className: "min-w-0 flex-1 truncate",
133
+ children: row.node.name
134
+ }),
135
+ row.node.meta ? /* @__PURE__ */ jsx("span", {
136
+ className: "text-muted-foreground shrink-0 text-xs",
137
+ children: row.node.meta
138
+ }) : null
139
+ ]
140
+ }, row.node.id);
141
+ })
142
+ })
143
+ });
144
+ }
145
+ export { FileTree };
@@ -0,0 +1,10 @@
1
+ import * as React$1 from "react";
2
+ export type JsonViewerProps = Omit<React$1.HTMLAttributes<HTMLDivElement>, "children"> & {
3
+ value: unknown;
4
+ rootName?: string;
5
+ defaultExpandedDepth?: number;
6
+ maxStringLength?: number;
7
+ copyable?: boolean;
8
+ label?: string;
9
+ };
10
+ export declare function JsonViewer({ value, rootName, defaultExpandedDepth, maxStringLength, copyable, label, className, ...rootProps }: JsonViewerProps): React$1.JSX.Element;
@@ -0,0 +1,230 @@
1
+ "use client";
2
+ import { cn } from "../shadcn/lib/utils.mjs";
3
+ import * as React$1 from "react";
4
+ import { jsx, jsxs } from "react/jsx-runtime";
5
+ import { Check, ChevronRight, Clipboard } from "lucide-react";
6
+ // src/vendor/tailored/json-viewer.tsx
7
+ function kindOf(value) {
8
+ if (value === null) return "null";
9
+ if (Array.isArray(value)) return "array";
10
+ switch (typeof value) {
11
+ case "object": return "object";
12
+ case "string": return "string";
13
+ case "number": return "number";
14
+ case "boolean": return "boolean";
15
+ default: return "other";
16
+ }
17
+ }
18
+ function entriesOf(value) {
19
+ if (Array.isArray(value)) return value.map((entry, index) => [String(index), entry]);
20
+ return Object.entries(value);
21
+ }
22
+ function size(value) {
23
+ return Array.isArray(value) ? value.length : Object.keys(value).length;
24
+ }
25
+ /** What a collapsed branch says about itself, without unfolding it. */
26
+ function summarise(value, kind) {
27
+ const count = size(value);
28
+ const noun = count === 1 ? "item" : "items";
29
+ return kind === "array" ? `[ ${count} ${noun} ]` : `{ ${count} ${noun} }`;
30
+ }
31
+ /**
32
+ * A JSON path in the notation somebody would paste back into code, so the copy
33
+ * is worth taking: `data.rows[0].name` rather than a list of segments.
34
+ */
35
+ function joinPath(parent, key, inArray) {
36
+ if (inArray) return `${parent}[${key}]`;
37
+ return /^[A-Za-z_$][\w$]*$/.test(key) ? `${parent}.${key}` : `${parent}[${JSON.stringify(key)}]`;
38
+ }
39
+ function flatten(value, expanded, rootName) {
40
+ const walk = (node, key, path, level, index) => {
41
+ const kind = kindOf(node);
42
+ const branch = (kind === "object" || kind === "array") && size(node) > 0;
43
+ const open = branch && expanded.has(path);
44
+ const self = {
45
+ path,
46
+ key,
47
+ value: node,
48
+ kind,
49
+ level,
50
+ branch,
51
+ expanded: open,
52
+ index
53
+ };
54
+ if (!open) return [self];
55
+ const inArray = kind === "array";
56
+ return [self, ...entriesOf(node).flatMap(([childKey, child], at) => walk(child, childKey, joinPath(path, childKey, inArray), level + 1, inArray ? at : null))];
57
+ };
58
+ return walk(value, null, rootName, 1, null);
59
+ }
60
+ /** Every path down to the requested depth, so the first view is not one line. */
61
+ function pathsToDepth(value, rootName, depth) {
62
+ const open = /* @__PURE__ */ new Set();
63
+ const walk = (node, path, level) => {
64
+ const kind = kindOf(node);
65
+ if (kind !== "object" && kind !== "array") return;
66
+ if (size(node) === 0 || level > depth) return;
67
+ open.add(path);
68
+ const inArray = kind === "array";
69
+ for (const [key, child] of entriesOf(node)) walk(child, joinPath(path, key, inArray), level + 1);
70
+ };
71
+ walk(value, rootName, 1);
72
+ return open;
73
+ }
74
+ const VALUE_CLASS = {
75
+ string: "text-emerald-600 dark:text-emerald-400",
76
+ number: "text-sky-600 dark:text-sky-400",
77
+ boolean: "text-violet-600 dark:text-violet-400",
78
+ null: "text-muted-foreground",
79
+ other: "text-muted-foreground",
80
+ object: "text-muted-foreground",
81
+ array: "text-muted-foreground"
82
+ };
83
+ function JsonViewer({ value, rootName = "root", defaultExpandedDepth = 1, maxStringLength = 120, copyable = true, label = "JSON", className, ...rootProps }) {
84
+ const [expanded, setExpanded] = React$1.useState(() => pathsToDepth(value, rootName, defaultExpandedDepth));
85
+ const [copied, setCopied] = React$1.useState(null);
86
+ const [activePath, setActivePath] = React$1.useState(null);
87
+ const containerRef = React$1.useRef(null);
88
+ const timer = React$1.useRef(null);
89
+ React$1.useEffect(() => () => {
90
+ if (timer.current) clearTimeout(timer.current);
91
+ }, []);
92
+ const rows = React$1.useMemo(() => flatten(value, expanded, rootName), [
93
+ value,
94
+ expanded,
95
+ rootName
96
+ ]);
97
+ const active = activePath && rows.some((row) => row.path === activePath) ? activePath : rows[0]?.path;
98
+ const toggle = (path, open) => {
99
+ setExpanded((current) => {
100
+ const shouldOpen = open ?? !current.has(path);
101
+ if (shouldOpen === current.has(path)) return current;
102
+ const next = new Set(current);
103
+ if (shouldOpen) next.add(path);
104
+ else next.delete(path);
105
+ return next;
106
+ });
107
+ };
108
+ const focusRow = (index) => {
109
+ const target = rows[index];
110
+ if (!target) return;
111
+ setActivePath(target.path);
112
+ containerRef.current?.querySelector(`[data-path="${CSS.escape(target.path)}"]`)?.focus();
113
+ };
114
+ async function copyValue(row) {
115
+ try {
116
+ await navigator.clipboard.writeText(typeof row.value === "string" ? row.value : JSON.stringify(row.value, null, 2));
117
+ setCopied(row.path);
118
+ } catch {
119
+ setCopied(null);
120
+ }
121
+ if (timer.current) clearTimeout(timer.current);
122
+ timer.current = setTimeout(() => setCopied(null), 1400);
123
+ }
124
+ const onKeyDown = (event, row, index) => {
125
+ switch (event.key) {
126
+ case "ArrowDown":
127
+ event.preventDefault();
128
+ focusRow(Math.min(index + 1, rows.length - 1));
129
+ break;
130
+ case "ArrowUp":
131
+ event.preventDefault();
132
+ focusRow(Math.max(index - 1, 0));
133
+ break;
134
+ case "ArrowRight":
135
+ event.preventDefault();
136
+ if (row.branch && !row.expanded) toggle(row.path, true);
137
+ else if (row.branch) focusRow(index + 1);
138
+ break;
139
+ case "ArrowLeft": {
140
+ event.preventDefault();
141
+ if (row.branch && row.expanded) {
142
+ toggle(row.path, false);
143
+ break;
144
+ }
145
+ const back = rows.slice(0, index).reverse().findIndex((entry) => entry.level < row.level);
146
+ if (back >= 0) focusRow(index - 1 - back);
147
+ break;
148
+ }
149
+ case "Home":
150
+ event.preventDefault();
151
+ focusRow(0);
152
+ break;
153
+ case "End":
154
+ event.preventDefault();
155
+ focusRow(rows.length - 1);
156
+ break;
157
+ case "Enter":
158
+ case " ":
159
+ if (!row.branch) break;
160
+ event.preventDefault();
161
+ toggle(row.path);
162
+ }
163
+ };
164
+ return /* @__PURE__ */ jsxs("div", {
165
+ "data-slot": "json-viewer",
166
+ ref: containerRef,
167
+ className: cn("border-border bg-muted/40 min-w-0 overflow-hidden rounded-[calc(var(--radius)+0.15rem)] border font-mono text-xs", className),
168
+ ...rootProps,
169
+ children: [/* @__PURE__ */ jsx("div", {
170
+ role: "tree",
171
+ "aria-label": label,
172
+ className: "max-h-96 overflow-auto py-1",
173
+ children: rows.map((row, index) => {
174
+ const preview = row.kind === "string" ? JSON.stringify(row.value).length > maxStringLength ? `${JSON.stringify(row.value).slice(0, maxStringLength)}…"` : JSON.stringify(row.value) : row.branch || row.kind === "object" || row.kind === "array" ? summarise(row.value, row.kind) : String(row.value);
175
+ return /* @__PURE__ */ jsxs("div", {
176
+ "data-path": row.path,
177
+ role: "treeitem",
178
+ "aria-level": row.level,
179
+ "aria-expanded": row.branch ? row.expanded : void 0,
180
+ "aria-selected": row.path === active,
181
+ tabIndex: row.path === active ? 0 : -1,
182
+ onFocus: () => setActivePath(row.path),
183
+ onKeyDown: (event) => onKeyDown(event, row, index),
184
+ onClick: () => row.branch && toggle(row.path),
185
+ style: { paddingInlineStart: `${row.level * .85}rem` },
186
+ className: cn("group hover:bg-muted/70 focus-visible:ring-ring flex min-h-7 items-center gap-1.5 pr-2 focus-visible:ring-2 focus-visible:outline-none", row.branch && "cursor-pointer"),
187
+ children: [
188
+ /* @__PURE__ */ jsx(ChevronRight, {
189
+ "aria-hidden": "true",
190
+ size: 13,
191
+ className: cn("text-muted-foreground shrink-0 transition-transform motion-reduce:transition-none", !row.branch && "invisible", row.expanded && "rotate-90")
192
+ }),
193
+ row.key !== null ? /* @__PURE__ */ jsx("span", {
194
+ className: "text-foreground shrink-0",
195
+ children: row.index === null ? `${row.key}:` : `${row.index}:`
196
+ }) : /* @__PURE__ */ jsx("span", {
197
+ className: "text-muted-foreground shrink-0",
198
+ children: rootName
199
+ }),
200
+ /* @__PURE__ */ jsx("span", {
201
+ className: cn("truncate", VALUE_CLASS[row.kind]),
202
+ children: preview
203
+ }),
204
+ copyable ? /* @__PURE__ */ jsx("button", {
205
+ type: "button",
206
+ "aria-label": copied === row.path ? "Copied" : `Copy ${row.path}`,
207
+ onClick: (event) => {
208
+ event.stopPropagation();
209
+ copyValue(row);
210
+ },
211
+ className: "text-muted-foreground hover:text-foreground focus-visible:ring-ring ml-auto hidden size-6 shrink-0 items-center justify-center rounded group-focus-within:flex group-hover:flex focus-visible:ring-2 focus-visible:outline-none",
212
+ children: copied === row.path ? /* @__PURE__ */ jsx(Check, {
213
+ "aria-hidden": "true",
214
+ size: 12
215
+ }) : /* @__PURE__ */ jsx(Clipboard, {
216
+ "aria-hidden": "true",
217
+ size: 12
218
+ })
219
+ }) : null
220
+ ]
221
+ }, row.path);
222
+ })
223
+ }), /* @__PURE__ */ jsx("span", {
224
+ "aria-live": "polite",
225
+ className: "sr-only",
226
+ children: copied ? `${copied} copied to clipboard.` : ""
227
+ })]
228
+ });
229
+ }
230
+ export { JsonViewer };
@@ -32,6 +32,7 @@ import { Dropzone, formatSize } from "./dropzone.mjs";
32
32
  import { LightboxActions, LightboxBackdrop, LightboxCaptions, LightboxChangeReason, LightboxClose, LightboxContent, LightboxControl, LightboxControlContext, LightboxCounter, LightboxDownload, LightboxImageChangeEventDetails, LightboxImageItem, LightboxItem, LightboxMore, LightboxNext, LightboxOpenEventDetails, LightboxPrevious, LightboxProps, LightboxSlide, LightboxSlides, LightboxThumbnail, LightboxThumbnails, LightboxToolbar, LightboxTrigger, LightboxTriggerProps, LightboxVideoItem, LightboxViewport, LightboxZoom, LightboxZoomButton, useLightbox } from "../vendor/tailored/lightbox.mjs";
33
33
  import { Carousel, Image, ImageProps, Lightbox } from "./multimedia.mjs";
34
34
  import { Pagination } from "./pagination.mjs";
35
+ import { Form } from "../vendor/coss/form.mjs";
35
36
  import { Badge } from "../vendor/shadcn/badge.mjs";
36
37
  import { Switch } from "../vendor/shadcn/switch.mjs";
37
38
  import { Spinner } from "../vendor/shadcn/spinner.mjs";
@@ -39,8 +40,9 @@ import { Skeleton } from "../vendor/shadcn/skeleton.mjs";
39
40
  import { Checkbox } from "../vendor/shadcn/checkbox.mjs";
40
41
  import { Textarea } from "../vendor/shadcn/textarea.mjs";
41
42
  import { ScrollArea } from "../vendor/coss/scroll-area.mjs";
42
- import { Form } from "../vendor/coss/form.mjs";
43
43
  import { IconSwap } from "../vendor/tailored/icon-swap.mjs";
44
+ import { JsonViewer } from "../vendor/tailored/json-viewer.mjs";
45
+ import { FileTree, FileTreeNode } from "../vendor/tailored/file-tree.mjs";
44
46
  export declare const Kbd: typeof Kbd$1 & {
45
47
  Group: typeof KbdGroup;
46
48
  };
@@ -185,4 +187,4 @@ export declare const InputGroup: typeof InputGroup$1 & {
185
187
  Textarea: typeof InputGroupTextarea;
186
188
  Button: typeof InputGroupButton;
187
189
  };
188
- export { type AlertDialogHandle, Badge, Button, Carousel, Checkbox, type ComboboxItemCollection, type ComboboxPrimitiveValue, type CreateComboboxItemsOptions, DatePicker, type DateRange, DateRangePicker, type DialogHandle, type DrawerHandle, Dropzone, Form, IconSwap, Image, ImageProps, Input, Label, Lightbox, type LightboxActions, type LightboxBackdrop, type LightboxCaptions, type LightboxChangeReason, type LightboxClose, type LightboxContent, type LightboxControl, type LightboxControlContext, type LightboxCounter, type LightboxDownload, type LightboxImageChangeEventDetails, type LightboxImageItem, type LightboxItem, type LightboxMore, type LightboxNext, type LightboxOpenEventDetails, type LightboxPrevious, type LightboxProps, type LightboxSlide, type LightboxSlides, type LightboxThumbnail, type LightboxThumbnails, type LightboxToolbar, type LightboxTrigger, type LightboxTriggerProps, type LightboxVideoItem, type LightboxViewport, type LightboxZoom, type LightboxZoomButton, Pagination, ScrollArea, Separator, Sidebar, Skeleton, Spinner, Switch, Textarea, TimePicker, Toaster as ToastProvider, createAlertDialogHandle, createComboboxItems, createDialogHandle, createDrawerHandle, formatSize, toast, type useLightbox, useSidebar };
190
+ export { type AlertDialogHandle, Badge, Button, Carousel, Checkbox, type ComboboxItemCollection, type ComboboxPrimitiveValue, type CreateComboboxItemsOptions, DatePicker, type DateRange, DateRangePicker, type DialogHandle, type DrawerHandle, Dropzone, FileTree, type FileTreeNode, Form, IconSwap, Image, ImageProps, Input, JsonViewer, Label, Lightbox, type LightboxActions, type LightboxBackdrop, type LightboxCaptions, type LightboxChangeReason, type LightboxClose, type LightboxContent, type LightboxControl, type LightboxControlContext, type LightboxCounter, type LightboxDownload, type LightboxImageChangeEventDetails, type LightboxImageItem, type LightboxItem, type LightboxMore, type LightboxNext, type LightboxOpenEventDetails, type LightboxPrevious, type LightboxProps, type LightboxSlide, type LightboxSlides, type LightboxThumbnail, type LightboxThumbnails, type LightboxToolbar, type LightboxTrigger, type LightboxTriggerProps, type LightboxVideoItem, type LightboxViewport, type LightboxZoom, type LightboxZoomButton, Pagination, ScrollArea, Separator, Sidebar, Skeleton, Spinner, Switch, Textarea, TimePicker, Toaster as ToastProvider, createAlertDialogHandle, createComboboxItems, createDialogHandle, createDrawerHandle, formatSize, toast, type useLightbox, useSidebar };
@@ -32,12 +32,14 @@ import { DatePicker, DateRangePicker, TimePicker } from "./datetime.mjs";
32
32
  import { Dropzone, formatSize } from "./dropzone.mjs";
33
33
  import { Carousel, Image, Lightbox } from "./multimedia.mjs";
34
34
  import { Pagination } from "./pagination.mjs";
35
+ import { Form } from "../vendor/coss/form.mjs";
35
36
  import { Badge } from "../vendor/shadcn/badge.mjs";
36
37
  import { Switch } from "../vendor/shadcn/switch.mjs";
37
38
  import { Checkbox } from "../vendor/shadcn/checkbox.mjs";
38
39
  import { ScrollArea } from "../vendor/coss/scroll-area.mjs";
39
- import { Form } from "../vendor/coss/form.mjs";
40
40
  import { IconSwap } from "../vendor/tailored/icon-swap.mjs";
41
+ import { JsonViewer } from "../vendor/tailored/json-viewer.mjs";
42
+ import { FileTree } from "../vendor/tailored/file-tree.mjs";
41
43
  // src/widgets/index.ts
42
44
  const Kbd = /* @__PURE__ */ Object.assign(Kbd$1, { Group: KbdGroup });
43
45
  const Tabs = /* @__PURE__ */ Object.assign(Tabs$1, {
@@ -177,4 +179,4 @@ const InputGroup = /* @__PURE__ */ Object.assign(InputGroup$1, {
177
179
  Textarea: InputGroupTextarea,
178
180
  Button: InputGroupButton
179
181
  });
180
- export { AlertDialog, Badge, Breadcrumb, Button, ButtonGroup, Calendar, Card, Carousel, Checkbox, Combobox, DatePicker, DateRangePicker, Dialog, Drawer, Dropdown, Dropzone, Empty, Field, Form, IconSwap, Image, Input, InputGroup, Kbd, Label, Lightbox, Pagination, Popover, Progress, Radio, ScrollArea, Select, Separator, Sidebar, Skeleton, Spinner, Switch, Table, Tabs, Textarea, TimePicker, Toaster as ToastProvider, Tooltip, createAlertDialogHandle, createComboboxItems, createDialogHandle, createDrawerHandle, formatSize, toast, useSidebar };
182
+ export { AlertDialog, Badge, Breadcrumb, Button, ButtonGroup, Calendar, Card, Carousel, Checkbox, Combobox, DatePicker, DateRangePicker, Dialog, Drawer, Dropdown, Dropzone, Empty, Field, FileTree, Form, IconSwap, Image, Input, InputGroup, JsonViewer, Kbd, Label, Lightbox, Pagination, Popover, Progress, Radio, ScrollArea, Select, Separator, Sidebar, Skeleton, Spinner, Switch, Table, Tabs, Textarea, TimePicker, Toaster as ToastProvider, Tooltip, createAlertDialogHandle, createComboboxItems, createDialogHandle, createDrawerHandle, formatSize, toast, useSidebar };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@deftui/react",
3
- "version": "0.1.2",
3
+ "version": "0.1.4",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "git+https://github.com/KiriRainCat/deftui.git",
@@ -33,28 +33,29 @@
33
33
  "fix": "vp check --fix"
34
34
  },
35
35
  "dependencies": {
36
- "@base-ui/react": "^1.9.0",
37
36
  "@daypicker/react": "^10.0.2",
38
37
  "@fontsource-variable/inter": "^5.3.0",
39
38
  "class-variance-authority": "^0.7.1",
40
- "cn": "^0.5.0",
39
+ "cn": "^0.5.1",
41
40
  "date-fns": "^4.4.0",
42
41
  "embla-carousel-react": "^8.6.0",
43
- "lucide-react": "^1.62.0",
44
- "motion": "^14.7.1",
45
- "radashi": "^12.9.7",
42
+ "lucide-react": "^1.66.0",
43
+ "motion": "^14.9.0",
44
+ "radashi": "^13.0.1",
46
45
  "react-dropzone": "^20.1.4",
47
46
  "tw-animate-css": "^1.4.0"
48
47
  },
49
48
  "devDependencies": {
49
+ "@base-ui/react": "^1.9.0",
50
50
  "@types/react": "^19.3.0",
51
51
  "@types/react-dom": "^19.3.0",
52
- "shadcn": "^4.22.2",
52
+ "shadcn": "^4.23.0",
53
53
  "vite-plus": "catalog:"
54
54
  },
55
55
  "peerDependencies": {
56
- "react": "^19",
57
- "react-dom": "^19",
58
- "tailwindcss": "^4"
56
+ "@base-ui/react": "^1.9.0",
57
+ "react": "^19.0.0",
58
+ "react-dom": "^19.0.0",
59
+ "tailwindcss": "^4.3.0"
59
60
  }
60
61
  }