Skip to content

Commit

Permalink
feat(ModalCard, ModalPage): add preventClose prop (#6603)
Browse files Browse the repository at this point in the history
* feat(ModalCard, ModalPage): add preventClose prop

* doc: revert example
  • Loading branch information
BlackySoul authored Feb 29, 2024
1 parent df10510 commit 26b5cdc
Show file tree
Hide file tree
Showing 22 changed files with 104 additions and 45 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@ export const ModalCardPlayground = (props: ComponentPlaygroundProps) => {
</Button>
</React.Fragment>,
],
dismissButtonMode: ['inside', 'outside'],
dismissButtonMode: ['inside', 'outside', 'none'],
},
]}
AppWrapper={AppWrapper}
Expand Down
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.
33 changes: 24 additions & 9 deletions packages/vkui/src/components/ModalCardBase/ModalCardBase.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -65,10 +65,20 @@ export interface ModalCardBaseProps extends HTMLAttributesWithRootRef<HTMLDivEle
modalDismissButtonTestId?: string;
/**
* Расположение кнопки закрытия (внутри и вне `popout'a`)
*
* Доступно только в `compact`-режиме
*
* На `iOS` в `regular`-режиме всегда включен `inside`
*
* ⚠️ ВНИМАНИЕ: использование `none` скрывает крестик, это негативно сказывается на пользовательском опыте
*/
dismissButtonMode?: 'inside' | 'outside' | 'none';
/**
* Позволяет отключить возможность закрытия модальной страницы (смахивание, клавиша `ESC`, клик по подложке)
*
* ⚠️ ВНИМАНИЕ: использование этой опции негативно сказывается на пользовательском опыте
*/
dismissButtonMode?: 'inside' | 'outside';
preventClose?: boolean;
}

/**
Expand All @@ -88,6 +98,7 @@ export const ModalCardBase = ({
size: sizeProp,
modalDismissButtonTestId,
dismissButtonMode = 'outside',
preventClose,
...restProps
}: ModalCardBaseProps) => {
const platform = usePlatform();
Expand All @@ -96,7 +107,9 @@ export const ModalCardBase = ({

const size = isDesktop ? sizeProp : undefined;
const withSafeZone =
!icon && (dismissButtonMode === 'inside' || (platform === 'ios' && !isDesktop));
!icon &&
(dismissButtonMode === 'inside' ||
(platform === 'ios' && !isDesktop && dismissButtonMode !== 'none'));

const hasHeader = hasReactNode(header);
const hasSubheader = hasReactNode(subheader);
Expand Down Expand Up @@ -144,13 +157,15 @@ export const ModalCardBase = ({

{hasReactNode(actions) && <div className={styles['ModalCardBase__actions']}>{actions}</div>}

<ModalCardBaseCloseButton
testId={modalDismissButtonTestId}
onClose={onClose}
mode={dismissButtonMode}
>
{dismissLabel}
</ModalCardBaseCloseButton>
{dismissButtonMode !== 'none' && (
<ModalCardBaseCloseButton
testId={modalDismissButtonTestId}
onClose={onClose}
mode={dismissButtonMode}
>
{dismissLabel}
</ModalCardBaseCloseButton>
)}
</div>
</RootComponent>
);
Expand Down
22 changes: 20 additions & 2 deletions packages/vkui/src/components/ModalCardBase/Readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -76,10 +76,16 @@

## Кнопка для закрытия

Через свойство `dismissButtonMode=inside|outside` можно задать вид кнопки закрытия.
Через свойство `dismissButtonMode=inside|outside|none` можно задать вид кнопки закрытия.
Согласно нашим дизайн-гайдам, `dismissButtonMode=outside` отображается только для `compact`-режима (десктопная и планшетные версии).
Для `iOS` всегда будет применяться `dismissButtonMode=inside` в `regular`-режиме (мобильная версия).

> **Важно**
>
> Обратите внимание, что свойство `dismissButtonMode=none`, которое позволяет скрыть крестик, или свойство `preventClose`,
> отключающее возможность закрыть модалку стандартными способами,
> негативно влияет на пользовательский опыт, используйте эти свойства только если точно знаете, что делаете.
## Отступы между контентом и кнопками действий (`actions`)

По умолчанию верхний отступ от кнопок действий `actions` равняется `16px`. Согласно дизайн-системе отступ может быть больше в зависимости от того какие данные отображаются внутри `ModalCardBase`.
Expand Down Expand Up @@ -124,6 +130,12 @@
subheader="Безопасной зоны не будет, потому что есть иконка"
icon={<Icon56MoneyTransferOutline />}
/>
<ModalCardBase
dismissButtonMode="none"
style={{ width: 450, marginBottom: 20 }}
header="Десктопная и планшетная версии без крестика"
subheader="Очень плохая модалка"
/>
</AdaptivityProvider>
<AdaptivityProvider viewWidth={ViewWidth.MOBILE}>
<ModalCardBase
Expand All @@ -133,12 +145,18 @@
subheader="Сверху будет безопасный отступ до иконки"
/>
<ModalCardBase
style={{ width: 320 }}
style={{ width: 320, marginBottom: 20 }}
dismissButtonMode="inside"
icon={<Image borderRadius="l" src={getAvatarUrl('app_zagadki', 200)} size={72} />}
header="Мобильная версия с крестиком внутри"
subheader="Безопасной зоны не будет, потому что есть иконка"
/>
<ModalCardBase
dismissButtonMode="none"
style={{ width: 450 }}
header="Мобильная версия без крестика"
subheader="Очень плохая модалка"
/>
</AdaptivityProvider>
</div>
```
7 changes: 7 additions & 0 deletions packages/vkui/src/components/ModalPage/ModalPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,12 @@ export interface ModalPageProps extends HTMLAttributesWithRootRef<HTMLDivElement
* `data-testid` для кнопки закрытия
*/
modalDismissButtonTestId?: string;
/**
* Позволяет отключить возможность закрытия модальной страницы (смахивание, клавиша `ESC`, клик по подложке)
*
* ⚠️ ВНИМАНИЕ: использование этой опции негативно сказывается на пользовательском опыте
*/
preventClose?: boolean;
}

