tailwind-factory

A lib to create and extends React components like stitches using Tailwind classes!


Keywords
tailwind, styled-components, stitches, react, typescript, next
License
MIT
Install
npm install tailwind-factory@2.3.2

Documentation

image info

A lib to create and extends React components defining variants like Stitches using Tailwind!

Summary

  1. Installation
    1. Tailwind Configuration
  2. Basic Usage
    1. Custom components
  3. Heritage
  4. [Experimental] Deep Classes
    1. Available syntaxes
    2. Unavailable syntaxes
    3. Tailwind Group
    4. Using with variants
  5. How it works
  6. Classes Priority
  7. Snippets
  8. Bug Fix
  9. Roadmap - big news!

Installation

To install Tailwind Factory you need to run in your project:

//Using pnpm
pnpm add tailwind-factory

//Using npm
npm install tailwind-factory

//Using yarn
yarn add tailwind-factory

Tailwind Configuration

If you want to use with Tailwind you need to install and configure Tailwind before!

To use Tailwind CSS IntelliSense you need to add the following configuration in your User Settings:

//Tailwind IntelliSense Regex
"tailwindCSS.experimental.classRegex": [
  ["tf\\(([^)]*)\\)", "(?:`)([^'\"`]*)(?:`)"], // tf(`...`)
  ["\\.extends\\(([^)]*)\\)", "(?:`)([^'\"`]*)(?:`)"], // xxx.extends(`...`)
],

Basic Usage

import { tf } from "tailwind-factory";

//Example of a common use case
//Note: use ` to use Tailwind CSS IntelliSense
// and " or ' to use the properties' autocomplete
export const Container = tf("div", `
  flex
  flex-col
`, {
  variants: {
    theme: {
      dark: `bg-zinc-800 text-zinc-100`,
      light: `bg-white text-zinc-800`
    },
    size: {
      md: `w-full h-[200px]`,
      lg: `w-full h-screen`
    },
    centralized: {
      //These keys (true and false) are reserved for boolean values 
      // ​or their numerical value (0 and 1)
      true: `justify-center`,
      false: `justify-start`
    }
  },
  defaultVariants: {
    size: "lg",
    theme: "light"
  }
});

Use case:

<Container centralized>
  <p>Now you can use it as you wish</p>
</Container>

Custom components

Tailwind Factory also support custom components:

//Example using a custom JSX component
const JSXTitle = (
  //The component need to have the className property!
  { children, className }: { 
    children: ReactNode, 
    className?: string 
  }
) => <h2 className={className}>
  {children}
</h2>;

//Is recommended create the component outside the function
// to prevent a bug with Tailwind CSS IntelliSense
export const Title = tf(JSXTitle, `
  text-3xl
  text-inherit
`, {
  ...
})

Heritage

Components receive a function called extends which can be called by passing or not a new component. The first parameter is precisely this new type of component. If null, it will inherit the extended component. Otherwise, it will inherit all properties and variants of the new component.

//Example extending the styles
//Note: all factory components have a `extends` function
export const Header = Container.extends(
  null, //Will inherit the properties and variants of Container
`
  flex
  justify-center
  items-center
  w-full
`, {
  variants: {
    theme: {
      dark: `bg-zinc-800`,
    },
    border: {
      true: `border-b-4 border-zinc-600`,
      false: ``
    },
    size: {
      sm: `h-[20%]`
    }
  },
  defaultVariants: {
    //theme: "light", is not necessary
    border: true, //can be a string
    size: "sm"
  }
});

You can replace the null value with another component:

//Example extending another component
export const Header = Container.extends(
  //Will inherit the properties of AnotherComponent 
  // and variants of Container
  AnotherComponent, 
`
  flex
  justify-center
  items-center
  w-full
`, {
  variants: {
    ...
  },
  defaultVariants: {
    ...
  }
});

The idea was to make it closer to the 'as' property provided in some libraries. I won't go into details, but I failed to obtain this result and this was my way of mimicking this property.

I'm still wondering if the best way is to keep the extends function together with the components. If you have a problem with this or an idea, you can create an Issue.

[Experimental] Deep Classes

In some cases, to avoid creating multiple components you can use a syntax similar to CSS:

//Deep classes example
const Container = tf(
  "div",
  `
  bg-lime-200
  w-4

  h2 {
    italic
  }

  div {
    h-3
  }

  > div {
    flex
    flex-col
    bg-blue-200
    > h2 {
      font-bold
    }

    h2 {
      text-6xl
    }
  }

  > h2, h1, p {
    text-red-400
  }
`);

Example of component structure:

