From fecbb4b354307805a4a9b691e239b228f3ee918d Mon Sep 17 00:00:00 2001 From: miteruzo Date: Sun, 5 Jul 2026 02:04:07 +0900 Subject: [PATCH] #34 --- frontend/src/index.css | 40 +++--- frontend/src/lib/settings.ts | 153 ++++++++++++++++++++--- frontend/src/pages/users/SettingPage.tsx | 34 +++-- 3 files changed, 178 insertions(+), 49 deletions(-) diff --git a/frontend/src/index.css b/frontend/src/index.css index b41fb31..05d2ee0 100644 --- a/frontend/src/index.css +++ b/frontend/src/index.css @@ -10,6 +10,10 @@ { --background: 0 0% 100%; --foreground: 222.2 84% 4.9%; + --card: 0 0% 100%; + --card-foreground: 222.2 84% 4.9%; + --popover: 0 0% 100%; + --popover-foreground: 222.2 84% 4.9%; --primary: 222.2 47.4% 11.2%; --primary-foreground: 210 40% 98%; @@ -29,6 +33,7 @@ --border: 214.3 31.8% 91.4%; --input: 214.3 31.8% 91.4%; --ring: 222.2 84% 4.9%; + --theme-link: 221.2 83.2% 53.3%; --tag-colour-deerjikist: #9f1239; --tag-colour-deerjikist-hover: #b62a51; @@ -50,6 +55,10 @@ { --background: 222.2 84% 4.9%; --foreground: 210 40% 98%; + --card: 222.2 84% 4.9%; + --card-foreground: 210 40% 98%; + --popover: 222.2 84% 4.9%; + --popover-foreground: 210 40% 98%; --primary: 210 40% 98%; --primary-foreground: 222.2 47.4% 11.2%; @@ -73,16 +82,16 @@ body { - @apply overflow-x-clip; + @apply overflow-x-clip bg-background text-foreground; } a { - @apply text-blue-700 dark:text-blue-300; + color: hsl(var(--theme-link)); } a:hover { - @apply text-blue-500 dark:text-blue-100; + opacity: .85; } } @@ -93,8 +102,8 @@ font-weight: 400; color-scheme: light dark; - color: rgba(255, 255, 255, 0.87); - background-color: #242424; + color: hsl(var(--foreground)); + background-color: hsl(var(--background)); font-synthesis: none; text-rendering: optimizeLegibility; @@ -135,28 +144,15 @@ body { margin: 0; min-width: 320px; + background: hsl(var(--background)); + color: hsl(var(--foreground)); } #root { min-height: 100dvh; -} - -@media (prefers-color-scheme: light) -{ - :root - { - color: #213547; - background-color: #ffffff; - } - a:hover - { - color: #747bff; - } - button - { - background-color: #f9f9f9; - } + background: hsl(var(--background)); + color: hsl(var(--foreground)); } @keyframes wiki-blink diff --git a/frontend/src/lib/settings.ts b/frontend/src/lib/settings.ts index 0b064dd..51a8abc 100644 --- a/frontend/src/lib/settings.ts +++ b/frontend/src/lib/settings.ts @@ -14,12 +14,35 @@ export type BuiltinThemeId = 'light' | 'dark' export type ThemeId = BuiltinThemeId | `custom:${ string }` export type ActiveThemeId = ThemeId | 'system' export type ResolvedTheme = 'light' | 'dark' +export type ThemeTokens = { + background: string + foreground: string + card: string + cardForeground: string + popover: string + popoverForeground: string + primary: string + primaryForeground: string + secondary: string + secondaryForeground: string + muted: string + mutedForeground: string + accent: string + accentForeground: string + destructive: string + destructiveForeground: string + border: string + input: string + ring: string + link: string + tagColours: ThemeTagColours +} export type ClientTheme = { id: ThemeId name: string builtin: boolean baseTheme: BuiltinThemeId - tagColours: ThemeTagColours } + tokens: ThemeTokens } export type CustomClientTheme = ClientTheme & { builtin: false } @@ -119,20 +142,66 @@ export const DEFAULT_DARK_THEME_TAG_COLOURS: ThemeTagColours = { nico: '#e5e7eb', } export const DEFAULT_THEME_TAG_COLOURS = DEFAULT_LIGHT_THEME_TAG_COLOURS +export const LIGHT_THEME_TOKENS: ThemeTokens = { + background: '0 0% 100%', + foreground: '222.2 84% 4.9%', + card: '0 0% 100%', + cardForeground: '222.2 84% 4.9%', + popover: '0 0% 100%', + popoverForeground: '222.2 84% 4.9%', + primary: '222.2 47.4% 11.2%', + primaryForeground: '210 40% 98%', + secondary: '210 40% 96.1%', + secondaryForeground: '222.2 47.4% 11.2%', + muted: '210 40% 96.1%', + mutedForeground: '215.4 16.3% 46.9%', + accent: '210 40% 96.1%', + accentForeground: '222.2 47.4% 11.2%', + destructive: '0 72.2% 50.6%', + destructiveForeground: '210 40% 98%', + border: '214.3 31.8% 91.4%', + input: '214.3 31.8% 91.4%', + ring: '222.2 84% 4.9%', + link: '221.2 83.2% 53.3%', + tagColours: DEFAULT_LIGHT_THEME_TAG_COLOURS, +} +export const DARK_THEME_TOKENS: ThemeTokens = { + background: '222.2 84% 4.9%', + foreground: '210 40% 98%', + card: '222.2 84% 4.9%', + cardForeground: '210 40% 98%', + popover: '222.2 84% 4.9%', + popoverForeground: '210 40% 98%', + primary: '210 40% 98%', + primaryForeground: '222.2 47.4% 11.2%', + secondary: '217.2 32.6% 17.5%', + secondaryForeground: '210 40% 98%', + muted: '217.2 32.6% 17.5%', + mutedForeground: '215 20.2% 65.1%', + accent: '217.2 32.6% 17.5%', + accentForeground: '210 40% 98%', + destructive: '0 62.8% 45%', + destructiveForeground: '210 40% 98%', + border: '217.2 32.6% 17.5%', + input: '217.2 32.6% 17.5%', + ring: '212.7 26.8% 83.9%', + link: '213.1 93.9% 67.8%', + tagColours: DEFAULT_DARK_THEME_TAG_COLOURS, +} export const BUILTIN_THEMES: Record = { light: { id: 'light', name: 'ライト・モード', builtin: true, baseTheme: 'light', - tagColours: DEFAULT_LIGHT_THEME_TAG_COLOURS, + tokens: LIGHT_THEME_TOKENS, }, dark: { id: 'dark', name: 'ダーク・モード', builtin: true, baseTheme: 'dark', - tagColours: DEFAULT_DARK_THEME_TAG_COLOURS, + tokens: DARK_THEME_TOKENS, }, } @@ -369,10 +438,26 @@ const normaliseCustomTheme = ( name, builtin: false, baseTheme, - tagColours: normaliseThemeTagColours ( - theme.tagColours as Partial | undefined, - defaultThemeTagColoursFor (baseTheme), - ), + tokens: { + ...( + baseTheme === 'dark' + ? DARK_THEME_TOKENS + : LIGHT_THEME_TOKENS + ), + ...(theme.tokens && typeof theme.tokens === 'object' + ? theme.tokens as Partial + : { }), + tagColours: normaliseThemeTagColours ( + ( + theme.tokens + && typeof theme.tokens === 'object' + && 'tagColours' in theme.tokens + ) + ? (theme.tokens as { tagColours?: Partial }).tagColours + : theme.tagColours as Partial | undefined, + defaultThemeTagColoursFor (baseTheme), + ), + }, } } @@ -405,10 +490,13 @@ export const getClientCustomThemes = (): Record => { name: themeCopyName (baseTheme), builtin: false, baseTheme, - tagColours: normaliseThemeTagColours ( - legacyTagColours, - defaultThemeTagColoursFor (baseTheme), - ), + tokens: { + ...(baseTheme === 'dark' ? DARK_THEME_TOKENS : LIGHT_THEME_TOKENS), + tagColours: normaliseThemeTagColours ( + legacyTagColours, + defaultThemeTagColoursFor (baseTheme), + ), + }, }, } } @@ -477,7 +565,7 @@ export const deriveUserThemeModeFromSelection = ( export const createCustomThemeFromBase = ( baseTheme: BuiltinThemeId, - tagColours?: ThemeTagColours, + tokens?: ThemeTokens, ): CustomClientTheme => { const id = `custom:${ crypto.randomUUID () }` as ThemeId return { @@ -485,7 +573,9 @@ export const createCustomThemeFromBase = ( name: themeCopyName (baseTheme), builtin: false, baseTheme, - tagColours: tagColours ?? { ...defaultThemeTagColoursFor (baseTheme) }, + tokens: tokens ?? { + ...(baseTheme === 'dark' ? DARK_THEME_TOKENS : LIGHT_THEME_TOKENS), + }, } } @@ -495,7 +585,7 @@ export const getClientThemeTagColours = ( customThemes: Record = getClientCustomThemes (), ): ThemeTagColours => { return { - ...getResolvedThemeFromSelection (activeThemeId, customThemes).tagColours, + ...getResolvedThemeFromSelection (activeThemeId, customThemes).tokens.tagColours, } } @@ -531,7 +621,10 @@ export const setClientThemeTagColours = ( ...customThemes, [activeThemeId]: { ...customThemes[activeThemeId], - tagColours, + tokens: { + ...customThemes[activeThemeId].tokens, + tagColours, + }, }, }, ) @@ -561,13 +654,41 @@ export const applyThemeTagColours = (tagColours: ThemeTagColours): void => { } +export const applyThemeTokens = (tokens: ThemeTokens): void => { + const root = document.documentElement + + root.style.setProperty ('--background', tokens.background) + root.style.setProperty ('--foreground', tokens.foreground) + root.style.setProperty ('--card', tokens.card) + root.style.setProperty ('--card-foreground', tokens.cardForeground) + root.style.setProperty ('--popover', tokens.popover) + root.style.setProperty ('--popover-foreground', tokens.popoverForeground) + root.style.setProperty ('--primary', tokens.primary) + root.style.setProperty ('--primary-foreground', tokens.primaryForeground) + root.style.setProperty ('--secondary', tokens.secondary) + root.style.setProperty ('--secondary-foreground', tokens.secondaryForeground) + root.style.setProperty ('--muted', tokens.muted) + root.style.setProperty ('--muted-foreground', tokens.mutedForeground) + root.style.setProperty ('--accent', tokens.accent) + root.style.setProperty ('--accent-foreground', tokens.accentForeground) + root.style.setProperty ('--destructive', tokens.destructive) + root.style.setProperty ('--destructive-foreground', tokens.destructiveForeground) + root.style.setProperty ('--border', tokens.border) + root.style.setProperty ('--input', tokens.input) + root.style.setProperty ('--ring', tokens.ring) + root.style.setProperty ('--theme-link', tokens.link) + + applyThemeTagColours (tokens.tagColours) +} + + export const applyThemeSelection = ( activeThemeId: ActiveThemeId, customThemes: Record, ): void => { const theme = getResolvedThemeFromSelection (activeThemeId, customThemes) applyThemeMode (theme.baseTheme) - applyThemeTagColours (theme.tagColours) + applyThemeTokens (theme.tokens) } diff --git a/frontend/src/pages/users/SettingPage.tsx b/frontend/src/pages/users/SettingPage.tsx index ee94c08..9d1af16 100644 --- a/frontend/src/pages/users/SettingPage.tsx +++ b/frontend/src/pages/users/SettingPage.tsx @@ -454,7 +454,7 @@ const SettingPage: FC = ({ user, setUser }) => { () => getResolvedThemeFromSelection (draftActiveThemeId, draftCustomThemes), [draftActiveThemeId, draftCustomThemes], ) - const draftTagColours = selectedTheme.tagColours + const draftTagColours = selectedTheme.tokens.tagColours const setActiveTab = (tab: SettingsTab) => { const params = new URLSearchParams (location.search) @@ -498,7 +498,10 @@ const SettingPage: FC = ({ user, setUser }) => { const customTheme = createCustomThemeFromBase ( selectedTheme.baseTheme, - { ...selectedTheme.tagColours }, + { + ...selectedTheme.tokens, + tagColours: { ...selectedTheme.tokens.tagColours }, + }, ) const nextCustomThemes = { ...draftCustomThemes, @@ -681,9 +684,12 @@ const SettingPage: FC = ({ user, setUser }) => { ...editable.customThemes, [editable.activeThemeId]: { ...editableTheme, - tagColours: { - ...editableTheme.tagColours, - [category]: colour, + tokens: { + ...editableTheme.tokens, + tagColours: { + ...editableTheme.tokens.tagColours, + [category]: colour, + }, }, }, } @@ -699,8 +705,11 @@ const SettingPage: FC = ({ user, setUser }) => { ...draftCustomThemes, [draftActiveThemeId]: { ...draftCustomThemes[draftActiveThemeId], - tagColours: { - ...defaultThemeTagColoursFor (selectedTheme.baseTheme), + tokens: { + ...draftCustomThemes[draftActiveThemeId].tokens, + tagColours: { + ...defaultThemeTagColoursFor (selectedTheme.baseTheme), + }, }, }, }, @@ -716,10 +725,13 @@ const SettingPage: FC = ({ user, setUser }) => { ...draftCustomThemes, [draftActiveThemeId]: { ...draftCustomThemes[draftActiveThemeId], - tagColours: { - ...draftCustomThemes[draftActiveThemeId].tagColours, - [category]: - defaultThemeTagColoursFor (selectedTheme.baseTheme)[category], + tokens: { + ...draftCustomThemes[draftActiveThemeId].tokens, + tagColours: { + ...draftCustomThemes[draftActiveThemeId].tokens.tagColours, + [category]: + defaultThemeTagColoursFor (selectedTheme.baseTheme)[category], + }, }, }, },