{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "ds-dashbox",
  "title": "Dashbox",
  "description": "A structured dashboard card with collapse/expand, fullscreen, refresh, loading skeleton, status badge, and configurable body padding.",
  "dependencies": [
    "lucide-react",
    "class-variance-authority"
  ],
  "registryDependencies": [
    "@lema-ds/tooltip",
    "@lema-ds/skeleton",
    "@lema-ds/ui-i18n",
    "@lema-ds/separator",
    "@lema-ds/ds-locale-provider",
    "@lema-ds/tokens"
  ],
  "files": [
    {
      "path": "components/ds/ds-dashbox.tsx",
      "content": "\"use client\"\n\nimport * as React from \"react\"\nimport { Minus, Plus, Maximize2, RefreshCw, Shrink } from \"lucide-react\"\nimport { cva } from \"class-variance-authority\"\nimport type { VariantProps } from \"class-variance-authority\"\nimport type { HTMLAttributes, ReactNode } from \"react\"\nimport { cn } from \"@/lib/utils\"\nimport { UI_I18N, type UILocale } from \"@/lib/ui-i18n\"\nimport { useUILocale } from \"@/components/ui/ds-locale-provider\"\nimport {\n  Tooltip,\n  TooltipContent,\n  TooltipProvider,\n  TooltipTrigger,\n} from \"@/components/ui/tooltip\"\nimport { Skeleton } from \"@/components/ui/skeleton\"\nimport { Separator } from \"@/components/ui/separator\"\n\n// ── Types ──────────────────────────────────────────────────────────────────\n\nexport type DashboxSize = \"sm\" | \"md\" | \"lg\"\nexport type DashboxBodyPadding = \"none\" | \"sm\" | \"md\" | \"lg\"\nexport type DashboxStatus = \"live\" | \"warning\" | \"error\" | \"idle\"\nexport type DashboxLocale = UILocale\n\n/** Read-only toolbar state passed to `children` when used as a render function. */\nexport interface DashboxRenderState {\n  maximized: boolean\n  minimized: boolean\n}\n\nexport interface DashboxProps\n  extends\n    VariantProps<typeof dashboxVariants>,\n    Omit<HTMLAttributes<HTMLDivElement>, \"children\"> {\n  title?: string\n  description?: string\n  /**\n   * Body content. Pass a function to react to the toolbar's maximize/minimize\n   * state (e.g. growing a chart to fill the fullscreen viewport).\n   */\n  children?: ReactNode | ((state: DashboxRenderState) => ReactNode)\n  toolbar?: ReactNode\n  /** @default 'md' */\n  bodyPadding?: DashboxBodyPadding\n  loading?: boolean\n  status?: DashboxStatus\n  locale?: DashboxLocale\n  /** Override status label per instance (e.g. from API data) */\n  statusLabels?: Partial<Record<DashboxStatus, string>>\n  onRefresh?: () => void\n  /** @default true */\n  showMaximize?: boolean\n  /** @default true */\n  showMinimize?: boolean\n  /** @default true */\n  showToolbar?: boolean\n  /** @default true */\n  showHeader?: boolean\n}\n\n// ── Variants ───────────────────────────────────────────────────────────────\n\nexport const dashboxVariants = cva(\n  [\n    \"flex w-full flex-col\",\n    \"bg-card text-card-foreground\",\n    \"rounded-xl border border-border\",\n    \"overflow-hidden shadow-sm transition-all duration-300\",\n  ],\n  {\n    variants: {\n      size: {\n        sm: \"text-xs\",\n        md: \"text-sm\",\n        lg: \"text-base\",\n      },\n    },\n    defaultVariants: { size: \"md\" },\n  }\n)\n\nexport const dashboxHeaderVariants = cva([\n  \"flex items-start justify-between gap-3 px-4 py-3\",\n  \"shrink-0 bg-card\",\n])\n\nexport const dashboxTitleVariants = cva(\n  \"leading-tight font-semibold text-foreground\",\n  {\n    variants: {\n      size: {\n        sm: \"text-xs\",\n        md: \"text-sm\",\n        lg: \"text-base\",\n      },\n    },\n    defaultVariants: { size: \"md\" },\n  }\n)\n\nexport const dashboxDescriptionVariants = cva(\"text-muted-foreground\", {\n  variants: {\n    size: {\n      sm: \"mt-0.5 text-xs\",\n      md: \"mt-0.5 text-sm\",\n      lg: \"mt-1 text-base\",\n    },\n  },\n  defaultVariants: { size: \"md\" },\n})\n\nexport const dashboxToolbarVariants = cva(\n  \"ml-auto flex shrink-0 items-center gap-0.5\"\n)\n\nexport const dashboxToolbarButtonVariants = cva([\n  \"flex size-7 items-center justify-center rounded-md\",\n  \"text-muted-foreground\",\n  \"hover:bg-accent hover:text-accent-foreground\",\n  \"cursor-pointer transition-colors duration-150\",\n  \"focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-1 focus-visible:outline-none\",\n])\n\nexport const dashboxBodyVariants = cva(\"min-h-0 flex-1 overflow-auto\", {\n  variants: {\n    padding: {\n      none: \"p-0\",\n      sm: \"p-2\",\n      md: \"p-4\",\n      lg: \"p-6\",\n    },\n  },\n  defaultVariants: { padding: \"md\" },\n})\n\nexport const dashboxStatusBadgeVariants = cva(\n  \"inline-flex items-center gap-1 rounded-full border px-2 py-0.5 text-xs font-medium\",\n  {\n    variants: {\n      status: {\n        live: \"border-success/20 bg-success/10 text-success\",\n        warning: \"border-warning/20 bg-warning/10 text-warning\",\n        error: \"border-destructive/20 bg-destructive/10 text-destructive\",\n        idle: \"border-transparent bg-muted text-muted-foreground\",\n      },\n    },\n    defaultVariants: { status: \"idle\" },\n  }\n)\n\nconst titleSkeletonH: Record<DashboxSize, string> = {\n  sm: \"h-3\",\n  md: \"h-3.5\",\n  lg: \"h-4\",\n}\n\n// ── Private subcomponents ──────────────────────────────────────────────────\n\nconst STATUS_DOT: Record<string, string> = {\n  live: \"bg-success motion-safe:animate-pulse\",\n  warning: \"bg-warning\",\n  error: \"bg-destructive\",\n  idle: \"bg-muted-foreground/40\",\n}\n\nfunction DashboxStatusBadge({\n  status,\n  label,\n  locale: localeProp,\n  statusLabels,\n}: {\n  status?: DashboxStatus\n  label?: string\n  locale?: DashboxLocale\n  statusLabels?: Partial<Record<string, string>>\n}) {\n  const locale = useUILocale(localeProp)\n  if (!status || status === \"idle\") return null\n  const labels = { ...UI_I18N[locale].dashbox.status, ...statusLabels }\n  return (\n    <span\n      data-slot=\"dashbox-status\"\n      className={dashboxStatusBadgeVariants({ status })}\n    >\n      <span className={cn(\"size-1.5 rounded-full\", STATUS_DOT[status])} />\n      {label ?? labels[status] ?? status}\n    </span>\n  )\n}\n\nfunction ToolbarButton({\n  label,\n  onClick,\n  children,\n  className,\n}: {\n  label: string\n  onClick?: () => void\n  children: React.ReactNode\n  className?: string\n}) {\n  return (\n    <Tooltip>\n      <TooltipTrigger asChild>\n        <button\n          data-slot=\"dashbox-toolbar-button\"\n          type=\"button\"\n          onClick={onClick}\n          aria-label={label}\n          className={cn(dashboxToolbarButtonVariants(), className)}\n        >\n          {children}\n        </button>\n      </TooltipTrigger>\n      <TooltipContent side=\"bottom\">\n        <p>{label}</p>\n      </TooltipContent>\n    </Tooltip>\n  )\n}\n\n// ── Dashbox ────────────────────────────────────────────────────────────────\n\nexport const Dashbox = React.forwardRef<HTMLDivElement, DashboxProps>(\n  (\n    {\n      title,\n      description,\n      children,\n      toolbar,\n      size = \"md\",\n      bodyPadding = \"md\",\n      loading = false,\n      status,\n      locale: localeProp,\n      statusLabels,\n      onRefresh,\n      showToolbar = true,\n      showHeader = true,\n      showMaximize = true,\n      showMinimize = true,\n      className,\n      ...props\n    },\n    ref\n  ) => {\n    const locale = useUILocale(localeProp)\n    const [minimized, setMinimized] = React.useState(false)\n    const [maximized, setMaximized] = React.useState(false)\n    const [refreshing, setRefreshing] = React.useState(false)\n\n    const tl = UI_I18N[locale].dashbox.toolbar\n\n    const handleRefresh = React.useCallback(async () => {\n      setRefreshing(true)\n      await onRefresh?.()\n      setTimeout(() => setRefreshing(false), 600)\n    }, [onRefresh])\n\n    React.useEffect(() => {\n      if (!maximized) return\n      const onKey = (e: KeyboardEvent) => {\n        if (e.key === \"Escape\") setMaximized(false)\n      }\n      window.addEventListener(\"keydown\", onKey)\n      const prev = document.body.style.overflow\n      document.body.style.overflow = \"hidden\"\n      return () => {\n        window.removeEventListener(\"keydown\", onKey)\n        document.body.style.overflow = prev\n      }\n    }, [maximized])\n\n    return (\n      <TooltipProvider delayDuration={300}>\n        <div\n          ref={ref}\n          data-slot=\"dashbox\"\n          role={maximized ? \"dialog\" : undefined}\n          aria-modal={maximized ? true : undefined}\n          aria-label={maximized ? title : undefined}\n          tabIndex={maximized ? -1 : undefined}\n          className={cn(\n            dashboxVariants({ size }),\n            maximized && \"fixed! inset-0 z-50 overflow-auto rounded-none\",\n            className\n          )}\n          {...props}\n        >\n          {showHeader && (\n            <div data-slot=\"dashbox-header\" className={dashboxHeaderVariants()}>\n              <div className=\"flex min-w-0 flex-1 flex-col\">\n                <div className=\"flex items-center gap-2\">\n                  {loading ? (\n                    <Skeleton\n                      data-slot=\"dashbox-title-skeleton\"\n                      className={cn(titleSkeletonH[size ?? \"md\"], \"w-2/5\")}\n                    />\n                  ) : (\n                    title && (\n                      <span\n                        data-slot=\"dashbox-title\"\n                        className={dashboxTitleVariants({ size })}\n                        title={title}\n                      >\n                        {title}\n                      </span>\n                    )\n                  )}\n                  {!loading && status && (\n                    <DashboxStatusBadge\n                      status={status}\n                      locale={locale}\n                      statusLabels={statusLabels}\n                    />\n                  )}\n                </div>\n                {loading ? (\n                  <Skeleton\n                    data-slot=\"dashbox-description-skeleton\"\n                    className={cn(\n                      \"mt-0.5\",\n                      titleSkeletonH[size ?? \"md\"],\n                      \"w-3/5\"\n                    )}\n                  />\n                ) : (\n                  description && (\n                    <span\n                      data-slot=\"dashbox-description\"\n                      className={dashboxDescriptionVariants({ size })}\n                    >\n                      {description}\n                    </span>\n                  )\n                )}\n              </div>\n\n              {showToolbar && (\n                <div\n                  data-slot=\"dashbox-toolbar\"\n                  className={cn(dashboxToolbarVariants(), \"shrink-0\")}\n                >\n                  {loading ? (\n                    <>\n                      {toolbar != null && (\n                        <Skeleton className=\"size-7 rounded-md\" />\n                      )}\n                      {onRefresh && <Skeleton className=\"size-7 rounded-md\" />}\n                      {showMinimize && (\n                        <Skeleton className=\"size-7 rounded-md\" />\n                      )}\n                      {showMaximize && (\n                        <Skeleton className=\"size-7 rounded-md\" />\n                      )}\n                    </>\n                  ) : (\n                    <>\n                      {toolbar}\n\n                      {onRefresh && (\n                        <ToolbarButton\n                          label={tl.refresh}\n                          onClick={handleRefresh}\n                        >\n                          <RefreshCw\n                            className={cn(\n                              \"size-3.5\",\n                              refreshing && \"animate-spin\"\n                            )}\n                          />\n                        </ToolbarButton>\n                      )}\n\n                      {showMinimize &&\n                        !maximized &&\n                        (minimized ? (\n                          <ToolbarButton\n                            label={tl.expand}\n                            onClick={() => setMinimized(false)}\n                          >\n                            <Plus className=\"size-3.5\" />\n                          </ToolbarButton>\n                        ) : (\n                          <ToolbarButton\n                            label={tl.collapse}\n                            onClick={() => setMinimized(true)}\n                          >\n                            <Minus className=\"size-3.5\" />\n                          </ToolbarButton>\n                        ))}\n\n                      {showMaximize &&\n                        !minimized &&\n                        (maximized ? (\n                          <ToolbarButton\n                            label={tl.restore}\n                            onClick={() => setMaximized(false)}\n                          >\n                            <Shrink className=\"size-3.5\" />\n                          </ToolbarButton>\n                        ) : (\n                          <ToolbarButton\n                            label={tl.fullscreen}\n                            onClick={() => setMaximized(true)}\n                          >\n                            <Maximize2 className=\"size-3.5\" />\n                          </ToolbarButton>\n                        ))}\n                    </>\n                  )}\n                </div>\n              )}\n            </div>\n          )}\n\n          {showHeader && <Separator />}\n\n          {!minimized && (\n            <div\n              data-slot=\"dashbox-body\"\n              className={cn(dashboxBodyVariants({ padding: bodyPadding }))}\n            >\n              {loading ? (\n                <div\n                  data-slot=\"dashbox-skeleton\"\n                  className=\"flex flex-col gap-3\"\n                >\n                  <Skeleton className=\"h-4 w-3/4\" />\n                  <Skeleton className=\"h-4 w-full\" />\n                  <Skeleton className=\"h-4 w-2/3\" />\n                  <Skeleton className=\"h-24 w-full\" />\n                </div>\n              ) : typeof children === \"function\" ? (\n                children({ maximized, minimized })\n              ) : (\n                children\n              )}\n            </div>\n          )}\n        </div>\n      </TooltipProvider>\n    )\n  }\n)\n\nDashbox.displayName = \"Dashbox\"\n",
      "type": "registry:ui"
    }
  ],
  "type": "registry:ui"
}
