diff --git a/common/changes/@uifabric/styling/activity-item-animation_2018-04-13-22-07.json b/common/changes/@uifabric/styling/activity-item-animation_2018-04-13-22-07.json new file mode 100644 index 0000000000000..e0300b30da2fc --- /dev/null +++ b/common/changes/@uifabric/styling/activity-item-animation_2018-04-13-22-07.json @@ -0,0 +1,11 @@ +{ + "changes": [ + { + "comment": "PulsingBeaconAnimationStyles: Distinguish between single and double pulse.", + "packageName": "@uifabric/styling", + "type": "minor" + } + ], + "packageName": "@uifabric/styling", + "email": "lynam.emily@gmail.com" +} \ No newline at end of file diff --git a/common/changes/office-ui-fabric-react/activity-item-animation_2018-04-05-17-04.json b/common/changes/office-ui-fabric-react/activity-item-animation_2018-04-05-17-04.json new file mode 100644 index 0000000000000..a1e142e39515a --- /dev/null +++ b/common/changes/office-ui-fabric-react/activity-item-animation_2018-04-05-17-04.json @@ -0,0 +1,11 @@ +{ + "changes": [ + { + "packageName": "office-ui-fabric-react", + "comment": "ActivityItem: Added the pulsing beacon animation for the compact size.", + "type": "minor" + } + ], + "packageName": "office-ui-fabric-react", + "email": "lynam.emily@gmail.com" +} \ No newline at end of file diff --git a/packages/office-ui-fabric-react/src/components/ActivityItem/ActivityItem.classNames.ts b/packages/office-ui-fabric-react/src/components/ActivityItem/ActivityItem.classNames.ts index 4bc5f7842f2c6..71344018de9b6 100644 --- a/packages/office-ui-fabric-react/src/components/ActivityItem/ActivityItem.classNames.ts +++ b/packages/office-ui-fabric-react/src/components/ActivityItem/ActivityItem.classNames.ts @@ -16,6 +16,7 @@ export interface IActivityItemClassNames { activityTypeIcon?: string; commentText?: string; timeStamp?: string; + pulsingBeacon?: string; } export const getClassNames = memoizeFunction(( @@ -32,6 +33,11 @@ export const getClassNames = memoizeFunction(( isCompact && styles.isCompactRoot ), + pulsingBeacon: mergeStyles( + 'ms-ActivityItem-pulsingBeacon', + styles.pulsingBeacon + ), + personaContainer: mergeStyles( 'ms-ActivityItem-personaContainer', styles.personaContainer, diff --git a/packages/office-ui-fabric-react/src/components/ActivityItem/ActivityItem.styles.ts b/packages/office-ui-fabric-react/src/components/ActivityItem/ActivityItem.styles.ts index 4833241cd5c22..e6fe80298c0e9 100644 --- a/packages/office-ui-fabric-react/src/components/ActivityItem/ActivityItem.styles.ts +++ b/packages/office-ui-fabric-react/src/components/ActivityItem/ActivityItem.styles.ts @@ -2,22 +2,66 @@ import { concatStyleSets, ITheme, getTheme, - HighContrastSelector + HighContrastSelector, + keyframes, + PulsingBeaconAnimationStyles } from '../../Styling'; import { memoizeFunction } from '../../Utilities'; -import { IActivityItemStyles } from './ActivityItem.types'; +import { IActivityItemStyles, IActivityItemProps } from './ActivityItem.types'; const DEFAULT_PERSONA_SIZE = '32px'; const COMPACT_PERSONA_SIZE = '16px'; const DEFAULT_ICON_SIZE = '16px'; const COMPACT_ICON_SIZE = '13px'; +const ANIMATION_INNER_DIMENSION = '4px'; +const ANIMATION_OUTER_DIMENSION = '28px'; +const ANIMATION_BORDER_WIDTH = '4px'; export const getStyles = memoizeFunction(( + props: IActivityItemProps, theme: ITheme = getTheme(), customStyles?: IActivityItemStyles ): IActivityItemStyles => { + + const continuousPulse = PulsingBeaconAnimationStyles.continuousPulseAnimationSingle( + props.beaconColorOne ? props.beaconColorOne : theme.palette.themePrimary, + props.beaconColorTwo ? props.beaconColorTwo : theme.palette.themeTertiary, + ANIMATION_INNER_DIMENSION, + ANIMATION_OUTER_DIMENSION, + ANIMATION_BORDER_WIDTH + ); + + const fadeIn: string = keyframes({ + from: { opacity: 0, }, + to: { opacity: 1, } + }); + + const slideIn: string = keyframes({ + from: { transform: 'translateX(-10px)' }, + to: { transform: 'translateX(0)' } + }); + + const continuousPulseAnimation = { + animationName: continuousPulse, + animationIterationCount: '1', + animationDuration: '.8s', + zIndex: 1 + }; + + const slideInAnimation = { + animationName: slideIn, + animationIterationCount: '1', + animationDuration: '.5s', + }; + + const fadeInAnimation = { + animationName: fadeIn, + animationIterationCount: '1', + animationDuration: '.5s', + }; + const ActivityItemStyles: IActivityItemStyles = { root: [ @@ -29,7 +73,23 @@ export const getStyles = memoizeFunction(( lineHeight: '17px', boxSizing: 'border-box', color: theme.palette.neutralSecondary - } + }, + (props.isCompact && props.animateBeaconSignal) && fadeInAnimation + ], + + pulsingBeacon: [ + { + position: 'absolute', + top: '50%', + left: '50%', + transform: 'translate(-50%, -50%)', + width: '0px', + height: '0px', + borderRadius: '225px', + borderStyle: 'solid', + opacity: 0 + }, + (props.isCompact && props.animateBeaconSignal) && continuousPulseAnimation ], isCompactRoot: { @@ -63,19 +123,24 @@ export const getStyles = memoizeFunction(( isCompactIcon: { height: COMPACT_PERSONA_SIZE, + minWidth: COMPACT_PERSONA_SIZE, fontSize: COMPACT_ICON_SIZE, lineHeight: COMPACT_ICON_SIZE, color: theme.palette.themePrimary, marginTop: '1px', + position: 'relative', + display: 'flex', + justifyContent: 'center', + alignItems: 'center', selectors: { '.ms-Persona-imageArea': { - marginTop: '-2px', + margin: '-2px 0 0 -2px', border: '2px solid' + theme.palette.white, borderRadius: '50%', selectors: { [HighContrastSelector]: { border: 'none', - marginTop: '0' + margin: '0' } } } @@ -101,9 +166,12 @@ export const getStyles = memoizeFunction(( overflow: 'visible' }, - activityContent: { - padding: '0 8px' - }, + activityContent: [ + { + padding: '0 8px' + }, + (props.isCompact && props.animateBeaconSignal) && slideInAnimation + ], activityText: { display: 'inline' diff --git a/packages/office-ui-fabric-react/src/components/ActivityItem/ActivityItem.test.tsx b/packages/office-ui-fabric-react/src/components/ActivityItem/ActivityItem.test.tsx index e27dcbb687ee9..56359571948d5 100644 --- a/packages/office-ui-fabric-react/src/components/ActivityItem/ActivityItem.test.tsx +++ b/packages/office-ui-fabric-react/src/components/ActivityItem/ActivityItem.test.tsx @@ -100,4 +100,17 @@ describe('ActivityItem', () => { expect(tree).toMatchSnapshot(); }); + it('renders compact with animation correctly', () => { + const component = renderer.create( + + ); + const tree = component.toJSON(); + expect(tree).toMatchSnapshot(); + }); + }); diff --git a/packages/office-ui-fabric-react/src/components/ActivityItem/ActivityItem.tsx b/packages/office-ui-fabric-react/src/components/ActivityItem/ActivityItem.tsx index 978f0f9f80461..6c7281ebe9814 100644 --- a/packages/office-ui-fabric-react/src/components/ActivityItem/ActivityItem.tsx +++ b/packages/office-ui-fabric-react/src/components/ActivityItem/ActivityItem.tsx @@ -1,7 +1,7 @@ /* tslint:disable */ import * as React from 'react'; /* tslint:enable */ - +import { DefaultPalette } from '../../Styling'; import { BaseComponent } from '../../Utilities'; import { IActivityItemProps, IActivityItemStyles } from './ActivityItem.types'; import { IActivityItemClassNames, getClassNames } from './ActivityItem.classNames'; @@ -9,6 +9,7 @@ import { getStyles } from './ActivityItem.styles'; import { PersonaSize, PersonaCoin, IPersonaSharedProps } from '../../Persona'; export class ActivityItem extends BaseComponent { + constructor(props: IActivityItemProps) { super(props); } @@ -19,7 +20,9 @@ export class ActivityItem extends BaseComponent { onRenderActivityDescription = this._onRenderActivityDescription, onRenderComments = this._onRenderComments, onRenderTimeStamp = this._onRenderTimeStamp, - styles: customStyles + styles: customStyles, + animateBeaconSignal, + isCompact } = this.props; const classNames = this._getClassNames(this.props); @@ -29,6 +32,9 @@ export class ActivityItem extends BaseComponent { { (this.props.activityPersonas || this.props.activityIcon || this.props.onRenderIcon) &&
+ { animateBeaconSignal && isCompact && +
+ } { onRenderIcon(this.props) }
} @@ -122,6 +128,6 @@ export class ActivityItem extends BaseComponent { } private _getClassNames(props: IActivityItemProps): IActivityItemClassNames { - return getClassNames(getStyles(undefined, props.styles), props.className!, props.activityPersonas!, props.isCompact!); + return getClassNames(getStyles(props, undefined, props.styles), props.className!, props.activityPersonas!, props.isCompact!); } } diff --git a/packages/office-ui-fabric-react/src/components/ActivityItem/ActivityItem.types.ts b/packages/office-ui-fabric-react/src/components/ActivityItem/ActivityItem.types.ts index cb9ac03a1c519..f869598ee9e8c 100644 --- a/packages/office-ui-fabric-react/src/components/ActivityItem/ActivityItem.types.ts +++ b/packages/office-ui-fabric-react/src/components/ActivityItem/ActivityItem.types.ts @@ -76,6 +76,23 @@ export interface IActivityItemProps extends React.AllHTMLAttributes * Element shown as a timestamp on this activity. If not included, no timestamp is shown. */ timeStamp?: string | React.ReactNode[] | React.ReactNode; + + /** + * Beacon color one + */ + beaconColorOne?: string; + + /** + * Beacon color two + */ + beaconColorTwo?: string; + + /** + * Enables/Disables the beacon that radiates + * from the center of the center of the activity icon. Signals an activity has started. + * @default false + */ + animateBeaconSignal?: boolean; } export interface IActivityItemStyles { @@ -84,6 +101,11 @@ export interface IActivityItemStyles { */ root?: IStyle; + /** + * Styles applied to the root activity item container. + */ + pulsingBeacon?: IStyle; + /** * Styles applied to the main container of the activity's description. */ diff --git a/packages/office-ui-fabric-react/src/components/ActivityItem/__snapshots__/ActivityItem.test.tsx.snap b/packages/office-ui-fabric-react/src/components/ActivityItem/__snapshots__/ActivityItem.test.tsx.snap index 080d69cf5cce9..164b56b09f407 100644 --- a/packages/office-ui-fabric-react/src/components/ActivityItem/__snapshots__/ActivityItem.test.tsx.snap +++ b/packages/office-ui-fabric-react/src/components/ActivityItem/__snapshots__/ActivityItem.test.tsx.snap @@ -23,19 +23,30 @@ exports[`ActivityItem renders compact with a single persona correctly 1`] = ` className= ms-ActivityItem-activityTypeIcon { + align-items: center; color: #0078d4; + display: flex; font-size: 13px; height: 16px; + justify-content: center; line-height: 13px; margin-top: 1px; + min-width: 16px; + position: relative; } & .ms-Persona-imageArea { border-radius: 50%; border: 2px solid#ffffff; + margin-bottom: 0; + margin-left: -2px; + margin-right: 0; margin-top: -2px; } @media screen and (-ms-high-contrast: active){& .ms-Persona-imageArea { border: none; + margin-bottom: 0; + margin-left: 0; + margin-right: 0; margin-top: 0; } > @@ -214,19 +225,30 @@ exports[`ActivityItem renders compact with an icon correctly 1`] = ` className= ms-ActivityItem-activityTypeIcon { + align-items: center; color: #0078d4; + display: flex; font-size: 13px; height: 16px; + justify-content: center; line-height: 13px; margin-top: 1px; + min-width: 16px; + position: relative; } & .ms-Persona-imageArea { border-radius: 50%; border: 2px solid#ffffff; + margin-bottom: 0; + margin-left: -2px; + margin-right: 0; margin-top: -2px; } @media screen and (-ms-high-contrast: active){& .ms-Persona-imageArea { border: none; + margin-bottom: 0; + margin-left: 0; + margin-right: 0; margin-top: 0; } > @@ -270,6 +292,233 @@ exports[`ActivityItem renders compact with an icon correctly 1`] = `
`; +exports[`ActivityItem renders compact with animation correctly 1`] = ` +
+
+
+
+
+
+ +
+ +
+
+
+
+
+
+ + + description text + + +
+
+`; + exports[`ActivityItem renders compact with multiple personas correctly 1`] = `
diff --git a/packages/office-ui-fabric-react/src/components/Coachmark/Coachmark.styles.ts b/packages/office-ui-fabric-react/src/components/Coachmark/Coachmark.styles.ts index 08374d695f52e..fbca8bb40fa53 100644 --- a/packages/office-ui-fabric-react/src/components/Coachmark/Coachmark.styles.ts +++ b/packages/office-ui-fabric-react/src/components/Coachmark/Coachmark.styles.ts @@ -2,7 +2,9 @@ import { IStyle, IRawStyle, keyframes, - PulsingBeaconAnimationStyles + PulsingBeaconAnimationStyles, + ITheme, + getTheme } from '../../Styling'; export interface ICoachmarkStyleProps { @@ -217,14 +219,15 @@ export const rotateOne: string = keyframes({ } }); -export function getStyles(props: ICoachmarkStyleProps): ICoachmarkStyles { +export function getStyles(props: ICoachmarkStyleProps, theme: ITheme = getTheme(), +): ICoachmarkStyles { const animationInnerDimension = '35px'; const animationOuterDimension = '150px'; const animationBorderWidth = '10px'; - const ContinuousPulse: string = PulsingBeaconAnimationStyles.continuousPulseAnimation( - props.beaconColorOne!, - props.beaconColorTwo!, + const ContinuousPulse: string = PulsingBeaconAnimationStyles.continuousPulseAnimationDouble( + props.beaconColorOne ? props.beaconColorOne : theme.palette.themePrimary, + props.beaconColorTwo ? props.beaconColorTwo : theme.palette.themeTertiary, animationInnerDimension, animationOuterDimension, animationBorderWidth diff --git a/packages/office-ui-fabric-react/src/components/Coachmark/Coachmark.tsx b/packages/office-ui-fabric-react/src/components/Coachmark/Coachmark.tsx index 0500dd656c5f7..456be317cb78d 100644 --- a/packages/office-ui-fabric-react/src/components/Coachmark/Coachmark.tsx +++ b/packages/office-ui-fabric-react/src/components/Coachmark/Coachmark.tsx @@ -71,8 +71,6 @@ export class Coachmark extends BaseComponent { delayBeforeMouseOpen: 3600, // The approximate time the coachmark shows up width: 36, height: 36, - beaconColorOne: '#00FFEC', - beaconColorTwo: '#005EDD', color: DefaultPalette.themePrimary }; @@ -108,9 +106,7 @@ export class Coachmark extends BaseComponent { target, width, height, - color, - beaconColorOne, - beaconColorTwo + color } = this.props; const classNames = getClassNames(getStyles, { @@ -121,9 +117,7 @@ export class Coachmark extends BaseComponent { entityHostWidth: this.state.entityInnerHostRect.width + 'px', width: width + 'px', height: height + 'px', - color: color, - beaconColorOne: beaconColorOne, - beaconColorTwo: beaconColorTwo + color: color }); return ( diff --git a/packages/styling/src/styles/PulsingBeaconAnimationStyles.ts b/packages/styling/src/styles/PulsingBeaconAnimationStyles.ts index eac7e609045cf..bc15dbadd6a60 100644 --- a/packages/styling/src/styles/PulsingBeaconAnimationStyles.ts +++ b/packages/styling/src/styles/PulsingBeaconAnimationStyles.ts @@ -45,7 +45,7 @@ function _continuousPulseStepFive(beaconColorOne: string, innerDimension: string }; } -function _continuousPulseAnimation( +function _continuousPulseAnimationDouble( beaconColorOne: string, beaconColorTwo: string, innerDimension: string, @@ -73,8 +73,24 @@ function _continuousPulseAnimation( }); } +function _continuousPulseAnimationSingle( + beaconColorOne: string, + beaconColorTwo: string, + innerDimension: string, + outerDimension: string, + borderWidth: string +): string { + return keyframes({ + '0%': _continuousPulseStepOne(beaconColorOne, innerDimension), + '14.2%': _continuousPulseStepTwo(borderWidth), + '35.7%': _continuousPulseStepThree(), + '71.4%': _continuousPulseStepFour(beaconColorTwo, outerDimension), + '100%': {} + }); +} + function _createDefaultAnimation( - animationName: string, + animationName: string ): IRawStyle { return { animationName, @@ -85,6 +101,7 @@ function _createDefaultAnimation( } export const PulsingBeaconAnimationStyles = { - continuousPulseAnimation: _continuousPulseAnimation, - createDefaultAnimation: _createDefaultAnimation + continuousPulseAnimationDouble: _continuousPulseAnimationDouble, + continuousPulseAnimationSingle: _continuousPulseAnimationSingle, + createDefaultAnimation: _createDefaultAnimation, }; \ No newline at end of file