kaelansmith / tailwind-extended-shadows

TailwindCSS utility classes for fine-grain control over box-shadows, including layers.
https://play.tailwindcss.com/6rFqo93e6h
29 stars 0 forks source link

Adding inset shadows #1

Open barrelltech opened 7 months ago

barrelltech commented 7 months ago

Hey, thanks for the great plugin! I added support in my project for inset shadows with these two utilities. I think all of your modifiers work, you just need to use shadow-inside and shadows-inside-{n}`.

I'm sure there's a smarter way to do it but it was a quick job and thought I'd share :)

  matchUtilities(
    {
      "shadow-inside": (value) => {
        const shadowValues = parseShadowValue(value)

        if (shadowValues?.length) {
          let preBaseShadowValues = '0 0 #0000'
          let lastBaseShadowValue = shadowValues[0]

          if (shadowValues?.length >= 2) {
            /**
             * If values from Tailwind's `theme.boxShadow` include multiple shadow layers,
             * we handle that below. The last layer becomes the "base" layer used for
             * auto-generating additional layers via the `shadows-{2-8}` utility.
             */

            preBaseShadowValues = ''
            lastBaseShadowValue = shadowValues.pop()
            shadowValues.forEach(({ x, y, blur, spread, color }, i) => {
              if (i > 0) preBaseShadowValues += ', '
              preBaseShadowValues += `inset ${x} ${y} ${blur} ${spread} var(--tw-shadow-color, ${color})`
            })
          }

          const { x, y, blur, spread, color } = lastBaseShadowValue

          return {
            /**
             * Note: we set defaults for offset/blur/spread/opacity/layers/multiplier/ease variables here to avoid inheriting
             * from one of their respective utility classes higher up the tree. If one of these utility classes gets applied
             * alongside a shadow-{size} class, it will override the defaults because those classes get output after the shadow-{size} classes.
             */
            '--tw-shadow-x-offset': x,
            '--tw-shadow-y-offset': y,
            '--tw-shadow-blur': blur,
            '--tw-shadow-spread': spread,
            '--tw-shadow-opacity': '1',
            '--tw-shadow-layers': '0 0 #0000',
            '--tw-shadows-multiplier': '1',
            '--tw-shadow-layer-base': `${preBaseShadowValues}, inset var(--tw-shadow-x-offset) var(--tw-shadow-y-offset) var(--tw-shadow-blur) var(--tw-shadow-spread) var(--tw-shadow-color, ${color})`,
            '--tw-shadow': 'var(--tw-shadow-layer-base)',
            'box-shadow': `var(--tw-inset-shadow, 0 0 #0000), var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow)`,
          }
        }
      },
    },
    {
      values: shadowTheme,
      type: 'shadow',
    }
  )

  matchUtilities(
    {
      "shadows-inside": (value) => {
        const totalIterations = parseInt(value)
        let layers = 'inset '
        let layersEaseIn = 'inset '
        let layersEaseOut = 'inset '
        let multiplier = ''

        // note: `shadows-5` means we add 4 additional shadows to the base layer, hence `<` and not `<=` in loop:
        for (let i = 1; i < totalIterations; i++) {
          if (i > 1) {
            layers += ', inset '
            layersEaseIn += ', inset '
            layersEaseOut += ', inset '
            multiplier += ' * '
          }

          multiplier += 'var(--tw-shadows-multiplier)'

          const x = `calc(var(--tw-shadow-x-offset) * ${multiplier})`
          const y = `calc(var(--tw-shadow-y-offset) * ${multiplier})`
          const blur = `calc(var(--tw-shadow-blur) * ${multiplier})`
          const end = 'var(--tw-shadow-spread) var(--tw-shadow-color, rgb(0 0 0 / 0.1))'
          layers += `${x} ${y} ${blur} ${end}`

          let multiplierEaseIn = i / totalIterations
          let multiplierEaseOut = (1 - i) / totalIterations

          const props = [x, y, blur]
          props.forEach((val) => {
            layersEaseIn += `calc(${val} * ${multiplierEaseIn} * ${multiplierEaseIn}) `
            layersEaseOut += `calc(${val} * (1 - (${multiplierEaseOut} * ${multiplierEaseOut}))) `
          })

          layersEaseIn += end
          layersEaseOut += end
        }

        return {
          '--tw-shadows-multiplier': '1',
          '--tw-shadow-layers': layers,
          '--tw-shadow': `var(--tw-shadow-layer-base), var(--tw-shadow-layers)`,
          '&.shadows-ease-in': {
            '--tw-shadow-layers': layersEaseIn,
          },
          '&.shadows-ease-out': {
            '--tw-shadow-layers': layersEaseOut,
          },
        }
      },
    },
    {
      values: layerValues,
    }
  )
barrelltech commented 7 months ago

