Skip to content

Commit 10bd108

Browse files
committed
Support alpha modifier for theme color values
1 parent 2068227 commit 10bd108

6 files changed

+459
-15
lines changed

src/lib/evaluateTailwindFunctions.js

+18-4
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import parseValue from 'postcss-value-parser'
55
import { normalizeScreens } from '../util/normalizeScreens'
66
import buildMediaQuery from '../util/buildMediaQuery'
77
import { toPath } from '../util/toPath'
8+
import { withAlphaValue } from '../util/withAlphaVariable'
89

910
function isObject(input) {
1011
return typeof input === 'object' && input !== null
@@ -37,7 +38,7 @@ function listKeys(obj) {
3738
return list(Object.keys(obj))
3839
}
3940

40-
function validatePath(config, path, defaultValue) {
41+
function validatePath(config, path, defaultValue, themeOpts = {}) {
4142
const pathString = Array.isArray(path)
4243
? pathToString(path)
4344
: path.replace(/^['"]+/g, '').replace(/['"]+$/g, '')
@@ -114,7 +115,7 @@ function validatePath(config, path, defaultValue) {
114115

115116
return {
116117
isValid: true,
117-
value: transformThemeValue(themeSection)(value),
118+
value: transformThemeValue(themeSection)(value, themeOpts),
118119
}
119120
}
120121

@@ -160,16 +161,29 @@ let nodeTypePropertyMap = {
160161
export default function ({ tailwindConfig: config }) {
161162
let functions = {
162163
theme: (node, path, ...defaultValue) => {
163-
const { isValid, value, error } = validatePath(
164+
let matches = path.match(/^([^\/\s]+)(?:\s*\/\s*([^\/\s]+))$/)
165+
let alpha = undefined
166+
167+
if (matches) {
168+
path = matches[1]
169+
alpha = matches[2]
170+
}
171+
172+
let { isValid, value, error } = validatePath(
164173
config,
165174
path,
166-
defaultValue.length ? defaultValue : undefined
175+
defaultValue.length ? defaultValue : undefined,
176+
{ opacityValue: alpha }
167177
)
168178

169179
if (!isValid) {
170180
throw node.error(error)
171181
}
172182

183+
if (alpha !== undefined) {
184+
value = withAlphaValue(value, alpha, value)
185+
}
186+
173187
return value
174188
},
175189
screen: (node, screen) => {

src/lib/setupContextUtils.js

+14-5
Original file line numberDiff line numberDiff line change
@@ -221,16 +221,25 @@ function buildPluginApi(tailwindConfig, context, { variantList, variantMap, offs
221221
return context.tailwindConfig.prefix + identifier
222222
}
223223

224+
function resolveThemeValue(path, defaultValue, opts = {}) {
225+
const [pathRoot, ...subPaths] = toPath(path)
226+
const value = getConfigValue(['theme', pathRoot, ...subPaths], defaultValue)
227+
return transformThemeValue(pathRoot)(value, opts)
228+
}
229+
230+
const theme = Object.assign(
231+
(path, defaultValue = undefined) => resolveThemeValue(path, defaultValue),
232+
{
233+
withAlpha: (path, opacityValue) => resolveThemeValue(path, undefined, { opacityValue }),
234+
}
235+
)
236+
224237
let api = {
225238
postcss,
226239
prefix: applyConfiguredPrefix,
227240
e: escapeClassName,
228241
config: getConfigValue,
229-
theme(path, defaultValue) {
230-
const [pathRoot, ...subPaths] = toPath(path)
231-
const value = getConfigValue(['theme', pathRoot, ...subPaths], defaultValue)
232-
return transformThemeValue(pathRoot)(value)
233-
},
242+
theme,
234243
corePlugins: (path) => {
235244
if (Array.isArray(tailwindConfig.corePlugins)) {
236245
return tailwindConfig.corePlugins.includes(path)

src/util/resolveConfig.js

+16-4
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import { toPath } from './toPath'
88
import { normalizeConfig } from './normalizeConfig'
99
import isPlainObject from './isPlainObject'
1010
import { cloneDeep } from './cloneDeep'
11+
import { withAlphaValue } from './withAlphaVariable'
1112

1213
function isFunction(input) {
1314
return typeof input === 'function'
@@ -187,11 +188,22 @@ function resolveFunctionKeys(object) {
187188
return val
188189
}
189190

190-
resolvePath.theme = resolvePath
191+
Object.assign(resolvePath, {
192+
theme: resolvePath,
193+
...configUtils,
194+
withAlpha(key, opacityValue) {
195+
// TODO: This is kinda iffy but it works
196+
const path = toPath(key)
197+
const lastSegment = path.pop()
198+
let value = resolvePath(path)[lastSegment]
199+
200+
if (value === undefined) {
201+
return value
202+
}
191203

192-
for (let key in configUtils) {
193-
resolvePath[key] = configUtils[key]
194-
}
204+
return withAlphaValue(value, opacityValue)
205+
},
206+
})
195207

196208
return Object.keys(object).reduce((resolved, key) => {
197209
return {

src/util/transformThemeValue.js

+4-2
Original file line numberDiff line numberDiff line change
@@ -44,8 +44,10 @@ export default function transformThemeValue(themeSection) {
4444
}
4545
}
4646

47-
return (value) => {
48-
if (typeof value === 'function') value = value({})
47+
return (value, opts = {}) => {
48+
if (typeof value === 'function') {
49+
value = value(opts)
50+
}
4951

5052
return value
5153
}

tests/evaluateTailwindFunctions.test.js

+207
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,16 @@
11
import postcss from 'postcss'
22
import plugin from '../src/lib/evaluateTailwindFunctions'
3+
import tailwind from '../src/index'
34
import { css } from './util/run'
45

56
function run(input, opts = {}) {
67
return postcss([plugin({ tailwindConfig: opts })]).process(input, { from: undefined })
78
}
89

10+
function runFull(input, config) {
11+
return postcss([tailwind(config)]).process(input, { from: undefined })
12+
}
13+
914
test('it looks up values in the theme using dot notation', () => {
1015
let input = css`
1116
.banana {
@@ -817,3 +822,205 @@ test('screen arguments can be quoted', () => {
817822
expect(result.warnings().length).toBe(0)
818823
})
819824
})
825+
826+
test('Theme function can extract alpha values for colors (1)', () => {
827+
let input = css`
828+
.foo {
829+
color: theme(colors.blue.500 / 50%);
830+
}
831+
`
832+
833+
let output = css`
834+
.foo {
835+
color: rgb(59 130 246 / 50%);
836+
}
837+
`
838+
839+
return run(input, {
840+
theme: {
841+
colors: { blue: { 500: '#3b82f6' } },
842+
},
843+
}).then((result) => {
844+
expect(result.css).toMatchCss(output)
845+
expect(result.warnings().length).toBe(0)
846+
})
847+
})
848+
849+
test('Theme function can extract alpha values for colors (2)', () => {
850+
let input = css`
851+
.foo {
852+
color: theme(colors.blue.500 / 0.5);
853+
}
854+
`
855+
856+
let output = css`
857+
.foo {
858+
color: rgb(59 130 246 / 0.5);
859+
}
860+
`
861+
862+
return run(input, {
863+
theme: {
864+
colors: { blue: { 500: '#3b82f6' } },
865+
},
866+
}).then((result) => {
867+
expect(result.css).toMatchCss(output)
868+
expect(result.warnings().length).toBe(0)
869+
})
870+
})
871+
872+
test('Theme function can extract alpha values for colors (3)', () => {
873+
let input = css`
874+
.foo {
875+
color: theme(colors.blue.500 / var(--my-alpha));
876+
}
877+
`
878+
879+
let output = css`
880+
.foo {
881+
color: rgb(59 130 246 / var(--my-alpha));
882+
}
883+
`
884+
885+
return run(input, {
886+
theme: {
887+
colors: { blue: { 500: '#3b82f6' } },
888+
},
889+
}).then((result) => {
890+
expect(result.css).toMatchCss(output)
891+
expect(result.warnings().length).toBe(0)
892+
})
893+
})
894+
895+
test('Theme function can extract alpha values for colors (4)', () => {
896+
let input = css`
897+
.foo {
898+
color: theme(colors.blue.500 / 50%);
899+
}
900+
`
901+
902+
let output = css`
903+
.foo {
904+
color: hsl(217 91% 60% / 50%);
905+
}
906+
`
907+
908+
return run(input, {
909+
theme: {
910+
colors: {
911+
blue: { 500: 'hsl(217, 91%, 60%)' },
912+
},
913+
},
914+
}).then((result) => {
915+
expect(result.css).toMatchCss(output)
916+
expect(result.warnings().length).toBe(0)
917+
})
918+
})
919+
920+
test('Theme function can extract alpha values for colors (5)', () => {
921+
let input = css`
922+
.foo {
923+
color: theme(colors.blue.500 / 0.5);
924+
}
925+
`
926+
927+
let output = css`
928+
.foo {
929+
color: hsl(217 91% 60% / 0.5);
930+
}
931+
`
932+
933+
return run(input, {
934+
theme: {
935+
colors: {
936+
blue: { 500: 'hsl(217, 91%, 60%)' },
937+
},
938+
},
939+
}).then((result) => {
940+
expect(result.css).toMatchCss(output)
941+
expect(result.warnings().length).toBe(0)
942+
})
943+
})
944+
945+
test('Theme function can extract alpha values for colors (6)', () => {
946+
let input = css`
947+
.foo {
948+
color: theme(colors.blue.500 / var(--my-alpha));
949+
}
950+
`
951+
952+
let output = css`
953+
.foo {
954+
color: hsl(217 91% 60% / var(--my-alpha));
955+
}
956+
`
957+
958+
return run(input, {
959+
theme: {
960+
colors: {
961+
blue: { 500: 'hsl(217, 91%, 60%)' },
962+
},
963+
},
964+
}).then((result) => {
965+
expect(result.css).toMatchCss(output)
966+
expect(result.warnings().length).toBe(0)
967+
})
968+
})
969+
970+
test('Theme function can extract alpha values for colors (7)', () => {
971+
let input = css`
972+
.foo {
973+
color: theme(colors.blue.500 / var(--my-alpha));
974+
}
975+
`
976+
977+
let output = css`
978+
.foo {
979+
color: rgb(var(--foo) / var(--my-alpha));
980+
}
981+
`
982+
983+
return runFull(input, {
984+
theme: {
985+
colors: ({ rgb }) => ({
986+
blue: {
987+
500: rgb('--foo'),
988+
},
989+
}),
990+
},
991+
}).then((result) => {
992+
expect(result.css).toMatchCss(output)
993+
expect(result.warnings().length).toBe(0)
994+
})
995+
})
996+
997+
test('Theme function can extract alpha values for colors (8)', () => {
998+
let input = css`
999+
.foo {
1000+
color: theme(colors.blue.500 / theme(opacity.myalpha));
1001+
}
1002+
`
1003+
1004+
let output = css`
1005+
.foo {
1006+
color: rgb(var(--foo) / 50%);
1007+
}
1008+
`
1009+
1010+
return runFull(input, {
1011+
theme: {
1012+
colors: ({ rgb }) => ({
1013+
blue: {
1014+
500: rgb('--foo'),
1015+
},
1016+
}),
1017+
1018+
opacity: {
1019+
myalpha: '50%',
1020+
},
1021+
},
1022+
}).then((result) => {
1023+
expect(result.css).toMatchCss(output)
1024+
expect(result.warnings().length).toBe(0)
1025+
})
1026+
})

0 commit comments

Comments
 (0)