<Container>
  <h1>Red Title</h1>
  <h2>Red</h2>
  <p>Red Text</p>
  <div>
    <h2>Normal</h2>
    <div className="hover:bg-red-300">
      <h2>Normal</h2>
    </div>
  </div>
</Container>

Example output:

<div class="bg-lime-200 w-4">
  <h1 class="text-red-400">Red Title</h1>
  <h2 class="italic text-red-400">Red</h2>
  <p class="text-red-400">Red Text</p>
  <div class="h-3 flex flex-col bg-blue-200">
    <h2 class="italic font-bold text-6xl">Normal</h2>
    <div class="h-3 hover:bg-red-300">
      <h2 class="italic text-6xl">Normal</h2>
    </div>
  </div>
</div>

Available syntaxes

To inject by tag:

div {
  bg-red-500

  h1 {
    text-gray-200
  }
}

To inject by class:

.hero {
  bg-red-500

  h1 {
    text-gray-200
  }
}

On inject by class expected classes are saved, but are sent to the beginning of the class list. It is understood, in this case, that the expected classes cannot overlap with other classes and variants of Tailwind Factory.

To inject by id:

#hero {
  bg-red-500

  h1 {
    text-gray-200
  }
}

To inject into multiple:

#hero, section, header, .title  {
  bg-red-500

  h1 {
    text-gray-200
  }
}

To inject only in the first group of children inside the component (support multiple syntax):

> div {
  bg-red-500

  h1 {
    text-gray-200
  }

  > .main, input {
    rounded-md
  }
}

Unavailable syntaxes

First, this deep class approach is not the same as defining classes in a typical style file! Some things like checking states is not supported. Example with :hover:

//not work!
div:hover {
  h2 {
    text-red-500
  }
}

This happens because Tailwind Factory only works with class management! You can get around this by defining your classes in a styling file.

Tailwind Group

In some cases, a group in Tailwind is the sufficient to set up a hover:

//work!
div {
  group
  hover:bg-gray-500
  h2 {
    group-hover:text-red-500
  }
}

In other cases you may prefer to use external classes:

//style.scss
//With Tailwind (you can use the common CSS too)
.custom-class:hover {
  h2 {
    @apply
      text-red-500;
  }
}
//work too!
div {
  hover:bg-gray-500
  custom-class
}

Using with variants

The variants support deep classes:

const Container = tf(
  "div",
  `
  bg-lime-200
  w-4
`, {
  variants: {
    italic: {
      true: `
        h1, h2, h3 {
          italic
        }

        a {
          no-underline
        }
      `,
      false: `
        h2 {
          underline
        }
      `
    }
  },
  defaultVariants: {
    italic: false
  }
});

You can extends too:

const Hero = Container.extends(null, `
  h1 {
    text-9xl
  }
`);

How it works

Tailwind Factory just arranges the classes within the variants according to the properties passed for the component. Tailwind does the rest, so I think you'll have no problem using other forms of styling based on class definitions. Like traditional CSS, Sass, or CSS Modules.

Come to think of it, maybe the name should be Style Factory or Class Factory. But now it's too late... I will keep the name.

Note: Classes are formatted before being passed to components. Reducing the number of spaces between classes to one.

Classes Priority

  1. Inline Classes
  2. Factory Variants
  3. Extended Factory Variants
  4. Factory Styles
  5. Extended Factory Styles
  6. Inline Saved Classes
    • Inline Classes used in Deep Classes

Snippets

Tailwind Factory has an official extension that accompanies some snippets. See in: Tailwind Factory Extension

List of Snippets:

Documented version: 0.1.0

tfi: Import Tailwind Factory and create a new factory component

import { tf } from "tailwind-factory";

export const Container = tf("div", `
  
`, {
  variants: {},
  defaultVariants: {}
});

tfc: Create a new factory component

export const NewComponent = tf("div", `
  
`, {
  variants: {},
  defaultVariants: {}
});

tfe: Create a new extended factory component

export const NewComponent = Parent.extends(ParentComponent, `
  
`, {
  variants: {},
  defaultVariants: {}
});

Bug Fix

First, sorry for the inconvenience.

There was a bug that I hadn't noticed that affected the rendering of elements that had a string as a child. It was happening because React passed the element's children as an object, and back in the tests that didn't happen with strings! I discovered, then, the existence of a function that converts the children to the expected format, the: React.Children.toArray.

Roadmap

  • Generate styles to provide further support and optimization (with support for SSR).

    Unfortunately I will have to change a lot of things about Deep Classes, but this may be essential so that I can expand the functionality of Deep Classes.

    I will try to maintain the independence of tailwind and will not change the way other functionality not linked to Deep Classes works. It will take a lot longer to develop, so be patient!