Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
373f7f0
[notice] replaced old spacing-numbers variable to spacing-content-pad…
ilyabrower Aug 4, 2026
ae15809
[notice] redesign
ilyabrower Aug 5, 2026
22a33fb
[chore] Merge branch 'UIK-5351/add-more-variables-to-theme' into UIK-…
ilyabrower Aug 7, 2026
8df4b5d
[notice] fixed build
ilyabrower Aug 7, 2026
dbaa20b
[chore] Merge remote-tracking branch 'origin/UIK-5351/add-more-variab…
ilyabrower Aug 7, 2026
afa18d9
[chore] Merge remote-tracking branch 'origin/UIK-5351/add-more-variab…
ilyabrower Aug 7, 2026
19d41a9
[notice] fixed build
ilyabrower Aug 7, 2026
7e77a20
[chore] Merge remote-tracking branch 'origin/UIK-5351/add-more-variab…
ilyabrower Aug 10, 2026
200a2f4
Merge branch 'UIK-5351/add-more-variables-to-theme' into UIK-5641/not…
j-mnizhek Aug 10, 2026
f3bc11c
Merge branch 'UIK-5351/add-more-variables-to-theme' into UIK-5641/not…
Valeria-Zimnitskaya Aug 14, 2026
b58a596
[chore] update snapshots
Valeria-Zimnitskaya Aug 14, 2026
615d13f
[chore] Merge branch 'UIK-5641/notice-redesign' of https://github.com…
Valeria-Zimnitskaya Aug 14, 2026
ca4ce49
[chore] update snapshots
Valeria-Zimnitskaya Aug 14, 2026
a64ffa7
[notice] fixed noticeSmart chidlren
ilyabrower Aug 17, 2026
5a8cc2c
[notice] exclude icon/illustration from DOM attributes
ilyabrower Aug 17, 2026
1421a9b
[notice] fixed in date-picker examples
ilyabrower Aug 17, 2026
ad178f3
[notice] update tests and snapshots
Valeria-Zimnitskaya Aug 17, 2026
8888ab8
[notice] fixed in date-picker examples
ilyabrower Aug 17, 2026
fa18b28
[chore] Merge branch 'UIK-5641/notice-redesign' of github.com:semrush…
ilyabrower Aug 17, 2026
b36e21d
[notice] fixed types
ilyabrower Aug 17, 2026
7c5e117
Merge branch 'UIK-5351/add-more-variables-to-theme' into UIK-5641/not…
Valeria-Zimnitskaya Aug 17, 2026
2d0f53b
[chore] update snapshots
Valeria-Zimnitskaya Aug 17, 2026
9cae3eb
[notice] fixed types
ilyabrower Aug 17, 2026
ea1cb45
[chore] Merge branch 'UIK-5641/notice-redesign' of github.com:semrush…
ilyabrower Aug 17, 2026
8c3dc0b
Merge branch 'UIK-5351/add-more-variables-to-theme' into UIK-5641/not…
Valeria-Zimnitskaya Aug 17, 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
3 changes: 3 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

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.
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.
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.
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.
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.
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.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
230 changes: 175 additions & 55 deletions semcore/notice/__tests__/notice.browser-test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,15 @@ import { expect, test } from '@semcore/testing-utils/playwright';
import { loadPage } from '@semcore/testing-utils/shared/helpers';
import { TAG } from '@semcore/testing-utils/shared/tags';

const SMART_STORY = 'stories/components/notice/tests/examples/noticesmart_full_props.tsx';

export const locators = {
notice: (page: Page) => page.locator('[data-ui-name="Notice"]'),
noticeSmart: (page: Page) => page.locator('[data-ui-name="NoticeSmart"]'),
close: (page: Page) => page.locator('[data-ui-name="Notice.Close"]'),
content: (page: Page) => page.locator('[data-ui-name="Notice.Content"]'),
icon: (page: Page) =>
page.locator('[data-ui-name="Notice"] > [data-ui-name="Box"], [data-ui-name="NoticeSmart"] > [data-ui-name="Box"]'),
title: (page: Page) => page.locator('[data-ui-name="Notice.Title"]'),
text: (page: Page) => page.locator('[data-ui-name="Notice.Text"]'),
};
Expand Down Expand Up @@ -47,53 +51,91 @@ test.describe(`${TAG.VISUAL}`, () => {
const noticeContent = locators.content(page);
const title = noticeContent.first().locator('[data-ui-name="Notice.Title"]');
await expect(title).toHaveCSS('margin-top', '2px');
await expect(title).toHaveCSS('margin-bottom', '2px');
await expect(title).toHaveCSS('margin-bottom', '0px');

const text = noticeContent.first().locator('[data-ui-name="Notice.Text"]');
await expect(text).toHaveCSS('margin-top', '4px');
await expect(text).toHaveCSS('margin-bottom', '4px');
});
});

