Drawer
View .mdA drawer is a secondary content surface that slides in from one edge of a layout. Use drawers for supplemental info and simple actions related to the main content.
Default
Loading interactive example. Full source is available below.
View example source
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
import type { DrawerProps } from "@coreai-ds/react-components";
import { useId } from "@coreai-ds/react-components";
import * as React from 'react';
import { useRestoreFocusTarget } from "@coreai-ds/react-components";
import { useRestoreFocusSource } from "@coreai-ds/react-components";
import { Drawer } from "@coreai-ds/react-components";
import { DrawerHeader } from "@coreai-ds/react-components";
import { DrawerHeaderTitle } from "@coreai-ds/react-components";
import { Button } from "@coreai-ds/react-components";
import { Dismiss24Regular } from "@fluentui/react-icons";
import { DrawerBody } from "@coreai-ds/react-components";
import { ToggleButton } from "@coreai-ds/react-components";
import { Label } from "@coreai-ds/react-components";
import { RadioGroup } from "@coreai-ds/react-components";
import { Radio } from "@coreai-ds/react-components";
export const Default = () => {
type DrawerType = Required<DrawerProps>['type'];
const labelId = useId('type-label');
const [isOpen, setIsOpen] = React.useState(false);
const [type, setType] = React.useState<DrawerType>('overlay');
// all Drawers need manual focus restoration attributes
// unless (as in the case of some inline drawers, you do not want automatic focus restoration)
const restoreFocusTargetAttributes = useRestoreFocusTarget();
const restoreFocusSourceAttributes = useRestoreFocusSource();
const styles = /* css */ `
.drawer-default-example {
border: 2px solid #ccc;
overflow: hidden;
display: flex;
height: 480px;
background-color: #fff;
}
.drawer-default-example__content {
flex: 1;
padding: var(--spacingHorizontalL);
display: grid;
justify-content: flex-start;
align-items: flex-start;
row-gap: var(--spacingVerticalXXL);
grid-auto-rows: max-content;
}
.drawer-default-example__field {
display: grid;
row-gap: var(--spacingVerticalS);
}
`;
return (<>
<style>{styles}</style>
<div className="drawer-default-example">
<Drawer {...restoreFocusSourceAttributes} type={type} separator open={isOpen} onOpenChange={(_, { open }) => setIsOpen(open)}>
<DrawerHeader>
<DrawerHeaderTitle action={<Button appearance="subtle" aria-label="Close" icon={<Dismiss24Regular />} onClick={() => setIsOpen(false)}/>}>
Default Drawer
</DrawerHeaderTitle>
</DrawerHeader>
<DrawerBody>
<p>Drawer content</p>
</DrawerBody>
</Drawer>
<div className="drawer-default-example__content">
{type === 'inline' ? (<ToggleButton {...restoreFocusTargetAttributes} appearance="primary" onClick={() => setIsOpen(!isOpen)} checked={isOpen}>
Toggle
</ToggleButton>) : (<Button {...restoreFocusTargetAttributes} appearance="primary" onClick={() => setIsOpen(!isOpen)}>
Open
</Button>)}
<div className="drawer-default-example__field">
<Label id={labelId}>Type</Label>
<RadioGroup value={type} onChange={(_, data) => setType(data.value as DrawerType)} aria-labelledby={labelId}>
<Radio value="overlay" label="Overlay (Default)"/>
<Radio value="inline" label="Inline"/>
</RadioGroup>
</div>
</div>
</div>
</>);
};
export default Default;
Resources
Best practices
Do
- Reserve modal drawers for important tasks that are clear and quick to complete.
- Use non-modal overlay drawers for at-a-glance information that can be easily dismissed.
- Use a dialog to confirm actions that might cause data loss.
Don't
- Open multiple overlay drawers at the same time.
- Break established placement conventions, such as navigation on the left and notifications on the right.
- Use drawers for long blocks of text.
Behavior and states
- Overlay drawers are elevated above main page content and are modal by default.
- Drawers can be closed by interacting with the button that opened them or an included dismiss icon.
- If a drawer gathers information, alert people if they might lose progress when they close it.
- Keep multi-step tasks in drawers to two or three steps.
- Wrap body content in DrawerBody to enable scrolling when content exceeds the drawer height.
- An overlay drawer is an intrusive surface that obscures all or part of the main page content.
- Use overlay drawers to draw more attention to the content in the drawer.
- An inline drawer is a passive surface that sits side-by-side with main page content.
- Use inline drawers when it’s helpful to view and interact with page content and drawer content at the same time.
- For short information that’s related to a specific part of the main layout, a drawer may be overwhelming.
- Try a tooltip or a popover instead.
- If you need people to confirm an action they’re trying to take, use a dialog.
API referenceGenerated CoreAI prop and type summaries
API
Generated CoreAI prop summaries, not replacement type declarations. Most native DOM/event and ARIA props are omitted. ? means optional; JSDoc shows defaults, deprecations, and conditional requirements. Defaults assume the standard CoreAI provider.
Drawer API
Drawer contains supplementary content and are used for complex creation, edit, or management experiences.
interface DrawerProps {
as?: "aside" | "div";
/**
* Dimmed background of dialog.
* The default backdrop is rendered as a `<div>` with styling.
* This slot expects a `<div>` element which will replace the default backdrop.
* The backdrop should have `aria-hidden="true"`.
*
* Accepts an `appearance` prop to control backdrop visibility:
* - `'dimmed'`: Always shows a dimmed backdrop, regardless of nesting.
* - `'transparent'`: Always shows a transparent backdrop.
*/
backdrop?: WithSlotShorthandValue<{ as?: "div"; } & Omit<Omit<DetailedHTMLProps<HTMLAttributes<HTMLDivElement>, HTMLDivElement>, "ref"> & { ref?: ((instance: HTMLDivElement | null) => void) | RefObject<HTMLDivElement> | null; }, "children"> & { children?: any; } & { appearance?: "transparent" | "dimmed" | undefined; }> | null | undefined;
/**
* For more information refer to the [Motion docs page](https://react.fluentui.dev/?path=/docs/motion-motion-slot--docs).
*/
backdropMotion?: PresenceMotionSlotProps<FadeParams> | (Pick<PresenceComponentProps, "imperativeRef" | "onMotionFinish" | "onMotionStart"> & Partial<Required<Pick<DrawerBaseProps, "size">>> & { as?: JSXIntrinsicElementKeys; children?: SlotRenderFunction<PresenceMotionSlotRenderProps & Required<Pick<DrawerBaseProps, "size">> & { children: JSXElement; }>; } & Partial<FadeParams> & { as?: JSXIntrinsicElementKeys | undefined; children?: SlotRenderFunction<PresenceMotionSlotRenderProps & PresenceDuration & PresenceEasing & PresenceDelay & { outOpacity?: number | undefined; inOpacity?: number | undefined; } & { children: JSXElement; }> | undefined; }) | (Pick<PresenceComponentProps, "imperativeRef" | "onMotionFinish" | "onMotionStart"> & Partial<FadeParams> & { as?: JSXIntrinsicElementKeys | undefined; children?: SlotRenderFunction<PresenceMotionSlotRenderProps & PresenceDuration & PresenceEasing & PresenceDelay & { outOpacity?: number | undefined; inOpacity?: number | undefined; } & { children: JSXElement; }> | undefined; } & Partial<Required<Pick<DrawerBaseProps, "size">>> & { as?: JSXIntrinsicElementKeys | undefined; children?: SlotRenderFunction<PresenceMotionSlotRenderProps & Required<Pick<DrawerBaseProps, "size">> & { children: JSXElement; }> | undefined; }) | null | undefined;
children?: any;
className?: string;
/**
* @deprecated OverlayDrawer can work only as a controlled component
* and does not support uncontrolled mode i.e. defaultOpen prop
*/
defaultOpen?: boolean;
/**
* Enables standard behavior according to the [HTML dialog spec](https://developer.mozilla.org/en-US/docs/Web/API/HTMLDialogElement/showModal)
* where the focus trap involves setting outside elements inert.
* @default false
*/
inertTrapFocus?: boolean;
/**
* Dialog variations.
*
* `modal`: When this type of dialog is open, the rest of the page is dimmed out and cannot be interacted with.
* The tab sequence is kept within the dialog and moving the focus outside
* the dialog will imply closing it. This is the default type of the component.
*
* `non-modal`: When a non-modal dialog is open, the rest of the page is not dimmed out
* and users can interact with the rest of the page.
* This also implies that the tab focus can move outside the dialog when it reaches the last focusable element.
*
* `alert`: is a special type of modal dialogs that interrupts the user's workflow
* to communicate an important message or ask for a decision.
* Unlike a typical modal dialog, the user must take an action through the options given to dismiss the dialog,
* and it cannot be dismissed through the dimmed background.
* @default modal
*/
modalType?: "alert" | "modal" | "non-modal";
/**
* Where the portal children are mounted on DOM
* @default a new element on document.body without any styling
*/
mountNode?: HTMLElement | { element?: HTMLElement | null; className?: string; } | null | undefined;
/**
* Callback fired when the component changes value from open state.
*/
onOpenChange?: DialogOpenChangeEventHandler;
/**
* Controls the open state of the Drawer
* @default false
*/
open?: boolean;
/**
* Position of the drawer.
*
* Note: 'bottom' does not supports size, but it supports customized height.
* @default 'start'
*/
position?: "start" | "end" | "bottom";
ref?: Ref<HTMLElement> | ((((instance: HTMLDivElement | null) => void) | RefObject<HTMLDivElement>) & (((instance: HTMLElement | null) => void) | RefObject<HTMLElement>));
/**
* Whether the drawer has a separator line.
* @default false
*/
separator?: boolean;
/**
* Size of the drawer.
*
* - 'small' - Drawer is 320px wide.
* - 'medium' - Drawer is 592px wide.
* - 'large' - Drawer is 940px wide.
* - 'full' - Drawer is 100vw wide.
* @default 'small'
*/
size?: "small" | "medium" | "large" | "full";
style?: CSSProperties;
/**
* For more information refer to the [Motion docs page](https://react.fluentui.dev/?path=/docs/motion-motion-slot--docs).
*/
surfaceMotion?: PresenceMotionSlotProps<Required<Pick<DrawerBaseProps, "size" | "position"> & Pick<ProviderContextValue_unstable, "dir">>> | SurfaceMotionSlotProps | null;
/**
* Type of the drawer.
*
* - 'overlay' - Drawer is hidden by default and can be opened by clicking on the trigger.
* - 'inline' - Drawer is stacked with the content
* @default overlay
*/
type?: "inline" | "overlay";
/**
* Decides whether the drawer should be removed from the DOM tree when it is closed.
* This can be useful when dealing with components that may contain state that should not
* be reset when the drawer is closed.
* @default true
*/
unmountOnClose?: boolean;
}
DrawerBody API
DrawerBody provides with a container for the main content of a Drawer.
interface DrawerBodyProps {
as?: "div";
children?: any;
className?: string;
ref?: Ref<HTMLDivElement>;
style?: CSSProperties;
}
DrawerFooter API
DrawerFooter provides a structured footer for the drawer component.
interface DrawerFooterProps {
as?: "footer";
children?: any;
className?: string;
ref?: Ref<HTMLElement>;
style?: CSSProperties;
}
DrawerHeader API
DrawerHeader provides a structured header for the drawer component.
interface DrawerHeaderProps {
as?: "header";
children?: any;
className?: string;
ref?: Ref<HTMLElement>;
style?: CSSProperties;
}
DrawerHeaderNavigation API
DrawerHeaderNavigation provides a header navigation area for the Drawer.
interface DrawerHeaderNavigationProps {
as?: "nav";
children?: any;
className?: string;
ref?: Ref<HTMLElement>;
style?: CSSProperties;
}
DrawerHeaderTitle API
DrawerHeader provides a structured header for the drawer component.
interface DrawerHeaderTitleProps {
/**
* Action slot for the close button
*/
action?: WithSlotShorthandValue<{ as?: "div"; } & Omit<Omit<DetailedHTMLProps<HTMLAttributes<HTMLDivElement>, HTMLDivElement>, "ref"> & { ref?: ((instance: HTMLDivElement | null) => void) | RefObject<HTMLDivElement> | null; }, "children"> & { children?: any; }> | null | undefined;
as?: "div";
children?: any;
className?: string;
/**
* By default this is a h2, but can be any heading or div.
* If `div` is provided do not forget to also provide proper `role="heading"` and `aria-level` attributes
*/
heading?: ({ as: "h1"; } & Omit<Omit<DetailedHTMLProps<HTMLAttributes<HTMLHeadingElement>, HTMLHeadingElement>, "ref"> & { ref?: ((instance: HTMLHeadingElement | null) => void) | RefObject<HTMLHeadingElement> | null; }, "children"> & { children?: any; }) | ({ as: "h3"; } & Omit<Omit<DetailedHTMLProps<HTMLAttributes<HTMLHeadingElement>, HTMLHeadingElement>, "ref"> & { ref?: ((instance: HTMLHeadingElement | null) => void) | RefObject<HTMLHeadingElement> | null; }, "children"> & { children?: any; }) | ({ as: "h4"; } & Omit<Omit<DetailedHTMLProps<HTMLAttributes<HTMLHeadingElement>, HTMLHeadingElement>, "ref"> & { ref?: ((instance: HTMLHeadingElement | null) => void) | RefObject<HTMLHeadingElement> | null | undefined; }, "children"> & { children?: any; }) | ({ as: "h5"; } & Omit<Omit<DetailedHTMLProps<HTMLAttributes<HTMLHeadingElement>, HTMLHeadingElement>, "ref"> & { ref?: ((instance: HTMLHeadingElement | null) => void) | RefObject<HTMLHeadingElement> | null | undefined; }, "children"> & { children?: any; }) | ({ as: "h6"; } & Omit<Omit<DetailedHTMLProps<HTMLAttributes<HTMLHeadingElement>, HTMLHeadingElement>, "ref"> & { ref?: ((instance: HTMLHeadingElement | null) => void) | RefObject<HTMLHeadingElement> | null | undefined; }, "children"> & { children?: any; }) | ({ as: "div"; } & Omit<Omit<DetailedHTMLProps<HTMLAttributes<HTMLDivElement>, HTMLDivElement>, "ref"> & { ref?: ((instance: HTMLDivElement | null) => void) | RefObject<HTMLDivElement> | null | undefined; }, "children"> & { children?: any; }) | WithSlotShorthandValue<{ as?: "h2" | undefined; } & Omit<Omit<DetailedHTMLProps<HTMLAttributes<HTMLHeadingElement>, HTMLHeadingElement>, "ref"> & { ref?: ((instance: HTMLHeadingElement | null) => void) | RefObject<HTMLHeadingElement> | null | undefined; }, "children"> & { children?: any; }> | null | undefined;
ref?: Ref<HTMLDivElement>;
style?: CSSProperties;
}
InlineDrawer API
InlineDrawer is often used for navigation that is not dismissible. As it is on the same level as the main surface, users can still interact with other UI elements.
interface InlineDrawerProps {
as?: "aside" | "div";
children?: any;
className?: string;
/**
* Controls the open state of the Drawer
* @default false
*/
open?: boolean;
/**
* Position of the drawer.
*
* Note: 'bottom' does not supports size, but it supports customized height.
* @default 'start'
*/
position?: "start" | "end" | "bottom";
ref?: Ref<HTMLElement>;
/**
* Whether the drawer has a separator line.
* @default false
*/
separator?: boolean;
/**
* Size of the drawer.
*
* - 'small' - Drawer is 320px wide.
* - 'medium' - Drawer is 592px wide.
* - 'large' - Drawer is 940px wide.
* - 'full' - Drawer is 100vw wide.
* @default 'small'
*/
size?: "small" | "medium" | "large" | "full";
style?: CSSProperties;
surfaceMotion?: SurfaceMotionSlotProps | null;
/**
* Decides whether the drawer should be removed from the DOM tree when it is closed.
* This can be useful when dealing with components that may contain state that should not
* be reset when the drawer is closed.
* @default true
*/
unmountOnClose?: boolean;
}
OverlayDrawer API
OverlayDrawer contains supplementary content and are used for complex creation, edit, or management experiences.
interface OverlayDrawerProps {
as?: "aside" | "div";
/**
* Dimmed background of dialog.
* The default backdrop is rendered as a `<div>` with styling.
* This slot expects a `<div>` element which will replace the default backdrop.
* The backdrop should have `aria-hidden="true"`.
*
* Accepts an `appearance` prop to control backdrop visibility:
* - `'dimmed'`: Always shows a dimmed backdrop, regardless of nesting.
* - `'transparent'`: Always shows a transparent backdrop.
*/
backdrop?: WithSlotShorthandValue<{ as?: "div"; } & Omit<Omit<DetailedHTMLProps<HTMLAttributes<HTMLDivElement>, HTMLDivElement>, "ref"> & { ref?: ((instance: HTMLDivElement | null) => void) | RefObject<HTMLDivElement> | null; }, "children"> & { children?: any; } & { appearance?: "transparent" | "dimmed" | undefined; }> | null | undefined;
/**
* For more information refer to the [Motion docs page](https://react.fluentui.dev/?path=/docs/motion-motion-slot--docs).
*/
backdropMotion?: (Pick<PresenceComponentProps, "imperativeRef" | "onMotionFinish" | "onMotionStart"> & Partial<Required<Pick<DrawerBaseProps, "size">>> & { as?: JSXIntrinsicElementKeys; children?: SlotRenderFunction<PresenceMotionSlotRenderProps & Required<Pick<DrawerBaseProps, "size">> & { children: JSXElement; }>; } & Partial<FadeParams> & { as?: JSXIntrinsicElementKeys | undefined; children?: SlotRenderFunction<PresenceMotionSlotRenderProps & PresenceDuration & PresenceEasing & PresenceDelay & { outOpacity?: number | undefined; inOpacity?: number | undefined; } & { children: JSXElement; }> | undefined; }) | null | undefined;
children?: any;
className?: string;
/**
* @deprecated OverlayDrawer can work only as a controlled component
* and does not support uncontrolled mode i.e. defaultOpen prop
*/
defaultOpen?: boolean;
/**
* Enables standard behavior according to the [HTML dialog spec](https://developer.mozilla.org/en-US/docs/Web/API/HTMLDialogElement/showModal)
* where the focus trap involves setting outside elements inert.
* @default false
*/
inertTrapFocus?: boolean;
/**
* Dialog variations.
*
* `modal`: When this type of dialog is open, the rest of the page is dimmed out and cannot be interacted with.
* The tab sequence is kept within the dialog and moving the focus outside
* the dialog will imply closing it. This is the default type of the component.
*
* `non-modal`: When a non-modal dialog is open, the rest of the page is not dimmed out
* and users can interact with the rest of the page.
* This also implies that the tab focus can move outside the dialog when it reaches the last focusable element.
*
* `alert`: is a special type of modal dialogs that interrupts the user's workflow
* to communicate an important message or ask for a decision.
* Unlike a typical modal dialog, the user must take an action through the options given to dismiss the dialog,
* and it cannot be dismissed through the dimmed background.
* @default modal
*/
modalType?: "alert" | "modal" | "non-modal";
/**
* Where the portal children are mounted on DOM
* @default a new element on document.body without any styling
*/
mountNode?: HTMLElement | { element?: HTMLElement | null; className?: string; } | null | undefined;
/**
* Callback fired when the component changes value from open state.
*/
onOpenChange?: DialogOpenChangeEventHandler;
/**
* Controls the open state of the Drawer
* @default false
*/
open?: boolean;
/**
* Position of the drawer.
*
* Note: 'bottom' does not supports size, but it supports customized height.
* @default 'start'
*/
position?: "start" | "end" | "bottom";
ref?: Ref<HTMLElement>;
/**
* Size of the drawer.
*
* - 'small' - Drawer is 320px wide.
* - 'medium' - Drawer is 592px wide.
* - 'large' - Drawer is 940px wide.
* - 'full' - Drawer is 100vw wide.
* @default 'small'
*/
size?: "small" | "medium" | "large" | "full";
style?: CSSProperties;
/**
* For more information refer to the [Motion docs page](https://react.fluentui.dev/?path=/docs/motion-motion-slot--docs).
*/
surfaceMotion?: PresenceMotionSlotProps<Required<Pick<DrawerBaseProps, "size" | "position"> & Pick<ProviderContextValue_unstable, "dir">>> | null;
/**
* Decides whether the drawer should be removed from the DOM tree when it is closed.
* This can be useful when dealing with components that may contain state that should not
* be reset when the drawer is closed.
* @default true
*/
unmountOnClose?: boolean;
}
Implementation
CoreAI 0.12.0; Fluent 9.74.9.
Import components from @coreai-ds/react-components. CoreAI guidance above takes precedence over API demonstration examples.
Always Open
Loading interactive example. Full source is available below.
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
import type { JSXElement } from "@coreai-ds/react-components";
import { makeStyles } from "@coreai-ds/react-components";
import { InlineDrawer } from "@coreai-ds/react-components";
import { DrawerHeader } from "@coreai-ds/react-components";
import { DrawerHeaderTitle } from "@coreai-ds/react-components";
import { DrawerBody } from "@coreai-ds/react-components";
import * as React from 'react';
const useStyles = makeStyles({
root: {
border: '2px solid #ccc',
overflow: 'hidden',
display: 'flex',
height: '480px',
backgroundColor: '#fff',
},
content: {
flex: '1',
padding: '16px',
display: 'flex',
justifyContent: 'center',
alignItems: 'flex-start',
},
});
export const AlwaysOpen = (): JSXElement => {
const styles = useStyles();
return (<div className={styles.root}>
<InlineDrawer separator open>
<DrawerHeader>
<DrawerHeaderTitle>Always open</DrawerHeaderTitle>
</DrawerHeader>
<DrawerBody>
<p>Drawer content</p>
</DrawerBody>
</InlineDrawer>
<div className={styles.content}>
<p>This is the page content</p>
</div>
</div>);
};
export default AlwaysOpen;
Custom Size
Loading interactive example. Full source is available below.
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
import type { JSXElement } from "@coreai-ds/react-components";
import { makeStyles } from "@coreai-ds/react-components";
import { tokens } from "@coreai-ds/react-components";
import * as React from 'react';
import { useRestoreFocusTarget } from "@coreai-ds/react-components";
import { useRestoreFocusSource } from "@coreai-ds/react-components";
import { OverlayDrawer } from "@coreai-ds/react-components";
import { DrawerHeader } from "@coreai-ds/react-components";
import { DrawerHeaderTitle } from "@coreai-ds/react-components";
import { Button } from "@coreai-ds/react-components";
import { Dismiss24Regular } from "@fluentui/react-icons";
import { DrawerBody } from "@coreai-ds/react-components";
import { Field } from "@coreai-ds/react-components";
import { Input } from "@coreai-ds/react-components";
const useStyles = makeStyles({
main: {
display: 'grid',
justifyContent: 'flex-start',
gridRowGap: tokens.spacingVerticalXXL,
},
field: {
display: 'grid',
gridRowGap: tokens.spacingVerticalS,
},
});
export const CustomSize = (): JSXElement => {
const styles = useStyles();
const [open, setOpen] = React.useState(false);
const [customSize, setCustomSize] = React.useState(600);
// all Drawers need manual focus restoration attributes
// unless (as in the case of some inline drawers, you do not want automatic focus restoration)
const restoreFocusTargetAttributes = useRestoreFocusTarget();
const restoreFocusSourceAttributes = useRestoreFocusSource();
return (<div>
<OverlayDrawer {...restoreFocusSourceAttributes} open={open} position="end" onOpenChange={(_, state) => setOpen(state.open)} style={{ width: `${customSize}px` }}>
<DrawerHeader>
<DrawerHeaderTitle action={<Button appearance="subtle" aria-label="Close" icon={<Dismiss24Regular />} onClick={() => setOpen(false)}/>}>
Drawer with {customSize}px size
</DrawerHeaderTitle>
</DrawerHeader>
<DrawerBody>
<p>Drawer content</p>
</DrawerBody>
</OverlayDrawer>
<div className={styles.main}>
<Button {...restoreFocusTargetAttributes} appearance="primary" onClick={() => setOpen(true)}>
Open Drawer
</Button>
<div className={styles.field}>
<Field label="Size">
<Input pattern="[0-9]*" value={customSize.toString()} onChange={(_, data) => setCustomSize(data.value ? parseInt(data.value, 10) : 0)}/>
</Field>
</div>
</div>
</div>);
};
export default CustomSize;
Inline
Loading interactive example. Full source is available below.
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
import type { JSXElement } from "@coreai-ds/react-components";
import { makeStyles } from "@coreai-ds/react-components";
import { tokens } from "@coreai-ds/react-components";
import * as React from 'react';
import { useRestoreFocusTarget } from "@coreai-ds/react-components";
import { mergeClasses } from "@coreai-ds/react-components";
import type { InlineDrawerProps } from "@coreai-ds/react-components";
import { useRestoreFocusSource } from "@coreai-ds/react-components";
import { InlineDrawer } from "@coreai-ds/react-components";
import { DrawerHeader } from "@coreai-ds/react-components";
import { DrawerHeaderTitle } from "@coreai-ds/react-components";
import { Button } from "@coreai-ds/react-components";
import { Dismiss24Regular } from "@fluentui/react-icons";
import { DrawerBody } from "@coreai-ds/react-components";
import { ToggleButton } from "@coreai-ds/react-components";
const useStyles = makeStyles({
root: {
border: '2px solid #ccc',
overflow: 'hidden',
display: 'flex',
height: '480px',
backgroundColor: '#fff',
},
content: {
flex: '1',
padding: '16px',
overflow: 'auto',
position: 'relative',
},
flexColumn: {
flexDirection: 'column',
},
buttons: {
flex: '1',
padding: '16px',
position: 'sticky',
top: '-16px',
right: '-16px',
left: '-16px',
display: 'flex',
flexWrap: 'wrap',
justifyContent: 'center',
alignItems: 'flex-start',
columnGap: tokens.spacingHorizontalXS,
backgroundColor: '#fff',
transitionDuration: tokens.durationFast,
},
});
type DrawerInlineExampleProps = InlineDrawerProps & {
setOpen: (open: boolean) => void;
};
const capitalize = (str: string) => str.charAt(0).toUpperCase() + str.slice(1);
const DrawerInlineExample: React.FC<DrawerInlineExampleProps> = ({ setOpen, ...props }) => {
const restoreFocusSourceAttributes = useRestoreFocusSource();
return (<InlineDrawer {...restoreFocusSourceAttributes} {...props}>
<DrawerHeader>
<DrawerHeaderTitle action={<Button appearance="subtle" aria-label="Close" icon={<Dismiss24Regular />} onClick={() => setOpen(false)}/>}>
{capitalize(props.position!)} Inline Drawer
</DrawerHeaderTitle>
</DrawerHeader>
<DrawerBody>
<p>Drawer content</p>
</DrawerBody>
</InlineDrawer>);
};
export const Inline = (): JSXElement => {
const styles = useStyles();
const [startOpen, setStartOpen] = React.useState(false);
const [endOpen, setEndOpen] = React.useState(false);
const [bottomOpen, setBottomOpen] = React.useState(false);
const restoreFocusTargetAttributes = useRestoreFocusTarget();
return (<div className={mergeClasses(styles.root, styles.flexColumn)}>
<div className={styles.root}>
<DrawerInlineExample as="aside" open={startOpen} setOpen={setStartOpen} position="start"/>
<div className={styles.content}>
<div className={styles.buttons}>
<ToggleButton {...restoreFocusTargetAttributes} appearance="primary" onClick={() => setStartOpen(!startOpen)} checked={startOpen}>
Toggle start
</ToggleButton>
<ToggleButton {...restoreFocusTargetAttributes} appearance="primary" onClick={() => setEndOpen(!endOpen)} checked={endOpen}>
Toggle end
</ToggleButton>
<ToggleButton {...restoreFocusTargetAttributes} appearance="primary" onClick={() => setBottomOpen(!bottomOpen)} checked={bottomOpen}>
Toggle bottom
</ToggleButton>
</div>
{Array.from({ length: 100 }, (_, i) => (<p key={i}>
Lorem, ipsum dolor sit amet consectetur adipisicing elit. Tempore voluptatem similique reiciendis, ipsa
accusamus distinctio dolorum quisquam, tenetur minima animi autem nobis. Molestias totam natus, deleniti
nam itaque placeat quisquam!
</p>))}
</div>
<DrawerInlineExample open={endOpen} setOpen={setEndOpen} position="end"/>
</div>
<DrawerInlineExample open={bottomOpen} setOpen={setBottomOpen} position="bottom"/>
</div>);
};
export default Inline;
Keep Rendered In The DOM
Loading interactive example. Full source is available below.
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
import type { JSXElement } from "@coreai-ds/react-components";
import { makeStyles } from "@coreai-ds/react-components";
import { tokens } from "@coreai-ds/react-components";
import { useId } from "@coreai-ds/react-components";
import * as React from 'react';
import type { DrawerProps } from "@coreai-ds/react-components";
import { useRestoreFocusTarget } from "@coreai-ds/react-components";
import { useRestoreFocusSource } from "@coreai-ds/react-components";
import { Drawer } from "@coreai-ds/react-components";
import { DrawerHeader } from "@coreai-ds/react-components";
import { DrawerHeaderTitle } from "@coreai-ds/react-components";
import { Button } from "@coreai-ds/react-components";
import { Dismiss24Regular } from "@fluentui/react-icons";
import { DrawerBody } from "@coreai-ds/react-components";
import { ToggleButton } from "@coreai-ds/react-components";
import { Label } from "@coreai-ds/react-components";
import { RadioGroup } from "@coreai-ds/react-components";
import { Radio } from "@coreai-ds/react-components";
const useStyles = makeStyles({
root: {
border: '2px solid #ccc',
overflow: 'hidden',
display: 'flex',
height: '480px',
backgroundColor: '#fff',
},
content: {
flex: '1',
padding: '16px',
display: 'grid',
justifyContent: 'flex-start',
alignItems: 'flex-start',
gridRowGap: tokens.spacingVerticalXXL,
gridAutoRows: 'max-content',
},
field: {
display: 'grid',
gridRowGap: tokens.spacingVerticalS,
},
});
type DrawerType = Required<DrawerProps>['type'];
export const KeepRenderedInTheDOM = (): JSXElement => {
const styles = useStyles();
const labelId = useId('type-label');
const [isOpen, setIsOpen] = React.useState(false);
const [type, setType] = React.useState<DrawerType>('overlay');
// all Drawers need manual focus restoration attributes
// unless (as in the case of some inline drawers, you do not want automatic focus restoration)
const restoreFocusTargetAttributes = useRestoreFocusTarget();
const restoreFocusSourceAttributes = useRestoreFocusSource();
return (<div className={styles.root}>
<Drawer {...restoreFocusSourceAttributes} type={type} separator open={isOpen} onOpenChange={(_, { open }) => setIsOpen(open)} unmountOnClose={false}>
<DrawerHeader>
<DrawerHeaderTitle action={<Button appearance="subtle" aria-label="Close" icon={<Dismiss24Regular />} onClick={() => setIsOpen(false)}/>}>
Default Drawer
</DrawerHeaderTitle>
</DrawerHeader>
<DrawerBody>
<p>
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et
dolore magna aliqua. Nisl pretium fusce id velit ut tortor. Leo vel fringilla est ullamcorper. Eget est
lorem ipsum dolor sit amet consectetur adipiscing elit. In mollis nunc sed id semper risus in hendrerit
gravida. Ullamcorper sit amet risus nullam eget felis eget. Dolor sed viverra ipsum nunc aliquet bibendum.
Facilisi morbi tempus iaculis urna id volutpat. Porta non pulvinar neque laoreet suspendisse. Nunc id cursus
metus aliquam eleifend mi in. A iaculis at erat pellentesque adipiscing commodo. Proin nibh nisl condimentum
id. In hac habitasse platea dictumst vestibulum rhoncus est. Non tellus orci ac auctor augue mauris augue
neque. Enim nulla aliquet porttitor lacus luctus accumsan tortor. Nascetur ridiculus mus mauris vitae
ultricies leo integer. Ullamcorper eget nulla facilisi etiam dignissim. Leo in vitae turpis massa sed
elementum tempus egestas sed.
</p>
<p>
Ut enim blandit volutpat maecenas volutpat. Venenatis urna cursus eget nunc scelerisque viverra mauris.
Neque aliquam vestibulum morbi blandit. Porttitor eget dolor morbi non. Nisi quis eleifend quam adipiscing
vitae. Aliquam ultrices sagittis orci a scelerisque purus semper. Interdum varius sit amet mattis vulputate
enim nulla aliquet. Ut sem viverra aliquet eget sit amet tellus cras. Sit amet tellus cras adipiscing enim
eu turpis egestas. Amet cursus sit amet dictum sit amet justo donec enim. Neque gravida in fermentum et
sollicitudin ac. Arcu cursus euismod quis viverra nibh cras pulvinar mattis nunc. Ultrices eros in cursus
turpis massa tincidunt dui. Nisl rhoncus mattis rhoncus urna neque viverra justo. Odio pellentesque diam
volutpat commodo sed egestas. Nunc mi ipsum faucibus vitae aliquet nec ullamcorper. Ipsum nunc aliquet
bibendum enim. Faucibus ornare suspendisse sed nisi lacus sed. Sapien nec sagittis aliquam malesuada
bibendum arcu vitae elementum. Metus vulputate eu scelerisque felis imperdiet.
</p>
<p>
Consequat interdum varius sit amet mattis vulputate enim. Amet cursus sit amet dictum sit amet justo. Eget
aliquet nibh praesent tristique magna sit. Ut consequat semper viverra nam libero justo. Pharetra massa
massa ultricies mi. Sem viverra aliquet eget sit amet. Pulvinar mattis nunc sed blandit libero volutpat sed.
Pharetra diam sit amet nisl suscipit adipiscing bibendum. Consectetur adipiscing elit ut aliquam. Volutpat
diam ut venenatis tellus in metus vulputate. Scelerisque in dictum non consectetur a erat. Venenatis lectus
magna fringilla urna porttitor rhoncus. Vitae congue mauris rhoncus aenean vel elit. Neque laoreet
suspendisse interdum consectetur. Ultrices gravida dictum fusce ut placerat orci. Bibendum ut tristique et
egestas quis ipsum suspendisse. Mattis rhoncus urna neque viverra justo nec ultrices dui. Elit duis
tristique sollicitudin nibh sit amet.
</p>
<p>
At risus viverra adipiscing at. Interdum posuere lorem ipsum dolor sit amet consectetur adipiscing elit.
Nunc vel risus commodo viverra maecenas. Sit amet est placerat in egestas erat imperdiet sed euismod. Turpis
egestas maecenas pharetra convallis posuere. Egestas tellus rutrum tellus pellentesque eu tincidunt tortor
aliquam. Dolor sit amet consectetur adipiscing elit. Aliquam purus sit amet luctus venenatis lectus magna
fringilla. Scelerisque fermentum dui faucibus in ornare quam viverra. Egestas maecenas pharetra convallis
posuere morbi leo urna. A diam sollicitudin tempor id eu nisl nunc. Lectus sit amet est placerat.
</p>
<p>
Mattis ullamcorper velit sed ullamcorper morbi tincidunt ornare massa eget. At tellus at urna condimentum
mattis pellentesque id nibh. Dui faucibus in ornare quam. Tincidunt id aliquet risus feugiat in ante metus
dictum. Adipiscing commodo elit at imperdiet dui. Dolor sed viverra ipsum nunc. Sodales neque sodales ut
etiam sit amet nisl. Hendrerit dolor magna eget est lorem ipsum dolor sit amet. Mattis molestie a iaculis at
erat pellentesque adipiscing. Adipiscing elit duis tristique sollicitudin nibh sit amet commodo nulla.
Fringilla urna porttitor rhoncus dolor purus.
</p>
<p>
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et
dolore magna aliqua. Nisl pretium fusce id velit ut tortor. Leo vel fringilla est ullamcorper. Eget est
lorem ipsum dolor sit amet consectetur adipiscing elit. In mollis nunc sed id semper risus in hendrerit
gravida. Ullamcorper sit amet risus nullam eget felis eget. Dolor sed viverra ipsum nunc aliquet bibendum.
Facilisi morbi tempus iaculis urna id volutpat. Porta non pulvinar neque laoreet suspendisse. Nunc id cursus
metus aliquam eleifend mi in. A iaculis at erat pellentesque adipiscing commodo. Proin nibh nisl condimentum
id. In hac habitasse platea dictumst vestibulum rhoncus est. Non tellus orci ac auctor augue mauris augue
neque. Enim nulla aliquet porttitor lacus luctus accumsan tortor. Nascetur ridiculus mus mauris vitae
ultricies leo integer. Ullamcorper eget nulla facilisi etiam dignissim. Leo in vitae turpis massa sed
elementum tempus egestas sed.
</p>
<p>
Ut enim blandit volutpat maecenas volutpat. Venenatis urna cursus eget nunc scelerisque viverra mauris.
Neque aliquam vestibulum morbi blandit. Porttitor eget dolor morbi non. Nisi quis eleifend quam adipiscing
vitae. Aliquam ultrices sagittis orci a scelerisque purus semper. Interdum varius sit amet mattis vulputate
enim nulla aliquet. Ut sem viverra aliquet eget sit amet tellus cras. Sit amet tellus cras adipiscing enim
eu turpis egestas. Amet cursus sit amet dictum sit amet justo donec enim. Neque gravida in fermentum et
sollicitudin ac. Arcu cursus euismod quis viverra nibh cras pulvinar mattis nunc. Ultrices eros in cursus
turpis massa tincidunt dui. Nisl rhoncus mattis rhoncus urna neque viverra justo. Odio pellentesque diam
volutpat commodo sed egestas. Nunc mi ipsum faucibus vitae aliquet nec ullamcorper. Ipsum nunc aliquet
bibendum enim. Faucibus ornare suspendisse sed nisi lacus sed. Sapien nec sagittis aliquam malesuada
bibendum arcu vitae elementum. Metus vulputate eu scelerisque felis imperdiet.
</p>
<p>
Consequat interdum varius sit amet mattis vulputate enim. Amet cursus sit amet dictum sit amet justo. Eget
aliquet nibh praesent tristique magna sit. Ut consequat semper viverra nam libero justo. Pharetra massa
massa ultricies mi. Sem viverra aliquet eget sit amet. Pulvinar mattis nunc sed blandit libero volutpat sed.
Pharetra diam sit amet nisl suscipit adipiscing bibendum. Consectetur adipiscing elit ut aliquam. Volutpat
diam ut venenatis tellus in metus vulputate. Scelerisque in dictum non consectetur a erat. Venenatis lectus
magna fringilla urna porttitor rhoncus. Vitae congue mauris rhoncus aenean vel elit. Neque laoreet
suspendisse interdum consectetur. Ultrices gravida dictum fusce ut placerat orci. Bibendum ut tristique et
egestas quis ipsum suspendisse. Mattis rhoncus urna neque viverra justo nec ultrices dui. Elit duis
tristique sollicitudin nibh sit amet.
</p>
</DrawerBody>
</Drawer>
<div className={styles.content}>
{type === 'inline' ? (<ToggleButton {...restoreFocusTargetAttributes} appearance="primary" onClick={() => setIsOpen(!isOpen)} checked={isOpen}>
Toggle
</ToggleButton>) : (<Button {...restoreFocusTargetAttributes} appearance="primary" onClick={() => setIsOpen(!isOpen)}>
Open
</Button>)}
<div className={styles.field}>
<Label id={labelId}>Type</Label>
<RadioGroup value={type} onChange={(_, data) => setType(data.value as DrawerType)} aria-labelledby={labelId}>
<Radio value="overlay" label="Overlay (Default)"/>
<Radio value="inline" label="Inline"/>
</RadioGroup>
</div>
</div>
</div>);
};
export default KeepRenderedInTheDOM;
Motion Custom
Loading interactive example. Full source is available below.
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
import type { JSXElement } from "@coreai-ds/react-components";
import { makeStyles } from "@coreai-ds/react-components";
import { tokens } from "@coreai-ds/react-components";
import * as React from 'react';
import { InlineDrawer } from "@coreai-ds/react-components";
import { createPresenceComponent } from "@coreai-ds/react-components";
import { motionTokens } from "@coreai-ds/react-components";
import { DrawerHeader } from "@coreai-ds/react-components";
import { DrawerHeaderTitle } from "@coreai-ds/react-components";
import { Button } from "@coreai-ds/react-components";
import { Dismiss24Regular } from "@fluentui/react-icons";
import { DrawerBody } from "@coreai-ds/react-components";
const drawerWidth = '320px';
const useStyles = makeStyles({
root: {
border: '2px solid #ccc',
overflow: 'hidden',
position: 'relative',
display: 'flex',
height: '480px',
backgroundColor: tokens.colorNeutralBackground1,
},
drawer: {
width: drawerWidth,
border: '1px solid',
},
content: {
flex: '1',
padding: '16px',
display: 'grid',
justifyContent: 'flex-start',
alignItems: 'flex-start',
gap: tokens.spacingVerticalM,
gridAutoRows: 'max-content',
boxSizing: 'border-box',
position: 'absolute',
inset: 0,
backgroundColor: tokens.colorNeutralBackground1,
border: `1px solid ${tokens.colorNeutralBackground1}`,
},
});
const drawerMargin = tokens.spacingVerticalM;
/*
* Create a custom DrawerMotion component that animates the drawer surface.
*/
const DrawerMotion = createPresenceComponent(() => {
const keyframes = [
{
opacity: 0,
transform: 'translate3D(-100%, 0, 0)',
margin: 0,
backgroundColor: tokens.colorNeutralBackground1,
borderColor: tokens.colorNeutralBackground1,
borderRadius: 0,
},
{
opacity: 1,
transform: 'translate3D(0, 0, 0)',
margin: drawerMargin,
backgroundColor: tokens.colorNeutralBackground3,
borderColor: tokens.colorNeutralBackground4,
borderRadius: tokens.borderRadiusXLarge,
},
];
return {
enter: {
keyframes,
duration: motionTokens.durationNormal,
easing: motionTokens.curveDecelerateMin,
},
exit: {
keyframes: [...keyframes].reverse(),
duration: motionTokens.durationSlow,
easing: motionTokens.curveAccelerateMin,
},
};
});
/*
* Create a custom ContentMotion component that animates the content element.
*/
const ContentMotion = createPresenceComponent(() => {
const keyframes = [
{
transform: 'translate3D(0, 0, 0)',
width: '100%',
margin: 0,
backgroundColor: tokens.colorNeutralBackground1,
borderColor: tokens.colorNeutralBackground1,
borderRadius: 0,
},
{
transform: `translate3D(calc(${drawerWidth} + ${drawerMargin} * 2), 0, 0)`,
width: `calc(100% - ${drawerWidth} - ${drawerMargin} * 3)`,
margin: `${drawerMargin} 0`,
backgroundColor: tokens.colorNeutralBackground3,
borderColor: tokens.colorNeutralBackground4,
borderRadius: tokens.borderRadiusXLarge,
},
];
return {
enter: {
keyframes,
duration: motionTokens.durationGentle,
easing: motionTokens.curveDecelerateMin,
},
exit: {
keyframes: [...keyframes].reverse(),
duration: motionTokens.durationGentle,
easing: motionTokens.curveAccelerateMin,
},
};
});
export const MotionCustom = (): JSXElement => {
const styles = useStyles();
const [isOpen, setIsOpen] = React.useState(false);
return (<div className={styles.root}>
<InlineDrawer className={styles.drawer}
// Override motion settings for the drawer surface
surfaceMotion={{ children: (_, props) => <DrawerMotion {...props}/> }} separator open={isOpen}>
<DrawerHeader>
<DrawerHeaderTitle action={<Button appearance="subtle" aria-label="Close" icon={<Dismiss24Regular />} onClick={() => setIsOpen(false)}/>}>
Default Drawer
</DrawerHeaderTitle>
</DrawerHeader>
<DrawerBody>
<p>Drawer content</p>
</DrawerBody>
</InlineDrawer>
<ContentMotion visible={isOpen}>
<div className={styles.content}>
<Button appearance="primary" onClick={() => setIsOpen(!isOpen)}>
Toggle Drawer
</Button>
<p>Drawer content</p>
</div>
</ContentMotion>
</div>);
};
export default MotionCustom;
Motion Disabled
Loading interactive example. Full source is available below.
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
import type { JSXElement } from "@coreai-ds/react-components";
import { makeStyles } from "@coreai-ds/react-components";
import { tokens } from "@coreai-ds/react-components";
import { useId } from "@coreai-ds/react-components";
import * as React from 'react';
import type { DrawerProps } from "@coreai-ds/react-components";
import { useRestoreFocusTarget } from "@coreai-ds/react-components";
import { useRestoreFocusSource } from "@coreai-ds/react-components";
import { Drawer } from "@coreai-ds/react-components";
import { DrawerHeader } from "@coreai-ds/react-components";
import { DrawerHeaderTitle } from "@coreai-ds/react-components";
import { Button } from "@coreai-ds/react-components";
import { Dismiss24Regular } from "@fluentui/react-icons";
import { DrawerBody } from "@coreai-ds/react-components";
import { Label } from "@coreai-ds/react-components";
import { RadioGroup } from "@coreai-ds/react-components";
import { Radio } from "@coreai-ds/react-components";
const useStyles = makeStyles({
root: {
border: '2px solid #ccc',
overflow: 'hidden',
display: 'flex',
height: '480px',
backgroundColor: '#fff',
},
content: {
flex: '1',
padding: '16px',
display: 'grid',
justifyContent: 'flex-start',
alignItems: 'flex-start',
gap: tokens.spacingVerticalXXL,
gridAutoRows: 'max-content',
},
field: {
display: 'grid',
gap: tokens.spacingVerticalS,
},
});
type DrawerType = Required<DrawerProps>['type'];
export const MotionDisabled = (): JSXElement => {
const styles = useStyles();
const labelId = useId('type-label');
const [isOpen, setIsOpen] = React.useState(false);
const [type, setType] = React.useState<DrawerType>('overlay');
// all Drawers need manual focus restoration attributes
// unless (as in the case of some inline drawers, you do not want automatic focus restoration)
const restoreFocusTargetAttributes = useRestoreFocusTarget();
const restoreFocusSourceAttributes = useRestoreFocusSource();
return (<div className={styles.root}>
<Drawer backdropMotion={null} surfaceMotion={null} type={type} {...restoreFocusSourceAttributes} separator open={isOpen} onOpenChange={(_, { open }) => setIsOpen(open)}>
<DrawerHeader>
<DrawerHeaderTitle action={<Button appearance="subtle" aria-label="Close" icon={<Dismiss24Regular />} onClick={() => setIsOpen(false)}/>}>
Default Drawer
</DrawerHeaderTitle>
</DrawerHeader>
<DrawerBody>
<p>Drawer content</p>
</DrawerBody>
</Drawer>
<div className={styles.content}>
<Button {...restoreFocusTargetAttributes} appearance="primary" onClick={() => setIsOpen(!isOpen)}>
{type === 'inline' ? 'Toggle' : 'Open'}
</Button>
<div className={styles.field}>
<Label id={labelId}>Type</Label>
<RadioGroup value={type} onChange={(_, data) => setType(data.value as DrawerType)} aria-labelledby={labelId}>
<Radio value="overlay" label="Overlay (Default)"/>
<Radio value="inline" label="Inline"/>
</RadioGroup>
</div>
</div>
</div>);
};
export default MotionDisabled;
Multiple Levels
Loading interactive example. Full source is available below.
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
import type { JSXElement } from "@coreai-ds/react-components";
import { makeStyles } from "@coreai-ds/react-components";
import * as React from 'react';
import { useRestoreFocusTarget } from "@coreai-ds/react-components";
import { useRestoreFocusSource } from "@coreai-ds/react-components";
import { OverlayDrawer } from "@coreai-ds/react-components";
import { DrawerHeader } from "@coreai-ds/react-components";
import { DrawerHeaderNavigation } from "@coreai-ds/react-components";
import { Toolbar } from "@coreai-ds/react-components";
import { ToolbarGroup } from "@coreai-ds/react-components";
import { createPresenceComponentVariant } from "@coreai-ds/react-components";
import { Scale } from "@fluentui/react-motion-components-preview";
import { motionTokens } from "@coreai-ds/react-components";
import { ToolbarButton } from "@coreai-ds/react-components";
import { ArrowLeft24Regular } from "@fluentui/react-icons";
import { Calendar24Regular } from "@fluentui/react-icons";
import { Settings24Regular } from "@fluentui/react-icons";
import { Dismiss24Regular } from "@fluentui/react-icons";
import { createPresenceComponent } from "@coreai-ds/react-components";
import { fadeAtom } from "@fluentui/react-motion-components-preview";
import { slideAtom } from "@fluentui/react-motion-components-preview";
import { DrawerBody } from "@coreai-ds/react-components";
import { DrawerHeaderTitle } from "@coreai-ds/react-components";
import { DrawerFooter } from "@coreai-ds/react-components";
import { Button } from "@coreai-ds/react-components";
const useStyles = makeStyles({
toolbar: {
justifyContent: 'space-between',
},
body: {
flex: '1',
width: '100%',
maxWidth: '100%',
position: 'relative',
},
level: {
position: 'absolute',
top: 0,
left: 0,
right: 0,
bottom: 0,
':first-child': {
paddingTop: 0,
},
':last-child': {
paddingBottom: 0,
},
},
footer: {
justifyContent: 'space-between',
},
});
const IconPresenceMotion = createPresenceComponentVariant(Scale, {
duration: motionTokens.durationNormal,
easing: motionTokens.curveEasyEase,
});
const BodyPresenceMotion = createPresenceComponent<{
level: 1 | 2;
}>(({ level }) => {
const duration = motionTokens.durationNormal;
const easing = motionTokens.curveEasyEase;
const outX = level === 1 ? '-100%' : '100%';
return {
enter: [
fadeAtom({ direction: 'enter', duration, easing }),
slideAtom({ direction: 'enter', duration, easing, outX }),
],
exit: [fadeAtom({ direction: 'exit', duration, easing }), slideAtom({ direction: 'exit', duration, easing, outX })],
};
});
export const MultipleLevels = (): JSXElement => {
const styles = useStyles();
const [isOpen, setIsOpen] = React.useState(false);
const [level, setLevel] = React.useState<1 | 2>(1);
// all Drawers need manual focus restoration attributes
// unless (as in the case of some inline drawers, you do not want automatic focus restoration)
const restoreFocusTargetAttributes = useRestoreFocusTarget();
const restoreFocusSourceAttributes = useRestoreFocusSource();
return (<div>
<OverlayDrawer {...restoreFocusSourceAttributes} position="start" open={isOpen} onOpenChange={(_, { open }) => setIsOpen(open)}>
<DrawerHeader>
<DrawerHeaderNavigation>
<Toolbar className={styles.toolbar}>
<ToolbarGroup>
<IconPresenceMotion visible={level === 2} unmountOnExit>
<ToolbarButton aria-label="Back" appearance="subtle" icon={<ArrowLeft24Regular />} onClick={() => setLevel(1)}/>
</IconPresenceMotion>
</ToolbarGroup>
<ToolbarGroup>
<IconPresenceMotion visible={level === 1} unmountOnExit>
<ToolbarButton aria-label="Go to calendar" appearance="subtle" icon={<Calendar24Regular />} onClick={() => setLevel(2)}/>
</IconPresenceMotion>
<ToolbarButton aria-label="Settings" appearance="subtle" icon={<Settings24Regular />}/>
<ToolbarButton aria-label="Close panel" appearance="subtle" icon={<Dismiss24Regular />} onClick={() => setIsOpen(false)}/>
</ToolbarGroup>
</Toolbar>
</DrawerHeaderNavigation>
</DrawerHeader>
<div className={styles.body}>
<BodyPresenceMotion level={1} visible={level === 1} unmountOnExit>
<DrawerBody className={styles.level}>
<DrawerHeaderTitle>Level 1 title</DrawerHeaderTitle>
<p>Level 1 content</p>
</DrawerBody>
</BodyPresenceMotion>
<BodyPresenceMotion level={2} visible={level === 2} unmountOnExit>
<DrawerBody className={styles.level}>
<DrawerHeaderTitle>Level 2 title</DrawerHeaderTitle>
<p>Level 2 content</p>
</DrawerBody>
</BodyPresenceMotion>
</div>
<DrawerFooter className={styles.footer}>
<Button appearance="subtle" disabled={level === 1} onClick={() => setLevel(1)}>
Previous
</Button>
<Button appearance="primary" disabled={level === 2} onClick={() => setLevel(2)}>
Next
</Button>
</DrawerFooter>
</OverlayDrawer>
<Button {...restoreFocusTargetAttributes} appearance="primary" onClick={() => setIsOpen(true)}>
Open Drawer
</Button>
</div>);
};
export default MultipleLevels;
Overlay
Loading interactive example. Full source is available below.
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
import type { JSXElement } from "@coreai-ds/react-components";
import * as React from 'react';
import { useRestoreFocusTarget } from "@coreai-ds/react-components";
import { useRestoreFocusSource } from "@coreai-ds/react-components";
import { OverlayDrawer } from "@coreai-ds/react-components";
import { DrawerHeader } from "@coreai-ds/react-components";
import { DrawerHeaderTitle } from "@coreai-ds/react-components";
import { Button } from "@coreai-ds/react-components";
import { Dismiss24Regular } from "@fluentui/react-icons";
import { DrawerBody } from "@coreai-ds/react-components";
export const Overlay = (): JSXElement => {
const [isOpen, setIsOpen] = React.useState(false);
// all Drawers need manual focus restoration attributes
// unless (as in the case of some inline drawers, you do not want automatic focus restoration)
const restoreFocusTargetAttributes = useRestoreFocusTarget();
const restoreFocusSourceAttributes = useRestoreFocusSource();
return (<div>
<OverlayDrawer as="aside" {...restoreFocusSourceAttributes} open={isOpen} onOpenChange={(_, { open }) => setIsOpen(open)}>
<DrawerHeader>
<DrawerHeaderTitle action={<Button appearance="subtle" aria-label="Close" icon={<Dismiss24Regular />} onClick={() => setIsOpen(false)}/>}>
Overlay Drawer
</DrawerHeaderTitle>
</DrawerHeader>
<DrawerBody>
<p>Drawer content</p>
</DrawerBody>
</OverlayDrawer>
<Button {...restoreFocusTargetAttributes} appearance="primary" onClick={() => setIsOpen(true)}>
Open Drawer
</Button>
</div>);
};
export default Overlay;
Overlay Inside Container
Loading interactive example. Full source is available below.
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
import type { JSXElement } from "@coreai-ds/react-components";
import * as React from 'react';
import { makeStyles } from "@coreai-ds/react-components";
import { tokens } from "@coreai-ds/react-components";
import { useRestoreFocusTarget } from "@coreai-ds/react-components";
import { useRestoreFocusSource } from "@coreai-ds/react-components";
import { OverlayDrawer } from "@coreai-ds/react-components";
import { DrawerHeader } from "@coreai-ds/react-components";
import { DrawerHeaderTitle } from "@coreai-ds/react-components";
import { Button } from "@coreai-ds/react-components";
import { Dismiss24Regular } from "@fluentui/react-icons";
import { DrawerBody } from "@coreai-ds/react-components";
const useStyles = makeStyles({
root: {
display: 'flex',
alignItems: 'flex-start',
gap: tokens.spacingHorizontalL,
},
container: {
width: '500px',
height: '300px',
padding: `${tokens.spacingVerticalL} ${tokens.spacingHorizontalL}`,
position: 'relative',
overflow: 'hidden',
border: `${tokens.strokeWidthThicker} solid ${tokens.colorNeutralStroke1}`,
backgroundColor: tokens.colorBrandBackground2,
},
});
export const OverlayInsideContainer = (): JSXElement => {
const [isOpen, setIsOpen] = React.useState(false);
const ref = React.useRef<HTMLDivElement>(null);
const styles = useStyles();
// all Drawers need manual focus restoration attributes
// unless (as in the case of some inline drawers, you do not want automatic focus restoration)
const restoreFocusTargetAttributes = useRestoreFocusTarget();
const restoreFocusSourceAttributes = useRestoreFocusSource();
return (<div className={styles.root}>
<div className={styles.container} ref={ref}>
<OverlayDrawer as="aside" {...restoreFocusSourceAttributes} mountNode={ref.current} open={isOpen} onOpenChange={(_, { open }) => setIsOpen(open)}>
<DrawerHeader>
<DrawerHeaderTitle action={<Button appearance="subtle" aria-label="Close" icon={<Dismiss24Regular />} onClick={() => setIsOpen(false)}/>}>
Overlay Drawer
</DrawerHeaderTitle>
</DrawerHeader>
<DrawerBody>
<p>Drawer content</p>
</DrawerBody>
</OverlayDrawer>
<p>Drawer will be rendered within this container</p>
</div>
<Button {...restoreFocusTargetAttributes} appearance="primary" onClick={() => setIsOpen(true)}>
Open Drawer
</Button>
</div>);
};
export default OverlayInsideContainer;
Overlay No Modal
Loading interactive example. Full source is available below.
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
import type { JSXElement } from "@coreai-ds/react-components";
import * as React from 'react';
import { useRestoreFocusTarget } from "@coreai-ds/react-components";
import { useRestoreFocusSource } from "@coreai-ds/react-components";
import { OverlayDrawer } from "@coreai-ds/react-components";
import { DrawerHeader } from "@coreai-ds/react-components";
import { DrawerHeaderTitle } from "@coreai-ds/react-components";
import { Button } from "@coreai-ds/react-components";
import { Dismiss24Regular } from "@fluentui/react-icons";
import { DrawerBody } from "@coreai-ds/react-components";
export const OverlayNoModal = (): JSXElement => {
const [isOpen, setIsOpen] = React.useState(false);
// all Drawers need manual focus restoration attributes
// unless (as in the case of some inline drawers, you do not want automatic focus restoration)
const restoreFocusTargetAttributes = useRestoreFocusTarget();
const restoreFocusSourceAttributes = useRestoreFocusSource();
return (<div>
<OverlayDrawer modalType="non-modal" {...restoreFocusSourceAttributes} open={isOpen} onOpenChange={(_, { open }) => setIsOpen(open)}>
<DrawerHeader>
<DrawerHeaderTitle action={<Button appearance="subtle" aria-label="Close" icon={<Dismiss24Regular />} onClick={() => setIsOpen(false)}/>}>
Overlay Drawer
</DrawerHeaderTitle>
</DrawerHeader>
<DrawerBody>
<p>Drawer content</p>
</DrawerBody>
</OverlayDrawer>
<Button {...restoreFocusTargetAttributes} appearance="primary" onClick={() => setIsOpen(!isOpen)}>
Toggle
</Button>
</div>);
};
export default OverlayNoModal;
Position
Loading interactive example. Full source is available below.
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
import type { JSXElement } from "@coreai-ds/react-components";
import { makeStyles } from "@coreai-ds/react-components";
import { tokens } from "@coreai-ds/react-components";
import * as React from 'react';
import type { DrawerProps } from "@coreai-ds/react-components";
import { useRestoreFocusTarget } from "@coreai-ds/react-components";
import { useRestoreFocusSource } from "@coreai-ds/react-components";
import { OverlayDrawer } from "@coreai-ds/react-components";
import { DrawerHeader } from "@coreai-ds/react-components";
import { DrawerHeaderTitle } from "@coreai-ds/react-components";
import { Button } from "@coreai-ds/react-components";
import { Dismiss24Regular } from "@fluentui/react-icons";
import { DrawerBody } from "@coreai-ds/react-components";
const useStyles = makeStyles({
content: {
display: 'flex',
justifyContent: 'center',
alignItems: 'flex-start',
columnGap: tokens.spacingHorizontalXS,
},
});
const capitalize = (str: string) => str.charAt(0).toUpperCase() + str.slice(1);
export const Position = (): JSXElement => {
const styles = useStyles();
const [isOpen, setIsOpen] = React.useState(false);
const [position, setPosition] = React.useState<Required<DrawerProps>['position']>('start');
const onClickStartButton = React.useCallback(() => {
setPosition('start');
setIsOpen(true);
}, []);
const onClickEndButton = React.useCallback(() => {
setPosition('end');
setIsOpen(true);
}, []);
const onClickBottomButton = React.useCallback(() => {
setPosition('bottom');
setIsOpen(true);
}, []);
// all Drawers need manual focus restoration attributes
// unless (as in the case of some inline drawers, you do not want automatic focus restoration)
const restoreFocusTargetAttributes = useRestoreFocusTarget();
const restoreFocusSourceAttributes = useRestoreFocusSource();
return (<div>
<OverlayDrawer position={position} {...restoreFocusSourceAttributes} open={isOpen} onOpenChange={(_, { open }) => setIsOpen(open)}>
<DrawerHeader>
<DrawerHeaderTitle action={<Button appearance="subtle" aria-label="Close" icon={<Dismiss24Regular />} onClick={() => setIsOpen(false)}/>}>
{capitalize(position)} Drawer
</DrawerHeaderTitle>
</DrawerHeader>
<DrawerBody>
<p>Drawer content</p>
</DrawerBody>
</OverlayDrawer>
<div className={styles.content}>
<Button {...restoreFocusTargetAttributes} appearance="primary" onClick={onClickStartButton}>
Open start
</Button>
<Button {...restoreFocusTargetAttributes} appearance="primary" onClick={onClickEndButton}>
Open end
</Button>
<Button {...restoreFocusTargetAttributes} appearance="primary" onClick={onClickBottomButton}>
Open Bottom
</Button>
</div>
</div>);
};
export default Position;
Prevent Close
Loading interactive example. Full source is available below.
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
import type { JSXElement } from "@coreai-ds/react-components";
import * as React from 'react';
import { useRestoreFocusTarget } from "@coreai-ds/react-components";
import { useRestoreFocusSource } from "@coreai-ds/react-components";
import { OverlayDrawer } from "@coreai-ds/react-components";
import { DrawerHeader } from "@coreai-ds/react-components";
import { DrawerHeaderTitle } from "@coreai-ds/react-components";
import { Button } from "@coreai-ds/react-components";
import { Dismiss24Regular } from "@fluentui/react-icons";
import { DrawerBody } from "@coreai-ds/react-components";
export const PreventClose = (): JSXElement => {
const [open, setOpen] = React.useState(false);
// all Drawers need manual focus restoration attributes
// unless (as in the case of some inline drawers, you do not want automatic focus restoration)
const restoreFocusTargetAttributes = useRestoreFocusTarget();
const restoreFocusSourceAttributes = useRestoreFocusSource();
return (<div>
<OverlayDrawer {...restoreFocusSourceAttributes} position="end" open={open} modalType="alert">
<DrawerHeader>
<DrawerHeaderTitle action={<Button appearance="subtle" aria-label="Close" icon={<Dismiss24Regular />} onClick={() => setOpen(false)}/>}>
Prevent close with Esc or outside click
</DrawerHeaderTitle>
</DrawerHeader>
<DrawerBody>
<p>This drawer cannot be closed when clicking outside nor using the "ESC" key</p>
</DrawerBody>
</OverlayDrawer>
<Button {...restoreFocusTargetAttributes} appearance="primary" onClick={() => setOpen(true)}>
Open Drawer
</Button>
</div>);
};
export default PreventClose;
Resizable
Loading interactive example. Full source is available below.
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
import type { JSXElement } from "@coreai-ds/react-components";
import { makeStyles } from "@coreai-ds/react-components";
import { tokens } from "@coreai-ds/react-components";
import { shorthands } from "@coreai-ds/react-components";
import * as React from 'react';
import { useId } from "@coreai-ds/react-components";
import type { InputProps } from "@coreai-ds/react-components";
import { mergeClasses } from "@coreai-ds/react-components";
import { InlineDrawer } from "@coreai-ds/react-components";
import { DrawerHeader } from "@coreai-ds/react-components";
import { DrawerHeaderTitle } from "@coreai-ds/react-components";
import { DrawerBody } from "@coreai-ds/react-components";
import { Dialog } from "@coreai-ds/react-components";
import { DialogTrigger } from "@coreai-ds/react-components";
import { Button } from "@coreai-ds/react-components";
import { DialogSurface } from "@coreai-ds/react-components";
import { DialogBody } from "@coreai-ds/react-components";
import { DialogTitle } from "@coreai-ds/react-components";
import { Dismiss20Regular } from "@fluentui/react-icons";
import { DialogContent } from "@coreai-ds/react-components";
import { Label } from "@coreai-ds/react-components";
import { Input } from "@coreai-ds/react-components";
import { DialogActions } from "@coreai-ds/react-components";
const useStyles = makeStyles({
root: {
border: '2px solid #ccc',
overflow: 'hidden',
display: 'flex',
height: '480px',
backgroundColor: '#fff',
userSelect: 'auto',
},
rootResizerActive: {
userSelect: 'none',
},
container: {
position: 'relative',
},
drawer: {
willChange: 'width',
transitionProperty: 'width',
transitionDuration: '16.666ms', // 60fps
},
resizer: {
width: '24px',
height: '100%',
position: 'absolute',
top: 0,
right: 0,
bottom: 0,
cursor: 'col-resize',
border: 'none',
minWidth: 'unset',
borderRadius: tokens.borderRadiusNone,
'&:before': {
content: '""',
position: 'absolute',
borderRight: `1px solid ${tokens.colorNeutralBackground5}`,
width: '1px',
height: '100%',
transform: 'translateX(-50%)',
left: '50%',
},
':hover': {
borderRightWidth: '4px',
cursor: 'col-resize',
backgroundColor: 'transparent',
},
':hover:active': {
backgroundColor: 'transparent',
cursor: 'col-resize',
},
},
resizerActive: {
borderRightWidth: '4px',
borderRightColor: tokens.colorNeutralBackground5Pressed,
},
content: {
margin: `${tokens.spacingVerticalXL} ${tokens.spacingHorizontalXL}`,
flex: '1',
},
dialogContent: {
display: 'flex',
flexDirection: 'column',
},
errorMessage: {
color: tokens.colorPaletteRedForeground1,
},
invalidInput: {
...shorthands.borderColor(tokens.colorPaletteRedBorder2),
},
});
const MIN_SIDEBAR_WIDTH = 240;
export const Resizable = (): JSXElement => {
const styles = useStyles();
const inputRef = React.useRef<HTMLInputElement>(null);
const [isDialogOpen, setDialogOpen] = React.useState(false);
const animationFrame = React.useRef<number>(0);
const sidebarRef = React.useRef<HTMLDivElement>(null);
const [isResizing, setIsResizing] = React.useState(false);
const [sidebarWidth, setSidebarWidth] = React.useState(320);
const [showMessage, setShowMessage] = React.useState(false);
const [resizeInput, setResizeInput] = React.useState<string>(sidebarWidth.toString());
const inputId = useId('input');
const startResizing = React.useCallback(() => setIsResizing(true), []);
const stopResizing = React.useCallback(() => setIsResizing(false), []);
const resize = React.useCallback(({ clientX }: {
clientX: number;
}) => {
animationFrame.current = requestAnimationFrame(() => {
if (isResizing && sidebarRef.current) {
const newSidebarWidth = clientX - sidebarRef.current.getBoundingClientRect().left;
setSidebarWidth(newSidebarWidth);
setResizeInput(Math.round(newSidebarWidth).toString());
}
});
}, [isResizing]);
React.useEffect(() => {
window.addEventListener('mousemove', resize);
window.addEventListener('mouseup', stopResizing);
return () => {
cancelAnimationFrame(animationFrame.current);
window.removeEventListener('mousemove', resize);
window.removeEventListener('mouseup', stopResizing);
};
}, [resize, stopResizing]);
React.useEffect(() => {
if (isDialogOpen && inputRef.current) {
inputRef.current.focus();
}
}, [isDialogOpen]);
const onResizeInputChange: InputProps['onChange'] = (ev, data) => {
if (data.value) {
setResizeInput(data.value);
}
};
const handleKeyDown = (ev: React.KeyboardEvent) => {
if (ev.key === 'Enter') {
submitWidth(ev);
}
if (ev.key === 'Escape') {
setDialogOpen(false);
}
};
function submitWidth(e: React.MouseEvent<HTMLButtonElement> | React.KeyboardEvent) {
if (resizeInput && parseInt(resizeInput, 10) >= MIN_SIDEBAR_WIDTH) {
setSidebarWidth(Number(resizeInput));
setShowMessage(false);
setDialogOpen(false);
}
else {
setShowMessage(true);
e.preventDefault();
}
}
function resizeWithArrows(e: React.KeyboardEvent) {
if (e.key === 'ArrowUp' || e.key === 'ArrowRight') {
setSidebarWidth(prev => prev + 10);
setResizeInput((prev: string) => (parseInt(prev, 10) + 10).toString());
}
else if (e.key === 'ArrowDown' || e.key === 'ArrowLeft') {
setSidebarWidth(prev => prev - 10);
setResizeInput((prev: string) => (parseInt(prev, 10) - 10).toString());
}
}
return (<>
<div className={mergeClasses(styles.root, isResizing && styles.rootResizerActive)}>
<div className={styles.container}>
<InlineDrawer open ref={sidebarRef} className={styles.drawer} style={{ width: `${sidebarWidth}px` }} onMouseDown={e => e.preventDefault()}>
<DrawerHeader>
<DrawerHeaderTitle>Default Drawer</DrawerHeaderTitle>
</DrawerHeader>
<DrawerBody>
<p>Resizable content</p>
</DrawerBody>
</InlineDrawer>
<Dialog open={isDialogOpen} onOpenChange={(event, data) => setDialogOpen(data.open)}>
<DialogTrigger disableButtonEnhancement>
<Button className={mergeClasses(styles.resizer, isResizing && styles.resizerActive)} onMouseDown={startResizing} aria-label="Resize drawer" role="separator" aria-orientation="vertical" onKeyDown={resizeWithArrows} aria-valuenow={sidebarWidth * 0.01} aria-valuemin={MIN_SIDEBAR_WIDTH * 0.01} aria-valuemax={100}/>
</DialogTrigger>
<DialogSurface>
<DialogBody>
<DialogTitle action={<DialogTrigger action="close">
<Button appearance="subtle" aria-label="close" icon={<Dismiss20Regular />}/>
</DialogTrigger>}>
Resize drawer
</DialogTitle>
<DialogContent>
<div className={styles.dialogContent}>
<Label htmlFor={inputId}>Enter desired drawer width pixels:</Label>
<Input id={inputId} ref={inputRef} onChange={onResizeInputChange} value={resizeInput} type="number" className={showMessage ? styles.invalidInput : ''} onKeyDown={handleKeyDown}/>
{showMessage ? (<Label className={styles.errorMessage}>
Recommended minimum width of the drawer should be greater than or equal to `240px`.
</Label>) : null}
</div>
</DialogContent>
<DialogActions>
<DialogTrigger disableButtonEnhancement>
<Button appearance="primary" onClick={e => submitWidth(e)}>
Resize
</Button>
</DialogTrigger>
<DialogTrigger disableButtonEnhancement>
<Button>Cancel</Button>
</DialogTrigger>
</DialogActions>
</DialogBody>
</DialogSurface>
</Dialog>
</div>
<p className={styles.content}>Resize the drawer to see the change</p>
</div>
</>);
};
export default Resizable;
Responsive
Loading interactive example. Full source is available below.
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
import type { JSXElement } from "@coreai-ds/react-components";
import { makeStyles } from "@coreai-ds/react-components";
import { tokens } from "@coreai-ds/react-components";
import * as React from 'react';
import type { DrawerProps } from "@coreai-ds/react-components";
import { useRestoreFocusTarget } from "@coreai-ds/react-components";
import { useRestoreFocusSource } from "@coreai-ds/react-components";
import { Drawer } from "@coreai-ds/react-components";
import { DrawerHeader } from "@coreai-ds/react-components";
import { DrawerHeaderTitle } from "@coreai-ds/react-components";
import { Button } from "@coreai-ds/react-components";
import { Dismiss24Regular } from "@fluentui/react-icons";
import { DrawerBody } from "@coreai-ds/react-components";
const useStyles = makeStyles({
root: {
border: '2px solid #ccc',
overflow: 'hidden',
display: 'flex',
height: '480px',
backgroundColor: '#fff',
},
content: {
margin: `${tokens.spacingVerticalXL} ${tokens.spacingHorizontalXL}`,
flex: '1',
gridRowGap: tokens.spacingVerticalXXL,
},
});
type DrawerType = Required<DrawerProps>['type'];
export const Responsive = (): JSXElement => {
const styles = useStyles();
const [isOpen, setIsOpen] = React.useState(true);
const [type, setType] = React.useState<DrawerType>('inline');
const onMediaQueryChange = React.useCallback(({ matches }: {
matches: boolean;
}) => setType(matches ? 'overlay' : 'inline'), [setType]);
React.useEffect(() => {
const match = window.matchMedia('(max-width: 720px)');
if (match.matches) {
setType('overlay');
}
match.addEventListener('change', onMediaQueryChange);
return () => match.removeEventListener('change', onMediaQueryChange);
}, [onMediaQueryChange]);
// all Drawers need manual focus restoration attributes
// unless (as in the case of some inline drawers, you do not want automatic focus restoration)
const restoreFocusTargetAttributes = useRestoreFocusTarget();
const restoreFocusSourceAttributes = useRestoreFocusSource();
return (<div className={styles.root}>
<Drawer type={type} {...restoreFocusSourceAttributes} separator position="start" open={isOpen} onOpenChange={(_, { open }) => setIsOpen(open)}>
<DrawerHeader>
<DrawerHeaderTitle action={<Button appearance="subtle" aria-label="Close" icon={<Dismiss24Regular />} onClick={() => setIsOpen(false)}/>}>
Responsive Drawer
</DrawerHeaderTitle>
</DrawerHeader>
<DrawerBody>
<p>Drawer content</p>
</DrawerBody>
</Drawer>
<div className={styles.content}>
<Button {...restoreFocusTargetAttributes} appearance="primary" onClick={() => setIsOpen(!isOpen)}>
Toggle
</Button>
<p>Resize the window to see the change</p>
</div>
</div>);
};
export default Responsive;
Separator
Loading interactive example. Full source is available below.
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
import type { JSXElement } from "@coreai-ds/react-components";
import { makeStyles } from "@coreai-ds/react-components";
import { tokens } from "@coreai-ds/react-components";
import * as React from 'react';
import { mergeClasses } from "@coreai-ds/react-components";
import type { DrawerProps } from "@coreai-ds/react-components";
import { InlineDrawer } from "@coreai-ds/react-components";
import { DrawerHeader } from "@coreai-ds/react-components";
import { DrawerHeaderTitle } from "@coreai-ds/react-components";
import { Button } from "@coreai-ds/react-components";
import { Dismiss24Regular } from "@fluentui/react-icons";
import { DrawerBody } from "@coreai-ds/react-components";
const useStyles = makeStyles({
root: {
border: '2px solid #ccc',
overflow: 'hidden',
display: 'flex',
height: '480px',
backgroundColor: '#fff',
},
content: {
flex: '1',
padding: '16px',
display: 'flex',
justifyContent: 'center',
alignItems: 'flex-start',
columnGap: tokens.spacingHorizontalXS,
},
flexColumn: {
flexDirection: 'column',
},
});
type DrawerSeparatorExampleProps = {
open: boolean;
setOpen: (open: boolean) => void;
position: DrawerProps['position'];
className?: string;
};
const DrawerSeparatorExample: React.FC<DrawerSeparatorExampleProps> = ({ open, setOpen, position }) => {
return (<InlineDrawer separator position={position} open={open}>
<DrawerHeader>
<DrawerHeaderTitle action={<Button appearance="subtle" aria-label="Close" icon={<Dismiss24Regular />} onClick={() => setOpen(false)}/>}>
Drawer with separator
</DrawerHeaderTitle>
</DrawerHeader>
<DrawerBody>
<p>Drawer content</p>
</DrawerBody>
</InlineDrawer>);
};
export const Separator = (): JSXElement => {
const styles = useStyles();
const [startOpen, setStartOpen] = React.useState(true);
const [endOpen, setEndOpen] = React.useState(true);
const [bottomOpen, setBottomOpen] = React.useState(true);
return (<div className={mergeClasses(styles.root, styles.flexColumn)}>
<div className={styles.root} style={{ borderBottomWidth: 0 }}>
<DrawerSeparatorExample open={startOpen} setOpen={setStartOpen} position="start"/>
<div className={styles.content}>
<Button appearance="primary" onClick={() => setStartOpen(!startOpen)}>
Toggle start
</Button>
<Button appearance="primary" onClick={() => setEndOpen(!endOpen)}>
Toggle end
</Button>
<Button appearance="primary" onClick={() => setBottomOpen(!bottomOpen)}>
Toggle bottom
</Button>
</div>
<DrawerSeparatorExample open={endOpen} setOpen={setEndOpen} position="end"/>
</div>
<DrawerSeparatorExample open={bottomOpen} setOpen={setBottomOpen} position="bottom"/>
</div>);
};
export default Separator;
Size
Loading interactive example. Full source is available below.
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
import type { JSXElement } from "@coreai-ds/react-components";
import { makeStyles } from "@coreai-ds/react-components";
import { tokens } from "@coreai-ds/react-components";
import { useId } from "@coreai-ds/react-components";
import * as React from 'react';
import type { DrawerProps } from "@coreai-ds/react-components";
import { useRestoreFocusTarget } from "@coreai-ds/react-components";
import { useRestoreFocusSource } from "@coreai-ds/react-components";
import { OverlayDrawer } from "@coreai-ds/react-components";
import { DrawerHeader } from "@coreai-ds/react-components";
import { DrawerHeaderTitle } from "@coreai-ds/react-components";
import { Button } from "@coreai-ds/react-components";
import { Dismiss24Regular } from "@fluentui/react-icons";
import { DrawerBody } from "@coreai-ds/react-components";
import { Label } from "@coreai-ds/react-components";
import { RadioGroup } from "@coreai-ds/react-components";
import { Radio } from "@coreai-ds/react-components";
const useStyles = makeStyles({
main: {
display: 'grid',
justifyContent: 'flex-start',
gridRowGap: tokens.spacingVerticalXXL,
},
field: {
display: 'grid',
gridRowGap: tokens.spacingVerticalS,
},
});
type DrawerSizeStory = Required<DrawerProps>['size'];
export const Size = (): JSXElement => {
const styles = useStyles();
const labelId = useId('size-label');
const [open, setOpen] = React.useState(false);
const [size, setSize] = React.useState<DrawerSizeStory>('small');
const labelMap: Record<DrawerSizeStory, string> = {
small: 'Small (Default)',
medium: 'Medium',
large: 'Large',
full: 'Full',
};
// all Drawers need manual focus restoration attributes
// unless (as in the case of some inline drawers, you do not want automatic focus restoration)
const restoreFocusTargetAttributes = useRestoreFocusTarget();
const restoreFocusSourceAttributes = useRestoreFocusSource();
return (<div>
<OverlayDrawer size={size} {...restoreFocusSourceAttributes} position="end" open={open} onOpenChange={(_, state) => setOpen(state.open)}>
<DrawerHeader>
<DrawerHeaderTitle action={<Button appearance="subtle" aria-label="Close" icon={<Dismiss24Regular />} onClick={() => setOpen(false)}/>}>
{labelMap[size]} size
</DrawerHeaderTitle>
</DrawerHeader>
<DrawerBody>
<p>Drawer content</p>
</DrawerBody>
</OverlayDrawer>
<div className={styles.main}>
<Button {...restoreFocusTargetAttributes} appearance="primary" onClick={() => setOpen(true)}>
Open Drawer
</Button>
<div className={styles.field}>
<Label id={labelId}>Size</Label>
<RadioGroup value={size} onChange={(_, data) => setSize(data.value as DrawerSizeStory)} aria-labelledby={labelId}>
{Object.keys(labelMap).map(key => (<Radio key={key} value={key} label={labelMap[key as DrawerSizeStory]}/>))}
</RadioGroup>
</div>
</div>
</div>);
};
export default Size;
With Scroll
Loading interactive example. Full source is available below.
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
import type { JSXElement } from "@coreai-ds/react-components";
import { makeStyles } from "@coreai-ds/react-components";
import { tokens } from "@coreai-ds/react-components";
import { InlineDrawer } from "@coreai-ds/react-components";
import { DrawerBody } from "@coreai-ds/react-components";
import { DrawerHeader } from "@coreai-ds/react-components";
import { DrawerHeaderTitle } from "@coreai-ds/react-components";
import { DrawerFooter } from "@coreai-ds/react-components";
import { Button } from "@coreai-ds/react-components";
import * as React from 'react';
const useStyles = makeStyles({
root: {
display: 'flex',
flexWrap: 'wrap',
rowGap: tokens.spacingHorizontalXXL,
columnGap: tokens.spacingHorizontalXXL,
},
drawer: {
height: '400px',
minWidth: '320px',
},
});
const Body = () => {
return (<DrawerBody tabIndex={0} role="group" aria-label="Example scrolling content">
Lorem ipsum dolor sit amet consectetur, adipisicing elit. Doloribus nam aut amet similique, iure vel voluptates
cum cumque repellendus perferendis maiores officia unde in? Autem neque sequi maiores eum omnis. Lorem ipsum,
dolor sit amet consectetur adipisicing elit. Perspiciatis ipsam explicabo tempora ipsum saepe nam. Eum aliquid
aperiam, laborum labore excepturi nisi odio deserunt facilis error. Mollitia dolor quidem a. Lorem ipsum, dolor
sit amet consectetur adipisicing elit. Eius soluta ea repellendus voluptatum provident ad aut unde accusantium
sed. Officia qui praesentium repudiandae maxime molestias, non mollitia animi laboriosam quis. Lorem, ipsum dolor
sit amet consectetur adipisicing elit. Inventore, architecto eligendi earum dolor voluptas hic minima nihil porro
odio suscipit quaerat accusantium, aperiam, neque beatae ipsa explicabo consequatur cum quam?
</DrawerBody>);
};
const Header = () => {
return (<DrawerHeader>
<DrawerHeaderTitle>Title goes here</DrawerHeaderTitle>
</DrawerHeader>);
};
const Footer = () => {
return (<DrawerFooter>
<Button appearance="primary">Primary</Button>
<Button>Secondary</Button>
</DrawerFooter>);
};
export const WithScroll = (): JSXElement => {
const styles = useStyles();
return (<div className={styles.root}>
<InlineDrawer className={styles.drawer} open>
<Body />
</InlineDrawer>
<InlineDrawer className={styles.drawer} open>
<Header />
<Body />
</InlineDrawer>
<InlineDrawer className={styles.drawer} open>
<Body />
<Footer />
</InlineDrawer>
<InlineDrawer className={styles.drawer} open>
<Header />
<Body />
<Footer />
</InlineDrawer>
</div>);
};
export default WithScroll;
With Title
Loading interactive example. Full source is available below.
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
import type { JSXElement } from "@coreai-ds/react-components";
import { InlineDrawer } from "@coreai-ds/react-components";
import { makeStyles } from "@coreai-ds/react-components";
import { DrawerHeader } from "@coreai-ds/react-components";
import { DrawerHeaderTitle } from "@coreai-ds/react-components";
import { Button } from "@coreai-ds/react-components";
import { Dismiss24Regular } from "@fluentui/react-icons";
import * as React from 'react';
const useStyles = makeStyles({
drawer: {
width: '400px',
height: '600px',
},
});
export const WithTitle = (): JSXElement => {
return (<InlineDrawer className={useStyles().drawer} open>
<DrawerHeader>
<DrawerHeaderTitle>Drawer with title</DrawerHeaderTitle>
<DrawerHeaderTitle heading={{ as: 'h1' }}>Drawer with custom tag</DrawerHeaderTitle>
<DrawerHeaderTitle action={<Button appearance="subtle" aria-label="Close" icon={<Dismiss24Regular />}/>}>
Drawer with title and action
</DrawerHeaderTitle>
</DrawerHeader>
</InlineDrawer>);
};
export default WithTitle;