Files
GMW/design/patterns/10-responsive-system.md
T
asepharyana d59b59a7a7 feat: migrate frontend to Astro + expand AI moderation + backend admin/runtime config
Frontend:
- migrate from Vite to Astro (astro.config.mjs, pages/, layouts/)
- add admin panel, settings page, command palette, error boundary
- refactor App.tsx, MascotChatbot, Sidebar, Header, DashboardLayout
- update API client, WebSocket, auth, dashboard features

Backend:
- add admin module and config routes
- refactor middlewares, Redis connection, WebSocket server/bridge
- add runtime config loader

Discord Gateway:
- refactor AI moderation: circuit breaker, concurrency limiter, fallback processor
- add media analysis client, Seaxng search, user profile learner
- add new drizzle migration

Shared:
- extend database schema, add new config fields
2026-07-02 00:02:41 +07:00

7.2 KiB
Raw Blame History

Responsive System — Shapeshifting Glass

"Content is like water — it should flow into whatever container it's poured into." — Ethan Marcotte


🎯 Filosofi Responsif

BETE menggunakan pendekatan mobile-first dengan tiga prinsip:

  1. Content parity — Konten yang sama di semua ukuran, layout yang berbeda
  2. Touch-optimized — Target 44×44px minimum di mobile
  3. Progressive enhancement — Desktop mendapat fitur tambahan (hover, sidebar, multi-column)

📐 Breakpoint System

:root {
  --bp-sm:  640px;  /* Mobile landscape */
  --bp-md:  768px;  /* Tablet portrait */
  --bp-lg:  1024px; /* Tablet landscape / small desktop */
  --bp-xl:  1280px; /* Desktop */
  --bp-2xl: 1536px; /* Wide desktop */
}

Layout Behavior Matrix

Viewport Sidebar Header Content Grid Font Size
< 640px Bottom tab (56px) Compact 1 col sm
640-768 Bottom tab Compact 1-2 col sm
768-1024 Icon 64px Standard 2 col base
1024-1280 Full 256px Standard 2-3 col base
> 1280px Full 256px Full 3-4 col base+

📱 Mobile Adaptations

Navigation

  • < 768px: Bottom tab bar menggantikan sidebar
  • Tab icons: Home, Live, Messages, Settings (maks 5 tabs)
  • Tab bar height: 56px (dengan safe area padding untuk notched phones)

Content

  • Cards: Full-width (margin 16px), stacked vertical
  • Tables: Horizontal scroll atau card view alternatif
  • Charts: Simplified (less data points, larger labels)
  • Modals: Full-screen drawer dari bawah (bottom sheet)

Touch Targets

/* Minimum 44×44px untuk semua interactive elements */
.button, .nav-item, .tab-item {
  min-height: 44px;
  min-width: 44px;
}

/* Forms on mobile */
.input, .select {
  height: 48px;  /* Larger tap target */
  font-size: 16px;  /* Prevent iOS zoom on focus */
}

💻 Desktop Adaptations

Navigation

  • ≥ 1024px: Full sidebar (256px) dengan label teks
  • Sidebar states: Collapsed (icon-only, 64px) ↔ Expanded (256px)
  • Keyboard shortcuts: Didokumentasikan di help panel

Content

  • Multi-column grids: 2-4 columns depending on container width
  • Sticky elements: Sidebar, header, filter bars
  • Hover previews: Tooltips, popovers untuk informasi tambahan
  • Drag & drop: Dukungan untuk reorder, upload area

🧩 Responsive Component Patterns

Pattern 1: Responsive Card Grid

.card-grid {
  display: grid;
  grid-template-columns: 1fr;
  gap: var(--sp-3);
}

@media (min-width: 640px) {
  .card-grid {
    grid-template-columns: repeat(2, 1fr);
  }
}

@media (min-width: 1024px) {
  .card-grid {
    grid-template-columns: repeat(3, 1fr);
  }
}

@media (min-width: 1280px) {
  .card-grid {
    grid-template-columns: repeat(4, 1fr);
  }
}

Pattern 2: Responsive Sidebar + Content

