9a8196a395
주요 변경: - 회사 라우트 정리: COMPANY_7/8/9/10/29/30 6개 폴더 삭제, COMPANY_16(하이큐마그)만 유지 - 모듈 수 17,470 → 16,121, /main 컴파일 시간 110s → 53s - 브랜딩: VEXPLOR → RPS (로고/파비콘/타이틀/메타데이터/푸터 회사정보 전부 교체) - 로그인 페이지 fito 스타일 리디자인 (다크 그라디언트 + 글래스 카드 + 격자 배경 + 하단 푸터) - AppLayout/TabBar/ThemeToggle/ThemeProvider/Logo Pipeline 디자인 채용 - 6색 컬러 테마 시스템 (blue/teal/green/purple/red/dark) + 좌→우 커튼 전환 애니메이션 - 우상단 모드 전환 알약 버튼 (관리자만 노출) - 시안톤 탭 + Teal 활성 탭, 130px 컴팩트 너비 - 도커 포트 충돌 회피: 백엔드 8080→8090, 프론트 9771→9781, 컨테이너명 rps_backend/rps-front - DB: vexplor_rps 신규 생성 후 vexplor_dev 데이터 이관, DATABASE_URL 변경 - useAuth: SUPER_ADMIN userType도 isAdmin으로 인정 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
67 lines
2.5 KiB
TypeScript
67 lines
2.5 KiB
TypeScript
"use client";
|
|
|
|
import { useEffect, useState, useRef } from "react";
|
|
import { Palette, Check } from "lucide-react";
|
|
import { useColorTheme, type ColorTheme } from "@/components/providers/ThemeProvider";
|
|
|
|
const THEMES: { id: ColorTheme; label: string; color: string }[] = [
|
|
{ id: "blue", label: "Blue", color: "#3b82f6" },
|
|
{ id: "teal", label: "Teal", color: "#14b8a6" },
|
|
{ id: "green", label: "Green", color: "#22c55e" },
|
|
{ id: "purple", label: "Purple", color: "#8b5cf6" },
|
|
{ id: "red", label: "Red", color: "#ef4444" },
|
|
{ id: "dark", label: "Dark", color: "#1e293b" },
|
|
];
|
|
|
|
export function ThemeToggle({ collapsed = false }: { collapsed?: boolean }) {
|
|
const { colorTheme, setColorTheme } = useColorTheme();
|
|
const [open, setOpen] = useState(false);
|
|
const ref = useRef<HTMLDivElement>(null);
|
|
|
|
useEffect(() => {
|
|
const handleClick = (e: MouseEvent) => {
|
|
if (ref.current && !ref.current.contains(e.target as Node)) {
|
|
setOpen(false);
|
|
}
|
|
};
|
|
document.addEventListener("mousedown", handleClick);
|
|
return () => document.removeEventListener("mousedown", handleClick);
|
|
}, []);
|
|
|
|
const current = THEMES.find((t) => t.id === colorTheme) || THEMES[0];
|
|
|
|
return (
|
|
<div ref={ref} className="relative">
|
|
<button
|
|
onClick={() => setOpen(!open)}
|
|
className="flex h-6 items-center gap-1 rounded-full border border-border/50 px-1.5 py-0.5 text-[10px] font-medium transition-colors hover:bg-accent"
|
|
>
|
|
<span className="h-2.5 w-2.5 rounded-full" style={{ backgroundColor: current.color }} />
|
|
<span className="text-muted-foreground">{current.label}</span>
|
|
</button>
|
|
|
|
{open && (
|
|
<div className="absolute right-0 top-full mt-1 z-[9999] min-w-[140px] rounded-xl border border-border bg-popover p-1.5 shadow-lg">
|
|
{THEMES.map((theme) => (
|
|
<button
|
|
key={theme.id}
|
|
onClick={(e) => {
|
|
setColorTheme(theme.id, e);
|
|
setOpen(false);
|
|
}}
|
|
className="flex w-full items-center gap-2.5 rounded-lg px-2.5 py-2 text-sm transition-colors hover:bg-accent"
|
|
>
|
|
<span
|
|
className="h-3.5 w-3.5 rounded-full ring-1 ring-border/30"
|
|
style={{ backgroundColor: theme.color }}
|
|
/>
|
|
<span className="flex-1 text-left text-[13px] font-medium">{theme.label}</span>
|
|
{colorTheme === theme.id && <Check className="h-3.5 w-3.5 text-primary" />}
|
|
</button>
|
|
))}
|
|
</div>
|
|
)}
|
|
</div>
|
|
);
|
|
}
|