From 327a7551cf3ea558c66bdf09b754f815008faef7 Mon Sep 17 00:00:00 2001 From: Marija Najdova Date: Tue, 14 Jan 2020 15:30:29 +0100 Subject: [PATCH 01/24] -converted Box component to use hooks --- packages/react/src/components/Box/Box.tsx | 69 ++++++++++++++--------- packages/react/src/types.ts | 11 ++++ 2 files changed, 54 insertions(+), 26 deletions(-) diff --git a/packages/react/src/components/Box/Box.tsx b/packages/react/src/components/Box/Box.tsx index 536b5bf898..ede3389f36 100644 --- a/packages/react/src/components/Box/Box.tsx +++ b/packages/react/src/components/Box/Box.tsx @@ -1,4 +1,8 @@ +import { getElementType, getUnhandledProps, useStyles } from '@fluentui/react-bindings' import * as React from 'react' +// @ts-ignore +import { ThemeContext } from 'react-fela' + import { childrenExist, createShorthandFactory, @@ -8,38 +12,51 @@ import { commonPropTypes, rtlTextContainer, } from '../../utils' -import createComponentInternal from '../../utils/createComponentInternal' -import { WithAsProp, withSafeTypeForAs } from '../../types' +import { + ProviderContextPrepared, + WithAsProp, + withSafeTypeForAs, + FluentComponentStaticProps, +} from '../../types' export interface BoxProps extends UIComponentProps, ContentComponentProps, ChildrenComponentProps {} -const Box = createComponentInternal>({ - displayName: 'Box', - - className: 'ui-box', - - propTypes: { - ...commonPropTypes.createCommon(), - }, - - render(config, props) { - const { ElementType, classes, unhandledProps } = config - const { children, content } = props - - return ( - - {childrenExist(children) ? children : content} - - ) - }, -}) +const Box: React.FC> & FluentComponentStaticProps = props => { + const { className, design, styles, variables, children, content } = props + const context: ProviderContextPrepared = React.useContext(ThemeContext) + const [classes] = useStyles(Box.displayName, { + className: Box.className, + mapPropsToInlineStyles: () => ({ + className, + design, + styles, + variables, + }), + rtl: context.rtl, + }) + + const unhandledProps = getUnhandledProps(Box.handledProps, props) + const ElementType = getElementType(props) + + return ( + + {childrenExist(children) ? children : content} + + ) +} + +Box.className = 'ui-box' +Box.displayName = 'Box' + +Box.propTypes = commonPropTypes.createCommon({ animated: false, accessibility: false }) +Box.handledProps = Object.keys(Box.propTypes) as any Box.create = createShorthandFactory({ Component: Box }) diff --git a/packages/react/src/types.ts b/packages/react/src/types.ts index f5d5d73513..3c3e7a1d7a 100644 --- a/packages/react/src/types.ts +++ b/packages/react/src/types.ts @@ -2,6 +2,7 @@ import { StylesContextInputValue, StylesContextValue } from '@fluentui/react-bin import * as React from 'react' import Telemetry from './utils/Telemetry' +import { ShorthandFactory } from 'src/utils' // ======================================================== // Utilities @@ -13,6 +14,16 @@ export type ObjectOf = { [key: string]: T } export type Omit = Pick> +// ======================================================== +// Components +// ======================================================== + +export type FluentComponentStaticProps

= { + className: string + handledProps: (keyof P)[] + create: ShorthandFactory

+} + // ======================================================== // Props // ======================================================== From cbe0c44a2914f7e50d90fda17375b42067047a2a Mon Sep 17 00:00:00 2001 From: Marija Najdova Date: Tue, 14 Jan 2020 15:33:31 +0100 Subject: [PATCH 02/24] -fix Box props --- packages/react/src/components/Box/Box.tsx | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/packages/react/src/components/Box/Box.tsx b/packages/react/src/components/Box/Box.tsx index ede3389f36..2220d9b48f 100644 --- a/packages/react/src/components/Box/Box.tsx +++ b/packages/react/src/components/Box/Box.tsx @@ -1,4 +1,9 @@ -import { getElementType, getUnhandledProps, useStyles } from '@fluentui/react-bindings' +import { + ComponentDesignProp, + getElementType, + getUnhandledProps, + useStyles, +} from '@fluentui/react-bindings' import * as React from 'react' // @ts-ignore import { ThemeContext } from 'react-fela' @@ -6,11 +11,11 @@ import { ThemeContext } from 'react-fela' import { childrenExist, createShorthandFactory, - UIComponentProps, ContentComponentProps, ChildrenComponentProps, commonPropTypes, rtlTextContainer, + StyledComponentProps, } from '../../utils' import { ProviderContextPrepared, @@ -20,9 +25,13 @@ import { } from '../../types' export interface BoxProps - extends UIComponentProps, + extends StyledComponentProps, ContentComponentProps, - ChildrenComponentProps {} + ChildrenComponentProps { + /** Additional CSS class name(s) to apply. */ + className?: string + design?: ComponentDesignProp +} const Box: React.FC> & FluentComponentStaticProps = props => { const { className, design, styles, variables, children, content } = props From 1dad61e4523230a07f8f2ddd5b2bf40ebb9db5e4 Mon Sep 17 00:00:00 2001 From: Oleksandr Fediashov Date: Tue, 14 Jan 2020 15:37:12 +0100 Subject: [PATCH 03/24] temporary support for animation --- .../react-bindings/src/hooks/useStyles.ts | 3 +++ packages/react/src/components/Box/Box.tsx | 21 +++++++------------ 2 files changed, 10 insertions(+), 14 deletions(-) diff --git a/packages/react-bindings/src/hooks/useStyles.ts b/packages/react-bindings/src/hooks/useStyles.ts index 7b454bebe5..53528297b1 100644 --- a/packages/react-bindings/src/hooks/useStyles.ts +++ b/packages/react-bindings/src/hooks/useStyles.ts @@ -10,6 +10,7 @@ import * as React from 'react' import { ThemeContext } from 'react-fela' import { + ComponentAnimationProp, ComponentDesignProp, ComponentSlotClasses, RendererRenderRule, @@ -26,6 +27,8 @@ type UseStylesOptions = { } type InlineStyleProps = { + unstable_animation?: ComponentAnimationProp + /** Additional CSS class name(s) to apply. */ className?: string diff --git a/packages/react/src/components/Box/Box.tsx b/packages/react/src/components/Box/Box.tsx index 2220d9b48f..dedbd5e7e0 100644 --- a/packages/react/src/components/Box/Box.tsx +++ b/packages/react/src/components/Box/Box.tsx @@ -1,9 +1,4 @@ -import { - ComponentDesignProp, - getElementType, - getUnhandledProps, - useStyles, -} from '@fluentui/react-bindings' +import { getElementType, getUnhandledProps, useStyles } from '@fluentui/react-bindings' import * as React from 'react' // @ts-ignore import { ThemeContext } from 'react-fela' @@ -15,7 +10,7 @@ import { ChildrenComponentProps, commonPropTypes, rtlTextContainer, - StyledComponentProps, + UIComponentProps, } from '../../utils' import { ProviderContextPrepared, @@ -25,20 +20,18 @@ import { } from '../../types' export interface BoxProps - extends StyledComponentProps, + extends UIComponentProps, ContentComponentProps, - ChildrenComponentProps { - /** Additional CSS class name(s) to apply. */ - className?: string - design?: ComponentDesignProp -} + ChildrenComponentProps {} const Box: React.FC> & FluentComponentStaticProps = props => { - const { className, design, styles, variables, children, content } = props + const { animation, className, design, styles, variables, children, content } = props + const context: ProviderContextPrepared = React.useContext(ThemeContext) const [classes] = useStyles(Box.displayName, { className: Box.className, mapPropsToInlineStyles: () => ({ + unstable_animation: animation, className, design, styles, From 6cc85439b2aac5a069b7de5892633f322923f6d2 Mon Sep 17 00:00:00 2001 From: Oleksandr Fediashov Date: Tue, 14 Jan 2020 15:51:59 +0100 Subject: [PATCH 04/24] convert Image to hooks --- .../src/behaviors/Image/imageBehavior.ts | 2 +- packages/accessibility/src/behaviors/index.ts | 1 + packages/react/src/components/Image/Image.tsx | 121 ++++++++++++------ 3 files changed, 83 insertions(+), 41 deletions(-) diff --git a/packages/accessibility/src/behaviors/Image/imageBehavior.ts b/packages/accessibility/src/behaviors/Image/imageBehavior.ts index 00bdc141f1..ec03e2548a 100644 --- a/packages/accessibility/src/behaviors/Image/imageBehavior.ts +++ b/packages/accessibility/src/behaviors/Image/imageBehavior.ts @@ -18,7 +18,7 @@ const imageBehavior: Accessibility = props => ({ export default imageBehavior -type ImageBehaviorProps = { +export type ImageBehaviorProps = { /** Alternative text. */ alt?: string } & Pick diff --git a/packages/accessibility/src/behaviors/index.ts b/packages/accessibility/src/behaviors/index.ts index a8584fe8dc..fb2a0b1dca 100644 --- a/packages/accessibility/src/behaviors/index.ts +++ b/packages/accessibility/src/behaviors/index.ts @@ -3,6 +3,7 @@ export { default as alertWarningBehavior } from './Alert/alertWarningBehavior' export { default as attachmentBehavior } from './Attachment/attachmentBehavior' export { default as buttonBehavior } from './Button/buttonBehavior' export { default as toggleButtonBehavior } from './Button/toggleButtonBehavior' +export * from './Image/imageBehavior' export { default as imageBehavior } from './Image/imageBehavior' export { default as menuBehavior } from './Menu/menuBehavior' export { default as menuItemBehavior } from './Menu/menuItemBehavior' diff --git a/packages/react/src/components/Image/Image.tsx b/packages/react/src/components/Image/Image.tsx index f9876c98ec..b6872e575d 100644 --- a/packages/react/src/components/Image/Image.tsx +++ b/packages/react/src/components/Image/Image.tsx @@ -1,19 +1,29 @@ -import { Accessibility, imageBehavior } from '@fluentui/accessibility' +import { + Accessibility, + AccessibilityAttributes, + imageBehavior, + ImageBehaviorProps, +} from '@fluentui/accessibility' +import { + getElementType, + getUnhandledProps, + useAccessibility, + useStyles, +} from '@fluentui/react-bindings' import * as PropTypes from 'prop-types' import * as React from 'react' -import { - createShorthandFactory, - UIComponent, - UIComponentProps, - commonPropTypes, - ShorthandFactory, -} from '../../utils' -import { WithAsProp, withSafeTypeForAs } from '../../types' - -export interface ImageProps extends UIComponentProps { +import { createShorthandFactory, UIComponentProps, commonPropTypes } from '../../utils' +import { FluentComponentStaticProps, WithAsProp, withSafeTypeForAs } from '../../types' + +export interface ImageProps extends UIComponentProps, ImageBehaviorProps { + /** Alternative text. */ + alt?: string + + 'aria-label'?: AccessibilityAttributes['aria-label'] + /** Accessibility behavior if overridden by the user. */ - accessibility?: Accessibility + accessibility?: Accessibility /** An image may be formatted to appear inline with text as an avatar. */ avatar?: boolean @@ -28,39 +38,70 @@ export interface ImageProps extends UIComponentProps { src?: string } -class Image extends UIComponent, any> { - static create: ShorthandFactory +const Image: React.FC> & FluentComponentStaticProps = props => { + const { + accessibility, + alt, + animation, + 'aria-label': ariaLabel, + avatar, + circular, + className, + design, + fluid, + styles, + variables, + } = props - static className = 'ui-image' + const getA11Props = useAccessibility(accessibility, { + debugName: Image.displayName, + mapPropsToBehavior: () => ({ + alt, + 'aria-label': ariaLabel, + }), + }) + const [classes] = useStyles(Image.displayName, { + className: Image.className, + mapPropsToStyles: () => ({ + avatar, + circular, + fluid, + }), + mapPropsToInlineStyles: () => ({ + unstable_animation: animation, + className, + design, + styles, + variables, + }), + }) - static displayName = 'Image' + const ElementType = getElementType(props) + const unhandledProps = getUnhandledProps(Image.handledProps, props) - static propTypes = { - ...commonPropTypes.createCommon({ - children: false, - content: false, - }), - avatar: PropTypes.bool, - circular: PropTypes.bool, - fluid: PropTypes.bool, - } - - static defaultProps = { - as: 'img', - accessibility: imageBehavior as Accessibility, - } - - renderComponent({ ElementType, classes, accessibility, unhandledProps }) { - return ( - - ) - } + return +} + +Image.className = 'ui-image' +Image.displayName = 'Image' +Image.defaultProps = { + as: 'img', + accessibility: imageBehavior, } +Image.propTypes = { + ...commonPropTypes.createCommon({ + children: false, + content: false, + }), + accessibility: PropTypes.func, + avatar: PropTypes.bool, + circular: PropTypes.bool, + fluid: PropTypes.bool, +} + +Image.handledProps = Object.keys(Image.propTypes) as any + Image.create = createShorthandFactory({ Component: Image, mappedProp: 'src', allowsJSX: false }) /** From 1deef88aacfc2a268e03ab6a0a11c1709329f24e Mon Sep 17 00:00:00 2001 From: Marija Najdova Date: Tue, 14 Jan 2020 16:22:47 +0100 Subject: [PATCH 05/24] -converted Button component to use hooks --- packages/react-proptypes/src/index.ts | 16 +- .../react/src/components/Button/Button.tsx | 249 +++++++++++------- .../teams/components/Button/buttonStyles.ts | 7 +- 3 files changed, 163 insertions(+), 109 deletions(-) diff --git a/packages/react-proptypes/src/index.ts b/packages/react-proptypes/src/index.ts index 8bc39cc8c2..714b9df0e4 100644 --- a/packages/react-proptypes/src/index.ts +++ b/packages/react-proptypes/src/index.ts @@ -476,17 +476,11 @@ export const deprecate = (help: string, validator?: Function) => ( return error } -export const accessibility = PropTypes.oneOfType([PropTypes.func, PropTypes.object]) - -export const size = PropTypes.oneOf([ - 'smallest', - 'smaller', - 'small', - 'medium', - 'large', - 'larger', - 'largest', -]) +export const accessibility = PropTypes.func + +export const size = PropTypes.oneOf< + 'smallest' | 'smaller' | 'small' | 'medium' | 'large' | 'larger' | 'largest' +>(['smallest', 'smaller', 'small', 'medium', 'large', 'larger', 'largest']) export const align = PropTypes.oneOf(['start', 'end', 'center', 'justify']) diff --git a/packages/react/src/components/Button/Button.tsx b/packages/react/src/components/Button/Button.tsx index d1633e3c05..8e7b4ebb29 100644 --- a/packages/react/src/components/Button/Button.tsx +++ b/packages/react/src/components/Button/Button.tsx @@ -5,7 +5,6 @@ import * as React from 'react' import * as _ from 'lodash' import { - UIComponent, childrenExist, createShorthandFactory, UIComponentProps, @@ -13,15 +12,28 @@ import { ChildrenComponentProps, commonPropTypes, rtlTextContainer, - applyAccessibilityKeyHandlers, SizeValue, - ShorthandFactory, } from '../../utils' import Icon, { IconProps } from '../Icon/Icon' import Box, { BoxProps } from '../Box/Box' import Loader, { LoaderProps } from '../Loader/Loader' -import { ComponentEventHandler, WithAsProp, ShorthandValue, withSafeTypeForAs } from '../../types' +import { + ComponentEventHandler, + WithAsProp, + ShorthandValue, + withSafeTypeForAs, + FluentComponentStaticProps, + ProviderContextPrepared, +} from '../../types' import ButtonGroup from './ButtonGroup' +import { + getElementType, + getUnhandledProps, + useAccessibility, + useStyles, +} from '@fluentui/react-bindings' +// @ts-ignore +import { ThemeContext } from 'react-fela' export interface ButtonProps extends UIComponentProps, @@ -84,120 +96,165 @@ export interface ButtonProps size?: SizeValue } -class Button extends UIComponent> { - static create: ShorthandFactory - - static displayName = 'Button' - - static className = 'ui-button' - - static propTypes = { - ...commonPropTypes.createCommon({ - content: 'shorthand', - }), - circular: PropTypes.bool, - disabled: PropTypes.bool, - fluid: PropTypes.bool, - icon: customPropTypes.itemShorthandWithoutJSX, - iconOnly: PropTypes.bool, - iconPosition: PropTypes.oneOf(['before', 'after']), - loader: customPropTypes.itemShorthandWithoutJSX, - loading: PropTypes.bool, - onClick: PropTypes.func, - onFocus: PropTypes.func, - primary: customPropTypes.every([customPropTypes.disallow(['secondary']), PropTypes.bool]), - text: PropTypes.bool, - secondary: customPropTypes.every([customPropTypes.disallow(['primary']), PropTypes.bool]), - size: customPropTypes.size, - } - - static defaultProps = { - as: 'button', - accessibility: buttonBehavior as Accessibility, - size: 'medium', - } - - static Group = ButtonGroup - - actionHandlers = { - performClick: event => { - event.preventDefault() - this.handleClick(event) - }, - } - - renderComponent({ - ElementType, - classes, +const Button: React.FC> & + FluentComponentStaticProps & { Group: any } = props => { + const { accessibility, - variables, + as, + children, + content, + icon, + loader, + disabled, + iconPosition, + loading, + text, + primary, + inverted, + size, + iconOnly, + fluid, + circular, + className, styles, - unhandledProps, - }): React.ReactNode { - const { children, content, disabled, iconPosition, loading } = this.props - const hasChildren = childrenExist(children) - - return ( - - {hasChildren && children} - {!hasChildren && loading && this.renderLoader(variables, styles)} - {!hasChildren && iconPosition !== 'after' && this.renderIcon(variables, styles)} - {Box.create(!hasChildren && content, { - defaultProps: () => ({ as: 'span', styles: styles.content }), - })} - {!hasChildren && iconPosition === 'after' && this.renderIcon(variables, styles)} - - ) - } + variables, + animation, + design, + } = props + const context: ProviderContextPrepared = React.useContext(ThemeContext) + const hasChildren = childrenExist(children) + + const [classes, resolvedStyles] = useStyles(Button.displayName, { + className: Button.className, + mapPropsToStyles: () => ({ + text, + primary, + disabled, + circular, + size, + loading, + inverted, + iconOnly, + fluid, + hasContent: !!content, + }), + mapPropsToInlineStyles: () => ({ + unstable_animation: animation, + className, + design, + styles, + variables, + }), + rtl: context.rtl, + }) + + const getA11Props = useAccessibility(accessibility, { + debugName: Button.displayName, + mapPropsToBehavior: () => ({ + as, + disabled, + loading, + }), + actionHandlers: { + performClick: event => { + event.preventDefault() + handleClick(event) + }, + }, + rtl: context.rtl, + }) - renderIcon = (variables, styles) => { - const { icon, iconPosition, content } = this.props + const unhandledProps = getUnhandledProps(Button.handledProps, props) + const ElementType = getElementType(props) + const renderIcon = () => { return Icon.create(icon, { - defaultProps: () => ({ - styles: styles.icon, - xSpacing: !content ? 'none' : iconPosition === 'after' ? 'before' : 'after', - variables: variables.icon, - }), + defaultProps: () => + getA11Props('icon', { + styles: resolvedStyles.icon, + xSpacing: !content ? 'none' : iconPosition === 'after' ? 'before' : 'after', + }), }) } - renderLoader = (variables, styles) => { - const { loader } = this.props - + const renderLoader = () => { return Loader.create(loader || {}, { - defaultProps: () => ({ - role: undefined, - styles: styles.loader, - }), + defaultProps: () => + getA11Props('loader', { + role: undefined, + styles: resolvedStyles.loader, + }), }) } - handleClick = (e: React.SyntheticEvent) => { - const { disabled } = this.props - + const handleClick = (e: React.SyntheticEvent) => { if (disabled) { e.preventDefault() return } - _.invoke(this.props, 'onClick', e, this.props) + _.invoke(props, 'onClick', e, props) } - handleFocus = (e: React.SyntheticEvent) => { - _.invoke(this.props, 'onFocus', e, this.props) + const handleFocus = (e: React.SyntheticEvent) => { + _.invoke(props, 'onFocus', e, props) } + + return ( + + {hasChildren && children} + {!hasChildren && loading && renderLoader()} + {!hasChildren && iconPosition !== 'after' && renderIcon()} + {Box.create(!hasChildren && content, { + defaultProps: () => getA11Props('content', { as: 'span', styles: resolvedStyles.content }), + })} + {!hasChildren && iconPosition === 'after' && renderIcon()} + + ) +} + +Button.defaultProps = { + as: 'button', + accessibility: buttonBehavior, + size: 'medium', } +Button.displayName = 'Button' +Button.className = 'ui-button' + +Button.propTypes = { + ...commonPropTypes.createCommon({ + content: 'shorthand', + }), + circular: PropTypes.bool, + disabled: PropTypes.bool, + fluid: PropTypes.bool, + icon: customPropTypes.itemShorthandWithoutJSX, + iconOnly: PropTypes.bool, + iconPosition: PropTypes.oneOf(['before', 'after']), + loader: customPropTypes.itemShorthandWithoutJSX, + loading: PropTypes.bool, + onClick: PropTypes.func, + onFocus: PropTypes.func, + primary: customPropTypes.every([customPropTypes.disallow(['secondary']), PropTypes.bool]), + text: PropTypes.bool, + secondary: customPropTypes.every([customPropTypes.disallow(['primary']), PropTypes.bool]), + size: customPropTypes.size, +} + +Button.handledProps = Object.keys(Button.propTypes) as any + +Button.Group = ButtonGroup + Button.create = createShorthandFactory({ Component: Button, mappedProp: 'content' }) /** diff --git a/packages/react/src/themes/teams/components/Button/buttonStyles.ts b/packages/react/src/themes/teams/components/Button/buttonStyles.ts index 205a72c6a7..79af431d06 100644 --- a/packages/react/src/themes/teams/components/Button/buttonStyles.ts +++ b/packages/react/src/themes/teams/components/Button/buttonStyles.ts @@ -8,7 +8,10 @@ import { ButtonVariables } from './buttonVariables' import getBorderFocusStyles from '../../getBorderFocusStyles' import getIconFillOrOutlineStyles from '../../getIconFillOrOutlineStyles' -const buttonStyles: ComponentSlotStylesPrepared = { +const buttonStyles: ComponentSlotStylesPrepared< + ButtonProps & { hasContent?: boolean }, + ButtonVariables +> = { root: ({ props: p, variables: v, theme }): ICSSInJSStyle => { const { siteVariables } = theme const { borderWidth } = siteVariables @@ -265,7 +268,7 @@ const buttonStyles: ComponentSlotStylesPrepared = }, }, - ...(p.content && { + ...(p.hasContent && { marginRight: pxToRem(4), }), }), From ca915a16dd855c1892d22957fdbdd70533c35884 Mon Sep 17 00:00:00 2001 From: Oleksandr Fediashov Date: Tue, 14 Jan 2020 16:31:54 +0100 Subject: [PATCH 06/24] convert Avatar to hooks and update isConformant test --- packages/react-proptypes/src/index.ts | 16 +- .../react/src/components/Avatar/Avatar.tsx | 207 +++++++++++------- packages/react/src/components/Image/Image.tsx | 13 +- .../test/specs/commonTests/isConformant.tsx | 15 +- .../specs/components/Avatar/Avatar-test.tsx | 4 +- packages/react/test/utils/withProvider.tsx | 20 +- 6 files changed, 166 insertions(+), 109 deletions(-) diff --git a/packages/react-proptypes/src/index.ts b/packages/react-proptypes/src/index.ts index 8bc39cc8c2..714b9df0e4 100644 --- a/packages/react-proptypes/src/index.ts +++ b/packages/react-proptypes/src/index.ts @@ -476,17 +476,11 @@ export const deprecate = (help: string, validator?: Function) => ( return error } -export const accessibility = PropTypes.oneOfType([PropTypes.func, PropTypes.object]) - -export const size = PropTypes.oneOf([ - 'smallest', - 'smaller', - 'small', - 'medium', - 'large', - 'larger', - 'largest', -]) +export const accessibility = PropTypes.func + +export const size = PropTypes.oneOf< + 'smallest' | 'smaller' | 'small' | 'medium' | 'large' | 'larger' | 'largest' +>(['smallest', 'smaller', 'small', 'medium', 'large', 'larger', 'largest']) export const align = PropTypes.oneOf(['start', 'end', 'center', 'justify']) diff --git a/packages/react/src/components/Avatar/Avatar.tsx b/packages/react/src/components/Avatar/Avatar.tsx index ec13bca42d..8dc9b61b20 100644 --- a/packages/react/src/components/Avatar/Avatar.tsx +++ b/packages/react/src/components/Avatar/Avatar.tsx @@ -1,25 +1,33 @@ import { Accessibility } from '@fluentui/accessibility' +import { + getElementType, + getUnhandledProps, + useAccessibility, + useStyles, +} from '@fluentui/react-bindings' import * as customPropTypes from '@fluentui/react-proptypes' import * as PropTypes from 'prop-types' import * as React from 'react' +// @ts-ignore +import { ThemeContext } from 'react-fela' + import Image, { ImageProps } from '../Image/Image' import Label, { LabelProps } from '../Label/Label' import Status, { StatusProps } from '../Status/Status' -import { WithAsProp, ShorthandValue, withSafeTypeForAs } from '../../types' import { - createShorthandFactory, - UIComponent, - UIComponentProps, - commonPropTypes, - SizeValue, - ShorthandFactory, -} from '../../utils' + WithAsProp, + ShorthandValue, + withSafeTypeForAs, + FluentComponentStaticProps, + ProviderContextPrepared, +} from '../../types' +import { createShorthandFactory, UIComponentProps, commonPropTypes, SizeValue } from '../../utils' export interface AvatarProps extends UIComponentProps { /** * Accessibility behavior if overridden by the user. */ - accessibility?: Accessibility + accessibility?: Accessibility /** Shorthand for the image. */ image?: ShorthandValue @@ -40,87 +48,118 @@ export interface AvatarProps extends UIComponentProps { getInitials?: (name: string) => string } -class Avatar extends UIComponent, any> { - static create: ShorthandFactory - - static className = 'ui-avatar' - - static displayName = 'Avatar' - - static propTypes = { - ...commonPropTypes.createCommon({ - children: false, - content: false, +const Avatar: React.FC> & + FluentComponentStaticProps = props => { + const { + accessibility, + animation, + className, + design, + getInitials, + label, + image, + name, + size, + status, + styles, + variables, + } = props + const context: ProviderContextPrepared = React.useContext(ThemeContext) + + const getA11Props = useAccessibility(accessibility, { + debugName: Avatar.displayName, + rtl: context.rtl, + }) + const [classes, resolvedStyles] = useStyles(Avatar.displayName, { + className: Avatar.className, + mapPropsToStyles: () => ({ size }), + mapPropsToInlineStyles: () => ({ + unstable_animation: animation, + className, + design, + styles, + variables, }), - name: PropTypes.string, - image: customPropTypes.itemShorthandWithoutJSX, - label: customPropTypes.itemShorthand, - size: customPropTypes.size, - status: customPropTypes.itemShorthand, - getInitials: PropTypes.func, - } - - static defaultProps = { - size: 'medium', - getInitials(name: string) { - if (!name) { - return '' - } - - const reducedName = name - .replace(/\s*\(.*?\)\s*/g, ' ') - .replace(/\s*{.*?}\s*/g, ' ') - .replace(/\s*\[.*?]\s*/g, ' ') - - const initials = reducedName - .split(' ') - .filter(item => item !== '') - .map(item => item.charAt(0)) - .reduce((accumulator, currentValue) => accumulator + currentValue) - - if (initials.length > 2) { - return initials.charAt(0) + initials.charAt(initials.length - 1) - } - return initials - }, - } as AvatarProps - - renderComponent({ accessibility, ElementType, classes, unhandledProps, styles, variables }) { - const { name, status, image, label, getInitials, size } = this.props as AvatarProps - - return ( - - {Image.create(image, { + }) + + const ElementType = getElementType(props) + const unhandledProps = getUnhandledProps(Avatar.handledProps, props) + + return ( + + {Image.create(image, { + defaultProps: () => ({ + fluid: true, + avatar: true, + title: name, + styles: resolvedStyles.image, + }), + })} + {!image && + Label.create(label || {}, { defaultProps: () => ({ - fluid: true, - avatar: true, + content: getInitials(name), + circular: true, title: name, - styles: styles.image, - }), - })} - {!image && - Label.create(label || {}, { - defaultProps: () => ({ - content: getInitials(name), - circular: true, - title: name, - styles: styles.label, - }), - })} - {Status.create(status, { - defaultProps: () => ({ - size, - styles: styles.status, - variables: { - borderColor: variables.statusBorderColor, - borderWidth: variables.statusBorderWidth, - }, + styles: resolvedStyles.label, }), })} - - ) - } + {Status.create(status, { + defaultProps: () => ({ + size, + styles: resolvedStyles.status, + variables: { + // TODO: Fix me + // borderColor: variables.statusBorderColor, + // borderWidth: variables.statusBorderWidth, + }, + }), + })} + + ) +} + +Avatar.className = 'ui-avatar' +Avatar.displayName = 'Avatar' + +Avatar.defaultProps = { + size: 'medium', + getInitials(name: string) { + if (!name) { + return '' + } + + const reducedName = name + .replace(/\s*\(.*?\)\s*/g, ' ') + .replace(/\s*{.*?}\s*/g, ' ') + .replace(/\s*\[.*?]\s*/g, ' ') + + const initials = reducedName + .split(' ') + .filter(item => item !== '') + .map(item => item.charAt(0)) + .reduce((accumulator, currentValue) => accumulator + currentValue) + + if (initials.length > 2) { + return initials.charAt(0) + initials.charAt(initials.length - 1) + } + return initials + }, +} + +Avatar.propTypes = { + ...commonPropTypes.createCommon({ + children: false, + content: false, + }), + name: PropTypes.string, + image: customPropTypes.itemShorthandWithoutJSX, + label: customPropTypes.itemShorthand, + size: customPropTypes.size, + status: customPropTypes.itemShorthand, + getInitials: PropTypes.func, } +Avatar.handledProps = Object.keys(Avatar.propTypes) as any Avatar.create = createShorthandFactory({ Component: Avatar, mappedProp: 'name' }) diff --git a/packages/react/src/components/Image/Image.tsx b/packages/react/src/components/Image/Image.tsx index b6872e575d..65b17214c5 100644 --- a/packages/react/src/components/Image/Image.tsx +++ b/packages/react/src/components/Image/Image.tsx @@ -12,9 +12,16 @@ import { } from '@fluentui/react-bindings' import * as PropTypes from 'prop-types' import * as React from 'react' +// @ts-ignore +import { ThemeContext } from 'react-fela' import { createShorthandFactory, UIComponentProps, commonPropTypes } from '../../utils' -import { FluentComponentStaticProps, WithAsProp, withSafeTypeForAs } from '../../types' +import { + FluentComponentStaticProps, + ProviderContextPrepared, + WithAsProp, + withSafeTypeForAs, +} from '../../types' export interface ImageProps extends UIComponentProps, ImageBehaviorProps { /** Alternative text. */ @@ -52,6 +59,7 @@ const Image: React.FC> & FluentComponentStaticProps> & FluentComponentStaticProps> & FluentComponentStaticProps { + const componentClassName = + info.componentClassName || `ui-${Component.displayName}`.toLowerCase() const getClassesOfRootElement = component => { const classes = component .find('[className]') @@ -467,8 +470,8 @@ export default function isConformant( return classes } - test(`is a static equal to "${info.componentClassName}"`, () => { - expect(Component.className).toEqual(info.componentClassName) + test(`is a static equal to "${componentClassName}"`, () => { + expect(Component.className).toEqual(componentClassName) }) test(`is applied to the root element`, () => { @@ -476,9 +479,7 @@ export default function isConformant( // only test components that implement className if (component.find('[className]').hostNodes().length > 0) { - expect( - _.includes(getClassesOfRootElement(component), `${info.componentClassName}`), - ).toEqual(true) + expect(_.includes(getClassesOfRootElement(component), componentClassName)).toEqual(true) } }) diff --git a/packages/react/test/specs/components/Avatar/Avatar-test.tsx b/packages/react/test/specs/components/Avatar/Avatar-test.tsx index 2a38004fea..e9d9f49e40 100644 --- a/packages/react/test/specs/components/Avatar/Avatar-test.tsx +++ b/packages/react/test/specs/components/Avatar/Avatar-test.tsx @@ -8,7 +8,9 @@ const avatarImplementsShorthandProp = implementsShorthandProp(Avatar) const { getInitials } = (Avatar as any).defaultProps describe('Avatar', () => { - isConformant(Avatar) + isConformant(Avatar, { + constructorName: 'Avatar', + }) avatarImplementsShorthandProp('label', Label) avatarImplementsShorthandProp('image', Image, { mapsValueToProp: 'src' }) diff --git a/packages/react/test/utils/withProvider.tsx b/packages/react/test/utils/withProvider.tsx index 9e897f2d7a..03ce491b36 100644 --- a/packages/react/test/utils/withProvider.tsx +++ b/packages/react/test/utils/withProvider.tsx @@ -1,12 +1,24 @@ -import { ThemeInput } from '@fluentui/styles' +import { emptyTheme, ThemeInput } from '@fluentui/styles' import * as React from 'react' import { mount } from 'enzyme' import { ThemeProvider } from 'react-fela' + import { felaRenderer } from 'src/utils' +import { ProviderContextPrepared } from 'src/types' + +export const EmptyThemeProvider: React.FunctionComponent = ({ children }) => { + const theme: ProviderContextPrepared = { + renderer: felaRenderer, + target: document, + _internal_resolvedComponentVariables: {}, + disableAnimations: false, + rtl: false, + theme: emptyTheme, + telemetry: undefined, + } -export const EmptyThemeProvider: React.FunctionComponent = ({ children }) => ( - {children} -) + return {children} +} export const mountWithProvider = (node, options?, theme?: ThemeInput) => { return mount(node, { From 76d1be459e963fdd3746ce4a6bad73af8a690902 Mon Sep 17 00:00:00 2001 From: Oleksandr Fediashov Date: Tue, 14 Jan 2020 16:34:17 +0100 Subject: [PATCH 07/24] apply attributes to Avatar slots --- .../react/src/components/Avatar/Avatar.tsx | 45 ++++++++++--------- 1 file changed, 24 insertions(+), 21 deletions(-) diff --git a/packages/react/src/components/Avatar/Avatar.tsx b/packages/react/src/components/Avatar/Avatar.tsx index 8dc9b61b20..f7216e6f2b 100644 --- a/packages/react/src/components/Avatar/Avatar.tsx +++ b/packages/react/src/components/Avatar/Avatar.tsx @@ -88,32 +88,35 @@ const Avatar: React.FC> & return ( {Image.create(image, { - defaultProps: () => ({ - fluid: true, - avatar: true, - title: name, - styles: resolvedStyles.image, - }), + defaultProps: () => + getA11Props('image', { + fluid: true, + avatar: true, + title: name, + styles: resolvedStyles.image, + }), })} {!image && Label.create(label || {}, { - defaultProps: () => ({ - content: getInitials(name), - circular: true, - title: name, - styles: resolvedStyles.label, - }), + defaultProps: () => + getA11Props('label', { + content: getInitials(name), + circular: true, + title: name, + styles: resolvedStyles.label, + }), })} {Status.create(status, { - defaultProps: () => ({ - size, - styles: resolvedStyles.status, - variables: { - // TODO: Fix me - // borderColor: variables.statusBorderColor, - // borderWidth: variables.statusBorderWidth, - }, - }), + defaultProps: () => + getA11Props('status', { + size, + styles: resolvedStyles.status, + variables: { + // TODO: Fix me + // borderColor: variables.statusBorderColor, + // borderWidth: variables.statusBorderWidth, + }, + }), })} ) From ad41491b94566a54753c814d4452d90618aff5e3 Mon Sep 17 00:00:00 2001 From: Marija Najdova Date: Tue, 14 Jan 2020 16:37:07 +0100 Subject: [PATCH 08/24] -fixed Button test --- packages/react/test/specs/components/Button/Button-test.tsx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/react/test/specs/components/Button/Button-test.tsx b/packages/react/test/specs/components/Button/Button-test.tsx index 2d2809acc9..aa0ea7686b 100644 --- a/packages/react/test/specs/components/Button/Button-test.tsx +++ b/packages/react/test/specs/components/Button/Button-test.tsx @@ -16,7 +16,9 @@ import Icon from 'src/components/Icon/Icon' const buttonImplementsShorthandProp = implementsShorthandProp(Button) describe('Button', () => { - isConformant(Button) + isConformant(Button, { + constructorName: 'Button', + }) buttonImplementsShorthandProp('icon', Icon, { mapsValueToProp: 'name', requiredShorthandProps: { name: 'at' }, From 31f7015e8e766c17922caf3722c81af6bd378f0b Mon Sep 17 00:00:00 2001 From: Oleksandr Fediashov Date: Tue, 14 Jan 2020 16:41:31 +0100 Subject: [PATCH 09/24] update Avatar to avoid variables usage --- packages/react/src/components/Avatar/Avatar.tsx | 5 ----- .../src/themes/teams/components/Avatar/avatarStyles.ts | 7 ++++--- .../src/themes/teams/components/Avatar/avatarVariables.ts | 8 ++++---- 3 files changed, 8 insertions(+), 12 deletions(-) diff --git a/packages/react/src/components/Avatar/Avatar.tsx b/packages/react/src/components/Avatar/Avatar.tsx index f7216e6f2b..6ce633fa95 100644 --- a/packages/react/src/components/Avatar/Avatar.tsx +++ b/packages/react/src/components/Avatar/Avatar.tsx @@ -111,11 +111,6 @@ const Avatar: React.FC> & getA11Props('status', { size, styles: resolvedStyles.status, - variables: { - // TODO: Fix me - // borderColor: variables.statusBorderColor, - // borderWidth: variables.statusBorderWidth, - }, }), })} diff --git a/packages/react/src/themes/teams/components/Avatar/avatarStyles.ts b/packages/react/src/themes/teams/components/Avatar/avatarStyles.ts index 94cea677c9..f2edd75675 100644 --- a/packages/react/src/themes/teams/components/Avatar/avatarStyles.ts +++ b/packages/react/src/themes/teams/components/Avatar/avatarStyles.ts @@ -28,7 +28,7 @@ const avatarStyles: ComponentSlotStylesPrepared = { image: ({ variables: v }): ICSSInJSStyle => ({ borderColor: v.avatarBorderColor, borderStyle: 'solid', - borderWidth: `${v.avatarBorderWidth}px`, + borderWidth: v.avatarBorderWidth, height: '100%', objectFit: 'cover', @@ -50,8 +50,9 @@ const avatarStyles: ComponentSlotStylesPrepared = { }, status: ({ variables: v }): ICSSInJSStyle => ({ position: 'absolute', - bottom: `-${v.statusBorderWidth}px`, - right: `-${v.statusBorderWidth}px`, + bottom: 0, + right: 0, + boxShadow: `0 0 0 ${v.statusBorderWidth} ${v.statusBorderColor}`, }), } diff --git a/packages/react/src/themes/teams/components/Avatar/avatarVariables.ts b/packages/react/src/themes/teams/components/Avatar/avatarVariables.ts index f952d65e12..0bdaa8c0bd 100644 --- a/packages/react/src/themes/teams/components/Avatar/avatarVariables.ts +++ b/packages/react/src/themes/teams/components/Avatar/avatarVariables.ts @@ -1,13 +1,13 @@ export interface AvatarVariables { avatarBorderColor: string - avatarBorderWidth: number + avatarBorderWidth: string statusBorderColor: string - statusBorderWidth: number + statusBorderWidth: string } export default (siteVariables): AvatarVariables => ({ avatarBorderColor: '', - avatarBorderWidth: 0, + avatarBorderWidth: '0', statusBorderColor: siteVariables.bodyBackground, - statusBorderWidth: 2, + statusBorderWidth: '2px', }) From 6ff264c0bad2219e94d85a7772c4ad928bb4dfd6 Mon Sep 17 00:00:00 2001 From: Oleksandr Fediashov Date: Tue, 14 Jan 2020 17:06:01 +0100 Subject: [PATCH 10/24] fix tests for tests --- .../react-bindings/src/hooks/useStyles.ts | 5 ++- packages/react/src/components/Box/Box.tsx | 2 +- .../react/src/components/Button/Button.tsx | 33 ++++++++++--------- .../commonTests/handlesAccessibility.tsx | 14 +++++--- .../test/specs/commonTests/isConformant.tsx | 2 +- .../specs/components/Image/Image-test.tsx | 4 ++- 6 files changed, 35 insertions(+), 25 deletions(-) diff --git a/packages/react-bindings/src/hooks/useStyles.ts b/packages/react-bindings/src/hooks/useStyles.ts index 53528297b1..eb39e53434 100644 --- a/packages/react-bindings/src/hooks/useStyles.ts +++ b/packages/react-bindings/src/hooks/useStyles.ts @@ -64,13 +64,16 @@ const useStyles = ( // Stores debug information for component. const debug = React.useRef<{ fluentUIDebug: DebugData | null }>({ fluentUIDebug: null }) + const inlineProps = mapPropsToInlineStyles() + const { classes, styles: resolvedStyles } = getStyles({ // Input values className, displayName, props: { ...mapPropsToStyles(), - ...mapPropsToInlineStyles(), + ...inlineProps, + animation: inlineProps.unstable_animation, }, // Context values diff --git a/packages/react/src/components/Box/Box.tsx b/packages/react/src/components/Box/Box.tsx index dedbd5e7e0..d4e24ab823 100644 --- a/packages/react/src/components/Box/Box.tsx +++ b/packages/react/src/components/Box/Box.tsx @@ -57,7 +57,7 @@ const Box: React.FC> & FluentComponentStaticProps Box.className = 'ui-box' Box.displayName = 'Box' -Box.propTypes = commonPropTypes.createCommon({ animated: false, accessibility: false }) +Box.propTypes = commonPropTypes.createCommon({ accessibility: false }) Box.handledProps = Object.keys(Box.propTypes) as any Box.create = createShorthandFactory({ Component: Box }) diff --git a/packages/react/src/components/Button/Button.tsx b/packages/react/src/components/Button/Button.tsx index 8e7b4ebb29..88681b38b3 100644 --- a/packages/react/src/components/Button/Button.tsx +++ b/packages/react/src/components/Button/Button.tsx @@ -100,6 +100,7 @@ const Button: React.FC> & FluentComponentStaticProps & { Group: any } = props => { const { accessibility, + active, as, children, content, @@ -124,6 +125,22 @@ const Button: React.FC> & const context: ProviderContextPrepared = React.useContext(ThemeContext) const hasChildren = childrenExist(children) + const getA11Props = useAccessibility(accessibility, { + debugName: Button.displayName, + mapPropsToBehavior: () => ({ + as, + active, + disabled, + loading, + }), + actionHandlers: { + performClick: event => { + event.preventDefault() + handleClick(event) + }, + }, + rtl: context.rtl, + }) const [classes, resolvedStyles] = useStyles(Button.displayName, { className: Button.className, mapPropsToStyles: () => ({ @@ -148,22 +165,6 @@ const Button: React.FC> & rtl: context.rtl, }) - const getA11Props = useAccessibility(accessibility, { - debugName: Button.displayName, - mapPropsToBehavior: () => ({ - as, - disabled, - loading, - }), - actionHandlers: { - performClick: event => { - event.preventDefault() - handleClick(event) - }, - }, - rtl: context.rtl, - }) - const unhandledProps = getUnhandledProps(Button.handledProps, props) const ElementType = getElementType(props) diff --git a/packages/react/test/specs/commonTests/handlesAccessibility.tsx b/packages/react/test/specs/commonTests/handlesAccessibility.tsx index 15efdbc7d0..2209baf223 100644 --- a/packages/react/test/specs/commonTests/handlesAccessibility.tsx +++ b/packages/react/test/specs/commonTests/handlesAccessibility.tsx @@ -136,19 +136,23 @@ export default ( const wrapper = mountWithProvider() const component = wrapper.find(Component) const instance = component.instance() as UIComponent - if (instance.actionHandlers) { - instance.actionHandlers.mockAction = actionHandler + + if (instance) { + if (instance.actionHandlers) { + instance.actionHandlers.mockAction = actionHandler + } + // Force render component to apply updated key handlers + wrapper.setProps({}) } - // Force render component to apply updated key handlers - wrapper.setProps({}) getEventTargetComponent(component, 'onKeyDown').simulate('keydown', { keyCode: keyboardKey.Enter, }) - if (instance.actionHandlers) { + if (instance && instance.actionHandlers) { expect(actionHandler).toBeCalledTimes(1) } + expect(eventHandler).toBeCalledTimes(1) }) } diff --git a/packages/react/test/specs/commonTests/isConformant.tsx b/packages/react/test/specs/commonTests/isConformant.tsx index 2dcad584e8..979131e335 100644 --- a/packages/react/test/specs/commonTests/isConformant.tsx +++ b/packages/react/test/specs/commonTests/isConformant.tsx @@ -542,7 +542,7 @@ export default function isConformant( // ---------------------------------------- describe('static displayName (common)', () => { test('matches constructor name', () => { - expect(Component.displayName).toEqual(info.constructorName) + expect(Component.displayName).toEqual(constructorName) }) }) diff --git a/packages/react/test/specs/components/Image/Image-test.tsx b/packages/react/test/specs/components/Image/Image-test.tsx index 6d8c7cd8fc..ff7c14a120 100644 --- a/packages/react/test/specs/components/Image/Image-test.tsx +++ b/packages/react/test/specs/components/Image/Image-test.tsx @@ -5,7 +5,9 @@ import Image from 'src/components/Image/Image' import { mountWithProviderAndGetComponent } from 'test/utils' describe('Image', () => { - isConformant(Image) + isConformant(Image, { + constructorName: 'Image', + }) describe('accessibility', () => { handlesAccessibility(Image, { From 78c6234fea385e4a958e2f34953e4e2707a424f0 Mon Sep 17 00:00:00 2001 From: Oleksandr Fediashov Date: Tue, 14 Jan 2020 17:17:12 +0100 Subject: [PATCH 11/24] fix typings errors --- packages/react/src/components/Button/Button.tsx | 1 + .../teams-high-contrast/components/Avatar/avatarVariables.ts | 2 +- packages/react/src/types.ts | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/react/src/components/Button/Button.tsx b/packages/react/src/components/Button/Button.tsx index 88681b38b3..7971b3a853 100644 --- a/packages/react/src/components/Button/Button.tsx +++ b/packages/react/src/components/Button/Button.tsx @@ -100,6 +100,7 @@ const Button: React.FC> & FluentComponentStaticProps & { Group: any } = props => { const { accessibility, + // @ts-ignore active, as, children, diff --git a/packages/react/src/themes/teams-high-contrast/components/Avatar/avatarVariables.ts b/packages/react/src/themes/teams-high-contrast/components/Avatar/avatarVariables.ts index 08cf6cc6b9..047326ca10 100644 --- a/packages/react/src/themes/teams-high-contrast/components/Avatar/avatarVariables.ts +++ b/packages/react/src/themes/teams-high-contrast/components/Avatar/avatarVariables.ts @@ -2,6 +2,6 @@ import { AvatarVariables } from '../../../teams/components/Avatar/avatarVariable export default (siteVariables: any): Partial => ({ avatarBorderColor: siteVariables.colors.white, - avatarBorderWidth: 2, + avatarBorderWidth: '2px', statusBorderColor: siteVariables.colors.black, }) diff --git a/packages/react/src/types.ts b/packages/react/src/types.ts index 3c3e7a1d7a..167aa4b575 100644 --- a/packages/react/src/types.ts +++ b/packages/react/src/types.ts @@ -1,8 +1,8 @@ import { StylesContextInputValue, StylesContextValue } from '@fluentui/react-bindings' import * as React from 'react' +import { ShorthandFactory } from './utils/factories' import Telemetry from './utils/Telemetry' -import { ShorthandFactory } from 'src/utils' // ======================================================== // Utilities From 6d9c6056b6441f4f9804eed6770cf313babac09c Mon Sep 17 00:00:00 2001 From: Marija Najdova Date: Wed, 15 Jan 2020 15:19:39 +0100 Subject: [PATCH 12/24] -added useTelemetry hook and used it in the renderComponent function and Button component --- docs/src/prototypes/customToolbar/index.tsx | 3 +- packages/react-bindings/src/index.ts | 3 ++ .../src/telemetry/types.ts} | 2 +- .../src/telemetry/useTelemetry.ts | 42 +++++++++++++++++++ .../react/src/components/Button/Button.tsx | 5 +++ .../src/components/Provider/Provider.tsx | 3 +- packages/react/src/types.ts | 3 +- packages/react/src/utils/index.ts | 1 - packages/react/src/utils/renderComponent.tsx | 30 ++----------- 9 files changed, 59 insertions(+), 33 deletions(-) rename packages/{react/src/utils/Telemetry.ts => react-bindings/src/telemetry/types.ts} (89%) create mode 100644 packages/react-bindings/src/telemetry/useTelemetry.ts diff --git a/docs/src/prototypes/customToolbar/index.tsx b/docs/src/prototypes/customToolbar/index.tsx index f96c81865b..1943246677 100644 --- a/docs/src/prototypes/customToolbar/index.tsx +++ b/docs/src/prototypes/customToolbar/index.tsx @@ -1,13 +1,14 @@ import * as _ from 'lodash' import * as React from 'react' import { KnobsSnippet } from '@fluentui/code-sandbox' +import { Telemetry } from '@fluentui/react-bindings' import { KnobProvider, useBooleanKnob, useSelectKnob, KnobInspector, } from '@fluentui/docs-components' -import { Provider, Flex, themes, mergeThemes, Telemetry } from '@fluentui/react' +import { Provider, Flex, themes, mergeThemes } from '@fluentui/react' import { darkThemeOverrides } from './darkThemeOverrides' import { highContrastThemeOverrides } from './highContrastThemeOverrides' diff --git a/packages/react-bindings/src/index.ts b/packages/react-bindings/src/index.ts index c029805dbc..96d3dd8f22 100644 --- a/packages/react-bindings/src/index.ts +++ b/packages/react-bindings/src/index.ts @@ -18,5 +18,8 @@ export { default as unstable_createAnimationStyles } from './styles/createAnimat export { default as unstable_getStyles } from './styles/getStyles' export * from './styles/types' +export { default as useTelemetry } from './telemetry/useTelemetry' +export * from './telemetry/types' + export { default as getElementType } from './utils/getElementType' export { default as getUnhandledProps } from './utils/getUnhandledProps' diff --git a/packages/react/src/utils/Telemetry.ts b/packages/react-bindings/src/telemetry/types.ts similarity index 89% rename from packages/react/src/utils/Telemetry.ts rename to packages/react-bindings/src/telemetry/types.ts index f4a9792693..80aa7b37e0 100644 --- a/packages/react/src/utils/Telemetry.ts +++ b/packages/react-bindings/src/telemetry/types.ts @@ -5,7 +5,7 @@ type ComponentPerfStats = { msMax: number } -export default class Telemetry { +export class Telemetry { performance: Record enabled: boolean diff --git a/packages/react-bindings/src/telemetry/useTelemetry.ts b/packages/react-bindings/src/telemetry/useTelemetry.ts new file mode 100644 index 0000000000..5f3d2d8a0b --- /dev/null +++ b/packages/react-bindings/src/telemetry/useTelemetry.ts @@ -0,0 +1,42 @@ +import { Telemetry } from './types' + +const useTelemetry = (displayName: string, context: any) => { + const { telemetry = undefined as Telemetry | undefined } = context || {} + + let start: number = -1 + let end: number = -1 + + const setStart = () => { + start = telemetry && telemetry.enabled ? performance.now() : -1 + } + + const setEnd = () => { + if (telemetry && telemetry.enabled && start !== -1) { + end = performance.now() + const duration = end - start + if (telemetry.performance[displayName]) { + telemetry.performance[displayName].count++ + telemetry.performance[displayName].msTotal += duration + telemetry.performance[displayName].msMin = Math.min( + duration, + telemetry.performance[displayName].msMin, + ) + telemetry.performance[displayName].msMax = Math.max( + duration, + telemetry.performance[displayName].msMax, + ) + } else { + telemetry.performance[displayName] = { + count: 1, + msTotal: duration, + msMin: duration, + msMax: duration, + } + } + } + } + + return [setStart, setEnd] +} + +export default useTelemetry diff --git a/packages/react/src/components/Button/Button.tsx b/packages/react/src/components/Button/Button.tsx index 7971b3a853..b5951e0203 100644 --- a/packages/react/src/components/Button/Button.tsx +++ b/packages/react/src/components/Button/Button.tsx @@ -31,6 +31,7 @@ import { getUnhandledProps, useAccessibility, useStyles, + useTelemetry, } from '@fluentui/react-bindings' // @ts-ignore import { ThemeContext } from 'react-fela' @@ -124,8 +125,11 @@ const Button: React.FC> & design, } = props const context: ProviderContextPrepared = React.useContext(ThemeContext) + const [startTelemetry, endTelemetry] = useTelemetry(Button.displayName, context) const hasChildren = childrenExist(children) + startTelemetry() + const getA11Props = useAccessibility(accessibility, { debugName: Button.displayName, mapPropsToBehavior: () => ({ @@ -202,6 +206,7 @@ const Button: React.FC> & _.invoke(props, 'onFocus', e, props) } + endTelemetry() return ( { ElementType: React.ElementType

