{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "report-builder",
  "title": "Report Builder",
  "description": "Select metrics, dimensions, filters, visualization, and export format.",
  "dependencies": [
    "class-variance-authority"
  ],
  "registryDependencies": [
    "button",
    "https://uaiblocks.dev/r/uai-theme.json",
    "https://uaiblocks.dev/r/uai-utils.json",
    "https://uaiblocks.dev/r/filter-bar.json",
    "https://uaiblocks.dev/r/metric-card.json"
  ],
  "files": [
    {
      "path": "src/registry/uai/blocks/report-builder.tsx",
      "content": "\"use client\";\n\nimport { cva } from \"class-variance-authority\";\nimport {\n  type ComponentProps,\n  type CSSProperties,\n  createContext,\n  type RefObject,\n  useContext,\n  useEffect,\n  useId,\n  useRef,\n  useState,\n} from \"react\";\nimport { Button } from \"@/components/ui/button\";\nimport {\n  FilterBar,\n  type FilterBarProps,\n  type FilterBarVariant,\n} from \"@/components/ui/uai/filter-bar\";\nimport {\n  MetricCard,\n  type MetricCardProps,\n  type MetricCardVariant,\n} from \"@/components/ui/uai/metric-card\";\nimport { cn } from \"@/lib/uai-utils\";\n\nexport const REPORT_BUILDER_VARIANTS = [\"sidebar\", \"stacked\", \"compact\"] as const;\nexport type ReportBuilderVariant = (typeof REPORT_BUILDER_VARIANTS)[number];\nexport type ReportBuilderChartOrientation = \"horizontal\" | \"vertical\";\nexport type ReportBuilderProps = ComponentProps<\"section\"> & { variant?: ReportBuilderVariant };\n\ntype BuilderContext = { id: string; variant: ReportBuilderVariant };\nconst Context = createContext<BuilderContext | null>(null);\nfunction useBuilder(part: string) {\n  const context = useContext(Context);\n  if (!context) throw new Error(`${part} must be used within ReportBuilder`);\n  return context;\n}\ntype ChartContext = { max: number; orientation: ReportBuilderChartOrientation };\nconst ChartCtx = createContext<ChartContext | null>(null);\nfunction useChart(part: string) {\n  const context = useContext(ChartCtx);\n  if (!context) throw new Error(`${part} must be used within ReportBuilderChart`);\n  return context;\n}\nconst BarContext = createContext<number | null>(null);\n\nconst filterVariants: Record<ReportBuilderVariant, FilterBarVariant> = {\n  sidebar: \"compact\",\n  stacked: \"toolbar\",\n  compact: \"compact\",\n};\nconst metricVariants: Record<ReportBuilderVariant, MetricCardVariant> = {\n  sidebar: \"plain\",\n  stacked: \"plain\",\n  compact: \"compact\",\n};\nconst reportBuilderActionVariants = cva(\n  \"gap-1.5 rounded-full border-0 py-0 transition-[filter,box-shadow,scale] duration-[120ms,120ms,140ms] ease-[ease-out,ease-out,cubic-bezier(0.23,1,0.32,1)] focus-visible:ring-0 focus-visible:outline-solid focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ring enabled:cursor-pointer enabled:active:scale-[0.97] disabled:cursor-not-allowed disabled:opacity-50 motion-reduce:transition-none motion-reduce:enabled:active:scale-100\",\n  {\n    variants: {\n      emphasis: {\n        primary: \"bg-primary text-primary-foreground hover:bg-primary enabled:hover:brightness-108\",\n        secondary:\n          \"bg-secondary text-secondary-foreground hover:bg-secondary enabled:hover:shadow-[inset_0_0_0_999px_color-mix(in_oklab,var(--foreground)_9%,transparent)]\",\n      },\n      compact: {\n        true: \"h-[26px] px-[11px] text-[12px]/4 has-[>svg]:px-[11px]\",\n        false: \"h-[30px] px-[13px] text-[12.5px]/4 has-[>svg]:px-[13px]\",\n      },\n    },\n  },\n);\n\nconst reportBuilderVariants = cva(\"grid min-w-0 content-start text-[13px]/[18px] text-foreground\", {\n  variants: { variant: { sidebar: \"gap-4\", stacked: \"gap-4\", compact: \"gap-2.5\" } },\n});\n\n/** Report composer: metrics, dimensions, filters, visualization, and export format. */\nexport function ReportBuilder({\n  variant = \"sidebar\",\n  className,\n  children,\n  ...props\n}: ReportBuilderProps) {\n  const id = useId();\n  return (\n    <Context.Provider value={{ id, variant }}>\n      <section\n        aria-labelledby={`${id}-title`}\n        data-slot=\"report-builder\"\n        className={cn(reportBuilderVariants({ variant }), className)}\n        {...props}\n        data-variant={variant}\n      >\n        {children}\n      </section>\n    </Context.Provider>\n  );\n}\n\nexport function ReportBuilderHeader({ className, ...props }: ComponentProps<\"div\">) {\n  useBuilder(\"ReportBuilderHeader\");\n  return (\n    <div\n      data-slot=\"report-builder-header\"\n      className={cn(\"flex min-w-0 flex-wrap items-end justify-between gap-3\", className)}\n      {...props}\n    />\n  );\n}\n\nexport function ReportBuilderHeading({ className, ...props }: ComponentProps<\"div\">) {\n  return (\n    <div\n      data-slot=\"report-builder-heading\"\n      className={cn(\"grid min-w-0 flex-[1_1_240px] gap-1\", className)}\n      {...props}\n    />\n  );\n}\n\nexport function ReportBuilderTitle({ className, ...props }: ComponentProps<\"h2\">) {\n  const context = useBuilder(\"ReportBuilderTitle\");\n  return (\n    <h2\n      data-slot=\"report-builder-title\"\n      className={cn(\n        \"m-0 font-semibold tracking-[-0.015em] wrap-anywhere\",\n        context.variant === \"compact\" ? \"text-[15px]/5\" : \"text-lg/6\",\n        className,\n      )}\n      {...props}\n      id={`${context.id}-title`}\n    />\n  );\n}\n\nexport function ReportBuilderDescription({ className, ...props }: ComponentProps<\"p\">) {\n  return (\n    <p\n      data-slot=\"report-builder-description\"\n      className={cn(\"m-0 text-muted-foreground tabular-nums\", className)}\n      {...props}\n    />\n  );\n}\n\nexport function ReportBuilderActions({ className, ...props }: ComponentProps<\"div\">) {\n  return (\n    <div\n      data-slot=\"report-builder-actions\"\n      className={cn(\"flex flex-wrap items-center gap-1.5\", className)}\n      {...props}\n    />\n  );\n}\n\nexport function ReportBuilderAction({\n  emphasis = \"secondary\",\n  type = \"button\",\n  className,\n  ...props\n}: ComponentProps<\"button\"> & { emphasis?: \"primary\" | \"secondary\" }) {\n  const context = useBuilder(\"ReportBuilderAction\");\n  return (\n    <Button\n      data-slot=\"report-builder-action\"\n      variant={emphasis === \"primary\" ? \"default\" : \"secondary\"}\n      className={cn(\n        reportBuilderActionVariants({ emphasis, compact: context.variant === \"compact\" }),\n        className,\n      )}\n      {...props}\n      type={type}\n      data-emphasis={emphasis}\n    />\n  );\n}\n\n/** Places settings beside the canvas in Sidebar and above it otherwise. */\nexport function ReportBuilderBody({ className, ...props }: ComponentProps<\"div\">) {\n  const context = useBuilder(\"ReportBuilderBody\");\n  return (\n    <div\n      data-slot=\"report-builder-body\"\n      className={cn(\n        \"min-w-0 flex-wrap items-start\",\n        context.variant === \"sidebar\" ? \"flex\" : \"grid\",\n        context.variant === \"compact\" ? \"gap-2\" : \"gap-3\",\n        className,\n      )}\n      {...props}\n    />\n  );\n}\n\n/** Report settings form. Saving and querying stay with the consumer. */\nexport function ReportBuilderConfig({\n  \"aria-label\": label = \"Report settings\",\n  className,\n  ...props\n}: ComponentProps<\"form\">) {\n  const context = useBuilder(\"ReportBuilderConfig\");\n  const compact = context.variant === \"compact\";\n  return (\n    <form\n      aria-label={label}\n      data-slot=\"report-builder-config\"\n      className={cn(\n        \"m-0 min-w-0 flex-[1_1_240px] flex-wrap content-start border-0 bg-[color-mix(in_oklab,var(--muted)_70%,var(--card))]\",\n        context.variant === \"stacked\" ? \"flex\" : \"grid\",\n        compact ? \"gap-2.5 rounded-xl p-3\" : \"gap-4 rounded-[14px] p-4\",\n        className,\n      )}\n      {...props}\n    />\n  );\n}\n\nexport function ReportBuilderSection({ className, ...props }: ComponentProps<\"fieldset\">) {\n  const context = useBuilder(\"ReportBuilderSection\");\n  return (\n    <fieldset\n      data-slot=\"report-builder-section\"\n      className={cn(\n        \"m-0 grid min-w-0 flex-[1_1_200px] border-0 p-0\",\n        context.variant === \"compact\" ? \"gap-1.5\" : \"gap-2\",\n        className,\n      )}\n      {...props}\n    />\n  );\n}\n\nexport function ReportBuilderSectionTitle({ className, ...props }: ComponentProps<\"legend\">) {\n  return (\n    <legend\n      data-slot=\"report-builder-section-title\"\n      className={cn(\"mb-1.5 p-0 text-[11.5px]/4 font-medium text-subtle-foreground\", className)}\n      {...props}\n    />\n  );\n}\n\nexport function ReportBuilderOptions({ className, ...props }: ComponentProps<\"div\">) {\n  return (\n    <div\n      data-slot=\"report-builder-options\"\n      className={cn(\"flex flex-wrap gap-1.5\", className)}\n      {...props}\n    />\n  );\n}\n\n/** A checkbox or radio styled as a pill. Native inputs keep arrow-key and Space behavior. */\nexport function ReportBuilderOption({\n  type = \"checkbox\",\n  children,\n  className,\n  style,\n  ...props\n}: Omit<ComponentProps<\"input\">, \"type\"> & { type?: \"checkbox\" | \"radio\" }) {\n  const context = useBuilder(\"ReportBuilderOption\");\n  // Styling reads the live input through has-checked, so uncontrolled options update on click.\n  return (\n    <label\n      data-slot=\"report-builder-option\"\n      data-checked={props.checked || undefined}\n      className={cn(\n        \"relative inline-flex items-center rounded-full border-0 text-[12px] font-medium whitespace-nowrap transition-[background-color,color,box-shadow,scale] duration-[120ms,120ms,120ms,140ms] ease-[ease-out,ease-out,ease-out,cubic-bezier(0.23,1,0.32,1)] active:scale-[0.97] has-[:focus-visible]:outline-2 has-[:focus-visible]:outline-offset-1 has-[:focus-visible]:outline-ring motion-reduce:transition-none motion-reduce:active:scale-100\",\n        context.variant === \"compact\" ? \"h-6 px-2.5\" : \"h-7 px-3\",\n        \"bg-card text-muted-foreground shadow-[inset_0_0_0_1px_var(--border)] not-has-checked:hover:text-foreground not-has-checked:hover:shadow-[inset_0_0_0_1px_var(--border-strong)]\",\n        \"has-checked:bg-[color-mix(in_oklab,var(--primary)_16%,var(--card))] has-checked:text-primary-ink has-checked:shadow-[inset_0_0_0_1px_color-mix(in_oklab,var(--primary)_40%,transparent)]\",\n        props.disabled ? \"cursor-not-allowed opacity-50\" : \"cursor-pointer\",\n        className,\n      )}\n      style={style}\n    >\n      <input {...props} type={type} className=\"absolute inset-0 m-0 cursor-[inherit] opacity-0\" />\n      {children}\n    </label>\n  );\n}\n\n/** Report filters. Compose Filter Bar parts inside it. */\nexport function ReportBuilderFilters(props: Omit<FilterBarProps, \"variant\">) {\n  const context = useBuilder(\"ReportBuilderFilters\");\n  return <FilterBar {...props} variant={filterVariants[context.variant]} />;\n}\n\n/** The live preview of the report. */\nexport function ReportBuilderCanvas({ className, ...props }: ComponentProps<\"section\">) {\n  const context = useBuilder(\"ReportBuilderCanvas\");\n  return (\n    <section\n      aria-labelledby={`${context.id}-canvas-title`}\n      data-slot=\"report-builder-canvas\"\n      className={cn(\n        \"grid min-w-0 flex-[999_1_360px] content-start border bg-card shadow-[0_1px_2px_oklch(0_0_0/0.04)]\",\n        context.variant === \"compact\" ? \"gap-2.5 rounded-xl p-3\" : \"gap-3.5 rounded-[14px] p-4\",\n        className,\n      )}\n      {...props}\n    />\n  );\n}\n\nexport function ReportBuilderCanvasTitle({ className, ...props }: ComponentProps<\"h3\">) {\n  const context = useBuilder(\"ReportBuilderCanvasTitle\");\n  return (\n    <h3\n      data-slot=\"report-builder-canvas-title\"\n      className={cn(\"m-0 text-[13px]/[18px] font-medium\", className)}\n      {...props}\n      id={`${context.id}-canvas-title`}\n    />\n  );\n}\n\n/** A single-figure visualization. Compose Metric Card parts inside it. */\nexport function ReportBuilderMetric(props: Omit<MetricCardProps, \"variant\">) {\n  const context = useBuilder(\"ReportBuilderMetric\");\n  return <MetricCard {...props} variant={metricVariants[context.variant]} />;\n}\n\n/** A bar chart rendered as a list, so every value is available as text. */\ntype ScrollEdges = { start: boolean; end: boolean };\n\n/** Tracks which ends of a horizontal scroller hide content, so those edges can fade. */\nfunction useScrollEdges(ref: RefObject<HTMLElement | null>) {\n  const [edges, setEdges] = useState<ScrollEdges>({ start: false, end: false });\n  useEffect(() => {\n    const element = ref.current;\n    if (!element) return;\n    const measure = () => {\n      const offset = Math.abs(element.scrollLeft);\n      const room = element.scrollWidth - element.clientWidth;\n      const start = offset > 1;\n      const end = room - offset > 1;\n      setEdges((previous) =>\n        previous.start === start && previous.end === end ? previous : { start, end },\n      );\n    };\n    measure();\n    element.addEventListener(\"scroll\", measure, { passive: true });\n    const observer = typeof ResizeObserver === \"function\" ? new ResizeObserver(measure) : null;\n    observer?.observe(element);\n    for (const child of element.children) observer?.observe(child);\n    return () => {\n      element.removeEventListener(\"scroll\", measure);\n      observer?.disconnect();\n    };\n  }, [ref]);\n  return edges;\n}\n\nexport function ReportBuilderChart({\n  max,\n  orientation = \"horizontal\",\n  className,\n  ...props\n}: ComponentProps<\"ul\"> & { max: number; orientation?: ReportBuilderChartOrientation }) {\n  const context = useBuilder(\"ReportBuilderChart\");\n  const vertical = orientation === \"vertical\";\n  const ref = useRef<HTMLUListElement>(null);\n  const edges = useScrollEdges(ref);\n  return (\n    <ChartCtx.Provider value={{ max: Math.max(max, 1), orientation }}>\n      <ul\n        ref={ref}\n        data-slot=\"report-builder-chart\"\n        data-overflow-start={edges.start ? \"\" : undefined}\n        data-overflow-end={edges.end ? \"\" : undefined}\n        className={cn(\n          \"m-0 min-w-0 list-none p-0\",\n          vertical\n            ? \"flex items-stretch gap-2 overflow-x-auto data-overflow-start:mask-l-from-[calc(100%-24px)] data-overflow-end:mask-r-from-[calc(100%-24px)]\"\n            : cn(\"grid\", context.variant === \"compact\" ? \"gap-1.5\" : \"gap-2.5\"),\n          className,\n        )}\n        {...props}\n        data-orientation={orientation}\n      />\n    </ChartCtx.Provider>\n  );\n}\n\nexport function ReportBuilderBar({\n  value,\n  className,\n  children,\n  ...props\n}: Omit<ComponentProps<\"li\">, \"value\"> & { value: number }) {\n  const chart = useChart(\"ReportBuilderBar\");\n  const builder = useBuilder(\"ReportBuilderBar\");\n  const percent = Math.min(Math.max(value / chart.max, 0), 1) * 100;\n  const vertical = chart.orientation === \"vertical\";\n  const compact = builder.variant === \"compact\";\n  return (\n    <BarContext.Provider value={value}>\n      <li\n        data-slot=\"report-builder-bar\"\n        className={cn(\n          \"grid min-w-0 items-center gap-x-2.5 gap-y-1\",\n          vertical\n            ? cn(\n                \"flex-[1_1_0] grid-cols-[minmax(48px,1fr)] [grid-template-areas:'value'_'bar'_'label']\",\n                compact ? \"grid-rows-[auto_96px_auto]\" : \"grid-rows-[auto_140px_auto]\",\n              )\n            : \"grid-cols-[minmax(72px,30%)_minmax(0,1fr)_auto] [grid-template-areas:'label_bar_value']\",\n          className,\n        )}\n        {...props}\n      >\n        <span\n          aria-hidden=\"true\"\n          className={cn(\n            \"flex items-end self-stretch overflow-hidden rounded-full bg-muted [grid-area:bar]\",\n            vertical ? \"h-full\" : compact ? \"h-2\" : \"h-2.5\",\n          )}\n        >\n          <span\n            data-slot=\"report-builder-bar-fill\"\n            // A full-size fill slides by `translate`, so value changes animate without touching\n            // width or height. The mount entrance slides in from the track start via `transform`.\n            className={cn(\n              \"size-full bg-[color-mix(in_oklab,var(--primary)_85%,var(--foreground))] transition-[translate] duration-300 ease-out-quint animate-in animation-duration-400 motion-reduce:animate-none motion-reduce:transition-none\",\n              vertical\n                ? \"translate-y-(--fill-offset) slide-in-from-bottom rounded-md\"\n                : \"translate-x-(--fill-offset) slide-in-from-left rounded-full\",\n            )}\n            style={\n              { \"--fill-offset\": `${vertical ? 100 - percent : percent - 100}%` } as CSSProperties\n            }\n          />\n        </span>\n        {children}\n      </li>\n    </BarContext.Provider>\n  );\n}\n\nexport function ReportBuilderBarLabel({ className, ...props }: ComponentProps<\"span\">) {\n  const chart = useChart(\"ReportBuilderBarLabel\");\n  const vertical = chart.orientation === \"vertical\";\n  return (\n    <span\n      data-slot=\"report-builder-bar-label\"\n      className={cn(\n        \"min-w-0 text-muted-foreground wrap-anywhere [grid-area:label]\",\n        vertical ? \"text-center text-[12px]\" : \"text-[13px]\",\n        className,\n      )}\n      {...props}\n    />\n  );\n}\n\nexport function ReportBuilderBarValue({ className, children, ...props }: ComponentProps<\"span\">) {\n  const chart = useChart(\"ReportBuilderBarValue\");\n  const value = useContext(BarContext);\n  return (\n    <span\n      data-slot=\"report-builder-bar-value\"\n      className={cn(\n        \"text-[12px] font-medium whitespace-nowrap tabular-nums [grid-area:value]\",\n        chart.orientation === \"vertical\" ? \"text-center\" : \"text-right\",\n        className,\n      )}\n      {...props}\n    >\n      {children ?? value}\n    </span>\n  );\n}\n\n/** Row counts, refresh times, or export confirmations, announced politely. */\nexport function ReportBuilderStatus({ className, ...props }: ComponentProps<\"p\">) {\n  return (\n    <p\n      role=\"status\"\n      data-slot=\"report-builder-status\"\n      className={cn(\"m-0 px-0.5 text-[12px] text-subtle-foreground tabular-nums\", className)}\n      {...props}\n    />\n  );\n}\n",
      "type": "registry:component",
      "target": "@components/uai/report-builder.tsx"
    }
  ],
  "categories": [
    "operations",
    "reporting",
    "charts"
  ],
  "type": "registry:block"
}