shinodalabs-ui
v1.15.17
Published
A collection of reusable React components styled with Tailwind CSS, including buttons, date pickers, modals, tabs, progress bars, forms, checkboxes, radio buttons, selects, inputs, textareas, dropdowns, tooltips, alerts, and badges.
Downloads
172
Maintainers
Readme
🎨 ShinodaLabs UI
Uma biblioteca de componentes React moderna e elegante, construída com TailwindCSS e TypeScript. Projetada para oferecer componentes reutilizáveis com excelente experiência de desenvolvedor.
📋 Índice
- ✨ Características
- 📦 Instalação
- ⚙️ Configuração do TailwindCSS
- 🧩 Componentes
- 🌙 Suporte ao Modo Escuro
- 📱 Responsividade
- 🎯 Exemplos Práticos
- 🔧 Desenvolvimento
- 📄 Licença
- 🤝 Contribuição
✨ Características
- 🎯 Componentes modernos com design limpo e profissional
- 🌙 Suporte completo a tema escuro/claro
- 🎨 Totalmente estilizado com TailwindCSS
- 🔧 TypeScript nativo com tipagem completa
- ♿ Acessibilidade seguindo as melhores práticas
- 🚀 Performance otimizada com bundle pequeno
- 📱 Responsivo por padrão
📦 Instalação
npm install shinodalabs-uiDependências necessárias
Certifique-se de ter essas dependências instaladas no seu projeto:
npm install react react-dom tailwindcss lucide-react clsx react-hook-form react-day-picker date-fns⚙️ Configuração do TailwindCSS
Para que os estilos funcionem corretamente, você precisa configurar o TailwindCSS no seu projeto para incluir os componentes da biblioteca.
1. Instale o TailwindCSS (se ainda não tiver)
npm install -D tailwindcss postcss autoprefixer
npx tailwindcss init -p2. Configure o tailwind.config.js
/** @type {import('tailwindcss').Config} */
module.exports = {
content: [
"./src/**/*.{js,ts,jsx,tsx,mdx}",
"./pages/**/*.{js,ts,jsx,tsx,mdx}",
"./components/**/*.{js,ts,jsx,tsx,mdx}",
"./app/**/*.{js,ts,jsx,tsx,mdx}",
// 👇 Adicione esta linha para incluir os componentes da biblioteca
"./node_modules/shinodalabs-ui/**/*.{js,ts,jsx,tsx}",
],
theme: {
extend: {},
},
plugins: [],
// 👇 Adicione suporte ao modo escuro
darkMode: "class",
};3. Importe os estilos do Tailwind no seu CSS
/* globals.css ou app.css */
@tailwind base;
@tailwind components;
@tailwind utilities;🧩 Componentes
Button
Um botão versátil e customizável com suporte a ícones e múltiplas variações.
Importação
import { Button } from "shinodalabs-ui";Uso básico
import { Button } from "shinodalabs-ui";
export default function Example() {
return (
<div className="space-y-4">
<Button>Botão padrão</Button>
<Button variant="blue">Botão azul</Button>
<Button variant="red" size="lg">
Botão grande vermelho
</Button>
</div>
);
}Com ícones
import { Button } from "shinodalabs-ui";
import { Plus, Download, Trash2, Settings } from "lucide-react";
export default function ButtonsWithIcons() {
return (
<div className="flex gap-4">
<Button icon={Plus}>Adicionar</Button>
<Button variant="blue" icon={Download}>
Download
</Button>
<Button variant="red" icon={Trash2}>
Excluir
</Button>
<Button variant="neutral" icon={Settings}>
Configurações
</Button>
</div>
);
}Estados e interações
import { Button } from "shinodalabs-ui";
import { Save, Send } from "lucide-react";
export default function ButtonStates() {
const handleClick = () => {
alert("Botão clicado!");
};
return (
<div className="space-y-4">
{/* Botão normal */}
<Button onClick={handleClick}>Clique aqui</Button>
{/* Botão desabilitado */}
<Button disabled icon={Save}>
Salvando...
</Button>
{/* Botão com classe customizada */}
<Button variant="blue" className="w-full" icon={Send}>
Enviar formulário
</Button>
</div>
);
}Props do Button
| Propriedade | Tipo | Padrão | Descrição |
| ----------- | ------------------------------------------------- | ----------- | ------------------------------------------------- |
| variant | "emerald" | "blue" | "red" | "neutral" | "emerald" | Define a cor do botão |
| size | "sm" | "md" | "lg" | "md" | Define o tamanho do botão |
| icon | LucideIcon | undefined | Ícone do Lucide React exibido à esquerda do texto |
| children | React.ReactNode | - | Conteúdo do botão (texto, elementos, etc.) |
| disabled | boolean | false | Desabilita o botão |
| className | string | undefined | Classes CSS adicionais |
| onClick | (event: MouseEvent) => void | undefined | Função executada ao clicar |
| ...props | HTMLButtonAttributes | - | Todas as props nativas do elemento <button> |
Variações de cor
<div className="flex gap-2">
<Button variant="emerald">Emerald</Button>
<Button variant="blue">Blue</Button>
<Button variant="red">Red</Button>
<Button variant="neutral">Neutral</Button>
</div>Tamanhos disponíveis
<div className="flex items-center gap-2">
<Button size="sm">Pequeno</Button>
<Button size="md">Médio</Button>
<Button size="lg">Grande</Button>
</div>Input
Um componente de input versátil com suporte a diferentes tipos, validação, formatação de moeda e recursos avançados.
Importação
import { Input } from "shinodalabs-ui";Uso básico
import { Input } from "shinodalabs-ui";
import { useState } from "react";
export default function BasicInputs() {
const [name, setName] = useState("");
const [email, setEmail] = useState("");
return (
<div className="space-y-4">
<Input
label="Nome completo"
value={name}
onChange={(e) => setName(e.target.value)}
placeholder="Digite seu nome"
/>
<Input
label="E-mail"
type="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
placeholder="[email protected]"
/>
</div>
);
}Input com senha
import { Input } from "shinodalabs-ui";
import { useState } from "react";
export default function PasswordInput() {
const [password, setPassword] = useState("");
return (
<Input
label="Senha"
type="password"
value={password}
onChange={(e) => setPassword(e.target.value)}
placeholder="Digite sua senha"
/>
);
}Input numérico
import { Input } from "shinodalabs-ui";
import { useState } from "react";
export default function NumericInput() {
const [phone, setPhone] = useState("");
const [code, setCode] = useState("");
return (
<div className="space-y-4">
<Input
label="Telefone"
type="number"
value={phone}
onChange={(e) => setPhone(e.target.value)}
maxLength={11}
placeholder="11999999999"
/>
<Input
label="Código de verificação"
type="number"
value={code}
onChange={(e) => setCode(e.target.value)}
maxLength={6}
centerContent
placeholder="000000"
/>
</div>
);
}Input de moeda
import { Input } from "shinodalabs-ui";
import { useState } from "react";
export default function MoneyInput() {
const [price, setPrice] = useState(0);
const [salary, setSalary] = useState(0);
return (
<div className="space-y-4">
<Input
label="Preço do produto"
type="money"
value={price}
onChange={(value) => setPrice(value as number)}
currency="BRL"
language="pt-BR"
/>
<Input
label="Salário (USD)"
type="money"
value={salary}
onChange={(value) => setSalary(value as number)}
currency="USD"
language="en-US"
/>
</div>
);
}Input com validação e elementos extras
import { Input } from "shinodalabs-ui";
import { useState } from "react";
import { Info } from "lucide-react";
export default function AdvancedInput() {
const [username, setUsername] = useState("");
const [error, setError] = useState("");
const validateUsername = (value: string) => {
if (value.length < 3) {
setError("Nome de usuário deve ter pelo menos 3 caracteres");
} else if (!/^[a-zA-Z0-9_]+$/.test(value)) {
setError("Apenas letras, números e underscore são permitidos");
} else {
setError("");
}
};
return (
<Input
label="Nome de usuário"
value={username}
onChange={(e) => {
const value = e.target.value;
setUsername(value);
validateUsername(value);
}}
error={error}
placeholder="meu_usuario"
headerRight={
<div className="flex items-center text-zinc-500">
<Info size={16} />
<span className="ml-1 text-xs">Disponível</span>
</div>
}
/>
);
}Props do Input
| Propriedade | Tipo | Padrão | Descrição |
| --------------- | --------------------- | ---------------- | ----------------------------------------------------------------------- |
| label | string | - | Obrigatório. Texto do label do input |
| type | string | "text" | Tipo do input: "text", "email", "password", "number", "money" |
| value | string \| number | undefined | Valor controlado do input |
| onChange | function | undefined | Função chamada quando o valor muda |
| error | string | undefined | Mensagem de erro a ser exibida |
| placeholder | string | undefined | Texto placeholder |
| disabled | boolean | false | Desabilita o input |
| maxLength | number | undefined | Limite máximo de caracteres (para type="number") |
| headerRight | React.ReactNode | undefined | Elemento exibido à direita do label |
| centerContent | boolean | false | Centraliza o conteúdo do input |
| currency | string | "BRL" | Código da moeda (para type="money") |
| language | string | "pt-BR" | Idioma para formatação (para type="money") |
| id | string | auto-generated | ID do input (gerado automaticamente se não fornecido) |
| className | string | undefined | Classes CSS adicionais |
| ...props | HTMLInputAttributes | - | Todas as props nativas do elemento <input> |
Tipos especiais
Input de Moeda (type="money")
O input de moeda formata automaticamente valores monetários conforme você digita:
// Formatação em Real Brasileiro
<Input
label="Valor"
type="money"
currency="BRL"
language="pt-BR"
value={1234.56}
onChange={(value) => console.log(value)} // 1234.56
/>
// Exibe: R$ 1.234,56
// Formatação em Dólar Americano
<Input
label="Price"
type="money"
currency="USD"
language="en-US"
value={1234.56}
onChange={(value) => console.log(value)} // 1234.56
/>
// Exibe: $1,234.56Input Numérico (type="number")
Aceita apenas dígitos e respeita o maxLength:
<Input
label="CEP"
type="number"
maxLength={8}
value={cep}
onChange={(e) => setCep(e.target.value)}
/>Input de Senha (type="password")
Inclui botão para mostrar/ocultar senha:
<Input
label="Senha"
type="password"
value={password}
onChange={(e) => setPassword(e.target.value)}
/>🌙 Suporte ao Modo Escuro
Todos os componentes suportam automaticamente o modo escuro quando você configura o TailwindCSS com darkMode: 'class'.
Exemplo de implementação
// Componente para alternar tema
import { useState, useEffect } from "react";
import { Button } from "shinodalabs-ui";
import { Moon, Sun } from "lucide-react";
export default function ThemeToggle() {
const [isDark, setIsDark] = useState(false);
useEffect(() => {
if (isDark) {
document.documentElement.classList.add("dark");
} else {
document.documentElement.classList.remove("dark");
}
}, [isDark]);
return (
<Button
variant="neutral"
icon={isDark ? Sun : Moon}
onClick={() => setIsDark(!isDark)}
>
{isDark ? "Modo Claro" : "Modo Escuro"}
</Button>
);
}📱 Responsividade
Os componentes são responsivos por padrão. O ícone do botão, por exemplo, tem espaçamento adaptativo:
// Em telas pequenas, o ícone fica colado ao texto
// Em telas médias e maiores (md+), há espaçamento
<Button icon={Plus}>Adicionar item</Button>🎯 Exemplos Práticos
Formulário de login completo
import { Button, Input } from "shinodalabs-ui";
import { LogIn, UserPlus } from "lucide-react";
import { useState } from "react";
export default function LoginForm() {
const [email, setEmail] = useState("");
const [password, setPassword] = useState("");
const [errors, setErrors] = useState<{ email?: string; password?: string }>(
{}
);
const validateForm = () => {
const newErrors: { email?: string; password?: string } = {};
if (!email) {
newErrors.email = "E-mail é obrigatório";
} else if (!/\S+@\S+\.\S+/.test(email)) {
newErrors.email = "E-mail inválido";
}
if (!password) {
newErrors.password = "Senha é obrigatória";
} else if (password.length < 6) {
newErrors.password = "Senha deve ter pelo menos 6 caracteres";
}
setErrors(newErrors);
return Object.keys(newErrors).length === 0;
};
const handleSubmit = (e: React.FormEvent) => {
e.preventDefault();
if (validateForm()) {
console.log("Login válido:", { email, password });
}
};
return (
<form onSubmit={handleSubmit} className="space-y-4 max-w-md mx-auto p-6">
<h2 className="text-2xl font-bold text-center mb-6">Login</h2>
<Input
label="E-mail"
type="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
error={errors.email}
placeholder="[email protected]"
/>
<Input
label="Senha"
type="password"
value={password}
onChange={(e) => setPassword(e.target.value)}
error={errors.password}
placeholder="Digite sua senha"
/>
<div className="flex gap-2 pt-4">
<Button type="submit" variant="blue" icon={LogIn} className="flex-1">
Entrar
</Button>
<Button
type="button"
variant="neutral"
icon={UserPlus}
className="flex-1"
>
Cadastrar
</Button>
</div>
</form>
);
}Formulário de produto com preço
import { Button, Input } from "shinodalabs-ui";
import { Save, Package } from "lucide-react";
import { useState } from "react";
export default function ProductForm() {
const [name, setName] = useState("");
const [price, setPrice] = useState(0);
const [code, setCode] = useState("");
return (
<form className="space-y-4 max-w-md mx-auto p-6">
<h2 className="text-2xl font-bold text-center mb-6">Novo Produto</h2>
<Input
label="Nome do produto"
value={name}
onChange={(e) => setName(e.target.value)}
placeholder="Digite o nome do produto"
/>
<Input
label="Preço"
type="money"
value={price}
onChange={(value) => setPrice(value as number)}
currency="BRL"
language="pt-BR"
/>
<Input
label="Código do produto"
type="number"
value={code}
onChange={(e) => setCode(e.target.value)}
maxLength={10}
centerContent
placeholder="0000000000"
headerRight={<span className="text-xs text-zinc-500">10 dígitos</span>}
/>
<Button type="submit" icon={Save} className="w-full">
Salvar Produto
</Button>
</form>
);
}Dashboard com ações
import { Button } from "shinodalabs-ui";
import { Plus, Download, Filter, Search } from "lucide-react";
export default function Dashboard() {
return (
<div className="p-6">
<div className="flex justify-between items-center mb-6">
<h1 className="text-2xl font-bold">Dashboard</h1>
<div className="flex gap-2">
<Button variant="neutral" icon={Filter} size="sm">
Filtrar
</Button>
<Button variant="blue" icon={Download} size="sm">
Exportar
</Button>
<Button icon={Plus}>Novo Item</Button>
</div>
</div>
{/* Conteúdo do dashboard */}
</div>
);
}🔧 Desenvolvimento
Estrutura do projeto
shinodalabs-ui/
├── src/
│ ├── components/
│ │ ├── Button/
│ │ │ └── index.tsx
│ │ └── Input/
│ │ └── index.tsx
│ └── index.ts
├── dist/ # Arquivos compilados
├── package.json
├── tsconfig.json
├── tsup.config.ts
└── README.mdScripts disponíveis
# Compilar para produção
npm run build
# Desenvolvimento com watch mode
npm run dev
# Limpar pasta dist
npm run clean📄 Licença
ISC © ShinodaLabs
🤝 Contribuição
Contribuições são bem-vindas! Sinta-se à vontade para abrir issues ou pull requests.
Feito com ❤️ pela equipe ShinodaLabs
