Skip to content

feat: add Progress component#692

Open
rohanchkrabrty wants to merge 1 commit intomainfrom
feat-progress
Open

feat: add Progress component#692
rohanchkrabrty wants to merge 1 commit intomainfrom
feat-progress

Conversation

@rohanchkrabrty
Copy link
Contributor

@rohanchkrabrty rohanchkrabrty commented Mar 11, 2026

Summary

  • Add Progress component with linear and circular variants, based on Base UI Progress primitive
  • Composable API: Progress.Root, Progress.Track, Progress.Label, Progress.Value
  • Circular variant uses SVG with viewBox scaling — size via CSS width/height, stroke via --rs-progress-track-size
  • Own progress.module.css with --rs-progress-* prefixed CSS variables
  • Includes tests, docs with playground, animated demo, and customization examples

Summary by CodeRabbit

  • New Features

    • Added a new Progress component with linear and circular variants
    • Progress component supports labels, custom values, and animated states
    • Full accessibility support with ARIA attributes (progressbar role, valuenow/min/max)
    • Customizable styling options for track heights and styles
  • Documentation

    • Complete component documentation with anatomy, API reference, and examples
    • Interactive playground with configurable controls
  • Tests

    • Comprehensive test suite covering rendering, variants, and accessibility

@vercel
Copy link

vercel bot commented Mar 11, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
apsara Ready Ready Preview, Comment Mar 11, 2026 6:20am

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Mar 11, 2026

📝 Walkthrough

Walkthrough

This PR introduces a new Progress component to the Raystack UI library with full implementation, documentation, and test coverage. It includes component implementation using a compound component pattern with context API, CSS styling for linear and circular variants, comprehensive prop type definitions, an interactive documentation page with multiple demo examples, and a test suite covering various component states and accessibility features.

Changes

Cohort / File(s) Summary
Progress Component Implementation
packages/raystack/components/progress/progress.tsx, progress-root.tsx, progress-misc.tsx, progress-track.tsx, index.tsx
Implements compound Progress component with ProgressRoot providing context for variant and computed percentage, ProgressLabel/Value as styled wrappers, and ProgressTrack with conditional SVG rendering for circular variant. Sets up barrel export and sub-component composition pattern.
Progress Styling
packages/raystack/components/progress/progress.module.css
Defines CSS module styles for linear and circular variants, including SVG circle rendering with computed radius/circumference variables, stroke-dashoffset animation for progress updates, and layout positioning for labels and values.
Progress Testing
packages/raystack/components/progress/__tests__/progress.test.tsx
Comprehensive test suite covering rendering, ref forwarding, className application, variant rendering (linear/circular), sub-components, indeterminate state, and accessibility attributes (role, aria-valuenow, aria-valuemin, aria-valuemax).
Progress Documentation & Props
apps/www/src/content/docs/components/progress/demo.ts, index.mdx, props.ts
Adds documentation page with anatomy section, API reference, multiple interactive demos (playground, variant, customization, animated, with labels), and type definitions for ProgressProps, ProgressLabelProps, ProgressValueProps, and ProgressTrackProps.
Library Exports
packages/raystack/index.tsx
Adds Progress to public library exports alongside existing components.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Suggested labels

Do not merge

Suggested reviewers

  • rsbh
  • paanSinghCoder
  • rohilsurana

Poem

🐰 A Progress component hops into view,
With circular SVG and linear paths too,
Context flows downward, styles dance with care,
Linear or circular, variants to share! ✨

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'feat: add Progress component' directly and clearly describes the primary change in the pull request, which is the addition of a new Progress component with multiple variants and subcomponents.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat-progress

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/raystack/components/progress/progress-root.tsx`:
- Around line 47-71: Normalize and validate inputs before computing percentage:
clamp value between min and max, ensure the denominator (max - min) is positive
(fallback to 1 if max <= min) to avoid division by zero/negative, compute
percentage = ((clampedValue - min) * 100) / safeDenominator, then clamp the
resulting percentage to 0–100 and use that bounded percentage when setting
ProgressContext (value/percentage) and the CSS variable sent into
ProgressPrimitive.Root; update the logic around percentage calculation in the
component that provides ProgressContext and renders ProgressPrimitive.Root (the
percentage/value handling in this component).

In `@packages/raystack/components/progress/progress-track.tsx`:
- Around line 15-31: The circular branch of ProgressPrimitive.Track is accepting
full Track props but then hard-codes render and injects children into an SVG,
which lets consumers pass a non-SVG `render` or regular HTML children that will
be ignored or produce invalid SVG; fix by narrowing the props passed into
ProgressPrimitive.Track when variant === 'circular' so `render` and non-SVG
`children` are not accepted (e.g., explicitly pick/omit keys: remove `render`
and `children` from the spread into ProgressPrimitive.Track), or else move any
non-SVG `children` outside the <svg> host and only pass SVG-safe props (or cast
to an appropriate SVG prop type) to ProgressPrimitive.Track/Indicator; update
the code around ProgressPrimitive.Track and ProgressPrimitive.Indicator usage to
reflect the narrowed prop set.

In `@packages/raystack/components/progress/progress.module.css`:
- Around line 55-63: The stylelint errors are caused by inconsistent declaration
spacing and multiline calc() formatting in .circularSvg; normalize by making
each custom property declaration use a single space after the colon and format
the calc() expressions as single-line with spaces around operators (e.g.,
--rs-progress-radius: calc((72px - var(--rs-progress-track-size) * 2) / 2); and
--rs-progress-circumference: calc(2 * 3.14159265 * var(--rs-progress-radius));),
and ensure the same spacing convention is applied to the other calc()
declarations referenced elsewhere (the similar declarations around lines 81–84).

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 8b9d309e-ecaf-486e-b178-7ec52c543ab0

📥 Commits

Reviewing files that changed from the base of the PR and between 14de0ba and ccb15a0.

📒 Files selected for processing (11)
  • apps/www/src/content/docs/components/progress/demo.ts
  • apps/www/src/content/docs/components/progress/index.mdx
  • apps/www/src/content/docs/components/progress/props.ts
  • packages/raystack/components/progress/__tests__/progress.test.tsx
  • packages/raystack/components/progress/index.tsx
  • packages/raystack/components/progress/progress-misc.tsx
  • packages/raystack/components/progress/progress-root.tsx
  • packages/raystack/components/progress/progress-track.tsx
  • packages/raystack/components/progress/progress.module.css
  • packages/raystack/components/progress/progress.tsx
  • packages/raystack/index.tsx

Comment on lines +47 to +71
value = 0,
min = 0,
max = 100,
...props
},
ref
) => {
const percentage = value === null ? 0 : ((value - min) * 100) / (max - min);

return (
<ProgressContext.Provider
value={{ variant: variant ?? 'linear', value, percentage }}
>
<ProgressPrimitive.Root
ref={ref}
className={progress({ variant, className })}
style={
{
...style,
'--rs-progress-percentage': percentage
} as React.CSSProperties
}
value={value}
min={min}
max={max}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Clamp and validate the range before computing percentage.

This derived value is fed straight into --rs-progress-percentage. For determinate progress, max <= min produces invalid math, and out-of-range values can drive the indicator below 0% or above 100%.

🛡️ One safe way to normalize the inputs
-    const percentage = value === null ? 0 : ((value - min) * 100) / (max - min);
+    if (value !== null && max <= min) {
+      throw new Error('Progress requires max to be greater than min');
+    }
+
+    const normalizedValue =
+      value === null ? null : Math.min(Math.max(value, min), max);
+    const percentage =
+      normalizedValue === null ? 0 : ((normalizedValue - min) * 100) / (max - min);
@@
-        value={{ variant: variant ?? 'linear', value, percentage }}
+        value={{
+          variant: variant ?? 'linear',
+          value: normalizedValue,
+          percentage
+        }}
@@
-          value={value}
+          value={normalizedValue}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
value = 0,
min = 0,
max = 100,
...props
},
ref
) => {
const percentage = value === null ? 0 : ((value - min) * 100) / (max - min);
return (
<ProgressContext.Provider
value={{ variant: variant ?? 'linear', value, percentage }}
>
<ProgressPrimitive.Root
ref={ref}
className={progress({ variant, className })}
style={
{
...style,
'--rs-progress-percentage': percentage
} as React.CSSProperties
}
value={value}
min={min}
max={max}
value = 0,
min = 0,
max = 100,
...props
},
ref
) => {
if (value !== null && max <= min) {
throw new Error('Progress requires max to be greater than min');
}
const normalizedValue =
value === null ? null : Math.min(Math.max(value, min), max);
const percentage =
normalizedValue === null ? 0 : ((normalizedValue - min) * 100) / (max - min);
return (
<ProgressContext.Provider
value={{
variant: variant ?? 'linear',
value: normalizedValue,
percentage
}}
>
<ProgressPrimitive.Root
ref={ref}
className={progress({ variant, className })}
style={
{
...style,
'--rs-progress-percentage': percentage
} as React.CSSProperties
}
value={normalizedValue}
min={min}
max={max}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/raystack/components/progress/progress-root.tsx` around lines 47 -
71, Normalize and validate inputs before computing percentage: clamp value
between min and max, ensure the denominator (max - min) is positive (fallback to
1 if max <= min) to avoid division by zero/negative, compute percentage =
((clampedValue - min) * 100) / safeDenominator, then clamp the resulting
percentage to 0–100 and use that bounded percentage when setting ProgressContext
(value/percentage) and the CSS variable sent into ProgressPrimitive.Root; update
the logic around percentage calculation in the component that provides
ProgressContext and renders ProgressPrimitive.Root (the percentage/value
handling in this component).

Comment on lines +15 to +31
if (variant === 'circular') {
return (
<ProgressPrimitive.Track
ref={ref}
className={cx(styles.circularSvg, className)}
{...props}
render={({ children: trackChildren, ...trackProps }) => (
<svg viewBox='0 0 72 72' {...trackProps}>
<circle className={styles.circularTrackCircle} />
{trackChildren}
</svg>
)}
>
<ProgressPrimitive.Indicator
render={() => <circle className={styles.circularIndicatorCircle} />}
/>
{children}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Don't expose linear-only Track behavior in circular mode.

This branch still accepts ProgressPrimitive.Track.Props, but it hard-codes render and moves children into an <svg>. A consumer can pass a custom render or regular HTML child that works in linear mode and is silently ignored / becomes invalid SVG content here. Please either narrow the wrapper props for the circular path or keep non-SVG children outside the SVG host.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/raystack/components/progress/progress-track.tsx` around lines 15 -
31, The circular branch of ProgressPrimitive.Track is accepting full Track props
but then hard-codes render and injects children into an SVG, which lets
consumers pass a non-SVG `render` or regular HTML children that will be ignored
or produce invalid SVG; fix by narrowing the props passed into
ProgressPrimitive.Track when variant === 'circular' so `render` and non-SVG
`children` are not accepted (e.g., explicitly pick/omit keys: remove `render`
and `children` from the spread into ProgressPrimitive.Track), or else move any
non-SVG `children` outside the <svg> host and only pass SVG-safe props (or cast
to an appropriate SVG prop type) to ProgressPrimitive.Track/Indicator; update
the code around ProgressPrimitive.Track and ProgressPrimitive.Indicator usage to
reflect the narrowed prop set.

Comment on lines +55 to +63
.circularSvg {
--rs-progress-track-size: 4px;
--rs-progress-radius: calc((72px - var(--rs-progress-track-size) * 2) / 2);
--rs-progress-circumference: calc(2 * 3.14159265 * var(--rs-progress-radius));
width: 72px;
height: 72px;
aspect-ratio: 1;
transform: rotate(-90deg);
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Fix the current Stylelint failures.

This rule set already trips the linter, so the PR will stay noisy until the declaration spacing and multiline calc() formatting are normalized.

🧹 Minimal lint-only cleanup
 .circularSvg {
   --rs-progress-track-size: 4px;
   --rs-progress-radius: calc((72px - var(--rs-progress-track-size) * 2) / 2);
   --rs-progress-circumference: calc(2 * 3.14159265 * var(--rs-progress-radius));
+
   width: 72px;
   height: 72px;
   aspect-ratio: 1;
   transform: rotate(-90deg);
 }
@@
   stroke: var(--rs-color-background-accent-emphasis);
   stroke-dasharray: var(--rs-progress-circumference);
-  stroke-dashoffset: calc(
-    var(--rs-progress-circumference) *
-    (1 - var(--rs-progress-percentage, 0) / 100)
-  );
+  stroke-dashoffset: calc(
+    var(--rs-progress-circumference) * (1 - var(--rs-progress-percentage, 0) / 100)
+  );
   stroke-linecap: butt;
   transition: stroke-dashoffset 500ms;
 }

Also applies to: 81-84

🧰 Tools
🪛 Stylelint (17.4.0)

[error] 59-59: Expected empty line before declaration (declaration-empty-line-before)

(declaration-empty-line-before)

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/raystack/components/progress/progress.module.css` around lines 55 -
63, The stylelint errors are caused by inconsistent declaration spacing and
multiline calc() formatting in .circularSvg; normalize by making each custom
property declaration use a single space after the colon and format the calc()
expressions as single-line with spaces around operators (e.g.,
--rs-progress-radius: calc((72px - var(--rs-progress-track-size) * 2) / 2); and
--rs-progress-circumference: calc(2 * 3.14159265 * var(--rs-progress-radius));),
and ensure the same spacing convention is applied to the other calc()
declarations referenced elsewhere (the similar declarations around lines 81–84).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant