Skip to content
Merged
Show file tree
Hide file tree
Changes from 10 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
2 changes: 1 addition & 1 deletion packages/base/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
"core-js": "^3.1.4",
"deepmerge": "^3.2.0",
"hoist-non-react-statics": "^3.3.0",
"react-jss": "^8.6.1",
"react-jss": "^10.0.0-alpha.21",
Copy link
Contributor

Choose a reason for hiding this comment

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

I think we should use a fixed version for the alpha release

Suggested change
"react-jss": "^10.0.0-alpha.21",
"react-jss": "10.0.0-alpha.21",

"tslib": "^1.9.3"
}
}
5 changes: 3 additions & 2 deletions packages/base/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,8 @@ import KeyCodes from './KeyCodes';
import { LOG_LEVEL, Logger } from './Logger';
import Optional from './Optional';
import StyleClassHelper from './StyleClassHelper';

import { deprecationNotice, pushElementBackInScreen } from './Util';
import { createGenerateClassName } from './withStyles/createGenerateClassName';

export {
StyleClassHelper,
Expand All @@ -35,5 +35,6 @@ export {
HSLColor,
sap_fiori_3,
bootstrap,
withStyles
withStyles,
createGenerateClassName
};
78 changes: 23 additions & 55 deletions packages/base/src/withStyles/index.tsx
Original file line number Diff line number Diff line change
@@ -1,66 +1,34 @@
import deepMerge from 'deepmerge';
import hoistNonReactStatics from 'hoist-non-react-statics';
import React, { ComponentType, Ref } from 'react';
import injectSheet from 'react-jss';
import { createGenerateClassName } from './createGenerateClassName';

const generateClassName = createGenerateClassName();

const getStyle = (style) => (...args) => (typeof style === 'function' ? style(...args) : style);

const mergeStyles = (styles, ...newStyles) => {
return (...args) =>
deepMerge.all([getStyle(styles)(...args), ...newStyles.map((newStyle) => getStyle(newStyle)(...args))]);
};
import React, { ComponentType, ForwardRefExoticComponent, RefAttributes, RefObject } from 'react';
// @ts-ignore
import { createUseStyles, useTheme } from 'react-jss';

const getDisplayName = (Component) => Component.displayName || Component.name || 'Component';
const wrapComponentName = (componentName) => `WithStyles(${componentName})`;

export interface WithStylesPropTypes {
innerComponentRef: Ref<any>;
export interface WithStylesComponent<T = {}> extends ForwardRefExoticComponent<RefAttributes<T>> {
InnerComponent?: ComponentType<any>;
}

export const withStyles = (styles) => (Component: ComponentType<any>) => {
class WithStyles extends React.Component<WithStylesPropTypes> {
static defaultProps = Component.defaultProps;
static InnerComponent = Component;
static displayName = wrapComponentName(getDisplayName(Component));
static withCustomStyles = (...newStyles) => {
return withStyles(mergeStyles(styles, ...newStyles))(Component);
};
export function withStyles<T>(styles): any {
return (Component: ComponentType<T>) => {
const displayName = wrapComponentName(getDisplayName(Component));

state = {
error: false
};
const useStyles = createUseStyles(styles, {
name: displayName
});

componentDidUpdate(prevProps) {
if (prevProps !== this.props) {
// retry rendering of Component
this.setState({ error: false });
}
}
const WithStyles: WithStylesComponent<T> = React.forwardRef((props: T, ref: RefObject<any>) => {
const classes = useStyles(props);
const theme = useTheme();

componentDidCatch(error, info) {
// Logger.error(error.message, Component.displayName || WithStyles.displayName);
this.setState({ error: true });
}
return <Component {...props} ref={ref} classes={classes} theme={theme} />;
});

render() {
const { innerComponentRef, ...rest } = this.props;
const { error } = this.state;

// props containing theme, classes generated by react-jss as well as
// user defined props
if (!error) {
return <Component ref={innerComponentRef} {...rest} />;
} else {
return null;
}
}
}

hoistNonReactStatics(WithStyles, Component);
return injectSheet(styles, {
generateClassName
})(WithStyles);
};
WithStyles.defaultProps = Component.defaultProps;
WithStyles.displayName = displayName;
WithStyles.InnerComponent = Component;
hoistNonReactStatics(WithStyles, Component);
return WithStyles;
};
}
63 changes: 3 additions & 60 deletions packages/base/src/withStyles/withStyles.test.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,9 @@
import React, { cloneElement, Component } from 'react';
import React, { Component } from 'react';
import { withStyles } from './index';
import { mountThemedComponent } from '@shared/tests/utils';
import sinon from 'sinon';

const testAttribute = 'TEST';

class UnitTestDemo extends Component<any> {
static testAttribute = testAttribute;
render() {
const { classes } = this.props;
if (!classes) {
Expand All @@ -16,71 +13,17 @@ class UnitTestDemo extends Component<any> {
}
}

const UnitTestDemoSfc = (props) => {
const { classes } = props;
if (!classes) {
throw 'No classes passed down';
}
return <div />;
};

// @ts-ignore
UnitTestDemoSfc.testAttribute = testAttribute;

const FaultyComponent = withStyles(() => ({}))((props) => {
if (props.fail) {
throw 'Oooops';
}

return <div>All fine!</div>;
});

const styles = () => ({
testClass: {
color: 'green'
}
});

describe('withStyles', () => {
test('Extend Class Component with Styles and check statics', () => {
const DemoWithStyles = withStyles(styles)(UnitTestDemo);
expect(DemoWithStyles.testAttribute).toBe(testAttribute);
expect(DemoWithStyles.InnerComponent).toBe(UnitTestDemo);

const ExtendedDemo = DemoWithStyles.withCustomStyles({ testClass: { color: 'black' } });
expect(ExtendedDemo.testAttribute).toBe(testAttribute);
expect(ExtendedDemo.InnerComponent).toBe(UnitTestDemo);
});

test('render styled component without crashing', () => {
const DemoWithStyles = withStyles(styles)(UnitTestDemo);
mountThemedComponent(<DemoWithStyles />);
const ExtendedDemo = DemoWithStyles.withCustomStyles({ testClass: { color: 'black' } });
mountThemedComponent(<ExtendedDemo />);
});

test('innerComponentRef', () => {
test('component ref', () => {
const callback = sinon.spy();
const DemoWithStyles = withStyles(styles)(UnitTestDemo);
mountThemedComponent(<DemoWithStyles innerComponentRef={callback} />);
mountThemedComponent(<DemoWithStyles ref={callback} />);
expect(callback.callCount).toBe(1);
});

test('test with SFC', () => {
const DemoWithStyles = withStyles(styles)(UnitTestDemoSfc);
mountThemedComponent(<DemoWithStyles />);
expect(DemoWithStyles.testAttribute).toBe(testAttribute);
const ExtendedDemo = DemoWithStyles.withCustomStyles({ testClass: { color: 'black' } });
mountThemedComponent(<ExtendedDemo />);
expect(ExtendedDemo.testAttribute).toBe(testAttribute);
});

test('Error Boundary', () => {
const wrapper = mountThemedComponent(<FaultyComponent fail />);
expect((wrapper.find('WithStyles(Component)').instance().state as any).error).toBe(true);
wrapper.setProps({
children: cloneElement(wrapper.props().children, { fail: false })
});
expect((wrapper.find('WithStyles(Component)').instance().state as any).error).toBe(false);
});
});
30 changes: 13 additions & 17 deletions packages/main/__karma_snapshots__/ActionSheet.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,30 +4,26 @@

```
<ThemeProvider withToastContainer={false}>
<ThemeProvider jss={{...}} theme={{...}}>
<Jss(WithStyles(ActionSheet)) openBy={{...}} placement="Bottom">
<WithStyles(ActionSheet) openBy={{...}} placement="Bottom" classes={{...}}>
<ActionSheet openBy={{...}} placement="Bottom" classes={{...}}>
<Popover noHeader={true} innerComponentRef={[Function]} openBy={{...}} placementType="Bottom" style={[undefined]} data-ui5-slot={[undefined]} initialFocus={{...}} headerText="" horizontalAlign="Center" verticalAlign="Center">
<JssProvider generateId={[Function]} id={{...}}>
<ThemeProvider theme={{...}}>
<WithStyles(ActionSheet) openBy={{...}} placement="Bottom">
<ActionSheet openBy={{...}} placement="Bottom" classes={{...}} theme={{...}}>
<Popover noHeader={true} openBy={{...}} placementType="Bottom" style={[undefined]} data-ui5-slot={[undefined]} initialFocus={{...}} headerText="" horizontalAlign="Center" verticalAlign="Center">
<div style={{...}} onClick={[Function]}>
<Button design="Default">
<WithWebComponent theme={{...}} design="Default">
<ui5-button design="Default" class="" />
</WithWebComponent>
<ui5-button design="Default" class="" />
</Button>
</div>
<WithTheme(WithWebComponent) noHeader={true} innerComponentRef={[Function]} placementType="Bottom" style={[undefined]} data-ui5-slot={[undefined]} initialFocus={{...}} headerText="" horizontalAlign="Center" verticalAlign="Center">
<WithWebComponent theme={{...}} noHeader={true} innerComponentRef={[Function]} placementType="Bottom" style={[undefined]} data-ui5-slot={[undefined]} initialFocus={{...}} headerText="" horizontalAlign="Center" verticalAlign="Center">
<ui5-popover no-header={true} inner-component-ref={[Function]} placement-type="Bottom" style={[undefined]} data-ui5-slot={[undefined]} initial-focus={{...}} header-text="" horizontal-align="Center" vertical-align="Center" class="">
<ul className="ActionSheet-actionSheet---" />
</ui5-popover>
</WithWebComponent>
</WithTheme(WithWebComponent)>
<WithWebComponent(Popover) noHeader={true} placementType="Bottom" style={[undefined]} data-ui5-slot={[undefined]} initialFocus={{...}} headerText="" horizontalAlign="Center" verticalAlign="Center">
<ui5-popover no-header={true} placement-type="Bottom" style={[undefined]} data-ui5-slot={[undefined]} initial-focus={{...}} header-text="" horizontal-align="Center" vertical-align="Center" class="">
<ul className="ActionSheet-actionSheet---" />
</ui5-popover>
</WithWebComponent(Popover)>
</Popover>
</ActionSheet>
</WithStyles(ActionSheet)>
</Jss(WithStyles(ActionSheet))>
</ThemeProvider>
</ThemeProvider>
</JssProvider>
</ThemeProvider>
```

Loading