Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
66 changes: 46 additions & 20 deletions src/components/Layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,26 @@ import { useTheme } from 'theme-o-rama';
import { useLocalStorage } from 'usehooks-ts';
import { BottomNav, TopNav } from './Nav';

function WalletTransitionWrapper({ children }: PropsWithChildren) {
const { isSwitching, wallet } = useWallet();

// Only show content if we have a wallet or we're not switching
// This prevents old wallet data from showing during transition
const shouldShow = wallet !== null || !isSwitching;

return (
<div
className={`transition-all duration-300 ${
!shouldShow
? 'opacity-0 blur-sm pointer-events-none'
: 'opacity-100 blur-0'
}`}
>
{shouldShow ? children : null}
</div>
);
}

const SIDEBAR_COLLAPSED_STORAGE_KEY = 'sage-wallet-sidebar-collapsed';

type LayoutProps = PropsWithChildren<object> & {
Expand Down Expand Up @@ -117,7 +137,9 @@ export function FullLayout(props: LayoutProps) {
onClick={() => setIsCollapsed(!isCollapsed)}
className='text-2xl hover:scale-110 transition-transform cursor-pointer'
aria-label={t`Expand sidebar - ${wallet.name}`}
aria-expanded={!isCollapsed}
{...(isCollapsed
? { 'aria-expanded': false }
: { 'aria-expanded': true })}
>
<span role='img' aria-label={t`Wallet emoji`}>
{wallet.emoji}
Expand All @@ -140,7 +162,9 @@ export function FullLayout(props: LayoutProps) {
aria-label={
isCollapsed ? t`Expand sidebar` : t`Collapse sidebar`
}
aria-expanded={!isCollapsed}
{...(isCollapsed
? { 'aria-expanded': false }
: { 'aria-expanded': true })}
>
{isCollapsed ? (
<PanelLeft className='h-5 w-5' aria-hidden='true' />
Expand Down Expand Up @@ -183,27 +207,29 @@ export function FullLayout(props: LayoutProps) {
</div>
</div>
</div>
<div
className={`flex flex-col h-screen overflow-hidden ${
props.transparentBackground ? 'bg-transparent' : 'bg-background'
}`}
style={{
paddingBottom: insets.bottom
? `${insets.bottom}px`
: 'env(safe-area-inset-bottom)',
}}
>
<WalletTransitionWrapper>
<div
className='bg-background'
className={`flex flex-col h-screen overflow-hidden ${
props.transparentBackground ? 'bg-transparent' : 'bg-background'
}`}
style={{
height:
insets.top !== 0
? `${insets.top + 8}px`
: 'env(safe-area-inset-top)',
paddingBottom: insets.bottom
? `${insets.bottom}px`
: 'env(safe-area-inset-bottom)',
}}
/>
{props.children}
</div>
>
<div
className='bg-background'
style={{
height:
insets.top !== 0
? `${insets.top + 8}px`
: 'env(safe-area-inset-top)',
}}
/>
{props.children}
</div>
</WalletTransitionWrapper>
</div>
</TooltipProvider>
);
Expand Down
92 changes: 4 additions & 88 deletions src/components/Nav.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,3 @@
import {
Tooltip,
TooltipContent,
TooltipTrigger,
} from '@/components/ui/tooltip';
import { usePeers } from '@/hooks/usePeers';
import { logoutAndUpdateState, useWalletState } from '@/state';
import { t } from '@lingui/core/macro';
Expand All @@ -16,15 +11,15 @@ import {
FilePenLine,
Handshake,
Images,
LogOut,
MonitorCheck,
MonitorCog,
SquareUserRound,
WalletIcon,
} from 'lucide-react';
import { PropsWithChildren } from 'react';
import { Link, useLocation, useNavigate } from 'react-router-dom';
import { useNavigate } from 'react-router-dom';
import { NavLink } from './NavLink';
import { Separator } from './ui/separator';
import { WalletSwitcher } from './WalletSwitcher';

interface NavProps {
isCollapsed?: boolean;
Expand Down Expand Up @@ -212,86 +207,7 @@ export function BottomNav({ isCollapsed }: NavProps) {
<Cog className={className} aria-hidden='true' />
</NavLink>

<NavLink
url={logout}
isCollapsed={isCollapsed}
message={<Trans>Logout</Trans>}
>
<LogOut className={className} aria-hidden='true' />
</NavLink>
<WalletSwitcher isCollapsed={isCollapsed} logout={logout} />
</nav>
);
}

interface NavLinkProps extends PropsWithChildren {
url: string | (() => void);
isCollapsed?: boolean;
message: React.ReactNode;
customTooltip?: React.ReactNode;
ariaCurrent?: 'page' | 'step' | 'location' | 'date' | 'time' | true | false;
}

function NavLink({
url,
children,
isCollapsed,
message,
customTooltip,
ariaCurrent,
}: NavLinkProps) {
const location = useLocation();
const isActive =
typeof url === 'string' &&
(location.pathname === url ||
(url !== '/' && location.pathname.startsWith(url)));

const baseClassName = `flex items-center gap-3 transition-all ${
isCollapsed ? 'justify-center p-2 rounded-full' : 'px-2 rounded-lg py-1.5'
} text-lg md:text-base`;

const className = isActive
? `${baseClassName} text-primary border-primary`
: `${baseClassName} text-muted-foreground hover:text-primary`;

const activeStyle = isActive
? { backgroundColor: 'var(--nav-active-background)' }
: {};

const link =
typeof url === 'string' ? (
<Link
to={url}
className={className}
style={activeStyle}
aria-current={isActive ? 'page' : ariaCurrent}
aria-label={isCollapsed ? message?.toString() : undefined}
>
{children}
{!isCollapsed && message}
</Link>
) : (
<button
type='button'
onClick={url}
className={className}
style={activeStyle}
aria-label={isCollapsed ? message?.toString() : undefined}
>
{children}
{!isCollapsed && message}
</button>
);

if (isCollapsed || customTooltip) {
return (
<Tooltip>
<TooltipTrigger asChild>{link}</TooltipTrigger>
<TooltipContent side='right' role='tooltip' aria-live='polite'>
{customTooltip || message}
</TooltipContent>
</Tooltip>
);
}

return link;
}
80 changes: 80 additions & 0 deletions src/components/NavLink.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
import {
Tooltip,
TooltipContent,
TooltipTrigger,
} from '@/components/ui/tooltip';
import { PropsWithChildren } from 'react';
import { Link, useLocation } from 'react-router-dom';

interface NavLinkProps extends PropsWithChildren {
url: string | (() => void);
isCollapsed?: boolean;
message: React.ReactNode;
customTooltip?: React.ReactNode;
ariaCurrent?: 'page' | 'step' | 'location' | 'date' | 'time' | true | false;
}

export function NavLink({
url,
children,
isCollapsed,
message,
customTooltip,
ariaCurrent,
}: NavLinkProps) {
const location = useLocation();
const isActive =
typeof url === 'string' &&
(location.pathname === url ||
(url !== '/' && location.pathname.startsWith(url)));

const baseClassName = `flex items-center gap-3 transition-all ${
isCollapsed ? 'justify-center p-2 rounded-full' : 'px-2 rounded-lg py-1.5'
} text-lg md:text-base`;

const className = isActive
? `${baseClassName} text-primary border-primary`
: `${baseClassName} text-muted-foreground hover:text-primary`;

const activeStyle = isActive
? { backgroundColor: 'var(--nav-active-background)' }
: {};

const link =
typeof url === 'string' ? (
<Link
to={url}
className={className}
style={activeStyle}
aria-current={isActive ? 'page' : ariaCurrent}
aria-label={isCollapsed ? message?.toString() : undefined}
>
{children}
{!isCollapsed && message}
</Link>
) : (
<button
type='button'
onClick={url}
className={className}
style={activeStyle}
aria-label={isCollapsed ? message?.toString() : undefined}
>
{children}
{!isCollapsed && message}
</button>
);

if (isCollapsed || customTooltip) {
return (
<Tooltip>
<TooltipTrigger asChild>{link}</TooltipTrigger>
<TooltipContent side='right' role='tooltip' aria-live='polite'>
{customTooltip || message}
</TooltipContent>
</Tooltip>
);
}

return link;
}
Loading
Loading