Stepper

Repo Deploy Bars

Bars twin of Repo Deploy: repository, branch and root-directory fields, then a monospace build log that fills on a timer, with progress drawn as segments instead of orb indicators.

Free, MIT licenseOpen in Browse
"use client"

import { useEffect, useId, useState } from "react"

import {
  Stepper,
  StepperContent,
  StepperItem,
  StepperList,
  StepperSeparator,
  StepperTitle,
} from "@/components/stepper/stepper"
import { Button } from "@/components/ui/button"
import { Field, FieldLabel } from "@/components/ui/field"
import { Input } from "@/components/ui/input"

// The copy: change text here, not in the markup.
const COPY = {
  title: "Deploy a project",
  back: "Back",
  next: "Continue",
  deploy: "Deploy",
  steps: [
    { title: "Connect", palette: "delft" },
    { title: "Configure", palette: "marigold" },
    { title: "Deploy", palette: "aurora" },
  ],
  repo: "Repository",
  branch: "Production branch",
  root: "Root directory",
  idle: "Press Deploy to start the build.",
  deploying: "Deploying",
  log: [
    "Cloning orbcore/registry@main",
    "Installing 299 packages",
    "Building with Next 16",
    "Uploading 1,204 files",
    "Ready at registry.orbcore.dev",
  ],
} as const

// The bars twin of repo-deploy: one segment per stage. The last step shows
// the build log as it would run.
export default function RepoDeployBars() {
  const id = useId()
  const [step, setStep] = useState(0)
  // Local stand-in for a build: lines appear on a timer. A real block
  // streams them from your deploy API.
  const [lines, setLines] = useState(0)
  const running = lines > 0 && lines < COPY.log.length
  useEffect(() => {
    if (!running) return
    const t = setTimeout(() => setLines((n) => n + 1), 700)
    return () => clearTimeout(t)
  }, [running, lines])
  const last = COPY.steps.length - 1
  return (
    <section className="mx-auto w-full max-w-lg">
      <h2 className="mb-6 text-xl font-semibold tracking-tight">
        {COPY.title}
      </h2>
      <Stepper value={step} onValueChange={setStep} variant="bars">
        <StepperList>
          {COPY.steps.map((s, i) => (
            <StepperItem key={s.title} value={i}>
              <StepperSeparator />
              <StepperTitle className="sr-only">{s.title}</StepperTitle>
            </StepperItem>
          ))}
        </StepperList>
        <StepperContent keepMounted value={0}>
          <Field>
            <FieldLabel htmlFor={`${id}rd-repo`}>{COPY.repo}</FieldLabel>
            <Input
              id={`${id}rd-repo`}
              defaultValue="github.com/orbcore/registry"
            />
          </Field>
        </StepperContent>
        <StepperContent keepMounted value={1} className="gap-4">
          <Field>
            <FieldLabel htmlFor={`${id}rd-branch`}>{COPY.branch}</FieldLabel>
            <Input id={`${id}rd-branch`} defaultValue="main" />
          </Field>
          <Field>
            <FieldLabel htmlFor={`${id}rd-root`}>{COPY.root}</FieldLabel>
            <Input id={`${id}rd-root`} defaultValue="apps/www" />
          </Field>
        </StepperContent>
        <StepperContent keepMounted value={2}>
          <pre
            aria-live="polite"
            className="min-h-24 overflow-x-auto rounded-md bg-muted p-3 font-mono text-xs leading-6 text-muted-foreground"
          >
            {lines === 0
              ? COPY.idle
              : COPY.log
                  .slice(0, lines)
                  .map((l, i) => `${String(i * 12).padStart(3, " ")}s  ${l}`)
                  .join("\n")}
          </pre>
        </StepperContent>
        <div className="flex justify-between">
          <Button
            variant="outline"
            disabled={step === 0 || running || lines === COPY.log.length}
            onClick={() => setStep(step - 1)}
          >
            {COPY.back}
          </Button>
          <Button
            disabled={running || lines === COPY.log.length}
            onClick={() => (step < last ? setStep(step + 1) : setLines(1))}
          >
            {step < last ? COPY.next : running ? COPY.deploying : COPY.deploy}
          </Button>
        </div>
      </Stepper>
    </section>
  )
}

Installation

pnpm dlx shadcn add @01orbital/repo-deploy-bars
npx shadcn add @01orbital/repo-deploy-bars
yarn shadcn add @01orbital/repo-deploy-bars
bunx --bun shadcn add @01orbital/repo-deploy-bars

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

It lands at components/blocks/stepper/repo-deploy-bars/index.tsx, as source you own and edit.

Usage

import RepoDeployBars from "@/components/blocks/stepper/repo-deploy-bars"

export default function Example() {
  return <RepoDeployBars />
}

Dependencies

Registry pieces it installs with it:

  • button
  • field
  • font-geist-mono
  • input
  • stepper