# Custom Components

Distinguimos entre dois tipos de componentes personalizados, cada um com processos e expectativas diferentes.

## Tipos de Componentes

### A) Componentes Personalizados Específicos do Projeto

Componentes construídos para um projeto ou tela específicos, não destinados ao reuso em outros projetos.

**Exemplos:**

* Um `Caixa` com layout especial usado dentro de um projeto
* Um `Card` variante com layout personalizado para as telas de um projeto
* Visualizações de dados específicas do projeto
* Componentes de layout pontuais

**Quando usar:**

* Componente resolve um problema único de um projeto
* Pouco provável que seja necessário em outros projetos
* Muito específico para generalizar

### B) Componentes Candidatos a UI2

Componentes destinados ao reuso em múltiplos projetos e produtos.

**Exemplos:**

* `Navbar` - Navegação em todo o site
* `UserMenu` - Menu da conta do usuário
* Padronizado `Modal` diálogos
* Componentes sendo migrados do UI1

**Quando usar:**

* Componente será usado em múltiplos projetos
* Representa um padrão comum do Decentraland
* Substitui ou estende um componente do UI1

***

## Componentes Específicos do Projeto

### Requisitos

#### Usar MUI como Base

**DEVE** estender componentes MUI existentes sempre que possível:

```tsx
// ✅ Bom: Estende MUI Card
import { Card as MuiCard } from '@mui/material';
import { styled } from '@mui/material/styles';

const ProjectCard = styled(MuiCard)(({ theme }) => ({
  padding: theme.spacing(3),
  display: 'flex',
  flexDirection: 'column',
  gap: theme.spacing(2),
}));

// ❌ Ruim: Constrói do zero
const ProjectCard = styled('div')(({ theme }) => ({
  padding: theme.spacing(3),
  borderRadius: '4px',
  boxShadow: '0 2px 4px rgba(0,0,0,0.1)',
  // Duplicando funcionalidade do Card
}));
```

**Não bifurque ou duplique padrões que o MUI já cobre:**

* Use `Card` em vez de criar uma caixa customizada com sombras
* Use `Button` em vez de criar uma âncora estilizada
* Use `TextField` em vez de criar um input personalizado
* Estender `Dialog` em vez de criar um modal personalizado

#### Somente Valores do Tema

**DEVE** usar valores do tema UI2:

```tsx
// ✅ Bom: Todos os valores do tema
const StyledBox = styled('div')(({ theme }) => ({
  color: theme.palette.text.primary,
  backgroundColor: theme.palette.background.paper,
  padding: theme.spacing(2),
  borderRadius: theme.shape.borderRadius,
  border: `1px solid ${theme.palette.divider}`,
}));

// ❌ Ruim: Valores ad-hoc
const StyledBox = styled('div')({
  color: '#333333',
  backgroundColor: '#FFFFFF',
  padding: '16px',
  borderRadius: '8px',
  border: '1px solid #E0E0E0',
});
```

**Nenhum valor arbitrário permitido:**

* Cores: Use `theme.palette` ou `dclColors`
* Espaçamentos: Use `theme.spacing(n)`
* Raio da borda: Use `theme.shape.borderRadius`
* Tipografia: Use `theme.typography` variantes
* Breakpoints: Use `theme.breakpoints` ajudantes

#### Estados e Acessibilidade

**DEVE** definir e implementar todos os estados interativos:

```tsx
const ActionButton = styled('button')(({ theme }) => ({
  // Estado base/ocioso
  padding: theme.spacing(1, 2),
  backgroundColor: theme.palette.primary.main,
  color: theme.palette.primary.contrastText,
  border: 'none',
  borderRadius: theme.shape.borderRadius,
  cursor: 'pointer',
  transition: theme.transitions.create(['background-color', 'transform']),
  
  // Estado hover
  '&:hover': {
    backgroundColor: theme.palette.primary.dark,
  },
  
  // Estado focus (navegação por teclado)
  '&:focus-visible': {
    outline: `2px solid ${theme.palette.primary.main}`,
    outlineOffset: 2,
  },
  
  // Estado ativo/pressionado
  '&:active': {
    transform: 'scale(0.98)',
  },
  
  // Estado desabilitado
  '&:disabled': {
    backgroundColor: theme.palette.action.disabledBackground,
    color: theme.palette.action.disabled,
    cursor: 'not-allowed',
  },
}));
```

