# eli-vue — Design System (Vue 3 + TypeScript)

Biblioteca de componentes Vue 3 (Design System) para reutilização em múltiplos projetos.

Este README foi escrito para **humanos e IAs**: ele descreve o objetivo do repositório, regras, estrutura, fluxo de contribuição, comandos e um roadmap de melhorias.

## Fonte da verdade (regras)

As regras oficiais do repositório estão no arquivo **`.agent`**.

Antes de propor mudanças:

- leia o `.agent`
- procure padrões já existentes no código
- atualize a documentação correspondente (README raiz e/ou README do componente)

## Objetivos

- consistência visual e comportamental
- tipagem forte (TypeScript `strict`)
- documentação em português
- exemplos executáveis via playground

## O que NÃO entra no contexto

- `node_modules/`: dependências (não versionar; não usar como fonte da verdade)
- `dist/`: gerado no build (não versionar)

## Arquitetura do repositório

```
src/
  componentes/
    botao/
      EliBotao.vue
      index.ts
      README.md
    cartao/
      EliCartao.vue
      index.ts
      README.md
    indicador/
      EliBadge.vue
      index.ts
      README.md
    EliEntrada/
      EliEntradaTexto.vue
      EliEntradaNumero.vue
      EliEntradaDataHora.vue
      index.ts
      README.md
    EliTabela/
      EliTabela.vue
      index.ts
      README.md
      celulas/
        README.md
  tipos/
    botao.ts
    indicador.ts
    index.ts
  playground/
    App.vue
    *.playground.vue
  index.ts
```

### Convenções (nomenclatura)

- Componentes usam **prefixo `Eli`** (ex.: `EliBotao`, `EliEntradaTexto`).
- Pastas preferem **português** (ex.: `src/componentes/botao`, `src/componentes/campo`).
- Tipos compartilhados ficam em `src/tipos/`.
- Sem TSX; padrão SFC: `<template>` + `<script lang="ts">` + `defineComponent`.
- `any` é proibido.

## Instalação

```bash
pnpm add eli-vue
```

> Observação: `vue` e `vuetify` são **peerDependencies**.

### Instalação (projeto consumidor já usa Vuetify)

Se o seu projeto já está configurado com Vuetify 3, basta instalar este pacote:

```bash
pnpm add eli-vue
```

> Dica: mantenha `vue` e `vuetify` atualizados dentro das faixas suportadas.

## Uso

### 1) Registro global (plugin)

```ts
import { createApp } from "vue";
import EliVue from "eli-vue";

import App from "./App.vue";

createApp(App).use(EliVue).mount("#app");
```

#### Exemplo completo (main.ts) — com Vuetify 3

Este é o modelo recomendado para projetos consumidores (Vite + Vue 3) **que já usam Vuetify 3**:

```ts
import { createApp } from "vue";
import App from "./App.vue";

// Vuetify
import "vuetify/styles";
import { createVuetify } from "vuetify";

// eli-vue (Design System)
import EliVue from "eli-vue";

const vuetify = createVuetify({
  // opcional: componentes/directives/tema do seu projeto
});

createApp(App)
  .use(vuetify)
  .use(EliVue)
  .mount("#app");
```

> Nota: o `eli-vue` **não cria** nem configura Vuetify; ele assume que o consumidor já tem Vuetify.

### 2) Importação direta de componentes

```ts
import {
  EliBotao,
  EliBadge,
  EliCartao,
  EliTabela,
  EliEntradaTexto,
  EliEntradaNumero,
  EliEntradaDataHora,
  EliEntradaParagrafo,
  EliEntradaSelecao,
} from "eli-vue";
```

## Exemplos rápidos de uso

### EliBotao

```vue
<template>
  <EliBotao @click="salvar">Salvar</EliBotao>
</template>
```

### Entradas (EliEntrada*) com v-model

```vue
<template>
  <EliEntradaTexto
    v-model:value="nome"
    :opcoes="{ rotulo: 'Nome', placeholder: 'Digite seu nome' }"
  />
</template>

<script lang="ts">
import { defineComponent, ref } from "vue";

export default defineComponent({
  setup() {
    const nome = ref<string | null>("");
    return { nome };
  },
});
</script>
```

### EliEntradaNumero (exemplo)

```vue
<template>
  <EliEntradaNumero
    v-model:value="taxa"
    :opcoes="{ rotulo: 'Taxa', placeholder: '0,00' }"
  />
</template>

<script lang="ts">
import { defineComponent, ref } from "vue";

export default defineComponent({
  setup() {
    const taxa = ref<number | null>(null);
    return { taxa };
  },
});
</script>
```

### EliBadge

```vue
<template>
  <EliBadge badge="3">
    <v-icon>mdi-bell</v-icon>
  </EliBadge>
</template>
```

### EliEntradaDataHora

> Entrada/saída sempre em **ISO 8601**.
> - Aceita UTC absoluto (`Z`) ou com offset.
> - Emite ISO com **offset local**.

