{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "careers-1",
  "title": "Careers 1: open roles with filters and perks",
  "author": "Ballmac <https://ui.ballmac.com>",
  "description": "Open roles grouped by team with team filter chips, live search and an empty state, then a hairline grid of perks. Every role row is one large link with location and type.",
  "dependencies": [
    "lucide-react"
  ],
  "registryDependencies": [
    "utils",
    "https://ui.ballmac.com/r/button.json",
    "https://ui.ballmac.com/r/search-field.json"
  ],
  "files": [
    {
      "path": "registry/ballmac/components/blocks/careers-1/careers-1.tsx",
      "content": "// Ballmac UI: Careers 1. https://ui.ballmac.com/blocks/careers-1\n\"use client\"\n\nimport * as React from \"react\"\nimport { ArrowUpRight, BookOpen, Globe, HeartPulse, Laptop, MapPin, Plane, SearchX, Wallet } from \"lucide-react\"\n\nimport { buttonVariants } from \"@/components/ballmac/button\"\nimport { SearchField } from \"@/components/ballmac/search-field\"\nimport { cn } from \"@/lib/utils\"\n\ntype Careers1Job = {\n  title: string\n  /** Team, used to group and filter the list. */\n  team: string\n  /** Where the role is based, e.g. \"Remote (Europe)\". */\n  location: string\n  /** Employment type. */\n  type?: string\n  /** Where to apply. */\n  href: string\n}\n\ntype Careers1Perk = {\n  icon?: React.ReactNode\n  title: string\n  description: string\n}\n\ntype Careers1Props = Omit<React.ComponentProps<\"section\">, \"title\"> & {\n  /** Label above the heading. */\n  eyebrow?: string\n  /** Section heading. */\n  title?: string\n  /** One or two sentences under the heading. */\n  description?: string\n  /** Open roles. */\n  jobs?: Careers1Job[]\n  /** Reasons to join, shown as a grid under the list. Pass an empty array to hide it. */\n  perks?: Careers1Perk[]\n  /** Link for people who do not see a fitting role. Pass null to hide it. */\n  openApplication?: { label: string; href: string } | null\n}\n\nconst defaultJobs: Careers1Job[] = [\n  { title: \"Senior Backend Engineer\", team: \"Engineering\", location: \"Remote (Europe)\", type: \"Full-time\", href: \"#\" },\n  { title: \"Staff Frontend Engineer\", team: \"Engineering\", location: \"Remote (Americas)\", type: \"Full-time\", href: \"#\" },\n  { title: \"Site Reliability Engineer\", team: \"Engineering\", location: \"Lisbon\", type: \"Full-time\", href: \"#\" },\n  { title: \"Product Designer\", team: \"Design\", location: \"Remote (Europe)\", type: \"Full-time\", href: \"#\" },\n  { title: \"Brand Designer\", team: \"Design\", location: \"Berlin\", type: \"Contract\", href: \"#\" },\n  { title: \"Customer Success Manager\", team: \"Customer\", location: \"Remote (Americas)\", type: \"Full-time\", href: \"#\" },\n  { title: \"Support Engineer\", team: \"Customer\", location: \"Remote (Europe)\", type: \"Full-time\", href: \"#\" },\n  { title: \"Head of Growth\", team: \"Marketing\", location: \"Lisbon\", type: \"Full-time\", href: \"#\" },\n]\n\nconst defaultPerks: Careers1Perk[] = [\n  { icon: <Globe />, title: \"Remote-first\", description: \"Work from anywhere in our time zones. Offices are optional meeting places.\" },\n  { icon: <Wallet />, title: \"Real ownership\", description: \"Meaningful equity for every full-time teammate, with a four-year schedule.\" },\n  { icon: <BookOpen />, title: \"Learning budget\", description: \"$2,000 a year for books, courses and conferences, no questions asked.\" },\n  { icon: <HeartPulse />, title: \"Health first\", description: \"Private health cover, mental health support and 25 days of paid leave.\" },\n  { icon: <Laptop />, title: \"Great tools\", description: \"Pick your own hardware and set up a home office with a $1,500 allowance.\" },\n  { icon: <Plane />, title: \"Twice-a-year retreats\", description: \"The whole company meets somewhere warm to plan, build and rest.\" },\n]\n\nfunction Careers1({\n  eyebrow = \"Careers\",\n  title = \"Build calm software with us.\",\n  description = \"We are a small, remote team that ships often and cares about the details. Come do the best work of your career.\",\n  jobs = defaultJobs,\n  perks = defaultPerks,\n  openApplication = { label: \"Send an open application\", href: \"#\" },\n  className,\n  ...props\n}: Careers1Props) {\n  const [team, setTeam] = React.useState(\"All\")\n  const [query, setQuery] = React.useState(\"\")\n  const teams = [\"All\", ...Array.from(new Set(jobs.map((j) => j.team)))]\n  const q = query.trim().toLowerCase()\n  const visible = jobs.filter(\n    (j) => (team === \"All\" || j.team === team) && (!q || `${j.title} ${j.team} ${j.location}`.toLowerCase().includes(q))\n  )\n  const groups = Array.from(new Set(visible.map((j) => j.team))).map((t) => ({ team: t, jobs: visible.filter((j) => j.team === t) }))\n\n  return (\n    <section data-slot=\"careers-1\" className={cn(\"mx-auto max-w-5xl px-4 py-20 sm:px-6 md:py-28\", className)} {...props}>\n      <div className=\"max-w-2xl\">\n        <p className=\"text-muted-foreground text-sm font-medium\">{eyebrow}</p>\n        <h2 className=\"mt-3 text-3xl font-semibold tracking-[-0.035em] text-balance sm:text-4xl lg:text-5xl\">{title}</h2>\n        <p className=\"text-muted-foreground mt-4 text-lg text-pretty\">{description}</p>\n      </div>\n\n      <div className=\"mt-12 flex flex-col gap-4 lg:flex-row lg:items-center lg:justify-between\">\n        <div role=\"group\" aria-label=\"Filter by team\" className=\"-mx-4 flex gap-1.5 overflow-x-auto px-4 pb-1 [scrollbar-width:none] sm:mx-0 sm:px-0\">\n          {teams.map((t) => (\n            <button\n              key={t}\n              type=\"button\"\n              aria-pressed={team === t}\n              onClick={() => setTeam(t)}\n              className={cn(\n                \"focus-visible:ring-ring/50 h-9 shrink-0 rounded-full border px-4 text-sm font-medium outline-none transition-colors focus-visible:ring-[3px]\",\n                team === t ? \"bg-foreground text-background border-transparent\" : \"text-muted-foreground hover:text-foreground hover:bg-accent\"\n              )}\n            >\n              {t}\n            </button>\n          ))}\n        </div>\n        <SearchField className=\"lg:w-72\" label=\"Search roles\" placeholder=\"Search roles or places\" value={query} onValueChange={setQuery} />\n      </div>\n\n      <p role=\"status\" className=\"sr-only\">{visible.length} open {visible.length === 1 ? \"role\" : \"roles\"} shown.</p>\n\n      <div className=\"mt-8 space-y-8\">\n        {groups.map((g) => (\n          <div key={g.team}>\n            <h3 className=\"text-muted-foreground flex items-center gap-2 px-1 pb-3 text-sm font-medium\">\n              {g.team}\n              <span className=\"bg-muted text-foreground rounded-full px-2 py-0.5 text-xs tabular-nums\">{g.jobs.length}</span>\n            </h3>\n            <ul className=\"bg-card divide-y overflow-hidden rounded-3xl border\">\n              {g.jobs.map((j) => (\n                <li key={j.title + j.location}>\n                  <a href={j.href} className=\"group/job hover:bg-accent/60 focus-visible:ring-ring/50 flex items-center gap-4 px-5 py-4 outline-none transition-colors focus-visible:ring-[3px] focus-visible:ring-inset sm:px-6 sm:py-5\">\n                    <span className=\"min-w-0 flex-1\">\n                      <span className=\"block font-medium text-balance\">{j.title}</span>\n                      <span className=\"text-muted-foreground mt-1 flex flex-wrap items-center gap-x-3 gap-y-1 text-sm\">\n                        <span className=\"inline-flex items-center gap-1\"><MapPin className=\"size-3.5\" aria-hidden=\"true\" />{j.location}</span>\n                        {j.type && <span>{j.type}</span>}\n                      </span>\n                    </span>\n                    <span className=\"text-muted-foreground group-hover/job:text-foreground inline-flex items-center gap-1 text-sm font-medium transition-colors\">\n                      <span className=\"hidden sm:inline\">Apply</span>\n                      <ArrowUpRight className=\"size-4 transition-transform group-hover/job:translate-x-0.5 group-hover/job:-translate-y-0.5 motion-reduce:transition-none rtl:-scale-x-100 rtl:group-hover/job:-translate-x-0.5\" aria-hidden=\"true\" />\n                    </span>\n                  </a>\n                </li>\n              ))}\n            </ul>\n          </div>\n        ))}\n        {visible.length === 0 && (\n          <div className=\"flex flex-col items-center rounded-3xl border border-dashed px-6 py-14 text-center\">\n            <span className=\"bg-muted flex size-12 items-center justify-center rounded-full\"><SearchX className=\"size-5\" aria-hidden=\"true\" /></span>\n            <p className=\"mt-4 font-semibold\">No open roles match</p>\n            <p className=\"text-muted-foreground mt-1 max-w-sm text-sm\">Try another team or a broader search. New roles are posted here first.</p>\n            <button type=\"button\" onClick={() => { setQuery(\"\"); setTeam(\"All\") }} className={buttonVariants({ variant: \"outline\", size: \"sm\", shape: \"pill\", className: \"mt-5\" })}>Reset filters</button>\n          </div>\n        )}\n      </div>\n\n      {openApplication && (\n        <p className=\"text-muted-foreground mt-8 text-center text-sm\">\n          Don’t see the right role?{\" \"}\n          <a href={openApplication.href} className=\"text-foreground focus-visible:ring-ring/50 rounded-sm font-medium underline underline-offset-4 outline-none focus-visible:ring-[3px]\">{openApplication.label}</a>.\n        </p>\n      )}\n\n      {perks.length > 0 && (\n        <div className=\"mt-24\">\n          <h3 className=\"text-2xl font-semibold tracking-[-0.03em]\">Why you’ll like it here</h3>\n          <ul className=\"bg-border mt-8 grid gap-px overflow-hidden rounded-3xl border sm:grid-cols-2 lg:grid-cols-3\">\n            {perks.map((p) => (\n              <li key={p.title} className=\"bg-card p-6 sm:p-8\">\n                <span aria-hidden=\"true\" className=\"bg-muted flex size-10 items-center justify-center rounded-xl [&_svg]:size-5\">{p.icon ?? <Globe />}</span>\n                <p className=\"mt-5 font-semibold\">{p.title}</p>\n                <p className=\"text-muted-foreground mt-1.5 text-sm leading-relaxed text-pretty\">{p.description}</p>\n              </li>\n            ))}\n          </ul>\n        </div>\n      )}\n    </section>\n  )\n}\n\nexport { Careers1, type Careers1Props, type Careers1Job, type Careers1Perk }\n",
      "type": "registry:component",
      "target": "@components/ballmac/blocks/careers-1/careers-1.tsx"
    }
  ],
  "meta": {
    "schema": 1,
    "tier": "free",
    "tags": [
      "careers",
      "jobs",
      "hiring",
      "roles",
      "filter",
      "perks"
    ],
    "version": "1.0.0",
    "updated": "2026-10-01",
    "ai": {
      "summary": "A jobs section. Pass jobs=[{ title, team, location, type?, href }] and optionally perks=[{ icon?, title, description }]; teams and filters are derived from the jobs.",
      "whenToUse": [
        "Company careers pages",
        "Open source projects looking for maintainers"
      ],
      "whenNotToUse": [
        "A full applicant tracking system"
      ],
      "composesWith": [
        "team-1",
        "contact-1",
        "header-2",
        "footer-2"
      ],
      "a11y": [
        {
          "keys": "Enter / Space on a team chip",
          "action": "Filters the roles (aria-pressed); the count is announced politely"
        },
        {
          "keys": "Tab",
          "action": "Each role is a single link with its location and type in the name"
        }
      ],
      "customization": [
        "jobs / perks arrays",
        "perks: [] hides the grid",
        "openApplication: null hides the link"
      ]
    },
    "examples": [
      "careers-1-demo",
      "careers-1-small"
    ],
    "url": "https://ui.ballmac.com/components/careers-1"
  },
  "categories": [
    "blocks",
    "careers"
  ],
  "type": "registry:block"
}