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

Commit 6f3dbb6

Browse files
feat(bindings): useStyles hook (#2217)
* feat(bindings): useStyles hook * build it * remove compose changes * continue on this * -reverted changes to components * -typos * add initial docs * remove todo * fix dependencies * fix docs * fix dependencies * move tests, fix tests * wip, needs cleanup * -moving changes to different PRs * -reverted style param required related changes * fix UT * add changelog entry * enforce types * fix TS issue * do not reexport packages * restore changes Co-authored-by: Marija Najdova <[email protected]>
1 parent d3b7be0 commit 6f3dbb6

28 files changed

+623
-286
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm
2626

2727
### Features
2828
- 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))
29+
- Add `useStyles()` hook to use theming capabilities in custom components @layershifter, @mnajdova ([#2217](https://github.com/microsoft/fluent-ui-react/pull/2217))
2930

3031
<!--------------------------------[ v0.43.0 ]------------------------------- -->
3132
## [v0.43.0](https://github.com/microsoft/fluent-ui-react/tree/v0.43.0) (2020-01-08)

packages/react-bindings/README.md

Lines changed: 88 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,33 @@
1-
`@fluentui/react-bindings`
2-
===
1+
# `@fluentui/react-bindings`
32

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

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

9-
108
- [Installation](#installation)
119
- [Hooks](#hooks)
1210
- [`useAccesibility()`](#useaccesibility)
13-
- [Usage](#usage)
11+
- [Usage](#usage)
1412
- [`useStateManager()`](#usestatemanager)
1513
- [Usage](#usage-1)
1614
- [Reference](#reference)
15+
- [`useStyles()`](#usestyles)
16+
- [Usage](#usage-2)
17+
- [Reference](#reference-1)
1718

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

2021
# Installation
2122

2223
**NPM**
24+
2325
```bash
2426
npm install --save @fluentui/react-bindings
2527
```
2628

2729
**Yarn**
30+
2831
```bash
2932
yarn add @fluentui/react-bindings
3033
```
@@ -43,105 +46,143 @@ The example below assumes a component called `<Image>` will be used this way:
4346
const imageBehavior: Accessibility<{ disabled: boolean }> = props => ({
4447
attributes: {
4548
root: {
46-
"aria-disabled": props.disabled,
47-
tabIndex: -1
49+
'aria-disabled': props.disabled,
50+
tabIndex: -1,
4851
},
4952
img: {
50-
role: "presentation"
51-
}
53+
role: 'presentation',
54+
},
5255
},
5356
keyActions: {
5457
root: {
5558
click: {
56-
keyCombinations: [{ keyCode: 13 /* equals Enter */ }]
57-
}
58-
}
59-
}
60-
});
59+
keyCombinations: [{ keyCode: 13 /* equals Enter */ }],
60+
},
61+
},
62+
},
63+
})
6164

6265
type ImageProps = {
63-
disabled?: boolean;
64-
onClick?: (
65-
e: React.MouseEvent<HTMLDivElement> | React.KeyboardEvent<HTMLDivElement>
66-
) => void;
67-
src: string;
68-
};
66+
disabled?: boolean
67+
onClick?: (e: React.MouseEvent<HTMLDivElement> | React.KeyboardEvent<HTMLDivElement>) => void
68+
src: string
69+
}
6970

7071
const Image: React.FC<ImageProps> = props => {
71-
const { disabled, onClick, src, ...rest } = props;
72+
const { disabled, onClick, src, ...rest } = props
7273
const getA11Props = useAccessibility(imageBehavior, {
7374
mapPropsToBehavior: () => ({
74-
disabled
75+
disabled,
7576
}),
7677
actionHandlers: {
7778
click: (e: React.KeyboardEvent<HTMLDivElement>) => {
78-
if (onClick) onClick(e);
79-
}
80-
}
81-
});
79+
if (onClick) onClick(e)
80+
},
81+
},
82+
})
8283

8384
return (
84-
<div {...getA11Props("root", { onClick, ...rest })}>
85-
<img {...getA11Props("img", { src })} />
85+
<div {...getA11Props('root', { onClick, ...rest })}>
86+
<img {...getA11Props('img', { src })} />
8687
</div>
87-
);
88-
};
88+
)
89+
}
8990
```
9091

9192
## `useStateManager()`
9293

93-
A React hook that provides bindings for state managers.
94+
A React hook that provides bindings for state managers.
9495

95-
### Usage
96+
### Usage
9697

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

99100
```tsx
100101
type InputProps = {
101-
defaultValue?: string;
102-
value?: string;
103-
onChange?: (value: string) => void;
104-
};
105-
type InputState = { value: string };
106-
type InputActions = { change: (value: string) => void };
102+
defaultValue?: string
103+
value?: string
104+
onChange?: (value: string) => void
105+
}
106+
type InputState = { value: string }
107+
type InputActions = { change: (value: string) => void }
107108

