Skip to content
This repository was archived by the owner on Mar 4, 2020. It is now read-only.

chore(Box|Button|Image|Avatar): converting components with hooks #2238

Merged
merged 30 commits into from
Jan 24, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
327a755
-converted Box component to use hooks
mnajdova Jan 14, 2020
cbe0c44
-fix Box props
mnajdova Jan 14, 2020
1dad61e
temporary support for animation
layershifter Jan 14, 2020
6cc8543
convert Image to hooks
layershifter Jan 14, 2020
1deef88
-converted Button component to use hooks
mnajdova Jan 14, 2020
ca915a1
convert Avatar to hooks and update isConformant test
layershifter Jan 14, 2020
b4453b8
Merge branch 'feat/use-hooks-in-box-component' of https://github.com/…
layershifter Jan 14, 2020
76d1be4
apply attributes to Avatar slots
layershifter Jan 14, 2020
ad41491
-fixed Button test
mnajdova Jan 14, 2020
31f7015
update Avatar to avoid variables usage
layershifter Jan 14, 2020
b4768dd
Merge branch 'feat/use-hooks-in-box-component' of https://github.com/…
layershifter Jan 14, 2020
6ff264c
fix tests for tests
layershifter Jan 14, 2020
78c6234
fix typings errors
layershifter Jan 14, 2020
6d9c605
-added useTelemetry hook and used it in the renderComponent function …
mnajdova Jan 15, 2020
c0d2eb7
-improved typings
mnajdova Jan 15, 2020
c065a08
-changed return value of useTelemetry to be an object
mnajdova Jan 15, 2020
643af73
-updated return value from the hooks
mnajdova Jan 17, 2020
77397b0
Merge branch 'master' into feat/use-hooks-in-box-component
mnajdova Jan 17, 2020
4a231df
-wip - added cache
mnajdova Jan 17, 2020
bde4b3a
-wip
mnajdova Jan 17, 2020
bf22378
Merge branch 'master' into feat/use-hooks-in-box-component
mnajdova Jan 17, 2020
7605698
Merge branch 'master' into feat/use-hooks-in-box-component
mnajdova Jan 17, 2020
331adcc
-removed unstable animation
mnajdova Jan 17, 2020
7786bd0
-fixes
mnajdova Jan 17, 2020
e46ab21
-updated changelog
mnajdova Jan 17, 2020
c31d3f4
-added telemetry to all components
mnajdova Jan 17, 2020
118624d
-fixed telemetry info
mnajdova Jan 17, 2020
bed3659
-fixed Button telemetry start
mnajdova Jan 23, 2020
d67e9a7
Merge branch 'master' into feat/use-hooks-in-box-component
mnajdova Jan 24, 2020
e2108eb
-improved typings
mnajdova Jan 24, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm
- Remove `animation` prop from all components @joschect ([#2239](https://github.com/microsoft/fluent-ui-react/pull/2239))
- `mode` property from `focusZone` configuration in accessibility behaviors is no longer supported - the focus zone will always be in embed mode @layershifter ([#2265](https://github.com/microsoft/fluent-ui-react/pull/2265))
- `FocusZoneMode` and `FOCUSZONE_WRAP_ATTRIBUTE` are no longer exported @layershifter ([#2265](https://github.com/microsoft/fluent-ui-react/pull/2265))
- 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))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ const imageBehavior: Accessibility<ImageBehaviorProps> = props => ({

export default imageBehavior

type ImageBehaviorProps = {
export type ImageBehaviorProps = {
/** Alternative text. */
alt?: string
} & Pick<AccessibilityAttributes, 'aria-label'>
1 change: 1 addition & 0 deletions packages/accessibility/src/behaviors/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down
16 changes: 5 additions & 11 deletions packages/react-proptypes/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ This change is correct as all behaviors are functions


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'])

Expand Down
197 changes: 120 additions & 77 deletions packages/react/src/components/Avatar/Avatar.tsx
Original file line number Diff line number Diff line change
@@ -1,25 +1,34 @@
import { Accessibility } from '@fluentui/accessibility'
import {
getElementType,
getUnhandledProps,
useAccessibility,
useStyles,
useTelemetry,
} 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'
Copy link
Member

@dzearing dzearing Jan 15, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Avatar.tsx should not refer to fela specifics directly. @jdhuntington for heads up here.

We should expect the base component to simply take in a classes prop. So, Avatar renders DOM and injects the right classes.

Then createComponent can abstract how classes gets injected.

export const Avatar = createComponent(AvatarBase, { stuff to build `classes` attribute });

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What do you think?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's not Fela specifics. We use this context only by historical reasons. We should use own context instead.


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<never>

/** Shorthand for the image. */
image?: ShorthandValue<ImageProps>
Expand All @@ -40,87 +49,121 @@ export interface AvatarProps extends UIComponentProps {
getInitials?: (name: string) => string
}

class Avatar extends UIComponent<WithAsProp<AvatarProps>, any> {
static create: ShorthandFactory<AvatarProps>

static className = 'ui-avatar'
const Avatar: React.FC<WithAsProp<AvatarProps>> &
FluentComponentStaticProps<AvatarProps> = props => {
const context: ProviderContextPrepared = React.useContext(ThemeContext)
const { setStart, setEnd } = useTelemetry(Avatar.displayName, context.telemetry)
setStart()

const {
accessibility,
className,
design,
getInitials,
label,
image,
name,
size,
status,
styles,
variables,
} = props

const getA11Props = useAccessibility(accessibility, {
debugName: Avatar.displayName,
rtl: context.rtl,
})
const { classes, styles: resolvedStyles } = useStyles(Avatar.displayName, {
className: Avatar.className,
mapPropsToStyles: () => ({ size }),
mapPropsToInlineStyles: () => ({
className,
design,
styles,
variables,
}),
})

static displayName = 'Avatar'
const ElementType = getElementType(props)
const unhandledProps = getUnhandledProps(Avatar.handledProps, props)

static 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,
}

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 (
<ElementType {...accessibility.attributes.root} {...unhandledProps} className={classes.root}>
{Image.create(image, {
defaultProps: () => ({
const result = (
<ElementType {...getA11Props('root', { className: classes.root, ...unhandledProps })}>
{Image.create(image, {
defaultProps: () =>
getA11Props('image', {
fluid: true,
avatar: true,
title: name,
styles: styles.image,
styles: resolvedStyles.image,
}),
})}
{!image &&
Label.create(label || {}, {
defaultProps: () => ({
})}
{!image &&
Label.create(label || {}, {
defaultProps: () =>
getA11Props('label', {
content: getInitials(name),
circular: true,
title: name,
styles: styles.label,
styles: resolvedStyles.label,
}),
})}
{Status.create(status, {
defaultProps: () => ({
})}
{Status.create(status, {
defaultProps: () =>
getA11Props('status', {
size,
styles: styles.status,
variables: {
borderColor: variables.statusBorderColor,
borderWidth: variables.statusBorderWidth,
},
styles: resolvedStyles.status,
}),
})}
</ElementType>
)
}
})}
</ElementType>
)

setEnd()

return result
}

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' })

Expand Down
84 changes: 57 additions & 27 deletions packages/react/src/components/Box/Box.tsx
Original file line number Diff line number Diff line change
@@ -1,45 +1,75 @@
import {
getElementType,
getUnhandledProps,
useStyles,
useTelemetry,
} from '@fluentui/react-bindings'
import * as React from 'react'
// @ts-ignore
import { ThemeContext } from 'react-fela'

import {
childrenExist,
createShorthandFactory,
UIComponentProps,
ContentComponentProps,
ChildrenComponentProps,
commonPropTypes,
rtlTextContainer,
UIComponentProps,
} from '../../utils'
import createComponentInternal from '../../utils/createComponentInternal'
import { WithAsProp, withSafeTypeForAs } from '../../types'
import {
ProviderContextPrepared,
WithAsProp,
withSafeTypeForAs,
FluentComponentStaticProps,
} from '../../types'

export interface BoxProps
extends UIComponentProps<BoxProps>,
ContentComponentProps,
ChildrenComponentProps {}

const Box = createComponentInternal<WithAsProp<BoxProps>>({
displayName: 'Box',

className: 'ui-box',

propTypes: {
...commonPropTypes.createCommon(),
},

render(config, props) {
const { ElementType, classes, unhandledProps } = config
const { children, content } = props

return (
<ElementType
{...rtlTextContainer.getAttributes({ forElements: [children, content] })}
{...unhandledProps}
className={classes.root}
>
{childrenExist(children) ? children : content}
</ElementType>
)
},
})
const Box: React.FC<WithAsProp<BoxProps>> & FluentComponentStaticProps<BoxProps> = 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 { 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)

const result = (
<ElementType
{...rtlTextContainer.getAttributes({ forElements: [children, content] })}
{...unhandledProps}
className={classes.root}
>
{childrenExist(children) ? children : content}
</ElementType>
)

setEnd()

return result
}

Box.className = 'ui-box'
Box.displayName = 'Box'

Box.propTypes = commonPropTypes.createCommon({ accessibility: false })
Box.handledProps = Object.keys(Box.propTypes) as any

Box.create = createShorthandFactory({ Component: Box })

Expand Down
Loading