Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Theme import #106

Merged
merged 1 commit into from
Jul 22, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 = () => <input css={css({ color: theme`colors.purple.500` })} />
```

**💥 Go important with a bang** - Add important to any class with a trailing bang!

```js
Expand Down
39 changes: 39 additions & 0 deletions src/logging.js
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -152,4 +189,6 @@ export {
debugPlugins,
inOutPlugins,
errorSuggestions,
themeErrorNotString,
themeErrorNotFound,
}
4 changes: 4 additions & 0 deletions src/macro.js
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down Expand Up @@ -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 &&
Expand Down
83 changes: 83 additions & 0 deletions src/macro/theme.js
Original file line number Diff line number Diff line change
@@ -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 }
11 changes: 9 additions & 2 deletions src/macroHelpers.js
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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'`
)
)
}
Expand Down
15 changes: 15 additions & 0 deletions types/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,25 @@ export interface TwStyle {
[key: string]: string | number | TwStyle
}

export interface ThemeStyle {
[key: string]: string | number | ThemeStyle
}

export type TemplateFn<R> = (
strings: Readonly<TemplateStringsArray>,
...values: readonly string[]
) => R

export type TwFn = TemplateFn<TwStyle>

export type ThemeSearchFn<R> = (...values: readonly string[]) => R
export type ThemeSearchTaggedFn<R> = (
strings: Readonly<TemplateStringsArray>
) => R

export type ThemeFn = ThemeSearchFn<ThemeStyle> &
ThemeSearchTaggedFn<ThemeStyle>

export type TwComponent<K extends keyof JSX.IntrinsicElements> = (
props: JSX.IntrinsicElements[K]
) => JSX.Element
Expand All @@ -36,3 +48,6 @@ declare global {
}
}
}

declare const theme: ThemeFn
export { theme }