Skip to content
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
59 changes: 51 additions & 8 deletions src/ThemedStyleSheet.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,25 +11,68 @@ function registerInterface(interfaceToRegister) {
styleInterface = interfaceToRegister;
}

function extendStyles(makeFromTheme, extendFromTheme = []) {
function validateStyle(style, extendableStyles, currentPath = '') {
if (style === null || Array.isArray(style) || typeof style !== 'object') {
return;
}

const styleKeys = Object.keys(style);
if (styleKeys.length) {
styleKeys.forEach((styleKey) => {
const path = `${currentPath}.${styleKey}`;
const isValid = extendableStyles[styleKey];
if (!isValid) {
throw new Error(
`withStyles() extending style is invalid: ${path}. If this style is expected, add it to`
+ 'the component\'s "extendableStyles" option.',
);
}
validateStyle(style[styleKey], extendableStyles[styleKey], path);
});
}
}

function validateAndMergeStyles(makeFromTheme, extendFromTheme = [], extendableStyles) {
const baseStyle = makeFromTheme(styleTheme);
const extendedStyles = extendFromTheme.map(extendStyleFn => extendStyleFn(styleTheme));
const extendedStyles = extendFromTheme.map((extendStyleFn) => {
const style = extendStyleFn(styleTheme);

if (process.env.NODE_ENV !== 'production') {
validateStyle(style, extendableStyles);
}

return style;
});

return deepmerge.all([baseStyle, ...extendedStyles]);
}

function create(makeFromTheme, createWithDirection, extendFromTheme) {
const styles = createWithDirection(extendStyles(makeFromTheme, extendFromTheme));
function create(makeFromTheme, createWithDirection, extendFromTheme, extendableStyles) {
const styles = createWithDirection(validateAndMergeStyles(
makeFromTheme,
extendFromTheme,
extendableStyles,
));

return () => styles;
}

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

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

function get() {
Expand Down
5 changes: 3 additions & 2 deletions src/withStyles.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ export function withStyles(
themePropName = 'theme',
cssPropName = 'css',
extendStyleFnPropName = '_extendStyleFn',
extendableStyles = {},
flushBefore = false,
pureComponent = false,
} = {},
Expand Down Expand Up @@ -97,14 +98,14 @@ export function withStyles(

if (isRTL) {
styleDefRTL = styleFn
? ThemedStyleSheet.createRTL(styleFn, extendStyleFns)
? ThemedStyleSheet.createRTL(styleFn, extendStyleFns, extendableStyles)
: EMPTY_STYLES_FN;

currentThemeRTL = registeredTheme;
styleDef = styleDefRTL;
} else {
styleDefLTR = styleFn
? ThemedStyleSheet.createLTR(styleFn, extendStyleFns)
? ThemedStyleSheet.createLTR(styleFn, extendStyleFns, extendableStyles)
: EMPTY_STYLES_FN;

currentThemeLTR = registeredTheme;
Expand Down
176 changes: 145 additions & 31 deletions test/withExtendStyles_test.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -95,12 +95,21 @@ describe('withExtendStyles()', () => {
return null;
}

const WrappedComponent = withStyles(() => ({
container: {
background: 'red',
color: 'blue',
const WrappedComponent = withStyles(
() => ({
container: {
background: 'red',
color: 'blue',
},
}),
{
extendableStyles: {
container: {
background: true,
},
},
},
}))(MyComponent);
)(MyComponent);
const WrappedComponentWithExtendedStyles = withExtendStyles(() => ({
container: {
background: 'green',
Expand All @@ -124,19 +133,34 @@ describe('withExtendStyles()', () => {
return null;
}

const WrappedComponent = withStyles(() => ({
container: {
background: 'red',
color: 'blue',
},
innerContainer: {
background: 'white',
border: '1px solid black',
},
content: {
fontSize: '25px',
const WrappedComponent = withStyles(
() => ({
container: {
background: 'red',
color: 'blue',
},
innerContainer: {
background: 'white',
border: '1px solid black',
},
content: {
fontSize: '25px',
},
}),
{
extendableStyles: {
container: {
background: true,
},
innerContainer: {
border: true,
},
content: {
fontSize: true,
},
},
},
}))(MyComponent);
)(MyComponent);
const WrappedComponentWithExtendedStyles = withExtendStyles(() => ({
container: {
background: 'green',
Expand Down Expand Up @@ -173,13 +197,23 @@ describe('withExtendStyles()', () => {
return null;
}

const WrappedComponent = withStyles(() => ({
container: {
background: 'red',
color: 'blue',
fontSize: '10px',
const WrappedComponent = withStyles(
() => ({
container: {
background: 'red',
color: 'blue',
fontSize: '10px',
},
}),
{
extendableStyles: {
container: {
background: true,
fontSize: true,
},
},
},
}))(MyComponent);
)(MyComponent);

const WrappedComponentWithExtendedStyles = withExtendStyles(() => ({
container: {
Expand All @@ -188,14 +222,14 @@ describe('withExtendStyles()', () => {
},
}))(WrappedComponent);

const WrappedComponentWithNestedCustomStyles = withExtendStyles(() => ({
const WrappedComponentWithNestedExtendedStyles = withExtendStyles(() => ({
container: {
fontSize: '20px',
},
}))(WrappedComponentWithExtendedStyles);

render(
<WrappedComponentWithNestedCustomStyles />,
<WrappedComponentWithNestedExtendedStyles />,
);

expect(testInterface.createLTR.callCount).to.equal(1);
Expand All @@ -208,7 +242,71 @@ describe('withExtendStyles()', () => {
});
});

it('uses original base styles if no extended styles are provided', () => {
it('throws an error if an invalid extending style is provided', () => {
function MyComponent() {
return null;
}

const WrappedComponent = withStyles(
() => ({
container: {
background: 'red',
color: 'blue',
fontSize: '10px',
},
}),
{
extendableStyles: {
container: {
background: true,
},
},
},
)(MyComponent);

const WrappedComponentWithExtendedStyles = withExtendStyles(() => ({
container: {
// fontSize is invalid
fontSize: '12px',
},
}))(WrappedComponent);

expect(() => render(
<WrappedComponentWithExtendedStyles />,
)).to.throw();
});

it('throws an error if extendableStyles is not defined, and an extending style is provided', () => {
function MyComponent() {
return null;
}

const WrappedComponent = withStyles(
() => ({
container: {
background: 'red',
color: 'blue',
fontSize: '10px',
},
}),
{
// no extendableStyles
},
)(MyComponent);

const WrappedComponentWithExtendedStyles = withExtendStyles(() => ({
container: {
// fontSize is invalid
fontSize: '12px',
},
}))(WrappedComponent);

expect(() => render(
<WrappedComponentWithExtendedStyles />,
)).to.throw();
});

it('uses original base styles if no extending styles are provided', () => {
function MyComponent() {
return null;
}
Expand Down Expand Up @@ -261,7 +359,14 @@ describe('withExtendStyles()', () => {
color: 'blue',
},
}),
{ extendStyleFnPropName: 'foobar' },
{
extendStyleFnPropName: 'foobar',
extendableStyles: {
container: {
background: true,
},
},
},
)(MyComponent);
const WrappedComponentWithExtendedStyles = withExtendStyles(
() => ({
Expand Down Expand Up @@ -290,11 +395,20 @@ describe('withExtendStyles()', () => {
return null;
}

const WrappedComponent = withStyles(({ color }) => ({
foo: {
color: color.red,
const WrappedComponent = withStyles(
({ color }) => ({
foo: {
color: color.red,
},
}),
{
extendableStyles: {
foo: {
color: true,
},
},
},
}))(MyComponent);
)(MyComponent);
const WrappedComponentWithExtendedStyles = withExtendStyles(({ color }) => ({
foo: {
color: color.blue,
Expand Down
Loading