Pular para o conteúdo
🎨

Módulo 1 - Aula 3: Primeiro Componente

Implementando um componente Cult UI completo com live preview e aplicação prática

Duração
25 minutos
Dificuldade
Intermediário
Progresso
3/35 aulas

✅ O que você vai dominar:

  • • Implementar um componente Cult UI completo e funcional
  • • Entender todos os estados e variantes do componente
  • • Usar o live preview para testar em tempo real
  • • Aplicar o componente em interface real de restaurante
  • • Criar suas próprias variações e customizações

🎨 Componente Neumorph Button Completo

🔍 Anatomia do componente

Funcionalidades:

  • • forwardRef para referências DOM
  • • Framer Motion para animações
  • • Sistema de variantes dinâmico
  • • Loading states gerenciados
  • • Acessibilidade nativa

Características:

  • • Efeito neumórfico com sombras
  • • Animações de hover e tap
  • • Responsivo e customizável
  • • TypeScript type-safe
  • • Performance otimizada

Neumorph Button - Todos os Estados

Teste interativo com todas as variantes, tamanhos e estados

Variantes disponíveis:

Tamanhos disponíveis:

Estados interativos:

Layout responsivo:

components/cult-ui/neumorph-button.tsx
'use client';

import { cn } from '@/lib/utils';
import { forwardRef } from 'react';
import { motion, HTMLMotionProps } from 'framer-motion';

// 🎯 Interface completa do componente
interface NeumorphButtonProps extends Omit<HTMLMotionProps<'button'>, 'ref'> {
  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-neumorph hover:shadow-neumorph-hover',
  'active:shadow-neumorph-active'
);

// 🎭 Variantes de cores
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 (
      <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}
      >
        {/* 🔄 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>
      </motion.button>
    );
  }
);

NeumorphButton.displayName = 'NeumorphButton';

🔧 Customizações avançadas

💡 Estendendo o componente

Você pode criar variações personalizadas do NeumorphButton para casos específicos:

components/cult-ui/custom-buttons.tsx
// 🎨 Exemplo de customização avançada
const CustomNeumorphButton = forwardRef<HTMLButtonElement, NeumorphButtonProps>(
  ({ className, ...props }, ref) => {
    return (
      <NeumorphButton
        className={cn(
          // Customizações específicas
          'transition-all duration-300',
          'hover:shadow-lg hover:shadow-lime-500/20',
          'active:transform active:scale-95',
          className
        )}
        ref={ref}
        {...props}
      />
    );
  }
);

// 🎯 Variante customizada para notificações
const NotificationButton = ({ count, ...props }) => {
  return (
    <div className="relative">
      <NeumorphButton {...props} />
      {count > 0 && (
        <span className="absolute -top-2 -right-2 bg-red-500 text-foreground text-xs rounded-full w-5 h-5 flex items-center justify-center">
          {count}
        </span>
      )}
    </div>
  );
};

⚡ Dicas de performance

React.memo: Use para componentes que re-renderizam frequentemente com as mesmas props.

useCallback: Memoize funções onClick para evitar re-renders desnecessários.

Framer Motion: As animações são otimizadas automaticamente para 60fps.

CSS-in-JS: Classes Tailwind são compiladas em tempo de build, sem overhead runtime.

🏪 Aplicação Real: SaaS de Restaurante

🎯 Cenário da aplicação

RestaurantePro: Sistema completo de gestão para restaurantes. O NeumorphButton é usado em todas as interações principais.

Funcionalidades: Dashboard com métricas, gestão de pedidos, controle de cardápio e notificações em tempo real.

UX/UI: Interface moderna com efeitos neumórficos que transmitem profissionalismo e confiabilidade aos usuários.

RestaurantePro - Sistema Completo

Dashboard funcional de um SaaS de restaurante usando Neumorph Buttons

RestaurantePro

Dashboard Administrativo

3
+12%

47

Clientes Ativos

+8%

23

Pedidos Hoje

+23%

R$ 2.847

Faturamento

components/examples/restaurant-dashboard.tsx
'use client';

import { NeumorphButton } from '@/components/cult-ui/neumorph-button';
import { ChefHat, Users, TrendingUp, Clock, MapPin, Star } from 'lucide-react';

export function RestaurantDashboard() {
  const [activeTab, setActiveTab] = useState('dashboard');
  const [notifications, setNotifications] = useState(3);
  const [isProcessing, setIsProcessing] = useState(false);

  const handleProcessOrder = () => {
    setIsProcessing(true);
    setTimeout(() => {
      setIsProcessing(false);
      setNotifications(prev => prev + 1);
    }, 1500);
  };

  return (
    <div className="min-h-screen bg-bg-1 p-6">
      <div className="max-w-7xl mx-auto">
        
        {/* 🎯 Header com navegação */}
        <div className="mb-8">
          <div className="flex items-center justify-between mb-6">
            <div className="flex items-center gap-3">
              <div className="w-10 h-10 bg-lime-500/20 rounded-lg flex items-center justify-center">
                <ChefHat className="w-6 h-6 text-lime-400" />
              </div>
              <div>
                <h1 className="text-foreground text-2xl font-bold">RestaurantePro</h1>
                <p className="text-fg-muted text-sm">Dashboard Administrativo</p>
              </div>
            </div>
            
            <div className="flex items-center gap-3">
              <div className="relative">
                <NeumorphButton variant="secondary" size="sm">
                  <span className="w-4 h-4 mr-2">🔔</span>
                  Notificações
                </NeumorphButton>
                {notifications > 0 && (
                  <span className="absolute -top-2 -right-2 bg-red-500 text-foreground text-xs rounded-full w-5 h-5 flex items-center justify-center">
                    {notifications}
                  </span>
                )}
              </div>
              <NeumorphButton variant="primary">
                + Novo Pedido
              </NeumorphButton>
            </div>
          </div>
          
          {/* 🎛️ Tabs de navegação */}
          <div className="flex gap-2">
            {[
              { id: 'dashboard', label: 'Dashboard', icon: TrendingUp },
              { id: 'pedidos', label: 'Pedidos', icon: Clock },
              { id: 'cardapio', label: 'Cardápio', icon: ChefHat }
            ].map((tab) => (
              <NeumorphButton
                key={tab.id}
                variant={activeTab === tab.id ? 'primary' : 'default'}
                size="sm"
                onClick={() => setActiveTab(tab.id)}
              >
                <tab.icon className="w-4 h-4 mr-2" />
                {tab.label}
              </NeumorphButton>
            ))}
          </div>
        </div>

        {/* 📊 Conteúdo das tabs */}
        {activeTab === 'dashboard' && (
          <div className="grid grid-cols-1 md:grid-cols-3 gap-6">
            <StatsCard
              icon={Users}
              title="Clientes Ativos"
              value="47"
              change="+12%"
              color="lime"
            />
            <StatsCard
              icon={Clock}
              title="Pedidos Hoje"
              value="23"
              change="+8%"
              color="blue"
            />
            <StatsCard
              icon={TrendingUp}
              title="Faturamento"
              value="R$ 2.847"
              change="+23%"
              color="green"
            />
          </div>
        )}

        {activeTab === 'pedidos' && (
          <div className="space-y-4">
            <OrderCard
              id="PED-001"
              table="Mesa 12"
              items={["Pizza Margherita", "Coca-Cola 350ml"]}
              total="R$ 45,90"
              status="preparing"
              onProcess={handleProcessOrder}
              isProcessing={isProcessing}
            />
            <OrderCard
              id="PED-002"
              table="Mesa 8"
              items={["Hambúrguer Gourmet", "Batata Frita"]}
              total="R$ 32,50"
              status="ready"
            />
          </div>
        )}

        {activeTab === 'cardapio' && (
          <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
            <MenuCard
              name="Pizza Margherita"
              description="Molho de tomate, mussarela, manjericão"
              price="R$ 35,90"
              image="🍕"
            />
            <MenuCard
              name="Hambúrguer Gourmet"
              description="Carne artesanal, queijo, alface, tomate"
              price="R$ 28,50"
              image="🍔"
            />
          </div>
        )}
      </div>
    </div>
  );
}

