@gouno/ui 0.3.2
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/CHANGELOG.md +120 -0
- package/LICENSE +21 -0
- package/README.md +144 -0
- package/dist/base.css +239 -0
- package/dist/bootstrap.js +22 -0
- package/dist/components/primitives/alert-dialog.d.ts +18 -0
- package/dist/components/primitives/alert-dialog.js +43 -0
- package/dist/components/primitives/alert.d.ts +9 -0
- package/dist/components/primitives/alert.js +25 -0
- package/dist/components/primitives/avatar.d.ts +14 -0
- package/dist/components/primitives/avatar.js +42 -0
- package/dist/components/primitives/badge.d.ts +9 -0
- package/dist/components/primitives/badge.js +25 -0
- package/dist/components/primitives/button.d.ts +10 -0
- package/dist/components/primitives/button.js +36 -0
- package/dist/components/primitives/checkbox.d.ts +4 -0
- package/dist/components/primitives/checkbox.js +10 -0
- package/dist/components/primitives/dialog.d.ts +20 -0
- package/dist/components/primitives/dialog.js +37 -0
- package/dist/components/primitives/dropdown-menu.d.ts +25 -0
- package/dist/components/primitives/dropdown-menu.js +51 -0
- package/dist/components/primitives/empty.d.ts +11 -0
- package/dist/components/primitives/empty.js +33 -0
- package/dist/components/primitives/field.d.ts +24 -0
- package/dist/components/primitives/field.js +75 -0
- package/dist/components/primitives/input.d.ts +3 -0
- package/dist/components/primitives/input.js +7 -0
- package/dist/components/primitives/label.d.ts +4 -0
- package/dist/components/primitives/label.js +8 -0
- package/dist/components/primitives/popover.d.ts +16 -0
- package/dist/components/primitives/popover.js +31 -0
- package/dist/components/primitives/radio-group.d.ts +5 -0
- package/dist/components/primitives/radio-group.js +13 -0
- package/dist/components/primitives/select.d.ts +15 -0
- package/dist/components/primitives/select.js +38 -0
- package/dist/components/primitives/separator.d.ts +4 -0
- package/dist/components/primitives/separator.js +8 -0
- package/dist/components/primitives/sheet.d.ts +17 -0
- package/dist/components/primitives/sheet.js +41 -0
- package/dist/components/primitives/skeleton.d.ts +2 -0
- package/dist/components/primitives/skeleton.js +6 -0
- package/dist/components/primitives/switch.d.ts +6 -0
- package/dist/components/primitives/switch.js +8 -0
- package/dist/components/primitives/table.d.ts +28 -0
- package/dist/components/primitives/table.js +44 -0
- package/dist/components/primitives/tabs.d.ts +11 -0
- package/dist/components/primitives/tabs.js +30 -0
- package/dist/components/primitives/textarea.d.ts +3 -0
- package/dist/components/primitives/textarea.js +7 -0
- package/dist/components/primitives/tooltip.d.ts +13 -0
- package/dist/components/primitives/tooltip.js +24 -0
- package/dist/core/affix.d.ts +14 -0
- package/dist/core/affix.js +29 -0
- package/dist/core/alert.d.ts +54 -0
- package/dist/core/alert.js +56 -0
- package/dist/core/anchor.d.ts +16 -0
- package/dist/core/anchor.js +31 -0
- package/dist/core/app.d.ts +5 -0
- package/dist/core/app.js +5 -0
- package/dist/core/aspect-ratio.d.ts +6 -0
- package/dist/core/aspect-ratio.js +6 -0
- package/dist/core/autocomplete.d.ts +24 -0
- package/dist/core/autocomplete.js +139 -0
- package/dist/core/avatar.d.ts +26 -0
- package/dist/core/avatar.js +20 -0
- package/dist/core/badge.d.ts +16 -0
- package/dist/core/badge.js +22 -0
- package/dist/core/breadcrumb.d.ts +55 -0
- package/dist/core/breadcrumb.js +69 -0
- package/dist/core/button.d.ts +49 -0
- package/dist/core/button.js +66 -0
- package/dist/core/calendar.d.ts +48 -0
- package/dist/core/calendar.js +179 -0
- package/dist/core/card.d.ts +30 -0
- package/dist/core/card.js +12 -0
- package/dist/core/carousel.d.ts +49 -0
- package/dist/core/carousel.js +166 -0
- package/dist/core/cascader.d.ts +19 -0
- package/dist/core/cascader.js +45 -0
- package/dist/core/code-block.d.ts +16 -0
- package/dist/core/code-block.js +47 -0
- package/dist/core/collapse.d.ts +49 -0
- package/dist/core/collapse.js +96 -0
- package/dist/core/color-picker.d.ts +9 -0
- package/dist/core/color-picker.js +8 -0
- package/dist/core/config-provider.d.ts +10 -0
- package/dist/core/config-provider.js +14 -0
- package/dist/core/control-types.d.ts +2 -0
- package/dist/core/control-types.js +7 -0
- package/dist/core/date-picker.d.ts +13 -0
- package/dist/core/date-picker.js +14 -0
- package/dist/core/date-range-picker.d.ts +26 -0
- package/dist/core/date-range-picker.js +10 -0
- package/dist/core/descriptions.d.ts +41 -0
- package/dist/core/descriptions.js +133 -0
- package/dist/core/drawer.d.ts +9 -0
- package/dist/core/drawer.js +58 -0
- package/dist/core/empty.d.ts +8 -0
- package/dist/core/empty.js +5 -0
- package/dist/core/feedback.d.ts +5 -0
- package/dist/core/feedback.js +5 -0
- package/dist/core/field.d.ts +15 -0
- package/dist/core/field.js +19 -0
- package/dist/core/float-button.d.ts +13 -0
- package/dist/core/float-button.js +22 -0
- package/dist/core/form.d.ts +19 -0
- package/dist/core/form.js +56 -0
- package/dist/core/icon-button.d.ts +7 -0
- package/dist/core/icon-button.js +4 -0
- package/dist/core/icon.d.ts +17 -0
- package/dist/core/icon.js +35 -0
- package/dist/core/image.d.ts +76 -0
- package/dist/core/image.js +189 -0
- package/dist/core/index.d.ts +79 -0
- package/dist/core/index.js +77 -0
- package/dist/core/input-number.d.ts +24 -0
- package/dist/core/input-number.js +70 -0
- package/dist/core/input-otp.d.ts +14 -0
- package/dist/core/input-otp.js +76 -0
- package/dist/core/input.d.ts +14 -0
- package/dist/core/input.js +51 -0
- package/dist/core/kbd.d.ts +8 -0
- package/dist/core/kbd.js +11 -0
- package/dist/core/layout-primitives.d.ts +11 -0
- package/dist/core/layout-primitives.js +8 -0
- package/dist/core/layout.d.ts +71 -0
- package/dist/core/layout.js +119 -0
- package/dist/core/list.d.ts +37 -0
- package/dist/core/list.js +35 -0
- package/dist/core/locale.d.ts +48 -0
- package/dist/core/locale.js +32 -0
- package/dist/core/mentions.d.ts +10 -0
- package/dist/core/mentions.js +112 -0
- package/dist/core/menu.d.ts +79 -0
- package/dist/core/menu.js +265 -0
- package/dist/core/message.d.ts +16 -0
- package/dist/core/message.js +36 -0
- package/dist/core/modal.d.ts +40 -0
- package/dist/core/modal.js +67 -0
- package/dist/core/notification.d.ts +15 -0
- package/dist/core/notification.js +39 -0
- package/dist/core/page-layout.d.ts +6 -0
- package/dist/core/page-layout.js +7 -0
- package/dist/core/pagination.d.ts +28 -0
- package/dist/core/pagination.js +69 -0
- package/dist/core/popconfirm.d.ts +13 -0
- package/dist/core/popconfirm.js +42 -0
- package/dist/core/progress.d.ts +6 -0
- package/dist/core/progress.js +8 -0
- package/dist/core/public-props.d.ts +82 -0
- package/dist/core/public-props.js +1 -0
- package/dist/core/qrcode.d.ts +14 -0
- package/dist/core/qrcode.js +17 -0
- package/dist/core/rate.d.ts +11 -0
- package/dist/core/rate.js +48 -0
- package/dist/core/result.d.ts +12 -0
- package/dist/core/result.js +19 -0
- package/dist/core/segmented.d.ts +29 -0
- package/dist/core/segmented.js +38 -0
- package/dist/core/select.d.ts +27 -0
- package/dist/core/select.js +143 -0
- package/dist/core/selection-controls.d.ts +12 -0
- package/dist/core/selection-controls.js +23 -0
- package/dist/core/separator.d.ts +24 -0
- package/dist/core/separator.js +17 -0
- package/dist/core/slider.d.ts +2 -0
- package/dist/core/slider.js +6 -0
- package/dist/core/spin.d.ts +7 -0
- package/dist/core/spin.js +7 -0
- package/dist/core/spinner.d.ts +3 -0
- package/dist/core/spinner.js +6 -0
- package/dist/core/splitter.d.ts +44 -0
- package/dist/core/splitter.js +208 -0
- package/dist/core/statistic.d.ts +12 -0
- package/dist/core/statistic.js +6 -0
- package/dist/core/steps.d.ts +54 -0
- package/dist/core/steps.js +106 -0
- package/dist/core/tabs.d.ts +40 -0
- package/dist/core/tabs.js +80 -0
- package/dist/core/tag.d.ts +19 -0
- package/dist/core/tag.js +29 -0
- package/dist/core/textarea.d.ts +8 -0
- package/dist/core/textarea.js +14 -0
- package/dist/core/time-picker.d.ts +9 -0
- package/dist/core/time-picker.js +8 -0
- package/dist/core/timeline.d.ts +37 -0
- package/dist/core/timeline.js +48 -0
- package/dist/core/tour.d.ts +18 -0
- package/dist/core/tour.js +51 -0
- package/dist/core/transfer.d.ts +16 -0
- package/dist/core/transfer.js +46 -0
- package/dist/core/tree-select.d.ts +19 -0
- package/dist/core/tree-select.js +19 -0
- package/dist/core/tree.d.ts +89 -0
- package/dist/core/tree.js +386 -0
- package/dist/core/typography.d.ts +20 -0
- package/dist/core/typography.js +12 -0
- package/dist/core/upload.d.ts +20 -0
- package/dist/core/upload.js +72 -0
- package/dist/core/watermark.d.ts +10 -0
- package/dist/core/watermark.js +21 -0
- package/dist/fonts/inter-LICENSE +93 -0
- package/dist/fonts/inter-latin-wght-normal.woff2 +0 -0
- package/dist/fonts/jetbrains-mono-LICENSE +93 -0
- package/dist/fonts/jetbrains-mono-latin-400-normal.woff2 +0 -0
- package/dist/gouno/app-shell.d.ts +22 -0
- package/dist/gouno/app-shell.js +27 -0
- package/dist/gouno/index.d.ts +4 -0
- package/dist/gouno/index.js +4 -0
- package/dist/gouno/page-container.d.ts +4 -0
- package/dist/gouno/page-container.js +6 -0
- package/dist/gouno/page-header.d.ts +8 -0
- package/dist/gouno/page-header.js +6 -0
- package/dist/hooks/use-overlay-body.d.ts +6 -0
- package/dist/hooks/use-overlay-body.js +18 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.js +5 -0
- package/dist/lib/utils.d.ts +2 -0
- package/dist/lib/utils.js +5 -0
- package/dist/patterns/bulk-action-bar.d.ts +8 -0
- package/dist/patterns/bulk-action-bar.js +7 -0
- package/dist/patterns/index.d.ts +5 -0
- package/dist/patterns/index.js +5 -0
- package/dist/theme/index.d.ts +2 -0
- package/dist/theme/index.js +2 -0
- package/dist/theme/provider.d.ts +22 -0
- package/dist/theme/provider.js +76 -0
- package/dist/theme/theme-toggle.d.ts +6 -0
- package/dist/theme/theme-toggle.js +10 -0
- package/dist/tokens.css +164 -0
- package/package.json +111 -0
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { type ChangeEvent, type CSSProperties, type HTMLAttributes, type Key, type KeyboardEvent, type MouseEvent, type ReactNode, type Ref } from "react";
|
|
2
|
+
export interface TreeNode {
|
|
3
|
+
key: Key;
|
|
4
|
+
title: ReactNode;
|
|
5
|
+
children?: readonly TreeNode[];
|
|
6
|
+
disabled?: boolean;
|
|
7
|
+
selectable?: boolean;
|
|
8
|
+
checkable?: boolean;
|
|
9
|
+
disableCheckbox?: boolean;
|
|
10
|
+
icon?: ReactNode | ((info: TreeNodeRenderInfo) => ReactNode);
|
|
11
|
+
isLeaf?: boolean;
|
|
12
|
+
}
|
|
13
|
+
export interface TreeNodeRenderInfo {
|
|
14
|
+
node: TreeNode;
|
|
15
|
+
expanded: boolean;
|
|
16
|
+
selected: boolean;
|
|
17
|
+
checked: boolean;
|
|
18
|
+
halfChecked: boolean;
|
|
19
|
+
loading: boolean;
|
|
20
|
+
disabled: boolean;
|
|
21
|
+
}
|
|
22
|
+
export interface TreeExpandInfo {
|
|
23
|
+
expanded: boolean;
|
|
24
|
+
node: TreeNode;
|
|
25
|
+
}
|
|
26
|
+
export interface TreeSelectInfo {
|
|
27
|
+
selected: boolean;
|
|
28
|
+
selectedNodes: TreeNode[];
|
|
29
|
+
node: TreeNode;
|
|
30
|
+
nativeEvent: MouseEvent<HTMLDivElement> | KeyboardEvent<HTMLDivElement>;
|
|
31
|
+
}
|
|
32
|
+
export interface TreeCheckInfo {
|
|
33
|
+
checked: boolean;
|
|
34
|
+
checkedNodes: TreeNode[];
|
|
35
|
+
halfCheckedKeys: Key[];
|
|
36
|
+
node: TreeNode;
|
|
37
|
+
nativeEvent: ChangeEvent<HTMLInputElement> | KeyboardEvent<HTMLDivElement>;
|
|
38
|
+
}
|
|
39
|
+
export type TreeCheckedKeys = readonly Key[] | {
|
|
40
|
+
checked: readonly Key[];
|
|
41
|
+
halfChecked: readonly Key[];
|
|
42
|
+
};
|
|
43
|
+
export type TreeSemantic = "root" | "item" | "switcher" | "checkbox" | "icon" | "title" | "group";
|
|
44
|
+
export interface TreeSemanticInfo {
|
|
45
|
+
props: Readonly<{
|
|
46
|
+
checkable: boolean;
|
|
47
|
+
multiple: boolean;
|
|
48
|
+
disabled: boolean;
|
|
49
|
+
showLine: boolean;
|
|
50
|
+
blockNode: boolean;
|
|
51
|
+
}>;
|
|
52
|
+
}
|
|
53
|
+
export type TreeClassNames = Partial<Record<TreeSemantic, string>> | ((info: TreeSemanticInfo) => Partial<Record<TreeSemantic, string>>);
|
|
54
|
+
export type TreeStyles = Partial<Record<TreeSemantic, CSSProperties>> | ((info: TreeSemanticInfo) => Partial<Record<TreeSemantic, CSSProperties>>);
|
|
55
|
+
export interface TreeProps extends Omit<HTMLAttributes<HTMLDivElement>, "children" | "onLoad" | "onSelect"> {
|
|
56
|
+
treeData: readonly TreeNode[];
|
|
57
|
+
expandedKeys?: readonly Key[];
|
|
58
|
+
defaultExpandedKeys?: readonly Key[];
|
|
59
|
+
defaultExpandAll?: boolean;
|
|
60
|
+
selectedKeys?: readonly Key[];
|
|
61
|
+
defaultSelectedKeys?: readonly Key[];
|
|
62
|
+
checkedKeys?: TreeCheckedKeys;
|
|
63
|
+
defaultCheckedKeys?: readonly Key[];
|
|
64
|
+
loadedKeys?: readonly Key[];
|
|
65
|
+
checkable?: boolean;
|
|
66
|
+
checkStrictly?: boolean;
|
|
67
|
+
multiple?: boolean;
|
|
68
|
+
selectable?: boolean;
|
|
69
|
+
disabled?: boolean;
|
|
70
|
+
blockNode?: boolean;
|
|
71
|
+
showIcon?: boolean;
|
|
72
|
+
showLine?: boolean;
|
|
73
|
+
icon?: ReactNode | ((info: TreeNodeRenderInfo) => ReactNode);
|
|
74
|
+
switcherIcon?: ReactNode | ((info: TreeNodeRenderInfo) => ReactNode);
|
|
75
|
+
switcherLoadingIcon?: ReactNode;
|
|
76
|
+
titleRender?: (node: TreeNode) => ReactNode;
|
|
77
|
+
filterTreeNode?: (node: TreeNode) => boolean;
|
|
78
|
+
loadData?: (node: TreeNode) => Promise<void>;
|
|
79
|
+
onExpand?: (expandedKeys: Key[], info: TreeExpandInfo) => void;
|
|
80
|
+
onSelect?: (selectedKeys: Key[], info: TreeSelectInfo) => void;
|
|
81
|
+
onCheck?: (checkedKeys: TreeCheckedKeys, info: TreeCheckInfo) => void;
|
|
82
|
+
onLoad?: (loadedKeys: Key[], info: {
|
|
83
|
+
node: TreeNode;
|
|
84
|
+
}) => void;
|
|
85
|
+
classNames?: TreeClassNames;
|
|
86
|
+
styles?: TreeStyles;
|
|
87
|
+
ref?: Ref<HTMLDivElement>;
|
|
88
|
+
}
|
|
89
|
+
export declare function Tree({ treeData, expandedKeys, defaultExpandedKeys, defaultExpandAll, selectedKeys, defaultSelectedKeys, checkedKeys, defaultCheckedKeys, loadedKeys, checkable, checkStrictly, multiple, selectable, disabled, blockNode, showIcon, showLine, icon, switcherIcon, switcherLoadingIcon, titleRender, filterTreeNode, loadData, onExpand, onSelect, onCheck, onLoad, classNames, styles, className, style, ref, ...props }: TreeProps): import("react").JSX.Element;
|
|
@@ -0,0 +1,386 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useMemo, useRef, useState, } from "react";
|
|
3
|
+
import { ChevronDown, ChevronRight } from "lucide-react";
|
|
4
|
+
import { cn } from "../lib/utils.js";
|
|
5
|
+
import { Button } from "./button.js";
|
|
6
|
+
import { Spinner } from "./spinner.js";
|
|
7
|
+
function keyToken(key) {
|
|
8
|
+
return `${typeof key === "number" ? "n" : "s"}:${String(key)}`;
|
|
9
|
+
}
|
|
10
|
+
function keyIncludes(keys, key) {
|
|
11
|
+
return keys.some((candidate) => candidate === key);
|
|
12
|
+
}
|
|
13
|
+
function flattenAll(nodes, parentKey, level = 1) {
|
|
14
|
+
return nodes.flatMap((node, index) => [
|
|
15
|
+
{ node, parentKey, level, index },
|
|
16
|
+
...flattenAll(node.children ?? [], node.key, level + 1),
|
|
17
|
+
]);
|
|
18
|
+
}
|
|
19
|
+
function flattenVisible(nodes, expanded, parentKey, level = 1) {
|
|
20
|
+
return nodes.flatMap((node, index) => {
|
|
21
|
+
const entry = { node, parentKey, level, index };
|
|
22
|
+
return [
|
|
23
|
+
entry,
|
|
24
|
+
...(keyIncludes(expanded, node.key)
|
|
25
|
+
? flattenVisible(node.children ?? [], expanded, node.key, level + 1)
|
|
26
|
+
: []),
|
|
27
|
+
];
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
function branchKeys(nodes) {
|
|
31
|
+
return flattenAll(nodes)
|
|
32
|
+
.filter(({ node }) => Boolean(node.children?.length) || node.isLeaf === false)
|
|
33
|
+
.map(({ node }) => node.key);
|
|
34
|
+
}
|
|
35
|
+
function uniqueKeys(keys) {
|
|
36
|
+
return Array.from(new Set(keys));
|
|
37
|
+
}
|
|
38
|
+
function isStrictCheckedKeys(value) {
|
|
39
|
+
return !Array.isArray(value);
|
|
40
|
+
}
|
|
41
|
+
function rawCheckedKeys(value) {
|
|
42
|
+
if (!value)
|
|
43
|
+
return [];
|
|
44
|
+
return isStrictCheckedKeys(value) ? value.checked : value;
|
|
45
|
+
}
|
|
46
|
+
function checkedNodesFor(keys, entries) {
|
|
47
|
+
const set = new Set(keys);
|
|
48
|
+
return entries.filter(({ node }) => set.has(node.key)).map(({ node }) => node);
|
|
49
|
+
}
|
|
50
|
+
function resolveNodeIcon(source, info) {
|
|
51
|
+
return typeof source === "function" ? source(info) : source;
|
|
52
|
+
}
|
|
53
|
+
function resolveSwitcherIcon(source, info) {
|
|
54
|
+
if (source)
|
|
55
|
+
return typeof source === "function" ? source(info) : source;
|
|
56
|
+
return info.expanded ? (_jsx(ChevronDown, { "aria-hidden": "true" })) : (_jsx(ChevronRight, { "aria-hidden": "true" }));
|
|
57
|
+
}
|
|
58
|
+
function titleId(key, level, index) {
|
|
59
|
+
const safeKey = keyToken(key).replace(/[^a-zA-Z0-9_-]/g, "-");
|
|
60
|
+
return `gouno-tree-title-${level}-${index}-${safeKey}`;
|
|
61
|
+
}
|
|
62
|
+
export function Tree({ treeData, expandedKeys, defaultExpandedKeys = [], defaultExpandAll = false, selectedKeys, defaultSelectedKeys = [], checkedKeys, defaultCheckedKeys = [], loadedKeys, checkable = false, checkStrictly = false, multiple = false, selectable = true, disabled = false, blockNode = false, showIcon = false, showLine = false, icon, switcherIcon, switcherLoadingIcon, titleRender, filterTreeNode, loadData, onExpand, onSelect, onCheck, onLoad, classNames, styles, className, style, ref, ...props }) {
|
|
63
|
+
const rootRef = useRef(null);
|
|
64
|
+
const allEntries = useMemo(() => flattenAll(treeData), [treeData]);
|
|
65
|
+
const entryByKey = useMemo(() => new Map(allEntries.map((entry) => [entry.node.key, entry])), [allEntries]);
|
|
66
|
+
const orderByKey = useMemo(() => new Map(allEntries.map((entry, index) => [entry.node.key, index])), [allEntries]);
|
|
67
|
+
const [internalExpandedKeys, setInternalExpandedKeys] = useState(() => uniqueKeys(defaultExpandAll
|
|
68
|
+
? [...branchKeys(treeData), ...defaultExpandedKeys]
|
|
69
|
+
: defaultExpandedKeys));
|
|
70
|
+
const activeExpandedKeys = expandedKeys ?? internalExpandedKeys;
|
|
71
|
+
const [internalSelectedKeys, setInternalSelectedKeys] = useState(() => uniqueKeys(defaultSelectedKeys));
|
|
72
|
+
const activeSelectedKeys = selectedKeys ?? internalSelectedKeys;
|
|
73
|
+
const [internalCheckedKeys, setInternalCheckedKeys] = useState(() => uniqueKeys(defaultCheckedKeys));
|
|
74
|
+
const incomingCheckedKeys = checkedKeys
|
|
75
|
+
? rawCheckedKeys(checkedKeys)
|
|
76
|
+
: internalCheckedKeys;
|
|
77
|
+
const [internalLoadedKeys, setInternalLoadedKeys] = useState([]);
|
|
78
|
+
const activeLoadedKeys = loadedKeys ?? internalLoadedKeys;
|
|
79
|
+
const [loadingKeys, setLoadingKeys] = useState([]);
|
|
80
|
+
const semanticInfo = {
|
|
81
|
+
props: { checkable, multiple, disabled, showLine, blockNode },
|
|
82
|
+
};
|
|
83
|
+
const semanticClassNames = typeof classNames === "function" ? classNames(semanticInfo) : (classNames ?? {});
|
|
84
|
+
const semanticStyles = typeof styles === "function" ? styles(semanticInfo) : (styles ?? {});
|
|
85
|
+
const conductedCheckedKeys = useMemo(() => {
|
|
86
|
+
if (checkStrictly)
|
|
87
|
+
return uniqueKeys(incomingCheckedKeys);
|
|
88
|
+
const checked = new Set();
|
|
89
|
+
const addDescendants = (node) => {
|
|
90
|
+
if (!node.disabled && !node.disableCheckbox && node.checkable !== false) {
|
|
91
|
+
checked.add(node.key);
|
|
92
|
+
}
|
|
93
|
+
for (const child of node.children ?? [])
|
|
94
|
+
addDescendants(child);
|
|
95
|
+
};
|
|
96
|
+
for (const key of incomingCheckedKeys) {
|
|
97
|
+
const entry = entryByKey.get(key);
|
|
98
|
+
if (entry)
|
|
99
|
+
addDescendants(entry.node);
|
|
100
|
+
}
|
|
101
|
+
for (const { node } of [...allEntries].reverse()) {
|
|
102
|
+
if (node.disabled || node.disableCheckbox || node.checkable === false)
|
|
103
|
+
continue;
|
|
104
|
+
const eligibleChildren = (node.children ?? []).filter((child) => !child.disabled && !child.disableCheckbox && child.checkable !== false);
|
|
105
|
+
if (eligibleChildren.length &&
|
|
106
|
+
eligibleChildren.every((child) => checked.has(child.key))) {
|
|
107
|
+
checked.add(node.key);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
return allEntries
|
|
111
|
+
.filter(({ node }) => checked.has(node.key))
|
|
112
|
+
.map(({ node }) => node.key);
|
|
113
|
+
}, [allEntries, checkStrictly, entryByKey, incomingCheckedKeys]);
|
|
114
|
+
const halfCheckedKeys = useMemo(() => {
|
|
115
|
+
if (checkStrictly) {
|
|
116
|
+
return checkedKeys && isStrictCheckedKeys(checkedKeys)
|
|
117
|
+
? [...checkedKeys.halfChecked]
|
|
118
|
+
: [];
|
|
119
|
+
}
|
|
120
|
+
const checked = new Set(conductedCheckedKeys);
|
|
121
|
+
const half = new Set();
|
|
122
|
+
for (const { node } of [...allEntries].reverse()) {
|
|
123
|
+
if (checked.has(node.key))
|
|
124
|
+
continue;
|
|
125
|
+
const children = (node.children ?? []).filter((child) => !child.disabled && !child.disableCheckbox && child.checkable !== false);
|
|
126
|
+
if (children.some((child) => checked.has(child.key) || half.has(child.key))) {
|
|
127
|
+
half.add(node.key);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
return allEntries
|
|
131
|
+
.filter(({ node }) => half.has(node.key))
|
|
132
|
+
.map(({ node }) => node.key);
|
|
133
|
+
}, [allEntries, checkStrictly, checkedKeys, conductedCheckedKeys]);
|
|
134
|
+
const visibleEntries = useMemo(() => flattenVisible(treeData, activeExpandedKeys), [activeExpandedKeys, treeData]);
|
|
135
|
+
const initialFocusKey = activeSelectedKeys.find((key) => visibleEntries.some(({ node }) => node.key === key)) ?? visibleEntries[0]?.node.key;
|
|
136
|
+
const [focusKey, setFocusKey] = useState(initialFocusKey);
|
|
137
|
+
const setRootRef = (node) => {
|
|
138
|
+
rootRef.current = node;
|
|
139
|
+
if (typeof ref === "function")
|
|
140
|
+
ref(node);
|
|
141
|
+
else if (ref)
|
|
142
|
+
ref.current = node;
|
|
143
|
+
};
|
|
144
|
+
const focusNode = (key) => {
|
|
145
|
+
if (key === undefined)
|
|
146
|
+
return;
|
|
147
|
+
setFocusKey(key);
|
|
148
|
+
queueMicrotask(() => {
|
|
149
|
+
const token = keyToken(key);
|
|
150
|
+
const candidates = rootRef.current?.querySelectorAll("[data-tree-key]") ?? [];
|
|
151
|
+
Array.from(candidates)
|
|
152
|
+
.find((element) => element.dataset.treeKey === token)
|
|
153
|
+
?.focus();
|
|
154
|
+
});
|
|
155
|
+
};
|
|
156
|
+
const sortKeys = (keys) => Array.from(new Set(keys)).sort((a, b) => (orderByKey.get(a) ?? Number.MAX_SAFE_INTEGER) -
|
|
157
|
+
(orderByKey.get(b) ?? Number.MAX_SAFE_INTEGER));
|
|
158
|
+
const requestLoad = async (node) => {
|
|
159
|
+
if (!loadData ||
|
|
160
|
+
node.isLeaf === true ||
|
|
161
|
+
keyIncludes(activeLoadedKeys, node.key) ||
|
|
162
|
+
keyIncludes(loadingKeys, node.key)) {
|
|
163
|
+
return;
|
|
164
|
+
}
|
|
165
|
+
setLoadingKeys((keys) => uniqueKeys([...keys, node.key]));
|
|
166
|
+
try {
|
|
167
|
+
await loadData(node);
|
|
168
|
+
const nextLoadedKeys = sortKeys([...activeLoadedKeys, node.key]);
|
|
169
|
+
if (loadedKeys === undefined)
|
|
170
|
+
setInternalLoadedKeys(nextLoadedKeys);
|
|
171
|
+
onLoad?.(nextLoadedKeys, { node });
|
|
172
|
+
}
|
|
173
|
+
finally {
|
|
174
|
+
setLoadingKeys((keys) => keys.filter((key) => key !== node.key));
|
|
175
|
+
}
|
|
176
|
+
};
|
|
177
|
+
const setExpanded = (node, expanded) => {
|
|
178
|
+
const nextKeys = expanded
|
|
179
|
+
? sortKeys([...activeExpandedKeys, node.key])
|
|
180
|
+
: activeExpandedKeys.filter((key) => key !== node.key);
|
|
181
|
+
if (expandedKeys === undefined)
|
|
182
|
+
setInternalExpandedKeys(nextKeys);
|
|
183
|
+
onExpand?.(nextKeys, { expanded, node });
|
|
184
|
+
if (expanded)
|
|
185
|
+
void requestLoad(node);
|
|
186
|
+
};
|
|
187
|
+
const selectNode = (node, event) => {
|
|
188
|
+
const nodeDisabled = disabled || node.disabled;
|
|
189
|
+
if (nodeDisabled || !selectable || node.selectable === false)
|
|
190
|
+
return;
|
|
191
|
+
const currentlySelected = keyIncludes(activeSelectedKeys, node.key);
|
|
192
|
+
const additive = multiple && (event.metaKey || event.ctrlKey);
|
|
193
|
+
const nextKeys = !multiple || !additive
|
|
194
|
+
? currentlySelected && multiple
|
|
195
|
+
? []
|
|
196
|
+
: [node.key]
|
|
197
|
+
: currentlySelected
|
|
198
|
+
? activeSelectedKeys.filter((key) => key !== node.key)
|
|
199
|
+
: sortKeys([...activeSelectedKeys, node.key]);
|
|
200
|
+
if (selectedKeys === undefined)
|
|
201
|
+
setInternalSelectedKeys(nextKeys);
|
|
202
|
+
onSelect?.(nextKeys, {
|
|
203
|
+
selected: keyIncludes(nextKeys, node.key),
|
|
204
|
+
selectedNodes: checkedNodesFor(nextKeys, allEntries),
|
|
205
|
+
node,
|
|
206
|
+
nativeEvent: event,
|
|
207
|
+
});
|
|
208
|
+
};
|
|
209
|
+
const setChecked = (node, nextChecked, event) => {
|
|
210
|
+
const nodeDisabled = disabled || node.disabled || node.disableCheckbox || node.checkable === false;
|
|
211
|
+
if (nodeDisabled || !checkable)
|
|
212
|
+
return;
|
|
213
|
+
const current = new Set(conductedCheckedKeys);
|
|
214
|
+
if (checkStrictly) {
|
|
215
|
+
if (nextChecked)
|
|
216
|
+
current.add(node.key);
|
|
217
|
+
else
|
|
218
|
+
current.delete(node.key);
|
|
219
|
+
}
|
|
220
|
+
else {
|
|
221
|
+
const descendants = flattenAll([node]).map(({ node: child }) => child);
|
|
222
|
+
for (const child of descendants) {
|
|
223
|
+
if (child.disabled || child.disableCheckbox || child.checkable === false) {
|
|
224
|
+
continue;
|
|
225
|
+
}
|
|
226
|
+
if (nextChecked)
|
|
227
|
+
current.add(child.key);
|
|
228
|
+
else
|
|
229
|
+
current.delete(child.key);
|
|
230
|
+
}
|
|
231
|
+
let parentKey = entryByKey.get(node.key)?.parentKey;
|
|
232
|
+
while (parentKey !== undefined) {
|
|
233
|
+
current.delete(parentKey);
|
|
234
|
+
parentKey = entryByKey.get(parentKey)?.parentKey;
|
|
235
|
+
}
|
|
236
|
+
for (const { node: candidate } of [...allEntries].reverse()) {
|
|
237
|
+
if (candidate.disabled ||
|
|
238
|
+
candidate.disableCheckbox ||
|
|
239
|
+
candidate.checkable === false) {
|
|
240
|
+
continue;
|
|
241
|
+
}
|
|
242
|
+
const children = (candidate.children ?? []).filter((child) => !child.disabled && !child.disableCheckbox && child.checkable !== false);
|
|
243
|
+
if (children.length && children.every((child) => current.has(child.key))) {
|
|
244
|
+
current.add(candidate.key);
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
const nextKeys = sortKeys(current);
|
|
249
|
+
if (checkedKeys === undefined)
|
|
250
|
+
setInternalCheckedKeys(nextKeys);
|
|
251
|
+
const nextHalfKeys = checkStrictly
|
|
252
|
+
? checkedKeys && isStrictCheckedKeys(checkedKeys)
|
|
253
|
+
? [...checkedKeys.halfChecked]
|
|
254
|
+
: []
|
|
255
|
+
: (() => {
|
|
256
|
+
const checked = new Set(nextKeys);
|
|
257
|
+
const half = new Set();
|
|
258
|
+
for (const { node: candidate } of [...allEntries].reverse()) {
|
|
259
|
+
if (checked.has(candidate.key))
|
|
260
|
+
continue;
|
|
261
|
+
const children = (candidate.children ?? []).filter((child) => !child.disabled &&
|
|
262
|
+
!child.disableCheckbox &&
|
|
263
|
+
child.checkable !== false);
|
|
264
|
+
if (children.some((child) => checked.has(child.key) || half.has(child.key))) {
|
|
265
|
+
half.add(candidate.key);
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
return sortKeys(half);
|
|
269
|
+
})();
|
|
270
|
+
const output = checkStrictly
|
|
271
|
+
? { checked: nextKeys, halfChecked: nextHalfKeys }
|
|
272
|
+
: nextKeys;
|
|
273
|
+
onCheck?.(output, {
|
|
274
|
+
checked: nextChecked,
|
|
275
|
+
checkedNodes: checkedNodesFor(nextKeys, allEntries),
|
|
276
|
+
halfCheckedKeys: nextHalfKeys,
|
|
277
|
+
node,
|
|
278
|
+
nativeEvent: event,
|
|
279
|
+
});
|
|
280
|
+
};
|
|
281
|
+
const handleKeyDown = (event, entry) => {
|
|
282
|
+
const { node, parentKey } = entry;
|
|
283
|
+
const visibleIndex = visibleEntries.findIndex(({ node: visibleNode }) => visibleNode.key === node.key);
|
|
284
|
+
const expanded = keyIncludes(activeExpandedKeys, node.key);
|
|
285
|
+
const hasChildren = Boolean(node.children?.length) || (Boolean(loadData) && node.isLeaf !== true);
|
|
286
|
+
if (event.key === "ArrowDown") {
|
|
287
|
+
event.preventDefault();
|
|
288
|
+
focusNode(visibleEntries[Math.min(visibleEntries.length - 1, visibleIndex + 1)]
|
|
289
|
+
?.node.key);
|
|
290
|
+
return;
|
|
291
|
+
}
|
|
292
|
+
if (event.key === "ArrowUp") {
|
|
293
|
+
event.preventDefault();
|
|
294
|
+
focusNode(visibleEntries[Math.max(0, visibleIndex - 1)]?.node.key);
|
|
295
|
+
return;
|
|
296
|
+
}
|
|
297
|
+
if (event.key === "Home") {
|
|
298
|
+
event.preventDefault();
|
|
299
|
+
focusNode(visibleEntries[0]?.node.key);
|
|
300
|
+
return;
|
|
301
|
+
}
|
|
302
|
+
if (event.key === "End") {
|
|
303
|
+
event.preventDefault();
|
|
304
|
+
focusNode(visibleEntries.at(-1)?.node.key);
|
|
305
|
+
return;
|
|
306
|
+
}
|
|
307
|
+
if (event.key === "ArrowRight" && hasChildren) {
|
|
308
|
+
event.preventDefault();
|
|
309
|
+
if (!expanded)
|
|
310
|
+
setExpanded(node, true);
|
|
311
|
+
else
|
|
312
|
+
focusNode(node.children?.[0]?.key);
|
|
313
|
+
return;
|
|
314
|
+
}
|
|
315
|
+
if (event.key === "ArrowLeft") {
|
|
316
|
+
if (expanded && hasChildren) {
|
|
317
|
+
event.preventDefault();
|
|
318
|
+
setExpanded(node, false);
|
|
319
|
+
}
|
|
320
|
+
else if (parentKey !== undefined) {
|
|
321
|
+
event.preventDefault();
|
|
322
|
+
focusNode(parentKey);
|
|
323
|
+
}
|
|
324
|
+
return;
|
|
325
|
+
}
|
|
326
|
+
if (event.key === " " &&
|
|
327
|
+
checkable &&
|
|
328
|
+
node.checkable !== false &&
|
|
329
|
+
!node.disableCheckbox) {
|
|
330
|
+
event.preventDefault();
|
|
331
|
+
setChecked(node, !keyIncludes(conductedCheckedKeys, node.key), event);
|
|
332
|
+
return;
|
|
333
|
+
}
|
|
334
|
+
if (event.key === "Enter" || event.key === " ") {
|
|
335
|
+
event.preventDefault();
|
|
336
|
+
selectNode(node, event);
|
|
337
|
+
}
|
|
338
|
+
};
|
|
339
|
+
const renderNodes = (nodes, parentKey, level = 1) => (_jsx("ul", { role: level === 1 ? "none" : "group", "data-slot": level === 1 ? "tree-list" : "tree-group", className: cn("m-0 flex list-none flex-col gap-0 p-0", level > 1 && (showLine ? "ml-3 border-l pl-3" : "pl-6"), level > 1 && semanticClassNames.group), style: level > 1 ? semanticStyles.group : undefined, children: nodes.map((node, index) => {
|
|
340
|
+
const entry = { node, parentKey, level, index };
|
|
341
|
+
const nodeDisabled = disabled || Boolean(node.disabled);
|
|
342
|
+
const expanded = keyIncludes(activeExpandedKeys, node.key);
|
|
343
|
+
const selected = keyIncludes(activeSelectedKeys, node.key);
|
|
344
|
+
const checked = keyIncludes(conductedCheckedKeys, node.key);
|
|
345
|
+
const halfChecked = keyIncludes(halfCheckedKeys, node.key);
|
|
346
|
+
const loading = keyIncludes(loadingKeys, node.key);
|
|
347
|
+
const hasChildren = Boolean(node.children?.length) ||
|
|
348
|
+
(Boolean(loadData) && node.isLeaf !== true);
|
|
349
|
+
const nodeInfo = {
|
|
350
|
+
node,
|
|
351
|
+
expanded,
|
|
352
|
+
selected,
|
|
353
|
+
checked,
|
|
354
|
+
halfChecked,
|
|
355
|
+
loading,
|
|
356
|
+
disabled: nodeDisabled,
|
|
357
|
+
};
|
|
358
|
+
const resolvedIcon = showIcon
|
|
359
|
+
? resolveNodeIcon(node.icon ?? icon, nodeInfo)
|
|
360
|
+
: null;
|
|
361
|
+
const title = titleRender ? titleRender(node) : node.title;
|
|
362
|
+
const matched = Boolean(filterTreeNode?.(node));
|
|
363
|
+
const tabbable = focusKey === undefined ? node.key === initialFocusKey : node.key === focusKey;
|
|
364
|
+
const nodeTitleId = titleId(node.key, level, index);
|
|
365
|
+
return (_jsxs("li", { role: "none", className: "min-w-0", children: [_jsxs("div", { role: "treeitem", tabIndex: tabbable ? 0 : -1, "data-tree-key": keyToken(node.key), "data-slot": "tree-item", "data-selected": selected || undefined, "data-filter-match": matched || undefined, "aria-expanded": hasChildren ? expanded : undefined, "aria-selected": selectable && node.selectable !== false ? selected : undefined, "aria-checked": checkable && node.checkable !== false
|
|
366
|
+
? halfChecked
|
|
367
|
+
? "mixed"
|
|
368
|
+
: checked
|
|
369
|
+
: undefined, "aria-disabled": nodeDisabled || undefined, "aria-busy": loading || undefined, "aria-level": level, "aria-posinset": index + 1, "aria-setsize": nodes.length, onFocus: () => setFocusKey(node.key), onClick: (event) => selectNode(node, event), onKeyDown: (event) => handleKeyDown(event, entry), className: cn("flex min-w-0 items-center gap-1 rounded-md px-1 py-0.5 text-sm outline-none transition-colors", blockNode ? "w-full" : "w-fit max-w-full", !nodeDisabled &&
|
|
370
|
+
"hover:bg-accent focus-visible:ring-2 focus-visible:ring-ring", selected && "bg-accent text-accent-foreground", matched && "font-medium", nodeDisabled && "cursor-not-allowed opacity-50", semanticClassNames.item), style: semanticStyles.item, children: [hasChildren ? (_jsx(Button, { variant: "text", shape: "circle", size: "small", tabIndex: -1, "aria-label": expanded ? "Collapse" : "Expand", "aria-expanded": expanded, disabled: nodeDisabled, onClick: (event) => {
|
|
371
|
+
event.stopPropagation();
|
|
372
|
+
setExpanded(node, !expanded);
|
|
373
|
+
}, className: cn("size-6 min-h-0 shrink-0 p-0", semanticClassNames.switcher), style: semanticStyles.switcher, children: loading
|
|
374
|
+
? switcherLoadingIcon ?? _jsx(Spinner, {})
|
|
375
|
+
: resolveSwitcherIcon(switcherIcon, nodeInfo) })) : (_jsx("span", { "aria-hidden": "true", "data-slot": "tree-switcher-placeholder", className: cn("size-6 shrink-0", semanticClassNames.switcher), style: semanticStyles.switcher })), checkable && node.checkable !== false ? (_jsx("input", { ref: (input) => {
|
|
376
|
+
if (input)
|
|
377
|
+
input.indeterminate = halfChecked;
|
|
378
|
+
}, type: "checkbox", tabIndex: -1, checked: checked, disabled: nodeDisabled || node.disableCheckbox, "aria-labelledby": nodeTitleId, "aria-checked": halfChecked ? "mixed" : checked, "data-indeterminate": halfChecked || undefined, onClick: (event) => event.stopPropagation(), onChange: (event) => {
|
|
379
|
+
event.stopPropagation();
|
|
380
|
+
setChecked(node, event.currentTarget.checked, event);
|
|
381
|
+
}, className: cn("size-4 shrink-0 accent-primary", semanticClassNames.checkbox), style: semanticStyles.checkbox })) : null, resolvedIcon ? (_jsx("span", { "data-slot": "tree-icon", "aria-hidden": "true", className: cn("flex size-5 shrink-0 items-center justify-center", semanticClassNames.icon), style: semanticStyles.icon, children: resolvedIcon })) : null, _jsx("span", { id: nodeTitleId, "data-slot": "tree-title", className: cn("min-w-0 break-words", semanticClassNames.title), style: semanticStyles.title, children: title })] }), expanded && node.children?.length
|
|
382
|
+
? renderNodes(node.children, node.key, level + 1)
|
|
383
|
+
: null] }, node.key));
|
|
384
|
+
}) }));
|
|
385
|
+
return (_jsx("div", { ...props, ref: setRootRef, role: "tree", "aria-multiselectable": multiple || undefined, "aria-disabled": disabled || undefined, "data-slot": "tree", "data-checkable": checkable || undefined, "data-show-line": showLine || undefined, className: cn("min-w-0", semanticClassNames.root, className), style: { ...semanticStyles.root, ...style }, children: renderNodes(treeData) }));
|
|
386
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { ElementType, HTMLAttributes, ReactNode } from "react";
|
|
2
|
+
export type HeadingLevel = 1 | 2 | 3 | 4 | 5 | 6;
|
|
3
|
+
export type TextSize = "xs" | "sm" | "md" | "lg";
|
|
4
|
+
export type TextTone = "default" | "muted" | "danger" | "success";
|
|
5
|
+
export interface HeadingProps extends HTMLAttributes<HTMLHeadingElement> {
|
|
6
|
+
level?: HeadingLevel;
|
|
7
|
+
children?: ReactNode;
|
|
8
|
+
}
|
|
9
|
+
export interface TextProps extends HTMLAttributes<HTMLElement> {
|
|
10
|
+
as?: ElementType;
|
|
11
|
+
size?: TextSize;
|
|
12
|
+
tone?: TextTone;
|
|
13
|
+
children?: ReactNode;
|
|
14
|
+
}
|
|
15
|
+
export interface TypographyProps extends HTMLAttributes<HTMLElement> {
|
|
16
|
+
as?: ElementType;
|
|
17
|
+
}
|
|
18
|
+
export declare function Heading({ level, children, className, ...props }: HeadingProps): import("react").JSX.Element;
|
|
19
|
+
export declare function Text({ as: Component, size, tone, children, className, ...props }: TextProps): import("react").JSX.Element;
|
|
20
|
+
export declare function Typography({ as: Component, className, ...props }: TypographyProps): import("react").JSX.Element;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { cn } from "../lib/utils.js";
|
|
3
|
+
export function Heading({ level = 2, children, className, ...props }) {
|
|
4
|
+
const Tag = `h${level}`;
|
|
5
|
+
return _jsx(Tag, { ...props, "data-slot": "heading", className: cn(level === 1 ? "text-3xl font-semibold tracking-tight" : level === 2 ? "text-2xl font-semibold tracking-tight" : level === 3 ? "text-xl font-semibold" : "text-lg font-semibold", className), children: children });
|
|
6
|
+
}
|
|
7
|
+
export function Text({ as: Component = "p", size = "md", tone = "default", children, className, ...props }) {
|
|
8
|
+
return _jsx(Component, { ...props, "data-slot": "text", className: cn(size === "xs" ? "text-xs" : size === "sm" ? "text-sm" : size === "lg" ? "text-lg" : "text-base", tone === "muted" && "text-muted-foreground", tone === "danger" && "text-destructive", tone === "success" && "text-success", className), children: children });
|
|
9
|
+
}
|
|
10
|
+
export function Typography({ as: Component = "p", className, ...props }) {
|
|
11
|
+
return _jsx(Component, { ...props, "data-slot": "typography", className: cn("text-sm text-foreground", className) });
|
|
12
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { UploadLocale } from "./locale.js";
|
|
2
|
+
import { type InputHTMLAttributes, type ReactNode } from "react";
|
|
3
|
+
export interface UploadProps extends Omit<InputHTMLAttributes<HTMLInputElement>, "type" | "onChange" | "multiple" | "accept" | "value" | "defaultValue"> {
|
|
4
|
+
locale?: Partial<UploadLocale>;
|
|
5
|
+
children?: ReactNode;
|
|
6
|
+
files?: File[];
|
|
7
|
+
defaultFiles?: File[];
|
|
8
|
+
onFiles?: (files: File[]) => void;
|
|
9
|
+
onRemove?: (file: File) => void;
|
|
10
|
+
multiple?: boolean;
|
|
11
|
+
accept?: string;
|
|
12
|
+
maxCount?: number;
|
|
13
|
+
maxSize?: number;
|
|
14
|
+
error?: ReactNode;
|
|
15
|
+
showFileList?: boolean;
|
|
16
|
+
drag?: boolean;
|
|
17
|
+
beforeSelect?: (file: File, files: File[]) => boolean;
|
|
18
|
+
onReject?: (file: File, reason: "type" | "size" | "beforeSelect" | "maxCount") => void;
|
|
19
|
+
}
|
|
20
|
+
export declare function Upload({ locale, children, files, defaultFiles, onFiles, onRemove, multiple, accept, maxCount, maxSize, disabled, readOnly, error, showFileList, drag, beforeSelect, onReject, id, className, ...props }: UploadProps): import("react").JSX.Element;
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useComponentLocale } from "./config-provider.js";
|
|
3
|
+
import { useId, useState, } from "react";
|
|
4
|
+
import { UploadCloud, X } from "lucide-react";
|
|
5
|
+
import { cn } from "../lib/utils.js";
|
|
6
|
+
export function Upload({ locale, children, files, defaultFiles = [], onFiles, onRemove, multiple = false, accept, maxCount, maxSize, disabled, readOnly, error, showFileList = true, drag = false, beforeSelect, onReject, id, className, ...props }) {
|
|
7
|
+
const text = useComponentLocale("upload", locale);
|
|
8
|
+
const generatedId = useId();
|
|
9
|
+
const inputId = id ?? generatedId;
|
|
10
|
+
const [internalFiles, setInternalFiles] = useState(defaultFiles);
|
|
11
|
+
const currentFiles = files ?? internalFiles;
|
|
12
|
+
const update = (next) => {
|
|
13
|
+
if (files === undefined)
|
|
14
|
+
setInternalFiles(next);
|
|
15
|
+
onFiles?.(next);
|
|
16
|
+
};
|
|
17
|
+
const acceptTokens = accept
|
|
18
|
+
?.split(",")
|
|
19
|
+
.map((token) => token.trim())
|
|
20
|
+
.filter(Boolean) ?? [];
|
|
21
|
+
const accepts = (file) => acceptTokens.length === 0 ||
|
|
22
|
+
acceptTokens.some((token) => token.startsWith(".")
|
|
23
|
+
? file.name.toLowerCase().endsWith(token.toLowerCase())
|
|
24
|
+
: token.endsWith("/*")
|
|
25
|
+
? file.type.startsWith(token.slice(0, -1))
|
|
26
|
+
: file.type === token);
|
|
27
|
+
const select = (incoming) => {
|
|
28
|
+
if (disabled || readOnly)
|
|
29
|
+
return;
|
|
30
|
+
const selected = incoming.filter((file) => {
|
|
31
|
+
if (!accepts(file)) {
|
|
32
|
+
onReject?.(file, "type");
|
|
33
|
+
return false;
|
|
34
|
+
}
|
|
35
|
+
if (maxSize !== undefined && file.size > maxSize) {
|
|
36
|
+
onReject?.(file, "size");
|
|
37
|
+
return false;
|
|
38
|
+
}
|
|
39
|
+
if (beforeSelect && !beforeSelect(file, incoming)) {
|
|
40
|
+
onReject?.(file, "beforeSelect");
|
|
41
|
+
return false;
|
|
42
|
+
}
|
|
43
|
+
return true;
|
|
44
|
+
});
|
|
45
|
+
const combined = multiple
|
|
46
|
+
? [...currentFiles, ...selected]
|
|
47
|
+
: selected.slice(0, 1);
|
|
48
|
+
if (maxCount !== undefined && combined.length > maxCount)
|
|
49
|
+
combined.slice(maxCount).forEach((file) => onReject?.(file, "maxCount"));
|
|
50
|
+
update(maxCount === undefined ? combined : combined.slice(0, maxCount));
|
|
51
|
+
};
|
|
52
|
+
const change = (event) => {
|
|
53
|
+
select(Array.from(event.target.files ?? []));
|
|
54
|
+
event.target.value = "";
|
|
55
|
+
};
|
|
56
|
+
const remove = (file) => {
|
|
57
|
+
if (disabled || readOnly)
|
|
58
|
+
return;
|
|
59
|
+
update(currentFiles.filter((candidate) => candidate !== file));
|
|
60
|
+
onRemove?.(file);
|
|
61
|
+
};
|
|
62
|
+
const descriptionId = error ? `${inputId}-error` : undefined;
|
|
63
|
+
return (_jsxs("div", { className: cn("space-y-3", className), "data-disabled": disabled || undefined, children: [_jsx("input", { ...props, id: inputId, type: "file", className: "sr-only", multiple: multiple, accept: accept, disabled: disabled || readOnly, "aria-invalid": Boolean(error) || undefined, "aria-describedby": descriptionId, onChange: change }), _jsxs("label", { htmlFor: inputId, onDragOver: drag ? (event) => event.preventDefault() : undefined, onDrop: drag
|
|
64
|
+
? (event) => {
|
|
65
|
+
event.preventDefault();
|
|
66
|
+
if (!disabled && !readOnly)
|
|
67
|
+
select(Array.from(event.dataTransfer.files));
|
|
68
|
+
}
|
|
69
|
+
: undefined, className: cn("inline-flex min-h-9 cursor-pointer items-center rounded-md border bg-background px-3 py-2 text-sm font-medium transition-colors hover:bg-muted focus-within:ring-2 focus-within:ring-ring", drag &&
|
|
70
|
+
"flex min-h-36 w-full flex-col justify-center gap-2 border-dashed text-center", (disabled || readOnly) &&
|
|
71
|
+
"pointer-events-none cursor-not-allowed opacity-50"), children: [drag ? (_jsx(UploadCloud, { "aria-hidden": "true", className: "size-7 text-muted-foreground" })) : null, children ?? (drag ? "点击或拖放文件到这里" : "Choose file")] }), showFileList && currentFiles.length > 0 ? (_jsx("ul", { className: "space-y-1", "aria-label": text.fileListLabel, children: currentFiles.map((file, index) => (_jsxs("li", { className: "flex items-center justify-between gap-3 rounded-md border px-3 py-2 text-sm", children: [_jsx("span", { className: "min-w-0 truncate", children: file.name }), _jsx("button", { type: "button", className: "rounded p-1 text-muted-foreground hover:bg-muted hover:text-foreground", "aria-label": text.removeLabel(file.name), disabled: disabled || readOnly, onClick: () => remove(file), children: _jsx(X, { className: "size-4" }) })] }, `${file.name}-${file.size}-${index}`))) })) : null, error ? (_jsx("div", { id: descriptionId, role: "alert", className: "text-sm text-destructive", children: error })) : null] }));
|
|
72
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { HTMLAttributes, ReactNode, Ref } from "react";
|
|
2
|
+
export interface WatermarkProps extends Omit<HTMLAttributes<HTMLDivElement>, "children"> {
|
|
3
|
+
content: string;
|
|
4
|
+
children: ReactNode;
|
|
5
|
+
rotate?: number;
|
|
6
|
+
gap?: number;
|
|
7
|
+
opacity?: number;
|
|
8
|
+
ref?: Ref<HTMLDivElement>;
|
|
9
|
+
}
|
|
10
|
+
export declare function Watermark({ content, children, rotate, gap, opacity, ref, className, style, ...props }: WatermarkProps): import("react").JSX.Element;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { cn } from "../lib/utils.js";
|
|
3
|
+
function finiteOr(value, fallback) {
|
|
4
|
+
return Number.isFinite(value) ? value : fallback;
|
|
5
|
+
}
|
|
6
|
+
function escapeXmlText(value) {
|
|
7
|
+
return value
|
|
8
|
+
.replaceAll("&", "&")
|
|
9
|
+
.replaceAll("<", "<")
|
|
10
|
+
.replaceAll(">", ">");
|
|
11
|
+
}
|
|
12
|
+
export function Watermark({ content, children, rotate = -22, gap = 120, opacity = 0.12, ref, className, style, ...props }) {
|
|
13
|
+
const tileSize = Math.max(32, Math.round(finiteOr(gap, 120)));
|
|
14
|
+
const center = tileSize / 2;
|
|
15
|
+
const safeRotate = finiteOr(rotate, -22);
|
|
16
|
+
const safeOpacity = Math.min(1, Math.max(0, finiteOr(opacity, 0.12)));
|
|
17
|
+
const escapedContent = escapeXmlText(content);
|
|
18
|
+
const svg = `<svg xmlns="http://www.w3.org/2000/svg" width="${tileSize}" height="${tileSize}" viewBox="0 0 ${tileSize} ${tileSize}"><text x="${center}" y="${center}" text-anchor="middle" dominant-baseline="middle" transform="rotate(${safeRotate} ${center} ${center})" fill="rgba(0,0,0,${safeOpacity})" font-size="16">${escapedContent}</text></svg>`;
|
|
19
|
+
const backgroundImage = `url("data:image/svg+xml,${encodeURIComponent(svg)}")`;
|
|
20
|
+
return (_jsx("div", { ...props, ref: ref, "data-slot": "watermark", className: cn("relative", className), style: { backgroundImage, ...style }, children: children }));
|
|
21
|
+
}
|