```vue
<template>
  <EliEntradaDataHora v-model:value="agendamento" :opcoes="{ rotulo: 'Agendamento' }" />
  <EliEntradaDataHora
    v-model:value="nascimento"
    :opcoes="{ rotulo: 'Nascimento', modo: 'data' }"
  />
</template>

<script lang="ts">
import { defineComponent, ref } from "vue";
import { EliEntradaDataHora } from "eli-vue";

export default defineComponent({
  components: { EliEntradaDataHora },
  setup() {
    const agendamento = ref<string | null>("2026-01-09T16:15:00Z");
    const nascimento = ref<string | null>("2026-01-09T00:00:00-03:00");
    return { agendamento, nascimento };
  },
});
</script>
```

### EliTabela — célula `tags`

Você pode renderizar múltiplas tags/chips numa célula usando `celulaTabela("tags", ...)`.

```ts
import { celulaTabela } from "eli-vue";
import { BadgeCheck, Pencil } from "lucide-vue-next";

celula: (l) =>
  celulaTabela("tags", {
    opcoes: [
      { rotulo: "Ativo", cor: "success", icone: BadgeCheck },
      { rotulo: "Editar", cor: "primary", icone: Pencil, acao: () => editar(l) },
    ],
  })
```

### EliTabela — célula `numero` com prefixo/sufixo

Para representar **moeda** ou **unidade de medida**, a célula `numero` aceita `prefixo` e `sufixo`:

```ts
import { celulaTabela } from "eli-vue";

// moeda
celula: (l) => celulaTabela("numero", { numero: l.total, prefixo: "R$" })

// unidade
celula: (l) => celulaTabela("numero", { numero: l.peso, sufixo: "kg" })
```

### EliTabela — célula `data`

Para exibir datas (ISO 8601) com diferentes formatos, use a célula `data`:

```ts
import { celulaTabela } from "eli-vue";

// somente data
celula: (l) => celulaTabela("data", { valor: l.criado_em, formato: "data" })

// data e hora
celula: (l) => celulaTabela("data", { valor: l.criado_em, formato: "data_hora" })

// relativo (dayjs)
celula: (l) => celulaTabela("data", { valor: l.atualizado_em, formato: "relativo" })
```

## Troubleshooting (problemas comuns)

### 1) "Failed to resolve component" / componente não registrado

Você provavelmente esqueceu de:

- usar o plugin: `app.use(EliVue)`
- ou importar e registrar o componente localmente

### 2) Estilos não aplicam / layout estranho

Confirme que o consumidor importou:

- `vuetify/styles`

### 3) Ícones não aparecem

Alguns exemplos usam `<v-icon>` com Material Design Icons. O `eli-vue` **não instala ícones** no seu projeto.

- garanta que o seu projeto consumidor está configurado com o pacote de ícones desejado (ex.: MDI)
- ou substitua o conteúdo do slot por outro elemento

## Exemplo de composição (pipeline estilo Trello)

O caso mais comum no **gestor de oportunidades e propostas comerciais** é compor UI a partir de componentes básicos.
Exemplo: um **pipeline** em colunas (estilo Trello/Kanban) com cards de oportunidades.

> Este exemplo usa Vuetify para layout/containers e o `eli-vue` para inputs, botões e indicadores.

