시계 위젯 구현
This commit is contained in:
@@ -2,7 +2,9 @@
|
||||
|
||||
interface AnalogClockProps {
|
||||
time: Date;
|
||||
theme: "light" | "dark" | "blue" | "gradient";
|
||||
theme: "light" | "dark" | "custom";
|
||||
timezone?: string;
|
||||
customColor?: string; // 사용자 지정 색상
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -10,8 +12,9 @@ interface AnalogClockProps {
|
||||
* - SVG 기반 아날로그 시계
|
||||
* - 시침, 분침, 초침 애니메이션
|
||||
* - 테마별 색상 지원
|
||||
* - 타임존 표시
|
||||
*/
|
||||
export function AnalogClock({ time, theme }: AnalogClockProps) {
|
||||
export function AnalogClock({ time, theme, timezone, customColor }: AnalogClockProps) {
|
||||
const hours = time.getHours() % 12;
|
||||
const minutes = time.getMinutes();
|
||||
const seconds = time.getSeconds();
|
||||
@@ -22,11 +25,14 @@ export function AnalogClock({ time, theme }: AnalogClockProps) {
|
||||
const hourAngle = hours * 30 + minutes * 0.5 - 90; // 30도씩 + 분당 0.5도
|
||||
|
||||
// 테마별 색상
|
||||
const colors = getThemeColors(theme);
|
||||
const colors = getThemeColors(theme, customColor);
|
||||
|
||||
// 타임존 라벨
|
||||
const timezoneLabel = timezone ? getTimezoneLabel(timezone) : "";
|
||||
|
||||
return (
|
||||
<div className="flex h-full items-center justify-center p-4">
|
||||
<svg viewBox="0 0 200 200" className="h-full max-h-[250px] w-full max-w-[250px]">
|
||||
<div className="flex h-full flex-col items-center justify-center p-2">
|
||||
<svg viewBox="0 0 200 200" className="h-full max-h-[200px] w-full max-w-[200px]">
|
||||
{/* 시계판 배경 */}
|
||||
<circle cx="100" cy="100" r="98" fill={colors.background} stroke={colors.border} strokeWidth="2" />
|
||||
|
||||
@@ -110,14 +116,56 @@ export function AnalogClock({ time, theme }: AnalogClockProps) {
|
||||
<circle cx="100" cy="100" r="6" fill={colors.center} />
|
||||
<circle cx="100" cy="100" r="3" fill={colors.background} />
|
||||
</svg>
|
||||
|
||||
{/* 타임존 표시 */}
|
||||
{timezoneLabel && (
|
||||
<div className="mt-1 text-center text-xs font-medium" style={{ color: colors.number }}>
|
||||
{timezoneLabel}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 타임존 라벨 반환
|
||||
*/
|
||||
function getTimezoneLabel(timezone: string): string {
|
||||
const timezoneLabels: Record<string, string> = {
|
||||
"Asia/Seoul": "서울 (KST)",
|
||||
"Asia/Tokyo": "도쿄 (JST)",
|
||||
"Asia/Shanghai": "베이징 (CST)",
|
||||
"America/New_York": "뉴욕 (EST)",
|
||||
"America/Los_Angeles": "LA (PST)",
|
||||
"Europe/London": "런던 (GMT)",
|
||||
"Europe/Paris": "파리 (CET)",
|
||||
"Australia/Sydney": "시드니 (AEDT)",
|
||||
};
|
||||
|
||||
return timezoneLabels[timezone] || timezone.split("/")[1];
|
||||
}
|
||||
|
||||
/**
|
||||
* 테마별 색상 반환
|
||||
*/
|
||||
function getThemeColors(theme: string) {
|
||||
function getThemeColors(theme: string, customColor?: string) {
|
||||
if (theme === "custom" && customColor) {
|
||||
// 사용자 지정 색상 사용 (약간 밝게/어둡게 조정)
|
||||
const lighterColor = adjustColor(customColor, 40);
|
||||
const darkerColor = adjustColor(customColor, -40);
|
||||
|
||||
return {
|
||||
background: lighterColor,
|
||||
border: customColor,
|
||||
tick: customColor,
|
||||
number: darkerColor,
|
||||
hourHand: darkerColor,
|
||||
minuteHand: customColor,
|
||||
secondHand: "#ef4444",
|
||||
center: darkerColor,
|
||||
};
|
||||
}
|
||||
|
||||
const themes = {
|
||||
light: {
|
||||
background: "#ffffff",
|
||||
@@ -139,27 +187,35 @@ function getThemeColors(theme: string) {
|
||||
secondHand: "#ef4444",
|
||||
center: "#f9fafb",
|
||||
},
|
||||
blue: {
|
||||
background: "#dbeafe",
|
||||
border: "#3b82f6",
|
||||
tick: "#60a5fa",
|
||||
number: "#1e40af",
|
||||
hourHand: "#1e3a8a",
|
||||
minuteHand: "#2563eb",
|
||||
custom: {
|
||||
background: "#e0e7ff",
|
||||
border: "#6366f1",
|
||||
tick: "#818cf8",
|
||||
number: "#4338ca",
|
||||
hourHand: "#4338ca",
|
||||
minuteHand: "#6366f1",
|
||||
secondHand: "#ef4444",
|
||||
center: "#1e3a8a",
|
||||
},
|
||||
gradient: {
|
||||
background: "#fce7f3",
|
||||
border: "#ec4899",
|
||||
tick: "#f472b6",
|
||||
number: "#9333ea",
|
||||
hourHand: "#7c3aed",
|
||||
minuteHand: "#a855f7",
|
||||
secondHand: "#ef4444",
|
||||
center: "#7c3aed",
|
||||
center: "#4338ca",
|
||||
},
|
||||
};
|
||||
|
||||
return themes[theme as keyof typeof themes] || themes.light;
|
||||
}
|
||||
|
||||
/**
|
||||
* 색상 밝기 조정
|
||||
*/
|
||||
function adjustColor(color: string, amount: number): string {
|
||||
const clamp = (num: number) => Math.min(255, Math.max(0, num));
|
||||
|
||||
const hex = color.replace("#", "");
|
||||
const r = parseInt(hex.substring(0, 2), 16);
|
||||
const g = parseInt(hex.substring(2, 4), 16);
|
||||
const b = parseInt(hex.substring(4, 6), 16);
|
||||
|
||||
const newR = clamp(r + amount);
|
||||
const newG = clamp(g + amount);
|
||||
const newB = clamp(b + amount);
|
||||
|
||||
return `#${newR.toString(16).padStart(2, "0")}${newG.toString(16).padStart(2, "0")}${newB.toString(16).padStart(2, "0")}`;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user