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 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..b5f555b6 --- /dev/null +++ b/src/components/ui/checkbox.tsx @@ -0,0 +1,73 @@ +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) => { + // 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 ? generatedId : 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/icon-switch.tsx b/src/components/ui/icon-switch.tsx index e964db37..f563f8cd 100644 --- a/src/components/ui/icon-switch.tsx +++ b/src/components/ui/icon-switch.tsx @@ -11,7 +11,7 @@ export type IconSwitchProps = { size?: 'xs' | 'sm' | 'md' | 'lg'; color?: 'primary' | 'secondary' | 'accent' | 'destructive'; onChange?: (selected: boolean) => void; - thumbIcon?: React.ComponentType<{ className?: string }>; + thumbIcon?: React.ComponentType<{ className?: string; isSelected?: boolean }> | null; thumbIconOn?: React.ComponentType<{ className?: string }>; thumbIconOff?: React.ComponentType<{ className?: string }>; classNames?: { @@ -35,7 +35,8 @@ type SizeConfig = { iconClass: string; }; -const SIZE_CONFIG: Record, SizeConfig> = { +// Size config for switches with icons +const SIZE_CONFIG_WITH_ICON: Record, SizeConfig> = { xs: { width: 38, height: 22, @@ -78,6 +79,50 @@ const SIZE_CONFIG: Record, SizeConfig> = { }, }; +// Size config for plain switches (no icon) - more compact +const SIZE_CONFIG_PLAIN: Record, SizeConfig> = { + xs: { + width: 28, + height: 16, + padding: 3, + thumbWidth: 10, + thumbHeight: 10, + radius: 8, + thumbRadius: 5, + iconClass: '', + }, + sm: { + width: 36, + height: 20, + padding: 3, + thumbWidth: 14, + thumbHeight: 14, + radius: 10, + thumbRadius: 7, + iconClass: '', + }, + md: { + width: 44, + height: 24, + padding: 4, + thumbWidth: 16, + thumbHeight: 16, + radius: 12, + thumbRadius: 8, + iconClass: '', + }, + lg: { + width: 52, + height: 28, + padding: 4, + thumbWidth: 20, + thumbHeight: 20, + radius: 14, + thumbRadius: 10, + iconClass: '', + }, +}; + const TRACK_COLOR: Record, string> = { primary: 'bg-[var(--palette-orange)]', secondary: 'bg-[var(--color-background-secondary)]', @@ -104,12 +149,13 @@ export function IconSwitch({ const isControlled = controlledSelected !== undefined; const isSelected = isControlled ? controlledSelected : internalSelected; - const config = SIZE_CONFIG[size]; - const translate = config.width - config.thumbWidth - config.padding * 2; - - // Determine which icon to use + // Determine which icon to use (null means no icon) const IconComponent = thumbIconOn && thumbIconOff ? (isSelected ? thumbIconOn : thumbIconOff) : ThumbIcon; + // Use compact config for plain switches, icon config otherwise + const config = IconComponent ? SIZE_CONFIG_WITH_ICON[size] : SIZE_CONFIG_PLAIN[size]; + const translate = config.width - config.thumbWidth - config.padding * 2; + const handleToggle = useCallback(() => { if (disabled) return; @@ -155,7 +201,8 @@ export function IconSwitch({ onClick={handleToggle} onKeyDown={handleKeyDown} className={cn( - 'relative inline-flex shrink-0 items-center justify-start overflow-hidden rounded-[8px] transition-colors duration-200 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary/40 focus-visible:ring-offset-2 focus-visible:ring-offset-background ring-1 ring-[var(--color-background-secondary)]', + 'relative inline-flex shrink-0 items-center justify-start overflow-hidden rounded-[8px] transition-colors duration-200 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary/40 focus-visible:ring-offset-2 focus-visible:ring-offset-background', + IconComponent && 'ring-1 ring-[var(--color-background-secondary)]', isSelected ? TRACK_COLOR[color] : 'bg-main', disabled ? 'cursor-not-allowed opacity-50' : 'cursor-pointer', classNames?.base, @@ -167,7 +214,8 @@ export function IconSwitch({ > - - - + {IconComponent && ( + + + + )} ); 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/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; + + // 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 ( @@ -318,6 +338,60 @@ function VolumeChart({
); })} + + {/* Delta to target Utilization */} +
+

IRM Targets

+
+ + Supply Δ: + + } + > + + + + + + + {formatValue(Number(formatUnits(targetUtilizationData.supplyDelta, market.loanAsset.decimals)))} + +
+ +
+ + Borrow Δ: + + } + > + + + + + + + {formatValue(Number(formatUnits(targetUtilizationData.borrowDelta, 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 )} - {showSelectColumn && (
diff --git a/src/features/positions/components/positions-summary-table.tsx b/src/features/positions/components/positions-summary-table.tsx index 7591358b..915f25f3 100644 --- a/src/features/positions/components/positions-summary-table.tsx +++ b/src/features/positions/components/positions-summary-table.tsx @@ -1,5 +1,6 @@ import React, { useMemo, useState, useEffect } from 'react'; -import { Dropdown, DropdownTrigger, DropdownMenu, DropdownItem, Tooltip, Switch } from '@heroui/react'; +import { Dropdown, DropdownTrigger, DropdownMenu, DropdownItem, Tooltip } from '@heroui/react'; +import { IconSwitch } from '@/components/ui/icon-switch'; import { ChevronDownIcon, ChevronUpIcon } from '@radix-ui/react-icons'; import { ReloadIcon } from '@radix-ui/react-icons'; import { GearIcon } from '@radix-ui/react-icons'; @@ -250,10 +251,11 @@ export function PositionsSummaryTable({ >
Show Empty Positions -
Show Collateral Exposure -
Use {getNativeTokenSymbol(market.morphoBlue.chain.id)} instead
- -
- - I understand that direct lending requires active management - -
+ setIsChecked(checked === true)} + />
-
+
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()} />