feat: replace header emoji buttons with reference Lucide-style icons
Add users, plus, and dual-monitor SVG icons with matching 32px circular button styles from Design Planning Hub. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -1,3 +1,7 @@
|
|||||||
|
import { useState } from 'react';
|
||||||
|
import { isDetailWindowOpen } from '../../lib/dualMonitor';
|
||||||
|
import { DualMonitorIcon, PlusIcon, UsersIcon } from './HeaderIcons';
|
||||||
|
|
||||||
interface Stats {
|
interface Stats {
|
||||||
total: number;
|
total: number;
|
||||||
inProgress: number;
|
inProgress: number;
|
||||||
@@ -15,9 +19,6 @@ interface DashboardHeaderProps {
|
|||||||
onOpenTaskManager: () => void;
|
onOpenTaskManager: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
const ICON_BTN =
|
|
||||||
'team-status-btn-new grid h-8 w-8 shrink-0 place-items-center rounded-full border-[1.5px] border-[#1a4d42] bg-[linear-gradient(180deg,#0d3f34_0%,#051f19_100%)] text-[#cef1eb] shadow-[0_0_0_1px_#000,0_2px_2px_rgba(0,0,0,0.60)] transition hover:brightness-125';
|
|
||||||
|
|
||||||
const STAT_ACCENT = {
|
const STAT_ACCENT = {
|
||||||
전체: 'text-[#ffdb3a]',
|
전체: 'text-[#ffdb3a]',
|
||||||
IN_PROGRESS: 'text-[#10b981]',
|
IN_PROGRESS: 'text-[#10b981]',
|
||||||
@@ -34,8 +35,15 @@ export function DashboardHeader({
|
|||||||
onOpenDetailWindow,
|
onOpenDetailWindow,
|
||||||
onOpenTaskManager,
|
onOpenTaskManager,
|
||||||
}: DashboardHeaderProps) {
|
}: DashboardHeaderProps) {
|
||||||
|
const [detailViewActive, setDetailViewActive] = useState(isDetailWindowOpen);
|
||||||
const quarterLabel = quarter.replace(/^(\d{4})-Q(\d)$/, '$1 $2분기 업무');
|
const quarterLabel = quarter.replace(/^(\d{4})-Q(\d)$/, '$1 $2분기 업무');
|
||||||
|
|
||||||
|
const handleOpenDetailWindow = () => {
|
||||||
|
void Promise.resolve(onOpenDetailWindow()).then(() => {
|
||||||
|
setDetailViewActive(isDetailWindowOpen());
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
const statItems = [
|
const statItems = [
|
||||||
{ label: '전체', value: stats.total, statusKey: '전체' as const },
|
{ label: '전체', value: stats.total, statusKey: '전체' as const },
|
||||||
{ label: '진행', value: stats.inProgress, statusKey: 'IN_PROGRESS' as const },
|
{ label: '진행', value: stats.inProgress, statusKey: 'IN_PROGRESS' as const },
|
||||||
@@ -52,8 +60,8 @@ export function DashboardHeader({
|
|||||||
<span>|</span>
|
<span>|</span>
|
||||||
<span>People Growth Hub</span>
|
<span>People Growth Hub</span>
|
||||||
</span>
|
</span>
|
||||||
<button type="button" title="팀 현황" className={`${ICON_BTN} text-[15px]`}>
|
<button type="button" title="팀 현황" className="team-status-btn-new">
|
||||||
👥
|
<UsersIcon size={16} />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -78,21 +86,16 @@ export function DashboardHeader({
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="side-right-actions shrink-0">
|
<div className="side-right-actions shrink-0">
|
||||||
<button
|
<button type="button" onClick={onOpenTaskManager} title="신규 프로젝트 추가" className="header-action-btn-new">
|
||||||
type="button"
|
<PlusIcon size={16} />
|
||||||
onClick={onOpenTaskManager}
|
|
||||||
title="업무관리"
|
|
||||||
className={`header-action-btn-new ${ICON_BTN} text-[18px] font-bold leading-none`}
|
|
||||||
>
|
|
||||||
+
|
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={onOpenDetailWindow}
|
onClick={handleOpenDetailWindow}
|
||||||
title="우측 모니터에 상세 창 열기"
|
title="듀얼뷰"
|
||||||
className={`header-view-btn-new ${ICON_BTN} text-[16px] leading-none`}
|
className={`header-view-btn-new ${detailViewActive ? 'active' : ''}`}
|
||||||
>
|
>
|
||||||
🖥
|
<DualMonitorIcon size={16} />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
|
|||||||
73
frontend/src/components/dashboard/HeaderIcons.tsx
Normal file
73
frontend/src/components/dashboard/HeaderIcons.tsx
Normal file
@@ -0,0 +1,73 @@
|
|||||||
|
import type { ReactNode } from 'react';
|
||||||
|
|
||||||
|
interface IconProps {
|
||||||
|
size?: number;
|
||||||
|
className?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
function LucideSvg({ size = 16, className, children }: IconProps & { children: ReactNode }) {
|
||||||
|
return (
|
||||||
|
<svg
|
||||||
|
width={size}
|
||||||
|
height={size}
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
fill="none"
|
||||||
|
stroke="currentColor"
|
||||||
|
strokeWidth="2"
|
||||||
|
strokeLinecap="round"
|
||||||
|
strokeLinejoin="round"
|
||||||
|
className={className}
|
||||||
|
aria-hidden
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 참고 사이트 lucide `users` */
|
||||||
|
export function UsersIcon({ size = 16, className }: IconProps) {
|
||||||
|
return (
|
||||||
|
<LucideSvg size={size} className={className}>
|
||||||
|
<path d="M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2" />
|
||||||
|
<path d="M16 3.128a4 4 0 0 1 0 7.744" />
|
||||||
|
<path d="M22 21v-2a4 4 0 0 0-3-3.87" />
|
||||||
|
<circle cx="9" cy="7" r="4" />
|
||||||
|
</LucideSvg>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 참고 사이트 lucide `plus` */
|
||||||
|
export function PlusIcon({ size = 16, className }: IconProps) {
|
||||||
|
return (
|
||||||
|
<LucideSvg size={size} className={className}>
|
||||||
|
<path d="M5 12h14" />
|
||||||
|
<path d="M12 5v14" />
|
||||||
|
</LucideSvg>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function MonitorIcon({ size = 16, className }: IconProps) {
|
||||||
|
return (
|
||||||
|
<LucideSvg size={size} className={className}>
|
||||||
|
<rect width="20" height="14" x="2" y="3" rx="2" />
|
||||||
|
<line x1="8" x2="16" y1="21" y2="21" />
|
||||||
|
<line x1="12" x2="12" y1="17" y2="21" />
|
||||||
|
</LucideSvg>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 참고 사이트 `dual-monitor-icon-wrap` — monitor 2개 겹침 */
|
||||||
|
export function DualMonitorIcon({ size = 16, className }: IconProps) {
|
||||||
|
const monitorSize = size * 0.75;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className={`dual-monitor-icon-wrap ${className ?? ''}`}
|
||||||
|
style={{ width: size, height: size }}
|
||||||
|
aria-hidden
|
||||||
|
>
|
||||||
|
<MonitorIcon size={monitorSize} className="m-icon m-back" />
|
||||||
|
<MonitorIcon size={monitorSize} className="m-icon m-front" />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -115,6 +115,78 @@ body,
|
|||||||
gap: 12px;
|
gap: 12px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* F12 — 헤더 원형 아이콘 버튼 (team / + / 듀얼모니터) */
|
||||||
|
.header-action-btn-new,
|
||||||
|
.header-view-btn-new,
|
||||||
|
.team-status-btn-new {
|
||||||
|
display: flex;
|
||||||
|
flex-shrink: 0;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
width: 32px;
|
||||||
|
height: 32px;
|
||||||
|
border: 1.5px solid #1a4d42;
|
||||||
|
border-radius: 50%;
|
||||||
|
color: #cef1eb;
|
||||||
|
cursor: pointer;
|
||||||
|
background: linear-gradient(180deg, #0d3f34 0%, #051f19 100%);
|
||||||
|
box-shadow: 0 0 0 1px #000, 0 2px 2px rgba(0, 0, 0, 0.6);
|
||||||
|
transition: all 0.2s ease-in-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header-action-btn-new svg,
|
||||||
|
.header-view-btn-new svg,
|
||||||
|
.team-status-btn-new svg {
|
||||||
|
color: inherit;
|
||||||
|
filter: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header-action-btn-new:hover,
|
||||||
|
.header-view-btn-new:hover,
|
||||||
|
.team-status-btn-new:hover {
|
||||||
|
color: #fff;
|
||||||
|
border-color: #36816d;
|
||||||
|
background: linear-gradient(180deg, #115244 0%, #072b23 100%);
|
||||||
|
transform: translateY(-1px);
|
||||||
|
box-shadow:
|
||||||
|
0 0 0 1px #000,
|
||||||
|
0 0 8px rgba(36, 204, 158, 0.4),
|
||||||
|
0 4px 8px rgba(0, 0, 0, 0.7);
|
||||||
|
}
|
||||||
|
|
||||||
|
.header-action-btn-new:active,
|
||||||
|
.header-action-btn-new.active,
|
||||||
|
.header-view-btn-new.active,
|
||||||
|
.team-status-btn-new.active {
|
||||||
|
color: #cef1eb;
|
||||||
|
border-color: #1a4d42;
|
||||||
|
background: linear-gradient(180deg, #072b23 0%, #051f19 100%);
|
||||||
|
transform: translateY(0);
|
||||||
|
box-shadow: 0 0 0 1px #000, 0 1px 2px rgba(0, 0, 0, 0.6);
|
||||||
|
}
|
||||||
|
|
||||||
|
.dual-monitor-icon-wrap {
|
||||||
|
position: relative;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dual-monitor-icon-wrap .m-icon {
|
||||||
|
position: absolute;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dual-monitor-icon-wrap .m-back {
|
||||||
|
top: 0;
|
||||||
|
left: -3px;
|
||||||
|
opacity: 0.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dual-monitor-icon-wrap .m-front {
|
||||||
|
right: -3px;
|
||||||
|
bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
/* F12 Design Planning Hub — 타이틀 */
|
/* F12 Design Planning Hub — 타이틀 */
|
||||||
.main_tit {
|
.main_tit {
|
||||||
color: #bad8ca;
|
color: #bad8ca;
|
||||||
|
|||||||
Reference in New Issue
Block a user