@atmos.build/ui 0.1.4 → 0.1.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -11,7 +11,7 @@ export function BarChart(props) {
11
11
  const barW = stacked
12
12
  ? Math.max(7, Math.min(36, groupW * 0.54))
13
13
  : Math.max(5, Math.min(28, groupW / Math.max(1, yKeys.length) - 5));
14
- const cornerRadius = Math.min(8, barW / 2);
14
+ const cornerRadius = Math.min(4, barW / 2);
15
15
  const { state: hoverState, move, clear } = useCartesianHover(metrics, data.length, true);
16
16
  const hoveredRow = hoverState ? data[hoverState.index] : undefined;
17
17
  return (_jsxs("div", { className: "relative h-full w-full", children: [_jsxs("svg", { viewBox: `0 0 ${metrics.w} ${metrics.h}`, className: cn('h-full w-full overflow-visible', className), role: "img", "aria-label": props.ariaLabel, onPointerMove: move, onPointerLeave: clear, children: [_jsx(CartesianGrid, { ...metrics, data: data, xKey: xKey, banded: true }), hoverState ? (_jsx("rect", { x: metrics.pad.l + hoverState.index * groupW, y: metrics.pad.t, width: groupW, height: metrics.innerH, fill: "currentColor", opacity: 0.05, "aria-hidden": true })) : null, data.map((row, rowIndex) => {
@@ -33,7 +33,9 @@ export function BarChart(props) {
33
33
  if (stacked && keyIndex === topStackIndex && height > 0) {
34
34
  return (_jsx("path", { d: topRoundedRectPath(barX, barY, barW, height, cornerRadius), fill: color, opacity: opacity, className: "animate-in fade-in slide-in-from-bottom-1 duration-300 hover:opacity-90" }, `${rowIndex}:${key}`));
35
35
  }
36
- return (_jsx("rect", { x: barX, y: barY, width: barW, height: height, rx: stacked ? 0 : cornerRadius, fill: color, opacity: opacity, className: "animate-in fade-in slide-in-from-bottom-1 duration-300 hover:opacity-90" }, `${rowIndex}:${key}`));
36
+ return (_jsx("path", { d: stacked
37
+ ? `M ${barX} ${barY} h ${barW} v ${height} h ${-barW} Z`
38
+ : topRoundedRectPath(barX, barY, barW, height, cornerRadius), fill: color, opacity: opacity, className: "animate-in fade-in slide-in-from-bottom-1 duration-300 hover:opacity-90" }, `${rowIndex}:${key}`));
37
39
  });
38
40
  })] }), hoverState && hoveredRow ? (_jsx(ChartTooltip, { state: hoverState, row: hoveredRow, xKey: xKey, yKeys: yKeys, colors: colors, format: format })) : null] }));
39
41
  }
