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

tachyons-measured

v1.0.3

Published

A set of higher order components for creating stateless functional UI components using tachyons.

Downloads

14

Readme

CircleCI

📏 📐 tachyons-measured

A set of higher order components (HOC) for creating stateless functional UI components using tachyons.

API

Media Query (MQ) Support

The following properties support the media query syntax:

  • r, rounded, bw
  • f, lh
  • h, w
  • pa, pl, pr, pb, pt, pv, ph
  • ma, ml, mr, mb, mt, mv, mh
  • na, nl, nr, nb, nt

This means that you can either provide regular values – such as a scale step number and literal values – or an object which specifies values by breakpoints.

For example: <Text f={1} /> or <Text f={{ all: 3, ns: 2, m: 1, l: 'headline' }} />

all: All breakpoints (unless otherwise specified with another breakpoint) ns: Not small m: Medium l: Large

Higher Order Components

withBaseStyles

withBaseStyles(
  baseStyles: Array<string> or string
): HigherOrderComponent

HOC for creating a styled component with a set of classNames applied to it.

const ButtonLink = compose('f6 link dim br1 ph3 pv2 mb2 dib white bg-black')('a');

<ButtonLink>Link Text</ButtonLink>

withSpacing

withSpacing(): HigherOrderComponent

Exposes the spacing scale as props.

const Div = withSpacing('div');

<Div
  mh={3} mv={{ l: 4, m: 3, ns: 2, all: 1 }}
  nl={{ l: 3, m: 2, ns: 4, all: 1 }}
  pr={4} pl={4} pv={2}
  className="myClass my-other-class"
/>

| Prop | Type | MQ Support | |---|---|---| | ma | oneOf([0, 1, 2, 3, 4, 5, 6, 7]) | 🚫 | | mt | oneOf([0, 1, 2, 3, 4, 5, 6, 7]) | 🚫 | | ml | oneOf([0, 1, 2, 3, 4, 5, 6, 7]) | 🚫 | | mr | oneOf([0, 1, 2, 3, 4, 5, 6, 7]) | 🚫 | | mb | oneOf([0, 1, 2, 3, 4, 5, 6, 7]) | 🚫 | | mv | oneOf([0, 1, 2, 3, 4, 5, 6, 7]) | 🚫 | | mh | oneOf([0, 1, 2, 3, 4, 5, 6, 7]) | 🚫 | | na | oneOf([0, 1, 2, 3, 4, 5, 6, 7]) | 🚫 | | nt | oneOf([0, 1, 2, 3, 4, 5, 6, 7]) | 🚫 | | nl | oneOf([0, 1, 2, 3, 4, 5, 6, 7]) | 🚫 | | nr | oneOf([0, 1, 2, 3, 4, 5, 6, 7]) | 🚫 | | nb | oneOf([0, 1, 2, 3, 4, 5, 6, 7]) | 🚫 | | pa | oneOf([0, 1, 2, 3, 4, 5, 6, 7]) | 🚫 | | pt | oneOf([0, 1, 2, 3, 4, 5, 6, 7]) | 🚫 | | pl | oneOf([0, 1, 2, 3, 4, 5, 6, 7]) | 🚫 | | pr | oneOf([0, 1, 2, 3, 4, 5, 6, 7]) | 🚫 | | pb | oneOf([0, 1, 2, 3, 4, 5, 6, 7]) | 🚫 | | pv | oneOf([0, 1, 2, 3, 4, 5, 6, 7]) | 🚫 | | ph | oneOf([0, 1, 2, 3, 4, 5, 6, 7]) | 🚫 |

withBackgroundColor

withBackgroundColor(
  colors: Array<string>
): HigherOrderComponent

Allows you to set the background color using the bg prop. You will have to provide it a list of colour names that you are using in your project.

const clrs = ['red', 'green', 'blue', 'washed-yellow'];
const Div = withBackgroundColor(clrs)('div');

<Div
  bg="washed-yellow"
  className="myClass my-other-class"
/>

| Prop | Type | MQ Support | |---|---|---| | bg | oneOf([...<list of colors provided>]) | 🚫 |

withColor

withColor(
  colors: Array<string>
): HigherOrderComponent

Allows you to set the font color using the color prop. You will have to provide it a list of colour names that you are using in your project.

const clrs = ['medium-gray', 'red', 'green', 'blue'];
const Text = withColor(clrs)('p');

<Text
  color="medium-gray"
  className="myClass my-other-class"
/>

| Prop | Type | MQ Support | |---|---|---| | color | oneOf([...<list of colors provided>]) | 🚫 |

