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

tailwind-factory

v2.3.7

Published

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

Downloads

6

Readme

image info

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

Summary

  1. Installation
    1. Run without plugin
      1. Advantages
      2. Disadvantages
    2. Tailwind configuration
    3. Plugin configuration
      1. Plugin options
      2. With Next
      3. With Webpack
  2. Basic usage
    1. Custom components
  3. Heritage
  4. Deep classes
    1. Available syntaxes
    2. Is it possible to use external classes?
    3. Tailwind group
    4. Using with variants
  5. Classes priority
  6. How it works
  7. Extension
    1. Color tokens
    2. Snippets

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

Run without plugin

You can run Tailwind Factory without its plugin. It's faster in many cases.

I will list here some advantages and disadvantages of running Tailwind Factory without the plugin:

Advantages

  • Extremely fastest in development (because it will not be depending on the library's own cache to be checking the style of unchanged files);
  • Fastest build (because you won't be using Babel);
  • Support external classes (which do not belong to Tailwind);
  • It doesn't generate a styling file (it doesn't actually need one);
  • Specific classes of Tailwind may work better (because I don't have conditions to go out checking class by class).

Disadvantages

  • Extremely limited Deep Classes support;
  • Styles generated within Deep Classes will only be applied to the parent component's children, not to other child components. It works on the childrens of a HTML tag;
  • Costs a little more memory on the Client-Side, mainly if you are using Deep Classes resources.

Tailwind configuration

Now you should install and configure Tailwind!

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(`...`);
],

In this case it is necessary to put a semicolon at the end of the function call. Using the snippets you will not suffer from this. It already puts the semicolon.

If you don't like the idea very much (what do you mean you didn't come from Java, just kidding) you can use the old regex from the library, but you'll have problems if you call a parenthesis inside the function if you do. Also, you will have problems with the colors highlight set by the plugin if you are using it.

Still, I'll leave it here:

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

Plugin configuration

Tailwind Factory has its own Babel plugin that is used to generate the styles that are already included with the library. To use it you will need to provide it in your babel configuration file:

//babel.config.js
module.exports = (api) => {
  //Can be true, but I haven't tested the effects.
  api.cache(false); 
  
  return {
    //...
    plugins: [
      //...,
      [
        "tailwind-factory/plugin",
        {
          logs: "normal",
          styles: {
            config: require("./tailwind.config"),
            outputPath: "src/styles/generated.css"
          },
        },
      ],
    ],
  };
};

If you want to pass your Tailwind configuration the babel file has to export a JavaScript module so that you can pass your configuration using require. You can also pass the configuration directly, but this will limit you further.

Plugin options

//types definition
export type PluginType = {
  preset?: "react"; //In case you need it someday
  logs?: "none" | "all" | "normal" | "debug" | "errors";
  styles?: {
    outputPath?: string;
    inputPath?: string; //Disabled
    config?: Promise<TailwindConfig | undefined>;
  };
};
  • logs - How the plugin should print the log, see the presets:

    • "none" - the plugin will not output or format any logs;
    • "all" - the plugin will output and format any logs, except debug logs;
    • "normal" - (default) the plugin will output and format some logs, except debug logs;
    • "debug" - the plugin will output and format some logs, including debug logs;
    • "errors" - the plugin will output and format only error logs.
  • styles.config - Tailwind config (default: {});

  • styles.outputPath - Path to put the generated styles (default: "src/styles/generated.css"). The file should be created before.

With Next

Edit your Next configuration file so it understands which files are important for the plugin:

//next.config.js
/* eslint-disable @typescript-eslint/no-var-requires */
const { nextWithFactory } = require("tailwind-factory");

module.exports = nextWithFactory({
  reactStrictMode: true,
  //...
});

Import the generated styles file and the Tailwind configuration into one of the first files to be called before rendering (the import has to come before any components created by the library).

Common example in Next:

//src/pages/_app.tsx
import type { AppProps } from "next/app";

import "../../tailwind.config";
import "../styles/generated.css";

export default function App({ Component, pageProps }: AppProps) {
  return <Component {...pageProps} />;
}

With Webpack

Edit your Webpack configuration file so it understands which files are important for the plugin:

//webpack.config.js
/* eslint-disable @typescript-eslint/no-var-requires */
const { webpackWithFactory } = require("tailwind-factory");

module.exports = webpackWithFactory({
  //...
});

Import the generated styles file and the Tailwind configuration into one of the first files to be called before rendering (the import has to come before any components created by the library).

import "../../tailwind.config";
import "../styles/generated.css";

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.

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
    }

    .test {
      text-6xl
    }
  }

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

Plugin generated styles example:

