Tree Code Viewer
Tree Code Viewer é um componente de dados para React e Tailwind CSS, da biblioteca UI Layouts, com licença MIT. Copia e cola no teu projeto.
UI Layouts
MIT
data
O que é
Tree Code Viewer é um componente de dados para React e Tailwind CSS, da biblioteca UI Layouts, com licença MIT. Copia e cola no teu projeto.
Como usar
Abre o componente no catálogo, carrega em copiar, e cola no teu projeto. O prompt copiado leva o código e as regras para o teu agente o reproduzir sem alterar nada.
Cinco cópias grátis por mês. Sem cartão.
Precisa de
npm i shiki
Licença
Este componente vem de UI Layouts e é redistribuído sob a licença MIT, que permite uso comercial. O aviso de copyright viaja com o código.
Tree Code Viewer
'use client';
import {
TreeExpander,
TreeIcon,
TreeLabel,
TreeNode,
TreeNodeContent,
TreeNodeTrigger,
TreeProvider,
TreeView,
} from '@/components/ui/tree';
import { type TreeNodeData, buildSimpleTree, getFileIcon } from '@/lib/tree-structure';
import { useState } from 'react';
import { ClientPreCode } from '../website/code-components/client-pre-code';
export function TreeCodeViewer({
files,
}: {
files: {
id: string;
fileName: string;
virtualPath: string[];
ext: string;
raw: string;
html: string;
}[];
}) {
const tree = buildSimpleTree(files);
console.log('tree', tree);
const [selectedId, setSelectedId] = useState(files[0]?.id);
const fileMap = Object.fromEntries(files.map((f) => [f.id, f]));
const selectedFile = selectedId ? fileMap[selectedId] : null;
return (
<div className='grid grid-cols-[250px_1fr] border dark:border-neutral-800 bg-neutral-50 dark:bg-neutral-950 overflow-hidden'>
{/* LEFT */}
<TreeProvider
selectable
multiSelect={false}
defaultExpandedIds={[tree[0].name, 'ui', 'lib']}
onSelectionChange={(ids) => {
if (ids[0]) setSelectedId(ids[0]);
}}
className='border-r dark:border-neutral-800 bg-neutral-50 dark:bg-neutral-900'
>
<TreeView>
<RenderTree nodes={tree} />
</TreeView>
</TreeProvider>
{/* RIGHT */}
<div className='min-w-0 p-2'>
{selectedFile ? (
<ClientPreCode html={selectedFile.html} raw={selectedFile.raw} />
) : (
<div className='p-6 text-sm text-muted-foreground'>Select a file</div>
)}
</div>
</div>
);
}
function RenderTree({ nodes, level = 0 }: { nodes: TreeNodeData[]; level?: number }) {
return (
<>
{nodes.map((node, index) => {
const isLast = index === nodes.length - 1;
// 📁 FOLDER
if (node.type === 'folder') {
return (
<TreeNode key={node.name} nodeId={node.name} level={level} isLast={isLast} isFolder>
<TreeNodeTrigger>
<TreeExpander hasChildren />
<TreeIcon icon={getFileIcon('folder')} />
<TreeLabel>{node.name}</TreeLabel>
</TreeNodeTrigger>
<TreeNodeContent hasChildren>
<RenderTree nodes={node.children} level={level + 1} />
</TreeNodeContent>
</TreeNode>
);
}
// 📄 FILE
return (
<TreeNode key={node.id} nodeId={node.id} level={level} isLast={isLast}>
<TreeNodeTrigger>
<TreeExpander />
<TreeIcon icon={getFileIcon('file', node.ext, node.name)} />
<TreeLabel>{node.name}</TreeLabel>
</TreeNodeTrigger>
</TreeNode>
);
})}
</>
);
}
'use client';
import { cn } from '@/lib/utils';
import { CopyButton } from './copy-button';
export function ClientPreCode({
html,
raw,
className,
}: {
html: string;
raw: string;
className?: string;
}) {
return (
<div className={cn('relative', className)}>
<CopyButton code={raw} classname='right-2 top-2 bg-white dark:bg-neutral-800' />
<div
className='not-prose max-h-[550px] overflow-x-hidden rounded-md text-sm border dark:border-neutral-800'
dangerouslySetInnerHTML={{ __html: html }}
/>
</div>
);
}
'use client';
import { cn } from '@/lib/utils';
import { Check, CheckCheck, Copy } from 'lucide-react';
import { useState } from 'react';
export function CopyButton({ code, classname }: { code: string; classname?: string }) {
const [hasCheckIcon, setHasCheckIcon] = useState(false);
const onCopy = () => {
navigator.clipboard.writeText(code);
setHasCheckIcon(true);
setTimeout(() => {
setHasCheckIcon(false);
}, 1000);
};
return (
<>
<div
className={cn(
'absolute right-2 top-2 cursor-pointer dark:hover:shadow-[0px_1px_10px_5px_#3f7ef3] hover:shadow-[0px_1px_10px_5px_#9abaf7] dark:hover:border-blue-500 hover:border-blue-300 dark:bg-zinc-800 backdrop-blur-2xl bg-white rounded-md border-2',
classname
)}
onClick={onCopy}
>
<div
className={` inset-0 transform transition-all duration-300 w-9 h-8 grid place-content-center ${
hasCheckIcon ? 'scale-0 opacity-0' : 'scale-100 opacity-100'
}`}
>
<Copy className='h-4 w-4 text-foreground/80' />
</div>
<div
className={`absolute inset-0 transform transition-all duration-300 w-8 h-8 grid place-content-center ${
hasCheckIcon ? 'scale-100 opacity-100' : 'scale-0 opacity-0'
}`}
>
<CheckCheck className='h-4 w-4 text-foreground/80' />
</div>
</div>
</>
);
}