{"version":3,"file":"EmptyState-BfZYCnLi.cjs","names":[],"sources":["../src/atoms/Sparkline/sparklineGeometry.ts","../src/atoms/Sparkline/Sparkline.tsx","../src/molecules/StatTile/StatTile.tsx","../src/organisms/EmptyState/EmptyState.tsx"],"sourcesContent":["/**\n * Sparkline path maths, kept separate from the component so it can be tested in\n * the node project without a browser.\n *\n * This consolidates six divergent implementations that disagreed on\n * normalisation, flat-series handling and stroke inset. `normalize` makes the\n * first of those an explicit caller decision rather than an accident.\n */\n\nexport type SparklineNormalize = 'minMax' | 'zeroMax';\n\nexport interface SparklineGeometryOptions {\n  width: number;\n  height: number;\n  strokeWidth: number;\n  normalize: SparklineNormalize;\n}\n\nexport interface SparklineGeometry {\n  /** SVG path `d` for the line. Empty string when there is no data. */\n  line: string;\n  /** SVG path `d` for the filled area, closed to the baseline. */\n  area: string;\n  /** Last plotted point, for the optional end marker. */\n  endPoint: { x: number; y: number } | null;\n}\n\nconst round = (n: number): number => Math.round(n * 100) / 100;\n\nexport function buildSparklinePath(\n  values: readonly number[],\n  { width, height, strokeWidth, normalize }: SparklineGeometryOptions,\n): SparklineGeometry {\n  if (values.length === 0) {\n    return { line: '', area: '', endPoint: null };\n  }\n\n  const inset = strokeWidth;\n  const usableHeight = Math.max(0, height - inset * 2);\n  const max = Math.max(...values);\n  const min = normalize === 'zeroMax' ? 0 : Math.min(...values);\n  const span = max - min;\n\n  const stepX = values.length > 1 ? width / (values.length - 1) : 0;\n\n  const points = values.map((value, index) => {\n    const x = index * stepX;\n    // A flat series has no meaningful position within its own range, so it sits\n    // mid-height rather than collapsing onto the baseline.\n    const ratio = span === 0 ? 0.5 : (value - min) / span;\n    const y = inset + (1 - ratio) * usableHeight;\n    return { x, y };\n  });\n\n  const line = points\n    .map((point, index) => `${index === 0 ? 'M' : 'L'}${round(point.x)},${round(point.y)}`)\n    .join(' ');\n\n  const baseline = height - inset;\n  const first = points[0];\n  const last = points[points.length - 1];\n  const area = `${line} L${round(last.x)},${round(baseline)} L${round(first.x)},${round(baseline)} Z`;\n\n  return { line, area, endPoint: { x: last.x, y: last.y } };\n}\n","/**\n * Sparkline\n * Classification: custom\n *\n * A dependency-free trend line for dense contexts (table cells, stat tiles).\n * Carries no ECharts dependency, so it ships from the root barrel.\n */\nimport { useId, useMemo } from 'react';\nimport { useTheme } from '@mui/material/styles';\nimport { chartTokens } from '@/tokens/chart';\nimport { severityColorsByMode, statusColors, type SeverityKey } from '@/tokens/colors';\nimport { buildSparklinePath, type SparklineNormalize } from './sparklineGeometry';\n\nexport interface SparklineProps {\n  /** Series values, oldest first. */\n  data: readonly number[];\n  /** `line` draws a stroke only; `area` adds a gradient fill beneath it. */\n  variant?: 'line' | 'area';\n  /**\n   * `minMax` stretches the series across the full height, exaggerating small\n   * movements. `zeroMax` anchors the baseline at zero, keeping magnitude honest.\n   */\n  normalize?: SparklineNormalize;\n  /**\n   * A categorical series slot index (0-6), a severity key, or `'accent'`.\n   *\n   * Use `'accent'` when the sparkline shows a single measure rather than one\n   * series among several - a volume column in a table, a trend beside a KPI.\n   * Series slots encode identity, and borrowing slot 0 for a lone measure\n   * implies a categorical relationship that is not there.\n   */\n  tone?: number | SeverityKey | 'accent';\n  showEndPoint?: boolean;\n  width?: number;\n  height?: number;\n  strokeWidth?: number;\n  /**\n   * Accessible description, e.g. \"Alert volume, last 7 days, trending up\".\n   * Required: a sparkline conveys information and must not be invisible to\n   * assistive technology.\n   */\n  label: string;\n}\n\nexport function Sparkline({\n  data,\n  variant = 'line',\n  normalize = 'zeroMax',\n  tone = 0,\n  showEndPoint = false,\n  width = 96,\n  height = 24,\n  strokeWidth = 1.5,\n  label,\n}: SparklineProps) {\n  const theme = useTheme();\n  const mode = theme.palette.mode === 'light' ? 'light' : 'dark';\n  const gradientId = useId();\n\n  const color =\n    typeof tone === 'number'\n      ? chartTokens[mode].series[tone % chartTokens[mode].series.length]\n      : tone === 'accent'\n        ? statusColors.general\n        : severityColorsByMode[mode][tone];\n\n  const geometry = useMemo(\n    () => buildSparklinePath(data, { width, height, strokeWidth, normalize }),\n    [data, width, height, strokeWidth, normalize],\n  );\n\n  if (!geometry.line) return null;\n\n  return (\n    <svg\n      width={width}\n      height={height}\n      viewBox={`0 0 ${width} ${height}`}\n      role=\"img\"\n      aria-label={label}\n      style={{ display: 'block', overflow: 'visible' }}\n    >\n      {variant === 'area' && (\n        <>\n          <defs>\n            <linearGradient id={gradientId} x1=\"0\" y1=\"0\" x2=\"0\" y2=\"1\">\n              <stop\n                offset=\"0%\"\n                stopColor={color}\n                stopOpacity={chartTokens[mode].ink.areaOpacity}\n              />\n              <stop offset=\"100%\" stopColor={color} stopOpacity={0} />\n            </linearGradient>\n          </defs>\n          <path d={geometry.area} fill={`url(#${gradientId})`} />\n        </>\n      )}\n      <path\n        d={geometry.line}\n        fill=\"none\"\n        stroke={color}\n        strokeWidth={strokeWidth}\n        strokeLinecap=\"round\"\n        strokeLinejoin=\"round\"\n      />\n      {showEndPoint && geometry.endPoint && (\n        <circle\n          cx={geometry.endPoint.x}\n          cy={geometry.endPoint.y}\n          r={strokeWidth * 1.6}\n          fill={color}\n        />\n      )}\n    </svg>\n  );\n}\n","/**\n * StatTile\n * Classification: custom\n *\n * A headline figure with optional delta and trend. This is what `chartType:\n * 'kpi'` renders — a number is not a chart, and rendering one as an ECharts\n * instance with a faked title costs a canvas for no benefit.\n */\nimport type { ReactNode } from 'react';\nimport { useTheme } from '@mui/material/styles';\nimport Box from '@mui/material/Box';\nimport Typography from '@mui/material/Typography';\nimport { chartTokens } from '@/tokens/chart';\nimport { severityColorsByMode } from '@/tokens/colors';\nimport { Sparkline } from '@/atoms/Sparkline';\n\nexport interface StatTileProps {\n  value: ReactNode;\n  label: string;\n  /** Secondary context, e.g. \"vs. previous 30 days\". */\n  caption?: string;\n  /** Signed percentage change. Direction is conveyed by an arrow, not colour alone. */\n  delta?: number;\n  /**\n   * Whether a rising delta is good. Drives the delta colour; the arrow and sign\n   * carry the direction independently.\n   */\n  deltaPolarity?: 'higherIsBetter' | 'lowerIsBetter';\n  trend?: readonly number[];\n}\n\nexport function StatTile({\n  value,\n  label,\n  caption,\n  delta,\n  deltaPolarity = 'higherIsBetter',\n  trend,\n}: StatTileProps) {\n  const theme = useTheme();\n  const mode = theme.palette.mode === 'light' ? 'light' : 'dark';\n  const tokens = chartTokens[mode];\n  const severity = severityColorsByMode[mode];\n\n  const hasDelta = typeof delta === 'number' && Number.isFinite(delta);\n  const rising = hasDelta && delta > 0;\n  const good = deltaPolarity === 'higherIsBetter' ? rising : !rising;\n  const deltaColor =\n    !hasDelta || delta === 0 ? tokens.ink.label : good ? severity.info : severity.high;\n\n  return (\n    <Box sx={{ display: 'flex', flexDirection: 'column', gap: 0.5, minWidth: 0 }}>\n      <Typography variant=\"body1\" sx={{ color: 'text.secondary' }}>\n        {label}\n      </Typography>\n      <Box sx={{ display: 'flex', alignItems: 'baseline', gap: 1, flexWrap: 'wrap' }}>\n        <Typography variant=\"h3\" sx={{ color: 'text.primary', lineHeight: 1 }}>\n          {value}\n        </Typography>\n        {hasDelta && (\n          <Typography component=\"span\" variant=\"body2\" sx={{ color: deltaColor, fontWeight: 600 }}>\n            {/* The arrow and sign carry direction; colour is reinforcement only. */}\n            {delta > 0 ? '▲' : delta < 0 ? '▼' : '—'} {Math.abs(delta).toFixed(1)}%\n          </Typography>\n        )}\n      </Box>\n      {caption && (\n        <Typography variant=\"caption\" sx={{ color: 'text.secondary' }}>\n          {caption}\n        </Typography>\n      )}\n      {trend && trend.length > 0 && (\n        <Box sx={{ mt: 0.5 }}>\n          <Sparkline data={trend} variant=\"area\" label={`${label} trend`} width={120} height={28} />\n        </Box>\n      )}\n    </Box>\n  );\n}\n","import type { ReactNode } from 'react';\nimport Box from '@mui/material/Box';\nimport Typography from '@mui/material/Typography';\n\n/**\n * EmptyState\n * Classification: custom\n *\n * Domain-specific empty/zero-data placeholder with an illustration slot, title,\n * description and an optional primary action. Used for empty tables, no search\n * results, and first-run states.\n */\nexport interface EmptyStateProps {\n  title: string;\n  description?: string;\n  /** Illustration or icon. */\n  icon?: ReactNode;\n  /** Primary action (e.g. a Button). */\n  action?: ReactNode;\n  dense?: boolean;\n}\n\nexport function EmptyState({ title, description, icon, action, dense = false }: EmptyStateProps) {\n  return (\n    <Box\n      role=\"status\"\n      sx={{\n        display: 'flex',\n        flexDirection: 'column',\n        alignItems: 'center',\n        justifyContent: 'center',\n        textAlign: 'center',\n        gap: 1.5,\n        py: dense ? 4 : 8,\n        px: 3,\n        color: 'text.secondary',\n      }}\n    >\n      {icon ? (\n        <Box sx={{ fontSize: dense ? 40 : 56, lineHeight: 0, color: 'text.disabled' }}>{icon}</Box>\n      ) : null}\n      <Typography variant={dense ? 'subtitle1' : 'h6'} color=\"text.primary\">\n        {title}\n      </Typography>\n      {description ? (\n        <Typography variant=\"body1\" sx={{ maxWidth: 420 }}>\n          {description}\n        </Typography>\n      ) : null}\n      {action ? <Box sx={{ mt: 1 }}>{action}</Box> : null}\n    </Box>\n  );\n}\n"],"mappings":"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"}