@@ -69,10 +69,10 @@ const renderComponent =

( logProviderMissingWarning() } - const { telemetry = undefined as Telemetry } = context || {} + const [telemetryStart, telemetryEnd] = useTelemetry(displayName, context) const rtl = context.rtl || false - const startTime = telemetry && telemetry.enabled ? performance.now() : 0 + telemetryStart() const ElementType = getElementType(props) as React.ReactType

const unhandledProps = getUnhandledProps(handledProps, props) @@ -109,29 +109,7 @@ const renderComponent =

( } let wrapInFocusZone: (element: React.ReactElement) => React.ReactElement = element => element - if (telemetry && telemetry.enabled) { - const duration = performance.now() - startTime - - if (telemetry.performance[displayName]) { - telemetry.performance[displayName].count++ - telemetry.performance[displayName].msTotal += duration - telemetry.performance[displayName].msMin = Math.min( - duration, - telemetry.performance[displayName].msMin, - ) - telemetry.performance[displayName].msMax = Math.max( - duration, - telemetry.performance[displayName].msMax, - ) - } else { - telemetry.performance[displayName] = { - count: 1, - msTotal: duration, - msMin: duration, - msMax: duration, - } - } - } + telemetryEnd() if (accessibility.focusZone && accessibility.focusZone.mode === FocusZoneMode.Wrap) { wrapInFocusZone = element => From c0d2eb7c4a012843603b3c12ea098bf980291b6c Mon Sep 17 00:00:00 2001 From: Marija Najdova Date: Wed, 15 Jan 2020 15:26:06 +0100 Subject: [PATCH 13/24] -improved typings --- packages/react-bindings/src/telemetry/useTelemetry.ts | 4 +--- packages/react/src/components/Button/Button.tsx | 2 +- packages/react/src/utils/renderComponent.tsx | 2 +- 3 files changed, 3 insertions(+), 5 deletions(-) diff --git a/packages/react-bindings/src/telemetry/useTelemetry.ts b/packages/react-bindings/src/telemetry/useTelemetry.ts index 5f3d2d8a0b..c96f1a5106 100644 --- a/packages/react-bindings/src/telemetry/useTelemetry.ts +++ b/packages/react-bindings/src/telemetry/useTelemetry.ts @@ -1,8 +1,6 @@ import { Telemetry } from './types' -const useTelemetry = (displayName: string, context: any) => { - const { telemetry = undefined as Telemetry | undefined } = context || {} - +const useTelemetry = (displayName: string, telemetry: Telemetry | undefined) => { let start: number = -1 let end: number = -1 diff --git a/packages/react/src/components/Button/Button.tsx b/packages/react/src/components/Button/Button.tsx index b5951e0203..62be4dca4b 100644 --- a/packages/react/src/components/Button/Button.tsx +++ b/packages/react/src/components/Button/Button.tsx @@ -125,7 +125,7 @@ const Button: React.FC> & design, } = props const context: ProviderContextPrepared = React.useContext(ThemeContext) - const [startTelemetry, endTelemetry] = useTelemetry(Button.displayName, context) + const [startTelemetry, endTelemetry] = useTelemetry(Button.displayName, context.telemetry) const hasChildren = childrenExist(children) startTelemetry() diff --git a/packages/react/src/utils/renderComponent.tsx b/packages/react/src/utils/renderComponent.tsx index c07c9712b1..cbfd444a32 100644 --- a/packages/react/src/utils/renderComponent.tsx +++ b/packages/react/src/utils/renderComponent.tsx @@ -69,7 +69,7 @@ const renderComponent =

( logProviderMissingWarning() } - const [telemetryStart, telemetryEnd] = useTelemetry(displayName, context) + const [telemetryStart, telemetryEnd] = useTelemetry(displayName, context.telemetry) const rtl = context.rtl || false telemetryStart() From c065a08c3043433303fd69a3e80e6eedc8cf7923 Mon Sep 17 00:00:00 2001 From: Marija Najdova Date: Wed, 15 Jan 2020 19:58:56 +0100 Subject: [PATCH 14/24] -changed return value of useTelemetry to be an object --- packages/react-bindings/src/telemetry/types.ts | 5 +++++ packages/react-bindings/src/telemetry/useTelemetry.ts | 9 ++++++--- packages/react/src/components/Button/Button.tsx | 7 ++++--- packages/react/src/utils/renderComponent.tsx | 6 +++--- 4 files changed, 18 insertions(+), 9 deletions(-) diff --git a/packages/react-bindings/src/telemetry/types.ts b/packages/react-bindings/src/telemetry/types.ts index 80aa7b37e0..ba43811b2f 100644 --- a/packages/react-bindings/src/telemetry/types.ts +++ b/packages/react-bindings/src/telemetry/types.ts @@ -5,6 +5,11 @@ type ComponentPerfStats = { msMax: number } +export type UseTelemetryReturnValue = { + setStart: () => void + setEnd: () => void +} + export class Telemetry { performance: Record enabled: boolean diff --git a/packages/react-bindings/src/telemetry/useTelemetry.ts b/packages/react-bindings/src/telemetry/useTelemetry.ts index c96f1a5106..00cc709888 100644 --- a/packages/react-bindings/src/telemetry/useTelemetry.ts +++ b/packages/react-bindings/src/telemetry/useTelemetry.ts @@ -1,6 +1,9 @@ -import { Telemetry } from './types' +import { Telemetry, UseTelemetryReturnValue } from './types' -const useTelemetry = (displayName: string, telemetry: Telemetry | undefined) => { +const useTelemetry = ( + displayName: string, + telemetry: Telemetry | undefined, +): UseTelemetryReturnValue => { let start: number = -1 let end: number = -1 @@ -34,7 +37,7 @@ const useTelemetry = (displayName: string, telemetry: Telemetry | undefined) => } } - return [setStart, setEnd] + return { setStart, setEnd } } export default useTelemetry diff --git a/packages/react/src/components/Button/Button.tsx b/packages/react/src/components/Button/Button.tsx index 62be4dca4b..3c3d66c7ad 100644 --- a/packages/react/src/components/Button/Button.tsx +++ b/packages/react/src/components/Button/Button.tsx @@ -125,10 +125,10 @@ const Button: React.FC> & design, } = props const context: ProviderContextPrepared = React.useContext(ThemeContext) - const [startTelemetry, endTelemetry] = useTelemetry(Button.displayName, context.telemetry) + const { setStart, setEnd } = useTelemetry(Button.displayName, context.telemetry) const hasChildren = childrenExist(children) - startTelemetry() + setStart() const getA11Props = useAccessibility(accessibility, { debugName: Button.displayName, @@ -206,7 +206,8 @@ const Button: React.FC> & _.invoke(props, 'onFocus', e, props) } - endTelemetry() + setEnd() + return ( ( logProviderMissingWarning() } - const [telemetryStart, telemetryEnd] = useTelemetry(displayName, context.telemetry) + const { setStart, setEnd } = useTelemetry(displayName, context.telemetry) const rtl = context.rtl || false - telemetryStart() + setStart() const ElementType = getElementType(props) as React.ReactType

const unhandledProps = getUnhandledProps(handledProps, props) @@ -109,7 +109,7 @@ const renderComponent =

( } let wrapInFocusZone: (element: React.ReactElement) => React.ReactElement = element => element - telemetryEnd() + setEnd() if (accessibility.focusZone && accessibility.focusZone.mode === FocusZoneMode.Wrap) { wrapInFocusZone = element => From 643af7364eafa3629965bbb37650d1ae3740014c Mon Sep 17 00:00:00 2001 From: Marija Najdova Date: Fri, 17 Jan 2020 11:02:52 +0100 Subject: [PATCH 15/24] -updated return value from the hooks --- packages/react-bindings/src/hooks/useStyles.ts | 9 +++++++-- packages/react-bindings/src/telemetry/types.ts | 2 +- packages/react-bindings/src/telemetry/useTelemetry.ts | 4 ++-- packages/react-bindings/test/hooks/useStyles-test.tsx | 2 +- packages/react/src/components/Avatar/Avatar.tsx | 2 +- packages/react/src/components/Box/Box.tsx | 2 +- packages/react/src/components/Button/Button.tsx | 2 +- packages/react/src/components/Image/Image.tsx | 2 +- 8 files changed, 15 insertions(+), 10 deletions(-) diff --git a/packages/react-bindings/src/hooks/useStyles.ts b/packages/react-bindings/src/hooks/useStyles.ts index eb39e53434..1992544e41 100644 --- a/packages/react-bindings/src/hooks/useStyles.ts +++ b/packages/react-bindings/src/hooks/useStyles.ts @@ -26,6 +26,11 @@ type UseStylesOptions = { rtl?: boolean } +type UseStylesResult = { + classes: ComponentSlotClasses + styles: ComponentSlotStylesPrepared +} + type InlineStyleProps = { unstable_animation?: ComponentAnimationProp @@ -51,7 +56,7 @@ const defaultContext: StylesContextValue<{ renderRule: RendererRenderRule }> = { const useStyles = ( displayName: string, options: UseStylesOptions, -): [ComponentSlotClasses, ComponentSlotStylesPrepared] => { +): UseStylesResult => { const context: StylesContextValue<{ renderRule: RendererRenderRule }> = React.useContext(ThemeContext) || defaultContext @@ -85,7 +90,7 @@ const useStyles = ( _internal_resolvedComponentVariables: context._internal_resolvedComponentVariables, }) - return [classes, resolvedStyles] + return { classes, styles: resolvedStyles } } export default useStyles diff --git a/packages/react-bindings/src/telemetry/types.ts b/packages/react-bindings/src/telemetry/types.ts index ba43811b2f..9ca3be5dbc 100644 --- a/packages/react-bindings/src/telemetry/types.ts +++ b/packages/react-bindings/src/telemetry/types.ts @@ -5,7 +5,7 @@ type ComponentPerfStats = { msMax: number } -export type UseTelemetryReturnValue = { +export type UseTelemetryResult = { setStart: () => void setEnd: () => void } diff --git a/packages/react-bindings/src/telemetry/useTelemetry.ts b/packages/react-bindings/src/telemetry/useTelemetry.ts index 00cc709888..440fbd766f 100644 --- a/packages/react-bindings/src/telemetry/useTelemetry.ts +++ b/packages/react-bindings/src/telemetry/useTelemetry.ts @@ -1,9 +1,9 @@ -import { Telemetry, UseTelemetryReturnValue } from './types' +import { Telemetry, UseTelemetryResult } from './types' const useTelemetry = ( displayName: string, telemetry: Telemetry | undefined, -): UseTelemetryReturnValue => { +): UseTelemetryResult => { let start: number = -1 let end: number = -1 diff --git a/packages/react-bindings/test/hooks/useStyles-test.tsx b/packages/react-bindings/test/hooks/useStyles-test.tsx index d203f67d7e..d97892855b 100644 --- a/packages/react-bindings/test/hooks/useStyles-test.tsx +++ b/packages/react-bindings/test/hooks/useStyles-test.tsx @@ -16,7 +16,7 @@ type TestComponentProps = { const TestComponent: React.FunctionComponent = props => { const { className, color, styles, variables } = props - const [classes] = useStyles('Test', { + const { classes } = useStyles('Test', { className: 'ui-test', mapPropsToStyles: () => ({ color }), mapPropsToInlineStyles: () => ({ className, styles, variables }), diff --git a/packages/react/src/components/Avatar/Avatar.tsx b/packages/react/src/components/Avatar/Avatar.tsx index 6ce633fa95..203197db7b 100644 --- a/packages/react/src/components/Avatar/Avatar.tsx +++ b/packages/react/src/components/Avatar/Avatar.tsx @@ -70,7 +70,7 @@ const Avatar: React.FC> & debugName: Avatar.displayName, rtl: context.rtl, }) - const [classes, resolvedStyles] = useStyles(Avatar.displayName, { + const { classes, styles: resolvedStyles } = useStyles(Avatar.displayName, { className: Avatar.className, mapPropsToStyles: () => ({ size }), mapPropsToInlineStyles: () => ({ diff --git a/packages/react/src/components/Box/Box.tsx b/packages/react/src/components/Box/Box.tsx index d4e24ab823..d28a67c2cc 100644 --- a/packages/react/src/components/Box/Box.tsx +++ b/packages/react/src/components/Box/Box.tsx @@ -28,7 +28,7 @@ const Box: React.FC> & FluentComponentStaticProps const { animation, className, design, styles, variables, children, content } = props const context: ProviderContextPrepared = React.useContext(ThemeContext) - const [classes] = useStyles(Box.displayName, { + const { classes } = useStyles(Box.displayName, { className: Box.className, mapPropsToInlineStyles: () => ({ unstable_animation: animation, diff --git a/packages/react/src/components/Button/Button.tsx b/packages/react/src/components/Button/Button.tsx index 3c3d66c7ad..13d90863f3 100644 --- a/packages/react/src/components/Button/Button.tsx +++ b/packages/react/src/components/Button/Button.tsx @@ -146,7 +146,7 @@ const Button: React.FC> & }, rtl: context.rtl, }) - const [classes, resolvedStyles] = useStyles(Button.displayName, { + const { classes, styles: resolvedStyles } = useStyles(Button.displayName, { className: Button.className, mapPropsToStyles: () => ({ text, diff --git a/packages/react/src/components/Image/Image.tsx b/packages/react/src/components/Image/Image.tsx index 65b17214c5..6abbf996a1 100644 --- a/packages/react/src/components/Image/Image.tsx +++ b/packages/react/src/components/Image/Image.tsx @@ -69,7 +69,7 @@ const Image: React.FC> & FluentComponentStaticProps ({ avatar, From 4a231df8c64093a9daadad47da6884dff9a1cf2f Mon Sep 17 00:00:00 2001 From: Marija Najdova Date: Fri, 17 Jan 2020 12:27:43 +0100 Subject: [PATCH 16/24] -wip - added cache --- .../react-bindings/src/hooks/useStyles.ts | 95 +++++++++++++++---- 1 file changed, 75 insertions(+), 20 deletions(-) diff --git a/packages/react-bindings/src/hooks/useStyles.ts b/packages/react-bindings/src/hooks/useStyles.ts index 1992544e41..4082ed177b 100644 --- a/packages/react-bindings/src/hooks/useStyles.ts +++ b/packages/react-bindings/src/hooks/useStyles.ts @@ -67,30 +67,85 @@ const useStyles = ( rtl = false, } = options + let classes + let resolvedStyles + + const styleProps = mapPropsToStyles() + const inlineStyleOverrides = mapPropsToInlineStyles() + + // TODO I don't care for now + const noInlineStylesOverrides = Object.values(inlineStyleOverrides).every( + x => x === null || x === undefined, + ) + // Stores debug information for component. const debug = React.useRef<{ fluentUIDebug: DebugData | null }>({ fluentUIDebug: null }) - const inlineProps = mapPropsToInlineStyles() - - const { classes, styles: resolvedStyles } = getStyles({ - // Input values - className, - displayName, - props: { - ...mapPropsToStyles(), - ...inlineProps, - animation: inlineProps.unstable_animation, - }, - - // Context values - disableAnimations: context.disableAnimations, - renderer: context.renderer, - rtl, - saveDebug: fluentUIDebug => (debug.current = { fluentUIDebug }), - theme: context.theme, - _internal_resolvedComponentVariables: context._internal_resolvedComponentVariables, - }) + + if (noInlineStylesOverrides) { + // get value from cache + if (!useStyles.cache.get(context.theme)) { + useStyles.cache.set(context.theme, {}) + } + + const themeCache = useStyles.cache.get(context.theme) + + if (!themeCache[displayName]) { + themeCache[displayName] = {} + } + + const propsCache = + JSON.stringify(styleProps) + + JSON.stringify({ rtl, disableAnimations: context.disableAnimations }) + + if (!themeCache[displayName][propsCache]) { + themeCache[displayName][propsCache] = getStyles({ + // Input values + className, + displayName, + props: styleProps, + + // Context values + disableAnimations: context.disableAnimations, + renderer: context.renderer, // TODO: this should invalidate the cache + rtl, + saveDebug: fluentUIDebug => (debug.current = { fluentUIDebug }), + theme: context.theme, + _internal_resolvedComponentVariables: context._internal_resolvedComponentVariables, + }) + } + + const result = themeCache[displayName][propsCache] + classes = result.classes + resolvedStyles = result.styles + } else { + const inlineProps = mapPropsToInlineStyles() + + const result = getStyles({ + // Input values + className, + displayName, + props: { + ...mapPropsToStyles(), + ...inlineProps, + animation: inlineProps.unstable_animation, + }, + + // Context values + disableAnimations: context.disableAnimations, + renderer: context.renderer, + rtl, + saveDebug: fluentUIDebug => (debug.current = { fluentUIDebug }), + theme: context.theme, + _internal_resolvedComponentVariables: context._internal_resolvedComponentVariables, + }) + + classes = result.classes + resolvedStyles = result.styles + } return { classes, styles: resolvedStyles } } +useStyles.cache = new WeakMap() + export default useStyles From bde4b3a4b256114214213b07900207a220cdede2 Mon Sep 17 00:00:00 2001 From: Marija Najdova Date: Fri, 17 Jan 2020 12:29:32 +0100 Subject: [PATCH 17/24] -wip --- packages/react-bindings/src/hooks/useStyles.ts | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/packages/react-bindings/src/hooks/useStyles.ts b/packages/react-bindings/src/hooks/useStyles.ts index 4082ed177b..3ed5ea6eda 100644 --- a/packages/react-bindings/src/hooks/useStyles.ts +++ b/packages/react-bindings/src/hooks/useStyles.ts @@ -118,16 +118,14 @@ const useStyles = ( classes = result.classes resolvedStyles = result.styles } else { - const inlineProps = mapPropsToInlineStyles() - const result = getStyles({ // Input values className, displayName, props: { - ...mapPropsToStyles(), - ...inlineProps, - animation: inlineProps.unstable_animation, + ...styleProps, + ...inlineStyleOverrides, + animation: inlineStyleOverrides.unstable_animation, }, // Context values From 331adcc722ca187832d2d108c00e3a802874d110 Mon Sep 17 00:00:00 2001 From: Marija Najdova Date: Fri, 17 Jan 2020 15:35:15 +0100 Subject: [PATCH 18/24] -removed unstable animation --- packages/react/src/components/Avatar/Avatar.tsx | 2 -- packages/react/src/components/Button/Button.tsx | 2 -- packages/react/src/components/Image/Image.tsx | 2 -- 3 files changed, 6 deletions(-) diff --git a/packages/react/src/components/Avatar/Avatar.tsx b/packages/react/src/components/Avatar/Avatar.tsx index 203197db7b..9bcf6b8786 100644 --- a/packages/react/src/components/Avatar/Avatar.tsx +++ b/packages/react/src/components/Avatar/Avatar.tsx @@ -52,7 +52,6 @@ const Avatar: React.FC> & FluentComponentStaticProps = props => { const { accessibility, - animation, className, design, getInitials, @@ -74,7 +73,6 @@ const Avatar: React.FC> & className: Avatar.className, mapPropsToStyles: () => ({ size }), mapPropsToInlineStyles: () => ({ - unstable_animation: animation, className, design, styles, diff --git a/packages/react/src/components/Button/Button.tsx b/packages/react/src/components/Button/Button.tsx index 13d90863f3..5988b8d290 100644 --- a/packages/react/src/components/Button/Button.tsx +++ b/packages/react/src/components/Button/Button.tsx @@ -121,7 +121,6 @@ const Button: React.FC> & className, styles, variables, - animation, design, } = props const context: ProviderContextPrepared = React.useContext(ThemeContext) @@ -161,7 +160,6 @@ const Button: React.FC> & hasContent: !!content, }), mapPropsToInlineStyles: () => ({ - unstable_animation: animation, className, design, styles, diff --git a/packages/react/src/components/Image/Image.tsx b/packages/react/src/components/Image/Image.tsx index 6abbf996a1..43c7cb5639 100644 --- a/packages/react/src/components/Image/Image.tsx +++ b/packages/react/src/components/Image/Image.tsx @@ -49,7 +49,6 @@ const Image: React.FC> & FluentComponentStaticProps> & FluentComponentStaticProps ({ - unstable_animation: animation, className, design, styles, From 7786bd020ac01622b69ef165c6131e9746933abe Mon Sep 17 00:00:00 2001 From: Marija Najdova Date: Fri, 17 Jan 2020 15:54:48 +0100 Subject: [PATCH 19/24] -fixes --- packages/react/src/components/Box/Box.tsx | 3 +-- .../react/src/components/Button/Button.tsx | 23 ++++++++++++------- 2 files changed, 16 insertions(+), 10 deletions(-) diff --git a/packages/react/src/components/Box/Box.tsx b/packages/react/src/components/Box/Box.tsx index d28a67c2cc..024a5ff476 100644 --- a/packages/react/src/components/Box/Box.tsx +++ b/packages/react/src/components/Box/Box.tsx @@ -25,13 +25,12 @@ export interface BoxProps ChildrenComponentProps {} const Box: React.FC> & FluentComponentStaticProps = props => { - const { animation, className, design, styles, variables, children, content } = props + const { className, design, styles, variables, children, content } = props const context: ProviderContextPrepared = React.useContext(ThemeContext) const { classes } = useStyles(Box.displayName, { className: Box.className, mapPropsToInlineStyles: () => ({ - unstable_animation: animation, className, design, styles, diff --git a/packages/react/src/components/Button/Button.tsx b/packages/react/src/components/Button/Button.tsx index 5988b8d290..65451cec94 100644 --- a/packages/react/src/components/Button/Button.tsx +++ b/packages/react/src/components/Button/Button.tsx @@ -125,10 +125,11 @@ const Button: React.FC> & } = props const context: ProviderContextPrepared = React.useContext(ThemeContext) const { setStart, setEnd } = useTelemetry(Button.displayName, context.telemetry) - const hasChildren = childrenExist(children) setStart() + const hasChildren = childrenExist(children) + const getA11Props = useAccessibility(accessibility, { debugName: Button.displayName, mapPropsToBehavior: () => ({ @@ -217,13 +218,19 @@ const Button: React.FC> & ...unhandledProps, })} > - {hasChildren && children} - {!hasChildren && loading && renderLoader()} - {!hasChildren && iconPosition !== 'after' && renderIcon()} - {Box.create(!hasChildren && content, { - defaultProps: () => getA11Props('content', { as: 'span', styles: resolvedStyles.content }), - })} - {!hasChildren && iconPosition === 'after' && renderIcon()} + {hasChildren ? ( + children + ) : ( + <> + {loading && renderLoader()} + {iconPosition !== 'after' && renderIcon()} + {Box.create(content, { + defaultProps: () => + getA11Props('content', { as: 'span', styles: resolvedStyles.content }), + })} + {iconPosition === 'after' && renderIcon()} + + )} ) } From e46ab21d10802214563301e9abb3d06e3bd632b3 Mon Sep 17 00:00:00 2001 From: Marija Najdova Date: Fri, 17 Jan 2020 16:17:46 +0100 Subject: [PATCH 20/24] -updated changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index aad55303fe..76ecda55ec 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,6 +20,7 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm ### BREAKING CHANGES - Add `@fluentui/styles` package for all styles' related utilities and TS types @layershifter, @mnajdova ([#2222](https://github.com/microsoft/fluent-ui-react/pull/2222)) - Remove `animation` prop from all components @joschect ([#2239](https://github.com/microsoft/fluent-ui-react/pull/2239)) +- Changed `avatarBorderWidth` and `statusBorderWidth` avatar variables types from number to string and updated styles in Teams theme @mnajdova ([#2238](https://github.com/microsoft/fluent-ui-react/pull/2238)) ### Fixes - Fix event listener leak in `FocusZone` @miroslavstastny ([#2227](https://github.com/microsoft/fluent-ui-react/pull/2227)) From c31d3f47384a9accf72c066e442a6fdae6f00202 Mon Sep 17 00:00:00 2001 From: Marija Najdova Date: Fri, 17 Jan 2020 16:37:02 +0100 Subject: [PATCH 21/24] -added telemetry to all components --- packages/react/src/components/Avatar/Avatar.tsx | 8 +++++++- packages/react/src/components/Box/Box.tsx | 14 ++++++++++++-- packages/react/src/components/Button/Button.tsx | 1 - packages/react/src/components/Image/Image.tsx | 8 +++++++- 4 files changed, 26 insertions(+), 5 deletions(-) diff --git a/packages/react/src/components/Avatar/Avatar.tsx b/packages/react/src/components/Avatar/Avatar.tsx index 9bcf6b8786..a17ac7ffd7 100644 --- a/packages/react/src/components/Avatar/Avatar.tsx +++ b/packages/react/src/components/Avatar/Avatar.tsx @@ -4,6 +4,7 @@ import { getUnhandledProps, useAccessibility, useStyles, + useTelemetry, } from '@fluentui/react-bindings' import * as customPropTypes from '@fluentui/react-proptypes' import * as PropTypes from 'prop-types' @@ -50,6 +51,10 @@ export interface AvatarProps extends UIComponentProps { const Avatar: React.FC> & FluentComponentStaticProps = props => { + const context: ProviderContextPrepared = React.useContext(ThemeContext) + const { setStart, setEnd } = useTelemetry(Avatar.displayName, context.telemetry) + setStart() + const { accessibility, className, @@ -63,7 +68,6 @@ const Avatar: React.FC> & styles, variables, } = props - const context: ProviderContextPrepared = React.useContext(ThemeContext) const getA11Props = useAccessibility(accessibility, { debugName: Avatar.displayName, @@ -83,6 +87,8 @@ const Avatar: React.FC> & const ElementType = getElementType(props) const unhandledProps = getUnhandledProps(Avatar.handledProps, props) + setEnd() + return ( {Image.create(image, { diff --git a/packages/react/src/components/Box/Box.tsx b/packages/react/src/components/Box/Box.tsx index 024a5ff476..d716b9be1b 100644 --- a/packages/react/src/components/Box/Box.tsx +++ b/packages/react/src/components/Box/Box.tsx @@ -1,4 +1,9 @@ -import { getElementType, getUnhandledProps, useStyles } from '@fluentui/react-bindings' +import { + getElementType, + getUnhandledProps, + useStyles, + useTelemetry, +} from '@fluentui/react-bindings' import * as React from 'react' // @ts-ignore import { ThemeContext } from 'react-fela' @@ -25,9 +30,12 @@ export interface BoxProps ChildrenComponentProps {} const Box: React.FC> & FluentComponentStaticProps = props => { + const context: ProviderContextPrepared = React.useContext(ThemeContext) + const { setStart, setEnd } = useTelemetry(Box.displayName, context.telemetry) + setStart() + const { className, design, styles, variables, children, content } = props - const context: ProviderContextPrepared = React.useContext(ThemeContext) const { classes } = useStyles(Box.displayName, { className: Box.className, mapPropsToInlineStyles: () => ({ @@ -42,6 +50,8 @@ const Box: React.FC> & FluentComponentStaticProps const unhandledProps = getUnhandledProps(Box.handledProps, props) const ElementType = getElementType(props) + setEnd() + return ( > & } = props const context: ProviderContextPrepared = React.useContext(ThemeContext) const { setStart, setEnd } = useTelemetry(Button.displayName, context.telemetry) - setStart() const hasChildren = childrenExist(children) diff --git a/packages/react/src/components/Image/Image.tsx b/packages/react/src/components/Image/Image.tsx index 43c7cb5639..98dce977f0 100644 --- a/packages/react/src/components/Image/Image.tsx +++ b/packages/react/src/components/Image/Image.tsx @@ -9,6 +9,7 @@ import { getUnhandledProps, useAccessibility, useStyles, + useTelemetry, } from '@fluentui/react-bindings' import * as PropTypes from 'prop-types' import * as React from 'react' @@ -46,6 +47,10 @@ export interface ImageProps extends UIComponentProps, ImageBehaviorProps { } const Image: React.FC> & FluentComponentStaticProps = props => { + const context: ProviderContextPrepared = React.useContext(ThemeContext) + const { setStart, setEnd } = useTelemetry(Image.displayName, context.telemetry) + setStart() + const { accessibility, alt, @@ -58,7 +63,6 @@ const Image: React.FC> & FluentComponentStaticProps> & FluentComponentStaticProps } From 118624d32a970fe33e5a5be776f54acd53c03801 Mon Sep 17 00:00:00 2001 From: Marija Najdova Date: Fri, 17 Jan 2020 16:54:59 +0100 Subject: [PATCH 22/24] -fixed telemetry info --- packages/react/src/components/Avatar/Avatar.tsx | 8 +++++--- packages/react/src/components/Box/Box.tsx | 8 +++++--- packages/react/src/components/Button/Button.tsx | 8 +++++--- packages/react/src/components/Image/Image.tsx | 6 +++++- packages/react/src/utils/renderComponent.tsx | 8 +++++--- 5 files changed, 25 insertions(+), 13 deletions(-) diff --git a/packages/react/src/components/Avatar/Avatar.tsx b/packages/react/src/components/Avatar/Avatar.tsx index a17ac7ffd7..bf50e2cc18 100644 --- a/packages/react/src/components/Avatar/Avatar.tsx +++ b/packages/react/src/components/Avatar/Avatar.tsx @@ -87,9 +87,7 @@ const Avatar: React.FC> & const ElementType = getElementType(props) const unhandledProps = getUnhandledProps(Avatar.handledProps, props) - setEnd() - - return ( + const result = ( {Image.create(image, { defaultProps: () => @@ -119,6 +117,10 @@ const Avatar: React.FC> & })} ) + + setEnd() + + return result } Avatar.className = 'ui-avatar' diff --git a/packages/react/src/components/Box/Box.tsx b/packages/react/src/components/Box/Box.tsx index d716b9be1b..a4b9c8c33b 100644 --- a/packages/react/src/components/Box/Box.tsx +++ b/packages/react/src/components/Box/Box.tsx @@ -50,9 +50,7 @@ const Box: React.FC> & FluentComponentStaticProps const unhandledProps = getUnhandledProps(Box.handledProps, props) const ElementType = getElementType(props) - setEnd() - - return ( + const result = ( > & FluentComponentStaticProps {childrenExist(children) ? children : content} ) + + setEnd() + + return result } Box.className = 'ui-box' diff --git a/packages/react/src/components/Button/Button.tsx b/packages/react/src/components/Button/Button.tsx index 6cd515afce..e17578569d 100644 --- a/packages/react/src/components/Button/Button.tsx +++ b/packages/react/src/components/Button/Button.tsx @@ -204,9 +204,7 @@ const Button: React.FC> & _.invoke(props, 'onFocus', e, props) } - setEnd() - - return ( + const result = ( > & )} ) + + setEnd() + + return result } Button.defaultProps = { diff --git a/packages/react/src/components/Image/Image.tsx b/packages/react/src/components/Image/Image.tsx index 98dce977f0..805bc7a780 100644 --- a/packages/react/src/components/Image/Image.tsx +++ b/packages/react/src/components/Image/Image.tsx @@ -91,9 +91,13 @@ const Image: React.FC> & FluentComponentStaticProps + ) + setEnd() - return + return result } Image.className = 'ui-image' diff --git a/packages/react/src/utils/renderComponent.tsx b/packages/react/src/utils/renderComponent.tsx index 8487acdac8..369c401818 100644 --- a/packages/react/src/utils/renderComponent.tsx +++ b/packages/react/src/utils/renderComponent.tsx @@ -109,8 +109,6 @@ const renderComponent =

( } let wrapInFocusZone: (element: React.ReactElement) => React.ReactElement = element => element - setEnd() - if (accessibility.focusZone && accessibility.focusZone.mode === FocusZoneMode.Wrap) { wrapInFocusZone = element => React.createElement( @@ -136,7 +134,11 @@ const renderComponent =

( resolvedConfig.unhandledProps.isRtl = resolvedConfig.rtl } - return wrapInFocusZone(render(resolvedConfig)) + const result = wrapInFocusZone(render(resolvedConfig)) + + setEnd() + + return result } export default renderComponent From bed3659d4b9075eac5d30615be1b600299828e31 Mon Sep 17 00:00:00 2001 From: Marija Najdova Date: Thu, 23 Jan 2020 15:14:20 +0100 Subject: [PATCH 23/24] -fixed Button telemetry start --- packages/react/src/components/Button/Button.tsx | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/packages/react/src/components/Button/Button.tsx b/packages/react/src/components/Button/Button.tsx index e17578569d..cb70564f4c 100644 --- a/packages/react/src/components/Button/Button.tsx +++ b/packages/react/src/components/Button/Button.tsx @@ -99,6 +99,10 @@ export interface ButtonProps const Button: React.FC> & FluentComponentStaticProps & { Group: any } = props => { + const context: ProviderContextPrepared = React.useContext(ThemeContext) + const { setStart, setEnd } = useTelemetry(Button.displayName, context.telemetry) + setStart() + const { accessibility, // @ts-ignore @@ -123,9 +127,6 @@ const Button: React.FC> & variables, design, } = props - const context: ProviderContextPrepared = React.useContext(ThemeContext) - const { setStart, setEnd } = useTelemetry(Button.displayName, context.telemetry) - setStart() const hasChildren = childrenExist(children) From e2108eb9dfc4dcb26c2745ee5d769edde2d6f0f2 Mon Sep 17 00:00:00 2001 From: Marija Najdova Date: Fri, 24 Jan 2020 12:40:43 +0100 Subject: [PATCH 24/24] -improved typings --- .../react/src/components/Button/Button.tsx | 2 +- .../components/Button/buttonStyles.ts | 6 ++--- .../teams/components/Avatar/avatarStyles.ts | 5 ++++- .../teams/components/Button/buttonStyles.ts | 22 ++++++++++++++----- .../teams/components/Image/imageStyles.ts | 9 ++++++-- .../teams/components/Image/imageVariables.ts | 10 ++++++++- packages/react/src/themes/teams/types.ts | 12 +++++----- 7 files changed, 47 insertions(+), 19 deletions(-) diff --git a/packages/react/src/components/Button/Button.tsx b/packages/react/src/components/Button/Button.tsx index cb70564f4c..8278b95c4d 100644 --- a/packages/react/src/components/Button/Button.tsx +++ b/packages/react/src/components/Button/Button.tsx @@ -98,7 +98,7 @@ export interface ButtonProps } const Button: React.FC> & - FluentComponentStaticProps & { Group: any } = props => { + FluentComponentStaticProps & { Group: typeof ButtonGroup } = props => { const context: ProviderContextPrepared = React.useContext(ThemeContext) const { setStart, setEnd } = useTelemetry(Button.displayName, context.telemetry) setStart() diff --git a/packages/react/src/themes/teams-high-contrast/components/Button/buttonStyles.ts b/packages/react/src/themes/teams-high-contrast/components/Button/buttonStyles.ts index 5615392659..e362fe06a1 100644 --- a/packages/react/src/themes/teams-high-contrast/components/Button/buttonStyles.ts +++ b/packages/react/src/themes/teams-high-contrast/components/Button/buttonStyles.ts @@ -1,13 +1,13 @@ import { ComponentSlotStylesPrepared, ICSSInJSStyle } from '@fluentui/styles' -import { ButtonProps } from '../../../../components/Button/Button' +import { ButtonStylesProps } from '../../../teams/components/Button/buttonStyles' import { ButtonVariables } from '../../../teams/components/Button/buttonVariables' import { ButtonHighContrastVariables } from './buttonVariables' const buttonStyles: ComponentSlotStylesPrepared< - ButtonProps, + ButtonStylesProps, ButtonVariables & ButtonHighContrastVariables > = { - root: ({ props: p, variables: v, theme: { siteVariables } }): ICSSInJSStyle => { + root: ({ props: p, variables: v }): ICSSInJSStyle => { return { // rectangular button defaults ...(!p.text && { diff --git a/packages/react/src/themes/teams/components/Avatar/avatarStyles.ts b/packages/react/src/themes/teams/components/Avatar/avatarStyles.ts index f2edd75675..cd88754beb 100644 --- a/packages/react/src/themes/teams/components/Avatar/avatarStyles.ts +++ b/packages/react/src/themes/teams/components/Avatar/avatarStyles.ts @@ -1,6 +1,9 @@ import { pxToRem } from '../../../../utils' import { ComponentSlotStylesPrepared, ICSSInJSStyle } from '@fluentui/styles' import { AvatarProps } from '../../../../components/Avatar/Avatar' +import { AvatarVariables } from './avatarVariables' + +export type AvatarStylesProps = Pick const sizeToPxValue = { smallest: 24, @@ -12,7 +15,7 @@ const sizeToPxValue = { largest: 48, } -const avatarStyles: ComponentSlotStylesPrepared = { +const avatarStyles: ComponentSlotStylesPrepared = { root: ({ props: { size } }): ICSSInJSStyle => { const sizeInRem = pxToRem(sizeToPxValue[size]) diff --git a/packages/react/src/themes/teams/components/Button/buttonStyles.ts b/packages/react/src/themes/teams/components/Button/buttonStyles.ts index 79af431d06..b87518eccf 100644 --- a/packages/react/src/themes/teams/components/Button/buttonStyles.ts +++ b/packages/react/src/themes/teams/components/Button/buttonStyles.ts @@ -8,10 +8,22 @@ import { ButtonVariables } from './buttonVariables' import getBorderFocusStyles from '../../getBorderFocusStyles' import getIconFillOrOutlineStyles from '../../getIconFillOrOutlineStyles' -const buttonStyles: ComponentSlotStylesPrepared< - ButtonProps & { hasContent?: boolean }, - ButtonVariables -> = { +export type ButtonStylesProps = Pick< + ButtonProps, + | 'text' + | 'primary' + | 'disabled' + | 'circular' + | 'size' + | 'loading' + | 'inverted' + | 'iconOnly' + | 'fluid' +> & { + hasContent?: boolean +} + +const buttonStyles: ComponentSlotStylesPrepared = { root: ({ props: p, variables: v, theme }): ICSSInJSStyle => { const { siteVariables } = theme const { borderWidth } = siteVariables @@ -239,7 +251,7 @@ const buttonStyles: ComponentSlotStylesPrepared< }), }), - icon: ({ props: p, variables: v }) => ({ + icon: ({ props: p }) => ({ // when loading, hide the icon ...(p.loading && { margin: 0, diff --git a/packages/react/src/themes/teams/components/Image/imageStyles.ts b/packages/react/src/themes/teams/components/Image/imageStyles.ts index 01b1ed3a8a..f23037f521 100644 --- a/packages/react/src/themes/teams/components/Image/imageStyles.ts +++ b/packages/react/src/themes/teams/components/Image/imageStyles.ts @@ -1,7 +1,10 @@ import { ComponentSlotStylesPrepared, ICSSInJSStyle } from '@fluentui/styles' import { ImageProps } from '../../../../components/Image/Image' +import { ImageVariables } from './imageVariables' -export default { +export type ImageStylesProps = Pick + +const imageStyles: ComponentSlotStylesPrepared = { root: ({ props, variables }): ICSSInJSStyle => ({ boxSizing: 'border-box', display: 'inline-block', @@ -14,4 +17,6 @@ export default { borderRadius: variables.avatarRadius, }), }), -} as ComponentSlotStylesPrepared +} + +export default imageStyles diff --git a/packages/react/src/themes/teams/components/Image/imageVariables.ts b/packages/react/src/themes/teams/components/Image/imageVariables.ts index 580023877d..e967a17799 100644 --- a/packages/react/src/themes/teams/components/Image/imageVariables.ts +++ b/packages/react/src/themes/teams/components/Image/imageVariables.ts @@ -1,6 +1,14 @@ import { pxToRem } from '../../../../utils' -export default () => ({ +export type ImageVariables = { + width?: string + height?: string + avatarRadius: string + avatarSize: string + circularRadius: string +} + +export default (): ImageVariables => ({ width: undefined, height: undefined, avatarRadius: pxToRem(9999), diff --git a/packages/react/src/themes/teams/types.ts b/packages/react/src/themes/teams/types.ts index f3f20fa2f3..a93507b1db 100644 --- a/packages/react/src/themes/teams/types.ts +++ b/packages/react/src/themes/teams/types.ts @@ -13,9 +13,9 @@ import { AccordionTitleProps } from '../../components/Accordion/AccordionTitle' import { AlertProps } from '../../components/Alert/Alert' import { AnimationProps } from '../../components/Animation/Animation' import { AttachmentProps } from '../../components/Attachment/Attachment' -import { AvatarProps } from '../../components/Avatar/Avatar' +import { AvatarStylesProps } from './components/Avatar/avatarStyles' import { ButtonGroupProps } from '../../components/Button/ButtonGroup' -import { ButtonProps } from '../../components/Button/Button' +import { ButtonStylesProps } from './components/Button/buttonStyles' import { ChatItemProps } from '../../components/Chat/ChatItem' import { ChatMessageProps } from '../../components/Chat/ChatMessage' import { ChatProps } from '../../components/Chat/Chat' @@ -31,7 +31,7 @@ import { GridProps } from '../../components/Grid/Grid' import { HeaderDescriptionProps } from '../../components/Header/HeaderDescription' import { HeaderProps } from '../../components/Header/Header' import { IconProps } from '../../components/Icon/Icon' -import { ImageProps } from '../../components/Image/Image' +import { ImageStylesProps } from './components/Image/imageStyles' import { InputProps } from '../../components/Input/Input' import { ItemLayoutProps } from '../../components/ItemLayout/ItemLayout' import { LabelProps } from '../../components/Label/Label' @@ -70,8 +70,8 @@ export type TeamsThemeStylesProps = { Alert?: AlertProps Animation?: AnimationProps Attachment?: AttachmentProps - Avatar?: AvatarProps - Button?: ButtonProps + Avatar?: AvatarStylesProps + Button?: ButtonStylesProps ButtonGroup?: ButtonGroupProps Chat?: ChatProps ChatItem?: ChatItemProps @@ -88,7 +88,7 @@ export type TeamsThemeStylesProps = { Header?: HeaderProps HeaderDescription?: HeaderDescriptionProps Icon?: IconProps - Image?: ImageProps + Image?: ImageStylesProps Input?: InputProps ItemLayout?: ItemLayoutProps Label?: LabelProps