tailwind-factory
v2.3.7
Published
A lib to create and extends React components like stitches using Tailwind classes!
Downloads
32
Maintainers
Readme
A lib to create and extends React components defining variants like Stitches and using Tailwind classes!
Summary
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
aresaved
, but are sent to the beginning of the class list. It is understood, in this case, that theexpected 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 withinvariants
. I even tried to make the Tailwind Factory style syntax closer toCSS
syntax and not betoo
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
- Inline Classes
- Factory Variants
- Extended Factory Variants
- Factory Styles
- Extended Factory Styles
- 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
: #91c26eentity.factory.symbol
: #abb3c0entity.factory.style.internal.class
: #d7c075entity.factory.style.html.tag
: #da6f77entity.factory.style.pseudo
: #ba7de6 / italicentity.factory.style.rule
: #c16bff / italic / boldentity.factory.style.internal.id
: #7d8be6entity.factory.style.number
: #efba89entity.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: {}
});