Skip to content
This repository was archived by the owner on Dec 16, 2022. It is now read-only.

Commit 288e0b3

Browse files
authored
[UI]: add tree component. (#188)
1 parent bb82f54 commit 288e0b3

File tree

10 files changed

+218
-33
lines changed

10 files changed

+218
-33
lines changed

.changeset/old-shirts-live.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@resembli/ui": patch
3+
---
4+
5+
Add initial tree component and move framer-motion to be a peer dependency

packages/ui/package.json

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -31,21 +31,21 @@
3131
"clean:build": "rimraf dist && swc ./src -d dist && tsc"
3232
},
3333
"peerDependencies": {
34-
"@types/react": "^17.0.0",
35-
"@types/react-dom": "^17.0.0",
36-
"react": "^17.0.0",
37-
"react-dom": "^17.0.0"
34+
"@types/react": "^16.8.6 || ^17.0.0",
35+
"react": "^17.0.0"
3836
},
3937
"peerDependenciesMeta": {
4038
"@types/react": {
4139
"optional": true
42-
},
43-
"@types/react-dom": {
44-
"optional": true
4540
}
4641
},
4742
"sideEffects": false,
4843
"dependencies": {
49-
"framer-motion": "^6.2.8"
44+
"@stitches/core": "^1.2.6"
45+
},
46+
"devDependencies": {
47+
"framer-motion": "^6.2.8",
48+
"react": "^18.0.0-rc.1",
49+
"react-dom": "^18.0.0-rc.0"
5050
}
5151
}

packages/ui/src/components/Collapse.tsx

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,22 +3,31 @@ import * as React from "react"
33

44
export interface CollapseProps {
55
open?: boolean
6+
className?: string
7+
style?: React.CSSProperties
68
as?: keyof typeof motion
79
}
810