// 📊 Componente de estatísticas
function StatsCard({ icon: Icon, title, value, change, color }) {
  return (
    <div className="bg-bg-2/80 border border-border rounded-lg p-6">
      <div className="flex items-center justify-between mb-4">
        <div className={`w-10 h-10 rounded-lg flex items-center justify-center ${
          color === 'lime' ? 'bg-lime-500/20' :
          color === 'blue' ? 'bg-blue-500/20' : 'bg-green-500/20'
        }`}>
          <Icon className={`w-5 h-5 ${
            color === 'lime' ? 'text-lime-400' :
            color === 'blue' ? 'text-blue-400' : 'text-green-400'
          }`} />
        </div>
        <span className="text-green-400 text-sm font-semibold">{change}</span>
      </div>
      <h3 className="text-foreground text-2xl font-bold mb-1">{value}</h3>
      <p className="text-fg-muted text-sm">{title}</p>
    </div>
  );
}

// 🍽️ Componente de pedido
function OrderCard({ id, table, items, total, status, onProcess, isProcessing }) {
  const statusConfig = {
    preparing: { label: 'Preparando', color: 'yellow' },
    ready: { label: 'Pronto', color: 'green' },
    delivered: { label: 'Entregue', color: 'gray' }
  };

  return (
    <div className="bg-bg-2/80 border border-border rounded-lg p-6">
      <div className="flex items-center justify-between mb-4">
        <div>
          <h3 className="text-foreground font-semibold">{id}</h3>
          <p className="text-fg-muted text-sm">{table}</p>
        </div>
        <div className="text-right">
          <p className="text-foreground font-semibold">{total}</p>
          <span className={`text-xs px-2 py-1 rounded ${
            statusConfig[status].color === 'yellow' ? 'bg-yellow-500/20 text-yellow-400' :
            statusConfig[status].color === 'green' ? 'bg-green-500/20 text-green-400' :
            'bg-bg-3/80 text-fg-muted'
          }`}>
            {statusConfig[status].label}
          </span>
        </div>
      </div>
      
      <div className="mb-4">
        <h4 className="text-fg-muted text-sm mb-2">Itens:</h4>
        <ul className="text-fg-muted text-sm space-y-1">
          {items.map((item, index) => (
            <li key={index}>• {item}</li>
          ))}
        </ul>
      </div>
      
      <div className="flex gap-2">
        {status === 'preparing' && (
          <NeumorphButton
            variant="primary"
            size="sm"
            onClick={onProcess}
            isLoading={isProcessing}
            fullWidth
          >
            {isProcessing ? 'Processando...' : 'Marcar como Pronto'}
          </NeumorphButton>
        )}
        {status === 'ready' && (
          <NeumorphButton variant="secondary" size="sm" fullWidth>
            Entregar
          </NeumorphButton>
        )}
        <NeumorphButton variant="danger" size="sm">
          Cancelar
        </NeumorphButton>
      </div>
    </div>
  );
}