withSize

withSize(): HigherOrderComponent

Exposes widths & heights as props.

const Div = withSize('div');;

<Div
  w={{ l: 5, m: 4, ns: 'third', all: 3 }}
  h={5}
  className="myClass my-other-class"
/>

| Prop | Type | MQ Support | |---|---|---| | w | oneOf([1, 2, 3, 4, 5, 10, 20, 25, 30, 33, 34, 40, 50, 60, 70, 75, 80, 90, 100, 'third', 'two-thirds', 'auto']) | ✅ | | h | oneOf([1, 2, 3, 4, 5, 25, 50, 75, 100, 'auto']) | ✅ |

withTypography

withTypography(): HigherOrderComponent

Allows you to set the font size and line-height using the f and lh props respectively.

const Text = withTypography('p');;

<Text
  f={{ l: 4, m: 3, ns: 2, all: 1 }}
  lh="copy"
  className="myClass my-other-class"
/>

| Prop | Type | MQ Support | |---|---|---| | f | oneOf([1, 2, 3, 4, 5, 6, 7, 'headline', 'subheadline']) | ✅ | | lh | oneOf(['solid', 'title', 'copy']) | ✅ |

withBorder

withBorder(
  colors: Array<string>
): HigherOrderComponent

Allows you to set border styles using props. You will have to provide it a list of colour names that you are using in your project.

const clrs = ['medium-gray', 'red', 'green', 'blue'];
const Div = withBorder(clrs)('div');

<Div
  ba="gray" bw={2}
  radius={{ l: 1, m: 2, ns: 100, all: 4 }}
  rounded={{ l: 'bottom', m: 'top', ns: 'right', all: 'left' }}
  className="myClass my-other-class"
/>

| Prop | Type | MQ Support | |---|---|---| | ba | boolean or oneOf([...<list of colors provided>]) | 🚫 | | bl | boolean or oneOf([...<list of colors provided>]) | 🚫 | | br | boolean or oneOf([...<list of colors provided>]) | 🚫 | | bt | boolean or oneOf([...<list of colors provided>]) | 🚫 | | bb | boolean or oneOf([...<list of colors provided>]) | 🚫 | | bn | boolean | 🚫 | | bw | oneOf([[0, 1, 2, 3, 4, 5]]) | ✅ | | radius | oneOf([0, 1, 2, 3, 4, 100, 'pill']) | ✅ | | rounded | oneOf(['bottom', 'top', 'right', 'left']) | ✅ |

withDefaults

withDefaults(
  defaultsForProps: Object
): HigherOrderComponent

Allows you to provide default values for any props.

const Title = compose(
  withTypography,
  withDefaults({ f: 1, lh: 'title' }),
)('h1');

// Will receive f as 1 and lh as 'title'
<Title className="myClass my-other-class" />
// Will receive f as 2 and lh as 'title'
<Title f={2} className="myClass my-other-class" />

withMeasured

withMeasured(
  colors: Array<string>
): HigherOrderComponent

A composition of withSpacing, withBackgroundColor(colors), withColor(colors), withSize, withBorder(colors) and withTypography.

const clrs = ['white', 'red', 'green', 'blue'];
export const Block = withMeasured(clrs)('div');

<Block
  f={{ l: 4, m: 3, ns: 2, all: 1 }}
  lh="copy"
  mh={3} mv={2} mt={4} nl={3}
  pa={{ l: 4, m: 4, ns: 3, all: 2 }}
  bg="blue"
  color="white"
  w={5}
  h={{ l: 50, m: 4, ns: 3, all: 2 }}
  bb="gray" bw={{ l: 1, m: 2, ns: 3, all: 4 }}
  radius="pill"
  rounded="top"
/>

Compose

tachyons-measured provides the ramda compose function. However, should be able to use any compose function. Such as the one provided by underscore or recompose, etc.

import { compose } from 'tachyons-measured';

Performance

All the HOC provided by this library are stateless and mostly just responsible for mapping or generating props. Therefore, they have been setup to be eagerly evaluated. This is based on the createEagerElement pattern from recompose.

Without eager evaluation the component tree would look something like this:

<withSpacing>
  <withBackgroundColor>
    <withColor>
      <withSize>
        <withBorder>
          <div>
            Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
          <div>
        <withBorder>
      <withSize>
    <withColor>
  <withBackgroundColor>
</withSpacing>

With eager evaluation all the HOC are collapsed into one component instance. This helps achieve better performance since a fewer component instances are created. Also, it should help with debugging since the component tree is much flatter.

