feat: add single column layout toggle option
This commit is contained in:
72
src/App.tsx
72
src/App.tsx
@@ -24,43 +24,43 @@ import { PageManager } from './PageManager'
|
||||
|
||||
export default function App(): JSX.Element {
|
||||
return (
|
||||
<ThemeProvider>
|
||||
<ContentPolicyProvider>
|
||||
<ScreenSizeProvider>
|
||||
<DeletedEventProvider>
|
||||
<NostrProvider>
|
||||
<ZapProvider>
|
||||
<TranslationServiceProvider>
|
||||
<FavoriteRelaysProvider>
|
||||
<FollowListProvider>
|
||||
<MuteListProvider>
|
||||
<UserTrustProvider>
|
||||
<BookmarksProvider>
|
||||
<PinListProvider>
|
||||
<FeedProvider>
|
||||
<ReplyProvider>
|
||||
<MediaUploadServiceProvider>
|
||||
<KindFilterProvider>
|
||||
<UserPreferencesProvider>
|
||||
<UserPreferencesProvider>
|
||||
<ThemeProvider>
|
||||
<ContentPolicyProvider>
|
||||
<ScreenSizeProvider>
|
||||
<DeletedEventProvider>
|
||||
<NostrProvider>
|
||||
<ZapProvider>
|
||||
<TranslationServiceProvider>
|
||||
<FavoriteRelaysProvider>
|
||||
<FollowListProvider>
|
||||
<MuteListProvider>
|
||||
<UserTrustProvider>
|
||||
<BookmarksProvider>
|
||||
<PinListProvider>
|
||||
<FeedProvider>
|
||||
<ReplyProvider>
|
||||
<MediaUploadServiceProvider>
|
||||
<KindFilterProvider>
|
||||
<PageManager />
|
||||
<Toaster />
|
||||
</UserPreferencesProvider>
|
||||
</KindFilterProvider>
|
||||
</MediaUploadServiceProvider>
|
||||
</ReplyProvider>
|
||||
</FeedProvider>
|
||||
</PinListProvider>
|
||||
</BookmarksProvider>
|
||||
</UserTrustProvider>
|
||||
</MuteListProvider>
|
||||
</FollowListProvider>
|
||||
</FavoriteRelaysProvider>
|
||||
</TranslationServiceProvider>
|
||||
</ZapProvider>
|
||||
</NostrProvider>
|
||||
</DeletedEventProvider>
|
||||
</ScreenSizeProvider>
|
||||
</ContentPolicyProvider>
|
||||
</ThemeProvider>
|
||||
</KindFilterProvider>
|
||||
</MediaUploadServiceProvider>
|
||||
</ReplyProvider>
|
||||
</FeedProvider>
|
||||
</PinListProvider>
|
||||
</BookmarksProvider>
|
||||
</UserTrustProvider>
|
||||
</MuteListProvider>
|
||||
</FollowListProvider>
|
||||
</FavoriteRelaysProvider>
|
||||
</TranslationServiceProvider>
|
||||
</ZapProvider>
|
||||
</NostrProvider>
|
||||
</DeletedEventProvider>
|
||||
</ScreenSizeProvider>
|
||||
</ContentPolicyProvider>
|
||||
</ThemeProvider>
|
||||
</UserPreferencesProvider>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -29,6 +29,7 @@ import SearchPage from './pages/primary/SearchPage'
|
||||
import { NotificationProvider } from './providers/NotificationProvider'
|
||||
import { useScreenSize } from './providers/ScreenSizeProvider'
|
||||
import { useTheme } from './providers/ThemeProvider'
|
||||
import { useUserPreferences } from './providers/UserPreferencesProvider'
|
||||
import { routes } from './routes'
|
||||
import modalManager from './services/modal-manager.service'
|
||||
|
||||
@@ -106,6 +107,7 @@ export function PageManager({ maxStackSize = 5 }: { maxStackSize?: number }) {
|
||||
const [secondaryStack, setSecondaryStack] = useState<TStackItem[]>([])
|
||||
const { isSmallScreen } = useScreenSize()
|
||||
const { themeSetting } = useTheme()
|
||||
const { enableSingleColumnLayout } = useUserPreferences()
|
||||
const ignorePopStateRef = useRef(false)
|
||||
|
||||
useEffect(() => {
|
||||
@@ -243,7 +245,7 @@ export function PageManager({ maxStackSize = 5 }: { maxStackSize?: number }) {
|
||||
if (needScrollToTop) {
|
||||
PRIMARY_PAGE_REF_MAP[page].current?.scrollToTop('smooth')
|
||||
}
|
||||
if (isSmallScreen) {
|
||||
if (isSmallScreen || enableSingleColumnLayout) {
|
||||
clearSecondaryPages()
|
||||
}
|
||||
}
|
||||
@@ -333,6 +335,79 @@ export function PageManager({ maxStackSize = 5 }: { maxStackSize?: number }) {
|
||||
)
|
||||
}
|
||||
|
||||
if (enableSingleColumnLayout) {
|
||||
return (
|
||||
<PrimaryPageContext.Provider
|
||||
value={{
|
||||
navigate: navigatePrimaryPage,
|
||||
current: currentPrimaryPage,
|
||||
display: secondaryStack.length === 0
|
||||
}}
|
||||
>
|
||||
<SecondaryPageContext.Provider
|
||||
value={{
|
||||
push: pushSecondaryPage,
|
||||
pop: popSecondaryPage,
|
||||
currentIndex: secondaryStack.length
|
||||
? secondaryStack[secondaryStack.length - 1].index
|
||||
: 0
|
||||
}}
|
||||
>
|
||||
<CurrentRelaysProvider>
|
||||
<NotificationProvider>
|
||||
<div className="flex flex-col items-center bg-surface-background">
|
||||
<div
|
||||
className="flex h-[var(--vh)] w-full bg-surface-background"
|
||||
style={{
|
||||
maxWidth: '1920px'
|
||||
}}
|
||||
>
|
||||
<div className="grid grid-cols-10 w-full">
|
||||
<div className="col-span-3 flex justify-end">
|
||||
<Sidebar />
|
||||
</div>
|
||||
<div className="col-span-4 bg-background overflow-hidden border-x">
|
||||
{!!secondaryStack.length &&
|
||||
secondaryStack.map((item, index) => (
|
||||
<div
|
||||
key={item.index}
|
||||
className="flex flex-col h-full w-full"
|
||||
style={{
|
||||
display: index === secondaryStack.length - 1 ? 'block' : 'none'
|
||||
}}
|
||||
>
|
||||
{item.component}
|
||||
</div>
|
||||
))}
|
||||
{primaryPages.map(({ name, element, props }) => (
|
||||
<div
|
||||
key={name}
|
||||
className="flex flex-col h-full w-full"
|
||||
style={{
|
||||
display:
|
||||
secondaryStack.length === 0 && currentPrimaryPage === name
|
||||
? 'block'
|
||||
: 'none'
|
||||
}}
|
||||
>
|
||||
{props ? cloneElement(element as React.ReactElement, props) : element}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
<div className="col-span-3" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<TooManyRelaysAlertDialog />
|
||||
<CreateWalletGuideToast />
|
||||
<BackgroundAudio className="fixed bottom-20 right-0 z-50 w-80 rounded-l-full rounded-r-none overflow-hidden shadow-lg border" />
|
||||
</NotificationProvider>
|
||||
</CurrentRelaysProvider>
|
||||
</SecondaryPageContext.Provider>
|
||||
</PrimaryPageContext.Provider>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<PrimaryPageContext.Provider
|
||||
value={{
|
||||
|
||||
@@ -17,7 +17,7 @@ import SettingsButton from './SettingsButton'
|
||||
export default function PrimaryPageSidebar() {
|
||||
const { isSmallScreen } = useScreenSize()
|
||||
const { themeSetting } = useTheme()
|
||||
const { sidebarCollapse, updateSidebarCollapse } = useUserPreferences()
|
||||
const { sidebarCollapse, updateSidebarCollapse, enableSingleColumnLayout } = useUserPreferences()
|
||||
|
||||
if (isSmallScreen) return null
|
||||
|
||||
@@ -25,11 +25,11 @@ export default function PrimaryPageSidebar() {
|
||||
<div
|
||||
className={cn(
|
||||
'relative flex flex-col pb-2 pt-3 justify-between h-full shrink-0',
|
||||
sidebarCollapse ? 'px-2 w-16' : 'px-4 w-52'
|
||||
sidebarCollapse && !enableSingleColumnLayout ? 'px-2 w-16' : 'px-4 w-52'
|
||||
)}
|
||||
>
|
||||
<div className="space-y-2">
|
||||
{sidebarCollapse ? (
|
||||
{sidebarCollapse && !enableSingleColumnLayout ? (
|
||||
<div className="px-3 py-1 mb-6 w-full">
|
||||
<Icon />
|
||||
</div>
|
||||
@@ -38,27 +38,29 @@ export default function PrimaryPageSidebar() {
|
||||
<Logo />
|
||||
</div>
|
||||
)}
|
||||
<HomeButton collapse={sidebarCollapse} />
|
||||
<RelaysButton collapse={sidebarCollapse} />
|
||||
<NotificationsButton collapse={sidebarCollapse} />
|
||||
<SearchButton collapse={sidebarCollapse} />
|
||||
<ProfileButton collapse={sidebarCollapse} />
|
||||
<SettingsButton collapse={sidebarCollapse} />
|
||||
<PostButton collapse={sidebarCollapse} />
|
||||
<HomeButton collapse={sidebarCollapse && !enableSingleColumnLayout} />
|
||||
<RelaysButton collapse={sidebarCollapse && !enableSingleColumnLayout} />
|
||||
<NotificationsButton collapse={sidebarCollapse && !enableSingleColumnLayout} />
|
||||
<SearchButton collapse={sidebarCollapse && !enableSingleColumnLayout} />
|
||||
<ProfileButton collapse={sidebarCollapse && !enableSingleColumnLayout} />
|
||||
<SettingsButton collapse={sidebarCollapse && !enableSingleColumnLayout} />
|
||||
<PostButton collapse={sidebarCollapse && !enableSingleColumnLayout} />
|
||||
</div>
|
||||
<AccountButton collapse={sidebarCollapse} />
|
||||
<button
|
||||
className={cn(
|
||||
'absolute flex flex-col justify-center items-center right-0 w-5 h-6 p-0 rounded-l-md hover:shadow-md text-muted-foreground hover:text-foreground hover:bg-background transition-colors [&_svg]:size-4',
|
||||
themeSetting === 'pure-black' ? 'top-3' : 'top-5'
|
||||
)}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation()
|
||||
updateSidebarCollapse(!sidebarCollapse)
|
||||
}}
|
||||
>
|
||||
{sidebarCollapse ? <ChevronsRight /> : <ChevronsLeft />}
|
||||
</button>
|
||||
<AccountButton collapse={sidebarCollapse && !enableSingleColumnLayout} />
|
||||
{!enableSingleColumnLayout && (
|
||||
<button
|
||||
className={cn(
|
||||
'absolute flex flex-col justify-center items-center right-0 w-5 h-6 p-0 rounded-l-md hover:shadow-md text-muted-foreground hover:text-foreground hover:bg-background transition-colors [&_svg]:size-4',
|
||||
themeSetting === 'pure-black' ? 'top-3' : 'top-5'
|
||||
)}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation()
|
||||
updateSidebarCollapse(!sidebarCollapse)
|
||||
}}
|
||||
>
|
||||
{sidebarCollapse ? <ChevronsRight /> : <ChevronsLeft />}
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -50,6 +50,7 @@ export const StorageKey = {
|
||||
SHOWN_CREATE_WALLET_GUIDE_TOAST_PUBKEYS: 'shownCreateWalletGuideToastPubkeys',
|
||||
SIDEBAR_COLLAPSE: 'sidebarCollapse',
|
||||
PRIMARY_COLOR: 'primaryColor',
|
||||
ENABLE_SINGLE_COLUMN_LAYOUT: 'enableSingleColumnLayout',
|
||||
MEDIA_UPLOAD_SERVICE: 'mediaUploadService', // deprecated
|
||||
HIDE_UNTRUSTED_EVENTS: 'hideUntrustedEvents', // deprecated
|
||||
ACCOUNT_RELAY_LIST_EVENT_MAP: 'accountRelayListEventMap', // deprecated
|
||||
|
||||
@@ -476,6 +476,9 @@ export default {
|
||||
Fuchsia: 'فوشيا',
|
||||
Pink: 'وردي',
|
||||
Rose: 'وردة',
|
||||
'Primary color': 'اللون الأساسي'
|
||||
'Primary color': 'اللون الأساسي',
|
||||
Layout: 'التخطيط',
|
||||
'Double column': 'عمودين',
|
||||
'Single column': 'عمود واحد'
|
||||
}
|
||||
}
|
||||
|
||||
@@ -490,6 +490,9 @@ export default {
|
||||
Fuchsia: 'Fuchsia',
|
||||
Pink: 'Rosa',
|
||||
Rose: 'Rose',
|
||||
'Primary color': 'Primärfarbe'
|
||||
'Primary color': 'Primärfarbe',
|
||||
Layout: 'Layout',
|
||||
'Double column': 'Zweispaltig',
|
||||
'Single column': 'Einspaltig'
|
||||
}
|
||||
}
|
||||
|
||||
@@ -475,6 +475,9 @@ export default {
|
||||
Fuchsia: 'Fuchsia',
|
||||
Pink: 'Pink',
|
||||
Rose: 'Rose',
|
||||
'Primary color': 'Primary color'
|
||||
'Primary color': 'Primary color',
|
||||
Layout: 'Layout',
|
||||
'Double column': 'Double column',
|
||||
'Single column': 'Single column'
|
||||
}
|
||||
}
|
||||
|
||||
@@ -484,6 +484,9 @@ export default {
|
||||
Fuchsia: 'Fucsia',
|
||||
Pink: 'Rosa',
|
||||
Rose: 'Rosa',
|
||||
'Primary color': 'Color primario'
|
||||
'Primary color': 'Color primario',
|
||||
Layout: 'Diseño',
|
||||
'Double column': 'Doble columna',
|
||||
'Single column': 'Columna única'
|
||||
}
|
||||
}
|
||||
|
||||
@@ -479,6 +479,9 @@ export default {
|
||||
Fuchsia: 'فوشیا',
|
||||
Pink: 'صورتی',
|
||||
Rose: 'گلی',
|
||||
'Primary color': 'رنگ اصلی'
|
||||
'Primary color': 'رنگ اصلی',
|
||||
Layout: 'چیدمان',
|
||||
'Double column': 'دو ستونی',
|
||||
'Single column': 'تک ستونی'
|
||||
}
|
||||
}
|
||||
|
||||
@@ -489,6 +489,9 @@ export default {
|
||||
Fuchsia: 'Fuchsia',
|
||||
Pink: 'Rose',
|
||||
Rose: 'Rose',
|
||||
'Primary color': 'Couleur principale'
|
||||
'Primary color': 'Couleur principale',
|
||||
Layout: 'Disposition',
|
||||
'Double column': 'Double colonne',
|
||||
'Single column': 'Colonne unique'
|
||||
}
|
||||
}
|
||||
|
||||
@@ -481,6 +481,9 @@ export default {
|
||||
Fuchsia: 'फुशिया',
|
||||
Pink: 'गुलाबी',
|
||||
Rose: 'गुलाब',
|
||||
'Primary color': 'प्राथमिक रंग'
|
||||
'Primary color': 'प्राथमिक रंग',
|
||||
Layout: 'लेआउट',
|
||||
'Double column': 'दोहरा स्तंभ',
|
||||
'Single column': 'एकल स्तंभ'
|
||||
}
|
||||
}
|
||||
|
||||
@@ -484,6 +484,9 @@ export default {
|
||||
Fuchsia: 'Fucsia',
|
||||
Pink: 'Rosa',
|
||||
Rose: 'Rosa',
|
||||
'Primary color': 'Colore primario'
|
||||
'Primary color': 'Colore primario',
|
||||
Layout: 'Layout',
|
||||
'Double column': 'Doppia colonna',
|
||||
'Single column': 'Colonna singola'
|
||||
}
|
||||
}
|
||||
|
||||
@@ -480,6 +480,9 @@ export default {
|
||||
Fuchsia: 'フクシア',
|
||||
Pink: 'ピンク',
|
||||
Rose: 'ローズ',
|
||||
'Primary color': '主要な色'
|
||||
'Primary color': '主要な色',
|
||||
Layout: 'レイアウト',
|
||||
'Double column': '2列',
|
||||
'Single column': '1列'
|
||||
}
|
||||
}
|
||||
|
||||
@@ -480,6 +480,9 @@ export default {
|
||||
Fuchsia: '자홍',
|
||||
Pink: '분홍',
|
||||
Rose: '장미',
|
||||
'Primary color': '기본 색상'
|
||||
'Primary color': '기본 색상',
|
||||
Layout: '레이아웃',
|
||||
'Double column': '두 열',
|
||||
'Single column': '한 열'
|
||||
}
|
||||
}
|
||||
|
||||
@@ -484,6 +484,9 @@ export default {
|
||||
Fuchsia: 'Fuksja',
|
||||
Pink: 'Różowy',
|
||||
Rose: 'Różany',
|
||||
'Primary color': 'Kolor podstawowy'
|
||||
'Primary color': 'Kolor podstawowy',
|
||||
Layout: 'Układ',
|
||||
'Double column': 'Dwie kolumny',
|
||||
'Single column': 'Jedna kolumna'
|
||||
}
|
||||
}
|
||||
|
||||
@@ -481,6 +481,9 @@ export default {
|
||||
Fuchsia: 'Fúcsia',
|
||||
Pink: 'Rosa',
|
||||
Rose: 'Rosa',
|
||||
'Primary color': 'Cor primária'
|
||||
'Primary color': 'Cor primária',
|
||||
Layout: 'Layout',
|
||||
'Double column': 'Coluna dupla',
|
||||
'Single column': 'Coluna única'
|
||||
}
|
||||
}
|
||||
|
||||
@@ -484,6 +484,9 @@ export default {
|
||||
Fuchsia: 'Fúcsia',
|
||||
Pink: 'Rosa',
|
||||
Rose: 'Rosa',
|
||||
'Primary color': 'Cor primária'
|
||||
'Primary color': 'Cor primária',
|
||||
Layout: 'Layout',
|
||||
'Double column': 'Coluna dupla',
|
||||
'Single column': 'Coluna única'
|
||||
}
|
||||
}
|
||||
|
||||
@@ -486,6 +486,9 @@ export default {
|
||||
Fuchsia: 'Фуксия',
|
||||
Pink: 'Розовый',
|
||||
Rose: 'Роза',
|
||||
'Primary color': 'Основной цвет'
|
||||
'Primary color': 'Основной цвет',
|
||||
Layout: 'Макет',
|
||||
'Double column': 'Две колонки',
|
||||
'Single column': 'Одна колонка'
|
||||
}
|
||||
}
|
||||
|
||||
@@ -474,6 +474,9 @@ export default {
|
||||
Fuchsia: 'บานเย็น',
|
||||
Pink: 'ชมพู',
|
||||
Rose: 'กุหลาบ',
|
||||
'Primary color': 'สีหลัก'
|
||||
'Primary color': 'สีหลัก',
|
||||
Layout: 'เค้าโครง',
|
||||
'Double column': 'สองคอลัมน์',
|
||||
'Single column': 'คอลัมน์เดียว'
|
||||
}
|
||||
}
|
||||
|
||||
@@ -472,6 +472,9 @@ export default {
|
||||
Fuchsia: '紫红色',
|
||||
Pink: '粉色',
|
||||
Rose: '玫瑰色',
|
||||
'Primary color': '主色调'
|
||||
'Primary color': '主色调',
|
||||
Layout: '布局',
|
||||
'Double column': '双栏',
|
||||
'Single column': '单栏'
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,43 +3,56 @@ import { PRIMARY_COLORS, TPrimaryColor } from '@/constants'
|
||||
import SecondaryPageLayout from '@/layouts/SecondaryPageLayout'
|
||||
import { cn } from '@/lib/utils'
|
||||
import { useTheme } from '@/providers/ThemeProvider'
|
||||
import { Monitor, Moon, Sun } from 'lucide-react'
|
||||
import { useUserPreferences } from '@/providers/UserPreferencesProvider'
|
||||
import { Monitor, Moon, Sun, Columns2, PanelLeft } 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" /> }
|
||||
{ key: 'system', label: 'System', icon: <Monitor className="size-5" /> },
|
||||
{ key: 'light', label: 'Light', icon: <Sun className="size-5" /> },
|
||||
{ key: 'dark', label: 'Dark', icon: <Moon className="size-5" /> },
|
||||
{ key: 'pure-black', label: 'Pure Black', icon: <Moon className="size-5 fill-current" /> }
|
||||
] as const
|
||||
|
||||
const LAYOUTS = [
|
||||
{ key: false, label: 'Double column', icon: <Columns2 className="size-5" /> },
|
||||
{ key: true, label: 'Single column', icon: <PanelLeft className="size-5" /> }
|
||||
] as const
|
||||
|
||||
const AppearanceSettingsPage = forwardRef(({ index }: { index?: number }, ref) => {
|
||||
const { t } = useTranslation()
|
||||
const { themeSetting, setThemeSetting, primaryColor, setPrimaryColor } = useTheme()
|
||||
const { enableSingleColumnLayout, updateEnableSingleColumnLayout } = useUserPreferences()
|
||||
|
||||
return (
|
||||
<SecondaryPageLayout ref={ref} index={index} title={t('Appearance')}>
|
||||
<div className="space-y-4 my-3">
|
||||
<div className="flex flex-col gap-2 px-4">
|
||||
<Label className="text-base">{t('Layout')}</Label>
|
||||
<div className="grid grid-cols-2 gap-4 w-full">
|
||||
{LAYOUTS.map(({ key, label, icon }) => (
|
||||
<OptionButton
|
||||
key={key.toString()}
|
||||
isSelected={enableSingleColumnLayout === key}
|
||||
icon={icon}
|
||||
label={t(label)}
|
||||
onClick={() => updateEnableSingleColumnLayout(key)}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
<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
|
||||
<OptionButton
|
||||
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-muted-foreground/40'
|
||||
)}
|
||||
>
|
||||
<div className="flex items-center justify-center w-8 h-8">{icon}</div>
|
||||
<span className="text-xs font-medium">{t(label)}</span>
|
||||
</button>
|
||||
isSelected={themeSetting === key}
|
||||
icon={icon}
|
||||
label={t(label)}
|
||||
onClick={() => setThemeSetting(key)}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
@@ -47,24 +60,20 @@ const AppearanceSettingsPage = forwardRef(({ index }: { index?: number }, ref) =
|
||||
<Label className="text-base">{t('Primary color')}</Label>
|
||||
<div className="grid grid-cols-4 gap-4 w-full">
|
||||
{Object.entries(PRIMARY_COLORS).map(([key, config]) => (
|
||||
<button
|
||||
<OptionButton
|
||||
key={key}
|
||||
isSelected={primaryColor === key}
|
||||
icon={
|
||||
<div
|
||||
className="size-8 rounded-full shadow-md"
|
||||
style={{
|
||||
backgroundColor: `hsl(${config.light.primary})`
|
||||
}}
|
||||
/>
|
||||
}
|
||||
label={t(config.name)}
|
||||
onClick={() => setPrimaryColor(key as TPrimaryColor)}
|
||||
className={cn(
|
||||
'flex flex-col items-center gap-2 py-4 rounded-lg border-2 transition-all',
|
||||
primaryColor === key
|
||||
? 'border-primary'
|
||||
: 'border-border hover:border-muted-foreground/40'
|
||||
)}
|
||||
>
|
||||
<div
|
||||
className="size-8 rounded-full shadow-md"
|
||||
style={{
|
||||
backgroundColor: `hsl(${config.light.primary})`
|
||||
}}
|
||||
/>
|
||||
<span className="text-xs font-medium">{t(config.name)}</span>
|
||||
</button>
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
@@ -74,3 +83,28 @@ const AppearanceSettingsPage = forwardRef(({ index }: { index?: number }, ref) =
|
||||
})
|
||||
AppearanceSettingsPage.displayName = 'AppearanceSettingsPage'
|
||||
export default AppearanceSettingsPage
|
||||
|
||||
const OptionButton = ({
|
||||
isSelected,
|
||||
onClick,
|
||||
icon,
|
||||
label
|
||||
}: {
|
||||
isSelected: boolean
|
||||
onClick: () => void
|
||||
icon: React.ReactNode
|
||||
label: string
|
||||
}) => {
|
||||
return (
|
||||
<button
|
||||
onClick={onClick}
|
||||
className={cn(
|
||||
'flex flex-col items-center gap-2 py-4 rounded-lg border-2 transition-all',
|
||||
isSelected ? 'border-primary' : 'border-border hover:border-muted-foreground/40'
|
||||
)}
|
||||
>
|
||||
<div className="flex items-center justify-center w-8 h-8">{icon}</div>
|
||||
<span className="text-xs font-medium">{label}</span>
|
||||
</button>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -11,6 +11,9 @@ type TUserPreferencesContext = {
|
||||
|
||||
sidebarCollapse: boolean
|
||||
updateSidebarCollapse: (collapse: boolean) => void
|
||||
|
||||
enableSingleColumnLayout: boolean
|
||||
updateEnableSingleColumnLayout: (enable: boolean) => void
|
||||
}
|
||||
|
||||
const UserPreferencesContext = createContext<TUserPreferencesContext | undefined>(undefined)
|
||||
@@ -29,6 +32,9 @@ export function UserPreferencesProvider({ children }: { children: React.ReactNod
|
||||
)
|
||||
const [muteMedia, setMuteMedia] = useState(true)
|
||||
const [sidebarCollapse, setSidebarCollapse] = useState(storage.getSidebarCollapse())
|
||||
const [enableSingleColumnLayout, setEnableSingleColumnLayout] = useState(
|
||||
storage.getEnableSingleColumnLayout()
|
||||
)
|
||||
|
||||
const updateNotificationListStyle = (style: TNotificationStyle) => {
|
||||
setNotificationListStyle(style)
|
||||
@@ -40,6 +46,11 @@ export function UserPreferencesProvider({ children }: { children: React.ReactNod
|
||||
storage.setSidebarCollapse(collapse)
|
||||
}
|
||||
|
||||
const updateEnableSingleColumnLayout = (enable: boolean) => {
|
||||
setEnableSingleColumnLayout(enable)
|
||||
storage.setEnableSingleColumnLayout(enable)
|
||||
}
|
||||
|
||||
return (
|
||||
<UserPreferencesContext.Provider
|
||||
value={{
|
||||
@@ -48,7 +59,9 @@ export function UserPreferencesProvider({ children }: { children: React.ReactNod
|
||||
muteMedia,
|
||||
updateMuteMedia: setMuteMedia,
|
||||
sidebarCollapse,
|
||||
updateSidebarCollapse: updateSidebarCollapse
|
||||
updateSidebarCollapse,
|
||||
enableSingleColumnLayout,
|
||||
updateEnableSingleColumnLayout
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
|
||||
@@ -51,6 +51,7 @@ class LocalStorageService {
|
||||
private shownCreateWalletGuideToastPubkeys: Set<string> = new Set()
|
||||
private sidebarCollapse: boolean = false
|
||||
private primaryColor: TPrimaryColor = 'DEFAULT'
|
||||
private enableSingleColumnLayout: boolean = false
|
||||
|
||||
constructor() {
|
||||
if (!LocalStorageService.instance) {
|
||||
@@ -201,6 +202,9 @@ class LocalStorageService {
|
||||
this.primaryColor =
|
||||
(window.localStorage.getItem(StorageKey.PRIMARY_COLOR) as TPrimaryColor) ?? 'DEFAULT'
|
||||
|
||||
this.enableSingleColumnLayout =
|
||||
window.localStorage.getItem(StorageKey.ENABLE_SINGLE_COLUMN_LAYOUT) === 'true'
|
||||
|
||||
// Clean up deprecated data
|
||||
window.localStorage.removeItem(StorageKey.ACCOUNT_PROFILE_EVENT_MAP)
|
||||
window.localStorage.removeItem(StorageKey.ACCOUNT_FOLLOW_LIST_EVENT_MAP)
|
||||
@@ -502,6 +506,15 @@ class LocalStorageService {
|
||||
this.primaryColor = color
|
||||
window.localStorage.setItem(StorageKey.PRIMARY_COLOR, color)
|
||||
}
|
||||
|
||||
getEnableSingleColumnLayout() {
|
||||
return this.enableSingleColumnLayout
|
||||
}
|
||||
|
||||
setEnableSingleColumnLayout(enable: boolean) {
|
||||
this.enableSingleColumnLayout = enable
|
||||
window.localStorage.setItem(StorageKey.ENABLE_SINGLE_COLUMN_LAYOUT, enable.toString())
|
||||
}
|
||||
}
|
||||
|
||||
const instance = new LocalStorageService()
|
||||
|
||||
Reference in New Issue
Block a user