# Widget component 01

> Total earnings widget with progress tracking, interactive controls, and comprehensive financial overview

A shadcn/ui block from the [@shadcn-studio](https://shadcnstudio.com) registry, listed on [shadcn.exchange](https://shadcn.exchange/r/shadcn-studio/widget-component-01).

- Page, with a live preview: https://shadcn.exchange/r/shadcn-studio/widget-component-01
- Screenshot: https://abzjhwgwpdpz7ua5.public.blob.vercel-storage.com/shots/shadcn-studio/widget-component-01-light-4abb537c12015bbb.png
- Registry item: https://shadcnstudio.com/r/new-york-v4/widget-component-01.json
- Listed: 2026-10-13, source last changed 2026-10-13

## Install

```bash
npx shadcn@latest add https://shadcnstudio.com/r/new-york-v4/widget-component-01.json
```

Run it in a project that has shadcn/ui set up. It adds the files below and installs what they depend on.

## Dependencies

- npm packages: `lucide-react`
- Registry items: `avatar`, `button`, `card`, `dropdown-menu`, `progress`

## Files

### `src/registry/new-york/blocks/dashboard-and-application/widgets-component/widget-component-01/page.tsx`

```tsx
import TotalEarningCard from '@/registry/new-york/components/blocks/widget-total-earning'

const earningData = [
  {
    img: 'https://cdn.shadcnstudio.com/ss-assets/blocks/dashboard-application/widgets/zipcar.png',
    platform: 'Zipcar',
    technologies: 'Vuejs & HTML',
    earnings: '-$23,569.26',
    progressPercentage: 75
  },
  {
    img: 'https://cdn.shadcnstudio.com/ss-assets/blocks/dashboard-application/widgets/bitbank.png',
    platform: 'Bitbank',
    technologies: 'Figma & React',
    earnings: '-$12,650.31',
    progressPercentage: 25
  },
  {
    img: 'https://cdn.shadcnstudio.com/ss-assets/blocks/dashboard-application/widgets/aviato.png',
    platform: 'Aviato',
    technologies: 'HTML & Angular',
    earnings: '-$55,699.50',
    progressPercentage: 50
  }
]

const WidgetCardPreview = () => {
  return (
    <div className='mx-auto max-w-7xl px-4 py-8 sm:px-8 sm:py-16'>
      <div className='flex justify-center'>
        <TotalEarningCard
          title='Total Earning'
          earning={24650}
          trend='up'
          percentage={10}
          comparisonText='Compare to last year ($84,325)'
          earningData={earningData}
          className='w-full max-w-100'
        />
      </div>
    </div>
  )
}

export default WidgetCardPreview
```

### `src/registry/new-york/components/blocks/widget-total-earning.tsx`

```tsx
import { ChevronDownIcon, ChevronUpIcon, EllipsisVerticalIcon } from 'lucide-react'

import { Avatar, AvatarFallback } from '@/registry/new-york/ui/avatar'
import { Button } from '@/registry/new-york/ui/button'
import { Card, CardContent, CardHeader } from '@/registry/new-york/ui/card'
import {
  DropdownMenu,
  DropdownMenuContent,
  DropdownMenuGroup,
  DropdownMenuItem,
  DropdownMenuTrigger
} from '@/registry/new-york/ui/dropdown-menu'
import { Progress } from '@/registry/new-york/ui/progress'

const listItems = ['Share', 'Update', 'Refresh']

type Props = {
  title: string
  earning: number
  trend: 'up' | 'down'
  percentage: number
  comparisonText: string
  earningData: {
    img: string
    platform: string
    technologies: string
    earnings: string
    progressPercentage: number
  }[]
  className?: string
}

const TotalEarningCard = ({ earningData, title, earning, trend, percentage, comparisonText, className }: Props) => {
  return (
    <Card className={className}>
      <CardHeader className='flex items-center justify-between'>
        <span className='text-lg font-semibold'>{title}</span>
        <DropdownMenu>
          <DropdownMenuTrigger asChild>
            <Button variant='ghost' size='icon' className='text-muted-foreground size-6 rounded-full'>
              <EllipsisVerticalIcon />
              <span className='sr-only'>Menu</span>
            </Button>
          </DropdownMenuTrigger>
          <DropdownMenuContent align='end'>
            <DropdownMenuGroup>
              {listItems.map((item, index) => (
                <DropdownMenuItem key={index}>{item}</DropdownMenuItem>
              ))}
            </DropdownMenuGroup>
          </DropdownMenuContent>
        </DropdownMenu>
      </CardHeader>
      <CardContent className='flex flex-1 flex-col gap-4'>
        <div className='flex flex-col gap-1'>
          <div className='flex items-center gap-2'>
            <span className='text-2xl font-semibold'>${earning}</span>
            <span className='flex items-center gap-1'>
              {trend === 'up' ? <ChevronUpIcon className='size-4' /> : <ChevronDownIcon className='size-4' />}
              <span className='text-sm'>{percentage}%</span>
            </span>
          </div>
          <span className='text-muted-foreground text-sm'>{comparisonText}</span>
        </div>
        <div className='flex flex-1 flex-col justify-evenly gap-4'>
          {earningData.map((earning, index) => (
            <div key={index} className='flex items-center justify-between gap-2.5'>
              <div className='flex items-center justify-between gap-2.5'>
                <Avatar className='size-11 rounded-sm'>
                  <AvatarFallback className='bg-primary/10 shrink-0 rounded-sm'>
                    <img src={earning.img} alt={earning.platform} className='size-6' />
                  </AvatarFallback>
                </Avatar>
                <div className='flex flex-col gap-1'>
                  <span className='font-medium'>{earning.platform}</span>
                  <span className='text-muted-foreground text-sm'>{earning.technologies}</span>
                </div>
              </div>
              <div className='space-y-2'>
                <p className='text-sm'>{earning.earnings}</p>
                <Progress value={earning.progressPercentage} className='w-36' />
              </div>
            </div>
          ))}
        </div>
      </CardContent>
    </Card>
  )
}

export default TotalEarningCard
```
