style(frontend): ABAP_OPENCODE 디자인 시스템으로 맞춤 + 화면 재구성
DESIGN_SYSTEM.md 의 Dark Workspace 토큰을 shadcn 토큰으로 이식(다크 기본, radius 8px, success/warning/shadow-float), 시스템 산세리프, PwC 로고(LogoMark). 목록·새 대화·대화· 로그인 화면을 가운데 760px 열로 통일하고, 떠 있던 테마·로그아웃 상자는 상단 바로, 말풍선은 다크 토큰(AI 답변은 상자 없이), ABAP 흰 에디터 룩은 라이트 모드에서만. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
@@ -4,8 +4,11 @@ import { invoke } from "@tauri-apps/api/core"
|
||||
import { X } from "lucide-react"
|
||||
import { toast } from "sonner"
|
||||
import { useLocation } from "react-router-dom"
|
||||
import { LogoMark } from "@/shared/components/LogoMark"
|
||||
import { SnapUserControls } from "@/features/snap/components/SnapUserControls"
|
||||
|
||||
/** Tauri 프레임리스 창의 공통 제목줄. 닫기는 트레이로 숨김. */
|
||||
/** Tauri 프레임리스 창의 공통 제목줄. 닫기는 트레이로 숨김.
|
||||
* 로그인 뒤 화면(snap·snippet)에선 테마·로그아웃도 여기 오른쪽에 — 따로 떠 있는 상자 없이. */
|
||||
export function DesktopWindowFrame({ children }: { children: ReactNode }) {
|
||||
const { pathname } = useLocation()
|
||||
const palette = pathname === "/snippet" || pathname === "/snap" || pathname.startsWith("/snap/")
|
||||
@@ -18,29 +21,33 @@ export function DesktopWindowFrame({ children }: { children: ReactNode }) {
|
||||
return (
|
||||
<div className="bg-background text-foreground flex h-screen flex-col overflow-hidden">
|
||||
<header
|
||||
className={`flex shrink-0 items-center select-none ${palette ? "h-7" : "border-border h-9 border-b"}`}
|
||||
className={`border-border flex h-8 shrink-0 items-center border-b select-none ${palette ? "" : "h-9"}`}
|
||||
>
|
||||
<div
|
||||
className="text-muted-foreground flex h-full min-w-0 flex-1 items-center px-4 text-[10px] font-medium"
|
||||
className="text-muted-foreground flex h-full min-w-0 flex-1 items-center gap-2 px-3 text-[10.5px] font-semibold tracking-wide"
|
||||
onMouseDown={(event) => {
|
||||
if (event.button === 0) run("window_drag")
|
||||
}}
|
||||
// 창 이동은 포인터 전용 동작이며 버튼과 영역을 분리함.
|
||||
role="presentation"
|
||||
>
|
||||
<LogoMark width={22} />
|
||||
CodeAssist
|
||||
</div>
|
||||
{palette && <SnapUserControls />}
|
||||
<button
|
||||
type="button"
|
||||
aria-label="창 닫기"
|
||||
title="닫기 (트레이로 숨기기)"
|
||||
className="text-muted-foreground hover:bg-accent hover:text-foreground focus-visible:ring-ring mr-1 flex h-full w-8 shrink-0 items-center justify-center rounded-md transition-colors focus-visible:ring-2 focus-visible:outline-none focus-visible:ring-inset"
|
||||
className="text-muted-foreground hover:bg-accent hover:text-foreground focus-visible:ring-ring mr-1 ml-0.5 flex size-7 shrink-0 items-center justify-center rounded-md transition-colors focus-visible:ring-2 focus-visible:outline-none focus-visible:ring-inset"
|
||||
onClick={() => run("window_hide")}
|
||||
>
|
||||
<X className="size-4" aria-hidden="true" />
|
||||
<X className="size-3.5" aria-hidden="true" />
|
||||
</button>
|
||||
</header>
|
||||
<div className="min-h-0 flex-1 overflow-auto [&_.h-screen]:h-full">{children}</div>
|
||||
<div className="min-h-0 flex-1 overflow-auto [&_.h-screen]:h-full [&_.min-h-screen]:min-h-full">
|
||||
{children}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,19 +1,26 @@
|
||||
import { Link, Outlet } from "react-router-dom"
|
||||
import { hostKind } from "@/lib/bridge/transport"
|
||||
import { PATHS } from "@/config/routes"
|
||||
import { ThemeToggle } from "@/shared/components/ThemeToggle"
|
||||
import { LogoMark } from "@/shared/components/LogoMark"
|
||||
|
||||
/** 로그인 등 공개 화면 셸. Tauri 에선 창 제목줄(DesktopWindowFrame)이 로고·테마를 이미 보여주므로 헤더를 안 그림. */
|
||||
export default function Layout() {
|
||||
const tauri = hostKind() === "tauri"
|
||||
return (
|
||||
<div className="flex min-h-screen flex-col">
|
||||
<header className="border-b">
|
||||
<div className="container flex h-14 items-center justify-between">
|
||||
<Link to={PATHS.HOME} className="font-semibold">
|
||||
Frontend Template
|
||||
</Link>
|
||||
<ThemeToggle />
|
||||
</div>
|
||||
</header>
|
||||
<main className="container flex min-h-0 flex-1 flex-col py-8">
|
||||
{!tauri && (
|
||||
<header className="border-border border-b">
|
||||
<div className="mx-auto flex h-9 w-full max-w-5xl items-center justify-between pr-2 pl-3">
|
||||
<Link to={PATHS.HOME} className="flex items-center gap-2 text-[11px] font-semibold">
|
||||
<LogoMark width={26} />
|
||||
CodeAssist
|
||||
</Link>
|
||||
<ThemeToggle />
|
||||
</div>
|
||||
</header>
|
||||
)}
|
||||
<main className="mx-auto flex min-h-0 w-full max-w-5xl flex-1 flex-col px-4 py-8">
|
||||
<Outlet />
|
||||
</main>
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
import logo from "@/assets/logo.png"
|
||||
import logoDark from "@/assets/logo-dark.png"
|
||||
import { cn } from "@/lib/utils/cn"
|
||||
|
||||
interface LogoMarkProps {
|
||||
/** 로고 가로 폭(px) — 원본 비율(약 2.06:1) 유지 */
|
||||
width?: number
|
||||
className?: string
|
||||
}
|
||||
|
||||
/** 브랜드 마크 — ABAP_OPENCODE 와 같은 PwC 로고. 다크에선 밝은 글자 변형. */
|
||||
export function LogoMark({ width = 38, className }: LogoMarkProps) {
|
||||
return (
|
||||
<span className={cn("inline-flex shrink-0", className)} style={{ width }}>
|
||||
<img src={logo} alt="PwC" className="block h-auto w-full dark:hidden" />
|
||||
<img src={logoDark} alt="PwC" className="hidden h-auto w-full dark:block" />
|
||||
</span>
|
||||
)
|
||||
}
|
||||
@@ -35,8 +35,8 @@ const ORDER: Theme[] = ["light", "dark", "system"]
|
||||
export const useThemeStore = create<ThemeState>()(
|
||||
persist(
|
||||
(set, get) => ({
|
||||
theme: "light",
|
||||
resolved: resolve("light"),
|
||||
theme: "dark", // ABAP_OPENCODE 와 같이 다크가 기본
|
||||
resolved: resolve("dark"),
|
||||
setTheme: (theme) => {
|
||||
const resolved = resolve(theme)
|
||||
applyToDocument(resolved)
|
||||
|
||||
@@ -83,34 +83,35 @@ export type DerivedTokens = {
|
||||
// 의미색 — 모든 테마 공통
|
||||
const DESTRUCTIVE = "oklch(0.577 0.245 27.325)"
|
||||
|
||||
// 사용자가 선택한 목업 색을 그대로 유지. 명도 정렬로 primary가 차콜로 바뀌지 않게 함.
|
||||
// 기본 팔레트(id 는 하위호환으로 clean-blue 유지) — ABAP_OPENCODE DESIGN_SYSTEM.md §7 라이트 값.
|
||||
// globals.css :root 와 같은 값. 명도 정렬(derive)을 안 타서 블루가 차콜로 안 바뀜.
|
||||
const CLEAN_BLUE_TOKENS: DerivedTokens = {
|
||||
background: "#FFFFFF",
|
||||
foreground: "#242C39",
|
||||
card: "#F7F9FC",
|
||||
cardForeground: "#242C39",
|
||||
background: "#FAFBFC",
|
||||
foreground: "#1A1D22",
|
||||
card: "#FFFFFF",
|
||||
cardForeground: "#1A1D22",
|
||||
popover: "#FFFFFF",
|
||||
popoverForeground: "#242C39",
|
||||
primary: "#034EA2",
|
||||
popoverForeground: "#1A1D22",
|
||||
primary: "#3563E9",
|
||||
primaryForeground: "#FFFFFF",
|
||||
secondary: "#F0F3F8",
|
||||
secondaryForeground: "#242C39",
|
||||
muted: "#F7F9FC",
|
||||
mutedForeground: "#606D7F",
|
||||
accent: "#EDF3FC",
|
||||
accentForeground: "#034EA2",
|
||||
secondary: "#EFF1F4",
|
||||
secondaryForeground: "#1A1D22",
|
||||
muted: "#F2F3F5",
|
||||
mutedForeground: "#494F59",
|
||||
accent: "#E4E8EF",
|
||||
accentForeground: "#1A1D22",
|
||||
destructive: DESTRUCTIVE,
|
||||
border: "#E1E6EE",
|
||||
input: "#BBC7D8",
|
||||
ring: "#034EA2",
|
||||
sidebar: "#F7F9FC",
|
||||
sidebarForeground: "#242C39",
|
||||
sidebarPrimary: "#034EA2",
|
||||
border: "#E1E3E8",
|
||||
input: "#E1E3E8",
|
||||
ring: "#3563E9",
|
||||
sidebar: "#F2F3F5",
|
||||
sidebarForeground: "#1A1D22",
|
||||
sidebarPrimary: "#3563E9",
|
||||
sidebarPrimaryForeground: "#FFFFFF",
|
||||
sidebarAccent: "#EDF3FC",
|
||||
sidebarAccentForeground: "#034EA2",
|
||||
sidebarBorder: "#E1E6EE",
|
||||
sidebarRing: "#034EA2",
|
||||
sidebarAccent: "#E9EBEF",
|
||||
sidebarAccentForeground: "#1A1D22",
|
||||
sidebarBorder: "#E9EBEF",
|
||||
sidebarRing: "#3563E9",
|
||||
}
|
||||
|
||||
export function derivePaletteTokens(
|
||||
|
||||
@@ -10,9 +10,9 @@ export type RawPalette = {
|
||||
|
||||
export const PALETTES: RawPalette[] = [
|
||||
{
|
||||
id: "clean-blue",
|
||||
name: "클린 블루",
|
||||
swatch: ["#FFFFFF", "#F7F9FC", "#034EA2", "#242C39"],
|
||||
id: "clean-blue", // id 는 저장된 값 호환 위해 유지 — 실제 토큰은 derive.ts 워크스페이스(ABAP_OPENCODE) 값
|
||||
name: "워크스페이스",
|
||||
swatch: ["#FAFBFC", "#F2F3F5", "#3563E9", "#1A1D22"],
|
||||
},
|
||||
// Sheet 1
|
||||
{ id: "dark-teal", name: "Dark Teal", swatch: ["#1A222B", "#2D3540", "#009E9F", "#ECECEC"] },
|
||||
|
||||
Reference in New Issue
Block a user