diff --git a/common/changes/@uifabric/experiments/v-vibr-DetailsList-Shimmer_2018-03-23-00-09.json b/common/changes/@uifabric/experiments/v-vibr-DetailsList-Shimmer_2018-03-23-00-09.json new file mode 100644 index 0000000000000..cdf6a45b1a858 --- /dev/null +++ b/common/changes/@uifabric/experiments/v-vibr-DetailsList-Shimmer_2018-03-23-00-09.json @@ -0,0 +1,11 @@ +{ + "changes": [ + { + "packageName": "@uifabric/experiments", + "comment": "Sets up an example of Shimmer used with DetailsList Component.", + "type": "minor" + } + ], + "packageName": "@uifabric/experiments", + "email": "v-vibr@microsoft.com" +} \ No newline at end of file diff --git a/common/changes/office-ui-fabric-react/v-vibr-DetailsList-Shimmer_2018-03-23-00-09.json b/common/changes/office-ui-fabric-react/v-vibr-DetailsList-Shimmer_2018-03-23-00-09.json new file mode 100644 index 0000000000000..887333ee41ce2 --- /dev/null +++ b/common/changes/office-ui-fabric-react/v-vibr-DetailsList-Shimmer_2018-03-23-00-09.json @@ -0,0 +1,11 @@ +{ + "changes": [ + { + "packageName": "office-ui-fabric-react", + "comment": "Brings changes to DetailsList, DetailsRow and DetailsRowFields to enable use of a basic Shimmer.", + "type": "minor" + } + ], + "packageName": "office-ui-fabric-react", + "email": "v-vibr@microsoft.com" +} \ 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 71ee42a8c6cc6..d5c4ece6fbfe7 100644 --- a/packages/experiments/src/components/Shimmer/ShimmerPage.tsx +++ b/packages/experiments/src/components/Shimmer/ShimmerPage.tsx @@ -36,7 +36,7 @@ export class ShimmerPage extends React.Component { diff --git a/packages/experiments/src/components/Shimmer/examples/Shimmer.Application.Example.tsx b/packages/experiments/src/components/Shimmer/examples/Shimmer.Application.Example.tsx index 99455382cc1c4..8f3a33f1ce933 100644 --- a/packages/experiments/src/components/Shimmer/examples/Shimmer.Application.Example.tsx +++ b/packages/experiments/src/components/Shimmer/examples/Shimmer.Application.Example.tsx @@ -7,15 +7,15 @@ import { IExpandingCardProps } from 'office-ui-fabric-react/lib/HoverCard'; import { createListItems } from '@uifabric/example-app-base'; -import './Shimmer.Example.scss'; import { - Shimmer, -} from 'experiments/lib/Shimmer'; -import { IColumn, DetailsList, buildColumns } from 'office-ui-fabric-react/lib/DetailsList'; - -const PAGING_DELAY = 3000; -const ITEMS_COUNT = 1000; -const PAGING_SIZE = 10; + IColumn, + DetailsList, + buildColumns, + SelectionMode, + Toggle +} from 'office-ui-fabric-react'; +import { Shimmer } from 'experiments/lib/Shimmer'; +import './Shimmer.Example.scss'; export interface IItem { [index: string]: string | number; @@ -30,13 +30,6 @@ export interface IItem { height: number; } -export interface IShimmerElem { - [index: string]: HTMLElement; -} - -// tslint:disable-next-line:no-any -let _items: any[]; - const fileIcons: { name: string; }[] = [ { 'name': 'accdb' }, { 'name': 'csv' }, @@ -63,9 +56,19 @@ const fileIcons: { name: string; }[] = [ { 'name': 'xsn' } ]; +const ITEMS_COUNT = 500; +const ITEMS_BATCH_SIZE = 10; +const PAGING_DELAY = 2500; + +// tslint:disable-next-line:no-any +let _items: any[]; + export interface IShimmerApplicationExampleState { items?: IItem[]; columns?: IColumn[]; + isDataLoaded?: boolean; + isModalSelection?: boolean; + isCompactMode?: boolean; } export class ShimmerApplicationExample extends BaseComponent<{}, IShimmerApplicationExampleState> { @@ -74,57 +77,85 @@ export class ShimmerApplicationExample extends BaseComponent<{}, IShimmerApplica constructor(props: {}) { super(props); - if (!_items) { - _items = createListItems(ITEMS_COUNT); - _items.map((item: IItem) => { - const randomFileType = this._randomFileIcon(); - item.thumbnail = randomFileType.url; - }); - } - this.state = { - items: _items.slice(0, PAGING_SIZE).concat(new Array(ITEMS_COUNT - PAGING_SIZE)), - columns: _buildColumns() + items: new Array(), + columns: _buildColumns(), + isDataLoaded: false, + isModalSelection: false, + isCompactMode: false }; } public render(): JSX.Element { - const { items, columns } = this.state; + const { + items, + columns, + isDataLoaded, + isModalSelection, + isCompactMode + } = this.state; return ( -
-

Hover over location of a row item to see the card

- +
+
+ + +

Toggle the Load data switch to start async simulation.

+ +
+
+ +
); } - private _onRenderMissingItem = (index: number): JSX.Element => { - this._onDataMiss(index as number); + private _onRenderMissingItem = (index: number): React.ReactNode => { + const { isDataLoaded } = this.state; + isDataLoaded && this._onDataMiss(index as number); + return ( ); } - private _onDataMiss(index: number): void { - index = Math.floor(index / PAGING_SIZE) * PAGING_SIZE; - + // Simulating asynchronus data loading each 2.5 sec + private _onDataMiss = (index: number): void => { + index = Math.floor(index / ITEMS_BATCH_SIZE) * ITEMS_BATCH_SIZE; if (!this._isFetchingItems) { - this._isFetchingItems = true; - setTimeout(() => { this._isFetchingItems = false; // tslint:disable-next-line:no-any const itemsCopy = ([] as any[]).concat(this.state.items); - itemsCopy.splice.apply(itemsCopy, [index, PAGING_SIZE].concat(_items.slice(index, index + PAGING_SIZE))); - + itemsCopy.splice.apply(itemsCopy, [index, ITEMS_BATCH_SIZE].concat(_items.slice(index, index + ITEMS_BATCH_SIZE))); this.setState({ items: itemsCopy }); @@ -132,6 +163,35 @@ export class ShimmerApplicationExample extends BaseComponent<{}, IShimmerApplica } } + private _onLoadData = (checked: boolean): void => { + if (!_items) { + _items = createListItems(ITEMS_COUNT); + _items.map((item: IItem) => { + const randomFileType = this._randomFileIcon(); + item.thumbnail = randomFileType.url; + }); + } + let { isDataLoaded, items } = this.state; + isDataLoaded = checked; + if (isDataLoaded) { + items = _items.slice(0, ITEMS_BATCH_SIZE).concat(new Array(ITEMS_COUNT - ITEMS_BATCH_SIZE)); + } else { + items = new Array(); + } + this.setState({ + isDataLoaded: isDataLoaded, + items: items + }); + } + + private _onChangeModalSelection = (checked: boolean): void => { + this.setState({ isModalSelection: checked }); + } + + private _onChangeCompactMode = (checked: boolean): void => { + this.setState({ isCompactMode: checked }); + } + private _onRenderItemColumn = (item: IItem, index: number, column: IColumn): JSX.Element | string | number => { const expandingCardProps: IExpandingCardProps = { onRenderCompactCard: this._onRenderCompactCard, @@ -194,7 +254,8 @@ export class ShimmerApplicationExample extends BaseComponent<{}, IShimmerApplica } function _buildColumns(): IColumn[] { - const columns: IColumn[] = buildColumns(_items); + const _item = createListItems(1); + const columns: IColumn[] = buildColumns(_item); columns.forEach((column: IColumn) => { if (column.key === 'thumbnail') { diff --git a/packages/experiments/src/components/Shimmer/examples/Shimmer.Example.scss b/packages/experiments/src/components/Shimmer/examples/Shimmer.Example.scss index 8238a266957eb..501e970ec958c 100644 --- a/packages/experiments/src/components/Shimmer/examples/Shimmer.Example.scss +++ b/packages/experiments/src/components/Shimmer/examples/Shimmer.Example.scss @@ -14,8 +14,4 @@ text-decoration: underline; cursor: pointer; } - - .shimmerExample-application .ms-Shimmer-container{ - margin-left: 42px; - } } \ No newline at end of file diff --git a/packages/office-ui-fabric-react/src/components/DetailsList/DetailsList.tsx b/packages/office-ui-fabric-react/src/components/DetailsList/DetailsList.tsx index e3cbb0439d44a..14e8e53d0b9d1 100644 --- a/packages/office-ui-fabric-react/src/components/DetailsList/DetailsList.tsx +++ b/packages/office-ui-fabric-react/src/components/DetailsList/DetailsList.tsx @@ -60,6 +60,9 @@ const ISPADDED_WIDTH = 24; const DEFAULT_RENDERED_WINDOWS_AHEAD = 2; const DEFAULT_RENDERED_WINDOWS_BEHIND = 2; +const SHIMMER_INITIAL_ITEMS = 10; +const SHIMMER_ITEMS = new Array(SHIMMER_INITIAL_ITEMS); + @withViewport export class DetailsList extends BaseComponent implements IDetailsList { public static defaultProps = { @@ -67,7 +70,8 @@ export class DetailsList extends BaseComponent, IWithViewpo dragDropEvents?: IDragDropEvents; /** Callback for what to render when the item is missing. */ - onRenderMissingItem?: (index?: number) => React.ReactNode; + onRenderMissingItem?: (index?: number, rowProps?: IDetailsRowProps) => React.ReactNode; + + /** + * If set to true and we provide an empty array, it will render 10 lines of whatever provided in onRenderMissingItem. + * @default false + */ + enableShimmer?: boolean; /** * An override to render the details header. diff --git a/packages/office-ui-fabric-react/src/components/DetailsList/DetailsRow.scss b/packages/office-ui-fabric-react/src/components/DetailsList/DetailsRow.scss index f4d4cc67cd88b..5f23e2c4f63e4 100644 --- a/packages/office-ui-fabric-react/src/components/DetailsList/DetailsRow.scss +++ b/packages/office-ui-fabric-react/src/components/DetailsList/DetailsRow.scss @@ -7,6 +7,13 @@ $rowHorizontalPadding: 8px; $compactRowVerticalPadding: 6px; $isPaddedMargin: 24px; +$rowShimmerHorizontalBorder: $rowHorizontalPadding; +$rowShimmerLineHeight: 7px; +$rowShimmerIconPlaceholderHeight: 16px; +$rowShimmerVerticalBorder: ($rowHeight - $rowShimmerLineHeight) / 2; +$compactRowShimmerVerticalBorder: ($compactRowHeight - $rowShimmerLineHeight) / 2; + + /* DetailsList Colors */ // Default @@ -45,6 +52,23 @@ $detailsList-item-focus-meta-text-color: $ms-color-neutralDark; .cell { min-height: $compactRowHeight; padding: $compactRowVerticalPadding $rowHorizontalPadding; + + // Masking the running shimmer background with borders + &.shimmer { + padding: 0 0; + @include border-left($rowShimmerHorizontalBorder, solid, $detailsList-item-default-background-color); + @include border-right(($rowShimmerHorizontalBorder * 4), solid, $detailsList-item-default-background-color); + border-top: $compactRowShimmerVerticalBorder solid $detailsList-item-default-background-color; + border-bottom: $compactRowShimmerVerticalBorder solid $detailsList-item-default-background-color; + + } + + // Masking the running shimmer background with borders when it's an Icon placeholder + &.shimmerIconPlaceholder { + @include border-right($rowShimmerHorizontalBorder, solid, $detailsList-item-default-background-color); + border-bottom: ($compactRowHeight - $rowShimmerIconPlaceholderHeight) / 2 solid $detailsList-item-default-background-color; + border-top: ($compactRowHeight - $rowShimmerIconPlaceholderHeight) / 2 solid $detailsList-item-default-background-color; + } } .check { @@ -210,6 +234,23 @@ $detailsList-item-focus-meta-text-color: $ms-color-neutralDark; [data-is-focusable=true] { @include focus-border(0, $ms-color-neutralSecondary); } + + // Masking the running shimmer background with borders + &.shimmer { + padding: 0 0; + @include border-left($rowShimmerHorizontalBorder, solid, $detailsList-item-default-background-color); + @include border-right(($rowShimmerHorizontalBorder * 4), solid, $detailsList-item-default-background-color); + border-top: $rowShimmerVerticalBorder solid $detailsList-item-default-background-color; + border-bottom: $rowShimmerVerticalBorder solid $detailsList-item-default-background-color; + + } + + // Masking the running shimmer background with borders when it's an Icon placeholder + &.shimmerIconPlaceholder { + @include border-right($rowShimmerHorizontalBorder, solid, $detailsList-item-default-background-color); + border-bottom: ($rowHeight - $rowShimmerIconPlaceholderHeight) / 2 solid $detailsList-item-default-background-color; + border-top: ($rowHeight - $rowShimmerIconPlaceholderHeight) / 2 solid $detailsList-item-default-background-color; + } } @@ -249,3 +290,12 @@ $detailsList-item-focus-meta-text-color: $ms-color-neutralDark; display: block; } } + +// 40px to take into account the checkbox of items if present. +.shimmerLeftBorder { + border-left: 40px solid $detailsList-item-default-background-color; +} +// 1px to take into account the border-bottom when items replace shimmer lines and in default state. +.shimmerBottomBorder { + border-bottom: 1px solid $detailsList-item-default-background-color; +} \ No newline at end of file diff --git a/packages/office-ui-fabric-react/src/components/DetailsList/DetailsRow.tsx b/packages/office-ui-fabric-react/src/components/DetailsList/DetailsRow.tsx index 460310a79a087..98dff9abac039 100644 --- a/packages/office-ui-fabric-react/src/components/DetailsList/DetailsRow.tsx +++ b/packages/office-ui-fabric-react/src/components/DetailsList/DetailsRow.tsx @@ -54,6 +54,7 @@ export interface IDetailsRowProps extends React.Props { checkboxCellClassName?: string; rowFieldsAs?: React.StatelessComponent | React.ComponentClass; className?: string; + shimmer?: boolean; } export interface IDetailsRowSelectionState { @@ -188,6 +189,8 @@ export class DetailsRow extends BaseComponent + ); + // Rendering Shimmer Animation outside the focus zone + if (shimmer) { + return ( +
+ { rowFields } +
+ ); + } + return ( - ) } + { item && rowFields } { columnMeasureInfo && ( any; + shimmer?: boolean; } export interface IDetailsRowFieldsState { @@ -33,7 +34,7 @@ export class DetailsRowFields extends BaseComponent { @@ -74,7 +77,7 @@ export class DetailsRowFields extends BaseComponent