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

feat(bindings): useStyles hook #2217

Merged
merged 25 commits into from
Jan 14, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
2954cbe
feat(bindings): useStyles hook
layershifter Jan 9, 2020
f15ccec
Merge branches 'feat/use-styles-hook' and 'master' of https://github.…
layershifter Jan 13, 2020
eddaca4
build it
layershifter Jan 13, 2020
e542b74
remove compose changes
layershifter Jan 13, 2020
861e1fb
continue on this
layershifter Jan 13, 2020
69edaee
-reverted changes to components
mnajdova Jan 13, 2020
3405aa8
-typos
mnajdova Jan 13, 2020
c8f15ee
add initial docs
layershifter Jan 13, 2020
98fc985
Merge branch 'feat/use-styles-hook' of https://github.com/stardust-ui…
layershifter Jan 13, 2020
ce9b464
remove todo
layershifter Jan 13, 2020
9b31750
fix dependencies
layershifter Jan 13, 2020
c6f0841
fix docs
layershifter Jan 13, 2020
120bbe3
fix dependencies
layershifter Jan 13, 2020
31c4c4a
move tests, fix tests
layershifter Jan 13, 2020
7adc3c3
wip, needs cleanup
layershifter Jan 13, 2020
5d93849
-moving changes to different PRs
mnajdova Jan 14, 2020
d0c6bb8
-reverted style param required related changes
mnajdova Jan 14, 2020
6c59aee
Merge branches 'feat/use-styles-hook' and 'master' of https://github.…
layershifter Jan 14, 2020
2f74a10
fix UT
layershifter Jan 14, 2020
7e3852b
Merge branches 'feat/use-styles-hook' and 'master' of https://github.…
layershifter Jan 14, 2020
bf3bbca
add changelog entry
layershifter Jan 14, 2020
dd952e7
enforce types
layershifter Jan 14, 2020
0ce4806
fix TS issue
layershifter Jan 14, 2020
dc223cc
do not reexport packages
layershifter Jan 14, 2020
759d108
restore changes
layershifter Jan 14, 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 @@ -26,6 +26,7 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm

### Features
- 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))

<!--------------------------------[ v0.43.0 ]------------------------------- -->
## [v0.43.0](https://github.com/microsoft/fluent-ui-react/tree/v0.43.0) (2020-01-08)
Expand Down
135 changes: 88 additions & 47 deletions packages/react-bindings/README.md
Original file line number Diff line number Diff line change
@@ -1,30 +1,33 @@
`@fluentui/react-bindings`
===
# `@fluentui/react-bindings`

A set of reusable components and hooks to build component libraries and UI kits.

<!-- START doctoc generated TOC please keep comment here to allow auto update -->
<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->


- [Installation](#installation)
- [Hooks](#hooks)
- [`useAccesibility()`](#useaccesibility)
- [Usage](#usage)
- [Usage](#usage)
- [`useStateManager()`](#usestatemanager)
- [Usage](#usage-1)
- [Reference](#reference)
- [`useStyles()`](#usestyles)
- [Usage](#usage-2)
- [Reference](#reference-1)

<!-- END doctoc generated TOC please keep comment here to allow auto update -->

# Installation

**NPM**

```bash
npm install --save @fluentui/react-bindings
```

**Yarn**

```bash
yarn add @fluentui/react-bindings
```
Expand All @@ -43,105 +46,143 @@ The example below assumes a component called `<Image>` will be used this way:
const imageBehavior: Accessibility<{ disabled: boolean }> = props => ({
attributes: {
root: {
"aria-disabled": props.disabled,
tabIndex: -1
'aria-disabled': props.disabled,
tabIndex: -1,
},
img: {
role: "presentation"
}
role: 'presentation',
},
},
keyActions: {
root: {
click: {
keyCombinations: [{ keyCode: 13 /* equals Enter */ }]
}
}
}
});
keyCombinations: [{ keyCode: 13 /* equals Enter */ }],
},
},
},
})

type ImageProps = {
disabled?: boolean;
onClick?: (
e: React.MouseEvent<HTMLDivElement> | React.KeyboardEvent<HTMLDivElement>
) => void;
src: string;
};
disabled?: boolean
onClick?: (e: React.MouseEvent<HTMLDivElement> | React.KeyboardEvent<HTMLDivElement>) => void
src: string
}

const Image: React.FC<ImageProps> = props => {
const { disabled, onClick, src, ...rest } = props;
const { disabled, onClick, src, ...rest } = props
const getA11Props = useAccessibility(imageBehavior, {
mapPropsToBehavior: () => ({
disabled
disabled,
}),
actionHandlers: {
click: (e: React.KeyboardEvent<HTMLDivElement>) => {
if (onClick) onClick(e);
}
}
});
if (onClick) onClick(e)
},
},
})

return (
<div {...getA11Props("root", { onClick, ...rest })}>
<img {...getA11Props("img", { src })} />
<div {...getA11Props('root', { onClick, ...rest })}>
<img {...getA11Props('img', { src })} />
</div>
);
};
)
}
```

## `useStateManager()`

A React hook that provides bindings for state managers.
A React hook that provides bindings for state managers.

### Usage
### Usage

The example below assumes a component called `<Input>` will be used this way:

```tsx
type InputProps = {
defaultValue?: string;
value?: string;
onChange?: (value: string) => void;
};
type InputState = { value: string };
type InputActions = { change: (value: string) => void };
defaultValue?: string
value?: string
onChange?: (value: string) => void
}
type InputState = { value: string }
type InputActions = { change: (value: string) => void }