const warn = warnOnce('ModalPage');
Expand All @@ -94,6 +100,7 @@ export const ModalPage = ({
modalContentTestId,
modalDismissButtonTestId,
getRootRef,
preventClose,
...restProps
}: ModalPageProps) => {
const generatingId = React.useId();
Expand Down
16 changes: 15 additions & 1 deletion packages/vkui/src/components/ModalRoot/ModalRoot.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ describe.each([
});
});

describe('calls onClose', () => {
describe('handle onClose', () => {
describe('on fade click', () => {
it('calls modal onClose', async () => {
const onClose = jest.fn();
Expand Down Expand Up @@ -106,6 +106,20 @@ describe.each([
await clickFade();
expect(onCloseRoot).toHaveBeenCalledTimes(1);
});
it('does not call root onClose when preventClose is provided', async () => {
const onCloseRoot = jest.fn();
render(
<ModalRoot onClose={onCloseRoot} activeModal="m">
<ModalPage id="m" preventClose />
</ModalRoot>,
);
// wait for animations
act(jest.runAllTimers);
await clickFade();
expect(onCloseRoot).not.toHaveBeenCalled();
await userEvent.keyboard('{Escape}');
expect(onCloseRoot).not.toHaveBeenCalled();
});
});
if (name === 'ModalRootDesktop') {
it('on esc click', async () => {
Expand Down
6 changes: 3 additions & 3 deletions packages/vkui/src/components/ModalRoot/ModalRoot.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -405,19 +405,19 @@ class ModalRootTouchComponent extends React.Component<
} else if (numberInRange(translateY, modalState.collapsedRange)) {
translateY = modalState.translateYFrom ?? 0;
} else if (numberInRange(translateY, modalState.hiddenRange)) {
translateY = 100;
translateY = modalState.preventClose ? modalState.translateYFrom ?? 0 : 100;
} else {
translateY = modalState.translateYFrom ?? 0;
}
} else {
if (numberInRange(translateY, [0, 25])) {
translateY = 0;
} else {
translateY = 100;
translateY = modalState.preventClose ? modalState.translateYFrom ?? 0 : 100;
}
}

if (translateY !== 100 && shiftYEndPercent >= 75) {
if (translateY !== 100 && shiftYEndPercent >= 75 && !modalState.preventClose) {
translateY = 100;
}

Expand Down
4 changes: 4 additions & 0 deletions packages/vkui/src/components/ModalRoot/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,10 @@ export interface ModalsStateEntry extends ModalElements {
expandedRange?: TranslateRange;
collapsedRange?: TranslateRange;
hiddenRange?: TranslateRange;
/**
* Отключает возможность закрыть модалку стандартными способами
*/
preventClose?: boolean;
}

export interface ModalRootProps {
Expand Down
3 changes: 2 additions & 1 deletion packages/vkui/src/components/ModalRoot/useModalManager.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,7 @@ export function useModalManager(
state.onOpened = Modal.props.onOpened;
state.onClose = Modal.props.onClose;
state.onClosed = Modal.props.onClosed;
state.preventClose = Modal.props.preventClose;
// ModalPage props
if (typeof modalProps.settlingHeight === 'number') {
state.settlingHeight = modalProps.settlingHeight;
Expand Down Expand Up @@ -196,7 +197,7 @@ export function useModalManager(

function onExit() {
const modalState = transitionState.activeModal && modalsState[transitionState.activeModal];
if (modalState) {
if (modalState && !modalState.preventClose) {
if (isFunction(modalState.onClose)) {
modalState.onClose();
} else if (isFunction(onClose) && modalState.id) {
Expand Down

0 comments on commit 26b5cdc

Please sign in to comment.