UNPKG

rharuow-ds

Version:

Modern React Design System with 20 components and auto color system. Define only 2 colors (primary/secondary) and get automatic variations (hover, light, dark) with proper text contrast (WCAG AA). Includes: Table, Card, Button, Chip, Pagination, Input, Te

1,724 lines (1,385 loc) 55 kB
# rharuow-ds [![NPM Version](https://img.shields.io/npm/v/rharuow-ds)](https://www.npmjs.com/package/rharuow-ds) [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) [![Build Status](https://github.com/Rharuow/rharuow-ds-docs/workflows/CI/badge.svg)](https://github.com/Rharuow/rharuow-ds-docs/actions) Um Design System moderno em React com integração completa ao React Hook Form, estilizado com Tailwind CSS e baseado em shadcn/ui. ## 🌟 Características - ⚛️ **React 18+** com TypeScript - 🧩 **24 componentes** prontos para uso (Input, ColorInput, Textarea, Select, AsyncSelect, MultiSelect, MultiAsyncSelect, RadioGroup, Button, Switch, Chip, Pagination, Card, Table, Tooltip, Accordion, AsideSheet, Sidebar, BottomSheet, BottomTabNavigator, Modal, Toaster, ImageInput) - 💡 **Filtro digitável** em componentes Select - Digite para encontrar opções rapidamente - 🔗 **Integração nativa** com React Hook Form - 🎨 **Sistema de cores automático** - Defina apenas 2 cores e todas as variações são calculadas automaticamente - 🎯 **Contraste automático** para textos (WCAG AA compliance) - 🌓 **Dark mode** com ajustes automáticos de cores - 🎯 **Componentes acessíveis** (ARIA) - 📱 **Responsivo** por padrão - 🎭 **Animações suaves** e modernas - 📚 **Documentação interativa** com Storybook ## 📚 Documentação Acesse a documentação interativa dos componentes em: **[https://rharuow.github.io/rharuow-ds-docs/](https://rharuow.github.io/rharuow-ds-docs/)** --- ## 🚀 Instalação Adicione o pacote ao seu projeto: ```bash npm install rharuow-ds ``` ### Dependências necessárias Se você for usar componentes com formulários, instale também: ```bash npm install react-hook-form ``` > **💡 Atenção:** > Não é necessário instalar ou configurar Tailwind CSS no seu projeto consumidor para usar os estilos do rharuow-ds. O CSS já vem pronto no pacote! ### Compatibilidade - ⚛️ React 16.8+ (hooks) - 📋 React Hook Form 7.0+ - 🌐 Navegadores modernos (ES2018+) --- ## 📖 Como usar 1. **Importe o CSS do design system** No seu arquivo de entrada (ex: `src/main.tsx`, `src/index.tsx` ou `_app.tsx` no Next.js): ```js import "rharuow-ds/dist/styles.css"; ``` 2. **Use os componentes normalmente** ```tsx import { Card, Table, Button, Chip, Pagination, Input, Textarea, Select, AsyncSelect, MultiSelect, RadioGroup, Tooltip, Accordion, AsideSheet, Sidebar, BottomSheet, BottomTabNavigator, Switch, Modal, Toaster, ImageInput, ColorInput, } from "rharuow-ds"; function App() { return ( <div> {/* Exemplo básico do Card */} <Card variant="default"> <Card.Header> <h3>Título do Card</h3> <p>Subtítulo ou descrição</p> </Card.Header> <Card.Body> <p>Conteúdo principal do card</p> </Card.Body> <Card.Footer> <Button>Ação Principal</Button> </Card.Footer> </Card> {/* Exemplo da Table */} <Table variant="striped" size="md"> <Table.Header> <Table.Row> <Table.Cell as="th">Nome</Table.Cell> <Table.Cell as="th">Email</Table.Cell> <Table.Cell as="th">Ações</Table.Cell> </Table.Row> </Table.Header> <Table.Body> <Table.Row> <Table.Cell>João Silva</Table.Cell> <Table.Cell>joao@email.com</Table.Cell> <Table.Cell> <Button variant="outline">Editar</Button> </Table.Cell> </Table.Row> </Table.Body> </Table> {/* Outros componentes */} <Input label="E-mail" name="email" type="email" /> <Input label="Senha" name="password" type="password" /> <Textarea label="Comentários" name="comments" rows={4} /> <Select label="País" name="country" options={[ { label: "Brasil", value: "br" }, { label: "Estados Unidos", value: "us" }, ]} /> <RadioGroup label="Tamanho" name="size" options={[ { label: "Pequeno", value: "sm" }, { label: "Médio", value: "md" }, { label: "Grande", value: "lg" }, ]} /> <Tooltip content="Clique para enviar o formulário" position="top"> <Button variant="default">Enviar</Button> </Tooltip> </div> ); } ``` 3. **Para componentes com React Hook Form** ```tsx import { useForm, FormProvider } from "react-hook-form"; import { Card, Table, Input, Textarea, Select, AsyncSelect, MultiAsyncSelect, RadioGroup, Button, Tooltip, Accordion, AsideSheet, Sidebar, BottomSheet, BottomTabNavigator, Switch, Modal, Toaster, ImageInput, ColorInput, } from "rharuow-ds"; function FormExample() { const methods = useForm(); const loadCountries = async (search?: string) => { // Simular chamada à API const countries = [ { label: "Brasil", value: "br" }, { label: "Argentina", value: "ar" }, { label: "Estados Unidos", value: "us" }, { label: "Chile", value: "cl" }, { label: "Peru", value: "pe" }, ]; if (!search) return countries; return countries.filter((c) => c.label.toLowerCase().includes(search.toLowerCase()) ); }; return ( <Card variant="default" size="lg"> <Card.Header> <h2>Formulário de Cadastro</h2> <p>Preencha os dados abaixo</p> </Card.Header> <Card.Body> <FormProvider {...methods}> <form onSubmit={methods.handleSubmit(console.log)}> <Input label="Nome" name="name" /> <Input label="E-mail" name="email" type="email" /> <Input label="Senha" name="password" type="password" /> <Textarea label="Observações" name="notes" rows={3} /> <AsyncSelect label="País" name="country" loadOptions={loadCountries} searchable isClearable /> <MultiAsyncSelect label="Países favoritos" name="favoriteCountries" loadOptions={loadCountries} searchable isClearable maxVisibleItems={2} /> <RadioGroup label="Tamanho" name="size" options={[ { label: "Pequeno", value: "sm" }, { label: "Médio", value: "md" }, { label: "Grande", value: "lg" }, ]} /> </form> </FormProvider> </Card.Body> <Card.Footer> <div className="flex space-x-2"> <Button variant="outline">Cancelar</Button> <Button type="submit">Enviar</Button> </div> </Card.Footer> </Card> ); } ``` --- ## Componentes Disponíveis ### � **Card** Componente flexível para exibir conteúdo organizado em seções: - ✅ **Estrutura modular**: Header, Body e Footer independentes - ✅ **Múltiplas variantes**: default, outlined, elevated, flat - ✅ **Largura flexível**: Por padrão, cresce para ocupar largura disponível - ✅ **Controle de largura**: Use `constrainWidth=true` para aplicar limitações por tamanho - ✅ **Tamanhos configuráveis**: sm, md, lg (aplicados apenas com `constrainWidth`) - ✅ **Suporte ao tema dark**: Variáveis CSS para light/dark mode - ✅ **Elementos semânticos**: Props `as` para acessibilidade (header, main, footer) - ✅ **Flexibilidade total**: Use apenas as seções necessárias - ✅ **Customização completa**: Padding, bordas arredondadas e estilos ### 📊 **Table** Componente completo para exibição de dados tabulares: - ✅ **Estrutura modular**: Table, Header, Body, Footer, Row, Cell - ✅ **Múltiplas variantes**: default, striped, bordered - ✅ **Tamanhos configuráveis**: sm, md, lg - ✅ **Responsividade**: Scroll horizontal automático - ✅ **Header fixo**: Para tabelas com muitos dados - ✅ **Suporte ao tema dark**: Variáveis CSS para light/dark mode - ✅ **Alinhamento de células**: left, center, right - ✅ **Colspan e rowspan**: Células que ocupam múltiplas colunas/linhas - ✅ **Elementos semânticos**: Props `as` para acessibilidade (th/td, thead/tbody/tfoot) - ✅ **Linhas interativas**: Hover e estados de seleção ### � ### 🎯 **Button** Botão customizável com diferentes variantes, incluindo uma variante exclusiva para ícones. - ✅ Variante `default` — fundo primário com texto contrastante - ✅ Variante `outline` — borda primária com fundo transparente - ✅ Variante `secondary` — fundo secundário com texto contrastante - ✅ Variante `icon` — botão quadrado que centraliza um ícone filho ```tsx import { Button } from "rharuow-ds"; // Botão padrão <Button variant="default">Enviar</Button> // Botão outline <Button variant="outline">Cancelar</Button> // Botão secundário <Button variant="secondary">Mais opções</Button> // Botão apenas com ícone (ícone é passado como filho) <Button variant="icon" aria-label="Adicionar"> <svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" > <line x1="12" y1="5" x2="12" y2="19" /> <line x1="5" y1="12" x2="19" y2="12" /> </svg> </Button> ``` > **Dica:** Na variante `icon`, o ícone é passado como `children` do botão e fica automaticamente centralizado. Use sempre `aria-label` para descrever a ação do botão para acessibilidade. ### 🏷️ **Chip** Elemento pill interativo para filtros, tags e seleções toggleáveis: - ✅ Estado ativo/inativo com visual distinto - ✅ Ícone opcional à esquerda - ✅ `onChange(active: boolean)` — callback de toggle - ✅ Estado `disabled` com visual reduzido - ✅ Acessível (`role="switch"`, `aria-checked`) - ✅ Totalmente estilizável via `className` ```tsx import { Chip } from "rharuow-ds"; // Toggle simples const [active, setActive] = useState(false); <Chip label="Filtro" active={active} onChange={setActive} /> // Grupo de filtros const filters = ["React", "TypeScript", "Tailwind"]; const [selected, setSelected] = useState<string[]>([]); const toggle = (filter: string) => setSelected(prev => prev.includes(filter) ? prev.filter(f => f !== filter) : [...prev, filter] ); {filters.map(filter => ( <Chip key={filter} label={filter} active={selected.includes(filter)} onChange={() => toggle(filter)} /> ))} // Com ícone <Chip label="Favorito" active={isFavorite} onChange={setIsFavorite} icon={<StarIcon />} /> ``` ### � **Pagination** Componente de paginação com janela deslizante e ellipsis: - ✅ Exibe janela de **3 páginas** centrada na página atual - ✅ Ellipsis (`…`) + última página quando o total ultrapassa a janela visível - ✅ Seta esquerda (← anterior) oculta na primeira página - ✅ Seta direita (→ próxima) oculta na última página - ✅ Estado `disabled` para toda a navegação - ✅ Acessível (`role="navigation"`, `aria-label`, `aria-current`) ```tsx import { Pagination } from "rharuow-ds"; const [page, setPage] = useState(1); <Pagination totalPages={20} currentPage={page} onPageChange={setPage} /> ``` ### �📝 **Input** Campo de texto versátil com label flutuante e integração com React Hook Form: - ✅ Label flutuante animada - ✅ Suporte a múltiplos tipos (text, email, password, number, tel, url) - ✅ Variante de CEP com máscara automática (`00000-000`) e validação de formato - ✅ Funcionalidade de password com botão mostrar/ocultar - ✅ Variante de moeda com máscara e formatação automática (BRL por padrão) - ✅ Saída configurável no React Hook Form como string formatada ou number - ✅ `onChange` exposto para integrações como busca de endereço por CEP - ✅ Ícones customizados opcionais - ✅ Estados de erro integrados - ✅ Totalmente acessível (ARIA) Exemplo com CEP e `onChange` para busca de endereço: ```tsx import React from "react"; import { Input } from "rharuow-ds"; function AddressForm() { const [cep, setCep] = React.useState(""); const handleCepChange: React.ChangeEventHandler<HTMLInputElement> = async (event) => { const maskedCep = event.target.value; setCep(maskedCep); const normalizedCep = maskedCep.replace(/\D/g, ""); if (normalizedCep.length === 8) { // Exemplo: chamar servico externo quando CEP estiver completo // const endereco = await buscarEnderecoPorCep(normalizedCep); // preencher campos do formulario... } }; return ( <Input name="cep" label="CEP" cep value={cep} onChange={handleCepChange} /> ); } ``` Veja a story de CEP no Storybook para exemplos completos: [Storybook - Input CEP](https://rharuow.github.io/rharuow-ds-docs/?path=/story/components-input-cep--default) ### � **Textarea** Campo de texto multilinha com as mesmas funcionalidades do Input: - ✅ Label flutuante animada - ✅ Altura ajustável (propriedade `rows`) - ✅ Redimensionamento vertical permitido - ✅ Ícones customizados opcionais - ✅ Estados de erro integrados - ✅ Integração completa com React Hook Form - ✅ Mesma consistência visual do Input ### �📋 **Select** Seletor customizado com opções estáticas e suporte a busca. ### 🔄 **AsyncSelect** Seletor com carregamento assíncrono de opções: - ✅ Carregamento de dados via API - ✅ Busca em tempo real (searchable) - ✅ Debounce configurável - ✅ Estados de loading e "sem opções" - ✅ Integração completa com React Hook Form ### 🎛️ **MultiSelect** Seletor múltiplo para escolha de várias opções: - ✅ Seleção múltipla com checkboxes - ✅ **Filtro digitável** (`searchable`) - Digite para encontrar opções - ✅ Tags visuais para itens selecionados - ✅ Remoção individual de itens - ✅ Filtro case-sensitive configurável - ✅ Função de filtro customizável - ✅ Botão de limpeza geral (`isClearable`) - ✅ Integração completa com React Hook Form ```tsx // MultiSelect básico <MultiSelect name="fruits" label="Escolha suas frutas favoritas" options={fruitOptions} /> // MultiSelect com filtro <MultiSelect name="fruits" label="Escolha suas frutas favoritas" searchable filterPlaceholder="Digite para filtrar frutas..." caseSensitive={false} isClearable options={fruitOptions} /> ``` ### 🔄🎛️ **MultiAsyncSelect** Seletor múltiplo com carregamento assíncrono: - ✅ Todas as funcionalidades do AsyncSelect - ✅ Seleção múltipla com tags visuais - ✅ Remoção individual de itens selecionados - ✅ Limite configurável de itens exibidos - ✅ Contador de itens extras (+X mais) ### 🎯 **RadioGroup** Radio buttons modernos e criativos: - ✅ Design de botões estilizados (não radio tradicional) - ✅ Ícones customizados opcionais - ✅ Layout horizontal ou vertical - ✅ Diferentes tamanhos (sm, md, lg) - ✅ Animações ao selecionar ### 💡 **Tooltip** Componente de tooltip inteligente e acessível: - ✅ **Posicionamento automático**: top, bottom, left, right - ✅ **Detecção de bordas**: Ajusta posição automaticamente se não couber na tela - ✅ **Acessibilidade completa**: Suporte a navegação por teclado e screen readers - ✅ **Animações suaves**: Transições de entrada e saída elegantes - ✅ **Seta indicativa**: Aponta para o elemento que ativou o tooltip - ✅ **Suporte a temas**: Variáveis CSS para light/dark mode - ✅ **Flexível**: Funciona com qualquer elemento como trigger - ✅ **Controle de estado**: Pode ser desabilitado conforme necessário - ✅ **Largura configurável** (`maxWidth`): Limita a largura e permite quebra de linha no conteúdo ```tsx // Tooltip básico <Tooltip content="Informação útil" position="top"> <Button>Passe o mouse aqui</Button> </Tooltip> // Tooltip com texto <Tooltip content="Clique para mais detalhes" position="right"> <span className="underline cursor-help"> Texto com tooltip </span> </Tooltip> // Tooltip personalizado <Tooltip content="Tooltip customizado" position="bottom" className="bg-red-500 text-white" > <Button variant="outline">Hover aqui</Button> </Tooltip> // Tooltip desabilitado <Tooltip content="Este não aparece" disabled> <Button>Tooltip desabilitado</Button> </Tooltip> // Tooltip com largura máxima (texto longo quebra em múltiplas linhas) // Aceita número (convertido para px) ou qualquer string CSS válida <Tooltip content="Este é um texto longo que vai quebrar em múltiplas linhas quando a largura for limitada." position="top" maxWidth={200} > <Button>maxWidth numérico (200px)</Button> </Tooltip> <Tooltip content="Também é possível usar valores CSS como rem, em ou percentagem." position="bottom" maxWidth="12rem" > <Button>maxWidth string (12rem)</Button> </Tooltip> ``` #### Props do Tooltip | Prop | Tipo | Padrão | Descrição | |------|------|--------|-----------| | `content` | `string` | — | Texto exibido no tooltip | | `position` | `"top" \| "bottom" \| "left" \| "right"` | `"top"` | Posição preferencial do tooltip | | `disabled` | `boolean` | `false` | Desabilita o tooltip | | `maxWidth` | `string \| number` | `undefined` | Largura máxima. Número é convertido para `px`. Quando definido, o texto quebra em múltiplas linhas | | `className` | `string` | `""` | Classes CSS adicionais para o balão do tooltip | ### 🪗 **Accordion** Componente de accordion (acordeão) flexível e acessível para expandir e colapsar seções de conteúdo: - ✅ **Modo single**: Apenas um item aberto por vez - ✅ **Modo multiple**: Vários itens podem estar abertos simultaneamente - ✅ **Animações suaves**: Transições de altura com ease-in-out - ✅ **Variantes visuais**: default, bordered, separated - ✅ **Acessibilidade completa**: ARIA labels e navegação por teclado - ✅ **Itens desabilitados**: Suporte para itens que não podem ser expandidos - ✅ **Ícones customizados**: Adicione ícones aos títulos - ✅ **Collapsible configurável**: Controle se todos os itens podem ser fechados - ✅ **DefaultOpen**: Items podem iniciar abertos - ✅ **Customização total**: Classes CSS para header e content ```tsx // Accordion básico <Accordion> <Accordion.Item title="O que é React?"> <p>React é uma biblioteca JavaScript para construir interfaces de usuário.</p> </Accordion.Item> <Accordion.Item title="O que é TypeScript?"> <p>TypeScript é um superset de JavaScript que adiciona tipagem estática.</p> </Accordion.Item> </Accordion> // Accordion com múltiplos itens abertos <Accordion type="multiple"> <Accordion.Item title="Seção 1" defaultOpen> <p>Esta seção inicia aberta.</p> </Accordion.Item> <Accordion.Item title="Seção 2" defaultOpen> <p>Esta seção também inicia aberta.</p> </Accordion.Item> </Accordion> // Accordion com variant bordered <Accordion variant="bordered"> <Accordion.Item title="Recursos do Produto"> <ul> <li>Interface intuitiva</li> <li>Integração com múltiplas plataformas</li> <li>Suporte 24/7</li> </ul> </Accordion.Item> </Accordion> // Accordion com ícones customizados <Accordion variant="separated" type="multiple"> <Accordion.Item title="Documentação" icon={<DocumentIcon />} > <p>Acesse a documentação completa.</p> </Accordion.Item> <Accordion.Item title="Suporte" icon={<SupportIcon />} > <p>Entre em contato com nossa equipe.</p> </Accordion.Item> </Accordion> // Accordion não collapsible (sempre mantém um aberto) <Accordion collapsible={false}> <Accordion.Item title="Passo 1" defaultOpen> <p>Configure seu ambiente.</p> </Accordion.Item> <Accordion.Item title="Passo 2"> <p>Desenvolva sua aplicação.</p> </Accordion.Item> </Accordion> ``` ### 🪟 **AsideSheet** Componente tipo painel deslizante (sheet) que abre a partir das bordas da tela. - ✅ Suporta controle programático (controlled) e estado interno (uncontrolled) - ✅ Abre da direita para a esquerda ou da esquerda para a direita (`side: 'left' | 'right'`) - ✅ Largura configurável via `size` ou `className` - ✅ Acessível: foco gerenciado e comportamento esperado ao fechar (Esc) Props principais: - `isOpen?: boolean` — controla visibilidade (quando usado como controlled) - `defaultOpen?: boolean` — estado inicial (uncontrolled) - `onClose?: () => void` — callback chamado ao fechar - `side?: 'left' | 'right'` — lado de abertura (padrão: 'right') - `size?: 'sm' | 'md' | 'lg' | 'full'` — tamanho pré-definido do sheet - `className?: string` — classes adicionais para o container - `title?: string | React.ReactNode` — título opcional do painel Exemplo de uso (controlado): ```tsx import React from 'react'; import { AsideSheet, Button } from 'rharuow-ds'; function Example() { const [open, setOpen] = React.useState(false); return ( <div> <Button onClick={() => setOpen(true)}>Abrir Aside</Button> <AsideSheet isOpen={open} onClose={() => setOpen(false)} side="right" size="md" > <AsideSheet.Header> <h3>Detalhes</h3> </AsideSheet.Header> <AsideSheet.Body> <p>Conteúdo do painel.</p> </AsideSheet.Body> <AsideSheet.Footer> <Button variant="outline" onClick={() => setOpen(false)}> Fechar </Button> </AsideSheet.Footer> </AsideSheet> </div> ); } ``` Veja a story do componente no Storybook para demonstrações e variações (left/right, controlled/uncontrolled): [Storybook — AsideSheet](https://rharuow.github.io/rharuow-ds-docs/?path=/story/asidesheet--default) ### 🗂️ **Sidebar** Painel lateral persistente para telas `md+` (≥ 768px). Diferente do `AsideSheet`, a Sidebar não usa overlay nem portal — é parte do layout fixo da página. - ✅ **Exclusivo para telas médias e grandes** — oculto automaticamente em mobile (`hidden md:flex`) - ✅ Lado configurável: `left` ou `right` - ✅ Larguras predefinidas: `sm` (256px), `md` (288px), `lg` (384px) - ✅ Largura customizada com qualquer valor CSS (ex: `"320px"`, `"20rem"`) - ✅ Animação suave de slide in/out - ✅ Sem overlay — não bloqueia o conteúdo principal - ✅ Variável CSS `--sidebar-bg` (fallback para `--aside-bg` e depois `#ffffff`) - ✅ Acessível: `role="navigation"`, `aria-expanded` Props principais: - `open: boolean` — controla visibilidade (slide in/out) - `side?: 'left' | 'right'` — lado de abertura (padrão: `'left'`) - `size?: 'sm' | 'md' | 'lg' | string` — tamanho pré-definido ou valor CSS customizado (padrão: `'md'`) - `className?: string` — classes adicionais para o container Exemplo de uso: ```tsx import React from 'react'; import { Sidebar, Button } from 'rharuow-ds'; function Layout() { const [sidebarOpen, setSidebarOpen] = React.useState(true); return ( <div className="flex min-h-screen"> <Sidebar open={sidebarOpen} side="left" size="md"> <div className="p-6 flex flex-col gap-4"> <h3 className="text-lg font-semibold">Menu</h3> <nav className="flex flex-col gap-2 text-sm"> <a href="/dashboard">Dashboard</a> <a href="/users">Usuários</a> <a href="/settings">Configurações</a> </nav> </div> </Sidebar> {/* Conteúdo principal com margem para não ficar atrás da sidebar */} <main className="flex-1 md:ml-72 p-6"> <Button onClick={() => setSidebarOpen((v) => !v)}> {sidebarOpen ? 'Fechar Sidebar' : 'Abrir Sidebar'} </Button> <p>Conteúdo da página.</p> </main> </div> ); } ``` Exemplo com largura customizada: ```tsx <Sidebar open={open} side="left" size="320px"> {/* conteúdo */} </Sidebar> ``` Variável CSS para customizar a cor de fundo: ```css :root { --sidebar-bg: #1e293b; /* escuro */ } ``` Veja as stories do componente no Storybook: [Storybook — Sidebar](https://rharuow.github.io/rharuow-ds-docs/?path=/story/components-sidebar--left) ### 🔀 **Switch** Componente de alternancia (on/off) com foco em acessibilidade e controle simples de estado. - ✅ Funciona em modo controlado com `checked` + `onChange` - ✅ Label opcional com posicao configuravel (`left` ou `right`) - ✅ Tamanhos predefinidos: `sm`, `md`, `lg` - ✅ Estado `disabled` com estilo e comportamento apropriados - ✅ Acessivel com `role="switch"` e `aria-checked` Props principais: - `checked?: boolean` - Estado atual do switch - `onChange?: (checked: boolean) => void` - Callback ao alternar - `label?: string` - Texto exibido ao lado do controle - `labelPosition?: 'left' | 'right'` - Posicao do label (padrao: `'right'`) - `size?: 'sm' | 'md' | 'lg'` - Tamanho visual (padrao: `'md'`) - `disabled?: boolean` - Desabilita interacao Exemplo de uso: ```tsx import React from 'react'; import { Switch } from 'rharuow-ds'; function Settings() { const [notifications, setNotifications] = React.useState(false); return ( <Switch checked={notifications} onChange={setNotifications} label={notifications ? 'Notificacoes ativas' : 'Notificacoes inativas'} labelPosition="right" size="md" /> ); } ``` Storybook: [Storybook — Switch](https://rharuow.github.io/rharuow-ds-docs/?path=/story/components-switch--default) ### 📱 **BottomTabNavigator** Navegacao inferior fixa para mobile, ideal para areas principais do app. - ✅ Exibido somente em telas pequenas (`md:hidden`) - ✅ Suporte a modo controlado (`value`) e nao controlado (`defaultValue`) - ✅ Cada item pode ter icone, badge e estado desabilitado - ✅ Layout responsivo com colunas dinamicas conforme quantidade de tabs - ✅ Acessivel com `role="tablist"` e `role="tab"` Props principais: - `items: BottomTabItem[]` - Lista de abas - `value?: string` - Valor selecionado (modo controlado) - `defaultValue?: string` - Valor inicial (modo nao controlado) - `onValueChange?: (value: string) => void` - Callback de troca de aba Exemplo de uso: ```tsx import React from 'react'; import { BottomTabNavigator } from 'rharuow-ds'; function MobileLayout() { const [active, setActive] = React.useState('home'); return ( <BottomTabNavigator value={active} onValueChange={setActive} items={[ { id: 'home', label: 'Inicio', icon: '🏠' }, { id: 'search', label: 'Buscar', icon: '🔎' }, { id: 'wallet', label: 'Carteira', icon: '💳', badge: '●' }, { id: 'profile', label: 'Perfil', icon: '👤' }, ]} /> ); } ``` Storybook: [Storybook — BottomTabNavigator](https://rharuow.github.io/rharuow-ds-docs/?path=/story/components-bottomtabnavigator--default) ### 🧲 **BottomSheet** Painel deslizante que abre de baixo para cima em mobile, util para filtros, acoes e formularios curtos. - ✅ Exibido apenas em mobile (`md:hidden`) - ✅ Portal no `document.body` com overlay - ✅ Fechamento por overlay e tecla ESC (configuravel) - ✅ Bloqueio de scroll do body enquanto aberto (configuravel) - ✅ Suporte a arrastar para baixo para fechar (`closeOnDragDown`) - ✅ Tamanhos predefinidos (`sm`, `md`, `lg`, `full`) ou valor customizado Props principais: - `open: boolean` - Controla visibilidade do painel - `onClose: () => void` - Callback ao fechar - `size?: 'sm' | 'md' | 'lg' | 'full' | string` - Altura maxima do painel - `closeOnOverlayClick?: boolean` - Fecha ao clicar fora (padrao: `true`) - `closeOnEscape?: boolean` - Fecha ao pressionar ESC (padrao: `true`) - `lockBodyScroll?: boolean` - Bloqueia scroll da pagina (padrao: `true`) - `closeOnDragDown?: boolean` - Habilita gesto de arrastar para fechar (padrao: `true`) - `dragCloseThreshold?: number` - Distancia minima de arraste para fechar (padrao: `100`) Exemplo de uso: ```tsx import React from 'react'; import { BottomSheet, Button } from 'rharuow-ds'; function Filters() { const [open, setOpen] = React.useState(false); return ( <> <Button onClick={() => setOpen(true)}>Abrir filtros</Button> <BottomSheet open={open} onClose={() => setOpen(false)} size="md"> <div className="px-4 pb-6 pt-4"> <h3 className="text-lg font-semibold">Filtros rapidos</h3> <p className="mt-2 text-sm">Escolha os filtros e aplique.</p> </div> </BottomSheet> </> ); } ``` Storybook: [Storybook — BottomSheet](https://rharuow.github.io/rharuow-ds-docs/?path=/story/components-bottomsheet--default) ### 🎭 **Modal** Componente de diálogo modal para exibir conteúdo sobreposto à página principal. - ✅ Overlay com transparência configurável - ✅ Múltiplos tamanhos: sm, md, lg, xl, full - ✅ Variantes de cor: default, primary, secondary (usando CSS Variables) - ✅ Controle de fechamento via overlay, ESC ou botão X - ✅ Prevenção de scroll do body quando aberto - ✅ Animações suaves de entrada/saída - ✅ Sub-componentes para estruturação: Header, Body, Footer - ✅ Renderização via Portal (React Portal) - ✅ Acessível: role="dialog", aria-modal Props principais: - `open: boolean` — controla visibilidade do modal - `onClose: () => void` — callback chamado ao fechar - `size?: 'sm' | 'md' | 'lg' | 'xl' | 'full'` — tamanho do modal (padrão: 'md') - `variant?: 'default' | 'primary' | 'secondary'` — variante de cor (padrão: 'default') - `closeOnOverlayClick?: boolean` — fecha ao clicar fora (padrão: true) - `closeOnEscape?: boolean` — fecha ao pressionar ESC (padrão: true) - `showCloseButton?: boolean` — exibe botão X de fechar (padrão: true) - `className?: string` — classes adicionais para o container do modal Exemplo de uso básico: ```tsx import React from 'react'; import { Modal, Button } from 'rharuow-ds'; function Example() { const [open, setOpen] = React.useState(false); return ( <div> <Button onClick={() => setOpen(true)}>Abrir Modal</Button> <Modal open={open} onClose={() => setOpen(false)}> <h2>Título do Modal</h2> <p>Conteúdo do modal aqui.</p> </Modal> </div> ); } ``` Exemplo com estrutura completa: ```tsx import React from 'react'; import { Modal, Button } from 'rharuow-ds'; function Example() { const [open, setOpen] = React.useState(false); return ( <div> <Button onClick={() => setOpen(true)}>Confirmar Ação</Button> <Modal open={open} onClose={() => setOpen(false)} size="md" > <Modal.Header> <h2 className="text-2xl font-bold">Confirmar Exclusão</h2> <p className="text-sm text-gray-500">Esta ação não pode ser desfeita</p> </Modal.Header> <Modal.Body> <p className="text-gray-700"> Você tem certeza que deseja excluir este item? Todos os dados associados serão removidos permanentemente. </p> </Modal.Body> <Modal.Footer> <Button variant="outline" onClick={() => setOpen(false)}> Cancelar </Button> <Button onClick={() => { // Executar ação setOpen(false); }}> Confirmar </Button> </Modal.Footer> </Modal> </div> ); } ``` Exemplo com formulário integrado: ```tsx import React from 'react'; import { Modal, Button, Input } from 'rharuow-ds'; import { FormProvider, useForm } from 'react-hook-form'; function FormModal() { const [open, setOpen] = React.useState(false); const methods = useForm(); const onSubmit = (data: any) => { console.log('Form data:', data); setOpen(false); methods.reset(); }; return ( <div> <Button onClick={() => setOpen(true)}>Novo Cadastro</Button> <Modal open={open} onClose={() => setOpen(false)} size="lg" closeOnOverlayClick={false} > <FormProvider {...methods}> <form onSubmit={methods.handleSubmit(onSubmit)}> <Modal.Header> <h2 className="text-2xl font-bold">Cadastrar Usuário</h2> </Modal.Header> <Modal.Body> <div className="space-y-4"> <Input label="Nome completo" name="name" required /> <Input label="E-mail" name="email" type="email" required /> <Input label="Telefone" name="phone" /> </div> </Modal.Body> <Modal.Footer> <Button type="button" variant="outline" onClick={() => setOpen(false)} > Cancelar </Button> <Button type="submit">Salvar</Button> </Modal.Footer> </form> </FormProvider> </Modal> </div> ); } ``` Exemplo com variantes de cor: ```tsx import React from 'react'; import { Modal, Button } from 'rharuow-ds'; function ColorVariants() { const [openPrimary, setOpenPrimary] = React.useState(false); const [openSecondary, setOpenSecondary] = React.useState(false); return ( <div> {/* Modal com cor primária */} <Button onClick={() => setOpenPrimary(true)}> Modal Primary </Button> <Modal open={openPrimary} onClose={() => setOpenPrimary(false)} variant="primary" > <Modal.Header> <h2 className="text-2xl font-bold">Ação Importante</h2> </Modal.Header> <Modal.Body> <p className="opacity-95"> Este modal usa as cores primárias da aplicação, ideal para destacar ações principais. </p> </Modal.Body> <Modal.Footer> <Button variant="outline" onClick={() => setOpenPrimary(false)}> Fechar </Button> </Modal.Footer> </Modal> {/* Modal com cor secundária */} <Button onClick={() => setOpenSecondary(true)} variant="secondary"> Modal Secondary </Button> <Modal open={openSecondary} onClose={() => setOpenSecondary(false)} variant="secondary" > <Modal.Header> <h2 className="text-2xl font-bold">Aviso</h2> </Modal.Header> <Modal.Body> <p className="opacity-95"> Este modal usa as cores secundárias da aplicação, ideal para avisos e ações alternativas. </p> </Modal.Body> <Modal.Footer> <Button variant="secondary" onClick={() => setOpenSecondary(false)}> Fechar </Button> </Modal.Footer> </Modal> </div> ); } ``` #### Customizando o background do Modal (`--bg-modal`) O background do Modal na variante `default` pode ser customizado via a variável CSS `--bg-modal`. | Tema | Valor padrão | |------|-------------| | Light | `#ffffff` (branco) | | Dark | `#000000` (preto) | Para customizar no seu app consumidor, defina a variável no seu CSS global ou diretamente no elemento: ```css /* CSS global da aplicação */ :root { --bg-modal: #1e3a5f; /* Azul escuro customizado */ } /* Ou por tema */ :root { --bg-modal: #f0f4f8; /* Light */ } [data-theme="dark"], .dark { --bg-modal: #1a1a2e; /* Dark */ } ``` ```tsx // Ou inline via style (para casos específicos) <div style={{ "--bg-modal": "#1e3a5f" } as React.CSSProperties}> <Modal open={open} onClose={onClose}>...</Modal> </div> ``` > **Nota:** A variável `--bg-modal` afeta apenas a variante `default`. As variantes `primary` e `secondary` usam `--primary` e `--secondary` respectivamente. Veja a story do componente no Storybook para mais exemplos e variações: [Storybook — Modal](https://rharuow.github.io/rharuow-ds-docs/?path=/story/components-modal--basic) ### � **Toaster** Sistema completo de notificações toast para feedback ao usuário com múltiplas variantes e posicionamento flexível. - ✅ 5 variantes de toast: success, error, warning, info, default - ✅ 6 posições configuráveis na tela - ✅ Auto-dismiss com duração customizável - ✅ Toast permanente (duration: 0) - ✅ Ícones automáticos por variante - ✅ Animações suaves de entrada e saída - ✅ Limite de toasts simultâneos (padrão: 5) - ✅ Callbacks ao fechar - ✅ Hook `useToast` para uso simplificado - ✅ Gerenciamento via Context API **Configuração inicial:** O Toaster precisa ser configurado uma única vez no nível superior da aplicação: ```tsx import React from 'react'; import { ToasterProvider } from 'rharuow-ds'; function App() { return ( <ToasterProvider position="top-right" maxToasts={5}> {/* Sua aplicação aqui */} <YourApp /> </ToasterProvider> ); } ``` Props do `ToasterProvider`: - `position?: ToastPosition` - Posição dos toasts na tela (padrão: 'top-right') - Opções: 'top-left', 'top-center', 'top-right', 'bottom-left', 'bottom-center', 'bottom-right' - `maxToasts?: number` - Número máximo de toasts simultâneos (padrão: 5) **Uso básico com hook `useToast`:** ```tsx import React from 'react'; import { useToast, Button } from 'rharuow-ds'; function MyComponent() { const toast = useToast(); return ( <div> <Button onClick={() => toast.success('Operação realizada com sucesso!')}> Success </Button> <Button onClick={() => toast.error('Erro ao processar requisição')}> Error </Button> <Button onClick={() => toast.warning('Atenção: verifique os dados')}> Warning </Button> <Button onClick={() => toast.info('Você tem 3 novas mensagens')}> Info </Button> </div> ); } ``` **Toasts com duração customizada:** ```tsx import React from 'react'; import { useToast, Button } from 'rharuow-ds'; function CustomDuration() { const toast = useToast(); return ( <div> {/* Toast rápido - 2 segundos */} <Button onClick={() => toast.success('Toast rápido', 2000)}> 2 Segundos </Button> {/* Toast longo - 10 segundos */} <Button onClick={() => toast.info('Toast longo', 10000)}> 10 Segundos </Button> {/* Toast permanente - não fecha automaticamente */} <Button onClick={() => toast.toast('Toast permanente', { duration: 0 })} > Permanente </Button> </div> ); } ``` **Toast com callback ao fechar:** ```tsx import React from 'react'; import { useToaster, Button } from 'rharuow-ds'; function WithCallback() { const { addToast } = useToaster(); const handleAction = () => { addToast({ message: 'Processando dados...', variant: 'info', duration: 3000, onClose: () => { console.log('Toast fechado!'); // Executar ação após fechamento performNextAction(); }, }); }; return <Button onClick={handleAction}>Iniciar Processo</Button>; } ``` **Exemplo completo em um formulário:** ```tsx import React from 'react'; import { useForm, FormProvider } from 'react-hook-form'; import { Input, Button, useToast } from 'rharuow-ds'; function FormWithToast() { const methods = useForm(); const toast = useToast(); const onSubmit = async (data: any) => { try { // Simular chamada à API await saveData(data); toast.success('Dados salvos com sucesso!'); methods.reset(); } catch (error) { toast.error('Erro ao salvar dados. Tente novamente.'); console.error(error); } }; return ( <FormProvider {...methods}> <form onSubmit={methods.handleSubmit(onSubmit)}> <Input label="Nome" name="name" required /> <Input label="E-mail" name="email" type="email" required /> <Button type="submit">Salvar</Button> </form> </FormProvider> ); } ``` **API do hook `useToast`:** ```typescript const toast = useToast(); // Métodos disponíveis: toast.success(message: string, duration?: number) toast.error(message: string, duration?: number) toast.warning(message: string, duration?: number) toast.info(message: string, duration?: number) toast.toast(message: string, options?: ToastOptions) ``` **API avançada com `useToaster`:** ```typescript const { addToast, removeToast, clearAll, toasts } = useToaster(); // Adicionar toast com controle total const id = addToast({ message: 'Mensagem personalizada', variant: 'success', duration: 5000, onClose: () => console.log('Fechado'), }); // Remover toast específico removeToast(id); // Limpar todos os toasts clearAll(); ``` **Dicas de uso:** - Use `success` para operações bem-sucedidas (save, delete, update) - Use `error` para falhas e erros - Use `warning` para avisos que requerem atenção - Use `info` para informações gerais - Configure `duration: 0` para toasts que precisam de ação manual do usuário - Posicione toasts conforme o contexto: top para notificações gerais, bottom para ações específicas Veja a story do componente no Storybook para demonstrações interativas: [Storybook — Toaster](https://rharuow.github.io/rharuow-ds-docs/?path=/story/components-toaster--top-right) ### �📷 **ImageInput** Componente para seleção e upload de imagens com preview e ações de confirmação/remoção: - ✅ **Seleção via explorador** - Clique para abrir o explorador de arquivos (apenas imagens) - ✅ **Preview da imagem** - Visualização imediata após seleção - ✅ **Modo avatar** (`avatar={true}`) - Formato circular para fotos de perfil - ✅ **Ações de confirmação** - Botões para confirmar upload ou cancelar - ✅ **Remoção de imagem** - Botão para excluir imagem já salva - ✅ **Suporte a URLs externas** - Exibe imagens já salvas via `value` prop - ✅ **Validação de arquivos** - Controle de tipo e tamanho máximo - ✅ **Estados de loading** - Indicação visual durante upload/remoção - ✅ **Flexível** - Funciona com qualquer serviço (Cloudinary, Firebase, S3, etc.) - ✅ **Integração com React Hook Form** - Nome do campo e validação Props principais: - `avatar?: boolean` — formato circular (ideal para avatars) - `value?: string` — URL da imagem atual (já salva) - `onUpload?: (file: File) => Promise<string>` — callback para upload (retorna URL) - `onRemove?: (imageUrl?: string) => Promise<void>` — callback para remoção - `accept?: string` — tipos aceitos (padrão: "image/*") - `maxSize?: number` — tamanho máximo em bytes - `size?: 'sm' | 'md' | 'lg'` — tamanho do componente - `loading?: boolean` — estado de carregamento - `disabled?: boolean` — desabilitar interações Exemplo básico: ```tsx import React from 'react'; import { ImageInput } from 'rharuow-ds'; function ProfileForm() { const [avatarUrl, setAvatarUrl] = React.useState(''); const handleUpload = async (file: File): Promise<string> => { // Upload para seu serviço preferido (Cloudinary, Firebase, etc.) const formData = new FormData(); formData.append('file', file); const response = await fetch('/api/upload', { method: 'POST', body: formData }); const data = await response.json(); setAvatarUrl(data.url); return data.url; }; const handleRemove = async (url?: string) => { // Remover do serviço se necessário await fetch(`/api/delete?url=${encodeURIComponent(url || '')}`, { method: 'DELETE' }); setAvatarUrl(''); }; return ( <ImageInput avatar label="Foto do Perfil" value={avatarUrl} onUpload={handleUpload} onRemove={handleRemove} size="lg" maxSize={2 * 1024 * 1024} // 2MB /> ); } ``` Exemplo com Cloudinary: ```tsx const uploadToCloudinary = async (file: File): Promise<string> => { const formData = new FormData(); formData.append('file', file); formData.append('upload_preset', 'seu_preset'); const response = await fetch( `https://api.cloudinary.com/v1_1/seu_cloud_name/image/upload`, { method: 'POST', body: formData } ); const data = await response.json(); return data.secure_url; }; <ImageInput onUpload={uploadToCloudinary} placeholder="Upload para Cloudinary" /> ``` Veja a story do componente no Storybook para demonstrações completas: [Storybook — ImageInput](https://rharuow.github.io/rharuow-ds-docs/?path=/story/imageinput--default) --- ### 🎨 **ColorInput** Componente de seleção de cor (color picker) com label flutuante e integração nativa com React Hook Form: - ✅ **Seletor nativo** - Usa `<input type="color">` do navegador para máxima compatibilidade - ✅ **Valor hexadecimal visível** - Exibe o código hex selecionado ao lado do swatch - ✅ **Label flutuante** - Mesmo comportamento do componente `Input` - ✅ **Integração com React Hook Form** - Registra automaticamente via `useFormContext` - ✅ **Controlado ou não-controlado** - Funciona com ou sem `FormProvider` - ✅ **Acessível** - Label associado ao input via `htmlFor` Props principais: - `name: string` — nome do campo (obrigatório, usado no React Hook Form) - `label?: string` — label flutuante - `containerClassName?: string` — classe extra no wrapper - `disabled?: boolean` — desabilita o campo Exemplo básico: ```tsx import { ColorInput } from 'rharuow-ds'; import { FormProvider, useForm } from 'react-hook-form'; function ThemeForm() { const methods = useForm({ defaultValues: { primaryColor: '#8b5cf6' } }); const onSubmit = (data: { primaryColor: string }) => { console.log('Cor escolhida:', data.primaryColor); }; return ( <FormProvider {...methods}> <form onSubmit={methods.handleSubmit(onSubmit)}> <ColorInput name="primaryColor" label="Cor primária" /> <button type="submit">Salvar</button> </form> </FormProvider> ); } ``` Exemplo controlado (sem React Hook Form): ```tsx import React from 'react'; import { ColorInput } from 'rharuow-ds'; function ColorPicker() { const [color, setColor] = React.useState('#ec4899'); return ( <ColorInput name="cor" label="Escolha uma cor" value={color} onChange={(e) => setColor(e.target.value)} /> ); } ``` Veja a story do componente no Storybook para demonstrações completas: [Storybook — ColorInput](https://rharuow.github.io/rharuow-ds-docs/?path=/story/components-colorinput--default) --- ``` --- ## 🎨 Customização de Tema O rharuow-ds utiliza um **sistema de cores inteligente** que permite personalizar todo o design system definindo apenas **duas cores**: primária e secundária. Todas as variações (hover, light, dark) e cores de texto com contraste adequado são **calculadas automaticamente**. > ⚡ **NOVO**: Sistema de Cálculo Automático de Cores! Veja a [documentação completa](AUTO_COLOR_SYSTEM.md) para detalhes. ### ✨ Modo Simplificado (Recomendado) **Defina apenas 2 variáveis** e o sistema calcula automaticamente todas as variações: ```css /* Importar o DS primeiro */ @import 'rharuow-ds/dist/styles.css'; /* Defina APENAS as cores base - o resto é automático! */ :root { --primary: #8b5cf6; /* Roxo */ --secondary: #ec4899; /* Rosa */ } /* Para dark mode */ [data-theme="dark"] { --primary: #a78bfa; /* Versão mais clara para melhor contraste */ --secondary: #f472b6; } ``` O sistema automaticamente gera: - ✅ `--primary-hover`, `--primary-light`, `--primary-dark`, `--primary-text` - ✅ `--secondary-hover`, `--secondary-light`, `--secondary-dark`, `--secondary-text` - ✅ Contraste adequado para textos (WCAG AA compliance) - ✅ Ajustes automáticos para dark mode ### 💻 Uso com JavaScript/TypeScript Para aplicações que precisam mudar cores dinamicamente: ```typescript import { applyThemeColors } from 'rharuow-ds/lib/color.utils'; import 'rharuow-ds/dist/styles.css'; function App() { useEffect(() => { // Aplica cores e calcula automaticamente todas as variações applyThemeColors('#8b5cf6', '#ec4899'); }, []); return <div>...</div>; } ``` ### 🎨 Funções Utilitárias O DS exporta várias funções para cálculos de cor: ```typescript import { generateColorPalette, // Gera paleta completa de uma cor getContrastingTextColor, // Retorna branco ou preto com melhor contraste isLightColor, // Verifica se uma cor é clara ou escura lightenColor, // Clareia uma cor em X% darkenColor, // Escurece uma cor em X% hexToRgb, // Converte HEX para RGB getLuminance, // Calcula luminância relativa getContrastRatio // Calcula razão de contraste (WCAG) } from 'rharuow-ds/lib/color.utils'; // Exemplo: Gerar paleta completa const palette = generateColorPalette('#8b5cf6'); /* { base: '#8b5cf6', hover: '#7c3aed', light: '#ede9fe', dark: '#6d28d9', text: '#ffffff', textOnLight: '#1f2937' } */ ``` ### 🌈 Sistema de Cores #### Como os Componentes Usam as Cores Os componentes **derivam automaticamente** suas cores das variáveis primárias: - **Card Header**: Mescla 5% da cor primária com fundo neutro - **Table Header**: Mescla 8% da cor primária com fundo neutro - **Table Hover**: Mescla 10% da cor primária com fundo neutro - **Select Selected**: Usa diretamente `--primary-light` - **Button/Modal**: Usam cores primária/secundária com texto de alto contraste - **Elementos Selecionados**: Consistentemente usam a cor primária clara ### 💡 Modo Avançado (Controle Total) Se você precisa de controle total sobre cada variação: ```css :root { /* Defina todas as variações manualmente */ --primary: #8b5cf6; --primary-hover: #7c3aed; --primary-light: #ede9fe; --primary-dark: #6d28d9; --primary-text: #ffffff; --secondary: #ec4899; --secondary-hover: #db2777; --secondary-light: #fce7f3; --secondary-dark: #be185d; --secondary-text: #ffffff; } ``` ### 📖 Documentação Completa - **[AUTO_COLOR_SYSTEM.md](AUTO_COLOR_SYSTEM.md)** - Guia completo do sistema de cores automático - **[THEME_CUSTOMIZATION.md](THEME_CUSTOMIZATION.md)** - Customização detalhada de tema #### Método 2: Java