function DashboardLayout() {
  const [sidebarCollapsed, setSidebarCollapsed] = useState(false);
  const [mobileMenuOpen, setMobileMenuOpen] = useState(false);
  const isMobile = useMediaQuery('(max-width: 767px)');

  return (
    <div className="page-layout">
      {/* Mobile: slide-in drawer */}
      {isMobile && (
        <MobileTabBar activeTab={activeTab} onTabChange={setActiveTab} />
      )}

      {/* Desktop: persistent sidebar */}
      {!isMobile && (
        <Sidebar collapsed={sidebarCollapsed} onToggle={() => setSidebarCollapsed(!sidebarCollapsed)} />
      )}

      <main className="main-area">
        <Header onMenuClick={() => setMobileMenuOpen(true)} />
        <div className="content-area">
          {children}
        </div>
      </main>
    </div>
  );
}

Pattern 3: Responsive Typography (Fluid)

/* Fluid type scale — sudah didefinisikan di core/02-typography.md */
--fs-body: clamp(0.94rem, 0.94rem + 0.03vw, 1.00rem);
--fs-h2:   clamp(1.50rem, 1.50rem + 0.12vw, 1.88rem);

Pattern 4: Container Queries (for reusable components)

.card-grid-component {
  container-type: inline-size;
  container-name: card-list;
}

@container card-list (max-width: 400px) {
  .card-item { grid-template-columns: 1fr; }
}

@container card-list (min-width: 401px) {
  .card-item { grid-template-columns: 1fr 1fr; }
}

🎯 Responsive Decision Tree

Layout component →
├── Apakah ini navigasi?
│   ├── Mobile → Bottom tab bar (56px)
│   ├── Tablet → Icon sidebar (64px) + hamburger
│   └── Desktop → Full sidebar (256px)
│
├── Apakah ini konten list/grid?
│   ├── 1 item → Single column
│   ├── 2-4 items → 2 col (tablet), 3-4 col (desktop)
│   └── > 4 items → auto-fill grid with minmax
│
├── Apakah ini modal/dialog?
│   ├── Mobile → Bottom sheet (full width, 80% height)
│   └── Desktop → Centered modal (max-w-lg)
│
└── Apakah ini form?
    ├── Mobile → Stacked, full-width, larger inputs
    └── Desktop → Multi-column, side labels

📏 Responsive Spacing Scale

.content-padding {
  padding: var(--sp-3);      /* Mobile: 16px */
}

@media (min-width: 768px) {
  .content-padding { padding: var(--sp-4); }  /* Tablet: 24px */
}

@media (min-width: 1024px) {
  .content-padding { padding: var(--sp-5); }  /* Desktop: 32px */
}

🧪 Testing Responsive Design

// Test utility untuk responsive behavior
const VIEWPORTS = {
  mobile: { width: 375, height: 667 },
  tablet: { width: 768, height: 1024 },
  desktop: { width: 1280, height: 800 },
  wide: { width: 1920, height: 1080 },
};

describe('DashboardLayout', () => {
  it('shows MobileTabBar on mobile', () => {
    cy.viewport(VIEWPORTS.mobile);
    cy.get('[data-testid="mobile-tab-bar"]').should('be.visible');
    cy.get('[data-testid="sidebar"]').should('not.be.visible');
  });

  it('shows sidebar on desktop', () => {
    cy.viewport(VIEWPORTS.desktop);
    cy.get('[data-testid="sidebar"]').should('be.visible');
    cy.get('[data-testid="mobile-tab-bar"]').should('not.be.visible');
  });
});

⚠️ Anti-Patterns Responsive

Hanya media query untuk satu breakpoint

/* ❌ JANGAN — hanya mobile dan desktop */
.panel { padding: 16px; }
@media (min-width: 1024px) { .panel { padding: 32px; } }

/* ✅ Gunakan fluid atau multiple breakpoints */
.panel { padding: clamp(16px, 3vw, 32px); }

Hidden content on mobile

// ❌ JANGAN — "out of sight, out of mind" tapi konten hilang
{isMobile ? null : <ExpensiveChart />}

// ✅ Simplified version untuk mobile
<Chart variant={isMobile ? 'compact' : 'full'} />

Fixed width containers

/* ❌ JANGAN — overflow on smaller screens */
.container { width: 1200px; }

/* ✅ Gunakan max-width + padding */
.container { max-width: 1200px; margin: 0 auto; padding: 0 var(--sp-4); }

🔗 Referensi

Sumber Konsep
Every Layout Reusable layout patterns
Container Queries CSS container queries
Utopia.fyi Fluid type & space calculator

"Layout adalah air yang mengalir — ia mengambil bentuk wadahnya tanpa kehilangan esensi." ❄️🩵