mirror of
https://github.com/zhigang1992/react.git
synced 2026-03-26 06:55:07 +08:00
refactor: export all hooks functions directly from main module
refactor: rename the modules to make sure tree-shaking works
This commit is contained in:
21
components/tree/__tests__/__snapshots__/index.test.tsx.snap
Normal file
21
components/tree/__tests__/__snapshots__/index.test.tsx.snap
Normal file
@@ -0,0 +1,21 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`Tree should mount correctly 1`] = `
|
||||
<TreeFile
|
||||
className=""
|
||||
level={0}
|
||||
name="package.json"
|
||||
parentPath=""
|
||||
/>
|
||||
`;
|
||||
|
||||
exports[`Tree should mount correctly 2`] = `
|
||||
<TreeFolder
|
||||
className=""
|
||||
level={0}
|
||||
name="components"
|
||||
parentPath=""
|
||||
/>
|
||||
`;
|
||||
|
||||
exports[`Tree should mount correctly 3`] = `ReactWrapper {}`;
|
||||
87
components/tree/__tests__/index.test.tsx
Normal file
87
components/tree/__tests__/index.test.tsx
Normal file
@@ -0,0 +1,87 @@
|
||||
import React from 'react'
|
||||
import { mount } from 'enzyme'
|
||||
import { Tree } from 'components'
|
||||
import { nativeEvent } from 'tests/utils'
|
||||
import { FileTreeValue } from 'components/tree/tree'
|
||||
|
||||
const mockFiles: Array<FileTreeValue> = [
|
||||
{
|
||||
type: 'file',
|
||||
name: 'cs.js',
|
||||
},
|
||||
{
|
||||
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',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
type: 'file',
|
||||
name: 'views.md',
|
||||
},
|
||||
]
|
||||
|
||||
describe('Tree', () => {
|
||||
it('should mount correctly', () => {
|
||||
const wrapper = mount(
|
||||
<Tree>
|
||||
<Tree.File name="package.json" />
|
||||
<Tree.Folder name="components">
|
||||
<Tree.File name="layout.js" />
|
||||
<Tree.File name="header.js" />
|
||||
</Tree.Folder>
|
||||
<Tree.File name="readme.md" />
|
||||
</Tree>,
|
||||
)
|
||||
expect(<Tree.File name="package.json" />).toMatchSnapshot()
|
||||
expect(<Tree.Folder name="components" />).toMatchSnapshot()
|
||||
expect(wrapper).toMatchSnapshot()
|
||||
expect(() => wrapper.unmount()).not.toThrow()
|
||||
})
|
||||
|
||||
it('should show extra messages', () => {
|
||||
const files = mockFiles.map(item => ({ ...item, extra: 'extra' }))
|
||||
const wrapper = mount(<Tree value={files} />)
|
||||
const firstName = wrapper.find('.name').at(0)
|
||||
expect(firstName.text()).toContain('extra')
|
||||
expect(() => wrapper.unmount()).not.toThrow()
|
||||
})
|
||||
|
||||
it('should trigger event when file clicked', () => {
|
||||
const callback = jest.fn()
|
||||
const wrapper = mount(<Tree value={mockFiles} onClick={callback} />)
|
||||
wrapper.find('.file').at(0).simulate('click', nativeEvent)
|
||||
expect(callback).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('should be work when value is empty', () => {
|
||||
const wrapper = mount(<Tree value={[]} />)
|
||||
expect(() => wrapper.unmount()).not.toThrow()
|
||||
})
|
||||
})
|
||||
8
components/tree/index.ts
Normal file
8
components/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
|
||||
16
components/tree/tree-context.ts
Normal file
16
components/tree/tree-context.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
import React from 'react'
|
||||
|
||||
export interface TreeConfig {
|
||||
onFileClick?: (path: string) => void
|
||||
initialExpand: boolean
|
||||
isImperative: boolean
|
||||
}
|
||||
|
||||
const defaultContext = {
|
||||
initialExpand: false,
|
||||
isImperative: false,
|
||||
}
|
||||
|
||||
export const TreeContext = React.createContext<TreeConfig>(defaultContext)
|
||||
|
||||
export const useTreeContext = (): TreeConfig => React.useContext<TreeConfig>(TreeContext)
|
||||
44
components/tree/tree-file-icon.tsx
Normal file
44
components/tree/tree-file-icon.tsx
Normal file
@@ -0,0 +1,44 @@
|
||||
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>
|
||||
)
|
||||
}
|
||||
|
||||
const MemoTreeFileIcon = React.memo(TreeFileIcon)
|
||||
|
||||
export default withDefaults(MemoTreeFileIcon, defaultProps)
|
||||
109
components/tree/tree-file.tsx
Normal file
109
components/tree/tree-file.tsx
Normal file
@@ -0,0 +1,109 @@
|
||||
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: '',
|
||||
}
|
||||
|
||||
type NativeAttrs = Omit<React.HTMLAttributes<any>, keyof Props>
|
||||
export type TreeFileProps = Props & typeof defaultProps & NativeAttrs
|
||||
|
||||
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 && 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: 0.5rem;
|
||||
}
|
||||
|
||||
.name {
|
||||
transition: opacity 100ms ease 0ms;
|
||||
color: ${theme.palette.accents_8};
|
||||
white-space: nowrap;
|
||||
font-size: 0.875rem;
|
||||
}
|
||||
|
||||
.extra {
|
||||
font-size: 0.75rem;
|
||||
align-self: baseline;
|
||||
padding-left: 4px;
|
||||
color: ${theme.palette.accents_5};
|
||||
}
|
||||
|
||||
.name:hover {
|
||||
opacity: 0.7;
|
||||
}
|
||||
`}</style>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default withDefaults(TreeFile, defaultProps)
|
||||
43
components/tree/tree-folder-icon.tsx
Normal file
43
components/tree/tree-folder-icon.tsx
Normal file
@@ -0,0 +1,43 @@
|
||||
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>
|
||||
)
|
||||
}
|
||||
|
||||
const MemoTreeFolderIcon = React.memo(TreeFolderIcon)
|
||||
|
||||
export default withDefaults(MemoTreeFolderIcon, defaultProps)
|
||||
156
components/tree/tree-folder.tsx
Normal file
156
components/tree/tree-folder.tsx
Normal file
@@ -0,0 +1,156 @@
|
||||
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: '',
|
||||
}
|
||||
|
||||
type NativeAttrs = Omit<React.HTMLAttributes<any>, keyof Props>
|
||||
export type TreeFolderProps = Props & typeof defaultProps & NativeAttrs
|
||||
|
||||
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: 0.875rem;
|
||||
height: 0.875rem;
|
||||
z-index: 10;
|
||||
background-color: ${theme.palette.background};
|
||||
}
|
||||
|
||||
.icon {
|
||||
width: 1.5rem;
|
||||
height: 100%;
|
||||
margin-right: 0.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: 0.875rem;
|
||||
}
|
||||
|
||||
.extra {
|
||||
font-size: 0.75rem;
|
||||
align-self: baseline;
|
||||
padding-left: 4px;
|
||||
color: ${theme.palette.accents_5};
|
||||
}
|
||||
|
||||
.name:hover {
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
.content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: auto;
|
||||
}
|
||||
`}</style>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default withDefaults(TreeFolder, defaultProps)
|
||||
22
components/tree/tree-help.ts
Normal file
22
components/tree/tree-help.ts
Normal file
@@ -0,0 +1,22 @@
|
||||
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()
|
||||
}
|
||||
26
components/tree/tree-indents.tsx
Normal file
26
components/tree/tree-indents.tsx
Normal file
@@ -0,0 +1,26 @@
|
||||
import React from 'react'
|
||||
|
||||
interface Props {
|
||||
count: number
|
||||
}
|
||||
|
||||
const TreeIndents: React.FC<Props> = ({ count }) => {
|
||||
if (count === 0) return null
|
||||
return (
|
||||
/* eslint-disable react/jsx-no-useless-fragment */
|
||||
<>
|
||||
{[...new Array(count)].map((_, index) => (
|
||||
<span className="indent" key={`indent-${index}`}>
|
||||
<style jsx>{`
|
||||
span.indent {
|
||||
left: calc(-1.875rem * ${index + 1} + 0.75rem);
|
||||
}
|
||||
`}</style>
|
||||
</span>
|
||||
))}
|
||||
</>
|
||||
/* eslint-enable */
|
||||
)
|
||||
}
|
||||
|
||||
export default TreeIndents
|
||||
48
components/tree/tree-status-icon.tsx
Normal file
48
components/tree/tree-status-icon.tsx
Normal file
@@ -0,0 +1,48 @@
|
||||
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>
|
||||
)
|
||||
}
|
||||
|
||||
const MemoTreeStatusIcon = React.memo(TreeStatusIcon)
|
||||
|
||||
export default withDefaults(MemoTreeStatusIcon, defaultProps)
|
||||
102
components/tree/tree.tsx
Normal file
102
components/tree/tree.tsx
Normal file
@@ -0,0 +1,102 @@
|
||||
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 './/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: '',
|
||||
}
|
||||
|
||||
type NativeAttrs = Omit<React.HTMLAttributes<any>, keyof Props>
|
||||
export type TreeProps = Props & typeof defaultProps & NativeAttrs
|
||||
|
||||
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> &
|
||||
NativeAttrs
|
||||
|
||||
Tree.defaultProps = defaultProps
|
||||
|
||||
export default Tree as TreeComponent<ComponentProps>
|
||||
Reference in New Issue
Block a user