9-
export function Collapse({ open, children, as = "div" }: React.PropsWithChildren<CollapseProps>) {
11+
export function Collapse({
12+
open,
13+
children,
14+
as = "div",
15+
className,
16+
style,
17+
}: React.PropsWithChildren<CollapseProps>) {
1018
const MotionElement = motion[as]
1119

1220
return (
1321
<AnimatePresence initial={false}>
1422
{open && (
1523
<MotionElement
24+
className={className}
1625
key="content"
1726
initial="collapsed"
1827
animate="open"
1928
exit="collapsed"
2029
transition={{ duration: 0.2 }}
21-
style={{ overflow: "hidden" }}
30+
style={{ overflow: "hidden", ...style }}
2231
variants={{
2332
open: { height: "auto" },
2433
collapsed: { height: 0 },
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
import * as React from "react"
2+
3+
import { css } from "../../css/css.js"
4+
import { Collapse } from "../Collapse.js"
5+
import { useTreeContext } from "./TreeRoot.js"
6+
7+
const NodeCss = css({
8+
margin: 0,
9+
padding: 0,
10+
cursor: "pointer",
11+
userSelect: "none",
12+
})
13+
14+
const NodeItemCss = css({
15+
"&:hover": {
16+
background: "AliceBlue",
17+
},
18+
})
19+
20+
const NodeContainerCss = css({
21+
margin: 0,
22+
padding: 0,
23+
listStyle: "none",
24+
})
25+
26+
export interface TreeNodeProps {
27+
item: React.ReactNode
28+
}
29+
30+
interface InternalTreeProps extends TreeNodeProps {
31+
__depth: number
32+
}
33+
34+
export function TreeNode({ children, ...props }: React.PropsWithChildren<TreeNodeProps>) {
35+
const [open, setOpen] = React.useState(false)
36+
37+
const { item, __depth = 0 } = props as InternalTreeProps
38+
39+
const { indentSize } = useTreeContext()
40+
41+
// Add the internal depth property to the elements. We either expect an array or a single
42+
// item. Note the user should be using TreeNode component, if they've provided other types
43+
// of React components, we don't do anything, but will render them as normal.
44+
let childElements
45+
if (Array.isArray(children)) {
46+
childElements = children.map((child) => {
47+
if ((child as JSX.Element)?.type?.name === "TreeNode") {
48+
return React.cloneElement(child as JSX.Element, { __depth: __depth + 1 })
49+
}
50+
return child
51+
})
52+
} else {
53+
if ((children as JSX.Element)?.type?.name === "TreeNode") {
54+
childElements = React.cloneElement(children as JSX.Element, { __depth: __depth + 1 })
55+
}
56+
}
57+
58+
const indentValue =
59+
typeof indentSize === "number" ? indentSize * __depth : `calc(${indentSize} * ${__depth})`
60+
61+
return (
62+
<li className={NodeCss()}>
63+
<div
64+
onClick={() => setOpen((prev) => !prev)}
65+
className={NodeItemCss({ css: { paddingLeft: __depth && indentValue } })}
66+
>
67+
{item}
68+
</div>
69+
{childElements && (
70+
<Collapse as="ul" open={open} className={NodeContainerCss()}>
71+
{childElements}
72+
</Collapse>
73+
)}
74+
</li>
75+
)
76+
}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
import * as React from "react"
2+
3+
import { css } from "../../css/css.js"
4+
5+
const TreeRootCss = css({
6+
margin: 0,
7+
padding: 0,
8+
listStyle: "none",
9+
})
10+
11+
const DEFAULT_INDENT_SIZE = "1rem"
12+
13+
interface TreeContextValue {
14+
indentSize: React.CSSProperties["marginLeft"]
15+
}
16+
17+
const TreeContext = React.createContext<TreeContextValue>({ indentSize: DEFAULT_INDENT_SIZE })
18+
export function useTreeContext() {
19+
return React.useContext(TreeContext)
20+
}
21+
22+
export interface TreeRootProps {
23+
indent?: TreeContextValue["indentSize"]
24+
}
25+
26+
export function TreeRoot({ children, indent }: React.PropsWithChildren<TreeRootProps>) {
27+
return (
28+
<TreeContext.Provider value={{ indentSize: indent ?? DEFAULT_INDENT_SIZE }}>
29+
<ul className={TreeRootCss()}>{children}</ul>
30+
</TreeContext.Provider>
31+
)
32+
}

packages/ui/src/css/css.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
import { createStitches } from "@stitches/core"
2+
3+
export const { css, keyframes, globalCss } = createStitches({})

packages/ui/src/index.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,8 @@
1-
export { Collapse } from "./components/Collapse"
1+
export { Collapse } from "./components/Collapse.js"
2+
export type { CollapseProps } from "./components/Collapse.js"
3+
4+
export { TreeRoot } from "./components/Tree/TreeRoot.js"
5+
export type { TreeRootProps } from "./components/Tree/TreeRoot.js"
6+
7+
export { TreeNode } from "./components/Tree/TreeNode.js"
8+
export type { TreeNodeProps } from "./components/Tree/TreeNode.js"

playgrounds/package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,9 @@
99
},
1010
"dependencies": {
1111
"@resembli/react-virtualized-window": "workspace:^",
12+
"@resembli/ui": "workspace:^0.0.1",
1213
"@stitches/core": "^1.2.6",
14+
"framer-motion": "^6.2.8",
1315
"react": "^18.0.0-rc.0",
1416
"react-dom": "^18.0.0-rc.0"
1517
},

playgrounds/src/App.tsx

Lines changed: 23 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
import { css } from "@stitches/core"
22

3+
import { TreeNode, TreeRoot } from "@resembli/ui"
4+
35
const AppCss = css({
46
display: "flex",
57
alignItems: "center",
@@ -10,18 +12,28 @@ const AppCss = css({
1012
background: "Beige",
1113
})
1214

13-
const rowHeights = Array.from({ length: 1000 }, (_, i) => [20, 40, 20, 60][i % 4]).reduce(
14-
(acc, h, i) => {
15-
acc[i] = h
16-
17-
return acc
18-
},
19-
{} as Record<number, number>,
20-
)
21-
22-
console.log(rowHeights)
2315
function App() {
24-
return <div className={AppCss()}></div>
16+
return (
17+
<div className={AppCss()}>
18+
<div style={{ height: 500, width: 500, border: "1px solid black" }}>
19+
<TreeRoot>
20+
<TreeNode item={<div>Applications</div>}>
21+
<TreeNode item="Calendar" />
22+
<TreeNode item="Chrome" />
23+
<TreeNode item="Webstorm" />
24+
</TreeNode>
25+
<TreeNode item={<div>Documents</div>}>
26+
<TreeNode item="UI">
27+
<TreeNode item="src">
28+
<TreeNode item="index.js" />
29+
<TreeNode item="tree-view.js" />
30+
</TreeNode>
31+
</TreeNode>
32+
</TreeNode>
33+
</TreeRoot>
34+
</div>
35+
</div>
36+
)
2537
}
2638

2739
export default App

0 commit comments

Comments
 (0)