108109
const createInputManager: ManagerFactory<InputState, InputActions> = config =>
109110
createManager<InputState, InputActions>({
110111
...config,
111112
actions: {
112-
change: (value: string) => () => ({ value })
113+
change: (value: string) => () => ({ value }),
113114
},
114-
state: { value: "", ...config.state }
115-
});
115+
state: { value: '', ...config.state },
116+
})
116117

117118
const Input: React.FC<InputProps> = props => {
118119
const [state, actions] = useStateManager(createInputManager, {
119120
mapPropsToInitialState: () => ({ value: props.defaultValue }),
120-
mapPropsToState: () => ({ value: props.value })
121-
});
121+
mapPropsToState: () => ({ value: props.value }),
122+
})
122123

123124
return (
124125
<input
125126
onChange={e => {
126-
actions.change(e.target.value);
127-
if (props.onChange) props.onChange(e.target.value);
127+
actions.change(e.target.value)
128+
if (props.onChange) props.onChange(e.target.value)
128129
}}
129130
value={state.value}
130131
/>
131-
);
132-
};
132+
)
133+
}
133134
```
134135

135136
### Reference
136137

137138
```tsx
138139
const [state, actions] = useStateManager(createInputManager)
139140
const [state, actions] = useStateManager(
140-
managerFactory: ManagerFactory<State, Actions>,
141+
managerFactory: ManagerFactory<State, Actions>,
141142
options: UseStateManagerOptions<Props>,
142143
)
143144
```
144145

145146
- `managerFactory` - a factory that implements state manager API
146147
- `options.mapPropsToInitialState` - optional, maps component's props to the initial state
147148
- `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).
149+
150+
## `useStyles()`
151+
152+
A React hook that provides bindings for usage CSSinJS styles and Fluent theming.
153+
154+
### Usage
155+
156+
The example below assumes a component called `<Text>` will be used this way:
157+
158+
```tsx
159+
type TextComponentProps = {
160+
className?: string
161+
color?: string
162+
}
163+
164+
const Text: React.FunctionComponent<TextComponentProps> = props => {
165+
const { className, children, color } = props
166+
167+
const [classes] = useStyles('Text', {
168+
className: 'ui-text',
169+
mapPropsToStyles: () => ({ color }),
170+
})
171+
172+
return <span className={classes.root}>{children}</span>
173+
}
174+
```
175+
176+
### Reference
177+
178+
```tsx
179+
const [classes] = useStyles(
180+
displayName: string,
181+
options: UseStylesOptions<Props>,
182+
)
183+
```
184+
185+
- `displayName` - a component name to lookup in theme
186+
- `options.className` - optional, a special class name that will be always added to the `root` slot
187+
- `options.mapPropsToStyles` - optional, a set of props that will be passed style functions, only primitives are allowed
188+
- `options.rtl` - optional, sets RTL mode

