diff --git a/src/componentes/EliBadge/EliBadge.vue b/src/componentes/EliBadge/EliBadge.vue deleted file mode 100644 index 260974e..0000000 --- a/src/componentes/EliBadge/EliBadge.vue +++ /dev/null @@ -1,128 +0,0 @@ - - - - - diff --git a/src/componentes/EliBadge/README.md b/src/componentes/EliBadge/README.md deleted file mode 100644 index 66a8911..0000000 --- a/src/componentes/EliBadge/README.md +++ /dev/null @@ -1,182 +0,0 @@ -# EliBadge - -**Componente base de badge do design system** - -O `EliBadge` encapsula o `v-badge` do Vuetify para aplicar padrões visuais, tipagem e comportamento previsível em toda a aplicação. - -> ⚠️ **Nunca use `v-badge` diretamente fora do design system.** -> Utilize sempre o `EliBadge` para manter consistência visual e compatibilidade entre versões do Vuetify. - ---- - -## Visão geral - -O `EliBadge` foi projetado para: - -- Garantir consistência visual no uso de badges; -- Fornecer uma API tipada (TypeScript) e com autocomplete; -- Controlar corretamente a exibição do badge sem afetar o conteúdo principal; -- Permitir presets semânticos de `border-radius` e valores CSS customizados; -- Repassar atributos e listeners de forma transparente ao `v-badge`. - -### Principais decisões de implementação - -- `inheritAttrs: false` — o componente faz `v-bind="$attrs"` manualmente no `v-badge`. -- Uso de CSS Variable (`--eli-badge-radius`) para controlar o `border-radius`. -- Presets tipados para `radius` + fallback para valores CSS livres. -- O slot padrão **nunca é removido**: apenas o badge é condicional. - ---- - -## Tipagem (TypeScript) - -```ts -type LocalBadge = - | "top right" - | "right center" - | "bottom right" - | "top center" - | "bottom center" - | "top left" - | "left center" - | "bottom left"; - -type Offset = "-20" | "-15" | "-10" | "-5" | "0" | "20" | "15" | "10" | "5"; - -type BadgeRadiusPreset = "suave" | "pill"; - -type CssLength = `${number}px` | `${number}rem` | `${number}%` | "0"; - ---- - -## Props -| Prop | Tipo | Default | Descrição | -| ---------- | -------------------------------- | ------------- | ---------------------------------------------- | -| `color` | `string` | `"primary"` | Cor visual do badge (Vuetify theme). | -| `location` | `LocalBadge` | `"top right"` | Posição do badge em relação ao conteúdo. | -| `offsetX` | `Offset` | `"0"` | Deslocamento horizontal. | -| `offsetY` | `Offset` | `"0"` | Deslocamento vertical. | -| `dot` | `boolean` | `false` | Exibe badge no formato de ponto. | -| `visible` | `boolean` | `true` | Controla a exibição do badge (slot permanece). | -| `badge` | `string \| number \| undefined` | `undefined` | Conteúdo textual ou numérico do badge. | -| `radius` | `BadgeRadiusPreset \| CssLength` | `"suave"` | Preset ou valor CSS de `border-radius`. | - -Presets de radius -{ - suave: "4px", // cantos levemente arredondados - pill: "10px", // cantos bem arredondados -} - ---- - - Repasso de atributos e listeners - -Como inheritAttrs: false e v-bind="$attrs" são usados: - - 1. Atributos HTML (ex.: type, aria-label, class, style) passados para serão aplicados ao v-badge filho. - - 2. Listeners (ex.: @click) também são repassados ao v-badge — use o componente como se estivesse escutando eventos diretamente no v-badge. - -Exemplo: - - mdi-bell - - ---- - -Slot - -O EliBadge expõe um slot padrão para o conteúdo que será "badged" — normalmente um ícone, avatar ou texto. - -Exemplos: - - - mdi-bell - - - - - - -Se visible for false, o slot continua sendo renderizado (o badge some, mas o conteúdo permanece). - ---- - -Exemplos de uso - -Preset suave (padrão): - - mdi-email - - -Preset pill (mais arredondado): - - mdi-chat - - -Valor custom: - - mdi-alert - - - - mdi-star - - - -Esconder só o badge (manter conteúdo): - - Vistoria - - - -Mostrar dot (ponto): - - Usuário - - ---- - -Acessibilidade (A11y) - -1. Forneça aria-label quando o badge transmitir informação importante sem texto adicional. -2. Evite usar cor sozinha para transmitir significado — combine com texto ou atributos ARIA. -3. Para badges que comunicam contagem (ex.: notificações), adicione aria-live ou texto alternativo -no componente pai conforme a necessidade do caso de uso. - ---- - -Boas práticas - -1. Prefira presets (suave, pill) para consistência visual; use valores custom apenas quando necessário. -2. Não aplique estilos inline que conflitem com tokens do design system; prefira classes e variáveis do Vuetify. -3. Documente o uso do visible e badge nos locais onde o componente for amplamente adotado. -4. Evite usar visible=false se você espera apenas esconder zero/empty — prefira lógica que passe badge = undefined ou :visible="count > 0". - ---- - -Testes - -Recomenda-se testar: - -1. Renderização com badge presente e badge === undefined. -2. Comportamento de visible (assegurar que o slot continua visível quando visible=false). -3. dot true/false. -4. Aplicação da variável CSS (--eli-badge-radius) e que o border-radius interno do Vuetify muda conforme o radius. -5. $attrs repassados para o v-badge (por exemplo: aria-label, class). - -Exemplo (pseudocódigo): - const wrapper = mount(EliBadge, { - props: { badge: '3' }, - slots: { default: '' } - }); - expect(wrapper.html()).toContain('Inbox'); - expect(wrapper.findComponent({ name: 'v-badge' }).exists()).toBe(true); - ---- - -Observações sobre Vuetify - -1. O EliBadge usa seletores com ::v-deep para alterar o border-radius do elemento interno do v-badge. Isso funciona para Vuetify 2 e 3, mas as classes internas podem variar entre versões. Se você atualizar o Vuetify, verifique os nomes de classe (.v-badge__badge ou .v-badge__content) e ajuste o seletor se necessário. - -2. Prop names do v-badge (ex.: location, offset-x, offset-y, content, dot) podem variar entre versões do Vuetify — reveja a docs da versão em uso se algo não for aplicado como esperado. diff --git a/src/componentes/EliBadge/index.ts b/src/componentes/EliBadge/index.ts deleted file mode 100644 index 31741df..0000000 --- a/src/componentes/EliBadge/index.ts +++ /dev/null @@ -1,4 +0,0 @@ -import EliBadge from "./EliBadge.vue"; - -export { EliBadge }; -export default EliBadge; diff --git a/src/componentes/EliInput/EliInput.vue b/src/componentes/EliInput/EliInput.vue deleted file mode 100644 index 4a9827b..0000000 --- a/src/componentes/EliInput/EliInput.vue +++ /dev/null @@ -1,312 +0,0 @@ - - - - - diff --git a/src/componentes/EliInput/README.md b/src/componentes/EliInput/README.md deleted file mode 100644 index 5c4244b..0000000 --- a/src/componentes/EliInput/README.md +++ /dev/null @@ -1,116 +0,0 @@ -# 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 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 - -``` - ---- - -## 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. \ No newline at end of file diff --git a/src/componentes/EliInput/index.ts b/src/componentes/EliInput/index.ts deleted file mode 100644 index d616913..0000000 --- a/src/componentes/EliInput/index.ts +++ /dev/null @@ -1,4 +0,0 @@ -import EliInput from "./EliInput.vue"; - -export { EliInput }; -export default EliInput; diff --git a/src/componentes/EliInput/utils/cep.ts b/src/componentes/EliInput/utils/cep.ts deleted file mode 100644 index 701fd34..0000000 --- a/src/componentes/EliInput/utils/cep.ts +++ /dev/null @@ -1,9 +0,0 @@ -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"); -} diff --git a/src/componentes/EliInput/utils/cpfCnpj.ts b/src/componentes/EliInput/utils/cpfCnpj.ts deleted file mode 100644 index 8e66ac1..0000000 --- a/src/componentes/EliInput/utils/cpfCnpj.ts +++ /dev/null @@ -1,24 +0,0 @@ -function somenteNumeros(v: string): string { - return v.replace(/\D+/g, ""); -} - -export function formatarCpfCnpj(v: string): string { - const d = somenteNumeros(v); - - // CPF - if (d.length <= 11) { - return d - .replace(/(\d{3})(\d)/, "$1.$2") - .replace(/(\d{3})(\d)/, "$1.$2") - .replace(/(\d{3})(\d{1,2})$/, "$1-$2") - .slice(0, 14); - } - - // CNPJ - return d - .replace(/^(\d{2})(\d)/, "$1.$2") - .replace(/^(\d{2})\.(\d{3})(\d)/, "$1.$2.$3") - .replace(/\.(\d{3})(\d)/, ".$1/$2") - .replace(/(\d{4})(\d)/, "$1-$2") - .slice(0, 18); -} diff --git a/src/componentes/EliInput/utils/numerico.ts b/src/componentes/EliInput/utils/numerico.ts deleted file mode 100644 index 45b30f7..0000000 --- a/src/componentes/EliInput/utils/numerico.ts +++ /dev/null @@ -1,17 +0,0 @@ -export function somenteNumeros(valor: string) { - return valor.replace(/\D+/g, ""); -} - -export function formatarDecimal(valor: string) { - const limpo = valor.replace(/[^\d,]/g, ""); - const partes = limpo.split(","); - return partes.length > 2 ? partes[0] + "," + partes.slice(1).join("") : limpo; -} - -export function formatarMoeda(valor: string) { - const numero = somenteNumeros(valor); - if (!numero) return ""; - - const inteiro = (parseInt(numero, 10) / 100).toFixed(2); - return inteiro.replace(".", ",").replace(/\B(?=(\d{3})+(?!\d))/g, "."); -} diff --git a/src/componentes/EliInput/utils/telefone.ts b/src/componentes/EliInput/utils/telefone.ts deleted file mode 100644 index 66fbd46..0000000 --- a/src/componentes/EliInput/utils/telefone.ts +++ /dev/null @@ -1,31 +0,0 @@ -// utils/telefone.ts - -/** - * Remove tudo que não é número - */ -export function sanitizeTelefone(value: string): string { - return value.replace(/\D+/g, ""); -} - -/** - * Aplica máscara dinâmica de telefone BR - */ -export function formatTelefone(value: string): string { - const digits = sanitizeTelefone(value); - - if (!digits) return ""; - - // (99) 9999-9999 - if (digits.length <= 10) { - return digits - .replace(/^(\d{2})(\d)/, "($1) $2") - .replace(/(\d{4})(\d)/, "$1-$2") - .slice(0, 14); - } - - // (99) 99999-9999 - return digits - .replace(/^(\d{2})(\d)/, "($1) $2") - .replace(/(\d{5})(\d)/, "$1-$2") - .slice(0, 15); -} diff --git a/src/componentes/EliOlaMundo/EliOlaMundo.vue b/src/componentes/EliOlaMundo/EliOlaMundo.vue index f100efb..7949159 100644 --- a/src/componentes/EliOlaMundo/EliOlaMundo.vue +++ b/src/componentes/EliOlaMundo/EliOlaMundo.vue @@ -1,164 +1,23 @@ diff --git a/src/index.ts b/src/index.ts index 2892810..1caa83d 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,19 +1,13 @@ import type { App } from "vue"; import { EliOlaMundo } from "./componentes/EliOlaMundo"; import { EliBotao } from "./componentes/EliBotao"; -import { EliBadge } from "./componentes/EliBadge"; -import { EliInput } from "./componentes/EliInput"; export { EliOlaMundo }; export { EliBotao }; -export { EliBadge }; -export { EliInput }; export default { install(app: App) { app.component("EliOlaMundo", EliOlaMundo); app.component("EliBotao", EliBotao); - app.component("EliBadge", EliBadge); - app.component("EliInput", EliInput); }, }; diff --git a/src/playground/App.vue b/src/playground/App.vue index ab71d04..cb10261 100644 --- a/src/playground/App.vue +++ b/src/playground/App.vue @@ -1,16 +1,23 @@