Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(ScreenSpinner): remove size prop from component #7523

Merged
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { ScreenSpinner } from '@vkontakte/vkui';
import React from 'react';

const App = () => {
return (
<React.Fragment>
<ScreenSpinner
state="loading"
size="regular"
/>
<ScreenSpinner.Container>
<ScreenSpinner.Loader size="small" />
<ScreenSpinner.SwapIcon />
</ScreenSpinner.Container>
</React.Fragment>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`screen-spinner transforms correctly 1`] = `
"import { ScreenSpinner } from '@vkontakte/vkui';
import React from 'react';

const App = () => {
return (
(<React.Fragment>
<ScreenSpinner state="loading" />
<ScreenSpinner.Container>
<ScreenSpinner.Loader />
<ScreenSpinner.SwapIcon />
</ScreenSpinner.Container>
</React.Fragment>)
);
};"
`;
11 changes: 11 additions & 0 deletions packages/codemods/src/transforms/v7/__tests__/screen-spinner.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
jest.autoMockOff();
import { defineSnapshotTestFromFixture } from '../../../testHelpers/testHelper';

const name = 'screen-spinner';
const fixtures = ['basic'] as const;

describe(name, () => {
fixtures.forEach((test) =>
defineSnapshotTestFromFixture(__dirname, name, global.TRANSFORM_OPTIONS, `${name}/${test}`),
);
});
45 changes: 45 additions & 0 deletions packages/codemods/src/transforms/v7/screen-spinner.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import { API, ASTPath, FileInfo, JSXAttribute } from 'jscodeshift';
import { getImportInfo } from '../../codemod-helpers';
import { JSCodeShiftOptions } from '../../types';

export const parser = 'tsx';

export default function transformer(file: FileInfo, api: API, options: JSCodeShiftOptions) {
const { alias } = options;
const j = api.jscodeshift;
const source = j(file.source);
const { localName } = getImportInfo(j, file, 'ScreenSpinner', alias);

if (!localName) {
return source.toSource();
}

function removeSizeProp(attribute: ASTPath<JSXAttribute>) {
attribute.node.name.name === 'size' && j(attribute).remove();
}

// Обработка ScreenSpinner
source
.find(j.JSXOpeningElement)
.filter(
(path) => path.value.name.type === 'JSXIdentifier' && path.value.name.name === localName,
)
.find(j.JSXAttribute)
.forEach(removeSizeProp);

// Обработка ScreenSpinner.Loader
source
.find(j.JSXElement, {
openingElement: {
name: {
type: 'JSXMemberExpression',
object: { name: localName },
property: { name: 'Loader' },
},
},
})
.find(j.JSXAttribute)
.forEach(removeSizeProp);

return source.toSource();
}
13 changes: 13 additions & 0 deletions packages/vkui/src/components/ScreenSpinner/Readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,12 @@ const setCancelableScreenSpinner = () => {
setPopout(<ScreenSpinner state="cancelable" mode="overlay" onClick={clearPopout} />);
};

const setScreenSpinnerWithCustomIcon = () => {
setPopout(<ScreenSpinner state="custom" customIcon={<Icon56KeyOutline />} />);

setTimeout(clearPopout, 20000);
};

<SplitLayout popout={popout} aria-live="polite" aria-busy={!!popout}>
<SplitCol>
<View activePanel="spinner">
Expand All @@ -52,6 +58,9 @@ const setCancelableScreenSpinner = () => {
<CellButton onClick={setDoneScreenSpinner}>Запустить успешный процесс</CellButton>
<CellButton onClick={setErrorScreenSpinner}>Запустить процесс с ошибкой</CellButton>
<CellButton onClick={setCancelableScreenSpinner}>Запустить отменяемый процесс</CellButton>
<CellButton onClick={setScreenSpinnerWithCustomIcon}>
Запустить спинер с кастомной иконкой
</CellButton>
</Group>
</Panel>
</View>
Expand All @@ -71,5 +80,9 @@ const setCancelableScreenSpinner = () => {
<ScreenSpinner.Loader />
<ScreenSpinner.SwapIcon />
</ScreenSpinner.Container>
<ScreenSpinner.Container state="custom" customIcon={<Icon56CheckCircleOutline />}>
<ScreenSpinner.Loader />
<ScreenSpinner.SwapIcon />
</ScreenSpinner.Container>
</Flex>
```
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,8 @@
}

.ScreenSpinner--state-done .ScreenSpinner__spinner,
.ScreenSpinner--state-error .ScreenSpinner__spinner {
.ScreenSpinner--state-error .ScreenSpinner__spinner,
.ScreenSpinner--state-custom .ScreenSpinner__spinner {
opacity: 0;
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,18 @@
import type { Meta, StoryObj } from '@storybook/react';
import { CanvasFullLayout, DisableCartesianParam } from '../../storybook/constants';
import { createFieldWithPresets } from '../../testing/presets';
import { ScreenSpinner, type ScreenSpinnerProps } from './ScreenSpinner';

const story: Meta<ScreenSpinnerProps> = {
title: 'Popouts/ScreenSpinner',
component: ScreenSpinner,
parameters: { ...CanvasFullLayout, ...DisableCartesianParam },
argTypes: {
customIcon: createFieldWithPresets({
iconSizes: ['56'],
sizeIconsCount: 50,
}),
},
};

export default story;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,13 +23,14 @@ export const ScreenSpinner: React.FC<ScreenSpinnerProps> & {
cancelLabel,
mode,
caption,
customIcon,
...restProps
}: ScreenSpinnerProps): React.ReactNode => {
useScrollLock();

return (
<PopoutWrapper className={className} style={style} noBackground>
<ScreenSpinnerContainer state={state} mode={mode} caption={caption}>
<ScreenSpinnerContainer state={state} mode={mode} caption={caption} customIcon={customIcon}>
<ScreenSpinnerLoader {...restProps} />
<ScreenSpinnerSwapIcon onClick={onClick} cancelLabel={cancelLabel} />
</ScreenSpinnerContainer>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ const stateClassNames = {
cancelable: styles['ScreenSpinner--state-cancelable'],
done: styles['ScreenSpinner--state-done'],
error: styles['ScreenSpinner--state-error'],
custom: styles['ScreenSpinner--state-custom'],
};

const modeClassNames = {
Expand All @@ -19,17 +20,18 @@ const modeClassNames = {
};

type ScreenSpinnerContainerProps = HTMLAttributesWithRootRef<HTMLSpanElement> &
Pick<ScreenSpinnerProps, 'state' | 'mode' | 'caption'>;
Pick<ScreenSpinnerProps, 'state' | 'mode' | 'caption' | 'customIcon'>;

export const ScreenSpinnerContainer: React.FC<ScreenSpinnerContainerProps> = ({
state = 'loading',
mode = 'shadow',
customIcon,
caption,
children,
...restProps
}: ScreenSpinnerContainerProps) => {
return (
<ScreenSpinnerContext.Provider value={{ state, caption }}>
<ScreenSpinnerContext.Provider value={{ state, caption, customIcon }}>
<RootComponent
baseClassName={classNames(
styles['ScreenSpinner'],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,10 @@ import type { SpinnerProps } from '../Spinner/Spinner';
import { ScreenSpinnerContext } from './context';
import styles from './ScreenSpinner.module.css';

export const ScreenSpinnerLoader: React.FC<SpinnerProps> = ({
size = 'xl',
export const ScreenSpinnerLoader: React.FC<Omit<SpinnerProps, 'size'>> = ({
children,
...restProps
}: SpinnerProps) => {
}) => {
const { caption } = React.useContext(ScreenSpinnerContext);
// TODO [>=7]: см. https://github.com/VKCOM/VKUI/pull/7505#discussion_r1754153438
const a11yText = children ? children : caption ?? 'Пожалуйста, подождите...';
Expand All @@ -19,7 +18,7 @@ export const ScreenSpinnerLoader: React.FC<SpinnerProps> = ({
styles['ScreenSpinner__spinner'],
!caption && styles['ScreenSpinner__spinner--transition'],
)}
size={size}
size="xl"
noColor
{...restProps}
>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,21 +45,29 @@
cancelLabel,
...restProps
}: ScreenSpinnerSwapIconProps) => {
const { state } = React.useContext(ScreenSpinnerContext);
const { state, customIcon } = React.useContext(ScreenSpinnerContext);

if (state === 'cancelable') {
return <ScreenSpinnerCancelIcon aria-label={cancelLabel} {...restProps} />;
}

const Icon = {
loading: () => null,
done: Icon48DoneOutline,
error: Icon48CancelCircle,
}[state];
const getContent = () => {
if (state === 'custom') {
return customIcon;

Check warning on line 56 in packages/vkui/src/components/ScreenSpinner/ScreenSpinnerSwapIcon.tsx

View check run for this annotation

Codecov / codecov/patch

packages/vkui/src/components/ScreenSpinner/ScreenSpinnerSwapIcon.tsx#L56

Added line #L56 was not covered by tests
}

const Icon = {
loading: () => null,
done: Icon48DoneOutline,
error: Icon48CancelCircle,
}[state];

return <Icon />;
};

return (
<RootComponent baseClassName={styles['ScreenSpinner__icon']} {...restProps}>
<Icon />
{getContent()}
</RootComponent>
);
};
Expand Down
2 changes: 2 additions & 0 deletions packages/vkui/src/components/ScreenSpinner/context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,12 @@ import { type ScreenSpinnerProps } from './types';
export interface ScreenSpinnerContextProps {
state: NonNullable<ScreenSpinnerProps['state']>;
caption?: ScreenSpinnerProps['caption'];
customIcon?: ScreenSpinnerProps['customIcon'];
}

export const ScreenSpinnerContext: React.Context<ScreenSpinnerContextProps> =
React.createContext<ScreenSpinnerContextProps>({
state: 'loading',
caption: undefined,
customIcon: undefined,
});
10 changes: 7 additions & 3 deletions packages/vkui/src/components/ScreenSpinner/types.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,16 @@
import type * as React from 'react';
import type { SpinnerProps } from '../Spinner/Spinner';

export interface ScreenSpinnerProps extends SpinnerProps {
state?: 'loading' | 'cancelable' | 'done' | 'error';
export type ScreenSpinnerProps = Omit<SpinnerProps, 'size'> & {
state?: 'loading' | 'cancelable' | 'done' | 'error' | 'custom';
/**
* Кастомная иконка, работает совместно со `state="custom"`
*/
customIcon?: React.ReactNode;
mode?: 'shadow' | 'overlay';
/**
* Текст под иконкой
*/
caption?: React.ReactNode;
cancelLabel?: string;
}
};
Loading