Skip to content

Commit

Permalink
fix: make theme optional, return early if no theme or breakpoints pre…
Browse files Browse the repository at this point in the history
…sent (#7)
  • Loading branch information
johanneslumpe authored Aug 15, 2018
1 parent 8768960 commit 99f7410
Show file tree
Hide file tree
Showing 7 changed files with 70 additions and 28 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
"types": "lib/index.d.ts",
"scripts": {
"test": "tsc -p ./tsconfig.cjs.json --noEmit && jest",
"lint": "tslint --project tsconfig.base.json",
"lint": "tslint --project tsconfig.json",
"format": "prettier --write \"src/**/*.ts\"",
"test:watch": "jest --watch",
"build": "rimraf ./lib && rimraf ./es && tsc -p ./tsconfig.cjs.json && tsc -p ./tsconfig.esm.json",
Expand Down
60 changes: 46 additions & 14 deletions src/__tests__/style.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,17 +64,14 @@ describe('style', () => {
});

it('should return `undefined` if specified prop is not set', () => {
const result = style<IProps>({ cssProp: 'output', prop: 'input' })({
theme: {},
});
const result = style<IProps>({ cssProp: 'output', prop: 'input' })({});

expect(result).toBe(undefined);
});

it('should assign the value of `prop` to `cssProp` and return an object if a value is set', () => {
const result = style<IProps>({ cssProp: 'output', prop: 'input' })({
input: 'value',
theme: {},
});

expect(result).toEqual({ output: 'value' });
Expand Down Expand Up @@ -129,8 +126,8 @@ describe('style', () => {
it('should return the respective values for breakpoints', () => {
const result = style<
IColorProps<typeof theme.colors>,
ITheme,
IBreakpoints
typeof themeWithBreakpoints,
typeof themeWithBreakpoints.breakpoints
>({
cssProp: 'color',
prop: 'input',
Expand Down Expand Up @@ -160,8 +157,8 @@ describe('style', () => {
it('should return the value as is for each breakpoint, if no value can be found on the theme', () => {
const result = style<
IColorProps<{ [index: string]: string }>,
ITheme,
IBreakpoints
typeof themeWithBreakpoints,
typeof themeWithBreakpoints.breakpoints
>({
cssProp: 'color',
prop: 'input',
Expand Down Expand Up @@ -191,8 +188,8 @@ describe('style', () => {
it('should order media query results based on ordering within `theme.breakpoints` not based on ordering of the responsive object props', () => {
const result = style<
IColorProps<{ [index: string]: string }>,
ITheme,
IBreakpoints
typeof themeWithBreakpoints,
typeof themeWithBreakpoints.breakpoints
>({
cssProp: 'color',
prop: 'input',
Expand All @@ -218,8 +215,8 @@ describe('style', () => {
it('should allow `base` as value for breakpoints to define the base value (without media query) of a property', () => {
const result = style<
IColorProps<typeof theme.colors>,
ITheme,
IBreakpoints
typeof themeWithBreakpoints,
typeof themeWithBreakpoints.breakpoints
>({
cssProp: 'color',
prop: 'input',
Expand Down Expand Up @@ -251,8 +248,8 @@ describe('style', () => {
it('should put base values in front of all other values', () => {
const result = style<
IColorProps<typeof theme.colors>,
ITheme,
IBreakpoints
typeof themeWithBreakpoints,
typeof themeWithBreakpoints.breakpoints
>({
cssProp: 'color',
prop: 'input',
Expand All @@ -274,4 +271,39 @@ describe('style', () => {
themeWithBreakpoints.breakpoints.large,
]);
});

it('should return `undefined` if a responsive object is passed in but no theme and/or breakpoints are provided', () => {
const func = style<
IColorProps<typeof theme.colors>,
typeof themeWithBreakpoints,
typeof themeWithBreakpoints.breakpoints
>({
cssProp: 'color',
prop: 'input',
themeProp: 'colors',
});

const noThemeResult = func({
input: {
base: 'red',
large: 'red',
medium: 'green',
small: 'blue',
},
theme: null as typeof themeWithBreakpoints,
});

const noBreakpointsResult = func({
input: {
base: 'red',
large: 'red',
medium: 'green',
small: 'blue',
},
theme: {} as typeof themeWithBreakpoints,
});

expect(noThemeResult).toBe(undefined);
expect(noBreakpointsResult).toBe(undefined);
});
});
26 changes: 16 additions & 10 deletions src/style.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,16 @@
import {
IDictionary,
IStyleOptions,
IStyles,
ResponsivePropValue,
WithTheme,
} from './types';

const BASE_EMPTY_OBJECT: { [index: string]: string } = {};
const BASE_EMPTY_OBJECT = {};
const BASE_EMPTY_INDEXED_OBJECT: IDictionary<any> = BASE_EMPTY_OBJECT;
const BREAKPOINTS_BASE_VALUE_KEY = 'base';

export function style<P, T = {}, B = never>({
export function style<P, T extends {} = never, B extends {} = never>({
cssProp,
prop,
themeProp,
Expand All @@ -20,9 +22,11 @@ export function style<P, T = {}, B = never>({
return undefined;
}

const themeValue = themeProp
? (props.theme[themeProp] as { [index: string]: any })
: undefined;
const themeValue =
themeProp && props.theme
? (props.theme[themeProp] as { [index: string]: any })
: undefined;

const finalValue =
themeValue &&
(typeof propValue === 'string' || typeof propValue === 'number') &&
Expand All @@ -34,11 +38,12 @@ export function style<P, T = {}, B = never>({
return {
[cssProp]: finalValue,
};
} else if (!props.theme || !props.theme.breakpoints) {
return undefined;
} else {
const result: IStyles = {};
const { theme } = props;
const { breakpoints = BASE_EMPTY_OBJECT } = theme;
const themeVal = themeValue || BASE_EMPTY_OBJECT;
const { breakpoints } = props.theme;
const themeVal = themeValue || BASE_EMPTY_INDEXED_OBJECT;
// We rely on the fact that `Object.keys` enumerates keys in the way
// they are added to an object. This should be consistent across engines.
// In case that a bug is discovered we will have to switch to an implicit
Expand All @@ -62,8 +67,9 @@ export function style<P, T = {}, B = never>({
if (key === BREAKPOINTS_BASE_VALUE_KEY) {
acc[cssProp] = val;
} else {
// TODO how to get around the cast to any?
const breakpointValue: string = breakpoints[key] as any;
const breakpointValue: string = (breakpoints as IDictionary<any>)[
key.toString()
];
acc[breakpointValue] = {
[cssProp]: val,
};
Expand Down
6 changes: 5 additions & 1 deletion src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,10 @@ export type ResponsiveProp<ValueType, BreakPoints = never> = [
? ValueType
: ValueType | ResponsivePropValue<BreakPoints, ValueType>;

export interface IDictionary<T> {
[index: string]: T;
}

export type ResponsiveObject<P, B> = {
[K in keyof P]?: ResponsiveProp<P[K], B>
};
Expand All @@ -33,7 +37,7 @@ export interface IBreakpointTheme<T, B> {
}

export type ThemeWithBreakpoints<T, B> = [B] extends [never]
? ITheme<T>
? [T] extends [never] ? Partial<ITheme<T>> : ITheme<T>
: IBreakpointTheme<T, B>;

export type WithTheme<P, T, B> = ResponsiveObject<P, B> &
Expand Down
2 changes: 1 addition & 1 deletion tsconfig.cjs.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"extends": "./tsconfig.base.json",
"extends": "./tsconfig.json",
"compilerOptions": {
"module": "commonjs",
"outDir": "lib"
Expand Down
2 changes: 1 addition & 1 deletion tsconfig.esm.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"extends": "./tsconfig.base.json",
"extends": "./tsconfig.json",
"compilerOptions": {
"module": "esnext",
"outDir": "es"
Expand Down
File renamed without changes.

0 comments on commit 99f7410

Please # to comment.