Files
pipeline/frontend/components/providers/ThemeProvider.tsx
T
chpark 37cac72085 refactor: Pipeline 네이밍 통일 및 AI 에이전트/장비 연결 기능 추가
- Docker/K8s 배포 설정을 pipeline-backend/pipeline-front로 통일
- 네임스페이스, 서비스, PVC 등 k8s 리소스명 pipeline-* 로 변경
- AI 에이전트 관리 기능 추가 (에이전트, 그룹, 프로바이더, 대화, API 키, 지식베이스)
- 장비 연결 관리 기능 추가 (PLC/Modbus/OPC-UA/MQTT)
- 배치 스케줄러에 AI agent/device collection/crawling 타입 추가
- 배치 편집 UI 개선 (6가지 실행 방식 지원)
- 회사별 페이지(COMPANY_*) 제거 및 AdminPageRenderer 최적화
- 메뉴 재구성: 장비 연결 관리 시스템관리로 이동, 에이전트 오케스트레이션으로 개명
- ai-assistant 디렉토리 제거 (backend-node로 통합)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-20 12:14:50 +09:00

101 lines
2.9 KiB
TypeScript

"use client";
import { ThemeProvider as NextThemesProvider } from "next-themes";
import { createContext, useCallback, useContext, useEffect, useState } from "react";
export type ColorTheme = "blue" | "teal" | "green" | "purple" | "red" | "dark";
interface ColorThemeContextType {
colorTheme: ColorTheme;
setColorTheme: (theme: ColorTheme, event?: React.MouseEvent) => void;
}
const ColorThemeContext = createContext<ColorThemeContextType>({
colorTheme: "blue",
setColorTheme: () => {},
});
export const useColorTheme = () => useContext(ColorThemeContext);
const applyTheme = (theme: ColorTheme) => {
const html = document.documentElement;
html.classList.remove("theme-teal", "theme-green", "theme-purple", "theme-red", "dark");
if (theme === "dark") {
html.classList.add("dark");
} else if (theme !== "blue") {
html.classList.add(`theme-${theme}`);
}
};
const THEME_COLORS: Record<ColorTheme, string> = {
blue: "#3b82f6",
teal: "#14b8a6",
green: "#22c55e",
purple: "#8b5cf6",
red: "#ef4444",
dark: "#0f172a",
};
export function ThemeProvider({ children }: { children: React.ReactNode }) {
const [colorTheme, setColorThemeState] = useState<ColorTheme>("blue");
useEffect(() => {
const saved = localStorage.getItem("colorTheme") as ColorTheme | null;
if (saved) {
setColorThemeState(saved);
applyTheme(saved);
}
}, []);
const setColorTheme = useCallback((theme: ColorTheme) => {
// 커튼 오버레이 생성 (왼쪽에서 오른쪽으로 밀면서 덮기)
const curtain = document.createElement("div");
curtain.style.cssText = `
position: fixed;
inset: 0;
z-index: 99999;
pointer-events: none;
background: ${THEME_COLORS[theme]};
transform: translateX(-100%);
will-change: transform;
`;
document.body.appendChild(curtain);
// 1단계: 커튼이 왼→오 밀면서 화면을 덮음
requestAnimationFrame(() => {
curtain.style.transition = "transform 0.35s cubic-bezier(0.4, 0, 0.2, 1)";
curtain.style.transform = "translateX(0)";
});
// 2단계: 커튼이 화면을 완전히 덮었을 때 테마 적용
setTimeout(() => {
applyTheme(theme);
setColorThemeState(theme);
localStorage.setItem("colorTheme", theme);
// 3단계: 커튼이 오른쪽으로 빠져나감
requestAnimationFrame(() => {
curtain.style.transition = "transform 0.35s cubic-bezier(0.4, 0, 0.2, 1)";
curtain.style.transform = "translateX(100%)";
});
// 정리
setTimeout(() => curtain.remove(), 400);
}, 370);
}, []);
return (
<ColorThemeContext.Provider value={{ colorTheme, setColorTheme }}>
<NextThemesProvider
attribute="class"
defaultTheme="light"
enableSystem={false}
disableTransitionOnChange
>
{children}
</NextThemesProvider>
</ColorThemeContext.Provider>
);
}