Skip to content

Instantly share code, notes, and snippets.

@dested
Created December 23, 2018 21:28
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save dested/0ffc077414fa5d162190bc50c15046c2 to your computer and use it in GitHub Desktop.
Save dested/0ffc077414fa5d162190bc50c15046c2 to your computer and use it in GitHub Desktop.
import React from 'react';
import {Image, ScrollView, StyleProp, TextStyle, View, ViewProps, ViewStyle} from 'react-native';
export interface WithCssProps {
classNames?: string[];
parentComponentTree?: ComponentTree;
siblingsLength?: number;
childIndex?: number;
style?: StyleProp<TextStyle>;
}
type ComponentTree = {
classNames: string[];
parent?: ComponentTree;
siblingsLength?: number;
childIndex?: number;
};
const firstChildRegex = /([\w-\d]+):first-child/;
const lastChildRegex = /([\w-\d]+):last-child/;
const satisfiesStyle = (styleKey: string, componentTree: ComponentTree) => {
const keys = styleKey.split(' ');
let currentComponentTree = componentTree;
let i: number;
for (i = keys.length - 1; i >= 0; ) {
const key = keys[i];
if (!currentComponentTree) {
return false;
}
if (currentComponentTree.classNames) {
if (key === '*') {
i--;
continue;
}
if (currentComponentTree.classNames.includes(key)) {
i--;
continue;
}
const firstChildKey = key.match(firstChildRegex);
if (
firstChildKey &&
currentComponentTree.classNames.includes(firstChildKey[1]) &&
currentComponentTree.childIndex === 0
) {
i--;
continue;
}
const lastChildKey = key.match(lastChildRegex);
if (
lastChildKey &&
currentComponentTree.classNames.includes(lastChildKey[1]) &&
currentComponentTree.childIndex === currentComponentTree.siblingsLength - 1
) {
i--;
continue;
}
}
if (keys.length - 1 === i) {
// it must match the most specific key
return false;
}
currentComponentTree = currentComponentTree.parent;
}
return i === -1;
};
const deriveStyles = (styles: any, componentTree: ComponentTree) => {
const dStyles: ViewStyle[] = [];
if (componentTree.classNames) {
for (const styleKey of Object.keys(styles)) {
if (satisfiesStyle(styleKey, componentTree)) {
dStyles.push((styles as any)[styleKey]);
}
}
}
return dStyles;
};
export function withCss<P extends ViewProps>(
WrappedComponent: React.ComponentClass<P> | React.FunctionComponent<P>
): React.ComponentClass<P & WithCssProps> {
return class extends React.Component<P & WithCssProps> {
constructor(props: P & WithCssProps) {
super(props);
this.state = {};
this.componentTree = {
parent: this.props.parentComponentTree,
classNames: this.props.classNames,
childIndex: props.childIndex,
siblingsLength: props.siblingsLength,
};
}
componentTree: ComponentTree;
componentWillUpdate(nextProps: Readonly<P & WithCssProps>): void {
this.componentTree.siblingsLength = nextProps.siblingsLength;
this.componentTree.childIndex = nextProps.childIndex;
this.componentTree.classNames = nextProps.classNames;
}
render() {
const {style, children, ...props} = this.props;
try {
return (
<CssContext.Consumer>
{state => {
const siblingsLength = React.Children.count(children);
const derivedStyles = this.componentTree.classNames && deriveStyles(state, this.componentTree);
console.log(this.componentTree.classNames, JSON.stringify(derivedStyles));
return (
<WrappedComponent style={[derivedStyles, style]} {...props}>
{React.Children.map(children, (a, i) =>
React.cloneElement(a, {parentComponentTree: this.componentTree, childIndex: i, siblingsLength})
)}
</WrappedComponent>
);
}}
</CssContext.Consumer>
);
} catch (ex) {
console.error(ex);
return undefined;
}
}
};
}
export const CView = withCss(View);
export const CScrollView = withCss(ScrollView);
export const CImage = withCss(Image);
const store: any = {};
export const CssContext = React.createContext(store);
export class Screen extends Component<Props, State> {
render() {
return (
<CssContext.Provider value={styles}>
<CScrollView classNames={['form']}>
<CImage classNames={['full-image']} source={{uri: someImage}} />
<CView classNames={['form-body']}>
<CInputBox classNames={['form-element']} value={this.state.text} onChangeText={text => this.setState({text})} />
<CInputBox classNames={['form-element']} value={this.state.text} />
<CInputBox classNames={['form-element']} value={this.state.text} />
<CInputBox classNames={['form-element']} value={this.state.text} />
</CView>
<CView classNames={['divider']} />
<CView classNames={['form-body']}>
<CInputBox classNames={['form-element']} value={this.state.text} />
<CInputBox classNames={['form-element']} value={this.state.text} />
</CView>
<CImage classNames={['full-image']} source={{uri: someImage}} />
<CView classNames={['form-body']}>
<CInputBox classNames={['form-element']} value={this.state.text} />
<CInputBox classNames={['form-element']} value={this.state.text} />
<CInputBox classNames={['form-element']} value={this.state.text} />
<CInputBox classNames={['form-element']} value={this.state.text} />
</CView>
</CScrollView>
</CssContext.Provider>
);
}
}
const styles = StyleSheet.create({
form: {
backgroundColor: '#f0f0f0',
},
'form form-element': {
backgroundColor: '#dcdcdc',
marginVertical: 5,
marginHorizontal: 32,
},
'form form-element:first-child': {
marginTop: 32,
},
'form form-element:last-child': {
marginBottom: 32,
},
'form divider': {
width: '100%',
height: 5,
backgroundColor: 'black',
},
'form full-image': {
width: '100%',
height: UI.s.height * 0.3,
},
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment