Skip to content

Commit

Permalink
[AutoDirectionProvider] Add component for automatically detected dire…
Browse files Browse the repository at this point in the history
…ction

Adds the `AutoDirectionProvider` component for rendering a
`DirectionProvider` based on the first strong LTR/RTL character in the
`text` prop.

This provides a clean way to handle user-generated content, including
forms where UI changes on the fly to handle RTL/LTR input.
  • Loading branch information
garrettberg committed Jan 29, 2018
1 parent 1339e80 commit ad2095a
Show file tree
Hide file tree
Showing 4 changed files with 153 additions and 0 deletions.
20 changes: 20 additions & 0 deletions README.md
Expand Up @@ -63,6 +63,26 @@ import DirectionProvider, { DIRECTIONS } from 'react-with-direction/dist/Directi
</DirectionProvider>
```

## AutoDirectionProvider

Use `AutoDirectionProvider` around, for example, user-generated content where the text direction is unknown or may change. This renders a `DirectionProvider` with the `direction` prop automatically set based on the `text` prop provided.

Direction will be determined based on the first strong LTR/RTL character in the `text` string. Strings with no strong direction (e.g., numbers) will inherit the direction from it's nearest `DirectionProider` anscestor.

Usage example:

```js
import AutoDirectionProvider from 'react-with-direction/dist/AutoDirectionProvider';
```

```js
<AutoDirectionProvider text={userGeneratedContent}>
<ExampleComponent>
{userGeneratedContent}
</ExampleComponent>
</AutoDirectionProvider>
```

[package-url]: https://npmjs.org/package/react-with-direction
[npm-version-svg]: http://versionbadg.es/airbnb/react-with-direction.svg
[travis-svg]: https://travis-ci.org/airbnb/react-with-direction.svg
Expand Down
1 change: 1 addition & 0 deletions package.json
Expand Up @@ -25,6 +25,7 @@
"airbnb-prop-types": "^2.8.1",
"brcast": "^2.0.2",
"deepmerge": "^1.5.1",
"direction": "^1.0.1",
"hoist-non-react-statics": "^2.3.1",
"object.assign": "^4.1.0",
"object.values": "^1.0.4",
Expand Down
42 changes: 42 additions & 0 deletions src/AutoDirectionProvider.jsx
@@ -0,0 +1,42 @@
import PropTypes from 'prop-types';
import React from 'react';
import { forbidExtraProps } from 'airbnb-prop-types';
import getDirection from 'direction';
import directionPropType from './proptypes/direction';
import DirectionProvider from './DirectionProvider';
import withDirection from './withDirection';

const propTypes = forbidExtraProps({
children: PropTypes.node.isRequired,
direction: directionPropType.isRequired,
inline: PropTypes.bool,
text: PropTypes.string.isRequired,
});

const defaultProps = {
inline: false,
};

function AutoDirectionProvider({
children,
direction,
inline,
text,
}) {
const textDirection = getDirection(text);
const dir = textDirection === 'neutral' ? direction : textDirection;

return (
<DirectionProvider
direction={dir}
inline={inline}
>
{children}
</DirectionProvider>
);
}

AutoDirectionProvider.propTypes = propTypes;
AutoDirectionProvider.defaultProps = defaultProps;

export default withDirection(AutoDirectionProvider);
90 changes: 90 additions & 0 deletions tests/AutoDirectionProvider_test.jsx
@@ -0,0 +1,90 @@
import React from 'react';
import { expect } from 'chai';
import { shallow } from 'enzyme';

import AutoDirectionProvider from '../src/AutoDirectionProvider';
import DirectionProvider from '../src/DirectionProvider';
import { DIRECTIONS, CHANNEL } from '../src/constants';
import mockBrcast from './mocks/brcast_mock';

describe('<AutoDirectionProvider>', () => {
it('renders a DirectionProvider', () => {
const wrapper = shallow((
<AutoDirectionProvider text="a">
<div />
</AutoDirectionProvider>
)).dive();

expect(wrapper).to.have.exactly(1).descendants(DirectionProvider);
});

describe('direction prop', () => {
it('is LTR correct for LTR strings', () => {
const wrapper = shallow((
<AutoDirectionProvider text="a">
<div />
</AutoDirectionProvider>
)).dive();

expect(wrapper.find(DirectionProvider)).to.have.prop('direction', DIRECTIONS.LTR);
});

it('is RTL correct for RTL strings', () => {
const wrapper = shallow((
<AutoDirectionProvider text="א">
<div />
</AutoDirectionProvider>
)).dive();

expect(wrapper.find(DirectionProvider)).to.have.prop('direction', DIRECTIONS.RTL);
});

it('in inherited from context for neutral strings', () => {
const wrapper = shallow(
(
<AutoDirectionProvider text="1">
<div />
</AutoDirectionProvider>
), {
context: {
[CHANNEL]: mockBrcast({
data: DIRECTIONS.RTL,
}),
},
},
).dive();

expect(wrapper.find(DirectionProvider)).to.have.prop('direction', DIRECTIONS.RTL);
});
});

it('renders its children', () => {
const children = <div>Foo</div>;

const wrapper = shallow((
<AutoDirectionProvider text="a">
{children}
</AutoDirectionProvider>
)).dive();

expect(wrapper).to.contain(children);
});

it('passes the inline prop to DirectionProvider', () => {
let wrapper = shallow((
<AutoDirectionProvider text="a">
<div />
</AutoDirectionProvider>
)).dive();

expect(wrapper.find(DirectionProvider)).to.have.prop('inline', false);

wrapper = shallow((
<AutoDirectionProvider text="a" inline>
<div />
</AutoDirectionProvider>
)).dive();

expect(wrapper.find(DirectionProvider)).to.have.prop('inline', true);
});
});

0 comments on commit ad2095a

Please sign in to comment.