Search

Search bills, members, committees and pages...

Votes

The latest roll calls of the session, each with its tally and the split behind it, for either chamber.

On the site it lives at /home. Open it on the canvas, where it can be sized, coloured and rearranged with the rest.

Installation

pnpm dlx shadcn@latest add @44gov/votes

Or copy the source into your project and update the import paths to match it.

components/cards/votes.tsx
"use client" import * as React from "react"import Link from "next/link" import * as F from "@/lib/fixtures"import { useScoped } from "@/lib/policy/use-scoped"import { fmtBill, fmtDate, fmtNumber } from "@/lib/format"import { CardFrame, ComponentActions } from "@/components/card-frame"import { ChamberSeal } from "@/components/policy/imagery"import { CardFoot } from "@/components/card-foot"import { CardAnchor } from "@/components/admin/blocks/card-tools"import { CardAction, CardContent, CardHeader, CardTitle } from "@govblock/ui/components/card"import { Item, ItemContent, ItemDescription, ItemGroup, ItemMedia, ItemTitle } from "@govblock/ui/components/item" // Votes — the latest roll calls with their aye/nay split. Each row is the// vote: a two-tone bar sized by the tally, linking to the bill it decided.function Tally({ yea, nay }: { yea: number; nay: number }) {  const total = Math.max(yea + nay, 1)  return (    <span className="flex flex-col items-end gap-1">      <span className="text-sm font-semibold tabular-nums">        {fmtNumber(yea)}–{fmtNumber(nay)}      </span>      <span aria-hidden="true" className="flex h-1.5 w-16 overflow-hidden rounded-full bg-muted">        <span className="h-full" style={{ width: `${(yea / total) * 100}%`, background: "var(--chart-2)" }} />        <span className="h-full" style={{ width: `${(nay / total) * 100}%`, background: "var(--chart-5)" }} />      </span>    </span>  )} export function VotesCard() {  const [chamber, setChamber] = React.useState("")  const { data, state } = useScoped<typeof F.votes>("rollcalls", F.votes, { limit: 5, chamber: chamber || undefined })  return (    <CardFrame id="votes">      <CardHeader>        <CardAnchor>Votes</CardAnchor>        <CardAction>          <ComponentActions />        </CardAction>      </CardHeader>      <CardContent>        <ItemGroup>          {(data ?? []).map((row) => (            <Item key={row.roll_call_id} variant="muted" render={<Link href={`/bills/${row.bill_id}`} className="no-underline" />}>              <ItemMedia>                <ChamberSeal state={state} chamber={row.chamber} size={32} />              </ItemMedia>              <ItemContent>                <ItemTitle>{fmtBill(row.bill_number, state)}</ItemTitle>                <ItemDescription>                  {fmtDate(row.date, false)} · {row.chamber}                </ItemDescription>              </ItemContent>              <Tally yea={row.yea} nay={row.nay} />            </Item>          ))}        </ItemGroup>      </CardContent>      <CardFoot chamber={chamber} onChamber={setChamber} href={`/bills?state=${state}${chamber ? `&chamber=${encodeURIComponent(chamber)}` : ""}`} label="All bills" />    </CardFrame>  )} 

Usage

import { VotesCard } from "@/components/cards/votes"
<VotesCard />

Composition

The parts the block is built from, in the order they appear.

VotesCard├── CardHeader├── CardAction├── CardContent└── ItemGroup

Data

The routes the block reads, each under the jurisdiction and year in scope. Every one is documented in the API.

API Reference

VotesCard takes no props. It reads the jurisdiction and year from the page.