shinodalabs-ui
Version:
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.
726 lines (593 loc) • 19.8 kB
Markdown
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.
- [✨ Características](
- [📦 Instalação](
- [⚙️ Configuração do TailwindCSS](
- [🧩 Componentes](
- [Button](
- [Importação](
- [Uso básico](
- [Com ícones](
- [Estados e interações](
- [Props do Button](
- [Variações de cor](
- [Tamanhos disponíveis](
- [Input](
- [Importação](
- [Uso básico](
- [Input com senha](
- [Input numérico](
- [Input de moeda](
- [Input com validação](
- [Props do Input](
- [Tipos especiais](
- [🌙 Suporte ao Modo Escuro](
- [📱 Responsividade](
- [🎯 Exemplos Práticos](
- [Formulário de login completo](
- [Formulário de produto](
- [Dashboard com ações](
- [🔧 Desenvolvimento](
- [📄 Licença](
- [🤝 Contribuição](
---
- 🎯 **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
```bash
npm install shinodalabs-ui
```
Certifique-se de ter essas dependências instaladas no seu projeto:
```bash
npm install react react-dom tailwindcss lucide-react clsx react-hook-form react-day-picker date-fns
```
Para que os estilos funcionem corretamente, você precisa configurar o TailwindCSS no seu projeto para incluir os componentes da biblioteca.
```bash
npm install -D tailwindcss postcss autoprefixer
npx tailwindcss init -p
```
```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",
};
```
```css
/* globals.css ou app.css */
@tailwind base;
@tailwind components;
@tailwind utilities;
```
Um botão versátil e customizável com suporte a ícones e múltiplas variações.
```tsx
import { Button } from "shinodalabs-ui";
```
```tsx
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>
);
}
```
```tsx
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>
);
}
```
```tsx
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>
);
}
```
| 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>` |
```tsx
<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>
```
```tsx
<div className="flex items-center gap-2">
<Button size="sm">Pequeno</Button>
<Button size="md">Médio</Button>
<Button size="lg">Grande</Button>
</div>
```
Um componente de input versátil com suporte a diferentes tipos, validação, formatação de moeda e recursos avançados.
```tsx
import { Input } from "shinodalabs-ui";
```
```tsx
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="seu@email.com"
/>
</div>
);
}
```
```tsx
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"
/>
);
}
```
```tsx
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>
);
}
```
```tsx
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>
);
}
```
```tsx
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>
}
/>
);
}
```
| 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:
```tsx
// 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.56
```
Aceita apenas dígitos e respeita o `maxLength`:
```tsx
<Input
label="CEP"
type="number"
maxLength={8}
value={cep}
onChange={(e) => setCep(e.target.value)}
/>
```
Inclui botão para mostrar/ocultar senha:
```tsx
<Input
label="Senha"
type="password"
value={password}
onChange={(e) => setPassword(e.target.value)}
/>
```
Todos os componentes suportam automaticamente o modo escuro quando você configura o TailwindCSS com `darkMode: 'class'`.
```tsx
// 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>
);
}
```
Os componentes são responsivos por padrão. O ícone do botão, por exemplo, tem espaçamento adaptativo:
```tsx
// 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>
```
```tsx
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="seu@email.com"
/>
<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>
);
}
```
```tsx
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>
);
}
```
```tsx
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>
);
}
```
```
shinodalabs-ui/
├── src/
│ ├── components/
│ │ ├── Button/
│ │ │ └── index.tsx
│ │ └── Input/
│ │ └── index.tsx
│ └── index.ts
├── dist/
├── package.json
├── tsconfig.json
├── tsup.config.ts
└── README.md
```
```bash
npm run build
npm run dev
npm run clean
```
ISC © ShinodaLabs
Contribuições são bem-vindas! Sinta-se à vontade para abrir issues ou pull requests.
---
**Feito com ❤️ pela equipe ShinodaLabs**