diff --git a/apps/frontend/src/renderer/__tests__/ui-charts.test.ts b/apps/frontend/src/renderer/__tests__/ui-charts.test.ts index 723c7fe8a..8183d0d71 100644 --- a/apps/frontend/src/renderer/__tests__/ui-charts.test.ts +++ b/apps/frontend/src/renderer/__tests__/ui-charts.test.ts @@ -4,7 +4,7 @@ */ import { describe, expect, it } from 'vitest'; -import { pointsToArea, seriesToPoints } from '@auto-code/ui'; +import { barPercent, pointsToArea, seriesToPoints } from '@auto-code/ui'; describe('seriesToPoints', () => { it('spreads points evenly across the width and inverts Y', () => { @@ -50,3 +50,19 @@ describe('pointsToArea', () => { expect(pointsToArea('', 100, 40)).toBe(''); }); }); + +describe('barPercent', () => { + it('returns the value share of the scale, clamped to 0-100', () => { + expect(barPercent(26, 26)).toBe(100); + expect(barPercent(13, 26)).toBe(50); + expect(barPercent(1, 26)).toBeCloseTo(3.85, 2); + expect(barPercent(40, 26)).toBe(100); + }); + + it('yields 0 for non-positive value or scale', () => { + expect(barPercent(0, 26)).toBe(0); + expect(barPercent(-5, 26)).toBe(0); + expect(barPercent(5, 0)).toBe(0); + expect(barPercent(Number.NaN, 26)).toBe(0); + }); +}); diff --git a/libs/ui/src/index.ts b/libs/ui/src/index.ts index 3e36f74a4..5287c681c 100644 --- a/libs/ui/src/index.ts +++ b/libs/ui/src/index.ts @@ -16,7 +16,9 @@ export type { } from './primitives/LineChart'; export { StatTile } from './primitives/StatTile'; export type { StatTileProps, StatTileTone } from './primitives/StatTile'; -export { seriesToPoints, pointsToArea, CHART_TONE_VARS } from './primitives/charts'; +export { BarList } from './primitives/BarList'; +export type { BarListProps, BarListItem } from './primitives/BarList'; +export { seriesToPoints, pointsToArea, barPercent, CHART_TONE_VARS } from './primitives/charts'; export type { ChartTone } from './primitives/charts'; export { ThemeProvider, useTheme } from './theme/ThemeProvider'; export type { Theme, ResolvedTheme, ThemeProviderProps } from './theme/ThemeProvider'; diff --git a/libs/ui/src/primitives/BarList.css b/libs/ui/src/primitives/BarList.css new file mode 100644 index 000000000..ef1b72622 --- /dev/null +++ b/libs/ui/src/primitives/BarList.css @@ -0,0 +1,40 @@ +.ac-barlist { + list-style: none; + margin: 0; + padding: 0; + display: flex; + flex-direction: column; + gap: 8px; +} +.ac-barlist__row { + display: grid; + grid-template-columns: minmax(80px, auto) minmax(0, 1fr) auto; + align-items: center; + gap: 10px; + font-size: 12px; +} +.ac-barlist__label { + color: var(--muted); + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} +.ac-barlist__track { + height: 8px; + border-radius: 999px; + background: var(--rail); + overflow: hidden; +} +.ac-barlist__bar { + display: block; + height: 100%; + border-radius: 999px; + min-width: 2px; +} +.ac-barlist__value { + font-family: ui-monospace, SFMono-Regular, Menlo, monospace; + font-size: 11.5px; + color: var(--ink); + font-weight: 720; + text-align: right; +} diff --git a/libs/ui/src/primitives/BarList.stories.tsx b/libs/ui/src/primitives/BarList.stories.tsx new file mode 100644 index 000000000..d15656a07 --- /dev/null +++ b/libs/ui/src/primitives/BarList.stories.tsx @@ -0,0 +1,44 @@ +import type { Meta, StoryObj } from '@storybook/react-vite'; +import { BarList } from './BarList'; + +const meta = { + title: 'Primitives/BarList', + component: BarList, + decorators: [ + (Story) => ( +