UX/UI 개편 Phase A-3 / A-4 / A-5. 후속 phase가 곧바로 소비할 수 있도록 디자인 시스템의 코어 자산을 한꺼번에 도입한다. A-3 — 유틸 헬퍼 (lib/utils/) - pLimit.ts: 동시 실행 N개 제한 (5대 원칙 #4 — 일괄 PATCH/DELETE에서 GPU 서버/SSE 부하 방지). 외부 의존성 없음. - mergeDoc.ts: PATCH/SSE 응답을 로컬 cache에 머지할 때 updated_at으로 stale 갱신 차단 (5대 원칙 #6 — optimistic update conflict resolution). dropDoc 헬퍼 포함. A-4 — CI 토큰 차단 (5대 원칙 #1) - scripts/check-tokens.sh: bg-[var(--*)] 등 임의값 토큰 우회 grep 차단. - npm run lint:tokens 등록. - 현재 baseline 421건 — A-8 토큰 swap에서 0으로 떨어진 후 pre-commit 강제화. A-5 — 첫 6개 프리미티브 (lib/components/ui/) - Button.svelte: variant(primary/secondary/ghost/danger) × size(sm/md), loading/disabled, icon 슬롯, href 자동 a 변환, focus-visible ring. - IconButton.svelte: 정사각형, aria-label 필수, Button과 동일 variant 체계. - Card.svelte: bg-surface + rounded-card + border-default 패턴 1군데화. padded/interactive 옵션, interactive면 button 시맨틱. - Badge.svelte: 의미적 tone(neutral/success/warning/error/accent) 표시. TagPill과 별개 (TagPill은 도메인 prefix 코드 전용). - Skeleton.svelte: ad-hoc animate-pulse div 통합. w/h/rounded prop. - EmptyState.svelte: icon + title + description + action slot. 모든 프리미티브는 Svelte 5 runes mode strict (\$props/\$derived/\$bindable), @theme 토큰만 사용 (bg-surface, text-dim, border-default 등 — bg-[var(--*)] 미사용), focus-visible ring 통일, slot은 {@render children?.()}로 작성. svelte-check: 0 errors, 8 warnings (모두 기존 latent 이슈, 새 코드 무관). build: 1.95s 무경고. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
50 lines
1.2 KiB
Svelte
50 lines
1.2 KiB
Svelte
<script lang="ts">
|
|
// 공용 status pill (TagPill과 별개 — TagPill은 도메인 prefix 코드,
|
|
// Badge는 의미적 tone 표시).
|
|
import type { Snippet } from 'svelte';
|
|
|
|
type Tone = 'neutral' | 'success' | 'warning' | 'error' | 'accent';
|
|
type Size = 'sm' | 'md';
|
|
|
|
interface Props {
|
|
tone?: Tone;
|
|
size?: Size;
|
|
children?: Snippet;
|
|
class?: string;
|
|
}
|
|
|
|
let {
|
|
tone = 'neutral',
|
|
size = 'md',
|
|
children,
|
|
class: className = '',
|
|
...rest
|
|
}: Props = $props();
|
|
|
|
const toneClass: Record<Tone, string> = {
|
|
neutral: 'bg-surface text-dim border border-default',
|
|
success: 'bg-success/15 text-success border border-success/30',
|
|
warning: 'bg-warning/15 text-warning border border-warning/30',
|
|
error: 'bg-error/15 text-error border border-error/30',
|
|
accent: 'bg-accent/15 text-accent border border-accent/30',
|
|
};
|
|
|
|
const sizeClass: Record<Size, string> = {
|
|
sm: 'text-[10px] px-1.5 py-0.5',
|
|
md: 'text-xs px-2 py-0.5',
|
|
};
|
|
|
|
let baseClass = $derived(
|
|
[
|
|
'inline-flex items-center gap-1 rounded font-medium',
|
|
sizeClass[size],
|
|
toneClass[tone],
|
|
className,
|
|
].join(' ')
|
|
);
|
|
</script>
|
|
|
|
<span class={baseClass} {...rest}>
|
|
{@render children?.()}
|
|
</span>
|