const createInputManager: ManagerFactory<InputState, InputActions> = config =>
createManager<InputState, InputActions>({
...config,
actions: {
change: (value: string) => () => ({ value })
change: (value: string) => () => ({ value }),
},
state: { value: "", ...config.state }
});
state: { value: '', ...config.state },
})

const Input: React.FC<InputProps> = props => {
const [state, actions] = useStateManager(createInputManager, {
mapPropsToInitialState: () => ({ value: props.defaultValue }),
mapPropsToState: () => ({ value: props.value })
});
mapPropsToState: () => ({ value: props.value }),
})

return (
<input
onChange={e => {
actions.change(e.target.value);
if (props.onChange) props.onChange(e.target.value);
actions.change(e.target.value)
if (props.onChange) props.onChange(e.target.value)
}}
value={state.value}
/>
);
};
)
}
```

### Reference

```tsx
const [state, actions] = useStateManager(createInputManager)
const [state, actions] = useStateManager(
managerFactory: ManagerFactory<State, Actions>,
managerFactory: ManagerFactory<State, Actions>,
options: UseStateManagerOptions<Props>,
)
```

- `managerFactory` - a factory that implements state manager API
- `options.mapPropsToInitialState` - optional, maps component's props to the initial state
- `options.mapPropsToState` - optional, maps component's props to the state, should be used if your component implements [controlled mode](https://reactjs.org/docs/uncontrolled-components.html).

## `useStyles()`

A React hook that provides bindings for usage CSSinJS styles and Fluent theming.

### Usage

The example below assumes a component called `<Text>` will be used this way:

```tsx
type TextComponentProps = {
className?: string
color?: string
}