@@ -18,7 +18,7 @@ export function chartColor(colors, index) {
18
18
  const SAFE_CSS_COLOR_FUNCTION = /^(?:rgb|rgba|hsl|hsla|oklab|oklch)\([\d\s.,%/+-]+\)$/i;
19
19
  const SAFE_HEX_COLOR = /^#[\da-f]{3,8}$/i;
20
20
  const SAFE_COLOR_KEYWORD = /^(?:black|white|transparent|currentColor)$/i;
21
- const SAFE_INTERNAL_COLOR_TOKEN = /^var\(--(?:primary|foreground|muted-foreground|border|ring|destructive|background|category-[1-6]|(?:success|warning|danger|info)-(?:3|9|11))\)$/i;
21
+ const SAFE_INTERNAL_COLOR_TOKEN = /^var\(--(?:primary|foreground|muted-foreground|border|ring|destructive|background|category-[1-6]|usage|usage-strong|(?:success|warning|danger|info)-(?:3|9|11))\)$/i;
22
22
  function safeChartColor(color) {
23
23
  const value = color?.trim();
24
24
  if (!value || value.length > 96 || hasControlCharacter(value))
@@ -2,9 +2,9 @@ export { AreaChart } from './area-chart.js';
2
2
  export { BarChart } from './bar-chart.js';
3
3
  export { ChartLegend } from './legend.js';
4
4
  export { LineChart } from './line-chart.js';
5
- export { PieChart } from './pie-chart.js';
5
+ export { PieChart, type PieChartProps } from './pie-chart.js';
6
6
  export { RadarChart } from './radar-chart.js';
7
7
  export { RadialChart } from './radial-chart.js';
8
8
  export { ScatterChart } from './scatter-chart.js';
9
- export { chartColor } from './colors.js';
9
+ export { CHART_COLORS, chartColor } from './colors.js';
10
10
  export type { BarChartProps, CartesianChartProps, CategoricalChartProps, ChartAxisOptions, ChartDatum, ChartLegendItem, ScatterChartProps, } from './types.js';
@@ -6,4 +6,4 @@ export { PieChart } from './pie-chart.js';
6
6
  export { RadarChart } from './radar-chart.js';
7
7
  export { RadialChart } from './radial-chart.js';
8
8
  export { ScatterChart } from './scatter-chart.js';
9
- export { chartColor } from './colors.js';
9
+ export { CHART_COLORS, chartColor } from './colors.js';
@@ -24,12 +24,13 @@ export declare function CartesianGrid({ data, xKey, w, h, pad, innerW, innerH, m
24
24
  xKey: string;
25
25
  banded?: boolean;
26
26
  }): import("react").JSX.Element;
27
- export declare function categoricalMetrics(height: number): {
27
+ export declare function categoricalMetrics(height: number, layout?: 'wide' | 'square'): {
28
28
  w: number;
29
29
  h: number;
30
30
  cx: number;
31
31
  cy: number;
32
32
  r: number;
33
33
  };
34
+ export declare function donutPath(cx: number, cy: number, rOuter: number, rInner: number, start: number, end: number): string;
34
35
  export declare function piePath(cx: number, cy: number, r: number, start: number, end: number): string;
35
36
  export declare function ringArcPath(cx: number, cy: number, r: number, start: number, end: number): string;
@@ -1,15 +1,16 @@
1
1
  import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  export function cartesianMetrics(props, stacked) {
3
3
  const w = 720;
4
- const h = 220;
4
+ const h = Math.max(64, Math.min(props.height ?? 220, 720));
5
5
  const axis = props.axis;
6
6
  const showXAxis = axis?.showXAxis ?? true;
7
7
  const showYAxis = axis?.showYAxis ?? true;
8
+ const bare = !showXAxis && !showYAxis;
8
9
  const pad = {
9
- l: showYAxis ? (axis?.yLabel ? 62 : 44) : 20,
10
- r: 16,
11
- t: 14,
12
- b: showXAxis ? (axis?.xLabel ? 46 : 32) : 16,
10
+ l: showYAxis ? (axis?.yLabel ? 62 : 44) : bare ? 2 : 20,
11
+ r: bare ? 2 : 16,
12
+ t: bare ? 4 : 14,
13
+ b: showXAxis ? (axis?.xLabel ? 46 : 32) : bare ? 2 : 16,
13
14
  };
14
15
  const innerW = w - pad.l - pad.r;
15
16
  const innerH = h - pad.t - pad.b;
@@ -72,17 +73,23 @@ export function CartesianGrid({ data, xKey, w, h, pad, innerW, innerH, max, axis
72
73
  return (_jsx("text", { x: xPosition(index), y: h - (axis?.xLabel ? 24 : 10), textAnchor: "middle", fill: "currentColor", opacity: 0.58, fontSize: "9", children: String(row[xKey] ?? index + 1) }, `x-${index}`));
73
74
  }) })) : null, showXAxis && axis?.xLabel ? (_jsx("text", { x: pad.l + innerW / 2, y: h - 6, textAnchor: "middle", fill: "currentColor", opacity: 0.58, fontSize: "10", children: axis.xLabel })) : null, showYAxis && axis?.yLabel ? (_jsx("text", { x: 16, y: pad.t + innerH / 2, textAnchor: "middle", transform: `rotate(-90 16 ${pad.t + innerH / 2})`, fill: "currentColor", opacity: 0.58, fontSize: "10", children: axis.yLabel })) : null] }));
74
75
  }
75
- export function categoricalMetrics(height) {
76
- const w = 720;
76
+ export function categoricalMetrics(height, layout = 'wide') {
77
77
  const h = Math.max(170, Math.min(height, 420));
78
+ const w = layout === 'square' ? h : 720;
78
79
  return {
79
80
  w,
80
81
  h,
81
82
  cx: w / 2,
82
83
  cy: h / 2,
83
- r: Math.min(82, h / 2 - 22),
84
+ r: layout === 'square' ? h / 2 - 4 : Math.min(82, h / 2 - 22),
84
85
  };
85
86
  }
87
+ export function donutPath(cx, cy, rOuter, rInner, start, end) {
88
+ const large = end - start > Math.PI ? 1 : 0;
89
+ const point = (r, angle) => `${(cx + Math.cos(angle) * r).toFixed(2)} ${(cy + Math.sin(angle) * r).toFixed(2)}`;
90
+ return (`M ${point(rOuter, start)} A ${rOuter} ${rOuter} 0 ${large} 1 ${point(rOuter, end)} ` +
91
+ `L ${point(rInner, end)} A ${rInner} ${rInner} 0 ${large} 0 ${point(rInner, start)} Z`);
92
+ }
86
93
  export function piePath(cx, cy, r, start, end) {
87
94
  const large = end - start > Math.PI ? 1 : 0;
88
95
  const x1 = cx + Math.cos(start) * r;
@@ -1,2 +1,11 @@
1
+ import * as React from 'react';
1
2
  import type { CategoricalChartProps } from './types.js';
2
- export declare function PieChart({ data, categoryKey, valueKey, colors, height, ariaLabel, valueFormatter, className, }: CategoricalChartProps): import("react").JSX.Element;
3
+ export interface PieChartProps extends CategoricalChartProps {
4
+ innerRadius?: number;
5
+ center?: React.ReactNode;
6
+ activeKey?: string | null;
7
+ onActiveChange?: (key: string | null) => void;
8
+ layout?: 'wide' | 'square';
9
+ categoryFormatter?: (category: string) => string;
10
+ }
11
+ export declare function PieChart({ data, categoryKey, valueKey, colors, height, ariaLabel, valueFormatter, className, innerRadius, center, activeKey, onActiveChange, layout, categoryFormatter, }: PieChartProps): React.JSX.Element;
@@ -1,9 +1,12 @@
1
+ 'use client';
1
2
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import * as React from 'react';
2
4
  import { cn } from '../../lib/cn.js';
3
5
  import { chartColor } from './colors.js';
4
- import { categoricalMetrics, piePath } from './layout.js';
5
- export function PieChart({ data, categoryKey = 'label', valueKey = 'value', colors, height = 220, ariaLabel, valueFormatter, className, }) {
6
- const { w, h, cx, cy, r } = categoricalMetrics(height);
6
+ import { categoricalMetrics, donutPath, piePath } from './layout.js';
7
+ export function PieChart({ data, categoryKey = 'label', valueKey = 'value', colors, height = 220, ariaLabel, valueFormatter, className, innerRadius = 0.42, center, activeKey = null, onActiveChange, layout = 'wide', categoryFormatter, }) {
8
+ const { w, h, cx, cy, r } = categoricalMetrics(height, layout);
9
+ const hole = Math.max(0, Math.min(0.9, innerRadius)) * r;
7
10
  const total = data.reduce((sum, row) => sum + Math.max(0, Number(row[valueKey] ?? 0)), 0) || 1;
8
11
  const format = valueFormatter ?? ((value) => String(value));
9
12
  const slices = data.reduce((acc, row, index) => {
@@ -13,10 +16,32 @@ export function PieChart({ data, categoryKey = 'label', valueKey = 'value', colo
13
16
  acc.push({ row, value, start, end, index });
14
17
  return acc;
15
18
  }, []);
16
- return (_jsxs("svg", { viewBox: `0 0 ${w} ${h}`, className: cn('h-full w-full overflow-visible', className), role: "img", "aria-label": ariaLabel, children: [slices.map(({ row, value, start, end, index }) => {
17
- const label = String(row[categoryKey] ?? index + 1);
18
- const angle = (value / total) * Math.PI * 2;
19
- const d = angle >= Math.PI * 2 - 0.0001 ? undefined : piePath(cx, cy, r, start, end);
20
- return d ? (_jsx("path", { d: d, fill: chartColor(colors, index), stroke: "var(--background)", strokeWidth: 1.5, className: "animate-in fade-in zoom-in-95 duration-300 hover:opacity-90", children: _jsx("title", { children: `${label}: ${format(value)}` }) }, label)) : (_jsx("circle", { cx: cx, cy: cy, r: r, fill: chartColor(colors, index), stroke: "var(--background)", strokeWidth: 1.5, transform: `rotate(${start} ${cx} ${cy})`, className: "animate-in fade-in zoom-in-95 duration-300 hover:opacity-90", children: _jsx("title", { children: `${label}: ${format(value)}` }) }, label));
21
- }), _jsx("circle", { cx: cx, cy: cy, r: r * 0.42, fill: "var(--background)", opacity: 0.94 })] }));
19
+ const dimmed = activeKey !== null;
20
+ const chart = (_jsx("svg", { viewBox: `0 0 ${w} ${h}`, className: cn('h-full w-full overflow-visible', center ? undefined : className), role: "img", "aria-label": ariaLabel, children: slices.map(({ row, value, start, end, index }) => {
21
+ const category = String(row[categoryKey] ?? index + 1);
22
+ const label = categoryFormatter ? categoryFormatter(category) : category;
23
+ const angle = (value / total) * Math.PI * 2;
24
+ const full = angle >= Math.PI * 2 - 0.0001;
25
+ const active = activeKey === category;
26
+ const d = full
27
+ ? hole > 0
28
+ ? `${donutPath(cx, cy, r, hole, -Math.PI / 2, Math.PI / 2)} ${donutPath(cx, cy, r, hole, Math.PI / 2, Math.PI * 1.5)}`
29
+ : undefined
30
+ : hole > 0
31
+ ? donutPath(cx, cy, r, hole, start, end)
32
+ : piePath(cx, cy, r, start, end);
33
+ const shared = {
34
+ fill: chartColor(colors, index),
35
+ stroke: 'var(--background)',
36
+ strokeWidth: 1.5,
37
+ className: cn('animate-in fade-in zoom-in-95 duration-300 transition-opacity', dimmed && !active && 'opacity-30'),
38
+ onMouseEnter: () => onActiveChange?.(category),
39
+ onMouseLeave: () => onActiveChange?.(null),
40
+ children: _jsx("title", { children: `${label}: ${format(value)}` }),
41
+ };
42
+ return d ? (_jsx("path", { d: d, ...shared }, category)) : (_jsx("circle", { cx: cx, cy: cy, r: r, ...shared }, category));
43
+ }) }));
44
+ if (!center || hole <= 0)
45
+ return chart;
46
+ return (_jsxs("div", { className: cn('relative', className), children: [chart, _jsx("div", { className: "pointer-events-none absolute inset-0 grid place-content-center text-center", children: center })] }));
22
47
  }
@@ -33,11 +33,15 @@ export interface MetricBreakdownItem {
33
33
  tone?: MetricTone;
34
34
  title?: string;
35
35
  onSelect?: () => void;
36
+ color?: string;
36
37
  }
37
38
  export interface MetricBreakdownProps extends Omit<React.ComponentProps<'div'>, 'title'> {
38
39
  title?: React.ReactNode;
39
40
  items: MetricBreakdownItem[];
40
41
  emptyLabel?: React.ReactNode;
41
42
  showBars?: boolean;
43
+ barPlacement?: 'below' | 'inline';
44
+ activeKey?: string | null;
45
+ onActiveChange?: (key: string | null) => void;
42
46
  }
43
- export declare function MetricBreakdown({ title, items, emptyLabel, showBars, className, ...props }: MetricBreakdownProps): React.JSX.Element;
47
+ export declare function MetricBreakdown({ title, items, emptyLabel, showBars, barPlacement, activeKey, onActiveChange, className, ...props }: MetricBreakdownProps): React.JSX.Element;
@@ -10,7 +10,7 @@ const TONE_TEXT = {
10
10
  danger: 'text-danger-9',
11
11
  };
12
12
  export function MetricSectionHeading({ title, hint, action, className, ...props }) {
13
- return (_jsxs("div", { "data-slot": "metric-section-heading", className: cn('mb-2.5 flex flex-wrap items-start justify-between gap-2', className), ...props, children: [_jsxs("div", { className: "min-w-0", children: [_jsx("h2", { className: "text-[13px] font-medium", children: title }), hint ? (_jsx("p", { className: "text-muted-foreground mt-0.5 text-[11.5px] leading-snug", children: hint })) : null] }), action] }));
13
+ return (_jsxs("div", { "data-slot": "metric-section-heading", className: cn('mb-2.5 flex flex-wrap items-start justify-between gap-2', className), ...props, children: [_jsxs("div", { className: "min-w-0 flex-1", children: [_jsx("h2", { className: "text-[13px] font-medium", children: title }), hint ? (_jsx("p", { className: "text-muted-foreground mt-0.5 text-[11.5px] leading-snug", children: hint })) : null] }), action ? _jsx("div", { className: "shrink-0", children: action }) : null] }));
14
14
  }
15
15
  export function MetricGrid({ className, ...props }) {
16
16
  return (_jsx("div", { "data-slot": "metric-grid", className: cn('bg-background grid gap-px overflow-hidden rounded-xl', className), ...props }));
@@ -28,13 +28,24 @@ const TONE_BAR = {
28
28
  warning: 'bg-warning-9/60',
29
29
  danger: 'bg-danger-9/55',
30
30
  };
31
- export function MetricBreakdown({ title, items, emptyLabel, showBars = false, className, ...props }) {
32
- return (_jsxs("div", { "data-slot": "metric-breakdown", className: cn('min-w-0', className), ...props, children: [title ? _jsx("h3", { className: "mb-1 text-xs font-medium", children: title }) : null, items.length === 0 ? (_jsx("p", { className: "text-muted-foreground text-xs", children: emptyLabel })) : (_jsx("ul", { className: cn(showBars && 'grid gap-2'), children: items.map((item) => (_jsx("li", { children: _jsx(MetricBreakdownRow, { item: item, showBar: showBars }) }, item.key))) }))] }));
31
+ export function MetricBreakdown({ title, items, emptyLabel, showBars = false, barPlacement = 'below', activeKey = null, onActiveChange, className, ...props }) {
32
+ return (_jsxs("div", { "data-slot": "metric-breakdown", className: cn('min-w-0', className), ...props, children: [title ? _jsx("h3", { className: "mb-1 text-xs font-medium", children: title }) : null, items.length === 0 ? (_jsx("p", { className: "text-muted-foreground text-xs", children: emptyLabel })) : (_jsx("ul", { className: cn(showBars && barPlacement === 'below' && 'grid gap-2'), children: items.map((item) => (_jsx("li", { onMouseEnter: onActiveChange ? () => onActiveChange(item.key) : undefined, onMouseLeave: onActiveChange ? () => onActiveChange(null) : undefined, onFocus: onActiveChange ? () => onActiveChange(item.key) : undefined, onBlur: onActiveChange ? () => onActiveChange(null) : undefined, children: _jsx(MetricBreakdownRow, { item: item, showBar: showBars, placement: barPlacement, active: activeKey === item.key, dimmed: activeKey !== null && activeKey !== item.key }) }, item.key))) }))] }));
33
33
  }
34
- function MetricBreakdownRow({ item, showBar }) {
34
+ function MetricBreakdownRow({ item, showBar, placement, active, dimmed, }) {
35
35
  const tone = item.tone ?? 'neutral';
36
- const row = (_jsxs(_Fragment, { children: [_jsxs("span", { className: "flex items-center justify-between gap-3 text-xs", children: [_jsx("span", { className: cn('min-w-0 truncate', tone === 'neutral' ? 'text-muted-foreground' : TONE_TEXT[tone]), title: item.title, children: item.label }), _jsx("span", { className: "shrink-0 font-mono tabular-nums", children: item.value })] }), showBar ? (_jsx("span", { className: "bg-background mt-1.5 block h-1 overflow-hidden rounded-full", children: _jsx("span", { className: cn('block h-full rounded-full', TONE_BAR[tone]), style: { width: `${Math.round(Math.max(0, Math.min(1, item.fraction ?? 0)) * 100)}%` } }) })) : null] }));
37
- if (!item.onSelect)
38
- return _jsx("div", { className: "py-0.5", children: row });
39
- return (_jsx("button", { type: "button", onClick: item.onSelect, className: "hover:bg-background/60 focus-visible:ring-ring -mx-1.5 block w-[calc(100%+0.75rem)] rounded-sm px-1.5 py-0.5 text-left focus-visible:ring-2 focus-visible:outline-none", children: row }));
36
+ const inline = showBar && placement === 'inline';
37
+ const width = `${Math.round(Math.max(0, Math.min(1, item.fraction ?? 0)) * 100)}%`;
38
+ const fill = (_jsx("span", { className: cn('block h-full rounded-full', !item.color && TONE_BAR[tone]), style: { width, backgroundColor: item.color } }));
39
+ const row = (_jsxs(_Fragment, { children: [_jsxs("span", { className: cn('text-xs', inline
40
+ ? 'grid grid-cols-[auto_7rem_minmax(0,1fr)] items-center gap-2.5 sm:grid-cols-[auto_12rem_minmax(0,1fr)]'
41
+ : 'flex items-center gap-2.5'), children: [item.color ? (_jsx("span", { "aria-hidden": true, className: "size-2 shrink-0 rounded-[2px]", style: { backgroundColor: item.color } })) : inline ? (_jsx("span", { "aria-hidden": true, className: "size-2 shrink-0" })) : null, _jsx("span", { className: cn('min-w-0 truncate', !inline && 'flex-1', tone === 'neutral' ? 'text-muted-foreground' : TONE_TEXT[tone]), title: item.title, children: item.label }), inline ? (_jsxs("span", { className: "relative flex h-4 items-center", children: [_jsx("span", { className: "bg-background block h-1 w-full overflow-hidden rounded-full", children: fill }), _jsx("span", { "data-slot": "metric-row-value", className: cn('bg-muted absolute inset-y-0 right-0 flex items-center rounded-sm pl-1.5 font-mono tabular-nums transition-opacity', active ? 'opacity-100' : 'opacity-100 [@media(hover:hover)]:opacity-0'), children: item.value })] })) : (_jsx("span", { className: "shrink-0 font-mono tabular-nums", children: item.value }))] }), showBar && placement === 'below' ? (_jsx("span", { className: cn('bg-background mt-1.5 block h-1 overflow-hidden rounded-full', item.color && 'ml-4'), children: fill })) : null] }));
42
+ const dim = cn('transition-opacity', dimmed && 'opacity-40');
43
+ if (!item.onSelect) {
44
+ return (_jsx("div", { className: cn('py-0.5', showBar &&
45
+ placement === 'inline' &&
46
+ 'focus-visible:ring-ring -mx-1.5 rounded-sm px-1.5 focus-visible:ring-2 focus-visible:outline-none', showBar &&
47
+ placement === 'inline' &&
48
+ '[&:focus-visible_[data-slot=metric-row-value]]:opacity-100', dim), tabIndex: showBar && placement === 'inline' ? 0 : undefined, children: row }));
49
+ }
50
+ return (_jsx("button", { type: "button", onClick: item.onSelect, className: cn('hover:bg-background/60 focus-visible:ring-ring -mx-1.5 block w-[calc(100%+0.75rem)] rounded-sm px-1.5 py-0.5 text-left focus-visible:ring-2 focus-visible:outline-none', '[&:focus-visible_[data-slot=metric-row-value]]:opacity-100', dim), children: row }));
40
51
  }
@@ -4,10 +4,7 @@ export interface SpecStripItem {
4
4
  label: string;
5
5
  value: React.ReactNode;
6
6
  suffix?: React.ReactNode;
7
- /**
8
- * What the value is changing from. Present, the item reads "before → after", which is what a
9
- * shape being upgraded is: the new number alone says what you get without saying what moved.
10
- */
7
+ /** What the value is changing from. Present, the item reads "before → after". (audited:keep - documents the rendered text contract) */
11
8
  from?: React.ReactNode;
12
9
  }
13
10
  export interface SpecStripProps extends Omit<React.ComponentProps<'div'>, 'children'> {
@@ -9,9 +9,11 @@ export interface UsageBarProps extends Omit<React.ComponentProps<'div'>, 'childr
9
9
  label: string;
10
10
  /** Consumed share, 0–1. Values outside the range clamp rather than overflow the track. */
11
11
  fraction: number;
12
+ pendingFraction?: number;
12
13
  /** Right-aligned readout — a percentage, "12k of 200k", whatever the caller measures in. */
13
14
  value?: React.ReactNode;
14
15
  caption?: React.ReactNode;
15
16
  tone?: UsageBarTone;
17
+ srOnlyLabel?: boolean;
16
18
  }
17
- export declare function UsageBar({ label, fraction, value, caption, tone, className, ...props }: UsageBarProps): React.JSX.Element;
19
+ export declare function UsageBar({ label, fraction, pendingFraction, value, caption, tone, srOnlyLabel, className, ...props }: UsageBarProps): React.JSX.Element;
@@ -18,9 +18,11 @@ export function usageTone(fraction) {
18
18
  return 'warning';
19
19
  return 'usage';
20
20
  }
21
- export function UsageBar({ label, fraction, value, caption, tone, className, ...props }) {
21
+ export function UsageBar({ label, fraction, pendingFraction = 0, value, caption, tone, srOnlyLabel = false, className, ...props }) {
22
22
  const clamped = Math.max(0, Math.min(1, Number.isFinite(fraction) ? fraction : 0));
23
23
  const percent = Math.round(clamped * 100);
24
- const resolvedTone = tone ?? usageTone(clamped);
25
- return (_jsxs("div", { "data-slot": "usage-bar", "data-tone": resolvedTone, role: "meter", "aria-valuemin": 0, "aria-valuemax": 100, "aria-valuenow": percent, "aria-label": label, className: cn('flex flex-col gap-1', className), ...props, children: [_jsxs("div", { className: "flex items-baseline justify-between gap-2 text-[11.5px]", children: [_jsx("span", { className: "text-muted-foreground min-w-0 truncate", children: label }), value ? (_jsx("span", { className: "text-muted-foreground shrink-0 font-mono tabular-nums", children: value })) : null] }), _jsx("div", { className: "bg-muted h-1 overflow-hidden rounded-full", "aria-hidden": true, children: _jsx("span", { className: cn('block h-full rounded-full transition-[width] duration-150 ease-[var(--ease-out-expo)] motion-reduce:transition-none', TONE_FILL[resolvedTone]), style: { width: `${percent}%` } }) }), caption ? (_jsx("p", { className: "text-muted-foreground/75 font-mono text-[10.5px]", children: caption })) : null] }));
24
+ const pending = Math.max(0, Math.min(1 - clamped, Number.isFinite(pendingFraction) ? pendingFraction : 0));
25
+ const pendingPercent = Math.round(pending * 100);
26
+ const resolvedTone = tone ?? usageTone(clamped + pending);
27
+ return (_jsxs("div", { "data-slot": "usage-bar", "data-tone": resolvedTone, role: "meter", "aria-valuemin": 0, "aria-valuemax": 100, "aria-valuenow": Math.min(100, percent + pendingPercent), "aria-label": label, className: cn('flex flex-col gap-1', className), ...props, children: [srOnlyLabel && !value ? null : (_jsxs("div", { className: "flex items-baseline justify-between gap-2 text-[11.5px]", children: [srOnlyLabel ? null : (_jsx("span", { className: "text-muted-foreground min-w-0 truncate", children: label })), value ? (_jsx("span", { className: "text-muted-foreground ml-auto shrink-0 font-mono tabular-nums", children: value })) : null] })), _jsxs("div", { className: "bg-muted flex h-1 overflow-hidden rounded-full", "aria-hidden": true, children: [_jsx("span", { className: cn('block h-full transition-[width] duration-150 ease-[var(--ease-out-expo)] motion-reduce:transition-none', TONE_FILL[resolvedTone]), style: { width: `${percent}%` } }), pendingPercent > 0 ? (_jsx("span", { "data-slot": "usage-bar-pending", className: cn('block h-full opacity-40 transition-[width] duration-150 ease-[var(--ease-out-expo)] motion-reduce:transition-none', TONE_FILL[resolvedTone]), style: { width: `${pendingPercent}%` } })) : null] }), caption ? (_jsx("p", { className: "text-muted-foreground/75 font-mono text-[10.5px]", children: caption })) : null] }));
26
28
  }
package/dist/index.d.ts CHANGED
@@ -4,7 +4,7 @@ export { AudioPreview, type AudioPreviewProps, type AudioPreviewLabels, type Aud
4
4
  export { AvatarUploader, type AvatarUploaderLabels, type AvatarUploaderProps, } from './components/avatar-uploader.js';
5
5
  export { Avatar, avatarVariants, initialsFromName, type AvatarProps } from './components/avatar.js';
6
6
  export { Badge, badgeVariants } from './components/badge.js';
7
- export { AreaChart, BarChart, ChartLegend, LineChart, PieChart, RadarChart, RadialChart, ScatterChart, chartColor, type BarChartProps, type CartesianChartProps, type CategoricalChartProps, type ChartAxisOptions, type ChartDatum, type ChartLegendItem, type ScatterChartProps, } from './components/charts/index.js';
7
+ export { AreaChart, BarChart, CHART_COLORS, ChartLegend, LineChart, PieChart, RadarChart, RadialChart, ScatterChart, chartColor, type BarChartProps, type CartesianChartProps, type CategoricalChartProps, type ChartAxisOptions, type ChartDatum, type ChartLegendItem, type PieChartProps, type ScatterChartProps, } from './components/charts/index.js';
8
8
  export { ChannelBadge, ChannelListRow, channelIcon, channelListRowVariants, type ChannelBadgeProps, type ChannelListRowProps, type ChannelKind, } from './components/channel-list.js';
9
9
  export { Button, buttonVariants } from './components/button.js';
10
10
  export { Chip, chipVariants, type ChipProps, type ChipLabelProps, type ChipRemoveProps, } from './components/chip.js';
package/dist/index.js CHANGED
@@ -4,7 +4,7 @@ export { AudioPreview, } from './components/audio-preview.js';
4
4
  export { AvatarUploader, } from './components/avatar-uploader.js';
5
5
  export { Avatar, avatarVariants, initialsFromName } from './components/avatar.js';
6
6
  export { Badge, badgeVariants } from './components/badge.js';
7
- export { AreaChart, BarChart, ChartLegend, LineChart, PieChart, RadarChart, RadialChart, ScatterChart, chartColor, } from './components/charts/index.js';
7
+ export { AreaChart, BarChart, CHART_COLORS, ChartLegend, LineChart, PieChart, RadarChart, RadialChart, ScatterChart, chartColor, } from './components/charts/index.js';
8
8
  export { ChannelBadge, ChannelListRow, channelIcon, channelListRowVariants, } from './components/channel-list.js';
9
9
  export { Button, buttonVariants } from './components/button.js';
10
10
  export { Chip, chipVariants, } from './components/chip.js';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@atmos.build/ui",
3
- "version": "0.1.4",
3
+ "version": "0.1.6",
4
4
  "description": "The complete atmOS React component system and MCP App authoring helpers.",
5
5
  "type": "module",
6
6
  "license": "UNLICENSED",