Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add directional StyleSheet creation based on context #117

Merged
merged 1 commit into from
Nov 6, 2017
Merged
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
6 changes: 4 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -72,12 +72,14 @@
"sinon-sandbox": "^1.0.2"
},
"peerDependencies": {
"react": ">=0.14"
"react": ">=0.14",
"react-with-direction": "^1.1.0"
},
"dependencies": {
"deepmerge": "^1.5.2",
"global-cache": "^1.2.1",
"hoist-non-react-statics": "^2.3.1",
"prop-types": "^15.6.0"
"prop-types": "^15.6.0",
"react-with-direction": "^1.1.0"
Copy link
Collaborator

Choose a reason for hiding this comment

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

let's make this both a dep and a peer dep - a peer dep so there's no conflict in case they use a different version of react-with-direction, and "also a dep" so npm 3+ users don't have to manually add it to package.json.

}
}
15 changes: 12 additions & 3 deletions src/ThemedStyleSheet.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,19 +16,27 @@ function registerInterface(interfaceToRegister) {
styleInterface = interfaceToRegister;
}

function create(makeFromTheme) {
function create(makeFromTheme, createWithDirection) {
// Get an id to associate with this stylesheet
const id = internalId;
internalId += 1;

const { theme, styles } = styleTheme;
styles[id] = styleInterface.create(makeFromTheme(theme));
styles[id] = createWithDirection(makeFromTheme(theme));

makeFromThemes[id] = makeFromTheme;

return () => styleTheme.styles[id];
}

function createLTR(makeFromTheme) {
return create(makeFromTheme, styleInterface.create);
}

function createRTL(makeFromTheme) {
return create(makeFromTheme, styleInterface.createRTL || styleInterface.create);
}

function get() {
return styleTheme.theme;
}
Expand Down Expand Up @@ -59,7 +67,8 @@ export default globalCache.setIfMissingThenGet(
() => ({
registerTheme,
registerInterface,
create,
create: createLTR,
createRTL,
Copy link
Collaborator

Choose a reason for hiding this comment

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

should we also add createLTR, here, for symmetry?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I don't know how much sense that makes if create always defaults to createLTR and I wanna keep create for the default case (and for backwards compatibility)

get,
resolveNoRTL,
resolve,
Expand Down
28 changes: 24 additions & 4 deletions src/withStyles.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@ import PropTypes from 'prop-types';
import hoistNonReactStatics from 'hoist-non-react-statics';
import deepmerge from 'deepmerge';

import { CHANNEL, DIRECTIONS } from 'react-with-direction/dist/constants';
import brcastShape from 'react-with-direction/dist/proptypes/brcast';

import ThemedStyleSheet from './ThemedStyleSheet';

// Add some named exports for convenience.
Expand All @@ -28,6 +31,10 @@ function baseClass(pureComponent) {
return React.Component;
}

const contextTypes = {
[CHANNEL]: brcastShape,
Copy link
Collaborator

Choose a reason for hiding this comment

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

it's unfortunate we're not using withDirection here :-/

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I mean it would add an extra virtual DOM node to ... every single component in our tree... which doesn't seem good. :/

Copy link
Collaborator

Choose a reason for hiding this comment

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

Maybe we could export more things from react-with-direction to avoid as much duplication as possible?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Perhaps we could export directionProviderContextTypes? the grabbing of the direction from the context is also a bit gross.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I'll think about implementing this in a follow up

Copy link
Collaborator

Choose a reason for hiding this comment

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

Yes, we should definitely do that at a minimum.

};

export function withStyles(
styleFn,
{
Expand All @@ -37,18 +44,28 @@ export function withStyles(
pureComponent = false,
} = {},
) {
let styleDef;
let styleDefLTR;
let styleDefRTL;
const BaseClass = baseClass(pureComponent);

return function withStylesHOC(WrappedComponent) {
// NOTE: Use a class here so components are ref-able if need be:
// eslint-disable-next-line react/prefer-stateless-function
class WithStyles extends BaseClass {
componentWillMount() {
// defer StyleSheet creation to run-time
if (!styleDef) {
styleDef = styleFn ? ThemedStyleSheet.create(styleFn) : EMPTY_STYLES_FN;
this.maybeCreateStyles();
}

maybeCreateStyles() {
const direction = this.context[CHANNEL] && this.context[CHANNEL].getState();
const isRTL = direction === DIRECTIONS.RTL;
if (isRTL && !styleDefRTL) {
styleDefRTL = styleFn ? ThemedStyleSheet.createRTL(styleFn) : EMPTY_STYLES_FN;
} else if (!isRTL && !styleDefLTR) {
styleDefLTR = styleFn ? ThemedStyleSheet.create(styleFn) : EMPTY_STYLES_FN;
}

return isRTL ? styleDefRTL : styleDefLTR;
}

render() {
Expand All @@ -63,6 +80,8 @@ export function withStyles(
ThemedStyleSheet.flush();
}

const styleDef = this.maybeCreateStyles();

return (
<WrappedComponent
{...this.props}
Expand All @@ -81,6 +100,7 @@ export function withStyles(

WithStyles.WrappedComponent = WrappedComponent;
WithStyles.displayName = `withStyles(${wrappedComponentName})`;
WithStyles.contextTypes = contextTypes;
if (WrappedComponent.propTypes) {
WithStyles.propTypes = deepmerge({}, WrappedComponent.propTypes);
delete WithStyles.propTypes[stylesPropName];
Expand Down
53 changes: 49 additions & 4 deletions test/withStyles_test.jsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import React from 'react';
import PropTypes from 'prop-types';
import { expect } from 'chai';
import { shallow } from 'enzyme';
import { render, shallow } from 'enzyme';
import deepmerge from 'deepmerge';
import sinon from 'sinon-sandbox';
import DirectionProvider, { DIRECTIONS } from 'react-with-direction/dist/DirectionProvider';

import ThemedStyleSheet from '../src/ThemedStyleSheet';
import { css, cssNoRTL, withStyles, withStylesPropTypes } from '../src/withStyles';
Expand Down Expand Up @@ -63,7 +64,6 @@ describe('withStyles()', () => {
expect(testInterface.create.callCount).to.equal(1);
});


it('has a wrapped displayName', () => {
function MyComponent() {
return null;
Expand Down Expand Up @@ -263,18 +263,23 @@ describe('RTL support', () => {
let testInterface;
let resolveStub;
let resolveNoRTLStub;
let createStub;
let createRTLStub;

beforeEach(() => {
resolveStub = sinon.stub();
resolveNoRTLStub = sinon.stub();

createStub = sinon.stub();
createRTLStub = sinon.stub();

testInterface = {
create() {},
create: createStub,
createRTL: createRTLStub,
resolve: resolveStub,
resolveNoRTL: resolveNoRTLStub,
flush: sinon.spy(),
};
sinon.stub(testInterface, 'create').callsFake(styleHash => styleHash);

ThemedStyleSheet.registerTheme({});
ThemedStyleSheet.registerInterface(testInterface);
Expand Down Expand Up @@ -305,4 +310,44 @@ describe('RTL support', () => {
expect(resolveNoRTLStub.callCount).to.equal(1);
});
});

describe('contextual create', () => {
it('calls ThemedStyleSheet.create without direction set', () => {
function MyComponent() {
return null;
}

const WrappedComponent = withStyles(() => ({}))(MyComponent);
render(<WrappedComponent />);
expect(testInterface.create).to.have.property('callCount', 1);
});

it('calls ThemedStyleSheet.create with LTR direction', () => {
function MyComponent() {
return null;
}

const WrappedComponent = withStyles(() => ({}))(MyComponent);
render(
<DirectionProvider direction={DIRECTIONS.LTR}>
<WrappedComponent />
</DirectionProvider>,
);
expect(testInterface.create).to.have.property('callCount', 1);
});

it('calls ThemedStyleSheet.createRTL with RTL direction', () => {
function MyComponent() {
return null;
}

const WrappedComponent = withStyles(() => ({}))(MyComponent);
render(
<DirectionProvider direction={DIRECTIONS.RTL}>
<WrappedComponent />
</DirectionProvider>,
);
expect(testInterface.createRTL).to.have.property('callCount', 1);
});
});
});