Pular para o conteúdo
🔧

Módulo 1 - Aula 2: Instalação e Configuração

Configurando o projeto e implementando o primeiro componente Cult UI

Duração
20 minutos
Dificuldade
Iniciante
Progresso
2/35 aulas

✅ 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

Estrutura do projeto
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:

lib/utils.ts
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

tailwind.config.js
/** @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

components/cult-ui/neumorph-button.tsx
'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

app/page.tsx
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:

components/cult-ui/neumorph-button.tsx
'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

components/examples/restaurant-dashboard.tsx
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