@techify/ui
v1.1.0
Published
Componentes React com Inline Reactions para formulários e botões
Readme
@techify/ui
Componentes React com Inline Reactions para feedback visual elegante
Início Rápido • Componentes • Personalização • API
✨ Características
- 🎭 Estados visuais claros:
idle,loading,success,error - 🎬 Animações suaves com Framer Motion
- 🎨 Totalmente personalizável - estilo shadcn/ui
- 🔄 Múltiplos modos de loading: skeleton, spinner, blur, pulse, dots, bars
- 🎛️ Provider global para configuração centralizada
- ♿ Acessível com suporte completo a ARIA
- 📦 Tree-shakeable - importe apenas o que precisa
- 🌙 Dark mode via CSS variables
- 🔧 TypeScript - tipagem completa
- 🎯 Tamanhos e variantes configuráveis
📦 Instalação
# pnpm (recomendado)
pnpm add @techify/ui framer-motion
# npm
npm install @techify/ui framer-motion
# yarn
yarn add @techify/ui framer-motion🚀 Início Rápido
1. Configure o Provider (opcional, mas recomendado)
import { TechifyUIProvider } from '@techify/ui';
import '@techify/ui/styles.css';
function App() {
return (
<TechifyUIProvider
config={{
loading: { mode: 'spinner' },
defaults: { size: 'md', variant: 'primary' },
}}
>
<YourApp />
</TechifyUIProvider>
);
}2. Use os componentes
import { FormWithInlineReaction, useInlineReaction, ContactFormSkeleton } from '@techify/ui';
function ContactForm() {
const { status, setLoading, setSuccess, setError, reset } = useInlineReaction();
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
setLoading();
try {
await submitContactForm();
setSuccess();
} catch {
setError();
}
};
return (
<FormWithInlineReaction
status={status}
onSubmit={handleSubmit}
onReset={reset}
loadingMode="spinner" // ou "skeleton", "blur", "pulse"
loadingSkeleton={<ContactFormSkeleton />}
successMessage="Mensagem enviada!"
errorMessage="Erro ao enviar"
>
<input type="text" name="name" placeholder="Nome" />
<input type="email" name="email" placeholder="Email" />
<button type="submit">Enviar</button>
</FormWithInlineReaction>
);
}📚 Componentes
FormWithInlineReaction
Formulário com transições suaves entre estados.
import { FormWithInlineReaction, useInlineReaction, FormSkeleton } from '@techify/ui';
function MyForm() {
const { status, setLoading, setSuccess, setError, reset } = useInlineReaction();
return (
<FormWithInlineReaction
status={status}
onSubmit={handleSubmit}
onReset={reset}
// Mensagens
successMessage="Formulário enviado com sucesso!"
errorMessage="Erro ao enviar. Tente novamente."
resetButtonText="Voltar ao formulário"
// Modo de loading
loadingMode="skeleton" // "skeleton" | "spinner" | "blur" | "pulse" | "dots" | "bars" | "custom"
loadingSkeleton={<FormSkeleton />}
loadingComponent={<CustomLoader />} // para loadingMode="custom"
// Estilos
className="my-custom-class"
styles={{
container: "...",
content: "...",
loading: "...",
success: "...",
error: "...",
icon: "...",
message: "...",
resetButton: "...",
}}
// Tema local (sobrescreve o Provider)
theme={{
colors: { success: '#10b981' },
icons: { success: <CustomIcon /> },
}}
>
{/* Conteúdo do formulário */}
</FormWithInlineReaction>
);
}ButtonWithInlineReaction
Botão com feedback visual inline.
import { ButtonWithInlineReaction, useInlineReaction } from '@techify/ui';
function SaveButton() {
const { status, setLoading, setSuccess, setError, reset } = useInlineReaction();
return (
<ButtonWithInlineReaction
status={status}
onClick={handleSave}
// Textos
loadingText="Salvando..."
successText="Salvo!"
errorText="Erro ao salvar"
// Variante e tamanho
variant="primary" // "primary" | "secondary" | "outline" | "ghost" | "destructive" | "success" | "warning"
size="md" // "xs" | "sm" | "md" | "lg" | "xl"
// Comportamento
type="button" // "button" | "submit" | "reset"
disabled={false}
fullWidth={false}
// Auto-reset
autoReset={true}
autoResetDelay={2000} // ms
onAutoReset={reset}
// Estilos
className="..."
styles={{ button: "..." }}
>
Salvar
</ButtonWithInlineReaction>
);
}InputWithInlineReaction
Input com validação visual em tempo real.
import { InputWithInlineReaction } from '@techify/ui';
import { useState } from 'react';
import type { ReactionStatus } from '@techify/ui';
import { Mail, Check } from 'lucide-react';
function EmailInput() {
const [status, setStatus] = useState<ReactionStatus>('idle');
const validateEmail = async (value: string) => {
if (!value) return setStatus('idle');
setStatus('loading');
const isValid = await checkEmail(value);
setStatus(isValid ? 'success' : 'error');
};
return (
<InputWithInlineReaction
type="email"
status={status}
onChange={validateEmail}
// Labels e mensagens
label="Email"
placeholder="[email protected]"
helperText="Digite seu melhor email"
successMessage="Email válido!"
errorMessage="Email inválido"
// Tamanho e variante
size="md" // "xs" | "sm" | "md" | "lg" | "xl"
variant="default" // "default" | "filled" | "outline"
// Ícones
leftIcon={<Mail size={16} />}
rightIcon={<Check size={16} />} // aparece quando status é idle
// Estilos
className="..."
styles={{
container: "...",
input: "...",
icon: "...",
message: "...",
}}
/>
);
}CardWithInlineReaction
Card com estados de feedback para operações assíncronas.
import { CardWithInlineReaction, useInlineReaction, Skeleton } from '@techify/ui';
function ProductCard({ product }) {
const { status, setLoading, setSuccess, setError, reset } = useInlineReaction();
return (
<CardWithInlineReaction
status={status}
onReset={reset}
// Mensagens
successMessage="Adicionado ao carrinho!"
errorMessage="Erro ao adicionar"
resetButtonText="Voltar"
// Loading
loadingMode="blur" // "skeleton" | "spinner" | "blur" | "pulse"
loadingSkeleton={<Skeleton className="h-48" />}
// Tamanho e estilo
size="md"
padding="md" // Size ou number (px)
shadow="md" // "none" | "sm" | "md" | "lg" | "xl"
border={true}
hoverable={true}
// Estilos
className="..."
styles={{
card: "...",
content: "...",
loading: "...",
success: "...",
error: "...",
}}
>
<img src={product.image} alt={product.name} />
<h3>{product.name}</h3>
<button onClick={handleAddToCart}>Adicionar</button>
</CardWithInlineReaction>
);
}FormBuilder (React Hook Form + Zod)
Gerador de formulários automático com validação em tempo real e inline reactions.
import { FormBuilder, useInlineReaction } from '@techify/ui';
import { useForm } from 'react-hook-form';
import { zodResolver } from '@hookform/resolvers/zod';
import { z } from 'zod';
import type { FormField } from '@techify/ui';
// Schema Zod
const schema = z.object({
name: z.string().min(2, 'Nome muito curto'),
email: z.string().email('Email inválido'),
message: z.string().min(10, 'Mensagem muito curta'),
});
type FormData = z.infer<typeof schema>;
function ContactForm() {
const { status, setLoading, setSuccess, setError, reset } = useInlineReaction();
const form = useForm<FormData>({
resolver: zodResolver(schema),
mode: 'onChange', // Importante para validação em tempo real
});
const fields: FormField<FormData>[] = [
{
name: 'name',
type: 'text',
label: 'Nome',
placeholder: 'Seu nome',
required: true,
successMessage: 'Nome válido ✓', // Mostrado quando válido
},
{
name: 'email',
type: 'email',
label: 'Email',
placeholder: '[email protected]',
required: true,
successMessage: 'Email válido ✓',
},
{
name: 'message',
type: 'textarea',
label: 'Mensagem',
placeholder: 'Sua mensagem...',
required: true,
rows: 4,
colSpan: 'full',
successMessage: 'Mensagem válida ✓',
},
];
const onSubmit = async (data: FormData) => {
setLoading();
try {
await sendMessage(data);
setSuccess();
form.reset();
} catch {
setError();
}
};
return (
<FormBuilder
form={form}
fields={fields}
onSubmit={onSubmit}
status={status}
onReset={reset}
// Layout
columns={2} // 1 | 2 | 3 | 4 | 6 | 12
gap="md" // "xs" | "sm" | "md" | "lg" | "xl"
// Textos
submitText="Enviar"
loadingText="Enviando..."
resetText="Limpar"
showResetButton={true}
// Mensagens de feedback
successMessage="Enviado com sucesso! 🎉"
errorMessage="Erro ao enviar. Tente novamente."
// 🆕 Validação em tempo real com inline reaction
validationMode="inline-reaction" // "default" | "inline-reaction" | "both"
validateDebounce={300} // ms
// Customização de inputs
inputBorderRadius="md" // "none" | "sm" | "md" | "lg" | "xl" | "full"
inputBorderWidth="1px"
// Estilos
successColor="#10b981"
successBackgroundColor="#ecfdf5"
/>
);
}Modos de Validação
| Modo | Descrição |
|------|-----------|
| 'default' | Mostra erros em texto abaixo do campo |
| 'inline-reaction' | Mostra ícones de loading/success/error no campo |
| 'both' | Mostra ambos (ícones + texto) |
Tipos de Campo Suportados
text, email, password, number, tel, url, textarea, select, checkbox, radio, switch, date, time, datetime-local, file, hidden, custom
InlineReaction (Standalone)
Componente de reação standalone para uso em qualquer contexto.
import { InlineReaction, useInlineReaction } from '@techify/ui';
function StatusDisplay() {
const { status, setLoading, setSuccess, setError, reset } = useInlineReaction();
return (
<div>
{status === 'idle' ? (
<div>Conteúdo normal</div>
) : (
<InlineReaction
status={status}
onReset={reset}
// Mensagens
successMessage="Operação concluída!"
errorMessage="Algo deu errado"
loadingMessage="Processando..."
resetButtonText="Voltar"
// Exibição
showResetButton={true}
size="md" // "xs" | "sm" | "md" | "lg" | "xl"
// Estilos
className="..."
styles={{
container: "...",
icon: "...",
message: "...",
resetButton: "...",
loading: "...",
success: "...",
error: "...",
}}
/>
)}
</div>
);
}📱 Componentes Responsivos
ResponsiveModal
Componente inteligente que usa Modal no desktop e Drawer no mobile automaticamente.
import { ResponsiveModal, useResponsiveModal } from '@techify/ui';
function MyComponent() {
const { open, setOpen, isMobile } = useResponsiveModal();
return (
<>
<button onClick={() => setOpen(true)}>
{isMobile ? 'Abrir Drawer' : 'Abrir Modal'}
</button>
<ResponsiveModal
open={open}
onOpenChange={setOpen}
title="Modal Responsivo"
description="Adapta-se automaticamente ao dispositivo"
// Configuração do Drawer (mobile)
drawerSide="bottom" // "top" | "bottom" | "left" | "right"
showHandle={true}
draggable={true}
// Breakpoint customizado
mobileBreakpoint={768}
// Forçar modo específico
forceMode={undefined} // "modal" | "drawer" | undefined (auto)
footer={
<>
<button onClick={() => setOpen(false)}>Cancelar</button>
<button onClick={handleConfirm}>Confirmar</button>
</>
}
>
<p>Conteúdo do modal/drawer</p>
</ResponsiveModal>
</>
);
}Modal
Modal para desktop com animações suaves.
import { Modal } from '@techify/ui';
import { useState } from 'react';
function DesktopModal() {
const [open, setOpen] = useState(false);
return (
<>
<button onClick={() => setOpen(true)}>Abrir Modal</button>
<Modal
open={open}
onOpenChange={setOpen}
title="Título do Modal"
description="Descrição opcional"
size="md" // "xs" | "sm" | "md" | "lg" | "xl" | "full"
showCloseButton={true}
closeOnOverlayClick={true}
closeOnEscape={true}
footer={<button onClick={() => setOpen(false)}>Fechar</button>}
>
<p>Conteúdo do modal</p>
</Modal>
</>
);
}Drawer
Drawer/Sheet com suporte a arraste para fechar.
import { Drawer } from '@techify/ui';
import { useState } from 'react';
function MobileDrawer() {
const [open, setOpen] = useState(false);
return (
<>
<button onClick={() => setOpen(true)}>Abrir Menu</button>
<Drawer
open={open}
onOpenChange={setOpen}
side="bottom" // "top" | "bottom" | "left" | "right"
size="md" // "xs" | "sm" | "md" | "lg" | "xl" | "full"
title="Menu"
// Recursos mobile
showHandle={true} // Mostra handle de arraste
draggable={true} // Permite arrastar para fechar
closeOnOverlayClick={true}
closeOnEscape={true}
footer={<button onClick={() => setOpen(false)}>Fechar</button>}
>
<nav>
<a href="#">Link 1</a>
<a href="#">Link 2</a>
<a href="#">Link 3</a>
</nav>
</Drawer>
</>
);
}useIsMobile Hook
Hook para detectar se o dispositivo é mobile.
import { useIsMobile, MOBILE_BREAKPOINT } from '@techify/ui';
function ResponsiveComponent() {
const isMobile = useIsMobile(); // Padrão: 768px
const isTablet = useIsMobile(1024); // Breakpoint customizado
return (
<div>
{isMobile ? (
<MobileLayout />
) : (
<DesktopLayout />
)}
</div>
);
}🔄 Modos de Loading
A biblioteca oferece 6 modos de loading diferentes:
import {
LoadingSpinner,
LoadingBlur,
LoadingPulse,
LoadingDots,
LoadingBars,
LoadingRenderer
} from '@techify/ui';
// Uso direto dos componentes
<LoadingSpinner size="md" text="Carregando..." showText />
<LoadingBlur intensity={8}>{children}</LoadingBlur>
<LoadingPulse>{children}</LoadingPulse>
<LoadingDots />
<LoadingBars />
// Renderizador automático baseado no modo
<LoadingRenderer
mode="spinner" // "skeleton" | "spinner" | "blur" | "pulse" | "dots" | "bars" | "custom"
skeleton={<MySkeleton />}
customComponent={<MyCustomLoader />}
spinnerSize="md"
spinnerColor="#3b82f6"
blurIntensity={8}
text="Carregando..."
showText
>
{children}
</LoadingRenderer>🎣 Hook useInlineReaction
import { useInlineReaction } from '@techify/ui';
function MyComponent() {
const {
// Estado
status, // 'idle' | 'loading' | 'success' | 'error'
// Setters
setStatus, // (status: ReactionStatus) => void
setLoading, // () => void
setSuccess, // () => void
setError, // () => void
reset, // () => void
resetStatus, // Alias para reset
// Helpers booleanos
isIdle, // status === 'idle'
isLoading, // status === 'loading'
isSuccess, // status === 'success'
isError, // status === 'error'
} = useInlineReaction({
// Opções
initialStatus: 'idle',
autoResetDelay: 3000, // Auto-reset após sucesso/erro (ms)
onStatusChange: (status) => console.log('Status:', status),
});
}🎨 Personalização
Provider Global (estilo shadcn)
Configure uma vez, use em todos os componentes:
import { TechifyUIProvider } from '@techify/ui';
function App() {
return (
<TechifyUIProvider
config={{
// Cores
colors: {
success: '#22c55e',
error: '#ef4444',
loading: '#3b82f6',
warning: '#f59e0b',
primary: '#6366f1',
primaryForeground: '#ffffff',
secondary: '#f1f5f9',
secondaryForeground: '#0f172a',
destructive: '#ef4444',
background: '#ffffff',
foreground: '#0f172a',
muted: '#f1f5f9',
mutedForeground: '#64748b',
border: '#e2e8f0',
},
// Ícones customizados
icons: {
success: <CheckCircle className="w-12 h-12" />,
error: <XCircle className="w-12 h-12" />,
loading: <Loader2 className="w-12 h-12 animate-spin" />,
warning: <AlertTriangle className="w-12 h-12" />,
},
// Animações
animation: {
fadeVariants: {
hidden: { opacity: 0, filter: 'blur(8px)' },
visible: { opacity: 1, filter: 'blur(0px)' },
exit: { opacity: 0, filter: 'blur(8px)' },
},
defaultTransition: { duration: 0.25, ease: 'easeOut' },
feedbackTransition: { duration: 0.3, ease: 'easeOut' },
enabled: true,
},
// Loading
loading: {
mode: 'spinner', // Modo padrão
text: 'Carregando...',
showText: false,
spinnerSize: 'md',
blurIntensity: 8,
},
// Feedback
feedback: {
showIcon: true,
showMessage: true,
showResetButton: true,
resetButtonText: 'Voltar',
autoReset: false,
autoResetDelay: 3000,
},
// Valores padrão
defaults: {
size: 'md',
variant: 'primary',
loadingMode: 'skeleton',
},
// 🆕 Configuração de inputs
input: {
borderRadius: '0.5rem',
borderWidth: '1px',
backgroundColor: 'transparent',
focusRingWidth: '2px',
focusRingOffset: '2px',
focusRingColor: 'var(--tui-ring)',
},
// 🆕 Configuração de botões
button: {
borderRadius: '0.5rem',
},
// 🆕 Fonte global
fontFamily: 'inherit', // Herda do projeto
// Estilos globais
styles: {
container: '',
content: '',
loading: '',
success: '',
error: '',
icon: '',
message: '',
resetButton: '',
button: '',
input: '',
card: '',
form: '',
},
}}
>
<YourApp />
</TechifyUIProvider>
);
}CSS Variables
/* Variáveis disponíveis dentro de [data-tui] */
[data-tui] {
/* Cores principais */
--tui-success: #22c55e;
--tui-error: #ef4444;
--tui-loading: #3b82f6;
--tui-warning: #f59e0b;
/* Cores de UI */
--tui-background: #ffffff;
--tui-foreground: #0f172a;
--tui-muted: #f1f5f9;
--tui-muted-foreground: #64748b;
--tui-border: #e2e8f0;
--tui-ring: #3b82f6;
/* Cores de componentes */
--tui-primary: #3b82f6;
--tui-primary-foreground: #ffffff;
--tui-secondary: #f1f5f9;
--tui-secondary-foreground: #0f172a;
--tui-destructive: #ef4444;
--tui-destructive-foreground: #ffffff;
/* 🆕 Bordas e raios */
--tui-input-radius: 0.375rem;
--tui-button-radius: 0.375rem;
--tui-border-width: 1px;
/* 🆕 Focus ring */
--tui-focus-ring-width: 2px;
--tui-focus-ring-offset: 2px;
--tui-focus-ring-color: var(--tui-ring);
/* 🆕 Fonte */
--tui-font-family: inherit;
}
/* Dark Mode */
.dark {
--tui-success: #4ade80;
--tui-error: #f87171;
--tui-loading: #60a5fa;
--tui-warning: #fbbf24;
--tui-background: #0f172a;
--tui-foreground: #f1f5f9;
--tui-muted: #1e293b;
--tui-muted-foreground: #94a3b8;
--tui-border: #334155;
--tui-primary: #60a5fa;
--tui-secondary: #334155;
}Tema por Componente
Sobrescreva configurações localmente:
<ButtonWithInlineReaction
status={status}
onClick={onClick}
theme={{
colors: { success: '#10b981' },
icons: { success: <Heart /> },
defaults: { size: 'lg' },
}}
>
Curtir
</ButtonWithInlineReaction>🦴 Skeletons Prontos
import {
// Primitivos
Skeleton,
SkeletonInput,
SkeletonLabel,
SkeletonTextarea,
SkeletonButton,
SkeletonSwitch,
SkeletonFormField,
// Formulários completos
FormSkeleton,
ContactFormSkeleton,
BlogFormSkeleton,
ShowcaseFormSkeleton,
LearningFormSkeleton,
DevLogFormSkeleton,
CertificationFormSkeleton,
} from '@techify/ui';
// Uso
<FormWithInlineReaction
status={status}
loadingSkeleton={<ContactFormSkeleton />}
>
{/* ... */}
</FormWithInlineReaction>📋 API Reference
Tipos
import type {
// Estados e modos
ReactionStatus, // 'idle' | 'loading' | 'success' | 'error'
LoadingMode, // 'skeleton' | 'spinner' | 'blur' | 'pulse' | 'dots' | 'bars' | 'custom'
Size, // 'xs' | 'sm' | 'md' | 'lg' | 'xl'
Variant, // 'primary' | 'secondary' | 'outline' | 'ghost' | 'destructive' | 'success' | 'warning'
// Configuração
TechifyUIConfig,
ColorConfig,
IconConfig,
AnimationOptions,
LoadingConfig,
FeedbackConfig,
ComponentStyles,
DefaultsConfig,
InputConfig, // 🆕 Configuração de inputs
ButtonConfig, // 🆕 Configuração de botões
// Props de componentes
FormWithInlineReactionProps,
ButtonWithInlineReactionProps,
InlineReactionProps,
InputWithInlineReactionProps,
CardWithInlineReactionProps,
TechifyUIProviderProps,
// 🆕 FormBuilder
FormBuilderProps, // Props do FormBuilder
FormField, // Configuração de campo
FieldType, // Tipos de campo suportados
FieldOption, // Opção para select/radio
// Componentes Responsivos
ModalProps, // Props do Modal
DrawerProps, // Props do Drawer
DrawerSide, // 'top' | 'bottom' | 'left' | 'right'
ResponsiveModalProps, // Props do ResponsiveModal
// Hook
UseInlineReactionReturn,
UseInlineReactionOptions,
// Animações (Framer Motion)
AnimationVariants,
AnimationConfig,
} from '@techify/ui';Utilitários
import {
// Classes
cn, // Combina classes (clsx + tailwind-merge)
// Animações
fadeVariants, // Variantes fade + blur
scaleVariants, // Variantes scale
slideVariants, // Variantes slide
defaultTransition, // Transição padrão
feedbackTransition, // Transição para feedback
// Context
TechifyUIProvider, // Provider
TechifyUIContext, // Context (para uso avançado)
useTechifyUI, // Hook para acessar config global
useConfig, // Hook para merge config local + global
defaultConfig, // Configuração padrão
// Hooks responsivos
useIsMobile, // Hook para detectar mobile
useResponsiveModal, // Hook auxiliar para ResponsiveModal
MOBILE_BREAKPOINT, // Breakpoint padrão (768px)
// 🆕 FormBuilder
FormBuilder, // Gerador de formulários
} from '@techify/ui';Dependências Opcionais (para FormBuilder)
Para usar o FormBuilder com validação Zod, instale:
pnpm add react-hook-form zod @hookform/resolvers🔗 Peer Dependencies
| Pacote | Versão |
|--------|--------|
| react | >= 18.0.0 |
| react-dom | >= 18.0.0 |
| framer-motion | >= 10.0.0 |
📄 Licença
MIT © Techify
Feito com ❤️ pela equipe Techify
