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

refactoring: replace innerComponentRef with ref for Web Components #44

Merged
merged 13 commits into from
Jul 1, 2019
Merged
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",
"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