Skip to content

Commit 86fe81b

Browse files
authored
Merge pull request #655 from tailwindcss/extend-theme
Add first class support for extending the default theme
2 parents 000feb7 + c56b56d commit 86fe81b

File tree

2 files changed

+293
-5
lines changed

2 files changed

+293
-5
lines changed

__tests__/resolveConfig.test.js

Lines changed: 271 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -430,3 +430,274 @@ test('functions in the user theme section are lazily evaluated', () => {
430430
},
431431
})
432432
})
433+
434+
test('theme values in the extend section extend the existing theme', () => {
435+
const userConfig = {
436+
theme: {
437+
extend: {
438+
opacity: {
439+
'25': '25',
440+
'75': '.75',
441+
},
442+
backgroundColors: {
443+
customBackground: '#bada55',
444+
},
445+
},
446+
},
447+
}
448+
449+
const defaultConfig = {
450+
prefix: '-',
451+
important: false,
452+
separator: ':',
453+
theme: {
454+
colors: {
455+
cyan: 'cyan',
456+
magenta: 'magenta',
457+
yellow: 'yellow',
458+
},
459+
opacity: {
460+
'0': '0',
461+
'50': '.5',
462+
'100': '1',
463+
},
464+
backgroundColors: ({ colors }) => colors,
465+
},
466+
variants: {
467+
backgroundColors: ['responsive', 'hover', 'focus'],
468+
opacity: ['responsive', 'hover', 'focus'],
469+
},
470+
}
471+
472+
const result = resolveConfig([userConfig, defaultConfig])
473+
474+
expect(result).toEqual({
475+
prefix: '-',
476+
important: false,
477+
separator: ':',
478+
theme: {
479+
colors: {
480+
cyan: 'cyan',
481+
magenta: 'magenta',
482+
yellow: 'yellow',
483+
},
484+
opacity: {
485+
'0': '0',
486+
'50': '.5',
487+
'100': '1',
488+
'25': '25',
489+
'75': '.75',
490+
},
491+
backgroundColors: {
492+
cyan: 'cyan',
493+
magenta: 'magenta',
494+
yellow: 'yellow',
495+
customBackground: '#bada55',
496+
},
497+
},
498+
variants: {
499+
backgroundColors: ['responsive', 'hover', 'focus'],
500+
opacity: ['responsive', 'hover', 'focus'],
501+
},
502+
})
503+
})
504+
505+
test('theme values in the extend section extend the user theme', () => {
506+
const userConfig = {
507+
theme: {
508+
opacity: {
509+
'0': '0',
510+
'20': '.2',
511+
'40': '.4',
512+
},
513+
height: theme => theme.width,
514+
extend: {
515+
opacity: {
516+
'60': '.6',
517+
'80': '.8',
518+
'100': '1',
519+
},
520+
height: {
521+
customHeight: '500vh',
522+
},
523+
},
524+
},
525+
}
526+
527+
const defaultConfig = {
528+
prefix: '-',
529+
important: false,
530+
separator: ':',
531+
theme: {
532+
opacity: {
533+
'0': '0',
534+
'50': '.5',
535+
'100': '1',
536+
},
537+
height: {
538+
'0': 0,
539+
full: '100%',
540+
},
541+
width: {
542+
'0': 0,
543+
'1': '.25rem',
544+
'2': '.5rem',
545+
'3': '.75rem',
546+
'4': '1rem',
547+
},
548+
},
549+
variants: {
550+
opacity: ['responsive', 'hover', 'focus'],
551+
height: ['responsive'],
552+
width: ['responsive'],
553+
},
554+
}
555+
556+
const result = resolveConfig([userConfig, defaultConfig])
557+
558+
expect(result).toEqual({
559+
prefix: '-',
560+
important: false,
561+
separator: ':',
562+
theme: {
563+
opacity: {
564+
'0': '0',
565+
'20': '.2',
566+
'40': '.4',
567+
'60': '.6',
568+
'80': '.8',
569+
'100': '1',
570+
},
571+
height: {
572+
'0': 0,
573+
'1': '.25rem',
574+
'2': '.5rem',
575+
'3': '.75rem',
576+
'4': '1rem',
577+
customHeight: '500vh',
578+
},
579+
width: {
580+
'0': 0,
581+
'1': '.25rem',
582+
'2': '.5rem',
583+
'3': '.75rem',
584+
'4': '1rem',
585+
},
586+
},
587+
variants: {
588+
opacity: ['responsive', 'hover', 'focus'],
589+
height: ['responsive'],
590+
width: ['responsive'],
591+
},
592+
})
593+
})
594+
595+
test('theme values in the extend section can extend values that are depended on lazily', () => {
596+
const userConfig = {
597+
theme: {
598+
extend: {
599+
colors: {
600+
red: 'red',
601+
green: 'green',
602+
blue: 'blue',
603+
},
604+
backgroundColors: {
605+
customBackground: '#bada55',
606+
},
607+
},
608+
},
609+
}
610+
611+
const defaultConfig = {
612+
prefix: '-',
613+
important: false,
614+
separator: ':',
615+
theme: {
616+
colors: {
617+
cyan: 'cyan',
618+
magenta: 'magenta',
619+
yellow: 'yellow',
620+
},
621+
backgroundColors: ({ colors }) => colors,
622+
},
623+
variants: {
624+
backgroundColors: ['responsive', 'hover', 'focus'],
625+
},
626+
}
627+
628+
const result = resolveConfig([userConfig, defaultConfig])
629+
630+
expect(result).toEqual({
631+
prefix: '-',
632+
important: false,
633+
separator: ':',
634+
theme: {
635+
colors: {
636+
cyan: 'cyan',
637+
magenta: 'magenta',
638+
yellow: 'yellow',
639+
red: 'red',
640+
green: 'green',
641+
blue: 'blue',
642+
},
643+
backgroundColors: {
644+
cyan: 'cyan',
645+
magenta: 'magenta',
646+
yellow: 'yellow',
647+
red: 'red',
648+
green: 'green',
649+
blue: 'blue',
650+
customBackground: '#bada55',
651+
},
652+
},
653+
variants: {
654+
backgroundColors: ['responsive', 'hover', 'focus'],
655+
},
656+
})
657+
})
658+
659+
test('theme values in the extend section are not deeply merged', () => {
660+
const userConfig = {
661+
theme: {
662+
extend: {
663+
fonts: {
664+
sans: ['Comic Sans'],
665+
},
666+
},
667+
},
668+
}
669+
670+
const defaultConfig = {
671+
prefix: '-',
672+
important: false,
673+
separator: ':',
674+
theme: {
675+
fonts: {
676+
sans: ['system-ui', 'Helvetica Neue', 'sans-serif'],
677+
serif: ['Constantia', 'Georgia', 'serif'],
678+
mono: ['Menlo', 'Courier New', 'monospace'],
679+
},
680+
},
681+
variants: {
682+
fonts: ['responsive'],
683+
},
684+
}
685+
686+
const result = resolveConfig([userConfig, defaultConfig])
687+
688+
expect(result).toEqual({
689+
prefix: '-',
690+
important: false,
691+
separator: ':',
692+
theme: {
693+
fonts: {
694+
sans: ['Comic Sans'],
695+
serif: ['Constantia', 'Georgia', 'serif'],
696+
mono: ['Menlo', 'Courier New', 'monospace'],
697+
},
698+
},
699+
variants: {
700+
fonts: ['responsive'],
701+
},
702+
})
703+
})

