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

react-systyle

v0.3.1

Published

A thin layer on top of your React components to quickly build design systems with `emotion`.

Downloads

42

Readme

React-Systyle

A thin layer on top of your React components to quickly build design systems with emotion.

Installation

You will need react and emotion

npm install react-systyle @emotion/core

Using the Styled component

This package exports a single React component that you can use to style your application. To do so, the simplest way is to use this component's props:

Every prop with a name matching a valid CSS property will be used to generate the corresponding CSS, the others will be forwarded normally

import Styled from 'react-systyle'

const App = props => {
  // will render a div with id = main and a class that defines rules for the css props below
  return <Styled id="main" width={100} height={100} background="blue" />
}

Note that if some of the props you pass have a valid CSS rule or selector as key, they will also be used to style your component:

const css = {
  width: 100,
  height: 100,
  background: 'blue',

  '& p': {
    color: 'white'
  },

  ':hover': {
    background: 'yellow'
  },

  '@media (max-width: 720px)': {
    '&': {
      background: 'green'
    }

    '& p': {
      color: 'yellow'
    }
  }
}

<Styled {...css} />

Changing the rendered component

In case you'd like to set which element will be rendered by a systyle component, you can define it as the as prop.

// pass it directly as a prop
const Text = props => <Styled as="span">some text</Styled>

// or preset it
const Text = Styled.with({ as: 'span' })

// or use the shorthand
const Text = Styled.as('span')

Setting defaults

The Styled component comes with a static method .with() that allows you to define a more specific version of the original component. If you pass it an object, it will define the default props of your specialized component.

// this component and the one below render the same thing
const BlueSquare = props => (
  <Styled width={100} height={100} background="blue" />
)

// but this one is a styled component with all the static methods that come with it
const BlueSquare = Styled.with({ width: 100, height: 100, background: 'blue' })

Every component generated with .with() will also be a styled component so it'll also have access to this method. Defaults will stack in the order they were added, meaning values defined the latest will overwrite any previous one.

const Square = Styled.with({
  width: 100,
  height: 100,
  background: 'transparent'
})

const BlueSquare = Square.with({ background: 'blue' })

And you can again use CSS selectors as props:

const HoverRed = Styled.with({
  background: 'white',

  ':hover': {
    background: 'red'
  }
})

Every new kind of styled component gets a unique class name so you can use them as a key in the styling of other components:

const Text = Styled.as('span')
const Title = Text.as('h1')

const TextContainer = Styled.with({
  [Text]: {
    background: 'red',
    color: 'white'
  }
})

// Text below will be written in white on a red background
// Title was created from Text, but it doesn't have the same unique class
// => the Text styling won't apply to it.
;<TextContainer>
  <Title>title</Title>
  <Text>some text</Text>
</TextContainer>

Using a moulinette

A moulinette is a function that takes props as input and returns a modified version of those props. They are very useful if you want to control your component's final props depending on what was passed to it on render.

  // take 'active' out of the props
function setActiveStyle({ active, ...props }) {
  return {
    // do not forget to pass down the other props
    ...props,

    // add new props based on 'active'
    background: active ? 'green' : 'red',
    cursor: active ? 'pointer' : 'not-allowed'
  }
}

const Toggle = Styled
  .with({ width: 100, height: 100 })
  .with(setActiveStyle)

// will render a red square with a not-allowed cursor
<Toggle />

// will render a green square with a pointer cursor
// `active` was removed from the props in setActiveStyle so it won't end up in the DOM
<Toggle active />

For convenience, you can pass many parameters to the .with() method, it's equivalent to chaining the calls.

const Toggle = Styled.with({ width: 100, height: 100 }, setActiveStyle)

When you add many moulinettes to a component, the last added one will always be executed first, its result will be passed to the one added before and so on until everything is processed. This means that in a given moulinette, you'll only be able to access props generated by moulinettes added after this one.

function styleDisabled({ disabled, ...props }) {
  return {
    ...props,
    disabled,
    background: disabled ? 'gray' : 'white'
    cursor: disabled ? 'not-allowed' : 'pointer'
  }
}

function loader({ loading, disabled, children, fallback = '...', ...props }) {
  return {
    ...props,

    // set disable here so that styleDisabled can use it to generate the right style after
    disabled: loading || disabled,

    // modify the rendered children while loading is true
    children: loading ? fallback : children
  }
}

const Button = Styled.as('button').with(styleDisabled)
const AsyncButton = Button.with(loader)

// renders a button with a white background and pointer cursor
<Button>Run</Button>

// renders a disabled button with a gray background and not-allowed cursor
<Button disabled>Run</Button>

// renders a button with a white background and pointer cursor
<AsyncButton>Load</AsyncButton>

// renders a disabled button with a gray background and not-allowed cursor
<AsyncButton disabled>Load</AsyncButton>

// renders a disabled button with a gray background and not-allowed cursor and 'Loading...' written inside
<AsyncButton loading fallback="Loading...">Load</AsyncButton>

Prop aliases

