This commit is contained in:
Luiz Silva 2026-01-29 18:31:52 -03:00
parent 8c5a31ef30
commit a693081023
34 changed files with 14887 additions and 1146 deletions

View file

@ -0,0 +1,78 @@
<template>
<button
v-if="dados?.acao"
type="button"
class="eli-tabela__celula-link"
@click.stop.prevent="dados.acao()"
>
{{ textoData }}
</button>
<span v-else>{{ textoData }}</span>
</template>
<script lang="ts">
import { computed, defineComponent, PropType } from "vue";
import dayjs from "dayjs";
import relativeTime from "dayjs/plugin/relativeTime";
import type { TiposTabelaCelulas } from "./tiposTabelaCelulas";
// Necessário para `fromNow()`.
dayjs.extend(relativeTime);
export default defineComponent({
name: "EliTabelaCelulaData",
props: {
dados: {
type: Object as PropType<TiposTabelaCelulas["data"]>,
required: false,
},
},
setup({ dados }) {
const textoData = computed(() => {
const valorIso = dados?.valor;
if (!valorIso) return "";
const formato = dados?.formato ?? "data";
if (formato === "relativo") {
return dayjs(valorIso).fromNow();
}
if (formato === "data_hora") {
// Padrão pt-BR simples (sem depender de locale do dayjs)
return dayjs(valorIso).format("DD/MM/YYYY HH:mm");
}
// formato === "data"
return dayjs(valorIso).format("DD/MM/YYYY");
});
return { dados, textoData };
},
});
</script>
<style scoped>
.eli-tabela__celula-link {
all: unset;
display: inline;
color: #2563eb;
cursor: pointer;
text-decoration: underline;
text-decoration-color: rgba(37, 99, 235, 0.55);
text-underline-offset: 2px;
}
.eli-tabela__celula-link:hover {
color: #1d4ed8;
text-decoration-color: rgba(29, 78, 216, 0.75);
}
.eli-tabela__celula-link:focus-visible {
outline: 2px solid rgba(37, 99, 235, 0.45);
outline-offset: 2px;
border-radius: 4px;
}
</style>

View file

@ -5,13 +5,13 @@
class="eli-tabela__celula-link"
@click.stop.prevent="dados.acao()"
>
{{ String(dados?.numero).replace('.', ',') }}
{{ textoNumero }}
</button>
<span v-else>{{ String(dados?.numero).replace('.', ',') }}</span>
<span v-else>{{ textoNumero }}</span>
</template>
<script lang="ts">
import { defineComponent, PropType } from "vue"
import { computed, defineComponent, PropType } from "vue"
import type { TiposTabelaCelulas } from "./tiposTabelaCelulas";
export default defineComponent({
@ -21,16 +21,21 @@ export default defineComponent({
dados: {
type: Object as PropType<TiposTabelaCelulas["numero"]>,
},
},
data() {
return {
}
},
methods: {
},
setup({ dados }) {
return { dados }
const textoNumero = computed(() => {
// Mantemos o comportamento anterior (trocar "." por ","), mas agora suportamos prefixo/sufixo.
const numero = String(dados?.numero).replace(".", ",");
const prefixo = dados?.prefixo?.trim();
const sufixo = dados?.sufixo?.trim();
const inicio = prefixo ? `${prefixo} ` : "";
const fim = sufixo ? ` ${sufixo}` : "";
return `${inicio}${numero}${fim}`;
});
return { dados, textoNumero }
},
})
</script>

View file

@ -0,0 +1,61 @@
<template>
<div class="eli-tabela__celula-tags">
<v-chip
v-for="(tag, idx) in dados?.opcoes ?? []"
:key="idx"
class="eli-tabela__celula-tag"
size="small"
variant="tonal"
:color="tag.cor"
:clickable="Boolean(tag.acao)"
@click.stop.prevent="tag.acao?.()"
>
<component
:is="tag.icone"
v-if="tag.icone"
class="eli-tabela__celula-tag-icone"
:size="14"
/>
<span>{{ tag.rotulo }}</span>
</v-chip>
</div>
</template>
<script lang="ts">
import { defineComponent, PropType } from "vue";
import { VChip } from "vuetify/components";
import type { TiposTabelaCelulas } from "./tiposTabelaCelulas";
export default defineComponent({
name: "EliTabelaCelulaTags",
components: { VChip },
props: {
dados: {
type: Object as PropType<TiposTabelaCelulas["tags"]>,
required: false,
},
},
setup({ dados }) {
return { dados };
},
});
</script>
<style scoped>
.eli-tabela__celula-tags {
display: flex;
flex-wrap: wrap;
gap: 6px;
align-items: center;
}
.eli-tabela__celula-tag {
cursor: default;
}
.eli-tabela__celula-tag-icone {
margin-right: 6px;
}
</style>

View file

@ -0,0 +1,81 @@
# Células da EliTabela
Este diretório contém os componentes de **célula** usados pela `EliTabela`.
## Como funcionam as células
A `EliTabela` não renderiza o conteúdo direto: cada coluna retorna uma tupla tipada via helper:
```ts
import { celulaTabela } from "@/componentes/EliTabela";
celula: (linha) => celulaTabela("textoSimples", { texto: linha.nome })
```
O `tipo` seleciona qual componente de célula será usado (via registry) e o `dados` define o payload tipado.
---
## Tipos disponíveis
### 1) `textoSimples`
```ts
{ texto: string; acao?: () => void }
```
### 2) `textoTruncado`
```ts
{ texto: string; acao?: () => void }
```
### 3) `numero`
```ts
{ numero: number; prefixo?: string; sufixo?: string; acao?: () => void }
```
Exemplos:
```ts
// moeda
celula: (l) => celulaTabela("numero", { numero: l.total, prefixo: "R$" })
// unidade de medida
celula: (l) => celulaTabela("numero", { numero: l.peso, sufixo: "kg" })
```
### 4) `tags`
```ts
{
opcoes: {
rotulo: string;
cor?: string;
icone?: import("lucide-vue-next").LucideIcon;
acao?: () => void;
}[]
}
```
### 5) `data`
```ts
{ valor: string; formato: "data" | "data_hora" | "relativo"; acao?: () => void }
```
Exemplos:
```ts
celula: (l) => celulaTabela("data", { valor: l.criado_em, formato: "data" })
celula: (l) => celulaTabela("data", { valor: l.criado_em, formato: "data_hora" })
celula: (l) => celulaTabela("data", { valor: l.atualizado_em, formato: "relativo" })
```
---
## Onde ficam os tipos e o registry
- Tipos: `src/componentes/EliTabela/celulas/tiposTabelaCelulas.ts`
- Registry: `src/componentes/EliTabela/celulas/registryTabelaCelulas.ts`

View file

@ -3,10 +3,14 @@ import type { Component } from "vue";
import EliTabelaCelulaTextoSimples from "./EliTabelaCelulaTextoSimples.vue";
import EliTabelaCelulaTextoTruncado from "./EliTabelaCelulaTextoTruncado.vue";
import EliTabelaCelulaNumero from "./EliTabelaCelulaNumero.vue";
import EliTabelaCelulaTags from "./EliTabelaCelulaTags.vue";
import EliTabelaCelulaData from "./EliTabelaCelulaData.vue";
import type { TipoTabelaCelula } from "./tiposTabelaCelulas";
export const registryTabelaCelulas = {
textoSimples: EliTabelaCelulaTextoSimples,
textoTruncado: EliTabelaCelulaTextoTruncado,
numero: EliTabelaCelulaNumero,
tags: EliTabelaCelulaTags,
data: EliTabelaCelulaData,
} as const satisfies Record<TipoTabelaCelula, Component>;

View file

@ -2,6 +2,8 @@
* Tipagem dos dados de entrada dos componentes de celulas
*/
import type { LucideIcon } from "lucide-vue-next";
export type TiposTabelaCelulas = {
textoSimples: {
texto: string;
@ -13,6 +15,32 @@ export type TiposTabelaCelulas = {
};
numero: {
numero: number;
/** Texto opcional exibido antes do número (ex.: "R$", "≈"). */
prefixo?: string;
/** Texto opcional exibido depois do número (ex.: "kg", "%"). */
sufixo?: string;
acao?: () => void;
};
tags: {
opcoes: {
/** Texto exibido dentro da tag. */
rotulo: string;
/** Cor do chip (segue as cores do Vuetify, ex.: "primary", "success", "error"). */
cor?: string;
/** Ícone (Lucide) opcional exibido antes do rótulo. */
icone?: LucideIcon;
/** Ação opcional da tag. Quando existir, o chip vira clicável. */
acao?: () => void;
}[];
};
data: {
/** Valor em ISO 8601 (ex.: "2026-01-09T16:15:00Z"). */
valor: string;
/** Define o formato de exibição. */
formato: "data" | "data_hora" | "relativo";
/** Ação opcional ao clicar no valor. */
acao?: () => void;
};
};