512 lines
12 KiB
Markdown
512 lines
12 KiB
Markdown
# 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
|
|
campo/
|
|
EliInput.vue
|
|
index.ts
|
|
README.md
|
|
indicador/
|
|
EliBadge.vue
|
|
index.ts
|
|
README.md
|
|
data_hora/
|
|
EliDataHora.vue
|
|
index.ts
|
|
README.md
|
|
tipos/
|
|
botao.ts
|
|
campo.ts
|
|
indicador.ts
|
|
index.ts
|
|
playground/
|
|
App.vue
|
|
*.playground.vue
|
|
index.ts
|
|
```
|
|
|
|
### Convenções (nomenclatura)
|
|
|
|
- Componentes usam **prefixo `Eli`** (ex.: `EliBotao`, `EliInput`).
|
|
- 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";
|
|
import "eli-vue/dist/eli-vue.css";
|
|
|
|
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, EliInput, EliBadge, EliDataHora } from "eli-vue";
|
|
```
|
|
|
|
## Exemplos rápidos de uso
|
|
|
|
### EliBotao
|
|
|
|
```vue
|
|
<template>
|
|
<EliBotao @click="salvar">Salvar</EliBotao>
|
|
</template>
|
|
```
|
|
|
|
### EliInput (v-model)
|
|
|
|
```vue
|
|
<template>
|
|
<EliInput v-model="nome" label="Nome" placeholder="Digite seu nome" />
|
|
</template>
|
|
|
|
<script lang="ts">
|
|
import { defineComponent, ref } from "vue";
|
|
|
|
export default defineComponent({
|
|
setup() {
|
|
const nome = ref("");
|
|
return { nome };
|
|
},
|
|
});
|
|
</script>
|
|
```
|
|
|
|
### EliInput (porcentagem)
|
|
|
|
Use `type="porcentagem"` quando precisar de um campo numérico com sufixo `%` embutido.
|
|
|
|
```vue
|
|
<template>
|
|
<EliInput v-model="taxa" type="porcentagem" label="Taxa" placeholder="0,00" />
|
|
</template>
|
|
|
|
<script lang="ts">
|
|
import { defineComponent, ref } from "vue";
|
|
|
|
export default defineComponent({
|
|
setup() {
|
|
const taxa = ref("");
|
|
return { taxa };
|
|
},
|
|
});
|
|
</script>
|
|
```
|
|
|
|
### EliBadge
|
|
|
|
```vue
|
|
<template>
|
|
<EliBadge badge="3">
|
|
<v-icon>mdi-bell</v-icon>
|
|
</EliBadge>
|
|
</template>
|
|
```
|
|
|
|
### EliDataHora
|
|
|
|
> Entrada/saída sempre em **ISO 8601**.
|
|
> - Aceita UTC absoluto (`Z`) ou com offset.
|
|
> - Emite ISO com **offset local**.
|
|
|
|
```vue
|
|
<template>
|
|
<EliDataHora v-model="agendamento" rotulo="Agendamento" />
|
|
<EliDataHora v-model="nascimento" modo="data" rotulo="Nascimento" />
|
|
</template>
|
|
|
|
<script lang="ts">
|
|
import { defineComponent, ref } from "vue";
|
|
import { EliDataHora } from "eli-vue";
|
|
|
|
export default defineComponent({
|
|
components: { EliDataHora },
|
|
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>
|
|
```
|
|
|
|
## 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`
|
|
- `eli-vue/dist/eli-vue.css`
|
|
|
|
### 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>
|
|
<EliInput
|
|
v-model="filtro"
|
|
label="Buscar"
|
|
placeholder="Cliente, proposta, valor..."
|
|
density="compact"
|
|
/>
|
|
<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, EliInput } 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, EliInput },
|
|
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`).
|
|
|
|
## 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
|