diff --git a/cypress/snapshots/b2c/components/ModalBase/ModalBase.component-test.tsx/plasma-web ModalBase -- hasBody hasClose.snap.png b/cypress/snapshots/b2c/components/ModalBase/ModalBase.component-test.tsx/plasma-web ModalBase -- hasBody hasClose.snap.png new file mode 100644 index 0000000000..c4b806144d Binary files /dev/null and b/cypress/snapshots/b2c/components/ModalBase/ModalBase.component-test.tsx/plasma-web ModalBase -- hasBody hasClose.snap.png differ diff --git a/cypress/snapshots/b2c/components/ModalBase/ModalBase.component-test.tsx/plasma-web ModalBase -- hasBody.snap.png b/cypress/snapshots/b2c/components/ModalBase/ModalBase.component-test.tsx/plasma-web ModalBase -- hasBody.snap.png new file mode 100644 index 0000000000..6246e28910 Binary files /dev/null and b/cypress/snapshots/b2c/components/ModalBase/ModalBase.component-test.tsx/plasma-web ModalBase -- hasBody.snap.png differ diff --git a/cypress/snapshots/b2c/components/ModalBase/ModalBase.component-test.tsx/plasma-web ModalBase -- withBlur.snap.png b/cypress/snapshots/b2c/components/ModalBase/ModalBase.component-test.tsx/plasma-web ModalBase -- withBlur.snap.png index 3e1c1e0f41..21fa66d977 100644 Binary files a/cypress/snapshots/b2c/components/ModalBase/ModalBase.component-test.tsx/plasma-web ModalBase -- withBlur.snap.png and b/cypress/snapshots/b2c/components/ModalBase/ModalBase.component-test.tsx/plasma-web ModalBase -- withBlur.snap.png differ diff --git a/cypress/snapshots/web/components/ModalBase/ModalBase.component-test.tsx/plasma-web ModalBase -- hasBody hasClose.snap.png b/cypress/snapshots/web/components/ModalBase/ModalBase.component-test.tsx/plasma-web ModalBase -- hasBody hasClose.snap.png new file mode 100644 index 0000000000..17f026648b Binary files /dev/null and b/cypress/snapshots/web/components/ModalBase/ModalBase.component-test.tsx/plasma-web ModalBase -- hasBody hasClose.snap.png differ diff --git a/cypress/snapshots/web/components/ModalBase/ModalBase.component-test.tsx/plasma-web ModalBase -- hasBody.snap.png b/cypress/snapshots/web/components/ModalBase/ModalBase.component-test.tsx/plasma-web ModalBase -- hasBody.snap.png new file mode 100644 index 0000000000..c5818c43be Binary files /dev/null and b/cypress/snapshots/web/components/ModalBase/ModalBase.component-test.tsx/plasma-web ModalBase -- hasBody.snap.png differ diff --git a/packages/plasma-b2c/src/components/ModalBase/Modal.config.ts b/packages/plasma-b2c/src/components/ModalBase/Modal.config.ts index edc1be758d..fd0a3ff9c7 100644 --- a/packages/plasma-b2c/src/components/ModalBase/Modal.config.ts +++ b/packages/plasma-b2c/src/components/ModalBase/Modal.config.ts @@ -7,8 +7,17 @@ export const config = { variations: { view: { default: css` - ${modalTokens.modalOverlayWithBlurColor}: rgba(35, 35, 35, 0.2); + ${modalTokens.modalOverlayWithBlurColor}: var(--overlay-blur); ${modalTokens.modalOverlayColor}: var(--overlay-soft); + ${modalTokens.modalBodyBackground}: var(--surface-solid-card); + ${modalTokens.modalBodyBorderRadius}: 1.25rem; + ${modalTokens.modalBodyPadding}: 2rem; + ${modalTokens.modalContentPadding}: 0.625rem; + ${modalTokens.modalCloseButtonRadius}: 0.375rem; + ${modalTokens.modalCloseButtonColor}: var(--text-secondary); + ${modalTokens.modalCloseButtonHoverColor}: var(--text-secondary-hover); + ${modalTokens.modalCloseButtonActiveColor}: var(--text-secondary-active); + ${modalTokens.modalOutlineFocusColor}: var(--surface-accent); `, }, }, diff --git a/packages/plasma-b2c/src/components/ModalBase/ModalBase.stories.tsx b/packages/plasma-b2c/src/components/ModalBase/ModalBase.stories.tsx index 31908f7f50..bcbafcc656 100644 --- a/packages/plasma-b2c/src/components/ModalBase/ModalBase.stories.tsx +++ b/packages/plasma-b2c/src/components/ModalBase/ModalBase.stories.tsx @@ -2,7 +2,7 @@ import React, { useCallback, useRef, useState } from 'react'; import styled, { css } from 'styled-components'; import type { Meta, StoryObj } from '@storybook/react'; import { surfaceSolid02 } from '@salutejs/plasma-tokens-b2c'; -import { InSpacingDecorator } from '@salutejs/plasma-sb-utils'; +import { disableProps, InSpacingDecorator } from '@salutejs/plasma-sb-utils'; import { SSRProvider } from '../SSRProvider'; import { Button } from '../Button'; @@ -20,6 +20,24 @@ const meta: Meta = { docs: { story: { inline: false, iframeHeight: '30rem' } }, }, argTypes: { + ...disableProps([ + 'onEscKeyDown', + 'onOverlayClick', + 'initialFocusRef', + 'focusAfterRef', + 'onClose', + 'view', + 'isOpen', + 'opened', + 'offset', + 'frame', + 'children', + 'overlay', + 'zIndex', + 'popupInfo', + 'withAnimation', + 'hasBody', + ]), placement: { options: [ 'center', @@ -35,6 +53,37 @@ const meta: Meta = { control: { type: 'select', }, + table: { defaultValue: { summary: 'center' } }, + }, + offsetX: { + control: { + type: 'number', + }, + table: { defaultValue: { summary: 0 } }, + }, + offsetY: { + control: { + type: 'number', + }, + table: { defaultValue: { summary: 0 } }, + }, + closeOnEsc: { + control: { + type: 'boolean', + }, + table: { defaultValue: { summary: true } }, + }, + closeOnOverlayClick: { + control: { + type: 'boolean', + }, + table: { defaultValue: { summary: true } }, + }, + withBlur: { + control: { + type: 'boolean', + }, + table: { defaultValue: { summary: false } }, }, }, }; @@ -48,6 +97,7 @@ type StoryModalBaseProps = { closeOnEsc: boolean; closeOnOverlayClick: boolean; withBlur: boolean; + hasClose?: boolean; }; const StyledButton = styled(Button)` @@ -98,7 +148,90 @@ const StyledModal = styled(ModalBase)` } `; -const StoryModalBaseDemo = ({ placement, offsetX, offsetY, ...rest }: StoryModalBaseProps) => { +const StoryCustomModalDemo = ({ placement, offsetX, offsetY, ...rest }: StoryModalBaseProps) => { + const [isOpenA, setIsOpenA] = useState(false); + const [isOpenB, setIsOpenB] = useState(false); + const [isOpenC, setIsOpenC] = useState(false); + + return ( + + + +
+ setIsOpenA(true)} /> +
+ setIsOpenA(false)} + opened={isOpenA} + placement={placement} + offset={[offsetX, offsetY]} + hasBody + {...rest} + > + +
+ setIsOpenB(true)} /> +
+ setIsOpenB(false)} + opened={isOpenB} + placement="left" + offset={[offsetX, offsetY]} + hasBody + {...rest} + > + +
+ setIsOpenC(true)} /> +
+ setIsOpenC(false)} + opened={isOpenC} + placement="top" + offset={[offsetX, offsetY]} + hasBody + {...rest} + > + + <>Content + +
+
+
+
+
+ ); +}; + +export const Default: StoryObj = { + args: { + placement: 'center', + withBlur: false, + closeOnEsc: true, + closeOnOverlayClick: true, + offsetX: 0, + offsetY: 0, + hasClose: true, + }, + argTypes: { + hasClose: { + control: { + type: 'boolean', + }, + }, + }, + render: (args) => , +}; + +const StoryCustomModalBaseDemo = ({ placement, offsetX, offsetY, ...rest }: StoryModalBaseProps) => { const [isOpenA, setIsOpenA] = useState(false); const [isOpenB, setIsOpenB] = useState(false); const [isOpenC, setIsOpenC] = useState(false); @@ -173,7 +306,10 @@ export const ModalBaseDemo: StoryObj = { offsetX: 0, offsetY: 0, }, - render: (args) => , + argTypes: { + ...disableProps(['hasClose']), + }, + render: (args) => , }; const StyledModalAnimation = styled(ModalBase)` @@ -295,5 +431,8 @@ export const ModalBottomAnimation: StoryObj = { offsetX: 0, offsetY: 0, }, + argTypes: { + ...disableProps(['hasClose']), + }, render: (args) => , }; diff --git a/packages/plasma-new-hope/src/components/Modal/Modal.styles.ts b/packages/plasma-new-hope/src/components/Modal/Modal.styles.ts new file mode 100644 index 0000000000..f97c142ddb --- /dev/null +++ b/packages/plasma-new-hope/src/components/Modal/Modal.styles.ts @@ -0,0 +1,59 @@ +import { styled } from '@linaria/react'; + +import { addFocus } from '../../mixins'; + +import { tokens } from './Modal.tokens'; + +export const ModalBody = styled.div` + border-radius: var(${tokens.modalBodyBorderRadius}); + padding: var(${tokens.modalBodyPadding}); + background: var(${tokens.modalBodyBackground}); + box-shadow: var(--shadow-down-soft-l); +`; + +export const ModalContent = styled.div` + position: relative; + padding: var(${tokens.modalContentPadding}); +`; + +export const CloseButton = styled.button` + top: 0; + right: 0; + + width: 1.5rem; + height: 1.5rem; + + display: flex; + align-items: center; + justify-content: center; + + border: none; + border-radius: var(${tokens.modalCloseButtonRadius}); + + padding: 0; + margin: 0; + outline: none; + + cursor: pointer; + + background: transparent; + + ${addFocus({ + outlineSize: '0.063rem', + outlineOffset: '-0.125rem', + outlineColor: `var(${tokens.modalOutlineFocusColor})`, + outlineRadius: `calc(var(${tokens.modalCloseButtonRadius}) + 0.063rem)`, + })}; + + color: var(${tokens.modalCloseButtonColor}); + + &:hover { + color: var(${tokens.modalCloseButtonHoverColor}); + } + + &:active { + color: var(${tokens.modalCloseButtonActiveColor}); + } + + position: absolute; +`; diff --git a/packages/plasma-new-hope/src/components/Modal/Modal.template-doc.mdx b/packages/plasma-new-hope/src/components/Modal/Modal.template-doc.mdx index a5e89bbab3..e613b93d34 100644 --- a/packages/plasma-new-hope/src/components/Modal/Modal.template-doc.mdx +++ b/packages/plasma-new-hope/src/components/Modal/Modal.template-doc.mdx @@ -62,6 +62,53 @@ export function App() { } ``` +## Использование стилизованной обертки + +Для использования стилизованного модального окна с отступами и крестиком для закрытия, добавьте свойство `hasBody`. +```tsx live +import React, { useState } from 'react'; +import { SSRProvider, Button, Modal, PopupProvider } from '@salutejs/{{ package }}'; + +export function App() { + const [isOpenA, setIsOpenA] = useState(false); + const [isOpenB, setIsOpenB] = useState(false); + + return ( + + +
+
+
+ setIsOpenA(false)} + opened={isOpenA} + placement="center" + offset={[0, 0]} + hasBody + > + + + Content + + +
+
+
+ ); +} +``` + ## Подключение анимации Подключение анимации аналогично тому, как это происходит в `Popup` - через свойство `withAnimation`(управление через `popupClasses`, `modalClasses`). Для добавления анимации в оверлей необходимо использовать класс `.modal-overlay` через переменную `modalClasses.overlay` из пакета. diff --git a/packages/plasma-new-hope/src/components/Modal/Modal.tokens.ts b/packages/plasma-new-hope/src/components/Modal/Modal.tokens.ts index dbeab540e3..5b82b14f4b 100644 --- a/packages/plasma-new-hope/src/components/Modal/Modal.tokens.ts +++ b/packages/plasma-new-hope/src/components/Modal/Modal.tokens.ts @@ -8,4 +8,13 @@ export const classes = { export const tokens = { modalOverlayWithBlurColor: '--plasma-modal-overlay-with-blur-color', modalOverlayColor: '--plasma-modal-overlay-color', + modalBodyBackground: '--plasma-modal-body-background', + modalBodyBorderRadius: '--plasma-modal-body-border-radius', + modalBodyPadding: '--plasma-modal-body-padding', + modalContentPadding: '--plasma-modal-content-padding', + modalOutlineFocusColor: '--plasma-modal-outline-focus-color', + modalCloseButtonRadius: '--plasma-modal-close-button-radius', + modalCloseButtonColor: '--plasma-modal-close-button-color', + modalCloseButtonHoverColor: '--plasma-modal-close-button-hover-color', + modalCloseButtonActiveColor: '--plasma-modal-close-button-active-color', }; diff --git a/packages/plasma-new-hope/src/components/Modal/Modal.tsx b/packages/plasma-new-hope/src/components/Modal/Modal.tsx index ca551bff69..d6cfe42ec5 100644 --- a/packages/plasma-new-hope/src/components/Modal/Modal.tsx +++ b/packages/plasma-new-hope/src/components/Modal/Modal.tsx @@ -6,12 +6,14 @@ import { popupConfig, usePopupContext } from '../Popup'; import { Overlay } from '../Overlay'; import { DEFAULT_Z_INDEX } from '../Popup/utils'; import { useFocusTrap } from '../../hooks'; +import { IconClose } from '../_Icon/Icons/IconClose'; import { classes, tokens } from './Modal.tokens'; import { ModalProps } from './Modal.types'; import { useModal } from './hooks'; import { base as viewCSS } from './variations/_view/base'; import { getIdLastModal } from './ModalContext'; +import { CloseButton, ModalBody, ModalContent } from './Modal.styles'; // issue #823 const Popup = component(popupConfig); @@ -40,11 +42,14 @@ export const modalRoot = (Root: RootProps) => view, opened, isOpen, + hasBody, + hasClose, ...rest }, outerRootRef, ) => { const innerIsOpen = Boolean(isOpen || opened); + const innerHasClose = (hasClose === undefined && hasBody) || hasClose; const trapRef = useFocusTrap(true, initialFocusRef, focusAfterRef, true); const popupController = usePopupContext(); @@ -84,6 +89,18 @@ export const modalRoot = (Root: RootProps) => [closeOnOverlayClick, onOverlayClick, onClose], ); + const overlayNode = ( + + ); + return ( ) => popupInfo={modalInfo} withAnimation={withAnimation} zIndex={zIndex} - overlay={ - - - - } + overlay={hasBody ? overlayNode : {overlayNode}} {...rest} > - {children} + {hasBody ? ( + + + + {innerHasClose && ( + + + + )} + {children} + + + + ) : ( + <>{children} + )} ); }, diff --git a/packages/plasma-new-hope/src/components/Modal/Modal.types.ts b/packages/plasma-new-hope/src/components/Modal/Modal.types.ts index e8c503f874..a1bb14b3e0 100644 --- a/packages/plasma-new-hope/src/components/Modal/Modal.types.ts +++ b/packages/plasma-new-hope/src/components/Modal/Modal.types.ts @@ -1,6 +1,22 @@ import { PopupHookArgs, PopupProps, PopupRootProps } from '../Popup/Popup.types'; -export interface ModalProps extends PopupProps { +export type ModalBodyProps = + | { + /** + * Оборачивает children в стилизованную обертку + */ + hasBody: true; + /** + * Отображает кнопку с крестиком для закрытия + */ + hasClose?: boolean; + } + | { + hasBody?: never; + hasClose?: never; + }; + +export interface CommonModalProps extends PopupProps { /** * Нужно ли применять blur для подложки. */ @@ -37,6 +53,8 @@ export interface ModalProps extends PopupProps { view?: string; } +export type ModalProps = CommonModalProps & ModalBodyProps; + export type ModalBaseRootProps = PopupRootProps & Pick; export type ModalOverlayProps = Pick & diff --git a/packages/plasma-new-hope/src/components/Modal/index.ts b/packages/plasma-new-hope/src/components/Modal/index.ts index fa65055a9e..5d65da0299 100644 --- a/packages/plasma-new-hope/src/components/Modal/index.ts +++ b/packages/plasma-new-hope/src/components/Modal/index.ts @@ -1,3 +1,3 @@ export { modalConfig, modalRoot } from './Modal'; export { classes as modalClasses, tokens as modalTokens } from './Modal.tokens'; -export type { ModalProps, ModalOverlayProps } from './Modal.types'; +export type { ModalBodyProps, CommonModalProps, ModalProps, ModalOverlayProps } from './Modal.types'; diff --git a/packages/plasma-new-hope/src/examples/plasma_b2c/components/Modal/Modal.config.ts b/packages/plasma-new-hope/src/examples/plasma_b2c/components/Modal/Modal.config.ts index 066fff6029..e964ad353f 100644 --- a/packages/plasma-new-hope/src/examples/plasma_b2c/components/Modal/Modal.config.ts +++ b/packages/plasma-new-hope/src/examples/plasma_b2c/components/Modal/Modal.config.ts @@ -11,6 +11,15 @@ export const config = { default: css` ${modalTokens.modalOverlayWithBlurColor}: var(--overlay-blur); ${modalTokens.modalOverlayColor}: var(--overlay-soft); + ${modalTokens.modalBodyBackground}: var(--surface-solid-card); + ${modalTokens.modalBodyBorderRadius}: 1.25rem; + ${modalTokens.modalBodyPadding}: 2rem; + ${modalTokens.modalContentPadding}: 0.625rem; + ${modalTokens.modalCloseButtonRadius}: 0.375rem; + ${modalTokens.modalCloseButtonColor}: var(--text-secondary); + ${modalTokens.modalCloseButtonHoverColor}: var(--text-secondary-hover); + ${modalTokens.modalCloseButtonActiveColor}: var(--text-secondary-active); + ${modalTokens.modalOutlineFocusColor}: var(--surface-accent); `, }, }, diff --git a/packages/plasma-new-hope/src/examples/plasma_b2c/components/Modal/Modal.stories.tsx b/packages/plasma-new-hope/src/examples/plasma_b2c/components/Modal/Modal.stories.tsx index a6f2c1b0b9..5c9f6ccdff 100644 --- a/packages/plasma-new-hope/src/examples/plasma_b2c/components/Modal/Modal.stories.tsx +++ b/packages/plasma-new-hope/src/examples/plasma_b2c/components/Modal/Modal.stories.tsx @@ -3,6 +3,7 @@ import { styled } from '@linaria/react'; import type { ComponentProps } from 'react'; import type { StoryObj, Meta } from '@storybook/react'; import { SSRProvider } from '@salutejs/plasma-core'; +import { disableProps } from '@salutejs/plasma-sb-utils'; import { PopupProvider, popupClasses } from '../Popup/Popup'; import { Button } from '../Button/Button'; @@ -18,6 +19,7 @@ export default { docs: { story: { inline: false, iframeHeight: '30rem' } }, }, argTypes: { + ...disableProps(['hasBody']), placement: { options: [ 'center', @@ -75,6 +77,7 @@ type StoryModalProps = ComponentProps & { closeOnEsc: boolean; closeOnOverlayClick: boolean; withBlur: boolean; + hasClose?: boolean; }; const StyledButton = styled(Button)` @@ -135,6 +138,74 @@ const StoryModalDemo = ({ placement, offsetX, offsetY, ...rest }: StoryModalProp const [isOpenB, setIsOpenB] = useState(false); const [isOpenC, setIsOpenC] = useState(false); + return ( + + + + + setIsOpenA(true)} /> + + setIsOpenA(false)} + opened={isOpenA} + placement={placement} + offset={[offsetX, offsetY]} + hasBody + {...rest} + > + + + setIsOpenB(true)} /> + + setIsOpenB(false)} + opened={isOpenB} + placement="left" + offset={[offsetX, offsetY]} + hasBody + {...rest} + > + + + setIsOpenC(true)} /> + + setIsOpenC(false)} + opened={isOpenC} + placement="top" + offset={[offsetX, offsetY]} + hasBody + {...rest} + > +
+ + <>Content +
+
+
+
+
+
+
+ ); +}; + +const StoryCustomModalDemo = ({ placement, offsetX, offsetY, ...rest }: StoryModalProps) => { + const [isOpenA, setIsOpenA] = useState(false); + const [isOpenB, setIsOpenB] = useState(false); + const [isOpenC, setIsOpenC] = useState(false); + return ( @@ -199,7 +270,7 @@ const StoryModalDemo = ({ placement, offsetX, offsetY, ...rest }: StoryModalProp ); }; -export const ModalDemo: StoryObj = { +export const Default: StoryObj = { args: { placement: 'center', withBlur: false, @@ -207,10 +278,30 @@ export const ModalDemo: StoryObj = { closeOnOverlayClick: true, offsetX: 0, offsetY: 0, + hasClose: true, + }, + argTypes: { + hasClose: { + control: { + type: 'boolean', + }, + }, }, render: (args) => , }; +export const CustomModalDemo: StoryObj = { + args: { + placement: 'center', + withBlur: false, + closeOnEsc: true, + closeOnOverlayClick: true, + offsetX: 0, + offsetY: 0, + }, + render: (args) => , +}; + const StyledModalAnimation = styled(Modal)` /* stylelint-disable */ && .${popupClasses.root} { diff --git a/packages/plasma-new-hope/src/examples/plasma_web/components/Modal/Modal.config.ts b/packages/plasma-new-hope/src/examples/plasma_web/components/Modal/Modal.config.ts index 066fff6029..e964ad353f 100644 --- a/packages/plasma-new-hope/src/examples/plasma_web/components/Modal/Modal.config.ts +++ b/packages/plasma-new-hope/src/examples/plasma_web/components/Modal/Modal.config.ts @@ -11,6 +11,15 @@ export const config = { default: css` ${modalTokens.modalOverlayWithBlurColor}: var(--overlay-blur); ${modalTokens.modalOverlayColor}: var(--overlay-soft); + ${modalTokens.modalBodyBackground}: var(--surface-solid-card); + ${modalTokens.modalBodyBorderRadius}: 1.25rem; + ${modalTokens.modalBodyPadding}: 2rem; + ${modalTokens.modalContentPadding}: 0.625rem; + ${modalTokens.modalCloseButtonRadius}: 0.375rem; + ${modalTokens.modalCloseButtonColor}: var(--text-secondary); + ${modalTokens.modalCloseButtonHoverColor}: var(--text-secondary-hover); + ${modalTokens.modalCloseButtonActiveColor}: var(--text-secondary-active); + ${modalTokens.modalOutlineFocusColor}: var(--surface-accent); `, }, }, diff --git a/packages/plasma-new-hope/src/examples/plasma_web/components/Modal/Modal.stories.tsx b/packages/plasma-new-hope/src/examples/plasma_web/components/Modal/Modal.stories.tsx index f4c45bf766..e6d3abcfd5 100644 --- a/packages/plasma-new-hope/src/examples/plasma_web/components/Modal/Modal.stories.tsx +++ b/packages/plasma-new-hope/src/examples/plasma_web/components/Modal/Modal.stories.tsx @@ -3,6 +3,7 @@ import { styled } from '@linaria/react'; import type { ComponentProps } from 'react'; import type { StoryObj, Meta } from '@storybook/react'; import { SSRProvider } from '@salutejs/plasma-core'; +import { disableProps } from '@salutejs/plasma-sb-utils'; import { PopupProvider, popupClasses } from '../Popup/Popup'; import { Button } from '../Button/Button'; @@ -18,6 +19,7 @@ export default { docs: { story: { inline: false, iframeHeight: '30rem' } }, }, argTypes: { + ...disableProps(['hasBody']), placement: { options: [ 'center', @@ -75,6 +77,7 @@ type StoryModalProps = ComponentProps & { closeOnEsc: boolean; closeOnOverlayClick: boolean; withBlur: boolean; + hasClose?: boolean; }; const StyledButton = styled(Button)` @@ -135,6 +138,72 @@ const StoryModalDemo = ({ placement, offsetX, offsetY, ...rest }: StoryModalProp const [isOpenB, setIsOpenB] = useState(false); const [isOpenC, setIsOpenC] = useState(false); + return ( + + + + + setIsOpenA(true)} /> + + setIsOpenA(false)} + opened={isOpenA} + placement={placement} + offset={[offsetX, offsetY]} + hasBody + {...rest} + > + + + setIsOpenB(true)} /> + + setIsOpenB(false)} + opened={isOpenB} + placement="left" + offset={[offsetX, offsetY]} + hasBody + {...rest} + > + + + setIsOpenC(true)} /> + + setIsOpenC(false)} + opened={isOpenC} + placement="top" + offset={[offsetX, offsetY]} + hasBody + {...rest} + > + + <>Content + + + + + + + ); +}; + +const StoryCustomModalDemo = ({ placement, offsetX, offsetY, ...rest }: StoryModalProps) => { + const [isOpenA, setIsOpenA] = useState(false); + const [isOpenB, setIsOpenB] = useState(false); + const [isOpenC, setIsOpenC] = useState(false); + return ( @@ -199,7 +268,7 @@ const StoryModalDemo = ({ placement, offsetX, offsetY, ...rest }: StoryModalProp ); }; -export const ModalDemo: StoryObj = { +export const Default: StoryObj = { args: { placement: 'center', withBlur: false, @@ -207,10 +276,30 @@ export const ModalDemo: StoryObj = { closeOnOverlayClick: true, offsetX: 0, offsetY: 0, + hasClose: true, + }, + argTypes: { + hasClose: { + control: { + type: 'boolean', + }, + }, }, render: (args) => , }; +export const CustomModalDemo: StoryObj = { + args: { + placement: 'center', + withBlur: false, + closeOnEsc: true, + closeOnOverlayClick: true, + offsetX: 0, + offsetY: 0, + }, + render: (args) => , +}; + const StyledModalAnimation = styled(Modal)` /* stylelint-disable */ && .${popupClasses.root} { diff --git a/packages/plasma-web/src/components/ModalBase/Modal.config.ts b/packages/plasma-web/src/components/ModalBase/Modal.config.ts index edc1be758d..8b107b98ea 100644 --- a/packages/plasma-web/src/components/ModalBase/Modal.config.ts +++ b/packages/plasma-web/src/components/ModalBase/Modal.config.ts @@ -9,6 +9,15 @@ export const config = { default: css` ${modalTokens.modalOverlayWithBlurColor}: rgba(35, 35, 35, 0.2); ${modalTokens.modalOverlayColor}: var(--overlay-soft); + ${modalTokens.modalBodyBackground}: var(--surface-solid-card); + ${modalTokens.modalBodyBorderRadius}: 1.25rem; + ${modalTokens.modalBodyPadding}: 2rem; + ${modalTokens.modalContentPadding}: 0.625rem; + ${modalTokens.modalCloseButtonRadius}: 0.375rem; + ${modalTokens.modalCloseButtonColor}: var(--text-secondary); + ${modalTokens.modalCloseButtonHoverColor}: var(--text-secondary-hover); + ${modalTokens.modalCloseButtonActiveColor}: var(--text-secondary-active); + ${modalTokens.modalOutlineFocusColor}: var(--surface-accent); `, }, }, diff --git a/packages/plasma-web/src/components/ModalBase/ModalBase.component-test.tsx b/packages/plasma-web/src/components/ModalBase/ModalBase.component-test.tsx index a2d549ef8a..254a583db9 100644 --- a/packages/plasma-web/src/components/ModalBase/ModalBase.component-test.tsx +++ b/packages/plasma-web/src/components/ModalBase/ModalBase.component-test.tsx @@ -49,6 +49,37 @@ describe('plasma-web: ModalBase', () => { ); } + function DemoWithBody({ + open = false, + withBlur = false, + placement, + hasClose, + }: { + open?: boolean; + withBlur?: boolean; + placement?: string; + hasClose?: boolean; + }) { + const [isOpen, setIsOpen] = React.useState(open); + + return ( + + +
+ setIsOpenB(true)} /> +
+ setIsOpenB(false)} + opened={isOpenB} + placement="left" + offset={[offsetX, offsetY]} + hasBody + {...rest} + > + +
+ setIsOpenC(true)} /> +
+ setIsOpenC(false)} + opened={isOpenC} + placement="top" + offset={[offsetX, offsetY]} + hasBody + {...rest} + > + + <>Content + +
+ +
+
+
+ ); +}; + +export const Default: StoryObj = { + args: { + placement: 'center', + withBlur: false, + closeOnEsc: true, + closeOnOverlayClick: true, + offsetX: 0, + offsetY: 0, + hasClose: true, + }, + argTypes: { + hasClose: { + control: { + type: 'boolean', + }, + }, + }, + render: (args) => , +}; + +const StoryCustomModalBaseDemo = ({ placement, offsetX, offsetY, ...rest }: StoryModalBaseProps) => { const [isOpenA, setIsOpenA] = useState(false); const [isOpenB, setIsOpenB] = useState(false); const [isOpenC, setIsOpenC] = useState(false); @@ -173,7 +306,10 @@ export const ModalBaseDemo: StoryObj = { offsetX: 0, offsetY: 0, }, - render: (args) => , + argTypes: { + ...disableProps(['hasClose']), + }, + render: (args) => , }; const StyledModalAnimation = styled(ModalBase)` @@ -295,5 +431,8 @@ export const ModalBottomAnimation: StoryObj = { offsetX: 0, offsetY: 0, }, + argTypes: { + ...disableProps(['hasClose']), + }, render: (args) => , }; diff --git a/packages/sdds-cs/src/components/Modal/Modal.config.ts b/packages/sdds-cs/src/components/Modal/Modal.config.ts index edc1be758d..8b107b98ea 100644 --- a/packages/sdds-cs/src/components/Modal/Modal.config.ts +++ b/packages/sdds-cs/src/components/Modal/Modal.config.ts @@ -9,6 +9,15 @@ export const config = { default: css` ${modalTokens.modalOverlayWithBlurColor}: rgba(35, 35, 35, 0.2); ${modalTokens.modalOverlayColor}: var(--overlay-soft); + ${modalTokens.modalBodyBackground}: var(--surface-solid-card); + ${modalTokens.modalBodyBorderRadius}: 1.25rem; + ${modalTokens.modalBodyPadding}: 2rem; + ${modalTokens.modalContentPadding}: 0.625rem; + ${modalTokens.modalCloseButtonRadius}: 0.375rem; + ${modalTokens.modalCloseButtonColor}: var(--text-secondary); + ${modalTokens.modalCloseButtonHoverColor}: var(--text-secondary-hover); + ${modalTokens.modalCloseButtonActiveColor}: var(--text-secondary-active); + ${modalTokens.modalOutlineFocusColor}: var(--surface-accent); `, }, }, diff --git a/packages/sdds-cs/src/components/Modal/Modal.stories.tsx b/packages/sdds-cs/src/components/Modal/Modal.stories.tsx index ee950ba52a..bab20ebd3a 100644 --- a/packages/sdds-cs/src/components/Modal/Modal.stories.tsx +++ b/packages/sdds-cs/src/components/Modal/Modal.stories.tsx @@ -1,7 +1,8 @@ import React, { useCallback, useRef, useState } from 'react'; -import styled, { css } from 'styled-components'; +import type { ComponentProps } from 'react'; +import styled from 'styled-components'; import type { Meta, StoryObj } from '@storybook/react'; -import { disableProps, InSpacingDecorator } from '@salutejs/plasma-sb-utils'; +import { InSpacingDecorator, disableProps } from '@salutejs/plasma-sb-utils'; import { SSRProvider } from '../SSRProvider'; import { Button } from '../Button'; @@ -18,7 +19,7 @@ const meta: Meta = { docs: { story: { inline: false, iframeHeight: '30rem' } }, }, argTypes: { - ...disableProps(['view']), + ...disableProps(['view', 'hasBody']), placement: { options: [ 'center', @@ -34,19 +35,51 @@ const meta: Meta = { control: { type: 'select', }, + table: { defaultValue: { summary: 'center' } }, + }, + offsetX: { + control: { + type: 'number', + }, + table: { defaultValue: { summary: 0 } }, + }, + offsetY: { + control: { + type: 'number', + }, + table: { defaultValue: { summary: 0 } }, + }, + closeOnEsc: { + control: { + type: 'boolean', + }, + table: { defaultValue: { summary: true } }, + }, + closeOnOverlayClick: { + control: { + type: 'boolean', + }, + table: { defaultValue: { summary: true } }, + }, + withBlur: { + control: { + type: 'boolean', + }, + table: { defaultValue: { summary: false } }, }, }, -}; +} as Meta; export default meta; -type StoryModalProps = { +type StoryModalProps = ComponentProps & { placement: string; offsetX: number; offsetY: number; closeOnEsc: boolean; closeOnOverlayClick: boolean; withBlur: boolean; + hasClose?: boolean; }; const StyledButton = styled(Button)` @@ -64,6 +97,11 @@ const Content = styled.div` border-radius: 1rem; `; +const ButtonWrapper = styled.div` + display: flex; + flex-direction: column; +`; + const StyledModal = styled(Modal)` && > .${popupClasses.root}, .${modalClasses.overlay} { animation: fadeIn 1s forwards; @@ -99,19 +137,86 @@ const StyledModal = styled(Modal)` `; const StoryModalDemo = ({ placement, offsetX, offsetY, ...rest }: StoryModalProps) => { - const [isOpenA, setIsOpenA] = React.useState(false); - const [isOpenB, setIsOpenB] = React.useState(false); - const [isOpenC, setIsOpenC] = React.useState(false); + const [isOpenA, setIsOpenA] = useState(false); + const [isOpenB, setIsOpenB] = useState(false); + const [isOpenC, setIsOpenC] = useState(false); + + return ( + + + + + setIsOpenA(true)} /> + + setIsOpenA(false)} + opened={isOpenA} + placement={placement} + offset={[offsetX, offsetY]} + hasBody + {...rest} + > + + + setIsOpenB(true)} /> + + setIsOpenB(false)} + opened={isOpenB} + placement="left" + offset={[offsetX, offsetY]} + hasBody + {...rest} + > + + + setIsOpenC(true)} /> + + setIsOpenC(false)} + opened={isOpenC} + placement="top" + offset={[offsetX, offsetY]} + hasBody + {...rest} + > + + <>Content + + + + + + + ); +}; + +const StoryCustomModalDemo = ({ placement, offsetX, offsetY, ...rest }: StoryModalProps) => { + const [isOpenA, setIsOpenA] = useState(false); + const [isOpenB, setIsOpenB] = useState(false); + const [isOpenC, setIsOpenC] = useState(false); return ( -
+ setIsOpenA(true)} /> -
+ setIsOpenA(false)} opened={isOpenA} @@ -121,11 +226,12 @@ const StoryModalDemo = ({ placement, offsetX, offsetY, ...rest }: StoryModalProp > -
+ setIsOpenB(true)} /> -
+ setIsOpenB(false)} opened={isOpenB} placement="left" @@ -136,11 +242,12 @@ const StoryModalDemo = ({ placement, offsetX, offsetY, ...rest }: StoryModalProp -
+ setIsOpenC(true)} /> -
+ setIsOpenC(false)} opened={isOpenC} placement="top" @@ -164,7 +271,7 @@ const StoryModalDemo = ({ placement, offsetX, offsetY, ...rest }: StoryModalProp ); }; -export const ModalDemo: StoryObj = { +export const Default: StoryObj = { args: { placement: 'center', withBlur: false, @@ -172,10 +279,30 @@ export const ModalDemo: StoryObj = { closeOnOverlayClick: true, offsetX: 0, offsetY: 0, + hasClose: true, + }, + argTypes: { + hasClose: { + control: { + type: 'boolean', + }, + }, }, render: (args) => , }; +export const CustomModalDemo: StoryObj = { + args: { + placement: 'center', + withBlur: false, + closeOnEsc: true, + closeOnOverlayClick: true, + offsetX: 0, + offsetY: 0, + }, + render: (args) => , +}; + const StyledModalAnimation = styled(Modal)` /* stylelint-disable */ ${({ placement }) => diff --git a/packages/sdds-dfa/src/components/Modal/Modal.config.ts b/packages/sdds-dfa/src/components/Modal/Modal.config.ts index edc1be758d..8b107b98ea 100644 --- a/packages/sdds-dfa/src/components/Modal/Modal.config.ts +++ b/packages/sdds-dfa/src/components/Modal/Modal.config.ts @@ -9,6 +9,15 @@ export const config = { default: css` ${modalTokens.modalOverlayWithBlurColor}: rgba(35, 35, 35, 0.2); ${modalTokens.modalOverlayColor}: var(--overlay-soft); + ${modalTokens.modalBodyBackground}: var(--surface-solid-card); + ${modalTokens.modalBodyBorderRadius}: 1.25rem; + ${modalTokens.modalBodyPadding}: 2rem; + ${modalTokens.modalContentPadding}: 0.625rem; + ${modalTokens.modalCloseButtonRadius}: 0.375rem; + ${modalTokens.modalCloseButtonColor}: var(--text-secondary); + ${modalTokens.modalCloseButtonHoverColor}: var(--text-secondary-hover); + ${modalTokens.modalCloseButtonActiveColor}: var(--text-secondary-active); + ${modalTokens.modalOutlineFocusColor}: var(--surface-accent); `, }, }, diff --git a/packages/sdds-dfa/src/components/Modal/Modal.stories.tsx b/packages/sdds-dfa/src/components/Modal/Modal.stories.tsx index e6fc6db601..ed100b6801 100644 --- a/packages/sdds-dfa/src/components/Modal/Modal.stories.tsx +++ b/packages/sdds-dfa/src/components/Modal/Modal.stories.tsx @@ -1,7 +1,8 @@ import React, { useCallback, useRef, useState } from 'react'; -import styled, { css } from 'styled-components'; +import type { ComponentProps } from 'react'; +import styled from 'styled-components'; import type { Meta, StoryObj } from '@storybook/react'; -import { InSpacingDecorator } from '@salutejs/plasma-sb-utils'; +import { InSpacingDecorator, disableProps } from '@salutejs/plasma-sb-utils'; import { SSRProvider } from '../SSRProvider'; import { Button } from '../Button'; @@ -18,6 +19,7 @@ const meta: Meta = { docs: { story: { inline: false, iframeHeight: '30rem' } }, }, argTypes: { + ...disableProps(['hasBody']), placement: { options: [ 'center', @@ -33,19 +35,51 @@ const meta: Meta = { control: { type: 'select', }, + table: { defaultValue: { summary: 'center' } }, + }, + offsetX: { + control: { + type: 'number', + }, + table: { defaultValue: { summary: 0 } }, + }, + offsetY: { + control: { + type: 'number', + }, + table: { defaultValue: { summary: 0 } }, + }, + closeOnEsc: { + control: { + type: 'boolean', + }, + table: { defaultValue: { summary: true } }, + }, + closeOnOverlayClick: { + control: { + type: 'boolean', + }, + table: { defaultValue: { summary: true } }, + }, + withBlur: { + control: { + type: 'boolean', + }, + table: { defaultValue: { summary: false } }, }, }, -}; +} as Meta; export default meta; -type StoryModalProps = { +type StoryModalProps = ComponentProps & { placement: string; offsetX: number; offsetY: number; closeOnEsc: boolean; closeOnOverlayClick: boolean; withBlur: boolean; + hasClose?: boolean; }; const StyledButton = styled(Button)` @@ -62,6 +96,11 @@ const Content = styled.div` padding: 1rem; `; +const ButtonWrapper = styled.div` + display: flex; + flex-direction: column; +`; + const StyledModal = styled(Modal)` && > .${popupClasses.root}, .${modalClasses.overlay} { animation: fadeIn 1s forwards; @@ -97,19 +136,86 @@ const StyledModal = styled(Modal)` `; const StoryModalDemo = ({ placement, offsetX, offsetY, ...rest }: StoryModalProps) => { - const [isOpenA, setIsOpenA] = React.useState(false); - const [isOpenB, setIsOpenB] = React.useState(false); - const [isOpenC, setIsOpenC] = React.useState(false); + const [isOpenA, setIsOpenA] = useState(false); + const [isOpenB, setIsOpenB] = useState(false); + const [isOpenC, setIsOpenC] = useState(false); + + return ( + + + + + setIsOpenA(true)} /> + + setIsOpenA(false)} + opened={isOpenA} + placement={placement} + offset={[offsetX, offsetY]} + hasBody + {...rest} + > + + + setIsOpenB(true)} /> + + setIsOpenB(false)} + opened={isOpenB} + placement="left" + offset={[offsetX, offsetY]} + hasBody + {...rest} + > + + + setIsOpenC(true)} /> + + setIsOpenC(false)} + opened={isOpenC} + placement="top" + offset={[offsetX, offsetY]} + hasBody + {...rest} + > + + <>Content + + + + + + + ); +}; + +const StoryCustomModalDemo = ({ placement, offsetX, offsetY, ...rest }: StoryModalProps) => { + const [isOpenA, setIsOpenA] = useState(false); + const [isOpenB, setIsOpenB] = useState(false); + const [isOpenC, setIsOpenC] = useState(false); return ( -
+ setIsOpenA(true)} /> -
+ setIsOpenA(false)} opened={isOpenA} @@ -119,11 +225,12 @@ const StoryModalDemo = ({ placement, offsetX, offsetY, ...rest }: StoryModalProp > -
+ setIsOpenB(true)} /> -
+ setIsOpenB(false)} opened={isOpenB} placement="left" @@ -134,11 +241,12 @@ const StoryModalDemo = ({ placement, offsetX, offsetY, ...rest }: StoryModalProp -
+ setIsOpenC(true)} /> -
+ setIsOpenC(false)} opened={isOpenC} placement="top" @@ -162,7 +270,7 @@ const StoryModalDemo = ({ placement, offsetX, offsetY, ...rest }: StoryModalProp ); }; -export const ModalDemo: StoryObj = { +export const Default: StoryObj = { args: { placement: 'center', withBlur: false, @@ -170,10 +278,30 @@ export const ModalDemo: StoryObj = { closeOnOverlayClick: true, offsetX: 0, offsetY: 0, + hasClose: true, + }, + argTypes: { + hasClose: { + control: { + type: 'boolean', + }, + }, }, render: (args) => , }; +export const CustomModalDemo: StoryObj = { + args: { + placement: 'center', + withBlur: false, + closeOnEsc: true, + closeOnOverlayClick: true, + offsetX: 0, + offsetY: 0, + }, + render: (args) => , +}; + const StyledModalAnimation = styled(Modal)` /* stylelint-disable */ ${({ placement }) => diff --git a/packages/sdds-finportal/src/components/Modal/Modal.config.ts b/packages/sdds-finportal/src/components/Modal/Modal.config.ts index edc1be758d..8b107b98ea 100644 --- a/packages/sdds-finportal/src/components/Modal/Modal.config.ts +++ b/packages/sdds-finportal/src/components/Modal/Modal.config.ts @@ -9,6 +9,15 @@ export const config = { default: css` ${modalTokens.modalOverlayWithBlurColor}: rgba(35, 35, 35, 0.2); ${modalTokens.modalOverlayColor}: var(--overlay-soft); + ${modalTokens.modalBodyBackground}: var(--surface-solid-card); + ${modalTokens.modalBodyBorderRadius}: 1.25rem; + ${modalTokens.modalBodyPadding}: 2rem; + ${modalTokens.modalContentPadding}: 0.625rem; + ${modalTokens.modalCloseButtonRadius}: 0.375rem; + ${modalTokens.modalCloseButtonColor}: var(--text-secondary); + ${modalTokens.modalCloseButtonHoverColor}: var(--text-secondary-hover); + ${modalTokens.modalCloseButtonActiveColor}: var(--text-secondary-active); + ${modalTokens.modalOutlineFocusColor}: var(--surface-accent); `, }, }, diff --git a/packages/sdds-finportal/src/components/Modal/Modal.stories.tsx b/packages/sdds-finportal/src/components/Modal/Modal.stories.tsx index e6fc6db601..ed100b6801 100644 --- a/packages/sdds-finportal/src/components/Modal/Modal.stories.tsx +++ b/packages/sdds-finportal/src/components/Modal/Modal.stories.tsx @@ -1,7 +1,8 @@ import React, { useCallback, useRef, useState } from 'react'; -import styled, { css } from 'styled-components'; +import type { ComponentProps } from 'react'; +import styled from 'styled-components'; import type { Meta, StoryObj } from '@storybook/react'; -import { InSpacingDecorator } from '@salutejs/plasma-sb-utils'; +import { InSpacingDecorator, disableProps } from '@salutejs/plasma-sb-utils'; import { SSRProvider } from '../SSRProvider'; import { Button } from '../Button'; @@ -18,6 +19,7 @@ const meta: Meta = { docs: { story: { inline: false, iframeHeight: '30rem' } }, }, argTypes: { + ...disableProps(['hasBody']), placement: { options: [ 'center', @@ -33,19 +35,51 @@ const meta: Meta = { control: { type: 'select', }, + table: { defaultValue: { summary: 'center' } }, + }, + offsetX: { + control: { + type: 'number', + }, + table: { defaultValue: { summary: 0 } }, + }, + offsetY: { + control: { + type: 'number', + }, + table: { defaultValue: { summary: 0 } }, + }, + closeOnEsc: { + control: { + type: 'boolean', + }, + table: { defaultValue: { summary: true } }, + }, + closeOnOverlayClick: { + control: { + type: 'boolean', + }, + table: { defaultValue: { summary: true } }, + }, + withBlur: { + control: { + type: 'boolean', + }, + table: { defaultValue: { summary: false } }, }, }, -}; +} as Meta; export default meta; -type StoryModalProps = { +type StoryModalProps = ComponentProps & { placement: string; offsetX: number; offsetY: number; closeOnEsc: boolean; closeOnOverlayClick: boolean; withBlur: boolean; + hasClose?: boolean; }; const StyledButton = styled(Button)` @@ -62,6 +96,11 @@ const Content = styled.div` padding: 1rem; `; +const ButtonWrapper = styled.div` + display: flex; + flex-direction: column; +`; + const StyledModal = styled(Modal)` && > .${popupClasses.root}, .${modalClasses.overlay} { animation: fadeIn 1s forwards; @@ -97,19 +136,86 @@ const StyledModal = styled(Modal)` `; const StoryModalDemo = ({ placement, offsetX, offsetY, ...rest }: StoryModalProps) => { - const [isOpenA, setIsOpenA] = React.useState(false); - const [isOpenB, setIsOpenB] = React.useState(false); - const [isOpenC, setIsOpenC] = React.useState(false); + const [isOpenA, setIsOpenA] = useState(false); + const [isOpenB, setIsOpenB] = useState(false); + const [isOpenC, setIsOpenC] = useState(false); + + return ( + + + + + setIsOpenA(true)} /> + + setIsOpenA(false)} + opened={isOpenA} + placement={placement} + offset={[offsetX, offsetY]} + hasBody + {...rest} + > + + + setIsOpenB(true)} /> + + setIsOpenB(false)} + opened={isOpenB} + placement="left" + offset={[offsetX, offsetY]} + hasBody + {...rest} + > + + + setIsOpenC(true)} /> + + setIsOpenC(false)} + opened={isOpenC} + placement="top" + offset={[offsetX, offsetY]} + hasBody + {...rest} + > + + <>Content + + + + + + + ); +}; + +const StoryCustomModalDemo = ({ placement, offsetX, offsetY, ...rest }: StoryModalProps) => { + const [isOpenA, setIsOpenA] = useState(false); + const [isOpenB, setIsOpenB] = useState(false); + const [isOpenC, setIsOpenC] = useState(false); return ( -
+ setIsOpenA(true)} /> -
+ setIsOpenA(false)} opened={isOpenA} @@ -119,11 +225,12 @@ const StoryModalDemo = ({ placement, offsetX, offsetY, ...rest }: StoryModalProp > -
+ setIsOpenB(true)} /> -
+ setIsOpenB(false)} opened={isOpenB} placement="left" @@ -134,11 +241,12 @@ const StoryModalDemo = ({ placement, offsetX, offsetY, ...rest }: StoryModalProp -
+ setIsOpenC(true)} /> -
+ setIsOpenC(false)} opened={isOpenC} placement="top" @@ -162,7 +270,7 @@ const StoryModalDemo = ({ placement, offsetX, offsetY, ...rest }: StoryModalProp ); }; -export const ModalDemo: StoryObj = { +export const Default: StoryObj = { args: { placement: 'center', withBlur: false, @@ -170,10 +278,30 @@ export const ModalDemo: StoryObj = { closeOnOverlayClick: true, offsetX: 0, offsetY: 0, + hasClose: true, + }, + argTypes: { + hasClose: { + control: { + type: 'boolean', + }, + }, }, render: (args) => , }; +export const CustomModalDemo: StoryObj = { + args: { + placement: 'center', + withBlur: false, + closeOnEsc: true, + closeOnOverlayClick: true, + offsetX: 0, + offsetY: 0, + }, + render: (args) => , +}; + const StyledModalAnimation = styled(Modal)` /* stylelint-disable */ ${({ placement }) => diff --git a/packages/sdds-insol/src/components/Modal/Modal.config.ts b/packages/sdds-insol/src/components/Modal/Modal.config.ts index edc1be758d..8b107b98ea 100644 --- a/packages/sdds-insol/src/components/Modal/Modal.config.ts +++ b/packages/sdds-insol/src/components/Modal/Modal.config.ts @@ -9,6 +9,15 @@ export const config = { default: css` ${modalTokens.modalOverlayWithBlurColor}: rgba(35, 35, 35, 0.2); ${modalTokens.modalOverlayColor}: var(--overlay-soft); + ${modalTokens.modalBodyBackground}: var(--surface-solid-card); + ${modalTokens.modalBodyBorderRadius}: 1.25rem; + ${modalTokens.modalBodyPadding}: 2rem; + ${modalTokens.modalContentPadding}: 0.625rem; + ${modalTokens.modalCloseButtonRadius}: 0.375rem; + ${modalTokens.modalCloseButtonColor}: var(--text-secondary); + ${modalTokens.modalCloseButtonHoverColor}: var(--text-secondary-hover); + ${modalTokens.modalCloseButtonActiveColor}: var(--text-secondary-active); + ${modalTokens.modalOutlineFocusColor}: var(--surface-accent); `, }, }, diff --git a/packages/sdds-insol/src/components/Modal/Modal.stories.tsx b/packages/sdds-insol/src/components/Modal/Modal.stories.tsx index e6fc6db601..ed100b6801 100644 --- a/packages/sdds-insol/src/components/Modal/Modal.stories.tsx +++ b/packages/sdds-insol/src/components/Modal/Modal.stories.tsx @@ -1,7 +1,8 @@ import React, { useCallback, useRef, useState } from 'react'; -import styled, { css } from 'styled-components'; +import type { ComponentProps } from 'react'; +import styled from 'styled-components'; import type { Meta, StoryObj } from '@storybook/react'; -import { InSpacingDecorator } from '@salutejs/plasma-sb-utils'; +import { InSpacingDecorator, disableProps } from '@salutejs/plasma-sb-utils'; import { SSRProvider } from '../SSRProvider'; import { Button } from '../Button'; @@ -18,6 +19,7 @@ const meta: Meta = { docs: { story: { inline: false, iframeHeight: '30rem' } }, }, argTypes: { + ...disableProps(['hasBody']), placement: { options: [ 'center', @@ -33,19 +35,51 @@ const meta: Meta = { control: { type: 'select', }, + table: { defaultValue: { summary: 'center' } }, + }, + offsetX: { + control: { + type: 'number', + }, + table: { defaultValue: { summary: 0 } }, + }, + offsetY: { + control: { + type: 'number', + }, + table: { defaultValue: { summary: 0 } }, + }, + closeOnEsc: { + control: { + type: 'boolean', + }, + table: { defaultValue: { summary: true } }, + }, + closeOnOverlayClick: { + control: { + type: 'boolean', + }, + table: { defaultValue: { summary: true } }, + }, + withBlur: { + control: { + type: 'boolean', + }, + table: { defaultValue: { summary: false } }, }, }, -}; +} as Meta; export default meta; -type StoryModalProps = { +type StoryModalProps = ComponentProps & { placement: string; offsetX: number; offsetY: number; closeOnEsc: boolean; closeOnOverlayClick: boolean; withBlur: boolean; + hasClose?: boolean; }; const StyledButton = styled(Button)` @@ -62,6 +96,11 @@ const Content = styled.div` padding: 1rem; `; +const ButtonWrapper = styled.div` + display: flex; + flex-direction: column; +`; + const StyledModal = styled(Modal)` && > .${popupClasses.root}, .${modalClasses.overlay} { animation: fadeIn 1s forwards; @@ -97,19 +136,86 @@ const StyledModal = styled(Modal)` `; const StoryModalDemo = ({ placement, offsetX, offsetY, ...rest }: StoryModalProps) => { - const [isOpenA, setIsOpenA] = React.useState(false); - const [isOpenB, setIsOpenB] = React.useState(false); - const [isOpenC, setIsOpenC] = React.useState(false); + const [isOpenA, setIsOpenA] = useState(false); + const [isOpenB, setIsOpenB] = useState(false); + const [isOpenC, setIsOpenC] = useState(false); + + return ( + + + + + setIsOpenA(true)} /> + + setIsOpenA(false)} + opened={isOpenA} + placement={placement} + offset={[offsetX, offsetY]} + hasBody + {...rest} + > + + + setIsOpenB(true)} /> + + setIsOpenB(false)} + opened={isOpenB} + placement="left" + offset={[offsetX, offsetY]} + hasBody + {...rest} + > + + + setIsOpenC(true)} /> + + setIsOpenC(false)} + opened={isOpenC} + placement="top" + offset={[offsetX, offsetY]} + hasBody + {...rest} + > + + <>Content + + + + + + + ); +}; + +const StoryCustomModalDemo = ({ placement, offsetX, offsetY, ...rest }: StoryModalProps) => { + const [isOpenA, setIsOpenA] = useState(false); + const [isOpenB, setIsOpenB] = useState(false); + const [isOpenC, setIsOpenC] = useState(false); return ( -
+ setIsOpenA(true)} /> -
+ setIsOpenA(false)} opened={isOpenA} @@ -119,11 +225,12 @@ const StoryModalDemo = ({ placement, offsetX, offsetY, ...rest }: StoryModalProp > -
+ setIsOpenB(true)} /> -
+ setIsOpenB(false)} opened={isOpenB} placement="left" @@ -134,11 +241,12 @@ const StoryModalDemo = ({ placement, offsetX, offsetY, ...rest }: StoryModalProp -
+ setIsOpenC(true)} /> -
+ setIsOpenC(false)} opened={isOpenC} placement="top" @@ -162,7 +270,7 @@ const StoryModalDemo = ({ placement, offsetX, offsetY, ...rest }: StoryModalProp ); }; -export const ModalDemo: StoryObj = { +export const Default: StoryObj = { args: { placement: 'center', withBlur: false, @@ -170,10 +278,30 @@ export const ModalDemo: StoryObj = { closeOnOverlayClick: true, offsetX: 0, offsetY: 0, + hasClose: true, + }, + argTypes: { + hasClose: { + control: { + type: 'boolean', + }, + }, }, render: (args) => , }; +export const CustomModalDemo: StoryObj = { + args: { + placement: 'center', + withBlur: false, + closeOnEsc: true, + closeOnOverlayClick: true, + offsetX: 0, + offsetY: 0, + }, + render: (args) => , +}; + const StyledModalAnimation = styled(Modal)` /* stylelint-disable */ ${({ placement }) => diff --git a/packages/sdds-serv/src/components/Modal/Modal.config.ts b/packages/sdds-serv/src/components/Modal/Modal.config.ts index edc1be758d..8b107b98ea 100644 --- a/packages/sdds-serv/src/components/Modal/Modal.config.ts +++ b/packages/sdds-serv/src/components/Modal/Modal.config.ts @@ -9,6 +9,15 @@ export const config = { default: css` ${modalTokens.modalOverlayWithBlurColor}: rgba(35, 35, 35, 0.2); ${modalTokens.modalOverlayColor}: var(--overlay-soft); + ${modalTokens.modalBodyBackground}: var(--surface-solid-card); + ${modalTokens.modalBodyBorderRadius}: 1.25rem; + ${modalTokens.modalBodyPadding}: 2rem; + ${modalTokens.modalContentPadding}: 0.625rem; + ${modalTokens.modalCloseButtonRadius}: 0.375rem; + ${modalTokens.modalCloseButtonColor}: var(--text-secondary); + ${modalTokens.modalCloseButtonHoverColor}: var(--text-secondary-hover); + ${modalTokens.modalCloseButtonActiveColor}: var(--text-secondary-active); + ${modalTokens.modalOutlineFocusColor}: var(--surface-accent); `, }, }, diff --git a/packages/sdds-serv/src/components/Modal/Modal.stories.tsx b/packages/sdds-serv/src/components/Modal/Modal.stories.tsx index e6fc6db601..ed100b6801 100644 --- a/packages/sdds-serv/src/components/Modal/Modal.stories.tsx +++ b/packages/sdds-serv/src/components/Modal/Modal.stories.tsx @@ -1,7 +1,8 @@ import React, { useCallback, useRef, useState } from 'react'; -import styled, { css } from 'styled-components'; +import type { ComponentProps } from 'react'; +import styled from 'styled-components'; import type { Meta, StoryObj } from '@storybook/react'; -import { InSpacingDecorator } from '@salutejs/plasma-sb-utils'; +import { InSpacingDecorator, disableProps } from '@salutejs/plasma-sb-utils'; import { SSRProvider } from '../SSRProvider'; import { Button } from '../Button'; @@ -18,6 +19,7 @@ const meta: Meta = { docs: { story: { inline: false, iframeHeight: '30rem' } }, }, argTypes: { + ...disableProps(['hasBody']), placement: { options: [ 'center', @@ -33,19 +35,51 @@ const meta: Meta = { control: { type: 'select', }, + table: { defaultValue: { summary: 'center' } }, + }, + offsetX: { + control: { + type: 'number', + }, + table: { defaultValue: { summary: 0 } }, + }, + offsetY: { + control: { + type: 'number', + }, + table: { defaultValue: { summary: 0 } }, + }, + closeOnEsc: { + control: { + type: 'boolean', + }, + table: { defaultValue: { summary: true } }, + }, + closeOnOverlayClick: { + control: { + type: 'boolean', + }, + table: { defaultValue: { summary: true } }, + }, + withBlur: { + control: { + type: 'boolean', + }, + table: { defaultValue: { summary: false } }, }, }, -}; +} as Meta; export default meta; -type StoryModalProps = { +type StoryModalProps = ComponentProps & { placement: string; offsetX: number; offsetY: number; closeOnEsc: boolean; closeOnOverlayClick: boolean; withBlur: boolean; + hasClose?: boolean; }; const StyledButton = styled(Button)` @@ -62,6 +96,11 @@ const Content = styled.div` padding: 1rem; `; +const ButtonWrapper = styled.div` + display: flex; + flex-direction: column; +`; + const StyledModal = styled(Modal)` && > .${popupClasses.root}, .${modalClasses.overlay} { animation: fadeIn 1s forwards; @@ -97,19 +136,86 @@ const StyledModal = styled(Modal)` `; const StoryModalDemo = ({ placement, offsetX, offsetY, ...rest }: StoryModalProps) => { - const [isOpenA, setIsOpenA] = React.useState(false); - const [isOpenB, setIsOpenB] = React.useState(false); - const [isOpenC, setIsOpenC] = React.useState(false); + const [isOpenA, setIsOpenA] = useState(false); + const [isOpenB, setIsOpenB] = useState(false); + const [isOpenC, setIsOpenC] = useState(false); + + return ( + + + + + setIsOpenA(true)} /> + + setIsOpenA(false)} + opened={isOpenA} + placement={placement} + offset={[offsetX, offsetY]} + hasBody + {...rest} + > + + + setIsOpenB(true)} /> + + setIsOpenB(false)} + opened={isOpenB} + placement="left" + offset={[offsetX, offsetY]} + hasBody + {...rest} + > + + + setIsOpenC(true)} /> + + setIsOpenC(false)} + opened={isOpenC} + placement="top" + offset={[offsetX, offsetY]} + hasBody + {...rest} + > + + <>Content + + + + + + + ); +}; + +const StoryCustomModalDemo = ({ placement, offsetX, offsetY, ...rest }: StoryModalProps) => { + const [isOpenA, setIsOpenA] = useState(false); + const [isOpenB, setIsOpenB] = useState(false); + const [isOpenC, setIsOpenC] = useState(false); return ( -
+ setIsOpenA(true)} /> -
+ setIsOpenA(false)} opened={isOpenA} @@ -119,11 +225,12 @@ const StoryModalDemo = ({ placement, offsetX, offsetY, ...rest }: StoryModalProp > -
+ setIsOpenB(true)} /> -
+ setIsOpenB(false)} opened={isOpenB} placement="left" @@ -134,11 +241,12 @@ const StoryModalDemo = ({ placement, offsetX, offsetY, ...rest }: StoryModalProp -
+ setIsOpenC(true)} /> -
+ setIsOpenC(false)} opened={isOpenC} placement="top" @@ -162,7 +270,7 @@ const StoryModalDemo = ({ placement, offsetX, offsetY, ...rest }: StoryModalProp ); }; -export const ModalDemo: StoryObj = { +export const Default: StoryObj = { args: { placement: 'center', withBlur: false, @@ -170,10 +278,30 @@ export const ModalDemo: StoryObj = { closeOnOverlayClick: true, offsetX: 0, offsetY: 0, + hasClose: true, + }, + argTypes: { + hasClose: { + control: { + type: 'boolean', + }, + }, }, render: (args) => , }; +export const CustomModalDemo: StoryObj = { + args: { + placement: 'center', + withBlur: false, + closeOnEsc: true, + closeOnOverlayClick: true, + offsetX: 0, + offsetY: 0, + }, + render: (args) => , +}; + const StyledModalAnimation = styled(Modal)` /* stylelint-disable */ ${({ placement }) => diff --git a/website/plasma-b2c-docs/docs/components/ModalBase.mdx b/website/plasma-b2c-docs/docs/components/ModalBase.mdx index 451330fee1..b639da89be 100644 --- a/website/plasma-b2c-docs/docs/components/ModalBase.mdx +++ b/website/plasma-b2c-docs/docs/components/ModalBase.mdx @@ -63,6 +63,53 @@ export function App() { } ``` +## Использование стилизованной обертки + +Для использования стилизованного модального окна с отступами и крестиком для закрытия, добавьте свойство `hasBody`. +```tsx live +import React, { useState } from 'react'; +import { SSRProvider, Button, Modal, PopupProvider } from '@salutejs/plasma-b2c'; + +export function App() { + const [isOpenA, setIsOpenA] = useState(false); + const [isOpenB, setIsOpenB] = useState(false); + + return ( + + +
+
+
+ setIsOpenA(false)} + opened={isOpenA} + placement="center" + offset={[0, 0]} + hasBody + > + + + Content + + +
+
+
+ ); +} +``` + ## Подключение анимации Подключение анимации аналогично тому, как это происходит в `PopupBase` - через свойство `withAnimation`(управление через `popupBaseClasses`, `modalBaseClasses`). diff --git a/website/plasma-web-docs/docs/components/ModalBase.mdx b/website/plasma-web-docs/docs/components/ModalBase.mdx index bc021b00a2..d02f8d87a0 100644 --- a/website/plasma-web-docs/docs/components/ModalBase.mdx +++ b/website/plasma-web-docs/docs/components/ModalBase.mdx @@ -65,6 +65,53 @@ export function App() { } ``` +## Использование стилизованной обертки + +Для использования стилизованного модального окна с отступами и крестиком для закрытия, добавьте свойство `hasBody`. +```tsx live +import React, { useState } from 'react'; +import { SSRProvider, Button, Modal, PopupProvider } from '@salutejs/plasma-web'; + +export function App() { + const [isOpenA, setIsOpenA] = useState(false); + const [isOpenB, setIsOpenB] = useState(false); + + return ( + + +
+
+
+ setIsOpenA(false)} + opened={isOpenA} + placement="center" + offset={[0, 0]} + hasBody + > + + + Content + + +
+
+
+ ); +} +``` + ## Подключение анимации Подключение анимации аналогично тому, как это происходит в `PopupBase` - через свойство `withAnimation`(управление через `popupBaseClasses`, `modalBaseClasses`). diff --git a/website/sdds-cs-docs/docs/components/Modal.mdx b/website/sdds-cs-docs/docs/components/Modal.mdx index 38110b9f8b..a41d52e407 100644 --- a/website/sdds-cs-docs/docs/components/Modal.mdx +++ b/website/sdds-cs-docs/docs/components/Modal.mdx @@ -62,6 +62,53 @@ export function App() { } ``` +## Использование стилизованной обертки + +Для использования стилизованного модального окна с отступами и крестиком для закрытия, добавьте свойство `hasBody`. +```tsx live +import React, { useState } from 'react'; +import { SSRProvider, Button, Modal, PopupProvider } from '@salutejs/sdds-cs'; + +export function App() { + const [isOpenA, setIsOpenA] = useState(false); + const [isOpenB, setIsOpenB] = useState(false); + + return ( + + +
+
+
+ setIsOpenA(false)} + opened={isOpenA} + placement="center" + offset={[0, 0]} + hasBody + > + + + Content + + +
+
+
+ ); +} +``` + ## Подключение анимации Подключение анимации аналогично тому, как это происходит в `Popup` - через свойство `withAnimation`(управление через `popupClasses`, `modalClasses`). Для добавления анимации в оверлей необходимо использовать класс `.modal-overlay` через переменную `modalClasses.overlay` из пакета. diff --git a/website/sdds-dfa-docs/docs/components/Modal.mdx b/website/sdds-dfa-docs/docs/components/Modal.mdx index edb612075e..c6f4d4450a 100644 --- a/website/sdds-dfa-docs/docs/components/Modal.mdx +++ b/website/sdds-dfa-docs/docs/components/Modal.mdx @@ -62,6 +62,53 @@ export function App() { } ``` +## Использование стилизованной обертки + +Для использования стилизованного модального окна с отступами и крестиком для закрытия, добавьте свойство `hasBody`. +```tsx live +import React, { useState } from 'react'; +import { SSRProvider, Button, Modal, PopupProvider } from '@salutejs/sdds-dfa'; + +export function App() { + const [isOpenA, setIsOpenA] = useState(false); + const [isOpenB, setIsOpenB] = useState(false); + + return ( + + +
+
+
+ setIsOpenA(false)} + opened={isOpenA} + placement="center" + offset={[0, 0]} + hasBody + > + + + Content + + +
+
+
+ ); +} +``` + ## Подключение анимации Подключение анимации аналогично тому, как это происходит в `Popup` - через свойство `withAnimation`(управление через `popupClasses`, `modalClasses`). Для добавления анимации в оверлей необходимо использовать класс `.modal-overlay` через переменную `modalClasses.overlay` из пакета. diff --git a/website/sdds-insol-docs/docs/components/Modal.mdx b/website/sdds-insol-docs/docs/components/Modal.mdx index f43d6176b7..cd595cb94c 100644 --- a/website/sdds-insol-docs/docs/components/Modal.mdx +++ b/website/sdds-insol-docs/docs/components/Modal.mdx @@ -62,6 +62,53 @@ export function App() { } ``` +## Использование стилизованной обертки + +Для использования стилизованного модального окна с отступами и крестиком для закрытия, добавьте свойство `hasBody`. +```tsx live +import React, { useState } from 'react'; +import { SSRProvider, Button, Modal, PopupProvider } from '@salutejs/sdds-insol'; + +export function App() { + const [isOpenA, setIsOpenA] = useState(false); + const [isOpenB, setIsOpenB] = useState(false); + + return ( + + +
+
+
+ setIsOpenA(false)} + opened={isOpenA} + placement="center" + offset={[0, 0]} + hasBody + > + + + Content + + +
+
+
+ ); +} +``` + ## Подключение анимации Подключение анимации аналогично тому, как это происходит в `Popup` - через свойство `withAnimation`(управление через `popupClasses`, `modalClasses`). Для добавления анимации в оверлей необходимо использовать класс `.modal-overlay` через переменную `modalClasses.overlay` из пакета. diff --git a/website/sdds-serv-docs/docs/components/Modal.mdx b/website/sdds-serv-docs/docs/components/Modal.mdx index f4bd108dde..b88ba3d779 100644 --- a/website/sdds-serv-docs/docs/components/Modal.mdx +++ b/website/sdds-serv-docs/docs/components/Modal.mdx @@ -62,6 +62,53 @@ export function App() { } ``` +## Использование стилизованной обертки + +Для использования стилизованного модального окна с отступами и крестиком для закрытия, добавьте свойство `hasBody`. +```tsx live +import React, { useState } from 'react'; +import { SSRProvider, Button, Modal, PopupProvider } from '@salutejs/sdds-serv'; + +export function App() { + const [isOpenA, setIsOpenA] = useState(false); + const [isOpenB, setIsOpenB] = useState(false); + + return ( + + +
+
+
+ setIsOpenA(false)} + opened={isOpenA} + placement="center" + offset={[0, 0]} + hasBody + > + + + Content + + +
+
+
+ ); +} +``` + ## Подключение анимации Подключение анимации аналогично тому, как это происходит в `Popup` - через свойство `withAnimation`(управление через `popupClasses`, `modalClasses`). Для добавления анимации в оверлей необходимо использовать класс `.modal-overlay` через переменную `modalClasses.overlay` из пакета.