Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -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"
}
Original file line number Diff line number Diff line change
Expand Up @@ -7711,6 +7711,11 @@ interface IFitContentToBoundsOptions {
mode: FitMode;
}

// @public (undocumented)
interface IFocusTrapCalloutProps extends ICalloutProps {
focusTrapProps?: IFocusTrapZoneProps;
}

// @public (undocumented)
interface IFocusTrapZone {
focus: () => void;
Expand Down Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -32,6 +34,11 @@ export const CalloutPageProps: IDocPageProps = {
code: CalloutNestedExampleCode,
view: <CalloutNestedExample {...cmdBarParamsTextAndIcons} />
},
{
title: 'Focus Trap Callout',
code: CalloutFocusTrapExampleCode,
view: <CalloutFocusTrapExample {...cmdBarParamsTextAndIcons} />
},
{
title: 'Callout with directional hint',
code: CalloutDirectionalExampleCode,
Expand Down
Original file line number Diff line number Diff line change
@@ -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<IFocusTrapCalloutProps> = (props: IFocusTrapCalloutProps): JSX.Element => {
Comment thread
atneik marked this conversation as resolved.
return (
<Callout {...props}>
<FocusTrapZone {...props.focusTrapProps}>{props.children}</FocusTrapZone>
</Callout>
);
};
Original file line number Diff line number Diff line change
@@ -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;
}
Original file line number Diff line number Diff line change
@@ -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 (
<div className="ms-CalloutExample">
<div className="ms-CalloutBasicExample-buttonArea" ref={menuButton => (this._menuButtonElement = menuButton)}>
<DefaultButton onClick={this._onDismiss} text={isCalloutVisible ? 'Hide callout' : 'Show callout'} />
</div>
{isCalloutVisible ? (
<div>
<FocusTrapCallout
role={'alertdialog'}
ariaLabelledBy={'callout-label-2'}
className="ms-CalloutExample-callout"
gapSpace={0}
target={this._menuButtonElement}
onDismiss={this._onDismiss}
setInitialFocus={true}
>
<div className="ms-CalloutExample-header">
<p className="ms-CalloutExample-title" id={'callout-label-2'}>
Callout title here
</p>
</div>
<div className="ms-CalloutExample-inner">
<div className="ms-CalloutExample-content">
<p className="ms-CalloutExample-subText">
Message body is optional. If help documentation is available, consider adding a link to learn more at the bottom.
</p>
</div>
</div>
<CommandBar items={this.props.items} />
</FocusTrapCallout>
</div>
) : null}
</div>
);
}

private _onDismiss(ev: any): void {
this.setState({
isCalloutVisible: !this.state.isCalloutVisible
});
}
}
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
export * from './Callout';
export * from './Callout.types';
export * from './FocusTrapCallout';
Comment thread
cliffkoh marked this conversation as resolved.
export * from './FocusTrapCallout.types';
export * from '../../common/DirectionalHint';
Original file line number Diff line number Diff line change
Expand Up @@ -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<{}, {}, {}> {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I just realized that whatever I said above about FocusTrapCallout applies to this very component itself too, although at least this is a stateless component (though not explicitly typed as such).

Can we document these props?

finalHeight?: number;
Expand All @@ -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 (
<Callout
{...getNativeProps(props, divProperties)}
className={className}
target={targetElement}
isBeakVisible={false}
directionalHint={directionalHint}
directionalHintFixed={directionalHintFixed}
finalHeight={finalHeight}
minPagePadding={24}
onDismiss={onLeave}
gapSpace={gapSpace}
>
<React.Fragment>
{trapFocus ? (
<FocusTrapZone forceFocusInsideTrap={false} isClickableOutsideFocusTrap={true} disableFirstFocus={!firstFocus}>
<FocusTrapCallout
{...calloutProps}
focusTrapProps={{
forceFocusInsideTrap: false,
isClickableOutsideFocusTrap: true,
disableFirstFocus: !firstFocus
}}
>
{content}
</FocusTrapZone>
</FocusTrapCallout>
) : (
content
<Callout {...calloutProps}>{content}</Callout>
)}
</Callout>
</React.Fragment>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`Component Examples renders Callout.FocusTrap.Example.tsx correctly 1`] = `
<div
className="ms-CalloutExample"
>
<div
className="ms-CalloutBasicExample-buttonArea"
>
<button
className=
ms-Button
ms-Button--default
{
-moz-osx-font-smoothing: grayscale;
-webkit-font-smoothing: antialiased;
background-color: #f4f4f4;
border-radius: 0px;
border: 1px solid transparent;
box-sizing: border-box;
color: #333333;
cursor: pointer;
display: inline-block;
font-family: 'Segoe UI', 'Segoe UI Web (West European)', 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif;
font-size: 14px;
font-weight: 400;
height: 32px;
min-width: 80px;
outline: transparent;
padding-bottom: 0;
padding-left: 16px;
padding-right: 16px;
padding-top: 0;
position: relative;
text-align: center;
text-decoration: none;
user-select: none;
vertical-align: top;
}
&::-moz-focus-inner {
border: 0;
}
.ms-Fabric--isFocusVisible &:focus:after {
border: 1px solid #ffffff;
bottom: 0px;
content: "";
left: 0px;
outline: 1px solid #666666;
position: absolute;
right: 0px;
top: 0px;
z-index: 1;
}
@media screen and (-ms-high-contrast: active){.ms-Fabric--isFocusVisible &:focus:after {
border: none;
bottom: -2px;
left: -2px;
outline-color: ButtonText;
right: -2px;
top: -2px;
}
&:active > * {
left: 0px;
position: relative;
top: 0px;
}
&:hover {
background-color: #eaeaea;
color: #212121;
}
@media screen and (-ms-high-contrast: active){&:hover {
border-color: Highlight;
color: Highlight;
}
&:active {
background-color: #c8c8c8;
color: #212121;
}
data-is-focusable={true}
onClick={[Function]}
onKeyDown={[Function]}
onKeyPress={[Function]}
onKeyUp={[Function]}
onMouseDown={[Function]}
onMouseUp={[Function]}
type="button"
>
<div
className=
ms-Button-flexContainer
{
align-items: center;
display: flex;
flex-wrap: nowrap;
height: 100%;
justify-content: center;
}
>
<div
className=
ms-Button-textContainer
{
flex-grow: 1;
}
>
<div
className=
ms-Button-label
{
font-weight: 600;
line-height: 100%;
margin-bottom: 0;
margin-left: 4px;
margin-right: 4px;
margin-top: 0;
}
id="id__0"
>
Show callout
</div>
</div>
</div>
</button>
</div>
</div>
`;