// 🍽️ Componente de item do cardápio
function MenuCard({ name, description, price, image }) {
  return (
    <div className="bg-bg-2/80 border border-border rounded-lg p-6">
      <div className="text-center mb-4">
        <div className="text-4xl mb-2">{image}</div>
        <h3 className="text-foreground font-semibold">{name}</h3>
        <p className="text-fg-muted text-sm mt-1">{description}</p>
      </div>
      
      <div className="flex items-center justify-between">
        <span className="text-lime-400 font-bold text-lg">{price}</span>
        <div className="flex gap-2">
          <NeumorphButton variant="secondary" size="sm">
            Editar
          </NeumorphButton>
          <NeumorphButton variant="primary" size="sm">
            + Adicionar
          </NeumorphButton>
        </div>
      </div>
    </div>
  );
}

🔍 Análise da implementação

Casos de uso do NeumorphButton:

  • • Navegação: Tabs do dashboard
  • • Ações principais: Novo pedido, processar
  • • Estados: Loading, disabled, notifications
  • • Feedback: Confirmações e alertas

Benefícios da abordagem:

  • • Consistência: Mesmo comportamento em todo sistema
  • • Acessibilidade: Focus states e aria attributes
  • • Performance: Animações otimizadas
  • • Manutenibilidade: Código reutilizável

🚀 Próximos Passos

🎯 Módulo 1: Fundamentos - Completado!

✅ Aula 1: Ambiente configurado com Next.js, TypeScript, Tailwind CSS e Framer Motion

✅ Aula 2: Utilitários avançados implementados (cn, animations, colorVariants)

✅ Aula 3: Primeiro componente completo com live preview e aplicação prática

🎛️ Módulo 2: Buttons & Controls - Preview

Aula 1: Family Button - Botões que funcionam em conjunto

Aula 2: Texture Button - Efeitos de textura avançados

Aula 3: Bg Animate Button - Animações de background

Prévia do Módulo 2
// 🎛️ Próximo: Family Button (Módulo 2, Aula 1)
import { NeumorphButton } from '@/components/cult-ui/neumorph-button';

export function FamilyButton() {
  return (
    <div className="flex items-center gap-2 bg-bg-2/80 p-3 rounded-lg">
      <NeumorphButton variant="secondary" size="sm">
        Pai
      </NeumorphButton>
      <NeumorphButton variant="primary" size="sm">
        Ativo
      </NeumorphButton>
      <NeumorphButton variant="default" size="sm">
        Filho
      </NeumorphButton>
    </div>
  );
}

// 🎨 Texture Button (Módulo 2, Aula 3)
export function TextureButton() {
  return (
    <button className="relative bg-gradient-to-br from-gray-700 to-gray-900 text-foreground px-6 py-3 rounded-lg overflow-hidden group">
      <div className="absolute inset-0 bg-[url('/noise.png')] opacity-20 mix-blend-overlay" />
      <span className="relative z-10">Texture Effect</span>
    </button>
  );
}

🏗️ Construindo o RestaurantePro

Progresso atual: Dashboard básico com NeumorphButton implementado

Próximas features: Sistema de pedidos, controle de estoque, relatórios

Componentes que vamos adicionar: Cards, Carousels, Modais, Formulários

🎨 Desafio: Customize o NeumorphButton

Missão: Criar 2 variações personalizadas do NeumorphButton

Sugestões:IconButton (apenas ícone), GradientButton (com gradiente), PulseButton (com animação pulsante)

Dica: Use o componente base e estenda com suas próprias classes CSS

🌟 Habilidades adquiridas

Técnicas:

  • • Implementação de componentes copy-paste
  • • Sistema de variantes com TypeScript
  • • Animações com Framer Motion
  • • Live preview para desenvolvimento

Conceitos:

  • • Neumorfismo e design moderno
  • • Filosofia copy-paste vs NPM packages
  • • Aplicação prática em SaaS real
  • • Performance e acessibilidade

📚 Recursos complementares

Documentação oficial:cult-ui.com

Framer Motion:framer.com/motion

Tailwind CSS:tailwindcss.com

🎉 Parabéns!

Você completou o Módulo 1 e já domina os fundamentos do Cult UI!

Ambiente ConfiguradoPrimeiro ComponenteLive PreviewSaaS Aplicado