<withSpacing(withBackgroundColor(withColor(withSize(withBorder(div)))))>
  Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
</withSpacing(withBackgroundColor(withColor(withSize(withBorder(div)))))>

For more info see this talk by Andrew Clark

Example

We are going to replicate this Product Card. We start by creating some base components by enhancing HTML elements using tachyons-measured HOCs.

export const Block = withMeasured(clrs)('div');
export const Article = withMeasured(clrs)('article');
export const Heading = withMeasured(clrs)('h1');

export const Text = compose(
  withDefaults({ f: 5, lh: 'copy' }),
  withMeasured(clrs),
)('p');

export const Media = compose(
  withBorder(clrs),
  withSize,
  withSpacing,
  withBaseStyles('db'),
)('img');

The <ProductCard> component is simply the <Article> component with some default styles applied to it. Therefore, we can create the <ProductCard> by wrapping <Article> with the withDefaults HOC.

export const ProductCard = withDefaults({
  ba: 'black-10',
  radius: 2,
  bg: 'white',
  color: 'dark-gray',
})(Article);

Finally, we combine them all together to create the <CatProductCard>.

export const CatProductCard = props => (
  <ProductCard {...props}>

    <Media
      src="http://placekitten.com/g/600/300"
      w={100}
      radius={2} rounded="top"
      alt="kitten looking menacing."
    />

    <Block pa={2} ph={{ ns: 3 }} pb={{ ns: 3 }}>

      <Block w={100} mt={1} className="flex items-center">
        <Heading
          f={{ all: 5, ns: 4 }} mv={0}
          className="flex-auto"
        >
          Cat
        </Heading>
        <Heading f={5} mv={0}>$1,000</Heading>
      </Block>

      <Text
        mt={2}
        f={6} lh="copy" color="mid-gray"
        className="measure"
      >
        If it fits, i sits burrow under covers. Destroy couch leave hair
        everywhere, and touch water with paw then recoil in horror.
      </Text>

    </Block>
  </ProductCard>
);

We are passing all props from <CatProductCard> to <ProductCard>. This means when we are using <CatProductCard> we can use props to control the styles for a specific instance. For example:

<CatProductCard
  w={{ all: 100, m: 5, l: 5 }}
  className="center"
/>

🚨 For more examples see the examples directory.

Why?

  1. It allows you to quickly create styled and/or stateless functional UI components which use tachyons for styling.

  2. It helps break up the styles into multiple props. This avoids className from becoming long and hard to read.

    <Button
      f={4} lh="solid"
      bg="near-white" color="black-60"
      br="3" rounded="top"
      mv={0} pv={2} ph={3}
    />
  3. It enforces typechecking using propTypes. This helps catch values not supported by tachyons.

  4. It makes it easier to provide defaults (see the explanation below).

When building components we often want to provide some base styling and then allow the user to override some of that styling. This can be challenging to achieve by providing all the overriding-styles through one prop. For example:

const Button = ({ className, ...props}) => {
  const styles = classNames('f6', 'link', 'dim', 'br1', 'bn',
    'ph3', 'pv2', 'mb2', 'dib', 'bg-green', 'white', className);

  return (
    <a className={styles} {...props} />
  );
};

This component provides all the base-styles. Including the default background and text colours. There are many ways to do this however, for this particular example I'm using classNames.

// Will render with green background and white text
<Button className="mr3">Button Text</Button>
// Will render with blue background and white text
<Button className="bg-blue mr3">Button Text</Button>
// Will render with green background and white text
<Button className="bg-red">Button Text</Button>

You might notice a problem with the above scenario. The first two buttons will render as expected however, the third one will not. This is because in tachyons CSS .bg-green is defined after .bg-red so it will take precedence.

/* Background colors */
.bg-red { background-color: #ff4136; }
  ...
.bg-green { background-color: #19a974; }
  ...
.bg-blue { background-color: #357edd; }

In order to get around this we can expose background and color as props.

const Button = ({
  bgColor = 'bg-green',
  color = 'white',
  className,
  ...props
}) => {
  const styles = classNames('f6', 'link', 'dim', 'br1', 'bn',
    'ph3', 'pv2', 'mb2', 'dib', bgColor, color, className);

  return (
    <a className={styles} {...props} />
  );
};

full example: codepen.io/winkerVSbecks/pen/LWBLYb

Inspired by and Related to

Feedback

This is still in the early stages. Any feedback and bug reports are much appreciated. Please submit them here or reach out to me on twitter.