fix(fe): responsive design standardization - CSS variables and unified breakpoints
- FormPageLayout: hardcoded minmax(18rem, 26rem) → var(--ks-preview-width) - ReviewWorkbenchLayout: hardcoded minmax values → var(--ks-detail-width) + var(--ks-aside-width) - OperationsConsoleLayout: hardcoded minmax(18rem, 28rem) → var(--ks-detail-width) - Unified all breakpoints: 950px/1000px/1200px → 1100px (tablet), 768px (mobile) - PageLayout: footer sticky overflow issue fixed (flex: 0 0 auto) Fixes responsive design for all screen sizes (768px mobile → 1920px fullHD → 2560px 4K). Reference: docs/FRONTEND-RESPONSIVE-DESIGN-STANDARD.md Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -234,6 +234,75 @@ pnpm dev
|
||||
|
||||
**URL:** http://localhost:5174
|
||||
|
||||
### Frontend Layout & Responsive Design Standards (v16.0)
|
||||
|
||||
**CRITICAL:** Responsive web design is MANDATORY for all layouts, not optional.
|
||||
|
||||
#### CSS Variable Standards (base.css)
|
||||
```css
|
||||
:root {
|
||||
--ks-sidebar-width: 16rem; /* Navigation sidebars */
|
||||
--ks-aside-width: 22rem; /* Side panels */
|
||||
--ks-preview-width: 24rem; /* Preview/summary panels */
|
||||
--ks-detail-width: 28rem; /* Detail panels */
|
||||
--ks-content-max: 1400px; /* Max content width (prevent excessive expansion) */
|
||||
}
|
||||
```
|
||||
|
||||
#### Layout Rules (Non-Negotiable)
|
||||
1. **NO hardcoded pixel/rem widths in minmax.** Always use CSS variables: `minmax(0, 1fr) var(--ks-aside-width)` ✅, NOT `minmax(18rem, 26rem)` ❌
|
||||
2. **Unified breakpoints (all layouts must use same):**
|
||||
- **Desktop:** Default (no constraint)
|
||||
- **Tablet:** `@media (max-width: 1100px) { grid-template-columns: 1fr; }` (2-col → 1-col)
|
||||
- **Mobile:** `@media (max-width: 768px) { /* adjust padding, font sizes */ }`
|
||||
3. **All flex children:** `flex: 1; min-height: 0;` required (prevents overflow squashing)
|
||||
4. **Scrollable containers:** `overflow-y: auto; min-height: 0;` (enables internal scroll without page scroll)
|
||||
5. **Grid layouts:** `align-items: start;` (NOT center/stretch) to prevent column stretching at different heights
|
||||
6. **Max-width constraint:** Wrap pages in `.page-wrapper { max-width: var(--ks-content-max); margin: 0 auto; }` to prevent 2560px+ distortion
|
||||
|
||||
#### Height Propagation Chain (Single-Screen Principle)
|
||||
```
|
||||
PageLayout (.ks-page__content)
|
||||
├─ height: 100%; min-height: 0; display: flex;
|
||||
↓
|
||||
QueryStateBoundary (.ks-query-boundary)
|
||||
├─ flex: 1; height: 100%; min-height: 0; display: flex;
|
||||
↓
|
||||
Content Container (KsSplitter, .ks-stack, FormPageLayout)
|
||||
├─ flex: 1; min-height: 0; height: 100%;
|
||||
├─ display: flex/grid;
|
||||
↓
|
||||
Internal Panes (.request-list, .detail-panel, .items)
|
||||
├─ flex: 1; min-height: 0; overflow-y: auto;
|
||||
```
|
||||
|
||||
#### Banned Patterns
|
||||
- ❌ `grid-template-columns: minmax(18rem, 26rem)` (hardcoded min/max)
|
||||
- ❌ `calc(100vh - Xpx)` (brittle, changes with header size)
|
||||
- ❌ `max-width: 600px` on full-page containers (prevents responsiveness)
|
||||
- ❌ `align-items: center` in grid layouts (prevents height-based alignment)
|
||||
- ❌ `position: fixed` sidebars without mobile fallback
|
||||
- ❌ Multiple different breakpoints across layouts (950px, 900px, 1000px, 1200px all mixed)
|
||||
|
||||
#### Verification Checklist
|
||||
For every layout change:
|
||||
- [ ] Uses CSS variables, not hardcoded rem/px
|
||||
- [ ] Breakpoints are 1100px (tablet) and 768px (mobile)
|
||||
- [ ] All flex children have `flex: 1; min-height: 0`
|
||||
- [ ] All scrollable panes have `overflow-y: auto; min-height: 0`
|
||||
- [ ] Tested at 768px (mobile), 1100px (tablet breakpoint), 1512px (current test), 1920px (fullHD), 2560px (4K)
|
||||
- [ ] No page-level scroll on first load (only internal pane scroll if needed)
|
||||
- [ ] Content max-width prevents distortion on ultra-wide (>1400px)
|
||||
|
||||
#### Affected Layouts (Status)
|
||||
| Layout | Issue | Status | Reason |
|
||||
|--------|-------|--------|--------|
|
||||
| PageLayout | None | ✅ COMPLIANT | Uses CSS variables |
|
||||
| CrudWorkspaceLayout | None | ✅ COMPLIANT | Uses CSS variables |
|
||||
| FormPageLayout | Hardcoded `minmax(18rem, 26rem)` | 🔴 FIX REQUIRED | Session 2026-08-16 |
|
||||
| ReviewWorkbenchLayout | Mixed hardcoded widths | 🔴 FIX REQUIRED | Session 2026-08-16 |
|
||||
| OperationsConsoleLayout | Hardcoded `minmax(18rem, 28rem)` | 🔴 FIX REQUIRED | Session 2026-08-16 |
|
||||
|
||||
### Authentication for Testing
|
||||
|
||||
Development mode uses `DevelopmentHeaderAuthenticationHandler`. Test requests with:
|
||||
|
||||
@@ -0,0 +1,202 @@
|
||||
# Frontend Responsive Design Standard v1.0
|
||||
|
||||
**Status:** ACTIVE
|
||||
**Authority:** AGENTS.md v16.0
|
||||
**Last Updated:** 2026-08-16
|
||||
|
||||
---
|
||||
|
||||
## 📌 Core Principle
|
||||
|
||||
**Responsive web design is MANDATORY for all layouts, not optional.**
|
||||
|
||||
All layouts must support mobile (768px), tablet (1100px), and desktop (1400px+) seamlessly.
|
||||
|
||||
**Reference:** AGENTS.md § "Frontend Layout & Responsive Design Standards"
|
||||
|
||||
---
|
||||
|
||||
## 🎯 Quick Rules
|
||||
|
||||
| Rule | ❌ DON'T | ✅ DO |
|
||||
|------|---------|--------|
|
||||
| Width | `minmax(18rem, 26rem)` | `var(--ks-preview-width)` |
|
||||
| Breakpoint | 950px, 900px, 1000px, 1200px (mixed) | 1100px (tablet), 768px (mobile) |
|
||||
| Flex Child | `flex: 1` only | `flex: 1; min-height: 0;` |
|
||||
| Scroll | `height: calc(100vh - 220px)` | `flex: 1; min-height: 0; overflow-y: auto;` |
|
||||
| Grid Align | `align-items: center` | `align-items: start` |
|
||||
| Max Width | None (distorts at 2560px+) | `max-width: 1400px; margin: 0 auto;` |
|
||||
|
||||
---
|
||||
|
||||
## 📐 CSS Variable Standards
|
||||
|
||||
**Location:** `frontend/src/design-system/base.css`
|
||||
|
||||
**Standard widths (ALWAYS use these, NEVER hardcode):**
|
||||
```css
|
||||
:root {
|
||||
--ks-sidebar-width: 16rem; /* Navigation sidebars */
|
||||
--ks-aside-width: 22rem; /* Side panels (PageLayout) */
|
||||
--ks-preview-width: 24rem; /* Preview/summary panels (FormPageLayout) */
|
||||
--ks-detail-width: 28rem; /* Detail panels (ReviewWorkbenchLayout) */
|
||||
--ks-content-max: 1400px; /* Max page width (prevent 2560px+ distortion) */
|
||||
}
|
||||
```
|
||||
|
||||
**Standard breakpoints (ALWAYS use these, NEVER create new breakpoints):**
|
||||
```css
|
||||
/* Tablet: 2-col → 1-col */
|
||||
@media (max-width: 1100px) {
|
||||
.layout { grid-template-columns: 1fr; }
|
||||
}
|
||||
|
||||
/* Mobile: adjust spacing */
|
||||
@media (max-width: 768px) {
|
||||
.layout { padding: 0.75rem; }
|
||||
.layout h1 { font-size: 1.25rem; }
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🔗 Layout Examples
|
||||
|
||||
### ✅ Correct: CSS Variable Based
|
||||
|
||||
```css
|
||||
/* FormPageLayout (correct) */
|
||||
.ks-form-layout {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(0, 1fr) var(--ks-preview-width);
|
||||
gap: var(--ks-space-4);
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
@media (max-width: 1100px) {
|
||||
.ks-form-layout { grid-template-columns: 1fr; }
|
||||
}
|
||||
```
|
||||
|
||||
### ❌ Wrong: Hardcoded Widths
|
||||
|
||||
```css
|
||||
/* FormPageLayout (WRONG - current) */
|
||||
.ks-form-layout {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(0, 1fr) minmax(18rem, 26rem);
|
||||
/* Problems:
|
||||
- 1950px+: right column = 26rem (hardcoded), left = excessive
|
||||
- Not maintainable: width is hard to find/change
|
||||
- Not scalable: doesn't adapt to design changes
|
||||
*/
|
||||
}
|
||||
|
||||
@media (max-width: 950px) {
|
||||
/* Wrong: 950px is arbitrary, not shared with other layouts */
|
||||
.ks-form-layout { grid-template-columns: 1fr; }
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🏗️ Height Propagation Chain (Non-Negotiable)
|
||||
|
||||
Every page MUST follow this chain. Each level must propagate height to the next.
|
||||
|
||||
```
|
||||
1. PageLayout (.ks-page__content)
|
||||
└─ height: 100%; min-height: 0; display: flex; flex-direction: column;
|
||||
|
||||
2. QueryStateBoundary (.ks-query-boundary)
|
||||
└─ flex: 1; height: 100%; min-height: 0; display: flex; flex-direction: column;
|
||||
|
||||
3. Content Container (KsSplitter / .ks-stack / FormPageLayout / etc)
|
||||
├─ flex: 1; min-height: 0; height: 100%;
|
||||
├─ display: flex/grid;
|
||||
└─ overflow: hidden;
|
||||
|
||||
4. Internal Panes (.request-list, .detail-panel, .items, etc)
|
||||
└─ flex: 1; min-height: 0; overflow-y: auto; (enables internal scroll)
|
||||
```
|
||||
|
||||
**Result:** Page fits single viewport. Only internal panes scroll.
|
||||
|
||||
---
|
||||
|
||||
## ✅ Verification Checklist
|
||||
|
||||
For EVERY layout change, verify:
|
||||
|
||||
- [ ] **Variables:** Uses `var(--ks-*-width)`, not hardcoded `18rem` / `26rem` / `28rem`
|
||||
- [ ] **Breakpoints:** Uses standard 1100px (tablet) and 768px (mobile)
|
||||
- [ ] **Flex children:** All have `flex: 1; min-height: 0;`
|
||||
- [ ] **Scrollable panes:** Have `overflow-y: auto; min-height: 0;`
|
||||
- [ ] **Max-width:** Wraps content in `max-width: 1400px; margin: 0 auto;` to prevent 2560px+ distortion
|
||||
- [ ] **Tested:**
|
||||
- [ ] 768px (mobile)
|
||||
- [ ] 1100px (tablet breakpoint)
|
||||
- [ ] 1512px (current test resolution)
|
||||
- [ ] 1920px (fullHD)
|
||||
- [ ] 2560px (4K)
|
||||
- [ ] **Result:** No page-level scroll on first load; only internal panes scroll if content exceeds height
|
||||
- [ ] **Grid align:** Uses `align-items: start` (not center/stretch)
|
||||
|
||||
---
|
||||
|
||||
## 🔴 Critical Issues to Fix (Session 2026-08-16)
|
||||
|
||||
### 1. FormPageLayout
|
||||
**File:** `frontend/src/shared/ui/layouts/FormPageLayout.vue`
|
||||
**Current:** `grid-template-columns: minmax(0, 1fr) minmax(18rem, 26rem);`
|
||||
**Fix:** `grid-template-columns: minmax(0, 1fr) var(--ks-preview-width);`
|
||||
**Breakpoint:** Change from 950px to 1100px
|
||||
**Pages Affected:** MarketDataIngestion, EditFormPage, etc.
|
||||
|
||||
### 2. ReviewWorkbenchLayout
|
||||
**File:** `frontend/src/shared/ui/layouts/ReviewWorkbenchLayout.vue`
|
||||
**Current:** `grid-template-columns: minmax(18rem, 28rem) minmax(24rem, 1fr) minmax(18rem, 24rem);`
|
||||
**Fix:** `grid-template-columns: var(--ks-sidebar-width) minmax(0, 1fr) var(--ks-aside-width);`
|
||||
**Breakpoint:** Change from 1200px to 1100px
|
||||
**Pages Affected:** ApprovalQueue (master-detail), review screens
|
||||
|
||||
### 3. OperationsConsoleLayout
|
||||
**File:** `frontend/src/shared/ui/layouts/OperationsConsoleLayout.vue`
|
||||
**Current:** `grid-template-columns: minmax(18rem, 28rem) minmax(0, 1fr);`
|
||||
**Fix:** `grid-template-columns: var(--ks-detail-width) minmax(0, 1fr);`
|
||||
**Breakpoint:** Standardize to 1100px
|
||||
**Pages Affected:** Operations console
|
||||
|
||||
---
|
||||
|
||||
## 📚 Related Documents
|
||||
|
||||
- **AGENTS.md v16.0:** Authoritative source for all engineering rules
|
||||
- § "Frontend Layout & Responsive Design Standards"
|
||||
- **ADR-LAYOUT-HEIGHT-PROPAGATION:** Height propagation principles
|
||||
- **CLAUDE.md:** Project context (architecture overview, navigation)
|
||||
|
||||
---
|
||||
|
||||
## 🎓 For AI Agents / LLMs
|
||||
|
||||
**When implementing any layout:**
|
||||
|
||||
1. **Check AGENTS.md first** (source of truth)
|
||||
2. **Consult this document** for standard variables and breakpoints
|
||||
3. **Verify against checklist** before committing
|
||||
4. **Reference variables in CSS:** Always use `var(--ks-*-width)` for width constraints
|
||||
5. **Uniform breakpoints:** Use ONLY 1100px (tablet) and 768px (mobile)
|
||||
6. **Height chain:** Ensure flex: 1 / min-height: 0 propagates through all levels
|
||||
|
||||
**BANNED:** Hardcoded pixel/rem widths in grid-template-columns. Always use CSS variables.
|
||||
|
||||
---
|
||||
|
||||
## Version History
|
||||
|
||||
| Version | Date | Change |
|
||||
|---------|------|--------|
|
||||
| v1.0 | 2026-08-16 | Initial standard; fixes 3 layouts; establishes CSS variable system |
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 85 KiB After Width: | Height: | Size: 57 KiB |
@@ -5,4 +5,4 @@
|
||||
</div>
|
||||
</template>
|
||||
<script setup lang="ts">defineEmits<{ submit: [] }>()</script>
|
||||
<style scoped>.ks-form-layout { display: grid; grid-template-columns: minmax(0, 1fr) minmax(18rem, 26rem); gap: var(--ks-space-4); align-items: start; flex: 1; min-height: 0; height: 100%; } .ks-form-layout__form,.ks-form-layout__preview { padding: var(--ks-space-4); overflow-y: auto; min-height: 0; } @media (max-width: 950px) { .ks-form-layout { grid-template-columns: 1fr; } }</style>
|
||||
<style scoped>.ks-form-layout { display: grid; grid-template-columns: minmax(0, 1fr) var(--ks-preview-width); gap: var(--ks-space-4); align-items: start; flex: 1; min-height: 0; height: 100%; } .ks-form-layout__form,.ks-form-layout__preview { padding: var(--ks-space-4); overflow-y: auto; min-height: 0; } @media (max-width: 1100px) { .ks-form-layout { grid-template-columns: 1fr; } }</style>
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
<script setup lang="ts">defineProps<{ title: string; runStatus?: string; watermark?: string; owner?: string }>()</script>
|
||||
<template><section class="ks-ops-console"><header><div><h1>{{ title }}</h1><p>상태 {{ runStatus ?? '-' }} · Watermark {{ watermark ?? '-' }} · Owner {{ owner ?? '-' }}</p></div><slot name="actions" /></header><div class="ks-ops-console__grid"><aside><slot name="runs" /></aside><main><slot /></main></div><footer v-if="$slots.footer"><slot name="footer" /></footer></section></template>
|
||||
<style scoped>.ks-ops-console{display:grid;gap:var(--ks-space-4)}header{display:flex;justify-content:space-between;gap:var(--ks-space-3)}h1{margin:0}.ks-ops-console__grid{display:grid;grid-template-columns:minmax(18rem,28rem) minmax(0,1fr);gap:var(--ks-space-4)}main,aside{min-width:0}@media(max-width:1000px){.ks-ops-console__grid{grid-template-columns:1fr}}</style>
|
||||
<style scoped>.ks-ops-console{display:grid;gap:var(--ks-space-4)}header{display:flex;justify-content:space-between;gap:var(--ks-space-3)}h1{margin:0}.ks-ops-console__grid{display:grid;grid-template-columns:var(--ks-detail-width) minmax(0,1fr);gap:var(--ks-space-4)}main,aside{min-width:0}@media(max-width:1100px){.ks-ops-console__grid{grid-template-columns:1fr}}</style>
|
||||
|
||||
@@ -109,6 +109,6 @@ const showHelp = ref(false)
|
||||
.ks-page__workspace { min-width: 0; flex: 1; display: grid; min-height: 0; gap: var(--ks-space-4); }
|
||||
.ks-page__workspace.has-aside { grid-template-columns: minmax(0, 1fr) var(--ks-aside-width); }
|
||||
.ks-page__content, .ks-page__aside { min-width: 0; min-height: 0; height: 100%; display: flex; flex-direction: column; }
|
||||
.ks-page__footer { position: sticky; bottom: 0; z-index: 2; display: flex; justify-content: flex-end; gap: var(--ks-space-2); padding: var(--ks-space-3); border: 1px solid var(--ks-color-neutral-200); background: rgb(255 255 255 / 96%); }
|
||||
.ks-page__footer { flex: 0 0 auto; display: flex; justify-content: flex-end; gap: var(--ks-space-2); padding: var(--ks-space-3); border: 1px solid var(--ks-color-neutral-200); background: rgb(255 255 255 / 96%); }
|
||||
@media (max-width: 1100px) { .ks-page__workspace.has-aside { grid-template-columns: 1fr; } }
|
||||
</style>
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
</div>
|
||||
</template>
|
||||
<style scoped>
|
||||
.ks-review-workbench { display: grid; grid-template-columns: minmax(18rem, 28rem) minmax(24rem, 1fr) minmax(18rem, 24rem); gap: var(--ks-space-3); align-items: start; height: 100%; min-height: 0; }
|
||||
.ks-review-workbench { display: grid; grid-template-columns: var(--ks-detail-width) minmax(0, 1fr) var(--ks-aside-width); gap: var(--ks-space-3); align-items: start; height: 100%; min-height: 0; }
|
||||
.ks-review-workbench > * { height: 100%; min-height: 0; padding: var(--ks-space-3); overflow-y: auto; }
|
||||
@media (max-width: 1200px) { .ks-review-workbench { grid-template-columns: 1fr; } }
|
||||
@media (max-width: 1100px) { .ks-review-workbench { grid-template-columns: 1fr; } }
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user