diff --git a/README.md b/README.md
index 619dc506..fc3eb4d1 100644
--- a/README.md
+++ b/README.md
@@ -124,6 +124,14 @@ ml-8 [2rem] / ml-10 [2.5rem] / ml-12 [3rem] / ml-16 [4rem] / ml-20 [5rem] / ml-2
ml-40 [10rem] / ml-48 [12rem] / ml-56 [14rem] / ml-64 [16rem] / ml-auto [auto] / ml-px [1px]
```
+**🖌️ Use the theme import to add values from your tailwind config**
+
+```js
+import { theme, css } from 'twin.macro'
+
+const Input = () =>
+```
+
**đź’Ą Go important with a bang** - Add important to any class with a trailing bang!
```js
diff --git a/src/logging.js b/src/logging.js
index ade276f5..d5e86fdb 100644
--- a/src/logging.js
+++ b/src/logging.js
@@ -142,6 +142,43 @@ const errorSuggestions = properties => {
return spaced(`${textNotFound}\n\n${suggestionText}`)
}
+const themeErrorNotString = ({ themeValue, input }) => {
+ const textNotFound = warning(
+ `${color.errorLight(input)} didn’t bring back a string theme value`
+ )
+ const suggestionText = `Try adding one of these values after a dot:\n${formatSuggestions(
+ Object.entries(themeValue).map(([k, v]) => ({
+ target: k,
+ value: typeof v === 'string' ? v : '...',
+ }))
+ )}`
+
+ return spaced(`${textNotFound}\n\n${suggestionText}`)
+}
+
+const themeErrorNotFound = ({ theme, input, trimInput }) => {
+ if (typeof theme === 'string') {
+ return spaced(logBadGood(input, trimInput))
+ }
+
+ const textNotFound = warning(
+ `${color.errorLight(input)} was not found in your theme`
+ )
+
+ if (!theme) {
+ return spaced(textNotFound)
+ }
+
+ const suggestionText = `Try one of these values:\n${formatSuggestions(
+ Object.entries(theme).map(([k, v]) => ({
+ target: k,
+ value: typeof v === 'string' ? v : '...',
+ }))
+ )}`
+
+ return spaced(`${textNotFound}\n\n${suggestionText}`)
+}
+
export {
logNoVariant,
logNoClass,
@@ -152,4 +189,6 @@ export {
debugPlugins,
inOutPlugins,
errorSuggestions,
+ themeErrorNotString,
+ themeErrorNotFound,
}
diff --git a/src/macro.js b/src/macro.js
index 01fb9327..2b62e12e 100644
--- a/src/macro.js
+++ b/src/macro.js
@@ -13,6 +13,7 @@ import {
updateStyledReferences,
addStyledImport,
} from './macro/styled'
+import { handleThemeFunction } from './macro/theme'
import { handleTwProperty, handleTwFunction } from './macro/tw'
import getUserPluginData from './utils/getUserPluginData'
import { debugPlugins } from './logging'
@@ -104,6 +105,9 @@ const twinMacro = ({ babel: { types: t }, references, state, config }) => {
addStyledImport({ program, t, styledImport, state })
}
+ // Theme import
+ handleThemeFunction({ references, t, state })
+
// Auto add css prop for styled components
if (
state.hasTwProp &&
diff --git a/src/macro/theme.js b/src/macro/theme.js
new file mode 100644
index 00000000..f4bfbd48
--- /dev/null
+++ b/src/macro/theme.js
@@ -0,0 +1,83 @@
+import dlv from 'dlv'
+import { replaceWithLocation, astify } from './../macroHelpers'
+import { getTheme, assert } from './../utils'
+import {
+ logGeneralError,
+ themeErrorNotString,
+ themeErrorNotFound,
+} from './../logging'
+
+const getFunctionValue = path => {
+ if (path.parent.type !== 'CallExpression') return
+
+ const parent = path.findParent(x => x.isCallExpression())
+ if (!parent) return
+
+ const argument = parent.get('arguments')[0] || ''
+ return { parent, input: argument.evaluate().value }
+}
+
+const getTaggedTemplateValue = path => {
+ if (path.parent.type !== 'TaggedTemplateExpression') return
+
+ const parent = path.findParent(x => x.isTaggedTemplateExpression())
+ if (!parent) return
+ if (parent.node.tag.type !== 'Identifier') return
+
+ return { parent, input: parent.get('quasi').evaluate().value }
+}
+
+const normalizeThemeValue = foundValue =>
+ Array.isArray(foundValue)
+ ? foundValue.join(', ')
+ : typeof foundValue === 'string'
+ ? foundValue.trim()
+ : foundValue
+
+const trimInput = themeValue => {
+ const arrayValues = themeValue.split('.').filter(Boolean)
+ if (arrayValues.length === 1) {
+ return arrayValues[0]
+ }
+
+ return arrayValues.slice(0, -1).join('.')
+}
+
+const handleThemeFunction = ({ references, t, state }) => {
+ if (!references.theme) return
+
+ const theme = getTheme(state.config.theme)
+
+ references.theme.forEach(path => {
+ const { input, parent } =
+ getTaggedTemplateValue(path) || getFunctionValue(path)
+
+ if (input === '') {
+ return replaceWithLocation(parent, astify('', t))
+ }
+
+ assert(!parent || !input, () =>
+ logGeneralError(
+ "The theme value doesn’t look right\n\nTry using it like this: theme`colors.black` or theme('colors.black')"
+ )
+ )
+
+ const themeValue = dlv(theme(), input)
+ assert(!themeValue, () =>
+ themeErrorNotFound({
+ theme: input.includes('.') ? dlv(theme(), trimInput(input)) : theme(),
+ input,
+ trimInput: trimInput(input),
+ })
+ )
+
+ const normalizedValue = normalizeThemeValue(themeValue)
+ assert(typeof normalizedValue !== 'string', () =>
+ themeErrorNotString({ themeValue, input })
+ )
+
+ return replaceWithLocation(parent, astify(normalizedValue, t))
+ })
+}
+
+export { handleThemeFunction }
diff --git a/src/macroHelpers.js b/src/macroHelpers.js
index 233781c9..65ecf287 100644
--- a/src/macroHelpers.js
+++ b/src/macroHelpers.js
@@ -191,7 +191,14 @@ function replaceWithLocation(path, replacement) {
return newPaths
}
-const validImports = new Set(['default', 'styled', 'css', 'TwStyle'])
+const validImports = new Set([
+ 'default',
+ 'styled',
+ 'css',
+ 'theme',
+ 'TwStyle',
+ 'ThemeStyle',
+])
const validateImports = imports => {
const unsupportedImport = Object.keys(imports).find(
reference => !validImports.has(reference)
@@ -206,7 +213,7 @@ const validateImports = imports => {
})
assert(unsupportedImport, () =>
logGeneralError(
- `Twin doesn't recognize { ${unsupportedImport} }\n\nTry one of these imports:\nimport tw, { styled, css } from 'twin.macro'`
+ `Twin doesn't recognize { ${unsupportedImport} }\n\nTry one of these imports:\nimport tw, { styled, css, theme } from 'twin.macro'`
)
)
}
diff --git a/types/index.d.ts b/types/index.d.ts
index 54b500c8..1cecc57d 100644
--- a/types/index.d.ts
+++ b/types/index.d.ts
@@ -5,6 +5,10 @@ export interface TwStyle {
[key: string]: string | number | TwStyle
}
+export interface ThemeStyle {
+ [key: string]: string | number | ThemeStyle
+}
+
export type TemplateFn = (
strings: Readonly,
...values: readonly string[]
@@ -12,6 +16,14 @@ export type TemplateFn = (
export type TwFn = TemplateFn
+export type ThemeSearchFn = (...values: readonly string[]) => R
+export type ThemeSearchTaggedFn = (
+ strings: Readonly
+) => R
+
+export type ThemeFn = ThemeSearchFn &
+ ThemeSearchTaggedFn
+
export type TwComponent = (
props: JSX.IntrinsicElements[K]
) => JSX.Element
@@ -36,3 +48,6 @@ declare global {
}
}
}
+
+declare const theme: ThemeFn
+export { theme }