Skip to content

Commit

Permalink
feat(Alert): add renderAction (#5530)
Browse files Browse the repository at this point in the history
* feat(Alert): add renderAction
Добавляем свойство `renderAction` и `actionsAlign`

Немного декомпозировал код

* docs: add example

* feat: use AlertActionInterface

* fix: suggest css update (#5558)

* feat: AlertActionProps

---------

Co-authored-by: Evgenia Polozova <[email protected]>
  • Loading branch information
SevereCloud and eugpoloz authored Aug 2, 2023
1 parent b5aa644 commit ae6355e
Show file tree
Hide file tree
Showing 7 changed files with 276 additions and 133 deletions.
64 changes: 34 additions & 30 deletions packages/vkui/src/components/Alert/Alert.module.css
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@

.Alert__content {
position: relative;
padding: 24px 24px 20px;
padding: 24px 24px 16px;
}

.Alert__action {
Expand All @@ -46,7 +46,7 @@
display: flex;
max-width: 100%;
position: relative;
padding: 0 16px 16px;
padding: 0 12px 12px;
}

.Alert__header {
Expand All @@ -57,30 +57,34 @@
color: var(--vkui--color_text_secondary);
}

.Alert--h .Alert__actions {
.Alert__actions--direction-horizontal {
justify-content: flex-end;
}

.Alert--h .Alert__button {
margin-left: 8px;
}

.Alert--v .Alert__actions {
.Alert__actions--direction-vertical {
flex-direction: column;
align-items: flex-end;
}

.Alert--v .Alert__button {
margin-top: 4px;
margin-bottom: 4px;
/* stylelint-disable @project-tools/stylelint-atomic */
.Alert__actions > * {
margin: 4px;
}
/* stylelint-enable @project-tools/stylelint-atomic */

.Alert__actions--align-left {
justify-content: flex-start;
align-items: flex-start;
}

.Alert--v .Alert__button:first-child {
margin-top: 0;
.Alert__actions--align-center {
justify-content: center;
align-items: center;
}

.Alert--v .Alert__button:last-child {
margin-bottom: 0;
.Alert__actions--align-right {
justify-content: flex-end;
align-items: flex-end;
}

/**
Expand Down Expand Up @@ -128,7 +132,7 @@
padding: initial;
}

.Alert--ios.Alert--v .Alert__actions {
.Alert--ios .Alert__actions--direction-vertical {
flex-direction: column;
align-items: initial;
}
Expand Down Expand Up @@ -157,65 +161,65 @@
background: var(--vkui--color_separator_primary_alpha);
}

.Alert--ios.Alert--h .Alert__action::after {
.Alert--ios .Alert__actions--direction-horizontal .Alert__action::after {
top: 0;
right: 0;
width: 1px;
height: 100%;
transform-origin: right center;
}

.Alert--ios.Alert--h .Alert__action:last-child::after {
.Alert--ios .Alert__actions--direction-horizontal .Alert__action:last-child::after {
content: none;
}

.Alert--ios.Alert--h .Alert__action {
.Alert--ios .Alert__actions--direction-horizontal .Alert__action {
flex-grow: 1;
flex-shrink: 1;
flex-basis: 0;
}

.Alert--ios.Alert--h .Alert__action:first-child {
.Alert--ios .Alert__actions--direction-horizontal .Alert__action:first-child {
border-bottom-left-radius: var(--vkui--size_border_radius_paper--regular);
}

.Alert--ios.Alert--h .Alert__action:last-child {
.Alert--ios .Alert__actions--direction-horizontal .Alert__action:last-child {
border-bottom-right-radius: var(--vkui--size_border_radius_paper--regular);
}

.Alert--ios.Alert--v .Alert__action::after {
.Alert--ios .Alert__actions--direction-vertical .Alert__action::after {
left: 0;
bottom: 0;
width: 100%;
height: 1px;
transform-origin: center bottom;
}

.Alert--ios.Alert--v .Alert__action:last-child::after {
.Alert--ios .Alert__actions--direction-vertical .Alert__action:last-child::after {
content: none;
}

.Alert--ios.Alert--v .Alert__action:last-child {
.Alert--ios .Alert__actions--direction-vertical .Alert__action:last-child {
border-radius: 0 0 12px 12px;
}

@media (min-resolution: 2dppx) {
.Alert--ios .Alert__content::after,
.Alert--ios.Alert--v .Alert__action::after {
.Alert--ios .Alert__actions--direction-vertical .Alert__action::after {
transform: scaleY(0.5);
}

.Alert--ios.Alert--h .Alert__action::after {
.Alert--ios .Alert__actions--direction-horizontal .Alert__action::after {
transform: scaleX(0.5);
}
}
@media (min-resolution: 3dppx) {
.Alert--ios .Alert__content::after,
.Alert--ios.Alert--v .Alert__action::after {
.Alert--ios .Alert__actions--direction-vertical .Alert__action::after {
transform: scaleY(0.33);
}

.Alert--ios.Alert--h .Alert__action::after {
.Alert--ios .Alert__actions--direction-horizontal .Alert__action::after {
transform: scaleX(0.33);
}
}
Expand All @@ -238,11 +242,11 @@
}

.Alert--vkcom .Alert__content {
padding: 24px;
padding-bottom: 20px;
}

.Alert--vkcom .Alert__actions {
padding: 0 24px 16px;
padding: 0 20px 12px;
}

.Alert--vkcom .Alert__button {
Expand Down
121 changes: 20 additions & 101 deletions packages/vkui/src/components/Alert/Alert.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,31 +6,33 @@ import { usePlatform } from '../../hooks/usePlatform';
import { useWaitTransitionFinish } from '../../hooks/useWaitTransitionFinish';
import { Platform } from '../../lib/platform';
import { stopPropagation } from '../../lib/utils';
import { AnchorHTMLAttributesOnly, HasChildren } from '../../types';
import { AlignType, AnchorHTMLAttributesOnly } from '../../types';
import { useScrollLock } from '../AppRoot/ScrollContext';
import { Button, ButtonProps } from '../Button/Button';
import { ButtonProps } from '../Button/Button';
import { FocusTrap } from '../FocusTrap/FocusTrap';
import { ModalDismissButton } from '../ModalDismissButton/ModalDismissButton';
import { PopoutWrapper } from '../PopoutWrapper/PopoutWrapper';
import { Tappable } from '../Tappable/Tappable';
import { Caption } from '../Typography/Caption/Caption';
import { Footnote } from '../Typography/Footnote/Footnote';
import { Text } from '../Typography/Text/Text';
import { Title } from '../Typography/Title/Title';
import { AlertActionProps } from './AlertAction';
import { AlertActions } from './AlertActions';
import { AlertHeader, AlertText } from './AlertTypography';
import styles from './Alert.module.css';

type AlertActionMode = 'cancel' | 'destructive' | 'default';

export interface AlertActionInterface
extends Pick<ButtonProps, 'Component'>,
AnchorHTMLAttributesOnly {
title: string;
action?: VoidFunction;
autoClose?: boolean;
mode: 'cancel' | 'destructive' | 'default';
mode: AlertActionMode;
}

export interface AlertProps extends React.HTMLAttributes<HTMLElement> {
actionsLayout?: 'vertical' | 'horizontal';
actionsAlign?: AlignType;
actions?: AlertActionInterface[];
renderAction?: (props: AlertActionProps) => React.ReactNode;
header?: React.ReactNode;
text?: React.ReactNode;
onClose: VoidFunction;
Expand All @@ -41,89 +43,6 @@ export interface AlertProps extends React.HTMLAttributes<HTMLElement> {
dismissLabel?: string;
}

type ItemClickHandler = (item: AlertActionInterface) => void;

interface AlertTypography extends HasChildren {
id: string;
}

const AlertHeader = (props: AlertTypography) => {
const platform = usePlatform();

switch (platform) {
case Platform.IOS:
return <Title className={styles['Alert__header']} weight="1" level="3" {...props} />;
default:
return <Title className={styles['Alert__header']} weight="2" level="2" {...props} />;
}
};

const AlertText = (props: AlertTypography) => {
const platform = usePlatform();

switch (platform) {
case Platform.VKCOM:
return <Footnote className={styles['Alert__text']} {...props} />;
case Platform.IOS:
return <Caption className={styles['Alert__text']} {...props} />;
default:
return <Text Component="span" className={styles['Alert__text']} weight="3" {...props} />;
}
};

interface AlertActionProps {
action: AlertActionInterface;
onItemClick: ItemClickHandler;
}

const AlertAction = ({ action, onItemClick, ...restProps }: AlertActionProps) => {
const platform = usePlatform();
const handleItemClick = React.useCallback(() => onItemClick(action), [onItemClick, action]);

if (platform === Platform.IOS) {
const { title, action: actionProp, autoClose, mode, ...restActionProps } = action;

return (
<Tappable
Component={restActionProps.href ? 'a' : 'button'}
className={classNames(
styles['Alert__action'],
mode === 'destructive' && styles['Alert__action--mode-destructive'],
mode === 'cancel' && styles['Alert__action--mode-cancel'],
)}
onClick={handleItemClick}
{...restActionProps}
{...restProps}
>
{title}
</Tappable>
);
}

let mode: ButtonProps['mode'] = 'tertiary';

if (platform === Platform.VKCOM) {
mode = action.mode === 'cancel' ? 'secondary' : 'primary';
}

return (
<Button
className={classNames(
styles['Alert__button'],
action.mode === 'cancel' && styles['Alert__button--mode-cancel'],
)}
mode={mode}
size="m"
onClick={handleItemClick}
Component={action.Component}
href={action.href}
target={action.target}
>
{action.title}
</Button>
);
};

/**
* @see https://vkcom.github.io/VKUI/#/Alert
*/
Expand All @@ -137,6 +56,8 @@ export const Alert = ({
header,
onClose,
dismissLabel = 'Закрыть предупреждение',
renderAction,
actionsAlign,
...restProps
}: AlertProps) => {
const generatedId = useId();
Expand All @@ -152,9 +73,6 @@ export const Alert = ({

const elementRef = React.useRef<HTMLDivElement>(null);

const resolvedActionsLayout: AlertProps['actionsLayout'] =
platform === Platform.VKCOM ? 'horizontal' : actionsLayout;

const timeout = platform === Platform.IOS ? 300 : 200;

const close = React.useCallback(() => {
Expand All @@ -170,7 +88,7 @@ export const Alert = ({
);
}, [elementRef, waitTransitionFinish, onClose, timeout]);

const onItemClick: ItemClickHandler = React.useCallback(
const onItemClick = React.useCallback(
(item: AlertActionInterface) => {
const { action, autoClose } = item;

Expand Down Expand Up @@ -207,7 +125,6 @@ export const Alert = ({
styles['Alert'],
platform === Platform.IOS && styles['Alert--ios'],
platform === Platform.VKCOM && styles['Alert--vkcom'],
resolvedActionsLayout === 'vertical' ? styles['Alert--v'] : styles['Alert--h'],
closing && styles['Alert--closing'],
isDesktop && styles['Alert--desktop'],
)}
Expand All @@ -221,11 +138,13 @@ export const Alert = ({
{hasReactNode(text) && <AlertText id={textId}>{text}</AlertText>}
{children}
</div>
<div className={styles['Alert__actions']}>
{actions.map((action, i) => (
<AlertAction key={i} action={action} onItemClick={onItemClick} />
))}
</div>
<AlertActions
actions={actions}
actionsAlign={actionsAlign}
actionsLayout={actionsLayout}
renderAction={renderAction}
onItemClick={onItemClick}
/>
{isDesktop && <ModalDismissButton onClick={close} aria-label={dismissLabel} />}
</FocusTrap>
</PopoutWrapper>
Expand Down
Loading

0 comments on commit ae6355e

Please sign in to comment.