Skip to content
This repository has been archived by the owner on Feb 12, 2021. It is now read-only.

Add withSwipe higher-order component #30

Open
wants to merge 22 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
3 changes: 0 additions & 3 deletions .babelrc

This file was deleted.

3 changes: 0 additions & 3 deletions .eslintignore

This file was deleted.

3 changes: 0 additions & 3 deletions .eslintrc

This file was deleted.

1 change: 1 addition & 0 deletions .npmignore
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,4 @@ coverage
node_modules
npm-debug.log
src
.sagui
123 changes: 63 additions & 60 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -116,18 +116,18 @@ export withTouchProps({
})(Touchable)
```

## NotifyOnLowFPS
## withNotifyOnLowFPS ({threshold: number}) (Component)

**notifyOnLowFPS** allows you to track the frames per second that the browser window is achieving when your component is rendered. This is particularly useful for components that are animated.
**withNotifyOnLowFPS** allows you to track the frames per second that the browser window is achieving when your component is rendered. This is particularly useful for components that are animated.

In order to do this, **notifyOnLowFPS** uses the `collect-fps` library to collect the rate in which `requestAnimationFrame` is being called. If the frames per second drop below a threshold (30 FPS by default) then a property is set in the decorated component to notify that the animation speed is slow (the property is `lowFPS` by default).
In order to do this, **withNotifyOnLowFPS** uses the `collect-fps` library to collect the rate in which `requestAnimationFrame` is being called. If the frames per second drop below a threshold (30 FPS by default) then a property is set in the decorated component to notify that the animation speed is slow (the property is `lowFPS` by default).

**notifyOnLowFPS** passes two props down to the inner component:
**withNotifyOnLowFPS** passes two props down to the inner component:

- `onStartFPSCollection`: to be called when the inner component starts a heavy animation of some sort
- `onEndFPSCollection`: to be called when the animation is complete

**notifyOnLowFPS** updates the value of the `lowFPS` prop when the collection is completed.
**withNotifyOnLowFPS** updates the value of the `lowFPS` prop when the collection is completed.

```javascript
class AnimatedComponent extends Component {
Expand All @@ -148,7 +148,7 @@ class AnimatedComponent extends Component {
}
}

