├── README.md ├── .eslintrc.json ├── app ├── icon.png ├── actions │ ├── getActionsData.tsx │ └── getChainData.tsx ├── loading.tsx ├── fonts.css ├── layout.tsx ├── globals.css ├── page.tsx └── chain │ └── [...slug] │ └── page.tsx ├── public ├── allium-logo.png ├── dune-icon.png ├── gas-station.png ├── worldmetrics_logo1.png ├── fonts │ ├── BerkeleyMono-Bold.otf │ ├── BerkeleyMono-Bold.ttf │ ├── BerkeleyMono-Bold.woff │ ├── BerkeleyMono-Bold.woff2 │ ├── BerkeleyMono-Italic.otf │ ├── BerkeleyMono-Italic.ttf │ ├── BerkeleyMono-Italic.woff │ ├── BerkeleyMono-Regular.otf │ ├── BerkeleyMono-Regular.ttf │ ├── BerkeleyMono-Italic.woff2 │ ├── BerkeleyMono-Regular.woff │ ├── BerkeleyMono-Regular.woff2 │ ├── BerkeleyMono-BoldItalic.otf │ ├── BerkeleyMono-BoldItalic.ttf │ ├── BerkeleyMono-BoldItalic.woff │ └── BerkeleyMono-BoldItalic.woff2 ├── vercel.svg └── next.svg ├── postcss.config.js ├── lib └── utils.ts ├── next.config.js ├── components ├── ui │ ├── skeleton.tsx │ ├── separator.tsx │ ├── input.tsx │ ├── checkbox.tsx │ ├── badge.tsx │ ├── popover.tsx │ ├── button.tsx │ ├── card.tsx │ ├── data-table-toolbar.tsx │ ├── date-table-column-header.tsx │ ├── table.tsx │ ├── data-table-view-options.tsx │ ├── dialog.tsx │ ├── data-table-pagination.tsx │ ├── command.tsx │ ├── navigation-menu.tsx │ ├── select.tsx │ ├── data-table-faceted-filter.tsx │ └── dropdown-menu.tsx ├── schema.ts ├── site-header.tsx ├── categories.tsx ├── main-nav.tsx ├── line-fees.tsx ├── site-footer.tsx ├── columns.tsx └── data-table.tsx ├── components.json ├── .gitignore ├── tsconfig.json ├── package.json └── tailwind.config.ts /README.md: -------------------------------------------------------------------------------- 1 | ## GasFees.io -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "next/core-web-vitals" 3 | } 4 | -------------------------------------------------------------------------------- /app/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jam516/gasfees-app/HEAD/app/icon.png -------------------------------------------------------------------------------- /public/allium-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jam516/gasfees-app/HEAD/public/allium-logo.png -------------------------------------------------------------------------------- /public/dune-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jam516/gasfees-app/HEAD/public/dune-icon.png -------------------------------------------------------------------------------- /public/gas-station.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jam516/gasfees-app/HEAD/public/gas-station.png -------------------------------------------------------------------------------- /public/worldmetrics_logo1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jam516/gasfees-app/HEAD/public/worldmetrics_logo1.png -------------------------------------------------------------------------------- /postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | } 7 | -------------------------------------------------------------------------------- /public/fonts/BerkeleyMono-Bold.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jam516/gasfees-app/HEAD/public/fonts/BerkeleyMono-Bold.otf -------------------------------------------------------------------------------- /public/fonts/BerkeleyMono-Bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jam516/gasfees-app/HEAD/public/fonts/BerkeleyMono-Bold.ttf -------------------------------------------------------------------------------- /public/fonts/BerkeleyMono-Bold.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jam516/gasfees-app/HEAD/public/fonts/BerkeleyMono-Bold.woff -------------------------------------------------------------------------------- /public/fonts/BerkeleyMono-Bold.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jam516/gasfees-app/HEAD/public/fonts/BerkeleyMono-Bold.woff2 -------------------------------------------------------------------------------- /public/fonts/BerkeleyMono-Italic.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jam516/gasfees-app/HEAD/public/fonts/BerkeleyMono-Italic.otf -------------------------------------------------------------------------------- /public/fonts/BerkeleyMono-Italic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jam516/gasfees-app/HEAD/public/fonts/BerkeleyMono-Italic.ttf -------------------------------------------------------------------------------- /public/fonts/BerkeleyMono-Italic.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jam516/gasfees-app/HEAD/public/fonts/BerkeleyMono-Italic.woff -------------------------------------------------------------------------------- /public/fonts/BerkeleyMono-Regular.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jam516/gasfees-app/HEAD/public/fonts/BerkeleyMono-Regular.otf -------------------------------------------------------------------------------- /public/fonts/BerkeleyMono-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jam516/gasfees-app/HEAD/public/fonts/BerkeleyMono-Regular.ttf -------------------------------------------------------------------------------- /public/fonts/BerkeleyMono-Italic.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jam516/gasfees-app/HEAD/public/fonts/BerkeleyMono-Italic.woff2 -------------------------------------------------------------------------------- /public/fonts/BerkeleyMono-Regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jam516/gasfees-app/HEAD/public/fonts/BerkeleyMono-Regular.woff -------------------------------------------------------------------------------- /public/fonts/BerkeleyMono-Regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jam516/gasfees-app/HEAD/public/fonts/BerkeleyMono-Regular.woff2 -------------------------------------------------------------------------------- /public/fonts/BerkeleyMono-BoldItalic.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jam516/gasfees-app/HEAD/public/fonts/BerkeleyMono-BoldItalic.otf -------------------------------------------------------------------------------- /public/fonts/BerkeleyMono-BoldItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jam516/gasfees-app/HEAD/public/fonts/BerkeleyMono-BoldItalic.ttf -------------------------------------------------------------------------------- /public/fonts/BerkeleyMono-BoldItalic.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jam516/gasfees-app/HEAD/public/fonts/BerkeleyMono-BoldItalic.woff -------------------------------------------------------------------------------- /public/fonts/BerkeleyMono-BoldItalic.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jam516/gasfees-app/HEAD/public/fonts/BerkeleyMono-BoldItalic.woff2 -------------------------------------------------------------------------------- /lib/utils.ts: -------------------------------------------------------------------------------- 1 | import { type ClassValue, clsx } from "clsx" 2 | import { twMerge } from "tailwind-merge" 3 | 4 | export function cn(...inputs: ClassValue[]) { 5 | return twMerge(clsx(inputs)) 6 | } 7 | -------------------------------------------------------------------------------- /next.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('next').NextConfig} */ 2 | module.exports = { 3 | webpack: (config) => { 4 | config.resolve.fallback = { fs: false, net: false, tls: false }; 5 | return config; 6 | }, 7 | 8 | }; 9 | -------------------------------------------------------------------------------- /components/ui/skeleton.tsx: -------------------------------------------------------------------------------- 1 | import { cn } from "@/lib/utils" 2 | 3 | function Skeleton({ 4 | className, 5 | ...props 6 | }: React.HTMLAttributes) { 7 | return ( 8 |
12 | ) 13 | } 14 | 15 | export { Skeleton } 16 | -------------------------------------------------------------------------------- /components.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://ui.shadcn.com/schema.json", 3 | "style": "default", 4 | "rsc": true, 5 | "tsx": true, 6 | "tailwind": { 7 | "config": "tailwind.config.ts", 8 | "css": "app/globals.css", 9 | "baseColor": "slate", 10 | "cssVariables": true 11 | }, 12 | "aliases": { 13 | "components": "@/components", 14 | "utils": "@/lib/utils" 15 | } 16 | } -------------------------------------------------------------------------------- /components/schema.ts: -------------------------------------------------------------------------------- 1 | import { z } from "zod" 2 | 3 | // We're keeping a simple non-relational schema here. 4 | // IRL, you will have a schema for your data models. 5 | export const actionSchema = z.object({ 6 | chain: z.string(), 7 | uniswap_trade_fee_usd: z.number(), 8 | seaport_trade_fee_usd: z.number(), 9 | usdc_transfer_fee_usd: z.number(), 10 | eth_transfer_fee_usd: z.number(), 11 | category: z.string(), 12 | }) 13 | 14 | export type Action = z.infer -------------------------------------------------------------------------------- /app/actions/getActionsData.tsx: -------------------------------------------------------------------------------- 1 | import { unstable_noStore as noStore } from "next/cache"; 2 | 3 | interface ActionData { 4 | time: string, 5 | } 6 | 7 | export async function getActionsData(): Promise { 8 | noStore(); 9 | const response = await fetch(`https://gasfees-api.onrender.com/actions`); 10 | if (!response.ok) { 11 | throw new Error(`HTTP Error: ${response.status} ${response.statusText}`); 12 | } 13 | 14 | const actionData: ActionData = await response.json(); 15 | 16 | return actionData; 17 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | .yarn/install-state.gz 8 | 9 | # testing 10 | /coverage 11 | 12 | # next.js 13 | /.next/ 14 | /out/ 15 | 16 | # production 17 | /build 18 | 19 | # misc 20 | .DS_Store 21 | *.pem 22 | 23 | # debug 24 | npm-debug.log* 25 | yarn-debug.log* 26 | yarn-error.log* 27 | 28 | # local env files 29 | .env*.local 30 | 31 | # vercel 32 | .vercel 33 | 34 | # typescript 35 | *.tsbuildinfo 36 | next-env.d.ts 37 | -------------------------------------------------------------------------------- /components/site-header.tsx: -------------------------------------------------------------------------------- 1 | import { MainNav } from "@/components/main-nav"; 2 | // import { MobileNav } from "@/components/mobile-nav"; 3 | // import { TimeSelect } from "@/components/time-select"; 4 | 5 | export function SiteHeader() { 6 | return ( 7 |
8 |
9 | 10 |
11 | {/*
12 | 13 |
*/} 14 |
15 | ); 16 | } -------------------------------------------------------------------------------- /components/categories.tsx: -------------------------------------------------------------------------------- 1 | import { 2 | StackIcon, 3 | StopIcon, 4 | Cross1Icon, 5 | ShadowOuterIcon, 6 | ComponentInstanceIcon 7 | } from "@radix-ui/react-icons" 8 | 9 | export const categories = [ 10 | { 11 | value: "l1", 12 | label: "L1", 13 | icon: ComponentInstanceIcon, 14 | }, 15 | { 16 | value: "l2 with blobs", 17 | label: "L2 with blobs", 18 | icon: ShadowOuterIcon, 19 | }, 20 | { 21 | value: "l2 without blobs", 22 | label: "L2 without blobs", 23 | icon: Cross1Icon, 24 | }, 25 | ] -------------------------------------------------------------------------------- /app/actions/getChainData.tsx: -------------------------------------------------------------------------------- 1 | import { unstable_noStore as noStore } from "next/cache"; 2 | 3 | interface ChainDataParams { 4 | chain: string; 5 | } 6 | 7 | export async function getChainData({ chain }: ChainDataParams) { 8 | noStore(); 9 | const lowercaseChain = chain.toLowerCase(); 10 | const response = await fetch(`https://gasfees-api.onrender.com/chain?chain_name=${lowercaseChain}`); 11 | if (!response.ok) { 12 | throw new Error(`HTTP Error: ${response.status} ${response.statusText}`); 13 | } 14 | 15 | const chainData: any[] = await response.json(); 16 | 17 | return chainData; 18 | } -------------------------------------------------------------------------------- /public/vercel.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/loading.tsx: -------------------------------------------------------------------------------- 1 | import { Skeleton } from "@/components/ui/skeleton" 2 | import { Separator } from "@/components/ui/separator"; 3 | 4 | export default function Loading() { 5 | 6 | return ( 7 | <> 8 |
9 |
10 | 11 |
12 |
13 |
14 |
15 | 16 |
17 |
18 | 19 | 20 | ); 21 | } -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "lib": ["dom", "dom.iterable", "esnext"], 5 | "allowJs": true, 6 | "skipLibCheck": true, 7 | "strict": true, 8 | "noEmit": true, 9 | "esModuleInterop": true, 10 | "module": "esnext", 11 | "moduleResolution": "bundler", 12 | "resolveJsonModule": true, 13 | "isolatedModules": true, 14 | "jsx": "preserve", 15 | "incremental": true, 16 | "plugins": [ 17 | { 18 | "name": "next" 19 | } 20 | ], 21 | "paths": { 22 | "@/*": ["./*"] 23 | } 24 | }, 25 | "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"], 26 | "exclude": ["node_modules"] 27 | } 28 | -------------------------------------------------------------------------------- /components/ui/separator.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import * as React from "react" 4 | import * as SeparatorPrimitive from "@radix-ui/react-separator" 5 | 6 | import { cn } from "@/lib/utils" 7 | 8 | const Separator = React.forwardRef< 9 | React.ElementRef, 10 | React.ComponentPropsWithoutRef 11 | >( 12 | ( 13 | { className, orientation = "horizontal", decorative = true, ...props }, 14 | ref 15 | ) => ( 16 | 27 | ) 28 | ) 29 | Separator.displayName = SeparatorPrimitive.Root.displayName 30 | 31 | export { Separator } 32 | -------------------------------------------------------------------------------- /components/ui/input.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react" 2 | 3 | import { cn } from "@/lib/utils" 4 | 5 | export interface InputProps 6 | extends React.InputHTMLAttributes {} 7 | 8 | const Input = React.forwardRef( 9 | ({ className, type, ...props }, ref) => { 10 | return ( 11 | 20 | ) 21 | } 22 | ) 23 | Input.displayName = "Input" 24 | 25 | export { Input } 26 | -------------------------------------------------------------------------------- /components/main-nav.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import Link from "next/link"; 4 | import { usePathname } from "next/navigation" 5 | import { 6 | NavigationMenu, 7 | NavigationMenuContent, 8 | NavigationMenuItem, 9 | NavigationMenuLink, 10 | NavigationMenuList, 11 | NavigationMenuTrigger, 12 | navigationMenuTriggerStyle, 13 | } from "@/components/ui/navigation-menu" 14 | // import { Fuel } from 'lucide-react'; 15 | 16 | export function MainNav() { 17 | const pathname = usePathname(); 18 | const segments = pathname.split("/")[1]; 19 | 20 | return ( 21 | 28 | ); 29 | } 30 | -------------------------------------------------------------------------------- /components/ui/checkbox.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import * as React from "react" 4 | import * as CheckboxPrimitive from "@radix-ui/react-checkbox" 5 | import { Check } from "lucide-react" 6 | 7 | import { cn } from "@/lib/utils" 8 | 9 | const Checkbox = React.forwardRef< 10 | React.ElementRef, 11 | React.ComponentPropsWithoutRef 12 | >(({ className, ...props }, ref) => ( 13 | 21 | 24 | 25 | 26 | 27 | )) 28 | Checkbox.displayName = CheckboxPrimitive.Root.displayName 29 | 30 | export { Checkbox } 31 | -------------------------------------------------------------------------------- /components/ui/badge.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react" 2 | import { cva, type VariantProps } from "class-variance-authority" 3 | 4 | import { cn } from "@/lib/utils" 5 | 6 | const badgeVariants = cva( 7 | "inline-flex items-center rounded-full border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2", 8 | { 9 | variants: { 10 | variant: { 11 | default: 12 | "border-transparent bg-primary text-primary-foreground hover:bg-primary/80", 13 | secondary: 14 | "border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80", 15 | destructive: 16 | "border-transparent bg-destructive text-destructive-foreground hover:bg-destructive/80", 17 | outline: "text-foreground", 18 | }, 19 | }, 20 | defaultVariants: { 21 | variant: "default", 22 | }, 23 | } 24 | ) 25 | 26 | export interface BadgeProps 27 | extends React.HTMLAttributes, 28 | VariantProps {} 29 | 30 | function Badge({ className, variant, ...props }: BadgeProps) { 31 | return ( 32 |
33 | ) 34 | } 35 | 36 | export { Badge, badgeVariants } 37 | -------------------------------------------------------------------------------- /public/next.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /components/ui/popover.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import * as React from "react" 4 | import * as PopoverPrimitive from "@radix-ui/react-popover" 5 | 6 | import { cn } from "@/lib/utils" 7 | 8 | const Popover = PopoverPrimitive.Root 9 | 10 | const PopoverTrigger = PopoverPrimitive.Trigger 11 | 12 | const PopoverContent = React.forwardRef< 13 | React.ElementRef, 14 | React.ComponentPropsWithoutRef 15 | >(({ className, align = "center", sideOffset = 4, ...props }, ref) => ( 16 | 17 | 27 | 28 | )) 29 | PopoverContent.displayName = PopoverPrimitive.Content.displayName 30 | 31 | export { Popover, PopoverTrigger, PopoverContent } 32 | -------------------------------------------------------------------------------- /app/fonts.css: -------------------------------------------------------------------------------- 1 | @font-face { 2 | font-family: 'BerkeleyMono'; 3 | src: url('/fonts/BerkeleyMono-Regular.woff2') format('woff2'), 4 | url('/fonts/BerkeleyMono-Regular.woff') format('woff'), 5 | url('/fonts/BerkeleyMono-Regular.otf') format('opentype'), 6 | url('/fonts/BerkeleyMono-Regular.ttf') format('truetype'); 7 | font-weight: normal; 8 | font-style: normal; 9 | } 10 | 11 | @font-face { 12 | font-family: 'BerkeleyMono'; 13 | src: url('/fonts/BerkeleyMono-Bold.woff2') format('woff2'), 14 | url('/fonts/BerkeleyMono-Bold.woff') format('woff'), 15 | url('/fonts/BerkeleyMono-Bold.otf') format('opentype'), 16 | url('/fonts/BerkeleyMono-Bold.ttf') format('truetype'); 17 | font-weight: bold; 18 | font-style: normal; 19 | } 20 | 21 | @font-face { 22 | font-family: 'BerkeleyMono'; 23 | src: url('/fonts/BerkeleyMono-Italic.woff2') format('woff2'), 24 | url('/fonts/BerkeleyMono-Italic.woff') format('woff'), 25 | url('/fonts/BerkeleyMono-Italic.otf') format('opentype'), 26 | url('/fonts/BerkeleyMono-Italic.ttf') format('truetype'); 27 | font-weight: normal; 28 | font-style: italic; 29 | } 30 | 31 | @font-face { 32 | font-family: 'BerkeleyMono'; 33 | src: url('/fonts/BerkeleyMono-BoldItalic.woff2') format('woff2'), 34 | url('/fonts/BerkeleyMono-BoldItalic.woff') format('woff'), 35 | url('/fonts/BerkeleyMono-BoldItalic.otf') format('opentype'), 36 | url('/fonts/BerkeleyMono-BoldItalic.ttf') format('truetype'); 37 | font-weight: bold; 38 | font-style: italic; 39 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "scrollstats", 3 | "version": "0.1.0", 4 | "private": true, 5 | "scripts": { 6 | "dev": "next dev", 7 | "build": "next build", 8 | "start": "next start", 9 | "lint": "next lint" 10 | }, 11 | "dependencies": { 12 | "@radix-ui/react-checkbox": "^1.0.4", 13 | "@radix-ui/react-dialog": "^1.0.5", 14 | "@radix-ui/react-dropdown-menu": "^2.0.6", 15 | "@radix-ui/react-icons": "^1.3.0", 16 | "@radix-ui/react-navigation-menu": "^1.1.4", 17 | "@radix-ui/react-popover": "^1.0.7", 18 | "@radix-ui/react-select": "^2.0.0", 19 | "@radix-ui/react-separator": "^1.0.3", 20 | "@radix-ui/react-slot": "^1.0.2", 21 | "@tanstack/react-table": "^8.10.7", 22 | "@vercel/analytics": "^1.1.1", 23 | "class-variance-authority": "^0.7.0", 24 | "clsx": "^2.0.0", 25 | "cmdk": "^0.2.1", 26 | "highcharts": "^11.2.0", 27 | "highcharts-react-official": "^3.2.1", 28 | "lucide-react": "^0.293.0", 29 | "moment": "^2.29.4", 30 | "next": "14.0.3", 31 | "numeral": "^2.0.6", 32 | "react": "^18", 33 | "react-dom": "^18", 34 | "recharts": "^2.10.3", 35 | "tailwind-merge": "^2.0.0", 36 | "tailwindcss-animate": "^1.0.7", 37 | "zod": "^3.22.4" 38 | }, 39 | "devDependencies": { 40 | "@types/node": "^20", 41 | "@types/numeral": "^2.0.5", 42 | "@types/react": "^18", 43 | "@types/react-dom": "^18", 44 | "autoprefixer": "^10.0.1", 45 | "eslint": "^8", 46 | "eslint-config-next": "14.0.3", 47 | "postcss": "^8", 48 | "tailwindcss": "^3.3.0", 49 | "typescript": "^5" 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /components/line-fees.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import { Line, LineChart, Tooltip, Legend, ResponsiveContainer, XAxis, YAxis, CartesianGrid } from "recharts"; 4 | import moment from 'moment'; 5 | 6 | type DataEntry = { 7 | day: string; 8 | [key: string]: string | number; 9 | }; 10 | 11 | interface LChartProps { 12 | data: DataEntry[]; 13 | xaxis: string; 14 | yaxis: string; 15 | usd: boolean; 16 | } 17 | 18 | 19 | export function FeeLineChart({ data, xaxis, yaxis, usd }: LChartProps) { 20 | 21 | const transformedData = data.map(entry => ({ 22 | ...entry, 23 | day: moment(entry.day).format('DD-MMM-YYYY') 24 | })); 25 | 26 | 27 | 28 | return ( 29 | 30 | 31 | 32 | 39 | 45 | usd ? `$${value.toLocaleString()}` : value.toLocaleString() 46 | } 47 | /> 48 | 49 | 50 | 51 | 52 | ); 53 | } 54 | -------------------------------------------------------------------------------- /app/layout.tsx: -------------------------------------------------------------------------------- 1 | import type { Metadata } from 'next' 2 | import './globals.css' 3 | import { SiteHeader } from "@/components/site-header"; 4 | import { Analytics } from '@vercel/analytics/react'; 5 | import { SiteFooter } from "@/components/site-footer"; 6 | import { Separator } from "@/components/ui/separator"; 7 | 8 | export const metadata: Metadata = { 9 | title: 'GasFees.io', 10 | description: "The most accurate gas cost tracker", 11 | keywords: [ 12 | "gas", 13 | "gas fees", 14 | "crypto", 15 | "blockchain", 16 | ], 17 | authors: [ 18 | { 19 | name: "0xKofi", 20 | url: "https://0xkofi.com", 21 | }, 22 | ], 23 | creator: "0xKofi", 24 | openGraph: { 25 | type: "website", 26 | locale: "en_US", 27 | url: "https://www.gasfees.io", 28 | title: "GasFees.io", 29 | description: "Gas Cost Data", 30 | siteName: "GasFees.io", 31 | images: [ 32 | { 33 | url: "https://i.imgur.com/1IIQMq8.png", 34 | width: 1200, 35 | height: 630, 36 | alt: "GasFees.io", 37 | }, 38 | ], 39 | }, 40 | twitter: { 41 | card: "summary_large_image", 42 | title: "GasFees.io", 43 | description: "The most accurate gas cost tracker", 44 | images: ["https://i.imgur.com/1IIQMq8.png"], 45 | creator: "@0xKofi", 46 | }, 47 | } 48 | 49 | export default function RootLayout({ 50 | children, 51 | }: { 52 | children: React.ReactNode 53 | }) { 54 | return ( 55 | 56 | 57 |
58 | 59 | 60 |
{children}
61 | 62 | 63 |
64 | 65 | 66 | 67 | ) 68 | } 69 | -------------------------------------------------------------------------------- /components/ui/button.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react" 2 | import { Slot } from "@radix-ui/react-slot" 3 | import { cva, type VariantProps } from "class-variance-authority" 4 | 5 | import { cn } from "@/lib/utils" 6 | 7 | const buttonVariants = cva( 8 | "inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50", 9 | { 10 | variants: { 11 | variant: { 12 | default: "bg-primary text-primary-foreground hover:bg-primary/90", 13 | destructive: 14 | "bg-destructive text-destructive-foreground hover:bg-destructive/90", 15 | outline: 16 | "border border-input bg-background hover:bg-accent hover:text-accent-foreground", 17 | secondary: 18 | "bg-secondary text-secondary-foreground hover:bg-secondary/80", 19 | ghost: "hover:bg-accent hover:text-accent-foreground", 20 | link: "text-primary underline-offset-4 hover:underline", 21 | }, 22 | size: { 23 | default: "h-10 px-4 py-2", 24 | sm: "h-9 rounded-md px-3", 25 | lg: "h-11 rounded-md px-8", 26 | icon: "h-10 w-10", 27 | }, 28 | }, 29 | defaultVariants: { 30 | variant: "default", 31 | size: "default", 32 | }, 33 | } 34 | ) 35 | 36 | export interface ButtonProps 37 | extends React.ButtonHTMLAttributes, 38 | VariantProps { 39 | asChild?: boolean 40 | } 41 | 42 | const Button = React.forwardRef( 43 | ({ className, variant, size, asChild = false, ...props }, ref) => { 44 | const Comp = asChild ? Slot : "button" 45 | return ( 46 | 51 | ) 52 | } 53 | ) 54 | Button.displayName = "Button" 55 | 56 | export { Button, buttonVariants } 57 | -------------------------------------------------------------------------------- /components/ui/card.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react" 2 | 3 | import { cn } from "@/lib/utils" 4 | 5 | const Card = React.forwardRef< 6 | HTMLDivElement, 7 | React.HTMLAttributes 8 | >(({ className, ...props }, ref) => ( 9 |
17 | )) 18 | Card.displayName = "Card" 19 | 20 | const CardHeader = React.forwardRef< 21 | HTMLDivElement, 22 | React.HTMLAttributes 23 | >(({ className, ...props }, ref) => ( 24 |
29 | )) 30 | CardHeader.displayName = "CardHeader" 31 | 32 | const CardTitle = React.forwardRef< 33 | HTMLParagraphElement, 34 | React.HTMLAttributes 35 | >(({ className, ...props }, ref) => ( 36 |

44 | )) 45 | CardTitle.displayName = "CardTitle" 46 | 47 | const CardDescription = React.forwardRef< 48 | HTMLParagraphElement, 49 | React.HTMLAttributes 50 | >(({ className, ...props }, ref) => ( 51 |

56 | )) 57 | CardDescription.displayName = "CardDescription" 58 | 59 | const CardContent = React.forwardRef< 60 | HTMLDivElement, 61 | React.HTMLAttributes 62 | >(({ className, ...props }, ref) => ( 63 |

64 | )) 65 | CardContent.displayName = "CardContent" 66 | 67 | const CardFooter = React.forwardRef< 68 | HTMLDivElement, 69 | React.HTMLAttributes 70 | >(({ className, ...props }, ref) => ( 71 |
76 | )) 77 | CardFooter.displayName = "CardFooter" 78 | 79 | export { Card, CardHeader, CardFooter, CardTitle, CardDescription, CardContent } 80 | -------------------------------------------------------------------------------- /app/globals.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | 5 | @import './fonts.css'; 6 | 7 | @layer base { 8 | :root { 9 | --background: 0 0% 100%; 10 | --foreground: 222.2 84% 4.9%; 11 | 12 | --card: 0 0% 100%; 13 | --card-foreground: 222.2 84% 4.9%; 14 | 15 | --popover: 0 0% 100%; 16 | --popover-foreground: 222.2 84% 4.9%; 17 | 18 | --primary: 222.2 47.4% 11.2%; 19 | --primary-foreground: 210 40% 98%; 20 | 21 | --secondary: 210 40% 96.1%; 22 | --secondary-foreground: 222.2 47.4% 11.2%; 23 | 24 | --muted: 210 40% 96.1%; 25 | --muted-foreground: 215.4 16.3% 46.9%; 26 | 27 | --accent: 210 40% 96.1%; 28 | --accent-foreground: 222.2 47.4% 11.2%; 29 | 30 | --destructive: 0 84.2% 60.2%; 31 | --destructive-foreground: 210 40% 98%; 32 | 33 | --border: 214.3 31.8% 91.4%; 34 | --input: 214.3 31.8% 91.4%; 35 | --ring: 222.2 84% 4.9%; 36 | 37 | --radius: 0.5rem; 38 | } 39 | 40 | .dark { 41 | --background: 222.2 84% 4.9%; 42 | --foreground: 210 40% 98%; 43 | 44 | --card: 222.2 84% 4.9%; 45 | --card-foreground: 210 40% 98%; 46 | 47 | --popover: 222.2 84% 4.9%; 48 | --popover-foreground: 210 40% 98%; 49 | 50 | --primary: 210 40% 98%; 51 | --primary-foreground: 222.2 47.4% 11.2%; 52 | 53 | --secondary: 217.2 32.6% 17.5%; 54 | --secondary-foreground: 210 40% 98%; 55 | 56 | --muted: 217.2 32.6% 17.5%; 57 | --muted-foreground: 215 20.2% 65.1%; 58 | 59 | --accent: 217.2 32.6% 17.5%; 60 | --accent-foreground: 210 40% 98%; 61 | 62 | --destructive: 0 62.8% 30.6%; 63 | --destructive-foreground: 210 40% 98%; 64 | 65 | --border: 217.2 32.6% 17.5%; 66 | --input: 217.2 32.6% 17.5%; 67 | --ring: 212.7 26.8% 83.9%; 68 | } 69 | } 70 | 71 | @layer base { 72 | * { 73 | @apply border-border; 74 | } 75 | 76 | body { 77 | @apply bg-background text-foreground; 78 | font-family: 'BerkeleyMono', sans-serif; 79 | } 80 | 81 | .padding-xl { 82 | padding-left: 10%; 83 | padding-right: 10%; 84 | } 85 | 86 | } 87 | 88 | @media (max-width: 767px) { 89 | .title-text { 90 | white-space: pre-wrap; 91 | text-align: left; 92 | } 93 | } -------------------------------------------------------------------------------- /components/site-footer.tsx: -------------------------------------------------------------------------------- 1 | // import { siteConfig } from "@/config/site" 2 | 3 | export function SiteFooter() { 4 | return ( 5 | 46 | ); 47 | } 48 | -------------------------------------------------------------------------------- /components/ui/data-table-toolbar.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import { Cross2Icon } from "@radix-ui/react-icons" 4 | import { Table } from "@tanstack/react-table" 5 | 6 | import { Button } from "@/components/ui/button" 7 | import { Input } from "@/components/ui/input" 8 | import { DataTableViewOptions } from "@/components/ui/data-table-view-options" 9 | 10 | import { categories } from "@/components/categories" 11 | import { DataTableFacetedFilter } from "./data-table-faceted-filter" 12 | 13 | interface DataTableToolbarProps { 14 | table: Table 15 | } 16 | 17 | export function DataTableToolbar({ 18 | table, 19 | }: DataTableToolbarProps) { 20 | const isFiltered = table.getState().columnFilters.length > 0 21 | 22 | return ( 23 |
24 |
25 | {/* 29 | table.getColumn("chain")?.setFilterValue(event.target.value) 30 | } 31 | className="h-8 w-[150px] lg:w-[250px]" 32 | /> */} 33 | {table.getColumn("category") && ( 34 | 39 | )} 40 | {isFiltered && ( 41 | 49 | )} 50 | 51 | 52 |
53 | {/* */} 54 |
55 | ) 56 | } -------------------------------------------------------------------------------- /app/page.tsx: -------------------------------------------------------------------------------- 1 | import { Metadata } from "next"; 2 | import { 3 | Card, 4 | CardContent, 5 | CardDescription, 6 | CardHeader, 7 | CardTitle, 8 | } from "@/components/ui/card"; 9 | import { Separator } from "@/components/ui/separator"; 10 | import { getActionsData } from "@/app/actions/getActionsData"; 11 | import { ChevronUp, ChevronsUp, ChevronDown, ChevronsDown } from 'lucide-react'; 12 | import { DataTable } from "@/components/data-table"; 13 | import { actionSchema } from "@/components/schema"; 14 | import { columns } from "@/components/columns"; 15 | import { z } from "zod"; 16 | 17 | export const metadata: Metadata = { 18 | title: "GasFees.io", 19 | description: "Gas fee data", 20 | }; 21 | 22 | export const maxDuration = 60; 23 | 24 | function AboutBlock() { 25 | return ( 26 |
27 |
28 |

What is GasFees.io?

29 |

Gasfees.io tracks the gas cost of performing common actions on different L1s and L2s.

30 |
31 |
32 |

How are these fees calculated?

33 |

The median cost of performing different transaction types is calculated for the past 24 hours.

34 |

A dynamic filter is used to exclude transactions that have anomalously high priority fees so that bots do not skew the results.

35 |
36 |
37 | ) 38 | } 39 | 40 | export default async function UsersPage() { 41 | 42 | const data = await getActionsData(); 43 | const actions = z.array(actionSchema).parse(data) 44 | 45 | return ( 46 | <> 47 |
48 |
49 | 50 | {/* */} 51 | 52 |
53 |
54 | 55 | ); 56 | }; -------------------------------------------------------------------------------- /app/chain/[...slug]/page.tsx: -------------------------------------------------------------------------------- 1 | import { Metadata } from "next"; 2 | import { getChainData } from "@/app/actions/getChainData"; 3 | import { FeeLineChart } from "@/components/line-fees" 4 | import { 5 | Card, 6 | CardContent, 7 | CardDescription, 8 | CardHeader, 9 | CardTitle, 10 | } from "@/components/ui/card"; 11 | 12 | export const metadata: Metadata = { 13 | title: "GasFees.io", 14 | description: "Gas cost data", 15 | }; 16 | 17 | export const maxDuration = 60; 18 | 19 | function AboutBlock() { 20 | return ( 21 |
22 |
23 |

What is GasFees.io?

24 |

Gasfees.io tracks the gas cost of performing common actions on different L1s and L2s.

25 |
26 |
27 |

How are these fees calculated?

28 |

The median cost of performing an ETH transfer is calculated for the past 30 days.

29 |

A dynamic filter is used to exclude transactions that have anomalously high priority fees so that bots do not skew the results.

30 |
31 |
32 | ) 33 | } 34 | 35 | export default async function ChainPage({ params }: { params: { slug: string[] } }) { 36 | 37 | let chain = params.slug[0]; 38 | const chain_data = await getChainData({ chain }); 39 | 40 | function capitalizeFirstLetter(string: string) { 41 | return string.charAt(0).toUpperCase() + string.slice(1); 42 | } 43 | 44 | return ( 45 | <> 46 |
47 |
48 |

{capitalizeFirstLetter(chain)}

49 | 50 | 51 | {"Daily Cost of ETH Transfer (USD)"} 52 | 53 | 54 | 55 | 56 | 57 | 58 |
59 |
60 | 61 | ); 62 | }; -------------------------------------------------------------------------------- /tailwind.config.ts: -------------------------------------------------------------------------------- 1 | /** @type {import('tailwindcss').Config} */ 2 | module.exports = { 3 | darkMode: ["class"], 4 | content: [ 5 | './pages/**/*.{ts,tsx}', 6 | './components/**/*.{ts,tsx}', 7 | './app/**/*.{ts,tsx}', 8 | './src/**/*.{ts,tsx}', 9 | ], 10 | theme: { 11 | container: { 12 | center: true, 13 | padding: "2rem", 14 | screens: { 15 | "2xl": "1400px", 16 | }, 17 | }, 18 | extend: { 19 | boxShadow: { 20 | 'custom': '3px 3px 0px 0px rgba(73, 73, 73, 71)', 21 | }, 22 | fontFamily: { 23 | berkeleymono: ['BerkeleyMono', 'sans-serif'], 24 | }, 25 | colors: { 26 | border: "hsl(var(--border))", 27 | input: "hsl(var(--input))", 28 | ring: "hsl(var(--ring))", 29 | background: "hsl(var(--background))", 30 | foreground: "hsl(var(--foreground))", 31 | primary: { 32 | DEFAULT: "hsl(var(--primary))", 33 | foreground: "hsl(var(--primary-foreground))", 34 | }, 35 | secondary: { 36 | DEFAULT: "hsl(var(--secondary))", 37 | foreground: "hsl(var(--secondary-foreground))", 38 | }, 39 | destructive: { 40 | DEFAULT: "hsl(var(--destructive))", 41 | foreground: "hsl(var(--destructive-foreground))", 42 | }, 43 | muted: { 44 | DEFAULT: "hsl(var(--muted))", 45 | foreground: "hsl(var(--muted-foreground))", 46 | }, 47 | accent: { 48 | DEFAULT: "hsl(var(--accent))", 49 | foreground: "hsl(var(--accent-foreground))", 50 | }, 51 | popover: { 52 | DEFAULT: "hsl(var(--popover))", 53 | foreground: "hsl(var(--popover-foreground))", 54 | }, 55 | card: { 56 | DEFAULT: "hsl(var(--card))", 57 | foreground: "hsl(var(--card-foreground))", 58 | }, 59 | }, 60 | borderRadius: { 61 | lg: "var(--radius)", 62 | md: "calc(var(--radius) - 2px)", 63 | sm: "calc(var(--radius) - 4px)", 64 | }, 65 | keyframes: { 66 | "accordion-down": { 67 | from: { height: 0 }, 68 | to: { height: "var(--radix-accordion-content-height)" }, 69 | }, 70 | "accordion-up": { 71 | from: { height: "var(--radix-accordion-content-height)" }, 72 | to: { height: 0 }, 73 | }, 74 | }, 75 | animation: { 76 | "accordion-down": "accordion-down 0.2s ease-out", 77 | "accordion-up": "accordion-up 0.2s ease-out", 78 | }, 79 | }, 80 | }, 81 | plugins: [require("tailwindcss-animate")], 82 | } -------------------------------------------------------------------------------- /components/ui/date-table-column-header.tsx: -------------------------------------------------------------------------------- 1 | import { ArrowDownIcon, ArrowUpIcon, CaretSortIcon, EyeNoneIcon } from "@radix-ui/react-icons" 2 | import { Column } from "@tanstack/react-table" 3 | import { cn } from "@/lib/utils" 4 | import { Button } from "@/components/ui/button" 5 | import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuSeparator, DropdownMenuTrigger } from "@/components/ui/dropdown-menu" 6 | import { useEffect, useRef } from "react" 7 | 8 | interface DataTableColumnHeaderProps extends React.HTMLAttributes { 9 | column: Column 10 | title: string 11 | } 12 | 13 | export function DataTableColumnHeader({ 14 | column, 15 | title, 16 | className, 17 | }: DataTableColumnHeaderProps) { 18 | const titleRef = useRef(null) 19 | 20 | useEffect(() => { 21 | if (titleRef.current) { 22 | titleRef.current.textContent = title.replace(/ /g, "\n") 23 | } 24 | }, [title]) 25 | 26 | if (!column.getCanSort()) { 27 | return ( 28 |
29 | 30 | {title} 31 | 32 |
33 | ) 34 | } 35 | 36 | return ( 37 |
38 | 39 | 40 | 56 | 57 | 58 | column.toggleSorting(false)}> 59 | 60 | Asc 61 | 62 | column.toggleSorting(true)}> 63 | 64 | Desc 65 | 66 | 67 | column.toggleVisibility(false)}> 68 | 69 | Hide 70 | 71 | 72 | 73 |
74 | ) 75 | } -------------------------------------------------------------------------------- /components/ui/table.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react" 2 | 3 | import { cn } from "@/lib/utils" 4 | 5 | const Table = React.forwardRef< 6 | HTMLTableElement, 7 | React.HTMLAttributes 8 | >(({ className, ...props }, ref) => ( 9 |
10 | 15 | 16 | )) 17 | Table.displayName = "Table" 18 | 19 | const TableHeader = React.forwardRef< 20 | HTMLTableSectionElement, 21 | React.HTMLAttributes 22 | >(({ className, ...props }, ref) => ( 23 | 24 | )) 25 | TableHeader.displayName = "TableHeader" 26 | 27 | const TableBody = React.forwardRef< 28 | HTMLTableSectionElement, 29 | React.HTMLAttributes 30 | >(({ className, ...props }, ref) => ( 31 | 36 | )) 37 | TableBody.displayName = "TableBody" 38 | 39 | const TableFooter = React.forwardRef< 40 | HTMLTableSectionElement, 41 | React.HTMLAttributes 42 | >(({ className, ...props }, ref) => ( 43 | tr]:last:border-b-0", 47 | className 48 | )} 49 | {...props} 50 | /> 51 | )) 52 | TableFooter.displayName = "TableFooter" 53 | 54 | const TableRow = React.forwardRef< 55 | HTMLTableRowElement, 56 | React.HTMLAttributes 57 | >(({ className, ...props }, ref) => ( 58 | 66 | )) 67 | TableRow.displayName = "TableRow" 68 | 69 | const TableHead = React.forwardRef< 70 | HTMLTableCellElement, 71 | React.ThHTMLAttributes 72 | >(({ className, ...props }, ref) => ( 73 |
81 | )) 82 | TableHead.displayName = "TableHead" 83 | 84 | const TableCell = React.forwardRef< 85 | HTMLTableCellElement, 86 | React.TdHTMLAttributes 87 | >(({ className, ...props }, ref) => ( 88 | 93 | )) 94 | TableCell.displayName = "TableCell" 95 | 96 | const TableCaption = React.forwardRef< 97 | HTMLTableCaptionElement, 98 | React.HTMLAttributes 99 | >(({ className, ...props }, ref) => ( 100 |
105 | )) 106 | TableCaption.displayName = "TableCaption" 107 | 108 | export { 109 | Table, 110 | TableHeader, 111 | TableBody, 112 | TableFooter, 113 | TableHead, 114 | TableRow, 115 | TableCell, 116 | TableCaption, 117 | } 118 | -------------------------------------------------------------------------------- /components/ui/data-table-view-options.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import { DropdownMenuTrigger } from "@radix-ui/react-dropdown-menu" 4 | import { MixerHorizontalIcon } from "@radix-ui/react-icons" 5 | import { Table } from "@tanstack/react-table" 6 | 7 | import { Button } from "@/components/ui/button" 8 | import { 9 | DropdownMenu, 10 | DropdownMenuCheckboxItem, 11 | DropdownMenuContent, 12 | DropdownMenuLabel, 13 | DropdownMenuSeparator, 14 | } from "@/components/ui/dropdown-menu" 15 | 16 | import { useState, useEffect } from 'react'; 17 | 18 | interface WindowSize { 19 | width: number | undefined; 20 | } 21 | 22 | function useWindowSize() { 23 | const [windowSize, setWindowSize] = useState({ width: undefined }); 24 | 25 | useEffect(() => { 26 | function handleResize() { 27 | setWindowSize({ 28 | width: window.innerWidth, 29 | }); 30 | } 31 | 32 | window.addEventListener('resize', handleResize); 33 | handleResize(); 34 | 35 | return () => window.removeEventListener('resize', handleResize); 36 | }, []); 37 | 38 | return windowSize; 39 | } 40 | 41 | 42 | interface DataTableViewOptionsProps { 43 | table: Table 44 | } 45 | 46 | export function DataTableViewOptions({ 47 | table, 48 | }: DataTableViewOptionsProps) { 49 | const { width } = useWindowSize(); 50 | const isMediumScreenOrLarger = (width ?? 0) >= 768; 51 | 52 | const columnsToHideOnSmallScreens = ['category', 'seaport_trade_fee_usd', 'uniswap_trade_fee_usd']; 53 | 54 | useEffect(() => { 55 | table.getAllColumns().forEach(column => { 56 | if (columnsToHideOnSmallScreens.includes(column.id)) { 57 | column.toggleVisibility(isMediumScreenOrLarger); 58 | } 59 | }); 60 | }, [isMediumScreenOrLarger, table]); 61 | 62 | // useEffect(() => { 63 | // // Find the 'category' column and update its visibility 64 | // const categoryColumn = table.getAllColumns().find(column => column.id === 'category'); 65 | // if (categoryColumn) { 66 | // categoryColumn.toggleVisibility(isMediumScreenOrLarger); 67 | // } 68 | // }, [isMediumScreenOrLarger, table]); 69 | 70 | return ( 71 | 72 | 73 | 81 | 82 | 83 | Toggle columns 84 | 85 | {table 86 | .getAllColumns() 87 | .filter( 88 | (column) => 89 | typeof column.accessorFn !== "undefined" && column.getCanHide() 90 | ) 91 | .map((column) => { 92 | return ( 93 | column.toggleVisibility(!!value)} 98 | > 99 | {column.id} 100 | 101 | ) 102 | })} 103 | 104 | 105 | ) 106 | } -------------------------------------------------------------------------------- /components/columns.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import { ColumnDef } from "@tanstack/react-table"; 4 | 5 | import { Badge } from "@/components/ui/badge" 6 | import { Checkbox } from "@/components/ui/checkbox" 7 | import { DataTableColumnHeader } from "@/components/ui/date-table-column-header" 8 | import { categories } from "@/components/categories" 9 | import { Action } from "@/components/schema" 10 | import { ArrowUpDown, MoreHorizontal } from "lucide-react"; 11 | import { Button } from "@/components/ui/button"; 12 | import numeral from 'numeral'; 13 | 14 | export const columns: ColumnDef[] = [ 15 | { 16 | accessorKey: "chain", 17 | header: ({ column }) => ( 18 | 19 | ), 20 | cell: ({ row }) =>
{row.getValue("chain")}
, 21 | enableSorting: false, 22 | enableHiding: false, 23 | }, 24 | { 25 | accessorKey: "uniswap_trade_fee_usd", 26 | header: ({ column }) => ( 27 | 28 | ), 29 | cell: ({ row }) => { 30 | const amount = parseFloat(row.getValue("uniswap_trade_fee_usd")); 31 | const displayValue = amount === 0 ? '-' : amount.toFixed(4); 32 | return ( 33 |
34 | ${displayValue} 35 |
36 | ) 37 | }, 38 | }, 39 | { 40 | accessorKey: "seaport_trade_fee_usd", 41 | header: ({ column }) => ( 42 | 43 | ), 44 | cell: ({ row }) => { 45 | const amount = parseFloat(row.getValue("seaport_trade_fee_usd")); 46 | const displayValue = amount === 0 ? '-' : amount.toFixed(4); 47 | return ( 48 |
49 | ${displayValue} 50 |
51 | ) 52 | }, 53 | }, 54 | { 55 | accessorKey: "usdc_transfer_fee_usd", 56 | header: ({ column }) => ( 57 | 58 | ), 59 | cell: ({ row }) => { 60 | const amount = parseFloat(row.getValue("usdc_transfer_fee_usd")); 61 | const displayValue = amount === 0 ? '-' : amount.toFixed(4); 62 | return ( 63 |
64 | ${displayValue} 65 |
66 | ) 67 | }, 68 | }, 69 | { 70 | accessorKey: "eth_transfer_fee_usd", 71 | header: ({ column }) => ( 72 | 73 | ), 74 | cell: ({ row }) => { 75 | const amount = parseFloat(row.getValue("eth_transfer_fee_usd")); 76 | const displayValue = amount === 0 ? '-' : amount.toFixed(5); 77 | return ( 78 |
79 | ${displayValue} 80 |
81 | ) 82 | }, 83 | }, 84 | 85 | { 86 | accessorKey: "category", 87 | header: ({ column }) => ( 88 | 89 | ), 90 | cell: ({ row }) => { 91 | const status = categories.find( 92 | (status) => status.value === row.getValue("category") 93 | ) 94 | 95 | if (!status) { 96 | return null 97 | } 98 | 99 | return ( 100 |
101 | {status.icon && ( 102 | 103 | )} 104 | {status.label} 105 |
106 | ) 107 | }, 108 | filterFn: (row, id, value) => { 109 | return value.includes(row.getValue(id)) 110 | }, 111 | }, 112 | ] -------------------------------------------------------------------------------- /components/ui/dialog.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import * as React from "react" 4 | import * as DialogPrimitive from "@radix-ui/react-dialog" 5 | import { X } from "lucide-react" 6 | 7 | import { cn } from "@/lib/utils" 8 | 9 | const Dialog = DialogPrimitive.Root 10 | 11 | const DialogTrigger = DialogPrimitive.Trigger 12 | 13 | const DialogPortal = DialogPrimitive.Portal 14 | 15 | const DialogClose = DialogPrimitive.Close 16 | 17 | const DialogOverlay = React.forwardRef< 18 | React.ElementRef, 19 | React.ComponentPropsWithoutRef 20 | >(({ className, ...props }, ref) => ( 21 | 29 | )) 30 | DialogOverlay.displayName = DialogPrimitive.Overlay.displayName 31 | 32 | const DialogContent = React.forwardRef< 33 | React.ElementRef, 34 | React.ComponentPropsWithoutRef 35 | >(({ className, children, ...props }, ref) => ( 36 | 37 | 38 | 46 | {children} 47 | 48 | 49 | Close 50 | 51 | 52 | 53 | )) 54 | DialogContent.displayName = DialogPrimitive.Content.displayName 55 | 56 | const DialogHeader = ({ 57 | className, 58 | ...props 59 | }: React.HTMLAttributes) => ( 60 |
67 | ) 68 | DialogHeader.displayName = "DialogHeader" 69 | 70 | const DialogFooter = ({ 71 | className, 72 | ...props 73 | }: React.HTMLAttributes) => ( 74 |
81 | ) 82 | DialogFooter.displayName = "DialogFooter" 83 | 84 | const DialogTitle = React.forwardRef< 85 | React.ElementRef, 86 | React.ComponentPropsWithoutRef 87 | >(({ className, ...props }, ref) => ( 88 | 96 | )) 97 | DialogTitle.displayName = DialogPrimitive.Title.displayName 98 | 99 | const DialogDescription = React.forwardRef< 100 | React.ElementRef, 101 | React.ComponentPropsWithoutRef 102 | >(({ className, ...props }, ref) => ( 103 | 108 | )) 109 | DialogDescription.displayName = DialogPrimitive.Description.displayName 110 | 111 | export { 112 | Dialog, 113 | DialogPortal, 114 | DialogOverlay, 115 | DialogClose, 116 | DialogTrigger, 117 | DialogContent, 118 | DialogHeader, 119 | DialogFooter, 120 | DialogTitle, 121 | DialogDescription, 122 | } 123 | -------------------------------------------------------------------------------- /components/ui/data-table-pagination.tsx: -------------------------------------------------------------------------------- 1 | import { 2 | ChevronLeftIcon, 3 | ChevronRightIcon, 4 | DoubleArrowLeftIcon, 5 | DoubleArrowRightIcon, 6 | } from "@radix-ui/react-icons" 7 | import { Table } from "@tanstack/react-table" 8 | 9 | import { Button } from "@/components/ui/button" 10 | import { 11 | Select, 12 | SelectContent, 13 | SelectItem, 14 | SelectTrigger, 15 | SelectValue, 16 | } from "@/components/ui/select" 17 | 18 | interface DataTablePaginationProps { 19 | table: Table 20 | } 21 | 22 | export function DataTablePagination({ 23 | table, 24 | }: DataTablePaginationProps) { 25 | return ( 26 |
27 | {/*
28 | {table.getFilteredSelectedRowModel().rows.length} of{" "} 29 | {table.getFilteredRowModel().rows.length} row(s) selected. 30 |
*/} 31 |
32 | {/*
33 |

Rows per page

34 | 51 |
*/} 52 |
53 | Page {table.getState().pagination.pageIndex + 1} of{" "} 54 | {table.getPageCount()} 55 |
56 |
57 | 66 | 75 | 84 | 93 |
94 |
95 |
96 | ) 97 | } -------------------------------------------------------------------------------- /components/ui/command.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import * as React from "react" 4 | import { type DialogProps } from "@radix-ui/react-dialog" 5 | import { Command as CommandPrimitive } from "cmdk" 6 | import { Search } from "lucide-react" 7 | 8 | import { cn } from "@/lib/utils" 9 | import { Dialog, DialogContent } from "@/components/ui/dialog" 10 | 11 | const Command = React.forwardRef< 12 | React.ElementRef, 13 | React.ComponentPropsWithoutRef 14 | >(({ className, ...props }, ref) => ( 15 | 23 | )) 24 | Command.displayName = CommandPrimitive.displayName 25 | 26 | interface CommandDialogProps extends DialogProps {} 27 | 28 | const CommandDialog = ({ children, ...props }: CommandDialogProps) => { 29 | return ( 30 | 31 | 32 | 33 | {children} 34 | 35 | 36 | 37 | ) 38 | } 39 | 40 | const CommandInput = React.forwardRef< 41 | React.ElementRef, 42 | React.ComponentPropsWithoutRef 43 | >(({ className, ...props }, ref) => ( 44 |
45 | 46 | 54 |
55 | )) 56 | 57 | CommandInput.displayName = CommandPrimitive.Input.displayName 58 | 59 | const CommandList = React.forwardRef< 60 | React.ElementRef, 61 | React.ComponentPropsWithoutRef 62 | >(({ className, ...props }, ref) => ( 63 | 68 | )) 69 | 70 | CommandList.displayName = CommandPrimitive.List.displayName 71 | 72 | const CommandEmpty = React.forwardRef< 73 | React.ElementRef, 74 | React.ComponentPropsWithoutRef 75 | >((props, ref) => ( 76 | 81 | )) 82 | 83 | CommandEmpty.displayName = CommandPrimitive.Empty.displayName 84 | 85 | const CommandGroup = React.forwardRef< 86 | React.ElementRef, 87 | React.ComponentPropsWithoutRef 88 | >(({ className, ...props }, ref) => ( 89 | 97 | )) 98 | 99 | CommandGroup.displayName = CommandPrimitive.Group.displayName 100 | 101 | const CommandSeparator = React.forwardRef< 102 | React.ElementRef, 103 | React.ComponentPropsWithoutRef 104 | >(({ className, ...props }, ref) => ( 105 | 110 | )) 111 | CommandSeparator.displayName = CommandPrimitive.Separator.displayName 112 | 113 | const CommandItem = React.forwardRef< 114 | React.ElementRef, 115 | React.ComponentPropsWithoutRef 116 | >(({ className, ...props }, ref) => ( 117 | 125 | )) 126 | 127 | CommandItem.displayName = CommandPrimitive.Item.displayName 128 | 129 | const CommandShortcut = ({ 130 | className, 131 | ...props 132 | }: React.HTMLAttributes) => { 133 | return ( 134 | 141 | ) 142 | } 143 | CommandShortcut.displayName = "CommandShortcut" 144 | 145 | export { 146 | Command, 147 | CommandDialog, 148 | CommandInput, 149 | CommandList, 150 | CommandEmpty, 151 | CommandGroup, 152 | CommandItem, 153 | CommandShortcut, 154 | CommandSeparator, 155 | } 156 | -------------------------------------------------------------------------------- /components/ui/navigation-menu.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react" 2 | import * as NavigationMenuPrimitive from "@radix-ui/react-navigation-menu" 3 | import { cva } from "class-variance-authority" 4 | import { ChevronDown } from "lucide-react" 5 | 6 | import { cn } from "@/lib/utils" 7 | 8 | const NavigationMenu = React.forwardRef< 9 | React.ElementRef, 10 | React.ComponentPropsWithoutRef 11 | >(({ className, children, ...props }, ref) => ( 12 | 20 | {children} 21 | 22 | 23 | )) 24 | NavigationMenu.displayName = NavigationMenuPrimitive.Root.displayName 25 | 26 | const NavigationMenuList = React.forwardRef< 27 | React.ElementRef, 28 | React.ComponentPropsWithoutRef 29 | >(({ className, ...props }, ref) => ( 30 | 38 | )) 39 | NavigationMenuList.displayName = NavigationMenuPrimitive.List.displayName 40 | 41 | const NavigationMenuItem = NavigationMenuPrimitive.Item 42 | 43 | const navigationMenuTriggerStyle = cva( 44 | "group inline-flex h-10 w-max items-center justify-center rounded-md bg-background px-4 py-2 text-sm font-medium transition-colors hover:bg-accent hover:text-accent-foreground focus:bg-accent focus:text-accent-foreground focus:outline-none disabled:pointer-events-none disabled:opacity-50 data-[active]:bg-accent/50 data-[state=open]:bg-accent/50" 45 | ) 46 | 47 | const NavigationMenuTrigger = React.forwardRef< 48 | React.ElementRef, 49 | React.ComponentPropsWithoutRef 50 | >(({ className, children, ...props }, ref) => ( 51 | 56 | {children}{" "} 57 | 62 | )) 63 | NavigationMenuTrigger.displayName = NavigationMenuPrimitive.Trigger.displayName 64 | 65 | const NavigationMenuContent = React.forwardRef< 66 | React.ElementRef, 67 | React.ComponentPropsWithoutRef 68 | >(({ className, ...props }, ref) => ( 69 | 77 | )) 78 | NavigationMenuContent.displayName = NavigationMenuPrimitive.Content.displayName 79 | 80 | const NavigationMenuLink = NavigationMenuPrimitive.Link 81 | 82 | const NavigationMenuViewport = React.forwardRef< 83 | React.ElementRef, 84 | React.ComponentPropsWithoutRef 85 | >(({ className, ...props }, ref) => ( 86 |
87 | 95 |
96 | )) 97 | NavigationMenuViewport.displayName = 98 | NavigationMenuPrimitive.Viewport.displayName 99 | 100 | const NavigationMenuIndicator = React.forwardRef< 101 | React.ElementRef, 102 | React.ComponentPropsWithoutRef 103 | >(({ className, ...props }, ref) => ( 104 | 112 |
113 | 114 | )) 115 | NavigationMenuIndicator.displayName = 116 | NavigationMenuPrimitive.Indicator.displayName 117 | 118 | export { 119 | navigationMenuTriggerStyle, 120 | NavigationMenu, 121 | NavigationMenuList, 122 | NavigationMenuItem, 123 | NavigationMenuContent, 124 | NavigationMenuTrigger, 125 | NavigationMenuLink, 126 | NavigationMenuIndicator, 127 | NavigationMenuViewport, 128 | } 129 | -------------------------------------------------------------------------------- /components/ui/select.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import * as React from "react" 4 | import * as SelectPrimitive from "@radix-ui/react-select" 5 | import { Check, ChevronDown, ChevronUp } from "lucide-react" 6 | 7 | import { cn } from "@/lib/utils" 8 | 9 | const Select = SelectPrimitive.Root 10 | 11 | const SelectGroup = SelectPrimitive.Group 12 | 13 | const SelectValue = SelectPrimitive.Value 14 | 15 | const SelectTrigger = React.forwardRef< 16 | React.ElementRef, 17 | React.ComponentPropsWithoutRef 18 | >(({ className, children, ...props }, ref) => ( 19 | span]:line-clamp-1", 23 | className 24 | )} 25 | {...props} 26 | > 27 | {children} 28 | 29 | 30 | 31 | 32 | )) 33 | SelectTrigger.displayName = SelectPrimitive.Trigger.displayName 34 | 35 | const SelectScrollUpButton = React.forwardRef< 36 | React.ElementRef, 37 | React.ComponentPropsWithoutRef 38 | >(({ className, ...props }, ref) => ( 39 | 47 | 48 | 49 | )) 50 | SelectScrollUpButton.displayName = SelectPrimitive.ScrollUpButton.displayName 51 | 52 | const SelectScrollDownButton = React.forwardRef< 53 | React.ElementRef, 54 | React.ComponentPropsWithoutRef 55 | >(({ className, ...props }, ref) => ( 56 | 64 | 65 | 66 | )) 67 | SelectScrollDownButton.displayName = 68 | SelectPrimitive.ScrollDownButton.displayName 69 | 70 | const SelectContent = React.forwardRef< 71 | React.ElementRef, 72 | React.ComponentPropsWithoutRef 73 | >(({ className, children, position = "popper", ...props }, ref) => ( 74 | 75 | 86 | 87 | 94 | {children} 95 | 96 | 97 | 98 | 99 | )) 100 | SelectContent.displayName = SelectPrimitive.Content.displayName 101 | 102 | const SelectLabel = React.forwardRef< 103 | React.ElementRef, 104 | React.ComponentPropsWithoutRef 105 | >(({ className, ...props }, ref) => ( 106 | 111 | )) 112 | SelectLabel.displayName = SelectPrimitive.Label.displayName 113 | 114 | const SelectItem = React.forwardRef< 115 | React.ElementRef, 116 | React.ComponentPropsWithoutRef 117 | >(({ className, children, ...props }, ref) => ( 118 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | {children} 133 | 134 | )) 135 | SelectItem.displayName = SelectPrimitive.Item.displayName 136 | 137 | const SelectSeparator = React.forwardRef< 138 | React.ElementRef, 139 | React.ComponentPropsWithoutRef 140 | >(({ className, ...props }, ref) => ( 141 | 146 | )) 147 | SelectSeparator.displayName = SelectPrimitive.Separator.displayName 148 | 149 | export { 150 | Select, 151 | SelectGroup, 152 | SelectValue, 153 | SelectTrigger, 154 | SelectContent, 155 | SelectLabel, 156 | SelectItem, 157 | SelectSeparator, 158 | SelectScrollUpButton, 159 | SelectScrollDownButton, 160 | } 161 | -------------------------------------------------------------------------------- /components/data-table.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import * as React from "react" 4 | import { 5 | ColumnDef, 6 | ColumnFiltersState, 7 | SortingState, 8 | VisibilityState, 9 | flexRender, 10 | getCoreRowModel, 11 | getFacetedRowModel, 12 | getFacetedUniqueValues, 13 | getFilteredRowModel, 14 | getPaginationRowModel, 15 | getSortedRowModel, 16 | useReactTable, 17 | } from "@tanstack/react-table" 18 | 19 | import { 20 | Table, 21 | TableBody, 22 | TableCell, 23 | TableHead, 24 | TableHeader, 25 | TableRow, 26 | } from "@/components/ui/table" 27 | 28 | import Link from 'next/link'; 29 | import { ExternalLink } from 'lucide-react'; 30 | 31 | import { DataTablePagination } from "@/components/ui/data-table-pagination" 32 | import { DataTableToolbar } from "@/components/ui/data-table-toolbar" 33 | 34 | interface DataTableProps { 35 | columns: ColumnDef[] 36 | data: TData[] 37 | link_names: boolean 38 | } 39 | 40 | export function DataTable({ 41 | columns, 42 | data, 43 | link_names, 44 | }: DataTableProps) { 45 | const [rowSelection, setRowSelection] = React.useState({}) 46 | const [columnVisibility, setColumnVisibility] = 47 | React.useState({}) 48 | const [columnFilters, setColumnFilters] = React.useState( 49 | [] 50 | ) 51 | const [sorting, setSorting] = React.useState([]) 52 | 53 | const table = useReactTable({ 54 | data, 55 | columns, 56 | state: { 57 | sorting, 58 | columnVisibility, 59 | rowSelection, 60 | columnFilters, 61 | }, 62 | enableRowSelection: true, 63 | onRowSelectionChange: setRowSelection, 64 | onSortingChange: setSorting, 65 | onColumnFiltersChange: setColumnFilters, 66 | onColumnVisibilityChange: setColumnVisibility, 67 | getCoreRowModel: getCoreRowModel(), 68 | getFilteredRowModel: getFilteredRowModel(), 69 | getPaginationRowModel: getPaginationRowModel(), 70 | getSortedRowModel: getSortedRowModel(), 71 | getFacetedRowModel: getFacetedRowModel(), 72 | getFacetedUniqueValues: getFacetedUniqueValues(), 73 | }) 74 | 75 | // Function to calculate background color based on cell value 76 | const getBackgroundColor = (value: number) => { 77 | const normalizedValue = Math.min(value, 3) / 3; 78 | const lightness = 100 - 23 * normalizedValue; 79 | return `hsl(240, 100%, ${lightness}%)`; 80 | } 81 | 82 | 83 | return ( 84 |
85 | 86 |
87 | 88 | 89 | {table.getHeaderGroups().map((headerGroup) => ( 90 | 91 | {headerGroup.headers.map((header) => { 92 | return ( 93 | 94 | {header.isPlaceholder 95 | ? null 96 | : flexRender( 97 | header.column.columnDef.header, 98 | header.getContext() 99 | )} 100 | 101 | ) 102 | })} 103 | 104 | ))} 105 | 106 | 107 | {table.getRowModel().rows?.length ? ( 108 | table.getRowModel().rows.map((row) => ( 109 | 113 | {row.getVisibleCells().map((cell, index) => { 114 | // Determine background color based on cell value 115 | const bgColor = getBackgroundColor(Number(cell.getValue())); 116 | return ( 117 | 118 | {index === 0 && link_names ? ( 119 | 120 |

121 | {cell.getValue() as string} 122 | {/* */} 123 |

124 | 125 | ) : ( 126 | flexRender(cell.column.columnDef.cell, cell.getContext()) 127 | )} 128 | {/* {flexRender( 129 | cell.column.columnDef.cell, 130 | cell.getContext() 131 | )} */} 132 |
133 | ) 134 | })} 135 |
136 | )) 137 | ) : ( 138 | 139 | 143 | No results. 144 | 145 | 146 | )} 147 |
148 |
149 |
150 | {/* */} 151 |
152 | ) 153 | } -------------------------------------------------------------------------------- /components/ui/data-table-faceted-filter.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react" 2 | import { CheckIcon, PlusCircledIcon } from "@radix-ui/react-icons" 3 | import { Column } from "@tanstack/react-table" 4 | 5 | import { cn } from "@/lib/utils" 6 | import { Badge } from "@/components/ui/badge" 7 | import { Button } from "@/components/ui/button" 8 | import { 9 | Command, 10 | CommandEmpty, 11 | CommandGroup, 12 | CommandInput, 13 | CommandItem, 14 | CommandList, 15 | CommandSeparator, 16 | } from "@/components/ui/command" 17 | import { 18 | Popover, 19 | PopoverContent, 20 | PopoverTrigger, 21 | } from "@/components/ui/popover" 22 | import { Separator } from "@/components/ui/separator" 23 | 24 | interface DataTableFacetedFilterProps { 25 | column?: Column 26 | title?: string 27 | options: { 28 | label: string 29 | value: string 30 | icon?: React.ComponentType<{ className?: string }> 31 | }[] 32 | } 33 | 34 | export function DataTableFacetedFilter({ 35 | column, 36 | title, 37 | options, 38 | }: DataTableFacetedFilterProps) { 39 | const facets = column?.getFacetedUniqueValues() 40 | const selectedValues = new Set(column?.getFilterValue() as string[]) 41 | 42 | return ( 43 | 44 | 45 | 82 | 83 | 84 | 85 | 86 | 87 | No results found. 88 | 89 | {options.map((option) => { 90 | const isSelected = selectedValues.has(option.value) 91 | return ( 92 | { 95 | if (isSelected) { 96 | selectedValues.delete(option.value) 97 | } else { 98 | selectedValues.add(option.value) 99 | } 100 | const filterValues = Array.from(selectedValues) 101 | column?.setFilterValue( 102 | filterValues.length ? filterValues : undefined 103 | ) 104 | }} 105 | > 106 |
114 | 115 |
116 | {option.icon && ( 117 | 118 | )} 119 | {option.label} 120 | {facets?.get(option.value) && ( 121 | 122 | {facets.get(option.value)} 123 | 124 | )} 125 |
126 | ) 127 | })} 128 |
129 | {selectedValues.size > 0 && ( 130 | <> 131 | 132 | 133 | column?.setFilterValue(undefined)} 135 | className="justify-center text-center" 136 | > 137 | Clear filters 138 | 139 | 140 | 141 | )} 142 |
143 |
144 |
145 |
146 | ) 147 | } -------------------------------------------------------------------------------- /components/ui/dropdown-menu.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import * as React from "react" 4 | import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu" 5 | import { Check, ChevronRight, Circle } from "lucide-react" 6 | 7 | import { cn } from "@/lib/utils" 8 | 9 | const DropdownMenu = DropdownMenuPrimitive.Root 10 | 11 | const DropdownMenuTrigger = DropdownMenuPrimitive.Trigger 12 | 13 | const DropdownMenuGroup = DropdownMenuPrimitive.Group 14 | 15 | const DropdownMenuPortal = DropdownMenuPrimitive.Portal 16 | 17 | const DropdownMenuSub = DropdownMenuPrimitive.Sub 18 | 19 | const DropdownMenuRadioGroup = DropdownMenuPrimitive.RadioGroup 20 | 21 | const DropdownMenuSubTrigger = React.forwardRef< 22 | React.ElementRef, 23 | React.ComponentPropsWithoutRef & { 24 | inset?: boolean 25 | } 26 | >(({ className, inset, children, ...props }, ref) => ( 27 | 36 | {children} 37 | 38 | 39 | )) 40 | DropdownMenuSubTrigger.displayName = 41 | DropdownMenuPrimitive.SubTrigger.displayName 42 | 43 | const DropdownMenuSubContent = React.forwardRef< 44 | React.ElementRef, 45 | React.ComponentPropsWithoutRef 46 | >(({ className, ...props }, ref) => ( 47 | 55 | )) 56 | DropdownMenuSubContent.displayName = 57 | DropdownMenuPrimitive.SubContent.displayName 58 | 59 | const DropdownMenuContent = React.forwardRef< 60 | React.ElementRef, 61 | React.ComponentPropsWithoutRef 62 | >(({ className, sideOffset = 4, ...props }, ref) => ( 63 | 64 | 73 | 74 | )) 75 | DropdownMenuContent.displayName = DropdownMenuPrimitive.Content.displayName 76 | 77 | const DropdownMenuItem = React.forwardRef< 78 | React.ElementRef, 79 | React.ComponentPropsWithoutRef & { 80 | inset?: boolean 81 | } 82 | >(({ className, inset, ...props }, ref) => ( 83 | 92 | )) 93 | DropdownMenuItem.displayName = DropdownMenuPrimitive.Item.displayName 94 | 95 | const DropdownMenuCheckboxItem = React.forwardRef< 96 | React.ElementRef, 97 | React.ComponentPropsWithoutRef 98 | >(({ className, children, checked, ...props }, ref) => ( 99 | 108 | 109 | 110 | 111 | 112 | 113 | {children} 114 | 115 | )) 116 | DropdownMenuCheckboxItem.displayName = 117 | DropdownMenuPrimitive.CheckboxItem.displayName 118 | 119 | const DropdownMenuRadioItem = React.forwardRef< 120 | React.ElementRef, 121 | React.ComponentPropsWithoutRef 122 | >(({ className, children, ...props }, ref) => ( 123 | 131 | 132 | 133 | 134 | 135 | 136 | {children} 137 | 138 | )) 139 | DropdownMenuRadioItem.displayName = DropdownMenuPrimitive.RadioItem.displayName 140 | 141 | const DropdownMenuLabel = React.forwardRef< 142 | React.ElementRef, 143 | React.ComponentPropsWithoutRef & { 144 | inset?: boolean 145 | } 146 | >(({ className, inset, ...props }, ref) => ( 147 | 156 | )) 157 | DropdownMenuLabel.displayName = DropdownMenuPrimitive.Label.displayName 158 | 159 | const DropdownMenuSeparator = React.forwardRef< 160 | React.ElementRef, 161 | React.ComponentPropsWithoutRef 162 | >(({ className, ...props }, ref) => ( 163 | 168 | )) 169 | DropdownMenuSeparator.displayName = DropdownMenuPrimitive.Separator.displayName 170 | 171 | const DropdownMenuShortcut = ({ 172 | className, 173 | ...props 174 | }: React.HTMLAttributes) => { 175 | return ( 176 | 180 | ) 181 | } 182 | DropdownMenuShortcut.displayName = "DropdownMenuShortcut" 183 | 184 | export { 185 | DropdownMenu, 186 | DropdownMenuTrigger, 187 | DropdownMenuContent, 188 | DropdownMenuItem, 189 | DropdownMenuCheckboxItem, 190 | DropdownMenuRadioItem, 191 | DropdownMenuLabel, 192 | DropdownMenuSeparator, 193 | DropdownMenuShortcut, 194 | DropdownMenuGroup, 195 | DropdownMenuPortal, 196 | DropdownMenuSub, 197 | DropdownMenuSubContent, 198 | DropdownMenuSubTrigger, 199 | DropdownMenuRadioGroup, 200 | } 201 | --------------------------------------------------------------------------------