diff --git a/packages/core/.eslintrc.cjs b/packages/core/.eslintrc.cjs index 91e951502f..7fa4453b0f 100644 --- a/packages/core/.eslintrc.cjs +++ b/packages/core/.eslintrc.cjs @@ -97,16 +97,21 @@ module.exports = { } }, { - files: [ - "*.stories.@(js|jsx|ts|tsx)", - "*.stories.helpers.@(js|jsx|ts|tsx)", - "src/storybook/decorators/**/with*.{js,jsx,ts,tsx}" - ], + files: ["*.stories.@(js|jsx)", "*.stories.helpers.@(js|jsx)", "src/storybook/decorators/**/with*.{js,jsx}"], rules: { ...commonRules, "react-hooks/rules-of-hooks": "off", "react/jsx-key": "off" } + }, + { + files: ["*.stories.@(ts|tsx)", "*.stories.helpers.@(ts|tsx)", "src/storybook/decorators/**/with*.{ts,tsx}"], + rules: { + ...commonRules, + "react-hooks/rules-of-hooks": "off", + "react/jsx-key": "off", + "react/require-default-props": "off" + } } ], env: { diff --git a/packages/core/.storybook/manager.jsx b/packages/core/.storybook/manager.jsx index 5203144af6..47e73d451d 100644 --- a/packages/core/.storybook/manager.jsx +++ b/packages/core/.storybook/manager.jsx @@ -27,14 +27,20 @@ addons.setConfig({ return {name.replace(storyStatus, "").trim()}; }, filters: { - patterns: filterStory + patterns: shouldShowStory }, showRoots: false } }); -function filterStory(item) { +/** + * In order to hide stories you need to add `tags: ['internal']` to the stories file metadata. + * In order to hide MDX, you need to add `Internal` to the title in the MDX's `Meta` declaration `title` or to the `title` in the stories file metadata. + * + * Notice that all stories are available in development mode and in Chromatic. + */ +function shouldShowStory(item) { const isDev = isChromatic() || process.env.NODE_ENV === "development"; - const isInternal = !item.tags?.includes?.("internal") && !item.title?.startsWith?.("Internal"); - return isDev || isInternal; + const isPublic = !item.tags?.includes?.("internal") && !item.title?.startsWith?.("Internal"); + return isDev || isPublic; } diff --git a/packages/core/.storybook/preview-head.html b/packages/core/.storybook/preview-head.html index 8ef44dcf01..a8f2eb17fd 100644 --- a/packages/core/.storybook/preview-head.html +++ b/packages/core/.storybook/preview-head.html @@ -41,9 +41,14 @@ .sbdocs .docs-story { border: var(--sb-layout-border-color); } - .sbdocs .docs-story > div { + .sbdocs .docs-story > div:first-child { background: var(--sb-secondary-background-color); } + .sbdocs .docs-story > div:nth-child(2) { + /* prevent Show Code from rendering above full-screen components (such as Modal's overlay) */ + z-index: unset; + background: unset; + } .sbdocs { color: var(--sb-primary-text-color); diff --git a/packages/core/src/components/ModalNew/Modal/Modal.module.scss b/packages/core/src/components/ModalNew/Modal/Modal.module.scss index 982fdd57db..9f083ddd63 100644 --- a/packages/core/src/components/ModalNew/Modal/Modal.module.scss +++ b/packages/core/src/components/ModalNew/Modal/Modal.module.scss @@ -15,8 +15,8 @@ display: flex; flex-direction: column; - width: var(--modal-width, 50%); - max-height: var(--modal-max-height, 80%); + width: var(--modal-width); + max-height: var(--modal-max-height); background-color: var(--primary-background-color); overflow: hidden; border-radius: var(--border-radius-big); @@ -28,58 +28,16 @@ &.sizeSmall { --modal-max-height: 50%; - --modal-width: 45%; + --modal-width: 480px; } &.sizeMedium { --modal-max-height: 80%; - --modal-width: 50%; + --modal-width: 580px; } &.sizeLarge { --modal-max-height: 80%; - --modal-width: 70%; - } - - @media (min-width: 1280px) { - &.sizeSmall { - --modal-width: 40%; - } - - &.sizeMedium { - --modal-width: 44%; - } - - &.sizeLarge { - --modal-width: 66%; - } - } - - @media (min-width: 1440px) { - &.sizeSmall { - --modal-width: 35%; - } - - &.sizeMedium { - --modal-width: 38%; - } - - &.sizeLarge { - --modal-width: 60%; - } - } - - @media (min-width: 1720px) { - &.sizeSmall { - --modal-width: 34%; - } - - &.sizeMedium { - --modal-width: 36%; - } - - &.sizeLarge { - --modal-width: 58%; - } + --modal-width: 840px; } } diff --git a/packages/core/src/components/ModalNew/Modal/Modal.tsx b/packages/core/src/components/ModalNew/Modal/Modal.tsx index 203cccbf63..ef42add745 100644 --- a/packages/core/src/components/ModalNew/Modal/Modal.tsx +++ b/packages/core/src/components/ModalNew/Modal/Modal.tsx @@ -120,7 +120,7 @@ const Modal = forwardRef( {children} diff --git a/packages/core/src/components/ModalNew/Modal/Modal.types.tsx b/packages/core/src/components/ModalNew/Modal/Modal.types.tsx index d5fe642f3a..fa117ddbe9 100644 --- a/packages/core/src/components/ModalNew/Modal/Modal.types.tsx +++ b/packages/core/src/components/ModalNew/Modal/Modal.types.tsx @@ -9,15 +9,48 @@ export type ModalCloseEvent = | React.KeyboardEvent; export interface ModalProps extends VibeComponentProps { + /** + * Unique identifier for the modal. + */ id: string; + /** + * Controls the visibility of the modal. + */ show: boolean; + /** + * Determines the width and max-height of the modal. + */ size?: ModalSize; - closeButtonTheme?: ModalTopActionsProps["color"]; + /** + * Theme color for the close button. + */ + closeButtonTheme?: ModalTopActionsProps["theme"]; + /** + * Accessibility label for the close button. + */ closeButtonAriaLabel?: ModalTopActionsProps["closeButtonAriaLabel"]; + /** + * Callback fired when the modal should close. + */ onClose?: (event: ModalCloseEvent) => void; + /** + * Additional action to render in the header area. + */ renderHeaderAction?: ModalTopActionsProps["renderAction"]; + /** + * Reference to an element that triggered the modal, used for animations. + */ anchorElementRef?: React.RefObject; + /** + * When true, prevents closing the modal when clicking the overlay ("click-outside") or pressing ESC. + */ alertModal?: boolean; + /** + * Modal content. + */ children: React.ReactNode; + /** + * Additional inline styles for the modal. + */ style?: React.CSSProperties; } diff --git a/packages/core/src/components/ModalNew/Modal/__stories__/Modal.mdx b/packages/core/src/components/ModalNew/Modal/__stories__/Modal.mdx new file mode 100644 index 0000000000..83edd6b8b4 --- /dev/null +++ b/packages/core/src/components/ModalNew/Modal/__stories__/Modal.mdx @@ -0,0 +1,136 @@ +import { Canvas, Meta } from "@storybook/blocks"; +import { ModalTip } from "./Modal.stories.helpers"; +import { Overview as BasicModalPreview } from "../../layouts/ModalBasicLayout/__stories__/ModalBasicLayout.stories"; +import { Overview as SideBySideModalPreview } from "../../layouts/ModalSideBySideLayout/__stories__/ModalSideBySideLayout.stories"; +import { Overview as MediaModalPreview } from "../../layouts/ModalMediaLayout/__stories__/ModalMediaLayout.stories"; +import backdropDo from "./assets/backdrop-do.png"; +import backdropDont from "./assets/backdrop-dont.png"; +import loadingDo from "./assets/loading-do.png"; +import loadingDont from "./assets/loading-dont.png"; +import ctaDo from "./assets/cta-do.png"; +import ctaDont from "./assets/cta-dont.png"; +import styles from "./Modal.stories.module.scss"; +import { StorybookLink } from "vibe-storybook-components"; +import { + DIALOG, + TIPSEEN, + TOOLTIP +} from "../../../../storybook/components/related-components/component-description-map"; + + + +# Modal + +- [Overview](#overview) +- [Props](#props) +- [Usage](#usage) +- [Do's and dont's](do's-and-don'ts) +- [Related components](#related-components) +- [Feedback](#feedback) + +## Overview + +Modals help users focus on a single task or a piece of information by popping up and blocking the rest of the page’s content. Modals disappear when user complete a required action or dismiss it. Use modals for quick, infrequent tasks. + +We have 3 different modal component, each one provide a different layout for a different use case: + +### Basic modal + +The Basic Modal is intended for straightforward tasks, like selecting items or gathering basic information. Basic Modals help users focus on a single task without distractions. These modals do not support images or videos. + + + +### Side by side modal + +The Side-by-Side Modal offers two distinct sections: the left for text or inputs, and the right for supporting visuals. +It's ideal when users need to reference media alongside information. + + + +### Media modal + +The Media Modal includes a highlighted media section followed by text, perfect for grabbing attention with visuals before users interact with the content. Ideal for introducing new features or onboarding. + + + +## Usage + + + + + +## Do's and don'ts + +, + description: "Modal must include backdrop element." + }, + negative: { + component: modal without a backdrop, + description: "Don't remove the backdrop element of the modal or the modal's title." + } + }, + { + componentContainerClassName: styles.largeComponentRule, + positive: { + component: ( + modal with skeleton components as a loading experience + ), + description: ( + <> + Use our Skeleton component if loading is needed. Try + that at least the actions will appear immediately. + + ) + }, + negative: { + component: ( + modal with a spinner loading components as a loading experience + ), + description: "Don't use Loader component in case of necessary loading." + } + }, + { + componentContainerClassName: styles.largeComponentRule, + positive: { + component: ( + modal with a footer that includes one primary action and one tertiary action + ), + description: "Use one primary button as your main call to action, for extra buttons use the tertiary button." + }, + negative: { + component: ( + modal with a footer that includes two primary actions + ), + description: "Don't use more than one primary button, we don't want to distract the user from the main action." + } + } + ]} +/> + +## Related components + + diff --git a/packages/core/src/components/ModalNew/Modal/__stories__/Modal.stories.helpers.tsx b/packages/core/src/components/ModalNew/Modal/__stories__/Modal.stories.helpers.tsx new file mode 100644 index 0000000000..7e91f2aa5e --- /dev/null +++ b/packages/core/src/components/ModalNew/Modal/__stories__/Modal.stories.helpers.tsx @@ -0,0 +1,77 @@ +import React, { useEffect, useState } from "react"; +import Button from "../../../Button/Button"; +import { StorybookLink, Tip } from "vibe-storybook-components"; +import { useAfterFirstRender } from "../../../../hooks"; +import cx from "classnames"; +import styles from "./Modal.stories.module.scss"; +import { createPortal } from "react-dom"; +import { getStyle } from "../../../../helpers/typesciptCssModulesHelper"; + +export const OpenedModalPreview = ({ + onOpenModalClick, + isDocsView, + size = "small", + children: modal +}: { + onOpenModalClick: () => void; + isDocsView?: boolean; + size?: "small" | "medium" | "large"; + children: React.ReactNode; +}) => { + const isAfterFirstRender = useAfterFirstRender(); + return ( +
+ + {isDocsView ? modal : createPortal(modal, document.body)} +
+ ); +}; + +export const useRemoveModalScrollLock = (show: boolean, isDocsView?: boolean) => { + useEffect(() => { + if (show && document.body.attributes.getNamedItem("data-scroll-locked") && isDocsView) { + document.body.attributes.removeNamedItem("data-scroll-locked"); + document.documentElement.addEventListener( + "wheel", + e => { + e.stopImmediatePropagation(); + }, + true + ); + } + // eslint-disable-next-line react-hooks/exhaustive-deps -- this is intended to run once, on mount + }, []); +}; + +export function withOpenedModalPreview( + Story: React.FunctionComponent<{ show: boolean; setShow: (show: boolean) => void }>, + { size, isDocsView }: { size?: "small" | "medium" | "large"; isDocsView: boolean } +) { + const [show, setShow] = useState(true); + useRemoveModalScrollLock(show, isDocsView); // internal hook, for documentation purposes, to enable scroll on first load + + return ( + // internal component, for documentation purposes, to open modal inside a container + setShow(true)} isDocsView={isDocsView}> + + + ); +} + +export const ModalTip = () => ( +
+ + Since the modal is used for short and non-frequent tasks, consider using the main flow for common tasks. For + creating a popover positioned next to other components, like customized menus, check out our{" "} + + Dialog + {" "} + component. + +
+); diff --git a/packages/core/src/components/ModalNew/Modal/__stories__/Modal.stories.module.scss b/packages/core/src/components/ModalNew/Modal/__stories__/Modal.stories.module.scss new file mode 100644 index 0000000000..c54af9e71a --- /dev/null +++ b/packages/core/src/components/ModalNew/Modal/__stories__/Modal.stories.module.scss @@ -0,0 +1,47 @@ +.largeComponentRule { + height: fit-content !important; + width: fit-content; + padding: var(--sb-spacing-large); +} + +.preview { + padding-inline-start: 32px; + padding-block-start: 40px; + height: 360px; + width: 100%; + container-type: inline-size; + + &.small { + height: 360px; + } + + &.medium { + height: 416px; + } + + &.large { + height: 530px; + } + + /** + * The following css is to override the default dimensions of the modal component + * this is necessary because in the documentation, we're "trapping" the modal component inside the preview component + * so the modal component width and height be relative to the preview component and not the viewport + */ + [aria-modal][role="dialog"] { + &[class*="sizeSmall"] { + --modal-max-height: 50%; + --modal-width: 480px; + } + + &[class*="sizeMedium"] { + --modal-max-height: 80%; + --modal-width: 580px; + } + + &[class*="sizeLarge"] { + --modal-max-height: 80%; + --modal-width: 840px; + } + } +} diff --git a/packages/core/src/components/ModalNew/Modal/__stories__/assets/backdrop-do.png b/packages/core/src/components/ModalNew/Modal/__stories__/assets/backdrop-do.png new file mode 100644 index 0000000000..b2948973c8 Binary files /dev/null and b/packages/core/src/components/ModalNew/Modal/__stories__/assets/backdrop-do.png differ diff --git a/packages/core/src/components/ModalNew/Modal/__stories__/assets/backdrop-dont.png b/packages/core/src/components/ModalNew/Modal/__stories__/assets/backdrop-dont.png new file mode 100644 index 0000000000..6202c99e72 Binary files /dev/null and b/packages/core/src/components/ModalNew/Modal/__stories__/assets/backdrop-dont.png differ diff --git a/packages/core/src/components/ModalNew/Modal/__stories__/assets/cta-do.png b/packages/core/src/components/ModalNew/Modal/__stories__/assets/cta-do.png new file mode 100644 index 0000000000..a30f744271 Binary files /dev/null and b/packages/core/src/components/ModalNew/Modal/__stories__/assets/cta-do.png differ diff --git a/packages/core/src/components/ModalNew/Modal/__stories__/assets/cta-dont.png b/packages/core/src/components/ModalNew/Modal/__stories__/assets/cta-dont.png new file mode 100644 index 0000000000..7f2d324869 Binary files /dev/null and b/packages/core/src/components/ModalNew/Modal/__stories__/assets/cta-dont.png differ diff --git a/packages/core/src/components/ModalNew/Modal/__stories__/assets/loading-do.png b/packages/core/src/components/ModalNew/Modal/__stories__/assets/loading-do.png new file mode 100644 index 0000000000..0fa869ab5f Binary files /dev/null and b/packages/core/src/components/ModalNew/Modal/__stories__/assets/loading-do.png differ diff --git a/packages/core/src/components/ModalNew/Modal/__stories__/assets/loading-dont.png b/packages/core/src/components/ModalNew/Modal/__stories__/assets/loading-dont.png new file mode 100644 index 0000000000..5a71ee77ed Binary files /dev/null and b/packages/core/src/components/ModalNew/Modal/__stories__/assets/loading-dont.png differ diff --git a/packages/core/src/components/ModalNew/ModalContent/ModalContent.types.ts b/packages/core/src/components/ModalNew/ModalContent/ModalContent.types.ts index d50def5bd6..9484a843cb 100644 --- a/packages/core/src/components/ModalNew/ModalContent/ModalContent.types.ts +++ b/packages/core/src/components/ModalNew/ModalContent/ModalContent.types.ts @@ -2,5 +2,8 @@ import React from "react"; import { VibeComponentProps } from "../../../types"; export interface ModalContentProps extends VibeComponentProps { + /** + * Main content of the modal. + */ children?: React.ReactNode; } diff --git a/packages/core/src/components/ModalNew/ModalHeader/ModalHeader.types.ts b/packages/core/src/components/ModalNew/ModalHeader/ModalHeader.types.ts index 8e5f9a750f..61aa13250c 100644 --- a/packages/core/src/components/ModalNew/ModalHeader/ModalHeader.types.ts +++ b/packages/core/src/components/ModalNew/ModalHeader/ModalHeader.types.ts @@ -7,7 +7,14 @@ interface WithoutDescription { } interface WithDescription { + /** + * Descriptive text or content below the title. + * When supplied, would also add an aria-describedby attribute to the modal dialog element. + */ description: string | React.ReactNode; + /** + * Icon to display before the description. Can only be passed when description is supplied. + */ descriptionIcon?: | SubIcon | { @@ -16,4 +23,11 @@ interface WithDescription { }; } -export type ModalHeaderProps = { title: string } & (WithoutDescription | WithDescription) & VibeComponentProps; +export type ModalHeaderProps = { + /** + * Main heading text of the modal. + * When supplied, would also add an aria-labelledby attribute to the modal dialog element. + */ + title: string; +} & (WithDescription | WithoutDescription) & + VibeComponentProps; diff --git a/packages/core/src/components/ModalNew/ModalTopActions/ModalTopActions.tsx b/packages/core/src/components/ModalNew/ModalTopActions/ModalTopActions.tsx index e44eddac2d..e381325439 100644 --- a/packages/core/src/components/ModalNew/ModalTopActions/ModalTopActions.tsx +++ b/packages/core/src/components/ModalNew/ModalTopActions/ModalTopActions.tsx @@ -1,17 +1,17 @@ import React from "react"; import styles from "./ModalTopActions.module.scss"; -import { ModalTopActionsButtonColor, ModalTopActionsColor, ModalTopActionsProps } from "./ModalTopActions.types"; +import { ModalTopActionsButtonColor, ModalTopActionsTheme, ModalTopActionsProps } from "./ModalTopActions.types"; import IconButton from "../../IconButton/IconButton"; import { CloseMedium } from "@vibe/icons"; import { ButtonColor } from "../../Button/ButtonConstants"; -const colorToButtonColor: Record = { +const colorToButtonColor: Record = { dark: ButtonColor.ON_INVERTED_BACKGROUND, light: ButtonColor.ON_PRIMARY_COLOR }; -const ModalTopActions = ({ renderAction, color, closeButtonAriaLabel, onClose }: ModalTopActionsProps) => { - const buttonColor = colorToButtonColor[color] || ButtonColor.PRIMARY; +const ModalTopActions = ({ renderAction, theme, closeButtonAriaLabel, onClose }: ModalTopActionsProps) => { + const buttonColor = colorToButtonColor[theme] || ButtonColor.PRIMARY; return (
diff --git a/packages/core/src/components/ModalNew/ModalTopActions/ModalTopActions.types.ts b/packages/core/src/components/ModalNew/ModalTopActions/ModalTopActions.types.ts index 9ee221d54f..56bf075f89 100644 --- a/packages/core/src/components/ModalNew/ModalTopActions/ModalTopActions.types.ts +++ b/packages/core/src/components/ModalNew/ModalTopActions/ModalTopActions.types.ts @@ -3,7 +3,7 @@ import MenuButton from "../../MenuButton/MenuButton"; import IconButton from "../../IconButton/IconButton"; import { ButtonColor } from "../../Button/ButtonConstants"; -export type ModalTopActionsColor = "light" | "dark"; +export type ModalTopActionsTheme = "light" | "dark"; export type ModalTopActionsButtonColor = | ButtonColor.PRIMARY | ButtonColor.ON_PRIMARY_COLOR @@ -11,13 +11,22 @@ export type ModalTopActionsButtonColor = export interface ModalTopActionsProps { /** - * action can be passed either as a function or direct - * it allows passing back to consumer the color he chose, so he won't have to define it twice + * Action element or render function for the top-right area. + * When provided as a function, receives the current button color theme */ renderAction?: | React.ReactElement | ((color?: ModalTopActionsButtonColor) => React.ReactElement); - color?: ModalTopActionsColor; + /** + * Color theme for the top actions + */ + theme?: ModalTopActionsTheme; + /** + * Accessibility label for the close button + */ closeButtonAriaLabel?: string; + /** + * Callback fired when the close button is clicked + */ onClose?: (event: React.MouseEvent) => void; } diff --git a/packages/core/src/components/ModalNew/ModalTopActions/__tests__/ModalTopActions.test.tsx b/packages/core/src/components/ModalNew/ModalTopActions/__tests__/ModalTopActions.test.tsx index 8901e0e9de..38584d9fd9 100644 --- a/packages/core/src/components/ModalNew/ModalTopActions/__tests__/ModalTopActions.test.tsx +++ b/packages/core/src/components/ModalNew/ModalTopActions/__tests__/ModalTopActions.test.tsx @@ -41,7 +41,7 @@ describe("ModalTopActions", () => { it("calls renderAction with correct color argument", () => { const renderAction = jest.fn(color => ); - render(); + render(); expect(renderAction).toHaveBeenCalledWith(ButtonColor.ON_INVERTED_BACKGROUND); }); @@ -56,12 +56,12 @@ describe("ModalTopActions", () => { }); it("applies the correct color when 'dark' is passed", () => { - const { getByLabelText } = render(); + const { getByLabelText } = render(); expect(getByLabelText(closeButtonAriaLabel)).toHaveClass(camelCase("color-" + ButtonColor.ON_INVERTED_BACKGROUND)); }); it("applies the correct color when 'light' is passed", () => { - const { getByLabelText } = render(); + const { getByLabelText } = render(); expect(getByLabelText(closeButtonAriaLabel)).toHaveClass(camelCase("color-" + ButtonColor.ON_PRIMARY_COLOR)); }); diff --git a/packages/core/src/components/ModalNew/context/ModalContext.types.ts b/packages/core/src/components/ModalNew/context/ModalContext.types.ts index 9ec22d54e9..4903b75bbd 100644 --- a/packages/core/src/components/ModalNew/context/ModalContext.types.ts +++ b/packages/core/src/components/ModalNew/context/ModalContext.types.ts @@ -3,12 +3,29 @@ import React from "react"; export type ModalContextProps = ModalProviderValue; export type ModalProviderValue = { + /** + * Unique identifier for the modal. + * In use to set the modal title and description IDs to be unique. + */ modalId: string; + /** + * Callback to set the title element ID for accessibility. + */ setTitleId: (id: string) => void; + /** + * Callback to set the description element ID for accessibility. + */ setDescriptionId: (id: string) => void; }; export interface ModalProviderProps { + /** + * Context value containing modal state and handlers. + */ value: ModalProviderValue; + /** + * Modal provider children. + * Should be the Modal root. + */ children: React.ReactNode; } diff --git a/packages/core/src/components/ModalNew/footers/ModalFooter/ModalFooter.types.ts b/packages/core/src/components/ModalNew/footers/ModalFooter/ModalFooter.types.ts index 1ce64b56ca..cd21581f4f 100644 --- a/packages/core/src/components/ModalNew/footers/ModalFooter/ModalFooter.types.ts +++ b/packages/core/src/components/ModalNew/footers/ModalFooter/ModalFooter.types.ts @@ -2,5 +2,8 @@ import React from "react"; import { ModalFooterBaseProps } from "../ModalFooterBase/ModalFooterBase.types"; export interface ModalFooterProps extends Omit { + /** + * Optional content to render on the left side of the footer. + */ renderSideAction?: React.ReactNode; } diff --git a/packages/core/src/components/ModalNew/footers/ModalFooterBase/ModalFooterBase.types.ts b/packages/core/src/components/ModalNew/footers/ModalFooterBase/ModalFooterBase.types.ts index f9e2944392..4607862e8f 100644 --- a/packages/core/src/components/ModalNew/footers/ModalFooterBase/ModalFooterBase.types.ts +++ b/packages/core/src/components/ModalNew/footers/ModalFooterBase/ModalFooterBase.types.ts @@ -3,11 +3,23 @@ import React from "react"; import { VibeComponentProps } from "../../../../types"; export interface ModalFooterActionProps extends Omit { + /** + * Text to display as the Button's content. + */ text: string; } export interface ModalFooterBaseProps extends VibeComponentProps { + /** + * Props for the primary action button. + */ primaryButton: ModalFooterActionProps; + /** + * Props for the optional secondary action button. + */ secondaryButton?: ModalFooterActionProps; + /** + * Additional content to render in the footer. + */ renderAction?: React.ReactNode; } diff --git a/packages/core/src/components/ModalNew/footers/ModalFooterWizard/ModalFooterWizard.tsx b/packages/core/src/components/ModalNew/footers/ModalFooterWizard/ModalFooterWizard.tsx index ab15e90a7e..844a4f941d 100644 --- a/packages/core/src/components/ModalNew/footers/ModalFooterWizard/ModalFooterWizard.tsx +++ b/packages/core/src/components/ModalNew/footers/ModalFooterWizard/ModalFooterWizard.tsx @@ -1,20 +1,11 @@ import React, { forwardRef } from "react"; -import { VibeComponentProps } from "../../../../types"; import cx from "classnames"; import ModalFooterBase from "../ModalFooterBase/ModalFooterBase"; -import { ModalFooterBaseProps } from "../ModalFooterBase/ModalFooterBase.types"; import { getTestId } from "../../../../tests/test-ids-utils"; import { ComponentDefaultTestId } from "../../../../tests/constants"; import styles from "./ModalFooterWizard.module.scss"; import { StepsGalleryHeader } from "../../../Steps/StepsGalleryHeader"; - -export interface ModalFooterWizardProps - extends Required>, - VibeComponentProps { - stepCount: number; - activeStep: number; - onStepClick: (stepIndex: number) => void; -} +import { ModalFooterWizardProps } from "./ModalFooterWizard.types"; const ModalFooterWizard = forwardRef( ( diff --git a/packages/core/src/components/ModalNew/footers/ModalFooterWizard/ModalFooterWizard.types.ts b/packages/core/src/components/ModalNew/footers/ModalFooterWizard/ModalFooterWizard.types.ts new file mode 100644 index 0000000000..beb070e853 --- /dev/null +++ b/packages/core/src/components/ModalNew/footers/ModalFooterWizard/ModalFooterWizard.types.ts @@ -0,0 +1,21 @@ +import { ModalFooterBaseProps } from "../ModalFooterBase/ModalFooterBase.types"; +import { VibeComponentProps } from "../../../../types"; + +export interface ModalFooterWizardProps + extends Required>, + VibeComponentProps { + /** + * Total number of steps in the wizard. + * This would render the appropriate number of step indicators ("dots") in the footer. + */ + stepCount: number; + /** + * Current active step (0-based index). + * This would highlight the corresponding step indicator ("dot") in the footer. + */ + activeStep: number; + /** + * Callback fired when a step indicator ("dot") is clicked. + */ + onStepClick: (stepIndex: number) => void; +} diff --git a/packages/core/src/components/ModalNew/layouts/ModalBasicLayout/ModalBasicLayout.types.ts b/packages/core/src/components/ModalNew/layouts/ModalBasicLayout/ModalBasicLayout.types.ts index 67c237cdee..dc620c6948 100644 --- a/packages/core/src/components/ModalNew/layouts/ModalBasicLayout/ModalBasicLayout.types.ts +++ b/packages/core/src/components/ModalNew/layouts/ModalBasicLayout/ModalBasicLayout.types.ts @@ -2,5 +2,10 @@ import React from "react"; import { VibeComponentProps } from "../../../../types"; export interface ModalBasicLayoutProps extends VibeComponentProps { + /** + * Layout children in the following order: + * 1. Header content + * 2. Main content + */ children: React.ReactNode; } diff --git a/packages/core/src/components/ModalNew/layouts/ModalBasicLayout/__stories__/ModalBasicLayout.mdx b/packages/core/src/components/ModalNew/layouts/ModalBasicLayout/__stories__/ModalBasicLayout.mdx new file mode 100644 index 0000000000..fa7654dc84 --- /dev/null +++ b/packages/core/src/components/ModalNew/layouts/ModalBasicLayout/__stories__/ModalBasicLayout.mdx @@ -0,0 +1,129 @@ +import { Meta } from "@storybook/blocks"; +import * as ModalBasicLayoutStories from "./ModalBasicLayout.stories"; +import actionDo from "./assets/action-do.png"; +import actionDont from "./assets/action-dont.png"; +import styles from "../../../Modal/__stories__/Modal.stories.module.scss"; +import { + MODAL_MEDIA_LAYOUT, + MODAL_SIDE_BY_SIDE_LAYOUT, + TIPSEEN +} from "../../../../../storybook/components/related-components/component-description-map"; +import { BasicModalTip } from "./ModalBasicLayout.stories.helpers"; + + + +# Basic modal + +- [Overview](#overview) +- [Props](#props) +- [Usage](#usage) +- [Variants](#variants) +- [Scroll](#scroll) +- [Use cases and examples](#use-cases-and-examples) +- [Do's and dont's](do's-and-don'ts) +- [Related components](#related-components) +- [Feedback](#feedback) + +## Overview + +The Basic Modal is intended for straightforward tasks, like selecting items or gathering basic information. Basic Modals help users focus on a single task without distractions. These modals do not support images or videos. + + + +## Props + + + +## Usage + + + + + +## Variants + +### Sizes + +The modal component has three sizes - small, medium, and large. The modal width is responsive and adjust in width based on screen size. Each size also has a maximum height to keep harmonic window ratio, while the content area adapting to fit. + + + +### Alert Modal + +Use the "alertModal" boolean prop in order to allow closing the modal only by the close buttons and not by ESC or by clicking outside. Use this variant in case of sensitive or important messages, and in modals that requires data from the user, such as forms. + + + +## Scroll + +When the content of the modal is too large to fit within the viewport, the modal content should become scrollable while the header and footer stay sticky. If the scroll is too long, consider switching to a different modal size or a different layout. + + + +## Use cases and examples + +### Wizard footer + +When multi steps modal, use the "wizard footer". For more guidelines about the footer we recommend to check our ModalFooter page. + + + +### Footer with side action + +The footer has an option to include additional content on the left side when needed. This extra content can consist of a button, checkbox, or simple text for notes. Note that this option is only available with the default footer. + + + +### Header with extra icon button + +In case of a need of an icon button in the modal header, you can use our default header "Action slot". +You can also use it as a menu button component. + + + +### Animation + +Each modal includes an animation type based on its entrance point, with wizard modals also featuring transition animations. The default is the element trigger animation, which can be replaced with a center pop animation if there's no specific trigger. Transition animation is used exclusively for wizard modals and cannot be changed or removed. + + + +## Do's and don'ts + + + ), + description: "Use button, checkbox, or simple text for notes as an extra content to the footer." + }, + negative: { + component: ( + modal featuring an extra action image on the footer + ), + description: "Don't use images, inputs or any kind of content that can overload the user." + } + } + ]} +/> + +## Related components + + diff --git a/packages/core/src/components/ModalNew/layouts/ModalBasicLayout/__stories__/ModalBasicLayout.stories.helpers.tsx b/packages/core/src/components/ModalNew/layouts/ModalBasicLayout/__stories__/ModalBasicLayout.stories.helpers.tsx new file mode 100644 index 0000000000..aec3ffc3f6 --- /dev/null +++ b/packages/core/src/components/ModalNew/layouts/ModalBasicLayout/__stories__/ModalBasicLayout.stories.helpers.tsx @@ -0,0 +1,18 @@ +import React from "react"; +import { StorybookLink, Tip } from "vibe-storybook-components"; + +export const BasicModalTip = () => ( +
+ + If your content is not scrollable and you need to add media as supporting element, consider using{" "} + + Side-by-side modal + {" "} + or{" "} + + Media modal + {" "} + depends on your use case. + +
+); diff --git a/packages/core/src/components/ModalNew/layouts/ModalBasicLayout/__stories__/ModalBasicLayout.stories.tsx b/packages/core/src/components/ModalNew/layouts/ModalBasicLayout/__stories__/ModalBasicLayout.stories.tsx new file mode 100644 index 0000000000..5bf644c9f5 --- /dev/null +++ b/packages/core/src/components/ModalNew/layouts/ModalBasicLayout/__stories__/ModalBasicLayout.stories.tsx @@ -0,0 +1,468 @@ +import React, { useRef, useState } from "react"; +import { Meta, StoryObj } from "@storybook/react"; +import Modal from "../../../Modal/Modal"; +import { createStoryMetaSettingsDecorator } from "../../../../../storybook"; +import ModalBasicLayout from "../ModalBasicLayout"; +import ModalHeader from "../../../ModalHeader/ModalHeader"; +import ModalContent from "../../../ModalContent/ModalContent"; +import ModalFooter from "../../../footers/ModalFooter/ModalFooter"; +import Flex from "../../../../Flex/Flex"; +import Button from "../../../../Button/Button"; +import { createPortal } from "react-dom"; +import Text from "../../../../Text/Text"; +import Link from "../../../../Link/Link"; +import TransitionView from "../../../../TransitionView/TransitionView"; +import ModalFooterWizard from "../../../footers/ModalFooterWizard/ModalFooterWizard"; +import useWizard from "../../../../../hooks/useWizard/useWizard"; +import { Checkbox } from "../../../../Checkbox"; +import IconButton from "../../../../IconButton/IconButton"; +import { Menu } from "@vibe/icons"; +import { withOpenedModalPreview } from "../../../Modal/__stories__/Modal.stories.helpers"; + +type Story = StoryObj; + +const metaSettings = createStoryMetaSettingsDecorator({ + component: Modal +}); + +export default { + title: "Internal/Components/Modal [New]/Basic modal", + component: Modal, + subcomponents: { ModalBasicLayout, ModalHeader, ModalContent, ModalFooter, ModalFooterWizard, TransitionView }, + argTypes: metaSettings.argTypes, + decorators: metaSettings.decorators, + tags: ["internal"] +} satisfies Meta; + +export const Overview: Story = { + decorators: [(Story, context) => withOpenedModalPreview(Story, { isDocsView: context.viewMode === "docs" })], + render: (args, { show, setShow }) => { + return ( + setShow(false)} {...args}> + + + Modal subtitle, can come with icon + + } + /> + + + Modal content will appear here, you can custom it however you want, according to the user needs. Please + make sure that the content is clear for completing the relevant task. + + + + setShow(false) }} + secondaryButton={{ text: "Cancel", onClick: () => setShow(false) }} + /> + + ); + }, + parameters: { + docs: { + liveEdit: { + isEnabled: false + } + } + } +}; + +export const Sizes: Story = { + render: () => { + const [showSmall, setShowSmall] = useState(false); + const [showMedium, setShowMedium] = useState(false); + const [showLarge, setShowLarge] = useState(false); + + return ( + <> + + + + + + {createPortal( + setShowSmall(false)}> + + + Modal subtitle, can come with icon + + } + /> + + + Modal content will appear here, you can custom it however you want, according to the user needs. + Please make sure that the content is clear for completing the relevant task. + + + + setShowSmall(false) }} + secondaryButton={{ text: "Cancel", onClick: () => setShowSmall(false) }} + /> + , + document.body + )} + {createPortal( + setShowMedium(false)}> + + + Modal subtitle, can come with icon + + } + /> + + + Modal content will appear here, you can custom it however you want, according to the user needs. + Please make sure that the content is clear for completing the relevant task. + + + + setShowMedium(false) }} + secondaryButton={{ text: "Cancel", onClick: () => setShowMedium(false) }} + /> + , + document.body + )} + {createPortal( + setShowLarge(false)}> + + + Modal subtitle, can come with icon + + } + /> + + + Modal content will appear here, you can custom it however you want, according to the user needs. + Please make sure that the content is clear for completing the relevant task. + + + + setShowLarge(false) }} + secondaryButton={{ text: "Cancel", onClick: () => setShowLarge(false) }} + /> + , + document.body + )} + + ); + } +}; + +export const AlertModal: Story = { + decorators: [(Story, context) => withOpenedModalPreview(Story, { isDocsView: context.viewMode === "docs" })], + render: (_, { show, setShow }) => { + return ( + setShow(false)}> + + + + This will allow closing the modal only by the close buttons and not by ESC or by clicking outside. + + + setShow(false) }} + secondaryButton={{ text: "Cancel", onClick: () => setShow(false) }} + /> + + ); + } +}; + +export const Scroll: Story = { + decorators: [(Story, context) => withOpenedModalPreview(Story, { isDocsView: context.viewMode === "docs" })], + render: (_, { show, setShow }) => { + return ( + setShow(false)}> + + + + + Modal content will appear here, you can custom it however you want, according to the user needs. Please + make sure that the content is clear for completing the relevant task. The Basic Modal is intended for + straightforward tasks, like selecting items or gathering basic information. Basic Modals help users focus + on a single task without distractions. These modals do not support images or videos. When the content of + the modal is too large to fit within the viewport, the modal content should become scrollable while the + header and footer stay sticky. If the scroll is too long, consider switching to a different modal size or + a different layout. Modal content will appear here, you can custom it however you want, according to the + user needs. Please make sure that the content is clear for completing the relevant task. + + + + setShow(false) }} + secondaryButton={{ text: "Cancel", onClick: () => setShow(false) }} + /> + + ); + } +}; + +export const Wizard: Story = { + decorators: [(Story, context) => withOpenedModalPreview(Story, { isDocsView: context.viewMode === "docs" })], + render: (_, { show, setShow }) => { + const steps = [ + + + + + Modal content will appear here, you can custom it however you want, according to the user needs. Please make + sure that the content is clear for completing the relevant task. + + + , + + + + + Modal content will appear here, you can custom it however you want, according to the user needs. Please make + sure that the content is clear for completing the relevant task. + + + , + + + + + Modal content will appear here, you can custom it however you want, according to the user needs. Please make + sure that the content is clear for completing the relevant task. + + + + ]; + + const { activeStep, direction, next, back, isFirstStep, goToStep } = useWizard({ + stepCount: steps.length + }); + + return ( + setShow(false)}> + + {steps} + + + + ); + } +}; + +export const FooterWithSideAction: Story = { + decorators: [(Story, context) => withOpenedModalPreview(Story, { isDocsView: context.viewMode === "docs" })], + render: (_, { show, setShow }) => { + return ( + setShow(false)}> + + + Modal subtitle, can come with icon + + } + /> + + + Modal content will appear here, you can custom it however you want, according to the user needs. Please + make sure that the content is clear for completing the relevant task. + + + + setShow(false) }} + secondaryButton={{ text: "Cancel", onClick: () => setShow(false) }} + renderSideAction={} + /> + + ); + } +}; + +export const HeaderWithExtraIconButton: Story = { + decorators: [(Story, context) => withOpenedModalPreview(Story, { isDocsView: context.viewMode === "docs" })], + render: (_, { show, setShow }) => { + return ( + } + size="medium" + onClose={() => setShow(false)} + > + + + Modal subtitle, can come with icon + + } + /> + + + Modal content will appear here, you can custom it however you want, according to the user needs. Please + make sure that the content is clear for completing the relevant task. + + + + setShow(false) }} + secondaryButton={{ text: "Cancel", onClick: () => setShow(false) }} + renderSideAction={} + /> + + ); + } +}; + +export const Animation: Story = { + render: () => { + const [showAnchor, setShowAnchor] = useState(false); + const [showCenterPop, setShowCenterPop] = useState(false); + const [showTransition, setShowTransition] = useState(false); + + const anchorButtonRef = useRef(null); + + const transitionSteps = [ + + + + + Modal content will appear here, you can custom it however you want, according to the user needs. Please make + sure that the content is clear for completing the relevant task. + + + , + + + + + Modal content will appear here, you can custom it however you want, according to the user needs. Please make + sure that the content is clear for completing the relevant task. + + + , + + + + + Modal content will appear here, you can custom it however you want, according to the user needs. Please make + sure that the content is clear for completing the relevant task. + + + + ]; + + const { activeStep, direction, next, back, isFirstStep, goToStep } = useWizard({ + stepCount: transitionSteps.length + }); + + return ( + <> + + + + + + {createPortal( + setShowAnchor(false)} + > + + + Modal subtitle, can come with icon + + } + /> + + + Modal content will appear here, you can custom it however you want, according to the user needs. + Please make sure that the content is clear for completing the relevant task. + + + + setShowAnchor(false) }} + secondaryButton={{ text: "Cancel", onClick: () => setShowAnchor(false) }} + /> + , + document.body + )} + {createPortal( + setShowCenterPop(false)}> + + + Modal subtitle, can come with icon + + } + /> + + + Modal content will appear here, you can custom it however you want, according to the user needs. + Please make sure that the content is clear for completing the relevant task. + + + + setShowCenterPop(false) }} + secondaryButton={{ text: "Cancel", onClick: () => setShowCenterPop(false) }} + /> + , + document.body + )} + {createPortal( + setShowTransition(false)} + > + + {transitionSteps} + + + , + document.body + )} + + ); + } +}; diff --git a/packages/core/src/components/ModalNew/layouts/ModalBasicLayout/__stories__/ModalBasicLayoutRelatedComponent.tsx b/packages/core/src/components/ModalNew/layouts/ModalBasicLayout/__stories__/ModalBasicLayoutRelatedComponent.tsx new file mode 100644 index 0000000000..82946034da --- /dev/null +++ b/packages/core/src/components/ModalNew/layouts/ModalBasicLayout/__stories__/ModalBasicLayoutRelatedComponent.tsx @@ -0,0 +1,24 @@ +import React, { useMemo } from "react"; +import { RelatedComponent } from "vibe-storybook-components"; +import relatedComponentImage from "./assets/related-component.png"; + +export const ModalBasicLayoutRelatedComponent = () => { + const component = useMemo(() => { + return ( + modal with basic layout variation + ); + }, []); + + return ( + + ); +}; diff --git a/packages/core/src/components/ModalNew/layouts/ModalBasicLayout/__stories__/assets/action-do.png b/packages/core/src/components/ModalNew/layouts/ModalBasicLayout/__stories__/assets/action-do.png new file mode 100644 index 0000000000..ed743c16ab Binary files /dev/null and b/packages/core/src/components/ModalNew/layouts/ModalBasicLayout/__stories__/assets/action-do.png differ diff --git a/packages/core/src/components/ModalNew/layouts/ModalBasicLayout/__stories__/assets/action-dont.png b/packages/core/src/components/ModalNew/layouts/ModalBasicLayout/__stories__/assets/action-dont.png new file mode 100644 index 0000000000..8f58cdc90b Binary files /dev/null and b/packages/core/src/components/ModalNew/layouts/ModalBasicLayout/__stories__/assets/action-dont.png differ diff --git a/packages/core/src/components/ModalNew/layouts/ModalBasicLayout/__stories__/assets/related-component.png b/packages/core/src/components/ModalNew/layouts/ModalBasicLayout/__stories__/assets/related-component.png new file mode 100644 index 0000000000..b0f588188e Binary files /dev/null and b/packages/core/src/components/ModalNew/layouts/ModalBasicLayout/__stories__/assets/related-component.png differ diff --git a/packages/core/src/components/ModalNew/layouts/ModalFooterShadow.types.ts b/packages/core/src/components/ModalNew/layouts/ModalFooterShadow.types.ts index f3d4d656aa..8319aef7fb 100644 --- a/packages/core/src/components/ModalNew/layouts/ModalFooterShadow.types.ts +++ b/packages/core/src/components/ModalNew/layouts/ModalFooterShadow.types.ts @@ -1,3 +1,6 @@ export interface ModalFooterShadowProps { + /** + * Controls the visibility of the shadow. + */ show: boolean; } diff --git a/packages/core/src/components/ModalNew/layouts/ModalLayoutScrollableContent.types.ts b/packages/core/src/components/ModalNew/layouts/ModalLayoutScrollableContent.types.ts index 7fa753ff9a..2f83533c05 100644 --- a/packages/core/src/components/ModalNew/layouts/ModalLayoutScrollableContent.types.ts +++ b/packages/core/src/components/ModalNew/layouts/ModalLayoutScrollableContent.types.ts @@ -1,7 +1,16 @@ import { ReactNode, UIEventHandler } from "react"; export interface ModalLayoutScrollableContentProps { + /** + * Callback fired when the content is scrolled. + */ onScroll?: UIEventHandler; + /** + * Additional class name. + */ className?: string; + /** + * Scrollable content. + */ children: ReactNode; } diff --git a/packages/core/src/components/ModalNew/layouts/ModalMedia.module.scss b/packages/core/src/components/ModalNew/layouts/ModalMedia.module.scss index 9eb13e01cf..67197d4106 100644 --- a/packages/core/src/components/ModalNew/layouts/ModalMedia.module.scss +++ b/packages/core/src/components/ModalNew/layouts/ModalMedia.module.scss @@ -4,7 +4,7 @@ display: flex; justify-content: center; align-items: center; - flex-shrink: 0; + flex: 1 0; overflow: hidden; position: relative; } diff --git a/packages/core/src/components/ModalNew/layouts/ModalMedia.types.ts b/packages/core/src/components/ModalNew/layouts/ModalMedia.types.ts index 550b46dc33..6584a726b4 100644 --- a/packages/core/src/components/ModalNew/layouts/ModalMedia.types.ts +++ b/packages/core/src/components/ModalNew/layouts/ModalMedia.types.ts @@ -2,5 +2,8 @@ import React from "react"; import { VibeComponentProps } from "../../../types"; export interface ModalMediaProps extends VibeComponentProps { + /** + * Media content to be displayed in the modal (image, video, Lottie, etc.). + */ children: React.ReactNode; } diff --git a/packages/core/src/components/ModalNew/layouts/ModalMediaLayout/ModalMediaLayout.module.scss b/packages/core/src/components/ModalNew/layouts/ModalMediaLayout/ModalMediaLayout.module.scss index d729853637..80609ce63f 100644 --- a/packages/core/src/components/ModalNew/layouts/ModalMediaLayout/ModalMediaLayout.module.scss +++ b/packages/core/src/components/ModalNew/layouts/ModalMediaLayout/ModalMediaLayout.module.scss @@ -11,17 +11,6 @@ position: relative; width: 100%; flex-shrink: 0; - - height: var(--modal-top-media-height, 240px); - @media (min-width: 1280px) { - --modal-top-media-height: 260px; - } - @media (min-width: 1440px) { - --modal-top-media-height: 260px; - } - @media (min-width: 1720px) { - --modal-top-media-height: 320px; - } } .header { diff --git a/packages/core/src/components/ModalNew/layouts/ModalMediaLayout/ModalMediaLayout.types.ts b/packages/core/src/components/ModalNew/layouts/ModalMediaLayout/ModalMediaLayout.types.ts index 3d9481f36f..509075b87f 100644 --- a/packages/core/src/components/ModalNew/layouts/ModalMediaLayout/ModalMediaLayout.types.ts +++ b/packages/core/src/components/ModalNew/layouts/ModalMediaLayout/ModalMediaLayout.types.ts @@ -2,5 +2,11 @@ import React from "react"; import { VibeComponentProps } from "../../../../types"; export interface ModalMediaLayoutProps extends VibeComponentProps { + /** + * Layout children in the following order: + * 1. Media content + * 2. Header content + * 3. Main content + */ children: React.ReactNode; } diff --git a/packages/core/src/components/ModalNew/layouts/ModalMediaLayout/__stories__/ModalMediaLayout.mdx b/packages/core/src/components/ModalNew/layouts/ModalMediaLayout/__stories__/ModalMediaLayout.mdx new file mode 100644 index 0000000000..d17590f2fa --- /dev/null +++ b/packages/core/src/components/ModalNew/layouts/ModalMediaLayout/__stories__/ModalMediaLayout.mdx @@ -0,0 +1,101 @@ +import { Meta } from "@storybook/blocks"; +import * as MediaModalStories from "./ModalMediaLayout.stories"; +import ratioDo from "./assets/ratio-do.png"; +import ratioDont from "./assets/ratio-dont.png"; + +import styles from "../../../Modal/__stories__/Modal.stories.module.scss"; +import { + MODAL_BASIC_LAYOUT, + MODAL_SIDE_BY_SIDE_LAYOUT, + TIPSEEN +} from "../../../../../storybook/components/related-components/component-description-map"; +import { MediaModalTip } from "./ModalMediaLayout.stories.helpers"; + + + +# Media Modal + +- [Overview](#overview) +- [Props](#props) +- [Usage](#usage) +- [Use cases and examples](#use-cases-and-examples) +- [Do's and dont's](do's-and-don'ts) +- [Related components](#related-components) +- [Feedback](#feedback) + +## Overview + +The Media Modal includes a highlighted media section, followed by a textual content area. This modal is intended for cases when you need to catch the user’s attention using visual elements before they interact with the text. It’s ideal for introducing new features or short onboarding flows. + + + +## Props + + + +## Usage + + + + + +## Use cases and examples + +### Wizard footer + +When multi steps modal, use the "wizard footer". For more guidelines about the footer we recommend to check our ModalFooter page. + + + +### Header with extra icon button + +In case of a need of an icon button in the modal header, you can use our default header "Action slot". +You can also use it as a menu button component. + + + +### Animation + +Each modal includes an animation type based on its entrance point, with wizard modals also featuring transition animations. The default is the element trigger animation, which can be replaced with a center pop animation if there's no specific trigger. Transition animation is used exclusively for wizard modals and cannot be changed or removed. + + + +## Do's and don'ts + + + ), + description: "Keep a balanced ratio between the media section, to the content and footer section." + }, + negative: { + component: ( + modal with bad ratio between image to content and footer, image is very small + ), + description: + "Don't create a media that will be too small or too big for the modal width, as it create unbalanced look." + } + } + ]} +/> + +## Related components + + diff --git a/packages/core/src/components/ModalNew/layouts/ModalMediaLayout/__stories__/ModalMediaLayout.stories.helpers.tsx b/packages/core/src/components/ModalNew/layouts/ModalMediaLayout/__stories__/ModalMediaLayout.stories.helpers.tsx new file mode 100644 index 0000000000..bc860367fc --- /dev/null +++ b/packages/core/src/components/ModalNew/layouts/ModalMediaLayout/__stories__/ModalMediaLayout.stories.helpers.tsx @@ -0,0 +1,14 @@ +import React from "react"; +import { StorybookLink, Tip } from "vibe-storybook-components"; + +export const MediaModalTip = () => ( +
+ + If your content is scrollable or wide (you need more space), consider using{" "} + + Basic modal + + . + +
+); diff --git a/packages/core/src/components/ModalNew/layouts/ModalMediaLayout/__stories__/ModalMediaLayout.stories.tsx b/packages/core/src/components/ModalNew/layouts/ModalMediaLayout/__stories__/ModalMediaLayout.stories.tsx new file mode 100644 index 0000000000..0f3662479c --- /dev/null +++ b/packages/core/src/components/ModalNew/layouts/ModalMediaLayout/__stories__/ModalMediaLayout.stories.tsx @@ -0,0 +1,252 @@ +import React, { useState } from "react"; +import { Meta, StoryObj } from "@storybook/react"; +import Modal from "../../../Modal/Modal"; +import { createStoryMetaSettingsDecorator } from "../../../../../storybook"; +import ModalHeader from "../../../ModalHeader/ModalHeader"; +import ModalContent from "../../../ModalContent/ModalContent"; +import ModalMedia from "../../ModalMedia"; +import mediaImage from "./assets/media-image.png"; +import ModalFooter from "../../../footers/ModalFooter/ModalFooter"; +import ModalMediaLayout from "../ModalMediaLayout"; +import Text from "../../../../Text/Text"; +import Link from "../../../../Link/Link"; +import useWizard from "../../../../../hooks/useWizard/useWizard"; +import TransitionView from "../../../../TransitionView/TransitionView"; +import ModalFooterWizard from "../../../footers/ModalFooterWizard/ModalFooterWizard"; +import IconButton from "../../../../IconButton/IconButton"; +import { Menu } from "@vibe/icons"; +import Flex from "../../../../Flex/Flex"; +import Button from "../../../../Button/Button"; +import { createPortal } from "react-dom"; +import { withOpenedModalPreview } from "../../../Modal/__stories__/Modal.stories.helpers"; + +type Story = StoryObj; + +const metaSettings = createStoryMetaSettingsDecorator({ + component: Modal +}); + +export default { + title: "Internal/Components/Modal [New]/Media modal", + component: Modal, + subcomponents: { + ModalMediaLayout, + ModalMedia, + ModalHeader, + ModalContent, + ModalFooter, + ModalFooterWizard, + TransitionView + }, + argTypes: metaSettings.argTypes, + decorators: metaSettings.decorators, + tags: ["internal"] +} satisfies Meta; + +export const Overview: Story = { + decorators: [ + (Story, context) => withOpenedModalPreview(Story, { size: "large", isDocsView: context.viewMode === "docs" }) + ], + render: (args, { show, setShow }) => { + return ( + setShow(false)} {...args}> + + + media placeholder + + + + + The media modal is ideal for introducing new features or onboarding, the user can also{" "} + . + + + + setShow(false) }} + secondaryButton={{ text: "Cancel", onClick: () => setShow(false) }} + /> + + ); + }, + parameters: { + docs: { + liveEdit: { + isEnabled: false + } + } + } +}; + +export const Wizard: Story = { + decorators: [ + (Story, context) => withOpenedModalPreview(Story, { size: "large", isDocsView: context.viewMode === "docs" }) + ], + render: (_, { show, setShow }) => { + const steps = [ + + + media placeholder + + + + + We have made some changes to our modal component. Keep reading to see what improvements we made. + + + , + + + media placeholder + + + + + Now the modal can also allow wizard process, when including stepper in the modal footer, it also contain an + animation. + + + + ]; + + const { activeStep, direction, next, back, isFirstStep, goToStep } = useWizard({ + stepCount: steps.length + }); + + return ( + setShow(false)}> + + {steps} + + + + ); + } +}; + +export const HeaderWithExtraIconButton: Story = { + decorators: [ + (Story, context) => withOpenedModalPreview(Story, { size: "large", isDocsView: context.viewMode === "docs" }) + ], + render: (_, { show, setShow }) => { + return ( + } + size="medium" + onClose={() => setShow(false)} + > + + + media placeholder + + + + + The media modal is ideal for introducing new features or onboarding, the user can also{" "} + . + + + + setShow(false) }} + secondaryButton={{ text: "Cancel", onClick: () => setShow(false) }} + /> + + ); + } +}; + +export const Animation: Story = { + render: () => { + const [showCenterPop, setShowCenterPop] = useState(false); + const [showTransition, setShowTransition] = useState(false); + + const transitionSteps = [ + + + media placeholder + + + + + We have made some changes to our modal component. Keep reading to see what improvements we made. + + + , + + + media placeholder + + + + + Now the modal can also allow wizard process, when including stepper in the modal footer, it also contain an + animation. + + + + ]; + + const { activeStep, direction, next, back, isFirstStep, goToStep } = useWizard({ + stepCount: transitionSteps.length + }); + + return ( + <> + + + + + {createPortal( + setShowCenterPop(false)}> + + + media placeholder + + + + + The media modal is ideal for introducing new features or onboarding, the user can also{" "} + . + + + + setShowCenterPop(false) }} + secondaryButton={{ text: "Cancel", onClick: () => setShowCenterPop(false) }} + /> + , + document.body + )} + {createPortal( + setShowTransition(false)} + > + + {transitionSteps} + + + , + document.body + )} + + ); + } +}; diff --git a/packages/core/src/components/ModalNew/layouts/ModalMediaLayout/__stories__/ModalMediaLayoutRelatedComponent.tsx b/packages/core/src/components/ModalNew/layouts/ModalMediaLayout/__stories__/ModalMediaLayoutRelatedComponent.tsx new file mode 100644 index 0000000000..6dbc919d70 --- /dev/null +++ b/packages/core/src/components/ModalNew/layouts/ModalMediaLayout/__stories__/ModalMediaLayoutRelatedComponent.tsx @@ -0,0 +1,24 @@ +import React, { useMemo } from "react"; +import { RelatedComponent } from "vibe-storybook-components"; +import relatedComponentImage from "./assets/related-component.png"; + +export const ModalMediaLayoutRelatedComponent = () => { + const component = useMemo(() => { + return ( + modal with media layout variation + ); + }, []); + + return ( + + ); +}; diff --git a/packages/core/src/components/ModalNew/layouts/ModalMediaLayout/__stories__/assets/media-image.png b/packages/core/src/components/ModalNew/layouts/ModalMediaLayout/__stories__/assets/media-image.png new file mode 100644 index 0000000000..838801c9cf Binary files /dev/null and b/packages/core/src/components/ModalNew/layouts/ModalMediaLayout/__stories__/assets/media-image.png differ diff --git a/packages/core/src/components/ModalNew/layouts/ModalMediaLayout/__stories__/assets/ratio-do.png b/packages/core/src/components/ModalNew/layouts/ModalMediaLayout/__stories__/assets/ratio-do.png new file mode 100644 index 0000000000..8394ed580c Binary files /dev/null and b/packages/core/src/components/ModalNew/layouts/ModalMediaLayout/__stories__/assets/ratio-do.png differ diff --git a/packages/core/src/components/ModalNew/layouts/ModalMediaLayout/__stories__/assets/ratio-dont.png b/packages/core/src/components/ModalNew/layouts/ModalMediaLayout/__stories__/assets/ratio-dont.png new file mode 100644 index 0000000000..8a65236f3c Binary files /dev/null and b/packages/core/src/components/ModalNew/layouts/ModalMediaLayout/__stories__/assets/ratio-dont.png differ diff --git a/packages/core/src/components/ModalNew/layouts/ModalMediaLayout/__stories__/assets/related-component.png b/packages/core/src/components/ModalNew/layouts/ModalMediaLayout/__stories__/assets/related-component.png new file mode 100644 index 0000000000..9e5479fe31 Binary files /dev/null and b/packages/core/src/components/ModalNew/layouts/ModalMediaLayout/__stories__/assets/related-component.png differ diff --git a/packages/core/src/components/ModalNew/layouts/ModalSideBySideLayout/ModalSideBySideLayout.types.ts b/packages/core/src/components/ModalNew/layouts/ModalSideBySideLayout/ModalSideBySideLayout.types.ts index 1534fd491b..205179396c 100644 --- a/packages/core/src/components/ModalNew/layouts/ModalSideBySideLayout/ModalSideBySideLayout.types.ts +++ b/packages/core/src/components/ModalNew/layouts/ModalSideBySideLayout/ModalSideBySideLayout.types.ts @@ -2,5 +2,11 @@ import { VibeComponentProps } from "../../../../types"; import React from "react"; export interface ModalSideBySideLayoutProps extends VibeComponentProps { + /** + * Layout children in the following order: + * 1. Header content + * 2. Main content + * 3. Media content + */ children: React.ReactNode; } diff --git a/packages/core/src/components/ModalNew/layouts/ModalSideBySideLayout/__stories__/ModalSideBySideLayout.mdx b/packages/core/src/components/ModalNew/layouts/ModalSideBySideLayout/__stories__/ModalSideBySideLayout.mdx new file mode 100644 index 0000000000..ad23b65fb8 --- /dev/null +++ b/packages/core/src/components/ModalNew/layouts/ModalSideBySideLayout/__stories__/ModalSideBySideLayout.mdx @@ -0,0 +1,157 @@ +import { Meta } from "@storybook/blocks"; +import * as SideBySideModalStories from "./ModalSideBySideLayout.stories"; +import breakdownDo from "./assets/breakdown-do.png"; +import breakdownDont from "./assets/breakdown-dont.png"; +import columnsDo from "./assets/columns-do.png"; +import columnsDont from "./assets/columns-dont.png"; +import wizardDo from "./assets/wizard-do.png"; +import wizardDont from "./assets/wizard-dont.png"; +import styles from "../../../Modal/__stories__/Modal.stories.module.scss"; +import { + MODAL_BASIC_LAYOUT, + MODAL_MEDIA_LAYOUT, + TIPSEEN +} from "../../../../../storybook/components/related-components/component-description-map"; +import { SideBySideModalTip } from "./ModalSideBySideLayout.stories.helpers"; + + + +# Side-by-side modal + +- [Overview](#overview) +- [Props](#props) +- [Usage](#usage) +- [Use cases and examples](#use-cases-and-examples) +- [Do's and dont's](do's-and-don'ts) +- [Related components](#related-components) +- [Feedback](#feedback) + +## Overview + +The Side-by-side Modal offers a layout with two distinct sections. The left side is reserved for providing information or inputs, like text fields or dropdown. The right side is reserved for visual media that supports the content on the left, like an image that adds context. This layout works best when users need to reference visual elements alongside textual information. + + + +## Props + + + +## Usage + + + + + +## Use cases and examples + +### Wizard footer + +When multi steps modal, use the "wizard footer". For more guidelines about the footer we recommend to check our ModalFooter page. + + + +### Header with extra icon button + +In case of a need of an icon button in the modal header, you can use our default header "Action slot". +You can also use it as a menu button component. + + + +### Animation + +Each modal includes an animation type based on its entrance point, with wizard modals also featuring transition animations. The default is the element trigger animation, which can be replaced with a center pop animation if there's no specific trigger. Transition animation is used exclusively for wizard modals and cannot be changed or removed. + + + +## Do's and don'ts + + + ), + description: "Split up processes with several tasks into distinct steps using our wizard modal footer." + }, + negative: { + component: ( + modal with scroll and with a lot of content in left side + ), + description: "Don't use scrolling for side-by-side modals in case of several tasks." + } + }, + { + componentContainerClassName: styles.largeComponentRule, + positive: { + component: ( + modal with side by side layout without media content in its right side + ), + description: "The right side of the modal is for media content. You can remove it if you don't need it." + }, + negative: { + component: ( + modal with side by side layout with extra content in its right side + ), + description: ( + <> + Don't turn this modal into a two-column grid. If you don't need an image, consider using the{" "} + basic modal. + + ) + } + }, + { + componentContainerClassName: styles.largeComponentRule, + positive: { + component: ( + modal with a wizard footer with the last button enabled + ), + description: + "When using a wizard modal, allow the user to complete the process and close the modal by leaving the last CTA enabled. " + }, + negative: { + component: ( + modal with a wizard footer with the last button disabled + ), + description: + "Don’t finish the Tipseen wizard process with a disabled CTA. Also, when in first step, make sure the “Back” button is disabled." + } + } + ]} +/> + +## Related components + + diff --git a/packages/core/src/components/ModalNew/layouts/ModalSideBySideLayout/__stories__/ModalSideBySideLayout.stories.helpers.tsx b/packages/core/src/components/ModalNew/layouts/ModalSideBySideLayout/__stories__/ModalSideBySideLayout.stories.helpers.tsx new file mode 100644 index 0000000000..78e1aac6ff --- /dev/null +++ b/packages/core/src/components/ModalNew/layouts/ModalSideBySideLayout/__stories__/ModalSideBySideLayout.stories.helpers.tsx @@ -0,0 +1,14 @@ +import React from "react"; +import { StorybookLink, Tip } from "vibe-storybook-components"; + +export const SideBySideModalTip = () => ( +
+ + If your content is scrollable consider using{" "} + + Basic modal + + . + +
+); diff --git a/packages/core/src/components/ModalNew/layouts/ModalSideBySideLayout/__stories__/ModalSideBySideLayout.stories.tsx b/packages/core/src/components/ModalNew/layouts/ModalSideBySideLayout/__stories__/ModalSideBySideLayout.stories.tsx new file mode 100644 index 0000000000..8090d9e792 --- /dev/null +++ b/packages/core/src/components/ModalNew/layouts/ModalSideBySideLayout/__stories__/ModalSideBySideLayout.stories.tsx @@ -0,0 +1,369 @@ +import React, { useRef, useState } from "react"; +import { Meta, StoryObj } from "@storybook/react"; +import Modal from "../../../Modal/Modal"; +import { createStoryMetaSettingsDecorator } from "../../../../../storybook"; +import { withOpenedModalPreview } from "../../../Modal/__stories__/Modal.stories.helpers"; +import ModalHeader from "../../../ModalHeader/ModalHeader"; +import ModalContent from "../../../ModalContent/ModalContent"; +import ModalSideBySideLayout from "../ModalSideBySideLayout"; +import ModalMedia from "../../ModalMedia"; +import mediaImage from "./assets/media-image.png"; +import useWizard from "../../../../../hooks/useWizard/useWizard"; +import TransitionView from "../../../../TransitionView/TransitionView"; +import ModalFooterWizard from "../../../footers/ModalFooterWizard/ModalFooterWizard"; +import TextField from "../../../../TextField/TextField"; +import Flex from "../../../../Flex/Flex"; +import Dropdown from "../../../../Dropdown/Dropdown"; +import FieldLabel from "../../../../FieldLabel/FieldLabel"; +import IconButton from "../../../../IconButton/IconButton"; +import { Menu } from "@vibe/icons"; +import ModalFooter from "../../../footers/ModalFooter/ModalFooter"; +import Button from "../../../../Button/Button"; +import { createPortal } from "react-dom"; +import Text from "../../../../Text/Text"; +import Link from "../../../../Link/Link"; + +type Story = StoryObj; + +const metaSettings = createStoryMetaSettingsDecorator({ + component: Modal +}); + +export default { + title: "Internal/Components/Modal [New]/Side by side modal", + component: Modal, + subcomponents: { + ModalSideBySideLayout, + ModalMedia, + ModalHeader, + ModalContent, + ModalFooter, + ModalFooterWizard, + TransitionView + }, + argTypes: metaSettings.argTypes, + decorators: metaSettings.decorators, + tags: ["internal"] +} satisfies Meta; + +export const Overview: Story = { + decorators: [ + (Story, context) => withOpenedModalPreview(Story, { size: "medium", isDocsView: context.viewMode === "docs" }) + ], + render: (args, { show, setShow }) => { + const steps = [ + + + Modal subtitle, can come with icon + + } + /> + + + Modal content will appear here, you can custom it however you want, according to the user needs. Please make + sure that the content is clear for completing the relevant task. + + + + side by side placeholder + + , + + + Modal subtitle, can come with icon + + } + /> + + + Modal content will appear here, you can custom it however you want, according to the user needs. Please make + sure that the content is clear for completing the relevant task. + + + + side by side placeholder + + + ]; + + const { activeStep, direction, next, back, isFirstStep, goToStep } = useWizard({ + stepCount: steps.length + }); + + return ( + setShow(false)} style={{ height: 400 }} {...args}> + + {steps} + + + + ); + }, + parameters: { + docs: { + liveEdit: { + isEnabled: false + } + } + } +}; + +export const Wizard: Story = { + decorators: [ + (Story, context) => withOpenedModalPreview(Story, { size: "medium", isDocsView: context.viewMode === "docs" }) + ], + render: (_, { show, setShow }) => { + const dropdownOptions = [ + { + label: "English", + value: "en" + }, + { + label: "Hebrew", + value: "he" + } + ]; + + const steps = [ + + + + + + + + + + side by side placeholder + + , + + + + + + + + + + + + + side by side placeholder + + + ]; + + const { activeStep, direction, next, back, isFirstStep, goToStep } = useWizard({ + stepCount: steps.length + }); + + return ( + setShow(false)} style={{ height: 400 }}> + + {steps} + + + + ); + } +}; + +export const HeaderWithExtraIconButton: Story = { + decorators: [ + (Story, context) => withOpenedModalPreview(Story, { size: "medium", isDocsView: context.viewMode === "docs" }) + ], + render: (_, { show, setShow }) => { + return ( + } + size="large" + onClose={() => setShow(false)} + style={{ height: 400 }} + > + + + + + Modal content will appear here, you can custom it however you want, according to the user needs. Please + make sure that the content is clear for completing the relevant task. + + + + side by side placeholder + + + setShow(false) }} + secondaryButton={{ text: "Cancel", onClick: () => setShow(false) }} + /> + + ); + } +}; + +export const Animation: Story = { + render: () => { + const [showAnchor, setShowAnchor] = useState(false); + const [showCenterPop, setShowCenterPop] = useState(false); + const [showTransition, setShowTransition] = useState(false); + + const anchorButtonRef = useRef(null); + + const transitionSteps = [ + + + + + Modal content will appear here, you can custom it however you want, according to the user needs. Please make + sure that the content is clear for completing the relevant task. + + + + side by side placeholder + + , + + + + + Modal content will appear here, you can custom it however you want, according to the user needs. Please make + sure that the content is clear for completing the relevant task. + + + + side by side placeholder + + , + + + + + Modal content will appear here, you can custom it however you want, according to the user needs. Please make + sure that the content is clear for completing the relevant task. + + + + side by side placeholder + + + ]; + + const { activeStep, direction, next, back, isFirstStep, goToStep } = useWizard({ + stepCount: transitionSteps.length + }); + + return ( + <> + + + + + + {createPortal( + setShowAnchor(false)} + style={{ height: 400 }} + > + + + + + Modal content will appear here, you can custom it however you want, according to the user needs. + Please make sure that the content is clear for completing the relevant task. + + + + side by side placeholder + + + setShowAnchor(false) }} + secondaryButton={{ text: "Cancel", onClick: () => setShowAnchor(false) }} + /> + , + document.body + )} + {createPortal( + setShowCenterPop(false)} + style={{ height: 400 }} + > + + + + + Modal content will appear here, you can custom it however you want, according to the user needs. + Please make sure that the content is clear for completing the relevant task. + + + + side by side placeholder + + + setShowCenterPop(false) }} + secondaryButton={{ text: "Cancel", onClick: () => setShowCenterPop(false) }} + /> + , + document.body + )} + {createPortal( + setShowTransition(false)} + style={{ height: 400 }} + > + + {transitionSteps} + + + , + document.body + )} + + ); + } +}; diff --git a/packages/core/src/components/ModalNew/layouts/ModalSideBySideLayout/__stories__/ModalSideBySideLayoutRelatedComponent.tsx b/packages/core/src/components/ModalNew/layouts/ModalSideBySideLayout/__stories__/ModalSideBySideLayoutRelatedComponent.tsx new file mode 100644 index 0000000000..33bd78654f --- /dev/null +++ b/packages/core/src/components/ModalNew/layouts/ModalSideBySideLayout/__stories__/ModalSideBySideLayoutRelatedComponent.tsx @@ -0,0 +1,24 @@ +import React, { useMemo } from "react"; +import { RelatedComponent } from "vibe-storybook-components"; +import relatedComponentImage from "./assets/related-component.png"; + +export const ModalSideBySideLayoutRelatedComponent = () => { + const component = useMemo(() => { + return ( + modal with side by side layout variation + ); + }, []); + + return ( + + ); +}; diff --git a/packages/core/src/components/ModalNew/layouts/ModalSideBySideLayout/__stories__/assets/breakdown-do.png b/packages/core/src/components/ModalNew/layouts/ModalSideBySideLayout/__stories__/assets/breakdown-do.png new file mode 100644 index 0000000000..31c8267988 Binary files /dev/null and b/packages/core/src/components/ModalNew/layouts/ModalSideBySideLayout/__stories__/assets/breakdown-do.png differ diff --git a/packages/core/src/components/ModalNew/layouts/ModalSideBySideLayout/__stories__/assets/breakdown-dont.png b/packages/core/src/components/ModalNew/layouts/ModalSideBySideLayout/__stories__/assets/breakdown-dont.png new file mode 100644 index 0000000000..ff93a91e29 Binary files /dev/null and b/packages/core/src/components/ModalNew/layouts/ModalSideBySideLayout/__stories__/assets/breakdown-dont.png differ diff --git a/packages/core/src/components/ModalNew/layouts/ModalSideBySideLayout/__stories__/assets/columns-do.png b/packages/core/src/components/ModalNew/layouts/ModalSideBySideLayout/__stories__/assets/columns-do.png new file mode 100644 index 0000000000..694392b327 Binary files /dev/null and b/packages/core/src/components/ModalNew/layouts/ModalSideBySideLayout/__stories__/assets/columns-do.png differ diff --git a/packages/core/src/components/ModalNew/layouts/ModalSideBySideLayout/__stories__/assets/columns-dont.png b/packages/core/src/components/ModalNew/layouts/ModalSideBySideLayout/__stories__/assets/columns-dont.png new file mode 100644 index 0000000000..97197cecb6 Binary files /dev/null and b/packages/core/src/components/ModalNew/layouts/ModalSideBySideLayout/__stories__/assets/columns-dont.png differ diff --git a/packages/core/src/components/ModalNew/layouts/ModalSideBySideLayout/__stories__/assets/media-image.png b/packages/core/src/components/ModalNew/layouts/ModalSideBySideLayout/__stories__/assets/media-image.png new file mode 100644 index 0000000000..9981ce2496 Binary files /dev/null and b/packages/core/src/components/ModalNew/layouts/ModalSideBySideLayout/__stories__/assets/media-image.png differ diff --git a/packages/core/src/components/ModalNew/layouts/ModalSideBySideLayout/__stories__/assets/related-component.png b/packages/core/src/components/ModalNew/layouts/ModalSideBySideLayout/__stories__/assets/related-component.png new file mode 100644 index 0000000000..bb2f2af2b1 Binary files /dev/null and b/packages/core/src/components/ModalNew/layouts/ModalSideBySideLayout/__stories__/assets/related-component.png differ diff --git a/packages/core/src/components/ModalNew/layouts/ModalSideBySideLayout/__stories__/assets/wizard-do.png b/packages/core/src/components/ModalNew/layouts/ModalSideBySideLayout/__stories__/assets/wizard-do.png new file mode 100644 index 0000000000..84b83a886f Binary files /dev/null and b/packages/core/src/components/ModalNew/layouts/ModalSideBySideLayout/__stories__/assets/wizard-do.png differ diff --git a/packages/core/src/components/ModalNew/layouts/ModalSideBySideLayout/__stories__/assets/wizard-dont.png b/packages/core/src/components/ModalNew/layouts/ModalSideBySideLayout/__stories__/assets/wizard-dont.png new file mode 100644 index 0000000000..fca73f6772 Binary files /dev/null and b/packages/core/src/components/ModalNew/layouts/ModalSideBySideLayout/__stories__/assets/wizard-dont.png differ diff --git a/packages/core/src/storybook/components/related-components/component-description-map.tsx b/packages/core/src/storybook/components/related-components/component-description-map.tsx index 6f218e0c5c..74029fd604 100644 --- a/packages/core/src/storybook/components/related-components/component-description-map.tsx +++ b/packages/core/src/storybook/components/related-components/component-description-map.tsx @@ -59,6 +59,9 @@ import { BoxDescription } from "./descriptions/box-description"; import { TableDescription } from "./descriptions/table-description"; import { VirtualizedGridDescription } from "./descriptions/virtualized-grid-description/virtualized-grid-description"; import { MenuGridItemDescription } from "./descriptions/menu-grid-item-description"; +import { ModalMediaLayoutRelatedComponent } from "../../../components/ModalNew/layouts/ModalMediaLayout/__stories__/ModalMediaLayoutRelatedComponent"; +import { ModalSideBySideLayoutRelatedComponent } from "../../../components/ModalNew/layouts/ModalSideBySideLayout/__stories__/ModalSideBySideLayoutRelatedComponent"; +import { ModalBasicLayoutRelatedComponent } from "../../../components/ModalNew/layouts/ModalBasicLayout/__stories__/ModalBasicLayoutRelatedComponent"; export const SPLIT_BUTTON = "split-button"; export const BUTTON_GROUP = "button-group"; @@ -81,6 +84,9 @@ export const TOAST = "toast"; export const BADGE = "badge"; export const MULTI_STEP_INDICATOR = "wizard"; export const TIPSEEN = "tipseen"; +export const MODAL_BASIC_LAYOUT = "modal-basic-layout"; +export const MODAL_SIDE_BY_SIDE_LAYOUT = "modal-side-by-side-layout"; +export const MODAL_MEDIA_LAYOUT = "modal-media-layout"; export const TEXT_FIELD = "text-field"; export const SEARCH = "search"; export const COMBOBOX = "combobox"; @@ -153,6 +159,9 @@ const COMPONENTS_DESCRIPTIONS_ENTRIES: [string, JSX.Element][] = [ [STEPS, ], [SPINNER, ], [SKELETON, ], + [MODAL_BASIC_LAYOUT, ], + [MODAL_SIDE_BY_SIDE_LAYOUT, ], + [MODAL_MEDIA_LAYOUT, ], [SLIDER, ], [ICON_BUTTON, ], [MENU_BUTTON, ],