{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "help-1",
  "title": "Help 1: help center home",
  "author": "Ballmac <https://ui.ballmac.com>",
  "description": "A help center home: a hero search that suggests articles as you type with highlighted matches, topic cards that light up under the pointer, popular articles, and ways to reach a person with who is online.",
  "dependencies": [
    "lucide-react"
  ],
  "registryDependencies": [
    "utils",
    "https://ui.ballmac.com/r/blur-fade.json",
    "https://ui.ballmac.com/r/button.json",
    "https://ui.ballmac.com/r/cover-art.json",
    "https://ui.ballmac.com/r/dot-pattern.json",
    "https://ui.ballmac.com/r/kbd.json",
    "https://ui.ballmac.com/r/match-highlight.json",
    "https://ui.ballmac.com/r/skeleton.json",
    "https://ui.ballmac.com/r/spotlight-card.json",
    "https://ui.ballmac.com/r/state-view.json"
  ],
  "files": [
    {
      "path": "registry/ballmac/components/blocks/help-1/help-1.tsx",
      "content": "// Ballmac UI: Help 1. https://ui.ballmac.com/blocks/help-1\n\"use client\"\n\nimport * as React from \"react\"\nimport { ArrowRight, BookOpen, Code2, CreditCard, FileSearch, LifeBuoy, MessageCircle, Plug, Rocket, Search, ShieldCheck, Users, X } from \"lucide-react\"\n\nimport { BlurFade, BlurFadeGroup } from \"@/components/ballmac/blur-fade\"\nimport { Button } from \"@/components/ballmac/button\"\nimport { CoverArt } from \"@/components/ballmac/cover-art\"\nimport { DotPattern } from \"@/components/ballmac/dot-pattern\"\nimport { Kbd } from \"@/components/ballmac/kbd\"\nimport { MatchHighlight } from \"@/components/ballmac/match-highlight\"\nimport { Skeleton } from \"@/components/ballmac/skeleton\"\nimport { SpotlightCard } from \"@/components/ballmac/spotlight-card\"\nimport { frameProps, StateView, type StateProps } from \"@/components/ballmac/state-view\"\nimport { cn } from \"@/lib/utils\"\n\ntype Help1Category = {\n  id: string\n  title: string\n  description: string\n  icon?: React.ReactNode\n  /** How many articles it holds. */\n  count: number\n  href?: string\n}\n\ntype Help1Article = {\n  id: string\n  title: string\n  /** One line shown under the title in search. Also searched. */\n  summary?: string\n  categoryId: string\n  /** Reading time in minutes. */\n  minutes?: number\n  href?: string\n  /** Listed under Popular articles. */\n  popular?: boolean\n}\n\ntype Help1Channel = {\n  id: string\n  label: string\n  description: string\n  icon?: React.ReactNode\n  /** A green dot and \"Online\" when true. */\n  online?: boolean\n  /** The button's text (\"Start a chat\", \"Send an email\"). */\n  action: string\n  href?: string\n}\n\ntype Help1Props = Omit<React.ComponentProps<\"section\">, \"title\"> &\n  StateProps & {\n    title?: string\n    description?: string\n    categories?: Help1Category[]\n    articles?: Help1Article[]\n    /** Searches shown under the box as shortcuts. */\n    suggestions?: string[]\n    /** Ways to reach a person. */\n    channels?: Help1Channel[]\n    /** Called when an article is chosen from search or the Popular list. Without it, articles with an href are followed. */\n    onOpenArticle?: (article: Help1Article) => void\n    onOpenCategory?: (category: Help1Category) => void\n    onContact?: (channel: Help1Channel) => void\n    /** Called with each search, once typing has paused, for analytics or your own results. */\n    onSearch?: (query: string) => void\n    /** Text in the search box at first, with its suggestions open. */\n    defaultQuery?: string\n  }\n\nconst sampleCategories: Help1Category[] = [\n  { id: \"start\", title: \"Getting started\", description: \"Set up your workspace, invite your team and import your data.\", icon: <Rocket />, count: 14 },\n  { id: \"billing\", title: \"Billing and plans\", description: \"Invoices, seats, payment methods and changing your plan.\", icon: <CreditCard />, count: 11 },\n  { id: \"security\", title: \"Security and privacy\", description: \"Two-step sign-in, single sign-on, data export and deletion.\", icon: <ShieldCheck />, count: 9 },\n  { id: \"integrations\", title: \"Integrations\", description: \"Connect chat, calendars, code hosting and your own tools.\", icon: <Plug />, count: 18 },\n  { id: \"teams\", title: \"Teams and roles\", description: \"Permissions, groups and what each role can see and change.\", icon: <Users />, count: 8 },\n  { id: \"api\", title: \"API and webhooks\", description: \"Keys, rate limits, events and examples in four languages.\", icon: <Code2 />, count: 22 },\n]\n\nconst sampleArticles: Help1Article[] = [\n  { id: \"a1\", title: \"Reset your password\", summary: \"Get back into your account in two minutes.\", categoryId: \"security\", minutes: 2, popular: true },\n  { id: \"a2\", title: \"Download invoices and receipts\", summary: \"Find every invoice as a PDF, with your company details.\", categoryId: \"billing\", minutes: 3, popular: true },\n  { id: \"a3\", title: \"Create and rotate an API key\", summary: \"Keys are shown once. Here is how to replace one safely.\", categoryId: \"api\", minutes: 4, popular: true },\n  { id: \"a4\", title: \"Invite teammates and set their role\", summary: \"Add people by email or by link, and choose what they can do.\", categoryId: \"teams\", minutes: 3, popular: true },\n  { id: \"a5\", title: \"Turn on two-step sign-in\", summary: \"Use an authenticator app or a passkey to protect your account.\", categoryId: \"security\", minutes: 3, popular: true },\n  { id: \"a6\", title: \"Connect your chat workspace\", summary: \"Post updates to a channel and reply to alerts from chat.\", categoryId: \"integrations\", minutes: 5, popular: true },\n  { id: \"a7\", title: \"Change or cancel your plan\", summary: \"What happens to your seats and data when you change plans.\", categoryId: \"billing\", minutes: 4 },\n  { id: \"a8\", title: \"Import data from a spreadsheet\", summary: \"Upload a CSV and map its columns in under five minutes.\", categoryId: \"start\", minutes: 6 },\n  { id: \"a9\", title: \"Webhook events and retries\", summary: \"Every event we send, its payload and how failed deliveries retry.\", categoryId: \"api\", minutes: 7 },\n  { id: \"a10\", title: \"Export or delete your data\", summary: \"Download everything, or permanently remove your workspace.\", categoryId: \"security\", minutes: 4 },\n]\n\nconst sampleSuggestions = [\"Reset password\", \"Invoices\", \"API keys\", \"Two-step sign-in\"]\n\nconst sampleChannels: Help1Channel[] = [\n  { id: \"chat\", label: \"Live chat\", description: \"Replies in about 2 minutes\", icon: <MessageCircle />, online: true, action: \"Start a chat\" },\n  { id: \"email\", label: \"Email\", description: \"Replies within one working day\", icon: <LifeBuoy />, action: \"Send an email\" },\n  { id: \"docs\", label: \"Community\", description: \"Ask other people who use it\", icon: <BookOpen />, action: \"Open the forum\" },\n]\n\nconst tones = [\"bg-chart-1/20\", \"bg-chart-2/20\", \"bg-chart-3/25\", \"bg-chart-4/20\", \"bg-chart-5/20\", \"bg-chart-1/20\"]\n\nfunction HelpSkeleton() {\n  return (\n    <div className=\"grid gap-10\" aria-hidden=\"true\">\n      <div className=\"grid gap-4 sm:grid-cols-2 lg:grid-cols-3\">\n        {[0, 1, 2, 3, 4, 5].map((i) => (\n          <Skeleton key={i} className=\"h-40 rounded-2xl\" />\n        ))}\n      </div>\n      <div className=\"grid gap-3 sm:grid-cols-2\">\n        {[0, 1, 2, 3].map((i) => (\n          <Skeleton key={i} className=\"h-14 rounded-xl\" />\n        ))}\n      </div>\n    </div>\n  )\n}\n\n/** A link when there is an address, otherwise a button, so one row works for both. */\nfunction Action({ href, onClick, className, children, ...rest }: { href?: string; onClick?: () => void; className?: string; children: React.ReactNode } & Omit<React.ComponentProps<\"button\">, \"onClick\">) {\n  if (href)\n    return (\n      <a href={href} onClick={onClick} className={className}>\n        {children}\n      </a>\n    )\n  return (\n    <button type=\"button\" onClick={onClick} className={className} {...rest}>\n      {children}\n    </button>\n  )\n}\n\n/**\n * A help center home: a hero with a search that suggests articles as you type (highlighted matches, arrow keys, press / to focus),\n * categories that light up under the pointer, the most read articles, and ways to reach a person with who is online.\n */\nfunction Help1({\n  title = \"How can we help?\",\n  description = \"Search the guides, or browse by topic.\",\n  categories = sampleCategories,\n  articles = sampleArticles,\n  suggestions = sampleSuggestions,\n  channels = sampleChannels,\n  onOpenArticle,\n  onOpenCategory,\n  onContact,\n  onSearch,\n  defaultQuery = \"\",\n  state = \"ready\",\n  onRetry,\n  emptyState,\n  errorState,\n  className,\n  ...props\n}: Help1Props) {\n  const id = React.useId()\n  const inputRef = React.useRef<HTMLInputElement>(null)\n  const [query, setQuery] = React.useState(defaultQuery)\n  const [open, setOpen] = React.useState(defaultQuery !== \"\")\n  const [active, setActive] = React.useState(0)\n  const ready = state === \"ready\" || state === \"empty\"\n  const noGuides = state === \"empty\" || (state === \"ready\" && !categories.length)\n\n  const tokens = query.trim().toLowerCase().split(/\\s+/).filter(Boolean)\n  const results = tokens.length\n    ? articles\n        .map((a) => {\n          const haystack = `${a.title} ${a.summary ?? \"\"}`.toLowerCase()\n          const inTitle = a.title.toLowerCase()\n          return { a, hit: tokens.every((t) => haystack.includes(t)), score: tokens.filter((t) => inTitle.includes(t)).length }\n        })\n        .filter((r) => r.hit)\n        .sort((x, y) => y.score - x.score)\n        .map((r) => r.a)\n        .slice(0, 6)\n    : []\n  const showList = open && ready && tokens.length > 0\n  const categoryName = (cid: string) => categories.find((c) => c.id === cid)?.title\n\n  React.useEffect(() => setActive(0), [query])\n  React.useEffect(() => {\n    if (!onSearch || !tokens.length) return\n    const t = setTimeout(() => onSearch(query.trim()), 600)\n    return () => clearTimeout(t)\n    // eslint-disable-next-line react-hooks/exhaustive-deps\n  }, [query])\n  // \"/\" jumps to the search from anywhere on the page, as it does on most documentation sites\n  React.useEffect(() => {\n    const onKey = (e: KeyboardEvent) => {\n      if (e.key !== \"/\" || e.metaKey || e.ctrlKey || e.altKey) return\n      const el = e.target as HTMLElement | null\n      if (el && (el.tagName === \"INPUT\" || el.tagName === \"TEXTAREA\" || el.tagName === \"SELECT\" || el.isContentEditable)) return\n      e.preventDefault()\n      inputRef.current?.focus()\n    }\n    document.addEventListener(\"keydown\", onKey)\n    return () => document.removeEventListener(\"keydown\", onKey)\n  }, [])\n\n  function choose(a: Help1Article) {\n    setOpen(false)\n    if (onOpenArticle) onOpenArticle(a)\n    else if (a.href) window.location.assign(a.href)\n  }\n\n  const popular = articles.filter((a) => a.popular).slice(0, 6)\n\n  const content = (\n    <div className=\"grid gap-14\">\n      <section aria-labelledby={`${id}-cats`}>\n        <h2 id={`${id}-cats`} className=\"text-xl font-semibold tracking-tight\">\n          Browse by topic\n        </h2>\n        <StateView\n          state={noGuides ? \"empty\" : \"ready\"}\n          bare\n          empty={{ title: emptyState?.title ?? \"No guides yet\", description: emptyState?.description ?? \"Help articles will be listed here.\", icon: emptyState?.icon ?? <FileSearch />, action: emptyState?.action }}\n          className=\"mt-5\"\n        >\n          <BlurFadeGroup stagger={0.06} className=\"grid gap-4 sm:grid-cols-2 lg:grid-cols-3 [&>*]:h-full [&>*>*]:h-full\" item={{ direction: \"up\", offset: 12, blur: 6 }}>\n            {categories.map((c, i) => (\n              <SpotlightCard key={c.id} size={300} className=\"focus-within:ring-ring/50 group/card flex flex-col gap-4 rounded-2xl p-6 transition-shadow duration-200 focus-within:ring-[3px] hover:shadow-md\">\n                <span aria-hidden=\"true\" className={cn(\"text-foreground flex size-11 items-center justify-center rounded-xl border [&_svg]:size-5\", tones[i % tones.length])}>\n                  {c.icon ?? <BookOpen />}\n                </span>\n                <div className=\"flex-1\">\n                  <h3 className=\"text-base font-semibold tracking-tight\">\n                    <Action href={c.href} onClick={() => onOpenCategory?.(c)} className=\"outline-none after:absolute after:inset-0 after:content-['']\">\n                      {c.title}\n                    </Action>\n                  </h3>\n                  <p className=\"text-muted-foreground mt-1.5 text-sm text-pretty\">{c.description}</p>\n                </div>\n                <p className=\"text-muted-foreground flex items-center justify-between text-xs tabular-nums\">\n                  <span>{c.count === 1 ? \"1 article\" : `${c.count} articles`}</span>\n                  <ArrowRight aria-hidden=\"true\" className=\"text-foreground size-4 -translate-x-1 opacity-0 transition-all duration-200 group-hover/card:translate-x-0 group-hover/card:opacity-100 group-focus-within/card:translate-x-0 group-focus-within/card:opacity-100 motion-reduce:transition-none rtl:translate-x-1 rtl:rotate-180 rtl:group-hover/card:translate-x-0 rtl:group-focus-within/card:translate-x-0\" />\n                </p>\n              </SpotlightCard>\n            ))}\n          </BlurFadeGroup>\n        </StateView>\n      </section>\n\n      {popular.length ? (\n        <section aria-labelledby={`${id}-popular`}>\n          <h2 id={`${id}-popular`} className=\"text-xl font-semibold tracking-tight\">\n            Popular articles\n          </h2>\n          <ol className=\"mt-5 grid gap-x-10 sm:grid-cols-2\">\n            {popular.map((a, i) => (\n              <li key={a.id} className=\"border-b\">\n                <Action href={a.href} onClick={() => choose(a)} className=\"group/row hover:bg-accent/50 focus-visible:ring-ring/50 -mx-3 flex w-[calc(100%+1.5rem)] items-center gap-4 rounded-lg px-3 py-4 text-start outline-none focus-visible:ring-[3px]\">\n                  <span aria-hidden=\"true\" className=\"text-muted-foreground w-5 shrink-0 font-mono text-xs tabular-nums\">\n                    {String(i + 1).padStart(2, \"0\")}\n                  </span>\n                  <span className=\"min-w-0 flex-1\">\n                    <span className=\"block text-sm font-medium text-pretty\">{a.title}</span>\n                    <span className=\"text-muted-foreground mt-0.5 block text-xs\">\n                      {categoryName(a.categoryId)}\n                      {a.minutes ? ` · ${a.minutes} min read` : \"\"}\n                    </span>\n                  </span>\n                  <ArrowRight aria-hidden=\"true\" className=\"text-muted-foreground group-hover/row:text-foreground size-4 shrink-0 transition-transform duration-200 group-hover/row:translate-x-0.5 motion-reduce:transition-none motion-reduce:group-hover/row:translate-x-0 rtl:rotate-180 rtl:group-hover/row:-translate-x-0.5\" />\n                </Action>\n              </li>\n            ))}\n          </ol>\n        </section>\n      ) : null}\n\n    </div>\n  )\n\n  return (\n    <section data-slot=\"help-1\" data-state={state} aria-label=\"Help center\" className={cn(\"mx-auto w-full max-w-6xl px-4 py-8 sm:px-6\", className)} {...frameProps(props)}>\n      {/* the suggestions open over the sections below, so the hero is not clipped: only its decoration is */}\n      <div className=\"bg-card relative isolate z-10 mb-14 rounded-3xl border px-5 py-14 text-center sm:px-10 sm:py-20\">\n        <div aria-hidden=\"true\" className=\"pointer-events-none absolute inset-0 -z-10 overflow-hidden rounded-3xl\">\n          <div className=\"absolute inset-0 bg-[radial-gradient(ellipse_55%_60%_at_50%_30%,color-mix(in_oklch,var(--chart-1)_14%,transparent),transparent)]\" />\n          <DotPattern width={16} height={16} radius={1} radialMask />\n        </div>\n        <BlurFade direction=\"up\" offset={10} duration={0.5}>\n          <h1 className=\"mx-auto max-w-2xl text-4xl font-semibold tracking-[-0.04em] text-balance sm:text-5xl\">{title}</h1>\n          <p className=\"text-muted-foreground mx-auto mt-3 max-w-md text-base text-pretty\">{description}</p>\n        </BlurFade>\n\n        <BlurFade direction=\"up\" offset={10} duration={0.5} delay={0.1}>\n          <div className=\"relative mx-auto mt-8 max-w-2xl text-start\">\n            <div className=\"bg-background focus-within:border-ring focus-within:ring-ring/40 flex h-14 items-center gap-3 rounded-2xl border px-4 shadow-[0_10px_30px_-14px_rgb(0_0_0/0.3)] transition-shadow focus-within:ring-[3px]\">\n              <Search aria-hidden=\"true\" className=\"text-muted-foreground size-5 shrink-0\" />\n              <input\n                ref={inputRef}\n                role=\"combobox\"\n                aria-label=\"Search the help center\"\n                aria-expanded={showList}\n                aria-controls={`${id}-results`}\n                aria-activedescendant={showList && results[active] ? `${id}-r-${results[active]!.id}` : undefined}\n                aria-autocomplete=\"list\"\n                aria-describedby={`${id}-hint`}\n                autoComplete=\"off\"\n                spellCheck={false}\n                disabled={!ready}\n                value={query}\n                placeholder=\"Search for answers\"\n                onChange={(e) => {\n                  setQuery(e.target.value)\n                  setOpen(true)\n                }}\n                onFocus={() => setOpen(true)}\n                onBlur={() => setOpen(false)}\n                onKeyDown={(e) => {\n                  if (e.key === \"Escape\") {\n                    if (open && query) setOpen(false)\n                    else setQuery(\"\")\n                    return\n                  }\n                  if (!showList || !results.length) return\n                  if (e.key === \"ArrowDown\") {\n                    e.preventDefault()\n                    setActive((a) => (a + 1) % results.length)\n                  } else if (e.key === \"ArrowUp\") {\n                    e.preventDefault()\n                    setActive((a) => (a - 1 + results.length) % results.length)\n                  } else if (e.key === \"Enter\" && results[active]) {\n                    e.preventDefault()\n                    choose(results[active]!)\n                  }\n                }}\n                className=\"placeholder:text-muted-foreground min-w-0 flex-1 bg-transparent text-base outline-none disabled:opacity-50\"\n              />\n              {query ? (\n                <button\n                  type=\"button\"\n                  aria-label=\"Clear search\"\n                  onMouseDown={(e) => e.preventDefault()}\n                  onClick={() => {\n                    setQuery(\"\")\n                    inputRef.current?.focus()\n                  }}\n                  className=\"text-muted-foreground hover:bg-accent hover:text-foreground focus-visible:ring-ring/50 flex size-7 items-center justify-center rounded-md outline-none focus-visible:ring-[3px]\"\n                >\n                  <X aria-hidden=\"true\" className=\"size-4\" />\n                </button>\n              ) : (\n                <Kbd className=\"hidden sm:inline-flex\" aria-hidden=\"true\">\n                  /\n                </Kbd>\n              )}\n            </div>\n            <p id={`${id}-hint`} className=\"sr-only\">\n              Type to see matching articles. Up and down arrows choose one, Enter opens it. Press slash anywhere on the page to jump here.\n            </p>\n\n            {showList ? (\n              <div className=\"bg-popover absolute inset-x-0 top-full z-20 mt-2 overflow-hidden rounded-2xl border p-2 shadow-xl\">\n                {results.length ? (\n                  <ul id={`${id}-results`} role=\"listbox\" aria-label=\"Matching articles\">\n                    {results.map((a, i) => (\n                      <li\n                        key={a.id}\n                        id={`${id}-r-${a.id}`}\n                        role=\"option\"\n                        aria-selected={i === active}\n                        onMouseDown={(e) => e.preventDefault()}\n                        onMouseMove={() => setActive(i)}\n                        onClick={() => choose(a)}\n                        className={cn(\"flex cursor-default items-center gap-3 rounded-xl px-3 py-2.5\", i === active && \"bg-accent\")}\n                      >\n                        <FileSearch aria-hidden=\"true\" className=\"text-muted-foreground size-4 shrink-0\" />\n                        <span className=\"min-w-0 flex-1\">\n                          <span className=\"block truncate text-sm font-medium\">\n                            <MatchHighlight text={a.title} query={tokens} />\n                          </span>\n                          {a.summary ? (\n                            <span className=\"text-muted-foreground block truncate text-xs\">\n                              <MatchHighlight text={a.summary} query={tokens} />\n                            </span>\n                          ) : null}\n                        </span>\n                        <span className=\"text-muted-foreground hidden shrink-0 text-xs sm:block\">{categoryName(a.categoryId)}</span>\n                      </li>\n                    ))}\n                  </ul>\n                ) : (\n                  <div id={`${id}-results`} className=\"px-4 py-8 text-center\">\n                    <p className=\"text-sm font-medium\">No articles match “{query.trim()}”</p>\n                    <p className=\"text-muted-foreground mt-1 text-xs text-pretty\">Check the spelling, try fewer words, or ask us directly.</p>\n                    {channels[0] ? (\n                      <Button variant=\"outline\" size=\"sm\" className=\"mt-4\" onMouseDown={(e) => e.preventDefault()} onClick={() => onContact?.(channels[0]!)}>\n                        {channels[0].action}\n                      </Button>\n                    ) : null}\n                  </div>\n                )}\n              </div>\n            ) : null}\n            <p aria-live=\"polite\" className=\"sr-only\">\n              {showList ? (results.length === 1 ? \"1 article found\" : `${results.length} articles found`) : \"\"}\n            </p>\n          </div>\n\n          {suggestions.length && ready && !showList ? (\n            <p className=\"mt-5 flex flex-wrap items-center justify-center gap-2 text-sm\">\n              <span className=\"text-muted-foreground\">Popular:</span>\n              {suggestions.map((s) => (\n                <button\n                  key={s}\n                  type=\"button\"\n                  onClick={() => {\n                    setQuery(s)\n                    setOpen(true)\n                    inputRef.current?.focus()\n                  }}\n                  className=\"hover:bg-accent focus-visible:ring-ring/50 rounded-full border px-3 py-1 text-[13px] outline-none focus-visible:ring-[3px]\"\n                >\n                  {s}\n                </button>\n              ))}\n            </p>\n          ) : null}\n        </BlurFade>\n      </div>\n\n      <StateView\n        state={state === \"loading\" || state === \"error\" ? state : \"ready\"}\n        onRetry={onRetry}\n        label=\"Loading the help center\"\n        loading={<HelpSkeleton />}\n        error={{ title: errorState?.title ?? \"The help center could not be loaded\", description: errorState?.description ?? \"Check your connection and try again. You can still reach us below.\", ...errorState }}\n      >\n        {content}\n      </StateView>\n      <div className=\"mt-14\">\n      {channels.length ? (\n          <section aria-labelledby={`${id}-contact`} className=\"bg-card relative isolate overflow-hidden rounded-3xl border\">\n            <CoverArt seed=\"support\" variant=\"mesh\" className=\"absolute inset-0 -z-10 opacity-60\" />\n            <div className=\"from-card via-card/85 absolute inset-0 -z-10 bg-gradient-to-r to-transparent\" aria-hidden=\"true\" />\n            <div className=\"grid gap-8 p-6 sm:p-10 lg:grid-cols-[minmax(0,1fr)_minmax(0,1.6fr)] lg:items-center\">\n              <div>\n                <h2 id={`${id}-contact`} className=\"text-2xl font-semibold tracking-[-0.03em] text-balance\">\n                  Still stuck? Talk to a person.\n                </h2>\n                <p className=\"text-muted-foreground mt-2 max-w-sm text-sm text-pretty\">Our team reads every message. Tell us what you tried and we will take it from there.</p>\n              </div>\n              <ul className=\"grid gap-3 sm:grid-cols-3\">\n                {channels.map((c) => (\n                  <li key={c.id} className=\"bg-background/80 flex flex-col gap-3 rounded-2xl border p-4 backdrop-blur-sm\">\n                    <span aria-hidden=\"true\" className=\"bg-muted flex size-9 items-center justify-center rounded-lg [&_svg]:size-4\">\n                      {c.icon}\n                    </span>\n                    <div className=\"flex-1\">\n                      <p className=\"text-sm font-semibold\">{c.label}</p>\n                      <p className=\"text-muted-foreground mt-0.5 flex items-center gap-1.5 text-xs\">\n                        {c.online ? <span aria-hidden=\"true\" className=\"bg-chart-2 size-1.5 shrink-0 rounded-full\" /> : null}\n                        {c.online ? <><span className=\"sr-only\">Online.</span>{\" \"}</> : null}\n                        {c.description}\n                      </p>\n                    </div>\n                    {c.href ? (\n                      <a href={c.href} onClick={() => onContact?.(c)} className=\"focus-visible:ring-ring/50 hover:bg-accent inline-flex h-8 items-center justify-center gap-1.5 rounded-md border px-3 text-[13px] font-medium outline-none focus-visible:ring-[3px]\">\n                        {c.action}\n                      </a>\n                    ) : (\n                      <Button variant=\"outline\" size=\"sm\" onClick={() => onContact?.(c)}>\n                        {c.action}\n                      </Button>\n                    )}\n                  </li>\n                ))}\n              </ul>\n            </div>\n          </section>\n        ) : null}\n      </div>\n    </section>\n  )\n}\n\nexport { Help1, type Help1Props, type Help1Category, type Help1Article, type Help1Channel }\n",
      "type": "registry:component",
      "target": "@components/ballmac/blocks/help-1/help-1.tsx"
    }
  ],
  "meta": {
    "schema": 1,
    "tier": "free",
    "tags": [
      "help center",
      "support",
      "knowledge base",
      "docs",
      "search",
      "faq",
      "categories",
      "contact",
      "self-serve"
    ],
    "version": "1.0.0",
    "updated": "2026-10-17",
    "tests": true,
    "ai": {
      "summary": "A help center home. <Help1 categories={[{ id, title, description, icon, count, href }]} articles={[{ id, title, summary, categoryId, minutes, href, popular }]} suggestions={['Reset password']} channels={[{ id, label, description, icon, online, action, href }]} onOpenArticle onOpenCategory onContact onSearch />. Search runs in the browser over title and summary; pass onSearch to log queries or fetch your own results.",
      "whenToUse": [
        "The front page of a help center or knowledge base",
        "Self-serve support before people contact you"
      ],
      "whenNotToUse": [
        "A short list of questions (use faq-1)",
        "A documentation site's navigation (use sidebar and command)",
        "A contact form (use contact-1)"
      ],
      "composesWith": [
        "header-2",
        "footer-2",
        "faq-1",
        "contact-1"
      ],
      "a11y": [
        {
          "keys": "/",
          "action": "Jumps to the search from anywhere on the page, unless you are typing in a field"
        },
        {
          "keys": "Up / Down, Enter, Escape in the search",
          "action": "Chooses a suggestion, opens it, and closes the list (then clears the box); the result count is announced"
        },
        {
          "keys": "Tab",
          "action": "Each topic card and popular article is one link with a single tab stop"
        }
      ],
      "customization": [
        "title, description, suggestions, channels (set online to show the green dot and \"Online\" for screen readers)",
        "defaultQuery opens the search with a query already typed",
        "Search matches every word in an article's title or summary and lists title matches first; replace it with your own search through onSearch"
      ]
    },
    "states": [
      {
        "id": "loading",
        "title": "Loading",
        "example": "help-1-loading",
        "trigger": "state=\"loading\"",
        "note": "Skeleton topic cards and article rows under the real heading; search is disabled."
      },
      {
        "id": "empty",
        "title": "No guides yet",
        "example": "help-1-empty",
        "trigger": "state=\"empty\" (or categories={[]})",
        "note": "Says guides will be listed here; emptyState sets the copy. The contact strip stays."
      },
      {
        "id": "error",
        "title": "Could not load",
        "example": "help-1-error",
        "trigger": "state=\"error\"",
        "note": "An alert with Try again (onRetry). The hero and the contact strip stay, so people can still reach you."
      }
    ],
    "examples": [
      "help-1-demo",
      "help-1-searching",
      "help-1-no-results",
      "help-1-loading",
      "help-1-empty",
      "help-1-error"
    ],
    "url": "https://ui.ballmac.com/components/help-1"
  },
  "categories": [
    "blocks",
    "help"
  ],
  "type": "registry:block"
}