feat: wds modal improvements (#28829)
## Description - Add the ability to use a dialog without a trigger component. - Add the ability to pass a triggerRef to the component that helps to set the correct and aria attributes to trigger. #### PR fixes following issue(s) Fixes #28814 #### Type of change - New feature (non-breaking change which adds functionality) #### How Has This Been Tested? > Please describe the tests that you ran to verify your changes. Also list any relevant details for your test configuration. > Delete anything that is not relevant - [x] Manual - [ ] JUnit - [ ] Jest - [ ] Cypress ## Checklist: #### Dev activity - [x] My code follows the style guidelines of this project - [x] I have performed a self-review of my own code - [x] I have commented my code, particularly in hard-to-understand areas - [x] I have made corresponding changes to the documentation - [x] My changes generate no new warnings - [ ] I have added tests that prove my fix is effective or that my feature works - [x] New and existing unit tests pass locally with my changes - [ ] PR is being merged under a feature flag
This commit is contained in:
parent
90bb8532a0
commit
3196b9c452
|
|
@ -1,9 +1,8 @@
|
|||
import React, { forwardRef } from "react";
|
||||
import React, { forwardRef, useEffect } from "react";
|
||||
import {
|
||||
FloatingFocusManager,
|
||||
FloatingPortal,
|
||||
useMergeRefs,
|
||||
FloatingOverlay,
|
||||
useTransitionStatus,
|
||||
} from "@floating-ui/react";
|
||||
import { usePopoverContext } from "./PopoverContext";
|
||||
|
|
@ -16,65 +15,56 @@ const _PopoverContent = (props: PopoverContentProps, ref: Ref<HTMLElement>) => {
|
|||
children,
|
||||
closeOnFocusOut = true,
|
||||
contentClassName,
|
||||
overlayClassName,
|
||||
style,
|
||||
...rest
|
||||
} = props;
|
||||
const { context, descriptionId, duration, getFloatingProps, labelId, modal } =
|
||||
usePopoverContext();
|
||||
const {
|
||||
context,
|
||||
descriptionId,
|
||||
duration,
|
||||
getFloatingProps,
|
||||
labelId,
|
||||
onClose,
|
||||
} = usePopoverContext();
|
||||
const refs = useMergeRefs([context.refs.setFloating, ref]);
|
||||
const { isMounted, status } = useTransitionStatus(context, { duration });
|
||||
|
||||
useEffect(() => {
|
||||
if (!isMounted && status === "close" && onClose) {
|
||||
onClose();
|
||||
}
|
||||
}, [isMounted, status]);
|
||||
|
||||
if (!Boolean(isMounted)) return null;
|
||||
|
||||
const root = context.refs.domReference.current?.closest(
|
||||
"[data-theme-provider]",
|
||||
) as HTMLElement;
|
||||
|
||||
const content = (
|
||||
<div
|
||||
aria-describedby={descriptionId}
|
||||
aria-labelledby={labelId}
|
||||
className={contentClassName}
|
||||
data-status={status}
|
||||
ref={refs}
|
||||
style={{
|
||||
...(modal ? {} : context.floatingStyles),
|
||||
...style,
|
||||
}}
|
||||
{...getFloatingProps(rest)}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
|
||||
if (modal) {
|
||||
return (
|
||||
<FloatingPortal root={root}>
|
||||
<FloatingOverlay className={overlayClassName} data-status={status}>
|
||||
<FloatingFocusManager
|
||||
closeOnFocusOut={closeOnFocusOut}
|
||||
context={context}
|
||||
modal
|
||||
>
|
||||
{content}
|
||||
</FloatingFocusManager>
|
||||
</FloatingOverlay>
|
||||
</FloatingPortal>
|
||||
);
|
||||
} else {
|
||||
return (
|
||||
<FloatingPortal root={root}>
|
||||
<FloatingFocusManager
|
||||
closeOnFocusOut={closeOnFocusOut}
|
||||
context={context}
|
||||
modal={false}
|
||||
return (
|
||||
<FloatingPortal root={root}>
|
||||
<FloatingFocusManager
|
||||
closeOnFocusOut={closeOnFocusOut}
|
||||
context={context}
|
||||
modal={false}
|
||||
>
|
||||
<div
|
||||
aria-describedby={descriptionId}
|
||||
aria-labelledby={labelId}
|
||||
className={contentClassName}
|
||||
data-status={status}
|
||||
ref={refs}
|
||||
style={{
|
||||
...context.floatingStyles,
|
||||
...style,
|
||||
}}
|
||||
{...getFloatingProps(rest)}
|
||||
>
|
||||
{content}
|
||||
</FloatingFocusManager>
|
||||
</FloatingPortal>
|
||||
);
|
||||
}
|
||||
{children}
|
||||
</div>
|
||||
</FloatingFocusManager>
|
||||
</FloatingPortal>
|
||||
);
|
||||
};
|
||||
|
||||
export const PopoverContent = forwardRef(_PopoverContent);
|
||||
|
|
|
|||
|
|
@ -0,0 +1,113 @@
|
|||
import React, { forwardRef, useEffect, useState } from "react";
|
||||
import {
|
||||
FloatingFocusManager,
|
||||
FloatingPortal,
|
||||
useMergeRefs,
|
||||
FloatingOverlay,
|
||||
useTransitionStatus,
|
||||
} from "@floating-ui/react";
|
||||
import { usePopoverContext } from "./PopoverContext";
|
||||
|
||||
import type { Ref } from "react";
|
||||
import type { PopoverModalContentProps } from "./types";
|
||||
|
||||
const setAriaAttrs = (
|
||||
triggerElem: HTMLElement,
|
||||
referenceProps: Record<string, unknown>,
|
||||
) => {
|
||||
if (Boolean(referenceProps["aria-controls"])) {
|
||||
triggerElem?.setAttribute(
|
||||
"aria-controls",
|
||||
referenceProps["aria-controls"] as string,
|
||||
);
|
||||
}
|
||||
|
||||
triggerElem?.setAttribute(
|
||||
"aria-expanded",
|
||||
referenceProps["aria-expanded"] as string,
|
||||
);
|
||||
|
||||
triggerElem?.setAttribute(
|
||||
"aria-haspopup",
|
||||
referenceProps["aria-haspopup"] as string,
|
||||
);
|
||||
};
|
||||
|
||||
const _PopoverModalContent = (
|
||||
props: PopoverModalContentProps,
|
||||
ref: Ref<HTMLElement>,
|
||||
) => {
|
||||
const {
|
||||
children,
|
||||
closeOnFocusOut = true,
|
||||
contentClassName,
|
||||
overlayClassName,
|
||||
style,
|
||||
...rest
|
||||
} = props;
|
||||
const {
|
||||
context,
|
||||
descriptionId,
|
||||
duration,
|
||||
getFloatingProps,
|
||||
getReferenceProps,
|
||||
initialFocus,
|
||||
labelId,
|
||||
onClose,
|
||||
triggerRef,
|
||||
} = usePopoverContext();
|
||||
const refs = useMergeRefs([context.refs.setFloating, ref]);
|
||||
const [root, setRoot] = useState<Element | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (triggerRef?.current != null) {
|
||||
setRoot(triggerRef.current?.closest("[data-theme-provider]"));
|
||||
} else {
|
||||
setRoot(document.body.querySelector("[data-theme-provider]"));
|
||||
}
|
||||
}, [triggerRef?.current]);
|
||||
|
||||
const referenceProps = getReferenceProps({ ref: refs });
|
||||
useEffect(() => {
|
||||
if (triggerRef?.current != null) {
|
||||
setAriaAttrs(triggerRef?.current, referenceProps);
|
||||
}
|
||||
}, [referenceProps, triggerRef?.current]);
|
||||
|
||||
const { isMounted, status } = useTransitionStatus(context, { duration });
|
||||
|
||||
useEffect(() => {
|
||||
if (!isMounted && status === "close" && onClose) {
|
||||
onClose();
|
||||
}
|
||||
}, [isMounted, status]);
|
||||
|
||||
if (!Boolean(isMounted)) return null;
|
||||
|
||||
return (
|
||||
<FloatingPortal root={root as HTMLElement}>
|
||||
<FloatingOverlay className={overlayClassName} data-status={status}>
|
||||
<FloatingFocusManager
|
||||
closeOnFocusOut={closeOnFocusOut}
|
||||
context={context}
|
||||
initialFocus={initialFocus}
|
||||
modal
|
||||
>
|
||||
<div
|
||||
aria-describedby={descriptionId}
|
||||
aria-labelledby={labelId}
|
||||
className={contentClassName}
|
||||
data-status={status}
|
||||
ref={refs}
|
||||
style={style}
|
||||
{...getFloatingProps(rest)}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
</FloatingFocusManager>
|
||||
</FloatingOverlay>
|
||||
</FloatingPortal>
|
||||
);
|
||||
};
|
||||
|
||||
export const PopoverModalContent = forwardRef(_PopoverModalContent);
|
||||
|
|
@ -1,5 +1,6 @@
|
|||
export { Popover } from "./Popover";
|
||||
export { PopoverContent } from "./PopoverContent";
|
||||
export { PopoverModalContent } from "./PopoverModalContent";
|
||||
export { PopoverTrigger } from "./PopoverTrigger";
|
||||
export { usePopoverContext } from "./PopoverContext";
|
||||
export * from "./types";
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
import type { MutableRefObject } from "react";
|
||||
import type { CSSProperties } from "react";
|
||||
import type { ReactNode } from "react";
|
||||
import type { Dispatch, SetStateAction } from "react";
|
||||
|
|
@ -42,6 +43,10 @@ export interface PopoverProps {
|
|||
onClose?: () => void;
|
||||
/** Open and close animation durations. */
|
||||
duration?: number;
|
||||
/** Ref of trigger element. This ref is necessary for adding aria attributes to trigger */
|
||||
triggerRef?: MutableRefObject<HTMLElement | null>;
|
||||
/** Which element to initially focus. Can be either a number (tabbable index as specified by the order) or a ref. */
|
||||
initialFocus?: number | MutableRefObject<HTMLElement | null>;
|
||||
}
|
||||
|
||||
export interface PopoverContentProps {
|
||||
|
|
@ -54,12 +59,15 @@ export interface PopoverContentProps {
|
|||
closeOnFocusOut?: boolean;
|
||||
/** Sets inline [style](https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/style) for the element. Only use as a **last resort**. Use style props instead. */
|
||||
style?: CSSProperties;
|
||||
/** Sets the CSS className for the overlay. Only use as a **last resort**. */
|
||||
overlayClassName?: string;
|
||||
/** Sets the CSS className for the content popover. Only use as a **last resort**. */
|
||||
contentClassName?: string;
|
||||
}
|
||||
|
||||
export interface PopoverModalContentProps extends PopoverContentProps {
|
||||
/** Sets the CSS className for the overlay. Only use as a **last resort**. */
|
||||
overlayClassName?: string;
|
||||
}
|
||||
|
||||
export interface PopoverTriggerProps {
|
||||
/** The children of the component. */
|
||||
children: ReactNode;
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import type { UseFloatingOptions } from "@floating-ui/react/src/types";
|
||||
import React, { useState } from "react";
|
||||
import { useState, useMemo } from "react";
|
||||
import {
|
||||
autoUpdate,
|
||||
flip,
|
||||
|
|
@ -18,17 +18,18 @@ const DEFAULT_POPOVER_OFFSET = 10;
|
|||
export function usePopover({
|
||||
defaultOpen = false,
|
||||
duration = 0,
|
||||
initialFocus,
|
||||
isOpen: controlledOpen,
|
||||
modal = false,
|
||||
offset: offsetProp = DEFAULT_POPOVER_OFFSET,
|
||||
onClose,
|
||||
placement = "bottom",
|
||||
setOpen: setControlledOpen,
|
||||
triggerRef,
|
||||
}: PopoverProps = {}) {
|
||||
const [uncontrolledOpen, setUncontrolledOpen] = useState(defaultOpen);
|
||||
const [labelId, setLabelId] = useState<string | undefined>();
|
||||
const [descriptionId, setDescriptionId] = useState<string | undefined>();
|
||||
|
||||
const open = controlledOpen ?? uncontrolledOpen;
|
||||
const setOpen = setControlledOpen ?? setUncontrolledOpen;
|
||||
|
||||
|
|
@ -48,13 +49,6 @@ export function usePopover({
|
|||
const data = useFloating({
|
||||
open,
|
||||
onOpenChange: setOpen,
|
||||
whileElementsMounted: () => {
|
||||
return () => {
|
||||
if (onClose) {
|
||||
onClose();
|
||||
}
|
||||
};
|
||||
},
|
||||
...(modal ? {} : config),
|
||||
});
|
||||
|
||||
|
|
@ -66,7 +60,7 @@ export function usePopover({
|
|||
const role = useRole(context);
|
||||
const interactions = useInteractions([click, dismiss, role]);
|
||||
|
||||
return React.useMemo(
|
||||
return useMemo(
|
||||
() => ({
|
||||
open,
|
||||
setOpen,
|
||||
|
|
@ -78,7 +72,21 @@ export function usePopover({
|
|||
setLabelId,
|
||||
setDescriptionId,
|
||||
duration,
|
||||
triggerRef,
|
||||
initialFocus,
|
||||
onClose,
|
||||
}),
|
||||
[open, setOpen, interactions, data, modal, labelId, descriptionId],
|
||||
[
|
||||
open,
|
||||
setOpen,
|
||||
interactions,
|
||||
data,
|
||||
modal,
|
||||
labelId,
|
||||
descriptionId,
|
||||
triggerRef,
|
||||
initialFocus,
|
||||
onClose,
|
||||
],
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ import {
|
|||
Popover,
|
||||
PopoverTrigger,
|
||||
PopoverContent,
|
||||
PopoverModalContent,
|
||||
Button,
|
||||
} from "@design-system/headless";
|
||||
import { ControlledPopover } from "./ControlledPopover";
|
||||
|
|
@ -46,16 +47,31 @@ Based on the [headless Popover component](/?path=/docs/design-system-headless-po
|
|||
|
||||
A modal is a floating element that displays information that requires immediate attention, appearing over the page content and blocking interactions with the page until it is dismissed.
|
||||
|
||||
<Canvas>
|
||||
<Story name="Modal">
|
||||
<Popover modal>
|
||||
<PopoverTrigger>
|
||||
<Button>Modal trigger</Button>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent>My modal content</PopoverContent>
|
||||
</Popover>
|
||||
</Story>
|
||||
</Canvas>
|
||||
## PopoverModalContent props
|
||||
|
||||
<ArgsTable of={PopoverModalContent} />
|
||||
|
||||
<Source
|
||||
dark
|
||||
code={`
|
||||
export const Modal = () => {
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
const ref = useRef(null);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Button onPress={() => setIsOpen(!isOpen)} ref={ref}>
|
||||
Controlled popover
|
||||
</Button>
|
||||
<Popover isOpen={isOpen} setOpen={setIsOpen} triggerRef={ref}>
|
||||
<PopoverContent>Controlled popover content</PopoverContent>
|
||||
</Popover>
|
||||
</>
|
||||
);
|
||||
|
||||
};
|
||||
`}
|
||||
/>
|
||||
|
||||
# Placement
|
||||
|
||||
|
|
|
|||
|
|
@ -1,9 +1,5 @@
|
|||
import React, { Children } from "react";
|
||||
import {
|
||||
Popover,
|
||||
PopoverTrigger,
|
||||
PopoverContent,
|
||||
} from "@design-system/headless";
|
||||
import React from "react";
|
||||
import { Popover, PopoverModalContent } from "@design-system/headless";
|
||||
import styles from "./styles.module.css";
|
||||
import type { ModalProps } from "./types";
|
||||
import clsx from "clsx";
|
||||
|
|
@ -11,23 +7,21 @@ import clsx from "clsx";
|
|||
export const Modal = (props: ModalProps) => {
|
||||
const {
|
||||
children,
|
||||
contentClassName,
|
||||
overlayClassName,
|
||||
size = "medium",
|
||||
triggerRef,
|
||||
...rest
|
||||
} = props;
|
||||
const [trigger, ...content] = Children.toArray(children);
|
||||
|
||||
return (
|
||||
<Popover duration={200} modal {...rest}>
|
||||
<PopoverTrigger>{trigger}</PopoverTrigger>
|
||||
<PopoverContent
|
||||
contentClassName={clsx(styles.content, contentClassName)}
|
||||
// don't forget to change the transition-duration CSS as well
|
||||
<Popover duration={200} modal triggerRef={triggerRef} {...rest}>
|
||||
<PopoverModalContent
|
||||
data-size={size}
|
||||
overlayClassName={clsx(styles.overlay, overlayClassName)}
|
||||
>
|
||||
{content}
|
||||
</PopoverContent>
|
||||
{children}
|
||||
</PopoverModalContent>
|
||||
</Popover>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -0,0 +1,10 @@
|
|||
import React from "react";
|
||||
import styles from "./styles.module.css";
|
||||
import clsx from "clsx";
|
||||
|
||||
import type { ModalContentProps } from "./types";
|
||||
|
||||
export const ModalContent = (props: ModalContentProps) => {
|
||||
const { children, className } = props;
|
||||
return <div className={clsx(styles.content, className)}>{children}</div>;
|
||||
};
|
||||
|
|
@ -26,7 +26,7 @@ export const ModalFooter = (props: ModalFooterProps) => {
|
|||
</Button>
|
||||
|
||||
{onSubmit && (
|
||||
<Button autoFocus isLoading={isLoading} onPress={handleSubmit}>
|
||||
<Button isLoading={isLoading} onPress={handleSubmit}>
|
||||
{submitText}
|
||||
</Button>
|
||||
)}
|
||||
|
|
|
|||
|
|
@ -2,3 +2,4 @@ export * from "./Modal";
|
|||
export * from "./ModalHeader";
|
||||
export * from "./ModalFooter";
|
||||
export * from "./ModalBody";
|
||||
export * from "./ModalContent";
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
background: var(--color-bg-neutral-opacity);
|
||||
display: grid;
|
||||
place-items: center;
|
||||
z-index: var(--z-index-99);
|
||||
}
|
||||
|
||||
.content {
|
||||
|
|
@ -18,15 +19,15 @@
|
|||
padding-block: var(--outer-spacing-4);
|
||||
}
|
||||
|
||||
.content[data-size="small"] {
|
||||
[data-size="small"] .content {
|
||||
width: var(--sizing-120);
|
||||
}
|
||||
|
||||
.content[data-size="medium"] {
|
||||
[data-size="medium"] .content {
|
||||
width: var(--sizing-180);
|
||||
}
|
||||
|
||||
.content[data-size="large"] {
|
||||
[data-size="large"] .content {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
|
@ -47,6 +48,7 @@
|
|||
.content[data-status="close"],
|
||||
.overlay[data-status="open"],
|
||||
.overlay[data-status="close"] {
|
||||
/* don't forget to change the duration Modal.tsx as well */
|
||||
transition-duration: 200ms;
|
||||
}
|
||||
.content[data-status="initial"],
|
||||
|
|
|
|||
|
|
@ -1,12 +1,15 @@
|
|||
import type {
|
||||
PopoverContentProps,
|
||||
PopoverModalContentProps,
|
||||
PopoverProps,
|
||||
} from "@design-system/headless";
|
||||
import type { ReactNode } from "react";
|
||||
|
||||
export interface ModalProps
|
||||
extends Pick<PopoverProps, "defaultOpen" | "isOpen" | "setOpen" | "onClose">,
|
||||
Pick<PopoverContentProps, "overlayClassName" | "contentClassName"> {
|
||||
extends Pick<
|
||||
PopoverProps,
|
||||
"isOpen" | "setOpen" | "onClose" | "triggerRef" | "initialFocus"
|
||||
>,
|
||||
Pick<PopoverModalContentProps, "overlayClassName"> {
|
||||
/** Size of the Modal
|
||||
* @default medium
|
||||
*/
|
||||
|
|
@ -15,6 +18,13 @@ export interface ModalProps
|
|||
children: ReactNode;
|
||||
}
|
||||
|
||||
export interface ModalContentProps {
|
||||
/** The children of the component. */
|
||||
children: ReactNode;
|
||||
/** Sets the CSS className for the overlay. Only use as a **last resort**. */
|
||||
className?: string;
|
||||
}
|
||||
|
||||
export interface ModalHeaderProps {
|
||||
/** Adds a header modal Title and the necessary aria attributes. */
|
||||
title: string;
|
||||
|
|
|
|||
|
|
@ -1,15 +0,0 @@
|
|||
import React, { useState } from "react";
|
||||
import { Modal, ModalBody, Button } from "@design-system/widgets";
|
||||
|
||||
export const ControlledModal = () => {
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
return (
|
||||
<Modal isOpen={isOpen} setOpen={setIsOpen}>
|
||||
<Button>My modal trigger</Button>
|
||||
<ModalBody>
|
||||
Lorem ipsum dolor sit amet, consectetur adipisicing elit. Doloribus,
|
||||
vero!
|
||||
</ModalBody>
|
||||
</Modal>
|
||||
);
|
||||
};
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
import React, { useRef, useState } from "react";
|
||||
import { Modal, ModalBody, ModalContent, Button } from "@design-system/widgets";
|
||||
|
||||
export const CustomModal = () => {
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
const ref = useRef(null);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Button onPress={() => setIsOpen(!isOpen)} ref={ref}>
|
||||
Modal trigger
|
||||
</Button>
|
||||
<Modal isOpen={isOpen} setOpen={setIsOpen} triggerRef={ref}>
|
||||
<div style={{ border: "4px solid rgb(255, 155, 78)" }}>
|
||||
<ModalContent>
|
||||
<ModalBody>
|
||||
Lorem ipsum dolor sit amet, consectetur adipisicing elit. Alias
|
||||
amet animi corporis laboriosam libero voluptas! A, reiciendis,
|
||||
veniam?
|
||||
</ModalBody>
|
||||
</ModalContent>
|
||||
</div>
|
||||
</Modal>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
|
@ -1,24 +1,18 @@
|
|||
import {
|
||||
Button,
|
||||
Modal,
|
||||
ModalHeader,
|
||||
ModalBody,
|
||||
ModalFooter,
|
||||
ButtonGroup,
|
||||
ButtonGroupItem,
|
||||
ModalContent,
|
||||
} from "@design-system/widgets";
|
||||
import { Canvas, Meta, Story, ArgsTable, Source } from "@storybook/addon-docs";
|
||||
import { ControlledModal } from "./ControlledModal";
|
||||
import { Canvas, Meta, Story, ArgsTable } from "@storybook/addon-docs";
|
||||
import { SimpleModal } from "./SimpleModal";
|
||||
import { ModalExamples } from "./ModalExamples";
|
||||
import { CustomModal } from "./CustomModal";
|
||||
|
||||
<Meta title="Design-system/Widgets/Modal" component={Modal} />
|
||||
|
||||
export const Template = (args) => {
|
||||
return (
|
||||
<Modal {...args}>
|
||||
<Button>My modal trigger</Button>
|
||||
<div>My modal content</div>
|
||||
</Modal>
|
||||
);
|
||||
return <SimpleModal {...args} />;
|
||||
};
|
||||
|
||||
# Modal
|
||||
|
|
@ -35,13 +29,17 @@ Modal developed on basis of Popover headless component. Additional information a
|
|||
|
||||
<ArgsTable story="Modal" of={Modal} />
|
||||
|
||||
## ModalContent props
|
||||
|
||||
<ArgsTable of={ModalContent} />
|
||||
|
||||
## ModalHeader props
|
||||
|
||||
<ArgsTable of={ModalHeader} />
|
||||
|
||||
## ModalBody props
|
||||
|
||||
ModalBody component has no props, it is responsible for the correct layout and supports the built-in browser scroll.
|
||||
`ModalBody` component has no props, it is responsible for the correct layout and supports the built-in browser scroll.
|
||||
|
||||
## ModalFooter props
|
||||
|
||||
|
|
@ -49,101 +47,12 @@ ModalBody component has no props, it is responsible for the correct layout and s
|
|||
|
||||
# ModalExamples
|
||||
|
||||
export const fakeSubmit = async () => {
|
||||
return new Promise((resolve) =>
|
||||
setTimeout(() => {
|
||||
alert("Submitted");
|
||||
resolve();
|
||||
}, 500),
|
||||
);
|
||||
};
|
||||
|
||||
<Canvas>
|
||||
<Story name="Modal Examples">
|
||||
<ButtonGroup>
|
||||
<Modal size="small">
|
||||
<ButtonGroupItem>Small</ButtonGroupItem>
|
||||
<ModalHeader title="Modal title" />
|
||||
<ModalBody>
|
||||
Lorem ipsum dolor sit amet, consectetur adipisicing elit. Ad amet
|
||||
aperiam assumenda cupiditate dicta dolore error expedita explicabo ipsum
|
||||
iure mollitia nam quo, reiciendis repellat reprehenderit vel vitae.
|
||||
Delectus dolores illo labore laudantium nihil nobis placeat soluta.
|
||||
Dolorum esse et laboriosam libero nesciunt non placeat similique? Alias
|
||||
animi at commodi cum delectus, ducimus earum enim esse exercitationem
|
||||
facere facilis fugit illo illum laborum laudantium modi numquam officia
|
||||
pariatur praesentium quos rem suscipit vel voluptas. Ab adipisci
|
||||
asperiores blanditiis corporis, cum eligendi et, incidunt laborum nulla
|
||||
odit quaerat quibusdam ut, velit. Animi assumenda at doloremque eius
|
||||
facilis harum, libero praesentium quo.
|
||||
</ModalBody>
|
||||
<ModalFooter onSubmit={fakeSubmit} />
|
||||
</Modal>
|
||||
|
||||
<Modal size="medium">
|
||||
<ButtonGroupItem>Medium</ButtonGroupItem>
|
||||
<ModalHeader title="Modal title" />
|
||||
<ModalBody>
|
||||
Lorem ipsum dolor sit amet, consectetur adipisicing elit. Ad amet
|
||||
aperiam assumenda cupiditate dicta dolore error expedita explicabo ipsum
|
||||
iure mollitia nam quo, reiciendis repellat reprehenderit vel vitae.
|
||||
Delectus dolores illo labore laudantium nihil nobis placeat soluta.
|
||||
Dolorum esse et laboriosam libero nesciunt non placeat similique? Alias
|
||||
animi at commodi cum delectus, ducimus earum enim esse exercitationem
|
||||
facere facilis fugit illo illum laborum laudantium modi numquam officia
|
||||
pariatur praesentium quos rem suscipit vel voluptas. Ab adipisci
|
||||
asperiores blanditiis corporis, cum eligendi et, incidunt laborum nulla
|
||||
odit quaerat quibusdam ut, velit. Animi assumenda at doloremque eius
|
||||
facilis harum, libero praesentium quo.
|
||||
</ModalBody>
|
||||
<ModalFooter onSubmit={fakeSubmit} />
|
||||
</Modal>
|
||||
|
||||
<Modal size="large">
|
||||
<ButtonGroupItem>Large</ButtonGroupItem>
|
||||
<ModalHeader title="Modal title" />
|
||||
<ModalBody>
|
||||
Lorem ipsum dolor sit amet, consectetur adipisicing elit. Ad amet
|
||||
aperiam assumenda cupiditate dicta dolore error expedita explicabo ipsum
|
||||
iure mollitia nam quo, reiciendis repellat reprehenderit vel vitae.
|
||||
Delectus dolores illo labore laudantium nihil nobis placeat soluta.
|
||||
Dolorum esse et laboriosam libero nesciunt non placeat similique? Alias
|
||||
animi at commodi cum delectus, ducimus earum enim esse exercitationem
|
||||
facere facilis fugit illo illum laborum laudantium modi numquam officia
|
||||
pariatur praesentium quos rem suscipit vel voluptas. Ab adipisci
|
||||
asperiores blanditiis corporis, cum eligendi et, incidunt laborum nulla
|
||||
odit quaerat quibusdam ut, velit. Animi assumenda at doloremque eius
|
||||
facilis harum, libero praesentium quo.
|
||||
</ModalBody>
|
||||
<ModalFooter onSubmit={fakeSubmit} />
|
||||
</Modal>
|
||||
</ButtonGroup>
|
||||
|
||||
</Story>
|
||||
<Story name="Modal Examples">{<ModalExamples />}</Story>
|
||||
</Canvas>
|
||||
|
||||
# Controlled Modal
|
||||
# Custom Modal
|
||||
|
||||
<Canvas>
|
||||
<Story name="Controlled Modal">
|
||||
<ControlledModal />
|
||||
</Story>
|
||||
<Story name="Custom Modal">{<CustomModal />}</Story>
|
||||
</Canvas>
|
||||
|
||||
<Source
|
||||
dark
|
||||
code={`
|
||||
export const ControlledModal = () => {
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
return (
|
||||
<Modal isOpen={isOpen} setOpen={setIsOpen}>
|
||||
<Button>My modal trigger</Button>
|
||||
<ModalBody>
|
||||
Lorem ipsum dolor sit amet, consectetur adipisicing elit. Doloribus,
|
||||
vero!
|
||||
</ModalBody>
|
||||
</Modal>
|
||||
);
|
||||
};
|
||||
`}
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,133 @@
|
|||
import React, { useRef, useState } from "react";
|
||||
import {
|
||||
ButtonGroup,
|
||||
ButtonGroupItem,
|
||||
Modal,
|
||||
ModalBody,
|
||||
ModalContent,
|
||||
ModalFooter,
|
||||
ModalHeader,
|
||||
} from "@design-system/widgets";
|
||||
|
||||
const fakeSubmit = async () => {
|
||||
return new Promise<void>((resolve) =>
|
||||
setTimeout(() => {
|
||||
alert("Submitted");
|
||||
resolve();
|
||||
}, 500),
|
||||
);
|
||||
};
|
||||
|
||||
export const ModalExamples = () => {
|
||||
const [isSmallOpen, setSmallOpen] = useState(false);
|
||||
const [isMediumOpen, setMediumOpen] = useState(false);
|
||||
const [isLargeOpen, setLargeOpen] = useState(false);
|
||||
const smallRef = useRef(null);
|
||||
const mediumRef = useRef(null);
|
||||
const largeRef = useRef(null);
|
||||
|
||||
return (
|
||||
<ButtonGroup>
|
||||
<ButtonGroupItem
|
||||
onPress={() => setSmallOpen(!isSmallOpen)}
|
||||
ref={smallRef}
|
||||
>
|
||||
Small
|
||||
</ButtonGroupItem>
|
||||
<Modal
|
||||
initialFocus={2}
|
||||
isOpen={isSmallOpen}
|
||||
setOpen={setSmallOpen}
|
||||
size="small"
|
||||
triggerRef={smallRef}
|
||||
>
|
||||
<ModalContent>
|
||||
<ModalHeader title="Small modal title" />
|
||||
<ModalBody>
|
||||
Lorem ipsum dolor sit amet, consectetur adipisicing elit. Alias amet
|
||||
animi corporis laboriosam libero voluptas! A, reiciendis, veniam?
|
||||
</ModalBody>
|
||||
<ModalFooter onSubmit={fakeSubmit} />
|
||||
</ModalContent>
|
||||
</Modal>
|
||||
|
||||
<ButtonGroupItem
|
||||
onPress={() => setMediumOpen(!isMediumOpen)}
|
||||
ref={mediumRef}
|
||||
>
|
||||
Medium
|
||||
</ButtonGroupItem>
|
||||
<Modal
|
||||
initialFocus={2}
|
||||
isOpen={isMediumOpen}
|
||||
setOpen={setMediumOpen}
|
||||
triggerRef={mediumRef}
|
||||
>
|
||||
<ModalContent>
|
||||
<ModalHeader title="Medium modal title" />
|
||||
<ModalBody>
|
||||
Lorem ipsum dolor sit amet, consectetur adipisicing elit. Alias amet
|
||||
animi corporis laboriosam libero voluptas! A, reiciendis, veniam?
|
||||
</ModalBody>
|
||||
<ModalFooter onSubmit={fakeSubmit} />
|
||||
</ModalContent>
|
||||
</Modal>
|
||||
|
||||
<ButtonGroupItem
|
||||
onPress={() => setLargeOpen(!isLargeOpen)}
|
||||
ref={largeRef}
|
||||
>
|
||||
Large
|
||||
</ButtonGroupItem>
|
||||
<Modal
|
||||
initialFocus={2}
|
||||
isOpen={isLargeOpen}
|
||||
setOpen={setLargeOpen}
|
||||
size="large"
|
||||
triggerRef={largeRef}
|
||||
>
|
||||
<ModalContent>
|
||||
<ModalHeader title="Large modal title" />
|
||||
<ModalBody>
|
||||
Lorem ipsum dolor sit amet, consectetur adipisicing elit. Aut ex
|
||||
illo ipsa iste mollitia non nulla qui, sed. Amet aperiam aspernatur
|
||||
at autem beatae blanditiis commodi, cum delectus dignissimos ea enim
|
||||
ex hic illum ipsam ipsum iure iusto magnam mollitia nobis nulla odit
|
||||
pariatur possimus praesentium quaerat quia quo repellat repellendus
|
||||
sequi similique soluta sunt tempora tempore temporibus? Accusamus
|
||||
accusantium ad cumque deserunt dolorum enim error, excepturi
|
||||
exercitationem facere fugiat impedit in ipsum labore laboriosam
|
||||
minus modi mollitia neque nulla officiis porro, quo quos, sapiente
|
||||
totam veritatis vitae voluptas voluptatibus? Aliquid amet asperiores
|
||||
aut exercitationem facilis ipsa itaque magni nam odio reiciendis
|
||||
repellendus rerum tempore ullam, vero voluptatem! Animi cupiditate
|
||||
et minus porro recusandae, temporibus tenetur! Aliquid aperiam
|
||||
aspernatur beatae dolore eius ex exercitationem expedita fuga iste
|
||||
iusto laboriosam laudantium modi necessitatibus nemo nulla odio
|
||||
optio perferendis, placeat praesentium quae quidem rem rerum soluta
|
||||
tempore tenetur unde velit voluptas? At consequuntur corporis
|
||||
delectus earum eos nihil odio officiis, quae quis sed. Asperiores
|
||||
excepturi hic molestiae nesciunt nostrum quae temporibus. Commodi
|
||||
corporis eos illo, ipsum laboriosam molestias neque numquam rerum
|
||||
veniam veritatis. Doloribus impedit iste nulla quia. Assumenda et
|
||||
facilis id minima praesentium quaerat similique. Ad adipisci
|
||||
assumenda aut blanditiis dicta dignissimos eligendi ipsa mollitia
|
||||
natus nobis, obcaecati possimus quam quia recusandae repellat sed
|
||||
sit veniam. Animi consectetur libero praesentium temporibus velit!
|
||||
Amet atque culpa, debitis deleniti eius harum libero maxime odit
|
||||
officia officiis quibusdam, repellat sunt tempora? Accusantium
|
||||
atque, cumque doloribus eveniet laudantium magni molestias officia,
|
||||
sequi temporibus vel velit veritatis vero voluptatibus! Consequuntur
|
||||
delectus eaque minus obcaecati repellat repudiandae sapiente,
|
||||
tempora unde. Ab ad autem beatae commodi, culpa cupiditate debitis
|
||||
dolores doloribus earum eos eveniet ex excepturi expedita explicabo
|
||||
fuga incidunt inventore maxime minus modi molestias nulla odio,
|
||||
perspiciatis quam quisquam quo ratione sapiente voluptatem? Autem
|
||||
inventore quae velit.
|
||||
</ModalBody>
|
||||
<ModalFooter onSubmit={fakeSubmit} />
|
||||
</ModalContent>
|
||||
</Modal>
|
||||
</ButtonGroup>
|
||||
);
|
||||
};
|
||||
|
|
@ -0,0 +1,39 @@
|
|||
import React, { useState } from "react";
|
||||
import {
|
||||
Modal,
|
||||
ModalBody,
|
||||
ModalHeader,
|
||||
ModalFooter,
|
||||
ModalContent,
|
||||
Button,
|
||||
} from "@design-system/widgets";
|
||||
import type { ModalProps } from "../src/types";
|
||||
|
||||
const fakeSubmit = async () => {
|
||||
return new Promise<void>((resolve) =>
|
||||
setTimeout(() => {
|
||||
alert("Submitted");
|
||||
resolve();
|
||||
}, 500),
|
||||
);
|
||||
};
|
||||
|
||||
export const SimpleModal = (props: Omit<ModalProps, "children">) => {
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Button onPress={() => setIsOpen(!isOpen)}>Modal trigger</Button>
|
||||
<Modal initialFocus={2} isOpen={isOpen} setOpen={setIsOpen} {...props}>
|
||||
<ModalContent>
|
||||
<ModalHeader title="Modal title" />
|
||||
<ModalBody>
|
||||
Lorem ipsum dolor sit amet, consectetur adipisicing elit. Alias amet
|
||||
animi corporis laboriosam libero voluptas! A, reiciendis, veniam?
|
||||
</ModalBody>
|
||||
<ModalFooter onSubmit={fakeSubmit} />
|
||||
</ModalContent>
|
||||
</Modal>
|
||||
</>
|
||||
);
|
||||
};
|
||||
Loading…
Reference in New Issue
Block a user