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

Commit

Permalink
[UI]: add tree component. (#188)
Browse files Browse the repository at this point in the history
  • Loading branch information
leebeydoun committed Mar 6, 2022
1 parent bb82f54 commit 288e0b3
Show file tree
Hide file tree
Showing 10 changed files with 218 additions and 33 deletions.
5 changes: 5 additions & 0 deletions .changeset/old-shirts-live.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@resembli/ui": patch
---

Add initial tree component and move framer-motion to be a peer dependency
16 changes: 8 additions & 8 deletions packages/ui/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,21 +31,21 @@
"clean:build": "rimraf dist && swc ./src -d dist && tsc"
},
"peerDependencies": {
"@types/react": "^17.0.0",
"@types/react-dom": "^17.0.0",
"react": "^17.0.0",
"react-dom": "^17.0.0"
"@types/react": "^16.8.6 || ^17.0.0",
"react": "^17.0.0"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
},
"@types/react-dom": {
"optional": true
}
},
"sideEffects": false,
"dependencies": {
"framer-motion": "^6.2.8"
"@stitches/core": "^1.2.6"
},
"devDependencies": {
"framer-motion": "^6.2.8",
"react": "^18.0.0-rc.1",
"react-dom": "^18.0.0-rc.0"
}
}
13 changes: 11 additions & 2 deletions packages/ui/src/components/Collapse.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,22 +3,31 @@ import * as React from "react"

export interface CollapseProps {
open?: boolean
className?: string
style?: React.CSSProperties
as?: keyof typeof motion
}

export function Collapse({ open, children, as = "div" }: React.PropsWithChildren<CollapseProps>) {
export function Collapse({
open,
children,
as = "div",
className,
style,
}: React.PropsWithChildren<CollapseProps>) {
const MotionElement = motion[as]

return (
<AnimatePresence initial={false}>
{open && (
<MotionElement
className={className}
key="content"
initial="collapsed"
animate="open"
exit="collapsed"
transition={{ duration: 0.2 }}
style={{ overflow: "hidden" }}
style={{ overflow: "hidden", ...style }}
variants={{
open: { height: "auto" },
collapsed: { height: 0 },
Expand Down
76 changes: 76 additions & 0 deletions packages/ui/src/components/Tree/TreeNode.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
import * as React from "react"

import { css } from "../../css/css.js"
import { Collapse } from "../Collapse.js"
import { useTreeContext } from "./TreeRoot.js"

const NodeCss = css({
margin: 0,
padding: 0,
cursor: "pointer",
userSelect: "none",
})

const NodeItemCss = css({
"&:hover": {
background: "AliceBlue",
},
})

const NodeContainerCss = css({
margin: 0,
padding: 0,
listStyle: "none",
})

export interface TreeNodeProps {
item: React.ReactNode
}

interface InternalTreeProps extends TreeNodeProps {
__depth: number
}

export function TreeNode({ children, ...props }: React.PropsWithChildren<TreeNodeProps>) {
const [open, setOpen] = React.useState(false)

const { item, __depth = 0 } = props as InternalTreeProps

const { indentSize } = useTreeContext()

// Add the internal depth property to the elements. We either expect an array or a single
// item. Note the user should be using TreeNode component, if they've provided other types
// of React components, we don't do anything, but will render them as normal.
let childElements
if (Array.isArray(children)) {
childElements = children.map((child) => {
if ((child as JSX.Element)?.type?.name === "TreeNode") {
return React.cloneElement(child as JSX.Element, { __depth: __depth + 1 })
}
return child
})
} else {
if ((children as JSX.Element)?.type?.name === "TreeNode") {
childElements = React.cloneElement(children as JSX.Element, { __depth: __depth + 1 })
}
}

const indentValue =
typeof indentSize === "number" ? indentSize * __depth : `calc(${indentSize} * ${__depth})`

return (
<li className={NodeCss()}>
<div
onClick={() => setOpen((prev) => !prev)}
className={NodeItemCss({ css: { paddingLeft: __depth && indentValue } })}
>
{item}
</div>
{childElements && (
<Collapse as="ul" open={open} className={NodeContainerCss()}>
{childElements}
</Collapse>
)}
</li>
)
}
32 changes: 32 additions & 0 deletions packages/ui/src/components/Tree/TreeRoot.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import * as React from "react"

import { css } from "../../css/css.js"

const TreeRootCss = css({
margin: 0,
padding: 0,
listStyle: "none",
})

const DEFAULT_INDENT_SIZE = "1rem"

interface TreeContextValue {
indentSize: React.CSSProperties["marginLeft"]
}

const TreeContext = React.createContext<TreeContextValue>({ indentSize: DEFAULT_INDENT_SIZE })
export function useTreeContext() {
return React.useContext(TreeContext)
}

export interface TreeRootProps {
indent?: TreeContextValue["indentSize"]
}

export function TreeRoot({ children, indent }: React.PropsWithChildren<TreeRootProps>) {
return (
<TreeContext.Provider value={{ indentSize: indent ?? DEFAULT_INDENT_SIZE }}>
<ul className={TreeRootCss()}>{children}</ul>
</TreeContext.Provider>
)
}
3 changes: 3 additions & 0 deletions packages/ui/src/css/css.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import { createStitches } from "@stitches/core"

export const { css, keyframes, globalCss } = createStitches({})
9 changes: 8 additions & 1 deletion packages/ui/src/index.ts
Original file line number Diff line number Diff line change
@@ -1 +1,8 @@
export { Collapse } from "./components/Collapse"
export { Collapse } from "./components/Collapse.js"
export type { CollapseProps } from "./components/Collapse.js"

export { TreeRoot } from "./components/Tree/TreeRoot.js"
export type { TreeRootProps } from "./components/Tree/TreeRoot.js"

export { TreeNode } from "./components/Tree/TreeNode.js"
export type { TreeNodeProps } from "./components/Tree/TreeNode.js"
2 changes: 2 additions & 0 deletions playgrounds/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,9 @@
},
"dependencies": {
"@resembli/react-virtualized-window": "workspace:^",
"@resembli/ui": "workspace:^0.0.1",
"@stitches/core": "^1.2.6",
"framer-motion": "^6.2.8",
"react": "^18.0.0-rc.0",
"react-dom": "^18.0.0-rc.0"
},
Expand Down
34 changes: 23 additions & 11 deletions playgrounds/src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import { css } from "@stitches/core"

import { TreeNode, TreeRoot } from "@resembli/ui"

const AppCss = css({
display: "flex",
alignItems: "center",
Expand All @@ -10,18 +12,28 @@ const AppCss = css({
background: "Beige",
})

const rowHeights = Array.from({ length: 1000 }, (_, i) => [20, 40, 20, 60][i % 4]).reduce(
(acc, h, i) => {
acc[i] = h

return acc
},
{} as Record<number, number>,
)

console.log(rowHeights)
function App() {
return <div className={AppCss()}></div>
return (
<div className={AppCss()}>
<div style={{ height: 500, width: 500, border: "1px solid black" }}>
<TreeRoot>
<TreeNode item={<div>Applications</div>}>
<TreeNode item="Calendar" />
<TreeNode item="Chrome" />
<TreeNode item="Webstorm" />
</TreeNode>
<TreeNode item={<div>Documents</div>}>
<TreeNode item="UI">
<TreeNode item="src">
<TreeNode item="index.js" />
<TreeNode item="tree-view.js" />
</TreeNode>
</TreeNode>
</TreeNode>
</TreeRoot>
</div>
</div>
)
}

export default App

0 comments on commit 288e0b3

Please sign in to comment.