Skip to content

Commit 34f560a

Browse files
artyorshArtur Yorsh
authored andcommitted
feat(theme): themed-style component (#176)
* chore(playground): themed component example * refactor(theme): module structure * feat(theme): themed component props forward * feat(theme): themed-styles component * refactor(theme): themed-styles component to separate file * test(theme): themed-styles component tests * test(theme): describe themed component and styled themed component tests * chore(playground): remove themed component example * chore(lint): update tslint to ignore arrow-functions semicolon * refactor(theme): fix code style issues * test(theme): add theme change test * refactor(theme): fix code style issues * test(theme): add theme provider overrides parent theme test * refactor(theme): remove untracked files
1 parent b0e1285 commit 34f560a

14 files changed

+371
-152
lines changed
Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
1-
export { ThemeProvider } from './theme-provider.component';
2-
export {
3-
withTheme,
4-
ThemedComponentProps,
5-
} from './theme-consumer.component';
1+
export { ThemeProvider } from './themeProvider.component';
2+
export { withTheme } from './themeConsumer.component';
3+
export { withThemedStyles } from './themeConsumerStyled.component';
4+
export { ThemeType } from './type';

src/framework/theme/component/theme-consumer.component.tsx

Lines changed: 0 additions & 56 deletions
This file was deleted.

src/framework/theme/component/theme-provider.component.tsx

Lines changed: 0 additions & 21 deletions
This file was deleted.
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
import React, { ComponentType } from 'react';
2+
import { ThemeType } from './type';
3+
import {
4+
Consumer,
5+
forwardProps,
6+
} from '../service';
7+
8+
export interface Props<P> extends React.ClassAttributes<P> {
9+
theme: ThemeType;
10+
}
11+
12+
export function withTheme<P extends Props<P>>(Component: ComponentType<P>) {
13+
type TExcept = Exclude<keyof P, keyof Props<P>>;
14+
type ForwardedProps = Pick<P, TExcept>;
15+
16+
class Shadow extends React.Component<ForwardedProps> {
17+
wrappedComponentRef = undefined;
18+
getWrappedInstance = undefined;
19+
20+
setWrappedComponentRef = (ref) => {
21+
this.wrappedComponentRef = ref;
22+
};
23+
24+
renderWrappedComponent = (theme: ThemeType) => (
25+
<Component
26+
ref={this.setWrappedComponentRef}
27+
theme={theme}
28+
{...this.props}
29+
/>
30+
);
31+
32+
render() {
33+
return (
34+
<Consumer>
35+
{this.renderWrappedComponent}
36+
</Consumer>
37+
);
38+
}
39+
}
40+
41+
const Result = Shadow;
42+
Result.prototype.getWrappedInstance = function getWrappedInstance() {
43+
const hasWrappedInstance = this.wrappedComponentRef && this.wrappedComponentRef.getWrappedInstance;
44+
return hasWrappedInstance ? this.wrappedComponentRef.getWrappedInstance() : this.wrappedComponentRef;
45+
};
46+
47+
return forwardProps(Component, Result);
48+
}
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
import React from 'react';
2+
import { StyleSheet } from 'react-native';
3+
import {
4+
ThemeType,
5+
ThemedStyleType,
6+
StyleSheetType,
7+
} from './type';
8+
import {
9+
Consumer,
10+
forwardProps,
11+
} from '../service';
12+
13+
export interface Props<P> extends React.ClassAttributes<P> {
14+
theme: ThemeType;
15+
themedStyle: ThemedStyleType;
16+
}
17+
18+
export function withThemedStyles<P extends Props<P>>(
19+
Component: React.ComponentType<P>,
20+
createStyles: (theme: ThemeType) => StyleSheetType,
21+
) {
22+
type TExcept = Exclude<keyof P, keyof Props<P>>;
23+
type ForwardedProps = Pick<P, TExcept>;
24+
25+
class Shadow extends React.Component<ForwardedProps> {
26+
wrappedComponentRef = undefined;
27+
getWrappedInstance = undefined;
28+
29+
setWrappedComponentRef = (ref) => {
30+
this.wrappedComponentRef = ref;
31+
};
32+
33+
renderWrappedComponent = (theme: ThemeType) => {
34+
const styles = StyleSheet.create(createStyles(theme));
35+
return (
36+
<Component
37+
ref={this.setWrappedComponentRef}
38+
theme={theme}
39+
themedStyle={styles}
40+
{...this.props}
41+
/>
42+
);
43+
};
44+
45+
render() {
46+
return (
47+
<Consumer>
48+
{this.renderWrappedComponent}
49+
</Consumer>
50+
);
51+
}
52+
}
53+
54+
const Result = Shadow;
55+
Result.prototype.getWrappedInstance = function getWrappedInstance() {
56+
const hasWrappedInstance = this.wrappedComponentRef && this.wrappedComponentRef.getWrappedInstance;
57+
return hasWrappedInstance ? this.wrappedComponentRef.getWrappedInstance() : this.wrappedComponentRef;
58+
};
59+
60+
return forwardProps(Component, Result);
61+
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import React, { ReactNode } from 'react';
2+
import { Provider } from '../service';
3+
import { ThemeType } from './type';
4+
5+
interface Props {
6+
children: JSX.Element | ReactNode;
7+
theme: ThemeType;
8+
}
9+
10+
export class ThemeProvider extends React.PureComponent<Props> {
11+
12+
static defaultProps = {
13+
theme: {},
14+
};
15+
16+
render() {
17+
return (
18+
<Provider
19+
value={this.props.theme}>
20+
{this.props.children}
21+
</Provider>
22+
);
23+
}
24+
}

src/framework/theme/component/type.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
// TODO(@type): declare theme types
2+
3+
export type ThemeType = any;
4+
export type ThemedStyleType = any;
5+
export type StyleSheetType = any;

src/framework/theme/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
11
export * from './component';
2+
export * from './service';

src/framework/theme/service/index.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,5 @@
1-
export { Consumer, Provider } from './react-context.service';
2-
export { forwardProps } from './react-props-mapping.service';
1+
export {
2+
Provider,
3+
Consumer,
4+
} from './reactContext.service';
5+
export { forwardProps } from './reactPropsForward.service';

src/framework/theme/service/react-context.service.ts

Lines changed: 0 additions & 5 deletions
This file was deleted.
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import React from 'react';
2+
import { ThemeType } from '../component';
3+
4+
const defaultThemeValue: ThemeType = {};
5+
6+
const {
7+
Provider,
8+
Consumer,
9+
} = React.createContext(defaultThemeValue);
10+
11+
export {
12+
Provider,
13+
Consumer,
14+
};

src/framework/theme/service/react-props-mapping.service.ts renamed to src/framework/theme/service/reactPropsForward.service.ts

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ const REACT_METHODS = [
2626
];
2727

2828
export function forwardProps<P>(Source: ComponentType<P>, Target: ComponentType<P>): ComponentType<P> {
29-
function filterProps(prop) {
29+
const filterProps = (prop) => {
3030
// React specific methods and properties or properties from React's prototype
3131
const isReactProp = REACT_METHODS.includes(prop) || prop in React.Component.prototype;
3232
// Properties from enhanced component's prototype
@@ -35,26 +35,26 @@ export function forwardProps<P>(Source: ComponentType<P>, Target: ComponentType<
3535
const isPrivateProp = prop.startsWith('_');
3636

3737
return !(isReactProp || isTargetProp || isPrivateProp);
38-
}
38+
};
3939

40-
function mapProps(prop) {
40+
const mapProps = (prop) => {
4141
if (typeof Source.prototype[prop] === 'function') {
4242
// Make sure the function is called with correct context
4343
Target.prototype[prop] = function (...args) {
44-
return Source.prototype[prop].apply(this.getConsumingComponent(), args);
44+
return Source.prototype[prop].apply(this.getWrappedInstance(), args);
4545
};
4646
} else {
4747
// Copy properties as getters and setters
4848
Object.defineProperty(Target.prototype, prop, {
4949
get() {
50-
return this.getConsumingComponent()[prop];
50+
return this.getWrappedInstance()[prop];
5151
},
5252
set(value) {
53-
this.getConsumingComponent()[prop] = value;
53+
this.getWrappedInstance()[prop] = value;
5454
},
5555
});
5656
}
57-
}
57+
};
5858

5959
Object.getOwnPropertyNames(Source.prototype).filter(filterProps).forEach(mapProps);
6060

0 commit comments

Comments
 (0)