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

289 lines
7.2 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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
```css
: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
```css
/* 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
```css
.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
```tsx
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)
```css
/* 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)
```css
.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
```css
.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
```typescript
// 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
```css
/* ❌ 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
```tsx
// ❌ 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
```css
/* ❌ 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](https://every-layout.dev/) | Reusable layout patterns |
| [Container Queries](https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_container_queries) | CSS container queries |
| [Utopia.fyi](https://utopia.fyi/) | Fluid type & space calculator |
---
*"Layout adalah air yang mengalir — ia mengambil bentuk wadahnya tanpa kehilangan esensi."* ❄️🩵