6. Atomic Design Strategy

Metodologi Atomic Design membantu kita membangun sistem komponen yang sangat modular, reusable, dan mudah di-maintain. Dengan membagi UI ke dalam hierarki tingkat komponen yang jelas, kita dapat menghindari duplikasi kode dan membatasi dependensi yang rumit.

Berikut adalah diagram komposisi interaksi komponen dalam arsitektur Atomic Design kita:

Atomic Design Composition


A. Pembagian Layer Komponen

1. Atoms (Atom)

Atoms adalah blok bangunan dasar dari semua antarmuka pengguna kita. Komponen tingkat atom bersifat independen, murni presentasional (stateless dalam konteks bisnis), dan tidak boleh mengimpor komponen lain dari folder components.

  • Karakteristik:
    • Hanya menerima props untuk konfigurasi penampilan & fungsionalitas dasar (variant, size, disabled, onClick).
    • Bebas dari logika bisnis, pemanggilan API, global state, atau router.
    • Wajib memiliki dokumentasi Storybook yang lengkap.
  • Contoh: Button, Input, Badge, Spinner, Icon, Avatar.
Contoh Kode Atom (components/ui/Button/index.tsx):
import React from "react";
import { cva, type VariantProps } from "class-variance-authority";
import { cn } from "@/utils/cn";

const buttonVariants = cva(
  "inline-flex items-center justify-center rounded-xl text-sm font-semibold transition-all focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 active:scale-[0.98]",
  {
    variants: {
      variant: {
        primary: "bg-primary text-primary-foreground hover:bg-primary/90 shadow-lg shadow-primary/20",
        secondary: "bg-slate-100 text-slate-900 hover:bg-slate-200 dark:bg-slate-800 dark:text-slate-100 dark:hover:bg-slate-700/80",
        danger: "bg-destructive text-destructive-foreground hover:bg-destructive/90 shadow-lg shadow-danger/20",
        ghost: "hover:bg-accent hover:text-accent-foreground",
      },
      size: {
        sm: "h-9 px-4 text-xs",
        md: "h-11 px-6 text-sm",
        lg: "h-13 px-8 text-base",
      },
    },
    defaultVariants: {
      variant: "primary",
      size: "md",
    },
  }
);

export interface ButtonProps
  extends React.ButtonHTMLAttributes<HTMLButtonElement>,
    VariantProps<typeof buttonVariants> {
  isLoading?: boolean;
}

export const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
  ({ className, variant, size, isLoading, children, ...props }, ref) => {
    return (
      <button
        className={cn(buttonVariants({ variant, size, className }))}
        ref={ref}
        disabled={isLoading || props.disabled}
        {...props}
      >
        {isLoading && (
          <svg className="animate-spin -ml-1 mr-2 h-4 w-4 text-current" fill="none" viewBox="0 0 24 24">
            <circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4" />
            <path className="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4z" />
          </svg>
        )}
        {children}
      </button>
    );
  }
);
Button.displayName = "Button";
export default Button;

2. Molecules (Molekul)

Molecules adalah kombinasi dari beberapa Atom yang disatukan untuk membentuk suatu fungsi spesifik yang sederhana. Molekul memiliki tanggung jawab yang lebih luas dibandingkan Atom tetapi tetap bersifat reusable.

  • Karakteristik:
    • Dapat memiliki local state sederhana (misalnya untuk melacak input, visibilitas dropdown, dll.).
    • Dilarang keras mengakses global state (Redux/Zustand), TanStack Query, atau memanggil service API secara langsung.
    • Berinteraksi dengan parent component menggunakan callback props (seperti onSearch, onChange).
  • Contoh: SearchBar (Input + Button), FormField (Label + Input + ErrorMessage), Breadcrumbs, AccordionItem.
Contoh Kode Molecule (components/molecules/SearchBar/index.tsx):
import React, { useState } from "react";
import Button from "@/components/ui/Button";
import { Search } from "lucide-react";

interface SearchBarProps {
  placeholder?: string;
  onSearch: (value: string) => void;
  isLoading?: boolean;
}

export const SearchBar: React.FC<SearchBarProps> = ({
  placeholder = "Cari sesuatu...",
  onSearch,
  isLoading = false,
}) => {
  const [query, setQuery] = useState("");

  const handleSubmit = (e: React.FormEvent) => {
    e.preventDefault();
    if (query.trim()) {
      onSearch(query);
    }
  };

  return (
    <form onSubmit={handleSubmit} className="flex w-full max-w-md items-center gap-2">
      <div className="relative flex-1">
        <Search className="absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-slate-400" />
        <input
          type="text"
          value={query}
          onChange={(e) => setQuery(e.target.value)}
          placeholder={placeholder}
          className="h-11 w-full rounded-xl border border-slate-200 bg-white pl-10 pr-4 text-sm outline-none transition-all focus:border-primary focus:ring-2 focus:ring-primary/20 dark:border-slate-800 dark:bg-slate-900"
        />
      </div>
      <Button type="submit" isLoading={isLoading} size="sm" className="h-11">
        Cari
      </Button>
    </form>
  );
};

export default SearchBar;

3. Organisms (Organisme)