const Text: React.FunctionComponent<TextComponentProps> = props => {
const { className, children, color } = props

const [classes] = useStyles('Text', {
className: 'ui-text',
mapPropsToStyles: () => ({ color }),
})

return <span className={classes.root}>{children}</span>
}
```

### Reference

```tsx
const [classes] = useStyles(
displayName: string,
options: UseStylesOptions<Props>,
)
```

- `displayName` - a component name to lookup in theme
- `options.className` - optional, a special class name that will be always added to the `root` slot
- `options.mapPropsToStyles` - optional, a set of props that will be passed style functions, only primitives are allowed
- `options.rtl` - optional, sets RTL mode
6 changes: 5 additions & 1 deletion packages/react-bindings/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,11 @@
"dependencies": {
"@babel/runtime": "^7.1.2",
"@fluentui/accessibility": "^0.43.0",
"@fluentui/state": "^0.43.0"
"@fluentui/state": "^0.43.0",
"@fluentui/styles": "^0.43.0",
"classnames": "^2.2.6",
"fela": "^10.6.1",
"lodash": "^4.17.15"
},
"devDependencies": {
"@fluentui/internal-tooling": "^0.43.0",
Expand Down
85 changes: 85 additions & 0 deletions packages/react-bindings/src/hooks/useStyles.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
import {
ComponentSlotStyle,
ComponentSlotStylesPrepared,
ComponentVariablesInput,
DebugData,
emptyTheme,
} from '@fluentui/styles'
import * as React from 'react'
// @ts-ignore We have this export in package, but it is not present in typings
import { ThemeContext } from 'react-fela'

import {
ComponentDesignProp,
ComponentSlotClasses,
RendererRenderRule,
StylesContextValue,
} from '../styles/types'
import getStyles from '../styles/getStyles'

type PrimitiveProps = Record<string, boolean | number | string | undefined>
type UseStylesOptions<StyleProps extends PrimitiveProps> = {
className?: string
mapPropsToStyles?: () => StyleProps
mapPropsToInlineStyles?: () => InlineStyleProps<StyleProps>
rtl?: boolean
}

type InlineStyleProps<StyleProps> = {
/** Additional CSS class name(s) to apply. */
className?: string

design?: ComponentDesignProp

/** Additional CSS styles to apply to the component instance. */
styles?: ComponentSlotStyle<StyleProps, any> // TODO: see if we can improve it

/** Override for theme site variables to allow modifications of component styling via themes. */
variables?: ComponentVariablesInput
}

const defaultContext: StylesContextValue<{ renderRule: RendererRenderRule }> = {
disableAnimations: false,
renderer: { renderRule: () => '' },
theme: emptyTheme,
_internal_resolvedComponentVariables: {},
}

const useStyles = <StyleProps extends PrimitiveProps>(
displayName: string,
options: UseStylesOptions<StyleProps>,
): [ComponentSlotClasses, ComponentSlotStylesPrepared] => {
const context: StylesContextValue<{ renderRule: RendererRenderRule }> =
React.useContext(ThemeContext) || defaultContext
Copy link
Contributor

Choose a reason for hiding this comment

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

Any ideas on how we could decouple this call from the concrete theme? It would be nice to be able to reuse the base components with a different design system (which would likely have a separate theme shape).

Copy link
Member Author

Choose a reason for hiding this comment

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

This is next stage goal 🙇‍♂️

This is actually even not ThemeContext as it contains everything, like renderer and disableAnimations. Currently we can't we have multiple contexts as the most of our components are class components 🙃

I have a naive idea that we will be able to move out renderer to a separate context and use the same interface different engines, https://codesandbox.io/embed/t-rex-proto-1w8bp


const {
className = process.env.NODE_ENV === 'production' ? '' : 'no-classname-🙉',
mapPropsToStyles = () => ({} as StyleProps),
mapPropsToInlineStyles = () => ({} as InlineStyleProps<StyleProps>),
rtl = false,
} = options

// Stores debug information for component.
const debug = React.useRef<{ fluentUIDebug: DebugData | null }>({ fluentUIDebug: null })
const { classes, styles: resolvedStyles } = getStyles({
// Input values
className,
displayName,
props: {
...mapPropsToStyles(),
...mapPropsToInlineStyles(),
},

// Context values
disableAnimations: context.disableAnimations,
renderer: context.renderer,
rtl,
saveDebug: fluentUIDebug => (debug.current = { fluentUIDebug }),
theme: context.theme,
_internal_resolvedComponentVariables: context._internal_resolvedComponentVariables,
})

return [classes, resolvedStyles]
}

export default useStyles
5 changes: 5 additions & 0 deletions packages/react-bindings/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,13 @@ export * from './FocusZone/FocusZone.types'
export * from './FocusZone/focusUtilities'

export { default as useAccessibility } from './hooks/useAccessibility'
export { default as useStyles } from './hooks/useStyles'
export { default as unstable_useDispatchEffect } from './hooks/useDispatchEffect'
export { default as useStateManager } from './hooks/useStateManager'

export { default as unstable_createAnimationStyles } from './styles/createAnimationStyles'
export { default as unstable_getStyles } from './styles/getStyles'
export * from './styles/types'

export { default as getElementType } from './utils/getElementType'
export { default as getUnhandledProps } from './utils/getUnhandledProps'
Loading