設定画面 (#34) #397
+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
|
||||||
|
|||||||
+137
-16
@@ -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,
|
||||||
tagColours: normaliseThemeTagColours (
|
tokens: {
|
||||||
theme.tagColours as Partial<ThemeTagColours> | undefined,
|
...(
|
||||||
defaultThemeTagColoursFor (baseTheme),
|
baseTheme === 'dark'
|
||||||
),
|
? DARK_THEME_TOKENS
|
||||||
|
: LIGHT_THEME_TOKENS
|
||||||
|
),
|
||||||
|
...(theme.tokens && typeof theme.tokens === 'object'
|
||||||
|
? theme.tokens as Partial<ThemeTokens>
|
||||||
|
: { }),
|
||||||
|
tagColours: normaliseThemeTagColours (
|
||||||
|
(
|
||||||
|
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),
|
||||||
|
),
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -405,10 +490,13 @@ export const getClientCustomThemes = (): Record<string, CustomClientTheme> => {
|
|||||||
name: themeCopyName (baseTheme),
|
name: themeCopyName (baseTheme),
|
||||||
builtin: false,
|
builtin: false,
|
||||||
baseTheme,
|
baseTheme,
|
||||||
tagColours: normaliseThemeTagColours (
|
tokens: {
|
||||||
legacyTagColours,
|
...(baseTheme === 'dark' ? DARK_THEME_TOKENS : LIGHT_THEME_TOKENS),
|
||||||
defaultThemeTagColoursFor (baseTheme),
|
tagColours: normaliseThemeTagColours (
|
||||||
),
|
legacyTagColours,
|
||||||
|
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,7 +621,10 @@ export const setClientThemeTagColours = (
|
|||||||
...customThemes,
|
...customThemes,
|
||||||
[activeThemeId]: {
|
[activeThemeId]: {
|
||||||
...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 = (
|
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)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -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,9 +684,12 @@ const SettingPage: FC<Props> = ({ user, setUser }) => {
|
|||||||
...editable.customThemes,
|
...editable.customThemes,
|
||||||
[editable.activeThemeId]: {
|
[editable.activeThemeId]: {
|
||||||
...editableTheme,
|
...editableTheme,
|
||||||
tagColours: {
|
tokens: {
|
||||||
...editableTheme.tagColours,
|
...editableTheme.tokens,
|
||||||
[category]: colour,
|
tagColours: {
|
||||||
|
...editableTheme.tokens.tagColours,
|
||||||
|
[category]: colour,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@@ -699,8 +705,11 @@ const SettingPage: FC<Props> = ({ user, setUser }) => {
|
|||||||
...draftCustomThemes,
|
...draftCustomThemes,
|
||||||
[draftActiveThemeId]: {
|
[draftActiveThemeId]: {
|
||||||
...draftCustomThemes[draftActiveThemeId],
|
...draftCustomThemes[draftActiveThemeId],
|
||||||
tagColours: {
|
tokens: {
|
||||||
...defaultThemeTagColoursFor (selectedTheme.baseTheme),
|
...draftCustomThemes[draftActiveThemeId].tokens,
|
||||||
|
tagColours: {
|
||||||
|
...defaultThemeTagColoursFor (selectedTheme.baseTheme),
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -716,10 +725,13 @@ const SettingPage: FC<Props> = ({ user, setUser }) => {
|
|||||||
...draftCustomThemes,
|
...draftCustomThemes,
|
||||||
[draftActiveThemeId]: {
|
[draftActiveThemeId]: {
|
||||||
...draftCustomThemes[draftActiveThemeId],
|
...draftCustomThemes[draftActiveThemeId],
|
||||||
tagColours: {
|
tokens: {
|
||||||
...draftCustomThemes[draftActiveThemeId].tagColours,
|
...draftCustomThemes[draftActiveThemeId].tokens,
|
||||||
[category]:
|
tagColours: {
|
||||||
defaultThemeTagColoursFor (selectedTheme.baseTheme)[category],
|
...draftCustomThemes[draftActiveThemeId].tokens.tagColours,
|
||||||
|
[category]:
|
||||||
|
defaultThemeTagColoursFor (selectedTheme.baseTheme)[category],
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
新しい課題から参照
ユーザをブロックする