**DEVE** implementar acessibilidade básica:\*\*

* **Navegação por teclado** - Focável e operável com teclado
* **Indicadores de foco** - Estados de foco visíveis
* **Labels ARIA** - Onde o texto não é visível
* **HTML semântico** - Usar elementos apropriados
* **Contraste de cor** - Atendimento aos padrões WCAG AA

### Exemplo: Componente Específico do Projeto

```tsx
// src/components/LandCard/LandCard.tsx
import { Card, CardContent, CardActions, Typography, Button } from '@mui/material';
import { styled } from '@mui/material/styles';
import type { Parcel } from '@/types';

interface LandCardProps {
  parcel: Parcel;
  onTransfer: (id: string) => void;
  onView: (id: string) => void;
}

const StyledCard = styled(Card)(({ theme }) => ({
  display: 'flex',
  flexDirection: 'column',
  height: '100%',
  transition: theme.transitions.create('transform'),
  
  '&:hover': {
    transform: 'translateY(-4px)',
  },
}));

const CoordinatesText = styled(Typography)(({ theme }) => ({
  color: theme.palette.text.secondary,
  fontFamily: theme.typography.fontFamilyMono,
}));

export function LandCard({ parcel, onTransfer, onView }: LandCardProps) {
  return (
    <StyledCard>
      <CardContent>
        <Typography variant="h6" gutterBottom>
          {parcel.name || `Parcel ${parcel.x},${parcel.y}`}
        </Typography>
        <CoordinatesText variant="body2">
          ({parcel.x}, {parcel.y})
        </CoordinatesText>
        <Typography variant="body2" color="text.secondary">
          Owner: {parcel.owner}
        </Typography>
      </CardContent>
      <CardActions>
        <Button size="small" onClick={() => onView(parcel.id)}>
          Ver
        </Button>
        <Button size="small" onClick={() => onTransfer(parcel.id)}>
          Transferir
        </Button>
      </CardActions>
    </StyledCard>
  );
}
```

***

## Componentes Candidatos a UI2

Componentes que serão compartilhados entre projetos requerem padrões mais elevados e documentação mais completa.

### Requisitos

#### Alinhamento ao Tema

**DEVE** confiar exclusivamente nos valores do tema UI2:

```tsx
// ✅ Bom: Integração completa com o tema
const NavbarContainer = styled('nav')(({ theme }) => ({
  backgroundColor: theme.palette.background.paper,
  borderBottom: `1px solid ${theme.palette.divider}`,
  padding: theme.spacing(0, 2),
  height: 64,
  display: 'flex',
  alignItems: 'center',
  gap: theme.spacing(2),
  
  [theme.breakpoints.down('md')]: {
    padding: theme.spacing(0, 1),
  },
}));
```

#### Cobertura no Storybook

**DEVE** adicionar stories abrangentes no Storybook:

**Cobertura obrigatória:**

1. **Todas as props e variantes**
   * Todas as combinações de props
   * Todas as variações de tamanho
   * Todas as variações de cor
2. **Todos os estados**
   * Ocioso/padrão
   * Carregando
   * Erro
   * Desabilitado
   * Hover (via addon de pseudo estados)
   * Foco (via addon de pseudo estados)
3. **Interações**
   * Manipuladores de clique
   * Envio de formulários
   * Navegação por teclado
4. **Esquemas de cor**
   * Modo claro
   * Modo escuro
5. **Comportamento responsivo**
   * Breakpoints chave (xs, md, lg)
   * Documentar comportamento em cada breakpoint

