refactor: sidebar
This commit is contained in:
@@ -9,6 +9,7 @@ import {
|
||||
} from '@/components/ui/dropdown-menu'
|
||||
import { toWallet } from '@/lib/link'
|
||||
import { formatPubkey, generateImageByPubkey } from '@/lib/pubkey'
|
||||
import { cn } from '@/lib/utils'
|
||||
import { usePrimaryPage, useSecondaryPage } from '@/PageManager'
|
||||
import { useNostr } from '@/providers/NostrProvider'
|
||||
import { ArrowDownUp, LogIn, LogOut, UserRound, Wallet } from 'lucide-react'
|
||||
@@ -18,17 +19,17 @@ import LoginDialog from '../LoginDialog'
|
||||
import LogoutDialog from '../LogoutDialog'
|
||||
import SidebarItem from './SidebarItem'
|
||||
|
||||
export default function AccountButton() {
|
||||
export default function AccountButton({ collapse }: { collapse: boolean }) {
|
||||
const { pubkey } = useNostr()
|
||||
|
||||
if (pubkey) {
|
||||
return <ProfileButton />
|
||||
return <ProfileButton collapse={collapse} />
|
||||
} else {
|
||||
return <LoginButton />
|
||||
return <LoginButton collapse={collapse} />
|
||||
}
|
||||
}
|
||||
|
||||
function ProfileButton() {
|
||||
function ProfileButton({ collapse }: { collapse: boolean }) {
|
||||
const { t } = useTranslation()
|
||||
const { account, profile } = useNostr()
|
||||
const pubkey = account?.pubkey
|
||||
@@ -46,7 +47,10 @@ function ProfileButton() {
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button
|
||||
variant="ghost"
|
||||
className="clickable shadow-none p-2 xl:px-2 xl:py-2 w-12 h-12 xl:w-full xl:h-auto flex items-center bg-transparent text-foreground hover:text-accent-foreground rounded-lg justify-start gap-4 text-lg font-semibold"
|
||||
className={cn(
|
||||
'clickable shadow-none p-2 flex items-center bg-transparent text-foreground hover:text-accent-foreground rounded-lg justify-start gap-4 text-lg font-semibold',
|
||||
collapse ? 'w-12 h-12' : 'w-full h-auto'
|
||||
)}
|
||||
>
|
||||
<div className="flex gap-2 items-center flex-1 w-0">
|
||||
<Avatar className="w-8 h-8">
|
||||
@@ -55,7 +59,7 @@ function ProfileButton() {
|
||||
<img src={defaultAvatar} />
|
||||
</AvatarFallback>
|
||||
</Avatar>
|
||||
<div className="truncate font-semibold text-lg">{username}</div>
|
||||
{!collapse && <div className="truncate font-semibold text-lg">{username}</div>}
|
||||
</div>
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
@@ -88,12 +92,12 @@ function ProfileButton() {
|
||||
)
|
||||
}
|
||||
|
||||
function LoginButton() {
|
||||
function LoginButton({ collapse }: { collapse: boolean }) {
|
||||
const { checkLogin } = useNostr()
|
||||
|
||||
return (
|
||||
<SidebarItem onClick={() => checkLogin()} title="Login">
|
||||
<LogIn strokeWidth={3} />
|
||||
<SidebarItem onClick={() => checkLogin()} title="Login" collapse={collapse}>
|
||||
<LogIn />
|
||||
</SidebarItem>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@ import { Compass } from 'lucide-react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import SidebarItem from './SidebarItem'
|
||||
|
||||
export default function RelaysButton() {
|
||||
export default function RelaysButton({ collapse }: { collapse: boolean }) {
|
||||
const { t } = useTranslation()
|
||||
const { navigate, current } = usePrimaryPage()
|
||||
|
||||
@@ -12,8 +12,9 @@ export default function RelaysButton() {
|
||||
title={t('Explore')}
|
||||
onClick={() => navigate('explore')}
|
||||
active={current === 'explore'}
|
||||
collapse={collapse}
|
||||
>
|
||||
<Compass strokeWidth={3} />
|
||||
<Compass />
|
||||
</SidebarItem>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -2,12 +2,17 @@ import { usePrimaryPage } from '@/PageManager'
|
||||
import { Home } from 'lucide-react'
|
||||
import SidebarItem from './SidebarItem'
|
||||
|
||||
export default function HomeButton() {
|
||||
export default function HomeButton({ collapse }: { collapse: boolean }) {
|
||||
const { navigate, current } = usePrimaryPage()
|
||||
|
||||
return (
|
||||
<SidebarItem title="Home" onClick={() => navigate('home')} active={current === 'home'}>
|
||||
<Home strokeWidth={3} />
|
||||
<SidebarItem
|
||||
title="Home"
|
||||
onClick={() => navigate('home')}
|
||||
active={current === 'home'}
|
||||
collapse={collapse}
|
||||
>
|
||||
<Home />
|
||||
</SidebarItem>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ import { useNotification } from '@/providers/NotificationProvider'
|
||||
import { Bell } from 'lucide-react'
|
||||
import SidebarItem from './SidebarItem'
|
||||
|
||||
export default function NotificationsButton() {
|
||||
export default function NotificationsButton({ collapse }: { collapse: boolean }) {
|
||||
const { checkLogin } = useNostr()
|
||||
const { navigate, current } = usePrimaryPage()
|
||||
const { hasNewNotification } = useNotification()
|
||||
@@ -14,9 +14,10 @@ export default function NotificationsButton() {
|
||||
title="Notifications"
|
||||
onClick={() => checkLogin(() => navigate('notifications'))}
|
||||
active={current === 'notifications'}
|
||||
collapse={collapse}
|
||||
>
|
||||
<div className="relative">
|
||||
<Bell strokeWidth={3} />
|
||||
<Bell />
|
||||
{hasNewNotification && (
|
||||
<div className="absolute -top-1 right-0 w-2 h-2 ring-2 ring-background bg-primary rounded-full" />
|
||||
)}
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
import PostEditor from '@/components/PostEditor'
|
||||
import { cn } from '@/lib/utils'
|
||||
import { useNostr } from '@/providers/NostrProvider'
|
||||
import { PencilLine } from 'lucide-react'
|
||||
import { useState } from 'react'
|
||||
import SidebarItem from './SidebarItem'
|
||||
|
||||
export default function PostButton() {
|
||||
export default function PostButton({ collapse }: { collapse: boolean }) {
|
||||
const { checkLogin } = useNostr()
|
||||
const [open, setOpen] = useState(false)
|
||||
|
||||
@@ -20,9 +21,10 @@ export default function PostButton() {
|
||||
})
|
||||
}}
|
||||
variant="default"
|
||||
className="bg-primary xl:justify-center gap-2"
|
||||
className={cn('bg-primary gap-2', !collapse && 'justify-center')}
|
||||
collapse={collapse}
|
||||
>
|
||||
<PencilLine strokeWidth={3} />
|
||||
<PencilLine />
|
||||
</SidebarItem>
|
||||
<PostEditor open={open} setOpen={setOpen} />
|
||||
</div>
|
||||
|
||||
@@ -3,7 +3,7 @@ import { useNostr } from '@/providers/NostrProvider'
|
||||
import { UserRound } from 'lucide-react'
|
||||
import SidebarItem from './SidebarItem'
|
||||
|
||||
export default function ProfileButton() {
|
||||
export default function ProfileButton({ collapse }: { collapse: boolean }) {
|
||||
const { navigate, current } = usePrimaryPage()
|
||||
const { checkLogin } = useNostr()
|
||||
|
||||
@@ -12,8 +12,9 @@ export default function ProfileButton() {
|
||||
title="Profile"
|
||||
onClick={() => checkLogin(() => navigate('profile'))}
|
||||
active={current === 'profile'}
|
||||
collapse={collapse}
|
||||
>
|
||||
<UserRound strokeWidth={3} />
|
||||
<UserRound />
|
||||
</SidebarItem>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@ import { usePrimaryPage } from '@/PageManager'
|
||||
import { Search } from 'lucide-react'
|
||||
import SidebarItem from './SidebarItem'
|
||||
|
||||
export default function SearchButton() {
|
||||
export default function SearchButton({ collapse }: { collapse: boolean }) {
|
||||
const { navigate, current, display } = usePrimaryPage()
|
||||
|
||||
return (
|
||||
@@ -10,8 +10,9 @@ export default function SearchButton() {
|
||||
title="Search"
|
||||
onClick={() => navigate('search')}
|
||||
active={current === 'search' && display}
|
||||
collapse={collapse}
|
||||
>
|
||||
<Search strokeWidth={3} />
|
||||
<Search />
|
||||
</SidebarItem>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -3,12 +3,12 @@ import { useSecondaryPage } from '@/PageManager'
|
||||
import { Settings } from 'lucide-react'
|
||||
import SidebarItem from './SidebarItem'
|
||||
|
||||
export default function SettingsButton() {
|
||||
export default function SettingsButton({ collapse }: { collapse: boolean }) {
|
||||
const { push } = useSecondaryPage()
|
||||
|
||||
return (
|
||||
<SidebarItem title="Settings" onClick={() => push(toSettings())}>
|
||||
<Settings strokeWidth={3} />
|
||||
<SidebarItem title="Settings" onClick={() => push(toSettings())} collapse={collapse}>
|
||||
<Settings />
|
||||
</SidebarItem>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -5,14 +5,17 @@ import { useTranslation } from 'react-i18next'
|
||||
|
||||
const SidebarItem = forwardRef<
|
||||
HTMLButtonElement,
|
||||
ButtonProps & { title: string; description?: string; active?: boolean }
|
||||
>(({ children, title, description, className, active, ...props }, ref) => {
|
||||
ButtonProps & { title: string; collapse: boolean; description?: string; active?: boolean }
|
||||
>(({ children, title, description, className, active, collapse, ...props }, ref) => {
|
||||
const { t } = useTranslation()
|
||||
|
||||
return (
|
||||
<Button
|
||||
className={cn(
|
||||
'flex shadow-none items-center transition-colors duration-500 bg-transparent w-12 h-12 xl:w-full xl:h-auto p-3 m-0 xl:py-2 xl:px-3 rounded-lg xl:justify-start gap-4 text-lg font-semibold [&_svg]:size-full xl:[&_svg]:size-4',
|
||||
'flex shadow-none items-center transition-colors duration-500 bg-transparent m-0 rounded-lg gap-4 text-lg font-semibold',
|
||||
collapse
|
||||
? 'w-12 h-12 p-3 [&_svg]:size-full'
|
||||
: 'justify-start w-full h-auto py-2 px-3 [&_svg]:size-5',
|
||||
active && 'text-primary hover:text-primary bg-primary/10 hover:bg-primary/10',
|
||||
className
|
||||
)}
|
||||
@@ -22,7 +25,7 @@ const SidebarItem = forwardRef<
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
<div className="max-xl:hidden">{t(description ?? title)}</div>
|
||||
{!collapse && <div>{t(description ?? title)}</div>}
|
||||
</Button>
|
||||
)
|
||||
})
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
import Icon from '@/assets/Icon'
|
||||
import Logo from '@/assets/Logo'
|
||||
import { cn } from '@/lib/utils'
|
||||
import { useScreenSize } from '@/providers/ScreenSizeProvider'
|
||||
import { useUserPreferences } from '@/providers/UserPreferencesProvider'
|
||||
import { ChevronsLeft, ChevronsRight } from 'lucide-react'
|
||||
import AccountButton from './AccountButton'
|
||||
import RelaysButton from './ExploreButton'
|
||||
import HomeButton from './HomeButton'
|
||||
@@ -12,24 +15,45 @@ import SettingsButton from './SettingsButton'
|
||||
|
||||
export default function PrimaryPageSidebar() {
|
||||
const { isSmallScreen } = useScreenSize()
|
||||
const { sidebarCollapse, updateSidebarCollapse } = useUserPreferences()
|
||||
|
||||
if (isSmallScreen) return null
|
||||
|
||||
return (
|
||||
<div className="w-16 xl:w-52 flex flex-col pb-2 pt-4 px-2 xl:px-4 justify-between h-full shrink-0">
|
||||
<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'
|
||||
)}
|
||||
>
|
||||
<div className="space-y-2">
|
||||
<div className="px-3 xl:px-4 mb-6 w-full">
|
||||
<Icon className="xl:hidden" />
|
||||
<Logo className="max-xl:hidden" />
|
||||
</div>
|
||||
<HomeButton />
|
||||
<RelaysButton />
|
||||
<NotificationsButton />
|
||||
<SearchButton />
|
||||
<ProfileButton />
|
||||
<SettingsButton />
|
||||
<PostButton />
|
||||
{sidebarCollapse ? (
|
||||
<div className="px-3 py-1 mb-6 w-full">
|
||||
<Icon />
|
||||
</div>
|
||||
) : (
|
||||
<div className="px-4 mb-6 w-full">
|
||||
<Logo />
|
||||
</div>
|
||||
)}
|
||||
<HomeButton collapse={sidebarCollapse} />
|
||||
<RelaysButton collapse={sidebarCollapse} />
|
||||
<NotificationsButton collapse={sidebarCollapse} />
|
||||
<SearchButton collapse={sidebarCollapse} />
|
||||
<ProfileButton collapse={sidebarCollapse} />
|
||||
<SettingsButton collapse={sidebarCollapse} />
|
||||
<PostButton collapse={sidebarCollapse} />
|
||||
</div>
|
||||
<AccountButton />
|
||||
<AccountButton collapse={sidebarCollapse} />
|
||||
<button
|
||||
className="absolute flex flex-col justify-center items-center top-5 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"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation()
|
||||
updateSidebarCollapse(!sidebarCollapse)
|
||||
}}
|
||||
>
|
||||
{sidebarCollapse ? <ChevronsRight /> : <ChevronsLeft />}
|
||||
</button>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -48,6 +48,7 @@ export const StorageKey = {
|
||||
NOTIFICATION_LIST_STYLE: 'notificationListStyle',
|
||||
MEDIA_AUTO_LOAD_POLICY: 'mediaAutoLoadPolicy',
|
||||
SHOWN_CREATE_WALLET_GUIDE_TOAST_PUBKEYS: 'shownCreateWalletGuideToastPubkeys',
|
||||
SIDEBAR_COLLAPSE: 'sidebarCollapse',
|
||||
MEDIA_UPLOAD_SERVICE: 'mediaUploadService', // deprecated
|
||||
HIDE_UNTRUSTED_EVENTS: 'hideUntrustedEvents', // deprecated
|
||||
ACCOUNT_RELAY_LIST_EVENT_MAP: 'accountRelayListEventMap', // deprecated
|
||||
|
||||
@@ -8,6 +8,9 @@ type TUserPreferencesContext = {
|
||||
|
||||
muteMedia: boolean
|
||||
updateMuteMedia: (mute: boolean) => void
|
||||
|
||||
sidebarCollapse: boolean
|
||||
updateSidebarCollapse: (collapse: boolean) => void
|
||||
}
|
||||
|
||||
const UserPreferencesContext = createContext<TUserPreferencesContext | undefined>(undefined)
|
||||
@@ -25,19 +28,27 @@ export function UserPreferencesProvider({ children }: { children: React.ReactNod
|
||||
storage.getNotificationListStyle()
|
||||
)
|
||||
const [muteMedia, setMuteMedia] = useState(true)
|
||||
const [sidebarCollapse, setSidebarCollapse] = useState(storage.getSidebarCollapse())
|
||||
|
||||
const updateNotificationListStyle = (style: TNotificationStyle) => {
|
||||
setNotificationListStyle(style)
|
||||
storage.setNotificationListStyle(style)
|
||||
}
|
||||
|
||||
const updateSidebarCollapse = (collapse: boolean) => {
|
||||
setSidebarCollapse(collapse)
|
||||
storage.setSidebarCollapse(collapse)
|
||||
}
|
||||
|
||||
return (
|
||||
<UserPreferencesContext.Provider
|
||||
value={{
|
||||
notificationListStyle,
|
||||
updateNotificationListStyle,
|
||||
muteMedia,
|
||||
updateMuteMedia: setMuteMedia
|
||||
updateMuteMedia: setMuteMedia,
|
||||
sidebarCollapse,
|
||||
updateSidebarCollapse: updateSidebarCollapse
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
|
||||
@@ -48,6 +48,7 @@ class LocalStorageService {
|
||||
private notificationListStyle: TNotificationStyle = NOTIFICATION_LIST_STYLE.DETAILED
|
||||
private mediaAutoLoadPolicy: TMediaAutoLoadPolicy = MEDIA_AUTO_LOAD_POLICY.ALWAYS
|
||||
private shownCreateWalletGuideToastPubkeys: Set<string> = new Set()
|
||||
private sidebarCollapse: boolean = false
|
||||
|
||||
constructor() {
|
||||
if (!LocalStorageService.instance) {
|
||||
@@ -193,6 +194,8 @@ class LocalStorageService {
|
||||
? new Set(JSON.parse(shownCreateWalletGuideToastPubkeysStr))
|
||||
: new Set()
|
||||
|
||||
this.sidebarCollapse = window.localStorage.getItem(StorageKey.SIDEBAR_COLLAPSE) === 'true'
|
||||
|
||||
// Clean up deprecated data
|
||||
window.localStorage.removeItem(StorageKey.ACCOUNT_PROFILE_EVENT_MAP)
|
||||
window.localStorage.removeItem(StorageKey.ACCOUNT_FOLLOW_LIST_EVENT_MAP)
|
||||
@@ -476,6 +479,15 @@ class LocalStorageService {
|
||||
JSON.stringify(Array.from(this.shownCreateWalletGuideToastPubkeys))
|
||||
)
|
||||
}
|
||||
|
||||
getSidebarCollapse() {
|
||||
return this.sidebarCollapse
|
||||
}
|
||||
|
||||
setSidebarCollapse(collapse: boolean) {
|
||||
this.sidebarCollapse = collapse
|
||||
window.localStorage.setItem(StorageKey.SIDEBAR_COLLAPSE, collapse.toString())
|
||||
}
|
||||
}
|
||||
|
||||
const instance = new LocalStorageService()
|
||||
|
||||
Reference in New Issue
Block a user