From e0dd7d1bc476677323b41555eacf1342bf1de1db Mon Sep 17 00:00:00 2001 From: caadiq Date: Sat, 18 Apr 2026 12:15:04 +0900 Subject: [PATCH] =?UTF-8?q?=EB=B3=B4=EC=8A=A4=20=EC=88=98=EC=9D=B5=20?= =?UTF-8?q?=EA=B3=84=EC=82=B0=EA=B8=B0=20=ED=8E=98=EC=9D=B4=EC=A7=80=20?= =?UTF-8?q?=ED=85=8C=EB=A7=88=20=ED=86=A0=ED=81=B0=ED=99=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Select/Tooltip 공통 컴포넌트 테마 대응 - BossCrystal 루트/CharacterPanel/BossSelector 전체 이관 - 비활성 row/난이도 버튼 테마별 처리 (--disabled-opacity, --inactive-filter) - 라이트 테마 warning 색상 갈색 → 주황 계열로 조정 Co-Authored-By: Claude Opus 4.7 (1M context) --- frontend/src/components/Select.jsx | 80 +++++++---- frontend/src/components/Tooltip.jsx | 5 +- .../src/features/boss-crystal/BossCrystal.jsx | 20 ++- .../boss-crystal/user/BossSelector.jsx | 65 +++++++-- .../boss-crystal/user/CharacterPanel.jsx | 129 ++++++++++++++---- frontend/src/index.css | 94 +++++++++++++ 6 files changed, 320 insertions(+), 73 deletions(-) diff --git a/frontend/src/components/Select.jsx b/frontend/src/components/Select.jsx index ae71e59..da5fb31 100644 --- a/frontend/src/components/Select.jsx +++ b/frontend/src/components/Select.jsx @@ -61,35 +61,49 @@ export default function Select({ value, onChange, options, disabled, className = animate={{ opacity: 1, y: 0, scale: 1 }} exit={{ opacity: 0, y: flipUp ? 6 : -6, scale: 0.98 }} transition={{ duration: 0.15 }} - className={`fixed z-[100] rounded-lg border border-white/10 bg-gray-900 text-white shadow-xl overflow-hidden ${ + className={`fixed z-[100] rounded-lg border overflow-hidden ${ flipUp ? 'origin-bottom' : 'origin-top' }`} - style={ - flipUp + style={{ + background: 'var(--popup-bg)', + borderColor: 'var(--popup-border)', + boxShadow: 'var(--popup-shadow)', + color: 'var(--text-strong)', + ...(flipUp ? { bottom: pos.bottomOffset + 4, left: pos.left, minWidth: pos.width } : { top: pos.top + 4, left: pos.left, minWidth: pos.width } - } + ), + }} >
- {options.map((opt) => ( - - ))} + {options.map((opt) => { + const isActive = opt.value === value + return ( + + ) + })}
)} @@ -103,14 +117,24 @@ export default function Select({ value, onChange, options, disabled, className = type="button" disabled={disabled} onClick={() => !disabled && setOpen((v) => !v)} - className={`w-full flex items-center justify-between gap-2 rounded-lg border bg-gray-950 px-3 py-2 text-sm transition outline-none ${ - open ? 'border-emerald-500/50' : 'border-white/10 hover:border-white/20' - } ${disabled ? 'opacity-50 !cursor-default' : ''}`} + className={`w-full flex items-center justify-between gap-2 rounded-lg border px-3 py-2 text-sm outline-none ${ + disabled ? 'opacity-50 !cursor-default' : '' + }`} + style={{ + background: 'var(--input-bg)', + borderColor: open ? 'var(--input-border-focus)' : 'var(--input-border)', + color: 'var(--text-strong)', + }} > - + {selected ? selected.label : placeholder} - + diff --git a/frontend/src/components/Tooltip.jsx b/frontend/src/components/Tooltip.jsx index 9ddb917..f89b1a0 100644 --- a/frontend/src/components/Tooltip.jsx +++ b/frontend/src/components/Tooltip.jsx @@ -98,8 +98,11 @@ export default function Tooltip({ text, children, placement = 'top', delay = 200 zIndex: 9999, opacity: coords ? 1 : 0, transition: 'opacity 120ms ease-out', + background: 'var(--tooltip-bg)', + color: 'var(--tooltip-text)', + borderColor: 'var(--tooltip-border)', }} - className="pointer-events-none px-2 py-1 rounded-md bg-gray-900 border border-white/10 text-xs text-gray-200 shadow-lg whitespace-nowrap" + className="pointer-events-none px-2 py-1 rounded-md border text-xs shadow-lg whitespace-nowrap" > {text} , diff --git a/frontend/src/features/boss-crystal/BossCrystal.jsx b/frontend/src/features/boss-crystal/BossCrystal.jsx index f0ce9a9..ebe57c9 100644 --- a/frontend/src/features/boss-crystal/BossCrystal.jsx +++ b/frontend/src/features/boss-crystal/BossCrystal.jsx @@ -72,12 +72,26 @@ export default function BossCrystal() { return (
{isLoading ? ( -
-
+
+
) : (
-
+
+
좌측에서 캐릭터를 선택해주세요
) @@ -14,16 +21,37 @@ export default function BossSelector({ characterName, bosses, selections, onChan if (bosses.length === 0) { return ( -
+
등록된 보스가 없습니다
) } return ( -
+
{/* 헤더 (고정) */} -
+
보스
난이도
파티원 수
@@ -31,7 +59,7 @@ export default function BossSelector({ characterName, bosses, selections, onChan
{/* 목록 (스크롤) */}
-
+
{bosses.map((boss) => { const availableDiffs = DIFFICULTIES.filter((d) => boss.difficulties.some((bd) => bd.difficulty === d.key) @@ -52,13 +80,20 @@ export default function BossSelector({ characterName, bosses, selections, onChan return (
{/* 보스 이미지 + 이름 */}
-
+
{boss.name}
{boss.name} @@ -72,7 +107,7 @@ export default function BossSelector({ characterName, bosses, selections, onChan background: d.colors.bg, borderColor: d.colors.border, color: d.colors.text, - filter: active ? 'none' : 'brightness(0.4)', + filter: active ? 'none' : 'var(--inactive-filter)', } return (
{/* 수익 */} -
+
{sel ? formatMeso(revenue) : '-'}
diff --git a/frontend/src/features/boss-crystal/user/CharacterPanel.jsx b/frontend/src/features/boss-crystal/user/CharacterPanel.jsx index 512295b..77d47c1 100644 --- a/frontend/src/features/boss-crystal/user/CharacterPanel.jsx +++ b/frontend/src/features/boss-crystal/user/CharacterPanel.jsx @@ -48,14 +48,16 @@ function CharacterContent({ char, selections, bosses }) { draggable={false} /> ) : ( - ? + ? )}
{char.character_name} - Lv.{char.character_level} · {char.job_name} + + Lv.{char.character_level} · {char.job_name} +
{visibleBosses.length > 0 ? ( @@ -68,7 +70,13 @@ function CharacterContent({ char, selections, bosses }) { text={`${diff?.label || ''} ${item.boss.name} · ${formatMeso(item.revenue)}`} >
-
+
@@ -85,17 +93,38 @@ function CharacterContent({ char, selections, bosses }) { })}
) : ( -
보스 미선택
+
+ 보스 미선택 +
)}
-
+
- 0 ? 'text-amber-300' : 'text-gray-600'}`}>{count} - / {MAX_PER_CHARACTER} + 0 ? 'var(--warning-text-bright)' : 'var(--text-dim)' }} + > + {count} + + 0 ? 'var(--warning-text-dim)' : 'var(--text-dim)' }} + > + / {MAX_PER_CHARACTER} +
-
0 ? 'text-emerald-300' : 'text-gray-700'}`}> +
0 ? 'var(--accent-bright)' : 'var(--text-dim)' }} + > {count > 0 ? formatMeso(totalRevenue) : '-'}
@@ -122,17 +151,17 @@ function CharacterItem({ char, isSelected, selections, bosses, onSelect, onRemov if (e.target.closest('button')) return onSelect(char.character_name) }} - className={`group relative rounded-xl border cursor-pointer select-none ${ - isSelected - ? 'border-emerald-500/40 bg-emerald-500/[0.08]' - : 'border-white/5 hover:border-white/15 bg-gray-950/40 hover:bg-gray-950/60' - }`} + className="group relative rounded-xl border cursor-pointer select-none" + style={{ + borderColor: isSelected ? 'var(--selected-border)' : 'var(--panel-border)', + background: isSelected ? 'var(--selected-bg)' : 'var(--surface-3)', + }} > {/* 드래그 핸들 */}
{ e.preventDefault(); dragControls.start(e) }} - className="absolute left-0 top-0 bottom-0 w-8 flex items-center justify-center text-gray-600 hover:text-gray-400 cursor-grab active:cursor-grabbing" - style={{ touchAction: 'none' }} + className="absolute left-0 top-0 bottom-0 w-8 flex items-center justify-center cursor-grab active:cursor-grabbing" + style={{ touchAction: 'none', color: 'var(--text-dim)' }} > @@ -147,7 +176,8 @@ function CharacterItem({ char, isSelected, selections, bosses, onSelect, onRemov
{/* 총 수익 카드 (고정) */} -
+
-
총 주간 수익
+
총 주간 수익
{totalText}
@@ -237,25 +274,42 @@ export default function CharacterPanel({
-
총 결정 개수
-
+
총 결정 개수
+
MAX_PER_ACCOUNT ? 'bg-amber-500' : 'bg-emerald-500'}`} - style={{ width: `${usagePct}%` }} + className="h-full transition-all" + style={{ + width: `${usagePct}%`, + background: totalCount > MAX_PER_ACCOUNT ? 'var(--progress-amber)' : 'var(--progress-emerald)', + }} />
- MAX_PER_ACCOUNT ? 'text-red-400' : 'text-amber-300'}`}> + MAX_PER_ACCOUNT ? 'var(--danger-text)' : 'var(--warning-text-bright)' }} + > {accountUsage} - MAX_PER_ACCOUNT ? 'text-red-400/40' : 'text-amber-300/40'}`}> + MAX_PER_ACCOUNT ? 'var(--danger-text)' : 'var(--warning-text-dim)', + opacity: totalCount > MAX_PER_ACCOUNT ? 0.4 : 1, + }} + > / {MAX_PER_ACCOUNT}
{totalCount > MAX_PER_ACCOUNT && ( -

⚠ 한도 {totalCount - MAX_PER_ACCOUNT}개 초과

+

+ ⚠ 한도 {totalCount - MAX_PER_ACCOUNT}개 초과 +

)}
@@ -263,7 +317,10 @@ export default function CharacterPanel({
- + @@ -274,18 +331,30 @@ export default function CharacterPanel({ value={name} onChange={(e) => { setName(e.target.value); if (error) setError('') }} placeholder="캐릭터 닉네임 검색" - className="w-full rounded-lg border-2 border-white/10 bg-gray-950 pl-10 pr-3 py-2.5 text-sm outline-none focus:border-emerald-500/60 hover:border-white/20 transition" + className="w-full rounded-lg border-2 pl-10 pr-3 py-2.5 text-sm outline-none transition focus:border-[var(--input-border-focus)] hover:border-[var(--input-border-hover)]" + style={{ + background: 'var(--input-bg)', + borderColor: 'var(--input-border)', + color: 'var(--text-strong)', + }} />
- {error &&

{error}

} + {error && ( +

{error}

+ )}
{/* 캐릭터 목록 (스크롤) */} diff --git a/frontend/src/index.css b/frontend/src/index.css index 7c582e5..a25a6ce 100644 --- a/frontend/src/index.css +++ b/frontend/src/index.css @@ -76,6 +76,53 @@ --maintenance-text: #fbbf24; --dashed-border: rgba(255, 255, 255, 0.1); + + --surface-2: rgba(2, 6, 23, 0.6); + --surface-3: rgba(17, 24, 39, 0.4); + --surface-nested: #0f172a; + + --input-bg: #020617; + --input-border: rgba(255, 255, 255, 0.1); + --input-border-hover: rgba(255, 255, 255, 0.2); + --input-border-focus: rgba(16, 185, 129, 0.5); + --input-placeholder: #6b7280; + --input-icon: #6b7280; + + --selected-bg: rgba(16, 185, 129, 0.08); + --selected-border: rgba(16, 185, 129, 0.4); + --option-selected-bg: rgba(16, 185, 129, 0.12); + --option-selected-text: #6ee7b7; + + --btn-primary-bg: #059669; + --btn-primary-bg-hover: #10b981; + --btn-primary-text: #ffffff; + --btn-primary-shadow: 0 4px 14px rgba(16, 185, 129, 0.2); + + --danger-text: #f87171; + --danger-text-strong: #dc2626; + --danger-bg-hover: rgba(239, 68, 68, 0.1); + --warning-text: #fbbf24; + --warning-text-bright: #fcd34d; + --warning-text-dim: rgba(252, 211, 77, 0.4); + + --progress-track: #0f172a; + --progress-emerald: #10b981; + --progress-amber: #f59e0b; + + --accent-bright: #6ee7b7; + --accent-muted: rgba(16, 185, 129, 0.1); + --accent-text-on-emerald: #ecfdf5; + + --tooltip-bg: #111827; + --tooltip-border: rgba(255, 255, 255, 0.1); + --tooltip-text: #e5e7eb; + + --popup-bg: #111827; + --popup-border: rgba(255, 255, 255, 0.1); + --popup-shadow: 0 10px 30px rgba(0, 0, 0, 0.5); + + --disabled-opacity: 0.3; + --inactive-filter: brightness(0.4); } /* 테마 토큰 - light */ @@ -148,6 +195,53 @@ --maintenance-text: #b45309; --dashed-border: rgba(0, 0, 0, 0.12); + + --surface-2: #f8fafc; + --surface-3: #f9fafb; + --surface-nested: #f3f4f6; + + --input-bg: #ffffff; + --input-border: rgba(0, 0, 0, 0.12); + --input-border-hover: rgba(0, 0, 0, 0.22); + --input-border-focus: rgba(5, 150, 105, 0.55); + --input-placeholder: #9ca3af; + --input-icon: #94a3b8; + + --selected-bg: rgba(16, 185, 129, 0.08); + --selected-border: rgba(5, 150, 105, 0.5); + --option-selected-bg: rgba(16, 185, 129, 0.12); + --option-selected-text: #047857; + + --btn-primary-bg: #059669; + --btn-primary-bg-hover: #047857; + --btn-primary-text: #ffffff; + --btn-primary-shadow: 0 4px 14px rgba(16, 185, 129, 0.25); + + --danger-text: #dc2626; + --danger-text-strong: #b91c1c; + --danger-bg-hover: rgba(220, 38, 38, 0.08); + --warning-text: #c2410c; + --warning-text-bright: #ea580c; + --warning-text-dim: rgba(234, 88, 12, 0.4); + + --progress-track: #e5e7eb; + --progress-emerald: #10b981; + --progress-amber: #f59e0b; + + --accent-bright: #047857; + --accent-muted: rgba(16, 185, 129, 0.1); + --accent-text-on-emerald: #ecfdf5; + + --tooltip-bg: #111827; + --tooltip-border: rgba(255, 255, 255, 0.08); + --tooltip-text: #f3f4f6; + + --popup-bg: #ffffff; + --popup-border: rgba(0, 0, 0, 0.1); + --popup-shadow: 0 10px 30px rgba(15, 23, 42, 0.15); + + --disabled-opacity: 0.5; + --inactive-filter: opacity(0.25); } html, body, #root {