From 2432fca523bc7e12352ae90b0dcd02cd09158b0d Mon Sep 17 00:00:00 2001 From: dev-minsoo Date: Mon, 20 Apr 2026 21:27:39 +0900 Subject: [PATCH] =?UTF-8?q?feat:=20=ED=8C=9D=EC=97=85=EA=B3=BC=20=EC=84=A4?= =?UTF-8?q?=EC=A0=95=20=ED=99=94=EB=A9=B4=EC=97=90=20=ED=85=8C=EB=A7=88=20?= =?UTF-8?q?=EB=AA=A8=EB=93=9C=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/app/options/Options.tsx | 251 ++++++++++++++++++++++++++++++----- src/app/popup/Popup.tsx | 193 +++++++++++++++++++++++---- src/app/welcome/Welcome.tsx | 104 +++++++++++++-- src/core/storage/settings.ts | 2 + src/core/types/domain.ts | 2 + src/shared/theme.ts | 45 +++++++ 6 files changed, 526 insertions(+), 71 deletions(-) create mode 100644 src/shared/theme.ts diff --git a/src/app/options/Options.tsx b/src/app/options/Options.tsx index fe08ccc..3994e91 100644 --- a/src/app/options/Options.tsx +++ b/src/app/options/Options.tsx @@ -9,15 +9,18 @@ import type { Locale, PlatformId, RepositoryTemplateSegment, + ThemeMode, } from "../../core/types/domain"; import type { RuntimeMessageResponse } from "../../core/types/messages"; import { BrandWordmark } from "../../shared/components/BrandWordmark"; +import { useResolvedTheme } from "../../shared/theme"; const ISSUE_URL = "https://github.com/dev-minsoo/AlgorithmHub/issues"; const REPOSITORY_URL = "https://github.com/dev-minsoo/AlgorithmHub"; const emptySettings: ExtensionSettings = { locale: "en", + themeMode: "system", github: { oauthClientId: "", token: "", @@ -78,6 +81,10 @@ const OPTIONS_COPY = { noRepository: "No repository connected", connectHint: "Connect a repository from the welcome flow first.", solving: "Start solving problems right away:", + theme: "Theme", + themeSystem: "System", + themeLight: "Light", + themeDark: "Dark", language: "Language", autoUpload: "Auto Upload", enabled: "Enabled", @@ -102,6 +109,10 @@ const OPTIONS_COPY = { noRepository: "연결된 저장소가 없습니다", connectHint: "먼저 welcome 화면에서 저장소를 연결하세요.", solving: "바로 문제를 풀어보세요:", + theme: "테마", + themeSystem: "System", + themeLight: "Light", + themeDark: "Dark", language: "언어", autoUpload: "자동 업로드", enabled: "활성화", @@ -137,6 +148,7 @@ function PathTemplateCard({ onToggleSegment, onToggleCombine, copy, + resolvedTheme, }: { platform: PlatformId; settings: ExtensionSettings; @@ -152,6 +164,7 @@ function PathTemplateCard({ ) => Promise; onToggleCombine: (platform: PlatformId) => Promise; copy: OptionsCopy; + resolvedTheme: "light" | "dark"; }) { const template = settings.repositoryTemplate[platform]; const previewPath = buildRepositoryDirectory(template, { @@ -163,18 +176,40 @@ function PathTemplateCard({ const fileName = platform === "leetcode" ? "solution.py" : "solution.js"; return ( -
+

{platform === "leetcode" ? copy.templateLeetCode : copy.templateProgrammers}

-

+

{copy.templateHint}

-
+
-

{copy.combine}

+

+ {copy.combine} +

-
+
{previewPath}/{fileName}
@@ -429,9 +484,38 @@ export default function Options() { const isConnected = Boolean(settings.github.repository.trim()); const copy = OPTIONS_COPY[settings.locale]; + const resolvedTheme = useResolvedTheme(settings.themeMode); + + async function handleChangeTheme(themeMode: ThemeMode) { + const response = (await chrome.runtime.sendMessage({ + type: "SAVE_SETTINGS", + settings: { themeMode }, + })) as RuntimeMessageResponse; + + if (response.type === "SETTINGS_SAVED") { + setSettings(response.settings); + } + } + + const pageClass = + resolvedTheme === "dark" + ? "min-h-screen bg-[radial-gradient(circle_at_top,_rgba(251,191,36,0.16),_transparent_30%),linear-gradient(180deg,_#140f0c,_#060606)] text-stone-100" + : "min-h-screen bg-[radial-gradient(circle_at_top,_rgba(251,191,36,0.16),_transparent_30%),linear-gradient(180deg,_#fcf5e8,_#fffdf8)] text-stone-900"; + const shellClass = + resolvedTheme === "dark" + ? "border-amber-950/60 bg-[linear-gradient(180deg,rgba(41,24,13,0.92),rgba(12,12,12,0.94))] shadow-[0_24px_80px_rgba(0,0,0,0.45)]" + : "border-amber-300 bg-[linear-gradient(180deg,rgba(255,255,255,0.99),rgba(255,247,232,0.98))] shadow-[0_24px_64px_rgba(180,120,0,0.10)]"; + const cardClass = + resolvedTheme === "dark" + ? "border-stone-800 bg-stone-950/60" + : "border-amber-300 bg-white"; + const rowClass = + resolvedTheme === "dark" + ? "border-stone-800 bg-stone-900/70" + : "border-amber-300 bg-amber-50/85"; return ( -
+

@@ -440,20 +524,42 @@ export default function Options() {

-

+

{copy.description}

-
-
-

+

+
+

{copy.connected}

{isConnected ? ( -

+

) : ( -

+

{copy.noRepository}

)} {!isConnected ? ( -

+

{copy.connectHint}

) : null}
-
+
-
-

+

+

{copy.autoUpload} {extensionEnabled ? copy.enabled : copy.disabled}

-
- +
+ + {copy.theme} + + +
+ +
+ {copy.language} { const nextMode = event.target.value as RepoMode; @@ -259,7 +305,11 @@ export default function Welcome() { setRepositoryName(event.target.value)} placeholder="algorithm" @@ -270,8 +320,18 @@ export default function Welcome() { {mode === "link" ? (
{!settings.github.token.trim() ? ( -
-

+

+

Authenticate with GitHub to load your repositories.