diff --git a/common/changes/@uifabric/experiments/v-vibr-RevertShimmerRevert_2018-05-30-18-36.json b/common/changes/@uifabric/experiments/v-vibr-RevertShimmerRevert_2018-05-30-18-36.json new file mode 100644 index 0000000000000..7ab61d9fe4b6d --- /dev/null +++ b/common/changes/@uifabric/experiments/v-vibr-RevertShimmerRevert_2018-05-30-18-36.json @@ -0,0 +1,11 @@ +{ + "changes": [ + { + "packageName": "@uifabric/experiments", + "comment": "Shimmer: Shimmer refactor to use new props and deprecate others. Build more examples of Shimmer use.", + "type": "minor" + } + ], + "packageName": "@uifabric/experiments", + "email": "v-vibr@microsoft.com" +} \ No newline at end of file diff --git a/packages/experiments/src/components/Shimmer/Shimmer.base.tsx b/packages/experiments/src/components/Shimmer/Shimmer.base.tsx index ab2fabec2a6ac..60cfb73554f14 100644 --- a/packages/experiments/src/components/Shimmer/Shimmer.base.tsx +++ b/packages/experiments/src/components/Shimmer/Shimmer.base.tsx @@ -1,34 +1,77 @@ import * as React from 'react'; -import { BaseComponent, classNamesFunction } from '../../Utilities'; -import { DefaultPalette, IStyleSet } from '../../Styling'; +import { + BaseComponent, + classNamesFunction, + customizable, + DelayedRender +} from '../../Utilities'; import { IShimmerProps, IShimmerStyleProps, IShimmerStyles, - ShimmerElementType, - ICircle, - ILine, - IGap, - ShimmerElementVerticalAlign, + IShimmerElement, } from './Shimmer.types'; -import { ShimmerLine } from './ShimmerLine/ShimmerLine'; -import { ShimmerGap } from './ShimmerGap/ShimmerGap'; -import { ShimmerCircle } from './ShimmerCircle/ShimmerCircle'; +import { ShimmerElementsGroup } from './ShimmerElementsGroup/ShimmerElementsGroup'; + +export interface IShimmerState { + /** + * Flag for knowing when to remove the shimmerWrapper from the DOM. + */ + contentLoaded?: boolean; +} -const LINE_DEFAULT_HEIGHT = 16; -const GAP_DEFAULT_HEIGHT = 16; -const CIRCLE_DEFAULT_HEIGHT = 24; +const TRANSITION_ANIMATION_INTERVAL = 200; /* ms */ const getClassNames = classNamesFunction(); -export class ShimmerBase extends BaseComponent { +@customizable('Shimmer', ['theme']) +export class ShimmerBase extends BaseComponent { public static defaultProps: IShimmerProps = { isDataLoaded: false, isBaseStyle: false }; + private _classNames: { [key in keyof IShimmerStyles]: string }; + private _lastTimeoutId: number | undefined; + constructor(props: IShimmerProps) { super(props); + + this.state = { + contentLoaded: props.isDataLoaded + }; + + this._warnDeprecations({ + 'isBaseStyle': 'customElementsGroup', + 'width': 'widthInPercentage or widthInPixel', + 'lineElements': 'shimmerElements' + }); + + this._warnMutuallyExclusive({ + 'lineElements': 'shimmerElements', + 'customElementsGroup': 'lineElements' + }); + } + + public componentWillReceiveProps(nextProps: IShimmerProps): void { + const { isDataLoaded } = nextProps; + + if (this._lastTimeoutId !== undefined) { + this._async.clearTimeout(this._lastTimeoutId); + this._lastTimeoutId = undefined; + } + if (isDataLoaded) { + this._lastTimeoutId = this._async.setTimeout(() => { + this.setState({ + contentLoaded: isDataLoaded + }); + this._lastTimeoutId = undefined; + }, TRANSITION_ANIMATION_INTERVAL); + } else { + this.setState({ + contentLoaded: isDataLoaded + }); + } } public render(): JSX.Element { @@ -36,126 +79,58 @@ export class ShimmerBase extends BaseComponent { styles, width, lineElements, + shimmerElements, children, isDataLoaded, isBaseStyle, widthInPercentage, - widthInPixel + widthInPixel, + className, + customElementsGroup, + theme, + ariaLabel } = this.props; - const rowHeight: number | undefined = lineElements ? findMaxElementHeight(lineElements) : undefined; + const { contentLoaded } = this.state; + + // lineElements is a deprecated prop so need to check which one was used. + const elements: IShimmerElement[] | undefined = shimmerElements || lineElements; this._classNames = getClassNames(styles!, { - width, rowHeight, isDataLoaded, isBaseStyle, widthInPercentage, widthInPixel + theme: theme!, + width, + isDataLoaded, + widthInPercentage, + widthInPixel, + className, + transitionAnimationInterval: TRANSITION_ANIMATION_INTERVAL }); - const renderedElements: React.ReactNode = getRenderedElements(lineElements, rowHeight); - return (
-
- { !!isBaseStyle ? children : renderedElements } -
- - { !!isDataLoaded && + { !contentLoaded && +
+ { isBaseStyle ? children : // isBaseStyle prop is deprecated and this check needs to be removed in the future + customElementsGroup ? customElementsGroup : + + } +
+ } + { !isBaseStyle && children && // isBaseStyle prop is deprecated and needs to be removed in the future
- { !!children ? children : null } + { children } +
+ } + { ariaLabel && !isDataLoaded && +
+ +
{ ariaLabel }
+
}
); } -} - -export function getRenderedElements(lineElements?: Array, rowHeight?: number): React.ReactNode { - const renderedElements: React.ReactNode = lineElements ? - lineElements.map((elem: ICircle | ILine | IGap, index: number): JSX.Element => { - switch (elem.type) { - case ShimmerElementType.circle: - return ( - - ); - case ShimmerElementType.gap: - return ( - - ); - case ShimmerElementType.line: - return ( - - ); - } - }) : ( - - ); - - return renderedElements; -} - -export function getBorderStyles(elem: ICircle | IGap | ILine, rowHeight?: number): IStyleSet | undefined { - const elemHeight: number | undefined = elem.height; - - const dif: number = rowHeight && elemHeight ? rowHeight - elemHeight : 0; - - let borderStyle: IStyleSet | undefined; - - if (!elem.verticalAlign || elem.verticalAlign === ShimmerElementVerticalAlign.center) { - borderStyle = { - borderBottom: `${dif ? Math.floor(dif / 2) : 0}px solid ${DefaultPalette.white}`, - borderTop: `${dif ? Math.ceil(dif / 2) : 0}px solid ${DefaultPalette.white}` - }; - } else if (elem.verticalAlign && elem.verticalAlign === ShimmerElementVerticalAlign.top) { - borderStyle = { - borderBottom: `${dif ? dif : 0}px solid ${DefaultPalette.white}`, - borderTop: `0px solid ${DefaultPalette.white}` - }; - } else if (elem.verticalAlign && elem.verticalAlign === ShimmerElementVerticalAlign.bottom) { - borderStyle = { - borderBottom: `0px solid ${DefaultPalette.white}`, - borderTop: `${dif ? dif : 0}px solid ${DefaultPalette.white}` - }; - } - - return borderStyle; -} - -export function findMaxElementHeight(elements: Array): number { - const itemsDefaulted: Array = elements.map((elem: ICircle | IGap | ILine): ICircle | IGap | ILine => { - switch (elem.type) { - case ShimmerElementType.circle: - if (!elem.height) { - elem.height = CIRCLE_DEFAULT_HEIGHT; - } - case ShimmerElementType.line: - if (!elem.height) { - elem.height = LINE_DEFAULT_HEIGHT; - } - case ShimmerElementType.gap: - if (!elem.height) { - elem.height = GAP_DEFAULT_HEIGHT; - } - } - return elem; - }); - - const rowHeight = itemsDefaulted.reduce((acc: number, next: ICircle | IGap | ILine): number => { - return next.height ? - next.height > acc ? next.height : acc - : acc; - }, 0); - - return rowHeight; } \ No newline at end of file diff --git a/packages/experiments/src/components/Shimmer/Shimmer.styles.ts b/packages/experiments/src/components/Shimmer/Shimmer.styles.ts index 730d0c737b1b7..66493a4e99cfc 100644 --- a/packages/experiments/src/components/Shimmer/Shimmer.styles.ts +++ b/packages/experiments/src/components/Shimmer/Shimmer.styles.ts @@ -1,17 +1,53 @@ import { IShimmerStyleProps, IShimmerStyles } from './Shimmer.types'; -import { keyframes, DefaultPalette } from '../../Styling'; +import { + keyframes, + getGlobalClassNames, + hiddenContentStyle, + HighContrastSelector +} from '../../Styling'; +import { getRTL } from '../../Utilities'; + +const GlobalClassNames = { + root: 'ms-Shimmer-container', + shimmerWrapper: 'ms-Shimmer-shimmerWrapper', + dataWrapper: 'ms-Shimmer-dataWrapper' +}; + +const BACKGROUND_OFF_SCREEN_POSITION = '1000%'; + +const shimmerAnimation: string = keyframes({ + '0%': { + backgroundPosition: `-${BACKGROUND_OFF_SCREEN_POSITION}` + }, + '100%': { + backgroundPosition: BACKGROUND_OFF_SCREEN_POSITION + } +}); + +const shimmerAnimationRTL: string = keyframes({ + '100%': { + backgroundPosition: `-${BACKGROUND_OFF_SCREEN_POSITION}` + }, + '0%': { + backgroundPosition: BACKGROUND_OFF_SCREEN_POSITION + } +}); export function getStyles(props: IShimmerStyleProps): IShimmerStyles { const { width, - rowHeight, isDataLoaded, - isBaseStyle, widthInPercentage, - widthInPixel + widthInPixel, + className, + theme, + transitionAnimationInterval } = props; - const BACKGROUND_OFF_SCREEN_POSITION = '1000%'; + const { palette } = theme; + const classNames = getGlobalClassNames(GlobalClassNames, theme); + + const isRTL = getRTL(); // TODO reduce the logic after the deprecated value will be removed. const ACTUAL_WIDTH = @@ -19,83 +55,74 @@ export function getStyles(props: IShimmerStyleProps): IShimmerStyles { widthInPercentage ? widthInPercentage + '%' : widthInPixel ? widthInPixel + 'px' : '100%'; - const shimmerAnimation: string = keyframes({ - '0%': { - backgroundPosition: `-${BACKGROUND_OFF_SCREEN_POSITION}` - }, - '100%': { - backgroundPosition: BACKGROUND_OFF_SCREEN_POSITION - } - }); - return { root: [ - 'ms-Shimmer-container', + classNames.root, { position: 'relative', - margin: '10px', - width: 'auto', - boxSizing: 'content-box', - minHeight: rowHeight ? `${rowHeight}px` : '16px' + height: 'auto' }, - isBaseStyle && { - margin: '0', - minHeight: 'inherit', - display: 'flex', - alignItems: 'center' - } + className ], shimmerWrapper: [ - 'ms-Shimmer-shimmerWrapper', + classNames.shimmerWrapper, { - display: 'flex', - position: 'absolute', - top: '0', - bottom: '0', - left: '0', - right: '0', - alignItems: 'center', - alignContent: 'space-between', width: ACTUAL_WIDTH, - height: 'auto', - boxSizing: 'border-box', - background: `${DefaultPalette.neutralLighter} + background: `${palette.neutralLighter} linear-gradient( to right, - ${DefaultPalette.neutralLighter} 0%, - ${DefaultPalette.neutralLight} 50%, - ${DefaultPalette.neutralLighter} 100%) + ${palette.neutralLighter} 0%, + ${palette.neutralLight} 50%, + ${palette.neutralLighter} 100%) 0 0 / 90% 100% - no-repeat - content-box`, + no-repeat`, animationDuration: '2s', animationTimingFunction: 'ease-in-out', animationDirection: 'normal', animationIterationCount: 'infinite', - animationName: shimmerAnimation, - transition: 'opacity 200ms, visibility 200ms' + animationName: isRTL ? shimmerAnimationRTL : shimmerAnimation, + transition: `opacity ${transitionAnimationInterval}ms`, + selectors: { + [HighContrastSelector]: { + background: `WindowText + linear-gradient( + to right, + transparent 0%, + Window 50%, + transparent 100%) + 0 0 / 90% 100% + no-repeat` + } + } }, isDataLoaded && { opacity: '0', - visibility: 'hidden' - }, - isBaseStyle && { - position: 'static' + position: 'absolute', + top: '0', + bottom: '0', + left: '0', + right: '0', } ], dataWrapper: [ - 'ms-Shimmer-dataWrapper', + classNames.dataWrapper, { + position: 'absolute', + top: '0', + bottom: '0', + left: '0', + right: '0', opacity: '0', - lineHeight: '1', background: 'none', backgroundColor: 'transparent', border: 'none', - transition: 'opacity 200ms' + transition: `opacity ${transitionAnimationInterval}ms` }, isDataLoaded && { - opacity: '1' + opacity: '1', + position: 'static' } - ] + ], + screenReaderText: hiddenContentStyle }; } diff --git a/packages/experiments/src/components/Shimmer/Shimmer.types.ts b/packages/experiments/src/components/Shimmer/Shimmer.types.ts index 9af22fb366e77..b57552b8344a8 100644 --- a/packages/experiments/src/components/Shimmer/Shimmer.types.ts +++ b/packages/experiments/src/components/Shimmer/Shimmer.types.ts @@ -1,5 +1,5 @@ import * as React from 'react'; -import { IStyle } from '../../Styling'; +import { IStyle, ITheme } from '../../Styling'; import { IStyleFunctionOrObject } from '../../Utilities'; export interface IShimmer { @@ -16,14 +16,6 @@ export interface IShimmerProps extends React.AllHTMLAttributes { */ componentRef?: (component: IShimmer | null) => void; - /** - * Sets the width of the shimmer wave wrapper in percentages. - * Deprecated, use a more specific width like widthInPixel or widthInPercentage. - * @default 100% - * @deprecated - */ - width?: number; - /** * Sets the width of the shimmer wave wrapper in percentages relative to the containig parent element. * @default 100% @@ -43,22 +35,62 @@ export interface IShimmerProps extends React.AllHTMLAttributes { isDataLoaded?: boolean; /** - * Provide when Shimmer is intended to be used when using 'onRenderMissingItem' optional callback of the DetailsList Fabric Component. - * @default false + * Elements to render in one line of the Shimmer. */ - isBaseStyle?: boolean; + shimmerElements?: IShimmerElement[]; /** - * Elements to render in one line of the Shimmer. + * Custom elements when necessary to build complex placeholder skeletons. */ - lineElements?: Array; + customElementsGroup?: React.ReactNode; + + /** + * Localized string of the status label for screen reader + */ + ariaLabel?: string; /** * Call to provide customized styling that will layer on top of the variant rules. */ styles?: IStyleFunctionOrObject; + + /** + * Additional CSS class(es) to apply to the Shimmer container. + */ + className?: string; + + /** + * Theme provided by High-Order Component. + */ + theme?: ITheme; + + /** + * Elements to render in one line of the Shimmer. + * Deprecated, use 'shimmerElements' for better semantic meaning. + * @deprecated Use 'shimmerElements' instead. + */ + lineElements?: IShimmerElement[]; + + /** + * Sets the width of the shimmer wave wrapper in percentages. + * Deprecated, use a more specific width like 'widthInPixel' or 'widthInPercentage'. + * @default 100% + * @deprecated Use a more specific width like 'widthInPixel' or 'widthInPercentage'. + */ + width?: number; + + /** + * Use when providing custom skeleton as children wrapped by shimmer. + * Deprecated in favor of 'customElementsGroup' + * @default false + * @deprecated Use 'customElementsGroup' instead. + */ + isBaseStyle?: boolean; } +/** + * Shimmer Elements Interface + */ export interface IShimmerElement { /** * Required for every element you intend to use. @@ -66,7 +98,7 @@ export interface IShimmerElement { type: ShimmerElementType; /** - * The height of the element (ICircle, ILine) in pixels. + * The height of the element (ICircle, ILine, IGap) in pixels. * Read more details for each specific element. */ height?: number; @@ -140,41 +172,98 @@ export interface IGap extends IShimmerElement { export interface IShimmerStyleProps { width?: number; - rowHeight?: number; widthInPercentage?: number; widthInPixel?: number; isDataLoaded?: boolean; - isBaseStyle?: boolean; + className?: string; + theme: ITheme; + transitionAnimationInterval?: number; } export interface IShimmerStyles { root?: IStyle; shimmerWrapper?: IStyle; dataWrapper?: IStyle; + screenReaderText?: IStyle; } -/** - * The CAPS lock values will be deprecated soon. - * @deprecated - */ -export const enum ShimmerElementType { - LINE = 'line', - CIRCLE = 'circle', - GAP = 'gap', - line = 'line', - circle = 'circle', - gap = 'gap' +export enum ShimmerElementType { + /** + * Line element type + */ + line = 1, + + /** + * Circle element type + */ + circle = 2, + + /** + * Gap element type + */ + gap = 3, + + /** + * @deprecated Use 'line' instead + */ + LINE = 1, + + /** + * @deprecated Use 'circle' instead + */ + CIRCLE = 2, + + /** + * @deprecated Use 'gap' instead + */ + GAP = 3 } -/** - * The CAPS lock values will be deprecated soon. - * @deprecated - */ -export const enum ShimmerElementVerticalAlign { - CENTER = 'center', - BOTTOM = 'bottom', - TOP = 'top', - center = 'center', - bottom = 'bottom', - top = 'top' +export enum ShimmerElementVerticalAlign { + /** + * @deprecated Use 'center' instead + */ + CENTER = 1, + + /** + * @deprecated Use 'bottom' instead + */ + BOTTOM = 2, + + /** + * @deprecated Use 'top' instead + */ + TOP = 3, + + /** + * Positions the element vertically in center + */ + center = 1, + + /** + * Positions the element vertically at the bottom + */ + bottom = 2, + + /** + * Positions the element vertically at the top + */ + top = 3 +} + +export enum ShimmerElementsDefaultHeights { + /** + * Default height of the line element when not provided by user: 16px + */ + line = 16, + + /** + * Default height of the gap element when not provided by user: 16px + */ + gap = 16, + + /** + * Default height of the circle element when not provided by user: 24px + */ + circle = 24 } \ No newline at end of file diff --git a/packages/experiments/src/components/Shimmer/ShimmerCircle/ShimmerCircle.base.tsx b/packages/experiments/src/components/Shimmer/ShimmerCircle/ShimmerCircle.base.tsx index 744828c878f81..59d332d489360 100644 --- a/packages/experiments/src/components/Shimmer/ShimmerCircle/ShimmerCircle.base.tsx +++ b/packages/experiments/src/components/Shimmer/ShimmerCircle/ShimmerCircle.base.tsx @@ -2,6 +2,7 @@ import * as React from 'react'; import { BaseComponent, classNamesFunction, + customizable } from '../../../Utilities'; import { IShimmerCircleProps, @@ -11,6 +12,7 @@ import { const getClassNames = classNamesFunction(); +@customizable('ShimmerCircle', ['theme']) export class ShimmerCircleBase extends BaseComponent { private _classNames: { [key in keyof IShimmerCircleStyles]: string }; @@ -19,8 +21,17 @@ export class ShimmerCircleBase extends BaseComponent { } public render(): JSX.Element { - const { height, styles, borderStyle } = this.props; - this._classNames = getClassNames(styles!, { height, borderStyle }); + const { + height, + styles, + borderStyle, + theme + } = this.props; + this._classNames = getClassNames(styles!, { + theme: theme!, + height, + borderStyle + }); return (
diff --git a/packages/experiments/src/components/Shimmer/ShimmerCircle/ShimmerCircle.styles.ts b/packages/experiments/src/components/Shimmer/ShimmerCircle/ShimmerCircle.styles.ts index 6ca7cf7207b03..c47f2a7205702 100644 --- a/packages/experiments/src/components/Shimmer/ShimmerCircle/ShimmerCircle.styles.ts +++ b/packages/experiments/src/components/Shimmer/ShimmerCircle/ShimmerCircle.styles.ts @@ -2,29 +2,56 @@ import { IShimmerCircleStyleProps, IShimmerCircleStyles } from './ShimmerCircle.types'; -import { IStyleSet, DefaultPalette } from '../../../Styling'; +import { + IStyleSet, + getGlobalClassNames, + HighContrastSelector +} from '../../../Styling'; + +const GlobalClassNames = { + root: 'ms-ShimmerCircle-root', + svg: 'ms-ShimmerCircle-svg' +}; export function getStyles(props: IShimmerCircleStyleProps): IShimmerCircleStyles { const { height, - borderStyle + borderStyle, + theme } = props; + const { palette } = theme; + const classNames = getGlobalClassNames(GlobalClassNames, theme); + const styles: IStyleSet = !!borderStyle ? borderStyle : {}; return { root: [ - 'ms-ShimmerCircle-root', + classNames.root, + styles, { width: `${height}px`, height: `${height}px`, - }, - styles + minWidth: `${height}px`, // Fix for IE11 flex items + borderTopStyle: 'solid', + borderBottomStyle: 'solid', + borderColor: palette.white, + selectors: { + [HighContrastSelector]: { + borderColor: 'Window' + } + } + } ], svg: [ - 'ms-ShimmerCircle-svg', + classNames.svg, { - fill: `${DefaultPalette.white}` + fill: palette.white, + selectors: { + [HighContrastSelector]: { + fill: 'Window' + } + } } ] }; diff --git a/packages/experiments/src/components/Shimmer/ShimmerCircle/ShimmerCircle.types.ts b/packages/experiments/src/components/Shimmer/ShimmerCircle/ShimmerCircle.types.ts index 13bc25d641a55..ecf8514beeeb5 100644 --- a/packages/experiments/src/components/Shimmer/ShimmerCircle/ShimmerCircle.types.ts +++ b/packages/experiments/src/components/Shimmer/ShimmerCircle/ShimmerCircle.types.ts @@ -1,5 +1,5 @@ import * as React from 'react'; -import { IStyle, IStyleSet } from '../../../Styling'; +import { IStyle, IStyleSet, ITheme } from '../../../Styling'; import { IStyleFunctionOrObject } from '../../../Utilities'; export interface IShimmerCircle { @@ -27,6 +27,11 @@ export interface IShimmerCircleProps extends React.AllHTMLAttributes(); + +@customizable('ShimmerElementsGroup', ['theme']) +export class ShimmerElementsGroupBase extends BaseComponent { + public static defaultProps: IShimmerElementsGroupProps = { + flexWrap: false + }; + + private _classNames: { [key in keyof IShimmerElementsGroupStyles]: string }; + + constructor(props: IShimmerElementsGroupProps) { + super(props); + } + + public render(): JSX.Element { + const { + styles, + width, + shimmerElements, + rowHeight, + flexWrap, + theme + } = this.props; + + this._classNames = getClassNames(styles!, { + theme: theme!, + flexWrap, + width + }); + + const height = rowHeight ? rowHeight : this._findMaxElementHeight(shimmerElements ? shimmerElements : []); + + return ( +
+ { this._getRenderedElements(shimmerElements, height) } +
+ ); + } + + private _getRenderedElements = (shimmerElements?: IShimmerElement[], rowHeight?: number): React.ReactNode => { + const renderedElements: React.ReactNode = shimmerElements ? + shimmerElements.map((elem: IShimmerElement, index: number): JSX.Element => { + const { type, ...filteredElem } = elem; + switch (elem.type) { + case ShimmerElementType.circle: + return ( + + ); + case ShimmerElementType.gap: + return ( + + ); + case ShimmerElementType.line: + return ( + + ); + } + }) : ( + + ); + + return renderedElements; + } + + private _getBorderStyles = (elem: IShimmerElement, rowHeight?: number): IStyleSet | undefined => { + const elemHeight: number | undefined = elem.height; + const dif: number = rowHeight && elemHeight ? rowHeight - elemHeight : 0; + + let borderStyle: IStyleSet | undefined; + + if (!elem.verticalAlign || elem.verticalAlign === ShimmerElementVerticalAlign.center) { + borderStyle = { + borderBottomWidth: `${dif ? Math.floor(dif / 2) : 0}px`, + borderTopWidth: `${dif ? Math.ceil(dif / 2) : 0}px` + }; + } else if (elem.verticalAlign && elem.verticalAlign === ShimmerElementVerticalAlign.top) { + borderStyle = { + borderBottomWidth: `${dif ? dif : 0}px`, + borderTopWidth: `0px` + }; + } else if (elem.verticalAlign && elem.verticalAlign === ShimmerElementVerticalAlign.bottom) { + borderStyle = { + borderBottomWidth: `0px`, + borderTopWidth: `${dif ? dif : 0}px` + }; + } + + return borderStyle; + } + + /** + * User should not worry to provide which of the elements is the highest, we do the calculation for him. + * Plus if user forgot to specify the height we assign their defaults. + */ + private _findMaxElementHeight = (elements: IShimmerElement[]): number => { + const itemsDefaulted: IShimmerElement[] = elements.map((elem: IShimmerElement): IShimmerElement => { + switch (elem.type) { + case ShimmerElementType.circle: + if (!elem.height) { + elem.height = ShimmerElementsDefaultHeights.circle; + } + case ShimmerElementType.line: + if (!elem.height) { + elem.height = ShimmerElementsDefaultHeights.line; + } + case ShimmerElementType.gap: + if (!elem.height) { + elem.height = ShimmerElementsDefaultHeights.gap; + } + } + return elem; + }); + + const rowHeight = itemsDefaulted.reduce((acc: number, next: IShimmerElement): number => { + return next.height ? + next.height > acc ? next.height : acc + : acc; + }, 0); + + return rowHeight; + } +} \ No newline at end of file diff --git a/packages/experiments/src/components/Shimmer/ShimmerElementsGroup/ShimmerElementsGroup.styles.ts b/packages/experiments/src/components/Shimmer/ShimmerElementsGroup/ShimmerElementsGroup.styles.ts new file mode 100644 index 0000000000000..ff5fdff5dfde6 --- /dev/null +++ b/packages/experiments/src/components/Shimmer/ShimmerElementsGroup/ShimmerElementsGroup.styles.ts @@ -0,0 +1,31 @@ +import { + IShimmerElementsGroupStyleProps, + IShimmerElementsGroupStyles +} from './ShimmerElementsGroup.types'; +import { getGlobalClassNames } from '../../../Styling'; + +const GlobalClassNames = { + root: 'ms-ShimmerElementsGroup-root' +}; + +export function getStyles(props: IShimmerElementsGroupStyleProps): IShimmerElementsGroupStyles { + const { + width, + flexWrap, + theme + } = props; + + const classNames = getGlobalClassNames(GlobalClassNames, theme); + + return { + root: [ + classNames.root, + { + display: 'flex', + alignItems: 'center', + flexWrap: flexWrap ? 'wrap' : 'nowrap', + width: width ? width : 'auto' + } + ] + }; +} diff --git a/packages/experiments/src/components/Shimmer/ShimmerElementsGroup/ShimmerElementsGroup.tsx b/packages/experiments/src/components/Shimmer/ShimmerElementsGroup/ShimmerElementsGroup.tsx new file mode 100644 index 0000000000000..ddc92b292c22b --- /dev/null +++ b/packages/experiments/src/components/Shimmer/ShimmerElementsGroup/ShimmerElementsGroup.tsx @@ -0,0 +1,13 @@ +import { styled } from '../../../Utilities'; +import { + IShimmerElementsGroupProps, + IShimmerElementsGroupStyleProps, + IShimmerElementsGroupStyles +} from './ShimmerElementsGroup.types'; +import { ShimmerElementsGroupBase } from './ShimmerElementsGroup.base'; +import { getStyles } from './ShimmerElementsGroup.styles'; + +export const ShimmerElementsGroup = styled( + ShimmerElementsGroupBase, + getStyles +); diff --git a/packages/experiments/src/components/Shimmer/ShimmerElementsGroup/ShimmerElementsGroup.types.ts b/packages/experiments/src/components/Shimmer/ShimmerElementsGroup/ShimmerElementsGroup.types.ts new file mode 100644 index 0000000000000..910fece2722ef --- /dev/null +++ b/packages/experiments/src/components/Shimmer/ShimmerElementsGroup/ShimmerElementsGroup.types.ts @@ -0,0 +1,59 @@ +import * as React from 'react'; +import { IStyle, ITheme } from '../../../Styling'; +import { IStyleFunctionOrObject } from '../../../Utilities'; +import { IShimmerElement } from '../Shimmer.types'; + +export interface IShimmerElementsGroup { +} + +/** + * ShimmerElementsGroup component props. + */ +export interface IShimmerElementsGroupProps extends React.AllHTMLAttributes { + /** + * Optional callback to access the IShimmerElementsGroup interface. Use this instead of ref for accessing + * the public methods and properties of the component. + */ + componentRef?: (component: IShimmerElementsGroup | null) => void; + + /** + * Optional maximum row height of the shimmerElements container. + */ + rowHeight?: number; + + /** + * Elements to render in one group of the Shimmer. + */ + shimmerElements?: IShimmerElement[]; + + /** + * Optional boolean for enabling flexWrap of the container containing the shimmerElements. + * @default false + */ + flexWrap?: boolean; + + /** + * Optional width for ShimmerElements container. + */ + width?: string; + + /** + * Theme provided by High-Order Component. + */ + theme?: ITheme; + + /** + * Call to provide customized styling that will layer on top of the variant rules. + */ + styles?: IStyleFunctionOrObject; +} + +export interface IShimmerElementsGroupStyleProps { + flexWrap?: boolean; + width?: string; + theme: ITheme; +} + +export interface IShimmerElementsGroupStyles { + root?: IStyle; +} \ No newline at end of file diff --git a/packages/experiments/src/components/Shimmer/ShimmerGap/ShimmerGap.base.tsx b/packages/experiments/src/components/Shimmer/ShimmerGap/ShimmerGap.base.tsx index ca9f371644530..9ec730c09ad65 100644 --- a/packages/experiments/src/components/Shimmer/ShimmerGap/ShimmerGap.base.tsx +++ b/packages/experiments/src/components/Shimmer/ShimmerGap/ShimmerGap.base.tsx @@ -2,6 +2,7 @@ import * as React from 'react'; import { BaseComponent, classNamesFunction, + customizable } from '../../../Utilities'; import { IShimmerGapProps, @@ -11,6 +12,7 @@ import { const getClassNames = classNamesFunction(); +@customizable('ShimmerGap', ['theme']) export class ShimmerGapBase extends BaseComponent { private _classNames: { [key in keyof IShimmerGapStyles]: string }; @@ -19,9 +21,22 @@ export class ShimmerGapBase extends BaseComponent { } public render(): JSX.Element { - const { height, styles, widthInPercentage, widthInPixel, borderStyle } = this.props; + const { + height, + styles, + widthInPercentage, + widthInPixel, + borderStyle, + theme + } = this.props; - this._classNames = getClassNames(styles!, { height, widthInPixel, widthInPercentage, borderStyle }); + this._classNames = getClassNames(styles!, { + theme: theme!, + height, + widthInPixel, + widthInPercentage, + borderStyle + }); return (
diff --git a/packages/experiments/src/components/Shimmer/ShimmerGap/ShimmerGap.styles.ts b/packages/experiments/src/components/Shimmer/ShimmerGap/ShimmerGap.styles.ts index cf5bbfb0842e7..c71bb783a9009 100644 --- a/packages/experiments/src/components/Shimmer/ShimmerGap/ShimmerGap.styles.ts +++ b/packages/experiments/src/components/Shimmer/ShimmerGap/ShimmerGap.styles.ts @@ -2,29 +2,51 @@ import { IShimmerGapStyleProps, IShimmerGapStyles } from './ShimmerGap.types'; -import { IStyleSet, DefaultPalette } from '../../../Styling'; +import { + IStyleSet, + getGlobalClassNames, + HighContrastSelector +} from '../../../Styling'; + +const GlobalClassNames = { + root: 'ms-ShimmerGap-root' +}; export function getStyles(props: IShimmerGapStyleProps): IShimmerGapStyles { const { height, widthInPercentage, widthInPixel, - borderStyle + borderStyle, + theme } = props; + const { palette } = theme; + const classNames = getGlobalClassNames(GlobalClassNames, theme); + const styles: IStyleSet = !!borderStyle ? borderStyle : {}; const ACTUAL_WIDTH = widthInPercentage ? widthInPercentage + '%' : widthInPixel ? widthInPixel + 'px' : '10px'; return { root: [ - 'ms-ShimmerGap-root', + classNames.root, + styles, { - backgroundColor: `${DefaultPalette.white}`, + backgroundColor: palette.white, width: ACTUAL_WIDTH, + minWidth: widthInPixel ? ACTUAL_WIDTH : 'auto', // Fix for IE11 flex items height: `${height}px`, boxSizing: 'content-box', - }, - styles + borderTopStyle: 'solid', + borderBottomStyle: 'solid', + borderColor: palette.white, + selectors: { + [HighContrastSelector]: { + backgroundColor: 'Window', + borderColor: 'Window' + } + } + } ] }; } diff --git a/packages/experiments/src/components/Shimmer/ShimmerGap/ShimmerGap.types.ts b/packages/experiments/src/components/Shimmer/ShimmerGap/ShimmerGap.types.ts index 9e884e60b7bde..ca139c65b55f1 100644 --- a/packages/experiments/src/components/Shimmer/ShimmerGap/ShimmerGap.types.ts +++ b/packages/experiments/src/components/Shimmer/ShimmerGap/ShimmerGap.types.ts @@ -1,5 +1,5 @@ import * as React from 'react'; -import { IStyle, IStyleSet } from '../../../Styling'; +import { IStyle, IStyleSet, ITheme } from '../../../Styling'; import { IStyleFunctionOrObject } from '../../../Utilities'; export interface IShimmerGap { @@ -39,6 +39,11 @@ export interface IShimmerGapProps extends React.AllHTMLAttributes { */ borderStyle?: IStyleSet; + /** + * Theme provided by High-Order Component. + */ + theme?: ITheme; + /** * Call to provide customized styling that will layer on top of the variant rules. */ @@ -50,6 +55,7 @@ export interface IShimmerGapStyleProps { widthInPercentage?: number; widthInPixel?: number; borderStyle?: IStyleSet; + theme: ITheme; } export interface IShimmerGapStyles { diff --git a/packages/experiments/src/components/Shimmer/ShimmerLine/ShimmerLine.base.tsx b/packages/experiments/src/components/Shimmer/ShimmerLine/ShimmerLine.base.tsx index ade884ef355f9..76d41135d143a 100644 --- a/packages/experiments/src/components/Shimmer/ShimmerLine/ShimmerLine.base.tsx +++ b/packages/experiments/src/components/Shimmer/ShimmerLine/ShimmerLine.base.tsx @@ -2,6 +2,7 @@ import * as React from 'react'; import { BaseComponent, classNamesFunction, + customizable } from '../../../Utilities'; import { IShimmerLineProps, @@ -11,6 +12,7 @@ import { const getClassNames = classNamesFunction(); +@customizable('ShimmerLine', ['theme']) export class ShimmerLineBase extends BaseComponent { private _classNames: { [key in keyof IShimmerLineStyles]: string }; @@ -19,12 +21,40 @@ export class ShimmerLineBase extends BaseComponent { } public render(): JSX.Element { - const { height, styles, widthInPercentage, widthInPixel, borderStyle } = this.props; + const { + height, + styles, + widthInPercentage, + widthInPixel, + borderStyle, + theme + } = this.props; - this._classNames = getClassNames(styles!, { height, widthInPixel, widthInPercentage, borderStyle }); + this._classNames = getClassNames(styles!, { + theme: theme!, + height, + widthInPixel, + widthInPercentage, + borderStyle + }); return ( -
+
+ + + + + + + + + + + + +
); } } \ No newline at end of file diff --git a/packages/experiments/src/components/Shimmer/ShimmerLine/ShimmerLine.styles.ts b/packages/experiments/src/components/Shimmer/ShimmerLine/ShimmerLine.styles.ts index 3025743b7437b..ceb6b79905ad5 100644 --- a/packages/experiments/src/components/Shimmer/ShimmerLine/ShimmerLine.styles.ts +++ b/packages/experiments/src/components/Shimmer/ShimmerLine/ShimmerLine.styles.ts @@ -2,28 +2,96 @@ import { IShimmerLineStyleProps, IShimmerLineStyles } from './ShimmerLine.types'; -import { IStyleSet } from '../../../Styling'; +import { + IStyleSet, + getGlobalClassNames, + HighContrastSelector +} from '../../../Styling'; + +const GlobalClassNames = { + root: 'ms-ShimmerLine-root', + topLeftCorner: 'ms-ShimmerLine-topLeftCorner', + topRightCorner: 'ms-ShimmerLine-topRightCorner', + bottomLeftCorner: 'ms-ShimmerLine-bottomLeftCorner', + bottomRightCorner: 'ms-ShimmerLine-bottomRightCorner' +}; export function getStyles(props: IShimmerLineStyleProps): IShimmerLineStyles { const { height, widthInPercentage, widthInPixel, - borderStyle + borderStyle, + theme } = props; - const styles: IStyleSet = !!borderStyle ? borderStyle : {}; + const { palette } = theme; + const classNames = getGlobalClassNames(GlobalClassNames, theme); + + const styles: IStyleSet = !!borderStyle ? borderStyle : { borderWidth: '0px' }; const ACTUAL_WIDTH = widthInPercentage ? widthInPercentage + '%' : widthInPixel ? widthInPixel + 'px' : '100%'; + const sharedCornerStyles: IStyleSet = { + position: 'absolute', + fill: palette.white + }; + return { root: [ - 'ms-ShimmerLine-root', + classNames.root, + styles, { width: ACTUAL_WIDTH, + minWidth: widthInPixel ? ACTUAL_WIDTH : 'auto', // Fix for IE11 flex items height: `${height}px`, boxSizing: 'content-box', + position: 'relative', + borderTopStyle: 'solid', + borderBottomStyle: 'solid', + borderColor: palette.white, + selectors: { + [HighContrastSelector]: { + borderColor: 'Window', + selectors: { + '> *': { + fill: 'Window' + } + } + } + } + } + ], + topLeftCorner: [ + classNames.topLeftCorner, + { + top: '0', + left: '0' + }, + sharedCornerStyles + ], + topRightCorner: [ + classNames.topRightCorner, + { + top: '0', + right: '0' + }, + sharedCornerStyles + ], + bottomRightCorner: [ + classNames.bottomRightCorner, + { + bottom: '0', + right: '0' + }, + sharedCornerStyles + ], + bottomLeftCorner: [ + classNames.bottomLeftCorner, + { + bottom: '0', + left: '0' }, - styles + sharedCornerStyles ] }; } diff --git a/packages/experiments/src/components/Shimmer/ShimmerLine/ShimmerLine.types.ts b/packages/experiments/src/components/Shimmer/ShimmerLine/ShimmerLine.types.ts index 169f0e971999e..fca9fc87935ec 100644 --- a/packages/experiments/src/components/Shimmer/ShimmerLine/ShimmerLine.types.ts +++ b/packages/experiments/src/components/Shimmer/ShimmerLine/ShimmerLine.types.ts @@ -1,5 +1,5 @@ import * as React from 'react'; -import { IStyle, IStyleSet } from '../../../Styling'; +import { IStyle, IStyleSet, ITheme } from '../../../Styling'; import { IStyleFunctionOrObject } from '../../../Utilities'; export interface IShimmerLine { @@ -39,6 +39,11 @@ export interface IShimmerLineProps extends React.AllHTMLAttributes */ borderStyle?: IStyleSet; + /** + * Theme provided by High-Order Component. + */ + theme?: ITheme; + /** * Call to provide customized styling that will layer on top of the variant rules. */ @@ -50,8 +55,13 @@ export interface IShimmerLineStyleProps { widthInPercentage?: number; widthInPixel?: number; borderStyle?: IStyleSet; + theme: ITheme; } export interface IShimmerLineStyles { root?: IStyle; + topLeftCorner?: IStyle; + topRightCorner?: IStyle; + bottomRightCorner?: IStyle; + bottomLeftCorner?: IStyle; } \ No newline at end of file diff --git a/packages/experiments/src/components/Shimmer/ShimmerPage.tsx b/packages/experiments/src/components/Shimmer/ShimmerPage.tsx index c6856225b2518..f8c1bc02bbccf 100644 --- a/packages/experiments/src/components/Shimmer/ShimmerPage.tsx +++ b/packages/experiments/src/components/Shimmer/ShimmerPage.tsx @@ -5,14 +5,25 @@ import { IComponentDemoPageProps, PropertiesTableSet } from '@uifabric/example-app-base'; +import { Link } from 'office-ui-fabric-react/lib/Link'; import { ShimmerBasicExample } from './examples/Shimmer.Basic.Example'; +import { ShimmerCustomElementsExample } from './examples/Shimmer.CustomElements.Example'; import { ShimmerLoadDataExample } from './examples/Shimmer.LoadData.Example'; import { ShimmerApplicationExample } from './examples/Shimmer.Application.Example'; +import { ShimmerStylingExample } from './examples/Shimmer.Styling.Example'; const ShimmerBasicExampleCode = require( '!raw-loader!@uifabric/experiments/src/components/Shimmer/examples/Shimmer.Basic.Example.tsx' ) as string; +const ShimmerCustomExampleCode = require( + '!raw-loader!@uifabric/experiments/src/components/Shimmer/examples/Shimmer.CustomElements.Example.tsx' +) as string; + +const ShimmerStylingExampleCode = require( + '!raw-loader!@uifabric/experiments/src/components/Shimmer/examples/Shimmer.Styling.Example.tsx' +) as string; + const ShimmerLoadDataExampleCode = require( '!raw-loader!@uifabric/experiments/src/components/Shimmer/examples/Shimmer.LoadData.Example.tsx' ) as string; @@ -30,23 +41,35 @@ export class ShimmerPage extends React.Component { exampleCards={
+ + + + + +
} propertiesTables={ @@ -59,7 +82,22 @@ export class ShimmerPage extends React.Component { overview={

- Shimmer is a temporary animation placeholder for the upcoming data from an API call + Shimmer is a temporary animation placeholder for when data from the service call takes time to get back and we don't want + to block rendering the rest of the UI. +

+

+ When Shimmer is not wrapping the actual component to be rendered while data is + fetching, shimmerElements or customElementsGroup props should be used, and later just replace the + Shimmer UI with the intended content. Otherwise, if smooth transition from Shimmer UI to content is wanted, wrap the content + node with Shimmer tags and use isDataLoaded prop to trigger the transition. For reference use the examples + provided below. +

+

+ For cases when your application supports theming, Shimmer component is equiped with everything you need to just load the + custom theme to the application, and as long as the color palette you provide has an overried for the + two Fabric colors used in + Shimmer, everything should be ok. If no theming is supported, then follow the example showing the use of + the styles prop.

} @@ -70,12 +108,26 @@ export class ShimmerPage extends React.Component {
  • - When construncting a shimmer line using different elements like Circle, Line or Gap, best if providing widths for each - of them to experience a better layout looking as close as possible to real data it is replacing. + Use shimmer to help ease a UI transition when we know the service will potentially take a longer amount of time to retrieve + the data.
  • - Try avoiding multiple shimmer lines of different widths. Each shimmer line is it's own animation and it is dependent on the - width you provide. So for a better visual animation keep the widths consistent. + Provide widths for each of the shimmer elements you used to build a skeleton layout looking as close as possible to real + content it is replacing. +
  • +
  • + Use isDataLoaded prop to trigger the transition once we have the data from the service. + The Shimmer UI should Fade out while the real UI Fades In. +
  • +
  • + Use shimmer if you know the UI loading time is longer than 1 second. +
  • +
  • + Provide an ETA as quickly as possible to help the user understand that the system isn’t broken if you use shimmer and the + delay is longer than 10 seconds you must. +
  • +
  • + Provide shimmer designs for the breakpoints that your experience is supported in.
@@ -84,7 +136,17 @@ export class ShimmerPage extends React.Component {
  • - Do not try using on the same element both types of widths. It will always default to just one of them. + Use on the same element both types of widths. It will always default to just one of them. See documentation below. +
  • +
  • + Build Shimmer UI should with a lot of details. Circles and rectangles are really as detailed as you want to get. Adding more + detail will result in confusion once the UI loads. +
  • +
  • + Use shimmer if you are confident that the UI will take less than a second to load. +
  • +
  • + Use shimmer as a way to not make improvements in your code to improve performance.
diff --git a/packages/experiments/src/components/Shimmer/ShimmerTile/ShimmerTile.base.tsx b/packages/experiments/src/components/Shimmer/ShimmerTile/ShimmerTile.base.tsx index 732ebe57fbeb2..1112a5dd0f0a8 100644 --- a/packages/experiments/src/components/Shimmer/ShimmerTile/ShimmerTile.base.tsx +++ b/packages/experiments/src/components/Shimmer/ShimmerTile/ShimmerTile.base.tsx @@ -10,7 +10,7 @@ import { } from './ShimmerTile.types'; import { TileLayoutSizes, TileSize } from '../../../Tile'; import { ShimmerGap } from '../ShimmerGap/ShimmerGap'; -import { getRenderedElements } from '../Shimmer.base'; +import { ShimmerElementsGroup } from '../ShimmerElementsGroup/ShimmerElementsGroup'; import { ShimmerElementType as ElemType } from '../Shimmer.types'; const enum ShimmerTileLayoutValues { @@ -113,38 +113,33 @@ export class ShimmerTileBase extends BaseComponent { widthInPixel={ contentSize.width } height={ contentSize.height - squareHeight - nameplateHeight } /> -
- { - getRenderedElements( - [ + + /> { itemActivity || itemName ?
@@ -154,61 +149,51 @@ export class ShimmerTileBase extends BaseComponent { /> { itemName ? -
- { - getRenderedElements( - [ - { - type: ElemType.gap, - widthInPixel: (contentSize.width - nameWidth) / 2, - height: nameplateNameHeight - }, - { - type: ElemType.line, - widthInPixel: nameWidth, - height: nameHeight - }, - { - type: ElemType.gap, - widthInPixel: (contentSize.width - nameWidth) / 2, - height: nameplateNameHeight - } - ], - nameplateNameHeight - ) + : null + /> : null } { itemActivity ? -
- { - getRenderedElements( - [ - { - type: ElemType.gap, - widthInPixel: (contentSize.width - activityWidth) / 2, - height: nameplateActivityHeight - }, - { - type: ElemType.line, - widthInPixel: activityWidth, - height: activityHeight - }, - { - type: ElemType.gap, - widthInPixel: (contentSize.width - activityWidth) / 2, - height: nameplateActivityHeight - } - ], - nameplateActivityHeight - ) + : null + /> : null } { private _isFetchingItems: boolean; + private _lastTimeoutId: number; constructor(props: {}) { super(props); @@ -115,15 +112,14 @@ export class ShimmerApplicationExample extends BaseComponent<{}, IShimmerApplica onText='Compact' offText='Normal' /> +
- Toggle the Load data switch to start async simulation. -
+ ); + return ( - - + customElementsGroup={ shimmerRow } + /> ); } @@ -160,7 +158,7 @@ export class ShimmerApplicationExample extends BaseComponent<{}, IShimmerApplica index = Math.floor(index / ITEMS_BATCH_SIZE) * ITEMS_BATCH_SIZE; if (!this._isFetchingItems) { this._isFetchingItems = true; - setTimeout(() => { + this._lastTimeoutId = this._async.setTimeout(() => { this._isFetchingItems = false; // tslint:disable-next-line:no-any const itemsCopy = ([] as any[]).concat(this.state.items); @@ -186,6 +184,7 @@ export class ShimmerApplicationExample extends BaseComponent<{}, IShimmerApplica items = _items.slice(0, ITEMS_BATCH_SIZE).concat(new Array(ITEMS_COUNT - ITEMS_BATCH_SIZE)); } else { items = new Array(); + this._async.clearTimeout(this._lastTimeoutId); } this.setState({ isDataLoaded: checked, @@ -202,22 +201,6 @@ export class ShimmerApplicationExample extends BaseComponent<{}, IShimmerApplica } private _onRenderItemColumn = (item: IItem, index: number, column: IColumn): JSX.Element | string | number => { - const expandingCardProps: IExpandingCardProps = { - onRenderCompactCard: this._onRenderCompactCard, - onRenderExpandedCard: this._onRenderExpandedCard, - renderData: item - }; - - if (column.key === 'key') { - return ( - -
- { item.key } -
-
- ); - } - if (column.key === 'thumbnail') { return ( { - return ( - - ); - } - - private _onRenderExpandedCard = (item: IItem): JSX.Element => { - const { items, columns } = this.state; - return ( -
- { item.description } - -
- ); - } - private _randomFileIcon(): { docType: string; url: string; } { const docType: string = fileIcons[Math.floor(Math.random() * fileIcons.length) + 0].name; return { diff --git a/packages/experiments/src/components/Shimmer/examples/Shimmer.Basic.Example.tsx b/packages/experiments/src/components/Shimmer/examples/Shimmer.Basic.Example.tsx index 3016cb7e96599..339fe4234158b 100644 --- a/packages/experiments/src/components/Shimmer/examples/Shimmer.Basic.Example.tsx +++ b/packages/experiments/src/components/Shimmer/examples/Shimmer.Basic.Example.tsx @@ -1,10 +1,11 @@ import * as React from 'react'; + import { Shimmer, - getRenderedElements, ShimmerElementType as ElemType, ShimmerElementVerticalAlign as ElemVerticalAlign } from '@uifabric/experiments/lib/Shimmer'; + import './Shimmer.Example.scss'; export class ShimmerBasicExample extends React.Component<{}, {}> { @@ -16,9 +17,8 @@ export class ShimmerBasicExample extends React.Component<{}, {}> { public render(): JSX.Element { return ( - // tslint:disable-next-line:jsx-ban-props -
- Generic Shimmer with no elements provided. +
+ Basic Shimmer with no elements provided. It defaults to a line of 16px height. { - Custom Shimmer with elements provided. + Basic Shimmer with elements provided. - Notice how the same elements change relative to the shimmer width provided. { /> { /> Variations of vertical alignment for Circles and Lines. { { type: ElemType.line, height: 10, verticalAlign: ElemVerticalAlign.bottom } ] } /> - Split line examples. -
- - { getRenderedElements([ - { type: ElemType.line, widthInPixel: 40, height: 40 }, - { type: ElemType.gap, widthInPixel: 10, height: 40 } - ], 40) } -
- { getRenderedElements([ - { type: ElemType.line, widthInPixel: 300, height: 10 }, - { type: ElemType.line, widthInPixel: 200, height: 10 }, - { type: ElemType.gap, widthInPixel: 100, height: 20 } - ], 20) } -
-
-
-
- - { getRenderedElements([ - { type: ElemType.circle, height: 40 }, - { type: ElemType.gap, widthInPixel: 10, height: 40 } - ], 40) } -
- { getRenderedElements([ - { type: ElemType.line, widthInPixel: 400, height: 10 }, - { type: ElemType.gap, widthInPixel: 100, height: 20 }, - { type: ElemType.line, widthInPixel: 500, height: 10 } - ], 20) } -
-
-
); } diff --git a/packages/experiments/src/components/Shimmer/examples/Shimmer.CustomElements.Example.tsx b/packages/experiments/src/components/Shimmer/examples/Shimmer.CustomElements.Example.tsx new file mode 100644 index 0000000000000..d5ad5f8c5c3a4 --- /dev/null +++ b/packages/experiments/src/components/Shimmer/examples/Shimmer.CustomElements.Example.tsx @@ -0,0 +1,134 @@ +import * as React from 'react'; + +import { + Shimmer, + ShimmerElementsGroup, + ShimmerElementType as ElemType, + ShimmerElementVerticalAlign as ElemVerticalAlign +} from '@uifabric/experiments/lib/Shimmer'; + +import './Shimmer.Example.scss'; + +export class ShimmerCustomElementsExample extends React.Component<{}, {}> { + + constructor(props: {}) { + super(props); + } + + public render(): JSX.Element { + + return ( +
+ Using ShimmerElementsGroup component to build complex structures of the placeholder you need. + + + +
+ ); + } + + private _getCustomElementsExampleOne = (): JSX.Element => { + return ( +
+ + +
+ ); + } + + private _getCustomElementsExampleTwo = (): JSX.Element => { + return ( +
+ + +
+ ); + } + + private _getCustomElementsExampleThree = (): JSX.Element => { + return ( +
+ +
+ + + +
+
+ ); + } +} \ No newline at end of file diff --git a/packages/experiments/src/components/Shimmer/examples/Shimmer.Example.scss b/packages/experiments/src/components/Shimmer/examples/Shimmer.Example.scss index 2e6f3ba1a5ac5..6710baae638e7 100644 --- a/packages/experiments/src/components/Shimmer/examples/Shimmer.Example.scss +++ b/packages/experiments/src/components/Shimmer/examples/Shimmer.Example.scss @@ -1,18 +1,10 @@ :global { - .hoverCardExample-compactCard { - display: flex; - align-items: center; - justify-content: center; - height: 100%; - } - - .hoverCardExample-expandedCard { - padding: 16px 24px; - } + .shimmerBasicExample-container { + padding: 2px; - .HoverCard-item:hover { - text-decoration: underline; - cursor: pointer; + .ms-Shimmer-container { + margin: 10px 0; + } } .shimmerExample-flexGroup { @@ -22,8 +14,4 @@ margin-right: 30px; } } - - .shimmerBasicExample-wrapper { - margin: 10px; - } -} \ No newline at end of file +} diff --git a/packages/experiments/src/components/Shimmer/examples/Shimmer.LoadData.Example.tsx b/packages/experiments/src/components/Shimmer/examples/Shimmer.LoadData.Example.tsx index 7864e1ed4805e..b39eb2a6d7312 100644 --- a/packages/experiments/src/components/Shimmer/examples/Shimmer.LoadData.Example.tsx +++ b/packages/experiments/src/components/Shimmer/examples/Shimmer.LoadData.Example.tsx @@ -1,40 +1,130 @@ import * as React from 'react'; -import { Shimmer } from '@uifabric/experiments/lib/Shimmer'; +import { + Shimmer, + ShimmerElementsGroup, + ShimmerElementType as ElemType, + ShimmerElementVerticalAlign as ElemVerticalAlign +} from '@uifabric/experiments/lib/Shimmer'; +import { + Persona, + PersonaSize, + PersonaPresence, + IPersonaProps +} from 'office-ui-fabric-react/lib/Persona'; import { Toggle } from 'office-ui-fabric-react/lib/Toggle'; +import { PersonaDetails } from './ExampleHelper'; -export interface IShimmerLoadDataExample { - isDataLoaded?: boolean; +export interface IShimmerLoadDataExampleState { + isDataLoadedOne?: boolean; + isDataLoadedTwo?: boolean; + contentOne?: string; + examplePersona?: IPersonaProps; } -export class ShimmerLoadDataExample extends React.Component<{}, IShimmerLoadDataExample> { +export class ShimmerLoadDataExample extends React.Component<{}, IShimmerLoadDataExampleState> { constructor(props: {}) { super(props); this.state = { - isDataLoaded: false + isDataLoadedOne: false, + isDataLoadedTwo: false, + contentOne: '', + examplePersona: {} }; } public render(): JSX.Element { const { - isDataLoaded: dataLoaded, + isDataLoadedOne, + isDataLoadedTwo, + contentOne, + examplePersona } = this.state; return ( - // tslint:disable-next-line:jsx-ban-props -
+
+ -
Data Loaded Data Loaded Data Loaded Data Loaded Data Loaded Data Loaded Data Loaded
+
+ { contentOne } + { contentOne } + { contentOne } +
this.setState({ isDataLoaded }) } - onText='Loaded' - offText='Loading...' + checked={ isDataLoadedTwo } + onChanged={ this._getContentTwo } + onText='Toggle to show shimmer' + offText='Toggle to load content' + /> + + + +
+ ); + } + + private _getContentOne = (checked: boolean): void => { + const { isDataLoadedOne } = this.state; + this.setState({ + isDataLoadedOne: checked, + contentOne: !isDataLoadedOne ? 'Congratulations!!! You have successfully loaded the content. ' : '' + }); + } + + private _getContentTwo = (checked: boolean): void => { + const { isDataLoadedTwo } = this.state; + this.setState({ + isDataLoadedTwo: checked, + examplePersona: !isDataLoadedTwo ? { ...PersonaDetails } : {} + }); + } + + private _getCustomElements = (): JSX.Element => { + return ( +
+ +
); diff --git a/packages/experiments/src/components/Shimmer/examples/Shimmer.Styling.Example.tsx b/packages/experiments/src/components/Shimmer/examples/Shimmer.Styling.Example.tsx new file mode 100644 index 0000000000000..1f0ae9821141b --- /dev/null +++ b/packages/experiments/src/components/Shimmer/examples/Shimmer.Styling.Example.tsx @@ -0,0 +1,51 @@ +import * as React from 'react'; + +import { + Shimmer, IShimmerStyleProps, IShimmerStyles +} from '@uifabric/experiments/lib/Shimmer'; + +import './Shimmer.Example.scss'; + +export class ShimmerStylingExample extends React.Component<{}, {}> { + + constructor(props: {}) { + super(props); + } + + public render(): JSX.Element { + + return ( +
+ + + + + +
+ ); + } + + private _getShimmerStyles = (props: IShimmerStyleProps): IShimmerStyles => { + return { + shimmerWrapper: [{ + backgroundColor: '#deecf9', + backgroundImage: 'linear-gradient(to right, rgba(255, 255, 255, 0) 0%, #c7e0f4 50%, rgba(255, 255, 255, 0) 100%)' + }] + }; + } +} \ No newline at end of file diff --git a/packages/experiments/src/components/Shimmer/index.ts b/packages/experiments/src/components/Shimmer/index.ts index 616724aa5a726..88daed3258c1c 100644 --- a/packages/experiments/src/components/Shimmer/index.ts +++ b/packages/experiments/src/components/Shimmer/index.ts @@ -8,4 +8,5 @@ export * from './ShimmerLine/ShimmerLine.types'; export * from './ShimmerCircle/ShimmerCircle'; export * from './ShimmerCircle/ShimmerCircle.types'; export * from './ShimmerGap/ShimmerGap'; -export * from './ShimmerGap/ShimmerGap.types'; \ No newline at end of file +export * from './ShimmerGap/ShimmerGap.types'; +export * from './ShimmerElementsGroup/ShimmerElementsGroup'; \ No newline at end of file diff --git a/packages/experiments/src/components/TilesList/TilesList.tsx b/packages/experiments/src/components/TilesList/TilesList.tsx index 3f85151a29ecb..9b6417b455381 100644 --- a/packages/experiments/src/components/TilesList/TilesList.tsx +++ b/packages/experiments/src/components/TilesList/TilesList.tsx @@ -329,11 +329,9 @@ export class TilesList extends React.Component, IT ( - { finalGrid } - + /> ) : finalGrid ); diff --git a/packages/experiments/src/components/TilesList/examples/TilesList.Document.Example.tsx b/packages/experiments/src/components/TilesList/examples/TilesList.Document.Example.tsx index 9b730404d7792..1ee4ebd57ab4b 100644 --- a/packages/experiments/src/components/TilesList/examples/TilesList.Document.Example.tsx +++ b/packages/experiments/src/components/TilesList/examples/TilesList.Document.Example.tsx @@ -24,7 +24,7 @@ import { ISize } from '@uifabric/experiments/lib/Utilities'; import { ShimmerTile, ShimmerElementType as ElemType, - getRenderedElements + ShimmerElementsGroup } from '@uifabric/experiments/lib/Shimmer'; const HEADER_VERTICAL_PADDING = 13; @@ -263,16 +263,14 @@ export class TilesListDocumentExample extends React.Component { return ( -
- { - getRenderedElements( - [ - { type: ElemType.line, height: HEADER_FONT_SIZE, widthInPercentage: 100 }, - ], - HEADER_VERTICAL_PADDING * 2 + HEADER_FONT_SIZE - ) + + /> ); } }