Skip to content

Commit

Permalink
feat(theme): themed-style component (#176)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
artyorsh authored and Artur Yorsh committed Nov 27, 2018
1 parent b0e1285 commit 34f560a
Show file tree
Hide file tree
Showing 14 changed files with 371 additions and 152 deletions.
9 changes: 4 additions & 5 deletions src/framework/theme/component/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
export { ThemeProvider } from './theme-provider.component';
export {
withTheme,
ThemedComponentProps,
} from './theme-consumer.component';
export { ThemeProvider } from './themeProvider.component';
export { withTheme } from './themeConsumer.component';
export { withThemedStyles } from './themeConsumerStyled.component';
export { ThemeType } from './type';
56 changes: 0 additions & 56 deletions src/framework/theme/component/theme-consumer.component.tsx

This file was deleted.

21 changes: 0 additions & 21 deletions src/framework/theme/component/theme-provider.component.tsx

This file was deleted.

48 changes: 48 additions & 0 deletions src/framework/theme/component/themeConsumer.component.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import React, { ComponentType } from 'react';
import { ThemeType } from './type';
import {
Consumer,
forwardProps,
} from '../service';

export interface Props<P> extends React.ClassAttributes<P> {
theme: ThemeType;
}

export function withTheme<P extends Props<P>>(Component: ComponentType<P>) {
type TExcept = Exclude<keyof P, keyof Props<P>>;
type ForwardedProps = Pick<P, TExcept>;

class Shadow extends React.Component<ForwardedProps> {
wrappedComponentRef = undefined;
getWrappedInstance = undefined;

setWrappedComponentRef = (ref) => {
this.wrappedComponentRef = ref;
};

renderWrappedComponent = (theme: ThemeType) => (
<Component
ref={this.setWrappedComponentRef}
theme={theme}
{...this.props}
/>
);

render() {
return (
<Consumer>
{this.renderWrappedComponent}
</Consumer>
);
}
}

const Result = Shadow;
Result.prototype.getWrappedInstance = function getWrappedInstance() {
const hasWrappedInstance = this.wrappedComponentRef && this.wrappedComponentRef.getWrappedInstance;
return hasWrappedInstance ? this.wrappedComponentRef.getWrappedInstance() : this.wrappedComponentRef;
};

return forwardProps(Component, Result);
}
61 changes: 61 additions & 0 deletions src/framework/theme/component/themeConsumerStyled.component.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import React from 'react';
import { StyleSheet } from 'react-native';
import {
ThemeType,
ThemedStyleType,
StyleSheetType,
} from './type';
import {
Consumer,
forwardProps,
} from '../service';

export interface Props<P> extends React.ClassAttributes<P> {
theme: ThemeType;
themedStyle: ThemedStyleType;
}

export function withThemedStyles<P extends Props<P>>(
Component: React.ComponentType<P>,
createStyles: (theme: ThemeType) => StyleSheetType,
) {
type TExcept = Exclude<keyof P, keyof Props<P>>;
type ForwardedProps = Pick<P, TExcept>;

class Shadow extends React.Component<ForwardedProps> {
wrappedComponentRef = undefined;
getWrappedInstance = undefined;

setWrappedComponentRef = (ref) => {
this.wrappedComponentRef = ref;
};

renderWrappedComponent = (theme: ThemeType) => {
const styles = StyleSheet.create(createStyles(theme));
return (
<Component
ref={this.setWrappedComponentRef}
theme={theme}
themedStyle={styles}
{...this.props}
/>
);
};

render() {
return (
<Consumer>
{this.renderWrappedComponent}
</Consumer>
);
}
}

const Result = Shadow;
Result.prototype.getWrappedInstance = function getWrappedInstance() {
const hasWrappedInstance = this.wrappedComponentRef && this.wrappedComponentRef.getWrappedInstance;
return hasWrappedInstance ? this.wrappedComponentRef.getWrappedInstance() : this.wrappedComponentRef;
};

return forwardProps(Component, Result);
}
24 changes: 24 additions & 0 deletions src/framework/theme/component/themeProvider.component.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import React, { ReactNode } from 'react';
import { Provider } from '../service';
import { ThemeType } from './type';

interface Props {
children: JSX.Element | ReactNode;
theme: ThemeType;
}

export class ThemeProvider extends React.PureComponent<Props> {

static defaultProps = {
theme: {},
};

render() {
return (
<Provider
value={this.props.theme}>
{this.props.children}
</Provider>
);
}
}
5 changes: 5 additions & 0 deletions src/framework/theme/component/type.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
// TODO(@type): declare theme types

export type ThemeType = any;
export type ThemedStyleType = any;
export type StyleSheetType = any;
1 change: 1 addition & 0 deletions src/framework/theme/index.ts
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
export * from './component';
export * from './service';
7 changes: 5 additions & 2 deletions src/framework/theme/service/index.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,5 @@
export { Consumer, Provider } from './react-context.service';
export { forwardProps } from './react-props-mapping.service';
export {
Provider,
Consumer,
} from './reactContext.service';
export { forwardProps } from './reactPropsForward.service';
5 changes: 0 additions & 5 deletions src/framework/theme/service/react-context.service.ts

This file was deleted.

14 changes: 14 additions & 0 deletions src/framework/theme/service/reactContext.service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import React from 'react';
import { ThemeType } from '../component';

const defaultThemeValue: ThemeType = {};

const {
Provider,
Consumer,
} = React.createContext(defaultThemeValue);

export {
Provider,
Consumer,
};
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ const REACT_METHODS = [
];

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

return !(isReactProp || isTargetProp || isPrivateProp);
}
};

function mapProps(prop) {
const mapProps = (prop) => {
if (typeof Source.prototype[prop] === 'function') {
// Make sure the function is called with correct context
Target.prototype[prop] = function (...args) {
return Source.prototype[prop].apply(this.getConsumingComponent(), args);
return Source.prototype[prop].apply(this.getWrappedInstance(), args);
};
} else {
// Copy properties as getters and setters
Object.defineProperty(Target.prototype, prop, {
get() {
return this.getConsumingComponent()[prop];
return this.getWrappedInstance()[prop];
},
set(value) {
this.getConsumingComponent()[prop] = value;
this.getWrappedInstance()[prop] = value;
},
});
}
}
};

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

Expand Down
Loading

0 comments on commit 34f560a

Please sign in to comment.