refactor(tokens): A-8 Batch 2 — Sidebar / DocumentCard / DocumentTable

목록/사이드바 영역의 var() 토큰을 의미 토큰으로 swap. Phase A 디자인
시스템 정착의 두 번째 mechanical refactor batch (8 파일 중 5/8 누적).

Sidebar:
- bg-[var(--sidebar-bg)]  → bg-sidebar  (이름 변경)
- border-[var(--border)]  → border-default
- text-[var(--text)]      → text-text
- text-[var(--text-dim)]  → text-dim
- bg-[var(--accent)]/15   → bg-accent/15
- hover:bg-[var(--surface)] → hover:bg-surface
- domain 색상 inline style (DOMAIN_COLORS)은 그대로 유지

DocumentCard:
- bg/border/text/hover 토큰 일괄 swap
- DOMAIN_COLORS의 var(--domain-*) 유지 (plan B2 비고)
- blue-400/blue-900/30 (news icon, data_origin work) 그대로
  (lint:tokens 미검출 + plan 명시 없음)

DocumentTable:
- 헤더 + 행 + selected 상태 + 컬럼 텍스트 일괄 swap
- border-l-[var(--accent)] → border-l-accent
- border-default/30 opacity suffix (행 구분선) v4 시각 검증 필요

검증:
- npm run lint:tokens : 407 → 360 (-47, B2 파일 0 hit)
- npm run build       : 
- npx svelte-check    :  0 errors
- ⚠ 3-risk grep       : hover/border-border/var() 잔여 0건

플랜: ~/.claude/plans/compressed-churning-dragon.md §A.4 Batch 2
This commit is contained in:
Hyungi Ahn
2026-04-07 12:04:37 +09:00
parent 451c2181a0
commit 8ec89517ee
3 changed files with 35 additions and 35 deletions

View File

