Restyle navigation with V-angled bars and Agora bolt feed button
Replace the smooth arc shapes shared by the mobile top bar, sub-header tabs, and bottom nav with angled V polylines centralized in ArcBackground. The top bar and sub-header now use flat rectangles, and the bottom nav has a sharp V apex that cradles a centered Agora-bolt Feed button. The bottom nav row layout changes from [Home, Search, Notifications, Profile] to [Search, Communities, _apex_, Notifications, World] with smaller outer items, and the apex links to the configured home/feed page with scroll-to-top + invalidation on re-tap. Also drop the redundant 'Feed' page header on the home feed and the border under the compact ComposeBox so it blends with the tabs strip below it.
This commit is contained in:
@@ -6,11 +6,15 @@ export const ARC_OVERHANG_PX = 20;
|
||||
/** Larger overhang for the upward arc (bottom nav) so the harsher curve isn't clipped. */
|
||||
export const ARC_UP_OVERHANG_PX = 28;
|
||||
|
||||
/** SVG path for a downward arc (used by top bar and sub-header bar). */
|
||||
const ARC_DOWN_PATH = 'M0,0 L100,0 L100,44 Q50,64 0,44 Z';
|
||||
/** SVG path for a downward angled bar (used by top bar and sub-header bar).
|
||||
* Bottom edge slopes from each corner down to a center apex, forming an
|
||||
* inverted-V that points toward the content below. */
|
||||
const ARC_DOWN_PATH = 'M0,0 L100,0 L100,34 L50,46 L0,34 Z';
|
||||
|
||||
/** SVG path for an upward arc (used by bottom nav). */
|
||||
const ARC_UP_PATH = 'M0,30 Q50,0 100,30 L100,64 L0,64 Z';
|
||||
/** SVG path for an upward angled bar (used by bottom nav).
|
||||
* Top edge slopes from each corner up to a center apex, forming a V that
|
||||
* points away from the content. */
|
||||
const ARC_UP_PATH = 'M0,40 L50,16 L100,40 L100,64 L0,64 Z';
|
||||
|
||||
/** SVG path for a plain rectangle with no arc. */
|
||||
const RECT_PATH = 'M0,0 L100,0 L100,64 L0,64 Z';
|
||||
@@ -54,8 +58,8 @@ export function ArcBackground({ variant, className }: ArcBackgroundProps) {
|
||||
style={hasArc ? (variant === 'up' ? arcUpHeightStyle : arcDownHeightStyle) : fullHeightStyle}
|
||||
>
|
||||
<path d={path} className="fill-background/85" />
|
||||
{variant === 'down' && <path d="M0,44 Q50,64 100,44" fill="none" className="stroke-border" strokeWidth="1" vectorEffect="non-scaling-stroke" />}
|
||||
{variant === 'up' && <path d="M0,30 Q50,0 100,30" fill="none" className="stroke-border" strokeWidth="1" vectorEffect="non-scaling-stroke" />}
|
||||
{variant === 'down' && <path d="M0,34 L50,46 L100,34" fill="none" className="stroke-border" strokeWidth="1" vectorEffect="non-scaling-stroke" strokeLinejoin="round" />}
|
||||
{variant === 'up' && <path d="M0,40 L50,16 L100,40" fill="none" className="stroke-border" strokeWidth="1" vectorEffect="non-scaling-stroke" strokeLinejoin="round" />}
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -251,7 +251,7 @@ export function Feed({ kinds, tagFilters, header, hideCompose, emptyMessage, fee
|
||||
/>
|
||||
)}
|
||||
|
||||
{!hideCompose && <ComposeBox compact />}
|
||||
{!hideCompose && <ComposeBox compact hideBorder />}
|
||||
|
||||
{/* Tabs (logged in) */}
|
||||
{user && (
|
||||
|
||||
@@ -1,14 +1,13 @@
|
||||
import { useCallback, useState } from 'react';
|
||||
import { Link, useLocation } from 'react-router-dom';
|
||||
import { useQueryClient } from '@tanstack/react-query';
|
||||
import { Bell, Home, Search, User } from 'lucide-react';
|
||||
import { Avatar, AvatarImage, AvatarFallback } from '@/components/ui/avatar';
|
||||
import { Bell, Earth, Search, Users } from 'lucide-react';
|
||||
import { AgoraBoltIcon } from '@/components/icons/AgoraBoltIcon';
|
||||
import { cn } from '@/lib/utils';
|
||||
import { selectionChanged } from '@/lib/haptics';
|
||||
import { useHasUnreadNotifications } from '@/hooks/useHasUnreadNotifications';
|
||||
import { useCurrentUser } from '@/hooks/useCurrentUser';
|
||||
import { useScrollDirection } from '@/hooks/useScrollDirection';
|
||||
import { useProfileUrl } from '@/hooks/useProfileUrl';
|
||||
import { useAppContext } from '@/hooks/useAppContext';
|
||||
import { useLayoutSnapshot } from '@/contexts/LayoutContext';
|
||||
import { getSidebarItem } from '@/lib/sidebarItems';
|
||||
@@ -20,20 +19,51 @@ const hiddenStyle: React.CSSProperties = {
|
||||
transform: `translateY(calc(100% + ${ARC_UP_OVERHANG_PX}px))`,
|
||||
};
|
||||
|
||||
interface NavItemProps {
|
||||
icon: React.ComponentType<{ className?: string }>;
|
||||
label: string;
|
||||
active: boolean;
|
||||
badge?: boolean;
|
||||
onClick?: (e: React.MouseEvent) => void;
|
||||
to?: string;
|
||||
/** 'sm' shrinks the slot (smaller flex basis + smaller icon/label) for outer items. */
|
||||
size?: 'sm' | 'md';
|
||||
}
|
||||
|
||||
/** A side item in the bottom nav row. */
|
||||
function NavItem({ icon: Icon, label, active, badge, onClick, to, size = 'md' }: NavItemProps) {
|
||||
const isSm = size === 'sm';
|
||||
const className = cn(
|
||||
'flex flex-col items-center justify-center gap-0.5 py-2 transition-colors min-w-0',
|
||||
isSm ? 'flex-[0.7]' : 'flex-1',
|
||||
active ? 'text-primary' : 'text-muted-foreground',
|
||||
);
|
||||
const inner = (
|
||||
<>
|
||||
<span className="relative">
|
||||
<Icon className={isSm ? 'size-4' : 'size-5'} />
|
||||
{badge && (
|
||||
<span className="absolute -top-1 right-0 size-2 bg-primary rounded-full" />
|
||||
)}
|
||||
</span>
|
||||
<span className={cn('font-medium truncate', isSm ? 'text-[9px]' : 'text-[10px]')}>{label}</span>
|
||||
</>
|
||||
);
|
||||
if (to) return <Link to={to} onClick={onClick} className={className}>{inner}</Link>;
|
||||
return <button onClick={onClick} className={className}>{inner}</button>;
|
||||
}
|
||||
|
||||
export function MobileBottomNav() {
|
||||
const location = useLocation();
|
||||
const queryClient = useQueryClient();
|
||||
const { user, metadata } = useCurrentUser();
|
||||
const { user } = useCurrentUser();
|
||||
const hasUnread = useHasUnreadNotifications();
|
||||
const { scrollContainer, noArcs } = useLayoutSnapshot();
|
||||
const { hidden } = useScrollDirection(scrollContainer);
|
||||
const profileUrl = useProfileUrl(user?.pubkey ?? '', metadata);
|
||||
|
||||
const { config } = useAppContext();
|
||||
const homeItem = getSidebarItem(config.homePage);
|
||||
const HomeIcon = homeItem?.icon ?? Home;
|
||||
const homeLabel = homeItem?.label ?? 'Home';
|
||||
const homePath = homeItem?.path;
|
||||
const homePath = homeItem?.path ?? '/';
|
||||
|
||||
const [searchOpen, setSearchOpen] = useState(false);
|
||||
|
||||
@@ -43,11 +73,24 @@ export function MobileBottomNav() {
|
||||
setSearchOpen((v) => !v);
|
||||
}, []);
|
||||
|
||||
const handleFeedClick = useCallback((e: React.MouseEvent) => {
|
||||
selectionChanged();
|
||||
setSearchOpen(false);
|
||||
if (location.pathname === '/' || location.pathname === homePath) {
|
||||
e.preventDefault();
|
||||
window.scrollTo({ top: 0, behavior: 'smooth' });
|
||||
void queryClient.invalidateQueries({ queryKey: ['feed'] });
|
||||
void queryClient.invalidateQueries({ queryKey: ['ditto-curated-feed'] });
|
||||
}
|
||||
}, [location.pathname, homePath, queryClient]);
|
||||
|
||||
// Hide the nav when search sheet is open so it doesn't compete for space
|
||||
const isHidden = hidden || searchOpen;
|
||||
|
||||
const displayName = metadata?.name || metadata?.display_name;
|
||||
const isOnProfile = user && location.pathname === profileUrl;
|
||||
const isOnFeed = location.pathname === '/' || location.pathname === homePath;
|
||||
const isOnCommunities = location.pathname === '/communities' || location.pathname.startsWith('/communities/');
|
||||
const isOnWorld = location.pathname === '/world' || location.pathname.startsWith('/world/');
|
||||
const isOnNotifications = location.pathname === '/notifications';
|
||||
|
||||
return (
|
||||
<>
|
||||
@@ -63,91 +106,79 @@ export function MobileBottomNav() {
|
||||
{/* Arc + items wrapper */}
|
||||
<div className="relative">
|
||||
<ArcBackground variant={noArcs ? 'rect' : 'up'} />
|
||||
<div className="h-11 flex items-center relative">
|
||||
<div className="h-12 flex items-end pb-0 relative translate-y-2">
|
||||
|
||||
{/* Home */}
|
||||
<Link
|
||||
to="/"
|
||||
onClick={() => {
|
||||
selectionChanged();
|
||||
setSearchOpen(false);
|
||||
// When already on the home page, scroll to top and refresh the feed
|
||||
if (location.pathname === '/' || location.pathname === homePath) {
|
||||
window.scrollTo({ top: 0, behavior: 'smooth' });
|
||||
void queryClient.invalidateQueries({ queryKey: ['feed'] });
|
||||
void queryClient.invalidateQueries({ queryKey: ['ditto-curated-feed'] });
|
||||
}
|
||||
}}
|
||||
className={cn(
|
||||
'flex flex-col items-center justify-center gap-0.5 flex-1 py-2 transition-colors',
|
||||
(location.pathname === '/' || location.pathname === homePath) ? 'text-primary' : 'text-muted-foreground',
|
||||
)}
|
||||
>
|
||||
<HomeIcon className="size-5" />
|
||||
<span className="text-[10px] font-medium">{homeLabel}</span>
|
||||
</Link>
|
||||
{/* Search */}
|
||||
<NavItem
|
||||
icon={Search}
|
||||
label="Search"
|
||||
active={searchOpen}
|
||||
onClick={handleSearchClick}
|
||||
size="sm"
|
||||
/>
|
||||
|
||||
{/* Search */}
|
||||
<button
|
||||
onClick={handleSearchClick}
|
||||
className={cn(
|
||||
'flex flex-col items-center justify-center gap-0.5 flex-1 py-2 transition-colors',
|
||||
searchOpen ? 'text-primary' : 'text-muted-foreground',
|
||||
)}
|
||||
>
|
||||
<Search className="size-5" />
|
||||
<span className="text-[10px] font-medium">Search</span>
|
||||
</button>
|
||||
|
||||
{/* Notifications */}
|
||||
{user && (
|
||||
<Link
|
||||
to="/notifications"
|
||||
{/* Communities */}
|
||||
<NavItem
|
||||
icon={Users}
|
||||
label="Communities"
|
||||
active={isOnCommunities}
|
||||
to="/communities"
|
||||
onClick={() => { selectionChanged(); setSearchOpen(false); }}
|
||||
className={cn(
|
||||
'flex flex-col items-center justify-center gap-0.5 flex-1 py-2 transition-colors',
|
||||
location.pathname === '/notifications' ? 'text-primary' : 'text-muted-foreground',
|
||||
)}
|
||||
>
|
||||
<span className="relative">
|
||||
<Bell className="size-5" />
|
||||
{hasUnread && (
|
||||
<span className="absolute -top-1 right-0 size-2 bg-primary rounded-full" />
|
||||
)}
|
||||
</span>
|
||||
<span className="text-[10px] font-medium">Notifications</span>
|
||||
</Link>
|
||||
)}
|
||||
/>
|
||||
|
||||
{/* Profile */}
|
||||
{user ? (
|
||||
<Link
|
||||
to={profileUrl}
|
||||
{/* Center spacer — reserved for the apex Feed button */}
|
||||
<div className="flex-[0.4]" aria-hidden="true" />
|
||||
|
||||
{/* Notifications */}
|
||||
{user ? (
|
||||
<NavItem
|
||||
icon={Bell}
|
||||
label="Notifications"
|
||||
active={isOnNotifications}
|
||||
badge={hasUnread}
|
||||
to="/notifications"
|
||||
onClick={() => { selectionChanged(); setSearchOpen(false); }}
|
||||
/>
|
||||
) : (
|
||||
<div className="flex-1" aria-hidden="true" />
|
||||
)}
|
||||
|
||||
{/* World */}
|
||||
<NavItem
|
||||
icon={Earth}
|
||||
label="World"
|
||||
active={isOnWorld}
|
||||
to="/world"
|
||||
onClick={() => { selectionChanged(); setSearchOpen(false); }}
|
||||
className={cn(
|
||||
'flex flex-col items-center justify-center gap-0.5 flex-1 py-2 transition-colors',
|
||||
isOnProfile ? 'text-primary' : 'text-muted-foreground',
|
||||
)}
|
||||
>
|
||||
<Avatar className="size-5">
|
||||
<AvatarImage src={metadata?.picture} alt={displayName} />
|
||||
<AvatarFallback className="bg-primary/20 text-primary text-[8px]">
|
||||
{displayName?.[0]?.toUpperCase() || <User className="size-3" />}
|
||||
</AvatarFallback>
|
||||
</Avatar>
|
||||
<span className="text-[10px] font-medium">Profile</span>
|
||||
</Link>
|
||||
) : (
|
||||
<Link
|
||||
to="/profile"
|
||||
className="flex flex-col items-center justify-center gap-0.5 flex-1 py-2 transition-colors text-muted-foreground"
|
||||
>
|
||||
<User className="size-5" />
|
||||
<span className="text-[10px] font-medium">Profile</span>
|
||||
</Link>
|
||||
)}
|
||||
size="sm"
|
||||
/>
|
||||
|
||||
</div>
|
||||
|
||||
{/* Apex Feed button — Agora bolt mark cradled in the V notch, with label below. */}
|
||||
<Link
|
||||
to={homePath}
|
||||
onClick={handleFeedClick}
|
||||
aria-label={homeItem?.label ?? 'Feed'}
|
||||
className={cn(
|
||||
'absolute left-1/2 -translate-x-1/2 z-10 -top-6',
|
||||
'flex flex-col items-center gap-3',
|
||||
'transition-transform hover:scale-105 active:scale-95',
|
||||
)}
|
||||
>
|
||||
<AgoraBoltIcon
|
||||
className={cn(
|
||||
'size-16 drop-shadow-md',
|
||||
isOnFeed && 'drop-shadow-[0_0_8px_hsl(var(--primary)/0.6)]',
|
||||
)}
|
||||
/>
|
||||
<span className={cn(
|
||||
'text-[10px] font-semibold leading-none',
|
||||
isOnFeed ? 'text-primary' : 'text-foreground',
|
||||
)}>
|
||||
{homeItem?.label ?? 'Feed'}
|
||||
</span>
|
||||
</Link>
|
||||
</div>
|
||||
{/* Safe area fill — matches the arc's semi-transparent background */}
|
||||
<div className="safe-area-bottom bg-background/85" />
|
||||
|
||||
@@ -11,7 +11,7 @@ interface MobileTopBarProps {
|
||||
hasSubHeader?: boolean;
|
||||
}
|
||||
|
||||
export function MobileTopBar({ onAvatarClick, hasSubHeader }: MobileTopBarProps) {
|
||||
export function MobileTopBar({ onAvatarClick, hasSubHeader: _hasSubHeader }: MobileTopBarProps) {
|
||||
const location = useLocation();
|
||||
const navHidden = useNavHidden();
|
||||
|
||||
@@ -34,7 +34,7 @@ export function MobileTopBar({ onAvatarClick, hasSubHeader }: MobileTopBarProps)
|
||||
/>
|
||||
{/* Relative wrapper so ArcBackground only covers the content area, not the safe-area padding above it. */}
|
||||
<div className="relative">
|
||||
<ArcBackground variant={hasSubHeader ? 'rect' : 'down'} />
|
||||
<ArcBackground variant="rect" />
|
||||
<div className="relative flex items-center px-3 h-10">
|
||||
{/* Left: hamburger menu icon */}
|
||||
<div className="flex items-center justify-center w-7 shrink-0">
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { useState, useRef, useEffect, useCallback } from 'react';
|
||||
import { ChevronLeft, ChevronRight } from 'lucide-react';
|
||||
import { cn } from '@/lib/utils';
|
||||
import { ArcBackground, ARC_OVERHANG_PX } from '@/components/ArcBackground';
|
||||
import { ArcBackground } from '@/components/ArcBackground';
|
||||
import { useNavHidden } from '@/contexts/LayoutContext';
|
||||
import { SubHeaderBarContext } from '@/components/SubHeaderBarContext';
|
||||
|
||||
@@ -30,7 +30,7 @@ interface SubHeaderBarProps {
|
||||
* Used by all tab bars (Feed, Search, Notifications, etc.) and the MobileTopBar
|
||||
* fallback arc.
|
||||
*/
|
||||
export function SubHeaderBar({ children, className, innerClassName, noArc, pinned }: SubHeaderBarProps) {
|
||||
export function SubHeaderBar({ children, className, innerClassName, noArc: _noArc, pinned }: SubHeaderBarProps) {
|
||||
const [hover, setHover] = useState<HoverSlice | null>(null);
|
||||
const [active, setActive] = useState<HoverSlice | null>(null);
|
||||
const navHidden = useNavHidden();
|
||||
@@ -125,38 +125,35 @@ export function SubHeaderBar({ children, className, innerClassName, noArc, pinne
|
||||
style={{ height: 'var(--safe-area-inset-top, env(safe-area-inset-top, 0px))' }}
|
||||
/>
|
||||
)}
|
||||
{/* Inner wrapper so ArcBackground covers only the tab area, not the safe-area padding above.
|
||||
sidebar:pt-2 adds desktop top padding inside the arc rather than outside it. */}
|
||||
<div className="relative sidebar:pt-2">
|
||||
<ArcBackground variant={noArc ? 'rect' : 'down'} />
|
||||
{/* Per-tab arc hover highlight: full-width arc, clipped to the hovered tab's x-slice */}
|
||||
{hover && !noArc && (
|
||||
{/* Inner wrapper holds the ArcBackground and tab content. */}
|
||||
<div className="relative">
|
||||
<ArcBackground variant="rect" />
|
||||
{/* Per-tab hover highlight: a flat-bottomed slab clipped to the hovered tab's x-slice */}
|
||||
{hover && (
|
||||
<svg
|
||||
aria-hidden
|
||||
className="absolute top-0 left-0 w-full pointer-events-none"
|
||||
className="absolute top-0 left-0 w-full h-full pointer-events-none"
|
||||
style={{
|
||||
height: `calc(100% + ${ARC_OVERHANG_PX}px)`,
|
||||
clipPath: `inset(0 calc(100% - ${hover.left + hover.width}px) 0 ${hover.left}px)`,
|
||||
}}
|
||||
viewBox="0 0 100 64"
|
||||
preserveAspectRatio="none"
|
||||
>
|
||||
<path d="M0,0 L100,0 L100,44 Q50,64 0,44 Z" className="fill-secondary/40" />
|
||||
<path d="M0,0 L100,0 L100,64 L0,64 Z" className="fill-secondary/40" />
|
||||
</svg>
|
||||
)}
|
||||
{/* Active tab indicator: the arc's bottom edge as a stroke, clipped to the active tab's x-slice */}
|
||||
{active && !noArc && (
|
||||
{/* Active tab indicator: a flat underline along the bottom edge, clipped to the active tab's x-slice */}
|
||||
{active && (
|
||||
<svg
|
||||
aria-hidden
|
||||
className="absolute top-0 left-0 w-full pointer-events-none"
|
||||
className="absolute top-0 left-0 w-full h-full pointer-events-none"
|
||||
style={{
|
||||
height: `calc(100% + ${ARC_OVERHANG_PX}px)`,
|
||||
clipPath: `inset(0 calc(100% - ${active.left + active.width}px) 0 ${active.left}px)`,
|
||||
}}
|
||||
viewBox="0 0 100 64"
|
||||
preserveAspectRatio="none"
|
||||
>
|
||||
<path d="M100,44 Q50,64 0,44" fill="none" className="stroke-primary" strokeWidth="3" />
|
||||
<path d="M0,62 L100,62" fill="none" className="stroke-primary" strokeWidth="3" vectorEffect="non-scaling-stroke" />
|
||||
</svg>
|
||||
)}
|
||||
{/* Tab content sits above the SVG background */}
|
||||
@@ -174,7 +171,7 @@ export function SubHeaderBar({ children, className, innerClassName, noArc, pinne
|
||||
)}
|
||||
<div
|
||||
ref={scrollRef}
|
||||
className={cn('relative flex overflow-x-auto scrollbar-none', innerClassName)}
|
||||
className={cn('relative flex overflow-x-auto scrollbar-none py-1', innerClassName)}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,110 @@
|
||||
import React from 'react';
|
||||
|
||||
/**
|
||||
* Agora lightning-bolt icon — a stylized double-bolt mark in primary brand orange.
|
||||
* The artwork uses fixed brand colors and gradients (it's a logo mark, not a
|
||||
* monochrome icon), so it ignores `currentColor`. Pass `className` to size it
|
||||
* (e.g. `size-6`).
|
||||
*/
|
||||
export const AgoraBoltIcon = React.forwardRef<SVGSVGElement, React.SVGProps<SVGSVGElement>>(
|
||||
({ className, ...props }, ref) => (
|
||||
<svg
|
||||
ref={ref}
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width={24}
|
||||
height={24}
|
||||
viewBox="0 0 720 880"
|
||||
fill="none"
|
||||
className={className}
|
||||
aria-hidden="true"
|
||||
{...props}
|
||||
>
|
||||
<g filter="url(#agora_bolt_inner_shadow)">
|
||||
<path
|
||||
d="M415.596 0L0 417.12L284.123 702.287L346.922 468.3H189.533L415.596 0Z"
|
||||
fill="url(#agora_bolt_grad_a)"
|
||||
fillOpacity="0.9"
|
||||
/>
|
||||
<path
|
||||
d="M415.596 0L0 417.12L284.123 702.287L346.922 468.3H189.533L415.596 0Z"
|
||||
fill="#FF6600"
|
||||
/>
|
||||
<path
|
||||
d="M431.879 127.936L363.762 381.328H527.876L258.808 880L720 417.114L431.879 127.936Z"
|
||||
fill="url(#agora_bolt_grad_b)"
|
||||
fillOpacity="0.9"
|
||||
/>
|
||||
<path
|
||||
d="M431.879 127.936L363.762 381.328H527.876L258.808 880L720 417.114L431.879 127.936Z"
|
||||
fill="#FF6600"
|
||||
/>
|
||||
</g>
|
||||
<defs>
|
||||
<filter
|
||||
id="agora_bolt_inner_shadow"
|
||||
x="0"
|
||||
y="0"
|
||||
width="720"
|
||||
height="914"
|
||||
filterUnits="userSpaceOnUse"
|
||||
colorInterpolationFilters="sRGB"
|
||||
>
|
||||
<feFlood floodOpacity="0" result="BackgroundImageFix" />
|
||||
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape" />
|
||||
<feColorMatrix
|
||||
in="SourceAlpha"
|
||||
type="matrix"
|
||||
values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0"
|
||||
result="hardAlpha"
|
||||
/>
|
||||
<feOffset dy="34" />
|
||||
<feGaussianBlur stdDeviation="27" />
|
||||
<feComposite in2="hardAlpha" operator="arithmetic" k2="-1" k3="1" />
|
||||
<feColorMatrix
|
||||
type="matrix"
|
||||
values="0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 0 0 0 0.25 0"
|
||||
/>
|
||||
<feBlend mode="normal" in2="shape" result="effect1_innerShadow" />
|
||||
<feColorMatrix
|
||||
in="SourceAlpha"
|
||||
type="matrix"
|
||||
values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0"
|
||||
result="hardAlpha"
|
||||
/>
|
||||
<feOffset dy="7" />
|
||||
<feGaussianBlur stdDeviation="0.5" />
|
||||
<feComposite in2="hardAlpha" operator="arithmetic" k2="-1" k3="1" />
|
||||
<feColorMatrix
|
||||
type="matrix"
|
||||
values="0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 0 0 0 0.25 0"
|
||||
/>
|
||||
<feBlend mode="normal" in2="effect1_innerShadow" result="effect2_innerShadow" />
|
||||
</filter>
|
||||
<linearGradient
|
||||
id="agora_bolt_grad_a"
|
||||
x1="-19.0481"
|
||||
y1="318.823"
|
||||
x2="373.469"
|
||||
y2="591.355"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
>
|
||||
<stop stopColor="white" />
|
||||
<stop offset="1" stopColor="white" stopOpacity="0.5" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
id="agora_bolt_grad_b"
|
||||
x1="346.531"
|
||||
y1="288.645"
|
||||
x2="739.048"
|
||||
y2="561.177"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
>
|
||||
<stop stopColor="white" />
|
||||
<stop offset="1" stopColor="white" stopOpacity="0.5" />
|
||||
</linearGradient>
|
||||
</defs>
|
||||
</svg>
|
||||
),
|
||||
);
|
||||
|
||||
AgoraBoltIcon.displayName = 'AgoraBoltIcon';
|
||||
+1
-9
@@ -1,7 +1,5 @@
|
||||
import { useSeoMeta } from '@unhead/react';
|
||||
import { Megaphone } from 'lucide-react';
|
||||
import { Feed } from '@/components/Feed';
|
||||
import { PageHeader } from '@/components/PageHeader';
|
||||
import { useAppContext } from '@/hooks/useAppContext';
|
||||
import { useCurrentUser } from '@/hooks/useCurrentUser';
|
||||
import { useLayoutOptions } from '@/contexts/LayoutContext';
|
||||
@@ -17,13 +15,7 @@ const Index = () => {
|
||||
|
||||
useLayoutOptions({ showFAB: true, fabKind: 1, hasSubHeader: !!user });
|
||||
|
||||
return (
|
||||
<Feed
|
||||
header={(
|
||||
<PageHeader title="Feed" icon={<Megaphone className="size-5 text-primary" />} />
|
||||
)}
|
||||
/>
|
||||
);
|
||||
return <Feed />;
|
||||
};
|
||||
|
||||
export default Index;
|
||||
|
||||
Reference in New Issue
Block a user