DSNiemeyer
Idioma

Instalação

Niemeyer é distribuído como um pacote npm privado no GitHub Packages, construído sobre Tailwind 4. Os seis passos numerados abaixo levam de um app vazio até um <Button> funcionando.

1. Configurar acesso ao GitHub Packages

Crie um Personal Access Token (clássico) no GitHub com read:packages e exporte como GITHUB_TOKEN. Em seguida, adicione este .npmrc na raiz do projeto (Yarn Classic lê direto; usuários de Yarn Berry podem configurar o equivalente npmScopes em .yarnrc.yml):

# .npmrc — at project root
@morada-ai:registry=https://npm.pkg.github.com
//npm.pkg.github.com/:_authToken=${GITHUB_TOKEN}

2. Instalar o pacote e suas peers

Tailwind 4 e @tabler/icons-react são peer dependencies — instale no seu app para que uma única cópia seja hoisted ao lado do pacote.

# Install the package
yarn add @morada-ai/niemeyer

# Plus the peer dependencies (one copy in your app)
yarn add tailwindcss@^4
yarn add @tabler/icons-react

3. Configurar PostCSS

Tailwind 4 publica seu plugin PostCSS separadamente. Adicione-o ao seu config (não precisa de autoprefixer):

// postcss.config.mjs
export default {
    plugins: {
        "@tailwindcss/postcss": {},
    },
};

4. Conectar a folha de estilos

Três @imports explícitos em app/globals.css, nesta ordem. Você só precisa de um @source para o código do seu app — o theme.css do pacote já faz self-source dos próprios componentes, e o Tailwind 4 acumula diretivas @source de todas as folhas importadas.

/* app/globals.css */ 
@import "tailwindcss";
@import "@morada-ai/niemeyer/styles";
@import "tw-animate-css";

/* Only your app code — the package self-sources its own components */
@source "./app/**/*.{ts,tsx}";

A ordem importa: tailwindcss precisa vir primeiro para que os blocos @theme enviados dentro do pacote sejam reconhecidos.

5. Carregar fontes

Os design tokens do pacote referenciam Outfit (títulos) e Lato (corpo) apenas pelo nome da família — você escolhe como entregar os arquivos de fonte. Três opções abaixo, em ordem de recomendação.

Opção A — <link> carregado pelo browser (recomendado; funciona em todo lugar)

Adicione no <head> do seu layout (ou via @font-face). O browser busca o CSS em runtime, então o servidor de build nunca precisa de acesso à internet para o Google Fonts. Trade-off: pequeno flash de texto não estilizado (FOUT) no primeiro paint enquanto o CSS resolve; com display=swap não há layout shift.

<!-- in <head>, plus a preconnect for the WOFF2 host -->
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link
  href="https://fonts.googleapis.com/css2?family=Outfit:wght@400;500;600;700&family=Lato:wght@300;400;700;900&display=swap"
  rel="stylesheet"
/>

Opção B — next/font/google (apps Next.js com acesso à internet em build time)

Se o ambiente de build alcança fonts.googleapis.com, next/font/google faz self-host dos WOFF2 em build time, eliminando a requisição runtime ao Google. Atenção: o **build falha** em qualquer ambiente sem acesso ao Google Fonts (VPN corporativa, CI sandboxado, runners air-gapped) — use a Opção A nesses casos.

// app/layout.tsx
import { Outfit, Lato } from "next/font/google";

const outfit = Outfit({
    subsets: ["latin"],
    weight: ["400", "500", "600", "700"],
    variable: "--font-outfit",
    display: "swap",
});
const lato = Lato({
    subsets: ["latin"],
    weight: ["300", "400", "700", "900"],
    variable: "--font-lato",
    display: "swap",
});

export default function RootLayout({
    children,
}: {
    children: React.ReactNode;
}) {
    return (
        <html className={`${outfit.variable} ${lato.variable}`}>
            <body>{children}</body>
        </html>
    );
}

Em seguida, conecte os tokens do niemeyer às CSS variables do next/font no seu stylesheet, DEPOIS de @import "@morada-ai/niemeyer/styles":

/* app/globals.css — AFTER @import "@morada-ai/niemeyer/styles" */
:root {
    --font-heading: var(--font-outfit), system-ui, -apple-system, sans-serif;
    --font-body:    var(--font-lato),   system-ui, -apple-system, sans-serif;
}

Opção C — Self-host

Coloque Outfit-*.woff2 e Lato-*.woff2 em public/fonts/ e declare regras @font-face no seu stylesheet. Zero dependência externa, mas você assume o pipeline de assets.

/* app/globals.css */
@font-face {
    font-family: "Outfit";
    src: url("/fonts/Outfit-Variable.woff2") format("woff2-variations");
    font-weight: 100 900;
    font-display: swap;
}
@font-face {
    font-family: "Lato";
    src: url("/fonts/Lato-Regular.woff2") format("woff2");
    font-weight: 400;
    font-display: swap;
}
/* Repeat @font-face for every Lato weight you ship (300, 700, 900). */

6. Re-exportar `cn()` para uso local no consumer

O pacote usa o mesmo padrão de cn do shadcn (clsx + tailwind-merge). Os dois helpers já vêm bundled, então uma re-exportação de uma linha basta:

// src/lib/utils.ts
export { cn } from "@morada-ai/niemeyer/utils";

Opcional: ativar o plugin ESLint

Pega violações do design system automaticamente (cores hardcoded em className="…", imports de lucide-react, <button>/<input> nativos):

// eslint.config.js
const niemeyer = require("@morada-ai/niemeyer/eslint");

module.exports = [niemeyer.configs.recommended];