diff --git a/app/admin/stats/page.tsx b/app/admin/stats/page.tsx index aedad56c..55adaef4 100644 --- a/app/admin/stats/page.tsx +++ b/app/admin/stats/page.tsx @@ -2,10 +2,16 @@ import { useState, useEffect } from 'react'; import { Button } from '@/components/ui/button'; -import { Dropdown, DropdownTrigger, DropdownMenu, DropdownItem } from '@heroui/react'; import Image from 'next/image'; import ButtonGroup from '@/components/ui/button-group'; import { Spinner } from '@/components/ui/spinner'; +import { + DropdownMenu, + DropdownMenuTrigger, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuCheckboxItem, +} from '@/components/ui/dropdown-menu'; import { TokenIcon } from '@/components/shared/token-icon'; import { useMarkets } from '@/contexts/MarketsContext'; import { fetchAllStatistics } from '@/services/statsService'; @@ -179,8 +185,8 @@ export default function StatsPage() {

Platform Statistics

{/* Network selector */} - - + + - - setSelectedNetwork(Number(key) as SupportedNetworks)} - className="font-zen" - > - + + setSelectedNetwork(SupportedNetworks.Base)} startContent={ getNetworkImg(SupportedNetworks.Base) && ( {baseNetworkName} - - + setSelectedNetwork(SupportedNetworks.Mainnet)} startContent={ getNetworkImg(SupportedNetworks.Mainnet) && ( {mainnetNetworkName} - - - + + + {/* Timeframe selector */} {/* Loan Asset Filter */} - - + + - - { - const selected = Array.from(keys) as string[]; - setSelectedLoanAssets(selected); - }} - className="font-zen" - > + + {uniqueLoanAssets.map((asset) => { const assetKey = asset.networks.map((n) => `${n.address}-${n.chain.id}`).join('|'); const firstNetwork = asset.networks[0]; return ( - { + if (checked) { + setSelectedLoanAssets([...selectedLoanAssets, assetKey]); + } else { + setSelectedLoanAssets(selectedLoanAssets.filter((k) => k !== assetKey)); + } + }} className="py-2" startContent={ {asset.symbol} - + ); })} - - + + {/* Side Filter */} - - + + - - { - const selected = Array.from(keys) as ('Supply' | 'Withdraw')[]; - setSelectedSides(selected); - }} - className="font-zen" - > - + + { + if (checked) { + setSelectedSides([...selectedSides, 'Supply']); + } else { + setSelectedSides(selectedSides.filter((s) => s !== 'Supply')); + } + }} className="py-2" > Supply - - + { + if (checked) { + setSelectedSides([...selectedSides, 'Withdraw']); + } else { + setSelectedSides(selectedSides.filter((s) => s !== 'Withdraw')); + } + }} className="py-2" > Withdraw - - - + + +
+ + + + + Action + + + +// With icons +}>Supply +}>Explorer + +// Sections + + +// Multi-select + + Item + + +// Prevent auto-close (for switches) + e.preventDefault()}> + {/* custom content */} + +``` + +Alignment: `align="start"` | `"center"` | `"end"` + ## Background, Border - Use `bg-surface` first layer components @@ -461,7 +508,6 @@ import { TablePagination } from '@/components/common/TablePagination'; - Jump-to-page search icon (appears when >10 pages) - Optional entry count display ("Showing X-Y of Z entries") - Loading states with disabled buttons -- Rounded-md styling with bg-surface - Tighter spacing (gap-2) when used in layouts **Props:** diff --git a/package.json b/package.json index 464f1aec..e639077f 100644 --- a/package.json +++ b/package.json @@ -20,7 +20,6 @@ }, "dependencies": { "@heroicons/react": "^2.2.0", - "@heroui/accordion": "^2.0.35", "@heroui/input": "^2.2.2", "@heroui/react": "^2.4.2", "@heroui/system": "^2.2.2", @@ -30,7 +29,7 @@ "@merkl/api": "^1.7.0", "@morpho-org/blue-sdk": "^5.3.0", "@radix-ui/react-checkbox": "^1.3.3", - "@radix-ui/react-dropdown-menu": "^2.0.6", + "@radix-ui/react-dropdown-menu": "^2.1.16", "@radix-ui/react-icons": "^1.3.0", "@radix-ui/react-navigation-menu": "^1.1.4", "@radix-ui/react-slot": "^1.2.4", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 8f2606ab..071684a1 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -11,9 +11,6 @@ importers: '@heroicons/react': specifier: ^2.2.0 version: 2.2.0(react@18.3.1) - '@heroui/accordion': - specifier: ^2.0.35 - version: 2.2.21(@heroui/system@2.4.20(@heroui/theme@2.4.20(tailwindcss@4.1.17))(framer-motion@11.18.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@heroui/theme@2.4.20(tailwindcss@4.1.17))(framer-motion@11.18.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@heroui/input': specifier: ^2.2.2 version: 2.4.25(@heroui/system@2.4.20(@heroui/theme@2.4.20(tailwindcss@4.1.17))(framer-motion@11.18.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@heroui/theme@2.4.20(tailwindcss@4.1.17))(@types/react@18.3.23)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) @@ -42,7 +39,7 @@ importers: specifier: ^1.3.3 version: 1.3.3(@types/react-dom@18.3.7(@types/react@18.3.23))(@types/react@18.3.23)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@radix-ui/react-dropdown-menu': - specifier: ^2.0.6 + specifier: ^2.1.16 version: 2.1.16(@types/react-dom@18.3.7(@types/react@18.3.23))(@types/react@18.3.23)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@radix-ui/react-icons': specifier: ^1.3.0 diff --git a/src/components/layout/header/AccountDropdown.tsx b/src/components/layout/header/AccountDropdown.tsx index 29ec3d02..3e0854dd 100644 --- a/src/components/layout/header/AccountDropdown.tsx +++ b/src/components/layout/header/AccountDropdown.tsx @@ -1,13 +1,13 @@ 'use client'; import { useCallback } from 'react'; -import { Dropdown, DropdownTrigger, DropdownMenu, DropdownItem } from '@heroui/react'; import { ExitIcon, ExternalLinkIcon, CopyIcon } from '@radix-ui/react-icons'; import { clsx } from 'clsx'; import { useConnection, useDisconnect } from 'wagmi'; import { useAppKit } from '@reown/appkit/react'; import { Avatar } from '@/components/Avatar/Avatar'; import { AccountIdentity } from '@/components/shared/account-identity'; +import { DropdownMenu, DropdownMenuTrigger, DropdownMenuContent, DropdownMenuItem } from '@/components/ui/dropdown-menu'; import { useStyledToast } from '@/hooks/useStyledToast'; import { getExplorerURL } from '@/utils/external'; @@ -36,8 +36,8 @@ export function AccountDropdown() { if (!address) return null; return ( - - + + - - - + +
-
+ - } > Copy Address - + - } onClick={() => window.open(getExplorerURL(address, chainId ?? 1), '_blank')} > View on Explorer - + - } - className="text-red-500 data-[hover=true]:text-red-500" + className="text-red-500 hover:text-red-500 focus:text-red-500" > Log out - -
-
+ + + ); } diff --git a/src/components/layout/header/Navbar.tsx b/src/components/layout/header/Navbar.tsx index 5e77c8f5..7a3b182d 100644 --- a/src/components/layout/header/Navbar.tsx +++ b/src/components/layout/header/Navbar.tsx @@ -1,7 +1,6 @@ 'use client'; import { useEffect, useState } from 'react'; -import { Dropdown, DropdownTrigger, DropdownMenu, DropdownItem } from '@heroui/react'; import { ChevronDownIcon } from '@radix-ui/react-icons'; import { clsx } from 'clsx'; import Image from 'next/image'; @@ -13,6 +12,7 @@ import { FiSettings } from 'react-icons/fi'; import { LuSunMedium } from 'react-icons/lu'; import { RiBookLine, RiDiscordFill, RiGithubFill } from 'react-icons/ri'; import { useConnection } from 'wagmi'; +import { DropdownMenu, DropdownMenuTrigger, DropdownMenuContent, DropdownMenuItem } from '@/components/ui/dropdown-menu'; import { EXTERNAL_LINKS } from '@/utils/external'; import logo from '../../imgs/logo.png'; import AccountConnect from './AccountConnect'; @@ -117,12 +117,8 @@ export function Navbar() { )} - - + + - - + - } onClick={() => window.open(EXTERNAL_LINKS.docs, '_blank')} > Docs - - + } onClick={() => window.open(EXTERNAL_LINKS.discord, '_blank')} > Discord - - + } onClick={() => window.open(EXTERNAL_LINKS.github, '_blank')} > GitHub - - + : )} onClick={toggleTheme} > {theme === 'dark' ? 'Light Theme' : 'Dark Theme'} - - + } onClick={() => router.push('/settings')} > Settings - - - + + +
diff --git a/src/components/layout/header/NavbarMobile.tsx b/src/components/layout/header/NavbarMobile.tsx index 95fb2e31..a0742acd 100644 --- a/src/components/layout/header/NavbarMobile.tsx +++ b/src/components/layout/header/NavbarMobile.tsx @@ -1,7 +1,6 @@ 'use client'; import { useEffect, useState } from 'react'; -import { Dropdown, DropdownTrigger, DropdownMenu, DropdownItem, DropdownSection } from '@heroui/react'; import { HamburgerMenuIcon } from '@radix-ui/react-icons'; import { clsx } from 'clsx'; import Image from 'next/image'; @@ -13,6 +12,13 @@ import { FiSettings } from 'react-icons/fi'; import { LuSunMedium } from 'react-icons/lu'; import { RiBookLine, RiDiscordFill, RiGithubFill, RiLineChartLine, RiBriefcaseLine, RiGiftLine } from 'react-icons/ri'; import { useConnection } from 'wagmi'; +import { + DropdownMenu, + DropdownMenuTrigger, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuSeparator, +} from '@/components/ui/dropdown-menu'; import { EXTERNAL_LINKS } from '@/utils/external'; import logo from '../../imgs/logo.png'; import AccountConnect from './AccountConnect'; @@ -57,13 +63,11 @@ export default function NavbarMobile() { /> - - + - - + - - } - onClick={() => handleNavigation('/markets')} - className="py-3" - > - Markets - - } - onClick={() => handleNavigation(address ? `/positions/${address}` : '/positions')} - className="py-3" - > - Portfolio - - } - onClick={() => handleNavigation(address ? `/rewards/${address}` : '/rewards')} - className="py-3" - > - Rewards - - - - } - onClick={() => handleExternalLink(EXTERNAL_LINKS.docs)} - > - Docs - - } - onClick={() => handleExternalLink(EXTERNAL_LINKS.discord)} - > - Discord - - } - onClick={() => handleExternalLink(EXTERNAL_LINKS.github)} - > - GitHub - - : )} - onClick={toggleTheme} - > - {mounted && (theme === 'dark' ? 'Light Theme' : 'Dark Theme')} - - } - onClick={() => handleNavigation('/settings')} - > - Settings - - - - + } + onClick={() => handleNavigation('/markets')} + className="py-3" + > + Markets + + } + onClick={() => handleNavigation(address ? `/positions/${address}` : '/positions')} + className="py-3" + > + Portfolio + + } + onClick={() => handleNavigation(address ? `/rewards/${address}` : '/rewards')} + className="py-3" + > + Rewards + + + + + } + onClick={() => handleExternalLink(EXTERNAL_LINKS.docs)} + > + Docs + + } + onClick={() => handleExternalLink(EXTERNAL_LINKS.discord)} + > + Discord + + } + onClick={() => handleExternalLink(EXTERNAL_LINKS.github)} + > + GitHub + + : )} + onClick={toggleTheme} + > + {mounted && (theme === 'dark' ? 'Light Theme' : 'Dark Theme')} + + } + onClick={() => handleNavigation('/settings')} + > + Settings + + +
diff --git a/src/components/ui/dropdown-menu.tsx b/src/components/ui/dropdown-menu.tsx new file mode 100644 index 00000000..466432cc --- /dev/null +++ b/src/components/ui/dropdown-menu.tsx @@ -0,0 +1,235 @@ +import * as React from "react" +import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu" +import { FaCheck } from "react-icons/fa6"; +import { GoChevronRight, GoCircle } from "react-icons/go"; + +import { cn } from "@/utils/components" + +// Wrapper to set modal={false} by default to prevent page shifts from scrollbar +const DropdownMenu = ({ modal = false, ...props }: React.ComponentProps) => ( + +) + +const DropdownMenuTrigger = DropdownMenuPrimitive.Trigger + +const DropdownMenuGroup = DropdownMenuPrimitive.Group + +const DropdownMenuPortal = DropdownMenuPrimitive.Portal + +const DropdownMenuSub = DropdownMenuPrimitive.Sub + +const DropdownMenuRadioGroup = DropdownMenuPrimitive.RadioGroup + +const DropdownMenuSubTrigger = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef & { + inset?: boolean + } +>(({ className, inset, children, ...props }, ref) => ( + + {children} + + +)) +DropdownMenuSubTrigger.displayName = + DropdownMenuPrimitive.SubTrigger.displayName + +const DropdownMenuSubContent = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +DropdownMenuSubContent.displayName = + DropdownMenuPrimitive.SubContent.displayName + +const DropdownMenuContent = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, sideOffset = 4, ...props }, ref) => ( + + + +)) +DropdownMenuContent.displayName = DropdownMenuPrimitive.Content.displayName + +const DropdownMenuItem = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef & { + inset?: boolean + startContent?: React.ReactNode + endContent?: React.ReactNode + } +>(({ className, inset, startContent, endContent, children, ...props }, ref) => ( + svg]:size-4 [&>svg]:shrink-0", + inset && "pl-8", + className + )} + {...props} + > + {startContent && {startContent}} + {children} + {endContent && {endContent}} + +)) +DropdownMenuItem.displayName = DropdownMenuPrimitive.Item.displayName + +const DropdownMenuCheckboxItem = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef & { + startContent?: React.ReactNode + endContent?: React.ReactNode + } +>(({ className, children, checked, startContent, endContent, ...props }, ref) => ( + + {!startContent && ( + + + + + + )} + {startContent && {startContent}} + {children} + {endContent && {endContent}} + +)) +DropdownMenuCheckboxItem.displayName = + DropdownMenuPrimitive.CheckboxItem.displayName + +const DropdownMenuRadioItem = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef & { + startContent?: React.ReactNode + endContent?: React.ReactNode + } +>(({ className, children, startContent, endContent, ...props }, ref) => ( + + {!startContent && ( + + + + + + )} + {startContent && {startContent}} + {children} + {endContent && {endContent}} + +)) +DropdownMenuRadioItem.displayName = DropdownMenuPrimitive.RadioItem.displayName + +const DropdownMenuLabel = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef & { + inset?: boolean + } +>(({ className, inset, ...props }, ref) => ( + +)) +DropdownMenuLabel.displayName = DropdownMenuPrimitive.Label.displayName + +const DropdownMenuSeparator = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +DropdownMenuSeparator.displayName = DropdownMenuPrimitive.Separator.displayName + +const DropdownMenuShortcut = ({ + className, + ...props +}: React.HTMLAttributes) => { + return ( + + ) +} +DropdownMenuShortcut.displayName = "DropdownMenuShortcut" + +export { + DropdownMenu, + DropdownMenuTrigger, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuCheckboxItem, + DropdownMenuRadioItem, + DropdownMenuLabel, + DropdownMenuSeparator, + DropdownMenuShortcut, + DropdownMenuGroup, + DropdownMenuPortal, + DropdownMenuSub, + DropdownMenuSubContent, + DropdownMenuSubTrigger, + DropdownMenuRadioGroup, +} diff --git a/src/features/markets/components/market-actions-dropdown.tsx b/src/features/markets/components/market-actions-dropdown.tsx index b5f1273f..5298b970 100644 --- a/src/features/markets/components/market-actions-dropdown.tsx +++ b/src/features/markets/components/market-actions-dropdown.tsx @@ -2,12 +2,12 @@ import type React from 'react'; import { useState } from 'react'; -import { Dropdown, DropdownTrigger, DropdownMenu, DropdownItem } from '@heroui/react'; import { AiOutlineStop } from 'react-icons/ai'; import { GoStarFill, GoStar, GoGraph } from 'react-icons/go'; import { IoEllipsisVertical } from 'react-icons/io5'; import { TbArrowUp } from 'react-icons/tb'; import { Button } from '@/components/ui/button'; +import { DropdownMenu, DropdownMenuTrigger, DropdownMenuContent, DropdownMenuItem } from '@/components/ui/dropdown-menu'; import type { Market } from '@/utils/types'; import { BlacklistConfirmationModal } from './blacklist-confirmation-modal'; @@ -64,8 +64,8 @@ export function MarketActionsDropdown({ role="button" tabIndex={-1} > - - + + - - - + + { setSelectedMarket(market); setShowSupplyModal(true); @@ -91,20 +83,18 @@ export function MarketActionsDropdown({ startContent={} > Supply - + - { onMarketClick(market); }} startContent={} > View Market - + - { if (isStared) { unstarMarket(market.uniqueKey); @@ -115,19 +105,18 @@ export function MarketActionsDropdown({ startContent={isStared ? : } > {isStared ? 'Unstar' : 'Star'} - + - } className={isBlacklisted?.(market.uniqueKey) || !addBlacklistedMarket ? 'opacity-50 cursor-not-allowed' : ''} - isDisabled={isBlacklisted?.(market.uniqueKey) || !addBlacklistedMarket} + disabled={isBlacklisted?.(market.uniqueKey) || !addBlacklistedMarket} > {isBlacklisted?.(market.uniqueKey) ? 'Blacklisted' : 'Blacklist'} - - - + + +
- - + + - - setEarningsPeriod(key as EarningsPeriod)} - > + + {Object.entries(periodLabels).map(([period, label]) => ( - {label} + setEarningsPeriod(period as EarningsPeriod)} + > + {label} + ))} - - + + - - + + - - - + + e.preventDefault()} >
Show Empty Positions @@ -263,10 +261,10 @@ export function PositionsSummaryTable({ }} />
-
- + e.preventDefault()} >
Show Collateral Exposure @@ -281,9 +279,9 @@ export function PositionsSummaryTable({ }} />
-
-
-
+ + +