style(fe): standardize ModelList master-detail page with central width tokens, selection highlight, and viewport-fit bounds

This commit is contained in:
2026-08-15 22:12:16 +09:00
parent 38a8cb85cd
commit 5dbf0077e4
4 changed files with 65 additions and 47 deletions
Binary file not shown.

Before

Width:  |  Height:  |  Size: 69 KiB

After

Width:  |  Height:  |  Size: 80 KiB

@@ -108,17 +108,17 @@ const handleRetry = () => {
<section class="filters" aria-label="모델 검색 필터">
<input
v-model="filterModel.search"
placeholder="Search models..."
class="input"
placeholder="모델명 검색..."
class="input search-input"
aria-label="모델 이름 검색"
role="searchbox"
/>
<select
v-model="filterModel.phase"
class="input"
class="input status-select"
aria-label="모델 단계별 필터"
>
<option value="">All Phases</option>
<option value="">전체 단계 (All Phases)</option>
<option value="Mature">Mature</option>
<option value="Validate">Validate</option>
<option value="Review">Review</option>
@@ -290,27 +290,38 @@ h3 {
}
.filters {
display: grid;
grid-template-columns: 1fr 150px;
gap: var(--spacing-4);
margin-bottom: var(--spacing-5);
display: flex;
align-items: center;
gap: var(--ks-space-2);
flex-wrap: wrap;
margin-bottom: 0;
}
.input {
padding: var(--spacing-2) var(--spacing-3);
border: var(--border-width-1) solid var(--color-input-border);
border-radius: var(--border-radius-base);
background: var(--color-input-background);
color: var(--color-text-primary);
font-size: var(--font-size-sm);
font-family: var(--font-sans);
transition: all var(--transition-fast);
height: var(--ks-control-height);
padding: 0 var(--ks-space-2);
border: 1px solid var(--ks-color-neutral-200);
border-radius: var(--ks-radius-sm);
background: var(--ks-color-surface);
color: var(--ks-color-neutral-900);
font-size: var(--ks-font-body);
font-family: inherit;
box-sizing: border-box;
transition: all 0.15s ease;
}
.input.search-input {
width: var(--ks-control-width-search);
}
.input.status-select {
width: var(--ks-control-width-select);
}
.input:focus {
outline: none;
border-color: var(--ks-color-action);
box-shadow: 0 0 0 2px var(--ks-color-action);
box-shadow: 0 0 0 2px rgba(37, 99, 235, 0.15);
}
.content-skeleton {
@@ -331,46 +342,52 @@ h3 {
}
.master-list {
border: var(--border-width-1) solid var(--color-border-primary);
border-radius: var(--border-radius-lg);
border: 1px solid var(--ks-color-neutral-200);
border-radius: var(--ks-radius-sm);
overflow: hidden;
background: var(--color-background-primary);
background: var(--ks-color-surface);
height: calc(100vh - 210px);
display: flex;
flex-direction: column;
}
.master-list h2 {
padding: var(--spacing-4);
background: var(--color-background-secondary);
border-bottom: var(--border-width-1) solid var(--color-border-secondary);
padding: var(--ks-space-2) var(--ks-space-3);
background: #f1f5f9;
border-bottom: 1px solid var(--ks-color-neutral-300);
font-size: var(--ks-font-section);
font-weight: 700;
color: var(--ks-color-neutral-800);
margin: 0;
}
.items {
display: flex;
flex-direction: column;
gap: var(--spacing-1);
padding: var(--spacing-2);
max-height: 600px;
gap: var(--ks-space-2);
padding: var(--ks-space-2);
flex: 1;
overflow-y: auto;
}
.model-item {
padding: var(--spacing-3);
background: var(--color-background-primary);
border: var(--border-width-1) solid var(--color-border-secondary);
border-radius: var(--border-radius-base);
padding: var(--ks-space-2) var(--ks-space-3);
background: var(--ks-color-surface);
border: 1px solid var(--ks-color-neutral-200);
border-radius: var(--ks-radius-sm);
cursor: pointer;
transition: all var(--transition-base);
transition: all 0.15s ease;
}
.model-item:hover {
background: var(--color-background-hover);
box-shadow: var(--shadow-sm);
background: #e0f2fe;
}
.model-item.is-selected {
border-color: var(--color-primary-500);
background: var(--color-primary-50);
box-shadow: var(--shadow-sm);
border-color: var(--ks-color-action);
background: #eff6ff;
color: #0f172a;
box-shadow: 0 0 0 1px var(--ks-color-action);
}
.item-header {
@@ -442,24 +459,24 @@ h3 {
}
.detail-panel {
border: var(--border-width-1) solid var(--color-border-primary);
border-radius: var(--border-radius-lg);
padding: var(--spacing-4);
background: var(--color-background-secondary);
max-height: 700px;
border: 1px solid var(--ks-color-neutral-200);
border-radius: var(--ks-radius-sm);
padding: var(--ks-space-3);
background: var(--ks-color-surface);
height: calc(100vh - 210px);
overflow-y: auto;
}
.empty-detail {
text-align: center;
padding: var(--spacing-8);
color: var(--color-text-tertiary);
padding: var(--ks-space-8);
color: var(--ks-color-neutral-500);
}
.model-detail {
display: flex;
flex-direction: column;
gap: var(--spacing-4);
gap: var(--ks-space-3);
}
.section {
@@ -5,6 +5,7 @@ import {
ClientSideRowModelModule,
ColumnAutoSizeModule,
CellStyleModule,
ValidationModule,
TextFilterModule,
themeQuartz,
ModuleRegistry,
@@ -18,6 +19,7 @@ ModuleRegistry.registerModules([
ClientSideRowModelModule,
ColumnAutoSizeModule,
CellStyleModule,
ValidationModule,
TextFilterModule,
RowSelectionModule
])
@@ -1,10 +1,10 @@
<script setup lang="ts">
import { computed } from 'vue'
import { AgGridVue } from 'ag-grid-vue3'
import { ClientSideRowModelModule, ColumnAutoSizeModule, CellStyleModule, ModuleRegistry, RowSelectionModule, TextFilterModule, themeQuartz, type ColDef, type RowClickedEvent } from 'ag-grid-community'
import { ClientSideRowModelModule, ColumnAutoSizeModule, CellStyleModule, ValidationModule, ModuleRegistry, RowSelectionModule, TextFilterModule, themeQuartz, type ColDef, type RowClickedEvent } from 'ag-grid-community'
import type { UiGridColumn } from '../adapter/contracts'
ModuleRegistry.registerModules([ClientSideRowModelModule, ColumnAutoSizeModule, CellStyleModule, TextFilterModule, RowSelectionModule])
ModuleRegistry.registerModules([ClientSideRowModelModule, ColumnAutoSizeModule, CellStyleModule, ValidationModule, TextFilterModule, RowSelectionModule])
const emit = defineEmits<{ rowSelected: [row: unknown] }>()
const props = withDefaults(defineProps<{ rows: unknown[]; columns: UiGridColumn[]; loading?: boolean; height?: string; rowSelection?: 'single' | 'multiple' | 'none'; showRowNumber?: boolean }>(), { loading: false, height: '32rem', rowSelection: 'single', showRowNumber: true })
@@ -60,7 +60,6 @@ function onGridReady(params: { api: { sizeColumnsToFit: () => void } }): void {
:column-defs="columnDefs"
:row-selection="rowSelectionOptions"
:loading="props.loading"
:auto-size-strategy="{ type: 'fitGridWidth' }"
overlay-no-rows-template="<div class='ks-ag-empty-overlay'><span style='font-size:2rem;'>📭</span><p style='margin-top:8px;font-weight:600;color:var(--ks-color-neutral-700);'>조회된 데이터가 없습니다</p><p style='font-size:12px;color:var(--ks-color-neutral-500);'>검색 조건을 변경하거나 신규 데이터를 수집하세요.</p></div>"
@grid-ready="onGridReady"
@first-data-rendered="onGridReady"