feat: improve mobile experience
This commit is contained in:
parent
8ec0d46d58
commit
3946e603b3
98 changed files with 2508 additions and 1058 deletions
89
src/components/Sidebar/AccountButton.tsx
Normal file
89
src/components/Sidebar/AccountButton.tsx
Normal file
|
|
@ -0,0 +1,89 @@
|
|||
import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuTrigger
|
||||
} from '@/components/ui/dropdown-menu'
|
||||
import { useFetchProfile } from '@/hooks'
|
||||
import { toProfile, toSettings } from '@/lib/link'
|
||||
import { formatPubkey, generateImageByPubkey } from '@/lib/pubkey'
|
||||
import { useSecondaryPage } from '@/PageManager'
|
||||
import { useNostr } from '@/providers/NostrProvider'
|
||||
import { LogIn } from 'lucide-react'
|
||||
import { useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import LoginDialog from '../LoginDialog'
|
||||
import LogoutDialog from '../LogoutDialog'
|
||||
import SidebarItem from './SidebarItem'
|
||||
|
||||
export default function AccountButton() {
|
||||
const { pubkey } = useNostr()
|
||||
|
||||
if (pubkey) {
|
||||
return <ProfileButton />
|
||||
} else {
|
||||
return <LoginButton />
|
||||
}
|
||||
}
|
||||
|
||||
function ProfileButton() {
|
||||
const { t } = useTranslation()
|
||||
const { account } = useNostr()
|
||||
const pubkey = account?.pubkey
|
||||
const { profile } = useFetchProfile(pubkey)
|
||||
const { push } = useSecondaryPage()
|
||||
const [loginDialogOpen, setLoginDialogOpen] = useState(false)
|
||||
const [logoutDialogOpen, setLogoutDialogOpen] = useState(false)
|
||||
if (!pubkey) return null
|
||||
|
||||
const defaultAvatar = generateImageByPubkey(pubkey)
|
||||
const { username, avatar } = profile || { username: formatPubkey(pubkey), avatar: defaultAvatar }
|
||||
|
||||
return (
|
||||
<DropdownMenu>
|
||||
<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"
|
||||
>
|
||||
<div className="flex gap-2 items-center flex-1 w-0">
|
||||
<Avatar className="w-8 h-8">
|
||||
<AvatarImage src={avatar} />
|
||||
<AvatarFallback>
|
||||
<img src={defaultAvatar} />
|
||||
</AvatarFallback>
|
||||
</Avatar>
|
||||
<div className="truncate font-semibold text-lg">{username}</div>
|
||||
</div>
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent>
|
||||
<DropdownMenuItem onClick={() => push(toProfile(pubkey))}>{t('Profile')}</DropdownMenuItem>
|
||||
<DropdownMenuItem onClick={() => push(toSettings())}>{t('Settings')}</DropdownMenuItem>
|
||||
<DropdownMenuItem onClick={() => setLoginDialogOpen(true)}>
|
||||
{t('Switch account')}
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem
|
||||
className="text-destructive focus:text-destructive"
|
||||
onClick={() => setLogoutDialogOpen(true)}
|
||||
>
|
||||
{t('Logout')}
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
<LoginDialog open={loginDialogOpen} setOpen={setLoginDialogOpen} />
|
||||
<LogoutDialog open={logoutDialogOpen} setOpen={setLogoutDialogOpen} />
|
||||
</DropdownMenu>
|
||||
)
|
||||
}
|
||||
|
||||
function LoginButton() {
|
||||
const { checkLogin } = useNostr()
|
||||
|
||||
return (
|
||||
<SidebarItem onClick={() => checkLogin()} title="Login">
|
||||
<LogIn strokeWidth={3} />
|
||||
</SidebarItem>
|
||||
)
|
||||
}
|
||||
13
src/components/Sidebar/HomeButton.tsx
Normal file
13
src/components/Sidebar/HomeButton.tsx
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
import { usePrimaryPage } from '@/PageManager'
|
||||
import { Home } from 'lucide-react'
|
||||
import SidebarItem from './SidebarItem'
|
||||
|
||||
export default function HomeButton() {
|
||||
const { navigate, current } = usePrimaryPage()
|
||||
|
||||
return (
|
||||
<SidebarItem title="Home" onClick={() => navigate('home')} active={current === 'home'}>
|
||||
<Home strokeWidth={3} />
|
||||
</SidebarItem>
|
||||
)
|
||||
}
|
||||
17
src/components/Sidebar/NotificationButton.tsx
Normal file
17
src/components/Sidebar/NotificationButton.tsx
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
import { usePrimaryPage } from '@/PageManager'
|
||||
import { Bell } from 'lucide-react'
|
||||
import SidebarItem from './SidebarItem'
|
||||
|
||||
export default function NotificationsButton() {
|
||||
const { navigate, current } = usePrimaryPage()
|
||||
|
||||
return (
|
||||
<SidebarItem
|
||||
title="Notifications"
|
||||
onClick={() => navigate('notifications')}
|
||||
active={current === 'notifications'}
|
||||
>
|
||||
<Bell strokeWidth={3} />
|
||||
</SidebarItem>
|
||||
)
|
||||
}
|
||||
24
src/components/Sidebar/PostButton.tsx
Normal file
24
src/components/Sidebar/PostButton.tsx
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
import PostEditor from '@/components/PostEditor'
|
||||
import { PencilLine } from 'lucide-react'
|
||||
import { useState } from 'react'
|
||||
import SidebarItem from './SidebarItem'
|
||||
|
||||
export default function PostButton() {
|
||||
const [open, setOpen] = useState(false)
|
||||
|
||||
return (
|
||||
<>
|
||||
<SidebarItem
|
||||
title="New post"
|
||||
description="Post"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation()
|
||||
setOpen(true)
|
||||
}}
|
||||
>
|
||||
<PencilLine strokeWidth={3} />
|
||||
</SidebarItem>
|
||||
<PostEditor open={open} setOpen={setOpen} />
|
||||
</>
|
||||
)
|
||||
}
|
||||
24
src/components/Sidebar/SearchButton.tsx
Normal file
24
src/components/Sidebar/SearchButton.tsx
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
import { Search } from 'lucide-react'
|
||||
import { useState } from 'react'
|
||||
import { SearchDialog } from '../SearchDialog'
|
||||
import SidebarItem from './SidebarItem'
|
||||
|
||||
export default function SearchButton() {
|
||||
const [open, setOpen] = useState(false)
|
||||
|
||||
return (
|
||||
<>
|
||||
<SidebarItem
|
||||
title="Search"
|
||||
description="Search"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation()
|
||||
setOpen(true)
|
||||
}}
|
||||
>
|
||||
<Search strokeWidth={3} />
|
||||
</SidebarItem>
|
||||
<SearchDialog open={open} setOpen={setOpen} />
|
||||
</>
|
||||
)
|
||||
}
|
||||
31
src/components/Sidebar/SidebarItem.tsx
Normal file
31
src/components/Sidebar/SidebarItem.tsx
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
import { Button, ButtonProps } from '@/components/ui/button'
|
||||
import { cn } from '@/lib/utils'
|
||||
import { forwardRef } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
|
||||
const SidebarItem = forwardRef<
|
||||
HTMLButtonElement,
|
||||
ButtonProps & { title: string; description?: string; active?: boolean }
|
||||
>(({ children, title, description, className, active, ...props }, ref) => {
|
||||
const { t } = useTranslation()
|
||||
|
||||
return (
|
||||
<Button
|
||||
className={cn(
|
||||
'flex shadow-none items-center bg-transparent w-12 h-12 xl:w-full xl:h-auto p-3 m-0 xl:py-2 xl:px-4 rounded-lg xl:justify-start gap-4 text-lg font-semibold [&_svg]:size-full xl:[&_svg]:size-4',
|
||||
active && 'text-primary disabled:opacity-100',
|
||||
className
|
||||
)}
|
||||
disabled={active}
|
||||
variant="ghost"
|
||||
title={t(title)}
|
||||
ref={ref}
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
<div className="max-xl:hidden">{t(description ?? title)}</div>
|
||||
</Button>
|
||||
)
|
||||
})
|
||||
SidebarItem.displayName = 'SidebarItem'
|
||||
export default SidebarItem
|
||||
|
|
@ -1,35 +1,25 @@
|
|||
import Icon from '@/assets/Icon'
|
||||
import Logo from '@/assets/Logo'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { Info } from 'lucide-react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import AboutInfoDialog from '../AboutInfoDialog'
|
||||
import AccountButton from '../AccountButton'
|
||||
import NotificationButton from '../NotificationButton'
|
||||
import PostButton from '../PostButton'
|
||||
import RelaySettingsButton from '../RelaySettingsButton'
|
||||
import SearchButton from '../SearchButton'
|
||||
import AccountButton from './AccountButton'
|
||||
import HomeButton from './HomeButton'
|
||||
import NotificationsButton from './NotificationButton'
|
||||
import PostButton from './PostButton'
|
||||
import SearchButton from './SearchButton'
|
||||
|
||||
export default function PrimaryPageSidebar() {
|
||||
const { t } = useTranslation()
|
||||
return (
|
||||
<div className="w-52 h-full shrink-0 hidden xl:flex flex-col pb-8 pt-10 pl-4 justify-between relative">
|
||||
<div className="absolute top-0 left-0 h-11 w-full" />
|
||||
<div className="w-16 xl:w-52 hidden sm:flex flex-col pb-2 pt-4 px-2 justify-between h-full shrink-0">
|
||||
<div className="space-y-2">
|
||||
<div className="ml-4 mb-8 w-40">
|
||||
<Logo />
|
||||
<div className="px-2 mb-10 w-full">
|
||||
<Icon className="xl:hidden" />
|
||||
<Logo className="max-xl:hidden" />
|
||||
</div>
|
||||
<PostButton variant="sidebar" />
|
||||
<RelaySettingsButton variant="sidebar" />
|
||||
<NotificationButton variant="sidebar" />
|
||||
<SearchButton variant="sidebar" />
|
||||
<AboutInfoDialog>
|
||||
<Button variant="sidebar" size="sidebar">
|
||||
<Info />
|
||||
{t('About')}
|
||||
</Button>
|
||||
</AboutInfoDialog>
|
||||
<HomeButton />
|
||||
<NotificationsButton />
|
||||
<SearchButton />
|
||||
<PostButton />
|
||||
</div>
|
||||
<AccountButton variant="sidebar" />
|
||||
<AccountButton />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue