rspress-plugin-file-tree 0.1.1

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.
Files changed (52) hide show
  1. package/CHANGELOG.md +19 -0
  2. package/LICENSE +21 -0
  3. package/README.md +84 -0
  4. package/dist/components/Tree/Expand.d.ts +14 -0
  5. package/dist/components/Tree/Expand.js +104 -0
  6. package/dist/components/Tree/FileIcon.d.ts +8 -0
  7. package/dist/components/Tree/FileIcon.js +12 -0
  8. package/dist/components/Tree/FileTreeRender.d.ts +11 -0
  9. package/dist/components/Tree/FileTreeRender.js +7 -0
  10. package/dist/components/Tree/Tree.d.ts +20 -0
  11. package/dist/components/Tree/Tree.js +52 -0
  12. package/dist/components/Tree/TreeContext.d.ts +9 -0
  13. package/dist/components/Tree/TreeContext.js +7 -0
  14. package/dist/components/Tree/TreeFile.d.ts +14 -0
  15. package/dist/components/Tree/TreeFile.js +26 -0
  16. package/dist/components/Tree/TreeFolder.d.ts +14 -0
  17. package/dist/components/Tree/TreeFolder.js +37 -0
  18. package/dist/components/Tree/TreeFolderIcon.d.ts +8 -0
  19. package/dist/components/Tree/TreeFolderIcon.js +12 -0
  20. package/dist/components/Tree/TreeIndents.d.ts +6 -0
  21. package/dist/components/Tree/TreeIndents.js +10 -0
  22. package/dist/components/Tree/TreeStatusIcon.d.ts +9 -0
  23. package/dist/components/Tree/TreeStatusIcon.js +13 -0
  24. package/dist/components/helpers.d.ts +5 -0
  25. package/dist/components/helpers.js +35 -0
  26. package/dist/components/presets.d.ts +2 -0
  27. package/dist/components/presets.js +4 -0
  28. package/dist/index.d.ts +6 -0
  29. package/dist/index.js +31 -0
  30. package/dist/parser.d.ts +7 -0
  31. package/dist/parser.js +37 -0
  32. package/doc_build/static/search_index.aad48136.json +1 -0
  33. package/docs/index.md +26 -0
  34. package/image.png +0 -0
  35. package/package.json +44 -0
  36. package/rspress.config.ts +13 -0
  37. package/src/components/Tree/Expand.tsx +149 -0
  38. package/src/components/Tree/FileIcon.tsx +41 -0
  39. package/src/components/Tree/FileTreeRender.tsx +16 -0
  40. package/src/components/Tree/Tree.tsx +112 -0
  41. package/src/components/Tree/TreeContext.tsx +18 -0
  42. package/src/components/Tree/TreeFile.tsx +69 -0
  43. package/src/components/Tree/TreeFolder.tsx +108 -0
  44. package/src/components/Tree/TreeFolderIcon.tsx +40 -0
  45. package/src/components/Tree/TreeIndents.tsx +26 -0
  46. package/src/components/Tree/TreeStatusIcon.tsx +45 -0
  47. package/src/components/Tree/index.less +178 -0
  48. package/src/components/helpers.ts +42 -0
  49. package/src/components/presets.ts +5 -0
  50. package/src/index.ts +50 -0
  51. package/src/parser.ts +50 -0
  52. package/tsconfig.json +8 -0
