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:
Valera Melnikov 2023-11-15 16:50:44 +03:00 committed by GitHub
parent 90bb8532a0
commit 3196b9c452
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 457 additions and 212 deletions

View File

@ -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);

View File

@ -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);

View File

@ -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";

View File

@ -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;

View File

@ -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,
],
);
}

View File

@ -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

View File

@ -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>
);
};

View File

@ -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>;
};

View File

@ -26,7 +26,7 @@ export const ModalFooter = (props: ModalFooterProps) => {
</Button>
{onSubmit && (
<Button autoFocus isLoading={isLoading} onPress={handleSubmit}>
<Button isLoading={isLoading} onPress={handleSubmit}>
{submitText}
</Button>
)}

View File

@ -2,3 +2,4 @@ export * from "./Modal";
export * from "./ModalHeader";
export * from "./ModalFooter";
export * from "./ModalBody";
export * from "./ModalContent";

View File

@ -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"],

View File

@ -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;

View File

@ -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>
);
};

View File

@ -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>
</>
);
};

View File

@ -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>
);
};
`}
/>

View File

@ -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>
);
};

View File

@ -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>
</>
);
};