const DecoratedAnimatedComponent = notifyOnLowFPS({
const DecoratedAnimatedComponent = withNotifyOnLowFPS({
threshold: 30, // default threshold of frames per second. Below this number it will be considered to be low frame rate
})(AnimatedComponent)
```
Expand All @@ -166,9 +166,36 @@ render(
)
```

## UniqueName
## withDeprecationWarning (config) (Component)

**UniqueName** is a helper for components that need a `name` prop, so that it defaults to a namespaced UUID if not specified. This is useful for components that wrap `checkbox` or `radio` input types, which will not behave properly without a unique name. When using those Component types as fully controlled, name are unimportant, so it’s easy to forget to add them. This is a common source of mistakes for this family of components:
A component wrapped with `withDeprecationWarning` will print an error to the console when used so that consumers know they need to update their codebase to the latest component. It can be configured with the name of a component to use instead, and a URL where to read more.

```javascript
import React from 'react'
import {withDeprecationWarning} from '@klarna/higher-order-components'

function ObsoleteUnderlinedComponent ({ children }) {
return <u>{children}</u>
}

export default withDeprecationWarning({
readMore: 'http://example.com/why-old-component-is-deprecated',
useInstead: 'Underlined'
})(ObsoleteUnderlinedComponent)
```

If the component doesn’t have a defined `name` or `displayName`, you can specify its name:

```javascript
withDeprecationWarning({
…,
name: 'ObsoleteUnderlinedComponent'
})
```

## withUniqueFormIdentifier

**withUniqueFormIdentifier** is a helper for components that need a `name` prop, so that it defaults to a namespaced UUID if not specified. This is useful for components that wrap `checkbox` or `radio` input types, which will not behave properly without an unique name. When using those Component types as fully controlled, names are unimportant, so it’s easy to forget to add them. This is a common source of problem for this family of components, which **withUniqueFormIdentifier** helps you to avoid.

Say that you have the component:

Expand Down Expand Up @@ -222,13 +249,13 @@ function Radio ({name, value, onChange}) {
export default Radio
```

…you can add the `uniqueName` higher-order component around it:
…you can add the `withUniqueFormIdentifier` higher-order component around it:

```diff
+import {uniqueName} from '@klarna/higher-order-components'
+import {withUniqueFormIdentifier} from '@klarna/higher-order-components'

-export default Radio
+export default uniqueName(Radio)
+export default withUniqueFormIdentifier(Radio)
```

…and it no longer matters if you forget to set a `name` when using it, unless you actually care about that name of course.
Expand Down Expand Up @@ -273,9 +300,9 @@ type Overridable = (
) => (target: ReactComponent) => Component
```

## Themeable
## withTheme (themeToProps) (Component)

**Themeable** allows you to configure your components so that they can take information from the React.context to customize some props, whenever in the tree they might be. This higher-order component is useful for theming your components without having to use React.context explicitly in your component implementation.
**withTheme** allows you to configure your components so that they can take information from the `React.context` to customize some props, whenever in the tree they might be. This higher-order component is useful for theming your components without having to use `React.context` explicitly in your component implementation.

Say you have a set of textual components that support a small version of themselves via the `small: boolean` prop.

Expand Down Expand Up @@ -321,37 +348,37 @@ render(
)
```

You could of course pass a `small` prop to the MoreComplexView and have that one send the value down to each Title and Paragraph, but it can easily get cumbersome. If you happen, for example, to use a component inside MoreComplexView that in turn uses Title or Paragraph inside, you would have to pass `small` to that new component as well, and so on and so forth. What you really want to do is to set a global option for whether the text is regular or small, which is what React.context is for. Adding support for contextProps in your Title and Paragraph components makes their implementation complex though: there is a more elegant way to do it, with the **themeable** higher-order component:
You could of course pass a `small` prop to the MoreComplexView and have that one send the value down to each Title and Paragraph, but it can easily get cumbersome. If you happen, for example, to use a component inside MoreComplexView that in turn uses Title or Paragraph inside, you would have to pass `small` to that new component as well, and so on and so forth. What you really want to do is to set a global option for whether the text is regular or small, which is what React.context is for. Adding support for contextProps in your Title and Paragraph components makes their implementation complex though: there is a more elegant way to do it, with the **withTheme** higher-order component:

```diff
// Title.jsx
+import {themeable} from '@klarna/higher-order-components'
+import {withTheme} from '@klarna/higher-order-components'

function Title ({children, small}) {
return <h2 style={{ fontSize: small ? '12px' : '18px' }}>{children}</h2>
}

-export default Title
+export default themeable((customizations, props) => ({
+export default withTheme((customizations, props) => ({
+ small: customizations.textSize === 'small'
+}))(Title)
```

```diff
// Paragraph.jsx
+import {themeable} from '@klarna/higher-order-components'
+import {withTheme} from '@klarna/higher-order-components'

function Paragraph ({children, small}) {
return <p style={{ fontSize: small ? '12px' : '18px' }}>{children}</p>
}

-export default Paragraph
+export default themeable((customizations, props) => ({
+export default withTheme((customizations, props) => ({
+ small: customizations.textSize === 'small'
+}))(Paragraph)
```

The predicate function that you pass to `themeable` will only be called if there is a `customizations` from in the context, which means that wrapping your components with `themeable` is safe since nothing will change unless that prop is set.
The predicate function that you pass to `withTheme` will only be called if there is a `customizations` from in the context, which means that wrapping your components with `withTheme` is safe since nothing will change unless that prop is set.

Now you only need to set the prop in the React.context. You can easily do that with a little help from [`react-context-props`](https://github.com/xaviervia/react-context-props):

Expand Down Expand Up @@ -401,61 +428,37 @@ render(
- why the props are necessary in the predicate function (again, an example)
- how this could be used to make arbitrary components themeable, including third party ones

## Uncontrolled decorator
## withUncontrolledProp (config) (Component)

**Uncontrolled** is a generic method of making a controlled property of a Component behave as an uncontrolled prop when not set. This is the default behavior that React exposes for form components such as `<input>`:
**withUncontrolledProp** is a generic method of making a controlled property of a Component behave as an uncontrolled prop when not set. This is the default behavior that React exposes for form components such as `<input>`:

- `<input value='Controlled' />` and `<input value='' />` will have a controlled value
- `<input />` and `<input defaultValue='Initial value, before user interaction' />` will have an uncontrolled value

**Uncontrolled** is a generic interface however, and allows you to modify all kind of properties to behave in this way (**TODO**: which)
By using the **withUncontrolledProp**, the prop `prop` will be treated as uncontrolled if not defined by the user and the functions specified on `handlers` will be called with the current props and the arguments that the original handlers got called with, and the return value will be used as the new value for the prop. `defaultProp` allows you to configure a new prop that, when used, will set an initial value to the prop but make it stay uncontrolled.

```javascript
import {uncontrolled} from '@klarna/higher-order-components'
import {withUncontrolledProp} from '@klarna/higher-order-components'

function Counter ({value, onClick}) {
return <div>
<button onClick={onClick}>
Add one
</button>
{value}
</div>
}

uncontrolled({
export default withUncontrolledProp({
prop: 'value',
defaultProp: 'defaultValue',
handlers: {
onChange: ({value: currentValue}) => (e) => e.target.value,
onClear: () => () => '',
onDoubleClick: ({value: currentValue}) => () => `${currentValue}${currentValue}`
onClick: props => e => props.value + 1
}
})(Input)
})(Counter)
```

## Deprecated decorator

**TODO** what does it do?

```javascript
type Url = string

type Deprecated = (options: { readMore: ?Url, useInstead: ?string, name: ?string }) => (component: ReactComponent) => ReactComponent
```

```javascript
import React from 'react'
import {deprecated} from '@klarna/higher-order-components'

function OldUnderlined ({ … }) {
}

export default deprecated({
readMore: 'http://example.com/why-old-component-is-deprecated',
useInstead: 'Underlined'
})(OldUnderlined)
```

If the component doesn’t have a defined `name` or `displayName`, you can specify its name like:

```javascript
deprecated({
…,
name: 'OldUnderlined'
})
```
> The behavior of this higher-order component is very close to combining `withState` and `withHandlers` from [`recompose`](https://github.com/acdlite/recompose/blob/master/docs/API.md#withstate). The reason why it was created anyway is that it also provides the `defaultProp`.

## License

Expand Down
11 changes: 8 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
"scripts": {
"build": "sagui build",
"dist": "sagui dist",
"format": "sagui format",
"prepublish": "sagui build",
"start": "sagui develop --port 3000",
"test": "sagui test",
Expand All @@ -28,19 +29,23 @@
},
"homepage": "https://github.com/klarna/higher-order-components#readme",
"devDependencies": {
"prettier": "^1.4.4",
"prop-types": "^15.5.10",
"react": "^15.5.0",
"react-context-props": "^2.0.0",
"react-component-queries": "^2.1.1",
"react-context-props": "^2.1.0",
"react-dom": "^15.5.0",
"sagui": "8.0.7"
"react-sizeme": "^2.3.2",
"sagui": "9.0.0-rc.16"
},
"peerDependencies": {
"prop-types": "^15.5.10",
"react": "^15.5.0",
"react-context-props": "^2.0.0"
"react-context-props": "^2.1.0"
},
"dependencies": {
"collect-fps": "2.0.0",
"recompose": "^0.23.4",
"seed-random": "^2.2.0"
}
}
14 changes: 8 additions & 6 deletions sagui.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,17 @@
module.exports = {
libraries: [
'index',
'deprecated',
'notifyOnLowFPS',
'overridable',
'themeable',
'uncontrolled',
'withDeprecationWarning',
'withDisplayName',
'withFocusProps',
'withNotifyOnLowFPS',
'withHoverProps',
'withMouseDownProps',
'withTouchProps'
]
'withTheme',
'withTouchProps',
'withUncontrolledProp',
'withUniqueFormIdentifier',
],
pages: ['example/index', 'example/WithSwipeExample'],
}
25 changes: 0 additions & 25 deletions src/deprecated.js

This file was deleted.

39 changes: 39 additions & 0 deletions src/example/WithDeprecationWarningExample.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import React from 'react'
import { withDeprecationWarning } from '../'

export default withDeprecationWarning({
readMore: 'http://example.com/why-old-component-is-deprecated',
useInstead: 'Underlined',
name: 'DeprecatedComponent',
})(function DeprecatedComponent() {
return (
<article>
<h1>withDeprecationWarning</h1>
<code>
<pre
style={{
color: 'lightgreen',
backgroundColor: 'black',
padding: 10,
overflowX: 'scroll',
}}
>{`import React from 'react'
import { withDeprecationWarning } from '@klarna/higher-order-components'

export default withDeprecationWarning({
readMore: 'http://example.com/why-old-component-is-deprecated',
useInstead: 'Underlined',
name: 'DeprecatedComponent',
})(function DeprecatedComponent() {
return (
<article>
<h1>withDeprecationWarning</h1>
<p>This one will log a deprecation warning in the console</p>
</article>
)
})`}</pre>
</code>
<p>This one will log a deprecation warning in the console</p>
</article>
)
})
Loading