Skip to content

Commit

Permalink
feat(components): add theming hooks
Browse files Browse the repository at this point in the history
  • Loading branch information
artyorsh authored Dec 20, 2019
1 parent 40987be commit 67a8f8f
Show file tree
Hide file tree
Showing 52 changed files with 1,402 additions and 653 deletions.
4 changes: 3 additions & 1 deletion docs/src/structure.ts
Original file line number Diff line number Diff line change
Expand Up @@ -232,8 +232,10 @@ export const structure = [
{
type: 'tabs',
name: 'Themed Component',
icon: 'with-styles.svg',
icon: 'withStyles.svg',
source: [
'useTheme',
'useStyleSheet',
'withStyles',
],
},
Expand Down
2 changes: 1 addition & 1 deletion src/components/theme/application/application.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ import {
theme,
themeInverse,
} from '../support/tests';
import { ThemeType } from '@kitten/theme';
import { ThemeType } from '../style/styleSheet.service';

describe('@app: application wrapper check', () => {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ import {
import { StyleProvider } from '../style/styleProvider.component';
import { ThemeProviderProps } from '../theme/themeProvider.component';
import { ModalPanel } from '../modal/modalPanel.component';
import { ThemeType } from '../theme/type';

interface ComponentProps {
mapping: SchemaType;
Expand Down Expand Up @@ -78,7 +77,7 @@ export class ApplicationProvider extends React.Component<ApplicationProviderProp

constructor(props: ApplicationProviderProps) {
super(props);
const { mapping, customMapping, theme } = this.props;
const { mapping, customMapping } = this.props;

const styles: ThemeStyleType = this.createStyles(mapping, customMapping);

Expand Down
39 changes: 20 additions & 19 deletions src/components/theme/index.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,3 @@
export {
styled,
StyledComponentProps,
StyledComponentClass,
} from './style/styleConsumer.component';
export {
withStyles,
ThemedComponentProps,
ThemedComponentClass,
} from './theme/themeConsumer.component';
export {
ApplicationProvider,
ApplicationProviderProps,
Expand All @@ -17,21 +7,32 @@ export {
ModalPanel,
ModalPanelProps,
} from './modal/modalPanel.component';
export {
ThemeProvider,
ThemeProviderProps,
} from './theme/themeProvider.component';
export {
ModalService,
ModalPresentingConfig,
} from './modal/modal.service';
export {
styled,
StyledComponentProps,
StyledComponentClass,
} from './style/styled';
export { useStyleSheet } from './style/useStyleSheet';
export {
StyleType,
Styles,
ThemeType,
} from './style/styleSheet.service';
export {
Interaction,
State,
StyleType,
} from './style/type';
export {
ThemeType,
ThemedStyleType,
StyleSheetType,
} from './theme/type';
ThemeProvider,
ThemeProviderProps,
} from './theme/themeProvider.component';
export {
withStyles,
ThemedComponentProps,
ThemedComponentClass,
} from './theme/withStyles';
export { useTheme } from './theme/useTheme';
18 changes: 0 additions & 18 deletions src/components/theme/style/style.service.ts

This file was deleted.

64 changes: 39 additions & 25 deletions src/components/theme/style/style.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import {
TouchableOpacity,
View,
ViewProps,
ViewStyle,
} from 'react-native';
import {
fireEvent,
Expand All @@ -18,18 +19,13 @@ import {
import {
styled,
StyledComponentProps,
ContextProps,
} from './styleConsumer.component';
} from './styled';
import { StyleConsumerService } from './styleConsumer.service';
import { createThemedStyle } from './style.service';
import { Interaction } from './type';
import {
Interaction,
StyleType,
} from './type';
import {
ThemedStyleType,
StyleSheet,
ThemeType,
} from '../theme/type';
} from './styleSheet.service';
import {
styles,
theme,
Expand Down Expand Up @@ -98,12 +94,8 @@ const json = (value: any): string => JSON.stringify(value);

describe('@style: consumer service methods check', () => {

const context: ContextProps = {
style: styles,
theme: theme,
};

const service: StyleConsumerService = new StyleConsumerService('Radio', context);
// @ts-ignore
const service: StyleConsumerService = new StyleConsumerService('Radio', styles);

describe('* style mapping', () => {

Expand All @@ -129,7 +121,8 @@ describe('@style: consumer service methods check', () => {
...derivedProps,
};

const value: StyledComponentProps = service.withStyledProps(props, context, [Interaction.ACTIVE]);
// @ts-ignore
const value: StyledComponentProps = service.withStyledProps(props, styles, theme, [Interaction.ACTIVE]);

expect(value.themedStyle).toMatchSnapshot();
});
Expand All @@ -138,23 +131,44 @@ describe('@style: consumer service methods check', () => {

});

describe('@style: service methods checks', () => {
describe('@style-sheet: service checks', () => {

it('finds theme value properly', async () => {
const themeValue = StyleSheet.getThemeValue('gray-100', theme);
const undefinedValue = StyleSheet.getThemeValue('undefined', theme);

expect(themeValue).toEqual(theme['gray-100']);
expect(undefinedValue).toBeUndefined();
});

it('finds referencing theme value properly', async () => {
const themeValue = StyleSheet.getThemeValue('referencing', theme);

expect(themeValue).toEqual(theme['gray-100']);
});

describe('* styling', () => {
it('finds multiple referencing theme value properly', async () => {
const themeValue = StyleSheet.getThemeValue('double-referencing', theme);

const mapping: ThemedStyleType = {
expect(themeValue).toEqual(theme['gray-100']);
});

it('finds referencing theme value properly (initial reference)', async () => {
const themeValue = StyleSheet.getThemeValue('referencing', theme);

expect(themeValue).toEqual(theme['gray-100']);
});

it('* creates themedStyle property for mapping properly', () => {
const mapping = {
prop1: 'blue-primary',
prop2: 'blue-dark',
prop3: 'gray-primary',
prop4: 42,
};

it('* default theme', () => {
const value: StyleType = createThemedStyle(mapping, theme);

expect(value).toMatchSnapshot();
});

const value = StyleSheet.createThemedStyle(mapping as ViewStyle, theme);
expect(value).toMatchSnapshot();
});

});
Expand Down
18 changes: 9 additions & 9 deletions src/components/theme/style/style.spec.tsx.snap
Original file line number Diff line number Diff line change
Expand Up @@ -22,15 +22,6 @@ Object {
}
`;

exports[`@style: service methods checks * styling * default theme 1`] = `
Object {
"prop1": "#3366FF",
"prop2": "#2541CC",
"prop3": "#A6AEBD",
"prop4": 42,
}
`;

exports[`@style: ui component checks * default appearance styled properly 1`] = `
Object {
"borderColor": "#A6AEBD",
Expand Down Expand Up @@ -140,3 +131,12 @@ Object {
"width": 36,
}
`;

exports[`@style-sheet: service checks * creates themedStyle property for mapping properly 1`] = `
Object {
"prop1": "#3366FF",
"prop2": "#2541CC",
"prop3": "#A6AEBD",
"prop4": 42,
}
`;
35 changes: 16 additions & 19 deletions src/components/theme/style/styleConsumer.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,13 @@ import {
ControlMetaType,
ControlThemedStyleType,
ThemedStyleType,
ThemeStyleType,
} from '@eva-design/dss';
import { StyledComponentProps } from './styled';
import {
ContextProps,
StyledComponentProps,
} from './styleConsumer.component';
import { createThemedStyle } from './style.service';
StyleSheet,
StyleType,
ThemeType,
} from './styleSheet.service';
import { Interaction } from './type';

const SEPARATOR_MAPPING_ENTRY: string = '.';
Expand All @@ -30,10 +30,10 @@ export class StyleConsumerService {
private readonly name: string;
private readonly meta: ControlMetaType;

constructor(name: string, context: ContextProps) {
constructor(name: string, style: StyleType) {
this.name = name;

this.meta = this.safe(context.style[name], (generatedConfig): ControlMetaType => {
this.meta = this.safe(style[name], (generatedConfig): ControlMetaType => {
return generatedConfig.meta;
});

Expand Down Expand Up @@ -61,12 +61,13 @@ export class StyleConsumerService {
}

public withStyledProps<P extends object>(source: P,
context: ContextProps,
style: StyleType,
theme: ThemeType,
interaction: Interaction[]): P & StyledComponentProps {

const styleInfo: StyleInfo = this.getStyleInfo(source, interaction);

const generatedMapping: ThemedStyleType = this.getGeneratedStyleMapping(context.style, styleInfo);
const generatedMapping: StyleType = this.getGeneratedStyleMapping(style, styleInfo);

if (!generatedMapping) {
const docRoot: string = 'https://akveo.github.io/react-native-ui-kitten/docs';
Expand All @@ -79,20 +80,16 @@ export class StyleConsumerService {

console.warn(message);

return this.withStyledProps({ ...source, ...this.createDefaultProps() }, context, interaction);
return this.withStyledProps({ ...source, ...this.createDefaultProps() }, style, theme, interaction);
}

const mapping = this.withValidParameters(generatedMapping);
const mapping: StyleType = this.withValidParameters(generatedMapping);
const themedStyle: StyleType = StyleSheet.createThemedStyle(mapping, theme);

return {
...source,
theme: context.theme,
themedStyle: createThemedStyle(mapping, context.theme),
};
return { ...source, theme, themedStyle };
}

private getGeneratedStyleMapping<P extends StyledComponentProps>(style: ThemeStyleType,
info: StyleInfo): ThemedStyleType {
private getGeneratedStyleMapping<P extends StyledComponentProps>(style: StyleType, info: StyleInfo): StyleType {

return this.safe(style[this.name], (componentStyles: ControlThemedStyleType): ThemedStyleType => {
const styleKeys: string[] = Object.keys(componentStyles.styles);
Expand All @@ -102,7 +99,7 @@ export class StyleConsumerService {
});
}

private withValidParameters(mapping: ThemedStyleType): ThemedStyleType {
private withValidParameters(mapping: StyleType): StyleType {
const invalidParameters: string[] = [];

Object.keys(mapping).forEach((key: string) => {
Expand Down
63 changes: 63 additions & 0 deletions src/components/theme/style/styleSheet.service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import { StyleSheet as RNStyleSheet } from 'react-native';

type ThemeValue = string;

export type ThemeType = Record<string, ThemeValue>;
export type StyleType = Record<string, any>;
export type Styles<T> = RNStyleSheet.NamedStyles<T>;

export class StyleSheet {

static createThemedStyles = <T>(styles: Styles<T>, theme: ThemeType): T => {
return Object.keys(styles).reduce((acc: T, key: string): T => {
return { ...acc, [key]: StyleSheet.createThemedStyle(styles[key], theme) };
}, {} as T);
};

static createThemedStyle = (style: StyleType, theme: ThemeType): StyleType => {
return Object.keys(style).reduce((acc: StyleType, key: string): StyleType => {
const value: any = style[key];
return { ...acc, [key]: StyleSheet.getThemeValue(value, theme, value) };
}, {});
};

static createCompiledTheme = (theme: ThemeType): ThemeType => {
return Object.keys(theme).reduce((acc: ThemeType, key: string): ThemeType => {
return { ...acc, [key]: StyleSheet.getThemeValue(key, theme, key) };
}, {});
};

static getThemeValue = (name: string, theme: ThemeType, fallback?: ThemeValue): ThemeValue | undefined => {
if (StyleSheet.isReference(name)) {
const themeKey: string = StyleSheet.toThemeKey(name);
return StyleSheet.findThemeValue(themeKey, theme) || fallback;
}

return StyleSheet.findThemeValue(name, theme) || fallback;
};

static findThemeValue = (name: string, theme: ThemeType): ThemeValue | undefined => {
const value: ThemeValue = theme[name];

if (StyleSheet.isReference(value)) {
const themeKey: string = StyleSheet.toThemeKey(value);
return StyleSheet.findThemeValue(themeKey, theme);
}

return value;
};

/**
* @returns true if theme value references to another
*/
static isReference = (value: ThemeValue): boolean => {
return `${value}`.startsWith('$');
};

/**
* Transforms reference key to theme key
*/
static toThemeKey = (value: ThemeValue): string => {
return `${value}`.substring(1);
};
}
Loading

0 comments on commit 67a8f8f

Please sign in to comment.