mirror of
https://github.com/zhigang1992/react.git
synced 2026-02-06 22:44:08 +08:00
feat(file-tree): add component
This commit is contained in:
8
components/file-tree/index.ts
Normal file
8
components/file-tree/index.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
import Tree from './tree'
|
||||
import TreeFile from './tree-file'
|
||||
import TreeFolder from './tree-folder'
|
||||
|
||||
Tree.File = TreeFile
|
||||
Tree.Folder = TreeFolder
|
||||
|
||||
export default Tree
|
||||
17
components/file-tree/tree-context.ts
Normal file
17
components/file-tree/tree-context.ts
Normal file
@@ -0,0 +1,17 @@
|
||||
import React from 'react'
|
||||
|
||||
export interface TreeConfig {
|
||||
onFileClick: (path: string) => void
|
||||
initialExpand: boolean
|
||||
isImperative: boolean
|
||||
}
|
||||
|
||||
const defaultContext = {
|
||||
onFileClick: () => {},
|
||||
initialExpand: false,
|
||||
isImperative: false,
|
||||
}
|
||||
|
||||
export const TreeContext = React.createContext<TreeConfig>(defaultContext)
|
||||
|
||||
export const useTreeContext = (): TreeConfig => React.useContext<TreeConfig>(TreeContext)
|
||||
36
components/file-tree/tree-file-icon.tsx
Normal file
36
components/file-tree/tree-file-icon.tsx
Normal file
@@ -0,0 +1,36 @@
|
||||
import React from 'react'
|
||||
import useTheme from '../styles/use-theme'
|
||||
import withDefaults from '../utils/with-defaults'
|
||||
|
||||
interface Props {
|
||||
color?: string
|
||||
width?: number
|
||||
height?: number
|
||||
}
|
||||
|
||||
const defaultProps = {
|
||||
width: 22,
|
||||
height: 22,
|
||||
}
|
||||
|
||||
export type TreeFileIconProps = Props & typeof defaultProps
|
||||
|
||||
const TreeFileIcon: React.FC<TreeFileIconProps> = ({
|
||||
color, width, height
|
||||
}) => {
|
||||
const theme = useTheme()
|
||||
return (
|
||||
<svg viewBox="0 0 24 24" width={width} height={height} stroke="currentColor" strokeWidth="1" strokeLinecap="round"
|
||||
strokeLinejoin="round" fill="none" shapeRendering="geometricPrecision">
|
||||
<path d="M13 2H6a2 2 0 00-2 2v16a2 2 0 002 2h12a2 2 0 002-2V9z" />
|
||||
<path d="M13 2v7h7" />
|
||||
<style jsx>{`
|
||||
svg {
|
||||
color: ${color || theme.palette.accents_8};
|
||||
}
|
||||
`}</style>
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
|
||||
export default withDefaults(TreeFileIcon, defaultProps)
|
||||
98
components/file-tree/tree-file.tsx
Normal file
98
components/file-tree/tree-file.tsx
Normal file
@@ -0,0 +1,98 @@
|
||||
import React, { useMemo } from 'react'
|
||||
import withDefaults from '../utils/with-defaults'
|
||||
import useTheme from '../styles/use-theme'
|
||||
import TreeFileIcon from './tree-file-icon'
|
||||
import { useTreeContext } from './tree-context'
|
||||
import TreeIndents from './tree-indents'
|
||||
import { makeChildPath, stopPropagation } from './tree-help'
|
||||
|
||||
interface Props {
|
||||
name: string
|
||||
extra?: string
|
||||
parentPath?: string
|
||||
level?: number
|
||||
className?: string
|
||||
}
|
||||
|
||||
const defaultProps = {
|
||||
level: 0,
|
||||
className: '',
|
||||
parentPath: '',
|
||||
}
|
||||
|
||||
export type TreeFileProps = Props & typeof defaultProps & React.HTMLAttributes<any>
|
||||
|
||||
const TreeFile: React.FC<React.PropsWithChildren<TreeFileProps>> = ({
|
||||
name, parentPath, level, extra, className, ...props
|
||||
}) => {
|
||||
const theme = useTheme()
|
||||
const { onFileClick } = useTreeContext()
|
||||
const currentPath = useMemo(() => makeChildPath(name, parentPath), [])
|
||||
const clickHandler = (event: React.MouseEvent) => {
|
||||
stopPropagation(event)
|
||||
onFileClick(currentPath)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={`file ${className}`} onClick={clickHandler} {...props}>
|
||||
<div className="names">
|
||||
<TreeIndents count={level} />
|
||||
<span className="icon"><TreeFileIcon /></span>
|
||||
<span className="name">{name}{extra && <span className="extra">{extra}</span>}</span>
|
||||
</div>
|
||||
<style jsx>{`
|
||||
.file {
|
||||
cursor: pointer;
|
||||
line-height: 1;
|
||||
user-select: none;
|
||||
margin-left: calc(1.875rem * ${level});
|
||||
}
|
||||
|
||||
.names {
|
||||
display: flex;
|
||||
height: 1.75rem;
|
||||
align-items: center;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.names > :global(.indent) {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
width: 1px;
|
||||
height: 100%;
|
||||
background-color: ${theme.palette.accents_2};
|
||||
margin-left: -1px;
|
||||
}
|
||||
|
||||
.icon {
|
||||
width: 1.5rem;
|
||||
height: 100%;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
margin-right: .5rem;
|
||||
}
|
||||
|
||||
.name {
|
||||
transition: opacity 100ms ease 0ms;
|
||||
color: ${theme.palette.accents_8};
|
||||
white-space: nowrap;
|
||||
font-size: .875rem;
|
||||
}
|
||||
|
||||
.extra {
|
||||
font-size: .75rem;
|
||||
align-self: baseline;
|
||||
padding-left: 4px;
|
||||
color: ${theme.palette.accents_5};
|
||||
}
|
||||
|
||||
.name:hover {
|
||||
opacity: .7;
|
||||
}
|
||||
`}</style>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default withDefaults(TreeFile, defaultProps)
|
||||
36
components/file-tree/tree-folder-icon.tsx
Normal file
36
components/file-tree/tree-folder-icon.tsx
Normal file
@@ -0,0 +1,36 @@
|
||||
import React from 'react'
|
||||
import useTheme from '../styles/use-theme'
|
||||
import withDefaults from '../utils/with-defaults'
|
||||
|
||||
interface Props {
|
||||
color?: string
|
||||
width?: number
|
||||
height?: number
|
||||
}
|
||||
|
||||
const defaultProps = {
|
||||
width: 22,
|
||||
height: 22,
|
||||
}
|
||||
|
||||
export type TreeFolderIconProps = Props & typeof defaultProps
|
||||
|
||||
const TreeFolderIcon: React.FC<TreeFolderIconProps> = ({
|
||||
color, width, height,
|
||||
}) => {
|
||||
const theme = useTheme()
|
||||
return (
|
||||
<svg viewBox="0 0 24 24" width={width} height={height} stroke="currentColor" strokeWidth="1" strokeLinecap="round"
|
||||
strokeLinejoin="round" fill="none" shapeRendering="geometricPrecision">
|
||||
<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" />
|
||||
<style jsx>{`
|
||||
svg {
|
||||
color: ${color || theme.palette.accents_8};
|
||||
}
|
||||
`}</style>
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
|
||||
export default withDefaults(TreeFolderIcon, defaultProps)
|
||||
139
components/file-tree/tree-folder.tsx
Normal file
139
components/file-tree/tree-folder.tsx
Normal file
@@ -0,0 +1,139 @@
|
||||
import React, { useEffect, useMemo, useState } from 'react'
|
||||
import useTheme from '../styles/use-theme'
|
||||
import withDefaults from '../utils/with-defaults'
|
||||
import { setChildrenProps } from '../utils/collections'
|
||||
import TreeFile from './tree-file'
|
||||
import Expand from '../shared/expand'
|
||||
import TreeIndents from './tree-indents'
|
||||
import { useTreeContext } from './tree-context'
|
||||
import TreeFolderIcon from './tree-folder-icon'
|
||||
import TreeStatusIcon from './tree-status-icon'
|
||||
import { sortChildren, makeChildPath, stopPropagation } from './tree-help'
|
||||
|
||||
interface Props {
|
||||
name: string
|
||||
extra?: string
|
||||
parentPath?: string
|
||||
level?: number
|
||||
className?: string
|
||||
}
|
||||
|
||||
const defaultProps = {
|
||||
level: 0,
|
||||
className: '',
|
||||
parentPath: '',
|
||||
}
|
||||
|
||||
export type TreeFolderProps = Props & typeof defaultProps & React.HTMLAttributes<any>
|
||||
|
||||
const TreeFolder: React.FC<React.PropsWithChildren<TreeFolderProps>> = ({
|
||||
name, children, parentPath, level: parentLevel, extra, className, ...props
|
||||
}) => {
|
||||
const theme = useTheme()
|
||||
const { initialExpand, isImperative } = useTreeContext()
|
||||
const [expanded, setExpanded] = useState<boolean>(initialExpand)
|
||||
useEffect(() => setExpanded(initialExpand), [])
|
||||
|
||||
const currentPath = useMemo(() => makeChildPath(name, parentPath), [])
|
||||
const clickHandler = () => setExpanded(!expanded)
|
||||
|
||||
const nextChildren = setChildrenProps(
|
||||
children,
|
||||
{
|
||||
parentPath: currentPath,
|
||||
level: parentLevel + 1,
|
||||
},
|
||||
[TreeFolder, TreeFile],
|
||||
)
|
||||
|
||||
const sortedChildren = isImperative ? nextChildren : sortChildren(nextChildren, TreeFolder)
|
||||
|
||||
return (
|
||||
<div className={`folder ${className}`} onClick={clickHandler} {...props}>
|
||||
<div className="names">
|
||||
<TreeIndents count={parentLevel} />
|
||||
<span className="status"><TreeStatusIcon active={expanded} /></span>
|
||||
<span className="icon"><TreeFolderIcon /></span>
|
||||
<span className="name">{name}{extra && <span className="extra">{extra}</span>}</span>
|
||||
</div>
|
||||
<Expand isExpanded={expanded}>
|
||||
<div className="content" onClick={stopPropagation}>{sortedChildren}</div>
|
||||
</Expand>
|
||||
|
||||
<style jsx>{`
|
||||
.folder {
|
||||
cursor: pointer;
|
||||
line-height: 1;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.names {
|
||||
display: flex;
|
||||
height: 1.75rem;
|
||||
align-items: center;
|
||||
margin-left: calc(1.875rem * ${parentLevel});
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.names > :global(.indent) {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
width: 1px;
|
||||
height: 100%;
|
||||
background-color: ${theme.palette.accents_2};
|
||||
margin-left: -1px;
|
||||
}
|
||||
|
||||
.status {
|
||||
position: absolute;
|
||||
left: calc(-1.125rem);
|
||||
top: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
width: .875rem;
|
||||
height: .875rem;
|
||||
z-index: 10;
|
||||
background-color: ${theme.palette.background};
|
||||
}
|
||||
|
||||
.icon {
|
||||
width: 1.5rem;
|
||||
height: 100%;
|
||||
margin-right: .5rem;
|
||||
}
|
||||
|
||||
.status, .icon {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.name {
|
||||
transition: opacity 100ms ease 0ms;
|
||||
color: ${theme.palette.accents_8};
|
||||
white-space: nowrap;
|
||||
font-size: .875rem;
|
||||
}
|
||||
|
||||
.extra {
|
||||
font-size: .75rem;
|
||||
align-self: baseline;
|
||||
padding-left: 4px;
|
||||
color: ${theme.palette.accents_5};
|
||||
}
|
||||
|
||||
.name:hover {
|
||||
opacity: .7;
|
||||
}
|
||||
|
||||
.content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: auto;
|
||||
}
|
||||
`}</style>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default withDefaults(TreeFolder, defaultProps)
|
||||
24
components/file-tree/tree-help.ts
Normal file
24
components/file-tree/tree-help.ts
Normal file
@@ -0,0 +1,24 @@
|
||||
import React, { ReactNode } from 'react'
|
||||
|
||||
export const sortChildren = (
|
||||
children: ReactNode | undefined,
|
||||
folderComponentType: React.ElementType
|
||||
) => {
|
||||
return React.Children.toArray(children)
|
||||
.sort((a, b) => {
|
||||
if (!React.isValidElement(a) || !React.isValidElement(b)) return 0
|
||||
if (a.type !== b.type) return a.type !== folderComponentType ? 1 : -1
|
||||
return `${a.props.name}`.charCodeAt(0) - `${b.props.name}`.charCodeAt(0)
|
||||
})
|
||||
}
|
||||
|
||||
export const makeChildPath = (name: string, parentPath?: string) => {
|
||||
if (!parentPath) return name
|
||||
return `${parentPath}/${name}`
|
||||
}
|
||||
|
||||
export const stopPropagation = (event: React.MouseEvent) => {
|
||||
event.stopPropagation()
|
||||
event.nativeEvent.stopImmediatePropagation()
|
||||
}
|
||||
|
||||
24
components/file-tree/tree-indents.tsx
Normal file
24
components/file-tree/tree-indents.tsx
Normal file
@@ -0,0 +1,24 @@
|
||||
import React from 'react'
|
||||
|
||||
interface Props {
|
||||
count: number
|
||||
}
|
||||
|
||||
const TreeIndents: React.FC<Props> = ({ count }) => {
|
||||
if (count === 0) return null
|
||||
return (
|
||||
<>
|
||||
{[...new Array(count)].map((_, index) => (
|
||||
<span className="indent" key={`indent-${index}`}>
|
||||
<style jsx>{`
|
||||
span.indent {
|
||||
left: calc(-1.875rem * ${index + 1} + .75rem);
|
||||
}
|
||||
`}</style>
|
||||
</span>
|
||||
))}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default TreeIndents
|
||||
40
components/file-tree/tree-status-icon.tsx
Normal file
40
components/file-tree/tree-status-icon.tsx
Normal file
@@ -0,0 +1,40 @@
|
||||
import React from 'react'
|
||||
import useTheme from '../styles/use-theme'
|
||||
import withDefaults from '../utils/with-defaults'
|
||||
|
||||
interface Props {
|
||||
color?: string
|
||||
width?: number
|
||||
height?: number
|
||||
active?: boolean
|
||||
}
|
||||
|
||||
const defaultProps = {
|
||||
width: 12,
|
||||
height: 12,
|
||||
active: false,
|
||||
}
|
||||
|
||||
export type TreeStatusIconProps = Props & typeof defaultProps
|
||||
|
||||
const TreeStatusIcon: React.FC<TreeStatusIconProps> = ({
|
||||
color, width, height, active,
|
||||
}) => {
|
||||
const theme = useTheme()
|
||||
return (
|
||||
<svg viewBox="0 0 24 24" width={width} height={height} stroke="currentColor" strokeWidth="1.5" strokeLinecap="round"
|
||||
strokeLinejoin="round" fill="none" shapeRendering="geometricPrecision">
|
||||
<rect x="3" y="3" width="18" height="18" rx="2" ry="2" />
|
||||
{!active && <path d="M12 8v8" />}
|
||||
<path d="M8 12h8" />
|
||||
|
||||
<style jsx>{`
|
||||
svg {
|
||||
color: ${color || theme.palette.accents_8};
|
||||
}
|
||||
`}</style>
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
|
||||
export default withDefaults(TreeStatusIcon, defaultProps)
|
||||
93
components/file-tree/tree.tsx
Normal file
93
components/file-tree/tree.tsx
Normal file
@@ -0,0 +1,93 @@
|
||||
import React, { useMemo } from 'react'
|
||||
import TreeFile from './tree-file'
|
||||
import TreeFolder from './tree-folder'
|
||||
import { TreeContext } from './tree-context'
|
||||
import { tuple } from '../utils/prop-types'
|
||||
import { sortChildren } from 'components/file-tree/tree-help'
|
||||
|
||||
const FileTreeValueType = tuple(
|
||||
'directory',
|
||||
'file',
|
||||
)
|
||||
|
||||
const directoryType = FileTreeValueType[0]
|
||||
|
||||
export type FileTreeValue = {
|
||||
type: typeof FileTreeValueType[number]
|
||||
name: string
|
||||
extra?: string
|
||||
files?: Array<FileTreeValue>
|
||||
}
|
||||
|
||||
interface Props {
|
||||
value?: Array<FileTreeValue>
|
||||
initialExpand?: boolean
|
||||
onClick?: (path: string) => void
|
||||
className?: string
|
||||
}
|
||||
|
||||
const defaultProps = {
|
||||
initialExpand: false,
|
||||
className: '',
|
||||
}
|
||||
|
||||
export type TreeProps = Props & typeof defaultProps & React.HTMLAttributes<any>
|
||||
|
||||
const makeChildren = (value: Array<FileTreeValue> = []) => {
|
||||
if (!value || !value.length) return null
|
||||
return value
|
||||
.sort((a, b) => {
|
||||
if (a.type !== b.type) return a.type !== directoryType ? 1 : -1
|
||||
|
||||
return `${a.name}`.charCodeAt(0) - `${b.name}`.charCodeAt(0)
|
||||
})
|
||||
.map((item, index) => {
|
||||
if (item.type === directoryType) return (
|
||||
<TreeFolder name={item.name} extra={item.extra} key={`folder-${item.name}-${index}`}>
|
||||
{makeChildren(item.files)}
|
||||
</TreeFolder>
|
||||
)
|
||||
return <TreeFile name={item.name} extra={item.extra} key={`file-${item.name}-${index}`} />
|
||||
})
|
||||
}
|
||||
|
||||
const Tree: React.FC<React.PropsWithChildren<TreeProps>> = ({
|
||||
children, onClick, initialExpand, value, className, ...props
|
||||
}) => {
|
||||
const isImperative = Boolean(value && value.length > 0)
|
||||
const onFileClick = (path: string) => {
|
||||
onClick && onClick(path)
|
||||
}
|
||||
|
||||
const initialValue = useMemo(() => ({
|
||||
onFileClick,
|
||||
initialExpand,
|
||||
isImperative,
|
||||
}), [initialExpand])
|
||||
|
||||
const customChildren = isImperative ? makeChildren(value) : sortChildren(children, TreeFolder)
|
||||
|
||||
return (
|
||||
<TreeContext.Provider value={initialValue}>
|
||||
<div className={`tree ${className}`} {...props}>
|
||||
{customChildren}
|
||||
<style jsx>{`
|
||||
.tree {
|
||||
padding-left: 1.625rem;
|
||||
}
|
||||
`}</style>
|
||||
</div>
|
||||
</TreeContext.Provider>
|
||||
)
|
||||
}
|
||||
|
||||
type TreeComponent<P = {}> = React.FC<P> & {
|
||||
File: typeof TreeFile
|
||||
Folder: typeof TreeFolder
|
||||
}
|
||||
|
||||
type ComponentProps = Partial<typeof defaultProps> & Omit<Props, keyof typeof defaultProps>
|
||||
|
||||
Tree.defaultProps = defaultProps
|
||||
|
||||
export default Tree as TreeComponent<ComponentProps>
|
||||
@@ -35,3 +35,4 @@ export { default as Radio } from './radio'
|
||||
export { default as Select } from './select'
|
||||
export { default as Tabs } from './tabs'
|
||||
export { default as Progress } from './progress'
|
||||
export { default as Tree } from './file-tree'
|
||||
|
||||
@@ -61,3 +61,48 @@ export const pickChildrenFirst = (
|
||||
): ReactNode | undefined => {
|
||||
return React.Children.toArray(children)[0]
|
||||
}
|
||||
|
||||
export const setChildrenProps = (
|
||||
children: ReactNode | undefined,
|
||||
props: object = {},
|
||||
targetComponents: Array<React.ElementType> = []
|
||||
): ReactNode | undefined => {
|
||||
if (React.Children.count(children) === 0) return []
|
||||
const allowAll = targetComponents.length === 0
|
||||
const clone = (child: React.ReactElement, props = {}) => React.cloneElement(child, props)
|
||||
|
||||
return React.Children.map(children, item => {
|
||||
if (!React.isValidElement(item)) return item
|
||||
if (allowAll) return clone(item, props)
|
||||
|
||||
const isAllowed = targetComponents.find(child => child === item.type)
|
||||
if (isAllowed) return clone(item, props)
|
||||
return item
|
||||
})
|
||||
}
|
||||
|
||||
export type ShapeType = {
|
||||
width: number,
|
||||
height: number,
|
||||
}
|
||||
|
||||
export const getRealShape = (el: HTMLElement | null): ShapeType => {
|
||||
const defaultShape: ShapeType = { width: 0, height: 0 }
|
||||
if (!el || typeof window === 'undefined') return defaultShape
|
||||
|
||||
const rect = el.getBoundingClientRect()
|
||||
const { width, height } = window.getComputedStyle(el)
|
||||
|
||||
const getCSSStyleVal = (str: string, parentNum: number) => {
|
||||
if (!str) return 0
|
||||
const strVal = str.includes('px') ? +str.split('px')[0]
|
||||
: str.includes('%') ? +str.split('%')[0] * parentNum * 0.01 : str
|
||||
|
||||
return Number.isNaN(+strVal) ? 0 : +strVal
|
||||
}
|
||||
|
||||
return {
|
||||
width: getCSSStyleVal(`${width}`, rect.width),
|
||||
height: getCSSStyleVal(`${height}`, rect.height),
|
||||
}
|
||||
}
|
||||
|
||||
176
pages/docs/components/file-tree.mdx
Normal file
176
pages/docs/components/file-tree.mdx
Normal file
@@ -0,0 +1,176 @@
|
||||
import { Layout, Playground, Attributes } from 'lib/components'
|
||||
import { Tree, useToasts } from 'components'
|
||||
|
||||
export const meta = {
|
||||
title: 'File-Tree',
|
||||
description: 'File-Tree',
|
||||
}
|
||||
|
||||
## File Tree
|
||||
|
||||
Display a list of files and folders in a hierarchical tree structure.
|
||||
|
||||
<Playground
|
||||
title="Basic"
|
||||
desc="All folders and files are sorted automatically."
|
||||
scope={{ Tree }}
|
||||
code={`
|
||||
<Tree>
|
||||
<Tree.File name="package.json" />
|
||||
<Tree.Folder name="components">
|
||||
<Tree.File name="layout.js" />
|
||||
<Tree.Folder name="footer">
|
||||
<Tree.File name="footer.js" />
|
||||
<Tree.File name="footer-text.js" />
|
||||
<Tree.File name="footer-license.js" />
|
||||
</Tree.Folder>
|
||||
<Tree.File name="header.js" />
|
||||
</Tree.Folder>
|
||||
<Tree.File name="readme.md" />
|
||||
</Tree>
|
||||
`} />
|
||||
|
||||
|
||||
<Playground
|
||||
title="Imperative"
|
||||
desc="Use props `value` to show more complex file tree."
|
||||
scope={{ Tree }}
|
||||
code={`
|
||||
() => {
|
||||
const files = [{
|
||||
type: 'directory',
|
||||
name: 'bin',
|
||||
files: [{
|
||||
type: 'file',
|
||||
name: 'cs.js',
|
||||
}],
|
||||
}, {
|
||||
type: 'directory',
|
||||
name: 'docs',
|
||||
files: [{
|
||||
type: 'file',
|
||||
name: 'controllers.md',
|
||||
}, {
|
||||
type: 'file',
|
||||
name: 'es6.md',
|
||||
}, {
|
||||
type: 'file',
|
||||
name: 'production.md',
|
||||
}, {
|
||||
type: 'file',
|
||||
name: 'views.md',
|
||||
}],
|
||||
}]
|
||||
return <Tree value={files} />
|
||||
}
|
||||
`} />
|
||||
|
||||
|
||||
<Playground
|
||||
title="Extra Message"
|
||||
desc="Use props `value` to show more complex file tree."
|
||||
scope={{ Tree }}
|
||||
code={`
|
||||
() => {
|
||||
const files = [{
|
||||
type: 'directory',
|
||||
name: 'controllers',
|
||||
extra: '1 file',
|
||||
files: [{
|
||||
type: 'file',
|
||||
name: 'cs.js',
|
||||
extra: '1kb',
|
||||
}],
|
||||
}, {
|
||||
type: 'directory',
|
||||
name: 'docs',
|
||||
extra: '2 files',
|
||||
files: [{
|
||||
type: 'file',
|
||||
name: 'controllers.md',
|
||||
extra: '2.5kb',
|
||||
}, {
|
||||
type: 'file',
|
||||
name: 'es6.md',
|
||||
extra: '2.9kb',
|
||||
}],
|
||||
}, {
|
||||
type: 'file',
|
||||
name: 'production.md',
|
||||
extra: '0.8kb',
|
||||
}, {
|
||||
type: 'file',
|
||||
name: 'views.md',
|
||||
extra: '8.1kb',
|
||||
}]
|
||||
return <Tree value={files} />
|
||||
}
|
||||
`} />
|
||||
|
||||
<Playground
|
||||
title="Event"
|
||||
desc="Path will be reported when file is clicked."
|
||||
scope={{ Tree, useToasts }}
|
||||
code={`
|
||||
() => {
|
||||
const [_, setToast] = useToasts()
|
||||
const handler = path => setToast({ text: path })
|
||||
return (
|
||||
<Tree onClick={handler}>
|
||||
<Tree.Folder name="components">
|
||||
<Tree.File name="layout.js" />
|
||||
<Tree.File name="layout.js" />
|
||||
<Tree.Folder name="footer">
|
||||
<Tree.File name="footer.js" />
|
||||
<Tree.File name="footer-text.js" />
|
||||
<Tree.File name="footer-license.js" />
|
||||
</Tree.Folder>
|
||||
</Tree.Folder>
|
||||
<Tree.File name="package.json" />
|
||||
<Tree.File name="readme.md" />
|
||||
</Tree>
|
||||
)
|
||||
}
|
||||
`} />
|
||||
|
||||
|
||||
<Attributes edit="/pages/docs/components/file-tree.mdx">
|
||||
<Attributes.Title>Tree.Props</Attributes.Title>
|
||||
|
||||
| Attribute | Description | Type | Accepted values | Default
|
||||
| ---------- | ---------- | ---- | -------------- | ------ |
|
||||
| **value** | value of files | `Array<FileTreeValue>` | - | - |
|
||||
| **initialExpand** | expand by default | `boolean` | - | `false` |
|
||||
| **onClick** | click file event | `(path: string) => void` | - | - |
|
||||
| ... | native props | `HTMLAttributes` | `'id', 'title', 'className', ...` | - |
|
||||
|
||||
<Attributes.Title>Tree.File.Props</Attributes.Title>
|
||||
|
||||
| Attribute | Description | Type | Accepted values | Default
|
||||
| ---------- | ---------- | ---- | -------------- | ------ |
|
||||
| **name**(required) | file name | `string` | - | - |
|
||||
| **extra** | extra message | `string` | - | - |
|
||||
| ... | native props | `HTMLAttributes` | `'id', 'title', 'className', ...` | - |
|
||||
|
||||
<Attributes.Title>Tree.Folder.Props</Attributes.Title>
|
||||
|
||||
| Attribute | Description | Type | Accepted values | Default
|
||||
| ---------- | ---------- | ---- | -------------- | ------ |
|
||||
| **name**(required) | folder name | `string` | - | - |
|
||||
| **extra** | extra message | `string` | - | - |
|
||||
| ... | native props | `HTMLAttributes` | `'id', 'title', 'className', ...` | - |
|
||||
|
||||
<Attributes.Title>type FileTreeValue</Attributes.Title>
|
||||
|
||||
```ts
|
||||
type FileTreeValue = {
|
||||
type: 'directory' || 'file'
|
||||
name: string
|
||||
extra?: string
|
||||
files?: Array<FileTreeValue>
|
||||
}
|
||||
```
|
||||
|
||||
</Attributes>
|
||||
|
||||
export default ({ children }) => <Layout meta={meta}>{children}</Layout>
|
||||
Reference in New Issue
Block a user