All files / web/styled-system/jsx create-style-context.mjs

0% Statements 0/118
0% Branches 0/1
0% Functions 0/1
0% Lines 0/118

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118                                                                                                                                                                                                                                           
'use client'

import { cx, css, sva } from '../css/index.mjs';
import { styled } from './factory.mjs';
import { getDisplayName } from './factory-helper.mjs';
import { createContext, useContext, createElement, forwardRef } from 'react'

function createSafeContext(contextName) {
  const Context = createContext(undefined)
  const useStyleContext = (componentName, slot) => {
    const context = useContext(Context)
    if (context === undefined) {
      const componentInfo = componentName ? `Component "${componentName}"` : 'A component'
      const slotInfo = slot ? ` (slot: "${slot}")` : ''
      
      throw new Error(
        `${componentInfo}${slotInfo} cannot access ${contextName} because it's missing its Provider.`
      )
    }
    return context
  }
  return [Context, useStyleContext]
}

export function createStyleContext(recipe) {
  const isConfigRecipe = '__recipe__' in recipe
  const recipeName = isConfigRecipe && recipe.__name__ ? recipe.__name__ : undefined
  const contextName = recipeName ? `createStyleContext("${recipeName}")` : 'createStyleContext'
  
  const [StyleContext, useStyleContext] = createSafeContext(contextName)
  const svaFn = isConfigRecipe ? recipe : sva(recipe.config)

  const getResolvedProps = (props, slotStyles) => {
    const { unstyled, ...restProps } = props
    if (unstyled) return restProps
    if (isConfigRecipe) {
       return { ...restProps, className: cx(slotStyles, restProps.className) }
    }
    return { ...slotStyles, ...restProps }
  }

  const withRootProvider = (Component, options) => {
    const WithRootProvider = (props) => {
      const [variantProps, otherProps] = svaFn.splitVariantProps(props)
      
      const slotStyles = isConfigRecipe ? svaFn(variantProps) : svaFn.raw(variantProps)
      slotStyles._classNameMap = svaFn.classNameMap

      const mergedProps = options?.defaultProps 
        ? { ...options.defaultProps, ...otherProps } 
        : otherProps

      return createElement(StyleContext.Provider, {
        value: slotStyles,
        children: createElement(Component, mergedProps)
      })
    }
    
    const componentName = getDisplayName(Component)
    WithRootProvider.displayName = `withRootProvider(${componentName})`
    
    return WithRootProvider
  }

  const withProvider = (Component, slot, options) => {
    const StyledComponent = styled(Component, {}, options)
    
    const WithProvider = forwardRef((props, ref) => {
      const [variantProps, restProps] = svaFn.splitVariantProps(props)
      
      const slotStyles = isConfigRecipe ? svaFn(variantProps) : svaFn.raw(variantProps)
      slotStyles._classNameMap = svaFn.classNameMap

      const propsWithClass = { ...restProps, className: restProps.className ?? options?.defaultProps?.className }
      const resolvedProps = getResolvedProps(propsWithClass, slotStyles[slot])
      return createElement(StyleContext.Provider, {
        value: slotStyles,
        children: createElement(StyledComponent, {
          ...resolvedProps,
          className: cx(resolvedProps.className, slotStyles._classNameMap[slot]),
          ref,
        })
      })
    })
    
    const componentName = getDisplayName(Component)
    WithProvider.displayName = `withProvider(${componentName})`
    
    return WithProvider
  }

  const withContext = (Component, slot, options) => {
    const StyledComponent = styled(Component, {}, options)
    const componentName = getDisplayName(Component)
    
    const WithContext = forwardRef((props, ref) => {
      const slotStyles = useStyleContext(componentName, slot)

      const propsWithClass = { ...props, className: props.className ?? options?.defaultProps?.className }
      const resolvedProps = getResolvedProps(propsWithClass, slotStyles[slot])
      return createElement(StyledComponent, {
        ...resolvedProps,
        className: cx(resolvedProps.className, slotStyles._classNameMap[slot]),
        ref,
      })
    })
    
    WithContext.displayName = `withContext(${componentName})`
    
    return WithContext
  }

  return {
    withRootProvider,
    withProvider,
    withContext,
  }
}