pittiquita
v0.1.4
Published
React toolkit for capturing HTML components to Figma via the HTML to Design plugin
Maintainers
Readme
O problema
O fluxo normal pra capturar um componente vivo pro Figma costuma ser assim:
- Abre o DevTools
- Caça o elemento certo no painel de Elements
- Copia o HTML externo na mão
- Cola no plugin HTML to Design
- Fica se perguntando por que ficou diferente do que tá renderizado
- Repete tudo
O pittiquita substitui tudo isso por um painel flutuante que só existe no localhost. Clica em Activate capture, cola a URL no plugin do Figma, pronto. Você também pode marcar regiões específicas da página como alvos nomeados — cada uma vira um botão de atalho no painel.
Retorna null em produção. A verificação de localhost acontece antes de qualquer renderização, o pacote é tree-shakeable, e sideEffects: false garante que os bundlers não vão incluí-lo a menos que você realmente use.
Demo
┌─────────────────────────────┐
│ 🎯 pittiquita _ □ │
│─────────────────────────────│
│ [ Activate capture ] │
│ │
│ Regions │
│ ┌──────────┐ ┌───────────┐ │
│ │ Hero │ │ Pricing │ │
│ └──────────┘ └───────────┘ │
│ ┌──────────┐ │
│ │ Footer │ │
│ └──────────┘ │
│─────────────────────────────│
│ Figma file ref: [________] │
└─────────────────────────────┘Uma gravação de tela de verdade tá chegando. Por enquanto, imagina um painel discreto no canto inferior direito do seu servidor de desenvolvimento.
Instalação
pnpm add pittiquita
# or
npm install pittiquita
# or
yarn add pittiquitaPeer deps: react >=18 e react-dom >=18 (testado no React 18 e 19).
Início rápido
Joga o <FigmaCapturePanel /> no layout raiz e esquece:
// app/layout.tsx (Next.js) or src/main.tsx (Vite)
import { FigmaCapturePanel } from "pittiquita";
export default function Layout({ children }) {
return (
<>
{children}
<FigmaCapturePanel />
</>
);
}Roda pnpm dev, abre http://localhost:xxxx, e o painel vai estar lá te esperando. Clica em Activate capture, copia a URL (com o hash), cola no plugin HTML to Design dentro do Figma. É isso.
Marcando regiões
Regiões permitem criar atalhos nomeados para partes específicas da página. Elas aparecem como botões no painel — clicar em uma delas rola a página até aquele elemento e o destaca no DOM.
Componente wrapper
import { FigmaTarget } from "pittiquita";
<FigmaTarget name="hero-section" label="Hero">
<section className="hero">...</section>
</FigmaTarget>;Spread helper (sem nó extra no DOM)
import { figmaTarget } from "pittiquita";
<section {...figmaTarget("hero-section", { label: "Hero" })}>...</section>;Os dois jeitos escrevem os atributos data-figma-target e data-figma-label no elemento. O painel os descobre automaticamente via MutationObserver — sem precisar dar refresh manual.
Hooks headless
Não quer o painel pronto? Importa só os hooks e monta sua própria UI:
import {
useLocalOrigin,
useFigmaCapture,
useFigmaRegions,
useFigmaFileRef,
} from "pittiquita/hooks";
function MyDevPanel() {
const isLocal = useLocalOrigin(); // SSR-safe — começa false, atualiza após a montagem
const { isActive, activate, reset } = useFigmaCapture();
const { regions, refresh } = useFigmaRegions();
const { value, setValue, openExistingFile } = useFigmaFileRef();
if (!isLocal) return null;
return (
<div>
<button onClick={activate}>Capture</button>
<ul>
{regions.map((r) => (
<li key={r.name}>{r.label ?? r.name}</li>
))}
</ul>
</div>
);
}Todos os hooks são SSR-safe e só fazem trabalho de verdade no cliente, e apenas no localhost.
Referência dos hooks
| Hook | Retorna | Observações |
| --------------------------- | --------------------------------------- | --------------------------------------------------- |
| useLocalOrigin() | boolean | true apenas em localhost / 127.0.0.1 |
| useFigmaCapture(options?) | { isActive, activate, reset } | Injeta o script do Figma ao ativar |
| useFigmaRegions(options?) | { regions, refresh } | Reativo via MutationObserver + debounce com rAF |
| useFigmaFileRef() | { value, setValue, openExistingFile } | Persiste no localStorage |
Plugins de build
Vite
O plugin do Vite injeta o painel automaticamente numa shadow root durante o dev — nada toca no build de produção.
// vite.config.ts
import { defineConfig } from "vite";
import react from "@vitejs/plugin-react";
import { pittiquita } from "pittiquita/vite";
export default defineConfig({
plugins: [react(), pittiquita()],
});O plugin só roda com apply: 'serve'. O output do build sai limpo.
Next.js (App Router)
Nenhum plugin necessário. Importa o componente direto no layout raiz:
// app/layout.tsx
import { FigmaCapturePanel } from "pittiquita";
export default function RootLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
<html lang="pt-BR">
<body>
{children}
{process.env.NODE_ENV === "development" && <FigmaCapturePanel />}
</body>
</html>
);
}O guard process.env.NODE_ENV é opcional (o componente já retorna null fora do localhost), mas é uma boa prática pra manter código exclusivo de dev fora do bundle de produção.
Customização
Tokens de tema
Todos os estilos visuais são controlados por CSS custom properties. Passa um objeto theme parcial — só o que você quer sobrescrever:
<FigmaCapturePanel
theme={{
accentColor: "#8b5cf6",
panelBg: "#0f172a",
textPrimary: "#f8fafc",
borderRadius: "12px",
}}
/>| Token | Padrão | Descrição |
| --------------- | --------- | ------------------------- |
| panelBg | #ffffff | Fundo do painel |
| borderColor | #e2e8f0 | Borda do painel |
| borderRadius | 8px | Arredondamento das bordas |
| accentColor | #6366f1 | Botões e destaques |
| textPrimary | #0f172a | Texto principal |
| textSecondary | #475569 | Texto secundário |
| textMuted | #94a3b8 | Texto suave / placeholder |
| fontFamily | system-ui | Fonte do painel |
| fontSize | 13px | Tamanho base da fonte |
| gap | 8px | Espaçamento interno |
| padding | 12px | Padding do painel |
| zIndex | 9999 | Ordem de empilhamento |
Labels (i18n)
Sobrescreve qualquer string. O resto cai no padrão em inglês:
<FigmaCapturePanel
labels={{
panelTitle: "Capturar para Figma",
activateCapture: "Ativar",
reset: "Limpar",
regionsTitle: "Regiões marcadas",
regionsEmpty: "Nenhuma região marcada",
}}
/>Slots de classes CSS (pronto para Tailwind)
<FigmaCapturePanel
classNames={{
header: "border-b border-slate-200 pb-2",
actions: "flex gap-3",
regionList: "max-h-72 overflow-y-auto",
fileField: "mt-2",
hiddenBar: "opacity-50",
}}
/>Posição do painel
<FigmaCapturePanel position="top-left" />Opções: 'bottom-right' (padrão) · 'bottom-left' · 'top-right' · 'top-left'
Callbacks
<FigmaCapturePanel
onCaptureActivate={() => analytics.track("figma_capture_activated")}
onRegionSelect={(region) => console.log("jumped to:", region.label)}
/>CSP / script self-hosted
Por padrão, o pittiquita injeta:
https://mcp.figma.com/mcp/html-to-design/capture.jsSomente quando hostname === 'localhost' e o hash da URL contém figmacapture=. Se o seu ambiente usa uma Content Security Policy restrita:
<FigmaCapturePanel
scriptSrc="https://your-mirror.example.com/capture.js"
integrity="sha384-..."
nonce={yourCspNonce}
crossOrigin="anonymous"
/>As mesmas opções estão disponíveis em useFigmaCapture(options) para uso headless.
Entry points
| Import | Arquivo fonte | Use para |
| ------------------ | -------------------- | ------------------------------------------------------ |
| pittiquita | src/index.ts | Components + hooks + utils + types |
| pittiquita/hooks | src/hooks.ts | Apenas hooks headless (sem bundle de componente React) |
| pittiquita/vite | src/vite/plugin.ts | Plugin do Vite |
| pittiquita/next | src/next/plugin.ts | Wrapper de config withPittiquita() pro Next.js |
Solução de problemas
O painel não aparece
- Você precisa estar em
localhostou127.0.0.1. Domínios.localcustomizados e IPs da rede local (192.168.x.x) não vão funcionar — por design. - Em apps com SSR, o painel começa oculto e aparece após a hidratação. Se nunca aparecer, verifica se
useLocalOrigin()retornatrueno console do navegador.
Cliquei em Activate e nada aconteceu no Figma
- O hash da URL deve ter mudado para
#figmacapture=manual. - Um
<script data-figma-capture-loader>deve estar no<head>— confere no painel de Elements. - O plugin HTML to Design precisa estar aberto no Figma com o modo "Import from URL" selecionado.
- Cola a URL completa (com o hash) no campo do plugin.
O CSP bloqueou o script
Usa nonce (ou integrity). Exemplo com Next.js App Router:
import { headers } from "next/headers";
import { FigmaCapturePanel } from "pittiquita";
export default async function Layout({ children }) {
const nonce = (await headers()).get("x-csp-nonce") ?? undefined;
return (
<html>
<body>
{children}
<FigmaCapturePanel nonce={nonce} />
</body>
</html>
);
}As regiões não atualizam na navegação (SPA)
O pittiquita escuta popstate e hashchange automaticamente. Para roteadores que atualizam apenas o estado interno (Next.js App Router, TanStack Router), passa o pathname atual:
"use client";
import { usePathname } from "next/navigation";
import { FigmaCapturePanel } from "pittiquita";
export function DevPanel() {
return <FigmaCapturePanel pathname={usePathname()} />;
}FAQ
É seguro subir o import pra produção?
Sim. O componente retorna null fora do localhost. Pra ter ainda mais certeza, envolve em process.env.NODE_ENV === 'development' — os bundlers vão eliminar o branch morto completamente.
Funciona com React Server Components?
<FigmaCapturePanel> usa hooks internamente, então precisa ser um Client Component. Adiciona 'use client' no arquivo que o importa, ou cria um wrapper fino com essa diretiva.
Posso usar com Tailwind / styled-components / CSS Modules?
Sim. Usa os slots de classNames pra injetar suas próprias classes e mantém theme para os tokens de CSS variable.
Algum dado é enviado para servidores externos?
Nada vindo do pittiquita em si. Quando você ativa a captura, o script do Figma (mcp.figma.com) cuida da leitura do DOM de acordo com a política de privacidade do próprio Figma.
Funciona com design tokens / CSS variables que já tenho no projeto?
Sim. A prop theme mapeia os tokens internos do pittiquita para variáveis CSS --pittiquita-*. Seus tokens de design system existentes não são tocados.
Desenvolvimento
pnpm install
pnpm test:run # roda todos os 58 testes de uma vez (Vitest)
pnpm typecheck # tsc --noEmit
pnpm build # tsup → dist/ (ESM + CJS + .d.ts)
pnpm dev # tsup --watch
# playground interativo (app Vite linkado ao pacote local)
# build the library first, then:
cd playground && pnpm install && pnpm devRodando um arquivo de teste específico:
pnpm vitest run tests/core/utils/regions.test.tsStack: TypeScript 6 · React 18/19 · Vite 8 · Vitest 4 · tsup 8 · pnpm
Visão geral da arquitetura
src/
├── core/ # TypeScript puro — zero dependência do React
│ ├── utils/ # capture, regions, file-ref, labels
│ ├── hooks/ # useFigmaCapture, useFigmaRegions, useFigmaFileRef, useLocalOrigin
│ └── types.ts # Todos os tipos compartilhados
├── react/ # Camada de apresentação React
│ ├── FigmaTarget.tsx
│ ├── FigmaCapturePanel.tsx
│ ├── components/ # ActionsRow, FileRefField, HiddenBar, RegionList
│ └── styles.ts # Estilos inline + sistema de CSS variables
├── vite/plugin.ts # Plugin do Vite
└── next/plugin.ts # Wrapper de config para Next.jsO core é headless e agnóstico de framework. Nova lógica vai em core/. Nova UI vai em react/. Mantém essa separação.
Contribuindo
- Faz um fork do repositório
- Cria uma branch:
git checkout -b feat/sua-feature - Adiciona testes para tudo que for novo
- Roda
pnpm test:run && pnpm typecheck && pnpm build - Abre um PR
Licença & créditos
MIT © Pedro Nazarito
Batizado em homenagem à Pittiquita — a gata que supervisionou cada commit deste projeto e não teve nenhuma nota a dar. 🐱
"Se não fosse ela miando no meu colo enquanto eu escrevia isso, o nome seria algo sem graça tipo
figma-capture-kit."
