Tree View
A component that is used to show a tree hierarchy
Usage
- Item 1
- Item 1.1
- Item 1.2
- Item 1.2.1
- Item 1.2.2
- Item 2
- Item 2.1
- Item 2.2
- Item 3
import { TreeView, type TreeViewData } from '~/components/ui'
export const Demo = () => {
return <TreeView data={data} maxW="2xs" />
}
const data: TreeViewData = {
label: 'Root',
children: [
{
value: '1',
name: 'Item 1',
children: [
{
value: '1.1',
name: 'Item 1.1',
},
{
value: '1.2',
name: 'Item 1.2',
children: [
{
value: '1.2.1',
name: 'Item 1.2.1',
},
{
value: '1.2.2',
name: 'Item 1.2.2',
},
],
},
],
},
{
value: '2',
name: 'Item 2',
children: [
{
value: '2.1',
name: 'Item 2.1',
},
{
value: '2.2',
name: 'Item 2.2',
},
],
},
{
value: '3',
name: 'Item 3',
},
],
}
Installation
npx @park-ui/cli components add tree-view
1
Styled Primitive
Copy the code snippet below into ~/components/ui/primitives/tree-view.tsx
'use client'
import type { Assign } from '@ark-ui/react'
import { TreeView } from '@ark-ui/react/tree-view'
import { type TreeViewVariantProps, treeView } from 'styled-system/recipes'
import type { ComponentProps, HTMLStyledProps } from 'styled-system/types'
import { createStyleContext } from '~/lib/create-style-context'
const { withProvider, withContext } = createStyleContext(treeView)
export type RootProviderProps = ComponentProps<typeof RootProvider>
export const RootProvider = withProvider<
HTMLDivElement,
Assign<Assign<HTMLStyledProps<'div'>, TreeView.RootProviderBaseProps>, TreeViewVariantProps>
>(TreeView.RootProvider, 'root')
export type RootProps = ComponentProps<typeof Root>
export const Root = withProvider<
HTMLDivElement,
Assign<Assign<HTMLStyledProps<'div'>, TreeView.RootBaseProps>, TreeViewVariantProps>
>(TreeView.Root, 'root')
export const BranchContent = withContext<
HTMLUListElement,
Assign<HTMLStyledProps<'ul'>, TreeView.BranchContentBaseProps>
>(TreeView.BranchContent, 'branchContent')
export const BranchControl = withContext<
HTMLDivElement,
Assign<HTMLStyledProps<'div'>, TreeView.BranchControlBaseProps>
>(TreeView.BranchControl, 'branchControl')
export const BranchIndicator = withContext<
HTMLDivElement,
Assign<HTMLStyledProps<'div'>, TreeView.BranchIndicatorBaseProps>
>(TreeView.BranchIndicator, 'branchIndicator')
export const Branch = withContext<
HTMLLIElement,
Assign<HTMLStyledProps<'li'>, TreeView.BranchBaseProps>
>(TreeView.Branch, 'branch')
export const BranchText = withContext<
HTMLSpanElement,
Assign<HTMLStyledProps<'span'>, TreeView.BranchTextBaseProps>
>(TreeView.BranchText, 'branchText')
export const BranchTrigger = withContext<
HTMLDivElement,
Assign<HTMLStyledProps<'div'>, TreeView.BranchTriggerBaseProps>
>(TreeView.BranchTrigger, 'branchTrigger')
export const ItemIndicator = withContext<
HTMLDivElement,
Assign<HTMLStyledProps<'div'>, TreeView.ItemIndicatorBaseProps>
>(TreeView.ItemIndicator, 'itemIndicator')
export const Item = withContext<
HTMLLIElement,
Assign<HTMLStyledProps<'li'>, TreeView.ItemBaseProps>
>(TreeView.Item, 'item')
export const ItemText = withContext<
HTMLSpanElement,
Assign<HTMLStyledProps<'span'>, TreeView.ItemTextBaseProps>
>(TreeView.ItemText, 'itemText')
export const Label = withContext<
HTMLLabelElement,
Assign<HTMLStyledProps<'label'>, TreeView.LabelBaseProps>
>(TreeView.Label, 'label')
export const Tree = withContext<
HTMLUListElement,
Assign<HTMLStyledProps<'ul'>, TreeView.TreeBaseProps>
>(TreeView.Tree, 'tree')
export { TreeViewContext as Context } from '@ark-ui/react/tree-view'
import { type Assign, TreeView } from '@ark-ui/solid'
import type { ComponentProps } from 'solid-js'
import { type TreeViewVariantProps, treeView } from 'styled-system/recipes'
import type { HTMLStyledProps } from 'styled-system/types'
import { createStyleContext } from '~/lib/create-style-context'
const { withProvider, withContext } = createStyleContext(treeView)
export type RootProviderProps = ComponentProps<typeof RootProvider>
export const RootProvider = withProvider<
Assign<Assign<HTMLStyledProps<'div'>, TreeView.RootProviderBaseProps>, TreeViewVariantProps>
>(TreeView.RootProvider, 'root')
export type RootProps = ComponentProps<typeof Root>
export const Root = withProvider<
Assign<Assign<HTMLStyledProps<'div'>, TreeView.RootBaseProps>, TreeViewVariantProps>
>(TreeView.Root, 'root')
export const BranchContent = withContext<
Assign<HTMLStyledProps<'ul'>, TreeView.BranchContentBaseProps>
>(TreeView.BranchContent, 'branchContent')
export const BranchControl = withContext<
Assign<HTMLStyledProps<'div'>, TreeView.BranchControlBaseProps>
>(TreeView.BranchControl, 'branchControl')
export const BranchIndicator = withContext<
Assign<HTMLStyledProps<'div'>, TreeView.BranchIndicatorBaseProps>
>(TreeView.BranchIndicator, 'branchIndicator')
export const Branch = withContext<Assign<HTMLStyledProps<'li'>, TreeView.BranchBaseProps>>(
TreeView.Branch,
'branch',
)
export const BranchText = withContext<
Assign<HTMLStyledProps<'span'>, TreeView.BranchTextBaseProps>
>(TreeView.BranchText, 'branchText')
export const BranchTrigger = withContext<
Assign<HTMLStyledProps<'div'>, TreeView.BranchTriggerBaseProps>
>(TreeView.BranchTrigger, 'branchTrigger')
export const ItemIndicator = withContext<
Assign<HTMLStyledProps<'div'>, TreeView.ItemIndicatorBaseProps>
>(TreeView.ItemIndicator, 'itemIndicator')
export const Item = withContext<Assign<HTMLStyledProps<'li'>, TreeView.ItemBaseProps>>(
TreeView.Item,
'item',
)
export const ItemText = withContext<Assign<HTMLStyledProps<'span'>, TreeView.ItemTextBaseProps>>(
TreeView.ItemText,
'itemText',
)
export const Label = withContext<Assign<HTMLStyledProps<'label'>, TreeView.LabelBaseProps>>(
TreeView.Label,
'label',
)
export const Tree = withContext<Assign<HTMLStyledProps<'ul'>, TreeView.TreeBaseProps>>(
TreeView.Tree,
'tree',
)
export { TreeViewContext as Context } from '@ark-ui/solid'
No snippet found
Extend ~/components/ui/primitives/index.ts
with the following line:
export * as TreeView from './tree-view'
2
Add Composition
Copy the code snippet below into ~/components/ui/tree-view.tsx
'use client'
import { forwardRef } from 'react'
import { TreeView as ArkTreeView } from '~/components/ui/primitives'
interface Child {
value: string
name: string
children?: Child[]
}
export interface TreeViewData {
label: string
children: Child[]
}
export interface TreeViewProps extends ArkTreeView.RootProps {
data: TreeViewData
}
export const TreeView = forwardRef<HTMLDivElement, TreeViewProps>((props, ref) => {
const { data, ...rootProps } = props
const renderChild = (child: Child) => (
<ArkTreeView.Branch key={child.value} value={child.value}>
<ArkTreeView.BranchControl>
<ArkTreeView.BranchIndicator>
<ChevronRightIcon />
</ArkTreeView.BranchIndicator>
<ArkTreeView.BranchText>{child.name}</ArkTreeView.BranchText>
</ArkTreeView.BranchControl>
<ArkTreeView.BranchContent>
{child.children?.map((child) =>
child.children ? (
renderChild(child)
) : (
<ArkTreeView.Item key={child.value} value={child.value}>
<ArkTreeView.ItemText>{child.name}</ArkTreeView.ItemText>
</ArkTreeView.Item>
),
)}
</ArkTreeView.BranchContent>
</ArkTreeView.Branch>
)
return (
<ArkTreeView.Root ref={ref} aria-label={data.label} {...rootProps}>
<ArkTreeView.Tree>{data.children.map(renderChild)}</ArkTreeView.Tree>
</ArkTreeView.Root>
)
})
TreeView.displayName = 'TreeView'
const ChevronRightIcon = () => (
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
<title>Chevron Right Icon</title>
<path
fill="none"
stroke="currentColor"
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="2"
d="m9 18l6-6l-6-6"
/>
</svg>
)
import { For, Show, splitProps } from 'solid-js'
import { TreeView as ArkTreeView } from '~/components/ui/primitives'
interface Child {
value: string
name: string
children?: Child[]
}
export interface TreeViewData {
label: string
children: Child[]
}
export interface TreeViewProps extends ArkTreeView.RootProps {
data: TreeViewData
}
export const TreeView = (props: TreeViewProps) => {
const [localProps, rootProps] = splitProps(props, ['data'])
const renderChild = (child: Child) => (
<Show
when={child.children}
fallback={
<ArkTreeView.Item value={child.value}>
<ArkTreeView.ItemText>{child.name}</ArkTreeView.ItemText>
</ArkTreeView.Item>
}
>
<ArkTreeView.Branch value={child.value}>
<ArkTreeView.BranchControl>
<ArkTreeView.BranchIndicator>
<ChevronRightIcon />
</ArkTreeView.BranchIndicator>
<ArkTreeView.BranchText>{child.name}</ArkTreeView.BranchText>
</ArkTreeView.BranchControl>
<ArkTreeView.BranchContent>
<For each={child.children}>{(child) => renderChild(child)}</For>
</ArkTreeView.BranchContent>
</ArkTreeView.Branch>
</Show>
)
return (
<ArkTreeView.Root aria-label={localProps.data.label} {...rootProps}>
<ArkTreeView.Tree>
<For each={localProps.data.children}>{(child) => renderChild(child)}</For>
</ArkTreeView.Tree>
</ArkTreeView.Root>
)
}
const ChevronRightIcon = () => (
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
<title>Chevron Right Icon</title>
<path
fill="none"
stroke="currentColor"
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="m9 18l6-6l-6-6"
/>
</svg>
)
Extend ~/components/ui/index.ts
with the following line:
export * from './primitives'
export { TreeView, type TreeViewProps, type TreeViewData } from './tree-view'
3
Integrate Recipe
If you're not using @park-ui/preset
, add the following recipe to yourpanda.config.ts
:
import { treeViewAnatomy } from '@ark-ui/anatomy'
import { defineSlotRecipe } from '@pandacss/dev'
export const treeView = defineSlotRecipe({
className: 'treeView',
slots: treeViewAnatomy.keys(),
base: {
root: {
width: 'full',
},
branch: {
"&[data-depth='1'] > [data-part='branch-content']": {
_before: {
bg: 'border.default',
content: '""',
height: 'full',
left: '3',
position: 'absolute',
width: '1px',
zIndex: '1',
},
},
},
branchContent: {
position: 'relative',
},
branchControl: {
alignItems: 'center',
borderRadius: 'l2',
color: 'fg.muted',
display: 'flex',
fontWeight: 'medium',
gap: '1.5',
ps: 'calc((var(--depth) - 1) * 22px)',
py: '1.5',
textStyle: 'sm',
transitionDuration: 'normal',
transitionProperty: 'background, color',
transitionTimingFunction: 'default',
"&[data-depth='1']": {
ps: '1',
},
"&[data-depth='1'] > [data-part='branch-text'] ": {
fontWeight: 'semibold',
color: 'fg.default',
},
_hover: {
background: 'gray.a2',
color: 'fg.default',
},
},
branchIndicator: {
color: 'accent.default',
transformOrigin: 'center',
transitionDuration: 'normal',
transitionProperty: 'transform',
transitionTimingFunction: 'default',
'& svg': {
fontSize: 'md',
width: '4',
height: '4',
},
_open: {
transform: 'rotate(90deg)',
},
},
item: {
borderRadius: 'l2',
color: 'fg.muted',
cursor: 'pointer',
fontWeight: 'medium',
position: 'relative',
ps: 'calc(((var(--depth) - 1) * 22px) + 22px)',
py: '1.5',
textStyle: 'sm',
transitionDuration: 'normal',
transitionProperty: 'background, color',
transitionTimingFunction: 'default',
"&[data-depth='1']": {
ps: '6',
fontWeight: 'semibold',
color: 'fg.default',
_selected: {
_before: {
bg: 'transparent',
},
},
},
_hover: {
background: 'gray.a2',
color: 'fg.default',
},
_selected: {
background: 'accent.a2',
color: 'accent.text',
_hover: {
background: 'accent.a2',
color: 'accent.text',
},
_before: {
content: '""',
position: 'absolute',
left: '3',
top: '0',
width: '2px',
height: 'full',
bg: 'accent.default',
zIndex: '1',
},
},
},
tree: {
display: 'flex',
flexDirection: 'column',
gap: '3',
},
},
})