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

Commit a4d68f5

Browse files
chore(Box|Button|Image|Avatar): converting components with hooks (#2238)
* -converted Box component to use hooks * -fix Box props * temporary support for animation * convert Image to hooks * -converted Button component to use hooks * convert Avatar to hooks and update isConformant test * apply attributes to Avatar slots * -fixed Button test * update Avatar to avoid variables usage * fix tests for tests * fix typings errors * -added useTelemetry hook and used it in the renderComponent function and Button component * -improved typings * -changed return value of useTelemetry to be an object * -updated return value from the hooks * -wip - added cache * -wip * -removed unstable animation * -fixes * -updated changelog * -added telemetry to all components * -fixed telemetry info * -fixed Button telemetry start * -improved typings Co-authored-by: Oleksandr Fediashov <[email protected]>
1 parent 9208c19 commit a4d68f5

File tree

23 files changed

+560
-294
lines changed

23 files changed

+560
-294
lines changed

CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm
2222
- Remove `animation` prop from all components @joschect ([#2239](https://github.com/microsoft/fluent-ui-react/pull/2239))
2323
- `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))
2424
- `FocusZoneMode` and `FOCUSZONE_WRAP_ATTRIBUTE` are no longer exported @layershifter ([#2265](https://github.com/microsoft/fluent-ui-react/pull/2265))
25+
- 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))
2526

2627
### Fixes
2728
- Fix event listener leak in `FocusZone` @miroslavstastny ([#2227](https://github.com/microsoft/fluent-ui-react/pull/2227))

packages/accessibility/src/behaviors/Image/imageBehavior.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ const imageBehavior: Accessibility<ImageBehaviorProps> = props => ({
1818

1919
export default imageBehavior
2020

21-
type ImageBehaviorProps = {
21+
export type ImageBehaviorProps = {
2222
/** Alternative text. */
2323
alt?: string
2424
} & Pick<AccessibilityAttributes, 'aria-label'>

packages/accessibility/src/behaviors/index.ts

+1
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ export { default as alertWarningBehavior } from './Alert/alertWarningBehavior'
33
export { default as attachmentBehavior } from './Attachment/attachmentBehavior'
44
export { default as buttonBehavior } from './Button/buttonBehavior'
55
export { default as toggleButtonBehavior } from './Button/toggleButtonBehavior'
6+
export * from './Image/imageBehavior'
67
export { default as imageBehavior } from './Image/imageBehavior'
78
export { default as menuBehavior } from './Menu/menuBehavior'
89
export { default as menuItemBehavior } from './Menu/menuItemBehavior'

packages/react-proptypes/src/index.ts

+5-11
Original file line numberDiff line numberDiff line change
@@ -476,17 +476,11 @@ export const deprecate = (help: string, validator?: Function) => (
476476
return error
477477
}
478478

479-
export const accessibility = PropTypes.oneOfType([PropTypes.func, PropTypes.object])
480-
481-
export const size = PropTypes.oneOf([
482-
'smallest',
483-
'smaller',
484-
'small',
485-
'medium',
486-
'large',
487-
'larger',
488-
'largest',
489-
])
479+
export const accessibility = PropTypes.func
480+
481+
export const size = PropTypes.oneOf<
482+
'smallest' | 'smaller' | 'small' | 'medium' | 'large' | 'larger' | 'largest'
483+
>(['smallest', 'smaller', 'small', 'medium', 'large', 'larger', 'largest'])
490484

491485
export const align = PropTypes.oneOf(['start', 'end', 'center', 'justify'])
492486

packages/react/src/components/Avatar/Avatar.tsx

+120-77
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,34 @@
11
import { Accessibility } from '@fluentui/accessibility'
2+
import {
3+
getElementType,
4+
getUnhandledProps,
5+
useAccessibility,
6+
useStyles,
7+
useTelemetry,
8+
} from '@fluentui/react-bindings'
29
import * as customPropTypes from '@fluentui/react-proptypes'
310
import * as PropTypes from 'prop-types'
411
import * as React from 'react'
12+
// @ts-ignore
13+
import { ThemeContext } from 'react-fela'
14+
515
import Image, { ImageProps } from '../Image/Image'
616
import Label, { LabelProps } from '../Label/Label'
717
import Status, { StatusProps } from '../Status/Status'
8-
import { WithAsProp, ShorthandValue, withSafeTypeForAs } from '../../types'
918
import {
10-
createShorthandFactory,
11-
UIComponent,
12-
UIComponentProps,
13-
commonPropTypes,
14-
SizeValue,
15-
ShorthandFactory,
16-
} from '../../utils'
19+
WithAsProp,
20+
ShorthandValue,
21+
withSafeTypeForAs,
22+
FluentComponentStaticProps,
23+
ProviderContextPrepared,
24+
} from '../../types'
25+
import { createShorthandFactory, UIComponentProps, commonPropTypes, SizeValue } from '../../utils'
1726

1827
export interface AvatarProps extends UIComponentProps {
1928
/**
2029
* Accessibility behavior if overridden by the user.
2130
*/
22-
accessibility?: Accessibility
31+
accessibility?: Accessibility<never>
2332

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

43-
class Avatar extends UIComponent<WithAsProp<AvatarProps>, any> {
44-
static create: ShorthandFactory<AvatarProps>
45-
46-
static className = 'ui-avatar'
52+
const Avatar: React.FC<WithAsProp<AvatarProps>> &
53+
FluentComponentStaticProps<AvatarProps> = props => {
54+
const context: ProviderContextPrepared = React.useContext(ThemeContext)
55+
const { setStart, setEnd } = useTelemetry(Avatar.displayName, context.telemetry)
56+
setStart()
57+
58+
const {
59+
accessibility,
60+
className,
61+
design,
62+
getInitials,
63+
label,
64+
image,
65+
name,
66+
size,
67+
status,
68+
styles,
69+
variables,
70+
} = props
71+
72+
const getA11Props = useAccessibility(accessibility, {
73+
debugName: Avatar.displayName,
74+
rtl: context.rtl,
75+
})
76+
const { classes, styles: resolvedStyles } = useStyles(Avatar.displayName, {
77+
className: Avatar.className,
78+
mapPropsToStyles: () => ({ size }),
79+
mapPropsToInlineStyles: () => ({
80+
className,
81+
design,
82+
styles,
83+
variables,
84+
}),
85+
})
4786

48-
static displayName = 'Avatar'
87+
const ElementType = getElementType(props)
88+
const unhandledProps = getUnhandledProps(Avatar.handledProps, props)
4989

50-
static propTypes = {
51-
...commonPropTypes.createCommon({
52-
children: false,
53-
content: false,
54-
}),
55-
name: PropTypes.string,
56-
image: customPropTypes.itemShorthandWithoutJSX,
57-
label: customPropTypes.itemShorthand,
58-
size: customPropTypes.size,
59-
status: customPropTypes.itemShorthand,
60-
getInitials: PropTypes.func,
61-
}
62-
63-
static defaultProps = {
64-
size: 'medium',
65-
getInitials(name: string) {
66-
if (!name) {
67-
return ''
68-
}
69-
70-
const reducedName = name
71-
.replace(/\s*\(.*?\)\s*/g, ' ')
72-
.replace(/\s*{.*?}\s*/g, ' ')
73-
.replace(/\s*\[.*?]\s*/g, ' ')
74-
75-
const initials = reducedName
76-
.split(' ')
77-
.filter(item => item !== '')
78-
.map(item => item.charAt(0))
79-
.reduce((accumulator, currentValue) => accumulator + currentValue)
80-
81-
if (initials.length > 2) {
82-
return initials.charAt(0) + initials.charAt(initials.length - 1)
83-
}
84-
return initials
85-
},
86-
} as AvatarProps
87-
88-
renderComponent({ accessibility, ElementType, classes, unhandledProps, styles, variables }) {
89-
const { name, status, image, label, getInitials, size } = this.props as AvatarProps
90-
91-
return (
92-
<ElementType {...accessibility.attributes.root} {...unhandledProps} className={classes.root}>
93-
{Image.create(image, {
94-
defaultProps: () => ({
90+
const result = (
91+
<ElementType {...getA11Props('root', { className: classes.root, ...unhandledProps })}>
92+
{Image.create(image, {
93+
defaultProps: () =>
94+
getA11Props('image', {
9595
fluid: true,
9696
avatar: true,
9797
title: name,
98-
styles: styles.image,
98+
styles: resolvedStyles.image,
9999
}),
100-
})}
101-
{!image &&
102-
Label.create(label || {}, {
103-
defaultProps: () => ({
100+
})}
101+
{!image &&
102+
Label.create(label || {}, {
103+
defaultProps: () =>
104+
getA11Props('label', {
104105
content: getInitials(name),
105106
circular: true,
106107
title: name,
107-
styles: styles.label,
108+
styles: resolvedStyles.label,
108109
}),
109-
})}
110-
{Status.create(status, {
111-
defaultProps: () => ({
110+
})}
111+
{Status.create(status, {
112+
defaultProps: () =>
113+
getA11Props('status', {
112114
size,
113-
styles: styles.status,
114-
variables: {
115-
borderColor: variables.statusBorderColor,
116-
borderWidth: variables.statusBorderWidth,
117-
},
115+
styles: resolvedStyles.status,
118116
}),
119-
})}
120-
</ElementType>
121-
)
122-
}
117+
})}
118+
</ElementType>
119+
)
120+
121+
setEnd()
122+
123+
return result
124+
}
125+
126+
Avatar.className = 'ui-avatar'
127+
Avatar.displayName = 'Avatar'
128+
129+
Avatar.defaultProps = {
130+
size: 'medium',
131+
getInitials(name: string) {
132+
if (!name) {
133+
return ''
134+
}
135+
136+
const reducedName = name
137+
.replace(/\s*\(.*?\)\s*/g, ' ')
138+
.replace(/\s*{.*?}\s*/g, ' ')
139+
.replace(/\s*\[.*?]\s*/g, ' ')
140+
141+
const initials = reducedName
142+
.split(' ')
143+
.filter(item => item !== '')
144+
.map(item => item.charAt(0))
145+
.reduce((accumulator, currentValue) => accumulator + currentValue)
146+
147+
if (initials.length > 2) {
148+
return initials.charAt(0) + initials.charAt(initials.length - 1)
149+
}
150+
return initials
151+
},
152+
}
153+
154+
Avatar.propTypes = {
155+
...commonPropTypes.createCommon({
156+
children: false,
157+
content: false,
158+
}),
159+
name: PropTypes.string,
160+
image: customPropTypes.itemShorthandWithoutJSX,
161+
label: customPropTypes.itemShorthand,
162+
size: customPropTypes.size,
163+
status: customPropTypes.itemShorthand,
164+
getInitials: PropTypes.func,
123165
}
166+
Avatar.handledProps = Object.keys(Avatar.propTypes) as any
124167

125168
Avatar.create = createShorthandFactory({ Component: Avatar, mappedProp: 'name' })
126169

packages/react/src/components/Box/Box.tsx

+57-27
Original file line numberDiff line numberDiff line change
@@ -1,45 +1,75 @@
1+
import {
2+
getElementType,
3+
getUnhandledProps,
4+
useStyles,
5+
useTelemetry,
6+
} from '@fluentui/react-bindings'
17
import * as React from 'react'
8+
// @ts-ignore
9+
import { ThemeContext } from 'react-fela'
10+
211
import {
312
childrenExist,
413
createShorthandFactory,
5-
UIComponentProps,
614
ContentComponentProps,
715
ChildrenComponentProps,
816
commonPropTypes,
917
rtlTextContainer,
18+
UIComponentProps,
1019
} from '../../utils'
11-
import createComponentInternal from '../../utils/createComponentInternal'
12-
import { WithAsProp, withSafeTypeForAs } from '../../types'
20+
import {
21+
ProviderContextPrepared,
22+
WithAsProp,
23+
withSafeTypeForAs,
24+
FluentComponentStaticProps,
25+
} from '../../types'
1326

1427
export interface BoxProps
1528
extends UIComponentProps<BoxProps>,
1629
ContentComponentProps,
1730
ChildrenComponentProps {}
1831

19-
const Box = createComponentInternal<WithAsProp<BoxProps>>({
20-
displayName: 'Box',
21-
22-
className: 'ui-box',
23-
24-
propTypes: {
25-
...commonPropTypes.createCommon(),
26-
},
27-
28-
render(config, props) {
29-
const { ElementType, classes, unhandledProps } = config
30-
const { children, content } = props
31-
32-
return (
33-
<ElementType
34-
{...rtlTextContainer.getAttributes({ forElements: [children, content] })}
35-
{...unhandledProps}
36-
className={classes.root}
37-
>
38-
{childrenExist(children) ? children : content}
39-
</ElementType>
40-
)
41-
},
42-
})
32+
const Box: React.FC<WithAsProp<BoxProps>> & FluentComponentStaticProps<BoxProps> = props => {
33+
const context: ProviderContextPrepared = React.useContext(ThemeContext)
34+
const { setStart, setEnd } = useTelemetry(Box.displayName, context.telemetry)
35+
setStart()
36+
37+
const { className, design, styles, variables, children, content } = props
38+
39+
const { classes } = useStyles(Box.displayName, {
40+
className: Box.className,
41+
mapPropsToInlineStyles: () => ({
42+
className,
43+
design,
44+
styles,
45+
variables,
46+
}),
47+
rtl: context.rtl,
48+
})
49+
50+
const unhandledProps = getUnhandledProps(Box.handledProps, props)
51+
const ElementType = getElementType(props)
52+
53+
const result = (
54+
<ElementType
55+
{...rtlTextContainer.getAttributes({ forElements: [children, content] })}
56+
{...unhandledProps}
57+
className={classes.root}
58+
>
59+
{childrenExist(children) ? children : content}
60+
</ElementType>
61+
)
62+
63+
setEnd()
64+
65+
return result
66+
}
67+
68+
Box.className = 'ui-box'
69+
Box.displayName = 'Box'
70+
71+
Box.propTypes = commonPropTypes.createCommon({ accessibility: false })
72+
Box.handledProps = Object.keys(Box.propTypes) as any
4373

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

0 commit comments

Comments
 (0)