@@ -57,8 +57,8 @@
<button <button
onclick={handleClick} onclick={handleClick}
aria-label={doc.title || '문서 선택'} aria-label={doc.title || '문서 선택'}
class="flex items-stretch bg-[var(--surface)] border rounded-lg hover:border-[var(--accent)] transition-colors group w-full text-left overflow-hidden class="flex items-stretch bg-surface border rounded-lg hover:border-accent transition-colors group w-full text-left overflow-hidden
{selected ? 'border-[var(--accent)] bg-[var(--accent)]/5' : 'border-[var(--border)]'}" {selected ? 'border-accent bg-accent/5' : 'border-default'}"
> >
<!-- domain 색상 바 --> <!-- domain 색상 바 -->
<div class="w-1 shrink-0 rounded-l-lg" style="background: {domainColor}"></div> <div class="w-1 shrink-0 rounded-l-lg" style="background: {domainColor}"></div>
@@ -66,21 +66,21 @@
<!-- 콘텐츠 --> <!-- 콘텐츠 -->
<div class="flex items-start gap-3 p-3 flex-1 min-w-0"> <div class="flex items-start gap-3 p-3 flex-1 min-w-0">
<!-- 포맷 아이콘 --> <!-- 포맷 아이콘 -->
<div class="shrink-0 mt-0.5 text-[var(--text-dim)] group-hover:text-[var(--accent)]"> <div class="shrink-0 mt-0.5 text-dim group-hover:text-accent">
<FormatIcon format={doc.file_format} size={18} /> <FormatIcon format={doc.file_format} size={18} />
</div> </div>
<!-- 메인 콘텐츠 --> <!-- 메인 콘텐츠 -->
<div class="flex-1 min-w-0"> <div class="flex-1 min-w-0">
<p class="text-sm font-medium truncate group-hover:text-[var(--accent)]"> <p class="text-sm font-medium truncate group-hover:text-accent">
{doc.title || '제목 없음'} {doc.title || '제목 없음'}
</p> </p>
{#if doc.ai_summary} {#if doc.ai_summary}
<p class="text-xs text-[var(--text-dim)] truncate mt-0.5">{doc.ai_summary.replace(/[*#_`~]/g, '').slice(0, 100)}</p> <p class="text-xs text-dim truncate mt-0.5">{doc.ai_summary.replace(/[*#_`~]/g, '').slice(0, 100)}</p>
{/if} {/if}
<div class="flex items-center gap-2 mt-1.5 flex-wrap"> <div class="flex items-center gap-2 mt-1.5 flex-wrap">
{#if showDomain && doc.ai_domain} {#if showDomain && doc.ai_domain}
<span class="text-[10px] text-[var(--text-dim)]"> <span class="text-[10px] text-dim">
{doc.ai_domain.replace('Knowledge/', '')}{doc.ai_sub_group ? ` / ${doc.ai_sub_group}` : ''} {doc.ai_domain.replace('Knowledge/', '')}{doc.ai_sub_group ? ` / ${doc.ai_sub_group}` : ''}
</span> </span>
{/if} {/if}
@@ -100,16 +100,16 @@
<span class="text-blue-400">📰</span> <span class="text-blue-400">📰</span>
{/if} {/if}
{#if doc.score !== undefined} {#if doc.score !== undefined}
<span class="text-[var(--accent)] font-medium">{(doc.score * 100).toFixed(0)}%</span> <span class="text-accent font-medium">{(doc.score * 100).toFixed(0)}%</span>
{/if} {/if}
{#if doc.data_origin} {#if doc.data_origin}
<span class="px-1.5 py-0.5 rounded {doc.data_origin === 'work' ? 'bg-blue-900/30 text-blue-400' : 'bg-gray-800 text-gray-400'}"> <span class="px-1.5 py-0.5 rounded {doc.data_origin === 'work' ? 'bg-blue-900/30 text-blue-400' : 'bg-gray-800 text-gray-400'}">
{doc.data_origin} {doc.data_origin}
</span> </span>
{/if} {/if}
<span class="text-[var(--text-dim)]">{formatDate(doc.created_at)}</span> <span class="text-dim">{formatDate(doc.created_at)}</span>
{#if doc.file_size} {#if doc.file_size}
<span class="text-[var(--text-dim)]">{formatSize(doc.file_size)}</span> <span class="text-dim">{formatSize(doc.file_size)}</span>
{/if} {/if}
</div> </div>
</div> </div>

View File

@@ -94,15 +94,15 @@
<div class="w-full"> <div class="w-full">
<!-- 헤더 --> <!-- 헤더 -->
<div class="flex items-center gap-1 px-2 py-1.5 border-b border-[var(--border)] text-[10px] text-[var(--text-dim)] uppercase tracking-wider"> <div class="flex items-center gap-1 px-2 py-1.5 border-b border-default text-[10px] text-dim uppercase tracking-wider">
{#each columns as col} {#each columns as col}
<button <button
onclick={() => toggleSort(col.key)} onclick={() => toggleSort(col.key)}
class="flex items-center gap-1 {col.flex || col.width || ''} px-1 hover:text-[var(--text)] transition-colors text-left" class="flex items-center gap-1 {col.flex || col.width || ''} px-1 hover:text-text transition-colors text-left"
> >
{col.label} {col.label}
{#if sortKey === col.key} {#if sortKey === col.key}
<span class="text-[var(--accent)]">{sortOrder === 'asc' ? '↑' : '↓'}</span> <span class="text-accent">{sortOrder === 'asc' ? '↑' : '↓'}</span>
{/if} {/if}
</button> </button>
{/each} {/each}
@@ -112,29 +112,29 @@
{#each sortedItems() as doc} {#each sortedItems() as doc}
<button <button
onclick={() => handleClick(doc)} onclick={() => handleClick(doc)}
class="flex items-center gap-1 px-2 py-1.5 w-full text-left border-b border-[var(--border)]/30 hover:bg-[var(--surface)] transition-colors group class="flex items-center gap-1 px-2 py-1.5 w-full text-left border-b border-default/30 hover:bg-surface transition-colors group
{selectedId === doc.id ? 'bg-[var(--accent)]/5 border-l-2 border-l-[var(--accent)]' : ''}" {selectedId === doc.id ? 'bg-accent/5 border-l-2 border-l-accent' : ''}"
> >
<!-- 이름 --> <!-- 이름 -->
<div class="flex-1 flex items-center gap-2 min-w-0"> <div class="flex-1 flex items-center gap-2 min-w-0">
<span class="w-1 h-4 rounded-full shrink-0" style="background: {getDomainColor(doc.ai_domain)}"></span> <span class="w-1 h-4 rounded-full shrink-0" style="background: {getDomainColor(doc.ai_domain)}"></span>
<FormatIcon format={doc.file_format} size={14} /> <FormatIcon format={doc.file_format} size={14} />
<span class="text-xs truncate group-hover:text-[var(--accent)]">{doc.title || '제목 없음'}</span> <span class="text-xs truncate group-hover:text-accent">{doc.title || '제목 없음'}</span>
</div> </div>
<!-- 분류 --> <!-- 분류 -->
<div class="w-48 text-[10px] text-[var(--text-dim)] truncate"> <div class="w-48 text-[10px] text-dim truncate">
{doc.ai_domain?.replace('Industrial_Safety/', 'IS/') || '-'} {doc.ai_domain?.replace('Industrial_Safety/', 'IS/') || '-'}
</div> </div>
<!-- 타입 --> <!-- 타입 -->
<div class="w-24 text-[10px] text-[var(--text-dim)]"> <div class="w-24 text-[10px] text-dim">
{doc.document_type || doc.file_format?.toUpperCase() || '-'} {doc.document_type || doc.file_format?.toUpperCase() || '-'}
</div> </div>
<!-- 크기 --> <!-- 크기 -->
<div class="w-20 text-[10px] text-[var(--text-dim)] text-right"> <div class="w-20 text-[10px] text-dim text-right">
{formatSize(doc.file_size)} {formatSize(doc.file_size)}
</div> </div>
<!-- 등록일 --> <!-- 등록일 -->
<div class="w-20 text-[10px] text-[var(--text-dim)] text-right"> <div class="w-20 text-[10px] text-dim text-right">
{formatDate(doc.created_at)} {formatDate(doc.created_at)}
</div> </div>
</button> </button>

View File

@@ -68,9 +68,9 @@
let totalCount = $derived(tree.reduce((sum, n) => sum + n.count, 0)); let totalCount = $derived(tree.reduce((sum, n) => sum + n.count, 0));
</script> </script>
<aside class="h-full flex flex-col bg-[var(--sidebar-bg)] border-r border-[var(--border)] overflow-y-auto"> <aside class="h-full flex flex-col bg-sidebar border-r border-default overflow-y-auto">
<div class="px-4 py-3 border-b border-[var(--border)]"> <div class="px-4 py-3 border-b border-default">
<h2 class="text-sm font-semibold text-[var(--text-dim)] uppercase tracking-wider">분류</h2> <h2 class="text-sm font-semibold text-dim uppercase tracking-wider">분류</h2>
</div> </div>
<!-- 전체 문서 --> <!-- 전체 문서 -->
@@ -78,14 +78,14 @@
<button <button
onclick={() => navigate(null)} onclick={() => navigate(null)}
class="w-full flex items-center justify-between px-3 py-2 rounded-md text-sm transition-colors class="w-full flex items-center justify-between px-3 py-2 rounded-md text-sm transition-colors
{!activeDomain ? 'bg-[var(--accent)]/15 text-[var(--accent)]' : 'text-[var(--text)] hover:bg-[var(--surface)]'}" {!activeDomain ? 'bg-accent/15 text-accent' : 'text-text hover:bg-surface'}"
> >
<span class="flex items-center gap-2"> <span class="flex items-center gap-2">
<FolderOpen size={16} /> <FolderOpen size={16} />
전체 문서 전체 문서
</span> </span>
{#if totalCount > 0} {#if totalCount > 0}
<span class="text-xs text-[var(--text-dim)]">{totalCount}</span> <span class="text-xs text-dim">{totalCount}</span>
{/if} {/if}
</button> </button>
</div> </div>
@@ -94,7 +94,7 @@
<nav class="flex-1 px-2 py-2"> <nav class="flex-1 px-2 py-2">
{#if loading} {#if loading}
{#each Array(5) as _} {#each Array(5) as _}
<div class="h-8 bg-[var(--surface)] rounded-md animate-pulse mx-1 mb-1"></div> <div class="h-8 bg-surface rounded-md animate-pulse mx-1 mb-1"></div>
{/each} {/each}
{:else} {:else}
{#each tree as node} {#each tree as node}
@@ -109,7 +109,7 @@
{#if hasChildren} {#if hasChildren}
<button <button
onclick={() => toggleExpand(n.path)} onclick={() => toggleExpand(n.path)}
class="p-0.5 rounded hover:bg-[var(--surface)] text-[var(--text-dim)]" class="p-0.5 rounded hover:bg-surface text-dim"
> >
{#if isExpanded} {#if isExpanded}
<ChevronDown size={14} /> <ChevronDown size={14} />
@@ -124,7 +124,7 @@
<button <button
onclick={() => navigate(n.path)} onclick={() => navigate(n.path)}
class="flex-1 flex items-center justify-between px-2 py-1.5 rounded-md text-sm transition-colors class="flex-1 flex items-center justify-between px-2 py-1.5 rounded-md text-sm transition-colors
{isActive ? 'bg-[var(--accent)]/15 text-[var(--accent)]' : isParent ? 'text-[var(--text)]' : 'text-[var(--text-dim)] hover:bg-[var(--surface)] hover:text-[var(--text)]'}" {isActive ? 'bg-accent/15 text-accent' : isParent ? 'text-text' : 'text-dim hover:bg-surface hover:text-text'}"
> >
<span class="flex items-center gap-2"> <span class="flex items-center gap-2">
{#if depth === 0} {#if depth === 0}
@@ -132,7 +132,7 @@
{/if} {/if}
<span class="truncate">{n.name}</span> <span class="truncate">{n.name}</span>
</span> </span>
<span class="text-xs text-[var(--text-dim)] shrink-0 ml-2">{n.count}</span> <span class="text-xs text-dim shrink-0 ml-2">{n.count}</span>
</button> </button>
</div> </div>
@@ -148,33 +148,33 @@
</nav> </nav>
<!-- 스마트 그룹 --> <!-- 스마트 그룹 -->
<div class="px-2 py-2 border-t border-[var(--border)]"> <div class="px-2 py-2 border-t border-default">
<h3 class="px-3 py-1 text-[10px] font-semibold text-[var(--text-dim)] uppercase tracking-wider">스마트 그룹</h3> <h3 class="px-3 py-1 text-[10px] font-semibold text-dim uppercase tracking-wider">스마트 그룹</h3>
<button <button
onclick={() => goto('/documents', { noScroll: true })} onclick={() => goto('/documents', { noScroll: true })}
class="w-full flex items-center gap-2 px-3 py-1.5 rounded-md text-sm text-[var(--text-dim)] hover:bg-[var(--surface)] hover:text-[var(--text)]" class="w-full flex items-center gap-2 px-3 py-1.5 rounded-md text-sm text-dim hover:bg-surface hover:text-text"
> >
<Clock size={14} /> 최근 7일 <Clock size={14} /> 최근 7일
</button> </button>
<button <button
onclick={() => { const p = new URLSearchParams(); p.set('source', 'law_monitor'); goto(`/documents?${p}`, { noScroll: true }); }} onclick={() => { const p = new URLSearchParams(); p.set('source', 'law_monitor'); goto(`/documents?${p}`, { noScroll: true }); }}
class="w-full flex items-center gap-2 px-3 py-1.5 rounded-md text-sm text-[var(--text-dim)] hover:bg-[var(--surface)] hover:text-[var(--text)]" class="w-full flex items-center gap-2 px-3 py-1.5 rounded-md text-sm text-dim hover:bg-surface hover:text-text"
> >
<Scale size={14} /> 법령 알림 <Scale size={14} /> 법령 알림
</button> </button>
<button <button
onclick={() => { const p = new URLSearchParams(); p.set('source', 'email'); goto(`/documents?${p}`, { noScroll: true }); }} onclick={() => { const p = new URLSearchParams(); p.set('source', 'email'); goto(`/documents?${p}`, { noScroll: true }); }}
class="w-full flex items-center gap-2 px-3 py-1.5 rounded-md text-sm text-[var(--text-dim)] hover:bg-[var(--surface)] hover:text-[var(--text)]" class="w-full flex items-center gap-2 px-3 py-1.5 rounded-md text-sm text-dim hover:bg-surface hover:text-text"
> >
<Mail size={14} /> 이메일 <Mail size={14} /> 이메일
</button> </button>
</div> </div>
<!-- Inbox --> <!-- Inbox -->
<div class="px-2 py-2 border-t border-[var(--border)]"> <div class="px-2 py-2 border-t border-default">
<a <a
href="/inbox" href="/inbox"
class="flex items-center justify-between px-3 py-2 rounded-md text-sm text-[var(--text)] hover:bg-[var(--surface)] transition-colors" class="flex items-center justify-between px-3 py-2 rounded-md text-sm text-text hover:bg-surface transition-colors"
> >
<span class="flex items-center gap-2"> <span class="flex items-center gap-2">
<Inbox size={16} /> <Inbox size={16} />