diff --git a/apps/vr-tests/src/stories/PeoplePicker.stories.tsx b/apps/vr-tests/src/stories/PeoplePicker.stories.tsx index 49aaefbe388bb4..8acb13aeae8b24 100644 --- a/apps/vr-tests/src/stories/PeoplePicker.stories.tsx +++ b/apps/vr-tests/src/stories/PeoplePicker.stories.tsx @@ -110,9 +110,9 @@ const suggestionProps = { suggestionsContainerAriaLabel: 'Suggested contacts' }; -let getTextFromItem = (persona: IPersonaProps): string => persona.primaryText as string; +const getTextFromItem = (persona: IPersonaProps): string => persona.primaryText as string; -let getPeople = () => people; +const getPeople = () => people; // Pickers that are 'disabled' are added before the Screener decorator because css classes for suggestion items won't exist storiesOf('PeoplePicker', module) diff --git a/common/changes/@uifabric/merge-styles/mergeStyles-persona-part2_2018-04-07-22-05.json b/common/changes/@uifabric/merge-styles/mergeStyles-persona-part2_2018-04-07-22-05.json new file mode 100644 index 00000000000000..65fc763eb19891 --- /dev/null +++ b/common/changes/@uifabric/merge-styles/mergeStyles-persona-part2_2018-04-07-22-05.json @@ -0,0 +1,11 @@ +{ + "changes": [ + { + "packageName": "@uifabric/merge-styles", + "comment": "Add backgroundClip property definition.", + "type": "minor" + } + ], + "packageName": "@uifabric/merge-styles", + "email": "jordancjanzen@gmail.com" +} \ No newline at end of file diff --git a/common/changes/@uifabric/styling/mergeStyles-persona-part2_2018-04-04-20-44.json b/common/changes/@uifabric/styling/mergeStyles-persona-part2_2018-04-04-20-44.json new file mode 100644 index 00000000000000..8f0f63f7bf3536 --- /dev/null +++ b/common/changes/@uifabric/styling/mergeStyles-persona-part2_2018-04-04-20-44.json @@ -0,0 +1,11 @@ +{ + "changes": [ + { + "packageName": "@uifabric/styling", + "comment": "Add noWrap style set.", + "type": "minor" + } + ], + "packageName": "@uifabric/styling", + "email": "v-jojanz@microsoft.com" +} \ No newline at end of file diff --git a/common/changes/office-ui-fabric-react/mergeStyles-persona-part2_2018-04-04-20-44.json b/common/changes/office-ui-fabric-react/mergeStyles-persona-part2_2018-04-04-20-44.json new file mode 100644 index 00000000000000..9cfa8908d33de0 --- /dev/null +++ b/common/changes/office-ui-fabric-react/mergeStyles-persona-part2_2018-04-04-20-44.json @@ -0,0 +1,11 @@ +{ + "changes": [ + { + "packageName": "office-ui-fabric-react", + "comment": "Convert Persona and PersonaCoin and PersonaPresence to mergeStyles. Create IPersonaSharedProps for props that apply to Persona and PersonaCoin and PersonaPresence. Other refactors.", + "type": "minor" + } + ], + "packageName": "office-ui-fabric-react", + "email": "v-jojanz@microsoft.com" +} \ No newline at end of file diff --git a/packages/merge-styles/src/IRawStyleBase.ts b/packages/merge-styles/src/IRawStyleBase.ts index c0a2414ee3dcd8..67952762bdd8b4 100644 --- a/packages/merge-styles/src/IRawStyleBase.ts +++ b/packages/merge-styles/src/IRawStyleBase.ts @@ -293,6 +293,16 @@ export interface IRawStyleBase extends IRawFontStyle { */ backgroundBlendMode?: ICSSRule | string; + /** + * The background-clip CSS property specifies if an element's background, whether a + * or an , extends underneath its border. + * + * \* Does not work in IE + * + * \* The `text` value is experimental and should not be used in production code. + */ + backgroundClip?: ICSSRule | 'border-box' | 'padding-box' | 'content-box' | 'text'; + /** * Sets the background color of an element. */ diff --git a/packages/office-ui-fabric-react/src/PersonaCoin.ts b/packages/office-ui-fabric-react/src/PersonaCoin.ts index 62003eef0c6e08..f74dace95a2bb9 100644 --- a/packages/office-ui-fabric-react/src/PersonaCoin.ts +++ b/packages/office-ui-fabric-react/src/PersonaCoin.ts @@ -1,3 +1 @@ -export * from './components/Persona/PersonaCoin'; -export * from './components/Persona/Persona.types'; -export * from './components/Persona/PersonaConsts'; +export * from './components/Persona/index'; 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 35a656fccf66fc..978f0f9f80461e 100644 --- a/packages/office-ui-fabric-react/src/components/ActivityItem/ActivityItem.tsx +++ b/packages/office-ui-fabric-react/src/components/ActivityItem/ActivityItem.tsx @@ -6,7 +6,7 @@ import { BaseComponent } from '../../Utilities'; import { IActivityItemProps, IActivityItemStyles } from './ActivityItem.types'; import { IActivityItemClassNames, getClassNames } from './ActivityItem.classNames'; import { getStyles } from './ActivityItem.styles'; -import { PersonaSize, PersonaCoin, IPersonaProps } from '../../Persona'; +import { PersonaSize, PersonaCoin, IPersonaSharedProps } from '../../Persona'; export class ActivityItem extends BaseComponent { constructor(props: IActivityItemProps) { @@ -90,7 +90,7 @@ export class ActivityItem extends BaseComponent { const classNames = this._getClassNames(props); let personaElement: JSX.Element | null = null; - const activityPersonas = props.activityPersonas as Array; + const activityPersonas = props.activityPersonas as Array; if (activityPersonas[0].imageUrl || activityPersonas[0].imageInitials) { const personaList: Array = []; const showSize16Personas = (activityPersonas.length > 1 || 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 ac4bd18861bf0f..cb9ac03a1c5197 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 @@ -1,7 +1,7 @@ import * as React from 'react'; import { IStyle } from '../../Styling'; import { IRenderFunction } from '../../Utilities'; -import { IPersonaProps } from '../../Persona'; +import { IPersonaSharedProps } from '../../Persona'; // Please keep alphabetized export interface IActivityItemProps extends React.AllHTMLAttributes { @@ -24,7 +24,7 @@ export interface IActivityItemProps extends React.AllHTMLAttributes /** * If activityIcon is not set, then the persona props in this array will be used as the icon for the this activity item. */ - activityPersonas?: Array; + activityPersonas?: Array; /** * An element containing the text of comments or @mention messages. If no comments, commentText, or onRenderComments are included, no comments are shown. 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 10a242fb6db7b2..080d69cf5cce9d 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 @@ -53,7 +53,16 @@ exports[`ActivityItem renders compact with a single persona correctly 1`] = ` } >
@@ -86,7 +116,16 @@ exports[`ActivityItem renders compact with a single persona correctly 1`] = ` ms-Image ms-Persona-image { + border-radius: 50%; + border: 0px; + height: 16px; + left: 0px; + margin-right: 10px; overflow: hidden; + perspective: 1px; + position: absolute; + top: 0px; + width: 16px; } style={ Object { @@ -106,9 +145,9 @@ exports[`ActivityItem renders compact with a single persona correctly 1`] = ` { display: block; height: auto; - left: 50%; + left: 50% /* @noflip */; opacity: 0; - position: relative; + position: absolute; top: 50%; transform: translate(-50%,-50%); width: 100%; @@ -284,7 +323,16 @@ exports[`ActivityItem renders compact with multiple personas correctly 1`] = ` } >
@@ -317,7 +386,16 @@ exports[`ActivityItem renders compact with multiple personas correctly 1`] = ` ms-Image ms-Persona-image { + border-radius: 50%; + border: 0px; + height: 16px; + left: 0px; + margin-right: 10px; overflow: hidden; + perspective: 1px; + position: absolute; + top: 0px; + width: 16px; } style={ Object { @@ -337,9 +415,9 @@ exports[`ActivityItem renders compact with multiple personas correctly 1`] = ` { display: block; height: auto; - left: 50%; + left: 50% /* @noflip */; opacity: 0; - position: relative; + position: absolute; top: 50%; transform: translate(-50%,-50%); width: 100%; @@ -353,7 +431,16 @@ exports[`ActivityItem renders compact with multiple personas correctly 1`] = `
@@ -442,7 +576,16 @@ exports[`ActivityItem renders compact with multiple personas correctly 1`] = ` ms-Image ms-Persona-image { + border-radius: 50%; + border: 0px; + height: 16px; + left: 0px; + margin-right: 10px; overflow: hidden; + perspective: 1px; + position: absolute; + top: 0px; + width: 16px; } style={ Object { @@ -462,9 +605,9 @@ exports[`ActivityItem renders compact with multiple personas correctly 1`] = ` { display: block; height: auto; - left: 50%; + left: 50% /* @noflip */; opacity: 0; - position: relative; + position: absolute; top: 50%; transform: translate(-50%,-50%); width: 100%; @@ -549,12 +692,26 @@ exports[`ActivityItem renders with a single persona correctly 1`] = ` } >
@@ -575,7 +745,16 @@ exports[`ActivityItem renders with a single persona correctly 1`] = ` ms-Image ms-Persona-image { + border-radius: 50%; + border: 0px; + height: 32px; + left: 0px; + margin-right: 10px; overflow: hidden; + perspective: 1px; + position: absolute; + top: 0px; + width: 32px; } style={ Object { @@ -595,9 +774,9 @@ exports[`ActivityItem renders with a single persona correctly 1`] = ` { display: block; height: auto; - left: 50%; + left: 50% /* @noflip */; opacity: 0; - position: relative; + position: absolute; top: 50%; transform: translate(-50%,-50%); width: 100%; @@ -793,12 +972,26 @@ exports[`ActivityItem renders with multiple personas correctly 1`] = ` } >
@@ -819,7 +1025,16 @@ exports[`ActivityItem renders with multiple personas correctly 1`] = ` ms-Image ms-Persona-image { + border-radius: 50%; + border: 0px; + height: 16px; + left: 0px; + margin-right: 10px; overflow: hidden; + perspective: 1px; + position: absolute; + top: 0px; + width: 16px; } style={ Object { @@ -839,9 +1054,9 @@ exports[`ActivityItem renders with multiple personas correctly 1`] = ` { display: block; height: auto; - left: 50%; + left: 50% /* @noflip */; opacity: 0; - position: relative; + position: absolute; top: 50%; transform: translate(-50%,-50%); width: 100%; @@ -855,12 +1070,26 @@ exports[`ActivityItem renders with multiple personas correctly 1`] = `
@@ -930,7 +1195,16 @@ exports[`ActivityItem renders with multiple personas correctly 1`] = ` ms-Image ms-Persona-image { + border-radius: 50%; + border: 0px; + height: 16px; + left: 0px; + margin-right: 10px; overflow: hidden; + perspective: 1px; + position: absolute; + top: 0px; + width: 16px; } style={ Object { @@ -950,9 +1224,9 @@ exports[`ActivityItem renders with multiple personas correctly 1`] = ` { display: block; height: auto; - left: 50%; + left: 50% /* @noflip */; opacity: 0; - position: relative; + position: absolute; top: 50%; transform: translate(-50%,-50%); width: 100%; @@ -966,12 +1240,26 @@ exports[`ActivityItem renders with multiple personas correctly 1`] = `
@@ -62,7 +86,16 @@ exports[`DocumentCard renders DocumentCard correctly 1`] = ` ms-Image ms-Persona-image { + border-radius: 50%; + border: 0px; + height: 32px; + left: 0px; + margin-right: 10px; overflow: hidden; + perspective: 1px; + position: absolute; + top: 0px; + width: 32px; } style={ Object { @@ -82,9 +115,9 @@ exports[`DocumentCard renders DocumentCard correctly 1`] = ` { display: block; height: auto; - left: 50%; + left: 50% /* @noflip */; opacity: 0; - position: relative; + position: absolute; top: 50%; transform: translate(-50%,-50%); width: 100%; diff --git a/packages/office-ui-fabric-react/src/components/Facepile/Facepile.test.tsx b/packages/office-ui-fabric-react/src/components/Facepile/Facepile.test.tsx index 8127da0ffca433..3fc6b70cf06a37 100644 --- a/packages/office-ui-fabric-react/src/components/Facepile/Facepile.test.tsx +++ b/packages/office-ui-fabric-react/src/components/Facepile/Facepile.test.tsx @@ -176,7 +176,8 @@ describe('Facepile', () => { expect(wrapper.find(PersonaCoin).length).toEqual(facepilePersonas.length); wrapper.find(PersonaCoin).forEach((node) => { - expect(node.dive().hasClass('ms-Persona--size24')).toBeTruthy(); + // Need multiple Dives since PersonaCoin is decorated + expect(node.dive().dive().dive().hasClass('ms-Persona--size24')).toBeTruthy(); }); // Test small size renders @@ -188,7 +189,8 @@ describe('Facepile', () => { expect(wrapper.find(PersonaCoin).length).toEqual(facepilePersonas.length); wrapper.find(PersonaCoin).forEach((node) => { - expect(node.dive().hasClass('ms-Persona--size40')).toBeTruthy(); + // Need multiple Dives since PersonaCoin is decorated + expect(node.dive().dive().dive().hasClass('ms-Persona--size40')).toBeTruthy(); }); }); diff --git a/packages/office-ui-fabric-react/src/components/Facepile/Facepile.types.ts b/packages/office-ui-fabric-react/src/components/Facepile/Facepile.types.ts index c78f5dcc3f819d..1eb1d92628afa2 100644 --- a/packages/office-ui-fabric-react/src/components/Facepile/Facepile.types.ts +++ b/packages/office-ui-fabric-react/src/components/Facepile/Facepile.types.ts @@ -2,7 +2,7 @@ import * as React from 'react'; import { Facepile } from './Facepile'; import { IButtonProps } from '../Button/index'; import { - IPersonaProps, + IPersonaSharedProps, PersonaInitialsColor, PersonaSize } from '../Persona/index'; @@ -56,7 +56,7 @@ export interface IFacepileProps extends React.Props { overflowButtonType?: OverflowButtonType; /** Method to access properties on the underlying Persona control */ - getPersonaProps?: (persona: IFacepilePersona) => IPersonaProps; + getPersonaProps?: (persona: IFacepilePersona) => IPersonaSharedProps; /** * Optional class for Facepile root element. diff --git a/packages/office-ui-fabric-react/src/components/Facepile/__snapshots__/Facepile.test.tsx.snap b/packages/office-ui-fabric-react/src/components/Facepile/__snapshots__/Facepile.test.tsx.snap index 1ece2b465d1fcb..a75223fb0d25fa 100644 --- a/packages/office-ui-fabric-react/src/components/Facepile/__snapshots__/Facepile.test.tsx.snap +++ b/packages/office-ui-fabric-react/src/components/Facepile/__snapshots__/Facepile.test.tsx.snap @@ -27,11 +27,22 @@ exports[`Facepile renders Facepile correctly 1`] = ` title="Annie Lindqvist" >
@@ -233,7 +297,16 @@ exports[`Facepile renders Facepile correctly 1`] = ` ms-Image ms-Persona-image { + border-radius: 50%; + border: 0px; + height: 32px; + left: 0px; + margin-right: 10px; overflow: hidden; + perspective: 1px; + position: absolute; + top: 0px; + width: 32px; } style={ Object { @@ -253,9 +326,9 @@ exports[`Facepile renders Facepile correctly 1`] = ` { display: block; height: auto; - left: 50%; + left: 50% /* @noflip */; opacity: 0; - position: relative; + position: absolute; top: 50%; transform: translate(-50%,-50%); width: 100%; diff --git a/packages/office-ui-fabric-react/src/components/Image/Image.styles.ts b/packages/office-ui-fabric-react/src/components/Image/Image.styles.ts index 6c7b4bb62b0a28..bff0a259dbe231 100644 --- a/packages/office-ui-fabric-react/src/components/Image/Image.styles.ts +++ b/packages/office-ui-fabric-react/src/components/Image/Image.styles.ts @@ -25,8 +25,8 @@ export const getStyles = ( } = props; const ImageFitStyles: IStyle = { - position: 'relative', - left: '50%', + position: 'absolute', + left: '50% /* @noflip */', top: '50%', transform: 'translate(-50%,-50%)' // @todo test RTL renders transform: translate(50%,-50%); }; @@ -44,6 +44,9 @@ export const getStyles = ( width: '100%' } ], + (isCenter || isContain || isCover) && { + position: 'relative', + }, className ], image: [ diff --git a/packages/office-ui-fabric-react/src/components/Persona/Persona.base.tsx b/packages/office-ui-fabric-react/src/components/Persona/Persona.base.tsx index d6b7ba1edc7ba8..b723aa4a152ac9 100644 --- a/packages/office-ui-fabric-react/src/components/Persona/Persona.base.tsx +++ b/packages/office-ui-fabric-react/src/components/Persona/Persona.base.tsx @@ -1,25 +1,30 @@ import * as React from 'react'; import { BaseComponent, - css, + classNamesFunction, + customizable, divProperties, getNativeProps, - IRenderFunction + IRenderFunction, } from '../../Utilities'; import { TooltipHost, TooltipOverflowMode, DirectionalHint } from '../../Tooltip'; import { PersonaCoin } from './PersonaCoin'; import { IPersonaProps, + IPersonaSharedProps, + IPersonaStyleProps, + IPersonaStyles, PersonaPresence as PersonaPresenceEnum, - PersonaSize + PersonaSize, } from './Persona.types'; -import { - PERSONA_PRESENCE, - PERSONA_SIZE -} from './PersonaConsts'; -import * as stylesImport from './Persona.scss'; -const styles: any = stylesImport; +const getClassNames = classNamesFunction(); + +/** + * Persona with no default styles. + * [Use the `getStyles` API to add your own styles.](https://github.com/OfficeDev/office-ui-fabric-react/wiki/Styling) + */ +@customizable('Persona', ['theme']) export class PersonaBase extends BaseComponent { public static defaultProps: IPersonaProps = { primaryText: '', @@ -35,67 +40,77 @@ export class PersonaBase extends BaseComponent { public render() { const { hidePersonaDetails, + onRenderOptionalText, onRenderPrimaryText, onRenderSecondaryText, onRenderTertiaryText, - onRenderOptionalText, } = this.props; const size = this.props.size as PersonaSize; // These properties are to be explicitly passed into PersonaCoin because they are the only props directly used const { + allowPhoneInitials, className, coinProps, coinSize, - imageUrl, + getStyles, imageAlt, imageInitials, + imageShouldFadeIn, + imageShouldStartVisible, + imageUrl, initialsColor, - allowPhoneInitials, + onPhotoLoadingStateChange, + onRenderCoin, presence, primaryText, - imageShouldFadeIn, - imageShouldStartVisible, showSecondaryText, - onPhotoLoadingStateChange, - onRenderCoin + theme, } = this.props; - const personaCoinProps = { + const personaCoinProps: IPersonaSharedProps = { + allowPhoneInitials, coinProps, coinSize, - imageUrl, imageAlt, imageInitials, + imageShouldFadeIn, + imageShouldStartVisible, + imageUrl, initialsColor, - allowPhoneInitials, + onPhotoLoadingStateChange, + onRenderCoin, presence, primaryText, - imageShouldFadeIn, - imageShouldStartVisible, size, - onPhotoLoadingStateChange, - onRenderCoin }; + const classNames = getClassNames(getStyles, { + theme: theme!, + className, + showSecondaryText, + presence, + size, + }); + const divProps = getNativeProps(this.props, divProperties); const personaDetails = ( -
+
{ this._renderElement( this.props.primaryText, - css('ms-Persona-primaryText', styles.primaryText), + classNames.primaryText, onRenderPrimaryText) } { this._renderElement( this.props.secondaryText, - css('ms-Persona-secondaryText', styles.secondaryText), + classNames.secondaryText, onRenderSecondaryText) } { this._renderElement( this.props.tertiaryText, - css('ms-Persona-tertiaryText', styles.tertiaryText), + classNames.tertiaryText, onRenderTertiaryText) } { this._renderElement( this.props.optionalText, - css('ms-Persona-optionalText', styles.optionalText), + classNames.optionalText, onRenderOptionalText) } { this.props.children }
@@ -104,15 +119,7 @@ export class PersonaBase extends BaseComponent { return (
diff --git a/packages/office-ui-fabric-react/src/components/Persona/Persona.scss b/packages/office-ui-fabric-react/src/components/Persona/Persona.scss deleted file mode 100644 index 24f15f0e191a93..00000000000000 --- a/packages/office-ui-fabric-react/src/components/Persona/Persona.scss +++ /dev/null @@ -1,687 +0,0 @@ -@import '../../common/common'; - -// Copyright (c) Microsoft. All rights reserved. Licensed under the MIT license. See LICENSE in the project root for license information. - -// -// Office UI Fabric -// -------------------------------------------------- -// Persona styles - -// Skype presence colors -$ms-color-presence-available: #7FBA00; -$ms-color-presence-away: #FCD116; -$ms-color-presence-busy: #E81123; -$ms-color-presence-dnd-background: #E81123; -$ms-color-presence-dnd-line: #FFFFFF; -$ms-color-presence-offline: #93ABBD; -$ms-color-presence-out-of-office: $ms-color-magenta; - -// Other presence colors -$ms-color-presence-blocked-background: #DEDEDE; -$ms-color-presence-blocked-line: #C72D25; -$ms-color-presence-busy-stripe-light: #E57A79; -$ms-color-presence-busy-stripe-dark: #D00E0D; -$ms-color-presence-busy-average: #D93B3B; - -// Persona Sizes -$ms-Persona-size10: 20px; -$ms-Persona-size16: 16px; -$ms-Persona-size24: 24px; -$ms-Persona-size28: 28px; -$ms-Persona-size32: 32px; -$ms-Persona-size40: 40px; -$ms-Persona-size48: 48px; -$ms-Persona-size72: 72px; -$ms-Persona-size100: 100px; - -// Presence Sizes -$ms-Persona-presenceSize6: 6px; -$ms-Persona-presenceSize8: 8px; -$ms-Persona-presenceSize12: 12px; -$ms-Persona-presenceSize20: 20px; -$ms-Persona-presenceSize28: 28px; -$ms-Persona-presenceBorder: 2px; - -.root { - @include ms-normalize; - color: $ms-color-neutralPrimary; - font-size: $ms-font-size-m; - font-weight: $ms-font-weight-regular; - position: relative; - height: $ms-Persona-size48; - display: flex; - align-items: center; - - .contextualHost { - display: none; - } - - &:hover { - .primaryText { - color: $ms-color-neutralDark; - } - } -} - -//= Note: The doughboy placeholder is being deprecated. -// The initials color block (.ms-Persona-initials) will be used going forward -// as a fallback when the persona does not have an image. -.placeholder { - color: $ms-color-white; - position: absolute; - right: 0; - left: 0; - font-size: 47px; - top: 9px; - z-index: $ms-zIndex-middle; -} - -.presence { - background-color: $ms-color-presence-available; - position: absolute; - height: $ms-Persona-presenceSize12; - width: $ms-Persona-presenceSize12; - border-radius: 50%; - top: auto; - @include ms-right(-$ms-Persona-presenceBorder); - bottom: -$ms-Persona-presenceBorder; - border: $ms-Persona-presenceBorder solid $ms-color-white; - text-align: center; - box-sizing: content-box; - // Setting -ms-high-contrast-adjust to none Overrides the default behaviors of high contrast more in the Edge browser. - -ms-high-contrast-adjust: none; - // This is a temporary local fix and should be removed once Fabric Core 6.0 is released. - - @include high-contrast { - border-color: Window; - color: Window; - background-color: WindowText; - } - - .presenceIcon { - color: $ms-color-white; - font-size: 6px; - line-height: $ms-Persona-presenceSize12; - vertical-align: top; - - @include high-contrast { - color: Window; - } - } -} - -.details { - @include padding(0, 24px, 0, 16px); - min-width: 0; - width: 100%; - @include text-align(left); - display: flex; - flex-direction: column; - justify-content: space-around; -} - -.primaryText, -.secondaryText, -.tertiaryText, -.optionalText, -.textContent { - @include ms-no-wrap(); -} - -.primaryText { - color: $ms-color-neutralPrimary; - font-weight: $ms-font-weight-regular; - font-size: $ms-font-size-l; -} - -.secondaryText, -.tertiaryText, -.optionalText { - color: $ms-color-neutralSecondary; - font-weight: $ms-font-weight-regular; - font-size: $ms-font-size-s; - white-space: nowrap; -} - -.tertiaryText, -.optionalText { - display: none; // Hidden on default persona -} - - -//== Modifier: Size 10 Persona -// -.root.rootIsSize10 { - height: $ms-Persona-size10; - min-width: $ms-Persona-size10; -} - -.rootIsSize10 { - - .presence { - @include ms-right(auto); - top: 7px; - @include ms-left(0); - border: 0; - height: $ms-Persona-presenceSize8; - width: $ms-Persona-presenceSize8; - - @include high-contrast { - top: 9px; - border: 1px solid WindowText; - } - - .presenceIcon { - display: none; - } - } - - .details { - @include ms-padding-left(17px); - } - - .primaryText { - font-size: $ms-font-size-s; - line-height: $ms-Persona-size10; - } - - .secondaryText { - display: none; - } -} - -//== Modifier: Size 10 Persona with read only state -// -// This variant includes a semicolon, and is -// most often presented within a People Picker. -.root.rootIsSize10.rootIsReadonly { - padding: 0; - background-color: transparent; - - .primaryText:after { - content: ';'; - } -} - -//== Modifier: Size 16 Persona -// -.root.rootIsSize16 { - height: $ms-Persona-size16; - min-width: $ms-Persona-size16; -} - -.rootIsSize16 { - - .placeholder { - font-size: 18px; - top: 4px; - } - - .presence { - height: $ms-Persona-presenceSize6; - width: $ms-Persona-presenceSize6; - border-width: 1.5px; - } - - .presenceIcon { - display: none; - } - - .primaryText { - font-size: $ms-font-size-m; - line-height: $ms-Persona-size28; - } - - .secondaryText { - display: none; - } -} - -//== Modifier: Size 24 Persona -// -.root.rootIsSize24 { - height: $ms-Persona-size24; - min-width: $ms-Persona-size24; -} - -.rootIsSize24 { - - .placeholder { - font-size: 18px; - top: 4px; - } - - .presence { - height: $ms-Persona-presenceSize8; - width: $ms-Persona-presenceSize8; - - &:after { - display: none; - } - } - - .presenceIcon { - display: none; - } - - .details { - padding: 0 12px; - } - - .primaryText { - font-size: $ms-font-size-m; - } - - .secondaryText { - display: none; - } - - &.showSecondaryText { - height: 36px; - - .primaryText, - .secondaryText { - display: block; - height: 18px; - line-height: 16px; - overflow-x: hidden; - } - } -} - -//== Modifier: Size 28 Persona -// -.root.rootIsSize28 { - height: $ms-Persona-size28; - min-width: $ms-Persona-size28; -} - -.rootIsSize28 { - - .placeholder { - font-size: 18px; - top: 4px; - } - - .presence { - height: $ms-Persona-presenceSize8; - width: $ms-Persona-presenceSize8; - - &:after { - display: none; - } - } - - .presenceIcon { - display: none; - } - - .details { - padding: 0 12px; - } - - .primaryText { - font-size: $ms-font-size-m; - } - - .secondaryText { - display: none; - } - - &.showSecondaryText { - height: 32px; - - .primaryText, - .secondaryText { - display: block; - line-height: 16px; - height: 16px; - overflow-x: hidden; - } - } -} - - -//== Modifier: Size 32 Persona -// -.root.rootIsSize32 { - height: $ms-Persona-size32; - min-width: $ms-Persona-size32; -} - -.rootIsSize32 { - - .placeholder { - font-size: 28px; - top: 6px; - } - - .presence { - height: $ms-Persona-presenceSize8; - width: $ms-Persona-presenceSize8; - } - - .presenceIcon { - display: none; - } - - .primaryText { - font-size: $ms-font-size-m; - } - - .secondaryText { - display: none; - } - - &.showSecondaryText { - .primaryText, - .secondaryText { - display: block; - height: 16px; - line-height: 16px; - overflow-x: hidden; - } - } -} - - -//== Modifier: Size 40 Persona -// -.root.rootIsSize40 { - height: $ms-Persona-size40; - min-width: $ms-Persona-size40; -} - -.rootIsSize40 { - - .placeholder { - font-size: 38px; - top: 5px; - } - - .primaryText { - font-size: $ms-font-size-m; - } -} - - -//== Modifier: Size 72 Persona -// -.root.rootIsSize72 { - height: $ms-Persona-size72; - min-width: $ms-Persona-size72; -} - -.rootIsSize72 { - - .placeholder { - font-size: 67px; - top: 10px; - } - - .presence { - height: $ms-Persona-presenceSize20; - width: $ms-Persona-presenceSize20; - } - - .presenceIcon { - line-height: $ms-Persona-presenceSize20; - font-size: $ms-font-size-s; - } - - .primaryText { - font-size: $ms-font-size-xl; - } - - .secondaryText { - font-size: $ms-font-size-m; - } - - .tertiaryText { - display: block; - } -} - - -//== Modifier: Size 100 Persona -// -.root.rootIsSize100 { - height: $ms-Persona-size100; - min-width: $ms-Persona-size100; -} - -.rootIsSize100 { - - .placeholder { - font-size: 95px; - top: 12px; - } - - .presence { - height: $ms-Persona-presenceSize28; - width: $ms-Persona-presenceSize28; - } - - .presenceIcon { - line-height: $ms-Persona-presenceSize28; - font-size: $ms-font-size-m; - } - - .primaryText { - font-size: $ms-font-size-xl; - font-weight: $ms-font-weight-semilight; - } - - .secondaryText { - font-size: $ms-font-size-m; - } - - .tertiaryText, - .optionalText { - display: block; // Show tertiary and optional text - } -} - - -//== Modifier: Persona with darker text -// -// Note: Typically applied when the component has a colored background. -.root.rootIsDarkText { - .primaryText { - color: $ms-color-neutralDark; - } - - .secondaryText, - .tertiaryText, - .optionalText { - color: $ms-color-neutralPrimary; - } -} - - -//== Modifier: Selectable Persona -// -.root.rootIsSelectable { - cursor: pointer; - padding: 0 10px; - - &:not(.rootExtraLarge) { - &:hover, - &:focus { - background-color: $ms-color-themeLighter; - outline: 1px solid transparent; - } - } -} - - -//== Presence indicator variants. - -//== Modifier: Persona with available presence -// -.root.rootIsAvailable { - .presence { - background-color: $ms-color-presence-available; - - @include high-contrast { - background-color: WindowText; - } - - @media screen and (-ms-high-contrast: black-on-white) { - background-color: $ms-color-contrastWhiteDisabled; - } - } -} - - -//== Modifier: Persona with away presence -// -.root.rootIsAway { - .presence { - background-color: $ms-color-presence-away; - - @include high-contrast { - background-color: WindowText; - } - } - - .presenceIcon { - position: relative; - @include ms-left(1px); - } -} - - -//== Modifier: Persona with blocked presence -// -.root.rootIsBlocked { - .presence { - background-color: $ms-color-white; - - &::before { - content: ''; - width: 100%; - height: 100%; - position: absolute; - top: 0; - @include ms-left(0); - box-shadow: 0 0 0 2px $ms-color-presence-busy-average inset; - border-radius: 50%; - } - - &::after { - content: ''; - width: 100%; - height: 2px; - background-color: $ms-color-presence-busy-average; - transform: rotate(-45deg); - position: absolute; - // Using 40% as the top position of the cross bar element causes it to render in the correct position, even on very small persona sizes. - top: 40%; - @include ms-left(0); - } - - @include high-contrast { - color: $ms-color-contrastBlackDisabled; - background-color: Window; - - &::before { - box-shadow: 0 0 0 2px $ms-color-contrastBlackDisabled inset; - } - - &::after { - background-color: $ms-color-contrastBlackDisabled; - } - } - - @media screen and (-ms-high-contrast: black-on-white) { - color: $ms-color-contrastWhiteDisabled; - - &::before { - box-shadow: 0 0 0 2px $ms-color-contrastWhiteDisabled inset; - } - - &::after { - background-color: $ms-color-contrastWhiteDisabled; - } - } - } - - &.rootIsSize72 { - .presence { - &::after { - top: 9px; - } - } - } - - &.rootIsSize100 { - .presence { - &::after { - top: 13px; - } - } - } -} - - -//== Modifier: Persona with busy presence -// -.root.rootIsBusy { - .presence { - background-color: $ms-color-presence-busy-average; - - @include high-contrast { - background-color: $ms-color-contrastBlackDisabled; - } - - @media screen and (-ms-high-contrast: black-on-white) { - background-color: $ms-color-contrastWhiteDisabled; - } - } -} - - -//== Modifier: Persona with do not disturb presence -// -.root.rootIsDoNotDisturb { - .presence { - background-color: $ms-color-presence-dnd-background; - - @include high-contrast { - color: $ms-color-black; - background-color: $ms-color-contrastBlackDisabled; - - &::before { - background-color: $ms-color-contrastBlackDisabled; - } - - &::after { - background-color: $ms-color-contrastBlackDisabled; - } - } - - @media screen and (-ms-high-contrast: black-on-white) { - background-color: $ms-color-contrastWhiteDisabled; - } - } -} - - -//== Modifier: Persona with offline presence -// -.root.rootIsOffline { - .presence { - background-color: $ms-color-presence-offline; - - @include high-contrast { - background-color: $ms-color-contrastBlackDisabled; - box-shadow: 0 0 0 1px $ms-color-white inset; - } - - @media screen and (-ms-high-contrast: black-on-white) { - background-color: $ms-color-white; - box-shadow: 0 0 0 1px $ms-color-black inset; - } - } -} \ No newline at end of file diff --git a/packages/office-ui-fabric-react/src/components/Persona/Persona.styles.ts b/packages/office-ui-fabric-react/src/components/Persona/Persona.styles.ts index 61b84a0b2a2b5a..afcdcf15bdc545 100644 --- a/packages/office-ui-fabric-react/src/components/Persona/Persona.styles.ts +++ b/packages/office-ui-fabric-react/src/components/Persona/Persona.styles.ts @@ -1,16 +1,274 @@ -import { IPersonaStyleProps, IPersonaStyles } from './Persona.types'; +import { + IPersonaStyleProps, + IPersonaStyles, + PersonaPresence, + PersonaSize +} from './Persona.types'; +import { + FontSizes, + FontWeights, + IStyle, + normalize, + noWrap, +} from '../../Styling'; +import { + personaSize, + presenceBoolean, + sizeBoolean, +} from './PersonaConsts'; export const getStyles = ( props: IPersonaStyleProps ): IPersonaStyles => { + const { + className, + showSecondaryText, + theme, + } = props; + + const { palette } = theme; + + const size = sizeBoolean(props.size as PersonaSize); + const presence = presenceBoolean(props.presence as PersonaPresence); + + const showSecondaryTextDefaultHeight = '16px'; + + const sharedTextStyles: IStyle = { + color: palette.neutralSecondary, + fontWeight: FontWeights.regular, + fontSize: FontSizes.small, + }; + return ({ root: [ 'ms-Persona', + normalize, { - // Insert css properties + color: palette.neutralPrimary, + fontSize: FontSizes.medium, + fontWeight: FontWeights.regular, + position: 'relative', + height: personaSize.size48, + minWidth: personaSize.size48, + display: 'flex', + alignItems: 'center', + + selectors: { + '.contextualHost': { + display: 'none', + }, + + ':hover': { + selectors: { + '$primaryText': { + color: palette.neutralDark, + } + } + } + } + }, + + size.isSize10 && [ + 'ms-Persona--size10', + { + height: personaSize.size10, + minWidth: personaSize.size10, + } + ], + + size.isSize16 && [ + 'ms-Persona--size16', + { + height: personaSize.size16, + minWidth: personaSize.size16, + } + ], + + size.isSize24 && [ + 'ms-Persona--size24', + { + height: personaSize.size24, + minWidth: personaSize.size24, + } + ], + + size.isSize24 && showSecondaryText && { + height: '36px', + }, + + size.isSize28 && [ + 'ms-Persona--size28', + { + height: personaSize.size28, + minWidth: personaSize.size28, + } + ], + + size.isSize28 && showSecondaryText && { + height: '32px', + }, + + size.isSize32 && [ + 'ms-Persona--size32', + { + height: personaSize.size32, + minWidth: personaSize.size32, + } + ], + + size.isSize40 && [ + 'ms-Persona--size40', + { + height: personaSize.size40, + minWidth: personaSize.size40, + } + ], + + size.isSize48 && 'ms-Persona--size48', + + size.isSize72 && [ + 'ms-Persona--size72', + { + height: personaSize.size72, + minWidth: personaSize.size72, + } + ], + + size.isSize100 && [ + 'ms-Persona--size100', + { + height: personaSize.size100, + minWidth: personaSize.size100, + } + ], + + /** + * Modifiers: presence + */ + presence.isAvailable && 'ms-Persona--online', + presence.isAway && 'ms-Persona--away', + presence.isBlocked && 'ms-Persona--blocked', + presence.isBusy && 'ms-Persona--busy', + presence.isDoNotDisturb && 'ms-Persona--donotdisturb', + presence.isOffline && 'ms-Persona--offline', + className, + ], + + details: [ + 'ms-Persona-details', + { + padding: '0 24px 0 16px', + minWidth: 0, + width: '100%', + textAlign: 'left', + display: 'flex', + flexDirection: 'column', + justifyContent: 'space-around', + }, + + size.isSize10 && { + paddingLeft: '17px', + }, + + (size.isSize24 || size.isSize28) && { + padding: '0 12px', } ], - // Insert className styles + primaryText: [ + 'ms-Persona-primaryText', + noWrap, + { + color: palette.neutralPrimary, + fontWeight: FontWeights.regular, + fontSize: FontSizes.large, + }, + + showSecondaryText && { + height: showSecondaryTextDefaultHeight, + lineHeight: showSecondaryTextDefaultHeight, + overflowX: 'hidden', + }, + + size.isSize10 && { + fontSize: FontSizes.small, + lineHeight: personaSize.size10, + }, + + (size.isSize16 || size.isSize24 || size.isSize28 || size.isSize32 || size.isSize40) && { + fontSize: FontSizes.medium, + }, + + size.isSize16 && { + lineHeight: personaSize.size28, + }, + + size.isSize24 && showSecondaryText && { height: '18px' }, + + size.isSize72 && { + fontSize: FontSizes.xLarge, + }, + + size.isSize100 && { + fontSize: FontSizes.xLarge, + fontWeight: FontWeights.semilight, + } + ], + + secondaryText: [ + 'ms-Persona-secondaryText', + noWrap, + sharedTextStyles, + + (size.isSize10 || size.isSize16 || size.isSize24 || size.isSize28 || size.isSize32) && { + display: 'none', + }, + + size.isSize24 && showSecondaryText && { + height: '18px', + }, + + (size.isSize72 || size.isSize100) && { + fontSize: FontSizes.medium, + }, + + showSecondaryText && { + display: 'block', + height: showSecondaryTextDefaultHeight, + lineHeight: showSecondaryTextDefaultHeight, + overflowX: 'hidden', + } + ], + + tertiaryText: [ + 'ms-Persona-tertiaryText', + noWrap, + sharedTextStyles, + { + display: 'none', + }, + + (size.isSize72 || size.isSize100) && { + display: 'block', + } + ], + + optionalText: [ + 'ms-Persona-optionalText', + noWrap, + sharedTextStyles, + { + display: 'none', + }, + + size.isSize100 && { + display: 'block', + } + ], + + textContent: [ + 'ms-Persona-textContent', + noWrap + ], }); -}; \ No newline at end of file +}; diff --git a/packages/office-ui-fabric-react/src/components/Persona/Persona.tsx b/packages/office-ui-fabric-react/src/components/Persona/Persona.tsx index 89d1f91649fc89..c5a680f33d3611 100644 --- a/packages/office-ui-fabric-react/src/components/Persona/Persona.tsx +++ b/packages/office-ui-fabric-react/src/components/Persona/Persona.tsx @@ -7,6 +7,10 @@ import { import { PersonaBase } from './Persona.base'; import { getStyles } from './Persona.styles'; +/** + * Personas are used for rendering an individual's avatar, presence and details. + * They are used within the PeoplePicker components. + */ export const Persona = styled( PersonaBase, getStyles diff --git a/packages/office-ui-fabric-react/src/components/Persona/Persona.types.ts b/packages/office-ui-fabric-react/src/components/Persona/Persona.types.ts index 5bab4a4d845037..c9baa333da78e3 100644 --- a/packages/office-ui-fabric-react/src/components/Persona/Persona.types.ts +++ b/packages/office-ui-fabric-react/src/components/Persona/Persona.types.ts @@ -1,6 +1,8 @@ import * as React from 'react'; import { IRenderFunction } from '../../Utilities'; import { PersonaBase } from './Persona.base'; +import { PersonaCoinBase } from './PersonaCoin'; +import { PersonaPresenceBase } from './PersonaPresence'; import { ImageLoadState } from '../../Image'; import { IStyle, ITheme } from '../../Styling'; import { IStyleFunction } from '../../Utilities'; @@ -9,23 +11,12 @@ export interface IPersona { } -export interface IPersonaProps extends React.HTMLAttributes { - /** - * Optional callback to access the IPersona interface. Use this instead of ref for accessing - * the public methods and properties of the component. - */ - componentRef?: (component: IPersona | null) => void; - +export interface IPersonaSharedProps extends React.HTMLAttributes { /** * Primary text to display, usually the name of the person. */ primaryText?: string; - /** - * Optional custom renderer for the primary text. - */ - onRenderPrimaryText?: IRenderFunction; - /** * Decides the size of the control. * @defaultvalue PersonaSize.size48 @@ -35,7 +26,7 @@ export interface IPersonaProps extends React.HTMLAttributes { /** * Optional custom renderer for the coin */ - onRenderCoin?: IRenderFunction; + onRenderCoin?: IRenderFunction; /** * If true, adds the css class 'is-fadeIn' to the image. @@ -75,7 +66,7 @@ export interface IPersonaProps extends React.HTMLAttributes { /** * Optional custom renderer for the initials */ - onRenderInitials?: IRenderFunction; + onRenderInitials?: IRenderFunction; /** * Optional callback for when loading state of the photo changes @@ -100,64 +91,206 @@ export interface IPersonaProps extends React.HTMLAttributes { secondaryText?: string; /** - * Optional custom renderer for the secondary text. + * Tertiary text to display, usually the status of the user. */ - onRenderSecondaryText?: IRenderFunction; + tertiaryText?: string; /** - * Tertiary text to display, usually the status of the user. + * Optional text to display, usually a custom message set. */ - tertiaryText?: string; + optionalText?: string; /** - * Optional custom renderer for the tertiary text. + * Whether to not render persona details, and just render the persona image/initials. */ - onRenderTertiaryText?: IRenderFunction; + hidePersonaDetails?: boolean; + + /* + * If true, show the secondary text line regardless of the size of the persona + */ + showSecondaryText?: boolean; /** - * Optional text to display, usually a custom message set. + * Optional custom persona coin size in pixel. */ - optionalText?: string; + coinSize?: number; + + /** + * Optional HTML element props for Persona coin. + */ + coinProps?: React.HTMLAttributes; + + /** + * Theme provided by High-Order Component. + */ + theme?: ITheme; +} + +export interface IPersonaProps extends IPersonaSharedProps { + /** + * Optional callback to access the IPersona interface. Use this instead of ref for accessing + * the public methods and properties of the component. + */ + componentRef?: (component: IPersona | null) => void; + + /** + * Additional CSS class(es) to apply to the Persona + */ + className?: string; + + /** + * Call to provide customized styling that will layer on top of variant rules + */ + getStyles?: IStyleFunction; + + /** + * Optional custom renderer for the primary text. + */ + onRenderPrimaryText?: IRenderFunction; + + /** + * Optional custom renderer for the secondary text. + */ + onRenderSecondaryText?: IRenderFunction; + + /** + * Optional custom renderer for the tertiary text. + */ + onRenderTertiaryText?: IRenderFunction; /** * Optional custom renderer for the optional text. */ onRenderOptionalText?: IRenderFunction; +} +export interface IPersonaStyleProps { /** - * Whether to not render persona details, and just render the persona image/initials. + * Theme provided by High-Order Component. */ - hidePersonaDetails?: boolean; + theme: ITheme; /** - * Additional CSS class(es) to apply to the Persona + * Custom class name. */ className?: string; + /** + * Optional custom persona coin size in pixel. + */ + coinSize?: number; + + /** + * Decides the size of the control. + * @defaultvalue PersonaSize.size48 + */ + size?: PersonaSize; + + /** + * Presence of the person to display - will not display presence if undefined. + * @defaultvalue PersonaPresence.none + */ + presence?: PersonaPresence; + /* * If true, show the secondary text line regardless of the size of the persona */ showSecondaryText?: boolean; +} + +export interface IPersonaStyles { + root: IStyle; + details: IStyle; + primaryText: IStyle; + secondaryText: IStyle; + tertiaryText: IStyle; + optionalText: IStyle; + textContent: IStyle; +} +export interface IPersonaCoinProps extends IPersonaSharedProps { /** - * Optional custom persona coin size in pixel. + * Gets the component ref. */ - coinSize?: number; + componentRef?: (component: IPersonaCoinProps) => void; /** - * Optional HTML element props for Persona coin. + * Call to provide customized styling that will layer on top of the variant rules */ - coinProps?: React.HTMLAttributes; + getStyles?: IStyleFunction; /** - * Call to provide customized styling that will layer on top of variant rules + * Additional css class to apply to the PersonaCoin + * @defaultvalue undefined */ - getStyles?: IStyleFunction; + className?: string; +} +export interface IPersonaCoinStyleProps { /** - * Theme provided by HOC. + * Theme provided by High-Order Component. */ - theme?: ITheme; + theme: ITheme; + + /** + * Custom class name. + */ + className?: string; + + /** + * Decides the size of the control. + * @defaultvalue PersonaSize.size48 + */ + size?: PersonaSize; +} + +export interface IPersonaCoinStyles { + coin: IStyle; + imageArea: IStyle; + image: IStyle; + initials: IStyle; + size10WithoutPresenceIcon: IStyle; +} + +export interface IPersonaPresenceProps extends IPersonaSharedProps { + /** + * Gets the component ref. + */ + componentRef?: (component: IPersonaPresenceProps) => void; + + /** + * Call to provide customized styling that will layer on top of the variant rules + */ + getStyles?: IStyleFunction; +} + +export interface IPersonaPresenceStyleProps { + /** + * Theme provided by High-Order Component. + */ + theme: ITheme; + + /** + * Custom class name. + */ + className?: string; + + /** + * Presence of the person to display - will not display presence if undefined. + * @defaultvalue PersonaPresence.none + */ + presence?: PersonaPresence; + + /** + * Decides the size of the control. + * @defaultvalue PersonaSize.size48 + */ + size?: PersonaSize; +} + +export interface IPersonaPresenceStyles { + presence: IStyle; + presenceIcon: IStyle; } export enum PersonaSize { @@ -243,19 +376,3 @@ export enum PersonaInitialsColor { */ transparent = 15, } - -export interface IPersonaStyleProps { - /** - * Theme. - */ - theme: ITheme; - - /** - * Custom class name. - */ - className?: string; -} - -export interface IPersonaStyles { - root: IStyle; -} diff --git a/packages/office-ui-fabric-react/src/components/Persona/PersonaCoin.scss b/packages/office-ui-fabric-react/src/components/Persona/PersonaCoin.scss deleted file mode 100644 index 132c5af27cec1c..00000000000000 --- a/packages/office-ui-fabric-react/src/components/Persona/PersonaCoin.scss +++ /dev/null @@ -1,207 +0,0 @@ -@import '../../common/common'; - -// Copyright (c) Microsoft. All rights reserved. Licensed under the MIT license. See LICENSE in the project root for license information. - -// -// Office UI Fabric -// -------------------------------------------------- -// Persona Coin styles - -// Persona Sizes -$ms-Persona-size10: 20px; -$ms-Persona-size16: 16px; -$ms-Persona-size24: 24px; -$ms-Persona-size28: 28px; -$ms-Persona-size32: 32px; -$ms-Persona-size40: 40px; -$ms-Persona-size48: 48px; -$ms-Persona-size72: 72px; -$ms-Persona-size100: 100px; - -.imageArea { - position: relative; - text-align: center; - flex: 0 0 auto; - height: $ms-Persona-size48; - width: $ms-Persona-size48; - - .image { - border: 0px; - } -} - -.initials { - border-radius: 50%; - color: $ms-color-white; - font-size: $ms-font-size-l; - font-weight: $ms-font-weight-regular; - line-height: 46px; - height: $ms-Persona-size48; - - @include high-contrast { - border: 1px solid WindowText; - -ms-high-contrast-adjust: none; - color: WindowText; - box-sizing: border-box; - // Using important to override all of the more specific initialsIsColor rules - background-color: Window !important; - } -} - -.image { - @include ms-margin-right(10px); - position: absolute; - top: 0; - @include ms-left(0); - width: 100%; - height: 100%; - border-radius: 50%; - perspective: 1px; -} - -.image[src=""] { - display: none; -} - -//== Modifier: Size 10 Persona - -.rootIsSize10 { - .imageArea { - overflow: visible; - background: transparent; - height: 0; - width: 0; - } - -} - -.size10NoPresenceIcon { - font-size: 10px; - position: absolute; - top: 5px; - @include ms-right(auto); - @include ms-left(0); -} - -//== Modifier: Size 16 Persona - -.rootIsSize16 { - .imageArea, - .image { - height: $ms-Persona-size16; - width: $ms-Persona-size16; - } - - .initials { - font-size: $ms-font-size-xs; - height: $ms-Persona-size16; - line-height: $ms-Persona-size16; - } - -} - -//== Modifier: Size 24 Persona - -.rootIsSize24 { - .imageArea, - .image { - height: $ms-Persona-size24; - width: $ms-Persona-size24; - } - - .initials { - font-size: $ms-font-size-xs; - height: $ms-Persona-size24; - line-height: $ms-Persona-size24; - } - -} - -//== Modifier: Size 28 Persona - -.rootIsSize28 { - .imageArea, - .image { - height: $ms-Persona-size28; - width: $ms-Persona-size28; - } - - .initials { - font-size: $ms-font-size-xs; - height: $ms-Persona-size28; - line-height: $ms-Persona-size28; - } - -} - - -//== Modifier: Size 32 Persona - -.rootIsSize32 { - .imageArea, - .image { - height: $ms-Persona-size32; - width: $ms-Persona-size32; - } - - .initials { - font-size: $ms-font-size-m; - height: $ms-Persona-size32; - line-height: $ms-Persona-size32; - } - -} - - -//== Modifier: Size 40 Persona - -.rootIsSize40 { - .imageArea, - .image { - height: $ms-Persona-size40; - width: $ms-Persona-size40; - } - - .initials { - font-size: $ms-font-size-m; - height: $ms-Persona-size40; - line-height: $ms-Persona-size40; - } - -} - - -//== Modifier: Size 72 Persona - -.rootIsSize72 { - .imageArea, - .image { - height: $ms-Persona-size72; - width: $ms-Persona-size72; - } - - .initials { - font-size: $ms-font-size-xxl; - height: $ms-Persona-size72; - line-height: 70px; - } - -} - - -//== Modifier: Size 100 Persona - -.rootIsSize100 { - .imageArea, - .image { - height: $ms-Persona-size100; - width: $ms-Persona-size100; - } - - .initials { - font-size: $ms-font-size-su; - height: $ms-Persona-size100; - line-height: 96px; - } - -} diff --git a/packages/office-ui-fabric-react/src/components/Persona/PersonaCoin.styles.ts b/packages/office-ui-fabric-react/src/components/Persona/PersonaCoin.styles.ts new file mode 100644 index 00000000000000..3765b3b6e8ee2d --- /dev/null +++ b/packages/office-ui-fabric-react/src/components/Persona/PersonaCoin.styles.ts @@ -0,0 +1,228 @@ +import { + IPersonaCoinStyleProps, + IPersonaCoinStyles, + PersonaSize, +} from './Persona.types'; +import { + HighContrastSelector, + FontSizes, + FontWeights, +} from '../../Styling'; +import { + personaSize, + sizeBoolean, +} from './PersonaConsts'; + +export const getStyles = ( + props: IPersonaCoinStyleProps +): IPersonaCoinStyles => { + const { + className, + theme, + } = props; + + const { palette } = theme; + + const size = sizeBoolean(props.size as PersonaSize); + + return ({ + coin: [ + 'ms-Persona-coin', + size.isSize10 && 'ms-Persona--size10', + size.isSize16 && 'ms-Persona--size16', + size.isSize24 && 'ms-Persona--size24', + size.isSize28 && 'ms-Persona--size28', + size.isSize32 && 'ms-Persona--size32', + size.isSize40 && 'ms-Persona--size40', + size.isSize48 && 'ms-Persona--size48', + size.isSize72 && 'ms-Persona--size72', + size.isSize100 && 'ms-Persona--size100', + className + ], + + size10WithoutPresenceIcon: { + fontSize: '10px', + position: 'absolute', + top: '5px', + right: 'auto', + left: 0, + }, + + imageArea: [ + 'ms-Persona-imageArea', + { + position: 'relative', + textAlign: 'center', + flex: '0 0 auto', + height: personaSize.size48, + width: personaSize.size48, + }, + + size.isSize10 && { + overflow: 'visible', + background: 'transparent', + height: 0, + width: 0, + }, + + size.isSize16 && { + height: personaSize.size16, + width: personaSize.size16, + }, + + size.isSize24 && { + height: personaSize.size24, + width: personaSize.size24, + }, + + size.isSize28 && { + height: personaSize.size28, + width: personaSize.size28, + }, + + size.isSize32 && { + height: personaSize.size32, + width: personaSize.size32, + }, + + size.isSize40 && { + height: personaSize.size40, + width: personaSize.size40, + }, + + size.isSize72 && { + height: personaSize.size72, + width: personaSize.size72, + }, + + size.isSize100 && { + height: personaSize.size100, + width: personaSize.size100, + }, + ], + + image: [ + 'ms-Persona-image', + { + marginRight: '10px', + position: 'absolute', + top: 0, + left: 0, + width: '100%', + height: '100%', + border: 0, + borderRadius: '50%', + perspective: '1px', + }, + + size.isSize10 && { + overflow: 'visible', + background: 'transparent', + height: 0, + width: 0, + }, + + size.isSize16 && { + height: personaSize.size16, + width: personaSize.size16, + }, + + size.isSize24 && { + height: personaSize.size24, + width: personaSize.size24, + }, + + size.isSize28 && { + height: personaSize.size28, + width: personaSize.size28, + }, + + size.isSize32 && { + height: personaSize.size32, + width: personaSize.size32, + }, + + size.isSize40 && { + height: personaSize.size40, + width: personaSize.size40, + }, + + size.isSize72 && { + height: personaSize.size72, + width: personaSize.size72, + }, + + size.isSize100 && { + height: personaSize.size100, + width: personaSize.size100, + }, + ], + + initials: [ + 'ms-Persona-initials', + { + borderRadius: '50%', + color: palette.white, + fontSize: FontSizes.large, + fontWeight: FontWeights.regular, + lineHeight: '46px', + height: personaSize.size48, + + selectors: { + [HighContrastSelector]: { + border: '1px solid WindowText', + MsHighContrastAdjust: 'none', + color: 'WindowText', + boxSizing: 'border-box', + backgroundColor: 'Window !important', + } + } + }, + + (size.isSize16 || size.isSize24 || size.isSize28) && { + fontSize: FontSizes.xSmall, + }, + + size.isSize16 && { + height: personaSize.size16, + lineHeight: personaSize.size16, + }, + + size.isSize24 && { + height: personaSize.size24, + lineHeight: personaSize.size24, + }, + + size.isSize28 && { + height: personaSize.size28, + lineHeight: personaSize.size28, + }, + + (size.isSize32 || size.isSize40) && { + fontSize: FontSizes.medium, + }, + + size.isSize32 && { + height: personaSize.size32, + lineHeight: personaSize.size32, + }, + + size.isSize40 && { + height: personaSize.size40, + lineHeight: personaSize.size40, + }, + + size.isSize72 && { + fontSize: FontSizes.xxLarge, + height: personaSize.size72, + lineHeight: personaSize.size72, + }, + + size.isSize100 && { + fontSize: FontSizes.superLarge, + height: personaSize.size100, + lineHeight: personaSize.size100, + } + ] + }); +}; diff --git a/packages/office-ui-fabric-react/src/components/Persona/PersonaCoin.tsx b/packages/office-ui-fabric-react/src/components/Persona/PersonaCoin.tsx index 62f2479832ac52..5da9f432237f9d 100644 --- a/packages/office-ui-fabric-react/src/components/Persona/PersonaCoin.tsx +++ b/packages/office-ui-fabric-react/src/components/Persona/PersonaCoin.tsx @@ -1,23 +1,36 @@ import * as React from 'react'; import { - css, + BaseComponent, + classNamesFunction, + customizable, divProperties, getInitials, getNativeProps, - getRTL + getRTL, + styled, } from '../../Utilities'; -import { Image, ImageFit, ImageLoadState } from '../../Image'; +import { mergeStyles } from '../../Styling'; import { PersonaPresence } from './PersonaPresence'; -import { - IPersonaProps, - PersonaPresence as PersonaPresenceEnum, - PersonaSize -} from './Persona.types'; import { Icon } from '../../Icon'; -import * as stylesImport from './PersonaCoin.scss'; -const styles: any = stylesImport; +import { + Image, + ImageFit, + ImageLoadState +} from '../../Image'; +import { + IPersonaCoinProps, + IPersonaCoinStyleProps, + IPersonaCoinStyles, + IPersonaPresenceProps, + PersonaPresence as PersonaPresenceEnum, + PersonaSize, +} from './Persona.types'; +import { initialsColorPropToColorCode } from './PersonaInitialsColor'; +import { getStyles } from './PersonaCoin.styles'; + +const getClassNames = classNamesFunction(); const SIZE_TO_PIXELS: { [key: number]: number } = { [PersonaSize.tiny]: 20, @@ -37,44 +50,26 @@ const SIZE_TO_PIXELS: { [key: number]: number } = { [PersonaSize.size72]: 72, [PersonaSize.size100]: 100 }; -import { mergeStyles } from '../../Styling'; -import { initialsColorPropToColorCode } from './PersonaInitialsColor'; - -export const PERSONACOIN_SIZE: { [key: number]: string } = { - // All non-numerically named sizes are deprecated, use the numerically named classes below - [PersonaSize.tiny]: 'ms-Persona--tiny ' + styles.rootIsSize10, - [PersonaSize.extraExtraSmall]: 'ms-Persona--xxs ' + styles.rootIsSize24, - [PersonaSize.extraSmall]: 'ms-Persona--xs ' + styles.rootIsSize28, - [PersonaSize.small]: 'ms-Persona--sm ' + styles.rootIsSize40, - [PersonaSize.regular]: '', - [PersonaSize.large]: 'ms-Persona--lg ' + styles.rootIsSize72, - [PersonaSize.extraLarge]: 'ms-Persona--xl ' + styles.rootIsSize100, - - [PersonaSize.size10]: 'ms-Persona--size10 ' + styles.rootIsSize10, - [PersonaSize.size16]: 'ms-Persona--size16 ' + styles.rootIsSize16, - [PersonaSize.size24]: 'ms-Persona--size24 ' + styles.rootIsSize24, - [PersonaSize.size28]: 'ms-Persona--size28 ' + styles.rootIsSize28, - [PersonaSize.size32]: 'ms-Persona--size32 ' + styles.rootIsSize32, - [PersonaSize.size40]: 'ms-Persona--size40 ' + styles.rootIsSize40, - [PersonaSize.size48]: 'ms-Persona--size48 ' + styles.rootIsSize48, - [PersonaSize.size72]: 'ms-Persona--size72 ' + styles.rootIsSize72, - [PersonaSize.size100]: 'ms-Persona--size100 ' + styles.rootIsSize100 -}; export interface IPersonaState { isImageLoaded?: boolean; isImageError?: boolean; } -export class PersonaCoin extends React.Component { - public static defaultProps: IPersonaProps = { +/** + * PersonaCoin with no default styles. + * [Use the `getStyles` API to add your own styles.](https://github.com/OfficeDev/office-ui-fabric-react/wiki/Styling) + */ +@customizable('PersonaCoin', ['theme']) +export class PersonaCoinBase extends BaseComponent { + public static defaultProps: IPersonaCoinProps = { primaryText: '', size: PersonaSize.size48, presence: PersonaPresenceEnum.none, - imageAlt: '' + imageAlt: '', }; - constructor(props: IPersonaProps) { + constructor(props: IPersonaCoinProps) { super(props); this.state = { @@ -85,80 +80,106 @@ export class PersonaCoin extends React.Component { public render(): JSX.Element | null { const { + className, coinProps, coinSize, + getStyles: getStylesProp, // Use getStyles from props. imageUrl, onRenderCoin = this._onRenderCoin, onRenderInitials = this._onRenderInitials, + presence, + theme, } = this.props; const size = this.props.size as PersonaSize; const divProps = getNativeProps(this.props, divProperties); const coinSizeStyle = coinSize ? { width: coinSize, height: coinSize } : undefined; + const personaPresenceProps: IPersonaPresenceProps = { + coinSize, + presence, + size, + theme, + }; + + // Use getStyles from props, or fall back to getStyles from styles file. + const classNames = getClassNames(getStylesProp, { + theme: theme!, + className: (coinProps && coinProps.className) ? coinProps.className : className, + size, + }); + return (
- { (size !== PersonaSize.size10 && size !== PersonaSize.tiny) ? ( -
- { - !this.state.isImageLoaded && - (!imageUrl || this.state.isImageError) && - ( - - ) - } - { onRenderCoin(this.props, this._onRenderCoin) } - -
- ) : - (this.props.presence ? - : - - ) + + { // Render PersonaCoin if size is not size10 + (size !== PersonaSize.size10 && size !== PersonaSize.tiny) + ? ( +
+ { + !this.state.isImageLoaded && + (!imageUrl || this.state.isImageError) && + ( + + ) + } + { onRenderCoin(this.props, this._onRenderCoin) } + +
+ ) : ( // Otherwise, render just PersonaPresence. + this.props.presence + ? + : // Just render Contact Icon if there isn't a Presence prop. + + ) } { this.props.children }
); } - private _onRenderCoin = (props: IPersonaProps): JSX.Element | null => { + private _onRenderCoin = (props: IPersonaCoinProps): JSX.Element | null => { const { coinSize, + getStyles: getStylesProp, imageUrl, imageAlt, imageShouldFadeIn, - imageShouldStartVisible + imageShouldStartVisible, + theme, } = this.props; const size = this.props.size as PersonaSize; + const classNames = getClassNames(getStylesProp, { + theme: theme!, + size + }); + return ( { ); } - private _onRenderInitials = (props: IPersonaProps): JSX.Element => { + private _onRenderInitials = (props: IPersonaCoinProps): JSX.Element => { let { imageInitials } = props; - const { primaryText } = props; - const { allowPhoneInitials } = props; + const { + allowPhoneInitials, + primaryText, + } = props; const isRTL = getRTL(); imageInitials = imageInitials || getInitials(primaryText, isRTL, allowPhoneInitials); return ( - imageInitials !== '' ? - { imageInitials } : - + imageInitials !== '' + ? { imageInitials } + : ); } @@ -193,8 +216,14 @@ export class PersonaCoin extends React.Component { isImageError: loadState === ImageLoadState.error }); - if (this.props.onPhotoLoadingStateChange) { - this.props.onPhotoLoadingStateChange(loadState); - } + this.props.onPhotoLoadingStateChange && this.props.onPhotoLoadingStateChange(loadState); } -} \ No newline at end of file +} + +/** + * PersonaCoin is used to render an individual's avatar and presence. + */ +export const PersonaCoin = styled( + PersonaCoinBase, + getStyles +); \ No newline at end of file diff --git a/packages/office-ui-fabric-react/src/components/Persona/PersonaConsts.ts b/packages/office-ui-fabric-react/src/components/Persona/PersonaConsts.ts deleted file mode 100644 index e2df04104471b7..00000000000000 --- a/packages/office-ui-fabric-react/src/components/Persona/PersonaConsts.ts +++ /dev/null @@ -1,36 +0,0 @@ -import { - PersonaPresence, - PersonaSize -} from './Persona.types'; -import * as stylesImport from './Persona.scss'; -const styles: any = stylesImport; - -export const PERSONA_SIZE: { [key: number]: string } = { - // All non-numerically named sizes are deprecated, use the numerically named classes below - [PersonaSize.tiny]: 'ms-Persona--tiny ' + styles.rootIsSize10, - [PersonaSize.extraExtraSmall]: 'ms-Persona--xxs ' + styles.rootIsSize24, - [PersonaSize.extraSmall]: 'ms-Persona--xs ' + styles.rootIsSize28, - [PersonaSize.small]: 'ms-Persona--sm ' + styles.rootIsSize40, - [PersonaSize.regular]: '', - [PersonaSize.large]: 'ms-Persona--lg ' + styles.rootIsSize72, - [PersonaSize.extraLarge]: 'ms-Persona--xl ' + styles.rootIsSize100, - - [PersonaSize.size10]: 'ms-Persona--size10 ' + styles.rootIsSize10, - [PersonaSize.size16]: 'ms-Persona--size16 ' + styles.rootIsSize16, - [PersonaSize.size24]: 'ms-Persona--size24 ' + styles.rootIsSize24, - [PersonaSize.size28]: 'ms-Persona--size28 ' + styles.rootIsSize28, - [PersonaSize.size32]: 'ms-Persona--size32 ' + styles.rootIsSize32, - [PersonaSize.size40]: 'ms-Persona--size40 ' + styles.rootIsSize40, - [PersonaSize.size48]: 'ms-Persona--size48 ' + styles.rootIsSize48, - [PersonaSize.size72]: 'ms-Persona--size72 ' + styles.rootIsSize72, - [PersonaSize.size100]: 'ms-Persona--size100 ' + styles.rootIsSize100 -}; - -export const PERSONA_PRESENCE: { [key: number]: string } = { - [PersonaPresence.offline]: 'ms-Persona--offline ' + styles.rootIsOffline, - [PersonaPresence.online]: 'ms-Persona--online ', - [PersonaPresence.away]: 'ms-Persona--away ' + styles.rootIsAway, - [PersonaPresence.dnd]: 'ms-Persona--dnd ' + styles.rootIsDoNotDisturb, - [PersonaPresence.blocked]: 'ms-Persona--blocked ' + styles.rootIsBlocked, - [PersonaPresence.busy]: 'ms-Persona--busy ' + styles.rootIsBusy -}; \ No newline at end of file diff --git a/packages/office-ui-fabric-react/src/components/Persona/PersonaConsts.tsx b/packages/office-ui-fabric-react/src/components/Persona/PersonaConsts.tsx new file mode 100644 index 00000000000000..19b2042deccb02 --- /dev/null +++ b/packages/office-ui-fabric-react/src/components/Persona/PersonaConsts.tsx @@ -0,0 +1,49 @@ +import * as React from 'react'; +import { + PersonaPresence, + PersonaSize, +} from './Persona.types'; + +// Persona Sizes +export namespace personaSize { + export const size10 = '20px'; + export const size16 = '16px'; + export const size24 = '24px'; + export const size28 = '28px'; + export const size32 = '32px'; + export const size40 = '40px'; + export const size48 = '48px'; + export const size72 = '72px'; + export const size100 = '100px'; +} + +// Persona Presence Sizes +export namespace personaPresenceSize { + export const size6 = '6px'; + export const size8 = '8px'; + export const size12 = '12px'; + export const size20 = '20px'; + export const size28 = '28px'; + export const border = '2px'; +} + +export const sizeBoolean = (size: PersonaSize) => ({ + isSize10: size === PersonaSize.size10 || size === PersonaSize.tiny, + isSize16: size === PersonaSize.size16, + isSize24: size === PersonaSize.size24 || size === PersonaSize.extraExtraSmall, + isSize28: size === PersonaSize.size28 || size === PersonaSize.extraSmall, + isSize32: size === PersonaSize.size32, + isSize40: size === PersonaSize.size40 || size === PersonaSize.small, + isSize48: size === PersonaSize.size48, + isSize72: size === PersonaSize.size72 || size === PersonaSize.large, + isSize100: size === PersonaSize.size100 || size === PersonaSize.extraLarge, +}); + +export const presenceBoolean = (presence: PersonaPresence) => ({ + isAvailable: presence === PersonaPresence.online, + isAway: presence === PersonaPresence.away, + isBlocked: presence === PersonaPresence.blocked, + isBusy: presence === PersonaPresence.busy, + isDoNotDisturb: presence === PersonaPresence.dnd, + isOffline: presence === PersonaPresence.offline, +}); diff --git a/packages/office-ui-fabric-react/src/components/Persona/PersonaPresence.styles.ts b/packages/office-ui-fabric-react/src/components/Persona/PersonaPresence.styles.ts new file mode 100644 index 00000000000000..28fd5f19caf784 --- /dev/null +++ b/packages/office-ui-fabric-react/src/components/Persona/PersonaPresence.styles.ts @@ -0,0 +1,207 @@ +import { + IPersonaPresenceStyleProps, + IPersonaPresenceStyles, + PersonaPresence, + PersonaSize, +} from './Persona.types'; +import { + FontSizes, + HighContrastSelector, +} from '../../Styling'; +import { + personaPresenceSize, + presenceBoolean, + sizeBoolean, +} from './PersonaConsts'; + +export const getStyles = ( + props: IPersonaPresenceStyleProps +): IPersonaPresenceStyles => { + const { + className, + theme, + } = props; + + const { semanticColors } = theme; + + const size = sizeBoolean(props.size as PersonaSize); + const presence = presenceBoolean(props.presence as PersonaPresence); + + // Presence colors + const presenceColorAvailable = '#7FBA00'; + const presenceColorAway = '#FCD116'; + const presenceColorBusy = '#D93B3B'; + const presenceColorDnd = '#E81123'; + const presenceColorOffline = '#93ABBD'; + + return ({ + presence: [ + 'ms-Persona-presence', + { + position: 'absolute', + height: personaPresenceSize.size12, + width: personaPresenceSize.size12, + borderRadius: '50%', + top: 'auto', + right: `-${personaPresenceSize.border}`, + bottom: `-${personaPresenceSize.border}`, + border: `${personaPresenceSize.border} solid ${semanticColors.bodyBackground}`, + textAlign: 'center', + boxSizing: 'content-box', + backgroundClip: 'content-box', + MsHighContrastAdjust: 'none', + + selectors: { + [HighContrastSelector]: { + borderColor: 'Window', + backgroundColor: 'WindowText', + } + } + }, + + size.isSize10 && { + right: 'auto', + top: '7px', + left: 0, + border: 0, + + selectors: { + [HighContrastSelector]: { + top: '9px', + border: '1px solid WindowText', + } + } + }, + + (size.isSize10 || size.isSize24 || size.isSize28 || size.isSize32) && { + height: personaPresenceSize.size8, + width: personaPresenceSize.size8, + }, + + size.isSize16 && { + height: personaPresenceSize.size6, + width: personaPresenceSize.size6, + borderWidth: '1.5px', + }, + + size.isSize72 && { + height: personaPresenceSize.size20, + width: personaPresenceSize.size20, + }, + + size.isSize100 && { + height: personaPresenceSize.size28, + width: personaPresenceSize.size28, + }, + + presence.isAvailable && { + backgroundColor: presenceColorAvailable, + + selectors: { + [HighContrastSelector]: { + backgroundColor: 'Highlight', + }, + } + }, + + presence.isAway && { + backgroundColor: presenceColorAway, + }, + + presence.isBlocked && [ + { + backgroundColor: semanticColors.bodyBackground, + + selectors: { + ':before': { + content: '""', + width: '100%', + height: '100%', + position: 'absolute', + top: 0, + left: 0, + border: `${personaPresenceSize.border} solid ${presenceColorBusy}`, + borderRadius: '50%', + boxSizing: 'border-box', + }, + + // Only show :after at larger sizes + ':after': (size.isSize40 || size.isSize48 || size.isSize72 || size.isSize100) ? { + content: '""', + width: '100%', + height: personaPresenceSize.border, + backgroundColor: presenceColorBusy, + transform: 'translateY(-50%) rotate(-45deg)', + position: 'absolute', + top: '50%', + left: 0, + } : undefined, + + [HighContrastSelector]: { + backgroundColor: 'WindowText', + + selectors: { + ':before': { + width: `calc(100% - ${personaPresenceSize.border})`, + height: `calc(100% - ${personaPresenceSize.border})`, + top: parseFloat(personaPresenceSize.border) / 2 + 'px', + left: parseFloat(personaPresenceSize.border) / 2 + 'px', + borderColor: 'Window', + }, + + ':after': { + width: `calc(100% - ${parseFloat(personaPresenceSize.border) * 2}px)`, + left: personaPresenceSize.border, + backgroundColor: 'Window', + } + } + }, + } + }, + ], + + presence.isBusy && { + backgroundColor: presenceColorBusy, + }, + + presence.isDoNotDisturb && { + backgroundColor: presenceColorDnd, + }, + + presence.isOffline && { + backgroundColor: presenceColorOffline, + }, + ], + + presenceIcon: [ + 'ms-Persona-presenceIcon', + { + color: semanticColors.bodyBackground, + fontSize: '6px', + lineHeight: personaPresenceSize.size12, + verticalAlign: 'top', + + selectors: { + [HighContrastSelector]: { + color: 'Window', + } + } + }, + + size.isSize72 && { + fontSize: FontSizes.small, + lineHeight: personaPresenceSize.size20, + }, + + size.isSize100 && { + fontSize: FontSizes.medium, + lineHeight: personaPresenceSize.size28, + }, + + presence.isAway && { + position: 'relative', + left: '1px', + }, + ] + }); +}; diff --git a/packages/office-ui-fabric-react/src/components/Persona/PersonaPresence.tsx b/packages/office-ui-fabric-react/src/components/Persona/PersonaPresence.tsx index cbc4adc36ab51e..37a471843a46ab 100644 --- a/packages/office-ui-fabric-react/src/components/Persona/PersonaPresence.tsx +++ b/packages/office-ui-fabric-react/src/components/Persona/PersonaPresence.tsx @@ -1,47 +1,85 @@ import * as React from 'react'; -import { css } from '../../Utilities'; +import { + BaseComponent, + classNamesFunction, + customizable, + styled, +} from '../../Utilities'; +import { IStyleSet } from '../../Styling'; import { Icon } from '../../Icon'; import { - IPersonaProps, - PersonaPresence as PersonaPresenceEnum + IPersonaPresenceProps, + IPersonaPresenceStyleProps, + IPersonaPresenceStyles, + PersonaPresence as PersonaPresenceEnum, + PersonaSize, } from './Persona.types'; -import * as stylesImport from './Persona.scss'; -const styles: any = stylesImport; +import { sizeBoolean } from './PersonaConsts'; +import { getStyles } from './PersonaPresence.styles'; + const coinSizeFontScaleFactor = 6; const coinSizePresenceScaleFactor = 3; const presenceMaxSize = 40; const presenceFontMaxSize = 20; -export class PersonaPresence extends React.Component { - constructor(props: IPersonaProps) { +const getClassNames = classNamesFunction(); + +/** + * PersonaPresence with no default styles. + * [Use the `getStyles` API to add your own styles.](https://github.com/OfficeDev/office-ui-fabric-react/wiki/Styling) + */ +@customizable('PersonaPresence', ['theme']) +export class PersonaPresenceBase extends BaseComponent { + constructor(props: IPersonaPresenceProps) { super(props); } public render(): JSX.Element | null { - const { presence, coinSize } = this.props; - const presenceHeightWidth = coinSize && (coinSize / coinSizePresenceScaleFactor < presenceMaxSize ? coinSize / coinSizePresenceScaleFactor : presenceMaxSize); - const presenceFontSize = coinSize && (coinSize / coinSizeFontScaleFactor < presenceFontMaxSize ? coinSize / coinSizeFontScaleFactor : presenceFontMaxSize); - const coinSizeWithPresenceIconStyle = coinSize ? { fontSize: presenceFontSize, lineHeight: presenceHeightWidth + 'px' } : undefined; + const { + coinSize, + getStyles: getStylesProp, // Use getStyles from props. + presence, + theme, + } = this.props; + const size = sizeBoolean(this.props.size as PersonaSize); + + // Render Presence Icon if Persona is above size 32. + const renderIcon = !(size.isSize10 || size.isSize16 || size.isSize24 || size.isSize28 || size.isSize32) && (coinSize ? coinSize > 32 : true); + + const presenceHeightWidth: string = coinSize ? (coinSize / coinSizePresenceScaleFactor < presenceMaxSize ? coinSize / coinSizePresenceScaleFactor + 'px' : presenceMaxSize + 'px') : ''; + const presenceFontSize: string = coinSize ? (coinSize / coinSizeFontScaleFactor < presenceFontMaxSize ? coinSize / coinSizeFontScaleFactor + 'px' : presenceFontMaxSize + 'px') : ''; + const coinSizeWithPresenceIconStyle = coinSize ? { fontSize: presenceFontSize, lineHeight: presenceHeightWidth } : undefined; const coinSizeWithPresenceStyle = coinSize ? { width: presenceHeightWidth, height: presenceHeightWidth } : undefined; + // Use getStyles from props, or fall back to getStyles from styles file. + const classNames = getClassNames(getStylesProp, { + theme: theme!, + presence, + size: this.props.size, + }); + if (presence === PersonaPresenceEnum.none) { return null; } return (
- + { renderIcon && this._onRenderIcon(classNames.presenceIcon, coinSizeWithPresenceIconStyle) }
); } + private _onRenderIcon = (className?: string, styles?: IStyleSet): JSX.Element => ( + + ) + private _determineIcon = (): string | undefined => { const { presence } = this.props; @@ -65,4 +103,12 @@ export class PersonaPresence extends React.Component { return userPresence; } } -} \ No newline at end of file +} + +/** + * PersonaPresence is used to render an individual's presence. + */ +export const PersonaPresence = styled( + PersonaPresenceBase, + getStyles +); diff --git a/packages/office-ui-fabric-react/src/components/Persona/__snapshots__/Persona.test.tsx.snap b/packages/office-ui-fabric-react/src/components/Persona/__snapshots__/Persona.test.tsx.snap index 0353479d3e09e9..5d2506121ceb58 100644 --- a/packages/office-ui-fabric-react/src/components/Persona/__snapshots__/Persona.test.tsx.snap +++ b/packages/office-ui-fabric-react/src/components/Persona/__snapshots__/Persona.test.tsx.snap @@ -2,16 +2,55 @@ exports[`Persona renders Persona correctly with image 1`] = `
@@ -86,16 +185,55 @@ exports[`Persona renders Persona correctly with image 1`] = ` exports[`Persona renders Persona correctly with initials 1`] = `
@@ -116,7 +267,16 @@ exports[`Persona renders Persona correctly with initials 1`] = ` ms-Image ms-Persona-image { + border-radius: 50%; + border: 0px; + height: 100%; + left: 0px; + margin-right: 10px; overflow: hidden; + perspective: 1px; + position: absolute; + top: 0px; + width: 100%; } style={ Object { @@ -136,9 +296,9 @@ exports[`Persona renders Persona correctly with initials 1`] = ` { display: block; height: auto; - left: 50%; + left: 50% /* @noflip */; opacity: 0; - position: relative; + position: absolute; top: 50%; transform: translate(-50%,-50%); width: 100%; @@ -152,10 +312,32 @@ exports[`Persona renders Persona correctly with initials 1`] = `
diff --git a/packages/office-ui-fabric-react/src/components/Persona/index.ts b/packages/office-ui-fabric-react/src/components/Persona/index.ts index 26d2f1fddaa4eb..c10c0f10b7b519 100644 --- a/packages/office-ui-fabric-react/src/components/Persona/index.ts +++ b/packages/office-ui-fabric-react/src/components/Persona/index.ts @@ -1,5 +1,5 @@ export * from './Persona'; export * from './Persona.base'; export * from './Persona.types'; -export * from './PersonaConsts'; export * from './PersonaCoin'; +export * from './PersonaConsts'; diff --git a/packages/styling/src/styles/GeneralStyles.ts b/packages/styling/src/styles/GeneralStyles.ts index 4b3232a912f942..71e63dc5082cd2 100644 --- a/packages/styling/src/styles/GeneralStyles.ts +++ b/packages/styling/src/styles/GeneralStyles.ts @@ -7,4 +7,10 @@ export const normalize: IRawStyle = { margin: 0, padding: 0, boxSizing: 'border-box' -}; \ No newline at end of file +}; + +export const noWrap: IRawStyle = { + overflow: 'hidden', + textOverflow: 'ellipsis', + whiteSpace: 'nowrap', +}; diff --git a/packages/styling/src/styles/index.ts b/packages/styling/src/styles/index.ts index c734f9d0949874..7811a7c5b2d2ae 100644 --- a/packages/styling/src/styles/index.ts +++ b/packages/styling/src/styles/index.ts @@ -13,4 +13,4 @@ export { removeOnThemeChangeCallback } from './theme'; export * from './CommonStyles'; -export { normalize } from './GeneralStyles'; +export * from './GeneralStyles'; diff --git a/scripts/package.json b/scripts/package.json index bd33664c292d12..4178c1460bd56c 100644 --- a/scripts/package.json +++ b/scripts/package.json @@ -49,7 +49,7 @@ "bundlesize": [ { "path": "../apps/test-bundle-button/dist/test-bundle-button.min.js", - "maxSize": "45.5 kB" + "maxSize": "45.6 kB" } ] } \ No newline at end of file