```vue
<template>
  <v-container class="py-6">
    <div class="toolbar">
      <h2 class="text-h6">Pipeline de Oportunidades</h2>
      <EliEntradaTexto
        v-model:value="filtro"
        :opcoes="{ rotulo: 'Buscar', placeholder: 'Cliente, proposta, valor...' }"
      />
      <EliBotao @click="criarOportunidade">Nova oportunidade</EliBotao>
    </div>

    <div class="colunas">
      <section
        v-for="coluna in colunas"
        :key="coluna.id"
        class="coluna"
      >
        <header class="coluna-header">
          <strong>{{ coluna.titulo }}</strong>
          <EliBadge :badge="coluna.itens.length" radius="pill" />
        </header>

        <div class="cards">
          <v-card
            v-for="item in itensFiltrados(coluna.itens)"
            :key="item.id"
            class="card"
            variant="outlined"
          >
            <v-card-title class="d-flex justify-space-between align-center">
              <span class="text-subtitle-2">{{ item.titulo }}</span>
              <EliBadge
                v-if="item.alerta"
                dot
                color="error"
                :visible="true"
              >
                <span />
              </EliBadge>
            </v-card-title>

            <v-card-text class="text-body-2">
              <div><strong>Cliente:</strong> {{ item.cliente }}</div>
              <div><strong>Valor:</strong> {{ item.valor }}</div>
              <div><strong>Vencimento:</strong> {{ item.vencimento }}</div>
            </v-card-text>

            <v-card-actions>
              <EliBotao variant="text" @click="abrir(item)">Abrir</EliBotao>
              <EliBotao variant="text" color="secondary" @click="editar(item)">
                Editar
              </EliBotao>
            </v-card-actions>
          </v-card>
        </div>
      </section>
    </div>
  </v-container>
</template>

<script lang="ts">
import { defineComponent, ref } from "vue";
import { EliBadge, EliBotao, EliEntradaTexto } from "eli-vue";

type Oportunidade = {
  id: string;
  titulo: string;
  cliente: string;
  valor: string;
  vencimento: string;
  alerta?: boolean;
};

type Coluna = {
  id: string;
  titulo: string;
  itens: Oportunidade[];
};

export default defineComponent({
  name: "PipelineExemplo",
  components: { EliBadge, EliBotao, EliEntradaTexto },
  setup() {
    const filtro = ref("");

    const colunas = ref<Coluna[]>([
      {
        id: "novo",
        titulo: "Novo",
        itens: [
          {
            id: "1",
            titulo: "Proposta #1024",
            cliente: "ACME Ltda",
            valor: "R$ 12.500,00",
            vencimento: "10/01/2026",
            alerta: true,
          },
        ],
      },
      {
        id: "negociacao",
        titulo: "Negociação",
        itens: [
          {
            id: "2",
            titulo: "Oportunidade #204",
            cliente: "Empresa X",
            valor: "R$ 8.000,00",
            vencimento: "15/01/2026",
          },
        ],
      },
      {
        id: "ganho",
        titulo: "Ganho",
        itens: [],
      },
    ]);

    function itensFiltrados(itens: Oportunidade[]) {
      const q = filtro.value.trim().toLowerCase();
      if (!q) return itens;
      return itens.filter((i) =>
        `${i.titulo} ${i.cliente} ${i.valor}`.toLowerCase().includes(q)
      );
    }

    function criarOportunidade() {
      // aqui você abriria um modal / rota de criação
    }

    function abrir(item: Oportunidade) {
      // aqui você navega para detalhes da oportunidade
    }

    function editar(item: Oportunidade) {
      // aqui você abre edição
    }

    return {
      filtro,
      colunas,
      itensFiltrados,
      criarOportunidade,
      abrir,
      editar,
    };
  },
});
</script>

<style scoped>
.toolbar {
  display: grid;
  grid-template-columns: 1fr minmax(260px, 420px) auto;
  gap: 12px;
  align-items: center;
  margin-bottom: 16px;
}

.colunas {
  display: grid;
  gap: 16px;
  grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
}

.coluna {
  border: 1px solid rgba(0, 0, 0, 0.12);
  border-radius: 12px;
  padding: 12px;
  background: rgba(0, 0, 0, 0.02);
}

.coluna-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin-bottom: 12px;
}

.cards {
  display: grid;
  gap: 12px;
}

.card {
  border-radius: 12px;
}

@media (max-width: 720px) {
  .toolbar {
    grid-template-columns: 1fr;
  }
}
</style>
```

## Como rodar localmente

### Playground

```bash
pnpm dev
```

O playground (`src/playground`) valida visualmente variações e interações.

### Build da lib + geração de tipos

```bash
pnpm run build
```

Gera `dist/` (artefatos de build) e `dist/types` (declarações `.d.ts`).

> Observação importante: este repositório roda `npm version patch --no-git-tag-version` no `prebuild`.
> Ou seja, ao rodar `pnpm run build` a versão do `package.json` é incrementada automaticamente.

## Guia rápido para IAs (antes de codar)

1) **Leia** `.agent` e este README.
2) **Busque padrões** antes de criar API nova (props/emits/slots).
3) **Não use `any`**.
4) **Centralize tipos** em `src/tipos/`.
5) Ao mudar API/comportamento:
   - atualize o `README.md` do componente
   - atualize ou crie um `*.playground.vue`
6) Rode `pnpm run build` antes de finalizar.

## Como criar/alterar um componente (checklist)

1. Criar pasta em `src/componentes/<nome_em_portugues>/`
2. Criar `EliNomeDoComponente.vue` com `defineComponent` + comentários úteis
3. Criar `index.ts` com re-export (ex.: `export { default as EliX } from './EliX.vue'`)
4. Criar `README.md` do componente (API, exemplos, casos de borda, A11y)
5. Criar playground em `src/playground/<nome>.playground.vue` (mín. 3 variações)
6. Exportar no `src/index.ts`

### Critérios de aceite (qualidade)

- `pnpm run build` passa (inclui `vue-tsc`)
- zero `any`
- `defineComponent` em todos os componentes
- README do componente atualizado quando API mudar
- playground demonstrando variações + interação

## Roadmap de melhorias (a aplicar)

### Curto prazo (qualidade / consistência)
- [ ] Padronizar comentários úteis em todos os componentes (explicar decisões e estados)
- [ ] Tipar `emits` com validação de payload (quando houver)
- [ ] Consolidar nomenclatura de props/eventos em português (avaliar breaking changes)

### Médio prazo (DX + segurança)
- [ ] Adicionar ESLint + Prettier e regras (incluindo proibição de `any`)
- [ ] Adicionar testes unitários com Vitest + @vue/test-utils
- [ ] Criar pipeline de CI rodando `pnpm run build`

### Longo prazo (docs e maturidade)
- [ ] Documentação visual (Storybook ou VitePress)
- [ ] Tokens de design (cores/spacing) e guideline de acessibilidade
