-
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #176 from kosori/feat/tree-view
- Loading branch information
Showing
7 changed files
with
377 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,123 @@ | ||
--- | ||
title: File Tree | ||
description: Renders a hierarchical structure of files and folders. | ||
links: | ||
doc: https://www.radix-ui.com/docs/primitives/components/collapsible | ||
api: https://www.radix-ui.com/docs/primitives/components/collapsible#api-reference | ||
dependencies: ['@radix-ui/react-collapsible'] | ||
--- | ||
|
||
<ComponentPreview name='file-tree'> | ||
```json doc-gen:file | ||
{ | ||
"file": "./src/components/demos/FileTree/FileTree.tsx", | ||
"codeblock": true | ||
} | ||
``` | ||
</ComponentPreview> | ||
|
||
## Installation | ||
|
||
<Steps> | ||
<Step> | ||
### Install the primitive | ||
|
||
Install the `@radix-ui/react-collapsible` package. | ||
|
||
```package-install | ||
@radix-ui/react-collapsible | ||
``` | ||
|
||
</Step> | ||
|
||
<Step> | ||
### Copy-paste the component | ||
|
||
Copy and paste the component code in a `.tsx` file. | ||
|
||
```json doc-gen:file | ||
{ | ||
"file": "../../packages/ui/src/file-tree.tsx", | ||
"codeblock": true | ||
} | ||
``` | ||
|
||
</Step> | ||
|
||
<Step> | ||
### Update import paths | ||
|
||
Update the `@kosori/ui` import paths to fit your project structure, for example, using `~/components/ui`. | ||
|
||
</Step> | ||
|
||
<Step> | ||
### Add keyframes animation | ||
|
||
Add the `collapsible-down` and `collapsible-up` keyframes animation to your Tailwind CSS config. | ||
|
||
```ts title="tailwind.config.ts" | ||
import type { Config } from 'tailwindcss'; | ||
|
||
const config: Config = { | ||
// ..., | ||
keyframes: { | ||
// [!code highlight:9] | ||
'collapsible-down': { | ||
from: { height: '0' }, | ||
to: { height: 'var(--radix-collapsible-content-height)' }, | ||
}, | ||
'collapsible-up': { | ||
from: { height: 'var(--radix-collapsible-content-height)' }, | ||
to: { height: '0' }, | ||
}, | ||
}, | ||
animation: { | ||
// [!code highlight:3] | ||
'collapsible-down': 'collapsible-down 0.15s ease-out', | ||
'collapsible-up': 'collapsible-up 0.15s ease-out', | ||
} | ||
}; | ||
``` | ||
|
||
</Step> | ||
|
||
</Steps> | ||
|
||
## Usage | ||
|
||
```ts | ||
import { | ||
File, | ||
FileTree, | ||
Folder, | ||
FolderFiles, | ||
FolderName, | ||
} from '~/components/ui/file-tree'; | ||
``` | ||
|
||
```tsx | ||
<FileTree> | ||
<Folder> | ||
<FolderName>app</FolderName> | ||
<FolderFiles> | ||
<File>layout.tsx</File> | ||
<File>page.tsx</File> | ||
</FolderFiles> | ||
</Folder> | ||
</FileTree> | ||
``` | ||
|
||
## Reference | ||
|
||
### File | ||
|
||
<TypeTable | ||
type={{ | ||
icon: { | ||
default: `<FileIcon />`, | ||
description: 'Optional icon to display next to the file name.', | ||
type: 'ReactNode', | ||
}, | ||
}} | ||
/> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,43 @@ | ||
import { | ||
File, | ||
FileTree, | ||
Folder, | ||
FolderFiles, | ||
FolderName, | ||
} from '@kosori/ui/file-tree'; | ||
|
||
export const FileTreeDemo = () => { | ||
return ( | ||
<FileTree className='w-full max-w-xs'> | ||
<Folder> | ||
<FolderName>app</FolderName> | ||
|
||
<FolderFiles> | ||
<Folder> | ||
<FolderName>components</FolderName> | ||
|
||
<FolderFiles> | ||
<File>button.tsx</File> | ||
<File>card.tsx</File> | ||
</FolderFiles> | ||
</Folder> | ||
|
||
<File>layout.tsx</File> | ||
<File>global.css</File> | ||
<File>page.tsx</File> | ||
</FolderFiles> | ||
</Folder> | ||
|
||
<Folder defaultOpen> | ||
<FolderName>public</FolderName> | ||
|
||
<FolderFiles> | ||
<File>favicon.ico</File> | ||
<File>index.html</File> | ||
</FolderFiles> | ||
</Folder> | ||
|
||
<File>package.json</File> | ||
</FileTree> | ||
); | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
export * from './FileTree'; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,187 @@ | ||
'use client'; | ||
|
||
import { forwardRef } from 'react'; | ||
import { clsx } from 'clsx/lite'; | ||
import { FileIcon, FolderIcon, FolderOpenIcon } from 'lucide-react'; | ||
import { tv } from 'tailwind-variants'; | ||
|
||
import { | ||
Collapsible, | ||
CollapsibleContent, | ||
CollapsibleTrigger, | ||
} from '@kosori/ui/collapsible'; | ||
|
||
const fileTreeStyles = tv({ | ||
slots: { | ||
fileTree: 'rounded-lg border bg-grey-bg-subtle p-2', | ||
folder: 'w-full', | ||
folderName: clsx( | ||
'group w-full inline-flex items-center gap-2 rounded-lg px-2 h-8', | ||
'hover:bg-grey-bg-hover', | ||
), | ||
folderFiles: clsx( | ||
'overflow-hidden', | ||
'data-[state=closed]:animate-collapsible-up', | ||
'data-[state=open]:animate-collapsible-down', | ||
), | ||
file: clsx( | ||
'w-full inline-flex h-8 select-none items-center gap-2 rounded-lg px-2 text-sm', | ||
'hover:bg-grey-bg-hover', | ||
), | ||
}, | ||
}); | ||
|
||
const { fileTree, folder, folderName, folderFiles, file } = fileTreeStyles(); | ||
|
||
type FileTreeRef = HTMLDivElement; | ||
type FileTreeProps = React.ComponentPropsWithoutRef<'div'>; | ||
|
||
/** | ||
* FileTree component that represents a hierarchical structure of files and folders. | ||
* | ||
* @param {FileTreeProps} props - Additional props to pass to the file tree container. | ||
* | ||
* @example | ||
* <FileTree> | ||
* <Folder> | ||
* <FolderName>Documents</FolderName> | ||
* <FolderFiles> | ||
* <File>Resume.pdf</File> | ||
* <File>CoverLetter.docx</File> | ||
* </FolderFiles> | ||
* </Folder> | ||
* <Folder> | ||
* <FolderName>Images</FolderName> | ||
* <FolderFiles> | ||
* <File>Vacation.jpg</File> | ||
* <File>Profile.png</File> | ||
* </FolderFiles> | ||
* </Folder> | ||
* </FileTree> | ||
*/ | ||
export const FileTree = forwardRef<FileTreeRef, FileTreeProps>( | ||
({ className, ...props }, ref) => ( | ||
<div ref={ref} className={fileTree({ className })} {...props} /> | ||
), | ||
); | ||
|
||
FileTree.displayName = 'FileTree'; | ||
|
||
type FolderRef = React.ElementRef<typeof Collapsible>; | ||
type FolderProps = React.ComponentPropsWithoutRef<typeof Collapsible>; | ||
|
||
/** | ||
* Folder component that represents a collapsible folder in the file tree. | ||
* | ||
* @param {FolderProps} props - Additional props to pass to the folder component. | ||
* | ||
* @example | ||
* <Folder> | ||
* <FolderName>My Folder</FolderName> | ||
* <FolderFiles> | ||
* <File>File1.txt</File> | ||
* <File>File2.txt</File> | ||
* </FolderFiles> | ||
* </Folder> | ||
*/ | ||
export const Folder = forwardRef<FolderRef, FolderProps>( | ||
({ className, ...props }, ref) => ( | ||
<Collapsible ref={ref} className={folder({ className })} {...props} /> | ||
), | ||
); | ||
|
||
Folder.displayName = 'Folder'; | ||
|
||
type FolderNameRef = React.ElementRef<typeof CollapsibleTrigger>; | ||
type FolderNameProps = React.ComponentPropsWithoutRef< | ||
typeof CollapsibleTrigger | ||
>; | ||
|
||
/** | ||
* FolderName component that acts as the clickable header for a Folder. | ||
* | ||
* @param {FolderNameProps} props - Additional props to pass to the folder name. | ||
* | ||
* @example | ||
* <FolderName>My Folder</FolderName> | ||
*/ | ||
export const FolderName = forwardRef<FolderNameRef, FolderNameProps>( | ||
({ className, children, ...props }, ref) => ( | ||
<CollapsibleTrigger | ||
ref={ref} | ||
className={folderName({ className })} | ||
{...props} | ||
> | ||
<FolderIcon | ||
className={clsx('size-4', 'group-data-[state=open]:hidden')} | ||
/> | ||
<FolderOpenIcon | ||
className={clsx('size-4', 'group-data-[state=closed]:hidden')} | ||
/> | ||
{children} | ||
</CollapsibleTrigger> | ||
), | ||
); | ||
|
||
FolderName.displayName = 'FolderName'; | ||
|
||
type FolderFilesRef = React.ElementRef<typeof CollapsibleContent>; | ||
type FolderFilesProps = React.ComponentPropsWithoutRef< | ||
typeof CollapsibleContent | ||
>; | ||
|
||
/** | ||
* FolderFiles component that displays the contents of a Folder. | ||
* | ||
* @param {FolderFilesProps} props - Additional props to pass to the folder files container. | ||
* | ||
* @example | ||
* <FolderFiles> | ||
* <File>File1.txt</File> | ||
* <File>File2.txt</File> | ||
* </FolderFiles> | ||
*/ | ||
export const FolderFiles = forwardRef<FolderFilesRef, FolderFilesProps>( | ||
({ className, children, ...props }, ref) => ( | ||
<CollapsibleContent | ||
ref={ref} | ||
className={folderFiles({ className })} | ||
{...props} | ||
> | ||
<div className='ms-2 flex flex-col border-l ps-2'>{children}</div> | ||
</CollapsibleContent> | ||
), | ||
); | ||
|
||
FolderFiles.displayName = 'FolderFiles'; | ||
|
||
type FileRef = HTMLDivElement; | ||
type FileProps = React.ComponentPropsWithoutRef<'div'> & { | ||
/** | ||
* Optional icon to display next to the file name. | ||
*/ | ||
icon?: React.ReactNode; | ||
}; | ||
|
||
/** | ||
* File component that represents a single file in the file tree. | ||
* | ||
* @param {FileProps} props - Additional props to pass to the file component. | ||
* @param {React.ReactNode} [icon=<FileIcon />] - Optional icon to display next to the file name. | ||
* | ||
* @example | ||
* <File>MyFile.txt</File> | ||
*/ | ||
export const File = forwardRef<FileRef, FileProps>( | ||
( | ||
{ className, icon = <FileIcon className='size-4' />, children, ...props }, | ||
ref, | ||
) => ( | ||
<div ref={ref} className={file({ className })} {...props}> | ||
{icon} | ||
{children} | ||
</div> | ||
), | ||
); | ||
|
||
File.displayName = 'File'; |
Oops, something went wrong.