Todos os componentes

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.

Abrir no catálogo

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

./components/ui/tree-view-code.tsx
'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>
        );
      })}
    </>
  );
}
./components/website/code-components/client-pre-code.tsx
'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>
  );
}
./components/website/code-components/copy-button.tsx
'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>
    </>
  );
}

Componentes parecidos