-
Notifications
You must be signed in to change notification settings - Fork 2.9k
Shimmer: refactor in preparation for migration to OUFR #4958
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
6d0decd
94ea1cd
0db37ed
762e3c5
1c409ba
f2d84d5
053648b
567983c
8c375a6
3b2d554
ce8d116
8689193
df48bbb
a536e6c
22a23e0
c2af0ab
e0831cf
9607b8f
61356d3
a063dd2
4a261d1
dff7fd9
074a924
6fcd497
c12bad1
7e68d54
535bd69
ed177be
8ab9d75
d8ae65b
f2a6c76
84fe6c8
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,11 @@ | ||
| { | ||
| "changes": [ | ||
| { | ||
| "packageName": "@uifabric/experiments", | ||
| "comment": "Shimmer: Refactors and enhances Shimmer with more features.", | ||
| "type": "minor" | ||
| } | ||
| ], | ||
| "packageName": "@uifabric/experiments", | ||
| "email": "v-vibr@microsoft.com" | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,161 +1,136 @@ | ||
| 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<IShimmerStyleProps, IShimmerStyles>(); | ||
|
|
||
| export class ShimmerBase extends BaseComponent<IShimmerProps, {}> { | ||
| @customizable('Shimmer', ['theme']) | ||
| export class ShimmerBase extends BaseComponent<IShimmerProps, IShimmerState> { | ||
| 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 { | ||
| const { | ||
| getStyles, | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Approved, but the
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. See this commit for details: f226fb4 |
||
| 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(getStyles!, { | ||
| 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 ( | ||
| <div className={ this._classNames.root }> | ||
| <div className={ this._classNames.shimmerWrapper }> | ||
| { !!isBaseStyle ? children : renderedElements } | ||
| </div> | ||
|
|
||
| { !!isDataLoaded && | ||
| { !contentLoaded && | ||
| <div className={ this._classNames.shimmerWrapper }> | ||
| { isBaseStyle ? children : // isBaseStyle prop is deprecated and this check needs to be removed in the future | ||
| customElementsGroup ? customElementsGroup : | ||
| <ShimmerElementsGroup | ||
| shimmerElements={ elements } | ||
| /> | ||
| } | ||
| </div> | ||
| } | ||
| { !isBaseStyle && children && // isBaseStyle prop is deprecated and needs to be removed in the future | ||
| <div className={ this._classNames.dataWrapper }> | ||
| { !!children ? children : null } | ||
| { children } | ||
| </div> | ||
| } | ||
| { ariaLabel && !isDataLoaded && | ||
| <div role='status' aria-live='polite'> | ||
| <DelayedRender> | ||
| <div className={ this._classNames.screenReaderText }>{ ariaLabel }</div> | ||
| </DelayedRender> | ||
| </div> | ||
| } | ||
| </div> | ||
| ); | ||
| } | ||
| } | ||
|
|
||
| export function getRenderedElements(lineElements?: Array<ICircle | IGap | ILine>, 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 ( | ||
| <ShimmerCircle | ||
| key={ index } | ||
| { ...elem } | ||
| borderStyle={ getBorderStyles(elem, rowHeight) } | ||
| /> | ||
| ); | ||
| case ShimmerElementType.gap: | ||
| return ( | ||
| <ShimmerGap | ||
| key={ index } | ||
| { ...elem } | ||
| borderStyle={ getBorderStyles(elem, rowHeight) } | ||
| /> | ||
| ); | ||
| case ShimmerElementType.line: | ||
| return ( | ||
| <ShimmerLine | ||
| key={ index } | ||
| { ...elem } | ||
| borderStyle={ getBorderStyles(elem, rowHeight) } | ||
| /> | ||
| ); | ||
| } | ||
| }) : ( | ||
| <ShimmerLine | ||
| height={ LINE_DEFAULT_HEIGHT } | ||
| /> | ||
| ); | ||
|
|
||
| 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<ICircle | IGap | ILine>): number { | ||
| const itemsDefaulted: Array<ICircle | IGap | ILine> = 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; | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There's no need to support deprecated props in an experimental component. Of course anything using it will have to be changed and tested.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@JasonGore This experimental component is used in odsp-common repo in items-view and odsp-shared-react packages. So removing the deprecated props will cause a break on their side. The intention is to merge this change and right after I will migrate this component to OUFR without the deprecated props. Next step will be to go and change in odsp-common the imports from experiments to office-ui-fabric-react and use the correct props. Only when all this happens I will be able to remove the deprecated props in experiments package without breaking the contract with Shimmer API.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok, thanks for the explanation