feat: update layout

This commit is contained in:
codytseng
2025-08-27 21:56:46 +08:00
parent f41536a793
commit 8b1c2ebe3f
30 changed files with 230 additions and 250 deletions

View File

@@ -18,7 +18,7 @@
<link rel="apple-touch-icon" href="/apple-touch-icon.png" /> <link rel="apple-touch-icon" href="/apple-touch-icon.png" />
<link rel="icon" href="/favicon.ico" sizes="48x48" /> <link rel="icon" href="/favicon.ico" sizes="48x48" />
<link rel="icon" href="/favicon.svg" sizes="any" type="image/svg+xml" /> <link rel="icon" href="/favicon.svg" sizes="any" type="image/svg+xml" />
<meta name="theme-color" content="#09090b" media="(prefers-color-scheme: dark)" /> <meta name="theme-color" content="#171717" media="(prefers-color-scheme: dark)" />
<meta name="theme-color" content="#FFFFFF" media="(prefers-color-scheme: light)" /> <meta name="theme-color" content="#FFFFFF" media="(prefers-color-scheme: light)" />
<meta property="og:url" content="https://jumble.social" /> <meta property="og:url" content="https://jumble.social" />

View File

@@ -1,5 +1,4 @@
import Sidebar from '@/components/Sidebar' import Sidebar from '@/components/Sidebar'
import { Separator } from '@/components/ui/separator'
import { cn } from '@/lib/utils' import { cn } from '@/lib/utils'
import NoteListPage from '@/pages/primary/NoteListPage' import NoteListPage from '@/pages/primary/NoteListPage'
import HomePage from '@/pages/secondary/HomePage' import HomePage from '@/pages/secondary/HomePage'
@@ -15,6 +14,7 @@ import {
useRef, useRef,
useState useState
} from 'react' } from 'react'
import BottomNavigationBar from './components/BottomNavigationBar'
import TooManyRelaysAlertDialog from './components/TooManyRelaysAlertDialog' import TooManyRelaysAlertDialog from './components/TooManyRelaysAlertDialog'
import ExplorePage from './pages/primary/ExplorePage' import ExplorePage from './pages/primary/ExplorePage'
import MePage from './pages/primary/MePage' import MePage from './pages/primary/MePage'
@@ -90,12 +90,10 @@ export function PageManager({ maxStackSize = 5 }: { maxStackSize?: number }) {
} }
]) ])
const [secondaryStack, setSecondaryStack] = useState<TStackItem[]>([]) const [secondaryStack, setSecondaryStack] = useState<TStackItem[]>([])
const [isShared, setIsShared] = useState(false)
const { isSmallScreen } = useScreenSize() const { isSmallScreen } = useScreenSize()
const ignorePopStateRef = useRef(false) const ignorePopStateRef = useRef(false)
useEffect(() => { useEffect(() => {
const hasHistoryState = !!history.state
if (['/npub1', '/nprofile1'].some((prefix) => window.location.pathname.startsWith(prefix))) { if (['/npub1', '/nprofile1'].some((prefix) => window.location.pathname.startsWith(prefix))) {
window.history.replaceState( window.history.replaceState(
null, null,
@@ -115,12 +113,6 @@ export function PageManager({ maxStackSize = 5 }: { maxStackSize?: number }) {
} }
window.history.pushState(null, '', window.location.href) window.history.pushState(null, '', window.location.href)
if (window.location.pathname !== '/') { if (window.location.pathname !== '/') {
if (
['/users', '/notes', '/relays'].some((path) => window.location.pathname.startsWith(path)) &&
!hasHistoryState
) {
setIsShared(true)
}
const url = window.location.pathname + window.location.search + window.location.hash const url = window.location.pathname + window.location.search + window.location.hash
setSecondaryStack((prevStack) => { setSecondaryStack((prevStack) => {
if (isCurrentPage(prevStack, url)) return prevStack if (isCurrentPage(prevStack, url)) return prevStack
@@ -248,7 +240,6 @@ export function PageManager({ maxStackSize = 5 }: { maxStackSize?: number }) {
if (secondaryStack.length === 1) { if (secondaryStack.length === 1) {
// back to home page // back to home page
window.history.replaceState(null, '', '/') window.history.replaceState(null, '', '/')
setIsShared(false)
setSecondaryStack([]) setSecondaryStack([])
} else { } else {
window.history.go(-1) window.history.go(-1)
@@ -301,6 +292,7 @@ export function PageManager({ maxStackSize = 5 }: { maxStackSize?: number }) {
{element} {element}
</div> </div>
))} ))}
<BottomNavigationBar />
<TooManyRelaysAlertDialog /> <TooManyRelaysAlertDialog />
</NotificationProvider> </NotificationProvider>
</SecondaryPageContext.Provider> </SecondaryPageContext.Provider>
@@ -308,39 +300,6 @@ export function PageManager({ maxStackSize = 5 }: { maxStackSize?: number }) {
) )
} }
if (isShared && secondaryStack.length > 0) {
return (
<PrimaryPageContext.Provider
value={{
navigate: navigatePrimaryPage,
current: currentPrimaryPage,
display: false
}}
>
<SecondaryPageContext.Provider
value={{
push: pushSecondaryPage,
pop: popSecondaryPage,
currentIndex: secondaryStack[secondaryStack.length - 1].index
}}
>
<NotificationProvider>
<div className="h-screen overflow-hidden max-w-4xl mx-auto border-x">
{secondaryStack.map((item, index) => (
<div
key={item.index}
style={{ display: index === secondaryStack.length - 1 ? 'block' : 'none' }}
>
{item.component}
</div>
))}
</div>
</NotificationProvider>
</SecondaryPageContext.Provider>
</PrimaryPageContext.Provider>
)
}
return ( return (
<PrimaryPageContext.Provider <PrimaryPageContext.Provider
value={{ value={{
@@ -357,11 +316,10 @@ export function PageManager({ maxStackSize = 5 }: { maxStackSize?: number }) {
}} }}
> >
<NotificationProvider> <NotificationProvider>
<div className="flex h-screen overflow-hidden"> <div className="flex h-screen overflow-hidden bg-surface-background">
<Sidebar /> <Sidebar />
<Separator orientation="vertical" /> <div className="grid grid-cols-2 gap-2 w-full pr-2">
<div className="grid grid-cols-2 w-full"> <div className="flex rounded-lg my-2 max-h-screen shadow-md bg-background overflow-hidden">
<div className="flex border-r">
{primaryPages.map(({ name, element }) => ( {primaryPages.map(({ name, element }) => (
<div <div
key={name} key={name}
@@ -374,16 +332,21 @@ export function PageManager({ maxStackSize = 5 }: { maxStackSize?: number }) {
</div> </div>
))} ))}
</div> </div>
<div> <div className="flex rounded-lg my-2 max-h-screen shadow-md bg-background overflow-hidden">
{secondaryStack.map((item, index) => ( {secondaryStack.map((item, index) => (
<div <div
key={item.index} key={item.index}
className="w-full"
style={{ display: index === secondaryStack.length - 1 ? 'block' : 'none' }} style={{ display: index === secondaryStack.length - 1 ? 'block' : 'none' }}
> >
{item.component} {item.component}
</div> </div>
))} ))}
<div key="home" style={{ display: secondaryStack.length === 0 ? 'block' : 'none' }}> <div
key="home"
className="w-full"
style={{ display: secondaryStack.length === 0 ? 'block' : 'none' }}
>
<HomePage /> <HomePage />
</div> </div>
</div> </div>

View File

@@ -8,7 +8,7 @@ export default function BottomNavigationBar() {
return ( return (
<div <div
className={cn( className={cn(
'fixed bottom-0 w-full z-40 bg-background/80 backdrop-blur-xl flex items-center justify-around [&_svg]:size-4 [&_svg]:shrink-0' 'fixed bottom-0 w-full z-40 bg-background border-t flex items-center justify-around [&_svg]:size-4 [&_svg]:shrink-0'
)} )}
style={{ style={{
height: 'calc(3rem + env(safe-area-inset-bottom))', height: 'calc(3rem + env(safe-area-inset-bottom))',

View File

@@ -88,7 +88,7 @@ export default function KindFilter({
key={label} key={label}
className={cn( className={cn(
'cursor-pointer grid gap-1.5 rounded-lg border px-4 py-3', 'cursor-pointer grid gap-1.5 rounded-lg border px-4 py-3',
checked ? 'border-primary bg-primary/20' : 'clickable' checked ? 'border-primary/60 bg-primary/5' : 'clickable'
)} )}
onClick={() => { onClick={() => {
console.log(checked) console.log(checked)
@@ -166,7 +166,7 @@ export default function KindFilter({
return ( return (
<Popover open={open} onOpenChange={setOpen}> <Popover open={open} onOpenChange={setOpen}>
<PopoverTrigger asChild>{trigger}</PopoverTrigger> <PopoverTrigger asChild>{trigger}</PopoverTrigger>
<PopoverContent className="w-96" collisionPadding={16}> <PopoverContent className="w-96" collisionPadding={16} sideOffset={0}>
{content} {content}
</PopoverContent> </PopoverContent>
</Popover> </Popover>

View File

@@ -2,6 +2,7 @@ import { Button } from '@/components/ui/button'
import { SimpleUserAvatar } from '@/components/UserAvatar' import { SimpleUserAvatar } from '@/components/UserAvatar'
import { cn } from '@/lib/utils' import { cn } from '@/lib/utils'
import { useScreenSize } from '@/providers/ScreenSizeProvider' import { useScreenSize } from '@/providers/ScreenSizeProvider'
import { ArrowUp } from 'lucide-react'
import { Event } from 'nostr-tools' import { Event } from 'nostr-tools'
import { useMemo } from 'react' import { useMemo } from 'react'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
@@ -32,34 +33,25 @@ export default function NewNotesButton({
<div <div
className={cn( className={cn(
'w-full flex justify-center z-40 pointer-events-none', 'w-full flex justify-center z-40 pointer-events-none',
isSmallScreen ? 'fixed' : 'absolute bottom-4' isSmallScreen ? 'fixed' : 'absolute bottom-6'
)} )}
style={isSmallScreen ? { bottom: 'calc(4rem + env(safe-area-inset-bottom))' } : undefined} style={isSmallScreen ? { bottom: 'calc(4rem + env(safe-area-inset-bottom))' } : undefined}
> >
<Button <Button
onClick={onClick} onClick={onClick}
className="group rounded-full h-fit pl-2 pr-3 hover:bg-primary-hover pointer-events-auto" className="group rounded-full h-fit py-2 pl-2 pr-3 hover:bg-primary-hover pointer-events-auto"
> >
{pubkeys.length > 0 && ( {pubkeys.length > 0 && (
<div className="flex items-center"> <div className="*:data-[slot=avatar]:ring-background flex -space-x-2 *:data-[slot=avatar]:ring-2 *:data-[slot=avatar]:grayscale">
{pubkeys.map((pubkey, index) => ( {pubkeys.map((pubkey) => (
<div <SimpleUserAvatar userId={pubkey} size="small" />
key={pubkey}
className="relative -mr-2.5 last:mr-0"
style={{ zIndex: 3 - index }}
>
<SimpleUserAvatar
userId={pubkey}
size="small"
className="border-primary border-2 group-hover:border-primary-hover"
/>
</div>
))} ))}
</div> </div>
)} )}
<div className="text-md font-medium"> <div className="text-md font-medium">
{t('Show n new notes', { n: newEvents.length > 99 ? '99+' : newEvents.length })} {t('Show n new notes', { n: newEvents.length > 99 ? '99+' : newEvents.length })}
</div> </div>
<ArrowUp />
</Button> </Button>
</div> </div>
)} )}

View File

@@ -209,7 +209,7 @@ const NoteList = forwardRef(
setEvents((oldEvents) => [...newEvents, ...oldEvents]) setEvents((oldEvents) => [...newEvents, ...oldEvents])
setNewEvents([]) setNewEvents([])
setTimeout(() => { setTimeout(() => {
scrollToTop() scrollToTop('smooth')
}, 0) }, 0)
} }
@@ -218,7 +218,7 @@ const NoteList = forwardRef(
{filteredNewEvents.length > 0 && ( {filteredNewEvents.length > 0 && (
<NewNotesButton newEvents={filteredNewEvents} onClick={showNewEvents} /> <NewNotesButton newEvents={filteredNewEvents} onClick={showNewEvents} />
)} )}
<div ref={topRef} className="scroll-mt-24" /> <div ref={topRef} className="scroll-mt-[calc(6rem+1px)]" />
<PullToRefresh <PullToRefresh
onRefresh={async () => { onRefresh={async () => {
setRefreshCount((count) => count + 1) setRefreshCount((count) => count + 1)

View File

@@ -35,7 +35,7 @@ export default function ProfileList({ pubkeys }: { pubkeys: string[] }) {
}, [visiblePubkeys, pubkeys]) }, [visiblePubkeys, pubkeys])
return ( return (
<div className="px-4"> <div className="px-4 pt-2">
{visiblePubkeys.map((pubkey, index) => ( {visiblePubkeys.map((pubkey, index) => (
<UserItem key={`${index}-${pubkey}`} pubkey={pubkey} /> <UserItem key={`${index}-${pubkey}`} pubkey={pubkey} />
))} ))}

View File

@@ -12,8 +12,8 @@ const SidebarItem = forwardRef<
return ( return (
<Button <Button
className={cn( 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', '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',
active && 'text-primary hover:text-primary', active && 'text-primary hover:text-primary bg-primary/10 hover:bg-primary/10',
className className
)} )}
variant="ghost" variant="ghost"

View File

@@ -14,7 +14,7 @@ export default function PrimaryPageSidebar() {
if (isSmallScreen) return null if (isSmallScreen) return null
return ( return (
<div className="w-16 xl:w-52 flex flex-col pb-2 pt-4 px-2 justify-between h-full shrink-0"> <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="space-y-2"> <div className="space-y-2">
<div className="px-3 xl:px-4 mb-6 w-full"> <div className="px-3 xl:px-4 mb-6 w-full">
<Icon className="xl:hidden" /> <Icon className="xl:hidden" />

View File

@@ -87,7 +87,7 @@ export default function Tabs({
<div <div
ref={containerRef} ref={containerRef}
className={cn( className={cn(
'sticky flex justify-between top-12 bg-background z-30 px-1 w-full transition-transform', 'sticky flex justify-between top-12 bg-background z-30 px-1 w-full transition-transform border-b',
deepBrowsing && lastScrollTop > threshold ? '-translate-y-[calc(100%+12rem)]' : '' deepBrowsing && lastScrollTop > threshold ? '-translate-y-[calc(100%+12rem)]' : ''
)} )}
> >

View File

@@ -2,15 +2,18 @@ import { cn } from '@/lib/utils'
export function Titlebar({ export function Titlebar({
children, children,
className className,
hideBottomBorder = false
}: { }: {
children?: React.ReactNode children?: React.ReactNode
className?: string className?: string
hideBottomBorder?: boolean
}) { }) {
return ( return (
<div <div
className={cn( className={cn(
'sticky top-0 w-full h-12 z-40 bg-background [&_svg]:size-5 [&_svg]:shrink-0 select-none', 'sticky top-0 w-full h-12 z-40 bg-background [&_svg]:size-5 [&_svg]:shrink-0 select-none',
!hideBottomBorder && 'border-b',
className className
)} )}
> >

View File

@@ -50,6 +50,14 @@
pointer-events: none; pointer-events: none;
} }
.scrollbar-hide {
-ms-overflow-style: none; /* Internet Explorer 10+ */
scrollbar-width: none; /* Firefox */
}
.scrollbar-hide::-webkit-scrollbar {
display: none; /* Safari and Chrome */
}
@media (hover: hover) and (pointer: fine) { @media (hover: hover) and (pointer: fine) {
.clickable:hover { .clickable:hover {
background-color: hsl(var(--muted) / 0.5); background-color: hsl(var(--muted) / 0.5);
@@ -70,6 +78,7 @@
} }
:root { :root {
--surface-background: 0 0% 98%;
--background: 0 0% 100%; --background: 0 0% 100%;
--foreground: 240 10% 3.9%; --foreground: 240 10% 3.9%;
--card: 0 0% 100%; --card: 0 0% 100%;
@@ -79,11 +88,11 @@
--primary: 259 43% 56%; --primary: 259 43% 56%;
--primary-hover: 259 43% 65%; --primary-hover: 259 43% 65%;
--primary-foreground: 0 0% 98%; --primary-foreground: 0 0% 98%;
--secondary: 240 4.8% 95.9%; --secondary: 240 4.8% 94%;
--secondary-foreground: 240 5.9% 10%; --secondary-foreground: 240 5.9% 10%;
--muted: 240 4.8% 95.9%; --muted: 240 4.8% 94%;
--muted-foreground: 240 3.8% 46.1%; --muted-foreground: 240 3.8% 46.1%;
--accent: 240 4.8% 95.9%; --accent: 240 4.8% 94%;
--accent-foreground: 240 5.9% 10%; --accent-foreground: 240 5.9% 10%;
--destructive: 0 84.2% 60.2%; --destructive: 0 84.2% 60.2%;
--destructive-foreground: 0 0% 98%; --destructive-foreground: 0 0% 98%;
@@ -98,11 +107,12 @@
--radius: 0.5rem; --radius: 0.5rem;
} }
.dark { .dark {
--background: 240 10% 3.9%; --surface-background: 240 10% 3.9%;
--background: 0 0% 9%;
--foreground: 0 0% 98%; --foreground: 0 0% 98%;
--card: 240 10% 3.9%; --card: 0 0% 9%;
--card-foreground: 0 0% 98%; --card-foreground: 0 0% 98%;
--popover: 240 10% 3.9%; --popover: 0 0% 9%;
--popover-foreground: 0 0% 98%; --popover-foreground: 0 0% 98%;
--primary: 259 43% 56%; --primary: 259 43% 56%;
--primary-hover: 259 43% 65%; --primary-hover: 259 43% 65%;

View File

@@ -1,4 +1,3 @@
import BottomNavigationBar from '@/components/BottomNavigationBar'
import ScrollToTopButton from '@/components/ScrollToTopButton' import ScrollToTopButton from '@/components/ScrollToTopButton'
import { Titlebar } from '@/components/Titlebar' import { Titlebar } from '@/components/Titlebar'
import { ScrollArea } from '@/components/ui/scroll-area' import { ScrollArea } from '@/components/ui/scroll-area'
@@ -13,12 +12,14 @@ const PrimaryPageLayout = forwardRef(
children, children,
titlebar, titlebar,
pageName, pageName,
displayScrollToTopButton = false displayScrollToTopButton = false,
hideTitlebarBottomBorder = false
}: { }: {
children?: React.ReactNode children?: React.ReactNode
titlebar: React.ReactNode titlebar: React.ReactNode
pageName: TPrimaryPageName pageName: TPrimaryPageName
displayScrollToTopButton?: boolean displayScrollToTopButton?: boolean
hideTitlebarBottomBorder?: boolean
}, },
ref ref
) => { ) => {
@@ -69,9 +70,10 @@ const PrimaryPageLayout = forwardRef(
paddingBottom: 'calc(env(safe-area-inset-bottom) + 3rem)' paddingBottom: 'calc(env(safe-area-inset-bottom) + 3rem)'
}} }}
> >
<PrimaryPageTitlebar>{titlebar}</PrimaryPageTitlebar> <PrimaryPageTitlebar hideBottomBorder={hideTitlebarBottomBorder}>
{titlebar}
</PrimaryPageTitlebar>
{children} {children}
<BottomNavigationBar />
</div> </div>
{displayScrollToTopButton && <ScrollToTopButton />} {displayScrollToTopButton && <ScrollToTopButton />}
</DeepBrowsingProvider> </DeepBrowsingProvider>
@@ -81,7 +83,7 @@ const PrimaryPageLayout = forwardRef(
return ( return (
<DeepBrowsingProvider active={current === pageName && display} scrollAreaRef={scrollAreaRef}> <DeepBrowsingProvider active={current === pageName && display} scrollAreaRef={scrollAreaRef}>
<ScrollArea <ScrollArea
className="h-screen overflow-auto" className="h-full overflow-auto"
scrollBarClassName="z-50 pt-12" scrollBarClassName="z-50 pt-12"
ref={scrollAreaRef} ref={scrollAreaRef}
> >
@@ -101,6 +103,16 @@ export type TPrimaryPageLayoutRef = {
scrollToTop: () => void scrollToTop: () => void
} }
function PrimaryPageTitlebar({ children }: { children?: React.ReactNode }) { function PrimaryPageTitlebar({
return <Titlebar className="p-1">{children}</Titlebar> children,
hideBottomBorder = false
}: {
children?: React.ReactNode
hideBottomBorder?: boolean
}) {
return (
<Titlebar className="p-1" hideBottomBorder={hideBottomBorder}>
{children}
</Titlebar>
)
} }

View File

@@ -1,5 +1,4 @@
import BackButton from '@/components/BackButton' import BackButton from '@/components/BackButton'
import BottomNavigationBar from '@/components/BottomNavigationBar'
import ScrollToTopButton from '@/components/ScrollToTopButton' import ScrollToTopButton from '@/components/ScrollToTopButton'
import { Titlebar } from '@/components/Titlebar' import { Titlebar } from '@/components/Titlebar'
import { ScrollArea } from '@/components/ui/scroll-area' import { ScrollArea } from '@/components/ui/scroll-area'
@@ -16,6 +15,7 @@ const SecondaryPageLayout = forwardRef(
title, title,
controls, controls,
hideBackButton = false, hideBackButton = false,
hideTitlebarBottomBorder = false,
displayScrollToTopButton = false displayScrollToTopButton = false
}: { }: {
children?: React.ReactNode children?: React.ReactNode
@@ -23,6 +23,7 @@ const SecondaryPageLayout = forwardRef(
title?: React.ReactNode title?: React.ReactNode
controls?: React.ReactNode controls?: React.ReactNode
hideBackButton?: boolean hideBackButton?: boolean
hideTitlebarBottomBorder?: boolean
displayScrollToTopButton?: boolean displayScrollToTopButton?: boolean
}, },
ref ref
@@ -65,9 +66,9 @@ const SecondaryPageLayout = forwardRef(
title={title} title={title}
controls={controls} controls={controls}
hideBackButton={hideBackButton} hideBackButton={hideBackButton}
hideBottomBorder={hideTitlebarBottomBorder}
/> />
{children} {children}
<BottomNavigationBar />
</div> </div>
{displayScrollToTopButton && <ScrollToTopButton />} {displayScrollToTopButton && <ScrollToTopButton />}
</DeepBrowsingProvider> </DeepBrowsingProvider>
@@ -77,7 +78,7 @@ const SecondaryPageLayout = forwardRef(
return ( return (
<DeepBrowsingProvider active={currentIndex === index} scrollAreaRef={scrollAreaRef}> <DeepBrowsingProvider active={currentIndex === index} scrollAreaRef={scrollAreaRef}>
<ScrollArea <ScrollArea
className="h-screen overflow-auto" className="h-full overflow-auto"
scrollBarClassName="z-50 pt-12" scrollBarClassName="z-50 pt-12"
ref={scrollAreaRef} ref={scrollAreaRef}
> >
@@ -85,6 +86,7 @@ const SecondaryPageLayout = forwardRef(
title={title} title={title}
controls={controls} controls={controls}
hideBackButton={hideBackButton} hideBackButton={hideBackButton}
hideBottomBorder={hideTitlebarBottomBorder}
/> />
{children} {children}
<div className="h-4" /> <div className="h-4" />
@@ -100,14 +102,19 @@ export default SecondaryPageLayout
export function SecondaryPageTitlebar({ export function SecondaryPageTitlebar({
title, title,
controls, controls,
hideBackButton = false hideBackButton = false,
hideBottomBorder = false
}: { }: {
title?: React.ReactNode title?: React.ReactNode
controls?: React.ReactNode controls?: React.ReactNode
hideBackButton?: boolean hideBackButton?: boolean
hideBottomBorder?: boolean
}): JSX.Element { }): JSX.Element {
return ( return (
<Titlebar className="flex gap-1 p-1 items-center justify-between font-semibold"> <Titlebar
className="flex gap-1 p-1 items-center justify-between font-semibold"
hideBottomBorder={hideBottomBorder}
>
{hideBackButton ? ( {hideBackButton ? (
<div className="flex gap-2 items-center pl-3 w-fit truncate text-lg font-semibold"> <div className="flex gap-2 items-center pl-3 w-fit truncate text-lg font-semibold">
{title} {title}

View File

@@ -33,7 +33,12 @@ const MePage = forwardRef((_, ref) => {
if (!pubkey) { if (!pubkey) {
return ( return (
<PrimaryPageLayout ref={ref} pageName="home" titlebar={<MePageTitlebar />}> <PrimaryPageLayout
ref={ref}
pageName="home"
titlebar={<MePageTitlebar />}
hideTitlebarBottomBorder
>
<div className="flex flex-col p-4 gap-4 overflow-auto"> <div className="flex flex-col p-4 gap-4 overflow-auto">
<AccountManager /> <AccountManager />
</div> </div>
@@ -42,7 +47,12 @@ const MePage = forwardRef((_, ref) => {
} }
return ( return (
<PrimaryPageLayout ref={ref} pageName="home" titlebar={<MePageTitlebar />}> <PrimaryPageLayout
ref={ref}
pageName="home"
titlebar={<MePageTitlebar />}
hideTitlebarBottomBorder
>
<div className="flex gap-4 items-center p-4"> <div className="flex gap-4 items-center p-4">
<SimpleUserAvatar userId={pubkey} size="big" /> <SimpleUserAvatar userId={pubkey} size="big" />
<div className="space-y-1 flex-1 w-0"> <div className="space-y-1 flex-1 w-0">

View File

@@ -34,7 +34,11 @@ export default function FeedButton({ className }: { className?: string }) {
<PopoverTrigger asChild> <PopoverTrigger asChild>
<FeedSwitcherTrigger className={className} /> <FeedSwitcherTrigger className={className} />
</PopoverTrigger> </PopoverTrigger>
<PopoverContent side="bottom" className="w-96 p-4 max-h-[80vh] overflow-auto"> <PopoverContent
sideOffset={0}
side="bottom"
className="w-96 p-4 max-h-[80vh] overflow-auto scrollbar-hide"
>
<FeedSwitcher close={() => setOpen(false)} /> <FeedSwitcher close={() => setOpen(false)} />
</PopoverContent> </PopoverContent>
</Popover> </Popover>

View File

@@ -24,7 +24,7 @@ const NoteListPage = forwardRef((_, ref) => {
useEffect(() => { useEffect(() => {
if (layoutRef.current) { if (layoutRef.current) {
layoutRef.current.scrollToTop() layoutRef.current.scrollToTop('instant')
} }
}, [JSON.stringify(relayUrls), feedInfo]) }, [JSON.stringify(relayUrls), feedInfo])

View File

@@ -26,7 +26,7 @@ const GeneralSettingsPage = forwardRef(({ index }: { index?: number }, ref) => {
return ( return (
<SecondaryPageLayout ref={ref} index={index} title={t('General')}> <SecondaryPageLayout ref={ref} index={index} title={t('General')}>
<div className="space-y-4 mt-2"> <div className="space-y-4 mt-3">
<SettingItem> <SettingItem>
<Label htmlFor="languages" className="text-base font-normal"> <Label htmlFor="languages" className="text-base font-normal">
{t('Languages')} {t('Languages')}

View File

@@ -30,7 +30,7 @@ const HomePage = forwardRef(({ index }: { index?: number }, ref) => {
if (!recommendedRelayInfos.length) { if (!recommendedRelayInfos.length) {
return ( return (
<SecondaryPageLayout ref={ref} index={index} hideBackButton> <SecondaryPageLayout ref={ref} index={index} hideBackButton hideTitlebarBottomBorder>
<div className="text-muted-foreground w-full h-screen flex items-center justify-center"> <div className="text-muted-foreground w-full h-screen flex items-center justify-center">
{t('Welcome! 🥳')} {t('Welcome! 🥳')}
</div> </div>
@@ -49,8 +49,9 @@ const HomePage = forwardRef(({ index }: { index?: number }, ref) => {
</> </>
} }
hideBackButton hideBackButton
hideTitlebarBottomBorder
> >
<div className="px-4"> <div className="px-4 pt-2">
<div className="grid grid-cols-2 gap-3"> <div className="grid grid-cols-2 gap-3">
{recommendedRelayInfos.map((relayInfo) => ( {recommendedRelayInfos.map((relayInfo) => (
<RelaySimpleInfo <RelaySimpleInfo

View File

@@ -1,14 +0,0 @@
import SecondaryPageLayout from '@/layouts/SecondaryPageLayout'
import { forwardRef } from 'react'
const LoadingPage = forwardRef(({ title, index }: { title?: string; index?: number }, ref) => {
return (
<SecondaryPageLayout ref={ref} index={index} title={title}>
<div className="text-muted-foreground text-center">
<div>Loading...</div>
</div>
</SecondaryPageLayout>
)
})
LoadingPage.displayName = 'LoadingPage'
export default LoadingPage

View File

@@ -35,7 +35,7 @@ const NotePage = forwardRef(({ id, index }: { id?: string; index?: number }, ref
if (!event && isFetching) { if (!event && isFetching) {
return ( return (
<SecondaryPageLayout ref={ref} index={index} title={t('Note')}> <SecondaryPageLayout ref={ref} index={index} title={t('Note')}>
<div className="px-4"> <div className="px-4 pt-3">
<div className="flex items-center space-x-2"> <div className="flex items-center space-x-2">
<Skeleton className="w-10 h-10 rounded-full" /> <Skeleton className="w-10 h-10 rounded-full" />
<div className={`flex-1 w-0`}> <div className={`flex-1 w-0`}>
@@ -69,7 +69,7 @@ const NotePage = forwardRef(({ id, index }: { id?: string; index?: number }, ref
return ( return (
<SecondaryPageLayout ref={ref} index={index} title={t('Note')} displayScrollToTopButton> <SecondaryPageLayout ref={ref} index={index} title={t('Note')} displayScrollToTopButton>
<div className="px-4"> <div className="px-4 pt-3">
{rootITag && <ExternalRoot value={rootITag[1]} />} {rootITag && <ExternalRoot value={rootITag[1]} />}
{rootEventId !== parentEventId && ( {rootEventId !== parentEventId && (
<ParentNote <ParentNote
@@ -132,12 +132,12 @@ function ParentNote({
if (isFetching) { if (isFetching) {
return ( return (
<div> <div>
<Card className="flex space-x-1 px-1.5 py-1 items-center clickable text-sm text-muted-foreground"> <div className="flex space-x-1 px-[0.4375rem] py-1 items-center rounded-full border clickable text-sm text-muted-foreground">
<Skeleton className="shrink w-4 h-4 rounded-full" /> <Skeleton className="shrink w-4 h-4 rounded-full" />
<div className="py-1 flex-1"> <div className="py-1 flex-1">
<Skeleton className="h-3" /> <Skeleton className="h-3" />
</div> </div>
</Card> </div>
<div className="ml-5 w-px h-3 bg-border" /> <div className="ml-5 w-px h-3 bg-border" />
</div> </div>
) )
@@ -146,9 +146,9 @@ function ParentNote({
return ( return (
<div> <div>
<Card <div
className={cn( className={cn(
'flex space-x-1 px-1.5 py-1 items-center clickable text-sm text-muted-foreground', 'flex space-x-1 px-[0.4375rem] py-1 items-center rounded-full border clickable text-sm text-muted-foreground',
event && 'hover:text-foreground' event && 'hover:text-foreground'
)} )}
onClick={() => { onClick={() => {
@@ -158,7 +158,7 @@ function ParentNote({
> >
{event && <UserAvatar userId={event.pubkey} size="tiny" className="shrink-0" />} {event && <UserAvatar userId={event.pubkey} size="tiny" className="shrink-0" />}
<ContentPreview className="truncate" event={event} /> <ContentPreview className="truncate" event={event} />
</Card> </div>
{isConsecutive ? ( {isConsecutive ? (
<div className="ml-5 w-px h-3 bg-border" /> <div className="ml-5 w-px h-3 bg-border" />
) : ( ) : (

View File

@@ -18,7 +18,7 @@ const RelaySettingsPage = forwardRef(({ id, index }: { id?: string; index?: numb
index={index} index={index}
title={t("username's used relays", { username: profile.username })} title={t("username's used relays", { username: profile.username })}
> >
<div className="px-4"> <div className="px-4 pt-3">
<OthersRelayList userId={id} /> <OthersRelayList userId={id} />
</div> </div>
</SecondaryPageLayout> </SecondaryPageLayout>

View File

@@ -8,7 +8,7 @@ const PostSettingsPage = forwardRef(({ index }: { index?: number }, ref) => {
return ( return (
<SecondaryPageLayout ref={ref} index={index} title={t('Post settings')}> <SecondaryPageLayout ref={ref} index={index} title={t('Post settings')}>
<div className="px-4 pt-2 space-y-4"> <div className="px-4 pt-3 space-y-4">
<MediaUploadServiceSetting /> <MediaUploadServiceSetting />
</div> </div>
</SecondaryPageLayout> </SecondaryPageLayout>

View File

@@ -124,8 +124,7 @@ const ProfileEditorPage = forwardRef(({ index }: { index?: number }, ref) => {
return ( return (
<SecondaryPageLayout ref={ref} index={index} title={profile.username} controls={controls}> <SecondaryPageLayout ref={ref} index={index} title={profile.username} controls={controls}>
<div className="px-4"> <div className="relative bg-cover bg-center mb-2">
<div className="relative bg-cover bg-center rounded-lg mb-2">
<Uploader <Uploader
onUploadSuccess={onBannerUploadSuccess} onUploadSuccess={onBannerUploadSuccess}
onUploadStart={() => setUploadingBanner(true)} onUploadStart={() => setUploadingBanner(true)}
@@ -135,14 +134,10 @@ const ProfileEditorPage = forwardRef(({ index }: { index?: number }, ref) => {
<ProfileBanner <ProfileBanner
banner={banner} banner={banner}
pubkey={account.pubkey} pubkey={account.pubkey}
className="w-full aspect-[3/1] object-cover rounded-lg" className="w-full aspect-[3/1] object-cover"
/> />
<div className="absolute top-0 bg-muted/30 w-full h-full rounded-lg flex flex-col justify-center items-center"> <div className="absolute top-0 bg-muted/30 w-full h-full flex flex-col justify-center items-center">
{uploadingBanner ? ( {uploadingBanner ? <Loader size={36} className="animate-spin" /> : <Upload size={36} />}
<Loader size={36} className="animate-spin" />
) : (
<Upload size={36} />
)}
</div> </div>
</Uploader> </Uploader>
<Uploader <Uploader
@@ -162,7 +157,7 @@ const ProfileEditorPage = forwardRef(({ index }: { index?: number }, ref) => {
</div> </div>
</Uploader> </Uploader>
</div> </div>
<div className="pt-14 flex flex-col gap-4"> <div className="pt-14 px-4 flex flex-col gap-4">
<Item> <Item>
<Label htmlFor="profile-username-input">{t('Display Name')}</Label> <Label htmlFor="profile-username-input">{t('Display Name')}</Label>
<Input <Input
@@ -230,7 +225,6 @@ const ProfileEditorPage = forwardRef(({ index }: { index?: number }, ref) => {
)} )}
</Item> </Item>
</div> </div>
</div>
</SecondaryPageLayout> </SecondaryPageLayout>
) )
}) })

View File

@@ -87,9 +87,9 @@ const ProfilePage = forwardRef(({ id, index }: { id?: string; index?: number },
if (!profile && isFetching) { if (!profile && isFetching) {
return ( return (
<SecondaryPageLayout index={index} ref={ref}> <SecondaryPageLayout index={index} ref={ref}>
<div className="sm:px-4"> <div>
<div className="relative bg-cover bg-center mb-2"> <div className="relative bg-cover bg-center mb-2">
<Skeleton className="w-full aspect-[3/1] sm:rounded-lg" /> <Skeleton className="w-full aspect-[3/1] rounded-none" />
<Skeleton className="w-24 h-24 absolute bottom-0 left-3 translate-y-1/2 border-4 border-background rounded-full" /> <Skeleton className="w-24 h-24 absolute bottom-0 left-3 translate-y-1/2 border-4 border-background rounded-full" />
</div> </div>
</div> </div>
@@ -106,13 +106,8 @@ const ProfilePage = forwardRef(({ id, index }: { id?: string; index?: number },
return ( return (
<SecondaryPageLayout index={index} title={username} displayScrollToTopButton ref={ref}> <SecondaryPageLayout index={index} title={username} displayScrollToTopButton ref={ref}>
<div ref={topContainerRef}> <div ref={topContainerRef}>
<div className="sm:px-4">
<div className="relative bg-cover bg-center mb-2"> <div className="relative bg-cover bg-center mb-2">
<ProfileBanner <ProfileBanner banner={banner} pubkey={pubkey} className="w-full aspect-[3/1]" />
banner={banner}
pubkey={pubkey}
className="w-full aspect-[3/1] sm:rounded-lg"
/>
<Avatar className="w-24 h-24 absolute left-3 bottom-0 translate-y-1/2 border-4 border-background"> <Avatar className="w-24 h-24 absolute left-3 bottom-0 translate-y-1/2 border-4 border-background">
<AvatarImage src={avatar} className="object-cover object-center" /> <AvatarImage src={avatar} className="object-cover object-center" />
<AvatarFallback> <AvatarFallback>
@@ -120,9 +115,8 @@ const ProfilePage = forwardRef(({ id, index }: { id?: string; index?: number },
</AvatarFallback> </AvatarFallback>
</Avatar> </Avatar>
</div> </div>
</div>
<div className="px-4"> <div className="px-4">
<div className="flex justify-end h-8 gap-2 items-center max-sm:translate-x-2"> <div className="flex justify-end h-8 gap-2 items-center">
<ProfileOptions pubkey={pubkey} /> <ProfileOptions pubkey={pubkey} />
{isSelf ? ( {isSelf ? (
<Button <Button

View File

@@ -42,6 +42,7 @@ const RelayPage = forwardRef(({ url, index }: { url?: string; index?: number },
controls={<RelayPageControls url={normalizedUrl} />} controls={<RelayPageControls url={normalizedUrl} />}
displayScrollToTopButton displayScrollToTopButton
> >
<div className="h-3 w-full" />
<RelayInfo url={normalizedUrl} /> <RelayInfo url={normalizedUrl} />
{relayInfo?.supported_nips?.includes(50) && ( {relayInfo?.supported_nips?.includes(50) && (
<div className="px-4 py-2"> <div className="px-4 py-2">

View File

@@ -22,7 +22,7 @@ const RelaySettingsPage = forwardRef(({ index }: { index?: number }, ref) => {
return ( return (
<SecondaryPageLayout ref={ref} index={index} title={t('Relay settings')}> <SecondaryPageLayout ref={ref} index={index} title={t('Relay settings')}>
<Tabs value={tabValue} onValueChange={setTabValue} className="px-4 pb-4 space-y-4"> <Tabs value={tabValue} onValueChange={setTabValue} className="px-4 py-3 space-y-4">
<TabsList> <TabsList>
<TabsTrigger value="favorite-relays">{t('Favorite Relays')}</TabsTrigger> <TabsTrigger value="favorite-relays">{t('Favorite Relays')}</TabsTrigger>
<TabsTrigger value="mailbox">{t('Read & Write Relays')}</TabsTrigger> <TabsTrigger value="mailbox">{t('Read & Write Relays')}</TabsTrigger>

View File

@@ -27,7 +27,7 @@ const TranslationPage = forwardRef(({ index }: { index?: number }, ref) => {
return ( return (
<SecondaryPageLayout ref={ref} index={index} title={t('Translation')}> <SecondaryPageLayout ref={ref} index={index} title={t('Translation')}>
<div className="px-4 pt-2 space-y-4"> <div className="px-4 pt-3 space-y-4">
<div className="space-y-2"> <div className="space-y-2">
<Label htmlFor="languages" className="text-base font-medium"> <Label htmlFor="languages" className="text-base font-medium">
{t('Languages')} {t('Languages')}

View File

@@ -12,7 +12,7 @@ const WalletPage = forwardRef(({ index }: { index?: number }, ref) => {
return ( return (
<SecondaryPageLayout ref={ref} index={index} title={t('Wallet')}> <SecondaryPageLayout ref={ref} index={index} title={t('Wallet')}>
<div className="px-4 pt-2 space-y-4"> <div className="px-4 pt-3 space-y-4">
<BcButton /> <BcButton />
<LightningAddressInput /> <LightningAddressInput />
<DefaultZapAmountInput /> <DefaultZapAmountInput />

View File

@@ -10,6 +10,9 @@ export default {
sm: 'calc(var(--radius) - 4px)' sm: 'calc(var(--radius) - 4px)'
}, },
colors: { colors: {
surface: {
background: 'hsl(var(--surface-background))'
},
background: 'hsl(var(--background))', background: 'hsl(var(--background))',
foreground: 'hsl(var(--foreground))', foreground: 'hsl(var(--foreground))',
card: { card: {