From edb1f4d8da7bd9353eff38a8e4cd4fa9393488fa Mon Sep 17 00:00:00 2001 From: antoncoding Date: Sat, 20 Dec 2025 11:31:51 +0800 Subject: [PATCH 1/5] feat: checkbox --- package.json | 2 +- pnpm-lock.yaml | 35 ++++++++- src/components/shared/table-pagination.tsx | 2 +- src/components/ui/button.tsx | 2 +- src/components/ui/checkbox.tsx | 71 +++++++++++++++++++ src/components/ui/table.tsx | 2 +- src/components/ui/tabs.tsx | 2 +- .../deployment/deployment-modal.tsx | 20 ++---- .../components/markets-table-same-loan.tsx | 14 ++-- src/lib/utils.ts | 6 -- src/modals/risk-notification-modal.tsx | 18 ++--- src/utils/index.ts | 1 + 12 files changed, 130 insertions(+), 45 deletions(-) create mode 100644 src/components/ui/checkbox.tsx delete mode 100644 src/lib/utils.ts create mode 100644 src/utils/index.ts diff --git a/package.json b/package.json index 24811f9f..464f1aec 100644 --- a/package.json +++ b/package.json @@ -21,7 +21,6 @@ "dependencies": { "@heroicons/react": "^2.2.0", "@heroui/accordion": "^2.0.35", - "@heroui/checkbox": "^2.1.2", "@heroui/input": "^2.2.2", "@heroui/react": "^2.4.2", "@heroui/system": "^2.2.2", @@ -30,6 +29,7 @@ "@internationalized/date": "^3.8.2", "@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-icons": "^1.3.0", "@radix-ui/react-navigation-menu": "^1.1.4", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 8d36b41e..8f2606ab 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -14,9 +14,6 @@ importers: '@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/checkbox': - specifier: ^2.1.2 - version: 2.3.24(@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))(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) @@ -41,6 +38,9 @@ importers: '@morpho-org/blue-sdk': specifier: ^5.3.0 version: 5.3.0(@morpho-org/morpho-ts@2.4.4) + '@radix-ui/react-checkbox': + 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 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) @@ -2257,6 +2257,19 @@ packages: '@types/react-dom': optional: true + '@radix-ui/react-checkbox@1.3.3': + resolution: {integrity: sha512-wBbpv+NQftHDdG86Qc0pIyXk5IR3tM8Vd0nWLKDcX8nNn4nXFOFwsKuqw2okA/1D/mpaAkmuyndrPJTYDNZtFw==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + '@radix-ui/react-collection@1.1.7': resolution: {integrity: sha512-Fh9rGN0MoI4ZFUNyfFVNU4y9LUz93u9/0K+yLgA2bwRojxM8JU1DyvvMBabnZPBgMWREAJvU2jjVzq+LrFUglw==} peerDependencies: @@ -9292,6 +9305,22 @@ snapshots: '@types/react': 18.3.23 '@types/react-dom': 18.3.7(@types/react@18.3.23) + '@radix-ui/react-checkbox@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)': + dependencies: + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-compose-refs': 1.1.2(@types/react@18.3.23)(react@18.3.1) + '@radix-ui/react-context': 1.1.2(@types/react@18.3.23)(react@18.3.1) + '@radix-ui/react-presence': 1.1.5(@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-primitive': 2.1.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-use-controllable-state': 1.2.2(@types/react@18.3.23)(react@18.3.1) + '@radix-ui/react-use-previous': 1.1.1(@types/react@18.3.23)(react@18.3.1) + '@radix-ui/react-use-size': 1.1.1(@types/react@18.3.23)(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + optionalDependencies: + '@types/react': 18.3.23 + '@types/react-dom': 18.3.7(@types/react@18.3.23) + '@radix-ui/react-collection@1.1.7(@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)': dependencies: '@radix-ui/react-compose-refs': 1.1.2(@types/react@18.3.23)(react@18.3.1) diff --git a/src/components/shared/table-pagination.tsx b/src/components/shared/table-pagination.tsx index d9548e41..806f7896 100644 --- a/src/components/shared/table-pagination.tsx +++ b/src/components/shared/table-pagination.tsx @@ -3,7 +3,7 @@ import { Input, Popover, PopoverTrigger, PopoverContent, Tooltip } from '@heroui import { ChevronLeftIcon, ChevronRightIcon, MagnifyingGlassIcon } from '@radix-ui/react-icons'; import { TooltipContent } from '@/components/shared/tooltip-content'; import { Button } from '@/components/ui/button'; -import { cn } from '@/lib/utils'; +import { cn } from '@/utils'; type TablePaginationProps = { currentPage: number; diff --git a/src/components/ui/button.tsx b/src/components/ui/button.tsx index c196cec6..f109decc 100644 --- a/src/components/ui/button.tsx +++ b/src/components/ui/button.tsx @@ -2,7 +2,7 @@ import { forwardRef } from 'react'; import { Slot } from '@radix-ui/react-slot'; import { cva, type VariantProps } from 'class-variance-authority'; -import { cn } from '@/lib/utils'; +import { cn } from '@/utils'; const buttonVariants = cva( 'inline-flex items-center justify-center gap-2 whitespace-nowrap text-sm font-medium transition-all duration-200 ease-in-out border-0 outline-0 ring-0 focus:border-0 focus:outline-0 focus:ring-0 active:border-0 active:outline-0 active:ring-0 disabled:pointer-events-none disabled:opacity-50 disabled:cursor-not-allowed cursor-pointer [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0', diff --git a/src/components/ui/checkbox.tsx b/src/components/ui/checkbox.tsx new file mode 100644 index 00000000..5d395273 --- /dev/null +++ b/src/components/ui/checkbox.tsx @@ -0,0 +1,71 @@ +import * as React from "react" +import * as CheckboxPrimitive from "@radix-ui/react-checkbox" + +import { cn } from "@/utils/components" +import { FaCheck } from "react-icons/fa6"; +type CheckboxVariant = "default" | "highlighted" + +type CheckboxProps = React.ComponentPropsWithoutRef & { + variant?: CheckboxVariant + label?: React.ReactNode +} + +const Checkbox = React.forwardRef< + React.ElementRef, + CheckboxProps +>(({ className, variant = "default", label, id, ...props }, ref) => { + // Auto-generate ID if label is provided but no ID is given + const checkboxId = id ?? (label ? React.useId() : undefined) + + const checkbox = ( + + + + + + ) + + // If no label, return just the checkbox + if (!label) { + return checkbox + } + + // Highlighted variant with label + if (variant === "highlighted") { + return ( + + ) + } + + // Default variant with label + return ( +
+ {checkbox} + +
+ ) +}) +Checkbox.displayName = "Checkbox" + +export { Checkbox } diff --git a/src/components/ui/table.tsx b/src/components/ui/table.tsx index fa298b74..04aa8ca4 100644 --- a/src/components/ui/table.tsx +++ b/src/components/ui/table.tsx @@ -1,7 +1,7 @@ import { forwardRef } from 'react'; import type { HTMLAttributes, ThHTMLAttributes, TdHTMLAttributes } from 'react'; -import { cn } from '@/lib/utils'; +import { cn } from '@/utils'; const Table = forwardRef>(({ className, ...props }, ref) => ( - - - I understand I already deployed an autovault for this token on {getNetworkName(selectedTokenAndNetwork.networkId)}. - - - + setAckExistingVault(checked === true)} + /> )}
diff --git a/src/features/markets/components/markets-table-same-loan.tsx b/src/features/markets/components/markets-table-same-loan.tsx index 347abae6..740ac0fa 100644 --- a/src/features/markets/components/markets-table-same-loan.tsx +++ b/src/features/markets/components/markets-table-same-loan.tsx @@ -1,5 +1,6 @@ import React, { useMemo, useState, useRef, useEffect, useCallback } from 'react'; -import { Checkbox, Input } from '@heroui/react'; +import { Input } from '@heroui/react'; +import { Checkbox } from '@/components/ui/checkbox'; import { ArrowDownIcon, ArrowUpIcon, ChevronDownIcon, TrashIcon, GearIcon } from '@radix-ui/react-icons'; import { motion, AnimatePresence } from 'framer-motion'; import Image from 'next/image'; @@ -487,14 +488,13 @@ function MarketRow({ > {showSelectColumn && (
diff --git a/src/lib/utils.ts b/src/lib/utils.ts deleted file mode 100644 index 9ad0df42..00000000 --- a/src/lib/utils.ts +++ /dev/null @@ -1,6 +0,0 @@ -import { type ClassValue, clsx } from 'clsx'; -import { twMerge } from 'tailwind-merge'; - -export function cn(...inputs: ClassValue[]) { - return twMerge(clsx(inputs)); -} diff --git a/src/modals/risk-notification-modal.tsx b/src/modals/risk-notification-modal.tsx index 351c2918..60e589af 100644 --- a/src/modals/risk-notification-modal.tsx +++ b/src/modals/risk-notification-modal.tsx @@ -1,7 +1,7 @@ 'use client'; import { useState, useEffect } from 'react'; -import { Checkbox } from '@heroui/react'; +import { Checkbox } from '@/components/ui/checkbox'; import { Button } from '@/components/ui/button'; import { usePathname } from 'next/navigation'; import { PiButterflyDuotone } from 'react-icons/pi'; @@ -59,16 +59,12 @@ export default function RiskNotificationModal() { This flexibility comes with the responsibility to actively manage your positions, monitor market conditions, and make informed decisions about rebalancing.

-
- - I understand that direct lending requires active management - -
+ setIsChecked(checked === true)} + /> ); diff --git a/src/features/autovault/components/vault-detail/vault-market-allocations.tsx b/src/features/autovault/components/vault-detail/vault-market-allocations.tsx index 4f828648..4fadf85b 100644 --- a/src/features/autovault/components/vault-detail/vault-market-allocations.tsx +++ b/src/features/autovault/components/vault-detail/vault-market-allocations.tsx @@ -1,5 +1,5 @@ import { useMemo, useState } from 'react'; -import { Switch } from '@heroui/react'; +import { IconSwitch } from '@/components/ui/icon-switch'; import { HiOutlineCube } from 'react-icons/hi'; import { MdOutlineAccountBalance } from 'react-icons/md'; import { Spinner } from '@/components/ui/spinner'; @@ -20,7 +20,7 @@ type VaultMarketAllocationsProps = { type ViewMode = 'collateral' | 'market'; -function ViewIcon({ isSelected, className }: { isSelected: boolean; className?: string }) { +function ViewIcon({ isSelected, className }: { isSelected?: boolean; className?: string }) { return isSelected ? : ; } @@ -79,7 +79,7 @@ export function VaultMarketAllocations({

{hasAnyAllocations ? 'Active Allocations' : 'Market Configuration'}

{viewMode === 'collateral' ? 'By Collateral' : 'By Market'} - { + const supply = market.state.supplyAssets ? BigInt(market.state.supplyAssets) : 0n; + const borrow = market.state.borrowAssets ? BigInt(market.state.borrowAssets) : 0n; + + const targetBorrow = supply * 9n / 10n; // target u is always 90% + return borrow - targetBorrow; + }, []) + return ( @@ -318,6 +327,14 @@ function VolumeChart({
); })} + + {/* Delta to target Utilization */} +
+ Delta to Target: + + {formatValue(Number(formatUnits(deltaToTargetUtilization, market.loanAsset.decimals)))} + +
diff --git a/src/features/market-detail/components/position-stats.tsx b/src/features/market-detail/components/position-stats.tsx index 769f1349..1b19bbd8 100644 --- a/src/features/market-detail/components/position-stats.tsx +++ b/src/features/market-detail/components/position-stats.tsx @@ -1,6 +1,6 @@ import { useState } from 'react'; import { Card } from '@heroui/react'; -import { Switch } from '@heroui/react'; +import { IconSwitch } from '@/components/ui/icon-switch'; import { ReloadIcon } from '@radix-ui/react-icons'; import { FiUser } from 'react-icons/fi'; import { HiOutlineGlobeAsiaAustralia } from 'react-icons/hi2'; @@ -24,7 +24,7 @@ type PositionStatsProps = { isRefreshing?: boolean; }; -function ThumbIcon({ isSelected, className }: { isSelected: boolean; className?: string }) { +function ThumbIcon({ isSelected, className }: { isSelected?: boolean; className?: string }) { return isSelected ? : ; } @@ -207,9 +207,9 @@ export function PositionStats({ market, userPosition, positionLoading, cardStyle )} -
Show Empty Positions -
Show Collateral Exposure -
Use {getNativeTokenSymbol(market.morphoBlue.chain.id)} instead
-
Use {getNativeTokenSymbol(market.morphoBlue.chain.id)} instead
- Date: Sat, 20 Dec 2025 12:08:52 +0800 Subject: [PATCH 3/5] docs: cleanup styling --- docs/Styling.md | 464 ++++++++++++------------------------------------ 1 file changed, 109 insertions(+), 355 deletions(-) diff --git a/docs/Styling.md b/docs/Styling.md index fd9f4e04..a3bc7a03 100644 --- a/docs/Styling.md +++ b/docs/Styling.md @@ -275,121 +275,86 @@ For custom modals using `framer-motion`, apply `font-zen` to the outer container - Tags/badges - Helper text containers -### Button Component +### Button -```typescript -import { Button } from '@/components/ui/button'; -``` +Always use `Button` from `@/components/ui/button`. -#### Button Variants +```tsx +import { Button } from '@/components/ui/button'; -Our button system uses 4 simple, purpose-driven variants: +// Primary - main actions + -**default** - For buttons on background areas -- Uses `bg-surface` color -- Hover: Darkens slightly and increases opacity -- Use for: Navigation buttons, actions on main background -- Example: "Back to Markets", top-level page actions +// Surface - actions in cards, tables + -**primary** - For important actions -- Uses `bg-primary` color (primary theme color, NOT orange) -- Hover: Increases shadow and opacity, slight scale down on click -- Use for: Main CTAs, confirmations, primary flows -- Example: "New Position", "Execute Rebalance", "Supply", "Withdraw" +// Default - navigation + -**surface** - For buttons on surface-colored backgrounds -- Uses `bg-hovered` color - Subtle, doesn't stand out too much -- Hover: `bg-default-200`, Active: `bg-default-300` (gentle color progression) -- Use for: Actions in cards, modals, table rows -- Example: "Claim" button in tables, dropdown triggers, actions in cards +// Ghost - icon buttons + +``` -**ghost** - For icon buttons and minimal actions -- Transparent background with responsive hover states -- Icon buttons: Scale up slightly on hover, visible background feedback -- Size-specific hover styles for optimal feedback -- Use for: Icon-only buttons, utility actions, settings -- Example: Refresh icons, settings icons, filter toggles +Variants: `primary` | `surface` | `default` | `ghost` +Sizes: `xs` | `sm` | `md` | `lg` | `icon` -#### Examples +When wrapping Button in HeroUI Tooltip, wrap it in a `` to prevent ResizeObserver errors. -```tsx -// Primary - Important action - - -// Surface - Action in a table/card (subtle) - - -// Default - Navigation on background - - -// Ghost - Icon-only button with tooltip (always wrap in span for HeroUI Tooltip) -} -> - - - - -``` +### Switch -#### Button Sizes +Always use `IconSwitch` from `@/components/ui/icon-switch`. Never use HeroUI Switch. -- `xs`: Extra small (h-6, 40px min-width) - Rare use -- `sm`: Small (h-8, 64px min-width) - Common for compact actions -- `md`/`default`: Medium (h-10, 80px min-width) - Standard size -- `lg`: Large (h-12, 96px min-width) - Important CTAs -- `icon`: Icon-only (h-8 w-8, 14px icons) - Use for icon buttons +```tsx +import { IconSwitch } from '@/components/ui/icon-switch'; + +// Plain switch (no icon) + -#### Button Hover Effects +// With icon +function ViewIcon({ isSelected, className }: { isSelected?: boolean; className?: string }) { + return isSelected ? : ; +} -All buttons have subtle hover refinements: -- **Opacity**: Buttons start at 95% opacity and become 100% on hover for a refined look -- **Color transitions**: Smooth 200ms transitions for all state changes -- **Scale feedback**: Active state provides tactile press feedback -- **Ghost buttons**: Size-specific hover behaviors for optimal UX - - Icon size: Scales to 105% on hover, darker background - - Small size: Scales to 102% on hover, medium background - - Medium/Large: Scales to 101% on hover, lighter background + +``` -**Important Note**: When wrapping Button in HeroUI Tooltip, always wrap the Button in a `` to prevent ResizeObserver errors. +Sizes: `xs` | `sm` | `md` | `lg` -### Toggle Controls +### Checkbox -- Always use the shared `IconSwitch` component (`@/components/common/IconSwitch`) for boolean toggles so dimensions, motion, and iconography stay consistent across pages. -- Prefer the `xs` size inside dense settings groups (e.g., Market Settings, global Settings). Pair the switch with a left-aligned label and secondary description beneath it to mirror existing sections. -- Example: +Always use `Checkbox` from `@/components/ui/checkbox`. Never use HeroUI Checkbox. ```tsx -import { IconSwitch } from '@/components/common/IconSwitch'; +import { Checkbox } from '@/components/ui/checkbox'; -
-
-

Show Unknown Tokens

-

Display assets flagged as unverified.

-
- -
+// Default + + +// Highlighted + ``` +Variants: `default` | `highlighted` + ## Background, Border - Use `bg-surface` first layer components @@ -397,69 +362,37 @@ import { IconSwitch } from '@/components/common/IconSwitch'; ## Tooltip -Use the `TooltipContent` component for consistent tooltip styling. The component supports two modes: - -### Simple Tooltip (no detail) -Shows icon, title, and optional action link on the right: +Use `TooltipContent` from `@/components/shared/tooltip-content` for consistent tooltip styling. ```tsx -} title="Tooltip Title" />} -> - {/* Your trigger element */} - -``` - -### Complex Tooltip (with detail) -Shows icon, title, detail text, and optional secondary detail text: +import { TooltipContent } from '@/components/shared/tooltip-content'; -```tsx +// Simple } - title="Tooltip Title" - detail="Main description (text-primary, text-sm)" - secondaryDetail="Additional info (text-secondary, text-xs)" - /> - } + classNames={{ base: 'p-0 m-0 bg-transparent shadow-sm border-none', content: 'p-0 m-0 bg-transparent shadow-sm border-none' }} + content={} > - {/* Your trigger element */} + {/* trigger */} -``` -### Tooltip with Action Link -Add an action link (like explorer) in the top-right corner: +// With detail +} + title="Tooltip Title" + detail="Main description" + secondaryDetail="Additional info" +/> -```tsx +// With action link } - actionHref="https://explorer.com/address/0x123" - onActionClick={(e) => e.stopPropagation()} + detail="Description" + actionIcon={} + actionHref="https://explorer.com" /> ``` -**Important:** -- Always use the `classNames` configuration shown above to remove HeroUI's default styling -- `detail`: Main description text (text-primary, text-sm) -- `secondaryDetail`: Additional info below detail (text-secondary, text-xs) - -## Shared UI Elements - -- Render token avatars with `TokenIcon` (`@/components/TokenIcon`) so chain-specific fallbacks, glyph sizing, and tooltips stay consistent. -- Display oracle provenance data with `OracleVendorBadge` (`@/components/OracleVendorBadge`) instead of plain text to benefit from vendor icons, warnings, and tooltips. +Always use the `classNames` configuration shown above to remove HeroUI's default styling. ### Table Component @@ -582,190 +515,64 @@ import { TablePagination } from '@/components/common/TablePagination';
``` -**Styling Notes:** -- Uses `font-zen !font-normal` throughout (overrides button's default font-medium) -- All buttons have consistent 8px height (h-8) -- Rounded-md container with bg-surface and shadow-sm -- Primary color for active page button -- Jump-to-page popover with Input and Go button -- Entry count uses text-xs text-secondary - -### Account Identity Component - -**AccountIdentity** (`@/components/common/AccountIdentity`) -- Unified component for displaying addresses, vault names, and ENS names -- Three variants: `badge`, `compact`, `full` -- All avatars are round by default - -**Variant Behaviors:** - -**Badge** - Minimal inline (no avatar) -- Shows: Vault name → ENS name → Shortened address - -**Compact** - Avatar (16px) wrapped in badge -- Avatar + (Vault name → ENS name → Shortened address) -- Single badge wraps both avatar and text - -**Full** - Horizontal layout with all info -- Avatar (36px) + Address badge + Extra badges (all on one line, centered) -- **Address badge**: Always shows shortened address (e.g., 0x1234...5678), click to copy -- **Extra badges** (shown based on conditions): - - Connected badge (if wallet is connected) - - ENS badge (if `showAddress=true` and no vault name) - - Vault badge (if address is a known vault) - -**Styling Rules:** -- Use `rounded-sm` for badges (not `rounded`) -- Background: `bg-hovered` (or `bg-green-500/10` for connected) -- Text: `font-zen` with `text-secondary` or `text-primary` -- No underscores in variable names -- All avatars are round -- Full variant: all elements centered vertically -- Smooth Framer Motion animations on all interactions +### AccountIdentity + +Use `AccountIdentity` from `@/components/common/AccountIdentity` for displaying addresses, ENS names, and vault names. ```tsx import { AccountIdentity } from '@/components/common/AccountIdentity'; -// Badge variant - minimal inline (no avatar) - - -// Compact variant - avatar (16px) wrapped in badge background - +// Badge - minimal inline (no avatar) + -// Full variant - avatar + address + extra info badges - +// Compact - small avatar + text in badge + -// Full variant for vault address - +// Full - avatar + address badge + extra info badges + ``` -**Props:** -- `variant`: `'badge'` | `'compact'` | `'full'` -- `linkTo`: `'explorer'` | `'profile'` | `'none'` -- `showCopy`: Show copy icon at end of badge -- `copyable`: Make entire component clickable to copy -- `showAddress`: Show ENS badge (full variant only) -- `showActions`: Show actions popover on click (default: `true`) - -**Actions Popover (Default Behavior):** - -By default, clicking any AccountIdentity shows a minimal popover with: -1. **Copy Address** - Copies address to clipboard -2. **View Account** - Navigate to positions page -3. **View on Explorer** - Opens Etherscan in new tab - -To disable: `showActions={false}` - -```tsx -// Default - shows actions popover on click - - -// Disable actions (e.g., in dropdown menus) - -``` +Variants: `badge` | `compact` | `full` -### Market Display Components +Clicking opens actions popover (copy, view account, explorer). Disable with `showActions={false}`. -Use the right component for displaying market information: +### MarketIdentity -**MarketIdentity** (`@/components/MarketIdentity`) -- Use for displaying market info in compact rows (tables, lists, cards) -- Shows token icons, symbols, LLTV badge, and oracle badge -- Three modes: `Normal`, `Focused`, `Minimum` -- Focus parameter: `Loan` or `Collateral` (affects which symbol is emphasized) +Use `MarketIdentity` from `@/components/MarketIdentity` for displaying market info in tables, lists, and cards. ```tsx import { MarketIdentity, MarketIdentityMode, MarketIdentityFocus } from '@/components/MarketIdentity'; -// Focused mode (default) - emphasizes one asset - - // Normal mode - both assets shown equally - + -// Minimum mode - only shows the focused asset (with LLTV and oracle if enabled) +// Focused mode - emphasizes one asset -// Wide layout - spreads content across full width (useful for tables) -// Icon + name on left, LLTV in middle, oracle on right +// Minimum mode - only focused asset -``` - -**Wide Layout:** - -The `wide` prop changes the layout to use `justify-between` with full width, perfect for table cells: -- **Left side**: Token icon(s) + symbol(s) -- **Middle**: LLTV badge (if enabled) -- **Right side**: Oracle badge (if enabled) +// Wide layout - for table cells + +``` -Works with all three modes (Normal, Focused, Minimum). Use in table cells with a fixed width for consistent alignment: +Modes: `Normal` | `Focused` | `Minimum` -```tsx -
-``` +### MarketDetailsBlock -**MarketDetailsBlock** (`@/components/common/MarketDetailsBlock`) -- Used for previewing transactions onto a existing market. -- Use as an expandable row in modals (e.g., supply/borrow flows) -- Shows market state details when expanded (APY, liquidity, utilization, etc.) -- Includes collapse/expand functionality +Use `MarketDetailsBlock` from `@/components/common/MarketDetailsBlock` for expandable market details in modals. ```tsx import { MarketDetailsBlock } from '@/components/common/MarketDetailsBlock'; @@ -773,73 +580,20 @@ import { MarketDetailsBlock } from '@/components/common/MarketDetailsBlock'; ``` -**When to use which:** -- Tables/Lists/Cards, Data display → Use `MarketIdentity` -- Modal flows during a transaction, with expandable details → Use `MarketDetailsBlock` +### TransactionIdentity -**TransactionIdentity** (`@/components/common/TransactionIdentity`) -- Use to display transaction hashes with explorer links -- Consistent monospace badge styling with external link icon -- Supports full or truncated hash display -- Always opens in new tab with security attributes +Use `TransactionIdentity` from `@/components/common/TransactionIdentity` for displaying transaction hashes. ```tsx import { TransactionIdentity } from '@/components/common/TransactionIdentity'; -// Standard usage (truncated hash) - - -// Full hash display - - -// With custom styling - -``` - -**Styling:** -- Uses `font-monospace text-[0.65rem]` for hash display -- Badge with `bg-hovered rounded-sm px-2 py-1` -- Hover: `hover:bg-gray-300 hover:text-primary dark:hover:bg-gray-700` -- External link icon (3x3) aligned with text -- Click event stops propagation to prevent row/parent click handlers - -**MarketIdBadge** (`@/components/MarketIdBadge`) -- Use to display a short market ID badge with optional network icon and warning indicator -- Consistent styling across all tables -- `chainId` is required -- Warning indicator reserves space for alignment even when no warnings present - -```tsx -import { MarketIdBadge } from '@/components/MarketIdBadge'; - -// Basic usage (required chainId) - - -// With network icon and warnings - - + + ``` ## Input Components From 1efd631095c82be3f0e7ceb0b5227981c7b6e8d4 Mon Sep 17 00:00:00 2001 From: antoncoding Date: Sat, 20 Dec 2025 12:36:56 +0800 Subject: [PATCH 4/5] feat: liquidity delta tooltip --- .../components/charts/volume-chart.tsx | 79 ++++++++++++++++--- 1 file changed, 68 insertions(+), 11 deletions(-) diff --git a/src/features/market-detail/components/charts/volume-chart.tsx b/src/features/market-detail/components/charts/volume-chart.tsx index 78724147..ad8f234f 100644 --- a/src/features/market-detail/components/charts/volume-chart.tsx +++ b/src/features/market-detail/components/charts/volume-chart.tsx @@ -1,11 +1,13 @@ /* eslint-disable react/no-unstable-nested-components */ import { useState, useMemo } from 'react'; -import { Card, CardHeader, CardBody } from '@heroui/react'; +import { Card, CardHeader, CardBody, Tooltip as HeroTooltip } from '@heroui/react'; import { AreaChart, Area, XAxis, YAxis, CartesianGrid, Tooltip, Legend, ResponsiveContainer } from 'recharts'; import { formatUnits } from 'viem'; +import { HiOutlineInformationCircle } from 'react-icons/hi2'; import ButtonGroup from '@/components/ui/button-group'; import { Spinner } from '@/components/ui/spinner'; +import { TooltipContent } from '@/components/shared/tooltip-content'; import { CHART_COLORS } from '@/constants/chartColors'; import { formatReadable } from '@/utils/balance'; import type { MarketVolumes } from '@/utils/types'; @@ -137,14 +139,23 @@ function VolumeChart({ liquidity: true, }); - // This is only for adoptive curve - const deltaToTargetUtilization = useMemo(() => { + // This is only for adaptive curve + const targetUtilizationData = useMemo(() => { const supply = market.state.supplyAssets ? BigInt(market.state.supplyAssets) : 0n; const borrow = market.state.borrowAssets ? BigInt(market.state.borrowAssets) : 0n; - const targetBorrow = supply * 9n / 10n; // target u is always 90% - return borrow - targetBorrow; - }, []) + // Calculate deltas to reach 90% target utilization + const targetBorrow = (supply * 9n) / 10n; // B_target = S * 0.9 + const borrowDelta = targetBorrow - borrow; + + const targetSupply = (borrow * 10n) / 9n; // S_target = B / 0.9 + const supplyDelta = targetSupply - supply; + + return { + borrowDelta, + supplyDelta, + }; + }, [market.state.supplyAssets, market.state.borrowAssets]); return ( @@ -329,11 +340,57 @@ function VolumeChart({ })} {/* Delta to target Utilization */} -
- Delta to Target: - - {formatValue(Number(formatUnits(deltaToTargetUtilization, market.loanAsset.decimals)))} - +
+

IRM Targets

+
+ + Supply Δ: + + } + > + + + + + + + {formatValue(Number(formatUnits(targetUtilizationData.supplyDelta, market.loanAsset.decimals)))} + +
+ +
+ + Borrow Δ: + + } + > + + + + + + + {formatValue(Number(formatUnits(targetUtilizationData.borrowDelta, market.loanAsset.decimals)))} + +
From fcc2dae1079f7f7a9ee5636c6d67aa8238256393 Mon Sep 17 00:00:00 2001 From: antoncoding Date: Sat, 20 Dec 2025 12:39:09 +0800 Subject: [PATCH 5/5] chore: fixes --- pnpm-lock.yaml | 31 ------------------------------- src/components/ui/checkbox.tsx | 4 +++- 2 files changed, 3 insertions(+), 32 deletions(-) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 3d7c5890..8f2606ab 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -53,9 +53,6 @@ importers: '@radix-ui/react-slot': specifier: ^1.2.4 version: 1.2.4(@types/react@18.3.23)(react@18.3.1) - '@radix-ui/react-switch': - specifier: ^1.2.6 - version: 1.2.6(@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-tabs': specifier: ^1.1.13 version: 1.1.13(@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) @@ -2484,19 +2481,6 @@ packages: '@types/react': optional: true - '@radix-ui/react-switch@1.2.6': - resolution: {integrity: sha512-bByzr1+ep1zk4VubeEVViV592vu2lHE2BZY5OnzehZqOOgogN80+mNtCqPkhn2gklJqOpxWgPoYTSnhBCqpOXQ==} - peerDependencies: - '@types/react': '*' - '@types/react-dom': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - '@types/react-dom': - optional: true - '@radix-ui/react-tabs@1.1.13': resolution: {integrity: sha512-7xdcatg7/U+7+Udyoj2zodtI9H/IIopqo+YOIcZOq1nJwXWBZ9p8xiu5llXlekDbZkca79a/fozEYQXIA4sW6A==} peerDependencies: @@ -9549,21 +9533,6 @@ snapshots: optionalDependencies: '@types/react': 18.3.23 - '@radix-ui/react-switch@1.2.6(@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)': - dependencies: - '@radix-ui/primitive': 1.1.3 - '@radix-ui/react-compose-refs': 1.1.2(@types/react@18.3.23)(react@18.3.1) - '@radix-ui/react-context': 1.1.2(@types/react@18.3.23)(react@18.3.1) - '@radix-ui/react-primitive': 2.1.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-use-controllable-state': 1.2.2(@types/react@18.3.23)(react@18.3.1) - '@radix-ui/react-use-previous': 1.1.1(@types/react@18.3.23)(react@18.3.1) - '@radix-ui/react-use-size': 1.1.1(@types/react@18.3.23)(react@18.3.1) - react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) - optionalDependencies: - '@types/react': 18.3.23 - '@types/react-dom': 18.3.7(@types/react@18.3.23) - '@radix-ui/react-tabs@1.1.13(@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)': dependencies: '@radix-ui/primitive': 1.1.3 diff --git a/src/components/ui/checkbox.tsx b/src/components/ui/checkbox.tsx index 5d395273..b5f555b6 100644 --- a/src/components/ui/checkbox.tsx +++ b/src/components/ui/checkbox.tsx @@ -14,8 +14,10 @@ const Checkbox = React.forwardRef< React.ElementRef, CheckboxProps >(({ className, variant = "default", label, id, ...props }, ref) => { + // Always call useId unconditionally (Rules of Hooks) + const generatedId = React.useId() // Auto-generate ID if label is provided but no ID is given - const checkboxId = id ?? (label ? React.useId() : undefined) + const checkboxId = id ?? (label ? generatedId : undefined) const checkbox = (
-
+
onToggle()} disabled={disabled} - className="h-6 w-4 cursor-pointer rounded border-gray-300 text-primary" - onSelect={(e) => e.stopPropagation()} - size="sm" + className="cursor-pointer" + onClick={(e) => e.stopPropagation()} />
- -