{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "ds-risk-level-bar",
  "title": "Risk Level Bar",
  "description": "A segmented progress bar with a movable marker. Uses semantic tokens (--risk-1 to --risk-4) for flexible theming.",
  "dependencies": [
    "class-variance-authority"
  ],
  "registryDependencies": [
    "@lema-ds/skeleton",
    "@lema-ds/ui-i18n",
    "@lema-ds/format-utils",
    "@lema-ds/ds-locale-provider"
  ],
  "files": [
    {
      "path": "components/ds/ds-risk-level-bar.tsx",
      "content": "\"use client\"\n\nimport * as React from \"react\"\nimport { cva } from \"class-variance-authority\"\nimport type { VariantProps } from \"class-variance-authority\"\nimport type { HTMLAttributes } 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 { formatValue } from \"@/lib/format-utils\"\nimport { Skeleton } from \"@/components/ui/skeleton\"\n\n// ── Types ──────────────────────────────────────────────────────────────────\n\nexport type RiskLevelBarSize = \"sm\" | \"md\" | \"lg\"\n\nexport interface RiskSegment {\n  color: string\n  range: [number, number]\n  textClass?: string\n}\n\nexport interface RiskLevelBarProps extends HTMLAttributes<HTMLDivElement> {\n  labelLeft: string\n  labelRight: string\n  value: number\n  loading?: boolean\n  segments?: RiskSegment[]\n  locale?: UILocale\n  size?: RiskLevelBarSize\n}\n\nexport type RiskLevelLabelVariants = VariantProps<typeof riskLevelLabelVariants>\n\n// ── Variants ───────────────────────────────────────────────────────────────\n\nexport const riskLevelContainerVariants = cva(\"flex w-full flex-col\")\n\nexport const riskLevelWrapperVariants = cva(\n  \"flex w-full flex-col items-start justify-start gap-2 md:flex-row md:items-center md:justify-between md:gap-0\"\n)\n\nexport const riskLevelLabelVariants = cva(\n  \"flex h-full items-center font-medium text-foreground transition-colors\",\n  {\n    variants: {\n      side: {\n        left: \"md:mr-3\",\n        right: \"flex items-center gap-2 md:ml-3\",\n      },\n      size: {\n        sm: \"text-xs\",\n        md: \"text-sm\",\n        lg: \"text-base\",\n      },\n    },\n    defaultVariants: { side: \"left\", size: \"sm\" },\n  }\n)\n\nexport const riskLevelValueVariants = cva(\n  \"flex items-center justify-center rounded-md font-semibold tabular-nums shadow-sm transition-all duration-200\",\n  {\n    variants: {\n      size: {\n        sm: \"min-w-[48px] px-2 py-0.5 text-xs leading-none\",\n        md: \"min-w-[60px] px-2.5 py-1 text-sm leading-none\",\n        lg: \"min-w-[72px] px-3 py-1.5 text-base leading-none\",\n      },\n    },\n    defaultVariants: { size: \"sm\" },\n  }\n)\n\nexport const riskLevelSubtitleVariants = cva(\n  \"font-semibold text-muted-foreground\",\n  {\n    variants: {\n      size: {\n        sm: \"text-xs\",\n        md: \"text-sm\",\n        lg: \"text-base\",\n      },\n    },\n    defaultVariants: { size: \"sm\" },\n  }\n)\n\nexport const riskLevelBarContainerVariants = cva(\n  \"relative isolate mt-6 w-full min-w-[100px] overflow-hidden rounded-full bg-muted\",\n  {\n    variants: {\n      size: {\n        sm: \"h-2\",\n        md: \"h-3\",\n        lg: \"h-4\",\n      },\n    },\n    defaultVariants: { size: \"sm\" },\n  }\n)\n\nexport const riskLevelSegmentVariants = cva(\n  \"absolute top-0 h-full transition-all duration-300 ease-in-out\"\n)\n\nexport const riskLevelMarkerVariants = cva([\n  \"absolute -top-2.5 h-0 w-0 border-l-8 border-l-transparent\", // risk scale marker triangle — intentional\n  \"border-t-[12px] border-r-8 border-t-foreground border-r-transparent\",\n  \"group z-10 -translate-x-1/2 cursor-pointer transition-[left] duration-300 ease-in-out\",\n])\n\nexport const riskLevelTooltipVariants = cva(\n  [\n    \"absolute top-[-30px] left-1/2 text-xs\",\n    \"-translate-x-1/2 -translate-y-[120%] scale-95\",\n    \"rounded-md bg-foreground px-2 py-1.5 text-background shadow-lg\",\n    \"pointer-events-none z-20 whitespace-nowrap opacity-0 transition-all duration-150\",\n    \"group-hover:scale-100 group-hover:opacity-100\",\n    \"group-focus:scale-100 group-focus:opacity-100\",\n  ],\n  {\n    variants: {\n      isVisible: {\n        true: \"scale-100 opacity-100\",\n        false: \"\",\n      },\n    },\n    defaultVariants: { isVisible: false },\n  }\n)\n\n// ── Skeleton sizes keyed by size prop ──────────────────────────────────────\n\nconst skeletonDims: Record<\n  RiskLevelBarSize,\n  { labelLeft: string; labelRight: string; value: string; track: string }\n> = {\n  sm: {\n    labelLeft: \"h-3 w-24\",\n    labelRight: \"h-3 w-16\",\n    value: \"h-5 w-12\",\n    track: \"h-2\",\n  },\n  md: {\n    labelLeft: \"h-4 w-28\",\n    labelRight: \"h-4 w-20\",\n    value: \"h-6 w-14\",\n    track: \"h-3\",\n  },\n  lg: {\n    labelLeft: \"h-5 w-32\",\n    labelRight: \"h-5 w-24\",\n    value: \"h-7 w-16\",\n    track: \"h-4\",\n  },\n}\n\n// ── RiskLevelBar ───────────────────────────────────────────────────────────\n\nconst DEFAULT_SEGMENTS: RiskSegment[] = [\n  {\n    color: \"var(--color-risk-1)\",\n    range: [0, 0.25],\n    textClass: \"text-white [text-shadow:0_1px_2px_rgba(0,0,0,0.4)]\", // intentional: white on saturated risk color\n  },\n  {\n    color: \"var(--color-risk-2)\",\n    range: [0.25, 0.5],\n    textClass: \"text-white [text-shadow:0_1px_2px_rgba(0,0,0,0.4)]\", // intentional: white on saturated risk color\n  },\n  {\n    color: \"var(--color-risk-3)\",\n    range: [0.5, 0.75],\n    textClass: \"text-foreground\",\n  },\n  {\n    color: \"var(--color-risk-4)\",\n    range: [0.75, 1],\n    textClass: \"text-foreground\",\n  },\n]\n\nexport const RiskLevelBar = React.forwardRef<HTMLDivElement, RiskLevelBarProps>(\n  (\n    {\n      labelLeft,\n      labelRight,\n      value = 0,\n      loading = false,\n      locale: localeProp,\n      segments = DEFAULT_SEGMENTS,\n      size = \"sm\",\n      className,\n      ...props\n    },\n    ref\n  ) => {\n    const locale = useUILocale(localeProp)\n    const [isTooltipVisible, setIsTooltipVisible] = React.useState(false)\n\n    if (loading) {\n      return (\n        <div\n          ref={ref}\n          className={cn(riskLevelContainerVariants(), className)}\n          data-slot=\"risk-level-bar\"\n          {...props}\n        >\n          <div className={riskLevelWrapperVariants()}>\n            <Skeleton className={skeletonDims[size].labelLeft} />\n            <div className=\"flex items-center gap-2\">\n              <Skeleton className={skeletonDims[size].labelRight} />\n              <Skeleton\n                className={cn(skeletonDims[size].value, \"rounded-md\")}\n              />\n            </div>\n          </div>\n          <Skeleton\n            className={cn(\"mt-6 w-full rounded-full\", skeletonDims[size].track)}\n          />\n        </div>\n      )\n    }\n\n    const clampedValue = Math.max(0, Math.min(1, Number(value) || 0))\n    const markerPosition = `${clampedValue * 100}%`\n\n    const percentageLabel = formatValue(clampedValue, \"percent\", {\n      decimals: 1,\n      locale,\n    })\n\n    const currentSegment = segments.find(\n      (s) => clampedValue >= s.range[0] && clampedValue <= s.range[1]\n    )\n\n    return (\n      <div\n        ref={ref}\n        className={cn(riskLevelContainerVariants(), className)}\n        data-slot=\"risk-level-bar\"\n        {...props}\n      >\n        <div className={riskLevelWrapperVariants()}>\n          <span\n            className={riskLevelLabelVariants({ side: \"left\", size })}\n            data-slot=\"risk-level-bar-label-left\"\n          >\n            {labelLeft}\n          </span>\n          <div\n            className={riskLevelLabelVariants({ side: \"right\", size })}\n            data-slot=\"risk-level-bar-label-right\"\n          >\n            <span className={riskLevelSubtitleVariants({ size })}>\n              {labelRight}\n            </span>\n            <span\n              className={cn(\n                riskLevelValueVariants({ size }),\n                currentSegment?.textClass\n              )}\n              style={{\n                backgroundColor: currentSegment?.color || \"var(--color-muted)\",\n              }}\n              data-slot=\"risk-level-bar-value\"\n            >\n              {percentageLabel}\n            </span>\n          </div>\n        </div>\n\n        <div\n          className={riskLevelBarContainerVariants({ size })}\n          data-slot=\"risk-level-bar-track\"\n        >\n          {segments.map((seg, idx) => (\n            <div\n              key={idx}\n              className={cn(riskLevelSegmentVariants())}\n              style={{\n                left: `${seg.range[0] * 100}%`,\n                width: `${(seg.range[1] - seg.range[0]) * 100}%`,\n                backgroundColor: seg.color,\n              }}\n            />\n          ))}\n\n          <div\n            role=\"button\"\n            tabIndex={0}\n            aria-label={`${UI_I18N[locale].riskLevelBar.value}: ${percentageLabel}`}\n            className={cn(riskLevelMarkerVariants())}\n            style={{ left: markerPosition }}\n            onMouseEnter={() => setIsTooltipVisible(true)}\n            onMouseLeave={() => setIsTooltipVisible(false)}\n            onFocus={() => setIsTooltipVisible(true)}\n            onBlur={() => setIsTooltipVisible(false)}\n            data-slot=\"risk-level-bar-marker\"\n          >\n            <span\n              className={riskLevelTooltipVariants({\n                isVisible: isTooltipVisible,\n              })}\n            >\n              {percentageLabel}\n            </span>\n          </div>\n        </div>\n      </div>\n    )\n  }\n)\n\nRiskLevelBar.displayName = \"RiskLevelBar\"\n",
      "type": "registry:ui"
    }
  ],
  "type": "registry:ui"
}
