Created
December 23, 2018 21:28
-
-
Save dested/0ffc077414fa5d162190bc50c15046c2 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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