stitches icon indicating copy to clipboard operation
stitches copied to clipboard

Add `resolveToken` method to theme that resolves stitches tokens to css values

Open cpakken opened this issue 4 years ago • 0 comments

It would be useful to expose a native way to resolve stitches css tokens from theme.

const color = theme.resolveToken('$colors$neutral400')  //'#a3a3a3'

This would increase the interoperability with other libraries that rely on styles like framer-motion, react-spring

<motion.div whileHover={color: theme.resolveToken('$colors$neutral400')}/>

Since we cannot apply opacity to css variables, we have to resolve the token first

const color = theme.resolveToken('$colors$red00') 
const colorAlpha = setAlpha(color, 0.5)

We can use this for colored box shadows https://tailwindcss.com/docs/box-shadow-color.

Manual implementation:

//Since stitches themes are strings (classname) and also contains the theme object it is created from
const resolveToken = (token, theme) => {
  const matches = token.match(/^\$(?<scale>[^\$]+)\$(?<value>[^\$]+)/)
  if (matches?.groups) {
      const { scale, value } = matches.groups
      const token: Token | null = theme[scale][value] ?? null
      if (token) {
        //If the token value points to a css variable, resolve it recursively
        return token.value.match(/^var/) ? resolveToken(cssVarToToken(token.value), theme) : token.value
      }
  }
  return null
}

function cssVarToToken(cssVar: string) {
  console.log(cssVar)
  const { scale, value } = cssVar.match(/^var\(--(?<scale>\w+)-(?<value>\w+)\)/)?.groups!
  return `$${scale}$${value}`
}

But since we are manually accessing theme data and not through stitches own token resolution process. The styles are not appended to the critical css path with getCssText()

Use Case: Create a hook that resolves themeTokens

Since token resolution depends on what theme is applied in the dom tree. Check which theme is currently applied and fallback to baseTheme

//baseTheme is from const {theme} = createStitches()
//type BaseTheme = typeof theme
//BaseTheme is string (classname) and also contains the original theme object  
export const ResolveTokenProvider: FC<{ baseTheme: BaseTheme }> = ({ children, baseTheme }) => {

  const theme = useTheme().theme as BaseTheme //From ThemeProvider that keeps track of which theme is applied currently

  const resolveToken = useCallback(
    (token: string) => {
      //use natively if implemented
      //return theme.resolveToken(token) ?? baseTheme.resolveToken(token) ?? null

     //manual implmentation
      const matches = token.match(/^\$(?<scale>[^\$]+)\$(?<value>[^\$]+)/)
      if (matches?.groups) {
        const { scale, value } = matches.groups
        const token: Token | null = theme[scale][value] ?? baseTheme[scale][value] ?? null
        if (token) {
          //If the token value points to a css variable, resolve it recursively
          return token.value.match(/^var/) ? resolveToken(cssVarToToken(token.value)) : token.value
        }
      }
      return null
    },
    [baseTheme, theme]
  )

  return (
    <ResolveTokenContext.Provider value={resolveToken}>{children}</ResolveTokenContext.Provider>
  )
}

cpakken avatar Dec 17 '21 20:12 cpakken