feat: pure black
This commit is contained in:
@@ -28,6 +28,7 @@ import RelayPage from './pages/primary/RelayPage'
|
|||||||
import SearchPage from './pages/primary/SearchPage'
|
import SearchPage from './pages/primary/SearchPage'
|
||||||
import { NotificationProvider } from './providers/NotificationProvider'
|
import { NotificationProvider } from './providers/NotificationProvider'
|
||||||
import { useScreenSize } from './providers/ScreenSizeProvider'
|
import { useScreenSize } from './providers/ScreenSizeProvider'
|
||||||
|
import { useTheme } from './providers/ThemeProvider'
|
||||||
import { routes } from './routes'
|
import { routes } from './routes'
|
||||||
import modalManager from './services/modal-manager.service'
|
import modalManager from './services/modal-manager.service'
|
||||||
|
|
||||||
@@ -104,6 +105,7 @@ export function PageManager({ maxStackSize = 5 }: { maxStackSize?: number }) {
|
|||||||
])
|
])
|
||||||
const [secondaryStack, setSecondaryStack] = useState<TStackItem[]>([])
|
const [secondaryStack, setSecondaryStack] = useState<TStackItem[]>([])
|
||||||
const { isSmallScreen } = useScreenSize()
|
const { isSmallScreen } = useScreenSize()
|
||||||
|
const { themeSetting } = useTheme()
|
||||||
const ignorePopStateRef = useRef(false)
|
const ignorePopStateRef = useRef(false)
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -356,8 +358,18 @@ export function PageManager({ maxStackSize = 5 }: { maxStackSize?: number }) {
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Sidebar />
|
<Sidebar />
|
||||||
<div className="grid grid-cols-2 gap-2 w-full pr-2 py-2">
|
<div
|
||||||
<div className="rounded-lg shadow-lg bg-background overflow-hidden">
|
className={cn(
|
||||||
|
'grid grid-cols-2 w-full',
|
||||||
|
themeSetting === 'pure-black' ? '' : 'gap-2 pr-2 py-2'
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className={cn(
|
||||||
|
'bg-background overflow-hidden',
|
||||||
|
themeSetting === 'pure-black' ? 'border-l' : 'rounded-lg shadow-lg'
|
||||||
|
)}
|
||||||
|
>
|
||||||
{primaryPages.map(({ name, element, props }) => (
|
{primaryPages.map(({ name, element, props }) => (
|
||||||
<div
|
<div
|
||||||
key={name}
|
key={name}
|
||||||
@@ -370,7 +382,12 @@ export function PageManager({ maxStackSize = 5 }: { maxStackSize?: number }) {
|
|||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
<div className="rounded-lg shadow-lg bg-background overflow-hidden">
|
<div
|
||||||
|
className={cn(
|
||||||
|
'bg-background overflow-hidden',
|
||||||
|
themeSetting === 'pure-black' ? 'border-l' : 'rounded-lg shadow-lg'
|
||||||
|
)}
|
||||||
|
>
|
||||||
{secondaryStack.map((item, index) => (
|
{secondaryStack.map((item, index) => (
|
||||||
<div
|
<div
|
||||||
key={item.index}
|
key={item.index}
|
||||||
|
|||||||
@@ -80,7 +80,7 @@ function AccountManagerNav({
|
|||||||
const wizard = new NstartModal({
|
const wizard = new NstartModal({
|
||||||
baseUrl: 'https://nstart.me',
|
baseUrl: 'https://nstart.me',
|
||||||
an: 'Jumble',
|
an: 'Jumble',
|
||||||
am: themeSetting,
|
am: themeSetting === 'pure-black' ? 'dark' : themeSetting,
|
||||||
al: i18n.language.slice(0, 2),
|
al: i18n.language.slice(0, 2),
|
||||||
onComplete: ({ nostrLogin }) => {
|
onComplete: ({ nostrLogin }) => {
|
||||||
if (!nostrLogin) return
|
if (!nostrLogin) return
|
||||||
|
|||||||
@@ -1,16 +1,14 @@
|
|||||||
// import { useTheme } from "next-themes"
|
|
||||||
import { useTheme } from '@/providers/ThemeProvider'
|
import { useTheme } from '@/providers/ThemeProvider'
|
||||||
import { Toaster as Sonner } from 'sonner'
|
import { Toaster as Sonner } from 'sonner'
|
||||||
|
|
||||||
type ToasterProps = React.ComponentProps<typeof Sonner>
|
type ToasterProps = React.ComponentProps<typeof Sonner>
|
||||||
|
|
||||||
const Toaster = ({ ...props }: ToasterProps) => {
|
const Toaster = ({ ...props }: ToasterProps) => {
|
||||||
// const { theme = "system" } = useTheme()
|
|
||||||
const { themeSetting } = useTheme()
|
const { themeSetting } = useTheme()
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Sonner
|
<Sonner
|
||||||
theme={themeSetting}
|
theme={themeSetting === 'pure-black' ? 'dark' : themeSetting}
|
||||||
className="toaster group"
|
className="toaster group"
|
||||||
richColors
|
richColors
|
||||||
mobileOffset={64}
|
mobileOffset={64}
|
||||||
|
|||||||
@@ -455,6 +455,8 @@ export default {
|
|||||||
'Unpinned!': 'تم إلغاء التثبيت!',
|
'Unpinned!': 'تم إلغاء التثبيت!',
|
||||||
'Failed to unpin: {{error}}': 'فشل في إلغاء التثبيت: {{error}}',
|
'Failed to unpin: {{error}}': 'فشل في إلغاء التثبيت: {{error}}',
|
||||||
'Unpin from profile': 'إلغاء التثبيت من الملف الشخصي',
|
'Unpin from profile': 'إلغاء التثبيت من الملف الشخصي',
|
||||||
'Pin to profile': 'تثبيت في الملف الشخصي'
|
'Pin to profile': 'تثبيت في الملف الشخصي',
|
||||||
|
Appearance: 'المظهر',
|
||||||
|
'Pure Black': 'أسود نقي'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -469,6 +469,8 @@ export default {
|
|||||||
'Unpinned!': 'Anheften aufgehoben!',
|
'Unpinned!': 'Anheften aufgehoben!',
|
||||||
'Failed to unpin: {{error}}': 'Fehler beim Anheften aufheben: {{error}}',
|
'Failed to unpin: {{error}}': 'Fehler beim Anheften aufheben: {{error}}',
|
||||||
'Unpin from profile': 'Vom Profil lösen',
|
'Unpin from profile': 'Vom Profil lösen',
|
||||||
'Pin to profile': 'An Profil anheften'
|
'Pin to profile': 'An Profil anheften',
|
||||||
|
Appearance: 'Aussehen',
|
||||||
|
'Pure Black': 'Reines Schwarz'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -454,6 +454,8 @@ export default {
|
|||||||
'Unpinned!': 'Unpinned!',
|
'Unpinned!': 'Unpinned!',
|
||||||
'Failed to unpin: {{error}}': 'Failed to unpin: {{error}}',
|
'Failed to unpin: {{error}}': 'Failed to unpin: {{error}}',
|
||||||
'Unpin from profile': 'Unpin from profile',
|
'Unpin from profile': 'Unpin from profile',
|
||||||
'Pin to profile': 'Pin to profile'
|
'Pin to profile': 'Pin to profile',
|
||||||
|
Appearance: 'Appearance',
|
||||||
|
'Pure Black': 'Pure Black'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -463,6 +463,8 @@ export default {
|
|||||||
'Unpinned!': '¡Desfijado!',
|
'Unpinned!': '¡Desfijado!',
|
||||||
'Failed to unpin: {{error}}': 'Error al desfijar: {{error}}',
|
'Failed to unpin: {{error}}': 'Error al desfijar: {{error}}',
|
||||||
'Unpin from profile': 'Desfijar del perfil',
|
'Unpin from profile': 'Desfijar del perfil',
|
||||||
'Pin to profile': 'Fijar al perfil'
|
'Pin to profile': 'Fijar al perfil',
|
||||||
|
Appearance: 'Apariencia',
|
||||||
|
'Pure Black': 'Negro Puro'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -458,6 +458,8 @@ export default {
|
|||||||
'Unpinned!': 'لغو پین شد!',
|
'Unpinned!': 'لغو پین شد!',
|
||||||
'Failed to unpin: {{error}}': 'لغو پین ناموفق بود: {{error}}',
|
'Failed to unpin: {{error}}': 'لغو پین ناموفق بود: {{error}}',
|
||||||
'Unpin from profile': 'لغو پین از پروفایل',
|
'Unpin from profile': 'لغو پین از پروفایل',
|
||||||
'Pin to profile': 'پین به پروفایل'
|
'Pin to profile': 'پین به پروفایل',
|
||||||
|
Appearance: 'ظاهر',
|
||||||
|
'Pure Black': 'سیاه خالص'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -468,6 +468,8 @@ export default {
|
|||||||
'Unpinned!': 'Retrait de l’épingle effectué !',
|
'Unpinned!': 'Retrait de l’épingle effectué !',
|
||||||
'Failed to unpin: {{error}}': 'Échec du retrait de l’épingle : {{error}}',
|
'Failed to unpin: {{error}}': 'Échec du retrait de l’épingle : {{error}}',
|
||||||
'Unpin from profile': 'Retirer l’épingle du profil',
|
'Unpin from profile': 'Retirer l’épingle du profil',
|
||||||
'Pin to profile': 'Épingler au profil'
|
'Pin to profile': 'Épingler au profil',
|
||||||
|
Appearance: 'Apparence',
|
||||||
|
'Pure Black': 'Noir pur'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -460,6 +460,8 @@ export default {
|
|||||||
'Unpinned!': 'पिन हटा दिया गया!',
|
'Unpinned!': 'पिन हटा दिया गया!',
|
||||||
'Failed to unpin: {{error}}': 'पिन हटाने में असफल: {{error}}',
|
'Failed to unpin: {{error}}': 'पिन हटाने में असफल: {{error}}',
|
||||||
'Unpin from profile': 'प्रोफ़ाइल से पिन हटाएं',
|
'Unpin from profile': 'प्रोफ़ाइल से पिन हटाएं',
|
||||||
'Pin to profile': 'प्रोफ़ाइल पर पिन करें'
|
'Pin to profile': 'प्रोफ़ाइल पर पिन करें',
|
||||||
|
Appearance: 'दिखावट',
|
||||||
|
'Pure Black': 'शुद्ध काला'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -463,6 +463,8 @@ export default {
|
|||||||
'Unpinned!': 'Rimosso fissaggio!',
|
'Unpinned!': 'Rimosso fissaggio!',
|
||||||
'Failed to unpin: {{error}}': 'Impossibile rimuovere il fissaggio: {{error}}',
|
'Failed to unpin: {{error}}': 'Impossibile rimuovere il fissaggio: {{error}}',
|
||||||
'Unpin from profile': 'Rimuovi fissaggio dal profilo',
|
'Unpin from profile': 'Rimuovi fissaggio dal profilo',
|
||||||
'Pin to profile': 'Fissa al profilo'
|
'Pin to profile': 'Fissa al profilo',
|
||||||
|
Appearance: 'Aspetto',
|
||||||
|
'Pure Black': 'Nero Puro'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -459,6 +459,8 @@ export default {
|
|||||||
'Unpinned!': '固定が解除されました!',
|
'Unpinned!': '固定が解除されました!',
|
||||||
'Failed to unpin: {{error}}': '固定解除に失敗しました: {{error}}',
|
'Failed to unpin: {{error}}': '固定解除に失敗しました: {{error}}',
|
||||||
'Unpin from profile': 'プロフィールから固定解除',
|
'Unpin from profile': 'プロフィールから固定解除',
|
||||||
'Pin to profile': 'プロフィールに固定'
|
'Pin to profile': 'プロフィールに固定',
|
||||||
|
Appearance: '外観',
|
||||||
|
'Pure Black': '純黒'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -459,6 +459,8 @@ export default {
|
|||||||
'Unpinned!': '고정 해제됨!',
|
'Unpinned!': '고정 해제됨!',
|
||||||
'Failed to unpin: {{error}}': '고정 해제 실패: {{error}}',
|
'Failed to unpin: {{error}}': '고정 해제 실패: {{error}}',
|
||||||
'Unpin from profile': '프로필에서 고정 해제',
|
'Unpin from profile': '프로필에서 고정 해제',
|
||||||
'Pin to profile': '프로필에 고정'
|
'Pin to profile': '프로필에 고정',
|
||||||
|
Appearance: '외관',
|
||||||
|
'Pure Black': '순수한 검은색'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -463,6 +463,8 @@ export default {
|
|||||||
'Unpinned!': 'Odpięte!',
|
'Unpinned!': 'Odpięte!',
|
||||||
'Failed to unpin: {{error}}': 'Nie udało się przypiąć: {{error}}',
|
'Failed to unpin: {{error}}': 'Nie udało się przypiąć: {{error}}',
|
||||||
'Unpin from profile': 'Odpiń z profilu',
|
'Unpin from profile': 'Odpiń z profilu',
|
||||||
'Pin to profile': 'Przypnij do profilu'
|
'Pin to profile': 'Przypnij do profilu',
|
||||||
|
Appearance: 'Wygląd',
|
||||||
|
'Pure Black': 'Czysta Czerń'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -460,6 +460,8 @@ export default {
|
|||||||
'Unpinned!': 'Desafixado!',
|
'Unpinned!': 'Desafixado!',
|
||||||
'Failed to unpin: {{error}}': 'Falha ao desafixar: {{error}}',
|
'Failed to unpin: {{error}}': 'Falha ao desafixar: {{error}}',
|
||||||
'Unpin from profile': 'Desafixar do perfil',
|
'Unpin from profile': 'Desafixar do perfil',
|
||||||
'Pin to profile': 'Fixar no perfil'
|
'Pin to profile': 'Fixar no perfil',
|
||||||
|
Appearance: 'Aparência',
|
||||||
|
'Pure Black': 'Preto Puro'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -463,6 +463,8 @@ export default {
|
|||||||
'Unpinned!': 'Desafixado!',
|
'Unpinned!': 'Desafixado!',
|
||||||
'Failed to unpin: {{error}}': 'Falha ao desafixar: {{error}}',
|
'Failed to unpin: {{error}}': 'Falha ao desafixar: {{error}}',
|
||||||
'Unpin from profile': 'Desafixar do perfil',
|
'Unpin from profile': 'Desafixar do perfil',
|
||||||
'Pin to profile': 'Fixar no perfil'
|
'Pin to profile': 'Fixar no perfil',
|
||||||
|
Appearance: 'Aparência',
|
||||||
|
'Pure Black': 'Preto Puro'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -465,6 +465,8 @@ export default {
|
|||||||
'Unpinned!': 'Откреплено!',
|
'Unpinned!': 'Откреплено!',
|
||||||
'Failed to unpin: {{error}}': 'Не удалось открепить: {{error}}',
|
'Failed to unpin: {{error}}': 'Не удалось открепить: {{error}}',
|
||||||
'Unpin from profile': 'Открепить из профиля',
|
'Unpin from profile': 'Открепить из профиля',
|
||||||
'Pin to profile': 'Закрепить в профиле'
|
'Pin to profile': 'Закрепить в профиле',
|
||||||
|
Appearance: 'Внешний вид',
|
||||||
|
'Pure Black': 'Чистый Черный'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -453,6 +453,8 @@ export default {
|
|||||||
'Unpinned!': 'ยกเลิกปักหมุดแล้ว!',
|
'Unpinned!': 'ยกเลิกปักหมุดแล้ว!',
|
||||||
'Failed to unpin: {{error}}': 'ไม่สามารถยกเลิกปักหมุดได้: {{error}}',
|
'Failed to unpin: {{error}}': 'ไม่สามารถยกเลิกปักหมุดได้: {{error}}',
|
||||||
'Unpin from profile': 'ยกเลิกปักหมุดจากโปรไฟล์',
|
'Unpin from profile': 'ยกเลิกปักหมุดจากโปรไฟล์',
|
||||||
'Pin to profile': 'ปักหมุดไปที่โปรไฟล์'
|
'Pin to profile': 'ปักหมุดไปที่โปรไฟล์',
|
||||||
|
Appearance: 'รูปลักษณ์',
|
||||||
|
'Pure Black': 'สีดำล้วน'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -451,6 +451,8 @@ export default {
|
|||||||
'Unpinned!': '已取消置顶!',
|
'Unpinned!': '已取消置顶!',
|
||||||
'Failed to unpin: {{error}}': '取消置顶失败: {{error}}',
|
'Failed to unpin: {{error}}': '取消置顶失败: {{error}}',
|
||||||
'Unpin from profile': '从个人资料取消置顶',
|
'Unpin from profile': '从个人资料取消置顶',
|
||||||
'Pin to profile': '置顶到个人资料'
|
'Pin to profile': '置顶到个人资料',
|
||||||
|
Appearance: '外观',
|
||||||
|
'Pure Black': '纯黑'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -134,6 +134,12 @@
|
|||||||
--chart-4: 280 65% 60%;
|
--chart-4: 280 65% 60%;
|
||||||
--chart-5: 340 75% 55%;
|
--chart-5: 340 75% 55%;
|
||||||
}
|
}
|
||||||
|
.dark.pure-black {
|
||||||
|
--surface-background: 0 0% 0%;
|
||||||
|
--background: 0 0% 0%;
|
||||||
|
--card: 0 0% 0%;
|
||||||
|
--popover: 0 0% 0%;
|
||||||
|
}
|
||||||
|
|
||||||
.dark input[type='datetime-local']::-webkit-calendar-picker-indicator {
|
.dark input[type='datetime-local']::-webkit-calendar-picker-indicator {
|
||||||
filter: invert(1) brightness(1.5);
|
filter: invert(1) brightness(1.5);
|
||||||
|
|||||||
@@ -69,6 +69,7 @@ export const toRelaySettings = (tag?: 'mailbox' | 'favorite-relays') => {
|
|||||||
export const toWallet = () => '/settings/wallet'
|
export const toWallet = () => '/settings/wallet'
|
||||||
export const toPostSettings = () => '/settings/posts'
|
export const toPostSettings = () => '/settings/posts'
|
||||||
export const toGeneralSettings = () => '/settings/general'
|
export const toGeneralSettings = () => '/settings/general'
|
||||||
|
export const toAppearanceSettings = () => '/settings/appearance'
|
||||||
export const toTranslation = () => '/settings/translation'
|
export const toTranslation = () => '/settings/translation'
|
||||||
export const toProfileEditor = () => '/profile-editor'
|
export const toProfileEditor = () => '/profile-editor'
|
||||||
export const toRelay = (url: string) => `/relays/${encodeURIComponent(url)}`
|
export const toRelay = (url: string) => `/relays/${encodeURIComponent(url)}`
|
||||||
|
|||||||
48
src/pages/secondary/AppearanceSettingsPage/index.tsx
Normal file
48
src/pages/secondary/AppearanceSettingsPage/index.tsx
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
import { Label } from '@/components/ui/label'
|
||||||
|
import SecondaryPageLayout from '@/layouts/SecondaryPageLayout'
|
||||||
|
import { cn } from '@/lib/utils'
|
||||||
|
import { useTheme } from '@/providers/ThemeProvider'
|
||||||
|
import { Monitor, Moon, Sun } from 'lucide-react'
|
||||||
|
import { forwardRef } from 'react'
|
||||||
|
import { useTranslation } from 'react-i18next'
|
||||||
|
|
||||||
|
const THEMES = [
|
||||||
|
{ key: 'system', label: 'System', icon: <Monitor className="w-5 h-5" /> },
|
||||||
|
{ key: 'light', label: 'Light', icon: <Sun className="w-5 h-5" /> },
|
||||||
|
{ key: 'dark', label: 'Dark', icon: <Moon className="w-5 h-5" /> },
|
||||||
|
{ key: 'pure-black', label: 'Pure Black', icon: <Moon className="w-5 h-5 fill-current" /> }
|
||||||
|
] as const
|
||||||
|
|
||||||
|
const AppearanceSettingsPage = forwardRef(({ index }: { index?: number }, ref) => {
|
||||||
|
const { t } = useTranslation()
|
||||||
|
const { themeSetting, setThemeSetting } = useTheme()
|
||||||
|
|
||||||
|
return (
|
||||||
|
<SecondaryPageLayout ref={ref} index={index} title={t('Appearance')}>
|
||||||
|
<div className="space-y-4 mt-3">
|
||||||
|
<div className="flex flex-col gap-2 px-4">
|
||||||
|
<Label className="text-base">{t('Theme')}</Label>
|
||||||
|
<div className="grid grid-cols-2 md:grid-cols-4 gap-4 w-full">
|
||||||
|
{THEMES.map(({ key, label, icon }) => (
|
||||||
|
<button
|
||||||
|
key={key}
|
||||||
|
onClick={() => {
|
||||||
|
setThemeSetting(key)
|
||||||
|
}}
|
||||||
|
className={cn(
|
||||||
|
'flex flex-col items-center gap-2 py-4 rounded-lg border-2 transition-all',
|
||||||
|
themeSetting === key ? 'border-primary' : 'border-border hover:border-primary/60'
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<div className="flex items-center justify-center w-8 h-8">{icon}</div>
|
||||||
|
<span className="text-xs font-medium">{t(label)}</span>
|
||||||
|
</button>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</SecondaryPageLayout>
|
||||||
|
)
|
||||||
|
})
|
||||||
|
AppearanceSettingsPage.displayName = 'AppearanceSettingsPage'
|
||||||
|
export default AppearanceSettingsPage
|
||||||
@@ -6,7 +6,6 @@ import { LocalizedLanguageNames, TLanguage } from '@/i18n'
|
|||||||
import SecondaryPageLayout from '@/layouts/SecondaryPageLayout'
|
import SecondaryPageLayout from '@/layouts/SecondaryPageLayout'
|
||||||
import { cn, isSupportCheckConnectionType } from '@/lib/utils'
|
import { cn, isSupportCheckConnectionType } from '@/lib/utils'
|
||||||
import { useContentPolicy } from '@/providers/ContentPolicyProvider'
|
import { useContentPolicy } from '@/providers/ContentPolicyProvider'
|
||||||
import { useTheme } from '@/providers/ThemeProvider'
|
|
||||||
import { useUserPreferences } from '@/providers/UserPreferencesProvider'
|
import { useUserPreferences } from '@/providers/UserPreferencesProvider'
|
||||||
import { useUserTrust } from '@/providers/UserTrustProvider'
|
import { useUserTrust } from '@/providers/UserTrustProvider'
|
||||||
import { TMediaAutoLoadPolicy } from '@/types'
|
import { TMediaAutoLoadPolicy } from '@/types'
|
||||||
@@ -18,7 +17,6 @@ import { useTranslation } from 'react-i18next'
|
|||||||
const GeneralSettingsPage = forwardRef(({ index }: { index?: number }, ref) => {
|
const GeneralSettingsPage = forwardRef(({ index }: { index?: number }, ref) => {
|
||||||
const { t, i18n } = useTranslation()
|
const { t, i18n } = useTranslation()
|
||||||
const [language, setLanguage] = useState<TLanguage>(i18n.language as TLanguage)
|
const [language, setLanguage] = useState<TLanguage>(i18n.language as TLanguage)
|
||||||
const { themeSetting, setThemeSetting } = useTheme()
|
|
||||||
const {
|
const {
|
||||||
autoplay,
|
autoplay,
|
||||||
setAutoplay,
|
setAutoplay,
|
||||||
@@ -57,21 +55,6 @@ const GeneralSettingsPage = forwardRef(({ index }: { index?: number }, ref) => {
|
|||||||
</SelectContent>
|
</SelectContent>
|
||||||
</Select>
|
</Select>
|
||||||
</SettingItem>
|
</SettingItem>
|
||||||
<SettingItem>
|
|
||||||
<Label htmlFor="theme" className="text-base font-normal">
|
|
||||||
{t('Theme')}
|
|
||||||
</Label>
|
|
||||||
<Select defaultValue="system" value={themeSetting} onValueChange={setThemeSetting}>
|
|
||||||
<SelectTrigger id="theme" className="w-48">
|
|
||||||
<SelectValue />
|
|
||||||
</SelectTrigger>
|
|
||||||
<SelectContent>
|
|
||||||
<SelectItem value="system">{t('System')}</SelectItem>
|
|
||||||
<SelectItem value="light">{t('Light')}</SelectItem>
|
|
||||||
<SelectItem value="dark">{t('Dark')}</SelectItem>
|
|
||||||
</SelectContent>
|
|
||||||
</Select>
|
|
||||||
</SettingItem>
|
|
||||||
<SettingItem>
|
<SettingItem>
|
||||||
<Label htmlFor="notification-list-style" className="text-base font-normal">
|
<Label htmlFor="notification-list-style" className="text-base font-normal">
|
||||||
<div>{t('Notification list style')}</div>
|
<div>{t('Notification list style')}</div>
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import AboutInfoDialog from '@/components/AboutInfoDialog'
|
|||||||
import Donation from '@/components/Donation'
|
import Donation from '@/components/Donation'
|
||||||
import SecondaryPageLayout from '@/layouts/SecondaryPageLayout'
|
import SecondaryPageLayout from '@/layouts/SecondaryPageLayout'
|
||||||
import {
|
import {
|
||||||
|
toAppearanceSettings,
|
||||||
toGeneralSettings,
|
toGeneralSettings,
|
||||||
toPostSettings,
|
toPostSettings,
|
||||||
toRelaySettings,
|
toRelaySettings,
|
||||||
@@ -18,6 +19,7 @@ import {
|
|||||||
Info,
|
Info,
|
||||||
KeyRound,
|
KeyRound,
|
||||||
Languages,
|
Languages,
|
||||||
|
Palette,
|
||||||
PencilLine,
|
PencilLine,
|
||||||
Server,
|
Server,
|
||||||
Settings2,
|
Settings2,
|
||||||
@@ -42,6 +44,13 @@ const SettingsPage = forwardRef(({ index }: { index?: number }, ref) => {
|
|||||||
</div>
|
</div>
|
||||||
<ChevronRight />
|
<ChevronRight />
|
||||||
</SettingItem>
|
</SettingItem>
|
||||||
|
<SettingItem className="clickable" onClick={() => push(toAppearanceSettings())}>
|
||||||
|
<div className="flex items-center gap-4">
|
||||||
|
<Palette />
|
||||||
|
<div>{t('Appearance')}</div>
|
||||||
|
</div>
|
||||||
|
<ChevronRight />
|
||||||
|
</SettingItem>
|
||||||
<SettingItem className="clickable" onClick={() => push(toRelaySettings())}>
|
<SettingItem className="clickable" onClick={() => push(toRelaySettings())}>
|
||||||
<div className="flex items-center gap-4">
|
<div className="flex items-center gap-4">
|
||||||
<Server />
|
<Server />
|
||||||
|
|||||||
@@ -2,14 +2,8 @@ import storage from '@/services/local-storage.service'
|
|||||||
import { TTheme, TThemeSetting } from '@/types'
|
import { TTheme, TThemeSetting } from '@/types'
|
||||||
import { createContext, useContext, useEffect, useState } from 'react'
|
import { createContext, useContext, useEffect, useState } from 'react'
|
||||||
|
|
||||||
type ThemeProviderProps = {
|
|
||||||
children: React.ReactNode
|
|
||||||
defaultTheme?: TTheme
|
|
||||||
}
|
|
||||||
|
|
||||||
type ThemeProviderState = {
|
type ThemeProviderState = {
|
||||||
themeSetting: TThemeSetting
|
themeSetting: TThemeSetting
|
||||||
theme: TTheme
|
|
||||||
setThemeSetting: (themeSetting: TThemeSetting) => Promise<void>
|
setThemeSetting: (themeSetting: TThemeSetting) => Promise<void>
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -19,7 +13,7 @@ function getSystemTheme() {
|
|||||||
|
|
||||||
const ThemeProviderContext = createContext<ThemeProviderState | undefined>(undefined)
|
const ThemeProviderContext = createContext<ThemeProviderState | undefined>(undefined)
|
||||||
|
|
||||||
export function ThemeProvider({ children, ...props }: ThemeProviderProps) {
|
export function ThemeProvider({ children }: { children: React.ReactNode }) {
|
||||||
const [themeSetting, setThemeSetting] = useState<TThemeSetting>(
|
const [themeSetting, setThemeSetting] = useState<TThemeSetting>(
|
||||||
(localStorage.getItem('themeSetting') as TThemeSetting | null) ?? 'system'
|
(localStorage.getItem('themeSetting') as TThemeSetting | null) ?? 'system'
|
||||||
)
|
)
|
||||||
@@ -39,7 +33,10 @@ export function ThemeProvider({ children, ...props }: ThemeProviderProps) {
|
|||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (themeSetting !== 'system') return
|
if (themeSetting !== 'system') {
|
||||||
|
setTheme(themeSetting)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)')
|
const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)')
|
||||||
const handleChange = (e: MediaQueryListEvent) => {
|
const handleChange = (e: MediaQueryListEvent) => {
|
||||||
@@ -57,27 +54,27 @@ export function ThemeProvider({ children, ...props }: ThemeProviderProps) {
|
|||||||
const updateTheme = async () => {
|
const updateTheme = async () => {
|
||||||
const root = window.document.documentElement
|
const root = window.document.documentElement
|
||||||
root.classList.remove('light', 'dark')
|
root.classList.remove('light', 'dark')
|
||||||
root.classList.add(theme)
|
root.classList.add(theme === 'pure-black' ? 'dark' : theme)
|
||||||
localStorage.setItem('theme', theme)
|
|
||||||
|
if (theme === 'pure-black') {
|
||||||
|
root.classList.add('pure-black')
|
||||||
|
} else {
|
||||||
|
root.classList.remove('pure-black')
|
||||||
|
}
|
||||||
}
|
}
|
||||||
updateTheme()
|
updateTheme()
|
||||||
}, [theme])
|
}, [theme])
|
||||||
|
|
||||||
|
const updateThemeSetting = async (themeSetting: TThemeSetting) => {
|
||||||
|
storage.setThemeSetting(themeSetting)
|
||||||
|
setThemeSetting(themeSetting)
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ThemeProviderContext.Provider
|
<ThemeProviderContext.Provider
|
||||||
{...props}
|
|
||||||
value={{
|
value={{
|
||||||
themeSetting: themeSetting,
|
themeSetting: themeSetting,
|
||||||
theme: theme,
|
setThemeSetting: updateThemeSetting
|
||||||
setThemeSetting: async (themeSetting: TThemeSetting) => {
|
|
||||||
storage.setThemeSetting(themeSetting)
|
|
||||||
setThemeSetting(themeSetting)
|
|
||||||
if (themeSetting === 'system') {
|
|
||||||
setTheme(getSystemTheme())
|
|
||||||
return
|
|
||||||
}
|
|
||||||
setTheme(themeSetting)
|
|
||||||
}
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { match } from 'path-to-regexp'
|
import { match } from 'path-to-regexp'
|
||||||
import { isValidElement } from 'react'
|
import { isValidElement } from 'react'
|
||||||
|
import AppearanceSettingsPage from './pages/secondary/AppearanceSettingsPage'
|
||||||
import FollowingListPage from './pages/secondary/FollowingListPage'
|
import FollowingListPage from './pages/secondary/FollowingListPage'
|
||||||
import GeneralSettingsPage from './pages/secondary/GeneralSettingsPage'
|
import GeneralSettingsPage from './pages/secondary/GeneralSettingsPage'
|
||||||
import MuteListPage from './pages/secondary/MuteListPage'
|
import MuteListPage from './pages/secondary/MuteListPage'
|
||||||
@@ -34,6 +35,7 @@ const ROUTES = [
|
|||||||
{ path: '/settings/wallet', element: <WalletPage /> },
|
{ path: '/settings/wallet', element: <WalletPage /> },
|
||||||
{ path: '/settings/posts', element: <PostSettingsPage /> },
|
{ path: '/settings/posts', element: <PostSettingsPage /> },
|
||||||
{ path: '/settings/general', element: <GeneralSettingsPage /> },
|
{ path: '/settings/general', element: <GeneralSettingsPage /> },
|
||||||
|
{ path: '/settings/appearance', element: <AppearanceSettingsPage /> },
|
||||||
{ path: '/settings/translation', element: <TranslationPage /> },
|
{ path: '/settings/translation', element: <TranslationPage /> },
|
||||||
{ path: '/profile-editor', element: <ProfileEditorPage /> },
|
{ path: '/profile-editor', element: <ProfileEditorPage /> },
|
||||||
{ path: '/mutes', element: <MuteListPage /> },
|
{ path: '/mutes', element: <MuteListPage /> },
|
||||||
|
|||||||
4
src/types/index.d.ts
vendored
4
src/types/index.d.ts
vendored
@@ -71,8 +71,8 @@ export type TConfig = {
|
|||||||
theme: TThemeSetting
|
theme: TThemeSetting
|
||||||
}
|
}
|
||||||
|
|
||||||
export type TThemeSetting = 'light' | 'dark' | 'system'
|
export type TThemeSetting = 'light' | 'dark' | 'system' | 'pure-black'
|
||||||
export type TTheme = 'light' | 'dark'
|
export type TTheme = 'light' | 'dark' | 'pure-black'
|
||||||
|
|
||||||
export type TDraftEvent = Pick<Event, 'content' | 'created_at' | 'kind' | 'tags'>
|
export type TDraftEvent = Pick<Event, 'content' | 'created_at' | 'kind' | 'tags'>
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user