test('Verify different pairs of sub-components', {
test('Verify notice with icon prop', {
tag: [TAG.PRIORITY_HIGH, '@notice'],
}, async ({ page }) => {
await loadPage(page, 'stories/components/notice/tests/examples/notice_with_different_states.tsx', 'en');
await loadPage(page, 'stories/components/notice/docs/examples/basic_notice.tsx', 'en');

await test.step('Verify all notice states render correctly', async () => {
await page.setViewportSize({ width: 1600, height: 1200 });
await expect(page).toHaveScreenshot();
await test.step('Verify icon is rendered inside every notice', async () => {
const notices = await locators.notice(page).all();
const icons = locators.icon(page);

await expect(icons).toHaveCount(notices.length);

for (const icon of await icons.all()) {
await expect(icon).toBeVisible();
await expect(icon.locator('svg')).toBeVisible();
}
});
});

test('Verify notice with big illustrations', {
tag: [TAG.PRIORITY_HIGH, '@notice'],
}, async ({ page }) => {
await loadPage(page, 'stories/components/notice/tests/examples/notice_big_illustration.tsx', 'en');
await test.step('Verify icon color depends on notice theme', async () => {
const icons = locators.icon(page);
// basic_notice.tsx order: info, muted, warning, danger, success
const [info, muted, warning, danger, success] = await Promise.all(
[0, 1, 2, 3, 4].map((index) =>
icons.nth(index).evaluate((node) => getComputedStyle(node).color),
),
);

expect(info).toBe(muted);
expect(new Set([info, warning, danger, success]).size).toBe(4);
});

await test.step('Verify big illustrations display correctly', async () => {
await test.step('Verify notices with icon render correctly', async () => {
await page.setViewportSize({ width: 1600, height: 1000 });
await expect(page).toHaveScreenshot();
});
});

test('Verify notice with medium illustrations', {
test('Verify NoticeSmart media variations', {
tag: [TAG.PRIORITY_HIGH, '@notice'],
}, async ({ page }) => {
await loadPage(page, 'stories/components/notice/tests/examples/notice_medium_illustration.tsx', 'en');
for (const media of ['none', 'icon', 'illustration']) {
await test.step(`Verify NoticeSmart with media set to ${media}`, async () => {
await loadPage(page, SMART_STORY, 'en', { media });
await expect(page).toHaveScreenshot();
});
}
});

await test.step('Verify medium illustrations display correctly', async () => {
await page.setViewportSize({ width: 1600, height: 1000 });
test('Verify NoticeSmart content combinations', {
tag: [TAG.PRIORITY_HIGH, '@notice'],
}, async ({ page }) => {
await test.step('Verify NoticeSmart without title', async () => {
await loadPage(page, SMART_STORY, 'en', { title: '' });
await expect(page).toHaveScreenshot();
});

await test.step('Verify NoticeSmart without text', async () => {
await loadPage(page, SMART_STORY, 'en', { text: '' });
await expect(page).toHaveScreenshot();
});

await test.step('Verify NoticeSmart without actions and close button', async () => {
await loadPage(page, SMART_STORY, 'en', { withActions: false, closable: false });
await expect(page).toHaveScreenshot();
});

await test.step('Verify NoticeSmart in narrow container wraps text', async () => {
await loadPage(page, SMART_STORY, 'en', { w: 320 });
await expect(page).toHaveScreenshot();
});
});

test('Verify notice with small illustrations', {
test('Verify notice with big illustrations', {
tag: [TAG.PRIORITY_HIGH, '@notice'],
}, async ({ page }) => {
await loadPage(page, 'stories/components/notice/tests/examples/notice_small_illustration.tsx', 'en');
await loadPage(page, 'stories/components/notice/tests/examples/notice_big_illustration.tsx', 'en');

await test.step('Verify small illustrations display correctly', async () => {
await test.step('Verify big illustrations display correctly', async () => {
await page.setViewportSize({ width: 1600, height: 1000 });
await expect(page).toHaveScreenshot();
});
Expand Down Expand Up @@ -154,40 +196,115 @@ test.describe(`${TAG.FUNCTIONAL}`, () => {
});
});

test.describe(`Notice `, () => {
test('Verify hidden prop toggle shows and hides notice', {
test.describe(`NoticeSmart `, () => {
test('Verify hidden prop hides notice', {
tag: [TAG.PRIORITY_HIGH, '@notice'],
}, async ({ page }) => {
await test.step('Verify notice is visible by default', async () => {
await loadPage(page, SMART_STORY, 'en');
await expect(page.getByTestId('smart-configurable')).toBeVisible();
});

await test.step('Verify notice is not visible with hidden prop', async () => {
await loadPage(page, SMART_STORY, 'en', { hidden: true });
await expect(page.getByTestId('smart-configurable')).not.toBeVisible();
});
});

test('Verify closable prop and closing by click', {
tag: [TAG.PRIORITY_HIGH, TAG.MOUSE, '@notice'],
}, async ({ page }) => {
await loadPage(page, 'stories/components/notice/tests/examples/notice_with_different_states.tsx', 'en');
await test.step('Verify close button is absent when closable is false', async () => {
await loadPage(page, SMART_STORY, 'en', { closable: false });
await expect(locators.close(page)).toHaveCount(0);
});

await test.step('Verify notice closes on close button click', async () => {
await loadPage(page, SMART_STORY, 'en');
await locators.close(page).click();
await expect(page.getByTestId('smart-configurable')).not.toBeVisible();
});
});

test('Verify close button is reachable by keyboard', {
tag: [TAG.PRIORITY_HIGH, TAG.KEYBOARD, '@notice'],
}, async ({ page }) => {
await loadPage(page, SMART_STORY, 'en');

await test.step('Verify Tab moves focus to actions and then to close button', async () => {
await page.keyboard.press('Tab');
await expect(page.getByRole('button', { name: 'Learn more' })).toBeFocused();

await test.step('Verify notice is initially hidden', async () => {
await expect(page.getByLabel('Toggleable notice')).not.toBeVisible();
await page.keyboard.press('Tab');
await expect(page.getByRole('button', { name: 'Dismiss' })).toBeFocused();

await page.keyboard.press('Tab');
await expect(locators.close(page)).toBeFocused();
});

await test.step('Verify notice becomes visible on toggle', async () => {
await page.getByTestId('toggle-btn').click();
await expect(page.getByLabel('Toggleable notice')).toBeVisible();
await test.step('Verify notice closes on Enter', async () => {
await page.keyboard.press('Enter');
await expect(page.getByTestId('smart-configurable')).not.toBeVisible();
});
});

await test.step('Verify notice hides again on toggle', async () => {
await page.getByTestId('toggle-btn').click();
await expect(page.getByLabel('Toggleable notice')).not.toBeVisible();
test('Verify actions are rendered only when passed', {
tag: [TAG.PRIORITY_HIGH, '@notice'],
}, async ({ page }) => {
await test.step('Verify actions are present by default', async () => {
await loadPage(page, SMART_STORY, 'en');
await expect(page.getByRole('button', { name: 'Learn more' })).toBeVisible();
});

await test.step('Verify actions are absent when not passed', async () => {
await loadPage(page, SMART_STORY, 'en', { withActions: false });
await expect(page.getByRole('button', { name: 'Learn more' })).toHaveCount(0);
});
});

test('Verify aria-live attribute passthrough', {
test('Verify title and text are rendered only when passed', {
tag: [TAG.PRIORITY_HIGH, '@notice'],
}, async ({ page }) => {
await loadPage(page, 'stories/components/notice/tests/examples/notice_with_different_states.tsx', 'en');
await test.step('Verify title and text are present by default', async () => {
await loadPage(page, SMART_STORY, 'en');
await expect(locators.title(page)).toHaveCount(1);
await expect(locators.text(page)).toHaveCount(1);
});

await test.step('Verify aria-live is set on notice', async () => {
const liveNotice = page.getByLabel('Live notice');
await expect(liveNotice).toHaveAttribute('aria-live', 'polite');
await test.step('Verify title is absent when empty', async () => {
await loadPage(page, SMART_STORY, 'en', { title: '' });
await expect(locators.title(page)).toHaveCount(0);
await expect(locators.text(page)).toHaveCount(1);
});

await test.step('Verify text is absent when empty', async () => {
await loadPage(page, SMART_STORY, 'en', { text: '' });
await expect(locators.title(page)).toHaveCount(1);
await expect(locators.text(page)).toHaveCount(0);
});
});

test('Verify media element is rendered according to media prop', {
tag: [TAG.PRIORITY_HIGH, '@notice'],
}, async ({ page }) => {
await test.step('Verify only one media element is rendered for icon', async () => {
await loadPage(page, SMART_STORY, 'en', { media: 'icon' });
await expect(locators.icon(page)).toHaveCount(1);
});

await test.step('Verify only one media element is rendered for illustration', async () => {
await loadPage(page, SMART_STORY, 'en', { media: 'illustration' });
await expect(locators.icon(page)).toHaveCount(1);
});

await test.step('Verify no media element is rendered for none', async () => {
await loadPage(page, SMART_STORY, 'en', { media: 'none' });
await expect(locators.icon(page)).toHaveCount(0);
});
});
});

test.describe(`NoticeSmart `, () => {
test.describe(`NoticeSmart docs example `, () => {
test('Verify NoticeSmart roles and attributes', {
tag: [TAG.PRIORITY_HIGH, '@notice'],
}, async ({ page }) => {
Expand All @@ -214,31 +331,34 @@ test.describe(`${TAG.FUNCTIONAL}`, () => {
}
});
});
});

test('Verify NoticeSmart interactions', {
tag: [TAG.PRIORITY_HIGH, TAG.KEYBOARD, TAG.MOUSE, '@notice'],
test.describe(`Notice illustrations `, () => {
test('Verify medium illustrations keep their default size and stay left of the content', {
tag: [TAG.PRIORITY_HIGH, '@notice'],
}, async ({ page }) => {
await loadPage(page, 'stories/components/notice/docs/examples/noticesmart.tsx', 'en');
await loadPage(page, 'stories/components/notice/tests/examples/notice_medium_illustration.tsx', 'en');

await test.step('Verify keyboard navigation to close button', async () => {
const closes = locators.close(page);
await page.keyboard.press('Tab');
await expect(closes.first()).toBeFocused();
const notices = locators.notice(page);
const noticesCount = await notices.count();

await test.step('Verify every notice renders a single illustration', async () => {
await expect(locators.icon(page)).toHaveCount(noticesCount);
});

await test.step('Verify notices close on Enter and click', async () => {
await page.keyboard.press('Enter');
await expect(page.getByLabel('New tool announcement')).not.toBeVisible();
for (let index = 0; index < noticesCount; index++) {
await test.step(`Verify illustration ${index + 1} size and position`, async () => {
const illustration = locators.icon(page).nth(index);
const illustrationBox = await illustration.locator('svg').boundingBox();
const contentBox = await locators.content(page).nth(index).boundingBox();

await locators.close(page).first().click();
await expect(page.getByLabel('New feature announcement')).not.toBeVisible();
await expect(page.locator('[data-ui-name="Notice.Label"][color="muted"]')).not.toBeVisible();
});
// Illustrations render at their own default size, the component does not resize them
expect(illustrationBox?.width).toBeCloseTo(80, 3);
expect(illustrationBox?.height).toBeCloseTo(80, 3);

expect(illustrationBox!.x + illustrationBox!.width).toBeLessThanOrEqual(contentBox!.x);
});
}
});
});
});

/* =====================================================
@functional
Additional coverage — hidden toggle, custom theme, aria-live, muted+closable.
===================================================== */
Diff not rendered.
Diff not rendered.
Diff not rendered.
Diff not rendered.
Diff not rendered.
Diff not rendered.
Diff not rendered.
Diff not rendered.
Diff not rendered.
1 change: 1 addition & 0 deletions semcore/notice/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
"@semcore/core": "workspace:*",
"@semcore/base-components": "workspace:*",
"@semcore/icon": "workspace:*",
"@semcore/illustration": "workspace:*",
"@semcore/testing-utils": "workspace:*"
}
}
10 changes: 9 additions & 1 deletion semcore/notice/src/Notice.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,9 @@ class RootNotice extends Component<

render() {
const SNotice = Root;
const { Children, styles, hidden, theme, resolveColor, getI18nText } = this.asProps;
const SIcon = Box;
const SIllustration = Box;
const { Children, styles, hidden, theme, resolveColor, getI18nText, icon, illustration } = this.asProps;
const color = resolveColor(theme);
const useTheme = isCustomTheme(theme) ? 'custom' : theme;

Expand All @@ -114,7 +116,13 @@ class RootNotice extends Component<
role={role}
aria-label={ariaLabel}
ref={this.ref}
__excludeProps={['icon', 'illustration']}
>
{icon !== undefined && (
// @ts-expect-error we use theme in css-only
<SIcon theme={useTheme}>{icon}</SIcon>
)}
{illustration !== undefined && (<SIllustration>{illustration}</SIllustration>)}
<Children />
</SNotice>,
);
Expand Down
Loading
Loading