Skip to content

Commit

Permalink
chore: update package name and start from scratch documentation
Browse files Browse the repository at this point in the history
  • Loading branch information
flozero committed Jun 17, 2024
1 parent fd8edae commit 3f8115d
Show file tree
Hide file tree
Showing 28 changed files with 295 additions and 272 deletions.
2 changes: 1 addition & 1 deletion .github/scripts/release.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ const headerMatchRegex = /^##[^\S\r\n]+v(\d+\.\d+\.\d+)/gm;
* ****************************************************************************************************************** */
const CHANGELOG_PATH = path.resolve(
__dirname,
"../../packages/tailwind-classes-authority/CHANGELOG.md"
"../../packages/@busbud/tailwind-buddy/CHANGELOG.md"
);

function extractLatestReleaseLog() {
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/run-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ jobs:
run: npm i -g pnpm && pnpm i

- name: Run build
run: pnpm tca:build
run: pnpm tailwindbuddy:build

- name: Run test
run: pnpm tca:test
run: pnpm tailwindbuddy:test
316 changes: 145 additions & 171 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,222 +1,196 @@
# Tailwind classes variants authority 🎨
# Tailwind Buddy: Your Friendly Helper for Composing Tailwind Classes 🎨

You are looking for the [documentation go here](#documentation)
You are at the good place if you are looking for:
- The Fastest tailwind variant utilty that you can ask [our benchmarks](./packages/benchmark/README.md) or [see here](#benchmarks)
- Building tailwind variant components powered by tailwind
- Wants to handle variants props responsivness
- Ease of use (We try to find the good balance between developer experience who will build the library and the ones who will use your ui library)
- Compound variants that works with responsivness (Compound can base their classes overrides based on variants values but also other random props you give)
- Framework agnostic (Even if we have build the library base on react it will works nicely with other frameworks)
- SSR friendly (we do generate the necessary classes you need both responsive and not)
- Want to be able to use slots (Sometimes you want to be able to cut down your component into smaller piece but want to use the same props)
- A package actively maintained as its used by our company to power our new design system

## What it is ?
This library is opinonated inspired by [CVA](https://cva.style/docs) and [tailwind-variants](https://github.com/nextui-org/tailwind-variants).

This library is highly opinonated inspired on the object definition by [tailwind-variants](https://github.com/nextui-org/tailwind-variants).
## Minimum setup (no responsive values, no compounds)

## What this library is trying to solve ?
### Vscode settings for tailwind auto complete

First we wanted a solution that can be framework agnostic. To achieve that we need something that build string and receive parameters that do not care if you are from react, vue, vanilla JS.
We do have experimented few path to have auto complete and we still looking for a better way. But for now we suggest you to setup your vscode settings like this.

We found solution that was really close to our needs. [tailwind-variants](https://github.com/nextui-org/tailwind-variants)
`.vscode/settings.json`

The promise of this library was great:
- Responsive props
- Having compound variant that override classes.
- Typescript auto complete is pretty good overhall
- Not so much configurations to work with it

But the reality is different:
- The package is not super actively maintained.
- Dealing with some complex use case seems inconsistent or not working like:
- We can't mix regular props with, variant props, with responsive variant props. Basically
- working with responsive props and compoundsVariants are just not working
- composing configuration seems to broke typings
- when you make responsive variants it makes all of them responsive
- can't interact with other kinds of props inside the compounds conditions

## How do tried to solve this

First we focus on that every features is working:
- variants
- responsive variants
- slots
- make it fast
- compound variants
- compounds variants + responsive variants
- compounds can interact with kind of props than variants values

Worth mentionning that we still do not support extends. [Check the github discussion](https://github.com/flozero/tailwind-classes-authority/discussions/1) to participate how you would expect to have it working.

## What would need to be improved

We will create few github issues with good first issue label.

## Documentation

If you need like a cookbook Have a look at:
- [Our tests](packages/core/tests/configs) or [Our utils tests](packages/core/tests/utils/)
- [The test configs that our tests are usings](packages/core/tests/setup)

### Installation

```
pnpm add tailwind-classes-authority
```

You may need to add `tailwind-merge` if so.

```
pnpm add tailwind-merge
```json
{
"editor.quickSuggestions": {
"strings": "on"
},
"css.validate": false,
"editor.inlineSuggest.enabled": true,
"tailwindCSS.classAttributes": [
"class",
"className",
".*Styles.",
".*Classes."
],
"tailwindCSS.experimental.classRegex": [
"@tw\\s\\*\/\\s+[\"'`]([^\"'`]*)"
]
}
```

### Setup your tailwind types and screens
The important part is `tailwindCSS.experimental.classRegex` that will autocomplete the string when you put `/** @tw */` in front. You will see in the label example how we do use it.

If you are extending / overriding tailwind screens config

You will need to setup as we have done [inside our tailwind.config test](./packages/ui/tailwind.config.ts)
### Installation

```js
export type Screens = "lg" | "xl"
export const screens: Screens[] = ["lg", "xl"]
```

We will need that later to handle responsive props and having a single source of truth.

### Build your first component

We will use our [label component](./packages/ui/src/components/Label/Label.types.ts) as reference.

Generally you will want to start by creating the types of your component

#### We will need to define the slots we will find. By default you will always have a root slot.

```js
export type Slots = "root"
pnpm add @busbud/tailwind-buddy
```

#### Define what variants we will find
### Create your first component

```js
export interface Variants {
fontWeight?: string
size?: ResponsiveVariants<"small" | "large", Screens>
}
```
Component typing:

You will see that a variant can be responsive or not. This will impact how we will use props on the component. You can omit the Screen generic parameter if you didn't override default tailwind screens.

#### Defining component Props

```js
export interface LabelProps
extends React.HTMLAttributes<HTMLBaseElement>,
Variants {
/** The component used for the root node. Either a string to use a HTML element or a component. */
``` tsx
export interface LabelBaseProps
extends React.HTMLAttributes<HTMLBaseElement> {
as?: React.ElementType;
disabled?: boolean;
}
```

#### Creating variant configuration type

```js
export type VariantConfiguration = TCA_VARIANT_DEFINITION<Slots, Variants, LabelProps>
```

This config will help us type our variantConfiguration.

Basically Slots will tell the object what we will need to find as slots.
Component variant definition

What variants need to be defined.
``` tsx
import { compose } from "@busbud/tailwind-buddy"
import { LabelBaseProps } from "./Label.types"
import type { VariantsProps } from "@busbud/tailwind-buddy"

What other props compounds will use to create conditions here we put any props the component can receive but you can restrict it.

### Defining variant configuration

```js
export const labelVariantsConfigurations: VariantConfiguration = {
"slots": {
"root": /** @tw */ "text-blue-500"
export const labelVariants = compose({
"slots": { // you will always have at least the root slot to define
"root": /** @tw */ "text-blue-500" // We do use /** @tw */ to be able to have auto complete from tailwind
},
"variants": {
"size": {
"default": "small",
"values": {
"small": /** @tw */ "text-sm",
"large": /** @tw */ "text-7xl"
}
"small": /** @tw */ "text-xs",
"large": /** @tw */ "text-7xl"
},
"fontWeight": {
"default": "xxl",
"values": {
xxl: {
"root": /** @tw */ "font-extrabold"
}
xxl: {
"root": /** @tw */ "font-extrabold"
}
}
},
"defaultVariants": { // all variants should have a default values
"size": "small",
"fontWeight": "xxl"
}
}
})<LabelBaseProps>()


export const labelVariants = tca(labelVariantsConfigurations)
export type LabelProps = VariantsProps<typeof labelVariants>
```
We want to have auto complete from tailwind. We tried other solution but it was crashing our IDE. We decided to use something that works until we find something better using `/** @tw */`
And that's it. The key take aways here are:
- You need at least on slot root
- You need to define every default variants
- We do have auto complete using special comments `/** @tw */`
If you want to have this update your vscode settings with this.
If you want to benefits to the maximum we suggest you to use [tailwind merge](#adding-tailwind-merge-to-minify-the-string-generated)
```json
{
"editor.quickSuggestions": {
"strings": "on"
},
"css.validate": false,
"editor.inlineSuggest.enabled": true,
"tailwindCSS.classAttributes": [
"class",
"className",
".*Styles.",
".*Classes."
],
"tailwindCSS.experimental.classRegex": [
"@tw\\s\\*\/\\s+[\"'`]([^\"'`]*)"
]
}
```
## Adding tailwind merge to minify the string generated
Our package do not take care of giving you the smaller possible string. As `tailwind merge` is probably the best one you can find to handle this. We didn't wanted to create another one.
We choose to not expose it from our package in case as for us you also wants to use tailwind merge outside of the design system itself but wants to control one version of it. With that you don't have to manage two versions of the same package.
Key take aways:
- As you will see with typescript. You will have to define all the slots.
- You will also have to define the default values of variants
- the values from variants can received a string that means all slots will receive this values otherwise define the slots
- for compounds same rule expect its for class.
### Install tailwind merge
### Apply it inside the component
`pnpm add tailwind-merge`
Now what you have everything defined. lets check [our component](./packages/ui/src/components/Label/Label.tsx).
now you can use it in two ways
```js
const { root } = labelVariants();
1) without updating tailwind merge
```tsx
import React from "react";

import { PropsWithChildren } from "react";
import { LabelProps, labelVariants } from "./Label.variants";
import { twMerge } from "tailwind-merge"

export const Label: React.FC<PropsWithChildren<LabelProps>> = ({
as: Component = "span",
className,
children,
fontWeight,
size,
disabled,
...restProps
}) => {
const { root } = labelVariants

return (
<Component
className={twMerge(root({
fontWeight,
size,
className,
disabled
}))}
{...restProps}
>
{children}
</Component>
);
};
```

You will apply elements like this
2) By extending the default tailwind merge

```tsx
import {extendTailwindMerge} from "tailwind-merge";

```js
className={root({
fontWeight,
size,
}, {
className,
disabled
})}
export const COMMON_UNITS = ["small", "medium", "large"];

export const twMergeConfig = {
theme: {
opacity: ["disabled"],
spacing: [
"divider",
"unit",
],
borderWidth: COMMON_UNITS,
borderRadius: COMMON_UNITS,
},
classGroups: {
shadow: [{shadow: COMMON_UNITS}],
"font-size": [{text: ["tiny", ...COMMON_UNITS]}],
"bg-image": ["bg-stripe-gradient"],
"min-w": [
{
"min-w": ["unit", "unit-2", "unit-4", "unit-6", "unit-8", "unit-10", "unit-12", "unit-14"],
},
],
},
};
export const twMerge = extendTailwindMerge(twMergeConfig);
```

The first object will take variants values
Then use it as the first example but instead of importing from `tailwind-merge` you would import from this file.

## Working with slots

the second object will take any other props and also the `className`. The `className` will override everything else. Check [How override works](#how-override-works) if you want to understand a bit more.
## Working with compound variants

### How override works
## Working with responsive Variants

Override will works in this order:
- slots values
- variants values
- compound class when condition meet
- className override
## local development

Basically we put everything in a long string in this order and let tailwind-merge do its job.
## Contributing

### Simple slots Configuration
## Benchmarks

Check [this config](./packages/core/tests/setup/simple.ts)
TCA is our lib.

![](./packages/benchmark/benchmarks.png)
Loading

0 comments on commit 3f8115d

Please sign in to comment.