App

Activity Goal

A focus-goal card with a 13-day recharts bar chart of minutes against an adjustable goal line, stepper buttons, a met count and a Set goal button that announces the saved value.

Free, MIT licenseOpen in Browse
"use client"

import { useState } from "react"

import { ActivityGoalCard } from "@/components/activity/activity-goal-card"
import { Button } from "@/components/ui/button"

// The copy: change text here, not in the markup.
const COPY = {
  title: "Focus goal",
  body: "Deep work, held to a line you set.",
  range: "Last 13 days",
  unit: "min",
  met: "met",
  cta: "Set goal",
  series: "Focus",
  saved: "Goal set to",
} as const

const STEPPER = { down: "Fewer minutes", up: "More minutes" } as const

const DAYS = [
  { id: "day-01", name: "M", value: 120 },
  { id: "day-02", name: "T", value: 80 },
  { id: "day-03", name: "W", value: 150 },
  { id: "day-04", name: "T", value: 60 },
  { id: "day-05", name: "F", value: 95 },
  { id: "day-06", name: "S", value: 140 },
  { id: "day-07", name: "S", value: 70 },
  { id: "day-08", name: "M", value: 110 },
  { id: "day-09", name: "T", value: 165 },
  { id: "day-10", name: "W", value: 90 },
  { id: "day-11", name: "T", value: 130 },
  { id: "day-12", name: "F", value: 55 },
  { id: "day-13", name: "S", value: 145 },
]

const STEP = 15
const MIN = 30
const MAX = 180

export default function ActivityGoal() {
  const [goal, setGoal] = useState(90)
  const [appliedGoal, setAppliedGoal] = useState<number | null>(null)

  return (
    <ActivityGoalCard
      title={COPY.title}
      description={COPY.body}
      data={DAYS}
      value={goal}
      onValueChange={setGoal}
      limits={{ min: MIN, max: MAX, step: STEP }}
      labels={{
        decrease: STEPPER.down,
        increase: STEPPER.up,
        unit: COPY.unit,
        range: COPY.range,
        series: COPY.series,
        met: (count) => (
          <>
            {count} {COPY.met}
          </>
        ),
      }}
      actions={
        <>
          <Button
            className="w-full"
            size="lg"
            onClick={() => setAppliedGoal(goal)}
          >
            {COPY.cta}
          </Button>
          <span role="status" className="sr-only">
            {appliedGoal != null && `${COPY.saved} ${appliedGoal} ${COPY.unit}`}
          </span>
        </>
      }
    />
  )
}

Installation

pnpm dlx shadcn add @01orbital/activity-goal
npx shadcn add @01orbital/activity-goal
yarn shadcn add @01orbital/activity-goal
bunx --bun shadcn add @01orbital/activity-goal

Free under the MIT license. Add the @01orbital registry to components.json once (install guide), then run the command.

It lands at components/blocks/app/activity-goal/index.tsx, as source you own and edit.

Usage

import ActivityGoal from "@/components/blocks/app/activity-goal"

// The wrapper sets the width: a centred grid item otherwise shrinks to its
// content, and the frame's own cap never reaches the card. 26rem (416px) sits
// between the sm and md tokens; her call.
export default function ActivityGoalDemo() {
  return (
    <div className="w-full max-w-[26rem]">
      <ActivityGoal />
    </div>
  )
}

Dependencies

Registry pieces it installs with it:

  • activity-goal-card
  • button