ITpS

Desenvolver

Com shadcn (React)

Como usar Sinapse + shadcn em React / Next.js. Tokens semantic compatíveis — componentes shadcn ficam Sinapse-coral automático.

Por que funciona

shadcn/ui usa os mesmos semantic tokens que a Sinapse @itps/styles: --primary, --background, --card, etc. Carregando nossa CSS entry, os componentes shadcn viram coral Sinapse automaticamente — sem fork, sem patch.

1 · Setup

Instalar tudo

Terminal

# 1. Tailwind v4 (se ainda não tem)
pnpm add tailwindcss

# 2. shadcn init (cria components.json, alias, etc)
pnpm dlx shadcn@latest init

# 3. Sinapse tokens — workspace OU copy
#    workspace: pnpm add @itps/styles --workspace
#    copy:       cp <repo-sinapse>/packages/styles/*.css ./styles/

2 · CSS entry

Importar Sinapse no globals.css

shadcn cria por default um app/globals.css com os semantic vars. Substitua por @itps/styles:

app/globals.css

@import "tailwindcss";
@import "@itps/styles";  /* nossa CSS — sobrescreve defaults shadcn */

3 · Adicionar componentes

CLI shadcn

pnpm dlx shadcn@latest add button
pnpm dlx shadcn@latest add card
pnpm dlx shadcn@latest add dialog    # Modal
pnpm dlx shadcn@latest add tabs
pnpm dlx shadcn@latest add select
# ... etc — veja ui.shadcn.com/docs/components

Componentes React ficam em components/ui/. Use direto:

import { Button } from '@/components/ui/button'

export default function Demo() {
  return (
    <>
      <Button>Salvar</Button>           {/* coral Sinapse */}
      <Button variant="outline">Cancelar</Button>
    </>
  )
}

4 · Componentes Sinapse drop-in

Substitua os gerados pra ficar 1:1 com o DS

O Button.tsx que a CLI shadcn gera tem styling próprio (radius, altura, hover) — coral mas não idêntico ao Sinapse. Substitua o conteúdo de cada arquivo em components/ui/ pelos snippets abaixo. Wrappers finos que usam .itps-* — um source of truth (components.css).

Button.tsx

components/ui/button.tsx

import type { ButtonHTMLAttributes } from 'react';

type Variant = 'primary' | 'secondary' | 'ghost' | 'outline';
type Size = 'sm' | 'md' | 'lg';

interface ButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> {
  variant?: Variant;
  size?: Size;
}

export function Button({
  variant = 'primary',
  size = 'md',
  className,
  ...rest
}: ButtonProps) {
  const cls = [
    'itps-btn',
    `itps-btn--${variant}`,
    size !== 'md' && `itps-btn--${size}`,
    className,
  ].filter(Boolean).join(' ');

  return <button className={cls} {...rest} />;
}

Card.tsx

components/ui/card.tsx

import type { HTMLAttributes } from 'react';

interface CardProps extends HTMLAttributes<HTMLDivElement> {
  elevated?: boolean;
}

export function Card({ elevated, className, ...rest }: CardProps) {
  const cls = ['itps-card', elevated && 'itps-card--elevated', className]
    .filter(Boolean).join(' ');
  return <div className={cls} {...rest} />;
}

Chip.tsx

components/ui/chip.tsx

import type { HTMLAttributes } from 'react';

type Variant = 'default' | 'primary' | 'success' | 'warning' | 'danger';

interface ChipProps extends HTMLAttributes<HTMLSpanElement> {
  variant?: Variant;
}

export function Chip({ variant = 'default', className, ...rest }: ChipProps) {
  const cls = [
    'itps-chip',
    variant !== 'default' && `itps-chip--${variant}`,
    className,
  ].filter(Boolean).join(' ');
  return <span className={cls} {...rest} />;
}

Callout.tsx

components/ui/callout.tsx

import type { HTMLAttributes } from 'react';

type Variant = 'default' | 'info' | 'success' | 'warning' | 'danger';

interface CalloutProps extends HTMLAttributes<HTMLDivElement> {
  variant?: Variant;
}

export function Callout({ variant = 'default', className, ...rest }: CalloutProps) {
  const cls = [
    'itps-callout',
    variant !== 'default' && `itps-callout--${variant}`,
    className,
  ].filter(Boolean).join(' ');
  return <div className={cls} {...rest} />;
}

Input.tsx

components/ui/input.tsx

import { forwardRef, type InputHTMLAttributes } from 'react';

interface InputProps extends InputHTMLAttributes<HTMLInputElement> {
  error?: boolean;
}

export const Input = forwardRef<HTMLInputElement, InputProps>(
  ({ error, className, ...rest }, ref) => {
    const cls = ['itps-input', error && 'itps-input--error', className]
      .filter(Boolean).join(' ');
    return <input ref={ref} className={cls} {...rest} />;
  }
);
Input.displayName = 'Input';

Field.tsx

components/ui/field.tsx

import type { ReactNode } from 'react';

interface FieldProps {
  label?: string;
  hint?: string;
  error?: string;
  htmlFor?: string;
  children: ReactNode;
}

export function Field({ label, hint, error, htmlFor, children }: FieldProps) {
  return (
    <div className="itps-field">
      {label && (
        <label htmlFor={htmlFor} className="itps-field__label">{label}</label>
      )}
      {children}
      {error
        ? <p className="itps-field__error">{error}</p>
        : hint && <p className="itps-field__hint">{hint}</p>}
    </div>
  );
}

5 · Componentes Sinapse-only

RiskClassification, DataOverlay, FeedbackIcon

Estes 3 componentes não existem em shadcn (são domain-specific de saúde pública). Pegue o HTML+classes da página de cada um em /componentes e converta pra TSX manualmente (apenas classclassName).

Próximos passos

Onde ir