.factory__52dad3ab6fb6 {
  width: 1rem;
}
.factory__52dad3ab6fb6 {
  --tw-bg-opacity: 1;
  background-color: rgb(217 249 157/var(--tw-bg-opacity));
}
.factory__52dad3ab6fb6 h2 {
  font-style: italic;
}
.factory__52dad3ab6fb6 div {
  height: 0.75rem;
}
.factory__52dad3ab6fb6 > div {
  display: flex;
}
.factory__52dad3ab6fb6 > div {
  flex-direction: column;
}
.factory__52dad3ab6fb6 > div {
  --tw-bg-opacity: 1;
  background-color: rgb(191 219 254/var(--tw-bg-opacity));
}
.factory__52dad3ab6fb6 > div > h2 {
  font-weight: 700;
}
.factory__52dad3ab6fb6 > div .test {
  font-size: 3.75rem;
  line-height: 1;
}
.factory__52dad3ab6fb6 > h2, .factory__52dad3ab6fb6 > h1, .factory__52dad3ab6fb6 p {
  --tw-text-opacity: 1;
  color: rgb(248 113 113/var(--tw-text-opacity));
}

Component structure example:

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

Output example (with plugin):

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

Available syntaxes

Know that spaces between values ​​count here. The way Tailwind Factory separates classes is very much related to the use of commas and the space between classes. I don't mean the tabs, but it's good to pay attention to the details.

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 run without plugin the 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
  }

  //Need to repeat the '>' to apply in all
  //Repeat is unnecessary if you are not using the plugin
  > .main, > input {
    rounded-md
  }

  //Just #all receive '>'
  > #alt, textarea {
    rounded-lg
  }
}

Inject with pseudo classes (need plugin):

:hover {
  p:first-of-type {
    text-sm
  }
}

div:focus {
  rounded-md
  border-2
}

This first focus is not applied to everyone, but to the component created by the function.

Inject into all (need plugin):

*:focus {
  p:first-of-type {
    text-sm
  }
}

Inject with media query (need plugin):

//work
md:rounded-md
p:first-of-type {
  md:text-red-500
}

//work
@media(min-width:900px) {
  w-6
  p:first-of-type {
    text-red-400

    @media(min-width:100px) {
      text-blue-500
    }
  }
}

Inject with arbitrary value:

max-w-[30rem]
text-[#5a74db]

Is it possible to use external classes?

Tailwind Factory with plugin does NOT support external classes (not part of Tailwind) in function call. However, you can still call a class by passing it directly to the component:

<div className="custom-class"/>

The idea is that you don't need to use this, since within the function call itself you can declare a class even within variants. I even tried to make the Tailwind Factory style syntax closer to CSS syntax and not be too limited.

Tailwind Group

In some cases, a group in Tailwind is the sufficient to set up a hover (I consider this a good practice):

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

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
  }
`);

Classes Priority

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

How it works

Tailwind Factory (without the plugin) just arranges the classes within the variants according to the properties passed for the component. The plugin does the rest, checks the changed file, gets the classes, transforms the classes using Tailwind, preprocesses the styles with Postcss and Sass, puts the processed styles into the cache, loads the cache (which contains the other styles) and generates the file with all the styles.

The cache is tied to the component's style parameter and its variants, not the deep classes within the component's styles.

At the moment it is inevitable that, if you change any parameter of the function that brings style, a flash will occur in its rendering. There is no way for this to happen in production, because it is not possible to pass parameters within the styles defined in the function (this will probably cause an error in the plugin). The variants serve to reduce this limitation.

This flash happens because when a change is made the name of the class linked to the style parameter or component variant will change. So, since it's faster to change the class name than to load the change into the style sheet, the components that haven't changed will keep their styles (since their class name won't change) while the one with the changed class will wait it will be generated by the plugin and loaded by the browser with a class that doesn't exist yet.

Generating style names earlier was actually a way around Babel's limitations. It was not designed/structured to support asynchronous functions because that reduces performance and slows down rendering. And, in fact, the Tailwind Factory plugin is very heavy and needs your time to generate the component styles, this is a limitation of the library itself.

I am providing only what I can and with sincerely.

Extension

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

Documented version: 1.2.0

Warning: it is important that you put the semicolon at the end of the function!

Color tokens

All detected errors identifying classes fixed

  • entity.factory.style: #91c26e
  • entity.factory.symbol: #abb3c0
  • entity.factory.style.internal.class: #d7c075
  • entity.factory.style.html.tag: #da6f77
  • entity.factory.style.pseudo: #ba7de6 / italic
  • entity.factory.style.rule: #c16bff / italic / bold
  • entity.factory.style.internal.id: #7d8be6
  • entity.factory.style.number: #efba89
  • entity.factory.style.unit: #eda460 / italic

You can change the colors in your VSCode configuration:

{
  "editor.tokenColorCustomizations": {
    "textMateRules": [
      {
        "scope": "entity.factory.style",
        "name": "Factory Style",
        "settings": {
          "foreground": "#91c26e"
        }
      }
    ]
  }
}

Snippets

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: {}
});