To ease the writing of frequently changed styles, some props that you can pass to a styled component are actually shortcuts for other props:

  • bg = backgroundColor
  • font = fontFamily
  • size = fontSize
  • m = margin
  • mx = marginLeft + marginRight
  • my = marginTop + marginBottom
  • mt = marginTop
  • mr = marginRight
  • mb = marginBottom
  • ml = marginLeft
  • p = padding
  • px = paddingLeft + paddingRight
  • py = paddingTop + paddingBottom
  • pt = paddingTop
  • pr = paddingRight
  • pb = paddingBottom
  • pl = paddingLeft
  • b = border
  • bx = borderLeft + borderRight
  • by = borderTop + borderBottom
  • bt = borderTop
  • br = borderRight
  • bb = borderBottom
  • bl = borderLeft

For example, your component's margin will vary quite often so it's better to define the value directly when rendering rather than defining one specific variation of a component for one specific margin.

const Box = Styled.with({ display: 'flex' })
const Text = Styled.as('span').with({ display: 'inline-flex' })

function App() {
  return (
    <Box flexDirection="column" alignItems="center">
      <Text as="h1" size={32} mb={8}>
        Title
      </Text>
      <Text as="p" size={18} mb={24}>
        Subtitle
      </Text>

      <Box>
        <Text mr={8}>Left</Text>
        <Text>Right</Text>
      </Box>
    </Box>
  )
}

Theme helpers

Thanks to emotion, if a theme context is provided to your component tree, your styled components get access to some sugar.

import { ThemeContext } from '@emotion/core'

const theme = {
  // multiplier for margin and padding numeric values
  spacing: 8,

  // aliases for color, borderColor, backgroundColor values
  colors: {
    primary: 'red'
  },

  // aliases for fontFamily values
  fonts: {
    header: 'serif',
    body: 'sans-serif'
  },

  // aliases for fontSize values
  sizes: {
    M: 16,
    L: 24
  }
}

// creates a span with fontFamily = sans-serif and fontSize = 16px
const Text = Styled.as('span').with({ font: 'body', size: 'M' })

// creates a h1 with fontFamily = serif and fontSize = 24px and color = red
const Title = Styled.as('h1').with({
  font: 'header',
  size: 'L',
  color: 'primary'
})

// creates a div with padding = '8px 16px', border = '1px solid black'
const Box = Styled.with({ px: 2, py: 1, b: '1px solid black' })

const App = props => (
  <ThemeContext.Provider value={theme}>
    <Box>
      <Title>title</Title>
      <Text>text</Text>
    </Box>
  </ThemeContext.Provider>
)

Responsive props

In your theme, you can define a set of breakpoints indexed by a unique name. You can specify 3 kinds of values for your breakpoints:

  • full media query: will be used as is
  • number: will generate @media screen and (min-width: <number>px)
  • string: will generate @media screen and (min-width: <string>)
const theme = {
  breakpoints: {
    mobile: `@media screen and (max-width: 320px)`,
    tablet: 600, // @media screen and (min-width: 600px)
    desktop: '70em' // @media screen and (min-width: 70em)
  }
}

With the above config injected in your styled components, you can now use those names as key in your props, they will ultimately be converted to the corresponding media query in your component's final CSS.

const Reponsive = Styled.with({
  width: '100%',
  background: '#000',

  mobile: {
    background: '#eee'
  },

  tablet: {
    width: '66%'
  },

  desktop: {
    width: '50%'
  }
})

Using refs

Refs are automatically forwared to the element rendered by your styled component. This means that if your component generates a dom element, you'll have direct access to it.

class App extends React.Component {
  video = React.createRef()

  componentDidMount() {
    this.video.current.play()
  }

  render() {
    return <Styled as="video" ref={this.video} />
  }
}

Recommendations

Most of the time, it's nicer to predefine your styled components instead of passing them styling props on render:

const Container = Styled.with({
  display: 'flex',
  flexDirection: 'column'
})

const Title = Styled.as('h1')
const Text = Styled.as('span')

const App = props => (
  <Container>
    <Title>Hi</Title>
    <Text>Some text</Text>
  </Container>
)

The only exception being for styling props that tend to vary often between different uses of a same component, like margins:

const Row = Container.with({ flexDirection: 'row' })

const App = props => (
  <Container>
    <Title mb={16}>Hi</Title>

    <Row>
      <Text>A</Text>
      <Text mx={8}>B</Text>
      <Text>C</Text>
    </Row>

    <Text mt={16}>D</Text>
  </Container>
)

Styled components are defined as PureComponent so they will only rerender if their props change. Be sure to avoid passing them new objects or arrow functions on render if you want to avoid running the whole set of moulinette computation every time something changes.

const App = props => (
  <Styled>
    <Styled
      as="button"
      // the arrow function here is recreated on every render of App
      // => this styled component will recompute itself everytime
      onClick={() => alert('clicked!')}
    >
      CLICK ME
    </Styled>

    <Styled
      width="100%"
      // a new object is created here => rerender
      dekstop={{ width: '50%' }}
    />
  </Styled>
)