diff --git a/CHANGELOG.md b/CHANGELOG.md index 9086fc51e6..aad55303fe 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -29,6 +29,7 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm - Allow `useRef` hook used for storing debugging data to be defined in any order with other hooks in functional components @layershifter, @mnajdova ([#2236](https://github.com/microsoft/fluent-ui-react/pull/2236)) - Add `useStyles()` hook to use theming capabilities in custom components @layershifter, @mnajdova ([#2217](https://github.com/microsoft/fluent-ui-react/pull/2217)) - Add optional wrapper function to `List` which can be used to inject custom scrollbars to `Dropdown` @jurokapsiar ([#2092](https://github.com/microsoft/fluent-ui-react/pull/2092)) +- Add `useTelemetry()` hook for adding telemetry information for the Fluent components and improve return types for the `useStyles` and `useStateManager` hooks @mnajdova ([#2257](https://github.com/microsoft/fluent-ui-react/pull/2257)) ### Documentation - Add per-component performance charts @miroslavstastny ([#2240](https://github.com/microsoft/fluent-ui-react/pull/2240)) 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/README.md b/packages/react-bindings/README.md index 03fb5ac326..9ce96d7101 100644 --- a/packages/react-bindings/README.md +++ b/packages/react-bindings/README.md @@ -116,7 +116,7 @@ const createInputManager: ManagerFactory = config => }) const Input: React.FC = props => { - const [state, actions] = useStateManager(createInputManager, { + const { state, actions } = useStateManager(createInputManager, { mapPropsToInitialState: () => ({ value: props.defaultValue }), mapPropsToState: () => ({ value: props.value }), }) @@ -136,8 +136,8 @@ const Input: React.FC = props => { ### Reference ```tsx -const [state, actions] = useStateManager(createInputManager) -const [state, actions] = useStateManager( +const { state, actions } = useStateManager(createInputManager) +const { state, actions } = useStateManager( managerFactory: ManagerFactory, options: UseStateManagerOptions, ) @@ -164,7 +164,7 @@ type TextComponentProps = { const Text: React.FunctionComponent = props => { const { className, children, color } = props - const [classes] = useStyles('Text', { + const { classes } = useStyles('Text', { className: 'ui-text', mapPropsToStyles: () => ({ color }), }) @@ -176,7 +176,7 @@ const Text: React.FunctionComponent = props => { ### Reference ```tsx -const [classes] = useStyles( +const { classes } = useStyles( displayName: string, options: UseStylesOptions, ) diff --git a/packages/react-bindings/src/hooks/useStateManager.ts b/packages/react-bindings/src/hooks/useStateManager.ts index 3c6aa36507..95e642d7a5 100644 --- a/packages/react-bindings/src/hooks/useStateManager.ts +++ b/packages/react-bindings/src/hooks/useStateManager.ts @@ -7,6 +7,11 @@ type UseStateManagerOptions = { sideEffects?: SideEffect[] } +type UseStateManagerResult = { + state: Readonly + actions: Readonly +} + const getDefinedProps = >(props: Props): Partial => { const definedProps: Partial = {} @@ -25,7 +30,7 @@ const useStateManager = < >( managerFactory: ManagerFactory, options: UseStateManagerOptions = {}, -): [Readonly, Readonly] => { +): UseStateManagerResult => { const { mapPropsToInitialState = () => ({} as Partial), mapPropsToState = () => ({} as Partial), @@ -58,11 +63,14 @@ const useStateManager = < // https://github.com/facebook/react/issues/11527#issuecomment-360199710 if (process.env.NODE_ENV === 'production') { - return [latestManager.current.state, latestManager.current.actions] + return { state: latestManager.current.state, actions: latestManager.current.actions } } // Object.freeze() is used only in dev-mode to avoid usage mistakes - return [Object.freeze(latestManager.current.state), Object.freeze(latestManager.current.actions)] + return { + state: Object.freeze(latestManager.current.state), + actions: Object.freeze(latestManager.current.actions), + } } export default useStateManager diff --git a/packages/react-bindings/src/hooks/useStyles.ts b/packages/react-bindings/src/hooks/useStyles.ts index 7b454bebe5..d69c691999 100644 --- a/packages/react-bindings/src/hooks/useStyles.ts +++ b/packages/react-bindings/src/hooks/useStyles.ts @@ -25,6 +25,11 @@ type UseStylesOptions = { rtl?: boolean } +type UseStylesResult = { + classes: ComponentSlotClasses + styles: ComponentSlotStylesPrepared +} + type InlineStyleProps = { /** Additional CSS class name(s) to apply. */ className?: string @@ -48,7 +53,7 @@ const defaultContext: StylesContextValue<{ renderRule: RendererRenderRule }> = { const useStyles = ( displayName: string, options: UseStylesOptions, -): [ComponentSlotClasses, ComponentSlotStylesPrepared] => { +): UseStylesResult => { const context: StylesContextValue<{ renderRule: RendererRenderRule }> = React.useContext(ThemeContext) || defaultContext @@ -79,7 +84,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/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 72% rename from packages/react/src/utils/Telemetry.ts rename to packages/react-bindings/src/telemetry/types.ts index f4a9792693..9ca3be5dbc 100644 --- a/packages/react/src/utils/Telemetry.ts +++ b/packages/react-bindings/src/telemetry/types.ts @@ -5,7 +5,12 @@ type ComponentPerfStats = { msMax: number } -export default class Telemetry { +export type UseTelemetryResult = { + 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 new file mode 100644 index 0000000000..440fbd766f --- /dev/null +++ b/packages/react-bindings/src/telemetry/useTelemetry.ts @@ -0,0 +1,43 @@ +import { Telemetry, UseTelemetryResult } from './types' + +const useTelemetry = ( + displayName: string, + telemetry: Telemetry | undefined, +): UseTelemetryResult => { + 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-bindings/test/hooks/useDispatchEffect-test.tsx b/packages/react-bindings/test/hooks/useDispatchEffect-test.tsx index 6a104bab6a..c0c7d8bbb5 100644 --- a/packages/react-bindings/test/hooks/useDispatchEffect-test.tsx +++ b/packages/react-bindings/test/hooks/useDispatchEffect-test.tsx @@ -37,7 +37,7 @@ const TestComponent: React.FunctionComponent = props => { } }, ) - const [state, actions] = useStateManager(createTestManager, { + const { state, actions } = useStateManager(createTestManager, { mapPropsToInitialState: () => ({ value: props.defaultValue }), mapPropsToState: () => ({ value: props.value }), sideEffects: [dispatchEffect], diff --git a/packages/react-bindings/test/hooks/useStateManager-test.tsx b/packages/react-bindings/test/hooks/useStateManager-test.tsx index 6c0211b359..f39e597616 100644 --- a/packages/react-bindings/test/hooks/useStateManager-test.tsx +++ b/packages/react-bindings/test/hooks/useStateManager-test.tsx @@ -33,7 +33,7 @@ type TestComponentProps = Partial & { } const TestComponent: React.FunctionComponent = props => { - const [state, actions] = useStateManager(createTestManager, { + const { state, actions } = useStateManager(createTestManager, { mapPropsToInitialState: () => ({ open: props.defaultOpen, value: props.defaultValue, 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/Animation/Animation.tsx b/packages/react/src/components/Animation/Animation.tsx index 5c426f968b..5c5c03cc95 100644 --- a/packages/react/src/components/Animation/Animation.tsx +++ b/packages/react/src/components/Animation/Animation.tsx @@ -89,7 +89,6 @@ class Animation extends UIComponent, any> { static propTypes = { ...commonPropTypes.createCommon({ accessibility: false, - animated: false, content: false, children: 'element', }), diff --git a/packages/react/src/components/MenuButton/MenuButton.tsx b/packages/react/src/components/MenuButton/MenuButton.tsx index 04d25d3b71..d5910ee7eb 100644 --- a/packages/react/src/components/MenuButton/MenuButton.tsx +++ b/packages/react/src/components/MenuButton/MenuButton.tsx @@ -113,7 +113,6 @@ export default class MenuButton extends AutoControlledComponent { static propTypes = { ...commonPropTypes.createCommon({ accessibility: false, - animated: false, as: false, className: false, styled: false, diff --git a/packages/react/src/components/Portal/PortalInner.tsx b/packages/react/src/components/Portal/PortalInner.tsx index 391a0165ad..901963b794 100644 --- a/packages/react/src/components/Portal/PortalInner.tsx +++ b/packages/react/src/components/Portal/PortalInner.tsx @@ -35,7 +35,6 @@ class PortalInner extends React.Component { static propTypes = { ...commonPropTypes.createCommon({ accessibility: false, - animated: false, as: false, className: false, content: false, diff --git a/packages/react/src/components/Provider/Provider.tsx b/packages/react/src/components/Provider/Provider.tsx index 16ac891336..c11e38c259 100644 --- a/packages/react/src/components/Provider/Provider.tsx +++ b/packages/react/src/components/Provider/Provider.tsx @@ -1,6 +1,6 @@ import { IStyle } from 'fela' import * as _ from 'lodash' -import { Renderer } from '@fluentui/react-bindings' +import { Renderer, Telemetry } from '@fluentui/react-bindings' import * as customPropTypes from '@fluentui/react-proptypes' import { mergeSiteVariables, @@ -28,7 +28,6 @@ import { withSafeTypeForAs, } from '../../types' import mergeContexts from '../../utils/mergeProviderContexts' -import Telemetry from '../../utils/Telemetry' export interface ProviderProps extends ChildrenComponentProps { renderer?: Renderer diff --git a/packages/react/src/components/Tooltip/Tooltip.tsx b/packages/react/src/components/Tooltip/Tooltip.tsx index 8adc5f7fd8..bdbc0adaeb 100644 --- a/packages/react/src/components/Tooltip/Tooltip.tsx +++ b/packages/react/src/components/Tooltip/Tooltip.tsx @@ -111,7 +111,6 @@ export default class Tooltip extends AutoControlledComponent { const { accessibility = true, - animated = true, as = true, children = 'node', className = true, @@ -27,9 +25,6 @@ export const createCommon = (config: CreateCommonConfig = {}) => { ...(accessibility && { accessibility: customPropTypes.accessibility, }), - ...(animated && { - animation: customPropTypes.animation, - }), ...(as && { as: PropTypes.elementType, }), diff --git a/packages/react/src/utils/index.ts b/packages/react/src/utils/index.ts index 988a7f7b71..4480c75214 100644 --- a/packages/react/src/utils/index.ts +++ b/packages/react/src/utils/index.ts @@ -43,4 +43,3 @@ export module commonPropTypes { export type CreateCommonConfig = CreateCommonConfigLocal export const createCommon = createCommonLocal } -export { default as Telemetry } from './Telemetry' diff --git a/packages/react/src/utils/renderComponent.tsx b/packages/react/src/utils/renderComponent.tsx index 9d51a1e7b3..8487acdac8 100644 --- a/packages/react/src/utils/renderComponent.tsx +++ b/packages/react/src/utils/renderComponent.tsx @@ -10,6 +10,7 @@ import { ReactAccessibilityBehavior, unstable_getAccessibility as getAccessibility, unstable_getStyles as getStyles, + useTelemetry, } from '@fluentui/react-bindings' import { emptyTheme, @@ -24,7 +25,6 @@ import * as React from 'react' import { Props, ProviderContextPrepared } from '../types' import logProviderMissingWarning from './providerMissingHandler' -import Telemetry from './Telemetry' export interface RenderResultConfig

{ ElementType: React.ElementType

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

( logProviderMissingWarning() } - const { telemetry = undefined as Telemetry } = context || {} + const { setStart, setEnd } = useTelemetry(displayName, context.telemetry) const rtl = context.rtl || false - const startTime = telemetry && telemetry.enabled ? performance.now() : 0 + setStart() 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, - } - } - } + setEnd() if (accessibility.focusZone && accessibility.focusZone.mode === FocusZoneMode.Wrap) { wrapInFocusZone = element =>