I also added a decay mechanism for the opacity, so you can use shadows-decay-25 to add an exponential decay to the shadows :)


  matchUtilities(
    {
      shadows: (value) => {
        const totalIterations = parseInt(value)
        let layers = ''
        let layersEaseIn = ''
        let layersEaseOut = ''
        let multiplier = ''
        let decay = ''

        // note: `shadows-5` means we add 4 additional shadows to the base layer, hence `<` and not `<=` in loop:
        for (let i = 1; i < totalIterations; i++) {
          if (i > 1) {
            layers += ', '
            layersEaseIn += ', '
            layersEaseOut += ', '
            multiplier += ' * '
            decay += ' * '
          }

          multiplier += 'var(--tw-shadows-multiplier)'
          decay += 'var(--tw-shadows-decay)'

          const x = `calc(var(--tw-shadow-x-offset) * ${multiplier})`
          const y = `calc(var(--tw-shadow-y-offset) * ${multiplier})`
          const blur = `calc(var(--tw-shadow-blur) * ${multiplier})`
          const end = `var(--tw-shadow-spread) var(--tw-shadow-color, rgb(0 0 0 / calc(var(--tw-shadow-opacity) * ${decay})))`
          layers += `${x} ${y} ${blur} ${end}`

          let multiplierEaseIn = i / totalIterations
          let multiplierEaseOut = (1 - i) / totalIterations

          const props = [x, y, blur]
          props.forEach((val) => {
            layersEaseIn += `calc(${val} * ${multiplierEaseIn} * ${multiplierEaseIn}) `
            layersEaseOut += `calc(${val} * (1 - (${multiplierEaseOut} * ${multiplierEaseOut}))) `
          })

          layersEaseIn += end
          layersEaseOut += end
        }

        return {
          '--tw-shadows-multiplier': '1',
          '--tw-shadows-decay': '0.5',
          '--tw-shadow-layers': layers,
          '--tw-shadow': `var(--tw-shadow-layer-base), var(--tw-shadow-layers)`,
          '&.shadows-ease-in': {
            '--tw-shadow-layers': layersEaseIn,
          },
          '&.shadows-ease-out': {
            '--tw-shadow-layers': layersEaseOut,
          },
        }
      },
    },
    {
      values: layerValues,
    }
  )

  matchUtilities(
    {
      "shadows-inside": (value) => {
        const totalIterations = parseInt(value)
        let layers = 'inset '
        let layersEaseIn = 'inset '
        let layersEaseOut = 'inset '
        let multiplier = ''
        let decay = ''

        // note: `shadows-5` means we add 4 additional shadows to the base layer, hence `<` and not `<=` in loop:
        for (let i = 1; i < totalIterations; i++) {
          if (i > 1) {
            layers += ', inset '
            layersEaseIn += ', inset '
            layersEaseOut += ', inset '
            multiplier += ' * ' 
            decay += ' * '
          }

          multiplier += 'var(--tw-shadows-multiplier)'
          decay += 'var(--tw-shadows-decay)'

          const x = `calc(var(--tw-shadow-x-offset) * ${multiplier})`
          const y = `calc(var(--tw-shadow-y-offset) * ${multiplier})`
          const blur = `calc(var(--tw-shadow-blur) * ${multiplier})`
          const end = `var(--tw-shadow-spread) var(--tw-shadow-color, rgb(0 0 0 / calc(var(--tw-shadow-opacity) * ${decay})))`
          layers += `${x} ${y} ${blur} ${end}`

          let multiplierEaseIn = i / totalIterations
          let multiplierEaseOut = (1 - i) / totalIterations

          const props = [x, y, blur]
          props.forEach((val) => {
            layersEaseIn += `calc(${val} * ${multiplierEaseIn} * ${multiplierEaseIn}) `
            layersEaseOut += `calc(${val} * (1 - (${multiplierEaseOut} * ${multiplierEaseOut}))) `
          })

          layersEaseIn += end
          layersEaseOut += end
        }

        return {
          '--tw-shadows-multiplier': '1',
          '--tw-shadows-decay': '0.5',
          '--tw-shadow-layers': layers,
          '--tw-shadow': `var(--tw-shadow-layer-base), var(--tw-shadow-layers)`,
          '&.shadows-ease-in': {
            '--tw-shadow-layers': layersEaseIn,
          },
          '&.shadows-ease-out': {
            '--tw-shadow-layers': layersEaseOut,
          },
        }
      },
    },
    {
      values: layerValues,
    }
  )

  matchUtilities(
    {
      'shadows-decay': (value) => ({
        '--tw-shadows-decay': `${value}`,
      }),
    },
    {
      values: scaleValues,
      supportsNegativeValues: true,
    }
  )
kaelansmith commented 6 months ago

Hey @barrelltech, sorry for the delayed response -- this all looks great! Any chance you'd be willing to submit a PR with these additions?