diff --git a/semcore/spin-container/__tests__/index.test.tsx b/semcore/spin-container/__tests__/index.test.tsx index ed88eecc8c..df199fbeab 100644 --- a/semcore/spin-container/__tests__/index.test.tsx +++ b/semcore/spin-container/__tests__/index.test.tsx @@ -22,4 +22,17 @@ describe('SpinContainer', () => { const { container } = render(spinContainer); expect(extractUIName(container)).toMatchSnapshot(); }); + + test('Verify Overlay falls back to Spin when children are explicitly undefined', () => { + const { container } = render( + + Content + {undefined} + , + ); + + const overlay = container.querySelector('[data-ui-name="SpinContainer.Overlay"]'); + + expect(overlay?.querySelector('[data-ui-name="Spin"]')).not.toBeNull(); + }); }); diff --git a/semcore/spin-container/__tests__/spin-container.browser-test.tsx b/semcore/spin-container/__tests__/spin-container.browser-test.tsx index 2037c9d127..3b73b9420d 100644 --- a/semcore/spin-container/__tests__/spin-container.browser-test.tsx +++ b/semcore/spin-container/__tests__/spin-container.browser-test.tsx @@ -4,6 +4,8 @@ import { loadPage } from '@semcore/testing-utils/shared/helpers'; import { TAG } from '@semcore/testing-utils/shared/tags'; const ALL_SIZES_EXAMPLE = 'stories/components/spin-container/advanced/examples/all-sizes.tsx'; +const AUTOFOCUS_EXAMPLE = + 'stories/components/spin-container/tests/examples/spin-container-with-autofocus-input.tsx'; const BASE_EXAMPLE = 'stories/components/spin-container/tests/examples/spin-container-base.tsx'; const CONTENT_EXAMPLE = 'stories/components/spin-container/docs/examples/usage_in_content.tsx'; @@ -243,6 +245,36 @@ test.describe(`${TAG.FUNCTIONAL}`, () => { await expect(locators.input(page, 0)).toBeFocused(); }); + test('Verify autofocused content gets focus as soon as loading ends', { + tag: [TAG.PRIORITY_HIGH, TAG.KEYBOARD, '@spin-container', + '@base-components', + '@button', + '@input', + '@typography'], + }, async ({ page }) => { + await loadPage(page, AUTOFOCUS_EXAMPLE, 'en'); + + const input = page.getByRole('textbox', { name: 'Input' }); + + await test.step('Verify content is inert while loading', async () => { + await expect(locators.spinContainerContent(page)).toHaveAttribute('inert', ''); + await expect(input).toHaveCount(0); + }); + + await test.step('Verify autofocused input is focused once loading stops', async () => { + await page.getByRole('button', { name: 'Stop loading' }).click(); + + await expect(input).toBeFocused(); + }); + + await test.step('Verify content becomes inert again when loading restarts', async () => { + await page.getByRole('button', { name: 'Start loading' }).click(); + + await expect(locators.spinContainerContent(page)).toHaveAttribute('inert', ''); + await expect(input).toHaveCount(0); + }); + }); + test('Verify size and theme are forwarded to the nested Spin', { tag: [TAG.PRIORITY_HIGH, '@spin-container', '@base-components', diff --git a/semcore/spin-container/src/SpinContainer.tsx b/semcore/spin-container/src/SpinContainer.tsx index 4db7410f00..bd361dbe71 100644 --- a/semcore/spin-container/src/SpinContainer.tsx +++ b/semcore/spin-container/src/SpinContainer.tsx @@ -9,16 +9,12 @@ import React from 'react'; import type { NSSpinContainer } from './SpinContainer.type'; import style from './style/spin-container.shadow.css'; -type State = { - inert?: boolean; -}; - class SpinContainerRoot extends Component< Intergalactic.InternalTypings.InferComponentProps, typeof SpinContainerRoot.enhance, {}, {}, - State, + {}, NSSpinContainer.DefaultProps > { static displayName = 'SpinContainer'; @@ -31,29 +27,6 @@ class SpinContainerRoot extends Component< static enhance = [resolveColorEnhance()] as const; - state: State = { - inert: this.props.loading, - }; - - private inertTimer: ReturnType | null = null; - - componentDidUpdate(prevProps: typeof this.asProps) { - const { loading } = this.props; - if (prevProps.loading !== loading) { - if (this.inertTimer) { - clearTimeout(this.inertTimer); - } - - if (loading) { - this.setState({ inert: true }); - } else { - this.inertTimer = setTimeout(() => { - this.setState({ inert: false }); - }, this.asProps.duration + 50); - } - } - } - getOverlayProps() { const { loading, background, duration, size, theme, resolveColor } = this.asProps; return { @@ -69,9 +42,11 @@ class SpinContainerRoot extends Component< } getContentProps() { - const { inert } = this.state; + const { loading } = this.asProps; - return { inert: inert ? '' : undefined }; + return { + inert: loading ? '' : undefined, + }; } render() { @@ -103,18 +78,24 @@ class SpinContainerRoot extends Component< type OverlayProps = Intergalactic.InternalTypings.InferChildComponentProps; class Overlay extends Component { - static defaultProps = ({ size, theme }: OverlayProps) => ({ - children: , - }); - render() { const SOverlay = Root; - const { styles, theme, background, loading, duration, resolveColor } = this.asProps; + const { styles, theme, size, background, loading, duration, resolveColor, children: hasChildren, Children } = this.asProps; const useTheme = background ? 'custom' : theme; return sstyled(styles)( - + + {hasChildren + ? + : ( + + )} + , ); } diff --git a/stories/components/spin-container/tests/examples/spin-container-with-autofocus-input.tsx b/stories/components/spin-container/tests/examples/spin-container-with-autofocus-input.tsx new file mode 100644 index 0000000000..39509e7d57 --- /dev/null +++ b/stories/components/spin-container/tests/examples/spin-container-with-autofocus-input.tsx @@ -0,0 +1,33 @@ +import { Flex } from '@semcore/ui/base-components'; +import Button from '@semcore/ui/button'; +import Input from '@semcore/ui/input'; +import SpinContainer from '@semcore/ui/spin-container'; +import { Text } from '@semcore/ui/typography'; +import React from 'react'; + +const Demo = () => { + const [loading, setLoading] = React.useState(true); + + return ( + + + + + {!loading && ( + + + Input + + + + + + )} + + + ); +}; + +export default Demo; diff --git a/stories/components/spin-container/tests/spin-container.stories.tsx b/stories/components/spin-container/tests/spin-container.stories.tsx index 88611a6b46..91e8ab9f81 100644 --- a/stories/components/spin-container/tests/spin-container.stories.tsx +++ b/stories/components/spin-container/tests/spin-container.stories.tsx @@ -4,6 +4,7 @@ import type { Meta, StoryObj } from '@storybook/react-vite'; import SpinContainerBaseExample, { defaultSpinContainerProps, } from './examples/spin-container-base'; +import SpinContainerWithAutofocusInputExample from './examples/spin-container-with-autofocus-input'; const meta: Meta = { title: 'Components/SpinContainer/Tests', @@ -62,3 +63,7 @@ export const SpinContainerBase: StoryObj = { argTypes: spinContainerArgTypes, args: defaultSpinContainerProps, }; + +export const SpinContainerWithAutofocusInput = { + render: SpinContainerWithAutofocusInputExample, +};