feat: add linear variant to ProgressGauge component#927
Conversation
Add `variant: 'circular' | 'linear'` prop (default: 'circular') to ProgressGauge. Linear variant renders a horizontal bar gauge with the same color thresholds, percentage display, and accessibility attributes as the circular variant. Includes Storybook stories and unit tests. Closes #836 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Pre-reviewed by 5 agents, 8 findings addressed: - Update CLAUDE.md and design spec component descriptions - Replace inline transitionTimingFunction with Tailwind ease-in-out - Add data-testid attributes to track/fill for stable test selectors - Add aria-label, aria-valuenow with custom max, size-sm, value-0 tests Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Repository UI Review profile: ASSERTIVE Plan: Pro Run ID: 📒 Files selected for processing (2)
📜 Recent review details⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (6)
🧰 Additional context used📓 Path-based instructions (3)web/src/**/*.{ts,tsx}📄 CodeRabbit inference engine (CLAUDE.md)
Files:
web/src/components/ui/**/*.{ts,tsx}📄 CodeRabbit inference engine (CLAUDE.md)
Files:
web/src/**/__tests__/**/*.{ts,tsx}📄 CodeRabbit inference engine (CLAUDE.md)
Files:
🧠 Learnings (8)📚 Learning: 2026-03-30T06:54:37.737ZApplied to files:
📚 Learning: 2026-03-30T06:54:37.737ZApplied to files:
📚 Learning: 2026-03-30T06:54:37.737ZApplied to files:
📚 Learning: 2026-03-20T08:28:32.845ZApplied to files:
📚 Learning: 2026-03-30T06:54:37.737ZApplied to files:
📚 Learning: 2026-03-30T06:54:37.737ZApplied to files:
📚 Learning: 2026-03-30T06:54:37.737ZApplied to files:
📚 Learning: 2026-03-30T06:54:37.737ZApplied to files:
🔇 Additional comments (10)
WalkthroughThe PR adds a 🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. Comment |
There was a problem hiding this comment.
Code Review
This pull request introduces a linear variant to the ProgressGauge component, complementing the existing circular design. The changes include updates to the documentation, component logic, Storybook stories, and unit tests. The component now supports both circular and linear displays with configurable sizes and semantic coloring. Feedback focuses on improving code maintainability by refactoring the test suite to use describe.each for shared logic and extracting duplicated JSX elements within the linear rendering path to adhere to DRY principles.
| }) | ||
| }) | ||
|
|
||
| describe('ProgressGauge linear variant', () => { |
There was a problem hiding this comment.
This new test suite for the linear variant duplicates many tests from the original suite for the circular variant (e.g., value clamping, percentage calculation, handling of NaN/Infinity).
To avoid code duplication and improve the maintainability of your tests, consider refactoring to use describe.each for tests that are common to both variants. This way, you can run the same test logic for both 'circular' and 'linear' variants, and keep variant-specific tests in separate describe blocks.
Here's an example structure:
describe.each([
['circular' as const],
['linear' as const],
])('ProgressGauge common logic (variant: %s)', (variant) => {
it('clamps value to 0 minimum', () => {
render(<ProgressGauge value={-10} variant={variant} />);
expect(screen.getByText('0%')).toBeInTheDocument();
});
// ... other common tests
});
describe('ProgressGauge circular variant', () => {
it('renders an SVG', () => {
// ... test specific to circular
});
});
describe('ProgressGauge linear variant', () => {
it('does not render an SVG', () => {
// ... test specific to linear
});
});| if (variant === 'linear') { | ||
| return ( | ||
| <div | ||
| role="meter" | ||
| aria-valuenow={percentage} | ||
| aria-valuemin={0} | ||
| aria-valuemax={100} | ||
| aria-label={label ? `${label}: ${percentage}%` : `${percentage}%`} | ||
| className={cn('flex flex-col gap-1', className)} | ||
| > | ||
| {label && ( | ||
| <div className="flex items-baseline justify-between"> | ||
| <span className={cn('text-muted-foreground', config.barLabelSize)}> | ||
| {label} | ||
| </span> | ||
| <span className={cn('font-mono font-semibold text-foreground', config.percentSize)}> | ||
| {percentage}% | ||
| </span> | ||
| </div> | ||
| )} | ||
| <div | ||
| data-testid="progress-track" | ||
| className={cn('w-full overflow-hidden rounded-full bg-border', config.trackHeight)} | ||
| > | ||
| <div | ||
| data-testid="progress-fill" | ||
| className={cn( | ||
| 'h-full rounded-full transition-all duration-[900ms] ease-in-out', | ||
| FILL_COLOR_CLASSES[color], | ||
| )} | ||
| style={{ width: `${percentage}%` }} | ||
| /> | ||
| </div> | ||
| {!label && ( | ||
| <span className={cn('font-mono font-semibold text-foreground', config.percentSize)}> | ||
| {percentage}% | ||
| </span> | ||
| )} | ||
| </div> | ||
| ) | ||
| } |
There was a problem hiding this comment.
The rendering logic for the linear variant has some duplicated code for displaying the percentage. To improve maintainability and adhere to the DRY (Don't Repeat Yourself) principle, you can extract the percentage <span> into a constant and reuse it.
if (variant === 'linear') {
const percentageText = (
<span className={cn('font-mono font-semibold text-foreground', config.percentSize)}>
{percentage}%
</span>
);
return (
<div
role="meter"
aria-valuenow={percentage}
aria-valuemin={0}
aria-valuemax={100}
aria-label={label ? `${label}: ${percentage}%` : `${percentage}%`}
className={cn('flex flex-col gap-1', className)}
>
{label && (
<div className="flex items-baseline justify-between">
<span className={cn('text-muted-foreground', config.barLabelSize)}>
{label}
</span>
{percentageText}
</div>
)}
<div
data-testid="progress-track"
className={cn('w-full overflow-hidden rounded-full bg-border', config.trackHeight)}
>
<div
data-testid="progress-fill"
className={cn(
'h-full rounded-full transition-all duration-[900ms] ease-in-out',
FILL_COLOR_CLASSES[color],
)}
style={{ width: `${percentage}%` }}
/>
</div>
{!label && percentageText}
</div>
);
}
Dependency Review✅ No vulnerabilities or license issues or OpenSSF Scorecard issues found.Snapshot WarningsEnsure that dependencies are being submitted on PR branches. Re-running this action after a short time may resolve the issue. See the documentation for more information and troubleshooting advice. Scanned FilesNone |
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@web/src/__tests__/components/ui/progress-gauge.test.tsx`:
- Around line 184-202: Replace the four nearly identical unit tests in
progress-gauge.test.tsx with a single table-driven test using test.each (or
it.each) that iterates over rows of {value, expectedClass}; for each row render
the ProgressGauge component (use ProgressGauge and variant="linear") and assert
screen.getByTestId('progress-fill') has the expected class; this reduces
duplication and centralizes threshold-to-class mappings so updates to
thresholds/classes require changing only the test table.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository UI
Review profile: ASSERTIVE
Plan: Pro
Run ID: f2f60776-0540-49c0-af59-0d2564dc97d2
📒 Files selected for processing (5)
CLAUDE.mddocs/design/brand-and-ux.mdweb/src/__tests__/components/ui/progress-gauge.test.tsxweb/src/components/ui/progress-gauge.stories.tsxweb/src/components/ui/progress-gauge.tsx
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (7)
- GitHub Check: Deploy Preview
- GitHub Check: Dashboard Test
- GitHub Check: Build Backend
- GitHub Check: Build Sandbox
- GitHub Check: Build Web
- GitHub Check: Dependency Review
- GitHub Check: Analyze (python)
🧰 Additional context used
📓 Path-based instructions (6)
docs/design/**/*.md
📄 CodeRabbit inference engine (CLAUDE.md)
When approved deviations from the spec occur, update the relevant
docs/design/page to reflect the new reality.
Files:
docs/design/brand-and-ux.md
docs/**/*.md
📄 CodeRabbit inference engine (CLAUDE.md)
Documentation: use Markdown in
docs/directory, built with Zensical. Design spec indocs/design/(11 pages), Architecture indocs/architecture/, Roadmap indocs/roadmap/.
Files:
docs/design/brand-and-ux.md
web/src/**/*.{ts,tsx}
📄 CodeRabbit inference engine (CLAUDE.md)
web/src/**/*.{ts,tsx}: React component reuse: ALWAYS reuse existing components fromweb/src/components/ui/before creating new ones. The design system inventory covers all common patterns.
Design tokens: use Tailwind semantic classes (text-foreground,bg-card,text-accent,text-success,bg-danger, etc.) or CSS variables (var(--so-accent)). NEVER hardcode hex values in.tsx/.tsfiles.
Typography in web code: usefont-sansorfont-mono(maps to Geist tokens). NEVER setfontFamilydirectly.
Spacing in web code: use density-aware tokens (p-card,gap-section-gap,gap-grid-gap) or standard Tailwind spacing. NEVER hardcode pixel values for layout spacing.
Shadows/borders in web code: use token variables (var(--so-shadow-card-hover),border-border,border-bright).
Usecn()from@/lib/utilsfor conditional class merging in React components.
Do NOT recreate status dots inline - use<StatusBadge>component.
Do NOT build card-with-header layouts from scratch - use<SectionCard>component.
Do NOT create metric displays withtext-metric font-bold- use<MetricCard>component.
Do NOT render initials circles manually - use<Avatar>component.
Do NOT create complex (>8 line) JSX inside.map()- extract to a shared component.
Do NOT usergba()with hardcoded values - use design token variables.
React: TypeScript 6.0 hasnoUncheckedSideEffectImportsdefaulting to true - CSS side-effect imports need type declarations (Vite's/// <reference types='vite/client' />covers this).
React/Web: ESLint zero warnings enforced onweb/src/**/*.{ts,tsx}files.
React/Web: use React 19, react-router for routing, shadcn/ui + Radix UI for components, Tailwind CSS 4 for styling.
React/Web: use Zustand for state management and@tanstack/react-queryfor server state.
Files:
web/src/__tests__/components/ui/progress-gauge.test.tsxweb/src/components/ui/progress-gauge.tsxweb/src/components/ui/progress-gauge.stories.tsx
web/src/**/__tests__/**/*.{ts,tsx}
📄 CodeRabbit inference engine (CLAUDE.md)
React/Web: use Vitest for unit testing with coverage scoped to files changed vs origin/main.
Files:
web/src/__tests__/components/ui/progress-gauge.test.tsx
web/src/components/ui/**/*.{ts,tsx}
📄 CodeRabbit inference engine (CLAUDE.md)
When creating new React components: place in
web/src/components/ui/with descriptive kebab-case filename. Create.stories.tsxfile with all component states (default, hover, loading, error, empty). Export props as TypeScript interface. Use design tokens exclusively - no hardcoded colors, fonts, or spacing.
Files:
web/src/components/ui/progress-gauge.tsxweb/src/components/ui/progress-gauge.stories.tsx
web/src/**/*.stories.{ts,tsx}
📄 CodeRabbit inference engine (CLAUDE.md)
Storybook 10: use
storybook/test(not@storybook/test) andstorybook/actions(not@storybook/addon-actions) for imports.
Files:
web/src/components/ui/progress-gauge.stories.tsx
🧠 Learnings (14)
📚 Learning: 2026-03-15T21:20:09.993Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T21:20:09.993Z
Learning: Applies to web/src/components/** : Vue components organized by feature (agents/, approvals/, budget/, common/, dashboard/, layout/, messages/, org-chart/, tasks/).
Applied to files:
CLAUDE.mddocs/design/brand-and-ux.md
📚 Learning: 2026-03-30T06:54:37.737Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-30T06:54:37.737Z
Learning: Applies to web/src/**/*.{ts,tsx} : Do NOT build card-with-header layouts from scratch - use `<SectionCard>` component.
Applied to files:
CLAUDE.mddocs/design/brand-and-ux.md
📚 Learning: 2026-03-30T06:54:37.737Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-30T06:54:37.737Z
Learning: Applies to web/src/**/*.{ts,tsx} : Do NOT create metric displays with `text-metric font-bold` - use `<MetricCard>` component.
Applied to files:
CLAUDE.mddocs/design/brand-and-ux.mdweb/src/components/ui/progress-gauge.tsx
📚 Learning: 2026-03-30T06:54:37.737Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-30T06:54:37.737Z
Learning: Applies to web/src/components/ui/**/*.{ts,tsx} : When creating new React components: place in `web/src/components/ui/` with descriptive kebab-case filename. Create `.stories.tsx` file with all component states (default, hover, loading, error, empty). Export props as TypeScript interface. Use design tokens exclusively - no hardcoded colors, fonts, or spacing.
Applied to files:
docs/design/brand-and-ux.mdweb/src/components/ui/progress-gauge.stories.tsx
📚 Learning: 2026-03-30T06:54:37.737Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-30T06:54:37.737Z
Learning: Applies to web/src/**/*.{ts,tsx} : React component reuse: ALWAYS reuse existing components from `web/src/components/ui/` before creating new ones. The design system inventory covers all common patterns.
Applied to files:
docs/design/brand-and-ux.md
📚 Learning: 2026-03-30T06:54:37.737Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-30T06:54:37.737Z
Learning: Applies to web/src/**/*.{ts,tsx} : Do NOT recreate status dots inline - use `<StatusBadge>` component.
Applied to files:
docs/design/brand-and-ux.mdweb/src/components/ui/progress-gauge.tsx
📚 Learning: 2026-03-30T06:54:37.737Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-30T06:54:37.737Z
Learning: Applies to web/src/**/*.{ts,tsx} : Design tokens: use Tailwind semantic classes (`text-foreground`, `bg-card`, `text-accent`, `text-success`, `bg-danger`, etc.) or CSS variables (`var(--so-accent)`). NEVER hardcode hex values in `.tsx`/`.ts` files.
Applied to files:
docs/design/brand-and-ux.mdweb/src/components/ui/progress-gauge.tsx
📚 Learning: 2026-03-30T06:54:37.737Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-30T06:54:37.737Z
Learning: Applies to web/src/**/*.{ts,tsx} : Spacing in web code: use density-aware tokens (`p-card`, `gap-section-gap`, `gap-grid-gap`) or standard Tailwind spacing. NEVER hardcode pixel values for layout spacing.
Applied to files:
docs/design/brand-and-ux.md
📚 Learning: 2026-03-30T06:54:37.737Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-30T06:54:37.737Z
Learning: Applies to web/src/**/__tests__/**/*.{ts,tsx} : React/Web: use Vitest for unit testing with coverage scoped to files changed vs origin/main.
Applied to files:
web/src/__tests__/components/ui/progress-gauge.test.tsx
📚 Learning: 2026-03-20T08:28:32.845Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-20T08:28:32.845Z
Learning: Applies to web/src/__tests__/**/*.{ts,js} : Dashboard testing: Vitest unit tests organized by feature under `web/src/__tests__/`. Use fast-check for property-based testing (`fc.assert` + `fc.property`).
Applied to files:
web/src/__tests__/components/ui/progress-gauge.test.tsx
📚 Learning: 2026-03-30T06:54:37.737Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-30T06:54:37.737Z
Learning: Applies to web/src/**/*.stories.{ts,tsx} : Storybook 10: use `storybook/test` (not `storybook/test`) and `storybook/actions` (not `storybook/addon-actions`) for imports.
Applied to files:
web/src/components/ui/progress-gauge.stories.tsx
📚 Learning: 2026-03-30T06:54:37.737Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-30T06:54:37.737Z
Learning: Applies to web/.storybook/**/*.{ts,tsx} : Storybook 10: use `parameters.backgrounds.options` (object keyed by name) + `initialGlobals.backgrounds.value` for backgrounds (replaces old `default` + `values` array).
Applied to files:
web/src/components/ui/progress-gauge.stories.tsx
📚 Learning: 2026-03-30T06:54:37.737Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-30T06:54:37.737Z
Learning: Applies to web/.storybook/**/*.{ts,tsx} : Storybook 10: use `parameters.a11y.test: 'error' | 'todo' | 'off'` for a11y testing (replaces old `.element` and `.manual`). Set globally in `preview.tsx` to enforce WCAG compliance on all stories.
Applied to files:
web/src/components/ui/progress-gauge.stories.tsx
📚 Learning: 2026-03-30T06:54:37.737Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-30T06:54:37.737Z
Learning: Applies to web/.storybook/{main,preview}.{ts,tsx} : Storybook 10: use `defineMain` from `storybook/react-vite/node` in main.ts and `definePreview` from `storybook/react-vite` in preview.tsx (must still include explicit `framework` field).
Applied to files:
web/src/components/ui/progress-gauge.stories.tsx
🔇 Additional comments (5)
web/src/components/ui/progress-gauge.tsx (1)
52-92: Linear variant implementation looks solid.Shared clamping/color logic is reused correctly, ARIA meter attributes are complete, and styling stays within semantic design tokens.
docs/design/brand-and-ux.md (1)
204-204: Design inventory update is aligned with the component API.The
variant?prop and default'circular'behavior are documented clearly.web/src/__tests__/components/ui/progress-gauge.test.tsx (1)
117-248: Excellent linear-variant test coverage.This suite meaningfully validates DOM structure, accessibility, clamping semantics, token class mapping, and edge-case handling.
web/src/components/ui/progress-gauge.stories.tsx (1)
59-101: Story coverage for the new variant is comprehensive.The added linear and side-by-side stories are useful for regression checks and visual QA.
CLAUDE.md (1)
251-251: CLAUDE.md component inventory update is accurate.The
ProgressGaugedescription now correctly reflects thevariantbehavior and defaults.
- Extract duplicated percentage span to const in linear variant (DRY) - Refactor color tests to it.each with boundary values (24/25, 49/50, 74/75) - Add negative max test verifying Math.max(max, 1) guard - Use describe.each for shared cross-variant tests (clamping, NaN, edge cases) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
🤖 I have created a release *beep* *boop* --- ## [0.5.1](v0.5.0...v0.5.1) (2026-03-30) ### Features * add linear variant to ProgressGauge component ([#927](#927)) ([89bf8d0](89bf8d0)) * frontend security hardening -- ESLint XSS ban + MotionConfig CSP nonce ([#926](#926)) ([6592ed0](6592ed0)) * set up MSW for Storybook API mocking ([#930](#930)) ([214078c](214078c)) ### Refactoring * **web:** replace Sidebar tablet overlay with shared Drawer component ([#928](#928)) ([ad5451d](ad5451d)) --- This PR was generated with [Release Please](https://github.com/googleapis/release-please). See [documentation](https://github.com/googleapis/release-please#release-please).
Summary
variant: 'circular' | 'linear'prop toProgressGauge(default:'circular', backward compatible)getHealthColorcolor logic, safe-value clamping, androle="meter"aria attributesdocs/design/brand-and-ux.mdcomponent inventory to reflect the new variantTest plan
npm --prefix web run storybookand check UI/ProgressGauge storiesReview coverage
Pre-reviewed by 5 agents (docs-consistency, frontend-reviewer, test-quality, issue-resolution-verifier, security-reviewer). 8 findings addressed, 0 skipped. Security: clean.
Closes #836