**Arquivo de exemplo do Storybook:**

```tsx
// Navbar.stories.tsx
import type { Meta, StoryObj } from '@storybook/react';
import { Navbar } from './Navbar';

const meta: Meta<typeof Navbar> = {
  title: 'Components/Navbar',
  component: Navbar,
  parameters: {
    layout: 'fullscreen',
  },
  argTypes: {
    variant: {
      control: 'select',
      options: ['default', 'compact'],
    },
    showUserMenu: {
      control: 'boolean',
    },
  },
};

export default meta;
type Story = StoryObj<typeof Navbar>;

export const Default: Story = {
  args: {
    variant: 'default',
    showUserMenu: true,
  },
};

export const Compact: Story = {
  args: {
    variant: 'compact',
    showUserMenu: true,
  },
};

export const WithoutUserMenu: Story = {
  args: {
    variant: 'default',
    showUserMenu: false,
  },
};

export const Loading: Story = {
  args: {
    variant: 'default',
    showUserMenu: true,
    isLoading: true,
  },
};

// Testar diferentes viewports
export const Mobile: Story = {
  args: {
    variant: 'compact',
    showUserMenu: true,
  },
  parameters: {
    viewport: {
      defaultViewport: 'mobile1',
    },
  },
};

export const Tablet: Story = {
  args: {
    variant: 'default',
    showUserMenu: true,
  },
  parameters: {
    viewport: {
      defaultViewport: 'tablet',
    },
  },
};

// Testar esquemas de cor
export const DarkMode: Story = {
  args: {
    variant: 'default',
    showUserMenu: true,
  },
  parameters: {
    backgrounds: {
      default: 'dark',
    },
  },
};
```

### Estrutura do Componente

Componentes candidatos a UI2 DEVEM seguir esta estrutura:

```
src/components/Navbar/
├── Navbar.tsx           # Componente principal
├── Navbar.styles.ts     # Componentes estilizados
├── Navbar.stories.tsx   # Stories do Storybook
├── Navbar.test.tsx      # Testes unitários
├── types.ts             # Tipos TypeScript
├── index.ts             # Exports públicos
└── README.md            # Documentação do componente
```

### Requisitos de Documentação

**DEVE** incluir no README do componente:

1. **Propósito** - Qual problema isso resolve?
2. **Uso** - Como usar o componente
3. **Props** - Todas as props com tipos e descrições
4. **Exemplos** - Casos de uso comuns
5. **Acessibilidade** - Suporte de teclado, labels ARIA
6. **Theming** - Quais valores do tema ele usa
7. **Notas de migração** - Se estiver substituindo um componente do UI1

**Exemplo de README:**

````markdown
# Navbar

Componente de navegação do site com menu de usuário e comportamento responsivo.

## Uso

\```tsx
import { Navbar } from 'decentraland-ui2';

function App() {
  return (
    <Navbar
      variant="default"
      showUserMenu={true}
      onLogoClick={() => navigate('/') }
      onLoginClick={handleLogin}
    />
  );
}
\```

## Props

| Prop         | Type                   | Default   | Description                   |
| ------------ | ---------------------- | --------- | ----------------------------- |
| variant      | 'default' \| 'compact' | 'default' | Variação de navegação         |
| showUserMenu | boolean                | true      | Mostrar menu do usuário quando logado |
| onLogoClick  | () => void             | -         | Manipulador de clique no logo |
| onLoginClick | () => void             | -         | Manipulador de clique do botão de login |

## Acessibilidade

* Navegação por teclado: Tabular pelos itens do menu
* ARIA: Marcos e labels apropriados
* Leitor de tela: Anuncia o estado do menu

## Theming

Usa estes valores do tema:

* `theme.palette.background.paper`
* `theme.palette.divider`
* `theme.spacing`
* `theme.breakpoints`

````

### Requisitos de Testes

**DEVE** incluir testes para:

* Renderização de props
* Interações do usuário
* Recursos de acessibilidade
* Comportamento responsivo
* Estados de erro

```tsx
// Navbar.test.tsx
import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { Navbar } from './Navbar';

