feat(TEAMMSBMOB-14876): виджет быстрые действия

This commit is contained in:
Конюхова Дарья
2025-06-19 14:55:04 +07:00
parent 0bacce88bd
commit df8efcf861
17 changed files with 264 additions and 0 deletions
@@ -0,0 +1,99 @@
import styled, { keyframes } from 'styled-components';
import type { ExpandableProps } from './types';
const HEIGHT = 92;
const ANIMATION_DURATION = 0.3;
const expandAnimation = keyframes`
from {
opacity: 0;
height: 0;
}
to {
opacity: 1;
height: ${HEIGHT}px;
}
`;
const collapseAnimation = keyframes`
from {
opacity: 1;
height: ${HEIGHT}px;
}
to {
opacity: 0;
height: 0;
}
`;
export const Actions = styled.div`
cursor: pointer;
display: flex;
gap: 40px;
padding: 8px 4px 8px 16px;
position: relative;
`;
export const ActionsBox = styled.div<ExpandableProps>`
display: grid;
width: 100%;
row-gap: ${({ $isExpanded }) => ($isExpanded ? '32px' : '0')};
transition: row-gap ${ANIMATION_DURATION}s ease-in-out;
`;
export const ActionSection = styled.div`
display: grid;
grid-template-columns: repeat(auto-fill, 108px);
gap: 8px;
justify-content: space-between;
max-width: 572px;
width: 100%;
`;
export const ActionSectionExpanded = styled.div<ExpandableProps>`
display: grid;
grid-template-columns: repeat(auto-fill, 108px);
gap: 8px;
justify-content: space-between;
max-width: 572px;
width: 100%;
overflow: hidden;
animation: ${({ $isExpanded }) => ($isExpanded ? expandAnimation : collapseAnimation)} ${ANIMATION_DURATION}s ease forwards;
`;
export const ActionsItem = styled.div`
display: flex;
flex-direction: column;
align-items: center;
text-align: center;
min-height: ${HEIGHT}px;
max-width: 98px;
`;
export const Icon = styled.img`
object-fit: cover;
width: 32px;
height: 32px;
`;
export const IconBox = styled.div`
width: 48px;
height: 48px;
border-radius: 12px;
background-color: var(--bg-primary);
display: grid;
place-items: center;
margin-bottom: 8px;
transition: box-shadow ${ANIMATION_DURATION}s ease;
&:hover {
box-shadow: 0px 2px 16px 0px #1e2e611a;
}
`;
export const ButtonBox = styled.div<ExpandableProps>`
svg {
transition: transform ${ANIMATION_DURATION}s ease;
transform: ${({ $isExpanded }) => ($isExpanded ? 'rotate(180deg)' : 'rotate(0)')};
}
`;
@@ -0,0 +1,76 @@
import type { ReactElement } from 'react';
import { useCallback, useState } from 'react';
import { ButtonIcon } from '@fractal-ui/core';
import { DownIcon } from '@fractal-ui/library';
import { Text } from '@fractal-ui/styling';
import * as S from './QuickActions.styles';
import { ACTIONS, MAX_VISIBLE_ITEMS } from './constants';
import type { ActionItem } from './types';
const QuickActions = (): ReactElement => {
const [isExpanded, setExpanded] = useState(false);
const [showHiddenContent, setShowHiddenContent] = useState(false);
const toggleClick = useCallback(() => {
if (isExpanded) {
setExpanded(false);
} else {
setExpanded(true);
setShowHiddenContent(true);
}
}, [isExpanded]);
const handleAnimationEnd = useCallback(() => {
if (!isExpanded) {
setShowHiddenContent(false);
}
}, [isExpanded]);
const getActionItem = (action: ActionItem[]): JSX.Element[] =>
action.map(({ name, icon }) => (
<S.ActionsItem
key={name?.toString()}
role="button"
tabIndex={0}
onClick={() => {
// TODO: поведение при клике на элементе
}}
>
<S.IconBox>
<S.Icon alt="icon" src={icon} />
</S.IconBox>
<Text.P3Short as="span">{name}</Text.P3Short>
</S.ActionsItem>
));
const visibleActions = ACTIONS.slice(0, MAX_VISIBLE_ITEMS);
const hiddenActions = ACTIONS.slice(MAX_VISIBLE_ITEMS);
return (
<S.Actions>
<S.ActionsBox $isExpanded={isExpanded}>
<S.ActionSection>{getActionItem(visibleActions)}</S.ActionSection>
{showHiddenContent && (
<S.ActionSectionExpanded $isExpanded={isExpanded} onAnimationEnd={handleAnimationEnd}>
{getActionItem(hiddenActions)}
</S.ActionSectionExpanded>
)}
</S.ActionsBox>
<S.ButtonBox $isExpanded={isExpanded}>
<ButtonIcon
dataAction="toggle"
icon={DownIcon}
iconColor="text.secondary"
shape="default"
size="M"
variant="secondary"
onClick={toggleClick}
/>
</S.ButtonBox>
</S.Actions>
);
};
export default QuickActions;
Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 KiB

@@ -0,0 +1,25 @@
import icon1 from './01.png';
import icon2 from './02.png';
import icon3 from './03.png';
import icon4 from './04.png';
import icon5 from './05.png';
import icon6 from './06.png';
import icon7 from './07.png';
import icon8 from './08.png';
import icon9 from './09.png';
import icon10 from './10.png';
export const Icons = {
payment: icon1,
statement: icon2,
deposit: icon3,
application: icon4,
cash: icon5,
phoneTransfer: icon6,
qrPayment: icon7,
signPayment: icon8,
certificate: icon9,
offices: icon10,
} as const;
export type IconName = keyof typeof Icons;
@@ -0,0 +1,52 @@
import { Icons } from './assets';
import type { ActionItem } from './types';
export const ACTIONS: ActionItem[] = [
{
name: (
<>
Перевести
<br />и оплатить
</>
),
icon: Icons.payment,
},
{ name: 'Заказать выписку', icon: Icons.statement },
{ name: 'Открыть депозит', icon: Icons.deposit },
{ name: 'Создать заявку', icon: Icons.application },
{ name: 'Заказать наличные', icon: Icons.cash },
{
name: (
<>
Перевести
<br />
по телефону
</>
),
icon: Icons.phoneTransfer,
},
{
name: (
<>
Оплатить
<br />
по QR-коду
</>
),
icon: Icons.qrPayment,
},
{
name: (
<>
Платежи
<br />
на подпись
</>
),
icon: Icons.signPayment,
},
{ name: 'Оформить справку', icon: Icons.certificate },
{ name: 'Офисы и банкоматы', icon: Icons.offices },
];
export const MAX_VISIBLE_ITEMS = 5;
@@ -0,0 +1 @@
export { default } from './QuickActions';
@@ -0,0 +1,10 @@
import type { ReactNode } from 'react';
export interface ActionItem {
name: ReactNode | string;
icon: string;
}
export interface ExpandableProps {
$isExpanded: boolean;
}
@@ -0,0 +1 @@
export { default as QuickActions } from './QuickActions';