diff --git a/common/changes/office-ui-fabric-react/callout-trapfocus_2018-12-28-02-01.json b/common/changes/office-ui-fabric-react/callout-trapfocus_2018-12-28-02-01.json new file mode 100644 index 0000000000000..42b530442a0b3 --- /dev/null +++ b/common/changes/office-ui-fabric-react/callout-trapfocus_2018-12-28-02-01.json @@ -0,0 +1,11 @@ +{ + "changes": [ + { + "packageName": "office-ui-fabric-react", + "comment": "Add FocusTrapCallout component", + "type": "minor" + } + ], + "packageName": "office-ui-fabric-react", + "email": "anihan@microsoft.com" +} \ No newline at end of file diff --git a/packages/office-ui-fabric-react/etc/office-ui-fabric-react.api.ts b/packages/office-ui-fabric-react/etc/office-ui-fabric-react.api.ts index f423ebeadecf5..4b54b025fbb7e 100644 --- a/packages/office-ui-fabric-react/etc/office-ui-fabric-react.api.ts +++ b/packages/office-ui-fabric-react/etc/office-ui-fabric-react.api.ts @@ -7711,6 +7711,11 @@ interface IFitContentToBoundsOptions { mode: FitMode; } +// @public (undocumented) +interface IFocusTrapCalloutProps extends ICalloutProps { + focusTrapProps?: IFocusTrapZoneProps; +} + // @public (undocumented) interface IFocusTrapZone { focus: () => void; @@ -12525,6 +12530,7 @@ module ZIndexes { // WARNING: Unsupported export: Breadcrumb // WARNING: Unsupported export: CommandButton +// WARNING: Unsupported export: FocusTrapCallout // WARNING: Unsupported export: DirectionalHint // WARNING: Unsupported export: DirectionalHint // WARNING: Unsupported export: Check diff --git a/packages/office-ui-fabric-react/src/components/Callout/Callout.doc.tsx b/packages/office-ui-fabric-react/src/components/Callout/Callout.doc.tsx index e033ecfa69b53..548626363bf0c 100644 --- a/packages/office-ui-fabric-react/src/components/Callout/Callout.doc.tsx +++ b/packages/office-ui-fabric-react/src/components/Callout/Callout.doc.tsx @@ -5,12 +5,14 @@ import { IDocPageProps } from '../../common/DocPage.types'; import { CalloutBasicExample } from './examples/Callout.Basic.Example'; import { CalloutNestedExample } from './examples/Callout.Nested.Example'; +import { CalloutFocusTrapExample } from './examples/Callout.FocusTrap.Example'; import { CalloutDirectionalExample } from './examples/Callout.Directional.Example'; import { CalloutCoverExample } from './examples/Callout.Cover.Example'; import { CalloutStatus } from './Callout.checklist'; const CalloutBasicExampleCode = require('!raw-loader!office-ui-fabric-react/src/components/Callout/examples/Callout.Basic.Example.tsx') as string; const CalloutNestedExampleCode = require('!raw-loader!office-ui-fabric-react/src/components/Callout/examples/Callout.Nested.Example.tsx') as string; +const CalloutFocusTrapExampleCode = require('!raw-loader!office-ui-fabric-react/src/components/Callout/examples/Callout.FocusTrap.Example.tsx') as string; const CalloutDirectionalExampleCode = require('!raw-loader!office-ui-fabric-react/src/components/Callout/examples/Callout.Directional.Example.tsx') as string; const CalloutCoverExampleCode = require('!raw-loader!office-ui-fabric-react/src/components/Callout/examples/Callout.Cover.Example.tsx') as string; @@ -32,6 +34,11 @@ export const CalloutPageProps: IDocPageProps = { code: CalloutNestedExampleCode, view: }, + { + title: 'Focus Trap Callout', + code: CalloutFocusTrapExampleCode, + view: + }, { title: 'Callout with directional hint', code: CalloutDirectionalExampleCode, diff --git a/packages/office-ui-fabric-react/src/components/Callout/FocusTrapCallout.tsx b/packages/office-ui-fabric-react/src/components/Callout/FocusTrapCallout.tsx new file mode 100644 index 0000000000000..99c507688d398 --- /dev/null +++ b/packages/office-ui-fabric-react/src/components/Callout/FocusTrapCallout.tsx @@ -0,0 +1,17 @@ +import * as React from 'react'; + +import { Callout } from './Callout'; +import { IFocusTrapCalloutProps } from './FocusTrapCallout.types'; +import { FocusTrapZone } from '../../FocusTrapZone'; + +/** + * A special Callout that uses FocusTrapZone to trap focus + * @param props - Props for the component + */ +export const FocusTrapCallout: React.StatelessComponent = (props: IFocusTrapCalloutProps): JSX.Element => { + return ( + + {props.children} + + ); +}; diff --git a/packages/office-ui-fabric-react/src/components/Callout/FocusTrapCallout.types.ts b/packages/office-ui-fabric-react/src/components/Callout/FocusTrapCallout.types.ts new file mode 100644 index 0000000000000..c2d05de98129b --- /dev/null +++ b/packages/office-ui-fabric-react/src/components/Callout/FocusTrapCallout.types.ts @@ -0,0 +1,9 @@ +import { ICalloutProps } from './Callout.types'; +import { IFocusTrapZoneProps } from '../../FocusTrapZone'; + +export interface IFocusTrapCalloutProps extends ICalloutProps { + /** + * Optional props to be passed on to FocusTrapZone + */ + focusTrapProps?: IFocusTrapZoneProps; +} diff --git a/packages/office-ui-fabric-react/src/components/Callout/examples/Callout.FocusTrap.Example.tsx b/packages/office-ui-fabric-react/src/components/Callout/examples/Callout.FocusTrap.Example.tsx new file mode 100644 index 0000000000000..f695b5abc7a9b --- /dev/null +++ b/packages/office-ui-fabric-react/src/components/Callout/examples/Callout.FocusTrap.Example.tsx @@ -0,0 +1,73 @@ +import * as React from 'react'; +import { DefaultButton } from 'office-ui-fabric-react/lib/Button'; +import { FocusTrapCallout } from 'office-ui-fabric-react/lib/Callout'; +import { CommandBar, ICommandBarItemProps } from 'office-ui-fabric-react/lib/CommandBar'; +import './CalloutExample.scss'; + +export interface ICalloutFocusTrapExampleProps { + items: ICommandBarItemProps[]; +} + +export class CalloutFocusTrapExample extends React.Component< + ICalloutFocusTrapExampleProps, + { + isCalloutVisible: boolean; + } +> { + private _menuButtonElement: HTMLElement | null; + + public constructor(props: ICalloutFocusTrapExampleProps) { + super(props); + + this._onDismiss = this._onDismiss.bind(this); + + this.state = { + isCalloutVisible: false + }; + } + + public render(): JSX.Element { + const { isCalloutVisible } = this.state; + + return ( +
+
(this._menuButtonElement = menuButton)}> + +
+ {isCalloutVisible ? ( +
+ +
+

+ Callout title here +

+
+
+
+

+ Message body is optional. If help documentation is available, consider adding a link to learn more at the bottom. +

+
+
+ +
+
+ ) : null} +
+ ); + } + + private _onDismiss(ev: any): void { + this.setState({ + isCalloutVisible: !this.state.isCalloutVisible + }); + } +} diff --git a/packages/office-ui-fabric-react/src/components/Callout/index.ts b/packages/office-ui-fabric-react/src/components/Callout/index.ts index b7bb1837bfe1d..b25a3958224c9 100644 --- a/packages/office-ui-fabric-react/src/components/Callout/index.ts +++ b/packages/office-ui-fabric-react/src/components/Callout/index.ts @@ -1,3 +1,5 @@ export * from './Callout'; export * from './Callout.types'; +export * from './FocusTrapCallout'; +export * from './FocusTrapCallout.types'; export * from '../../common/DirectionalHint'; diff --git a/packages/office-ui-fabric-react/src/components/HoverCard/CardCallout/CardCallout.tsx b/packages/office-ui-fabric-react/src/components/HoverCard/CardCallout/CardCallout.tsx index 24fb1853d345f..9c4aebce21d0e 100644 --- a/packages/office-ui-fabric-react/src/components/HoverCard/CardCallout/CardCallout.tsx +++ b/packages/office-ui-fabric-react/src/components/HoverCard/CardCallout/CardCallout.tsx @@ -3,8 +3,8 @@ import * as React from 'react'; import { divProperties, getNativeProps } from '../../../Utilities'; import { Callout } from '../../../Callout'; import { DirectionalHint } from '../../../common/DirectionalHint'; -import { FocusTrapZone } from '../../../FocusTrapZone'; import { IBaseCardProps } from '../BaseCard.types'; +import { FocusTrapCallout, ICalloutProps } from '../../../Callout'; export interface ICardCalloutProps extends IBaseCardProps<{}, {}, {}> { finalHeight?: number; @@ -25,26 +25,35 @@ export const CardCallout = (props: ICardCalloutProps) => { content } = props; + const calloutProps: ICalloutProps = { + ...getNativeProps(props, divProperties), + className: className, + target: targetElement, + isBeakVisible: false, + directionalHint: directionalHint, + directionalHintFixed: directionalHintFixed, + finalHeight: finalHeight, + minPagePadding: 24, + onDismiss: onLeave, + gapSpace: gapSpace + }; + return ( - + {trapFocus ? ( - + {content} - + ) : ( - content + {content} )} - + ); }; diff --git a/packages/office-ui-fabric-react/src/components/__snapshots__/Callout.FocusTrap.Example.tsx.shot b/packages/office-ui-fabric-react/src/components/__snapshots__/Callout.FocusTrap.Example.tsx.shot new file mode 100644 index 0000000000000..7fcc503ffc3c4 --- /dev/null +++ b/packages/office-ui-fabric-react/src/components/__snapshots__/Callout.FocusTrap.Example.tsx.shot @@ -0,0 +1,126 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Component Examples renders Callout.FocusTrap.Example.tsx correctly 1`] = ` +
+
+ +
+
+`;