packages/react-bindings/package.json

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,11 @@
77
"dependencies": {
88
"@babel/runtime": "^7.1.2",
99
"@fluentui/accessibility": "^0.43.0",
10-
"@fluentui/state": "^0.43.0"
10+
"@fluentui/state": "^0.43.0",
11+
"@fluentui/styles": "^0.43.0",
12+
"classnames": "^2.2.6",
13+
"fela": "^10.6.1",
14+
"lodash": "^4.17.15"
1115
},
1216
"devDependencies": {
1317
"@fluentui/internal-tooling": "^0.43.0",
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
import {
2+
ComponentSlotStyle,
3+
ComponentSlotStylesPrepared,
4+
ComponentVariablesInput,
5+
DebugData,
6+
emptyTheme,
7+
} from '@fluentui/styles'
8+
import * as React from 'react'
9+
// @ts-ignore We have this export in package, but it is not present in typings
10+
import { ThemeContext } from 'react-fela'
11+
12+
import {
13+
ComponentDesignProp,
14+
ComponentSlotClasses,
15+
RendererRenderRule,
16+
StylesContextValue,
17+
} from '../styles/types'
18+
import getStyles from '../styles/getStyles'
19+
20+
type PrimitiveProps = Record<string, boolean | number | string | undefined>
21+
type UseStylesOptions<StyleProps extends PrimitiveProps> = {
22+
className?: string
23+
mapPropsToStyles?: () => StyleProps
24+
mapPropsToInlineStyles?: () => InlineStyleProps<StyleProps>
25+
rtl?: boolean
26+
}
27+
28+
type InlineStyleProps<StyleProps> = {
29+
/** Additional CSS class name(s) to apply. */
30+
className?: string
31+
32+
design?: ComponentDesignProp
33+
34+
/** Additional CSS styles to apply to the component instance. */
35+
styles?: ComponentSlotStyle<StyleProps, any> // TODO: see if we can improve it
36+
37+
/** Override for theme site variables to allow modifications of component styling via themes. */
38+
variables?: ComponentVariablesInput
39+
}
40+
41+
const defaultContext: StylesContextValue<{ renderRule: RendererRenderRule }> = {
42+
disableAnimations: false,
43+
renderer: { renderRule: () => '' },
44+
theme: emptyTheme,
45+
_internal_resolvedComponentVariables: {},
46+
}
47+
48+
const useStyles = <StyleProps extends PrimitiveProps>(
49+
displayName: string,
50+
options: UseStylesOptions<StyleProps>,
51+
): [ComponentSlotClasses, ComponentSlotStylesPrepared] => {
52+
const context: StylesContextValue<{ renderRule: RendererRenderRule }> =
53+
React.useContext(ThemeContext) || defaultContext
54+
55+
const {
56+
className = process.env.NODE_ENV === 'production' ? '' : 'no-classname-🙉',
57+
mapPropsToStyles = () => ({} as StyleProps),
58+
mapPropsToInlineStyles = () => ({} as InlineStyleProps<StyleProps>),
59+
rtl = false,
60+
} = options
61+
62+
// Stores debug information for component.
63+
const debug = React.useRef<{ fluentUIDebug: DebugData | null }>({ fluentUIDebug: null })
64+
const { classes, styles: resolvedStyles } = getStyles({
65+
// Input values
66+
className,
67+
displayName,
68+
props: {
69+
...mapPropsToStyles(),
70+
...mapPropsToInlineStyles(),
71+
},
72+
73+
// Context values
74+
disableAnimations: context.disableAnimations,
75+
renderer: context.renderer,
76+
rtl,
77+
saveDebug: fluentUIDebug => (debug.current = { fluentUIDebug }),
78+
theme: context.theme,
79+
_internal_resolvedComponentVariables: context._internal_resolvedComponentVariables,
80+
})
81+
82+
return [classes, resolvedStyles]
83+
}
84+
85+
export default useStyles

packages/react-bindings/src/index.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,13 @@ export * from './FocusZone/FocusZone.types'
1010
export * from './FocusZone/focusUtilities'
1111

1212
export { default as useAccessibility } from './hooks/useAccessibility'
13+
export { default as useStyles } from './hooks/useStyles'
1314
export { default as unstable_useDispatchEffect } from './hooks/useDispatchEffect'
1415
export { default as useStateManager } from './hooks/useStateManager'
1516

17+
export { default as unstable_createAnimationStyles } from './styles/createAnimationStyles'
18+
export { default as unstable_getStyles } from './styles/getStyles'
19+
export * from './styles/types'
20+
1621
export { default as getElementType } from './utils/getElementType'
1722
export { default as getUnhandledProps } from './utils/getUnhandledProps'

0 commit comments

Comments
 (0)