Organisms adalah gabungan dari Molecules dan/atau Atoms yang membentuk suatu bagian antarmuka pengguna yang kompleks, fungsional, dan utuh. Organisme biasanya merupakan penghubung utama antara UI dan data nyata.

  • Karakteristik:
    • Mengelola struktur layout internal yang kompleks.
    • Dapat terhubung ke data nyata melalui custom hooks, global state (Zustand/Redux), TanStack Query, atau API Service.
    • Memiliki keterikatan dengan konteks bisnis/fitur aplikasi.
  • Contoh: Header, Sidebar, LoginForm, UserTable, ProductGrid.
Contoh Kode Organism (components/organisms/LoginForm/index.tsx):
import React from "react";
import { useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";
import * as z from "zod";
import Button from "@/components/ui/Button";
import { useAuthStore } from "@/store/slices/authStore";

const loginSchema = z.object({
  email: z.string().email("Format email tidak valid"),
  password: z.string().min(6, "Password minimal 6 karakter"),
});

type LoginFields = z.infer<typeof loginSchema>;

export const LoginForm: React.FC = () => {
  const { login, isLoggingIn, loginError } = useAuthStore();
  
  const {
    register,
    handleSubmit,
    formState: { errors },
  } = useForm<LoginFields>({
    resolver: zodResolver(loginSchema),
  });

  const onSubmit = (data: LoginFields) => {
    login(data.email, data.password);
  };

  return (
    <form onSubmit={handleSubmit(onSubmit)} className="space-y-6 w-full max-w-sm p-8 rounded-2xl bg-white dark:bg-slate-950 border border-slate-200 dark:border-slate-800 shadow-xl">
      <div className="space-y-2 text-center">
        <h2 className="text-2xl font-bold">Selamat Datang</h2>
        <p className="text-xs text-slate-500">Masukkan email dan password untuk mengakses dashboard</p>
      </div>

      {loginError && (
        <div className="p-3.5 rounded-xl bg-destructive/10 text-destructive text-xs border border-destructive/20 font-medium">
          {loginError}
        </div>
      )}

      <div className="space-y-4">
        <div className="space-y-1.5">
          <label className="text-xs font-bold text-slate-600 dark:text-slate-400">Email</label>
          <input
            {...register("email")}
            type="email"
            className="h-11 w-full rounded-xl border border-slate-200 bg-transparent px-4 text-sm outline-none focus:border-primary focus:ring-2 focus:ring-primary/20 dark:border-slate-800"
          />
          {errors.email && <p className="text-[11px] text-destructive font-medium">{errors.email.message}</p>}
        </div>

        <div className="space-y-1.5">
          <label className="text-xs font-bold text-slate-600 dark:text-slate-400">Password</label>
          <input
            {...register("password")}
            type="password"
            className="h-11 w-full rounded-xl border border-slate-200 bg-transparent px-4 text-sm outline-none focus:border-primary focus:ring-2 focus:ring-primary/20 dark:border-slate-800"
          />
          {errors.password && <p className="text-[11px] text-destructive font-medium">{errors.password.message}</p>}
        </div>
      </div>

      <Button type="submit" isLoading={isLoggingIn} className="w-full">
        Masuk ke Akun
      </Button>
    </form>
  );
};

export default LoginForm;

4. Templates (Kerangka Layout)

Templates adalah kerangka tata letak tingkat tinggi yang menempatkan berbagai Organisms ke dalam posisi spasial yang ditentukan pada halaman. Berbeda dengan Pages, Templates tidak peduli dengan data nyata dan tidak memiliki logika bisnis; fokusnya adalah pada strategi tata letak (layout strategy), responsivitas, dan alur visual menggunakan placeholder atau skeleton components. Templates berfungsi sebagai jembatan antara desain statis (Figma) dan halaman fungsional.

  • Karakteristik:
    • Hanya peduli tentang layout grid/flexbox, ukuran viewport, responsivitas, dan penempatan komponen.
    • Tidak memiliki logika bisnis dan tidak peduli dengan data dinamis asli; data nyata disuplai oleh Page tingkat atas yang merendernya.
    • Memungkinkan pengujian struktur layout menggunakan placeholder komponen atau kerangka skeleton.
  • Contoh: DashboardLayout, AuthLayout, ListDetailTemplate.
Contoh Kode Template (components/templates/DashboardLayout/index.tsx):
import React from "react";

interface DashboardLayoutProps {
  sidebar: React.ReactNode;
  header: React.ReactNode;
  children: React.ReactNode;
}

export const DashboardLayout: React.FC<DashboardLayoutProps> = ({
  sidebar,
  header,
  children,
}) => {
  return (
    <div className="flex h-screen w-screen overflow-hidden bg-slate-50 dark:bg-slate-900 transition-colors">
      {/* Sidebar Area */}
      <div className="hidden md:flex h-full w-80 flex-shrink-0 border-r border-slate-200 dark:border-slate-800">
        {sidebar}
      </div>

      {/* Main Container */}
      <div className="flex flex-1 flex-col overflow-hidden">
        {/* Header Area */}
        <header className="h-16 border-b border-slate-200 dark:border-slate-800 bg-white/80 dark:bg-slate-950/80 backdrop-blur-md">
          {header}
        </header>

        {/* Scrollable Content Area */}
        <main className="flex-1 overflow-y-auto px-8 py-8">
          <div className="mx-auto max-w-7xl">
            {children}
          </div>
        </main>
      </div>
    </div>
  );
};

export default DashboardLayout;

© 2026 Bit Group Asia. All rights reserved.

ArchitectureSecurityGuidelines