Módulo 1 - Aula 2: Instalação e Configuração
Configurando o projeto e implementando o primeiro componente Cult UI
✅ O que você vai aprender:
- • Entender a filosofia "copy-paste" do Cult UI
- • Configurar utilitários avançados (cn, animations)
- • Implementar seu primeiro componente prático
- • Testar o componente com live preview
- • Aplicar em exemplo de SaaS de restaurante
📦 Filosofia Copy-Paste do Cult UI
🎯 Por que Cult UI não é um pacote NPM?
Controle total: Você possui 100% do código. Pode modificar, adaptar e evoluir sem depender de atualizações externas.
Sem bloat: Apenas os componentes que você usa. Não há código desnecessário no seu bundle final.
Customização: Cada componente pode ser totalmente personalizado para suas necessidades específicas.
🔧 Estrutura de pastas para Cult UI
src/
├── components/
│ ├── ui/ # Componentes base (shadcn/ui style)
│ │ ├── button.tsx
│ │ ├── card.tsx
│ │ └── ...
│ ├── cult-ui/ # Componentes do Cult UI
│ │ ├── neumorph-button.tsx
│ │ ├── family-button.tsx
│ │ ├── expandable-card.tsx
│ │ └── index.ts # Exports centralizados
│ └── examples/ # Exemplos de uso
│ ├── restaurant-saas/
│ └── dashboard/
├── lib/
│ └── utils.ts # Utilitários (cn, animations)
└── app/
└── ...🎨 Utilitários avançados
💡 Função cn() aprimorada
Vamos expandir nossa função de utilitários para incluir variantes e animações:
import clsx, { ClassValue } from "clsx"
import { twMerge } from "tailwind-merge"
// 🎯 Função principal para combinar classes
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs))
}
// 🎭 Sistema de animações do Cult UI
export const animations = {
// Entrada
fadeInUp: 'animate-fade-in-up opacity-0 translate-y-8',
fadeIn: 'animate-fade-in opacity-0',
slideInLeft: 'animate-slide-in-left opacity-0 -translate-x-8',
slideInRight: 'animate-slide-in-right opacity-0 translate-x-8',
slideInDown: 'animate-slide-in-down opacity-0 -translate-y-8',
// Hover states
hoverScale: 'hover:scale-105 transition-transform duration-200',
hoverGlow: 'hover:shadow-lg hover:shadow-lime-500/25 transition-shadow duration-300',
// Loading
spin: 'animate-spin',
pulse: 'animate-pulse',
bounce: 'animate-bounce',
}
// 🎨 Variantes de cores do Cult UI
export const colorVariants = {
primary: {
bg: 'bg-lime-600',
text: 'text-lime-600',
border: 'border-lime-600',
hover: 'hover:bg-lime-500',
},
secondary: {
bg: 'bg-blue-600',
text: 'text-blue-600',
border: 'border-blue-600',
hover: 'hover:bg-blue-500',
},
danger: {
bg: 'bg-red-600',
text: 'text-red-600',
border: 'border-red-600',
hover: 'hover:bg-red-500',
},
success: {
bg: 'bg-green-600',
text: 'text-green-600',
border: 'border-green-600',
hover: 'hover:bg-green-500',
},
}
// 🔧 Utilitário para delays de animação
export function getAnimationDelay(index: number, baseDelay: number = 0.1) {
return `${baseDelay + (index * 0.1)}s`;
}
// 🎯 Utilitário para responsividade
export const breakpoints = {
sm: '640px',
md: '768px',
lg: '1024px',
xl: '1280px',
'2xl': '1536px',
}⚡ Tailwind Config para Cult UI
/** @type {import('tailwindcss').Config} */
module.exports = {
content: [
'./pages/**/*.{js,ts,jsx,tsx,mdx}',
'./components/**/*.{js,ts,jsx,tsx,mdx}',
'./app/**/*.{js,ts,jsx,tsx,mdx}',
],
theme: {
extend: {
// 🎨 Animações customizadas para Cult UI
animation: {
'fade-in-up': 'fadeInUp 0.5s ease-out forwards',
'fade-in': 'fadeIn 0.3s ease-out forwards',
'slide-in-left': 'slideInLeft 0.5s ease-out forwards',
'slide-in-right': 'slideInRight 0.5s ease-out forwards',
'slide-in-down': 'slideInDown 0.5s ease-out forwards',
'float': 'float 3s ease-in-out infinite',
},
keyframes: {
fadeInUp: {
'0%': { opacity: '0', transform: 'translateY(20px)' },
'100%': { opacity: '1', transform: 'translateY(0)' },
},
fadeIn: {
'0%': { opacity: '0' },
'100%': { opacity: '1' },
},
slideInLeft: {
'0%': { opacity: '0', transform: 'translateX(-20px)' },
'100%': { opacity: '1', transform: 'translateX(0)' },
},
slideInRight: {
'0%': { opacity: '0', transform: 'translateX(20px)' },
'100%': { opacity: '1', transform: 'translateX(0)' },
},
slideInDown: {
'0%': { opacity: '0', transform: 'translateY(-20px)' },
'100%': { opacity: '1', transform: 'translateY(0)' },
},
float: {
'0%, 100%': { transform: 'translateY(0px)' },
'50%': { transform: 'translateY(-10px)' },
},
},
// 🎭 Sombras neumórficas
boxShadow: {
'neumorph': '8px 8px 16px #1a1a1a, -8px -8px 16px #2a2a2a',
'neumorph-hover': '4px 4px 8px #1a1a1a, -4px -4px 8px #2a2a2a',
'neumorph-active': 'inset 4px 4px 8px #1a1a1a, inset -4px -4px 8px #2a2a2a',
},
},
},
plugins: [],
}✅ Configuração completa!
Agora você tem um setup robusto para trabalhar com Cult UI:
- • Sistema de utilitários avançado (cn, animations, colors)
- • Animações customizadas no Tailwind
- • Estrutura de pastas organizada
- • Sombras neumórficas pré-configuradas
- • Pronto para implementar qualquer componente Cult UI
🎨 Primeiro Componente: Neumorph Button
🎯 Por que começar com botões?
Botões são fundamentais em qualquer interface. O Neumorph Button do Cult UI demonstra conceitos importantes como variantes, estados e animações suaves.
📋 Implementação completa
'use client';
import { cn } from '@/lib/utils';
import { ButtonHTMLAttributes, forwardRef } from 'react';
// 🎯 Definindo as variantes do botão
interface NeumorphButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> {
variant?: 'default' | 'primary' | 'secondary' | 'danger';
size?: 'sm' | 'md' | 'lg';
isLoading?: boolean;
fullWidth?: boolean;
children: React.ReactNode;
}
// 🎨 Estilos base para efeito neumórfico
const baseStyles = cn(
'relative inline-flex items-center justify-center',
'font-medium transition-all duration-200',
'focus:outline-none focus:ring-2 focus:ring-offset-2',
'disabled:opacity-50 disabled:cursor-not-allowed',
'shadow-[8px_8px_16px_#1a1a1a,-8px_-8px_16px_#2a2a2a]',
'hover:shadow-[4px_4px_8px_#1a1a1a,-4px_-4px_8px_#2a2a2a]',
'active:shadow-[inset_4px_4px_8px_#1a1a1a,inset_-4px_-4px_8px_#2a2a2a]'
);
// 🎭 Variantes de cor
const variants = {
default: cn(
'bg-bg-2 text-fg-muted border border-border',
'hover:bg-bg-3 hover:text-foreground',
'focus:ring-gray-500'
),
primary: cn(
'bg-lime-600 text-black border border-lime-500',
'hover:bg-lime-500 hover:text-black',
'focus:ring-lime-400'
),
secondary: cn(
'bg-blue-600 text-foreground border border-blue-500',
'hover:bg-blue-500',
'focus:ring-blue-400'
),
danger: cn(
'bg-red-600 text-foreground border border-red-500',
'hover:bg-red-500',
'focus:ring-red-400'
),
};
// 📏 Tamanhos disponíveis
const sizes = {
sm: 'px-3 py-1.5 text-sm rounded-md',
md: 'px-4 py-2 text-base rounded-lg',
lg: 'px-6 py-3 text-lg rounded-xl',
};
export const NeumorphButton = forwardRef<HTMLButtonElement, NeumorphButtonProps>(
({
className,
variant = 'default',
size = 'md',
isLoading = false,
fullWidth = false,
children,
disabled,
...props
}, ref) => {
return (
<button
className={cn(
baseStyles,
variants[variant],
sizes[size],
fullWidth && 'w-full',
className
)}
disabled={disabled || isLoading}
ref={ref}
{...props}
>
{/* 🔄 Loading spinner */}
{isLoading && (
<div className="mr-2 animate-spin">
<div className="w-4 h-4 border-2 border-current border-t-transparent rounded-full" />
</div>
)}
{/* 📝 Conteúdo do botão */}
<span className={cn(isLoading && 'opacity-70')}>
{children}
</span>
</button>
);
}
);
NeumorphButton.displayName = 'NeumorphButton';🔍 Conceitos importantes
Neumorfismo:
Efeito visual que simula botões "pressionados" ou "elevados" usando sombras internas e externas.
forwardRef:
Permite que o componente passe referências para o elemento DOM, essencial para integração com formulários.
Variantes:
Sistema para diferentes estilos do mesmo componente, mantendo consistência visual.
Estados:
Loading, disabled, hover, active - todos os estados interativos tratados adequadamente.
🎨 Exemplo de uso
import { NeumorphButton } from '@/components/cult-ui/neumorph-button';
export default function HomePage() {
return (
<div className="p-8 space-y-4">
{/* 🎯 Botão padrão */}
<NeumorphButton onClick={() => console.log('Clicou!')}>
Botão Padrão
</NeumorphButton>
{/* 🎨 Botão primário */}
<NeumorphButton variant="primary" size="lg">
Ação Principal
</NeumorphButton>
{/* 🔄 Botão com loading */}
<NeumorphButton variant="secondary" isLoading>
Carregando...
</NeumorphButton>
{/* 📏 Botão full width */}
<NeumorphButton variant="danger" fullWidth>
Deletar Conta
</NeumorphButton>
</div>
);
}🧪 Teste e Implementação
Neumorph Button - Live Preview
Teste todas as variantes e estados do componente
Variantes:
Tamanhos:
Estados:
'use client';
import { cn } from '@/lib/utils';
import { ButtonHTMLAttributes, forwardRef } from 'react';
import { motion } from 'framer-motion';
interface NeumorphButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> {
variant?: 'default' | 'primary' | 'secondary' | 'danger';
size?: 'sm' | 'md' | 'lg';
isLoading?: boolean;
fullWidth?: boolean;
children: React.ReactNode;
}
const baseStyles = cn(
'relative inline-flex items-center justify-center',
'font-medium transition-all duration-200',
'focus:outline-none focus:ring-2 focus:ring-offset-2',
'disabled:opacity-50 disabled:cursor-not-allowed',
'shadow-neumorph hover:shadow-neumorph-hover',
'active:shadow-neumorph-active'
);
const variants = {
default: 'bg-bg-2 text-fg-muted border border-border hover:bg-bg-3',
primary: 'bg-lime-600 text-black border border-lime-500 hover:bg-lime-500',
secondary: 'bg-blue-600 text-foreground border border-blue-500 hover:bg-blue-500',
danger: 'bg-red-600 text-foreground border border-red-500 hover:bg-red-500',
};
const sizes = {
sm: 'px-3 py-1.5 text-sm rounded-md',
md: 'px-4 py-2 text-base rounded-lg',
lg: 'px-6 py-3 text-lg rounded-xl',
};
export const NeumorphButton = forwardRef<HTMLButtonElement, NeumorphButtonProps>(
({ className, variant = 'default', size = 'md', isLoading = false, fullWidth = false, children, disabled, ...props }, ref) => {
return (
<motion.button
whileHover={{ scale: 1.02 }}
whileTap={{ scale: 0.98 }}
className={cn(
baseStyles,
variants[variant],
sizes[size],
fullWidth && 'w-full',
className
)}
disabled={disabled || isLoading}
ref={ref}
{...props}
>
{isLoading && (
<div className="mr-2 animate-spin">
<div className="w-4 h-4 border-2 border-current border-t-transparent rounded-full" />
</div>
)}
<span className={cn(isLoading && 'opacity-70')}>
{children}
</span>
</motion.button>
);
}
);
NeumorphButton.displayName = 'NeumorphButton';🏪 Aplicação prática: Dashboard de Restaurante
Dashboard de Restaurante - Seção de Pedidos
Exemplo real de uso do Neumorph Button em um SaaS de restaurante
Pedidos Ativos
Mesa 12, 15, 8
import { NeumorphButton } from '@/components/cult-ui/neumorph-button';
export function RestaurantDashboard() {
return (
<div className="p-6 space-y-4">
<NeumorphButton variant="primary" size="lg">
Novo Pedido
</NeumorphButton>
<NeumorphButton variant="secondary">
Ver Cardápio
</NeumorphButton>
<NeumorphButton variant="danger" size="sm">
Cancelar Pedido
</NeumorphButton>
</div>
);
}💡 Dicas de implementação
Performance: Use React.memo() para componentes que re-renderizam frequentemente.
Acessibilidade: O componente já inclui focus states e aria attributes.
Customização: Modifique as variantes e tamanhos conforme seu design system.
Mobile: Use tamanhos 'sm' e 'md' para interfaces mobile.
✅ Primeiro componente implementado!
Parabéns! Você acabou de implementar seu primeiro componente Cult UI e aplicar em um cenário real.
- • NeumorphButton funcional com todas as variantes
- • Live preview implementado
- • Exemplo prático de SaaS de restaurante
- • Entendimento da filosofia copy-paste