src/util/resolveConfig.js

Lines changed: 22 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,36 @@
1-
import _ from 'lodash'
1+
import mergeWith from 'lodash/mergeWith'
2+
import isFunction from 'lodash/isFunction'
3+
import defaults from 'lodash/defaults'
4+
import map from 'lodash/map'
25

36
function resolveFunctionKeys(object) {
47
return Object.keys(object).reduce((resolved, key) => {
58
return {
69
...resolved,
7-
[key]: _.isFunction(object[key]) ? object[key](object) : object[key],
10+
[key]: isFunction(object[key]) ? object[key](object) : object[key],
811
}
912
}, {})
1013
}
1114

15+
function mergeExtensions({ extend, ...theme }) {
16+
return mergeWith({}, theme, extend, (_, extensions, key) => {
17+
return isFunction(theme[key])
18+
? mergedTheme => ({
19+
...theme[key](mergedTheme),
20+
...extensions,
21+
})
22+
: {
23+
...theme[key],
24+
...extensions,
25+
}
26+
})
27+
}
28+
1229
export default function(configs) {
13-
return _.defaults(
30+
return defaults(
1431
{
15-
theme: resolveFunctionKeys(_.defaults(..._.map(configs, 'theme'))),
16-
variants: _.defaults(..._.map(configs, 'variants')),
32+
theme: resolveFunctionKeys(mergeExtensions(defaults(...map(configs, 'theme')))),
33+
variants: defaults(...map(configs, 'variants')),
1734
},
1835
...configs
1936
)

0 commit comments

Comments
 (0)