rharuow-ds
v1.10.14
Published
Modern React Design System with 19 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
Maintainers
Keywords
Readme
rharuow-ds
[ - Digite para encontrar opções - ✅ Filtro case-sensitive configurável
- ✅ Função de filtro customizável
- ✅ Placeholder personalizado para busca
- ✅ Validação de valores (só aceita opções válidas)
- ✅ Botão de limpeza (
isClearable) - ✅ Integração completa com React Hook Form
// Select básico
<Select
name="fruit"
label="Escolha uma fruta"
options={[
{ label: "Maçã", value: "apple" },
{ label: "Banana", value: "banana" },
{ label: "Manga", value: "mango" }
]}
/>
// Select com filtro
<Select
name="fruit"
label="Escolha uma fruta"
searchable
filterPlaceholder="Digite para filtrar frutas..."
caseSensitive={false}
isClearable
options={fruitOptions}
/>
```dge.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
- 🧩 **19 componentes** prontos para uso (Input, Textarea, Select, AsyncSelect, MultiSelect, MultiAsyncSelect, RadioGroup, Button, Chip, Pagination, Card, Table, Tooltip, Accordion, AsideSheet, 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-dsDependências necessárias
Se você for usar componentes com formulários, instale também:
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
Importe o CSS do design system
No seu arquivo de entrada (ex:src/main.tsx,src/index.tsxou_app.tsxno Next.js):import "rharuow-ds/dist/styles.css";Use os componentes normalmente
import { Card, Table, Button, Chip, Pagination, Input, Textarea, Select, AsyncSelect, MultiSelect, RadioGroup, Tooltip, Accordion, AsideSheet, Modal, Toaster, ImageInput, } 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>[email protected]</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> ); }Para componentes com React Hook Form
import { useForm, FormProvider } from "react-hook-form"; import { Card, Table, Input, Textarea, Select, AsyncSelect, MultiAsyncSelect, RadioGroup, Button, Tooltip, Accordion, AsideSheet, Modal, Toaster, ImageInput, } 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=truepara 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
aspara 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
aspara 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
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 comochildrendo botão e fica automaticamente centralizado. Use semprearia-labelpara 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
disabledcom visual reduzido - ✅ Acessível (
role="switch",aria-checked) - ✅ Totalmente estilizável via
className
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
disabledpara toda a navegação - ✅ Acessível (
role="navigation",aria-label,aria-current)
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)
- ✅ Funcionalidade de password com botão mostrar/ocultar
- ✅ Ícones customizados opcionais
- ✅ Estados de erro integrados
- ✅ Totalmente acessível (ARIA)
� 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
// 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
// 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
// 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
sizeouclassName - ✅ 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 fecharside?: 'left' | 'right'— lado de abertura (padrão: 'right')size?: 'sm' | 'md' | 'lg' | 'full'— tamanho pré-definido do sheetclassName?: string— classes adicionais para o containertitle?: string | React.ReactNode— título opcional do painel
Exemplo de uso (controlado):
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):
🎭 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 modalonClose: () => void— callback chamado ao fecharsize?: '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:
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:
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:
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:
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>
);
}Veja a story do componente no Storybook para mais exemplos e variações:
� 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
useToastpara uso simplificado - ✅ Gerenciamento via Context API
Configuração inicial:
O Toaster precisa ser configurado uma única vez no nível superior da aplicação:
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:
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:
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:
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:
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:
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:
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
successpara operações bem-sucedidas (save, delete, update) - Use
errorpara falhas e erros - Use
warningpara avisos que requerem atenção - Use
infopara informações gerais - Configure
duration: 0para 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:
�📷 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
valueprop - ✅ 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çãoaccept?: string— tipos aceitos (padrão: "image/*")maxSize?: number— tamanho máximo em bytessize?: 'sm' | 'md' | 'lg'— tamanho do componenteloading?: boolean— estado de carregamentodisabled?: boolean— desabilitar interações
Exemplo básico:
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:
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:
---
## 🎨 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:
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:
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:
: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 - Guia completo do sistema de cores automático
- THEME_CUSTOMIZATION.md - Customização detalhada de tema
Método 2: JavaScript/React
// App.tsx ou main.tsx
useEffect(() => {
const root = document.documentElement;
root.style.setProperty('--primary', '#f59e0b');
root.style.setProperty('--primary-hover', '#d97706');
root.style.setProperty('--primary-light', '#fef3c7');
}, []);🎯 Exemplos de Paletas
/* Paleta Corporativa (Azul) */
:root {
--primary: #0ea5e9;
--primary-hover: #0284c7;
--primary-light: #e0f2fe;
}
/* Paleta Moderna (Roxo/Rosa) */
:root {
--primary: #8b5cf6;
--primary-hover: #7c3aed;
--primary-light: #ede9fe;
--secondary: #ec4899;
}
/* Paleta Natureza (Verde) */
:root {
--primary: #10b981;
--primary-hover: #059669;
--primary-light: #d1fae5;
}
/* Paleta Minimalista (Cinza) */
:root {
--primary: #6b7280;
--primary-hover: #4b5563;
--primary-light: #f3f4f6;
}🔧 Customização Avançada
Para controle total, você pode sobrescrever variáveis específicas:
:root {
/* Cores base da marca */
--primary: #8b5cf6;
/* Customização específica de Card */
--card-header-bg: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
--card-header-border: #8b5cf6;
/* Customização específica de Table */
--table-header-bg: #f3e8ff;
--table-row-selected: #ede9fe;
}📚 Variáveis Disponíveis por Componente
Card
--card-bg,--card-border,--card-text--card-header-bg,--card-header-border--card-footer-bg,--card-footer-border
Table
--table-bg,--table-border,--table-text--table-header-bg,--table-row-hover,--table-row-selected
Select/AsyncSelect/MultiSelect
--select-dropdown-bg,--select-dropdown-border--select-dropdown-hover,--select-dropdown-selected
Tooltip
--tooltip-bg,--tooltip-text
🌓 Dark Mode
O sistema ajusta automaticamente as cores no dark mode:
/* Ative o dark mode adicionando o atributo */
<html data-theme="dark">
<!-- ou -->
<html class="dark">No Storybook
Na documentação do Storybook, você pode testar diferentes temas na story "Theme Customization".
Para mais detalhes, consulte THEME_CUSTOMIZATION.md.
🛠️ Desenvolvimento
- ✅ Ícones customizados opcionais
- ✅ Três tamanhos: sm, md, lg
- ✅ Layout horizontal ou vertical
- ✅ Animações e estados visuais
- ✅ Label flutuante integrada
Documentação
Acesse a documentação interativa dos componentes em GitHub Pages (link será atualizado após o deploy).
Desenvolvimento
Para contribuir ou rodar localmente:
git clone https://github.com/Rharuow/rharuow-ds-docs.git
cd rharuow-ds-docs
npm installComandos disponíveis:
# Iniciar Storybook para desenvolvimento
npm run storybook
# Build dos componentes
npm run build
# Gerar CSS do Tailwind
npm run build:css
# Executar testes
npm test
# Build do Storybook para produção
npm run build-storybookEstrutura do projeto:
src/
├── components/ # Componentes React
│ ├── Button.tsx
│ ├── Input.tsx
│ ├── Select.tsx
│ ├── AsyncSelect.tsx
│ ├── MultiSelect.tsx
│ ├── types.ts # Tipos compartilhados
│ └── index.ts # Exportações
├── lib/
│ └── utils.ts # Utilitários (cn, etc.)
├── stories/ # Stories do Storybook
└── styles/
└── ds.css # Estilos TailwindTecnologias
- ⚛️ React 18 - Biblioteca base
- 📋 React Hook Form - Gerenciamento de formulários
- 🎨 Tailwind CSS - Estilização
- 📚 Storybook - Documentação interativa
- 📦 Vite - Build tool
- 🔷 TypeScript - Tipagem estática
Desenvolvido por Harysson.
