Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
4d36fe8
[progress-bar] redesign
ilyabrower Aug 19, 2026
f608a50
[progress-bar] update test and snapshots
Valeria-Zimnitskaya Aug 20, 2026
8093ca5
[progress-bar] fixed value themes and animation for 100%
ilyabrower Aug 24, 2026
9fd7daa
[progress-bar] fixed custom themes for value
ilyabrower Aug 25, 2026
297f148
[progress-bar] added deprecate for theme in the Value
ilyabrower Aug 25, 2026
a1bbbc9
[progress-bar] update tests
Valeria-Zimnitskaya Aug 25, 2026
1adb6ab
[progress-bar] update tests
Valeria-Zimnitskaya Aug 25, 2026
6d133e2
[progress-bar] update stories and tests
Valeria-Zimnitskaya Aug 25, 2026
7bb0b63
[progress-bar] update stories and tests
Valeria-Zimnitskaya Aug 25, 2026
205b5b3
[chore] Merge branch 'UIK-5351/add-more-variables-to-theme' into UIK-…
sheila-semrush Aug 26, 2026
9e6f44f
[chore] Merge branch 'UIK-5351/add-more-variables-to-theme' into UIK-…
sheila-semrush Aug 26, 2026
54909b1
[docs] removed deprecated example, updated theme illustrations, remov…
sheila-semrush Aug 26, 2026
47f2068
Merge branch 'UIK-5351/add-more-variables-to-theme' into UIK-5668/pro…
Valeria-Zimnitskaya Aug 27, 2026
cbb72f2
[stories] remove custom ProgressBar story
sheila-semrush Aug 27, 2026
6f87db4
[progress-bar] added animation for bg without value
ilyabrower Aug 27, 2026
67cb493
[progress-bar] set value for empty bar pattern gradient
ilyabrower Aug 28, 2026
2b1b016
[progress-bar] added transparent background for empty progress
sheila-semrush Aug 28, 2026
221cd07
[progress-bar] update snapshots
Valeria-Zimnitskaya Aug 28, 2026
99ab23f
[progress-bar] update test
Valeria-Zimnitskaya Aug 28, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions playground/entries/ProgressBar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,8 @@ const entry: PlaygroundEntry<NSProgressBar.Props> = {
},
theme: {
type: 'select',
value: 'invert',
options: ['invert', 'dark'],
value: 'default',
options: ['default', 'brand', 'invert'],
displayName: 'Theme',
},
duration: {
Expand Down
4 changes: 2 additions & 2 deletions semcore/progress-bar/__tests__/progress-bar.axe-test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@ test.describe(`@progress-bar ${TAG.ACCESSIBILITY}`, () => {
expect(violations).toEqual([]);
});

test('Customizing the bar', async ({ page }) => {
await loadPage(page, 'stories/components/progress-bar/docs/examples/customizing_the_bar.tsx', 'en');
test('All themes on light and dark backgrounds', async ({ page }) => {
await loadPage(page, 'stories/components/progress-bar/advanced/examples/all_themes.tsx', 'en');

const violations = await getAccessibilityViolations({ page });
expect(violations).toEqual([]);
Expand Down
74 changes: 47 additions & 27 deletions semcore/progress-bar/__tests__/progress-bar.browser-test.tsx
Original file line number Diff line number Diff line change
@@ -1,53 +1,73 @@
import { expect, test } from '@semcore/testing-utils/playwright';
import type { Page } from '@semcore/testing-utils/playwright';
import { loadPage } from '@semcore/testing-utils/shared/helpers';
import { TAG } from '@semcore/testing-utils/shared/tags';

const BAR_EXAMPLE = 'stories/components/progress-bar/tests/examples/customizing_the_bar_with_background.tsx';

/** The example is mounted into `#root`, so snapshots are scoped to it instead of the whole viewport. */
const stand = (page: Page) => page.locator('#root');

/**
* `default`, `invert` and `brand` are the built-in themes; any other value falls
* through to the custom-theme path, so one custom token is covered alongside them.
*/
const BAR_THEMES = ['default', 'invert', 'brand', 'violet-100'];
const SIZES = ['s', 'm', 'l'] as const;
/** One boundary value per size, so each snapshot also covers a distinct edge case. */
const BOUNDARY_VALUE_BY_SIZE = { s: 1, m: 99, l: 110 } as const;

const barMatrix = BAR_THEMES.flatMap((theme) =>
SIZES.map((size) => ({ theme, size, value: BOUNDARY_VALUE_BY_SIZE[size] })),
);

/**
* `theme` on ProgressBar.Value is deprecated ("Use only predefined themes") and accepts
* a color token only, so it gets a single regression guard instead of a matrix — just
* enough to catch the deprecated path breaking before it is removed.
*/
const deprecatedValueTheme = {
theme: 'default',
size: 'm',
value: 99,
valueTheme: 'violet-100',
} as const;

/* =====================================================
@visual
Visual states, hover and focus styles, paddings, margins, and snapshots.
===================================================== */
test.describe(`${TAG.VISUAL}`, () => {
const variablesBarCustomization = [
{ theme: 'invert', size: 's', value: 0 },
{ theme: 'dark', size: 'm', value: 110 },
{ theme: 'violet-100', size: 'l', value: 50 },
];
variablesBarCustomization.forEach((item) => {
// Every case renders the theme on both light and dark backgrounds, across
// value=<case>, value=0, value=100 and the no value state.
barMatrix.forEach((item) => {
test(`Verify progress bar with background customization with value=${item.value}, size=${item.size}, theme=${item.theme}`, {
tag: [TAG.PRIORITY_HIGH, '@progress-bar'],
}, async ({ page }) => {
await loadPage(page, 'stories/components/progress-bar/tests/examples/customizing_the_bar_with_background.tsx', 'en', item);
await loadPage(page, BAR_EXAMPLE, 'en', item);

await test.step('Verify progress bar visual with keyboard focus', async () => {
await page.keyboard.press('Tab');
await expect(page).toHaveScreenshot();
await expect(stand(page)).toHaveScreenshot();
});
});
});

const variablesValueCustomization = [
{ value: 0, theme: 'inviolet-500', size: 's' },
{ value: 110, theme: 'violet-100', size: 'm' },
{ value: 50, theme: 'violet-100', size: 'l' },
];
variablesValueCustomization.forEach((item) => {
test(`Verify progress bar value customization with value=${item.value}, size=${item.size}, theme=${item.theme}`, {
tag: [TAG.PRIORITY_HIGH, '@progress-bar'],
}, async ({ page }) => {
await loadPage(page, 'stories/components/progress-bar/tests/examples/customizing_the_value.tsx', 'en', item);
test(`Verify progress bar value customization with value=${deprecatedValueTheme.value}, size=${deprecatedValueTheme.size}, valueTheme=${deprecatedValueTheme.valueTheme}`, {
tag: [TAG.PRIORITY_MEDIUM, '@progress-bar'],
}, async ({ page }) => {
await loadPage(page, BAR_EXAMPLE, 'en', deprecatedValueTheme);

await test.step('Verify progress bar value visual with keyboard focus', async () => {
await page.keyboard.press('Tab');
await expect(page).toHaveScreenshot();
});
await test.step('Verify progress bar value visual with keyboard focus', async () => {
await page.keyboard.press('Tab');
await expect(stand(page)).toHaveScreenshot();
});
});

test('Verify both value and progress bar customized', {
tag: [TAG.PRIORITY_MEDIUM, '@progress-bar'],
test('Verify all themes on light and dark backgrounds', {
tag: [TAG.PRIORITY_HIGH, '@progress-bar'],
}, async ({ page }) => {
await loadPage(page, 'stories/components/progress-bar/docs/examples/customizing_the_bar.tsx', 'en');
await page.keyboard.press('Tab');
await expect(page).toHaveScreenshot();
await loadPage(page, 'stories/components/progress-bar/advanced/examples/all_themes.tsx', 'en');
await expect(stand(page)).toHaveScreenshot();
});
});
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Diff not rendered.
Diff not rendered.
Diff not rendered.
Diff not rendered.
Diff not rendered.
Diff not rendered.
20 changes: 18 additions & 2 deletions semcore/progress-bar/src/ProgressBar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import style from './style/progress-bar.shadow.css';
function isCustomTheme(theme?: string) {
if (!theme) return false;

return !['dark', 'invert'].includes(theme);
return !['default', 'invert', 'brand'].includes(theme);
}

class ProgressBarRoot extends Component<
Expand All @@ -27,7 +27,7 @@ class ProgressBarRoot extends Component<
static defaultProps = () => ({
duration: 1000,
size: 'm',
theme: 'invert',
theme: 'default',
children: <ProgressBar.Value />,
} as const);

Expand Down Expand Up @@ -73,12 +73,28 @@ function Value(
const { styles, value, theme, duration, resolveColor } = props;
const width = `${value}%`;

const [stopAnimation, setStopAnimation] = React.useState(value === 100);

const handleTransitionEnd = React.useCallback(() => {
if (value === 100) {
setTimeout(() => {
setStopAnimation(true);
});
} else {
setTimeout(() => {
setStopAnimation(false);
});
}
}, [value]);

return sstyled(styles)(
<SValue
render={Box}
use:width={width}
use:duration={`${duration}ms`}
colorBg={resolveColor(theme)}
onTransitionEnd={handleTransitionEnd}
stopAnimation={stopAnimation}
/>,
);
}
Expand Down
11 changes: 7 additions & 4 deletions semcore/progress-bar/src/ProgressBar.type.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,9 @@ declare namespace NSProgressBar {
type Props = NSBox.Props & {
/**
* Progress bar theme
* @default invert
* @default default
*/
theme?: 'dark' | 'invert' | string;
theme?: 'default' | 'invert' | 'brand' | string;
/**
* Progress bar size
* @default m
Expand All @@ -23,7 +23,7 @@ declare namespace NSProgressBar {
type DefaultProps = {
duration: 1000;
size: 'm';
theme: 'invert';
theme: 'default';
children: React.ReactNode;
};

Expand All @@ -39,7 +39,10 @@ declare namespace NSProgressBar {
value?: number;
/** Animation diration in milliseconds for transitions */
duration?: number;
/** Color theme */
/**
* Color theme
* @deprecated. Use only predefined themes.
*/
theme?: string;
};

Expand Down
56 changes: 38 additions & 18 deletions semcore/progress-bar/src/style/progress-bar.shadow.css
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,13 @@ SProgressBar {
position: relative;
width: 100%;
overflow: hidden;
background-color: var(--intergalactic-progress-bar-bg, oklch(0.157 0.029 176 / 0.11));
}

SProgressBar[theme='dark'] {
background-color: var(--intergalactic-progress-bar-bg-invert, oklch(0.987 0.023 140 / 0.212));
}

SValue {
position: absolute;
top: 0;
left: 0;
background-color: var(--intergalactic-progress-bar-value-bg, oklch(1 0 0));
background-image: var(--intergalactic-progress-bar-value-gradient,
linear-gradient(-45deg, oklch(from oklch(0.22 0.01 140) l c h / 0.78) 25%, oklch(0.22 0.01 140) 0%, oklch(0.22 0.01 140) 50%, oklch(from oklch(0.22 0.01 140) l c h / 0.78) 0%, oklch(from oklch(0.22 0.01 140) l c h / 0.78) 75%, oklch(0.22 0.01 140) 0%));
overflow: hidden;

animation-name: loading;
Expand All @@ -26,9 +19,12 @@ SValue {
width: var(--width);
animation-duration: var(--duration);
transition-duration: var(--duration);
}

SValue[theme] {
&[stopAnimation] {
animation-name: none;
}
}
SProgressBar[theme] SValue[colorBg] {
background-color: var(--colorBg);
background-image: var(--colorBg);
}
Expand All @@ -54,12 +50,6 @@ SValue[size='l'] {
background-size: 16px 16px;
}



SProgressBar[theme='invert'] {
background-color: var(--intergalactic-progress-bar-bg, oklch(0.157 0.029 176 / 0.11));
}

@keyframes loading {
from {
background-position: 0 0;
Expand All @@ -71,13 +61,43 @@ SProgressBar[theme='invert'] {
}

SProgressBar[animation] {
background-color: var(--intergalactic-progress-bar-value-bg, oklch(1 0 0));
background-image: var(--intergalactic-progress-bar-pattern-gradient,
linear-gradient(-45deg, oklch(0 0 0 / 0.1) 25%, oklch(0.157 0.029 176 / 0.11) 0%, oklch(0.157 0.029 176 / 0.11) 50%, oklch(0 0 0 / 0.1) 0%, oklch(0 0 0 / 0.1) 75%, oklch(0.157 0.029 176 / 0.11) 0%));
linear-gradient(-45deg, oklch(from oklch(0.157 0.029 176 / 0.11) l c h / 0.18) 25%, oklch(0.157 0.029 176 / 0.11) 0%, oklch(0.157 0.029 176 / 0.11) 50%, oklch(from oklch(0.157 0.029 176 / 0.11) l c h / 0.18) 0%, oklch(from oklch(0.157 0.029 176 / 0.11) l c h / 0.18) 75%, oklch(0.157 0.029 176 / 0.11) 0%));
animation-name: loading;
animation-timing-function: linear;
animation-iteration-count: infinite;
animation-duration: var(--duration);

&:not([theme='invert']) {
background-color: transparent;
}
}

SProgressBar[theme='default'] {
background-color: var(--intergalactic-progress-bar-bg, oklch(0.157 0.029 176 / 0.11));

SValue {
background-image: var(--intergalactic-progress-bar-value-gradient,
linear-gradient(-45deg, oklch(from oklch(0.22 0.01 140) l c h / 0.78) 25%, oklch(0.22 0.01 140) 0%, oklch(0.22 0.01 140) 50%, oklch(from oklch(0.22 0.01 140) l c h / 0.78) 0%, oklch(from oklch(0.22 0.01 140) l c h / 0.78) 75%, oklch(0.22 0.01 140) 0%));
}
}

SProgressBar[theme='invert'] {
background-color: var(--intergalactic-progress-bar-bg-invert, oklch(0.987 0.023 140 / 0.212));

SValue {
background-image: var(--intergalactic-progress-bar-value-gradient-invert,
linear-gradient(-45deg, oklch(from oklch(0.74 0.17 303) l c h / 0.78) 25%, oklch(0.74 0.17 303) 0%, oklch(0.74 0.17 303) 50%, oklch(from oklch(0.74 0.17 303) l c h / 0.78) 0%, oklch(from oklch(0.74 0.17 303) l c h / 0.78) 75%, oklch(0.74 0.17 303) 0%));
}
}

SProgressBar[theme='brand'] {
background-color: var(--intergalactic-progress-bar-bg, oklch(0.157 0.029 176 / 0.11));

SValue {
background-image: var(--intergalactic-progress-bar-value-gradient-invert,
linear-gradient(-45deg, oklch(from oklch(0.74 0.17 303) l c h / 0.78) 25%, oklch(0.74 0.17 303) 0%, oklch(0.74 0.17 303) 50%, oklch(from oklch(0.74 0.17 303) l c h / 0.78) 0%, oklch(from oklch(0.74 0.17 303) l c h / 0.78) 75%, oklch(0.74 0.17 303) 0%));
}
}

SProgressBar[theme='custom'] {
Expand All @@ -94,4 +114,4 @@ SProgressBar[theme='custom'] {
SProgressBar[animation] {
animation-name: none;
}
}
}
35 changes: 29 additions & 6 deletions stories/components/progress-bar/advanced/examples/all_themes.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,29 @@ export default function AllThemes() {
<ProgressBar value={100} aria-label='Progress 100%' />
</Box>
</Flex>
<Flex gap={6} flexWrap alignItems='flex-start' direction='column'>
<Box w={240}>
<ProgressBar
theme='brand'
value={0}
aria-label='Progress 0%, brand theme on light background'
/>
</Box>
<Box w={240}>
<ProgressBar
theme='brand'
value={40}
aria-label='Progress 40%, brand theme on light background'
/>
</Box>
<Box w={240}>
<ProgressBar
theme='brand'
value={100}
aria-label='Progress 100%, brand theme on light background'
/>
</Box>
</Flex>
<Box
p={4}
style={{
Expand All @@ -26,23 +49,23 @@ export default function AllThemes() {
<Flex gap={6} flexWrap alignItems='flex-start' direction='column'>
<Box w={240}>
<ProgressBar
theme='dark'
theme='invert'
value={0}
aria-label='Progress 0%, dark theme on dark background'
aria-label='Progress 0%, invert theme on dark background'
/>
</Box>
<Box w={240}>
<ProgressBar
theme='dark'
theme='invert'
value={40}
aria-label='Progress 40%, dark theme on dark background'
aria-label='Progress 40%, invert theme on dark background'
/>
</Box>
<Box w={240}>
<ProgressBar
theme='dark'
theme='invert'
value={100}
aria-label='Progress 100%, dark theme on dark background'
aria-label='Progress 100%, invert theme on dark background'
/>
</Box>
</Flex>
Expand Down
Loading
Loading