[Design System] Chore: add tree-view documentation (#26821)

* chore: init docs

* remove old stories

* update source panels
This commit is contained in:
Jonathan Summers-Muir authored and GitHub committed 2024-05-28 17:22:37 +08:00
1 parent 33abcaa873
commit 73ff6c1085
15 files changed
+662 -487

No files matched your search

+44
View File
@@ -1699,6 +1699,50 @@ export const Index: Record<string, any> = {
subcategory: "undefined",
chunks: []
},
"tree-view-demo": {
name: "tree-view-demo",
type: "components:example",
registryDependencies: undefined,
component: React.lazy(() => import("@/registry/default/example/tree-view-demo")),
source: "",
files: ["registry/default/example/tree-view-demo.tsx"],
category: "undefined",
subcategory: "undefined",
chunks: []
},
"tree-view-edit": {
name: "tree-view-edit",
type: "components:example",
registryDependencies: undefined,
component: React.lazy(() => import("@/registry/default/example/tree-view-edit")),
source: "",
files: ["registry/default/example/tree-view-edit.tsx"],
category: "undefined",
subcategory: "undefined",
chunks: []
},
"tree-view-directories": {
name: "tree-view-directories",
type: "components:example",
registryDependencies: undefined,
component: React.lazy(() => import("@/registry/default/example/tree-view-directories")),
source: "",
files: ["registry/default/example/tree-view-directories.tsx"],
category: "undefined",
subcategory: "undefined",
chunks: []
},
"tree-view-multi-select": {
name: "tree-view-multi-select",
type: "components:example",
registryDependencies: undefined,
component: React.lazy(() => import("@/registry/default/example/tree-view-multi-select")),
source: "",
files: ["registry/default/example/tree-view-multi-select.tsx"],
category: "undefined",
subcategory: "undefined",
chunks: []
},
"color-usage-surface-www-and-docs": {
name: "color-usage-surface-www-and-docs",
type: "docs:example",
+74 -11
View File
@@ -9,12 +9,6 @@ import Image from 'next/image'
const SourcePanel = forwardRef<HTMLDivElement, React.HTMLProps<HTMLDivElement> & { doc: Doc }>(
({ doc, children, ...props }, ref) => {
const label = {
radix: 'Radix UI',
vaul: 'Vaul',
shadcn: 'ui.shadcn',
}
const ShadcnPanel = () => {
if (doc.source?.shadcn) {
return (
@@ -79,9 +73,9 @@ const SourcePanel = forwardRef<HTMLDivElement, React.HTMLProps<HTMLDivElement> &
alt="Vaul"
className="h-6 w-6 rounded-full"
/>
<span className="hidden font-bold sm:inline-block">vaul</span>
<span className="hidden font-bold text-xs sm:inline-block">vaul</span>
</div>
<span className="text-foreground-light text-sm">
<span className="text-foreground-light text-xs">
This component is based on vaul by emilkowalski
</span>
</div>
@@ -89,6 +83,74 @@ const SourcePanel = forwardRef<HTMLDivElement, React.HTMLProps<HTMLDivElement> &
}
}
const ReactAccesibleTreeViewPanel = () => {
if (doc.source?.reactAccessibleTreeview) {
return (
<div
className={cn(
'bg-surface-75/50 border flex items-center p-3 px-5 gap-6 first:rounded-t-md last:rounded-b-md justify-between',
props.className
)}
{...props}
>
<div className="flex items-center gap-2">
<Image
width={24}
height={24}
src="https://avatars.githubusercontent.com/u/14020024?v=4"
alt="dgreene1"
className="h-6 w-6 rounded-full"
/>
<span className="hidden text-xs font-bold sm:inline-block">
react-accessible-treeview
</span>
<span className="text-foreground-light text-xs">
Component based on react-accessible-treeview by dgreene1
</span>
</div>
{doc.links ? (
<div className="flex items-center gap-2 justify-end">
{doc.links?.doc && (
<Button
type="outline"
className="rounded-full"
icon={<ExternalLink className="text-foreground-muted" strokeWidth={1} />}
>
<Link
href={doc.links.doc}
target="_blank"
rel="noreferrer"
// className={cn(buttonVariants({ variant: 'default' }), 'gap-1')}
>
Docs
</Link>
</Button>
)}
{doc.links?.api && (
<Button
type="outline"
className="rounded-full"
icon={<ExternalLink className="text-foreground-muted" strokeWidth={1} />}
>
<Link
href={doc.links.api}
target="_blank"
rel="noreferrer"
// className={cn(badgeVariants({ variant: 'default' }), 'gap-1')}
>
API Reference
</Link>
</Button>
)}
</div>
) : null}
</div>
)
}
}
const InputOtp = () => {
if (doc.source?.inputOtp) {
return (
@@ -107,9 +169,9 @@ const SourcePanel = forwardRef<HTMLDivElement, React.HTMLProps<HTMLDivElement> &
alt="inputOtp"
className="h-6 w-6 rounded-full"
/>
<span className="hidden font-bold sm:inline-block">input-otp</span>
<span className="hidden font-bold text-xs sm:inline-block">input-otp</span>
</div>
<span className="text-foreground-light text-sm">
<span className="text-foreground-light text-xs">
This component is based on input-otp by guilhermerodz
</span>
</div>
@@ -145,7 +207,7 @@ const SourcePanel = forwardRef<HTMLDivElement, React.HTMLProps<HTMLDivElement> &
<path d="M11.5 7C13.433 7 15 5.433 15 3.5C15 1.567 13.433 0 11.5 0C9.56704 0 8 1.567 8 3.5C8 5.433 9.56704 7 11.5 7Z"></path>
</svg>
<div className="flex flex-row items-center justify-between text-sm w-full">
<span className="text-foreground-light text-sm">This component uses Radix UI</span>
<span className="text-foreground-light text-xs">This component uses Radix UI</span>
{doc.links ? (
<div className="flex items-center gap-2 justify-end">
{doc.links?.doc && (
@@ -195,6 +257,7 @@ const SourcePanel = forwardRef<HTMLDivElement, React.HTMLProps<HTMLDivElement> &
<RadixPanel />
<VaulPanel />
<InputOtp />
<ReactAccesibleTreeViewPanel />
{/* <ShadcnPanel /> */}
</div>
)
+5
View File
@@ -364,6 +364,11 @@ export const docsConfig: DocsConfig = {
href: '/docs/components/tooltip',
items: [],
},
{
title: 'Tree View',
href: '/docs/components/tree-view',
items: [],
},
],
},
],
@@ -0,0 +1,121 @@
---
title: Tree View
description: A tree view that assembles all the functionalities of the Accordion component to create a tree view.
component: true
links:
doc: https://dgreene1.github.io/react-accessible-treeview/
api: https://dgreene1.github.io/react-accessible-treeview/docs/api
source:
reactAccessibleTreeview: true
---
<ComponentPreview name="tree-view-demo" peekCode wide />
## Usage
```tsx
import { TreeView, TreeViewItem } from 'ui'
import { flattenTree } from 'react-accessible-treeview'
```
```tsx
const data: {
name: ''
children: [
{
name: 'Current batch'
},
{
name: 'Older queries'
},
{
name: 'query all users'
},
{
name: 'users in last day'
},
{
name: 'new users over time'
},
]
}
function App() {
return (
<TreeView
{...args}
data={flattenTree(args.data)}
aria-label="directory tree"
nodeRenderer={({ element, isBranch, isExpanded, getNodeProps, level, isSelected }) => (
<TreeViewItem
isExpanded={isExpanded}
isBranch={isBranch}
isSelected={isSelected}
level={level}
xPadding={16}
name={element.name}
{...getNodeProps()}
/>
)}
/>
)
}
```
## Examples
### With directories
Use a `children` node in your data object to create a tree view with directories.
```tsx {9-16}
const data = {
//..
children: [
{
name: 'Current batch',
},
{
name: 'Older queries',
children: [
{
name: 'all countries',
},
{
name: 'add new countries',
},
],
},
],
}
```
<ComponentPreview name="tree-view-directories" />
### With inline editing
The Tree View can be edited in the app to enable different types of inline editing.
In this example, right click on any item to rename it, and we traverse the data object setting `isEditing` to true.
`TreeViewItem` then accepts a `isEditing` prop to show and focus an inline input to edit.
<ComponentPreview name="tree-view-edit" />
### With multi selection
Use the `multiSelect`, `togglableSelect`, `clickAction` set to "EXCLUSIVE_SELECT" to enable a multi-select tree view.
An example can be seen on the [react-accessible-treeview docs](https://dgreene1.github.io/react-accessible-treeview/docs/examples-MultiSelectDirectoryTree).
```tsx {4-6}
<TreeView
data={flattenTree(args.data)}
aria-label="directory tree"
multiSelect
togglableSelect
clickAction="EXCLUSIVE_SELECT"
// other options..
/>
```
<ComponentPreview name="tree-view-multi-select" />
@@ -50,6 +50,9 @@ const NestedProperties = defineNestedType(() => ({
inputOtp: {
type: 'boolean',
},
reactAccessibleTreeview: {
type: 'boolean',
},
},
}))
@@ -0,0 +1,44 @@
import { TreeView, TreeViewItem } from 'ui'
import { flattenTree } from 'react-accessible-treeview'
export default function TreeViewDemo() {
const data = {
name: '',
children: [
{
name: 'Current batch',
},
{
name: 'Older queries',
},
{
name: 'query all users',
},
{
name: 'users in last day',
},
{
name: 'new users over time',
},
],
}
return (
<TreeView
data={flattenTree(data)}
aria-label="directory tree"
className="w-[420px]"
nodeRenderer={({ element, isBranch, isExpanded, getNodeProps, level, isSelected }) => (
<TreeViewItem
isExpanded={isExpanded}
isBranch={isBranch}
isSelected={isSelected}
level={level}
xPadding={16}
name={element.name}
{...getNodeProps()}
/>
)}
/>
)
}
@@ -0,0 +1,59 @@
import { TreeView, TreeViewItem } from 'ui'
import { flattenTree } from 'react-accessible-treeview'
export default function TreeViewDemo() {
const data = {
name: '',
children: [
{
name: 'Current batch',
children: [{ name: 'index.js' }, { name: 'styles.css' }],
},
{
name: 'Older queries',
children: [
{
name: 'all countries',
},
{
name: 'add new countries',
},
{
name: 'regions',
},
{
name: 'regions by customer',
},
],
},
{
name: 'query all users',
},
{
name: 'users in last day',
},
{
name: 'new users over time',
},
],
}
return (
<TreeView
data={flattenTree(data)}
aria-label="directory tree"
className="w-[420px]"
nodeRenderer={({ element, isBranch, isExpanded, getNodeProps, level, isSelected }) => (
<TreeViewItem
isExpanded={isExpanded}
isBranch={isBranch}
isSelected={isSelected}
level={level}
xPadding={16}
name={element.name}
{...getNodeProps()}
/>
)}
/>
)
}
@@ -0,0 +1,142 @@
import { useState } from 'react'
import { TreeView, TreeViewItem } from 'ui'
import { flattenTree } from 'react-accessible-treeview'
import {
ContextMenu_Shadcn_,
ContextMenuContent_Shadcn_,
ContextMenuItem_Shadcn_,
ContextMenuSeparator_Shadcn_,
ContextMenuTrigger_Shadcn_,
} from 'ui'
const data = {
name: 'basic tree',
children: [
{
id: 1,
name: 'Current batch',
children: [
{ id: 2, name: 'index.js' },
{ id: 3, name: 'styles.css' },
],
},
{
id: 4,
name: 'Older queries',
children: [
{
id: 5,
name: 'all countries',
},
{
id: 6,
name: 'add new countries',
},
{
id: 7,
name: 'regions',
},
{
id: 8,
name: 'regions by customer',
},
],
},
{
id: 9,
name: 'query all users',
},
{
id: 10,
name: 'users in last day',
},
{
id: 11,
name: 'new users over time',
},
],
}
export default function TreeViewDemo() {
const [treeData, setDataTreeState] = useState(data)
function handleRenameSelect(id: number) {
// find id in treeData and set it to edit mode
let updatedTreeData = { ...treeData }
const findNode = (node: any) => {
if (node.id === id) {
node.metadata = { isEditing: true }
}
if (node.children) {
node.children.forEach(findNode)
}
}
console.log(updatedTreeData)
updatedTreeData.children.forEach(findNode)
setDataTreeState(updatedTreeData)
}
return (
<TreeView
data={flattenTree(data)}
aria-label="directory tree"
nodeRenderer={({ element, isBranch, isExpanded, getNodeProps, level, isSelected }) => {
return (
<ContextMenu_Shadcn_ modal={false}>
<ContextMenuTrigger_Shadcn_ asChild>
<TreeViewItem
isExpanded={isExpanded}
isBranch={isBranch}
isSelected={isSelected}
level={level}
xPadding={16}
name={element.name}
isEditing={element.metadata?.isEditing === true}
onEditSubmit={(value) => {
console.log('value', value)
let updatedTreeData = { ...treeData }
const findNode = (node: any) => {
if (node.id === element.id) {
node.name = value
node.metadata = { isEditing: false }
}
if (node.children) {
node.children.forEach(findNode)
}
}
updatedTreeData.children.forEach(findNode)
setDataTreeState(updatedTreeData)
}}
{...getNodeProps()}
/>
</ContextMenuTrigger_Shadcn_>
<ContextMenuContent_Shadcn_
onCloseAutoFocus={(e) => {
// stop focus propagation
// so input in TreeViewItem gets focus
e.stopPropagation()
}}
>
<ContextMenuItem_Shadcn_
onSelect={() => {
handleRenameSelect(element.id as number)
}}
onFocusCapture={(e) => {
// stop focus propagation
// so input in TreeViewItem gets focus
e.stopPropagation()
}}
>
Rename
</ContextMenuItem_Shadcn_>
<ContextMenuItem_Shadcn_ disabled>Open in new tab</ContextMenuItem_Shadcn_>
<ContextMenuItem_Shadcn_ disabled>Share with team</ContextMenuItem_Shadcn_>
<ContextMenuSeparator_Shadcn_ />
<ContextMenuItem_Shadcn_ disabled>Delete</ContextMenuItem_Shadcn_>
</ContextMenuContent_Shadcn_>
</ContextMenu_Shadcn_>
)
}}
/>
)
}
@@ -0,0 +1,145 @@
import { useState } from 'react'
import { TreeView, TreeViewItem } from 'ui'
import { flattenTree } from 'react-accessible-treeview'
import {
ContextMenu_Shadcn_,
ContextMenuContent_Shadcn_,
ContextMenuItem_Shadcn_,
ContextMenuSeparator_Shadcn_,
ContextMenuTrigger_Shadcn_,
} from 'ui'
const data = {
name: 'basic tree',
children: [
{
id: 1,
name: 'Current batch',
children: [
{ id: 2, name: 'index.js' },
{ id: 3, name: 'styles.css' },
],
},
{
id: 4,
name: 'Older queries',
children: [
{
id: 5,
name: 'all countries',
},
{
id: 6,
name: 'add new countries',
},
{
id: 7,
name: 'regions',
},
{
id: 8,
name: 'regions by customer',
},
],
},
{
id: 9,
name: 'query all users',
},
{
id: 10,
name: 'users in last day',
},
{
id: 11,
name: 'new users over time',
},
],
}
export default function TreeViewDemo() {
const [treeData, setDataTreeState] = useState(data)
function handleRenameSelect(id: number) {
// find id in treeData and set it to edit mode
let updatedTreeData = { ...treeData }
const findNode = (node: any) => {
if (node.id === id) {
node.metadata = { isEditing: true }
}
if (node.children) {
node.children.forEach(findNode)
}
}
console.log(updatedTreeData)
updatedTreeData.children.forEach(findNode)
setDataTreeState(updatedTreeData)
}
return (
<TreeView
data={flattenTree(data)}
aria-label="directory tree"
togglableSelect
clickAction="EXCLUSIVE_SELECT"
multiSelect
nodeRenderer={({ element, isBranch, isExpanded, getNodeProps, level, isSelected }) => {
return (
<ContextMenu_Shadcn_ modal={false}>
<ContextMenuTrigger_Shadcn_ asChild>
<TreeViewItem
isExpanded={isExpanded}
isBranch={isBranch}
isSelected={isSelected}
level={level}
xPadding={16}
name={element.name}
isEditing={element.metadata?.isEditing === true}
onEditSubmit={(value) => {
console.log('value', value)
let updatedTreeData = { ...treeData }
const findNode = (node: any) => {
if (node.id === element.id) {
node.name = value
node.metadata = { isEditing: false }
}
if (node.children) {
node.children.forEach(findNode)
}
}
updatedTreeData.children.forEach(findNode)
setDataTreeState(updatedTreeData)
}}
{...getNodeProps()}
/>
</ContextMenuTrigger_Shadcn_>
<ContextMenuContent_Shadcn_
onCloseAutoFocus={(e) => {
// stop focus propagation
// so input in TreeViewItem gets focus
e.stopPropagation()
}}
>
<ContextMenuItem_Shadcn_
onSelect={(e) => {
handleRenameSelect(element.id as number)
}}
onFocusCapture={(e) => {
// stop focus propagation
// so input in TreeViewItem gets focus
e.stopPropagation()
}}
>
Rename
</ContextMenuItem_Shadcn_>
<ContextMenuItem_Shadcn_ disabled>Open in new tab</ContextMenuItem_Shadcn_>
<ContextMenuItem_Shadcn_ disabled>Share with team</ContextMenuItem_Shadcn_>
<ContextMenuSeparator_Shadcn_ />
<ContextMenuItem_Shadcn_ disabled>Delete</ContextMenuItem_Shadcn_>
</ContextMenuContent_Shadcn_>
</ContextMenu_Shadcn_>
)
}}
/>
)
}
+20
View File
@@ -986,6 +986,26 @@ export const examples: Registry = [
type: 'components:example',
files: ['example/multi-select-form.tsx'],
},
{
name: 'tree-view-demo',
type: 'components:example',
files: ['example/tree-view-demo.tsx'],
},
{
name: 'tree-view-edit',
type: 'components:example',
files: ['example/tree-view-edit.tsx'],
},
{
name: 'tree-view-directories',
type: 'components:example',
files: ['example/tree-view-directories.tsx'],
},
{
name: 'tree-view-multi-select',
type: 'components:example',
files: ['example/tree-view-multi-select.tsx'],
},
{
name: 'color-usage-surface-www-and-docs',
type: 'docs:example',
+2
View File
@@ -242,6 +242,8 @@ export * from './src/components/shadcn/ui/resizable'
export * from './src/components/radio-group-stacked'
export * from './src/components/radio-group-card'
export * from './src/components/TreeView'
// links
export * from './src/components/TextLink'
@@ -1,438 +0,0 @@
import { StoryObj } from '@storybook/react'
import React, { useState } from 'react'
import { flattenTree } from 'react-accessible-treeview'
import {
ContextMenu,
ContextMenuContent,
ContextMenuItem,
ContextMenuSeparator,
ContextMenuTrigger,
} from '../shadcn/ui/context-menu'
import { TreeView, TreeViewItem } from './TreeView'
// More on how to set up stories at: https://storybook.js.org/docs/writing-stories#default-export
export default {
title: 'Primitives/TreeView',
component: TreeView,
subcomponents: { TreeViewItem },
parameters: {
// Optional parameter to center the component in the Canvas. More info: https://storybook.js.org/docs/configure/story-layout
layout: 'centered',
},
decorators: [
(Story: any) => {
return (
<div className="bg-studio border-l border-r">
<Story />
</div>
)
},
],
// This component will have an automatically generated Autodocs entry: https://storybook.js.org/docs/writing-docs/autodocs
tags: ['autodocs'],
// More on argTypes: https://storybook.js.org/docs/api/argtypes
argTypes: {
data: {},
},
}
type Story = StoryObj<
// Omit data so raw data can be passed to the story
Omit<typeof TreeView, 'data'> & { data: any; className?: string }
>
export const DefaultFlatTree: Story = {
args: {
className: 'w-[420px] h-[520px]',
data: {
name: '',
children: [
{
name: 'Current batch',
},
{
name: 'Older queries',
},
{
name: 'query all users',
},
{
name: 'users in last day',
},
{
name: 'new users over time',
},
],
},
},
/**
* 👇 To avoid linting issues, it is recommended to use a function with a capitalized name.
* If you are not concerned with linting, you may use an arrow function.
*/
render: function Render(args) {
return (
<TreeView
{...args}
data={flattenTree(args.data)}
aria-label="directory tree"
nodeRenderer={({ element, isBranch, isExpanded, getNodeProps, level, isSelected }) => (
<TreeViewItem
isExpanded={isExpanded}
isBranch={isBranch}
isSelected={isSelected}
level={level}
xPadding={16}
name={element.name}
{...getNodeProps()}
/>
)}
/>
)
},
}
export const WithDirectories: Story = {
args: {
className: 'w-[420px] h-[520px]',
data: {
name: '',
children: [
{
name: 'Current batch',
children: [{ name: 'index.js' }, { name: 'styles.css' }],
},
{
name: 'Older queries',
children: [
{
name: 'all countries',
},
{
name: 'add new countries',
},
{
name: 'regions',
},
{
name: 'regions by customer',
},
],
},
{
name: 'query all users',
},
{
name: 'users in last day',
},
{
name: 'new users over time',
},
],
},
},
/**
* 👇 To avoid linting issues, it is recommended to use a function with a capitalized name.
* If you are not concerned with linting, you may use an arrow function.
*/
render: function Render(args) {
return (
<TreeView
{...args}
data={flattenTree(args.data)}
aria-label="directory tree"
nodeRenderer={({ element, isBranch, isExpanded, getNodeProps, level, isSelected }) => (
<TreeViewItem
isExpanded={isExpanded}
isBranch={isBranch}
isSelected={isSelected}
level={level}
xPadding={16}
name={element.name}
{...getNodeProps()}
/>
)}
/>
)
},
}
export const withEditEntity: Story = {
args: {
className: 'w-[420px] h-[520px]',
data: {
name: 'basic tree',
children: [
{
id: 1,
name: 'Current batch',
children: [
{ id: 2, name: 'index.js' },
{ id: 3, name: 'styles.css' },
],
},
{
id: 4,
name: 'Older queries',
children: [
{
id: 5,
name: 'all countries',
},
{
id: 6,
name: 'add new countries',
},
{
id: 7,
name: 'regions',
},
{
id: 8,
name: 'regions by customer',
},
],
},
{
id: 9,
name: 'query all users',
},
{
id: 10,
name: 'users in last day',
},
{
id: 11,
name: 'new users over time',
},
],
},
},
/**
* 👇 To avoid linting issues, it is recommended to use a function with a capitalized name.
* If you are not concerned with linting, you may use an arrow function.
*/
render: function Render(args) {
const [treeData, setDataTreeState] = useState(args.data)
function handleRenameSelect(id) {
// find id in treeData and set it to edit mode
let updatedTreeData = { ...treeData }
const findNode = (node: any) => {
if (node.id === id) {
node.metadata = { isEditing: true }
}
if (node.children) {
node.children.forEach(findNode)
}
}
console.log(updatedTreeData)
updatedTreeData.children.forEach(findNode)
setDataTreeState(updatedTreeData)
}
return (
<TreeView
{...args}
data={flattenTree(args.data)}
aria-label="directory tree"
nodeRenderer={({ element, isBranch, isExpanded, getNodeProps, level, isSelected }) => {
return (
<ContextMenu modal={false}>
<ContextMenuTrigger asChild>
<TreeViewItem
isExpanded={isExpanded}
isBranch={isBranch}
isSelected={isSelected}
level={level}
xPadding={16}
name={element.name}
isEditing={element.metadata?.isEditing === true}
onEditSubmit={(value) => {
console.log('value', value)
let updatedTreeData = { ...treeData }
const findNode = (node: any) => {
if (node.id === element.id) {
node.name = value
node.metadata = { isEditing: false }
}
if (node.children) {
node.children.forEach(findNode)
}
}
updatedTreeData.children.forEach(findNode)
setDataTreeState(updatedTreeData)
}}
{...getNodeProps()}
/>
</ContextMenuTrigger>
<ContextMenuContent
onCloseAutoFocus={(e) => {
// stop focus propagation
// so input in TreeViewItem gets focus
e.stopPropagation()
}}
>
<ContextMenuItem
onSelect={(e) => {
handleRenameSelect(element.id)
}}
onFocusCapture={(e) => {
// stop focus propagation
// so input in TreeViewItem gets focus
e.stopPropagation()
}}
>
Rename
</ContextMenuItem>
<ContextMenuItem disabled>Open in new tab</ContextMenuItem>
<ContextMenuItem disabled>Share with team</ContextMenuItem>
<ContextMenuSeparator />
<ContextMenuItem disabled>Delete</ContextMenuItem>
</ContextMenuContent>
</ContextMenu>
)
}}
/>
)
},
}
export const withMultiSelect: Story = {
args: {
className: 'w-[420px] h-[520px]',
multiselect: true,
data: {
name: 'basic tree',
children: [
{
id: 1,
name: 'Current batch',
children: [
{ id: 2, name: 'index.js' },
{ id: 3, name: 'styles.css' },
],
},
{
id: 4,
name: 'Older queries',
children: [
{
id: 5,
name: 'all countries',
},
{
id: 6,
name: 'add new countries',
},
{
id: 7,
name: 'regions',
},
{
id: 8,
name: 'regions by customer',
},
],
},
{
id: 9,
name: 'query all users',
},
{
id: 10,
name: 'users in last day',
},
{
id: 11,
name: 'new users over time',
},
],
},
},
/**
* 👇 To avoid linting issues, it is recommended to use a function with a capitalized name.
* If you are not concerned with linting, you may use an arrow function.
*/
render: function Render(args) {
const [treeData, setDataTreeState] = useState(args.data)
function handleRenameSelect(id) {
// find id in treeData and set it to edit mode
let updatedTreeData = { ...treeData }
const findNode = (node: any) => {
if (node.id === id) {
node.metadata = { isEditing: true }
}
if (node.children) {
node.children.forEach(findNode)
}
}
console.log(updatedTreeData)
updatedTreeData.children.forEach(findNode)
setDataTreeState(updatedTreeData)
}
return (
<TreeView
{...args}
data={flattenTree(args.data)}
aria-label="directory tree"
nodeRenderer={({ element, isBranch, isExpanded, getNodeProps, level, isSelected }) => {
return (
<ContextMenu modal={false}>
<ContextMenuTrigger asChild>
<TreeViewItem
isExpanded={isExpanded}
isBranch={isBranch}
isSelected={isSelected}
level={level}
xPadding={16}
name={element.name}
isEditing={element.metadata?.isEditing === true}
onEditSubmit={(value) => {
console.log('value', value)
let updatedTreeData = { ...treeData }
const findNode = (node: any) => {
if (node.id === element.id) {
node.name = value
node.metadata = { isEditing: false }
}
if (node.children) {
node.children.forEach(findNode)
}
}
updatedTreeData.children.forEach(findNode)
setDataTreeState(updatedTreeData)
}}
{...getNodeProps()}
/>
</ContextMenuTrigger>
<ContextMenuContent
onCloseAutoFocus={(e) => {
// stop focus propagation
// so input in TreeViewItem gets focus
e.stopPropagation()
}}
>
<ContextMenuItem
onSelect={(e) => {
handleRenameSelect(element.id)
}}
onFocusCapture={(e) => {
// stop focus propagation
// so input in TreeViewItem gets focus
e.stopPropagation()
}}
>
Rename
</ContextMenuItem>
<ContextMenuItem disabled>Open in new tab</ContextMenuItem>
<ContextMenuItem disabled>Share with team</ContextMenuItem>
<ContextMenuSeparator />
<ContextMenuItem disabled>Delete</ContextMenuItem>
</ContextMenuContent>
</ContextMenu>
)
}}
/>
)
},
}
@@ -1,3 +1,5 @@
'use client'
import { ChevronRight, FolderClosed, FolderOpen } from 'lucide-react'
import { ComponentPropsWithoutRef, forwardRef, useEffect, useRef, useState } from 'react'
import TreeViewPrimitive from 'react-accessible-treeview'
@@ -1,38 +0,0 @@
import { StoryObj } from '@storybook/react'
import React from 'react'
import { TreeViewItem } from './TreeView'
// More on how to set up stories at: https://storybook.js.org/docs/writing-stories#default-export
export default {
title: 'Primitives/TreeView',
component: TreeViewItem,
parameters: {
// Optional parameter to center the component in the Canvas. More info: https://storybook.js.org/docs/configure/story-layout
layout: 'centered',
},
// This component will have an automatically generated Autodocs entry: https://storybook.js.org/docs/writing-docs/autodocs
tags: ['autodocs'],
// More on argTypes: https://storybook.js.org/docs/api/argtypes
argTypes: {},
}
type Story = StoryObj<typeof TreeViewItem>
export const SingleItem: Story = {
args: {
isSelected: false,
level: 1,
xPadding: 16,
isBranch: false,
isExpanded: false,
levelPadding: 16,
name: 'Element name',
},
/**
* 👇 To avoid linting issues, it is recommended to use a function with a capitalized name.
* If you are not concerned with linting, you may use an arrow function.
*/
render: function Render(args) {
return <TreeViewItem {...args}>Element name</TreeViewItem>
},
}
@@ -0,0 +1 @@
export * from './TreeView'