package/CHANGELOG.md ADDED
@@ -0,0 +1,19 @@
1
+ # rspress-plugin-file-tree
2
+
3
+ ## 0.1.1
4
+
5
+ ### Patch Changes
6
+
7
+ - 546dcf0: release beta
8
+ - 5e28903: fixup publish config
9
+ - Updated dependencies [546dcf0]
10
+ - Updated dependencies [5e28903]
11
+ - rspress-plugin-devkit@0.1.1
12
+
13
+ ## 0.1.1-beta.0
14
+
15
+ ### Patch Changes
16
+
17
+ - 546dcf0: release beta
18
+ - Updated dependencies [546dcf0]
19
+ - rspress-plugin-devkit@0.1.1-beta.0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024 Linbudu
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,84 @@
1
+ # rspress-plugin-file-tree
2
+
3
+ Rspress plugin that add file tree view support.
4
+
5
+ Write tree view using code block with language `tree`:
6
+
7
+ ````markdown
8
+ ```tree
9
+ .
10
+ ├── rspress.config.ts
11
+ ├── src
12
+ │ ├── components
13
+ │ │ ├── FileTreeRender.tsx
14
+ │ │ ├── Tree
15
+ │ │ │ ├── Expand.tsx
16
+ │ │ │ ├── FileIcon.tsx
17
+ │ │ │ ├── Tree.tsx
18
+ │ │ │ ├── TreeContext.tsx
19
+ │ │ │ ├── TreeFile.tsx
20
+ │ │ │ ├── TreeFolder.tsx
21
+ │ │ │ ├── TreeFolderIcon.tsx
22
+ │ │ │ ├── TreeIndents.tsx
23
+ │ │ │ ├── TreeStatusIcon.tsx
24
+ │ │ │ ├── index.less
25
+ │ │ │ └── index.tsx
26
+ │ │ ├── helpers.ts
27
+ │ │ └── presets.ts
28
+ │ ├── index.ts
29
+ │ └── parser.ts
30
+ └── tsconfig.json
31
+ ```
32
+ ````
33
+
34
+ And it will be rendered as:
35
+
36
+ <div align="center">
37
+ <img src="./image.png" alt="sample" width="400" height="300" />
38
+ </div>
39
+
40
+ > [!NOTE]
41
+ >
42
+ > **The renderer component was forked from [Geist UI](https://geist-ui.dev/) which created by [witt](https://github.com/unix), huge thanks to his great work!**
43
+
44
+ ## Usage
45
+
46
+ ```bash
47
+ npm i rspress-plugin-file-tree
48
+ pnpm add rspress-plugin-file-tree
49
+ ```
50
+
51
+ ```ts
52
+ import * as path from 'path';
53
+ import { defineConfig } from 'rspress/config';
54
+ import fileTree from 'rspress-plugin-file-tree';
55
+
56
+ export default defineConfig({
57
+ root: path.join(__dirname, 'docs'),
58
+ plugins: [fileTree()],
59
+ });
60
+ ```
61
+
62
+ ## Configure
63
+
64
+ ## initialExpandDepth
65
+
66
+ Initial expand depth of the tree view.
67
+
68
+ - Type: `number`
69
+ - Default: `0`
70
+
71
+ ```ts
72
+ import * as path from 'path';
73
+ import { defineConfig } from 'rspress/config';
74
+ import fileTree from 'rspress-plugin-file-tree';
75
+
76
+ export default defineConfig({
77
+ root: path.join(__dirname, 'docs'),
78
+ plugins: [
79
+ fileTree({
80
+ initialExpandDepth: Infinity,
81
+ }),
82
+ ],
83
+ });
84
+ ```
@@ -0,0 +1,14 @@
1
+ import React from 'react';
2
+ export type ShapeType = {
3
+ width: number;
4
+ height: number;
5
+ };
6
+ export declare const getRealShape: (el: HTMLElement | null) => ShapeType;
7
+ export type ShapeResult = [ShapeType, () => void];
8
+ export type ExpandProps = {
9
+ isExpanded?: boolean;
10
+ delay?: number;
11
+ parentExpanded?: boolean[];
12
+ };
13
+ declare const Expand: React.FC<React.PropsWithChildren<ExpandProps>>;
14
+ export default Expand;
@@ -0,0 +1,104 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { useEffect, useRef, useState } from 'react';
3
+ import clsx from 'clsx';
4
+ export const getRealShape = (el) => {
5
+ const defaultShape = { width: 0, height: 0 };
6
+ if (!el || typeof window === 'undefined')
7
+ return defaultShape;
8
+ const rect = el.getBoundingClientRect();
9
+ const { width, height } = window.getComputedStyle(el);
10
+ const getCSSStyleVal = (str, parentNum) => {
11
+ if (!str)
12
+ return 0;
13
+ const strVal = str.includes('px')
14
+ ? +str.split('px')[0]
15
+ : str.includes('%')
16
+ ? +str.split('%')[0] * parentNum * 0.01
17
+ : str;
18
+ return Number.isNaN(+strVal) ? 0 : +strVal;
19
+ };
20
+ return {
21
+ width: getCSSStyleVal(`${width}`, rect.width),
22
+ height: getCSSStyleVal(`${height}`, rect.height),
23
+ };
24
+ };
25
+ const useRealShape = (ref) => {
26
+ const [state, setState] = useState({
27
+ width: 0,
28
+ height: 0,
29
+ });
30
+ const update = () => {
31
+ const { width, height } = getRealShape(ref.current);
32
+ setState({ width, height });
33
+ };
34
+ useEffect(() => update(), [ref.current]);
35
+ return [state, update];
36
+ };
37
+ const defaultProps = {
38
+ isExpanded: false,
39
+ delay: 200,
40
+ };
41
+ const Expand = ({ isExpanded, delay, parentExpanded = [], children, }) => {
42
+ const [height, setHeight] = useState(isExpanded ? 'auto' : '0');
43
+ const [selfExpanded, setSelfExpanded] = useState(isExpanded);
44
+ const [visible, setVisible] = useState(isExpanded);
45
+ const contentRef = useRef(null);
46
+ const entryTimer = useRef();
47
+ const leaveTimer = useRef();
48
+ const resetTimer = useRef();
49
+ const [state, updateShape] = useRealShape(contentRef);
50
+ const [parentClosed, setParentClosed] = useState(false);
51
+ useEffect(() => setHeight(`${state.height}px`), [state.height]);
52
+ useEffect(() => {
53
+ // show element or reset height.
54
+ // force an update once manually, even if the element does not change.
55
+ // (the height of the element might be "auto")
56
+ if (isExpanded) {
57
+ setVisible(isExpanded);
58
+ }
59
+ else {
60
+ updateShape();
61
+ setHeight(`${state.height}px`);
62
+ }
63
+ // show expand animation
64
+ entryTimer.current = window.setTimeout(() => {
65
+ setSelfExpanded(isExpanded);
66
+ clearTimeout(entryTimer.current);
67
+ }, 30);
68
+ // Reset height after animation
69
+ if (isExpanded) {
70
+ resetTimer.current = window.setTimeout(() => {
71
+ setHeight('auto');
72
+ clearTimeout(resetTimer.current);
73
+ }, delay);
74
+ }
75
+ else {
76
+ leaveTimer.current = window.setTimeout(() => {
77
+ setVisible(isExpanded);
78
+ clearTimeout(leaveTimer.current);
79
+ }, delay / 2);
80
+ }
81
+ return () => {
82
+ clearTimeout(entryTimer.current);
83
+ clearTimeout(leaveTimer.current);
84
+ clearTimeout(resetTimer.current);
85
+ };
86
+ }, [isExpanded]);
87
+ useEffect(() => {
88
+ const parentClosed = parentExpanded.some((i) => i === false);
89
+ setParentClosed(parentClosed);
90
+ }, [parentExpanded]);
91
+ return (_jsx("div", { className: clsx('rspress-file-tree-expand-container', {
92
+ 'rspress-file-tree-expand-container-expanded': isExpanded,
93
+ }), style: {
94
+ height: 0,
95
+ visibility: visible && !parentClosed ? 'visible' : 'hidden',
96
+ transition: `height ${delay}ms ease`,
97
+ ...(selfExpanded
98
+ ? { height, visibility: parentClosed ? 'hidden' : 'visible' }
99
+ : {}),
100
+ }, children: _jsx("div", { ref: contentRef, className: clsx('rspress-file-tree-expand-content'), children: children }) }));
101
+ };
102
+ Expand.defaultProps = defaultProps;
103
+ Expand.displayName = 'GeistExpand';
104
+ export default Expand;
@@ -0,0 +1,8 @@
1
+ import React from 'react';
2
+ export interface TreeFileIconProps {
3
+ color?: string;
4
+ width?: number;
5
+ height?: number;
6
+ }
7
+ declare const TreeFileIcon: React.FC<TreeFileIconProps>;
8
+ export default TreeFileIcon;
@@ -0,0 +1,12 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { buildClassName } from '../presets';
3
+ const defaultProps = {
4
+ width: 22,
5
+ height: 22,
6
+ };
7
+ const TreeFileIcon = ({ color, width, height, }) => {
8
+ return (_jsxs("svg", { className: buildClassName('file-icon'), viewBox: "0 0 24 24", width: width, height: height, stroke: "currentColor", strokeWidth: "1", strokeLinecap: "round", strokeLinejoin: "round", fill: "none", shapeRendering: "geometricPrecision", children: [_jsx("path", { d: "M13 2H6a2 2 0 00-2 2v16a2 2 0 002 2h12a2 2 0 002-2V9z" }), _jsx("path", { d: "M13 2v7h7" })] }));
9
+ };
10
+ TreeFileIcon.defaultProps = defaultProps;
11
+ TreeFileIcon.displayName = 'GeistTreeFileIcon';
12
+ export default TreeFileIcon;
@@ -0,0 +1,11 @@
1
+ import Tree from './Tree';
2
+ import TreeFile from './TreeFile';
3
+ import TreeFolder from './TreeFolder';
4
+ import './index.less';
5
+ export type TreeComponentType = typeof Tree & {
6
+ File: typeof TreeFile;
7
+ Folder: typeof TreeFolder;
8
+ };
9
+ export type { TreeProps, TreeFile } from './Tree';
10
+ declare const _default: TreeComponentType;
11
+ export default _default;
@@ -0,0 +1,7 @@
1
+ import Tree from './Tree';
2
+ import TreeFile from './TreeFile';
3
+ import TreeFolder from './TreeFolder';
4
+ import './index.less';
5
+ Tree.File = TreeFile;
6
+ Tree.Folder = TreeFolder;
7
+ export default Tree;
@@ -0,0 +1,20 @@
1
+ import React from 'react';
2
+ export declare const tuple: <T extends string[]>(...args: T) => T;
3
+ declare const FileTreeValueType: ["directory", "file"];
4
+ export type TreeFile = {
5
+ type: (typeof FileTreeValueType)[number];
6
+ name: string;
7
+ extra?: string;
8
+ files?: Array<TreeFile>;
9
+ };
10
+ interface Props {
11
+ tree?: TreeFile[];
12
+ initialExpand?: boolean;
13
+ onClick?: (path: string) => void;
14
+ className?: string;
15
+ initialExpandDepth?: number;
16
+ }
17
+ type NativeAttrs = Omit<React.HTMLAttributes<any>, keyof Props>;
18
+ export type TreeProps = Props & NativeAttrs;
19
+ declare const Tree: React.FC<React.PropsWithChildren<TreeProps>>;
20
+ export default Tree;
@@ -0,0 +1,52 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { useMemo } from 'react';
3
+ import TreeFile from './TreeFile';
4
+ import TreeFolder from './TreeFolder';
5
+ import { TreeContext } from './TreeContext';
6
+ import { sortChildren } from '../helpers';
7
+ import { buildClassName } from '../presets';
8
+ import { useDark } from 'rspress/runtime';
9
+ export const tuple = (...args) => args;
10
+ const FileTreeValueType = tuple('directory', 'file');
11
+ const directoryType = FileTreeValueType[0];
12
+ const defaultProps = {
13
+ initialExpand: false,
14
+ className: '',
15
+ };
16
+ const makeChildren = (value = []) => {
17
+ if (!value || !value.length)
18
+ return null;
19
+ return value
20
+ .sort((a, b) => {
21
+ if (a.type !== b.type)
22
+ return a.type !== directoryType ? 1 : -1;
23
+ return `${a.name}`.charCodeAt(0) - `${b.name}`.charCodeAt(0);
24
+ })
25
+ .map((item, index) => {
26
+ if (item.type === directoryType)
27
+ return (_jsx(TreeFolder, { name: item.name, extra: item.extra, parentExpanded: [], children: makeChildren(item.files) }, `folder-${item.name}-${index}`));
28
+ return (_jsx(TreeFile, { name: item.name, extra: item.extra, parentExpanded: [] }, `file-${item.name}-${index}`));
29
+ });
30
+ };
31
+ const Tree = ({ children, onClick, initialExpand, initialExpandDepth, tree, className, ...props }) => {
32
+ if (!tree)
33
+ return null;
34
+ const isImperative = Boolean(tree.length > 0);
35
+ const onFileClick = (path) => {
36
+ onClick && onClick(path);
37
+ };
38
+ const initialValue = useMemo(() => ({
39
+ onFileClick,
40
+ initialExpand,
41
+ initialExpandDepth,
42
+ isImperative,
43
+ }), [initialExpand]);
44
+ const customChildren = isImperative
45
+ ? makeChildren(tree)
46
+ : sortChildren(children, TreeFolder);
47
+ const dark = useDark();
48
+ return (_jsx(TreeContext.Provider, { value: initialValue, children: _jsx("div", { "data-dark": String(dark), className: buildClassName(), ...props, children: customChildren }) }));
49
+ };
50
+ Tree.defaultProps = defaultProps;
51
+ Tree.displayName = 'GeistTree';
52
+ export default Tree;
@@ -0,0 +1,9 @@
1
+ import React from 'react';
2
+ export interface TreeConfig {
3
+ onFileClick?: (path: string) => void;
4
+ initialExpand: boolean;
5
+ initialExpandDepth?: number;
6
+ isImperative: boolean;
7
+ }
8
+ export declare const TreeContext: React.Context<TreeConfig>;
9
+ export declare const useTreeContext: () => TreeConfig;
@@ -0,0 +1,7 @@
1
+ import React from 'react';
2
+ const defaultContext = {
3
+ initialExpand: false,
4
+ isImperative: false,
5
+ };
6
+ export const TreeContext = React.createContext(defaultContext);
7
+ export const useTreeContext = () => React.useContext(TreeContext);
@@ -0,0 +1,14 @@
1
+ import React from 'react';
2
+ interface Props {
3
+ name: string;
4
+ extra?: string;
5
+ parentPath?: string;
6
+ level?: number;
7
+ className?: string;
8
+ }
9
+ type NativeAttrs = Omit<React.HTMLAttributes<any>, keyof Props>;
10
+ export type TreeFileProps = Props & NativeAttrs & {
11
+ parentExpanded: boolean[];
12
+ };
13
+ declare const TreeFile: React.FC<React.PropsWithChildren<TreeFileProps>>;
14
+ export default TreeFile;
@@ -0,0 +1,26 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useMemo } from 'react';
3
+ import TreeFileIcon from './FileIcon';
4
+ import { useTreeContext } from './TreeContext';
5
+ import TreeIndents from './TreeIndents';
6
+ import { makeChildPath, stopPropagation } from '../helpers';
7
+ import { buildClassName } from '../presets';
8
+ const defaultProps = {
9
+ level: 0,
10
+ className: '',
11
+ parentPath: '',
12
+ };
13
+ const TreeFile = ({ name, parentPath, level, extra, className, parentExpanded, ...props }) => {
14
+ const { onFileClick } = useTreeContext();
15
+ const currentPath = useMemo(() => makeChildPath(name, parentPath), []);
16
+ const clickHandler = (event) => {
17
+ stopPropagation(event);
18
+ onFileClick && onFileClick(currentPath);
19
+ };
20
+ return (_jsx("div", { className: buildClassName('file'), onClick: clickHandler, ...props, children: _jsxs("div", { className: buildClassName('file-names'), style: {
21
+ marginLeft: `calc(1.875rem * ${level})`,
22
+ }, children: [_jsx(TreeIndents, { count: level }), _jsx("span", { className: buildClassName('file-icon'), children: _jsx(TreeFileIcon, {}) }), _jsxs("span", { className: buildClassName('file-name'), children: [name, extra && (_jsx("span", { className: buildClassName('file-extra'), children: extra }))] })] }) }));
23
+ };
24
+ TreeFile.defaultProps = defaultProps;
25
+ TreeFile.displayName = 'GeistTreeFile';
26
+ export default TreeFile;
@@ -0,0 +1,14 @@
1
+ import React from 'react';
2
+ interface Props {
3
+ name: string;
4
+ extra?: string;
5
+ parentPath?: string;
6
+ level?: number;
7
+ className?: string;
8
+ }
9
+ type NativeAttrs = Omit<React.HTMLAttributes<any>, keyof Props>;
10
+ export type TreeFolderProps = Props & NativeAttrs & {
11
+ parentExpanded: boolean[];
12
+ };
13
+ declare const TreeFolder: React.FC<React.PropsWithChildren<TreeFolderProps>>;
14
+ export default TreeFolder;
@@ -0,0 +1,37 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useMemo, useState } from 'react';
3
+ import TreeFile from './TreeFile';
4
+ import Expand from './Expand';
5
+ import TreeIndents from './TreeIndents';
6
+ import { useTreeContext } from './TreeContext';
7
+ import TreeFolderIcon from './TreeFolderIcon';
8
+ import TreeStatusIcon from './TreeStatusIcon';
9
+ import { sortChildren, makeChildPath, stopPropagation, setChildrenProps, } from '../helpers';
10
+ import { buildClassName } from '../presets';
11
+ const defaultProps = {
12
+ level: 0,
13
+ className: '',
14
+ parentPath: '',
15
+ };
16
+ const TreeFolder = ({ name, children, parentPath, level: parentLevel, extra, className, parentExpanded = [], ...props }) => {
17
+ const { initialExpand, isImperative, initialExpandDepth = 0, } = useTreeContext();
18
+ const [expanded, setExpanded] = useState(() => {
19
+ return parentLevel + 1 <= initialExpandDepth;
20
+ });
21
+ const currentPath = useMemo(() => makeChildPath(name, parentPath), []);
22
+ const clickHandler = () => setExpanded(!expanded);
23
+ const nextChildren = setChildrenProps(children, {
24
+ parentPath: currentPath,
25
+ level: parentLevel + 1,
26
+ parentExpanded: [...parentExpanded, expanded],
27
+ }, [TreeFolder, TreeFile]);
28
+ const sortedChildren = isImperative
29
+ ? nextChildren
30
+ : sortChildren(nextChildren, TreeFolder);
31
+ return (_jsxs("div", { className: buildClassName('folder'), onClick: clickHandler, ...props, children: [_jsxs("div", { className: buildClassName('folder-names'), style: {
32
+ marginLeft: `calc(1.875rem * ${parentLevel})`,
33
+ }, children: [_jsx(TreeIndents, { count: parentLevel }), _jsx("span", { className: buildClassName('folder-status'), children: _jsx(TreeStatusIcon, { active: expanded }) }), _jsx("span", { className: buildClassName('folder-icon'), children: _jsx(TreeFolderIcon, {}) }), _jsxs("span", { className: buildClassName('folder-name'), children: [name, extra && (_jsx("span", { className: buildClassName('folder-extra'), children: extra }))] })] }), _jsx(Expand, { isExpanded: expanded, parentExpanded: parentExpanded, children: _jsx("div", { className: buildClassName('folder-content'), onClick: stopPropagation, children: sortedChildren }) })] }));
34
+ };
35
+ TreeFolder.defaultProps = defaultProps;
36
+ TreeFolder.displayName = 'GeistTreeFolder';
37
+ export default TreeFolder;
@@ -0,0 +1,8 @@
1
+ import React from 'react';
2
+ export interface TreeFolderIconProps {
3
+ color?: string;
4
+ width?: number;
5
+ height?: number;
6
+ }
7
+ declare const TreeFolderIcon: React.FC<TreeFolderIconProps>;
8
+ export default TreeFolderIcon;
@@ -0,0 +1,12 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { buildClassName } from '../presets';
3
+ const defaultProps = {
4
+ width: 22,
5
+ height: 22,
6
+ };
7
+ const TreeFolderIcon = ({ color, width, height, }) => {
8
+ return (_jsx("svg", { className: buildClassName('folder-icon'), viewBox: "0 0 24 24", width: width, height: height, stroke: "currentColor", strokeWidth: "1", strokeLinecap: "round", strokeLinejoin: "round", fill: "none", shapeRendering: "geometricPrecision", children: _jsx("path", { d: "M2.707 7.454V5.62C2.707 4.725 3.469 4 4.409 4h4.843c.451 0 .884.17 1.204.474l.49.467c.126.12.296.186.473.186h8.399c.94 0 1.55.695 1.55 1.59v.737m-18.661 0h-.354a.344.344 0 00-.353.35l.508 11.587c.015.34.31.609.668.609h17.283c.358 0 .652-.269.667-.61L22 7.805a.344.344 0 00-.353-.35h-.278m-18.662 0h18.662" }) }));
9
+ };
10
+ TreeFolderIcon.defaultProps = defaultProps;
11
+ TreeFolderIcon.displayName = 'GeistTreeFolderIcon';
12
+ export default TreeFolderIcon;
@@ -0,0 +1,6 @@
1
+ import React from 'react';
2
+ interface Props {
3
+ count: number;
4
+ }
5
+ declare const TreeIndents: React.FC<Props>;
6
+ export default TreeIndents;
@@ -0,0 +1,10 @@
1
+ import { jsx as _jsx, Fragment as _Fragment } from "react/jsx-runtime";
2
+ import { buildClassName } from '../presets';
3
+ const TreeIndents = ({ count }) => {
4
+ if (count === 0)
5
+ return null;
6
+ return (_jsx(_Fragment, { children: [...new Array(count)].map((_, index) => (_jsx("span", { className: buildClassName('indent'), style: {
7
+ left: `calc(-1.875rem * ${index + 1} + 0.75rem)`,
8
+ } }, `indent-${index}`))) }));
9
+ };
10
+ export default TreeIndents;
@@ -0,0 +1,9 @@
1
+ import React from 'react';
2
+ export interface TreeStatusIconProps {
3
+ color?: string;
4
+ width?: number;
5
+ height?: number;
6
+ active?: boolean;
7
+ }
8
+ declare const TreeStatusIcon: React.FC<TreeStatusIconProps>;
9
+ export default TreeStatusIcon;
@@ -0,0 +1,13 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { buildClassName } from '../presets';
3
+ const defaultProps = {
4
+ width: 12,
5
+ height: 12,
6
+ active: false,
7
+ };
8
+ const TreeStatusIcon = ({ color, width, height, active, }) => {
9
+ return (_jsxs("svg", { className: buildClassName('folder-status-icon'), viewBox: "0 0 24 24", width: width, height: height, stroke: "currentColor", strokeWidth: "1.5", strokeLinecap: "round", strokeLinejoin: "round", fill: "none", shapeRendering: "geometricPrecision", children: [_jsx("rect", { x: "3", y: "3", width: "18", height: "18", rx: "2", ry: "2" }), !active && _jsx("path", { d: "M12 8v8" }), _jsx("path", { d: "M8 12h8" })] }));
10
+ };
11
+ TreeStatusIcon.defaultProps = defaultProps;
12
+ TreeStatusIcon.displayName = 'GeistTreeStatusIcon';
13
+ export default TreeStatusIcon;
@@ -0,0 +1,5 @@
1
+ import React, { type ReactNode } from 'react';
2
+ export declare const sortChildren: (children: ReactNode | undefined, folderComponentType: React.ElementType) => (string | number | React.ReactElement<any, string | React.JSXElementConstructor<any>> | Iterable<React.ReactNode> | React.ReactPortal)[];
3
+ export declare const makeChildPath: (name: string, parentPath?: string) => string;
4
+ export declare const stopPropagation: (event: React.MouseEvent) => void;
5
+ export declare const setChildrenProps: (children: ReactNode | undefined, props: Record<string, unknown>, targetComponents?: Array<React.ElementType>) => ReactNode | undefined;
@@ -0,0 +1,35 @@
1
+ import React from 'react';
2
+ export const sortChildren = (children, folderComponentType) => {
3
+ return React.Children.toArray(children).sort((a, b) => {
4
+ if (!React.isValidElement(a) || !React.isValidElement(b))
5
+ return 0;
6
+ if (a.type !== b.type)
7
+ return a.type !== folderComponentType ? 1 : -1;
8
+ return `${a.props.name}`.charCodeAt(0) - `${b.props.name}`.charCodeAt(0);
9
+ });
10
+ };
11
+ export const makeChildPath = (name, parentPath) => {
12
+ if (!parentPath)
13
+ return name;
14
+ return `${parentPath}/${name}`;
15
+ };
16
+ export const stopPropagation = (event) => {
17
+ event.stopPropagation();
18
+ event.nativeEvent.stopImmediatePropagation();
19
+ };
20
+ export const setChildrenProps = (children, props, targetComponents = []) => {
21
+ if (React.Children.count(children) === 0)
22
+ return [];
23
+ const allowAll = targetComponents.length === 0;
24
+ const clone = (child, props = {}) => React.cloneElement(child, props);
25
+ return React.Children.map(children, (item) => {
26
+ if (!React.isValidElement(item))
27
+ return item;
28
+ if (allowAll)
29
+ return clone(item, props);
30
+ const isAllowed = targetComponents.find((child) => child === item.type);
31
+ if (isAllowed)
32
+ return clone(item, props);
33
+ return item;
34
+ });
35
+ };
@@ -0,0 +1,2 @@
1
+ export declare const presetClassName = "rp-file-tree";
2
+ export declare function buildClassName(fragment?: string): string;
@@ -0,0 +1,4 @@
1
+ export const presetClassName = 'rp-file-tree';
2
+ export function buildClassName(fragment) {
3
+ return (fragment === null || fragment === void 0 ? void 0 : fragment.length) ? `${presetClassName}-${fragment}` : presetClassName;
4
+ }
@@ -0,0 +1,6 @@
1
+ import type { RspressPlugin } from '@rspress/shared';
2
+ interface RspressPluginFileTreeOptions {
3
+ initialExpandDepth?: number;
4
+ }
5
+ export default function rspressPluginFileTree(options?: RspressPluginFileTreeOptions): RspressPlugin;
6
+ export {};
package/dist/index.js ADDED
@@ -0,0 +1,31 @@
1
+ import path from 'node:path';
2
+ import { PresetConfigMutator, RemarkCodeBlockToGlobalComponentPluginFactory, } from 'rspress-plugin-devkit';
3
+ import { parseInput } from './parser';
4
+ export default function rspressPluginFileTree(options = {}) {
5
+ const { initialExpandDepth = 0 } = options;
6
+ const remarkFileTree = new RemarkCodeBlockToGlobalComponentPluginFactory({
7
+ components: [
8
+ {
9
+ lang: 'tree',
10
+ componentPath: path.join(__dirname, './components/Tree/FileTreeRender.tsx'),
11
+ propsProvider(code) {
12
+ return {
13
+ tree: parseInput(code),
14
+ initialExpandDepth,
15
+ };
16
+ },
17
+ },
18
+ ],
19
+ });
20
+ return {
21
+ name: 'rspress-plugin-file-tree',
22
+ config(config) {
23
+ return new PresetConfigMutator(config).disableMdxRs().toConfig();
24
+ },
25
+ markdown: {
26
+ remarkPlugins: [remarkFileTree.remarkPlugin],
27
+ globalComponents: remarkFileTree.mdxComponents,
28
+ },
29
+ builderConfig: remarkFileTree.builderConfig,
30
+ };
31
+ }
@@ -0,0 +1,7 @@
1
+ type TreeItem = {
2
+ type: 'file' | 'directory';
3
+ name: string;
4
+ files?: TreeItem[];
5
+ };
6
+ export declare function parseInput(input: string): TreeItem[];
7
+ export {};