npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

use-sx

v0.1.0

Published

Composable styles for React.

Downloads

14

Readme

use-sx

CSS-in-JS, inside out and upside down.

What if?

What if instead of mapping selectors-to-properties-to-values...

<button css={{
  borderStyle: 'solid',
  borderColor: 'black',
  ':hover': {
    borderColor: 'red',
  }
}}>

You mapped properties-to-selectors-to-values?

<Button sx={{
  borderStyle: 'solid',
  borderColor: {
    default: 'black',
    ':hover': 'red',
  }
}}>

If your css attributes could accept functions as values, you could even specify colors for all states in a single theme object...

<Button sx={{
  borderStyle: 'solid',
  borderColor: theme => theme.buttons.borderColor
}}>

And what if instead of tying your pseudo-selectors to individual elements:

<button css={{
  ':hover': {
    borderColor: 'red'
  }
}}>

You could specified a control boundary, and nested elements would automatically place the :hover selector where it makes sense?

<ButtonControl onClick={() => {}}>
  <Icon
    glyph={TranslationsMenu}
    sx={{
      fill: {
        default: 'black' 
        hover: 'red',
      }
    }}
  />
  <Caret sx={{
    borderColor: {
      default: 'black' 
      hover: 'red',
    }
  }} />
</ButtonControl>

If you could create custom style properties, you could even compose your styled components without a care for where they'll eventually be used...

<LinkControl href='/start'>
  <ButtonBody sx={{
    color: {
      default: 'gray',
      hover: 'red',
    },
    margin: '8px 4px',
  }}>
    <ButtonContent
      caret
      glyph={Play}
      label="Play"
      sx={{
        color: 'white'
      }}
    />
  </ButtonBody>
</LinkControl>

See this as a live example.

How it works

It's just a hook.

This package gives you a useSx() hook that takes styles in the format of the sx prop above, and returns styles in the format expected by a css prop -- as supported by Styled Components.

Note: Currently, the useSx hook requires Styled Components, as it reads directly from the styled-components theme context. Support for emotion would make a great Pull Request! 😉

useSx(options)

Basic usage

export const Button = React.forwardRef(({ sx, ...rest }, ref) => {
  const css = useSx({
    // The `sx` option accepts an array of styles
    sx: [
      {
        // Pass your default styles first
        backgroundColor: 'transparent',
        border: '1px solid black',
        borderColor: {
          focus: 'blue',
        }
        color: 'black'
      },
      // Then pass in styles from the component's `sx` prop
      sx,
      {
        // Finally, pass in any override styles -- things you don't want to be
        // customizable by the `sx` prop.
        cursor: 'pointer',
      },
    ],
  })

  return <button {...rest} css={css} ref={ref} />
})

// Then render your component like any other
ReactDOM.render(
  <Button sx={{
    opacity: {
      default: 1,
      hover: 0.9,
      active: 0.8,
    }
  }}>
    Hello, world!
  </Button>,
  document.body
)

Mapped props

By passing a maps property to useSx, you can add custom style properties to your components. These custom properties can then be used with theme functions and selector objects -- just as with standard CSS properties.

For example, this <Caret> component accepts three custom style props:

  • color, which maps directly to the borderTopColor prop
  • direction, which maps to varying rotate transforms
  • size, which sets borderWidth and a corresponding negative margin
export const Caret = React.forwardRef(({ sx, ...rest }, ref) => {
  const css = useSx({
    sx: [
      {
        color: (theme = {}) => theme.foregroundColor || 'black',
        direction: 'down',
        width: 5,
      },
      sx,
      {
        borderColor: 'transparent',
        borderStyle: 'solid',
        height: 0,
        width: 0,
      },
    ],
    maps: {
      color: (value: string) => ({
        borderTopColor: value,
      }),
      direction: (value: CaretDirection) => ({
        transform: {
          down: `rotate(0)`,
          up: `rotate(180deg)`,
          left: `rotate(-90deg)`,
          right: `rotate(90deg)`,
        }[value],
      }),
      width: (value: number) => ({
        borderWidth: value,
        marginBottom: -value,
      }),
    },
  })

  return <div {...rest} css={css} ref={ref} />
})

ReactDOM.render(
  <Caret sx={{
    color: {
      default: 'black',
      hover: 'red',
    },
    width: 5,
  }}>
    Hello, world!
  </Button>,
  document.body
)

control(StyledComponent)

This is a Higher Order Function which expects a Styled Component, returning an identical component that acts as a control boundary -- i.e. the component to which :hover pseudoselectors on nested elements will be applied.

mergeSx(sx)

This function merges anything that can be passed into the sx option for useSx into a single object.

const mergedSx = mergeSx([
  null,
  {
    color: 'inherit', 
  },
  {
    color: 'black',
  }
])

console.log(mergedSx.color) // black

This comes in handy when creating composite components that accept an sx prop, and pass individual style properties down to specific children.

For example, here's how you might use mergeSx() to extract the color style from a <ButtonContent> component's props, and pass it down to nested <Icon> and <Caret> elements:

export const ButtonContent = React.forwardRef((props, ref) => {
  const { caret, glyph, label, sx, ...rest } = props

  const css = useSx({ sx })
  const color = mergeSx(sx).color

  return (
    <div css={css} ref={ref} {...rest}>
      <Icon
        glyph={glyph}
        label={label}
        sx={{
          color,
          size: 16,
        }}
      />
      {label}
      {caret && (
        <>
          <Space width={4} />
          <Caret sx={{ color }} />
        </>
      )}
    </div>
  )
})

License

MIT licensed.