diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index faad6677b5..16d0046279 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1585,6 +1585,9 @@ importers: '@semcore/input': specifier: ^17.2.0 version: link:../input + '@semcore/spin': + specifier: ^17.2.0 + version: link:../spin '@semcore/typography': specifier: ^17.2.0 version: link:../typography diff --git a/semcore/base-components/src/components/flex-box/screen-reader-only-box/ScreenReaderOnlyBox.tsx b/semcore/base-components/src/components/flex-box/screen-reader-only-box/ScreenReaderOnlyBox.tsx index 603df2d322..1ff2358845 100644 --- a/semcore/base-components/src/components/flex-box/screen-reader-only-box/ScreenReaderOnlyBox.tsx +++ b/semcore/base-components/src/components/flex-box/screen-reader-only-box/ScreenReaderOnlyBox.tsx @@ -4,13 +4,44 @@ import React from 'react'; import style from './screenReaderOnlyBox.shadow.css'; import Box from '../Box'; -function ScreenReaderOnlyComponent() { +type SROnlyType = { + ariaLive?: boolean; + children?: React.ReactNode; +}; + +function ScreenReaderOnlyComponent(props: SROnlyType) { const SScreenReaderOnly = Root; + const { ariaLive, children } = props; + const [content, setContent] = React.useState(ariaLive ? null : children); + + React.useEffect(() => { + if (!ariaLive) { + setContent(children); + return; + } + + const timer = setTimeout(() => { + setContent(children); + }, 100); + + return () => { + clearTimeout(timer); + }; + }, [children]); - return sstyled(style)(); + return sstyled(style)( + + {content} + , + ); }; -type ScreenReaderOnlyType = Intergalactic.Component<'span'>; +type ScreenReaderOnlyType = Intergalactic.Component<'span', SROnlyType>; export const ScreenReaderOnly = createComponent< ScreenReaderOnlyType, diff --git a/semcore/date-picker/__tests__/date-range-comparator.browser-test.tsx b/semcore/date-picker/__tests__/date-range-comparator.browser-test.tsx index 488b4331e7..352c7ae510 100644 --- a/semcore/date-picker/__tests__/date-range-comparator.browser-test.tsx +++ b/semcore/date-picker/__tests__/date-range-comparator.browser-test.tsx @@ -116,6 +116,7 @@ test.describe(`${TAG.VISUAL}`, () => { TAG.MOUSE, '@date-picker'], }, async ({ page }) => { + await page.clock.setFixedTime(new Date('2024-01-15T12:00:00')); await loadPage(page, 'stories/components/date-picker/docs/examples/date_range_comparator_advanced_use.tsx', 'en'); const from = page.locator('[data-ui-name="DateRangeComparator.ValueDateRange"]').first(); diff --git a/semcore/dropdown-menu/__tests__/dropdown-menu.browser-test.tsx b/semcore/dropdown-menu/__tests__/dropdown-menu.browser-test.tsx index e07e218924..28489627ec 100644 --- a/semcore/dropdown-menu/__tests__/dropdown-menu.browser-test.tsx +++ b/semcore/dropdown-menu/__tests__/dropdown-menu.browser-test.tsx @@ -703,7 +703,6 @@ test.describe(`${TAG.VISUAL} `, () => { await expect(search).toBeFocused(); await page.keyboard.press('Tab'); - if (browserName == 'firefox') await page.keyboard.press('Tab'); // because in ff one additional focus on the list (bug) await expect(locators.item(page).nth(30)).toHaveClass(/highlighted/); }); @@ -1746,9 +1745,9 @@ test.describe(`${TAG.FUNCTIONAL}`, () => { }); await test.step('Verify result count is exposed to screen readers only', async () => { - const status = page.locator('#search-result'); + const status = page.getByRole('status'); await expect(status).toContainText('2 results found'); - await expect(status).toHaveAttribute('aria-hidden', 'true'); + await expect(status).toHaveAttribute('aria-live', 'polite'); await expect(page.locator('text="Nothing found"')).toHaveCount(0); }); }); diff --git a/semcore/dropdown-menu/src/DropdownMenu.jsx b/semcore/dropdown-menu/src/DropdownMenu.jsx index a2f19d8898..0b2775aa5a 100644 --- a/semcore/dropdown-menu/src/DropdownMenu.jsx +++ b/semcore/dropdown-menu/src/DropdownMenu.jsx @@ -331,10 +331,14 @@ function List({ styles, Children }) { const SBar = ScrollAreaComponent.Bar; const SScrollContainer = ScrollAreaComponent.Container; + const preventFocusByClick = React.useCallback((e) => { + e.preventDefault(); + }, []); + return sstyled(styles)( - + diff --git a/semcore/dropdown/__tests__/index.test.tsx b/semcore/dropdown/__tests__/index.test.tsx index 7e6af5bea0..ccdcb68c61 100644 --- a/semcore/dropdown/__tests__/index.test.tsx +++ b/semcore/dropdown/__tests__/index.test.tsx @@ -4,6 +4,7 @@ import { render, userEvent, screen, + waitFor, } from '@semcore/testing-utils/testing-library'; import { expect, test, describe, beforeEach, vi } from '@semcore/testing-utils/vitest'; import React from 'react'; @@ -30,16 +31,19 @@ describe('Dropdown.StatusItem', () => { test('Verify renders screen reader result count', () => { render(); - const status = screen.getByText('2 results found'); - expect(status).toBeInTheDocument(); - expect(status).toHaveAttribute('id', 'search-result'); - expect(status).toHaveAttribute('aria-hidden', 'true'); + waitFor(() => { + const status = screen.getByRole('status', { name: '2 results found' }); + expect(status).toBeInTheDocument(); + expect(status).toHaveAttribute('aria-live', 'polite'); + }); }); test('Verify renders singular result count', () => { render(); - expect(screen.getByText('1 result found')).toBeInTheDocument(); + waitFor(() => { + expect(screen.getByRole('status', { name: '1 result found' })).toBeInTheDocument(); + }); }); test('Verify renders loading and error states', () => { diff --git a/semcore/dropdown/src/AbstractDropdown.tsx b/semcore/dropdown/src/AbstractDropdown.tsx index 439e53025e..b3320f0575 100644 --- a/semcore/dropdown/src/AbstractDropdown.tsx +++ b/semcore/dropdown/src/AbstractDropdown.tsx @@ -312,10 +312,10 @@ export abstract class AbstractDropdown extends Component - {children ?? text} - + <> + + {children ?? text} + + + {children ?? text} + + ); } @@ -61,7 +66,7 @@ class StatusItemRoot extends Component< return this.renderText(getI18nText('StatusItem.defaultState.nothingFound'), children); } else { return ( -