feat: tipo cep e tipo select mais documentação do eli input (ainda incompleta)
This commit is contained in:
parent
6c84508996
commit
4fdc5a95ce
4 changed files with 215 additions and 6 deletions
|
|
@ -47,6 +47,28 @@
|
|||
v-bind="attrs"
|
||||
/>
|
||||
|
||||
<!-- SELECT -->
|
||||
<v-select
|
||||
v-else-if="type === 'select'"
|
||||
v-model="value"
|
||||
:items="computedItems"
|
||||
:label="label"
|
||||
:placeholder="placeholder"
|
||||
:multiple="multiple"
|
||||
:chips="chips"
|
||||
:clearable="clearable"
|
||||
:disabled="disabled"
|
||||
:density="density"
|
||||
:variant="variant"
|
||||
item-title="label"
|
||||
item-value="value"
|
||||
:error="error"
|
||||
:error-messages="errorMessages"
|
||||
v-bind="attrs"
|
||||
@focus="onFocus"
|
||||
@blur="onBlur"
|
||||
/>
|
||||
|
||||
<!-- RADIO -->
|
||||
<v-radio-group
|
||||
v-else-if="type === 'radio'"
|
||||
|
|
@ -80,6 +102,7 @@ import { defineComponent, ref, computed, PropType } from "vue";
|
|||
import { formatarCpfCnpj } from "./utils/cpfCnpj";
|
||||
import { formatTelefone } from "./utils/telefone";
|
||||
import { formatarDecimal, formatarMoeda, somenteNumeros } from "./utils/numerico"
|
||||
import { formatarCep } from "./utils/cep";
|
||||
|
||||
type Option = {
|
||||
label: string;
|
||||
|
|
@ -114,6 +137,8 @@ type InputType =
|
|||
| "checkbox"
|
||||
| "telefone"
|
||||
| "cpfCnpj"
|
||||
| "cep"
|
||||
| "select"
|
||||
| TipoNumerico;
|
||||
|
||||
export default defineComponent({
|
||||
|
|
@ -138,6 +163,8 @@ export default defineComponent({
|
|||
color: { type: String, default: "primary" },
|
||||
row: Boolean,
|
||||
showPasswordToggle: Boolean,
|
||||
multiple: Boolean,
|
||||
chips: Boolean,
|
||||
},
|
||||
|
||||
emits: ["update:modelValue", "change", "focus", "blur"],
|
||||
|
|
@ -166,6 +193,7 @@ export default defineComponent({
|
|||
"numericoInteiro",
|
||||
"numericoDecimal",
|
||||
"numericoMoeda",
|
||||
"cep",
|
||||
].includes(props.type)
|
||||
);
|
||||
|
||||
|
|
@ -211,6 +239,10 @@ export default defineComponent({
|
|||
case "cpfCnpj":
|
||||
resultado = formatarCpfCnpj(resultado);
|
||||
break;
|
||||
|
||||
case "cep":
|
||||
resultado = formatarCep(resultado);
|
||||
break;
|
||||
}
|
||||
|
||||
target.value = resultado;
|
||||
|
|
@ -222,6 +254,27 @@ export default defineComponent({
|
|||
showPassword.value = !showPassword.value;
|
||||
}
|
||||
|
||||
// --- Helpers para select / radio / checkbox (aceita objetos ou primitivos) ---
|
||||
const computedItems = computed(() => {
|
||||
// Normaliza options para [{ label, value, disabled? }]
|
||||
return (props.options || []).map((o: any) =>
|
||||
o && typeof o === "object" && ("label" in o || "value" in o)
|
||||
? { label: o.label ?? String(o.value), value: o.value, disabled: o.disabled }
|
||||
: { label: String(o), value: o }
|
||||
);
|
||||
});
|
||||
|
||||
function optLabel(opt: any) {
|
||||
if (opt && typeof opt === "object") return opt.label ?? String(opt.value);
|
||||
return String(opt);
|
||||
}
|
||||
|
||||
function optValue(opt: any) {
|
||||
if (opt && typeof opt === "object") return opt.value;
|
||||
return opt;
|
||||
}
|
||||
|
||||
|
||||
return {
|
||||
attrs,
|
||||
value,
|
||||
|
|
@ -234,6 +287,9 @@ export default defineComponent({
|
|||
onInput,
|
||||
onFocus: () => emit("focus"),
|
||||
onBlur: () => emit("blur"),
|
||||
computedItems,
|
||||
optLabel,
|
||||
optValue,
|
||||
};
|
||||
},
|
||||
});
|
||||
|
|
|
|||
|
|
@ -0,0 +1,116 @@
|
|||
# EliInput
|
||||
|
||||
**Componente base de input do design system**
|
||||
|
||||
O EliInput unifica vários tipos de campo (v-text-field, v-textarea, v-select, v-radio-group, v-checkbox) em uma única API consistente. Ele encapsula comportamentos, máscaras e regras comuns (CPF/CNPJ, telefone, CEP, numéricos, formatação de moeda etc.) para manter coerência visual e de lógica em toda a aplicação.
|
||||
|
||||
> ⚠️ Nunca use os componentes Vuetify diretamente fora do design system para esses casos.
|
||||
> Utilize sempre EliInput para garantir formatação, tipagem e repasse de atributos padronizados.
|
||||
|
||||
---
|
||||
|
||||
## Visão geral
|
||||
|
||||
EliInput foi projetado para:
|
||||
|
||||
* Centralizar formatação (máscaras) para tipos de entrada comuns (telefone, CPF/CNPJ, CEP, numéricos).
|
||||
* Fornecer uma API única (type) que representa diferentes controles (text, textarea, select, radio, checkbox, etc.).
|
||||
* Repassar atributos/props do pai para o componente Vuetify interno (v-bind="$attrs") mantendo inheritAttrs: false.
|
||||
* Emitir eventos padronizados: update:modelValue, change, focus, blur.
|
||||
|
||||
---
|
||||
|
||||
## Principais decisões de implementação
|
||||
|
||||
* inheritAttrs: false — o componente controla explicitamente para onde os atributos são passados (v-bind="attrs" no elemento interno).
|
||||
* Uso de um computed value que faz emit("update:modelValue", v) e emit("change", v) — assim qualquer v-model no pai funciona como esperado.
|
||||
* Normalização de props.options para aceitar objetos { label, value, disabled } ou primitivos ('A', 1).
|
||||
* Separação clara entre lógica de formatação (aplicada em onInput) e componentes que não devem ser formatados (ex.: v-select).
|
||||
|
||||
---
|
||||
|
||||
## Tipagem (TypeScript)
|
||||
|
||||
```ts
|
||||
type Option = { label: string; value: any; disabled?: boolean };
|
||||
|
||||
type InputVariant = 'outlined' | 'filled' | 'plain' | 'solo' | 'solo-filled' | 'solo-inverted' | 'underlined';
|
||||
type Density = 'default' | 'comfortable' | 'compact';
|
||||
type TipoNumerico = 'numericoInteiro' | 'numericoDecimal' | 'numericoMoeda';
|
||||
|
||||
type InputType =
|
||||
| 'text' | 'password' | 'email' | 'search' | 'url' | 'textarea'
|
||||
| 'radio' | 'checkbox' | 'telefone' | 'cpfCnpj' | 'cep' | 'select'
|
||||
| TipoNumerico;
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Props
|
||||
|
||||
| Prop | Tipo | Default | Descrição |
|
||||
| ---------------- | --------------------------- | --------------- | ------------------------------------------------------ |
|
||||
| `modelValue` | `string \| number \| any[]` | `""` | Valor controlado (use com `v-model`). |
|
||||
| `type` | `InputType` | `"text"` | Tipo do controle (ver `InputType`). |
|
||||
| `label` | `string` | `-` | Rótulo do campo. |
|
||||
| `placeholder` | `string` | `-` | Texto exibido quando o campo está vazio. |
|
||||
| `disabled` | `boolean` | `false` | Desabilita o campo. |
|
||||
| `error` | `boolean` | `false` | Força estado visual de erro. |
|
||||
| `errorMessages` | `string \| string[]` | `[]` | Mensagem ou lista de mensagens de erro. |
|
||||
| `hint` | `string` | `-` | Texto de ajuda exibido abaixo do campo. |
|
||||
| `persistentHint` | `boolean` | `false` | Mantém o hint sempre visível. |
|
||||
| `variant` | `InputVariant` | `"outlined"` | Variante visual do Vuetify. |
|
||||
| `density` | `Density` | `"comfortable"` | Densidade visual do campo. |
|
||||
| `color` | `string` | `"primary"` | Cor do campo quando focado (ou `error`, se aplicável). |
|
||||
| `clearable` | `boolean` | `false` | Permite limpar o valor do campo. |
|
||||
|
||||
|
||||
## Notas sobre props
|
||||
|
||||
* options: aceita arrays como ['Frontend','Backend'] ou { label:'São Paulo', value:'SP' }. O componente normaliza para { label, value, disabled? }.
|
||||
* type determina quais comportamentos internos são aplicados. Tipos numéricos e máscara (telefone, cpfCnpj, cep) passam por formatação em onInput.
|
||||
* multiple/chips: úteis para type="select". O v-select interno recebe item-title="label" e item-value="value" para compatibilidade com objetos.
|
||||
|
||||
---
|
||||
|
||||
## Emissões (events)
|
||||
|
||||
* update:modelValue — padrão v-model.
|
||||
* change — emitido sempre que o valor muda (alinha com update:modelValue).
|
||||
* focus — quando o campo interno recebe foco.
|
||||
* blur — quando perde o foco.
|
||||
* Observação: como value é um computed com getter/setter que emite ambos, v-model e listeners de mudança no pai funcionarão normalmente.
|
||||
|
||||
---
|
||||
|
||||
## Repasso de atributos e listeners
|
||||
|
||||
* O componente define inheritAttrs: false e usa v-bind="attrs" nos componentes internos (v-text-field, v-select, etc.). Isso implica que:
|
||||
* Atributos HTML (ex.: type, aria-label, class, style) passados para <EliInput> são aplicados ao componente Vuetify interno apropriado.
|
||||
* Listeners (ex.: @click, @keydown) também fazem parte de $attrs e serão repassados para o componente interno — use o EliInput como se estivesse ouvindo eventos diretamente no input.
|
||||
|
||||
## Exemplo:
|
||||
|
||||
```vue
|
||||
<EliInput type="text" v-model="nome" aria-label="Nome completo" @keydown.enter="enviar" />
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Comportamentos de formatação importantes
|
||||
|
||||
* numericoInteiro — remove tudo que não for dígito.
|
||||
* numericoDecimal — mantém separador decimal (aplica formatarDecimal).
|
||||
* numericoMoeda — formata para moeda conforme util (formatarMoeda).
|
||||
* telefone — aplica máscara/format formatTelefone.
|
||||
* cpfCnpj — aplica formatarCpfCnpj.
|
||||
* cep — aplica formatarCep.
|
||||
|
||||
**Importante: a formatação ocorre no onInput (campos text-like). O v-select não passa por onInput — ele usa v-model="value" e o computed que emite o update. Se desejar formatação específica para itens do select (por exemplo, mostrar label formatado), trate nos options antes de passar.**
|
||||
|
||||
---
|
||||
|
||||
## Slot
|
||||
|
||||
* O componente não expõe slots customizados diretamente. Ele controla internamente o append para toggle de senha quando type === 'password' && showPasswordToggle (ícone de olho).
|
||||
* Se você precisa de slots específicos do v-text-field/v-select, considere estender o componente ou criar uma variação que exponha os slots desejados.
|
||||
9
src/componentes/EliInput/utils/cep.ts
Normal file
9
src/componentes/EliInput/utils/cep.ts
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
import { somenteNumeros } from "./numerico";
|
||||
|
||||
export function formatarCep(v: string): string {
|
||||
const d = somenteNumeros(v).slice(0, 8);
|
||||
|
||||
if (d.length <= 5) return d;
|
||||
|
||||
return d.replace(/^(\d{5})(\d{1,3})$/, "$1-$2");
|
||||
}
|
||||
|
|
@ -18,20 +18,44 @@
|
|||
density="compact"
|
||||
/>
|
||||
|
||||
<EliInput v-model="idade" type="numericoInteiro" label="Idade" density="default" />
|
||||
<EliInput
|
||||
v-model="idade"
|
||||
type="numericoInteiro"
|
||||
label="Idade"
|
||||
density="default"
|
||||
/>
|
||||
|
||||
<EliInput v-model="altura" type="numericoDecimal" label="Altura" density="comfortable" />
|
||||
<EliInput
|
||||
v-model="altura"
|
||||
type="numericoDecimal"
|
||||
label="Altura"
|
||||
density="comfortable"
|
||||
/>
|
||||
|
||||
<EliInput v-model="valor" type="numericoMoeda" label="Valor" />
|
||||
|
||||
<EliInput v-model="telefone" type="telefone" label="Telefone" />
|
||||
|
||||
<EliInput
|
||||
v-model="documento"
|
||||
type="cpfCnpj"
|
||||
label="CPF / CNPJ"
|
||||
v-model="cep"
|
||||
type="cep"
|
||||
label="CEP"
|
||||
placeholder="00000-000"
|
||||
/>
|
||||
|
||||
<EliInput
|
||||
type="select"
|
||||
label="Estado"
|
||||
:options="[
|
||||
{ label: 'São Paulo', value: 'SP' },
|
||||
{ label: 'Rio de Janeiro', value: 'RJ' }
|
||||
]"
|
||||
v-model="estado"
|
||||
multiple
|
||||
/>
|
||||
|
||||
<EliInput v-model="documento" type="cpfCnpj" label="CPF / CNPJ" />
|
||||
|
||||
<EliInput
|
||||
v-model="email"
|
||||
label="Email"
|
||||
|
|
@ -108,6 +132,8 @@ export default defineComponent({
|
|||
},
|
||||
setup() {
|
||||
const nome = ref("");
|
||||
const estado = ref([])
|
||||
const cep = ref("");
|
||||
const telefone = ref("");
|
||||
const idade = ref("");
|
||||
const altura = ref("");
|
||||
|
|
@ -115,13 +141,14 @@ export default defineComponent({
|
|||
const email = ref("");
|
||||
const mensagem = ref("");
|
||||
const senha = ref("");
|
||||
const documento = ref("")
|
||||
const documento = ref("");
|
||||
const cor = ref(null);
|
||||
const habilidades = ref<any[]>([]);
|
||||
return {
|
||||
nome,
|
||||
email,
|
||||
documento,
|
||||
estado,
|
||||
telefone,
|
||||
mensagem,
|
||||
senha,
|
||||
|
|
@ -129,6 +156,7 @@ export default defineComponent({
|
|||
habilidades,
|
||||
idade,
|
||||
altura,
|
||||
cep,
|
||||
valor,
|
||||
};
|
||||
},
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue