Skip to content

feat(LinearProgressBar): add allowExceedingMax prop to support values…#3255

Open
talkor wants to merge 2 commits intomasterfrom
feat/progress-bar/allow-exceeding-max-label
Open

feat(LinearProgressBar): add allowExceedingMax prop to support values…#3255
talkor wants to merge 2 commits intomasterfrom
feat/progress-bar/allow-exceeding-max-label

Conversation

@talkor
Copy link
Member

@talkor talkor commented Feb 25, 2026

User description

https://monday.monday.com/boards/3532714909/pulses/11338817756


PR Type

Enhancement


Description

  • Add allowExceedingMax prop to LinearProgressBar for displaying values over 100%

  • Implement visual width capping at 100% to prevent UI overflow

  • Update aria-valuemax dynamically when value exceeds max

  • Add comprehensive tests and Storybook story for new feature


Diagram Walkthrough

flowchart LR
  A["allowExceedingMax prop"] --> B["calculatePercentage helper"]
  B --> C["valuePercentage calculation"]
  C --> D["visualWidthPercentage capped at 100%"]
  C --> E["ariaValueMax adjusted for accessibility"]
  D --> F["Bar component width"]
  E --> F
  A --> G["Tests and Storybook story"]
Loading

File Walkthrough

Relevant files
Enhancement
LinearProgressBarHelpers.ts
Add allowExceeding parameter to percentage calculationΒ  Β  Β 

packages/core/src/components/ProgressBars/LinearProgressBar/LinearProgressBarHelpers.ts

  • Add optional allowExceeding parameter to calculatePercentage function
  • Conditionally cap percentage at 100 based on allowExceeding flag
+2/-2Β  Β  Β 
Bar.tsx
Implement visual capping and aria accessibility updatesΒ  Β 

packages/core/src/components/ProgressBars/LinearProgressBar/Bar/Bar.tsx

  • Add allowExceedingMax prop to BarProps interface
  • Introduce visualWidthPercentage memo to cap bar width at 100%
  • Introduce ariaValueMax memo to dynamically set aria-valuemax based on
    actual percentage
  • Update aria-valuemax and width style to use new calculated values
  • Pass allowExceedingMax to calculatePercentage function
+24/-5Β  Β 
LinearProgressBar.tsx
Thread allowExceedingMax prop through component hierarchy

packages/core/src/components/ProgressBars/LinearProgressBar/LinearProgressBar.tsx

  • Add allowExceedingMax prop to LinearProgressBarProps interface
  • Pass allowExceedingMax to all Bar component instances (primary,
    secondary, and multi-bars)
  • Update dependency arrays to include allowExceedingMax in useMemo hooks
+12/-4Β  Β 
Tests
LinearProgressBar.test.jsx
Add comprehensive tests for exceeding max featureΒ  Β  Β  Β  Β  Β  Β  Β 

packages/core/src/components/ProgressBars/LinearProgressBar/tests/LinearProgressBar.test.jsx

  • Add test suite "exceeding max value" with four test cases
  • Test default capping behavior at 100% when value exceeds max
  • Test actual percentage display when allowExceedingMax is true
  • Test secondary value exceeding max with allowExceedingMax
  • Test visual bar width capped at 100% despite allowExceedingMax
+55/-0Β  Β 
Documentation
LinearProgressBar.stories.tsx
Add Storybook story for exceeding max featureΒ  Β  Β  Β  Β  Β  Β  Β  Β  Β  Β  Β 

packages/docs/src/pages/components/ProgressBars/LinearProgressBar.stories.tsx

  • Add new Storybook story "ExceedingMaxValue" demonstrating the feature
  • Show side-by-side comparison of standard behavior vs allowExceedingMax
  • Include descriptive labels and value explanations for clarity
+44/-0Β  Β 

@qodo-free-for-open-source-projects
Copy link
Contributor

qodo-free-for-open-source-projects bot commented Feb 25, 2026

PR Reviewer Guide πŸ”

(Review updated until commit 36145f8)

Here are some key observations to aid the review process:

⏱️ Estimated effort to review: 2 πŸ”΅πŸ”΅βšͺβšͺβšͺ
πŸ§ͺΒ PR contains tests
πŸ”’Β No security concerns identified
⚑ Recommended focus areas for review

Accessibility

Validate that setting aria-valuemax to a computed percentage (potentially > 100 and possibly fractional) is the intended accessibility behavior for this component, and that consumers/screen readers interpret aria-valuenow/aria-valuemax consistently when values exceed the traditional 0–100 range.

const ariaValueMax = useMemo(() => {
  // When allowExceedingMax is true and value exceeds max,
  // set aria-valuemax to the actual value percentage to maintain consistency
  if (allowExceedingMax && valuePercentage > 100) {
    return Math.max(valuePercentage, 100);
  }
  return 100;
}, [allowExceedingMax, valuePercentage]);

if (!value) return null;

return (
  <div
    role="progressbar"
    aria-label={barLabelName}
    aria-valuenow={valuePercentage}
    aria-valuemin={0}
    aria-valuemax={ariaValueMax}
    className={classNames}
    style={{
      width: `${visualWidthPercentage}%`,
      ...(color && { backgroundColor: color })
Edge Cases

calculatePercentage can yield Infinity/NaN when max equals min (division by zero). Consider guarding against this to avoid propagating invalid widths/ARIA values, especially now that values may exceed 100%.

export const calculatePercentage = (value: number, min: number, max: number, allowExceeding?: boolean) => {
  const valuePercentage = (Number(value - min) / Number(max - min)) * 100;
  return !allowExceeding && valuePercentage > 100 ? 100 : valuePercentage;
};
Test Robustness

The new tests rely on checking progressBarElement.style.width after rerenders; ensure the assertion matches how width is applied (inline style vs computed style) across the test environment, and consider also asserting aria-valuemax behavior when allowExceedingMax is enabled.

describe("exceeding max value", () => {
  it("should cap percentage at 100% by default when value exceeds max", () => {
    const { rerender } = component;
    act(() => {
      component = rerender(<LinearProgressBar value={120} max={100} id="test" indicateProgress={true} />);
    });

    expect(screen.getByText("100%")).toBeTruthy();
    expect(screen.queryByText("120%")).toBeNull();
  });

  it("should show actual percentage when allowExceedingMax is true", () => {
    const { rerender } = component;
    act(() => {
      component = rerender(
        <LinearProgressBar value={120} max={100} id="test" indicateProgress={true} allowExceedingMax={true} />
      );
    });

    expect(screen.getByText("120%")).toBeTruthy();
    expect(screen.queryByText("100%")).toBeNull();
  });

  it("should handle secondary value exceeding max when allowExceedingMax is true", () => {
    const { rerender } = component;
    act(() => {
      component = rerender(
        <LinearProgressBar
          value={150}
          valueSecondary={180}
          max={100}
          id="test"
          indicateProgress={true}
          allowExceedingMax={true}
        />
      );
    });

    expect(screen.getByText("150%")).toBeTruthy();

    expect(screen.queryAllByRole("progressbar").length).toBe(2);
  });

  it("should cap visual bar width at 100% even when allowExceedingMax is true", () => {
    const { rerender } = component;
    act(() => {
      component = rerender(<LinearProgressBar value={120} max={100} id="test" allowExceedingMax={true} />);
    });

    const progressBarElement = screen.getByRole("progressbar");

    expect(progressBarElement.style.width).toBe("100%");
  });
});

@github-actions
Copy link
Contributor

github-actions bot commented Feb 25, 2026

πŸ“¦ Bundle Size Analysis

βœ… No bundle size changes detected.

Unchanged Components
Component Base PR Diff
@vibe/button 17.74KB 17.79KB +57B πŸ”Ί
@vibe/clickable 6.07KB 6.05KB -21B 🟒
@vibe/dialog 53.85KB 53.8KB -48B 🟒
@vibe/icon-button 68.09KB 68.07KB -18B 🟒
@vibe/icon 13.01KB 13.01KB +3B πŸ”Ί
@vibe/layer 2.96KB 2.96KB 0B βž–
@vibe/layout 10.56KB 10.54KB -24B 🟒
@vibe/loader 5.8KB 5.82KB +16B πŸ”Ί
@vibe/tooltip 62.98KB 62.88KB -107B 🟒
@vibe/typography 65.4KB 65.41KB +2B πŸ”Ί
Accordion 6.35KB 6.34KB -16B 🟒
AccordionItem 68.13KB 68.16KB +30B πŸ”Ί
AlertBanner 72.93KB 72.9KB -38B 🟒
AlertBannerButton 19.23KB 19.25KB +26B πŸ”Ί
AlertBannerLink 15.56KB 15.56KB -1B 🟒
AlertBannerText 65.53KB 65.48KB -44B 🟒
AttentionBox 74.53KB 74.39KB -138B 🟒
AttentionBoxLink 15.41KB 15.37KB -44B 🟒
Avatar 68.36KB 68.3KB -61B 🟒
AvatarGroup 96.04KB 95.98KB -68B 🟒
Badge 43.53KB 43.56KB +26B πŸ”Ί
BreadcrumbItem 66.26KB 66.12KB -145B 🟒
BreadcrumbMenu 70.3KB 70.34KB +36B πŸ”Ί
BreadcrumbMenuItem 79.45KB 79.36KB -99B 🟒
BreadcrumbsBar 5.81KB 5.78KB -31B 🟒
ButtonGroup 70.25KB 70.28KB +28B πŸ”Ί
Checkbox 68.43KB 68.43KB -1B 🟒
Chips 77.18KB 77.11KB -72B 🟒
ColorPicker 76.35KB 76.42KB +74B πŸ”Ί
ColorPickerContent 75.57KB 75.52KB -43B 🟒
Combobox 86.37KB 86.28KB -87B 🟒
Counter 42.49KB 42.42KB -72B 🟒
DatePicker 134.59KB 134.56KB -32B 🟒
Divider 5.56KB 5.58KB +22B πŸ”Ί
Dropdown 125.94KB 125.85KB -88B 🟒
menu 59.95KB 59.94KB -13B 🟒
option 93.15KB 93.2KB +54B πŸ”Ί
singleValue 93.09KB 93.01KB -79B 🟒
EditableHeading 68.35KB 68.31KB -38B 🟒
EditableText 68.26KB 68.22KB -36B 🟒
EmptyState 72.75KB 72.64KB -108B 🟒
ExpandCollapse 68KB 68KB +3B πŸ”Ί
FormattedNumber 5.91KB 5.94KB +33B πŸ”Ί
GridKeyboardNavigationContext 4.66KB 4.65KB -5B 🟒
HiddenText 5.45KB 5.45KB -4B 🟒
Info 74.34KB 74.32KB -25B 🟒
Label 70.43KB 70.38KB -54B 🟒
LegacyModal 76.89KB 76.86KB -37B 🟒
LegacyModalContent 66.82KB 66.84KB +23B πŸ”Ί
LegacyModalFooter 3.45KB 3.45KB 0B βž–
LegacyModalFooterButtons 20.68KB 20.66KB -22B 🟒
LegacyModalHeader 72.97KB 72.96KB -8B 🟒
Link 15.23KB 15.18KB -50B 🟒
List 74.99KB 74.97KB -21B 🟒
ListItem 67.34KB 67.29KB -61B 🟒
ListItemAvatar 68.57KB 68.53KB -37B 🟒
ListItemIcon 14.21KB 14.23KB +19B πŸ”Ί
ListTitle 66.75KB 66.71KB -43B 🟒
Menu 8.71KB 8.72KB +10B πŸ”Ί
MenuDivider 5.65KB 5.65KB +5B πŸ”Ί
MenuGridItem 7.24KB 7.21KB -30B 🟒
MenuItem 79.22KB 79.27KB +53B πŸ”Ί
MenuItemButton 72.2KB 72.22KB +15B πŸ”Ί
MenuTitle 67.21KB 67.15KB -61B 🟒
MenuButton 67.8KB 67.82KB +24B πŸ”Ί
Modal 111.93KB 111.88KB -60B 🟒
ModalContent 4.77KB 4.77KB +2B πŸ”Ί
ModalHeader 67.64KB 67.51KB -125B 🟒
ModalMedia 7.77KB 7.76KB -5B 🟒
ModalFooter 69.48KB 69.58KB +109B πŸ”Ί
ModalFooterWizard 70.48KB 70.5KB +26B πŸ”Ί
ModalBasicLayout 9.25KB 9.25KB -3B 🟒
ModalMediaLayout 8.32KB 8.33KB +7B πŸ”Ί
ModalSideBySideLayout 6.36KB 6.38KB +26B πŸ”Ί
MultiStepIndicator 53.31KB 53.35KB +45B πŸ”Ί
NumberField 74.87KB 74.91KB +45B πŸ”Ί
LinearProgressBar 7.49KB 7.57KB +83B πŸ”Ί
RadioButton 67.59KB 67.59KB -5B 🟒
Search 72.45KB 72.41KB -37B 🟒
Skeleton 6.18KB 6.22KB +41B πŸ”Ί
Slider 75.97KB 75.93KB -40B 🟒
SplitButton 68.83KB 68.86KB +27B πŸ”Ί
SplitButtonMenu 8.89KB 8.88KB -7B 🟒
Steps 73.52KB 73.56KB +45B πŸ”Ί
Table 7.33KB 7.32KB -10B 🟒
TableBody 68.67KB 68.66KB -2B 🟒
TableCell 66.9KB 66.98KB +82B πŸ”Ί
TableContainer 5.36KB 5.37KB +14B πŸ”Ί
TableHeader 5.69KB 5.71KB +17B πŸ”Ί
TableHeaderCell 74.24KB 74.16KB -89B 🟒
TableRow 5.63KB 5.63KB +3B πŸ”Ί
TableRowMenu 70.62KB 70.61KB -18B 🟒
TableVirtualizedBody 73.42KB 73.29KB -128B 🟒
Tab 65.54KB 65.55KB +12B πŸ”Ί
TabList 8.97KB 8.94KB -28B 🟒
TabPanel 5.34KB 5.34KB -3B 🟒
TabPanels 5.97KB 5.96KB -12B 🟒
TabsContext 5.55KB 5.56KB +18B πŸ”Ί
TextArea 68.08KB 68.05KB -33B 🟒
TextField 71.36KB 71.38KB +21B πŸ”Ί
TextWithHighlight 65.86KB 65.93KB +69B πŸ”Ί
ThemeProvider 4.68KB 4.69KB +7B πŸ”Ί
Tipseen 73.16KB 73.23KB +67B πŸ”Ί
TipseenContent 73.65KB 73.66KB +11B πŸ”Ί
TipseenImage 73.49KB 73.5KB +2B πŸ”Ί
TipseenMedia 73.39KB 73.46KB +81B πŸ”Ί
TipseenWizard 76KB 75.99KB -14B 🟒
Toast 76.19KB 76.26KB +71B πŸ”Ί
ToastButton 19.07KB 19.04KB -32B 🟒
ToastLink 15.37KB 15.35KB -26B 🟒
Toggle 68.3KB 68.32KB +28B πŸ”Ί
TransitionView 37.69KB 37.73KB +41B πŸ”Ί
VirtualizedGrid 12.63KB 12.67KB +39B πŸ”Ί
VirtualizedList 12.42KB 12.4KB -14B 🟒
AttentionBox (Next) 76.41KB 76.45KB +38B πŸ”Ί
DatePicker (Next) 114.46KB 114.4KB -66B 🟒
Dialog (Next) 52.71KB 52.7KB -9B 🟒
Dropdown (Next) 97.57KB 97.45KB -122B 🟒
List (Next) 8.2KB 8.2KB +3B πŸ”Ί
ListItem (Next) 71.66KB 71.64KB -23B 🟒
ListTitle (Next) 67.08KB 67.05KB -30B 🟒

πŸ“Š Summary:

  • Total Base Size: 5.88MB
  • Total PR Size: 5.88MB
  • Total Difference: 1.3KB

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant