diff --git a/common/changes/office-ui-fabric-react/mergeStyles-Pivot-part1_2018-04-23-22-47.json b/common/changes/office-ui-fabric-react/mergeStyles-Pivot-part1_2018-04-23-22-47.json new file mode 100644 index 0000000000000..7076174683a95 --- /dev/null +++ b/common/changes/office-ui-fabric-react/mergeStyles-Pivot-part1_2018-04-23-22-47.json @@ -0,0 +1,11 @@ +{ + "changes": [ + { + "packageName": "office-ui-fabric-react", + "comment": "Begin converting Pivot to mergeStyles", + "type": "minor" + } + ], + "packageName": "office-ui-fabric-react", + "email": "v-jojanz@microsoft.com" +} \ No newline at end of file diff --git a/packages/office-ui-fabric-react/src/components/Pivot/Pivot.base.tsx b/packages/office-ui-fabric-react/src/components/Pivot/Pivot.base.tsx new file mode 100644 index 0000000000000..62302ac165387 --- /dev/null +++ b/packages/office-ui-fabric-react/src/components/Pivot/Pivot.base.tsx @@ -0,0 +1,291 @@ +import * as React from 'react'; +import { + BaseComponent, + KeyCodes, + css, + getId, + createRef +} from '../../Utilities'; +import { CommandButton } from '../../Button'; +import { IPivotProps } from './Pivot.types'; +import { IPivotItemProps } from './PivotItem.types'; +import { FocusZone, FocusZoneDirection } from '../../FocusZone'; +import { PivotItem } from './PivotItem'; +import { PivotLinkFormat } from './Pivot.types'; +import { PivotLinkSize } from './Pivot.types'; +import { Icon } from '../../Icon'; +import * as stylesImport from './Pivot.scss'; +const styles: any = stylesImport; + +/** + * Usage: + * + * + * + * + * + * + * + * + * + * + * + * + */ + +export interface IPivotState { + links: IPivotItemProps[]; + selectedKey: string; + selectedTabId: string; +} + +export class PivotBase extends BaseComponent { + private _keyToIndexMapping: { [key: string]: number }; + private _keyToTabIds: { [key: string]: string }; + private _pivotId: string; + private focusZone = createRef(); + + constructor(props: IPivotProps) { + super(props); + this._pivotId = getId('Pivot'); + const links: IPivotItemProps[] = this._getPivotLinks(this.props); + let selectedKey: string | undefined; + + if (props.initialSelectedKey) { + selectedKey = props.initialSelectedKey; + } else if (props.initialSelectedIndex) { + selectedKey = links[props.initialSelectedIndex].itemKey as string; + } else if (props.selectedKey) { + selectedKey = props.selectedKey; + } else if (links.length) { + selectedKey = links[0].itemKey as string; + } + + this.state = { + links, + selectedKey: selectedKey!, + selectedTabId: this._keyToTabIds[selectedKey!], + } as IPivotState; + + this._renderPivotLink = this._renderPivotLink.bind(this); + } + + public componentWillReceiveProps(nextProps: IPivotProps): void { + const links: IPivotItemProps[] = this._getPivotLinks(nextProps); + + this.setState((prevState, props) => { + let selectedKey: string | undefined; + if (this._isKeyValid(nextProps.selectedKey)) { + selectedKey = nextProps.selectedKey; + } else if (this._isKeyValid(prevState.selectedKey)) { + selectedKey = prevState.selectedKey; + } else if (links.length) { + selectedKey = links[0].itemKey; + } + + return { + links: links, + selectedKey, + selectedTabId: this._keyToTabIds[selectedKey as string], + } as IPivotState; + }); + } + + /** + * Sets focus to the first pivot tab. + */ + public focus(): void { + if (this.focusZone.current) { + this.focusZone.current.focus(); + } + } + + public render(): JSX.Element { + return ( +
+ { this._renderPivotLinks() } + { this._renderPivotItem() } +
+ ); + } + + /** + * Renders the set of links to route between pivots + */ + private _renderPivotLinks(): JSX.Element { + return ( + +
    + { this.state.links.map(this._renderPivotLink) } +
+
+ ); + } + + private _renderPivotLink = (link: IPivotItemProps): JSX.Element => { + const { itemKey, headerButtonProps } = link; + const tabId = this._keyToTabIds[itemKey as string]; + const { onRenderItemLink } = link; + let linkContent: JSX.Element | null; + + if (onRenderItemLink) { + linkContent = onRenderItemLink(link, this._renderLinkContent); + } else { + linkContent = this._renderLinkContent(link); + } + + return ( + + { linkContent } + + ); + } + + private _renderLinkContent = (link: IPivotItemProps): JSX.Element => { + const { itemCount, itemIcon, headerText } = link; + + return ( + + { itemIcon !== undefined && ( + + + + ) } + { headerText !== undefined && { link.headerText } } + { itemCount !== undefined && ({ itemCount }) } + + ); + } + + /** + * Renders the current Pivot Item + */ + private _renderPivotItem(): JSX.Element | null { + if (this.props.headersOnly) { + return null; + } + + const itemKey: string = this.state.selectedKey; + const index = this._keyToIndexMapping[itemKey]; + const { selectedTabId } = this.state; + + return ( +
+ { React.Children.toArray(this.props.children)[index] } +
+ ); + } + + /** + * Gets the set of PivotLinks as arrary of IPivotItemProps + * The set of Links is determined by child components of type PivotItem + */ + private _getPivotLinks(props: IPivotProps): IPivotItemProps[] { + const links: IPivotItemProps[] = []; + this._keyToIndexMapping = {}; + this._keyToTabIds = {}; + + React.Children.map(props.children, (child: any, index: number) => { + if (typeof child === 'object' && child.type === PivotItem) { + const pivotItem = child as PivotItem; + const itemKey = pivotItem.props.itemKey || index.toString(); + + links.push({ + headerText: pivotItem.props.headerText || pivotItem.props.linkText, + headerButtonProps: pivotItem.props.headerButtonProps, + ariaLabel: pivotItem.props.ariaLabel, + itemKey: itemKey, + itemCount: pivotItem.props.itemCount, + itemIcon: pivotItem.props.itemIcon, + onRenderItemLink: pivotItem.props.onRenderItemLink + }); + this._keyToIndexMapping[itemKey] = index; + this._keyToTabIds[itemKey] = this._getTabId(itemKey, index); + } + }); + + return links; + } + + /** + * Generates the Id for the tab button. + */ + private _getTabId(itemKey: string, index: number): string { + if (this.props.getTabId) { + return this.props.getTabId(itemKey, index); + } + + return this._pivotId + `-Tab${index}`; + } + + /** + * whether the key exists in the pivot items. + */ + private _isKeyValid(itemKey: string | undefined): boolean { + return itemKey !== undefined && this._keyToIndexMapping[itemKey] !== undefined; + } + + /** + * Handles the onClick event on PivotLinks + */ + private _onLinkClick(itemKey: string, ev: React.MouseEvent): void { + ev.preventDefault(); + this._updateSelectedItem(itemKey, ev); + } + + /** + * Handle the onKeyPress eventon the PivotLinks + */ + private _onKeyPress(itemKey: string, ev: React.KeyboardEvent): void { + ev.preventDefault(); + if (ev.which === KeyCodes.enter) { + this._updateSelectedItem(itemKey); + } + } + + /** + * Updates the state with the new selected index + */ + private _updateSelectedItem(itemKey: string, ev?: React.MouseEvent): void { + this.setState({ + selectedKey: itemKey, + selectedTabId: this._keyToTabIds[itemKey] + } as IPivotState); + + if (this.props.onLinkClick && this._keyToIndexMapping[itemKey] >= 0) { + const index = this._keyToIndexMapping[itemKey]; + + // React.Element cannot directly convert to PivotItem. + const item = React.Children.toArray(this.props.children)[index] as any; + + if (typeof item === 'object' && item.type === PivotItem) { + this.props.onLinkClick(item as PivotItem, ev); + } + } + } +} diff --git a/packages/office-ui-fabric-react/src/components/Pivot/Pivot.styles.ts b/packages/office-ui-fabric-react/src/components/Pivot/Pivot.styles.ts new file mode 100644 index 0000000000000..2850c2c7743ac --- /dev/null +++ b/packages/office-ui-fabric-react/src/components/Pivot/Pivot.styles.ts @@ -0,0 +1,63 @@ +import { IPivotStyleProps, IPivotStyles } from './Pivot.types'; +import { + normalize, + FontSizes, + FontWeights, +} from '../../Styling'; + +export const getStyles = ( + props: IPivotStyleProps +): IPivotStyles => { + const { + className, + theme, + } = props; + + const { palette } = theme; + + return ({ + root: [ + 'ms-Pivot', + normalize, + { + fontSize: FontSizes.medium, + fontWeight: FontWeights.regular, + position: 'relative', + color: palette.themePrimary, + whiteSpace: 'nowrap', + }, + className + ], + + links: [ + 'ms-Pivot-links', + {} + ], + + link: [ + 'ms-Pivot-link', + {} + ], + + text: [ + 'ms-Pivot-text', + {} + ], + + count: [ + 'ms-Pivot-count', + {} + ], + + icon: [ + 'ms-Pivot-icon', + {} + ], + + ellipsis: [ + 'ms-Pivot-ellipsis', + {} + ], + + }); +}; \ No newline at end of file diff --git a/packages/office-ui-fabric-react/src/components/Pivot/Pivot.tsx b/packages/office-ui-fabric-react/src/components/Pivot/Pivot.tsx index aa686955bf417..8bb22e4f3eb59 100644 --- a/packages/office-ui-fabric-react/src/components/Pivot/Pivot.tsx +++ b/packages/office-ui-fabric-react/src/components/Pivot/Pivot.tsx @@ -1,291 +1,18 @@ -import * as React from 'react'; +import { styled } from '../../Utilities'; import { - BaseComponent, - KeyCodes, - css, - getId, - createRef -} from '../../Utilities'; -import { CommandButton } from '../../Button'; -import { IPivotProps } from './Pivot.types'; -import { IPivotItemProps } from './PivotItem.types'; -import { FocusZone, FocusZoneDirection } from '../../FocusZone'; -import { PivotItem } from './PivotItem'; -import { PivotLinkFormat } from './Pivot.types'; -import { PivotLinkSize } from './Pivot.types'; -import { Icon } from '../../Icon'; -import * as stylesImport from './Pivot.scss'; -const styles: any = stylesImport; + IPivotProps, + IPivotStyleProps, + IPivotStyles +} from './Pivot.types'; +import { PivotBase } from './Pivot.base'; +import { getStyles } from './Pivot.styles'; /** - * Usage: - * - * - * - * - * - * - * - * - * - * - * - * + * The Pivot control and related tabs pattern are used for navigating frequently accessed, + * distinct content categories. Pivots allow for navigation between two or more content + * views and relies on text headers to articulate the different sections of content. */ - -export interface IPivotState { - links: IPivotItemProps[]; - selectedKey: string; - selectedTabId: string; -} - -export class Pivot extends BaseComponent { - private _keyToIndexMapping: { [key: string]: number }; - private _keyToTabIds: { [key: string]: string }; - private _pivotId: string; - private focusZone = createRef(); - - constructor(props: IPivotProps) { - super(props); - this._pivotId = getId('Pivot'); - const links: IPivotItemProps[] = this._getPivotLinks(this.props); - let selectedKey: string | undefined; - - if (props.initialSelectedKey) { - selectedKey = props.initialSelectedKey; - } else if (props.initialSelectedIndex) { - selectedKey = links[props.initialSelectedIndex].itemKey as string; - } else if (props.selectedKey) { - selectedKey = props.selectedKey; - } else if (links.length) { - selectedKey = links[0].itemKey as string; - } - - this.state = { - links, - selectedKey: selectedKey!, - selectedTabId: this._keyToTabIds[selectedKey!], - } as IPivotState; - - this._renderPivotLink = this._renderPivotLink.bind(this); - } - - public componentWillReceiveProps(nextProps: IPivotProps): void { - const links: IPivotItemProps[] = this._getPivotLinks(nextProps); - - this.setState((prevState, props) => { - let selectedKey: string | undefined; - if (this._isKeyValid(nextProps.selectedKey)) { - selectedKey = nextProps.selectedKey; - } else if (this._isKeyValid(prevState.selectedKey)) { - selectedKey = prevState.selectedKey; - } else if (links.length) { - selectedKey = links[0].itemKey; - } - - return { - links: links, - selectedKey, - selectedTabId: this._keyToTabIds[selectedKey as string], - } as IPivotState; - }); - } - - /** - * Sets focus to the first pivot tab. - */ - public focus(): void { - if (this.focusZone.current) { - this.focusZone.current.focus(); - } - } - - public render(): JSX.Element { - return ( -
- { this._renderPivotLinks() } - { this._renderPivotItem() } -
- ); - } - - /** - * Renders the set of links to route between pivots - */ - private _renderPivotLinks(): JSX.Element { - return ( - -
    - { this.state.links.map(this._renderPivotLink) } -
-
- ); - } - - private _renderPivotLink = (link: IPivotItemProps): JSX.Element => { - const { itemKey, headerButtonProps } = link; - const tabId = this._keyToTabIds[itemKey as string]; - const { onRenderItemLink } = link; - let linkContent: JSX.Element | null; - - if (onRenderItemLink) { - linkContent = onRenderItemLink(link, this._renderLinkContent); - } else { - linkContent = this._renderLinkContent(link); - } - - return ( - - { linkContent } - - ); - } - - private _renderLinkContent = (link: IPivotItemProps): JSX.Element => { - const { itemCount, itemIcon, headerText } = link; - - return ( - - { itemIcon !== undefined && ( - - - - ) } - { headerText !== undefined && { link.headerText } } - { itemCount !== undefined && ({ itemCount }) } - - ); - } - - /** - * Renders the current Pivot Item - */ - private _renderPivotItem(): JSX.Element | null { - if (this.props.headersOnly) { - return null; - } - - const itemKey: string = this.state.selectedKey; - const index = this._keyToIndexMapping[itemKey]; - const { selectedTabId } = this.state; - - return ( -
- { React.Children.toArray(this.props.children)[index] } -
- ); - } - - /** - * Gets the set of PivotLinks as arrary of IPivotItemProps - * The set of Links is determined by child components of type PivotItem - */ - private _getPivotLinks(props: IPivotProps): IPivotItemProps[] { - const links: IPivotItemProps[] = []; - this._keyToIndexMapping = {}; - this._keyToTabIds = {}; - - React.Children.map(props.children, (child: any, index: number) => { - if (typeof child === 'object' && child.type === PivotItem) { - const pivotItem = child as PivotItem; - const itemKey = pivotItem.props.itemKey || index.toString(); - - links.push({ - headerText: pivotItem.props.headerText || pivotItem.props.linkText, - headerButtonProps: pivotItem.props.headerButtonProps, - ariaLabel: pivotItem.props.ariaLabel, - itemKey: itemKey, - itemCount: pivotItem.props.itemCount, - itemIcon: pivotItem.props.itemIcon, - onRenderItemLink: pivotItem.props.onRenderItemLink - }); - this._keyToIndexMapping[itemKey] = index; - this._keyToTabIds[itemKey] = this._getTabId(itemKey, index); - } - }); - - return links; - } - - /** - * Generates the Id for the tab button. - */ - private _getTabId(itemKey: string, index: number): string { - if (this.props.getTabId) { - return this.props.getTabId(itemKey, index); - } - - return this._pivotId + `-Tab${index}`; - } - - /** - * whether the key exists in the pivot items. - */ - private _isKeyValid(itemKey: string | undefined): boolean { - return itemKey !== undefined && this._keyToIndexMapping[itemKey] !== undefined; - } - - /** - * Handles the onClick event on PivotLinks - */ - private _onLinkClick(itemKey: string, ev: React.MouseEvent): void { - ev.preventDefault(); - this._updateSelectedItem(itemKey, ev); - } - - /** - * Handle the onKeyPress eventon the PivotLinks - */ - private _onKeyPress(itemKey: string, ev: React.KeyboardEvent): void { - ev.preventDefault(); - if (ev.which === KeyCodes.enter) { - this._updateSelectedItem(itemKey); - } - } - - /** - * Updates the state with the new selected index - */ - private _updateSelectedItem(itemKey: string, ev?: React.MouseEvent): void { - this.setState({ - selectedKey: itemKey, - selectedTabId: this._keyToTabIds[itemKey] - } as IPivotState); - - if (this.props.onLinkClick && this._keyToIndexMapping[itemKey] >= 0) { - const index = this._keyToIndexMapping[itemKey]; - - // React.Element cannot directly convert to PivotItem. - const item = React.Children.toArray(this.props.children)[index] as any; - - if (typeof item === 'object' && item.type === PivotItem) { - this.props.onLinkClick(item as PivotItem, ev); - } - } - } -} +export const Pivot = styled( + PivotBase, + getStyles +); diff --git a/packages/office-ui-fabric-react/src/components/Pivot/Pivot.types.ts b/packages/office-ui-fabric-react/src/components/Pivot/Pivot.types.ts index 594dabd61284c..45f9286e8fb41 100644 --- a/packages/office-ui-fabric-react/src/components/Pivot/Pivot.types.ts +++ b/packages/office-ui-fabric-react/src/components/Pivot/Pivot.types.ts @@ -1,6 +1,7 @@ import * as React from 'react'; - -import { Pivot } from './Pivot'; +import { PivotBase } from './Pivot.base'; +import { IStyle, ITheme } from '../../Styling'; +import { IStyleFunction } from '../../Utilities'; import { PivotItem } from './PivotItem'; export interface IPivot { @@ -10,13 +11,29 @@ export interface IPivot { focus(): void; } -export interface IPivotProps extends React.Props { +export interface IPivotProps extends React.Props { /** * Optional callback to access the IPivot interface. Use this instead of ref for accessing * the public methods and properties of the component. */ componentRef?: (component: IPivot | null) => void; + /** + * Call to provide customized styling that will layer on top of the variant rules. + */ + getStyles?: IStyleFunction; + + /** + * Theme provided by High-Order Component. + */ + theme?: ITheme; + + /** + * Additional css class to apply to the Pivot + * @defaultvalue undefined + */ + className?: string; + /** * The index of the pivot item initially selected. * @@ -67,6 +84,36 @@ export interface IPivotProps extends React.Props { getTabId?: (itemKey: string, index: number) => string; } +export interface IPivotStyleProps { + /** + * Theme provided by High-Order Component. + */ + theme: ITheme; + + /** + * Accept custom classNames + */ + className?: string; + linkIsSelected?: boolean; + linkIsDisabled?: boolean; + linkIsOverflow?: boolean; + rootIsLarge?: boolean; + rootIsTabs?: boolean; +} + +export interface IPivotStyles { + /** + * Style for the root element. + */ + root: IStyle; + links: IStyle; + link: IStyle; + text: IStyle; + count: IStyle; + icon: IStyle; + ellipsis: IStyle; +} + export enum PivotLinkFormat { /** * Display Pivot Links as links @@ -90,4 +137,4 @@ export enum PivotLinkSize { * Display links using large font size */ large = 1 -} +} \ No newline at end of file diff --git a/packages/office-ui-fabric-react/src/components/Pivot/index.ts b/packages/office-ui-fabric-react/src/components/Pivot/index.ts index 666c1856e32a4..99040588b37c6 100644 --- a/packages/office-ui-fabric-react/src/components/Pivot/index.ts +++ b/packages/office-ui-fabric-react/src/components/Pivot/index.ts @@ -1,4 +1,5 @@ export * from './Pivot'; +export * from './Pivot.base'; export { PivotItem as PivotItem } from './PivotItem'; export * from './Pivot.types'; export * from './PivotItem.types';