describe('Navbar', () => {
  it('deve renderizar o logo', () => {
    render(<Navbar />);
    expect(screen.getByRole('banner')).toBeInTheDocument();
  });

  it('deve chamar onLogoClick quando o logo for clicado', async () => {
    const onLogoClick = jest.fn();
    render(<Navbar onLogoClick={onLogoClick} />);
    
    await userEvent.click(screen.getByRole('link', { name: /decentraland/i }));
    expect(onLogoClick).toHaveBeenCalled();
  });

  it('deve ser navegável por teclado', async () => {
    render(<Navbar />);
    const firstLink = screen.getAllByRole('link')[0];
    
    firstLink.focus();
    expect(firstLink).toHaveFocus();
  });
});
```

***

## Matriz de Decisão

Use isto para decidir qual tipo de componente criar:

| Pergunta                                  | Específico do Projeto | Candidato a UI2 |
| ----------------------------------------- | --------------------- | --------------- |
| Outros projetos usarão isto?              | Não                   | Sim             |
| O UI1 tem um equivalente?                 | N/A                   | Provavelmente   |
| Precisa de documentação no Storybook?     | Não                   | **Sim**         |
| Precisa de testes abrangentes?            | Básico                | **Extensivo**   |
| Revisão de design necessária?             | Nível do projeto      | **Nível UI2**   |
| Pode usar padrões específicos do projeto? | Sim                   | **Não**         |
| Deve funcionar em todos os temas?         | Não                   | **Sim**         |

***

## Processo de Aprovação

### Componentes Específicos do Projeto

1. Revisão de código pelo mantenedor do projeto
2. Verificar conformidade com o tema
3. Testar no contexto do projeto
4. Fazer merge quando aprovado

### Componentes Candidatos a UI2

1. Revisão e aprovação de design
2. Revisão técnica de design
3. Implementação
4. Stories do Storybook
5. Testes abrangentes
6. Revisão de acessibilidade
7. Revisão de código
8. PR para o repositório UI2
9. Versionar e publicar
10. Atualizar projetos dependentes

***

## Boas Práticas

### Composição Sobre Customização

```tsx
// ✅ Bom: Compor componentes MUI
function FeatureCard({ title, children }) {
  return (
    <Card>
      <CardContent>
        <Typography variant="h6">{title}</Typography>
        {children}
      </CardContent>
    </Card>
  );
}

// ❌ Ruim: Re-implementar a funcionalidade do Card
function FeatureCard({ title, children }) {
  return (
    <div className="custom-card">
      <div className="custom-card-content">
        <h3>{title}</h3>
        {children}
      </div>
    </div>
  );
}
```

### Aprimoramento Progressivo

Começar simples e adicionar recursos conforme necessário:

1. Versão básica com funcionalidade central
2. Adicionar comportamento responsivo
3. Adicionar recursos de acessibilidade
4. Adicionar interações avançadas
5. Otimizar desempenho

### Documentação Primeiro

Antes de escrever código:

1. Escrever README do componente
2. Definir a interface de props
3. Listar estados necessários
4. Planejar stories do Storybook
5. Então implementar

***

## Próximos Passos

* Rever [Estilização & Theming](https://docs.decentraland.org/contributor/contributor-pt/guias-para-contribuidores/web-ui-standards/styling-and-theming) para detalhes de implementação
* Veja [Guia de Migração](https://github.com/decentraland/docs/blob/main/contributor/contributor-guides/web-ui-standards/broken-reference/README.md) para migrações de UI1 para UI2
* Verificar [Visão Geral do Processo](https://docs.decentraland.org/contributor/contributor-pt/guias-para-contribuidores/web-ui-standards/process-overview) para o fluxo de trabalho completo
