設定画面 (#34) #397

マージ済み
みてるぞ が 31 個のコミットを feature/034 から main へマージ 2026-07-07 00:48:41 +09:00
3個のファイルの変更178行の追加49行の削除
コミット fecbb4b354 の変更だけを表示してゐます - すべてのコミットを表示
+18 -22
ファイルの表示
@@ -10,6 +10,10 @@
{ {
--background: 0 0% 100%; --background: 0 0% 100%;
--foreground: 222.2 84% 4.9%; --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: 222.2 47.4% 11.2%;
--primary-foreground: 210 40% 98%; --primary-foreground: 210 40% 98%;
@@ -29,6 +33,7 @@
--border: 214.3 31.8% 91.4%; --border: 214.3 31.8% 91.4%;
--input: 214.3 31.8% 91.4%; --input: 214.3 31.8% 91.4%;
--ring: 222.2 84% 4.9%; --ring: 222.2 84% 4.9%;
--theme-link: 221.2 83.2% 53.3%;
--tag-colour-deerjikist: #9f1239; --tag-colour-deerjikist: #9f1239;
--tag-colour-deerjikist-hover: #b62a51; --tag-colour-deerjikist-hover: #b62a51;
@@ -50,6 +55,10 @@
{ {
--background: 222.2 84% 4.9%; --background: 222.2 84% 4.9%;
--foreground: 210 40% 98%; --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: 210 40% 98%;
--primary-foreground: 222.2 47.4% 11.2%; --primary-foreground: 222.2 47.4% 11.2%;
@@ -73,16 +82,16 @@
body body
{ {
@apply overflow-x-clip; @apply overflow-x-clip bg-background text-foreground;
} }
a a
{ {
@apply text-blue-700 dark:text-blue-300; color: hsl(var(--theme-link));
} }
a:hover a:hover
{ {
@apply text-blue-500 dark:text-blue-100; opacity: .85;
} }
} }
@@ -93,8 +102,8 @@
font-weight: 400; font-weight: 400;
color-scheme: light dark; color-scheme: light dark;
color: rgba(255, 255, 255, 0.87); color: hsl(var(--foreground));
background-color: #242424; background-color: hsl(var(--background));
font-synthesis: none; font-synthesis: none;
text-rendering: optimizeLegibility; text-rendering: optimizeLegibility;
@@ -135,28 +144,15 @@ body
{ {
margin: 0; margin: 0;
min-width: 320px; min-width: 320px;
background: hsl(var(--background));
color: hsl(var(--foreground));
} }
#root #root
{ {
min-height: 100dvh; min-height: 100dvh;
} background: hsl(var(--background));
color: hsl(var(--foreground));
@media (prefers-color-scheme: light)
{
:root
{
color: #213547;
background-color: #ffffff;
}
a:hover
{
color: #747bff;
}
button
{
background-color: #f9f9f9;
}
} }
@keyframes wiki-blink @keyframes wiki-blink
+129 -8
ファイルの表示
@@ -14,12 +14,35 @@ export type BuiltinThemeId = 'light' | 'dark'
export type ThemeId = BuiltinThemeId | `custom:${ string }` export type ThemeId = BuiltinThemeId | `custom:${ string }`
export type ActiveThemeId = ThemeId | 'system' export type ActiveThemeId = ThemeId | 'system'
export type ResolvedTheme = 'light' | 'dark' 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 = { export type ClientTheme = {
id: ThemeId id: ThemeId
name: string name: string
builtin: boolean builtin: boolean
baseTheme: BuiltinThemeId baseTheme: BuiltinThemeId
tagColours: ThemeTagColours } tokens: ThemeTokens }
export type CustomClientTheme = export type CustomClientTheme =
ClientTheme & { builtin: false } ClientTheme & { builtin: false }
@@ -119,20 +142,66 @@ export const DEFAULT_DARK_THEME_TAG_COLOURS: ThemeTagColours = {
nico: '#e5e7eb', nico: '#e5e7eb',
} }
export const DEFAULT_THEME_TAG_COLOURS = DEFAULT_LIGHT_THEME_TAG_COLOURS 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<BuiltinThemeId, ClientTheme> = { export const BUILTIN_THEMES: Record<BuiltinThemeId, ClientTheme> = {
light: { light: {
id: 'light', id: 'light',
name: 'ライト・モード', name: 'ライト・モード',
builtin: true, builtin: true,
baseTheme: 'light', baseTheme: 'light',
tagColours: DEFAULT_LIGHT_THEME_TAG_COLOURS, tokens: LIGHT_THEME_TOKENS,
}, },
dark: { dark: {
id: 'dark', id: 'dark',
name: 'ダーク・モード', name: 'ダーク・モード',
builtin: true, builtin: true,
baseTheme: 'dark', baseTheme: 'dark',
tagColours: DEFAULT_DARK_THEME_TAG_COLOURS, tokens: DARK_THEME_TOKENS,
}, },
} }
@@ -369,10 +438,26 @@ const normaliseCustomTheme = (
name, name,
builtin: false, builtin: false,
baseTheme, baseTheme,
tokens: {
...(
baseTheme === 'dark'
? DARK_THEME_TOKENS
: LIGHT_THEME_TOKENS
),
...(theme.tokens && typeof theme.tokens === 'object'
? theme.tokens as Partial<ThemeTokens>
: { }),
tagColours: normaliseThemeTagColours ( tagColours: normaliseThemeTagColours (
theme.tagColours as Partial<ThemeTagColours> | undefined, (
theme.tokens
&& typeof theme.tokens === 'object'
&& 'tagColours' in theme.tokens
)
? (theme.tokens as { tagColours?: Partial<ThemeTagColours> }).tagColours
: theme.tagColours as Partial<ThemeTagColours> | undefined,
defaultThemeTagColoursFor (baseTheme), defaultThemeTagColoursFor (baseTheme),
), ),
},
} }
} }
@@ -405,11 +490,14 @@ export const getClientCustomThemes = (): Record<string, CustomClientTheme> => {
name: themeCopyName (baseTheme), name: themeCopyName (baseTheme),
builtin: false, builtin: false,
baseTheme, baseTheme,
tokens: {
...(baseTheme === 'dark' ? DARK_THEME_TOKENS : LIGHT_THEME_TOKENS),
tagColours: normaliseThemeTagColours ( tagColours: normaliseThemeTagColours (
legacyTagColours, legacyTagColours,
defaultThemeTagColoursFor (baseTheme), defaultThemeTagColoursFor (baseTheme),
), ),
}, },
},
} }
} }
@@ -477,7 +565,7 @@ export const deriveUserThemeModeFromSelection = (
export const createCustomThemeFromBase = ( export const createCustomThemeFromBase = (
baseTheme: BuiltinThemeId, baseTheme: BuiltinThemeId,
tagColours?: ThemeTagColours, tokens?: ThemeTokens,
): CustomClientTheme => { ): CustomClientTheme => {
const id = `custom:${ crypto.randomUUID () }` as ThemeId const id = `custom:${ crypto.randomUUID () }` as ThemeId
return { return {
@@ -485,7 +573,9 @@ export const createCustomThemeFromBase = (
name: themeCopyName (baseTheme), name: themeCopyName (baseTheme),
builtin: false, builtin: false,
baseTheme, baseTheme,
tagColours: tagColours ?? { ...defaultThemeTagColoursFor (baseTheme) }, tokens: tokens ?? {
...(baseTheme === 'dark' ? DARK_THEME_TOKENS : LIGHT_THEME_TOKENS),
},
} }
} }
@@ -495,7 +585,7 @@ export const getClientThemeTagColours = (
customThemes: Record<string, CustomClientTheme> = getClientCustomThemes (), customThemes: Record<string, CustomClientTheme> = getClientCustomThemes (),
): ThemeTagColours => { ): ThemeTagColours => {
return { return {
...getResolvedThemeFromSelection (activeThemeId, customThemes).tagColours, ...getResolvedThemeFromSelection (activeThemeId, customThemes).tokens.tagColours,
} }
} }
@@ -531,9 +621,12 @@ export const setClientThemeTagColours = (
...customThemes, ...customThemes,
[activeThemeId]: { [activeThemeId]: {
...customThemes[activeThemeId], ...customThemes[activeThemeId],
tokens: {
...customThemes[activeThemeId].tokens,
tagColours, 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 = ( export const applyThemeSelection = (
activeThemeId: ActiveThemeId, activeThemeId: ActiveThemeId,
customThemes: Record<string, CustomClientTheme>, customThemes: Record<string, CustomClientTheme>,
): void => { ): void => {
const theme = getResolvedThemeFromSelection (activeThemeId, customThemes) const theme = getResolvedThemeFromSelection (activeThemeId, customThemes)
applyThemeMode (theme.baseTheme) applyThemeMode (theme.baseTheme)
applyThemeTagColours (theme.tagColours) applyThemeTokens (theme.tokens)
} }
+16 -4
ファイルの表示
@@ -454,7 +454,7 @@ const SettingPage: FC<Props> = ({ user, setUser }) => {
() => getResolvedThemeFromSelection (draftActiveThemeId, draftCustomThemes), () => getResolvedThemeFromSelection (draftActiveThemeId, draftCustomThemes),
[draftActiveThemeId, draftCustomThemes], [draftActiveThemeId, draftCustomThemes],
) )
const draftTagColours = selectedTheme.tagColours const draftTagColours = selectedTheme.tokens.tagColours
const setActiveTab = (tab: SettingsTab) => { const setActiveTab = (tab: SettingsTab) => {
const params = new URLSearchParams (location.search) const params = new URLSearchParams (location.search)
@@ -498,7 +498,10 @@ const SettingPage: FC<Props> = ({ user, setUser }) => {
const customTheme = createCustomThemeFromBase ( const customTheme = createCustomThemeFromBase (
selectedTheme.baseTheme, selectedTheme.baseTheme,
{ ...selectedTheme.tagColours }, {
...selectedTheme.tokens,
tagColours: { ...selectedTheme.tokens.tagColours },
},
) )
const nextCustomThemes = { const nextCustomThemes = {
...draftCustomThemes, ...draftCustomThemes,
@@ -681,11 +684,14 @@ const SettingPage: FC<Props> = ({ user, setUser }) => {
...editable.customThemes, ...editable.customThemes,
[editable.activeThemeId]: { [editable.activeThemeId]: {
...editableTheme, ...editableTheme,
tokens: {
...editableTheme.tokens,
tagColours: { tagColours: {
...editableTheme.tagColours, ...editableTheme.tokens.tagColours,
[category]: colour, [category]: colour,
}, },
}, },
},
} }
applyDraftThemeSelection (editable.activeThemeId, nextCustomThemes) applyDraftThemeSelection (editable.activeThemeId, nextCustomThemes)
}, },
@@ -699,11 +705,14 @@ const SettingPage: FC<Props> = ({ user, setUser }) => {
...draftCustomThemes, ...draftCustomThemes,
[draftActiveThemeId]: { [draftActiveThemeId]: {
...draftCustomThemes[draftActiveThemeId], ...draftCustomThemes[draftActiveThemeId],
tokens: {
...draftCustomThemes[draftActiveThemeId].tokens,
tagColours: { tagColours: {
...defaultThemeTagColoursFor (selectedTheme.baseTheme), ...defaultThemeTagColoursFor (selectedTheme.baseTheme),
}, },
}, },
}, },
},
) )
}, },
onResetTagColour: category => { onResetTagColour: category => {
@@ -716,13 +725,16 @@ const SettingPage: FC<Props> = ({ user, setUser }) => {
...draftCustomThemes, ...draftCustomThemes,
[draftActiveThemeId]: { [draftActiveThemeId]: {
...draftCustomThemes[draftActiveThemeId], ...draftCustomThemes[draftActiveThemeId],
tokens: {
...draftCustomThemes[draftActiveThemeId].tokens,
tagColours: { tagColours: {
...draftCustomThemes[draftActiveThemeId].tagColours, ...draftCustomThemes[draftActiveThemeId].tokens.tagColours,
[category]: [category]:
defaultThemeTagColoursFor (selectedTheme.baseTheme)[category], defaultThemeTagColoursFor (selectedTheme.baseTheme)[category],
}, },
}, },
}, },
},
) )
}, },
onThemeSubmit: handleThemeSubmit, onThemeSubmit: handleThemeSubmit,