first commit
This commit is contained in:
commit
c9fb67e751
423 files changed
+53287
No files matched your search
@@ -0,0 +1,69 @@
|
||||
import React from 'react';
|
||||
|
||||
type ButtonVariant = 'primary' | 'secondary' | 'success' | 'danger' | 'warning' | 'info' | 'outline' | 'link';
|
||||
|
||||
interface ButtonProps {
|
||||
children: React.ReactNode;
|
||||
type?: 'button' | 'submit' | 'reset';
|
||||
onClick?: (e: React.MouseEvent<HTMLButtonElement>) => void;
|
||||
variant?: ButtonVariant;
|
||||
size?: 'sm' | 'md' | 'lg';
|
||||
disabled?: boolean;
|
||||
className?: string;
|
||||
icon?: React.ReactNode;
|
||||
}
|
||||
|
||||
export function Button({
|
||||
children,
|
||||
type = 'button',
|
||||
onClick,
|
||||
variant = 'primary',
|
||||
size = 'md',
|
||||
disabled = false,
|
||||
className = '',
|
||||
icon,
|
||||
}: ButtonProps) {
|
||||
const baseClasses = 'inline-flex items-center justify-center font-medium rounded-md focus:outline-none transition-colors';
|
||||
|
||||
// Definir classes de variante
|
||||
const variantClasses = {
|
||||
primary: 'bg-blue-600 text-white hover:bg-blue-700 focus:ring-2 focus:ring-offset-2 focus:ring-blue-500',
|
||||
secondary: 'bg-gray-600 text-white hover:bg-gray-700 focus:ring-2 focus:ring-offset-2 focus:ring-gray-500',
|
||||
success: 'bg-green-600 text-white hover:bg-green-700 focus:ring-2 focus:ring-offset-2 focus:ring-green-500',
|
||||
danger: 'bg-red-600 text-white hover:bg-red-700 focus:ring-2 focus:ring-offset-2 focus:ring-red-500',
|
||||
warning: 'bg-yellow-500 text-white hover:bg-yellow-600 focus:ring-2 focus:ring-offset-2 focus:ring-yellow-400',
|
||||
info: 'bg-blue-400 text-white hover:bg-blue-500 focus:ring-2 focus:ring-offset-2 focus:ring-blue-300',
|
||||
outline: 'bg-white text-gray-700 border border-gray-300 hover:bg-gray-50 focus:ring-2 focus:ring-offset-2 focus:ring-blue-500',
|
||||
link: 'bg-transparent text-blue-600 hover:text-blue-800 hover:underline focus:ring-0',
|
||||
};
|
||||
|
||||
// Definir classes de tamanho
|
||||
const sizeClasses = {
|
||||
sm: 'px-2.5 py-1.5 text-xs',
|
||||
md: 'px-4 py-2 text-sm',
|
||||
lg: 'px-6 py-3 text-base',
|
||||
};
|
||||
|
||||
// Definir classes de estado desabilitado
|
||||
const disabledClasses = 'opacity-50 cursor-not-allowed';
|
||||
|
||||
const buttonClasses = [
|
||||
baseClasses,
|
||||
variantClasses[variant],
|
||||
sizeClasses[size],
|
||||
disabled ? disabledClasses : '',
|
||||
className,
|
||||
].join(' ');
|
||||
|
||||
return (
|
||||
<button
|
||||
type={type}
|
||||
onClick={onClick}
|
||||
disabled={disabled}
|
||||
className={buttonClasses}
|
||||
>
|
||||
{icon && <span className="mr-2">{icon}</span>}
|
||||
{children}
|
||||
</button>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
import React from 'react';
|
||||
|
||||
interface CardProps {
|
||||
children: React.ReactNode;
|
||||
className?: string;
|
||||
padding?: 'none' | 'sm' | 'md' | 'lg';
|
||||
}
|
||||
|
||||
export function Card({ children, className = '', padding = 'md' }: CardProps) {
|
||||
const paddingClasses = {
|
||||
none: '',
|
||||
sm: 'p-2',
|
||||
md: 'p-4',
|
||||
lg: 'p-6',
|
||||
};
|
||||
|
||||
const cardClasses = [
|
||||
'bg-white shadow overflow-hidden rounded-lg',
|
||||
paddingClasses[padding],
|
||||
className,
|
||||
].join(' ');
|
||||
|
||||
return <div className={cardClasses}>{children}</div>;
|
||||
}
|
||||
@@ -0,0 +1,265 @@
|
||||
import React from 'react';
|
||||
|
||||
interface InputBaseProps {
|
||||
label: string;
|
||||
id: string;
|
||||
name: string;
|
||||
required?: boolean;
|
||||
help?: string;
|
||||
errorMessage?: string;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
interface TextInputProps extends InputBaseProps {
|
||||
type?: 'text' | 'email' | 'password' | 'tel' | 'url';
|
||||
value: string;
|
||||
onChange: (e: React.ChangeEvent<HTMLInputElement>) => void;
|
||||
placeholder?: string;
|
||||
maxLength?: number;
|
||||
}
|
||||
|
||||
export function TextInput({
|
||||
label,
|
||||
id,
|
||||
name,
|
||||
required,
|
||||
help,
|
||||
errorMessage,
|
||||
className,
|
||||
value,
|
||||
onChange,
|
||||
placeholder,
|
||||
maxLength,
|
||||
type = 'text',
|
||||
}: TextInputProps) {
|
||||
return (
|
||||
<div className={className}>
|
||||
<label htmlFor={id} className="block text-sm font-medium text-gray-700">
|
||||
{label} {required && <span className="text-red-500">*</span>}
|
||||
</label>
|
||||
<div className="mt-1">
|
||||
<input
|
||||
type={type}
|
||||
id={id}
|
||||
name={name}
|
||||
value={value}
|
||||
onChange={onChange}
|
||||
placeholder={placeholder}
|
||||
maxLength={maxLength}
|
||||
required={required}
|
||||
className="block w-full rounded-md border-gray-300 shadow-sm focus:border-blue-500 focus:ring-blue-500 sm:text-sm"
|
||||
/>
|
||||
</div>
|
||||
{errorMessage && <p className="mt-1 text-sm text-red-600">{errorMessage}</p>}
|
||||
{help && <p className="mt-1 text-xs text-gray-500">{help}</p>}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
interface NumberInputProps extends InputBaseProps {
|
||||
value: string;
|
||||
onChange: (e: React.ChangeEvent<HTMLInputElement>) => void;
|
||||
placeholder?: string;
|
||||
min?: number;
|
||||
max?: number;
|
||||
step?: number;
|
||||
}
|
||||
|
||||
export function NumberInput({
|
||||
label,
|
||||
id,
|
||||
name,
|
||||
required,
|
||||
help,
|
||||
errorMessage,
|
||||
className,
|
||||
value,
|
||||
onChange,
|
||||
placeholder,
|
||||
min,
|
||||
max,
|
||||
step,
|
||||
}: NumberInputProps) {
|
||||
// Processar input para garantir que seja numérico com vírgula como separador decimal
|
||||
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
// Permitir apenas números, vírgula, ponto e sinais +/-
|
||||
const cleanValue = e.target.value.replace(/[^0-9,.+-]/g, '');
|
||||
|
||||
// Simular o evento com o valor limpo
|
||||
const simulatedEvent = {
|
||||
...e,
|
||||
target: {
|
||||
...e.target,
|
||||
value: cleanValue,
|
||||
},
|
||||
};
|
||||
|
||||
onChange(simulatedEvent);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={className}>
|
||||
<label htmlFor={id} className="block text-sm font-medium text-gray-700">
|
||||
{label} {required && <span className="text-red-500">*</span>}
|
||||
</label>
|
||||
<div className="mt-1 relative rounded-md shadow-sm">
|
||||
<input
|
||||
type="text"
|
||||
inputMode="decimal"
|
||||
id={id}
|
||||
name={name}
|
||||
value={value}
|
||||
onChange={handleChange}
|
||||
placeholder={placeholder}
|
||||
required={required}
|
||||
className="block w-full rounded-md border-gray-300 shadow-sm focus:border-blue-500 focus:ring-blue-500 sm:text-sm"
|
||||
min={min}
|
||||
max={max}
|
||||
step={step}
|
||||
/>
|
||||
</div>
|
||||
{errorMessage && <p className="mt-1 text-sm text-red-600">{errorMessage}</p>}
|
||||
{help && <p className="mt-1 text-xs text-gray-500">{help}</p>}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
interface DateInputProps extends InputBaseProps {
|
||||
value: string;
|
||||
onChange: (e: React.ChangeEvent<HTMLInputElement>) => void;
|
||||
min?: string;
|
||||
max?: string;
|
||||
}
|
||||
|
||||
export function DateInput({
|
||||
label,
|
||||
id,
|
||||
name,
|
||||
required,
|
||||
help,
|
||||
errorMessage,
|
||||
className,
|
||||
value,
|
||||
onChange,
|
||||
min,
|
||||
max,
|
||||
}: DateInputProps) {
|
||||
return (
|
||||
<div className={className}>
|
||||
<label htmlFor={id} className="block text-sm font-medium text-gray-700">
|
||||
{label} {required && <span className="text-red-500">*</span>}
|
||||
</label>
|
||||
<div className="mt-1">
|
||||
<input
|
||||
type="date"
|
||||
id={id}
|
||||
name={name}
|
||||
value={value}
|
||||
onChange={onChange}
|
||||
required={required}
|
||||
min={min}
|
||||
max={max}
|
||||
className="block w-full rounded-md border-gray-300 shadow-sm focus:border-blue-500 focus:ring-blue-500 sm:text-sm"
|
||||
/>
|
||||
</div>
|
||||
{errorMessage && <p className="mt-1 text-sm text-red-600">{errorMessage}</p>}
|
||||
{help && <p className="mt-1 text-xs text-gray-500">{help}</p>}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
interface SelectOption {
|
||||
value: string;
|
||||
label: string;
|
||||
}
|
||||
|
||||
interface SelectInputProps extends InputBaseProps {
|
||||
value: string;
|
||||
onChange: (e: React.ChangeEvent<HTMLSelectElement>) => void;
|
||||
options: SelectOption[];
|
||||
placeholder?: string;
|
||||
}
|
||||
|
||||
export function SelectInput({
|
||||
label,
|
||||
id,
|
||||
name,
|
||||
required,
|
||||
help,
|
||||
errorMessage,
|
||||
className,
|
||||
value,
|
||||
onChange,
|
||||
options,
|
||||
placeholder,
|
||||
}: SelectInputProps) {
|
||||
return (
|
||||
<div className={className}>
|
||||
<label htmlFor={id} className="block text-sm font-medium text-gray-700">
|
||||
{label} {required && <span className="text-red-500">*</span>}
|
||||
</label>
|
||||
<div className="mt-1">
|
||||
<select
|
||||
id={id}
|
||||
name={name}
|
||||
value={value}
|
||||
onChange={onChange}
|
||||
required={required}
|
||||
className="block w-full rounded-md border-gray-300 shadow-sm focus:border-blue-500 focus:ring-blue-500 sm:text-sm"
|
||||
>
|
||||
{placeholder && <option value="">{placeholder}</option>}
|
||||
{options.map((option) => (
|
||||
<option key={option.value} value={option.value}>
|
||||
{option.label}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
{errorMessage && <p className="mt-1 text-sm text-red-600">{errorMessage}</p>}
|
||||
{help && <p className="mt-1 text-xs text-gray-500">{help}</p>}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
interface TextAreaInputProps extends InputBaseProps {
|
||||
value: string;
|
||||
onChange: (e: React.ChangeEvent<HTMLTextAreaElement>) => void;
|
||||
placeholder?: string;
|
||||
rows?: number;
|
||||
}
|
||||
|
||||
export function TextAreaInput({
|
||||
label,
|
||||
id,
|
||||
name,
|
||||
required,
|
||||
help,
|
||||
errorMessage,
|
||||
className,
|
||||
value,
|
||||
onChange,
|
||||
placeholder,
|
||||
rows = 3,
|
||||
}: TextAreaInputProps) {
|
||||
return (
|
||||
<div className={className}>
|
||||
<label htmlFor={id} className="block text-sm font-medium text-gray-700">
|
||||
{label} {required && <span className="text-red-500">*</span>}
|
||||
</label>
|
||||
<div className="mt-1">
|
||||
<textarea
|
||||
id={id}
|
||||
name={name}
|
||||
value={value}
|
||||
onChange={onChange}
|
||||
placeholder={placeholder}
|
||||
required={required}
|
||||
rows={rows}
|
||||
className="block w-full rounded-md border-gray-300 shadow-sm focus:border-blue-500 focus:ring-blue-500 sm:text-sm"
|
||||
/>
|
||||
</div>
|
||||
{errorMessage && <p className="mt-1 text-sm text-red-600">{errorMessage}</p>}
|
||||
{help && <p className="mt-1 text-xs text-gray-500">{help}</p>}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,135 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
|
||||
interface TabsProps {
|
||||
children: React.ReactNode;
|
||||
activeTab?: string;
|
||||
onChange?: (tabId: string) => void;
|
||||
}
|
||||
|
||||
interface TabListProps {
|
||||
children: React.ReactNode;
|
||||
}
|
||||
|
||||
interface TabProps {
|
||||
children: React.ReactNode;
|
||||
id: string;
|
||||
}
|
||||
|
||||
interface TabPanelProps {
|
||||
children: React.ReactNode;
|
||||
id: string;
|
||||
}
|
||||
|
||||
interface TabContext {
|
||||
activeTab: string;
|
||||
setActiveTab: (id: string) => void;
|
||||
}
|
||||
|
||||
const TabsContext = React.createContext<TabContext | undefined>(undefined);
|
||||
|
||||
export function Tabs({ children, activeTab: externalActiveTab, onChange }: TabsProps) {
|
||||
// Se o activeTab for fornecido externamente, use-o como valor inicial
|
||||
const [activeTab, setActiveTabInternal] = useState<string>('');
|
||||
|
||||
// Encontrar o primeiro id de tab disponível para valor inicial
|
||||
useEffect(() => {
|
||||
if (!activeTab && !externalActiveTab) {
|
||||
// Buscar os filhos TabPanel para encontrar o primeiro id
|
||||
React.Children.forEach(children, (child) => {
|
||||
if (React.isValidElement(child) && child.type === TabList) {
|
||||
React.Children.forEach(child.props.children, (tab) => {
|
||||
if (React.isValidElement(tab) && tab.props.id && !activeTab) {
|
||||
setActiveTabInternal(tab.props.id);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
}, []);
|
||||
|
||||
// Sincronizar com o activeTab externo, se fornecido
|
||||
useEffect(() => {
|
||||
if (externalActiveTab) {
|
||||
setActiveTabInternal(externalActiveTab);
|
||||
}
|
||||
}, [externalActiveTab]);
|
||||
|
||||
const setActiveTab = (id: string) => {
|
||||
setActiveTabInternal(id);
|
||||
if (onChange) {
|
||||
onChange(id);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<TabsContext.Provider value={{ activeTab: externalActiveTab || activeTab, setActiveTab }}>
|
||||
<div className="tabs-container">{children}</div>
|
||||
</TabsContext.Provider>
|
||||
);
|
||||
}
|
||||
|
||||
export function TabList({ children }: TabListProps) {
|
||||
return (
|
||||
<div className="border-b border-gray-200">
|
||||
<nav className="flex space-x-8" aria-label="Tabs">
|
||||
{children}
|
||||
</nav>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export function Tab({ children, id }: TabProps) {
|
||||
const context = React.useContext(TabsContext);
|
||||
|
||||
if (!context) {
|
||||
throw new Error('Tab must be used within a Tabs component');
|
||||
}
|
||||
|
||||
const { activeTab, setActiveTab } = context;
|
||||
|
||||
const isActive = activeTab === id;
|
||||
|
||||
const tabClasses = `
|
||||
whitespace-nowrap py-4 px-1 border-b-2 font-medium text-sm
|
||||
${isActive
|
||||
? 'border-blue-500 text-blue-600'
|
||||
: 'border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300'}
|
||||
`;
|
||||
|
||||
return (
|
||||
<button
|
||||
className={tabClasses}
|
||||
onClick={() => setActiveTab(id)}
|
||||
role="tab"
|
||||
aria-selected={isActive}
|
||||
id={`tab-${id}`}
|
||||
aria-controls={`panel-${id}`}
|
||||
>
|
||||
{children}
|
||||
</button>
|
||||
);
|
||||
}
|
||||
|
||||
export function TabPanel({ children, id }: TabPanelProps) {
|
||||
const context = React.useContext(TabsContext);
|
||||
|
||||
if (!context) {
|
||||
throw new Error('TabPanel must be used within a Tabs component');
|
||||
}
|
||||
|
||||
const { activeTab } = context;
|
||||
|
||||
if (activeTab !== id) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
id={`panel-${id}`}
|
||||
role="tabpanel"
|
||||
aria-labelledby={`tab-${id}`}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
// Arquivo de barril (barrel file) para exportar todos os componentes UI
|
||||
|
||||
// Exportar o componente Button
|
||||
export { Button } from './Button';
|
||||
|
||||
// Exportar o componente Card
|
||||
export { Card } from './Card';
|
||||
|
||||
// Exportar componentes de Tabs
|
||||
export { Tabs, TabList, Tab, TabPanel } from './Tabs';
|
||||
|
||||
// Exportar componentes de formulário
|
||||
export {
|
||||
TextInput,
|
||||
NumberInput,
|
||||
DateInput,
|
||||
SelectInput,
|
||||
TextAreaInput
|
||||
} from './FormInputs';
|
||||
Reference in new issue
Block a user